git/intake/main
README.md
170 lines8 KB last change raw · source · history

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 ignore leaves 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.toml and 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
  • b on the album list files everything ready in one unattended pass, parking whatever needs a decision and naming it at the end; n jumps straight to the next parked album
  • J on 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, vorbiscomment
  • magick (ImageMagick), for measuring, optimising and generating cover art
  • chafa is 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.