A Mac app for home DJs who want a clean, batched alternative to dragging YouTube links one at a time. Strict matching by default — Cratify refuses remixes, edits, covers, or sped-up versions unless the source track explicitly contains those keywords. When auto-match isn't confident, you pick the right candidate from the top 8 YouTube results yourself.
Download the latest DMG, drag Cratify.app to Applications, right-click → Open the first time.
That's it — no Homebrew, no Terminal. ffmpeg and yt-dlp are bundled inside the app, and yt-dlp auto-updates in the background once a day so YouTube can't break it for long.
Drop a Spotify-exported CSV onto the window, or click Import from Spotify… to pull one straight from your account.
Hacking on it instead?
git clone https://github.com/farmhutsoftwareteam/savemymusic && cd savemymusic && ./savemymusic.sh ui— the wrapper creates a.venvand uses the binaries frombuild/. If you don't havebuild/ffmpegyet, runbuild/build_ffmpeg.sh(one-time, ~5min) andbuild/fetch_ytdlp.sh.
The fast path — grab the latest release:
- Download
Cratify-x.y.z.dmg - Open it → drag Cratify.app to Applications
- Double-click to launch. (Cratify v0.4.1+ is signed with a Developer ID and notarised by Apple — no right-click dance.)
That's it. Both ffmpeg (LGPL build, ~20MB) and yt-dlp (~35MB) live inside the .app. On first launch they're copied to ~/Library/Application Support/Cratify/bin/ (user-writable) so the auto-updater can keep yt-dlp current.
Spotify playlist
│
▼
Exportify CSV ◀── pulled inside Cratify via the
│ "Import from Spotify" button,
│ or dropped onto the window
▼
Strict YouTube matcher
│
▼ (or: manual pick from top 8 candidates)
│
yt-dlp → ffmpeg → 320kbps MP3 + ID3 tags + album art
│
▼
~/Music/SaveMyMusic/<crate>/ ◀── ready to drag into Serato
The matcher rejects modified versions by default (remix, edit, bootleg, live, instrumental, karaoke, sped-up, slowed, lyric video, mashup, etc.) — unless the source track title itself contains those keywords (so "CamelPhat – Home (Samm & Ajna Remix)" still matches correctly because "Remix" is in the source).
It also enforces a duration tolerance (within ±12s of the Spotify duration by default) and a composite score gate. Tracks that don't pass land in the Unmatched panel with the reason and the closest rejected title — you can retry with looser strictness, edit the search query, or open the candidate panel and pick manually.
- Drag-drop multiple CSVs at once. Each becomes a section with its own table, tabs (All / Matched / Unmatched / Done / Skipped), and download queue.
- Live progress via SSE — rows update one by one as they resolve and download. Auto-reconnects after transient network drops via
Last-Event-ID. - Strict / Normal / Loose presets per CSV. Loose mode allows the disqualifying-keyword bypass for festival-edit playlists; Strict tightens duration tolerance + raises the score gate.
- Manual candidate picker on every row — shows the top 8 YouTube results with thumbnail, channel, duration, score, and reject reason. Pick one, paste a URL, or leave auto-match alone.
- Cancel mid-flight — abort resolve or download cleanly; partial files cleaned up.
- Automatic retry on transient yt-dlp failures (one retry after 5s before marking a row failed).
- Subprocess timeouts — idle 90s, total 10 min — so a stalled download can't hang the queue.
- Tags — title, artist (with featured), album, genre(s), year, embedded album art. (BPM + key are left blank for Serato/Rekordbox to auto-detect on first import — they're better at audio analysis than we are.)
- Reveal in Finder per row or per crate. Preview audio on completed rows (only one preview plays at a time).
- Logs at
~/Library/Logs/Cratify/app.log(5MB × 3 rotation).
The Import from Spotify… button opens Exportify in a second window inside the app. Log in to Spotify, pick a playlist, click Export — Cratify intercepts the CSV in-page (no Downloads detour) and ships it straight into your session. Four capture strategies catch it whether Exportify uses data-URLs, blob URLs, FileSaver, or programmatic anchor clicks.
Files land in ~/Music/SaveMyMusic/<crate-name>/ as:
Artist - Track Title.mp3
with full ID3v2 tags. Drag the folder into Serato and they'll appear with BPM + key already populated.
Why is the folder called SaveMyMusic and not Cratify? Cratify started as "SaveMyMusic". The output folder kept the old name so existing crates and any DJ-software references to that path keep working. Move freely if you want — set a custom output via the
--outflag in the CLI.
Cratify is also a command-line tool — handy for automating batch jobs or running headless:
git clone https://github.com/farmhutsoftwareteam/savemymusic
cd savemymusic
./savemymusic.sh path/to/playlist.csv [path/to/another.csv ...] [--limit N] [--out DIR]The wrapper script creates a .venv, installs Python deps, and runs the same pipeline as the desktop app. Use --limit 3 for a quick smoke test.
To launch the desktop UI from the same checkout: ./savemymusic.sh ui.
The .app ships everything it needs. No Homebrew, no Terminal, no install prerequisites.
We build a minimal LGPL ffmpeg from source (build/build_ffmpeg.sh) — --disable-gpl --disable-nonfree, only libmp3lame as an external dep (statically linked + bundled as a dylib next to the binary via install_name_tool so it's fully relocatable). The result is a ~20MB binary that depends only on Apple system frameworks and travels inside the .app.
This means Cratify can stay MIT-licensed — we never touch the GPL ffmpeg builds Homebrew ships by default (x264, x265, libfdk-aac).
The bundle includes the latest yt-dlp_macos universal binary at build time. On every launch (throttled to once per 24h), updater.py hits GitHub's API and replaces it if a newer release exists. This solves yt-dlp's biggest weakness: YouTube changes its player code constantly, so a frozen version goes stale within weeks. The user-writable copy at ~/Library/Application Support/Cratify/bin/yt-dlp is what actually gets executed; the bundled one is just the seed.
Both binaries follow the same resolution chain in savemymusic/bundled_bins.py:
~/Library/Application Support/Cratify/bin/<name>(user-writable, kept fresh by the updater)Cratify.app/Contents/Resources/bin/<name>(frozen at build time, seeded into the user dir on first launch)/opt/homebrew/bin/<name>//usr/local/bin/<name>(Homebrew, if you happen to have it)shutil.which(<name>)from PATH (last resort)
So if you've got newer-than-bundled yt-dlp from Homebrew and want Cratify to use that instead, just delete ~/Library/Application Support/Cratify/bin/yt-dlp — the resolver falls through to your Homebrew copy.
The Import from Spotify window opens but nothing happens when I export.
Check ~/Library/Logs/Cratify/app.log — the JS hook logs [cratify] Exportify hook installed when it injects. If you don't see that, the page was navigated before the hook installed; close the window and try again.
Downloads keep failing with HTTP 403.
The auto-updater normally keeps yt-dlp fresh, but if it hasn't run yet (or GitHub was unreachable), force-update by deleting the user copy: rm ~/Library/Application\ Support/Cratify/bin/yt-dlp and restart. The next launch will reseed from the bundled binary AND ping GitHub for the latest. If you have a newer Homebrew yt-dlp you'd rather use, that works too — see the lookup chain above.
The .app won't open — "Cratify is damaged and can't be opened".
Should only happen on builds before v0.4.1 (ad-hoc signed). From v0.4.1 onwards the app is notarised; double-click should just work. If you do hit it on an old version: xattr -dr com.apple.quarantine /Applications/Cratify.app.
"FFmpeg not found" — but the bundle includes it.
Should never happen with the v0.2.0 .app since ffmpeg is shipped inside. If it does: delete ~/Library/Application Support/Cratify/bin/ffmpeg and libmp3lame.0.dylib, restart — the bundled copies are re-seeded on launch. If you're running from source, run build/build_ffmpeg.sh to produce them.
Requirements: macOS 12+, Python 3.9+, Xcode Command Line Tools.
git clone https://github.com/farmhutsoftwareteam/savemymusic
cd savemymusic
build/fetch_ytdlp.sh # downloads latest yt-dlp into build/
build/build_ffmpeg.sh # one-time, ~5min: builds LGPL ffmpeg into build/
./savemymusic.sh ui # creates .venv on first run, then launchesYou only need Homebrew for the lame library (brew install lame) — that's the one upstream LGPL audio dep ffmpeg links against.
Project layout:
savemymusic/ ← Python CLI core
├── parser.py – Exportify CSV → Track dataclass
├── matcher.py – yt-dlp search + strict scoring (find_best, find_candidates)
├── downloader.py – yt-dlp subprocess wrapper with progress + timeouts
├── tagger.py – mutagen ID3v2 writer
├── keys.py – Spotify key+mode → Camelot
└── __main__.py – CLI entry, also delegates to UI when first arg is "ui"
savemymusic_ui/ ← FastAPI + PyWebView desktop layer
├── launcher.py – PyWebView window + uvicorn glue
├── app.py – FastAPI app: routes for CSV / resolve / download / picker
├── workers.py – Sync background workers (resolve_worker, download_worker)
├── tasks.py – SSE event bus with Last-Event-ID replay
├── session.py – In-memory state
├── exportify_bridge.py – PyWebView js_api + Exportify popup window
├── logging_setup.py – Rotating file handler at ~/Library/Logs/Cratify/
└── static/ – Vanilla JS + HTML + CSS frontend
build/make_icon.py ← Pillow icon renderer (regenerates .icns)
setup.py ← py2app build config → dist/Cratify.app
Build the .app yourself:
build/fetch_ytdlp.sh # ensure binaries are present
build/build_ffmpeg.sh # (skip if build/ffmpeg exists)
.venv/bin/python build/make_icon.py # regen icon (only if you changed it)
iconutil -c icns build/Cratify.iconset
.venv/bin/python setup.py py2app --bdist-base build/cratify-build --dist-dir dist
codesign --force --deep --sign - dist/Cratify.appPRs welcome — especially small ones that fix a real annoyance you hit. See CONTRIBUTING.md for the dev loop, project layout, and what makes a good PR. Bugs go through the issue template; security issues follow SECURITY.md.
Areas where help would move the needle most:
- More DJ software backends — Rekordbox / Traktor metadata mapping
- CI-built LGPL ffmpeg — the build script works locally, but baking it into GitHub Actions so the release artifacts are reproducible would be nice
- A real matcher evaluation suite — calibrated on known-good track↔YouTube-URL pairs
- Linux + Windows builds (PyInstaller / Briefcase; PyWebView already supports both OSes)
- Apple Developer ID notarisation so the DMG opens without right-click → Open
MIT — fork it, ship it, embed it. Just keep the copyright + permission notice.
The runtime bundle is fully MIT-compatible: every Python dependency is MIT/BSD/Apache, and the only LGPL components (ffmpeg, libmp3lame) are correctly attributed and replaceable per LGPL terms. See THIRDPARTY-LICENSES.md for the full inventory.
Cratify is a tool. It can be pointed at YouTube content that's properly licensed for you to download (your own uploads, Creative Commons material, public-domain music, content where you hold the rights) or content that isn't. The tool doesn't enforce licensing — that's your responsibility.
Working DJs typically use licensed DJ-pool services (Beatport DJ, Beatsource, DJcity, BPM Supreme) for source files — those handle the licensing for DJ use. Use Cratify the way that fits your jurisdiction and your conscience.