1e51ef7def
- 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>
119 lines
5.3 KiB
PHP
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";
|