@@ -40,7 +40,7 @@ import {
4040 loadNarrationCache ,
4141 narrationCacheKey ,
4242 VOICE_SETTING_KEYS as _SHARED_VOICE_KEYS
43- } from '../../lib /narration-parser.js' ;
43+ } from './narration-parser.js' ;
4444
4545const __filename = fileURLToPath ( import . meta. url ) ;
4646const __dirname = path . dirname ( __filename ) ;
@@ -65,6 +65,10 @@ const VERBOSE = args.includes('--verbose') || args.includes('-v');
6565const SLIDE_FILTER = args . includes ( '--slide' ) ? args [ args . indexOf ( '--slide' ) + 1 ] : null ;
6666const SHOW_PROVIDERS = args . includes ( '--providers' ) || args . includes ( '--provider' ) ;
6767const SHOW_HELP = args . includes ( '--help' ) || args . includes ( '-h' ) ;
68+ // Rebuild .narration-cache.json from current slide text without calling any
69+ // TTS provider. Useful when audio is committed but the cache is missing
70+ // (e.g. after a fresh clone or migrating a project).
71+ const REBUILD_CACHE = args . includes ( '--rebuild-cache' ) ;
6872
6973/**
7074 * Load environment variables from .env file
@@ -224,7 +228,7 @@ function parseSlideNarration(filePath, baseName) {
224228}
225229
226230// Removed: original inline parseSlideNarration / parseNarrationObject implementations
227- // now live in lib /narration-parser.js so the build linter can reuse them.
231+ // now live in framework/scripts /narration-parser.js so the build linter can reuse them.
228232
229233/**
230234 * Main execution
@@ -244,11 +248,12 @@ async function main() {
244248
245249Usage:
246250 npm run narration Generate all changed narration
247- npm run narration -- --force Regenerate all (ignore cache)
248- npm run narration -- --dry-run Preview without generating
249- npm run narration -- --slide <id> Generate specific slide only
250- npm run narration -- --providers List available TTS providers
251- npm run narration -- --verbose Show detailed output
251+ npm run narration -- --force Regenerate all (ignore cache)
252+ npm run narration -- --dry-run Preview without generating
253+ npm run narration -- --slide <id> Generate specific slide only
254+ npm run narration -- --rebuild-cache Rebuild cache from existing audio (no TTS calls)
255+ npm run narration -- --providers List available TTS providers
256+ npm run narration -- --verbose Show detailed output
252257
253258Provider Selection:
254259 Set TTS_PROVIDER env var to: elevenlabs, openai, or azure
@@ -266,17 +271,22 @@ Examples:
266271 // Load environment variables
267272 loadEnv ( ) ;
268273
269- // Initialize TTS provider
274+ // Initialize TTS provider — skipped in --rebuild-cache mode since we
275+ // never call the provider when only refreshing the cache file.
270276 let provider ;
271- try {
272- provider = getActiveProvider ( ) ;
273- provider . validateConfig ( ) ;
274- const defaultVoice = provider . getDefaultVoiceId ( ) ;
275- console . log ( `🔊 Using TTS provider: ${ provider . getName ( ) } (voice: ${ defaultVoice } )\n` ) ;
276- } catch ( error ) {
277- console . error ( `❌ Provider error: ${ error . message } \n` ) ;
278- printProviderHelp ( ) ;
279- process . exit ( 1 ) ;
277+ if ( REBUILD_CACHE ) {
278+ console . log ( '🔁 Rebuild-cache mode: hashing existing audio, no TTS calls.\n' ) ;
279+ } else {
280+ try {
281+ provider = getActiveProvider ( ) ;
282+ provider . validateConfig ( ) ;
283+ const defaultVoice = provider . getDefaultVoiceId ( ) ;
284+ console . log ( `🔊 Using TTS provider: ${ provider . getName ( ) } (voice: ${ defaultVoice } )\n` ) ;
285+ } catch ( error ) {
286+ console . error ( `❌ Provider error: ${ error . message } \n` ) ;
287+ printProviderHelp ( ) ;
288+ process . exit ( 1 ) ;
289+ }
280290 }
281291
282292 // Load course config
@@ -375,7 +385,24 @@ Examples:
375385 const cacheKey = key === 'slide' ? source . src : `${ source . src } #${ key } ` ;
376386 const cachedHash = cache [ cacheKey ] ;
377387 const outputExists = fs . existsSync ( outputPath ) ;
378-
388+
389+ // --rebuild-cache: hash text+settings for existing audio and move on.
390+ // Never call TTS, never write or delete audio files.
391+ if ( REBUILD_CACHE ) {
392+ if ( outputExists ) {
393+ newCache [ cacheKey ] = contentHash ;
394+ skipped ++ ;
395+ if ( VERBOSE ) {
396+ const label = key === 'slide' ? source . slideId : `${ source . slideId } #${ key } ` ;
397+ console . log ( ` ✅ ${ label } : cached (${ path . relative ( ROOT_DIR , outputPath ) } )` ) ;
398+ }
399+ } else if ( VERBOSE ) {
400+ const label = key === 'slide' ? source . slideId : `${ source . slideId } #${ key } ` ;
401+ console . log ( ` ⏭️ ${ label } : no audio yet, skipping` ) ;
402+ }
403+ continue ;
404+ }
405+
379406 if ( cachedHash === contentHash && outputExists && ! FORCE_REGENERATE ) {
380407 if ( VERBOSE ) {
381408 const label = key === 'slide' ? source . slideId : `${ source . slideId } #${ key } ` ;
0 commit comments