@bitmagic/asset-core 0.2.24 → 0.2.25-dev.1

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.
Files changed (89) hide show
  1. package/dist/trailer/analyze.d.ts +24 -0
  2. package/dist/trailer/analyze.d.ts.map +1 -0
  3. package/dist/trailer/analyze.js +186 -0
  4. package/dist/trailer/analyze.js.map +1 -0
  5. package/dist/trailer/auto-edl.d.ts +51 -0
  6. package/dist/trailer/auto-edl.d.ts.map +1 -0
  7. package/dist/trailer/auto-edl.js +266 -0
  8. package/dist/trailer/auto-edl.js.map +1 -0
  9. package/dist/trailer/beat-cut.d.ts +58 -0
  10. package/dist/trailer/beat-cut.d.ts.map +1 -0
  11. package/dist/trailer/beat-cut.js +232 -0
  12. package/dist/trailer/beat-cut.js.map +1 -0
  13. package/dist/trailer/capture.d.ts +102 -0
  14. package/dist/trailer/capture.d.ts.map +1 -0
  15. package/dist/trailer/capture.js +268 -0
  16. package/dist/trailer/capture.js.map +1 -0
  17. package/dist/trailer/edl-time.d.ts +62 -0
  18. package/dist/trailer/edl-time.d.ts.map +1 -0
  19. package/dist/trailer/edl-time.js +105 -0
  20. package/dist/trailer/edl-time.js.map +1 -0
  21. package/dist/trailer/errors.d.ts +15 -0
  22. package/dist/trailer/errors.d.ts.map +1 -0
  23. package/dist/trailer/errors.js +22 -0
  24. package/dist/trailer/errors.js.map +1 -0
  25. package/dist/trailer/ffmpeg.d.ts +19 -0
  26. package/dist/trailer/ffmpeg.d.ts.map +1 -0
  27. package/dist/trailer/ffmpeg.js +100 -0
  28. package/dist/trailer/ffmpeg.js.map +1 -0
  29. package/dist/trailer/hud-state.d.ts +182 -0
  30. package/dist/trailer/hud-state.d.ts.map +1 -0
  31. package/dist/trailer/hud-state.js +523 -0
  32. package/dist/trailer/hud-state.js.map +1 -0
  33. package/dist/trailer/index.d.ts +30 -0
  34. package/dist/trailer/index.d.ts.map +1 -0
  35. package/dist/trailer/index.js +30 -0
  36. package/dist/trailer/index.js.map +1 -0
  37. package/dist/trailer/io.d.ts +23 -0
  38. package/dist/trailer/io.d.ts.map +1 -0
  39. package/dist/trailer/io.js +20 -0
  40. package/dist/trailer/io.js.map +1 -0
  41. package/dist/trailer/link.d.ts +2 -0
  42. package/dist/trailer/link.d.ts.map +1 -0
  43. package/dist/trailer/link.js +15 -0
  44. package/dist/trailer/link.js.map +1 -0
  45. package/dist/trailer/make.d.ts +60 -0
  46. package/dist/trailer/make.d.ts.map +1 -0
  47. package/dist/trailer/make.js +196 -0
  48. package/dist/trailer/make.js.map +1 -0
  49. package/dist/trailer/merge.d.ts +8 -0
  50. package/dist/trailer/merge.d.ts.map +1 -0
  51. package/dist/trailer/merge.js +165 -0
  52. package/dist/trailer/merge.js.map +1 -0
  53. package/dist/trailer/music-beats.d.ts +124 -0
  54. package/dist/trailer/music-beats.d.ts.map +1 -0
  55. package/dist/trailer/music-beats.js +672 -0
  56. package/dist/trailer/music-beats.js.map +1 -0
  57. package/dist/trailer/paths.d.ts +29 -0
  58. package/dist/trailer/paths.d.ts.map +1 -0
  59. package/dist/trailer/paths.js +101 -0
  60. package/dist/trailer/paths.js.map +1 -0
  61. package/dist/trailer/png.d.ts +10 -0
  62. package/dist/trailer/png.d.ts.map +1 -0
  63. package/dist/trailer/png.js +37 -0
  64. package/dist/trailer/png.js.map +1 -0
  65. package/dist/trailer/render.d.ts +93 -0
  66. package/dist/trailer/render.d.ts.map +1 -0
  67. package/dist/trailer/render.js +527 -0
  68. package/dist/trailer/render.js.map +1 -0
  69. package/dist/trailer/revision.d.ts +9 -0
  70. package/dist/trailer/revision.d.ts.map +1 -0
  71. package/dist/trailer/revision.js +31 -0
  72. package/dist/trailer/revision.js.map +1 -0
  73. package/dist/trailer/synth.d.ts +10 -0
  74. package/dist/trailer/synth.d.ts.map +1 -0
  75. package/dist/trailer/synth.js +120 -0
  76. package/dist/trailer/synth.js.map +1 -0
  77. package/dist/trailer/timeline-types.d.ts +196 -0
  78. package/dist/trailer/timeline-types.d.ts.map +1 -0
  79. package/dist/trailer/timeline-types.js +419 -0
  80. package/dist/trailer/timeline-types.js.map +1 -0
  81. package/dist/trailer/titles.d.ts +38 -0
  82. package/dist/trailer/titles.d.ts.map +1 -0
  83. package/dist/trailer/titles.js +110 -0
  84. package/dist/trailer/titles.js.map +1 -0
  85. package/dist/trailer/video-chain.d.ts +130 -0
  86. package/dist/trailer/video-chain.d.ts.map +1 -0
  87. package/dist/trailer/video-chain.js +261 -0
  88. package/dist/trailer/video-chain.js.map +1 -0
  89. package/package.json +5 -1
@@ -0,0 +1,527 @@
1
+ /**
2
+ * Trailer rendering: EDL -> final MP4 with reconstructed sound.
3
+ *
4
+ * Video: one image2 input per clip, trim + setpts (speed) + fps, chained with
5
+ * concat (cuts) / xfade (crossfades), then — with --ui — the recorded HUD
6
+ * composited over that chain from a single densified alpha PNG sequence built
7
+ * in final output time. Audio: every placed sound instance is its own
8
+ * input, adelay-ed to its output-timeline position and amix-ed, optionally
9
+ * mixed with a music bed, then loudnorm. The whole filtergraph goes through
10
+ * -filter_complex_script to dodge argument-length limits. All time math comes
11
+ * from edl-time.ts.
12
+ */
13
+ import * as fs from 'fs';
14
+ import * as path from 'path';
15
+ import { frameFilePath, loadEdl, loadTimeline } from './timeline-types.js';
16
+ import { DEFAULT_TAIL_PAD_FRAMES, computeClipTimeline, placeSounds } from './edl-time.js';
17
+ import { collectUiKeyframes, computeRasterGeometry, isBlankHudState } from './hud-state.js';
18
+ import { TrailerError } from './errors.js';
19
+ import { probeAudioDurationSec } from './music-beats.js';
20
+ import { recipeHash, synthesizeWav } from './synth.js';
21
+ import { runFfmpeg } from './ffmpeg.js';
22
+ import { out, warn } from './io.js';
23
+ import { appendUiSequenceChain, buildLogoChain, buildUiSequence, buildVideoChain, fmtNum, foldUiOverlays, } from './video-chain.js';
24
+ import { nextFreeRevision } from './revision.js';
25
+ import { buildTitleOverlayChain, titleInputArgs } from './titles.js';
26
+ export const DEFAULT_MUSIC_GAIN_DB = -6;
27
+ export const DEFAULT_MUSIC_START_SEC = 0;
28
+ export const DEFAULT_MUSIC_FADE_SEC = 2;
29
+ /** Watermark defaults, all as fractions of the video so they hold at any resolution. */
30
+ export const DEFAULT_LOGO_OPACITY = 0.7;
31
+ export const DEFAULT_LOGO_HEIGHT_FRAC = 0.055;
32
+ export const DEFAULT_LOGO_MARGIN_FRAC = 0.025;
33
+ /**
34
+ * Copy the nearest existing earlier frame over each missing frame that any
35
+ * clip needs, so the image2 sequence has no gaps. Additive and idempotent.
36
+ */
37
+ function fillMissingFrames(recordingDir, timeline, timings) {
38
+ const { name, frameCount } = timeline.recording;
39
+ const missing = new Set(timeline.recording.missingFrames ?? []);
40
+ let filled = 0;
41
+ for (const frame of missing) {
42
+ if (!timings.some((t) => frame >= t.in && frame < t.out))
43
+ continue;
44
+ const target = frameFilePath(recordingDir, name, frame);
45
+ if (fs.existsSync(target))
46
+ continue;
47
+ let source = null;
48
+ for (let f = frame - 1; f >= 0; f--) {
49
+ if (!missing.has(f) && fs.existsSync(frameFilePath(recordingDir, name, f))) {
50
+ source = f;
51
+ break;
52
+ }
53
+ }
54
+ if (source === null) {
55
+ for (let f = frame + 1; f < frameCount; f++) {
56
+ if (!missing.has(f) && fs.existsSync(frameFilePath(recordingDir, name, f))) {
57
+ source = f;
58
+ break;
59
+ }
60
+ }
61
+ }
62
+ if (source === null) {
63
+ warn(`missing frame ${frame}: no existing frame available to substitute`);
64
+ continue;
65
+ }
66
+ fs.copyFileSync(frameFilePath(recordingDir, name, source), target);
67
+ filled++;
68
+ }
69
+ return filled;
70
+ }
71
+ function assetExtension(url) {
72
+ const ext = path.extname(new URL(url).pathname);
73
+ return ext.length > 0 ? ext : '.bin';
74
+ }
75
+ /**
76
+ * Download each unique asset URL once into `.assets/` (skip if cached).
77
+ * Failures warn and map to null so the sound is skipped, never aborting.
78
+ */
79
+ async function prepareAssetFiles(assetsDir, sounds) {
80
+ const byUrl = new Map();
81
+ for (const sound of sounds) {
82
+ if (byUrl.has(sound.url))
83
+ continue;
84
+ try {
85
+ const safeId = sound.assetId.replace(/[^a-zA-Z0-9_-]/g, '_');
86
+ const filePath = path.join(assetsDir, `${safeId}${assetExtension(sound.url)}`);
87
+ if (!fs.existsSync(filePath)) {
88
+ const response = await fetch(sound.url);
89
+ if (!response.ok)
90
+ throw new Error(`HTTP ${response.status}`);
91
+ fs.writeFileSync(filePath, Buffer.from(await response.arrayBuffer()));
92
+ }
93
+ byUrl.set(sound.url, filePath);
94
+ }
95
+ catch (err) {
96
+ warn(`asset download failed, skipping sound "${sound.assetId}" (${sound.url}): ${err instanceof Error ? err.message : String(err)}`);
97
+ byUrl.set(sound.url, null);
98
+ }
99
+ }
100
+ return byUrl;
101
+ }
102
+ /** Synthesize (or reuse) the cached WAV for a synth sound's recipe. */
103
+ function prepareSynthFile(assetsDir, sound, cache) {
104
+ const hash = recipeHash(sound.recipe);
105
+ const cached = cache.get(hash);
106
+ if (cached !== undefined)
107
+ return cached;
108
+ const filePath = path.join(assetsDir, `synth-${hash}.wav`);
109
+ if (!fs.existsSync(filePath))
110
+ fs.writeFileSync(filePath, synthesizeWav(sound.recipe));
111
+ cache.set(hash, filePath);
112
+ return filePath;
113
+ }
114
+ /**
115
+ * Filter chain that turns the music input into a bed the length of the video.
116
+ *
117
+ * `[N:a:0]` is deliberate: music files routinely carry embedded cover art as a
118
+ * video stream, and a bare `[N:a]`-style guess is not worth the risk. asetpts
119
+ * re-bases the timestamps after an input seek so the fade lands at the end of
120
+ * the VIDEO, not at some offset inherited from the track.
121
+ */
122
+ function musicFilterChain(music, inputIndex, totalDuration) {
123
+ const parts = ['aresample=48000', 'aformat=channel_layouts=stereo', 'asetpts=PTS-STARTPTS'];
124
+ if (music.gainDb !== 0)
125
+ parts.push(`volume=${fmtNum(music.gainDb)}dB`);
126
+ if (music.fadeSec > 0) {
127
+ const fadeStart = Math.max(0, totalDuration - music.fadeSec);
128
+ parts.push(`afade=t=out:st=${fmtNum(fadeStart)}:d=${fmtNum(music.fadeSec)}`);
129
+ }
130
+ parts.push(`atrim=end=${fmtNum(totalDuration)}`);
131
+ return `[${inputIndex}:a:0]${parts.join(',')}`;
132
+ }
133
+ /**
134
+ * The tail every audio path shares: normalize, then pad/trim to exactly the
135
+ * video length.
136
+ */
137
+ function audioTail(totalDuration) {
138
+ return ('loudnorm=I=-16:TP=-1.5:LRA=11,aresample=48000,aformat=sample_fmts=fltp:channel_layouts=stereo,' +
139
+ `apad,atrim=end=${fmtNum(totalDuration)}[aout]`);
140
+ }
141
+ /**
142
+ * Per-RENDER scratch beside the output, like `<out>.filtergraph.txt`: a
143
+ * recording-scoped `.ui-seq` would be wiped by any other render of the same
144
+ * recording (`make` and `render` overlapping on two music variants) while
145
+ * this one's ffmpeg is still reading it — and its entries hardlink rasters
146
+ * that render rewrites in place. Removed with the filtergraph on success, kept
147
+ * on failure for the same diagnostic reason.
148
+ */
149
+ function uiSequenceDir(outPath) {
150
+ return `${outPath}.ui-seq`;
151
+ }
152
+ /** Build the full ffmpeg argument list and write the filtergraph script file. */
153
+ function buildFfmpegArgs(build) {
154
+ const { recordingDir, timeline, timings, audioPlacements, overlays, totalDuration, music, logo, titles, titlePngs, outPath } = build;
155
+ const fps = timeline.recording.fps;
156
+ const framePattern = path.join(recordingDir, `${timeline.recording.name}_%06d.png`);
157
+ const args = ['-y'];
158
+ for (const timing of timings) {
159
+ args.push('-framerate', String(fps), '-start_number', String(timing.in), '-i', framePattern);
160
+ }
161
+ const audioBase = timings.length;
162
+ for (const placement of audioPlacements) {
163
+ args.push('-i', placement.filePath);
164
+ }
165
+ // Silent bed so the MP4 always has an audio track — not needed when the
166
+ // music is about to become the audio track by itself.
167
+ const silentBed = audioPlacements.length === 0 && music === undefined;
168
+ if (silentBed) {
169
+ args.push('-f', 'lavfi', '-t', fmtNum(totalDuration), '-i', 'anullsrc=r=48000:cl=stereo');
170
+ }
171
+ const musicIndex = audioBase + audioPlacements.length + (silentBed ? 1 : 0);
172
+ if (music !== undefined) {
173
+ // -stream_loop is an input option, so it goes before this input's -i.
174
+ if (music.loop)
175
+ args.push('-stream_loop', '-1');
176
+ if (music.startSec > 0)
177
+ args.push('-ss', fmtNum(music.startSec));
178
+ args.push('-i', music.filePath);
179
+ }
180
+ // `card` titles dim the footage behind their own scrim, but the scrim is
181
+ // deliberately translucent — a bright HUD element under it punches through
182
+ // and reads as a glitch on the end card. Blank the HUD for the card's whole
183
+ // window, fades included; `title`/`line` styles sit over live footage and
184
+ // keep the HUD.
185
+ const blocked = titles
186
+ .filter((cue) => (cue.style ?? 'line') === 'card')
187
+ // Parsed back from the same 6-decimal strings the card's own `enable` is
188
+ // built from (titles.ts), so both gates compare one number: a raw
189
+ // `at + dur` (0.1 + 0.2 = 0.30000000000000004) kept the HUD blank for a
190
+ // frame after the card had already released.
191
+ .map((cue) => ({ startSec: Number(fmtNum(cue.at)), endSec: Number(fmtNum(cue.at + cue.dur)) }));
192
+ // One image2 input carrying every output frame, rather than one PNG input
193
+ // per keyframe — see buildUiSequence.
194
+ const uiIndex = musicIndex + (music !== undefined ? 1 : 0);
195
+ const sequence = overlays.length > 0
196
+ ? buildUiSequence(overlays, fps, totalDuration, uiSequenceDir(outPath), blocked)
197
+ : null;
198
+ if (sequence !== null) {
199
+ // ffmpeg reports a short sequence by silently dropping the HUD and exiting
200
+ // 0, so the count is checked here instead of trusting it.
201
+ const written = fs.readdirSync(sequence.dir).filter((name) => name.startsWith('ui_')).length;
202
+ if (written < sequence.frameCount) {
203
+ throw new TrailerError(`ui: overlay sequence is short (${written}/${sequence.frameCount} frames) — refusing to render`);
204
+ }
205
+ out(`ui: overlay sequence ${sequence.frameCount} frame(s), ` +
206
+ `${sequence.frameCount - sequence.blankFrames} drawn / ${sequence.blankFrames} blank`);
207
+ // A rasterized overlay that owns no output frame cost ~25 ms of Playwright
208
+ // and a PNG for nothing — a window shorter than 1/fps, which a speed ramp
209
+ // or --ui-keyframe-rate 0 can produce. Everything else here speaks when it
210
+ // degrades; so does this.
211
+ if (sequence.unusedOverlays > 0) {
212
+ warn(`ui: ${sequence.unusedOverlays} rasterized overlay(s) were too short to own an output frame ` +
213
+ 'and never appear — lower --ui-keyframe-rate to stop paying for them');
214
+ }
215
+ args.push('-framerate', String(fps), '-start_number', '0', '-i', path.join(sequence.dir, 'ui_%06d.png'));
216
+ }
217
+ // The logo goes last so adding it can't shift any existing input index.
218
+ const logoIndex = uiIndex + (sequence !== null ? 1 : 0);
219
+ if (logo !== undefined) {
220
+ args.push('-i', logo.filePath);
221
+ }
222
+ const titleBase = logoIndex + (logo !== undefined ? 1 : 0);
223
+ args.push(...titleInputArgs(titles, titlePngs));
224
+ // Stages applied on top of the cut video, in draw order: titles sit above
225
+ // the HUD overlay (they're the marketing layer, not part of the game), and
226
+ // the watermark sits above everything.
227
+ const { width, height } = timeline.recording;
228
+ const stages = [];
229
+ if (titles.length > 0) {
230
+ stages.push((i, o) => buildTitleOverlayChain(i, o, titles, titleBase));
231
+ }
232
+ if (logo !== undefined) {
233
+ stages.push((i, o) => buildLogoChain(i, o, {
234
+ inputIndex: logoIndex,
235
+ opacity: logo.opacity,
236
+ heightFrac: logo.heightFrac,
237
+ marginFrac: logo.marginFrac,
238
+ corner: logo.corner,
239
+ }, width, height));
240
+ }
241
+ const videoEnd = stages.length > 0 ? 'vstage0' : 'vout';
242
+ const chainEnd = sequence !== null ? 'vbase' : videoEnd;
243
+ const lines = buildVideoChain(timings, fps, chainEnd);
244
+ if (sequence !== null) {
245
+ appendUiSequenceChain(lines, sequence, uiIndex, width, height, videoEnd);
246
+ }
247
+ stages.forEach((stage, i) => {
248
+ const from = `vstage${i}`;
249
+ const to = i === stages.length - 1 ? 'vout' : `vstage${i + 1}`;
250
+ lines.push(...stage(from, to));
251
+ });
252
+ const tail = audioTail(totalDuration);
253
+ if (audioPlacements.length > 0) {
254
+ audioPlacements.forEach((placement, j) => {
255
+ const delayMs = Math.max(0, Math.round(placement.outputTimeSec * 1000));
256
+ lines.push(`[${audioBase + j}:a]aresample=48000,aformat=channel_layouts=stereo,volume=${fmtNum(placement.volume)},adelay=${delayMs}:all=1[snd${j}]`);
257
+ });
258
+ const mixInputs = audioPlacements.map((_, j) => `[snd${j}]`).join('');
259
+ const sfxBus = `${mixInputs}amix=inputs=${audioPlacements.length}:duration=longest:normalize=0` +
260
+ (build.sfxGainDb !== 0 ? `,volume=${fmtNum(build.sfxGainDb)}dB` : '');
261
+ if (music === undefined) {
262
+ lines.push(`${sfxBus},${tail}`);
263
+ }
264
+ else {
265
+ lines.push(`${sfxBus}[sfxbus]`);
266
+ lines.push(`${musicFilterChain(music, musicIndex, totalDuration)}[music]`);
267
+ lines.push(`[sfxbus][music]amix=inputs=2:duration=longest:normalize=0,${tail}`);
268
+ }
269
+ }
270
+ else if (music !== undefined) {
271
+ // No SFX to mix with: the music IS the audio track.
272
+ lines.push(`${musicFilterChain(music, musicIndex, totalDuration)},${tail}`);
273
+ }
274
+ else {
275
+ lines.push(`[${audioBase}:a]anull[aout]`);
276
+ }
277
+ fs.writeFileSync(build.scriptPath, `${lines.join(';\n')}\n`);
278
+ args.push('-filter_complex_script', build.scriptPath, '-map', '[vout]', '-map', '[aout]', '-c:v', 'libx264', '-crf', '20', '-pix_fmt', 'yuv420p', '-c:a', 'aac', '-b:a', '192k', '-movflags', '+faststart', build.outPath);
279
+ return args;
280
+ }
281
+ /**
282
+ * Collect, rasterize and place the HUD overlays. Every "can't draw the UI"
283
+ * path returns [] with a spoken reason: --ui must never silently produce a
284
+ * bare video.
285
+ */
286
+ async function prepareUiOverlays(timeline, timings, options, outPath) {
287
+ const globalUi = options.ui === true;
288
+ const clipEnabled = (index) => timings[index].ui ?? globalUi;
289
+ if (!timings.some((_, i) => clipEnabled(i))) {
290
+ warn('ui: every clip sets "ui": false — rendering without overlays');
291
+ return [];
292
+ }
293
+ if (timeline.hud.length === 0) {
294
+ warn('ui: this recording has no HUD track (timeline.json has no hud[] entries). Either the game builds its ' +
295
+ 'UI as custom DOM outside the GameHUD API, or it was captured before HUD recording existed. ' +
296
+ 'Rendering without overlays.');
297
+ return [];
298
+ }
299
+ const keyframes = collectUiKeyframes(timeline.hud, timings, {
300
+ fps: timeline.recording.fps,
301
+ clipEnabled,
302
+ ...(options.uiKeyframesPerSec !== undefined ? { maxPerSecond: options.uiKeyframesPerSec } : {})
303
+ });
304
+ // Blank states are dropped only AFTER windows are chained, so a hidden HUD
305
+ // leaves a real gap instead of stretching the previous overlay over it.
306
+ const drawable = keyframes.filter((k) => !isBlankHudState(k.state));
307
+ if (drawable.length === 0) {
308
+ warn('ui: the HUD is hidden or empty across every selected clip — rendering without overlays');
309
+ return [];
310
+ }
311
+ // Custom-element markup without its stylesheets replays as bare browser-
312
+ // default markup — the single most common "my UI looks broken in the
313
+ // trailer" cause. Recordings made before the engine captured stylesheets
314
+ // can't be fixed offline, so say it once, precisely.
315
+ const hasCustom = timeline.hud.some((op) => (op.op === 'create' && op.elType === 'custom') || op.op === 'custom-html');
316
+ const hasStylesheets = timeline.hud.some((op) => op.op === 'stylesheet');
317
+ // `recording.window` and stylesheet capture shipped in the same engine
318
+ // change, so a manifest without a window is the one reliable marker that
319
+ // this recording PREDATES capture. Without that check the warning also fires
320
+ // on current recordings of games that style their widgets purely inline —
321
+ // telling the user to re-record when re-recording would change nothing.
322
+ if (hasCustom && !hasStylesheets && timeline.recording.window === undefined) {
323
+ warn('ui: this recording has custom HUD elements but predates stylesheet capture — styled widgets will ' +
324
+ 'replay unstyled. Re-record with a current engine to capture the game\'s CSS (in a CLI project the ' +
325
+ 'recorder is the vendored engine: run `bitmagic upgrade` first, or re-recording changes nothing).');
326
+ }
327
+ if (options.harnessUrl === undefined) {
328
+ warn('ui: no HUD replay harness (is `bitmagic dev` running?) — rendering without overlays');
329
+ return [];
330
+ }
331
+ const geometry = computeRasterGeometry(timeline.recording.width, timeline.recording.height, timeline.recording.window);
332
+ const rasterizeUi = options.rasterizers?.ui;
333
+ if (rasterizeUi === undefined) {
334
+ warn('ui: this host cannot draw the HUD replay — rendering without overlays');
335
+ return [];
336
+ }
337
+ const paths = await rasterizeUi(drawable, {
338
+ harnessUrl: options.harnessUrl,
339
+ outDir: `${outPath}.ui`,
340
+ width: geometry.viewportWidth,
341
+ height: geometry.viewportHeight,
342
+ deviceScaleFactor: geometry.deviceScaleFactor
343
+ });
344
+ if (paths === null)
345
+ return [];
346
+ // A null slot is a keyframe that failed to rasterize; foldUiOverlays holds
347
+ // the previous overlay across it, but only within the same clip.
348
+ const { overlays, bare } = foldUiOverlays(paths, drawable);
349
+ if (bare > 0) {
350
+ warn(`ui: ${bare} failed keyframe window(s) had no adjacent overlay to hold — left without UI`);
351
+ }
352
+ return overlays;
353
+ }
354
+ /**
355
+ * Resolve the music bed and decide whether it has to loop. A track that ends
356
+ * before the video does is a real editorial surprise, so it warns rather than
357
+ * quietly dropping to silence half way through.
358
+ */
359
+ /**
360
+ * Resolve the watermark. Like `--music`, the path is relative to the cwd —
361
+ * brand assets live in the repo, not in the recording folder.
362
+ */
363
+ function prepareLogo(logoPath, options) {
364
+ const filePath = path.resolve(logoPath);
365
+ if (!fs.existsSync(filePath)) {
366
+ throw new TrailerError(`logo not found: ${filePath}`);
367
+ }
368
+ const opacity = options.logoOpacity ?? DEFAULT_LOGO_OPACITY;
369
+ if (!(opacity > 0 && opacity <= 1)) {
370
+ throw new TrailerError(`--logo-opacity must be in (0, 1], got ${opacity}`);
371
+ }
372
+ const heightFrac = options.logoHeightFrac ?? DEFAULT_LOGO_HEIGHT_FRAC;
373
+ if (!(heightFrac > 0 && heightFrac <= 0.5)) {
374
+ throw new TrailerError(`--logo-height must be a fraction in (0, 0.5], got ${heightFrac}`);
375
+ }
376
+ const marginFrac = options.logoMarginFrac ?? DEFAULT_LOGO_MARGIN_FRAC;
377
+ if (!(marginFrac >= 0 && marginFrac <= 0.4)) {
378
+ throw new TrailerError(`--logo-margin must be a fraction in [0, 0.4], got ${marginFrac}`);
379
+ }
380
+ return { filePath, opacity, heightFrac, marginFrac, corner: options.logoCorner ?? 'br' };
381
+ }
382
+ async function prepareMusicBed(musicPath, options, totalDuration) {
383
+ const filePath = path.resolve(musicPath);
384
+ if (!fs.existsSync(filePath))
385
+ throw new TrailerError(`music file not found: ${filePath}`);
386
+ const startSec = options.musicStartSec ?? DEFAULT_MUSIC_START_SEC;
387
+ const fadeSec = options.musicFadeSec ?? DEFAULT_MUSIC_FADE_SEC;
388
+ if (fadeSec > totalDuration) {
389
+ warn(`music: --music-fade ${fadeSec}s is longer than the ${totalDuration.toFixed(2)}s video — it fades from the first frame`);
390
+ }
391
+ let loop = false;
392
+ try {
393
+ const trackDuration = await probeAudioDurationSec(filePath);
394
+ const usable = trackDuration - startSec;
395
+ if (usable <= 0) {
396
+ throw new TrailerError(`--music-start ${startSec}s is past the end of a ${trackDuration.toFixed(2)}s track`);
397
+ }
398
+ if (usable < totalDuration) {
399
+ warn(`music: ${usable.toFixed(1)}s of track from --music-start ${startSec}s does not cover the ` +
400
+ `${totalDuration.toFixed(1)}s video — looping it`);
401
+ loop = true;
402
+ }
403
+ }
404
+ catch (err) {
405
+ if (err instanceof Error && err.message.startsWith('--music-start'))
406
+ throw err;
407
+ warn(`music: could not read the track duration (${err instanceof Error ? err.message : String(err)}) — looping it to be safe`);
408
+ loop = true;
409
+ }
410
+ return { filePath, gainDb: options.musicGainDb ?? DEFAULT_MUSIC_GAIN_DB, startSec, fadeSec, loop };
411
+ }
412
+ export async function runRender(recordingDir, options = {}) {
413
+ const timeline = loadTimeline(recordingDir);
414
+ const { name, fps, frameCount } = timeline.recording;
415
+ const edlPath = path.resolve(recordingDir, options.edlPath ?? 'edl.json');
416
+ const edl = loadEdl(edlPath);
417
+ const requestedOut = path.resolve(recordingDir, options.outPath ?? `${name}-trailer.mp4`);
418
+ const outPath = options.overwrite === true ? requestedOut : nextFreeRevision(requestedOut);
419
+ const { timings, totalDuration, warnings } = computeClipTimeline(edl.clips, fps);
420
+ warnings.forEach((w) => warn(w));
421
+ for (const timing of timings) {
422
+ if (timing.out > frameCount) {
423
+ throw new TrailerError(`clip ${timing.index}: out=${timing.out} exceeds recording frameCount=${frameCount}`);
424
+ }
425
+ }
426
+ const filled = fillMissingFrames(recordingDir, timeline, timings);
427
+ if (filled > 0)
428
+ out(`filled ${filled} missing frame(s) from nearest neighbours`);
429
+ const placements = placeSounds(timings, timeline.sounds, fps, options.tailPadFrames ?? DEFAULT_TAIL_PAD_FRAMES);
430
+ const assetsDir = path.join(recordingDir, '.assets');
431
+ fs.mkdirSync(assetsDir, { recursive: true });
432
+ const assetSounds = placements
433
+ .map((p) => p.sound)
434
+ .filter((s) => s.kind === 'asset');
435
+ const assetFiles = await prepareAssetFiles(assetsDir, assetSounds);
436
+ const synthCache = new Map();
437
+ const audioPlacements = [];
438
+ let skipped = 0;
439
+ for (const placement of placements) {
440
+ const sound = placement.sound;
441
+ if (sound.kind === 'asset') {
442
+ const filePath = assetFiles.get(sound.url) ?? null;
443
+ if (filePath === null) {
444
+ skipped++;
445
+ continue;
446
+ }
447
+ audioPlacements.push({ filePath, outputTimeSec: placement.outputTimeSec, volume: sound.volume ?? 1 });
448
+ }
449
+ else {
450
+ audioPlacements.push({
451
+ filePath: prepareSynthFile(assetsDir, sound, synthCache),
452
+ outputTimeSec: placement.outputTimeSec,
453
+ volume: sound.volume ?? 1
454
+ });
455
+ }
456
+ }
457
+ const wantUi = options.ui === true || edl.clips.some((clip) => clip.ui === true);
458
+ const overlays = wantUi ? await prepareUiOverlays(timeline, timings, options, outPath) : [];
459
+ const music = options.musicPath !== undefined ? await prepareMusicBed(options.musicPath, options, totalDuration) : undefined;
460
+ const logo = options.logoPath !== undefined ? prepareLogo(options.logoPath, options) : undefined;
461
+ // Titles are timed in output seconds; warn rather than fail if one would
462
+ // land past the end of the cut, since that only means it is never seen.
463
+ const titles = edl.titles ?? [];
464
+ for (const cue of titles) {
465
+ if (cue.at >= totalDuration) {
466
+ warn(`title "${cue.text}" starts at ${cue.at.toFixed(2)}s, past the ${totalDuration.toFixed(2)}s cut — it will not appear`);
467
+ }
468
+ }
469
+ let titlePngs = [];
470
+ if (titles.length > 0) {
471
+ const font = options.titleFontPath !== undefined ? path.resolve(options.titleFontPath) : undefined;
472
+ const rasterizeTitles = options.rasterizers?.titles;
473
+ if (rasterizeTitles === undefined)
474
+ warn('titles: this host cannot draw title cards — rendering without them');
475
+ const rendered = rasterizeTitles === undefined ? null : await rasterizeTitles(titles, timeline.recording.width, timeline.recording.height, path.join(recordingDir, '.titles'), font);
476
+ if (rendered === null)
477
+ titles.length = 0;
478
+ else
479
+ titlePngs = rendered;
480
+ }
481
+ const scriptPath = `${outPath}.filtergraph.txt`;
482
+ const build = {
483
+ recordingDir,
484
+ timeline,
485
+ timings,
486
+ audioPlacements,
487
+ overlays,
488
+ totalDuration,
489
+ scriptPath,
490
+ outPath,
491
+ titles,
492
+ titlePngs,
493
+ sfxGainDb: options.sfxGainDb ?? 0
494
+ };
495
+ if (music !== undefined)
496
+ build.music = music;
497
+ if (logo !== undefined)
498
+ build.logo = logo;
499
+ const args = buildFfmpegArgs(build);
500
+ try {
501
+ await runFfmpeg(args);
502
+ }
503
+ catch (err) {
504
+ warn(`render failed; filtergraph kept at ${scriptPath}`);
505
+ throw err;
506
+ }
507
+ fs.unlinkSync(scriptPath);
508
+ // The UI scratch (rasters + densified sequence) served its purpose; the
509
+ // filtergraph kept on failure is the debugging trail, not these.
510
+ fs.rmSync(uiSequenceDir(outPath), { recursive: true, force: true });
511
+ fs.rmSync(`${outPath}.ui`, { recursive: true, force: true });
512
+ // The EDL is the entire creative decision and is a few KB; keep a copy with
513
+ // the video so any render can be reproduced or re-cut later.
514
+ fs.writeFileSync(`${outPath}.edl.json`, JSON.stringify(edl, null, 2));
515
+ out(`out: ${outPath} | duration: ${totalDuration.toFixed(2)}s | clips: ${timings.length} | ` +
516
+ `sounds: ${audioPlacements.length} placed / ${skipped} skipped` +
517
+ (wantUi ? ` | ui overlays: ${overlays.length}` : '') +
518
+ (music !== undefined
519
+ ? ` | music: ${path.basename(music.filePath)} @ ${music.gainDb}dB${music.loop ? ' (looped)' : ''}`
520
+ : '') +
521
+ (logo !== undefined
522
+ ? ` | logo: ${path.basename(logo.filePath)} ${logo.corner} @ ${Math.round(logo.opacity * 100)}%`
523
+ : '') +
524
+ (titles.length > 0 ? ` | titles: ${titles.length}` : ''));
525
+ return { outPath, durationSec: totalDuration, clipCount: timings.length };
526
+ }
527
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/trailer/render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * First unused `<name>-rN.<ext>` for a path that already exists.
3
+ *
4
+ * Renders are iterative and the EDL is the whole creative decision, so
5
+ * overwriting silently destroys the previous cut. A fresh filename also means
6
+ * a video player opens a genuinely new file rather than a cached old one.
7
+ */
8
+ export declare function nextFreeRevision(filePath: string): string;
9
+ //# sourceMappingURL=revision.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revision.d.ts","sourceRoot":"","sources":["../../src/trailer/revision.ts"],"names":[],"mappings":"AAWA;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAUzD"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Never overwrite a rendered trailer.
3
+ *
4
+ * Renders are iterative and the EDL is the whole creative decision, so
5
+ * replacing the file destroys the only record of that edit. A fresh filename
6
+ * also means a video player opens a genuinely new file instead of showing a
7
+ * cached stale one.
8
+ */
9
+ import * as fs from 'fs';
10
+ import * as path from 'path';
11
+ /**
12
+ * First unused `<name>-rN.<ext>` for a path that already exists.
13
+ *
14
+ * Renders are iterative and the EDL is the whole creative decision, so
15
+ * overwriting silently destroys the previous cut. A fresh filename also means
16
+ * a video player opens a genuinely new file rather than a cached old one.
17
+ */
18
+ export function nextFreeRevision(filePath) {
19
+ if (!fs.existsSync(filePath))
20
+ return filePath;
21
+ const dir = path.dirname(filePath);
22
+ const ext = path.extname(filePath);
23
+ const base = path.basename(filePath, ext).replace(/-r\d+$/, '');
24
+ for (let rev = 2; rev < 1000; rev++) {
25
+ const candidate = path.join(dir, `${base}-r${rev}${ext}`);
26
+ if (!fs.existsSync(candidate))
27
+ return candidate;
28
+ }
29
+ throw new Error(`could not find a free revision name for ${filePath}`);
30
+ }
31
+ //# sourceMappingURL=revision.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revision.js","sourceRoot":"","sources":["../../src/trailer/revision.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAChE,KAAK,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,GAAG,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC;QACpC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,KAAK,GAAG,GAAG,GAAG,EAAE,CAAC,CAAC;QAC1D,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC;YAAE,OAAO,SAAS,CAAC;IAClD,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,2CAA2C,QAAQ,EAAE,CAAC,CAAC;AACzE,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { SynthRecipe } from './timeline-types.js';
2
+ export declare const SYNTH_SAMPLE_RATE = 48000;
3
+ /** Render a recipe to mono float samples in [-1, 1], faded at both ends. */
4
+ export declare function synthesizeSamples(recipe: SynthRecipe): Float32Array;
5
+ /** Encode mono float samples as a 48kHz 16-bit PCM WAV file. */
6
+ export declare function samplesToWav(samples: Float32Array): Buffer;
7
+ export declare function synthesizeWav(recipe: SynthRecipe): Buffer;
8
+ /** Stable cache key for a recipe (16 hex chars of sha256 over its JSON). */
9
+ export declare function recipeHash(recipe: SynthRecipe): string;
10
+ //# sourceMappingURL=synth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"synth.d.ts","sourceRoot":"","sources":["../../src/trailer/synth.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAc,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEnE,eAAO,MAAM,iBAAiB,QAAQ,CAAC;AAyDvC,4EAA4E;AAC5E,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,GAAG,YAAY,CAmBnE;AAED,gEAAgE;AAChE,wBAAgB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAoB1D;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAEzD;AAED,4EAA4E;AAC5E,wBAAgB,UAAU,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAEtD"}