Clean-up
This commit is contained in:
56
README.md
56
README.md
@@ -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).
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user