Skip to content

feat: show subtitle quality score, provider, and sync/translate status on movie detail#3337

Closed
davidgut1982 wants to merge 6 commits into
morpheus65535:developmentfrom
davidgut1982:feature/subtitle-quality-metadata-ui
Closed

feat: show subtitle quality score, provider, and sync/translate status on movie detail#3337
davidgut1982 wants to merge 6 commits into
morpheus65535:developmentfrom
davidgut1982:feature/subtitle-quality-metadata-ui

Conversation

@davidgut1982
Copy link
Copy Markdown

Summary

Closes #3336

Adds subtitle quality metadata to the movie detail view so users can see the quality of their downloaded subtitles at a glance — without inspecting the database.

New columns on the movie subtitle table

Column Content
Score Colour-coded badge: 🟢 ≥90% · 🟡 70–89% · 🔴 <70% · if no history
Provider Which provider downloaded the subtitle (e.g. opensubtitlescom, whisperai)
Status Icons for sync (⏱) and/or translate (🌐) if those actions ran on the subtitle

Data is sourced from the existing movie history API (useMovieHistory hook — already in the codebase, already used in HistoryModal). No backend changes required.

Implementation details

  • History records are joined to subtitles via subtitles_path with full null guards (embedded tracks, delete events, etc.)
  • Most recent download/upgrade (action=1 or action=3) wins for score and provider
  • Sync (action=5) and translate (action=6) events shown as overlay icons via the existing HistoryIcon component
  • ScoreBadge parses the pre-formatted score string already returned by the API

Scope

Phase 1 (this PR): movies only. Episode support is deferred — the episode view renders a full series table where calling useEpisodeHistory per row creates an N+1 query problem. A follow-up PR will address this with a series-level history endpoint or lazy-load pattern.

Files changed

File Change
frontend/src/pages/Movies/Details/index.tsx Add useMovieHistory call; pass history prop to table
frontend/src/pages/Movies/Details/table.tsx Add ScoreBadge, historyMap, statusMap, and 3 new columns

Testing

  • TypeScript build: ✅ no errors (vite build in 4.19s)
  • Existing test suite: ✅ 21 passed, 0 new failures
  • Manual: score badge, provider, and status icons visible on movie detail; embedded-track subtitles show gracefully

🤖 Generated with Claude Code

davidgut1982 and others added 5 commits May 21, 2026 11:09
…ate From)

Adds a translate_from field to language profile items so Bazarr can
automatically translate a downloaded subtitle to a target language
without manual intervention.

Behaviour:
- Providers are still searched first (translate_from is a parallel trigger)
- When a source language subtitle is downloaded and the target language
  is still missing, translation is queued automatically via the job queue
- Translated subtitles are immediately re-indexed so missing_subtitles
  is updated without waiting for the next scheduled scan
- Backward compatible: translate_from defaults to null (disabled)

Files changed:
- bazarr/app/database.py: DB migration adds translate_from field
- bazarr/subtitles/processing.py: post-download auto-translation hook
- bazarr/subtitles/tools/translate/main.py: re-index after translation
- frontend/src/types/api.d.ts: ProfileItem type extension
- frontend/src/components/forms/ProfileEditForm.tsx: Translate From UI column

Closes morpheus65535#3334

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ensures consistent 'series'/'movies' values are passed to
translate_subtitles_file(), matching the convention used by
the existing API endpoint handler.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Only trigger automatic subtitle translation when the source subtitle
meets a minimum quality score (default: 90%). Prevents translating
poorly-matched or badly-synced subtitles.

- bazarr/app/config.py: translator.min_source_score setting (default 90)
- subtitles/processing.py: check score before queuing translation
- subtitles/wanted/movies.py: check history score for existing subs
- subtitles/wanted/series.py: same for series
- frontend: min score input in Settings -> Subtitles -> Translator

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ie detail

Adds three new columns to the movie detail subtitle table:
- Score badge (green ≥90%, yellow 70-89%, red <70%)
- Provider name
- Sync/translate status icons (reuses existing HistoryIcon)

Data is sourced from the existing movie history API (useMovieHistory hook).
History records are joined to subtitles by subtitles_path with null guards.
Most recent download/upgrade record wins for score and provider.
Sync (action=5) and translate (action=6) events shown as overlay icons.

Phase 1: movies only. Episode support deferred (N+1 query concern).
Closes morpheus65535#3336

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ScoreBadge: remove duplicate % (API already appends it)
- historyMap: use first-wins (backend returns DESC, first = most recent)
- historyMap: include action=2 (Manual downloads) for score display

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@davidgut1982 davidgut1982 force-pushed the feature/subtitle-quality-metadata-ui branch from 61d0d03 to 0a92852 Compare May 21, 2026 11:30
davidgut1982 added a commit to davidgut1982/bazarr that referenced this pull request May 21, 2026
…er/status columns (PR morpheus65535#3337) to LavX

- Add translate_from field to Language.ProfileItem type
- Add TranslateFromCell selector in ProfileEditForm
- Add min_source_score Number control in Translator settings
- Add history prop + Score/Provider/Status columns to Movies/Details/table
- Preserve all LavX-specific SubtitleToolsMenu and navigation code
LavX added a commit to LavX/bazarr that referenced this pull request May 27, 2026
* feat: port auto-translate from language profile (PR morpheus65535#3335) to LavX

- Add translator.min_source_score config validator (default 90%)
- Add translate_from field migration in upgrade_languages_profile_values
- Add _trigger_auto_translation hook in processing.py
- Add translate_from_map logic to wanted/movies.py and series.py

* feat: port translate_from UI (PR morpheus65535#3335) and score/provider/status columns (PR morpheus65535#3337) to LavX

- Add translate_from field to Language.ProfileItem type
- Add TranslateFromCell selector in ProfileEditForm
- Add min_source_score Number control in Translator settings
- Add history prop + Score/Provider/Status columns to Movies/Details/table
- Preserve all LavX-specific SubtitleToolsMenu and navigation code

* fix: treat missing history as at-threshold for auto-translate

When a source-language subtitle exists on disk but has no
TableHistory/TableHistoryMovie row (manual placement or pre-tracking
era), source_score_pct was 0 and auto-translation was silently
skipped. Set source_score_pct = min_score in that case so
translation proceeds instead of silently blocking.

* fix: auto-translate forced guard, double-translate prevention, shared util

- Fix forced-subtitle guard in _trigger_auto_translation: inspect the
  forced bool parameter instead of string-scanning downloaded_lang which
  never contains ':forced'
- Write history entry before translate_subtitles_file in wanted-scan
  paths to prevent double-translation when async job has not completed
  before next scan cycle
- Extract _find_existing_subtitle_path to shared wanted/utils.py to
  eliminate copy-paste divergence risk between series and movies paths
- Hoist check_missing_languages() above profile-items loop in
  _trigger_auto_translation (called once instead of N times per item)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: wire double-translate guard query to correct language column

The previous commit wrote history entries with language=target_lang but
the guard query checked language=source_lang, so the guard could never
find the written entry. Add explicit guard queries on TableHistory /
TableHistoryMovie for action=6 AND language=target_lang before queuing
translation, so post-restart re-queuing for completed translations is
actually blocked.

Also remove duplicate store_subtitles import in wanted/series.py.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: guard query matches hi/forced language suffixes in translate dedup

TableHistory.language stores 'fr:hi' or 'fr:forced' for hearing-impaired
and forced-subtitle targets. The previous guard used an equality check on
the bare lang_code ('fr') which never matched suffixed rows, allowing
re-queuing of HI/forced auto-translations on every scan cycle. Change to
.like(f'{lang_code}%') to match all suffix variants.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: source score lookup matches :hi/:forced language suffixes (closes #142)

The history query for source subtitle score used exact equality on the
language column (e.g. 'en') but the DB stores 'en:hi' or 'en:forced'
for HI/forced subtitle tracks. The query returned no rows for those
cases, silently falling back to min_score instead of validating actual
source quality.

Change to .like(f"{translate_cfg['from']}%") to match all suffix
variants, consistent with the identical pattern already used in the
deduplication guard queries in the same files.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* chore: add pre-commit hooks (ruff, detect-secrets, standard checks)

Adds .pre-commit-config.yaml with:
- pre-commit-hooks: trailing whitespace, EOF fixer, YAML/JSON check,
  large-file guard (500 KB), debug-statement and merge-conflict detection
- ruff (lint --fix) and ruff-format, respecting existing ruff.toml ruleset
- detect-secrets with baseline covering 159 known findings in 52 files;
  future commits will only fail on *new* secrets

Chains with the existing husky/pretty-quick frontend hook by appending
a call to frontend/.husky/pre-commit after pre-commit runs in
.git/hooks/pre-commit (required because core.hooksPath was pointing to
husky's shim directory).

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: address LavX PR #137 review feedback

- Remove pre-logging of history action=6 before async translation job
  completes; translate services already write history on success, so
  pre-logging caused the duplicate guard to permanently skip failed jobs
- Add path_replace_fn parameter to _find_existing_subtitle_path so
  caller passes path_mappings.path_replace / path_replace_movie; raw
  os.path.exists on DB-stored paths breaks Docker path-mapped setups
- Convert f-string logging to % formatting to fix G004 lint failures
  in series.py, movies.py, and processing.py

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: guard translate re-queue with jobs_queue pending check, clean up logging

- Add jobs_queue in-memory dedup check before translate_subtitles_file
  so wanted-scan doesn't re-queue the same translation while an async
  job is already pending (complements the history guard which only fires
  after successful completion)
- Remove redundant str(e) from logging.exception calls (traceback is
  already included automatically)
- Use %.1f%% for min_score to match source_score_percent formatting

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* Add per-provider language settings

* Invert provider language selection

* Fix auto-translate media type and variant matching

Follow-up to #137. Queues series translations as episodes and matches Translate From profile targets by exact language variant.

* Fix embedded subtitle history upgrade handling (#154)

Co-authored-by: david <david.gutowsky@gmail.com>

* chore: prepare v2.3.1 draft release

* docs: revise v2.3.1 draft notes

* docs: add v2.3.1 codename

* fix: track per-provider apply state and add Apply all in updates panel (#155)

Applying one provider update used to flip every card's spinner because
all cards shared a single useMutation isPending boolean. Track applying
provider ids in local state so each card spins independently.

Also add an Apply all button to the Available now header that fans out
mutateAsync calls in parallel and settles once every apply finishes.

* docs: rename v2.3.1 codename to Keystone, add hero sketch and PNG

Drop the redundant "Bridge" suffix so v2.3.1 shares the Keystone
codename with v2.3.0. New hero sketch (keystone-v2.3.1.html) varies
seed and adds a teal accent for the auto-translate headline.

---------

Co-authored-by: david <david.gutowsky@gmail.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@morpheus65535
Copy link
Copy Markdown
Owner

So, basically, you should read CONTRIBUTING.md and, once done, get back to your PR and explain us, in your words, what you're trying to achieve. This isn't acceptable use of AI by our standards. Please refrain from doing pure vibe coding. We won't be accepting this kind of PR. Feel free to reopen this PR when you're ready or open a new one cleaned and done by yourself. Until then, please respect our wishes.

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.

2 participants