From b3a4da4b97eadf80ac3c79d0f14e55bd6c8e6d26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Mon, 27 Jul 2026 14:51:58 +0000 Subject: [PATCH] music indexer: index track lyrics (.lrc/.txt sidecars + embedded) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each track's lyrics are resolved and cached at cache//lyrics/., with the format recorded as `lyrics: 'lrc'|'txt'` on the meta.tracks entry. Precedence: external ".lrc" > external ".txt" > embedded tag (lyrics / lyrics- / unsyncedlyrics — ffprobe now reads all format tags). Content that contains [mm:ss] lines is stored as lrc even from a .txt/embedded source. Only track-matching sidecars affect the version signature (a stray notes.txt is ignored). Lyrics dir is wiped+regenerated per rebuild; new `lyricsIndexed` counter. Served by GET /api/music/lyrics?path=&file= (text/plain + X-Lyrics-Format header, ETag=, 304, 404 when none). Verified end-to-end: external .lrc wins over embedded; embedded → plain txt; unmatched .txt ignored. MUSIC_API.md documents the field + endpoint. Co-Authored-By: Claude Opus 4.8 --- MUSIC_API.md | 18 ++++- src/servers/sidecar/music/index.ts | 20 +++++ src/servers/sidecar/music/indexer.ts | 108 +++++++++++++++++++++++---- 3 files changed, 129 insertions(+), 17 deletions(-) diff --git a/MUSIC_API.md b/MUSIC_API.md index f45dc94a..eb941bd1 100644 --- a/MUSIC_API.md +++ b/MUSIC_API.md @@ -96,7 +96,8 @@ Returns the album's `meta.json`. Sends `ETag: `; a request with `If-None-Matc "album": "Back in Black", "track": "1", "year": "1980", - "durationSec": 312 + "durationSec": 312, + "lyrics": "lrc" // present if lyrics exist: "lrc" = synced, "txt" = plain (see §2.3.2) } // … ], @@ -134,6 +135,19 @@ A compressed frame grab for a video (≤600px, same treatment as covers), taken the video's filename within `` (URL-encode it). Sends `ETag: `; `If-None-Match: ` → `304`; `404` when the video has no poster. Only request it when that video's `meta.videos[]` entry has a `poster` field. +#### 2.3.2 Lyrics + +``` +GET /api/music/lyrics?path=&file= +``` +Plain-text body of the track's lyrics; the `X-Lyrics-Format` header is `lrc` (synced, `[mm:ss.xx]`-timestamped) +or `txt` (plain). Sends `ETag: `; `If-None-Match: ` → `304`; `404` when the track has no lyrics. Only +request it when that track's `meta.tracks[]` entry has a `lyrics` field (`"lrc"`/`"txt"`). + +Sources, in precedence order (indexed at build time): an external **`.lrc`** > external +**`.txt`** > **embedded** lyrics in the audio tags (`lyrics` / `lyrics-` / +`unsyncedlyrics`). A `.txt` (or embedded) whose text actually contains `[mm:ss]` lines is served as `lrc`. + ### 2.4 Discography (artist album grouping) For artist folders (manifest entry with `"disco": true`), this returns a map of **album folder → release @@ -184,7 +198,7 @@ GET /api/music/reindex/status → IndexStatus snapshot "running": true, "startedAt": 1785034701973, "finishedAt": null, "foldersScanned": 45, "albumsBuilt": 12, "albumsSkipped": 3, - "tracksIndexed": 320, "videosIndexed": 4, "coversSaved": 12, "postersSaved": 4, "discographies": 3, + "tracksIndexed": 320, "videosIndexed": 4, "coversSaved": 12, "postersSaved": 4, "lyricsIndexed": 45, "discographies": 3, "currentPath": "Albums/AC-DC/[1980] Back in Black", "error": null } diff --git a/src/servers/sidecar/music/index.ts b/src/servers/sidecar/music/index.ts index 79c34442..90917117 100644 --- a/src/servers/sidecar/music/index.ts +++ b/src/servers/sidecar/music/index.ts @@ -13,6 +13,7 @@ import { coverFilePath, discographyFilePath, posterFilePath, + lyricsFilePath, onIndexProgress, buildReport, } from './indexer'; @@ -38,6 +39,7 @@ const DATA_PATH = process.env.DATA_PATH ?? join(process.cwd(), 'data'); // GET /meta?path= album meta.json (IndexMeta). ETag: ; If-None-Match → 304. // GET /cover?path= compressed cover.jpg. ETag: ; If-None-Match → 304. // GET /poster?path=&file=