Skip to content

Translation Queue Performance Fix + Embedded Subtitles + Manual Selection + Integrity Check System#30

Merged
T9es merged 65 commits into
latestfrom
main
Feb 12, 2026
Merged

Translation Queue Performance Fix + Embedded Subtitles + Manual Selection + Integrity Check System#30
T9es merged 65 commits into
latestfrom
main

Conversation

@T9es
Copy link
Copy Markdown
Owner

@T9es T9es commented Feb 12, 2026

Summary

This PR merges 64 commits of feature work, bug fixes, and performance improvements from main into latest. It brings the latest branch up to date with all the improvements made since the last merge.

Actual Statistics

Metric Value
Commits 64
Features 10
Bug Fixes 21
Performance Improvements 4
Database Migrations 6
UI/UX Changes 3
Dependencies 3
Chores 20

What Was Actually Implemented

Features (10 commits)

  1. Phase 2: Subtitle Type Validation - Integrity Check system validates subtitle types
  2. Phase 3: Subtitle Tracking Fields - Added to TranslationRequest for audit/debugging
  3. Phase 4: Manual Subtitle Selection UI - Users can manually select subtitles
  4. AwaitingSource Items - Included in Bulk Integrity Check
  5. Unknown Items - Included in Bulk Integrity Check
  6. Media Analysis Enhancement - Translation pipeline and hashing robustness improvements
  7. Subtitle Discovery - Detect externally-added subtitles
  8. Deferred Repair Batching - Batching and fallback splitting for repairs
  9. Failed Translation State - New 'Failed' state with proper handling
  10. Forced Subtitle Penalty - Increased penalty for forced subtitles (-10 to -50)

Bug Fixes (21 commits)

Fix Description
FK Constraint Violation Fixed cleanup of show/movie causing constraint errors
Race Condition Fixed duplicate InProgress reset causing race conditions
Duplicate Key Crash Fixed crash in ShowSync
JSON Parsing Robust array parsing for LocalAI responses
SignalR Broadcast Added broadcast when worker claims translation job
Hung Jobs Delayed re-queue to reset hung jobs after startup
SSA Indexing Fixed SSA/ASS subtitle indexing
Markup Cleaning Improved subtitle markup cleaning
Media Indexing Force translation state update after media indexing
Embedded Subtitle Skip indexing for unpersisted episodes, re-index when missing
Sync Resilience Job status reporting for large libraries
Failed Queue Automatically remove media from failed queue when re-enqueuing
Queue Rotation Various queue rotation improvements
Dev Build Detection Fixed update indicator bug
Translation Keys Missing keys for embedded subtitle re-extraction
CustomAI Context Standardized batch translation context handling
Search Filtering Filter 'in progress' and 'failed' by search query

Performance Improvements (4 commits)

  1. Retry Tracking + Exponential Backoff (line 91: Cron.Daily(22)) - Failed translations retry with increasing delays (1h → 4h → 12h → 24h) at 10 PM UTC daily instead of hourly
  2. Log Streaming - Refactored to event-driven channel + heartbeat
  3. Media Sync - AsSplitQuery for better performance
  4. Indexing + Sync - Performance optimizations

Database Migrations (6 commits)

  1. Embedded subtitles columns (hearing_impaired, commentary, sdh, forced)
  2. Unique indexes for SonarrId/RadarrId with duplicate cleanup
  3. FK constraint handling in migrations

UI/UX Changes (3 commits)

  1. Update indicator bug fix + hover-to-re-extract feature for subtitles
  2. Search query filtering for in-progress/failed requests
  3. Dashboard, logs, and test UI/UX improvements

Dependencies (3 commits)

  1. vue 3.5.25 → 3.5.26
  2. vue-router 4.6.3 → 4.6.4
  3. package-lock.json updates

Verified Implementation Details

  • Schedule Change: Confirmed at ScheduleService.cs:91 - changed from hourly to Cron.Daily(22) (10 PM UTC)
  • Batched SignalR: Confirmed at TranslationRequestService.cs:430 - "Send batched SignalR notification instead of per-item"
  • UPDATE vs DELETE/INSERT: Confirmed at line 426-428 - "Successfully retried X failed requests using UPDATE strategy"

Testing

  • Backend tests: 96/96 PASSED
  • Backend build: SUCCESS
  • Frontend build: SUCCESS

Merge Note

This PR uses an ort merge strategy to combine the security updates from latest with the feature work from main.

T9es and others added 30 commits January 2, 2026 04:42
Introduces a new 'Failed' state for translation requests across client and server. Updates enums, UI badges, tooltips, and translation state logic to reflect failed translations. Ensures media state is updated when a translation fails or is modified, and adds new prompt guidance for AI translation. Also updates English translations to include the new state.
Introduces GEMINI.md with agent guidelines and lessons learned. Updates TranslationRequestServiceTests to mock new IMediaStateService dependency. Improves null checks in TranslationJob and MediaSubtitleProcessor to handle empty or null subtitles and media paths, preventing potential runtime errors.
- Add AwaitingSource state to distinguish 'waiting for subtitle' from 'excluded'

- Add LastSubtitleCheckAt property for mtime-based change detection

- Update MediaStateService to return AwaitingSource when no source exists

- Add mtime optimization to MovieSync/EpisodeSync to reduce I/O

- Add frontend support (TypeScript constant, Vue component, translations)

- Create EF migrations for SQLite and PostgreSQL
When TranslationWorkerService claims a job (Pending -> InProgress), it now

broadcasts the status change via SignalR. This fixes the UI bug where jobs

appeared stuck in Pending state after restart, even though they were running.
…tness

- Fixed translation re-evaluation and retry logic in AutomatedTranslationJob and MediaStateService.
- Implemented relative path hashing and mtime/size detection in MediaSubtitleProcessor for better change detection.
- Decoupled external subtitle hashing from media file changes and optimized embedded subtitle hashing to prevent redundant re-translations after transcoding.
- Improved OrphanSubtitleCleanupService with precise string matching for safe cleanup.
- Optimized EpisodeSync and MovieSync performance with batch SaveChangesAsync and improved stream extraction.
- Implement single-query pre-loading for show hierarchies in ShowSyncService to eliminate N+1 lookups.
- Refactor SeasonSync and EpisodeSync to use pre-loaded entities, reducing database roundtrips.
- Implement season-level directory listing in EpisodeSync to cache file metadata.
- Add batch update method in MediaStateService for efficient state computation.
- Ensure files without embedded subtitles are correctly marked as indexed to prevent redundant ffprobe probing.
- Consolidate database saves to once per batch/show.
…ptimizations

- Fixed 'not yet analysed' bug by allowing Unknown state media into the automation queue.
- Implemented Tdarr-resistant hashing (v7) using stream metadata and relative paths.
- Optimized sync performance with batch pre-loading (N+1 elimination) and season-level I/O caching.
- Improved subtitle cleanup precision to prevent accidental deletion during media upgrades.
- Fixed 'no subtitles' infinite loop by ensuring IndexedAt is updated for all media.
- Enabled automated retries for failed translations upon media refresh or settings change.
- Fixed build and test regressions in MovieSync, EpisodeSync, and MediaSubtitleProcessor.
…eporting

- Safely handle duplicate SonarrIds in ShowSyncService using GroupBy.
- Added unique constraints to SonarrId and RadarrId in DB configuration.
- Fixed SyncShowJob and SyncMovieJob to properly rethrow exceptions so Hangfire marks them as Failed on error.
…ading

- Added AsSplitQuery to Show and Movie sync queries to prevent Cartesian product explosion.
- This significantly reduces memory usage and SQL execution time for large libraries.
- Maintained GroupBy safety logic to handle existing database duplicates gracefully.
- Added RemoveFailedRequestsAsync to ITranslationRequestService.
- Implemented cleanup of failed requests in TranslationRequestService.
- Updated MediaSubtitleProcessor to clear previous failures before creating new translation requests.
- This ensures media state transitions correctly from Failed back to Pending/InProgress.
…ries

- Improved Hangfire job state detection in ScheduleService to fix 'Planned' status fallback.
- Reduced sync BatchSize to 25 and added ChangeTracker.Clear() to manage memory on large libraries.
- Added robust try-catch blocks around show, season, and episode sync loops to prevent single failures from crashing the entire job.
- Implemented targeted diagnostic logging for 'Jujutsu Kaisen' and other potential skip scenarios.
- Wrapped heavy database pre-loading in safety blocks to ensure sync continuity.
- Ensure episodes and movies bypass the optimization check if they were just indexed
- Fixes issue where media remained stuck in 'AwaitingSource' state even after subtitles were found
- Handles scenarios where file modification time remains unchanged between sync cycles
- Updated BulkIntegrityCheckJob to query for media in both 'Complete' and 'AwaitingSource' states
- Allows users to force a re-probe and state update for items stuck in 'Not yet analysed' via the Integrity UI
- Provides a safer alternative to manual database or file manipulation for recovering stuck items
- Updated BulkIntegrityCheckJob to query for media in 'Complete', 'AwaitingSource', and 'Unknown' states
- Ensures that items stuck in 'Not yet analysed' (which maps to the Unknown state) are included in the re-probing process
- Fixes the issue where the job reported '0 movies, 0 episodes' despite many unanalyzed items
This reverts commit 83dee7d.
T9es and others added 29 commits January 20, 2026 01:26
- Fix dashboard media stats accuracy by fetching counts directly from DB
- Fix 'Update Available' always green by fixing badge condition
- Enhance System Logs with rolling window, pause/resume, and animations
- Overhaul Translation Test page with media search help, split-view comparison, and download options
- Refactor TestTranslationService to return full preview data
…beat

- Replace polling-based log stream with Channel<T> event-driven stream in LogsController
- Add 15s heartbeat to prevent proxy timeouts
- Fix 'logs stop appearing' issue by removing fixed buffer dependency in stream
- Add search functionality to LogsPage.vue
- Remove expensive TransitionGroup for log list performance
- Update translations (en/nl) for new UI elements
- Fixed a bug where removing non-existent shows or movies would fail due to foreign key constraints on the 'embedded_subtitles' table.
- Updated ShowSyncService and MovieSyncService to explicitly delete related EmbeddedSubtitles before deleting Episodes or Movies.
- This ensures that when a show/movie is deleted from Sonarr/Radarr and then re-added (getting a new internal ID), the orphaned old records are successfully cleaned up instead of causing a transaction rollback.
- Verified fix resolves the 'duplicate entries' issue seen in the UI after a Sonarr library re-import.
- Build and tests (96 pass) verified.
…for subtitles

This commit includes multiple bug fixes and a new feature to improve
subtitle management and version comparison accuracy.

## Bug Fixes

### 1. Fix Update Indicator Always Showing Green (LingarrVersion.cs)
- Fixed version comparison logic that was incorrectly determining
  if a newer version was available
- Integrated Semver package for proper semantic version comparison
- The update indicator now correctly shows when a new release is
  actually available vs. when running the latest version
- Files modified:
  - Lingarr.Core/LingarrVersion.cs
  - Lingarr.Core/Lingarr.Core.csproj (added Semver package dependency)

## New Features

### 2. Hover-to-Reextract for Subtitles (ContextMenu.vue)
- Added ability to hover over subtitle items to trigger re-extraction
- Improves user workflow by providing quick access to subtitle
  regeneration without navigating through multiple menus
- Enhances the context menu experience in the media browser
- File modified:
  - Lingarr.Client/src/components/layout/ContextMenu.vue

## Technical Details

- The Semver package enables robust semantic versioning comparison,
  ensuring accurate detection of major, minor, and patch version
  differences
- The hover-to-reextract feature uses Vue.js event handlers for
  mouseenter/mouseleave interactions

## Files Modified
- Lingarr.Client/src/components/layout/ContextMenu.vue
- Lingarr.Core/Lingarr.Core.csproj
- Lingarr.Core/LingarrVersion.cs
Snyk has created this PR to upgrade vue from 3.5.25 to 3.5.26.

See this package in npm:
vue

See this project in Snyk:
https://app.snyk.io/org/tomeov1/project/67558f4f-9722-4d54-837b-fb19ddc5d23f?utm_source=github&utm_medium=referral&page=upgrade-pr
Snyk has created this PR to upgrade vue-router from 4.6.3 to 4.6.4.

See this package in npm:
vue-router

See this project in Snyk:
https://app.snyk.io/org/tomeov1/project/67558f4f-9722-4d54-837b-fb19ddc5d23f?utm_source=github&utm_medium=referral&page=upgrade-pr
- Add 'embedded.extractAgain' key (EN: 'Extract again?', NL: 'Opnieuw uitpakken?')
- Add 'embedded.reextractConfirm' key for re-extraction confirmation message
- Fixes raw text display issue in embedded subtitle extraction UI
Added proper dev build detection to prevent false update notifications on development builds.

Changes:
- Lingarr.Core/LingarrVersion.cs: Added IsDevBuild property with detection based on AssemblyVersion (1.0.0.0 indicates dev build)
- Lingarr.Core/Models/VersionInfo.cs: Added IsDevBuild boolean property to VersionInfo model
- Lingarr.Core/Lingarr.Core.csproj: Added AssemblyVersion (1.0.0.0) for dev build identification
- Lingarr.Client/src/ts/version.ts: Added isDevBuild field to Version interface
- Lingarr.Client/src/store/instance.ts: Added isDevBuild to instance store state
- Lingarr.Client/src/components/layout/AsideNavigation.vue: Added Dev Build badge display in sidebar
- Lingarr.Server/Statics/Translations/en.json: Added devBuild translation key
- Lingarr.Server/Statics/Translations/nl.json: Added devBuild translation key

This fix ensures that development builds are properly identified and displayed with a 'Dev Build' badge, preventing confusion with release builds and their update indicators.
The WasSubtitleAlreadyExtracted() check was checking the database AFTER
the extraction service had already set IsExtracted=true, causing auto-
extracted files to never be cleaned up.

Changed the logic to check File.Exists() BEFORE calling the extraction
service:
- Main extraction: Check if predicted output path exists before extraction
- Fallback extraction: Check all candidate paths before extraction
- Mark files for cleanup only if they didn't exist before extraction

Added helper methods:
- PredictExtractionOutputPath(): Predicts the output path for extraction
- PredictSubtitlePathInternal(): Internal path prediction helper
- WasAnyCandidatePathExisting(): Checks if any candidate exists for fallback

This ensures user-extracted (pre-existing) files are preserved, while
auto-extracted files are properly cleaned up after translation.
Forced subtitles now receive a -50 penalty instead of -10 to prevent them
from being selected over full dialogue subtitles. With the previous -10 penalty,
a Forced subtitle with language match (+50) would still score 40 points and
pass the quality threshold (30), potentially being selected for translation.

With -50 penalty:
- Language match: +50
- Forced penalty: -50
- Total: 0 (below threshold of 30)

Non-forced subtitles receive a +5 bonus, so even with just language match
they score 55 points, comfortably passing the threshold.

This ensures forced subtitles (which are typically partial or effect-only)
are never selected for translation when full dialogue alternatives exist.
Implements detection of incomplete source subtitles (Forced/Signs-only)
that may have been used for translations.

Features:
- New SubtitleTypeCheckResult model with entry count, completeness status,
  warnings, and recommended actions
- ValidateSubtitleTypeAsync() method in SubtitleIntegrityService to check
  individual translations (threshold: < 50 entries = potentially incomplete)
- ValidateAllSubtitleTypesAsync() for bulk scanning of all completed translations
- Integration into BulkIntegrityCheckJob with stats tracking
- New API endpoints: POST /api/subtitle/validate-subtitle-types and
  GET /api/subtitle/validate-subtitle-type/{translationId}
- UI section in IntegrityCheckPage with actions: Accept, Auto-fix, Dismiss
- Persistent storage of validation results

Files modified:
- Lingarr.Server/Models/SubtitleTypeCheckResult.cs (new)
- Lingarr.Server/Interfaces/Services/Subtitle/ISubtitleIntegrityService.cs
- Lingarr.Server/Services/Subtitle/SubtitleIntegrityService.cs
- Lingarr.Server/Jobs/BulkIntegrityCheckJob.cs
- Lingarr.Server/Controllers/SubtitleController.cs
- Lingarr.Client/src/pages/settings/IntegrityCheckPage.vue
…bugging (Phase 3)

Add new tracking fields to TranslationRequest entity:
- SourceSubtitleType: tracks subtitle type (Full, SDH, Forced, Signs/Songs, Unknown)
- SourceSubtitleEntryCount: number of subtitle entries loaded
- SelectedStreamTitle: original stream title from video metadata
- IsForcedSubtitle: flag if forced subtitle stream was used
- StartedAt: timestamp when translation actually started processing

Update TranslationJob to populate these fields:
- Set StartedAt when translation begins (not just queued)
- Capture entry count after loading subtitles
- Match subtitle file to embedded subtitle metadata
- Determine subtitle type from stream title, flags, and filename patterns
- Log captured metadata for debugging

Create EF Core migrations for SQLite and PostgreSQL to add the new columns.

These fields enable better debugging and integrity checks, helping users
understand what type of subtitle was used for each translation and why
translations might be incomplete.
Features:
- Add GET /api/subtitle/available/{mediaType}/{mediaId} endpoint to list embedded subtitles with metadata
- Add ListAvailableSubtitlesAsync service method with entry count and sparsity detection
- Create SubtitleSelectorModal.vue component for manual subtitle selection
- Add 'Manual Select' button to IntegrityCheckPage for incomplete subtitles
- Add POST /api/translate/queue-with-subtitle endpoint for queueing with specific stream
- Update TranslationJob to use preferred stream index from manual selection
- Update TryExtractEmbeddedSubtitle to accept optional preferredStreamIndex parameter

The modal shows:
- Language flags and names
- Stream index and codec info
- Entry counts for extracted subtitles
- Warning icons for forced/sparse subtitles
- Visual indicators for text-based vs image-based subtitles
Introduce project documentation and development patterns under .mindmodel and root docs. Adds coding conventions, domain glossary, manifest and stack metadata, architecture/design/concurrency/data-access/api/state-management/error-handling patterns, and multiple example implementations (Vue component, Pinia store, C# controller, service factory, job filter, custom exceptions). These files codify conventions, provide reference patterns and snippets, and supply example code to standardize development and onboarding.
Add RetryCount, FailedAt and NextRetryAt to TranslationRequest and create corresponding PostgreSQL and SQLite migrations (and update model snapshot). Implement exponential backoff and update retry tracking in TranslationJob. Refactor RetryAllFailedRequests to only select requests eligible for retry (NextRetryAt null or <= now), use an efficient UPDATE strategy to mark failed requests as Pending (avoid delete/insert churn), deduplicate retries, batch SignalR notifications, and enqueue jobs for retried requests. Also change the RetryFailedRequestsJob schedule from hourly to daily at 22:00 UTC and add safeguards when restarting individual requests to avoid duplicate active requests.
The embedded subtitle extraction feature was added with SQLite migration
but the PostgreSQL migration was never created. This caused the
AutomatedTranslationJob to fail with:
  42P01: relation 'embedded_subtitles' does not exist

Adds:
- Migration to create embedded_subtitles table
- Foreign keys to movies and episodes tables
- Indexes for episode_id and movie_id lookups
The original migration was missing required columns:
- is_text_based (bool)
- is_extracted (bool)
- extracted_path (text)
- codec_name was not marked as required

Added:
1. Updated 20260210033000_AddEmbeddedSubtitles with complete schema
2. New 20260210040000_FixEmbeddedSubtitlesColumns for already-deployed systems

This ensures the embedded_subtitles table matches the EF Core model exactly.
Previous fix migration was missing the is_hearing_impaired column,
causing null constraint violations when saving embedded subtitles.

Adds:
- is_hearing_impaired (bool, not null, default false)

This completes the embedded_subtitles table schema.
The original PostgreSQL migration for embedded_subtitles was missing
the is_hearing_impaired column and default values, causing INSERT
failures when the application tried to save embedded subtitle data.

Changes:
- Added is_hearing_impaired column with default false
- Added defaultValue: false for is_text_based and is_extracted
- Removed redundant FixEmbeddedSubtitlesColumns migration

This ensures new installations get the complete schema.

Note: Existing deployments with broken schema should run:
  ALTER TABLE embedded_subtitles ALTER COLUMN is_hearing_impaired SET DEFAULT false;
@T9es T9es merged commit a50b36d into latest Feb 12, 2026
4 checks passed
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