# PostersPlus environment configuration # Copy this file to .env and fill in your values. # All variables are optional # ============================================================================= # Server Side Keys (if not set, must be passed by users in URL as parameter) # ============================================================================= # [Required] TMDB API key — fetches posters, logos, and metadata. # Get one free at https://www.themoviedb.org/settings/api TMDB_API_KEY= # [Required] MDBList API key — fetches aggregated ratings and keywords. # Get one free at https://mdblist.com/ MDBLIST_API_KEY= # Optional second MDBList API key. When the primary key is rate-limited, # PostersPlus automatically rotates to this key and back, so ratings # remain available even during peak usage. MDBLIST_API_KEY_2= # Optional TheTVDB v4 API key. When set, TVDB becomes a *fallback* art source: # for titles where TMDB has no usable logo / textless art, PostersPlus pulls a # TVDB clearlogo, background (fanart), or — opt-in — poster, reducing fallbacks # to plain text titles or genre canvases. Leave blank to disable TVDB entirely. # Get a key at https://thetvdb.com/api-information TVDB_API_KEY= # Only needed for user-supported ("subscriber") TVDB keys; blank for company keys. TVDB_SUBSCRIBER_PIN= # Per-asset toggles. Logos/backdrops are low-risk pure fallbacks (on by default). # Posters often have burned-in title text, so they're off by default and, when # enabled, only used when text detection confirms the image is clean. TVDB_USE_LOGOS=true TVDB_USE_BACKDROPS=true TVDB_USE_POSTERS=false # Where a TVDB clearlogo ranks in the logo chain. TVDB logos are frequently # higher quality than TMDB/Metahub, so raising this is usually an improvement: # 1 = TVDB first (beats TMDB and Metahub) # 2 = TVDB after TMDB's own logos, but before the Metahub CDN # 3 = last resort, only when TMDB and Metahub both have nothing (default) TVDB_LOGO_PRIORITY=3 # Days to cache a title's resolved TVDB id and artwork listing. # TVDB_ARTWORK_CACHE_DURATION=14 # Days to cache a "no TVDB match / no art" result — kept shorter than the # positive-match duration above so newly-added TVDB art is picked up sooner. # TVDB_NEG_CACHE_DURATION=3 # Days to cache the TVDB artwork-type catalogue, which rarely changes. # TVDB_TYPES_CACHE_DURATION=30 # Anime-native art sources (AniList / Kitsu). Neither needs an API key. # These engage ONLY when a client passes anilist_id= or kitsu_id= on /poster — # no id conversion is ever performed, so metadata providers that only speak # imdb/tmdb/tvdb (where anime is grouped under TV series) are unaffected. # When engaged, the provider supplies the cover art, title, genres, air dates, # status AND its community score in a single request; the score enters the # weighted rating as the "anilist" / "kitsu" source (weight 0 by default). ANIME_SOURCES_ENABLED=true # Caps concurrent calls, per provider — their limits differ by an order of # magnitude. AniList advertises 90 req/min per IP but has served a degraded 30 # for a long while, so keep it low. Kitsu publishes no hard limit and answers in # ~0.2s. Art and metadata are cached after first fetch, so these only matter # while the cache is cold. # ANILIST_CONCURRENCY=3 # KITSU_CONCURRENCY=8 # Days to cache anime metadata. Shorter than the TVDB artwork window because the # community score drifts, and the score is part of this payload. # ANIME_METADATA_CACHE_DURATION=7 # Days to cache a "no such id on this provider" result. Throttles and network # errors are never cached — only definitive 404s. # ANIME_NEG_CACHE_DURATION=3 # Override the provider endpoints (useful for a proxy or a mirror). # ANILIST_API_URL=https://graphql.anilist.co # KITSU_API_BASE=https://kitsu.io/api/edge # ============================================================================= # Access control # ============================================================================= # Shared secret accepted as X-Jellyfin-Artwork-Key on every route except /health. # Existing routes also accept ?access_key= unless HEADER_ONLY_AUTH=true. # /render/selected always requires this value in the header. ACCESS_KEY= # Optional explicit header-only secret for POST /v1/artwork/candidates. It is # accepted only as X-Jellyfin-Artwork-Discovery-Key and must differ from # ACCESS_KEY. When unset and ACCESS_KEY is set, PostersPlus derives the key as: # HMAC-SHA256(ACCESS_KEY, "jellyfin-artwork-discovery-v1").hexdigest() JELLYFIN_ARTWORK_DISCOVERY_KEY= # Fanart.tv v3 credentials used only for clean-candidate metadata discovery. # api_key is the PostersPlus application/project key; client_key is the # operator's personal key. Neither is placed in returned artwork URLs. JELLYFIN_ARTWORK_FANART_PROJECT_API_KEY= JELLYFIN_ARTWORK_FANART_CLIENT_KEY= # Require the header globally and reject legacy access_key query authentication. # Recommended for private service-to-service deployments. HEADER_ONLY_AUTH=false # Read-only mounted YAML profile used by /render/selected. # See profile.example.yml. Leave blank to disable selected-byte rendering. RENDER_PROFILE_PATH= # Exact source commit embedded by release builds and reported by GET /ready. POSTERSPLUS_SOURCE_REVISION=unknown # Selected-image safety limits (encoded bytes, dimensions, and decoded pixels). SELECTED_MAX_BYTES=10485760 SELECTED_MAX_WIDTH=8000 SELECTED_MAX_HEIGHT=8000 SELECTED_MAX_PIXELS=20000000 # ============================================================================= # Server # ============================================================================= # Number of Uvicorn worker processes. One worker avoids duplicate uncached work # and shares one in-process render/rating coordination layer. Default: 1. # When tuning OCR, keep WORKERS x TEXTLESS_DETECTION_CONCURRENCY at or below # the available CPU core count. WORKERS=1 # ============================================================================= # AIOStreams integration (quality badges) # ============================================================================= # Base URL of your AIOStreams instance, e.g. http://aiostreams:3000 (bypass authelia, docker network) or https://aiostreams.example.com # Leave blank to disable quality badges. AIOSTREAMS_URL= # AIOStreams config uuid:password encoded as Base64, including the colon. # A separate config is recommended for this integration. AIOSTREAMS_AUTH= # Quality backend: aiostreams (default), scraper, or qualicache. QUALITY_SOURCE=aiostreams # Base URL of a standalone Stremio stream addon when QUALITY_SOURCE=scraper. SCRAPER_URL= # Base URL of a QualiCache instance when QUALITY_SOURCE=qualicache, # e.g. http://qualicache:8000 over the Docker network. # QualiCache crawls addons in the background and answers from its own cache, so # poster rendering never waits on a scrape. Uncrawled titles report "pending": # the poster is served without badges and picked up on a later request. # Configure the addons in QualiCache's own STREMIO_ADDONS — not here. QUALICACHE_URL= # Only needed if QualiCache sets ACCESS_KEY; must match that value. QUALICACHE_API_KEY= # Days to cache quality data for titles older than 2 weeks. # New titles always use a 1-day TTL. Increase to reduce AIOStreams traffic. QUALITY_OLD_CACHE_DURATION=90 # Max concurrent background quality fetches. # Caps burst load when many uncached titles appear at once. # More checks still happen, they just wait in a queue. QUALITY_BG_CONCURRENCY=5 # Maximum seconds to wait when wait_for_quality=true is requested. QUALITY_WAIT_TIMEOUT=30 # Maximum concurrent outbound MDBList calls per worker. MDBLIST_CONCURRENCY=3 # ============================================================================= # CDN / caching # ============================================================================= # Adds Cache-Control: public, max-age= to poster responses. # Set to 0 to disable (default; recommended unless you're using a CDN). CDN_CACHE_TTL=0 # Image format for composited posters (webp or jpeg). # Default: webp. WebP provides better compression and smaller file sizes. IMAGE_FORMAT=webp # JPEG output quality for composited posters (70-95). Higher = larger files. # Default: 85. Raise to 92 for pixel-peepers; lower to 75 to reduce storage/bandwidth. JPEG_QUALITY=85 # WebP output quality for composited posters (70-95). Higher = larger files. # Default: 85. WEBP_QUALITY=85 # Movies whose only known release is a theatrical date older than this many # years are treated as "Streaming" rather than "Cinema" — guards against stale # TMDB data that never got a physical/digital release date added. # Set to 0 to disable the gate entirely. Default: 3. # CINEMA_MAX_AGE_YEARS=3 # +/- half this many days of per-title jitter applied to the TMDB poster/logo # cache durations, so a large batch cached at once (e.g. cache warming) doesn't # all expire on the same day. Default: 10. # TMDB_IMAGE_CACHE_JITTER_DAYS=10 # Seconds to keep a composited poster before re-rendering. # Default: 604800 (7 days) # Pruning happens automatically based on this value to avoid an ever-growing cache of dead content when users abandon configs COMPOSITE_CACHE_TTL=604800 # +/- half this many seconds of per-title jitter applied to COMPOSITE_CACHE_TTL, # so a large batch of composites rendered around the same time (e.g. cache # warming) don't all expire and re-render at once. Default: 172800 (2 days). # COMPOSITE_CACHE_TTL_JITTER=172800 # The time of day to fetch the trending list (e.g. "04:00"). If empty, defaults to a 24-hour interval. TRENDING_FETCH_TIME= # The timezone for the schedule (e.g. "America/New_York"). Defaults to "UTC". TRENDING_FETCH_TIMEZONE=UTC # The number of trending items to fetch during the background cycle (default 40). TRENDING_FETCH_COUNT=40 # The number of broad trending items to fetch during the background cycle (default 100). TRENDING_BROAD_FETCH_COUNT=100 # Maximum composite poster cache entries. 0 = no cap (rely on TTL alone). # I suggest setting this on a public instance, as a failsafe. COMPOSITE_MAX_ENTRIES=0 # Fully-rendered composites kept in an in-memory LRU (L1) cache, served without # a SQLite read. ~100-300 KB each; 500 entries is roughly 50-150 MB. Set to 0 # to disable the in-memory cache entirely. Default: 500. # COMPOSITE_MEM_ENTRIES=500 # Disable composite poster caching entirely. Every request re-renders from # scratch. Only use this during development — never in production. # DISABLE_COMPOSITE_CACHE=true # ============================================================================= # Cache warming (optional background pre-warm) # ============================================================================= # Proactively populate the TMDB metadata/image/logo cache and the MDBList # rating/award cache for trending, popular, and top-rated/now-playing/on-the-air # titles, so the first real request for a hot title is already cached. Off by # default — enable once you understand your server keys' rate limits. CACHE_WARM_ENABLED=false # Ceiling on TMDB / MDBList API calls spent per warm cycle. Cache hits are free # and don't count against the budget. CACHE_WARM_TMDB_BUDGET=2000 CACHE_WARM_MDBLIST_BUDGET=500 # Hours between the end of one warm cycle and the start of the next (ignored # once CACHE_WARM_AT_HOUR is set, after the first cycle). Default: 24. CACHE_WARM_INTERVAL_HOURS=24 # Optionally align steady-state warm cycles to a fixed local hour of day # (e.g. "4" or "4:30"), instead of running exactly CACHE_WARM_INTERVAL_HOURS # after the previous cycle. Useful for scheduling the OCR-heavy cycle # off-peak. Uses the container's TZ (UTC if unset). Empty = old behaviour. # CACHE_WARM_AT_HOUR=4 # Also pre-fetch quality badge data (resolution/source/HDR tokens) for every # warmed title via your configured quality source. WARNING: against a public # Stremio scraper addon (rather than your own self-hosted instance) this # volume of traffic can get your server's IP rate-limited or blocked. Only # enable this against your own AIOStreams/scraper instance. Off by default. # CACHE_WARM_QUALITY_ENABLED=false # Comma-separated Stremio addon manifest URLs to pre-warm in addition to TMDB # trending/popular/supplemental — useful for a custom catalog you want fast on # first load. Each catalog is fetched and warmed first, within the budgets above. # CACHE_WARM_CATALOG_URLS= # Max items pre-warmed per catalog (across pagination), so one large catalog # can't consume the whole cycle's budget. Default: 100. # CACHE_WARM_CATALOG_MAX_ITEMS=100 # ============================================================================= # Logo rendering # ============================================================================= # When a flat (single-colour) logo's average colour is too close to the poster # background, recolour it (white / black / complementary accent) so it stays # legible. Multi-colour and outline logos are never touched. Experimental and # off by default while it's tested — it can mis-handle some logos. Set to true # to enable. Default: false. # LOGO_CONTRAST_RESCUE=false # Logo fill-stretch: a slim logo whose clamped size leaves it looking lost is # enlarged toward its size cap by up to this factor (one axis only). 1.0 = no # enlargement. Only applies when stretching is enabled (see below). Default: 1.2. # LOGO_STRETCH_FACTOR=1.2 # Fill-stretch is OFF by default (every logo kept at its true clamped size). Set # to false to enable the stretch described above. Default: true. # LOGO_STRETCH_DISABLED=true # Emit per-logo sizing telemetry (source dims, aspect, final dims) at INFO level. # Handy when tuning logo size; noisy in normal operation. Default: false. # DEBUG_LOGO_SIZING=false # Minimum votes preferred when selecting among TMDB textless posters. Candidates # cannot be more than MAX_SCORE_DROP below the highest-rated textless option, so # several negative votes cannot force selection of very poorly rated art. # Defaults: 3 votes and a maximum 1.0-point downgrade. # TMDB_POSTER_MIN_VOTES=3 # TMDB_POSTER_MAX_SCORE_DROP=1.0 # Ignore provider ratings with fewer votes than this. Roger Ebert is exempt # because MDBList reports each review as one rating. Default: 10. # RATING_MIN_VOTES=10 # Detect burned-in title text on posters TMDB mislabelled as "textless" and skip # compositing our own logo so the title isn't doubled. Uses PP-OCRv5 Mobile; # its ~4.6MB model is downloaded once into the cache volume. On by default; # set to false to opt out. Default: true. # TEXTLESS_TEXT_DETECTION=true # Run burned-in-text detection in the foreground only for titles at or below # this TMDB vote count. Higher-vote assets render immediately, are not cached as # composites, and enter an idle background scan queue. Raise for more foreground # accuracy; lower for faster stale-cache bursts. Default: 3000. TEXTLESS_DETECTION_MAX_VOTES=3000 # Append OCR-rejected TMDB posters to a deduplicated human-review file at # /app/cache/fake_textless_posters.txt. Default: true. # TEXTLESS_FAKE_REPORT=true # Optional custom path inside the container. Keep it under /app/cache to retain # the report in the existing cache volume. # TEXTLESS_FAKE_REPORT_PATH=/app/cache/fake_textless_posters.txt # Minimum PP-OCR text-box confidence. Higher is stricter. Default: 0.70. # PPOCR_BOX_THRESHOLD=0.70 # Lower-confidence fallback for wide, title-shaped regions. A box qualifies when # it meets all three limits below. This catches stylised titles that PP-OCR scores # around 0.30-0.69 without accepting the compact shapes in the Alien artwork. # PPOCR_WIDE_BOX_THRESHOLD=0.30 # PPOCR_WIDE_MIN_ASPECT=3.0 # PPOCR_WIDE_MIN_AREA=0.01 # Minimum vertical centre for the poster-only geometric fallback used when OCR # cannot read a centred, title-shaped box. Backdrop scans require title confirmation. # PPOCR_WIDE_MIN_Y=0.55 # Independent PP-OCR sessions for parallel cold-cache scans. They use a dedicated # executor and split available ONNX threads. Each extra session costs roughly # 25-40MB with the bundled mobile model. Default 2 suits typical 3+ core hosts; # use 1 on one- or two-core/memory-constrained hosts. Keep WORKERS x this value # at or below available CPU cores. Capped at 4 and the detected CPU count. # TEXTLESS_DETECTION_CONCURRENCY=2 # Fraction of poster height skipped from the TOP before counting title text. # Titles can sit top/middle/bottom, so this scans almost the whole poster; the # small default margin ignores studio/network bugs at the very edge. Raise it if # top-edge logos cause false positives; 0 scans the entire poster. Default: 0.08. # TEXTLESS_SCAN_TOP=0.08 # BUILD-TIME ONLY (read by compose at `docker compose build`): bake the ~4.6MB # PP-OCRv5 Mobile model into the image. Set false to download it on first use. # BAKE_PPOCR_MODEL=true # ============================================================================= # Localisation # ============================================================================= # ISO 639-1 language code for title logos (e.g. en, fr, de, ja). # Falls back to English if the preferred language is unavailable. # This can also be passed in the URL as a parameter (see web configurator) DEFAULT_LOGO_LANGUAGE=en