simple-ffmpegjs 0.5.3 → 0.5.5
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 +1 -0
- package/package.json +1 -1
- package/src/core/resolve.js +33 -0
- package/src/core/validation.js +32 -4
- package/src/schema/modules/effect.js +5 -4
- package/src/schema/modules/text.js +4 -3
- package/src/simpleffmpeg.js +33 -1
- package/types/index.d.mts +52 -40
- package/types/index.d.ts +32 -40
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT"></a>
|
|
9
9
|
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E%3D20-brightgreen.svg" alt="Node.js ≥20"></a>
|
|
10
10
|
<a href="https://codecov.io/gh/Fats403/simple-ffmpegjs"><img src="https://codecov.io/gh/Fats403/simple-ffmpegjs/branch/main/graph/badge.svg" alt="Coverage"></a>
|
|
11
|
+
<img src="https://img.shields.io/badge/dependencies-0-brightgreen.svg" alt="Zero Dependencies">
|
|
11
12
|
</p>
|
|
12
13
|
|
|
13
14
|
<p align="center">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "simple-ffmpegjs",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "Declarative video composition for Node.js — define clips, transitions, text, and audio as simple objects, and let FFmpeg handle the rest.",
|
|
5
5
|
"author": "Brayden Blackwell <braydenblackwell21@gmail.com> (https://github.com/Fats403)",
|
|
6
6
|
"license": "MIT",
|
package/src/core/resolve.js
CHANGED
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
* `position`, it is placed immediately after the previous clip on the
|
|
12
12
|
* same track (visual or audio). The first clip defaults to position 0.
|
|
13
13
|
*
|
|
14
|
+
* 3. **fullDuration**: If an effect or text clip has `fullDuration: true`,
|
|
15
|
+
* its position defaults to 0 and end is resolved later in _prepareExport()
|
|
16
|
+
* once the visual timeline duration is known.
|
|
17
|
+
*
|
|
14
18
|
* Clips are shallow-cloned — the caller's original objects are not mutated.
|
|
15
19
|
*/
|
|
16
20
|
|
|
@@ -48,6 +52,35 @@ function resolveClips(clips) {
|
|
|
48
52
|
const c = { ...clip };
|
|
49
53
|
const path = `clips[${index}]`;
|
|
50
54
|
|
|
55
|
+
// ── fullDuration shorthand ─────────────────────────────────────────
|
|
56
|
+
if (c.fullDuration === true) {
|
|
57
|
+
if (c.end != null) {
|
|
58
|
+
errors.push({
|
|
59
|
+
code: "INVALID_VALUE",
|
|
60
|
+
path: `${path}`,
|
|
61
|
+
message:
|
|
62
|
+
"Cannot specify both 'fullDuration' and 'end'. fullDuration spans the entire visual timeline automatically.",
|
|
63
|
+
received: { fullDuration: true, end: c.end },
|
|
64
|
+
});
|
|
65
|
+
return c;
|
|
66
|
+
}
|
|
67
|
+
if (c.duration != null) {
|
|
68
|
+
errors.push({
|
|
69
|
+
code: "INVALID_VALUE",
|
|
70
|
+
path: `${path}`,
|
|
71
|
+
message:
|
|
72
|
+
"Cannot specify both 'fullDuration' and 'duration'. fullDuration spans the entire visual timeline automatically.",
|
|
73
|
+
received: { fullDuration: true, duration: c.duration },
|
|
74
|
+
});
|
|
75
|
+
return c;
|
|
76
|
+
}
|
|
77
|
+
// Default position to 0; end is resolved in _prepareExport()
|
|
78
|
+
if (c.position == null) {
|
|
79
|
+
c.position = 0;
|
|
80
|
+
}
|
|
81
|
+
return c;
|
|
82
|
+
}
|
|
83
|
+
|
|
51
84
|
// ── Conflict check: duration + end ──────────────────────────────────
|
|
52
85
|
if (c.duration != null && c.end != null) {
|
|
53
86
|
errors.push({
|
package/src/core/validation.js
CHANGED
|
@@ -388,7 +388,7 @@ function validateEffectClip(clip, path, errors) {
|
|
|
388
388
|
* Validate a single clip and return issues
|
|
389
389
|
*/
|
|
390
390
|
function validateClip(clip, index, options = {}) {
|
|
391
|
-
const { skipFileChecks = false } = options;
|
|
391
|
+
const { skipFileChecks = false, skipExtensionsCheck = false } = options;
|
|
392
392
|
const errors = [];
|
|
393
393
|
const warnings = [];
|
|
394
394
|
const path = `clips[${index}]`;
|
|
@@ -474,12 +474,37 @@ function validateClip(clip, index, options = {}) {
|
|
|
474
474
|
}
|
|
475
475
|
}
|
|
476
476
|
|
|
477
|
-
//
|
|
477
|
+
// fullDuration validation
|
|
478
|
+
const fullDurationTypes = ["effect", "text"];
|
|
479
|
+
if (clip.fullDuration != null) {
|
|
480
|
+
if (clip.fullDuration !== true) {
|
|
481
|
+
errors.push(
|
|
482
|
+
createIssue(
|
|
483
|
+
ValidationCodes.INVALID_VALUE,
|
|
484
|
+
`${path}.fullDuration`,
|
|
485
|
+
"fullDuration must be true when specified",
|
|
486
|
+
clip.fullDuration,
|
|
487
|
+
),
|
|
488
|
+
);
|
|
489
|
+
} else if (!fullDurationTypes.includes(clip.type)) {
|
|
490
|
+
errors.push(
|
|
491
|
+
createIssue(
|
|
492
|
+
ValidationCodes.INVALID_VALUE,
|
|
493
|
+
`${path}.fullDuration`,
|
|
494
|
+
`fullDuration is only supported on ${fullDurationTypes.join(", ")} clips`,
|
|
495
|
+
clip.type,
|
|
496
|
+
),
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Types that require position/end on timeline (unless fullDuration is set)
|
|
502
|
+
const hasFullDuration = clip.fullDuration === true && fullDurationTypes.includes(clip.type);
|
|
478
503
|
const requiresTimeline = ["video", "audio", "text", "image", "color", "effect"].includes(
|
|
479
504
|
clip.type,
|
|
480
505
|
);
|
|
481
506
|
|
|
482
|
-
if (requiresTimeline) {
|
|
507
|
+
if (requiresTimeline && !hasFullDuration) {
|
|
483
508
|
if (typeof clip.position !== "number") {
|
|
484
509
|
errors.push(
|
|
485
510
|
createIssue(
|
|
@@ -614,7 +639,9 @@ function validateClip(clip, index, options = {}) {
|
|
|
614
639
|
} catch (_) {}
|
|
615
640
|
}
|
|
616
641
|
|
|
617
|
-
|
|
642
|
+
if (!skipExtensionsCheck) {
|
|
643
|
+
validateMediaUrlExtension(clip, path, errors);
|
|
644
|
+
}
|
|
618
645
|
|
|
619
646
|
if (typeof clip.cutFrom === "number") {
|
|
620
647
|
if (!Number.isFinite(clip.cutFrom)) {
|
|
@@ -1381,6 +1408,7 @@ function validateTimelineGaps(clips) {
|
|
|
1381
1408
|
* @param {Array} clips - Array of clip objects to validate
|
|
1382
1409
|
* @param {Object} options - Validation options
|
|
1383
1410
|
* @param {boolean} options.skipFileChecks - Skip file existence checks (useful for AI validation)
|
|
1411
|
+
* @param {boolean} options.skipExtensionsCheck - Skip media extension/type checks (video/image)
|
|
1384
1412
|
* @returns {Object} Validation result { valid, errors, warnings }
|
|
1385
1413
|
*/
|
|
1386
1414
|
function validateConfig(clips, options = {}) {
|
|
@@ -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.",
|
|
@@ -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)
|
package/src/simpleffmpeg.js
CHANGED
|
@@ -63,6 +63,8 @@ class SIMPLEFFMPEG {
|
|
|
63
63
|
* @param {number} options.fps - Frames per second (default: 30)
|
|
64
64
|
* @param {string} options.preset - Platform preset ('tiktok', 'youtube', 'instagram-post', etc.)
|
|
65
65
|
* @param {string} options.validationMode - Validation behavior: 'warn' or 'strict' (default: 'warn')
|
|
66
|
+
* @param {boolean} options.skipFileChecks - Skip file existence checks during load() validation
|
|
67
|
+
* @param {boolean} options.skipExtensionsCheck - Skip media URL extension/type checks (video/image) during load() validation
|
|
66
68
|
* @param {string} options.fontFile - Default font file path (.ttf, .otf) applied to all text clips unless overridden per-clip
|
|
67
69
|
* @param {string} options.emojiFont - Path to a .ttf/.otf emoji font for rendering emoji in text overlays (opt-in). Without this, emoji are silently stripped from text. Recommended: Noto Emoji (B&W outline).
|
|
68
70
|
* @param {string} options.tempDir - Custom directory for temporary files (gradient images, unrotated videos, intermediate renders). Defaults to os.tmpdir(). Useful for fast SSDs, ramdisks, or environments with constrained /tmp.
|
|
@@ -97,6 +99,8 @@ class SIMPLEFFMPEG {
|
|
|
97
99
|
width: options.width || presetConfig.width || C.DEFAULT_WIDTH,
|
|
98
100
|
height: options.height || presetConfig.height || C.DEFAULT_HEIGHT,
|
|
99
101
|
validationMode: options.validationMode || C.DEFAULT_VALIDATION_MODE,
|
|
102
|
+
skipFileChecks: options.skipFileChecks === true,
|
|
103
|
+
skipExtensionsCheck: options.skipExtensionsCheck === true,
|
|
100
104
|
preset: options.preset || null,
|
|
101
105
|
fontFile: options.fontFile || null,
|
|
102
106
|
emojiFont: options.emojiFont || null,
|
|
@@ -279,6 +283,9 @@ class SIMPLEFFMPEG {
|
|
|
279
283
|
* @param {string} clipObjs[].text - Text content (for text clips)
|
|
280
284
|
* @param {string} clipObjs[].mode - Text mode: 'static', 'word-replace', 'word-sequential', 'karaoke'
|
|
281
285
|
* @param {string} clipObjs[].kenBurns - Ken Burns effect for images: 'zoom-in', 'zoom-out', 'pan-left', etc.
|
|
286
|
+
* @param {Object} options - Load options
|
|
287
|
+
* @param {boolean} options.skipFileChecks - Override file existence checks for media URLs
|
|
288
|
+
* @param {boolean} options.skipExtensionsCheck - Override extension/type validation for media URLs
|
|
282
289
|
* @returns {Promise<void>} Resolves when all clips are loaded
|
|
283
290
|
* @throws {ValidationError} If clip configuration is invalid
|
|
284
291
|
*
|
|
@@ -288,7 +295,7 @@ class SIMPLEFFMPEG {
|
|
|
288
295
|
* { type: 'text', text: 'Hello', position: 1, end: 4, fontSize: 48 }
|
|
289
296
|
* ]);
|
|
290
297
|
*/
|
|
291
|
-
async load(clipObjs) {
|
|
298
|
+
async load(clipObjs, options = {}) {
|
|
292
299
|
// Guard against concurrent load() calls
|
|
293
300
|
if (this._isLoading) {
|
|
294
301
|
throw new SimpleffmpegError(
|
|
@@ -308,11 +315,21 @@ class SIMPLEFFMPEG {
|
|
|
308
315
|
|
|
309
316
|
// Resolve shorthand: duration → end, auto-sequential positioning
|
|
310
317
|
const resolved = resolveClips(clipObjs);
|
|
318
|
+
const skipExtensionsCheck =
|
|
319
|
+
typeof options.skipExtensionsCheck === "boolean"
|
|
320
|
+
? options.skipExtensionsCheck
|
|
321
|
+
: this.options.skipExtensionsCheck;
|
|
322
|
+
const skipFileChecks =
|
|
323
|
+
typeof options.skipFileChecks === "boolean"
|
|
324
|
+
? options.skipFileChecks
|
|
325
|
+
: this.options.skipFileChecks;
|
|
311
326
|
|
|
312
327
|
// Merge resolution errors into validation
|
|
313
328
|
const result = validateConfig(resolved.clips, {
|
|
314
329
|
width: this.options.width,
|
|
315
330
|
height: this.options.height,
|
|
331
|
+
skipFileChecks,
|
|
332
|
+
skipExtensionsCheck,
|
|
316
333
|
});
|
|
317
334
|
|
|
318
335
|
// Prepend resolution errors (e.g. duration+end conflict)
|
|
@@ -561,6 +578,20 @@ class SIMPLEFFMPEG {
|
|
|
561
578
|
}
|
|
562
579
|
}
|
|
563
580
|
|
|
581
|
+
// Expand fullDuration clips now that finalVisualEnd is known
|
|
582
|
+
for (const clip of this.effectClips) {
|
|
583
|
+
if (clip.fullDuration === true) {
|
|
584
|
+
clip.position = clip.position ?? 0;
|
|
585
|
+
clip.end = finalVisualEnd;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
for (const clip of this.textClips) {
|
|
589
|
+
if (clip.fullDuration === true) {
|
|
590
|
+
clip.position = clip.position ?? 0;
|
|
591
|
+
clip.end = finalVisualEnd;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
564
595
|
// Overlay effects (adjustment layer clips) on the composed video output.
|
|
565
596
|
if (this.effectClips.length > 0 && hasVideo && finalVideoLabel) {
|
|
566
597
|
const effectRes = buildEffectFilters(this.effectClips, finalVideoLabel);
|
|
@@ -1272,6 +1303,7 @@ class SIMPLEFFMPEG {
|
|
|
1272
1303
|
* @param {Array} clips - Array of clip objects to validate
|
|
1273
1304
|
* @param {Object} options - Validation options
|
|
1274
1305
|
* @param {boolean} options.skipFileChecks - Skip file existence checks (useful for AI)
|
|
1306
|
+
* @param {boolean} options.skipExtensionsCheck - Skip media URL extension/type checks (video/image)
|
|
1275
1307
|
* @returns {Object} Validation result { valid, errors, warnings }
|
|
1276
1308
|
*
|
|
1277
1309
|
* @example
|
package/types/index.d.mts
CHANGED
|
@@ -4,9 +4,7 @@ declare namespace SIMPLEFFMPEG {
|
|
|
4
4
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
5
5
|
|
|
6
6
|
/** Base error class for all simple-ffmpeg errors */
|
|
7
|
-
class SimpleffmpegError extends Error {
|
|
8
|
-
name: "SimpleffmpegError";
|
|
9
|
-
}
|
|
7
|
+
class SimpleffmpegError extends Error {}
|
|
10
8
|
|
|
11
9
|
/** Thrown when clip validation fails */
|
|
12
10
|
class ValidationError extends SimpleffmpegError {
|
|
@@ -159,8 +157,12 @@ declare namespace SIMPLEFFMPEG {
|
|
|
159
157
|
interface TextClip {
|
|
160
158
|
type: "text";
|
|
161
159
|
text?: string;
|
|
162
|
-
|
|
163
|
-
|
|
160
|
+
/** Start time on timeline in seconds. Required unless fullDuration is true. */
|
|
161
|
+
position?: number;
|
|
162
|
+
/** End time on timeline in seconds. Mutually exclusive with fullDuration. */
|
|
163
|
+
end?: number;
|
|
164
|
+
/** When true, the clip spans the full visual timeline (position 0 to end of last video/image/color clip). Mutually exclusive with end and duration. */
|
|
165
|
+
fullDuration?: boolean;
|
|
164
166
|
mode?: TextMode;
|
|
165
167
|
words?: TextWordWindow[];
|
|
166
168
|
wordTimestamps?: number[];
|
|
@@ -337,12 +339,14 @@ declare namespace SIMPLEFFMPEG {
|
|
|
337
339
|
interface EffectClip {
|
|
338
340
|
type: "effect";
|
|
339
341
|
effect: EffectName;
|
|
340
|
-
/** Start time on timeline in seconds. Required
|
|
341
|
-
position
|
|
342
|
-
/** End time on timeline in seconds. Mutually exclusive with duration. */
|
|
342
|
+
/** Start time on timeline in seconds. Required unless fullDuration is true. */
|
|
343
|
+
position?: number;
|
|
344
|
+
/** End time on timeline in seconds. Mutually exclusive with duration and fullDuration. */
|
|
343
345
|
end?: number;
|
|
344
|
-
/** Duration in seconds (alternative to end). end = position + duration. */
|
|
346
|
+
/** Duration in seconds (alternative to end). end = position + duration. Mutually exclusive with fullDuration. */
|
|
345
347
|
duration?: number;
|
|
348
|
+
/** When true, the clip spans the full visual timeline (position 0 to end of last video/image/color clip). Mutually exclusive with end and duration. */
|
|
349
|
+
fullDuration?: boolean;
|
|
346
350
|
/** Ramp-in duration in seconds */
|
|
347
351
|
fadeIn?: number;
|
|
348
352
|
/** Ramp-out duration in seconds */
|
|
@@ -430,6 +434,8 @@ declare namespace SIMPLEFFMPEG {
|
|
|
430
434
|
interface ValidateOptions {
|
|
431
435
|
/** Skip file existence checks (useful for AI generating configs before files exist) */
|
|
432
436
|
skipFileChecks?: boolean;
|
|
437
|
+
/** Skip media URL extension/type checks for video/image clips */
|
|
438
|
+
skipExtensionsCheck?: boolean;
|
|
433
439
|
/** Project width - used to validate Ken Burns images are large enough */
|
|
434
440
|
width?: number;
|
|
435
441
|
/** Project height - used to validate Ken Burns images are large enough */
|
|
@@ -449,6 +455,10 @@ declare namespace SIMPLEFFMPEG {
|
|
|
449
455
|
height?: number;
|
|
450
456
|
/** Validation mode: 'warn' logs warnings, 'strict' throws on warnings (default: 'warn') */
|
|
451
457
|
validationMode?: "warn" | "strict";
|
|
458
|
+
/** Skip file existence checks during load() validation */
|
|
459
|
+
skipFileChecks?: boolean;
|
|
460
|
+
/** Skip media URL extension/type checks for video/image clips during load() validation */
|
|
461
|
+
skipExtensionsCheck?: boolean;
|
|
452
462
|
/** Default font file path (.ttf, .otf) applied to all text clips. Individual clips can override this with their own fontFile. */
|
|
453
463
|
fontFile?: string;
|
|
454
464
|
/** Path to a .ttf/.otf emoji font for rendering emoji in text overlays (opt-in). Without this, emoji are silently stripped from text. Recommended: Noto Emoji (B&W outline). */
|
|
@@ -864,6 +874,14 @@ declare namespace SIMPLEFFMPEG {
|
|
|
864
874
|
totalDuration: number;
|
|
865
875
|
}
|
|
866
876
|
|
|
877
|
+
/** Options for load() */
|
|
878
|
+
interface LoadOptions {
|
|
879
|
+
/** Override file existence checks for media URLs */
|
|
880
|
+
skipFileChecks?: boolean;
|
|
881
|
+
/** Override extension/type validation for media URLs (video/image) */
|
|
882
|
+
skipExtensionsCheck?: boolean;
|
|
883
|
+
}
|
|
884
|
+
|
|
867
885
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
868
886
|
// Media Info (probe)
|
|
869
887
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -907,8 +925,12 @@ declare class SIMPLEFFMPEG {
|
|
|
907
925
|
/**
|
|
908
926
|
* Load clips into the project
|
|
909
927
|
* @param clips Array of clip descriptors (video, audio, text, image, music)
|
|
928
|
+
* @param options Load options
|
|
910
929
|
*/
|
|
911
|
-
load(
|
|
930
|
+
load(
|
|
931
|
+
clips: SIMPLEFFMPEG.Clip[],
|
|
932
|
+
options?: SIMPLEFFMPEG.LoadOptions
|
|
933
|
+
): Promise<void[]>;
|
|
912
934
|
|
|
913
935
|
/**
|
|
914
936
|
* Get a preview of the FFmpeg command without executing it (dry-run)
|
|
@@ -979,6 +1001,26 @@ declare class SIMPLEFFMPEG {
|
|
|
979
1001
|
*/
|
|
980
1002
|
static getDuration(clips: SIMPLEFFMPEG.Clip[]): number;
|
|
981
1003
|
|
|
1004
|
+
/**
|
|
1005
|
+
* Calculate the total transition overlap for a clips configuration.
|
|
1006
|
+
* Returns the total seconds consumed by xfade transition overlaps
|
|
1007
|
+
* among visual clips (video, image, color).
|
|
1008
|
+
*
|
|
1009
|
+
* Pure function — same clips always produce the same result. No file I/O.
|
|
1010
|
+
*
|
|
1011
|
+
* @param clips - Array of clip objects
|
|
1012
|
+
* @returns Total transition overlap in seconds
|
|
1013
|
+
*
|
|
1014
|
+
* @example
|
|
1015
|
+
* const overlap = SIMPLEFFMPEG.getTransitionOverlap([
|
|
1016
|
+
* { type: "video", url: "./a.mp4", duration: 5 },
|
|
1017
|
+
* { type: "video", url: "./b.mp4", duration: 10,
|
|
1018
|
+
* transition: { type: "fade", duration: 0.5 } },
|
|
1019
|
+
* ]);
|
|
1020
|
+
* // overlap === 0.5
|
|
1021
|
+
*/
|
|
1022
|
+
static getTransitionOverlap(clips: SIMPLEFFMPEG.Clip[]): number;
|
|
1023
|
+
|
|
982
1024
|
/**
|
|
983
1025
|
* Probe a media file and return comprehensive metadata.
|
|
984
1026
|
*
|
|
@@ -1066,36 +1108,6 @@ declare class SIMPLEFFMPEG {
|
|
|
1066
1108
|
*/
|
|
1067
1109
|
static formatValidationResult(result: SIMPLEFFMPEG.ValidationResult): string;
|
|
1068
1110
|
|
|
1069
|
-
/**
|
|
1070
|
-
* Validation error codes for programmatic handling
|
|
1071
|
-
*/
|
|
1072
|
-
static readonly ValidationCodes: typeof SIMPLEFFMPEG.ValidationCodes;
|
|
1073
|
-
|
|
1074
|
-
/**
|
|
1075
|
-
* Base error class for all simple-ffmpeg errors
|
|
1076
|
-
*/
|
|
1077
|
-
static readonly SimpleffmpegError: typeof SIMPLEFFMPEG.SimpleffmpegError;
|
|
1078
|
-
|
|
1079
|
-
/**
|
|
1080
|
-
* Thrown when clip validation fails
|
|
1081
|
-
*/
|
|
1082
|
-
static readonly ValidationError: typeof SIMPLEFFMPEG.ValidationError;
|
|
1083
|
-
|
|
1084
|
-
/**
|
|
1085
|
-
* Thrown when FFmpeg command execution fails
|
|
1086
|
-
*/
|
|
1087
|
-
static readonly FFmpegError: typeof SIMPLEFFMPEG.FFmpegError;
|
|
1088
|
-
|
|
1089
|
-
/**
|
|
1090
|
-
* Thrown when a media file cannot be found or accessed
|
|
1091
|
-
*/
|
|
1092
|
-
static readonly MediaNotFoundError: typeof SIMPLEFFMPEG.MediaNotFoundError;
|
|
1093
|
-
|
|
1094
|
-
/**
|
|
1095
|
-
* Thrown when export is cancelled via AbortSignal
|
|
1096
|
-
*/
|
|
1097
|
-
static readonly ExportCancelledError: typeof SIMPLEFFMPEG.ExportCancelledError;
|
|
1098
|
-
|
|
1099
1111
|
/**
|
|
1100
1112
|
* Get the clip schema as formatted prompt-ready text.
|
|
1101
1113
|
* Returns a structured description of all clip types accepted by load(),
|
package/types/index.d.ts
CHANGED
|
@@ -4,9 +4,7 @@ declare namespace SIMPLEFFMPEG {
|
|
|
4
4
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
5
5
|
|
|
6
6
|
/** Base error class for all simple-ffmpeg errors */
|
|
7
|
-
class SimpleffmpegError extends Error {
|
|
8
|
-
name: "SimpleffmpegError";
|
|
9
|
-
}
|
|
7
|
+
class SimpleffmpegError extends Error {}
|
|
10
8
|
|
|
11
9
|
/** Thrown when clip validation fails */
|
|
12
10
|
class ValidationError extends SimpleffmpegError {
|
|
@@ -159,8 +157,12 @@ declare namespace SIMPLEFFMPEG {
|
|
|
159
157
|
interface TextClip {
|
|
160
158
|
type: "text";
|
|
161
159
|
text?: string;
|
|
162
|
-
|
|
163
|
-
|
|
160
|
+
/** Start time on timeline in seconds. Required unless fullDuration is true. */
|
|
161
|
+
position?: number;
|
|
162
|
+
/** End time on timeline in seconds. Mutually exclusive with fullDuration. */
|
|
163
|
+
end?: number;
|
|
164
|
+
/** When true, the clip spans the full visual timeline (position 0 to end of last video/image/color clip). Mutually exclusive with end and duration. */
|
|
165
|
+
fullDuration?: boolean;
|
|
164
166
|
mode?: TextMode;
|
|
165
167
|
words?: TextWordWindow[];
|
|
166
168
|
wordTimestamps?: number[];
|
|
@@ -337,12 +339,14 @@ declare namespace SIMPLEFFMPEG {
|
|
|
337
339
|
interface EffectClip {
|
|
338
340
|
type: "effect";
|
|
339
341
|
effect: EffectName;
|
|
340
|
-
/** Start time on timeline in seconds. Required
|
|
341
|
-
position
|
|
342
|
-
/** End time on timeline in seconds. Mutually exclusive with duration. */
|
|
342
|
+
/** Start time on timeline in seconds. Required unless fullDuration is true. */
|
|
343
|
+
position?: number;
|
|
344
|
+
/** End time on timeline in seconds. Mutually exclusive with duration and fullDuration. */
|
|
343
345
|
end?: number;
|
|
344
|
-
/** Duration in seconds (alternative to end). end = position + duration. */
|
|
346
|
+
/** Duration in seconds (alternative to end). end = position + duration. Mutually exclusive with fullDuration. */
|
|
345
347
|
duration?: number;
|
|
348
|
+
/** When true, the clip spans the full visual timeline (position 0 to end of last video/image/color clip). Mutually exclusive with end and duration. */
|
|
349
|
+
fullDuration?: boolean;
|
|
346
350
|
/** Ramp-in duration in seconds */
|
|
347
351
|
fadeIn?: number;
|
|
348
352
|
/** Ramp-out duration in seconds */
|
|
@@ -430,6 +434,8 @@ declare namespace SIMPLEFFMPEG {
|
|
|
430
434
|
interface ValidateOptions {
|
|
431
435
|
/** Skip file existence checks (useful for AI generating configs before files exist) */
|
|
432
436
|
skipFileChecks?: boolean;
|
|
437
|
+
/** Skip media URL extension/type checks for video/image clips */
|
|
438
|
+
skipExtensionsCheck?: boolean;
|
|
433
439
|
/** Project width - used to validate Ken Burns images are large enough */
|
|
434
440
|
width?: number;
|
|
435
441
|
/** Project height - used to validate Ken Burns images are large enough */
|
|
@@ -449,6 +455,10 @@ declare namespace SIMPLEFFMPEG {
|
|
|
449
455
|
height?: number;
|
|
450
456
|
/** Validation mode: 'warn' logs warnings, 'strict' throws on warnings (default: 'warn') */
|
|
451
457
|
validationMode?: "warn" | "strict";
|
|
458
|
+
/** Skip file existence checks during load() validation */
|
|
459
|
+
skipFileChecks?: boolean;
|
|
460
|
+
/** Skip media URL extension/type checks for video/image clips during load() validation */
|
|
461
|
+
skipExtensionsCheck?: boolean;
|
|
452
462
|
/** Default font file path (.ttf, .otf) applied to all text clips. Individual clips can override this with their own fontFile. */
|
|
453
463
|
fontFile?: string;
|
|
454
464
|
/** Path to a .ttf/.otf emoji font for rendering emoji in text overlays (opt-in). Without this, emoji are silently stripped from text. Recommended: Noto Emoji (B&W outline). */
|
|
@@ -864,6 +874,14 @@ declare namespace SIMPLEFFMPEG {
|
|
|
864
874
|
totalDuration: number;
|
|
865
875
|
}
|
|
866
876
|
|
|
877
|
+
/** Options for load() */
|
|
878
|
+
interface LoadOptions {
|
|
879
|
+
/** Override file existence checks for media URLs */
|
|
880
|
+
skipFileChecks?: boolean;
|
|
881
|
+
/** Override extension/type validation for media URLs (video/image) */
|
|
882
|
+
skipExtensionsCheck?: boolean;
|
|
883
|
+
}
|
|
884
|
+
|
|
867
885
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
868
886
|
// Media Info (probe)
|
|
869
887
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -907,8 +925,12 @@ declare class SIMPLEFFMPEG {
|
|
|
907
925
|
/**
|
|
908
926
|
* Load clips into the project
|
|
909
927
|
* @param clips Array of clip descriptors (video, audio, text, image, music)
|
|
928
|
+
* @param options Load options
|
|
910
929
|
*/
|
|
911
|
-
load(
|
|
930
|
+
load(
|
|
931
|
+
clips: SIMPLEFFMPEG.Clip[],
|
|
932
|
+
options?: SIMPLEFFMPEG.LoadOptions
|
|
933
|
+
): Promise<void[]>;
|
|
912
934
|
|
|
913
935
|
/**
|
|
914
936
|
* Get a preview of the FFmpeg command without executing it (dry-run)
|
|
@@ -1086,36 +1108,6 @@ declare class SIMPLEFFMPEG {
|
|
|
1086
1108
|
*/
|
|
1087
1109
|
static formatValidationResult(result: SIMPLEFFMPEG.ValidationResult): string;
|
|
1088
1110
|
|
|
1089
|
-
/**
|
|
1090
|
-
* Validation error codes for programmatic handling
|
|
1091
|
-
*/
|
|
1092
|
-
static readonly ValidationCodes: typeof SIMPLEFFMPEG.ValidationCodes;
|
|
1093
|
-
|
|
1094
|
-
/**
|
|
1095
|
-
* Base error class for all simple-ffmpeg errors
|
|
1096
|
-
*/
|
|
1097
|
-
static readonly SimpleffmpegError: typeof SIMPLEFFMPEG.SimpleffmpegError;
|
|
1098
|
-
|
|
1099
|
-
/**
|
|
1100
|
-
* Thrown when clip validation fails
|
|
1101
|
-
*/
|
|
1102
|
-
static readonly ValidationError: typeof SIMPLEFFMPEG.ValidationError;
|
|
1103
|
-
|
|
1104
|
-
/**
|
|
1105
|
-
* Thrown when FFmpeg command execution fails
|
|
1106
|
-
*/
|
|
1107
|
-
static readonly FFmpegError: typeof SIMPLEFFMPEG.FFmpegError;
|
|
1108
|
-
|
|
1109
|
-
/**
|
|
1110
|
-
* Thrown when a media file cannot be found or accessed
|
|
1111
|
-
*/
|
|
1112
|
-
static readonly MediaNotFoundError: typeof SIMPLEFFMPEG.MediaNotFoundError;
|
|
1113
|
-
|
|
1114
|
-
/**
|
|
1115
|
-
* Thrown when export is cancelled via AbortSignal
|
|
1116
|
-
*/
|
|
1117
|
-
static readonly ExportCancelledError: typeof SIMPLEFFMPEG.ExportCancelledError;
|
|
1118
|
-
|
|
1119
1111
|
/**
|
|
1120
1112
|
* Get the clip schema as formatted prompt-ready text.
|
|
1121
1113
|
* Returns a structured description of all clip types accepted by load(),
|