Declarative playlist build system for iTunes Library.xml. Define playlist rules in YAML, generate static playlists with folder hierarchy, and output a new XML file compatible with DJ software (rekordbox, Serato, Traktor, etc.).
iTunes Library.xml + rules.yml → build → library.generated.xml
iTunes/Music.app manages your master track library, metadata, ratings, and manual playlists. This tool handles:
- Smart-playlist-like condition evaluation
- Playlist dependency resolution
- Nested folder hierarchy generation
- BPM range bucketing and other generators
- Static playlist output for DJ software
- Node.js >= 20
- pnpm
- mise (optional, for dev toolchain)
pnpm install
pnpm -r buildnpx tsx packages/cli/src/cli.ts preview \
--input "iTunes Library.xml" \
--rules rules.ymlOutput:
_Generated
├ Base
│ └ Favorites
│ └ 4stars+ (7680)
└ BPM
└ Favorites
├ 120-124 (455)
├ 125-129 (734)
└ 130-134 (544)
Summary:
generated playlists: 4
generated folders: 3
referenced tracks: 8282
Options:
--jsonfor machine-readable output--verbosefor detailed rule info
npx tsx packages/cli/src/cli.ts build \
--input "iTunes Library.xml" \
--rules rules.yml \
--output "iTunes Library.generated.xml"Options:
--dry-runto evaluate and preview without writing
A JSON Schema is provided for editor validation and autocompletion. Add this comment at the top of your YAML file:
# yaml-language-server: $schema=https://raw.githubusercontent.com/tainakanchu/itunes-playlist-builder/master/packages/core/rules.schema.jsonRequires the YAML extension for VS Code (or equivalent for your editor).
namespace: "_Generated"
options:
removeExistingNamespace: true
failOnMissingPlaylist: true
dedupeTrackIds: true
caseSensitiveContains: false
playlists:
- name: "Base/Favorites/4stars+"
match:
all:
- field: rating
gte: 80
- not:
field: podcast
equals: true
sort:
- field: artist
order: asc
- field: album
order: asc
- name: "Genre/House/Favorites"
match:
all:
- inPlaylist:
source: generated
name: "Base/Favorites/4stars+"
- field: genre
contains: "House"
sort:
- field: bpm
order: asc
generators:
- type: bpmRange
basePath: "BPM/Favorites"
sourcePlaylist:
source: generated
name: "Base/Favorites/4stars+"
from: 80
to: 180
step: 5
pad: 3
sort:
- field: bpm
order: asc| Operator | Description |
|---|---|
equals |
Exact match (strings are case-insensitive by default) |
contains |
Substring match |
in |
Value in list |
gt |
Greater than |
gte |
Greater than or equal |
lt |
Less than |
lte |
Less than or equal |
exists |
Field is present (true) or absent (false) |
all— AND over child conditionsany— OR over child conditionsnot— Negation
- inPlaylist:
source: existing # or "generated"
name: "My Playlist"A minus B:
match:
all:
- inPlaylist:
source: existing
name: "All House"
- not:
inPlaylist:
source: existing
name: "Exclude/DoNotPlay"trackId, name, artist, albumArtist, composer, album, genre, bpm, rating, playCount, skipCount, year, trackNumber, discNumber, dateAdded, dateModified, location, comments, grouping, compilation, podcast, disabled, kind
Creates playlists for each BPM bucket (equal-step arithmetic):
generators:
- type: bpmRange
basePath: "BPM"
sourcePlaylist: { source: generated, name: "Base/Favorites/4stars+" }
from: 80
to: 180
step: 5
pad: 3Generates: BPM/080-084, BPM/085-089, ..., BPM/175-179
Arbitrary named numeric ranges (overlapping OK):
generators:
- type: ranges
basePath: "DJ/Zones"
sourcePlaylist: { source: generated, name: "Base/Favorites/4stars+" }
field: bpm
pad: 0
ranges:
- name: "House"
gte: 118
lt: 138
- name: "Techno"
gte: 125
lt: 148If name is omitted, auto-generates from bounds (e.g., 120-137). Supports gte, gt, lt, lte.
Creates one playlist per tag value using contains matching:
generators:
- type: tags
basePath: "Style"
sourcePlaylist: { source: generated, name: "Base/Favorites/4stars+" }
field: genre
values: ["House", "Techno", "Trance", "DnB"]Generates: Style/House, Style/Techno, etc.
Define generator parameters once and reuse across multiple sources:
templates:
bpmBuckets:
type: bpmRange
from: 70
to: 180
step: 5
pad: 3
sort:
- field: bpm
order: asc
generators:
- template: bpmBuckets
basePath: "Genre/House/BPM"
sourcePlaylist: { source: generated, name: "Genre/House/All" }
- template: bpmBuckets
basePath: "Genre/Techno/BPM"
sourcePlaylist: { source: generated, name: "Genre/Techno/All" }Template types: bpmRange, ranges, tags. The sort in a template ref overrides the template's sort.
packages/
core/ — Business logic (reusable for future GUI)
cli/ — Thin CLI wrapper
cd packages/core
pnpm test- XML serialization format may need tuning for specific DJ software compatibility
- No regex matching (planned)
- No date-relative conditions like
afterDaysAgo(planned) - No watch mode (planned)