All files / web/scripts generateAudioClips.ts

0% Statements 0/84
0% Branches 0/1
0% Functions 0/1
0% Lines 0/84

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85                                                                                                                                                                         
#!/usr/bin/env tsx

/**
 * Generate audio clips from the audio manifest using OpenAI's TTS API.
 *
 * Usage: OPENAI_API_KEY=sk-... npx tsx scripts/generateAudioClips.ts [--voice <voice>]
 *
 * Options:
 *   --voice <voice>  OpenAI TTS voice name (default: 'nova')
 *
 * Idempotent: skips clips that already exist in data/audio/{voice}/
 */

import { existsSync, mkdirSync, writeFileSync } from 'fs'
import { join } from 'path'
import OpenAI from 'openai'
import { AUDIO_MANIFEST } from '../src/lib/audio/audioManifest'

function parseArgs(): { voice: string } {
  const args = process.argv.slice(2)
  let voice = 'nova'

  for (let i = 0; i < args.length; i++) {
    if (args[i] === '--voice' && args[i + 1]) {
      voice = args[i + 1]
      i++
    }
  }

  return { voice }
}

async function main() {
  const { voice } = parseArgs()
  const apiKey = process.env.OPENAI_API_KEY
  if (!apiKey) {
    console.error('Error: OPENAI_API_KEY environment variable is required')
    process.exit(1)
  }

  const client = new OpenAI({ apiKey })

  const outputDir = join(__dirname, '..', 'data', 'audio', voice)
  mkdirSync(outputDir, { recursive: true })

  console.log(`Voice: ${voice}`)
  console.log(`Output: ${outputDir}`)

  let generated = 0
  let skipped = 0

  for (const clip of AUDIO_MANIFEST) {
    const outputPath = join(outputDir, clip.filename)

    if (existsSync(outputPath)) {
      skipped++
      continue
    }

    console.log(`Generating: ${clip.id} ("${clip.text}")`)

    try {
      const response = await client.audio.speech.create({
        model: 'tts-1',
        voice: voice as 'nova' | 'shimmer' | 'alloy' | 'echo' | 'fable' | 'onyx',
        input: clip.text,
        response_format: 'mp3',
      })

      const buffer = Buffer.from(await response.arrayBuffer())
      writeFileSync(outputPath, buffer)
      generated++
    } catch (error) {
      console.error(`Failed to generate ${clip.id}:`, error)
      process.exit(1)
    }
  }

  console.log(`\nDone! Generated: ${generated}, Skipped (existing): ${skipped}`)
  console.log(`Total clips in manifest: ${AUDIO_MANIFEST.length}`)
  console.log(`Output directory: ${outputDir}`)
}

main()