Organize large music libraries by genre — available as a command-line tool and a native macOS desktop app with integrated Spotify playlist downloading.
- CLI Tool: A high-performance Python command-line utility that scans local folders, reads embedded metadata (ID3, Vorbis, MP4) and filename patterns, and automatically organizes files into a structured genre folder hierarchy. Operating entirely locally, it requires no internet connection.
- Desktop App: A native macOS desktop application built with Tauri and React wrapping the organizer backend. It integrates a local Tairona downloader adapter (not generic spotdl credentials) to download Spotify playlists in the background and sort them automatically, providing a unified visual workflow.
- macOS 12+
- Node.js v18+
- Rust (stable, for Tauri compilation)
- Local Tairona downloader:
/Users/nicolasaguirre/zprojects/spot dl nico/tairona_dl.py(or setMUSIC_ORGANIZER_DOWNLOADER_PATH) - FFmpeg: Existing repository docs and downloader build assumptions treat FFmpeg as required for downloader audio conversion. Install via Homebrew:
brew install ffmpeg - Python 3.11+ (in a virtual environment or conda environment)
All commands should be run from the repository root directory.
Terminal 1 — Backend API:
# In the repository root:
pip install -r requirements.txt
PYTHONPATH=src uvicorn app.backend.main:app --host 127.0.0.1 --port 8000Terminal 2 — Desktop Frontend:
# From the repository root:
cd desktop
npm install
npm run tauri:dev- Download — Paste a Spotify playlist URL and select your destination folder. The app runs the local Tairona downloader in a background worker, parsing piped progress lines (
[N/M] Artist - Title queued|downloading|downloaded|skipped|failed) for live UI updates. - Active Queue — Monitor active operations and cancel any running downloads.
- Organize — Direct the local organizer to any music directory. Choose your classification depth (general, specific, or nested), configure copy vs. move mode, and execute a dry run to preview changes before they happen.
- History — View completed download records and task summaries.
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/v1/downloads |
Start a playlist download |
GET |
/api/v1/downloads/{id}/status |
Poll download progress |
POST |
/api/v1/downloads/{id}/cancel |
Cancel a download |
GET |
/api/v1/downloads |
List all downloads |
POST |
/api/v1/organize |
Organize a local folder |
POST |
/api/v1/analyze |
Analyze a folder without moving files |
Interactive API documentation is available at http://127.0.0.1:8000/docs when the backend is active.
For local production build status, packaging details, runtime limitations, and private-beta tester guidance, see:
Current packaged-app architecture:
packaged app → bundled frozen backend → bundled downloader
Release builds launch the bundled backend automatically. If the packaged app cannot reach its backend, testers should quit and relaunch the app, then inspect packaged-app logs rather than starting FastAPI manually.
The desktop app remains unsigned and not notarized. FFmpeg bundling is still deferred, and fresh-machine packaged download behavior without separately installed FFmpeg still needs explicit verification.
From the repository root:
pip install -r requirements.txt
# Optional: Install as an editable command-line tool
pip install -e .# Safe first run — preview classification and destination paths (no files modified)
python -m music_organizer.main "/source/path" "/destination/path" --dry-run --level both --report dry_run.csv# Copy into organized structure (default, safe mode)
python -m music_organizer.main "/src" "/dest" --mode copy --level both
# Move files (irreversible — always use --dry-run first)
python -m music_organizer.main "/src" "/dest" --mode move --level both
# Stats only — analyze library composition without copying or moving files
python -m music_organizer.main "/src" "/dest" --stats-only --report stats.csv
# Re-run targeting only unclassified/unknown files
python -m music_organizer.main "/src" "/dest" --mode copy --skip-unknown-only
# Skip files that already exist at the destination
python -m music_organizer.main "/src" "/dest" --mode copy --skip-existing
# Exclude specific subdirectories
python -m music_organizer.main "/src" "/dest" --mode copy --exclude-dir temp --exclude-dir incomplete
# Run in debug mode to see step-by-step classification decisions
python -m music_organizer.main "/src" "/dest" --mode copy --debug| Flag | Description | Example output |
|---|---|---|
--level general |
Broad category buckets | Electronic/, Pop/, Latin/ |
--level specific |
Detailed subgenres | Deep House/, Reggaeton/, Techno/ |
--level both |
Nested folder structure (recommended) | Electronic/Deep House/, Latin/Reggaeton/ |
/destination/
├── Electronic/
│ ├── Deep House/
│ ├── Techno/
│ └── Trance/
├── Hip-Hop Rap/
│ ├── Rap/
│ └── Trap/
├── Latin/
│ ├── Bachata/
│ └── Reggaeton/
└── Other Unknown/
└── Unknown/
MP3, M4A, AAC, FLAC, OGG, OPUS, WAV, AIFF, WMA
80+ specific genres mapped automatically across 9 general buckets:
| Bucket | Examples |
|---|---|
| Electronic | House, Techno, Trance, Drum And Bass, Dubstep, EDM |
| Hip-Hop / Rap | Rap, Trap, Drill, Grime |
| R&B / Soul / Funk | Soul, Funk, Neo-soul |
| Pop | Dance Pop, Latin Pop, Hyperpop |
| Rock / Indie / Metal | Rock, Metal, Indie Rock, Alternative |
| Latin | Reggaeton, Bachata, Salsa, Moombahton, Corridos |
| Reggae / Dub / Dancehall | Reggae, Dancehall, Dub |
| Jazz / Blues | Jazz, Blues, Swing |
| Classical / Score | Classical, Soundtracks, Ambient |
See src/music_organizer/rules.py for the full genre mapping rules. Custom mappings can be overridden or added locally by creating ~/.config/music-organizer/config.json.
- Metadata Reader: Reads embedded tags (ID3, Vorbis, MP4 metadata) using
mutagen. - Path & Filename Inference: If metadata is missing or empty, scans parent directory names and filenames using precise word-boundary pattern matching.
- Conservative Defaults: Undetected or ambiguous tracks default to
Other Unknown/Unknownto prevent misclassification. - Collision Handling: Handles identical filenames by appending numerical suffixes (
(1),(2)). Optionally,--on-collision hashwill deduplicate and skip files with matching content hashes.
# 1. Investigate why files were not classified
python -m music_organizer.main "/src" "/dest" --debug
# 2. Add your custom keywords to the local configuration file
# Place mappings under ~/.config/music-organizer/config.json
# 3. Re-run only on previously unclassified files
python -m music_organizer.main "/src" "/dest" --mode copy --skip-unknown-onlymusic-organizer supports using a local custom Tairona downloader adapter to download Spotify playlists and resolve metadata.
In development, the default local path for the downloader script is:
/Users/nicolasaguirre/zprojects/spot dl nico/tairona_dl.py
To configure it on other machines or use an alternate wrapper, define the MUSIC_ORGANIZER_DOWNLOADER_PATH environment variable:
export MUSIC_ORGANIZER_DOWNLOADER_PATH="/path/to/tairona_dl.py"Caution
This application and the downloader workflow must only be used with audio files that you own, created, have permission to download, or that are legally downloadable. Automated tests must not perform live copyrighted downloads.
Downloaded media and local output folders should never be checked into git. It is highly recommended to configure downloads to target isolated folders outside the repository path, such as:
~/Music/tairona-test-downloads/
The repository's .gitignore file is configured to safeguard against accidental inclusion of .mp3, .m4a, and other media files.
The local Tairona downloader lives in a separate folder (spot dl nico) with no configured git remote — progress-line changes there are local-only and not pushed.
Authorized desktop end-to-end validation succeeded in June 2026:
- Backend
/api/v1/downloadstask executes and manages the local Tairona downloader correctly. - Progress logs are parsed from piped machine-readable lines:
[N/M] Artist - Title queued|downloading|downloaded|skipped|failed. - Download completed successfully with authorized content only.
- Progress bars were difficult to observe because downloads finished quickly — this is not a progress failure; the pipeline works.
- Media files were kept outside the repository path.
- Packaged unsigned app smoke coverage now also includes bundled backend startup/shutdown, bundled downloader availability, dashboard, history, preferences, download-to-organize handoff, and repeated explicit in-place organization.
music-organizer/
├── src/music_organizer/ # Core CLI library (scanner, classifier, fileops, rules)
├── app/backend/ # FastAPI backend endpoints and local services
│ ├── routes/ # API routing handlers
│ ├── services/ # Tairona downloader integration and file organization service wrappers
│ └── store/ # SQLite download-task history database
├── desktop/ # Tauri + React application frontend
│ ├── src/screens/ # UI Screens (Home, Download, Organize, Active, History)
│ └── src-tauri/ # Rust Tauri desktop shell configuration
├── tests/ # pytest test suite (160 tests covering CLI & Backend)
├── docs/ # Architecture and design documentation
├── requirements.txt # Core dependencies
├── pyproject.toml # Build system configuration
└── LICENSE # License information
# Install package dependencies in editable mode with dev libraries
pip install -e .[dev]
# Run unit tests
python -m pytest tests/ -vcd desktop
npm install
npx tsc --noEmitThe backend launches the local Tairona downloader as a subprocess. If you get path errors, verify MUSIC_ORGANIZER_DOWNLOADER_PATH or the default path exists:
/Users/nicolasaguirre/zprojects/spot dl nico/tairona_dl.py
- If using virtual environments, activate the environment before starting the FastAPI backend.
The downloader requires ffmpeg to process audio files and embed metadata.
- Install on macOS using Homebrew:
brew install ffmpeg - Verify with
ffmpeg -version.
If another service is using port 8000, you can run the backend on a different port:
PYTHONPATH=src uvicorn app.backend.main:app --host 127.0.0.1 --port 8080Update your frontend configuration or Tauri API requests if modifying the backend port.
This repository is maintained as a portfolio project showcasing a hybrid Python/Rust application.
- Production Status: Stable v2.1.0 backend with fully integrated desktop GUI.
- Testing: Includes a comprehensive test suite (160 unit and integration tests) validating scanners, rules engines, database history, and file operations.
- Suggested GitHub Topics:
music-organizer,tauri,fastapi,react,python,spotdl,rust,music-library,mp3-tagger,audio-metadata. - Future Improvements:
- Archiving root-level developer planning files into
docs/archive/to keep repo root clean. - Adding a direct drag-and-drop interface for directories in the Tauri frontend.
- Archiving root-level developer planning files into
MIT License. See LICENSE for details.