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>
133 lines
4.1 KiB
Python
133 lines
4.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Busfahrt SFX-Generator für ElevenLabs Sound Effects API.
|
|
|
|
Liest App/.env.local (ELEVENLABS_API_KEY) + sounds-list.json und lädt für
|
|
jeden Eintrag einen MP3 von ElevenLabs in ../assets/sounds/. Existierende
|
|
Dateien werden übersprungen (für inkrementelles Laufenlassen).
|
|
|
|
Aufruf:
|
|
cd App/sims/busfahrt/scripts
|
|
python generate-sounds.py # alle fehlenden Sounds generieren
|
|
python generate-sounds.py --force # ALLE neu generieren
|
|
python generate-sounds.py hit-ok # nur diesen einen Sound
|
|
|
|
Pattern aus App/sims/klima/scripts/generate-sounds.py.
|
|
"""
|
|
import json
|
|
import os
|
|
import sys
|
|
import time
|
|
import urllib.request
|
|
import urllib.error
|
|
from pathlib import Path
|
|
|
|
# Stdout robust auf UTF-8 setzen (Windows cp1252 verschluckt sonst Pfeile etc.)
|
|
if hasattr(sys.stdout, 'reconfigure'):
|
|
try:
|
|
sys.stdout.reconfigure(encoding='utf-8')
|
|
sys.stderr.reconfigure(encoding='utf-8')
|
|
except Exception:
|
|
pass
|
|
|
|
API_URL = "https://api.elevenlabs.io/v1/sound-generation"
|
|
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
SOUNDS_LIST = SCRIPT_DIR / "sounds-list.json"
|
|
OUT_DIR = SCRIPT_DIR.parent / "assets" / "sounds"
|
|
APP_ROOT = SCRIPT_DIR.parent.parent.parent
|
|
ENV_FILE = APP_ROOT / ".env.local"
|
|
|
|
|
|
def load_env(file_path: Path) -> dict:
|
|
env = {}
|
|
if not file_path.exists():
|
|
return env
|
|
for line in file_path.read_text(encoding="utf-8").splitlines():
|
|
line = line.strip()
|
|
if not line or line.startswith("#") or "=" not in line:
|
|
continue
|
|
key, _, val = line.partition("=")
|
|
env[key.strip()] = val.strip()
|
|
return env
|
|
|
|
|
|
def generate_sound(api_key: str, prompt: str, duration: float, influence: float) -> bytes:
|
|
body = json.dumps({
|
|
"text": prompt,
|
|
"duration_seconds": duration,
|
|
"prompt_influence": influence,
|
|
}).encode("utf-8")
|
|
req = urllib.request.Request(
|
|
API_URL,
|
|
data=body,
|
|
method="POST",
|
|
headers={
|
|
"xi-api-key": api_key,
|
|
"Content-Type": "application/json",
|
|
"Accept": "audio/mpeg",
|
|
},
|
|
)
|
|
with urllib.request.urlopen(req, timeout=120) as resp:
|
|
return resp.read()
|
|
|
|
|
|
def main() -> int:
|
|
env = load_env(ENV_FILE)
|
|
api_key = env.get("ELEVENLABS_API_KEY") or os.environ.get("ELEVENLABS_API_KEY")
|
|
if not api_key:
|
|
print(f"FEHLER: ELEVENLABS_API_KEY nicht in {ENV_FILE} oder Umgebung gefunden.", file=sys.stderr)
|
|
return 1
|
|
|
|
force = "--force" in sys.argv
|
|
only_file = None
|
|
for arg in sys.argv[1:]:
|
|
if arg.startswith("--"):
|
|
continue
|
|
only_file = arg + ".mp3" if not arg.endswith(".mp3") else arg
|
|
break
|
|
|
|
data = json.loads(SOUNDS_LIST.read_text(encoding="utf-8"))
|
|
sounds = data["sounds"]
|
|
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
total = len(sounds)
|
|
generated = 0
|
|
skipped = 0
|
|
errors = 0
|
|
|
|
for i, s in enumerate(sounds, 1):
|
|
name = s["file"]
|
|
out = OUT_DIR / name
|
|
if only_file and name != only_file:
|
|
continue
|
|
if out.exists() and not force:
|
|
skipped += 1
|
|
print(f"[{i:2}/{total}] SKIP {name:28} (schon da, --force überschreibt)")
|
|
continue
|
|
|
|
prompt = s["prompt"]
|
|
dur = float(s.get("duration", 2.0))
|
|
infl = float(s.get("influence", 0.5))
|
|
print(f"[{i:2}/{total}] GEN {name:28} dur={dur}s -> {prompt[:60]}...", flush=True)
|
|
try:
|
|
audio = generate_sound(api_key, prompt, dur, infl)
|
|
out.write_bytes(audio)
|
|
generated += 1
|
|
time.sleep(0.5)
|
|
except urllib.error.HTTPError as e:
|
|
msg = e.read().decode("utf-8", errors="ignore")[:200]
|
|
print(f" HTTP {e.code}: {msg}", file=sys.stderr)
|
|
errors += 1
|
|
except Exception as e:
|
|
print(f" Fehler: {e}", file=sys.stderr)
|
|
errors += 1
|
|
|
|
print()
|
|
print(f"Ergebnis: {generated} neu, {skipped} übersprungen, {errors} Fehler")
|
|
print(f"Zielordner: {OUT_DIR}")
|
|
return 0 if errors == 0 else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|