You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(lyrics,art): real-time synced lyrics & multi-protocol terminal album 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>
-`pkg/ui/nowplaying.go`: Lyric/artwork lookup commands, sync offset, and the artwork rasterisation step.
26
+
-`pkg/util/`: Path resolution (`~/.config/halpradio/`, `~/.cache/halpradio/`) and clipboard helper.
24
27
25
28
## 🎨 Code Style & Architectural Constraints
26
29
1.**Thread Safety**: Always protect shared state in `player.Manager` with `m.mu.Lock()` / `m.mu.Unlock()`.
@@ -29,3 +32,4 @@
29
32
4.**Theme Tokens**: Never hardcode hex color strings in UI components. Use `theme.Primary`, `theme.Border`, `theme.Playing`, etc.
30
33
5.**Resilience**: Never call `panic()` or `os.Exit()` on playback errors. Set `m.status = StatusError` and let the TUI inform the user gracefully.
31
34
6.**Verification**: Always run `go test ./...` and `gofmt -s -w .` after making modifications.
35
+
7.**Terminal Images**: Every `art.Renderer` protocol must return exactly `rows` lines whose `lipgloss.Width` equals `cols`, so escape-sequence transports cannot shift the surrounding layout. Rasterise artwork in `pkg/ui/update.go`, never inside a component `View()`.
Copy file name to clipboardExpand all lines: README.md
+63Lines changed: 63 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -322,6 +322,65 @@ Internet radio ICY streams often emit dirty titles like:
322
322
323
323
---
324
324
325
+
## 📜 Real-Time Synced Karaoke Lyrics & Multi-Protocol Terminal Album Art
326
+
327
+
Modern terminals grew real graphics capabilities, so `halpradio` uses them. Press `L` for a live lyric sheet that scrolls itself, and `A` for the cover art of whatever is on air — all without leaving the terminal.
328
+
329
+
### 🎤 Live Synced Lyrics Drawer (`L` key)
330
+
-**LRCLIB First**: Queries [LRCLIB](https://lrclib.net) with the artist, title and duration taken from ICY stream metadata or the acoustic fingerprint, then falls back to NetEase when LRCLIB has no match.
331
+
-**Auto-Scrolling Karaoke View**: When timestamped `.lrc` data exists, the drawer highlights the line being sung, fades the surrounding lines, and draws a progress gauge across the active line.
332
+
-**Manual Scroll For Plain Text**: Unsynced lyrics render as a formatted sheet you scroll with `j` / `k`.
333
+
-**Sync Nudge**: Internet radio exposes no seek position, so the lyric clock starts when the station announces a new title. Press `,` and `.` to shift the sync in 0.5 second steps when a station announces late or early.
334
+
-**Never Blocks The UI**: Every lookup runs as a Bubble Tea command off the update loop, so the TUI stays responsive on slow connections, and the drawer takes its own columns rather than overlapping the station list.
335
+
-**Disk & Memory Cache**: Sheets are memoised in RAM and cached under `~/.cache/halpradio/lyrics/`, and stations with no match are negative-cached so the APIs are not hammered every track.
336
+
337
+
### 🖼️ Multi-Protocol Album Art (`A` key)
338
+
`halpradio` detects your terminal's best image transport at startup and encodes artwork for it:
| 4 |**Truecolor Half-Block**| every 24-bit colour terminal |
346
+
| 5 |**Braille**| 256-colour and monochrome fallback |
347
+
348
+
-**High-Res Cover Lookup**: Artwork is resolved from the iTunes Search API, Deezer, MusicBrainz plus the [Cover Art Archive](https://coverartarchive.org/), and Last.fm when you supply `lastfm_api_key`.
349
+
-**No Distortion On Basic Terminals**: The half-block and Braille renderers letterbox the image to keep covers square, and every renderer emits output padded to an exact cell grid so the surrounding layout never shifts.
350
+
-**Two Surfaces**: A thumbnail sits at the top of the lyrics drawer, and `A` opens a floating full-size viewer showing the album, the provider and the active protocol.
351
+
-**Cached Locally**: Downloaded covers live under `~/.cache/halpradio/art/`.
352
+
353
+
```text
354
+
┌─ 📻 CATALOG ──────────────────┬─ 📜 LIVE LYRICS ───────────────┐
lyrics_auto_open: false # open the drawer on startup
374
+
lyrics_offset_ms: 0# persistent sync correction
375
+
album_art_enabled: true # terminal cover art renderer
376
+
album_art_protocol: auto # auto | kitty | iterm2 | sixel | halfblock | braille | off
377
+
lastfm_api_key: ""# optional extra cover art provider
378
+
```
379
+
380
+
Set `HALPRADIO_NO_ART=1` to disable image rendering for a single run, or `HALPRADIO_ART_PROTOCOL=halfblock` to force a transport when detection guesses wrong.
381
+
382
+
---
383
+
325
384
## 🎉 Terminal Party Line: P2P Mesh Synchronized Radio Rooms & Reactions
326
385
327
386
Share the groove with teammates, study groups, or friends with zero central audio relaying! `halpradio` features an end-to-end encrypted (E2EE) P2P mesh party system powered by WebRTC data channels:
@@ -378,6 +437,9 @@ Press `?` or `F1` anywhere in **halpradio** to open the floating **WhichKey Over
| | `y` | Yank / copy track metadata (`Artist - Title`) or identified song to system clipboard |
382
444
| | `o` | Open streaming search in default web browser (Spotify, YT Music, Apple, DDG, Google) |
383
445
| | `s` | Star / bookmark track to `~/.config/halpradio/saved_tracks.txt` (on History tab) |
@@ -510,6 +572,7 @@ Explore detailed technical documentation in the [`docs/`](./docs) folder:
510
572
- 🔌 **[Plugin & Extension System Guide](./docs/PLUGINS.md)**: Sandboxed WebAssembly (Wasm) architecture, capability permissions, developer SDK, and publishing to the official registry.
|[`pkg/art`](../pkg/art/client.go)|`Client`, `Cover`, `Renderer`, `Protocol`| Resolves high-resolution cover art from iTunes, Deezer, MusicBrainz / Cover Art Archive and Last.fm, then encodes it for the terminal's best image transport with RAM and disk caching. |
77
+
|[`pkg/lyrics`](../pkg/lyrics/lrclib.go)|`Client`, `Sheet`, `Line`, `ParseLRC()`| Queries LRCLIB and falls back to NetEase, parses `.lrc` timestamps, resolves the active line for a playback offset, and caches sheets in RAM and on disk. |
74
78
|[`pkg/player`](../pkg/player/player.go)|`Player`, `Manager`, `TrackInfo`| Detects audio CLI backends (`mpv`, `vlc`, `ffplay`, etc.) or falls back to native Go audio. Runs ICY metadata streaming goroutine. |
0 commit comments