simple-ffmpegjs 0.3.6 → 0.4.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.
@@ -7,6 +7,7 @@ const Loaders = require("./loaders");
7
7
  const { buildVideoFilter } = require("./ffmpeg/video_builder");
8
8
  const { buildAudioForVideoClips } = require("./ffmpeg/audio_builder");
9
9
  const { buildBackgroundMusicMix } = require("./ffmpeg/bgm_builder");
10
+ const { buildEffectFilters } = require("./ffmpeg/effect_builder");
10
11
  const {
11
12
  getClipAudioString,
12
13
  hasProblematicChars,
@@ -16,7 +17,6 @@ const {
16
17
  validateConfig,
17
18
  formatValidationResult,
18
19
  ValidationCodes,
19
- normalizeFillGaps,
20
20
  } = require("./core/validation");
21
21
  const {
22
22
  SimpleffmpegError,
@@ -45,7 +45,6 @@ const {
45
45
  const { getSchema, getSchemaModules } = require("./schema");
46
46
  const { resolveClips } = require("./core/resolve");
47
47
  const { probeMedia } = require("./core/media_info");
48
- const { detectVisualGaps } = require("./core/gaps");
49
48
 
50
49
  class SIMPLEFFMPEG {
51
50
  /**
@@ -57,7 +56,6 @@ class SIMPLEFFMPEG {
57
56
  * @param {number} options.fps - Frames per second (default: 30)
58
57
  * @param {string} options.preset - Platform preset ('tiktok', 'youtube', 'instagram-post', etc.)
59
58
  * @param {string} options.validationMode - Validation behavior: 'warn' or 'strict' (default: 'warn')
60
- * @param {string|boolean} options.fillGaps - Gap handling: 'none'/false (disabled), true/'black' (black fill), or any valid FFmpeg color (default: 'none')
61
59
  *
62
60
  * @example
63
61
  * const project = new SIMPLEFFMPEG({ preset: 'tiktok' });
@@ -66,16 +64,7 @@ class SIMPLEFFMPEG {
66
64
  * const project = new SIMPLEFFMPEG({
67
65
  * width: 1920,
68
66
  * height: 1080,
69
- * fps: 30,
70
- * fillGaps: 'black'
71
- * });
72
- *
73
- * @example
74
- * // Fill gaps with a custom color
75
- * const project = new SIMPLEFFMPEG({
76
- * width: 1920,
77
- * height: 1080,
78
- * fillGaps: '#1a1a2e' // dark blue-gray
67
+ * fps: 30
79
68
  * });
80
69
  */
81
70
  constructor(options = {}) {
@@ -91,24 +80,18 @@ class SIMPLEFFMPEG {
91
80
  );
92
81
  }
93
82
 
94
- // Normalise and validate fillGaps
95
- const fillGapsResult = normalizeFillGaps(options.fillGaps);
96
- if (fillGapsResult.error) {
97
- throw new ValidationError(fillGapsResult.error);
98
- }
99
-
100
83
  // Explicit options override preset values
101
84
  this.options = {
102
85
  fps: options.fps || presetConfig.fps || C.DEFAULT_FPS,
103
86
  width: options.width || presetConfig.width || C.DEFAULT_WIDTH,
104
87
  height: options.height || presetConfig.height || C.DEFAULT_HEIGHT,
105
88
  validationMode: options.validationMode || C.DEFAULT_VALIDATION_MODE,
106
- fillGaps: fillGapsResult.color, // "none" | valid FFmpeg color
107
89
  preset: options.preset || null,
108
90
  };
109
91
  this.videoOrAudioClips = [];
110
92
  this.textClips = [];
111
93
  this.subtitleClips = [];
94
+ this.effectClips = [];
112
95
  this.filesToClean = [];
113
96
  this._isLoading = false; // Guard against concurrent load() calls
114
97
  this._isExporting = false; // Guard against concurrent export() calls
@@ -121,9 +104,15 @@ class SIMPLEFFMPEG {
121
104
  */
122
105
  _getInputStreams() {
123
106
  return this.videoOrAudioClips
107
+ .filter((clip) => {
108
+ // Flat color clips use the color= filter source — no file input needed
109
+ if (clip.type === "color" && clip._isFlatColor) return false;
110
+ return true;
111
+ })
124
112
  .map((clip) => {
125
113
  const escapedUrl = escapeFilePath(clip.url);
126
- if (clip.type === "image") {
114
+ // Gradient color clips and image clips are looped images
115
+ if (clip.type === "image" || (clip.type === "color" && !clip._isFlatColor)) {
127
116
  const duration = Math.max(0, clip.end - clip.position || 0);
128
117
  return `-loop 1 -t ${duration} -i "${escapedUrl}"`;
129
118
  }
@@ -204,7 +193,7 @@ class SIMPLEFFMPEG {
204
193
  * Load clips into the project for processing
205
194
  *
206
195
  * @param {Array} clipObjs - Array of clip configuration objects
207
- * @param {string} clipObjs[].type - Clip type: 'video', 'audio', 'image', 'text', 'music', 'backgroundAudio', 'subtitle'
196
+ * @param {string} clipObjs[].type - Clip type: 'video', 'audio', 'image', 'color', 'text', 'effect', 'music', 'backgroundAudio', 'subtitle'
208
197
  * @param {string} clipObjs[].url - Media file path (required for video, audio, image, music, subtitle)
209
198
  * @param {number} clipObjs[].position - Start time on timeline in seconds
210
199
  * @param {number} clipObjs[].end - End time on timeline in seconds
@@ -239,7 +228,6 @@ class SIMPLEFFMPEG {
239
228
 
240
229
  // Merge resolution errors into validation
241
230
  const result = validateConfig(resolved.clips, {
242
- fillGaps: this.options.fillGaps,
243
231
  width: this.options.width,
244
232
  height: this.options.height,
245
233
  });
@@ -271,12 +259,16 @@ class SIMPLEFFMPEG {
271
259
  if (clipObj.type === "video" || clipObj.type === "audio") {
272
260
  clipObj.volume = clipObj.volume != null ? clipObj.volume : 1;
273
261
  clipObj.cutFrom = clipObj.cutFrom || 0;
274
- if (clipObj.type === "video" && clipObj.transition) {
275
- clipObj.transition = {
276
- type: clipObj.transition.type || clipObj.transition,
277
- duration: clipObj.transition.duration || 0.5,
278
- };
279
- }
262
+ }
263
+ // Normalize transitions for all visual clip types
264
+ if (
265
+ (clipObj.type === "video" || clipObj.type === "image" || clipObj.type === "color") &&
266
+ clipObj.transition
267
+ ) {
268
+ clipObj.transition = {
269
+ type: clipObj.transition.type || clipObj.transition,
270
+ duration: clipObj.transition.duration || 0.5,
271
+ };
280
272
  }
281
273
  if (clipObj.type === "video") {
282
274
  return Loaders.loadVideo(this, clipObj);
@@ -287,9 +279,15 @@ class SIMPLEFFMPEG {
287
279
  if (clipObj.type === "text") {
288
280
  return Loaders.loadText(this, clipObj);
289
281
  }
282
+ if (clipObj.type === "effect") {
283
+ return Loaders.loadEffect(this, clipObj);
284
+ }
290
285
  if (clipObj.type === "image") {
291
286
  return Loaders.loadImage(this, clipObj);
292
287
  }
288
+ if (clipObj.type === "color") {
289
+ return Loaders.loadColor(this, clipObj);
290
+ }
293
291
  if (clipObj.type === "music" || clipObj.type === "backgroundAudio") {
294
292
  return Loaders.loadBackgroundAudio(this, clipObj);
295
293
  }
@@ -397,8 +395,22 @@ class SIMPLEFFMPEG {
397
395
  })
398
396
  );
399
397
 
398
+ // Build a mapping from clip to its FFmpeg input stream index.
399
+ // Flat color clips use the color= filter source and do not have file inputs,
400
+ // so they are skipped by _getInputStreams(). All other clips' indices must
401
+ // account for this offset.
402
+ this._inputIndexMap = new Map();
403
+ let _inputIdx = 0;
404
+ for (const clip of this.videoOrAudioClips) {
405
+ if (clip.type === "color" && clip._isFlatColor) {
406
+ continue; // No file input for flat color clips
407
+ }
408
+ this._inputIndexMap.set(clip, _inputIdx);
409
+ _inputIdx++;
410
+ }
411
+
400
412
  const videoClips = this.videoOrAudioClips.filter(
401
- (clip) => clip.type === "video" || clip.type === "image"
413
+ (clip) => clip.type === "video" || clip.type === "image" || clip.type === "color"
402
414
  );
403
415
  const audioClips = this.videoOrAudioClips.filter(
404
416
  (clip) => clip.type === "audio"
@@ -442,56 +454,6 @@ class SIMPLEFFMPEG {
442
454
  .map((c) => (typeof c.end === "number" ? c.end : 0));
443
455
  const bgOrAudioEnd = audioEnds.length > 0 ? Math.max(...audioEnds) : 0;
444
456
 
445
- // Compute desired timeline end for trailing gap filling.
446
- // When fillGaps is enabled, extend the video to cover text/audio clips
447
- // that extend past the last visual clip (e.g. ending with text on black).
448
- //
449
- // The trailing gap duration must be precise so the video output ends
450
- // exactly when the last content ends. Two factors affect this:
451
- //
452
- // 1. Transition compensation — when active (default), text timestamps
453
- // shift left by the cumulative transition overlap, so the target end
454
- // is the compensated value. When off, use the raw overall end.
455
- //
456
- // 2. Existing gaps — leading/middle gaps that will also be filled add
457
- // to the video output but are NOT included in totalVideoDuration.
458
- // We must subtract them so the trailing gap isn't oversized.
459
- let timelineEnd;
460
- if (this.options.fillGaps !== "none" && videoClips.length > 0) {
461
- const visualEnd = Math.max(...videoClips.map((c) => c.end || 0));
462
- const overallEnd = Math.max(visualEnd, textEnd, bgOrAudioEnd);
463
- if (overallEnd - visualEnd > 1e-3) {
464
- // Target output duration depends on whether text is compensated
465
- let desiredOutputDuration;
466
- if (exportOptions.compensateTransitions && videoClips.length > 1) {
467
- const transitionOverlap = this._getTransitionOffsetAt(
468
- videoClips,
469
- overallEnd
470
- );
471
- desiredOutputDuration = overallEnd - transitionOverlap;
472
- } else {
473
- desiredOutputDuration = overallEnd;
474
- }
475
-
476
- // Account for existing gaps (leading + middle) that will also be
477
- // filled — these add to the video output but aren't reflected in
478
- // totalVideoDuration (which only sums clip durations − transitions).
479
- const existingGaps = detectVisualGaps(videoClips);
480
- const existingGapDuration = existingGaps.reduce(
481
- (sum, g) => sum + g.duration,
482
- 0
483
- );
484
- const videoOutputBeforeTrailing =
485
- totalVideoDuration + existingGapDuration;
486
-
487
- const trailingGapDuration =
488
- desiredOutputDuration - videoOutputBeforeTrailing;
489
- if (trailingGapDuration > 1e-3) {
490
- timelineEnd = visualEnd + trailingGapDuration;
491
- }
492
- }
493
- }
494
-
495
457
  let finalVisualEnd =
496
458
  videoClips.length > 0
497
459
  ? Math.max(...videoClips.map((c) => c.end))
@@ -499,21 +461,18 @@ class SIMPLEFFMPEG {
499
461
 
500
462
  // Build video filter
501
463
  if (videoClips.length > 0) {
502
- const vres = buildVideoFilter(this, videoClips, { timelineEnd });
464
+ const vres = buildVideoFilter(this, videoClips);
503
465
  filterComplex += vres.filter;
504
466
  finalVideoLabel = vres.finalVideoLabel;
505
467
  hasVideo = vres.hasVideo;
506
468
 
507
- // Update durations to account for gap fills (including trailing gaps).
508
- // videoDuration reflects the actual output length of the video filter
509
- // chain, which includes any black gap-fill clips.
510
- if (typeof vres.videoDuration === "number" && vres.videoDuration > 0) {
511
- totalVideoDuration = vres.videoDuration;
512
- }
513
469
  // Use the actual video output length for finalVisualEnd so that
514
470
  // audio trim and BGM duration match the real video stream length,
515
471
  // rather than an original-timeline position that may differ due to
516
472
  // transition compression.
473
+ if (typeof vres.videoDuration === "number" && vres.videoDuration > 0) {
474
+ totalVideoDuration = vres.videoDuration;
475
+ }
517
476
  if (
518
477
  typeof vres.videoDuration === "number" &&
519
478
  vres.videoDuration > finalVisualEnd
@@ -522,6 +481,13 @@ class SIMPLEFFMPEG {
522
481
  }
523
482
  }
524
483
 
484
+ // Overlay effects (adjustment layer clips) on the composed video output.
485
+ if (this.effectClips.length > 0 && hasVideo && finalVideoLabel) {
486
+ const effectRes = buildEffectFilters(this.effectClips, finalVideoLabel);
487
+ filterComplex += effectRes.filter;
488
+ finalVideoLabel = effectRes.finalVideoLabel || finalVideoLabel;
489
+ }
490
+
525
491
  // Audio for video clips (aligned amix)
526
492
  // Compute cumulative transition offsets so audio adelay values
527
493
  // match the xfade-compressed video timeline.
@@ -545,7 +511,9 @@ class SIMPLEFFMPEG {
545
511
  let audioString = "";
546
512
  let audioConcatInputs = [];
547
513
  audioClips.forEach((clip) => {
548
- const inputIndex = this.videoOrAudioClips.indexOf(clip);
514
+ const inputIndex = this._inputIndexMap
515
+ ? this._inputIndexMap.get(clip)
516
+ : this.videoOrAudioClips.indexOf(clip);
549
517
  const { audioStringPart, audioConcatInput } = getClipAudioString(
550
518
  clip,
551
519
  inputIndex
@@ -1203,7 +1171,6 @@ class SIMPLEFFMPEG {
1203
1171
  * @param {Array} clips - Array of clip objects to validate
1204
1172
  * @param {Object} options - Validation options
1205
1173
  * @param {boolean} options.skipFileChecks - Skip file existence checks (useful for AI)
1206
- * @param {string|boolean} options.fillGaps - Gap handling ('none'/false to disable, or any valid FFmpeg color) - affects gap validation
1207
1174
  * @returns {Object} Validation result { valid, errors, warnings }
1208
1175
  *
1209
1176
  * @example
@@ -1250,9 +1217,9 @@ class SIMPLEFFMPEG {
1250
1217
  // Resolve shorthand (duration → end, auto-sequencing)
1251
1218
  const { clips: resolved } = resolveClips(clips);
1252
1219
 
1253
- // Filter to visual clips (video + image)
1220
+ // Filter to visual clips (video + image + color)
1254
1221
  const visual = resolved.filter(
1255
- (c) => c.type === "video" || c.type === "image"
1222
+ (c) => c.type === "video" || c.type === "image" || c.type === "color"
1256
1223
  );
1257
1224
 
1258
1225
  if (visual.length === 0) return 0;
@@ -1463,7 +1430,7 @@ class SIMPLEFFMPEG {
1463
1430
  * Get the list of available schema module IDs.
1464
1431
  * Use these IDs with getSchema({ include: [...] }) or getSchema({ exclude: [...] }).
1465
1432
  *
1466
- * @returns {string[]} Array of module IDs: ['video', 'audio', 'image', 'text', 'subtitle', 'music']
1433
+ * @returns {string[]} Array of module IDs: ['video', 'audio', 'image', 'color', 'effect', 'text', 'subtitle', 'music']
1467
1434
  */
1468
1435
  static getSchemaModules() {
1469
1436
  return getSchemaModules();
package/types/index.d.mts CHANGED
@@ -51,7 +51,9 @@ declare namespace SIMPLEFFMPEG {
51
51
  | "music"
52
52
  | "backgroundAudio"
53
53
  | "image"
54
- | "subtitle";
54
+ | "subtitle"
55
+ | "color"
56
+ | "effect";
55
57
 
56
58
  interface BaseClip {
57
59
  type: ClipType;
@@ -220,11 +222,82 @@ declare namespace SIMPLEFFMPEG {
220
222
  opacity?: number;
221
223
  }
222
224
 
225
+ /** Gradient specification for color clips */
226
+ interface GradientSpec {
227
+ type: "linear-gradient" | "radial-gradient";
228
+ /** Array of color strings (at least 2). Evenly distributed across the gradient. */
229
+ colors: string[];
230
+ /** For linear gradients: "vertical" (default), "horizontal", or angle in degrees */
231
+ direction?: "vertical" | "horizontal" | number;
232
+ }
233
+
234
+ /** Color clip — solid color or gradient for filling gaps, transitions, etc. */
235
+ interface ColorClip {
236
+ type: "color";
237
+ /** Flat color string (e.g. "black", "#FF0000") or gradient specification */
238
+ color: string | GradientSpec;
239
+ /** Start time on timeline in seconds. Omit to auto-sequence after previous visual clip. */
240
+ position?: number;
241
+ /** End time on timeline in seconds. Mutually exclusive with duration. */
242
+ end?: number;
243
+ /** Duration in seconds (alternative to end). end = position + duration. */
244
+ duration?: number;
245
+ /** Transition effect from the previous visual clip */
246
+ transition?: { type: string; duration: number };
247
+ }
248
+
249
+ type EffectName = "vignette" | "filmGrain" | "gaussianBlur" | "colorAdjust";
250
+ type EffectEasing = "linear" | "ease-in" | "ease-out" | "ease-in-out";
251
+
252
+ interface EffectParamsBase {
253
+ amount?: number;
254
+ }
255
+
256
+ interface VignetteEffectParams extends EffectParamsBase {
257
+ angle?: number;
258
+ }
259
+
260
+ interface FilmGrainEffectParams extends EffectParamsBase {
261
+ temporal?: boolean;
262
+ }
263
+
264
+ interface GaussianBlurEffectParams extends EffectParamsBase {
265
+ sigma?: number;
266
+ }
267
+
268
+ interface ColorAdjustEffectParams extends EffectParamsBase {
269
+ brightness?: number;
270
+ contrast?: number;
271
+ saturation?: number;
272
+ gamma?: number;
273
+ }
274
+
275
+ type EffectParams =
276
+ | VignetteEffectParams
277
+ | FilmGrainEffectParams
278
+ | GaussianBlurEffectParams
279
+ | ColorAdjustEffectParams;
280
+
281
+ /** Effect clip — timed overlay adjustment layer over composed video */
282
+ interface EffectClip {
283
+ type: "effect";
284
+ effect: EffectName;
285
+ position: number;
286
+ end?: number;
287
+ duration?: number;
288
+ fadeIn?: number;
289
+ fadeOut?: number;
290
+ easing?: EffectEasing;
291
+ params: EffectParams;
292
+ }
293
+
223
294
  type Clip =
224
295
  | VideoClip
225
296
  | AudioClip
226
297
  | BackgroundMusicClip
227
298
  | ImageClip
299
+ | ColorClip
300
+ | EffectClip
228
301
  | TextClip
229
302
  | SubtitleClip;
230
303
 
@@ -297,8 +370,6 @@ declare namespace SIMPLEFFMPEG {
297
370
  interface ValidateOptions {
298
371
  /** Skip file existence checks (useful for AI generating configs before files exist) */
299
372
  skipFileChecks?: boolean;
300
- /** Gap handling mode - affects timeline gap validation. Any valid FFmpeg color, or "none"/false to disable. */
301
- fillGaps?: "none" | string | boolean;
302
373
  /** Project width - used to validate Ken Burns images are large enough */
303
374
  width?: number;
304
375
  /** Project height - used to validate Ken Burns images are large enough */
@@ -318,8 +389,6 @@ declare namespace SIMPLEFFMPEG {
318
389
  height?: number;
319
390
  /** Validation mode: 'warn' logs warnings, 'strict' throws on warnings (default: 'warn') */
320
391
  validationMode?: "warn" | "strict";
321
- /** How to handle visual gaps: 'none'/false (disabled), true/'black' (black fill), or any valid FFmpeg color name/hex (default: 'none') */
322
- fillGaps?: "none" | string | boolean;
323
392
  }
324
393
 
325
394
  /** Log entry passed to onLog callback */
@@ -560,6 +629,8 @@ declare namespace SIMPLEFFMPEG {
560
629
  | "video"
561
630
  | "audio"
562
631
  | "image"
632
+ | "color"
633
+ | "effect"
563
634
  | "text"
564
635
  | "subtitle"
565
636
  | "music";
package/types/index.d.ts CHANGED
@@ -51,7 +51,9 @@ declare namespace SIMPLEFFMPEG {
51
51
  | "music"
52
52
  | "backgroundAudio"
53
53
  | "image"
54
- | "subtitle";
54
+ | "subtitle"
55
+ | "color"
56
+ | "effect";
55
57
 
56
58
  interface BaseClip {
57
59
  type: ClipType;
@@ -220,11 +222,93 @@ declare namespace SIMPLEFFMPEG {
220
222
  opacity?: number;
221
223
  }
222
224
 
225
+ /** Gradient specification for color clips */
226
+ interface GradientSpec {
227
+ type: "linear-gradient" | "radial-gradient";
228
+ /** Array of color strings (at least 2). Evenly distributed across the gradient. */
229
+ colors: string[];
230
+ /** For linear gradients: "vertical" (default), "horizontal", or angle in degrees */
231
+ direction?: "vertical" | "horizontal" | number;
232
+ }
233
+
234
+ /** Color clip — solid color or gradient for filling gaps, transitions, etc. */
235
+ interface ColorClip {
236
+ type: "color";
237
+ /** Flat color string (e.g. "black", "#FF0000") or gradient specification */
238
+ color: string | GradientSpec;
239
+ /** Start time on timeline in seconds. Omit to auto-sequence after previous visual clip. */
240
+ position?: number;
241
+ /** End time on timeline in seconds. Mutually exclusive with duration. */
242
+ end?: number;
243
+ /** Duration in seconds (alternative to end). end = position + duration. */
244
+ duration?: number;
245
+ /** Transition effect from the previous visual clip */
246
+ transition?: { type: string; duration: number };
247
+ }
248
+
249
+ type EffectName = "vignette" | "filmGrain" | "gaussianBlur" | "colorAdjust";
250
+ type EffectEasing = "linear" | "ease-in" | "ease-out" | "ease-in-out";
251
+
252
+ interface EffectParamsBase {
253
+ /** Base blend amount from 0 to 1 (default: 1) */
254
+ amount?: number;
255
+ }
256
+
257
+ interface VignetteEffectParams extends EffectParamsBase {
258
+ /** Vignette angle in radians (default: PI/5) */
259
+ angle?: number;
260
+ }
261
+
262
+ interface FilmGrainEffectParams extends EffectParamsBase {
263
+ /** Temporal grain changes every frame (default: true) */
264
+ temporal?: boolean;
265
+ }
266
+
267
+ interface GaussianBlurEffectParams extends EffectParamsBase {
268
+ /** Gaussian blur sigma (default derived from amount) */
269
+ sigma?: number;
270
+ }
271
+
272
+ interface ColorAdjustEffectParams extends EffectParamsBase {
273
+ brightness?: number;
274
+ contrast?: number;
275
+ saturation?: number;
276
+ gamma?: number;
277
+ }
278
+
279
+ type EffectParams =
280
+ | VignetteEffectParams
281
+ | FilmGrainEffectParams
282
+ | GaussianBlurEffectParams
283
+ | ColorAdjustEffectParams;
284
+
285
+ /** Effect clip — timed overlay adjustment layer over composed video */
286
+ interface EffectClip {
287
+ type: "effect";
288
+ effect: EffectName;
289
+ /** Start time on timeline in seconds. Required for effect clips. */
290
+ position: number;
291
+ /** End time on timeline in seconds. Mutually exclusive with duration. */
292
+ end?: number;
293
+ /** Duration in seconds (alternative to end). end = position + duration. */
294
+ duration?: number;
295
+ /** Ramp-in duration in seconds */
296
+ fadeIn?: number;
297
+ /** Ramp-out duration in seconds */
298
+ fadeOut?: number;
299
+ /** Envelope easing for fade ramps */
300
+ easing?: EffectEasing;
301
+ /** Effect-specific params */
302
+ params: EffectParams;
303
+ }
304
+
223
305
  type Clip =
224
306
  | VideoClip
225
307
  | AudioClip
226
308
  | BackgroundMusicClip
227
309
  | ImageClip
310
+ | ColorClip
311
+ | EffectClip
228
312
  | TextClip
229
313
  | SubtitleClip;
230
314
 
@@ -297,8 +381,6 @@ declare namespace SIMPLEFFMPEG {
297
381
  interface ValidateOptions {
298
382
  /** Skip file existence checks (useful for AI generating configs before files exist) */
299
383
  skipFileChecks?: boolean;
300
- /** Gap handling mode - affects timeline gap validation. Any valid FFmpeg color, or "none"/false to disable. */
301
- fillGaps?: "none" | string | boolean;
302
384
  /** Project width - used to validate Ken Burns images are large enough */
303
385
  width?: number;
304
386
  /** Project height - used to validate Ken Burns images are large enough */
@@ -318,8 +400,6 @@ declare namespace SIMPLEFFMPEG {
318
400
  height?: number;
319
401
  /** Validation mode: 'warn' logs warnings, 'strict' throws on warnings (default: 'warn') */
320
402
  validationMode?: "warn" | "strict";
321
- /** How to handle visual gaps: 'none'/false (disabled), true/'black' (black fill), or any valid FFmpeg color name/hex (default: 'none') */
322
- fillGaps?: "none" | string | boolean;
323
403
  }
324
404
 
325
405
  /** Log entry passed to onLog callback */
@@ -657,6 +737,8 @@ declare namespace SIMPLEFFMPEG {
657
737
  | "video"
658
738
  | "audio"
659
739
  | "image"
740
+ | "color"
741
+ | "effect"
660
742
  | "text"
661
743
  | "subtitle"
662
744
  | "music";