74 lines
3.0 KiB
Markdown
74 lines
3.0 KiB
Markdown
---
|
|
name: plex-metadata-title-cache-read
|
|
description: "Read the live Plex library DB read-only for clean titles/file paths (no sqlite3 binary, server running), and use it as the title authority over filename parsing."
|
|
---
|
|
|
|
Plex's metadata cache already holds a *clean* title for every file it matched
|
|
(`Alien`, not `Alien.Directors.Cut.1979.1080p.BRrip.x265-RARBG`). Read it instead
|
|
of parsing filenames, and name folders / group duplicates from it.
|
|
|
|
## Where it lives
|
|
|
|
Host package: data dir `/var/lib/plexmediaserver`, DB at
|
|
|
|
```
|
|
/var/lib/plexmediaserver/Library/Application Support/Plex Media Server/Plug-in Support/Databases/com.plexapp.plugins.library.db
|
|
```
|
|
|
|
(`Resources/com.plexapp.plugins.library.db` under `/usr/lib/plexmediaserver` is a
|
|
blank template — not the library.) The dir is `dank:dank`, so no sudo needed.
|
|
Containers: check `podman ps` first; on Dankserver Plex runs as a host process.
|
|
|
|
## Read it while Plex is RUNNING
|
|
|
|
No `sqlite3` binary on the fleet hosts — use `python3 -c` with `sqlite3`. Open
|
|
immutable read-only so no WAL/SHM is touched and no lock is taken:
|
|
|
|
```python
|
|
con = sqlite3.connect(f"file:{db}?mode=ro&immutable=1", uri=True)
|
|
```
|
|
|
|
A long local `find /` can hit the tool's own deadline; scope `find` with
|
|
`-maxdepth` or use the path above directly.
|
|
|
|
## Schema traps
|
|
|
|
* `library_sections` → column is **`section_type`**, not `type`. Movies = 1,
|
|
TV = 2, Music = 8.
|
|
* **`metadata_titles` does not exist** (empty name in the table list). Titles are
|
|
on `metadata_items`: `title`, `original_title`, `edition_title`, `year`,
|
|
`metadata_type`, `parent_id`, `deleted_at`.
|
|
* File path is `media_parts.file`; join
|
|
`media_parts → media_items (id = media_item_id) → metadata_items
|
|
(id = metadata_item_id)`, and `LEFT JOIN metadata_items par ON par.id =
|
|
mi.parent_id` for the show/movie parent.
|
|
* **Movies use the LEAF title** (`mi.title`); the parent row is empty. TV uses
|
|
the parent. Filter `p.deleted_at IS NULL AND mi.deleted_at IS NULL` or deleted
|
|
scans pollute the census.
|
|
|
|
```sql
|
|
select p.file, mi.title, mi.original_title, mi.edition_title, mi.year, m.container,
|
|
m.video_codec, m.audio_codec, m.width, m.height
|
|
from media_parts p
|
|
join media_items m on m.id = p.media_item_id
|
|
join metadata_items mi on mi.id = m.metadata_item_id
|
|
where p.file like '/mnt/entropy/Movies/%'
|
|
and p.deleted_at is null and mi.deleted_at is null
|
|
```
|
|
|
|
`media_items` also carries codec/resolution already — often no ffprobe pass is
|
|
needed for matched files.
|
|
|
|
## Use it as the title authority
|
|
|
|
Reject a Plex title only if it looks like a filename
|
|
(`2160p|1080p|x26[45]|hevc|bluray|web-?dl`) — a healthy cache scores **0** junk
|
|
titles. Coverage expectation measured on `/mnt/entropy/Movies`: 3,754 of 4,698
|
|
media files matched; the unmatched remainder is extras, BDMV disc trees, TV
|
|
episodes misfiled into Movies, and collection box sets — exactly the classes a
|
|
filename parser must still handle.
|
|
|
|
Propagate one match to every copy of a film (a group of versions) so sidecars,
|
|
`.nfo` and media files share one stem; fall back to the filename parser only
|
|
when no row exists.
|