This commit is contained in:
niksedk 2023-06-10 10:04:10 +02:00
parent 462707ea33
commit 77344c4e85
5 changed files with 471 additions and 0 deletions

View File

@ -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);
}
}
}

View File

@ -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()
{

View File

@ -64,6 +64,7 @@
<Compile Include="Assa\AssaTimeCodes.cs" />
<Compile Include="Core\AudioToTextTest.cs" />
<Compile Include="Core\CsvUtilTest.cs" />
<Compile Include="Core\WebVttToAssaTest.cs" />
<Compile Include="Dictionaries\StringWithoutSpaceSplitToWordsTest.cs" />
<Compile Include="GoogleCloudVision\GoogleCloudVisionJsonToLinesTest.cs" />
<Compile Include="LanguageFiles\LanguageFileTest.cs" />

View File

@ -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("<c\\.[a-z-_\\.A-Z#\\d]+>", 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<SsaStyle> 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("<i>", "{\\i1}")
.Replace("</i>", "{\\i0}")
.Replace("<b>", "{\\b1}")
.Replace("</b>", "{\\b0}")
.Replace("<u>", "{\\u1}")
.Replace("</u>", "{\\u0}").Trim();
var matches = LineTagRegex.Matches(paragraph.Text);
if (matches.Count == 1 &&
paragraph.Text.StartsWith("<c.", StringComparison.Ordinal) &&
paragraph.Text.EndsWith("</c>", 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<SsaStyle> ssaStyles)
{
throw new NotImplementedException();
}
private static List<SsaStyle> ConvertStyles(List<WebVttStyle> styles, SsaStyle defaultStyle)
{
var result = new List<SsaStyle>();
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<WebVttStyle> GetStyles(Subtitle webVttSubtitle)
{
if (string.IsNullOrEmpty(webVttSubtitle.Header))
{
return new List<WebVttStyle>();
}
var cueOn = false;
var styleOn = false;
var result = new List<WebVttStyle>();
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<WebVttStyle> 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;
}
}
}
}

View File

@ -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)