12 KiB
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
Thumbimage - Optionally generating a matching tall
Primaryposter - Using Emby-known logos and backdrops already attached to the item
- Reframing any Emby or imported image in a premium artwork editor
- Exporting editor output as
Primary,Thumb, orBackdrop - Searching provider-backed external image sources and importing remote images into cache
- Applying generated artwork back to Emby
- Adding optional studio badges and a red
NEW EPISODESseries 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.AsyncClientis 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_URLEMBY_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/searchis called withq,start, andlimit- Results are rendered in the left sidebar with lightweight poster images
Server:
/api/searchqueries 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:
logosbackdropsprimaries
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, selectedLogo, and selectedBackdropfrom 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_primaryis enabled, uploads the cached or regenerated tall poster asPrimary
Important:
Applymust remain tolerant of cache misses- Do not reintroduce a hard dependency on a pre-existing cache file only
5. Artwork editor flow
Browser:
- The editor appears after an Emby item is selected
- Editor state is kept separate from the existing thumb-generator
stateobject - The editor has three panels:
- source image panel
- fixed-ratio editor canvas
- export panel
- Users can choose Emby
Primary, selected EmbyBackdrop, EmbyThumb, or an imported remote image - Users can target
poster,thumb, orbackdrop - Users can drag, zoom, reset, and switch
cover/contain - Landscape-to-portrait conversion supports blurred, mirrored, or solid background fill
POST /api/artwork/exportgenerates a live preview and returns cache headersPOST /api/artwork/applyuploads 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 outputfit_image_positioned(...)is the shared crop/contain positioning helperbuild_editor_fill(...)handles blurred, mirrored, and solid background fillresolve_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/providersexposes available providers to the UIGET /api/artwork/searchcalls the selected providerPOST /api/artwork/importdownloads, validates, size-limits, and caches a selected remote image- Current providers are Wikimedia Commons, optional TMDB, and optional Google Custom Search
- TMDB is enabled with
TMDB_BEARER_TOKENorTMDB_API_KEY - Google Custom Search is enabled with
GOOGLE_CUSTOM_SEARCH_API_KEYandGOOGLE_CUSTOM_SEARCH_ENGINE_ID - Add future providers by implementing
ImageSearchProviderand registering it inIMAGE_SEARCH_PROVIDERS
Rendering Pipeline
The central renderer is generate_thumbnail(...).
It is used for:
- Wide thumbs
- Tall primary covers, through
generate_primary_cover(...)
Pipeline stages:
- Resolve background
- Crop/fit to target aspect ratio
- Apply darkening and vignette
- Resolve logo or fallback title text
- Position logo/text based on selected alignment
- Add studio logo overlay
- Add optional
NEW EPISODEStag - 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:
800x450thumb1000x1500primary
Caching
Cache files live in cache/.
Current cache helpers:
- Thumb cache:
<cache_key>.png - Primary cache:
<cache_key>.primary.png - Artwork editor export cache:
<cache_key>.editor.jpg - Imported remote image cache:
cache/imports/<sha256>.img
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-typesgenerate_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
ThumbandPrimaryuploads 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:
itembackdropIndexlogoIndeximageInfosearchQuerysearchStartsearchLimitsearchTotalgenerated
When adding a new visual option:
- Add UI control
- Add local JS state if needed
- Include it in
POST /api/generate - Include it in
POST /api/apply - Add it to
build_cache_key(...) - Apply it in the render pipeline
If any of those steps are skipped, behavior will drift.
Artwork editor state is intentionally separate:
editorState.sourceKindeditorState.sourceTypeeditorState.sourceIndexeditorState.importIdeditorState.cacheKey- transform controls from the editor sliders
Keep editor payloads aligned across:
/api/artwork/export/api/artwork/applybuild_editor_cache_key(...)render_artwork_editor_image(...)
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:
Applyuploads 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:
- Read both
app.pyand the relevant section oftemplates/index.html - Keep generate/apply payloads aligned
- Keep cache key aligned with render options
- Test thumb and primary rendering
- Run:
python -m py_compile app.py
- Manually verify:
- search
- item select
- generate
- apply
- optional primary generation
- logo/backdrop asset switching
Recommended Future Enhancements
Likely next improvements:
- Additional image search providers from Emby metadata sources
- 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 app’s 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.