Skip to main content

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 - format
  • uv run ruff check --fix --extend-fixable F401 - lint
  • uv run ty check - typecheck
  • uv run pytest - run test suite

Contributing

Contributions are welcome! Please follow the code of conduct when interacting with others.

Elsewhere

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)

Source distribution for playlistparser 4.2.0
File Size Uploaded
playlistparser-4.2.0.tar.gz 13.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for playlistparser 4.2.0
File Interpreter ABI Platform
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}
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