This commit is contained in:
Andras Schmelczer 2026-06-28 11:59:44 +01:00
parent f1601257c7
commit c2070693fb
68 changed files with 2305 additions and 212 deletions

View file

@ -8,7 +8,7 @@ import { storyboards } from './storyboard.js';
* Emit per-storyboard narration scripts for the synth step.
*
* Synth (tts/synth.py) runs BEFORE recording, so it needs the full ordered
* narration list text + per-cue gaps + voice config without depending
* narration list (text + per-cue gaps + voice config) without depending
* on Playwright, the dashboard, or auth. Walk each storyboard's cues, write
* a flat manifest under `output/<name>/narration-script.json`, then write
* an index manifest at `output/storyboards.json` so render.sh knows which
@ -18,11 +18,11 @@ import { storyboards } from './storyboard.js';
* matches storyboard cues to measured durations by index.
*/
// Em/en-dashes and ellipses make Qwen3-TTS produce dramatic pauses, sighs,
// or audible breaths — the captions still render the original (unicode-rich)
// or audible breaths. The captions still render the original (unicode-rich)
// text from the storyboard; only the synth input is sanitised.
function normalizeForTts(text: string): string {
return text
.replace(/\s*[—–]\s*/g, ', ')
.replace(/\s*[\u2014\u2013]\s*/g, ', ')
.replace(/…/g, '.')
.replace(/\.{3,}/g, '.')
.replace(/\s{2,}/g, ' ')
@ -39,7 +39,7 @@ function emitScript(storyboard: Storyboard): string {
gapBeforeMs: cue.gapBeforeMs,
}));
// The voice block is consumed by tts/synth.py — see _resolve_reference and
// The voice block is consumed by tts/synth.py. See _resolve_reference and
// the cache check there for which fields invalidate cached audio.
const manifest = {
storyboard: storyboard.name,
@ -61,7 +61,7 @@ function emitScript(storyboard: Storyboard): string {
/**
* Validate every stubbed/initial filter name and travel-destination slug
* against the LIVE API. Wrong names don't error in the app they silently
* against the LIVE API. Wrong names don't error in the app: they silently
* no-op, the map never changes, and you only find out after a full render.
* Fails hard on a mismatch; soft-warns if the API is unreachable (render.sh
* has already health-checked it by the time preflight runs).
@ -69,7 +69,7 @@ function emitScript(storyboard: Storyboard): string {
async function validateAgainstLiveApi(): Promise<void> {
const apiBase = process.env.API_URL ?? process.env.APP_URL;
if (!apiBase) {
console.warn('[preflight] no API_URL/APP_URL set skipping live filter validation');
console.warn('[preflight] no API_URL/APP_URL set, skipping live filter validation');
return;
}
@ -82,7 +82,7 @@ async function validateAgainstLiveApi(): Promise<void> {
};
featureNames = new Set(body.groups.flatMap((g) => g.features.map((f) => f.name)));
} catch (err) {
console.warn(`[preflight] could not fetch ${apiBase}/api/features (${err}) skipping validation`);
console.warn(`[preflight] could not fetch ${apiBase}/api/features (${err}), skipping validation`);
return;
}
@ -134,7 +134,7 @@ async function main(): Promise<void> {
for (const sb of storyboards) emitScript(sb);
// Index for shell loops each entry has every field render.sh needs to
// Index for shell loops: each entry has every field render.sh needs to
// address per-storyboard outputs without re-parsing the TS source.
const index = {
storyboards: storyboards.map((sb) => ({