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>
134 lines
6.4 KiB
PHP
134 lines
6.4 KiB
PHP
<?php
|
|
/**
|
|
* Sprecher-Portrait-Generator fuer Heli-Funk-Pille.
|
|
* Stil exakt wie Logistik (Flat Scandinavian Illustration), nur Format
|
|
* 1024x1024 (Portrait) statt 1792x1024 (Landscape).
|
|
*
|
|
* Aufruf via Browser:
|
|
* http://localhost/geograsim/App/php/generate-speaker-images.php
|
|
* ?ids=tower_f,pilot_d_f (nur diese zwei generieren)
|
|
* ?force=1 (ueberschreibt vorhandene)
|
|
*/
|
|
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"); }
|
|
|
|
// ---- Verbindlicher Stil-Vorspann (exakt wie Logistik) ----
|
|
$STYLE = "Flat Scandinavian illustration, simplified portrait headshot, painted style with clean vector shapes, dark forest green (#1f4b37) and sage green (#4a7c4e) tones, yellow-mustard (#e8c547) accents, beige (#e8e4d8) skin tones and uniform, white highlights. No text, no watermark, no logos, no borders, no frame.";
|
|
|
|
// ---- Sprecher-Profile mit Szenen-Beschreibung ----
|
|
$SPEAKERS = [
|
|
'tower_f' => [
|
|
'label' => 'Tower-Funkerin',
|
|
'scene' => "Female air traffic controller in beige uniform wearing aviation headset with boom microphone, calm focused expression, frontal pose facing camera, simplified sage green tower-interior background with subtle radar-screen silhouette.",
|
|
],
|
|
'briefing_f' => [
|
|
'label' => 'Briefing-Offizierin',
|
|
'scene' => "Female briefing officer in forest green uniform wearing slim aviation headset, friendly approachable expression, slight smile, frontal pose, simplified beige briefing-room background with a folded map and route lines.",
|
|
],
|
|
'pilot_a_m' => [
|
|
'label' => 'Pilot Hannes (C1/C5/C9/C12)',
|
|
'scene' => "Male helicopter pilot with short brown hair and beard stubble, in forest green flight suit wearing aviation headset with boom microphone, confident calm expression, frontal pose facing camera, simplified beige cockpit-interior background with sage green panels.",
|
|
],
|
|
'pilot_b_f' => [
|
|
'label' => 'Pilotin Marlene (C2/C6/C11/C16)',
|
|
'scene' => "Female helicopter pilot with shoulder-length blond hair, in forest green flight suit wearing aviation headset with boom microphone, friendly professional expression, frontal pose facing slightly left, simplified beige cockpit-interior background with sage green panels.",
|
|
],
|
|
'pilot_c_m' => [
|
|
'label' => 'Pilot Stefan (C3/C7/C10/C14)',
|
|
'scene' => "Male helicopter pilot with dark hair and glasses, in forest green flight suit wearing aviation headset with boom microphone, calm experienced expression, frontal pose, simplified beige cockpit-interior background with sage green panels.",
|
|
],
|
|
'pilot_d_f' => [
|
|
'label' => 'Pilotin Sarah (C4/C8/ARA)',
|
|
'scene' => "Female helicopter pilot with short red-brown hair, in forest green flight suit wearing aviation headset with boom microphone, focused friendly expression, frontal pose facing slightly right, simplified beige cockpit-interior background with sage green panels.",
|
|
],
|
|
'retter_m' => [
|
|
'label' => 'Flugretter (Bergrettung)',
|
|
'scene' => "Male mountain rescuer with climbing helmet and stubble, in red-orange rescue jacket with yellow-mustard reflective stripes, climbing harness visible, slim radio headset, focused determined expression, frontal pose, simplified outdoor mountain background with sage green pine trees.",
|
|
],
|
|
'sani_f' => [
|
|
'label' => 'Sanitaeterin (Autobahn)',
|
|
'scene' => "Female paramedic with brown ponytail, in high-visibility yellow-mustard jacket with reflective stripes over forest green uniform, slim radio headset, calm professional expression, frontal pose, simplified beige roadside background with subtle blue emergency-vehicle silhouette.",
|
|
],
|
|
];
|
|
|
|
$outDir = __DIR__ . '/../sims/heli/img/speakers';
|
|
if (!is_dir($outDir)) mkdir($outDir, 0777, true);
|
|
|
|
$ids = isset($_GET['ids']) ? array_filter(array_map('trim', explode(',', $_GET['ids']))) : array_keys($SPEAKERS);
|
|
$force = !empty($_GET['force']);
|
|
|
|
echo "Sprecher-Generator (DALL-E 3, 1024x1024)\n";
|
|
echo "Modus: " . ($force ? 'FORCE' : 'skip if exists') . "\n";
|
|
echo "Auswahl: " . implode(', ', $ids) . "\n";
|
|
echo str_repeat('-', 64) . "\n";
|
|
@ob_flush(); @flush();
|
|
|
|
$ok = 0; $skip = 0; $fail = 0;
|
|
foreach ($ids as $id) {
|
|
if (!isset($SPEAKERS[$id])) { echo "x $id: unbekannt\n"; $fail++; continue; }
|
|
$out = $outDir . '/' . $id . '.png';
|
|
if (file_exists($out) && !$force) {
|
|
echo "skip $id.png (vorhanden)\n";
|
|
$skip++;
|
|
continue;
|
|
}
|
|
$prompt = $STYLE . ' Subject: ' . $SPEAKERS[$id]['scene'];
|
|
$body = json_encode([
|
|
'model' => 'dall-e-3',
|
|
'prompt' => $prompt,
|
|
'n' => 1,
|
|
'size' => '1024x1024',
|
|
'quality' => 'standard',
|
|
]);
|
|
echo "-> $id (" . $SPEAKERS[$id]['label'] . ") generiere ... ";
|
|
@ob_flush(); @flush();
|
|
|
|
$ch = curl_init('https://api.openai.com/v1/images/generations');
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_POST => true,
|
|
CURLOPT_HTTPHEADER => [
|
|
'Authorization: Bearer ' . $OPENAI_KEY,
|
|
'Content-Type: application/json',
|
|
],
|
|
CURLOPT_POSTFIELDS => $body,
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_TIMEOUT => 180,
|
|
]);
|
|
$resp = curl_exec($ch);
|
|
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
curl_close($ch);
|
|
if ($code !== 200) {
|
|
echo "FAIL ($code) — " . substr($resp, 0, 200) . "\n";
|
|
$fail++;
|
|
continue;
|
|
}
|
|
$data = json_decode($resp, true);
|
|
$url = $data['data'][0]['url'] ?? null;
|
|
if (!$url) { echo "FAIL keine URL\n"; $fail++; continue; }
|
|
$img = @file_get_contents($url);
|
|
if (!$img || strlen($img) < 1000) { echo "FAIL Download\n"; $fail++; continue; }
|
|
file_put_contents($out, $img);
|
|
$ok++;
|
|
echo "ok (" . round(strlen($img) / 1024) . " kB)\n";
|
|
@ob_flush(); @flush();
|
|
sleep(2);
|
|
}
|
|
|
|
echo str_repeat('-', 64) . "\n";
|
|
echo "Zusammenfassung: ok=$ok, skip=$skip, fail=$fail\n";
|
|
echo "Ordner: $outDir\n";
|