@studiomeyer/mcp-video 1.0.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 (184) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +31 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
  3. package/.github/workflows/ci.yml +34 -0
  4. package/CHANGELOG.md +24 -0
  5. package/CONTRIBUTING.md +75 -0
  6. package/LICENSE +21 -0
  7. package/README.md +198 -0
  8. package/USAGE.md +144 -0
  9. package/dist/handlers/capcut.d.ts +6 -0
  10. package/dist/handlers/capcut.js +229 -0
  11. package/dist/handlers/capcut.js.map +1 -0
  12. package/dist/handlers/editing.d.ts +6 -0
  13. package/dist/handlers/editing.js +242 -0
  14. package/dist/handlers/editing.js.map +1 -0
  15. package/dist/handlers/index.d.ts +2 -0
  16. package/dist/handlers/index.js +33 -0
  17. package/dist/handlers/index.js.map +1 -0
  18. package/dist/handlers/post-production.d.ts +5 -0
  19. package/dist/handlers/post-production.js +109 -0
  20. package/dist/handlers/post-production.js.map +1 -0
  21. package/dist/handlers/smart-screenshot.d.ts +5 -0
  22. package/dist/handlers/smart-screenshot.js +83 -0
  23. package/dist/handlers/smart-screenshot.js.map +1 -0
  24. package/dist/handlers/tts.d.ts +5 -0
  25. package/dist/handlers/tts.js +83 -0
  26. package/dist/handlers/tts.js.map +1 -0
  27. package/dist/handlers/video.d.ts +5 -0
  28. package/dist/handlers/video.js +127 -0
  29. package/dist/handlers/video.js.map +1 -0
  30. package/dist/lib/dual-transport.d.ts +42 -0
  31. package/dist/lib/dual-transport.js +208 -0
  32. package/dist/lib/dual-transport.js.map +1 -0
  33. package/dist/lib/logger.d.ts +12 -0
  34. package/dist/lib/logger.js +42 -0
  35. package/dist/lib/logger.js.map +1 -0
  36. package/dist/lib/types.d.ts +16 -0
  37. package/dist/lib/types.js +15 -0
  38. package/dist/lib/types.js.map +1 -0
  39. package/dist/schemas/capcut.d.ts +608 -0
  40. package/dist/schemas/capcut.js +411 -0
  41. package/dist/schemas/capcut.js.map +1 -0
  42. package/dist/schemas/editing.d.ts +822 -0
  43. package/dist/schemas/editing.js +466 -0
  44. package/dist/schemas/editing.js.map +1 -0
  45. package/dist/schemas/index.d.ts +2366 -0
  46. package/dist/schemas/index.js +15 -0
  47. package/dist/schemas/index.js.map +1 -0
  48. package/dist/schemas/post-production.d.ts +379 -0
  49. package/dist/schemas/post-production.js +268 -0
  50. package/dist/schemas/post-production.js.map +1 -0
  51. package/dist/schemas/smart-screenshot.d.ts +127 -0
  52. package/dist/schemas/smart-screenshot.js +122 -0
  53. package/dist/schemas/smart-screenshot.js.map +1 -0
  54. package/dist/schemas/tts.d.ts +220 -0
  55. package/dist/schemas/tts.js +194 -0
  56. package/dist/schemas/tts.js.map +1 -0
  57. package/dist/schemas/video.d.ts +236 -0
  58. package/dist/schemas/video.js +210 -0
  59. package/dist/schemas/video.js.map +1 -0
  60. package/dist/server.d.ts +11 -0
  61. package/dist/server.js +239 -0
  62. package/dist/server.js.map +1 -0
  63. package/dist/server.test.d.ts +1 -0
  64. package/dist/server.test.js +87 -0
  65. package/dist/server.test.js.map +1 -0
  66. package/dist/tools/engine/audio-mixer.d.ts +40 -0
  67. package/dist/tools/engine/audio-mixer.js +169 -0
  68. package/dist/tools/engine/audio-mixer.js.map +1 -0
  69. package/dist/tools/engine/audio.d.ts +22 -0
  70. package/dist/tools/engine/audio.js +73 -0
  71. package/dist/tools/engine/audio.js.map +1 -0
  72. package/dist/tools/engine/beat-sync.d.ts +31 -0
  73. package/dist/tools/engine/beat-sync.js +270 -0
  74. package/dist/tools/engine/beat-sync.js.map +1 -0
  75. package/dist/tools/engine/capture.d.ts +12 -0
  76. package/dist/tools/engine/capture.js +290 -0
  77. package/dist/tools/engine/capture.js.map +1 -0
  78. package/dist/tools/engine/chroma-key.d.ts +27 -0
  79. package/dist/tools/engine/chroma-key.js +154 -0
  80. package/dist/tools/engine/chroma-key.js.map +1 -0
  81. package/dist/tools/engine/concat.d.ts +49 -0
  82. package/dist/tools/engine/concat.js +149 -0
  83. package/dist/tools/engine/concat.js.map +1 -0
  84. package/dist/tools/engine/cursor.d.ts +26 -0
  85. package/dist/tools/engine/cursor.js +185 -0
  86. package/dist/tools/engine/cursor.js.map +1 -0
  87. package/dist/tools/engine/easing.d.ts +15 -0
  88. package/dist/tools/engine/easing.js +100 -0
  89. package/dist/tools/engine/easing.js.map +1 -0
  90. package/dist/tools/engine/editing.d.ts +158 -0
  91. package/dist/tools/engine/editing.js +541 -0
  92. package/dist/tools/engine/editing.js.map +1 -0
  93. package/dist/tools/engine/encoder.d.ts +31 -0
  94. package/dist/tools/engine/encoder.js +154 -0
  95. package/dist/tools/engine/encoder.js.map +1 -0
  96. package/dist/tools/engine/index.d.ts +30 -0
  97. package/dist/tools/engine/index.js +23 -0
  98. package/dist/tools/engine/index.js.map +1 -0
  99. package/dist/tools/engine/lut-presets.d.ts +25 -0
  100. package/dist/tools/engine/lut-presets.js +141 -0
  101. package/dist/tools/engine/lut-presets.js.map +1 -0
  102. package/dist/tools/engine/narrated-video.d.ts +63 -0
  103. package/dist/tools/engine/narrated-video.js +163 -0
  104. package/dist/tools/engine/narrated-video.js.map +1 -0
  105. package/dist/tools/engine/scenes.d.ts +17 -0
  106. package/dist/tools/engine/scenes.js +223 -0
  107. package/dist/tools/engine/scenes.js.map +1 -0
  108. package/dist/tools/engine/smart-screenshot.d.ts +80 -0
  109. package/dist/tools/engine/smart-screenshot.js +744 -0
  110. package/dist/tools/engine/smart-screenshot.js.map +1 -0
  111. package/dist/tools/engine/social-format.d.ts +66 -0
  112. package/dist/tools/engine/social-format.js +107 -0
  113. package/dist/tools/engine/social-format.js.map +1 -0
  114. package/dist/tools/engine/template-renderer.d.ts +45 -0
  115. package/dist/tools/engine/template-renderer.js +233 -0
  116. package/dist/tools/engine/template-renderer.js.map +1 -0
  117. package/dist/tools/engine/templates.d.ts +87 -0
  118. package/dist/tools/engine/templates.js +272 -0
  119. package/dist/tools/engine/templates.js.map +1 -0
  120. package/dist/tools/engine/text-animations.d.ts +33 -0
  121. package/dist/tools/engine/text-animations.js +192 -0
  122. package/dist/tools/engine/text-animations.js.map +1 -0
  123. package/dist/tools/engine/text-overlay.d.ts +27 -0
  124. package/dist/tools/engine/text-overlay.js +84 -0
  125. package/dist/tools/engine/text-overlay.js.map +1 -0
  126. package/dist/tools/engine/tts.d.ts +54 -0
  127. package/dist/tools/engine/tts.js +186 -0
  128. package/dist/tools/engine/tts.js.map +1 -0
  129. package/dist/tools/engine/types.d.ts +166 -0
  130. package/dist/tools/engine/types.js +13 -0
  131. package/dist/tools/engine/types.js.map +1 -0
  132. package/dist/tools/engine/voice-effects.d.ts +18 -0
  133. package/dist/tools/engine/voice-effects.js +215 -0
  134. package/dist/tools/engine/voice-effects.js.map +1 -0
  135. package/dist/tools/index.d.ts +32 -0
  136. package/dist/tools/index.js +23 -0
  137. package/dist/tools/index.js.map +1 -0
  138. package/package.json +56 -0
  139. package/scripts/check-deps.js +39 -0
  140. package/src/handlers/capcut.ts +245 -0
  141. package/src/handlers/editing.ts +260 -0
  142. package/src/handlers/index.ts +34 -0
  143. package/src/handlers/post-production.ts +136 -0
  144. package/src/handlers/smart-screenshot.ts +86 -0
  145. package/src/handlers/tts.ts +103 -0
  146. package/src/handlers/video.ts +137 -0
  147. package/src/lib/dual-transport.ts +272 -0
  148. package/src/lib/logger.ts +59 -0
  149. package/src/lib/types.ts +25 -0
  150. package/src/schemas/capcut.ts +418 -0
  151. package/src/schemas/editing.ts +476 -0
  152. package/src/schemas/index.ts +15 -0
  153. package/src/schemas/post-production.ts +273 -0
  154. package/src/schemas/smart-screenshot.ts +122 -0
  155. package/src/schemas/tts.ts +197 -0
  156. package/src/schemas/video.ts +211 -0
  157. package/src/server.test.ts +99 -0
  158. package/src/server.ts +289 -0
  159. package/src/tools/engine/audio-mixer.ts +244 -0
  160. package/src/tools/engine/audio.ts +115 -0
  161. package/src/tools/engine/beat-sync.ts +356 -0
  162. package/src/tools/engine/capture.ts +360 -0
  163. package/src/tools/engine/chroma-key.ts +202 -0
  164. package/src/tools/engine/concat.ts +242 -0
  165. package/src/tools/engine/cursor.ts +222 -0
  166. package/src/tools/engine/easing.ts +120 -0
  167. package/src/tools/engine/editing.ts +809 -0
  168. package/src/tools/engine/encoder.ts +208 -0
  169. package/src/tools/engine/index.ts +33 -0
  170. package/src/tools/engine/lut-presets.ts +235 -0
  171. package/src/tools/engine/narrated-video.ts +267 -0
  172. package/src/tools/engine/scenes.ts +309 -0
  173. package/src/tools/engine/smart-screenshot.ts +923 -0
  174. package/src/tools/engine/social-format.ts +146 -0
  175. package/src/tools/engine/template-renderer.ts +294 -0
  176. package/src/tools/engine/templates.ts +370 -0
  177. package/src/tools/engine/text-animations.ts +282 -0
  178. package/src/tools/engine/text-overlay.ts +143 -0
  179. package/src/tools/engine/tts.ts +284 -0
  180. package/src/tools/engine/types.ts +191 -0
  181. package/src/tools/engine/voice-effects.ts +258 -0
  182. package/src/tools/index.ts +67 -0
  183. package/tsconfig.json +19 -0
  184. package/vitest.config.ts +7 -0
@@ -0,0 +1,290 @@
1
+ /**
2
+ * Frame-by-Frame Capture Engine
3
+ * The core of cinema-grade website video recording
4
+ *
5
+ * Pipeline: Playwright → Frame Screenshots → ffmpeg → MP4
6
+ * Result: Perfect 60fps video with zero frame drops
7
+ */
8
+ import { chromium } from 'playwright';
9
+ import * as fs from 'fs';
10
+ import * as path from 'path';
11
+ import * as os from 'os';
12
+ import { logger } from '../../lib/logger.js';
13
+ import { VIEWPORTS } from './types.js';
14
+ import { injectCursor, hideCursor } from './cursor.js';
15
+ import { executeScenes, createDefaultScenes } from './scenes.js';
16
+ import { encodeFrames, cleanupFrames } from './encoder.js';
17
+ const OUTPUT_DIR = process.env.VIDEO_OUTPUT_DIR || './output';
18
+ /**
19
+ * Record a website with cinema-quality frame-by-frame capture
20
+ */
21
+ export async function recordWebsite(config) {
22
+ const { url, outputPath = path.join(OUTPUT_DIR, `website-video-${Date.now()}`), fps = 60, scenes: userScenes, cursor = { enabled: true }, encoding = {}, dismissOverlays = true, preloadContent = true, deviceScaleFactor = 1, darkMode = false, disableSmoothScroll = true, } = config;
23
+ // Resolve viewport
24
+ const viewport = typeof config.viewport === 'string'
25
+ ? VIEWPORTS[config.viewport] ?? VIEWPORTS.desktop
26
+ : config.viewport ?? VIEWPORTS.desktop;
27
+ // Create temp directory for frames
28
+ const framesDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cinema-frames-'));
29
+ const framePattern = 'frame_%06d.png';
30
+ let browser;
31
+ let totalFrames = 0;
32
+ const startTime = Date.now();
33
+ try {
34
+ logger.info(`Starting cinema capture: ${url} (${viewport.width}x${viewport.height}, ${fps}fps)`);
35
+ // ─── 1. Launch Browser ────────────────────────────────────────
36
+ browser = await chromium.launch({
37
+ headless: true,
38
+ args: [
39
+ '--no-sandbox',
40
+ '--disable-setuid-sandbox',
41
+ '--disable-dev-shm-usage',
42
+ '--disable-gpu',
43
+ '--disable-web-security',
44
+ '--hide-scrollbars',
45
+ ],
46
+ });
47
+ const contextOptions = {
48
+ viewport: { width: viewport.width, height: viewport.height },
49
+ deviceScaleFactor,
50
+ colorScheme: darkMode ? 'dark' : 'light',
51
+ };
52
+ // Mobile user agent
53
+ if (config.viewport === 'mobile' || config.viewport === 'mobile-landscape') {
54
+ contextOptions.userAgent = config.userAgent ??
55
+ 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1';
56
+ contextOptions.isMobile = true;
57
+ contextOptions.hasTouch = true;
58
+ }
59
+ else if (config.userAgent) {
60
+ contextOptions.userAgent = config.userAgent;
61
+ }
62
+ const context = await browser.newContext(contextOptions);
63
+ const page = await context.newPage();
64
+ // ─── 2. Navigate to URL ───────────────────────────────────────
65
+ logger.info(`Navigating to ${url}...`);
66
+ await page.goto(url, { waitUntil: 'networkidle', timeout: 30000 }).catch(async () => {
67
+ // Fallback: try with just domcontentloaded
68
+ await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
69
+ });
70
+ // Wait for content to render
71
+ await page.waitForTimeout(2000);
72
+ // ─── 3. Prepare Page ──────────────────────────────────────────
73
+ // Disable CSS smooth scroll to prevent double-easing
74
+ if (disableSmoothScroll) {
75
+ await page.addStyleTag({
76
+ content: `*, html { scroll-behavior: auto !important; }`,
77
+ });
78
+ }
79
+ // Hide scrollbar
80
+ await page.addStyleTag({
81
+ content: `::-webkit-scrollbar { display: none !important; } * { scrollbar-width: none !important; }`,
82
+ });
83
+ // Dismiss overlays and cookie banners
84
+ if (dismissOverlays) {
85
+ await dismissPageOverlays(page);
86
+ // Wait for animations to complete (e.g. Framer Motion exit)
87
+ await page.waitForTimeout(1000);
88
+ }
89
+ // ─── 4. Pre-scroll to trigger lazy loading ────────────────────
90
+ if (preloadContent) {
91
+ await preloadAllContent(page, viewport.height);
92
+ }
93
+ // ─── 5. Inject cursor overlay ─────────────────────────────────
94
+ if (cursor.enabled) {
95
+ await injectCursor(page, cursor);
96
+ }
97
+ // Scroll back to top
98
+ await page.evaluate(() => window.scrollTo(0, 0));
99
+ await page.waitForTimeout(500);
100
+ // ─── 6. Execute scenes & capture frames ───────────────────────
101
+ const scenes = userScenes && userScenes.length > 0
102
+ ? userScenes
103
+ : createDefaultScenes();
104
+ logger.info(`Executing ${scenes.length} scene(s)...`);
105
+ let frameIndex = 0;
106
+ totalFrames = await executeScenes(page, scenes, fps, async (_fi) => {
107
+ const framePath = path.join(framesDir, `frame_${String(frameIndex).padStart(6, '0')}.png`);
108
+ await page.screenshot({ path: framePath, type: 'png' });
109
+ frameIndex++;
110
+ // Progress logging every 60 frames (= 1 second at 60fps)
111
+ if (frameIndex % fps === 0) {
112
+ logger.info(`Captured ${frameIndex} frames (${(frameIndex / fps).toFixed(1)}s)`);
113
+ }
114
+ }, cursor.enabled);
115
+ // Hide cursor for final frame
116
+ if (cursor.enabled) {
117
+ await hideCursor(page);
118
+ }
119
+ // Take thumbnail from first frame position
120
+ await page.evaluate(() => window.scrollTo(0, 0));
121
+ await page.waitForTimeout(300);
122
+ const thumbnailBuffer = await page.screenshot({ type: 'png' });
123
+ const thumbnailPath = `${outputPath}-thumbnail.png`;
124
+ const thumbnailDir = path.dirname(thumbnailPath);
125
+ if (!fs.existsSync(thumbnailDir)) {
126
+ fs.mkdirSync(thumbnailDir, { recursive: true });
127
+ }
128
+ fs.writeFileSync(thumbnailPath, thumbnailBuffer);
129
+ // ─── 7. Close browser ─────────────────────────────────────────
130
+ await context.close();
131
+ await browser.close();
132
+ browser = undefined;
133
+ logger.info(`Capture complete: ${totalFrames} frames in ${((Date.now() - startTime) / 1000).toFixed(1)}s`);
134
+ // ─── 8. Encode video ──────────────────────────────────────────
135
+ logger.info('Encoding video with ffmpeg...');
136
+ const encodeResult = await encodeFrames(framesDir, framePattern, outputPath, totalFrames, { ...encoding, fps });
137
+ // ─── 9. Cleanup frames ────────────────────────────────────────
138
+ cleanupFrames(framesDir);
139
+ const captureTimeSec = ((Date.now() - startTime) / 1000).toFixed(1);
140
+ logger.info(`Video ready: ${encodeResult.outputPath} (${encodeResult.sizeMB} MB, ${captureTimeSec}s total)`);
141
+ return {
142
+ success: true,
143
+ video: {
144
+ path: encodeResult.outputPath,
145
+ format: encodeResult.format,
146
+ codec: encodeResult.codec,
147
+ fps: encodeResult.fps,
148
+ duration: encodeResult.duration,
149
+ totalFrames: encodeResult.totalFrames,
150
+ resolution: { width: viewport.width, height: viewport.height },
151
+ sizeBytes: encodeResult.sizeBytes,
152
+ sizeMB: encodeResult.sizeMB,
153
+ },
154
+ thumbnail: {
155
+ path: thumbnailPath,
156
+ width: viewport.width,
157
+ height: viewport.height,
158
+ },
159
+ scenes: scenes.length,
160
+ url,
161
+ captureTime: `${captureTimeSec}s`,
162
+ };
163
+ }
164
+ catch (error) {
165
+ // Cleanup on error
166
+ cleanupFrames(framesDir);
167
+ if (browser) {
168
+ try {
169
+ await browser.close();
170
+ }
171
+ catch { /* ignore */ }
172
+ }
173
+ const message = error instanceof Error ? error.message : String(error);
174
+ logger.error(`Recording failed: ${message}`);
175
+ throw new Error(`Recording failed: ${message}`);
176
+ }
177
+ }
178
+ /**
179
+ * Dismiss cookie banners, fixed overlays, and popups
180
+ * Uses multiple strategies: localStorage pre-set, button clicks, CSS hiding
181
+ */
182
+ async function dismissPageOverlays(page) {
183
+ logger.info('Dismissing overlays and cookie banners...');
184
+ // Strategy 1: Pre-set common cookie consent localStorage/cookie values
185
+ // This prevents banners from appearing in the first place
186
+ await page.evaluate(() => {
187
+ // Common cookie consent localStorage key
188
+ localStorage.setItem('cookie-consent', 'accepted');
189
+ // Common cookie consent libraries
190
+ localStorage.setItem('cookieConsent', 'accepted');
191
+ localStorage.setItem('cookie_consent', 'true');
192
+ localStorage.setItem('cookies-accepted', 'true');
193
+ localStorage.setItem('gdpr-consent', 'true');
194
+ localStorage.setItem('CookieConsent', 'true');
195
+ // CookieBot
196
+ localStorage.setItem('CookieConsentV2', '{"stamp":"","necessary":true,"preferences":true,"statistics":true,"marketing":true}');
197
+ // OneTrust
198
+ localStorage.setItem('OptanonAlertBoxClosed', new Date().toISOString());
199
+ // Set cookies too
200
+ document.cookie = 'cookie-consent=accepted; path=/; max-age=31536000';
201
+ document.cookie = 'cookieconsent_status=dismiss; path=/; max-age=31536000';
202
+ // Dispatch custom consent event
203
+ window.dispatchEvent(new Event('cookie-consent-accepted'));
204
+ });
205
+ await page.waitForTimeout(300);
206
+ // Strategy 2: Try clicking accept buttons (multilingual)
207
+ const acceptButtonSelectors = [
208
+ // By text content (most reliable)
209
+ 'button:has-text("Akzeptieren")',
210
+ 'button:has-text("Accept")',
211
+ 'button:has-text("Aceptar")',
212
+ 'button:has-text("Accept all")',
213
+ 'button:has-text("Alle akzeptieren")',
214
+ 'button:has-text("Got it")',
215
+ 'button:has-text("OK")',
216
+ 'button:has-text("Verstanden")',
217
+ ];
218
+ for (const selector of acceptButtonSelectors) {
219
+ try {
220
+ const btn = page.locator(selector).first();
221
+ if (await btn.isVisible({ timeout: 500 }).catch(() => false)) {
222
+ await btn.click({ timeout: 1000 });
223
+ logger.info(`Clicked consent button: ${selector}`);
224
+ break;
225
+ }
226
+ }
227
+ catch {
228
+ // Button not found, try next
229
+ }
230
+ }
231
+ // Strategy 3: Click by CSS selectors (fallback)
232
+ await page.evaluate(() => {
233
+ const cssSelectors = [
234
+ '[class*="cookie"] button',
235
+ '[class*="consent"] button',
236
+ '[id*="cookie"] button',
237
+ '[id*="consent"] button',
238
+ 'button[class*="accept"]',
239
+ 'button[class*="Accept"]',
240
+ '[data-testid*="cookie"] button',
241
+ ];
242
+ for (const sel of cssSelectors) {
243
+ const btn = document.querySelector(sel);
244
+ if (btn) {
245
+ btn.click();
246
+ break;
247
+ }
248
+ }
249
+ });
250
+ await page.waitForTimeout(500);
251
+ // Strategy 4: Force hide any remaining overlays via CSS
252
+ await page.addStyleTag({
253
+ content: `
254
+ [class*="cookie"], [class*="Cookie"],
255
+ [class*="consent"], [class*="Consent"],
256
+ [class*="popup"]:not([class*="menu"]),
257
+ [class*="Popup"]:not([class*="menu"]),
258
+ [id*="cookie"], [id*="consent"],
259
+ [role="dialog"],
260
+ [class*="banner"]:not(header):not(nav):not([class*="hero"]),
261
+ .fixed.bottom-0.left-0.right-0.z-50 {
262
+ display: none !important;
263
+ visibility: hidden !important;
264
+ opacity: 0 !important;
265
+ pointer-events: none !important;
266
+ }
267
+ `,
268
+ });
269
+ logger.info('Overlays dismissed');
270
+ }
271
+ /**
272
+ * Pre-scroll entire page to trigger all lazy-loaded content
273
+ */
274
+ async function preloadAllContent(page, viewportHeight) {
275
+ logger.info('Pre-scrolling to trigger lazy content...');
276
+ const scrollHeight = await page.evaluate(() => document.documentElement.scrollHeight);
277
+ const steps = Math.ceil(scrollHeight / (viewportHeight * 0.7));
278
+ for (let i = 0; i <= steps; i++) {
279
+ const y = Math.min(i * viewportHeight * 0.7, scrollHeight);
280
+ await page.evaluate((scrollY) => window.scrollTo(0, scrollY), y);
281
+ await page.waitForTimeout(200);
282
+ // Wait for any network requests to settle
283
+ await page.waitForLoadState('networkidle').catch(() => { });
284
+ }
285
+ // Scroll back to top
286
+ await page.evaluate(() => window.scrollTo(0, 0));
287
+ await page.waitForTimeout(500);
288
+ logger.info('Lazy content preloaded');
289
+ }
290
+ //# sourceMappingURL=capture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture.js","sourceRoot":"","sources":["../../../src/tools/engine/capture.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE7C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,UAAU,CAAC;AAE9D;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAuB;IACzD,MAAM,EACJ,GAAG,EACH,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,iBAAiB,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,EACjE,GAAG,GAAG,EAAE,EACR,MAAM,EAAE,UAAU,EAClB,MAAM,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,EAC1B,QAAQ,GAAG,EAAE,EACb,eAAe,GAAG,IAAI,EACtB,cAAc,GAAG,IAAI,EACrB,iBAAiB,GAAG,CAAC,EACrB,QAAQ,GAAG,KAAK,EAChB,mBAAmB,GAAG,IAAI,GAC3B,GAAG,MAAM,CAAC;IAEX,mBAAmB;IACnB,MAAM,QAAQ,GAAmB,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ;QAClE,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAC,OAAO;QACjD,CAAC,CAAC,MAAM,CAAC,QAAQ,IAAI,SAAS,CAAC,OAAO,CAAC;IAEzC,mCAAmC;IACnC,MAAM,SAAS,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAC3E,MAAM,YAAY,GAAG,gBAAgB,CAAC;IAEtC,IAAI,OAA4B,CAAC;IACjC,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAE7B,IAAI,CAAC;QACH,MAAM,CAAC,IAAI,CAAC,4BAA4B,GAAG,KAAK,QAAQ,CAAC,KAAK,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC;QAEjG,iEAAiE;QACjE,OAAO,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC;YAC9B,QAAQ,EAAE,IAAI;YACd,IAAI,EAAE;gBACJ,cAAc;gBACd,0BAA0B;gBAC1B,yBAAyB;gBACzB,eAAe;gBACf,wBAAwB;gBACxB,mBAAmB;aACpB;SACF,CAAC,CAAC;QAEH,MAAM,cAAc,GAA4B;YAC9C,QAAQ,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE;YAC5D,iBAAiB;YACjB,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC,MAAe,CAAC,CAAC,CAAC,OAAgB;SAC3D,CAAC;QAEF,oBAAoB;QACpB,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,KAAK,kBAAkB,EAAE,CAAC;YAC3E,cAAc,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS;gBACzC,yIAAyI,CAAC;YAC5I,cAAc,CAAC,QAAQ,GAAG,IAAI,CAAC;YAC/B,cAAc,CAAC,QAAQ,GAAG,IAAI,CAAC;QACjC,CAAC;aAAM,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;YAC5B,cAAc,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC;QAC9C,CAAC;QAED,MAAM,OAAO,GAAmB,MAAM,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;QACzE,MAAM,IAAI,GAAS,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;QAE3C,iEAAiE;QACjE,MAAM,CAAC,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAC,CAAC;QACvC,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE;YAClF,2CAA2C;YAC3C,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,kBAAkB,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1E,CAAC,CAAC,CAAC;QAEH,6BAA6B;QAC7B,MAAM,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;QAEhC,iEAAiE;QAEjE,qDAAqD;QACrD,IAAI,mBAAmB,EAAE,CAAC;YACxB,MAAM,IAAI,CAAC,WAAW,CAAC;gBACrB,OAAO,EAAE,+CAA+C;aACzD,CAAC,CAAC;QACL,CAAC;QAED,iBAAiB;QACjB,MAAM,IAAI,CAAC,WAAW,CAAC;YACrB,OAAO,EAAE,2FAA2F;SACrG,CAAC,CAAC;QAEH,sCAAsC;QACtC,IAAI,eAAe,EAAE,CAAC;YACpB,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC;YAChC,4DAA4D;YAC5D,MAAM,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC;QAED,iEAAiE;QACjE,IAAI,cAAc,EAAE,CAAC;YACnB,MAAM,iBAAiB,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;QACjD,CAAC;QAED,iEAAiE;QACjE,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,MAAM,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACnC,CAAC;QAED,qBAAqB;QACrB,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACjD,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;QAE/B,iEAAiE;QACjE,MAAM,MAAM,GAAY,UAAU,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;YACzD,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,mBAAmB,EAAE,CAAC;QAE1B,MAAM,CAAC,IAAI,CAAC,aAAa,MAAM,CAAC,MAAM,cAAc,CAAC,CAAC;QAEtD,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,WAAW,GAAG,MAAM,aAAa,CAC/B,IAAI,EACJ,MAAM,EACN,GAAG,EACH,KAAK,EAAE,GAAW,EAAE,EAAE;YACpB,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,MAAM,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;YAC3F,MAAM,IAAI,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YACxD,UAAU,EAAE,CAAC;YAEb,yDAAyD;YACzD,IAAI,UAAU,GAAG,GAAG,KAAK,CAAC,EAAE,CAAC;gBAC3B,MAAM,CAAC,IAAI,CAAC,YAAY,UAAU,YAAY,CAAC,UAAU,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACnF,CAAC;QACH,CAAC,EACD,MAAM,CAAC,OAAO,CACf,CAAC;QAEF,8BAA8B;QAC9B,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;QACzB,CAAC;QAED,2CAA2C;QAC3C,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACjD,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;QAC/B,MAAM,eAAe,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC/D,MAAM,aAAa,GAAG,GAAG,UAAU,gBAAgB,CAAC;QACpD,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;QACjD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YACjC,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,CAAC;QACD,EAAE,CAAC,aAAa,CAAC,aAAa,EAAE,eAAe,CAAC,CAAC;QAEjD,iEAAiE;QACjE,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QACtB,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QACtB,OAAO,GAAG,SAAS,CAAC;QAEpB,MAAM,CAAC,IAAI,CAAC,qBAAqB,WAAW,cAAc,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAE3G,iEAAiE;QACjE,MAAM,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;QAC7C,MAAM,YAAY,GAAG,MAAM,YAAY,CACrC,SAAS,EACT,YAAY,EACZ,UAAU,EACV,WAAW,EACX,EAAE,GAAG,QAAQ,EAAE,GAAG,EAAE,CACrB,CAAC;QAEF,iEAAiE;QACjE,aAAa,CAAC,SAAS,CAAC,CAAC;QAEzB,MAAM,cAAc,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACpE,MAAM,CAAC,IAAI,CAAC,gBAAgB,YAAY,CAAC,UAAU,KAAK,YAAY,CAAC,MAAM,QAAQ,cAAc,UAAU,CAAC,CAAC;QAE7G,OAAO;YACL,OAAO,EAAE,IAAI;YACb,KAAK,EAAE;gBACL,IAAI,EAAE,YAAY,CAAC,UAAU;gBAC7B,MAAM,EAAE,YAAY,CAAC,MAAM;gBAC3B,KAAK,EAAE,YAAY,CAAC,KAAK;gBACzB,GAAG,EAAE,YAAY,CAAC,GAAG;gBACrB,QAAQ,EAAE,YAAY,CAAC,QAAQ;gBAC/B,WAAW,EAAE,YAAY,CAAC,WAAW;gBACrC,UAAU,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE;gBAC9D,SAAS,EAAE,YAAY,CAAC,SAAS;gBACjC,MAAM,EAAE,YAAY,CAAC,MAAM;aAC5B;YACD,SAAS,EAAE;gBACT,IAAI,EAAE,aAAa;gBACnB,KAAK,EAAE,QAAQ,CAAC,KAAK;gBACrB,MAAM,EAAE,QAAQ,CAAC,MAAM;aACxB;YACD,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,GAAG;YACH,WAAW,EAAE,GAAG,cAAc,GAAG;SAClC,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,mBAAmB;QACnB,aAAa,CAAC,SAAS,CAAC,CAAC;QACzB,IAAI,OAAO,EAAE,CAAC;YACZ,IAAI,CAAC;gBAAC,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;QACvD,CAAC;QAED,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,MAAM,CAAC,KAAK,CAAC,qBAAqB,OAAO,EAAE,CAAC,CAAC;QAC7C,MAAM,IAAI,KAAK,CAAC,qBAAqB,OAAO,EAAE,CAAC,CAAC;IAClD,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,mBAAmB,CAAC,IAAU;IAC3C,MAAM,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAC;IAEzD,uEAAuE;IACvE,0DAA0D;IAC1D,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE;QACvB,yCAAyC;QACzC,YAAY,CAAC,OAAO,CAAC,gBAAgB,EAAE,UAAU,CAAC,CAAC;QACnD,kCAAkC;QAClC,YAAY,CAAC,OAAO,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;QAClD,YAAY,CAAC,OAAO,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;QAC/C,YAAY,CAAC,OAAO,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC;QACjD,YAAY,CAAC,OAAO,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;QAC7C,YAAY,CAAC,OAAO,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC;QAC9C,YAAY;QACZ,YAAY,CAAC,OAAO,CAAC,iBAAiB,EAAE,qFAAqF,CAAC,CAAC;QAC/H,WAAW;QACX,YAAY,CAAC,OAAO,CAAC,uBAAuB,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;QAExE,kBAAkB;QAClB,QAAQ,CAAC,MAAM,GAAG,mDAAmD,CAAC;QACtE,QAAQ,CAAC,MAAM,GAAG,wDAAwD,CAAC;QAE3E,gCAAgC;QAChC,MAAM,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;IAE/B,yDAAyD;IACzD,MAAM,qBAAqB,GAAG;QAC5B,kCAAkC;QAClC,gCAAgC;QAChC,2BAA2B;QAC3B,4BAA4B;QAC5B,+BAA+B;QAC/B,qCAAqC;QACrC,2BAA2B;QAC3B,uBAAuB;QACvB,+BAA+B;KAChC,CAAC;IAEF,KAAK,MAAM,QAAQ,IAAI,qBAAqB,EAAE,CAAC;QAC7C,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,CAAC;YAC3C,IAAI,MAAM,GAAG,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC7D,MAAM,GAAG,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;gBACnC,MAAM,CAAC,IAAI,CAAC,2BAA2B,QAAQ,EAAE,CAAC,CAAC;gBACnD,MAAM;YACR,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,6BAA6B;QAC/B,CAAC;IACH,CAAC;IAED,gDAAgD;IAChD,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE;QACvB,MAAM,YAAY,GAAG;YACnB,0BAA0B;YAC1B,2BAA2B;YAC3B,uBAAuB;YACvB,wBAAwB;YACxB,yBAAyB;YACzB,yBAAyB;YACzB,gCAAgC;SACjC,CAAC;QAEF,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;YAC/B,MAAM,GAAG,GAAG,QAAQ,CAAC,aAAa,CAAc,GAAG,CAAC,CAAC;YACrD,IAAI,GAAG,EAAE,CAAC;gBACR,GAAG,CAAC,KAAK,EAAE,CAAC;gBACZ,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;IAE/B,wDAAwD;IACxD,MAAM,IAAI,CAAC,WAAW,CAAC;QACrB,OAAO,EAAE;;;;;;;;;;;;;;KAcR;KACF,CAAC,CAAC;IAEH,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;AACpC,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,iBAAiB,CAAC,IAAU,EAAE,cAAsB;IACjE,MAAM,CAAC,IAAI,CAAC,0CAA0C,CAAC,CAAC;IAExD,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC;IACtF,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,GAAG,CAAC,cAAc,GAAG,GAAG,CAAC,CAAC,CAAC;IAE/D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;QAChC,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,cAAc,GAAG,GAAG,EAAE,YAAY,CAAC,CAAC;QAC3D,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;QACjE,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;QAE/B,0CAA0C;QAC1C,MAAM,IAAI,CAAC,gBAAgB,CAAC,aAAa,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAC7D,CAAC;IAED,qBAAqB;IACrB,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACjD,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;IAE/B,MAAM,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;AACxC,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Chroma Key Engine — Green screen removal and background replacement.
3
+ *
4
+ * Supports:
5
+ * - chromakey (YUV space, best for green/blue screens)
6
+ * - colorkey (RGB space, best for arbitrary key colors)
7
+ * - despill (removes green/blue color spill on edges)
8
+ * - Composite onto replacement background (video, image, or solid color)
9
+ */
10
+ export interface ChromaKeyConfig {
11
+ /** Input video with green/blue screen */
12
+ inputPath: string;
13
+ outputPath: string;
14
+ /** Key color in hex (e.g., '00FF00' for green, '0000FF' for blue). Default: 00FF00 */
15
+ keyColor?: string;
16
+ /** How close a color must be to the key: 0.01-1.0. Higher = more removal. Default: 0.15 */
17
+ similarity?: number;
18
+ /** Edge softness: 0.0-1.0. Keep low (0.0-0.08) or entire frame becomes transparent. Default: 0.02 */
19
+ blend?: number;
20
+ /** Enable despill to remove green/blue color contamination on edges. Default: true */
21
+ despill?: boolean;
22
+ /** Background replacement — video file, image file, or hex color (e.g., '000000' for black). Required. */
23
+ background: string;
24
+ /** Use colorkey (RGB) instead of chromakey (YUV). Default: false (chromakey) */
25
+ useColorkey?: boolean;
26
+ }
27
+ export declare function applyChromaKey(config: ChromaKeyConfig): Promise<string>;
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Chroma Key Engine — Green screen removal and background replacement.
3
+ *
4
+ * Supports:
5
+ * - chromakey (YUV space, best for green/blue screens)
6
+ * - colorkey (RGB space, best for arbitrary key colors)
7
+ * - despill (removes green/blue color spill on edges)
8
+ * - Composite onto replacement background (video, image, or solid color)
9
+ */
10
+ import { execFile } from 'child_process';
11
+ import * as fs from 'fs';
12
+ import * as path from 'path';
13
+ import { logger } from '../../lib/logger.js';
14
+ // ─── Helpers ────────────────────────────────────────────────────────
15
+ function runFfmpeg(args, timeoutMs = 300_000) {
16
+ return new Promise((resolve, reject) => {
17
+ execFile('ffmpeg', args, { maxBuffer: 100 * 1024 * 1024, timeout: timeoutMs }, (error, stdout, stderr) => {
18
+ if (error) {
19
+ logger.error(`ffmpeg failed: ${stderr}`);
20
+ reject(new Error(`ffmpeg failed: ${stderr || error.message}`));
21
+ return;
22
+ }
23
+ resolve(stdout);
24
+ });
25
+ });
26
+ }
27
+ function ensureDir(filePath) {
28
+ const dir = path.dirname(filePath);
29
+ if (!fs.existsSync(dir))
30
+ fs.mkdirSync(dir, { recursive: true });
31
+ }
32
+ function assertExists(filePath, label = 'File') {
33
+ if (!fs.existsSync(filePath))
34
+ throw new Error(`${label} not found: ${filePath}`);
35
+ }
36
+ function fileInfo(filePath) {
37
+ const stats = fs.statSync(filePath);
38
+ return `${(stats.size / 1024 / 1024).toFixed(2)} MB`;
39
+ }
40
+ function isHexColor(s) {
41
+ return /^[0-9a-fA-F]{6}$/.test(s);
42
+ }
43
+ function isImageFile(filePath) {
44
+ const ext = path.extname(filePath).toLowerCase();
45
+ return ['.png', '.jpg', '.jpeg', '.webp', '.bmp', '.tiff'].includes(ext);
46
+ }
47
+ // ─── Main Function ──────────────────────────────────────────────────
48
+ export async function applyChromaKey(config) {
49
+ const { inputPath, outputPath, keyColor = '00FF00', similarity = 0.15, blend = 0.02, despill: enableDespill = true, background, useColorkey = false, } = config;
50
+ assertExists(inputPath, 'Input video');
51
+ ensureDir(outputPath);
52
+ // Validate key color
53
+ const cleanColor = keyColor.replace(/^#|^0x/, '');
54
+ if (!isHexColor(cleanColor)) {
55
+ throw new Error(`Invalid key color: ${keyColor}. Use 6-digit hex (e.g., 00FF00 for green)`);
56
+ }
57
+ const sim = Math.max(0.01, Math.min(1, similarity));
58
+ const bld = Math.max(0, Math.min(0.1, blend)); // Cap at 0.1 to prevent full-frame transparency
59
+ logger.info(`Chroma key: color=0x${cleanColor}, sim=${sim}, blend=${bld}, despill=${enableDespill}, mode=${useColorkey ? 'colorkey' : 'chromakey'}`);
60
+ // Build the keying filter
61
+ const keyFilter = useColorkey
62
+ ? `colorkey=0x${cleanColor}:${sim}:${bld}`
63
+ : `chromakey=0x${cleanColor}:${sim}:${bld}`;
64
+ // Optional despill
65
+ const despillFilter = enableDespill
66
+ ? getDespillFilter(cleanColor)
67
+ : '';
68
+ const fgFilter = despillFilter
69
+ ? `${keyFilter},${despillFilter}`
70
+ : keyFilter;
71
+ // Determine background type
72
+ const isSolidColor = isHexColor(background.replace(/^#|^0x/, ''));
73
+ const isImage = !isSolidColor && isImageFile(background);
74
+ const isVideo = !isSolidColor && !isImage;
75
+ if (isVideo)
76
+ assertExists(background, 'Background video');
77
+ if (isImage)
78
+ assertExists(background, 'Background image');
79
+ let args;
80
+ if (isSolidColor) {
81
+ // Solid color background — use color source
82
+ const bgColor = background.replace(/^#|^0x/, '');
83
+ const filterComplex = [
84
+ `color=c=0x${bgColor}:s=1920x1080:r=30[bg]`,
85
+ `[0:v]${fgFilter}[fg]`,
86
+ `[bg][fg]overlay=shortest=1[out]`,
87
+ ].join(';');
88
+ args = [
89
+ '-y', '-i', inputPath,
90
+ '-filter_complex', filterComplex,
91
+ '-map', '[out]', '-map', '0:a?',
92
+ '-c:v', 'libx264', '-crf', '18', '-preset', 'medium',
93
+ '-pix_fmt', 'yuv420p', '-movflags', '+faststart',
94
+ outputPath,
95
+ ];
96
+ }
97
+ else if (isImage) {
98
+ // Image background — use loop to make it a stream
99
+ const filterComplex = [
100
+ `[1:v]${fgFilter}[fg]`,
101
+ `[0:v][fg]overlay=shortest=1[out]`,
102
+ ].join(';');
103
+ args = [
104
+ '-y',
105
+ '-loop', '1', '-i', background,
106
+ '-i', inputPath,
107
+ '-filter_complex', filterComplex,
108
+ '-map', '[out]', '-map', '1:a?',
109
+ '-c:v', 'libx264', '-crf', '18', '-preset', 'medium',
110
+ '-pix_fmt', 'yuv420p', '-movflags', '+faststart',
111
+ '-shortest',
112
+ outputPath,
113
+ ];
114
+ }
115
+ else {
116
+ // Video background
117
+ const filterComplex = [
118
+ `[1:v]${fgFilter}[fg]`,
119
+ `[0:v][fg]overlay=shortest=1[out]`,
120
+ ].join(';');
121
+ args = [
122
+ '-y',
123
+ '-i', background,
124
+ '-i', inputPath,
125
+ '-filter_complex', filterComplex,
126
+ '-map', '[out]', '-map', '1:a?',
127
+ '-c:v', 'libx264', '-crf', '18', '-preset', 'medium',
128
+ '-pix_fmt', 'yuv420p', '-movflags', '+faststart',
129
+ '-shortest',
130
+ outputPath,
131
+ ];
132
+ }
133
+ await runFfmpeg(args, 600_000); // Longer timeout for compositing
134
+ logger.info(`Chroma key applied: ${outputPath} (${fileInfo(outputPath)})`);
135
+ return outputPath;
136
+ }
137
+ /** Get despill filter based on key color */
138
+ function getDespillFilter(hexColor) {
139
+ const r = parseInt(hexColor.substring(0, 2), 16);
140
+ const g = parseInt(hexColor.substring(2, 4), 16);
141
+ const b = parseInt(hexColor.substring(4, 6), 16);
142
+ // Determine dominant channel for despill
143
+ if (g > r && g > b) {
144
+ // Green screen — despill green
145
+ return 'despill=type=0:mix=0.5:green=-1';
146
+ }
147
+ else if (b > r && b > g) {
148
+ // Blue screen — despill blue
149
+ return 'despill=type=0:mix=0.5:blue=-1';
150
+ }
151
+ // For other colors, skip despill
152
+ return '';
153
+ }
154
+ //# sourceMappingURL=chroma-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chroma-key.js","sourceRoot":"","sources":["../../../src/tools/engine/chroma-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAsB7C,uEAAuE;AAEvE,SAAS,SAAS,CAAC,IAAc,EAAE,SAAS,GAAG,OAAO;IACpD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACvG,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,CAAC,KAAK,CAAC,kBAAkB,MAAM,EAAE,CAAC,CAAC;gBACzC,MAAM,CAAC,IAAI,KAAK,CAAC,kBAAkB,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;gBAC/D,OAAO;YACT,CAAC;YACD,OAAO,CAAC,MAAM,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,SAAS,CAAC,QAAgB;IACjC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAClE,CAAC;AAED,SAAS,YAAY,CAAC,QAAgB,EAAE,KAAK,GAAG,MAAM;IACpD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,eAAe,QAAQ,EAAE,CAAC,CAAC;AACnF,CAAC;AAED,SAAS,QAAQ,CAAC,QAAgB;IAChC,MAAM,KAAK,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACpC,OAAO,GAAG,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;AACvD,CAAC;AAED,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpC,CAAC;AAED,SAAS,WAAW,CAAC,QAAgB;IACnC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;IACjD,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AAC3E,CAAC;AAED,uEAAuE;AAEvE,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,MAAuB;IAC1D,MAAM,EACJ,SAAS,EACT,UAAU,EACV,QAAQ,GAAG,QAAQ,EACnB,UAAU,GAAG,IAAI,EACjB,KAAK,GAAG,IAAI,EACZ,OAAO,EAAE,aAAa,GAAG,IAAI,EAC7B,UAAU,EACV,WAAW,GAAG,KAAK,GACpB,GAAG,MAAM,CAAC;IAEX,YAAY,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;IACvC,SAAS,CAAC,UAAU,CAAC,CAAC;IAEtB,qBAAqB;IACrB,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAClD,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,sBAAsB,QAAQ,4CAA4C,CAAC,CAAC;IAC9F,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;IACpD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,gDAAgD;IAE/F,MAAM,CAAC,IAAI,CAAC,uBAAuB,UAAU,SAAS,GAAG,WAAW,GAAG,aAAa,aAAa,UAAU,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;IAErJ,0BAA0B;IAC1B,MAAM,SAAS,GAAG,WAAW;QAC3B,CAAC,CAAC,cAAc,UAAU,IAAI,GAAG,IAAI,GAAG,EAAE;QAC1C,CAAC,CAAC,eAAe,UAAU,IAAI,GAAG,IAAI,GAAG,EAAE,CAAC;IAE9C,mBAAmB;IACnB,MAAM,aAAa,GAAG,aAAa;QACjC,CAAC,CAAC,gBAAgB,CAAC,UAAU,CAAC;QAC9B,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,QAAQ,GAAG,aAAa;QAC5B,CAAC,CAAC,GAAG,SAAS,IAAI,aAAa,EAAE;QACjC,CAAC,CAAC,SAAS,CAAC;IAEd,4BAA4B;IAC5B,MAAM,YAAY,GAAG,UAAU,CAAC,UAAU,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC;IAClE,MAAM,OAAO,GAAG,CAAC,YAAY,IAAI,WAAW,CAAC,UAAU,CAAC,CAAC;IACzD,MAAM,OAAO,GAAG,CAAC,YAAY,IAAI,CAAC,OAAO,CAAC;IAE1C,IAAI,OAAO;QAAE,YAAY,CAAC,UAAU,EAAE,kBAAkB,CAAC,CAAC;IAC1D,IAAI,OAAO;QAAE,YAAY,CAAC,UAAU,EAAE,kBAAkB,CAAC,CAAC;IAE1D,IAAI,IAAc,CAAC;IAEnB,IAAI,YAAY,EAAE,CAAC;QACjB,4CAA4C;QAC5C,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QACjD,MAAM,aAAa,GAAG;YACpB,aAAa,OAAO,uBAAuB;YAC3C,QAAQ,QAAQ,MAAM;YACtB,iCAAiC;SAClC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAEZ,IAAI,GAAG;YACL,IAAI,EAAE,IAAI,EAAE,SAAS;YACrB,iBAAiB,EAAE,aAAa;YAChC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM;YAC/B,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ;YACpD,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY;YAChD,UAAU;SACX,CAAC;IACJ,CAAC;SAAM,IAAI,OAAO,EAAE,CAAC;QACnB,kDAAkD;QAClD,MAAM,aAAa,GAAG;YACpB,QAAQ,QAAQ,MAAM;YACtB,kCAAkC;SACnC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAEZ,IAAI,GAAG;YACL,IAAI;YACJ,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU;YAC9B,IAAI,EAAE,SAAS;YACf,iBAAiB,EAAE,aAAa;YAChC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM;YAC/B,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ;YACpD,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY;YAChD,WAAW;YACX,UAAU;SACX,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,mBAAmB;QACnB,MAAM,aAAa,GAAG;YACpB,QAAQ,QAAQ,MAAM;YACtB,kCAAkC;SACnC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAEZ,IAAI,GAAG;YACL,IAAI;YACJ,IAAI,EAAE,UAAU;YAChB,IAAI,EAAE,SAAS;YACf,iBAAiB,EAAE,aAAa;YAChC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM;YAC/B,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ;YACpD,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY;YAChD,WAAW;YACX,UAAU;SACX,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,iCAAiC;IACjE,MAAM,CAAC,IAAI,CAAC,uBAAuB,UAAU,KAAK,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IAC3E,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,4CAA4C;AAC5C,SAAS,gBAAgB,CAAC,QAAgB;IACxC,MAAM,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACjD,MAAM,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACjD,MAAM,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEjD,yCAAyC;IACzC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACnB,+BAA+B;QAC/B,OAAO,iCAAiC,CAAC;IAC3C,CAAC;SAAM,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,6BAA6B;QAC7B,OAAO,gCAAgC,CAAC;IAC1C,CAAC;IACD,iCAAiC;IACjC,OAAO,EAAE,CAAC;AACZ,CAAC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Video concatenation engine — merge multiple clips with cinematic transitions
3
+ */
4
+ export declare const TRANSITIONS: readonly ["fade", "fadeblack", "fadewhite", "dissolve", "wipeleft", "wiperight", "wipeup", "wipedown", "slideleft", "slideright", "slideup", "slidedown", "smoothleft", "smoothright", "smoothup", "smoothdown", "circlecrop", "circleopen", "circleclose", "rectcrop", "vertopen", "vertclose", "horzopen", "horzclose", "diagtl", "diagtr", "diagbl", "diagbr", "hlslice", "hrslice", "vuslice", "vdslice", "radial", "pixelize"];
5
+ export type TransitionType = typeof TRANSITIONS[number];
6
+ export interface ConcatClip {
7
+ /** Path to video file */
8
+ path: string;
9
+ /** Optional: trim start time (seconds) */
10
+ trimStart?: number;
11
+ /** Optional: trim end time (seconds) */
12
+ trimEnd?: number;
13
+ }
14
+ export interface ConcatConfig {
15
+ /** Video clips to concatenate (in order) */
16
+ clips: ConcatClip[];
17
+ /** Output path */
18
+ outputPath: string;
19
+ /** Transition between clips (default: fade) */
20
+ transition?: TransitionType;
21
+ /** Transition duration in seconds (default: 1) */
22
+ transitionDuration?: number;
23
+ /** Normalize all clips to this resolution (default: 1920x1080) */
24
+ targetWidth?: number;
25
+ targetHeight?: number;
26
+ /** Target FPS (default: 60) */
27
+ targetFps?: number;
28
+ }
29
+ export declare function concatenateVideos(config: ConcatConfig): Promise<string>;
30
+ export interface IntroConfig {
31
+ /** Text to display */
32
+ text: string;
33
+ /** Subtitle (optional) */
34
+ subtitle?: string;
35
+ /** Duration in seconds (default: 3) */
36
+ duration?: number;
37
+ /** Background color (default: #0a0a0a) */
38
+ backgroundColor?: string;
39
+ /** Text color (default: white) */
40
+ textColor?: string;
41
+ /** Resolution */
42
+ width?: number;
43
+ height?: number;
44
+ /** FPS (default: 60) */
45
+ fps?: number;
46
+ /** Output path */
47
+ outputPath: string;
48
+ }
49
+ export declare function generateIntro(config: IntroConfig): Promise<string>;