Build a Movie Shorts Editor Web App
Build a local web app that turns movie clips into social media shorts.
Introduction
30 Second Summary
You just watched an incredible movie scene and you want to share it on TikTok or Instagram Reels. But raw clips are too long, the wrong shape, and have no text hook to grab attention. You need a quick way to turn any clip into a polished short.
In this project, you will build a local Movie Shorts Editor web app that trims video clips, applies social media format presets, adds text overlays, and exports ready-to-post MP4 files.
What You'll Build
You will open your browser, drop in a movie clip, set your trim points, pick TikTok or YouTube Shorts as your format, type a text overlay, and hit Export. Seconds later you download a finished short ready to post.
By the end of this project, you'll have:
- A working full-stack web app where you upload any movie clip and instantly see its duration and resolution in the browser.
- A video editing pipeline that trims clips to any section, applies the correct aspect ratio for TikTok, YouTube Shorts, or Instagram Reels, and burns custom text directly onto the video.
- A polished interface with a video preview player, export progress feedback, and a one-click download for your finished short.
- Secret Mission: Add fade transitions and background music mixing to produce cinematic-quality shorts.
Are there any prerequisites?
None. This guide walks you through installing every tool from scratch, including Node.js and FFmpeg. You will use GitHub Copilot to generate most of the code through vibe coding prompts, so no prior coding experience is required.
Before We Start
Before We Start...
Before we dive in, let's lock in what you're about to build and why it matters. You're going to create a local Movie Shorts Editor web app that turns movie clips into polished social media shorts. Think about what kind of content you want to create. Do you have a favourite movie scene you want to share? A trailer you want to clip for TikTok?
Install Node.js and FFmpeg
Your Movie Shorts Editor needs two core pieces of software to run: a JavaScript runtime that powers the backend server, and a video processing toolkit that handles trimming, resizing, and encoding.
Node.js is the runtime that lets JavaScript run outside the browser. FFmpeg is the industry-standard command-line tool for manipulating video and audio files. In this step, you'll install both and create your project folder.
In this step, get ready to:
- Install Node.js on your machine and verify it runs.
- Install FFmpeg so your server can process video files.
- Create and initialize your project folder with a package.json file.
Install Node.js
Node.js is what lets you run JavaScript on your computer instead of only inside a browser. Your Express.js server in the next step depends on it.
- Open your browser and go to https://nodejs.org/en/download.
- Download the Windows 64-bit installer (.msi) for the LTS version (v24.18.0).
- Run the installer once it finishes downloading.
- Accept the default settings on each screen and click through to the end.
What does the installer do?
The Node.js MSI installer places the node and npm commands on your system PATH automatically. This means you can run them from any terminal window without extra configuration.
Now confirm Node.js is installed correctly.
- Switch back to VS Code.
- Open a new terminal by clicking Terminal in the top menu bar, then New Terminal.
- Check your Node.js version by running:
node --version
What do you expect to see printed? Take a guess before looking at the output.
✔️ I see v24.x.x
You should see something like v24.18.0 printed in your terminal. Node.js is installed and ready to go.
ⓧ I see an older version
If you see a version number lower than v24 (for example v18.x.x or v20.x.x), you have an outdated installation. The Node.js Windows installer replaces your existing version automatically.
- Go back to https://nodejs.org/en/download and download the v24.18.0 LTS Windows installer.
- Run the installer and accept defaults. It overwrites the older version.
- Close your terminal in VS Code and open a new one (Terminal > New Terminal).
- Run node --version again. You should now see v24.18.0.
ⓧ Command not found
If you see an error like 'node' is not recognized, Node.js was not added to your PATH during install.
- Go to https://nodejs.org/en/download and download the v24.18.0 LTS Windows 64-bit .msi installer.
- Run the installer and accept all defaults.
- Close VS Code completely and reopen it.
- Open a new terminal (Terminal > New Terminal) and run node --version again.
Closing and reopening VS Code forces it to reload the system PATH. You should now see v24.18.0.
Install FFmpeg
FFmpeg is the tool that actually processes your videos. It handles trimming, resizing, adding text, encoding, and everything else under the hood.
- In your VS Code terminal, install FFmpeg by running:
winget install "FFmpeg (Essentials Build)"
What does this command do?
Winget is the built-in Windows package manager. This command downloads the FFmpeg Essentials Build (which includes all the video codecs you need) and adds it to your system PATH automatically.
- Close your terminal and open a fresh one (Terminal > New Terminal). This reloads the updated PATH so your terminal can find FFmpeg.
- Verify FFmpeg is installed by running:
ffmpeg -version
✔️ I see version information
You should see output that starts with ffmpeg version followed by a version number and build details. FFmpeg is ready.
ⓧ Command not found or error
If you see 'ffmpeg' is not recognized, the PATH update has not taken effect yet.
- Close VS Code completely and reopen it.
- Open a new terminal and run ffmpeg -version again.
If it still fails, restart your computer to force a full PATH reload, then try once more.
Set up your project folder
Now that both tools are installed, you need a dedicated folder to hold your project files.
- In VS Code, click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop in the folder dialog.
- Click the New Folder button and name it movie-shorts-editor.
- Select the new movie-shorts-editor folder and click Select Folder.
VS Code now shows movie-shorts-editor in the Explorer sidebar on the left. The folder is empty for now.
- Open a terminal inside this folder by clicking Terminal > New Terminal.
- Initialize your project by running:
npm init -y
What does npm init -y do?
This command creates a package.json file, which is the manifest that tracks your project's name, version, and (soon) its dependencies. The -y flag accepts all defaults so you don't have to answer questions.
- Confirm the file was created by running:
ls
You should see package.json listed in the output.
Don't see package.json?
- Make sure your terminal prompt shows movie-shorts-editor as the current directory. If it shows a different path, you may have opened the wrong folder.
- Try running npm init -y again. If it still fails, confirm Node.js is installed by running node --version.
Still stuck? Help me troubleshoot why npm init isn't creating my package.json file.
✔️ Awesome, I've got everything!
Great. Double check you can see package.json in the VS Code Explorer sidebar on the left. Your project folder is ready.
ⓧ I'd like to double check the full code
Your package.json should look like this:
{
"name": "movie-shorts-editor",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
Your machine is ready to build. Next up, you'll create a working server that accepts video uploads and reads their details instantly.
Build a Basic Video Info Server
Before you can trim videos or add text overlays, you need a server that can receive a video file and tell you what is in it.
In this step, you will build your first working endpoint. The moment you upload a clip, the server reads its metadata and sends back the duration and resolution as JSON.
In this step, get ready to:
- Install Express, Multer, and simple-ffmpegjs as project dependencies.
- Create an Express server with a video upload endpoint using GitHub Copilot.
- Build a simple upload page and test the full flow in your browser.
Install dependencies and prompt Copilot for the server
Your server needs three packages. Express handles web requests. Multer handles file uploads. simple-ffmpegjs provides the video processing API that talks to FFmpeg under the hood.
- Install all three packages by running this command in your terminal:
npm install express multer simple-ffmpegjs
You should see output confirming the packages were added, and a node_modules folder appears in your project.
Seeing permission errors?
Make sure you are running the terminal inside the movie-shorts-editor folder. Check by running cd and confirming the path ends with movie-shorts-editor.
Help me fix npm install errors in my project
Now you need to tell Node.js to use ES modules. This lets you use import syntax instead of the older require style.
- Open package.json in the VS Code editor.
- Find the line that says "description": "", and add this line directly above it:
"type": "module",
- While you are in package.json, also change the "main" value from "index.js" to "server.js".
- Save package.json.
Why add type: module?
Node.js defaults to CommonJS (require()) syntax. Adding "type": "module" switches your project to ES modules so you can use modern import statements. GitHub Copilot generates better code with ES module syntax.
Time to create the server itself. You will give GitHub Copilot a prompt describing what you need, and it will generate the code for you.
- Create a new file called server.js in the root of your movie-shorts-editor folder.
- Open GitHub Copilot Chat by pressing Ctrl+Alt+I.
- Copy and paste this prompt into the chat:
Create an Express.js server in server.js that:
1. Serves static files from a 'public' folder
2. Has a POST /upload endpoint that accepts a video file using multer (save to 'uploads' folder)
3. After upload, uses the probe() static method from simple-ffmpegjs to get the video duration and resolution
4. Returns the video info as JSON: { filename, duration, width, height }
5. Listens on port 3000
Use import syntax (ES modules). Add "type": "module" note at the top as a comment reminder.
- Press Enter to send the prompt.
- Review the generated code, then click Insert at Cursor (or copy and paste it into your empty server.js file).
- Save server.js.
What should the generated code include?
Look for these key elements in the code Copilot generated:
- Import statements for express, multer, path, fs, and simple-ffmpegjs at the top.
- A multer storage configuration that saves files to an uploads directory.
- A POST /upload route that calls SIMPLEFFMPEG.probe() and returns JSON with filename, duration, width, and height.
- An app.listen(3000) call at the bottom.
Let's confirm the server starts before moving on.
- Start the server by running:
node server.js
You should see Movie Shorts Editor running at http://localhost:3000 printed in the terminal.
- Press Ctrl+C to stop the server. You will restart it after creating the upload page.
Server won't start?
If you see SyntaxError: Cannot use import statement outside a module, you are missing "type": "module" in your package.json. Open it and add that line after the version field.
If you see a "module not found" error for simple-ffmpegjs, make sure npm install completed successfully in the earlier step.
Help me fix my server.js startup error
Create the upload page
The server is working, but there is no page to interact with yet. You need a simple HTML page with a file picker that sends the video to your upload endpoint and displays the result.
- Create a folder called public inside your movie-shorts-editor folder by running:
mkdir public
- Create a new file called index.html inside the public folder.
- Open GitHub Copilot Chat (Ctrl+Alt+I) and paste this prompt:
Create an HTML page at public/index.html with:
1. A file input that accepts video files
2. When a file is selected, send it via fetch() as FormData to POST /upload
3. Display the returned JSON (filename, duration, width, height) on the page
Keep it simple - just a heading, the file input, and a div to show results.
- Accept the generated code and paste it into public/index.html.
- Save public/index.html.
What does this page do?
The page has a file picker (<input type="file">) that listens for a file selection. When you choose a video, JavaScript wraps it in a FormData object and sends it to your /upload endpoint.
The server processes the file, extracts metadata with SIMPLEFFMPEG.probe(), and sends back JSON. The page then displays that JSON so you can see the video's duration and resolution.
✔️ Awesome, I've got everything!
Great. Make sure both server.js and public/index.html are saved.
ⓧ I'd like to double check the full code
Here is what server.js should look like. Compare your file against these two sections.
The imports and setup section:
import express from "express";
import multer from "multer";
import path from "path";
import fs from "fs";
import { fileURLToPath } from "url";
import SIMPLEFFMPEG from "simple-ffmpegjs";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const app = express();
app.use(express.json());
app.use(express.static(path.join(__dirname, "public")));
const uploadsDir = path.join(__dirname, "uploads");
if (!fs.existsSync(uploadsDir)) fs.mkdirSync(uploadsDir);
const storage = multer.diskStorage({
destination: (req, file, cb) => cb(null, uploadsDir),
filename: (req, file, cb) => {
const uniqueName = Date.now() + "-" + file.originalname;
cb(null, uniqueName);
},
});
const upload = multer({ storage });
The upload route and server listener:
app.post("/upload", upload.single("video"), async (req, res) => {
try {
const filePath = req.file.path;
const info = await SIMPLEFFMPEG.probe(filePath);
res.json({
filename: req.file.filename,
duration: info.duration,
width: info.width,
height: info.height,
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(3000, () => {
console.log("Movie Shorts Editor running at http://localhost:3000");
});
And here is what public/index.html should look like:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Movie Shorts Editor</title>
</head>
<body>
<h1>Movie Shorts Editor</h1>
<input type="file" id="videoFile" accept="video/*" />
<div id="info"></div>
<script>
document.getElementById("videoFile").addEventListener("change", async (e) => {
const file = e.target.files[0];
if (!file) return;
const formData = new FormData();
formData.append("video", file);
const response = await fetch("/upload", { method: "POST", body: formData });
const data = await response.json();
document.getElementById("info").textContent = JSON.stringify(data);
});
</script>
</body>
</html>
Finally, confirm your package.json has "type": "module" and "main": "server.js":
{
"name": "movie-shorts-editor",
"version": "1.0.0",
"type": "module",
"description": "A local web app to create social media shorts from movie clips",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^4.21.2",
"multer": "^1.4.5-lts.2",
"simple-ffmpegjs": "^0.6.0"
}
}
Run and test the full flow
Everything is in place. Time to see it work end to end.
- Start the server by running:
node server.js
- Open your browser and navigate to http://localhost:3000.
You should see a page with the heading "Movie Shorts Editor" and a file picker button.
Before you upload a file, what do you think the server will send back? Think about which four pieces of information the /upload endpoint returns.
- Click the file picker and select any short video file from your computer (a phone recording, a downloaded clip, anything works).
After a moment, the page displays a JSON object with four fields: filename, duration, width, and height. You can see exactly how long the clip is and its resolution.
Page is blank or upload fails?
If the page is blank, check that your public/index.html file is inside a folder called public (not in the project root). The server only serves files from that folder.
If you see an error after uploading, make sure FFmpeg is installed and available in your PATH. Open a new terminal and run ffmpeg -version to confirm.
Help me debug my video upload server
Your server can now receive video files and read their metadata. Next up, you will add the ability to actually edit those videos by trimming them and choosing a social media format.
Add Trimming and Format Presets
Your app can receive a video file and tell you its duration and resolution. That is useful information, but you cannot actually edit anything yet.
If you tried to post a full movie clip to TikTok, it would be too long and the wrong aspect ratio. You need trimming to cut the clip down and format presets to reshape it for each platform. That is what this step builds.
In this step, get ready to:
- Add a POST /export endpoint that trims video and applies a platform preset.
- Update the frontend with trim controls and a format dropdown.
- Add a download endpoint and test the full trim-and-export flow.
Add the export endpoint
The export endpoint is where the real video processing happens. It receives trim times and a platform choice, then uses simple-ffmpegjs to produce a new file.
- Open server.js in VS Code.
- Open GitHub Copilot Chat by pressing Ctrl+Shift+I.
- Give Copilot this prompt:
Add a POST /export endpoint to server.js that:
1. Accepts JSON body with: { filename, startTime, endTime, preset }
2. The preset can be 'tiktok', 'youtube', or 'instagram'
3. Uses simple-ffmpegjs to create a project with the correct preset (tiktok, youtube, or instagram)
4. Loads the uploaded video file as a single clip, trimmed to startTime/endTime
5. Exports to an 'output' folder with a timestamped filename
6. Returns { success: true, outputFile: filename } as JSON
Create the output folder if it doesn't exist.
- Review the generated code and click Accept to apply it to server.js.
- Save server.js.
What does this endpoint do?
The /export endpoint receives the uploaded filename, a start time, an end time, and a platform preset. It creates a new SIMPLEFFMPEG project with the chosen preset, loads the video clip trimmed to the specified range, and exports the result to an output folder.
The platform presets (tiktok, youtube, instagram) handle aspect ratio and resolution automatically. You do not need to manually calculate dimensions.
Copilot generated something different?
Make sure your prompt includes the word simple-ffmpegjs so Copilot uses the correct library. If Copilot uses fluent-ffmpeg or raw FFmpeg commands instead, reject the suggestion and re-prompt with the library name explicitly.
Also confirm that server.js now includes a const outputDir line and a fs.mkdirSync call to create the output folder.
Need more help?
Update the frontend with trim controls
The backend can now export trimmed video, but the browser has no way to send trim times or a platform choice. You need form controls that collect this information and send it to /export.
- Open public/index.html in VS Code.
- In Copilot Chat, give it this prompt:
Update this HTML page to add:
1. After video info is displayed, show input fields for Start Time (seconds) and End Time (seconds), pre-filled with 0 and the video duration
2. A dropdown to select format: TikTok 9:16, YouTube Shorts 9:16, Instagram Reels 9:16
3. An Export button that sends a POST request to /export with the filename, startTime, endTime, and preset
4. Show a loading spinner during export, then a download link when complete
Map the dropdown values to: tiktok, youtube, instagram
- Review and accept the generated code.
- Save public/index.html.
What did this prompt add?
The updated page now has an edit section that appears after you upload a video. It includes number inputs for start and end time, a dropdown with three platform presets, and an Export button that sends all the values to your /export endpoint.
The dropdown maps display labels like "TikTok 9:16" to the values tiktok, youtube, and instagram that the server expects.
Let's verify the new UI is working before adding the final piece.
- If your server is still running from before, press Ctrl+C in the terminal to stop it.
- Start the server by running:
node server.js
- Open http://localhost:3000 in your browser.
- Upload any short video file.
After the upload completes, you should see the video info (duration and resolution) appear along with Start Time and End Time inputs, a platform dropdown, and an Export button.
Don't see the trim controls?
Check that public/index.html has an element with id="editSection" and that your JavaScript sets its display to block after a successful upload.
If the page is completely blank, open DevTools (F12) and check the Console tab for JavaScript errors.
Help me debug this.
Add the download endpoint and test the full flow
The export endpoint saves a new MP4 file to the output folder, but the browser needs a way to download it. A simple GET endpoint that serves files from that folder completes the chain.
- Press Ctrl+C in the terminal to stop the server.
- In Copilot Chat, prompt it to add the download route:
Add a GET /download/:filename endpoint to server.js that serves files from the output folder. Use res.download() to send the file. Return a 404 JSON error if the file doesn't exist.
- Accept the generated code and save server.js.
What does the download endpoint do?
When the export finishes, the frontend creates a link pointing to /download/short-1234567890.mp4. This new endpoint receives that request, finds the file in the output folder, and sends it back to the browser as a download.
Now test the complete trim-and-export flow.
- Start the server again by running:
node server.js
- Open http://localhost:3000 in your browser.
- Upload a short video clip.
- Set the Start Time to 2 and the End Time to 8.
- Select TikTok 9:16 from the dropdown.
Before you click Export, what do you expect to happen? Think about what a 6-second, portrait-oriented clip would look like compared to the original.
- Click Export Short.
After a few seconds, a download link appears. Click it and the browser downloads a trimmed MP4 file. The clip is 6 seconds long and in portrait orientation.
Export failed or no download link?
If you see an error in the browser, open the terminal where node server.js is running and check for error messages. Common causes:
- FFmpeg not found: make sure you restarted your terminal after installing FFmpeg so the PATH is updated.
- The output folder does not exist: check that your server code creates it with fs.mkdirSync on startup.
- Start or end time is out of range: make sure the end time is less than or equal to the video's actual duration.
Help me debug my export.
✔️ Awesome, I've got everything!
Great. Make sure both server.js and public/index.html are saved.
ⓧ I'd like to double check the full code
Your server.js should contain these sections in order:
- Imports (express, multer, path, fs, fileURLToPath, SIMPLEFFMPEG).
- Directory setup for both uploadsDir and outputDir with mkdirSync for each.
- Multer storage configuration.
- POST /upload endpoint returning video info.
- POST /export endpoint that creates a SIMPLEFFMPEG project with the preset, loads a trimmed video clip, and exports to the output folder.
- GET /download/:filename endpoint using res.download().
- Server listening on port 3000.
Your public/index.html should contain:
- A file input with id="videoFile".
- An info div with id="info".
- An edit section (initially hidden) with id="editSection" containing start time input, end time input, a preset dropdown, and an Export button.
- A status section with id="statusSection".
- JavaScript that uploads the file, stores the filename, shows the edit section, and has an exportVideo() function that POSTs to /export and creates a download link.
If anything looks off, re-prompt Copilot with the specific piece that is missing.
Your editor can now trim any video clip and export it in the correct aspect ratio for TikTok, YouTube Shorts, or Instagram Reels. Next up, you will add text overlays so your shorts look polished and professional.
Add Text Overlays
Your app can trim clips and format them for TikTok, YouTube Shorts, or Instagram Reels. That gets the shape right, but the video still looks like a raw clip someone cut.
Every viral short has text on screen. A quote, a title, a hook. In this step, you will add a text overlay feature so your shorts look polished and professional.
In this step, get ready to:
- Add a text input and font size dropdown to the frontend.
- Update the export endpoint to render text onto the video.
- Test the full flow and see your text appear on a downloaded short.
Add text input to the frontend
The learner needs a way to type their overlay text and pick a font size before exporting. You will add these controls to the existing edit section in public/index.html.
- Switch back to public/index.html in VS Code.
- Open GitHub Copilot Chat (Ctrl+Shift+I) and give it this prompt:
Update this HTML page to add:
1. After the format dropdown, add a labeled text input field "Text Overlay" with id "overlayText" and placeholder "e.g. Best Scene Ever"
2. After the text input, add a font size dropdown with id "fontSize" and options: Small (value 32), Medium (value 48, selected by default), Large (value 64)
3. In the exportVideo() function, include overlayText and fontSize in the POST request body to /export
- Accept the generated code from Copilot.
Verify that Copilot added the two new form elements. In the edit section of your HTML, you should now see a text input and a select dropdown between the platform format selector and the Export button.
- Confirm the text overlay input looks like this in your edit section, placed after the preset <select>:
<label>Text Overlay: <input type="text" id="overlayText" placeholder="e.g. Best Scene Ever" /></label>
<select id="fontSize">
<option value="32">Small</option>
<option value="48" selected>Medium</option>
<option value="64">Large</option>
</select>
- Also confirm the exportVideo() function now includes the two new fields in the body object:
// Inside the body object in exportVideo()
overlayText: document.getElementById("overlayText").value,
fontSize: document.getElementById("fontSize").value,
What does this code do?
The text input with id overlayText captures whatever text the user wants burned onto their video.
The fontSize dropdown maps human-readable labels (Small, Medium, Large) to pixel values (32, 48, 64) that the server will use when rendering text onto the video.
- Save public/index.html.
Copilot put the fields in the wrong place?
Make sure the text input and font size dropdown appear after the platform format <select> and before the Export <button>. If Copilot placed them elsewhere, move them manually.
Also check that the id attributes match exactly: overlayText and fontSize. A mismatch means the export function cannot read the values.
Help me fix the text overlay form fields in my index.html
Update the export endpoint to include text
The frontend now sends overlayText and fontSize to the server, but the /export endpoint does not use them yet. You need to add a text clip to the simple-ffmpegjs timeline when the user provides overlay text.
- Switch to server.js in VS Code.
- Open GitHub Copilot Chat (Ctrl+Shift+I) and give it this prompt:
Update the POST /export endpoint to:
1. Accept overlayText and fontSize from the request body
2. If overlayText is provided, add a text clip to the simple-ffmpegjs timeline with: type 'text', the text content, fontSize from the request, fontColor '#FFFFFF', position centered, and duration matching the trimmed video length
3. If overlayText is empty, skip adding the text clip
- Accept the generated code from Copilot.
Verify that the updated destructuring line now includes the two new fields:
const { filename, startTime, endTime, preset, overlayText, fontSize } = req.body;
- Confirm Copilot added a conditional block that pushes a text clip to the timeline. It should look like this, placed after the video clip is pushed:
// Add text overlay if the user provided text
if (overlayText && overlayText.trim() !== "") {
timeline.push({
type: "text",
text: overlayText,
position: 0.5,
end: parseFloat(endTime) - parseFloat(startTime),
fontSize: parseInt(fontSize) || 48,
fontColor: "#FFFFFF",
animation: { type: "pop", in: 0.3 },
});
}
What does this code do?
- The if check skips adding text when the field is blank. No text means no overlay.
- position: 0.5 centers the text vertically on screen.
- end matches the trimmed duration so the text stays visible for the entire clip.
- The animation object adds a subtle pop-in effect so the text does not just appear flatly.
- Save server.js.
Copilot generated something different?
The critical parts are: the clip must have type: "text", the text property set to overlayText, and fontColor: "#FFFFFF". If Copilot used different property names or a different structure, replace the text clip object with the code shown above.
Help me fix my text overlay code in server.js
Test the text overlay
Both files are updated. Time to see your text rendered onto a video.
- In the terminal, stop the running server by pressing Ctrl+C.
- Restart the server by running:
node server.js
- Open http://localhost:3000 in your browser.
- Upload a short video clip.
- Set a trim range (for example, start: 2, end: 8).
- Type Best Scene Ever in the Text Overlay field.
- Select Large from the font size dropdown.
- Select any platform format (e.g. TikTok 9:16).
Before you click Export, what do you expect to see in the downloaded MP4?
- Click Export Short.
- When the download link appears, download the MP4 and play it.
You should see "Best Scene Ever" in large white text displayed on the video. The text pops in and stays visible for the entire trimmed duration.
Text not appearing on the video?
- Check your terminal for errors. If you see a JSON parse error, make sure overlayText and fontSize are included in the body object in your exportVideo() function.
- Confirm you restarted the server after saving server.js. Old code is still running until you press Ctrl+C and re-run node server.js.
- Make sure FFmpeg supports text rendering. The essentials build installed via winget includes the required filters.
Help me debug why my text overlay is not showing on the exported video
✔️ Awesome, I've got everything!
Great. Make sure you have saved both public/index.html and server.js.
ⓧ I'd like to double check the full code
Scroll up and confirm each piece is present in your files, in order:
- public/index.html: The Text Overlay label and input with id overlayText appear after the preset dropdown.
- public/index.html: The fontSize select has three options (32, 48 selected, 64) after the text input.
- public/index.html: The exportVideo() body includes overlayText and fontSize.
- server.js: The destructuring line in /export includes overlayText and fontSize.
- server.js: The if (overlayText && overlayText.trim() !== "") block pushes a text clip with type: "text", fontColor: "#FFFFFF", and position: 0.5.
Your shorts now have text burned right into the video. Next up, you will polish the entire interface so it feels like a real tool instead of a bare HTML form.
Polish the Interface
Your Movie Shorts Editor works end-to-end. You can upload, trim, format, and add text overlays. But right now it looks like a bare HTML form from the early 2000s.
Professional tools have visual hierarchy, feedback during processing, and a preview so you know what you are editing. In this step, you will use GitHub Copilot prompts to transform the interface from a developer prototype into something that looks and feels like a real product.
In this step, get ready to:
- Add a video preview player so you can see the clip before editing.
- Restyle the entire interface with a dark theme and card layout.
- Add progress feedback during export with a status bar and download link.
Add a video preview route to the server
For the browser to play back your uploaded video, the server needs a route that serves files from the uploads folder. You will also restructure the export endpoint so it can accept both JSON requests and file uploads in the future.
- Open server.js in VS Code.
- Open GitHub Copilot Chat (Ctrl+Shift+I) and give it this prompt:
Update server.js to:
1. Add a GET /preview/:filename route that serves files from the uploads folder using res.sendFile(), with a 404 if the file doesn't exist
2. Update the POST /export endpoint to use upload.single("music") as middleware (so it can accept multipart form data in the future), destructure enableFade from req.body, build the video clip as a separate variable, add a conditional transition ({ type: "fade", duration: 0.5 }) if enableFade === "true", and add a music clip from req.file if present
Keep all existing functionality (upload, download, text overlay).
- Review what Copilot generates. The /preview route should look like this:
// Preview endpoint - serves uploaded files
app.get("/preview/:filename", (req, res) => {
const filePath = path.join(uploadsDir, req.params.filename);
if (fs.existsSync(filePath)) {
res.sendFile(filePath);
} else {
res.status(404).json({ error: "File not found" });
}
});
What does this route do?
When the browser requests /preview/my-video.mp4, the server finds that file in the uploads folder and streams it back. This lets an HTML5 <video> element play the uploaded clip directly in the browser.
- Verify the updated /export endpoint starts with upload.single("music") middleware and includes the enableFade logic:
// Export endpoint - trims, formats, adds text, fade, and music
app.post("/export", upload.single("music"), async (req, res) => {
try {
const { filename, startTime, endTime, preset,
overlayText, fontSize, enableFade } = req.body;
const inputPath = path.join(uploadsDir, filename);
const outputFilename = `short-${Date.now()}.mp4`;
const outputPath = path.join(outputDir, outputFilename);
const project = new SIMPLEFFMPEG({ preset: preset || "youtube" });
const timeline = [];
const videoClip = {
type: "video",
url: inputPath,
duration: parseFloat(endTime) - parseFloat(startTime),
startTime: parseFloat(startTime),
};
if (enableFade === "true") {
videoClip.transition = { type: "fade", duration: 0.5 };
}
timeline.push(videoClip);
if (overlayText && overlayText.trim() !== "") {
timeline.push({
type: "text",
text: overlayText,
position: 0.5,
end: parseFloat(endTime) - parseFloat(startTime),
fontSize: parseInt(fontSize) || 48,
fontColor: "#FFFFFF",
animation: { type: "pop", in: 0.3 },
});
}
if (req.file) {
timeline.push({
type: "music",
url: req.file.path,
volume: 0.2,
loop: true,
});
}
await project.load(timeline);
await project.export({ outputPath });
res.json({ success: true, outputFile: outputFilename });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
Why add music and fade support now?
The upload.single("music") middleware lets multer handle file uploads on this route. Right now the frontend sends JSON (no music file), so multer simply passes through and express.json() handles the body as normal.
The enableFade and req.file checks are harmless when unused. This prepares the server for the Secret Mission without a second rewrite later.
- Accept the generated code in Copilot and save server.js.
- Restart your server by pressing Ctrl+C in the terminal, then running:
node server.js
- Open http://localhost:3000 in your browser. The page still looks the same (we have not updated the frontend yet), but the server is ready.
Server won't start?
- Check for syntax errors in the terminal output. A missing comma or bracket in the new route code is the most common cause.
- Make sure the /preview route is placed between the /upload and /export routes.
Why won't my server.js start after adding the preview route?
Restyle the interface with a dark theme
The server is ready. Now you will rebuild the frontend from scratch so it looks like a professional tool instead of a bare HTML form.
- Open public/index.html in VS Code.
- Select all the content (Ctrl+A) and delete it so you start fresh.
- Open Copilot Chat (Ctrl+Shift+I) and give it this prompt:
Create a complete public/index.html for my Movie Shorts Editor app. Requirements:
- Dark background (#1a1a2e), card container (#16213e, max-width 600px, rounded corners, box-shadow)
- Gradient accent (#e94560 to #533483) on the h1 title and buttons
- Three numbered sections: "1. Upload Your Clip" (file input), "2. Preview" (HTML5 video player with controls, hidden until upload), "3. Edit Your Short" (start/end time inputs in a row, platform format dropdown, text overlay input, font size dropdown, Export button)
- The preview section uses a video element with src pointing to /preview/<filename>
- Section titles use uppercase 12px text in the accent color
- Styled inputs with dark backgrounds, rounded borders
- A subtitle paragraph under the h1: "Turn movie clips into social media shorts"
- Export button is full-width with gradient background, hover lifts, disabled state
- Include a hidden statusSection div for later use
- The upload handler: POST to /upload, show video player and info (duration, resolution), show edit section
- The export function sends JSON to POST /export with: filename, startTime, endTime, preset, overlayText, fontSize, enableFade set to "false"
- Add showStatus(message, type) and hideStatus() helper functions
- Use .hidden class (display:none) to show/hide sections
- All in one file with embedded <style> and <script>
- Review the generated code. Look for these key elements before accepting:
- A <style> block with background: #1a1a2e on the body and background: #16213e on .container.
- A <video> element with id="videoPlayer" and class="video-preview".
- The exportVideo() function sending enableFade: "false" in the request body.
- A showStatus() function and a hideStatus() function.
- Accept the generated code and save public/index.html.
- Refresh http://localhost:3000 in your browser.
You should see a dark-themed page with a gradient title, a card-based layout, and the upload section visible. The preview and edit sections are hidden until you upload a file.
Page looks broken or unstyled?
- Make sure you saved the file and hard-refreshed (Ctrl+Shift+R) to clear cached styles.
- Check that the <style> block is inside <head>, not inside <body>.
- Verify the file is saved as public/index.html (inside the public folder, not the project root).
Why does my styled page look broken or show no CSS?
Add export progress feedback
The styled page is up, but when you click Export the user gets no feedback until the download link appears. You need clear status messages and a progress animation so the user knows something is happening.
- Open Copilot Chat and give it this prompt:
Update the showStatus function in public/index.html so that:
- When type is "processing", it appends an animated progress bar div (a .progress-bar container with a .fill child that slides back and forth using CSS keyframes)
- Add CSS for .status.processing (background #0f3460, color #e94560), .status.done (background #1b4332, color #95d5b2), .status.error (background #3d0000, color #ff6b6b)
- The progress bar uses a gradient fill (#e94560 to #533483) with a translateX animation
- In the upload handler: call showStatus("Uploading...", "processing") before fetch, and hideStatus() on success
- In exportVideo: disable the Export button, show "Processing your short..." status, and on success show a styled download link with class "download-link"
- Add .progress-bar and @keyframes progress CSS
- Add a.download-link styling (inline-block, padding, #e94560 background, white text, rounded)
- Review the generated code. The key additions are:
- CSS classes for .status.processing, .status.done, and .status.error.
- A @keyframes progress animation that slides the fill bar.
- The Export button gets btn.disabled = true during processing and re-enables in a finally block.
- The success state shows a download link with class download-link.
- Accept the code and save public/index.html.
Time to test the full polished flow. Before you run this, predict: what will you see when you upload a video and hit Export?
- Refresh http://localhost:3000 in your browser.
- Upload a short video clip.
- You should see the video player appear with your clip ready to play, plus the duration and resolution displayed below it.
- Set a start time of 0 and an end time of 5.
- Select TikTok 9:16 from the format dropdown.
- Click Export Short.
You should see the button dim (disabled state), a "Processing your short..." message with an animated progress bar, and then a green "Done!" status with a styled download link.
Export button does nothing or stays disabled?
- Open the browser console (F12 then Console tab) and look for errors. A common cause is uploadedFilename being empty because the upload handler did not complete.
- Make sure your exportVideo() function has a finally block that sets btn.disabled = false so the button re-enables even after an error.
- Verify the server is still running. If it crashed during export, restart with node server.js.
Why is my Export button stuck or showing an error in the console?
✔️ Awesome, I've got everything!
Great. Double check you have saved both server.js and public/index.html.
ⓧ I'd like to double check the full code
Since Copilot generates code non-deterministically, your output might differ slightly. Scroll up and confirm these elements are present in your files:
In server.js:
- The GET /preview/:filename route that serves from uploadsDir using res.sendFile().
- The POST /export route using upload.single("music") middleware.
- Destructuring includes enableFade from req.body.
- A separate videoClip variable with a conditional transition property.
- A check for req.file that pushes a music clip to the timeline.
In public/index.html:
- CSS with body { background: #1a1a2e } and .container { background: #16213e }.
- A <video> element with id="videoPlayer" whose src is set to /preview/ + filename on upload.
- Three numbered sections (Upload, Preview, Edit) with .section-title labels.
- The exportVideo() function sends enableFade: "false" in the body.
- A showStatus() function that appends a .progress-bar div when type is "processing".
- A @keyframes progress animation and styled .status.done / .status.error classes.
- The Export button disables during processing via btn.disabled = true and re-enables in finally.
If anything is missing, highlight the relevant section in your file and ask Copilot to fix it. Describe what is missing and paste the expected behavior from the checklist above.
Your Movie Shorts Editor now looks and feels like a real tool. Next up, you will take on the Secret Mission and add cinematic fades and background music to make your shorts truly professional.
Secret mission
Add Fade Transitions and Background Music
Your shorts work, but they start and end abruptly. Professional content fades in smoothly, fades out gracefully, and has background music setting the mood. Can you wire up the cinematic controls your backend already supports?
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally, so there are no ongoing costs.
Resources you used:
- uploads/ folder containing your uploaded video files.
- output/ folder containing your exported shorts.
- node_modules/ folder (can be recreated with npm install).
Keep everything running
No action needed. Choose this if you are still actively using your Movie Shorts Editor to create content.
- Your project stays in the movie-shorts-editor folder on your Desktop, ready to run anytime with node server.js.
- Uploaded videos and exported shorts remain available in their respective folders.
- Node.js and FFmpeg remain installed on your system for future projects.
Pause - I'll come back to this later
Free up disk space by removing large video files while keeping your project code intact. You can always upload new clips later.
- Stop the server if it is still running by pressing Ctrl+C in the terminal.
- Delete your uploaded videos and exported shorts by running these commands in your project folder:
Remove-Item -Recurse -Force uploads\*
Remove-Item -Recurse -Force output\*
- Your project code (server.js, public/index.html, package.json) remains untouched.
- When you return, start the server again with node server.js and upload fresh clips.
Delete - I don't want to use this again
Remove all project files and start fresh. This deletes your entire project folder including all code, uploads, and exports.
- Stop the server if it is still running by pressing Ctrl+C in the terminal.
- Close VS Code.
- Open a new terminal and delete the entire project folder by running:
cd ~/Desktop
Remove-Item -Recurse -Force movie-shorts-editor
- Confirm the folder is gone by running:
ls ~/Desktop
You should no longer see movie-shorts-editor in the output.
That's a Wrap!
That's a Wrap!
Nice work! You just built a fully functional Movie Shorts Editor that transforms any video clip into a platform-ready social media short.
You've learned how to:
- Built a full-stack web app with an Express.js backend and vanilla HTML/CSS/JavaScript frontend using only vibe coding prompts in GitHub Copilot.
- Used simple-ffmpegjs to programmatically trim videos, apply social media format presets (TikTok, YouTube Shorts, Instagram Reels), and add text overlays to exported MP4 files.
- Created a polished interface with a video preview player, export progress feedback, and direct download links that make the tool feel like a real product.
- Secret Mission: Added fade transitions and background music mixing for cinematic shorts.
Ready to quiz yourself?