6.8 KiB
Votify Download System
Overview
Votify is the primary Spotify download backend. It invokes the votify CLI tool (a third-party Python package) as a subprocess, streams its output to the job log, and post-processes the resulting files.
Upstream is the official glomatico/votify project, installed from git in the Dockerfile. It previously used the GladistonXD/votify-fix fork; the fork's CLI flag names differ from upstream's, so run_download() was remapped when the swap was made (see CLI flag mapping).
How It Works
- User submits Spotify URLs and options via
POST /api/download. - A job is created and a background thread runs
run_download(). run_download()builds avotifyCLI command and launches it viasubprocess.Popen.- stdout is streamed line-by-line into the job's output log.
- On completion, post-processing runs:
- Flatten nested directories
- Rename files from embedded metadata
- Wrap single-track downloads in a folder
- Convert to MP3 if requested
Authentication
Votify authenticates with Spotify using a cookies.txt file in Netscape format. This file must be uploaded by an admin via the Settings page before any downloads will succeed.
Upstream supports three session types (--session-type: librespot, desktop, web), defaulting to librespot. The app does not pass the flag, so the default applies — which is why the Dockerfile installs the [librespot] extra. Installing plain votify without the extra will fail at runtime under the default session type.
Path: /config/cookies.txt (configurable via COOKIES_PATH env var)
A Widevine device certificate (device.wvd) may also be required depending on the content. It is uploaded separately via Settings.
Path: /config/device.wvd (configurable via WVD_PATH env var)
Download Options
| Option | Values | Description |
|---|---|---|
audio_quality |
aac-medium, aac-high, vorbis-low, vorbis-medium, vorbis-high |
Audio quality |
output_format |
original, mp3 |
Keep original format or convert to MP3 |
download_mode |
ytdlp, aria2c |
Download backend |
save_cover |
bool | Save cover art as a separate image file |
save_playlist |
bool | Save playlist metadata file |
overwrite |
bool | Re-download if file already exists |
download_music_videos |
bool | Prefer video streams where available |
no_lrc |
bool | Skip LRC (lyrics) file generation |
save_lrc |
bool | Download only the synced-lyrics file |
video_format |
mp4, webm |
Format for music videos |
cover_size |
small, medium, large, extra-large |
Cover art resolution |
truncate |
int (optional) | Max file/folder name length (not a track-count limit) |
These are the app-level option names sent to POST /api/download; they are stable and unchanged by the upstream swap. The CLI flags they translate into are not — see below.
Upstream also accepts curl for --audio-download-mode and the FLAC qualities flac-flac, flac-mp4, flac-flac-24, flac-mp4-24 (premium accounts only). Neither is exposed in the UI; the values above are what the frontend offers.
CLI Flag Mapping
run_download() builds the argv. Most flags were renamed between the old fork and upstream:
| App option | Old (votify-fix) flag |
Current (official) flag |
|---|---|---|
| output dir | --output-path |
--output |
| temp dir | --temp-path |
--temp |
download_mode |
--download-mode |
--audio-download-mode |
save_cover |
--save-cover |
--save-cover-file |
save_playlist |
--save-playlist |
--save-playlist-file |
download_music_videos |
--download-music-videos |
--prefer-video |
save_lrc |
--lrc-only |
--synced-lyrics-only |
no_lrc |
--no-lrc |
--no-synced-lyrics-file |
| folder flattening | --template-folder-{album,compilation,episode,music-video} |
--album-folder-template, --compilation-folder-template, --podcast-folder-template, --no-album-folder-template |
Unchanged: --cookies-path, --wvd-path, --audio-quality, --video-format, --cover-size, --overwrite, --truncate.
All four folder templates are set to . so downloads land flat in the job's output directory; post_process_votify_files() then imposes the final layout. Upstream has no music-video folder template — --no-album-folder-template (used for albumless tracks) took that slot.
--no-config-file is always passed so a stray ~/.votify/config.ini cannot silently override these flags.
MP3 Conversion
If output_format is set to mp3, files are converted after download using ffmpeg at 320 kbps. The conversion preserves embedded metadata. Original files are deleted after successful conversion.
Cancellation
The Popen process handle is stored on the job dict. POST /api/jobs/<id>/cancel calls process.terminate(), which sends SIGTERM to the votify subprocess.
Post-Processing Detail
After the subprocess exits, post_process_votify_files() runs:
- Snapshot before — records which audio files existed before the download started (to identify new files).
- Flatten — collapses single-subdirectory chains into the parent folder.
- Rename — calls
rename_from_metadata()to produceTitle - Artist.extfilenames. - Wrap singles — if exactly one file downloaded with no enclosing folder, wraps it in a folder named after the file.
- Cleanup — removes leftover empty directories.
External Dependencies
| Dependency | Purpose |
|---|---|
votify (GitHub: glomatico/votify, [librespot] extra) |
Spotify download CLI |
ffmpeg |
MP3 conversion |
aria2c |
Optional download manager |
yt-dlp |
Default download manager |
mp4decrypt (Bento4) |
MP4 DRM decryption |
Limitations
- Requires valid Spotify cookies (must be refreshed periodically when they expire).
- DRM-protected content requires a Widevine device certificate.
- Quality options are limited to what Votify and the Spotify API expose.
- Upstream warns that some users have had their Spotify accounts suspended for using it.
- Interactive prompts can break jobs. Upstream uses
inquirerpyand will prompt on stdin for artist URLs (unless--auto-media-optionis passed) and for--video-format ask. The subprocess has no usable stdin under Gunicorn, so such a job fails rather than completing. The UI only offersmp4/webm, so in practice this is reachable only via artist URLs or direct API calls.
Key Files
| File | Relevance |
|---|---|
| app.py | run_download(), post_process_votify_files(), route /api/download |
| utils.py | rename_from_metadata(), cleanup_empty_dirs() |
| Dockerfile | Installation of votify, ffmpeg, aria2, Bento4 |