From d005f3273fc4038670427b2312f0eb966e26c749 Mon Sep 17 00:00:00 2001 From: niksedk Date: Tue, 10 Nov 2026 20:20:45 +0100 Subject: [PATCH] Add Qwen3-ForcedAligner-Ultra as a forced aligner option Ultra (17slever17, Apache-2.0) is Qwen3-ForcedAligner-0.6B fine-tuned on hand-corrected word boundaries. Same architecture and timestamp head, so CrispASR runs cstr's F16 GGUF through its existing Qwen3 aligner path. Offered next to the 0.6B aligner in Speech to text, Import plain text and Improve time codes; the 0.6B stays the default. Co-Authored-By: Claude Opus 5.5 --- .../ImproveTimeCodesAligners.cs | 4 +++- .../SpeechToText/Engines/ForcedAlignerOption.cs | 17 +++++++++++++++++ .../Video/SpeechToText/SpeechToTextViewModel.cs | 1 + .../ImproveTimeCodesAlignersTests.cs | 14 ++++++++++++-- 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/ui/Features/Tools/ImproveTimeCodes/ImproveTimeCodesAligners.cs b/src/ui/Features/Tools/ImproveTimeCodes/ImproveTimeCodesAligners.cs index 70f762d8e38..ecce932dc3a 100644 --- a/src/ui/Features/Tools/ImproveTimeCodes/ImproveTimeCodesAligners.cs +++ b/src/ui/Features/Tools/ImproveTimeCodes/ImproveTimeCodesAligners.cs @@ -84,7 +84,9 @@ double Score(ForcedAlignerOption option) return CanaryLanguages.Contains(language) || language.Length == 0 ? 1 : 3; } - if (option.Choice == ForcedAlignerOption.Qwen3Choice) + // Ultra is a fine-tune of the same model with the same languages. Not yet measured on + // re-timing windows, so it ties with Qwen3 and stays behind it (stable sort). + if (option.Choice is ForcedAlignerOption.Qwen3Choice or ForcedAlignerOption.Qwen3UltraChoice) { return Qwen3Languages.Contains(language) ? 2 : 4; } diff --git a/src/ui/Features/Video/SpeechToText/Engines/ForcedAlignerOption.cs b/src/ui/Features/Video/SpeechToText/Engines/ForcedAlignerOption.cs index 017c61e63b0..f9f0f07fd3e 100644 --- a/src/ui/Features/Video/SpeechToText/Engines/ForcedAlignerOption.cs +++ b/src/ui/Features/Video/SpeechToText/Engines/ForcedAlignerOption.cs @@ -9,6 +9,7 @@ public class ForcedAlignerOption public const string BuiltInChoice = "built-in"; public const string CanaryCtcChoice = "canary-ctc"; public const string Qwen3Choice = "qwen3-forced"; + public const string Qwen3UltraChoice = "qwen3-forced-ultra"; // wav2vec2 forced aligner choices. The choice string mirrors CrispASR's // `-am wav2vec2-aligner-` alias so the persisted setting is self- @@ -78,6 +79,20 @@ public static ForcedAlignerOption Qwen3() => "https://huggingface.co/cstr/qwen3-forced-aligner-0.6b-GGUF/resolve/main/qwen3-forced-aligner-0.6b-q8_0.gguf", "986 MB"); + /// + /// Qwen3-ForcedAligner-Ultra (17slever17, Apache-2.0): the 0.6B aligner fine-tuned on + /// hand-corrected word boundaries. Same architecture and 5000-class timestamp head, so + /// CrispASR runs it through its Qwen3 aligner path; the file name must keep + /// "forced-aligner" for CrispASR to pick that path. Only an F16 conversion exists, and it + /// leaves out the optional local boundary-refinement head. + /// + public static ForcedAlignerOption Qwen3Ultra() => + new("Qwen3 forced aligner Ultra", + Qwen3UltraChoice, + "qwen3-forced-aligner-ultra-f16.gguf", + "https://huggingface.co/cstr/qwen3-forced-aligner-ultra-GGUF/resolve/main/qwen3-forced-aligner-ultra-f16.gguf", + "1.84 GB"); + /// /// 12 language-specific wav2vec2 CTC forced aligners shipped by CrispASR /// ("WhisperX aligner zoo"). Same Q4_K-quantized GGUFs that WhisperX itself @@ -199,6 +214,7 @@ public static IReadOnlyList All() BuiltIn(), CanaryCtc(), Qwen3(), + Qwen3Ultra(), }; list.AddRange(Wav2Vec2All()); return list; @@ -220,6 +236,7 @@ public static ForcedAlignerOption FromChoice(string? choice) { CanaryCtcChoice => CanaryCtc(), Qwen3Choice => Qwen3(), + Qwen3UltraChoice => Qwen3Ultra(), _ => BuiltIn(), }; } diff --git a/src/ui/Features/Video/SpeechToText/SpeechToTextViewModel.cs b/src/ui/Features/Video/SpeechToText/SpeechToTextViewModel.cs index d73efa5b1c5..2ad01f94afb 100644 --- a/src/ui/Features/Video/SpeechToText/SpeechToTextViewModel.cs +++ b/src/ui/Features/Video/SpeechToText/SpeechToTextViewModel.cs @@ -637,6 +637,7 @@ private void UpdateForcedAlignerUi() } newOptions.Add(ForcedAlignerOption.CanaryCtc()); newOptions.Add(ForcedAlignerOption.Qwen3()); + newOptions.Add(ForcedAlignerOption.Qwen3Ultra()); // wav2vec2 "WhisperX aligner zoo" — 12 language-specific CTC aligners // that work on top of any Crisp ASR backend via `-am `. newOptions.AddRange(ForcedAlignerOption.Wav2Vec2All()); diff --git a/tests/UI/Features/Tools/ImproveTimeCodes/ImproveTimeCodesAlignersTests.cs b/tests/UI/Features/Tools/ImproveTimeCodes/ImproveTimeCodesAlignersTests.cs index 126646e8f43..6968aa497d6 100644 --- a/tests/UI/Features/Tools/ImproveTimeCodes/ImproveTimeCodesAlignersTests.cs +++ b/tests/UI/Features/Tools/ImproveTimeCodes/ImproveTimeCodesAlignersTests.cs @@ -9,8 +9,8 @@ public class ImproveTimeCodesAlignersTests [InlineData("en", ForcedAlignerOption.CanaryCtcChoice, ForcedAlignerOption.Wav2Vec2EnChoice, ForcedAlignerOption.Qwen3Choice)] [InlineData("de", ForcedAlignerOption.Wav2Vec2DeChoice, ForcedAlignerOption.CanaryCtcChoice, ForcedAlignerOption.Qwen3Choice)] [InlineData("da", ForcedAlignerOption.CanaryCtcChoice, ForcedAlignerOption.Qwen3Choice, null)] - [InlineData("ja", ForcedAlignerOption.Wav2Vec2JaChoice, ForcedAlignerOption.Qwen3Choice, ForcedAlignerOption.CanaryCtcChoice)] - [InlineData("ko", ForcedAlignerOption.Qwen3Choice, ForcedAlignerOption.CanaryCtcChoice, null)] + [InlineData("ja", ForcedAlignerOption.Wav2Vec2JaChoice, ForcedAlignerOption.Qwen3Choice, ForcedAlignerOption.Qwen3UltraChoice)] + [InlineData("ko", ForcedAlignerOption.Qwen3Choice, ForcedAlignerOption.Qwen3UltraChoice, ForcedAlignerOption.CanaryCtcChoice)] [InlineData("", ForcedAlignerOption.CanaryCtcChoice, ForcedAlignerOption.Qwen3Choice, null)] public void Rank_PutsTheBestAlignersForTheLanguageFirst(string language, string first, string second, string? third) { @@ -34,6 +34,16 @@ public void Rank_OffersEveryAlignerOnce_AndNeverTheBuiltInOne() Assert.DoesNotContain(ranked, o => o.IsBuiltIn); } + [Fact] + public void Qwen3Ultra_RoundTripsThroughItsChoice_AndKeepsTheAlignerFileName() + { + var option = ForcedAlignerOption.FromChoice(ForcedAlignerOption.Qwen3UltraChoice); + + Assert.Equal(ForcedAlignerOption.Qwen3UltraChoice, option.Choice); + // CrispASR selects its Qwen3 aligner path from "forced-aligner" in the file name. + Assert.Contains("forced-aligner", option.FileName); + } + [Theory] [InlineData("en", "phonon2-q8_0.gguf")] [InlineData("EN", "phonon2-q8_0.gguf")]