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";