Files
trackpull/docs/votify.md
2026-08-16 23:29:15 +02:00

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

  1. User submits Spotify URLs and options via POST /api/download.
  2. A job is created and a background thread runs run_download().
  3. run_download() builds a votify CLI command and launches it via subprocess.Popen.
  4. stdout is streamed line-by-line into the job's output log.
  5. 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:

  1. Snapshot before — records which audio files existed before the download started (to identify new files).
  2. Flatten — collapses single-subdirectory chains into the parent folder.
  3. Rename — calls rename_from_metadata() to produce Title - Artist.ext filenames.
  4. Wrap singles — if exactly one file downloaded with no enclosing folder, wraps it in a folder named after the file.
  5. 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 inquirerpy and will prompt on stdin for artist URLs (unless --auto-media-option is 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 offers mp4/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