Skip to content

feat: quality (#1) and main-language (#6) overlay badges#57

Open
PNRxA wants to merge 12 commits into
mainfrom
feat/quality-and-language-icons
Open

feat: quality (#1) and main-language (#6) overlay badges#57
PNRxA wants to merge 12 commits into
mainfrom
feat/quality-and-language-icons

Conversation

@PNRxA

@PNRxA PNRxA commented Jun 7, 2026

Copy link
Copy Markdown
Owner

Closes #1
Closes #6

Adds two new non-rating overlay badges, drawn after the rating badges (so they bypass ratings_limit/ratings_order/ratings_exclude), flowing through the existing row/stack/split layout and honoring badge shape/background/size. Both are configurable per request and persisted per-key/global, and are folded into the cache key + CDN settings-hash.

#1 Quality icons

There is no quality/resolution metadata anywhere server-side (TMDB/OMDb/MDBList/Trakt/Fanart), so quality is caller-supplied by the addon/media server that knows the stream — modeled like the existing ?lang=/?textless= flow (no auto-detection).

  • ?quality=4k,1080p,720p,hdr,dvstackable (e.g. ?quality=4k,dv)
  • ?quality_style=text|logo — crisp text chips (4K/HDR/…), or real brand logos (Ultra HD Blu-ray, Full HD 1080, HD ready, HDR10+, Dolby Vision) rendered on a white plate so any logo colour stays legible. A tier without a bundled logo falls back to text.

#6 Main-language icons

Auto-derived from TMDB original_language (inherited from the parent show for episodes).

  • ?lang_icon=off|flag|text — a country flag, or the uppercase ISO code
  • ?lang_code=<iso> — optional override (independent of ?lang=, which selects artwork language)

en→US, pt→Portugal, etc. (a language code isn't a country — text mode and ?lang_code are the unambiguous escape hatches). Languages without a mapped flag fall back to text.

Implementation notes

  • New QualityTier / QualityStyle / LangIcon enums + validators + compact cache tokens (db.rs)
  • OverlayBadge renderer (Text / Logo / Flag) reusing the existing badge styling primitives (badge.rs); merged into each render_*_sync badge list (generate.rs)
  • icons.rs: bundled flag set (public-domain, from flagcdn) + quality logos (Wikimedia Commons), keyed lookups with graceful text fallback. Reproducible via scripts/fetch-flags.sh and scripts/fetch-quality-logos.sh.
  • original_language threaded onto ResolvedId
  • quality_style/lang_icon persisted (entity column + idempotent migration + admin/per-key/free-key DTOs); quality/lang_code are per-request only. Defaults keep existing cache keys unchanged (no migration of cached images).
  • Frontend: free "Try it out" card controls + persisted settings form (vitest specs updated)

Testing

  • cargo test — all suites green, incl. new enum/cache-suffix units, icon decode/lookup, and HTTP accept/reject/stacking/zero-ratings integration tests
  • cargo build --release --features test-support (CI parity)
  • npx vitest run (315 passed) and npm run build-only (CI gates)
  • Visually verified rendered output for poster (logos + flag), backdrop (text chips), and vertical stack (flag + logo)

Docs

README query-param list + applicability note, Cache Architecture suffix/short-value tables and an example key; scripts/README.md entries for the two fetch scripts.

PNRxA added 12 commits June 7, 2026 10:27
Adds two new non-rating overlay badges, drawn after the rating badges
(bypassing ratings order/exclude/limit) and configurable per request and
per key/global.

Quality (#1) — caller-supplied, since no quality metadata exists server-side:
  ?quality=4k,1080p,720p,hdr,dv  (stackable)
  ?quality_style=text|logo       (text chip, or brand logo on a white plate)

Main language (#6) — derived from TMDB original_language:
  ?lang_icon=off|flag|text       (country flag, or uppercase ISO code)
  ?lang_code=<iso>               (optional override)

- New QualityTier/QualityStyle/LangIcon enums + validators + cache tokens (db.rs)
- OverlayBadge renderer (Text/Logo/Flag) reusing badge styling (badge.rs)
- icons.rs: bundled flag set (flagcdn) + quality logos (Wikimedia), keyed
  lookups with graceful text fallback; scripts/fetch-{flags,quality-logos}.sh
- original_language threaded onto ResolvedId (inherited from the show for episodes)
- quality_style/lang_icon persisted (entity column + migration + DTOs);
  quality/lang_code are per-request only; both folded into the cache key
- Frontend: free "Try it out" card controls + persisted settings form
- Tests: enum/cache-suffix units, icon decode/lookup, HTTP accept/reject; docs
The quality (#1) and main-language (#6) overlay badges can now be placed at
their own anchor positions, independent of the rating badges and of each other.

  ?quality_position=bc|tc|l|r|tl|tr|bl|br   (default tr)
  ?lang_position=bc|tc|l|r|tl|tr|bl|br      (default tl)

- New OverlaySpec { quality, quality_position, language, language_position }
  threaded through generate; each group is laid out at its own anchor in
  poster/backdrop/episode (logos still stack all badges below the logo, so
  positions are ignored there)
- quality_position/lang_position persisted (entity column + migration + DTOs)
  and query-overridable; both folded into the cache key (.qp{pos}/.lp{pos})
- Frontend: position selects in the free card + settings form
- Tests (accept/reject + cache-key) and README updated
The overlay-badge 'Quality badge position' / 'Main-language badge position'
labels also contain 'Badge position', so the loose substring locator matched
3 elements. Match the poster label exactly.
… badges

When multiple quality tiers are shown, their layout direction can now be set
independently of the rating badges:

  ?quality_direction=d|h|v   (default d = auto, same as the rating badges)

- New quality_direction (BadgeDirection) setting; Default (auto) resolves to the
  image's rating badge_direction via BadgeDirection::resolve_or
- Carried on OverlaySpec; applied to the quality group's layout in
  poster/backdrop/episode (logos stack below the logo, unaffected)
- Persisted (entity column + migration + DTOs) and query-overridable; encoded in
  the cache key as .qd{dir} only when overridden from auto
- Frontend: quality direction select in the free card + settings form
- Tests (accept/reject + cache-key) and README updated
…at corners)

Previously "auto" (Default) copied the rating badges' raw direction, which on
posters is always horizontal, so quality at the default top-right rendered as a
row. Now auto resolves from the quality badge's own anchor via
BadgeDirection::resolve(quality_position): a column at corner/side positions, a
row at top/bottom-center — so the default top-right quality stack is a column.
An explicit ?quality_direction=h|v still overrides. (Removed the now-unused
BadgeDirection::resolve_or helper.)
Show the main-language badge for every title except the language(s) you
already understand:

  ?lang_exclude=en        (hide it on English titles; show on everything else)
  ?lang_exclude=en,es     (comma-separated)

- New persisted, query-overridable lang_exclude (Arc<str>, default empty),
  validated like a language list; matched against the title's main language and
  folded into the cache key (.lx{codes}, present only when the badge is on)
- serve skips the language badge when the resolved/overridden code is excluded
- Centralized language canonicalization (db::canonical_lang): strips
  region/script, maps TMDB aliases (cn->zh), and drops "no language" sentinels
  (xx/und/zxx/mul). Applied to the flag lookup, exclusion, and cache token so
  they stay consistent — fixes cn titles showing a "CN" text badge / escaping a
  zh exclude, and suppresses ugly XX badges
- Full persistence + DTO lockstep; frontend input (settings form + free card)
- Tests (canonical_lang, exclusion incl. alias, accept/reject, cache key) + docs

Found via an adversarial review pass of the lang_exclude change.
- id: always fetch parent show so episode language badge inherits the
  show's original_language even when the episode has its own still
- db: canonicalize lang_code before encoding the overlay cache key so
  equivalent forms (pt-BR/PT-BR/pt, cn/zh) share one cache entry
- db/serve: logos ignore anchor positions and quality direction, so use
  a logo-specific overlay token that omits .qp/.lp/.qd to avoid spurious
  cache entries for byte-identical logo renders
- web: emit lang_icon=off when explicitly chosen so it overrides a
  non-off server default
- web: add missing quality_direction field to RenderSettings interface
Episodes no longer render the quality (#1) or main-language (#6) overlay
badges — only posters, backdrops, and logos do.

- render_episode_sync no longer takes an OverlaySpec or runs the quality/language
  layout; generate_episode stops building it
- The episode cache key omits the `.q*`/`.li*` overlay token (it had no effect)
- The shared query params (quality/quality_style/lang_icon/lang_code/
  lang_exclude/positions/direction) are still accepted on the episode endpoint
  but ignored, consistent with other type-specific params
- README applicability updated; test asserts episode requests with these params
  still return non-400
Posters and backdrops now have independent overlay-badge anchor positions, with
their own defaults — backdrops default to quality top-left / language
bottom-left; posters keep quality top-right / language top-left.

- Split quality_position/lang_position into poster_/backdrop_ variants (4
  persisted settings) with per-type defaults; entity columns + migrations,
  parse_global/get_effective/Upsert, admin/api_keys/free-key DTOs all updated
- overlay_cache_suffix(settings, kind) picks the per-type anchors (logos still
  omit anchor tokens; episodes render no overlay)
- build_overlay_badges(settings, resolved, kind) selects the per-type anchors
- The ?quality_position= / ?lang_position= query params map to the requested
  image type (poster vs backdrop), like ?position=
- Frontend: per-type position selects in the settings form; the free card holds
  per-type state and emits the param for the selected type
- Tests (per-type cache key + accept/reject) and README updated
The language badge can now be turned on/off (and set to flag vs text)
independently per image type: poster, logo, and backdrop each have their own
lang_icon. Episodes never show it.

- Split lang_icon into poster_/logo_/backdrop_lang_icon (default off each);
  entity columns + migrations, RenderSettings/Default/parse_global/get_effective/
  Upsert, admin/api_keys/free-key DTOs all updated
- overlay_cache_suffix(settings, kind) + build_overlay_badges(settings, resolved,
  kind) select the per-type lang_icon
- ?lang_icon= query param maps to the requested image type, like ?label_style=
- Frontend: per-type lang_icon select in each section of the settings form; the
  free card holds per-type state and gates lang_code/lang_exclude/lang_position
  on the selected type's lang_icon
- Tests (per-type cache key) and README updated
# Conflicts:
#	README.md
#	api/src/image/generate.rs
#	api/tests/query_overrides.rs
#	web/src/views/SettingsView.vue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add icons for main language of the film or series Support quality icons (4k, etc)

1 participant