Skip to content

fix(dub): keep newer subtitles when a render or transcription finishes late - #2614

Merged
debpalash merged 11 commits into
mainfrom
fix/dub-subtitle-integrity
Oct 7, 2026
Merged

debpalash merged 11 commits into
mainfrom
fix/dub-subtitle-integrity

Conversation

@debpalash

@debpalash debpalash commented Oct 5, 2026 •

Copy link
Copy Markdown
Owner

Replaces #2585 with a maintainer-only reimplementation of its review findings.

What changes

  • A dub render or transcription that finishes after subtitles were imported, edited or re-transcribed keeps the newer subtitles. The user is asked to generate again.
  • New speech enters the regeneration cache only with its fingerprint. A cancel or error partway through no longer leaves new audio under an old fingerprint.
  • Publishing a new track clears stale QC marks, on the server and in the renderer.
  • Both transcription routes save their transcript and voice references in one step, leaving a concurrent subtitle import in place.
  • A cancel that arrives after work was saved still reports the task as done.
  • An empty DubRequest.segments returns 422 instead of replacing the track with silence.
  • Setup progress matches uv package names across -/_ spellings.

Each fix has a regression test that fails on the old code. The changelog, docs/electron-dubbing.md and all locales are updated.

Not included

  • Reusing speech from a cancelled render on retry (a new feature; main discards it today as well).
  • One known edge: a subtitle import during reference extraction can still overwrite reference audio in the job folder. A per-run reference folder is planned as a follow-up.

Dubbing now preserves the existing track when source subtitles change during generation, stages speech with its fingerprints before publication, and commits transcription results only if source subtitles remain unchanged. The changes also handle cancellation after work is saved, reject empty DubRequest.segments, and match setup progress across package-name variants. A known risk remains: importing subtitles during reference extraction can overwrite reference audio in the job folder.

Setup progress keyed downloads by the raw announced name, so a package
spelled pydantic-core on one line and pydantic_core on the next lost its
progress and completion. Names are now PEP 503 normalized for matching
while the announced spelling is still shown.
A cancel that arrived while a task committed its result made the worker
drop the done event and mark the task cancelled, so clients could redo
work that already existed. A done update is now always delivered.
Source subtitle replacements (SRT import, downloaded captions, cleanup,
transcription) now advance a per-job revision. A dub render publishes its
track, segments and manifest only if the revision and cancellation state
are unchanged, so imported corrections are never overwritten and the
previous track survives a late cancel. Fresh segment speech is staged and
enters the partial-regeneration cache only together with its fingerprint.
Transcription commits its transcript and voice references atomically and
yields to subtitles imported while it ran. Published tracks drop QC marks
measured on the previous track, and empty dub requests are rejected.
@greptile-apps

greptile-apps Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Retrigger

[Medium risk] Fixes subtitle and audio sync timing in the dubbing pipeline.

The PR appears safe to merge based on the reviewed changes and resolved previous findings.

Summary

The PR protects newer subtitles and saved voice references from late dubbing work, publishes rendered audio with its cache metadata, and updates task, setup, and renderer behavior. Since the previous review, it also adds model-licence records and credits, adjusts Pro descriptions, and packages licence notices. The three previous Greptile threads are resolved; no new merge-changing finding was established.

Reviews (5) · Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." · Reviewed by Greptile

Comment thread backend/api/routers/dub_generate.py Outdated
Comment thread backend/api/routers/dub_generate.py
@coderabbitai

coderabbitai Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: debpalash/VoiceStudio/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 678b33c5-f238-49a5-98ff-6688278b3e5e
📥 Commits

Reviewing files that changed from the base of the PR and between 5fe780f and 1885d65.

📒 Files selected for processing (44)
  • CHANGELOG.md
  • backend/services/dub_pipeline.py
  • electron/src/renderer/src/i18n/locales/ar.json
  • electron/src/renderer/src/i18n/locales/de.json
  • electron/src/renderer/src/i18n/locales/en.json
  • electron/src/renderer/src/i18n/locales/es.json
  • electron/src/renderer/src/i18n/locales/fr.json
  • electron/src/renderer/src/i18n/locales/hi.json
  • electron/src/renderer/src/i18n/locales/id.json
  • electron/src/renderer/src/i18n/locales/it.json
  • electron/src/renderer/src/i18n/locales/ja.json
  • electron/src/renderer/src/i18n/locales/ko.json
  • electron/src/renderer/src/i18n/locales/nl.json
  • electron/src/renderer/src/i18n/locales/pl.json
  • electron/src/renderer/src/i18n/locales/pt.json
  • electron/src/renderer/src/i18n/locales/ru.json
  • electron/src/renderer/src/i18n/locales/sv.json
  • electron/src/renderer/src/i18n/locales/th.json
  • electron/src/renderer/src/i18n/locales/tr.json
  • electron/src/renderer/src/i18n/locales/uk.json
  • electron/src/renderer/src/i18n/locales/vi.json
  • electron/src/renderer/src/i18n/locales/zh-CN.json
  • electron/src/renderer/src/i18n/locales/zh-TW.json
  • electron/src/shared/i18n/locales/ar.json
  • electron/src/shared/i18n/locales/de.json
  • electron/src/shared/i18n/locales/en.json
  • electron/src/shared/i18n/locales/es.json
  • electron/src/shared/i18n/locales/fr.json
  • electron/src/shared/i18n/locales/hi.json
  • electron/src/shared/i18n/locales/id.json
  • electron/src/shared/i18n/locales/it.json
  • electron/src/shared/i18n/locales/ja.json
  • electron/src/shared/i18n/locales/ko.json
  • electron/src/shared/i18n/locales/nl.json
  • electron/src/shared/i18n/locales/pl.json
  • electron/src/shared/i18n/locales/pt.json
  • electron/src/shared/i18n/locales/ru.json
  • electron/src/shared/i18n/locales/sv.json
  • electron/src/shared/i18n/locales/th.json
  • electron/src/shared/i18n/locales/tr.json
  • electron/src/shared/i18n/locales/uk.json
  • electron/src/shared/i18n/locales/vi.json
  • electron/src/shared/i18n/locales/zh-CN.json
  • electron/src/shared/i18n/locales/zh-TW.json
🚧 Files skipped from review as they are similar to previous changes (43)
  • electron/src/renderer/src/i18n/locales/en.json
  • electron/src/shared/i18n/locales/nl.json
  • electron/src/shared/i18n/locales/vi.json
  • electron/src/shared/i18n/locales/es.json
  • electron/src/renderer/src/i18n/locales/sv.json
  • electron/src/renderer/src/i18n/locales/pl.json
  • electron/src/shared/i18n/locales/it.json
  • electron/src/renderer/src/i18n/locales/zh-CN.json
  • electron/src/renderer/src/i18n/locales/fr.json
  • electron/src/shared/i18n/locales/pt.json
  • electron/src/renderer/src/i18n/locales/ja.json
  • electron/src/shared/i18n/locales/ar.json
  • electron/src/renderer/src/i18n/locales/tr.json
  • electron/src/renderer/src/i18n/locales/pt.json
  • electron/src/shared/i18n/locales/uk.json
  • electron/src/renderer/src/i18n/locales/nl.json
  • electron/src/shared/i18n/locales/tr.json
  • electron/src/shared/i18n/locales/fr.json
  • electron/src/shared/i18n/locales/id.json
  • electron/src/shared/i18n/locales/ru.json
  • electron/src/renderer/src/i18n/locales/vi.json
  • electron/src/renderer/src/i18n/locales/hi.json
  • electron/src/renderer/src/i18n/locales/zh-TW.json
  • electron/src/shared/i18n/locales/de.json
  • electron/src/shared/i18n/locales/ko.json
  • electron/src/shared/i18n/locales/th.json
  • electron/src/shared/i18n/locales/en.json
  • electron/src/renderer/src/i18n/locales/id.json
  • electron/src/shared/i18n/locales/zh-CN.json
  • electron/src/shared/i18n/locales/hi.json
  • electron/src/shared/i18n/locales/zh-TW.json
  • electron/src/shared/i18n/locales/pl.json
  • electron/src/renderer/src/i18n/locales/it.json
  • electron/src/renderer/src/i18n/locales/th.json
  • electron/src/renderer/src/i18n/locales/ar.json
  • electron/src/renderer/src/i18n/locales/de.json
  • electron/src/shared/i18n/locales/sv.json
  • electron/src/renderer/src/i18n/locales/ru.json
  • electron/src/renderer/src/i18n/locales/uk.json
  • electron/src/renderer/src/i18n/locales/es.json
  • CHANGELOG.md
  • electron/src/renderer/src/i18n/locales/ko.json
  • electron/src/shared/i18n/locales/ja.json

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 5 remain after this review.


📝 Walkthrough

Walkthrough

The changes add revision-checked transcription commits and staged dubbing publication. They preserve existing output when generation is cancelled or source subtitles change, clear prior quality-check marks, handle completion events during cancellation, validate non-empty dub requests, and normalize package names in setup progress tracking.

Changes

Dubbing integrity

Layer / File(s) Summary
Source-segment revisions and updates
backend/services/dub_pipeline.py, backend/api/routers/dub_core.py, backend/schemas/requests.py, tests/test_dub_subtitles_309.py, tests/test_router_smoke.py, tests/test_dub_source_revision.py
Source-segment replacements advance a revision under the dub-jobs lock. Subtitle imports, downloaded captions, and stored-segment cleanup use this replacement path. Empty dub requests fail validation.
Transcription revision checks
backend/services/dub_pipeline.py, backend/api/routers/dub_core.py, tests/test_dub_source_revision.py
Streaming and legacy transcription stage derived state and commit only if the source-segment revision is unchanged. Each pass uses a separate reference folder, and uncommitted references are discarded.
Staged render and track publication
backend/api/routers/dub_generate.py, tests/test_dub_complete_audio.py
Segment WAVs and the final track are staged. Cache and track state are published under the dub-job lock after cancellation and source-revision checks. Published segments discard prior quality-check fields. Tests cover source changes, cancellation, render failure, cache fingerprints, and quality-check fields.
Completion handling and integrity messages
backend/core/tasks.py, electron/src/renderer/src/features/dub/*, electron/src/renderer/src/lib/api/failure.ts, electron/src/renderer/src/components/pipeline-failure.test.tsx, electron/src/renderer/src/i18n/locales/*, electron/src/shared/i18n/locales/*, tests/test_task_stream_failure.py, docs/electron-dubbing.md, CHANGELOG.md
Completed stream events continue through task handling when cancellation arrives. Generation results clear prior quality-check measurements. Source-change errors map to localized messages. Documentation and changelog entries describe the listed fixes.

Setup download progress

Layer / File(s) Summary
Package-name matching and progress display
electron/src/main/setup-progress.ts, electron/src/main/setup-progress.test.ts
Package names are normalized for matching across case and separator variants. The tracker retains the first announced spelling for display, and tests cover progress and completion accounting.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Bug fix

Possibly related PRs

  • debpalash/VoiceStudio#1672: Both changes preserve voice references during SRT imports; this change also coordinates import updates with transcription commits.

Suggested reviewers: kevin9327

Merge Risk: ⚪ Minimal · up to 1885d

No identified issue blocks merging after normal checks.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 4eca8

Revision checks and staged publication strengthen protection against stale work replacing newer subtitles or audio. No introduced or worsened security issue was established in the inspected paths. Interrupted-work cleanup and coordination across multiple running processes remain incompletely established.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The inspected changes affect subtitles, reference mappings, segment-cache audio and published tracks belonging to the requested job. Base-to-head inspection established no new cross-job reference path or additional caller authority; deployment-wide access controls remain outside the established coverage.

Security Findings and Attack Paths

  • observed — Reference extraction still uses deterministic filenames within a shared job directory. Concurrent work can therefore collide at the file level even when stale reference metadata is discarded. The extraction directory and naming behavior predate this PR; no introduced or worsened exposure was established.

Trust Boundaries and Controls

  • observed — Source replacement and guarded publication share the job lock. Replacing subtitles invalidates in-flight work through revision mismatch; ordinary QC annotations do not advance the revision. This strengthens the in-process boundary between derived work and authoritative subtitle state.

Resilience and Maintainability Implications

  • observed — Filesystem replacement and SQLite persistence are not one durable transaction. Persistence exceptions are logged and swallowed, so completion can coexist with older persisted metadata. This boundary existed before the PR; staging improves blocked-publication containment but does not provide crash-atomic publication.

Hardening Proposals

  • proposed — Isolate extracted reference clips by run and define cleanup for failed or superseded runs. This would strengthen ownership of sensitive voice assets and address the pre-existing shared-file race, rather than remediate an established PR regression.
🚥 Pre-merge checks | ✅ 7 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.04% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 92 functions across 16 files. (43 skipped… Write docstrings for the functions missing them to satisfy the coverage threshold.
Cross-Platform Default Parity ⚠️ Warning FAIL — default dub rendering can leave different segment caches on Windows. In backend/api/routers/dub_generate.py:688-699, a failed os.replace is swallowed, preserving the old cache and fingerpri… Make cache publication independent of open-file replacement semantics. For example, write segments to immutable versioned paths and atomically switch the cache manifest with the track, or defer publication until replacement can complete; do…
✅ Passed checks (7 passed)
Check name Status Explanation
Title check ✅ Passed The title follows the required conventional-commit format with the fix(dub) scope and describes the subtitle-preservation change. The description references #2585, satisfying the issue-reference req…
Description check ✅ Passed The description includes a summary, key changes, out-of-scope items, and regression-test information. It does not fill in the template’s Type and Checklist sections, and it does not state whether the …
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
I18n Completeness (21 Locales) ✅ Passed The new dubIntegrity.sourceChanged key is used in electron/src/renderer/src/lib/api/failure.ts. It exists in all 21 renderer locale files and all 21 shared locale files, and all files parse as val…
Local-First Guarantee ✅ Passed The PR adds no outbound network calls, cloud requirements, accounts, or API keys. The changed code covers local dubbing, transcription, task handling, and setup-progress name matching; analytics and f…
Backward Compatibility ✅ Passed No backward-compatibility failure is introduced. The diff changes no database migration or persistent schema. New segment revision fields default to zero when absent, and the new transcription referen…
Full details: Docstring Coverage

Explanation

Docstring coverage is 38.04% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 92 functions across 16 files. (43 skipped: 43 unsupported.)

Full details: Cross-Platform Default Parity

Explanation

FAIL — default dub rendering can leave different segment caches on Windows. In backend/api/routers/dub_generate.py:688-699, a failed os.replace is swallowed, preserving the old cache and fingerprint while the new track is still published; the code identifies a preview-held file on Windows as a cause. Other platforms can replace an open file, so incremental generation can see different saved fingerprints and audio-cache state across platforms.

Resolution

Make cache publication independent of open-file replacement semantics. For example, write segments to immutable versioned paths and atomically switch the cache manifest with the track, or defer publication until replacement can complete; do not publish a track with platform-dependent cache state. Add a regression test for a held-open cache file and verify the same user-visible result on macOS, Windows, and Linux.

  • Fix all pre-merge checks with AI
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

… published edits

- A render cancelled or superseded during assembly had already moved its
  speech and fingerprints into the shared segment cache, so previews read
  through the previous track's manifest played rejected audio. Staged
  speech now reaches the cache in the publication commit.
- A published dub replaced the job's segments without advancing the
  revision transcription checks. Every segment write now goes through the
  revision helpers, with a separate write counter so renders of other
  languages are not rejected.
- Each transcription writes its voice references to its own folder, so
  cues imported mid-pass keep their clips; commits replace the previous
  transcript's clone maps. Existing jobs keep their references in place.
- The import-during-transcription test now uses asr_model_installed:
  without ASR weights the stream ended at the model-missing preflight,
  which is why it failed only in CI. A guard test requires that stub in
  every transcription test.
Comment thread backend/api/routers/dub_core.py Outdated
…on cleans up

The import selected the references it keeps outside the job lock, so a
transcription finishing meanwhile could commit, delete the previous
pass's reference folder, and leave the import saving paths to deleted
clips. Selection and save are now one locked step, and the folder
cleanup runs under the same lock and keeps any folder the job's saved
references still point into.
Comment thread backend/services/dub_pipeline.py Fixed
Comment thread backend/services/dub_pipeline.py Fixed
@debpalash
debpalash merged commit d961f65 into main Oct 7, 2026
22 checks passed
@debpalash
debpalash deleted the fix/dub-subtitle-integrity branch October 7, 2026 06:23
phvhao added a commit to phvhao/VoiceStudio that referenced this pull request Oct 8, 2026
…onunciation

Brings in the 206 upstream commits up to 06c6e07: the 0.5.7 release,
cross-site and Host checks, local-only ffmpeg inputs, offload to RAM after
generation (debpalash#2618), dub subtitle integrity (debpalash#2614), the Pro licence cleanup
and the 2026-10b issue batch.

Conflicts keep both sides:
- dub_generate: upstream's staged publication (seg_order) with our Smart Fit
  video_speed_cap and per-line video_fit in the fit fingerprint.
- dub_background: our shared retime helper builds each chunk with upstream's
  retime_chunk_filter (debpalash#2616); zero-length cues stay skipped.
- ipc: streamed, abortable saves report upstream's encoded download failure.
- replace-file: upstream's shared rename-retry with our streamed data.
- Locales: upstream's licence wording and new keys, our Vietnamese wording
  elsewhere; keys upstream removed stay removed.
- Docs: service count, both features' notes.

Follow-ups the merge needs:
- The true-peak meter's ffmpeg argv goes through local_inputs_only.
- The one-shot HTML export download refuses cross-site requests, like
  upstream's other side-effecting GET routes.
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