simple-ffmpegjs 0.5.2 → 0.5.4
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 +4 -1
- package/package.json +10 -3
- package/src/core/media_info.js +5 -5
- package/src/core/resolve.js +33 -0
- package/src/core/rotation.js +4 -4
- package/src/core/validation.js +255 -172
- package/src/ffmpeg/audio_builder.js +1 -1
- package/src/ffmpeg/bgm_builder.js +5 -5
- package/src/ffmpeg/command_builder.js +6 -6
- package/src/ffmpeg/effect_builder.js +11 -11
- package/src/ffmpeg/standalone_audio_builder.js +75 -0
- package/src/ffmpeg/strings.js +4 -17
- package/src/ffmpeg/subtitle_builder.js +6 -14
- package/src/ffmpeg/text_passes.js +33 -8
- package/src/ffmpeg/text_renderer.js +17 -17
- package/src/ffmpeg/video_builder.js +6 -4
- package/src/ffmpeg/watermark_builder.js +1 -1
- package/src/lib/utils.js +4 -4
- package/src/loaders.js +8 -8
- package/src/schema/formatter.js +15 -15
- package/src/schema/index.js +4 -4
- package/src/schema/modules/effect.js +5 -4
- package/src/schema/modules/music.js +1 -1
- package/src/schema/modules/text.js +4 -3
- package/src/simpleffmpeg.js +180 -167
- package/types/index.d.mts +33 -39
- package/types/index.d.ts +33 -39
package/src/lib/utils.js
CHANGED
|
@@ -49,7 +49,7 @@ function parseFFmpegProgress(line, totalDuration) {
|
|
|
49
49
|
if (totalDuration > 0) {
|
|
50
50
|
progress.percent = Math.min(
|
|
51
51
|
100,
|
|
52
|
-
Math.round((progress.timeProcessed / totalDuration) * 100)
|
|
52
|
+
Math.round((progress.timeProcessed / totalDuration) * 100),
|
|
53
53
|
);
|
|
54
54
|
}
|
|
55
55
|
}
|
|
@@ -146,7 +146,7 @@ function runFFmpeg({ command, totalDuration = 0, onProgress, signal, onLog }) {
|
|
|
146
146
|
new FFmpegError(`FFmpeg process error: ${error.message}`, {
|
|
147
147
|
stderr,
|
|
148
148
|
command,
|
|
149
|
-
})
|
|
149
|
+
}),
|
|
150
150
|
);
|
|
151
151
|
});
|
|
152
152
|
|
|
@@ -162,7 +162,7 @@ function runFFmpeg({ command, totalDuration = 0, onProgress, signal, onLog }) {
|
|
|
162
162
|
stderr,
|
|
163
163
|
command,
|
|
164
164
|
exitCode: code,
|
|
165
|
-
})
|
|
165
|
+
}),
|
|
166
166
|
);
|
|
167
167
|
return;
|
|
168
168
|
}
|
|
@@ -205,7 +205,7 @@ function parseFFmpegCommand(command) {
|
|
|
205
205
|
} else {
|
|
206
206
|
current += char;
|
|
207
207
|
}
|
|
208
|
-
} else if (char ===
|
|
208
|
+
} else if (char === "\"" || char === "'") {
|
|
209
209
|
inQuote = true;
|
|
210
210
|
quoteChar = char;
|
|
211
211
|
} else if (char === " " || char === "\t") {
|
package/src/loaders.js
CHANGED
|
@@ -20,7 +20,7 @@ async function loadVideo(project, clipObj) {
|
|
|
20
20
|
message: `cutFrom exceeds source duration`,
|
|
21
21
|
},
|
|
22
22
|
],
|
|
23
|
-
}
|
|
23
|
+
},
|
|
24
24
|
);
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -37,7 +37,7 @@ async function loadVideo(project, clipObj) {
|
|
|
37
37
|
console.warn(
|
|
38
38
|
`Video clip overruns source by ${(
|
|
39
39
|
requestedDuration - maxAvailable
|
|
40
|
-
).toFixed(3)}s. Clamping end from ${clipObj.end}s to ${clampedEnd}s
|
|
40
|
+
).toFixed(3)}s. Clamping end from ${clipObj.end}s to ${clampedEnd}s.`,
|
|
41
41
|
);
|
|
42
42
|
clipObj.end = clampedEnd;
|
|
43
43
|
}
|
|
@@ -65,7 +65,7 @@ async function loadAudio(project, clipObj) {
|
|
|
65
65
|
message: `cutFrom exceeds source duration`,
|
|
66
66
|
},
|
|
67
67
|
],
|
|
68
|
-
}
|
|
68
|
+
},
|
|
69
69
|
);
|
|
70
70
|
}
|
|
71
71
|
}
|
|
@@ -82,7 +82,7 @@ async function loadAudio(project, clipObj) {
|
|
|
82
82
|
console.warn(
|
|
83
83
|
`Audio clip overruns source by ${(
|
|
84
84
|
requestedDuration - maxAvailable
|
|
85
|
-
).toFixed(3)}s. Clamping end from ${clipObj.end}s to ${clampedEnd}s
|
|
85
|
+
).toFixed(3)}s. Clamping end from ${clipObj.end}s to ${clampedEnd}s.`,
|
|
86
86
|
);
|
|
87
87
|
clipObj.end = clampedEnd;
|
|
88
88
|
}
|
|
@@ -126,7 +126,7 @@ async function loadBackgroundAudio(project, clipObj) {
|
|
|
126
126
|
message: `cutFrom exceeds source duration`,
|
|
127
127
|
},
|
|
128
128
|
],
|
|
129
|
-
}
|
|
129
|
+
},
|
|
130
130
|
);
|
|
131
131
|
}
|
|
132
132
|
}
|
|
@@ -143,7 +143,7 @@ async function loadBackgroundAudio(project, clipObj) {
|
|
|
143
143
|
console.warn(
|
|
144
144
|
`Background audio overruns source by ${(
|
|
145
145
|
requestedDuration - maxAvailable
|
|
146
|
-
).toFixed(3)}s. Clamping end from ${clip.end}s to ${clampedEnd}s
|
|
146
|
+
).toFixed(3)}s. Clamping end from ${clip.end}s to ${clampedEnd}s.`,
|
|
147
147
|
);
|
|
148
148
|
clip.end = clampedEnd;
|
|
149
149
|
}
|
|
@@ -206,7 +206,7 @@ function loadSubtitle(project, clipObj) {
|
|
|
206
206
|
message: `Unsupported subtitle format '${ext}'`,
|
|
207
207
|
},
|
|
208
208
|
],
|
|
209
|
-
}
|
|
209
|
+
},
|
|
210
210
|
);
|
|
211
211
|
}
|
|
212
212
|
|
|
@@ -237,7 +237,7 @@ async function loadColor(project, clipObj) {
|
|
|
237
237
|
|
|
238
238
|
const tempPath = path.join(
|
|
239
239
|
project.options.tempDir || os.tmpdir(),
|
|
240
|
-
`simpleffmpeg-gradient-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.ppm
|
|
240
|
+
`simpleffmpeg-gradient-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.ppm`,
|
|
241
241
|
);
|
|
242
242
|
fs.writeFileSync(tempPath, ppmBuffer);
|
|
243
243
|
|
package/src/schema/formatter.js
CHANGED
|
@@ -97,7 +97,7 @@ function formatSchema(modules, options = {}) {
|
|
|
97
97
|
lines.push("# SIMPLEFFMPEG — Clip Schema");
|
|
98
98
|
lines.push("");
|
|
99
99
|
lines.push(
|
|
100
|
-
"Compose a video by defining an array of clips passed to `load(clips)`. Each clip has a `type` that determines its role."
|
|
100
|
+
"Compose a video by defining an array of clips passed to `load(clips)`. Each clip has a `type` that determines its role.",
|
|
101
101
|
);
|
|
102
102
|
lines.push("");
|
|
103
103
|
|
|
@@ -105,22 +105,22 @@ function formatSchema(modules, options = {}) {
|
|
|
105
105
|
lines.push("### Timeline");
|
|
106
106
|
lines.push("");
|
|
107
107
|
lines.push(
|
|
108
|
-
"- All times are in **seconds**. `position` = when the clip starts, `end` = when it ends."
|
|
108
|
+
"- All times are in **seconds**. `position` = when the clip starts, `end` = when it ends.",
|
|
109
109
|
);
|
|
110
110
|
lines.push(
|
|
111
|
-
"- **`duration`** can be used instead of `end`: the library computes `end = position + duration`. Cannot use both."
|
|
111
|
+
"- **`duration`** can be used instead of `end`: the library computes `end = position + duration`. Cannot use both.",
|
|
112
112
|
);
|
|
113
113
|
lines.push(
|
|
114
|
-
"- **Auto-sequencing:** For video, image, and audio clips, `position` can be omitted. The clip will be placed immediately after the previous clip on its track. The first clip defaults to position 0."
|
|
114
|
+
"- **Auto-sequencing:** For video, image, and audio clips, `position` can be omitted. The clip will be placed immediately after the previous clip on its track. The first clip defaults to position 0.",
|
|
115
115
|
);
|
|
116
116
|
lines.push(
|
|
117
|
-
"- Video/image clips form the visual timeline. Audio, text, and music are layered on top."
|
|
117
|
+
"- Video/image clips form the visual timeline. Audio, text, and music are layered on top.",
|
|
118
118
|
);
|
|
119
119
|
lines.push(
|
|
120
|
-
"- When video clips overlap with a transition, the overlapping time is shared — a 0.5s fade means the total video is 0.5s shorter."
|
|
120
|
+
"- When video clips overlap with a transition, the overlapping time is shared — a 0.5s fade means the total video is 0.5s shorter.",
|
|
121
121
|
);
|
|
122
122
|
lines.push(
|
|
123
|
-
"- Text and subtitle positions represent **visual time** (what the viewer sees). Transition compression is handled automatically."
|
|
123
|
+
"- Text and subtitle positions represent **visual time** (what the viewer sees). Transition compression is handled automatically.",
|
|
124
124
|
);
|
|
125
125
|
lines.push("");
|
|
126
126
|
|
|
@@ -140,14 +140,14 @@ function formatSchema(modules, options = {}) {
|
|
|
140
140
|
|
|
141
141
|
// Clip type summary
|
|
142
142
|
const typeMap = {
|
|
143
|
-
video:
|
|
144
|
-
audio:
|
|
145
|
-
image:
|
|
146
|
-
color:
|
|
147
|
-
effect:
|
|
148
|
-
text:
|
|
149
|
-
subtitle:
|
|
150
|
-
music:
|
|
143
|
+
video: "\"video\"",
|
|
144
|
+
audio: "\"audio\"",
|
|
145
|
+
image: "\"image\"",
|
|
146
|
+
color: "\"color\"",
|
|
147
|
+
effect: "\"effect\"",
|
|
148
|
+
text: "\"text\"",
|
|
149
|
+
subtitle: "\"subtitle\"",
|
|
150
|
+
music: "\"music\" or \"backgroundAudio\"",
|
|
151
151
|
};
|
|
152
152
|
const availableTypes = modules
|
|
153
153
|
.filter((m) => typeMap[m.id])
|
package/src/schema/index.js
CHANGED
|
@@ -51,8 +51,8 @@ function resolveModules(options = {}) {
|
|
|
51
51
|
if (!ALL_MODULES[id]) {
|
|
52
52
|
throw new Error(
|
|
53
53
|
`Unknown schema module "${id}". Available modules: ${ALL_MODULE_IDS.join(
|
|
54
|
-
", "
|
|
55
|
-
)}
|
|
54
|
+
", ",
|
|
55
|
+
)}`,
|
|
56
56
|
);
|
|
57
57
|
}
|
|
58
58
|
}
|
|
@@ -64,8 +64,8 @@ function resolveModules(options = {}) {
|
|
|
64
64
|
if (!ALL_MODULES[id]) {
|
|
65
65
|
throw new Error(
|
|
66
66
|
`Unknown schema module "${id}". Available modules: ${ALL_MODULE_IDS.join(
|
|
67
|
-
", "
|
|
68
|
-
)}
|
|
67
|
+
", ",
|
|
68
|
+
)}`,
|
|
69
69
|
);
|
|
70
70
|
}
|
|
71
71
|
}
|
|
@@ -8,9 +8,10 @@ module.exports = {
|
|
|
8
8
|
effect: "vignette" | "filmGrain" | "gaussianBlur" | "colorAdjust"
|
|
9
9
|
| "sepia" | "blackAndWhite" | "sharpen" | "chromaticAberration"
|
|
10
10
|
| "letterbox"; // Required: effect kind
|
|
11
|
-
position
|
|
12
|
-
end?: number; // End time on timeline (seconds). Use end OR duration, not both.
|
|
13
|
-
duration?: number; // Duration in seconds (alternative to end). end = position + duration.
|
|
11
|
+
position?: number; // Start time on timeline (seconds). Required unless fullDuration is true.
|
|
12
|
+
end?: number; // End time on timeline (seconds). Use end OR duration, not both. Mutually exclusive with fullDuration.
|
|
13
|
+
duration?: number; // Duration in seconds (alternative to end). end = position + duration. Mutually exclusive with fullDuration.
|
|
14
|
+
fullDuration?: boolean; // When true, spans the full visual timeline. Mutually exclusive with end and duration.
|
|
14
15
|
fadeIn?: number; // Optional: seconds to ramp in from 0 to full intensity
|
|
15
16
|
fadeOut?: number; // Optional: seconds to ramp out from full intensity to 0
|
|
16
17
|
params: EffectParams; // Required: effect-specific parameters
|
|
@@ -116,7 +117,7 @@ module.exports = {
|
|
|
116
117
|
"Effect clips are adjustment layers: they modify underlying video during their active window.",
|
|
117
118
|
"Effects do not satisfy visual timeline continuity checks and do not fill gaps.",
|
|
118
119
|
"Use duration instead of end to specify length: end = position + duration. Cannot use both.",
|
|
119
|
-
"position is required
|
|
120
|
+
"position is required unless fullDuration: true is set, which spans the entire visual timeline.",
|
|
120
121
|
"fadeIn/fadeOut are optional linear envelope controls that avoid abrupt on/off changes.",
|
|
121
122
|
"params.amount is a normalized blend amount from 0 to 1 (default: 1).",
|
|
122
123
|
"filmGrain: use params.strength (0-1) for noise intensity, params.amount for blend alpha.",
|
|
@@ -24,7 +24,7 @@ module.exports = {
|
|
|
24
24
|
},
|
|
25
25
|
],
|
|
26
26
|
notes: [
|
|
27
|
-
|
|
27
|
+
"The type can be either \"music\" or \"backgroundAudio\" — both work identically.",
|
|
28
28
|
"Default volume is 0.2 (20%), so background music doesn't overpower speech or main audio.",
|
|
29
29
|
"When loop is true, the track repeats seamlessly to fill the video duration. position/end are not required with loop.",
|
|
30
30
|
],
|
|
@@ -5,9 +5,10 @@ module.exports = {
|
|
|
5
5
|
"Render text overlays on the video with multiple display modes, positioning, styling, and animations.",
|
|
6
6
|
schema: `{
|
|
7
7
|
type: "text"; // Required: clip type identifier
|
|
8
|
-
position
|
|
9
|
-
end?: number; // End time on timeline (seconds). Use end OR duration, not both.
|
|
10
|
-
duration?: number; // Duration in seconds (alternative to end). end = position + duration.
|
|
8
|
+
position?: number; // Start time on timeline (seconds). Required unless fullDuration is true.
|
|
9
|
+
end?: number; // End time on timeline (seconds). Use end OR duration, not both. Mutually exclusive with fullDuration.
|
|
10
|
+
duration?: number; // Duration in seconds (alternative to end). end = position + duration. Mutually exclusive with fullDuration.
|
|
11
|
+
fullDuration?: boolean; // When true, spans the full visual timeline. Mutually exclusive with end and duration.
|
|
11
12
|
|
|
12
13
|
// Content
|
|
13
14
|
text?: string; // Text content (required for "static" mode)
|