PrinzPesia commited on
Commit
2d8cb17
·
verified ·
1 Parent(s): e269c12

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +173 -0
app.py ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import subprocess
3
+ import os
4
+ from datetime import datetime, timedelta
5
+ from apscheduler.schedulers.background import BackgroundScheduler
6
+ import pytz # Für Zeitzonen-Management
7
+
8
+ # --- Konfiguration ---
9
+ OUTPUT_DIR = "recordings"
10
+ # Erstelle das Verzeichnis, falls es nicht existiert
11
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
12
+
13
+ # Initialisiere den Scheduler
14
+ scheduler = BackgroundScheduler()
15
+ scheduler.start()
16
+
17
+ # --- Globale Variable für laufende Prozesse (optional, aber nützlich für Stop-Funktionalität) ---
18
+ # Für dieses einfache Beispiel lassen wir das "Stoppen" über die Dauer von ffmpeg
19
+ # Wenn du eine "Sofort Stoppen"-Funktion bräuchtest, müsstest du ffmpeg-Prozesse speichern und killen.
20
+
21
+ # --- Funktionen für die Aufnahme und Planung ---
22
+
23
+ def record_radio_stream(stream_url: str, output_filename: str, duration_seconds: int):
24
+ """
25
+ Startet die Aufnahme eines Webradio-Streams mit ffmpeg.
26
+ """
27
+ full_output_path = os.path.join(OUTPUT_DIR, output_filename)
28
+
29
+ print(f"🎬 Starte Aufnahme von {stream_url} für {duration_seconds} Sekunden nach {full_output_path}...")
30
+
31
+ # ffmpeg Kommando:
32
+ # -i: Input URL
33
+ # -c:a copy: Audio-Codec kopieren (kein Re-Encoding), das spart CPU und Zeit
34
+ # -map 0:a: Stelle sicher, dass nur der Audio-Stream gemappt wird
35
+ # -t: Dauer der Aufnahme in Sekunden
36
+ command = [
37
+ "ffmpeg",
38
+ "-i", stream_url,
39
+ "-c:a", "copy",
40
+ "-map", "0:a",
41
+ "-t", str(duration_seconds),
42
+ full_output_path
43
+ ]
44
+
45
+ try:
46
+ # Führe den ffmpeg-Befehl aus
47
+ # check=True wird eine CalledProcessError erzeugen, wenn ffmpeg fehlschlägt
48
+ subprocess.run(command, check=True, capture_output=True)
49
+ print(f"✅ Aufnahme abgeschlossen: {full_output_path}")
50
+ return full_output_path
51
+ except subprocess.CalledProcessError as e:
52
+ print(f"❌ Fehler bei der Aufnahme von {stream_url}:")
53
+ print(f"Stdout: {e.stdout.decode()}")
54
+ print(f"Stderr: {e.stderr.decode()}")
55
+ # Lösche unvollständige Datei, falls vorhanden
56
+ if os.path.exists(full_output_path):
57
+ os.remove(full_output_path)
58
+ return None # Signalisiere Fehler
59
+
60
+ def schedule_recording(stream_url: str, start_datetime_str: str, end_datetime_str: str):
61
+ """
62
+ Plant eine Webradio-Aufnahme basierend auf Start- und Endzeit.
63
+ Die Zeiten kommen als String von Gradio und müssen geparst werden.
64
+ """
65
+ try:
66
+ # Gradio gibt Datetime-Strings im Format 'YYYY-MM-DD HH:MM:SS' zurück
67
+ # Konvertiere in datetime-Objekte
68
+ start_datetime = datetime.fromisoformat(start_datetime_str)
69
+ end_datetime = datetime.fromisoformat(end_datetime_str)
70
+
71
+ # Optional: Zeitzone explizit setzen (z.B. UTC), wenn du sicherstellen willst, dass es unabhängig vom Server läuft
72
+ # Du kannst hier auch die Zeitzone des Nutzers abfragen, wenn es komplizierter werden soll
73
+ # Für Hugging Face Spaces ist UTC oft eine gute Wahl für den Server.
74
+ # Wenn der Nutzer aber eine lokale Zeit eingibt, muss dies berücksichtigt werden.
75
+ # Hier gehen wir davon aus, dass die eingegebene Zeit auf dem Server interpretiert wird.
76
+
77
+ # Berechne die Dauer der Aufnahme in Sekunden
78
+ duration = (end_datetime - start_datetime).total_seconds()
79
+
80
+ if duration <= 0:
81
+ return "❌ Fehler: Die Endzeit muss nach der Startzeit liegen."
82
+
83
+ # Generiere einen eindeutigen Dateinamen
84
+ timestamp = start_datetime.strftime("%Y%m%d_%H%M%S")
85
+ output_filename = f"radio_recording_{timestamp}.mp3"
86
+
87
+ # Füge den Job zum Scheduler hinzu
88
+ # 'date' trigger bedeutet, dass der Job zu einem spezifischen Datum und Uhrzeit ausgeführt wird
89
+ scheduler.add_job(
90
+ record_radio_stream,
91
+ 'date',
92
+ run_date=start_datetime,
93
+ args=[stream_url, output_filename, int(duration)] # Dauer als Integer übergeben
94
+ )
95
+
96
+ # Zeige geplante Jobs an (optional, zur Fehlersuche)
97
+ # for job in scheduler.get_jobs():
98
+ # print(f"Geplanter Job: {job.id} - Nächste Ausführung: {job.next_run_time}")
99
+
100
+ return f"✅ Aufnahme von **{stream_url}** erfolgreich geplant.\nStart: **{start_datetime}** | Ende: **{end_datetime}**.\nDatei: **{output_filename}**\nBitte aktualisiere die Dateiliste, nachdem die Aufnahme abgeschlossen ist."
101
+
102
+ except ValueError as e:
103
+ return f"❌ Fehler beim Parsen der Daten/Uhrzeiten: {e}. Bitte stelle sicher, dass das Format korrekt ist."
104
+ except Exception as e:
105
+ return f"❌ Ein unerwarteter Fehler ist aufgetreten: {e}"
106
+
107
+ def get_recorded_files():
108
+ """
109
+ Gibt eine Liste der Pfade zu allen aufgenommenen MP3-Dateien zurück.
110
+ """
111
+ files = [os.path.join(OUTPUT_DIR, f) for f in os.listdir(OUTPUT_DIR) if f.endswith(".mp3")]
112
+ # Gradio gr.Files erwartet eine Liste von Pfaden.
113
+ # Wenn keine Dateien da sind, gibt eine leere Liste zurück.
114
+ return files
115
+
116
+ # --- Gradio UI Definition ---
117
+
118
+ with gr.Blocks() as demo:
119
+ gr.Markdown("# 📻 Webradio Recorder")
120
+ gr.Markdown(
121
+ "Plane deine Webradio-Aufnahmen! Gib die Stream-URL, Start- und Endzeit an, "
122
+ "und die App nimmt den Stream für dich auf. Die fertige Datei kannst du dann herunterladen."
123
+ )
124
+
125
+ with gr.Tab("Aufnahme planen"):
126
+ with gr.Row():
127
+ stream_url_input = gr.Textbox(
128
+ label="Stream URL",
129
+ placeholder="z.B. http://stream.laut.fm/meinstream (MP3- oder AAC-Stream)"
130
+ )
131
+ with gr.Row():
132
+ start_datetime_input = gr.Datetime(
133
+ label="Start Datum & Uhrzeit",
134
+ value=datetime.now() + timedelta(minutes=1), # Voreinstellung: 1 Minute in der Zukunft
135
+ info="Wähle das Datum und die Uhrzeit, wann die Aufnahme beginnen soll."
136
+ )
137
+ end_datetime_input = gr.Datetime(
138
+ label="End Datum & Uhrzeit",
139
+ value=datetime.now() + timedelta(minutes=10), # Voreinstellung: 10 Minuten in der Zukunft
140
+ info="Wähle das Datum und die Uhrzeit, wann die Aufnahme enden soll."
141
+ )
142
+
143
+ schedule_button = gr.Button("▶️ Aufnahme planen", variant="primary")
144
+ schedule_output_message = gr.Textbox(label="Status der Planung", interactive=False)
145
+
146
+ schedule_button.click(
147
+ fn=schedule_recording,
148
+ inputs=[stream_url_input, start_datetime_input, end_datetime_input],
149
+ outputs=schedule_output_message
150
+ )
151
+
152
+ with gr.Tab("Verfügbare Aufnahmen"):
153
+ gr.Markdown("Hier siehst du alle bisher aufgenommenen Dateien.")
154
+
155
+ refresh_files_button = gr.Button("🔄 Aufnahmen aktualisieren", variant="secondary")
156
+ # gr.Files ermöglicht das Herunterladen der Dateien
157
+ downloadable_files = gr.Files(label="Deine Aufnahmen zum Herunterladen", type="file")
158
+
159
+ # Wenn der "Aktualisieren"-Button geklickt wird, rufe die Funktion auf, um die Dateien zu listen
160
+ refresh_files_button.click(
161
+ fn=get_recorded_files,
162
+ outputs=downloadable_files
163
+ )
164
+
165
+ # Beim Laden der App auch die Dateien einmal anzeigen
166
+ demo.load(
167
+ fn=get_recorded_files,
168
+ outputs=downloadable_files
169
+ )
170
+
171
+ # --- App starten ---
172
+ if __name__ == "__main__":
173
+ demo.launch()