Low-level reference for VirtualDJ's macOS application layout, user data, database files, stem sidecars, and practical open-source tooling.
Last reviewed against local files and live VirtualDJ sources on 2026-05-23 (Asia/Manila). Remote skin deployment and selector behavior updated from local testing on 2026-05-24 (Asia/Manila). External-prep cue workflow added from current official sources and local database context on 2026-05-27 (Asia/Manila).
This is an operational reference. It is meant to answer questions like:
- Where does VirtualDJ keep preferences, databases, skins, pads, mappers, and caches?
- What does
database.xmllook like? - What is in
extra.db? - How are prepared stem files shaped on disk?
- Which parts can be edited safely with command-line tools?
- Which parts are still unknown or only locally inferred?
Source labels used below:
Official: current VirtualDJ manual or VDJPedia.Official forum: post by VirtualDJ staff, Development Manager, CTO, or Support staff.External: non-VirtualDJ technical standard or tooling reference.Local observation: verified on this machine.Inference: conclusion drawn from local files, official docs, or repeatable CLI inspection.Unknown: documented explicitly as not yet understood.
macOS paths to know first:
| Path | Purpose | Source |
|---|---|---|
/Applications/VirtualDJ.app |
Signed application bundle. Treat as read-only. | Local observation |
/Applications/VirtualDJ.app/Contents/Resources |
Bundled default skins, pad XML pages, sample banks, icons, controller metadata, and language archives. | Local observation |
/Applications/VirtualDJ.app/Contents/Frameworks/ml113.dylib |
Bundled machine-learning runtime library. | Local observation |
~/Library/Application Support/VirtualDJ |
Active VirtualDJ home folder on this macOS install. | Official forum, Local observation |
~/Library/Application Support/VirtualDJ/settings.xml |
Preferences, audio setups, skin state, controller choices, browser settings, sampler settings, etc. | Local observation |
~/Library/Application Support/VirtualDJ/database.xml |
Main XML track database for media on the same drive as the home folder. | Official forum, Local observation |
~/Library/Application Support/VirtualDJ/extra.db |
SQLite database for related tracks, extra track identity rows, and lyrics cache. | Local observation |
~/Library/Application Support/VirtualDJ/Cache/cache.db |
SQLite waveform cache. | Local observation |
~/Library/Application Support/VirtualDJ/Pads |
User pad pages. | Local observation |
~/Library/Application Support/VirtualDJ/Mappers |
User controller and keyboard mappings. | Local observation |
~/Library/Application Support/VirtualDJ/Skins |
Installed user skins. | Local observation |
~/Library/Application Support/VirtualDJ/RemoteSkins |
Installed VirtualDJ Remote skins. | Official forum, Local observation |
~/Library/Application Support/VirtualDJ/VideoSkins |
Installed video skins. | Local observation |
~/Library/Application Support/VirtualDJ/Plugins64 |
Intel/x86_64 plugin files and settings. | Local observation |
~/Library/Application Support/VirtualDJ/PluginsMacArm |
Apple Silicon plugin files and settings. | Local observation |
~/Library/Application Support/VirtualDJ/MyLists |
VirtualDJ 2024+ XML lists. | Official, Local observation |
~/Library/Application Support/VirtualDJ/Folders |
Virtual/favorite/filter folder metadata. | Local observation |
~/Library/Application Support/VirtualDJ/History |
Daily history playlists, usually .m3u. |
Local observation |
~/Library/Application Support/VirtualDJ/Sampler |
Sampler banks, bank order, and .vdjsample files. |
Local observation |
~/Library/Application Support/VirtualDJ/ScratchBanks |
Scratch bank XML files. | Local observation |
/Volumes/<Drive>/VirtualDJ/database.xml |
Local database for media on a different drive from the VirtualDJ home folder. | Official forum, Inference |
Older answers and some Windows-centric forum posts refer to a VirtualDJ folder under Documents. On this macOS install the active folder is ~/Library/Application Support/VirtualDJ, and a 2024 CTO forum reply points macOS users there for database.xml.
The app bundle is useful for inspection, but user work should live in the home folder.
Observed local app version:
defaults read /Applications/VirtualDJ.app/Contents/Info.plist CFBundleShortVersionString
defaults read /Applications/VirtualDJ.app/Contents/Info.plist CFBundleVersionOn this machine those commands returned:
8.5.8769
18.0.9295
Useful bundled resources:
find /Applications/VirtualDJ.app/Contents/Resources -maxdepth 1 -type f \
\( -name '*.xml' -o -name '*.zip' -o -name '*.dat' \) \
-print | sortTypical results include:
skin.zippads_stems.xmlpads_hotcues.xmlpads_sampler.xmlcontrollers.dat- default sampler banks such as
AUDIO FX.xml,FAMOUS.xml, andINSTRUMENTS.xml
Do not edit these bundled files in place. Put overrides or custom work under the VirtualDJ home folder.
VirtualDJ interface packages are user data. Keep custom and installed skins under the VirtualDJ home folder, not inside the signed application bundle.
Observed deployment folders on this macOS install:
| Package type | Folder | Observed package forms | Notes |
|---|---|---|---|
| Desktop skin | ~/Library/Application Support/VirtualDJ/Skins |
.zip packages and uncompressed folders |
Official extension installs can be done from Settings -> Extensions. Local development can use an uncompressed folder for faster edit/test cycles. |
| Remote skin | ~/Library/Application Support/VirtualDJ/RemoteSkins |
.zip packages and uncompressed folders |
Staff forum guidance says to place the skin as a zip file inside RemoteSkins; uncompressed folders were also observed locally. |
| Video skin | ~/Library/Application Support/VirtualDJ/VideoSkins |
Not mapped yet | Folder observed locally; package details still need a focused pass. |
The app bundle also contains default packages that are useful for inspection:
ls -1 /Applications/VirtualDJ.app/Contents/Resources/*skin*.zipTypical bundled packages include:
skin.zipremoteskin.zipvideoskinbroadcast.zipvideoskinkaraoke.zipvideoskinlive.zip
Treat those as read-only references. Copy them out before inspecting or modifying:
work="$HOME/Desktop/vdj-skin-inspect"
mkdir -p "$work"
cp /Applications/VirtualDJ.app/Contents/Resources/remoteskin.zip "$work/"
(cd "$work" && unzip remoteskin.zip -d remoteskin)For normal desktop skins, this repo prefers a source/build split:
- keep editable source under the repo, often split into modules
- flatten includes at build time
- install only the flat
skin.xmlplus required image/assets
Example install shape:
VDJ_HOME="$HOME/Library/Application Support/VirtualDJ"
skin_name="ModularSkeleton"
xmllint --xinclude --output build/skin.xml src/skin.xml
mkdir -p "$VDJ_HOME/Skins/$skin_name"
rsync -a --delete build/ "$VDJ_HOME/Skins/$skin_name/"Zip-package variant:
VDJ_HOME="$HOME/Library/Application Support/VirtualDJ"
skin_name="My Skin"
(cd build && zip -r "../$skin_name.zip" .)
cp "$skin_name.zip" "$VDJ_HOME/Skins/"After installation, select the skin from Settings -> Interface. Official add-on installs use Settings -> Extensions, then the relevant software section; for skins, that is the Interface tab.
Development tip: when a skin variable controls structural XML such as conditional layout branches, conditional <breaklines>, conditional defines, or <nbdecks> choices, pair the state change with load_skin so VirtualDJ reparses the skin. For simple live visibility="" changes, a reload is usually unnecessary.
Source: Official, Official forum, Community, Local test, Inference
VirtualDJ Remote is the iOS/Android companion app. Official product text describes the Remote as skinnable and using the same skin format as VirtualDJ. Staff forum notes add the practical caveats:
Transport (Local test, 2026-07-27, live iOS Remote session observed from the desktop side): the device advertises Bonjour service type _vdjremote8._tcp (SRV record pointing at the device, port 4243 observed). VirtualDJ browses for it and connects out to the device — the desktop is the TCP client, the device is the server — over one persistent TCP connection. State updates are event-driven push, not polling: idle seconds carried 0 bytes in either direction, and a deck load pushed ~249 KiB desktop→device in one second with no inbound request (track metadata/waveform-scale payload). The Network Control HTTP channel (port 80) is not involved. The wire format (framing, handshake, message schema) is uncharacterized — see TODO task 8 for the phone-side shim plan.
- There is no separate hidden Remote skin SDK.
- The default Remote skin and forum examples are the main references.
- Remote skins share the same general structure and elements as desktop skins.
- Desktop skins usually cannot be dropped in unchanged; expect to recode the layout and device-specific behavior.
- Remote 8 can load v7 Remote skins, but older skins may need updates for the Remote browser.
- Older/forum examples may use
action="browser"to open the Remote browser. - The bundled Remote 8 skins under
Skins/Built-In/Remote/mostly implement the browser as an in-skin view:toggle '$rmbrowser'switches full-screen deck/browser panels, while wide phone layouts useskin_panel 'rmbrowser' oninside a panel group. - Remote skins are scaled proportionally to the device screen, with side bars as needed.
- Avoid video preview windows in Remote skins.
- Staff guidance says to place custom Remote skins as zip files inside
RemoteSkins. - Locally installed Remote skins are selected from Settings -> Interface -> Phone/tablet remote. Settings -> Extensions is the online add-on catalog and does not list every local Remote skin folder/zip.
Remote skin packages can contain multiple XML variants for different device classes, aspect ratios, and orientations. This is not just a third-party habit: the bundled VirtualDJ Remote skin package in /Applications/VirtualDJ.app/Contents/Resources/remoteskin.zip contains root-level files such as:
16x10T.xml
16x9P.xml
16x9T.xml
19x9P.xml
3x4T.xml
4x3T.xml
9x16P.xml
9x16T.xml
9x19P.xml
The XML roots identify the intended layout, for example:
<skin name="Remote Landscape (Phone 16:9)" version="8" width="1920" height="1080" nbdecks="2" image="tablet.png" preview="landscape.png">
<skin name="Remote Portrait (Tablet 9:16)" version="8" width="830" height="1476" nbdecks="2" image="tablet.png" preview="portrait.png">Observed naming convention:
Pmeans phone-targeted layout.Tmeans tablet-targeted layout.- The numeric part names the target aspect/orientation class, not necessarily a literal canvas size.
- Older Remote 8 Default packages used names such as
skinP169.xml,skinP32.xml,skinT1610.xml, andskinT43.xml.
This suggests two valid packaging styles:
- A simple package with one
skin.xml, letting Remote scale it proportionally and add black bars if needed. - A multi-variant package with several root-level XML layouts and shared assets, letting VirtualDJ/Remote choose or expose the better layout for the connected device class.
Some downloaded Remote add-ons also act as containers for multiple nested Remote-skin zips. In that case, extract the inner zip files into RemoteSkins before selecting them in VirtualDJ.
Bundled Remote browser/settings view patterns worth reusing:
<button action="toggle '$rmbrowser'"/>
<panel name="rmdecksview" visibility="var '$rmbrowser' 0"/>
<panel name="rmbrowserview" visibility="var '$rmbrowser' 1" breakline1="90" breakline2="1476-20"/>
<oninit action="set '$rmsettings' 0"/>
<button action="toggle '$rmsettings'"/>
<panel name="rmsettingsview" visibility="var '$rmsettings' 1"/>For wide phone variants, the bundled files instead put browser in the same manual panel group as deck/mixer views and add pane-focus buttons:
<button action="skin_panel 'rmbrowser' on"/>
<button action="browser_window 'folders'"/>
<button action="browser_window 'songs'"/>
<panel name="rmbrowser" group="rmporpanels" visible="no"/>Source: Built-in skin
For local development, this machine successfully used both installed forms at once: an expanded folder at RemoteSkins/<Remote Name>/ for fast inspection and a sibling RemoteSkins/<Remote Name>.zip for the staff-recommended package form. A compatibility-oriented package can include root-level files such as skin.xml/skin.png plus tablet aliases such as skinT1610.xml, 16x10T.xml, and 4x3T.xml. Some locally observed tooling also creates a wrapped top-level folder in the zip, so checking unzip -l matters before assuming one package shape.
Basic Remote skin deployment:
VDJ_HOME="$HOME/Library/Application Support/VirtualDJ"
remote_name="My Remote Skin"
xmllint --noout build/skin.xml
(cd build && zip -r "../$remote_name.zip" .)
mkdir -p "$VDJ_HOME/RemoteSkins"
cp "$remote_name.zip" "$VDJ_HOME/RemoteSkins/"
mkdir -p "$VDJ_HOME/RemoteSkins/$remote_name"
rsync -a --delete build/ "$VDJ_HOME/RemoteSkins/$remote_name/"Connection setup is separate from skin deployment. The official manual flow is:
- Put the mobile device on the same Wi-Fi network as the VirtualDJ computer.
- Open the VirtualDJ Remote app.
- In VirtualDJ, open Settings -> Controllers -> Phone/Tablet.
- Select and connect/authorize the Remote device.
Source: Official, Official forum, Local observation, Inference
Before trying a new skin in VirtualDJ:
xmllint --noout build/skin.xml
find build -maxdepth 1 -type f -print | sortFor zip packages, verify that the XML and assets are at the package root expected by the skin:
unzip -l "My Skin.zip" | sed -n '1,80p'Common failure points:
- installing source modules instead of the flattened build output
- putting a Remote skin under
Skinsor a desktop skin underRemoteSkins - looking under Settings -> Extensions instead of Settings -> Interface -> Phone/tablet remote for local Remote skins
- missing image files referenced by the XML
- zipping a parent folder when the skin expects files at the zip root
- packaging only one Remote layout when the target devices need different phone/tablet, portrait/landscape, or aspect-ratio layouts
- relying on desktop-only features such as video previews in Remote skins
VirtualDJ has a "home" folder that contains the master database and most user configuration. Staff forum guidance describes this model:
- Files on the same drive as the home folder use the master database in the home folder.
- Files on other drives can use a local database in a
VirtualDJfolder at that drive's root. - Moving the home folder changes which files land in the master database versus a local drive database.
macOS default to check:
VDJ_HOME="$HOME/Library/Application Support/VirtualDJ"
ls -la "$VDJ_HOME"External drive database check:
find /Volumes -maxdepth 3 -path '*/VirtualDJ/database.xml' -print 2>/dev/nullUse these rules before touching live data:
- Quit VirtualDJ.
- Back up the file you will edit.
- Validate XML before and after an XML edit.
- For SQLite, expect write-ahead log files such as
extra.db-walandcache.db-wal. - Prefer VirtualDJ's own UI for destructive operations, re-analysis, and database repair.
- Treat
Cache/,license.dat, binary.dat,.mlmodelc,.vdjsample, and unknown binary files as read-only until their format is understood.
Quick backup and XML validation:
#!/usr/bin/env zsh
set -euo pipefail
VDJ_HOME="${VDJ_HOME:-$HOME/Library/Application Support/VirtualDJ}"
stamp="$(date +%Y%m%d-%H%M%S)"
cp -p "$VDJ_HOME/database.xml" "$VDJ_HOME/database.xml.$stamp.bak"
xmllint --noout "$VDJ_HOME/database.xml"
print "backup: $VDJ_HOME/database.xml.$stamp.bak"
print "database.xml is well-formed"settings.xml is a single XML document rooted at <settings>.
Observed top-level sections include:
audioConfigautomationcontrolsskinsaudiocontrollerssamplerbrowseroptions
The file stores both durable settings and UI state. Examples observed locally:
- audio setups under
<audioConfig> - current skin under
<skins><skin> - skin panel and split states under
<skinPanels>and<skinSplitState> - controller to mapper choices under
<controllers> - browser columns and shortcuts under
<browser> - last database backup timestamp under
<options><databaseBackupLast>
Inspect setting names without dumping personal values:
xmlstarlet sel -t -m '/settings/*' -v 'name()' -n \
"$HOME/Library/Application Support/VirtualDJ/settings.xml"That XPath is intentionally minimal. For a practical overview, this is usually easier:
rg -n '<[A-Za-z][A-Za-z0-9_]*' \
"$HOME/Library/Application Support/VirtualDJ/settings.xml" |
sed -E 's/^([0-9]+:)[[:space:]]*<([^ >]+).*/\1 \2/' |
head -120database.xml is the main track metadata database. It is XML:
<?xml version="1.0" encoding="UTF-8"?>
<VirtualDJ_Database Version="8.5">
<Song FilePath="/Music/Tracks/example.flac" FileSize="12345678" Flag="33554432">
<Tags Author="Artist" Title="Title" Remix="Extended Mix" Stars="5" Key="Cm" Bpm="0.468750"/>
<Infos SongLength="240.000000" FirstSeen="1767225600" PlayCount="0" Bitrate="1411.2" Cover="1"/>
<Comment>#tag #another-tag</Comment>
<Scan Version="801" Bpm="0.468750" Volume="1.250000" Key="Cm"/>
<Poi Pos="0.000000" Type="beatgrid" Bpm="128.000000"/>
<Poi Type="cue" Pos="32.000000" Name="DROP" Num="1" Color="4278255360"/>
<Poi Type="loop" Pos="224.000000" Name="OUTRO" Num="2" Size="8" Slot="2" Color="4278190208"/>
</Song>
</VirtualDJ_Database>Observed structure on this machine:
VDJ_DB="$HOME/Library/Application Support/VirtualDJ/database.xml"
xmllint --xpath 'name(/*)' "$VDJ_DB"
xmllint --xpath 'string(/*/@Version)' "$VDJ_DB"
xmllint --xpath 'count(/VirtualDJ_Database/Song)' "$VDJ_DB"
rg -o '<[A-Za-z][A-Za-z0-9_:-]*' "$VDJ_DB" |
sed 's/^<//' |
sort |
uniq -c |
sort -nrLocal counts at time of review:
VirtualDJ_Database
8.5
1566 songs
17529 Poi
1566 Tags
1566 Song
1566 Scan
1566 Infos
1566 Comment
2 LockedCues
<Song> identifies one database item.
Common attributes:
FilePath: absolute path, URL-like source path, or other VirtualDJ source path.FileSize: size in bytes when known.Flag: bit field. Some values are discussed in forums, but the complete current map is not maintained here yet.
Do not use FilePath alone as a stable identity. VirtualDJ also uses file size and internal identifiers elsewhere.
<Tags> contains user-facing metadata:
AuthorTitleGenreAlbumLabelRemixRemixerTrackNumberGroupingYearStarsUser1,User2, etc.KeyBpmFlag
Important BPM detail:
Tags/@BpmandScan/@Bpmare observed as beat duration in seconds.- Display BPM can be derived as
60 / value. Poi[@Type="beatgrid"]/@Bpmis observed as display BPM.
Example:
awk 'BEGIN { stored = 0.468750; printf "%.3f BPM\n", 60 / stored }'<Infos> stores analysis and library state:
SongLength: seconds.FirstSeen: Unix timestamp.LastModified: Unix timestamp when present.PlayCountLastPlayBitrateUserColor: decimal color value.Cover: cover-art state.
Convert a timestamp:
date -r 1767225600 '+%Y-%m-%d %H:%M:%S %Z'<Scan> stores analysis output such as:
VersionBpmVolumeKeyFlag
Treat Scan/@Flag as an unknown bit field unless a specific value has been verified.
<Comment> contains the browser comment field. It may be empty:
<Comment/>or contain text:
<Comment>#minimal #loud</Comment><Poi> stores points of interest.
Observed Type values:
rg -o 'Type="[^"]+"' "$HOME/Library/Application Support/VirtualDJ/database.xml" |
sort |
uniq -c |
sort -nrLocal counts at time of review:
11253 Type="cue"
2858 Type="automix"
1721 Type="beatgrid"
1199 Type="remix"
498 Type="loop"
Common forms:
<Poi Pos="0.000000" Type="beatgrid" Bpm="128.000000"/>
<Poi Type="cue" Pos="64.000000" Name="BREAK" Num="2" Color="4293375736"/>
<Poi Type="loop" Pos="224.000000" Name="OUTRO" Num="8" Size="16" Slot="8" Color="4278190208"/>
<Poi Pos="0.050000" Type="automix" Point="realStart"/>
<Poi Name="Break 1" Pos="90.000000" Type="remix"/>Observed interpretation:
Posis seconds.- cue
Numis the cue number. - loop
Sizeis in beats. Coloris a decimal ARGB-like integer. For example, red is commonly4294901760, which is0xFFFF0000.
Convert decimal color to hex:
printf '0x%08X\n' 4294901760<LockedCues> appears inside some <Song> entries:
<LockedCues>1</LockedCues>Observed meaning is cue locking state, but the full set of values is not documented here yet.
This section answers the practical question: "If I prepare tracks outside VirtualDJ, what can VirtualDJ pick up without hand-editing every track in the POI Editor?"
Short answer: VirtualDJ can inherit hotcue data from supported file tags or from its own database.xml cue POIs, but VirtualDJ analysis does not turn musical structure into normal Hot Cues by itself. It creates analysis POIs such as first-beat/beatgrid, automix/mix points, and remix points; user-facing Hot Cues still need to come from user action, imported cue metadata, or database preparation.
Source: Official, Local observation, Inference
| Input | What VirtualDJ can use | Practical meaning | Source |
|---|---|---|---|
| File tags | Stored Hotcue/cue information when getCuesFromTags is enabled. Official option text calls this "Get the cues field from the tag"; VDJPedia says VirtualDJ can read stored Hotcue information from ID3 tags, by default for files seen for the first time, or always if the option is changed. |
A prep tool that writes cue metadata in a format VirtualDJ recognizes can feed VirtualDJ without touching the POI Editor. This is the cleanest path for new files. | Official |
Existing database.xml entries |
<Poi Type="cue" Pos="..." Num="..." Name="..." Color="..."/> entries under a matching <Song>. |
If the VirtualDJ database already contains the cue POIs, the built-in Hot Cues pad page and skins can display/trigger them via hot_cue, cue_display, cue_color, cue_name, and related verbs. |
Official, Built-in pad page, Built-in skin, Local observation |
| VirtualDJ analysis | Beatgrid/first-beat POIs, Automix/mix POIs, and Remix Points. | These can help navigation and the Remix Points pad page, but they are not the same as normal Hot Cues 1-8/16 on controller pads. | Official, Local observation |
| Saved loops in POIs | Saved-loop POIs can appear on the HotCue pad page when saved as cue-style loop slots. | Useful for prepared loop workflows, but still requires loop POIs with the right slot/size metadata rather than plain analysis markers. | Official, Local observation |
If the goal is "drop new tracks into a folder and have Hot Cues appear on pads with no manual VirtualDJ work," one of these steps has to happen before performance time:
- Write recognized cue/hotcue metadata into the media file tags before VirtualDJ first indexes the file, then leave
getCuesFromTagsat its default "for new files" behavior or set it toalwaysfor a tag-authoritative workflow. - Use a library-conversion tool, tag editor, or custom script that understands the Serato/Traktor/Rekordbox-style cue metadata you want to feed VirtualDJ. The exact tag-frame grammar is not mapped in this repo yet.
- Generate or update VirtualDJ
database.xmlcue POIs directly after VirtualDJ has created the<Song>rows. This is the most controllable path for custom auto-cue algorithms, but it is also the path that needs the strict backup/validation rules from this file.
Source: Official, Official forum, Local observation, Inference
Tag-first workflow, best for external DJ-library tools:
- Run the outside prep tool on the audio files.
- Make sure it writes hotcue metadata to file tags or to an intermediate library format VirtualDJ can read.
- In VirtualDJ, set
getCuesFromTagstofor new fileswhen tags should seed only first import, oralwayswhen the external tags should stay authoritative. - Put the files in a watched folder or browse them in VirtualDJ so they are added to the database.
- Load a track and use the built-in Hot Cues page, or this repo's cue pad pages, to confirm the cue names, positions, and colors.
Database-first workflow, best for custom phrase/structure detection:
- Add or reveal the tracks to VirtualDJ once so
~/Library/Application Support/VirtualDJ/database.xmlgets<Song>rows with the final file paths and sizes. - Quit VirtualDJ.
- Back up and validate
database.xml. - Run the external analyzer/prep script and merge cue POIs into the matching
<Song>entries:
<Poi Type="cue" Pos="64.000000" Name="DROP" Num="1" Color="4278255360"/>
<Poi Type="cue" Pos="128.000000" Name="BREAK" Num="2" Color="4293375736"/>- Validate
database.xmlagain, reopen VirtualDJ, and load the tracks.
The macOS paths for that workflow are:
| Path | Role | Source |
|---|---|---|
~/Library/Application Support/VirtualDJ/database.xml |
Main same-drive VirtualDJ track database and cue POI store. | Official forum, Local observation |
/Volumes/<Drive>/VirtualDJ/database.xml |
Per-drive database for media on other drives. | Official forum, Inference |
~/Library/Application Support/VirtualDJ/settings.xml |
Stores explicit overrides for options such as getCuesFromTags, getTagsAuto, watched folders, and related settings. Defaults may not appear until changed. |
Official, Local observation, Inference |
Source: Official, Official forum, Local observation, Inference
- VirtualDJ does not write its cue changes back to file tags; VirtualDJ's durable cue store is its database. If another app owns the tags, decide whether tags or VirtualDJ should be authoritative before setting
getCuesFromTagstoalways. - If a file already has a VirtualDJ database entry, tag changes may not be imported again unless the relevant settings and first-seen/database state allow it.
- Direct
database.xmledits should only happen while VirtualDJ is closed. Always back up, validate XML before and after, and match by exact file path plus other identity fields where possible. Posvalues are seconds in observeddatabase.xmlcue POIs, while cue/loop UI actions may use beats, milliseconds, or display-time formats. Convert deliberately.- Auto-created Remix Points can be played from the Remix Points pad page, but they are not a substitute for numbered Hot Cues when the controller workflow expects
hot_cue 1throughhot_cue 8or16.
Source: Official, Official forum, Built-in pad page, Local observation, Inference
Install helper tools:
brew install xmlstarletExport a browser-like TSV:
#!/usr/bin/env zsh
set -euo pipefail
VDJ_DB="${VDJ_DB:-$HOME/Library/Application Support/VirtualDJ/database.xml}"
xmlstarlet sel -T -t \
-m '/VirtualDJ_Database/Song' \
-v '@FilePath' -o $'\t' \
-v 'Tags/@Author' -o $'\t' \
-v 'Tags/@Title' -o $'\t' \
-v 'Tags/@Remix' -o $'\t' \
-v 'Tags/@Key' -o $'\t' \
-v 'format-number(60 div number(Tags/@Bpm), "0.000")' -o $'\t' \
-v 'Infos/@PlayCount' \
-n \
"$VDJ_DB"Find songs that have more than eight cue points:
xmlstarlet sel -T -t \
-m '/VirtualDJ_Database/Song[count(Poi[@Type="cue"]) > 8]' \
-v 'Tags/@Author' -o ' - ' -v 'Tags/@Title' -o $'\t' \
-v 'count(Poi[@Type="cue"])' \
-n \
"$HOME/Library/Application Support/VirtualDJ/database.xml"Prefer VirtualDJ's UI for normal tagging. Direct XML edits are useful for controlled bulk changes.
The script below updates Tags/@User1 for a song selected by exact file path. It:
- requires
xmlstarlet - requires VirtualDJ to be closed
- backs up
database.xml - validates XML before and after
- handles file paths containing either single or double quotes
#!/usr/bin/env zsh
set -euo pipefail
command -v xmlstarlet >/dev/null || {
print -u2 "missing dependency: brew install xmlstarlet"
exit 1
}
VDJ_DB="${VDJ_DB:-$HOME/Library/Application Support/VirtualDJ/database.xml}"
filepath="${1:?usage: vdj-db-set-user1 <exact-file-path> <new-user1-value>}"
value="${2:?usage: vdj-db-set-user1 <exact-file-path> <new-user1-value>}"
xpath_literal() {
local s="$1"
if [[ "$s" != *"'"* ]]; then
printf "'%s'" "$s"
return
fi
if [[ "$s" != *'"'* ]]; then
printf '"%s"' "$s"
return
fi
local out="concat("
local first=1
local rest="$s"
local part
while [[ "$rest" == *"'"* ]]; do
part="${rest%%\'*}"
if (( ! first )); then
out+=","
fi
out+="'$part',\"'\""
rest="${rest#*\'}"
first=0
done
out+=",'$rest')"
printf "%s" "$out"
}
xmllint --noout "$VDJ_DB"
stamp="$(date +%Y%m%d-%H%M%S)"
cp -p "$VDJ_DB" "$VDJ_DB.$stamp.bak"
literal="$(xpath_literal "$filepath")"
song="/VirtualDJ_Database/Song[@FilePath=${literal}]"
matches="$(xmlstarlet sel -t -v "count($song)" "$VDJ_DB")"
if [[ "$matches" != "1" ]]; then
print -u2 "expected exactly one matching Song, found $matches"
exit 2
fi
has_attr="$(xmlstarlet sel -t -v "count($song/Tags/@User1)" "$VDJ_DB")"
if [[ "$has_attr" == "0" ]]; then
xmlstarlet ed -P -L \
-i "$song/Tags" -t attr -n User1 -v "$value" \
"$VDJ_DB"
else
xmlstarlet ed -P -L \
-u "$song/Tags/@User1" -v "$value" \
"$VDJ_DB"
fi
xmllint --noout "$VDJ_DB"
print "updated User1"
print "backup: $VDJ_DB.$stamp.bak"extra.db is SQLite. It can be locked while VirtualDJ is running. For read-only inspection while VirtualDJ is open, SQLite's immutable URI mode is useful:
sqlite3 "file:$HOME/Library/Application Support/VirtualDJ/extra.db?mode=ro&immutable=1" '.schema'Observed schema:
CREATE TABLE related_tracks (id INTEGER PRIMARY KEY, sid1 INTEGER, sid2 INTEGER);
CREATE INDEX idx_sid1 ON related_tracks (sid1);
CREATE INDEX idx_sid2 ON related_tracks (sid2);
CREATE TABLE track_data (
id INTEGER PRIMARY KEY,
sid INTEGER,
file TEXT,
filesize INTEGER,
artist TEXT,
title TEXT,
remix TEXT,
UNIQUE(sid)
);
CREATE INDEX idx_sid ON track_data (sid);
CREATE TABLE lyrics (lid BLOB NOT NULL PRIMARY KEY, xml TEXT NOT NULL);Observed local counts at time of review:
track_data: 61
related_tracks: 81
lyrics: 331
VirtualDJ's linked/remix relationships are stored in extra.db:
track_data.sid: signed 64-bit opaque song identifier.track_data.file: path to a track.track_data.filesize: file size in bytes.track_data.artist,title,remix: display metadata.related_tracks.sid1,related_tracks.sid2: relationship edges betweentrack_data.sidvalues.
Known unknown:
- The algorithm that creates
sidis not known yet. - It appears to be a signed 64-bit identifier, not a plain path string.
- Because the hash/ID algorithm is unknown, creating linked-track rows from scratch is unsafe unless VirtualDJ has already created
track_datarows for both files.
Read related tracks:
#!/usr/bin/env zsh
set -euo pipefail
DB="${DB:-$HOME/Library/Application Support/VirtualDJ/extra.db}"
sqlite3 "file:$DB?mode=ro&immutable=1" <<'SQL'
.headers on
.mode tabs
select
a.file as file1,
b.file as file2,
a.artist || ' - ' || a.title as track1,
b.artist || ' - ' || b.title as track2
from related_tracks r
join track_data a on a.sid = r.sid1
join track_data b on b.sid = r.sid2
order by track1, track2;
SQLAdd a relationship only when both track_data rows already exist:
#!/usr/bin/env zsh
set -euo pipefail
DB="${DB:-$HOME/Library/Application Support/VirtualDJ/extra.db}"
left="${1:?usage: vdj-link-existing <left-file> <right-file>}"
right="${2:?usage: vdj-link-existing <left-file> <right-file>}"
sqlite_literal() {
local s
s="$(printf '%s' "$1" | sed "s/'/''/g")"
printf "'%s'" "$s"
}
stamp="$(date +%Y%m%d-%H%M%S)"
cp -p "$DB" "$DB.$stamp.bak"
left_sql="$(sqlite_literal "$left")"
right_sql="$(sqlite_literal "$right")"
sqlite3 "$DB" <<SQL
insert into related_tracks (sid1, sid2)
select a.sid, b.sid
from track_data a, track_data b
where a.file = $left_sql
and b.file = $right_sql
and not exists (
select 1
from related_tracks r
where r.sid1 = a.sid
and r.sid2 = b.sid
);
select changes() as inserted_rows;
SQL
print "backup: $DB.$stamp.bak"If that script prints 0, one or both files probably do not have track_data rows yet, or the relationship already exists.
lyrics contains:
lid: 18-byte BLOB in local samples.xml: text payload.
Despite the column name, observed lyric text can look like line-oriented timestamp ranges:
[0.69-0.83] I'm
[0.89-0.90] a
or a sentinel:
#NOLYRICS
Known unknown:
- The exact
lidderivation is unknown. - The full lyric payload grammar is not documented here yet.
- How lyric cache rows relate to audio signatures and server-side cache behavior needs more verification.
Cache/cache.db is SQLite and stores waveform blobs.
Observed schema:
CREATE TABLE waveforms (
id INTEGER PRIMARY KEY,
filepath TEXT,
filename TEXT,
filesize INTEGER,
type INTEGER,
version INTEGER,
valuesPerSecond REAL,
waveform BLOB
);
CREATE INDEX idx_waveform_filename ON waveforms (filename, type);Use this for inspection only. It is a generated cache.
Read-only waveform count:
sqlite3 "file:$HOME/Library/Application Support/VirtualDJ/Cache/cache.db?mode=ro&immutable=1" \
'select count(*) from waveforms;'Other observed cache files:
Cache/cache.db-shmCache/cache.db-walCache/cache2.dbCache/cache3.dbCache/cache4.dbCache/fft
The non-SQLite cache files are currently Unknown.
VirtualDJ 2024+ lists are XML files under MyLists by default. VDJPedia says the only required song entry attribute is path, and size is highly recommended.
Generic list:
<?xml version="1.0" encoding="UTF-8"?>
<VirtualFolder noDuplicates="yes" ordered="yes">
<song path="/Music/Tracks/example.flac" size="12345678" artist="Artist" title="Title" idx="0"/>
</VirtualFolder>Observed local empty folder/list forms:
<?xml version="1.0" encoding="UTF-8"?>
<VirtualFolder /><?xml version="1.0" encoding="UTF-8"?>
<VirtualFolder noDuplicates="no" ordered="yes" />Sampler bank XML lives under Sampler/.
Example shape:
<?xml version="1.0" encoding="UTF-8"?>
<samplerbank>
<sample path="Instruments\Kick.vdjsample" group="BEATS" color="red" col="0" row="0" />
</samplerbank>Scratch banks live under ScratchBanks/ and can embed a nested <Song> record:
<?xml version="1.0" encoding="UTF-8"?>
<scratchbank name="Bank A">
<sample path="/Music/Samples/example.flac" filesize="108637" idx="1" color="#00FF37">
<Song FilePath="/Music/Samples/example.flac" FileSize="108637" Flag="1">
<Tags Flag="1" />
<Infos SongLength="2.181812" FirstSeen="1755968487" Bitrate="398" Cover="2" />
</Song>
</sample>
</scratchbank>.vdjsample is binary. Some files show a VDJ header followed by Matroska-like content, but the full format is not mapped here.
See VirtualDJ Stem File Format for the focused
.vdjstems sidecar format reference. This section keeps the shorter
application-internals view: where the files fit, how to inspect them, and how
local helper scripts recreate the observed layout.
VirtualDJ's public docs describe five stem components:
- vocal
- instruments
- bass
- hihat
- kick
The docs also say that when real-time separation is too expensive, VirtualDJ can prepare tracks in advance by saving stems in a separate file.
VirtualDJ-prepared .vdjstems files observed locally are Matroska containers with five stereo AAC streams:
0 vocal
1 hihat
2 bass
3 instruments
4 kick
Inspect one:
ffprobe -v error -select_streams a \
-show_entries stream=index,codec_name,sample_fmt,channels:stream_tags=title \
-of compact=p=0:nk=1 \
"/path/to/track.flac.vdjstems"Expected shape:
0|aac|fltp|2|vocal
1|aac|fltp|2|hihat
2|aac|fltp|2|bass
3|aac|fltp|2|instruments
4|aac|fltp|2|kick
The sidecar naming pattern is usually:
/path/to/original.ext.vdjstems
This creates a VirtualDJ-like five-stream .vdjstems container from prepared WAV files:
#!/usr/bin/env zsh
set -euo pipefail
command -v ffmpeg >/dev/null || {
print -u2 "missing dependency: brew install ffmpeg"
exit 1
}
dir="${1:?usage: vdjstems-pack-matroska <stem-dir> [output.vdjstems]}"
out="${2:-$dir/$(basename "$dir").vdjstems}"
for name in vocal hihat bass instruments kick; do
[[ -f "$dir/$name.wav" ]] || {
print -u2 "missing $dir/$name.wav"
exit 2
}
done
ffmpeg -y \
-i "$dir/vocal.wav" \
-i "$dir/hihat.wav" \
-i "$dir/bass.wav" \
-i "$dir/instruments.wav" \
-i "$dir/kick.wav" \
-map 0:a -map 1:a -map 2:a -map 3:a -map 4:a \
-metadata:s:a:0 title="vocal" \
-metadata:s:a:1 title="hihat" \
-metadata:s:a:2 title="bass" \
-metadata:s:a:3 title="instruments" \
-metadata:s:a:4 title="kick" \
-c:a aac -b:a 320k \
-f matroska \
"$out"
ffprobe -v error -select_streams a \
-show_entries stream=index,codec_name,channels:stream_tags=title \
-of compact=p=0:nk=1 \
"$out"One workable pipeline is:
- Use Demucs or another open model to produce
vocals.wav,drums.wav,bass.wav, andother.wav. - Rename
vocals.wavtovocal.wav. - Rename
other.wavtoinstruments.wav. - Split
drums.wavintokick.wavandhihat.wavwith a drum separation model. - If no drum-element splitter is available, use
drums.wavfor bothkick.wavandhihat.wavas a low-quality compatibility fallback. - Pack with the Matroska script above.
Minimal Demucs example:
#!/usr/bin/env zsh
set -euo pipefail
command -v demucs >/dev/null || {
print -u2 "missing dependency: pipx install demucs"
exit 1
}
command -v ffmpeg >/dev/null || {
print -u2 "missing dependency: brew install ffmpeg"
exit 1
}
input="${1:?usage: vdjstems-demucs-basic <audio-file> [out-dir]}"
outdir="${2:-stems-basic}"
work="$outdir/_work"
mkdir -p "$work"
ffmpeg -y -v error -i "$input" -ar 44100 -ac 2 -c:a pcm_s16le "$work/mix.wav"
model="${DEMUCS_MODEL:-htdemucs_ft}"
demucs -n "$model" -o "$work" "$work/mix.wav"
stemdir="$(find "$work" -type d -path "*/$model/mix" -print -quit)"
[[ -n "$stemdir" ]] || {
print -u2 "could not find Demucs output under $work"
exit 2
}
cp "$stemdir/vocals.wav" "$outdir/vocal.wav"
cp "$stemdir/bass.wav" "$outdir/bass.wav"
cp "$stemdir/other.wav" "$outdir/instruments.wav"
# Compatibility fallback until a real drum-element splitter is added.
cp "$stemdir/drums.wav" "$outdir/kick.wav"
cp "$stemdir/drums.wav" "$outdir/hihat.wav"
print "wrote $outdir/{vocal,hihat,bass,instruments,kick}.wav"Local helper scripts outside this reference have also experimented with a six-stream MP4/M4A layout:
0 mixed track
1 vocal
2 hihat
3 bass
4 instruments
5 kick
This is useful for archival or compatibility experiments, especially with ALAC:
#!/usr/bin/env zsh
set -euo pipefail
dir="${1:?usage: vdjstems-pack-mp4-6 <stem-dir> [output.mp4]}"
out="${2:-$dir/$(basename "$dir").vdjstems.mp4}"
for name in mixed vocal hihat bass instruments kick; do
[[ -f "$dir/$name.wav" ]] || {
print -u2 "missing $dir/$name.wav"
exit 2
}
done
ffmpeg -y \
-i "$dir/mixed.wav" \
-i "$dir/vocal.wav" \
-i "$dir/hihat.wav" \
-i "$dir/bass.wav" \
-i "$dir/instruments.wav" \
-i "$dir/kick.wav" \
-map 0:a -map 1:a -map 2:a -map 3:a -map 4:a -map 5:a \
-metadata:s:a:0 title="mixed track" \
-metadata:s:a:1 title="vocal" \
-metadata:s:a:2 title="hihat" \
-metadata:s:a:3 title="bass" \
-metadata:s:a:4 title="instruments" \
-metadata:s:a:5 title="kick" \
-c:a alac \
-f mp4 \
"$out"Known unknown:
- Current VirtualDJ recognition rules for the six-stream MP4/M4A variant need more cross-version testing.
- Exact MP4 metadata atoms required for the broadest compatibility are not fully mapped.
| File | Format | Notes |
|---|---|---|
settings.xml |
XML | Preferences and UI state. |
database.xml |
XML | Main track database. |
*.vdjfolder |
XML | Lists, virtual folders, sideview lists. |
Mappers/*.xml |
XML | Controller and keyboard mappings. |
Pads/*.xml |
XML | Pad pages. |
Skins/*/*.xml |
XML | Installed skin files. |
RemoteSkins/*.zip |
ZIP | Installed Remote skin packages. |
RemoteSkins/*/*.xml |
XML | Uncompressed Remote skin folders observed locally. |
extra.db |
SQLite | Related tracks, track_data, lyrics. |
Cache/cache.db |
SQLite | Waveform cache. |
History/*.m3u |
M3U-like playlist text | Daily history. |
*.vdjstems |
Matroska in observed VirtualDJ-prepared files | Five AAC streams named vocal/hihat/bass/instruments/kick. |
*.vdjsample |
Binary | Not fully mapped. Some files show a VDJ header and media payload. |
extra.db track_data.sid: signed 64-bit identifier/hash for linked tracks. Algorithm unknown.extra.db lyrics.lid: 18-byte blob identifier. Algorithm unknown.- Full
Song/@Flag,Tags/@Flag, andScan/@Flagbit maps. Cache/cache2.db,Cache/cache3.db,Cache/cache4.db, andCache/fft.- Complete
.vdjsamplestructure. - Whether prepared
.vdjstemssidecar metadata has fields beyond stream titles that matter to every VirtualDJ version. - Whether every VirtualDJ 2026 build accepts custom Matroska
.vdjstemsgenerated externally. - Exact role and versioning of bundled
Drivers/model3.mlmodelc,Drivers/model4.mlmodelc, andml113.dylib.
- VirtualDJ forum: macOS
database.xmllocation -Official forum - VirtualDJ forum: home folder, master database, and per-drive local databases -
Official forum - VirtualDJ manual: Extensions -
Official - VirtualDJ manual: Options list -
Official - VirtualDJ manual: POI Editor -
Official - VirtualDJ manual: Pads -
Official - VDJPedia: Rekordbox settings and reading cues from tags -
Official - VirtualDJ manual: VirtualDJ Remote -
Official - VirtualDJ manual: Remote Setup -
Official - VirtualDJ product page: VirtualDJ Remote -
Official - VirtualDJ forum: v8 Remote skins -
Official forum - VirtualDJ forum: Remote skin creating/editing issue -
Official forum - VirtualDJ forum: Android Skins? -
Official forum - VirtualDJ forum: Remote Screen -
Official forum - VirtualDJ stems help -
Official - VDJPedia: Lists -
Official - Matroska stem files Internet-Draft -
External - Local files under
~/Library/Application Support/VirtualDJ-Local observation - Local app bundle under
/Applications/VirtualDJ.app-Local observation