@argo-video/cli 0.8.0 → 0.9.1
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/README.md +9 -7
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +12 -3
- package/dist/cli.js.map +1 -1
- package/dist/init.d.ts +15 -0
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +55 -2
- package/dist/init.js.map +1 -1
- package/dist/parse-playwright.d.ts +49 -0
- package/dist/parse-playwright.d.ts.map +1 -0
- package/dist/parse-playwright.js +265 -0
- package/dist/parse-playwright.js.map +1 -0
- package/dist/tts/engines/elevenlabs.js +3 -3
- package/dist/tts/engines/elevenlabs.js.map +1 -1
- package/dist/tts/engines/sarvam.d.ts.map +1 -1
- package/dist/tts/engines/sarvam.js +20 -23
- package/dist/tts/engines/sarvam.js.map +1 -1
- package/dist/tts/generate.d.ts.map +1 -1
- package/dist/tts/generate.js +16 -11
- package/dist/tts/generate.js.map +1 -1
- package/package.json +9 -2
- package/scripts/generate_logo_thumbnail.py +174 -0
- package/scripts/record-voice-ref.sh +87 -0
- package/scripts/setup-mlx-audio.sh +66 -0
- package/scripts/voice-clone-preview.sh +193 -0
package/README.md
CHANGED
|
@@ -47,8 +47,9 @@ npm i -D @argo-video/cli
|
|
|
47
47
|
# Initialize project
|
|
48
48
|
npx argo init
|
|
49
49
|
|
|
50
|
-
# Edit your demo script
|
|
50
|
+
# Edit your demo script (or convert an existing Playwright test)
|
|
51
51
|
vim demos/example.demo.ts
|
|
52
|
+
npx argo init --from tests/checkout.spec.ts # auto-convert
|
|
52
53
|
|
|
53
54
|
# Run the full pipeline
|
|
54
55
|
npx argo pipeline example
|
|
@@ -174,6 +175,7 @@ export default defineConfig({
|
|
|
174
175
|
|
|
175
176
|
```
|
|
176
177
|
argo init Scaffold demo files + config
|
|
178
|
+
argo init --from <test> Convert Playwright test to Argo demo
|
|
177
179
|
argo record <demo> Record browser session
|
|
178
180
|
argo tts generate <manifest> Generate TTS clips from manifest
|
|
179
181
|
argo export <demo> Merge video + audio to MP4
|
|
@@ -251,15 +253,19 @@ choco install ffmpeg # Windows
|
|
|
251
253
|
|--------|------|---------|---------|
|
|
252
254
|
| `engines.kokoro()` | local | built-in | none |
|
|
253
255
|
| `engines.mlxAudio()` | local | `pip install mlx-audio` | none |
|
|
256
|
+
| `engines.openai()` | cloud | `npm i openai` | `OPENAI_API_KEY` |
|
|
257
|
+
| `engines.elevenlabs()` | cloud | `npm i @elevenlabs/elevenlabs-js` | `ELEVENLABS_API_KEY` |
|
|
258
|
+
| `engines.gemini()` | cloud | `npm i @google/genai` | `GEMINI_API_KEY` |
|
|
259
|
+
| `engines.sarvam()` | cloud | `npm i sarvamai` | `SARVAM_API_KEY` |
|
|
254
260
|
|
|
255
261
|
**Voice cloning** — Clone your own voice locally with mlx-audio. Record a 15-second clip, and every demo sounds like you — privately, no data leaves your machine:
|
|
256
262
|
|
|
257
263
|
```bash
|
|
258
264
|
# Record a reference clip (macOS)
|
|
259
|
-
|
|
265
|
+
bash $(npm root)/@argo-video/cli/scripts/record-voice-ref.sh assets/ref-voice.wav
|
|
260
266
|
|
|
261
267
|
# Preview cloned voice against your manifest
|
|
262
|
-
|
|
268
|
+
bash $(npm root)/@argo-video/cli/scripts/voice-clone-preview.sh \
|
|
263
269
|
--ref-audio assets/ref-voice.wav \
|
|
264
270
|
--ref-text "Transcript of what I said." \
|
|
265
271
|
--voiceover demos/showcase.voiceover.json --play
|
|
@@ -274,10 +280,6 @@ choco install ffmpeg # Windows
|
|
|
274
280
|
}),
|
|
275
281
|
}
|
|
276
282
|
```
|
|
277
|
-
| `engines.openai()` | cloud | `npm i openai` | `OPENAI_API_KEY` |
|
|
278
|
-
| `engines.elevenlabs()` | cloud | `npm i elevenlabs` | `ELEVENLABS_API_KEY` |
|
|
279
|
-
| `engines.gemini()` | cloud | `npm i @google/genai` | `GEMINI_API_KEY` |
|
|
280
|
-
| `engines.sarvam()` | cloud | none (fetch) | `SARVAM_API_KEY` |
|
|
281
283
|
|
|
282
284
|
2. **Record** — Playwright runs the demo script in a real browser. The `narration` fixture records timestamps for each `mark()` call. Video is captured at native resolution.
|
|
283
285
|
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AA4B5C,wBAAgB,aAAa,IAAI,OAAO,
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AA4B5C,wBAAgB,aAAa,IAAI,OAAO,CAiJvC"}
|
package/dist/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import { record } from './record.js';
|
|
|
5
5
|
import { generateClips } from './tts/generate.js';
|
|
6
6
|
import { exportVideo } from './export.js';
|
|
7
7
|
import { runPipeline } from './pipeline.js';
|
|
8
|
-
import { init } from './init.js';
|
|
8
|
+
import { init, initFrom } from './init.js';
|
|
9
9
|
import { validateDemo } from './validate.js';
|
|
10
10
|
import { runDoctor, formatDoctorResults } from './doctor.js';
|
|
11
11
|
function validateDemoName(name) {
|
|
@@ -146,8 +146,17 @@ export function createProgram() {
|
|
|
146
146
|
program
|
|
147
147
|
.command('init')
|
|
148
148
|
.description('Initialize a new Argo project')
|
|
149
|
-
.
|
|
150
|
-
|
|
149
|
+
.option('--from <path>', 'convert an existing Playwright test into an Argo demo')
|
|
150
|
+
.option('--demo <name>', 'demo name (defaults to filename without extension)')
|
|
151
|
+
.action(async (cmdOpts) => {
|
|
152
|
+
if (cmdOpts.from) {
|
|
153
|
+
if (cmdOpts.demo)
|
|
154
|
+
validateDemoName(cmdOpts.demo);
|
|
155
|
+
await initFrom({ from: cmdOpts.from, demo: cmdOpts.demo });
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
await init();
|
|
159
|
+
}
|
|
151
160
|
});
|
|
152
161
|
return program;
|
|
153
162
|
}
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,UAAU,EAAuC,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/init.d.ts
CHANGED
|
@@ -1,2 +1,17 @@
|
|
|
1
1
|
export declare function init(cwd?: string): Promise<void>;
|
|
2
|
+
export interface InitFromOptions {
|
|
3
|
+
/** Path to the Playwright test file to convert. */
|
|
4
|
+
from: string;
|
|
5
|
+
/** Demo name override. Defaults to filename without extension. */
|
|
6
|
+
demo?: string;
|
|
7
|
+
/** Working directory. Defaults to process.cwd(). */
|
|
8
|
+
cwd?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Convert an existing Playwright test into an Argo demo project.
|
|
12
|
+
* Generates demo script (with fixture swap + mark() calls),
|
|
13
|
+
* skeleton voiceover.json (with _hint fields for LLM),
|
|
14
|
+
* and skeleton overlays.json.
|
|
15
|
+
*/
|
|
16
|
+
export declare function initFrom(options: InitFromOptions): Promise<void>;
|
|
2
17
|
//# sourceMappingURL=init.d.ts.map
|
package/dist/init.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAmJA,wBAAsB,IAAI,CAAC,GAAG,GAAE,MAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAcrE;AAED,MAAM,WAAW,eAAe;IAC9B,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oDAAoD;IACpD,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;GAKG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA4DtE"}
|
package/dist/init.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { mkdir, writeFile, access } from 'node:fs/promises';
|
|
2
|
-
import { join } from 'node:path';
|
|
1
|
+
import { mkdir, writeFile, readFile, access } from 'node:fs/promises';
|
|
2
|
+
import { join, basename } from 'node:path';
|
|
3
|
+
import { parsePlaywrightTest, generateDemoScript, generateVoiceoverSkeleton, generateOverlaysSkeleton, } from './parse-playwright.js';
|
|
3
4
|
async function writeIfMissing(filePath, content) {
|
|
4
5
|
try {
|
|
5
6
|
await access(filePath);
|
|
@@ -138,4 +139,56 @@ export async function init(cwd = process.cwd()) {
|
|
|
138
139
|
console.log(' 2. Run: npx argo record example');
|
|
139
140
|
console.log(' 3. Run: npx argo pipeline example');
|
|
140
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Convert an existing Playwright test into an Argo demo project.
|
|
144
|
+
* Generates demo script (with fixture swap + mark() calls),
|
|
145
|
+
* skeleton voiceover.json (with _hint fields for LLM),
|
|
146
|
+
* and skeleton overlays.json.
|
|
147
|
+
*/
|
|
148
|
+
export async function initFrom(options) {
|
|
149
|
+
const cwd = options.cwd ?? process.cwd();
|
|
150
|
+
const source = await readFile(options.from, 'utf-8');
|
|
151
|
+
// Derive demo name from filename: checkout.spec.ts → checkout
|
|
152
|
+
// Sanitize to match CLI's demo name validation: [a-zA-Z0-9][a-zA-Z0-9_-]*
|
|
153
|
+
const rawName = options.demo ??
|
|
154
|
+
basename(options.from)
|
|
155
|
+
.replace(/\.(spec|test|demo)\.(ts|js|mjs)$/, '')
|
|
156
|
+
.replace(/\.(ts|js|mjs)$/, '');
|
|
157
|
+
const demoName = rawName
|
|
158
|
+
.replace(/[^a-zA-Z0-9_-]/g, '-') // replace invalid chars with hyphens
|
|
159
|
+
.replace(/^-+/, '') // strip leading hyphens
|
|
160
|
+
.replace(/-+/g, '-'); // collapse consecutive hyphens
|
|
161
|
+
if (!demoName || !/^[a-zA-Z0-9]/.test(demoName)) {
|
|
162
|
+
throw new Error(`Cannot derive a valid demo name from "${rawName}". ` +
|
|
163
|
+
`Use --demo <name> to specify one (letters, numbers, hyphens, underscores).`);
|
|
164
|
+
}
|
|
165
|
+
const parsed = parsePlaywrightTest(source);
|
|
166
|
+
if (parsed.scenes.length === 0) {
|
|
167
|
+
throw new Error(`No scenes detected in ${options.from}. The file should contain Playwright actions like page.goto(), page.click(), etc.`);
|
|
168
|
+
}
|
|
169
|
+
const demosDir = join(cwd, 'demos');
|
|
170
|
+
await mkdir(demosDir, { recursive: true });
|
|
171
|
+
// Generate demo script
|
|
172
|
+
const demoScript = generateDemoScript(parsed);
|
|
173
|
+
await writeIfMissing(join(demosDir, `${demoName}.demo.ts`), demoScript);
|
|
174
|
+
// Generate voiceover skeleton with hints
|
|
175
|
+
const voiceover = generateVoiceoverSkeleton(parsed);
|
|
176
|
+
const voiceoverJson = JSON.stringify(voiceover, null, 2) + '\n';
|
|
177
|
+
await writeIfMissing(join(demosDir, `${demoName}.voiceover.json`), voiceoverJson);
|
|
178
|
+
// Generate overlays skeleton
|
|
179
|
+
const overlays = generateOverlaysSkeleton(parsed);
|
|
180
|
+
const overlaysJson = JSON.stringify(overlays, null, 2) + '\n';
|
|
181
|
+
await writeIfMissing(join(demosDir, `${demoName}.overlays.json`), overlaysJson);
|
|
182
|
+
// Scaffold config files if they don't exist
|
|
183
|
+
await writeIfMissing(join(cwd, 'argo.config.mjs'), ARGO_CONFIG);
|
|
184
|
+
await writeIfMissing(join(cwd, 'playwright.config.ts'), PLAYWRIGHT_CONFIG);
|
|
185
|
+
console.log(`\nConverted ${options.from} → ${demoName} (${parsed.scenes.length} scenes)`);
|
|
186
|
+
console.log('\nNext steps:');
|
|
187
|
+
console.log(` 1. Fill in voiceover text: demos/${demoName}.voiceover.json`);
|
|
188
|
+
console.log(` (use _hint fields as context, then remove them)`);
|
|
189
|
+
console.log(` 2. Refine overlays: demos/${demoName}.overlays.json`);
|
|
190
|
+
console.log(` 3. Run: npx argo pipeline ${demoName}`);
|
|
191
|
+
console.log(`\nTip: Ask your LLM to "flesh out the voiceover for ${demoName}" — it can`);
|
|
192
|
+
console.log('read the _hint fields and write natural narration text for each scene.');
|
|
193
|
+
}
|
|
141
194
|
//# sourceMappingURL=init.js.map
|
package/dist/init.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses a Playwright test/script and extracts logical scene boundaries
|
|
3
|
+
* for converting into an Argo demo.
|
|
4
|
+
*/
|
|
5
|
+
export interface ParsedScene {
|
|
6
|
+
/** Auto-generated scene name (kebab-case). */
|
|
7
|
+
name: string;
|
|
8
|
+
/** Lines of source code belonging to this scene. */
|
|
9
|
+
lines: string[];
|
|
10
|
+
/** Hint describing what happens in this scene (for LLM voiceover generation). */
|
|
11
|
+
hint: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ParsedPlaywrightTest {
|
|
14
|
+
/** The test/function name found in the source. */
|
|
15
|
+
testName: string;
|
|
16
|
+
/** Detected scenes with their source lines and hints. */
|
|
17
|
+
scenes: ParsedScene[];
|
|
18
|
+
/** Original source lines (for reference). */
|
|
19
|
+
sourceLines: string[];
|
|
20
|
+
/** Imports found in the original file. */
|
|
21
|
+
imports: string[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Parse a Playwright test file and extract scene boundaries.
|
|
25
|
+
*/
|
|
26
|
+
export declare function parsePlaywrightTest(source: string): ParsedPlaywrightTest;
|
|
27
|
+
/**
|
|
28
|
+
* Generate an Argo demo script from a parsed Playwright test.
|
|
29
|
+
*/
|
|
30
|
+
export declare function generateDemoScript(parsed: ParsedPlaywrightTest): string;
|
|
31
|
+
/**
|
|
32
|
+
* Generate a skeleton voiceover manifest from parsed scenes.
|
|
33
|
+
* Includes _hint fields for LLM-assisted text generation.
|
|
34
|
+
*/
|
|
35
|
+
export declare function generateVoiceoverSkeleton(parsed: ParsedPlaywrightTest): Array<{
|
|
36
|
+
scene: string;
|
|
37
|
+
text: string;
|
|
38
|
+
_hint: string;
|
|
39
|
+
}>;
|
|
40
|
+
/**
|
|
41
|
+
* Generate a skeleton overlays manifest from parsed scenes.
|
|
42
|
+
* Creates a lower-third for each scene as a starting point.
|
|
43
|
+
*/
|
|
44
|
+
export declare function generateOverlaysSkeleton(parsed: ParsedPlaywrightTest): Array<{
|
|
45
|
+
scene: string;
|
|
46
|
+
type: string;
|
|
47
|
+
text: string;
|
|
48
|
+
}>;
|
|
49
|
+
//# sourceMappingURL=parse-playwright.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-playwright.d.ts","sourceRoot":"","sources":["../src/parse-playwright.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,WAAW;IAC1B,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,oBAAoB;IACnC,kDAAkD;IAClD,QAAQ,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,6CAA6C;IAC7C,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,0CAA0C;IAC1C,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AA8FD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,oBAAoB,CA2ExE;AAmDD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAqCvE;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,oBAAoB,GAC3B,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAMvD;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,oBAAoB,GAC3B,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAMtD"}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses a Playwright test/script and extracts logical scene boundaries
|
|
3
|
+
* for converting into an Argo demo.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Pattern matchers for scene boundary detection.
|
|
7
|
+
* Each returns a scene name suggestion and hint text if the line matches.
|
|
8
|
+
*/
|
|
9
|
+
const BOUNDARY_PATTERNS = [
|
|
10
|
+
// test.step('name', ...) — explicit steps are the strongest signal
|
|
11
|
+
{
|
|
12
|
+
regex: /test\.step\(\s*(['"`])(.+?)\1/,
|
|
13
|
+
sceneName: (m) => slugify(m[2]),
|
|
14
|
+
hint: (m) => `test.step: ${m[2]}`,
|
|
15
|
+
alwaysBoundary: true,
|
|
16
|
+
},
|
|
17
|
+
// page.goto(url)
|
|
18
|
+
{
|
|
19
|
+
regex: /page\.goto\(\s*(['"`])(.+?)\1/,
|
|
20
|
+
sceneName: (m) => slugifyUrl(m[2]),
|
|
21
|
+
hint: (_m, line) => line.trim(),
|
|
22
|
+
alwaysBoundary: true,
|
|
23
|
+
},
|
|
24
|
+
// page.goto(variable/expression)
|
|
25
|
+
{
|
|
26
|
+
regex: /page\.goto\((.+?)\)/,
|
|
27
|
+
sceneName: () => 'navigate',
|
|
28
|
+
hint: (_m, line) => line.trim(),
|
|
29
|
+
alwaysBoundary: true,
|
|
30
|
+
},
|
|
31
|
+
// // Comment lines — strong candidate for scene label
|
|
32
|
+
{
|
|
33
|
+
regex: /^\s*\/\/\s*(.+)/,
|
|
34
|
+
sceneName: (m) => slugify(m[1]),
|
|
35
|
+
hint: (m) => `comment: ${m[1].trim()}`,
|
|
36
|
+
alwaysBoundary: true,
|
|
37
|
+
},
|
|
38
|
+
// page.click / locator.click
|
|
39
|
+
{
|
|
40
|
+
regex: /\.click\(/,
|
|
41
|
+
sceneName: () => 'click-action',
|
|
42
|
+
hint: (_m, line) => line.trim(),
|
|
43
|
+
alwaysBoundary: false,
|
|
44
|
+
},
|
|
45
|
+
// page.fill / locator.fill
|
|
46
|
+
{
|
|
47
|
+
regex: /\.fill\(/,
|
|
48
|
+
sceneName: () => 'form-input',
|
|
49
|
+
hint: (_m, line) => line.trim(),
|
|
50
|
+
alwaysBoundary: false,
|
|
51
|
+
},
|
|
52
|
+
// expect(...).toBeVisible / toHaveText / etc.
|
|
53
|
+
{
|
|
54
|
+
regex: /expect\(.+?\)\.\w+/,
|
|
55
|
+
sceneName: () => 'verify',
|
|
56
|
+
hint: (_m, line) => line.trim(),
|
|
57
|
+
alwaysBoundary: false,
|
|
58
|
+
},
|
|
59
|
+
// page.waitForSelector / page.waitForURL / locator.waitFor
|
|
60
|
+
{
|
|
61
|
+
regex: /\.waitFor(?:Selector|URL|Navigation)?\(/,
|
|
62
|
+
sceneName: () => 'wait',
|
|
63
|
+
hint: (_m, line) => line.trim(),
|
|
64
|
+
alwaysBoundary: false,
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
/** Lines that are structural and should not form their own scenes. */
|
|
68
|
+
const SKIP_PATTERNS = [
|
|
69
|
+
/^\s*$/, // blank lines
|
|
70
|
+
/^\s*import\s/, // imports
|
|
71
|
+
/^\s*(?:const|let|var)\s/, // variable declarations (unless they contain actions)
|
|
72
|
+
/^\s*[{})\]];?\s*$/, // lone braces/brackets
|
|
73
|
+
/^\s*(?:test|describe)\s*\(/, // test/describe wrappers
|
|
74
|
+
/^\s*(?:test|describe)\.(?!step)\w+\(/, // test.only, describe.skip, etc. (but NOT test.step)
|
|
75
|
+
/^\s*async\s/, // async arrow functions
|
|
76
|
+
/^\s*\}\s*\)\s*;?\s*$/, // closing }) patterns
|
|
77
|
+
];
|
|
78
|
+
function shouldSkipLine(line) {
|
|
79
|
+
// Don't skip if line contains a Playwright action
|
|
80
|
+
if (/page\.|locator|expect\(/.test(line))
|
|
81
|
+
return false;
|
|
82
|
+
return SKIP_PATTERNS.some((p) => p.test(line));
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Parse a Playwright test file and extract scene boundaries.
|
|
86
|
+
*/
|
|
87
|
+
export function parsePlaywrightTest(source) {
|
|
88
|
+
const sourceLines = source.split('\n');
|
|
89
|
+
// Extract imports
|
|
90
|
+
const imports = sourceLines.filter((l) => /^\s*import\s/.test(l));
|
|
91
|
+
// Find the test name
|
|
92
|
+
const testNameMatch = source.match(/test\(\s*(['"`])(.+?)\1/);
|
|
93
|
+
const testName = testNameMatch?.[2] ?? 'demo';
|
|
94
|
+
// Collect actions grouped into scenes
|
|
95
|
+
const scenes = [];
|
|
96
|
+
let currentActions = [];
|
|
97
|
+
let currentSceneName = '';
|
|
98
|
+
/** True when the current scene was named by test.step — suppresses other boundary flushes. */
|
|
99
|
+
let namedByStep = false;
|
|
100
|
+
let sceneNameCounts = new Map();
|
|
101
|
+
function flushScene() {
|
|
102
|
+
if (currentActions.length === 0)
|
|
103
|
+
return;
|
|
104
|
+
// Determine scene name
|
|
105
|
+
let name = currentSceneName || inferSceneName(currentActions);
|
|
106
|
+
name = deduplicateName(name, sceneNameCounts);
|
|
107
|
+
scenes.push({
|
|
108
|
+
name,
|
|
109
|
+
lines: currentActions.map((a) => a.line),
|
|
110
|
+
hint: currentActions.map((a) => a.hint).join(', '),
|
|
111
|
+
});
|
|
112
|
+
currentActions = [];
|
|
113
|
+
currentSceneName = '';
|
|
114
|
+
namedByStep = false;
|
|
115
|
+
}
|
|
116
|
+
for (const line of sourceLines) {
|
|
117
|
+
if (shouldSkipLine(line))
|
|
118
|
+
continue;
|
|
119
|
+
// Check if this line matches a boundary pattern
|
|
120
|
+
let matched = false;
|
|
121
|
+
for (const pattern of BOUNDARY_PATTERNS) {
|
|
122
|
+
const m = line.match(pattern.regex);
|
|
123
|
+
if (!m)
|
|
124
|
+
continue;
|
|
125
|
+
const isStep = pattern.regex.source.startsWith('test\\.step');
|
|
126
|
+
if (isStep) {
|
|
127
|
+
// test.step always starts a new scene
|
|
128
|
+
if (currentActions.length > 0)
|
|
129
|
+
flushScene();
|
|
130
|
+
currentSceneName = pattern.sceneName(m);
|
|
131
|
+
namedByStep = true;
|
|
132
|
+
}
|
|
133
|
+
else if (pattern.alwaysBoundary && !namedByStep && currentActions.length > 0) {
|
|
134
|
+
// Other boundaries (goto, comments) flush only if not inside a test.step scene
|
|
135
|
+
flushScene();
|
|
136
|
+
currentSceneName = pattern.sceneName(m);
|
|
137
|
+
}
|
|
138
|
+
else if (!currentSceneName) {
|
|
139
|
+
currentSceneName = pattern.sceneName(m);
|
|
140
|
+
}
|
|
141
|
+
currentActions.push({ line, hint: pattern.hint(m, line) });
|
|
142
|
+
matched = true;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
if (!matched && /page\.|locator|expect\(/.test(line)) {
|
|
146
|
+
// It's a Playwright action but not a recognized pattern — still include it
|
|
147
|
+
currentActions.push({ line, hint: line.trim() });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Flush remaining actions
|
|
151
|
+
flushScene();
|
|
152
|
+
return { testName, scenes, sourceLines, imports };
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Infer a scene name from the actions it contains.
|
|
156
|
+
*/
|
|
157
|
+
function inferSceneName(actions) {
|
|
158
|
+
for (const a of actions) {
|
|
159
|
+
// Try to extract a meaningful name from selectors
|
|
160
|
+
const selectorMatch = a.line.match(/['"`]([#.][a-zA-Z0-9_-]+)['"`]/);
|
|
161
|
+
if (selectorMatch) {
|
|
162
|
+
return slugify(selectorMatch[1].replace(/^[#.]/, ''));
|
|
163
|
+
}
|
|
164
|
+
// Try aria labels
|
|
165
|
+
const ariaMatch = a.line.match(/getByRole\(.+?,\s*\{\s*name:\s*['"`](.+?)['"`]/);
|
|
166
|
+
if (ariaMatch) {
|
|
167
|
+
return slugify(ariaMatch[1]);
|
|
168
|
+
}
|
|
169
|
+
// Try getByText
|
|
170
|
+
const textMatch = a.line.match(/getByText\(\s*['"`](.+?)['"`]/);
|
|
171
|
+
if (textMatch) {
|
|
172
|
+
return slugify(textMatch[1]);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return 'step';
|
|
176
|
+
}
|
|
177
|
+
function slugify(text) {
|
|
178
|
+
return text
|
|
179
|
+
.toLowerCase()
|
|
180
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
181
|
+
.replace(/^-+|-+$/g, '')
|
|
182
|
+
.slice(0, 30) || 'step';
|
|
183
|
+
}
|
|
184
|
+
function slugifyUrl(url) {
|
|
185
|
+
// Extract the meaningful path segment
|
|
186
|
+
try {
|
|
187
|
+
const pathname = new URL(url, 'http://localhost').pathname;
|
|
188
|
+
const segment = pathname.split('/').filter(Boolean).pop() ?? 'home';
|
|
189
|
+
return slugify(segment);
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return slugify(url);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function deduplicateName(name, counts) {
|
|
196
|
+
const count = counts.get(name) ?? 0;
|
|
197
|
+
counts.set(name, count + 1);
|
|
198
|
+
return count === 0 ? name : `${name}-${count + 1}`;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Generate an Argo demo script from a parsed Playwright test.
|
|
202
|
+
*/
|
|
203
|
+
export function generateDemoScript(parsed) {
|
|
204
|
+
const lines = [];
|
|
205
|
+
// Check if any scene lines contain expect() to decide on imports
|
|
206
|
+
const hasExpect = parsed.scenes.some(s => s.lines.some(l => /\bexpect\s*\(/.test(l)));
|
|
207
|
+
if (hasExpect) {
|
|
208
|
+
lines.push("import { test, expect } from '@argo-video/cli';");
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
lines.push("import { test } from '@argo-video/cli';");
|
|
212
|
+
}
|
|
213
|
+
lines.push("import { showOverlay, withOverlay } from '@argo-video/cli';");
|
|
214
|
+
lines.push('');
|
|
215
|
+
lines.push(`test('${parsed.testName}', async ({ page, narration }) => {`);
|
|
216
|
+
for (const scene of parsed.scenes) {
|
|
217
|
+
lines.push('');
|
|
218
|
+
lines.push(` narration.mark('${scene.name}');`);
|
|
219
|
+
for (const sourceLine of scene.lines) {
|
|
220
|
+
// Strip test.step() wrappers — we flatten steps into narration.mark() scenes
|
|
221
|
+
const stripped = sourceLine
|
|
222
|
+
.replace(/^\s*test\.step\s*\(\s*(['"`]).*?\1\s*,\s*async\s*\(\s*\)\s*=>\s*\{\s*$/, '')
|
|
223
|
+
.replace(/^\s*\}\s*\)\s*;?\s*$/, '');
|
|
224
|
+
if (stripped === '')
|
|
225
|
+
continue;
|
|
226
|
+
// Normalize indentation to 2-space inside the test block
|
|
227
|
+
const trimmed = stripped.replace(/^\s+/, '');
|
|
228
|
+
lines.push(` ${trimmed}`);
|
|
229
|
+
}
|
|
230
|
+
lines.push(` await page.waitForTimeout(narration.durationFor('${scene.name}'));`);
|
|
231
|
+
}
|
|
232
|
+
lines.push('});');
|
|
233
|
+
lines.push('');
|
|
234
|
+
return lines.join('\n');
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Generate a skeleton voiceover manifest from parsed scenes.
|
|
238
|
+
* Includes _hint fields for LLM-assisted text generation.
|
|
239
|
+
*/
|
|
240
|
+
export function generateVoiceoverSkeleton(parsed) {
|
|
241
|
+
return parsed.scenes.map((s) => ({
|
|
242
|
+
scene: s.name,
|
|
243
|
+
text: '',
|
|
244
|
+
_hint: s.hint,
|
|
245
|
+
}));
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Generate a skeleton overlays manifest from parsed scenes.
|
|
249
|
+
* Creates a lower-third for each scene as a starting point.
|
|
250
|
+
*/
|
|
251
|
+
export function generateOverlaysSkeleton(parsed) {
|
|
252
|
+
return parsed.scenes.map((s) => ({
|
|
253
|
+
scene: s.name,
|
|
254
|
+
type: 'lower-third',
|
|
255
|
+
text: humanize(s.name),
|
|
256
|
+
}));
|
|
257
|
+
}
|
|
258
|
+
/** Convert kebab-case scene name to Title Case display text. */
|
|
259
|
+
function humanize(name) {
|
|
260
|
+
return name
|
|
261
|
+
.split('-')
|
|
262
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
263
|
+
.join(' ');
|
|
264
|
+
}
|
|
265
|
+
//# sourceMappingURL=parse-playwright.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-playwright.js","sourceRoot":"","sources":["../src/parse-playwright.ts"],"names":[],"mappings":"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"}
|
|
@@ -22,11 +22,11 @@ export class ElevenLabsEngine {
|
|
|
22
22
|
throw new Error('TTS text must not be empty');
|
|
23
23
|
let ElevenLabsClient;
|
|
24
24
|
try {
|
|
25
|
-
// @ts-ignore — elevenlabs is an optional dependency
|
|
26
|
-
({ ElevenLabsClient } = await import('elevenlabs'));
|
|
25
|
+
// @ts-ignore — @elevenlabs/elevenlabs-js is an optional dependency
|
|
26
|
+
({ ElevenLabsClient } = await import('@elevenlabs/elevenlabs-js'));
|
|
27
27
|
}
|
|
28
28
|
catch {
|
|
29
|
-
throw new Error("ElevenLabs TTS engine requires the 'elevenlabs' package. Install it with: npm i elevenlabs");
|
|
29
|
+
throw new Error("ElevenLabs TTS engine requires the '@elevenlabs/elevenlabs-js' package. Install it with: npm i @elevenlabs/elevenlabs-js");
|
|
30
30
|
}
|
|
31
31
|
const client = new ElevenLabsClient({ apiKey: this.resolveApiKey() });
|
|
32
32
|
const audioStream = await client.textToSpeech.convert(options.voice ?? '21m00Tcm4TlvDq8ikWAM', // Rachel default
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"elevenlabs.js","sourceRoot":"","sources":["../../../src/tts/engines/elevenlabs.ts"],"names":[],"mappings":"AASA,MAAM,OAAO,gBAAgB;IACnB,MAAM,CAAS;IACf,KAAK,CAAS;IACd,SAAS,CAAS;IAClB,eAAe,CAAS;IAEhC,YAAY,OAAiC;QAC3C,IAAI,CAAC,MAAM,GAAG,OAAO,EAAE,MAAM,IAAI,EAAE,CAAC;QACpC,IAAI,CAAC,KAAK,GAAG,OAAO,EAAE,KAAK,IAAI,uBAAuB,CAAC;QACvD,IAAI,CAAC,SAAS,GAAG,OAAO,EAAE,SAAS,IAAI,GAAG,CAAC;QAC3C,IAAI,CAAC,eAAe,GAAG,OAAO,EAAE,eAAe,IAAI,IAAI,CAAC;IAC1D,CAAC;IAEO,aAAa;QACnB,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,EAAE,CAAC;QAChE,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,KAAK,CACb,6CAA6C;gBAC7C,oEAAoE,CACrE,CAAC;QACJ,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,IAAY,EAAE,OAAyB;QACpD,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;QAEjE,IAAI,gBAAqB,CAAC;QAC1B,IAAI,CAAC;YACH,
|
|
1
|
+
{"version":3,"file":"elevenlabs.js","sourceRoot":"","sources":["../../../src/tts/engines/elevenlabs.ts"],"names":[],"mappings":"AASA,MAAM,OAAO,gBAAgB;IACnB,MAAM,CAAS;IACf,KAAK,CAAS;IACd,SAAS,CAAS;IAClB,eAAe,CAAS;IAEhC,YAAY,OAAiC;QAC3C,IAAI,CAAC,MAAM,GAAG,OAAO,EAAE,MAAM,IAAI,EAAE,CAAC;QACpC,IAAI,CAAC,KAAK,GAAG,OAAO,EAAE,KAAK,IAAI,uBAAuB,CAAC;QACvD,IAAI,CAAC,SAAS,GAAG,OAAO,EAAE,SAAS,IAAI,GAAG,CAAC;QAC3C,IAAI,CAAC,eAAe,GAAG,OAAO,EAAE,eAAe,IAAI,IAAI,CAAC;IAC1D,CAAC;IAEO,aAAa;QACnB,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,EAAE,CAAC;QAChE,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,KAAK,CACb,6CAA6C;gBAC7C,oEAAoE,CACrE,CAAC;QACJ,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,IAAY,EAAE,OAAyB;QACpD,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;QAEjE,IAAI,gBAAqB,CAAC;QAC1B,IAAI,CAAC;YACH,mEAAmE;YACnE,CAAC,EAAE,gBAAgB,EAAE,GAAG,MAAM,MAAM,CAAC,2BAA2B,CAAC,CAAC,CAAC;QACrE,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CACb,0HAA0H,CAC3H,CAAC;QACJ,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,gBAAgB,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;QACtE,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,OAAO,CACnD,OAAO,CAAC,KAAK,IAAI,sBAAsB,EAAE,iBAAiB;QAC1D;YACE,IAAI;YACJ,QAAQ,EAAE,IAAI,CAAC,KAAK;YACpB,cAAc,EAAE;gBACd,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,gBAAgB,EAAE,IAAI,CAAC,eAAe;aACvC;SACF,CACF,CAAC;QAEF,6BAA6B;QAC7B,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;YACtC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAClC,CAAC;QACD,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAExC,iCAAiC;QACjC,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;QACtD,OAAO,YAAY,CAAC,SAAS,CAAC,CAAC;IACjC,CAAC;CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sarvam.d.ts","sourceRoot":"","sources":["../../../src/tts/engines/sarvam.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhE,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,YAAa,YAAW,SAAS;IAC5C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;gBAEV,OAAO,CAAC,EAAE,mBAAmB;IAKzC,OAAO,CAAC,aAAa;IAWf,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"sarvam.d.ts","sourceRoot":"","sources":["../../../src/tts/engines/sarvam.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhE,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,YAAa,YAAW,SAAS;IAC5C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;gBAEV,OAAO,CAAC,EAAE,mBAAmB;IAKzC,OAAO,CAAC,aAAa;IAWf,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC;CAoCzE"}
|
|
@@ -16,33 +16,30 @@ export class SarvamEngine {
|
|
|
16
16
|
async generate(text, options) {
|
|
17
17
|
if (!text?.trim())
|
|
18
18
|
throw new Error('TTS text must not be empty');
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
inputs: [text],
|
|
27
|
-
target_language_code: options.lang ?? 'hi-IN',
|
|
28
|
-
speaker: options.voice ?? 'meera',
|
|
29
|
-
model: this.model,
|
|
30
|
-
pitch: 0,
|
|
31
|
-
pace: options.speed ?? 1.0,
|
|
32
|
-
loudness: 1.5,
|
|
33
|
-
enable_preprocessing: true,
|
|
34
|
-
}),
|
|
35
|
-
});
|
|
36
|
-
if (!response.ok) {
|
|
37
|
-
const body = await response.text();
|
|
38
|
-
throw new Error(`Sarvam TTS API error ${response.status}: ${body}`);
|
|
19
|
+
let SarvamAI;
|
|
20
|
+
try {
|
|
21
|
+
// @ts-ignore — sarvamai is an optional dependency
|
|
22
|
+
({ default: SarvamAI } = await import('sarvamai'));
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
throw new Error("Sarvam TTS engine requires the 'sarvamai' package. Install it with: npm i sarvamai");
|
|
39
26
|
}
|
|
40
|
-
const
|
|
41
|
-
|
|
27
|
+
const client = new SarvamAI({ apiSubscriptionKey: this.resolveApiKey() });
|
|
28
|
+
const response = await client.textToSpeech.convert({
|
|
29
|
+
inputs: [text],
|
|
30
|
+
target_language_code: options.lang ?? 'hi-IN',
|
|
31
|
+
speaker: options.voice ?? 'meera',
|
|
32
|
+
model: this.model,
|
|
33
|
+
pitch: 0,
|
|
34
|
+
pace: options.speed ?? 1.0,
|
|
35
|
+
loudness: 1.5,
|
|
36
|
+
enable_preprocessing: true,
|
|
37
|
+
});
|
|
38
|
+
if (!response.audios?.[0]) {
|
|
42
39
|
throw new Error('Sarvam TTS returned no audio data');
|
|
43
40
|
}
|
|
44
41
|
// Sarvam returns base64-encoded WAV
|
|
45
|
-
const audioBuffer = Buffer.from(
|
|
42
|
+
const audioBuffer = Buffer.from(response.audios[0], 'base64');
|
|
46
43
|
// Convert to Argo WAV format (mono Float32 24kHz)
|
|
47
44
|
const { convertToWav } = await import('../engine.js');
|
|
48
45
|
return convertToWav(audioBuffer);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sarvam.js","sourceRoot":"","sources":["../../../src/tts/engines/sarvam.ts"],"names":[],"mappings":"AAOA,MAAM,OAAO,YAAY;IACf,MAAM,CAAS;IACf,KAAK,CAAS;IAEtB,YAAY,OAA6B;QACvC,IAAI,CAAC,MAAM,GAAG,OAAO,EAAE,MAAM,IAAI,EAAE,CAAC;QACpC,IAAI,CAAC,KAAK,GAAG,OAAO,EAAE,KAAK,IAAI,WAAW,CAAC;IAC7C,CAAC;IAEO,aAAa;QACnB,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,EAAE,CAAC;QAC5D,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,KAAK,CACb,yCAAyC;gBACzC,gEAAgE,CACjE,CAAC;QACJ,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,IAAY,EAAE,OAAyB;QACpD,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;QAEjE,
|
|
1
|
+
{"version":3,"file":"sarvam.js","sourceRoot":"","sources":["../../../src/tts/engines/sarvam.ts"],"names":[],"mappings":"AAOA,MAAM,OAAO,YAAY;IACf,MAAM,CAAS;IACf,KAAK,CAAS;IAEtB,YAAY,OAA6B;QACvC,IAAI,CAAC,MAAM,GAAG,OAAO,EAAE,MAAM,IAAI,EAAE,CAAC;QACpC,IAAI,CAAC,KAAK,GAAG,OAAO,EAAE,KAAK,IAAI,WAAW,CAAC;IAC7C,CAAC;IAEO,aAAa;QACnB,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,EAAE,CAAC;QAC5D,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,KAAK,CACb,yCAAyC;gBACzC,gEAAgE,CACjE,CAAC;QACJ,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,IAAY,EAAE,OAAyB;QACpD,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;QAEjE,IAAI,QAAa,CAAC;QAClB,IAAI,CAAC;YACH,kDAAkD;YAClD,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;QACrD,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CACb,oFAAoF,CACrF,CAAC;QACJ,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,QAAQ,CAAC,EAAE,kBAAkB,EAAE,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;QAC1E,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC;YACjD,MAAM,EAAE,CAAC,IAAI,CAAC;YACd,oBAAoB,EAAE,OAAO,CAAC,IAAI,IAAI,OAAO;YAC7C,OAAO,EAAE,OAAO,CAAC,KAAK,IAAI,OAAO;YACjC,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,KAAK,EAAE,CAAC;YACR,IAAI,EAAE,OAAO,CAAC,KAAK,IAAI,GAAG;YAC1B,QAAQ,EAAE,GAAG;YACb,oBAAoB,EAAE,IAAI;SAC3B,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;QACvD,CAAC;QAED,oCAAoC;QACpC,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;QAE9D,kDAAkD;QAClD,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;QACtD,OAAO,YAAY,CAAC,WAAW,CAAC,CAAC;IACnC,CAAC;CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/tts/generate.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAI7C,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,SAAS,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/C;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/tts/generate.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAI7C,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,SAAS,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/C;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAsExF"}
|
package/dist/tts/generate.js
CHANGED
|
@@ -45,17 +45,22 @@ export async function generateClips(options) {
|
|
|
45
45
|
};
|
|
46
46
|
return { entry, clipPath: cache.getClipPath(demoName, entry) };
|
|
47
47
|
});
|
|
48
|
-
//
|
|
49
|
-
// safe for concurrent generate() calls
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
48
|
+
// Log per-scene status and generate uncached clips sequentially —
|
|
49
|
+
// Kokoro's ONNX runtime is not safe for concurrent generate() calls.
|
|
50
|
+
for (const { entry } of entries) {
|
|
51
|
+
if (cache.isCached(demoName, entry)) {
|
|
52
|
+
console.log(` ▸ ${entry.scene} (cached)`);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
process.stdout.write(` ▸ ${entry.scene} (generating...)`);
|
|
56
|
+
const wavBuffer = await engine.generate(entry.text, {
|
|
57
|
+
voice: entry.voice,
|
|
58
|
+
speed: entry.speed,
|
|
59
|
+
lang: entry.lang,
|
|
60
|
+
});
|
|
61
|
+
cache.cacheClip(demoName, entry, wavBuffer);
|
|
62
|
+
process.stdout.write(' done\n');
|
|
63
|
+
}
|
|
59
64
|
}
|
|
60
65
|
// Read results (all clips now cached)
|
|
61
66
|
return entries.map(({ entry, clipPath }) => {
|
package/dist/tts/generate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../../src/tts/generate.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAsB,MAAM,YAAY,CAAC;AAgB3D,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAA6B;IAC/D,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;IAE1E,2BAA2B;IAC3B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,4BAA4B,YAAY,EAAE,CAAC,CAAC;IAC9D,CAAC;IAED,yBAAyB;IACzB,IAAI,UAAqB,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QACvD,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,WAAW,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CAAC,4BAA4B,YAAY,KAAK,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9E,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,YAAY,YAAY,4BAA4B,CAAC,CAAC;IACxE,CAAC;IAED,sBAAsB;IACtB,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;QAC/B,MAAM,CAAC,GAAG,KAAgC,CAAC;QAC3C,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC9D,MAAM,IAAI,KAAK,CAAC,oEAAoE,CAAC,CAAC;QACxF,CAAC;IACH,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,SAAS,CAAC,WAAW,CAAC,CAAC;IAEzC,uCAAuC;IACvC,MAAM,OAAO,GAAiD,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QACnF,MAAM,CAAC,GAAG,GAA8B,CAAC;QACzC,MAAM,KAAK,GAAkB;YAC3B,KAAK,EAAE,CAAC,CAAC,KAAe;YACxB,IAAI,EAAE,CAAC,CAAC,IAAc;YACtB,KAAK,EAAG,CAAC,CAAC,KAA4B,IAAI,QAAQ,EAAE,KAAK;YACzD,KAAK,EAAG,CAAC,CAAC,KAA4B,IAAI,QAAQ,EAAE,KAAK;YACzD,IAAI,EAAE,CAAC,CAAC,IAA0B;SACnC,CAAC;QACF,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,WAAW,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,
|
|
1
|
+
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../../src/tts/generate.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@argo-video/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Turn Playwright demo scripts into polished product demo videos with AI voiceover",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -29,6 +29,12 @@
|
|
|
29
29
|
"commander": "^12.0.0",
|
|
30
30
|
"kokoro-js": "^1.2.1"
|
|
31
31
|
},
|
|
32
|
+
"optionalDependencies": {
|
|
33
|
+
"@elevenlabs/elevenlabs-js": "^2.0.0",
|
|
34
|
+
"@google/genai": "^1.0.0",
|
|
35
|
+
"openai": "^4.0.0",
|
|
36
|
+
"sarvamai": "^1.0.0"
|
|
37
|
+
},
|
|
32
38
|
"publishConfig": {
|
|
33
39
|
"access": "public"
|
|
34
40
|
},
|
|
@@ -39,7 +45,8 @@
|
|
|
39
45
|
"license": "MIT",
|
|
40
46
|
"files": [
|
|
41
47
|
"dist",
|
|
42
|
-
"bin"
|
|
48
|
+
"bin",
|
|
49
|
+
"scripts"
|
|
43
50
|
],
|
|
44
51
|
"devDependencies": {
|
|
45
52
|
"@playwright/test": "^1.50.0",
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import random
|
|
5
|
+
|
|
6
|
+
from PIL import Image, ImageDraw, ImageFilter, ImageFont
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
10
|
+
ASSETS = ROOT / "assets"
|
|
11
|
+
OUTPUT_PATH = ASSETS / "logo-thumb.png"
|
|
12
|
+
SOURCE_MARK_PATH = ASSETS / "logo-mark-source.png"
|
|
13
|
+
|
|
14
|
+
WIDTH = 1920
|
|
15
|
+
HEIGHT = 1080
|
|
16
|
+
|
|
17
|
+
MONO_FONT = "/System/Library/Fonts/SFNSMono.ttf"
|
|
18
|
+
DISPLAY_BOLD = "/Library/Fonts/SF-Compact-Display-Bold.otf"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def load_font(path: str, size: int) -> ImageFont.FreeTypeFont:
|
|
22
|
+
return ImageFont.truetype(path, size=size)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def normalize_mark() -> Image.Image:
|
|
26
|
+
if SOURCE_MARK_PATH.exists():
|
|
27
|
+
return Image.open(SOURCE_MARK_PATH).convert("RGBA")
|
|
28
|
+
|
|
29
|
+
original = Image.open(OUTPUT_PATH).convert("RGBA")
|
|
30
|
+
mark = Image.new("RGBA", original.size, (0, 0, 0, 0))
|
|
31
|
+
in_px = original.load()
|
|
32
|
+
out_px = mark.load()
|
|
33
|
+
|
|
34
|
+
for y in range(original.height):
|
|
35
|
+
for x in range(original.width):
|
|
36
|
+
r, g, b, _ = in_px[x, y]
|
|
37
|
+
if b > 150 and g > 110 and r < 140:
|
|
38
|
+
out_px[x, y] = (107, 180, 255, 255)
|
|
39
|
+
|
|
40
|
+
bbox = mark.getbbox()
|
|
41
|
+
if bbox is None:
|
|
42
|
+
raise RuntimeError("Could not isolate source logo mark")
|
|
43
|
+
|
|
44
|
+
cropped = mark.crop(bbox)
|
|
45
|
+
cropped.save(SOURCE_MARK_PATH)
|
|
46
|
+
return cropped
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def lerp(a: int, b: int, t: float) -> int:
|
|
50
|
+
return int(a + (b - a) * t)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def make_background() -> Image.Image:
|
|
54
|
+
image = Image.new("RGBA", (WIDTH, HEIGHT), (0, 0, 0, 255))
|
|
55
|
+
px = image.load()
|
|
56
|
+
|
|
57
|
+
top_left = (8, 10, 18)
|
|
58
|
+
bottom_right = (18, 22, 37)
|
|
59
|
+
top_right = (12, 14, 26)
|
|
60
|
+
|
|
61
|
+
for y in range(HEIGHT):
|
|
62
|
+
ty = y / (HEIGHT - 1)
|
|
63
|
+
for x in range(WIDTH):
|
|
64
|
+
tx = x / (WIDTH - 1)
|
|
65
|
+
r = lerp(lerp(top_left[0], top_right[0], tx), bottom_right[0], ty)
|
|
66
|
+
g = lerp(lerp(top_left[1], top_right[1], tx), bottom_right[1], ty)
|
|
67
|
+
b = lerp(lerp(top_left[2], top_right[2], tx), bottom_right[2], ty)
|
|
68
|
+
px[x, y] = (r, g, b, 255)
|
|
69
|
+
|
|
70
|
+
return image
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def add_radial_glow(base: Image.Image, center: tuple[int, int], radius: int, color: tuple[int, int, int], alpha: int, blur: int) -> None:
|
|
74
|
+
layer = Image.new("RGBA", base.size, (0, 0, 0, 0))
|
|
75
|
+
draw = ImageDraw.Draw(layer)
|
|
76
|
+
x, y = center
|
|
77
|
+
draw.ellipse((x - radius, y - radius, x + radius, y + radius), fill=color + (alpha,))
|
|
78
|
+
layer = layer.filter(ImageFilter.GaussianBlur(blur))
|
|
79
|
+
base.alpha_composite(layer)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def add_grid(base: Image.Image) -> None:
|
|
83
|
+
grid = Image.new("RGBA", base.size, (0, 0, 0, 0))
|
|
84
|
+
draw = ImageDraw.Draw(grid)
|
|
85
|
+
|
|
86
|
+
for x in range(120, WIDTH, 96):
|
|
87
|
+
draw.line((x, 0, x, HEIGHT), fill=(92, 126, 188, 14), width=1)
|
|
88
|
+
for y in range(96, HEIGHT, 96):
|
|
89
|
+
draw.line((0, y, WIDTH, y), fill=(92, 126, 188, 10), width=1)
|
|
90
|
+
|
|
91
|
+
random.seed(7)
|
|
92
|
+
for _ in range(240):
|
|
93
|
+
x = random.randint(0, WIDTH - 1)
|
|
94
|
+
y = random.randint(0, HEIGHT - 1)
|
|
95
|
+
draw.point((x, y), fill=(160, 185, 255, random.randint(10, 28)))
|
|
96
|
+
|
|
97
|
+
base.alpha_composite(grid)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def draw_mark(base: Image.Image, mark: Image.Image) -> None:
|
|
101
|
+
mark = mark.resize((1440, int(mark.height * (1440 / mark.width))), Image.Resampling.LANCZOS)
|
|
102
|
+
|
|
103
|
+
glow = Image.new("RGBA", base.size, (0, 0, 0, 0))
|
|
104
|
+
glow_mark = mark.copy()
|
|
105
|
+
glow_mark = glow_mark.filter(ImageFilter.GaussianBlur(14))
|
|
106
|
+
gx = (WIDTH - glow_mark.width) // 2
|
|
107
|
+
gy = 200
|
|
108
|
+
glow.alpha_composite(glow_mark, (gx, gy))
|
|
109
|
+
|
|
110
|
+
tint = Image.new("RGBA", base.size, (0, 0, 0, 0))
|
|
111
|
+
tint_draw = ImageDraw.Draw(tint)
|
|
112
|
+
tint_draw.ellipse((420, 220, 1500, 760), fill=(71, 136, 255, 30))
|
|
113
|
+
tint = tint.filter(ImageFilter.GaussianBlur(54))
|
|
114
|
+
|
|
115
|
+
base.alpha_composite(tint)
|
|
116
|
+
base.alpha_composite(glow, (0, 0))
|
|
117
|
+
|
|
118
|
+
mark_shadow = Image.new("RGBA", base.size, (0, 0, 0, 0))
|
|
119
|
+
shadow = mark.copy().filter(ImageFilter.GaussianBlur(18))
|
|
120
|
+
mark_shadow.alpha_composite(shadow, (gx, gy + 8))
|
|
121
|
+
base.alpha_composite(mark_shadow)
|
|
122
|
+
base.alpha_composite(mark, (gx, gy))
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def draw_command_card(base: Image.Image) -> None:
|
|
126
|
+
card = Image.new("RGBA", base.size, (0, 0, 0, 0))
|
|
127
|
+
draw = ImageDraw.Draw(card)
|
|
128
|
+
|
|
129
|
+
x0, y0, x1, y1 = 500, 760, 1420, 892
|
|
130
|
+
shadow = Image.new("RGBA", base.size, (0, 0, 0, 0))
|
|
131
|
+
ImageDraw.Draw(shadow).rounded_rectangle((x0, y0 + 18, x1, y1 + 18), radius=34, fill=(0, 0, 0, 120))
|
|
132
|
+
shadow = shadow.filter(ImageFilter.GaussianBlur(24))
|
|
133
|
+
base.alpha_composite(shadow)
|
|
134
|
+
|
|
135
|
+
draw.rounded_rectangle((x0, y0, x1, y1), radius=34, fill=(14, 18, 30, 228), outline=(97, 127, 183, 88), width=2)
|
|
136
|
+
draw.rounded_rectangle((x0 + 2, y0 + 2, x1 - 2, y0 + 38), radius=32, fill=(18, 23, 38, 245))
|
|
137
|
+
|
|
138
|
+
button_y = y0 + 20
|
|
139
|
+
for idx, color in enumerate(((255, 95, 86), (255, 189, 46), (39, 201, 63))):
|
|
140
|
+
bx = x0 + 30 + idx * 22
|
|
141
|
+
draw.ellipse((bx, button_y, bx + 12, button_y + 12), fill=color)
|
|
142
|
+
|
|
143
|
+
mono = load_font(MONO_FONT, 40)
|
|
144
|
+
small = load_font(MONO_FONT, 24)
|
|
145
|
+
|
|
146
|
+
prompt_y = y0 + 64
|
|
147
|
+
draw.text((x0 + 34, prompt_y), "$", font=mono, fill=(123, 217, 129))
|
|
148
|
+
draw.text((x0 + 72, prompt_y), "npx argo", font=mono, fill=(110, 187, 255))
|
|
149
|
+
draw.text((x0 + 330, prompt_y), "pipeline", font=mono, fill=(233, 239, 252))
|
|
150
|
+
draw.text((x0 + 566, prompt_y), "showcase", font=mono, fill=(169, 146, 255))
|
|
151
|
+
|
|
152
|
+
note = "local-first • webkit-friendly • retina-ready"
|
|
153
|
+
note_box = draw.textbbox((0, 0), note, font=small)
|
|
154
|
+
note_x = (WIDTH - (note_box[2] - note_box[0])) / 2
|
|
155
|
+
draw.text((note_x, y1 + 24), note, font=small, fill=(100, 121, 160))
|
|
156
|
+
|
|
157
|
+
base.alpha_composite(card)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def main() -> None:
|
|
161
|
+
ASSETS.mkdir(exist_ok=True)
|
|
162
|
+
mark = normalize_mark()
|
|
163
|
+
image = make_background()
|
|
164
|
+
add_radial_glow(image, (WIDTH // 2, 420), 420, (48, 99, 235), 48, 96)
|
|
165
|
+
add_radial_glow(image, (1470, 210), 220, (41, 91, 235), 42, 84)
|
|
166
|
+
add_radial_glow(image, (360, 910), 300, (43, 74, 170), 34, 92)
|
|
167
|
+
add_grid(image)
|
|
168
|
+
draw_mark(image, mark)
|
|
169
|
+
draw_command_card(image)
|
|
170
|
+
image.convert("RGB").save(OUTPUT_PATH, quality=95)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
if __name__ == "__main__":
|
|
174
|
+
main()
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Record and process a voice reference clip for mlx-audio voice cloning.
|
|
4
|
+
# Usage: ./scripts/record-voice-ref.sh [output_path]
|
|
5
|
+
#
|
|
6
|
+
# Requirements: ffmpeg (brew install ffmpeg), macOS with built-in mic
|
|
7
|
+
#
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
OUTPUT="${1:-assets/ref-voice.wav}"
|
|
11
|
+
RAW_FILE=$(mktemp /tmp/voice-ref-raw.XXXXXX.wav)
|
|
12
|
+
|
|
13
|
+
# Suggested text — covers a wide range of English phonemes
|
|
14
|
+
cat <<'PROMPT'
|
|
15
|
+
╔══════════════════════════════════════════════════════════════════╗
|
|
16
|
+
║ Voice Reference Recording ║
|
|
17
|
+
╠══════════════════════════════════════════════════════════════════╣
|
|
18
|
+
║ ║
|
|
19
|
+
║ Read the following text naturally, at your demo narration pace: ║
|
|
20
|
+
║ ║
|
|
21
|
+
║ "Hi, my name is [YOUR NAME]. I build developer tools and love ║
|
|
22
|
+
║ creating great product demos. The quick brown fox jumps over ║
|
|
23
|
+
║ the lazy dog. Pack my box with five dozen liquor jugs." ║
|
|
24
|
+
║ ║
|
|
25
|
+
║ Tips: ║
|
|
26
|
+
║ • Sit ~6 inches from the mic ║
|
|
27
|
+
║ • Use a quiet room (close windows, turn off fans) ║
|
|
28
|
+
║ • Speak clearly at your natural pace ║
|
|
29
|
+
║ • Aim for 5–15 seconds ║
|
|
30
|
+
║ ║
|
|
31
|
+
╚══════════════════════════════════════════════════════════════════╝
|
|
32
|
+
|
|
33
|
+
PROMPT
|
|
34
|
+
|
|
35
|
+
echo "Press ENTER to start recording (Ctrl+C to cancel)..."
|
|
36
|
+
read -r
|
|
37
|
+
|
|
38
|
+
echo "🎙 Recording... Press Ctrl+C when done."
|
|
39
|
+
|
|
40
|
+
# Record using macOS Core Audio (avfoundation) via ffmpeg
|
|
41
|
+
# Uses the default input device (built-in mic or whatever is selected in
|
|
42
|
+
# System Settings > Sound > Input)
|
|
43
|
+
ffmpeg -y -f avfoundation -i ":default" \
|
|
44
|
+
-acodec pcm_s16le -ar 44100 -ac 1 \
|
|
45
|
+
"$RAW_FILE" 2>/dev/null || true
|
|
46
|
+
|
|
47
|
+
echo ""
|
|
48
|
+
echo "Processing audio..."
|
|
49
|
+
|
|
50
|
+
# Get duration
|
|
51
|
+
DURATION=$(ffprobe -v error -show_entries format=duration \
|
|
52
|
+
-of csv=p=0 "$RAW_FILE" 2>/dev/null || echo "0")
|
|
53
|
+
|
|
54
|
+
if [ "$DURATION" = "0" ] || [ "$(echo "$DURATION < 2" | bc -l 2>/dev/null || echo 0)" = "1" ]; then
|
|
55
|
+
echo "Error: Recording too short or failed. Try again."
|
|
56
|
+
rm -f "$RAW_FILE"
|
|
57
|
+
exit 1
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# Create output directory if needed
|
|
61
|
+
mkdir -p "$(dirname "$OUTPUT")"
|
|
62
|
+
|
|
63
|
+
# Process: mono, 24kHz, noise-reduced, normalized
|
|
64
|
+
ffmpeg -y -i "$RAW_FILE" \
|
|
65
|
+
-af "highpass=f=80,lowpass=f=12000,afftdn=nf=-25,loudnorm=I=-16:TP=-1.5:LRA=11" \
|
|
66
|
+
-ar 24000 -ac 1 -acodec pcm_s16le \
|
|
67
|
+
"$OUTPUT" 2>/dev/null
|
|
68
|
+
|
|
69
|
+
rm -f "$RAW_FILE"
|
|
70
|
+
|
|
71
|
+
FINAL_DURATION=$(ffprobe -v error -show_entries format=duration \
|
|
72
|
+
-of csv=p=0 "$OUTPUT" 2>/dev/null)
|
|
73
|
+
|
|
74
|
+
echo ""
|
|
75
|
+
echo "Done! Saved to: $OUTPUT (${FINAL_DURATION}s)"
|
|
76
|
+
echo ""
|
|
77
|
+
echo "Next steps:"
|
|
78
|
+
echo " 1. Listen: ffplay $OUTPUT"
|
|
79
|
+
echo " 2. Add to your argo config:"
|
|
80
|
+
echo ""
|
|
81
|
+
echo " tts: {"
|
|
82
|
+
echo " engine: engines.mlxAudio({"
|
|
83
|
+
echo " model: 'mlx-community/Qwen3-TTS-12Hz-0.6B-Base-bf16',"
|
|
84
|
+
echo " refAudio: './$OUTPUT',"
|
|
85
|
+
echo " refText: 'YOUR EXACT TRANSCRIPT HERE',"
|
|
86
|
+
echo " }),"
|
|
87
|
+
echo " }"
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Set up mlx-audio with all dependencies for Argo voice cloning.
|
|
4
|
+
# Usage: ./scripts/setup-mlx-audio.sh
|
|
5
|
+
#
|
|
6
|
+
# Creates a .venv in the project root with mlx-audio and its
|
|
7
|
+
# transitive deps that aren't auto-installed.
|
|
8
|
+
#
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
VENV_DIR="${1:-.venv}"
|
|
12
|
+
|
|
13
|
+
echo "╔══════════════════════════════════════════════╗"
|
|
14
|
+
echo "║ Argo — mlx-audio setup (Apple Silicon) ║"
|
|
15
|
+
echo "╚══════════════════════════════════════════════╝"
|
|
16
|
+
echo ""
|
|
17
|
+
|
|
18
|
+
# Check for uv
|
|
19
|
+
if ! command -v uv &>/dev/null; then
|
|
20
|
+
echo "✗ uv not found. Install it: curl -LsSf https://astral.sh/uv/install.sh | sh"
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# Check for Apple Silicon
|
|
25
|
+
if [[ "$(uname -m)" != "arm64" ]]; then
|
|
26
|
+
echo "! Warning: mlx-audio is optimized for Apple Silicon (arm64)."
|
|
27
|
+
echo " Current arch: $(uname -m). Performance may be poor."
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Create venv
|
|
31
|
+
echo "★ Creating venv at ${VENV_DIR}..."
|
|
32
|
+
uv venv "$VENV_DIR"
|
|
33
|
+
|
|
34
|
+
# Install mlx-audio + missing transitive deps
|
|
35
|
+
echo "★ Installing mlx-audio..."
|
|
36
|
+
uv pip install -p "$VENV_DIR" mlx-audio
|
|
37
|
+
|
|
38
|
+
echo "★ Installing missing transitive dependencies..."
|
|
39
|
+
uv pip install -p "$VENV_DIR" "misaki[en]" num2words pip
|
|
40
|
+
|
|
41
|
+
# setuptools < 70 needed for webrtcvad's pkg_resources import
|
|
42
|
+
echo "★ Installing setuptools (< 70 for pkg_resources compat)..."
|
|
43
|
+
uv pip install -p "$VENV_DIR" "setuptools<70"
|
|
44
|
+
|
|
45
|
+
# Server deps (for OpenAI-compatible HTTP API)
|
|
46
|
+
echo "★ Installing server dependencies..."
|
|
47
|
+
uv pip install -p "$VENV_DIR" uvicorn fastapi python-multipart webrtcvad
|
|
48
|
+
|
|
49
|
+
echo ""
|
|
50
|
+
echo "✓ Done! mlx-audio installed at ${VENV_DIR}"
|
|
51
|
+
echo ""
|
|
52
|
+
echo "Usage:"
|
|
53
|
+
echo " # Start the TTS server"
|
|
54
|
+
echo " ${VENV_DIR}/bin/python3 -m mlx_audio.server --port 8000"
|
|
55
|
+
echo ""
|
|
56
|
+
echo " # Record a voice reference clip"
|
|
57
|
+
echo " ./scripts/record-voice-ref.sh assets/ref-voice.wav"
|
|
58
|
+
echo ""
|
|
59
|
+
echo " # Preview cloned voice"
|
|
60
|
+
echo " ./scripts/voice-clone-preview.sh \\"
|
|
61
|
+
echo " --ref-audio assets/ref-voice.wav \\"
|
|
62
|
+
echo " --ref-text 'Your transcript here.' \\"
|
|
63
|
+
echo " --voiceover demos/showcase.voiceover.json --play"
|
|
64
|
+
echo ""
|
|
65
|
+
echo " # Use in argo config"
|
|
66
|
+
echo " engines.mlxAudio({ model: 'mlx-community/Qwen3-TTS-12Hz-0.6B-Base-bf16' })"
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Quick voice cloning preview — send voiceover text to mlx-audio server
|
|
4
|
+
# and get back individual clips + an optional joined clip.
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# ./scripts/voice-clone-preview.sh \
|
|
8
|
+
# --ref-audio ./assets/ref-voice.wav \
|
|
9
|
+
# --ref-text "Hi, my name is Shreyas. I build developer tools." \
|
|
10
|
+
# --voiceover demos/showcase.voiceover.json
|
|
11
|
+
#
|
|
12
|
+
# # Single line of text (no manifest):
|
|
13
|
+
# ./scripts/voice-clone-preview.sh \
|
|
14
|
+
# --ref-audio ./assets/ref-voice.wav \
|
|
15
|
+
# --ref-text "Hi, my name is Shreyas." \
|
|
16
|
+
# --text "Welcome to the demo."
|
|
17
|
+
#
|
|
18
|
+
# Options:
|
|
19
|
+
# --ref-audio PATH Reference voice WAV (required)
|
|
20
|
+
# --ref-text TEXT Transcript of reference audio (required)
|
|
21
|
+
# --voiceover PATH Voiceover JSON manifest (array of {scene, text, speed?, voice?})
|
|
22
|
+
# --text TEXT Single text to synthesize (alternative to --voiceover)
|
|
23
|
+
# --model ID Model ID (default: mlx-community/Qwen3-TTS-12Hz-0.6B-Base-bf16)
|
|
24
|
+
# --server URL Server URL (default: http://localhost:8000)
|
|
25
|
+
# --out-dir PATH Output directory (default: ./voice-preview)
|
|
26
|
+
# --join Also produce a single joined clip
|
|
27
|
+
# --play Play the output when done (requires ffplay)
|
|
28
|
+
# --voice NAME Default voice (default: af_heart)
|
|
29
|
+
#
|
|
30
|
+
set -euo pipefail
|
|
31
|
+
|
|
32
|
+
# Defaults
|
|
33
|
+
MODEL="mlx-community/Qwen3-TTS-12Hz-0.6B-Base-bf16"
|
|
34
|
+
SERVER="http://localhost:8000"
|
|
35
|
+
OUT_DIR="./voice-preview"
|
|
36
|
+
REF_AUDIO=""
|
|
37
|
+
REF_TEXT=""
|
|
38
|
+
VOICEOVER=""
|
|
39
|
+
SINGLE_TEXT=""
|
|
40
|
+
JOIN=false
|
|
41
|
+
PLAY=false
|
|
42
|
+
VOICE="af_heart"
|
|
43
|
+
|
|
44
|
+
while [[ $# -gt 0 ]]; do
|
|
45
|
+
case "$1" in
|
|
46
|
+
--ref-audio) REF_AUDIO="$2"; shift 2;;
|
|
47
|
+
--ref-text) REF_TEXT="$2"; shift 2;;
|
|
48
|
+
--voiceover) VOICEOVER="$2"; shift 2;;
|
|
49
|
+
--text) SINGLE_TEXT="$2"; shift 2;;
|
|
50
|
+
--model) MODEL="$2"; shift 2;;
|
|
51
|
+
--server) SERVER="$2"; shift 2;;
|
|
52
|
+
--out-dir) OUT_DIR="$2"; shift 2;;
|
|
53
|
+
--voice) VOICE="$2"; shift 2;;
|
|
54
|
+
--join) JOIN=true; shift;;
|
|
55
|
+
--play) PLAY=true; shift;;
|
|
56
|
+
-h|--help)
|
|
57
|
+
sed -n '2,/^set /{ /^#/s/^# \?//p }' "$0"
|
|
58
|
+
exit 0;;
|
|
59
|
+
*) echo "Unknown option: $1"; exit 1;;
|
|
60
|
+
esac
|
|
61
|
+
done
|
|
62
|
+
|
|
63
|
+
if [[ -z "$REF_AUDIO" || -z "$REF_TEXT" ]]; then
|
|
64
|
+
echo "Error: --ref-audio and --ref-text are required."
|
|
65
|
+
exit 1
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
if [[ -z "$VOICEOVER" && -z "$SINGLE_TEXT" ]]; then
|
|
69
|
+
echo "Error: provide --voiceover <manifest.json> or --text <string>."
|
|
70
|
+
exit 1
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# Check server is running
|
|
74
|
+
if ! curl -sf "$SERVER/v1/audio/speech" -o /dev/null -X POST \
|
|
75
|
+
-H "Content-Type: application/json" \
|
|
76
|
+
-d '{"model":"test","input":"test"}' 2>/dev/null; then
|
|
77
|
+
# It's OK if the request fails with a model error — server is up
|
|
78
|
+
if ! curl -sf --connect-timeout 3 "$SERVER" -o /dev/null 2>/dev/null && \
|
|
79
|
+
! curl -sf --connect-timeout 3 "$SERVER/docs" -o /dev/null 2>/dev/null; then
|
|
80
|
+
echo "Warning: mlx-audio server may not be running at $SERVER"
|
|
81
|
+
echo "Start it with: python3 -m mlx_audio.server --model $MODEL"
|
|
82
|
+
echo ""
|
|
83
|
+
fi
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
mkdir -p "$OUT_DIR"
|
|
87
|
+
|
|
88
|
+
# Build the list of clips to generate
|
|
89
|
+
# Format: index|scene_name|text|speed|voice
|
|
90
|
+
CLIPS_LIST=$(mktemp)
|
|
91
|
+
trap 'rm -f "$CLIPS_LIST"' EXIT
|
|
92
|
+
|
|
93
|
+
if [[ -n "$SINGLE_TEXT" ]]; then
|
|
94
|
+
echo "0|single|$SINGLE_TEXT|1.0|$VOICE" > "$CLIPS_LIST"
|
|
95
|
+
else
|
|
96
|
+
# Parse voiceover JSON with python (available on macOS)
|
|
97
|
+
python3 -c "
|
|
98
|
+
import json, sys
|
|
99
|
+
with open('$VOICEOVER') as f:
|
|
100
|
+
scenes = json.load(f)
|
|
101
|
+
for i, s in enumerate(scenes):
|
|
102
|
+
scene = s.get('scene', f'scene-{i}')
|
|
103
|
+
text = s['text'].replace('|', ' ')
|
|
104
|
+
speed = s.get('speed', 1.0)
|
|
105
|
+
voice = s.get('voice', '$VOICE')
|
|
106
|
+
print(f'{i}|{scene}|{text}|{speed}|{voice}')
|
|
107
|
+
" > "$CLIPS_LIST"
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
TOTAL=$(wc -l < "$CLIPS_LIST" | tr -d ' ')
|
|
111
|
+
echo "Generating $TOTAL clip(s) via $SERVER"
|
|
112
|
+
echo "Model: $MODEL"
|
|
113
|
+
echo "Ref audio: $REF_AUDIO"
|
|
114
|
+
echo "Output: $OUT_DIR/"
|
|
115
|
+
echo ""
|
|
116
|
+
|
|
117
|
+
GENERATED_FILES=()
|
|
118
|
+
IDX=0
|
|
119
|
+
|
|
120
|
+
while IFS='|' read -r _ SCENE TEXT SPEED CLIP_VOICE; do
|
|
121
|
+
IDX=$((IDX + 1))
|
|
122
|
+
OUTFILE="$OUT_DIR/$(printf '%02d' "$IDX")-${SCENE}.wav"
|
|
123
|
+
|
|
124
|
+
printf " [%d/%d] %s ... " "$IDX" "$TOTAL" "$SCENE"
|
|
125
|
+
|
|
126
|
+
# Build JSON payload
|
|
127
|
+
PAYLOAD=$(python3 -c "
|
|
128
|
+
import json
|
|
129
|
+
p = {
|
|
130
|
+
'model': '$MODEL',
|
|
131
|
+
'input': $(python3 -c "import json; print(json.dumps('$TEXT'))"),
|
|
132
|
+
'voice': '$CLIP_VOICE',
|
|
133
|
+
'speed': $SPEED,
|
|
134
|
+
'ref_audio': '$REF_AUDIO',
|
|
135
|
+
'ref_text': $(python3 -c "import json; print(json.dumps('$REF_TEXT'))"),
|
|
136
|
+
}
|
|
137
|
+
print(json.dumps(p))
|
|
138
|
+
")
|
|
139
|
+
|
|
140
|
+
HTTP_CODE=$(curl -sf -w '%{http_code}' -o "$OUTFILE.raw" \
|
|
141
|
+
-X POST "$SERVER/v1/audio/speech" \
|
|
142
|
+
-H "Content-Type: application/json" \
|
|
143
|
+
-d "$PAYLOAD" 2>/dev/null || echo "000")
|
|
144
|
+
|
|
145
|
+
if [[ "$HTTP_CODE" == "200" ]]; then
|
|
146
|
+
# Convert to consistent WAV format
|
|
147
|
+
ffmpeg -y -i "$OUTFILE.raw" \
|
|
148
|
+
-ar 24000 -ac 1 -acodec pcm_s16le \
|
|
149
|
+
"$OUTFILE" 2>/dev/null
|
|
150
|
+
rm -f "$OUTFILE.raw"
|
|
151
|
+
|
|
152
|
+
DURATION=$(ffprobe -v error -show_entries format=duration \
|
|
153
|
+
-of csv=p=0 "$OUTFILE" 2>/dev/null)
|
|
154
|
+
printf "done (%.1fs)\n" "$DURATION"
|
|
155
|
+
GENERATED_FILES+=("$OUTFILE")
|
|
156
|
+
else
|
|
157
|
+
rm -f "$OUTFILE.raw"
|
|
158
|
+
printf "FAILED (HTTP %s)\n" "$HTTP_CODE"
|
|
159
|
+
fi
|
|
160
|
+
done < "$CLIPS_LIST"
|
|
161
|
+
|
|
162
|
+
echo ""
|
|
163
|
+
|
|
164
|
+
# Join clips if requested
|
|
165
|
+
if $JOIN && [[ ${#GENERATED_FILES[@]} -gt 1 ]]; then
|
|
166
|
+
JOINED="$OUT_DIR/joined.wav"
|
|
167
|
+
CONCAT_LIST=$(mktemp)
|
|
168
|
+
for f in "${GENERATED_FILES[@]}"; do
|
|
169
|
+
echo "file '$(realpath "$f")'" >> "$CONCAT_LIST"
|
|
170
|
+
done
|
|
171
|
+
|
|
172
|
+
ffmpeg -y -f concat -safe 0 -i "$CONCAT_LIST" \
|
|
173
|
+
-ar 24000 -ac 1 -acodec pcm_s16le \
|
|
174
|
+
"$JOINED" 2>/dev/null
|
|
175
|
+
rm -f "$CONCAT_LIST"
|
|
176
|
+
|
|
177
|
+
TOTAL_DURATION=$(ffprobe -v error -show_entries format=duration \
|
|
178
|
+
-of csv=p=0 "$JOINED" 2>/dev/null)
|
|
179
|
+
echo "Joined clip: $JOINED (${TOTAL_DURATION}s)"
|
|
180
|
+
echo ""
|
|
181
|
+
|
|
182
|
+
if $PLAY; then
|
|
183
|
+
echo "Playing joined clip..."
|
|
184
|
+
ffplay -autoexit -nodisp "$JOINED" 2>/dev/null
|
|
185
|
+
fi
|
|
186
|
+
elif $PLAY && [[ ${#GENERATED_FILES[@]} -gt 0 ]]; then
|
|
187
|
+
LAST_IDX=$(( ${#GENERATED_FILES[@]} - 1 ))
|
|
188
|
+
PLAY_FILE="${GENERATED_FILES[$LAST_IDX]}"
|
|
189
|
+
echo "Playing: $PLAY_FILE"
|
|
190
|
+
ffplay -autoexit -nodisp "$PLAY_FILE" 2>/dev/null
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
echo "Done! Clips saved to $OUT_DIR/"
|