Skip to content

Commit f5a8334

Browse files
ofriwclaude
andcommitted
Convert audio generator from batch to interactive single-file selection
- Added interactive prompt to select specific podcast from available scripts - Replaced concurrent batch processing with single-file generation - Improved error handling to exit on failure instead of continuing - Updated documentation to reflect single-file workflow - Added readline interface for user selection 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent cb89544 commit f5a8334

File tree

1 file changed

+70
-46
lines changed

1 file changed

+70
-46
lines changed

scripts/generate-podcast-audio.js

Lines changed: 70 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,22 @@
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

1417
import { GoogleGenerativeAI } from '@google/generative-ai';
1518
import { readFileSync, writeFileSync, mkdirSync, readdirSync, statSync, existsSync } from 'fs';
1619
import { join, relative, dirname, basename, extname } from 'path';
1720
import { fileURLToPath } from 'url';
21+
import * as readline from 'readline';
1822

1923
// ES module __dirname equivalent
2024
const __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

Comments
 (0)