simple-ffmpegjs 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +706 -34
- package/package.json +3 -4
- package/src/core/constants.js +33 -0
- package/src/core/media_info.js +141 -66
- package/src/core/rotation.js +76 -7
- package/src/core/validation.js +758 -145
- package/src/ffmpeg/command_builder.js +17 -6
- package/src/ffmpeg/strings.js +52 -2
- package/src/ffmpeg/subtitle_builder.js +707 -0
- package/src/ffmpeg/text_passes.js +41 -5
- package/src/ffmpeg/text_renderer.js +149 -11
- package/src/ffmpeg/video_builder.js +3 -1
- package/src/ffmpeg/watermark_builder.js +411 -0
- package/src/loaders.js +81 -7
- package/src/simpleffmpeg.js +604 -62
- package/types/index.d.mts +266 -7
- package/types/index.d.ts +305 -9
- package/assets/example-thumbnail.jpg +0 -0
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
const os = require("os");
|
|
2
|
+
const { escapeFilePath } = require("./strings");
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Get the null device path for the current platform
|
|
6
|
+
* @returns {string} '/dev/null' on Unix, 'NUL' on Windows
|
|
7
|
+
*/
|
|
8
|
+
function getNullDevice() {
|
|
9
|
+
return os.platform() === "win32" ? "NUL" : "/dev/null";
|
|
10
|
+
}
|
|
11
|
+
|
|
1
12
|
/**
|
|
2
13
|
* Build the main FFmpeg export command
|
|
3
14
|
*/
|
|
@@ -77,7 +88,7 @@ function buildMainCommand({
|
|
|
77
88
|
if (twoPass && passNumber) {
|
|
78
89
|
cmd += `-pass ${passNumber} `;
|
|
79
90
|
if (passLogFile) {
|
|
80
|
-
cmd += `-passlogfile "${passLogFile}" `;
|
|
91
|
+
cmd += `-passlogfile "${escapeFilePath(passLogFile)}" `;
|
|
81
92
|
}
|
|
82
93
|
}
|
|
83
94
|
|
|
@@ -112,7 +123,7 @@ function buildMainCommand({
|
|
|
112
123
|
|
|
113
124
|
// For two-pass first pass, output to null
|
|
114
125
|
if (twoPass && passNumber === 1) {
|
|
115
|
-
cmd +=
|
|
126
|
+
cmd += `-f null ${getNullDevice()}`;
|
|
116
127
|
return cmd;
|
|
117
128
|
}
|
|
118
129
|
|
|
@@ -148,7 +159,7 @@ function buildMainCommand({
|
|
|
148
159
|
}
|
|
149
160
|
}
|
|
150
161
|
|
|
151
|
-
cmd += `"${outputPath}"`;
|
|
162
|
+
cmd += `"${escapeFilePath(outputPath)}"`;
|
|
152
163
|
return cmd;
|
|
153
164
|
}
|
|
154
165
|
|
|
@@ -163,14 +174,14 @@ function buildTextBatchCommand({
|
|
|
163
174
|
intermediateCrf,
|
|
164
175
|
outputPath,
|
|
165
176
|
}) {
|
|
166
|
-
return `ffmpeg -y -i "${inputPath}" -filter_complex "[0:v]null[invid];${filterString}" -map "[outVideoAndText]" -map 0:a? -c:v ${intermediateVideoCodec} -preset ${intermediatePreset} -crf ${intermediateCrf} -c:a copy -movflags +faststart "${outputPath}"`;
|
|
177
|
+
return `ffmpeg -y -i "${escapeFilePath(inputPath)}" -filter_complex "[0:v]null[invid];${filterString}" -map "[outVideoAndText]" -map 0:a? -c:v ${intermediateVideoCodec} -preset ${intermediatePreset} -crf ${intermediateCrf} -c:a copy -movflags +faststart "${escapeFilePath(outputPath)}"`;
|
|
167
178
|
}
|
|
168
179
|
|
|
169
180
|
/**
|
|
170
181
|
* Build command to generate a thumbnail
|
|
171
182
|
*/
|
|
172
183
|
function buildThumbnailCommand({ inputPath, outputPath, time, width, height }) {
|
|
173
|
-
let cmd = `ffmpeg -y -ss ${time} -i "${inputPath}" -vframes 1 `;
|
|
184
|
+
let cmd = `ffmpeg -y -ss ${time} -i "${escapeFilePath(inputPath)}" -vframes 1 `;
|
|
174
185
|
|
|
175
186
|
if (width || height) {
|
|
176
187
|
const w = width || -1;
|
|
@@ -178,7 +189,7 @@ function buildThumbnailCommand({ inputPath, outputPath, time, width, height }) {
|
|
|
178
189
|
cmd += `-vf "scale=${w}:${h}" `;
|
|
179
190
|
}
|
|
180
191
|
|
|
181
|
-
cmd += `"${outputPath}"`;
|
|
192
|
+
cmd += `"${escapeFilePath(outputPath)}"`;
|
|
182
193
|
return cmd;
|
|
183
194
|
}
|
|
184
195
|
|
package/src/ffmpeg/strings.js
CHANGED
|
@@ -2,9 +2,52 @@ function escapeSingleQuotes(text) {
|
|
|
2
2
|
return String(text).replace(/'/g, "\\'");
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Escape a file path for use in FFmpeg command line arguments.
|
|
7
|
+
* Prevents command injection by escaping quotes and backslashes.
|
|
8
|
+
* @param {string} filePath - The file path to escape
|
|
9
|
+
* @returns {string} Escaped file path safe for use in double-quoted command strings
|
|
10
|
+
*/
|
|
11
|
+
function escapeFilePath(filePath) {
|
|
12
|
+
if (typeof filePath !== "string") return "";
|
|
13
|
+
// Escape backslashes first, then double quotes
|
|
14
|
+
// This makes the path safe for use inside double-quoted shell strings
|
|
15
|
+
return filePath.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Check if text contains characters that are problematic for FFmpeg's
|
|
20
|
+
* drawtext filter and should use the textfile approach instead.
|
|
21
|
+
*/
|
|
22
|
+
function hasProblematicChars(text) {
|
|
23
|
+
if (typeof text !== "string") return false;
|
|
24
|
+
// These characters cannot be reliably escaped in filter_complex parsing
|
|
25
|
+
// when passed through shell double-quoting
|
|
26
|
+
return /[,;{}\[\]"]/.test(text);
|
|
27
|
+
}
|
|
28
|
+
|
|
5
29
|
function escapeDrawtextText(text) {
|
|
6
30
|
if (typeof text !== "string") return "";
|
|
7
|
-
|
|
31
|
+
// Escape characters that have special meaning in FFmpeg drawtext filter
|
|
32
|
+
// AND characters that break shell parsing (since filter_complex is double-quoted)
|
|
33
|
+
return text
|
|
34
|
+
.replace(/\\/g, "\\\\") // Escape backslashes first
|
|
35
|
+
.replace(/'/g, "\\'") // Escape single quotes (text delimiter)
|
|
36
|
+
.replace(/"/g, '\\"') // Escape double quotes (shell safety)
|
|
37
|
+
.replace(/:/g, "\\:") // Escape colons (option separator)
|
|
38
|
+
.replace(/\n/g, " ") // Replace newlines with space (multiline not supported)
|
|
39
|
+
.replace(/\r/g, ""); // Remove carriage returns
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Escape a file path for use in FFmpeg filters (Windows paths need special handling)
|
|
44
|
+
*/
|
|
45
|
+
function escapeTextFilePath(filePath) {
|
|
46
|
+
if (typeof filePath !== "string") return "";
|
|
47
|
+
// FFmpeg on Windows needs forward slashes and escaped colons
|
|
48
|
+
return filePath
|
|
49
|
+
.replace(/\\/g, "/") // Convert backslashes to forward slashes
|
|
50
|
+
.replace(/:/g, "\\:"); // Escape colons (for Windows drive letters)
|
|
8
51
|
}
|
|
9
52
|
|
|
10
53
|
function getClipAudioString(clip, inputIndex) {
|
|
@@ -18,4 +61,11 @@ function getClipAudioString(clip, inputIndex) {
|
|
|
18
61
|
return { audioStringPart, audioConcatInput };
|
|
19
62
|
}
|
|
20
63
|
|
|
21
|
-
module.exports = {
|
|
64
|
+
module.exports = {
|
|
65
|
+
escapeSingleQuotes,
|
|
66
|
+
escapeFilePath,
|
|
67
|
+
escapeDrawtextText,
|
|
68
|
+
getClipAudioString,
|
|
69
|
+
hasProblematicChars,
|
|
70
|
+
escapeTextFilePath,
|
|
71
|
+
};
|