128 Commits
Author SHA1 Message Date
trav 976d338574 v0.51.0: the web mix player has previous and next buttons 2026-10-10 23:54:57 -07:00
trav 96fac71075 v0.50.0: exports report the finished folder's size 2026-10-10 23:48:03 -07:00
trav 3dcd30eab6 v0.49.1: image pickers see uppercase suffixes like IMG_1897.JPG 2026-10-10 23:42:40 -07:00
trav 660740992a v0.49.0: a web mix can carry several images, one after another under the description 2026-10-10 23:39:06 -07:00
trav 9f390b6102 v0.48.2: the finished upload window hides Stop instead of greying it to blank 2026-10-10 23:29:58 -07:00
trav 4d7c17e41d v0.48.1: a web mix leaves a blank line between the description and the image 2026-10-10 23:29:46 -07:00
trav e9488c5289 v0.48.0: web mixes upload to a web server (rsync over ssh), and the player shuffles
Export Web Mix gets an 'Upload to web server' box: the mix is built in a
cache staging folder and mirrored with rsync --delete into <path>/<slug>/
on the host set in Preferences → Integrations → Web Server (config.json,
this machine only). An ssh check asks before replacing an existing mix;
a status window follows check, export and upload. The web player gains a
shuffle button (and the S key).
2026-10-10 23:23:56 -07:00
trav d4cf3f95c2 v0.47.1: the no-art panel sits on the window, shaded like the visualizer
With no art, the sidebar's art widget moves out of the playlist list's
frame onto the window below it, so the panel is set into the gray like the
visualizer rather than into a white box. A tall debossed panel is now
shaded along its top and bottom exactly as the 62 px visualizer is, flat
in the visualizer's color between, instead of one gradient stretched over
its whole height, which read as gray. The scrollbar thumb is shaded the
same way, like a button.
2026-10-10 23:03:47 -07:00
trav fbb439b7ee v0.47.0: no album art shows a debossed panel, not a flat square
The sidebar's art square, when a track has no art, is now the list's own
background with a rounded panel set into it, debossed like the visualizer
and the now-playing panel. paint_deboss takes its rims in pixels when
asked, so a panel as tall as the sidebar is recessed as much as a short
one rather than fading over 16 px.
2026-10-10 11:26:33 -07:00
trav 617402657f v0.46.0: scrollbars lose their arrows; the thumb is embossed like a button
The app style now paints the scrollbars itself: the trough runs the full
length with no arrow buttons, and the thumb gets the buttons' 4 px corners
and emboss, with rims as tall in pixels as a button's however long the
thumb. Bar thickness moved from QSS to the style's pixel metric, since any
QSS rule on a scrollbar part makes Qt draw the whole bar, arrows included.
2026-10-10 10:44:05 -07:00
trav c312751c86 v0.45.1: the laser's Afterimage is a number box, like Line width
Type the seconds, or step by 0.05 with the arrows or ↑/↓; it reads "Off"
at zero. Line width and Afterimage now share one number-box helper.
2026-10-10 02:24:17 -07:00
trav 43d2d21d9d v0.45.0: copying songs pastes into other apps as a song list
Ctrl+C (and Cut) on selected songs now also puts plain text on the
clipboard — one "Artist - Title" per line, in the order the table shows —
so a whole playlist pastes into a message or a document. Pasting back into
a playlist still reads the songs themselves, as before.
2026-10-10 02:17:00 -07:00
trav 491e31b4d6 v0.44.1: Preferences headings stay bold under the app's style sheet
The headings were set with setFont, which the app's own style sheet
(theme.apply_theme) overrides on re-polish — bold in a bare dialog, plain
in the running app. A style sheet on the heading itself wins. The test now
applies the theme first, as the app does.
2026-10-10 01:54:59 -07:00
trav 8339e0404f v0.44.0: per-track volume, laser rig diagram, bolder Preferences headings
Get Info has a Volume slider (−100%…+100%): iTunes' own per-song volume
adjustment, which the library already imported but never used. It scales
the volume slider while that song plays; a boost can't pass full volume.
Works on one song or several (Mixed when they differ), and undoes.

Get Info's Previous/Next buttons are gone: since the combined multi-song
editor they were always disabled (and blank in some themes).

The laser settings draw the rig — speaker and mirror, laser, beam, and the
screen at its distance and angles — as a small textbook line drawing that
labels distance, angles and dot size in place of the sliders' numbers.

Preferences section headings are bold and 2 px larger than the text.
2026-10-10 01:53:07 -07:00
trav 599972fa1a v0.43.0: visualizer presets
Each visualizer style's settings page has a Preset drop-down and a
"Save as Preset…" button. The drop-down lists the built-in Default, your
presets, and "Edit Presets…" (rename in place, delete); it reads "Custom"
whenever the settings match no preset. Presets sync with the preferences;
the per-machine sync delay is never part of one.
2026-10-10 01:41:01 -07:00
trav 8b42580f97 v0.42.0: laser speaker range, phosphor afterimage; line width box, radial dot size
Laser: a two-handle "Speaker range" sets what the speaker under the mirror
plays (36 dB/octave edges; the noise floor is measured per range so treble
alone still shows). Bass-only gives clean loops; treble-only is a shimmer
along one line, as a membrane far above its resonances really draws.

The afterimage is now a phosphor buffer, dimmed each frame and rounded
down so it always fades to nothing — constant cost however long the fade
(drawing every past sweep ran the big window past a frame).

Line width is a number box stepping by 0.5; radial dots have a size slider.
2026-10-10 01:24:37 -07:00
trav 726f7c4a89 v0.41.0: visualizer sync, fade sliders, laser look controls, custom highlight
Sync: decoding runs ahead of the speakers — by up to ~0.8 s after a GUI
stall, when buffers arrive in a burst — and the visualizer drew the newest
audio. It now keeps timestamped history and draws what the player says is
playing, plus a per-machine Sync delay (config.json) for latency Qt can't
see, like Bluetooth.

Every style has one Fade slider (0 = off); bars now leave fading steps.
Laser: afterimage time, line width down to 1 px, glow on/off, tip dot
on/off and size (a resting beam always shows its dot), and an oscilloscope
dot grid under the beam. Visualizer sliders run the full window width.

Highlight: click a swatch for a full color picker; the color picked
replaces that slot (right-click restores it).
2026-10-10 01:11:09 -07:00
trav 751273b14c v0.40.0: visualizer settings — laser screen angles, radial resolution
Laser: the screen's distance, pitch, yaw and roll are sliders (the beam ray
is intersected with the turned screen; the drawing shrinks only when a
steep screen would push the figure out of view), and the afterimage can be
turned off. The membrane is now a slack 50 Hz balloon whose split rocking
pairs land in the bass, so it draws loops ~60% of the time instead of ~25%.

Radial: fading trails can be turned off; the number of frequency bands and
volume steps are sliders; ghosts fade from the dot's own color into the
background; a 4096-point FFT and interpolation end the flat bass arcs; and
"Even out bass and treble" takes the song's overall tilt out so the dots
sit on a circle.
2026-10-10 00:51:33 -07:00
trav a81ab1e163 v0.39.0: visualizer styles — radial dots and a laser, double-click to enlarge
Preferences → Appearance → Visualizer picks Bars (as before), Radial dots
(the EQ in a circle: angle is frequency, radius is volume, moving dots
leave light-gray ghosts that fade over a set time), or Laser (a mirror on
a speaker membrane bouncing a beam onto a screen: simulated membrane
modes, D·tan(2θ) reflection, dwell-weighted brightness). Double-clicking
the panel opens it big; a click closes it.
2026-10-10 00:26:29 -07:00
trav f079d26416 v0.38.0: several imported songs are identified as one batch
No identify dialog pops up mid-download any more: songs are looked up
quietly as they land and reviewed together once the last one is in, as
rows in the app's columns. Songs that are one album get the album-wide
fields once, unified, plus a cover from the album-art search. Ticked
rows apply as one undo step; unticked ones get the ordinary dialog
after. Popups wait for LinTunes to be the app in front.
2026-10-09 01:42:13 -07:00
trav f164a22b64 v0.37.0: Import from URL takes Spotify links
A Spotify track, album or playlist is read off Spotify's public embed
page, each song found on YouTube by the result closest to Spotify's
length, downloaded with the song flags, tagged with Spotify's
artist/title/album/track number, then imported and identified.
2026-10-09 01:09:46 -07:00
trav 25b25f61fb v0.36.0: a playlist link says so before it downloads
Pasting a song from a YouTube Mix (watch?v=…&list=RD…) imported song
after song: yt-dlp takes the whole list by default, and a Mix runs to
thousands of rows and loops back on itself, so the linked song came in
again and again with nothing on screen saying why.

Now anything but a plain song link is listed first (--flat-playlist,
streamed into the dialog). One song goes straight through. Several become
a checklist: a song-in-a-list link ticks only its song and offers Just
This Song without waiting, and a playlist link ticks everything. Rows are
de-duplicated by id. The chosen songs download by their own pages, never
the list link, and more than one gets a per-song progress window (Hide
keeps downloading; the status-bar line brings it back).
2026-10-02 17:41:55 -07:00
trav d6140f5ac4 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.
2026-10-02 16:47:40 -07:00
trav 10ced778af v0.34.0: Calculate BPM from the audio, on the right-click menu
Spectral-flux onsets + autocorrelation in numpy, decoded by the ffmpeg CLI;
no new dependency. Runs on a worker with the status-bar progress bar, for
one track or many, and the batch is one Ctrl+Z.
2026-10-02 14:55:35 -07:00
trav 481edd65ce v0.33.0: title bars drawn by LinTunes, in the window's grey
On Wayland Qt paints the title bar through its adwaita decoration plugin,
which hardcodes pure white in light mode and ignores the palette. Every
window now goes frameless and gets an app-drawn bar in the palette's Window
color, so it follows the Background setting. Buttons follow GNOME's
button-layout; moving, snapping and edge resizing still go through the
compositor. Windows grow by the bar's height so their contents keep the
size they had.
2026-10-02 00:55:01 -07:00
trav 9e83b29778 new screenshot 2026-10-02 00:34:26 -07:00
trav 0b639909be moved docs around 2026-10-02 00:30:02 -07:00
trav 4f795fec95 docs 2026-10-02 00:11:50 -07:00
trav 154cb9ca6e docs 2026-10-02 00:03:52 -07:00
trav 176ec7bcce trave cleaned up the readme 2026-10-01 23:58:57 -07:00
trav 88029cdf6b trave cleaned up the readme 2026-10-01 23:57:25 -07:00
trav 3aeeee6da0 README: replace terminal intro with a Linux quick start; clone over HTTPS 2026-10-01 21:28:47 -07:00
trav b81969a0cc v0.32.1: a stuck Rabbit connection can no longer freeze the window
Opening Connections called find_device() on the GUI thread, which lists the
gvfs-MTP mount; on a wedged mount that blocks forever in an uninterruptible
FUSE wait and the app had to be force-quit. DeviceWatcher now probes on a
daemon thread (one at a time, STUCK after 3 s, late answers still land), the
menu reads its cached answer, and a stuck mount shows "Rabbit isn't
responding…" with a Reset Connection button (kill gvfsd-mtp, remount via
gio). Tests can no longer reach the real mount (conftest patches
find_device), which also takes the suite from ~200 s to ~22 s.
2026-10-01 00:58:26 -07:00
trav 3888248231 v0.32.0: folder tree in Rabbit Sync Settings, quieter art pass, tink, VLC-style Preferences
- Rabbit Sync Settings shows the sidebar's folder tree. A folder has its own
  tristate checkbox; a fully ticked folder is stored as device_sync.folder_ids
  and follows the folder, so playlists added to it later sync too.
- The album-art pass compares covers against the sizes the planner's device
  scan already read (plan.art_sizes) instead of an MTP stat per album, caches
  "no art" as a .none marker, and only unlinks covers that are really there.
  The summary reports covers actually updated rather than every cover.
- A quiet tink when a Rabbit sync, export or URL import finishes, with an
  on/off switch (sound_effects, on by default).
- Preferences is now General / Appearance / Integrations, a list on the left
  and the page on the right, opened big enough for the tallest page.
2026-10-01 00:45:42 -07:00
trav 5c19eb7b8e v0.31.9: Share Library dialog opens tall enough for all its text 2026-09-30 23:56:55 -07:00
trav b61808ddc9 v0.31.8: gap between playlists and songs matches the left inset 2026-09-29 17:14:29 -07:00
trav 9bd5990e72 Remove leftover brainstorm notes and an unused icon 2026-09-29 08:17:54 -07:00
trav 928d0a1a7b v0.31.7: fixed 16px gaps in the top row, panel fills the rest; right inset
The row of boxes keeps one 16px gap before, between and after its
controls at every width; the now-playing panel and the seek row under it
take all the remaining width. The track lists get the same 8px of bare
window on the right that the playlists have on the left. README updates.
2026-09-28 23:45:34 -07:00
trav 9549a140b5 v0.31.6: album art inside the playlists' frame; text and playhead lifted 2px
The playlist list and the album art under it share one frame (the list's
own style, drawn around both), and the no-art placeholder is a shade
lighter than that frame line so the line still reads around it. The
playlist name, times and playhead sit 2px up (HeaderRow.LIFT), on the
Library button's bottom line by eye.
2026-09-28 23:36:48 -07:00
trav 49b7126765 v0.31.5: Library button edge to edge with the playlists; one floor; floating top row
The sidebar gets 8px of bare window on its left, and the Library button
spans exactly the playlist list below it, scrollbar included. The Library
button, search box, playlist name, times and playhead all stand on one
floor 5px above the lists (text by its baseline). The top row floats:
equal space before, between and after the controls, with the now-playing
panel a fixed share of the width (700px at 1920). The header row grows if
the app font makes the Library button taller than the strip.
2026-09-28 23:04:12 -07:00
trav 429f543812 v0.31.4: wider now-playing panel, tighter knolled top row
The now-playing panel (and the seek row under it) caps at 700px, up from
560. The top row gets one equal gap before, between and after its
controls, capped at 28px; past that the extra splits between the two
ends, so the row sits centered instead of spreading out.
2026-09-28 22:51:05 -07:00
trav a9c815ba1a v0.31.3: header row moves up beside the seek bar; knolled top row
The Library button, the view's header (search box or playlist name) and
the seek row now share one band under the transport boxes, instead of the
seek row hanging alone with a header strip below it. The now-playing panel
caps at 560px and the gaps between controls share the rest evenly; more
room under the menu bar. Long playlist names shrink up to 2pt, then elide.
2026-09-28 22:43:21 -07:00
trav f280fc5330 v0.31.2: panels pushed in, buttons pushed out; black bpm text
- The now-playing panel and the visualizer are debossed by exactly as
  much as the buttons are embossed: the same rims and outline, flipped
  (theme.deboss_qss / theme.paint_deboss), so things you press stand out
  and things that show you something sit in.
- The bpm button's text is black, enabled or not.
2026-09-28 22:23:34 -07:00
trav 2d0f077af0 v0.31.1: one button look — crisper emboss, darken on hover
- The emboss hugs the edges: thin lit and shaded rims, a defined outline
  that's a touch darker along the bottom, and a nearly flat face — shape
  without depth.
- Hover darkens the whole button slightly instead of Qt's pale panel on
  top of it.
- One face for every button (theme.button_face_qss): regular buttons,
  Library and its ▾, the purple friend-mode buttons, and the transport
  boxes. Shuffle and bpm are boxes that are one button, so the whole box
  darkens (bpm's whole box is now its tap target); the play box's three
  tiles each darken on their own.
2026-09-28 22:17:05 -07:00
trav f1f5f450ff README: new features, and a Cassette guide for first-timers
Adds the features shipped since the list was written (Identify Track,
Import from URL, andTunes, art sources, New Tracks, merge improvements,
Cassette) without touching the existing lines, and a step-by-step guide
at the end: installing Syncthing, inviting a friend, choosing what to
share, cassetting songs, following playlists and troubleshooting. Icons
and screenshots in docs/cassette/ are rendered from the app's own
painting code against the synthetic dev library.
2026-09-28 22:06:35 -07:00
trav 35281b9864 v0.31.0: Cassette followed playlists — a friend's playlist in your sidebar
- A followed playlist (the UFO in friend mode) appears in my sidebar with
  a purple UFO: mixed in with my playlists alphabetically, or inside a
  folder named after the friend (per-friend, in Sync Settings). It's never
  a Playlist of mine, so it stays out of the library and its merges.
- It plays from the cache. Player's queue accepts friend-track keys,
  resolved through Cassette: they scrobble to Last.fm but don't count in
  my play journal. Songs still on their way are grey and skipped.
- The cassette column there is hidden by default (right-click the
  header); pressing one takes the song into my library — straight from
  the cache if it's there, otherwise by asking for it.
- Sync Settings per friend: keep followed songs separate (default) or add
  them to my library. Unfollowing (right-click) frees their cache.
- The bottom bar says when the friend's folder last synced.
2026-09-28 21:40:59 -07:00
trav cfeeeb2bf2 v0.30.0: Cassette delivery — requested songs arrive in your library
- Sending: a friend's requests ∩ what I still offer is copied into my
  outbox (via a .part name Syncthing never sees); whatever they no longer
  request is deleted, so the outbox cleans itself up.
- Receiving: a finished file I cassetted is imported exactly like Add to
  Library — organized into Artist/Album, tags read, date added now, none
  of their listening history — and a song wanted only for a followed
  playlist lands in the cache. Then requests.json is rewritten without
  what arrived and without what they stopped offering.
- Safe to repeat: Syncthing temp files ignored, Syncthing asked whether a
  file is whole, and an exact match already in my library is never
  imported twice.
- Folders are watched (re-armed after each rename), with a slow fallback.
- file_importer split into stage_file (off the GUI thread) and
  track_from_file (date added = now).
- pytest --syncthing now runs a real invite → share → request → deliver →
  cleanup between two Syncthing instances.
2026-09-28 21:32:25 -07:00
trav 8d2af17c70 v0.29.0: Cassette friend mode — browse a friend's library, cassette songs
- A ▾ cut from the right end of the Library button (shown once there's a
  friend) opens a popup of friends. Choosing one enters friend mode: the
  button becomes "<Name>'s Library" in light purple, the sidebar lists
  their shared playlists with a UFO follow toggle on each, and the content
  area is their library — browse only, nothing plays or edits.
- A cassette column on the far left and cassette buttons on every artist
  and album: pressed grey = send me this, medium grey on hover to let go.
  An artist cassettes all its albums and tracks (a snapshot).
- Bottom bar: Hide tracks I have (exact matches: artist, album, title,
  length within 2 s); close matches show light grey. Size of what's
  cassetted against free space: orange when Syncthing would stall at its
  free-space floor, red when it won't fit.
- Save and Close writes requests.json; Cancel discards; the ▾ or quitting
  with changes asks Save / Don't Save / Cancel.
2026-09-28 21:27:06 -07:00
trav 662217a7b7 v0.28.0: Cassette share selector — choose what friends can cassette
- Sync Settings gets a share selector on each friend page (and on General
  when "Share the same selection with all friends" is on; each friend's
  own selection is kept, greyed, and comes back when it's turned off).
- Library: a genre/artist/album column browser with a checkbox beside
  every artist, album and track, search, Select All / Select None and
  "Share whole library". Playlists: a checklist with "Share all
  playlists". Nothing is shared by default.
- Checking an artist or album is a snapshot of its tracks; the whole
  library and shared playlists are live. A shared playlist's tracks are
  checked and locked, with a tooltip naming the playlist(s).
- cassette/share.py writes the trimmed copy (library.json, playlists.json)
  into each friend's folder: tags yes, listening history and local paths
  no. Written on OK and after library edits (debounced), and only when the
  bytes would change.
2026-09-28 21:18:14 -07:00
trav 5520eb333e v0.27.0: Cassette invites — one paste connects two friends
- Connections ▸ Share Library with a Friend… makes a one-time invite code
  (device id, display name, token) and the inviter's send-only folder.
- Connections ▸ Add Friend Library… takes the pasted code: adds the
  inviter, shares our folder, and accepts theirs receive-only when it's
  offered back.
- Syncthing hides an unknown device's folders (verified on 1.30), so the
  inviter probes a new knock only while an invite is open: added with
  nothing shared, completed on the right token, otherwise removed and
  never probed again. Used and cancelled codes go nowhere.
- Sync Settings lists open invites (Cancel Invite) and gives each friend a
  page: avatar, plain-language status with a next step, last seen, last
  synced, Remove Friend. Removals are staged until OK.
- scripts/cassette_pair.py runs throwaway Syncthings on loopback;
  pytest --syncthing drives a real three-machine handshake.
2026-09-28 21:13:07 -07:00