venice-video-harness 2.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/CHANGELOG.md +28 -0
- package/LICENSE +21 -0
- package/README.md +598 -0
- package/dist/assembly/manifest-builder.d.ts +8 -0
- package/dist/assembly/manifest-builder.d.ts.map +1 -0
- package/dist/assembly/manifest-builder.js +196 -0
- package/dist/assembly/manifest-builder.js.map +1 -0
- package/dist/assembly/remotion-scaffold.d.ts +14 -0
- package/dist/assembly/remotion-scaffold.d.ts.map +1 -0
- package/dist/assembly/remotion-scaffold.js +292 -0
- package/dist/assembly/remotion-scaffold.js.map +1 -0
- package/dist/assembly/types.d.ts +67 -0
- package/dist/assembly/types.d.ts.map +1 -0
- package/dist/assembly/types.js +5 -0
- package/dist/assembly/types.js.map +1 -0
- package/dist/characters/describer.d.ts +32 -0
- package/dist/characters/describer.d.ts.map +1 -0
- package/dist/characters/describer.js +309 -0
- package/dist/characters/describer.js.map +1 -0
- package/dist/characters/extractor.d.ts +27 -0
- package/dist/characters/extractor.d.ts.map +1 -0
- package/dist/characters/extractor.js +274 -0
- package/dist/characters/extractor.js.map +1 -0
- package/dist/characters/reference-manager.d.ts +95 -0
- package/dist/characters/reference-manager.d.ts.map +1 -0
- package/dist/characters/reference-manager.js +346 -0
- package/dist/characters/reference-manager.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +310 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +22 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +66 -0
- package/dist/config.js.map +1 -0
- package/dist/editing/aligner.d.ts +44 -0
- package/dist/editing/aligner.d.ts.map +1 -0
- package/dist/editing/aligner.js +201 -0
- package/dist/editing/aligner.js.map +1 -0
- package/dist/editing/edl.d.ts +45 -0
- package/dist/editing/edl.d.ts.map +1 -0
- package/dist/editing/edl.js +133 -0
- package/dist/editing/edl.js.map +1 -0
- package/dist/editing/overlays.d.ts +97 -0
- package/dist/editing/overlays.d.ts.map +1 -0
- package/dist/editing/overlays.js +41 -0
- package/dist/editing/overlays.js.map +1 -0
- package/dist/editing/packer.d.ts +52 -0
- package/dist/editing/packer.d.ts.map +1 -0
- package/dist/editing/packer.js +170 -0
- package/dist/editing/packer.js.map +1 -0
- package/dist/editing/providers/whisper-cpp.d.ts +32 -0
- package/dist/editing/providers/whisper-cpp.d.ts.map +1 -0
- package/dist/editing/providers/whisper-cpp.js +239 -0
- package/dist/editing/providers/whisper-cpp.js.map +1 -0
- package/dist/editing/render.d.ts +36 -0
- package/dist/editing/render.d.ts.map +1 -0
- package/dist/editing/render.js +227 -0
- package/dist/editing/render.js.map +1 -0
- package/dist/editing/self-eval.d.ts +48 -0
- package/dist/editing/self-eval.d.ts.map +1 -0
- package/dist/editing/self-eval.js +293 -0
- package/dist/editing/self-eval.js.map +1 -0
- package/dist/editing/silence.d.ts +76 -0
- package/dist/editing/silence.d.ts.map +1 -0
- package/dist/editing/silence.js +189 -0
- package/dist/editing/silence.js.map +1 -0
- package/dist/editing/types.d.ts +178 -0
- package/dist/editing/types.d.ts.map +1 -0
- package/dist/editing/types.js +13 -0
- package/dist/editing/types.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/interactive.d.ts +12 -0
- package/dist/interactive.d.ts.map +1 -0
- package/dist/interactive.js +78 -0
- package/dist/interactive.js.map +1 -0
- package/dist/mini-drama/assembler.d.ts +61 -0
- package/dist/mini-drama/assembler.d.ts.map +1 -0
- package/dist/mini-drama/assembler.js +466 -0
- package/dist/mini-drama/assembler.js.map +1 -0
- package/dist/mini-drama/audio-mix.d.ts +65 -0
- package/dist/mini-drama/audio-mix.d.ts.map +1 -0
- package/dist/mini-drama/audio-mix.js +118 -0
- package/dist/mini-drama/audio-mix.js.map +1 -0
- package/dist/mini-drama/cli.d.ts +3 -0
- package/dist/mini-drama/cli.d.ts.map +1 -0
- package/dist/mini-drama/cli.js +2869 -0
- package/dist/mini-drama/cli.js.map +1 -0
- package/dist/mini-drama/fcpxml-export.d.ts +2 -0
- package/dist/mini-drama/fcpxml-export.d.ts.map +1 -0
- package/dist/mini-drama/fcpxml-export.js +5 -0
- package/dist/mini-drama/fcpxml-export.js.map +1 -0
- package/dist/mini-drama/generation-planner.d.ts +24 -0
- package/dist/mini-drama/generation-planner.d.ts.map +1 -0
- package/dist/mini-drama/generation-planner.js +304 -0
- package/dist/mini-drama/generation-planner.js.map +1 -0
- package/dist/mini-drama/location-generator.d.ts +23 -0
- package/dist/mini-drama/location-generator.d.ts.map +1 -0
- package/dist/mini-drama/location-generator.js +144 -0
- package/dist/mini-drama/location-generator.js.map +1 -0
- package/dist/mini-drama/music-cues.d.ts +96 -0
- package/dist/mini-drama/music-cues.d.ts.map +1 -0
- package/dist/mini-drama/music-cues.js +332 -0
- package/dist/mini-drama/music-cues.js.map +1 -0
- package/dist/mini-drama/panel-fixer.d.ts +12 -0
- package/dist/mini-drama/panel-fixer.d.ts.map +1 -0
- package/dist/mini-drama/panel-fixer.js +319 -0
- package/dist/mini-drama/panel-fixer.js.map +1 -0
- package/dist/mini-drama/prompt-builder.d.ts +149 -0
- package/dist/mini-drama/prompt-builder.d.ts.map +1 -0
- package/dist/mini-drama/prompt-builder.js +722 -0
- package/dist/mini-drama/prompt-builder.js.map +1 -0
- package/dist/mini-drama/reference-slots.d.ts +31 -0
- package/dist/mini-drama/reference-slots.d.ts.map +1 -0
- package/dist/mini-drama/reference-slots.js +161 -0
- package/dist/mini-drama/reference-slots.js.map +1 -0
- package/dist/mini-drama/shot-paths.d.ts +77 -0
- package/dist/mini-drama/shot-paths.d.ts.map +1 -0
- package/dist/mini-drama/shot-paths.js +101 -0
- package/dist/mini-drama/shot-paths.js.map +1 -0
- package/dist/mini-drama/storyboard-reference-generator.d.ts +47 -0
- package/dist/mini-drama/storyboard-reference-generator.d.ts.map +1 -0
- package/dist/mini-drama/storyboard-reference-generator.js +293 -0
- package/dist/mini-drama/storyboard-reference-generator.js.map +1 -0
- package/dist/mini-drama/subtitle-generator.d.ts +12 -0
- package/dist/mini-drama/subtitle-generator.d.ts.map +1 -0
- package/dist/mini-drama/subtitle-generator.js +139 -0
- package/dist/mini-drama/subtitle-generator.js.map +1 -0
- package/dist/mini-drama/timeline-export/davinci-fcpxml.d.ts +3 -0
- package/dist/mini-drama/timeline-export/davinci-fcpxml.d.ts.map +1 -0
- package/dist/mini-drama/timeline-export/davinci-fcpxml.js +150 -0
- package/dist/mini-drama/timeline-export/davinci-fcpxml.js.map +1 -0
- package/dist/mini-drama/timeline-export/fcpxml.d.ts +11 -0
- package/dist/mini-drama/timeline-export/fcpxml.d.ts.map +1 -0
- package/dist/mini-drama/timeline-export/fcpxml.js +140 -0
- package/dist/mini-drama/timeline-export/fcpxml.js.map +1 -0
- package/dist/mini-drama/timeline-export/index.d.ts +13 -0
- package/dist/mini-drama/timeline-export/index.d.ts.map +1 -0
- package/dist/mini-drama/timeline-export/index.js +26 -0
- package/dist/mini-drama/timeline-export/index.js.map +1 -0
- package/dist/mini-drama/timeline-export/premiere-xmeml.d.ts +3 -0
- package/dist/mini-drama/timeline-export/premiere-xmeml.d.ts.map +1 -0
- package/dist/mini-drama/timeline-export/premiere-xmeml.js +211 -0
- package/dist/mini-drama/timeline-export/premiere-xmeml.js.map +1 -0
- package/dist/mini-drama/timeline-export/probe.d.ts +53 -0
- package/dist/mini-drama/timeline-export/probe.d.ts.map +1 -0
- package/dist/mini-drama/timeline-export/probe.js +89 -0
- package/dist/mini-drama/timeline-export/probe.js.map +1 -0
- package/dist/mini-drama/timeline-export/types.d.ts +57 -0
- package/dist/mini-drama/timeline-export/types.d.ts.map +1 -0
- package/dist/mini-drama/timeline-export/types.js +14 -0
- package/dist/mini-drama/timeline-export/types.js.map +1 -0
- package/dist/mini-drama/video-generator.d.ts +22 -0
- package/dist/mini-drama/video-generator.d.ts.map +1 -0
- package/dist/mini-drama/video-generator.js +1305 -0
- package/dist/mini-drama/video-generator.js.map +1 -0
- package/dist/mini-drama/voice-reference.d.ts +42 -0
- package/dist/mini-drama/voice-reference.d.ts.map +1 -0
- package/dist/mini-drama/voice-reference.js +170 -0
- package/dist/mini-drama/voice-reference.js.map +1 -0
- package/dist/output/html-renderer.d.ts +2 -0
- package/dist/output/html-renderer.d.ts.map +1 -0
- package/dist/output/html-renderer.js +37 -0
- package/dist/output/html-renderer.js.map +1 -0
- package/dist/parsers/fountain-parser.d.ts +34 -0
- package/dist/parsers/fountain-parser.d.ts.map +1 -0
- package/dist/parsers/fountain-parser.js +71 -0
- package/dist/parsers/fountain-parser.js.map +1 -0
- package/dist/parsers/pdf-parser.d.ts +24 -0
- package/dist/parsers/pdf-parser.d.ts.map +1 -0
- package/dist/parsers/pdf-parser.js +237 -0
- package/dist/parsers/pdf-parser.js.map +1 -0
- package/dist/parsers/scene-extractor.d.ts +41 -0
- package/dist/parsers/scene-extractor.d.ts.map +1 -0
- package/dist/parsers/scene-extractor.js +400 -0
- package/dist/parsers/scene-extractor.js.map +1 -0
- package/dist/series/manager.d.ts +64 -0
- package/dist/series/manager.d.ts.map +1 -0
- package/dist/series/manager.js +282 -0
- package/dist/series/manager.js.map +1 -0
- package/dist/series/types.d.ts +641 -0
- package/dist/series/types.d.ts.map +1 -0
- package/dist/series/types.js +304 -0
- package/dist/series/types.js.map +1 -0
- package/dist/storyboard/assembler.d.ts +134 -0
- package/dist/storyboard/assembler.d.ts.map +1 -0
- package/dist/storyboard/assembler.js +319 -0
- package/dist/storyboard/assembler.js.map +1 -0
- package/dist/storyboard/prompt-builder.d.ts +113 -0
- package/dist/storyboard/prompt-builder.d.ts.map +1 -0
- package/dist/storyboard/prompt-builder.js +578 -0
- package/dist/storyboard/prompt-builder.js.map +1 -0
- package/dist/storyboard/shot-planner.d.ts +34 -0
- package/dist/storyboard/shot-planner.d.ts.map +1 -0
- package/dist/storyboard/shot-planner.js +446 -0
- package/dist/storyboard/shot-planner.js.map +1 -0
- package/dist/user-config.d.ts +15 -0
- package/dist/user-config.d.ts.map +1 -0
- package/dist/user-config.js +103 -0
- package/dist/user-config.js.map +1 -0
- package/dist/venice/audio-preflight.d.ts +80 -0
- package/dist/venice/audio-preflight.d.ts.map +1 -0
- package/dist/venice/audio-preflight.js +142 -0
- package/dist/venice/audio-preflight.js.map +1 -0
- package/dist/venice/audio.d.ts +88 -0
- package/dist/venice/audio.d.ts.map +1 -0
- package/dist/venice/audio.js +234 -0
- package/dist/venice/audio.js.map +1 -0
- package/dist/venice/client.d.ts +86 -0
- package/dist/venice/client.d.ts.map +1 -0
- package/dist/venice/client.js +375 -0
- package/dist/venice/client.js.map +1 -0
- package/dist/venice/edit.d.ts +57 -0
- package/dist/venice/edit.d.ts.map +1 -0
- package/dist/venice/edit.js +100 -0
- package/dist/venice/edit.js.map +1 -0
- package/dist/venice/generate.d.ts +47 -0
- package/dist/venice/generate.d.ts.map +1 -0
- package/dist/venice/generate.js +150 -0
- package/dist/venice/generate.js.map +1 -0
- package/dist/venice/image-bytes.d.ts +26 -0
- package/dist/venice/image-bytes.d.ts.map +1 -0
- package/dist/venice/image-bytes.js +101 -0
- package/dist/venice/image-bytes.js.map +1 -0
- package/dist/venice/models.d.ts +143 -0
- package/dist/venice/models.d.ts.map +1 -0
- package/dist/venice/models.js +984 -0
- package/dist/venice/models.js.map +1 -0
- package/dist/venice/multi-edit.d.ts +40 -0
- package/dist/venice/multi-edit.d.ts.map +1 -0
- package/dist/venice/multi-edit.js +97 -0
- package/dist/venice/multi-edit.js.map +1 -0
- package/dist/venice/provenance.d.ts +66 -0
- package/dist/venice/provenance.d.ts.map +1 -0
- package/dist/venice/provenance.js +151 -0
- package/dist/venice/provenance.js.map +1 -0
- package/dist/venice/recipe.d.ts +69 -0
- package/dist/venice/recipe.d.ts.map +1 -0
- package/dist/venice/recipe.js +103 -0
- package/dist/venice/recipe.js.map +1 -0
- package/dist/venice/rejection.d.ts +63 -0
- package/dist/venice/rejection.d.ts.map +1 -0
- package/dist/venice/rejection.js +111 -0
- package/dist/venice/rejection.js.map +1 -0
- package/dist/venice/seedance-preflight.d.ts +38 -0
- package/dist/venice/seedance-preflight.d.ts.map +1 -0
- package/dist/venice/seedance-preflight.js +29 -0
- package/dist/venice/seedance-preflight.js.map +1 -0
- package/dist/venice/types.d.ts +212 -0
- package/dist/venice/types.d.ts.map +1 -0
- package/dist/venice/types.js +9 -0
- package/dist/venice/types.js.map +1 -0
- package/dist/venice/video.d.ts +77 -0
- package/dist/venice/video.d.ts.map +1 -0
- package/dist/venice/video.js +163 -0
- package/dist/venice/video.js.map +1 -0
- package/dist/venice/voices.d.ts +40 -0
- package/dist/venice/voices.d.ts.map +1 -0
- package/dist/venice/voices.js +194 -0
- package/dist/venice/voices.js.map +1 -0
- package/package.json +79 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { ShotScript, MusicCueSpec } from '../series/types.js';
|
|
2
|
+
import { type PlacementMap } from './music-cues.js';
|
|
3
|
+
import type { AudioMixDefaults } from '../series/types.js';
|
|
4
|
+
export interface ShotTrim {
|
|
5
|
+
shotNumber: number;
|
|
6
|
+
trimStart?: number;
|
|
7
|
+
trimEnd?: number;
|
|
8
|
+
flip?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface AssemblyOptions {
|
|
11
|
+
videoFiles: string[];
|
|
12
|
+
outputPath: string;
|
|
13
|
+
srtPath?: string;
|
|
14
|
+
musicPath?: string;
|
|
15
|
+
musicVolume?: number;
|
|
16
|
+
/**
|
|
17
|
+
* ordered list of per-act music cues. When supplied (and non-empty),
|
|
18
|
+
* the assembler renders them with crossfades and uses the resulting track
|
|
19
|
+
* as the music bed in place of `musicPath`. The legacy single-bed path is
|
|
20
|
+
* preserved for back-compat.
|
|
21
|
+
*/
|
|
22
|
+
musicCues?: MusicCueSpec[];
|
|
23
|
+
/**
|
|
24
|
+
* Optional shot-id placement map (string keys, zero-padded for numeric
|
|
25
|
+
* ids per ). When omitted, the assembler derives it from the
|
|
26
|
+
* normalized clip durations using each clip's filename as the shot id.
|
|
27
|
+
* Caller can pass an explicit map when the clip order doesn't match the
|
|
28
|
+
* shot ids (e.g. multi-shot bundles).
|
|
29
|
+
*/
|
|
30
|
+
placementMap?: PlacementMap;
|
|
31
|
+
/**
|
|
32
|
+
* shot list used to derive musicHold automation. When omitted,
|
|
33
|
+
* automation is skipped.
|
|
34
|
+
*/
|
|
35
|
+
shots?: ShotScript[];
|
|
36
|
+
/** Continuous ambient bed (e.g. rain loop) mixed under all clips for audio continuity */
|
|
37
|
+
ambientBedPath?: string;
|
|
38
|
+
ambientBedVolume?: number;
|
|
39
|
+
dialogueDir?: string;
|
|
40
|
+
nativeAudioVolume?: number;
|
|
41
|
+
/** Per-shot trim/flip metadata from script.json */
|
|
42
|
+
shotTrims?: ShotTrim[];
|
|
43
|
+
/** Optional title that fades in over the ending of the assembled episode */
|
|
44
|
+
endingTitleOverlay?: {
|
|
45
|
+
text: string;
|
|
46
|
+
fadeInSec?: number;
|
|
47
|
+
holdSec?: number;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* audio-mix defaults. When supplied, a final loudnorm pass runs
|
|
51
|
+
* over the assembled master targeting `lufsTarget` integrated LUFS and
|
|
52
|
+
* `truePeakDb` dBTP. Default targets: -16 LUFS / -1 dBTP.
|
|
53
|
+
*
|
|
54
|
+
* Set to `false` (not undefined) to disable the pass explicitly — used
|
|
55
|
+
* by tests that don't want a re-encode they have to clean up.
|
|
56
|
+
*/
|
|
57
|
+
audioMix?: AudioMixDefaults | false;
|
|
58
|
+
}
|
|
59
|
+
export declare function assembleEpisode(options: AssemblyOptions): Promise<string>;
|
|
60
|
+
export declare function collectShotVideos(sceneDir: string): Promise<string[]>;
|
|
61
|
+
//# sourceMappingURL=assembler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assembler.d.ts","sourceRoot":"","sources":["../../src/mini-drama/assembler.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAKL,KAAK,YAAY,EAClB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAG3D,MAAM,WAAW,QAAQ;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,YAAY,EAAE,CAAC;IAC3B;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B;;;OAGG;IACH,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IACrB,yFAAyF;IACzF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mDAAmD;IACnD,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;IACvB,4EAA4E;IAC5E,kBAAkB,CAAC,EAAE;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IACF;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,gBAAgB,GAAG,KAAK,CAAC;CACrC;AAiJD,wBAAsB,eAAe,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CA8W/E;AAED,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAW3E"}
|
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
import { readdir, mkdir } from 'node:fs/promises';
|
|
2
|
+
import { join, dirname, basename } from 'node:path';
|
|
3
|
+
import { existsSync, writeFileSync, unlinkSync, rmSync, copyFileSync, statSync } from 'node:fs';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import { renderMusicCuesTrack, resolveMusicCues, buildMusicHoldExpr, applyMusicHoldAutomation, } from './music-cues.js';
|
|
6
|
+
import { loudnessNormalize, resolveAudioMix } from './audio-mix.js';
|
|
7
|
+
import { renameSync } from 'node:fs';
|
|
8
|
+
import { dialogueFileForShot, shotKey } from './shot-paths.js';
|
|
9
|
+
function runCommand(command, args) {
|
|
10
|
+
const result = spawnSync(command, args, {
|
|
11
|
+
encoding: 'utf-8',
|
|
12
|
+
stdio: 'pipe',
|
|
13
|
+
});
|
|
14
|
+
if (result.error)
|
|
15
|
+
throw result.error;
|
|
16
|
+
if (result.status !== 0) {
|
|
17
|
+
const stderr = typeof result.stderr === 'string' ? result.stderr.trim() : '';
|
|
18
|
+
const stdout = typeof result.stdout === 'string' ? result.stdout.trim() : '';
|
|
19
|
+
const detail = stderr || stdout || `exit code ${result.status}`;
|
|
20
|
+
throw new Error(`${command} failed: ${detail}`);
|
|
21
|
+
}
|
|
22
|
+
return typeof result.stdout === 'string' ? result.stdout : '';
|
|
23
|
+
}
|
|
24
|
+
function getVideoDuration(path) {
|
|
25
|
+
const out = runCommand('ffprobe', [
|
|
26
|
+
'-v',
|
|
27
|
+
'error',
|
|
28
|
+
'-show_entries',
|
|
29
|
+
'format=duration',
|
|
30
|
+
'-of',
|
|
31
|
+
'csv=p=0',
|
|
32
|
+
path,
|
|
33
|
+
]).trim();
|
|
34
|
+
return parseFloat(out);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* fallback: derive a placement map from the post-normalization clip
|
|
38
|
+
* list when the caller didn't supply an explicit one. Each clip is expected
|
|
39
|
+
* to be named `shot-NNN.mp4` or `shot-NNNb.mp4` per conventions.
|
|
40
|
+
*
|
|
41
|
+
* If the filename can't be parsed as a shot id, the clip is given an
|
|
42
|
+
* auto-incremented numeric key so the assembler still emits *something*
|
|
43
|
+
* rather than skipping a cue silently.
|
|
44
|
+
*/
|
|
45
|
+
function derivePlacementMapFromClips(clipPaths) {
|
|
46
|
+
const map = {};
|
|
47
|
+
let cursor = 0;
|
|
48
|
+
for (let i = 0; i < clipPaths.length; i++) {
|
|
49
|
+
const filename = basename(clipPaths[i], '.mp4');
|
|
50
|
+
const dur = getVideoDuration(clipPaths[i]);
|
|
51
|
+
let key;
|
|
52
|
+
const match = filename.match(/^shot-(\d+)([a-zA-Z]*)/);
|
|
53
|
+
if (match) {
|
|
54
|
+
key = String(match[1]).padStart(3, '0') + match[2];
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
key = String(i + 1).padStart(3, '0');
|
|
58
|
+
}
|
|
59
|
+
map[key] = { startSec: cursor, endSec: cursor + dur };
|
|
60
|
+
cursor += dur;
|
|
61
|
+
}
|
|
62
|
+
return map;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Normalize a single clip to consistent encoding params.
|
|
66
|
+
* Kling outputs can have subtle codec/container differences that cause
|
|
67
|
+
* concat duration errors when using `-c copy`. Re-encoding to identical
|
|
68
|
+
* params (h264/aac, 24fps, 44100Hz stereo) eliminates this.
|
|
69
|
+
*
|
|
70
|
+
* Also applies trimStart, trimEnd, and flip if specified.
|
|
71
|
+
*/
|
|
72
|
+
function normalizeClip(inputPath, outputPath, trim) {
|
|
73
|
+
const filters = [];
|
|
74
|
+
const ffmpegArgs = ['-y'];
|
|
75
|
+
if (trim?.trimStart && trim.trimStart > 0) {
|
|
76
|
+
ffmpegArgs.push('-ss', String(trim.trimStart));
|
|
77
|
+
}
|
|
78
|
+
ffmpegArgs.push('-i', inputPath);
|
|
79
|
+
if (trim?.trimEnd && trim.trimEnd > 0) {
|
|
80
|
+
const duration = getVideoDuration(inputPath);
|
|
81
|
+
const endTime = duration - trim.trimEnd - (trim.trimStart ?? 0);
|
|
82
|
+
if (endTime > 0) {
|
|
83
|
+
ffmpegArgs.push('-t', String(endTime));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (trim?.flip) {
|
|
87
|
+
filters.push('hflip');
|
|
88
|
+
}
|
|
89
|
+
if (filters.length > 0) {
|
|
90
|
+
ffmpegArgs.push('-vf', filters.join(','));
|
|
91
|
+
}
|
|
92
|
+
ffmpegArgs.push('-c:v', 'libx264', '-preset', 'fast', '-crf', '18', '-r', '24', '-c:a', 'aac', '-ar', '44100', '-ac', '2', '-b:a', '192k', outputPath);
|
|
93
|
+
runCommand('ffmpeg', ffmpegArgs);
|
|
94
|
+
}
|
|
95
|
+
function replaceDialogueInShot(videoPath, dialoguePath, outputPath, nativeVolume) {
|
|
96
|
+
runCommand('ffmpeg', [
|
|
97
|
+
'-y',
|
|
98
|
+
'-i',
|
|
99
|
+
videoPath,
|
|
100
|
+
'-i',
|
|
101
|
+
dialoguePath,
|
|
102
|
+
'-filter_complex',
|
|
103
|
+
`[0:a]volume=${nativeVolume}[native];[1:a]volume=1.0[tts];[native][tts]amix=inputs=2:duration=first:dropout_transition=0.5[aout]`,
|
|
104
|
+
'-map',
|
|
105
|
+
'0:v',
|
|
106
|
+
'-map',
|
|
107
|
+
'[aout]',
|
|
108
|
+
'-c:v',
|
|
109
|
+
'copy',
|
|
110
|
+
'-c:a',
|
|
111
|
+
'aac',
|
|
112
|
+
'-shortest',
|
|
113
|
+
outputPath,
|
|
114
|
+
]);
|
|
115
|
+
}
|
|
116
|
+
export async function assembleEpisode(options) {
|
|
117
|
+
const { videoFiles, outputPath, srtPath, musicPath, musicVolume = 0.15, ambientBedPath, ambientBedVolume = 0.3, dialogueDir, nativeAudioVolume = 0.2, shotTrims = [], endingTitleOverlay, } = options;
|
|
118
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
119
|
+
const trimMap = new Map(shotTrims.map(t => [t.shotNumber, t]));
|
|
120
|
+
// ── Step 1: Normalize all clips to identical encoding ──
|
|
121
|
+
const normDir = join(dirname(outputPath), '.tmp-norm');
|
|
122
|
+
await mkdir(normDir, { recursive: true });
|
|
123
|
+
console.log(` Normalizing ${videoFiles.length} clips...`);
|
|
124
|
+
const normalizedFiles = [];
|
|
125
|
+
for (const videoPath of videoFiles) {
|
|
126
|
+
const shotName = basename(videoPath, '.mp4');
|
|
127
|
+
const shotNumStr = shotName.replace('shot-', '');
|
|
128
|
+
const shotNum = parseInt(shotNumStr, 10);
|
|
129
|
+
const normPath = join(normDir, `${shotName}.mp4`);
|
|
130
|
+
const trim = trimMap.get(shotNum);
|
|
131
|
+
const trimInfo = [];
|
|
132
|
+
if (trim?.trimStart)
|
|
133
|
+
trimInfo.push(`trim start ${trim.trimStart}s`);
|
|
134
|
+
if (trim?.trimEnd)
|
|
135
|
+
trimInfo.push(`trim end ${trim.trimEnd}s`);
|
|
136
|
+
if (trim?.flip)
|
|
137
|
+
trimInfo.push('flip');
|
|
138
|
+
normalizeClip(videoPath, normPath, trim);
|
|
139
|
+
normalizedFiles.push(normPath);
|
|
140
|
+
const info = trimInfo.length > 0 ? ` (${trimInfo.join(', ')})` : '';
|
|
141
|
+
console.log(` ${shotName}${info}`);
|
|
142
|
+
}
|
|
143
|
+
// ── Step 2: Replace dialogue if TTS files exist ──
|
|
144
|
+
let processedFiles = normalizedFiles;
|
|
145
|
+
if (dialogueDir && existsSync(dialogueDir)) {
|
|
146
|
+
console.log(` Replacing dialogue with Venice TTS (native audio default ${Math.round(nativeAudioVolume * 100)}%; per-shot shot.nativeAudio overrides)...`);
|
|
147
|
+
const tmpDir = join(dirname(outputPath), '.tmp-dialogue-mix');
|
|
148
|
+
await mkdir(tmpDir, { recursive: true });
|
|
149
|
+
// Build a quick lookup so per-shot `nativeAudio` overrides win.
|
|
150
|
+
const shotByKey = new Map();
|
|
151
|
+
if (options.shots) {
|
|
152
|
+
for (const s of options.shots) {
|
|
153
|
+
shotByKey.set(shotKey(s.shotNumber), s);
|
|
154
|
+
if (s.shotIdSuffix) {
|
|
155
|
+
shotByKey.set(shotKey(`${s.shotNumber}${s.shotIdSuffix}`), s);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
processedFiles = [];
|
|
160
|
+
let missing = 0;
|
|
161
|
+
for (const videoPath of normalizedFiles) {
|
|
162
|
+
const shotName = basename(videoPath, '.mp4');
|
|
163
|
+
// use the canonical path builder so suffixed inserts like
|
|
164
|
+
// "3b" / "3c" map to dialogue-shot-003b.mp3 / 003c.mp3. Filenames
|
|
165
|
+
// emitted by the planner are already padded, but go through the
|
|
166
|
+
// builder anyway so the contract is enforced everywhere.
|
|
167
|
+
const shotNum = shotName.replace('shot-', '');
|
|
168
|
+
const dialoguePath = dialogueFileForShot(dialogueDir, shotNum);
|
|
169
|
+
const matchingShot = shotByKey.get(shotKey(shotNum));
|
|
170
|
+
let effectiveNative = nativeAudioVolume;
|
|
171
|
+
let nativeLabel;
|
|
172
|
+
if (matchingShot?.nativeAudio === 'mute') {
|
|
173
|
+
effectiveNative = 0;
|
|
174
|
+
nativeLabel = 'mute';
|
|
175
|
+
}
|
|
176
|
+
else if (matchingShot?.nativeAudio === 'duck') {
|
|
177
|
+
effectiveNative = 0.2;
|
|
178
|
+
nativeLabel = 'duck';
|
|
179
|
+
}
|
|
180
|
+
else if (matchingShot?.nativeAudio === 'keep') {
|
|
181
|
+
effectiveNative = 1.0;
|
|
182
|
+
nativeLabel = 'keep';
|
|
183
|
+
}
|
|
184
|
+
if (existsSync(dialoguePath)) {
|
|
185
|
+
const processedPath = join(tmpDir, `${shotName}-voiced.mp4`);
|
|
186
|
+
replaceDialogueInShot(videoPath, dialoguePath, processedPath, effectiveNative);
|
|
187
|
+
processedFiles.push(processedPath);
|
|
188
|
+
const label = nativeLabel ? ` native=${nativeLabel}` : '';
|
|
189
|
+
console.log(` ${shotName}: dialogue replaced (${shotKey(shotNum)})${label}`);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
processedFiles.push(videoPath);
|
|
193
|
+
// Warn loudly on fall-through. Without this, every `existsSync`
|
|
194
|
+
// returning false silently lets the master ship with the
|
|
195
|
+
// corresponding narration missing — a class of silent-failure bug
|
|
196
|
+
// that's only caught by a human reviewing the rendered audio.
|
|
197
|
+
console.warn(` ${shotName}: no dialogue file at ${dialoguePath} — using native audio`);
|
|
198
|
+
missing += 1;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (missing > 0) {
|
|
202
|
+
console.warn(` ${missing} shot(s) had no dialogue track. Verify intent — the placement loop falls through silently otherwise.`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// ── Step 3: Concatenate ──
|
|
206
|
+
const concatPath = join(normDir, 'concat.txt');
|
|
207
|
+
const lines = processedFiles.map(f => `file '${f}'`).join('\n');
|
|
208
|
+
writeFileSync(concatPath, lines, 'utf-8');
|
|
209
|
+
const concatenatedPath = outputPath.replace(/\.mp4$/, '-raw.mp4');
|
|
210
|
+
runCommand('ffmpeg', [
|
|
211
|
+
'-y',
|
|
212
|
+
'-f',
|
|
213
|
+
'concat',
|
|
214
|
+
'-safe',
|
|
215
|
+
'0',
|
|
216
|
+
'-i',
|
|
217
|
+
concatPath,
|
|
218
|
+
'-c',
|
|
219
|
+
'copy',
|
|
220
|
+
'-movflags',
|
|
221
|
+
'+faststart',
|
|
222
|
+
concatenatedPath,
|
|
223
|
+
]);
|
|
224
|
+
const concatDur = getVideoDuration(concatenatedPath);
|
|
225
|
+
console.log(` Concatenated ${processedFiles.length} clips -> ${concatDur.toFixed(1)}s`);
|
|
226
|
+
let currentInput = concatenatedPath;
|
|
227
|
+
// ── Step 3.5: Build the per-act music bed ( / ) ──
|
|
228
|
+
// When `musicCues` are supplied, render an ordered ffmpeg crossfade track
|
|
229
|
+
// and use it as the music bed in place of `musicPath`. The legacy single-
|
|
230
|
+
// bed path is preserved when no cues are supplied. musicHold automation is
|
|
231
|
+
// layered on top via a `volume=` expression so cue audio doesn't have to
|
|
232
|
+
// be re-rendered when only the automation changes.
|
|
233
|
+
let effectiveMusicPath = musicPath;
|
|
234
|
+
if (options.musicCues && options.musicCues.length > 0) {
|
|
235
|
+
const placementMap = options.placementMap ?? derivePlacementMapFromClips(processedFiles);
|
|
236
|
+
const resolved = resolveMusicCues(options.musicCues, placementMap, cue => cue.audioPath);
|
|
237
|
+
if (resolved.length > 0) {
|
|
238
|
+
const cuesTrackPath = outputPath.replace(/\.mp4$/, '-music-cues.mp3');
|
|
239
|
+
await renderMusicCuesTrack({
|
|
240
|
+
cues: resolved,
|
|
241
|
+
outputPath: cuesTrackPath,
|
|
242
|
+
totalDurationSec: concatDur,
|
|
243
|
+
placementMap,
|
|
244
|
+
});
|
|
245
|
+
let bedPath = cuesTrackPath;
|
|
246
|
+
if (options.shots) {
|
|
247
|
+
const holdExpr = buildMusicHoldExpr(options.shots, placementMap);
|
|
248
|
+
if (holdExpr) {
|
|
249
|
+
const automatedPath = outputPath.replace(/\.mp4$/, '-music-cues-auto.mp3');
|
|
250
|
+
await applyMusicHoldAutomation({
|
|
251
|
+
inputPath: cuesTrackPath,
|
|
252
|
+
outputPath: automatedPath,
|
|
253
|
+
volumeExpr: holdExpr,
|
|
254
|
+
});
|
|
255
|
+
bedPath = automatedPath;
|
|
256
|
+
console.log(` Applied musicHold automation (${holdExpr.length} chars)`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
effectiveMusicPath = bedPath;
|
|
260
|
+
console.log(` Music cues: ${resolved.length} segment(s) crossfaded -> ${basename(bedPath)}`);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
console.warn(' Music cues: no cues resolved against placement map; falling back to musicPath.');
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// ── Step 4: Mix in ambient bed (looped to duration) ──
|
|
267
|
+
if (ambientBedPath && existsSync(ambientBedPath)) {
|
|
268
|
+
const withAmbientPath = outputPath.replace(/\.mp4$/, '-with-ambient.mp4');
|
|
269
|
+
// Loop the ambient clip to cover full video duration, mix under native audio
|
|
270
|
+
runCommand('ffmpeg', [
|
|
271
|
+
'-y',
|
|
272
|
+
'-i',
|
|
273
|
+
currentInput,
|
|
274
|
+
'-stream_loop',
|
|
275
|
+
'-1',
|
|
276
|
+
'-i',
|
|
277
|
+
ambientBedPath,
|
|
278
|
+
'-filter_complex',
|
|
279
|
+
`[1:a]volume=${ambientBedVolume}[ambient];[0:a][ambient]amix=inputs=2:duration=first:dropout_transition=2[aout]`,
|
|
280
|
+
'-map',
|
|
281
|
+
'0:v',
|
|
282
|
+
'-map',
|
|
283
|
+
'[aout]',
|
|
284
|
+
'-c:v',
|
|
285
|
+
'copy',
|
|
286
|
+
'-c:a',
|
|
287
|
+
'aac',
|
|
288
|
+
withAmbientPath,
|
|
289
|
+
]);
|
|
290
|
+
console.log(` Mixed in ambient bed at ${Math.round(ambientBedVolume * 100)}% volume (looped)`);
|
|
291
|
+
currentInput = withAmbientPath;
|
|
292
|
+
}
|
|
293
|
+
// ── Step 5: Mix in background music ──
|
|
294
|
+
// Per , when music cues were used, their per-cue gain (-22 dB by
|
|
295
|
+
// default) is already baked into `effectiveMusicPath`. Skip the extra
|
|
296
|
+
// `volume=musicVolume` multiplier in that case to avoid double-attenuating.
|
|
297
|
+
if (effectiveMusicPath && existsSync(effectiveMusicPath)) {
|
|
298
|
+
const withMusicPath = outputPath.replace(/\.mp4$/, '-with-music.mp4');
|
|
299
|
+
const cuesBaked = effectiveMusicPath !== musicPath;
|
|
300
|
+
const musicFilter = cuesBaked
|
|
301
|
+
? `[0:a][1:a]amix=inputs=2:duration=first:dropout_transition=2[aout]`
|
|
302
|
+
: `[1:a]volume=${musicVolume}[music];[0:a][music]amix=inputs=2:duration=first:dropout_transition=2[aout]`;
|
|
303
|
+
runCommand('ffmpeg', [
|
|
304
|
+
'-y',
|
|
305
|
+
'-i',
|
|
306
|
+
currentInput,
|
|
307
|
+
'-i',
|
|
308
|
+
effectiveMusicPath,
|
|
309
|
+
'-filter_complex',
|
|
310
|
+
musicFilter,
|
|
311
|
+
'-map',
|
|
312
|
+
'0:v',
|
|
313
|
+
'-map',
|
|
314
|
+
'[aout]',
|
|
315
|
+
'-c:v',
|
|
316
|
+
'copy',
|
|
317
|
+
'-c:a',
|
|
318
|
+
'aac',
|
|
319
|
+
withMusicPath,
|
|
320
|
+
]);
|
|
321
|
+
const label = cuesBaked ? 'cues-baked' : `${Math.round(musicVolume * 100)}% volume`;
|
|
322
|
+
console.log(` Mixed in background music (${label})`);
|
|
323
|
+
currentInput = withMusicPath;
|
|
324
|
+
}
|
|
325
|
+
// ── Step 5.5: Fade title over the ending of the last shot ──
|
|
326
|
+
if (endingTitleOverlay?.text?.trim()) {
|
|
327
|
+
const withTitlePath = outputPath.replace(/\.mp4$/, '-with-title.mp4');
|
|
328
|
+
const totalDuration = getVideoDuration(currentInput);
|
|
329
|
+
const fadeInSec = endingTitleOverlay.fadeInSec ?? 1.2;
|
|
330
|
+
const holdSec = endingTitleOverlay.holdSec ?? 1.8;
|
|
331
|
+
const overlayStart = Math.max(0, totalDuration - holdSec);
|
|
332
|
+
const escapedText = endingTitleOverlay.text.replace(/'/g, "\\'").replace(/:/g, '\\:');
|
|
333
|
+
const fontPath = '/System/Library/Fonts/Supplemental/Arial Bold.ttf';
|
|
334
|
+
const titleFilter = [
|
|
335
|
+
`drawtext=fontfile='${fontPath}':text='${escapedText}':fontcolor=0xD66BFF@0.22:fontsize=104:x=(w-text_w)/2:y=(h-text_h)/2:alpha='if(lt(t,${overlayStart}),0,if(lt(t,${overlayStart + fadeInSec}),(t-${overlayStart})/${fadeInSec},1))'`,
|
|
336
|
+
`drawtext=fontfile='${fontPath}':text='${escapedText}':fontcolor=0x7EF9FF@0.35:fontsize=98:x=(w-text_w)/2:y=(h-text_h)/2:alpha='if(lt(t,${overlayStart}),0,if(lt(t,${overlayStart + fadeInSec}),(t-${overlayStart})/${fadeInSec},1))'`,
|
|
337
|
+
`drawtext=fontfile='${fontPath}':text='${escapedText}':fontcolor=white:fontsize=92:x=(w-text_w)/2:y=(h-text_h)/2:alpha='if(lt(t,${overlayStart}),0,if(lt(t,${overlayStart + fadeInSec}),(t-${overlayStart})/${fadeInSec},1))'`,
|
|
338
|
+
].join(',');
|
|
339
|
+
runCommand('ffmpeg', [
|
|
340
|
+
'-y',
|
|
341
|
+
'-i',
|
|
342
|
+
currentInput,
|
|
343
|
+
'-vf',
|
|
344
|
+
titleFilter,
|
|
345
|
+
'-c:v',
|
|
346
|
+
'libx264',
|
|
347
|
+
'-preset',
|
|
348
|
+
'fast',
|
|
349
|
+
'-crf',
|
|
350
|
+
'18',
|
|
351
|
+
'-c:a',
|
|
352
|
+
'copy',
|
|
353
|
+
withTitlePath,
|
|
354
|
+
]);
|
|
355
|
+
console.log(` Faded in ending title "${endingTitleOverlay.text}" over final ${holdSec.toFixed(1)}s`);
|
|
356
|
+
currentInput = withTitlePath;
|
|
357
|
+
}
|
|
358
|
+
// ── Step 6: Burn subtitles ──
|
|
359
|
+
if (srtPath && existsSync(srtPath)) {
|
|
360
|
+
// Save no-subtitles version as backup
|
|
361
|
+
const noSubsPath = outputPath.replace(/\.mp4$/, '-nosubs.mp4');
|
|
362
|
+
copyFileSync(currentInput, noSubsPath);
|
|
363
|
+
const escapedSrt = srtPath.replace(/'/g, "'\\''").replace(/:/g, '\\:');
|
|
364
|
+
const subtitleFilter = [
|
|
365
|
+
`subtitles='${escapedSrt}'`,
|
|
366
|
+
`:force_style='`,
|
|
367
|
+
`FontName=D-DIN Condensed,`,
|
|
368
|
+
`FontSize=11,`,
|
|
369
|
+
`PrimaryColour=&H00FFFFFF,`,
|
|
370
|
+
`OutlineColour=&H00000000,`,
|
|
371
|
+
`BorderStyle=1,`,
|
|
372
|
+
`Outline=0.8,`,
|
|
373
|
+
`Shadow=0,`,
|
|
374
|
+
`Alignment=2,`,
|
|
375
|
+
`MarginV=100,`,
|
|
376
|
+
`Spacing=0.5`,
|
|
377
|
+
`'`,
|
|
378
|
+
].join('');
|
|
379
|
+
runCommand('ffmpeg', [
|
|
380
|
+
'-y',
|
|
381
|
+
'-i',
|
|
382
|
+
currentInput,
|
|
383
|
+
'-vf',
|
|
384
|
+
subtitleFilter,
|
|
385
|
+
'-c:v',
|
|
386
|
+
'libx264',
|
|
387
|
+
'-preset',
|
|
388
|
+
'fast',
|
|
389
|
+
'-crf',
|
|
390
|
+
'18',
|
|
391
|
+
'-c:a',
|
|
392
|
+
'copy',
|
|
393
|
+
outputPath,
|
|
394
|
+
]);
|
|
395
|
+
console.log(` Burned subtitles (backup at ${basename(noSubsPath)})`);
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
if (currentInput !== outputPath) {
|
|
399
|
+
copyFileSync(currentInput, outputPath);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
// ── Cleanup ──
|
|
403
|
+
try {
|
|
404
|
+
if (existsSync(normDir))
|
|
405
|
+
rmSync(normDir, { recursive: true, force: true });
|
|
406
|
+
if (existsSync(concatenatedPath))
|
|
407
|
+
unlinkSync(concatenatedPath);
|
|
408
|
+
const withAmbientPath = outputPath.replace(/\.mp4$/, '-with-ambient.mp4');
|
|
409
|
+
if (existsSync(withAmbientPath))
|
|
410
|
+
unlinkSync(withAmbientPath);
|
|
411
|
+
const withMusicPath = outputPath.replace(/\.mp4$/, '-with-music.mp4');
|
|
412
|
+
if (existsSync(withMusicPath))
|
|
413
|
+
unlinkSync(withMusicPath);
|
|
414
|
+
const withTitlePath = outputPath.replace(/\.mp4$/, '-with-title.mp4');
|
|
415
|
+
if (existsSync(withTitlePath))
|
|
416
|
+
unlinkSync(withTitlePath);
|
|
417
|
+
const tmpDialogue = join(dirname(outputPath), '.tmp-dialogue-mix');
|
|
418
|
+
if (existsSync(tmpDialogue))
|
|
419
|
+
rmSync(tmpDialogue, { recursive: true, force: true });
|
|
420
|
+
}
|
|
421
|
+
catch { /* best-effort */ }
|
|
422
|
+
// ── Step 7: final loudness pass ──
|
|
423
|
+
// Targets -16 LUFS / -1 dBTP by default. Skipped when audioMix === false.
|
|
424
|
+
if (options.audioMix !== false) {
|
|
425
|
+
const mix = resolveAudioMix(options.audioMix);
|
|
426
|
+
const preLoudPath = outputPath.replace(/\.mp4$/, '-pre-loudnorm.mp4');
|
|
427
|
+
try {
|
|
428
|
+
renameSync(outputPath, preLoudPath);
|
|
429
|
+
await loudnessNormalize({
|
|
430
|
+
inputPath: preLoudPath,
|
|
431
|
+
outputPath,
|
|
432
|
+
audioMix: options.audioMix,
|
|
433
|
+
videoInput: true,
|
|
434
|
+
});
|
|
435
|
+
unlinkSync(preLoudPath);
|
|
436
|
+
console.log(` Loudness normalized to ${mix.lufsTarget} LUFS / TP ${mix.truePeakDb} dBTP`);
|
|
437
|
+
}
|
|
438
|
+
catch (err) {
|
|
439
|
+
// If loudnorm fails (e.g. ffmpeg lacks the filter on this system),
|
|
440
|
+
// restore the pre-loudnorm master rather than leaving a half-rename
|
|
441
|
+
// mess. Surface the error so the user knows to install a newer ffmpeg.
|
|
442
|
+
if (existsSync(preLoudPath) && !existsSync(outputPath)) {
|
|
443
|
+
renameSync(preLoudPath, outputPath);
|
|
444
|
+
}
|
|
445
|
+
console.warn(` ⚠ Loudness pass failed: ${err.message}. Delivered un-normalized master.`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
const finalDur = getVideoDuration(outputPath);
|
|
449
|
+
const finalBytes = statSync(outputPath).size;
|
|
450
|
+
const finalSizeMb = (finalBytes / (1024 * 1024)).toFixed(1);
|
|
451
|
+
console.log(` Final episode: ${outputPath} (${finalDur.toFixed(1)}s, ${finalSizeMb} MB)`);
|
|
452
|
+
return outputPath;
|
|
453
|
+
}
|
|
454
|
+
export async function collectShotVideos(sceneDir) {
|
|
455
|
+
if (!existsSync(sceneDir))
|
|
456
|
+
return [];
|
|
457
|
+
const entries = await readdir(sceneDir);
|
|
458
|
+
// Suffixed inserts (shot-003b.mp4) must be included and sort correctly:
|
|
459
|
+
// "shot-003" < "shot-003b" < "shot-004" lexicographically, so a plain
|
|
460
|
+
// sort keeps episode order.
|
|
461
|
+
return entries
|
|
462
|
+
.filter(f => /^shot-\d{3}[a-z]?\.mp4$/.test(f))
|
|
463
|
+
.sort()
|
|
464
|
+
.map(f => join(sceneDir, f));
|
|
465
|
+
}
|
|
466
|
+
//# sourceMappingURL=assembler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assembler.js","sourceRoot":"","sources":["../../src/mini-drama/assembler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { AudioMixDefaults } from '../series/types.js';
|
|
2
|
+
export declare const DEFAULT_SFX_MAX_DURATION_SEC = 2;
|
|
3
|
+
export declare const DEFAULT_SFX_FADE_OUT_SEC = 0.3;
|
|
4
|
+
export declare const DEFAULT_DIALOGUE_GAIN_DB = 0;
|
|
5
|
+
export declare const DEFAULT_MUSIC_GAIN_DB = -22;
|
|
6
|
+
export declare const DEFAULT_SFX_GAIN_DB = -16;
|
|
7
|
+
export declare const DEFAULT_LUFS_TARGET = -16;
|
|
8
|
+
export declare const DEFAULT_TRUE_PEAK_DB = -1;
|
|
9
|
+
/** Resolve a partial audio-mix config to a fully populated one. */
|
|
10
|
+
export declare function resolveAudioMix(defaults?: AudioMixDefaults): Required<AudioMixDefaults>;
|
|
11
|
+
export interface SfxTrimOptions {
|
|
12
|
+
/** Override the global max duration for this specific clip (e.g. ambient SFX). */
|
|
13
|
+
maxDurationSec?: number;
|
|
14
|
+
/** Override the fade-out duration for this clip. */
|
|
15
|
+
fadeOutSec?: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Trim a single SFX clip and apply a fade-out.
|
|
19
|
+
*
|
|
20
|
+
* Raw SFX clips from elevenlabs-sound-effects-v2 are 3-6 seconds long.
|
|
21
|
+
* Without trimming, fire crackle bleeds across cuts and panic shouts overlap
|
|
22
|
+
* dialogue. The fade-out is short (0.3s) — long enough to avoid a click,
|
|
23
|
+
* short enough to keep the SFX feeling punchy.
|
|
24
|
+
*/
|
|
25
|
+
export declare function trimAndFadeSfx(opts: {
|
|
26
|
+
inputPath: string;
|
|
27
|
+
outputPath: string;
|
|
28
|
+
options?: SfxTrimOptions;
|
|
29
|
+
globalDefaults?: AudioMixDefaults;
|
|
30
|
+
}): Promise<string>;
|
|
31
|
+
/**
|
|
32
|
+
* Apply trim+fade to every SFX clip in a directory.
|
|
33
|
+
*
|
|
34
|
+
* - The output directory mirrors the input layout.
|
|
35
|
+
* - Per-clip overrides can be supplied via the `perFile` map keyed on the
|
|
36
|
+
* clip basename (e.g. `"fire-crackle.mp3"`) — useful for ambient SFX that
|
|
37
|
+
* need a longer max duration than the global default.
|
|
38
|
+
* - Returns the list of output paths in input order.
|
|
39
|
+
*/
|
|
40
|
+
export declare function trimAndFadeSfxBatch(opts: {
|
|
41
|
+
inputPaths: string[];
|
|
42
|
+
outputDir: string;
|
|
43
|
+
perFile?: Record<string, SfxTrimOptions>;
|
|
44
|
+
globalDefaults?: AudioMixDefaults;
|
|
45
|
+
}): Promise<string[]>;
|
|
46
|
+
/**
|
|
47
|
+
* Final loudness normalization to a target integrated LUFS and true-peak
|
|
48
|
+
* ceiling. Uses ffmpeg's `loudnorm` filter (EBU R128 implementation).
|
|
49
|
+
*
|
|
50
|
+
* Two-pass loudnorm is more accurate but the single-pass form is fine for
|
|
51
|
+
* a final delivery encode — the input is already at a reasonable level
|
|
52
|
+
* after upstream gain decisions, and `loudnorm` will only nudge the
|
|
53
|
+
* envelope, not crush it.
|
|
54
|
+
*/
|
|
55
|
+
export declare function loudnessNormalize(opts: {
|
|
56
|
+
inputPath: string;
|
|
57
|
+
outputPath: string;
|
|
58
|
+
audioMix?: AudioMixDefaults;
|
|
59
|
+
/**
|
|
60
|
+
* When the input is a video file, set this to copy the video stream
|
|
61
|
+
* instead of re-encoding it. Defaults to false (input treated as audio).
|
|
62
|
+
*/
|
|
63
|
+
videoInput?: boolean;
|
|
64
|
+
}): Promise<string>;
|
|
65
|
+
//# sourceMappingURL=audio-mix.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio-mix.d.ts","sourceRoot":"","sources":["../../src/mini-drama/audio-mix.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAI3D,eAAO,MAAM,4BAA4B,IAAM,CAAC;AAChD,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAC5C,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAC1C,eAAO,MAAM,qBAAqB,MAAM,CAAC;AACzC,eAAO,MAAM,mBAAmB,MAAM,CAAC;AACvC,eAAO,MAAM,mBAAmB,MAAM,CAAC;AACvC,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC,mEAAmE;AACnE,wBAAgB,eAAe,CAAC,QAAQ,CAAC,EAAE,gBAAgB,GAAG,QAAQ,CAAC,gBAAgB,CAAC,CAWvF;AAED,MAAM,WAAW,cAAc;IAC7B,kFAAkF;IAClF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oDAAoD;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,cAAc,CAAC,EAAE,gBAAgB,CAAC;CACnC,GAAG,OAAO,CAAC,MAAM,CAAC,CAgBlB;AAED;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE;IAC9C,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACzC,cAAc,CAAC,EAAE,gBAAgB,CAAC;CACnC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAmBpB;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,GAAG,OAAO,CAAC,MAAM,CAAC,CAalB"}
|