feat(lyrics,art): real-time synced lyrics & multi-protocol terminal album art - #33
Merged
Merged
Conversation
…lbum art Closes #23. Adds two new zero-dependency engine packages and wires them into the TUI. pkg/lyrics - LRCLIB exact lookup, LRCLIB search fallback, then NetEase. - LRC parser handling [mm:ss(.xx|.xxx)], multiple stamps per line and [offset:NNN]; ActiveIndex/Progress resolve the line for a playback offset. - SplitTrackTitle turns dirty ICY titles into artist/title, returning nothing for station names and advert slugs so no pointless lookup is made. - RAM LRU plus a disk cache under ~/.cache/halpradio/lyrics/, with negative caching so unmatched stations do not hammer the providers. pkg/art - Cover lookup from iTunes, Deezer, MusicBrainz/Cover Art Archive and optionally Last.fm, capped and validated before use. - Terminal renderers for the Kitty graphics protocol, iTerm2 inline images, Sixel, truecolor half-blocks and Braille, plus env-based auto-detection. - Every renderer returns exactly `rows` lines whose lipgloss.Width equals `cols`, so escape-sequence transports cannot shift the Bubble Tea layout. TUI integration - L opens a lyrics drawer that takes its own columns rather than overlapping the station list, auto-scrolling around the active line with a progress gauge; j/k scroll unsynced sheets and , / . nudge the sync, which persists. - A opens a floating cover art viewer; the drawer also carries a thumbnail when the terminal is tall enough to spare the rows. - Lookups run as tea.Cmds keyed on a station+track signature, so every play path is covered and late results for a superseded track are dropped. - Artwork is rasterised in the update loop on a new cover, a resize or a surface toggle, keeping component views pure; a closed surface emits the protocol's image-delete escape so Kitty placements do not linger. While in the analog tuner, L keeps sweeping the dial. That branch also had its step sign inverted, so a fast sweep right now raises the frequency. No new module dependencies. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
🧪 Test Coverage & Motivation Report
📋 Detailed Package Coverage Breakdown |
Below 80 columns the lyrics drawer declined to open, because the station list will not render under 28 columns beside an 18 column sidebar. The only feedback was one dim status-bar line, so pressing L on a narrow terminal looked like a dead key. The sheet now has two surfaces. At 80 columns or wider it stays a drawer beside the station list; below that it takes over the content area as a full-width overlay. Resizing moves an open sheet between the two instead of making it vanish, and the width refusal is gone entirely. The status bar also carries [L] Lyrics and [A] Art on every station tab. Previously the keys appeared only in the which-key overlay, so nothing in the default view hinted the feature existed. The legend is clipped to the terminal width, so its order is now a priority list and the two new entries sit near the front; y/Yank and a/Add move to the widest tier only. Verified against the real binary in a pty at 70 columns: L opens the overlay and the legend shows both keys. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
🧪 Test Coverage & Motivation Report
📋 Detailed Package Coverage Breakdown |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #23.
Adds a synced-lyrics engine and a multi-protocol terminal album art renderer, then wires both into the TUI. No new module dependencies — both packages are standard library only.
pkg/lyrics/api/getlookup using artist, title, album and duration, then/api/searchwhen that misses, then NetEase as a last resort.[mm:ss],[mm:ss.xx],[mm:ss.xxx], several stamps sharing one line, and the[offset:NNN]tag.ActiveIndexandProgressresolve the line for a playback offset.SplitTrackTitleturns dirty ICY metadata into artist and title, stripping(Official Video),[HQ], trailing| StationNameand advert slugs. It returns nothing for a bare station name, so no pointless lookup is made.~/.cache/halpradio/lyrics/, with negative caching so a station full of unmatched tracks does not hammer the providers. Every disk failure is non-fatal.pkg/artlipgloss.Width. That holds for all five protocols and is asserted per protocol inrender_test.go, so an escape-sequence transport cannot shift the surrounding Bubble Tea frame.TUI integration
Lopens a lyrics drawer that takes its own columns rather than overlapping the station list, so the list keeps its layout and selection. The drawer auto-scrolls around the active line, fades its neighbours and draws a progress gauge.j/kscroll unsynced sheets;,/.nudge the lyric sync in 0.5s steps and the correction persists toconfig.yaml.Aopens a floating cover viewer. The drawer also carries a thumbnail, but only when the terminal is tall enough to spare the rows — the sheet gets priority.tea.Cmds keyed on a station-and-track signature, so every play path is covered (list, globe, tuner, party sync, media keys) and a result that arrives after the track changed is dropped.Things worth reviewing closely
q=2on the placement. Without it the terminal replies on stdin and Bubble Tea's input reader surfaces junk keypresses. A closed surface also emits the image-delete escape, because a Kitty placement survives a text repaint.FastSweepRightsubtracted its step while clamping toMaxFreq, so sweeping right lowered the frequency. I was already editing that branch to share theLkey, so I corrected it there. While the tuner is liveLstill sweeps the dial; everywhere else it toggles the drawer.Verification
pkg/lyricspkg/artpkg/ui/componentsProvider code is tested against
httptestservers with injectable base URLs, and image fixtures are generated in memory — no test touches the network.Beyond the unit tests I checked both engines against the live public APIs. Fetching Daft Punk's "Get Lucky" returned a 78-line synced sheet from LRCLIB and a 600x600 cover from iTunes, and all five renderers produced correct geometry from those real bytes.
Docs
New
docs/LYRICS_AND_ART.mdcovers the providers, the protocol detection table, the layout contract, the playback-position estimate and the privacy behaviour. README,docs/CONFIGURATION.md,docs/ARCHITECTURE.md,CLAUDE.mdandAGENTS.mdare updated, including the new keybindings in the which-key overlay.🤖 Generated with Claude Code