# app/utils/helpers.py import os import json import logging try: # Flask ist optional – bei Aufruf außerhalb des App-Contexts fallbacken wir from flask import current_app except Exception: # pragma: no cover - nur wenn Flask nicht verfügbar ist current_app = None logger = logging.getLogger(__name__) def _resolve_config_dir(config_dir=None): """ Ermittelt den zu nutzenden Config-Pfad: 1) Expliziter Parameter 2) Flask current_app.config['CONFIG_DIR'] 3) Fallback: lokaler 'configs' Ordner """ if config_dir: return config_dir if current_app: try: return current_app.config.get('CONFIG_DIR', 'configs') except Exception: pass return 'configs' def load_configs(config_dir=None): """ Lädt alle .json-Konfigurationsdateien aus dem configs-Ordner, außer default_sounds.json (die wird separat geladen). """ config_dir = _resolve_config_dir(config_dir) configs = [] if not os.path.exists(config_dir): logger.warning(f"Config-Verzeichnis nicht gefunden: {config_dir}") return configs for filename in os.listdir(config_dir): if not filename.lower().endswith('.json'): continue if filename == 'default_sounds.json': continue # globale Sounds separat laden path = os.path.join(config_dir, filename) try: with open(path, 'r', encoding='utf-8') as f: data = json.load(f) data['filename'] = filename data.setdefault('name', filename.replace('.json', '').replace('_', ' ').title()) configs.append(data) except json.JSONDecodeError as e: logger.error(f"Ungültiges JSON in {filename}: {e}") except FileNotFoundError: logger.error(f"Datei nicht gefunden (trotz listdir): {path}") except Exception as e: logger.error(f"Fehler beim Laden von {filename}: {e}") return sorted(configs, key=lambda x: x.get('name', '')) def load_default_sounds(config_dir=None): """ Lädt die globalen Standard-Sounds aus configs/default_sounds.json Wird immer geladen, unabhängig von der aktuellen Lok-Konfiguration """ config_dir = _resolve_config_dir(config_dir) path = os.path.join(config_dir, 'default_sounds.json') if not os.path.exists(path): logger.info("Keine default_sounds.json gefunden → keine globalen Sounds") return [] try: with open(path, 'r', encoding='utf-8') as f: data = json.load(f) # Flexibel: entweder 'global_sounds' oder direkt 'sounds' sounds = data.get('global_sounds', data.get('sounds', [])) logger.info(f"Globale Default-Sounds geladen: {len(sounds)} Einträge") return sounds except json.JSONDecodeError as e: logger.error(f"Ungültiges JSON in default_sounds.json: {e}") return [] except Exception as e: logger.error(f"Fehler beim Laden default_sounds.json: {e}") return [] # ---------- Soundboard-Themen ---------- def load_soundboard_configs(config_dir=None): """ Lädt alle Soundboard-Themen aus einem separaten Ordner (z. B. soundboards/). Unterstützt Unterordner. Jede *.json gilt als Thema (z. B. soundboards/station/theme.json). Erwartet Schema mit keys wie backgrounds / random_pool / sounds. """ config_dir = config_dir or _resolve_config_dir(None) configs = [] if not os.path.exists(config_dir): logger.warning(f"Soundboard-Verzeichnis nicht gefunden: {config_dir}") return configs for root, _, files in os.walk(config_dir): for filename in files: if not filename.lower().endswith('.json'): continue rel_path = os.path.relpath(os.path.join(root, filename), config_dir) try: with open(os.path.join(config_dir, rel_path), 'r', encoding='utf-8') as f: data = json.load(f) data['filename'] = rel_path # relativer Pfad ab soundboards/ data.setdefault('name', filename.replace('.json', '').replace('_', ' ').title()) configs.append(data) except Exception as e: logger.error(f"Fehler beim Laden Soundboard {rel_path}: {e}") return sorted(configs, key=lambda x: x.get('name', '')) def load_soundboard_config(filename, config_dir, sounds_dir=None): """ Lädt ein Soundboard-Theme. Falls die Theme-Datei in Unterordnern liegt, wird ein basis Sound-Pfad auf denselben relativen Unterordner unterhalb von sounds/ gesetzt. Alle Einträge (backgrounds/sounds/random_pool) erhalten ein 'base_path'. """ sounds_dir = sounds_dir or 'sounds' path = os.path.normpath(os.path.join(config_dir, filename)) if not path.startswith(os.path.abspath(config_dir)): raise ValueError("Ungültiger Pfad") rel_dir = os.path.dirname(filename) # z.B. "station" oder "urban/night" base_sound_dir = os.path.normpath(os.path.join(sounds_dir, rel_dir)) if rel_dir else sounds_dir with open(path, 'r', encoding='utf-8') as f: data = json.load(f) data['filename'] = filename # relative Angabe beibehalten data.setdefault('name', filename.replace('.json', '').replace('_', ' ').title()) data['base_sound_dir'] = base_sound_dir for key in ('backgrounds', 'sounds', 'random_pool'): if key in data and isinstance(data[key], list): for item in data[key]: item.setdefault('base_path', base_sound_dir) return data