From 40080f7f1e18f46f58cbccd39d8a0c877b40e498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Mon, 27 Jul 2026 21:49:30 +0000 Subject: [PATCH] music indexer: add disc number to each track (from ID3 TPOS) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ffprobe surfaces ID3 TPOS as the `disc` tag ("n" or "n/total"); parse the leading number → IndexTrack.disc?: number in meta.json (undefined when absent/unparseable), alongside `track`. Verified on Pink Floyd - The Wall (Disc 1 → 1, Disc 2 → 2). Co-Authored-By: Claude Opus 4.8 --- src/servers/sidecar/music/indexer.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/servers/sidecar/music/indexer.ts b/src/servers/sidecar/music/indexer.ts index 0743627f..213f1540 100644 --- a/src/servers/sidecar/music/indexer.ts +++ b/src/servers/sidecar/music/indexer.ts @@ -9,7 +9,7 @@ import { homedir } from 'node:os'; // (relative to the Music root): // // DATA_PATH/music/cache//meta.json { path, cover?, tracks: [{ file, title, artist, albumArtist, -// album, track, year, durationSec, lyrics?:'lrc'|'txt' }], +// album, track, disc?, year, durationSec, lyrics?:'lrc'|'txt' }], // videos?: [{ file, title, durationSec, width, height, poster? }], // images?: [{ file }] } (phone IndexMeta schema) // DATA_PATH/music/cache//cover.jpg compressed (<=600px, jpeg q5) @@ -89,6 +89,7 @@ export type IndexTrack = { albumArtist?: string; album?: string; track?: string; + disc?: number; // disc / part-of-set number, from ID3 TPOS (the `disc` tag "n" or "n/total") year?: string; durationSec?: number; lyrics?: 'lrc' | 'txt'; // available lyrics format ('lrc' = synced/timestamped); text via /lyrics. Absent = none. @@ -344,6 +345,8 @@ async function ffprobeTrack(absPath: string, file: string): Promise<{ track: Ind for (const [k, val] of Object.entries(fmt.tags ?? {})) tags[k.toLowerCase()] = val; const durNum = fmt.duration ? Math.round(parseFloat(fmt.duration)) : undefined; const yearMatch = tags.date?.match(/\d{4}/); + // Disc / part-of-set from ID3 TPOS (ffprobe surfaces it as `disc`, "n" or "n/total"). + const discNum = tags.disc ? parseInt(tags.disc.split('/')[0]!.trim(), 10) : NaN; // Embedded lyrics: USLT/foobar keys — `lyrics`, `lyrics-`, `unsyncedlyrics`. let lyricsText: string | undefined; for (const [k, val] of Object.entries(tags)) { @@ -360,6 +363,7 @@ async function ffprobeTrack(absPath: string, file: string): Promise<{ track: Ind albumArtist: tags.album_artist || tags.albumartist || undefined, album: tags.album || undefined, track: tags.track || undefined, + disc: Number.isFinite(discNum) ? discNum : undefined, year: yearMatch ? yearMatch[0] : undefined, durationSec: Number.isFinite(durNum) ? durNum : undefined, },