@bitmagic/cli 0.1.70-dev.0 → 0.1.70-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 (96) hide show
  1. package/dist/bin.js +5 -4
  2. package/dist/bin.js.map +1 -1
  3. package/dist/cli.js +6 -5
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/trailer.js +6 -10
  6. package/dist/commands/trailer.js.map +1 -1
  7. package/dist/editor/recording-sink.d.ts +2 -9
  8. package/dist/editor/recording-sink.js +3 -15
  9. package/dist/editor/recording-sink.js.map +1 -1
  10. package/dist/editor/recording-stream.d.ts +3 -2
  11. package/dist/editor/recording-stream.js +14 -48
  12. package/dist/editor/recording-stream.js.map +1 -1
  13. package/dist/editor/shell-page.js +1 -1
  14. package/dist/editor/shell-page.js.map +1 -1
  15. package/dist/errors.d.ts +8 -0
  16. package/dist/errors.js +16 -0
  17. package/dist/errors.js.map +1 -1
  18. package/dist/generate/video-review.js +1 -1
  19. package/dist/generate/video-review.js.map +1 -1
  20. package/dist/render/qr-png.d.ts +3 -6
  21. package/dist/render/qr-png.js +4 -33
  22. package/dist/render/qr-png.js.map +1 -1
  23. package/dist/scaffold/asset-pipeline-skill-files.d.ts +1 -1
  24. package/dist/scaffold/asset-pipeline-skill-files.js +3 -3
  25. package/dist/trailer/chrome-rasterizers.d.ts +7 -0
  26. package/dist/trailer/chrome-rasterizers.js +7 -0
  27. package/dist/trailer/chrome-rasterizers.js.map +1 -0
  28. package/dist/trailer/game-name.d.ts +2 -0
  29. package/dist/trailer/game-name.js +20 -0
  30. package/dist/trailer/game-name.js.map +1 -0
  31. package/dist/trailer/hud-raster.d.ts +1 -1
  32. package/dist/trailer/hud-raster.js +1 -1
  33. package/dist/trailer/hud-raster.js.map +1 -1
  34. package/dist/trailer/record-session.js +1 -2
  35. package/dist/trailer/record-session.js.map +1 -1
  36. package/dist/trailer/title-raster.d.ts +9 -2
  37. package/dist/trailer/title-raster.js +17 -3
  38. package/dist/trailer/title-raster.js.map +1 -1
  39. package/package.json +4 -4
  40. package/dist/editor/capture-file.d.ts +0 -70
  41. package/dist/editor/capture-file.js +0 -204
  42. package/dist/editor/capture-file.js.map +0 -1
  43. package/dist/trailer/analyze.d.ts +0 -23
  44. package/dist/trailer/analyze.js +0 -186
  45. package/dist/trailer/analyze.js.map +0 -1
  46. package/dist/trailer/auto-edl.d.ts +0 -50
  47. package/dist/trailer/auto-edl.js +0 -267
  48. package/dist/trailer/auto-edl.js.map +0 -1
  49. package/dist/trailer/beat-cut.d.ts +0 -57
  50. package/dist/trailer/beat-cut.js +0 -232
  51. package/dist/trailer/beat-cut.js.map +0 -1
  52. package/dist/trailer/edl-time.d.ts +0 -61
  53. package/dist/trailer/edl-time.js +0 -105
  54. package/dist/trailer/edl-time.js.map +0 -1
  55. package/dist/trailer/ffmpeg.d.ts +0 -18
  56. package/dist/trailer/ffmpeg.js +0 -100
  57. package/dist/trailer/ffmpeg.js.map +0 -1
  58. package/dist/trailer/hud-state.d.ts +0 -181
  59. package/dist/trailer/hud-state.js +0 -525
  60. package/dist/trailer/hud-state.js.map +0 -1
  61. package/dist/trailer/io.d.ts +0 -22
  62. package/dist/trailer/io.js +0 -20
  63. package/dist/trailer/io.js.map +0 -1
  64. package/dist/trailer/link.d.ts +0 -1
  65. package/dist/trailer/link.js +0 -15
  66. package/dist/trailer/link.js.map +0 -1
  67. package/dist/trailer/make.d.ts +0 -56
  68. package/dist/trailer/make.js +0 -212
  69. package/dist/trailer/make.js.map +0 -1
  70. package/dist/trailer/merge.d.ts +0 -7
  71. package/dist/trailer/merge.js +0 -165
  72. package/dist/trailer/merge.js.map +0 -1
  73. package/dist/trailer/music-beats.d.ts +0 -123
  74. package/dist/trailer/music-beats.js +0 -672
  75. package/dist/trailer/music-beats.js.map +0 -1
  76. package/dist/trailer/paths.d.ts +0 -28
  77. package/dist/trailer/paths.js +0 -101
  78. package/dist/trailer/paths.js.map +0 -1
  79. package/dist/trailer/render.d.ts +0 -59
  80. package/dist/trailer/render.js +0 -521
  81. package/dist/trailer/render.js.map +0 -1
  82. package/dist/trailer/revision.d.ts +0 -8
  83. package/dist/trailer/revision.js +0 -31
  84. package/dist/trailer/revision.js.map +0 -1
  85. package/dist/trailer/synth.d.ts +0 -9
  86. package/dist/trailer/synth.js +0 -120
  87. package/dist/trailer/synth.js.map +0 -1
  88. package/dist/trailer/timeline-types.d.ts +0 -195
  89. package/dist/trailer/timeline-types.js +0 -419
  90. package/dist/trailer/timeline-types.js.map +0 -1
  91. package/dist/trailer/titles.d.ts +0 -44
  92. package/dist/trailer/titles.js +0 -124
  93. package/dist/trailer/titles.js.map +0 -1
  94. package/dist/trailer/video-chain.d.ts +0 -129
  95. package/dist/trailer/video-chain.js +0 -261
  96. package/dist/trailer/video-chain.js.map +0 -1
@@ -1,204 +0,0 @@
1
- /**
2
- * The video side of the recording stream: the file the sidecar appends encoded chunks to during a
3
- * recording, and the ffmpeg steps that turn it into `capture.mp4` and the PNG frames after stop.
4
- *
5
- * The recorder's worker encodes every frame with WebCodecs (see game/src/debug/RecordStream.ts)
6
- * and sends `[frameIndex u32 LE][keyframe u8][chunk bytes]`. H.264 arrives as Annex B — SPS/PPS
7
- * inline on every keyframe — so appending the chunks gives a valid `.h264` elementary stream; VP9
8
- * has no such framing, so its chunks go into an IVF file (a 32-byte header and a 12-byte header
9
- * per frame). Both remux with `-c copy` and both extract the same `<name>_%06d.png` sequence the
10
- * trailer pipeline reads, numbered from 0 like every other reader in `cli/src/trailer/`.
11
- *
12
- * Pure helpers, testable without a socket or ffmpeg; `recording-stream.ts` runs them.
13
- */
14
- import * as fs from 'fs';
15
- import * as path from 'path';
16
- import { frameFileName } from './recording-sink.js';
17
- export const CHUNK_HEADER_BYTES = 5;
18
- export const CAPTURE_FPS = 60;
19
- /** Where ffmpeg drops the extracted PNGs before they are renamed onto the recorder's frame indices. */
20
- export const EXTRACT_DIR = '.extract';
21
- export function isCaptureCodec(value) {
22
- return value === 'avc' || value === 'vp9';
23
- }
24
- /** The elementary stream written during the recording. */
25
- export function elementaryName(codec) {
26
- return codec === 'avc' ? 'capture.h264' : 'capture.ivf';
27
- }
28
- /** The playable file kept next to the frames. */
29
- export function containerName(codec) {
30
- return codec === 'avc' ? 'capture.mp4' : 'capture.webm';
31
- }
32
- /** Split a binary message into its header and chunk bytes; null when it cannot be one. */
33
- export function parseChunkMessage(data) {
34
- if (data.length <= CHUNK_HEADER_BYTES)
35
- return null;
36
- return { frameIndex: data.readUInt32LE(0), key: data[4] === 1, bytes: data.subarray(CHUNK_HEADER_BYTES) };
37
- }
38
- /**
39
- * Which frame indices arrived. Chrome's encoders emit one chunk per frame in input order, so a
40
- * gap here is a frame the recorder itself reported as failed; `finalizeFrames` fills it from a
41
- * neighbour after extraction, the way `FrameSequencer` does for raw frames.
42
- */
43
- export class ChunkLedger {
44
- next = 0;
45
- present = [];
46
- missing = [];
47
- /** Whether this chunk is new (a late or duplicate index is ignored). */
48
- accept(frameIndex) {
49
- if (frameIndex < this.next)
50
- return false;
51
- while (this.next < frameIndex) {
52
- this.missing.push(this.next);
53
- this.next++;
54
- }
55
- this.present.push(frameIndex);
56
- this.next++;
57
- return true;
58
- }
59
- /** Mark the tail up to the recorder's declared count as missing. */
60
- finish(frameCount) {
61
- while (this.next < frameCount) {
62
- this.missing.push(this.next);
63
- this.next++;
64
- }
65
- }
66
- get received() {
67
- return this.present.length;
68
- }
69
- /** One past the highest index seen (or declared). */
70
- get expected() {
71
- return this.next;
72
- }
73
- }
74
- /** The 32-byte IVF file header (VP9, timebase 1/60). */
75
- export function ivfHeader(width, height, frameCount) {
76
- const header = Buffer.alloc(32);
77
- header.write('DKIF', 0, 'ascii');
78
- header.writeUInt16LE(0, 4);
79
- header.writeUInt16LE(32, 6);
80
- header.write('VP90', 8, 'ascii');
81
- header.writeUInt16LE(width, 12);
82
- header.writeUInt16LE(height, 14);
83
- header.writeUInt32LE(CAPTURE_FPS, 16);
84
- header.writeUInt32LE(1, 20);
85
- header.writeUInt32LE(frameCount, 24);
86
- header.writeUInt32LE(0, 28);
87
- return header;
88
- }
89
- function writeAll(fd, bytes, position) {
90
- let offset = 0;
91
- while (offset < bytes.length) {
92
- offset += fs.writeSync(fd, bytes, offset, bytes.length - offset, position === undefined ? null : position + offset);
93
- }
94
- }
95
- /** Appends chunks to the elementary stream as they arrive; closes with the IVF count patched in. */
96
- export class CaptureWriter {
97
- codec;
98
- width;
99
- height;
100
- filePath;
101
- fd;
102
- frames = 0;
103
- closed = false;
104
- constructor(codec, dir, width, height) {
105
- this.codec = codec;
106
- this.width = width;
107
- this.height = height;
108
- this.filePath = path.join(dir, elementaryName(codec));
109
- this.fd = fs.openSync(this.filePath, 'w');
110
- if (codec === 'vp9')
111
- writeAll(this.fd, ivfHeader(width, height, 0));
112
- }
113
- append(frameIndex, bytes) {
114
- if (this.closed)
115
- return;
116
- if (this.codec === 'vp9') {
117
- const frameHeader = Buffer.alloc(12);
118
- frameHeader.writeUInt32LE(bytes.length, 0);
119
- frameHeader.writeBigUInt64LE(BigInt(frameIndex), 4);
120
- writeAll(this.fd, frameHeader);
121
- }
122
- writeAll(this.fd, bytes);
123
- this.frames++;
124
- }
125
- close() {
126
- if (this.closed)
127
- return;
128
- this.closed = true;
129
- if (this.codec === 'vp9')
130
- writeAll(this.fd, ivfHeader(this.width, this.height, this.frames), 0);
131
- fs.closeSync(this.fd);
132
- }
133
- }
134
- /**
135
- * `-c copy` into a container. The raw H.264 demuxer would otherwise take its timing from the
136
- * encoder's VUI (Chrome's software encoder writes a 120 Hz tick), so the frame rate is forced on
137
- * the input and the timestamps regenerated.
138
- */
139
- export function remuxArgs(codec, dir) {
140
- return [
141
- '-v', 'error', '-y',
142
- '-fflags', '+genpts', '-r', String(CAPTURE_FPS),
143
- '-i', path.join(dir, elementaryName(codec)),
144
- '-c', 'copy',
145
- ...(codec === 'avc' ? ['-movflags', '+faststart'] : []),
146
- path.join(dir, containerName(codec)),
147
- ];
148
- }
149
- /**
150
- * One PNG per decoded frame, in decode order, into `.extract/`. `-vsync 0` (the spelling every
151
- * ffmpeg since 4.x accepts) stops ffmpeg from duplicating or dropping frames to hit a rate.
152
- */
153
- export function extractArgs(codec, dir) {
154
- return [
155
- '-v', 'error', '-y',
156
- '-i', path.join(dir, containerName(codec)),
157
- '-vsync', '0', '-start_number', '0',
158
- '-pix_fmt', 'rgb24', '-c:v', 'png', '-compression_level', '1',
159
- path.join(dir, EXTRACT_DIR, 'f_%06d.png'),
160
- ];
161
- }
162
- /**
163
- * Rename the extracted PNGs onto the frame indices the ledger says they belong to, then fill
164
- * every hole up to the declared count by copying the previous frame (or the first one, for a
165
- * leading gap) so `<name>_%06d.png` stays aligned with the recorder's frame clock. Returns the
166
- * same `{written, missing}` the raw path reports.
167
- */
168
- export function finalizeFrames(dir, recordingName, ledger, frameCount) {
169
- const extractDir = path.join(dir, EXTRACT_DIR);
170
- const files = fs.existsSync(extractDir)
171
- ? fs.readdirSync(extractDir).filter((name) => /^f_\d{6}\.png$/.test(name)).sort()
172
- : [];
173
- const missing = new Set(ledger.missing);
174
- const have = new Map();
175
- ledger.present.forEach((index, i) => {
176
- if (i < files.length) {
177
- const dest = path.join(dir, frameFileName(recordingName, index));
178
- fs.renameSync(path.join(extractDir, files[i]), dest);
179
- have.set(index, dest);
180
- }
181
- else {
182
- missing.add(index);
183
- }
184
- });
185
- const total = Math.max(frameCount, ledger.expected);
186
- const first = [...have.keys()].sort((a, b) => a - b)[0];
187
- let previous = first === undefined ? null : have.get(first);
188
- for (let index = 0; index < total; index++) {
189
- const own = have.get(index);
190
- if (own !== undefined) {
191
- previous = own;
192
- continue;
193
- }
194
- missing.add(index);
195
- if (previous !== null) {
196
- const dest = path.join(dir, frameFileName(recordingName, index));
197
- fs.copyFileSync(previous, dest);
198
- previous = dest;
199
- }
200
- }
201
- fs.rmSync(extractDir, { recursive: true, force: true });
202
- return { written: total, missing: [...missing].sort((a, b) => a - b) };
203
- }
204
- //# sourceMappingURL=capture-file.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"capture-file.js","sourceRoot":"","sources":["../../src/editor/capture-file.ts"],"names":[],"mappings":"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"}
@@ -1,23 +0,0 @@
1
- import type { Analysis, Timeline } from './timeline-types.js';
2
- export declare const EVENT_WEIGHTS: Readonly<Record<string, number>>;
3
- export declare const DEFAULT_EVENT_WEIGHT = 0.5;
4
- export declare const FORCED_EVENT_TYPES: readonly string[];
5
- export declare function eventWeight(type: string): number;
6
- export interface SelectedWindow {
7
- startFrame: number;
8
- endFrame: number;
9
- score: number;
10
- forced: boolean;
11
- events: string[];
12
- }
13
- /**
14
- * Deterministic window selection over a timeline. Pure — no filesystem or
15
- * ffmpeg — so it is unit-testable on synthetic timelines.
16
- */
17
- export declare function selectWindows(timeline: Timeline, windowSec: number, top: number): SelectedWindow[];
18
- /**
19
- * Score the timeline and write `analysis.json` + `sheets/` into `outDir` (default: the recording
20
- * dir itself, the web-lane layout; the CLI lane points it at the trailer work dir so recordings
21
- * stay pristine). Sheet paths inside analysis.json are relative to `outDir`.
22
- */
23
- export declare function runAnalyze(recordingDir: string, windowSec: number, top: number, outDir?: string): Promise<Analysis>;
@@ -1,186 +0,0 @@
1
- /**
2
- * Timeline analysis: score events into 0.5s bins, Gaussian-smooth, pick
3
- * top-N non-overlapping highlight windows (plus force-included windows around
4
- * score/lap/goal/match-end events), and render a 3x2 contact sheet per window.
5
- */
6
- import * as fs from 'fs';
7
- import * as path from 'path';
8
- import { frameFilePath, loadTimeline } from './timeline-types.js';
9
- import { runFfmpeg } from './ffmpeg.js';
10
- import { out } from './io.js';
11
- const BIN_SEC = 0.5;
12
- const SMOOTH_SIGMA_BINS = 2; // sigma ~= 1s at 0.5s bins
13
- const DEFAULT_INTENSITY = 0.7;
14
- const SHEET_FRAME_COUNT = 6;
15
- const SHEET_TILE_WIDTH = 320;
16
- /**
17
- * Top-N candidate peaks below this are ignored: they are smoothing tails
18
- * (a single event bleeds a tiny score across the whole kernel radius), not
19
- * real highlights. Force-included windows bypass the floor by design.
20
- */
21
- const MIN_WINDOW_PEAK_SCORE = 0.05;
22
- export const EVENT_WEIGHTS = {
23
- explosion: 1.0,
24
- 'match-end': 1.0,
25
- 'player-death': 0.9,
26
- 'vehicle-collision': 0.9,
27
- score: 0.9,
28
- lap: 0.9,
29
- goal: 0.9,
30
- death: 0.8,
31
- 'vehicle-hits-character': 0.8,
32
- 'projectile-hit': 0.5,
33
- damage: 0.4,
34
- pickup: 0.3,
35
- shot: 0.15,
36
- state: 0
37
- };
38
- export const DEFAULT_EVENT_WEIGHT = 0.5;
39
- export const FORCED_EVENT_TYPES = ['score', 'lap', 'goal', 'match-end'];
40
- export function eventWeight(type) {
41
- return EVENT_WEIGHTS[type] ?? DEFAULT_EVENT_WEIGHT;
42
- }
43
- /**
44
- * Gaussian smoothing with an UNNORMALIZED kernel (peak weight 1): a bin's
45
- * smoothed value reads as the gaussian-weighted event mass around it, so
46
- * window scores stay on the same scale as the summed per-event weights.
47
- */
48
- function gaussianSmooth(values, sigma) {
49
- const radius = Math.max(1, Math.ceil(sigma * 3));
50
- const kernel = [];
51
- for (let d = -radius; d <= radius; d++) {
52
- kernel.push(Math.exp(-(d * d) / (2 * sigma * sigma)));
53
- }
54
- const smoothed = new Float64Array(values.length);
55
- for (let i = 0; i < values.length; i++) {
56
- let acc = 0;
57
- for (let d = -radius; d <= radius; d++) {
58
- const j = i + d;
59
- if (j >= 0 && j < values.length)
60
- acc += values[j] * kernel[d + radius];
61
- }
62
- smoothed[i] = acc;
63
- }
64
- return smoothed;
65
- }
66
- /**
67
- * Deterministic window selection over a timeline. Pure — no filesystem or
68
- * ffmpeg — so it is unit-testable on synthetic timelines.
69
- */
70
- export function selectWindows(timeline, windowSec, top) {
71
- const { fps, frameCount } = timeline.recording;
72
- const binFrames = fps * BIN_SEC;
73
- const totalBins = Math.max(1, Math.ceil(frameCount / binFrames));
74
- const binOf = (frame) => Math.min(totalBins - 1, Math.max(0, Math.floor(frame / binFrames)));
75
- const raw = new Float64Array(totalBins);
76
- for (const event of timeline.events) {
77
- const contribution = eventWeight(event.type) * (event.intensity ?? DEFAULT_INTENSITY);
78
- if (contribution > 0)
79
- raw[binOf(event.frame)] += contribution;
80
- }
81
- for (const sound of timeline.sounds) {
82
- raw[binOf(sound.frame)] += sound.kind === 'asset' ? 0.1 : 0.05;
83
- }
84
- const smoothed = gaussianSmooth(raw, SMOOTH_SIGMA_BINS);
85
- const windowBins = Math.max(1, Math.round(windowSec / BIN_SEC));
86
- const windowFrames = Math.round(windowSec * fps);
87
- const maxStartBin = Math.max(0, totalBins - windowBins);
88
- const chosen = [];
89
- const overlapsChosen = (startBin) => chosen.some((c) => startBin < c.startBin + windowBins && c.startBin < startBin + windowBins);
90
- // Top-N peaks, greedily, highest smoothed bin first (ties -> earlier bin).
91
- const order = Array.from({ length: totalBins }, (_, i) => i).sort((a, b) => smoothed[b] - smoothed[a] || a - b);
92
- for (const bin of order) {
93
- if (chosen.length >= top)
94
- break;
95
- if (smoothed[bin] < MIN_WINDOW_PEAK_SCORE)
96
- break; // sorted descending: only noise remains
97
- const startBin = Math.min(maxStartBin, Math.max(0, bin - Math.floor(windowBins / 2)));
98
- if (!overlapsChosen(startBin))
99
- chosen.push({ startBin, forced: false });
100
- }
101
- // Force-include a window centered on every score/lap/goal/match-end event,
102
- // even if low-scoring; drop ones overlapping already-selected windows.
103
- for (const event of timeline.events) {
104
- if (!FORCED_EVENT_TYPES.includes(event.type))
105
- continue;
106
- const idealStartBin = (event.frame - windowFrames / 2) / binFrames;
107
- const startBin = Math.min(maxStartBin, Math.max(0, Math.round(idealStartBin)));
108
- if (!overlapsChosen(startBin))
109
- chosen.push({ startBin, forced: true });
110
- }
111
- chosen.sort((a, b) => a.startBin - b.startBin);
112
- return chosen.map((candidate) => {
113
- const startFrame = Math.round(candidate.startBin * binFrames);
114
- const endFrame = Math.min(frameCount, startFrame + windowFrames);
115
- let peak = 0;
116
- const lastBin = Math.min(totalBins, candidate.startBin + windowBins);
117
- for (let b = candidate.startBin; b < lastBin; b++)
118
- peak = Math.max(peak, smoothed[b]);
119
- const digest = timeline.events
120
- .filter((e) => e.frame >= startFrame && e.frame < endFrame && eventWeight(e.type) > 0)
121
- .map((e) => `f${e.frame} ${e.type} i${e.intensity ?? DEFAULT_INTENSITY}`);
122
- return {
123
- startFrame,
124
- endFrame,
125
- score: Math.round(peak * 1000) / 1000,
126
- forced: candidate.forced,
127
- events: digest
128
- };
129
- });
130
- }
131
- /** Find the frame index nearest to `ideal` whose PNG actually exists on disk. */
132
- function nearestExistingFrame(recordingDir, name, ideal, frameCount) {
133
- for (let distance = 0; distance < frameCount; distance++) {
134
- const candidates = distance === 0 ? [ideal] : [ideal - distance, ideal + distance];
135
- for (const frame of candidates) {
136
- if (frame >= 0 && frame < frameCount && fs.existsSync(frameFilePath(recordingDir, name, frame))) {
137
- return frame;
138
- }
139
- }
140
- }
141
- throw new Error(`no frame files found near frame ${ideal} in ${recordingDir}`);
142
- }
143
- async function renderContactSheet(recordingDir, timeline, window, sheetPath) {
144
- const { name, frameCount } = timeline.recording;
145
- const span = Math.max(1, window.endFrame - window.startFrame);
146
- const args = ['-y'];
147
- const labels = [];
148
- for (let k = 0; k < SHEET_FRAME_COUNT; k++) {
149
- const ideal = window.startFrame + Math.round((k * (span - 1)) / (SHEET_FRAME_COUNT - 1));
150
- const frame = nearestExistingFrame(recordingDir, name, ideal, frameCount);
151
- args.push('-i', frameFilePath(recordingDir, name, frame));
152
- labels.push(`[${k}:v]`);
153
- }
154
- args.push('-filter_complex', `${labels.join('')}concat=n=${SHEET_FRAME_COUNT}:v=1:a=0,scale=${SHEET_TILE_WIDTH}:-1,tile=3x2[sheet]`, '-map', '[sheet]', '-frames:v', '1', sheetPath);
155
- await runFfmpeg(args);
156
- }
157
- /**
158
- * Score the timeline and write `analysis.json` + `sheets/` into `outDir` (default: the recording
159
- * dir itself, the web-lane layout; the CLI lane points it at the trailer work dir so recordings
160
- * stay pristine). Sheet paths inside analysis.json are relative to `outDir`.
161
- */
162
- export async function runAnalyze(recordingDir, windowSec, top, outDir = recordingDir) {
163
- const timeline = loadTimeline(recordingDir);
164
- const selected = selectWindows(timeline, windowSec, top);
165
- const sheetsDir = path.join(outDir, 'sheets');
166
- fs.mkdirSync(sheetsDir, { recursive: true });
167
- const windows = [];
168
- for (let i = 0; i < selected.length; i++) {
169
- const sheetRel = path.join('sheets', `window_${String(i).padStart(2, '0')}.png`);
170
- await renderContactSheet(recordingDir, timeline, selected[i], path.join(outDir, sheetRel));
171
- const { startFrame, endFrame, score, events } = selected[i];
172
- windows.push({ startFrame, endFrame, score, events, sheet: sheetRel });
173
- }
174
- const analysis = {
175
- version: 1,
176
- fps: timeline.recording.fps,
177
- frameCount: timeline.recording.frameCount,
178
- windows
179
- };
180
- const analysisPath = path.join(outDir, 'analysis.json');
181
- fs.writeFileSync(analysisPath, `${JSON.stringify(analysis, null, 2)}\n`);
182
- const forcedCount = selected.filter((w) => w.forced).length;
183
- out(`Wrote ${analysisPath} — ${windows.length} windows (${forcedCount} forced), sheets in ${sheetsDir}`);
184
- return analysis;
185
- }
186
- //# sourceMappingURL=analyze.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"analyze.js","sourceRoot":"","sources":["../../src/trailer/analyze.ts"],"names":[],"mappings":"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"}
@@ -1,50 +0,0 @@
1
- /**
2
- * The first cut nobody had to author.
3
- *
4
- * `bitmagic trailer make` on a fresh recording has an analysis (scored highlight windows with
5
- * contact sheets) and a target length, and must produce a watchable trailer with no shots file —
6
- * so the agent has something to react to and tune, rather than a blank page. This module turns
7
- * the analysis into a `ShotsFile` that follows the craft rules the make-trailer skill paid for:
8
- *
9
- * - shots run 1.5–3 s and VARY in length (a fixed pattern, so no two neighbours match);
10
- * - the body is chronological — a first cut should tell the session's story, not shuffle it;
11
- * - a forced window (`score`, `lap`, `goal`, `match-end`) is always in, and a match-end is
12
- * pinned last so the end card dims over the finish;
13
- * - every shot starts a little BEFORE its peak event, so the action lands inside the shot
14
- * instead of on its first frame;
15
- * - speed is 1.0 everywhere — slow motion reads as a low framerate on gameplay footage;
16
- * - text is an end card only.
17
- *
18
- * When the analysis is thin (few events logged) the remaining slots are filled with evenly spaced
19
- * shots across the recording, so every recording yields a trailer — a boring cut beats no cut, and
20
- * the sheets let the agent replace shots in the next turn.
21
- */
22
- import type { Analysis } from './timeline-types.js';
23
- import type { BeatAnalysis } from './music-beats.js';
24
- import type { ShotsFile } from './beat-cut.js';
25
- /** Seconds per body shot, cycled. All inside the craft doc's 1.5–3 s band, never two alike in a row. */
26
- export declare const SHOT_LENGTH_PATTERN_SEC: readonly number[];
27
- export declare const END_CARD_SEC = 2.5;
28
- /** How far before the peak event a shot begins, as a fraction of its length. */
29
- export declare const LEAD_IN_FRACTION = 0.6;
30
- /** Nothing in the first second: the game is still fading in from its menu. */
31
- export declare const SKIP_LEADING_SEC = 1;
32
- export declare const MIN_BEATS_PER_SHOT = 2;
33
- export declare const MAX_BEATS_PER_SHOT = 8;
34
- export interface AutoShotsInput {
35
- analysis: Analysis;
36
- recordingName: string;
37
- /** Whole trailer including the end card, in seconds. */
38
- targetSeconds: number;
39
- /** When present, shot lengths become whole beats and the end card too. */
40
- beats: BeatAnalysis | null;
41
- title: string;
42
- sub?: string;
43
- /** Draw the HUD replay over every shot (the render's --ui). */
44
- ui?: boolean;
45
- /** Frames the recorder failed to save (timeline.recording.missingFrames). Shots avoid them. */
46
- missingFrames?: readonly number[];
47
- }
48
- /** A shot may carry at most this share of missing frames before it reads as a freeze. */
49
- export declare const MAX_MISSING_FRACTION = 0.1;
50
- export declare function buildAutoShots(input: AutoShotsInput): ShotsFile;