Playlistparser
Tool for parsing DJ software playlists. Currently supports Engine DJ, Rekordbox, Serato, Traktor, and VirtualDJ. Part of Slipmat.io tools.
Free hosted version of this tool: https://slipmat.io/tools/playlist-converter/
Installing
uv add playlistparser
Usage
One end-to-end example covering format detection, streaming, required fields, aggregates, per-track data and every exception you need to handle:
import logging
from playlistparser import (
MalformedPlaylistError,
MissingFieldError,
PlaylistParser,
PlaylistParserError,
PlaylistType,
UnknownFormatError,
)
logging.basicConfig(level=logging.INFO)
try:
# Construct a parser. All keyword arguments are optional.
#
# require — fail fast if any listed field is missing on a row.
# If the format itself can't expose the field (e.g. Serato
# has no bpm), MissingFieldError is raised before track
# parsing. CSV detection reads the header first.
# as_type — override format detection (use for unusual file
# extensions); otherwise the format is detected from the
# extension, and for .csv from the header row.
# default_artist — substituted when a row has no artist field.
#
# Recoverable per-row warnings are emitted via stdlib `logging` under the
# `playlistparser.parsers.*` logger names — configure logging at the root
# (or route through structlog with `structlog.stdlib.LoggerFactory()`).
pl = PlaylistParser(
"history.csv",
require=["title", "artist"],
default_artist="Unknown Artist",
# as_type=PlaylistType.ENGINE, # uncomment to bypass detection
)
# Detected format (PlaylistType enum: ENGINE, REKORDBOX, SERATO,
# TRAKTOR, VIRTUALDJ). For .csv this triggers a one-time header sniff.
print(f"Format: {pl.playlist_type.name}")
# Stream tracks. Iteration is lazy — each pass re-reads the file unless
# you materialise with .to_list() (cached for the lifetime of the parser).
for track in pl:
# str(track) → "Artist - Title"
print(track)
# Track is a frozen dataclass with these fields (all always present;
# unsupported / missing values are 0 or ""):
# title: str, artist: str, album: str, key: str
# duration: int (seconds), year: int, bpm: float
# file_path: str, vendor_id: str
print(track.bpm, track.year, track.duration_str()) # e.g. "128.0 2024 6:42"
# Serialise for JSON / DB. no_meta=True keeps only title + artist.
payload = track.as_dict()
# Aggregates materialise the full list once and cache it.
print(f"{pl.track_count} tracks, {pl.total_duration}s total")
# Explicit materialisation if you need the list directly.
tracks = pl.to_list()
except UnknownFormatError as e:
# Extension not recognised, or CSV header didn't match any known format.
# Pass as_type=PlaylistType.X to override.
print(f"Unsupported file: {e}")
except MissingFieldError as e:
# A required field was missing — either unsupported by the format
# (raised before parsing) or absent on a specific row.
# e.field, e.line, e.track_title are available for diagnostics.
print(f"Missing '{e.field}' on line {e.line}: {e.track_title!r}")
except MalformedPlaylistError as e:
# Structural problem with the file (bad XML, truncated row, etc).
# e.path and e.line locate the problem.
print(f"Corrupt playlist: {e}")
except PlaylistParserError as e:
# Base class — catch this if you don't care which of the above fired.
print(f"Could not parse playlist: {e}")
except FileNotFoundError:
# The library does not check existence in the constructor; the file is
# opened on the first iteration / aggregate access.
print("Playlist file does not exist")
Parsing progress
Pass a callback to stream() when displaying progress:
def report_progress(tracks_done, total_tracks, bytes_read, bytes_total):
byte_percent = bytes_read / bytes_total if bytes_total else 1
print(tracks_done, total_tracks, byte_percent)
for track in PlaylistParser("set.nml").stream(on_progress=report_progress):
save(track)
Byte progress is monotonic and completes even when malformed source records are skipped. When
available, track totals count source records, so tracks_done can finish below total_tracks when
records are skipped. total_tracks is None when the source has no valid count, such as a Traktor
collection without a valid ENTRIES value. Delimited formats count logical records in a pre-pass.
Supported formats and fields
BPM is a float rounded to one decimal place. Traktor's vendor_id is its ENTRY.AUDIO_ID.
| Format | title | artist | album | key | duration | year | bpm | file_path | vendor_id |
|---|---|---|---|---|---|---|---|---|---|
| Engine DJ | x | x | x | x | x | x | x | ||
| Rekordbox | x | x | x | x | x | x | x | x | |
| Serato | x | x | x | ||||||
| Traktor | x | x | x | x | x | x | x | x | x |
| VirtualDJ | x | x | x | x | x | x |
Developing
uv run ruff format- formatuv run ruff check --fix --extend-fixable F401- lintuv run ty check- typecheckuv run pytest- run test suite
Contributing
Contributions are welcome! Please follow the code of conduct when interacting with others.
Elsewhere
- Follow @uninen.net on Bluesky
- Read my continuously updating learnings from Python / TypeScript and other Web development topics from my Today I Learned site
Licence
Copyright © 2022, Ville Säävuori. Released under the GNU Affero General Public License v3.0.
Commercial licenses are also available.
Release files for playlistparser 4.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| playlistparser-4.2.0.tar.gz | 13.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| playlistparser-4.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 31.9 kB
Release files / playlistparser-4.2.0.tar.gz
| Download URL | playlistparser-4.2.0.tar.gz |
|---|---|
| Size | 13.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d750b930f656b7beee628ba6f781fbe287aec7f978d1e7804a543120eba0b91f
|
|
BLAKE2b-256 checksum How to use checksums |
aee7a3c52edd2131505a6b4c7c5393534f75e49c96e313b9370b8f9aef73e662
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / playlistparser-4.2.0-py3-none-any.whl
| Download URL | playlistparser-4.2.0-py3-none-any.whl |
|---|---|
| Size | 18.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5663790e304c9cb114157edad0b2fb2d7f0efe1ec861d28b2e87835c4c539ef4
|
|
BLAKE2b-256 checksum How to use checksums |
8be7ebf5280dfc8c0983766258af5f487aea6f122751e33346aaa78e83edccb4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|