33/**
44 * Podcast Audio Synthesis Script
55 *
6- * Converts saved podcast scripts (markdown files) to audio using Gemini TTS:
7- * 1. Scans scripts/output/podcasts/ for markdown script files
8- * 2. Parses script dialog and metadata
9- * 3. Synthesizes audio using Gemini 2.5 Flash TTS with multi-speaker config
10- * 4. Saves WAV files to website/static/audio/
11- * 5. Updates manifest mapping docs to audio URLs
6+ * Converts a single podcast script (markdown file) to audio using Gemini TTS:
7+ * 1. Displays available podcast scripts from scripts/output/podcasts/
8+ * 2. Prompts user to select a specific podcast
9+ * 3. Parses script dialog and metadata
10+ * 4. Synthesizes audio using Gemini 2.5 Flash TTS with multi-speaker config
11+ * 5. Saves WAV file to website/static/audio/
12+ * 6. Updates manifest mapping docs to audio URLs
13+ *
14+ * Usage: node generate-podcast-audio.js
1215 */
1316
1417import { GoogleGenerativeAI } from '@google/generative-ai' ;
1518import { readFileSync , writeFileSync , mkdirSync , readdirSync , statSync , existsSync } from 'fs' ;
1619import { join , relative , dirname , basename , extname } from 'path' ;
1720import { fileURLToPath } from 'url' ;
21+ import * as readline from 'readline' ;
1822
1923// ES module __dirname equivalent
2024const __filename = fileURLToPath ( import . meta. url ) ;
@@ -432,37 +436,42 @@ async function processScript(scriptPath, manifest) {
432436
433437 } catch ( error ) {
434438 console . error ( ` ❌ Error: ${ error . message } ` ) ;
435- console . error ( ` Skipping this file and continuing...` ) ;
439+ throw error ;
436440 }
437441}
438442
439443/**
440- * Process files with concurrency limit
444+ * Interactive prompt to select a podcast script from available options
441445 */
442- async function processFilesWithConcurrency ( files , manifest , concurrency = 3 ) {
443- const results = { processed : 0 , failed : 0 } ;
444-
445- // Process files in batches
446- for ( let i = 0 ; i < files . length ; i += concurrency ) {
447- const batch = files . slice ( i , i + concurrency ) ;
448-
449- console . log ( `\n🔄 Processing batch ${ Math . floor ( i / concurrency ) + 1 } /${ Math . ceil ( files . length / concurrency ) } (${ batch . length } files concurrently)...` ) ;
450-
451- // Process batch concurrently
452- await Promise . all (
453- batch . map ( async ( file ) => {
454- try {
455- await processScript ( file , manifest ) ;
456- results . processed ++ ;
457- } catch ( error ) {
458- console . error ( `\n❌ Failed to process ${ file } :` , error . message ) ;
459- results . failed ++ ;
460- }
461- } )
462- ) ;
463- }
446+ async function promptSelectPodcast ( files ) {
447+ return new Promise ( ( resolve , reject ) => {
448+ const rl = readline . createInterface ( {
449+ input : process . stdin ,
450+ output : process . stdout
451+ } ) ;
452+
453+ console . log ( '\n📚 Available podcast scripts:\n' ) ;
454+
455+ files . forEach ( ( file , index ) => {
456+ const relativePath = relative ( SCRIPT_INPUT_DIR , file ) ;
457+ console . log ( ` ${ index + 1 } . ${ relativePath } ` ) ;
458+ } ) ;
464459
465- return results ;
460+ console . log ( '\n' ) ;
461+
462+ rl . question ( 'Select a podcast by number (or press Ctrl+C to exit): ' , ( answer ) => {
463+ rl . close ( ) ;
464+
465+ const selection = parseInt ( answer , 10 ) ;
466+
467+ if ( isNaN ( selection ) || selection < 1 || selection > files . length ) {
468+ reject ( new Error ( `Invalid selection: ${ answer } . Please enter a number between 1 and ${ files . length } .` ) ) ;
469+ return ;
470+ }
471+
472+ resolve ( files [ selection - 1 ] ) ;
473+ } ) ;
474+ } ) ;
466475}
467476
468477/**
@@ -482,30 +491,45 @@ async function main() {
482491 process . exit ( 1 ) ;
483492 }
484493
485- console . log ( `\n📚 Found ${ files . length } script files\n ` ) ;
494+ console . log ( `\n📚 Found ${ files . length } script file ${ files . length !== 1 ? 's' : '' } ` ) ;
486495
487496 // Load existing manifest or create new
488497 let manifest = { } ;
489498 if ( existsSync ( AUDIO_MANIFEST_PATH ) ) {
490499 manifest = JSON . parse ( readFileSync ( AUDIO_MANIFEST_PATH , 'utf-8' ) ) ;
491- console . log ( `📋 Loaded existing manifest with ${ Object . keys ( manifest ) . length } entries\n ` ) ;
500+ console . log ( `📋 Loaded existing manifest with ${ Object . keys ( manifest ) . length } entries` ) ;
492501 }
493502
494- // Process files with concurrency limit of 3
495- const results = await processFilesWithConcurrency ( files , manifest , 3 ) ;
496-
497- // Save manifest
498- mkdirSync ( dirname ( AUDIO_MANIFEST_PATH ) , { recursive : true } ) ;
499- writeFileSync ( AUDIO_MANIFEST_PATH , JSON . stringify ( manifest , null , 2 ) ) ;
503+ // Prompt user to select a podcast
504+ let selectedFile ;
505+ try {
506+ selectedFile = await promptSelectPodcast ( files ) ;
507+ } catch ( error ) {
508+ console . error ( `\n❌ ${ error . message } ` ) ;
509+ process . exit ( 1 ) ;
510+ }
500511
501- console . log ( '\n' + '=' . repeat ( 60 ) ) ;
502- console . log ( '✨ Audio generation complete!\n' ) ;
503- console . log ( `📊 Summary:` ) ;
504- console . log ( ` ✅ Processed: ${ results . processed } ` ) ;
505- console . log ( ` ❌ Failed: ${ results . failed } ` ) ;
506- console . log ( ` 📁 Total files: ${ files . length } ` ) ;
507- console . log ( `\n📋 Manifest saved to: ${ AUDIO_MANIFEST_PATH } ` ) ;
512+ const relativePath = relative ( SCRIPT_INPUT_DIR , selectedFile ) ;
513+ console . log ( `\n✅ Selected: ${ relativePath } \n` ) ;
508514 console . log ( '=' . repeat ( 60 ) ) ;
515+
516+ // Process the selected script
517+ try {
518+ await processScript ( selectedFile , manifest ) ;
519+
520+ // Save manifest
521+ mkdirSync ( dirname ( AUDIO_MANIFEST_PATH ) , { recursive : true } ) ;
522+ writeFileSync ( AUDIO_MANIFEST_PATH , JSON . stringify ( manifest , null , 2 ) + '\n' ) ;
523+
524+ console . log ( '\n' + '=' . repeat ( 60 ) ) ;
525+ console . log ( '✨ Audio generation complete!' ) ;
526+ console . log ( `📋 Manifest updated: ${ AUDIO_MANIFEST_PATH } ` ) ;
527+ console . log ( '=' . repeat ( 60 ) ) ;
528+
529+ } catch ( error ) {
530+ console . error ( `\n❌ Failed to process podcast: ${ error . message } ` ) ;
531+ process . exit ( 1 ) ;
532+ }
509533}
510534
511535// Run
0 commit comments