Renaming a media library from Plex's own database
Every media library collects years of inconsistent filenames: release-group tags, odd separators, missing years, episodes numbered three different ways. Plex copes with most of it, but the files on disk stay a mess, and anything else that touches the library (backups, other players, me in a shell) has to cope too.
The usual answer is a renaming tool that guesses what each file is from its name and looks it up online. That felt backwards. Plex has already matched every file to a canonical title, year, season and episode. The answer is sitting in its database.
The approach
Plex keeps its library in a SQLite database. The script reads it directly, joins each media file to the metadata Plex matched it to, and builds a canonical name from that:
- Movies:
Title (Year)/Title (Year).ext - TV:
Show (Year)/Season 01/Show (Year) - S01E02 - Episode Title.ext
Sidecar files that share the media file’s base name (subtitles and the like) are renamed along with it.
It has three ways to run:
- Dry run prints every planned rename and changes nothing. This is the default, and the mode I use most.
- Interactive asks before each rename.
- Batch applies everything.
An optional --update-db flag writes the new paths back into Plex’s database, so Plex doesn’t see the
renamed files as deletions followed by brand-new items and lose watch history along the way.
Two bugs worth writing down
Reading a database another process is writing
Plex keeps its database open the whole time it runs, in SQLite’s WAL (write-ahead log) mode. Opening the live file from a second process worked most of the time and then occasionally didn’t: lock conflicts, or reads that didn’t line up with what Plex had just written.
The fix was to stop reading the live file. The script copies the database (and its WAL) into a
temporary directory and opens the copy read-only with SQLite’s immutable=1 URI parameter:
uri = f"file:{snapshot_path}?mode=ro&immutable=1"
conn = sqlite3.connect(uri, uri=True)
immutable=1 tells SQLite the file can’t change underneath it, so it skips locking entirely. That’s
only safe because it’s a private snapshot, which is exactly the point: the script gets a consistent
view and Plex never notices it was there.
Root-owned files breaking Plex
--update-db has to write to the real database, which means running with enough privilege to touch
Plex’s files. The first version did that as root, and SQLite helpfully created its -wal and -shm
sidecar files as root too. Plex, running as its own user, then couldn’t open its own database.
The fix is to record the owner of the database file before writing and restore that ownership on the database and its sidecar files afterwards. More generally: any tool that runs as root and writes into a service’s data directory should hand files back to that service’s user before it exits.
What I’d tell someone doing the same
- Stop Plex before using
--update-db. Reading from a snapshot is safe while it runs; writing to its database is not. - Back up the database first anyway.
- Run the dry run, read it, and only then let it rename anything.