Files

454 lines
12 KiB
Markdown
Raw Permalink Normal View History

2026-04-15 09:27:29 +12:00
# Emby Cover App Maintenance Guide
## Purpose
This app is a FastAPI-based internal tool for generating and applying custom Emby artwork.
It currently supports:
- Searching Emby movies and series
- Generating a wide `Thumb` image
- Optionally generating a matching tall `Primary` poster
- Using Emby-known logos and backdrops already attached to the item
2026-04-15 21:25:03 +12:00
- Reframing any Emby or imported image in a premium artwork editor
- Exporting editor output as `Primary`, `Thumb`, or `Backdrop`
- Searching provider-backed external image sources and importing remote images into cache
2026-04-15 09:27:29 +12:00
- Applying generated artwork back to Emby
- Adding optional studio badges and a red `NEW EPISODES` series tag
The codebase is intentionally small and centralized:
- Backend and image pipeline: `app.py`
- Frontend UI, CSS, and browser logic: `templates/index.html`
- Static studio logos: `static/studios/`
- Generated cache files: `cache/`
## Runtime Model
The app is mostly a single-server process with one HTML page.
- FastAPI serves the UI and JSON/image endpoints
- `httpx.AsyncClient` is reused for Emby API calls
- Pillow is used for all image composition
- The browser page is a single template with inline CSS and JS
There is no frontend build system and no database.
## Configuration
Environment variables:
- `EMBY_URL`
- `EMBY_API_KEY`
Defaults are currently hard-coded in `app.py`. That is convenient for local usage but should be treated carefully if this app is ever shared more broadly.
## Key Files
### `app.py`
This file contains:
- App startup/shutdown
- Emby API helpers
- Image loading and upload helpers
- Font selection helpers
- The entire artwork render pipeline
- Search/image/apply API routes
This is the core of the system. Most behavior changes happen here.
### `templates/index.html`
This file contains:
- Full UI markup
- Full CSS
- Full browser-side state management
- Search/pagination logic
- Asset picker logic for Emby-known logos and backdrops
- Generate/apply button behavior
There is no JS framework. Everything is plain DOM code.
## How The App Works
### 1. Search flow
Browser:
- User types into the search box
- JS debounces and cancels stale requests
- `GET /api/search` is called with `q`, `start`, and `limit`
- Results are rendered in the left sidebar with lightweight poster images
Server:
- `/api/search` queries Emby `/Items`
- Returns paginated search results with metadata needed by the UI
- Search result posters are served via `/api/poster/{item_id}` using reduced dimensions for speed
### 2. Item image discovery flow
When an item is selected:
- The browser immediately starts a preview generate call
- In parallel, it calls `GET /api/images/{item_id}`
That endpoint returns the Emby-known images already attached to the item:
- `logos`
- `backdrops`
- `primaries`
The UI uses that to populate:
- Logo asset picker
- Backdrop asset picker
- Accurate backdrop counts
Important:
- This is Phase 1 of image browsing only
- It only exposes images Emby already knows for the item
- It does not yet perform remote/provider image lookup
### 3. Generate flow
Browser:
- `POST /api/generate`
- Sends the selected options:
- background mode
- selected backdrop index
- selected logo index
- logo position
- logo size
- darkness
- studio badge
- series tag
- whether to also generate a matching primary poster
Server:
- Pulls `Primary`, selected `Logo`, and selected `Backdrop` from Emby
- Builds the wide thumb with `generate_thumbnail(...)`
- Writes the thumb to cache
- Optionally builds and caches a tall primary via `generate_primary_cover(...)`
- Returns the thumb image stream to the browser
### 4. Apply flow
Browser:
- `POST /api/apply`
- Sends the same effective rendering settings as generate
Server:
- Rebuilds the same cache key
- Reads the cached thumb if present
- If missing, regenerates on demand to avoid cache-miss 400 failures
- Uploads the thumb to Emby as `Thumb`
- If `generate_primary` is enabled, uploads the cached or regenerated tall poster as `Primary`
Important:
- `Apply` must remain tolerant of cache misses
- Do not reintroduce a hard dependency on a pre-existing cache file only
2026-04-15 21:25:03 +12:00
### 5. Artwork editor flow
Browser:
- The editor appears after an Emby item is selected
- Editor state is kept separate from the existing thumb-generator `state` object
- The editor has three panels:
- source image panel
- fixed-ratio editor canvas
- export panel
- Users can choose Emby `Primary`, selected Emby `Backdrop`, Emby `Thumb`, or an imported remote image
- Users can target `poster`, `thumb`, or `backdrop`
- Users can drag, zoom, reset, and switch `cover` / `contain`
- Landscape-to-portrait conversion supports blurred, mirrored, or solid background fill
- `POST /api/artwork/export` generates a live preview and returns cache headers
- `POST /api/artwork/apply` uploads the cached or regenerated export to the matching Emby image type
Server:
- Editor rendering is separate from `generate_thumbnail(...)`
- `render_artwork_editor_image(...)` handles fixed-frame reframing and high-quality JPEG output
- `fit_image_positioned(...)` is the shared crop/contain positioning helper
- `build_editor_fill(...)` handles blurred, mirrored, and solid background fill
- `resolve_editor_source_bytes(...)` loads either Emby source bytes or cached remote imports
- Editor exports are cached as `<cache_key>.editor.jpg`
- Remote imports are cached under `cache/imports/`
Remote image search:
- Provider lookup is intentionally abstracted behind `ImageSearchProvider`
- `GET /api/artwork/providers` exposes available providers to the UI
- `GET /api/artwork/search` calls the selected provider
- `POST /api/artwork/import` downloads, validates, size-limits, and caches a selected remote image
2026-04-20 23:16:53 +12:00
- Current providers are Wikimedia Commons, optional TMDB, and optional Google Custom Search
- TMDB is enabled with `TMDB_BEARER_TOKEN` or `TMDB_API_KEY`
- Google Custom Search is enabled with `GOOGLE_CUSTOM_SEARCH_API_KEY` and `GOOGLE_CUSTOM_SEARCH_ENGINE_ID`
- Add future providers by implementing `ImageSearchProvider` and registering it in `IMAGE_SEARCH_PROVIDERS`
2026-04-15 21:25:03 +12:00
2026-04-15 09:27:29 +12:00
## Rendering Pipeline
The central renderer is `generate_thumbnail(...)`.
It is used for:
- Wide thumbs
- Tall primary covers, through `generate_primary_cover(...)`
Pipeline stages:
1. Resolve background
2. Crop/fit to target aspect ratio
3. Apply darkening and vignette
4. Resolve logo or fallback title text
5. Position logo/text based on selected alignment
6. Add studio logo overlay
7. Add optional `NEW EPISODES` tag
8. Encode as PNG
### Layout assumptions
The renderer now contains separate behavior for tall layouts.
This matters because:
- Wide thumbnails and tall posters cannot share the same padding rules
- Tall primary posters need different bottom spacing and logo height caps
When changing artwork layout, always test both:
- `800x450` thumb
- `1000x1500` primary
## Caching
Cache files live in `cache/`.
Current cache helpers:
- Thumb cache: `<cache_key>.png`
- Primary cache: `<cache_key>.primary.png`
2026-04-15 21:25:03 +12:00
- Artwork editor export cache: `<cache_key>.editor.jpg`
- Imported remote image cache: `cache/imports/<sha256>.img`
2026-04-15 09:27:29 +12:00
The cache key is derived from rendering inputs such as:
- item id
- backdrop index
- logo index
- colors
- logo position
- scale
- darkness
- studio settings
- series tag state
Important:
- If you add any new visual option, add it to `build_cache_key(...)`
- If you forget, the app will reuse stale cached artwork from a different configuration
## Image Selection Rules
### Logo images
Logo selection is index-based.
Notes:
- The app should always treat invalid or non-image logo responses as optional
- `emby_get_image_optional(...)` already guards against non-image content-types
- `generate_thumbnail(...)` also guards against invalid logo bytes and falls back to text
Do not make logo loading a hard-fail path.
### Backdrops
Backdrop selection is index-based and uses the asset picker.
If a backdrop is unavailable:
- The render should still succeed
- The fallback is blurred poster or solid background
## Upload Rules
Uploading back to Emby is done through `emby_upload_image(...)`.
Current behavior:
- Upload source image is normalized to RGB JPEG
- `Thumb` and `Primary` uploads both use that path
Be careful when changing upload behavior:
- Emby may accept source formats differently than display formats
- The preview proxy can preserve PNG content-type
- The upload path currently standardizes to JPEG intentionally
## Frontend State
The browser keeps a single `state` object in `templates/index.html`.
Key fields:
- `item`
- `backdropIndex`
- `logoIndex`
- `imageInfo`
- `searchQuery`
- `searchStart`
- `searchLimit`
- `searchTotal`
- `generated`
When adding a new visual option:
1. Add UI control
2. Add local JS state if needed
3. Include it in `POST /api/generate`
4. Include it in `POST /api/apply`
5. Add it to `build_cache_key(...)`
6. Apply it in the render pipeline
If any of those steps are skipped, behavior will drift.
2026-04-15 21:25:03 +12:00
Artwork editor state is intentionally separate:
- `editorState.sourceKind`
- `editorState.sourceType`
- `editorState.sourceIndex`
- `editorState.importId`
- `editorState.cacheKey`
- transform controls from the editor sliders
Keep editor payloads aligned across:
- `/api/artwork/export`
- `/api/artwork/apply`
- `build_editor_cache_key(...)`
- `render_artwork_editor_image(...)`
2026-04-15 09:27:29 +12:00
## Laptop UX Constraints
The app has custom responsive rules for laptop-sized screens.
Areas that have already needed tuning:
- Preview area height
- Controls bar spacing
- Sidebar width
- Results panel layout
When changing layout:
- Test desktop wide view
- Test laptop-height view
- Test narrow desktop widths before mobile breakpoints
The controls container is particularly sensitive because the preview and controls compete vertically.
## Known Fragile Areas
### 1. Cache key drift
Symptoms:
- `Apply` uploads unexpected artwork
- Generate/apply feel out of sync
Cause:
- A new option was added to rendering but not to `build_cache_key(...)`
### 2. Emby returns non-image content for optional assets
Symptoms:
- PIL `UnidentifiedImageError`
Cause:
- Emby returned HTML, JSON, or another unexpected response for an optional asset
Current guardrails already exist. Preserve them.
### 3. Tall poster layout quality
Symptoms:
- Tall primary poster logo sits too low
- Title/logo scale looks like a stretched thumb layout
Cause:
- Using wide-layout spacing assumptions on primary posters
Always treat primary layout as distinct.
### 4. Search feels slow
Symptoms:
- Listing poster thumbnails load slowly
- Search UI feels laggy
Current mitigations:
- Paginated search
- Smaller poster fetches
- Shared HTTP client
- Stale request cancellation
Avoid switching result posters back to full-size originals.
## Safe Maintenance Workflow
When changing this app:
1. Read both `app.py` and the relevant section of `templates/index.html`
2. Keep generate/apply payloads aligned
3. Keep cache key aligned with render options
4. Test thumb and primary rendering
5. Run:
```powershell
python -m py_compile app.py
```
6. Manually verify:
- search
- item select
- generate
- apply
- optional primary generation
- logo/backdrop asset switching
## Recommended Future Enhancements
Likely next improvements:
2026-04-15 21:25:03 +12:00
- Additional image search providers from Emby metadata sources
2026-04-15 09:27:29 +12:00
- Better poster-specific typography/layout rules
- Better user-visible error reporting for partial apply failures
- Cache cleanup strategy
## Rules For Future Agents
- Do not split backend logic into multiple files unless there is a strong reason. This app is currently maintainable specifically because it is centralized.
- Do not introduce a frontend framework casually. The current plain JS approach is appropriate for the apps size.
- Do not remove the cache-miss regeneration fallback from `apply`.
- Do not assume Emby optional image endpoints always return valid images.
- Do not change wide-thumb behavior without checking tall-primary behavior too.
- If adding a new render option, update both generate and apply paths and the cache key in the same change.