Files
command-center/agent/managed-skills/plex-title-authority-for-relayout/SKILL.md
T

55 lines
3.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: plex-title-authority-for-relayout
description: "Read Plex's metadata cache as the clean-title authority (ziggurat schema, immutable read, no sqlite3 CLI) when naming or regrouping a media library from scripts instead of filename parsing."
---
Plex has already matched most of a library to real titles. When a script must name folders/files (Jellyfin relayout, dedup reports, library census), **read Plex's DB and let it override filename parsing** — `Alien.Directors.Cut.1979.1080p.BRrip.x265-RARBG` → `Alien`, `1917.2019.2160p…` → `1917`. Measured on dankserver's Movies section: 3,773 parts, **0** filename-shaped titles, 80% of media files matched (unmatched = extras, BDMV disc trees, TV).
## Locate and open the DB (no sqlite3 CLI needed)
```
DB="<data>/Library/Application Support/Plex Media Server/Plug-in Support/Databases/com.plexapp.plugins.library.db"
# data dir: /var/lib/plexmediaserver (host install) or the container's volume
```
```python
con = sqlite3.connect(f"file:{DB}?mode=ro&immutable=1", uri=True) # no copy, no wal, no lock
```
`immutable=1` is the whole trick: the DB was 1.5 GiB and Plex was live — no copy, no `sqlite3` binary, no risk of touching the service. Set `busy_timeout`; expect occasional inconsistency, acceptable for a read-only census.
## Schema trap: Plex renamed the tables ("ziggurat")
The old `sections` table **does not exist** — querying it throws, which reads like "wrong DB". Real names:
| table | join | key columns |
|---|---|---|
| `library_sections` | — | `id`, `name`, `section_type` (`movie`/`show`), `agent`, `scanner` |
| `section_locations` | `library_section_id` | `root_path`, `available` |
| `metadata_items` | `parent_id` → own `id` | `metadata_type`, `title`, `original_title`, `edition_title`, `year`, `indexed`, `deleted_at` |
| `media_items` | `metadata_item_id` | `container`, `video_codec`, `audio_codec`, `width`, `height` |
| `media_parts` | `media_item_id` | **`file`** (full path), `size`, `duration`, `deleted_at` |
Census section coverage first (`count(*) where file like '<root>/%'`), then dump:
```sql
select p.file, mi.title, mi.original_title, mi.edition_title, mi.year, m.video_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 '<root>/%' and p.deleted_at is null and mi.deleted_at is null
```
For a movie library the leaf *is* the film; `parent_id` is NULL, so a `left join` on parent returns the same title. `metadata_type`: 1 = movie, 2 = show (a show section riding the same root means episodes live in the MOVIES folder — worth reporting).
## Using it in a renamer
- **One film, one name:** match any copy → apply that title to the whole group. Keyed by `file` path, so the map is exact.
- **Override, don't replace:** the parser still names the unmatched (extras, disc trees, TV, unscanned). Keep it as a fallback tier.
- **Bucket on the old key, name with Plex:** group/score sidecars with the parser's words (a `.srt` matches on the year token), format the folder from Plex: `f"{title} ({year})"`.
- **Sanitize for the FS:** Plex titles carry `:` and `&` (`2001: A Space Odyssey`, `Rock & Rule`) — those must go through the same sanitizer as filename-derived names.
## Cross-check before trusting a parser
Score the parser against the cache: count titles matching `2160p|1080p|x26[45]|bluray|hevc|10bit` — Plex returned **0**. Every parser mismatch is a real bug class: year-echo pseudo-titles (`1917.2019.2160p…`), two-year titles (`2001…1968`), numeric titles eaten as track indexes (`12 Monkeys`, `007 Skyfall`, `000 Miles to Graceland`, `0 - First Dates` — keep the number unless it's 1–2 digits glued to the words with a year present, i.e. `01. Bye Bye Baby`).