Python package for the synciflow project.
Project description
Synciflow
๐ LIVE DEMO - [limited tracks download]
Synciflow is an offline-first music sync and download platform that lets you save and manage Spotify content locally through multiple interfaces:
โข Web โ browser-based interface for managing and syncing music
โข Desktop โ standalone app for local music workflows
โข API โ FastAPI backend for automation and integrations
โข CLI โ powerful command-line tools for advanced control
With Synciflow you can:
- Import tracks, albums, and playlists from Spotify URLs
- Automatically resolve and download high-quality audio via YouTube
- Build a local music library stored in SQLite + filesystem
- Sync and mirror Spotify playlists locally
- Access everything through Web UI, Desktop app, API, or CLI
Synciflow is ideal for users who want a self-hosted music library that mirrors Spotify while keeping full control of their files offline.
๐ผ Screenshots
Web UI (Windows)
| Your library | Likes list |
| View playlist | Playlist page view |
Console (CLI)
| CLI help view | Smart CLI main view |
| Smart CLI use view | Serve API view |
โจ Features
- Offline library from Spotify URLs
- Load individual tracks or entire playlists by Spotify URL.
- Metadata (title, artist, artwork) stored in SQLite.
- Spotify Liked Songs as a playlist
- Use
syncify-py's login-based likes scraping to mirror your Spotify Liked Songs. - Exposed locally as a pseudo-playlist with stable
playlist_idoflikes. - Can be listed, inspected, and exported just like any other playlist.
- Use
- Automated YouTube resolution & download
- Resolves Spotify tracks to YouTube using Selenium + headless Chrome.
- Downloads audio with
yt-dlpand converts to highโquality MP3 viaffmpeg.
- Cover art embedding
- Downloads artwork and embeds it directly into MP3 files via
mutagen.
- Downloads artwork and embeds it directly into MP3 files via
- Playlist sync engine
- Keeps a local playlist in sync with the current Spotify playlist contents.
- Adds new tracks, prunes removed ones, and cleans up unreferenced files.
- Structured local storage
- Deterministic path layout under
storage_data/for tracks, temp files, and playlist metadata. - Track filenames automatically sanitized and normalized.
- Deterministic path layout under
- Two CLIs
- Basic CLI (
synciflow ...) for scripting and automation. - Smart CLI (
synciflow smart) with rich, interactive menus powered byrich.
- Basic CLI (
- HTTP API
- FastAPI app exposing endpoints to:
- Load tracks/playlists
- Sync playlists
- List tracks/playlists
- Stream or download MP3s and playlist ZIPs.
- FastAPI app exposing endpoints to:
- Job tracking & notifications
- Long-running tasks (track/playlist load, sync) run in the background and are tracked in a SQLite
jobstable. - Real-time updates over a WebSocket (
/ws/notifications) for progress and completion events. - CLI commands show Rich progress bars for playlist download, track download, and sync.
- Long-running tasks (track/playlist load, sync) run in the background and are tracked in a SQLite
๐ง How It Works
At a high level:
- Input: You provide a Spotify track or playlist URL, or trigger a Liked Songs sync.
- Metadata fetch:
syncify(external library) is used to obtain structured metadata:
- Track: ID, title, artist, image URL.
- Playlist: ID, title, image, track URLs.
- Liked Songs: a list of liked track URLs scraped after you log into Spotify in a browser window.
- Resolution: For each track, synciflow:
- Searches YouTube via Selenium headless Chrome.
- Resolves a YouTube video ID for that track/artist pair.
- Download:
- Uses
yt-dlpto download the audio. - Uses
ffmpegto convert into a highโquality MP3.
- Library storage:
- Files are moved atomically into
storage_data/tracks/<prefix>/<track_id>.mp3. - Metadata is stored in SQLite via
SQLModel(Track,Playlist,PlaylistTrack).
- Serving & tooling:
- CLI commands and FastAPI endpoints operate against this local library.
- Playlists can be exported as ZIPs with embedded cover art and nice filenames.
Core Components
- Library orchestration:
Library(incore/library_manager.py) wires together:AppConfig(paths, database location)- SQLite engine + migrations (
db/database.py) - Filesystem layout (
storage/file_manager.py,storage/path_manager.py) - Managers:
TrackManager,PlaylistManager,SyncManager.
- Track management:
TrackManager
Handles:- Loading tracks by Spotify URL (
load_track) - Localโfirst loading from existing MP3s (
load_local) - Triggering YouTube resolution + download pipeline.
- Loading tracks by Spotify URL (
- Playlist management:
PlaylistManager
Handles:- Loading playlists by Spotify URL (
load_playlist) - Persisting playlist metadata to JSON in
storage_data/playlists/ - Rebuilding playlist/track relations from local files (
load_local).
- Loading playlists by Spotify URL (
- Sync engine:
SyncManager
Compares current Spotify track set vs. local DB, then:- Adds missing tracks.
- Rebuilds ordered relations.
- Removes unreferenced tracks from DB and disk.
- API layer:
api/server.py
FastAPI app with endpoints for loading, listing, streaming, and exporting. - Job tracking:
core/job_manager.py
Creates and updates jobs (pending โ running โ completed/failed) in thejobstable. Used by the API to return ajob_idimmediately and run work in a background thread. - Notification bus:
core/notification_bus.py
Thread-safe event bus: sync code (e.g. background workers) publishes events viapublish_sync(); a bridge task forwards them to async subscribers. WebSocket clients subscribe to receive real-time events (e.g.PLAYLIST_PROGRESS,TRACK_DOWNLOAD_COMPLETED,ERROR).
๐ Tech Stack
- Language: Python 3.10+
- Runtime & tooling:
- Audio & metadata:
- Automation:
- Selenium + Chrome / Chromium
- webdriver-manager
- Other:
pydantic,dataclasses,pathlib,zipfile, etc.
๐ฆ Installation
Via pip
pip install synciflow
1. Clone the repository
git clone https://github.com/adelelawady/synciflow.git
cd synciflow
2. Create and activate a virtual environment
python -m venv .venv
# Linux / macOS
source .venv/bin/activate
# Windows (PowerShell)
.venv\Scripts\Activate.ps1
3. Install in editable (development) mode
If your pyproject.toml defines dev extras (recommended):
pip install -e ".[dev]"
If not, install core dependencies manually (example):
pip install -e .
# plus system-level tools like ffmpeg and Chrome; see below
Building a standalone executable
You can build a single Windows executable so you can run synciflow without installing Python or dependencies.
- Install the project and build dependencies (from the repo root):
pip install -e .
pip install hatch
- Build the exe with one command (from the repo root, with your venv activated):
python -m hatch build --target pyinstaller
If hatch is not on your PATH, use:
3. The executable is written to dist/synciflow.exe. Copy it to any Windows machine; external tools (ffmpeg, Chrome) must still be available on that machine as described in System dependencies above.
This build produces a Windows exe. Building on Linux or macOS with the same command produces a binary for that OS; PyInstaller does not support cross-compilation.
โ๏ธ Configuration
Runtime configuration is primarily pathโbased and handled via AppConfig:
from pathlib import Path
from synciflow.config import AppConfig
cfg = AppConfig(
storage_root=Path("storage_data"), # where MP3s and playlist metadata go
data_root=Path("data"), # where SQLite DB lives
)
By default (AppConfig()):
- Database:
data/synciflow.sqlite3 - Storage root:
storage_data/- Tracks:
storage_data/tracks/<prefix>/<track_id>.mp3 - Temp files:
storage_data/tmp/ - Playlist metadata JSON:
storage_data/playlists/<playlist_id>.json
- Tracks:
The CLI and API entrypoints construct AppConfig() with default paths. If you embed synciflow in your own Python app, you can create a Library with a custom config:
from synciflow.core.library_manager import Library
from synciflow.config import AppConfig
lib = Library.create(AppConfig(storage_root=Path("/my/music"), data_root=Path("/my/db")))
๐ Usage
Once installed (and virtualenv activated), you should have a synciflow CLI entrypoint.
CLI โ Basic commands
Track, playlist, and sync commands show Rich progress bars (e.g. โDownloading trackโฆโ, โLoading playlistโฆโ, โSyncing playlistโฆโ) while work runs.
Load a single track from Spotify
synciflow track "https://open.spotify.com/track/XXXXXXXXXXXXXXX"
Outputs:
- Track title and artist
- Internal
track_id - Local audio file path (once downloaded)
Load a playlist from Spotify
synciflow playlist "https://open.spotify.com/playlist/YYYYYYYYYYYYYYY"
Outputs:
- Playlist title
- Internal
playlist_id
Sync your Spotify Liked Songs as a playlist
synciflow likes
This will:
- Open a browser window (via
syncify-py) so you can log into Spotify. - Scrape your Liked Songs and sync them into a local pseudo-playlist with
playlist_id=likes.
After that, you can treat likes like any other playlist:
synciflow playlist-local likes
synciflow download-playlist-zip likes /path/to/output
synciflow save-playlist likes /path/to/output
Sync an existing playlist to the latest Spotify state
synciflow sync "https://open.spotify.com/playlist/YYYYYYYYYYYYYYY"
Outputs a summary:
added=10 removed=2 kept=50
List tracks and playlists
synciflow tracks
synciflow playlists
Download / export tracks
# Print the path to an existing track by ID
synciflow download-track <track_id>
# Save a track to a specific file or directory
synciflow save-track <track_id> /path/to/output_or_directory
Export playlists as ZIP
# Build a playlist ZIP and save to a file/directory
synciflow download-playlist-zip <playlist_id> /path/to/output_or_directory
# Or use smart naming based on playlist title
synciflow save-playlist <playlist_id> /path/to/output_or_directory
Smart CLI โ Interactive mode
Launch the fullโscreen style interactive CLI:
synciflow smart
From there you can:
- Load tracks/playlists by URL or ID
- List tracks/playlists
- Save tracks or playlists to files/ZIPs
- Inspect and delete tracks or playlists from the DB
HTTP API & Frontend UI โ Development / Production server
Run the combined FastAPI API and React frontend UI via the CLI:
synciflow serve --host 127.0.0.1 --port 8080
Then open http://127.0.0.1:8080 in your browser to access the UI (which talks to the same server for API calls).
You can still run just the FastAPI app directly with uvicorn if you prefer:
uvicorn synciflow.api.server:create_app --factory --host 0.0.0.0 --port 8080
Once running, the API will be available at http://127.0.0.1:8080 and, if the frontend build is present, the UI will be served from the same origin.
Building and packaging the frontend UI
To include the React UI in your installation (wheel / sdist), build it before creating a release:
# from the project root
cd frontend
npm install
npm run build
Ensure the built assets are copied or moved into src/synciflow/frontend before running a Python build (for example with hatch build or python -m build). Those assets will then be bundled with the synciflow package and served automatically by synciflow serve on port 8080.
๐ก API Reference
All endpoints are defined in synciflow.api.server.create_app.
Load & sync (background jobs)
These endpoints create a job, return 202 Accepted with { "job_id": "<uuid>" }, and run the work in a background task. Use GET /jobs/{job_id} to poll status, or connect to WebSocket /ws/notifications for real-time progress.
| Method | Path | Body example | Description |
|---|---|---|---|
| POST | /track/load |
{ "url": "<spotify_track>" } |
Load a track by Spotify URL (download if needed). Returns 202 + job_id. |
| POST | /playlist/load |
{ "url": "<spotify_playlist>"} |
Load a playlist by Spotify URL, downloading tracks. Returns 202 + job_id. |
| POST | /playlist/sync |
{ "url": "<spotify_playlist>"} |
Sync a playlist to the latest Spotify track set. Returns 202 + job_id. |
| POST | /likes/load |
no body | Load your Spotify Liked Songs into a local pseudo-playlist with playlist_id=likes. Returns 202 + job_id. |
| POST | /likes/sync |
no body | Sync the likes pseudo-playlist against the current Spotify Liked Songs set. Returns 202 + job_id. |
Jobs & notifications
| Method | Path | Description |
|---|---|---|
| GET | /jobs/{job_id} |
Get job status: job_id, job_type, status, progress, message, created_at, updated_at. |
| WebSocket | /ws/notifications |
Real-time events: TRACK_DOWNLOAD_STARTED, TRACK_DOWNLOAD_COMPLETED, PLAYLIST_PROGRESS, PLAYLIST_COMPLETED, SYNC_PROGRESS, SYNC_COMPLETED, ERROR. Each message is JSON with event_type, job_id, progress, message, and optional payload. |
Flow: A POST to /track/load, /playlist/load, or /playlist/sync creates a row in the jobs table (status pending), returns 202 with job_id, and starts a background thread. The thread updates the job (running โ completed or failed) and publishes events to the notification bus. Clients can poll GET /jobs/{job_id} or subscribe to ws://host/ws/notifications to receive progress and completion events.
Local-first load
| Method | Path | Description |
|---|---|---|
| POST | /track/{track_id}/load_local |
Register/repair a track from a local MP3. |
| POST | /playlist/{playlist_id}/load_local |
Load playlist from stored metadata and local tracks. |
Querying library
| Method | Path | Description |
|---|---|---|
| GET | /track/{track_id} |
Get track metadata from DB. |
| GET | /playlist/{playlist_id} |
Get playlist metadata from DB. |
| GET | /tracks |
List all tracks. |
| GET | /playlists |
List all playlists. |
| GET | /playlist/{playlist_id}/tracks |
Get ordered tracks for a playlist. |
Streaming & download
| Method | Path | Description |
|---|---|---|
| GET | /track/{track_id}/stream |
Stream MP3 for a track. |
| GET | /track/{track_id}/download |
Download MP3 for a track (nice filename). |
| GET | /playlist/{playlist_id}/download.zip |
Download ZIP of playlist tracks. |
๐ Project Structure
A highโlevel overview of the key directories and modules:
synciflow/
โโ src/
โ โโ synciflow/
โ โโ __init__.py # Package metadata (__version__)
โ โโ config.py # AppConfig: storage + DB paths
โ โโ cli/
โ โ โโ __init__.py
โ โ โโ main.py # Typer CLI entrypoint (synciflow ...)
โ โ โโ smart.py # Rich-powered interactive CLI
โ โโ api/
โ โ โโ __init__.py
โ โ โโ server.py # FastAPI app + endpoints
โ โโ core/
โ โ โโ __init__.py
โ โ โโ utils.py # ID extraction, filename sanitization
โ โ โโ library_manager.py # Library: ties DB + storage + managers
โ โ โโ track_manager.py # Track loading/downloading logic
โ โ โโ playlist_manager.py # Playlist loading, metadata, relations
โ โ โโ sync_manager.py # Playlist sync against Spotify
โ โ โโ job_manager.py # Job CRUD: create_job, update_job_progress, complete_job, fail_job, get_job
โ โ โโ notification_bus.py # Event bus: publish_sync, subscribe, bridge for WebSocket
โ โโ db/
โ โ โโ __init__.py
โ โ โโ models.py # SQLModel tables: Track, Playlist, PlaylistTrack, Job
โ โ โโ database.py # SQLite engine + session helpers
โ โโ storage/
โ โ โโ __init__.py
โ โ โโ path_manager.py # StoragePaths, track paths, metadata paths
โ โ โโ file_manager.py # FileManager: atomic moves, deletes, tmp copies
โ โ โโ playlist_metadata.py # JSON metadata read/write for playlists
โ โ โโ zip_builder.py # ZIP creation for playlists
โ โโ schemas/
โ โ โโ __init__.py
โ โ โโ track.py # TrackDetails dataclass
โ โ โโ playlist.py # PlaylistDetails dataclass
โ โโ services/
โ โโ __init__.py
โ โโ spotify_client.py # Thin adapter to syncify.get_track/get_playlist
โ โโ tagging.py # Cover art embedding into MP3s
โ โโ downloader.py # Download pipeline: YouTube -> tmp MP3
โ โโ youtube.py # Selenium + yt-dlp + ffmpeg utilities
โโ storage_data/ # Default storage root (created at runtime)
โโ data/ # Default SQLite DB path (created at runtime)
๐งช Development
Set up for development
git clone https://github.com/adelelawady/synciflow.git
cd synciflow
python -m venv .venv
# Linux / macOS
source .venv/bin/activate
# Windows
.venv\Scripts\Activate.ps1
pip install -e ".[dev]"
Run tests
If a test suite is configured (e.g. with pytest):
pytest
Run the CLI locally
synciflow smart
synciflow track "https://open.spotify.com/track/XXXXXXXXXXXXXXX"
Run the API locally
uvicorn synciflow.api.server:create_app --factory --reload --host 127.0.0.1 --port 8000
Then open http://127.0.0.1:8000 or add a docs UI (e.g. via FastAPIโs automatic docs if enabled).
๐ค Contributing
Contributions are very welcome!
- Bug reports & feature requests:
Open an issue with a clear description and, if possible, reproduction steps. - Pull requests:
- Fork the repo.
- Create a feature branch:
git checkout -b feature/my-awesome-idea
- Make your changes, add tests where appropriate.
- Run the test suite (and format/lint, if configured).
- Open a Pull Request describing:
- What you changed
- Why itโs useful
- Any breaking changes or migration notes
Please keep code style consistent with the existing project and prefer small, focused PRs.
๐ License
This project is open source.
See the LICENSE file in the repository for the full license text.
โญ Support
If you find synciflow useful:
- Star the repository on GitHub โ it really helps others discover the project.
- Share it with friends or colleagues who might benefit from an offline, scriptable music library.
If you like it, star it โญ
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file synciflow-4.0.0.tar.gz.
File metadata
- Download URL: synciflow-4.0.0.tar.gz
- Upload date:
- Size: 232.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.0.1 CPython/3.12.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
258a02e44b428016b71fa11078e32bdf04f37c558012520c657ff0cee641276a
|
|
| MD5 |
9e0430d83250ff54bfc1e4b6e4f02f23
|
|
| BLAKE2b-256 |
6af2e0b9f782ca163db9e73b2a3ab6009704076a20ed063c42d8cae38df109d9
|
File details
Details for the file synciflow-4.0.0-py3-none-any.whl.
File metadata
- Download URL: synciflow-4.0.0-py3-none-any.whl
- Upload date:
- Size: 241.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.0.1 CPython/3.12.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bd395e59d9f642350d90b27023a7a840d12e7b960c62d8fd2923c44772087666
|
|
| MD5 |
2230e4f4cb9ec0273f231bce0f6c4fdd
|
|
| BLAKE2b-256 |
13811724de375413315a20c1a3f242cd6cc53ea027c4f0f6b4befdd08bd2a655
|