Files
geograsim/App/php/generate-audio-openai.php
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

115 lines
3.9 KiB
PHP

<?php
/**
* Batch-Audio-Generierung via OpenAI TTS (gpt-4o-mini-tts).
* Liest ein JSON-Textbuch aus App/sims/heli/scripts/<file>.json und
* erzeugt MP3s in App/sims/heli/sounds/radio/<id>.mp3
*
* Aufruf via Browser:
* http://localhost/geograsim/App/php/generate-audio-openai.php?file=tower-geo-lines.json
* http://localhost/geograsim/App/php/generate-audio-openai.php?file=tower-start-lines.json&voice=shimmer
* ?force=1 -> ueberschreibt bestehende
*/
set_time_limit(0);
ignore_user_abort(true);
header('Content-Type: text/plain; charset=utf-8');
$envFile = __DIR__ . '/../.env.local';
if (!file_exists($envFile)) { exit("ERROR: .env.local fehlt\n"); }
$OPENAI_KEY = '';
foreach (file($envFile) as $ln) {
$ln = trim($ln);
if ($ln === '' || $ln[0] === '#') continue;
if (preg_match('/^OPENAI_API_KEY\s*=\s*(.+)$/', $ln, $m)) {
$OPENAI_KEY = trim($m[1], " \t\"'");
break;
}
}
if (!$OPENAI_KEY) { exit("ERROR: OPENAI_API_KEY nicht in .env.local gefunden\n"); }
$file = $_GET['file'] ?? 'tower-geo-lines.json';
$voice = $_GET['voice'] ?? 'nova';
$model = $_GET['model'] ?? 'gpt-4o-mini-tts';
$force = !empty($_GET['force']);
$instr = "Speak warmly and patiently like an encouraging female flight controller guiding a young student co-pilot. Unhurried, clear, friendly German pronunciation. Gentle reassuring tone, never robotic.";
$jsonPath = __DIR__ . '/../sims/heli/scripts/' . basename($file);
if (!file_exists($jsonPath)) { exit("ERROR: JSON nicht gefunden: $jsonPath\n"); }
$data = json_decode(file_get_contents($jsonPath), true);
if (!$data || !isset($data['lines'])) { exit("ERROR: JSON ungültig\n"); }
$outDir = __DIR__ . '/../sims/heli/sounds/radio';
if (!is_dir($outDir)) mkdir($outDir, 0777, true);
echo "Modell: $model · Stimme: $voice\n";
echo "Quelle: $file\n";
echo "Zeilen: " . count($data['lines']) . "\n";
echo str_repeat('-', 60) . "\n";
@ob_flush(); @flush();
$ok = 0; $skip = 0; $fail = 0;
foreach ($data['lines'] as $line) {
$id = $line['id'] ?? null;
$text = $line['text'] ?? null;
if (!$id || !$text) continue;
$out = $outDir . '/' . $id . '.mp3';
if (file_exists($out) && !$force) {
$skip++;
echo "skip $id.mp3 (vorhanden)\n";
@ob_flush(); @flush();
continue;
}
$body = [
'model' => $model,
'voice' => $voice,
'input' => $text,
'response_format' => 'mp3',
'speed' => isset($_GET['speed']) ? floatval($_GET['speed']) : 1.12,
];
if (strpos($model, 'mini-tts') !== false) {
$body['instructions'] = $instr;
}
$ch = curl_init('https://api.openai.com/v1/audio/speech');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $OPENAI_KEY,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode($body, JSON_UNESCAPED_UNICODE),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 120,
]);
$resp = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$err = curl_error($ch);
curl_close($ch);
if ($http !== 200 || strlen($resp) < 1500) {
$fail++;
$snippet = substr($resp ?: $err, 0, 200);
echo "FAIL $id -> HTTP $http · $snippet\n";
@ob_flush(); @flush();
// bei Quota/Key-Problem abbrechen
if ($http === 401 || $http === 429 || $http === 403) {
echo "STOP (Auth- oder Quota-Problem)\n";
break;
}
continue;
}
file_put_contents($out, $resp);
$ok++;
echo "ok $id.mp3 (" . strlen($resp) . " Bytes)\n";
@ob_flush(); @flush();
usleep(200000); // 0.2 s
}
echo str_repeat('-', 60) . "\n";
echo "Zusammenfassung: ok=$ok, skip=$skip, fail=$fail\n";
$totalMp3 = count(glob($outDir . '/*.mp3'));
echo "Dateien im Ordner insgesamt: $totalMp3\n";