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>
115 lines
3.9 KiB
PHP
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";
|