Hide code cell content

import shutil

if shutil.which("ffmpeg") is None:
    raise RuntimeError(
        "ffmpeg not found on PATH. Install ffmpeg and restart the kernel."
    )

Transcode a Traktor playlist to MP3 files¶

This notebook exports a single Traktor playlist from a collection.nml and creates:

  • A output directory containing all audio files transcode into MP3 files

  • An .m3u playlist that references those files

Typical use case¶

  • Your Traktor collection was built on Windows (e.g. files live under D:/SYNC/...)

  • You are running this notebook on Linux, where the same drive is mounted somewhere like /mnt/media/music

  • Multiple music formats need to be converted to mp3 for other applications

Prerequisites¶

Make sure you have ffmpeg installed and available on your PATH (ffmpeg -version works).

Setup¶

First we obtain our traktor .nml library collection which contains the playlists saved inside the traktor application.

from plistsync.services.traktor import NMLLibrary

# Path to you traktor collection
collection_path = "/Users/paul/Music/Traktor/collection.nml"
collection = NMLLibrary(collection_path)

We can quickly print all available playlists to get an overview of our library.

for pl in collection.playlists:
    print(pl.name)

Next we select one playlist out of all playlist in the collection. For this change the name to the playlist you want to choose.

playlist = collection.get_playlist_or_raise(name="pl_name")

Let’s create a folder with the same name as the playlist to store the converted tracks.

from pathlib import Path

out_dir = Path(f"./{playlist.name}")
out_dir.mkdir(parents=True, exist_ok=True)

Converting NMLTracks to LocalTracks¶

NMLTrack/NMLPlaylistTrack objects are parsed from Traktor’s collection.nml and describe playlist tracks as Traktor stores them. LocalTrack represents the same track as a local filesystem Path, suitable for copying or transcoding.

In case you are running this on a machine with different mount points or paths to the one defined in the traktor .nml library you will need to apply a path rewrite the paths.

We expose the nifty PathRewrite class to help you with this.

from plistsync.core.rewrite import PathRewrite

# On windows we mount an external drive with music D:/Music
# While running the script on a linux machine the the music
# is now located in /mnt/media/music
rewrite = PathRewrite.from_str(old="/D:/SYNC", new="/mnt/media/music")

Converting to LocalTracks lets us verify the files exist and are readable/accessible before processing.

from plistsync.services.local import LocalTrack
from plistsync.services.traktor import NMLPlaylistTrack, NMLTrack


def to_local_track(track: NMLPlaylistTrack | NMLTrack) -> LocalTrack:
    """Convert a NMLTrack to a local track."""
    local_path = rewrite.apply(track.path)
    return LocalTrack(
        path=local_path,
    )


local_tracks = [to_local_track(t) for t in pl.tracks]

Not all local tracks are already MP3, and sometimes you need a specific format for playback. We use ffmpeg to transcode tracks when needed before writing them to the output directory.

import subprocess


def _transcode_to_mp3(input_path: Path, output_path: Path) -> None:
    """
    Transcode an audio file to MP3 using the system `ffmpeg` binary.

    Notes
    -----
    Requires `ffmpeg` to be installed and available on PATH.
    Metadata is copied from the input and ID3v2.3 tags are written for
    broad compatibility.
    """
    output_path.parent.mkdir(parents=True, exist_ok=True)
    if output_path.exists():
        return

    # fmt: off
    cmd = [
        "ffmpeg", "-hide_banner", "-loglevel", "error", "-n",
        "-i", str(input_path),
        "-map_metadata", "0", # Copy metadata
        "-id3v2_version", "3", # ID3v2.3
        "-vn", # Drop video stream
        "-codec:a", "libmp3lame", # encoder
        "-b:a", "320k", # bitrate
        str(output_path),
    ]
    subprocess.run(cmd, check=True)
    # fmt: on


def to_mp3_local_track(track: LocalTrack, out_dir: Path) -> LocalTrack:
    """
    Ensure `track` is available as an MP3 inside `out_dir`.

    - If the input is already an MP3, it is copied (no re-encoding).
    - Otherwise, it is transcoded to MP3 via `_transcode_to_mp3()`.

    Returns a `LocalTrack` pointing at the MP3 in `out_dir`.
    """
    # Copy if already mp3
    if track.path.suffix.lower() == ".mp3":
        out_path = out_dir / track.path.name
        if not out_path.exists():
            shutil.copy2(track.path, out_path)
        return LocalTrack(path=out_path)

    # Transcode if not mp3
    out_path = out_dir / track.path.with_suffix(".mp3").name
    _transcode_to_mp3(track.path, out_path)
    return LocalTrack(path=out_path)

We now copy tracks into the output directory, transcoding to MP3 only when needed.

mp3_tracks = [to_mp3_local_track(t, out_dir) for t in local_tracks]

Creating the M3U playlist¶

Lastly, we write an .m3u playlist file into the same output folder. It is a simple text format with one track path per line.

m3u_path = out_dir / f"{playlist.name}.m3u"
with m3u_path.open("w") as f:
    for track in mp3_tracks:
        f.write(f"./{track.path.relative_to(out_dir)}\n")