youtube/remotion/werkzeuge/musik.mts
Lara 69990b6c0f Stand 03.11.2026: Komponist, sechs Lieder-Entwürfe, Suno-Stimme und Auswahl, Remotion-Arbeiten
- Neuer Mitarbeiter Komponist (.claude/agents/komponist.md)
- Liederbuch: Plan + Lieder 1, 2, 3, 4, 5, 12 (drehbuecher/lieder/)
- Suno: feste Stimme aus dem Favoriten, Stilvorlage, ausgewählte Fassungen (suno-auswahl.md)
- Suno-Lizenzantwort für Noa in der Mitte (wissen/)
- Werkzeuge für Melodie-Skizzen und Leseseite
- Laufende Remotion-, Stimmen- und Figurenarbeiten des Tages

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018MyLus898ZL9jjJJDvvxa8
2026-11-03 21:29:39 +01:00

61 lines
2.7 KiB
TypeScript

// 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/<name>.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<string, {sekunden: number; beschreibung: string}>;
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<string, string> = 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}`);
}