- Python 95.7%
- JavaScript 2.9%
- Dockerfile 1.4%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .forgejo/workflows | ||
| music_assistant_archiver | ||
| tests | ||
| tools | ||
| .gitignore | ||
| README.md | ||
| repository.json | ||
Library Archiver → Music Assistant
Vlastní build Music Assistant serveru s přibaleným pluginem Library Archiver: cokoli, co má v MA srdíčko (tracky, celá alba, diskografie umělců, tracky oblíbených playlistů), se zrcadlí jako otagované audio soubory do složky Filesystem providera (typicky SMB share). Nezávisle na tom, odkud se hraje — plugin jde přes běžnou MA stream cestu providera.
Repo je zároveň Home Assistant add-on repository — release se propíše do HA
jako běžný update add-onu. (Repo si drží původní jméno music-assistant-octave
a add-on původní slug music_assistant_octave, protože změna URL i slugu by
v HA znamenala nový add-on s prázdným /data.)
Struktura
repository.json # HA add-on repo marker
music_assistant_archiver/ # 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 plugin do site-packages v image
├── patches/ # build-time patche: bug importu playlistů, „Share link“ v menu
└── archiver/ # samotný plugin
├── manifest.json
├── __init__.py # PluginProvider: konfigurace, eventy, debounce
└── archive.py # výběr „co chci“ z knihovny, stažení, tagování, trash
└── ytdlp/ # music provider: anonymní YouTube přes yt-dlp
├── manifest.json
├── __init__.py # search / get_track / get_artist / get_stream_details
└── helpers.py # čisté funkce (parsování názvu, výběr formátu)
└── share/ # plugin: veřejné odkazy na lokální tracky
├── __init__.py # /share/<token>.<ext> = audio soubor, API share/link
└── helpers.py # token (HMAC), Content-Disposition
tests/test_ytdlp.py # self-check helpers.py
tests/test_share.py # self-check tokenu, hlaviček a patche frontendu
tests/test_archive.py # self-check čistých částí (bez MA serveru)
tools/ma_import_convert.py # Spotify/YT Music CSV → M3U8 pro import do MA
MA načítá providery jen z package adresáře (music_assistant/providers/),
žádný custom-provider dir neexistuje (ověřeno na 2.10.5) — 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.
- Nainstaluj Music Assistant (Archiver). 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 plugin Library Archiver a v jeho nastavení vyber cílovou Filesystem instanci.
Release flow (Forgejo)
- Uprav plugin v
music_assistant_archiver/archiver/. - Bump
versionv config.yaml (schéma<MA verze>.<build>, např.2.10.5.1). - Commit + push.
- 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 sám bumpne +
pushne; HA add-on má auto_update. Minor/major skok workflow schválně
failne — ověř plugin proti novému music_assistant/models/plugin.py a
bumpni ručně.
Plugin — konfigurace
| Volba | Default | Poznámka |
|---|---|---|
archive_provider |
— | instance Filesystem providera (SMB/local/NFS); zrcadlí, co má v MA srdíčko |
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). 0 = navždy |
Provider YouTube (yt-dlp)
Oficiální YouTube Music provider v MA odmítne účet bez YT Music Premium
(User does not have Youtube Music Premium). Tenhle provider žádný účet nemá:
hledá přes ytsearch a streamuje nejlepší audio-only formát (m4a, jinak opus),
URL si tahá yt-dlp v okamžiku přehrání. Vyžaduje add-on YT Music PO Token
Generator (bgutil) – bez PO tokenu YouTube stream nevydá; adresa serveru je
v nastavení providera (default http://127.0.0.1:4416, host_network).
- Každé video je track, kanál je umělec. Název
Umělec - Skladbase dělí,(Official Video)-šum z názvu padá; jinak umělec = jméno kanálu bez- Topic. - Žádná knihovna na straně YouTube (bez účtu není co synchronizovat); srdíčko v MA ho uloží do MA knihovny a Archiver ho stáhne na share jako cokoliv jiného.
- Když YouTube odpoví
Sign in to confirm you're not a bot(blok anonymních streamů z dané IP, hledání jede dál), vlož do nastavení providera cookie: celou hlavičkuCookiez prohlížeče přihlášeného na youtube.com (jakýkoli Google účet, Premium netřeba). Google ji občas otočí, pak vložit znovu. yt-dlp+bgutil-ytdlp-pot-providerse instalují při startu providera (stejně jako v oficiálním), proto přežijí i stárnutí MA image.
Plugin Share links
Odkaz přímo na audio soubor lokálního songu: prohlížeč ho rovnou přehraje (a jde uložit jako „Umělec - Skladba.flac“), komukoli, bez účtu a bez HTML.
- Zdroj pravdy je playlist
Sdílené: každý track v něm má odkaz<External URL>/share/<token>.<přípona>, odebrání z playlistu odkaz zruší. Seznam odkazů je v nastavení pluginu, „Revoke all links“ (advanced) vymění tajný klíč a zneplatní všechny najednou. - „Sdílet“ / „Share link“ v kontextovém menu tracku (patch frontendu, viz níž) track do playlistu přidá a odkaz zkopíruje do schránky; potvrdí to toast frontendu (vue-sonner, patch ho najde v bundlu). Když schránka nejde (plain http, prošlý klik), toast nabídne tlačítko „Kopírovat“.
- Sdílet jde jen track z knihovny, který má soubor na Filesystem provideru. Soubor se posílá tak, jak je (FLAC zůstane FLAC), s podporou Range, takže přehrávač umí přetáčet. Přípona v odkazu je jen pro chat aplikace, které podle ní poznají audio; token platí i bez ní.
- Cache: soubor jde s
Cache-Control: public, max-age=3600, takže opakovaná stažení obslouží Cloudflare a zrušený odkaz (odebrání z playlistu, Revoke all links) hraje z cache nejvýš hodinu. 404 jeno-store, aby cachovaná chyba nepřekryla nový odkaz. - Token = HMAC-SHA256(klíč, id tracku v knihovně), klíč leží v
/data/share.secret(zálohuje se s add-onem). - Ven vystav jen
/share/: v npmplus proxy host na MA:8095s location/share/a vším ostatním zakázaným; v MA Settings → Core → Webserver → External URL nastav tu veřejnou adresu. Bez External URL vedou odkazy na LAN adresu MA. - Patch frontendu (
patches/frontend_share_menu.py): frontend nemá pro pluginy žádný rozšiřovací bod, takže patch vložíshare_menu.jsdoindex.htmla v minifikovaném bundlu necháshowContextMenuForMediaItemposlat menu přes hook. Build nikdy neshodí; když se bundle změní a vzor nesedí, položka v menu prostě chybí a odkazy zůstanou v nastavení pluginu. Text položky je česky nebo anglicky podle jazyka MA (bez nastavení podle prohlížeče); frontend překlady pluginů neumí, proto je slovník přímo v JS.
Implementační poznámky
- Zdroj pravdy je MA knihovna: favorite tracky + tracky favorited alb,
umělců (celá diskografie přes provider mapping umělce) a favorited playlistů.
Custom playlisty bez srdíčka se nestahují. Triggery: event bus
(
MEDIA_ITEM_ADDED/UPDATED— like i editace playlistu tím projdou), start add-onu; vše debounced 10 s, při selhání retry za 15 min. - Klíč záznamu v manifestu
.archive.jsonje id library položky (jeden soubor na nahrávku bez ohledu na providera). Track, který v knihovně ještě není (diskografie umělce), se klíčuje<instance>:<id>, dokud ho index nezařadí. - Stažení =
provider.get_stream_details()→ ffmpeg. HTTP/HLS/encrypted URL si ffmpeg tahá sám (vč.-decryption_key),CUSTOMstream jde do ffmpeg přes stdin. Známé kontejnery se kopírují bit po bitu (-c:a copy: flac/mp3/m4a/ogg/opus/wav/…), PCM a neznámé formáty se kódují do FLAC. Preferuje se mapping deklarovaný jako lossless; zdrojem nikdy není cílový Filesystem provider sám. - Adopce: track, který už má mapping na cílový Filesystem provider a soubor existuje, se jen zapíše do manifestu (file = item_id = relativní cesta). Díky tomu archiv z Octave éry pokračuje bez nového stahování.
- Nikdy nepřepisuj existující soubor: MA váže položku knihovny na soubor, výměna při dalším indexu položku zruší i se srdíčkem (ověřeno 2026-08-20). Stahuje se jen to, co na disku chybí.
- Tagy z MA
Track: title, artist (všichni), album_artist (album → jinak první interpret; Jellyfin podle něj seskupuje), album, track, disc, date, ISRC (na něm stojí párování).cover.jpgvedle souborů přesmass.metadata.get_image_data_for_item. - Unlike = přesun do
.trash/(mtime = čas unlike), nikdy hard delete; ochrana: víc než polovina archivu „nechtěná“ v jednom běhu = přeskočit úklid (nejspíš neúplné čtení knihovny). Max 25 stažení na běh, 2 s pauza. - Po zápisu se spustí sync cílové Filesystem instance — MA pak při přehrávání sám preferuje lokální kopii.
- Patch upstream bugu importu (
patches/builtin_resolve_path_uri.py, aplikuje se při buildu): matcher importovaných playlistů zapisuje spárované URI jen do cesty M3U položky, resolver staví dostupnost jen z#EXTPROV:řádků → spárované tracky zůstávaly šedé. Patch build nikdy neshodí; když upstream blok změní, tiše se přeskočí. - 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).
Jednorázový import ze Spotify / YT Music
- Spotify: exportify.app → export playlistu / Liked Songs jako CSV. YT Music: Google Takeout → YouTube Music → CSV.
python tools/ma_import_convert.py export.csv --name "Spotify Liked"→ vznikne.m3u8.- V MA: Playlists → Import playlist → nahrát
.m3u8, zapnout track matching. - Playlist ❤️ → archiver ho celý postupně stáhne na share.