This commit is contained in:
2026-03-25 22:03:39 +01:00
parent a0073b4fb1
commit faf4ca10c9
5603 changed files with 1030866 additions and 79 deletions

View File

@@ -42,8 +42,8 @@ ES_AWAYMODE_REQUIRED = 0x00000040
BG = "#0b0b0b"
CARD = "#151515"
BORDER = "#1e1e1e"
GREEN = "#1DB954"
GREEN_H = "#1ed760"
GREEN = "#7C9A3C"
GREEN_H = "#90AE4A"
RED = "#e74c3c"
RED_H = "#ff6b6b"
ORANGE = "#f59e0b"
@@ -222,8 +222,6 @@ class SpotifyRecorderApp:
self.root = ctk.CTk()
self.root.title("Surovy's Music Recorder")
self.root.geometry("1400x880")
self.root.minsize(1000, 620)
self.root.configure(fg_color=BG)
ico = os.path.join(os.path.dirname(os.path.abspath(__file__)),
@@ -233,9 +231,78 @@ class SpotifyRecorderApp:
self.root.after(200, lambda: self.root.iconbitmap(ico))
self.root.protocol("WM_DELETE_WINDOW", self._on_close)
# state
self.platform_name = "Spotify"
self.platform_cfg = PLATFORMS["Spotify"]
self._app_initialized = False
self._show_selector()
# ── platform selector ─────────────────────────────────────
_PLATFORM_ICONS = {
"Spotify": "\u266a",
"Apple Music": "\u266a",
"Amazon Music": "\u266a",
"Tidal": "\u266a",
"Deezer": "\u266a",
"YouTube Music": "\u25b6",
"TuneIn / Radio": "\U0001f4fb",
"SoundCloud": "\u2601",
"System Audio": "\U0001f50a",
}
def _show_selector(self):
self.root.geometry("680x530")
self.root.minsize(680, 530)
self.root.resizable(False, False)
self._sel = ctk.CTkFrame(self.root, fg_color=BG)
self._sel.pack(fill="both", expand=True)
hdr = ctk.CTkFrame(self._sel, fg_color="transparent")
hdr.pack(pady=(38, 0))
icon_box = ctk.CTkFrame(hdr, width=54, height=54, fg_color=GREEN,
corner_radius=15)
icon_box.pack()
icon_box.pack_propagate(False)
ctk.CTkLabel(icon_box, text="\u266a",
font=ctk.CTkFont(size=24, weight="bold"),
text_color="white").pack(expand=True)
ctk.CTkLabel(hdr, text="Surovy's Music Recorder",
font=ctk.CTkFont(size=22, weight="bold"),
text_color=TXT).pack(pady=(14, 4))
ctk.CTkLabel(hdr, text="Dienst auswaehlen",
font=ctk.CTkFont(size=13), text_color=TXT2).pack()
grid = ctk.CTkFrame(self._sel, fg_color="transparent")
grid.pack(pady=(28, 0))
for i, name in enumerate(PLATFORM_NAMES):
row, col = divmod(i, 3)
ic = self._PLATFORM_ICONS.get(name, "\u266a")
btn = ctk.CTkButton(
grid, text=f"{ic} {name}",
width=195, height=64, corner_radius=14,
font=ctk.CTkFont(size=13, weight="bold"),
fg_color=CARD, hover_color="#252525",
border_width=1, border_color=BORDER,
text_color=TXT,
command=lambda n=name: self._on_platform_selected(n),
)
btn.grid(row=row, column=col, padx=8, pady=8)
ctk.CTkLabel(self._sel,
text="Du kannst den Dienst spaeter jederzeit wechseln.",
font=ctk.CTkFont(size=10), text_color=DIM
).pack(pady=(22, 0))
def _on_platform_selected(self, name):
self._sel.destroy()
self.root.resizable(True, True)
self.root.geometry("1400x880")
self.root.minsize(1000, 620)
self.platform_name = name
self.platform_cfg = PLATFORMS[name]
self.recorder = AudioRecorder()
self.spotify_hwnd = None
self.original_style = None
@@ -266,6 +333,11 @@ class SpotifyRecorderApp:
self._restart_after_id = None
self._restart_fails = 0
self._lock = threading.Lock()
self._silence_start = None
self._silence_threshold = 0.012
self._min_silence_secs = 2.0
self._app_initialized = True
self._build_controls()
self._build_embed_area()
@@ -300,7 +372,7 @@ class SpotifyRecorderApp:
hcol = ctk.CTkFrame(hdr, fg_color="transparent")
hcol.pack(side="left", padx=10)
ctk.CTkLabel(hcol, text="Surovy's",
font=ctk.CTkFont(size=10), text_color=GREEN).pack(anchor="w")
font=ctk.CTkFont(size=10), text_color=TXT).pack(anchor="w")
ctk.CTkLabel(hcol, text="Music Recorder",
font=ctk.CTkFont(size=16, weight="bold"),
text_color=TXT).pack(anchor="w")
@@ -311,7 +383,7 @@ class SpotifyRecorderApp:
ctk.CTkLabel(src_row, text="QUELLE",
font=ctk.CTkFont(size=9, weight="bold"),
text_color=DIM).pack(side="left")
self.platform_var = ctk.StringVar(value="Spotify")
self.platform_var = ctk.StringVar(value=self.platform_name)
ctk.CTkOptionMenu(
src_row, values=PLATFORM_NAMES,
variable=self.platform_var, width=170, height=28,
@@ -1008,6 +1080,13 @@ class SpotifyRecorderApp:
# ── track monitoring ───────────────────────────────────────
def _tick_monitor(self):
try:
self._tick_monitor_inner()
except Exception:
pass
self.root.after(500, self._tick_monitor)
def _tick_monitor_inner(self):
sep = self.platform_cfg.get("sep")
pause_titles = self.platform_cfg.get("pause", set())
@@ -1017,11 +1096,7 @@ class SpotifyRecorderApp:
except Exception:
title = ""
if self.active and self.current_track is None and self.is_playing:
self.recorder.harvest_frames()
if not title:
self.root.after(500, self._tick_monitor)
return
if sep and sep in title:
@@ -1045,6 +1120,20 @@ class SpotifyRecorderApp:
parts = title.split(sep, 1)
self.artist_lbl.configure(text=parts[0].strip())
self.title_lbl.configure(text=parts[1].strip())
elif (self.active and self.current_track is None
and title == self.last_title):
parts = title.split(sep, 1)
artist = parts[0].strip()
song = parts[1].strip()
key = self._song_key(artist, song)
if key not in self.recorded_songs:
self.track_number += 1
self.current_track = {
"artist": artist, "title": song}
self._update_track_ui()
self._set_status("AUFNAHME", GREEN)
else:
self.recorder.harvest_frames()
self.last_title = title
else:
@@ -1080,7 +1169,7 @@ class SpotifyRecorderApp:
self.title_lbl.configure(
text="wird nicht aufgenommen")
self.tnum_lbl.configure(text="")
if self.active:
if self.active and self.current_track is None:
self.recorder.harvest_frames()
self.pause_since = None
@@ -1107,9 +1196,34 @@ class SpotifyRecorderApp:
text=lbl, text_color=ORANGE)
elif not self.platform_cfg.get("process"):
pass
self.root.after(500, self._tick_monitor)
if self.active:
lvl = self.recorder.get_level()
if lvl < self._silence_threshold:
if self._silence_start is None:
self._silence_start = time.time()
elif (time.time() - self._silence_start
>= self._min_silence_secs
and self.current_track):
with self._lock:
frames = self.recorder.harvest_frames()
if frames:
num = self.track_number
trk = dict(self.current_track)
threading.Thread(
target=self._save,
args=(frames, trk, num),
daemon=True).start()
self.track_number += 1
self.current_track = {
"artist": self.platform_name,
"title": f"Track {self.track_number:02d}",
}
self._update_track_ui()
self.rec_start = time.time()
self._silence_start = None
self._set_status("NEUER TRACK", GREEN)
else:
self._silence_start = None
def _handle_track_change(self, new_title):
sep = self.platform_cfg.get("sep", " - ")
@@ -1268,7 +1382,7 @@ class SpotifyRecorderApp:
if self.active:
self._pulse_on = not self._pulse_on
self.dot_lbl.configure(
text_color=GREEN if self._pulse_on else "#14532d")
text_color=GREEN if self._pulse_on else "#3E4D1E")
self.root.after(650, self._tick_pulse)
def _tick_alive(self):
@@ -1298,6 +1412,19 @@ class SpotifyRecorderApp:
anchor="center")
if self.active:
self._stop()
if self.active and self.recorder._stream:
try:
if not self.recorder._stream.is_active():
self.recorder.stop()
try:
self.recorder.start()
self._prevent_sleep()
except Exception:
self._stop()
except Exception:
pass
self.root.after(3000, self._tick_alive)
# ── statistics ─────────────────────────────────────────────
@@ -1349,6 +1476,9 @@ class SpotifyRecorderApp:
# ── cleanup ────────────────────────────────────────────────
def _on_close(self):
if not self._app_initialized:
self.root.destroy()
return
self._user_stopped = True
if self._restart_after_id:
self.root.after_cancel(self._restart_after_id)