-- ============================================================================ -- mediabox — Plex library path remap: Windows UNC -> Linux mount paths -- -- RUN WITH "Plex SQLite", NOT stock sqlite3. -- Plex ships its own SQLite build with specific compile options. -- Windows: "C:\Program Files\Plex\Plex Media Server\Plex SQLite.exe" -- Linux: /usr/lib/plexmediaserver/Plex\ SQLite -- Stock sqlite3 may refuse to open it, or worse, open it and corrupt it. -- -- RUN ON A COPY. Never against a database a server has open. Use either a -- copy taken from a cleanly STOPPED server, or one of Plex's own dated -- backups (com.plexapp.plugins.library.db-YYYY-MM-DD), which are already -- consistent snapshots. -- -- --------------------------------------------------------------------------- -- VERIFIED AGAINST THE LIVE LIBRARY 2026-07-27 -- -- Every text column in all 82 tables was scanned for the string 'korval'. -- Six columns matched. Only FOUR of them are paths: -- -- media_parts.file 80,126 rows backslash / UNC form -- section_locations.root_path 11 rows backslash / UNC form -- media_streams.url 14,837 rows file:// form, %20-encoded -- metadata_items.guid 9 rows file:// form, %20-encoded -- -- TWO COLUMNS MATCH THE WORD BUT ARE NOT PATHS AND MUST NEVER BE TOUCHED: -- -- taggings.text 1 row — "Dr. Korval", a credited person -- metadata_items.summary 4 rows — Liaden Universe book blurbs that -- mention "Clan Korval" -- -- This is why every statement below is anchored to a PATH PREFIX -- ('\\korval\' or 'file://korval/') and never to the bare word. A naive -- REPLACE(col,'korval',...) across matching columns would silently corrupt -- four book summaries and a cast credit. -- --------------------------------------------------------------------------- -- -- MOUNT CONTRACT — this SQL assumes each NAS share is mounted at -- /mnt/nas/, capitalisation and spaces preserved: -- -- //10.0.1.254/media -> /mnt/nas/media -- //10.0.1.254/Radio Shows -> /mnt/nas/Radio Shows -- //10.0.1.254/Education Videos -> /mnt/nas/Education Videos -- //10.0.1.254/Health -> /mnt/nas/Health -- //10.0.1.254/Home Movies -> /mnt/nas/Home Movies -- //10.0.1.254/Pictures -> /mnt/nas/Pictures -- -- Keeping the share names verbatim is what reduces this to one uniform -- prefix substitution per format instead of eleven special cases. The same -- paths must be bind-mounted into the container at the same location, so the -- database and the container agree with no further translation. -- ============================================================================ PRAGMA foreign_keys = OFF; BEGIN TRANSACTION; -- --------------------------------------------------------------------------- -- 1. Drop the two vestigial library roots. -- -- '\\korval\Audio Books' and '\\korval\Music Organized' are configured as -- library roots but contain ZERO files — all audio actually lives under -- \\korval\media\audiobooks and \\korval\media\music. Verified 2026-07-27: -- 0 files / 0 bytes under both. -- -- The NOT EXISTS guard makes this a no-op if that ever stops being true, so -- this statement can never orphan a media_parts row. -- --------------------------------------------------------------------------- DELETE FROM section_locations WHERE root_path IN ('\\korval\Audio Books', '\\korval\Music Organized') AND NOT EXISTS ( SELECT 1 FROM media_parts mp WHERE mp.file LIKE section_locations.root_path || '\%' ); -- --------------------------------------------------------------------------- -- 2. Backslash / UNC form. -- -- \\korval\media\movies\Film (2019)\Film.mkv -- -> /mnt/nas/media/movies/Film (2019)/Film.mkv -- -- Order matters: swap the '\\korval\' prefix FIRST, then convert the -- remaining separators. Doing it the other way round destroys the prefix -- before it can be matched. -- -- Spaces stay literal in this form — fstab needs \040 escaping, the database -- does not. -- --------------------------------------------------------------------------- UPDATE section_locations SET root_path = REPLACE(REPLACE(root_path, '\\korval\', '/mnt/nas/'), '\', '/') WHERE root_path LIKE '\\korval\%'; UPDATE media_parts SET file = REPLACE(REPLACE(file, '\\korval\', '/mnt/nas/'), '\', '/') WHERE file LIKE '\\korval\%'; -- --------------------------------------------------------------------------- -- 3. file:// URL form. -- -- file://korval/media/tvshows/Person%20of%20Interest/... -- -> file:///mnt/nas/media/tvshows/Person%20of%20Interest/... -- -- Note the THIRD slash: 'file://korval/' is a host-qualified URL; a local -- path is 'file:///'. Separators are already forward slashes here, so no -- second REPLACE. -- -- %20 IS LEFT ALONE ON PURPOSE. Plex URL-decodes these, so decoding them -- here would produce paths with literal '%20' in them and break every -- subtitle and stream reference that has a space in its name — which, given -- share names like "Radio Shows" and "Home Movies", is most of them. -- --------------------------------------------------------------------------- UPDATE media_streams SET url = REPLACE(url, 'file://korval/', 'file:///mnt/nas/') WHERE url LIKE 'file://korval/%'; UPDATE metadata_items SET guid = REPLACE(guid, 'file://korval/', 'file:///mnt/nas/') WHERE guid LIKE 'file://korval/%'; COMMIT; PRAGMA foreign_keys = ON; -- Reclaim the space freed by the rewrite and leave the file tidy for its -- first open by the new server. Safe, but slow on a 513 MB database — expect -- a minute or two. VACUUM;