Add FixType property to IFixCommonError - #23
Open
ivandrofly wants to merge 700 commits into
Open
Conversation
Update Persian language
…se-language-file-update-6ef75f Update Japanese.json for v5.2.0-beta28
…rewind Two things left over from the beta 28 fix, both reported on the issue. The waveform drew no paragraphs at all after Options/OK - no boxes, no text, no number/duration/CPS, just the time ruler and the cursor - until playback was resumed, which brought everything back at once. The paragraph sets the waveform draws are rebuilt on the 50 ms position timer into plain fields, so rebuilding them asks for no repaint (AllSelectedParagraphs is an AffectsRender property, but only its identity is, and the reload mutates the list in place). While the video is paused nothing else moves an AffectsRender property either, so the last painted frame simply stays on screen. Options/OK produced exactly that: the rebuilt video player reports 0 for a moment, "center video position" scrolls the waveform to the start on the 16 ms cursor timer, the paragraph reload - on the lower-priority 50 ms timer, competing with the rebuild - empties the set, and the frame painted when the player lands back on the real position shows the right time range with nothing in it. The reload a tick later fixed the set but not the picture. LoadParagraphs now repaints when the set it draws actually changed (reference comparison, so an unchanged tick still costs nothing). The occasional rewind survived in two holes in the PositionForRestore guard: - The undocked window announced nothing before its open. It publishes the new control synchronously and opens it from a posted continuation 100 ms later, so a rebuild landing in between (Settings -> Apply followed by OK) read the live 0 of a player that had not been given a file yet. It announces the target up front now, like the docked rebuild does. - EndPositionRestore ran unconditionally at the end of every restore sequence. Those sequences are bounded (a ready wait plus a fixed number of seeks), so one that runs out while mpv is still loading handed PositionForRestore back to a player still reporting 0 - the very rewind the guard exists to prevent. The three restore paths use EndPositionRestoreIfArrived instead, which keeps the target until the player really is there; the arrival check in the position tick drops it as soon as it lands. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…-preamble-cap Read the whole Manzanita preamble, not the first 200 KB
…Edit#14245) mpv's `frame-step` is a real un-pause: it sets pause=no, plays until the next video frame, then sets pause=yes. The observed pause flag therefore flickers, and the playhead estimate read that as a play->pause cycle - the edge cleared _playheadPausedSettled on the very tick mpv reported the stepped-to frame, so the follow-a-paused-seek branch skipped it, and settling deliberately never snaps (SubtitleEdit#12740). The frame was dropped from the cursor for good and stepping forward drifted a frame per press until the 0.5 s discontinuity snap caught up. Whether it bit on a given press depended on how long mpv's un-pause window lasted - i.e. on the audio period, which any other application starting or stopping audio can change (the reporter tracked it to the PipeWire quantum): a long window is seen by the cursor timer, a short one is coalesced by mpv into a single playloop iteration and never emits a pause change at all. Slow motion always lost, since one frame then takes several times longer to play out. Stepping backwards was never affected - `frame-back-step` seeks and stays paused - which is why it snapped the cursor back onto the right spot. Open a short window around a native frame step in which the estimate treats the core as paused and keeps the cursor authoritative, so the stepped-to frame is followed exactly like a paused seek. The window closes as soon as the step lands, so a real Play() right after a step is not swallowed - and a frame-step that mpv ignores (no video track) closes it on the first tick, since a moving clock means no step is in flight.
SE5's Compare built two independent TableViews, so the wheel and the scroll bars moved one side only. With larger files the two views ended up showing completely different line ranges - even comparing a file with itself - which is what the report shows. SE4 kept them together by copying the active list view's TopItem index to the other side (Compare.SelectLinesInBothListViews). Selection was already mirrored, but it scrolled the other side with ScrollIntoView, which only promises the row is somewhere in view: with rows of different heights the two sides could keep the same row selected and still show different ranges, so clicking did not recover the alignment either. TableViewScrollSync links the two grids' ScrollViewers and, on every scroll, puts the source's top row at the other side's viewport top. It works in row indices rather than pixels on purpose: the two grids hold different text, a row wraps to one or two lines depending on content, and TableView's VirtualizingStackPanel estimates its extent from the average realized row height - so equal offsets do not mean equal rows, and the mapping drifts as rows realize. Placing the row uses the same PrePositionScroll + settle loop as TableViewScrollAnchor and TableViewIndexScrollBar, suspends any anchor on the target while it moves, and guards against the mirrored scroll bouncing back. Rows below the top can still diverge where one side wraps to two lines and its twin does not; aligning those too means giving matched rows a shared height, which is a change to the compare rows rather than to scrolling. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…leedit-issue-14218-followup Repaint the waveform when its paragraphs change, and close two rewind holes (SubtitleEdit#14218)
…leedit-issue-14245-d925ec Keep the waveform cursor on the frame when stepping forward (SubtitleEdit#14245)
…leedit-issue-13504-43a6cd Compare: restore synchronized vertical scrolling (SubtitleEdit#13504)
…4231) Follow-up to SubtitleEdit#14236, which taught the six go-to-previous/next-line commands and the grid's arrow/page keys to re-center on every step with "subtitle grid, center when selecting prev/next row" on. Three more ways of stepping a line at a time were left on the old behaviour - they only scrolled once the target row had walked off the bottom of the view, and then jumped half a screen: - "Play next/previous line (and stop/loop)". These move the selection synchronously (a posted selection would be nulled by the SelectionChanged handler's ResetPlaySelection and break stop/loop), so they set the grid's SelectedItem themselves and never reached the centering in SelectAndScrollToRow. This is also what SE 4's Alt+Down default runs with a video open - "go to next subtitle (play translate)" (SubtitleEdit#14167) - i.e. the line-by-line translate/sync loop, where the setting matters most. - "Go to next/previous time code", which walks the cue boundaries. - "Set end time and go to next", from the position timer's key-up handling. The play commands get the same posted follow-up SelectAndScrollToRow does - center when the setting is on, otherwise just nudge the row fully into view; the other two ask for the centering variant. Regression net: three cases added to SubtitleGridCenterSelectedRowTests, each measuring the selected row's middle against the viewport's middle over 8-16 steps. All three drift by a row height per step without the fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The three new cases assigned a second VideoPlayerControl to the view model,
but MainView's layout already builds one - so each test left an extra
unparented control (with its own content presenter and brushes) behind on a
headless session that keeps the application alive across the whole assembly.
CI then failed with the known contamination cascade ("the calling thread
cannot access this object" out of the compositor) in unrelated windows, a
different victim per attempt, while main stayed green.
The commands only need GetVideoPlayerControl() to return something, which the
layout's control already does.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…-grid-remaining-paths Center the grid for the last prev/next-line paths too (SubtitleEdit#14231)
LibMpvEventLoopTests.Pause_RightAfterSeek_KeepsReportingTheSeekTarget failed under full-suite load on any machine with a loadable libmpv, and the diagnosis was not what it looked like: Pause() was keeping _pausedValue exactly as intended (traced: pausedValue=1.5, seekInFlight=true). The Position getter serves that cached seek target only while IsPaused, and IsPaused answers from _observedPause - which only the mpv event thread writes, when the pause property-change event is dequeued. The pause commands are synchronous (mpv_command returns after the core has applied them), so for the moment until that event is processed the cache says the opposite of the truth, the getter skips the cached target and serves mpv's pre-seek time-pos instead. That is the SubtitleEdit#14187 waveform-click window: seek, Pause(), read Position. The test only passed in isolation because mpv usually finished the seek fast enough that the observed time-pos already was the target; under load it was still the old one. Write the commanded pause state through into the cache after a successful synchronous pause command (Pause/Play/Stop/LoadFile); PlayOrPause's "cycle pause" re-reads the property instead of predicting the core's decision. Also harden the restart bookkeeping the SubtitleEdit#14187 fix leans on. A restart was stamped when the event thread processed it, not when mpv emitted it, so a restart queued earlier (opening a file fires one) could be stamped after a seek issued in the meantime and pass for that seek's own restart - which would clear a live seek target. Each async seek now carries a generation id in its mpv_command_async reply_userdata; the event loop records which generation every restart followed, and HasPlaybackRestartedSince answers for the outstanding seek rather than for the clock alone. mpv's event queue is FIFO, so this is an ordering guarantee rather than a timing assumption. Both invariants from SubtitleEdit#14187 are unchanged: an in-flight seek target survives Pause(), a long-settled one does not get resurrected. Regression tests, both of which fail without their fix even in isolation (the click test only failed by luck): - PlayAndPause_UpdateTheObservedPauseCacheImmediately - HasPlaybackRestartedSince_AnswersForTheOutstandingSeekNotAnOlderRestart Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…use-cache-and-seek-generation Fix mpv observed-pause staleness that dropped the clicked seek target
The blip was faked with a play-selection window, which the 50 ms position timer stops by comparing the *cursor estimate* against the window end. A one-frame window is 33-40 ms, so it always overshot: mpv played 50-150 ms past the frame with the cursor gliding along, and the stop then parked back one frame - a visible forward run and snap back on every single press. How far it ran depended on where the timer tick happened to fall, so it looked random. The next press then computed its target from vp.Position, a styled property refreshed from mpv by another 50 ms timer, which right after a blip holds mpv's overshoot rather than the frame the cursor is parked on. A held-down shortcut advanced a random one to four frames per press. Give the blip its own small state machine instead: - It ends from the 16 ms cursor timer, measured against mpv's own clock, and waits for MPV_EVENT_PLAYBACK_RESTART first so the async seek onto the frame has landed (a backward step's pre-seek clock is already past the stop point). 700 ms safety cap. - UpdatePlayheadEstimate holds the cursor on the stepped-to frame for the duration, so it no longer glides forward and snaps back. Every other play/pause/seek path cancels the blip, so it can never hold the cursor against playback the user started, nor pause it. - Each press chains off the frame the previous press stepped to, so the step is exactly one frame every time. Also adds VideoPlayerControl.SeekTo: assigning Position silently no-ops when it equals the value the property already holds, which the park-back-onto-the- frame seek can hit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The new frame-step-with-play tests assign their test-built VideoPlayerControl to the view model so the command under test can find it - but an unparented control left reachable on the per-assembly headless application is the documented contamination-cascade trigger (PR SubtitleEdit#14258): unrelated windows fail out of the compositor on CI, a different victim per attempt, while the suite passes locally. Detach it (and restore the global frame rate) after each test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…hortcut-jerkiness-6d0c0f Make "one frame forward/back (with play)" step smoothly
Findings from a responsiveness/accuracy review of the waveform seek paths: 1. SMPTE drop-frame seeks landed 0.1% early. While SMPTE timing is enabled every position READ from the player is compressed by 1000/1001 (the control's position timer, the playhead estimator, the waveform time axis via UseSmpteDropFrameTime), so all UI position values live on the drop-frame clock - but seeks passed those values to the player as real seconds. Every seek therefore landed early in proportion to the absolute position (~1 s per 17 minutes), and past ~2.5 minutes the error exceeds the playhead pin's 0.15 s arrive tolerance: a waveform click pinned the cursor to the click, timed out after 600 ms, and snapped back seconds. Seeks (NotifyPositionChanged and SeekTo) now expand UI values by 1001/1000 back to the player's real clock. 2. Keyboard nudges never pinned the cursor. SetVideoPositionSeconds (the +/-100 ms and custom-ms shortcuts, and snap-to-frames frame stepping) scrolled the view to the target immediately but left the cursor waiting the 100-200 ms the seek takes, so nudges read as laggy next to a waveform click or wheel scrub, which always pinned. It now seeks via SeekTo (the Position-property assignment silently no-ops when equal to the displayed value) and pins like every other seek path. 3. The frame-with-play blip compared clocks across spaces in SMPTE mode: its stop point is UI (drop-frame) time but it read the player's real clock, so far into a file the check tripped immediately and the blip degenerated to a silent step. The read is now compressed like the estimator's. Tests: VideoPlayerControlSmpteSeekTests (expansion on/off), and a PlayheadFrameStepTests case asserting the nudge pin. Full UITests suite clean (the one failure is the known flaky LibMpv live-core test, fixed separately on main). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The same-time-code merge from SubtitleEdit#10447 guards on `region:` only. Files that position cues with inline `line:` settings instead - Netflix WebVTT among them - leave `Region` null on both cues, so a caption pinned to the top of the screen and the dialogue underneath it are merged into one paragraph. The two placements then collapse into one: whichever alignment survives is applied to the whole event, leaving one of the two lines in the wrong half of the screen. Compare the vertical band each cue sits in as well. Cues split horizontally across one line still share a band and still merge, so SubtitleEdit#10444 - the case the merge was added for - keeps working; its two halves differ in `position:`, not in `line:`. The explicit --merge-same-time-codes operation is untouched and still merges across bands when asked for. The band is read by a new GetVerticalAlignment, extracted from the `line:` half of GetPositionInfo so both callers share one definition. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rm-seek-accuracy Waveform seek review: SMPTE seek drift, unpinned nudges, blip clock mix
The vertical bands the check borrowed from GetPositionInfo exist to pick an
{\an} tag, and the 25%/75% boundaries they draw fall in the middle of ordinary
caption blocks. On the very grid the reported file uses - rows 5.33% apart -
a three-row bottom caption sits at 74.00%/79.33%/84.67%, so the top row landed
in the "middle" band and split off into its own paragraph with a stray {\an5}
on it. Tall captions near the top broke the same way against the 25% boundary.
What separates two rows of one caption from a caption over unrelated dialogue
is how far apart they sit, not which band they fall in: consecutive rows are a
few percent apart, the reported top caption and its dialogue 69%. So compare
the distance instead, and read "line:" as a percentage down the video for it.
Negative line numbers count rows from the bottom, and NumberStyles.
AllowDecimalPoint alone never parsed them, so a top caption written "line:-16"
fell through to "bottom" and merged anyway. Allow the leading sign and count
from the bottom for it.
Only a cue that says where it sits can now rule a merge out, so a file whose
cues carry no usable "line:" merges exactly as it did before the check existed.
That leaves GetPositionInfo with no second caller, so it goes back untouched
and the change is confined to the merge.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SE 4 had an Edit-menu action (MainEditToggleTranslationOriginalInPreviews) that swapped the video preview and the waveform over to the original text while a translation was being edited. SE 5 had no equivalent (SubtitleEdit#14252). Adds the command, its shortcut registration (General / Translate group) and the SE 4 importer mapping so an imported SE 4 key binding keeps working. The video preview builds a throw-away subtitle from the rows' original text rather than going through GetUpdateSubtitleOriginal - that one owns the instance that gets saved and re-stamps every row's reference id, far too much for something the preview timer calls. Rows the original has no line for are left out. The waveform draws from the rows, so it gets a plain flag instead. The state is session-only, not a saved setting: it only means anything while an original is loaded, and a remembered "on" would start the next session with a blank preview and no visible way back. Hiding the original column clears it for the same reason. The paragraph footer keeps showing the row's own CPS. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The "Update video offset" window had to be reopened for every value, and every value had to be typed in full - so switching between the two or three offsets a job actually uses (09:59:40.000 and back, say) was retyping a twelve-character time code each way. The offsets that have been applied are now kept, most recent first, in a drop-down next to the time code box: picking one fills the box. The list holds ten and is stored in settings, so it survives a restart (SE 4 parity, which had the same list behind a pick button); an empty list starts out offering 1 hour and 10 hours. The drop-down also follows the box, so an entry typed over can be picked again. The window no longer closes on every apply. Apply updates the offset and stays open, OK updates and closes, Cancel closes and keeps what was applied - so several offsets can be tried against the video without reopening the dialog. Reset stays open too, since resetting is now just another value to try. Applying hands the offset to the main window through a callback instead of the caller reading it off a closed dialog. The time codes are still shifted against the offset in force at that moment, so applying twice lands where applying the second value straight away would. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Waveform paragraph text was always shaped with a left to right paragraph direction, so in an Arabic or Hebrew line the neutral characters - the dialogue dash, a trailing ellipsis, brackets - took the paragraph direction instead of the direction of the letters around them and landed on the wrong side of the line. SE 4 rendered the same lines correctly (issue SubtitleEdit#14262, matter H). The direction is now taken from the whole paragraph (after tags are stripped), through the same TextToFlowDirectionConverter helper the grid and the compare view use, and applied to every line of it - a second line holding only neutrals or a Latin name belongs to the same block as the first. Placement is unchanged: Avalonia lays a right to left line out from the draw origin as long as no finite paragraph width is set, so the text stays left aligned in its region like SE 4 has it. The shaped-text cache is keyed on the direction as well, because the footer labels ask for the left to right form of strings a paragraph may draw the other way. Chapter titles get the same treatment, with the alignment pinned to the left: that layout does set a finite MaxTextWidth, which would otherwise push a right to left title to the far end of it, outside the flag. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…cross-vertical-bands Do not merge WebVTT cues placed in different vertical bands
"Google it" was the only way to look up selected text, and its search engine
was hard-coded. Five "search via" slots now sit next to it: each holds a name
and a URL template, configured in Options > Shortcuts the same way the
"surround with" slots are (select the shortcut, press Configure).
The slots ship with SE 4's defaults - The Free Dictionary, Wikipedia,
DuckDuckGo - and appear in a "Search via" submenu in the text box context
menu; slots without a URL stay hidden, and the submenu disappears when none
is configured. Searching uses the selected text, or the whole text of the
focused text box when nothing is selected (SE 4 parity), with the text put
where "{0}" is - appended when the template has no placeholder.
Importing SE 4 shortcuts now carries MainTranslateCustomSearch1-5 along with
the name/URL pairs they fire (VideoControls/CustomSearchTextN + UrlN), so an
imported key searches the site it searched in SE 4.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…-h-rtl-support-dac64e Waveform: give right-to-left text the direction its letters ask for
…ut-request-ca1b23 Add "toggle translation and original in video/audio preview" shortcut
…offset-dialog-90fcc5 Video offset: recently used offsets and an Apply that keeps the window open
The footer kept showing the row's own characters-per-second while the paragraph drew the original text, so the number read as the original's and was wrong. It now counts whatever is on screen. OriginalCharactersPerSecond is memoized like CharactersPerSecond - the footer reads it for every visible paragraph on every painted frame. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ubtitleEdit#14420) Ports SE 4's two text-box shortcuts: one break at the first space at (or right before) the caret, existing breaks flattened first, on whichever text box has focus. The plain variant leaves the caret at the end of the new first line; the "and go to next" variant moves to the next row. Both are unbound by default, as in SE 4, and the SE 4 settings import now maps MainTextBoxBreakAtPosition and MainTextBoxBreakAtPositionAndGoToNext to them. Utilities.ReSplit gets its first unit tests. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…leedit-issue-14420-4f1bf1 Add "Break at first space from cursor position" text box shortcuts
…leedit-issue-14418-7028b7 Fix recalculate duration landing just over max CPS, and CPS readouts disagreeing
"Clone from video (voice of each line)" needs an engine that takes its reference per synthesis call. IndexTTS 2.5, Higgs Audio v3 and Fish Audio S2 Pro on audio.cpp already send `voice_ref` as a per-request path, so enabling it is the OmniVoice shape: the line's voice points straight at the cut clip, nothing is staged, nothing to clear between runs. audio.cpp resamples the reference itself, so the 24 kHz clips are used as cut. Fish refuses a reference without `reference_text`, so a clip with no transcript sidecar is not handed out as a voice there; the line falls back to an ordinary voice instead of failing the run. Higgs and IndexTTS clone from the audio alone and take every clip. Fixes SubtitleEdit#14425 (Higgs and Fish; Confucius4 needs the CrispASR adapter to re-apply the voice per request first). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Updates LlamaCppDownloadService.ReleaseTag and all 17 LlamaCpp hash lists (12 archives, 2 cudart redistributables, 5 unpacked llama-server binaries). Archive hashes come from GitHub's per-asset digest field and were re-verified by downloading and hashing each of the ten engine archives. The llama-server hashes were extracted from those archives. Both cudart-*.zip redistributables are still byte-identical to earlier releases (GitHub digest matches the committed hash), so those lists repeat the previous hash. The "one executable key per OS" invariant still holds at b10760: the Windows CPU/Vulkan/CUDA 12.4/CUDA 13.3 builds ship the same llama-server.exe, and the Linux x64 and ARM64 CPU/Vulkan builds each ship the same llama-server. Both macOS llama-server binaries are byte-identical to b10625, so those two lists repeat the previous hash with a note. Smoke-tested the real b10760 macOS ARM64 llama-server with the flags LlamaCppServerManager passes: TranslateGemma 4B (--no-jinja --chat-template gemma) produces the same Danish translation as b10625, and Qwen3.5 4B (--reasoning off) returns a well-formed json_schema response. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…leedit-issue-14425-4a4a0b TTS: per-line voice cloning on the audio.cpp engines (IndexTTS 2.5, Higgs, Fish)
…cpp-latest-8fa65e Update llama.cpp to b10760
…ing consent dialog VoiceCloningConsent.IsCloneVoice is a hand-kept list of every cloning engine's voice type, matched on a non-empty FilePath. Confucius4TtsVoice and PocketTtsVoice were never added, so a reference imported for either engine - or a WAV dropped into its voices folder by hand - was synthesised without the consent dialog ever showing at Generate/Test voice. Import still asked; synthesis did not. Add both types with the same rule, and add a catalog-driven test that walks TtsEngineCatalog.CreateVoiceCloningEngines() and asserts each engine's clone voice type is recognised, so the next cloning engine cannot ship with the same gap. Also add the two types (and DotsTtsVoice, which the switch already had but the theory data did not) to the existing clone/preset theory data. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…clone-consent-confucius4-pocket Gate Confucius4-TTS and Pocket TTS clone voices behind the voice-cloning consent dialog
…e menu A recent-video entry is only validated when clicked, so a moved or deleted file now drops out of the list (and is saved) instead of staying there behind an error box. The error uses the existing "X not found" string. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The set-end-at-key-up guard only rewrites the end once the playhead is 100 ms past the new start, so a tap shorter than that keeps the old end. Assigning StartTime alone leaves the end in place, which inverts a line whose old end lies before the new start. Move the whole line instead, matching SE4's SetStartTime(adjustEndTime: true). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… timing filter The zero/negative-duration filter does not change what libass shows: a zero-duration event never matches the render window, and libass parses the negative time codes SE writes as real negative times, so a line from -0.5 s to 1.5 s already shows from 0 s. Verified by rendering through mpv 0.17.4/libass. The clamp and the removal are dropped. The dedupe stays: the 1.001 stretch the mpv and VLC reloaders each carried now lives in SmptePreviewStretch, and the secondary subtitle gets it too - on a copy, since it is shared between refreshes. Without SMPTE the secondary paragraphs are still added by reference, so the normal refresh allocates nothing extra. VlcReloader gets its deep copy back: RefreshVlc receives the live GetUpdateSubtitle() instance, and mutating that stretched or filtered the working subtitle on every refresh. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…cent-video-menu feat(video): add open recent video menu option
…-set-end-at-keyup fix(shortcut): set end time and advance to next subtitle on key up
…e-preview Share the SMPTE preview stretch between the mpv and VLC reloaders and the secondary subtitle
…ffmpeg is missing Waveform extraction was the one ffmpeg consumer that bailed out silently when ffmpeg was not installed, leaving an empty waveform with no hint, status line or dialog (SubtitleEdit#14390). Every other ffmpeg feature already goes through RequireFfmpegOk. - Auto-generate on video open: when ffmpeg is missing, show the existing click-to-generate hint over the empty waveform plus a status-bar line, instead of a modal (video opens also run from session restore, drag and drop and the command line). - Clicking the empty waveform now runs RequireFfmpegOk first, so the user gets the "Download FFmpeg?" prompt on Windows/macOS and extraction starts right after the download. - Linux (no ffmpeg download): RequireFfmpegOk now shows a message saying ffmpeg was not found and how to install/configure it, instead of returning false silently. Fixes SubtitleEdit#14390 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…-install-prompt-waveform-5856fe Waveform: offer the ffmpeg download instead of failing silently when ffmpeg is missing
Zonos is not language-agnostic: CrispASR's zonos-tts backend phonemises the input with eSpeak in whatever language it was started with and defaults to en-us. SE never sent one, so every non-English line was read with English G2P (the "Language ?" half of SubtitleEdit#14433). The main window now shows the language combo for Zonos, populated from the GGUF's own zonos.language_codes table (eSpeak codes, English (US) leading as the backend default - there is no honest "Auto", the model cannot detect a language). The pick is persisted like the other CrispASR engines and passed as the server's startup -l flag; a language change restarts the server the same way a voice change already does, because the zonos backend reads -l once at init. Verified against v0.8.31: the request body's `language` / `target_lang` fields leave zonos on en-us, while -l cs / fr-fr / cmn all reach G2P. Czech sample through parakeet v3: "Dobri Dentoto G Okushka Chaskahol Jessica" before, "Dobrý den tohoto je skôska Českého jazyka" after. Fixes the Zonos part of SubtitleEdit#14433. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…14434) With "Allow edit of original subtitle" on, splitting a line split the translation and the timing but copied the complete original text onto both halves, so every split desynchronized the two columns. SplitManager now takes an optional OriginalSplit and runs the same text split (line break, weighted line count, auto-break, dialog dashes, continuation style, tag balancing) on OriginalText. "Split at cursor" takes the caret from whichever text box has focus: the original box splits the original at its caret and auto-splits the translation, and the translation box does the reverse, so the two languages never share one split position. Read-only references are left alone - the file is authoritative there and the sticky refresh re-syncs them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…leedit-issue-14433-52730d TTS: language picker for Zonos (CrispASR)
…leedit-issue-14434-0c8d3b Split the editable original along with the translation (SubtitleEdit#14434)
…titleEdit#14421) ApplySettings rebuilds the layout, and the fresh subtitle grid silently auto-selects row 0. Re-filling the subtitle format list afterwards fired the format-combo handler several times, each reading that row 0 back and scrolling to it - and because the scroll helper keeps one pending index (last writer wins), the restore SetLayout had posted for the user's row was overridden. Apply, and OK after Apply, jumped to the first line. - ApplySettings captures the selected index up front and restores it at the very end, after the format list refresh; the extra restore in the settings command is folded into it. - The format-combo handler only re-selects a row when the grid's selection is the view model's, so a silent row-0 auto-pick can no longer hijack a pending scroll. - Headless regression test. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ubtitleEdit#14434) With "show all original lines" and an editable original, merging two rows merged their original texts but then brought the second row's original line back as an extra display-only row under the merged line, showing that text twice. WithoutReferenceOnlyRows refreshed the reference against the capture taken before the merge, where that line was still its own paragraph. Capture the original from the rows again before the refresh. For an editable original the merged line is consumed, not orphaned; a read-only original is never captured, so its lines still come back as before. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…merge-editable-original Merge with an editable original no longer resurrects the merged line (SubtitleEdit#14434)
…leedit-issue-14421-fce488 Settings: keep the selected line when settings are applied (SubtitleEdit#14421)
Every fix rule now declares the kind of fix it performs via a new FixType enum (Time, Formatting, Dialog, Punctuation, Casing, Spacing, Characters, Ocr), so the UI/CLI can group, filter or sort rules by category later. The member is abstract on the interface so the compiler enforces it on all 40 implementations. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TTVPifejhQqpeh1mkQHeRg
ivandrofly
marked this pull request as ready for review
September 2, 2026 19:51
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every fix rule now declares the kind of fix it performs via a new
FixType enum (Time, Formatting, Dialog, Punctuation, Casing, Spacing,
Characters, Ocr), so the UI/CLI can group, filter or sort rules by
category later. The member is abstract on the interface so the
compiler enforces it on all 40 implementations.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01TTVPifejhQqpeh1mkQHeRg
Stack created with GitHub Stacks CLI • Give Feedback 💬