v0.35.0: a play history for the yearly Wrapped
The play journals keep running totals per track, which is enough for "most played ever" and nothing a Wrapped asks: top songs of this year, minutes listened, which month, what time of day, what got skipped. Those all come from one timestamped line per listen, so LinTunes now keeps that, while Preferences -> General -> "Keep a play history for my yearly Wrapped" is on. history/<machine>/<YYYY-MM>.jsonl, one writer per folder like the journals: local start time with its offset, ms actually heard (Player adds position steps under 1.5 s, so seeks and pauses don't count), how it ended, where it played and from what, and a name/artist/album snapshot that outlives a delete. Under 5 s is noise and never written. andTunes 0.3.0 writes the same lines on the Rabbit when sync says the desktop wants them, and sync copies each month home when the device's copy is longer. "Share this year's play history with friends" puts a trimmed copy (no ids, playlists or devices) in each friend's cassette folder, daily in November and December and monthly otherwise. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -104,6 +104,32 @@ persistence) → GUI (Qt widgets that read the manager and connect to its signal
|
||||
`journal.base_fields(tid)`, never the `Track`'s effective count. Those two
|
||||
places are the whole hazard; `tests/test_round38.py` pins both.
|
||||
|
||||
- **`lintunes/storage/listen_history.py`** — the yearly Wrapped's raw data
|
||||
(Round 74). The journals keep *totals*, which can't say "top songs of
|
||||
2026", minutes listened, month or time of day; this keeps **one line per
|
||||
listen** in `<data_dir>/history/<machine id>/<YYYY-MM>.jsonl` — the
|
||||
journals' owner-only-writer rule, append-only, a finished month never
|
||||
changes. `t` is *local* time with its offset (time-of-day is the listener's
|
||||
clock); `ms` is time actually **heard** — `Player._account_heard` adds
|
||||
position steps of 0–1.5 s, so seeks and pauses don't count; `end` is
|
||||
finish/skip/stop/quit; the artist/album snapshot outlives a delete or
|
||||
retag. Under 5 s is noise and never written. Only while the synced pref
|
||||
`wrapped.track_history` is on: `Player.listen_ended` → `MainWindow` (adds
|
||||
`src` desktop/cast and `ctx`, which it latches at `track_changed` because
|
||||
`_now_playing_context` moves before the old listen closes) →
|
||||
`LibraryManager.record_listen`, flushed with the journal. The Rabbit writes
|
||||
the same lines (`History.java`) only when sync put `"history": true` in its
|
||||
`library.json`; `andtunes/plays.bring_back_history` copies a month home when
|
||||
the device's copy is longer (two desktops can both do that, hence the
|
||||
union merge in `conflict_resolver._merge_listen_history`).
|
||||
`cassette/wrapped.py` puts this year's **trimmed** log
|
||||
(`trim_for_sharing`: no id/ctx/src/shuf) in every friend's `out/` as
|
||||
`wrapped-<year>.json` when `wrapped.share_with_friends` is also on —
|
||||
checked hourly by `CassetteUi`, written daily in Nov–Dec and monthly
|
||||
otherwise, never rewritten when the plays didn't change. No Wrapped page
|
||||
yet: that's built after Spotify's 2026 one drops, plus a Last.fm backfill
|
||||
for Jan–Sep (scrobbling is on; the R1 never scrobbled).
|
||||
|
||||
- **`lintunes/library_manager.py`** — `LibraryManager(QObject)` owns the
|
||||
`Library`, is the single funnel for all mutations, and persists them
|
||||
**debounced** (3 s) with per-area dirty tracking (a play-count bump rewrites
|
||||
|
||||
Reference in New Issue
Block a user