Commit Graph
7 Commits
Author SHA1 Message Date
travandClaude Opus 5.5 0dad4cc95e v0.26.0: Cassette footing — Syncthing client, host machine, Sync Settings
First step of friend library sharing (Cassette):

- cassette/syncthing_api.py: a typed wrapper over the *local* Syncthing's
  REST API. Key and address come from Syncthing's config.xml (config.json
  may override); every failure is a SyncthingError naming the step, what
  Syncthing said, and whether it's not running / a bad key / a refusal.
- cassette/state.py: friends, invites and share selections, kept in the
  machine-local ~/.local/share/lintunes/cassette, never the synced data dir.
- cassette/host.py: one machine runs Cassette. config.json flags it,
  preferences.json names it, and the other machine greys the menu group
  out and says where friend sharing lives.
- Connections ▸ Sync Settings…: VLC-style list + page, editing a copy
  that only OK writes. General shows Syncthing's status with a next step
  for every failure, the display name and an avatar picker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 21:01:17 -07:00
travandClaude Opus 5 cc1ca5ef78 v0.17.0: the desktop already knows what the songs are
The first half of andTunes, a music player for the Rabbit R1. Auxio re-reads
Android's MediaStore on every launch, which is the "your songs will show up
here" hang — so the answer isn't a faster scanner, it's not scanning at all.
LinTunes already knows the artist, album and year of every file it just
copied, so it writes them into Music/andTunes/library.json and the app reads
one file instead of indexing a filesystem.

New lintunes/andtunes/: layout.py (a de-duplicated Media/<Artist>/<Album>/
tree, so a song in three playlists is stored once), manifest.py (pure, Qt-free),
art.py (one 480 px cover per album via QImage, cached and invalidated by the
audio file's mtime — which is exactly what embedding new art moves), and
sync.py, a third sibling of plan_export/ExportWorker.

Three rules the code depends on: planning never renders art (a mutagen open
per album, and planning runs on the GUI thread); the index is written last and
after a cancel lists only tracks whose files actually landed; and every delete
goes through layout.assert_inside, which is why the old Music/<Playlist>/
folders are structurally unreachable rather than merely un-referenced.

Connections gains "Sync to Rabbit" and "Rabbit Sync Settings…" — the ticked
set is the device's contents, so unticking is how a playlist comes off, which
nothing could do before. The selection rides preferences.json so both machines
agree. The old per-playlist sync stays, renamed "(Auxio)": removing it now
would leave the device full of files and nothing able to open them until the
app exists.

The app itself is rounds 48-50; its board is andtunes/TASKS.md, including the
measured screen facts (480x640 px at density override 160 — one dp is half its
usual physical size on a 2.88" panel).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015wrTys5U1fLb4pBD2LKWzV
2026-09-06 03:25:40 -04:00
travandClaude Opus 5 f0d4d1eff2 v0.15.0: ask the song what it is
Right-click → Identify Track… fingerprints the file with Chromaprint's
fpcalc and asks AcoustID who it is, then proposes the tags it found.

The year is the point. A 60s song kept getting stamped with the year its
CD reissue came out, which sorts the library wrong, so every candidate
carries the recording's *original* year — the earliest release across all
of its release groups — including the candidate that proposes a later
greatest-hits as the album. Ranking separately prefers a plain studio
Album over EP/Single over anything wearing a Compilation or Live
secondary type, so the default proposal is the real album as well.

Nothing is written until accepted: the dialog shows each current value
beside an editable proposed one, and a row starts checked only where the
proposal actually differs from what the file already has, so an untouched
field can never quietly blank a tag. Applying goes through
edit_track_fields, which is what buys the tag write, the abort-if-it-fails
rule, artist/album file relocation and one undo step without re-deriving
any of them.

A selection is a queue reviewed one track at a time; a failure is a
status-bar message that moves on rather than ending the batch. fpcalc is
detected at runtime like ffmpeg and the AcoustID key lives in preferences
(so it syncs, and stays out of git) — the round adds no new dependency.

Also fixes a pre-existing round 40 test that used the real tummult mount
point as its stand-in for an unreachable path, so it failed whenever that
drive was plugged in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsiFHdyVg1UhBSxDJfTNRm
2026-09-02 15:40:52 -04:00
travandClaude Fable 5 0d8c9dd033 Visualizer: gray-mode color slider + mode persists across launches
- New prefs: visualizer_mode (on/dim/off, saved on every click) and
  color_visualizer_gray (None = keep the derived alternateBase look).
- VisualizerWidget takes prefs (optional, so existing call sites/tests
  keep working); restores its mode at startup and validates junk values.
- New "Visualizer (gray mode):" row in the Preferences color sliders;
  theme.default_gray mirrors the widget's 0.88-lightness derivation so
  an untouched slider shows the current appearance.
- TransportBar.refresh_theme() repaints the visualizer so slider drags
  preview live.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 16:52:59 -04:00
travandClaude Opus 4.8 1dd128b7f4 Add per-area color preference sliders
Five grayscale (black->white) sliders in Preferences that update live:
outer-chrome background (QPalette.Window), the rounded transport button
boxes, the now-playing panel, the now-playing text, and the row stripes
(QPalette.AlternateBase). Defaults are None ("inherit current") so nothing
changes until the user drags. Decouples the button boxes from the stripe
color (they used to share palette alternate-base). Live preview via
Preferences.set_live (no disk write); persisted ~0.4s after the drag.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 21:41:49 -04:00
travandClaude Opus 4.8 bb31e9e5cc Add volume slider to the transport bar
A slim horizontal master-volume slider in its own slot between the
visualizer and the timeline (TASKS.md). Perceptual->linear gain so it
ramps like iTunes; persists to preferences.json (debounced so it doesn't
thrash the theme). Clicking jumps to the spot via a new ClickJumpSlider
base extracted from SeekSlider.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 21:21:00 -04:00
travandClaude Opus 4.8 f6d35fa594 Import existing LinTunes project
Snapshot of the existing codebase before working through the TASKS.md
backlog. Real library data (data/) and the iTunes import fixture
(itunes-test-library/) are gitignored.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 21:12:01 -04:00