- Python 99.1%
- Dockerfile 0.9%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Renaming it in the Music Assistant UI used to orphan the list and create a second one on the next share; the id is now cached per configured name, so a UI rename keeps working while changing the setting still points somewhere else. Co-Authored-By: Claude Fable 5 <[email protected]> |
||
| .forgejo/workflows | ||
| music_assistant_octave | ||
| tests | ||
| tools | ||
| .gitignore | ||
| octave-api-reference.md | ||
| README.md | ||
| repository.json | ||
Octave Streaming → Music Assistant
Neoficiální read-only music provider pro Music Assistant (search + browse + přehrávání z veřejného katalogu Octave Streaming, Deezer ID). Volitelně i library sync z web účtu. API reference: octave-api-reference.md.
Repo je zároveň Home Assistant add-on repository — release se propíše do HA jako běžný update add-onu.
Struktura
repository.json # HA add-on repo marker
music_assistant_octave/ # HA add-on (wrapper nad oficiálním MA image)
├── config.yaml # verze add-onu — bump = release
├── build.yaml # pin MA base image (ghcr.io/music-assistant/server:X.Y.Z)
├── Dockerfile # kopíruje provider do site-packages v image
└── octave/ # samotný provider
├── manifest.json
├── __init__.py # MusicProvider implementace
└── parsers.py # Octave/Deezer JSON → MA modely (testovatelné standalone)
tests/test_parsers.py # self-check parserů
MA (i 2.10-dev) načítá providery jen z package adresáře
(music_assistant/providers/), žádný custom-provider dir neexistuje — proto
injektáž přes vlastní add-on postavený FROM oficiálního server image.
Instalace v Home Assistantu
- Settings → Add-ons → Add-on Store → ⋮ → Repositories → přidej URL tohoto Forgejo repa (musí být veřejně klonovatelné, nebo s embedded credentials v URL).
- Nainstaluj Music Assistant (Octave). Build proběhne lokálně na HA hostu.
- Zastav oficiální MA add-on (stejné porty,
host_network) — nesmí běžet oba. - V MA UI přidej provider Octave Streaming.
Data oficiálního add-onu se nepřenáší automaticky (jiný slug = jiný /data) —
detaily v DOCS.md.
Release flow (Forgejo)
- Uprav provider v
music_assistant_octave/octave/. - Bump
versionv config.yaml (schéma<MA verze>.<build>, např.2.9.13.2). - Commit + push (+ volitelně git tag).
- V HA: Add-on Store → ⋮ → Check for updates → u add-onu se objeví Update.
Upgrade MA verze: automaticky — auto-bump.yml
denně porovná upstream stable verzi MA add-onu a patch release (2.9.x) sám
bumpne + pushne; HA add-on má auto_update, takže se update nasadí sám.
Minor/major skok (2.10+) workflow schválně failne — MusicProvider
interface se mezi releasy mění, ověř provider proti novému
music_assistant/models/music_provider.py a bumpni ručně.
Provider — konfigurace
| Volba | Default | Poznámka |
|---|---|---|
base_url |
https://api.octavestreaming.com |
|
quality |
flac |
FLAC 16/44.1; bez lossless masteru fallback na MP3 320. 128 = on-the-fly transcode, bez seeku |
auth_token |
— | Bearer token web účtu (octv_…) → zapne library sync |
sync_interval |
60 s |
poll /api/sync?meta=1; full sync jen při změně verze. 0 = vypnuto |
archive_provider |
— | instance Filesystem providera; zrcadlí, co má v MA srdíčko: tracky, celá alba, diskografie umělců, tracky playlistů (s octave mappingem) — jako otagované soubory (FLAC / MP3 320) do jeho složky + re-index. Bez srdíčka se nestahuje nic (import playlistů sám o sobě stahování nespouští). Vyžaduje token |
archive_path |
— | advanced alternativa: libovolná absolutní cesta místo Filesystem instance |
trash_retention_days |
90 |
advanced: purge .trash/ souborů starších než X dní (od unlike; mtime se při trashnutí přerazí). 0 = navždy |
recognize_playlist |
Recognized |
jméno playlistu pro rozpoznanou hudbu z mobilu; publikuje sdílecí endpoint /octave/recognize (klíč odvozený ze server_id, URL v logu při startu). Prázdné = vypnuto |
storefront |
us |
jen editorial (spotlight) |
Implementační poznámky
- Playback:
GET /api/track/{id}vrací podepsanou URL. Od API updatu 2026-08-21 vyžaduje resolver Bearer token — bez něj přijdegated: truea URL bez podpisu, na kterou/audio/*odpoví 401. Provider proto volá resolver s_auth=Truea bez tokenu hlásí srozumitelnou chybu (přehrávání už není možné v režimu „jen katalog"). Resolvuje se až vget_stream_details, podpisy jsou krátkodobé — nikdy se nepersistují. - Formát z bajtů, ne z labelu: tier
320teče u části katalogu jako MP4/AAC (audio/mp4, magicftyp), jinde pořád MP3; lossless je FLAC. Provider čichne prvních 12 bajtů a podle nich deklaruje kontejner (FLAC / M4A / MP3, jinak bez hintu). Archiver stejně volí příponu.flac/.m4a/.mp3. - Lossless: kvalitu vybírá resolver —
GET /api/track/{id}?quality=losslessvrátí podepsanou URL a v poliqualityohlásí, co skutečně dal. Pozor 1: u skladby bez lossless masteru resolver spadne na MP3 128, což je horší než výchozích 320 — provider to detekuje a doptá se znovu bez parametru. Pozor 2 (živě 2026-08-18): u části tracků resolver ohlásílossless, ale/audio/losslessreálně servíruje MP3 bajty (0xFFFB,audio/mpeg). Tvrdý FLAC codec hint pak shodí ffmpeg („Invalid data") a MA track přeskočí — proto provider u lossless čichne první 4 bajty (range request) a deklaruje formát podle reality; při selhání probe nechá detekci na ffmpeg. Pozor 3 (živě 2026-08-20): ty „lossless" MP3 bajty jsou 128 kbps. U části tracků přitom origin nemá nic lepšího —/audio/320i/audio/losslessvracejí bajt v bajt stejný 128 kbps soubor (ověřeno: id 3127743, 6907156). Provider i archiver proto po zjištění ne-FLAC bajtů zkusí ještě výchozí URL, a archiver do manifestu píše změřenou kvalitu (quality: „lossless" / „320" / „128" z ffprobe) plusclaimed= co na lossless dotaz řekl resolver. Ta lež je přechodná — týž track (id 468232842) vrátil jednou 128 kbps a podruhé skutečný FLAC 939 kbps. Upgrady se přesto nedělají: MA váže položku knihovny na archivovaný soubor, takže jeho výměna při dalším indexu položku zruší i s uživatelovým srdíčkem (živě ověřeno 2026-08-20, 9 skladeb). Archiver proto existující soubor nikdy nepřepisuje a stahuje jen to, co na disku chybí — kvalita v manifestu slouží jen jako poctivá informace. - Latence startu (změřeno 2026-08-18 debug logem): provider dodá
streamdetails za 6–256 ms. Zbytek je mimo něj — audio origin má u nedávno
nehrané skladby TTFB 1,5–5 s (stejně pro lossless i 320), MA přidává pevný
1 s debounce na next/prev a čeká na naplnění bufferu. Mrtvé položky v katalogu
dřív stály 13–33 s čekáním na timeout; teď se poznají podle prázdného
previewa přeskočí okamžitě. - Retry-After je cooldown, ne pauza (jejich vlastní changelog 2026-08):
backend posílá
Retry-After: 900u tracku, který teď nedokáže naservírovat. MA respektuje serverovou hodnotu až do hodiny (MAX_RETRY_AFTER=3600), takže by na 15 minut uspal přehrávač. Provider proto cokoli nadMAX_SERVER_COOLDOWN(30 s) překlápí naResourceTemporarilyUnavailable— track se přeskočí, fronta běží dál. available: false: výpisy (typicky alba) označují nehratelné položky — celé album může být mrtvé (např.507208391Marigold Soundsystem, všech 9 tracků). Parser to promítá doProviderMapping.available, ať MA takový track vůbec nezařadí do fronty.- Měření API:
/api/*odmítáPython-urllibUser-Agent, ale/audio/*naopak blokuje podvržené prohlížečové UA (Cloudflare 403). MA posílá aiohttp UA a projde obojím — při benchmarcích audia nikdy neposílat falešný Chrome UA, výsledky jsou pak nesmyslné. - Library sync: vyžaduje
auth_token; čte/api/sync→octave:library.state(liked playlist, savedAlbums, followedArtists, playlists). Jednosměrné Octave → MA. Near-realtime přes poll?meta=1(jen{version, updatedAt}). - Metadata tracku: API nemá přímý track-metadata endpoint (
/api/track/{id}je resolver,/api/dz/track/{id}už neexistuje) →get_trackjde přescredits → ISRC → /api/track/isrc/{isrc}. Provider si proto pamatuje raw payloady z výpisů (_track_memo) a ten řetězec použije jen při promáchnutí; ISRC se doplní z dlouho cachovaných credits, protože výpisy ho neobsahují. - Cache: dvě úrovně, obě stale-while-revalidate — 30 dní na neměnná data
(track, album, umělec, diskografie) a 1 h na proměnlivá (search, playlisty,
editorial). Throttle 25 req/s + retry/backoff na 429/503, timeout 30 s
(connect 10 s) — mrtvé tracky chytá prázdný
preview, ne timeout, takže pomalý cold origin nesmí shazovat živé skladby. - Browse: zatím jen spotlight track — Apple editorial charts používají Apple ID, ne Deezer, chybí mapovací vrstva.
- Explore/Recommendations (
ProviderFeature.RECOMMENDATIONS): 6 řádků — Tvůj mix (radio ze 3 náhodných MA favorites), #1 dnes (spotlight,/api/spotlight/{sf}— region je path segment, query param API ignoruje), Světové hity (/api/lastfm/global-chart-tracks), Dnešní hity: (Apple chart/api/apple/charts?sf=mapovaný per-song přes/api/search/tracks; fallback/api/lastfm/geo), Stanice pro tebe (virtuální playlistyradio-artist-<id>→/api/artist/{id}/radio, umělci dohledaní přes/api/search/artistsz názvů v MA favorites), Podobné jako <umělec> (/api/similar/artists). Last.fm proxy vrací už namapované Deezer objekty vč. coverů. Texty řádků lokalizované podlemass.metadata.preferred_language(cs/en,REC_STRINGS). Vše přes 1h/30d cache; každý builder selhává samostatně. - Archiv knihovny (
archive.py): zdroj pravdy je MA knihovna — octave tracky s favorite=1 + tracky všech MA playlistů (octave web se přímo nečte, jeho library teče do MA běžným syncem; pozor, provider sync favorite flag v MA NENASTAVUJE). Triggery:set_favoritecallback (lajk v MA UI — vyžadujeFAVORITE_TRACKS_EDITfeaturu), event bus (změny playlistů), změna octave sync verze, start add-onu. Vše debounced. Zrcadlí se do složky Filesystem providera (SMB mount/tmp/<instance_id>žije ve stejném kontejneru). Resolver rozhodne kvalitu (lossless → FLAC, jinak 320; 128 fallback se detekuje a bere se místo něj 320), kontejner se pozná podlefLaCmagic bytes, taguje ffmpeg (-c:a copy, vč. ISRC — na něm stojí párování s Octave trackem), vedle souborů přistanecover.jpg. Stav drží.octave-archive.jsonv kořeni archivu; odebrané tracky jdou do.trash/(nikdy hard delete), ne-lossless soubory se při další změně zkusí upgradnout. Po úspěšném zápisu se spustí sync té Filesystem instance — MA pak při přehrávání sám preferuje lokální lossless kopii. - Patch upstream bugu importu (
patches/builtin_resolve_path_uri.py, aplikuje se při buildu image): matcher importovaných playlistů zapisuje spárované URI jen do cesty M3U položky, ale resolver staví dostupnost výhradně z#EXTPROV:řádků → spárované tracky zůstávaly šedé a playlist hlásil „No playable items found". Patch přidává fallback: cestu s://rozresolvovat přesget_item_by_uri. Patch build nikdy neshodí: když upstream blok změní nebo opraví, tiše se přeskočí (auto-bump běží dál; nejhorší dopad = importy zase zšednou). - AppArmor: add-on přibaluje
apparmor.txt(kopie z oficiálního add-onu). Bez něj Supervisor nasadídocker-defaultprofil, který tiše zaříznemount(2)— SMB provider pak hlásímount error(13)dřív, než odejde jediný paket (žádný CIFS záznam v dmesg, nic v samba logu).
Jednorázový import ze Spotify / YT Music
Pro libovolného MA uživatele, bez API klíčů:
- Spotify: exportify.app → export playlistu / Liked Songs jako CSV. YT Music: Google Takeout → YouTube Music → CSV playlistů.
python tools/ma_import_convert.py export.csv --name "Spotify Liked"→ vznikne.m3u8(sloupce se poznají samy, en i cs hlavičky).- V MA (přihlášen daný uživatel): Playlists → Import playlist → nahrát
.m3u8a zapnout track matching. MA na pozadí dohledá tracky u octave. - Playlist ❤️ → archiver ho celý postupně stáhne na share.
- Rozpoznávání z mobilu (
recognize.py): dynamická route na webserveru MA (mass.webserver.register_dynamic_route, tedy mimo auth — chrání ji klíč odvozený zserver_id+instance_id). Identita před jménem: Deezer odkaz → ID katalogu, MusicBrainz odkaz → ISRC přes MA MusicBrainz provider, holý ISRC →track/isrc/{isrc}; teprve pak skórované hledání (normalizace bez diakritiky/závorek, obě pořadí interpret/název, práhMIN_SCORE). Přidává přesmass.music.playlists.add_playlist_tracks— běžíme uvnitř MA, takže žádný MA login není potřeba (WS API vyžaduje auth i z localhostu).
Test
pip install music-assistant-models==1.1.129.post1 aiohttp
python tests/test_parsers.py
python tests/test_archive.py
python tests/test_recognize.py
python tests/test_import_convert.py
Omezení
- Reverse-engineered neoficiální API — může se kdykoli změnit/rozbít; použití mimo oficiální klient je pravděpodobně proti ToS služby.
- Library sync je jednosměrný (Octave → MA) a vyžaduje token web účtu; lajk udělaný v MA se do Octave nepropíše.
Repo: https://git.xn--la-mia8p.eu/ladab/music-assistant-octave
(punycode pro git.láďa.eu — do HA add-on store zadávej punycode formu,
git/curl si IDN převádí špatně).