210 lines
7.0 KiB
JavaScript
210 lines
7.0 KiB
JavaScript
#!/usr/bin/env node
|
||
|
||
/**
|
||
* Generate MP3 audio files for all German questions using ElevenLabs TTS API.
|
||
*
|
||
* Usage:
|
||
* 1. Get your API key from https://elevenlabs.io/app/settings/api-keys
|
||
* 2. Run: ELEVENLABS_API_KEY=your-key-here node generate-audio.js
|
||
*
|
||
* Output: src/public/audio/<topic_id>_<index>.mp3
|
||
*
|
||
* Free tier: ~10,000 characters/month — enough for all 60 questions (~2,500 chars total).
|
||
*/
|
||
|
||
const fs = require("fs");
|
||
const path = require("path");
|
||
|
||
const API_KEY = process.env.ELEVENLABS_API_KEY;
|
||
if (!API_KEY) {
|
||
console.error("Error: set ELEVENLABS_API_KEY environment variable");
|
||
console.error(" ELEVENLABS_API_KEY=your-key node generate-audio.js");
|
||
process.exit(1);
|
||
}
|
||
|
||
// Same questions as in index.html
|
||
const TOPICS = [
|
||
{
|
||
id: "alltag",
|
||
questions: [
|
||
"Wie sieht dein typischer Morgen aus?",
|
||
"Was machst du normalerweise in deiner Freizeit?",
|
||
"Wie oft kochst du zu Hause und was bereitest du gerne zu?",
|
||
"Beschreibe deinen Tagesablauf an einem Werktag.",
|
||
"Welche Aufgaben im Haushalt magst du und welche nicht?",
|
||
"Wie verbringst du deinen Feierabend?",
|
||
"Machst du regelmäßig Sport? Was und wie oft?",
|
||
"Wie oft triffst du dich mit Freunden oder Familie?",
|
||
"Was liest du gerne – Bücher, Zeitungen oder Online-Artikel?",
|
||
"Wie entspannst du dich nach einem stressigen Tag?",
|
||
],
|
||
},
|
||
{
|
||
id: "arbeit",
|
||
questions: [
|
||
"Was machst du beruflich und was gefällt dir an deiner Arbeit?",
|
||
"Wie sieht dein Arbeitsplatz aus – Büro, Homeoffice oder beides?",
|
||
"Was sind die größten Herausforderungen in deinem Job?",
|
||
"Wie lange arbeitest du täglich und wie ist die Work-Life-Balance?",
|
||
"Hast du nette Kollegen? Wie ist das Arbeitsklima?",
|
||
"Welche Fähigkeiten brauchst du für deinen Beruf?",
|
||
"Was würdest du beruflich ändern, wenn du könntest?",
|
||
"Wie wichtig ist Karriere für dich im Vergleich zu Freizeit?",
|
||
"Hast du schon mal den Job gewechselt? Warum?",
|
||
"Was sind deine beruflichen Ziele für die Zukunft?",
|
||
],
|
||
},
|
||
{
|
||
id: "reisen",
|
||
questions: [
|
||
"Wohin bist du zuletzt gereist und wie war es?",
|
||
"Reist du lieber alleine oder mit anderen? Warum?",
|
||
"Was ist dein Traumreiseziel und warum?",
|
||
"Bevorzugst du Strand, Berge oder Städtereisen?",
|
||
"Wie planst du normalerweise deine Reisen?",
|
||
"Was nimmst du immer mit auf Reisen?",
|
||
"Hast du schon mal einen Kulturschock erlebt?",
|
||
"Welches Essen aus einem anderen Land hat dir besonders gefallen?",
|
||
"Wie wichtig ist es für dich, die Landessprache zu kennen?",
|
||
"Erzähl von einem unvergesslichen Reiseerlebnis.",
|
||
],
|
||
},
|
||
{
|
||
id: "schweiz",
|
||
questions: [
|
||
"Was magst du am Leben in der Schweiz besonders?",
|
||
"Wie unterscheidet sich das Schweizerdeutsch vom Hochdeutsch?",
|
||
"Was sind typisch schweizerische Gewohnheiten oder Traditionen?",
|
||
"Welche Sehenswürdigkeiten in Bern kennst du?",
|
||
"Was ist das Besondere am schweizerischen Bildungssystem?",
|
||
"Wie ist das öffentliche Verkehrsnetz in der Schweiz?",
|
||
"Was denkst du über die Mehrsprachigkeit in der Schweiz?",
|
||
"Welche Schweizer Gerichte kennst du und magst du?",
|
||
"Wie erleben Ausländer das Einleben in der Schweiz?",
|
||
"Was sind Vor- und Nachteile der direkten Demokratie?",
|
||
],
|
||
},
|
||
{
|
||
id: "gesundheit",
|
||
questions: [
|
||
"Wie wichtig ist dir deine Gesundheit im Alltag?",
|
||
"Treibst du Sport für die Gesundheit oder zum Spaß?",
|
||
"Wie ernährst du dich – bewusst oder eher spontan?",
|
||
"Wie gehst du mit Stress um?",
|
||
"Schläfst du gut? Was machst du für besseren Schlaf?",
|
||
"Gehst du regelmäßig zum Arzt für Vorsorgeuntersuchungen?",
|
||
"Was meinst du – wie viel Einfluss hat man auf die eigene Gesundheit?",
|
||
"Wie beeinflusst das Wetter deine Stimmung und Gesundheit?",
|
||
"Hast du Allergien oder besondere gesundheitliche Themen?",
|
||
"Was ist für dich gesunde Ernährung?",
|
||
],
|
||
},
|
||
{
|
||
id: "meinungen",
|
||
questions: [
|
||
"Was denkst du über soziale Medien – Fluch oder Segen?",
|
||
"Glaubst du, dass Homeoffice die Produktivität steigert?",
|
||
"Was meinst du: Ist lebenslanges Lernen wichtig?",
|
||
"Wie stehst du zu vegetarischer oder veganer Ernährung?",
|
||
"Glaubst du, dass KI unsere Arbeitswelt stark verändern wird?",
|
||
"Was hältst du von öffentlichem Nahverkehr statt Autofahren?",
|
||
"Ist es wichtig, mehrere Sprachen zu sprechen? Warum?",
|
||
"Was denkst du über die vier-Tage-Arbeitswoche?",
|
||
"Sollten mehr Menschen ehrenamtlich arbeiten?",
|
||
"Wie wichtig ist lokales Einkaufen für die Umwelt?",
|
||
],
|
||
},
|
||
];
|
||
|
||
// ElevenLabs voice ID — "Daniel" (German male, clear, natural)
|
||
// Browse voices at https://elevenlabs.io/voice-library and replace if needed
|
||
const VOICE_ID = "onwK4e9ZLuTAKqWW03F9";
|
||
const MODEL_ID = "eleven_multilingual_v2";
|
||
|
||
const OUTPUT_DIR = path.join(__dirname, "src", "public", "audio");
|
||
|
||
async function generateAudio(text, outputPath) {
|
||
const response = await fetch(
|
||
`https://api.elevenlabs.io/v1/text-to-speech/${VOICE_ID}`,
|
||
{
|
||
method: "POST",
|
||
headers: {
|
||
"xi-api-key": API_KEY,
|
||
"Content-Type": "application/json",
|
||
Accept: "audio/mpeg",
|
||
},
|
||
body: JSON.stringify({
|
||
text,
|
||
model_id: MODEL_ID,
|
||
language_code: "de",
|
||
voice_settings: {
|
||
stability: 0.6,
|
||
similarity_boost: 0.8,
|
||
speed: 0.9,
|
||
},
|
||
}),
|
||
}
|
||
);
|
||
|
||
if (!response.ok) {
|
||
const error = await response.text();
|
||
throw new Error(`ElevenLabs API error ${response.status}: ${error}`);
|
||
}
|
||
|
||
const buffer = Buffer.from(await response.arrayBuffer());
|
||
fs.writeFileSync(outputPath, buffer);
|
||
}
|
||
|
||
async function main() {
|
||
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
||
|
||
let total = 0;
|
||
let generated = 0;
|
||
let skipped = 0;
|
||
|
||
for (const topic of TOPICS) {
|
||
total += topic.questions.length;
|
||
}
|
||
|
||
console.log(`Generating ${total} audio files...\n`);
|
||
|
||
for (const topic of TOPICS) {
|
||
for (let i = 0; i < topic.questions.length; i++) {
|
||
const filename = `${topic.id}_${i}.mp3`;
|
||
const outputPath = path.join(OUTPUT_DIR, filename);
|
||
|
||
// Skip if already exists (resume support)
|
||
if (fs.existsSync(outputPath)) {
|
||
skipped++;
|
||
console.log(` [skip] ${filename} (already exists)`);
|
||
continue;
|
||
}
|
||
|
||
const question = topic.questions[i];
|
||
process.stdout.write(
|
||
` [${generated + skipped + 1}/${total}] ${filename} ... `
|
||
);
|
||
|
||
try {
|
||
await generateAudio(question, outputPath);
|
||
generated++;
|
||
console.log("OK");
|
||
} catch (err) {
|
||
console.log(`FAILED: ${err.message}`);
|
||
console.log(" Stopping. Re-run to continue from where you left off.");
|
||
process.exit(1);
|
||
}
|
||
|
||
// Delay to respect rate limits
|
||
await new Promise((r) => setTimeout(r, 500));
|
||
}
|
||
}
|
||
|
||
console.log(
|
||
`\nDone! Generated: ${generated}, Skipped: ${skipped}, Total: ${total}`
|
||
);
|
||
console.log(`Files saved to: ${OUTPUT_DIR}`);
|
||
}
|
||
|
||
main();
|