Commit Graph
7 Commits
Author SHA1 Message Date
pastilhasandClaude Opus 4.8 b3a4da4b97 music indexer: index track lyrics (.lrc/.txt sidecars + embedded)
Each track's lyrics are resolved and cached at cache/<rel>/lyrics/<file>.<lrc|txt>,
with the format recorded as `lyrics: 'lrc'|'txt'` on the meta.tracks entry.

Precedence: external "<base>.lrc" > external "<base>.txt" > embedded tag
(lyrics / lyrics-<lang> / 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=<rel>&file=<track> (text/plain +
X-Lyrics-Format header, ETag=<v>, 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 <noreply@anthropic.com>
2026-07-27 14:51:58 +00:00
pastilhasandClaude Opus 4.8 10d7b6a425 music indexer: generate video poster thumbnails
Each indexed video gets a compressed poster (a frame grab ~10% in, capped at
30s, scaled ≤600px q5 like covers), written to cache/<rel>/posters/<file>.jpg
and recorded as `poster` on the meta.videos entry. The posters dir is wiped and
regenerated on each rebuild so orphans (removed videos) don't linger. New
`postersSaved` status counter.

Served by a new sidecar route GET /api/music/poster?path=<rel>&file=<video>
(image/jpeg, ETag=<v>, 304, 404 when none) — path-safe via basename.

Verified end-to-end on a real .mp4: video-only album → manifest {tracks:0,
videos:1}, meta.poster set, 14 KB poster on disk. MUSIC_API.md documents the
poster field + endpoint + postersSaved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 14:38:15 +00:00
pastilhasandClaude Opus 4.8 d5b3dbb473 music indexer: index videos (concerts/clips) in artist/album dirs
Videos (all phone-compatible .mp4, plus common containers) that live in an
artist or album folder are now indexed alongside audio:

- Move 'mp4' out of AUDIO_EXT into a new VIDEO_EXT (mp4/m4v/mkv/mov/webm/avi) —
  it was wrongly treated as an audio track before.
- ffprobeVideo captures file/title/durationSec/width/height per video.
- meta.json gains an optional `videos: IndexVideo[]`; a folder with only videos
  now still gets a meta.json. Manifest entries gain optional `videos: N`.
- Video files join the album version signature (changes bump `v` for resync).
- New `videosIndexed` status counter + resync-log line.

Location is inherent in the folder rel (always an artist/album dir), so no
extra location field is needed. MUSIC_API.md documents the videos field +
manifest count. No poster/thumbnail generation yet (folder cover is reused).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 14:16:45 +00:00
pastilhasandClaude Opus 4.8 bff11bc86d music: fresh-on-refresh reindex, resync logs, and stop the junk-cover rebuild loop
- POST /reindex now runs the build to completion before responding (via a
  coalescing reindexNow), and GET /manifest ensures a fresh (debounced 3s)
  index first — so on-disk changes show up on a plain app refresh, not only
  via the explicit reindex sheet.
- Log each resync in the officer-music sidecar (start + one-line summary,
  or a failure line).
- Fix albums whose cover file isn't a decodable image (junk .jpg): the
  manifest cover flag and the skip check now reflect whether a cover was
  actually cached, so they settle to cover:false instead of rebuilding every
  run (and the app no longer 404s fetching a cover that was never there).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 23:13:17 +00:00
pastilhasandClaude Opus 4.8 946da85e4c 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>
2026-07-26 09:39:11 +00:00
pastilhasandClaude Opus 4.8 6224f6be51 music: SSE reindex progress stream + reindex-music CLI
Adds a live progress channel for the library index:
- indexer.ts: progress subscribers (onIndexProgress) + throttled emit during the
  walk, and buildReport() for a final summary.
- sidecar: GET /reindex/stream (SSE) — triggers a build if idle (?trigger=0 to
  watch only), streams `progress` events, ends with a `done` event carrying the
  report; auto-proxied at /api/music/reindex/stream for the app. Sidecar also
  writes DATA_PATH/music/.server (its port) for local tooling.
- scripts/reindex-music.ts: CLI that reads the port file, follows the SSE, prints
  live progress + a final report. Run: bun scripts/reindex-music.ts

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 03:18:41 +00:00
pastilhasandClaude Opus 4.8 49b4773457 music: server-side library indexer + sync surface
The officer-music sidecar now builds a cache tree mirroring the library (server
counterpart of the app's music-index.ts), and exposes an rsync-clean diff surface.

Indexer (indexer.ts): walks HOME_DIR/Music; per album computes a version `v` =
hash of the source signature (track name+size+mtime, cover size+mtime); ffprobe
→ meta.json (phone IndexMeta schema: file/title/artist/albumArtist/album/track/
year/durationSec); ffmpeg compresses the cover to <=600px q5 cover.jpg. Writes
DATA_PATH/music/cache/<rel>/. Incremental (skip albums whose `v` is unchanged),
prunes cache dirs for albums removed from the library, maintains manifest.json.

Endpoints (sidecar, auto-proxied by /api/music/*):
  POST /reindex          async build; GET /reindex/status polls progress
  GET  /manifest         { version, albums: { "<rel>": { v, cover, tracks } } }
  GET  /meta?path=<rel>  album meta.json   (ETag: v, 304 on If-None-Match)
  GET  /cover?path=<rel> compressed cover  (ETag: v, 304 on If-None-Match)

Phone resync: GET /manifest, diff `v` against last-stored → fetch only changed
albums' meta+cover; drop rels missing from the manifest. No re-download of
unchanged albums.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 02:59:23 +00:00