v0.10.0: a first launch that welcomes you

Three things that only ever hurt new users.

The startup font modal is gone. _ensure_now_playing_font ran before
MainWindow existed, so on a machine without Century Gothic the very first
thing LinTunes did was open a parentless dialog demanding a font decision.
theme.NOW_PLAYING_FALLBACKS now picks the closest geometric sans installed
(URW Gothic, the Avant Garde clone CG derives from, leads the chain), and
the choice moved to Preferences ▸ Now-playing font. An uninstalled saved
family falls back to automatic instead of the app default.

A non-iTunes user can finally set their music folder. library.music_folder
was written in exactly one place — the iTunes importer — and with it unset
_music_import_dir fell back to a *relative* Path("Music"), resolved against
a working directory GNOME's dash does not set predictably (see
packaging/install-desktop.sh). Music scattered somewhere unfindable. Now
the first launch asks one plain-language question, Preferences can change
it later, and an import with no folder set refuses rather than guessing.
Picking ~/Music files into ~/Music, not ~/Music/Music.

music_folder is portable at last. It was the only path in the library
stored raw absolute in the *synced* metadata, so machine 2 inherited
machine 1's paths. It is now also stored relative to the data dir, the
same trick Track.location has used all along. The absolute key stays
forever as the shared floor between versions: old code reads it and
behaves exactly as before, and old code that writes the file just drops
the new keys, so no version combination hard-fails.

Two traps worth naming. set_music_folder must call
mark_library_settings_dirty() or reload_from_disk reverts the change on
the next sync tick. And the dirty flag only guards until flush, so a
music_folder_set_at stamp decides adoption semantically — _merge_metadata
picks the whole file by mtime, which moves when someone resizes a column
(the Round 39 lesson, applied to metadata).

Also: correct CLAUDE.md's claim that the importer skips smart playlists —
it imports them; system playlists are what's skipped. README gains a
"never used a terminal?" on-ramp and loses the instruction to hand-write
library_metadata.json before first launch.

Verified against the live 21,490-track library: still resolves (via the
legacy key), metadata untouched, 646 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G4Z46BMQYS57bcbxWbSS3C
This commit is contained in:
2026-08-22 13:04:24 -04:00
co-authored by Claude Opus 5
parent 574e476dc3
commit 9ef59dd3b2
15 changed files with 1340 additions and 124 deletions
+160 -50
View File
@@ -2,49 +2,110 @@
![LinTunes](screenshot.png)
an mp3 library manager and player for linux. Absolutely no guarantees, if it wrecks your itunes library or wipes your
harddrive that's on you (maybe just have your LLM of choice review the
software for bugs and vulns?).
an mp3 library manager and player for linux. Absolutely no guarantees, if it wrecks your itunes library or wipes your harddrive that's on you (maybe just have your LLM of choice review the software for bugs and vulns?).
## Features
- can import an iTunes 12 (untested on other versions) library, including
smart playlists (auto-updating, editable, marked with a ❧)
- runs on several machines off one Syncthing-synced library (see below)
**Library**
- Sortable track list with 24 optional columns (plays, skips, rating, BPM,
bit rate, date added…) — column choice, sort and widths are remembered
*per playlist*.
- **Column browser** (Ctrl+B) — filter by genre / artist / album, iTunes-style.
- Instant **search** over name, artist, album, composer and more.
- **Get Info** (Ctrl+I) edits tags on one track or a whole selection at once,
writing them back to the file. Artwork can be pasted, dragged in, or fetched
automatically (**Download Album Art…**, via the iTunes Search API).
- Rename an artist or album and the files **move themselves** into a tidy
`Artist/Album/` tree.
- **Undo/redo everything** (Ctrl+Z / Ctrl+Shift+Z) — edits, deletes, reorders.
- Deleting a track sends the file to your desktop **Trash**, never `rm`.
**Playlists**
- Regular playlists, **folders**, and **smart playlists** (auto-updating, with a
full rule editor — nested groups, limits; marked with a ❧).
- Drag and drop to reorder or insert at a spot; duplicate-aware adds;
copy/cut/paste tracks between playlists.
**Playing**
- mp3, m4a, flac, aac, wav, aiff via Qt Multimedia/FFmpeg.
- Shuffle, per-track **start/stop times**, play counts, skip counts and ratings.
- **Media keys** work (MPRIS), plus Space and ←/→ anywhere in the app.
- Live **spectrum visualizer** — click it to cycle bright / dim / off.
- Optional **last.fm scrobbling** (Edit ▸ Preferences).
- Theming: highlight color, UI scale, and custom colors for most of the chrome.
**Sending music elsewhere**
- **Chromecast** (Connections menu) — streams the original file bit-for-bit,
no transcode, with album art on the TV.
- **Export a playlist** (File ▸ Export Playlist…) — either a folder of
`Artist - Title.mp3` files plus an `.m3u`, or a self-contained **web mix**
(`index.html` you can upload anywhere).
- **Sync a playlist to a Rabbit R1** over MTP, `.m3u` included.
**Living on more than one machine**
- The whole library is **plain JSON**, designed to be synced with
[Syncthing](https://syncthing.net). Sync conflicts merge themselves on
startup, and a change synced in from elsewhere shows up **while the app is
running**.
- Play counts can't conflict: every machine keeps its own tally and they're
summed.
- The version number in the status bar checks for updates and, on a click,
pulls and restarts.
## How it works
- Your music files are never moved or rewritten (except when you edit tags).
- Your music files are never moved or rewritten (except when you edit tags,
rename an artist/album, or delete a track on purpose).
- The library lives as **plain JSON files** (`library.json` + one file per
playlist) in a directory you choose — designed to be synced with
[Syncthing](https://syncthing.net); sync conflicts are merged automatically
on startup (play counts take the max, edits take the newest, playlists
take the union).
- Playback via Qt Multimedia/FFmpeg (mp3, m4a, flac). Media keys work
through MPRIS. Scrobbling to last.fm is optional (Edit → Preferences).
playlist) in a directory you choose. Track paths are stored *relative to that
directory*, so the library works no matter where each machine mounts it.
- Playback via Qt Multimedia/FFmpeg. Media keys through MPRIS. Scrobbling to
last.fm is optional (Edit → Preferences).
## Why
I have been a mac user for ~34 years. I gave up daily driving mac os in 2020. I figured Apple
I have been a mac user for ~34 years. I gave up daily driving mac os in 2021. I got tired of my music being solely on my desktop for 5 years. So I vibe-coded this replacement of all the features I wanted from iTunes.
but my music library
even some tracks I got from napster all the way back in 2000 (I have since
paid for!!)
## Never used a terminal? Start here
If the commands below look intimidating, they're friendlier than they look.
The **terminal** is a window where you type commands instead of clicking. On
most Linux desktops you open it by pressing the Super key (the one with the
Windows/Command logo) and typing "terminal". Everything in a grey box below is
meant to be **copied and pasted** into that window — paste it, press Enter,
done. You don't need to understand a command to run it.
Two bits of shorthand that show up everywhere:
- **`~`** means your home folder. `~/Music` is just the Music folder inside your
home folder — the same one your file manager shows you.
- **Your "distro"** is which flavour of Linux you're running (Ubuntu, Fedora,
Debian, Mint…). If you don't know, it's almost certainly Ubuntu or Mint, and
you want the **Debian/Ubuntu** instructions below.
If a command fails, nothing is broken — copy the error into a search engine, or
ask whoever pointed you here. You cannot damage your music by running these.
## What you need
Linux, Python, and a handful of free add-ons. **Nothing to compile, no account
to make, no database to set up, nothing that phones home.** LinTunes keeps your
library as ordinary files on your own computer.
In detail, for anyone who wants it:
- **Linux** with Python **3.10 or newer** (already installed on essentially
every Linux machine — check with `python3 --version`)
- **PyQt6** (including its QtMultimedia part), **mutagen**, **numpy**, **requests**
- The **FFmpeg codecs for Qt Multimedia**. Without these the window opens fine
but nothing plays — so if you get a silent LinTunes, this is the thing that's
missing. They come from `qt6-qtmultimedia` built with ffmpeg (Fedora: enable
RPM Fusion; Debian/Ubuntu: it comes along with `python3-pyqt6.qtmultimedia`).
## Installing
LinTunes needs four Python packages — **PyQt6** (with its QtMultimedia module),
**mutagen**, **numpy**, **requests** — plus the **FFmpeg codecs** for Qt
Multimedia so audio actually plays. Nothing compiles; get the deps whichever way
fits your distro:
Pick whichever fits your distro:
- **pip** — simplest where your distro allows it; also puts a `lintunes` command
in `~/.local/bin`:
@@ -71,28 +132,85 @@ fits your distro:
**You never strictly need pip or the `lintunes` command.** As long as those four
deps import, `python3 -m lintunes.main` from the checkout runs the app — which is
exactly what the desktop launcher does. The FFmpeg codecs come from
`qt6-qtmultimedia` built with ffmpeg (Fedora: RPM Fusion; Debian/Ubuntu: pulled in
by `python3-pyqt6.qtmultimedia`). Without them the UI runs but tracks won't play.
exactly what the desktop launcher does.
Below, `lintunes` and `python3 -m lintunes.main` are interchangeable — use
whichever your install gave you.
## Starting from scratch (no iTunes needed)
LinTunes doesn't care where your music came from — a shelf of ripped CDs, a
Bandcamp folder, twenty years of downloads. You do **not** need iTunes, and you
don't need to have ever owned a Mac.
Tell it where to keep the library, once:
```sh
mkdir -p ~/Music/lintunes # the library lives here
lintunes --data-dir ~/Music/lintunes --save-config # remembers it from now on
```
Then just run `lintunes`. The first time it opens, it asks one question — where
to keep your music — and suggests `~/Music`. Pick a folder, or click **Not
Now** and set it later in **Edit ▸ Preferences**.
Now add music: **File ▸ Add Files to Library** (Ctrl+O), or drag files and
folders straight onto the window. Whole folders are fine — LinTunes looks
inside them, reads each song's tags, and skips anything already in your library.
Your songs are **copied** into the folder you chose and filed by artist and
album, iTunes-style:
```
~/Music/Neko Case/Fox Confessor Brings the Flood/Star Witness.mp3
```
The originals are left exactly where they were, so nothing is lost if you
change your mind. From there, make playlists and go.
## Importing an existing iTunes library
In iTunes 12.x: **File ▸ Library ▸ Export Library** to get the XML, then:
```sh
lintunes --import-xml "iTunes Library.xml" \
--music-root "/path/to/iTunes Media" \
--data-dir /path/to/library-data --save-config
lintunes # from now on, just run it
```
This brings across your tracks, playlists, playlist folders and smart playlists
(auto-updating and still editable), along with play counts, ratings and dates.
Mac paths are remapped to `--music-root` automatically, including
case/accent differences. Anything it couldn't find is written to
`import_missing_files.txt` in your data dir. Untested on iTunes versions other
than 12.
## Running it
```sh
# one-time import from iTunes (XML: File ▸ Library ▸ Export Library in iTunes 12.x)
python3 -m lintunes.main --import-xml "iTunes Library.xml" \
--music-root "/path/to/iTunes Media" \
--data-dir /path/to/library-data --save-config
python3 -m lintunes.main # run the app (uses the saved config)
lintunes # uses the saved config
```
(Installed with pip? Type `lintunes` instead of `python3 -m lintunes.main`.)
LinTunes looks best with **Century Gothic** installed
(drop the font in `~/.local/share/fonts/`); if it's missing, the first launch
asks you to pick a substitute or use the default.
LinTunes looks best with **Century Gothic** installed (`~/.local/share/fonts/`);
if it's missing, you'll be asked to pick a font on first run.
The desktop launcher runs LinTunes with no arguments, so it always uses whatever
is saved in `~/.config/lintunes/config.json` — point that config wherever you
want and the dock icon follows.
The desktop launcher runs LinTunes with no `--data-dir`, so it always uses
whatever is saved in `~/.config/lintunes/config.json` — point that config wherever
you want and the dock icon follows.
The now-playing panel uses **Century Gothic** if you have it and quietly picks
the closest match installed if you don't — it never interrupts you to ask. To
choose a different one, see **Edit ▸ Preferences ▸ Now-playing font**.
## Keys
Space play/pause · ←/→ previous/next · Ctrl+B column browser ·
Ctrl+I get info · Ctrl+L go to current song · Ctrl+O add files ·
Ctrl+N new playlist (Ctrl+Alt+N smart, Ctrl+Shift+N folder) ·
Ctrl+Z undo · Ctrl+, preferences · Ctrl+C/Ctrl+X/Ctrl+V copy/cut/paste tracks ·
double-click sidebar art for a big art window
## Running on a second machine (Syncthing)
@@ -112,23 +230,15 @@ cd lintunes
# 2. let Syncthing finish replicating the music folder, then point the config at
# THIS machine's paths and launch — no re-import, the data is already synced:
python3 -m lintunes.main --data-dir "/path/to/synced/music/lintunes" \
--music-root "/path/to/synced/music/iTunes Media" \
--save-config
python3 -m lintunes.main --data-dir "/path/to/synced/music/lintunes" --save-config
# 3. install the launcher + icon, then pin it (see "App icon" below)
bash packaging/install-desktop.sh
```
(Step 2 just writes `data_dir` + `music_root` into `~/.config/lintunes/config.json`
— you can also create that file by hand. After it's saved, every launch, including
the dock icon, uses the synced library automatically.)
## Keys
Space play/pause · ←/→ previous/next · Ctrl+B column browser ·
Ctrl+I get info · Ctrl+, preferences · Ctrl+C/Ctrl+X/Ctrl+V copy/cut/paste tracks ·
double-click sidebar art for a big art window
(Step 2 just writes `data_dir` into `~/.config/lintunes/config.json` — you can
also create that file by hand. After it's saved, every launch, including the
dock icon, uses the synced library automatically.)
## App icon