π Join us on Discord β let's improve this thing together!
PMDA (Plex Music Dedupe Assistant) is a powerful tool to scan your Plex Music library, detect duplicate albums, and remove the lowest-quality versions β automatically or via a beautiful web interface.
Whether you're a FLAC snob or just want fewer copies of the same album floating around, PMDA's got your back.
Here's what PMDA currently supports:
- π Scans your entire Plex Music library
- 𧬠Auto-detects Plex libraries, paths and DB structure
- π Automatically maps your Plex Music librar(ies) paths with PMDA paths
- π― Detects duplicate albums using precise matching and format heuristics
- π§ (Optionally (but recommended) Uses AI (OpenAI) to pick the best version among dupes with rationale
- π Calculates bitrate/sample-rate/bit-depth via FFmpeg
- π§Ή Trash and delete duplicate entries from Plex libraries via Plex API
- π§ͺ Supports dry-run / safe mode if you want to preview effects
- π₯οΈ Modern Web UI to dedupe one-by-one or all at once (But you want to use it in CLI mode anyway, right?)
- π§ Fully works offline (if AI is not used)
- βοΈ Full
config.jsonsupport with baked-in defaults, but you rather want to use the variable config, see below... - π³ Full Docker variable support β no file edits needed
- π Stats panel in UI: space saved, dupes removed, etc.
- π Merge extra tracks from lesser versions
- πΎ Caches audio info with SQLite so re-runs are fast
- π Uses path mapping (PATH_MAP) to resolve Docker volume mappings
- π§ Cross-library deduplication mode toggleable via config/env
PMDA uses OpenAI to determine the "best" version of an album β comparing format score, bitrate, depth, number of tracks, and presence of extra tracks. The UI even shows the rationale behind the decision. This is how you want to run it, really... don't be cheap.
- Table view
- Filter by artist or album
- One-click deduplication
- Merge and deduplicate with rationale
- Statistics on space saved, dedupes removed
Main dashboard showing total artists, albums, removed and remaining duplicates, total space saved, with live updates during scans.
Example of the analysis of two versions of an album.
Example of an album with extra tracks detected. Enabling a way to merge extra tracks to the winning edition folder.
All config can be controlled either via config.json or environment variables (in Docker).
Supported variables:
PLEX_DB_PATHβ Directory or full path to Plex DBPLEX_DB_FILEβ Plex DB filename (default:com.plexapp.plugins.library.db)PLEX_HOSTβ Base URL to Plex (e.g.http://192.168.3.2:32400)PLEX_TOKENβ Plex auth tokenSECTION_IDβ Section ID for music libraryPATH_MAPβ Map container paths to host paths (e.g."/mnt:/host/path")DUPE_ROOTβ Folder to move removed duplicatesWEBUI_PORTβ Port for the Web UI (default: 6000)SCAN_THREADSβ Parallelism level for scanningDISABLE_WEBUIβ If true, disables the web interfaceLOG_LEVELβ DEBUG / INFO / WARNING etc.OPENAI_API_KEYβ Optional key for smarter selectionOPENAI_MODELβ Model to use (gpt-4,gpt-3.5-turbo, etc.)STATE_DB_FILEβ Path for state cache (default:config_dir/state.db)CACHE_DB_FILEβ Path for FFmpeg audio info cacheFORMAT_PREFERENCEβ List of formats ordered by priorityPMDA_CONFIG_DIRβ Path to store config, state, and cache filesPMDA_DEFAULT_MODEβ Default mode to launch (serve,cli, etc.)
docker run --rm --name pmda \
-e PLEX_HOST="http://192.168.3.1:32400" \ # The full URL to your Plex server, including port
-e PLEX_TOKEN="your-real-plex-token" \ # Your Plex token (required for API access)
-e SECTION_ID="1" \ # Section ID of your music library (integer)
-e PLEX_DB_PATH="/database" \ # Path *inside the container* to the Plex DB mount
-e PLEX_DB_FILE="com.plexapp.plugins.library.db" \ # Plex database file name (default name)
-e PMDA_CONFIG_DIR="/app/config" \ # Config directory inside container (bind-mounted for persistence)
-e PMDA_DEFAULT_MODE="serve" \ # Mode to launch: 'serve', 'cli', 'dryrun', or 'dedupe'
-e LOG_LEVEL="INFO" \ # Logging level: DEBUG, INFO, WARNING, etc.
-e SCAN_THREADS="8" \ # Number of threads for faster scanning
-e OPENAI_MODEL="gpt-4.1-nano" \ # OpenAI model for enhanced matching (optional)
-e OPENAI_API_KEY="sk-..." \ # Your OpenAI API key (optional β leave empty to disable)
-e PATH_MAP='{"\/music\/matched":"\/music\/matched","\/music\/unmatched":"\/music\/unmatched","\/music\/compilations":"\/music\/compilations"}' \
# JSON mapping of Plex paths to container mounts
-e DUPE_ROOT="/dupes" \ # Directory inside container where deduped albums go
-v "/path/where/you/store/your/config:/app/config:rw" \ # Mount for config & state files
-v "/path/to/plex/database:/database:ro" \ # Mount of the Plex database folder
-v "/first/path/of/your/music/lib:/music/matched:rw" \
-v "/second/path/of/your/music/lib:/music/unmatched:rw" \
-v "/third/path/of/your/music/lib:/music/compilations:rw" \
-v "/mnt/user/MURRAY/Music/Music_dupes/Plex_dupes:/dupes:rw" \
-p 5005:5005 \ # Web UI port (only needed if using 'serve' mode)
meaning/pmda:latest # Docker image nameπ
PMDA_DEFAULT_MODEoptions:
"serve"β Web UI mode"cli"β Interactive terminal mode"dryrun"β Simulate deduplication without changes"dedupe"β Run auto-deduplication immediately
{
"PLEX_DB_FILE": "/database/com.plexapp.plugins.library.db",
"PLEX_HOST": "http://192.168.3.2:32401",
"PLEX_TOKEN": "YOUR_TOKEN_HERE",
"SECTION_ID": 1,
"PATH_MAP": {
"/music/matched": "/mnt/user/Music"
},
"DUPE_ROOT": "/mnt/user/Music/dupes",
"WEBUI_PORT": 5005,
"SCAN_THREADS": 8,
"STATE_DB_FILE": "/config/pmda_state.db",
"CACHE_DB_FILE": "/config/pmda_cache.db",
"OPENAI_API_KEY": "sk-...",
"OPENAI_MODEL": "gpt-4"
}Need help? Want to share cool use cases? Feature ideas? Bug reports?
π Join us on Discord: https://discord.gg/2jkwnNhHHR