diff --git a/src/ui/Features/Assa/AssaApplyAdvancedEffect/AssaApplyAdvancedEffectViewModel.cs b/src/ui/Features/Assa/AssaApplyAdvancedEffect/AssaApplyAdvancedEffectViewModel.cs
index a4c8e6fe9d5..db8444f909f 100644
--- a/src/ui/Features/Assa/AssaApplyAdvancedEffect/AssaApplyAdvancedEffectViewModel.cs
+++ b/src/ui/Features/Assa/AssaApplyAdvancedEffect/AssaApplyAdvancedEffectViewModel.cs
@@ -7,6 +7,7 @@
using Nikse.SubtitleEdit.Controls.VideoPlayer;
using Nikse.SubtitleEdit.Core.Common;
using Nikse.SubtitleEdit.Core.SubtitleFormats;
+using Nikse.SubtitleEdit.Features.Assa.AssaApplyAdvancedEffect.Effects;
using Nikse.SubtitleEdit.Features.Main;
using Nikse.SubtitleEdit.Features.Sync.VisualSync;
using Nikse.SubtitleEdit.Logic;
@@ -98,6 +99,23 @@ public void Initialize(
? Paragraphs.IndexOf(Paragraphs.First(p => p.Subtitle.Id == _selectedSubtitleLines[0].Id))
: 0;
+ // Default the karaoke effects to right-to-left when the subtitle text is a right-to-left
+ // language (Hebrew, Arabic, ...) so the highlight progresses in reading order. (#12271)
+ if (paragraphs.Any(p => LanguageAutoDetect.ContainsRightToLeftLetter(p.Text)))
+ {
+ foreach (var effect in OverrideTags)
+ {
+ if (effect is AdvancedEffectKaraoke karaoke)
+ {
+ karaoke.RightToLeft = true;
+ }
+ else if (effect is AdvancedEffectFancyKaraoke fancyKaraoke)
+ {
+ fancyKaraoke.RightToLeft = true;
+ }
+ }
+ }
+
if (audioVisualizer == null || audioVisualizer.WavePeaks == null || audioVisualizer.WavePeaks.Peaks.Count == 0)
{
// remove audio visualizer related effects if no audio visualizer data is available
diff --git a/src/ui/Features/Assa/AssaApplyAdvancedEffect/AssaApplyAdvancedEffectWindow.cs b/src/ui/Features/Assa/AssaApplyAdvancedEffect/AssaApplyAdvancedEffectWindow.cs
index d96579f3e1f..bcd3ba8ade9 100644
--- a/src/ui/Features/Assa/AssaApplyAdvancedEffect/AssaApplyAdvancedEffectWindow.cs
+++ b/src/ui/Features/Assa/AssaApplyAdvancedEffect/AssaApplyAdvancedEffectWindow.cs
@@ -75,12 +75,23 @@ public AssaApplyAdvancedEffectWindow(AssaApplyAdvancedEffectViewModel vm)
settingsStack.Children.Add(speedRow);
panel.Children.Add(settingsStack);
}
+ else if (item is AdvancedEffectKaraoke karaokeItem)
+ {
+ var rightToLeftRow = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8, Margin = new Thickness(0, 6, 0, 0) };
+ rightToLeftRow.Children.Add(new TextBlock { Text = Se.Language.Assa.AdvancedEffectKaraokeRightToLeft, VerticalAlignment = VerticalAlignment.Center, FontSize = 12 });
+ rightToLeftRow.Children.Add(UiUtil.MakeCheckBox(karaokeItem, nameof(AdvancedEffectKaraoke.RightToLeft)));
+ panel.Children.Add(rightToLeftRow);
+ }
else if (item is AdvancedEffectFancyKaraoke fancyKaraokeItem)
{
var autoDetectActiveWordRow = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8, Margin = new Thickness(0, 6, 0, 0) };
autoDetectActiveWordRow.Children.Add(new TextBlock { Text = Se.Language.Assa.AdvancedEffectFancyKaraokeAutoDetectActiveWord, VerticalAlignment = VerticalAlignment.Center, FontSize = 12 });
autoDetectActiveWordRow.Children.Add(UiUtil.MakeCheckBox(fancyKaraokeItem, nameof(AdvancedEffectFancyKaraoke.AutoDetectActiveWord)));
+ var rightToLeftRow = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8, Margin = new Thickness(0, 4, 0, 0) };
+ rightToLeftRow.Children.Add(new TextBlock { Text = Se.Language.Assa.AdvancedEffectKaraokeRightToLeft, VerticalAlignment = VerticalAlignment.Center, FontSize = 12 });
+ rightToLeftRow.Children.Add(UiUtil.MakeCheckBox(fancyKaraokeItem, nameof(AdvancedEffectFancyKaraoke.RightToLeft)));
+
var activeGlowRow = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8, Margin = new Thickness(0, 4, 0, 0) };
activeGlowRow.Children.Add(new TextBlock { Text = Se.Language.Assa.AdvancedEffectFancyKaraokeGlow, VerticalAlignment = VerticalAlignment.Center, FontSize = 12 });
var applyGlowCheckBox = UiUtil.MakeCheckBox(fancyKaraokeItem, nameof(AdvancedEffectFancyKaraoke.ApplyGlow));
@@ -100,6 +111,7 @@ public AssaApplyAdvancedEffectWindow(AssaApplyAdvancedEffectViewModel vm)
var settingsStack = new StackPanel { Spacing = 2 };
settingsStack.Children.Add(autoDetectActiveWordRow);
+ settingsStack.Children.Add(rightToLeftRow);
settingsStack.Children.Add(activeGlowRow);
settingsStack.Children.Add(activeColorRow);
settingsStack.Children.Add(inactiveColorRow);
diff --git a/src/ui/Features/Assa/AssaApplyAdvancedEffect/Effects/AdvancedEffectFancyKaraoke.cs b/src/ui/Features/Assa/AssaApplyAdvancedEffect/Effects/AdvancedEffectFancyKaraoke.cs
index 22e06d5f100..a53c2a7cb5b 100644
--- a/src/ui/Features/Assa/AssaApplyAdvancedEffect/Effects/AdvancedEffectFancyKaraoke.cs
+++ b/src/ui/Features/Assa/AssaApplyAdvancedEffect/Effects/AdvancedEffectFancyKaraoke.cs
@@ -34,6 +34,13 @@ public class AdvancedEffectFancyKaraoke : IAdvancedEffectDisplay
///
public bool AutoDetectActiveWord { get; set; } = false;
+ ///
+ /// When true (and auto-sequencing by words), advance the active word from the end of the line
+ /// towards the start so the highlight progresses visually right-to-left. Auto-enabled for
+ /// right-to-left languages (Hebrew, Arabic, ...) where the default order runs backwards. (#12271)
+ ///
+ public bool RightToLeft { get; set; }
+
///
/// Glow color used for the active word's 3c (ASS BGR).
///
@@ -203,6 +210,10 @@ public List ApplyEffect(
sb.Append(posTags);
}
+ // The active word for this step. In right-to-left mode advance from the last word
+ // towards the first so the highlight moves right-to-left visually.
+ var activeWordIndex = RightToLeft ? wordCount - 1 - w : w;
+
string? currentColor = null;
int localWordCounter = 0;
@@ -248,7 +259,7 @@ public List ApplyEffect(
// Non-whitespace token = a word/punctuation group
// Only the current word is active
- var colorTag = localWordCounter == w ? activeTags : inactiveTags;
+ var colorTag = localWordCounter == activeWordIndex ? activeTags : inactiveTags;
if (colorTag != currentColor)
{
sb.Append(colorTag);
diff --git a/src/ui/Features/Assa/AssaApplyAdvancedEffect/Effects/AdvancedEffectKaraoke.cs b/src/ui/Features/Assa/AssaApplyAdvancedEffect/Effects/AdvancedEffectKaraoke.cs
index 852f4915cae..ed69d48f80c 100644
--- a/src/ui/Features/Assa/AssaApplyAdvancedEffect/Effects/AdvancedEffectKaraoke.cs
+++ b/src/ui/Features/Assa/AssaApplyAdvancedEffect/Effects/AdvancedEffectKaraoke.cs
@@ -22,6 +22,13 @@ public class AdvancedEffectKaraoke : IAdvancedEffectDisplay
private const string HighlightColor = @"{\1c&HFFFFFF&}";
private const string DimColor = @"{\1c&H808080&}";
+ ///
+ /// When true, reveal the characters from the end of the text towards the start so the
+ /// highlight progresses visually right-to-left. Auto-enabled for right-to-left languages
+ /// (Hebrew, Arabic, ...) where the default left-to-right reveal runs backwards. (#12271)
+ ///
+ public bool RightToLeft { get; set; }
+
public override string ToString() => Name;
public List ApplyEffect(string header, List subtitles, int width, int height, WavePeakData2? wavePeaks)
@@ -53,18 +60,22 @@ public List ApplyEffect(string header, List= revealFromEnd : j <= step;
+ var color = highlight ? HighlightColor : DimColor;
if (color != currentColor)
{
sb.Append(color);
diff --git a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs
index 931c420e7b2..6adfc6db300 100644
--- a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs
+++ b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs
@@ -188,6 +188,7 @@ public class LanguageAssa
public string AdvancedEffectHeartsDescription { get; set; }
public string AdvancedEffectWordSpacing { get; set; }
public string AdvancedEffectWordSpacingDescription { get; set; }
+ public string AdvancedEffectKaraokeRightToLeft { get; set; }
public string AdvancedEffectFancyKaraokeAutoDetectActiveWord { get; set; }
public string AdvancedEffectFancyKaraokeGlow { get; set; }
public string AdvancedEffectFancyKaraokeActiveColor { get; set; }
@@ -401,6 +402,7 @@ public LanguageAssa()
AdvancedEffectHeartsDescription = "Bezier-drawn hearts in three shapes rain gently from the top of the screen, tumbling and fading throughout each subtitle";
AdvancedEffectWordSpacing = "Word spacing";
AdvancedEffectWordSpacingDescription = "Increases spacing between words using the \\fsp tag for better readability";
+ AdvancedEffectKaraokeRightToLeft = "Right-to-left";
AdvancedEffectFancyKaraokeAutoDetectActiveWord = "Auto-detect active word";
AdvancedEffectFancyKaraokeGlow = "Active word glow";
AdvancedEffectFancyKaraokeActiveColor = "Active color";