- Python 47.1%
- JavaScript 38.8%
- CSS 9.2%
- HTML 4.2%
- Dockerfile 0.4%
- Other 0.3%
| deploy | ||
| static | ||
| tests | ||
| vendor/mixengine | ||
| .containerignore | ||
| .gitignore | ||
| airsonic.py | ||
| airsonic_web.py | ||
| app.py | ||
| config.env.beispiel | ||
| Containerfile | ||
| COPYRIGHT | ||
| diagnose.py | ||
| LICENSE | ||
| mix_export.py | ||
| mix_routes.py | ||
| README.md | ||
| requirements.txt | ||
| start.sh | ||
Playlist-Manager für Airsonic
Arbeitsoberfläche zum Sortieren und Pflegen von Playlists, die über die Subsonic-API gegen eine bestehende Airsonic-Instanz arbeitet. Kein Player: Vorhören dient dem Sortieren, nicht dem Zuhören.
Starten
Die Mix-Engine liegt im Submodul vendor/mixengine (Repo mumblemixer,
gemeinsam mit dem Bot-Pult des Mumblebots genutzt) — beim ersten Auschecken bzw. nach einem
git pull einmal git submodule update --init laufen lassen, sonst ist der
Ordner leer und mix_routes.py findet dj/mixer nicht.
git submodule update --init
python3 -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt
cp config.env.beispiel config.env # und ausfüllen
./start.sh
Danach http://127.0.0.1:42200 öffnen. PM_HOST=0.0.0.0 in config.env
macht den Manager im Netz erreichbar — er hat keine eigene Anmeldung, wer
ihn erreicht, kann also Playlists ändern.
Als Podman-Container
git submodule update --init auch hier zuerst (Podman kopiert nur, was im
Arbeitsverzeichnis liegt):
git submodule update --init
podman build -t playlist-manager .
mkdir -p ~/.config/containers/systemd
cp deploy/playlist-manager.container ~/.config/containers/systemd/
systemctl --user daemon-reload
systemctl --user start playlist-manager
Der Manager lauscht dann auf http://127.0.0.1:42200. Die Zugangsdaten
trägst du beim ersten Aufruf in der Oberfläche ein; Übernehmen legt sie
im Volume playlist-manager-data ab (/data/config.env), dort überstehen
sie Neustarts und neue Images. Nach einem Update: Image neu bauen,
systemctl --user restart playlist-manager. Damit der Dienst ohne
Anmeldung startet (etwa auf einem Server): loginctl enable-linger.
Verbindung einrichten und prüfen
Die Adresse lässt sich auch in der Oberfläche eintragen: der Knopf oben
rechts zeigt die aktuelle Instanz und mit einem farbigen Punkt, ob sie
steht. Prüfen testet, ohne etwas zu ändern; Übernehmen schreibt die
Angaben nach config.env (Dateirechte 600, das Passwort steht im Klartext
darin). Steht beim Start keine Verbindung, geht der Dialog von selbst auf.
Der Test geht sieben Schritte durch und endet mit dem, worauf es ankommt: ob diese Instanz die Originaldatei mit exakter Länge und echten Byte-Ranges herausgibt — nur dann springt die Leiste genau. Fehlt dem Benutzer das Download-Recht, sagt der Bericht das mitsamt der Folge; das Vorhören funktioniert dann trotzdem, über den umgewandelten Stream. Der Unterschied ist messbar: derselbe Titel wird dabei als 281,6 s gemeldet statt der tatsächlichen 279,7 s, und um diese Differenz verrutscht das Springen.
Die API-Fassung wird ausgehandelt: der Manager fragt 1.16.1 an, und wenn die Instanz älter ist, nennt sie in ihrer Fehlerantwort ihre eigene Version — die wird dann verwendet.
Bedienung
Links die Liste, an der gearbeitet wird, rechts wahlweise Suche, Stöbern oder eine zweite Liste. Änderungen sammeln sich an und gehen erst beim Speichern zum Server — das Ungespeichert-Zeichen oben zeigt es an.
Gestöbert wird über die Ordner, nicht über ID3-Tags: Musikordner → A–Z → Unterordner → Titel, auch die Treffer der Suche („Ordner“, „Alben“) und „Neueste Alben“ sind Verzeichnisse. Die Brotkrumen zeigen immer den ganzen Pfad, das Feld „Hier filtern…“ grenzt die aktuelle Ansicht ein. Nur „Genres“ hängt naturgemäß an den Tags.
| Griff | Wirkung |
|---|---|
| ↑ / ↓ | Cursor in der aktiven Liste bewegen, die Auswahl folgt |
| Umschalt+↑ / ↓ | Auswahl erweitern |
| ← / → | Zwischen linker und rechter Seite wechseln |
| Umschalt+← / → | Im laufenden Titel 10 s zurück/vor |
| Leertaste halten + ↑ / ↓ | Links: Auswahl verschieben |
| Leertaste halten + ← | Rechts: Auswahl hinter den linken Cursor übernehmen, danach links weiterschieben |
| Leertaste halten + → | Links: Auswahl aus der Liste entfernen |
| Leertaste kurz | Wiedergabe anhalten/fortsetzen; läuft nichts, den Titel unter dem Cursor |
| Umschalt+Leertaste | Titel unter dem Cursor abspielen |
| Enter | Ordner öffnen, Titel abspielen |
| Backspace (beim Stöbern) | Eine Ordnerebene hoch |
| ↓ in einem Such- oder Filterfeld | In die Liste darunter springen |
| Ziehen | Titel umsortieren; aus der rechten Spalte an eine bestimmte Stelle einfügen |
| Klick, Strg+Klick, Umschalt+Klick | Einzeln, zusätzlich, Bereich auswählen |
| Alt+↑ / Alt+↓ | Auswahl um eine Position verschieben |
| Doppelklick links | Vorhören |
| Doppelklick rechts | Übernehmen |
| Entf | Auswahl aus der Liste entfernen |
| Strg+Z | Rückgängig |
| Strg+S | Speichern |
| Strg+F | In die Suche springen |
| Strg+A | Alles auswählen |
| Esc | Auswahl aufheben |
In der Abspielleiste lässt sich frei springen — klicken oder den Regler ziehen; mit Pfeiltasten in 5-Sekunden-Schritten, mit Umschalt in 30er. Ist Weiterspielen an (Schalter in der Abspielleiste), folgt am Ende der nächste Titel derselben Liste — in ihrer aktuellen, auch ungespeicherten Reihenfolge. Er wird 30 Sekunden vorher in einem zweiten Player vorgeladen, so ist der Übergang ohne Ladepause zu hören.
Mix
Mix (links oben) blendet zwischen den Titeln Übergangszeilen ein. Ein Klick klappt den Übergang auf: wo Titel A endet, wo Titel B einsetzt, wie lang überblendet wird und mit welcher Kurve. Nicht Gesetztes misst der Server — die Stille am Anfang und Ende jedes Titels fällt dann weg. ▶ rendert genau diesen Übergang auf dem Server (dieselbe Kette wie der fertige Mix) und spielt ihn ab. Übergänge hängen am Titelpaar, nicht an der Position: nach dem Umsortieren gelten sie weiter, wo dieselben zwei Titel aufeinanderfolgen.
Mix rendern… macht daraus MP3-Dateien (320 kbit/s), an den Titelgrenzen
geschnitten — jede Datei endet mit dem Übergang in die nächste. Lückenlos
hintereinander gespielt klingt das wie der Mix, Überspringen landet direkt
hinter einem Übergang. Die Dateien gehen über die Upload-Seite von Airsonic
(Web-Anmeldung, Upload-Recht nötig) in …/Mixe/<Name> neben dem Upload-Ordner;
danach liest Airsonic ein und die Playlist „Mix: “ entsteht (oder wird
ersetzt). Lautheit wird dabei auf ein gemeinsames Maß gebracht (EBU R128),
und jede Datei bekommt das Cover ihres Originals (so, wie Airsonic es zeigt —
eingebettet oder cover.jpg im Ordner; 500 px, damit der Tag klein bleibt).
Der Benutzer, unter dem Airsonic läuft, muss in Mixe schreiben dürfen;
scheitert das Hochladen, steht Airsonics eigene Meldung im Dialog. Ist der Mix
fertig, öffnet „Mix-Playlist öffnen“ ihn gleich.
Listen „Mix: …“ spielt auch der Player des Managers lückenlos: Er lädt jeden
Titel ganz, dekodiert ihn mit Web Audio (Chrome schneidet die MP3-Polsterung
dabei exakt ab) und plant den nächsten auf das Sample genau ans Ende des
laufenden — die zwei <audio>-Elemente der anderen Listen lassen an der Naht
gut 10 ms Stille. Dafür startet ein Titel erst, wenn er ganz geladen ist
(„lädt…“ in der Leiste), und der Browser hält den laufenden und den nächsten
dekodiert im Speicher (etwa 20 MB je Minute). Scheitert das, spielt der
normale Player.
Mix durchhören: Ist der Mix-Modus an, spielt ▶ an einem Titel der linken
Liste die Liste so, wie die Übergänge gerade eingestellt sind — ohne Rendern
und ohne Airsonic. Der Server rechnet Stück für Stück (Titel ab Ende des
einlaufenden bis Ende des auslaufenden Übergangs, mit der schnellen
Keylock-Engine, 1–3 s je Stück, danach aus dem Zwischenspeicher in
mixdata/segments/), der Player hängt die Stücke lückenlos aneinander
(„gemischt“ in der Leiste). Ändert man währenddessen einen Übergang, wird das
laufende Stück neu gerechnet und an derselben Stelle weitergespielt.
Übergänge speichern sich übrigens selbst, kurz nach jeder Änderung (Hinweis
neben „Mix rendern…“); der Knopf „Speichern“ betrifft nur die Playlist.
Mix wegräumen… erscheint bei Playlists „Mix: …“ und löscht die
gerenderten Dateien samt Ordner und die Playlist. Die Liste, aus der der Mix
entstand, und ihre Übergänge bleiben — neu rendern geht jederzeit. Dateien
löschen kann die Subsonic-API nicht; das macht deleteMixFolder aus dem
Fork airsonic-advanced (ab 5ffbeb4f), der nur flache Ordner direkt unter einem
Ordner „Mixe“ annimmt. Andere Instanzen melden beim Wegräumen, dass sie den
Aufruf nicht kennen; alles andere geht mit jeder Airsonic-Instanz.
Warum MP3: gemessen mit dem Player von Ultrasonic (Media3/ExoPlayer) sind MP3 mit LAME-Header und unumgewandeltes FLAC lückenlos, Opus nicht (ExoPlayer schneidet dessen Auffüllung am Ende nicht ab), und von Airsonic unterwegs umgewandeltes FLAC schon gar nicht.
Der Übergangs-Editor
Aufgeklappt ist ein Übergang eine kleine Zeitleiste wie in Audacity oder GarageBand (Vorbild: der Transition-Editor von DJ.Studio): A und B liegen als Clips übereinander, B beginnt am Nullpunkt, der Rahmen ist die Überblendung. Über jeder Welle liegt ihre Lautstärke als Linie, darunter die Tempo-Spur.
| Tun | Wirkung |
|---|---|
| Clip ziehen | A gegen B verschieben — mehr oder weniger Überlappung |
| Kante ziehen (Ende von A, Anfang von B) | abschneiden |
| Alt beim Ziehen | ohne Magnet |
| Lautstärke-Linie anklicken / Quadrat ziehen | Punkt setzen / Zeit und Pegel ändern (Umschalt: nur Pegel) |
| Tempo-Linie anklicken / Kreis ziehen | Punkt setzen / Zeit und Tempo ändern (Umschalt: nur Tempo) |
| Doppelklick auf einen Punkt | löschen |
| Im Lineal ziehen | Bereich markieren, läuft als Schleife |
| Klick | Abspielkopf setzen (in einer Spur: gleich abspielen) |
| Mausrad / Umschalt+Mausrad | zoomen / verschieben |
| Leertaste · ←/→ · Z · L | Abspielen · ein Takt (Umschalt: vier) · vor den Übergang · Schleife |
| A / B | A endet / B beginnt am Abspielkopf |
| Tempo-Punkt anklicken | auswählen: Zeit und BPM darunter genau eintippen; ↑/↓ ±0,01 BPM (Umschalt ±0,1), Entf löscht; Strg beim Ziehen = zehnmal feiner |
| Strg+Z / Strg+Umschalt+Z | Änderung an einem Übergang zurücknehmen / wiederholen (auch Knopf „↶ Rückgängig“) |
Magnet: Beim Ziehen rastet ein Clip nur ein, wenn eine seiner Taktlinien (oder Schläge, je nach „Magnet beim Ziehen“) höchstens 10 Pixel neben einer des anderen liegt; die gestrichelte Linie mit „Takt“ zeigt, wo. Sonst bleibt er genau da, wo man ihn loslässt — was von Hand gesetzt ist, verschiebt der Server nicht mehr; nur Automatisches rastet von selbst ein.
Lautstärke: Solange nichts gezeichnet ist, zeigt die Linie die gewählte Blendkurve (gleiche Leistung, linear, S-Kurve) mit Griffen an Anfang und Ende. Wer einen Griff zieht oder auf die Linie klickt, macht daraus eigene Punkte; ab da gilt die gezeichnete Linie statt der Kurve („Lautstärke zurücksetzen“ im Feld darunter holt die Kurve zurück). A spielt vor dem ersten Punkt voll, B nach dem letzten.
Tempo: Punkte auf der Tempo-Linie bilden eine Hüllkurve je Clip — etwa zwei Punkte, damit A nur in den letzten 7 Sekunden auf das Tempo von B gleitet. Vor dem ersten Punkt spielt A in seinem eigenen Tempo, B nach seinem letzten. „Tempo gekoppelt“ lässt B im Übergang der Kurve von A folgen. Ist das Tempo aus (die Automatik gleicht nur an, wenn beide Raster sicher erkannt sind), zeigt die Spur die erkannten Tempi trotzdem als Linien; ein Klick darauf setzt einen Punkt und schaltet auf Keylock.
Vorhören rechnet den Übergang samt Rampen auf dem Server (FLAC, mit der schnellen Keylock-Engine — der fertige Mix nimmt die feine); nach jeder Änderung wird neu gerechnet und an derselben Stelle weitergespielt. „Tempo gekoppelt“ hält A und B im Übergang im selben Tempo.
Takt und Tempo
Beim ersten Öffnen eines Übergangs misst der Server jeden Titel: Tempo (BPM) und Beatgrid — konstantes Tempo wie bei Rekordbox, die Takt-Eins aus den Bass-Anschlägen.
- Tempo angleichen: Keylock (Tonhöhe bleibt, rubberband) oder Vinyl (Tonhöhe geht mit, wie am Plattenspieler) — pro Übergang. Automatisch wird Keylock gewählt, wenn beide Raster sicher sind und die Tempi höchstens 8 % auseinanderliegen (Halb-/Doppeltempo zählt mit), dann zunächst in der Mitte zwischen beiden. Das Tempo je Clip ist frei (Punkte in der Tempo-Spur, höchstens ±40 %); ohne eigene Punkte gleitet A vorher hinein und B danach wieder heraus. Das Feld „Tempo des Titels“ ist dagegen die Korrektur der Erkennung.
- Überblendung in Schlägen (4–64), sobald das Tempo angeglichen wird; Einrasten legt den Überblendbeginn in A und den Einstieg von B auf Takt oder Schlag — dann liegen die Schläge deckungsgleich übereinander.
- Bass tauschen: A behält den Bass bis zur Mitte, dann übernimmt B (Weiche bei 180 Hz); die Höhen folgen der Kurve. Standard bei angeglichenem Tempo.
- A: Hochpass raus / B: Tiefpass rein: Filterfahrten über die Überlappung.
Das erkannte Raster lässt sich pro Titel korrigieren: BPM eintippen, ½ / ×2, Takt-Eins einen Schlag früher/später (◂ 1 / 1 ▸), fein um 10 ms (‹ ›); „Erkannt“ verwirft die Korrektur. Sie gilt für den Song, also in allen Mixen. Gemessen an synthetischen Spuren: Tempo auf ±0,01 BPM, Raster auf ±3 ms, Schläge nach Keylock/Vinyl auf ±1 ms. Bei Musik ohne klaren Schlag (Klavier) gilt ein Raster als „unsicher“ und es wird nicht von selbst angeglichen.
Originale und Messwerte liegen im Datenordner neben config.env unter
mixdata/ (höchstens 5 GB Originale, älteste fliegen zuerst; Korrekturen in
grids.json). Braucht ffmpeg mit libmp3lame und rubberband (CLI, ab 3.0);
das Container-Image bringt beides mit.
Wie es mit Airsonic redet
Drei Eigenheiten der API bestimmen den Aufbau.
Umsortieren gibt es nicht. Die Subsonic-API kann Titel nur anhängen oder
per Index entfernen. createPlaylist mit playlistId und einer
songId-Folge ruft serverseitig setFilesInPlaylist auf und ersetzt damit
die ganze Liste samt Reihenfolge — darüber laufen hier alle Änderungen.
Deshalb wird auch gesammelt statt sofort geschrieben: jede Einzeländerung
wäre ein vollständiges Neuschreiben.
Nicht abrufbare Titel fehlen stillschweigend. getPlaylist filtert alle
Einträge weg, deren Datei gerade nicht da ist (nicht eingehängt, noch nicht
eingelesen). Wer eine solche Liste ausliest und zurückschreibt, löscht diese
Einträge endgültig. Der Manager erkennt das an der Differenz zwischen dem
ungefilterten songCount und der Zahl gelieferter Einträge, warnt sichtbar
und verweigert das Speichern, bis man den Verlust ausdrücklich bestätigt.
Vorhören läuft nicht über stream. download.view liefert die
Originaldatei mit exakter Länge und echten Byte-Ranges — nur so springt die
Leiste im Browser genau; bei stream.view mit Transcodierung ist die Länge
nur geschätzt und großzügig gepolstert. Außerdem zählt stream bei jedem
Aufruf den Play-Count hoch, download nicht: Sich durch hunderte Titel zu
hören, verfälscht so nicht „Meistgespielt" und „Zuletzt gespielt".
Kann der Browser das Originalformat nicht abspielen, weicht der Manager selbsttätig auf den umgewandelten Stream aus und schreibt „umgewandelt" in die Abspielleiste — dann ist das Springen ungenau.
Alle Aufrufe laufen über das eigene Backend, weil Airsonic keine
CORS-Header setzt und die Zugangsdaten so nicht im Browser landen.
Schreibende Aufrufe gehen als POST auf URLs mit .view-Endung: lange
songId-Listen sprengen als GET das Headerlimit, und der CSRF-Schutz
lässt nur .view durch.
Grenzen
- Vorhören lädt die Originaldatei. Bei hochauflösendem FLAC sind das schnell 80 MB und mehr — im lokalen Netz kein Thema, über eine dünne Leitung schon.
- Beim Anlegen einer Liste wird sie über ihren Namen wiedergefunden, weil
createPlaylistkeine ID zurückgibt. Bei mehreren gleichnamigen Listen gewinnt die zuletzt angelegte. - Titel, die Airsonic nicht herausgibt, kann auch dieser Manager nicht anzeigen oder erhalten — nur davor warnen.