Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 206 additions & 0 deletions media-lyrics/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
# Changelog

All notable changes to **Media Lyrics** are documented here.
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.8.13] — 2026-09-02

### Fixed

- **Review-hardening round 2** (community review prep):
- i18n coverage extended: widget title fallback, panel "Unknown artist",
and the footer provider labels ("Local"/"Cache") now route through
`noctalia.tr()` (keys: `common.*`, `provider.*`).
- Network failures are no longer reported as "no lyrics found": a
transport error (curl exit ≠ 0 and ≠ 22) surfaces
`service.lyrics-unreachable` through the lyrics-error state, which the
panel now reaches for the first time.
- No nil children in the UI tree: the unsynced-list chevron was emitted
as `cond and node or nil` inside a children array (the host logs
"ui tree node is not a table"); children are now appended conditionally.
- UTF-8-safe cuts: `wrapLyric` hard-slices and the bar-widget title
truncation never split a multi-byte sequence (Cyrillic titles/lines).
- Karaoke class (font size/weight) is precomputed once into arrays shared
by the window budget and the render loop — they cannot drift apart.
- README/docs: "14 visible lines" corrected to 10/14/16 per preset; the
"Bilingual UI (en/ru)" claim softened to "translatable via Noctalia
i18n" (no ru file ships by community rule); stale comments in
plugin.toml/service.luau/panel.luau updated (150 ms poll, no "ring",
no "zero external tools").

## [0.8.12] — 2026-09-02

### Fixed

- **README Plugin section documents every panel entry** — the Plugin table
now lists `panel`, `panel-compact` and `panel-large` (the size presets
added in 0.8.7); the community `validate` CI requires each panel entry id
to appear in the README.

## [0.8.11] — 2026-09-02

### Fixed

- **Long lyric lines no longer spill past the panel edge** (reported live on
the compact preset): a `ui.label` wider than the panel is clipped at the
surface edge, cutting 47+ char lines mid-word. The host does not auto-wrap,
so long lines are now soft-wrapped into balanced sub-lines via `\n`
(verified that `ui.label` renders `\n`). The wrap budget uses a glyph
advance factor of ~0.62 em measured live at scale 1.5 (bold 0.66) and
per-preset content widths (compact 404 / medium 484 / large 604).
- **Karaoke window now fits a vertical sub-line budget** — the visible
window shrinks when wrapped lines would push the active line below the
panel edge (previously the fixed 10/14/16-row window could clip the
bottom rows once lines wrapped).

## [0.8.10] — 2026-09-02

### Fixed

- **Community review compliance** (noctalia-dev/community-plugins #592):
- `curl` now declared in `dependencies` and the README Requirements —
the service spawns it for LRCLIB fetches (`-sSf -m 8 -4`, argv-only, no
shell). `sleep` (coreutils) documented in the README Notes.
- Plugin description no longer claims "no external dependencies" — it
states the real runtime needs (`busctl` + `curl`).
- Panel chrome is fully routed through `noctalia.tr()` with keys in
`translations/en.json` ("Loading lyrics…", "Lyrics error: ", "No lyrics
found", "No media player", "(no title)", "NOW PLAYING", "Reload lyrics",
"synced"/"unsynced") — other locales can now be provided via Noctalia
Translate instead of rendering hardcoded English.
- Thumbnail regenerated with the official thumbnail generator
(assets.noctalia.dev, 960×540 WebP).

## [0.8.9] — 2026-09-01

### Fixed

- **Compact panel: layout fix actually wired up** — `panelLayout()` was
defined but never used in `buildInfoRow`; the header still rendered with
the fixed 520px metrics and the transport block stayed clipped at 440px.
`buildInfoRow` now applies the preset layout (cover size, gap, text width,
transport button sizes, no-player row height).

## [0.8.8] — 2026-09-01

### Fixed

- **Compact panel: transport block clipped** — the header (cover + title +
transport + time) overflowed 440px. `panelLayout()` now scales cover size,
text column width and transport button sizes per preset (compact 36/168/14,
medium 50/286/18, large 56/370/20); the disc placeholder glyph scales too.
- **Per-preset placement/position settings removed** — only the medium
`panel` declares `placement`/`position`; compact and large inherit the
default so the settings UI shows one placement/position block, not three.

## [0.8.7] — 2026-09-01

### Added

- **Panel size presets** — `panel_size` setting (compact 440 / medium 520 /
large 640) selects which panel preset the bar widget and control-center
tile open. Three `[[panel]]` entries share one `panel.luau`; the visible
lyric lines scale with the preset (10 / 14 / 16). Medium keeps the
historical `panel` id for IPC compatibility.

## [0.8.5] — 2026-09-01

### Added

- **Clickable lyric lines** — click a synced line to seek the player to that
timestamp. Lines without a timestamp (plain lyrics) are not clickable.
Implemented as a `ui.row` click target wrapping the label — `ui.label`
takes no `onClick` and `ui.button` ignores `color`/`fontWeight` (would
break the karaoke gradient).
- **Manual lyric scroll + line selection** — Up/Down step the lyric cursor
(highlighted with a chevron marker), Return/Space seek to the cursor line.
Works for synced AND plain lyrics (plain: highlight only, no seek).
Before the first timestamp the window starts at line 1 (was frozen).
`keyboard_focus = "exclusive"` so the panel receives keys; the host's chord
validator accepts only basic names (PageUp/PageDown/Home/End are rejected
and would drop the plugin from the store).
- `onScroll` declared on the panel (the host documents it as bar-widget-only;
if a future build delivers wheel events, scrolling steps the cursor).

### Fixed

- **Seek used the wrong D-Bus method** — `SeekActive` is RELATIVE (MPRIS
Seek): seeking to a line jumped by the timestamp instead of to it. Now uses
`SetPositionActive` (ABSOLUTE, verified live: 2:00 lands at 2:00).
- **`busctlCall` dropped the D-Bus signature** — typed arguments were passed
without their type (`SetPositionActive 90000000` instead of
`SetPositionActive x 90000000`), so busctl failed with «Too few parameters
for signature» and seek/shuffle/loop silently did nothing. Now the type is
passed for every argument (`b`, `s`, `x`).
- **Bar widget mixed render() and setGlyph/setText** — the host warns that
setGlyph/setText have no visible effect once a render() tree is active;
the empty state now renders a disc glyph tree too.

## [0.8.3] — 2026-09-01

### Changed

- `dependencies = ["busctl"]` declared in the manifest (community-plugins
review rule: every shelled-out command must be declared).
- `description` fixed — «ring progress» was removed in v0.8.0 (replaced by
the header progress bar); catalog copy now reads «progress bar» (111/120).

### Removed

- `translations/ru.json` — community rule is en.json only; other locales are
handled via Noctalia Translate.
- ROADMAP link from the plugin README (roadmap lives outside the plugin
directory in the community-plugins layout).

## [0.8.1] — 2026-09-01

### Changed

- Bar widget honors the instance's `Color` / `Icon Color` settings: explicit
color roles removed from the `barWidget.render()` tree (they silently
ignored the user's per-widget color configuration; the host colors the
built-in glyph/text row, which the empty state already used).
- `[widget.actions] middle = "none"` declared in the manifest — the host
default (`settings-open-widget`) swallowed `onMiddleClick`, so middle-click
play/pause never fired out of the box.

### Removed

- Custom URL from the planned additional lyric sources (contradicts the
install-and-use philosophy).

## [0.8.0] — 2026-09-01

### Added

- Marquee (scrolling) titles for long track/artist names: 2 s static hold,
then slow scroll; per-slice button keys prevent glyph overlap.
- Per-font-size marquee capacity and speed (`vwUnits(fs)`, `MARQUEE_SPEED/fs`).
- `singleLine` sanitizer for MPRIS metadata containing embedded newlines.
- Progress bar between header and lyrics (replaces the old separator).
- Localization: English + Russian UI strings.
- Screenshots, thumbnail, docs (`ROADMAP.md`), MIT license — publication-ready.

### Changed

- Visible lyric lines: 11 → 14 (carousel window).
- Lyric timing: marquee clock driven by `watch("media")` publishes
(the host never calls panel `update()`); service publish cadence 500 → 150 ms
for smooth animation.
- Title/artist pinned flush-left via ghost buttons with `contentAlign="start"`
(host ignores `textAlign` on labels).
- Lyric source fallback: LRCLIB `/api/get` → `/api/search` → local `.lrc` → cache.

### Fixed

- Lyric lines wrapping and letter overlap (newline sanitizer, integer
button heights, per-slice keys).
- Marquee not starting (host tick probe: `update()` never called on panels).
- Titles/artists drifting to center or clipping on long names.
- Progress ring under the cover removed; progress bar layout stable.

### Removed

- Shuffle/repeat randomness, progress seek buckets, cover progress ring
(replaced by the header progress bar).
21 changes: 21 additions & 0 deletions media-lyrics/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Ivan Zyablov (TraNZeM)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
113 changes: 113 additions & 0 deletions media-lyrics/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Media Lyrics

A full-featured media player panel with **time-synced lyrics** for the Noctalia desktop shell. Karaoke-style lyric carousel (10/14/16 visible lines per size preset), album cover, transport controls, and a progress bar — all in one floating panel. **Pure Luau implementation**: no playerctl, no python daemons, no GTK overlays — runtime needs `busctl` (MPRIS) and `curl` (LRCLIB HTTPS).

| Light theme | Dark theme |
| --- | --- |
| ![Media Lyrics panel (light)](screenshots/panel-light.png) | ![Media Lyrics panel (dark)](screenshots/panel-dark.png) |

## Plugin

| Field | Value |
| --- | --- |
| ID | `tranzem/media-lyrics` |
| Entries | Bar widget: `now-playing`; panels: `panel` (medium 520×520), `panel-compact` (440×440), `panel-large` (640×640); service: `service`; shortcut: `toggle` |

## Requirements

- Noctalia v5 (plugin API 24+)
- `busctl` (systemd, present on every Arch install)
- `curl` — used for the LRCLIB HTTPS fetch (spawned as `curl -sSf -m 8 -4 <url>`, argv-only, no shell; LRCLIB resolves IPv4 faster than the built-in HTTP client on some setups)
- Outbound HTTPS access to `https://lrclib.net` for synced lyrics

No player-specific software. Any MPRIS-capable player works: Spotify, MPD, Cider, web players, VLC, and anything else that exposes MPRIS over D-Bus. `sleep` (coreutils) is used for a short refresh delay after transport commands.

## Usage

Enable the plugin, then open the panel:

```sh
noctalia msg plugins enable tranzem/media-lyrics
noctalia msg panel-toggle tranzem/media-lyrics:panel
```

The panel opens at the size preset selected by the `panel_size` setting
(compact 440 / medium 520 / large 640). The `now-playing` bar widget and the
`toggle` control-center tile both open the selected preset; you can also open
a specific preset directly:

```sh
noctalia msg panel-toggle tranzem/media-lyrics:panel-compact
noctalia msg panel-toggle tranzem/media-lyrics:panel-large
```

Add the `now-playing` widget to your bar to get a compact indicator that opens the panel on click. A `toggle` shortcut (control-center tile) is also available. Bind it to a hotkey in Noctalia's shortcut settings, or from your compositor:

```toml
"Ctrl+Alt+M" = "spawn:noctalia msg panel-toggle tranzem/media-lyrics:panel"
```

The panel shows the active MPRIS player automatically; when nothing is playing it renders an empty state.

## Features

- **Karaoke lyric carousel** — 10/14/16 lines visible at once (compact/medium/large presets); the active line is bright, neighbours fade by distance (Clavis-style). Works with synced (LRC) and plain lyrics.
- **Clickable lyric lines** — click a synced line to seek the player to that timestamp.
- **Manual lyric scroll** — Up/Down step a line (the host's chord validator accepts only basic key names; PageUp/PageDown/Home/End are rejected).
- **LRCLIB integration** — exact `/api/get` lookup first, `/api/search` fallback, LRC parsed in pure Luau.
- **Local `.lrc` files** — drop `Artist - Title.lrc` into the local lyrics folder; they take priority over the network.
- **Marquee titles** — long track/artist names hold for 2 s, then scroll slowly instead of wrapping or clipping. Overlap-free (per-slice node recreation).
- **Album cover + progress bar** — interpolated progress between polls, transport controls (prev / play-pause / next), shuffle and repeat state.
- **Settings** — lyric timing offset in ms, on-disk cache, local lyrics folder. Translatable UI: strings go through Noctalia's i18n (`noctalia.tr`, English ships in the plugin; other locales via Noctalia Translate).

## Advantages over alternative lyric plugins

- **Lean runtime.** No playerctl, python daemons, pip packages, or GTK overlays to install and maintain — just `busctl` and `curl`, present on virtually every Linux system. Enable → works.
- **Player-agnostic.** Reads MPRIS directly via Noctalia's D-Bus aggregator — works with any player, not tied to a specific app.
- **A real panel, not a 1–3 line bar widget.** Full-screen-height carousel with 10–16 visible lines (per size preset) keeps whole verses in view.
- **Overflow handled properly.** Long titles get a marquee, single-line sanitizer strips embedded newlines, integer button heights prevent glyph overlap.
- **Offline-friendly.** LRCLIB responses are cached; local `.lrc` files work without network at all.

## Settings

| Setting | Type | Default | Description |
| --- | --- | --- | --- |
| `panel_size` | `select` | `medium` | Panel size preset: `compact` (440×440, 10 lyric lines), `medium` (520×520, 14 lines), `large` (640×640, 16 lines). The bar widget and the control-center tile open this preset. |
| `offset_ms` | `int` | `0` | Shift lyric timing: positive shows lines earlier, negative later. |
| `use_cache` | `bool` | `true` | Cache fetched lyrics in the plugin data directory for offline reuse. |
| `local_lyrics_dir` | `folder` | `~/.local/share/media-lyrics` | Folder with local `.lrc` files named `Artist - Title.lrc`; searched before LRCLIB. |

## IPC

```sh
noctalia msg panel-toggle tranzem/media-lyrics:panel
```

## Local development

Add the parent directory as a local Noctalia source:

```sh
noctalia msg plugins source add media-lyrics-dev path /path/to/media-lyrics-parent
noctalia msg plugins enable tranzem/media-lyrics
noctalia msg config-reload
```

## To Do

Upcoming work, roughly in priority order:

- [ ] Album cover inside a capsule shape
- [ ] Additional lyric sources (NetEase, Musixmatch, embedded MPRIS metadata, …)
- [x] Clickable lyric lines — click a line to seek the track to that moment (DONE in 0.8.5: click + Return/Space)
- [ ] Seek on progress-bar click
- [ ] Compact mode with a pinnable widget
- [x] Preconfigured widget actions — default gestures declared in the manifest (middle click → play/pause, scroll → track switching) work out of the box (DONE in 0.8.1: `[widget.actions] middle = "none"`)
- [x] Widget size setting — panel size presets (DONE in 0.8.7: `panel_size` select — compact 440 / medium 520 / large 640; the bar widget itself keeps its hard-coded look)

## Notes

- The service polls MPRIS via `busctl` (150 ms cadence) and publishes a snapshot to `noctalia.state`; the panel animates from those publishes.
- Lyrics are fetched from the public LRCLIB API with `curl`; nothing is uploaded. Cache and local lyrics live under the plugin data directory and `local_lyrics_dir`.
- Spawned processes (all argv-form, no shell): `busctl` (MPRIS poll), `curl` (LRCLIB fetch, IPv4, 8 s timeout), `sleep` (coreutils, 0.35 s refresh delay after transport commands).
- Adapted from the Clavis shell media player text layer (karaoke render + LRCLIB provider), ported to pure Luau for Noctalia v5.
Loading