music: index artist discographies (album → release type) for the app
Each Albums/<Artist>/_discography.md (author-maintained source of truth, never modified) is compiled into a per-artist discography.json in the cache = album folder → normalized release type (Studio/Live/Compilation/Single/EP/…), so the player can split an artist's album list into sections. - indexer.ts: parse the md table, normalize the Type (EP?→EP, Compilation (VA)→ Compilation, …), write discography.json. The artist folder's `v` now includes _discography.md so regenerating it re-syncs just that small JSON (isolated from the albums' meta/cover). Manifest gains `disco: true` on such entries. Also fixed the skip check to require all expected outputs to exist, so artist/ cover-only folders no longer rebuild every run. New `discographies` counter. - sidecar: GET /discography?path=<artist rel> (ETag/304), documented in the contract header. - MUSIC_API.md: §2.4 + manifest disco flag + resync algorithm updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+38
-5
@@ -64,12 +64,15 @@ GET /api/music/manifest
|
||||
"generatedAt": 1785034701973, // ms; when the index was last built
|
||||
"albums": {
|
||||
"Albums/AC-DC/[1980] Back in Black": { "v": "50856380f1ca8f9", "cover": true, "tracks": 10 },
|
||||
"DJ Sets/Dave Clarke": { "v": "a1b2c3d4e5f6a7b", "cover": false, "tracks": 3 }
|
||||
"DJ Sets/Dave Clarke": { "v": "a1b2c3d4e5f6a7b", "cover": false, "tracks": 3 },
|
||||
"Albums/AC-DC": { "v": "c0ffee1234567890", "cover": true, "tracks": 0, "disco": true }
|
||||
// …
|
||||
}
|
||||
}
|
||||
```
|
||||
`404` if the index has never been built (see §3).
|
||||
`404` if the index has never been built (see §3). Entries with **`tracks: 0`** are container folders (e.g. an
|
||||
**artist** folder). An entry with **`disco: true`** is an artist folder that has a discography — fetch its
|
||||
grouping via `/discography` (§2.4).
|
||||
|
||||
### 2.2 Album metadata
|
||||
|
||||
@@ -107,6 +110,36 @@ GET /api/music/cover?path=<rel>
|
||||
Compressed JPEG (≤600px on the long edge, ~30–80 KB). Sends `ETag: <v>`; `If-None-Match: <v>` → `304`.
|
||||
Only meaningful when the manifest entry has `"cover": true`.
|
||||
|
||||
### 2.4 Discography (artist album grouping)
|
||||
|
||||
For artist folders (manifest entry with `"disco": true`), this returns a map of **album folder → release
|
||||
type**, so the player can split an artist's album list into sections (Studio, Live, Compilation, Single, EP…).
|
||||
|
||||
```
|
||||
GET /api/music/discography?path=<artist rel> e.g. path=Albums/AC-DC
|
||||
```
|
||||
Sends `ETag: <v>`; `If-None-Match: <v>` → `304`.
|
||||
```jsonc
|
||||
{
|
||||
"artist": "Anthrax",
|
||||
"albums": {
|
||||
"[1984] Fistful Of Metal": "Studio",
|
||||
"[1985] Armed And Dangerous": "EP",
|
||||
"[1994] The Island Years": "Live",
|
||||
"[1991] Attack Of The Killer B's": "Compilation"
|
||||
// …
|
||||
}
|
||||
}
|
||||
```
|
||||
- Keys are **album folder names** (`[year] title`) — they map 1:1 to the artist's album folders, i.e. the
|
||||
last path segment of that album's manifest `<rel>`. Group the artist's albums by looking each up here.
|
||||
- **Types** are a normalized set: `Studio`, `Live`, `Compilation`, `Single`, `EP`, `Soundtrack`, `Remix`,
|
||||
`DJ-Mix`, `Demo`, `Mixtape`, `Bootleg`, `Other` (unknown values pass through as-is). The player defines
|
||||
section order.
|
||||
- An album folder **not present** here has no classification → put it in an "Other"/uncategorized section.
|
||||
- Source of truth is each artist's `_discography.md` (author-maintained); this JSON is derived from it and
|
||||
re-generated whenever that file changes (its `v` bumps independently of the albums' `meta`/`cover`).
|
||||
|
||||
---
|
||||
|
||||
## 3. Building / refreshing the index
|
||||
@@ -127,7 +160,7 @@ GET /api/music/reindex/status → IndexStatus snapshot
|
||||
"running": true,
|
||||
"startedAt": 1785034701973, "finishedAt": null,
|
||||
"foldersScanned": 45, "albumsBuilt": 12, "albumsSkipped": 3,
|
||||
"tracksIndexed": 320, "coversSaved": 12,
|
||||
"tracksIndexed": 320, "coversSaved": 12, "discographies": 3,
|
||||
"currentPath": "Albums/AC-DC/[1980] Back in Black",
|
||||
"error": null
|
||||
}
|
||||
@@ -153,7 +186,7 @@ data: {"albums":15,"built":12,"skipped":3,"foldersScanned":45,"tracksIndexed":32
|
||||
`IndexReport` (the `done` payload):
|
||||
```jsonc
|
||||
{ "albums": 15, "built": 12, "skipped": 3, "foldersScanned": 45,
|
||||
"tracksIndexed": 320, "coversSaved": 12, "elapsedSec": 37.2, "error": null }
|
||||
"tracksIndexed": 320, "coversSaved": 12, "discographies": 3, "elapsedSec": 37.2, "error": null }
|
||||
```
|
||||
|
||||
> First build of a large library takes a few minutes; re-runs are near-instant (unchanged albums skip via `v`).
|
||||
@@ -167,7 +200,7 @@ Keep the last `manifest.albums` you synced. On resync:
|
||||
1. `GET /api/music/manifest`.
|
||||
2. For each `<rel>` in the new manifest:
|
||||
- **new**, or **`v` differs** from your stored copy → fetch `GET /meta?path=<rel>` (+ `GET /cover?path=<rel>`
|
||||
if `cover:true`); store them under your local `<rel>/`.
|
||||
if `cover:true`, + `GET /discography?path=<rel>` if `disco:true`); store them under your local `<rel>/`.
|
||||
- **`v` unchanged** → **skip** (no download).
|
||||
3. For each `<rel>` you have locally that's **absent** from the new manifest → delete it.
|
||||
4. Save the new manifest as your baseline.
|
||||
|
||||
Reference in New Issue
Block a user