@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.
- package/LICENSE +21 -0
- package/README.md +2 -2
- package/dist/asset-server.d.ts +7 -0
- package/dist/asset-server.d.ts.map +1 -0
- package/dist/asset-server.js +69 -0
- package/dist/asset-server.js.map +1 -0
- package/dist/captions.d.ts +17 -0
- package/dist/captions.d.ts.map +1 -0
- package/dist/captions.js +23 -0
- package/dist/captions.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +87 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +49 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +76 -0
- package/dist/config.js.map +1 -0
- package/dist/export.d.ts +19 -0
- package/dist/export.d.ts.map +1 -0
- package/dist/export.js +66 -0
- package/dist/export.js.map +1 -0
- package/dist/fixtures.d.ts +13 -0
- package/dist/fixtures.d.ts.map +1 -0
- package/dist/fixtures.js +49 -0
- package/dist/fixtures.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +2 -0
- package/dist/init.d.ts.map +1 -0
- package/{src/init.ts → dist/init.js} +39 -54
- package/dist/init.js.map +1 -0
- package/dist/narration.d.ts +32 -0
- package/dist/narration.d.ts.map +1 -0
- package/dist/narration.js +86 -0
- package/dist/narration.js.map +1 -0
- package/dist/overlays/index.d.ts +13 -0
- package/dist/overlays/index.d.ts.map +1 -0
- package/dist/overlays/index.js +45 -0
- package/dist/overlays/index.js.map +1 -0
- package/dist/overlays/manifest.d.ts +5 -0
- package/dist/overlays/manifest.d.ts.map +1 -0
- package/dist/overlays/manifest.js +52 -0
- package/dist/overlays/manifest.js.map +1 -0
- package/dist/overlays/motion.d.ts +4 -0
- package/dist/overlays/motion.d.ts.map +1 -0
- package/dist/overlays/motion.js +25 -0
- package/dist/overlays/motion.js.map +1 -0
- package/dist/overlays/templates.d.ts +8 -0
- package/dist/overlays/templates.d.ts.map +1 -0
- package/dist/overlays/templates.js +102 -0
- package/dist/overlays/templates.js.map +1 -0
- package/dist/overlays/types.d.ts +46 -0
- package/dist/overlays/types.d.ts.map +1 -0
- package/dist/overlays/types.js +25 -0
- package/dist/overlays/types.js.map +1 -0
- package/dist/overlays/zones.d.ts +23 -0
- package/dist/overlays/zones.d.ts.map +1 -0
- package/dist/overlays/zones.js +117 -0
- package/dist/overlays/zones.js.map +1 -0
- package/dist/pipeline.d.ts +3 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +109 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/record.d.ts +15 -0
- package/dist/record.d.ts.map +1 -0
- package/dist/record.js +110 -0
- package/dist/record.js.map +1 -0
- package/dist/tts/align.d.ts +26 -0
- package/dist/tts/align.d.ts.map +1 -0
- package/dist/tts/align.js +53 -0
- package/dist/tts/align.js.map +1 -0
- package/dist/tts/cache.d.ts +31 -0
- package/dist/tts/cache.d.ts.map +1 -0
- package/dist/tts/cache.js +51 -0
- package/dist/tts/cache.js.map +1 -0
- package/dist/tts/engine.d.ts +41 -0
- package/dist/tts/engine.d.ts.map +1 -0
- package/dist/tts/engine.js +108 -0
- package/dist/tts/engine.js.map +1 -0
- package/dist/tts/generate.d.ts +21 -0
- package/dist/tts/generate.d.ts.map +1 -0
- package/dist/tts/generate.js +61 -0
- package/dist/tts/generate.js.map +1 -0
- package/dist/tts/kokoro.d.ts +30 -0
- package/dist/tts/kokoro.d.ts.map +1 -0
- package/dist/tts/kokoro.js +66 -0
- package/dist/tts/kokoro.js.map +1 -0
- package/package.json +13 -1
- package/.claude/settings.local.json +0 -34
- package/DESIGN.md +0 -261
- package/docs/enhancement-proposal.md +0 -262
- package/docs/superpowers/plans/2026-03-12-argo.md +0 -208
- package/docs/superpowers/plans/2026-03-12-editorial-overlay-system.md +0 -1560
- package/docs/superpowers/plans/2026-03-13-npm-rename-skill-showcase.md +0 -499
- package/docs/superpowers/specs/2026-03-13-npm-rename-skill-showcase-design.md +0 -109
- package/skills/argo-demo-creator.md +0 -355
- package/src/asset-server.ts +0 -81
- package/src/captions.ts +0 -36
- package/src/cli.ts +0 -97
- package/src/config.ts +0 -125
- package/src/export.ts +0 -93
- package/src/fixtures.ts +0 -50
- package/src/index.ts +0 -41
- package/src/narration.ts +0 -31
- package/src/overlays/index.ts +0 -54
- package/src/overlays/manifest.ts +0 -68
- package/src/overlays/motion.ts +0 -27
- package/src/overlays/templates.ts +0 -121
- package/src/overlays/types.ts +0 -73
- package/src/overlays/zones.ts +0 -82
- package/src/pipeline.ts +0 -120
- package/src/record.ts +0 -123
- package/src/tts/align.ts +0 -75
- package/src/tts/cache.ts +0 -65
- package/src/tts/engine.ts +0 -147
- package/src/tts/generate.ts +0 -83
- package/src/tts/kokoro.ts +0 -51
- package/tests/asset-server.test.ts +0 -67
- package/tests/captions.test.ts +0 -76
- package/tests/cli.test.ts +0 -131
- package/tests/config.test.ts +0 -150
- package/tests/e2e/fake-server.ts +0 -45
- package/tests/e2e/record.e2e.test.ts +0 -131
- package/tests/export.test.ts +0 -155
- package/tests/fixtures.test.ts +0 -74
- package/tests/init.test.ts +0 -77
- package/tests/narration.test.ts +0 -120
- package/tests/overlays/index.test.ts +0 -73
- package/tests/overlays/manifest.test.ts +0 -120
- package/tests/overlays/motion.test.ts +0 -34
- package/tests/overlays/templates.test.ts +0 -69
- package/tests/overlays/types.test.ts +0 -36
- package/tests/overlays/zones.test.ts +0 -49
- package/tests/pipeline.test.ts +0 -177
- package/tests/record.test.ts +0 -87
- package/tests/tts/align.test.ts +0 -118
- package/tests/tts/cache.test.ts +0 -110
- package/tests/tts/engine.test.ts +0 -204
- package/tests/tts/generate.test.ts +0 -177
- package/tests/tts/kokoro.test.ts +0 -25
- 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":"AAGA,MAAM,CAAC,MAAM,cAAc,GAAG,eAAe,CAAC;AAE9C,MAAM,cAAc,GAAyC;IAC3D,eAAe,EAAE;QACf,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,kBAAkB;KAC9E;IACD,UAAU,EAAE;QACV,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;KAC7C;IACD,WAAW,EAAE;QACX,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;KAC9C;IACD,aAAa,EAAE;QACb,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;KAChD;IACD,cAAc,EAAE;QACd,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;KACjD;IACD,QAAQ,EAAE;QACR,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,uBAAuB;KAC/E;CACF,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAU,EACV,IAAU,EACV,WAAmB,EACnB,eAAuC,EACvC,YAAqB;IAErB,MAAM,MAAM,GAAG,cAAc,GAAG,IAAI,CAAC;IACrC,MAAM,cAAc,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,UAAU,GAA2B;QACzC,GAAG,cAAc;QACjB,MAAM,EAAE,QAAQ;QAChB,aAAa,EAAE,MAAM;QACrB,UAAU,EAAE,sCAAsC;QAClD,GAAG,eAAe;KACnB,CAAC;IAEF,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,EAAE;QAC9C,MAAM,QAAQ,GAAG,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;QAC7C,IAAI,QAAQ;YAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;QAEhC,IAAI,GAAG,EAAE,CAAC;YACR,MAAM,OAAO,GAAG,EAAE,GAAG,QAAQ,CAAC;YAC9B,IAAI,OAAO,GAAG,QAAQ,CAAC,cAAc,CAAC,OAAO,CAA4B,CAAC;YAC1E,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;gBAC1C,OAAO,CAAC,EAAE,GAAG,OAAO,CAAC;gBACrB,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;YACrC,CAAC;YACD,OAAO,CAAC,WAAW,GAAG,GAAG,CAAC;QAC5B,CAAC;QAED,MAAM,SAAS,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAChD,SAAS,CAAC,EAAE,GAAG,EAAE,CAAC;QAClB,SAAS,CAAC,SAAS,GAAG,IAAI,CAAC;QAC3B,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACvC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;IACvC,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,IAAI,EAAE,CAAU,CAAC,CAAC;AACrE,CAAC;AAID;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,IAAU,EAAE,IAAU;IAChE,MAAM,GAAG,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACjC,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,EAAE;QAC/B,MAAM,EAAE,GAAG,MAAM,CAAC,UAAU,CAAC;QAC7B,MAAM,EAAE,GAAG,MAAM,CAAC,WAAW,CAAC;QAC9B,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACf,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAEf,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM;YAAE,CAAC,GAAG,EAAE,CAAC;aAC/B,IAAI,OAAO,CAAC,KAAK,KAAK,MAAM;YAAE,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;aAC1C,IAAI,OAAO,CAAC,IAAI,KAAK,KAAK;YAAE,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAE5C,IAAI,OAAO,CAAC,GAAG,KAAK,MAAM;YAAE,CAAC,GAAG,EAAE,CAAC;aAC9B,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;YAAE,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;aAC3C,IAAI,OAAO,CAAC,GAAG,KAAK,KAAK;YAAE,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAE3C,4EAA4E;QAC5E,MAAM,QAAQ,GAAG,QAAQ,CAAC,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAElD,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;YAC1B,MAAM,KAAK,GAAG,gBAAgB,CAAC,EAAE,CAAC,CAAC;YACnC,8EAA8E;YAC9E,IAAI,KAAK,CAAC,QAAQ,KAAK,OAAO,IAAI,KAAK,CAAC,QAAQ,KAAK,QAAQ;gBAAE,SAAS;YAExE,MAAM,EAAE,GAAG,KAAK,CAAC,eAAe,CAAC;YACjC,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC,mDAAmD,CAAC,CAAC;YAC5E,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC;gBAC7B,MAAM,KAAK,GAAG,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBAClD,IAAI,KAAK,GAAG,GAAG,EAAE,CAAC;oBAChB,MAAM,SAAS,GAAG,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;oBAC5E,OAAO,SAAS,GAAG,GAAG,CAAC,CAAC,CAAC,OAAgB,CAAC,CAAC,CAAC,MAAe,CAAC;gBAC9D,CAAC;YACH,CAAC;QACH,CAAC;QAED,4BAA4B;QAC5B,MAAM,WAAW,GAAG,MAAM,CAAC,UAAU,CAAC,8BAA8B,CAAC,CAAC,OAAO,CAAC;QAC9E,OAAO,WAAW,CAAC,CAAC,CAAC,OAAgB,CAAC,CAAC,CAAC,MAAe,CAAC;IAC1D,CAAC,EAAE,GAAG,CAAC,CAAC;AACV,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAU,EAAE,IAAU;IACrD,MAAM,MAAM,GAAG,cAAc,GAAG,IAAI,CAAC;IACrC,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,EAAE;QACzB,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;QACtC,QAAQ,CAAC,cAAc,CAAC,EAAE,GAAG,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACnD,CAAC,EAAE,MAAM,CAAC,CAAC;AACb,CAAC"}
|
|
@@ -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"}
|
package/dist/pipeline.js
ADDED
|
@@ -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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"}
|
package/dist/record.d.ts
ADDED
|
@@ -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"}
|