travandClaude Opus 5 afadf31a99 v0.8.0: export a playlist — as a folder, or as a whole website
File → Export Playlist…, and the same item on a playlist's right-click
menu. A sibling of device_sync: pure plan_export() first, then a worker
on a daemon thread sharing the status-bar progress widgets. The manifest
(index.html / .m3u) is written last, so an interrupted export never
leaves a page naming files that aren't there.

Two destinations. A folder gives the audio under "Artist - Title" names
plus an extended .m3u. A web mix gives a self-contained static site
reproducing the hand-made yearly mixes, with a dialog for title,
description and hero image.

audio.js and jQuery are gone. The old pages shipped ~293 KB: a build of
audio.js whose upstream hasn't moved since 2012, plus jQuery 3.2.1
(CVE-2019-11358, CVE-2020-11022, CVE-2020-11023 — unexploitable on a
static page, since nothing untrusted reaches a jQuery HTML sink, but
dead weight regardless). audio.js never used jQuery; jQuery was there
for ~25 lines of tracklist glue. Both are replaced by a dependency-free
player.js plus a player.css transcribed from the customized audio.js
skin, so the page looks identical — same 250px #c7b563 bar, same
player-graphics.gif (byte-identical: it's an animated GIF whose loading
frame is a spinner), same shortcuts. ~293 KB → ~6 KB. The Flash
fallback went too; audio.js gated it on !canPlayType("audio/mpeg;"),
unreachable since ~2010.

Conversion happens only when a browser genuinely can't decode a file,
never because of bitrate — a 320 kbps MP3 is copied verbatim. Everything
that does convert targets FLAC, so a conversion cannot cost a bit; a
test asserts the exported FLAC's decoded PCM hashes identical to the
ALAC source. Against the real library that's 105 Apple Lossless and 8
AIFF out of 21,382 tracks. DRM'd tracks are reported in a confirmation
dialog, never silently dropped and never attempted. ffmpeg is the CLI
binary here, not Qt's ffmpeg backend, so it's detected at runtime.

Verified in Chromium 151 and Firefox 153 over CDP/Marionette: every
exported format decodes including the converted FLAC, the player builds,
click / space / arrows / scrubber-seek / autoplay-next all work, and the
network log shows no request for jquery, audio.min.js or any .swf.

mix-example/ removed; the template reproduces it.

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

LinTunes

LinTunes

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)

How it works

  • Your music files are never moved or rewritten (except when you edit tags).
  • The library lives as plain JSON files (library.json + one file per playlist) in a directory you choose — designed to be synced with Syncthing; 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).

Why

I have been a mac user for ~34 years. I gave up daily driving mac os in 2020. I figured Apple

but my music library even some tracks I got from napster all the way back in 2000 (I have since paid for!!)

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:

  • pip — simplest where your distro allows it; also puts a lintunes command in ~/.local/bin:
    pip install -e .
    
  • Debian/Ubuntu — system pip is locked down (the externally-managed-environment / PEP 668 error), so install the deps from apt and skip pip entirely:
    sudo apt install python3-pyqt6 python3-pyqt6.qtmultimedia \
                     python3-mutagen python3-numpy python3-requests
    
    (If python3-pyqt6.qtmultimedia isn't found, apt search python3-pyqt6 and grab the multimedia one.) There's no lintunes command this way — run it with python3 -m lintunes.main.
  • venv — isolated:
    python3 -m venv .venv && .venv/bin/pip install -e .
    
    Run with .venv/bin/lintunes. One caveat: the dock launcher (see App icon) uses the system python, which can't see a venv's packages — so for the icon to work, edit the installed lintunes.desktop's Exec= line to the absolute venv path (…/lintunes/.venv/bin/lintunes %F).

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.

Running it

# 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)

(Installed with pip? Type lintunes instead of python3 -m lintunes.main.)

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 --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.

Running on a second machine (Syncthing)

Keep the data dir (which holds library.json, the playlists, and preferences.json) inside your Syncthing-shared music folder, so the music and the library travel together. Track paths are stored relative to the data dir, so the library resolves correctly no matter where each machine mounts the shared folder — you never edit anything inside the library to move it.

~/.config/lintunes/config.json is per-machine (it is not synced), so on a new machine you just tell LinTunes where the synced folder landed:

# 1. get the code, then install the deps (see "Installing" above: pip / apt / venv)
git clone ssh://git@git.autonomic.zone:2222/trav/lintunes.git
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

# 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

App icon

packaging/install-desktop.sh installs the launcher entry and icon for your user (lets you pin LinTunes to the GNOME dash). The icon is just a file — replace packaging/lintunes.png (256×256 PNG) and re-run the script to use your own. GNOME caches icons, so if the old one lingers, log out and back in.

Unpinning from the dash does not uninstall LinTunes — it's still in the GNOME app grid (open Activities and search "LinTunes"). Right-click it there → Pin to Dash to get it back.

Development

python3 -m pytest tests/

spec.md is the original design brief; tasks*.md track what's built.

S
Description
No description provided
Readme
2.2 MiB
Languages
Python 98.4%
JavaScript 0.7%
CSS 0.5%
Go Template 0.2%
Shell 0.2%