
Lokale Musik: Spotifys Release Radar reimplementieren
Release Radar auf Grundlage meiner Vorlieben, MusicBrainz und YouTube Music, versendet als Mail
Der Release Radar: Neue Musik aus dem eigenen Künstler-Universum#
Streaming-Dienste melden, wenn ein verfolgter Künstler etwas Neues herausbringt. Bei einer lokalen Sammlung macht das niemand, es sei denn, man baut es selbst. Mein Release Radar ist das lokale Gegenstück zu Spotifys gleichnamiger Funktion: Eine wöchentliche Mail mit Neuerscheinungen der Künstler, die mir wichtig sind, plus etwas Entdeckung am Rand.
Die erste Frage ist immer: Wessen Releases interessieren mich überhaupt? Die Antwort liefert die zentrale Künstler-Rangliste (interesting_artists.json). Sie definiert das Universum, das der Radar wöchentlich gegen die Welt abgleicht.
Was das Skript an Quellen einbindet und was am Ende herauskommt:
flowchart LR
IA["interesting_artists.json<br/>Künstler-Universum"]
MB["MusicBrainz<br/>Releases · Mitglieder"]
YTM["YouTube Music<br/>Gegencheck · related · Links"]
LFM["Last.fm<br/>Ähnlichkeit · Listener"]
BEETS["beets-Bibliothek<br/>Besitz (Cross-Check)"]
EINK["Einkaufsliste.json<br/>Kaufabsicht (Cross-Check)"]
RR["release_radar.py"]
MD["release-radar-*.md"]
MAIL["HTML-Mail<br/>(freitags)"]
IA --> RR
MB --> RR
YTM --> RR
LFM --> RR
BEETS --> RR
EINK --> RR
RR --> MD --> MAIL
classDef src fill:#e8f0fe,stroke:#3367d6,color:#000;
classDef script fill:#e6f4ea,stroke:#1e8e3e,color:#000,stroke-width:2px;
classDef out fill:#f3e8fd,stroke:#7b2ff7,color:#000;
class IA,MB,YTM,LFM,BEETS,EINK src;
class RR script;
class MD,MAIL out;
Woher die Releases kommen: MusicBrainz und YouTube Music#
Die Primärquelle ist MusicBrainz, die offene Musik-Enzyklopädie. Für jeden Künstler holt das Radar die Release-Groups (Alben, EPs, Singles) und behält nur die im Zeitfenster: Standardmäßig die letzten 60 Tage, optional auch schon angekündigte mit Zukunftsdatum.
MusicBrainz allein hat aber eine Schwäche: Ganz frische Singles tauchen dort oft erst mit Verzögerung auf. Deshalb gibt es einen Gegencheck über YouTube Music (via ytmusicapi). Er bestätigt einen Release, liefert einen Direktlink zum Anhören und fängt zusätzlich ab, was MusicBrainz noch nicht kennt. In der Mail sieht man in der Quellen-Spalte, woher ein Eintrag stammt: MB, MB+YT (von beiden bestätigt) oder YT (nur YouTube Music kennt ihn).
So sieht ein Ausschnitt aus „Neu erschienen” aus (wichtigste Künstler zuerst):
| # | Datum | Künstler | Album | Typ | Score | Plays 3m | Lokale Alben | Quellen |
|---|---|---|---|---|---|---|---|---|
| 1 | 2025-09-13 | Taylor Swift | From The Vault | Album | 620.8 | 490 | 33 | MB |
| 2 | 2025-10-05 | Taylor Swift | Elizabeth Taylor ↗ | Single | 620.8 | 490 | 33 | MB+YT |
| 3 | 2025-10-05 | Taylor Swift | The Fate of Ophelia ↗ | Single | 620.8 | 490 | 33 | MB+YT |
| 4 | 2025-10-05 | Taylor Swift | Opalite ↗ | Single | 620.8 | 490 | 33 | MB+YT |
Die Spalte Score berechnet das Radar nicht selbst: Es ist der Relevanz-Score des Künstlers aus der zentralen Rangliste, der hier nur die Sortierung bestimmt (wichtigste Künstler zuerst). Plays 3m und Lokale Alben geben Kontext, die Quellen-Spalte die Bestätigung.
Was rausfällt: Schon da, schon gewollt#
Eine Neuerscheinung ist nur dann interessant, wenn ich sie nicht ohnehin schon habe. Darum gleicht das Radar jeden Treffer gegen zwei Listen ab: Gegen den lokalen Bestand (die beets-Bibliothek) und gegen die Einkaufsliste der Musik, die ich sowieso schon kaufen will. Was dort steht, fliegt raus. Übrig bleibt nur, was wirklich neu für mich ist.
Über die eigene Liste hinaus#
Die direkte Künstlerliste ist die Basis, aber drei Erweiterungen fangen das ab, was sonst durchrutscht.
Gastbeiträge: Ein Künstler veröffentlicht nicht nur unter eigenem Namen, sondern ist manchmal nur Gast auf dem Release eines anderen. Der Feature-Durchlauf sucht für die obersten Künstler gezielt solche Co-Credits.
Bandmitglieder: Über MusicBrainz lässt sich auslesen, wer in einer Band spielt (member of band). Der Member-Durchlauf prüft die Mitglieder verfolgter Bands auf eigene Solo-Releases, auch wenn das Mitglied nicht selbst auf meiner Liste steht. So erreicht mich etwa eine neue Single von Lauren Mayberry, weil ich CHVRCHES höre:
| # | Datum | Künstler | Album | Typ | Score | Plays 3m | Lokale Alben | Quellen |
|---|---|---|---|---|---|---|---|---|
| 29 | 2026-02-12 | Lauren Mayberry (Mitglied von CHVRCHES) | Lonely Girl ↗ | Single | 117.4 | 0 | 0 | MB+YT |
Dieser Durchlauf braucht Filter, sonst wird erzeugt er Rauschen: MusicBrainz schreibt Re-Issues alter Band-Songs gerne einzelnen Mitgliedern zu (eine 2024er-Wiederveröffentlichung von „One” landet dann unter James Hetfield). Zwei Stufen halten das draußen: Titel aus dem Band-Katalog werden verworfen, und ein Mitglied muss eine echte Solo-Hörerschaft haben (mindestens 2.000 Last.fm-Listener). Schlagzeuger ohne Solokarriere fallen so weg, eine Lauren Mayberry mit 61.000 Hörern bleibt.
Discovery: Am Rand zeigt der Radar Künstler, die noch gar nicht in meinem Profil sind, aber zu mehreren meiner Lieblings-Acts benachbart sind, abgeleitet aus Last.fm-Ähnlichkeit und dem related-Graph von YouTube Music. Die Mail nennt dazu, über welche Seeds ein Vorschlag kam:
| # | Datum | Künstler | Album | Typ | Sim.-Score | Über Seeds |
|---|---|---|---|---|---|---|
| 1 | 2025-08-15 | Conan Gray | Wishbone ↗ | Album | 1.75 | Taylor Swift, Gracie Abrams, girl in red |
| 2 | 2026-01-16 | Madison Beer | locket ↗ | Album | 1.73 | Taylor Swift, Halsey, Gracie Abrams |
| 3 | 2025-08-01 | Reneé Rapp | BITE ME ↗ | Album | 1.68 | Halsey, Gracie Abrams |
Hier berechnet der Radar einen eigenen Wert, die Sim.-Score. Jeder Kandidat sammelt Punkte über jeden meiner Seeds, der ihn vorschlägt, aus zwei Quellen:
Sim.-Score(Kandidat) = Σ über jeden vorschlagenden Seed:
Last.fm-Match-Score (0 … 1)
+ ytmusicapi-related-Rang (~0,70 absteigend bis ~0,35)plaintextAufgenommen wird ein Kandidat erst ab einem Sim.-Score von 0,6. Der Effekt: Ein Künstler, den nur ein einziger Seed schwach vorschlägt, fällt durch; einer, den mehrere meiner Lieblings-Acts gemeinsam als ähnlich führen, landet oben. Conan Gray etwa kommt auf 1,75, weil ihn gleich drei meiner Seeds — Taylor Swift, Gracie Abrams und girl in red — als ähnlich listen und sich ihre Beiträge addieren. Anschließend wirft ein Genre-Abgleich grobe Ausreißer raus, und nur die obersten Kandidaten werden überhaupt gegen MusicBrainz auf neue Releases geprüft.
Von der Liste zur Mail#
Der Radar selbst schreibt nur eine Markdown-Datei. Daraus wird eine HTML-Mail gerendert: Pro Release eine abgerundete Karte, eingefärbt mit der dominanten Farbe des Künstler-Thumbnails, mit Typ-Pille und Tage-Zähler („in 5 Tagen”, „vor 3 Wochen”). Alles inline gestylt, damit es auch in Mail-Clients funktioniert, die externe Bilder oder Hintergründe blockieren. Versendet wird das Ganze per SMTP an mich selbst, automatisch jeden Freitagmorgen.

Grenzen#
Der Radar ist nur so gut wie seine Quellen. Was MusicBrainz gar nicht mit einem Künstler verknüpft — eine reine Titel-Erwähnung ohne formalen Credit, oder ein Soundtrack-Beitrag, der unter einem fremden Album-Artist läuft — bleibt unsichtbar. Der YouTube-Music-Gegencheck mildert das, schließt die Lücke aber nicht ganz. Das ist eine Datenlücke gegenüber Spotify.
Optionen#
Der Freitags-Cron läuft mit Standardwerten, alles Weitere sind Flags für manuelle oder experimentelle Läufe. Standardmäßig betrachtet das Skript ein Fenster von 60 Tagen, durchsucht die ganze Künstler-Rangliste und schaltet alle drei Erweiterungen (Features, Mitglieder, Discovery) ein.
| Flag | Default | Wirkung |
|---|---|---|
--days N | 60 | Zeitfenster: Releases der letzten N Tage |
--include-future | aus | Auch schon angekündigte Releases mit Zukunftsdatum aufnehmen |
--include-live-remix | aus | Live-, Remix- und Compilation-Releases nicht herausfiltern (näher an Spotify, mehr Rauschen) |
--top N | alle | Nur die obersten N Künstler der Rangliste prüfen |
--feature-top N | 10 | Gastbeiträge nur für die Top-N Künstler suchen (0 schaltet den Pass ab) |
--member-top N | 25 | Bandmitglieder der Top-N Künstler auf Solo-Releases prüfen (0 = aus) |
--member-min-listeners N | 2000 | Mindest-Last.fm-Hörer, damit ein Mitglied als eigener Solo-Act zählt |
--discovery-seeds N | 30 | Top-N Künstler als Discovery-Seeds (0 schaltet Discovery ab) |
--discovery-limit N | 20 | Ähnliche Künstler, die pro Seed betrachtet werden |
--discovery-min-score F | 0.6 | Mindest-Sim-Score, ab dem ein Discovery-Kandidat aufgenommen wird |
--no-ytm | aus | Den YouTube-Music-Gegencheck samt Direktlinks überspringen |
--no-discovery-ytm | aus | Discovery nur aus Last.fm-Ähnlichkeit, ohne den related-Graph von YouTube Music |
--refresh | aus | Veraltbare MusicBrainz-Caches vor dem Lauf löschen |
Ein Flag ist im Alltag entscheidend: --refresh. Die MusicBrainz-Antworten werden lokal gecacht, und dieser Cache hat kein Verfallsdatum. Ohne --refresh würde der Radar Woche für Woche dieselben zwischengespeicherten Release-Listen sehen und neue Veröffentlichungen schlicht übersehen. Der wöchentliche Lauf setzt es deshalb immer; gelöscht werden dabei nur die veraltbaren Listen (mb_release_groups*, mb_features), während die Zuordnung Künstlername → MusicBrainz-ID erhalten bleibt, weil sie sich praktisch nie ändert. Die übrigen Pfad-Defaults (--artists-json, --beets-dump, --src-dir, --out-dir) zeigen auf die Standardorte im Projekt und müssen selten angefasst werden.
Die beiden nachgelagerten Schritte haben eigene, kleinere Handgriffe. render_release_radar_html.py rendert standardmäßig die jüngste Markdown-Datei zu HTML; mit --md lässt sich gezielt eine bestimmte rendern. send_release_radar.py verschickt die jüngste HTML-Datei, --dry-run baut die Mail nur zusammen, ohne sie abzuschicken, und --to überschreibt den Empfänger für einen Testversand.
Das Setup hinter diesen Beiträgen#
Die Idee#
Ich baue Spotify-Funktionen wie Wrapped, Daylist, Radio, Release Radar und Discovery zu Hause nach, auf Basis meiner eigenen, in hoher Qualität gespeicherten Musiksammlung (FLAC-Dateien) und kostenloser Datenquellen. Ohne Abhängigkeit von Spotify, voll automatisiert, und Songs, die ich (noch) nicht besitze, kommen über YouTube dazu.
Jede dieser Funktionen erzeugt am Ende eine .m3u-Datei — eine simple Playlist-Textdatei, die entweder lokale Dateien oder YouTube-Links auflistet. Abgespielt wird sie im Mac-Player IINA.
KI als Kommandozeile#
Die Funktionen sind als Python-Skripte implementiert und können manuell oder automatisiert (cron) gestartet werden. bequemer ist allerdings ein KI-Tool, welches natürlichsprachige Anfragen interpretieren, ausführen und die erzeugte Playlist direkt abspielen lassen kann:
Erstelle und spiele ein Radio für “Against the Current”
Spiele Death Metal
Spiele 90er Metal/Crossover
Spiele die ersten beiden Alben von Gracie Abrams
Spiele das aktuelle Album von Taylor Swift
Die Datenquellen#
Vier Quellen liefern das Rohmaterial:
| Quelle | Was sie ist | Was ich daraus hole |
|---|---|---|
| Last.fm | Ein Dienst, der jeden abgespielten Song automatisch mitschreibt („Scrobbeln”) | Komplette Hörhistorie, Lieblingssongs („Loved”), wie oft ich was höre, „ähnliche Künstler/Songs”, Genre-Schlagworte, globale Popularität |
| MusicBrainz | Eine offene Musik-Enzyklopädie (wie Wikipedia für Musik) | Erscheinungsdaten neuer Releases, Band-Mitglieder, Genres |
| YouTube Music | Der Streaming-Dienst | (1) Abspielquelle für Songs, die ich nicht lokal habe — das Werkzeug yt-dlp findet die passende YouTube-URL; (2) ein „ähnliche Künstler”-Graph über die Bibliothek ytmusicapi |
| Lokale FLAC-Bibliothek | Meine tatsächlich besessene Musik, verwaltet mit beets (einem Musik-Bibliotheks-Tool) | Was „lokal verfügbar” ist, inkl. Künstler/Album/Jahr |
Dazu kommt eigene Handarbeit: kuratierte Lieblings-Playlists je Künstler oder Album (playlist_data/), eine Liste besuchter und geplanter Konzerte (concerts.json) und eine Einkaufsliste fehlender Musik.
Die Bausteine (Skripte)#
Kleine Python-Programme, jeweils für eine Aufgabe. Grob nach Zweck:
- Profile bilden — welche Künstler/Songs sind mir wichtig?
interesting_artists.py(Künstler-Rangliste),track_plays_snapshot.py,loved_snapshot.py. - Playlists erzeugen (die Spotify-Pendants) —
daylist.py(Mix nach Tageszeit),radio.py(Künstler-Radio),discovery_yt_playlist.py(neue, unbekannte Künstler),on_repeat.py,rediscovery.py. - Entdecken & pflegen —
release_radar.py(neue Veröffentlichungen meiner Künstler, als E-Mail),einkaufsliste.py(was mir noch fehlt). - Rückblicke —
wrapped_month.py(monatliche „Wrapped”-Grafiken),generate_topsongs_csv.py(Jahres-Top-100). - Migration —
match.pyordnet einen Spotify-Playlist-Export den lokalen Dateien zu.
Wann was läuft (Automatik per Cron)#
Cron ist ein Zeitplaner des Betriebssystems: er startet Programme automatisch zu festen Zeiten. Mein Zeitplan:
| Wann | Was | Wozu |
|---|---|---|
| täglich 09:00 | interesting_artists.py | Rangliste „wichtige Künstler” aktualisieren (Basis für Radio, Discovery, Radar) |
| täglich 09:30 | discovery_yt_playlist.py | Playlist mit neuen, noch unbekannten Künstlern |
| täglich 10:00 | on_repeat.py | „On Repeat” & „Repeat Rewind” |
| täglich 11:00 | wrapped_month.py | laufende Monatsrückblick-Grafiken |
| 6× täglich (0,5,8,12,17,21 Uhr) | daylist.py | Playlist passend zur Tageszeit |
| freitags 09:45 | release_radar.py (+ rendern + senden) | E-Mail mit neuen Releases |
| wöchentlich (montags 09:15) | Snapshots + einkaufsliste.py | Spielzahlen festhalten, Einkaufsliste aktualisieren |
| donnerstags 16:00 | Cache-Aufräumen | veraltete Daten löschen, damit sie frisch nachgeladen werden |
Das Radio (radio.py) läuft bewusst nicht automatisch, sondern auf Zuruf, wenn ich ein Radio für einen bestimmten Künstler will.
Caches (warum es schnell und höflich bleibt)#
Abfragen an Last.fm, MusicBrainz und YouTube sind langsam und haben Limits — MusicBrainz erlaubt z.B. nur eine Anfrage pro Sekunde. Deshalb wird jede Antwort lokal zwischengespeichert (ein Cache unter ~/.cache/lastfm-mb/,
aktuell ~150 MB). Die zweite Abfrage derselben Sache ist dann sofort da, und die Dienste werden nicht unnötig belastet.
Grob drei Gruppen: Last.fm (Hörhistorie, ähnliche Künstler, Tags), MusicBrainz (Künstler-Steckbriefe, Releases, Band-Mitglieder) und YouTube Music (ähnliche Künstler, Alben).
Wichtig: Manche Caches dürfen nicht ewig gelten — „ähnliche Künstler” und „neue Releases” sollen aktuell bleiben. Darum werden ausgewählte Bereiche wöchentlich gelöscht (donnerstags) bzw. beim Release Radar bewusst frisch geladen.