# PostersPlus environment configuration # Copy this file to .env and fill in your values. Everything is optional except # the two keys marked [Required]. # # This file is the settings most instances actually set. Tuning knobs — OCR # thresholds, cache TTLs, logo sizing, anime providers, poster selection — live # in ADVANCED.md. You should not need ADVANCED.md to run PostersPlus. # ============================================================================= # Admin dashboard # ============================================================================= # Set this and everything below can be managed from http://your-host:8000/admin # instead of this file (dashboard values take precedence over the environment). # Use a long random string. This is NOT ACCESS_KEY, which is shared with every # client that renders posters. Leave blank to keep the dashboard disabled. ADMIN_KEY= # ============================================================================= # API keys # ============================================================================= # [Strongly recommended] Fetches posters, logos, and metadata. Without one # (and no per-client tmdb_key) titles render from Cinemeta instead and need an # imdb_id on the request — see CINEMETA_ENABLED. # Get one free at https://www.themoviedb.org/settings/api TMDB_API_KEY= # Fetches aggregated ratings (Letterboxd, Trakt, Rotten Tomatoes, Metacritic, # IMDb, ...), keywords (used for the festival/cult/true-story/must-see sashes # and Oscar detection), and age ratings. # Optional despite the name suggesting otherwise in earlier docs: leaving this # blank does not stop the app from running. Posters still render with genre, # year, TMDB-sourced sashes (trending, Golden Globe, studio/director/cast, # foreign-language, release-status, structural), and quality badges; only the # MDBList-only sashes (festival, cult classic, true story, Metacritic # must-see, Oscar wins/noms) and the weighted rating score are unavailable, # and the score reads "N/A" unless IMDB_DATASET_ENABLED supplies the "imdb" # weight from the local dataset below instead. # Get one free at https://mdblist.com/ MDBLIST_API_KEY= # Optional second MDBList key. When the primary is rate-limited PostersPlus # rotates to this one and back, so ratings survive peak usage. MDBLIST_API_KEY_2= # Optional TheTVDB v4 key. When set, TVDB becomes a *fallback* art source: for # titles where TMDB has no usable logo or textless art, PostersPlus pulls a TVDB # clearlogo, backdrop, or (opt-in) poster instead of falling back to a plain text # title or genre canvas. Blank disables 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 TVDB toggles. Logos and backdrops are low-risk pure fallbacks, so # they are on. Posters often carry burned-in title text, so they are off; when # enabled they are only used if 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 often better # than TMDB/Metahub, so raising this is usually an improvement. # 1 = TVDB first (beats TMDB and Metahub) # 2 = after TMDB's own logos, before the Metahub CDN # 3 = last resort, only when TMDB and Metahub have nothing (default) TVDB_LOGO_PRIORITY=3 # Max concurrent outbound TVDB requests per worker. TVDB_CONCURRENCY=3 # Cinemeta (Stremio's catalogue, IMDb-keyed, no key) as the art and metadata # source when the TMDB path can't be used: no TMDB key on the server or the # request, or TMDB has no record for the IMDb id. Also tries its Metahub art # before the genre canvas when TMDB knows a title but has no artwork. Needs an # imdb_id (or a tt... stremio_id) on the request; with a TMDB key and a TMDB # record, nothing here runs. CINEMETA_ENABLED=true # Anime-native art (AniList / Kitsu). Neither needs a key. These engage ONLY # when a client passes an anime id on /poster (anilist_id=, kitsu_id=, or a # kitsu:/anilist: stremio_id=), so clients that only speak imdb/tmdb/tvdb are # unaffected. When # engaged, the provider supplies cover art, title, genres, air dates, status and # its community score in one request. ANIME_SOURCES_ENABLED=true # Composite a title logo over anime cover art. That art rarely carries a # logotype, so a proper logo is usually an improvement; false serves the # provider's art untouched. Needs a tmdb_id or imdb_id on the request, or # ANIME_ID_MAP_ENABLED to supply one. ANIME_COMPOSITE_LOGO=true # Fill in the TMDB/IMDb ids an anime request didn't send, from the community # Kitsu/AniList mapping (Fribb's anime-lists, downloaded daily into a local # table). A client that only sends kitsu:/anilist: ids (Nuvio's own resolver) # then gets TMDB logos, landscape backdrops and IMDb-keyed ratings, the same as # one going through AIOMetadata. Art still comes from the anime provider. ANIME_ID_MAP_ENABLED=true # ============================================================================= # Access control # ============================================================================= # Shared secret appended to every request as ?access_key=. # Blank allows unauthenticated access. ACCESS_KEY= # ============================================================================= # Server # ============================================================================= # Uvicorn worker processes. One worker avoids duplicate uncached work and shares # a single render/rating coordination layer, which is why it is the default. WORKERS=1 # ============================================================================= # Quality badges # ============================================================================= # Quality backend: aiostreams (default), scraper, or qualicache. QUALITY_SOURCE=aiostreams # Base URL of your AIOStreams instance, e.g. http://aiostreams:3000 over the # Docker network, or https://aiostreams.example.com. Blank disables the badges. AIOSTREAMS_URL= # AIOStreams config uuid:password encoded as Base64, including the colon. # A separate config is recommended for this integration. AIOSTREAMS_AUTH= # 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 rendering never waits on a # scrape; uncrawled titles report "pending" and are picked up on a later request. # Configure addons in QualiCache's own STREMIO_ADDONS, not here. QUALICACHE_URL= # Only needed if QualiCache sets an ACCESS_KEY; must match that value. QUALICACHE_API_KEY= # Lowest QualiCache release-group tier to accept: high, medium, or low. QUALICACHE_MIN_TRUST=high # Days to cache quality data for titles older than 2 weeks. New titles always use # a 1-day TTL. Raise to reduce traffic to your quality source. QUALITY_OLD_CACHE_DURATION=90 # Max concurrent background quality fetches. Caps burst load when many uncached # titles arrive at once — the rest queue rather than being dropped. QUALITY_BG_CONCURRENCY=5 # Max seconds to wait when a client requests wait_for_quality=true. QUALITY_WAIT_TIMEOUT=30 # Max concurrent outbound MDBList calls per worker. MDBLIST_CONCURRENCY=3 # Minimum seconds between MDBList request starts, shared by live renders and # cache warming. MDBList has a short per-IP burst limit besides the daily # quota; 0.2 holds the server to 5 calls/s. 0 turns the pacing off. MDBLIST_MIN_INTERVAL=0.2 # A rating source with fewer votes than this is ignored for the weighted score. RATING_MIN_VOTES=10 # Max uncached poster renders in flight per worker. Cache hits are never held # back. A cold catalog grid can request 50+ posters in a second; past this many # the rest queue instead of overwhelming TMDB/MDBList and the connection pool. POSTER_RENDER_CONCURRENCY=8 # ============================================================================= # IMDb local ratings dataset (MDBList-free IMDb score) # ============================================================================= # Every weighted rating source, including the one labelled "imdb", normally # comes from MDBList. Enabling this pulls IMDb's own free, no-key, # daily-refreshed non-commercial dataset instead # (https://datasets.imdbws.com/title.ratings.tsv.gz) and answers "imdb" weight # lookups from a local table — no MDBList key needed for that source, and it # still works if you have no MDBList key configured at all. Off by default. # # Selected per instance/request (default: mdblist, unchanged behaviour): # imdb_rating_source=dataset always use the local dataset # imdb_rating_source=fallback use MDBList, and fall back to the dataset # only when it has no IMDb score for a title — # a rate-limited or exhausted key, or a title # MDBList simply doesn't carry one for # # Note that "imdb" ships at weight 0 in the default movie/TV weights, so you # must also give it a weight on the configurator's Weights tab — otherwise # changing the source changes nothing and scores still read N/A. # # IMDb licenses these files for personal and non-commercial use only: # https://developer.imdb.com/non-commercial-datasets/ IMDB_DATASET_ENABLED=false # Where the downloaded dataset is stored (SQLite file). Put it on the same # persistent volume as the rest of the cache. Budget ~84 MB for it: the # download is ~8.6 MB gzipped and expands to ~1.7 million rows. IMDB_DATASET_PATH=/app/cache/imdb_ratings.db # Hours between dataset refreshes. IMDb regenerates the source file about once # a day, so anything below 24 just re-downloads the same data. IMDB_DATASET_REFRESH_HOURS=24 # Minimum vote count for a dataset rating to be used, mirroring RATING_MIN_VOTES. IMDB_DATASET_MIN_VOTES=10 # ============================================================================= # Output format and caching # ============================================================================= # Poster output format: webp (default) or jpeg. WebP compresses better. IMAGE_FORMAT=webp # Output quality, 70-95. Higher means larger files. Only the one matching # IMAGE_FORMAT is used. WEBP_QUALITY=85 JPEG_QUALITY=85 # Cache-Control: public, max-age on poster responses, capped at the composite's # own remaining life so a cached copy never outlives the trending rank or # release status baked into it. "auto" (default) advertises that remaining life # with no fixed ceiling, which is what a caching client or CDN wants: a # trending-sashed poster expires in a day, an Ended title lasts the full # COMPOSITE_CACHE_TTL. A number caps max-age at that many seconds; 0 sends no # Cache-Control at all. CDN_CACHE_TTL=auto # Seconds to keep a composited poster before re-rendering. Pruning runs off this # value so abandoned configs don't grow the cache forever. Default: 7 days. COMPOSITE_CACHE_TTL=604800 # Max composite cache entries. 0 = no cap, rely on the TTL alone. Worth setting # on a public instance as a failsafe. COMPOSITE_MAX_ENTRIES=0 # ============================================================================= # Trending # ============================================================================= # Time of day to fetch the trending list, e.g. "04:00". Empty = every 24 hours. TRENDING_FETCH_TIME= # Timezone for that schedule, e.g. "America/New_York". TRENDING_FETCH_TIMEZONE=UTC # How many trending / broad-trending items to fetch per background cycle. TRENDING_FETCH_COUNT=40 TRENDING_BROAD_FETCH_COUNT=100 # Where "trending" comes from. Empty = TMDB's own global list, which is # US-weighted and takes no region parameter. Setting one replaces TMDB's list for # that media type, for both the sash's ranking and what the cache warmer # pre-renders. The list's own order IS the ranking. Two shapes are accepted: # # MDBList — paste the ordinary list page URL, no MDBList API key needed: # TRENDING_SOURCE_MOVIE=https://mdblist.com/lists/snoak/trending-movies # MDBList mirrors Trakt, Letterboxd, IMDb and others, so it is the easy way to # seed trending from a service PostersPlus does not integrate with directly. # # TMDB-shaped — any URL returning {"results": [{"id": 1234}, ...]}, which is # how you get a regional list that TMDB's /trending endpoint cannot express: # TRENDING_SOURCE_MOVIE=https://api.themoviedb.org/3/discover/movie?api_key=KEY®ion=FR&sort_by=popularity.desc # # Entries must carry numeric TMDB ids; IMDb-keyed lists are rejected rather than # imported. Nothing checks that a list is genuinely "trending". If a source is # unreachable, malformed, or yields nothing usable, the error is logged and NO # trending is served for that media type until the next refresh — it does not # quietly fall back to TMDB, so a broken URL is visible instead of looking fine. TRENDING_SOURCE_MOVIE= TRENDING_SOURCE_TV= # ============================================================================= # Watchlist marker (self-hosted only) # ============================================================================= # Put a "Watchlist" sash on every title in ONE user's watchlist. One list per # instance: the rendered-poster cache is shared by every client, so this is # not a per-user feature. Empty = off. # mdblist the watchlist of the MDBLIST_API_KEY account. MDBList mirrors a # linked Trakt watchlist (Preferences > Trakt), which is the free # route for Trakt now that its API keys are VIP-gated. # simkl "Plan to Watch" on SIMKL. Needs SIMKL_CLIENT_ID from a free app at # https://simkl.com/settings/developer/ — register it as # "TV, devices & command line" (no secret, no redirect URL; AUTH V2 # preferred). Link the account once from the admin dashboard's # Watchlist group, which shows the link and code (also in the log). # trakt TRAKT_USERNAME's public watchlist, read with TRAKT_CLIENT_ID. # any MDBList list page, e.g. a shared "to watch" list. WATCHLIST_SOURCE= WATCHLIST_REFRESH_MINUTES=30 SIMKL_CLIENT_ID= TRAKT_CLIENT_ID= TRAKT_USERNAME= # ============================================================================= # Cache warming # ============================================================================= # Pre-populate the TMDB and MDBList caches for trending, popular and top-rated / # now-playing / on-the-air titles, so the first real request for a hot title is # already warm. Off by default — enable once you know your keys' rate limits. CACHE_WARM_ENABLED=false # Ceiling on API calls spent per warm cycle. Cache hits are free and do not count. CACHE_WARM_TMDB_BUDGET=2000 CACHE_WARM_MDBLIST_BUDGET=500 # MDBList's limit is a per-key DAILY quota (1000/day free) shared with live # poster requests. The warmer stops spending a key once its remaining daily # requests fall to this floor, so a cycle can't leave the rest of the day # rendering without ratings. 0 disables the floor. CACHE_WARM_MDBLIST_RESERVE=300 # Hours between the end of one warm cycle and the start of the next. # See CACHE_WARM_AT_HOUR in ADVANCED.md to pin cycles to a fixed local hour. CACHE_WARM_INTERVAL_HOURS=24 # ============================================================================= # Poster rendering # ============================================================================= # Detect title text burned into posters TMDB mislabelled as "textless", and skip # compositing a logo over them so the title isn't doubled. Uses the PP-OCRv5 # Mobile detector (~4.6 MB, baked into the image). false turns it off. TEXTLESS_TEXT_DETECTION=true # Burned-in-text detection runs in the foreground only for titles at or below # this TMDB vote count. Higher-vote assets render immediately and are scanned # later by an idle background queue. Raise for accuracy, lower for faster bursts # on a cold cache. Detection itself, and its thresholds, are in ADVANCED.md. TEXTLESS_DETECTION_MAX_VOTES=3000 # ============================================================================= # Localisation # ============================================================================= # ISO 639-1 language code for title logos (en, fr, de, ja, ...). Falls back to # English when the preferred language has no logo. Can also be passed per-request # as a URL parameter — see the web configurator. DEFAULT_LOGO_LANGUAGE=en