From 77344c4e8528a9f428d2699e6edc998c1eaf1b58 Mon Sep 17 00:00:00 2001 From: niksedk Date: Sat, 10 Jun 2023 10:04:10 +0200 Subject: [PATCH] Work on #7007 --- src/Test/Core/WebVttToAssaTest.cs | 62 +++ .../Forms/RemoveTextForHearImpairedTest.cs | 18 + src/Test/Test.csproj | 1 + src/libse/Common/WebVttToAssa.cs | 359 ++++++++++++++++++ src/libse/Forms/RemoveInterjection.cs | 31 ++ 5 files changed, 471 insertions(+) create mode 100644 src/Test/Core/WebVttToAssaTest.cs create mode 100644 src/libse/Common/WebVttToAssa.cs diff --git a/src/Test/Core/WebVttToAssaTest.cs b/src/Test/Core/WebVttToAssaTest.cs new file mode 100644 index 000000000..75fa78c92 --- /dev/null +++ b/src/Test/Core/WebVttToAssaTest.cs @@ -0,0 +1,62 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Nikse.SubtitleEdit.Core.Common; +using Nikse.SubtitleEdit.Core.SubtitleFormats; + +namespace Test.Core +{ + [TestClass] + public class WebVttToAssaTest + { + [TestMethod] + public void TestStyles1() + { + var subtitle = new Subtitle(); + subtitle.Header = "WEBVTT\r\n\r\nSTYLE\r\n::cue(.background-color_transparent) {\r\n background-color: rgba(255,255,255,0.0);\r\n}\r\n::cue(.color_EBEBEB) {\r\n color: rgba(235,235,235,1.000000);\r\n}\r\n::cue(.font-family_Arial) {\r\n font-family: Arial;\r\n}\r\n::cue(.font-style_normal) {\r\n font-style: normal;\r\n}\r\n::cue(.font-weight_normal) {\r\n font-weight: normal;\r\n}\r\n::cue(.text-shadow_#101010-3px) {\r\n text-shadow: #101010 3px;\r\n}\r\n::cue(.font-style_italic) {\r\n font-style: italic;\r\n}"; + var converted = WebVttToAssa.Convert(subtitle, new SsaStyle(), 1920, 1080); + var styles = AdvancedSubStationAlpha.GetSsaStylesFromHeader(converted.Header); + + Assert.AreEqual(".background-color_transparent", styles[0].Name); + Assert.AreEqual(".color_EBEBEB", styles[1].Name); + Assert.AreEqual(".font-family_Arial", styles[2].Name); + Assert.AreEqual(".font-style_normal", styles[3].Name); + Assert.AreEqual(".font-weight_normal", styles[4].Name); + Assert.AreEqual(".text-shadow_#101010-3px", styles[5].Name); + Assert.AreEqual(".font-style_italic", styles[6].Name); + + Assert.AreEqual(235, styles[1].Primary.R); + Assert.AreEqual("Arial", styles[2].FontName); + Assert.AreEqual(false, styles[3].Italic); + Assert.AreEqual(false, styles[4].Bold); + Assert.AreEqual(3, styles[5].ShadowWidth); + Assert.AreEqual(true, styles[6].Italic); + } + + [TestMethod] + public void TestStyles2() + { + var subtitle = new Subtitle(); + subtitle.Header = "STYLE\r\n::cue(.styledotEAC118) { color:#EAC118 }\r\n::cue(.styledotaqua) { color:aqua }\r\n::cue(.styledotaquadotitalic) { color:aqua;font-style:italic }\r\n::cue(.styledotitalic) { font-style:italic }\r\n::cue(.styledotEAC118dotitalic) { color:#EAC118;font-style:italic }"; + var converted = WebVttToAssa.Convert(subtitle, new SsaStyle(), 1920, 1080); + var styles = AdvancedSubStationAlpha.GetSsaStylesFromHeader(converted.Header); + + Assert.AreEqual(".styledotEAC118", styles[0].Name); + Assert.AreEqual(".styledotaqua", styles[1].Name); + Assert.AreEqual(".styledotaquadotitalic", styles[2].Name); + Assert.AreEqual(".styledotitalic", styles[3].Name); + Assert.AreEqual(".styledotEAC118dotitalic", styles[4].Name); + + Assert.AreEqual(234, styles[0].Primary.R); + Assert.AreEqual(193, styles[0].Primary.G); + Assert.AreEqual(24, styles[0].Primary.B); + Assert.AreEqual(0, styles[1].Primary.R); + Assert.AreEqual(255, styles[1].Primary.G); + Assert.AreEqual(255, styles[1].Primary.B); + Assert.AreEqual(true, styles[2].Italic); + Assert.AreEqual(true, styles[3].Italic); + Assert.AreEqual(true, styles[4].Italic); + Assert.AreEqual(234, styles[4].Primary.R); + Assert.AreEqual(193, styles[4].Primary.G); + Assert.AreEqual(24, styles[4].Primary.B); + } + } +} diff --git a/src/Test/Logic/Forms/RemoveTextForHearImpairedTest.cs b/src/Test/Logic/Forms/RemoveTextForHearImpairedTest.cs index 1f8416b2a..95b2ba5d5 100644 --- a/src/Test/Logic/Forms/RemoveTextForHearImpairedTest.cs +++ b/src/Test/Logic/Forms/RemoveTextForHearImpairedTest.cs @@ -658,6 +658,24 @@ namespace Test.Logic.Forms Assert.AreEqual(string.Empty, actual); } + [TestMethod] + public void RemoveInterjections19() + { + var text = $"- ¡Hm!{Environment.NewLine}- Increíble, ¿verdad?"; + var settings = GetRemoveInterjectionContext(text, true); + var actual = new RemoveInterjection().Invoke(settings); + Assert.AreEqual("Increíble, ¿verdad?", actual); + } + + [TestMethod] + public void RemoveInterjections19B() + { + var text = $"- ¿Hm?{Environment.NewLine}- Increíble, ¿verdad?"; + var settings = GetRemoveInterjectionContext(text, true); + var actual = new RemoveInterjection().Invoke(settings); + Assert.AreEqual("Increíble, ¿verdad?", actual); + } + [TestMethod] public void RemoveColonOnlyOnSeparateLine() { diff --git a/src/Test/Test.csproj b/src/Test/Test.csproj index 2cd449ab2..33b476785 100644 --- a/src/Test/Test.csproj +++ b/src/Test/Test.csproj @@ -64,6 +64,7 @@ + diff --git a/src/libse/Common/WebVttToAssa.cs b/src/libse/Common/WebVttToAssa.cs new file mode 100644 index 000000000..10e9680f0 --- /dev/null +++ b/src/libse/Common/WebVttToAssa.cs @@ -0,0 +1,359 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Nikse.SubtitleEdit.Core.SubtitleFormats; + +namespace Nikse.SubtitleEdit.Core.Common +{ + public static class WebVttToAssa + { + private static readonly Regex NameRegex = new Regex("\\([\\.a-zA-Z\\d#_-]+\\)", RegexOptions.Compiled); + private static readonly Regex PropertiesRegex = new Regex("{[ \\.a-zA-Z\\d:#\\s,_;:\\-\\(\\)]+}", RegexOptions.Compiled); + private static readonly Regex LineTagRegex = new Regex("", RegexOptions.Compiled); + + public static Subtitle Convert(Subtitle webVttSubtitle, SsaStyle defaultStyle, int videoWidth, int videoHeight) + { + var styles = GetStyles(webVttSubtitle); + var ssaStyles = ConvertStyles(styles, defaultStyle); + var header = AdvancedSubStationAlpha.GetHeaderAndStylesFromAdvancedSubStationAlpha(AdvancedSubStationAlpha.DefaultHeader, ssaStyles); + var assaSubtitle = ConvertSubtitle(webVttSubtitle, header, ssaStyles); + return assaSubtitle; + } + + private static Subtitle ConvertSubtitle(Subtitle webVttSubtitle, string header, List ssaStyles) + { + var assaSubtitle = new Subtitle(webVttSubtitle) { Header = header }; + var layer = 0; + foreach (var paragraph in assaSubtitle.Paragraphs) + { + paragraph.Layer = layer; + layer++; + + paragraph.Text = paragraph.Text + .Replace("", "{\\i1}") + .Replace("", "{\\i0}") + .Replace("", "{\\b1}") + .Replace("", "{\\b0}") + .Replace("", "{\\u1}") + .Replace("", "{\\u0}").Trim(); + + var matches = LineTagRegex.Matches(paragraph.Text); + if (matches.Count == 1 && + paragraph.Text.StartsWith("", StringComparison.Ordinal)) + { + var tag = matches[0].Value.Trim('<', '>', ' '); + if (ssaStyles.Any(p => p.Name == tag)) + { + paragraph.Extra = tag; + } + else + { + paragraph.Text = SetInlineStyles(paragraph.Text, tag, ssaStyles); + } + } + } + + return assaSubtitle; + } + + private static string SetInlineStyles(string paragraphText, string tag, List ssaStyles) + { + throw new NotImplementedException(); + } + + private static List ConvertStyles(List styles, SsaStyle defaultStyle) + { + var result = new List(); + foreach (var style in styles) + { + result.Add(new SsaStyle(new SsaStyle + { + Name = style.Name, + FontName = style.FontName ?? defaultStyle.FontName, + FontSize = style.FontSize ?? defaultStyle.FontSize, + Primary = style.Color ?? defaultStyle.Primary, + Background = style.BackgroundColor ?? defaultStyle.Background, + Bold = style.Bold ?? defaultStyle.Bold, + Italic = style.Italic ?? defaultStyle.Italic, + ShadowWidth = style.ShadowWidth ?? defaultStyle.ShadowWidth, + OutlineWidth = style.ShadowWidth ?? defaultStyle.OutlineWidth, + Outline = style.ShadowColor ?? defaultStyle.Outline, + })); + } + + return result; + } + + public class WebVttStyle + { + public string Name { get; set; } + public string FontName { get; set; } + public decimal? FontSize { get; set; } + public Color? Color { get; set; } + public Color? BackgroundColor { get; set; } + public bool? Italic { get; set; } + public bool? Bold { get; set; } + public int? ShadowWidth { get; set; } + public Color? ShadowColor { get; set; } + } + + private static List GetStyles(Subtitle webVttSubtitle) + { + if (string.IsNullOrEmpty(webVttSubtitle.Header)) + { + return new List(); + } + + var cueOn = false; + var styleOn = false; + var result = new List(); + var currentStyle = new StringBuilder(); + foreach (var line in webVttSubtitle.Header.SplitToLines()) + { + var s = line.Trim(); + if (styleOn) + { + if (s == string.Empty) + { + styleOn = false; + AddStyle(result, currentStyle); + } + else + { + if (cueOn && s.StartsWith("::cue(", StringComparison.Ordinal)) + { + AddStyle(result, currentStyle); + currentStyle = new StringBuilder(); + } + + if (s.StartsWith("::cue(", StringComparison.Ordinal)) + { + currentStyle.AppendLine(s); + cueOn = true; + } + else if (cueOn) + { + currentStyle.AppendLine(s); + } + } + } + else if (s.Equals("STYLE", StringComparison.OrdinalIgnoreCase)) + { + styleOn = true; + } + } + + AddStyle(result, currentStyle); + + return result; + + // https://www.w3.org/TR/webvtt1/ + //STYLE + //::cue { + // background-image: linear-gradient(to bottom, dimgray, lightgray); + // color: papayawhip; + // } + // /* Style blocks cannot use blank lines nor "dash dash greater than" */ + + // NOTE comment blocks can be used between style blocks. + + // STYLE + // ::cue(b) { + // color: peachpuff; + // } + + } + + private static void AddStyle(List result, StringBuilder currentStyle) + { + var text = currentStyle + .ToString() + .Replace(Environment.NewLine, " "); + var match = NameRegex.Match(text); + if (!match.Success) + { + return; + } + + var name = match.Value.Trim('(',')',' '); + + match = PropertiesRegex.Match(text); + if (!match.Success || string.IsNullOrWhiteSpace(match.Value)) + { + return; + } + + var properties = match.Value + .Trim('{', '}', ' ') + .RemoveChar('\r', '\n') + .Split(';'); + + var webVttStyle = new WebVttStyle { Name = name }; + foreach (var prop in properties) + { + SetProperty(webVttStyle, prop); + } + + result.Add(webVttStyle); + } + + private static void SetProperty(WebVttStyle webVttStyle, string prop) + { + var arr = prop.Split(':'); + if (arr.Length != 2) + { + return; + } + + var name = arr[0].Trim(); + var value = arr[1].Trim(); + + if (string.IsNullOrEmpty(value)) + { + return; + } + + if (name == "color") + { + SetColor(webVttStyle, value); + } + else if (name == "background-color") + { + SetBackgroundColor(webVttStyle, value); + } + else if (name == "font-family") + { + webVttStyle.FontName = value; + } + else if (name == "font-style") + { + SetFontStyle(webVttStyle, value); + } + else if (name == "font-weight") + { + SetFontWeight(webVttStyle, value); + } + else if (name == "text-shadow") + { + SetTextShadow(webVttStyle, value); + } + } + + private static void SetColor(WebVttStyle webVttStyle, string value) + { + var color = GetColorFromString(value, Color.Transparent); + if (color == Color.Transparent) + { + return; + } + + webVttStyle.Color = color; + } + + private static Color GetColorFromString(string s, Color defaultColor) + { + try + { + if (s.StartsWith("rgb(", StringComparison.OrdinalIgnoreCase)) + { + var arr = s + .RemoveChar(' ') + .Remove(0, 4) + .TrimEnd(')') + .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + return Color.FromArgb(int.Parse(arr[0]), int.Parse(arr[1]), int.Parse(arr[2])); + } + + if (s.StartsWith("rgba(", StringComparison.OrdinalIgnoreCase)) + { + var arr = s + .RemoveChar(' ') + .Remove(0, 5) + .TrimEnd(')') + .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + var alpha = byte.MaxValue; + if (arr.Length == 4 && float.TryParse(arr[3], NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var f)) + { + if (f >= 0 && f < 1) + { + alpha = (byte)(f * byte.MaxValue); + } + } + + return Color.FromArgb(alpha, int.Parse(arr[0]), int.Parse(arr[1]), int.Parse(arr[2])); + } + + return ColorTranslator.FromHtml(s); + } + catch + { + return defaultColor; + } + } + + private static void SetBackgroundColor(WebVttStyle webVttStyle, string value) + { + var color = GetColorFromString(value, Color.Transparent); + if (color == Color.Transparent) + { + return; + } + + webVttStyle.BackgroundColor = color; + } + + private static void SetFontWeight(WebVttStyle webVttStyle, string value) + { + if (value == "bold" || value == "bolder") + { + webVttStyle.Bold = true; + } + else if (value == "normal") + { + webVttStyle.Bold = false; + } + } + + private static void SetFontStyle(WebVttStyle webVttStyle, string value) + { + if (value == "italic" || value == "oblique") + { + webVttStyle.Italic = true; + } + else if (value == "normal") + { + webVttStyle.Italic = false; + } + } + + private static void SetTextShadow(WebVttStyle webVttStyle, string value) + { + // text-shadow: #101010 3px; + + var arr = value.Split(); + if (arr.Length != 2) + { + return; + } + + var color = GetColorFromString(arr[0], Color.Transparent); + if (color == Color.Transparent) + { + return; + } + + if (int.TryParse(arr[1].Replace("px", string.Empty),out var number)) + { + webVttStyle.ShadowColor = color; + webVttStyle.ShadowWidth = number; + } + } + } +} diff --git a/src/libse/Forms/RemoveInterjection.cs b/src/libse/Forms/RemoveInterjection.cs index 2b3f23e25..de83e4fc4 100644 --- a/src/libse/Forms/RemoveInterjection.cs +++ b/src/libse/Forms/RemoveInterjection.cs @@ -182,6 +182,26 @@ namespace Nikse.SubtitleEdit.Core.Forms temp = temp.Remove(subIndex, 2); removeAfter = false; } + else if (subTemp == " ¡!") + { + temp = temp.Remove(subIndex, 3); + removeAfter = false; + } + else if (subTemp == " ¿?") + { + temp = temp.Remove(subIndex, 3); + removeAfter = false; + } + else if (index == 1 && temp.StartsWith("¿?" + Environment.NewLine, StringComparison.Ordinal)) + { + temp = temp.Remove(0, 2).TrimEnd(); + removeAfter = false; + } + else if (index == 1 && temp.StartsWith("¡!" + Environment.NewLine, StringComparison.Ordinal)) + { + temp = temp.Remove(0, 2).TrimEnd(); + removeAfter = false; + } else { subTemp = temp.Substring(subIndex); @@ -237,6 +257,17 @@ namespace Nikse.SubtitleEdit.Core.Forms } } + if (index == 1 && temp.StartsWith("¿?")) + { + removeAfter = false; + temp = temp.Remove(0, 2); + } + else if (index == 1 && temp.StartsWith("¡!")) + { + removeAfter = false; + temp = temp.Remove(0, 2); + } + if (removeAfter) { if (index == 0)