This commit is contained in:
2026-03-22 19:56:47 +00:00
parent 5bb968e255
commit 10f3162b31

View File

@@ -4,35 +4,35 @@ A local music scrobbler for MPRIS-compatible players and MPD, built in Rust. It
## Features ## Features
- **Dual source scrobbling** tracks both MPRIS players (via `playerctl`) and MPD simultaneously; each maintains its own tracker writing to the same database - **Dual source scrobbling** -- tracks both MPRIS players (via `playerctl`) and MPD simultaneously; each maintains its own tracker writing to the same database
- **Accurate play time tracking** monitors both metadata changes and play/pause status, so paused time doesn't count toward scrobble thresholds - **Accurate play time tracking** -- monitors both metadata changes and play/pause status, so paused time doesn't count toward scrobble thresholds
- **Last.fm-style scrobble rules** a track is scrobbled after 50% of its duration or 4 minutes of play, whichever is shorter - **Last.fm-style scrobble rules** -- a track is scrobbled after 50% of its duration or 4 minutes of play, whichever is shorter
- **Local storage** all data stays on your machine in a single SQLite file - **Local storage** -- all data stays on your machine in a single SQLite file
- **Reports** terminal tables or JSON output, filterable by time period (today, week, month, year, all time) - **Reports** -- terminal tables or JSON output, filterable by time period (today, week, month, year, all time)
- **Adaptive terminal tables** report columns shrink to fit narrower terminal widths - **Adaptive terminal tables** -- report columns shrink to fit narrower terminal widths
- **HTML reports** generate a standalone dark-themed HTML file with album art cards, bar charts, and genre pills - **HTML reports** -- generate a standalone dark-themed HTML file with album art cards, bar charts, and genre pills
- **Source colour-coding** album cards and bar table rows are tinted by dominant scrobble source (MPRIS vs MPD), with a consistent per-source colour palette - **Source colour-coding** -- album cards and bar table rows are tinted by dominant scrobble source (MPRIS vs MPD), with a consistent per-source colour palette
- **Fair ranking tie-breaks** top artists/albums/tracks are ranked by plays first, then total listen time - **Fair ranking tie-breaks** -- top artists/albums/tracks are ranked by plays first, then total listen time
- **Genre stats** reports include top genres and per-period "Mood" labels; single-word broad genres (e.g. "ambient", "electronic") are deprioritised below multi-word ones - **Genre stats** -- reports include top genres and per-period "Mood" labels; single-word broad genres (e.g. "ambient", "electronic") are deprioritised below multi-word ones
- **Mobile-optimised report** responsive layout with a sticky jump nav; bar graphs drop on small screens leaving just rank + duration - **Mobile-optimised report** -- responsive layout with a sticky jump nav; bar graphs drop on small screens leaving just rank + duration
- **Terminal-style typography** HTML uses JetBrains Mono with monospace fallbacks - **Terminal-style typography** -- HTML uses JetBrains Mono with monospace fallbacks
- **Enrichment** extract embedded covers from MPD music files (offline), and/or fetch album art + genres from MusicBrainz / iTunes / Cover Art Archive - **Enrichment** -- extract embedded covers from MPD music files (offline), and/or fetch album art + genres from MusicBrainz / iTunes / Cover Art Archive
- **Targeted enrichment** `report --html` fetches covers only for the albums that actually appear in the report, not the entire library - **Targeted enrichment** -- `report --html` fetches covers only for the albums that actually appear in the report, not the entire library
- **Incremental publish helper** query latest scrobble and publish only when new data exists - **Incremental publish helper** -- query latest scrobble and publish only when new data exists
- **Configurable player** MPRIS player defaults to `com.blitzfc.qbz`, configurable via `--player` - **Configurable player** -- MPRIS player defaults to `com.blitzfc.qbz`, configurable via `--player`
## Requirements ## Requirements
- Rust toolchain (for building) - Rust toolchain (for building)
- [playerctl](https://github.com/altdesktop/playerctl) required only for MPRIS scrobbling; omit with `--no-mpris` if you use MPD only - [playerctl](https://github.com/altdesktop/playerctl) -- required only for MPRIS scrobbling; omit with `--no-mpris` if you use MPD only
- MPD required only for MPD scrobbling; omit with `--no-mpd` if you use MPRIS only - MPD -- required only for MPD scrobbling; omit with `--no-mpd` if you use MPRIS only
## Installation ## Installation
### Quick install (interactive) ### Quick install (interactive)
The repo includes an interactive install script that builds the binary, installs it, The repo includes an interactive install script that builds the binary, installs it,
sets up the systemd service, and starts it asking before each step: sets up the systemd service, and starts it -- asking before each step:
```bash ```bash
./install.sh ./install.sh
@@ -104,7 +104,7 @@ loginctl enable-linger "$USER"
### Important session note ### Important session note
`playerctl` and MPRIS are tied to the session D-Bus. The scrobbler must run in the **same user/session** as your player. If the player runs under another account/session, this service will not see it. MPD does not have this restriction it connects over TCP or Unix socket. `playerctl` and MPRIS are tied to the session D-Bus. The scrobbler must run in the **same user/session** as your player. If the player runs under another account/session, this service will not see it. MPD does not have this restriction -- it connects over TCP or Unix socket.
## Report workflow ## Report workflow
@@ -127,7 +127,7 @@ This creates a self-contained directory:
└── ... └── ...
``` ```
When `--html` is used, enrichment runs automatically before rendering first extracting embedded covers from MPD for any albums that appear in the report, then fetching missing covers/genres online. Use `--no-enrich` to skip this step. When `--html` is used, enrichment runs automatically before rendering -- first extracting embedded covers from MPD for any albums that appear in the report, then fetching missing covers/genres online. Use `--no-enrich` to skip this step.
## Building ## Building
@@ -292,7 +292,7 @@ scrbblr pin-album [OPTIONS]
Enrichment runs in two stages: Enrichment runs in two stages:
**Stage 1 MPD embedded covers (offline, no network)** **Stage 1 -- MPD embedded covers (offline, no network)**
By default, `enrich` connects to MPD and extracts embedded cover art from your music files using MPD's `readpicture` command. This is fast, works entirely offline, and only processes albums scrobbled via MPD that don't yet have a cover: By default, `enrich` connects to MPD and extracts embedded cover art from your music files using MPD's `readpicture` command. This is fast, works entirely offline, and only processes albums scrobbled via MPD that don't yet have a cover:
@@ -300,7 +300,7 @@ By default, `enrich` connects to MPD and extracts embedded cover art from your m
scrbblr enrich scrbblr enrich
``` ```
**Stage 2 Online lookup (MusicBrainz + iTunes + Cover Art Archive)** **Stage 2 -- Online lookup (MusicBrainz + iTunes + Cover Art Archive)**
Pass `--online` to also query MusicBrainz for album metadata (MBID, genres) and download covers for albums that still have none after the MPD pass: Pass `--online` to also query MusicBrainz for album metadata (MBID, genres) and download covers for albums that still have none after the MPD pass:
@@ -350,7 +350,7 @@ Downloaded covers are stored in:
#### Manually pinning an album (`pin-album`) #### Manually pinning an album (`pin-album`)
Sometimes automatic search fails most commonly for classical recordings where Sometimes automatic search fails -- most commonly for classical recordings where
the scrobbled artist tag (e.g. a choir or soloist) doesn't match the release the scrobbled artist tag (e.g. a choir or soloist) doesn't match the release
credits on MusicBrainz, or where the album title wording differs significantly. credits on MusicBrainz, or where the album title wording differs significantly.
Enrich will print: Enrich will print:
@@ -390,7 +390,7 @@ scrbblr pin-album \
``` ```
The image is downloaded, resized to 500 px, and stored locally just like a The image is downloaded, resized to 500 px, and stored locally just like a
Cover Art Archive image. Any HTTPS image URL works Discogs, Wikipedia, Cover Art Archive image. Any HTTPS image URL works -- Discogs, Wikipedia,
Bandcamp, etc. Bandcamp, etc.
### Incremental publish script ### Incremental publish script
@@ -467,14 +467,14 @@ Each scrobble records:
The `watch` command spawns two `playerctl --follow` child processes: The `watch` command spawns two `playerctl --follow` child processes:
1. **Metadata follower** emits a line each time the track changes 1. **Metadata follower** -- emits a line each time the track changes
2. **Status follower** emits `Playing`, `Paused`, or `Stopped` on state changes 2. **Status follower** -- emits `Playing`, `Paused`, or `Stopped` on state changes
Each process gets its own reader thread that sends typed events over an `mpsc` channel to the main thread. The main thread owns a `ScrobbleTracker` state machine, which processes events sequentially and decides when to write scrobbles to the database. Each process gets its own reader thread that sends typed events over an `mpsc` channel to the main thread. The main thread owns a `ScrobbleTracker` state machine, which processes events sequentially and decides when to write scrobbles to the database.
### MPD watcher ### MPD watcher
Unless `--no-mpd` is passed, a separate thread connects to MPD using the idle protocol and runs its own `ScrobbleTracker`. It writes to the same database independently no synchronisation is needed between the two watchers. The MPD watcher connects over TCP (default: `localhost:6600`) or a Unix socket if a socket path is given as `--mpd-host`. Unless `--no-mpd` is passed, a separate thread connects to MPD using the idle protocol and runs its own `ScrobbleTracker`. It writes to the same database independently -- no synchronisation is needed between the two watchers. The MPD watcher connects over TCP (default: `localhost:6600`) or a Unix socket if a socket path is given as `--mpd-host`.
Both watchers observe a shared shutdown flag. Ctrl+C sets it to false; the MPRIS main loop also receives an `Eof` event through its channel. The MPD watcher notices the flag on its next idle timeout (≤ 500 ms). Both watchers observe a shared shutdown flag. Ctrl+C sets it to false; the MPRIS main loop also receives an `Eof` event through its channel. The MPD watcher notices the flag on its next idle timeout (≤ 500 ms).