Series-Scripts

Batch convert videos to AV1 codec

Runtime:

Using FFmpeg alone

1
2
3
ffmpeg -i "input.mp4" \
-vf "colorspace=iall=bt709:all=bt709:fast=1" \
-c:v libsvtav1 -crf 30 -preset 8 -c:a copy "output.mkv"
ParameterDescription
-i "input.mp4"Specifies the input file, in this case "input.mp4".
-vf "colorspace=iall=bt709:all=bt709:fast=1"Applies the colorspace video filter:
- iall=bt709: Assume the input is in BT.709 color space (ignore metadata).
- all=bt709: Convert the output to BT.709 color space.
- fast=1: Enable fast mode for better performance with slight precision loss.
-c:v libsvtav1Sets the video codec to libsvtav1, which uses the SVT-AV1 encoder.
-crf 30Constant Rate Factor: controls quality. Higher value = smaller size, lower quality (range typically 0–63).
-preset 8Encoding speed preset; 8 usually means veryslow, yielding better compression at the cost of speed.
-c:a copyCopies the audio stream without re-encoding.
"output.mkv"Specifies the output file name as output.mkv.

Batch Conversion Using Node.js

1
node toAV1.js /dir-to-your-videos/

The converted AV1-encoded video file will be saved in the dist folder under the original video directory after the process completes.

toAV1.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
const fs = require("fs");
const path = require("path");
const { spawn } = require("child_process");
const trash = require("trash");

const inputDir = process.argv[2];

if (!inputDir || !fs.existsSync(inputDir)) {
console.error("❌ Please provide a valid directory path.");
process.exit(1);
}

// Supported video extensions
const VIDEO_EXTS = [".mp4", ".mov", ".mkv", ".avi", ".webm", ".m4v"];

// Output directory
const distDir = path.join(inputDir, "dist");
if (!fs.existsSync(distDir)) {
fs.mkdirSync(distDir);
}

async function encodeVideo(filePath, indexStr) {
const fileName = path.basename(filePath);
const baseName = path.parse(fileName).name;
const outputPath = path.join(distDir, `${baseName}.mkv`);

return new Promise((resolve, reject) => {
console.log(
`🎬 Encoding ${indexStr} : ${fileName}${path.relative(inputDir, outputPath)}`
);

const ffmpeg = spawn(
"ffmpeg",
[
"-i",
filePath,
"-vf",
"colorspace=iall=bt709:all=bt709:fast=1",
"-c:v",
"libsvtav1",
"-crf",
"30",
"-preset",
"8",
"-c:a",
"copy",
outputPath,
],
{ stdio: "inherit" }
);

ffmpeg.on("close", async (code) => {
if (code === 0) {
console.log(`✅ ${indexStr} Encoding completed: ${fileName}`);
try {
await trash([filePath]);
console.log(`🗑️ The original file has been moved to the trash: ${fileName}`);
} catch (err) {
console.error(`⚠️ Failed to move to trash: ${err.message}`);
}
resolve();
} else {
console.error(`❌ Encoding failed: ${fileName}`);
reject(new Error(`FFmpeg Exit Code: ${code}`));
}
});
});
}

(async () => {
const files = fs
.readdirSync(inputDir)
.filter((file) => VIDEO_EXTS.includes(path.extname(file).toLowerCase()))
.map((file) => path.join(inputDir, file));

if (files.length === 0) {
console.log("📭 Video File Not Found");
return;
}

for (const [index, file] of files.entries()) {
try {
const indexStr = `${index}/${files.length}`
await encodeVideo(file, indexStr);
} catch (err) {
console.error(`❌ Failed: ${err.message}`);
}
}

console.log("🏁 All Done!");
})();