Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
17 changes: 17 additions & 0 deletions src/ui/Features/Video/SpeechToText/Engines/ForcedAlignerOption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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-<code>` alias so the persisted setting is self-
Expand Down Expand Up @@ -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");

/// <summary>
/// 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.
/// </summary>
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");

/// <summary>
/// 12 language-specific wav2vec2 CTC forced aligners shipped by CrispASR
/// ("WhisperX aligner zoo"). Same Q4_K-quantized GGUFs that WhisperX itself
Expand Down Expand Up @@ -199,6 +214,7 @@ public static IReadOnlyList<ForcedAlignerOption> All()
BuiltIn(),
CanaryCtc(),
Qwen3(),
Qwen3Ultra(),
};
list.AddRange(Wav2Vec2All());
return list;
Expand All @@ -220,6 +236,7 @@ public static ForcedAlignerOption FromChoice(string? choice)
{
CanaryCtcChoice => CanaryCtc(),
Qwen3Choice => Qwen3(),
Qwen3UltraChoice => Qwen3Ultra(),
_ => BuiltIn(),
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <path>`.
newOptions.AddRange(ForcedAlignerOption.Wav2Vec2All());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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")]
Expand Down
Loading