simple-ffmpegjs 0.3.5 → 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,
@@ -55,7 +56,6 @@ class SIMPLEFFMPEG {
55
56
  * @param {number} options.fps - Frames per second (default: 30)
56
57
  * @param {string} options.preset - Platform preset ('tiktok', 'youtube', 'instagram-post', etc.)
57
58
  * @param {string} options.validationMode - Validation behavior: 'warn' or 'strict' (default: 'warn')
58
- * @param {string} options.fillGaps - Gap handling: 'none' or 'black' (default: 'none')
59
59
  *
60
60
  * @example
61
61
  * const project = new SIMPLEFFMPEG({ preset: 'tiktok' });
@@ -64,8 +64,7 @@ class SIMPLEFFMPEG {
64
64
  * const project = new SIMPLEFFMPEG({
65
65
  * width: 1920,
66
66
  * height: 1080,
67
- * fps: 30,
68
- * fillGaps: 'black'
67
+ * fps: 30
69
68
  * });
70
69
  */
71
70
  constructor(options = {}) {
@@ -87,12 +86,12 @@ class SIMPLEFFMPEG {
87
86
  width: options.width || presetConfig.width || C.DEFAULT_WIDTH,
88
87
  height: options.height || presetConfig.height || C.DEFAULT_HEIGHT,
89
88
  validationMode: options.validationMode || C.DEFAULT_VALIDATION_MODE,
90
- fillGaps: options.fillGaps || "none", // 'none' | 'black'
91
89
  preset: options.preset || null,
92
90
  };
93
91
  this.videoOrAudioClips = [];
94
92
  this.textClips = [];
95
93
  this.subtitleClips = [];
94
+ this.effectClips = [];
96
95
  this.filesToClean = [];
97
96
  this._isLoading = false; // Guard against concurrent load() calls
98
97
  this._isExporting = false; // Guard against concurrent export() calls
@@ -105,9 +104,15 @@ class SIMPLEFFMPEG {
105
104
  */
106
105
  _getInputStreams() {
107
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
+ })
108
112
  .map((clip) => {
109
113
  const escapedUrl = escapeFilePath(clip.url);
110
- if (clip.type === "image") {
114
+ // Gradient color clips and image clips are looped images
115
+ if (clip.type === "image" || (clip.type === "color" && !clip._isFlatColor)) {
111
116
  const duration = Math.max(0, clip.end - clip.position || 0);
112
117
  return `-loop 1 -t ${duration} -i "${escapedUrl}"`;
113
118
  }
@@ -188,7 +193,7 @@ class SIMPLEFFMPEG {
188
193
  * Load clips into the project for processing
189
194
  *
190
195
  * @param {Array} clipObjs - Array of clip configuration objects
191
- * @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'
192
197
  * @param {string} clipObjs[].url - Media file path (required for video, audio, image, music, subtitle)
193
198
  * @param {number} clipObjs[].position - Start time on timeline in seconds
194
199
  * @param {number} clipObjs[].end - End time on timeline in seconds
@@ -223,7 +228,6 @@ class SIMPLEFFMPEG {
223
228
 
224
229
  // Merge resolution errors into validation
225
230
  const result = validateConfig(resolved.clips, {
226
- fillGaps: this.options.fillGaps,
227
231
  width: this.options.width,
228
232
  height: this.options.height,
229
233
  });
@@ -255,12 +259,16 @@ class SIMPLEFFMPEG {
255
259
  if (clipObj.type === "video" || clipObj.type === "audio") {
256
260
  clipObj.volume = clipObj.volume != null ? clipObj.volume : 1;
257
261
  clipObj.cutFrom = clipObj.cutFrom || 0;
258
- if (clipObj.type === "video" && clipObj.transition) {
259
- clipObj.transition = {
260
- type: clipObj.transition.type || clipObj.transition,
261
- duration: clipObj.transition.duration || 0.5,
262
- };
263
- }
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
+ };
264
272
  }
265
273
  if (clipObj.type === "video") {
266
274
  return Loaders.loadVideo(this, clipObj);
@@ -271,9 +279,15 @@ class SIMPLEFFMPEG {
271
279
  if (clipObj.type === "text") {
272
280
  return Loaders.loadText(this, clipObj);
273
281
  }
282
+ if (clipObj.type === "effect") {
283
+ return Loaders.loadEffect(this, clipObj);
284
+ }
274
285
  if (clipObj.type === "image") {
275
286
  return Loaders.loadImage(this, clipObj);
276
287
  }
288
+ if (clipObj.type === "color") {
289
+ return Loaders.loadColor(this, clipObj);
290
+ }
277
291
  if (clipObj.type === "music" || clipObj.type === "backgroundAudio") {
278
292
  return Loaders.loadBackgroundAudio(this, clipObj);
279
293
  }
@@ -381,8 +395,22 @@ class SIMPLEFFMPEG {
381
395
  })
382
396
  );
383
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
+
384
412
  const videoClips = this.videoOrAudioClips.filter(
385
- (clip) => clip.type === "video" || clip.type === "image"
413
+ (clip) => clip.type === "video" || clip.type === "image" || clip.type === "color"
386
414
  );
387
415
  const audioClips = this.videoOrAudioClips.filter(
388
416
  (clip) => clip.type === "audio"
@@ -397,7 +425,7 @@ class SIMPLEFFMPEG {
397
425
  let hasVideo = false;
398
426
  let hasAudio = false;
399
427
 
400
- const totalVideoDuration = (() => {
428
+ let totalVideoDuration = (() => {
401
429
  if (videoClips.length === 0) return 0;
402
430
  const baseSum = videoClips.reduce(
403
431
  (acc, c) => acc + Math.max(0, (c.end || 0) - (c.position || 0)),
@@ -425,7 +453,8 @@ class SIMPLEFFMPEG {
425
453
  )
426
454
  .map((c) => (typeof c.end === "number" ? c.end : 0));
427
455
  const bgOrAudioEnd = audioEnds.length > 0 ? Math.max(...audioEnds) : 0;
428
- const finalVisualEnd =
456
+
457
+ let finalVisualEnd =
429
458
  videoClips.length > 0
430
459
  ? Math.max(...videoClips.map((c) => c.end))
431
460
  : Math.max(textEnd, bgOrAudioEnd);
@@ -436,6 +465,27 @@ class SIMPLEFFMPEG {
436
465
  filterComplex += vres.filter;
437
466
  finalVideoLabel = vres.finalVideoLabel;
438
467
  hasVideo = vres.hasVideo;
468
+
469
+ // Use the actual video output length for finalVisualEnd so that
470
+ // audio trim and BGM duration match the real video stream length,
471
+ // rather than an original-timeline position that may differ due to
472
+ // transition compression.
473
+ if (typeof vres.videoDuration === "number" && vres.videoDuration > 0) {
474
+ totalVideoDuration = vres.videoDuration;
475
+ }
476
+ if (
477
+ typeof vres.videoDuration === "number" &&
478
+ vres.videoDuration > finalVisualEnd
479
+ ) {
480
+ finalVisualEnd = vres.videoDuration;
481
+ }
482
+ }
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;
439
489
  }
440
490
 
441
491
  // Audio for video clips (aligned amix)
@@ -461,7 +511,9 @@ class SIMPLEFFMPEG {
461
511
  let audioString = "";
462
512
  let audioConcatInputs = [];
463
513
  audioClips.forEach((clip) => {
464
- const inputIndex = this.videoOrAudioClips.indexOf(clip);
514
+ const inputIndex = this._inputIndexMap
515
+ ? this._inputIndexMap.get(clip)
516
+ : this.videoOrAudioClips.indexOf(clip);
465
517
  const { audioStringPart, audioConcatInput } = getClipAudioString(
466
518
  clip,
467
519
  inputIndex
@@ -1119,7 +1171,6 @@ class SIMPLEFFMPEG {
1119
1171
  * @param {Array} clips - Array of clip objects to validate
1120
1172
  * @param {Object} options - Validation options
1121
1173
  * @param {boolean} options.skipFileChecks - Skip file existence checks (useful for AI)
1122
- * @param {string} options.fillGaps - Gap handling ('none' | 'black') - affects gap validation
1123
1174
  * @returns {Object} Validation result { valid, errors, warnings }
1124
1175
  *
1125
1176
  * @example
@@ -1166,9 +1217,9 @@ class SIMPLEFFMPEG {
1166
1217
  // Resolve shorthand (duration → end, auto-sequencing)
1167
1218
  const { clips: resolved } = resolveClips(clips);
1168
1219
 
1169
- // Filter to visual clips (video + image)
1220
+ // Filter to visual clips (video + image + color)
1170
1221
  const visual = resolved.filter(
1171
- (c) => c.type === "video" || c.type === "image"
1222
+ (c) => c.type === "video" || c.type === "image" || c.type === "color"
1172
1223
  );
1173
1224
 
1174
1225
  if (visual.length === 0) return 0;
@@ -1379,7 +1430,7 @@ class SIMPLEFFMPEG {
1379
1430
  * Get the list of available schema module IDs.
1380
1431
  * Use these IDs with getSchema({ include: [...] }) or getSchema({ exclude: [...] }).
1381
1432
  *
1382
- * @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']
1383
1434
  */
1384
1435
  static getSchemaModules() {
1385
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;
@@ -88,16 +90,37 @@ declare namespace SIMPLEFFMPEG {
88
90
  loop?: boolean;
89
91
  }
90
92
 
93
+ type KenBurnsEffect =
94
+ | "zoom-in"
95
+ | "zoom-out"
96
+ | "pan-left"
97
+ | "pan-right"
98
+ | "pan-up"
99
+ | "pan-down"
100
+ | "smart"
101
+ | "custom";
102
+
103
+ type KenBurnsAnchor = "top" | "bottom" | "left" | "right";
104
+ type KenBurnsEasing = "linear" | "ease-in" | "ease-out" | "ease-in-out";
105
+
106
+ interface KenBurnsSpec {
107
+ type?: KenBurnsEffect;
108
+ startZoom?: number;
109
+ endZoom?: number;
110
+ startX?: number;
111
+ startY?: number;
112
+ endX?: number;
113
+ endY?: number;
114
+ anchor?: KenBurnsAnchor;
115
+ easing?: KenBurnsEasing;
116
+ }
117
+
91
118
  interface ImageClip extends BaseClip {
92
119
  type: "image";
93
120
  url: string;
94
- kenBurns?:
95
- | "zoom-in"
96
- | "zoom-out"
97
- | "pan-left"
98
- | "pan-right"
99
- | "pan-up"
100
- | "pan-down";
121
+ width?: number;
122
+ height?: number;
123
+ kenBurns?: KenBurnsEffect | KenBurnsSpec;
101
124
  }
102
125
 
103
126
  type TextMode = "static" | "word-replace" | "word-sequential" | "karaoke";
@@ -199,11 +222,82 @@ declare namespace SIMPLEFFMPEG {
199
222
  opacity?: number;
200
223
  }
201
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
+
202
294
  type Clip =
203
295
  | VideoClip
204
296
  | AudioClip
205
297
  | BackgroundMusicClip
206
298
  | ImageClip
299
+ | ColorClip
300
+ | EffectClip
207
301
  | TextClip
208
302
  | SubtitleClip;
209
303
 
@@ -276,8 +370,6 @@ declare namespace SIMPLEFFMPEG {
276
370
  interface ValidateOptions {
277
371
  /** Skip file existence checks (useful for AI generating configs before files exist) */
278
372
  skipFileChecks?: boolean;
279
- /** Gap handling mode - affects timeline gap validation */
280
- fillGaps?: "none" | "black";
281
373
  /** Project width - used to validate Ken Burns images are large enough */
282
374
  width?: number;
283
375
  /** Project height - used to validate Ken Burns images are large enough */
@@ -297,8 +389,6 @@ declare namespace SIMPLEFFMPEG {
297
389
  height?: number;
298
390
  /** Validation mode: 'warn' logs warnings, 'strict' throws on warnings (default: 'warn') */
299
391
  validationMode?: "warn" | "strict";
300
- /** How to handle visual gaps: 'none' throws error, 'black' fills with black frames (default: 'none') */
301
- fillGaps?: "none" | "black";
302
392
  }
303
393
 
304
394
  /** Log entry passed to onLog callback */
@@ -539,6 +629,8 @@ declare namespace SIMPLEFFMPEG {
539
629
  | "video"
540
630
  | "audio"
541
631
  | "image"
632
+ | "color"
633
+ | "effect"
542
634
  | "text"
543
635
  | "subtitle"
544
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;
@@ -88,16 +90,37 @@ declare namespace SIMPLEFFMPEG {
88
90
  loop?: boolean;
89
91
  }
90
92
 
93
+ type KenBurnsEffect =
94
+ | "zoom-in"
95
+ | "zoom-out"
96
+ | "pan-left"
97
+ | "pan-right"
98
+ | "pan-up"
99
+ | "pan-down"
100
+ | "smart"
101
+ | "custom";
102
+
103
+ type KenBurnsAnchor = "top" | "bottom" | "left" | "right";
104
+ type KenBurnsEasing = "linear" | "ease-in" | "ease-out" | "ease-in-out";
105
+
106
+ interface KenBurnsSpec {
107
+ type?: KenBurnsEffect;
108
+ startZoom?: number;
109
+ endZoom?: number;
110
+ startX?: number;
111
+ startY?: number;
112
+ endX?: number;
113
+ endY?: number;
114
+ anchor?: KenBurnsAnchor;
115
+ easing?: KenBurnsEasing;
116
+ }
117
+
91
118
  interface ImageClip extends BaseClip {
92
119
  type: "image";
93
120
  url: string;
94
- kenBurns?:
95
- | "zoom-in"
96
- | "zoom-out"
97
- | "pan-left"
98
- | "pan-right"
99
- | "pan-up"
100
- | "pan-down";
121
+ width?: number;
122
+ height?: number;
123
+ kenBurns?: KenBurnsEffect | KenBurnsSpec;
101
124
  }
102
125
 
103
126
  type TextMode = "static" | "word-replace" | "word-sequential" | "karaoke";
@@ -199,11 +222,93 @@ declare namespace SIMPLEFFMPEG {
199
222
  opacity?: number;
200
223
  }
201
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
+
202
305
  type Clip =
203
306
  | VideoClip
204
307
  | AudioClip
205
308
  | BackgroundMusicClip
206
309
  | ImageClip
310
+ | ColorClip
311
+ | EffectClip
207
312
  | TextClip
208
313
  | SubtitleClip;
209
314
 
@@ -276,8 +381,6 @@ declare namespace SIMPLEFFMPEG {
276
381
  interface ValidateOptions {
277
382
  /** Skip file existence checks (useful for AI generating configs before files exist) */
278
383
  skipFileChecks?: boolean;
279
- /** Gap handling mode - affects timeline gap validation */
280
- fillGaps?: "none" | "black";
281
384
  /** Project width - used to validate Ken Burns images are large enough */
282
385
  width?: number;
283
386
  /** Project height - used to validate Ken Burns images are large enough */
@@ -297,8 +400,6 @@ declare namespace SIMPLEFFMPEG {
297
400
  height?: number;
298
401
  /** Validation mode: 'warn' logs warnings, 'strict' throws on warnings (default: 'warn') */
299
402
  validationMode?: "warn" | "strict";
300
- /** How to handle visual gaps: 'none' throws error, 'black' fills with black frames (default: 'none') */
301
- fillGaps?: "none" | "black";
302
403
  }
303
404
 
304
405
  /** Log entry passed to onLog callback */
@@ -636,6 +737,8 @@ declare namespace SIMPLEFFMPEG {
636
737
  | "video"
637
738
  | "audio"
638
739
  | "image"
740
+ | "color"
741
+ | "effect"
639
742
  | "text"
640
743
  | "subtitle"
641
744
  | "music";