@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,24 @@
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>;
24
+ //# sourceMappingURL=analyze.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../src/trailer/analyze.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAkB,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAiB9E,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAe1D,CAAC;AAEF,eAAO,MAAM,oBAAoB,MAAM,CAAC;AAExC,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAA0C,CAAC;AAE3F,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhD;AAyBD,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,cAAc,EAAE,CAoElG;AA2CD;;;;GAIG;AACH,wBAAsB,UAAU,CAC9B,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,MAAM,GAAE,MAAqB,GAC5B,OAAO,CAAC,QAAQ,CAAC,CA2BnB"}
@@ -0,0 +1,186 @@
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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyze.js","sourceRoot":"","sources":["../../src/trailer/analyze.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
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;
51
+ //# sourceMappingURL=auto-edl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-edl.d.ts","sourceRoot":"","sources":["../../src/trailer/auto-edl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAkB,MAAM,qBAAqB,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,OAAO,KAAK,EAAmB,SAAS,EAAE,MAAM,eAAe,CAAC;AAEhE,wGAAwG;AACxG,eAAO,MAAM,uBAAuB,EAAE,SAAS,MAAM,EAAgC,CAAC;AACtF,eAAO,MAAM,YAAY,MAAM,CAAC;AAChC,gFAAgF;AAChF,eAAO,MAAM,gBAAgB,MAAM,CAAC;AACpC,8EAA8E;AAC9E,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAClC,eAAO,MAAM,kBAAkB,IAAI,CAAC;AACpC,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAEpC,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,QAAQ,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,wDAAwD;IACxD,aAAa,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,+FAA+F;IAC/F,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAED,yFAAyF;AACzF,eAAO,MAAM,oBAAoB,MAAM,CAAC;AA8FxC,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,GAAG,SAAS,CA2J/D"}
@@ -0,0 +1,266 @@
1
+ import { FORCED_EVENT_TYPES, eventWeight } from './analyze.js';
2
+ /** Seconds per body shot, cycled. All inside the craft doc's 1.5–3 s band, never two alike in a row. */
3
+ export const SHOT_LENGTH_PATTERN_SEC = [2, 1.5, 3, 2, 2.5, 1.5, 3];
4
+ export const END_CARD_SEC = 2.5;
5
+ /** How far before the peak event a shot begins, as a fraction of its length. */
6
+ export const LEAD_IN_FRACTION = 0.6;
7
+ /** Nothing in the first second: the game is still fading in from its menu. */
8
+ export const SKIP_LEADING_SEC = 1;
9
+ export const MIN_BEATS_PER_SHOT = 2;
10
+ export const MAX_BEATS_PER_SHOT = 8;
11
+ /** A shot may carry at most this share of missing frames before it reads as a freeze. */
12
+ export const MAX_MISSING_FRACTION = 0.1;
13
+ /** Prefix sums over the missing set, so any range's missing count is two lookups. */
14
+ class MissingIndex {
15
+ constructor(missing, frameCount) {
16
+ const flags = new Uint8Array(frameCount);
17
+ for (const f of missing)
18
+ if (f >= 0 && f < frameCount)
19
+ flags[f] = 1;
20
+ this.prefix = new Uint32Array(frameCount + 1);
21
+ for (let i = 0; i < frameCount; i++)
22
+ this.prefix[i + 1] = this.prefix[i] + flags[i];
23
+ }
24
+ count(start, end) {
25
+ const a = Math.max(0, Math.min(start, this.prefix.length - 1));
26
+ const b = Math.max(a, Math.min(end, this.prefix.length - 1));
27
+ return this.prefix[b] - this.prefix[a];
28
+ }
29
+ fraction(start, end) {
30
+ return end > start ? this.count(start, end) / (end - start) : 0;
31
+ }
32
+ /**
33
+ * The last frame worth cutting up to: walk back from the end while the trailing second is
34
+ * mostly missing. A recorder that stalls at the stop leaves exactly that shape — a good body
35
+ * and a dead tail — and the auto cut should treat the tail as if it were never recorded.
36
+ */
37
+ usableEnd(frameCount, fps) {
38
+ let end = frameCount;
39
+ const window = Math.round(fps);
40
+ while (end > window && this.fraction(end - window, end) > MAX_MISSING_FRACTION)
41
+ end -= window;
42
+ return end;
43
+ }
44
+ }
45
+ /** `"f3121 explosion i0.9"` -> the event's frame, type and weighted intensity. */
46
+ function parseDigest(line) {
47
+ const match = /^f(\d+)\s+(\S+)\s+i([\d.]+)$/.exec(line.trim());
48
+ if (match === null)
49
+ return null;
50
+ const intensity = Number(match[3]);
51
+ return {
52
+ frame: Number(match[1]),
53
+ type: match[2],
54
+ weighted: eventWeight(match[2]) * (Number.isFinite(intensity) ? intensity : 1),
55
+ };
56
+ }
57
+ function toCandidate(window) {
58
+ let best = null;
59
+ let forced = false;
60
+ for (const line of window.events) {
61
+ const event = parseDigest(line);
62
+ if (event === null)
63
+ continue;
64
+ if (FORCED_EVENT_TYPES.includes(event.type))
65
+ forced = true;
66
+ if (best === null || event.weighted > best.weighted)
67
+ best = event;
68
+ }
69
+ const mid = Math.round((window.startFrame + window.endFrame) / 2);
70
+ return {
71
+ window,
72
+ peakFrame: best?.frame ?? mid,
73
+ peakType: best?.type ?? 'none',
74
+ forced,
75
+ };
76
+ }
77
+ /** A shot's length in whole beats when there is a grid — rounding the seconds, clamped to the band. */
78
+ function beatsFor(seconds, beats) {
79
+ const beatSec = 60 / beats.bpm;
80
+ return Math.min(MAX_BEATS_PER_SHOT, Math.max(MIN_BEATS_PER_SHOT, Math.round(seconds / beatSec)));
81
+ }
82
+ /** Frame count a shot of `seconds` will occupy, including the slack a beat-snapped cut can add. */
83
+ function framesFor(seconds, fps, beats) {
84
+ const slack = beats !== null ? 60 / beats.bpm : 0;
85
+ return Math.ceil((seconds + slack) * fps);
86
+ }
87
+ function describe(candidate, fps) {
88
+ const sec = candidate.peakFrame / fps;
89
+ const mm = Math.floor(sec / 60);
90
+ const ss = Math.floor(sec % 60).toString().padStart(2, '0');
91
+ const types = new Set();
92
+ for (const line of candidate.window.events) {
93
+ const event = parseDigest(line);
94
+ if (event !== null && event.weighted > 0)
95
+ types.add(event.type);
96
+ }
97
+ const what = types.size > 0 ? [...types].slice(0, 3).join(' + ') : 'gameplay';
98
+ return `${what} @${mm}:${ss}`;
99
+ }
100
+ export function buildAutoShots(input) {
101
+ const { analysis, beats } = input;
102
+ const fps = analysis.fps;
103
+ const lost = new MissingIndex(input.missingFrames ?? [], analysis.frameCount);
104
+ // Everything below cuts inside [0, frameCount): a dead tail is simply not part of the recording.
105
+ const frameCount = lost.usableEnd(analysis.frameCount, fps);
106
+ const skipFrames = Math.round(SKIP_LEADING_SEC * fps);
107
+ /**
108
+ * A start frame for a shot of `lenFrames` whose range is mostly present. An analysed moment is
109
+ * nudged EARLIER (the peak stays inside the shot); a filler is nudged LATER, so it never backs
110
+ * into the shot before it and repeats footage.
111
+ */
112
+ const placeShot = (ideal, lenFrames, direction) => {
113
+ const latest = Math.max(0, frameCount - lenFrames);
114
+ if (ideal > latest && direction === 'later')
115
+ return null;
116
+ for (let attempt = 0; attempt < 4; attempt++) {
117
+ const nudge = attempt * Math.round(lenFrames / 2);
118
+ const start = Math.max(0, Math.min(direction === 'earlier' ? ideal - nudge : ideal + nudge, latest));
119
+ if (lost.fraction(start, start + lenFrames) <= MAX_MISSING_FRACTION)
120
+ return start;
121
+ }
122
+ return null;
123
+ };
124
+ const bodyBudget = Math.max(SHOT_LENGTH_PATTERN_SEC[0], input.targetSeconds - END_CARD_SEC);
125
+ const meanLen = SHOT_LENGTH_PATTERN_SEC.reduce((a, b) => a + b, 0) / SHOT_LENGTH_PATTERN_SEC.length;
126
+ // Never ask for more footage than the recording holds: shots that overlap repeat the same
127
+ // seconds twice, which reads worse than a shorter trailer. A short recording gets a short cut.
128
+ const usableSec = Math.max(0, frameCount - skipFrames) / fps;
129
+ const slots = Math.max(1, Math.min(Math.floor(bodyBudget / meanLen), Math.floor(usableSec / meanLen)));
130
+ // Candidates: every analysed window with its peak inside the usable range; forced ones first,
131
+ // then by score, so the k best are taken but a forced window is never dropped for a louder one.
132
+ const candidates = analysis.windows
133
+ .map(toCandidate)
134
+ .filter((c) => c.peakFrame >= skipFrames && c.peakFrame < frameCount)
135
+ .filter((c) => c.peakType !== 'state');
136
+ const ranked = [...candidates].sort((a, b) => {
137
+ if (a.forced !== b.forced)
138
+ return a.forced ? -1 : 1;
139
+ return b.window.score - a.window.score;
140
+ });
141
+ const chosen = ranked.slice(0, Math.min(slots, ranked.length));
142
+ // Fill: evenly spaced across the recording, skipping spots an ANALYSED window already covers.
143
+ // Only the analysed picks count as cover — a filler must never shadow the next filler, or a
144
+ // short recording (fillers closer together than the radius) loses every second slot.
145
+ const missing = slots - chosen.length;
146
+ if (missing > 0) {
147
+ const analysed = [...chosen];
148
+ const usable = frameCount - skipFrames;
149
+ const spacing = usable / missing;
150
+ const radius = Math.min(3 * fps, spacing / 2);
151
+ for (let i = 0; i < missing; i++) {
152
+ const frame = skipFrames + Math.round((i + 0.5) * spacing);
153
+ const covered = analysed.some((c) => Math.abs(c.peakFrame - frame) < radius);
154
+ const filler = {
155
+ window: { startFrame: frame, endFrame: frame, score: 0, events: [], sheet: '' },
156
+ peakFrame: frame,
157
+ peakType: 'none',
158
+ forced: false,
159
+ };
160
+ if (!covered)
161
+ chosen.push(filler);
162
+ }
163
+ }
164
+ // Chronological, with a match-end pinned last.
165
+ chosen.sort((a, b) => a.peakFrame - b.peakFrame);
166
+ const endIndex = chosen.findIndex((c) => c.window.events.some((e) => parseDigest(e)?.type === 'match-end'));
167
+ if (endIndex >= 0 && endIndex !== chosen.length - 1) {
168
+ const [end] = chosen.splice(endIndex, 1);
169
+ chosen.push(end);
170
+ }
171
+ const shots = [];
172
+ let prevEnd = 0;
173
+ chosen.forEach((candidate, i) => {
174
+ const seconds = SHOT_LENGTH_PATTERN_SEC[i % SHOT_LENGTH_PATTERN_SEC.length];
175
+ const lenFrames = framesFor(seconds, fps, beats);
176
+ const isFiller = candidate.window.score === 0 && candidate.window.events.length === 0;
177
+ const lead = Math.round(LEAD_IN_FRACTION * seconds * fps);
178
+ // A filler starts no earlier than the previous shot ended: the same seconds twice is the one
179
+ // thing worse than a shorter trailer. A real moment keeps its peak in frame instead.
180
+ const ideal = isFiller ? Math.max(candidate.peakFrame - lead, prevEnd) : candidate.peakFrame - lead;
181
+ const start = placeShot(ideal, lenFrames, isFiller ? 'later' : 'earlier');
182
+ if (start === null)
183
+ return; // no clean footage near this moment — better one shot fewer than a freeze
184
+ const shot = { in: start, label: describe(candidate, fps) };
185
+ if (beats !== null)
186
+ shot.beats = beatsFor(seconds, beats);
187
+ else
188
+ shot.seconds = seconds;
189
+ if (input.ui === true)
190
+ shot.ui = true;
191
+ shots.push(shot);
192
+ prevEnd = start + lenFrames;
193
+ });
194
+ // Fillers dropped above (covered by an analysed moment, or with no clean footage where they
195
+ // fell) leave the cut short. Put them back where the footage is idle: the largest gap between
196
+ // shots, again and again, until the slots are full or no gap holds a shot.
197
+ const lenOf = (shot) => framesFor(shot.seconds ?? (shot.beats ?? 0) * (beats !== null ? 60 / beats.bpm : 0), fps, beats);
198
+ const pinnedEnd = endIndex >= 0 && shots.length > 0 ? shots[shots.length - 1] : null;
199
+ for (let guard = 0; shots.length < slots && guard < slots; guard++) {
200
+ const ordered = shots.filter((sh) => sh !== pinnedEnd).sort((a, b) => a.in - b.in);
201
+ const limit = pinnedEnd !== null ? pinnedEnd.in : frameCount;
202
+ let best = null;
203
+ let cursor = skipFrames;
204
+ for (const sh of [...ordered, null]) {
205
+ const gapEnd = sh === null ? limit : sh.in;
206
+ if (gapEnd - cursor > (best?.size ?? 0))
207
+ best = { start: cursor, size: gapEnd - cursor };
208
+ if (sh !== null)
209
+ cursor = Math.max(cursor, sh.in + lenOf(sh));
210
+ }
211
+ if (best === null)
212
+ break;
213
+ // The pattern's next length when it fits, else the longest pattern length that does — a 2 s
214
+ // gap still deserves a 1.5 s shot rather than nothing.
215
+ const preferred = SHOT_LENGTH_PATTERN_SEC[shots.length % SHOT_LENGTH_PATTERN_SEC.length];
216
+ const fitting = [preferred, ...[...SHOT_LENGTH_PATTERN_SEC].sort((a, b) => b - a)]
217
+ .find((sec) => framesFor(sec, fps, beats) <= best.size);
218
+ if (fitting === undefined)
219
+ break;
220
+ const seconds = fitting;
221
+ const lenFrames = framesFor(seconds, fps, beats);
222
+ const start = placeShot(best.start + Math.round((best.size - lenFrames) / 2), lenFrames, 'later');
223
+ if (start === null || start + lenFrames > best.start + best.size)
224
+ break;
225
+ const label = describe({ window: { startFrame: start, endFrame: start, score: 0, events: [], sheet: '' }, peakFrame: start + Math.round(lenFrames / 2), peakType: 'none', forced: false }, fps);
226
+ const shot = { in: start, label };
227
+ if (beats !== null)
228
+ shot.beats = beatsFor(seconds, beats);
229
+ else
230
+ shot.seconds = seconds;
231
+ if (input.ui === true)
232
+ shot.ui = true;
233
+ shots.push(shot);
234
+ }
235
+ // Chronological again after the gap fills, keeping a pinned match-end last.
236
+ shots.sort((a, b) => (a === pinnedEnd ? 1 : b === pinnedEnd ? -1 : a.in - b.in));
237
+ if (shots.length === 0) {
238
+ // Nothing clean anywhere: hold the opening, which at least exists.
239
+ const shot = { in: Math.min(skipFrames, Math.max(0, frameCount - 1)), label: 'gameplay @0:01' };
240
+ if (beats !== null)
241
+ shot.beats = beatsFor(END_CARD_SEC, beats);
242
+ else
243
+ shot.seconds = END_CARD_SEC;
244
+ shots.push(shot);
245
+ }
246
+ // The end card sits over the last shot, so that shot must be long enough to carry it.
247
+ const last = shots[shots.length - 1];
248
+ if (beats !== null)
249
+ last.beats = Math.max(last.beats ?? 0, beatsFor(END_CARD_SEC, beats));
250
+ else
251
+ last.seconds = Math.max(last.seconds ?? 0, END_CARD_SEC);
252
+ const lastLen = framesFor(last.seconds ?? END_CARD_SEC, fps, beats);
253
+ last.in = placeShot(Math.min(last.in, frameCount - lastLen), lastLen, 'earlier') ?? Math.max(0, Math.min(last.in, frameCount - lastLen));
254
+ const title = { at: 'last-clip', text: input.title.toUpperCase(), style: 'card' };
255
+ if (input.sub !== undefined)
256
+ title.sub = input.sub;
257
+ return {
258
+ version: 1,
259
+ recording: input.recordingName,
260
+ notes: 'First cut written by `bitmagic trailer make`. Edit freely: reorder, swap "in" frames for ' +
261
+ 'better moments from analysis.json, change beats/seconds, add titles. Re-run make to render.',
262
+ shots,
263
+ titles: [title],
264
+ };
265
+ }
266
+ //# sourceMappingURL=auto-edl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-edl.js","sourceRoot":"","sources":["../../src/trailer/auto-edl.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ import type { BeatAnalysis } from './music-beats.js';
2
+ import type { TitleCue } from './titles.js';
3
+ import type { Edl } from './timeline-types.js';
4
+ export interface Shot {
5
+ /** Source frame the shot starts on. */
6
+ in: number;
7
+ /** Hold for this many beats (needs a beat grid). */
8
+ beats?: number;
9
+ /** Hold for this many seconds (used when there is no beat grid, or the shot has no `beats`). */
10
+ seconds?: number;
11
+ label: string;
12
+ /** Playback speed; the craft rule is 1.0 — slow-mo reads as a low framerate. */
13
+ speed?: number;
14
+ /** Draw (or suppress) the HUD replay over this shot; menus want `false`. */
15
+ ui?: boolean;
16
+ transition?: 'cut' | 'crossfade';
17
+ transitionDuration?: number;
18
+ }
19
+ /** A title whose `at` may be the literal `"last-clip"`; `dur` is then the last clip's length. */
20
+ export type ShotTitle = Omit<TitleCue, 'at' | 'dur'> & {
21
+ at: number | 'last-clip';
22
+ dur?: number;
23
+ };
24
+ export interface ShotsFile {
25
+ version: 1;
26
+ /** Name of the recording these frame indices were chosen from. */
27
+ recording?: string;
28
+ /** Free-form notes the author keeps with the cut (never read by the tool). */
29
+ notes?: string;
30
+ shots: Shot[];
31
+ titles?: ShotTitle[];
32
+ }
33
+ export interface CutOptions {
34
+ /** Frames in the recording; a shot that would run past the end is shifted earlier and reported. */
35
+ frameCount?: number;
36
+ }
37
+ export interface CutResult {
38
+ edl: Edl;
39
+ /** Beats consumed from the downbeat (0 without a grid). */
40
+ beatsUsed: number;
41
+ totalSec: number;
42
+ /** Largest distance between a cut and its beat target, in ms (0 without a grid). */
43
+ worstCutErrorMs: number;
44
+ /** The first shot that did not fit in the track, when the grid ran out. */
45
+ truncatedAt?: string;
46
+ }
47
+ /** Parse a shots document: either a bare array of shots or `{ version, shots, titles? }`. */
48
+ export declare function parseShots(raw: unknown, source: string): ShotsFile;
49
+ export declare function loadShots(filePath: string): ShotsFile;
50
+ /**
51
+ * Turn shots into frame-exact clips. With `beats` every cut lands on the grid counted from the
52
+ * downbeat; without, each shot's `seconds` decides its length. Returns the EDL plus the numbers
53
+ * the command prints so the author can judge the fit.
54
+ */
55
+ export declare function cutShots(shots: ShotsFile, beats: BeatAnalysis | null, fps: number, options?: CutOptions): CutResult;
56
+ /** The one-line summary `bitmagic trailer cut` prints, mirroring the Python script's. */
57
+ export declare function describeCut(result: CutResult, beats: BeatAnalysis | null): string;
58
+ //# sourceMappingURL=beat-cut.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"beat-cut.d.ts","sourceRoot":"","sources":["../../src/trailer/beat-cut.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,GAAG,EAAW,MAAM,qBAAqB,CAAC;AAGxD,MAAM,WAAW,IAAI;IACnB,uCAAuC;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gGAAgG;IAChG,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,UAAU,CAAC,EAAE,KAAK,GAAG,WAAW,CAAC;IACjC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,iGAAiG;AACjG,MAAM,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,EAAE,IAAI,GAAG,KAAK,CAAC,GAAG;IAAE,EAAE,EAAE,MAAM,GAAG,WAAW,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAElG,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,CAAC,CAAC;IACX,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,mGAAmG;IACnG,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,GAAG,CAAC;IACT,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,oFAAoF;IACpF,eAAe,EAAE,MAAM,CAAC;IACxB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AA+DD,6FAA6F;AAC7F,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,CAiBlE;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CASrD;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,SAAS,EAChB,KAAK,EAAE,YAAY,GAAG,IAAI,EAC1B,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,UAAe,GACvB,SAAS,CA0FX;AAED,yFAAyF;AACzF,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI,GAAG,MAAM,CASjF"}