streamer-node 1.0.31 → 1.0.38

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 (123) hide show
  1. package/dist/package.json +6 -2
  2. package/dist/scripts/README.md +670 -0
  3. package/dist/scripts/install-streamer-node.sh +117 -0
  4. package/dist/scripts/reframe_ball.py +354 -0
  5. package/dist/src/application/interfaces/StartStreamUseCase.types.d.ts +3 -1
  6. package/dist/src/application/interfaces/StartStreamUseCase.types.d.ts.map +1 -1
  7. package/dist/src/application/interfaces/StartStreamUseCase.types.js.map +1 -1
  8. package/dist/src/application/services/HttpClient.d.ts +21 -0
  9. package/dist/src/application/services/HttpClient.d.ts.map +1 -1
  10. package/dist/src/application/services/HttpClient.js +37 -5
  11. package/dist/src/application/services/HttpClient.js.map +1 -1
  12. package/dist/src/application/services/StreamManagerService.d.ts.map +1 -1
  13. package/dist/src/application/services/StreamManagerService.js +2 -0
  14. package/dist/src/application/services/StreamManagerService.js.map +1 -1
  15. package/dist/src/application/use-cases/CaptureHighlightUseCase.d.ts +52 -0
  16. package/dist/src/application/use-cases/CaptureHighlightUseCase.d.ts.map +1 -0
  17. package/dist/src/application/use-cases/CaptureHighlightUseCase.js +169 -0
  18. package/dist/src/application/use-cases/CaptureHighlightUseCase.js.map +1 -0
  19. package/dist/src/application/use-cases/StartStreamUseCase.d.ts +9 -1
  20. package/dist/src/application/use-cases/StartStreamUseCase.d.ts.map +1 -1
  21. package/dist/src/application/use-cases/StartStreamUseCase.js +125 -2
  22. package/dist/src/application/use-cases/StartStreamUseCase.js.map +1 -1
  23. package/dist/src/application/use-cases/StopStreamUseCase.d.ts +5 -1
  24. package/dist/src/application/use-cases/StopStreamUseCase.d.ts.map +1 -1
  25. package/dist/src/application/use-cases/StopStreamUseCase.js +7 -1
  26. package/dist/src/application/use-cases/StopStreamUseCase.js.map +1 -1
  27. package/dist/src/domain/events/StreamEvent.d.ts +5 -0
  28. package/dist/src/domain/events/StreamEvent.d.ts.map +1 -1
  29. package/dist/src/domain/services/BallReframer.d.ts +35 -0
  30. package/dist/src/domain/services/BallReframer.d.ts.map +1 -0
  31. package/dist/src/domain/services/BallReframer.js +3 -0
  32. package/dist/src/domain/services/BallReframer.js.map +1 -0
  33. package/dist/src/domain/services/FFmpegService.d.ts +6 -2
  34. package/dist/src/domain/services/FFmpegService.d.ts.map +1 -1
  35. package/dist/src/domain/services/HighlightSignalSource.d.ts +113 -0
  36. package/dist/src/domain/services/HighlightSignalSource.d.ts.map +1 -0
  37. package/dist/src/domain/services/HighlightSignalSource.js +3 -0
  38. package/dist/src/domain/services/HighlightSignalSource.js.map +1 -0
  39. package/dist/src/domain/services/SSEService.d.ts +1 -0
  40. package/dist/src/domain/services/SSEService.d.ts.map +1 -1
  41. package/dist/src/infrastructure/config/Config.d.ts +32 -0
  42. package/dist/src/infrastructure/config/Config.d.ts.map +1 -1
  43. package/dist/src/infrastructure/config/Config.js +60 -0
  44. package/dist/src/infrastructure/config/Config.js.map +1 -1
  45. package/dist/src/infrastructure/listeners/SupabaseListener.d.ts +5 -0
  46. package/dist/src/infrastructure/listeners/SupabaseListener.d.ts.map +1 -1
  47. package/dist/src/infrastructure/listeners/SupabaseListener.js +43 -5
  48. package/dist/src/infrastructure/listeners/SupabaseListener.js.map +1 -1
  49. package/dist/src/infrastructure/logging/LogShippingService.d.ts +3 -1
  50. package/dist/src/infrastructure/logging/LogShippingService.d.ts.map +1 -1
  51. package/dist/src/infrastructure/logging/LogShippingService.js +13 -4
  52. package/dist/src/infrastructure/logging/LogShippingService.js.map +1 -1
  53. package/dist/src/infrastructure/logging/RemoteLogger.d.ts +1 -0
  54. package/dist/src/infrastructure/logging/RemoteLogger.d.ts.map +1 -1
  55. package/dist/src/infrastructure/logging/RemoteLogger.js +1 -1
  56. package/dist/src/infrastructure/logging/RemoteLogger.js.map +1 -1
  57. package/dist/src/infrastructure/services/AdDownloaderService.d.ts +85 -0
  58. package/dist/src/infrastructure/services/AdDownloaderService.d.ts.map +1 -0
  59. package/dist/src/infrastructure/services/AdDownloaderService.js +530 -0
  60. package/dist/src/infrastructure/services/AdDownloaderService.js.map +1 -0
  61. package/dist/src/infrastructure/services/AdRotator.d.ts +63 -0
  62. package/dist/src/infrastructure/services/AdRotator.d.ts.map +1 -0
  63. package/dist/src/infrastructure/services/AdRotator.js +223 -0
  64. package/dist/src/infrastructure/services/AdRotator.js.map +1 -0
  65. package/dist/src/infrastructure/services/HighlightBufferManager.d.ts +65 -0
  66. package/dist/src/infrastructure/services/HighlightBufferManager.d.ts.map +1 -0
  67. package/dist/src/infrastructure/services/HighlightBufferManager.js +203 -0
  68. package/dist/src/infrastructure/services/HighlightBufferManager.js.map +1 -0
  69. package/dist/src/infrastructure/services/HighlightBufferRegistry.d.ts +23 -0
  70. package/dist/src/infrastructure/services/HighlightBufferRegistry.d.ts.map +1 -0
  71. package/dist/src/infrastructure/services/HighlightBufferRegistry.js +44 -0
  72. package/dist/src/infrastructure/services/HighlightBufferRegistry.js.map +1 -0
  73. package/dist/src/infrastructure/services/HighlightExtractorService.d.ts +65 -0
  74. package/dist/src/infrastructure/services/HighlightExtractorService.d.ts.map +1 -0
  75. package/dist/src/infrastructure/services/HighlightExtractorService.js +264 -0
  76. package/dist/src/infrastructure/services/HighlightExtractorService.js.map +1 -0
  77. package/dist/src/infrastructure/services/HighlightRendererService.d.ts +38 -0
  78. package/dist/src/infrastructure/services/HighlightRendererService.d.ts.map +1 -0
  79. package/dist/src/infrastructure/services/HighlightRendererService.js +182 -0
  80. package/dist/src/infrastructure/services/HighlightRendererService.js.map +1 -0
  81. package/dist/src/infrastructure/services/NodeFFmpegService.d.ts +6 -4
  82. package/dist/src/infrastructure/services/NodeFFmpegService.d.ts.map +1 -1
  83. package/dist/src/infrastructure/services/NodeFFmpegService.js +154 -32
  84. package/dist/src/infrastructure/services/NodeFFmpegService.js.map +1 -1
  85. package/dist/src/infrastructure/services/NodeSSEService.d.ts +8 -0
  86. package/dist/src/infrastructure/services/NodeSSEService.d.ts.map +1 -1
  87. package/dist/src/infrastructure/services/NodeSSEService.js +71 -27
  88. package/dist/src/infrastructure/services/NodeSSEService.js.map +1 -1
  89. package/dist/src/infrastructure/services/NullBallReframer.d.ts +11 -0
  90. package/dist/src/infrastructure/services/NullBallReframer.d.ts.map +1 -0
  91. package/dist/src/infrastructure/services/NullBallReframer.js +16 -0
  92. package/dist/src/infrastructure/services/NullBallReframer.js.map +1 -0
  93. package/dist/src/infrastructure/services/PythonBallReframer.d.ts +35 -0
  94. package/dist/src/infrastructure/services/PythonBallReframer.d.ts.map +1 -0
  95. package/dist/src/infrastructure/services/PythonBallReframer.js +238 -0
  96. package/dist/src/infrastructure/services/PythonBallReframer.js.map +1 -0
  97. package/dist/src/infrastructure/services/SerialHighlightListener.d.ts +45 -0
  98. package/dist/src/infrastructure/services/SerialHighlightListener.d.ts.map +1 -0
  99. package/dist/src/infrastructure/services/SerialHighlightListener.js +440 -0
  100. package/dist/src/infrastructure/services/SerialHighlightListener.js.map +1 -0
  101. package/dist/src/infrastructure/services/YouTubeUploadService.d.ts +36 -0
  102. package/dist/src/infrastructure/services/YouTubeUploadService.d.ts.map +1 -0
  103. package/dist/src/infrastructure/services/YouTubeUploadService.js +402 -0
  104. package/dist/src/infrastructure/services/YouTubeUploadService.js.map +1 -0
  105. package/dist/src/infrastructure/utils/ResourceSampler.d.ts +47 -0
  106. package/dist/src/infrastructure/utils/ResourceSampler.d.ts.map +1 -0
  107. package/dist/src/infrastructure/utils/ResourceSampler.js +102 -0
  108. package/dist/src/infrastructure/utils/ResourceSampler.js.map +1 -0
  109. package/dist/src/infrastructure/utils/lowPriority.d.ts +29 -0
  110. package/dist/src/infrastructure/utils/lowPriority.d.ts.map +1 -0
  111. package/dist/src/infrastructure/utils/lowPriority.js +31 -0
  112. package/dist/src/infrastructure/utils/lowPriority.js.map +1 -0
  113. package/dist/src/infrastructure/utils/paths.d.ts +27 -0
  114. package/dist/src/infrastructure/utils/paths.d.ts.map +1 -0
  115. package/dist/src/infrastructure/utils/paths.js +69 -0
  116. package/dist/src/infrastructure/utils/paths.js.map +1 -0
  117. package/dist/src/infrastructure/utils/spawnToFile.d.ts +20 -0
  118. package/dist/src/infrastructure/utils/spawnToFile.d.ts.map +1 -0
  119. package/dist/src/infrastructure/utils/spawnToFile.js +98 -0
  120. package/dist/src/infrastructure/utils/spawnToFile.js.map +1 -0
  121. package/dist/src/main.js +228 -2
  122. package/dist/src/main.js.map +1 -1
  123. package/package.json +6 -2
@@ -0,0 +1,264 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.HighlightExtractorService = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const child_process_1 = require("child_process");
40
+ const paths_1 = require("../utils/paths");
41
+ const spawnToFile_1 = require("../utils/spawnToFile");
42
+ const lowPriority_1 = require("../utils/lowPriority");
43
+ /**
44
+ * Cuts a highlight clip out of the rolling buffer's segment files.
45
+ *
46
+ * ── IN SIMPLE WORDS ──
47
+ * Given the buffered video chunks that overlap a time window, this stitches
48
+ * them together and trims to exactly the window, producing one clip file. It
49
+ * runs a one-shot ffmpeg, completely separate from the live stream.
50
+ *
51
+ * ── BUSINESS RULES ──
52
+ * - The clip spans [windowStart, windowEnd]; the trim offset is measured from
53
+ * the FIRST overlapping segment's start (which sits at or before windowStart,
54
+ * guaranteed by the buffer manifest's leading-edge check).
55
+ * - The extracted clip is RAW (no logos/crop). Ball-tracking reframe + logo
56
+ * overlay (Phase 5) run on this clip afterwards; here we only get the window
57
+ * right.
58
+ *
59
+ * ── WHY IT'S BUILT THIS WAY (change at your peril) ──
60
+ * - Output-side `-ss`/`-t` (after `-i`) + a real re-encode gives a
61
+ * frame-accurate cut. Stream-copy would snap to the nearest keyframe (up to
62
+ * `bufferSegmentSec` off) — unacceptable for a tight highlight window.
63
+ * - temp-file + atomic rename: a killed/failed ffmpeg never leaves a corrupt
64
+ * clip at the final path. Fail-soft: any error returns null (logged), never
65
+ * throws into the caller — a failed highlight must not disrupt anything.
66
+ *
67
+ * - Segments are HARDLINKED into a private dir before ffmpeg reads them. The
68
+ * retention sweeper runs every 5s and this re-encode takes ~30s, so without
69
+ * pinning the sweeper deletes segments ffmpeg hasn't reached yet — and
70
+ * ffmpeg then TRUNCATES THE OUTPUT AND EXITS 0. That silent success turned a
71
+ * 30s window into a 9.12s clip while every log line reported normal.
72
+ *
73
+ * ── DO NOT ──
74
+ * - Do NOT assume success — callers must handle a null return.
75
+ * - Do NOT put the buffer dir and the output dir on different filesystems.
76
+ * Hardlinks cannot cross a filesystem boundary, so pinning silently degrades
77
+ * to the racy behaviour above (it is logged, but the reels get short again).
78
+ * - Do NOT treat a zero exit from ffmpeg as "the clip is complete" — compare
79
+ * the probed duration against the requested window, which is what the
80
+ * shortfall warning below does.
81
+ */
82
+ class HighlightExtractorService {
83
+ constructor(outputDir, logger) {
84
+ this.outputDir = outputDir;
85
+ this.logger = logger;
86
+ // Generous: runs at low priority (see withLowPriority), so it may take
87
+ // longer under load — better slow than killed. Live stream is protected.
88
+ this.extractTimeoutMs = 120000;
89
+ }
90
+ /**
91
+ * Concat the overlapping segments and trim to [windowStartMs, windowEndMs].
92
+ * Returns the written clip path, or null on any failure.
93
+ */
94
+ async extractWindow(segments, windowStartMs, windowEndMs, courtId) {
95
+ if (segments.length === 0)
96
+ return null;
97
+ const stamp = windowStartMs; // stable, sortable id for the clip
98
+ const courtOut = path.join(this.outputDir, (0, paths_1.safeSegment)(courtId));
99
+ const listPath = path.join(courtOut, `.concat-${stamp}.txt`);
100
+ const pinDir = path.join(courtOut, `.pin-${stamp}`);
101
+ const dest = path.join(courtOut, `highlight-${stamp}.mp4`);
102
+ try {
103
+ (0, paths_1.ensureDirSync)(courtOut);
104
+ // PIN THE SEGMENTS BEFORE READING THEM.
105
+ //
106
+ // The retention sweeper deletes buffer segments on a 5s timer, and this
107
+ // re-encode takes ~30s at nice 19 on a loaded box. Without pinning, the
108
+ // sweeper unlinks segments this ffmpeg has NOT REACHED YET, ffmpeg hits
109
+ // "Error during demuxing: No such file or directory" — and then EXITS 0
110
+ // with a truncated file. That silent success is what made this so hard
111
+ // to see: a 30s window came out at 9.12s and every log line said fine.
112
+ //
113
+ // A hardlink is a second name for the same inode. Once we hold one, the
114
+ // sweeper's unlink only removes ITS name; the data stays alive until we
115
+ // drop ours in the finally below. No locks, no coordination with the
116
+ // sweeper, and no copy — this is a directory entry, not the video.
117
+ const pinned = this.pinSegments(segments, pinDir, courtId);
118
+ if (pinned.length === 0)
119
+ return null;
120
+ // concat demuxer list. Entries MUST be absolute: ffmpeg resolves relative
121
+ // `file` paths against the LIST FILE's directory (not CWD), so with the
122
+ // default relative bufferDir the segments would be looked up under the
123
+ // output dir and never found. path.resolve makes this unambiguous.
124
+ // Escape single quotes per ffmpeg's list syntax.
125
+ const listBody = pinned
126
+ .map((p) => `file '${path.resolve(p).replace(/'/g, "'\\''")}'`)
127
+ .join("\n");
128
+ fs.writeFileSync(listPath, `${listBody}\n`);
129
+ const offsetSec = Math.max(0, (windowStartMs - segments[0].startMs) / 1000);
130
+ const durationSec = Math.max(0, (windowEndMs - windowStartMs) / 1000);
131
+ if (durationSec <= 0) {
132
+ this.logger.warn("Highlight extraction skipped: non-positive window", {
133
+ courtId,
134
+ windowStartMs,
135
+ windowEndMs,
136
+ });
137
+ return null;
138
+ }
139
+ // Low CPU priority so this re-encode can't starve the live ffmpeg.
140
+ const { command, args } = (0, lowPriority_1.withLowPriority)("ffmpeg", [
141
+ "-y",
142
+ "-f", "concat",
143
+ "-safe", "0",
144
+ "-i", listPath,
145
+ // output-side seek/duration → frame-accurate cut of the window
146
+ "-ss", String(offsetSec),
147
+ "-t", String(durationSec),
148
+ "-c:v", "libx264",
149
+ "-preset", "veryfast",
150
+ "-an",
151
+ ]);
152
+ await (0, spawnToFile_1.spawnToFile)(command, args, dest, this.extractTimeoutMs);
153
+ // Log the ACTUAL duration alongside the requested one. Logging only the
154
+ // request hid a real bug: the buffer's segments carry the camera
155
+ // sub-stream's timing, so a 30s window can extract to a far shorter clip
156
+ // and nothing in the logs said so. A mismatch here means the reel is
157
+ // time-compressed and every downstream step inherits it.
158
+ const actualSec = await this.probeDurationSec(dest);
159
+ const shortfall = actualSec !== null && durationSec > 0
160
+ ? 1 - actualSec / durationSec
161
+ : 0;
162
+ this.logger.info("Highlight clip extracted", {
163
+ courtId,
164
+ dest,
165
+ segments: segments.length,
166
+ offsetSec,
167
+ requestedSec: durationSec,
168
+ actualSec: actualSec !== null ? +actualSec.toFixed(2) : undefined,
169
+ });
170
+ // >10% short is not rounding — it means duration is being lost upstream.
171
+ if (shortfall > 0.1) {
172
+ this.logger.warn("Highlight clip is much shorter than the requested window — the reel will look sped up", {
173
+ courtId,
174
+ requestedSec: durationSec,
175
+ actualSec: actualSec !== null ? +actualSec.toFixed(2) : undefined,
176
+ lostPct: Math.round(shortfall * 100),
177
+ hint: "segments went missing mid-read (ffmpeg truncates and still exits 0) — check the pinning warning above",
178
+ });
179
+ }
180
+ return dest;
181
+ }
182
+ catch (error) {
183
+ this.logger.warn("Highlight extraction failed", {
184
+ courtId,
185
+ error: error instanceof Error ? error.message : String(error),
186
+ });
187
+ return null;
188
+ }
189
+ finally {
190
+ fs.unlink(listPath, () => { }); // best-effort cleanup of the list file
191
+ // Drop our hardlinks. For segments the sweeper already unlinked, this is
192
+ // the last reference and the disk space is reclaimed here.
193
+ fs.rm(pinDir, { recursive: true, force: true }, () => { });
194
+ }
195
+ }
196
+ /**
197
+ * Hardlink each segment into `pinDir` so the retention sweeper cannot delete
198
+ * the data out from under ffmpeg. Returns the pinned paths, in order.
199
+ *
200
+ * A segment that is ALREADY gone is skipped rather than passed through: the
201
+ * concat demuxer aborts the whole remaining list on a missing file, so
202
+ * skipping one costs a small jump while passing it through costs everything
203
+ * after it.
204
+ */
205
+ pinSegments(segments, pinDir, courtId) {
206
+ (0, paths_1.ensureDirSync)(pinDir);
207
+ const pinned = [];
208
+ let missing = 0;
209
+ let unlinkable = 0;
210
+ for (const seg of segments) {
211
+ const src = path.resolve(seg.path);
212
+ const dst = path.join(pinDir, path.basename(src));
213
+ try {
214
+ fs.linkSync(src, dst);
215
+ pinned.push(dst);
216
+ }
217
+ catch (error) {
218
+ const code = error.code;
219
+ if (code === "ENOENT") {
220
+ missing++; // swept between the manifest query and now
221
+ continue;
222
+ }
223
+ // EXDEV (different filesystem) or EPERM: we cannot pin, but the file is
224
+ // there right now. Use it directly and accept the original race rather
225
+ // than dropping footage we can still read.
226
+ unlinkable++;
227
+ pinned.push(src);
228
+ }
229
+ }
230
+ if (missing > 0 || unlinkable > 0) {
231
+ this.logger.warn("Some highlight segments could not be pinned", {
232
+ courtId,
233
+ missing,
234
+ unlinkable,
235
+ pinned: pinned.length,
236
+ of: segments.length,
237
+ hint: unlinkable > 0
238
+ ? "buffer and output dirs are on different filesystems — put them on one volume so segments can be hardlinked"
239
+ : "segments were swept before pinning; raise HIGHLIGHT_BUFFER_RETENTION_SEC",
240
+ });
241
+ }
242
+ return pinned;
243
+ }
244
+ /** Actual duration of `file` in seconds, or null if it can't be probed. */
245
+ probeDurationSec(file) {
246
+ return new Promise((resolve) => {
247
+ const proc = (0, child_process_1.spawn)("ffprobe", [
248
+ "-v", "error",
249
+ "-show_entries", "format=duration",
250
+ "-of", "default=nw=1:nk=1",
251
+ file,
252
+ ]);
253
+ let out = "";
254
+ proc.stdout?.on("data", (d) => (out += d.toString()));
255
+ proc.on("exit", (code) => {
256
+ const dur = parseFloat(out.trim());
257
+ resolve(code === 0 && Number.isFinite(dur) && dur > 0 ? dur : null);
258
+ });
259
+ proc.on("error", () => resolve(null));
260
+ });
261
+ }
262
+ }
263
+ exports.HighlightExtractorService = HighlightExtractorService;
264
+ //# sourceMappingURL=HighlightExtractorService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HighlightExtractorService.js","sourceRoot":"","sources":["../../../../src/infrastructure/services/HighlightExtractorService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import { Logger } from "../../application/interfaces/Logger";
2
+ /**
3
+ * Overlays the DropShot + client logos onto a highlight clip, producing the
4
+ * final reel. Works on either a full-frame (16:9) clip or a reframed (e.g. 9:16)
5
+ * one — logo sizes/positions are derived from the actual canvas dimensions.
6
+ *
7
+ * ── IN SIMPLE WORDS ──
8
+ * Stamps the two logos onto the highlight clip and writes the finished reel.
9
+ * Logos are sized relative to the video so they look right whether the clip is
10
+ * wide or vertical.
11
+ *
12
+ * ── WHY IT'S BUILT THIS WAY (change at your peril) ──
13
+ * - It probes the clip's real dimensions and sizes logos from them, so the same
14
+ * code brands a 1920x1080 full-frame clip and a 608x1080 reel correctly.
15
+ * - Only logos that actually exist on disk are overlaid; a missing client logo
16
+ * yields a clip with just the DropShot mark rather than a hard failure.
17
+ * - temp-file + atomic rename; fail-soft (returns null, logged) so a render
18
+ * problem never loses the underlying clip — the caller keeps the source.
19
+ *
20
+ * ── DO NOT ──
21
+ * - Do NOT assume both logos exist; check each.
22
+ * - Do NOT assume success — callers must handle a null return (fall back to the
23
+ * un-branded source clip).
24
+ */
25
+ export declare class HighlightRendererService {
26
+ private readonly dsLogoPath;
27
+ private readonly clientLogoPath;
28
+ private readonly logger;
29
+ private readonly renderTimeoutMs;
30
+ constructor(dsLogoPath: string, clientLogoPath: string, logger: Logger);
31
+ /**
32
+ * Overlay logos from `sourceClip` into `outPath`. Returns outPath on success,
33
+ * or null on any failure (caller should fall back to the source clip).
34
+ */
35
+ render(sourceClip: string, outPath: string, courtId: string): Promise<string | null>;
36
+ private probeDimensions;
37
+ }
38
+ //# sourceMappingURL=HighlightRendererService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HighlightRendererService.d.ts","sourceRoot":"","sources":["../../../../src/infrastructure/services/HighlightRendererService.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAC;AAI7D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,wBAAwB;IAMjC,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM;IALzB,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAW;gBAGxB,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,MAAM;IAGjC;;;OAGG;IACU,MAAM,CACjB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IA8FzB,OAAO,CAAC,eAAe;CAwBxB"}
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.HighlightRendererService = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const child_process_1 = require("child_process");
39
+ const spawnToFile_1 = require("../utils/spawnToFile");
40
+ const lowPriority_1 = require("../utils/lowPriority");
41
+ /**
42
+ * Overlays the DropShot + client logos onto a highlight clip, producing the
43
+ * final reel. Works on either a full-frame (16:9) clip or a reframed (e.g. 9:16)
44
+ * one — logo sizes/positions are derived from the actual canvas dimensions.
45
+ *
46
+ * ── IN SIMPLE WORDS ──
47
+ * Stamps the two logos onto the highlight clip and writes the finished reel.
48
+ * Logos are sized relative to the video so they look right whether the clip is
49
+ * wide or vertical.
50
+ *
51
+ * ── WHY IT'S BUILT THIS WAY (change at your peril) ──
52
+ * - It probes the clip's real dimensions and sizes logos from them, so the same
53
+ * code brands a 1920x1080 full-frame clip and a 608x1080 reel correctly.
54
+ * - Only logos that actually exist on disk are overlaid; a missing client logo
55
+ * yields a clip with just the DropShot mark rather than a hard failure.
56
+ * - temp-file + atomic rename; fail-soft (returns null, logged) so a render
57
+ * problem never loses the underlying clip — the caller keeps the source.
58
+ *
59
+ * ── DO NOT ──
60
+ * - Do NOT assume both logos exist; check each.
61
+ * - Do NOT assume success — callers must handle a null return (fall back to the
62
+ * un-branded source clip).
63
+ */
64
+ class HighlightRendererService {
65
+ constructor(dsLogoPath, clientLogoPath, logger) {
66
+ this.dsLogoPath = dsLogoPath;
67
+ this.clientLogoPath = clientLogoPath;
68
+ this.logger = logger;
69
+ // Generous: runs at low priority (see withLowPriority), so it may take
70
+ // longer under load — better slow than killed. Live stream is protected.
71
+ this.renderTimeoutMs = 120000;
72
+ }
73
+ /**
74
+ * Overlay logos from `sourceClip` into `outPath`. Returns outPath on success,
75
+ * or null on any failure (caller should fall back to the source clip).
76
+ */
77
+ async render(sourceClip, outPath, courtId) {
78
+ try {
79
+ const dims = await this.probeDimensions(sourceClip);
80
+ if (!dims) {
81
+ this.logger.warn("Highlight render skipped: could not probe clip", {
82
+ courtId,
83
+ sourceClip,
84
+ });
85
+ return null;
86
+ }
87
+ const { width, height } = dims;
88
+ // Logos present on disk (top-right = client, bottom-right = DropShot),
89
+ // matching the live-stream corners. Skip any that are missing.
90
+ const logos = [];
91
+ if (fs.existsSync(this.dsLogoPath)) {
92
+ logos.push({
93
+ file: this.dsLogoPath,
94
+ pos: `main_w-overlay_w-${Math.round(width * 0.02)}:main_h-overlay_h-${Math.round(height * 0.02)}`,
95
+ });
96
+ }
97
+ if (fs.existsSync(this.clientLogoPath)) {
98
+ logos.push({
99
+ file: this.clientLogoPath,
100
+ pos: `main_w-overlay_w-${Math.round(width * 0.02)}:${Math.round(height * 0.02)}`,
101
+ });
102
+ }
103
+ if (logos.length === 0) {
104
+ this.logger.warn("Highlight render skipped: no logo files present", {
105
+ courtId,
106
+ dsLogoPath: this.dsLogoPath,
107
+ clientLogoPath: this.clientLogoPath,
108
+ });
109
+ return null;
110
+ }
111
+ // Logo bounding box relative to canvas so it reads well at any aspect.
112
+ // A portrait (reel) canvas is narrow, so the 16:9 fraction renders the
113
+ // logos too small — give portrait a larger share of the width. Landscape
114
+ // (the full-frame reel) keeps the original sizing, so that output is
115
+ // unchanged.
116
+ const portrait = height > width;
117
+ const boxW = Math.round(width * (portrait ? 0.4 : 0.26));
118
+ const boxH = Math.round(height * (portrait ? 0.18 : 0.13));
119
+ const args = ["-y", "-i", sourceClip];
120
+ logos.forEach((l) => args.push("-i", l.file));
121
+ const steps = [];
122
+ let cur = "0:v";
123
+ logos.forEach((l, i) => {
124
+ const input = i + 1; // logo inputs follow the source at index 0
125
+ steps.push(`[${input}:v] scale=${boxW}:${boxH}:force_original_aspect_ratio=decrease [logo${i}];`);
126
+ const next = i === logos.length - 1 ? "vout" : `v${i}`;
127
+ steps.push(`[${cur}][logo${i}] overlay=${l.pos} [${next}];`);
128
+ cur = next;
129
+ });
130
+ // Drop the trailing ';' on the final step.
131
+ const filter = steps.join(" ").replace(/;\s*$/, "");
132
+ args.push("-filter_complex", filter, "-map", "[vout]", "-c:v", "libx264", "-preset", "veryfast",
133
+ // Web/social-safe output: broadly-compatible pixel format and a moved
134
+ // moov atom so the reel starts playing before it's fully downloaded.
135
+ "-pix_fmt", "yuv420p", "-movflags", "+faststart", "-an");
136
+ // Low CPU priority so logo compositing can't starve the live ffmpeg.
137
+ const lp = (0, lowPriority_1.withLowPriority)("ffmpeg", args);
138
+ await (0, spawnToFile_1.spawnToFile)(lp.command, lp.args, outPath, this.renderTimeoutMs);
139
+ this.logger.info("Highlight reel rendered", {
140
+ courtId,
141
+ outPath,
142
+ width,
143
+ height,
144
+ logos: logos.length,
145
+ });
146
+ return outPath;
147
+ }
148
+ catch (error) {
149
+ this.logger.warn("Highlight render failed", {
150
+ courtId,
151
+ error: error instanceof Error ? error.message : String(error),
152
+ });
153
+ return null;
154
+ }
155
+ }
156
+ probeDimensions(clip) {
157
+ return new Promise((resolve) => {
158
+ const proc = (0, child_process_1.spawn)("ffprobe", [
159
+ "-v", "error",
160
+ "-select_streams", "v:0",
161
+ "-show_entries", "stream=width,height",
162
+ "-of", "csv=p=0",
163
+ clip,
164
+ ]);
165
+ let out = "";
166
+ proc.stdout?.on("data", (d) => {
167
+ out += d.toString();
168
+ });
169
+ proc.on("exit", (code) => {
170
+ if (code !== 0)
171
+ return resolve(null);
172
+ const m = out.trim().match(/(\d+)\s*,\s*(\d+)/);
173
+ if (!m)
174
+ return resolve(null);
175
+ resolve({ width: parseInt(m[1], 10), height: parseInt(m[2], 10) });
176
+ });
177
+ proc.on("error", () => resolve(null));
178
+ });
179
+ }
180
+ }
181
+ exports.HighlightRendererService = HighlightRendererService;
182
+ //# sourceMappingURL=HighlightRendererService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HighlightRendererService.js","sourceRoot":"","sources":["../../../../src/infrastructure/services/HighlightRendererService.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { FFmpegService, FFmpegCommand, FFmpegProcess } from "../../domain/services/FFmpegService";
1
+ import { FFmpegService, FFmpegCommand, FFmpegProcess, AdOverlayPaths } from "../../domain/services/FFmpegService";
2
2
  import { StreamUrl } from "../../domain/value-objects/StreamUrl";
3
3
  import { Logger } from "../../application/interfaces/Logger";
4
4
  import { StartStreamRequest } from "../../application/interfaces/StartStreamUseCase.types";
@@ -6,6 +6,7 @@ import { Config } from "../config/Config";
6
6
  export declare class NodeFFmpegService implements FFmpegService {
7
7
  private readonly logger;
8
8
  private readonly config;
9
+ private static readonly VIDEO_EXTS;
9
10
  private readonly runningProcesses;
10
11
  private readonly clientLogoPath;
11
12
  private readonly scoreOverlayDir;
@@ -23,15 +24,16 @@ export declare class NodeFFmpegService implements FFmpegService {
23
24
  startStream(cameraUrl: StreamUrl, streamKey: string, hasAudio: boolean, courtId: string, retry: {
24
25
  event: StartStreamRequest;
25
26
  onRetryStream: (event: StartStreamRequest) => Promise<void>;
26
- }, isScorecardActivated?: boolean): Promise<FFmpegProcess>;
27
+ }, isScorecardActivated?: boolean, adPaths?: AdOverlayPaths, highlightBufferDir?: string | null): Promise<FFmpegProcess>;
27
28
  stopStream(pid: number): Promise<void>;
28
29
  isProcessRunning(pid: number): Promise<boolean>;
29
30
  detectAudio(cameraUrl: StreamUrl): Promise<boolean>;
30
- buildStreamCommand(cameraUrl: StreamUrl, streamKey: string, hasAudio: boolean, courtId: string, isScorecardActivated?: boolean): FFmpegCommand;
31
+ buildStreamCommand(cameraUrl: StreamUrl, streamKey: string, hasAudio: boolean, courtId: string, isScorecardActivated?: boolean, adPaths?: AdOverlayPaths, highlightBufferDir?: string | null): FFmpegCommand;
31
32
  getRunningProcesses(): Promise<FFmpegProcess[]>;
32
33
  killAllProcesses(): Promise<void>;
34
+ private buildAdInputFlags;
33
35
  private validateImageFiles;
34
- private ensureScoreOverlay;
36
+ private resetScoreOverlay;
35
37
  private getScoreOverlayPath;
36
38
  private createDefaultScoreOverlay;
37
39
  }
@@ -1 +1 @@
1
- {"version":3,"file":"NodeFFmpegService.d.ts","sourceRoot":"","sources":["../../../../src/infrastructure/services/NodeFFmpegService.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,aAAa,EACb,aAAa,EACb,aAAa,EACd,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,sCAAsC,CAAC;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,uDAAuD,CAAC;AAC3F,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C,qBAAa,iBAAkB,YAAW,aAAa;IAMnD,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IANzB,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAyC;IAC1E,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;gBAGtB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM;IAMjC;;;;;;;;;OASG;IACU,WAAW,CACtB,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,OAAO,EACjB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE;QACL,KAAK,EAAE,kBAAkB,CAAC;QAC1B,aAAa,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KAC7D,EACD,oBAAoB,CAAC,EAAE,OAAO,GAC7B,OAAO,CAAC,aAAa,CAAC;IAqIZ,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4DtC,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAU/C,WAAW,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC;IAkDzD,kBAAkB,CACvB,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,OAAO,EACjB,OAAO,EAAE,MAAM,EACf,oBAAoB,CAAC,EAAE,OAAO,GAC7B,aAAa;IAuGH,mBAAmB,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;IAI/C,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAa9C,OAAO,CAAC,kBAAkB;IAkB1B,OAAO,CAAC,kBAAkB;IAM1B,OAAO,CAAC,mBAAmB;IAI3B,OAAO,CAAC,yBAAyB;CAoBlC"}
1
+ {"version":3,"file":"NodeFFmpegService.d.ts","sourceRoot":"","sources":["../../../../src/infrastructure/services/NodeFFmpegService.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,aAAa,EACb,aAAa,EACb,aAAa,EACb,cAAc,EACf,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,sCAAsC,CAAC;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,uDAAuD,CAAC;AAC3F,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAG1C,qBAAa,iBAAkB,YAAW,aAAa;IAQnD,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IARzB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAwD;IAE1F,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAyC;IAC1E,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;gBAGtB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM;IAMjC;;;;;;;;;OASG;IACU,WAAW,CACtB,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,OAAO,EACjB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE;QACL,KAAK,EAAE,kBAAkB,CAAC;QAC1B,aAAa,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KAC7D,EACD,oBAAoB,CAAC,EAAE,OAAO,EAC9B,OAAO,CAAC,EAAE,cAAc,EACxB,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,GACjC,OAAO,CAAC,aAAa,CAAC;IAuIZ,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4DtC,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAU/C,WAAW,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC;IAkDzD,kBAAkB,CACvB,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,OAAO,EACjB,OAAO,EAAE,MAAM,EACf,oBAAoB,CAAC,EAAE,OAAO,EAC9B,OAAO,CAAC,EAAE,cAAc,EACxB,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,GACjC,aAAa;IAwPH,mBAAmB,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;IAI/C,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB9C,OAAO,CAAC,iBAAiB;IAQzB,OAAO,CAAC,kBAAkB;IAkB1B,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,mBAAmB;IAI3B,OAAO,CAAC,yBAAyB;CAoBlC"}