Skip to content

Hydrate TPDB scene entities and expand Plex metadata mapping fidelity - #1

Merged
adeze merged 3 commits into
mainfrom
copilot/enhance-plex-metadata-proxy
Jun 4, 2026
Merged

Hydrate TPDB scene entities and expand Plex metadata mapping fidelity#1
adeze merged 3 commits into
mainfrom
copilot/enhance-plex-metadata-proxy

Conversation

Copilot AI commented Jun 4, 2026

Copy link
Copy Markdown

This PR makes TPDB-backed metadata responses more complete by hydrating sparse scene payloads and broadening Plex field mapping. It improves performer imagery, studio normalization, image fallback quality, and adds director/collection support while preserving existing response shape when enrichment data is absent.

  • Performer hydration (with lightweight cache)

    • Added per-service in-memory lookup caches for /performers/{id|slug}.
    • Hydrates performer details only when inline performer entries are missing image/poster/thumb data.
    • Merges hydrated and inline payloads with inline scene fields taking precedence.
  • Studio/site hydration (with lightweight cache)

    • Added cached /sites/{id|slug} enrichment in match + metadata flows.
    • Normalizes incomplete embedded site objects using hydrated site data.
    • Exposes merged site and preserves site_hydrated for mapper compatibility.
  • Defensive multi-image fallback selection

    • Centralized image extraction utilities for heterogeneous TPDB payload shapes (string/dict/list/nested).
    • Poster/thumb preference now prioritizes poster-like fields, then image-like fallbacks.
    • Art/background preference now prioritizes background/fanart-like fields, then safe fallbacks.
  • Director mapping

    • Maps director/directors payload variants into Plex Director entries.
  • Collection/series/franchise mapping

    • Maps collection(s), series, and franchise(s) variants into Plex Collection entries.
    • Deduplicates collection names and handles mixed payload shapes defensively.
# New hydration + mapping flow (simplified)
scene = client.get_scene(rating_key)
scene = _hydrate_scene(scene)  # cached performer/site enrichment when sparse
metadata = map_scene_to_metadata(scene)  # improved image fallbacks + Director/Collection mapping
Original prompt

Enhance the repository adeze/plex-tpdb-scanner to make the TPDB-backed Plex metadata proxy more complete.

Implement the following improvements:

  1. Performer image hydration
  • Enrich performer data beyond what is embedded in the scene payload.
  • If a performer has an id or slug and is missing image/poster/thumb data in the scene payload, fetch /performers/{id} via the existing TPDB client and use the returned details to populate richer role metadata.
  • Add lightweight caching to avoid excessive repeated performer lookups.
  1. Multiple image fallback logic
  • Improve scene image selection so the proxy can choose the best available poster/thumb and background/art from available TPDB fields.
  • Prefer poster-like images first, then image, then any sensible fallback the TPDB payload provides.
  • Keep the implementation defensive and compatible with existing payload shapes.
  1. Director support
  • If director/directors data is available in the scene payload (or via related hydrated data already available), map it into Plex metadata using an appropriate Director structure.
  1. Studio/site hydration
  • Enrich studio/site metadata using the existing site endpoint when needed.
  • Normalize the studio/site name using hydrated site data if the embedded scene site object is incomplete.
  • If useful and safe, make richer site fields available to the mapper in a backwards-compatible way.
  • Add lightweight caching to avoid repeated site lookups.
  1. Collections/series support
  • If the scene payload contains collection/series/franchise-like metadata, map it into Plex metadata using a Plex-compatible Collection structure.
  • Be defensive about field names and payload shape.

Implementation guidance:

  • Repository: adeze/plex-tpdb-scanner
  • Keep the current Python stack and reuse the existing requests-based TPDB client.
  • Prefer minimal, maintainable changes over a large refactor.
  • Update the TPDB client / services / mapper as needed.
  • Preserve current behavior when enriched fields are unavailable.
  • Avoid breaking existing endpoints and response structure.
  • Add comments only where they materially help.

Files likely involved:

  • metadata_tool/api.py
  • provider/services/metadata_service.py
  • provider/services/match_service.py
  • provider/mappers/tpdb_to_plex.py

Expected outcome:

  • Match and metadata responses should include richer performer thumbs, better image selection, director mapping, normalized studio/site metadata, and collection/series mapping when TPDB provides those fields.
  • Open a pull request with the implementation.

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: are there any enhancements we can make to request more details from thepdb api https://api.theporndb.net/docs that would make this metadata proxy more complete? actress image etc?

Assistant: Yes — probably. The biggest gap in your proxy right now is not the basic TPDB fetch itself, but that you only map a fairly small subset of the scene payload into Plex metadata. Your client already calls GET /scenes/{id} and also has helper methods for GET /performers/{id} and GET /sites/{id}, but the mapper only emits title, summary, poster, background, studio, date, duration, performers, and tags. (api.theporndb.net)

From the repo, the current metadata mapping is here:

def map_scene_to_metadata(scene: dict[str, Any], media_type: int = 1) -> dict[str, Any]:
    slug = scene.get("slug", scene.get("id", ""))
    title = scene.get("title", "")
    date = scene.get("date", "")
    year = int(date.split("-")[0]) if date and "-" in date else None
    summary = scene.get("description") or ""

    poster = scene.get("poster") or scene.get("image") or ""
    ...
    background = scene.get("background") or ""
    ...
    site = scene.get("site") or {}
    studio = site.get("name", "") if isinstance(site, dict) else ""
    ...
    performers = scene.get("performers") or []
    ...
    tags = scene.get("tags") or []

and the client already exposes richer lookup points:

def get_scene(self, scene_id):
    result = self._request('GET', f'/scenes/{scene_id}')
    ...

def get_performer(self, performer_id):
    result = self._request('GET', f'/performers/{performer_id}')
    ...

def get_site(self, site_id):
    result = self._request('GET', f'/sites/{site_id}')
    ...

So the enhancement path I’d recommend is:

1. Enrich performer data

Yes, actress/performer images are the most obvious win. You already map performer thumbs when they are present inline on the scene response, but you do not curr...

This pull request was created from Copilot chat.

Copilot AI changed the title [WIP] Enhance TPDB-backed Plex metadata proxy with image hydration and director support Hydrate TPDB scene entities and expand Plex metadata mapping fidelity Jun 4, 2026
Copilot AI requested a review from adeze June 4, 2026 06:30
Copilot finished work on behalf of adeze June 4, 2026 06:30
@adeze
adeze marked this pull request as ready for review June 4, 2026 06:45
Copilot AI review requested due to automatic review settings June 4, 2026 06:45
@adeze
adeze merged commit 9898426 into main Jun 4, 2026
1 check passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances the TPDB-backed Plex metadata provider by enriching (hydrating) sparse scene payloads with cached performer/site lookups and by expanding TPDB→Plex field mapping (notably image fallbacks, Director, and Collection mapping) to produce more complete metadata responses.

Changes:

  • Added lightweight in-memory caches and scene hydration logic for performers and sites in both metadata and match services.
  • Centralized defensive image extraction and improved poster/art selection fallbacks; added Director and Collection/series/franchise mapping.
  • Added unit tests covering image fallback behavior, director/collection mapping, and hydration caching behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
provider/services/metadata_service.py Adds performer/site hydration with in-memory caching prior to mapping full metadata.
provider/services/match_service.py Adds hydration (and caching) during match search flow before mapping match results.
provider/mappers/tpdb_to_plex.py Implements robust image extraction + poster/art selection and maps Director/Collection fields.
tests/test_tpdb_enrichment.py Adds unit tests validating new mapping and hydration/caching behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +45 to +55
def _get_cached_performer(self, performer_identifier: str) -> Optional[dict]:
"""Get performer details with lightweight in-memory cache."""
if not performer_identifier:
return None
if performer_identifier in self._performer_cache:
self._performer_cache.move_to_end(performer_identifier)
else:
if len(self._performer_cache) >= self._CACHE_LIMIT:
self._performer_cache.popitem(last=False)
self._performer_cache[performer_identifier] = self.client.get_performer(performer_identifier)
return self._performer_cache[performer_identifier]
Comment on lines +57 to +67
def _get_cached_site(self, site_identifier: str) -> Optional[dict]:
"""Get site details with lightweight in-memory cache."""
if not site_identifier:
return None
if site_identifier in self._site_cache:
self._site_cache.move_to_end(site_identifier)
else:
if len(self._site_cache) >= self._CACHE_LIMIT:
self._site_cache.popitem(last=False)
self._site_cache[site_identifier] = self.client.get_site(site_identifier)
return self._site_cache[site_identifier]
Comment on lines +45 to +55
def _get_cached_performer(self, performer_identifier: str) -> Optional[dict]:
"""Get performer details with lightweight in-memory cache."""
if not performer_identifier:
return None
if performer_identifier in self._performer_cache:
self._performer_cache.move_to_end(performer_identifier)
else:
if len(self._performer_cache) >= self._CACHE_LIMIT:
self._performer_cache.popitem(last=False)
self._performer_cache[performer_identifier] = self.client.get_performer(performer_identifier)
return self._performer_cache[performer_identifier]
Comment on lines +57 to +67
def _get_cached_site(self, site_identifier: str) -> Optional[dict]:
"""Get site details with lightweight in-memory cache."""
if not site_identifier:
return None
if site_identifier in self._site_cache:
self._site_cache.move_to_end(site_identifier)
else:
if len(self._site_cache) >= self._CACHE_LIMIT:
self._site_cache.popitem(last=False)
self._site_cache[site_identifier] = self.client.get_site(site_identifier)
return self._site_cache[site_identifier]
Comment on lines 131 to 134
for i, scene in enumerate(results):
try:
scene = self._hydrate_scene(scene)
# Decrease score for each subsequent result
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants