@argo-video/cli 0.13.0 → 0.15.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 +104 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +126 -7
- package/dist/cli.js.map +1 -1
- package/dist/clip.d.ts +12 -0
- package/dist/clip.d.ts.map +1 -0
- package/dist/clip.js +78 -0
- package/dist/clip.js.map +1 -0
- package/dist/config.d.ts +18 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/dashboard.d.ts +22 -0
- package/dist/dashboard.d.ts.map +1 -0
- package/dist/dashboard.js +186 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/export.d.ts +13 -2
- package/dist/export.d.ts.map +1 -1
- package/dist/export.js +136 -16
- package/dist/export.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/media.d.ts +2 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +18 -0
- package/dist/media.js.map +1 -0
- package/dist/pipeline.d.ts +8 -0
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +59 -53
- package/dist/pipeline.js.map +1 -1
- package/dist/preview.d.ts +13 -0
- package/dist/preview.d.ts.map +1 -1
- package/dist/preview.js +27 -10
- package/dist/preview.js.map +1 -1
- package/dist/progress.d.ts +8 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +67 -0
- package/dist/progress.js.map +1 -0
- package/dist/speed-ramp.d.ts +39 -0
- package/dist/speed-ramp.d.ts.map +1 -0
- package/dist/speed-ramp.js +184 -0
- package/dist/speed-ramp.js.map +1 -0
- package/dist/timeline.d.ts +19 -0
- package/dist/timeline.d.ts.map +1 -0
- package/dist/timeline.js +82 -0
- package/dist/timeline.js.map +1 -0
- package/dist/transitions.d.ts +15 -0
- package/dist/transitions.d.ts.map +1 -0
- package/dist/transitions.js +124 -0
- package/dist/transitions.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Identify gap segments between scene placements and mark them for speed-up.
|
|
4
|
+
*/
|
|
5
|
+
export function computeSegments(placements, totalDurationMs, config) {
|
|
6
|
+
const minGapMs = config.minGapMs ?? 500;
|
|
7
|
+
const gapSpeed = config.gapSpeed;
|
|
8
|
+
if (gapSpeed <= 1.0)
|
|
9
|
+
return []; // No speed-up needed
|
|
10
|
+
const sorted = [...placements].sort((a, b) => a.startMs - b.startMs);
|
|
11
|
+
const segments = [];
|
|
12
|
+
let cursor = 0;
|
|
13
|
+
for (const p of sorted) {
|
|
14
|
+
if (p.startMs - cursor >= minGapMs) {
|
|
15
|
+
// Gap before this scene
|
|
16
|
+
segments.push({
|
|
17
|
+
startMs: cursor,
|
|
18
|
+
endMs: p.startMs,
|
|
19
|
+
speed: gapSpeed,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
// Scene itself at normal speed
|
|
23
|
+
segments.push({
|
|
24
|
+
startMs: p.startMs,
|
|
25
|
+
endMs: p.endMs,
|
|
26
|
+
speed: 1.0,
|
|
27
|
+
});
|
|
28
|
+
cursor = p.endMs;
|
|
29
|
+
}
|
|
30
|
+
// Trailing gap
|
|
31
|
+
if (totalDurationMs - cursor >= minGapMs) {
|
|
32
|
+
segments.push({
|
|
33
|
+
startMs: cursor,
|
|
34
|
+
endMs: totalDurationMs,
|
|
35
|
+
speed: gapSpeed,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return segments;
|
|
39
|
+
}
|
|
40
|
+
export function remapTimeMs(timeMs, segments) {
|
|
41
|
+
if (segments.length === 0)
|
|
42
|
+
return timeMs;
|
|
43
|
+
let outputMs = 0;
|
|
44
|
+
for (const segment of segments) {
|
|
45
|
+
const segDurationMs = segment.endMs - segment.startMs;
|
|
46
|
+
if (timeMs >= segment.endMs) {
|
|
47
|
+
outputMs += segDurationMs / segment.speed;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (timeMs > segment.startMs) {
|
|
51
|
+
outputMs += (timeMs - segment.startMs) / segment.speed;
|
|
52
|
+
}
|
|
53
|
+
return Math.round(outputMs);
|
|
54
|
+
}
|
|
55
|
+
return Math.round(outputMs);
|
|
56
|
+
}
|
|
57
|
+
export function applySpeedRampToTimeline(placements, totalDurationMs, config) {
|
|
58
|
+
if (!config || config.gapSpeed <= 1.0 || placements.length === 0) {
|
|
59
|
+
return { placements, totalDurationMs, segments: [] };
|
|
60
|
+
}
|
|
61
|
+
const segments = computeSegments(placements, totalDurationMs, config);
|
|
62
|
+
if (segments.length === 0 || segments.every((segment) => segment.speed === 1.0)) {
|
|
63
|
+
return { placements, totalDurationMs, segments: [] };
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
placements: placements.map((placement) => ({
|
|
67
|
+
scene: placement.scene,
|
|
68
|
+
startMs: remapTimeMs(placement.startMs, segments),
|
|
69
|
+
endMs: remapTimeMs(placement.endMs, segments),
|
|
70
|
+
})),
|
|
71
|
+
totalDurationMs: remapTimeMs(totalDurationMs, segments),
|
|
72
|
+
segments,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Build ffmpeg filter_complex for speed ramping.
|
|
77
|
+
*
|
|
78
|
+
* Splits the input into segments, applies setpts (video) and atempo (audio)
|
|
79
|
+
* to gap segments, then concatenates everything back together.
|
|
80
|
+
*/
|
|
81
|
+
export function buildSpeedRampFilter(segments, inputs) {
|
|
82
|
+
if (segments.length === 0)
|
|
83
|
+
return null;
|
|
84
|
+
// If all segments are speed 1.0, no filter needed
|
|
85
|
+
if (segments.every((s) => s.speed === 1.0))
|
|
86
|
+
return null;
|
|
87
|
+
const parts = [];
|
|
88
|
+
const videoLabels = [];
|
|
89
|
+
const audioLabels = [];
|
|
90
|
+
for (let i = 0; i < segments.length; i++) {
|
|
91
|
+
const seg = segments[i];
|
|
92
|
+
const vLabel = `v${i}`;
|
|
93
|
+
const aLabel = `a${i}`;
|
|
94
|
+
const vOut = `vout${i}`;
|
|
95
|
+
const aOut = `aout${i}`;
|
|
96
|
+
// Trim
|
|
97
|
+
parts.push(`[${inputs.video}]trim=start=${(seg.startMs / 1000).toFixed(3)}:end=${(seg.endMs / 1000).toFixed(3)},setpts=PTS-STARTPTS[${vLabel}]`);
|
|
98
|
+
if (inputs.audio) {
|
|
99
|
+
parts.push(`[${inputs.audio}]atrim=start=${(seg.startMs / 1000).toFixed(3)}:end=${(seg.endMs / 1000).toFixed(3)},asetpts=PTS-STARTPTS[${aLabel}]`);
|
|
100
|
+
}
|
|
101
|
+
// Apply speed change
|
|
102
|
+
if (seg.speed !== 1.0) {
|
|
103
|
+
const ptsFactor = (1 / seg.speed).toFixed(4);
|
|
104
|
+
parts.push(`[${vLabel}]setpts=${ptsFactor}*PTS[${vOut}]`);
|
|
105
|
+
videoLabels.push(`[${vOut}]`);
|
|
106
|
+
if (inputs.audio) {
|
|
107
|
+
// atempo only supports 0.5–100.0; chain multiple for extreme values
|
|
108
|
+
const tempoFilters = buildAtempoChain(seg.speed);
|
|
109
|
+
parts.push(`[${aLabel}]${tempoFilters}[${aOut}]`);
|
|
110
|
+
audioLabels.push(`[${aOut}]`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
videoLabels.push(`[${vLabel}]`);
|
|
115
|
+
if (inputs.audio)
|
|
116
|
+
audioLabels.push(`[${aLabel}]`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Concatenate
|
|
120
|
+
const n = videoLabels.length;
|
|
121
|
+
const concatStreams = inputs.audio ? 'v=1:a=1' : 'v=1:a=0';
|
|
122
|
+
parts.push(`${videoLabels.join('')}${inputs.audio ? audioLabels.join('') : ''}concat=n=${n}:${concatStreams}[outv]${inputs.audio ? '[outa]' : ''}`);
|
|
123
|
+
return {
|
|
124
|
+
filterComplex: parts.join(';\n'),
|
|
125
|
+
outputLabels: { video: 'outv', audio: inputs.audio ? 'outa' : undefined },
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Chain atempo filters for speeds outside the 0.5–100.0 range.
|
|
130
|
+
* Each atempo instance handles 0.5–100.0.
|
|
131
|
+
*/
|
|
132
|
+
function buildAtempoChain(speed) {
|
|
133
|
+
const filters = [];
|
|
134
|
+
let remaining = speed;
|
|
135
|
+
while (remaining > 100.0) {
|
|
136
|
+
filters.push('atempo=100.0');
|
|
137
|
+
remaining /= 100.0;
|
|
138
|
+
}
|
|
139
|
+
while (remaining < 0.5) {
|
|
140
|
+
filters.push('atempo=0.5');
|
|
141
|
+
remaining /= 0.5;
|
|
142
|
+
}
|
|
143
|
+
filters.push(`atempo=${remaining.toFixed(4)}`);
|
|
144
|
+
return filters.join(',');
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Apply speed ramp to an already-exported MP4.
|
|
148
|
+
* Replaces the file in-place (writes to temp, then renames).
|
|
149
|
+
*/
|
|
150
|
+
export async function applySpeedRamp(inputPath, segments, hasAudio, preset, crf) {
|
|
151
|
+
const filter = buildSpeedRampFilter(segments, { video: '0:v', audio: hasAudio ? '0:a' : undefined });
|
|
152
|
+
if (!filter)
|
|
153
|
+
return;
|
|
154
|
+
const tmpPath = inputPath.replace(/\.mp4$/, '.ramped.mp4');
|
|
155
|
+
const args = [
|
|
156
|
+
'-i', inputPath,
|
|
157
|
+
'-filter_complex', filter.filterComplex,
|
|
158
|
+
'-map', `[${filter.outputLabels.video}]`,
|
|
159
|
+
];
|
|
160
|
+
if (filter.outputLabels.audio) {
|
|
161
|
+
args.push('-map', `[${filter.outputLabels.audio}]`);
|
|
162
|
+
}
|
|
163
|
+
args.push('-c:v', 'libx264', '-preset', preset, '-crf', String(crf));
|
|
164
|
+
if (hasAudio) {
|
|
165
|
+
args.push('-c:a', 'aac', '-b:a', '192k');
|
|
166
|
+
}
|
|
167
|
+
args.push('-y', tmpPath);
|
|
168
|
+
await new Promise((resolve, reject) => {
|
|
169
|
+
const proc = spawn('ffmpeg', args, { stdio: 'inherit' });
|
|
170
|
+
proc.on('error', (err) => reject(new Error(`Failed to launch ffmpeg: ${err.message}`)));
|
|
171
|
+
proc.on('close', (code) => {
|
|
172
|
+
if (code !== 0) {
|
|
173
|
+
reject(new Error(`ffmpeg speed ramp failed with exit code ${code}`));
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
resolve();
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
// Replace original with ramped version
|
|
181
|
+
const { renameSync } = await import('node:fs');
|
|
182
|
+
renameSync(tmpPath, inputPath);
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=speed-ramp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"speed-ramp.js","sourceRoot":"","sources":["../src/speed-ramp.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Placement, type SceneTiming } from './tts/align.js';
|
|
2
|
+
export interface SceneManifestEntry {
|
|
3
|
+
scene: string;
|
|
4
|
+
text?: string;
|
|
5
|
+
voice?: string;
|
|
6
|
+
speed?: number;
|
|
7
|
+
lang?: string;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
export declare function readScenesManifest(manifestPath: string): SceneManifestEntry[];
|
|
11
|
+
export declare function buildSceneTexts(entries: SceneManifestEntry[]): Record<string, string>;
|
|
12
|
+
export declare function buildSceneDurationsFromCache(demoName: string, entries: SceneManifestEntry[], defaults: {
|
|
13
|
+
voice?: string;
|
|
14
|
+
speed?: number;
|
|
15
|
+
}, projectRoot?: string): Record<string, number>;
|
|
16
|
+
export declare function computeHeadTrimMs(timing: SceneTiming, leadInMs?: number, minTrimMs?: number): number;
|
|
17
|
+
export declare function buildPlacementsFromTimingAndDurations(timing: SceneTiming, sceneDurations: Record<string, number>, totalDurationMs: number): Placement[];
|
|
18
|
+
export declare function shiftPlacements(placements: Placement[], offsetMs: number): Placement[];
|
|
19
|
+
//# sourceMappingURL=timeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeline.d.ts","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"AACA,OAAO,EAAsB,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAItF,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAW7E;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,kBAAkB,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQrF;AAED,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,kBAAkB,EAAE,EAC7B,QAAQ,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EAC5C,WAAW,SAAM,GAChB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAsBxB;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EACnB,QAAQ,SAAM,EACd,SAAS,SAAM,GACd,MAAM,CAOR;AAED,wBAAgB,qCAAqC,CACnD,MAAM,EAAE,WAAW,EACnB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACtC,eAAe,EAAE,MAAM,GACtB,SAAS,EAAE,CAoBb;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,SAAS,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,CAOtF"}
|
package/dist/timeline.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { schedulePlacements } from './tts/align.js';
|
|
3
|
+
import { ClipCache } from './tts/cache.js';
|
|
4
|
+
import { parseWavHeader } from './tts/engine.js';
|
|
5
|
+
export function readScenesManifest(manifestPath) {
|
|
6
|
+
const raw = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
7
|
+
if (!Array.isArray(raw)) {
|
|
8
|
+
throw new Error(`Manifest ${manifestPath} must contain a JSON array`);
|
|
9
|
+
}
|
|
10
|
+
return raw.map((entry) => {
|
|
11
|
+
if (!entry || typeof entry !== 'object' || typeof entry.scene !== 'string') {
|
|
12
|
+
throw new Error(`Manifest ${manifestPath} contains an entry without a valid scene name`);
|
|
13
|
+
}
|
|
14
|
+
return entry;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export function buildSceneTexts(entries) {
|
|
18
|
+
const sceneTexts = {};
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
if (typeof entry.text === 'string' && entry.text.trim().length > 0) {
|
|
21
|
+
sceneTexts[entry.scene] = entry.text;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return sceneTexts;
|
|
25
|
+
}
|
|
26
|
+
export function buildSceneDurationsFromCache(demoName, entries, defaults, projectRoot = '.') {
|
|
27
|
+
const cache = new ClipCache(projectRoot);
|
|
28
|
+
const sceneDurations = {};
|
|
29
|
+
for (const entry of entries) {
|
|
30
|
+
if (typeof entry.text !== 'string' || entry.text.trim().length === 0)
|
|
31
|
+
continue;
|
|
32
|
+
const cacheEntry = {
|
|
33
|
+
scene: entry.scene,
|
|
34
|
+
text: entry.text,
|
|
35
|
+
voice: entry.voice ?? defaults.voice,
|
|
36
|
+
speed: entry.speed ?? defaults.speed,
|
|
37
|
+
lang: entry.lang,
|
|
38
|
+
};
|
|
39
|
+
const clipPath = cache.getClipPath(demoName, cacheEntry);
|
|
40
|
+
if (!existsSync(clipPath))
|
|
41
|
+
continue;
|
|
42
|
+
const wavBuf = readFileSync(clipPath);
|
|
43
|
+
sceneDurations[entry.scene] = parseWavHeader(wavBuf).durationMs;
|
|
44
|
+
}
|
|
45
|
+
return sceneDurations;
|
|
46
|
+
}
|
|
47
|
+
export function computeHeadTrimMs(timing, leadInMs = 200, minTrimMs = 500) {
|
|
48
|
+
const markTimes = Object.values(timing);
|
|
49
|
+
if (markTimes.length === 0)
|
|
50
|
+
return 0;
|
|
51
|
+
const firstMarkMs = Math.min(...markTimes);
|
|
52
|
+
const headTrimMs = Math.max(0, firstMarkMs - leadInMs);
|
|
53
|
+
return headTrimMs <= minTrimMs ? 0 : headTrimMs;
|
|
54
|
+
}
|
|
55
|
+
export function buildPlacementsFromTimingAndDurations(timing, sceneDurations, totalDurationMs) {
|
|
56
|
+
const voicedPlacements = schedulePlacements(Object.entries(timing)
|
|
57
|
+
.filter(([scene]) => (sceneDurations[scene] ?? 0) > 0)
|
|
58
|
+
.map(([scene, startMs]) => ({
|
|
59
|
+
scene,
|
|
60
|
+
startMs,
|
|
61
|
+
durationMs: sceneDurations[scene],
|
|
62
|
+
})));
|
|
63
|
+
const voicedScenes = new Set(voicedPlacements.map((p) => p.scene));
|
|
64
|
+
const sortedMarks = Object.entries(timing).sort((a, b) => a[1] - b[1]);
|
|
65
|
+
const silentPlacements = sortedMarks.flatMap(([scene, startMs], index) => {
|
|
66
|
+
if (voicedScenes.has(scene))
|
|
67
|
+
return [];
|
|
68
|
+
const endMs = index + 1 < sortedMarks.length ? sortedMarks[index + 1][1] : totalDurationMs;
|
|
69
|
+
return [{ scene, startMs, endMs }];
|
|
70
|
+
});
|
|
71
|
+
return [...voicedPlacements, ...silentPlacements].sort((a, b) => a.startMs - b.startMs);
|
|
72
|
+
}
|
|
73
|
+
export function shiftPlacements(placements, offsetMs) {
|
|
74
|
+
if (offsetMs <= 0)
|
|
75
|
+
return placements;
|
|
76
|
+
return placements.map((placement) => ({
|
|
77
|
+
...placement,
|
|
78
|
+
startMs: Math.max(0, placement.startMs - offsetMs),
|
|
79
|
+
endMs: Math.max(0, placement.endMs - offsetMs),
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=timeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeline.js","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Placement } from './tts/align.js';
|
|
2
|
+
import type { TransitionConfig } from './config.js';
|
|
3
|
+
/**
|
|
4
|
+
* Generate ffmpeg transition filters for scene boundaries.
|
|
5
|
+
*
|
|
6
|
+
* Returns either:
|
|
7
|
+
* - Simple string[] for -vf (wipe transitions only)
|
|
8
|
+
* - A filterComplex object for filter_complex (fade/dissolve — uses split+trim+fade+concat)
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildTransitionFilters(placements: Placement[], transition: TransitionConfig, hasAudio?: boolean, fps?: number): string[] | {
|
|
11
|
+
filterComplex: string;
|
|
12
|
+
videoOutput: string;
|
|
13
|
+
audioOutput: string | null;
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=transitions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transitions.d.ts","sourceRoot":"","sources":["../src/transitions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AA4HpD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,SAAS,EAAE,EACvB,UAAU,EAAE,gBAAgB,EAC5B,QAAQ,CAAC,EAAE,OAAO,EAClB,GAAG,GAAE,MAAW,GACf,MAAM,EAAE,GAAG;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAkCvF"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
function buildDirectionalWipe(direction, boundarySec, halfDur) {
|
|
2
|
+
const coverStart = boundarySec - halfDur;
|
|
3
|
+
const revealEnd = boundarySec + halfDur;
|
|
4
|
+
const coverDur = halfDur.toFixed(3);
|
|
5
|
+
const revealDur = halfDur.toFixed(3);
|
|
6
|
+
if (direction === 'left') {
|
|
7
|
+
return [
|
|
8
|
+
`drawbox=x='iw-if(between(t\\,${coverStart.toFixed(3)}\\,${boundarySec.toFixed(3)})\\,(t-${coverStart.toFixed(3)})/${coverDur}*iw\\,0)':y=0:w='if(between(t\\,${coverStart.toFixed(3)}\\,${boundarySec.toFixed(3)})\\,(t-${coverStart.toFixed(3)})/${coverDur}*iw\\,0)':h=ih:color=black:t=fill`,
|
|
9
|
+
`drawbox=x=0:y=0:w='if(between(t\\,${boundarySec.toFixed(3)}\\,${revealEnd.toFixed(3)})\\,(1-(t-${boundarySec.toFixed(3)})/${revealDur})*iw\\,0)':h=ih:color=black:t=fill`,
|
|
10
|
+
];
|
|
11
|
+
}
|
|
12
|
+
return [
|
|
13
|
+
`drawbox=x=0:y=0:w='if(between(t\\,${coverStart.toFixed(3)}\\,${boundarySec.toFixed(3)})\\,(t-${coverStart.toFixed(3)})/${coverDur}*iw\\,0)':h=ih:color=black:t=fill`,
|
|
14
|
+
`drawbox=x='iw-if(between(t\\,${boundarySec.toFixed(3)}\\,${revealEnd.toFixed(3)})\\,(1-(t-${boundarySec.toFixed(3)})/${revealDur})*iw\\,0)':y=0:w='if(between(t\\,${boundarySec.toFixed(3)}\\,${revealEnd.toFixed(3)})\\,(1-(t-${boundarySec.toFixed(3)})/${revealDur})*iw\\,0)':h=ih:color=black:t=fill`,
|
|
15
|
+
];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Build a filter_complex string for fade transitions using split+trim+fade+concat.
|
|
19
|
+
*
|
|
20
|
+
* This is the only reliable way to apply multiple fade transitions in ffmpeg —
|
|
21
|
+
* the fade filter only supports one fade-out per stream instance, so we split
|
|
22
|
+
* the video at each scene boundary, apply fades independently, then concatenate.
|
|
23
|
+
*
|
|
24
|
+
* Both fade-through-black and dissolve use dip-to-black. The difference is
|
|
25
|
+
* duration: dissolve uses a quicker dip (60% of half-duration) for a subtler
|
|
26
|
+
* effect. A true crossfade/dissolve with overlapping blend would require
|
|
27
|
+
* ffmpeg's xfade filter which needs re-encoded segment pairs — not practical
|
|
28
|
+
* for continuous recordings.
|
|
29
|
+
*/
|
|
30
|
+
function buildFadeFilterComplex(placements, halfDur, isDissolveDip, videoInputLabel, audioInputLabel, fps) {
|
|
31
|
+
// Build boundary times from placements (scene starts after the first)
|
|
32
|
+
const boundaries = [];
|
|
33
|
+
for (let i = 1; i < placements.length; i++) {
|
|
34
|
+
boundaries.push(placements[i].startMs / 1000);
|
|
35
|
+
}
|
|
36
|
+
// For dissolve, use shorter fade duration for a dip effect
|
|
37
|
+
const fadeDur = isDissolveDip ? halfDur * 0.6 : halfDur;
|
|
38
|
+
const numSegments = boundaries.length + 1;
|
|
39
|
+
const parts = [];
|
|
40
|
+
const frameSec = 1 / fps;
|
|
41
|
+
// Split video into N segments
|
|
42
|
+
parts.push(`${videoInputLabel}split=${numSegments}${Array.from({ length: numSegments }, (_, i) => `[vs${i}]`).join('')}`);
|
|
43
|
+
// Split audio if present
|
|
44
|
+
if (audioInputLabel) {
|
|
45
|
+
parts.push(`${audioInputLabel}asplit=${numSegments}${Array.from({ length: numSegments }, (_, i) => `[as${i}]`).join('')}`);
|
|
46
|
+
}
|
|
47
|
+
// Trim and fade each segment
|
|
48
|
+
const segLabels = [];
|
|
49
|
+
const aSegLabels = [];
|
|
50
|
+
for (let i = 0; i < numSegments; i++) {
|
|
51
|
+
const start = i === 0 ? 0 : boundaries[i - 1];
|
|
52
|
+
const end = i < boundaries.length ? boundaries[i] : '';
|
|
53
|
+
const trimEnd = end !== '' ? `:${end.toFixed(4)}` : '';
|
|
54
|
+
const label = `v${i}`;
|
|
55
|
+
let chain = `[vs${i}]trim=${start.toFixed(4)}${trimEnd},setpts=PTS-STARTPTS`;
|
|
56
|
+
// Fade out at end of segment (except last).
|
|
57
|
+
// End the fade one frame before the cut so the final visible frame of the
|
|
58
|
+
// outgoing segment is already black and cannot "peek" through at the join.
|
|
59
|
+
if (i < boundaries.length) {
|
|
60
|
+
const segDuration = end - start;
|
|
61
|
+
const fadeEnd = Math.max(0, segDuration - frameSec);
|
|
62
|
+
const effectiveFadeDur = Math.min(fadeDur, fadeEnd);
|
|
63
|
+
if (effectiveFadeDur > 0) {
|
|
64
|
+
const fadeStart = Math.max(0, fadeEnd - effectiveFadeDur);
|
|
65
|
+
chain += `,fade=t=out:st=${fadeStart.toFixed(4)}:d=${effectiveFadeDur.toFixed(4)}`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Fade in at start of segment (except first). The first visible frame of
|
|
69
|
+
// the incoming segment starts black, matching the outgoing black frame.
|
|
70
|
+
if (i > 0) {
|
|
71
|
+
chain += `,fade=t=in:st=0:d=${fadeDur.toFixed(4)}`;
|
|
72
|
+
}
|
|
73
|
+
chain += `[${label}]`;
|
|
74
|
+
parts.push(chain);
|
|
75
|
+
segLabels.push(`[${label}]`);
|
|
76
|
+
// Audio: trim to match video segment, no fading
|
|
77
|
+
if (audioInputLabel) {
|
|
78
|
+
const aLabel = `a${i}`;
|
|
79
|
+
parts.push(`[as${i}]atrim=${start.toFixed(4)}${trimEnd},asetpts=PTS-STARTPTS[${aLabel}]`);
|
|
80
|
+
aSegLabels.push(`[${aLabel}]`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Concat all segments
|
|
84
|
+
const totalConcatSegments = segLabels.length;
|
|
85
|
+
const videoOutput = 'vfaded';
|
|
86
|
+
if (audioInputLabel) {
|
|
87
|
+
const audioOutput = 'afaded';
|
|
88
|
+
const interleaved = segLabels.map((v, i) => `${v}${aSegLabels[i]}`).join('');
|
|
89
|
+
parts.push(`${interleaved}concat=n=${totalConcatSegments}:v=1:a=1[${videoOutput}][${audioOutput}]`);
|
|
90
|
+
return { filterComplex: parts.join(';\n'), videoOutput: `[${videoOutput}]`, audioOutput: `[${audioOutput}]` };
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
parts.push(`${segLabels.join('')}concat=n=${totalConcatSegments}:v=1:a=0[${videoOutput}]`);
|
|
94
|
+
return { filterComplex: parts.join(';\n'), videoOutput: `[${videoOutput}]`, audioOutput: null };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Generate ffmpeg transition filters for scene boundaries.
|
|
99
|
+
*
|
|
100
|
+
* Returns either:
|
|
101
|
+
* - Simple string[] for -vf (wipe transitions only)
|
|
102
|
+
* - A filterComplex object for filter_complex (fade/dissolve — uses split+trim+fade+concat)
|
|
103
|
+
*/
|
|
104
|
+
export function buildTransitionFilters(placements, transition, hasAudio, fps = 30) {
|
|
105
|
+
if (placements.length < 2)
|
|
106
|
+
return [];
|
|
107
|
+
const durMs = transition.durationMs ?? 500;
|
|
108
|
+
const durSec = durMs / 1000;
|
|
109
|
+
const halfDur = durSec / 2;
|
|
110
|
+
if (transition.type === 'wipe-left' || transition.type === 'wipe-right') {
|
|
111
|
+
const parts = [];
|
|
112
|
+
for (let i = 1; i < placements.length; i++) {
|
|
113
|
+
const boundarySec = placements[i].startMs / 1000;
|
|
114
|
+
parts.push(...buildDirectionalWipe(transition.type === 'wipe-left' ? 'left' : 'right', boundarySec, halfDur));
|
|
115
|
+
}
|
|
116
|
+
return parts;
|
|
117
|
+
}
|
|
118
|
+
// Fade-through-black and dissolve use filter_complex with split+trim+fade+concat.
|
|
119
|
+
// This is the only approach that reliably applies multiple fade transitions —
|
|
120
|
+
// ffmpeg's fade filter only supports one fade-out per stream instance.
|
|
121
|
+
const isDissolveDip = transition.type === 'dissolve';
|
|
122
|
+
return buildFadeFilterComplex(placements, halfDur, isDissolveDip, '[0:v]', hasAudio ? '[1:a]' : null, fps);
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=transitions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transitions.js","sourceRoot":"","sources":["../src/transitions.ts"],"names":[],"mappings":"AAGA,SAAS,oBAAoB,CAC3B,SAA2B,EAC3B,WAAmB,EACnB,OAAe;IAEf,MAAM,UAAU,GAAG,WAAW,GAAG,OAAO,CAAC;IACzC,MAAM,SAAS,GAAG,WAAW,GAAG,OAAO,CAAC;IACxC,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACpC,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAErC,IAAI,SAAS,KAAK,MAAM,EAAE,CAAC;QACzB,OAAO;YACL,gCAAgC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,QAAQ,mCAAmC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,QAAQ,mCAAmC;YAChS,qCAAqC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,SAAS,oCAAoC;SAC3K,CAAC;IACJ,CAAC;IAED,OAAO;QACL,qCAAqC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,QAAQ,mCAAmC;QACrK,gCAAgC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,SAAS,oCAAoC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,SAAS,oCAAoC;KAC3S,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAS,sBAAsB,CAC7B,UAAuB,EACvB,OAAe,EACf,aAAsB,EACtB,eAAuB,EACvB,eAA8B,EAC9B,GAAW;IAEX,sEAAsE;IACtE,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3C,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IAChD,CAAC;IAED,2DAA2D;IAC3D,MAAM,OAAO,GAAG,aAAa,CAAC,CAAC,CAAC,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC;IAExD,MAAM,WAAW,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,QAAQ,GAAG,CAAC,GAAG,GAAG,CAAC;IAEzB,8BAA8B;IAC9B,KAAK,CAAC,IAAI,CAAC,GAAG,eAAe,SAAS,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAE1H,yBAAyB;IACzB,IAAI,eAAe,EAAE,CAAC;QACpB,KAAK,CAAC,IAAI,CAAC,GAAG,eAAe,UAAU,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC7H,CAAC;IAED,6BAA6B;IAC7B,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,MAAM,UAAU,GAAa,EAAE,CAAC;IAEhC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9C,MAAM,GAAG,GAAG,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACvD,MAAM,OAAO,GAAG,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,IAAK,GAAc,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnE,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC;QAEtB,IAAI,KAAK,GAAG,MAAM,CAAC,SAAS,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,sBAAsB,CAAC;QAE7E,4CAA4C;QAC5C,0EAA0E;QAC1E,2EAA2E;QAC3E,IAAI,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC;YAC1B,MAAM,WAAW,GAAI,GAAc,GAAG,KAAK,CAAC;YAC5C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,GAAG,QAAQ,CAAC,CAAC;YACpD,MAAM,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACpD,IAAI,gBAAgB,GAAG,CAAC,EAAE,CAAC;gBACzB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAAC,CAAC;gBAC1D,KAAK,IAAI,kBAAkB,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;YACrF,CAAC;QACH,CAAC;QAED,yEAAyE;QACzE,wEAAwE;QACxE,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACV,KAAK,IAAI,qBAAqB,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QACrD,CAAC;QAED,KAAK,IAAI,IAAI,KAAK,GAAG,CAAC;QACtB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClB,SAAS,CAAC,IAAI,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;QAE7B,gDAAgD;QAChD,IAAI,eAAe,EAAE,CAAC;YACpB,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;YACvB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,yBAAyB,MAAM,GAAG,CAAC,CAAC;YAC1F,UAAU,CAAC,IAAI,CAAC,IAAI,MAAM,GAAG,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAED,sBAAsB;IACtB,MAAM,mBAAmB,GAAG,SAAS,CAAC,MAAM,CAAC;IAC7C,MAAM,WAAW,GAAG,QAAQ,CAAC;IAC7B,IAAI,eAAe,EAAE,CAAC;QACpB,MAAM,WAAW,GAAG,QAAQ,CAAC;QAC7B,MAAM,WAAW,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC7E,KAAK,CAAC,IAAI,CAAC,GAAG,WAAW,YAAY,mBAAmB,YAAY,WAAW,KAAK,WAAW,GAAG,CAAC,CAAC;QACpG,OAAO,EAAE,aAAa,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,WAAW,EAAE,IAAI,WAAW,GAAG,EAAE,WAAW,EAAE,IAAI,WAAW,GAAG,EAAE,CAAC;IAChH,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,YAAY,mBAAmB,YAAY,WAAW,GAAG,CAAC,CAAC;QAC3F,OAAO,EAAE,aAAa,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,WAAW,EAAE,IAAI,WAAW,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IAClG,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CACpC,UAAuB,EACvB,UAA4B,EAC5B,QAAkB,EAClB,MAAc,EAAE;IAEhB,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IAErC,MAAM,KAAK,GAAG,UAAU,CAAC,UAAU,IAAI,GAAG,CAAC;IAC3C,MAAM,MAAM,GAAG,KAAK,GAAG,IAAI,CAAC;IAC5B,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,CAAC;IAE3B,IAAI,UAAU,CAAC,IAAI,KAAK,WAAW,IAAI,UAAU,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;QACxE,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3C,MAAM,WAAW,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC;YACjD,KAAK,CAAC,IAAI,CACR,GAAG,oBAAoB,CACrB,UAAU,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAClD,WAAW,EACX,OAAO,CACR,CACF,CAAC;QACJ,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,kFAAkF;IAClF,8EAA8E;IAC9E,uEAAuE;IACvE,MAAM,aAAa,GAAG,UAAU,CAAC,IAAI,KAAK,UAAU,CAAC;IACrD,OAAO,sBAAsB,CAC3B,UAAU,EACV,OAAO,EACP,aAAa,EACb,OAAO,EACP,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EACzB,GAAG,CACJ,CAAC;AACJ,CAAC"}
|