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,293 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Storyboard reference (blocking plate) generation
|
|
3
|
+
//
|
|
4
|
+
// A StoryboardReference is a COMPOSED image of a scene beat: multiple
|
|
5
|
+
// characters positioned in a location in relation to each other (and to key
|
|
6
|
+
// props). It is consumed as a reference_image_urls entry on @Image-tag R2V
|
|
7
|
+
// models (Seedance 2.0 family, HappyHorse 1.1 R2V) — NEVER as a start frame —
|
|
8
|
+
// with a role clause restricting it to composition/blocking authority.
|
|
9
|
+
//
|
|
10
|
+
// Composition strategy mirrors panel generation: generateWithReferences with
|
|
11
|
+
// each character's front.png as a face ref plus the location wide.png as an
|
|
12
|
+
// environment anchor, so the plate itself stays consistent with the canonical
|
|
13
|
+
// assets it will sit alongside in the reference array.
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
import { join, basename } from 'node:path';
|
|
16
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
17
|
+
import { mkdir, writeFile, rename } from 'node:fs/promises';
|
|
18
|
+
import { getCharacterDir, getLocationDir, getLocation, getStoryboardDir, getStoryboardRefPath, } from '../series/manager.js';
|
|
19
|
+
import { generateWithReferences, generateImage } from '../venice/generate.js';
|
|
20
|
+
import { writeImageBytesSmart } from '../venice/image-bytes.js';
|
|
21
|
+
import { appendRecipePass } from '../venice/recipe.js';
|
|
22
|
+
import { DEFAULT_IMAGE_GENERATION_MODEL } from '../series/types.js';
|
|
23
|
+
function buildAestheticString(aesthetic) {
|
|
24
|
+
return [
|
|
25
|
+
aesthetic.style,
|
|
26
|
+
aesthetic.palette,
|
|
27
|
+
aesthetic.lighting,
|
|
28
|
+
aesthetic.lensCharacteristics,
|
|
29
|
+
aesthetic.filmStock ? `shot on ${aesthetic.filmStock}` : '',
|
|
30
|
+
]
|
|
31
|
+
.filter(Boolean)
|
|
32
|
+
.join(', ');
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Generate (or reuse) the composed blocking plate for a storyboard reference.
|
|
36
|
+
* Writes `storyboards/<slug>.png` plus a `.prompt.json` sidecar, provenance
|
|
37
|
+
* (hasFace matches whether characters were composed), and a recipe pass.
|
|
38
|
+
* Returns the on-disk path.
|
|
39
|
+
*/
|
|
40
|
+
export async function generateStoryboardReference(client, series, ref, options = {}) {
|
|
41
|
+
if (!series.aesthetic) {
|
|
42
|
+
throw new Error('Series aesthetic must be set before generating storyboard references.');
|
|
43
|
+
}
|
|
44
|
+
const dir = getStoryboardDir(series);
|
|
45
|
+
await mkdir(dir, { recursive: true });
|
|
46
|
+
const imgPath = getStoryboardRefPath(series, ref.slug);
|
|
47
|
+
if (existsSync(imgPath) && !options.force) {
|
|
48
|
+
return { path: imgPath, skipped: true };
|
|
49
|
+
}
|
|
50
|
+
if (existsSync(imgPath) && options.force) {
|
|
51
|
+
const archive = imgPath.replace(/\.png$/, `-force-archive-${Date.now()}.png`);
|
|
52
|
+
await rename(imgPath, archive);
|
|
53
|
+
}
|
|
54
|
+
const model = options.model
|
|
55
|
+
?? ref.referenceModel
|
|
56
|
+
?? series.videoDefaults.imageDefaults?.generationModel
|
|
57
|
+
?? DEFAULT_IMAGE_GENERATION_MODEL;
|
|
58
|
+
const cfgScale = options.cfgScale ?? 10;
|
|
59
|
+
const aspect = series.storyboardAspectRatio ?? '16:9';
|
|
60
|
+
const aestheticStr = buildAestheticString(series.aesthetic);
|
|
61
|
+
// Gather character face refs (front.png per character).
|
|
62
|
+
const refPaths = [];
|
|
63
|
+
const charRefs = [];
|
|
64
|
+
for (const name of ref.characters) {
|
|
65
|
+
const char = series.characters.find(c => c.name.toUpperCase() === name.toUpperCase());
|
|
66
|
+
if (!char) {
|
|
67
|
+
console.warn(` Storyboard ref "${ref.slug}": character "${name}" not found in series — skipping their face ref.`);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const frontPath = join(getCharacterDir(series, char.name), 'front.png');
|
|
71
|
+
if (!existsSync(frontPath)) {
|
|
72
|
+
console.warn(` Storyboard ref "${ref.slug}": no front.png for ${char.name} — generate character references first.`);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
refPaths.push(frontPath);
|
|
76
|
+
charRefs.push({
|
|
77
|
+
name: char.name,
|
|
78
|
+
role: char.description.slice(0, 80),
|
|
79
|
+
base64Image: readFileSync(frontPath).toString('base64'),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// Location environment ref (wide angle), appended after face refs.
|
|
83
|
+
let locationSuffix = '';
|
|
84
|
+
if (ref.location) {
|
|
85
|
+
const loc = getLocation(series, ref.location);
|
|
86
|
+
if (loc) {
|
|
87
|
+
const locDir = getLocationDir(series, loc.slug);
|
|
88
|
+
const wide = ['wide.png', 'medium.png', 'detail.png']
|
|
89
|
+
.map(f => join(locDir, f))
|
|
90
|
+
.find(p => existsSync(p));
|
|
91
|
+
if (wide) {
|
|
92
|
+
refPaths.push(wide);
|
|
93
|
+
charRefs.push({
|
|
94
|
+
name: 'LOCATION',
|
|
95
|
+
role: 'environment reference — setting, architecture, lighting',
|
|
96
|
+
base64Image: readFileSync(wide).toString('base64'),
|
|
97
|
+
});
|
|
98
|
+
locationSuffix = ' The final reference image is the location environment — match its setting, architecture, and lighting; it is not a character.';
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Blocking plates prioritize spatial clarity over cinematic drama: a clean
|
|
103
|
+
// medium-wide view where every character, their positions, and the key
|
|
104
|
+
// props are unambiguous. The video model reads geometry from this image.
|
|
105
|
+
const promptParts = [
|
|
106
|
+
`STYLE: ${aestheticStr}.`,
|
|
107
|
+
'Single cinematic frame, one continuous image, NOT a comic panel layout, NO panel borders, NO speech bubbles, NO text overlays.',
|
|
108
|
+
'Medium-wide blocking shot: every character fully visible, positions and spatial relationships unambiguous, clear staging.',
|
|
109
|
+
`${ref.description}.`,
|
|
110
|
+
`STYLE REMINDER: ${aestheticStr}.`,
|
|
111
|
+
];
|
|
112
|
+
const prompt = promptParts.join(' ') + locationSuffix;
|
|
113
|
+
const negativePrompt = [
|
|
114
|
+
'comic panels', 'multiple panels', 'panel layout', 'panel borders', 'panel grid',
|
|
115
|
+
'speech bubbles', 'text bubbles', 'split screen', 'multiple frames',
|
|
116
|
+
'deformed', 'blurry', 'bad anatomy', 'watermark', 'text', 'signature', 'low quality',
|
|
117
|
+
].join(', ');
|
|
118
|
+
let imgBuffer;
|
|
119
|
+
if (charRefs.length > 0) {
|
|
120
|
+
const result = await generateWithReferences(client, {
|
|
121
|
+
model,
|
|
122
|
+
prompt,
|
|
123
|
+
negative_prompt: negativePrompt,
|
|
124
|
+
resolution: '1K',
|
|
125
|
+
aspect_ratio: aspect,
|
|
126
|
+
steps: 30,
|
|
127
|
+
cfg_scale: cfgScale,
|
|
128
|
+
seed: ref.seed,
|
|
129
|
+
safe_mode: false,
|
|
130
|
+
hide_watermark: true,
|
|
131
|
+
referenceImages: charRefs,
|
|
132
|
+
faceSlots: Math.min(ref.characters.length, 5),
|
|
133
|
+
});
|
|
134
|
+
imgBuffer = Buffer.from(result.base64, 'base64');
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
const response = await generateImage(client, {
|
|
138
|
+
model,
|
|
139
|
+
prompt,
|
|
140
|
+
negative_prompt: negativePrompt,
|
|
141
|
+
resolution: '1K',
|
|
142
|
+
aspect_ratio: aspect,
|
|
143
|
+
steps: 30,
|
|
144
|
+
cfg_scale: cfgScale,
|
|
145
|
+
seed: ref.seed,
|
|
146
|
+
safe_mode: false,
|
|
147
|
+
hide_watermark: true,
|
|
148
|
+
});
|
|
149
|
+
if (!response.images?.[0]) {
|
|
150
|
+
throw new Error(`Storyboard reference "${ref.slug}": no image returned.`);
|
|
151
|
+
}
|
|
152
|
+
imgBuffer = Buffer.from(response.images[0].b64_json, 'base64');
|
|
153
|
+
}
|
|
154
|
+
const finalPath = await writeImageBytesSmart(imgBuffer, imgPath);
|
|
155
|
+
console.log(` Storyboard ref: saved -> ${basename(finalPath)}`);
|
|
156
|
+
await writeFile(imgPath.replace(/\.png$/, '.prompt.json'), JSON.stringify({
|
|
157
|
+
slug: ref.slug,
|
|
158
|
+
episode: ref.episode,
|
|
159
|
+
description: ref.description,
|
|
160
|
+
characters: ref.characters,
|
|
161
|
+
location: ref.location ?? null,
|
|
162
|
+
shotIds: ref.shotIds,
|
|
163
|
+
model,
|
|
164
|
+
prompt,
|
|
165
|
+
negative_prompt: negativePrompt,
|
|
166
|
+
cfg_scale: cfgScale,
|
|
167
|
+
aspect_ratio: aspect,
|
|
168
|
+
resolution: '1K',
|
|
169
|
+
seed: ref.seed,
|
|
170
|
+
generatedAt: new Date().toISOString(),
|
|
171
|
+
}, null, 2), 'utf-8');
|
|
172
|
+
await appendRecipePass(finalPath, {
|
|
173
|
+
kind: 'generate',
|
|
174
|
+
role: 'identity',
|
|
175
|
+
model,
|
|
176
|
+
label: `storyboard blocking plate (${ref.slug})`,
|
|
177
|
+
prompt,
|
|
178
|
+
negativePrompt,
|
|
179
|
+
seed: ref.seed,
|
|
180
|
+
cfgScale,
|
|
181
|
+
aspectRatio: aspect,
|
|
182
|
+
resolution: '1K',
|
|
183
|
+
referenceImagePaths: refPaths.length > 0 ? refPaths : undefined,
|
|
184
|
+
}, { provenance: 'generate', hasFace: ref.characters.length > 0 });
|
|
185
|
+
return { path: finalPath, skipped: false };
|
|
186
|
+
}
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
// Beat planning
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
190
|
+
function shotIdOf(shot) {
|
|
191
|
+
return shot.shotIdSuffix ? `${shot.shotNumber}${shot.shotIdSuffix}` : shot.shotNumber;
|
|
192
|
+
}
|
|
193
|
+
function seedFromString(s) {
|
|
194
|
+
return Math.abs([...s].reduce((h, c) => ((h << 5) - h + c.charCodeAt(0)) | 0, 0)) % 999_999_999;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Plan storyboard blocking plates per scene BEAT (user decision 2026-07-30:
|
|
198
|
+
* per-beat granularity, auto-planned, reused across the beat's shots).
|
|
199
|
+
*
|
|
200
|
+
* A beat is a maximal run of consecutive shots that:
|
|
201
|
+
* - involve 2+ characters (single-character shots don't need blocking), and
|
|
202
|
+
* - share the same location, and
|
|
203
|
+
* - keep an overlapping character set (a full cast change starts a new beat).
|
|
204
|
+
*
|
|
205
|
+
* Each beat gets one StoryboardReference whose description is synthesized
|
|
206
|
+
* from the beat's first multi-character shot; shots in the beat get
|
|
207
|
+
* `storyboardRef` set to the plate's slug. Pre-existing per-shot
|
|
208
|
+
* `storyboardRef` values are always respected and never overwritten.
|
|
209
|
+
*/
|
|
210
|
+
export function planStoryboardBeats(script) {
|
|
211
|
+
const refs = [];
|
|
212
|
+
const shots = script.shots;
|
|
213
|
+
let i = 0;
|
|
214
|
+
while (i < shots.length) {
|
|
215
|
+
const shot = shots[i];
|
|
216
|
+
const chars = shot.characters.map(c => c.toUpperCase());
|
|
217
|
+
if (chars.length < 2) {
|
|
218
|
+
i += 1;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
// Extend the beat window.
|
|
222
|
+
let j = i + 1;
|
|
223
|
+
while (j < shots.length) {
|
|
224
|
+
const next = shots[j];
|
|
225
|
+
const nextChars = next.characters.map(c => c.toUpperCase());
|
|
226
|
+
if (nextChars.length < 2)
|
|
227
|
+
break;
|
|
228
|
+
if ((next.location ?? '') !== (shot.location ?? ''))
|
|
229
|
+
break;
|
|
230
|
+
if (!nextChars.some(c => chars.includes(c)))
|
|
231
|
+
break;
|
|
232
|
+
j += 1;
|
|
233
|
+
}
|
|
234
|
+
const beatShots = shots.slice(i, j);
|
|
235
|
+
const allChars = Array.from(new Set(beatShots.flatMap(s => s.characters)));
|
|
236
|
+
const slug = [
|
|
237
|
+
`e${String(script.episode).padStart(2, '0')}`,
|
|
238
|
+
`beat-${String(shotIdOf(beatShots[0]))}`,
|
|
239
|
+
...(shot.location ? [shot.location] : []),
|
|
240
|
+
].join('-').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
241
|
+
refs.push({
|
|
242
|
+
slug,
|
|
243
|
+
description: shot.panelDescription ?? shot.description,
|
|
244
|
+
characters: allChars,
|
|
245
|
+
...(shot.location ? { location: shot.location } : {}),
|
|
246
|
+
episode: script.episode,
|
|
247
|
+
shotIds: beatShots.map(shotIdOf),
|
|
248
|
+
seed: seedFromString(slug),
|
|
249
|
+
});
|
|
250
|
+
for (const s of beatShots) {
|
|
251
|
+
if (!s.storyboardRef)
|
|
252
|
+
s.storyboardRef = slug;
|
|
253
|
+
}
|
|
254
|
+
i = j;
|
|
255
|
+
}
|
|
256
|
+
return refs;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Plan beats for an episode (idempotent — respects existing storyboardRefs
|
|
260
|
+
* and previously planned plates), merge them into script.storyboardRefs, and
|
|
261
|
+
* generate any plates missing on disk. Called from workshop-episode after
|
|
262
|
+
* location generation, and best-effort from generate-videos so hand-edited
|
|
263
|
+
* scripts pick up plates too.
|
|
264
|
+
*/
|
|
265
|
+
export async function ensureEpisodeStoryboardReferences(client, series, script, options = {}) {
|
|
266
|
+
const planned = planStoryboardBeats(script);
|
|
267
|
+
const existing = script.storyboardRefs ?? [];
|
|
268
|
+
const bySlug = new Map(existing.map(r => [r.slug, r]));
|
|
269
|
+
for (const ref of planned) {
|
|
270
|
+
if (!bySlug.has(ref.slug))
|
|
271
|
+
bySlug.set(ref.slug, ref);
|
|
272
|
+
}
|
|
273
|
+
script.storyboardRefs = Array.from(bySlug.values());
|
|
274
|
+
const generated = [];
|
|
275
|
+
const skipped = [];
|
|
276
|
+
if (!series.aesthetic) {
|
|
277
|
+
if (script.storyboardRefs.length > 0) {
|
|
278
|
+
console.warn(' ⚠ Storyboard beats planned but series aesthetic is not set — skipping plate generation.');
|
|
279
|
+
}
|
|
280
|
+
return { generated, skipped };
|
|
281
|
+
}
|
|
282
|
+
for (const ref of script.storyboardRefs) {
|
|
283
|
+
try {
|
|
284
|
+
const result = await generateStoryboardReference(client, series, ref, options);
|
|
285
|
+
(result.skipped ? skipped : generated).push(result.path);
|
|
286
|
+
}
|
|
287
|
+
catch (err) {
|
|
288
|
+
console.warn(` ⚠ Storyboard plate generation failed for "${ref.slug}": ${err.message}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return { generated, skipped };
|
|
292
|
+
}
|
|
293
|
+
//# sourceMappingURL=storyboard-reference-generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storyboard-reference-generator.js","sourceRoot":"","sources":["../../src/mini-drama/storyboard-reference-generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ShotScript } from '../series/types.js';
|
|
2
|
+
export interface SubtitleEntry {
|
|
3
|
+
index: number;
|
|
4
|
+
startTime: string;
|
|
5
|
+
endTime: string;
|
|
6
|
+
text: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function generateSubtitles(shots: ShotScript[], sceneDir?: string): SubtitleEntry[];
|
|
9
|
+
export declare function subtitlesToSrt(entries: SubtitleEntry[]): string;
|
|
10
|
+
export declare function saveSrt(entries: SubtitleEntry[], outputPath: string): Promise<string>;
|
|
11
|
+
export declare function burnSubtitles(inputVideo: string, srtPath: string, outputVideo: string): void;
|
|
12
|
+
//# sourceMappingURL=subtitle-generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subtitle-generator.d.ts","sourceRoot":"","sources":["../../src/mini-drama/subtitle-generator.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAqDD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,aAAa,EAAE,CAmDzF;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,MAAM,CAI/D;AAED,wBAAsB,OAAO,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAK3F;AAED,wBAAgB,aAAa,CAC3B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,GAClB,IAAI,CA6BN"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { writeFile, mkdir } from 'node:fs/promises';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
5
|
+
function runCommand(command, args) {
|
|
6
|
+
const result = spawnSync(command, args, {
|
|
7
|
+
encoding: 'utf-8',
|
|
8
|
+
stdio: 'pipe',
|
|
9
|
+
});
|
|
10
|
+
if (result.error)
|
|
11
|
+
throw result.error;
|
|
12
|
+
if (result.status !== 0) {
|
|
13
|
+
const stderr = typeof result.stderr === 'string' ? result.stderr.trim() : '';
|
|
14
|
+
const stdout = typeof result.stdout === 'string' ? result.stdout.trim() : '';
|
|
15
|
+
const detail = stderr || stdout || `exit code ${result.status}`;
|
|
16
|
+
throw new Error(`${command} failed: ${detail}`);
|
|
17
|
+
}
|
|
18
|
+
return typeof result.stdout === 'string' ? result.stdout : '';
|
|
19
|
+
}
|
|
20
|
+
function formatSrtTime(seconds) {
|
|
21
|
+
const h = Math.floor(seconds / 3600);
|
|
22
|
+
const m = Math.floor((seconds % 3600) / 60);
|
|
23
|
+
const s = Math.floor(seconds % 60);
|
|
24
|
+
const ms = Math.round((seconds % 1) * 1000);
|
|
25
|
+
return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')},${String(ms).padStart(3, '0')}`;
|
|
26
|
+
}
|
|
27
|
+
function parseDuration(dur) {
|
|
28
|
+
const match = dur.match(/^(\d+)s$/);
|
|
29
|
+
return match ? parseInt(match[1]) : 5;
|
|
30
|
+
}
|
|
31
|
+
function getActualShotDuration(sceneDir, shotNumber, fallbackDuration) {
|
|
32
|
+
if (!sceneDir)
|
|
33
|
+
return parseDuration(fallbackDuration);
|
|
34
|
+
const videoPath = join(sceneDir, `shot-${String(shotNumber).padStart(3, '0')}.mp4`);
|
|
35
|
+
if (!existsSync(videoPath))
|
|
36
|
+
return parseDuration(fallbackDuration);
|
|
37
|
+
try {
|
|
38
|
+
const out = runCommand('ffprobe', [
|
|
39
|
+
'-v',
|
|
40
|
+
'error',
|
|
41
|
+
'-show_entries',
|
|
42
|
+
'format=duration',
|
|
43
|
+
'-of',
|
|
44
|
+
'csv=p=0',
|
|
45
|
+
videoPath,
|
|
46
|
+
]).trim();
|
|
47
|
+
const duration = parseFloat(out);
|
|
48
|
+
return Number.isFinite(duration) ? duration : parseDuration(fallbackDuration);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return parseDuration(fallbackDuration);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export function generateSubtitles(shots, sceneDir) {
|
|
55
|
+
const entries = [];
|
|
56
|
+
let currentTime = 0;
|
|
57
|
+
let index = 1;
|
|
58
|
+
for (const shot of shots) {
|
|
59
|
+
const shotDuration = getActualShotDuration(sceneDir, shot.shotNumber, shot.duration);
|
|
60
|
+
if (shot.dialogue) {
|
|
61
|
+
const words = shot.dialogue.line.split(/\s+/);
|
|
62
|
+
const wordsPerSecond = 2.5;
|
|
63
|
+
const speakDuration = Math.min(words.length / wordsPerSecond + 0.5, shotDuration - 0.3);
|
|
64
|
+
// AI dialogue usually starts well after the cut; bias subtitles as late as
|
|
65
|
+
// possible while still letting the full line finish before the shot ends.
|
|
66
|
+
const startOffset = Math.max(0.3, shotDuration - speakDuration - 0.15);
|
|
67
|
+
const start = currentTime + startOffset;
|
|
68
|
+
const end = currentTime + startOffset + speakDuration;
|
|
69
|
+
if (words.length <= 8) {
|
|
70
|
+
entries.push({
|
|
71
|
+
index: index++,
|
|
72
|
+
startTime: formatSrtTime(start),
|
|
73
|
+
endTime: formatSrtTime(end),
|
|
74
|
+
text: shot.dialogue.line,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
const mid = Math.ceil(words.length / 2);
|
|
79
|
+
const firstHalf = words.slice(0, mid).join(' ');
|
|
80
|
+
const secondHalf = words.slice(mid).join(' ');
|
|
81
|
+
const halfDuration = speakDuration / 2;
|
|
82
|
+
entries.push({
|
|
83
|
+
index: index++,
|
|
84
|
+
startTime: formatSrtTime(start),
|
|
85
|
+
endTime: formatSrtTime(start + halfDuration),
|
|
86
|
+
text: firstHalf,
|
|
87
|
+
});
|
|
88
|
+
entries.push({
|
|
89
|
+
index: index++,
|
|
90
|
+
startTime: formatSrtTime(start + halfDuration),
|
|
91
|
+
endTime: formatSrtTime(end),
|
|
92
|
+
text: secondHalf,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
currentTime += shotDuration;
|
|
97
|
+
}
|
|
98
|
+
return entries;
|
|
99
|
+
}
|
|
100
|
+
export function subtitlesToSrt(entries) {
|
|
101
|
+
return entries
|
|
102
|
+
.map(e => `${e.index}\n${e.startTime} --> ${e.endTime}\n${e.text}\n`)
|
|
103
|
+
.join('\n');
|
|
104
|
+
}
|
|
105
|
+
export async function saveSrt(entries, outputPath) {
|
|
106
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
107
|
+
const srtContent = subtitlesToSrt(entries);
|
|
108
|
+
await writeFile(outputPath, srtContent, 'utf-8');
|
|
109
|
+
return outputPath;
|
|
110
|
+
}
|
|
111
|
+
export function burnSubtitles(inputVideo, srtPath, outputVideo) {
|
|
112
|
+
const escapedSrt = srtPath.replace(/'/g, "'\\''").replace(/:/g, '\\:');
|
|
113
|
+
const subtitleFilter = [
|
|
114
|
+
`subtitles='${escapedSrt}'`,
|
|
115
|
+
'force_style=\'',
|
|
116
|
+
'FontName=Arial,',
|
|
117
|
+
'FontSize=22,',
|
|
118
|
+
'Bold=1,',
|
|
119
|
+
'PrimaryColour=&H00FFFFFF,',
|
|
120
|
+
'OutlineColour=&H00000000,',
|
|
121
|
+
'BackColour=&H80000000,',
|
|
122
|
+
'Outline=2,',
|
|
123
|
+
'Shadow=1,',
|
|
124
|
+
'Alignment=2,',
|
|
125
|
+
'MarginV=150',
|
|
126
|
+
'\'',
|
|
127
|
+
].join('');
|
|
128
|
+
runCommand('ffmpeg', [
|
|
129
|
+
'-y',
|
|
130
|
+
'-i',
|
|
131
|
+
inputVideo,
|
|
132
|
+
'-vf',
|
|
133
|
+
subtitleFilter,
|
|
134
|
+
'-c:a',
|
|
135
|
+
'copy',
|
|
136
|
+
outputVideo,
|
|
137
|
+
]);
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=subtitle-generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subtitle-generator.js","sourceRoot":"","sources":["../../src/mini-drama/subtitle-generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"davinci-fcpxml.d.ts","sourceRoot":"","sources":["../../../src/mini-drama/timeline-export/davinci-fcpxml.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAqB,qBAAqB,EAAmB,MAAM,YAAY,CAAC;AAuB5F,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC,CAuHtF"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// DaVinci Resolve-tuned FCPXML 1.10 export.
|
|
3
|
+
//
|
|
4
|
+
// Resolve imports the FCPXML 1.10 emitted by ./fcpxml.ts today, but the
|
|
5
|
+
// import is lossy in three known ways:
|
|
6
|
+
//
|
|
7
|
+
// 1. The <format colorSpace="1-1-1 (Rec. 709)"/> attribute sometimes makes
|
|
8
|
+
// Resolve flag the timeline as "missing LUT" or apply an extra color
|
|
9
|
+
// transform that doesn't match the source. The Resolve-tuned variant
|
|
10
|
+
// drops the colorSpace attribute and lets Resolve infer.
|
|
11
|
+
// 2. Mono dialogue clips imported via the FCP X path get expanded to
|
|
12
|
+
// stereo Resolve tracks. The tuned variant emits the
|
|
13
|
+
// <audio-channel-source> / track-format hint Resolve actually honors,
|
|
14
|
+
// keeping mono dialogue on a mono track.
|
|
15
|
+
// 3. URI-encoded paths (`%20` for spaces, `%27` for apostrophes) sometimes
|
|
16
|
+
// fail on Linux Resolve builds when the source filename has spaces.
|
|
17
|
+
// The tuned variant uses raw absolute paths inside a plain `file://`
|
|
18
|
+
// prefix — works on macOS Resolve and tested-ok on Linux per the issue
|
|
19
|
+
// thread.
|
|
20
|
+
//
|
|
21
|
+
// Apart from those three differences, the output is byte-for-byte the same
|
|
22
|
+
// as the FCP X path. A future PR can fork further if Resolve ships new
|
|
23
|
+
// import gates we discover during testing.
|
|
24
|
+
//
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
27
|
+
import { existsSync } from 'node:fs';
|
|
28
|
+
import { dirname } from 'node:path';
|
|
29
|
+
import { probeAudioInfo, segmentContaining, toRationalTime, xmlEscape } from './probe.js';
|
|
30
|
+
/**
|
|
31
|
+
* DaVinci-tuned variant of `pathToFileUri`. Wraps an absolute path in a
|
|
32
|
+
* `file://` prefix WITHOUT URI-encoding spaces, apostrophes, or other
|
|
33
|
+
* filesystem-legal characters. macOS / Linux Resolve both accept this form;
|
|
34
|
+
* the FCP X path doesn't because FCPXML 1.10 validators sometimes flag it.
|
|
35
|
+
*/
|
|
36
|
+
function pathToResolveFileUri(p) {
|
|
37
|
+
return 'file://' + p;
|
|
38
|
+
}
|
|
39
|
+
export async function exportDavinciFcpxml(opts) {
|
|
40
|
+
const fps = opts.fps ?? 24;
|
|
41
|
+
const width = opts.width ?? 1920;
|
|
42
|
+
const height = opts.height ?? 1080;
|
|
43
|
+
const eventName = opts.eventName ?? 'Episode';
|
|
44
|
+
const projectName = opts.projectName ?? `${eventName} — fine-tune`;
|
|
45
|
+
const assetById = new Map();
|
|
46
|
+
let counter = 1;
|
|
47
|
+
const FORMAT_ID = 'r1';
|
|
48
|
+
async function registerAsset(path, hasVideo, durSec, name) {
|
|
49
|
+
const existing = assetById.get(path);
|
|
50
|
+
if (existing)
|
|
51
|
+
return existing;
|
|
52
|
+
const id = `r${++counter + 1}`;
|
|
53
|
+
let audioRate = 48000;
|
|
54
|
+
let audioChannels = 2;
|
|
55
|
+
try {
|
|
56
|
+
const info = await probeAudioInfo(path);
|
|
57
|
+
audioRate = info.sampleRate;
|
|
58
|
+
audioChannels = info.channels;
|
|
59
|
+
}
|
|
60
|
+
catch { /* best-effort */ }
|
|
61
|
+
const rec = {
|
|
62
|
+
id, name, path,
|
|
63
|
+
hasVideo,
|
|
64
|
+
hasAudio: true,
|
|
65
|
+
durRt: toRationalTime(durSec, fps),
|
|
66
|
+
audioRate,
|
|
67
|
+
audioChannels,
|
|
68
|
+
};
|
|
69
|
+
assetById.set(path, rec);
|
|
70
|
+
return rec;
|
|
71
|
+
}
|
|
72
|
+
for (const s of opts.segments) {
|
|
73
|
+
if (!existsSync(s.path)) {
|
|
74
|
+
throw new Error(`exportDavinciFcpxml: missing segment ${s.path}`);
|
|
75
|
+
}
|
|
76
|
+
s._asset =
|
|
77
|
+
await registerAsset(s.path, true, s.durSec, s.label);
|
|
78
|
+
}
|
|
79
|
+
for (const a of opts.audio) {
|
|
80
|
+
if (!existsSync(a.path)) {
|
|
81
|
+
throw new Error(`exportDavinciFcpxml: missing audio file ${a.path}`);
|
|
82
|
+
}
|
|
83
|
+
a._asset =
|
|
84
|
+
await registerAsset(a.path, false, a.audioDur, a.label);
|
|
85
|
+
}
|
|
86
|
+
const buckets = opts.segments.map(() => []);
|
|
87
|
+
for (const a of opts.audio) {
|
|
88
|
+
buckets[segmentContaining(opts.segments, a.startSec)].push(a);
|
|
89
|
+
}
|
|
90
|
+
const masterDur = opts.totalDurationSec
|
|
91
|
+
?? opts.segments.reduce((acc, s) => Math.max(acc, s.startSec + s.durSec), 0);
|
|
92
|
+
const lines = [];
|
|
93
|
+
lines.push(`<?xml version="1.0" encoding="UTF-8"?>`);
|
|
94
|
+
lines.push(`<!DOCTYPE fcpxml>`);
|
|
95
|
+
lines.push(`<fcpxml version="1.10">`);
|
|
96
|
+
lines.push(` <resources>`);
|
|
97
|
+
// difference (1): drop colorSpace attribute — Resolve infers it
|
|
98
|
+
// from the source media and avoids the false "missing LUT" warning.
|
|
99
|
+
lines.push(` <format id="${FORMAT_ID}" name="FFVideoFormat${width}x${height}p${fps}" frameDuration="1/${fps}s" width="${width}" height="${height}"/>`);
|
|
100
|
+
for (const a of assetById.values()) {
|
|
101
|
+
// difference (3): raw file:// path, no URI-encoding.
|
|
102
|
+
const uri = pathToResolveFileUri(a.path);
|
|
103
|
+
const hasVideo = a.hasVideo ? '1' : '0';
|
|
104
|
+
const formatAttr = a.hasVideo ? ` format="${FORMAT_ID}"` : '';
|
|
105
|
+
lines.push(` <asset id="${a.id}" name="${xmlEscape(a.name)}" start="0s" duration="${a.durRt}" hasVideo="${hasVideo}" hasAudio="1"${formatAttr} audioSources="1" audioChannels="${a.audioChannels}" audioRate="${a.audioRate}">`);
|
|
106
|
+
lines.push(` <media-rep kind="original-media" src="${uri}"/>`);
|
|
107
|
+
lines.push(` </asset>`);
|
|
108
|
+
}
|
|
109
|
+
lines.push(` </resources>`);
|
|
110
|
+
lines.push(` <library>`);
|
|
111
|
+
lines.push(` <event name="${xmlEscape(eventName)}">`);
|
|
112
|
+
lines.push(` <project name="${xmlEscape(projectName)}">`);
|
|
113
|
+
lines.push(` <sequence format="${FORMAT_ID}" duration="${toRationalTime(masterDur, fps)}" tcStart="0s" tcFormat="NDF" audioLayout="stereo" audioRate="48k">`);
|
|
114
|
+
lines.push(` <spine>`);
|
|
115
|
+
for (let i = 0; i < opts.segments.length; i++) {
|
|
116
|
+
const s = opts.segments[i];
|
|
117
|
+
const segAsset = s._asset;
|
|
118
|
+
const children = buckets[i];
|
|
119
|
+
const open = ` <asset-clip name="${xmlEscape(s.label)}" ref="${segAsset.id}" offset="${toRationalTime(s.startSec, fps)}" duration="${toRationalTime(s.durSec, fps)}" start="0s" tcFormat="NDF" audioRole="dialogue.dialogue"`;
|
|
120
|
+
if (children.length === 0) {
|
|
121
|
+
lines.push(open + `>`);
|
|
122
|
+
lines.push(` <adjust-volume amount="-96dB"/>`);
|
|
123
|
+
lines.push(` </asset-clip>`);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
lines.push(open + `>`);
|
|
127
|
+
lines.push(` <adjust-volume amount="-96dB"/>`);
|
|
128
|
+
for (const ch of children) {
|
|
129
|
+
const chAsset = ch._asset;
|
|
130
|
+
const localOffset = ch.startSec - s.startSec;
|
|
131
|
+
// difference (2): emit <audio-channel-source> hinting at the
|
|
132
|
+
// actual channel count Resolve should mount the track as. Mono
|
|
133
|
+
// dialogue (channels=1) imports as a mono Resolve track; stereo
|
|
134
|
+
// music (channels=2) imports as stereo. FCP X ignores this attribute.
|
|
135
|
+
const channelHint = chAsset.audioChannels === 1 ? ' srcCh="1"' : ' srcCh="1, 2"';
|
|
136
|
+
lines.push(` <audio name="${xmlEscape(ch.label)}" ref="${chAsset.id}" offset="${toRationalTime(localOffset, fps)}" duration="${toRationalTime(ch.audioDur, fps)}" start="0s" lane="${ch.lane}" role="${ch.role}"${channelHint}/>`);
|
|
137
|
+
}
|
|
138
|
+
lines.push(` </asset-clip>`);
|
|
139
|
+
}
|
|
140
|
+
lines.push(` </spine>`);
|
|
141
|
+
lines.push(` </sequence>`);
|
|
142
|
+
lines.push(` </project>`);
|
|
143
|
+
lines.push(` </event>`);
|
|
144
|
+
lines.push(` </library>`);
|
|
145
|
+
lines.push(`</fcpxml>`);
|
|
146
|
+
await mkdir(dirname(opts.outputPath), { recursive: true });
|
|
147
|
+
await writeFile(opts.outputPath, lines.join('\n') + '\n', 'utf-8');
|
|
148
|
+
return opts.outputPath;
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=davinci-fcpxml.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"davinci-fcpxml.js","sourceRoot":"","sources":["../../../src/mini-drama/timeline-export/davinci-fcpxml.ts"],"names":[],"mappings":"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"}
|