@argo-video/cli 0.1.0 → 0.2.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 (144) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2 -2
  3. package/dist/asset-server.d.ts +7 -0
  4. package/dist/asset-server.d.ts.map +1 -0
  5. package/dist/asset-server.js +69 -0
  6. package/dist/asset-server.js.map +1 -0
  7. package/dist/captions.d.ts +17 -0
  8. package/dist/captions.d.ts.map +1 -0
  9. package/dist/captions.js +23 -0
  10. package/dist/captions.js.map +1 -0
  11. package/dist/cli.d.ts +3 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +87 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/config.d.ts +49 -0
  16. package/dist/config.d.ts.map +1 -0
  17. package/dist/config.js +76 -0
  18. package/dist/config.js.map +1 -0
  19. package/dist/export.d.ts +19 -0
  20. package/dist/export.d.ts.map +1 -0
  21. package/dist/export.js +66 -0
  22. package/dist/export.js.map +1 -0
  23. package/dist/fixtures.d.ts +13 -0
  24. package/dist/fixtures.d.ts.map +1 -0
  25. package/dist/fixtures.js +49 -0
  26. package/dist/fixtures.js.map +1 -0
  27. package/dist/index.d.ts +8 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +14 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/init.d.ts +2 -0
  32. package/dist/init.d.ts.map +1 -0
  33. package/{src/init.ts → dist/init.js} +39 -54
  34. package/dist/init.js.map +1 -0
  35. package/dist/narration.d.ts +32 -0
  36. package/dist/narration.d.ts.map +1 -0
  37. package/dist/narration.js +86 -0
  38. package/dist/narration.js.map +1 -0
  39. package/dist/overlays/index.d.ts +13 -0
  40. package/dist/overlays/index.d.ts.map +1 -0
  41. package/dist/overlays/index.js +45 -0
  42. package/dist/overlays/index.js.map +1 -0
  43. package/dist/overlays/manifest.d.ts +5 -0
  44. package/dist/overlays/manifest.d.ts.map +1 -0
  45. package/dist/overlays/manifest.js +52 -0
  46. package/dist/overlays/manifest.js.map +1 -0
  47. package/dist/overlays/motion.d.ts +4 -0
  48. package/dist/overlays/motion.d.ts.map +1 -0
  49. package/dist/overlays/motion.js +25 -0
  50. package/dist/overlays/motion.js.map +1 -0
  51. package/dist/overlays/templates.d.ts +8 -0
  52. package/dist/overlays/templates.d.ts.map +1 -0
  53. package/dist/overlays/templates.js +102 -0
  54. package/dist/overlays/templates.js.map +1 -0
  55. package/dist/overlays/types.d.ts +46 -0
  56. package/dist/overlays/types.d.ts.map +1 -0
  57. package/dist/overlays/types.js +25 -0
  58. package/dist/overlays/types.js.map +1 -0
  59. package/dist/overlays/zones.d.ts +23 -0
  60. package/dist/overlays/zones.d.ts.map +1 -0
  61. package/dist/overlays/zones.js +117 -0
  62. package/dist/overlays/zones.js.map +1 -0
  63. package/dist/pipeline.d.ts +3 -0
  64. package/dist/pipeline.d.ts.map +1 -0
  65. package/dist/pipeline.js +109 -0
  66. package/dist/pipeline.js.map +1 -0
  67. package/dist/record.d.ts +15 -0
  68. package/dist/record.d.ts.map +1 -0
  69. package/dist/record.js +110 -0
  70. package/dist/record.js.map +1 -0
  71. package/dist/tts/align.d.ts +26 -0
  72. package/dist/tts/align.d.ts.map +1 -0
  73. package/dist/tts/align.js +53 -0
  74. package/dist/tts/align.js.map +1 -0
  75. package/dist/tts/cache.d.ts +31 -0
  76. package/dist/tts/cache.d.ts.map +1 -0
  77. package/dist/tts/cache.js +51 -0
  78. package/dist/tts/cache.js.map +1 -0
  79. package/dist/tts/engine.d.ts +41 -0
  80. package/dist/tts/engine.d.ts.map +1 -0
  81. package/dist/tts/engine.js +108 -0
  82. package/dist/tts/engine.js.map +1 -0
  83. package/dist/tts/generate.d.ts +21 -0
  84. package/dist/tts/generate.d.ts.map +1 -0
  85. package/dist/tts/generate.js +61 -0
  86. package/dist/tts/generate.js.map +1 -0
  87. package/dist/tts/kokoro.d.ts +30 -0
  88. package/dist/tts/kokoro.d.ts.map +1 -0
  89. package/dist/tts/kokoro.js +66 -0
  90. package/dist/tts/kokoro.js.map +1 -0
  91. package/package.json +13 -1
  92. package/.claude/settings.local.json +0 -34
  93. package/DESIGN.md +0 -261
  94. package/docs/enhancement-proposal.md +0 -262
  95. package/docs/superpowers/plans/2026-03-12-argo.md +0 -208
  96. package/docs/superpowers/plans/2026-03-12-editorial-overlay-system.md +0 -1560
  97. package/docs/superpowers/plans/2026-03-13-npm-rename-skill-showcase.md +0 -499
  98. package/docs/superpowers/specs/2026-03-13-npm-rename-skill-showcase-design.md +0 -109
  99. package/skills/argo-demo-creator.md +0 -355
  100. package/src/asset-server.ts +0 -81
  101. package/src/captions.ts +0 -36
  102. package/src/cli.ts +0 -97
  103. package/src/config.ts +0 -125
  104. package/src/export.ts +0 -93
  105. package/src/fixtures.ts +0 -50
  106. package/src/index.ts +0 -41
  107. package/src/narration.ts +0 -31
  108. package/src/overlays/index.ts +0 -54
  109. package/src/overlays/manifest.ts +0 -68
  110. package/src/overlays/motion.ts +0 -27
  111. package/src/overlays/templates.ts +0 -121
  112. package/src/overlays/types.ts +0 -73
  113. package/src/overlays/zones.ts +0 -82
  114. package/src/pipeline.ts +0 -120
  115. package/src/record.ts +0 -123
  116. package/src/tts/align.ts +0 -75
  117. package/src/tts/cache.ts +0 -65
  118. package/src/tts/engine.ts +0 -147
  119. package/src/tts/generate.ts +0 -83
  120. package/src/tts/kokoro.ts +0 -51
  121. package/tests/asset-server.test.ts +0 -67
  122. package/tests/captions.test.ts +0 -76
  123. package/tests/cli.test.ts +0 -131
  124. package/tests/config.test.ts +0 -150
  125. package/tests/e2e/fake-server.ts +0 -45
  126. package/tests/e2e/record.e2e.test.ts +0 -131
  127. package/tests/export.test.ts +0 -155
  128. package/tests/fixtures.test.ts +0 -74
  129. package/tests/init.test.ts +0 -77
  130. package/tests/narration.test.ts +0 -120
  131. package/tests/overlays/index.test.ts +0 -73
  132. package/tests/overlays/manifest.test.ts +0 -120
  133. package/tests/overlays/motion.test.ts +0 -34
  134. package/tests/overlays/templates.test.ts +0 -69
  135. package/tests/overlays/types.test.ts +0 -36
  136. package/tests/overlays/zones.test.ts +0 -49
  137. package/tests/pipeline.test.ts +0 -177
  138. package/tests/record.test.ts +0 -87
  139. package/tests/tts/align.test.ts +0 -118
  140. package/tests/tts/cache.test.ts +0 -110
  141. package/tests/tts/engine.test.ts +0 -204
  142. package/tests/tts/generate.test.ts +0 -177
  143. package/tests/tts/kokoro.test.ts +0 -25
  144. package/tsconfig.json +0 -19
@@ -0,0 +1,25 @@
1
+ export const ZONES = [
2
+ 'bottom-center',
3
+ 'top-left',
4
+ 'top-right',
5
+ 'bottom-left',
6
+ 'bottom-right',
7
+ 'center',
8
+ ];
9
+ export const TEMPLATE_TYPES = [
10
+ 'lower-third',
11
+ 'headline-card',
12
+ 'callout',
13
+ 'image-card',
14
+ ];
15
+ export const MOTIONS = ['none', 'fade-in', 'slide-in'];
16
+ export function isValidZone(value) {
17
+ return ZONES.includes(value);
18
+ }
19
+ export function isValidTemplateType(value) {
20
+ return TEMPLATE_TYPES.includes(value);
21
+ }
22
+ export function isValidMotion(value) {
23
+ return MOTIONS.includes(value);
24
+ }
25
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/overlays/types.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,eAAe;IACf,UAAU;IACV,WAAW;IACX,aAAa;IACb,cAAc;IACd,QAAQ;CACA,CAAC;AAIX,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,aAAa;IACb,eAAe;IACf,SAAS;IACT,YAAY;CACJ,CAAC;AAIX,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,UAAU,CAAU,CAAC;AA8ChE,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,OAAQ,KAA2B,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,KAAa;IAC/C,OAAQ,cAAoC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,OAAQ,OAA6B,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACxD,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { Page } from '@playwright/test';
2
+ import type { Zone } from './types.js';
3
+ export declare const ZONE_ID_PREFIX = "argo-overlay-";
4
+ /**
5
+ * Inject content into a zone. Replaces any existing overlay in that zone.
6
+ * Security: contentHtml is generated by template renderers which escape all
7
+ * text fields via escapeHtml(). Content originates from author-controlled
8
+ * manifest files, not end-user input.
9
+ */
10
+ export declare function injectIntoZone(page: Page, zone: Zone, contentHtml: string, containerStyles: Record<string, string>, animationCSS?: string): Promise<void>;
11
+ export type BackgroundTheme = 'dark' | 'light';
12
+ /**
13
+ * Detect whether the background at a zone's position is dark or light.
14
+ * Samples the element at the zone coordinates, skipping fixed-position elements
15
+ * (navbars, toolbars) to read the actual page content background.
16
+ * Returns the *overlay* theme for contrast: dark bg → 'light' overlay, light bg → 'dark' overlay.
17
+ */
18
+ export declare function detectBackgroundTheme(page: Page, zone: Zone): Promise<BackgroundTheme>;
19
+ /**
20
+ * Remove the overlay in the specified zone, if any.
21
+ */
22
+ export declare function removeZone(page: Page, zone: Zone): Promise<void>;
23
+ //# sourceMappingURL=zones.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zones.d.ts","sourceRoot":"","sources":["../../src/overlays/zones.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,eAAO,MAAM,cAAc,kBAAkB,CAAC;AAuB9C;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,IAAI,EACV,WAAW,EAAE,MAAM,EACnB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACvC,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC,CAgCf;AAED,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,OAAO,CAAC;AAE/C;;;;;GAKG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,eAAe,CAAC,CAwC5F;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAMtE"}
@@ -0,0 +1,117 @@
1
+ export const ZONE_ID_PREFIX = 'argo-overlay-';
2
+ const ZONE_POSITIONS = {
3
+ 'bottom-center': {
4
+ position: 'fixed', bottom: '60px', left: '50%', transform: 'translateX(-50%)',
5
+ },
6
+ 'top-left': {
7
+ position: 'fixed', top: '40px', left: '40px',
8
+ },
9
+ 'top-right': {
10
+ position: 'fixed', top: '40px', right: '40px',
11
+ },
12
+ 'bottom-left': {
13
+ position: 'fixed', bottom: '60px', left: '40px',
14
+ },
15
+ 'bottom-right': {
16
+ position: 'fixed', bottom: '60px', right: '40px',
17
+ },
18
+ 'center': {
19
+ position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)',
20
+ },
21
+ };
22
+ /**
23
+ * Inject content into a zone. Replaces any existing overlay in that zone.
24
+ * Security: contentHtml is generated by template renderers which escape all
25
+ * text fields via escapeHtml(). Content originates from author-controlled
26
+ * manifest files, not end-user input.
27
+ */
28
+ export async function injectIntoZone(page, zone, contentHtml, containerStyles, animationCSS) {
29
+ const zoneId = ZONE_ID_PREFIX + zone;
30
+ const positionStyles = ZONE_POSITIONS[zone];
31
+ const baseStyles = {
32
+ ...positionStyles,
33
+ zIndex: '999999',
34
+ pointerEvents: 'none',
35
+ fontFamily: 'system-ui, -apple-system, sans-serif',
36
+ ...containerStyles,
37
+ };
38
+ await page.evaluate(([id, html, styles, css]) => {
39
+ const existing = document.getElementById(id);
40
+ if (existing)
41
+ existing.remove();
42
+ if (css) {
43
+ const styleId = id + '-style';
44
+ let styleEl = document.getElementById(styleId);
45
+ if (!styleEl) {
46
+ styleEl = document.createElement('style');
47
+ styleEl.id = styleId;
48
+ document.head.appendChild(styleEl);
49
+ }
50
+ styleEl.textContent = css;
51
+ }
52
+ const container = document.createElement('div');
53
+ container.id = id;
54
+ container.innerHTML = html;
55
+ Object.assign(container.style, styles);
56
+ document.body.appendChild(container);
57
+ }, [zoneId, contentHtml, baseStyles, animationCSS ?? '']);
58
+ }
59
+ /**
60
+ * Detect whether the background at a zone's position is dark or light.
61
+ * Samples the element at the zone coordinates, skipping fixed-position elements
62
+ * (navbars, toolbars) to read the actual page content background.
63
+ * Returns the *overlay* theme for contrast: dark bg → 'light' overlay, light bg → 'dark' overlay.
64
+ */
65
+ export async function detectBackgroundTheme(page, zone) {
66
+ const pos = ZONE_POSITIONS[zone];
67
+ return page.evaluate((zonePos) => {
68
+ const vw = window.innerWidth;
69
+ const vh = window.innerHeight;
70
+ let x = vw / 2;
71
+ let y = vh / 2;
72
+ if (zonePos.left === '40px')
73
+ x = 80;
74
+ else if (zonePos.right === '40px')
75
+ x = vw - 80;
76
+ else if (zonePos.left === '50%')
77
+ x = vw / 2;
78
+ if (zonePos.top === '40px')
79
+ y = 80;
80
+ else if (zonePos.bottom === '60px')
81
+ y = vh - 80;
82
+ else if (zonePos.top === '50%')
83
+ y = vh / 2;
84
+ // Get all elements at this point, skip fixed-position ones (navs, toolbars)
85
+ const elements = document.elementsFromPoint(x, y);
86
+ for (const el of elements) {
87
+ const style = getComputedStyle(el);
88
+ // Skip fixed elements (navbars, overlays) — they don't represent page content
89
+ if (style.position === 'fixed' || style.position === 'sticky')
90
+ continue;
91
+ const bg = style.backgroundColor;
92
+ const match = bg.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
93
+ if (match) {
94
+ const [, r, g, b, a] = match;
95
+ const alpha = a !== undefined ? parseFloat(a) : 1;
96
+ if (alpha > 0.1) {
97
+ const luminance = 0.299 * Number(r) + 0.587 * Number(g) + 0.114 * Number(b);
98
+ return luminance < 128 ? 'light' : 'dark';
99
+ }
100
+ }
101
+ }
102
+ // Fall back to system theme
103
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
104
+ return prefersDark ? 'light' : 'dark';
105
+ }, pos);
106
+ }
107
+ /**
108
+ * Remove the overlay in the specified zone, if any.
109
+ */
110
+ export async function removeZone(page, zone) {
111
+ const zoneId = ZONE_ID_PREFIX + zone;
112
+ await page.evaluate((id) => {
113
+ document.getElementById(id)?.remove();
114
+ document.getElementById(id + '-style')?.remove();
115
+ }, zoneId);
116
+ }
117
+ //# sourceMappingURL=zones.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zones.js","sourceRoot":"","sources":["../../src/overlays/zones.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { ArgoConfig } from './config.js';
2
+ export declare function runPipeline(demoName: string, config: Pick<ArgoConfig, 'baseURL' | 'demosDir' | 'outputDir' | 'tts' | 'video' | 'export' | 'overlays'>): Promise<string>;
3
+ //# sourceMappingURL=pipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA0B9C,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,UAAU,CAAC,GACvG,OAAO,CAAC,MAAM,CAAC,CAqGjB"}
@@ -0,0 +1,109 @@
1
+ import { readFileSync, writeFileSync } from 'node:fs';
2
+ import { execFileSync } from 'node:child_process';
3
+ import { join } from 'node:path';
4
+ import { generateClips } from './tts/generate.js';
5
+ import { record } from './record.js';
6
+ import { alignClips } from './tts/align.js';
7
+ import { parseWavHeader, createWavBuffer } from './tts/engine.js';
8
+ import { exportVideo, checkFfmpeg } from './export.js';
9
+ function getVideoDurationMs(videoPath) {
10
+ let raw;
11
+ try {
12
+ raw = execFileSync('ffprobe', ['-v', 'error', '-show_entries', 'format=duration', '-of', 'csv=p=0', videoPath], { encoding: 'utf-8' }).trim();
13
+ }
14
+ catch (err) {
15
+ throw new Error(`Failed to get video duration from ${videoPath}. ` +
16
+ `Ensure ffprobe is installed (it usually comes with ffmpeg). ` +
17
+ `Original error: ${err.message}`);
18
+ }
19
+ const durationMs = Math.round(parseFloat(raw) * 1000);
20
+ if (isNaN(durationMs) || durationMs <= 0) {
21
+ throw new Error(`ffprobe returned invalid duration "${raw}" for ${videoPath}. The video file may be corrupt.`);
22
+ }
23
+ return durationMs;
24
+ }
25
+ export async function runPipeline(demoName, config) {
26
+ if (!config.baseURL) {
27
+ throw new Error('baseURL is required but not set. Set it in argo.config.js or pass --config.');
28
+ }
29
+ if (!config.tts.engine) {
30
+ throw new Error('TTS engine is not configured. Ensure config.tts.engine is set.');
31
+ }
32
+ checkFfmpeg();
33
+ const argoDir = join('.argo', demoName);
34
+ // Step 1: Generate TTS clips
35
+ console.log('Step 1/4: Generating TTS clips...');
36
+ const clipResults = await generateClips({
37
+ manifestPath: `${config.demosDir}/${demoName}.voiceover.json`,
38
+ demoName,
39
+ engine: config.tts.engine,
40
+ projectRoot: '.',
41
+ defaults: { voice: config.tts.defaultVoice, speed: config.tts.defaultSpeed },
42
+ });
43
+ if (clipResults.length === 0) {
44
+ throw new Error(`No TTS clips were generated from ${config.demosDir}/${demoName}.voiceover.json. ` +
45
+ `Ensure the manifest contains at least one entry.`);
46
+ }
47
+ // Write scene durations so demo scripts can use narration.durationFor()
48
+ const sceneDurations = {};
49
+ for (const cr of clipResults) {
50
+ sceneDurations[cr.scene] = cr.durationMs;
51
+ }
52
+ const sceneDurationsPath = join(argoDir, '.scene-durations.json');
53
+ writeFileSync(sceneDurationsPath, JSON.stringify(sceneDurations, null, 2), 'utf-8');
54
+ // Step 2: Record browser demo
55
+ console.log('Step 2/4: Recording browser demo...');
56
+ const { timingPath } = await record(demoName, {
57
+ demosDir: config.demosDir,
58
+ baseURL: config.baseURL,
59
+ video: { width: config.video.width, height: config.video.height },
60
+ autoBackground: config.overlays.autoBackground,
61
+ });
62
+ // Step 3: Align clips with timing
63
+ console.log('Step 3/4: Aligning narration with video...');
64
+ const timing = JSON.parse(readFileSync(timingPath, 'utf-8'));
65
+ // Load WAV clips into memory
66
+ const clips = clipResults.map((cr) => {
67
+ const wavBuf = readFileSync(cr.clipPath);
68
+ const header = parseWavHeader(wavBuf);
69
+ // Extract Float32 samples from the data chunk
70
+ const sampleCount = header.dataSize / 4; // 32-bit float = 4 bytes
71
+ const samples = new Float32Array(sampleCount);
72
+ for (let i = 0; i < sampleCount && header.dataOffset + i * 4 + 3 < wavBuf.length; i++) {
73
+ samples[i] = wavBuf.readFloatLE(header.dataOffset + i * 4);
74
+ }
75
+ return {
76
+ scene: cr.scene,
77
+ durationMs: header.durationMs,
78
+ samples,
79
+ };
80
+ });
81
+ // Use actual video duration for alignment
82
+ const videoPath = join(argoDir, 'video.webm');
83
+ const totalDurationMs = getVideoDurationMs(videoPath);
84
+ const aligned = alignClips(timing, clips, totalDurationMs);
85
+ const alignedWav = createWavBuffer(aligned.samples, 24_000);
86
+ const alignedPath = join(argoDir, 'narration-aligned.wav');
87
+ writeFileSync(alignedPath, alignedWav);
88
+ const tailPadMs = aligned.overflowMs > 0 ? aligned.overflowMs + 100 : undefined;
89
+ if (tailPadMs !== undefined) {
90
+ console.warn(`Aligned narration runs ${aligned.overflowMs}ms past the recording. ` +
91
+ `Padding the final video frame to preserve the full audio.`);
92
+ }
93
+ // Step 4: Export final video
94
+ console.log('Step 4/4: Exporting final video...');
95
+ const exportOptions = {
96
+ demoName,
97
+ argoDir: '.argo',
98
+ outputDir: config.outputDir,
99
+ preset: config.export.preset,
100
+ crf: config.export.crf,
101
+ fps: config.video.fps,
102
+ };
103
+ if (tailPadMs !== undefined)
104
+ exportOptions.tailPadMs = tailPadMs;
105
+ const outputPath = await exportVideo(exportOptions);
106
+ console.log(`Done! Video saved to: ${outputPath}`);
107
+ return outputPath;
108
+ }
109
+ //# sourceMappingURL=pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ export interface RecordOptions {
2
+ demosDir: string;
3
+ baseURL: string;
4
+ video: {
5
+ width: number;
6
+ height: number;
7
+ };
8
+ autoBackground?: boolean;
9
+ }
10
+ export interface RecordResult {
11
+ videoPath: string;
12
+ timingPath: string;
13
+ }
14
+ export declare function record(demoName: string, options: RecordOptions): Promise<RecordResult>;
15
+ //# sourceMappingURL=record.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../src/record.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAyCD,wBAAsB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CA2E5F"}
package/dist/record.js ADDED
@@ -0,0 +1,110 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { mkdirSync, readdirSync, copyFileSync, existsSync, rmSync, writeFileSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { startAssetServer } from './asset-server.js';
5
+ import { loadOverlayManifest, hasImageAssets } from './overlays/manifest.js';
6
+ function findVideoInResults(testResultsDir) {
7
+ if (!existsSync(testResultsDir))
8
+ return undefined;
9
+ for (const entry of readdirSync(testResultsDir, { recursive: true })) {
10
+ const name = typeof entry === 'string' ? entry : entry.toString();
11
+ if (name.endsWith('.webm')) {
12
+ return path.join(testResultsDir, name);
13
+ }
14
+ }
15
+ return undefined;
16
+ }
17
+ function createPlaywrightConfig(options, outputDir) {
18
+ const demosDir = path.resolve(options.demosDir);
19
+ const { width, height } = options.video;
20
+ return `import { defineConfig } from '@playwright/test';
21
+
22
+ export default defineConfig({
23
+ preserveOutput: 'always',
24
+ outputDir: ${JSON.stringify(outputDir)},
25
+ projects: [
26
+ {
27
+ name: 'demos',
28
+ testDir: ${JSON.stringify(demosDir)},
29
+ testMatch: '**/*.demo.ts',
30
+ use: {
31
+ baseURL: ${JSON.stringify(options.baseURL)},
32
+ viewport: { width: ${width}, height: ${height} },
33
+ video: {
34
+ mode: 'on',
35
+ size: { width: ${width}, height: ${height} },
36
+ },
37
+ },
38
+ },
39
+ ],
40
+ });
41
+ `;
42
+ }
43
+ export async function record(demoName, options) {
44
+ const argoDir = path.join('.argo', demoName);
45
+ mkdirSync(argoDir, { recursive: true });
46
+ const videoPath = path.join(argoDir, 'video.webm');
47
+ const timingPath = path.join(argoDir, '.timing.json');
48
+ const testResultsDir = path.resolve('test-results');
49
+ const recordConfigPath = path.join(argoDir, 'playwright.record.config.mjs');
50
+ writeFileSync(recordConfigPath, createPlaywrightConfig(options, testResultsDir), 'utf-8');
51
+ // Clean test-results to avoid picking up stale videos
52
+ rmSync(testResultsDir, { recursive: true, force: true });
53
+ // Start asset server if overlay manifest has image assets
54
+ let assetServer;
55
+ const overlayManifestPath = path.join(options.demosDir, `${demoName}.overlays.json`);
56
+ try {
57
+ const overlayEntries = await loadOverlayManifest(overlayManifestPath);
58
+ if (overlayEntries && hasImageAssets(overlayEntries)) {
59
+ const assetDir = path.join(options.demosDir, 'assets');
60
+ assetServer = await startAssetServer(assetDir);
61
+ }
62
+ }
63
+ catch (err) {
64
+ // A malformed overlay manifest should not block recording —
65
+ // overlays are rendered by explicit showOverlay()/withOverlay() calls in the demo script,
66
+ // not from the manifest. Only warn.
67
+ console.warn(`Warning: could not parse overlay manifest: ${err.message}`);
68
+ }
69
+ try {
70
+ return await new Promise((resolve, reject) => {
71
+ execFile('npx', ['playwright', 'test', '--config', recordConfigPath, '--grep', demoName, '--project', 'demos'], {
72
+ env: {
73
+ ...process.env,
74
+ ARGO_DEMO_NAME: demoName,
75
+ ARGO_OUTPUT_DIR: argoDir,
76
+ BASE_URL: options.baseURL,
77
+ ARGO_ASSET_URL: assetServer?.url ?? '',
78
+ ARGO_AUTO_BACKGROUND: options.autoBackground ? '1' : '',
79
+ ARGO_SCENE_DURATIONS_PATH: path.resolve(path.join('.argo', demoName, '.scene-durations.json')),
80
+ },
81
+ }, (error, stdout, stderr) => {
82
+ if (error) {
83
+ const output = [stdout, stderr].filter(Boolean).join('\n');
84
+ reject(new Error(`Playwright recording failed:\n${output}`));
85
+ return;
86
+ }
87
+ // Copy the video from test-results/ to .argo/<demo>/video.webm
88
+ const found = findVideoInResults(testResultsDir);
89
+ if (!found) {
90
+ reject(new Error(`No video recording found in test-results/. ` +
91
+ `Ensure playwright.config.ts has video: 'on' or video: { mode: 'on' }.`));
92
+ return;
93
+ }
94
+ copyFileSync(found, videoPath);
95
+ // Verify timing file was written by the narration fixture
96
+ if (!existsSync(timingPath)) {
97
+ reject(new Error(`No timing file found at ${timingPath}. ` +
98
+ `Ensure the demo uses the argo test fixture with narration.mark() calls.`));
99
+ return;
100
+ }
101
+ resolve({ videoPath, timingPath });
102
+ });
103
+ });
104
+ }
105
+ finally {
106
+ if (assetServer)
107
+ await assetServer.close();
108
+ }
109
+ }
110
+ //# sourceMappingURL=record.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record.js","sourceRoot":"","sources":["../src/record.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ export type SceneTiming = Record<string, number>;
2
+ export interface ClipInfo {
3
+ scene: string;
4
+ durationMs: number;
5
+ samples: Float32Array;
6
+ }
7
+ export interface Placement {
8
+ scene: string;
9
+ startMs: number;
10
+ endMs: number;
11
+ }
12
+ export interface AlignResult {
13
+ placements: Placement[];
14
+ samples: Float32Array;
15
+ requiredDurationMs: number;
16
+ overflowMs: number;
17
+ }
18
+ export interface ScheduledSceneInput {
19
+ scene: string;
20
+ startMs: number;
21
+ durationMs: number;
22
+ }
23
+ export declare const OVERLAP_GAP_MS = 100;
24
+ export declare function schedulePlacements(scenes: ScheduledSceneInput[]): Placement[];
25
+ export declare function alignClips(timing: SceneTiming, clips: ClipInfo[], totalDurationMs: number, sampleRate?: number): AlignResult;
26
+ //# sourceMappingURL=align.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"align.d.ts","sourceRoot":"","sources":["../../src/tts/align.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEjD,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,YAAY,CAAC;CACvB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,OAAO,EAAE,YAAY,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,cAAc,MAAM,CAAC;AAElC,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,mBAAmB,EAAE,GAAG,SAAS,EAAE,CAkB7E;AAED,wBAAgB,UAAU,CACxB,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,QAAQ,EAAE,EACjB,eAAe,EAAE,MAAM,EACvB,UAAU,SAAS,GAClB,WAAW,CA6Cb"}
@@ -0,0 +1,53 @@
1
+ export const OVERLAP_GAP_MS = 100;
2
+ export function schedulePlacements(scenes) {
3
+ const sorted = [...scenes].sort((a, b) => a.startMs - b.startMs);
4
+ const placements = [];
5
+ let previousEndMs = 0;
6
+ for (const scene of sorted) {
7
+ let startMs = scene.startMs;
8
+ if (placements.length > 0 && startMs < previousEndMs) {
9
+ startMs = previousEndMs + OVERLAP_GAP_MS;
10
+ }
11
+ const endMs = startMs + scene.durationMs;
12
+ placements.push({ scene: scene.scene, startMs, endMs });
13
+ previousEndMs = endMs;
14
+ }
15
+ return placements;
16
+ }
17
+ export function alignClips(timing, clips, totalDurationMs, sampleRate = 24_000) {
18
+ // 1. Filter to clips with matching scenes
19
+ const matched = clips.filter((c) => c.scene in timing);
20
+ const unmatched = clips.filter((c) => !(c.scene in timing));
21
+ if (unmatched.length > 0) {
22
+ const names = unmatched.map((c) => c.scene).join(', ');
23
+ console.warn(`Warning: ${unmatched.length} clip(s) have no matching scene in timing and will be skipped: ${names}. ` +
24
+ `Check that voiceover manifest scene names match narration.mark() calls.`);
25
+ }
26
+ // 2. Sort by scene timestamp ascending
27
+ matched.sort((a, b) => timing[a.scene] - timing[b.scene]);
28
+ // 3. Place each clip, preventing overlap
29
+ const placements = schedulePlacements(matched.map((clip) => ({
30
+ scene: clip.scene,
31
+ startMs: timing[clip.scene],
32
+ durationMs: clip.durationMs,
33
+ })));
34
+ const requiredDurationMs = placements.length > 0
35
+ ? placements[placements.length - 1].endMs
36
+ : 0;
37
+ const overflowMs = Math.max(0, requiredDurationMs - totalDurationMs);
38
+ // 4. Create silence buffer
39
+ const outputDurationMs = Math.max(totalDurationMs, requiredDurationMs);
40
+ const totalSamples = Math.round((outputDurationMs / 1000) * sampleRate);
41
+ const output = new Float32Array(totalSamples);
42
+ // 5. Mix each clip's samples into output
43
+ for (let i = 0; i < placements.length; i++) {
44
+ const placement = placements[i];
45
+ const clip = matched[i];
46
+ const startSample = Math.round((placement.startMs / 1000) * sampleRate);
47
+ for (let j = 0; j < clip.samples.length && startSample + j < totalSamples; j++) {
48
+ output[startSample + j] += clip.samples[j];
49
+ }
50
+ }
51
+ return { placements, samples: output, requiredDurationMs, overflowMs };
52
+ }
53
+ //# sourceMappingURL=align.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"align.js","sourceRoot":"","sources":["../../src/tts/align.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Content-addressed clip cache for Argo TTS output.
3
+ */
4
+ export interface ManifestEntry {
5
+ scene: string;
6
+ text: string;
7
+ voice?: string;
8
+ speed?: number;
9
+ }
10
+ export declare class ClipCache {
11
+ private readonly projectRoot;
12
+ constructor(projectRoot: string);
13
+ /**
14
+ * Returns the full file path for a cached clip.
15
+ */
16
+ getClipPath(demoName: string, entry: ManifestEntry): string;
17
+ /**
18
+ * Checks whether a clip is already cached on disk.
19
+ */
20
+ isCached(demoName: string, entry: ManifestEntry): boolean;
21
+ /**
22
+ * Returns the cached WAV buffer, or null if not cached.
23
+ */
24
+ getCachedClip(demoName: string, entry: ManifestEntry): Buffer | null;
25
+ /**
26
+ * Writes a WAV buffer to the cache, creating directories as needed.
27
+ */
28
+ cacheClip(demoName: string, entry: ManifestEntry, wavBuffer: Buffer): void;
29
+ private computeHash;
30
+ }
31
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/tts/cache.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;gBAEzB,WAAW,EAAE,MAAM;IAI/B;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,MAAM;IAK3D;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,OAAO;IAIzD;;OAEG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,MAAM,GAAG,IAAI;IAQpE;;OAEG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAM1E,OAAO,CAAC,WAAW;CAOpB"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Content-addressed clip cache for Argo TTS output.
3
+ */
4
+ import crypto from 'node:crypto';
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ export class ClipCache {
8
+ projectRoot;
9
+ constructor(projectRoot) {
10
+ this.projectRoot = projectRoot;
11
+ }
12
+ /**
13
+ * Returns the full file path for a cached clip.
14
+ */
15
+ getClipPath(demoName, entry) {
16
+ const hash = this.computeHash(entry);
17
+ return path.join(this.projectRoot, '.argo', demoName, 'clips', `${hash}.wav`);
18
+ }
19
+ /**
20
+ * Checks whether a clip is already cached on disk.
21
+ */
22
+ isCached(demoName, entry) {
23
+ return fs.existsSync(this.getClipPath(demoName, entry));
24
+ }
25
+ /**
26
+ * Returns the cached WAV buffer, or null if not cached.
27
+ */
28
+ getCachedClip(demoName, entry) {
29
+ const clipPath = this.getClipPath(demoName, entry);
30
+ if (!fs.existsSync(clipPath)) {
31
+ return null;
32
+ }
33
+ return fs.readFileSync(clipPath);
34
+ }
35
+ /**
36
+ * Writes a WAV buffer to the cache, creating directories as needed.
37
+ */
38
+ cacheClip(demoName, entry, wavBuffer) {
39
+ const clipPath = this.getClipPath(demoName, entry);
40
+ fs.mkdirSync(path.dirname(clipPath), { recursive: true });
41
+ fs.writeFileSync(clipPath, wavBuffer);
42
+ }
43
+ computeHash(entry) {
44
+ const { scene, text, voice, speed } = entry;
45
+ return crypto
46
+ .createHash('sha256')
47
+ .update(JSON.stringify({ scene, text, voice, speed }))
48
+ .digest('hex');
49
+ }
50
+ }
51
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/tts/cache.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAS7B,MAAM,OAAO,SAAS;IACH,WAAW,CAAS;IAErC,YAAY,WAAmB;QAC7B,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IACjC,CAAC;IAED;;OAEG;IACH,WAAW,CAAC,QAAgB,EAAE,KAAoB;QAChD,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QACrC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,IAAI,MAAM,CAAC,CAAC;IAChF,CAAC;IAED;;OAEG;IACH,QAAQ,CAAC,QAAgB,EAAE,KAAoB;QAC7C,OAAO,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED;;OAEG;IACH,aAAa,CAAC,QAAgB,EAAE,KAAoB;QAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QACnD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC7B,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IACnC,CAAC;IAED;;OAEG;IACH,SAAS,CAAC,QAAgB,EAAE,KAAoB,EAAE,SAAiB;QACjE,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QACnD,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1D,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IACxC,CAAC;IAEO,WAAW,CAAC,KAAoB;QACtC,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;QAC5C,OAAO,MAAM;aACV,UAAU,CAAC,QAAQ,CAAC;aACpB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;aACrD,MAAM,CAAC,KAAK,CAAC,CAAC;IACnB,CAAC;CACF"}