← back to Rentv Promos
recap-v2/scripts/tts.mjs
33 lines
#!/usr/bin/env node
/* Parse SCRIPT.md blocks → ElevenLabs (Steve clone) via clone-voice say.sh →
media/vo/NN-name.wav + media/vo/durations.json */
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
import { execFileSync } from 'node:child_process';
const ROOT = new URL('..', import.meta.url).pathname;
const OUT = ROOT + 'media/vo/';
mkdirSync(OUT, { recursive: true });
const md = readFileSync(ROOT + 'SCRIPT.md', 'utf8');
const blocks = [...md.matchAll(/^## (\d\d-[a-z]+)[^\n]*\n\n([\s\S]*?)(?=\n## |\n*$)/gm)]
.map(m => [m[1], m[2].trim().replace(/\s+/g, ' ')]);
const SAY = process.env.HOME + '/.claude/skills/clone-voice/scripts/say.sh';
const durations = {};
let chars = 0;
for (const [id, text] of blocks) {
chars += text.length;
const wav = OUT + id + '.wav';
execFileSync('bash', [SAY, text, wav, 'elevenlabs'], {
stdio: ['ignore', 'inherit', 'inherit'],
env: { ...process.env, PATH: '/opt/homebrew/bin:' + process.env.PATH },
});
const d = parseFloat(execFileSync('/opt/homebrew/bin/ffprobe',
['-v', 'error', '-show_entries', 'format=duration', '-of', 'csv=p=0', wav]).toString());
durations[id] = Math.round(d * 100) / 100;
console.log(id, durations[id] + 's', text.length + 'ch');
}
writeFileSync(OUT + 'durations.json', JSON.stringify(durations, null, 2));
const total = Object.values(durations).reduce((a, b) => a + b, 0);
console.log('TOTAL', Math.round(total) + 's', chars + ' chars');