Batch process an entire directory of videos or mp4s from a folder with FFmpeg and Editframe (Updated)
Published on May 28, 2023
Last updated on September 25, 2024
6 min read
If you’ve ever tried to perform any sort of changes to a large video collection en masse, you know it’s no easy task. Batch processing can be an efficient way to automate repetitive changes performed on a group of video files—like merging many individual videos into a single file, for example.
In this tutorial, we’ll walk through the process of programmatically merging an entire directory of MP4 videos into a single file using FFmpeg. Then, we’ll explore how to use the Editframe API to programmatically merge videos from a directory in the cloud. Both methods are incredibly useful in instances where you have hundreds of videos that you need combined into one single, continuous file.
Let’s do this.
Files assets
Here are the sample video files provided by pexels.com(opens in a new tab) that we will use in this tutorial:
file1.mp4
file2.mp4
file3.mp4
Part 1: Using FFmpeg
First, we’ll walk through this workflow using FFmpeg.
Required Tools
- Sample video and audio files (provided above)
- FFmpeg(opens in a new tab): (You’ll need to install FFmpeg and set up the appropriate environment variables before beginning this tutorial)
Create a video directory in which you would like the final merged video file to live:
mkdir video
Here is the FFmpeg command that merges all the videos in a directory into a single file:
for filename in video/*.mp4; do
ffmpeg -y -i "$filename" -c:a copy -c:v copy -bsf:v h264_mp4toannexb -f mpegts "${filename//.mp4/}.ts"
echo "file './${filename//.mp4/}.ts'" >> concat.txt
done
ffmpeg -f concat -segment_time_metadata 1 -safe 0 -i concat.txt -vf "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:-1:-1,setsar=1,fps=30,format=yuv420p" stitched-video.mp4
rm -rf concat.txt video/*.ts
Let’s break down what this is doing.
- In the loop below, we iterate over each mp4 video file in our folder, and run a command that creates a .ts video file. (TS stands for Video Transport Stream, and these files store video data compressed with standard MPEG-2.). Then, we specify a file path for the .ts files in the concat.txt file.
for filename in video/*.mp4; do
ffmpeg -y -i "$filename" -c:a copy -c:v copy -bsf:v h264_mp4toannexb -f mpegts "${filename//.mp4/}.ts"
echo "file './${filename//.mp4/}.ts'" >> concat.txt
done
- In this line, we concat all of the .ts videos files using the concat.txt file. Additionally, we resize the file to 1920:1080 at a frame rate of 30 fps.
ffmpeg -f concat -segment_time_metadata 1 -safe 0 -i concat.txt -vf "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:-1:-1,setsar=1,fps=30,format=yuv420p" stitched-video.mp4
- Here, after merging the video files together, we remove all of the .ts files we created previously. (This is optional):
rm -rf video/*.ts
- In this line, we remove the concat.txt file that holds the video path for the .ts files (this is also optional):
rm -rf concat.txt
Here’s the final output video from the FFmpeg command
stitched-video.mp4
Part 2: Using Editframe
Now let’s perform the same task using Editframe instead of FFmpeg.
Required tools:
- Node.js installed on your machine
- No need to have FFmpeg installed on your machine
- Editframe API Token (you can create an account from this link(opens in a new tab))
- No need to have an .srt file for the subtitles (you can add subtitles directly to the video using the Editframe elements)
Let’s get started:
- Setup a new Node.js project using the Editframe CLI:
npx @editframe/create@beta
- Install the required dependencies:
cd batch-process-videos && npm install
- Create a new directory called
videos
and add the video files to the project:
mkdir videos
- Add assets to the project:
Add file1.mp4
, file2.mp4
, and file3.mp4
files to the videos
folder.
- Update the
index.html
file with the following code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script type="module" src="./src/index.js"></script>
<link rel="stylesheet" href="./src/styles.css" />
</head>
<body>
<ef-timegroup mode="sequence" class="w-[1920px] h-[1080px] overflow-hidden">
{{content}}
</ef-timegroup>
</body>
</html>
Let’s walk through what the code in this file is doing.
- In these lines, we import the
index.js
file and thestyles.css
file which contains the styles for the video and@editframe/elements
:
<head>
<meta charset="UTF-8" />
<script type="module" src="./src/index.js"></script>
<link rel="stylesheet" href="./src/styles.css" />
</head>
- In this line, we create a new video composition using the
ef-timegroup
element.
<ef-timegroup
mode="sequence"
class="w-[1920px] h-[1080px"
>
{{content}}
</ef-timegroup>
- Create
process_videos.js
file in the root directory and add the following code:
import fs from 'fs/promises';
import path from 'path';
const sourceFolder = './videos';
const destFolder = './src/assets';
const indexFile = './index.html';
async function processVideos() {
try {
// Create destination folder if it doesn't exist
await fs.mkdir(destFolder, { recursive: true });
// Read the list of video files
const files = await fs.readdir(sourceFolder);
const videoFiles = files.filter(file => path.extname(file).toLowerCase() === '.mp4');
// Copy video files to src/assets
for (const file of videoFiles) {
const sourcePath = path.join(sourceFolder, file);
const destPath = path.join(destFolder, file);
await fs.copyFile(sourcePath, destPath);
console.log(`Copied ${file} to ${destFolder}`);
}
// Read the index.html file
let indexContent = await fs.readFile(indexFile, 'utf-8');
// Generate new video elements
const videoElements = videoFiles.map(file => `
<ef-timegroup mode="contain">
<ef-video src="/assets/${file}"></ef-video>
</ef-timegroup>
`
).join('');
// Replace the placeholder in index.html
indexContent = indexContent.replace(
'{{content}}',
videoElements
);
// Write the updated content back to index.html
await fs.writeFile(indexFile, indexContent, 'utf-8');
console.log('Updated index.html with new video elements');
} catch (error) {
console.error('An error occurred:', error);
}
}
processVideos();
- In this script, we read the list of video files from the
videos
folder, copy them to thesrc/assets
folder, and generate new video elements for each video file in theindex.html
file.
- Run the script:
node process_videos.js
This will copy the videos files form videos from videos
folder to src/assets
folder and update the index.html
file with the new video elements.
You should see the following output:
Copied file1.mp4 to ./src/assets
Copied file2.mp4 to ./src/assets
Copied file3.mp4 to ./src/assets
Updated index.html with new video elements
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script type="module" src="./src/index.js"></script>
<link rel="stylesheet" href="./src/styles.css" />
</head>
<body>
<ef-timegroup mode="sequence" class="w-[1920px] h-[1080px] overflow-hidden">
<ef-timegroup mode="contain">
<ef-video src="/assets/file1.mp4"></ef-video>
</ef-timegroup>
<ef-timegroup mode="contain">
<ef-video src="/assets/file2.mp4"></ef-video>
</ef-timegroup>
<ef-timegroup mode="contain">
<ef-video src="/assets/file3.mp4"></ef-video>
</ef-timegroup>
</ef-timegroup>
</body>
</html>
- Preview the project:
npx vite .
- Update the
.env
file with your Editframe API token:
EF_TOKEN="YOUR_API_TOKEN"
- Render the video:
npx @editframe/cli render .
Here is the final output video from the Editframe command:
editframe-stitched-video.mp4
Note: You can add transitions and filters, resize videos, trim videos, and much more using Editframe. Check out the docs here.
Comparison video between FFmpeg and Editframe API
Here’s a comparison between the videos we created with FFmpeg (left) and Editframe (right):