Distilled from tonight's session building SUB/WAVE anchor-playlist shows by hand: a CLI (search/artist/album/playlist get/create/replace/ add/remove) plus a playlist-audit command that flags accidental full-album dumps, replacing the one-off curl/Python snippets used throughout. SKILL.md documents the gotchas hit along the way (case-sensitive artist matching, deluxe-reissue duplicate tracks, createPlaylist's full-replace semantics) and this session's curation conventions (15-20 tracks/artist, prefer playlistStrict). Claude-Session: https://claude.ai/code/session_01L7Rwa6guD5wK8F8tWQwcJX
85 lines
4.6 KiB
Markdown
85 lines
4.6 KiB
Markdown
---
|
||
name: navidrome
|
||
description: Query and edit the Navidrome music library/playlists (search artists/albums, inspect or build playlists, audit for accidental full-album dumps). Use for any SUB/WAVE anchor-playlist curation work or general Navidrome library lookups.
|
||
---
|
||
|
||
# Navidrome (Subsonic API)
|
||
|
||
A CLI wrapper (`navidrome_api.py`, in this skill's directory) around Navidrome's
|
||
Subsonic API, distilled from a long session of building SUB/WAVE anchor-playlist
|
||
shows by hand. Use it instead of writing one-off `curl`/Python snippets — every
|
||
gotcha below was hit at least once doing it the ad-hoc way.
|
||
|
||
## Setup
|
||
|
||
Auth/URL come from `/srv/subwave/state/setup-config.json` (world-readable),
|
||
already pointed at `navidrome:4533` — the script rewrites that host to
|
||
`localhost` for you. No flags needed for auth.
|
||
|
||
Run it as: `python3 .claude/skills/navidrome/navidrome_api.py <command> ...`
|
||
|
||
## Commands
|
||
|
||
```
|
||
search <query> [--artists N] [--albums N] [--songs N] # raw multi-type search
|
||
artist <name> # albums + track counts for one artist
|
||
album <artist> <album> # track ids for one album
|
||
playlists # list all playlists (id, count, name)
|
||
playlist get <id> # dump entries
|
||
playlist create <name> --ids id1,id2,... # new playlist
|
||
playlist replace <id> --ids id1,id2,... # FULL REPLACE of contents
|
||
playlist add <id> --ids id1,id2,... # append in place
|
||
playlist remove <id> --ids id1,id2,... # remove by id, in place
|
||
playlist audit <id> [--min-size N] # full-album-dump check (see below)
|
||
```
|
||
|
||
## Gotchas learned the hard way
|
||
|
||
- **Artist name casing is inconsistent and case-sensitive matching silently
|
||
drops real hits.** `eels` is lowercase, `CAKE` is uppercase, most others are
|
||
title-case. A naive `artist["name"] == "Eels"` filter returns "not found" even
|
||
though the artist is right there. `artist`/`album` in this script already do
|
||
case-insensitive exact matching — don't re-derive this bug with a fresh `curl`.
|
||
|
||
- **An artist with 0 albums from `getArtist` isn't necessarily absent** — it
|
||
may only have loose tracks scattered across compilations (soundtrack/box-set
|
||
albums credited to "Various Artists"). Fall back to `search3` with a high
|
||
`songCount` and filter by exact artist name (the `artist` command does this
|
||
automatically when it finds zero grouped albums).
|
||
|
||
- **Reissues/deluxe editions duplicate the base album's tracks under a
|
||
different album name** (`Doolittle` vs `Doolittle 25`, `Bricks Are Heavy` vs
|
||
a live/remix bonus disc, etc). When curating, pick tracks from ONE edition —
|
||
check `album` output for suspiciously large counts before assuming it's all
|
||
distinct songs.
|
||
|
||
- **`createPlaylist` with `playlistId` set REPLACES the entire contents** —
|
||
it is not additive. Use `playlist replace` only when you intend to overwrite
|
||
everything (e.g., rebuilding after a curation pass). Use `playlist add` /
|
||
`playlist remove` for incremental edits to an existing playlist.
|
||
|
||
- **No native "remove by id" in the Subsonic API** — `playlist remove` here
|
||
works by reading the current entries, filtering out the unwanted ids in
|
||
Python, then doing a full `replace`. This is safe (order-preserving for
|
||
everything you keep) but means a remove is really a replace under the hood.
|
||
|
||
- **`playlist audit`'s "FULL ALBUM" flag is a signal, not a verdict.** A
|
||
genuinely short album (say, 10 tracks) will always show `10/10` with nothing
|
||
left to trim, and an artist you were explicitly told to go "heavy" on is
|
||
fine full. Use it to know where to *look*, then use judgment (or ask) before
|
||
trimming.
|
||
|
||
## Curation conventions established this session
|
||
|
||
- Default to **~15–20 tracks per artist** for an anchor playlist, not a full
|
||
discography — unless the user explicitly asks for heavier coverage of a
|
||
specific artist ("heavy Nirvana", "triple that, focusing on XTC").
|
||
- Prefer **`playlistStrict: true`** on SUB/WAVE shows with a pinned anchor
|
||
playlist. Soft-anchor (`playlistStrict: false`) was tried and rolled back
|
||
station-wide after live testing showed the playlist rarely won picks over
|
||
the genre/mood fallback pool, and the fallback's unfiltered "explore" source
|
||
could drift the show off-genre (see `subwave/bug-report-soft-anchor-drift.md`).
|
||
- Cross-show duplicate tracks are fine for these anchor-genre shows (the
|
||
no-duplicate rule only applies to the historical radio-chart recreation
|
||
shows) — don't spend time deduplicating against other playlists unless asked.
|