Ingest and self-host company/network logos
CI / lint (push) Canceled after 0s
CI / typecheck (push) Canceled after 0s
CI / unit-tests (push) Canceled after 0s
CI / integration-tests (push) Canceled after 0s
CI / container-build (push) Canceled after 0s
CI / dependency-audit (push) Canceled after 0s

CompanyBriefOut.logo_path and CompanyDetailOut.logo_path have existed
in the API schemas since Core 0.1's first pass, but nothing ever
actually populated them -- TMDb's production_companies/networks
entries carry a logo_path field that the normalizer simply never read,
so every response has silently returned null there.

- NormalizedCompanyRef now captures logo_path from both movie
  production_companies and series networks/production_companies.
- normalizer.company_logo_artwork() builds the single-image artwork
  entry for it (a company has exactly one logo ingested, so it's
  always is_primary) -- centralized there rather than duplicated in
  both ingestion modules, matching how movie/series artwork URLs are
  already built in one place.
- movie_ingestion.py and series_ingestion.py sync it via the same
  sync_artwork() the rest of section 10's self-hosting already uses,
  so it gets the same re-ingestion-safe upsert behavior for free.
- tasks.py cascades a download_artwork_task per referenced company
  after movie/series ingestion, same fan-out pattern as
  movie/series/season/person artwork.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
NixReaper
2026-09-05 07:46:30 -05:00
co-authored by Claude Sonnet 5
parent 69ce41d54f
commit c03a75989d
8 changed files with 133 additions and 8 deletions
+24 -1
View File
@@ -19,6 +19,7 @@ from app.models.enums import IngestionJobStatus
from app.models.provider import IngestionJob
from app.providers.base import MetadataProvider
from app.providers.tmdb import TMDbProvider
from app.repositories.movie_repository import get_movie_companies
from app.repositories.polymorphic import get_primary_artwork_ids
from app.repositories.provider_repository import (
create_ingestion_job,
@@ -28,7 +29,7 @@ from app.repositories.provider_repository import (
mark_job_running,
mark_job_succeeded,
)
from app.repositories.tv_repository import get_seasons_for_series
from app.repositories.tv_repository import get_seasons_for_series, get_series_companies
from app.services.ingestion.movie_ingestion import ingest_movie
from app.services.ingestion.person_ingestion import ingest_person
from app.services.ingestion.series_ingestion import ingest_season, ingest_series
@@ -185,3 +186,25 @@ async def get_primary_artwork_for_download(
async with session_scope() as session:
return await get_primary_artwork_ids(session, entity_type, entity_ids)
async def get_movie_company_ids(movie_id: uuid.UUID) -> list[uuid.UUID]:
"""Companies (production companies) a just-ingested movie references,
for cascading their logo into a download too — see
app/workers/tasks.py:_cascade_artwork_downloads. Shared across many
movies, so this fires on every movie referencing a given company, but
download_artwork is idempotent (skips once local_path is set)."""
async with session_scope() as session:
companies = await get_movie_companies(session, movie_id)
return [company.id for company, _role in companies]
async def get_series_company_ids(series_id: uuid.UUID) -> list[uuid.UUID]:
"""Same as get_movie_company_ids, but for a series' companies *and*
networks (get_series_companies with no company_type filter returns
both)."""
async with session_scope() as session:
companies = await get_series_companies(session, series_id)
return [company.id for company, _role in companies]