using System; using System.Collections.Generic; using System.Drawing; using System.Globalization; using System.Linq; using System.Text; using System.Xml; namespace Nikse.SubtitleEdit.Core.SubtitleFormats { public class AdvancedSubStationAlpha : SubtitleFormat { public string Errors { get; private set; } public static string DefaultStyle { get { string borderStyle = "1"; // 1=normal, 3=opaque box if (Configuration.Settings.SubtitleSettings.SsaOpaqueBox) borderStyle = "3"; string boldStyle = "0"; // 0=regular if (Configuration.Settings.SubtitleSettings.SsaFontBold) boldStyle = "-1"; var ssa = Configuration.Settings.SubtitleSettings; return "Style: Default," + ssa.SsaFontName + "," + (int)ssa.SsaFontSize + "," + GetSsaColorString(Color.FromArgb(ssa.SsaFontColorArgb)) + "," + "&H0300FFFF,&H00000000,&H02000000," + boldStyle + ",0,0,0,100,100,0,0," + borderStyle + "," + ssa.SsaOutline.ToString(CultureInfo.InvariantCulture) + "," + Configuration.Settings.SubtitleSettings.SsaShadow.ToString(CultureInfo.InvariantCulture) + ",2," + ssa.SsaMarginLeft + "," + ssa.SsaMarginRight + "," + ssa.SsaMarginTopBottom + ",1"; } } public static string DefaultHeader { get { SubtitleFormat format = new AdvancedSubStationAlpha(); var sub = new Subtitle(); string text = format.ToText(sub, string.Empty); var lines = text.SplitToLines(); format.LoadSubtitle(sub, lines, string.Empty); return sub.Header; } } public override string Extension => ".ass"; public const string NameOfFormat = "Advanced Sub Station Alpha"; public override string Name => NameOfFormat; public override bool IsMine(List lines, string fileName) { var subtitle = new Subtitle(); var sb = new StringBuilder(); lines.ForEach(line => sb.AppendLine(line)); string all = sb.ToString(); if (!string.IsNullOrEmpty(fileName) && fileName.EndsWith(".ass", StringComparison.OrdinalIgnoreCase) && !all.Contains("[V4 Styles]")) { } else if (!all.Contains("dialog:", StringComparison.OrdinalIgnoreCase) && !all.Contains("dialogue:", StringComparison.OrdinalIgnoreCase)) { return false; } else if (!all.Contains("[V4+ Styles]") && new SubStationAlpha().IsMine(lines, fileName)) { return false; } LoadSubtitle(subtitle, lines, fileName); Errors = null; if (subtitle.Paragraphs.Count > _errorCount) { if (!string.IsNullOrEmpty(subtitle.Header)) subtitle.Header = subtitle.Header.Replace("[V4 Styles]", "[V4+ Styles]"); return true; } return false; } public const string HeaderNoStyles = @"[Script Info] ; This is an Advanced Sub Station Alpha v4+ script. Title: {0} ScriptType: v4.00+ Collisions: Normal PlayDepth: 0 [V4+ Styles] Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding {1} [Events] Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text"; public override string ToText(Subtitle subtitle, string title) { bool fromTtml = false; string header = @"[Script Info] ; This is an Advanced Sub Station Alpha v4+ script. Title: {0} ScriptType: v4.00+ Collisions: Normal PlayDepth: 0 [V4+ Styles] Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding " + DefaultStyle + @" [Events] Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text"; const string timeCodeFormat = "{0}:{1:00}:{2:00}.{3:00}"; // h:mm:ss.cc const string paragraphWriteFormat = "Dialogue: {9},{0},{1},{3},{4},{5},{6},{7},{8},{2}"; const string commentWriteFormat = "Comment: {9},{0},{1},{3},{4},{5},{6},{7},{8},{2}"; var sb = new StringBuilder(); bool isValidAssHeader = !string.IsNullOrEmpty(subtitle.Header) && subtitle.Header.Contains("[V4+ Styles]"); var styles = new List(); if (isValidAssHeader) { sb.AppendLine(subtitle.Header.Trim()); sb.AppendLine("Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text"); styles = GetStylesFromHeader(subtitle.Header); } else if (!string.IsNullOrEmpty(subtitle.Header) && subtitle.Header.Contains("[V4 Styles]")) { LoadStylesFromSubstationAlpha(subtitle, title, header, HeaderNoStyles, sb); } else if (subtitle.Header != null && subtitle.Header.Contains("http://www.w3.org/ns/ttml")) { LoadStylesFromTimedText10(subtitle, title, header, HeaderNoStyles, sb); fromTtml = true; isValidAssHeader = !string.IsNullOrEmpty(subtitle.Header) && subtitle.Header.Contains("[V4+ Styles]"); if (isValidAssHeader) { styles = GetStylesFromHeader(subtitle.Header); } } else if (subtitle.Header != null && subtitle.Header.Contains("http://www.w3.org/2006/10/ttaf1")) { LoadStylesFromTimedTextTimedDraft2006Oct(subtitle, title, header, HeaderNoStyles, sb); fromTtml = true; isValidAssHeader = !string.IsNullOrEmpty(subtitle.Header) && subtitle.Header.Contains("[V4+ Styles]"); if (isValidAssHeader) { styles = GetStylesFromHeader(subtitle.Header); } } else { sb.AppendLine(string.Format(header, title)); } foreach (Paragraph p in subtitle.Paragraphs) { string start = string.Format(timeCodeFormat, p.StartTime.Hours, p.StartTime.Minutes, p.StartTime.Seconds, p.StartTime.Milliseconds / 10); string end = string.Format(timeCodeFormat, p.EndTime.Hours, p.EndTime.Minutes, p.EndTime.Seconds, p.EndTime.Milliseconds / 10); string style = "Default"; if (!string.IsNullOrEmpty(p.Extra) && isValidAssHeader && styles.Contains(p.Extra)) style = p.Extra; if (fromTtml && !string.IsNullOrEmpty(p.Style) && isValidAssHeader && styles.Contains(p.Style)) style = p.Style; string actor = ""; if (!string.IsNullOrEmpty(p.Actor)) actor = p.Actor; string marginL = "0"; if (!string.IsNullOrEmpty(p.MarginL) && Utilities.IsInteger(p.MarginL)) marginL = p.MarginL; string marginR = "0"; if (!string.IsNullOrEmpty(p.MarginR) && Utilities.IsInteger(p.MarginR)) marginR = p.MarginR; string marginV = "0"; if (!string.IsNullOrEmpty(p.MarginV) && Utilities.IsInteger(p.MarginV)) marginV = p.MarginV; string effect = ""; if (!string.IsNullOrEmpty(p.Effect)) effect = p.Effect; if (p.IsComment) sb.AppendLine(string.Format(commentWriteFormat, start, end, FormatText(p), style, actor, marginL, marginR, marginV, effect, p.Layer)); else sb.AppendLine(string.Format(paragraphWriteFormat, start, end, FormatText(p), style, actor, marginL, marginR, marginV, effect, p.Layer)); } if (!string.IsNullOrEmpty(subtitle.Footer) && (subtitle.Footer.Contains("[Fonts]" + Environment.NewLine) || subtitle.Footer.Contains("[Graphics]" + Environment.NewLine))) { sb.AppendLine(); sb.AppendLine(subtitle.Footer); } return sb.ToString().Trim(); } private static void LoadStylesFromSubstationAlpha(Subtitle subtitle, string title, string header, string headerNoStyles, StringBuilder sb) { try { bool styleFound = false; var ttStyles = new StringBuilder(); // Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding const string styleFormat = "Style: {0},{1},{2},{3},{4},{5},{6},{7},{8},{9},0,100,100,0,0,{10},{11},{12},{13},{14},{15},{16},1"; foreach (string styleName in GetStylesFromHeader(subtitle.Header)) { try { var ssaStyle = GetSsaStyle(styleName, subtitle.Header); if (ssaStyle != null) { string bold = "0"; if (ssaStyle.Bold) bold = "-1"; string italic = "0"; if (ssaStyle.Italic) italic = "-1"; string underline = "0"; if (ssaStyle.Underline) underline = "-1"; string newAlignment = "2"; switch (ssaStyle.Alignment) { case "1": newAlignment = "1"; break; case "3": newAlignment = "3"; break; case "9": newAlignment = "4"; break; case "10": newAlignment = "5"; break; case "11": newAlignment = "6"; break; case "5": newAlignment = "7"; break; case "6": newAlignment = "8"; break; case "7": newAlignment = "9"; break; } ttStyles.AppendLine(string.Format(styleFormat, ssaStyle.Name, ssaStyle.FontName, ssaStyle.FontSize, GetSsaColorString(ssaStyle.Primary), GetSsaColorString(ssaStyle.Secondary), GetSsaColorString(ssaStyle.Outline), GetSsaColorString(ssaStyle.Background), bold, italic, underline, ssaStyle.BorderStyle, ssaStyle.OutlineWidth, ssaStyle.ShadowWidth, newAlignment, ssaStyle.MarginLeft, ssaStyle.MarginRight, ssaStyle.MarginVertical)); styleFound = true; } } catch { // ignored } } if (styleFound) { sb.AppendLine(string.Format(headerNoStyles, title, ttStyles)); subtitle.Header = sb.ToString(); } else { sb.AppendLine(string.Format(header, title)); } } catch { sb.AppendLine(string.Format(header, title)); } } public static void LoadStylesFromTimedText10(Subtitle subtitle, string title, string header, string headerNoStyles, StringBuilder sb) { foreach (Paragraph p in subtitle.Paragraphs) { p.Effect = null; } try { var lines = subtitle.Header.SplitToLines(); var tt = new TimedText10(); var sub = new Subtitle(); tt.LoadSubtitle(sub, lines, string.Empty); var xml = new XmlDocument(); xml.LoadXml(subtitle.Header); var nsmgr = new XmlNamespaceManager(xml.NameTable); nsmgr.AddNamespace("ttml", "http://www.w3.org/ns/ttml"); XmlNode head = xml.DocumentElement.SelectSingleNode("ttml:head", nsmgr); int styleCount = 0; var ttStyles = new StringBuilder(); var styleNames = new List(); foreach (XmlNode node in head.SelectNodes("//ttml:style", nsmgr)) { string name = null; if (node.Attributes["xml:id"] != null) name = node.Attributes["xml:id"].Value; else if (node.Attributes["id"] != null) name = node.Attributes["id"].Value; if (name != null) { styleCount++; string fontFamily = "Arial"; if (node.Attributes["tts:fontFamily"] != null) fontFamily = node.Attributes["tts:fontFamily"].Value; string fontWeight = "normal"; if (node.Attributes["tts:fontWeight"] != null) fontWeight = node.Attributes["tts:fontWeight"].Value; string fontStyle = "normal"; if (node.Attributes["tts:fontStyle"] != null) fontStyle = node.Attributes["tts:fontStyle"].Value; string color = "#ffffff"; if (node.Attributes["tts:color"] != null) color = node.Attributes["tts:color"].Value.Trim(); Color c = Color.White; try { if (color.StartsWith("rgb(", StringComparison.Ordinal)) { string[] arr = color.Remove(0, 4).TrimEnd(')').Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); c = Color.FromArgb(int.Parse(arr[0]), int.Parse(arr[1]), int.Parse(arr[2])); } else { c = ColorTranslator.FromHtml(color); } } catch { // ignored } string fontSize = "20"; if (node.Attributes["tts:fontSize"] != null) fontSize = node.Attributes["tts:fontSize"].Value.Replace("px", string.Empty).Replace("em", string.Empty); int fSize; if (!int.TryParse(fontSize, out fSize)) fSize = 20; string italic = "0"; if (fontStyle == "italic") italic = "-1"; string bold = "0"; if (fontWeight == "bold") bold = "-1"; const string styleFormat = "Style: {0},{1},{2},{3},&H0300FFFF,&H00000000,&H02000000,{4},{5},0,0,100,100,0,0,1,2,2,2,10,10,10,1"; ttStyles.AppendLine(string.Format(styleFormat, name, fontFamily, fSize, GetSsaColorString(c), bold, italic)); styleNames.Add(name); } } if (styleCount > 0) { if (!styleNames.Contains("Default") && !styleNames.Contains("default") && subtitle.Paragraphs.Any(pa => string.IsNullOrEmpty(pa.Extra))) { ttStyles = new StringBuilder(DefaultStyle + Environment.NewLine + ttStyles); foreach (var paragraph in subtitle.Paragraphs) { if (string.IsNullOrEmpty(paragraph.Extra)) paragraph.Extra = "Default"; } } sb.AppendLine(string.Format(headerNoStyles, title, ttStyles)); subtitle.Header = sb.ToString(); } else { sb.AppendLine(string.Format(header, title)); } // Set correct style on paragraphs foreach (Paragraph p in subtitle.Paragraphs) { if (p.Extra != null && p.Extra.Contains('/')) { p.Extra = p.Extra.Split('/')[0].Trim(); } } } catch { sb.AppendLine(string.Format(header, title)); } } public static void LoadStylesFromTimedTextTimedDraft2006Oct(Subtitle subtitle, string title, string header, string headerNoStyles, StringBuilder sb) { foreach (Paragraph p in subtitle.Paragraphs) { p.Effect = null; } try { var lines = subtitle.Header.SplitToLines(); var sb2 = new StringBuilder(); lines.ForEach(line => sb2.AppendLine(line)); var xml = new XmlDocument { XmlResolver = null }; xml.LoadXml(sb2.ToString().Replace(" & ", " & ").Replace("Q&A", "Q&A").RemoveControlCharactersButWhiteSpace().Trim()); subtitle.Header = xml.OuterXml; var nsmgr = new XmlNamespaceManager(xml.NameTable); nsmgr.AddNamespace("ttaf1", xml.DocumentElement.NamespaceURI); int styleCount = 0; var ttStyles = new StringBuilder(); var styleNames = new List(); foreach (XmlNode node in xml.DocumentElement.SelectNodes("//ttaf1:style", nsmgr)) { string name = null; if (node.Attributes["xml:id"] != null) name = node.Attributes["xml:id"].Value; else if (node.Attributes["id"] != null) name = node.Attributes["id"].Value; if (name != null) { styleCount++; string fontFamily = "Arial"; if (node.Attributes["tts:fontFamily"] != null) fontFamily = node.Attributes["tts:fontFamily"].Value; string fontWeight = "normal"; if (node.Attributes["tts:fontWeight"] != null) fontWeight = node.Attributes["tts:fontWeight"].Value; string fontStyle = "normal"; if (node.Attributes["tts:fontStyle"] != null) fontStyle = node.Attributes["tts:fontStyle"].Value; string color = "#ffffff"; if (node.Attributes["tts:color"] != null) color = node.Attributes["tts:color"].Value.Trim(); Color c = Color.White; try { if (color.StartsWith("rgb(", StringComparison.Ordinal)) { string[] arr = color.Remove(0, 4).TrimEnd(')').Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); c = Color.FromArgb(int.Parse(arr[0]), int.Parse(arr[1]), int.Parse(arr[2])); } else { c = ColorTranslator.FromHtml(color); } } catch { // ignored } string fontSize = "20"; if (node.Attributes["tts:fontSize"] != null) fontSize = node.Attributes["tts:fontSize"].Value.Replace("px", string.Empty).Replace("em", string.Empty); int fSize; if (!int.TryParse(fontSize, out fSize)) fSize = 20; string italic = "0"; if (fontStyle == "italic") italic = "-1"; string bold = "0"; if (fontWeight == "bold") bold = "-1"; const string styleFormat = "Style: {0},{1},{2},{3},&H0300FFFF,&H00000000,&H02000000,{4},{5},0,0,100,100,0,0,1,2,2,2,10,10,10,1"; ttStyles.AppendLine(string.Format(styleFormat, name, fontFamily, fSize, GetSsaColorString(c), bold, italic)); styleNames.Add(name); } } if (styleCount > 0) { if (!styleNames.Contains("Default") && !styleNames.Contains("default") && subtitle.Paragraphs.Any(pa => string.IsNullOrEmpty(pa.Extra))) { ttStyles = new StringBuilder(DefaultStyle + Environment.NewLine + ttStyles); foreach (var paragraph in subtitle.Paragraphs) { if (string.IsNullOrEmpty(paragraph.Extra)) paragraph.Extra = "Default"; } } sb.AppendLine(string.Format(headerNoStyles, title, ttStyles)); subtitle.Header = sb.ToString(); } else { sb.AppendLine(string.Format(header, title)); } // Set correct style on paragraphs foreach (Paragraph p in subtitle.Paragraphs) { if (p.Extra != null && p.Extra.Contains('/')) { p.Extra = p.Extra.Split('/')[0].Trim(); } } } catch { sb.AppendLine(string.Format(header, title)); } } public static List GetStylesFromHeader(string headerLines) { var list = new List(); if (headerLines == null) headerLines = DefaultStyle; if (headerLines.Contains("http://www.w3.org/ns/ttml")) { var subtitle = new Subtitle { Header = headerLines }; LoadStylesFromTimedText10(subtitle, string.Empty, headerLines, HeaderNoStyles, new StringBuilder()); headerLines = subtitle.Header; } foreach (string line in headerLines.SplitToLines()) { if (line.StartsWith("style:", StringComparison.OrdinalIgnoreCase)) { int end = line.IndexOf(','); if (end > 0) list.Add(line.Substring(6, end - 6).Trim()); } } return list; } public static string FormatText(Paragraph p) { string text = p.Text.Replace(Environment.NewLine, "\\N"); if (!text.Contains('<')) return text; text = text.Replace("", @"{\i1}"); text = text.Replace("", @"{\i0}"); text = text.Replace("", @"{\i}"); text = text.Replace("", @"{\u1}"); text = text.Replace("", @"{\u0}"); text = text.Replace("", @"{\u}"); text = text.Replace("", @"{\b1}"); text = text.Replace("", @"{\b0}"); text = text.Replace("", @"{\b}"); int count = 0; while (text.Contains("', start); if (end > 0) { string fontTag = text.Substring(start + 5, end - (start + 4)); text = text.Remove(start, end - start + 1); int indexOfEndFont = text.IndexOf("", start, StringComparison.Ordinal); if (indexOfEndFont > 0) { text = text.Remove(indexOfEndFont, 7); if (indexOfEndFont < text.Length) { if (fontTag.Contains(" size=")) { text = text.Insert(indexOfEndFont, "{\\fs}"); } if (fontTag.Contains(" face=")) { text = text.Insert(indexOfEndFont, "{\\fn}"); } if (fontTag.Contains(" color=")) { text = text.Insert(indexOfEndFont, "{\\c}"); } } } fontTag = FormatTag(ref text, start, fontTag, "face=\"", "fn", "}"); fontTag = FormatTag(ref text, start, fontTag, "face='", "fn", "}"); fontTag = FormatTag(ref text, start, fontTag, "face=", "fn", "}"); fontTag = FormatTag(ref text, start, fontTag, "size=\"", "fs", "}"); fontTag = FormatTag(ref text, start, fontTag, "size='", "fs", "}"); fontTag = FormatTag(ref text, start, fontTag, "size=", "fs", "}"); fontTag = FormatTag(ref text, start, fontTag, "color=\"", "c&H", "&}"); fontTag = FormatTag(ref text, start, fontTag, "color='", "c&H", "&}"); FormatTag(ref text, start, fontTag, "color=", "c&H", "&}"); } count++; } text = text.Replace("{\\c}", "@___@@").Replace("}{", string.Empty).Replace("@___@@", "{\\c}").Replace("{\\c}{\\c&", "{\\c&"); while (text.EndsWith("{\\c}", StringComparison.Ordinal)) { text = text.Remove(text.Length - 4); } while (text.Contains("\\fs\\fs", StringComparison.Ordinal)) { text = text.Replace("\\fs\\fs", "\\fs"); } while (text.Contains("\\fn\\fn", StringComparison.Ordinal)) { text = text.Replace("\\fn\\fn", "\\fn"); } while (text.Contains("\\c\\c&H", StringComparison.Ordinal)) { text = text.Replace("\\c\\c&H", "\\c&H"); } return text; } private static string FormatTag(ref string text, int start, string fontTag, string tag, string ssaTagName, string endSsaTag) { if (fontTag.Contains(tag)) { int fontStart = fontTag.IndexOf(tag, StringComparison.Ordinal); int fontEnd = fontTag.IndexOfAny(new[] { '"', '\'' }, fontStart + tag.Length); if (fontEnd < 0) fontEnd = fontTag.IndexOfAny(new[] { ' ', '>' }, fontStart + tag.Length); if (fontEnd > 0) { string subTag = fontTag.Substring(fontStart + tag.Length, fontEnd - (fontStart + tag.Length)); if (tag.Contains("color")) { Color c; try { c = ColorTranslator.FromHtml(subTag); } catch { c = Color.White; } subTag = (c.B.ToString("X2") + c.G.ToString("X2") + c.R.ToString("X2")).ToLowerInvariant(); // use bbggrr } fontTag = fontTag.Remove(fontStart, fontEnd - fontStart + 1); if (start < text.Length) text = text.Insert(start, @"{\" + ssaTagName + subTag + endSsaTag); } } return fontTag; } public static string GetFormattedText(string text) { text = text.Replace("\\N", Environment.NewLine).Replace("\\n", Environment.NewLine); var tooComplex = ContainsUnsupportedTags(text); if (!tooComplex) { for (int i = 0; i < 10; i++) // just look ten times... { bool italic; if (text.Contains(@"{\fn")) { int start = text.IndexOf(@"{\fn", StringComparison.Ordinal); int end = text.IndexOf('}', start); if (end > 0 && !text.Substring(start).StartsWith("{\\fn}", StringComparison.Ordinal)) { string fontName = text.Substring(start + 4, end - (start + 4)); string extraTags = string.Empty; string unknownTags; CheckAndAddSubTags(ref fontName, ref extraTags, out unknownTags, out italic); text = text.Remove(start, end - start + 1); if (italic) text = text.Insert(start, "" + unknownTags + ""); else text = text.Insert(start, "" + unknownTags); int indexOfEndTag = text.IndexOf("{\\fn}", start, StringComparison.Ordinal); if (indexOfEndTag > 0) { text = text.Remove(indexOfEndTag, "{\\fn}".Length).Insert(indexOfEndTag, ""); } else { int indexOfNextTag1 = text.IndexOf("{\\fn", start, StringComparison.Ordinal); int indexOfNextTag2 = text.IndexOf("{\\c}", start, StringComparison.Ordinal); if (indexOfNextTag1 > 0) { text = text.Insert(indexOfNextTag1, ""); } else if (indexOfNextTag2 > 0 && text.IndexOf("{\\", start, StringComparison.Ordinal) >= indexOfNextTag2) { text = text.Insert(indexOfNextTag2, ""); } else { text += ""; } } } } if (text.Contains(@"{\fs")) { int start = text.IndexOf(@"{\fs", StringComparison.Ordinal); int end = text.IndexOf('}', start); if (end > 0 && !text.Substring(start).StartsWith("{\\fs}", StringComparison.Ordinal)) { string fontSize = text.Substring(start + 4, end - (start + 4)); string extraTags = string.Empty; string unknownTags; CheckAndAddSubTags(ref fontSize, ref extraTags, out unknownTags, out italic); if (Utilities.IsInteger(fontSize)) { text = text.Remove(start, end - start + 1); if (italic) text = text.Insert(start, "" + unknownTags + ""); else text = text.Insert(start, "" + unknownTags); int indexOfEndTag = text.IndexOf("{\\fs}", start, StringComparison.Ordinal); if (indexOfEndTag > 0) { text = text.Remove(indexOfEndTag, "{\\fs}".Length).Insert(indexOfEndTag, ""); } else { int indexOfNextTag1 = text.IndexOf("{\\fs", start, StringComparison.Ordinal); int indexOfNextTag2 = text.IndexOf("{\\c}", start, StringComparison.Ordinal); if (indexOfNextTag1 > 0) { text = text.Insert(indexOfNextTag1, ""); } else if (indexOfNextTag2 > 0 && text.IndexOf("{\\", start, StringComparison.Ordinal) >= indexOfNextTag2) { text = text.Insert(indexOfNextTag2, ""); } else { text += ""; } } } } } if (text.Contains(@"{\c")) { int start = text.IndexOf(@"{\c", StringComparison.Ordinal); int end = text.IndexOf('}', start); if (end > 0 && !text.Substring(start).StartsWith("{\\c}", StringComparison.Ordinal) && !text.Substring(start).StartsWith("{\\clip", StringComparison.Ordinal)) { string color = text.Substring(start + 4, end - (start + 4)); string extraTags = string.Empty; string unknownTags; CheckAndAddSubTags(ref color, ref extraTags, out unknownTags, out italic); color = color.RemoveChar('&').TrimStart('H'); color = color.PadLeft(6, '0'); // switch to rrggbb from bbggrr color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2); color = color.ToLower(); text = text.Remove(start, end - start + 1); if (italic) text = text.Insert(start, "" + unknownTags + ""); else text = text.Insert(start, "" + unknownTags); int indexOfEndTag = text.IndexOf("{\\c}", start, StringComparison.Ordinal); int indexOfNextColorTag = text.IndexOf("{\\c&", start, StringComparison.Ordinal); if (indexOfNextColorTag > 0 && (indexOfNextColorTag < indexOfEndTag || indexOfEndTag == -1)) text = text.Insert(indexOfNextColorTag, ""); else if (indexOfEndTag > 0) text = text.Remove(indexOfEndTag, "{\\c}".Length).Insert(indexOfEndTag, ""); else text += ""; } } if (text.Contains(@"{\1c")) // "1" specifices primary color { int start = text.IndexOf(@"{\1c", StringComparison.Ordinal); int end = text.IndexOf('}', start); if (end > 0 && !text.Substring(start).StartsWith("{\\1c}", StringComparison.Ordinal)) { string color = text.Substring(start + 5, end - (start + 5)); string extraTags = string.Empty; string unknownTags; CheckAndAddSubTags(ref color, ref extraTags, out unknownTags, out italic); color = color.RemoveChar('&').TrimStart('H'); color = color.PadLeft(6, '0'); // switch to rrggbb from bbggrr color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2); color = color.ToLower(); text = text.Remove(start, end - start + 1); if (italic) text = text.Insert(start, "" + unknownTags + ""); else text = text.Insert(start, "" + unknownTags); text += ""; } } } } text = text.Replace(@"{\i1}", ""); text = text.Replace(@"{\i0}", ""); text = text.Replace(@"{\i}", ""); if (Utilities.CountTagInText(text, "") > Utilities.CountTagInText(text, "")) text += ""; text = text.Replace(@"{\u1}", ""); text = text.Replace(@"{\u0}", ""); text = text.Replace(@"{\u}", ""); if (Utilities.CountTagInText(text, "") > Utilities.CountTagInText(text, "")) text += ""; text = text.Replace(@"{\b1}", ""); text = text.Replace(@"{\b0}", ""); text = text.Replace(@"{\b}", ""); if (Utilities.CountTagInText(text, "") > Utilities.CountTagInText(text, "")) text += ""; return text; } private static bool ContainsUnsupportedTags(string text) { if (string.IsNullOrEmpty(text) || !text.Contains("{\\", StringComparison.OrdinalIgnoreCase)) return false; var unsupportedTags = new List { "\\alpha", "\\be0", "\\be1", "\\bord", "\\blur", "\\clip", "\\fad", "\\fa", "\\fade", "\\fscx", "\\fscy", "\\fr", "\\iclip", "\\k", "\\K", "\\kf", "\\ko", "\\move", "\\org", "\\p", "\\pos", "\\s0", "\\s1", "\\t(", "\\xbord", "\\ybord", "\\xshad", "\\yshad" }; foreach (var unsupportedTag in unsupportedTags) { if (text.Contains(unsupportedTag, StringComparison.Ordinal)) return true; } return false; } private static void CheckAndAddSubTags(ref string tagName, ref string extraTags, out string unknownTags, out bool italic) { italic = false; unknownTags = string.Empty; int indexOfSPlit = tagName.IndexOf('\\'); if (indexOfSPlit > 0) { string rest = tagName.Substring(indexOfSPlit).TrimStart('\\'); tagName = tagName.Remove(indexOfSPlit); for (int i = 0; i < 10; i++) { if (rest.StartsWith("fs", StringComparison.Ordinal) && rest.Length > 2) { indexOfSPlit = rest.IndexOf('\\'); string fontSize = rest; if (indexOfSPlit > 0) { fontSize = rest.Substring(0, indexOfSPlit); rest = rest.Substring(indexOfSPlit).TrimStart('\\'); } else { rest = string.Empty; } extraTags += " size=\"" + fontSize.Substring(2) + "\""; } else if (rest.StartsWith("fn", StringComparison.Ordinal) && rest.Length > 2) { indexOfSPlit = rest.IndexOf('\\'); string fontName = rest; if (indexOfSPlit > 0) { fontName = rest.Substring(0, indexOfSPlit); rest = rest.Substring(indexOfSPlit).TrimStart('\\'); } else { rest = string.Empty; } extraTags += " face=\"" + fontName.Substring(2) + "\""; } else if (rest.StartsWith('c') && rest.Length > 2) { indexOfSPlit = rest.IndexOf('\\'); string fontColor = rest; if (indexOfSPlit > 0) { fontColor = rest.Substring(0, indexOfSPlit); rest = rest.Substring(indexOfSPlit).TrimStart('\\'); } else { rest = string.Empty; } string color = fontColor.Substring(2); color = color.RemoveChar('&').TrimStart('H'); color = color.PadLeft(6, '0'); // switch to rrggbb from bbggrr color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2); color = color.ToLower(); extraTags += " color=\"" + color + "\""; } else if (rest.StartsWith("i1", StringComparison.Ordinal) && rest.Length > 1) { indexOfSPlit = rest.IndexOf('\\'); italic = true; if (indexOfSPlit > 0) { rest = rest.Substring(indexOfSPlit).TrimStart('\\'); } else { rest = string.Empty; } } else if (rest.Length > 0 && rest.Contains("\\")) { indexOfSPlit = rest.IndexOf('\\'); var unknowntag = rest.Substring(0, indexOfSPlit); unknownTags += "\\" + unknowntag; rest = rest.Substring(indexOfSPlit).TrimStart('\\'); } else if (!string.IsNullOrEmpty(rest)) { unknownTags += "\\" + rest; rest = string.Empty; } } } if (!string.IsNullOrEmpty(unknownTags)) unknownTags = "{" + unknownTags + "}"; } public override void LoadSubtitle(Subtitle subtitle, List lines, string fileName) { _errorCount = 0; Errors = null; bool eventsStarted = false; bool fontsStarted = false; bool graphicsStarted = false; subtitle.Paragraphs.Clear(); // Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text int indexLayer = 0; int indexStart = 1; int indexEnd = 2; int indexStyle = 3; int indexActor = 4; int indexName = -1; // convert "Name" to "Actor" (if no "Actor") - "Name" is from SSA ? int indexMarginL = 5; int indexMarginR = 6; int indexMarginV = 7; int indexEffect = 8; int indexText = 9; var errors = new StringBuilder(); int lineNumber = 0; var header = new StringBuilder(); var footer = new StringBuilder(); foreach (string line in lines) { lineNumber++; if (!eventsStarted && !fontsStarted && !graphicsStarted && !line.Trim().Equals("[fonts]", StringComparison.InvariantCultureIgnoreCase) && !line.Trim().Equals("[graphics]", StringComparison.InvariantCultureIgnoreCase)) header.AppendLine(line); if (string.IsNullOrWhiteSpace(line) || line.TrimStart().StartsWith(';')) { // skip empty and comment lines } else if (line.TrimStart().StartsWith("dialog:", StringComparison.OrdinalIgnoreCase) || line.TrimStart().StartsWith("dialogue:", StringComparison.OrdinalIgnoreCase)) // fix faulty font tags... { eventsStarted = true; fontsStarted = false; graphicsStarted = false; } if (line.Trim().Equals("[events]", StringComparison.OrdinalIgnoreCase)) { if (header.ToString().IndexOf(Environment.NewLine + "[events]", StringComparison.OrdinalIgnoreCase) < 0) { var h = header.ToString().TrimEnd(); header.Clear(); header.AppendLine(h); header.AppendLine(); header.AppendLine("[Events]"); } eventsStarted = true; fontsStarted = false; graphicsStarted = false; } else if (line.Trim().Equals("[fonts]", StringComparison.OrdinalIgnoreCase)) { eventsStarted = false; fontsStarted = true; graphicsStarted = false; footer.AppendLine(); footer.AppendLine("[Fonts]"); } else if (line.Trim().Equals("[graphics]", StringComparison.OrdinalIgnoreCase)) { eventsStarted = false; fontsStarted = false; graphicsStarted = true; footer.AppendLine(); footer.AppendLine("[Graphics]"); } else if (fontsStarted) { footer.AppendLine(line); } else if (graphicsStarted) { footer.AppendLine(line); } else if (eventsStarted) { string s = line.Trim().ToLower(); if (line.Length > 10 && s.StartsWith("format:", StringComparison.Ordinal)) { indexLayer = -1; indexStart = -1; indexEnd = -1; indexStyle = -1; indexActor = -1; indexName = -1; indexMarginL = -1; indexMarginR = -1; indexMarginV = -1; indexEffect = -1; indexText = -1; var format = s.Substring(8).Split(','); for (int i = 0; i < format.Length; i++) { var formatTrimmed = format[i].Trim(); if (formatTrimmed.Equals("start", StringComparison.Ordinal)) indexStart = i; else if (formatTrimmed.Equals("end", StringComparison.Ordinal)) indexEnd = i; else if (formatTrimmed.Equals("text", StringComparison.Ordinal)) indexText = i; else if (formatTrimmed.Equals("style", StringComparison.Ordinal)) indexStyle = i; else if (formatTrimmed.Equals("actor", StringComparison.Ordinal)) indexActor = i; else if (formatTrimmed.Equals("name", StringComparison.Ordinal)) indexName = i; else if (formatTrimmed.Equals("marginl", StringComparison.Ordinal)) indexMarginL = i; else if (formatTrimmed.Equals("marginr", StringComparison.Ordinal)) indexMarginR = i; else if (formatTrimmed.Equals("marginv", StringComparison.Ordinal)) indexMarginV = i; else if (formatTrimmed.Equals("effect", StringComparison.Ordinal)) indexEffect = i; else if (formatTrimmed.Equals("layer", StringComparison.Ordinal)) indexLayer = i; } } else if (!string.IsNullOrEmpty(s)) { var text = string.Empty; var start = string.Empty; var end = string.Empty; var style = string.Empty; var actor = string.Empty; var marginL = string.Empty; var marginR = string.Empty; var marginV = string.Empty; var effect = string.Empty; var layer = 0; string[] splittedLine; if (s.StartsWith("dialog:", StringComparison.Ordinal)) splittedLine = line.Remove(0, 7).Split(','); else if (s.StartsWith("dialogue:", StringComparison.Ordinal)) splittedLine = line.Remove(0, 9).Split(','); else splittedLine = line.Split(','); for (int i = 0; i < splittedLine.Length; i++) { if (i == indexStart) start = splittedLine[i].Trim(); else if (i == indexEnd) end = splittedLine[i].Trim(); else if (i == indexStyle) style = splittedLine[i].Trim(); else if (i == indexActor) actor = splittedLine[i].Trim(); else if (i == indexName && indexActor == -1) actor = splittedLine[i].Trim(); else if (i == indexMarginL) marginL = splittedLine[i].Trim(); else if (i == indexMarginR) marginR = splittedLine[i].Trim(); else if (i == indexMarginV) marginV = splittedLine[i].Trim(); else if (i == indexEffect) effect = splittedLine[i].Trim(); else if (i == indexLayer) int.TryParse(splittedLine[i].Replace("Comment:", string.Empty).Trim(), out layer); else if (i == indexText) text = splittedLine[i]; else if (i > indexText) text += "," + splittedLine[i]; } try { var p = new Paragraph { StartTime = GetTimeCodeFromString(start), EndTime = GetTimeCodeFromString(end), Text = GetFormattedText(text) }; if (!string.IsNullOrEmpty(style)) p.Extra = style; if (!string.IsNullOrEmpty(actor)) p.Actor = actor; if (!string.IsNullOrEmpty(marginL)) p.MarginL = marginL; if (!string.IsNullOrEmpty(marginR)) p.MarginR = marginR; if (!string.IsNullOrEmpty(marginV)) p.MarginV = marginV; if (!string.IsNullOrEmpty(effect)) p.Effect = effect; p.Layer = layer; p.IsComment = s.StartsWith("comment:", StringComparison.Ordinal); subtitle.Paragraphs.Add(p); } catch { _errorCount++; if (errors.Length < 2000) errors.AppendLine(string.Format(Configuration.Settings.Language.Main.LineNumberXErrorReadingTimeCodeFromSourceLineY, lineNumber, line)); } } } } if (header.Length > 0) subtitle.Header = header.ToString(); if (footer.Length > 0) subtitle.Footer = footer.ToString().Trim(); subtitle.Renumber(); Errors = errors.ToString(); } private static TimeCode GetTimeCodeFromString(string time) { // h:mm:ss.cc string[] timeCode = time.Split(':', '.'); return new TimeCode(int.Parse(timeCode[0]), int.Parse(timeCode[1]), int.Parse(timeCode[2]), int.Parse(timeCode[3]) * 10); } public override void RemoveNativeFormatting(Subtitle subtitle, SubtitleFormat newFormat) { if (newFormat != null && newFormat.Name == SubStationAlpha.NameOfFormat) { foreach (Paragraph p in subtitle.Paragraphs) { string s = p.Text; if (s.Contains('{') && s.Contains('}')) { s = s.Replace(@"\u0", string.Empty); s = s.Replace(@"\u1", string.Empty); s = s.Replace(@"\s0", string.Empty); s = s.Replace(@"\s1", string.Empty); s = s.Replace(@"\be0", string.Empty); s = s.Replace(@"\be1", string.Empty); s = RemoveTag(s, "shad"); s = RemoveTag(s, "fsc"); s = RemoveTag(s, "fsp"); s = RemoveTag(s, "fr"); s = RemoveTag(s, "t("); s = RemoveTag(s, "move("); s = RemoveTag(s, "Position("); s = RemoveTag(s, "org("); s = RemoveTag(s, "fade("); s = RemoveTag(s, "fad("); s = RemoveTag(s, "clip("); s = RemoveTag(s, "iclip("); s = RemoveTag(s, "pbo("); s = RemoveTag(s, "bord"); s = RemoveTag(s, "pos"); // TODO: Alignment tags s = s.Replace("{}", string.Empty); p.Text = s; } } } else { foreach (Paragraph p in subtitle.Paragraphs) { int indexOfBegin = p.Text.IndexOf('{'); string pre = string.Empty; while (indexOfBegin >= 0 && p.Text.IndexOf('}') > indexOfBegin) { string s = p.Text.Substring(indexOfBegin); if (s.StartsWith("{\\an1}", StringComparison.Ordinal) || s.StartsWith("{\\an2}", StringComparison.Ordinal) || s.StartsWith("{\\an3}", StringComparison.Ordinal) || s.StartsWith("{\\an4}", StringComparison.Ordinal) || s.StartsWith("{\\an5}", StringComparison.Ordinal) || s.StartsWith("{\\an6}", StringComparison.Ordinal) || s.StartsWith("{\\an7}", StringComparison.Ordinal) || s.StartsWith("{\\an8}", StringComparison.Ordinal) || s.StartsWith("{\\an9}", StringComparison.Ordinal)) { pre = s.Substring(0, 6); } else if (s.StartsWith("{\\an1\\", StringComparison.Ordinal) || s.StartsWith("{\\an2\\", StringComparison.Ordinal) || s.StartsWith("{\\an3\\", StringComparison.Ordinal) || s.StartsWith("{\\an4\\", StringComparison.Ordinal) || s.StartsWith("{\\an5\\", StringComparison.Ordinal) || s.StartsWith("{\\an6\\", StringComparison.Ordinal) || s.StartsWith("{\\an7\\", StringComparison.Ordinal) || s.StartsWith("{\\an8\\", StringComparison.Ordinal) || s.StartsWith("{\\an9\\", StringComparison.Ordinal)) { pre = s.Substring(0, 5) + "}"; } int indexOfEnd = p.Text.IndexOf('}'); p.Text = p.Text.Remove(indexOfBegin, (indexOfEnd - indexOfBegin) + 1); indexOfBegin = p.Text.IndexOf('{'); } p.Text = pre + p.Text; } } } private static string RemoveTag(string s, string tag) { int indexOfTag = s.IndexOf(@"\" + tag, StringComparison.Ordinal); if (indexOfTag > 0) { var endIndex1 = s.IndexOf('\\', indexOfTag + 1); var endIndex2 = s.IndexOf('}', indexOfTag + 1); endIndex1 = Math.Min(endIndex1, endIndex2); if (endIndex1 > 0) s = s.Remove(indexOfTag, endIndex1 - indexOfTag); } return s; } /// /// BGR color like this: &HBBGGRR& (where BB, GG, and RR are hex values in uppercase) /// /// Input string /// Default color /// Input string as color, or default color if problems public static Color GetSsaColor(string f, Color defaultColor) { //Red = &H0000FF& //Green = &H00FF00& //Blue = &HFF0000& //White = &HFFFFFF& //Black = &H000000& string s = f.Trim().Trim('&'); if (s.StartsWith('h') && s.Length < 7) { while (s.Length < 7) s = s.Insert(1, "0"); } if (s.StartsWith('h') && s.Length == 7) { s = s.Substring(1); string hexColor = "#" + s.Substring(4, 2) + s.Substring(2, 2) + s.Substring(0, 2); try { return ColorTranslator.FromHtml(hexColor); } catch { return defaultColor; } } if (s.StartsWith('h') && s.Length == 9) { int alpha; if (int.TryParse(s.Substring(1, 2), NumberStyles.HexNumber, null, out alpha)) { alpha = 255 - alpha; // ASS stores alpha in reverse (0=full itentity and 255=fully transparent) } else { alpha = 255; // full color } s = s.Substring(3); string hexColor = "#" + s.Substring(4, 2) + s.Substring(2, 2) + s.Substring(0, 2); try { var c = ColorTranslator.FromHtml(hexColor); return Color.FromArgb(alpha, c); } catch { return defaultColor; } } int number; if (int.TryParse(f, out number)) { Color temp = Color.FromArgb(number); return Color.FromArgb(255, temp.B, temp.G, temp.R); } return defaultColor; } public static string GetSsaColorString(Color c) { return $"&H{255 - c.A:X2}{c.B:X2}{c.G:X2}{c.R:X2}"; // ASS stores alpha in reverse (0=full itentity and 255=fully transparent) } public static string CheckForErrors(string header) { if (string.IsNullOrEmpty(header)) return string.Empty; var sb = new StringBuilder(); int styleCount = -1; int nameIndex = -1; int fontNameIndex = -1; int fontsizeIndex = -1; int primaryColourIndex = -1; int secondaryColourIndex = -1; int outlineColourIndex = -1; int backColourIndex = -1; int boldIndex = -1; int italicIndex = -1; int underlineIndex = -1; int outlineIndex = -1; int shadowIndex = -1; int alignmentIndex = -1; int marginLIndex = -1; int marginRIndex = -1; int marginVIndex = -1; int borderStyleIndex = -1; foreach (string line in header.SplitToLines()) { string s = line.Trim().ToLower(); if (s.StartsWith("format:", StringComparison.Ordinal)) { if (line.Length > 10) { var format = line.Substring(8).ToLower().Split(','); styleCount = format.Length; for (int i = 0; i < format.Length; i++) { string f = format[i].Trim(); if (f == "name") nameIndex = i; else if (f == "fontname") fontNameIndex = i; else if (f == "fontsize") fontsizeIndex = i; else if (f == "primarycolour") primaryColourIndex = i; else if (f == "secondarycolour") secondaryColourIndex = i; else if (f == "outlinecolour") outlineColourIndex = i; else if (f == "backcolour") backColourIndex = i; else if (f == "bold") boldIndex = i; else if (f == "italic") italicIndex = i; else if (f == "underline") underlineIndex = i; else if (f == "outline") outlineIndex = i; else if (f == "shadow") shadowIndex = i; else if (f == "alignment") alignmentIndex = i; else if (f == "marginl") marginLIndex = i; else if (f == "marginr") marginRIndex = i; else if (f == "marginv") marginVIndex = i; else if (f == "borderstyle") borderStyleIndex = i; } } } else if (s.RemoveChar(' ').StartsWith("style:", StringComparison.Ordinal)) { if (line.Length > 10) { string rawLine = line; var format = line.Substring(6).Split(','); if (format.Length != styleCount) { sb.AppendLine("Number of expected Style elements do not match number of Format elements: " + rawLine); sb.AppendLine(); } else { Color dummyColor = Color.FromArgb(9, 14, 16, 26); for (int i = 0; i < format.Length; i++) { string f = format[i].Trim().ToLower(); if (i == nameIndex) { if (f.Length == 0) { sb.AppendLine("'Name' is empty: " + rawLine); sb.AppendLine(); } } else if (i == fontNameIndex) { if (f.Length == 0) { sb.AppendLine("'Fontname' is empty: " + rawLine); sb.AppendLine(); } } else if (i == fontsizeIndex) { int number; if (!int.TryParse(f, out number) || f.StartsWith('-')) { sb.AppendLine("'Fontsize' incorrect: " + rawLine); sb.AppendLine(); } } else if (i == primaryColourIndex) { if (GetSsaColor(f, dummyColor) == dummyColor || f == "&h") { sb.AppendLine("'PrimaryColour' incorrect: " + rawLine); sb.AppendLine(); } } else if (i == secondaryColourIndex) { if (GetSsaColor(f, dummyColor) == dummyColor) { sb.AppendLine("'SecondaryColour' incorrect: " + rawLine); sb.AppendLine(); } } else if (i == outlineColourIndex) { if (GetSsaColor(f, dummyColor) == dummyColor) { sb.AppendLine("'OutlineColour' incorrect: " + rawLine); sb.AppendLine(); } } else if (i == backColourIndex) { if (GetSsaColor(f, dummyColor) == dummyColor) { sb.AppendLine("'BackColour' incorrect: " + rawLine); sb.AppendLine(); } } else if (i == boldIndex) { if (Utilities.AllLetters.Contains(f)) { sb.AppendLine("'Bold' incorrect: " + rawLine); sb.AppendLine(); } } else if (i == italicIndex) { if (Utilities.AllLetters.Contains(f)) { sb.AppendLine("'Italic' incorrect: " + rawLine); sb.AppendLine(); } } else if (i == underlineIndex) { if (Utilities.AllLetters.Contains(f)) { sb.AppendLine("'Underline' incorrect: " + rawLine); sb.AppendLine(); } } else if (i == outlineIndex) { float number; if (!float.TryParse(f, out number) || f.StartsWith('-')) { sb.AppendLine("'Outline' (width) incorrect: " + rawLine); sb.AppendLine(); } } else if (i == shadowIndex) { float number; if (!float.TryParse(f, out number) || f.StartsWith('-')) { sb.AppendLine("'Shadow' (width) incorrect: " + rawLine); sb.AppendLine(); } } else if (i == alignmentIndex) { if (!"101123456789 ".Contains(f)) { sb.AppendLine("'Alignment' incorrect: " + rawLine); sb.AppendLine(); } } else if (i == marginLIndex) { int number; if (!int.TryParse(f, out number) || f.StartsWith('-')) { sb.AppendLine("'MarginL' incorrect: " + rawLine); sb.AppendLine(); } } else if (i == marginRIndex) { int number; if (!int.TryParse(f, out number) || f.StartsWith('-')) { sb.AppendLine("'MarginR' incorrect: " + rawLine); sb.AppendLine(); } } else if (i == marginVIndex) { int number; if (!int.TryParse(f, out number) || f.StartsWith('-')) { sb.AppendLine("'MarginV' incorrect: " + rawLine); sb.AppendLine(); } } else if (i == borderStyleIndex) { if (f.Length != 0 && !"123".Contains(f)) { sb.AppendLine("'BorderStyle' incorrect: " + rawLine); sb.AppendLine(); } } } } } } } return sb.ToString(); } /// /// Add new style to ASS header /// /// Header with new style public static string AddSsaStyle(SsaStyle style, string header) { if (string.IsNullOrEmpty(header)) header = DefaultHeader; var sb = new StringBuilder(); bool stylesStarted = false; bool styleAdded = false; string styleFormat = "Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding"; foreach (string line in header.SplitToLines()) { if (line.Equals("[V4+ Styles]", StringComparison.OrdinalIgnoreCase) || line.Equals("[V4 Styles]", StringComparison.OrdinalIgnoreCase)) stylesStarted = true; if (line.StartsWith("format:", StringComparison.OrdinalIgnoreCase)) styleFormat = line; if (!line.StartsWith("Style: " + style.Name + ",", StringComparison.Ordinal)) // overwrite existing style sb.AppendLine(line); if (!styleAdded && stylesStarted && line.TrimStart().StartsWith("style:", StringComparison.OrdinalIgnoreCase)) { sb.AppendLine(style.ToRawAss(styleFormat)); styleAdded = true; } } return sb.ToString(); } public static SsaStyle GetSsaStyle(string styleName, string header) { var style = new SsaStyle { Name = styleName }; int nameIndex = -1; int fontNameIndex = -1; int fontsizeIndex = -1; int primaryColourIndex = -1; int secondaryColourIndex = -1; int tertiaryColourIndex = -1; int outlineColourIndex = -1; int backColourIndex = -1; int boldIndex = -1; int italicIndex = -1; int underlineIndex = -1; int outlineIndex = -1; int shadowIndex = -1; int alignmentIndex = -1; int marginLIndex = -1; int marginRIndex = -1; int marginVIndex = -1; int borderStyleIndex = -1; if (header == null) header = DefaultHeader; foreach (string line in header.SplitToLines()) { string s = line.Trim().ToLower(); if (s.StartsWith("format:", StringComparison.Ordinal)) { if (line.Length > 10) { var format = line.ToLower().Substring(8).Split(','); for (int i = 0; i < format.Length; i++) { string f = format[i].Trim().ToLower(); if (f == "name") nameIndex = i; else if (f == "fontname") fontNameIndex = i; else if (f == "fontsize") fontsizeIndex = i; else if (f == "primarycolour") primaryColourIndex = i; else if (f == "secondarycolour") secondaryColourIndex = i; else if (f == "tertiarycolour") tertiaryColourIndex = i; else if (f == "outlinecolour") outlineColourIndex = i; else if (f == "backcolour") backColourIndex = i; else if (f == "bold") boldIndex = i; else if (f == "italic") italicIndex = i; else if (f == "underline") underlineIndex = i; else if (f == "outline") outlineIndex = i; else if (f == "shadow") shadowIndex = i; else if (f == "alignment") alignmentIndex = i; else if (f == "marginl") marginLIndex = i; else if (f == "marginr") marginRIndex = i; else if (f == "marginv") marginVIndex = i; else if (f == "borderstyle") borderStyleIndex = i; } } } else if (s.RemoveChar(' ').StartsWith("style:", StringComparison.Ordinal)) { if (line.Length > 10) { style.RawLine = line; var format = line.Substring(6).Split(','); for (int i = 0; i < format.Length; i++) { string f = format[i].Trim().ToLower(); if (i == nameIndex) { style.Name = format[i].Trim(); } else if (i == fontNameIndex) { style.FontName = f; } else if (i == fontsizeIndex) { int number; if (int.TryParse(f, out number)) style.FontSize = number; } else if (i == primaryColourIndex) { style.Primary = GetSsaColor(f, Color.White); } else if (i == secondaryColourIndex) { style.Secondary = GetSsaColor(f, Color.Yellow); } else if (i == tertiaryColourIndex) { style.Tertiary = GetSsaColor(f, Color.Yellow); } else if (i == outlineColourIndex) { style.Outline = GetSsaColor(f, Color.Black); } else if (i == backColourIndex) { style.Background = GetSsaColor(f, Color.Black); } else if (i == boldIndex) { style.Bold = f == "-1" || f == "1"; } else if (i == italicIndex) { style.Italic = f == "-1" || f == "1"; } else if (i == underlineIndex) { style.Underline = f == "-1" || f == "1"; } else if (i == outlineIndex) { decimal number; if (decimal.TryParse(f, out number)) style.OutlineWidth = number; } else if (i == shadowIndex) { decimal number; if (decimal.TryParse(f, out number)) style.ShadowWidth = number; } else if (i == alignmentIndex) { style.Alignment = f; } else if (i == marginLIndex) { int number; if (int.TryParse(f, out number)) style.MarginLeft = number; } else if (i == marginRIndex) { int number; if (int.TryParse(f, out number)) style.MarginRight = number; } else if (i == marginVIndex) { int number; if (int.TryParse(f, out number)) style.MarginVertical = number; } else if (i == borderStyleIndex) { style.BorderStyle = f; } } } if (styleName != null && style.Name != null && (styleName.Equals(style.Name, StringComparison.OrdinalIgnoreCase) || styleName.Equals("*Default", StringComparison.OrdinalIgnoreCase) && style.Name.Equals("Default", StringComparison.OrdinalIgnoreCase))) { style.LoadedFromHeader = true; return style; } } } return new SsaStyle { Name = styleName }; } public override bool HasStyleSupport => true; } }