intake
This is a personal tool. It exists to solve one specific problem — rebuilding my own lost music collection — and it is shaped entirely around my library layout and the way I like things named. It is custom, it is vibeslopped, and it is not built for general use or for anyone else's setup. It is public in case somebody finds a piece of it useful for whatever reason.
A music library intake utility.
Point it at a staging directory full of albums and it
produces a clean, consistently tagged, consistently named library: lossless masters in
archive/ and Vorbis streaming copies in library/.
What it does
- Reviews each album before touching anything: metadata, track names, cover art, then a confirmation step
- Strips every tag except a fixed whitelist, all of it recomputed rather than copied
- Names tracks
disc-track_title.ext, with collision and length guards - Finds cover art from embedded art, the album folder, a
Scans/-style subfolder, the Cover Art Archive, Deezer and iTunes, and can fetch it again for every placeholder album at once - Renames or merges an artist folder in both trees and repoints the journal and
artists.toml, for the two spellings of one artist that a rebuild always produces intake ignoreleaves a staging folder alone for good, anywhere album lists are built- Draws real cover previews in the terminal with the Kitty graphics protocol, falling
back to
chafa - Verifies every written master (count, codec, duration, tags, decode) before promoting it into the archive, so a failure leaves staging untouched
- Transcodes a streaming copy from the masters and optimises its cover
- Remembers artist folder decisions in
artists.tomland the journal, so the same artist always lands in the same folder - Keeps the edits you make to an album, so quitting mid-review or coming back to it later does not lose them
- Notices when an album is already in the library — by artist and name, not just by path — before the review starts, and offers keep both, replace or skip
bon the album list files everything ready in one unattended pass, parking whatever needs a decision and naming it at the end;njumps straight to the next parked albumJon the album list opens the journal: every record, with what is actually on disk beside it, and per record the choice to forget it (files untouched) or move the masters back to staging so it can be reviewed again
Requirements
ffmpeg,metaflac,flac,vorbiscommentmagick(ImageMagick), for measuring, optimising and generating cover artchafais optional, for previews on terminals without Kitty graphics
flac is not optional in practice: it is what proves an archived file is intact.
ffmpeg decodes a truncated flac without reporting anything at all, so without the
reference decoder a half-finished download would be promoted as a master.
flac ships with metaflac, so it comes along with a flac package.
Usage
intake # full-screen interface intake scan # list staged albums intake scan --fast # list without reading metadata intake album <name> # review one album, then archive it intake album <name> --dry-run # show the full plan, change nothing intake album <name> --edit-names # open the album sheet first intake resume # continue the first unarchived album intake batch # file every ready album, parking the ones that need you intake library verify # placeholder covers and missing libraries intake library audit # compare archive and library for drift intake library audit --fix # rebuild the library copies that are short intake library transcode <album> # rebuild a streaming copy from the masters intake library re-cover <album> # replace an album's cover intake library re-cover --placeholders # fetch art again for every placeholder intake artist rename <old> <new> # move an artist folder and repoint everything intake artist merge <old> <into> # move every album of one artist into another intake library adopt # build records for albums already in the archive intake library forget <album> # drop the record, leave every file alone intake library unfile <album> # move the masters back to staging and forget it intake config # effective config and resolved paths
Every command accepts --root, --config, --state-dir, --dry-run, --yes,
--json, --edit-names and --fast. The default root is ~/music, overridable with
--root or $INTAKE_ROOT.
--yes approves automatically, but refuses when the plan has hard issues (unknown
album or artist, colliding names), so it can never silently invent metadata.
intake batch files everything ready in one pass. An album that needs a decision — an
unknown artist, a name that cannot be normalised, a file that is not lossless — is parked
with the reason recorded against it and the run carries on, because one bad download must
not hold up the other two hundred. In the interface, b runs the same batch and n jumps
to the next parked album.
Everything about an album is editable before it is filed. Pressing e on any review
screen — including the cover picker — opens the album sheet in $EDITOR: the album and
album artist, both folder names, the year, the cover, and every track's title, artist,
disc, track number and file name. Deleting a track block leaves that file out of the
album. Nothing is guessed from a name that cannot be normalised — a Japanese title, a
symbol-only title — without opening the sheet.
The sheet is one document, and intake uses exactly what it says rather than what the tags said, so a wrong album artist or a missing year is fixed there instead of by retagging files.
Layout
<root>/
staging/ albums waiting to be processed
archive/ <artist>/<album>/ lossless masters + cover.jpg
library/ <artist>/<album>/ streaming copies + optimised cover.jpg
artists.toml
The journal lives in ~/.local/state/intake/state.json.
Configuration
~/.config/intake/config.toml. intake config prints the resolved values.
Building
make build # -> out/intake make check # fmt, vet, test
Testing
Real music is never used for testing. The fixture generator makes synthetic albums out of tiny tones with hand-picked tags, covering multi-disc sets, non-Latin titles, collisions, 120-track albums, missing tags and stray clutter:
scripts/make-fixtures.sh # into ~/music/staging scripts/make-fixtures.sh /tmp/staging # or anywhere else
scripts/make-edge-fixtures.sh builds a second set for the opposite purpose:
the shapes downloads actually arrive in, rather than tidy ones. Nested album
folders, CD1/CD2 folders, junk cover art, truncated files, text files wearing an
audio extension, every sidecar a rip ships with, and names built from emoji,
zero-width characters or nothing but punctuation. Each case carries an EDGE.md
saying what is odd about it and what intake is expected to do.
It is idempotent and takes a couple of seconds. Each fixture directory has a FIXTURE.md
saying what it tests and what the expected behaviour is.
The TUI needs looking at rather than asserting, so there is a harness for that:
scripts/tui-tour.py --make-fixtures # prints every screen scripts/tui-tour.py --width 120 --height 40 scripts/grid-probe.py --root .scratch/fx # checks the cover grid reaches the terminal
How this was built
Pretty much entirely by DeepSeek V4.1 Flash (High Effort) running in DeepSeek Harness, working from my direction across a long session. I decided what it should do, ran it against real music, and found and filed the bugs, but I did not write the code and it does not read like my own work.
That is worth knowing before you judge the style or assume I can explain any given line. It also means the parts that are good are mostly the parts where I kept pushing on something that was broken.
License
Released into the public domain under the Unlicense.