Go back to the hardlink deduplicator (1da0132), keeping backups

Restores the code of 1da0132 ("Add a setting to hide the quarantine from
Plex") and removes everything added after it: the tag editor, album art,
organizing/naming, the step-by-step tune-up and library types.

Kept:
- Database backups (Settings -> Backups): scheduled, manual, download,
  restore, and the optional backup before every action.
- The .env-driven docker-compose.yml for Arcane.

Databases that already ran the later migrations keep working: migrations
6-10 stay in the list (they only add columns) so numbering never goes
backwards, and Undo refuses History entries of removed features instead
of treating them as links.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cdnutterandClaude Opus 5.5 committed 2026-09-30 10:43:29 -07:00
1 parent 7b180e26db
commit e0617047f7
44 files changed
+103 -10456

No files matched your search

+5 -86
View File
@@ -7,32 +7,6 @@ up disk space only once, while both libraries keep looking exactly the same to P
It ships as a single Go binary with an embedded React web UI and SQLite state, packaged as a small Docker image, in the style of
[qui](https://github.com/autobrr/qui).
## Workflow: tune up, then link
Each library pair has a **type**: **Music** (recommended layout `Artist/Album/01 Title.flac`) or **Soundtracks** for video game and film
soundtracks (`Album/01 Title.flac`, named by game or film). The type sets the default naming; the *Organize* tab can change it.
The **Tune-up** tab takes each library through the same steps, the **master first, then the slave**, and then links the two:
1. **Folder structure.** Every album in its own folder at the right depth. Songs outside an album folder move into one named from their
tags; extra folders around an album (a composer folder around a soundtrack, `Metal/Artist/Album`, a `FLAC` folder inside an album) are
removed; songs in disc folders (`CD2`, `Disc 2`) get their disc number; missing titles, track numbers, albums and artists are filled in
from the folder and file names (`Album (2010) [FLAC]` → `Album`). When several albums sit in the same extra folder it may be a category
you want to keep (`Video Games/…`), so those moves are listed but not selected, with a hint to give that folder its own naming.
2. **Album tags** (optional). Each album is looked up on MusicBrainz: the best few releases are compared with your songs' titles (so a promo
single never beats the album it is named after), songs are matched to tracks by number or title, and the corrected titles, track and
disc numbers, album artist and date are shown per album for review, with a confidence rating. You can pick another release or edit the
album by hand. In the slave, albums that are also in the (fixed) master take the master's tags instead, so both libraries match.
MusicBrainz allows about one request a second, so large libraries take a while; limit it to a folder or skip albums whose tags are already
complete.
3. **Folder names.** Artist and album folders are renamed from the tags. Whole album folders move, so covers and booklets stay with them.
4. **File names.** Songs are renamed from the tags; multi-disc albums get `Disc 01`, `Disc 02` folders.
5. **Link the libraries.** Scan, fix any folders still named differently, link the identical songs, then open **Review** for the rest.
Every step is a check first: nothing changes until you apply the fixes you selected, each fix is an operation in History that can be undone,
and dry run applies. Problems that need a person (a song without an album tag, a folder with songs from several albums) are listed with an
**Edit album** button. A step is marked done when everything in it was fixed; you can also mark it done or skip it yourself.
## What it does
Files are matched by their **path relative to each library root**. Any folder layout works (`Artist/Album/Title.ext`,
@@ -97,59 +71,10 @@ anything runs, a preview shows how many songs will be processed and which are sk
downgrades, approximate matches, duration mismatches and files that move folders. Downgrades are skipped unless you tick a box, and
replacing master files needs an explicit acknowledgement. Each song then goes through the same checks as a single action.
**Tag editor.** The *Tags* tab browses either library. Click a song to edit its tags (title, artist, album artist, album, track and
disc numbers with totals, year, genre, composer, comment), or tick several, such as a whole album, to edit the tags they share. It
works for MP3, FLAC, M4A, OGG and Opus; other tags and cover art are kept. Review's compare panel also has **Slave tags** / **Master
tags** buttons. Edits are safe with hardlinks:
- A file used by only one library is rewritten to a new file that atomically replaces it; the old file goes to quarantine.
- A file hardlinked to the other library is updated in place, so both libraries see the new tags and stay linked; a verified backup
copy goes to quarantine first. You can instead choose to change only one library's copy, which breaks the link.
- Every written file is verified: the new values must read back, and the decoded audio must be identical to the original.
- Each edit is listed in History (old → new values) and can be undone; an in-place edit interrupted by a crash is rolled back on
startup. Dry run applies.
**Album editor.** Every folder in *Tags* has an **Edit album** button (like AudioShell's folder view). It shows the album fields
once (album, album artist, year, genre, discs, composer, comment); a change there updates every song in the album, including songs in
`Disc 01`/`Disc 02` subfolders. Below is a track grid to edit each song's disc, track number, title and artist, with **Number tracks
in order** (fills track and disc totals too) and **Artist = album artist**. All songs are saved as one job with the same safety
checks as above.
**Album art.** The song and album editors can swap the cover. Search iTunes, Deezer, MusicBrainz (Cover Art Archive), DuckDuckGo
or Google by artist and album or by free text; only square images are shown, and results under a minimum size are hidden. You can
also paste an image address or upload a file. The chosen image is converted to JPEG at the saved size (1000 × 1000 by default) and
can be embedded in the songs, saved as `cover.jpg` in the album folder, or both. OGG and Opus can't hold embedded art here, so they
use the folder image. A replaced `cover.jpg` goes to quarantine, and every change can be undone from History.
- **Auto-match art…** walks the current folder, looks up each album on iTunes, Deezer and MusicBrainz, and applies a cover only when
the artist and album clearly match (release suffixes like "Deluxe Edition" are ignored). Albums that already have art can be
skipped; albums without a confident match are listed and left untouched.
- Sources are chosen in *Settings → Album art*. Google needs an API key with the Custom Search API and a Programmable Search Engine
id (cx). DuckDuckGo has no official API and may stop working. Images are downloaded by playlink itself and never from addresses on
your local network.
**Organize (rename files from tags).** The *Organize* tab sets how files are named, in the style of Lidarr: an **artist folder**, an
**album folder**, a **track format** and a separate **multi-disc track format**, with presets. The default is
`{Artist Name}/{Album Title}/{track:00} {Track Title}`, and multi-disc albums get `{Medium Format} {medium:00}/…` → `Disc 01`, `Disc 02`.
Leave the artist folder empty for `Album/01 Title`; songs then don't need an artist tag.
**Folders with their own naming.** Part of a library can use different formats, such as game soundtracks in
`Video Games/{Album Title}/{track:00} {Track Title}` while the rest stays `Artist/Album/01 Title`. Files inside such a folder are only
renamed within it, and other files are never moved into it (they are skipped with a reason instead). Folder rules can't be nested.
Tokens: `{Artist Name}`, `{Artist NameThe}` (The Beatles → Beatles, The), `{Artist CleanName}`, `{Artist NameFirstCharacter}`,
`{Album Title}`, `{Album TitleThe}`, `{Album CleanTitle}`, `{Release Year}`, `{track:0}`/`{track:00}`, `{medium:0}`/`{medium:00}`,
`{Medium Format}`, `{Track Title}`, `{Track CleanTitle}`, `{Track ArtistName}`, `{Genre}`. Like Lidarr, `{Artist.Name}` or `{Artist_Name}`
puts dots or underscores between words and `{ARTIST NAME}` / `{artist name}` change the case. The artist is the album artist, falling back
to the artist. Colons can be replaced smartly (`A: B` → `A - B`), with a dash, space dash, space dash space, or deleted; other characters
Windows/SMB rejects are replaced (`AC/DC` → `AC-DC`) or removed. Brackets and separators left empty by a missing value are cleaned up
(`Album ({Release Year})` without a year → `Album`). Examples are rendered by the same code that renames files.
**Rename now…** checks the slave, the master or both, then shows every change grouped by destination folder (old → new, selectable per
file or folder), plus skipped files with the reason and a *Fix tags* button. Files are only moved within their library, so contents and
hardlinks never change; lyrics (`.lrc`) move with their song, cover art follows a whole folder, and emptied folders are removed. Each
rename is in History and can be undone, and the library is rescanned afterwards. **Organize automatically** (off by default) applies the
same renames during every scan. A slave file that is a hardlink of a master file at a different path is still recognised as linked.
**Export.** The Review tab's **Export CSV** button downloads either the current view (filter and search applied) or the entire scan.
Each row has the match status and type, a recommendation, both paths, where linking would place the file, codec, bitrate, sample rate,
bit depth and duration for both sides, the quality verdict and any notes. The file opens cleanly in Excel, Numbers and LibreOffice
(UTF-8, and cells a spreadsheet would treat as formulas are escaped).
**Backups** (Settings → Backups), similar to Sonarr. A backup is a zip of playlink's database (settings, library pairs, scan results and
history, not music files) taken safely while the app runs. Choose a folder (default `/config/backups`), a schedule (off, every 1, 3, 7
@@ -159,11 +84,6 @@ are refused), then playlink restarts to swap the database in and keeps the repla
policy brings it back automatically. Optionally, **back up before every action in live mode**: one backup per action you start (and
before scans change files), never one per file; if that backup fails the action does not run, and the newest 10 are kept.
**Export.** The Review tab's **Export CSV** button downloads either the current view (filter and search applied) or the entire scan.
Each row has the match status and type, a recommendation, both paths, where linking would place the file, codec, bitrate, sample rate,
bit depth and duration for both sides, the quality verdict and any notes. The file opens cleanly in Excel, Numbers and LibreOffice
(UTF-8, and cells a spreadsheet would treat as formulas are escaped).
**Build mode** covers the "start fresh" workflow. Point the slave at an empty folder, browse the master library, tick the
folders or songs you want, and playlink hardlinks them in at the same relative path. **Remove** deletes slave files, but only ones that are hardlinks of the
master file.
@@ -245,8 +165,7 @@ Run a scan, check the Review tab, and turn off dry run once the planned actions
| `PLAYLINK_DATA_DIR` | `/config` (Docker) or `./config` | Where the SQLite database lives. |
| `PLAYLINK_ALLOWED_ROOTS` | `/data` | Comma-separated folders that libraries and the quarantine must live in. `/` is refused. |
| `PLAYLINK_SECURE_COOKIES` | `false` | Set to `true` when served over HTTPS. |
| `PLAYLINK_FFPROBE` | `ffprobe` | Path to ffprobe. Without it, quality comparison and tag editing are disabled. |
| `PLAYLINK_FFMPEG` | `ffmpeg` | Path to ffmpeg, used by the tag editor. |
| `PLAYLINK_FFPROBE` | `ffprobe` | Path to ffprobe. Without it, quality comparison is disabled but everything else works. |
| `PLAYLINK_LOG_LEVEL` | `info` | `debug` for verbose logs. |
**Hide quarantine from Plex** (Settings → Media servers) writes a `.plexignore` into each pair's quarantine folder so Plex does not