""" Duenner Python-Wrapper um das MIT-lizenzierte `tfmx_player/render_tfmx` (siehe tools/tfmx_player/LICENSE, Copyright Peter Fors) -- rendert ein TFMX-Musikstueck (.TFX + .SAM aus PCKELL.DAT) einmalig zu einer WAV-Datei und cached das Ergebnis, damit die Intro-Sequenz die Musik per pygame.mixer abspielen kann. Warum Subprocess statt reinem Python-Reimplement: TFMX ist ein komplexes Amiga-Tracker-Format (siehe tfmx.h, ~95KB reine Player-Logik). Das MIT-lizenzierte C-Referenztool duerfen wir direkt nutzen (im Gegensatz zum lizenzlosen C#-Referenzprojekt fuer PCX/BOB/MAP, das wir bewusst nur als Format-Wissen genommen und selbst neu geschrieben haben). Fuer Musik- Wiedergabe ist der Sound selbst das Ziel, nicht der Decoder-Code -- ein funktionierender, korrekt lizenzierter Renderer ist hier der pragmatische Weg statt TFMX in Python neu zu erfinden. Gerenderte WAVs sind urheberrechtlich das Original-Spielmaterial (Musik), werden daher NICHT ins Git-Repo committed (wie raw/ und extracted_dat/) -- liegen nur lokal im Cache-Verzeichnis. """ import os import subprocess import sys _HERE = os.path.dirname(os.path.abspath(__file__)) _PLAYER_DIR = os.path.join(_HERE, 'tfmx_player') _BINARY = os.path.join(_PLAYER_DIR, 'render_tfmx') _PROJECT_ROOT = os.path.dirname(_HERE) _CACHE_DIR = os.environ.get('KELLOGG_AUDIO_CACHE', os.path.join(_PROJECT_ROOT, 'audio_cache')) def _ensure_binary(): if os.path.exists(_BINARY): return subprocess.run(['bash', os.path.join(_PLAYER_DIR, 'build.sh')], check=True) def render_wav(container, name, seconds=20, force=False): """container: kellogg_formats.DATContainer; name: z.B. 'TITEL' (ohne Extension). Gibt den Pfad zur gerenderten (gecachten) WAV-Datei zurueck.""" os.makedirs(_CACHE_DIR, exist_ok=True) out_path = os.path.join(_CACHE_DIR, f'{name}.wav') if os.path.exists(out_path) and not force: return out_path _ensure_binary() tfx_data = container.entries[name + '.TFX'] sam_data = container.entries[name + '.SAM'] tfx_path = os.path.join(_CACHE_DIR, f'{name}.TFX') sam_path = os.path.join(_CACHE_DIR, f'{name}.SAM') with open(tfx_path, 'wb') as f: f.write(tfx_data) with open(sam_path, 'wb') as f: f.write(sam_data) result = subprocess.run( [_BINARY, tfx_path, sam_path, out_path, str(seconds)], capture_output=True, text=True ) if result.returncode != 0: raise RuntimeError(f'render_tfmx failed for {name}: {result.stderr}') return out_path if __name__ == '__main__': # Kleiner CLI-Test: python3 tfmx_audio.py [seconds] sys.path.insert(0, _HERE) from kellogg_formats import DATContainer dat_path = sys.argv[1] name = sys.argv[2] seconds = int(sys.argv[3]) if len(sys.argv) > 3 else 20 c = DATContainer(dat_path) path = render_wav(c, name, seconds, force=True) print(f'rendered: {path}')