Files
geograsim/App/sims/farmer/scripts/generate-sounds.php
T
Adminator 1e51ef7def Nachtrag: alle bisher untracked Ordner + hängende Änderungen mit-committen
- Konzept/, didaktik_geografie/, didaktik_simulation/, v2-modules/, v2-platform/
- 12 code-workspace-Files
- STATUS-*.md
- viele M/D/R-Änderungen an bereits getrackten Files
- .gitignore verstärkt: **/.humaninput/, **/secret_keys.txt

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-08 02:27:02 +02:00

119 lines
5.3 KiB
PHP

<?php
/**
* Farmer — Sound-Effekt-Generator via ElevenLabs Sound Generation API.
*
* Liest Sound-Liste, ruft API für jeden Sound auf, speichert MP3 unter
* App/sims/farmer/assets/sounds/sfx/<name>.mp3. Idempotent — überspringt
* existierende Dateien.
*
* Aufruf: php scripts/generate-sounds.php [--force] [--only=name1,name2]
*/
$ENV_FILE = __DIR__ . '/../../../.env.local';
if (!file_exists($ENV_FILE)) { fwrite(STDERR, "Missing $ENV_FILE\n"); exit(1); }
// Manuelles Parsen — parse_ini_file scheitert an dt. Sonderzeichen in Kommentaren.
$KEY = null;
foreach (file($ENV_FILE, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
$line = trim($line);
if ($line === '' || $line[0] === '#') continue;
if (strpos($line, '=') === false) continue;
[$k, $v] = explode('=', $line, 2);
if (trim($k) === 'ELEVENLABS_API_KEY') { $KEY = trim($v, " \"'"); break; }
}
if (!$KEY) { fwrite(STDERR, "ELEVENLABS_API_KEY nicht in .env.local\n"); exit(1); }
$OUT_DIR = __DIR__ . '/../assets/sounds/sfx';
if (!is_dir($OUT_DIR)) mkdir($OUT_DIR, 0755, true);
$args = array_slice($argv, 1);
$force = in_array('--force', $args);
$only = null;
foreach ($args as $a) {
if (strpos($a, '--only=') === 0) {
$only = explode(',', substr($a, 7));
}
}
$SOUNDS = [
// Aktions-Sounds (15)
'harvest_start' => ['Distant farm bell ringing twice, calling workers to harvest, warm ambient countryside', 1.2],
'report_open' => ['Soft paper page turning, gentle crisp sound', 0.5],
'report_next' => ['Soft single tap on wooden table, very brief', 0.5],
'vehicle_truck' => ['Heavy diesel truck engine starting and driving away, departing motor', 1.5],
'vehicle_ship' => ['Cargo ship horn, deep low blast, single foghorn at sea', 2.0],
'vehicle_plane' => ['Cargo plane taking off, jet engine spinning up and lifting', 2.0],
'vehicle_arrive' => ['Cash register cha-ching sound, coins falling into drawer, brief and bright', 0.8],
'disease_outbreak' => ['Ominous low drone with subtle crackling and whispering wind, foreboding agricultural disaster', 1.5],
'monoculture_warn' => ['Subtle dry rustling of leaves, gentle warning tone', 0.8],
'year_advance' => ['Soft mechanical calendar page flipping with gentle clock chime', 1.0],
'unlock_crop' => ['Bright magical chime, discovery sound, hopeful and brief', 1.0],
'negative_balance' => ['Sad descending two-note tone, very subtle, regretful', 0.7],
'end_report_open' => ['Triumphant ascending chime, festive but warm, harvest celebration', 1.5],
'reset' => ['Soft swoosh, gentle reset, brief and clean', 0.5],
// Per-Crop Sounds (10) — beim Pflanzen abgespielt statt generischem 'plant'
'crop_wheat' => ['Soft rustling of wheat stalks in a gentle summer breeze, peaceful golden field', 0.8],
'crop_banana' => ['Tropical banana leaves rustling with distant exotic bird calls, jungle ambience', 0.8],
'crop_reindeer' => ['A single reindeer grunting softly with light antler clicking, cold arctic air', 0.9],
'crop_camel' => ['A single camel bellowing and grunting once, dry desert ambience', 1.0],
'crop_rice' => ['Water gently splashing in a flooded rice paddy, peaceful Asian field with crickets', 0.9],
'crop_coffee' => ['Soft tropical highland breeze with light leaves rustling and distant insects', 0.8],
'crop_dates' => ['Dry palm fronds rustling in a warm desert oasis breeze', 0.8],
'crop_corn' => ['Tall corn stalks rustling in a strong summer wind, big open field', 0.8],
'crop_fish' => ['Single fish splash, water surface ripple, brief and clean', 0.6],
'crop_olive' => ['Mediterranean olive grove with leaves rustling and distant cicadas, warm dry day', 0.9],
];
$generated = 0;
$skipped = 0;
$failed = [];
foreach ($SOUNDS as $name => [$prompt, $duration]) {
if ($only !== null && !in_array($name, $only)) continue;
$path = "$OUT_DIR/$name.mp3";
if (file_exists($path) && !$force) {
echo " [skip] $name.mp3 existiert\n";
$skipped++;
continue;
}
echo " [generate] $name.mp3 ($duration s) ... ";
$ch = curl_init('https://api.elevenlabs.io/v1/sound-generation');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'xi-api-key: ' . $KEY,
'Content-Type: application/json',
'Accept: audio/mpeg',
],
CURLOPT_POSTFIELDS => json_encode([
'text' => $prompt,
'duration_seconds' => $duration,
'prompt_influence' => 0.4,
]),
CURLOPT_TIMEOUT => 60,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$err = curl_error($ch);
curl_close($ch);
if ($status !== 200 || $err) {
echo "FAIL (HTTP $status: " . substr($body ?: $err, 0, 200) . ")\n";
$failed[] = $name;
} else {
file_put_contents($path, $body);
$size = round(filesize($path) / 1024, 1);
echo "OK ({$size} KB)\n";
$generated++;
}
// Rate-Limit-Pause
usleep(800000); // 0.8s zwischen Calls
}
echo "\n";
echo "Generiert: $generated · Übersprungen: $skipped · Fehlgeschlagen: " . count($failed) . "\n";
if (!empty($failed)) echo "Failed: " . implode(', ', $failed) . "\n";