@hyperframes/engine 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/dist/config.d.ts +62 -0
  2. package/dist/config.d.ts.map +1 -0
  3. package/dist/config.js +92 -0
  4. package/dist/config.js.map +1 -0
  5. package/dist/config.test.d.ts +2 -0
  6. package/dist/config.test.d.ts.map +1 -0
  7. package/dist/config.test.js +77 -0
  8. package/dist/config.test.js.map +1 -0
  9. package/dist/index.d.ts +49 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +55 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/services/audioMixer.d.ts +11 -0
  14. package/dist/services/audioMixer.d.ts.map +1 -0
  15. package/dist/services/audioMixer.js +306 -0
  16. package/dist/services/audioMixer.js.map +1 -0
  17. package/dist/services/audioMixer.types.d.ts +27 -0
  18. package/dist/services/audioMixer.types.d.ts.map +1 -0
  19. package/dist/services/audioMixer.types.js +2 -0
  20. package/dist/services/audioMixer.types.js.map +1 -0
  21. package/dist/services/browserManager.d.ts +29 -0
  22. package/dist/services/browserManager.d.ts.map +1 -0
  23. package/dist/services/browserManager.js +175 -0
  24. package/dist/services/browserManager.js.map +1 -0
  25. package/dist/services/chunkEncoder.d.ts +32 -0
  26. package/dist/services/chunkEncoder.d.ts.map +1 -0
  27. package/dist/services/chunkEncoder.js +347 -0
  28. package/dist/services/chunkEncoder.js.map +1 -0
  29. package/dist/services/chunkEncoder.test.d.ts +2 -0
  30. package/dist/services/chunkEncoder.test.d.ts.map +1 -0
  31. package/dist/services/chunkEncoder.test.js +24 -0
  32. package/dist/services/chunkEncoder.test.js.map +1 -0
  33. package/dist/services/chunkEncoder.types.d.ts +26 -0
  34. package/dist/services/chunkEncoder.types.d.ts.map +1 -0
  35. package/dist/services/chunkEncoder.types.js +2 -0
  36. package/dist/services/chunkEncoder.types.js.map +1 -0
  37. package/dist/services/fileServer.d.ts +25 -0
  38. package/dist/services/fileServer.d.ts.map +1 -0
  39. package/dist/services/fileServer.js +138 -0
  40. package/dist/services/fileServer.js.map +1 -0
  41. package/dist/services/frameCapture.d.ts +52 -0
  42. package/dist/services/frameCapture.d.ts.map +1 -0
  43. package/dist/services/frameCapture.js +317 -0
  44. package/dist/services/frameCapture.js.map +1 -0
  45. package/dist/services/parallelCoordinator.d.ts +40 -0
  46. package/dist/services/parallelCoordinator.d.ts.map +1 -0
  47. package/dist/services/parallelCoordinator.js +192 -0
  48. package/dist/services/parallelCoordinator.js.map +1 -0
  49. package/dist/services/parallelCoordinator.test.d.ts +2 -0
  50. package/dist/services/parallelCoordinator.test.d.ts.map +1 -0
  51. package/dist/services/parallelCoordinator.test.js +38 -0
  52. package/dist/services/parallelCoordinator.test.js.map +1 -0
  53. package/dist/services/screenshotService.d.ts +28 -0
  54. package/dist/services/screenshotService.d.ts.map +1 -0
  55. package/dist/services/screenshotService.js +190 -0
  56. package/dist/services/screenshotService.js.map +1 -0
  57. package/dist/services/streamingEncoder.d.ts +49 -0
  58. package/dist/services/streamingEncoder.d.ts.map +1 -0
  59. package/dist/services/streamingEncoder.js +227 -0
  60. package/dist/services/streamingEncoder.js.map +1 -0
  61. package/dist/services/videoFrameExtractor.d.ts +65 -0
  62. package/dist/services/videoFrameExtractor.d.ts.map +1 -0
  63. package/dist/services/videoFrameExtractor.js +302 -0
  64. package/dist/services/videoFrameExtractor.js.map +1 -0
  65. package/dist/services/videoFrameInjector.d.ts +17 -0
  66. package/dist/services/videoFrameInjector.d.ts.map +1 -0
  67. package/dist/services/videoFrameInjector.js +92 -0
  68. package/dist/services/videoFrameInjector.js.map +1 -0
  69. package/dist/types.d.ts +79 -0
  70. package/dist/types.d.ts.map +1 -0
  71. package/dist/types.js +8 -0
  72. package/dist/types.js.map +1 -0
  73. package/dist/utils/ffprobe.d.ts +18 -0
  74. package/dist/utils/ffprobe.d.ts.map +1 -0
  75. package/dist/utils/ffprobe.js +137 -0
  76. package/dist/utils/ffprobe.js.map +1 -0
  77. package/dist/utils/gpuEncoder.d.ts +11 -0
  78. package/dist/utils/gpuEncoder.d.ts.map +1 -0
  79. package/dist/utils/gpuEncoder.js +55 -0
  80. package/dist/utils/gpuEncoder.js.map +1 -0
  81. package/dist/utils/parityContract.d.ts +3 -0
  82. package/dist/utils/parityContract.d.ts.map +1 -0
  83. package/dist/utils/parityContract.js +35 -0
  84. package/dist/utils/parityContract.js.map +1 -0
  85. package/dist/utils/runFfmpeg.d.ts +19 -0
  86. package/dist/utils/runFfmpeg.d.ts.map +1 -0
  87. package/dist/utils/runFfmpeg.js +62 -0
  88. package/dist/utils/runFfmpeg.js.map +1 -0
  89. package/dist/utils/urlDownloader.d.ts +3 -0
  90. package/dist/utils/urlDownloader.d.ts.map +1 -0
  91. package/dist/utils/urlDownloader.js +68 -0
  92. package/dist/utils/urlDownloader.js.map +1 -0
  93. package/package.json +30 -0
  94. package/src/cdp-headless-experimental.d.ts +54 -0
  95. package/src/config.test.ts +95 -0
  96. package/src/config.ts +176 -0
  97. package/src/index.ts +154 -0
  98. package/src/services/audioMixer.ts +392 -0
  99. package/src/services/audioMixer.types.ts +28 -0
  100. package/src/services/browserManager.ts +219 -0
  101. package/src/services/chunkEncoder.test.ts +27 -0
  102. package/src/services/chunkEncoder.ts +399 -0
  103. package/src/services/chunkEncoder.types.ts +27 -0
  104. package/src/services/fileServer.ts +175 -0
  105. package/src/services/frameCapture.ts +452 -0
  106. package/src/services/parallelCoordinator.test.ts +44 -0
  107. package/src/services/parallelCoordinator.ts +293 -0
  108. package/src/services/screenshotService.ts +222 -0
  109. package/src/services/streamingEncoder.ts +308 -0
  110. package/src/services/videoFrameExtractor.ts +395 -0
  111. package/src/services/videoFrameInjector.ts +113 -0
  112. package/src/types.ts +91 -0
  113. package/src/utils/ffprobe.ts +192 -0
  114. package/src/utils/gpuEncoder.ts +58 -0
  115. package/src/utils/parityContract.ts +35 -0
  116. package/src/utils/runFfmpeg.ts +81 -0
  117. package/src/utils/urlDownloader.ts +78 -0
  118. package/tsconfig.json +18 -0
  119. package/vitest.config.ts +7 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parallelCoordinator.test.d.ts","sourceRoot":"","sources":["../../src/services/parallelCoordinator.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,38 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { distributeFrames } from "./parallelCoordinator.js";
3
+ describe("distributeFrames", () => {
4
+ it("distributes frames evenly across workers", () => {
5
+ const tasks = distributeFrames(100, 4, "/tmp/work");
6
+ expect(tasks).toHaveLength(4);
7
+ // First worker: frames 0-24
8
+ expect(tasks[0]?.startFrame).toBe(0);
9
+ expect(tasks[0]?.endFrame).toBe(25);
10
+ // Last worker: frames 75-99
11
+ expect(tasks[3]?.startFrame).toBe(75);
12
+ expect(tasks[3]?.endFrame).toBe(100);
13
+ });
14
+ it("handles single worker", () => {
15
+ const tasks = distributeFrames(50, 1, "/tmp/work");
16
+ expect(tasks).toHaveLength(1);
17
+ expect(tasks[0]?.startFrame).toBe(0);
18
+ expect(tasks[0]?.endFrame).toBe(50);
19
+ });
20
+ it("does not create empty tasks when workers exceed frames", () => {
21
+ const tasks = distributeFrames(3, 10, "/tmp/work");
22
+ // Can't have more tasks than frames
23
+ expect(tasks.length).toBeLessThanOrEqual(3);
24
+ // All frames are covered
25
+ const totalFrames = tasks.reduce((sum, t) => sum + (t.endFrame - t.startFrame), 0);
26
+ expect(totalFrames).toBe(3);
27
+ });
28
+ it("assigns worker output directories", () => {
29
+ const tasks = distributeFrames(60, 2, "/tmp/my-work");
30
+ expect(tasks[0]?.outputDir).toContain("worker-0");
31
+ expect(tasks[1]?.outputDir).toContain("worker-1");
32
+ });
33
+ it("assigns sequential worker IDs", () => {
34
+ const tasks = distributeFrames(100, 3, "/tmp/work");
35
+ expect(tasks.map((t) => t.workerId)).toEqual([0, 1, 2]);
36
+ });
37
+ });
38
+ //# sourceMappingURL=parallelCoordinator.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parallelCoordinator.test.js","sourceRoot":"","sources":["../../src/services/parallelCoordinator.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Screenshot Service
3
+ *
4
+ * BeginFrame-based deterministic screenshot capture and video frame injection.
5
+ */
6
+ import { type Page } from "puppeteer-core";
7
+ import { type CaptureOptions } from "../types.js";
8
+ export declare const cdpSessionCache: WeakMap<Page, import("puppeteer-core").CDPSession>;
9
+ export declare function getCdpSession(page: Page): Promise<import("puppeteer-core").CDPSession>;
10
+ /**
11
+ * BeginFrame result with screenshot data and damage detection.
12
+ */
13
+ export interface BeginFrameResult {
14
+ buffer: Buffer;
15
+ hasDamage: boolean;
16
+ }
17
+ export declare function beginFrameCapture(page: Page, options: CaptureOptions, frameTimeTicks: number, interval: number): Promise<BeginFrameResult>;
18
+ /**
19
+ * Capture a screenshot using standard Page.captureScreenshot CDP call.
20
+ * Fallback for environments where BeginFrame is unavailable (macOS, Windows).
21
+ */
22
+ export declare function pageScreenshotCapture(page: Page, options: CaptureOptions): Promise<Buffer>;
23
+ export declare function injectVideoFramesBatch(page: Page, updates: Array<{
24
+ videoId: string;
25
+ dataUri: string;
26
+ }>): Promise<void>;
27
+ export declare function syncVideoFrameVisibility(page: Page, activeVideoIds: string[]): Promise<void>;
28
+ //# sourceMappingURL=screenshotService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenshotService.d.ts","sourceRoot":"","sources":["../../src/services/screenshotService.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,eAAO,MAAM,eAAe,oDAA2D,CAAC;AAExF,wBAAsB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,gBAAgB,EAAE,UAAU,CAAC,CAO5F;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,OAAO,CAAC;CACpB;AAkBD,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,cAAc,EACvB,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,gBAAgB,CAAC,CA4C3B;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAShG;AAED,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,GACnD,OAAO,CAAC,IAAI,CAAC,CAyFf;AAED,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBlG"}
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Screenshot Service
3
+ *
4
+ * BeginFrame-based deterministic screenshot capture and video frame injection.
5
+ */
6
+ import { MEDIA_VISUAL_STYLE_PROPERTIES } from "@hyperframes/core";
7
+ export const cdpSessionCache = new WeakMap();
8
+ export async function getCdpSession(page) {
9
+ let client = cdpSessionCache.get(page);
10
+ if (!client) {
11
+ client = await page.createCDPSession();
12
+ cdpSessionCache.set(page, client);
13
+ }
14
+ return client;
15
+ }
16
+ /**
17
+ * Capture a frame using HeadlessExperimental.beginFrame.
18
+ *
19
+ * This is an atomic operation: one CDP call runs a single layout-paint-composite
20
+ * cycle and returns the screenshot + hasDamage boolean. Replaces the separate
21
+ * settle → screenshot pipeline with a single deterministic render cycle.
22
+ *
23
+ * Requires chrome-headless-shell with --enable-begin-frame-control and
24
+ * --deterministic-mode flags.
25
+ */
26
+ // Cache the last valid screenshot buffer per page for hasDamage=false frames.
27
+ // When Chrome reports no visual change, we reuse the previous frame rather than
28
+ // attempting Page.captureScreenshot (which times out in beginFrame mode since
29
+ // the compositor is paused).
30
+ const lastFrameCache = new WeakMap();
31
+ export async function beginFrameCapture(page, options, frameTimeTicks, interval) {
32
+ const client = await getCdpSession(page);
33
+ const format = options.format === "png" ? "png" : "jpeg";
34
+ const result = await client.send("HeadlessExperimental.beginFrame", {
35
+ frameTimeTicks,
36
+ interval,
37
+ screenshot: {
38
+ format,
39
+ quality: format === "jpeg" ? (options.quality ?? 80) : undefined,
40
+ optimizeForSpeed: true,
41
+ },
42
+ });
43
+ let buffer;
44
+ if (result.screenshotData) {
45
+ buffer = Buffer.from(result.screenshotData, "base64");
46
+ lastFrameCache.set(page, buffer);
47
+ }
48
+ else {
49
+ // hasDamage=false — nothing changed visually. Reuse the last frame.
50
+ const cached = lastFrameCache.get(page);
51
+ if (cached) {
52
+ buffer = cached;
53
+ }
54
+ else {
55
+ // No cached frame yet (shouldn't happen — frame 0 always has damage).
56
+ // Issue another beginFrame with a tiny time advance to force a composite.
57
+ const retry = await client.send("HeadlessExperimental.beginFrame", {
58
+ frameTimeTicks: frameTimeTicks + 0.001,
59
+ interval,
60
+ screenshot: {
61
+ format,
62
+ quality: format === "jpeg" ? (options.quality ?? 80) : undefined,
63
+ optimizeForSpeed: true,
64
+ },
65
+ });
66
+ buffer = retry.screenshotData ? Buffer.from(retry.screenshotData, "base64") : Buffer.alloc(0);
67
+ if (buffer.length > 0)
68
+ lastFrameCache.set(page, buffer);
69
+ }
70
+ }
71
+ return {
72
+ buffer,
73
+ hasDamage: result.hasDamage,
74
+ };
75
+ }
76
+ /**
77
+ * Capture a screenshot using standard Page.captureScreenshot CDP call.
78
+ * Fallback for environments where BeginFrame is unavailable (macOS, Windows).
79
+ */
80
+ export async function pageScreenshotCapture(page, options) {
81
+ const client = await getCdpSession(page);
82
+ const format = options.format === "png" ? "png" : "jpeg";
83
+ const result = await client.send("Page.captureScreenshot", {
84
+ format,
85
+ quality: format === "jpeg" ? (options.quality ?? 80) : undefined,
86
+ optimizeForSpeed: true,
87
+ });
88
+ return Buffer.from(result.data, "base64");
89
+ }
90
+ export async function injectVideoFramesBatch(page, updates) {
91
+ if (updates.length === 0)
92
+ return;
93
+ await page.evaluate(async (items, visualProperties) => {
94
+ const pendingDecodes = [];
95
+ for (const item of items) {
96
+ const video = document.getElementById(item.videoId);
97
+ if (!video)
98
+ continue;
99
+ let img = video.nextElementSibling;
100
+ const isNewImage = !img || !img.classList.contains("__render_frame__");
101
+ const computedStyle = window.getComputedStyle(video);
102
+ const computedOpacity = parseFloat(computedStyle.opacity) || 1;
103
+ const sourceIsStatic = !computedStyle.position || computedStyle.position === "static";
104
+ if (isNewImage) {
105
+ img = document.createElement("img");
106
+ img.classList.add("__render_frame__");
107
+ img.id = `__render_frame_${item.videoId}__`;
108
+ img.style.pointerEvents = "none";
109
+ video.parentNode?.insertBefore(img, video.nextSibling);
110
+ }
111
+ if (!img)
112
+ continue;
113
+ if (!sourceIsStatic) {
114
+ img.style.position = computedStyle.position;
115
+ img.style.width = computedStyle.width;
116
+ img.style.height = computedStyle.height;
117
+ img.style.top = computedStyle.top;
118
+ img.style.left = computedStyle.left;
119
+ img.style.right = computedStyle.right;
120
+ img.style.bottom = computedStyle.bottom;
121
+ img.style.inset = computedStyle.inset;
122
+ }
123
+ else {
124
+ const videoRect = video.getBoundingClientRect();
125
+ const offsetLeft = Number.isFinite(video.offsetLeft) ? video.offsetLeft : 0;
126
+ const offsetTop = Number.isFinite(video.offsetTop) ? video.offsetTop : 0;
127
+ const offsetWidth = video.offsetWidth > 0 ? video.offsetWidth : videoRect.width;
128
+ const offsetHeight = video.offsetHeight > 0 ? video.offsetHeight : videoRect.height;
129
+ img.style.position = "absolute";
130
+ img.style.inset = "auto";
131
+ img.style.left = `${offsetLeft}px`;
132
+ img.style.top = `${offsetTop}px`;
133
+ img.style.right = "auto";
134
+ img.style.bottom = "auto";
135
+ img.style.width = `${offsetWidth}px`;
136
+ img.style.height = `${offsetHeight}px`;
137
+ }
138
+ img.style.objectFit = computedStyle.objectFit;
139
+ img.style.objectPosition = computedStyle.objectPosition;
140
+ img.style.zIndex = computedStyle.zIndex;
141
+ for (const property of visualProperties) {
142
+ if (sourceIsStatic &&
143
+ (property === "top" ||
144
+ property === "left" ||
145
+ property === "right" ||
146
+ property === "bottom" ||
147
+ property === "inset")) {
148
+ continue;
149
+ }
150
+ const value = computedStyle.getPropertyValue(property);
151
+ if (value) {
152
+ img.style.setProperty(property, value);
153
+ }
154
+ }
155
+ img.decoding = "sync";
156
+ img.src = item.dataUri;
157
+ pendingDecodes.push(img
158
+ .decode()
159
+ .catch(() => undefined)
160
+ .then(() => undefined));
161
+ img.style.opacity = String(computedOpacity);
162
+ img.style.visibility = "visible";
163
+ video.style.setProperty("visibility", "hidden", "important");
164
+ video.style.setProperty("opacity", "0", "important");
165
+ video.style.setProperty("pointer-events", "none", "important");
166
+ }
167
+ if (pendingDecodes.length > 0) {
168
+ await Promise.all(pendingDecodes);
169
+ }
170
+ }, updates, [...MEDIA_VISUAL_STYLE_PROPERTIES]);
171
+ }
172
+ export async function syncVideoFrameVisibility(page, activeVideoIds) {
173
+ await page.evaluate((ids) => {
174
+ const active = new Set(ids);
175
+ const videos = Array.from(document.querySelectorAll("video[data-start]"));
176
+ for (const video of videos) {
177
+ if (active.has(video.id))
178
+ continue;
179
+ video.style.removeProperty("display");
180
+ video.style.setProperty("visibility", "hidden", "important");
181
+ video.style.setProperty("opacity", "0", "important");
182
+ video.style.setProperty("pointer-events", "none", "important");
183
+ const img = video.nextElementSibling;
184
+ if (img && img.classList.contains("__render_frame__")) {
185
+ img.style.visibility = "hidden";
186
+ }
187
+ }
188
+ }, activeVideoIds);
189
+ }
190
+ //# sourceMappingURL=screenshotService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenshotService.js","sourceRoot":"","sources":["../../src/services/screenshotService.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,EAAE,6BAA6B,EAAE,MAAM,mBAAmB,CAAC;AAElE,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,OAAO,EAA6C,CAAC;AAExF,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAU;IAC5C,IAAI,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,GAAG,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACvC,eAAe,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAUD;;;;;;;;;GASG;AACH,8EAA8E;AAC9E,gFAAgF;AAChF,8EAA8E;AAC9E,6BAA6B;AAC7B,MAAM,cAAc,GAAG,IAAI,OAAO,EAAgB,CAAC;AAEnD,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,IAAU,EACV,OAAuB,EACvB,cAAsB,EACtB,QAAgB;IAEhB,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,CAAC;IAEzC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;IACzD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,iCAAiC,EAAE;QAClE,cAAc;QACd,QAAQ;QACR,UAAU,EAAE;YACV,MAAM;YACN,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS;YAChE,gBAAgB,EAAE,IAAI;SACvB;KACF,CAAC,CAAC;IAEH,IAAI,MAAc,CAAC;IACnB,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC;QAC1B,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;QACtD,cAAc,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACnC,CAAC;SAAM,CAAC;QACN,oEAAoE;QACpE,MAAM,MAAM,GAAG,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,GAAG,MAAM,CAAC;QAClB,CAAC;aAAM,CAAC;YACN,sEAAsE;YACtE,0EAA0E;YAC1E,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,iCAAiC,EAAE;gBACjE,cAAc,EAAE,cAAc,GAAG,KAAK;gBACtC,QAAQ;gBACR,UAAU,EAAE;oBACV,MAAM;oBACN,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS;oBAChE,gBAAgB,EAAE,IAAI;iBACvB;aACF,CAAC,CAAC;YACH,MAAM,GAAG,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAC9F,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;gBAAE,cAAc,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;IAED,OAAO;QACL,MAAM;QACN,SAAS,EAAE,MAAM,CAAC,SAAS;KAC5B,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,IAAU,EAAE,OAAuB;IAC7E,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;IACzD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;QACzD,MAAM;QACN,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS;QAChE,gBAAgB,EAAE,IAAI;KACvB,CAAC,CAAC;IACH,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,IAAU,EACV,OAAoD;IAEpD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IACjC,MAAM,IAAI,CAAC,QAAQ,CACjB,KAAK,EAAE,KAAkD,EAAE,gBAA0B,EAAE,EAAE;QACvF,MAAM,cAAc,GAAyB,EAAE,CAAC;QAChD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,KAAK,GAAG,QAAQ,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAA4B,CAAC;YAC/E,IAAI,CAAC,KAAK;gBAAE,SAAS;YAErB,IAAI,GAAG,GAAG,KAAK,CAAC,kBAA6C,CAAC;YAC9D,MAAM,UAAU,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC;YACvE,MAAM,aAAa,GAAG,MAAM,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;YACrD,MAAM,eAAe,GAAG,UAAU,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC/D,MAAM,cAAc,GAAG,CAAC,aAAa,CAAC,QAAQ,IAAI,aAAa,CAAC,QAAQ,KAAK,QAAQ,CAAC;YAEtF,IAAI,UAAU,EAAE,CAAC;gBACf,GAAG,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;gBACpC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;gBACtC,GAAG,CAAC,EAAE,GAAG,kBAAkB,IAAI,CAAC,OAAO,IAAI,CAAC;gBAC5C,GAAG,CAAC,KAAK,CAAC,aAAa,GAAG,MAAM,CAAC;gBACjC,KAAK,CAAC,UAAU,EAAE,YAAY,CAAC,GAAG,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;YACzD,CAAC;YACD,IAAI,CAAC,GAAG;gBAAE,SAAS;YAEnB,IAAI,CAAC,cAAc,EAAE,CAAC;gBACpB,GAAG,CAAC,KAAK,CAAC,QAAQ,GAAG,aAAa,CAAC,QAAQ,CAAC;gBAC5C,GAAG,CAAC,KAAK,CAAC,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC;gBACtC,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC;gBACxC,GAAG,CAAC,KAAK,CAAC,GAAG,GAAG,aAAa,CAAC,GAAG,CAAC;gBAClC,GAAG,CAAC,KAAK,CAAC,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC;gBACpC,GAAG,CAAC,KAAK,CAAC,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC;gBACtC,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC;gBACxC,GAAG,CAAC,KAAK,CAAC,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC;YACxC,CAAC;iBAAM,CAAC;gBACN,MAAM,SAAS,GAAG,KAAK,CAAC,qBAAqB,EAAE,CAAC;gBAChD,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC5E,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;gBACzE,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC;gBAChF,MAAM,YAAY,GAAG,KAAK,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC;gBACpF,GAAG,CAAC,KAAK,CAAC,QAAQ,GAAG,UAAU,CAAC;gBAChC,GAAG,CAAC,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC;gBACzB,GAAG,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,UAAU,IAAI,CAAC;gBACnC,GAAG,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,SAAS,IAAI,CAAC;gBACjC,GAAG,CAAC,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC;gBACzB,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC;gBAC1B,GAAG,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,WAAW,IAAI,CAAC;gBACrC,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,YAAY,IAAI,CAAC;YACzC,CAAC;YACD,GAAG,CAAC,KAAK,CAAC,SAAS,GAAG,aAAa,CAAC,SAAS,CAAC;YAC9C,GAAG,CAAC,KAAK,CAAC,cAAc,GAAG,aAAa,CAAC,cAAc,CAAC;YACxD,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC;YAExC,KAAK,MAAM,QAAQ,IAAI,gBAAgB,EAAE,CAAC;gBACxC,IACE,cAAc;oBACd,CAAC,QAAQ,KAAK,KAAK;wBACjB,QAAQ,KAAK,MAAM;wBACnB,QAAQ,KAAK,OAAO;wBACpB,QAAQ,KAAK,QAAQ;wBACrB,QAAQ,KAAK,OAAO,CAAC,EACvB,CAAC;oBACD,SAAS;gBACX,CAAC;gBACD,MAAM,KAAK,GAAG,aAAa,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC;gBACvD,IAAI,KAAK,EAAE,CAAC;oBACV,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;gBACzC,CAAC;YACH,CAAC;YACD,GAAG,CAAC,QAAQ,GAAG,MAAM,CAAC;YACtB,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC;YACvB,cAAc,CAAC,IAAI,CACjB,GAAG;iBACA,MAAM,EAAE;iBACR,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;iBACtB,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CACzB,CAAC;YACF,GAAG,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC,eAAe,CAAC,CAAC;YAC5C,GAAG,CAAC,KAAK,CAAC,UAAU,GAAG,SAAS,CAAC;YACjC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,YAAY,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC;YAC7D,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,SAAS,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC;YACrD,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,gBAAgB,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;QACjE,CAAC;QACD,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9B,MAAM,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QACpC,CAAC;IACH,CAAC,EACD,OAAO,EACP,CAAC,GAAG,6BAA6B,CAAC,CACnC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAAC,IAAU,EAAE,cAAwB;IACjF,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAa,EAAE,EAAE;QACpC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,mBAAmB,CAAC,CAAuB,CAAC;QAChG,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;gBAAE,SAAS;YACnC,KAAK,CAAC,KAAK,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;YACtC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,YAAY,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC;YAC7D,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,SAAS,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC;YACrD,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,gBAAgB,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;YAC/D,MAAM,GAAG,GAAG,KAAK,CAAC,kBAAwC,CAAC;YAC3D,IAAI,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;gBACtD,GAAG,CAAC,KAAK,CAAC,UAAU,GAAG,QAAQ,CAAC;YAClC,CAAC;QACH,CAAC;IACH,CAAC,EAAE,cAAc,CAAC,CAAC;AACrB,CAAC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Streaming Encoder Service
3
+ *
4
+ * Pipes frame screenshot buffers directly to FFmpeg's stdin instead of writing
5
+ * them to disk and reading them back in a separate encode stage. Follows the
6
+ * Remotion pattern of image2pipe → FFmpeg.
7
+ *
8
+ * Two building blocks:
9
+ * 1. Frame reorder buffer – ensures out-of-order parallel workers feed
10
+ * frames to FFmpeg stdin in sequential order.
11
+ * 2. Streaming FFmpeg encoder – spawns FFmpeg with `-f image2pipe` and
12
+ * exposes a `writeFrame(buffer)` + `close()` API.
13
+ */
14
+ import { type EngineConfig } from "../config.js";
15
+ export type { EncoderOptions } from "./chunkEncoder.types.js";
16
+ export interface FrameReorderBuffer {
17
+ waitForFrame: (frame: number) => Promise<void>;
18
+ advanceTo: (frame: number) => void;
19
+ waitForAllDone: () => Promise<void>;
20
+ }
21
+ export declare function createFrameReorderBuffer(startFrame: number, endFrame: number): FrameReorderBuffer;
22
+ export interface StreamingEncoderOptions {
23
+ fps: number;
24
+ width: number;
25
+ height: number;
26
+ codec?: "h264" | "h265" | "vp9" | "prores";
27
+ preset?: string;
28
+ quality?: number;
29
+ bitrate?: string;
30
+ pixelFormat?: string;
31
+ useGpu?: boolean;
32
+ imageFormat?: "jpeg" | "png";
33
+ }
34
+ export interface StreamingEncoderResult {
35
+ success: boolean;
36
+ durationMs: number;
37
+ fileSize: number;
38
+ error?: string;
39
+ }
40
+ export interface StreamingEncoder {
41
+ writeFrame: (buffer: Buffer) => boolean;
42
+ close: () => Promise<StreamingEncoderResult>;
43
+ getExitStatus: () => "running" | "success" | "error";
44
+ }
45
+ /**
46
+ * Spawn a streaming FFmpeg encoder that accepts frame buffers on stdin.
47
+ */
48
+ export declare function spawnStreamingEncoder(outputPath: string, options: StreamingEncoderOptions, signal?: AbortSignal, config?: Partial<Pick<EngineConfig, "ffmpegStreamingTimeout">>): Promise<StreamingEncoder>;
49
+ //# sourceMappingURL=streamingEncoder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streamingEncoder.d.ts","sourceRoot":"","sources":["../../src/services/streamingEncoder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAQH,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAGjE,YAAY,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAM9D,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC;AAED,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,kBAAkB,CA6BjG;AAMD,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;IAC3C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CAC9B;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,KAAK,EAAE,MAAM,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC7C,aAAa,EAAE,MAAM,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;CACtD;AA6FD;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,uBAAuB,EAChC,MAAM,CAAC,EAAE,WAAW,EACpB,MAAM,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,wBAAwB,CAAC,CAAC,GAC7D,OAAO,CAAC,gBAAgB,CAAC,CAgH3B"}
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Streaming Encoder Service
3
+ *
4
+ * Pipes frame screenshot buffers directly to FFmpeg's stdin instead of writing
5
+ * them to disk and reading them back in a separate encode stage. Follows the
6
+ * Remotion pattern of image2pipe → FFmpeg.
7
+ *
8
+ * Two building blocks:
9
+ * 1. Frame reorder buffer – ensures out-of-order parallel workers feed
10
+ * frames to FFmpeg stdin in sequential order.
11
+ * 2. Streaming FFmpeg encoder – spawns FFmpeg with `-f image2pipe` and
12
+ * exposes a `writeFrame(buffer)` + `close()` API.
13
+ */
14
+ import { spawn } from "child_process";
15
+ import { existsSync, mkdirSync, statSync } from "fs";
16
+ import { dirname } from "path";
17
+ import { getCachedGpuEncoder, getGpuEncoderName } from "../utils/gpuEncoder.js";
18
+ import { DEFAULT_CONFIG } from "../config.js";
19
+ export function createFrameReorderBuffer(startFrame, endFrame) {
20
+ let nextFrame = startFrame;
21
+ let waiters = [];
22
+ const resolveWaiters = () => {
23
+ for (const waiter of waiters.slice()) {
24
+ if (waiter.frame === nextFrame) {
25
+ waiter.resolve();
26
+ waiters = waiters.filter((w) => w !== waiter);
27
+ }
28
+ }
29
+ };
30
+ return {
31
+ waitForFrame: (frame) => new Promise((resolve) => {
32
+ waiters.push({ frame, resolve });
33
+ resolveWaiters();
34
+ }),
35
+ advanceTo: (frame) => {
36
+ nextFrame = frame;
37
+ resolveWaiters();
38
+ },
39
+ waitForAllDone: () => new Promise((resolve) => {
40
+ waiters.push({ frame: endFrame, resolve });
41
+ resolveWaiters();
42
+ }),
43
+ };
44
+ }
45
+ /**
46
+ * Build FFmpeg args for streaming (image2pipe) input.
47
+ * Reuses the same codec/quality/GPU logic as chunkEncoder's buildEncoderArgs
48
+ * but with `-f image2pipe` instead of `-i <pattern>`.
49
+ */
50
+ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
51
+ const { fps, codec = "h264", preset = "medium", quality = 23, bitrate, pixelFormat = "yuv420p", useGpu = false, imageFormat = "jpeg", } = options;
52
+ // Input args: pipe from stdin
53
+ const inputCodec = imageFormat === "png" ? "png" : "mjpeg";
54
+ const args = [
55
+ "-f",
56
+ "image2pipe",
57
+ "-vcodec",
58
+ inputCodec,
59
+ "-framerate",
60
+ String(fps),
61
+ "-i",
62
+ "-",
63
+ "-r",
64
+ String(fps),
65
+ ];
66
+ const shouldUseGpu = useGpu && gpuEncoder !== null;
67
+ if (codec === "h264" || codec === "h265") {
68
+ if (shouldUseGpu) {
69
+ const encoderName = getGpuEncoderName(gpuEncoder, codec);
70
+ args.push("-c:v", encoderName);
71
+ switch (gpuEncoder) {
72
+ case "nvenc":
73
+ args.push("-preset", preset);
74
+ if (bitrate)
75
+ args.push("-b:v", bitrate);
76
+ else
77
+ args.push("-cq", String(quality));
78
+ break;
79
+ case "videotoolbox":
80
+ if (bitrate)
81
+ args.push("-b:v", bitrate);
82
+ else {
83
+ const vtQuality = Math.max(0, Math.min(100, 100 - quality * 2));
84
+ args.push("-q:v", String(vtQuality));
85
+ }
86
+ args.push("-allow_sw", "1");
87
+ break;
88
+ case "vaapi":
89
+ args.unshift("-vaapi_device", "/dev/dri/renderD128");
90
+ args.push("-vf", "format=nv12,hwupload");
91
+ if (bitrate)
92
+ args.push("-b:v", bitrate);
93
+ else
94
+ args.push("-qp", String(quality));
95
+ break;
96
+ case "qsv":
97
+ args.push("-preset", preset);
98
+ if (bitrate)
99
+ args.push("-b:v", bitrate);
100
+ else
101
+ args.push("-global_quality", String(quality));
102
+ break;
103
+ }
104
+ }
105
+ else {
106
+ const encoderName = codec === "h264" ? "libx264" : "libx265";
107
+ args.push("-c:v", encoderName, "-preset", preset);
108
+ if (bitrate)
109
+ args.push("-b:v", bitrate);
110
+ else
111
+ args.push("-crf", String(quality));
112
+ }
113
+ }
114
+ else if (codec === "vp9") {
115
+ args.push("-c:v", "libvpx-vp9", "-b:v", bitrate || "0", "-crf", String(quality));
116
+ args.push("-deadline", preset === "ultrafast" ? "realtime" : "good");
117
+ }
118
+ else if (codec === "prores") {
119
+ args.push("-c:v", "prores_ks", "-profile:v", preset, "-vendor", "apl0");
120
+ return [...args, "-y", outputPath];
121
+ }
122
+ if (gpuEncoder !== "vaapi") {
123
+ args.push("-pix_fmt", pixelFormat);
124
+ }
125
+ args.push("-y", outputPath);
126
+ return args;
127
+ }
128
+ /**
129
+ * Spawn a streaming FFmpeg encoder that accepts frame buffers on stdin.
130
+ */
131
+ export async function spawnStreamingEncoder(outputPath, options, signal, config) {
132
+ const outputDir = dirname(outputPath);
133
+ if (!existsSync(outputDir))
134
+ mkdirSync(outputDir, { recursive: true });
135
+ let gpuEncoder = null;
136
+ if (options.useGpu) {
137
+ gpuEncoder = await getCachedGpuEncoder();
138
+ }
139
+ const args = buildStreamingArgs(options, outputPath, gpuEncoder);
140
+ const startTime = Date.now();
141
+ const ffmpeg = spawn("ffmpeg", args, {
142
+ stdio: ["pipe", "pipe", "pipe"],
143
+ });
144
+ let exitStatus = "running";
145
+ let stderr = "";
146
+ let exitCode = null;
147
+ let exitPromiseResolve = null;
148
+ const exitPromise = new Promise((resolve) => (exitPromiseResolve = resolve));
149
+ // Track stderr for progress and error messages
150
+ ffmpeg.stderr?.on("data", (data) => {
151
+ stderr += data.toString();
152
+ });
153
+ ffmpeg.on("close", (code) => {
154
+ exitCode = code;
155
+ exitStatus = code === 0 ? "success" : "error";
156
+ exitPromiseResolve?.();
157
+ });
158
+ ffmpeg.on("error", (err) => {
159
+ exitStatus = "error";
160
+ stderr += `\nProcess error: ${err.message}`;
161
+ exitPromiseResolve?.();
162
+ });
163
+ // Handle abort signal
164
+ const onAbort = () => {
165
+ if (exitStatus === "running") {
166
+ ffmpeg.kill("SIGTERM");
167
+ }
168
+ };
169
+ if (signal) {
170
+ if (signal.aborted) {
171
+ ffmpeg.kill("SIGTERM");
172
+ }
173
+ else {
174
+ signal.addEventListener("abort", onAbort, { once: true });
175
+ }
176
+ }
177
+ // Timeout safety
178
+ const streamingTimeout = config?.ffmpegStreamingTimeout ?? DEFAULT_CONFIG.ffmpegStreamingTimeout;
179
+ const timer = setTimeout(() => {
180
+ if (exitStatus === "running") {
181
+ ffmpeg.kill("SIGTERM");
182
+ }
183
+ }, streamingTimeout);
184
+ const encoder = {
185
+ writeFrame: (buffer) => {
186
+ if (exitStatus !== "running" || !ffmpeg.stdin || ffmpeg.stdin.destroyed) {
187
+ return false;
188
+ }
189
+ return ffmpeg.stdin.write(buffer);
190
+ },
191
+ close: async () => {
192
+ clearTimeout(timer);
193
+ if (signal)
194
+ signal.removeEventListener("abort", onAbort);
195
+ // Close stdin to signal end of input
196
+ if (ffmpeg.stdin && !ffmpeg.stdin.destroyed) {
197
+ await new Promise((resolve) => {
198
+ ffmpeg.stdin.end(() => resolve());
199
+ });
200
+ }
201
+ // Wait for FFmpeg to finish
202
+ await exitPromise;
203
+ const durationMs = Date.now() - startTime;
204
+ if (signal?.aborted) {
205
+ return {
206
+ success: false,
207
+ durationMs,
208
+ fileSize: 0,
209
+ error: "Streaming encode cancelled",
210
+ };
211
+ }
212
+ if (exitCode !== 0) {
213
+ return {
214
+ success: false,
215
+ durationMs,
216
+ fileSize: 0,
217
+ error: `FFmpeg exited with code ${exitCode}`,
218
+ };
219
+ }
220
+ const fileSize = existsSync(outputPath) ? statSync(outputPath).size : 0;
221
+ return { success: true, durationMs, fileSize };
222
+ },
223
+ getExitStatus: () => exitStatus,
224
+ };
225
+ return encoder;
226
+ }
227
+ //# sourceMappingURL=streamingEncoder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streamingEncoder.js","sourceRoot":"","sources":["../../src/services/streamingEncoder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Video Frame Extractor Service
3
+ *
4
+ * Pre-extracts video frames using FFmpeg for frame-accurate rendering.
5
+ * Videos are replaced with <img> elements during capture.
6
+ */
7
+ import { type VideoMetadata } from "../utils/ffprobe.js";
8
+ import { type EngineConfig } from "../config.js";
9
+ export interface VideoElement {
10
+ id: string;
11
+ src: string;
12
+ start: number;
13
+ end: number;
14
+ mediaStart: number;
15
+ hasAudio: boolean;
16
+ }
17
+ export interface ExtractedFrames {
18
+ videoId: string;
19
+ srcPath: string;
20
+ outputDir: string;
21
+ framePattern: string;
22
+ fps: number;
23
+ totalFrames: number;
24
+ metadata: VideoMetadata;
25
+ framePaths: Map<number, string>;
26
+ }
27
+ export interface ExtractionOptions {
28
+ fps: number;
29
+ outputDir: string;
30
+ quality?: number;
31
+ format?: "jpg" | "png";
32
+ }
33
+ export interface ExtractionResult {
34
+ success: boolean;
35
+ extracted: ExtractedFrames[];
36
+ errors: Array<{
37
+ videoId: string;
38
+ error: string;
39
+ }>;
40
+ totalFramesExtracted: number;
41
+ durationMs: number;
42
+ }
43
+ export declare function parseVideoElements(html: string): VideoElement[];
44
+ export declare function extractVideoFramesRange(videoPath: string, videoId: string, startTime: number, duration: number, options: ExtractionOptions, signal?: AbortSignal, config?: Partial<Pick<EngineConfig, "ffmpegProcessTimeout">>): Promise<ExtractedFrames>;
45
+ export declare function extractAllVideoFrames(videos: VideoElement[], baseDir: string, options: ExtractionOptions, signal?: AbortSignal, config?: Partial<Pick<EngineConfig, "ffmpegProcessTimeout">>): Promise<ExtractionResult>;
46
+ export declare function getFrameAtTime(extracted: ExtractedFrames, globalTime: number, videoStart: number): string | null;
47
+ export declare class FrameLookupTable {
48
+ private videos;
49
+ private orderedVideos;
50
+ private activeVideoIds;
51
+ private startCursor;
52
+ private lastTime;
53
+ addVideo(extracted: ExtractedFrames, start: number, end: number, mediaStart: number): void;
54
+ getFrame(videoId: string, globalTime: number): string | null;
55
+ private resetActiveState;
56
+ private refreshActiveSet;
57
+ getActiveFramePayloads(globalTime: number): Map<string, {
58
+ framePath: string;
59
+ frameIndex: number;
60
+ }>;
61
+ getActiveFrames(globalTime: number): Map<string, string>;
62
+ cleanup(): void;
63
+ }
64
+ export declare function createFrameLookupTable(videos: VideoElement[], extracted: ExtractedFrames[]): FrameLookupTable;
65
+ //# sourceMappingURL=videoFrameExtractor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"videoFrameExtractor.d.ts","sourceRoot":"","sources":["../../src/services/videoFrameExtractor.ts"],"names":[],"mappings":"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"}