A music-library workbench for macOS, for people who still keep their music as
files. I got tired of juggling five different tools to rip, tag, convert and
organize my collection, so I built one that does the whole job. Then I made it
look like the hardware I wish I owned. It's not there yet, but if this project
picks up and people actually use it, I'll hire a talented designer to work on
the skeumorphic tool we all don't need.
The whole interface is a bit like a hardware console (the design system is called Carbon, I mean, why not?): chassis panels, an OLED display, VU meters, faders, knobs, buttons that light up when you hover. Light and dark, your pick. No cloud, no accounts, no database lock-in, your files your rules.
Library. Hit Dig Crate, point it at a folder, and it scans everything in it, however deep. Browse by Artist → Album → Track with cover art and artwork folder. Collections are saved as crates, plain JSON files, so nothing is held hostage. Newly dug music lands in a staging Prep Crate (which is like an Inbox) first so your sorted crates stay clean, you can also use it to just listen to some gibbershily named file or to convert one track that you need for editing (or whatever). Or skip all of that and just drop files and folders onto the window.
Playback. Queue, shuffle, repeat, ±8s nudge, output-device picker, a real 12-band EQ (20 Hz–20 kHz) and a live spectrum on the VU meters. There's a mini player for when you just want to play music while you do other stuff.
Artwork This is one of my favorite parts of the app, display album artwork nice and big on the screen, listening to music whilst looking at the albums artwork was one of my favorite things in the world back when I was a kid. So why can't we enjoy it now? (Btw, props to all people uploading scans and keeping archive of such vast library, and also new artists still releasing new physical records! Cheers!)
Tags & artwork. Inspect and edit metadata track by track, fix embedded artwork, pull missing covers, and keep album booklets and scans together with the music.
Cleanup & conversion. (THIS IS A BETA FEATURE, PLEASE USE IT WITH CAUTION) Move or consolidate the whole library, straighten out messy folder layouts, and batch-convert with FFmpeg: codec, bitrate, sample rate, artwork handling, and the output folder structure (mirror the source tree, flat, or built from metadata). Filename collisions are handled for you.
Vinyl rips. The Record Divider takes one long recording of a record side, finds the silences between songs, and cuts it into per-track files. You can name these tracks, but honestly what's the fun in that? Just look at the artwork and you'll see which is track 1 and 5 ;) Detection sensitivity is a slider, so quiet passages don't get a song chopped in half. It's a hit and miss thing, I think it's fun, let me know if it's frustrating.
Radio. Paste a YouTube URL and treat it like a tuner — live stations, full sets, mixes and playlists, straight through the same transport and meters. (Needs yt-dlp, see requirements.)
More sources. Rip audio CDs. Browse and stream a Subsonic / Navidrome server. M3U playlists.
Devices. Copy music onto anything that mounts as a drive — USB players, SD cards — with per-device profiles and one shortcut (⌘⇧T).
Scrobbling. Last.fm, if that's your thing.
- macOS 13 (Ventura) or later.
- Apple Silicon for the default build. (Intel/universal builds are possible
but need a universal Swift build and a
lipo-merged universal FFmpeg — see below.) - The packaged app bundles
ffmpegandffprobe, so there's nothing extra to install. When building from source, FFmpeg is optional: without it the app falls back to AVFoundation-only metadata and conversion is disabled. - YouTube radio needs yt-dlp
(
brew install yt-dlp). Everything else works fine without it.
Grab the latest signed .dmg from the
Releases page and drag
CrateDigger to Applications. Now at the 2.1.0 release.
Or build it yourself (below).
If CrateDigger is useful to you, you can chip in on Patreon or GitHub Sponsors. 💛
Build the app executable:
swift buildRun the test suite:
scripts/test.shscripts/test.sh forces the XCTest runner path, prefers a full Xcode developer
directory when one is installed, and prints clear guidance if Xcode still needs
its license accepted.
Assemble a shareable .app bundle with bundled ffmpeg and ffprobe:
scripts/package-app.shYou can also point the script at specific binaries:
scripts/package-app.sh --ffmpeg /opt/homebrew/bin/ffmpeg --ffprobe /opt/homebrew/bin/ffprobe --output ./distFor distribution, do NOT bundle Homebrew's ffmpeg. Homebrew builds are dynamically linked to
/opt/homebrew/Cellar/...dylibs that don't exist on other Macs, so conversion / ffprobe fail anywhere without Homebrew ffmpeg installed. Bundle statically-linked binaries instead — for Apple Silicon, the static arm64ffmpeg/ffprobefrom osxexperts.net are fully self-contained (verify the published SHA256). Homebrew is fine for local dev only. (Intel/universal distribution additionally needs a universal Swift build + alipo-merged universal ffmpeg.)
The packaged app is written to dist/CrateDigger.app. By default the bundle is
ad-hoc signed (suitable for local development only). The packaging script also
prefers a full Xcode developer directory when one is installed and uses a
repo-local module cache so the build is less sensitive to machine-wide Swift
cache state.
A shippable build needs Developer ID signing, hardened runtime, notarization, and a DMG:
# One-time: store an app-specific password under a notarytool profile name
xcrun notarytool store-credentials cratedigger-notary \
--apple-id <your-apple-id> --team-id <TEAMID> --password <app-specific-password>
# Each release
CRATEDIGGER_NOTARY_PROFILE=cratedigger-notary \
scripts/package-app.sh \
--ffmpeg /path/to/static/ffmpeg --ffprobe /path/to/static/ffprobe \
--sign "Developer ID Application: Your Name (TEAMID)" \
--notarize \
--dmgThis produces dist/CrateDigger-<version>.dmg, signed and stapled, that opens
cleanly on any Mac. The hardened runtime entitlements live in
Packaging/CrateDiggerApp/CrateDigger.entitlements (library-validation
disabled so the bundled ffmpeg/ffprobe binaries can run).
For the full release gate, see docs/BETA_RELEASE_CHECKLIST.md.
The shipped app updates itself: it reads an appcast served by GitHub Pages from
website/, verifies the EdDSA signature on the DMG, installs it and relaunches.
"Check for Updates…" in the app menu does it on demand; otherwise it checks once
a day in the background. A swift build run has no feed and no key, so the
updater is never created there and the menu item stays greyed out.
There are two feeds, and which one a build reads is decided at runtime, not
in Info.plist.
| Feed | Who reads it |
|---|---|
website/appcast.xml |
stable releases (currently the 1.5.x line) |
website/appcast-beta.xml |
the 2.0 line, published as GitHub prereleases |
SUFeedURL is deliberately identical on both branches and points at the
stable feed; it is only the fallback for a build that overrides nothing. The
real choice is UpdateFeed.override(channel:betaOptIn:) in
Updates/SoftwareUpdater.swift, which returns the beta feed when either the
build's own AppVersion.channel is non-empty or the owner has ticked Advanced
▸ Receive beta updates. So a prerelease build follows the beta feed
automatically, because AppVersion.channel is BETA on v2 and nobody has to
tick anything, while a stable build only ever sees a beta if its owner asked
for one.
Keeping that decision in one pure function rather than in Info.plist is the
point: there is no per-branch line to repoint, so there is no line that can be
forgotten or carried onto main by accident. UpdateFeedTests covers all four
channel/opt-in combinations and also reads Info.plist, failing if SUFeedURL
ever stops matching UpdateFeed.stable.
One-time setup. Generate the signing key. The private half goes into your login Keychain and never leaves it; the tool prints the public half:
swift package resolve
.build/artifacts/sparkle/Sparkle/bin/generate_keysPaste the printed public key into SUPublicEDKey in
Packaging/CrateDiggerApp/Info.plist, replacing REPLACE_WITH_SPARKLE_PUBLIC_KEY.
Back the private key up somewhere safe: losing it means no already-installed
copy can ever be updated again — every future release would have to be
installed by hand. scripts/package-app.sh refuses to sign a distribution build
while the placeholder is still there.
Each release, after the notarized DMG exists and before pushing:
scripts/update-appcast.sh # stable, run from main
scripts/update-appcast.sh --appcast website/appcast-beta.xml # beta, run from v2That signs the DMG into the chosen feed, with release notes lifted from the
matching CHANGELOG.md section. Each feed is generated on the branch that owns
it and the script refuses to run on the wrong one, because getting it backwards
is silent and destructive in both directions: it would either publish a beta
into the feed every stable copy reads, or overwrite the stable feed's history
from a branch that does not have it.
Upload the same DMG to the GitHub release (the feed points at that download URL and the signature covers those exact bytes — re-packaging afterwards invalidates it), then commit and push the feed; Pages redeploys on push.
One thing does have to cross branches: Pages only serves website/ from
main, so publishing a beta feed means copying just that one file over
(git checkout v2 -- website/appcast-beta.xml). Never carry anything else
across. The press-the-record skill scripts both paths, and using it is
preferable to releasing by hand.
--channel rc remains for the older convention, where prerelease items were
tagged inside the stable feed rather than split into their own. The 2.0 line
does not use it.
Last.fm requires an application API key + shared secret. These are not included in the source — the app runs fine without them (scrobbling is simply disabled). To enable Last.fm in your own build:
- Create an API account at https://www.last.fm/api/account/create.
- Copy
scripts/lastfm.env.exampletoscripts/.lastfm.env(gitignored) and fill in your key/secret.scripts/package-app.shembeds them into the app bundle automatically. - For a
swift rundev build, exportCRATEDIGGER_LASTFM_API_KEYandCRATEDIGGER_LASTFM_API_SECRETin your shell instead.
- Launch the packaged app on a Mac without Homebrew-installed FFmpeg tools.
- Load a mixed-format music folder.
- Confirm the empty state, loading state, and loaded-track state all make sense.
- Inspect artwork and metadata for several tracks.
- Play tracks, pause, seek on the OLED timeline, and use previous/next controls.
- Convert files using
Source Relative,Flat, andMetadata Templatefolder structures. - Use
Review album foldersand confirm the review sheet edits destinations correctly. - Convert files with duplicate basenames and verify CrateDigger renames outputs instead of overwriting them.
- Verify the bottom status area and readiness text clearly explain whether playback, metadata probing, and conversion are available.
Released under the MIT License. © 2026 Mr. Barkan.
