using System; using System.Collections.Generic; using System.Globalization; using System.Text; using System.Xml; namespace Nikse.SubtitleEdit.Core.SubtitleFormats { public class TimedTextImage : SubtitleFormat { public override string Extension => ".xml"; public override string Name => "Timed Text Image"; public override bool IsMine(List lines, string fileName) { var sb = new StringBuilder(); lines.ForEach(line => sb.AppendLine(line)); string xmlAsString = sb.ToString().RemoveControlCharactersButWhiteSpace().Trim(); if (xmlAsString.Contains("xmlns:tts=\"http://www.w3.org/2006/04")) { return false; } if (xmlAsString.Contains("=\"http://www.w3.org/ns/ttml#parameter") && (xmlAsString.Contains(".png") || xmlAsString.Contains(".jpg") || xmlAsString.Contains(".bmp"))) { var xml = new XmlDocument { XmlResolver = null }; try { xml.LoadXml(xmlAsString); var nsmgr = new XmlNamespaceManager(xml.NameTable); nsmgr.AddNamespace("tt", xml.DocumentElement.NamespaceURI); XmlNode body = xml.DocumentElement.SelectSingleNode("//tt:body", nsmgr); if (body == null) { body = xml.DocumentElement.SelectSingleNode("//tt:body", nsmgr); } int numberOfParagraphs = body.ChildNodes.Count; return numberOfParagraphs > 0; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); return false; } } return false; } public override string ToText(Subtitle subtitle, string title) { throw new NotImplementedException(); } public override void LoadSubtitle(Subtitle subtitle, List lines, string fileName) { _errorCount = 0; var sb = new StringBuilder(); lines.ForEach(line => sb.AppendLine(line)); var xml = new XmlDocument { XmlResolver = null }; xml.LoadXml(sb.ToString().RemoveControlCharactersButWhiteSpace().Trim()); var nsmgr = new XmlNamespaceManager(xml.NameTable); nsmgr.AddNamespace("tt", xml.DocumentElement.NamespaceURI); XmlNode body = xml.DocumentElement.SelectSingleNode("//tt:body", nsmgr); if (body == null) { body = xml.DocumentElement.SelectSingleNode("//tt:body", nsmgr); } bool couldBeFrames = true; bool couldBeMillisecondsWithMissingLastDigit = true; foreach (XmlNode node in body.ChildNodes) { try { var pText = new StringBuilder(); foreach (XmlNode innerNode in node.ChildNodes) { if (innerNode.Name == "image" || innerNode.Name.EndsWith(":image", StringComparison.Ordinal)) { var src = innerNode.Attributes["src"]; if (src != null) { pText.Append(src.InnerText); } break; } } string start = null; string end = null; string dur = null; foreach (XmlAttribute attr in node.Attributes) { if (attr.Name.EndsWith("begin", StringComparison.Ordinal)) { start = attr.InnerText; } else if (attr.Name.EndsWith("end", StringComparison.Ordinal)) { end = attr.InnerText; } else if (attr.Name.EndsWith("duration", StringComparison.Ordinal)) { dur = attr.InnerText; } } if (start == null) { continue; } string text = pText.ToString(); text = text.Replace(Environment.NewLine + "", "" + Environment.NewLine); text = text.Replace("", string.Empty).Trim(); if (end != null) { if (end.Length != 11 || end.Substring(8, 1) != ":" || start == null || start.Length != 11 || start.Substring(8, 1) != ":") { couldBeFrames = false; } if (couldBeMillisecondsWithMissingLastDigit && (end.Length != 11 || start == null || start.Length != 11 || end.Substring(8, 1) != "." || start.Substring(8, 1) != ".")) { couldBeMillisecondsWithMissingLastDigit = false; } double dBegin, dEnd; if (!start.Contains(':') && Utilities.CountTagInText(start, '.') == 1 && !end.Contains(':') && Utilities.CountTagInText(end, '.') == 1 && double.TryParse(start, NumberStyles.Float, CultureInfo.InvariantCulture, out dBegin) && double.TryParse(end, NumberStyles.Float, CultureInfo.InvariantCulture, out dEnd)) { subtitle.Paragraphs.Add(new Paragraph(text, dBegin * TimeCode.BaseUnit, dEnd * TimeCode.BaseUnit)); } else { if (!couldBeFrames) { if (end.Length == 11 && end[8] == '.') // 00:05:36.92 { end += "0"; } else if (end.Length == 10 && end[8] == '.') // 00:05:36.9 { end += "00"; } if (start.Length == 11 && start[8] == '.') // 00:05:36.92 { start += "0"; } else if (start.Length == 10 && start[8] == '.') // 00:05:36.9 { start += "00"; } couldBeMillisecondsWithMissingLastDigit = false; } if (start.Length == 8 && start[2] == ':' && start[5] == ':' && end.Length == 8 && end[2] == ':' && end[5] == ':') { var p = new Paragraph(); var parts = start.Split(SplitCharColon); p.StartTime = new TimeCode(int.Parse(parts[0]), int.Parse(parts[1]), int.Parse(parts[2]), 0); parts = end.Split(SplitCharColon); p.EndTime = new TimeCode(int.Parse(parts[0]), int.Parse(parts[1]), int.Parse(parts[2]), 0); p.Text = text; subtitle.Paragraphs.Add(p); } else { subtitle.Paragraphs.Add(new Paragraph(TimedText10.GetTimeCode(start, false), TimedText10.GetTimeCode(end, false), text)); } } } else if (dur != null) { if (dur.Length != 11 || dur.Substring(8, 1) != ":" || start == null || start.Length != 11 || start.Substring(8, 1) != ":") { couldBeFrames = false; } if (couldBeMillisecondsWithMissingLastDigit && (dur.Length != 11 || start == null || start.Length != 11 || dur.Substring(8, 1) != "." || start.Substring(8, 1) != ".")) { couldBeMillisecondsWithMissingLastDigit = false; } TimeCode duration = TimedText10.GetTimeCode(dur, false); TimeCode startTime = TimedText10.GetTimeCode(start, false); var endTime = new TimeCode(startTime.TotalMilliseconds + duration.TotalMilliseconds); subtitle.Paragraphs.Add(new Paragraph(startTime, endTime, text)); } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); _errorCount++; } } subtitle.RemoveEmptyLines(); if (couldBeFrames) { bool all30OrBelow = true; foreach (Paragraph p in subtitle.Paragraphs) { if (p.StartTime.Milliseconds > 30 || p.EndTime.Milliseconds > 30) { all30OrBelow = false; } } if (all30OrBelow) { foreach (Paragraph p in subtitle.Paragraphs) { p.StartTime.Milliseconds = FramesToMillisecondsMax999(p.StartTime.Milliseconds); p.EndTime.Milliseconds = FramesToMillisecondsMax999(p.EndTime.Milliseconds); } } } else if (couldBeMillisecondsWithMissingLastDigit) { foreach (Paragraph p in subtitle.Paragraphs) { p.StartTime.Milliseconds *= 10; p.EndTime.Milliseconds *= 10; } } subtitle.Renumber(); } } }