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.
- package/README.md +221 -55
- package/package.json +1 -1
- package/src/core/gaps.js +4 -4
- package/src/core/resolve.js +1 -1
- package/src/core/validation.js +509 -43
- package/src/ffmpeg/audio_builder.js +3 -1
- package/src/ffmpeg/bgm_builder.js +3 -1
- package/src/ffmpeg/effect_builder.js +138 -0
- package/src/ffmpeg/video_builder.js +220 -75
- package/src/ffmpeg/watermark_builder.js +13 -0
- package/src/lib/gradient.js +257 -0
- package/src/loaders.js +55 -2
- package/src/schema/formatter.js +2 -0
- package/src/schema/index.js +4 -0
- package/src/schema/modules/color.js +54 -0
- package/src/schema/modules/effect.js +77 -0
- package/src/schema/modules/image.js +24 -6
- package/src/simpleffmpeg.js +72 -21
- package/types/index.d.mts +104 -12
- package/types/index.d.ts +115 -12
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
- [Logging](#logging)
|
|
32
32
|
- [Error Handling](#error-handling)
|
|
33
33
|
- [Cancellation](#cancellation)
|
|
34
|
-
- [
|
|
34
|
+
- [Color Clips](#color-clips)
|
|
35
35
|
- [Examples](#examples)
|
|
36
36
|
- [Clips & Transitions](#clips--transitions)
|
|
37
37
|
- [Text & Animations](#text--animations)
|
|
@@ -77,7 +77,8 @@ _Click to watch a "Wonders of the World" video created with simple-ffmpeg — co
|
|
|
77
77
|
- **Image Support** — Ken Burns effects (zoom, pan) for static images
|
|
78
78
|
- **Progress Tracking** — Real-time export progress callbacks
|
|
79
79
|
- **Cancellation** — AbortController support for stopping exports
|
|
80
|
-
- **
|
|
80
|
+
- **Color Clips** — Flat colors and gradients (linear, radial) as first-class timeline clips with full transition support
|
|
81
|
+
- **Effect Clips** — Timed overlay effects (vignette, film grain, gaussian blur, color adjustment) with fade-in/out envelopes
|
|
81
82
|
- **Auto-Batching** — Automatically splits complex filter graphs to avoid OS command limits
|
|
82
83
|
- **Schema Export** — Generate a structured description of the clip format for documentation, code generation, or AI context
|
|
83
84
|
- **Pre-Validation** — Validate clip configurations before processing with structured, machine-readable error codes
|
|
@@ -237,7 +238,7 @@ const schema = SIMPLEFFMPEG.getSchema({ exclude: ["text", "subtitle"] });
|
|
|
237
238
|
|
|
238
239
|
// See all available module IDs
|
|
239
240
|
SIMPLEFFMPEG.getSchemaModules();
|
|
240
|
-
// ['video', 'audio', 'image', 'text', 'subtitle', 'music']
|
|
241
|
+
// ['video', 'audio', 'image', 'color', 'effect', 'text', 'subtitle', 'music']
|
|
241
242
|
```
|
|
242
243
|
|
|
243
244
|
Available modules:
|
|
@@ -247,6 +248,8 @@ Available modules:
|
|
|
247
248
|
| `video` | Video clips, transitions, volume, trimming |
|
|
248
249
|
| `audio` | Standalone audio clips |
|
|
249
250
|
| `image` | Image clips, Ken Burns effects |
|
|
251
|
+
| `color` | Color clips — flat colors, linear/radial gradients |
|
|
252
|
+
| `effect` | Overlay adjustment effects — vignette, grain, blur, grading |
|
|
250
253
|
| `text` | Text overlays — all modes, animations, positioning, styling |
|
|
251
254
|
| `subtitle` | Subtitle file import (SRT, VTT, ASS, SSA) |
|
|
252
255
|
| `music` | Background music / background audio, looping |
|
|
@@ -284,7 +287,6 @@ new SIMPLEFFMPEG(options?: {
|
|
|
284
287
|
height?: number; // Output height (default: 1080)
|
|
285
288
|
fps?: number; // Frame rate (default: 30)
|
|
286
289
|
validationMode?: 'warn' | 'strict'; // Validation behavior (default: 'warn')
|
|
287
|
-
fillGaps?: 'none' | 'black'; // Gap handling (default: 'none')
|
|
288
290
|
preset?: string; // Platform preset (e.g., 'tiktok', 'youtube', 'instagram-post')
|
|
289
291
|
})
|
|
290
292
|
```
|
|
@@ -406,13 +408,13 @@ await SIMPLEFFMPEG.snapshot("./video.mp4", {
|
|
|
406
408
|
|
|
407
409
|
**Snapshot Options:**
|
|
408
410
|
|
|
409
|
-
| Option | Type | Default | Description
|
|
410
|
-
| ------------ | -------- | ------- |
|
|
411
|
-
| `outputPath` | `string` | - | **Required.** Output image path (extension determines format)
|
|
412
|
-
| `time` | `number` | `0` | Time in seconds to capture the frame at
|
|
413
|
-
| `width` | `number` | - | Output width in pixels (maintains aspect ratio if height omitted)
|
|
414
|
-
| `height` | `number` | - | Output height in pixels (maintains aspect ratio if width omitted)
|
|
415
|
-
| `quality` | `number` | `2` | JPEG quality 1-31, lower is better (only applies to `.jpg`/`.jpeg` output)
|
|
411
|
+
| Option | Type | Default | Description |
|
|
412
|
+
| ------------ | -------- | ------- | -------------------------------------------------------------------------- |
|
|
413
|
+
| `outputPath` | `string` | - | **Required.** Output image path (extension determines format) |
|
|
414
|
+
| `time` | `number` | `0` | Time in seconds to capture the frame at |
|
|
415
|
+
| `width` | `number` | - | Output width in pixels (maintains aspect ratio if height omitted) |
|
|
416
|
+
| `height` | `number` | - | Output height in pixels (maintains aspect ratio if width omitted) |
|
|
417
|
+
| `quality` | `number` | `2` | JPEG quality 1-31, lower is better (only applies to `.jpg`/`.jpeg` output) |
|
|
416
418
|
|
|
417
419
|
**Supported formats:** `.jpg` / `.jpeg`, `.png`, `.webp`, `.bmp`, `.tiff`
|
|
418
420
|
|
|
@@ -552,7 +554,78 @@ await project.load([
|
|
|
552
554
|
position?: number; // Omit to auto-sequence after previous video/image clip
|
|
553
555
|
end?: number; // Use end OR duration, not both
|
|
554
556
|
duration?: number; // Duration in seconds (alternative to end)
|
|
555
|
-
|
|
557
|
+
width?: number; // Optional: source image width (skip probe / override)
|
|
558
|
+
height?: number; // Optional: source image height (skip probe / override)
|
|
559
|
+
kenBurns?:
|
|
560
|
+
| "zoom-in" | "zoom-out" | "pan-left" | "pan-right" | "pan-up" | "pan-down"
|
|
561
|
+
| "smart" | "custom"
|
|
562
|
+
| {
|
|
563
|
+
type?: "zoom-in" | "zoom-out" | "pan-left" | "pan-right" | "pan-up" | "pan-down" | "smart" | "custom";
|
|
564
|
+
startZoom?: number;
|
|
565
|
+
endZoom?: number;
|
|
566
|
+
startX?: number; // 0 = left, 1 = right
|
|
567
|
+
startY?: number; // 0 = top, 1 = bottom
|
|
568
|
+
endX?: number;
|
|
569
|
+
endY?: number;
|
|
570
|
+
anchor?: "top" | "bottom" | "left" | "right";
|
|
571
|
+
easing?: "linear" | "ease-in" | "ease-out" | "ease-in-out";
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
#### Color Clip
|
|
577
|
+
|
|
578
|
+
```ts
|
|
579
|
+
{
|
|
580
|
+
type: "color";
|
|
581
|
+
color: string | { // Flat color string or gradient spec
|
|
582
|
+
type: "linear-gradient" | "radial-gradient";
|
|
583
|
+
colors: string[]; // 2+ color stops (named, hex, or 0x hex)
|
|
584
|
+
direction?: "vertical" | "horizontal"; // For linear gradients (default: "vertical")
|
|
585
|
+
};
|
|
586
|
+
position?: number; // Timeline start (seconds). Omit to auto-sequence.
|
|
587
|
+
end?: number; // Timeline end. Use end OR duration, not both.
|
|
588
|
+
duration?: number; // Duration in seconds (alternative to end).
|
|
589
|
+
transition?: {
|
|
590
|
+
type: string; // Any xfade transition (e.g., 'fade', 'wipeleft')
|
|
591
|
+
duration: number;
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
#### Effect Clip
|
|
597
|
+
|
|
598
|
+
Effects are overlay adjustment layers. They apply to the already-composed video
|
|
599
|
+
for a time window, and can ramp in/out smoothly (instead of appearing instantly):
|
|
600
|
+
|
|
601
|
+
```ts
|
|
602
|
+
{
|
|
603
|
+
type: "effect";
|
|
604
|
+
effect: "vignette" | "filmGrain" | "gaussianBlur" | "colorAdjust";
|
|
605
|
+
position: number; // Required timeline start (seconds)
|
|
606
|
+
end?: number; // Use end OR duration, not both
|
|
607
|
+
duration?: number; // Duration in seconds (alternative to end)
|
|
608
|
+
fadeIn?: number; // Optional smooth ramp-in (seconds)
|
|
609
|
+
fadeOut?: number; // Optional smooth ramp-out (seconds)
|
|
610
|
+
easing?: "linear" | "ease-in" | "ease-out" | "ease-in-out"; // default: "linear"
|
|
611
|
+
params: {
|
|
612
|
+
amount?: number; // Blend amount 0..1 (default: 1)
|
|
613
|
+
|
|
614
|
+
// vignette
|
|
615
|
+
angle?: number; // radians
|
|
616
|
+
|
|
617
|
+
// filmGrain
|
|
618
|
+
temporal?: boolean; // default: true
|
|
619
|
+
|
|
620
|
+
// gaussianBlur
|
|
621
|
+
sigma?: number;
|
|
622
|
+
|
|
623
|
+
// colorAdjust
|
|
624
|
+
brightness?: number; // -1..1
|
|
625
|
+
contrast?: number; // 0..3
|
|
626
|
+
saturation?: number; // 0..3
|
|
627
|
+
gamma?: number; // 0.1..10
|
|
628
|
+
};
|
|
556
629
|
}
|
|
557
630
|
```
|
|
558
631
|
|
|
@@ -762,7 +835,7 @@ onProgress: ({ percent, phase }) => {
|
|
|
762
835
|
} else {
|
|
763
836
|
console.log(`${percent}%`);
|
|
764
837
|
}
|
|
765
|
-
}
|
|
838
|
+
};
|
|
766
839
|
```
|
|
767
840
|
|
|
768
841
|
### Logging
|
|
@@ -798,10 +871,10 @@ try {
|
|
|
798
871
|
if (error.name === "ValidationError") {
|
|
799
872
|
// Structured validation errors
|
|
800
873
|
error.errors.forEach((e) =>
|
|
801
|
-
console.error(`[${e.code}] ${e.path}: ${e.message}`)
|
|
874
|
+
console.error(`[${e.code}] ${e.path}: ${e.message}`),
|
|
802
875
|
);
|
|
803
876
|
error.warnings.forEach((w) =>
|
|
804
|
-
console.warn(`[${w.code}] ${w.path}: ${w.message}`)
|
|
877
|
+
console.warn(`[${w.code}] ${w.path}: ${w.message}`),
|
|
805
878
|
);
|
|
806
879
|
} else if (error.name === "FFmpegError") {
|
|
807
880
|
// Structured details for bug reports (last 50 lines of stderr, command, exitCode)
|
|
@@ -837,20 +910,90 @@ try {
|
|
|
837
910
|
}
|
|
838
911
|
```
|
|
839
912
|
|
|
840
|
-
###
|
|
913
|
+
### Color Clips
|
|
914
|
+
|
|
915
|
+
Color clips let you add flat colors or gradients as first-class visual elements in your timeline. They support transitions, text overlays, and all the same timeline features as video and image clips. Use them for intros, outros, title cards, or anywhere you need a background:
|
|
841
916
|
|
|
842
|
-
|
|
917
|
+
**Flat color:**
|
|
843
918
|
|
|
844
919
|
```ts
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
}
|
|
920
|
+
await project.load([
|
|
921
|
+
// Black intro screen for 2 seconds
|
|
922
|
+
{ type: "color", color: "black", position: 0, end: 2 },
|
|
923
|
+
// Video starts at 2s
|
|
924
|
+
{ type: "video", url: "./clip.mp4", position: 2, end: 7 },
|
|
925
|
+
]);
|
|
926
|
+
```
|
|
927
|
+
|
|
928
|
+
`color` accepts any valid FFmpeg color name or hex code:
|
|
929
|
+
|
|
930
|
+
```ts
|
|
931
|
+
{ type: "color", color: "navy", position: 0, end: 3 }
|
|
932
|
+
{ type: "color", color: "#1a1a2e", position: 0, end: 3 }
|
|
933
|
+
```
|
|
848
934
|
|
|
935
|
+
**Gradients:**
|
|
936
|
+
|
|
937
|
+
```ts
|
|
938
|
+
// Linear gradient (vertical by default)
|
|
939
|
+
{
|
|
940
|
+
type: "color",
|
|
941
|
+
color: { type: "linear-gradient", colors: ["#0a0a2e", "#4a148c"] },
|
|
942
|
+
position: 0,
|
|
943
|
+
end: 4,
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// Horizontal linear gradient
|
|
947
|
+
{
|
|
948
|
+
type: "color",
|
|
949
|
+
color: { type: "linear-gradient", colors: ["#e74c3c", "#f1c40f", "#2ecc71"], direction: "horizontal" },
|
|
950
|
+
position: 0,
|
|
951
|
+
end: 4,
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// Radial gradient
|
|
955
|
+
{
|
|
956
|
+
type: "color",
|
|
957
|
+
color: { type: "radial-gradient", colors: ["#ff8c00", "#1a0000"] },
|
|
958
|
+
position: 0,
|
|
959
|
+
end: 3,
|
|
960
|
+
}
|
|
961
|
+
```
|
|
962
|
+
|
|
963
|
+
**With transitions:**
|
|
964
|
+
|
|
965
|
+
Color clips support the same `transition` property as video and image clips:
|
|
966
|
+
|
|
967
|
+
```ts
|
|
849
968
|
await project.load([
|
|
850
|
-
{ type: "
|
|
969
|
+
{ type: "color", color: "black", position: 0, end: 3 },
|
|
970
|
+
{
|
|
971
|
+
type: "video",
|
|
972
|
+
url: "./main.mp4",
|
|
973
|
+
position: 3,
|
|
974
|
+
end: 8,
|
|
975
|
+
transition: { type: "fade", duration: 0.5 },
|
|
976
|
+
},
|
|
977
|
+
{
|
|
978
|
+
type: "color",
|
|
979
|
+
color: { type: "radial-gradient", colors: ["#2c3e50", "#000000"] },
|
|
980
|
+
position: 8,
|
|
981
|
+
end: 11,
|
|
982
|
+
transition: { type: "fade", duration: 0.5 },
|
|
983
|
+
},
|
|
984
|
+
{
|
|
985
|
+
type: "text",
|
|
986
|
+
text: "The End",
|
|
987
|
+
position: 8.5,
|
|
988
|
+
end: 10.5,
|
|
989
|
+
fontSize: 64,
|
|
990
|
+
fontColor: "white",
|
|
991
|
+
},
|
|
851
992
|
]);
|
|
852
993
|
```
|
|
853
994
|
|
|
995
|
+
> **Note:** Timeline gaps (periods with no visual content) always produce a validation error. If a gap is intentional, fill it with a `type: "color"` clip or adjust your clip positions to close the gap.
|
|
996
|
+
|
|
854
997
|
## Examples
|
|
855
998
|
|
|
856
999
|
### Clips & Transitions
|
|
@@ -880,9 +1023,43 @@ await project.load([
|
|
|
880
1023
|
]);
|
|
881
1024
|
```
|
|
882
1025
|
|
|
1026
|
+
**Custom Ken Burns (smart anchor + explicit endpoints):**
|
|
1027
|
+
|
|
1028
|
+
```ts
|
|
1029
|
+
await project.load([
|
|
1030
|
+
{
|
|
1031
|
+
type: "image",
|
|
1032
|
+
url: "./portrait.jpg",
|
|
1033
|
+
duration: 5,
|
|
1034
|
+
kenBurns: {
|
|
1035
|
+
type: "smart",
|
|
1036
|
+
anchor: "bottom",
|
|
1037
|
+
startZoom: 1.05,
|
|
1038
|
+
endZoom: 1.2,
|
|
1039
|
+
easing: "ease-in-out",
|
|
1040
|
+
},
|
|
1041
|
+
},
|
|
1042
|
+
{
|
|
1043
|
+
type: "image",
|
|
1044
|
+
url: "./wide.jpg",
|
|
1045
|
+
duration: 4,
|
|
1046
|
+
kenBurns: {
|
|
1047
|
+
type: "custom",
|
|
1048
|
+
startX: 0.15,
|
|
1049
|
+
startY: 0.7,
|
|
1050
|
+
endX: 0.85,
|
|
1051
|
+
endY: 0.2,
|
|
1052
|
+
easing: "ease-in-out",
|
|
1053
|
+
},
|
|
1054
|
+
},
|
|
1055
|
+
]);
|
|
1056
|
+
```
|
|
1057
|
+
|
|
883
1058
|
When `position` is omitted, clips are placed sequentially — each one starts where the previous one ended. `duration` is an alternative to `end`: the library computes `end = position + duration`. The explicit form (`position: 0, end: 3`) still works identically.
|
|
884
1059
|
|
|
885
1060
|
> **Note:** Ken Burns effects work best with images at least as large as your output resolution. Smaller images are automatically upscaled (with a validation warning). Use `strictKenBurns: true` in validation options to enforce size requirements instead.
|
|
1061
|
+
> If you pass `width`/`height`, they override probed dimensions (useful for remote or generated images).
|
|
1062
|
+
> `smart` mode uses source vs output aspect (when known) to choose pan direction.
|
|
886
1063
|
|
|
887
1064
|
### Text & Animations
|
|
888
1065
|
|
|
@@ -1244,7 +1421,7 @@ async function generateVideo(userPrompt, media) {
|
|
|
1244
1421
|
`Your previous output had validation errors:\n${errorFeedback}`,
|
|
1245
1422
|
`\nOriginal request: ${userPrompt}`,
|
|
1246
1423
|
"\nPlease fix the errors and return the corrected clips array.",
|
|
1247
|
-
].join("\n")
|
|
1424
|
+
].join("\n"),
|
|
1248
1425
|
);
|
|
1249
1426
|
|
|
1250
1427
|
result = SIMPLEFFMPEG.validate(clips, { skipFileChecks: true });
|
|
@@ -1254,7 +1431,7 @@ async function generateVideo(userPrompt, media) {
|
|
|
1254
1431
|
if (!result.valid) {
|
|
1255
1432
|
throw new Error(
|
|
1256
1433
|
`Failed to generate valid config after ${attempts} attempts:\n` +
|
|
1257
|
-
SIMPLEFFMPEG.formatValidationResult(result)
|
|
1434
|
+
SIMPLEFFMPEG.formatValidationResult(result),
|
|
1258
1435
|
);
|
|
1259
1436
|
}
|
|
1260
1437
|
|
|
@@ -1340,44 +1517,33 @@ npm run test:watch
|
|
|
1340
1517
|
|
|
1341
1518
|
### Manual Verification
|
|
1342
1519
|
|
|
1343
|
-
For visual verification
|
|
1520
|
+
For visual verification, run the demo suite to generate sample videos covering all major features. Each demo outputs to its own subfolder under `examples/output/` and includes annotated expected timelines so you know exactly what to look for:
|
|
1344
1521
|
|
|
1345
1522
|
```bash
|
|
1523
|
+
# Run all demos (color clips, effects, transitions, text, Ken Burns, audio, watermarks, karaoke, torture test)
|
|
1346
1524
|
node examples/run-examples.js
|
|
1347
|
-
```
|
|
1348
|
-
|
|
1349
|
-
This creates example videos in `examples/output/` covering:
|
|
1350
|
-
|
|
1351
|
-
- Basic video concatenation
|
|
1352
|
-
- Crossfade transitions
|
|
1353
|
-
- Text overlays with animations
|
|
1354
|
-
- Background music mixing
|
|
1355
|
-
- Ken Burns effects on images
|
|
1356
|
-
- Gap filling with black frames
|
|
1357
|
-
- Quality settings (CRF, preset)
|
|
1358
|
-
- Resolution scaling
|
|
1359
|
-
- Metadata embedding
|
|
1360
|
-
- Thumbnail generation
|
|
1361
|
-
- Complex multi-track compositions
|
|
1362
|
-
- Word-by-word text
|
|
1363
|
-
- Platform presets (TikTok, YouTube, etc.)
|
|
1364
|
-
- Typewriter text animation
|
|
1365
|
-
- Scale-in text animation
|
|
1366
|
-
- Pulse text animation
|
|
1367
|
-
- Fade-out text animation
|
|
1368
|
-
- Text watermarks
|
|
1369
|
-
- Image watermarks
|
|
1370
|
-
- Timed watermarks
|
|
1371
|
-
- Karaoke text (word-by-word highlighting)
|
|
1372
|
-
- SRT/VTT subtitle import
|
|
1373
|
-
|
|
1374
|
-
View the outputs to confirm everything renders correctly:
|
|
1375
1525
|
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1526
|
+
# Run a specific demo by name (partial match)
|
|
1527
|
+
node examples/run-examples.js transitions
|
|
1528
|
+
node examples/run-examples.js torture ken
|
|
1379
1529
|
```
|
|
1380
1530
|
|
|
1531
|
+
Available demo scripts (can also be run individually):
|
|
1532
|
+
|
|
1533
|
+
| Script | What it tests |
|
|
1534
|
+
| ------------------------------- | -------------------------------------------------------------------------------------- |
|
|
1535
|
+
| `demo-color-clips.js` | Flat colors, linear/radial gradients, transitions, full composition with color clips |
|
|
1536
|
+
| `demo-effects-pack-1.js` | Timed overlay effects (vignette, grain, blur, color adjustment) with smooth ramps |
|
|
1537
|
+
| `demo-transitions.js` | Fade, wipe, slide, dissolve, fadeblack/white, short/long durations, image transitions |
|
|
1538
|
+
| `demo-text-and-animations.js` | Positioning, fade, pop, pop-bounce, typewriter, scale-in, pulse, styling, word-replace |
|
|
1539
|
+
| `demo-ken-burns.js` | All 6 presets, smart anchors, custom diagonal, slideshow with transitions |
|
|
1540
|
+
| `demo-audio-mixing.js` | Volume levels, background music, standalone audio, loop, multi-source mix |
|
|
1541
|
+
| `demo-watermarks.js` | Text/image watermarks, all positions, timed appearance, styled over transitions |
|
|
1542
|
+
| `demo-karaoke-and-subtitles.js` | Smooth/instant karaoke, word timestamps, multiline, SRT, VTT, mixed text+karaoke |
|
|
1543
|
+
| `demo-torture-test.js` | Kitchen sink, many clips+gaps+transitions, 6 simultaneous text animations, edge cases |
|
|
1544
|
+
|
|
1545
|
+
Each script header contains a `WHAT TO CHECK` section describing the expected visual output at every timestamp, making it easy to spot regressions.
|
|
1546
|
+
|
|
1381
1547
|
## Contributing
|
|
1382
1548
|
|
|
1383
1549
|
Contributions are welcome. Please open an issue to discuss significant changes before submitting a pull request.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "simple-ffmpegjs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
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/gaps.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Detect visual gaps in a timeline of video/image clips.
|
|
2
|
+
* Detect visual gaps in a timeline of video/image/color clips.
|
|
3
3
|
* Returns an array of gap objects with {start, end, duration} properties.
|
|
4
4
|
*
|
|
5
5
|
* @param {Array<{type: string, position: number, end: number}>} clips - Array of clips
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
function detectVisualGaps(clips, options = {}) {
|
|
11
11
|
const { epsilon = 1e-3 } = options;
|
|
12
12
|
|
|
13
|
-
// Filter to only visual clips (video/image) and sort by position
|
|
13
|
+
// Filter to only visual clips (video/image/color) and sort by position
|
|
14
14
|
const visual = clips
|
|
15
|
-
.filter((c) => c.type === "video" || c.type === "image")
|
|
15
|
+
.filter((c) => c.type === "video" || c.type === "image" || c.type === "color")
|
|
16
16
|
.map((c) => ({
|
|
17
17
|
position: c.position || 0,
|
|
18
18
|
end: c.end || 0,
|
|
@@ -69,7 +69,7 @@ function hasVisualGaps(clips, options = {}) {
|
|
|
69
69
|
* @returns {number} The end time of the last visual clip, or 0 if no visual clips
|
|
70
70
|
*/
|
|
71
71
|
function getVisualTimelineEnd(clips) {
|
|
72
|
-
const visual = clips.filter((c) => c.type === "video" || c.type === "image");
|
|
72
|
+
const visual = clips.filter((c) => c.type === "video" || c.type === "image" || c.type === "color");
|
|
73
73
|
if (visual.length === 0) return 0;
|
|
74
74
|
return Math.max(...visual.map((c) => c.end || 0));
|
|
75
75
|
}
|
package/src/core/resolve.js
CHANGED