@argo-video/cli 0.20.0 → 0.21.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 +33 -7
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +28 -0
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +16 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/export.d.ts +10 -1
- package/dist/export.d.ts.map +1 -1
- package/dist/export.js +90 -6
- package/dist/export.js.map +1 -1
- package/dist/freeze.d.ts +59 -0
- package/dist/freeze.d.ts.map +1 -0
- package/dist/freeze.js +113 -0
- package/dist/freeze.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/music/musicgen.d.ts +43 -0
- package/dist/music/musicgen.d.ts.map +1 -0
- package/dist/music/musicgen.js +120 -0
- package/dist/music/musicgen.js.map +1 -0
- package/dist/narration.d.ts.map +1 -1
- package/dist/narration.js +13 -1
- package/dist/narration.js.map +1 -1
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +61 -13
- package/dist/pipeline.js.map +1 -1
- package/dist/preview.d.ts +3 -0
- package/dist/preview.d.ts.map +1 -1
- package/dist/preview.js +624 -9
- package/dist/preview.js.map +1 -1
- package/dist/record.d.ts.map +1 -1
- package/dist/record.js +26 -1
- package/dist/record.js.map +1 -1
- package/package.json +1 -1
package/dist/freeze.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve scene-relative freeze specs to absolute timeline positions using placements.
|
|
3
|
+
*
|
|
4
|
+
* Freezes whose scene is not found in placements are silently dropped.
|
|
5
|
+
* The returned array is sorted chronologically.
|
|
6
|
+
*/
|
|
7
|
+
export function resolveFreezes(freezes, placements) {
|
|
8
|
+
const placementMap = new Map();
|
|
9
|
+
for (const p of placements) {
|
|
10
|
+
placementMap.set(p.scene, p);
|
|
11
|
+
}
|
|
12
|
+
const resolved = [];
|
|
13
|
+
for (const f of freezes) {
|
|
14
|
+
const placement = placementMap.get(f.scene);
|
|
15
|
+
if (!placement)
|
|
16
|
+
continue;
|
|
17
|
+
resolved.push({
|
|
18
|
+
absoluteMs: placement.startMs + f.atMs,
|
|
19
|
+
durationMs: f.durationMs,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
resolved.sort((a, b) => a.absoluteMs - b.absoluteMs);
|
|
23
|
+
return resolved;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Build an ffmpeg filter_complex expression that freezes specific frames.
|
|
27
|
+
*
|
|
28
|
+
* Strategy: split the video at each freeze point using trim, apply tpad to clone
|
|
29
|
+
* the last frame for the freeze duration, then concatenate all segments.
|
|
30
|
+
*
|
|
31
|
+
* Freezes are processed in chronological order. Each freeze adds `durationMs`
|
|
32
|
+
* to the total video length.
|
|
33
|
+
*
|
|
34
|
+
* @param freezes - Resolved freezes sorted chronologically.
|
|
35
|
+
* @param totalDurationMs - Total video duration before freezes (used for the final trim).
|
|
36
|
+
* @param inputLabel - The ffmpeg stream label for the video input (e.g. '0:v').
|
|
37
|
+
* @returns The filter expression, output label, and total added duration — or null if no freezes.
|
|
38
|
+
*/
|
|
39
|
+
export function buildFreezeFilter(freezes, totalDurationMs, inputLabel) {
|
|
40
|
+
if (freezes.length === 0)
|
|
41
|
+
return null;
|
|
42
|
+
const parts = [];
|
|
43
|
+
const concatLabels = [];
|
|
44
|
+
let segIndex = 0;
|
|
45
|
+
let cursor = 0; // current position in the source timeline (ms)
|
|
46
|
+
let addedDurationMs = 0;
|
|
47
|
+
for (const freeze of freezes) {
|
|
48
|
+
const freezeTimeSec = (freeze.absoluteMs / 1000).toFixed(3);
|
|
49
|
+
const freezeDurSec = (freeze.durationMs / 1000).toFixed(3);
|
|
50
|
+
// Segment before the freeze point (may be zero-length if two freezes are at the same point)
|
|
51
|
+
if (freeze.absoluteMs > cursor) {
|
|
52
|
+
const startSec = (cursor / 1000).toFixed(3);
|
|
53
|
+
const label = `frzseg${segIndex}`;
|
|
54
|
+
parts.push(`[${inputLabel}]trim=start=${startSec}:end=${freezeTimeSec},setpts=PTS-STARTPTS[${label}]`);
|
|
55
|
+
concatLabels.push(`[${label}]`);
|
|
56
|
+
segIndex++;
|
|
57
|
+
}
|
|
58
|
+
// The frozen frame: trim a tiny slice at the freeze point, then tpad to clone it
|
|
59
|
+
const frozenLabel = `frzhold${segIndex}`;
|
|
60
|
+
// Trim a 1-frame slice (use a very short duration; tpad clones the last frame)
|
|
61
|
+
const sliceEndSec = ((freeze.absoluteMs + 1) / 1000).toFixed(3);
|
|
62
|
+
parts.push(`[${inputLabel}]trim=start=${freezeTimeSec}:end=${sliceEndSec},setpts=PTS-STARTPTS,tpad=stop_mode=clone:stop_duration=${freezeDurSec}[${frozenLabel}]`);
|
|
63
|
+
concatLabels.push(`[${frozenLabel}]`);
|
|
64
|
+
segIndex++;
|
|
65
|
+
addedDurationMs += freeze.durationMs;
|
|
66
|
+
cursor = freeze.absoluteMs;
|
|
67
|
+
}
|
|
68
|
+
// Remaining segment after the last freeze
|
|
69
|
+
if (cursor < totalDurationMs) {
|
|
70
|
+
const startSec = (cursor / 1000).toFixed(3);
|
|
71
|
+
const endSec = (totalDurationMs / 1000).toFixed(3);
|
|
72
|
+
const label = `frzseg${segIndex}`;
|
|
73
|
+
parts.push(`[${inputLabel}]trim=start=${startSec}:end=${endSec},setpts=PTS-STARTPTS[${label}]`);
|
|
74
|
+
concatLabels.push(`[${label}]`);
|
|
75
|
+
}
|
|
76
|
+
const outputLabel = 'frzout';
|
|
77
|
+
parts.push(`${concatLabels.join('')}concat=n=${concatLabels.length}:v=1:a=0[${outputLabel}]`);
|
|
78
|
+
return { filter: parts.join(';\n'), outputLabel, addedDurationMs };
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Shift placements, chapters, and subtitles forward to account for freeze-inserted duration.
|
|
82
|
+
*
|
|
83
|
+
* Each freeze at `absoluteMs` pushes everything after that point by `durationMs`.
|
|
84
|
+
* Freezes must be sorted chronologically.
|
|
85
|
+
*/
|
|
86
|
+
export function adjustPlacementsForFreezes(placements, freezes) {
|
|
87
|
+
if (freezes.length === 0)
|
|
88
|
+
return placements;
|
|
89
|
+
return placements.map((p) => {
|
|
90
|
+
let startShift = 0;
|
|
91
|
+
let endShift = 0;
|
|
92
|
+
for (const f of freezes) {
|
|
93
|
+
if (f.absoluteMs <= p.startMs) {
|
|
94
|
+
startShift += f.durationMs;
|
|
95
|
+
}
|
|
96
|
+
if (f.absoluteMs <= p.endMs) {
|
|
97
|
+
endShift += f.durationMs;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
scene: p.scene,
|
|
102
|
+
startMs: p.startMs + startShift,
|
|
103
|
+
endMs: p.endMs + endShift,
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Compute the total duration added by all freezes.
|
|
109
|
+
*/
|
|
110
|
+
export function totalFreezeDurationMs(freezes) {
|
|
111
|
+
return freezes.reduce((sum, f) => sum + f.durationMs, 0);
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=freeze.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"freeze.js","sourceRoot":"","sources":["../src/freeze.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { defineConfig, loadConfig, demosProject, type ArgoConfig, type UserConfig, type TTSConfig, type TTSEngine, type VideoConfig, type ExportConfig, type TransitionType, type TransitionConfig, type SpeedRampConfig, type VariantConfig, type AudioConfig, } from './config.js';
|
|
1
|
+
export { defineConfig, loadConfig, demosProject, type ArgoConfig, type UserConfig, type TTSConfig, type TTSEngine, type VideoConfig, type ExportConfig, type TransitionType, type TransitionConfig, type SpeedRampConfig, type VariantConfig, type AudioConfig, type WatermarkConfig, } from './config.js';
|
|
2
2
|
export { test, expect, demoType } from './fixtures.js';
|
|
3
3
|
export { NarrationTimeline, type SceneDurationOptions } from './narration.js';
|
|
4
4
|
export { showCaption, hideCaption, withCaption } from './captions.js';
|
|
@@ -16,10 +16,12 @@ export { runDoctor, formatDoctorResults } from './doctor.js';
|
|
|
16
16
|
export { runPipeline, runBatchPipeline, discoverDemos, type PipelineOptions } from './pipeline.js';
|
|
17
17
|
export { buildTransitionFilters } from './transitions.js';
|
|
18
18
|
export { computeSegments, applySpeedRamp } from './speed-ramp.js';
|
|
19
|
+
export { buildFreezeFilter, resolveFreezes, adjustPlacementsForFreezes, totalFreezeDurationMs, type FreezeSpec, type ResolvedFreeze, } from './freeze.js';
|
|
19
20
|
export { buildCameraMoveFilter, shiftCameraMoves, scaleCameraMoves, type CameraMove } from './camera-move.js';
|
|
20
21
|
export { runFfmpegWithProgress } from './progress.js';
|
|
21
22
|
export { startDashboardServer } from './dashboard.js';
|
|
22
23
|
export { extractClip, type ClipOptions } from './clip.js';
|
|
23
24
|
export { releasePrep, type ReleasePrepOptions } from './release-prep.js';
|
|
24
25
|
export { init } from './init.js';
|
|
26
|
+
export { generateMusic, generateMusicCached, type MusicGenOptions } from './music/musicgen.js';
|
|
25
27
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,WAAW,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,eAAe,GACrB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGvD,OAAO,EAAE,iBAAiB,EAAE,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAG9E,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAGtE,OAAO,EACL,WAAW,EACX,WAAW,EACX,WAAW,EACX,KAAK,UAAU,EACf,KAAK,oBAAoB,EACzB,KAAK,IAAI,EACT,KAAK,YAAY,EACjB,KAAK,YAAY,GAClB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,YAAY,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAGlE,OAAO,EACL,SAAS,EACT,SAAS,EACT,SAAS,EACT,MAAM,EACN,WAAW,EACX,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,aAAa,GACnB,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,eAAe,EACf,WAAW,EACX,KAAK,sBAAsB,GAC5B,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAGjD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAG1D,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAGxD,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAGpF,OAAO,EAAE,YAAY,EAAE,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAGxF,OAAO,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAG7D,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,aAAa,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAGnG,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAG1D,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGlE,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,0BAA0B,EAC1B,qBAAqB,EACrB,KAAK,UAAU,EACf,KAAK,cAAc,GACpB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9G,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAGtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAGtD,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAG1D,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAGzE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -32,6 +32,8 @@ export { runPipeline, runBatchPipeline, discoverDemos } from './pipeline.js';
|
|
|
32
32
|
export { buildTransitionFilters } from './transitions.js';
|
|
33
33
|
// Speed Ramp
|
|
34
34
|
export { computeSegments, applySpeedRamp } from './speed-ramp.js';
|
|
35
|
+
// Freeze
|
|
36
|
+
export { buildFreezeFilter, resolveFreezes, adjustPlacementsForFreezes, totalFreezeDurationMs, } from './freeze.js';
|
|
35
37
|
// Camera Moves
|
|
36
38
|
export { buildCameraMoveFilter, shiftCameraMoves, scaleCameraMoves } from './camera-move.js';
|
|
37
39
|
// Progress
|
|
@@ -44,4 +46,6 @@ export { extractClip } from './clip.js';
|
|
|
44
46
|
export { releasePrep } from './release-prep.js';
|
|
45
47
|
// Init
|
|
46
48
|
export { init } from './init.js';
|
|
49
|
+
// Music Generation
|
|
50
|
+
export { generateMusic, generateMusicCached } from './music/musicgen.js';
|
|
47
51
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qDAAqD;AAErD,SAAS;AACT,OAAO,EACL,YAAY,EACZ,UAAU,EACV,YAAY,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qDAAqD;AAErD,SAAS;AACT,OAAO,EACL,YAAY,EACZ,UAAU,EACV,YAAY,GAab,MAAM,aAAa,CAAC;AAErB,WAAW;AACX,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEvD,YAAY;AACZ,OAAO,EAAE,iBAAiB,EAA6B,MAAM,gBAAgB,CAAC;AAE9E,WAAW;AACX,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEtE,WAAW;AACX,OAAO,EACL,WAAW,EACX,WAAW,EACX,WAAW,GAMZ,MAAM,qBAAqB,CAAC;AAE7B,UAAU;AACV,OAAO,EAAE,YAAY,EAAwB,MAAM,cAAc,CAAC;AAElE,SAAS;AACT,OAAO,EACL,SAAS,EACT,SAAS,EACT,SAAS,EACT,MAAM,EACN,WAAW,GAKZ,MAAM,aAAa,CAAC;AAErB,SAAS;AACT,OAAO,EACL,eAAe,EACf,WAAW,GAEZ,MAAM,aAAa,CAAC;AAIrB,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEjD,YAAY;AACZ,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE1D,WAAW;AACX,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAExD,SAAS;AACT,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAoB,MAAM,aAAa,CAAC;AAEpF,WAAW;AACX,OAAO,EAAE,YAAY,EAA6C,MAAM,eAAe,CAAC;AAExF,SAAS;AACT,OAAO,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAE7D,WAAW;AACX,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,aAAa,EAAwB,MAAM,eAAe,CAAC;AAEnG,cAAc;AACd,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,aAAa;AACb,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAElE,SAAS;AACT,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,0BAA0B,EAC1B,qBAAqB,GAGtB,MAAM,aAAa,CAAC;AAErB,eAAe;AACf,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,gBAAgB,EAAmB,MAAM,kBAAkB,CAAC;AAE9G,WAAW;AACX,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAEtD,YAAY;AACZ,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEtD,OAAO;AACP,OAAO,EAAE,WAAW,EAAoB,MAAM,WAAW,CAAC;AAE1D,eAAe;AACf,OAAO,EAAE,WAAW,EAA2B,MAAM,mBAAmB,CAAC;AAEzE,OAAO;AACP,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,mBAAmB;AACnB,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAwB,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI-generated background music via MusicGen (Transformers.js).
|
|
3
|
+
* Uses Xenova/musicgen-small (~1.8GB ONNX model, downloaded on first run).
|
|
4
|
+
*/
|
|
5
|
+
export interface MusicGenOptions {
|
|
6
|
+
/** Text prompt describing the desired music (e.g., "lofi chill ambient with soft piano"). */
|
|
7
|
+
prompt: string;
|
|
8
|
+
/** Duration in seconds for generated music. Default: 30. */
|
|
9
|
+
durationSec?: number;
|
|
10
|
+
/** Classifier-free guidance scale. Higher = more prompt-adherent. Default: 3. */
|
|
11
|
+
guidanceScale?: number;
|
|
12
|
+
/** Sampling temperature. Default: 1.0. */
|
|
13
|
+
temperature?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Compute a content-addressed cache key from all generation parameters.
|
|
17
|
+
*/
|
|
18
|
+
export declare function computeCacheKey(options: MusicGenOptions): string;
|
|
19
|
+
/**
|
|
20
|
+
* Returns the cache file path for a given demo + options.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getCachePath(argoDir: string, options: MusicGenOptions): string;
|
|
23
|
+
/**
|
|
24
|
+
* Check if a cached music file exists for the given options.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isCached(argoDir: string, options: MusicGenOptions): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Generate background music from a text prompt using MusicGen.
|
|
29
|
+
*
|
|
30
|
+
* The model (~1.8GB) is downloaded on first run. Generation takes ~30-60s
|
|
31
|
+
* depending on duration and hardware.
|
|
32
|
+
*
|
|
33
|
+
* Returns a WAV buffer (Float32, mono, 32kHz — ffmpeg handles resampling during export).
|
|
34
|
+
*/
|
|
35
|
+
export declare function generateMusic(options: MusicGenOptions): Promise<Buffer>;
|
|
36
|
+
/**
|
|
37
|
+
* Generate music with caching. Returns the path to the WAV file.
|
|
38
|
+
*
|
|
39
|
+
* If a cached file exists for the same prompt + parameters, skips generation.
|
|
40
|
+
* On model load or generation failure, warns and returns null (best-effort).
|
|
41
|
+
*/
|
|
42
|
+
export declare function generateMusicCached(argoDir: string, options: MusicGenOptions): Promise<string | null>;
|
|
43
|
+
//# sourceMappingURL=musicgen.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"musicgen.d.ts","sourceRoot":"","sources":["../../src/music/musicgen.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,MAAM,WAAW,eAAe;IAC9B,6FAA6F;IAC7F,MAAM,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iFAAiF;IACjF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAWD;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,CAahE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,MAAM,CAG9E;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAE3E;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAwD7E;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAuBxB"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI-generated background music via MusicGen (Transformers.js).
|
|
3
|
+
* Uses Xenova/musicgen-small (~1.8GB ONNX model, downloaded on first run).
|
|
4
|
+
*/
|
|
5
|
+
import crypto from 'node:crypto';
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { createWavBuffer } from '../tts/engine.js';
|
|
9
|
+
/** MusicGen outputs audio at 32kHz sample rate. */
|
|
10
|
+
const MUSICGEN_SAMPLE_RATE = 32_000;
|
|
11
|
+
/** MusicGen uses ~50 tokens per second of audio. */
|
|
12
|
+
const TOKENS_PER_SECOND = 50;
|
|
13
|
+
const DEFAULT_DURATION_SEC = 30;
|
|
14
|
+
const DEFAULT_GUIDANCE_SCALE = 3;
|
|
15
|
+
const DEFAULT_TEMPERATURE = 1.0;
|
|
16
|
+
const MODEL_ID = 'Xenova/musicgen-small';
|
|
17
|
+
/**
|
|
18
|
+
* Compute a content-addressed cache key from all generation parameters.
|
|
19
|
+
*/
|
|
20
|
+
export function computeCacheKey(options) {
|
|
21
|
+
const { prompt, durationSec, guidanceScale, temperature } = options;
|
|
22
|
+
return crypto
|
|
23
|
+
.createHash('sha256')
|
|
24
|
+
.update(JSON.stringify({
|
|
25
|
+
prompt,
|
|
26
|
+
durationSec: durationSec ?? DEFAULT_DURATION_SEC,
|
|
27
|
+
guidanceScale: guidanceScale ?? DEFAULT_GUIDANCE_SCALE,
|
|
28
|
+
temperature: temperature ?? DEFAULT_TEMPERATURE,
|
|
29
|
+
}))
|
|
30
|
+
.digest('hex');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Returns the cache file path for a given demo + options.
|
|
34
|
+
*/
|
|
35
|
+
export function getCachePath(argoDir, options) {
|
|
36
|
+
const hash = computeCacheKey(options);
|
|
37
|
+
return path.join(argoDir, 'music', `${hash}.wav`);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Check if a cached music file exists for the given options.
|
|
41
|
+
*/
|
|
42
|
+
export function isCached(argoDir, options) {
|
|
43
|
+
return fs.existsSync(getCachePath(argoDir, options));
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Generate background music from a text prompt using MusicGen.
|
|
47
|
+
*
|
|
48
|
+
* The model (~1.8GB) is downloaded on first run. Generation takes ~30-60s
|
|
49
|
+
* depending on duration and hardware.
|
|
50
|
+
*
|
|
51
|
+
* Returns a WAV buffer (Float32, mono, 32kHz — ffmpeg handles resampling during export).
|
|
52
|
+
*/
|
|
53
|
+
export async function generateMusic(options) {
|
|
54
|
+
const durationSec = options.durationSec ?? DEFAULT_DURATION_SEC;
|
|
55
|
+
const guidanceScale = options.guidanceScale ?? DEFAULT_GUIDANCE_SCALE;
|
|
56
|
+
const temperature = options.temperature ?? DEFAULT_TEMPERATURE;
|
|
57
|
+
const maxNewTokens = Math.ceil(durationSec * TOKENS_PER_SECOND);
|
|
58
|
+
// Lazy-load transformers (same pattern as TTS Transformers engine)
|
|
59
|
+
let AutoTokenizer;
|
|
60
|
+
let MusicgenForConditionalGeneration;
|
|
61
|
+
try {
|
|
62
|
+
({ AutoTokenizer, MusicgenForConditionalGeneration } = await import('@huggingface/transformers'));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
throw new Error("MusicGen requires the '@huggingface/transformers' package. " +
|
|
66
|
+
'Install it with: npm i @huggingface/transformers');
|
|
67
|
+
}
|
|
68
|
+
console.log(` \u25b8 Loading model: ${MODEL_ID}`);
|
|
69
|
+
console.log(' \u25b8 First run downloads ~1.8GB — subsequent runs use the cache');
|
|
70
|
+
const tokenizer = await AutoTokenizer.from_pretrained(MODEL_ID);
|
|
71
|
+
const model = await MusicgenForConditionalGeneration.from_pretrained(MODEL_ID, {
|
|
72
|
+
dtype: {
|
|
73
|
+
text_encoder: 'q8',
|
|
74
|
+
decoder_model_merged: 'q8',
|
|
75
|
+
encodec_decode: 'fp32',
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
console.log(` \u25b8 Generating ${durationSec}s of music...`);
|
|
79
|
+
const inputs = tokenizer(options.prompt);
|
|
80
|
+
const audioValues = await model.generate({
|
|
81
|
+
...inputs,
|
|
82
|
+
max_new_tokens: maxNewTokens,
|
|
83
|
+
do_sample: true,
|
|
84
|
+
guidance_scale: guidanceScale,
|
|
85
|
+
temperature,
|
|
86
|
+
});
|
|
87
|
+
// audioValues is a Tensor — extract the Float32Array data
|
|
88
|
+
const rawSamples = audioValues.data instanceof Float32Array
|
|
89
|
+
? audioValues.data
|
|
90
|
+
: new Float32Array(audioValues.data);
|
|
91
|
+
// Save as WAV at the native 32kHz rate — ffmpeg resamples during export
|
|
92
|
+
return createWavBuffer(rawSamples, MUSICGEN_SAMPLE_RATE);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Generate music with caching. Returns the path to the WAV file.
|
|
96
|
+
*
|
|
97
|
+
* If a cached file exists for the same prompt + parameters, skips generation.
|
|
98
|
+
* On model load or generation failure, warns and returns null (best-effort).
|
|
99
|
+
*/
|
|
100
|
+
export async function generateMusicCached(argoDir, options) {
|
|
101
|
+
const cachePath = getCachePath(argoDir, options);
|
|
102
|
+
const hash = computeCacheKey(options);
|
|
103
|
+
if (fs.existsSync(cachePath)) {
|
|
104
|
+
console.log(` \u25b8 Cached: ${path.relative('.', cachePath)}`);
|
|
105
|
+
return cachePath;
|
|
106
|
+
}
|
|
107
|
+
// Ensure cache directory exists
|
|
108
|
+
fs.mkdirSync(path.dirname(cachePath), { recursive: true });
|
|
109
|
+
try {
|
|
110
|
+
const wavBuffer = await generateMusic(options);
|
|
111
|
+
fs.writeFileSync(cachePath, wavBuffer);
|
|
112
|
+
console.log(` \u25b8 Saved: ${path.relative('.', cachePath)}`);
|
|
113
|
+
return cachePath;
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
console.warn(`\u26a0\ufe0f Music generation failed (continuing without background music): ${err.message}`);
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=musicgen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"musicgen.js","sourceRoot":"","sources":["../../src/music/musicgen.ts"],"names":[],"mappings":"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"}
|
package/dist/narration.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"narration.d.ts","sourceRoot":"","sources":["../src/narration.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"narration.d.ts","sourceRoot":"","sources":["../src/narration.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,OAAO,CAAkC;IACjD,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,cAAc,CAA8B;IACpD,OAAO,CAAC,gBAAgB,CAA4B;IACpD,OAAO,CAAC,YAAY,CAAoB;gBAE5B,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAMnD,KAAK,IAAI,IAAI;IAMb,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAsBzB;;;OAGG;IACH,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,GAAG;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAQhF,cAAc,IAAI,UAAU,EAAE;IAI9B,UAAU,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAI9B,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAW9C,OAAO,CAAC,eAAe;IAcvB,OAAO,CAAC,aAAa;IAerB;;;;;;;;OAQG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,MAAM;CAmBnE"}
|
package/dist/narration.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { appendFileSync } from 'node:fs';
|
|
1
2
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
3
|
import { dirname } from 'node:path';
|
|
3
4
|
import { schedulePlacements } from './tts/align.js';
|
|
@@ -24,8 +25,19 @@ export class NarrationTimeline {
|
|
|
24
25
|
if (this.timings.has(scene)) {
|
|
25
26
|
throw new Error(`Duplicate scene name: "${scene}"`);
|
|
26
27
|
}
|
|
27
|
-
|
|
28
|
+
const ms = Date.now() - this.startTime;
|
|
29
|
+
this.timings.set(scene, ms);
|
|
28
30
|
this.cachedPlacements = null;
|
|
31
|
+
// Append to JSONL progress file for live scene status in the parent process
|
|
32
|
+
const progressPath = process.env.ARGO_PROGRESS_PATH;
|
|
33
|
+
if (progressPath) {
|
|
34
|
+
try {
|
|
35
|
+
appendFileSync(progressPath, JSON.stringify({ scene, ms }) + '\n');
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// Best-effort — don't fail recording over progress reporting
|
|
39
|
+
}
|
|
40
|
+
}
|
|
29
41
|
}
|
|
30
42
|
/**
|
|
31
43
|
* Record a camera move (zoom/pan) to be applied as an ffmpeg post-export effect.
|
package/dist/narration.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"narration.js","sourceRoot":"","sources":["../src/narration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,kBAAkB,EAAkB,MAAM,gBAAgB,CAAC;AAYpE,MAAM,OAAO,iBAAiB;IACpB,OAAO,GAAwB,IAAI,GAAG,EAAE,CAAC;IACzC,SAAS,GAAkB,IAAI,CAAC;IAChC,cAAc,GAA2B,EAAE,CAAC;IAC5C,gBAAgB,GAAuB,IAAI,CAAC;IAC5C,YAAY,GAAiB,EAAE,CAAC;IAExC,YAAY,cAAuC;QACjD,IAAI,cAAc,EAAE,CAAC;YACnB,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;QACvC,CAAC;IACH,CAAC;IAED,KAAK;QACH,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO,GAAG,IAAI,GAAG,EAAE,CAAC;QACzB,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAC/B,CAAC;IAED,IAAI,CAAC,KAAa;QAChB,IAAI,IAAI,CAAC,SAAS,KAAK,IAAI,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;QAChE,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,0BAA0B,KAAK,GAAG,CAAC,CAAC;QACtD,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,
|
|
1
|
+
{"version":3,"file":"narration.js","sourceRoot":"","sources":["../src/narration.ts"],"names":[],"mappings":"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"}
|
package/dist/pipeline.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAY9C,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CASxD;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,UAAU,CAAC,EACxG,YAAY,CAAC,EAAE,eAAe,GAC7B,OAAO,CAAC,MAAM,EAAE,CAAC,CAyBnB;AAED,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,UAAU,CAAC,EACxG,YAAY,CAAC,EAAE,eAAe,GAC7B,OAAO,CAAC,MAAM,CAAC,CAiZjB"}
|
package/dist/pipeline.js
CHANGED
|
@@ -10,7 +10,10 @@ import { generateChapterMetadata } from './chapters.js';
|
|
|
10
10
|
import { buildSceneReport, formatSceneReport } from './report.js';
|
|
11
11
|
import { applySpeedRampToTimeline } from './speed-ramp.js';
|
|
12
12
|
import { shiftCameraMoves, scaleCameraMoves } from './camera-move.js';
|
|
13
|
+
import { resolveFreezes, adjustPlacementsForFreezes, totalFreezeDurationMs, } from './freeze.js';
|
|
13
14
|
import { getVideoDurationMs } from './media.js';
|
|
15
|
+
// Note: MusicGen (AI music generation) is a preview-only feature — runs in browser via WebGPU.
|
|
16
|
+
// Pipeline uses saved WAV files via audio.music config path.
|
|
14
17
|
import { buildPlacementsFromTimingAndDurations, buildSceneTexts, computeHeadTrimMs, readScenesManifest, shiftPlacements, } from './timeline.js';
|
|
15
18
|
/**
|
|
16
19
|
* Discover all demo names in the demos directory by looking for `.scenes.json` files.
|
|
@@ -64,7 +67,7 @@ export async function runPipeline(demoName, config, pipelineOpts) {
|
|
|
64
67
|
const argoDir = join('.argo', demoName);
|
|
65
68
|
mkdirSync(argoDir, { recursive: true });
|
|
66
69
|
// Step 1: Generate TTS clips
|
|
67
|
-
console.log('
|
|
70
|
+
console.log('🎙️ Brewing voiceover clips...');
|
|
68
71
|
const clipResults = await generateClips({
|
|
69
72
|
manifestPath: `${config.demosDir}/${demoName}.scenes.json`,
|
|
70
73
|
demoName,
|
|
@@ -80,8 +83,11 @@ export async function runPipeline(demoName, config, pipelineOpts) {
|
|
|
80
83
|
}
|
|
81
84
|
const sceneDurationsPath = join(argoDir, '.scene-durations.json');
|
|
82
85
|
writeFileSync(sceneDurationsPath, JSON.stringify(sceneDurations, null, 2), 'utf-8');
|
|
86
|
+
// Note: AI music generation (MusicGen) is a preview-only feature.
|
|
87
|
+
// Users generate + audition clips in the browser (WebGPU), then save
|
|
88
|
+
// the selected WAV. Pipeline uses the saved file via audio.music.
|
|
83
89
|
// Step 2: Record browser demo
|
|
84
|
-
console.log('
|
|
90
|
+
console.log('🎬 Rolling camera...');
|
|
85
91
|
const { timingPath } = await record(demoName, {
|
|
86
92
|
demosDir: config.demosDir,
|
|
87
93
|
baseURL: config.baseURL,
|
|
@@ -125,7 +131,7 @@ export async function runPipeline(demoName, config, pipelineOpts) {
|
|
|
125
131
|
// Auto-trim: skip setup before first scene mark (with 200ms lead-in)
|
|
126
132
|
const headTrimMs = computeHeadTrimMs(timing);
|
|
127
133
|
if (!isSilent) {
|
|
128
|
-
console.log('
|
|
134
|
+
console.log('🎧 Mixing the soundtrack...');
|
|
129
135
|
// Load WAV clips into memory
|
|
130
136
|
const clips = clipResults.map((cr) => {
|
|
131
137
|
const wavBuf = readFileSync(cr.clipPath);
|
|
@@ -156,12 +162,38 @@ export async function runPipeline(demoName, config, pipelineOpts) {
|
|
|
156
162
|
shiftedPlacements = shiftPlacements(buildPlacementsFromTimingAndDurations(timing, sceneDurations, totalDurationMs), headTrimMs);
|
|
157
163
|
}
|
|
158
164
|
const speedRampPlan = applySpeedRampToTimeline(shiftedPlacements, shiftedDurationMs, config.export.speedRamp);
|
|
159
|
-
|
|
160
|
-
const
|
|
165
|
+
// Read freeze-frame holds from scenes manifest `post` arrays
|
|
166
|
+
const manifestPath = `${config.demosDir}/${demoName}.scenes.json`;
|
|
167
|
+
const freezeSpecs = [];
|
|
168
|
+
try {
|
|
169
|
+
const rawManifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
170
|
+
for (const entry of rawManifest) {
|
|
171
|
+
if (!entry.scene || !Array.isArray(entry.post))
|
|
172
|
+
continue;
|
|
173
|
+
for (const effect of entry.post) {
|
|
174
|
+
if (effect.type === 'freeze' &&
|
|
175
|
+
typeof effect.atMs === 'number' &&
|
|
176
|
+
typeof effect.durationMs === 'number') {
|
|
177
|
+
freezeSpecs.push({
|
|
178
|
+
scene: entry.scene,
|
|
179
|
+
atMs: effect.atMs,
|
|
180
|
+
durationMs: effect.durationMs,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
// Manifest read errors are handled elsewhere — freezes are best-effort
|
|
188
|
+
}
|
|
189
|
+
// Resolve freeze specs to absolute timeline positions and adjust placements
|
|
190
|
+
const resolvedFreezes = resolveFreezes(freezeSpecs, speedRampPlan.placements);
|
|
191
|
+
const finalPlacements = adjustPlacementsForFreezes(speedRampPlan.placements, resolvedFreezes);
|
|
192
|
+
const freezeAddedMs = totalFreezeDurationMs(resolvedFreezes);
|
|
193
|
+
const finalDurationMs = speedRampPlan.totalDurationMs + freezeAddedMs;
|
|
161
194
|
// Ensure output directory exists before writing subtitles
|
|
162
195
|
mkdirSync(config.outputDir, { recursive: true });
|
|
163
196
|
// Build scene text map for subtitles
|
|
164
|
-
const manifestPath = `${config.demosDir}/${demoName}.scenes.json`;
|
|
165
197
|
try {
|
|
166
198
|
const sceneTexts = buildSceneTexts(readScenesManifest(manifestPath));
|
|
167
199
|
// Generate subtitles on the final export timeline.
|
|
@@ -178,7 +210,7 @@ export async function runPipeline(demoName, config, pipelineOpts) {
|
|
|
178
210
|
const chapterMetadata = generateChapterMetadata(finalPlacements, finalDurationMs);
|
|
179
211
|
writeFileSync(chapterMetadataPath, chapterMetadata, 'utf-8');
|
|
180
212
|
// Step 4: Export final video
|
|
181
|
-
console.log('
|
|
213
|
+
console.log('🎞️ Cutting the final take...');
|
|
182
214
|
const exportOptions = {
|
|
183
215
|
demoName,
|
|
184
216
|
argoDir: '.argo',
|
|
@@ -197,7 +229,13 @@ export async function runPipeline(demoName, config, pipelineOpts) {
|
|
|
197
229
|
totalDurationMs: finalDurationMs,
|
|
198
230
|
speedRampSegments: speedRampPlan.segments,
|
|
199
231
|
loudnorm: config.export.audio?.loudnorm,
|
|
232
|
+
musicPath: config.export.audio?.music,
|
|
233
|
+
musicVolume: config.export.audio?.musicVolume,
|
|
234
|
+
watermark: config.export.watermark,
|
|
200
235
|
};
|
|
236
|
+
if (resolvedFreezes.length > 0) {
|
|
237
|
+
exportOptions.freezeSpecs = resolvedFreezes;
|
|
238
|
+
}
|
|
201
239
|
if (tailPadMs !== undefined)
|
|
202
240
|
exportOptions.tailPadMs = tailPadMs;
|
|
203
241
|
if (headTrimMs > 0)
|
|
@@ -247,13 +285,13 @@ export async function runPipeline(demoName, config, pipelineOpts) {
|
|
|
247
285
|
output: outputPath,
|
|
248
286
|
};
|
|
249
287
|
writeFileSync(join(config.outputDir, `${demoName}.meta.json`), JSON.stringify(pipelineMeta, null, 2) + '\n', 'utf-8');
|
|
250
|
-
console.log(`\n
|
|
288
|
+
console.log(`\n🚀 That's a wrap! Video saved to: ${outputPath}`);
|
|
251
289
|
// Viewport-native variants — re-record at different viewports
|
|
252
290
|
const variants = config.export.variants;
|
|
253
291
|
if (variants && variants.length > 0) {
|
|
254
292
|
for (const variant of variants) {
|
|
255
293
|
console.log(`\n${'─'.repeat(50)}`);
|
|
256
|
-
console.log(` Variant: ${variant.name} (${variant.video.width}×${variant.video.height})`);
|
|
294
|
+
console.log(` 📐 Variant: ${variant.name} (${variant.video.width}×${variant.video.height})`);
|
|
257
295
|
console.log(`${'─'.repeat(50)}\n`);
|
|
258
296
|
const variantArgoDir = join('.argo', `${demoName}-${variant.name}`);
|
|
259
297
|
mkdirSync(variantArgoDir, { recursive: true });
|
|
@@ -261,7 +299,7 @@ export async function runPipeline(demoName, config, pipelineOpts) {
|
|
|
261
299
|
writeFileSync(join(variantArgoDir, '.scene-durations.json'), JSON.stringify(sceneDurations, null, 2), 'utf-8');
|
|
262
300
|
// Record at variant viewport
|
|
263
301
|
const variantSubdir = `${demoName}-${variant.name}`;
|
|
264
|
-
console.log('
|
|
302
|
+
console.log('🎬 Rolling camera...');
|
|
265
303
|
const variantRecord = await record(demoName, {
|
|
266
304
|
demosDir: config.demosDir,
|
|
267
305
|
baseURL: config.baseURL,
|
|
@@ -284,7 +322,7 @@ export async function runPipeline(demoName, config, pipelineOpts) {
|
|
|
284
322
|
let variantPlacements = [];
|
|
285
323
|
let variantShiftedDurationMs = variantDurationMs;
|
|
286
324
|
if (!isSilent) {
|
|
287
|
-
console.log('
|
|
325
|
+
console.log('🎧 Mixing the soundtrack...');
|
|
288
326
|
const clips = clipResults.map((cr) => {
|
|
289
327
|
const wavBuf = readFileSync(cr.clipPath);
|
|
290
328
|
const header = parseWavHeader(wavBuf);
|
|
@@ -305,8 +343,14 @@ export async function runPipeline(demoName, config, pipelineOpts) {
|
|
|
305
343
|
}));
|
|
306
344
|
variantShiftedDurationMs = variantDurationMs - variantHeadTrimMs;
|
|
307
345
|
}
|
|
346
|
+
// Resolve freeze-frame holds for variant
|
|
347
|
+
const variantResolvedFreezes = resolveFreezes(freezeSpecs, variantPlacements);
|
|
348
|
+
if (variantResolvedFreezes.length > 0) {
|
|
349
|
+
variantPlacements = adjustPlacementsForFreezes(variantPlacements, variantResolvedFreezes);
|
|
350
|
+
variantShiftedDurationMs += totalFreezeDurationMs(variantResolvedFreezes);
|
|
351
|
+
}
|
|
308
352
|
// Export variant
|
|
309
|
-
console.log('
|
|
353
|
+
console.log('🎞️ Cutting the final take...');
|
|
310
354
|
const variantChapterPath = join('.argo', variantSubdir, 'chapters.txt');
|
|
311
355
|
writeFileSync(variantChapterPath, generateChapterMetadata(variantPlacements, variantShiftedDurationMs), 'utf-8');
|
|
312
356
|
// Subtitles — read from manifest file directly for text field
|
|
@@ -353,9 +397,13 @@ export async function runPipeline(demoName, config, pipelineOpts) {
|
|
|
353
397
|
totalDurationMs: variantShiftedDurationMs,
|
|
354
398
|
headTrimMs: variantHeadTrimMs > 0 ? variantHeadTrimMs : undefined,
|
|
355
399
|
loudnorm: config.export.audio?.loudnorm,
|
|
400
|
+
musicPath: config.export.audio?.music,
|
|
401
|
+
musicVolume: config.export.audio?.musicVolume,
|
|
356
402
|
cameraMoves: variantCameraMoves.length > 0 ? variantCameraMoves : undefined,
|
|
403
|
+
watermark: config.export.watermark,
|
|
404
|
+
freezeSpecs: variantResolvedFreezes.length > 0 ? variantResolvedFreezes : undefined,
|
|
357
405
|
});
|
|
358
|
-
console.log(
|
|
406
|
+
console.log(`🚀 Variant saved to: ${variantOutputPath}`);
|
|
359
407
|
}
|
|
360
408
|
}
|
|
361
409
|
return outputPath;
|