// Erzeugt die Hintergrundmusik einer Folge mit ElevenLabs Music (instrumental, // sprachunabhängig). Die Stücke stehen in src/folgeNN/musik.json // ({name: {sekunden, beschreibung}}), die Dateien landen in // public/musik/folgeNN/.mp3 (nicht im Git). Nur fehlende oder geänderte // Stücke werden neu erzeugt; welche Szene welches Stück spielt, steht in FolgeNN.tsx. // // npx tsx werkzeuge/musik.mts F01 // … F01 --stueck lied nur ein Stück // … F01 --neu alle neu erzeugen // … F01 --probe nur anzeigen (kostet nichts) import fs from 'node:fs'; import path from 'node:path'; import {fileURLToPath} from 'node:url'; import {Readable} from 'node:stream'; import {ElevenLabsClient} from '@elevenlabs/elevenlabs-js'; const WURZEL = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); process.chdir(WURZEL); const envDatei = path.join(WURZEL, '..', '.env.local'); if (!process.env.ELEVENLABS_API_KEY && fs.existsSync(envDatei)) process.loadEnvFile(envDatei); const args = process.argv.slice(2); const kuerzel = args.find((a) => !a.startsWith('--')); const wert = (n: string) => (args.includes(n) ? args[args.indexOf(n) + 1] : undefined); const nurStueck = wert('--stueck'); const neu = args.includes('--neu'); const probe = args.includes('--probe'); if (!kuerzel) throw new Error('Folge angeben, z. B. F01'); const nummer = kuerzel.replace(/\D/g, ''); const stuecke = JSON.parse(fs.readFileSync(`src/folge${nummer}/musik.json`, 'utf8')) as Record; const ordner = `public/musik/folge${nummer}`; fs.mkdirSync(ordner, {recursive: true}); // Merkt sich, mit welcher Beschreibung ein Stück erzeugt wurde const merkDatei = path.join(ordner, 'erzeugt.json'); const erzeugt: Record = fs.existsSync(merkDatei) ? JSON.parse(fs.readFileSync(merkDatei, 'utf8')) : {}; const client = probe ? null : new ElevenLabsClient(); for (const [name, s] of Object.entries(stuecke)) { if (nurStueck && name !== nurStueck) continue; const pfad = path.join(ordner, `${name}.mp3`); const kennung = `${s.sekunden}|${s.beschreibung}`; if (!neu && erzeugt[name] === kennung && fs.existsSync(pfad)) continue; if (probe) { console.log(`~ ${name} (${s.sekunden} s): ${s.beschreibung}`); continue; } const audio = await client!.music.compose({ prompt: s.beschreibung, musicLengthMs: Math.round(s.sekunden * 1000), forceInstrumental: true, outputFormat: 'mp3_44100_128' as never, }); const teile: Buffer[] = []; for await (const t of Readable.fromWeb(audio as never)) teile.push(Buffer.from(t)); fs.writeFileSync(pfad, Buffer.concat(teile)); erzeugt[name] = kennung; fs.writeFileSync(merkDatei, JSON.stringify(erzeugt, null, '\t') + '\n'); console.log(`✓ ${name} → ${pfad}`); }