Skip to content

Data model

This document describes how passive-music-dl persists state: the SQLite database, the tags embedded in downloaded files, and the supported audio formats.

SQLite database

State lives in a single SQLite database file (state.db by default), managed by passive_music_dl/state/database.py. The schema is created on startup by Database.initialize().

Track

One row per downloaded audio file.

Column Type Constraints Description
Id INTEGER PRIMARY KEY AUTOINCREMENT Database ID.
MusicBrainzId TEXT UNIQUE MusicBrainz recording ID.
YouTubeMusicId TEXT UNIQUE YouTube video ID.
SpotifyId TEXT UNIQUE Spotify track ID.
FilePath TEXT UNIQUE, NOT NULL Absolute path of the downloaded file.

Each provider ID column is nullable - a track has whichever IDs are known. The FilePath column is the link back to the filesystem. An index exists on every column to support lookups.

PlaylistEntry

Associates a track with the playlist it came from.

Column Type Constraints
PlaylistProvider TEXT spotify or youtube_music.
PlaylistId TEXT Provider-specific playlist ID.
TrackId INTEGER REFERENCES Track(Id) ON DELETE CASCADE.

Primary key is (PlaylistProvider, PlaylistId, TrackId), so the same track can belong to many playlists but only once per playlist. Deleting a track cascades to its playlist entries.

Filesystem-based identity

The database is not the source of truth for what has been downloaded - the download directory is. On startup a full scan reconciles the two:

  • a file on disk with no database row is inserted by reading its embedded IDs;
  • a database row with no file on disk is deleted.

For this to work, every downloaded file carries its provider and MusicBrainz IDs in tag frames that mutagen reads back. These are the UFID (Unique File Identifier) frames, owned by namespace-style owner strings:

ID UFID owner Stored value
MusicBrainz http://musicbrainz.org Recording MBID
YouTube Music https://music.youtube.com Video ID
Spotify https://spotify.com Track ID

UFID frames are read into UniqueTrackIdentifiers (metadata/models.py) and written from TrackMetadata.

See ADR-0003: Hybrid Filesystem-Based State Management for the history behind this design.

Embedded metadata

Besides the UFID frames, each file gets its standard metadata written by the per-format tagger (metadata/async_audio_file.py):

Field MP3 (ID3) M4A (MP4) OGG/FLAC/Opus (Vorbis)
Title TIT2 ©nam title
Artist TPE1 ©ART artist
Album TALB ©alb album
Year TDRC ©day date
Track number TRCK trkn tracknumber
Genre TCON ©gen genre
Album art APIC covr metadata_block_picture

Not every field is always populated - the Spotify and YouTube Music providers currently supply title, artist(s), album, and album art; MusicBrainz adds the recording ID. TrackMetadata (metadata/models.py) carries all of the above and any unused fields are simply not written.

Supported formats

The downloader converts every track to the configured codec, and the metadata layer reads back any of the following extensions (see util/constants.py):

  • .mp3 (ID3 tags)
  • .m4a (MP4 tags)
  • .ogg, .flac, .opus (Vorbis comments)

This lets the full scan and the taggers handle files regardless of where they came from, so long as they carry the UFID frames above.