A PowerShell 7 batch tool that exports iTunes playlists to MP3 files — with album-level ReplayGain baked in, EQ optimised for small Bluetooth speakers, and silence trimmed from each track.
- Reads all
.m3u8and.xml(iTunes plist) files from a playlist folder - For each playlist, re-encodes every track to MP3 with:
- Album-level ReplayGain measured and baked into the audio (not tag-only)
- EQ optimised for small dual-40mm driver Bluetooth speakers
- Silence trimmed from the start and end of each track
- Peak limiter applied to prevent clipping after gain
- Sequential number prefix prepended to each filename
- M4A files are re-encoded to MP3 automatically
- Writes a log file to each output folder
- PowerShell 7 — install from https://aka.ms/powershell (verify with
pwsh --version) - ffmpeg — recommended: gyan.dev "essentials" build from https://ffmpeg.org/download
| File | Description |
|---|---|
export-playlists.ps1 |
Main script (requires PS 7.0+) |
export-playlists.ini |
All user-editable configuration variables |
export-playlists.bat |
Double-click launcher |
- Install PowerShell 7 and ffmpeg (see requirements above)
- Edit
export-playlists.ini— set your paths and preferences - Double-click
export-playlists.batto run
All settings are in export-playlists.ini:
[Paths]
PlaylistDir = C:\path\to\playlists
OutputDir = C:\path\to\output
FfmpegPath = C:\path\to\ffmpeg.exe
[Audio]
OutputBitrate = 192k
ChannelLayout = stereo
SilenceThresholdDB = -60
[ReplayGain]
ApplyReplayGain = true
TargetLUFS = -16.0
LimiterCeiling = 0.95
ParallelJobs = 0
[EQ]
ApplyEQ = true
EQ_HighpassHz = 80
EQ_LowMidBoostHz = 150
EQ_LowMidBoostDB = 3
EQ_PresenceHz = 3500
EQ_PresenceDB = 2
EQ_HiShelfHz = 12000
EQ_HiShelfDB = -2For storage-limited devices, you can export to mono with automatic pan filter downmixing:
[Audio]
OutputBitrate = 160k
ChannelLayout = mono
[EQ]
EQ_LowMidBoostDB = 3.5How mono downmixing works:
- Standard stereo-to-mono loses 3-6 dB of width information (guitars, synth pads, backing vocals disappear)
- Script applies an intelligent pan filter that preserves width by using a hotter stereo sum:
0.6*L + 0.6*R - Built-in EQ compensates for frequency imbalances:
-1.5dB at 300Hz(removes mud),+2.5dB at 3kHz(restores presence) - Your
EQ_LowMidBoostDBis increased to3.5dBto restore warmth/body lost in the downmix
Results:
- File size: ~50% smaller than stereo
- Quality: Imperceptible loss for small speakers; recommended for storage-limited devices
- M3U8 — UTF-8 encoded, Windows absolute paths
- XML — iTunes plist format; one playlist per file; track order and paths read from the plist structure
The script tracks which playlists have been encoded using a manifest file (.export-manifest.json):
- First run: All tracks encoded; manifest saved
- Unchanged playlist: Skipped instantly (no processing)
- Changed playlist: Script detects added/removed/reordered tracks and prompts:
- (A)ll — Re-encode all tracks
- (N)ew — Encode only new tracks; recalculate ReplayGain from all tracks
- (D)elete — Delete folder and rebuild from scratch
- (S)kip — Skip this playlist
This saves time when updating playlists frequently.
After encoding all playlists, the script optionally syncs your music files to a USB drive or other removable media:
Options:
- (N) Don't sync — Exit without copying (default)
- (S) Sync changed files — Copy new/updated files to media; keep existing files untouched (safe, incremental)
- (M) Mirror with cleanup — Copy everything and delete old files from media (full sync, but dangerous — removes personal files)
- (D) Select drive — Choose a different removable drive first
How it works:
- Auto-detects removable drives (USB, memory cards, external drives)
- Shows source size, destination info, and available free space
- Uses
robocopyfor fast, reliable file sync - Writes a log to the destination drive:
robocopy.log - Safe by default: option (S) never deletes; option (M) prompts for confirmation first
Example: Copy your encoded playlists to a USB drive for car stereo or portable device in one step.
- If an output folder already exists, the script will ask before overwriting
- Missing tracks are counted and logged but do not stop processing
- Filenames with special characters (brackets, apostrophes) are handled correctly
- Set
ApplyReplayGain = falseto skip loudness measurement and encode at unity gain - Set
ApplyEQ = falseto bypass all EQ processing ParallelJobs = 0in config enables auto-detect (cores - 1, minimum 4)
Major stability and performance improvements:
- ✅ Real-time progress bars — Phase 1 & 2 now show live progress as tracks complete (previously froze during encoding)
- ✅ Robust path handling — All file system operations use
-LiteralPathto safely handle bracket characters in filenames - ✅ Safer robocopy sync — Phase 3 now uses
& robocopywith array arguments instead ofInvoke-Expression(prevents parsing errors with unusual paths) - ✅ Silent track handling — LUFS measurement now gracefully handles
-inf(pure silence) with fallback to -70.0 dB - ✅ Folder name sanitization — Removes trailing spaces and periods to prevent "ghost directories"
- ✅ Performance optimization — INI parsing now uses
[System.IO.File]::ReadAllLines()for faster startup
- Fixed:
-ThrottleLimit $using:variable scope error in Phase 2
- Added Phase 3: optional robocopy sync to removable media with 3 modes (No sync / Sync / Mirror)
- Fixed mono downmix with intelligent pan filter (
0.6*L + 0.6*R) instead of simple-ac 1 - Consolidated mono variant config into comments (no separate
.inifile needed)
- Fixed: "New only" encoding mode crashes (scalar unboxing + progress percentage overflow)
- Improved manifest-based change detection accuracy
- Migrated config from
.ps1to.inifile format - Added
ChannelLayoutoption for stereo/mono export
- CPU core auto-scaling (
ParallelJobs = 0for auto-detect) - TrackLUFS per-track manifest storage for consistent album gain on updates
- Phase 2 parallelization with
ForEach-Object -Parallel
- Fixed SHA256 hash computation (required
::Create()call)
- Manifest-based change detection (
.export-manifest.json)
- Fixed corrupted artwork crash (added
-map 0:ato ffmpeg)
- Phase 2 parallelization with throttle limit
- Fixed bracket wildcard crashes (added
-LiteralPath) - Fixed
$ParallelJobsnull crashes - Fixed PowerShell 7 scriptblock serialization for parallel scope