@bitmagic/cli 0.1.60 → 0.1.61-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 (119) hide show
  1. package/README.md +47 -8
  2. package/assets/fonts/NotoSans-Bold.ttf +0 -0
  3. package/assets/fonts/OFL.txt +94 -0
  4. package/dist/chrome-launch.d.ts +18 -0
  5. package/dist/chrome-launch.js +24 -0
  6. package/dist/chrome-launch.js.map +1 -0
  7. package/dist/cli.d.ts +1 -0
  8. package/dist/cli.js +2 -0
  9. package/dist/cli.js.map +1 -1
  10. package/dist/commands/generate.d.ts +44 -0
  11. package/dist/commands/generate.js +143 -0
  12. package/dist/commands/generate.js.map +1 -1
  13. package/dist/commands/trailer-publish.d.ts +15 -0
  14. package/dist/commands/trailer-publish.js +45 -0
  15. package/dist/commands/trailer-publish.js.map +1 -0
  16. package/dist/commands/trailer.d.ts +3 -0
  17. package/dist/commands/trailer.js +396 -0
  18. package/dist/commands/trailer.js.map +1 -0
  19. package/dist/editor/hud-replay-page.d.ts +7 -0
  20. package/dist/editor/hud-replay-page.js +219 -0
  21. package/dist/editor/hud-replay-page.js.map +1 -0
  22. package/dist/editor/recording-panel.d.ts +3 -1
  23. package/dist/editor/recording-panel.js +6 -2
  24. package/dist/editor/recording-panel.js.map +1 -1
  25. package/dist/editor/recording-sink.d.ts +3 -20
  26. package/dist/editor/recording-sink.js +14 -35
  27. package/dist/editor/recording-sink.js.map +1 -1
  28. package/dist/editor/server.js +15 -18
  29. package/dist/editor/server.js.map +1 -1
  30. package/dist/editor/shell-page.d.ts +0 -8
  31. package/dist/editor/shell-page.js +23 -19
  32. package/dist/editor/shell-page.js.map +1 -1
  33. package/dist/generate/music.d.ts +51 -0
  34. package/dist/generate/music.js +93 -0
  35. package/dist/generate/music.js.map +1 -0
  36. package/dist/generate/stream.d.ts +13 -0
  37. package/dist/generate/stream.js +17 -1
  38. package/dist/generate/stream.js.map +1 -1
  39. package/dist/project/jobs.d.ts +1 -1
  40. package/dist/scaffold/project-files.d.ts +0 -1
  41. package/dist/scaffold/project-files.js +6 -33
  42. package/dist/scaffold/project-files.js.map +1 -1
  43. package/dist/scaffold/project.js +3 -1
  44. package/dist/scaffold/project.js.map +1 -1
  45. package/dist/scaffold/skill-files.d.ts +22 -0
  46. package/dist/scaffold/skill-files.js +198 -0
  47. package/dist/scaffold/skill-files.js.map +1 -0
  48. package/dist/trailer/analyze.d.ts +23 -0
  49. package/dist/trailer/analyze.js +186 -0
  50. package/dist/trailer/analyze.js.map +1 -0
  51. package/dist/trailer/auto-edl.d.ts +46 -0
  52. package/dist/trailer/auto-edl.js +143 -0
  53. package/dist/trailer/auto-edl.js.map +1 -0
  54. package/dist/trailer/beat-cut.d.ts +57 -0
  55. package/dist/trailer/beat-cut.js +232 -0
  56. package/dist/trailer/beat-cut.js.map +1 -0
  57. package/dist/trailer/client.d.ts +52 -0
  58. package/dist/trailer/client.js +133 -0
  59. package/dist/trailer/client.js.map +1 -0
  60. package/dist/trailer/edl-time.d.ts +61 -0
  61. package/dist/trailer/edl-time.js +105 -0
  62. package/dist/trailer/edl-time.js.map +1 -0
  63. package/dist/trailer/ffmpeg.d.ts +8 -0
  64. package/dist/trailer/ffmpeg.js +55 -0
  65. package/dist/trailer/ffmpeg.js.map +1 -0
  66. package/dist/trailer/hud-raster.d.ts +28 -0
  67. package/dist/trailer/hud-raster.js +114 -0
  68. package/dist/trailer/hud-raster.js.map +1 -0
  69. package/dist/trailer/hud-state.d.ts +110 -0
  70. package/dist/trailer/hud-state.js +410 -0
  71. package/dist/trailer/hud-state.js.map +1 -0
  72. package/dist/trailer/io.d.ts +20 -0
  73. package/dist/trailer/io.js +15 -0
  74. package/dist/trailer/io.js.map +1 -0
  75. package/dist/trailer/make.d.ts +54 -0
  76. package/dist/trailer/make.js +205 -0
  77. package/dist/trailer/make.js.map +1 -0
  78. package/dist/trailer/merge.d.ts +7 -0
  79. package/dist/trailer/merge.js +144 -0
  80. package/dist/trailer/merge.js.map +1 -0
  81. package/dist/trailer/music-beats.d.ts +123 -0
  82. package/dist/trailer/music-beats.js +707 -0
  83. package/dist/trailer/music-beats.js.map +1 -0
  84. package/dist/trailer/paths.d.ts +28 -0
  85. package/dist/trailer/paths.js +101 -0
  86. package/dist/trailer/paths.js.map +1 -0
  87. package/dist/trailer/publish-record.d.ts +17 -0
  88. package/dist/trailer/publish-record.js +59 -0
  89. package/dist/trailer/publish-record.js.map +1 -0
  90. package/dist/trailer/publish.d.ts +45 -0
  91. package/dist/trailer/publish.js +149 -0
  92. package/dist/trailer/publish.js.map +1 -0
  93. package/dist/trailer/record-session.d.ts +72 -0
  94. package/dist/trailer/record-session.js +328 -0
  95. package/dist/trailer/record-session.js.map +1 -0
  96. package/dist/trailer/render.d.ts +57 -0
  97. package/dist/trailer/render.js +475 -0
  98. package/dist/trailer/render.js.map +1 -0
  99. package/dist/trailer/revision.d.ts +8 -0
  100. package/dist/trailer/revision.js +31 -0
  101. package/dist/trailer/revision.js.map +1 -0
  102. package/dist/trailer/synth.d.ts +9 -0
  103. package/dist/trailer/synth.js +120 -0
  104. package/dist/trailer/synth.js.map +1 -0
  105. package/dist/trailer/timeline-types.d.ts +176 -0
  106. package/dist/trailer/timeline-types.js +379 -0
  107. package/dist/trailer/timeline-types.js.map +1 -0
  108. package/dist/trailer/title-raster.d.ts +6 -0
  109. package/dist/trailer/title-raster.js +47 -0
  110. package/dist/trailer/title-raster.js.map +1 -0
  111. package/dist/trailer/titles.d.ts +44 -0
  112. package/dist/trailer/titles.js +119 -0
  113. package/dist/trailer/titles.js.map +1 -0
  114. package/dist/trailer/video-chain.d.ts +41 -0
  115. package/dist/trailer/video-chain.js +78 -0
  116. package/dist/trailer/video-chain.js.map +1 -0
  117. package/dist/verify/browser.js +12 -19
  118. package/dist/verify/browser.js.map +1 -1
  119. package/package.json +5 -4
@@ -0,0 +1,475 @@
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 as
6
+ * transparent PNG overlays applied AFTER that chain, so their enable windows
7
+ * are 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, isBlankHudState } from './hud-state.js';
18
+ import { rasterizeUiKeyframes, readPngSize } from './hud-raster.js';
19
+ import { CliError } from '../errors.js';
20
+ import { probeAudioDurationSec } from './music-beats.js';
21
+ import { recipeHash, synthesizeWav } from './synth.js';
22
+ import { runFfmpeg } from './ffmpeg.js';
23
+ import { out, warn } from './io.js';
24
+ import { buildLogoChain, buildVideoChain, fmtNum } from './video-chain.js';
25
+ import { nextFreeRevision } from './revision.js';
26
+ import { buildTitleOverlayChain, defaultTitleFont, titleInputArgs } from './titles.js';
27
+ import { rasterizeTitles } from './title-raster.js';
28
+ export const DEFAULT_MUSIC_GAIN_DB = -6;
29
+ export const DEFAULT_MUSIC_START_SEC = 0;
30
+ export const DEFAULT_MUSIC_FADE_SEC = 2;
31
+ /** Watermark defaults, all as fractions of the video so they hold at any resolution. */
32
+ export const DEFAULT_LOGO_OPACITY = 0.7;
33
+ export const DEFAULT_LOGO_HEIGHT_FRAC = 0.055;
34
+ export const DEFAULT_LOGO_MARGIN_FRAC = 0.025;
35
+ /**
36
+ * Copy the nearest existing earlier frame over each missing frame that any
37
+ * clip needs, so the image2 sequence has no gaps. Additive and idempotent.
38
+ */
39
+ function fillMissingFrames(recordingDir, timeline, timings) {
40
+ const { name, frameCount } = timeline.recording;
41
+ const missing = new Set(timeline.recording.missingFrames ?? []);
42
+ let filled = 0;
43
+ for (const frame of missing) {
44
+ if (!timings.some((t) => frame >= t.in && frame < t.out))
45
+ continue;
46
+ const target = frameFilePath(recordingDir, name, frame);
47
+ if (fs.existsSync(target))
48
+ continue;
49
+ let source = null;
50
+ for (let f = frame - 1; f >= 0; f--) {
51
+ if (!missing.has(f) && fs.existsSync(frameFilePath(recordingDir, name, f))) {
52
+ source = f;
53
+ break;
54
+ }
55
+ }
56
+ if (source === null) {
57
+ for (let f = frame + 1; f < frameCount; f++) {
58
+ if (!missing.has(f) && fs.existsSync(frameFilePath(recordingDir, name, f))) {
59
+ source = f;
60
+ break;
61
+ }
62
+ }
63
+ }
64
+ if (source === null) {
65
+ warn(`missing frame ${frame}: no existing frame available to substitute`);
66
+ continue;
67
+ }
68
+ fs.copyFileSync(frameFilePath(recordingDir, name, source), target);
69
+ filled++;
70
+ }
71
+ return filled;
72
+ }
73
+ function assetExtension(url) {
74
+ const ext = path.extname(new URL(url).pathname);
75
+ return ext.length > 0 ? ext : '.bin';
76
+ }
77
+ /**
78
+ * Download each unique asset URL once into `.assets/` (skip if cached).
79
+ * Failures warn and map to null so the sound is skipped, never aborting.
80
+ */
81
+ async function prepareAssetFiles(assetsDir, sounds) {
82
+ const byUrl = new Map();
83
+ for (const sound of sounds) {
84
+ if (byUrl.has(sound.url))
85
+ continue;
86
+ try {
87
+ const safeId = sound.assetId.replace(/[^a-zA-Z0-9_-]/g, '_');
88
+ const filePath = path.join(assetsDir, `${safeId}${assetExtension(sound.url)}`);
89
+ if (!fs.existsSync(filePath)) {
90
+ const response = await fetch(sound.url);
91
+ if (!response.ok)
92
+ throw new Error(`HTTP ${response.status}`);
93
+ fs.writeFileSync(filePath, Buffer.from(await response.arrayBuffer()));
94
+ }
95
+ byUrl.set(sound.url, filePath);
96
+ }
97
+ catch (err) {
98
+ warn(`asset download failed, skipping sound "${sound.assetId}" (${sound.url}): ${err instanceof Error ? err.message : String(err)}`);
99
+ byUrl.set(sound.url, null);
100
+ }
101
+ }
102
+ return byUrl;
103
+ }
104
+ /** Synthesize (or reuse) the cached WAV for a synth sound's recipe. */
105
+ function prepareSynthFile(assetsDir, sound, cache) {
106
+ const hash = recipeHash(sound.recipe);
107
+ const cached = cache.get(hash);
108
+ if (cached !== undefined)
109
+ return cached;
110
+ const filePath = path.join(assetsDir, `synth-${hash}.wav`);
111
+ if (!fs.existsSync(filePath))
112
+ fs.writeFileSync(filePath, synthesizeWav(sound.recipe));
113
+ cache.set(hash, filePath);
114
+ return filePath;
115
+ }
116
+ /**
117
+ * Chain the UI overlays onto the finished video chain. Runs in yuv444p so the
118
+ * alpha blend keeps HUD text edges crisp (yuv420 would subsample them), and
119
+ * converts back for libx264 at the end.
120
+ */
121
+ function appendOverlayChain(lines, overlays, uiBase, width, height, endLabel) {
122
+ lines.push('[vbase]format=yuv444p[uibase0]');
123
+ overlays.forEach((overlay, k) => {
124
+ const size = readPngSize(overlay.filePath);
125
+ const needsScale = size === null || size.width !== width || size.height !== height;
126
+ if (needsScale && size !== null) {
127
+ warn(`ui: overlay ${path.basename(overlay.filePath)} is ${size.width}x${size.height}, scaling to ${width}x${height}`);
128
+ }
129
+ const prep = needsScale ? `scale=${width}:${height},format=yuva444p` : 'format=yuva444p';
130
+ lines.push(`[${uiBase + k}:v]${prep}[ui${k}]`);
131
+ const next = k === overlays.length - 1 ? 'uidone' : `uibase${k + 1}`;
132
+ lines.push(`[uibase${k}][ui${k}]overlay=0:0:format=yuv444:eof_action=repeat:` +
133
+ `enable='between(t,${fmtNum(overlay.startSec)},${fmtNum(overlay.endSec)})'[${next}]`);
134
+ });
135
+ lines.push(`[uidone]format=yuv420p[${endLabel}]`);
136
+ }
137
+ /**
138
+ * Filter chain that turns the music input into a bed the length of the video.
139
+ *
140
+ * `[N:a:0]` is deliberate: music files routinely carry embedded cover art as a
141
+ * video stream, and a bare `[N:a]`-style guess is not worth the risk. asetpts
142
+ * re-bases the timestamps after an input seek so the fade lands at the end of
143
+ * the VIDEO, not at some offset inherited from the track.
144
+ */
145
+ function musicFilterChain(music, inputIndex, totalDuration) {
146
+ const parts = ['aresample=48000', 'aformat=channel_layouts=stereo', 'asetpts=PTS-STARTPTS'];
147
+ if (music.gainDb !== 0)
148
+ parts.push(`volume=${fmtNum(music.gainDb)}dB`);
149
+ if (music.fadeSec > 0) {
150
+ const fadeStart = Math.max(0, totalDuration - music.fadeSec);
151
+ parts.push(`afade=t=out:st=${fmtNum(fadeStart)}:d=${fmtNum(music.fadeSec)}`);
152
+ }
153
+ parts.push(`atrim=end=${fmtNum(totalDuration)}`);
154
+ return `[${inputIndex}:a:0]${parts.join(',')}`;
155
+ }
156
+ /**
157
+ * The tail every audio path shares: normalize, then pad/trim to exactly the
158
+ * video length.
159
+ */
160
+ function audioTail(totalDuration) {
161
+ return ('loudnorm=I=-16:TP=-1.5:LRA=11,aresample=48000,aformat=sample_fmts=fltp:channel_layouts=stereo,' +
162
+ `apad,atrim=end=${fmtNum(totalDuration)}[aout]`);
163
+ }
164
+ /** Build the full ffmpeg argument list and write the filtergraph script file. */
165
+ function buildFfmpegArgs(build) {
166
+ const { recordingDir, timeline, timings, audioPlacements, overlays, totalDuration, music, logo, titles, titlePngs } = build;
167
+ const fps = timeline.recording.fps;
168
+ const framePattern = path.join(recordingDir, `${timeline.recording.name}_%06d.png`);
169
+ const args = ['-y'];
170
+ for (const timing of timings) {
171
+ args.push('-framerate', String(fps), '-start_number', String(timing.in), '-i', framePattern);
172
+ }
173
+ const audioBase = timings.length;
174
+ for (const placement of audioPlacements) {
175
+ args.push('-i', placement.filePath);
176
+ }
177
+ // Silent bed so the MP4 always has an audio track — not needed when the
178
+ // music is about to become the audio track by itself.
179
+ const silentBed = audioPlacements.length === 0 && music === undefined;
180
+ if (silentBed) {
181
+ args.push('-f', 'lavfi', '-t', fmtNum(totalDuration), '-i', 'anullsrc=r=48000:cl=stereo');
182
+ }
183
+ const musicIndex = audioBase + audioPlacements.length + (silentBed ? 1 : 0);
184
+ if (music !== undefined) {
185
+ // -stream_loop is an input option, so it goes before this input's -i.
186
+ if (music.loop)
187
+ args.push('-stream_loop', '-1');
188
+ if (music.startSec > 0)
189
+ args.push('-ss', fmtNum(music.startSec));
190
+ args.push('-i', music.filePath);
191
+ }
192
+ // Single-frame PNGs; overlay's framesync repeats the last frame for the
193
+ // whole timeline, and `enable` decides when it is actually drawn.
194
+ const uiBase = musicIndex + (music !== undefined ? 1 : 0);
195
+ for (const overlay of overlays) {
196
+ args.push('-i', overlay.filePath);
197
+ }
198
+ // The logo goes last so adding it can't shift any existing input index.
199
+ const logoIndex = uiBase + overlays.length;
200
+ if (logo !== undefined) {
201
+ args.push('-i', logo.filePath);
202
+ }
203
+ const titleBase = logoIndex + (logo !== undefined ? 1 : 0);
204
+ args.push(...titleInputArgs(titles, titlePngs));
205
+ // Stages applied on top of the cut video, in draw order: titles sit above
206
+ // the HUD overlay (they're the marketing layer, not part of the game), and
207
+ // the watermark sits above everything.
208
+ const { width, height } = timeline.recording;
209
+ const stages = [];
210
+ if (titles.length > 0) {
211
+ stages.push((i, o) => buildTitleOverlayChain(i, o, titles, titleBase));
212
+ }
213
+ if (logo !== undefined) {
214
+ stages.push((i, o) => buildLogoChain(i, o, {
215
+ inputIndex: logoIndex,
216
+ opacity: logo.opacity,
217
+ heightFrac: logo.heightFrac,
218
+ marginFrac: logo.marginFrac,
219
+ corner: logo.corner,
220
+ }, width, height));
221
+ }
222
+ const videoEnd = stages.length > 0 ? 'vstage0' : 'vout';
223
+ const chainEnd = overlays.length > 0 ? 'vbase' : videoEnd;
224
+ const lines = buildVideoChain(timings, fps, chainEnd);
225
+ if (overlays.length > 0) {
226
+ appendOverlayChain(lines, overlays, uiBase, width, height, videoEnd);
227
+ }
228
+ stages.forEach((stage, i) => {
229
+ const from = `vstage${i}`;
230
+ const to = i === stages.length - 1 ? 'vout' : `vstage${i + 1}`;
231
+ lines.push(...stage(from, to));
232
+ });
233
+ const tail = audioTail(totalDuration);
234
+ if (audioPlacements.length > 0) {
235
+ audioPlacements.forEach((placement, j) => {
236
+ const delayMs = Math.max(0, Math.round(placement.outputTimeSec * 1000));
237
+ lines.push(`[${audioBase + j}:a]aresample=48000,aformat=channel_layouts=stereo,volume=${fmtNum(placement.volume)},adelay=${delayMs}:all=1[snd${j}]`);
238
+ });
239
+ const mixInputs = audioPlacements.map((_, j) => `[snd${j}]`).join('');
240
+ const sfxBus = `${mixInputs}amix=inputs=${audioPlacements.length}:duration=longest:normalize=0` +
241
+ (build.sfxGainDb !== 0 ? `,volume=${fmtNum(build.sfxGainDb)}dB` : '');
242
+ if (music === undefined) {
243
+ lines.push(`${sfxBus},${tail}`);
244
+ }
245
+ else {
246
+ lines.push(`${sfxBus}[sfxbus]`);
247
+ lines.push(`${musicFilterChain(music, musicIndex, totalDuration)}[music]`);
248
+ lines.push(`[sfxbus][music]amix=inputs=2:duration=longest:normalize=0,${tail}`);
249
+ }
250
+ }
251
+ else if (music !== undefined) {
252
+ // No SFX to mix with: the music IS the audio track.
253
+ lines.push(`${musicFilterChain(music, musicIndex, totalDuration)},${tail}`);
254
+ }
255
+ else {
256
+ lines.push(`[${audioBase}:a]anull[aout]`);
257
+ }
258
+ fs.writeFileSync(build.scriptPath, `${lines.join(';\n')}\n`);
259
+ 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);
260
+ return args;
261
+ }
262
+ /**
263
+ * Collect, rasterize and place the HUD overlays. Every "can't draw the UI"
264
+ * path returns [] with a spoken reason: --ui must never silently produce a
265
+ * bare video.
266
+ */
267
+ async function prepareUiOverlays(recordingDir, timeline, timings, options) {
268
+ const globalUi = options.ui === true;
269
+ const clipEnabled = (index) => timings[index].ui ?? globalUi;
270
+ if (!timings.some((_, i) => clipEnabled(i))) {
271
+ warn('ui: every clip sets "ui": false — rendering without overlays');
272
+ return [];
273
+ }
274
+ if (timeline.hud.length === 0) {
275
+ warn('ui: this recording has no HUD track (timeline.json has no hud[] entries). Either the game builds its ' +
276
+ 'UI as custom DOM outside the GameHUD API, or it was captured before HUD recording existed. ' +
277
+ 'Rendering without overlays.');
278
+ return [];
279
+ }
280
+ const keyframes = collectUiKeyframes(timeline.hud, timings, {
281
+ fps: timeline.recording.fps,
282
+ clipEnabled
283
+ });
284
+ // Blank states are dropped only AFTER windows are chained, so a hidden HUD
285
+ // leaves a real gap instead of stretching the previous overlay over it.
286
+ const drawable = keyframes.filter((k) => !isBlankHudState(k.state));
287
+ if (drawable.length === 0) {
288
+ warn('ui: the HUD is hidden or empty across every selected clip — rendering without overlays');
289
+ return [];
290
+ }
291
+ if (options.harnessUrl === undefined) {
292
+ warn('ui: no HUD replay harness (is `bitmagic dev` running?) — rendering without overlays');
293
+ return [];
294
+ }
295
+ const paths = await rasterizeUiKeyframes(drawable, {
296
+ harnessUrl: options.harnessUrl,
297
+ outDir: path.join(recordingDir, '.ui'),
298
+ width: timeline.recording.width,
299
+ height: timeline.recording.height
300
+ });
301
+ if (paths === null)
302
+ return [];
303
+ return drawable.map((keyframe, i) => ({
304
+ filePath: paths[i],
305
+ startSec: keyframe.outputStartSec,
306
+ endSec: keyframe.outputEndSec
307
+ }));
308
+ }
309
+ /**
310
+ * Resolve the music bed and decide whether it has to loop. A track that ends
311
+ * before the video does is a real editorial surprise, so it warns rather than
312
+ * quietly dropping to silence half way through.
313
+ */
314
+ /**
315
+ * Resolve the watermark. Like `--music`, the path is relative to the cwd —
316
+ * brand assets live in the repo, not in the recording folder.
317
+ */
318
+ function prepareLogo(logoPath, options) {
319
+ const filePath = path.resolve(logoPath);
320
+ if (!fs.existsSync(filePath)) {
321
+ throw new CliError(`logo not found: ${filePath}`);
322
+ }
323
+ const opacity = options.logoOpacity ?? DEFAULT_LOGO_OPACITY;
324
+ if (!(opacity > 0 && opacity <= 1)) {
325
+ throw new CliError(`--logo-opacity must be in (0, 1], got ${opacity}`);
326
+ }
327
+ const heightFrac = options.logoHeightFrac ?? DEFAULT_LOGO_HEIGHT_FRAC;
328
+ if (!(heightFrac > 0 && heightFrac <= 0.5)) {
329
+ throw new CliError(`--logo-height must be a fraction in (0, 0.5], got ${heightFrac}`);
330
+ }
331
+ const marginFrac = options.logoMarginFrac ?? DEFAULT_LOGO_MARGIN_FRAC;
332
+ if (!(marginFrac >= 0 && marginFrac <= 0.4)) {
333
+ throw new CliError(`--logo-margin must be a fraction in [0, 0.4], got ${marginFrac}`);
334
+ }
335
+ return { filePath, opacity, heightFrac, marginFrac, corner: options.logoCorner ?? 'br' };
336
+ }
337
+ async function prepareMusicBed(musicPath, options, totalDuration) {
338
+ const filePath = path.resolve(musicPath);
339
+ if (!fs.existsSync(filePath))
340
+ throw new CliError(`music file not found: ${filePath}`);
341
+ const startSec = options.musicStartSec ?? DEFAULT_MUSIC_START_SEC;
342
+ const fadeSec = options.musicFadeSec ?? DEFAULT_MUSIC_FADE_SEC;
343
+ if (fadeSec > totalDuration) {
344
+ warn(`music: --music-fade ${fadeSec}s is longer than the ${totalDuration.toFixed(2)}s video — it fades from the first frame`);
345
+ }
346
+ let loop = false;
347
+ try {
348
+ const trackDuration = await probeAudioDurationSec(filePath);
349
+ const usable = trackDuration - startSec;
350
+ if (usable <= 0) {
351
+ throw new CliError(`--music-start ${startSec}s is past the end of a ${trackDuration.toFixed(2)}s track`);
352
+ }
353
+ if (usable < totalDuration) {
354
+ warn(`music: ${usable.toFixed(1)}s of track from --music-start ${startSec}s does not cover the ` +
355
+ `${totalDuration.toFixed(1)}s video — looping it`);
356
+ loop = true;
357
+ }
358
+ }
359
+ catch (err) {
360
+ if (err instanceof Error && err.message.startsWith('--music-start'))
361
+ throw err;
362
+ warn(`music: could not read the track duration (${err instanceof Error ? err.message : String(err)}) — looping it to be safe`);
363
+ loop = true;
364
+ }
365
+ return { filePath, gainDb: options.musicGainDb ?? DEFAULT_MUSIC_GAIN_DB, startSec, fadeSec, loop };
366
+ }
367
+ export async function runRender(recordingDir, options = {}) {
368
+ const timeline = loadTimeline(recordingDir);
369
+ const { name, fps, frameCount } = timeline.recording;
370
+ const edlPath = path.resolve(recordingDir, options.edlPath ?? 'edl.json');
371
+ const edl = loadEdl(edlPath);
372
+ const requestedOut = path.resolve(recordingDir, options.outPath ?? `${name}-trailer.mp4`);
373
+ const outPath = options.overwrite === true ? requestedOut : nextFreeRevision(requestedOut);
374
+ const { timings, totalDuration, warnings } = computeClipTimeline(edl.clips, fps);
375
+ warnings.forEach((w) => warn(w));
376
+ for (const timing of timings) {
377
+ if (timing.out > frameCount) {
378
+ throw new CliError(`clip ${timing.index}: out=${timing.out} exceeds recording frameCount=${frameCount}`);
379
+ }
380
+ }
381
+ const filled = fillMissingFrames(recordingDir, timeline, timings);
382
+ if (filled > 0)
383
+ out(`filled ${filled} missing frame(s) from nearest neighbours`);
384
+ const placements = placeSounds(timings, timeline.sounds, fps, options.tailPadFrames ?? DEFAULT_TAIL_PAD_FRAMES);
385
+ const assetsDir = path.join(recordingDir, '.assets');
386
+ fs.mkdirSync(assetsDir, { recursive: true });
387
+ const assetSounds = placements
388
+ .map((p) => p.sound)
389
+ .filter((s) => s.kind === 'asset');
390
+ const assetFiles = await prepareAssetFiles(assetsDir, assetSounds);
391
+ const synthCache = new Map();
392
+ const audioPlacements = [];
393
+ let skipped = 0;
394
+ for (const placement of placements) {
395
+ const sound = placement.sound;
396
+ if (sound.kind === 'asset') {
397
+ const filePath = assetFiles.get(sound.url) ?? null;
398
+ if (filePath === null) {
399
+ skipped++;
400
+ continue;
401
+ }
402
+ audioPlacements.push({ filePath, outputTimeSec: placement.outputTimeSec, volume: sound.volume ?? 1 });
403
+ }
404
+ else {
405
+ audioPlacements.push({
406
+ filePath: prepareSynthFile(assetsDir, sound, synthCache),
407
+ outputTimeSec: placement.outputTimeSec,
408
+ volume: sound.volume ?? 1
409
+ });
410
+ }
411
+ }
412
+ const wantUi = options.ui === true || edl.clips.some((clip) => clip.ui === true);
413
+ const overlays = wantUi ? await prepareUiOverlays(recordingDir, timeline, timings, options) : [];
414
+ const music = options.musicPath !== undefined ? await prepareMusicBed(options.musicPath, options, totalDuration) : undefined;
415
+ const logo = options.logoPath !== undefined ? prepareLogo(options.logoPath, options) : undefined;
416
+ // Titles are timed in output seconds; warn rather than fail if one would
417
+ // land past the end of the cut, since that only means it is never seen.
418
+ const titles = edl.titles ?? [];
419
+ for (const cue of titles) {
420
+ if (cue.at >= totalDuration) {
421
+ warn(`title "${cue.text}" starts at ${cue.at.toFixed(2)}s, past the ${totalDuration.toFixed(2)}s cut — it will not appear`);
422
+ }
423
+ }
424
+ let titlePngs = [];
425
+ if (titles.length > 0) {
426
+ const font = options.titleFontPath !== undefined ? path.resolve(options.titleFontPath) : defaultTitleFont();
427
+ const rendered = await rasterizeTitles(titles, timeline.recording.width, timeline.recording.height, path.join(recordingDir, '.titles'), font);
428
+ if (rendered === null)
429
+ titles.length = 0;
430
+ else
431
+ titlePngs = rendered;
432
+ }
433
+ const scriptPath = `${outPath}.filtergraph.txt`;
434
+ const build = {
435
+ recordingDir,
436
+ timeline,
437
+ timings,
438
+ audioPlacements,
439
+ overlays,
440
+ totalDuration,
441
+ scriptPath,
442
+ outPath,
443
+ titles,
444
+ titlePngs,
445
+ sfxGainDb: options.sfxGainDb ?? 0
446
+ };
447
+ if (music !== undefined)
448
+ build.music = music;
449
+ if (logo !== undefined)
450
+ build.logo = logo;
451
+ const args = buildFfmpegArgs(build);
452
+ try {
453
+ await runFfmpeg(args);
454
+ }
455
+ catch (err) {
456
+ warn(`render failed; filtergraph kept at ${scriptPath}`);
457
+ throw err;
458
+ }
459
+ fs.unlinkSync(scriptPath);
460
+ // The EDL is the entire creative decision and is a few KB; keep a copy with
461
+ // the video so any render can be reproduced or re-cut later.
462
+ fs.writeFileSync(`${outPath}.edl.json`, JSON.stringify(edl, null, 2));
463
+ out(`out: ${outPath} | duration: ${totalDuration.toFixed(2)}s | clips: ${timings.length} | ` +
464
+ `sounds: ${audioPlacements.length} placed / ${skipped} skipped` +
465
+ (wantUi ? ` | ui overlays: ${overlays.length}` : '') +
466
+ (music !== undefined
467
+ ? ` | music: ${path.basename(music.filePath)} @ ${music.gainDb}dB${music.loop ? ' (looped)' : ''}`
468
+ : '') +
469
+ (logo !== undefined
470
+ ? ` | logo: ${path.basename(logo.filePath)} ${logo.corner} @ ${Math.round(logo.opacity * 100)}%`
471
+ : '') +
472
+ (titles.length > 0 ? ` | titles: ${titles.length}` : ''));
473
+ return { outPath, durationSec: totalDuration, clipCount: timings.length };
474
+ }
475
+ //# 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,8 @@
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;
@@ -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,9 @@
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;