simple-ffmpegjs 0.2.0 → 0.3.1
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 +33 -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 +165 -16
- 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
package/types/index.d.mts
CHANGED
|
@@ -44,7 +44,8 @@ declare namespace SIMPLEFFMPEG {
|
|
|
44
44
|
| "text"
|
|
45
45
|
| "music"
|
|
46
46
|
| "backgroundAudio"
|
|
47
|
-
| "image"
|
|
47
|
+
| "image"
|
|
48
|
+
| "subtitle";
|
|
48
49
|
|
|
49
50
|
interface BaseClip {
|
|
50
51
|
type: ClipType;
|
|
@@ -73,6 +74,8 @@ declare namespace SIMPLEFFMPEG {
|
|
|
73
74
|
url: string;
|
|
74
75
|
cutFrom?: number;
|
|
75
76
|
volume?: number;
|
|
77
|
+
/** Loop the audio to fill the entire video duration */
|
|
78
|
+
loop?: boolean;
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
interface ImageClip extends BaseClip {
|
|
@@ -87,18 +90,25 @@ declare namespace SIMPLEFFMPEG {
|
|
|
87
90
|
| "pan-down";
|
|
88
91
|
}
|
|
89
92
|
|
|
90
|
-
type TextMode = "static" | "word-replace" | "word-sequential";
|
|
93
|
+
type TextMode = "static" | "word-replace" | "word-sequential" | "karaoke";
|
|
91
94
|
type TextAnimationType =
|
|
92
95
|
| "none"
|
|
93
96
|
| "fade-in"
|
|
97
|
+
| "fade-out"
|
|
94
98
|
| "fade-in-out"
|
|
99
|
+
| "fade"
|
|
95
100
|
| "pop"
|
|
96
|
-
| "pop-bounce"
|
|
101
|
+
| "pop-bounce"
|
|
102
|
+
| "scale-in"
|
|
103
|
+
| "pulse"
|
|
104
|
+
| "typewriter";
|
|
97
105
|
|
|
98
106
|
interface TextWordWindow {
|
|
99
107
|
text: string;
|
|
100
108
|
start: number;
|
|
101
109
|
end: number;
|
|
110
|
+
/** Add line break after this word (for multi-line karaoke) */
|
|
111
|
+
lineBreak?: boolean;
|
|
102
112
|
}
|
|
103
113
|
|
|
104
114
|
interface TextClip {
|
|
@@ -125,6 +135,10 @@ declare namespace SIMPLEFFMPEG {
|
|
|
125
135
|
x?: number;
|
|
126
136
|
/** Absolute Y position in pixels */
|
|
127
137
|
y?: number;
|
|
138
|
+
/** Pixel offset added to X position (works with x, xPercent, or center default) */
|
|
139
|
+
xOffset?: number;
|
|
140
|
+
/** Pixel offset added to Y position (works with y, yPercent, or center default) */
|
|
141
|
+
yOffset?: number;
|
|
128
142
|
|
|
129
143
|
// Styling
|
|
130
144
|
borderColor?: string;
|
|
@@ -139,9 +153,40 @@ declare namespace SIMPLEFFMPEG {
|
|
|
139
153
|
// Animation
|
|
140
154
|
animation?: {
|
|
141
155
|
type: TextAnimationType;
|
|
156
|
+
/** Entry animation duration in seconds (default: 0.25) */
|
|
142
157
|
in?: number;
|
|
158
|
+
/** Exit animation duration in seconds (default: same as in) */
|
|
143
159
|
out?: number;
|
|
160
|
+
/** Animation intensity 0-1 for scale-in and pulse (default: 0.3) */
|
|
161
|
+
intensity?: number;
|
|
162
|
+
/** Speed for typewriter (sec/char, default: 0.05) or pulse (cycles/sec, default: 1) */
|
|
163
|
+
speed?: number;
|
|
144
164
|
};
|
|
165
|
+
|
|
166
|
+
/** Highlight color for karaoke mode (default: '#FFFF00') */
|
|
167
|
+
highlightColor?: string;
|
|
168
|
+
|
|
169
|
+
/** Highlight style for karaoke mode: 'smooth' (gradual fill) or 'instant' (default: 'smooth') */
|
|
170
|
+
highlightStyle?: "smooth" | "instant";
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Subtitle clip for importing external subtitle files */
|
|
174
|
+
interface SubtitleClip {
|
|
175
|
+
type: "subtitle";
|
|
176
|
+
/** Path to subtitle file (.srt, .ass, .ssa, .vtt) */
|
|
177
|
+
url: string;
|
|
178
|
+
/** Timeline position offset (default: 0) - adds to all subtitle timestamps */
|
|
179
|
+
position?: number;
|
|
180
|
+
/** Optional end time to cut off subtitles */
|
|
181
|
+
end?: number;
|
|
182
|
+
|
|
183
|
+
// Styling (for SRT/VTT import - ASS files use their own styles)
|
|
184
|
+
fontFamily?: string;
|
|
185
|
+
fontSize?: number;
|
|
186
|
+
fontColor?: string;
|
|
187
|
+
borderColor?: string;
|
|
188
|
+
borderWidth?: number;
|
|
189
|
+
opacity?: number;
|
|
145
190
|
}
|
|
146
191
|
|
|
147
192
|
type Clip =
|
|
@@ -149,18 +194,96 @@ declare namespace SIMPLEFFMPEG {
|
|
|
149
194
|
| AudioClip
|
|
150
195
|
| BackgroundMusicClip
|
|
151
196
|
| ImageClip
|
|
152
|
-
| TextClip
|
|
197
|
+
| TextClip
|
|
198
|
+
| SubtitleClip;
|
|
153
199
|
|
|
154
200
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
155
201
|
// Options
|
|
156
202
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
157
203
|
|
|
204
|
+
/** Platform preset names */
|
|
205
|
+
type PlatformPreset =
|
|
206
|
+
| "tiktok"
|
|
207
|
+
| "youtube-short"
|
|
208
|
+
| "instagram-reel"
|
|
209
|
+
| "instagram-story"
|
|
210
|
+
| "snapchat"
|
|
211
|
+
| "instagram-post"
|
|
212
|
+
| "instagram-square"
|
|
213
|
+
| "youtube"
|
|
214
|
+
| "twitter"
|
|
215
|
+
| "facebook"
|
|
216
|
+
| "landscape"
|
|
217
|
+
| "twitter-portrait"
|
|
218
|
+
| "instagram-portrait";
|
|
219
|
+
|
|
220
|
+
/** Platform preset configuration */
|
|
221
|
+
interface PresetConfig {
|
|
222
|
+
width: number;
|
|
223
|
+
height: number;
|
|
224
|
+
fps: number;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Validation error/warning codes */
|
|
228
|
+
const ValidationCodes: {
|
|
229
|
+
readonly INVALID_TYPE: "INVALID_TYPE";
|
|
230
|
+
readonly MISSING_REQUIRED: "MISSING_REQUIRED";
|
|
231
|
+
readonly INVALID_VALUE: "INVALID_VALUE";
|
|
232
|
+
readonly INVALID_RANGE: "INVALID_RANGE";
|
|
233
|
+
readonly INVALID_TIMELINE: "INVALID_TIMELINE";
|
|
234
|
+
readonly TIMELINE_GAP: "TIMELINE_GAP";
|
|
235
|
+
readonly FILE_NOT_FOUND: "FILE_NOT_FOUND";
|
|
236
|
+
readonly INVALID_FORMAT: "INVALID_FORMAT";
|
|
237
|
+
readonly INVALID_WORD_TIMING: "INVALID_WORD_TIMING";
|
|
238
|
+
readonly OUTSIDE_BOUNDS: "OUTSIDE_BOUNDS";
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
type ValidationCode = (typeof ValidationCodes)[keyof typeof ValidationCodes];
|
|
242
|
+
|
|
243
|
+
/** A single validation error or warning */
|
|
244
|
+
interface ValidationIssue {
|
|
245
|
+
/** Error code for programmatic handling */
|
|
246
|
+
code: ValidationCode;
|
|
247
|
+
/** Path to the problematic field (e.g., "clips[0].url") */
|
|
248
|
+
path: string;
|
|
249
|
+
/** Human-readable error message */
|
|
250
|
+
message: string;
|
|
251
|
+
/** The actual value that caused the issue (optional) */
|
|
252
|
+
received?: unknown;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Result from validate() */
|
|
256
|
+
interface ValidationResult {
|
|
257
|
+
/** Whether the configuration is valid (no errors) */
|
|
258
|
+
valid: boolean;
|
|
259
|
+
/** Array of validation errors (issues that will cause failures) */
|
|
260
|
+
errors: ValidationIssue[];
|
|
261
|
+
/** Array of validation warnings (potential issues that won't block) */
|
|
262
|
+
warnings: ValidationIssue[];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Options for validate() */
|
|
266
|
+
interface ValidateOptions {
|
|
267
|
+
/** Skip file existence checks (useful for AI generating configs before files exist) */
|
|
268
|
+
skipFileChecks?: boolean;
|
|
269
|
+
/** Gap handling mode - affects timeline gap validation */
|
|
270
|
+
fillGaps?: "none" | "black";
|
|
271
|
+
/** Project width - used to validate Ken Burns images are large enough */
|
|
272
|
+
width?: number;
|
|
273
|
+
/** Project height - used to validate Ken Burns images are large enough */
|
|
274
|
+
height?: number;
|
|
275
|
+
/** If true, undersized Ken Burns images will error instead of warn (default: false, images are auto-upscaled) */
|
|
276
|
+
strictKenBurns?: boolean;
|
|
277
|
+
}
|
|
278
|
+
|
|
158
279
|
interface SIMPLEFFMPEGOptions {
|
|
159
|
-
/**
|
|
280
|
+
/** Platform preset (e.g., 'tiktok', 'youtube', 'instagram-reel'). Sets width, height, fps. */
|
|
281
|
+
preset?: PlatformPreset;
|
|
282
|
+
/** Frames per second (default: 30, or from preset) */
|
|
160
283
|
fps?: number;
|
|
161
|
-
/** Output width in pixels (default: 1920) */
|
|
284
|
+
/** Output width in pixels (default: 1920, or from preset) */
|
|
162
285
|
width?: number;
|
|
163
|
-
/** Output height in pixels (default: 1080) */
|
|
286
|
+
/** Output height in pixels (default: 1080, or from preset) */
|
|
164
287
|
height?: number;
|
|
165
288
|
/** Validation mode: 'warn' logs warnings, 'strict' throws on warnings (default: 'warn') */
|
|
166
289
|
validationMode?: "warn" | "strict";
|
|
@@ -251,6 +374,65 @@ declare namespace SIMPLEFFMPEG {
|
|
|
251
374
|
|
|
252
375
|
type ResolutionPreset = "480p" | "720p" | "1080p" | "1440p" | "4k";
|
|
253
376
|
|
|
377
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
378
|
+
// Watermark Types
|
|
379
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
380
|
+
|
|
381
|
+
/** Preset position for watermarks */
|
|
382
|
+
type WatermarkPositionPreset =
|
|
383
|
+
| "top-left"
|
|
384
|
+
| "top-right"
|
|
385
|
+
| "bottom-left"
|
|
386
|
+
| "bottom-right"
|
|
387
|
+
| "center";
|
|
388
|
+
|
|
389
|
+
/** Custom position using percentages (0-1) */
|
|
390
|
+
interface WatermarkPositionPercent {
|
|
391
|
+
xPercent: number;
|
|
392
|
+
yPercent: number;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** Custom position using pixels */
|
|
396
|
+
interface WatermarkPositionPixel {
|
|
397
|
+
x: number;
|
|
398
|
+
y: number;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
type WatermarkPosition =
|
|
402
|
+
| WatermarkPositionPreset
|
|
403
|
+
| WatermarkPositionPercent
|
|
404
|
+
| WatermarkPositionPixel;
|
|
405
|
+
|
|
406
|
+
interface BaseWatermarkOptions {
|
|
407
|
+
position?: WatermarkPosition;
|
|
408
|
+
margin?: number;
|
|
409
|
+
opacity?: number;
|
|
410
|
+
startTime?: number;
|
|
411
|
+
endTime?: number;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
interface ImageWatermarkOptions extends BaseWatermarkOptions {
|
|
415
|
+
type: "image";
|
|
416
|
+
url: string;
|
|
417
|
+
scale?: number;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
interface TextWatermarkOptions extends BaseWatermarkOptions {
|
|
421
|
+
type: "text";
|
|
422
|
+
text: string;
|
|
423
|
+
fontSize?: number;
|
|
424
|
+
fontColor?: string;
|
|
425
|
+
fontFamily?: string;
|
|
426
|
+
fontFile?: string;
|
|
427
|
+
borderColor?: string;
|
|
428
|
+
borderWidth?: number;
|
|
429
|
+
shadowColor?: string;
|
|
430
|
+
shadowX?: number;
|
|
431
|
+
shadowY?: number;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
type WatermarkOptions = ImageWatermarkOptions | TextWatermarkOptions;
|
|
435
|
+
|
|
254
436
|
interface ExportOptions {
|
|
255
437
|
// Output
|
|
256
438
|
outputPath?: string;
|
|
@@ -302,6 +484,16 @@ declare namespace SIMPLEFFMPEG {
|
|
|
302
484
|
intermediateVideoCodec?: string;
|
|
303
485
|
intermediateCrf?: number;
|
|
304
486
|
intermediatePreset?: string;
|
|
487
|
+
|
|
488
|
+
// Watermark
|
|
489
|
+
watermark?: WatermarkOptions;
|
|
490
|
+
|
|
491
|
+
// Timeline
|
|
492
|
+
/**
|
|
493
|
+
* Automatically adjust text/subtitle timings to compensate for timeline
|
|
494
|
+
* compression caused by xfade transitions (default: true).
|
|
495
|
+
*/
|
|
496
|
+
compensateTransitions?: boolean;
|
|
305
497
|
}
|
|
306
498
|
|
|
307
499
|
/** Result from preview() method */
|
|
@@ -337,6 +529,73 @@ declare class SIMPLEFFMPEG {
|
|
|
337
529
|
* @param options Export options including outputPath, onProgress, and signal
|
|
338
530
|
*/
|
|
339
531
|
export(options?: SIMPLEFFMPEG.ExportOptions): Promise<string>;
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Get available platform presets
|
|
535
|
+
*/
|
|
536
|
+
static getPresets(): Record<
|
|
537
|
+
SIMPLEFFMPEG.PlatformPreset,
|
|
538
|
+
SIMPLEFFMPEG.PresetConfig
|
|
539
|
+
>;
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Get list of available preset names
|
|
543
|
+
*/
|
|
544
|
+
static getPresetNames(): SIMPLEFFMPEG.PlatformPreset[];
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Validate clips configuration without creating a project.
|
|
548
|
+
* Useful for AI feedback loops and pre-validation.
|
|
549
|
+
*
|
|
550
|
+
* @param clips - Array of clip objects to validate
|
|
551
|
+
* @param options - Validation options
|
|
552
|
+
* @returns Validation result with valid flag, errors, and warnings
|
|
553
|
+
*
|
|
554
|
+
* @example
|
|
555
|
+
* const result = SIMPLEFFMPEG.validate(clips, { skipFileChecks: true });
|
|
556
|
+
* if (!result.valid) {
|
|
557
|
+
* result.errors.forEach(e => console.log(`[${e.code}] ${e.path}: ${e.message}`));
|
|
558
|
+
* }
|
|
559
|
+
*/
|
|
560
|
+
static validate(
|
|
561
|
+
clips: SIMPLEFFMPEG.Clip[],
|
|
562
|
+
options?: SIMPLEFFMPEG.ValidateOptions
|
|
563
|
+
): SIMPLEFFMPEG.ValidationResult;
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Format validation result as human-readable string
|
|
567
|
+
*/
|
|
568
|
+
static formatValidationResult(result: SIMPLEFFMPEG.ValidationResult): string;
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Validation error codes for programmatic handling
|
|
572
|
+
*/
|
|
573
|
+
static readonly ValidationCodes: typeof SIMPLEFFMPEG.ValidationCodes;
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Base error class for all simple-ffmpeg errors
|
|
577
|
+
*/
|
|
578
|
+
static readonly SimpleffmpegError: typeof SIMPLEFFMPEG.SimpleffmpegError;
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Thrown when clip validation fails
|
|
582
|
+
*/
|
|
583
|
+
static readonly ValidationError: typeof SIMPLEFFMPEG.ValidationError;
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Thrown when FFmpeg command execution fails
|
|
587
|
+
*/
|
|
588
|
+
static readonly FFmpegError: typeof SIMPLEFFMPEG.FFmpegError;
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Thrown when a media file cannot be found or accessed
|
|
592
|
+
*/
|
|
593
|
+
static readonly MediaNotFoundError: typeof SIMPLEFFMPEG.MediaNotFoundError;
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Thrown when export is cancelled via AbortSignal
|
|
597
|
+
*/
|
|
598
|
+
static readonly ExportCancelledError: typeof SIMPLEFFMPEG.ExportCancelledError;
|
|
340
599
|
}
|
|
341
600
|
|
|
342
601
|
export default SIMPLEFFMPEG;
|