Skip to main content

GitHub GitHub top language PyPI Downloads GitHub Downloads GitHub last commit GitHub Repo stars PyPI Version Python Version License

🎵 Dotify

An asynchronous Python application and CLI for downloading playable tracks, albums, playlists, podcast episodes, shows, and video content directly from Spotify.

Installation • Quick start • Configuration • Development

[!IMPORTANT] Dotify is an independent project and is not affiliated with Spotify. Use it only for content you are authorized to access and download, and comply with the laws and service terms that apply to you.

The release version is defined once in dotify/version.py and automatically reused for the PyPI package, so the CLI and package metadata always match.

What Dotify downloads from

Dotify obtains metadata, stream URLs, manifests, cover art, and lyrics from Spotify. It does not search YouTube or substitute YouTube audio.

The yt-dlp dependency is used only as a mature HTTP/fragment transfer engine:

Spotify URL
  -> Dotify Spotify API and media interfaces
  -> Spotify CDN URL or Spotify media segments
  -> yt-dlp HTTP/fragment downloader (or aria2c/curl for audio)
  -> decrypt/remux, tag, and save

This is why yt-dlp is present even though Spotify remains the media source.

Features

  • Spotify track, album, playlist, artist, show, and episode URLs
  • Songs, podcast audio, music videos, and video podcast episodes when available
  • Vorbis, AAC, FLAC, MP4-FLAC, MP4, and WebM support
  • Ordered audio-quality fallbacks
  • Metadata, cover art embedding, and synced LRC lyrics when Spotify provides them
  • Configurable output folders, filenames, tags, remux tools, and download modes
  • Multiple URLs and URL lists from text files
  • Interactive terminal queue with item selection, progress, speed, ETA, and summary
  • Metadata-only TUI selection; streams and licenses are resolved only for chosen items
  • Persistent download state with safe --resume and --no-resume controls
  • Configurable Widevine request pacing and bounded 429 retries
  • English and Turkish terminal text
  • Guided first-time setup with dotify init
  • Environment diagnostics with dotify env doctor
  • Secure sp_dc import from Chrome, Firefox, Safari, Edge, Brave, and related browsers
  • Async, CLI-independent Python API and entry-point based plugins

Requirements

  • Python 3.10 or newer
  • A Netscape-format Spotify cookies.txt containing the logged-in account's sp_dc cookie
  • FFmpeg for the default remux workflow and video output
  • The librespot optional dependency (powered by the protobuf 6 compatible pyfreedom package) for the default librespot session

The web session downloads protected AAC/MP4 media and therefore requires a compatible Widevine device.wvd (except for dry-run and lyrics-only use). Alternative modes may require aria2c, curl, MP4Box, mp4decrypt, or Shaka Packager. Run dotify env doctor --verbose to see the current environment status.

Availability and maximum quality depend on the Spotify account, selected session type, market, media item, and supplied decryption material. A requested format is not guaranteed to exist for every item.

Installation

From PyPI

The default session type is librespot. Since the pyfreedom dependency is included in the base package, the recommended installation is:

python -m pip install --upgrade dotify-cli

To use the web session instead, a compatible Widevine device.wvd must be supplied and the session type selected explicitly:

dotify --session-type web "SPOTIFY_URL"

Run the guided setup and diagnostics:

dotify init
dotify auth librespot
dotify env doctor --verbose

Spotify web cookies and Librespot authorization are separate. The first dotify auth librespot run opens Spotify OAuth in the browser, waits for the local callback at 127.0.0.1:5588, and saves reusable credentials to ~/.dotify/librespot_credentials.json. The file is created with user-only permissions. Dotify does not silently switch to the web session if this authorization is missing or rejected.

Instead of exporting cookies.txt manually, Dotify can import only Spotify's required sp_dc cookie from a browser where you are already signed in:

dotify auth web --browser auto
dotify auth web --browser chrome --profile "Profile 2" --force

auto checks supported local browsers in order. The importer does not copy cookies belonging to other sites, writes the Netscape file atomically with user-only permissions, and leaves an existing file untouched when extraction fails. macOS may request Keychain access; Safari extraction may additionally require Full Disk Access for the terminal application.

To refresh the cookie immediately before one download, use:

dotify --cookies-from-browser auto "SPOTIFY_URL"
dotify --cookies-from-browser chrome --browser-profile "Profile 2" "SPOTIFY_URL"

The same behavior can be made persistent with cookies_from_browser in the configuration file. Because this reads the browser cookie store on every run, leave it as null if you prefer explicit refreshes through dotify auth web.

dotify init asks for the language, output directory, audio-quality profile, cookie and optional WVD paths, TUI preference, and delay between downloads. It writes ~/.dotify/config.ini and does not replace an existing configuration without confirmation or --force.

For unattended setup:

dotify init --non-interactive --language tr
dotify init --non-interactive --force --config-path /path/to/config.ini

From this repository

This checkout is the source of truth. Install it in editable mode so commands inside the virtual environment import the dotify/*.py files from this working tree directly:

git clone https://github.com/ashfiexe/dotify.git
cd dotify
python -m venv .venv
source .venv/bin/activate
python -m pip install -e ".[dev,librespot]"

If uv is installed, the equivalent setup is:

uv sync --extra dev --extra librespot
source .venv/bin/activate

Then run either form:

dotify --help
python -m dotify --help

The dotify executable in .venv/bin/ is only the command entry point for this editable project. It is not a second copy of the application. Changes to the repository's Python files are used on the next run; reinstalling is usually needed only after dependency or packaging metadata changes.

Quick start

The download subcommand is optional. dotify URL and dotify download URL are equivalent.

Authorize the default Librespot session once before the first download:

dotify auth librespot
dotify auth status
# Track
dotify "https://open.spotify.com/track/18gqCQzqYb0zvurQPlRkpo"

# Album
dotify "https://open.spotify.com/album/0r8D5N674HbTXlR3zNxeU1"

# Playlist
dotify "https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M"

# Show or individual podcast episode
dotify "https://open.spotify.com/show/SPOTIFY_ID"
dotify "https://open.spotify.com/episode/SPOTIFY_ID"

# Explicit subcommand form
dotify download "https://open.spotify.com/track/SPOTIFY_ID"

An artist URL asks which category to process. For non-interactive use, select the category explicitly:

dotify --auto-media-option artist-top-tracks "https://open.spotify.com/artist/SPOTIFY_ID"
dotify --auto-media-option artist-albums "https://open.spotify.com/artist/SPOTIFY_ID"
dotify --auto-media-option artist-singles "https://open.spotify.com/artist/SPOTIFY_ID"
dotify --auto-media-option artist-compilations "https://open.spotify.com/artist/SPOTIFY_ID"
dotify --auto-media-option artist-videos "https://open.spotify.com/artist/SPOTIFY_ID"

artist-albums, artist-singles, and artist-compilations are separate categories; an artist URL does not automatically mean "the full discography."

Terminal UI and languages

Use --tui to select items from multi-item URLs and display a live queue:

dotify --tui --language tr "SPOTIFY_URL"
dotify --no-tui "SPOTIFY_URL"

For playlist URLs the TUI first offers Download all tracks and Choose tracks. Download-all mode streams playlist entries directly instead of preparing the full track-selection list. Choose-tracks mode fetches metadata only and uses stable numeric selections. Stream URLs, audio keys, and Widevine licenses are resolved only when an item is about to download. Existing output and persistent-queue checks run first, so unselected or already completed items do not consume a license request. The TUI displays byte progress, speed, and ETA when the selected transfer backend reports them. aria2c and curl currently report an indeterminate state followed by the final transferred size.

Supported languages are en and tr. auto checks DOTIFY_LANGUAGE, then the system locale, and falls back to English:

dotify --language en "SPOTIFY_URL"
DOTIFY_LANGUAGE=tr dotify "SPOTIFY_URL"

See TUI and localization for more detail.

Common commands

Quality and output

Audio quality is a comma-separated priority list. Dotify tries entries in order until it finds one that the current session can play.

By default, Dotify may use an implicit Web/AAC fallback after a Librespot audio key rejection. Add --strict-audio-quality to restrict resolution to the explicit comma-separated list. The TUI completion line and summary report the actual codec/container; when fallback occurs, a separate table reports the requested qualities, actual format, and final file path.

dotify --audio-quality vorbis-high,aac-medium "SPOTIFY_URL"
dotify --audio-quality flac-flac-24,flac-flac,aac-high "SPOTIFY_URL"
dotify --strict-audio-quality --audio-quality vorbis-high,vorbis-medium "SPOTIFY_URL"
dotify --output "/path/to/music" "SPOTIFY_URL"
dotify --synced-lyrics-only "SPOTIFY_URL"
dotify --dry-run "SPOTIFY_URL"

Supported audio-quality identifiers:

Identifier Container/output Nominal quality Premium
vorbis-low Ogg Vorbis 96 kbps No
vorbis-medium Ogg Vorbis 160 kbps No
vorbis-high Ogg Vorbis 320 kbps Yes
aac-medium M4A/AAC 128 kbps No
aac-high M4A/AAC 256 kbps Yes
flac-flac FLAC Lossless Yes
flac-mp4 MP4-FLAC to FLAC Lossless Yes
flac-flac-24 FLAC 24-bit lossless Yes
flac-mp4-24 MP4-FLAC to FLAC 24-bit lossless Yes

Video

Use --prefer-video to choose an associated music video or a video podcast episode when Spotify reports one. Without it, Dotify prefers audio unless the track itself is video media.

dotify --prefer-video "SPOTIFY_URL"
dotify --prefer-video --video-format webm --video-resolution 1080p "SPOTIFY_URL"
dotify --auto-media-option artist-videos "https://open.spotify.com/artist/SPOTIFY_ID"

Valid resolutions are 144p, 240p, 360p, 480p, 576p, 720p, and 1080p. Valid video formats are mp4, webm, and ask.

Batch and automation

# Multiple URLs
dotify "URL1" "URL2" "URL3"

# One URL per line
dotify --read-urls-as-txt urls.txt
dotify -r urls.txt

# Non-interactive artist or liked-tracks selection
dotify --auto-media-option artist-albums "ARTIST_URL"
dotify --auto-media-option liked-tracks

# Skip already registered media with a persistent database
dotify --database-path ~/.dotify/downloads.db "SPOTIFY_URL"

Audio transfer and remux modes

dotify --audio-download-mode ytdlp "SPOTIFY_URL"  # default
dotify --audio-download-mode aria2c "SPOTIFY_URL" # requires aria2c
dotify --audio-download-mode curl "SPOTIFY_URL"   # requires curl

dotify --audio-remux-mode ffmpeg "SPOTIFY_URL"   # default
dotify --audio-remux-mode mp4box "SPOTIFY_URL"
dotify --audio-remux-mode mp4decrypt "SPOTIFY_URL"

--audio-download-mode changes only how an already resolved Spotify stream is transferred. It does not change the media provider.

Output templates

dotify \
  --album-folder-template "{album_artist}/{album} [{date:%Y}]" \
  --single-disc-file-template "{track:02d} - {title}" \
  "SPOTIFY_URL"

Available values include:

  • Media: {title}, {artist}, {album}, {album_artist}, {track}, {track_total}, {disc}, {disc_total}, {date}, {label}, {isrc}, {media_id}, {media_type}, {rating}
  • Credits: {composer}, {producer}, {publisher}
  • Playlist: {playlist_title}, {playlist_artist}, {playlist_track}, {playlist_id}

Templates use Python format syntax, so compatible values can include format specifiers such as {track:02d} and {date:%Y}.

Configuration

The default configuration file is ~/.dotify/config.ini on all supported platforms. CLI options take precedence over values in that file. Use --config-path for another file or --no-config-file for one run without it.

The file uses a single [dotify] section and option names without leading dashes:

[dotify]
language = auto
tui = false
cookies_path = /home/user/.dotify/cookies.txt
cookies_from_browser = null
browser_profile = null
librespot_credentials_path = /home/user/.dotify/librespot_credentials.json
wvd_path = /home/user/.dotify/keys/device.wvd
output = ./Spotify
temp = .
session_type = librespot
audio_quality = vorbis-medium
strict_audio_quality = false
audio_download_mode = ytdlp
audio_remux_mode = ffmpeg
video_format = mp4
video_resolution = 1080p
wait_interval = 10
widevine_retries = 2
widevine_backoff = 60
widevine_max_wait = 120
widevine_request_interval = 0
queue_state_path = /home/user/.dotify/queue.json
resume = true
log_level = INFO

The shown values are CLI defaults. The guided initializer intentionally uses a more convenient first-run profile: the current directory's Spotify folder, ~/.dotify/temp, balanced vorbis-high,aac-medium quality, TUI enabled, and a one-second wait.

Use dotify download --help as the authoritative list of configurable options.

Environment commands

dotify env setup
dotify env setup --create-placeholders
dotify env doctor
dotify env doctor --verbose
dotify env doctor --json
dotify env check config
dotify env check cookies
dotify env check wvd
dotify env check librespot
dotify env check ffmpeg
dotify env check python
dotify env paths

env doctor --verbose also reports optional aria2c, mp4box, mp4decrypt, and packager binaries. Preflight checks run before downloads by default; --skip-preflight bypasses them for debugging but does not make a missing runtime dependency usable.

Download commands return exit code 0 when all items succeed or are intentionally skipped, 1 when setup, authentication, URL, API, dependency, or download errors occur, and 2 for invalid command usage. User-facing error messages hide tracebacks by default; add --exceptions when debugging.

Troubleshooting

Symptom Check
dotify is not found Activate .venv, use .venv/bin/dotify, or run python -m dotify
Cookies or sp_dc error Export a current Netscape-format cookie file from a logged-in Spotify session and check --cookies-path
Browser cookie import cannot find sp_dc Sign in at open.spotify.com in the selected browser, close private/incognito mode, then run dotify auth web --browser chrome --force; on macOS approve Keychain access if requested
Librespot credentials are missing Run dotify auth librespot; Spotify web cookies do not replace this OAuth step
Librespot returns 403 for a Premium account Run dotify auth librespot --force to replace the stored authorization; Dotify will report the error instead of switching to Web
Librespot reports Audio key error, code: 1 This Spotify-side rejection can affect individual OAuth/Premium sessions. When a valid WVD is configured, Dotify retries that item with Spotify's protected AAC/Web stream; otherwise use --session-type web --wvd-path /path/to/device.wvd
Widevine license returns 429 Dotify honors Retry-After, serializes license calls, and applies bounded retries. Avoid parallel commands. Tune conservatively with --widevine-retries, --widevine-backoff, --widevine-max-wait, and --widevine-request-interval; these controls reduce request pressure but cannot bypass Spotify's limit
pyfreedom module is missing Install dotify-cli[librespot] or select another supported --session-type
FFmpeg error Install FFmpeg, add it to PATH, or set --ffmpeg-path
Requested quality is unavailable Check account tier, session type, WVD path, market availability, and the fallback list
Video decrypt/remux fails Run dotify env doctor --verbose and verify FFmpeg plus the selected MP4/WebM decryption tools
Need diagnostic detail Run with --exceptions --log-level DEBUG --log-file dotify.log

Python API and plugins

Dotify exposes a reusable async API that does not depend on Click configuration:

import asyncio
from pathlib import Path

from dotify import DotifyClient, DotifySettings


async def main() -> None:
    settings = DotifySettings(
        output_path="./Music",
        audio_quality=("vorbis-high", "aac-medium"),
        widevine_retries=2,
        widevine_request_interval=5,
        queue_state_path=str(Path.home() / ".dotify" / "queue.json"),
        resume=True,
    )
    cookies = Path.home() / ".dotify" / "cookies.txt"

    async with await DotifyClient.from_cookies(str(cookies), settings) as client:
        result = await client.download("https://open.spotify.com/track/SPOTIFY_ID")
        print(result.paths)


asyncio.run(main())

See Python API and plugins for the supported surface and extension protocols.

Development

After installing the checkout in editable mode:

pytest -m "not performance"
pytest -m performance
ruff check dotify tests
python -m build

Architecture and contribution guidance is in CONTRIBUTING.md.

License

Dotify is distributed under the MIT License.

Metadata

Release files for dotify-cli 3.0.6

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dotify-cli 3.0.6
File Size Uploaded
dotify_cli-3.0.6.tar.gz 135.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for dotify-cli 3.0.6
File Interpreter ABI Platform
dotify_cli-3.0.6-py3-none-any.whl Python 3 none any Details

Total release size: 269.7 kB

Release files / dotify_cli-3.0.6.tar.gz

Download URL dotify_cli-3.0.6.tar.gz
Size 135.9 kB
Tags Source
SHA-256 checksum
How to use checksums
a1c982b13525100bc40966b70ebd3ff17fbd678a3e43efaf373127be5992a8cf
BLAKE2b-256 checksum
How to use checksums
ab84687c858b47379d6f4f3970ec267b3143eccaff3948beabb22ebb5cf721c4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.

Transparency log

Release files / dotify_cli-3.0.6-py3-none-any.whl

Download URL dotify_cli-3.0.6-py3-none-any.whl
Size 133.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
cb7c65baf684a61b63a4853153ca16c1876c70a90061f0243c9e2bfcdc3cdc05
BLAKE2b-256 checksum
How to use checksums
8ec6f2ff00455aa125ad628c878a375a0b86dcb58512c061f96905ca80f92ad4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

3.0.6 This release

2 release files

3.0.5

2 release files

2.0.8

2 release files

2.0.7

2 release files

2.0.0

2 release files

1.9.8

2 release files

1.5.4

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page