Skip to content

Instantly share code, notes, and snippets.

@monostere0
Created June 24, 2026 19:26
Show Gist options
  • Select an option

  • Save monostere0/71d9206bbd82feb4252f7b8ad75aa3f8 to your computer and use it in GitHub Desktop.

Select an option

Save monostere0/71d9206bbd82feb4252f7b8ad75aa3f8 to your computer and use it in GitHub Desktop.
Removes files that are in your Rekordbox playlist but have been deleted from your drive
#!/usr/bin/env python3
"""
clean_missing_playlist.py
Remove all entries from a Rekordbox playlist whose linked audio file
no longer exists on disk.
By default it scans the playlist, reports every entry with a missing
file, then asks for confirmation before removing them.
Requires: pyrekordbox (pip install pyrekordbox)
Examples:
# Scan, report missing files, then ask before deleting
python clean_missing_playlist.py --playlist "My Playlist"
# Report only, never delete (no prompt either)
python clean_missing_playlist.py --playlist "My Playlist" --dry-run
# Skip the confirmation prompt (e.g. for scripting/cron)
python clean_missing_playlist.py --playlist "My Playlist" --yes
# Target a playlist by its ID instead of name
python clean_missing_playlist.py --playlist-id 123456789
# Supply the database decryption key manually (only needed if
# pyrekordbox can't find/derive it automatically)
python clean_missing_playlist.py --playlist "My Playlist" --key <KEY>
"""
import argparse
import os
import sys
def open_db(key=None):
try:
from pyrekordbox import Rekordbox6Database
except ImportError as e:
sys.exit(f"ERROR: pyrekordbox not installed: {e}\nRun: pip install pyrekordbox")
try:
return Rekordbox6Database(key=key) if key else Rekordbox6Database()
except Exception as e:
sys.exit(
f"ERROR opening rekordbox database: {e}\n"
"Make sure Rekordbox is closed (it locks the database) and has "
"been opened at least once on this machine."
)
def get_target_playlist(db, name, playlist_id):
if playlist_id is not None:
try:
return db.get_playlist(ID=playlist_id).one()
except Exception:
sys.exit(f"Error: no playlist found with ID {playlist_id!r}.")
matches = db.get_playlist(Name=name).all()
if not matches:
available = sorted(pl.Name for pl in db.get_playlist().all())
print(f"Playlist {name!r} not found. Available playlists:")
for pl_name in available:
print(f" - {pl_name}")
sys.exit(1)
if len(matches) > 1:
ids = ", ".join(str(p.ID) for p in matches)
sys.exit(
f"Error: multiple playlists named {name!r} found (IDs: {ids}). "
"Use --playlist-id to disambiguate."
)
return matches[0]
def main():
parser = argparse.ArgumentParser(
description="Remove playlist entries whose audio file is missing on disk."
)
target = parser.add_mutually_exclusive_group(required=True)
target.add_argument("--playlist", help="Name of the playlist to clean.")
target.add_argument(
"--playlist-id", type=int, help="ID of the playlist to clean."
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Show what would be removed without modifying the database.",
)
parser.add_argument(
"--yes", "-y",
action="store_true",
help="Skip the confirmation prompt and remove missing entries immediately.",
)
parser.add_argument(
"--key",
default=None,
help="Rekordbox database decryption key (rarely needed manually).",
)
args = parser.parse_args()
db = open_db(args.key)
playlist = get_target_playlist(db, args.playlist, args.playlist_id)
print(f"Scanning playlist {playlist.Name!r} (ID {playlist.ID})...")
songs = list(playlist.Songs)
missing = []
for song in songs:
content = song.Content
path = content.FolderPath if content is not None else None
if not path or not os.path.isfile(path):
missing.append((song, content, path))
if not missing:
print(f"No missing files found among {len(songs)} entries. Nothing to do.")
return
print(f"Found {len(missing)} of {len(songs)} entries with missing files:")
for _, content, path in missing:
title = content.Title if content is not None else "<unknown track>"
print(f" - {title} ({path or '<no file path>'})")
if args.dry_run:
print("\nDry run: no changes made.")
return
if not args.yes:
answer = input(f"\nRemove these {len(missing)} entries from {playlist.Name!r}? [y/N] ").strip().lower()
if answer not in ("y", "yes"):
print("Aborted. No changes made.")
return
for song, _, _ in missing:
db.remove_from_playlist(playlist, song)
db.commit()
print(f"\nRemoved {len(missing)} entries from {playlist.Name!r} and saved changes.")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment