Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import subprocess | |
| import os | |
| from datetime import datetime, timedelta | |
| from apscheduler.schedulers.background import BackgroundScheduler | |
| import pytz # Für Zeitzonen-Management (nicht direkt verwendet, aber gute Praxis) | |
| # --- Konfiguration --- | |
| OUTPUT_DIR = "recordings" | |
| # Erstelle das Verzeichnis, falls es nicht existiert | |
| os.makedirs(OUTPUT_DIR, exist_ok=True) | |
| # Initialisiere den Scheduler | |
| scheduler = BackgroundScheduler() | |
| scheduler.start() | |
| # --- Funktionen für die Aufnahme und Planung --- | |
| def record_radio_stream(stream_url: str, output_filename: str, duration_seconds: int): | |
| """ | |
| Startet die Aufnahme eines Webradio-Streams mit ffmpeg. | |
| """ | |
| full_output_path = os.path.join(OUTPUT_DIR, output_filename) | |
| print(f"🎬 Starte Aufnahme von {stream_url} für {duration_seconds} Sekunden nach {full_output_path}...") | |
| # ffmpeg Kommando: | |
| # -i: Input URL | |
| # -c:a copy: Audio-Codec kopieren (kein Re-Encoding), das spart CPU und Zeit | |
| # -map 0:a: Stelle sicher, dass nur der Audio-Stream gemappt wird | |
| # -t: Dauer der Aufnahme in Sekunden | |
| command = [ | |
| "ffmpeg", | |
| "-i", stream_url, | |
| "-c:a", "copy", | |
| "-map", "0:a", | |
| "-t", str(duration_seconds), | |
| full_output_path | |
| ] | |
| try: | |
| # Führe den ffmpeg-Befehl aus | |
| # check=True wird eine CalledProcessError erzeugen, wenn ffmpeg fehlschlägt | |
| subprocess.run(command, check=True, capture_output=True) | |
| print(f"✅ Aufnahme abgeschlossen: {full_output_path}") | |
| return full_output_path | |
| except subprocess.CalledProcessError as e: | |
| print(f"❌ Fehler bei der Aufnahme von {stream_url}:") | |
| print(f"Stdout: {e.stdout.decode()}") | |
| print(f"Stderr: {e.stderr.decode()}") | |
| # Lösche unvollständige Datei, falls vorhanden | |
| if os.path.exists(full_output_path): | |
| os.remove(full_output_path) | |
| return None # Signalisiere Fehler | |
| def schedule_recording(stream_url: str, start_datetime_obj: datetime, end_datetime_obj: datetime): | |
| """ | |
| Plant eine Webradio-Aufnahme basierend auf Start- und Endzeit. | |
| Die Zeiten kommen direkt als datetime-Objekte von gr.DateTime. | |
| """ | |
| try: | |
| # Berechne die Dauer der Aufnahme in Sekunden | |
| duration = (end_datetime_obj - start_datetime_obj).total_seconds() | |
| if duration <= 0: | |
| return "❌ Fehler: Die Endzeit muss nach der Startzeit liegen." | |
| # Generiere einen eindeutigen Dateinamen | |
| timestamp = start_datetime_obj.strftime("%Y%m%d_%H%M%S") | |
| output_filename = f"radio_recording_{timestamp}.mp3" | |
| # Füge den Job zum Scheduler hinzu | |
| # 'date' trigger bedeutet, dass der Job zu einem spezifischen Datum und Uhrzeit ausgeführt wird | |
| scheduler.add_job( | |
| record_radio_stream, | |
| 'date', | |
| run_date=start_datetime_obj, | |
| args=[stream_url, output_filename, int(duration)] # Dauer als Integer übergeben | |
| ) | |
| # Zeige geplante Jobs an (optional, zur Fehlersuche) | |
| # for job in scheduler.get_jobs(): | |
| # print(f"Geplanter Job: {job.id} - Nächste Ausführung: {job.next_run_time}") | |
| return f"✅ Aufnahme von **{stream_url}** erfolgreich geplant.\nStart: **{start_datetime_obj.strftime('%Y-%m-%d %H:%M:%S')}** | Ende: **{end_datetime_obj.strftime('%Y-%m-%d %H:%M:%S')}**.\nDatei: **{output_filename}**\nBitte aktualisiere die Dateiliste, nachdem die Aufnahme abgeschlossen ist." | |
| except Exception as e: | |
| return f"❌ Ein unerwarteter Fehler ist aufgetreten: {e}" | |
| def get_recorded_files(): | |
| """ | |
| Gibt eine Liste der Pfade zu allen aufgenommenen MP3-Dateien zurück. | |
| """ | |
| files = [os.path.join(OUTPUT_DIR, f) for f in os.listdir(OUTPUT_DIR) if f.endswith(".mp3")] | |
| # Gradio gr.Files erwartet eine Liste von Pfaden. | |
| # Wenn keine Dateien da sind, gibt eine leere Liste zurück. | |
| return files | |
| # --- Gradio UI Definition --- | |
| with gr.Blocks() as demo: | |
| gr.Markdown("# 📻 Webradio Recorder") | |
| gr.Markdown( | |
| "Plane deine Webradio-Aufnahmen! Gib die Stream-URL, Start- und Endzeit an, " | |
| "und die App nimmt den Stream für dich auf. Die fertige Datei kannst du dann herunterladen." | |
| ) | |
| with gr.Tab("Aufnahme planen"): | |
| with gr.Row(): | |
| stream_url_input = gr.Textbox( | |
| label="Stream URL", | |
| placeholder="z.B. http://stream.laut.fm/meinstream (MP3- oder AAC-Stream)" | |
| ) | |
| with gr.Row(): | |
| # HIER WURDE GR.DATETIME VERWENDET | |
| start_datetime_input = gr.DateTime( | |
| label="Start Datum & Uhrzeit", | |
| value=datetime.now() + timedelta(minutes=1), # Voreinstellung: 1 Minute in der Zukunft | |
| info="Wähle das Datum und die Uhrzeit, wann die Aufnahme beginnen soll." | |
| ) | |
| # HIER WURDE GR.DATETIME VERWENDET | |
| end_datetime_input = gr.DateTime( | |
| label="End Datum & Uhrzeit", | |
| value=datetime.now() + timedelta(minutes=10), # Voreinstellung: 10 Minuten in der Zukunft | |
| info="Wähle das Datum und die Uhrzeit, wann die Aufnahme enden soll." | |
| ) | |
| schedule_button = gr.Button("▶️ Aufnahme planen", variant="primary") | |
| schedule_output_message = gr.Textbox(label="Status der Planung", interactive=False) | |
| schedule_button.click( | |
| fn=schedule_recording, | |
| inputs=[stream_url_input, start_datetime_input, end_datetime_input], | |
| outputs=schedule_output_message | |
| ) | |
| with gr.Tab("Verfügbare Aufnahmen"): | |
| gr.Markdown("Hier siehst du alle bisher aufgenommenen Dateien.") | |
| refresh_files_button = gr.Button("🔄 Aufnahmen aktualisieren", variant="secondary") | |
| # gr.Files ermöglicht das Herunterladen der Dateien | |
| # HIER WURDE DER PARAMETER 'type' GEÄNDERT | |
| downloadable_files = gr.Files(label="Deine Aufnahmen zum Herunterladen", type="filepath") | |
| # Wenn der "Aktualisieren"-Button geklickt wird, rufe die Funktion auf, um die Dateien zu listen | |
| refresh_files_button.click( | |
| fn=get_recorded_files, | |
| outputs=downloadable_files | |
| ) | |
| # Beim Laden der App auch die Dateien einmal anzeigen | |
| demo.load( | |
| fn=get_recorded_files, | |
| outputs=downloadable_files | |
| ) | |
| # --- App starten --- | |
| if __name__ == "__main__": | |
| demo.launch() |