Dup Goto 📝

VirtualDJ Prepared Stems Format

DAW/dj/virtualdj virtualdj 11-13 12:06:42
To Pop
96 lines, 279 words, 2657 chars Thursday 2025-11-13 12:06:42

VirtualDJ Prepared Stems Format

As of the time of writing, Virutal DJ splits into 5 stems. Prepared stems are stored in a .mp4 file with (no video and) 5 audio tracks. In order, these are:

  1. Vocal
  2. Hihat
  3. Bass
  4. Instruments
  5. Kick

The .vdjstems file includes a title tag on each stream identifying what it is.

Extractor Script

This is a simple python script to extract the 5 stems. This works on newer .vdjstems that include a title on each stem.

#!/usr/bin/env python
from subprocess import run
import json
import os
import argparse

def main():
  parser = argparse.ArgumentParser(prog="vdj_extract_stems",
                                  description="Extract VDJ Stems")
  parser.add_argument("files",nargs="+")
  ns = parser.parse_args()

  os.makedirs("extract",exist_ok=True)
  for ifn in ns.files:
    proc(ifn)

def proc(ifn):
  m = run(["ffprobe","-print_format","json","-show_streams",ifn],capture_output=True)
  data = json.loads(m.stdout.decode())
  streams = data["streams"]
  for stream in streams:
    title = stream["tags"]["title"]
    index = stream["index"]
    xs = ifn.split(".")
    x = ".".join(xs[:-2])
    ofn = f"extract/{x}_{title.upper()}.m4a"
    run(["ffmpeg","-n","-i",ifn,"-c:a","copy","-map",f"0:{index}",ofn])

if __name__ == "__main__":
  main()

Slightly More Robust

This defaults to naming each stem STEM00, STEM01 etc. if the stems are not tagged. Most likely VDJ uses the same order.

#!/usr/bin/env python3
from subprocess import run
import json
import os
import argparse

def main():
  parser = argparse.ArgumentParser(prog="vdj_extract_stems",
                                  description="Extract VDJ Stems")
  parser.add_argument("files",nargs="+")
  ns = parser.parse_args()

  os.makedirs("extract",exist_ok=True)
  for ifn in ns.files:
    proc(ifn)

def proc(ifn):
  m = run(["ffprobe","-print_format","json","-show_streams",ifn],capture_output=True)
  data = json.loads(m.stdout.decode())
  streams = data["streams"]
  mapping = []
  try:
    for stream in streams:
      title = stream["tags"]["title"]
      index = stream["index"]
      xs = ifn.split(".")
      x = ".".join(xs[:-2])
      ofn = f"extract/{x}_{title.upper()}.m4a"
      mapping.append((index,ofn))
  except KeyError:
    for i,stream in enumerate(streams):
      index = stream["index"]
      title = f"STEM{i:02d}"
      xs = ifn.split(".")
      x = ".".join(xs[:-2])
      ofn = f"extract/{x}_{title.upper()}.m4a"
      mapping.append((index,ofn))
  for index,ofn in mapping:
    run(["ffmpeg","-n","-i",ifn,"-c:a","copy","-map",f"0:{index}",ofn])

if __name__ == "__main__":
  main()