using System; using System.Collections.Generic; using System.Text; using System.Xml; namespace Nikse.SubtitleEdit.Core.SubtitleFormats { public class FinalCutProXmlGap : SubtitleFormat { public double FrameRate { get; set; } public override string Extension => ".fcpxml"; public override string Name => "Final Cut Xml Gap"; public override string ToText(Subtitle subtitle, string title) { if (Configuration.Settings.General.CurrentFrameRate > 26) { FrameRate = 30; } else { FrameRate = 25; } string xmlStructure = "" + Environment.NewLine + "" + Environment.NewLine + Environment.NewLine + "" + Environment.NewLine + " " + Environment.NewLine + " " + Environment.NewLine + " " + Environment.NewLine + // // // // " " + Environment.NewLine + " " + Environment.NewLine + " " + Environment.NewLine + " " + Environment.NewLine + " " + Environment.NewLine + " " + Environment.NewLine + " " + Environment.NewLine + " " + Environment.NewLine + ""; string xmlClipStructure = " " + Environment.NewLine + " <adjust-transform position=\"0.267518 -32.3158\"/>" + Environment.NewLine + " <text></text>" + Environment.NewLine + " "; var xml = new XmlDocument(); xml.LoadXml(xmlStructure); XmlNode videoNode = xml.DocumentElement.SelectSingleNode("project/sequence/spine/gap"); int number = 1; foreach (Paragraph p in subtitle.Paragraphs) { XmlNode titleNode = xml.CreateElement("holder"); titleNode.InnerXml = xmlClipStructure; titleNode = titleNode.SelectSingleNode("title"); titleNode.Attributes["offset"].Value = Convert.ToInt64(p.StartTime.TotalSeconds * 60000) + "/60000s"; titleNode.Attributes["name"].Value = HtmlUtil.RemoveHtmlTags(p.Text); titleNode.Attributes["duration"].Value = Convert.ToInt64(p.Duration.TotalSeconds * 60000) + "/60000s"; titleNode.Attributes["start"].Value = Convert.ToInt64(p.StartTime.TotalSeconds * 60000) + "/60000s"; titleNode.SelectSingleNode("text").InnerText = HtmlUtil.RemoveHtmlTags(p.Text); videoNode.AppendChild(titleNode); number++; } string xmlAsText = ToUtf8XmlString(xml); xmlAsText = xmlAsText.Replace("fcpxml[]", "fcpxml"); xmlAsText = xmlAsText.Replace("fcpxml []", "fcpxml"); return xmlAsText; } public override void LoadSubtitle(Subtitle subtitle, List lines, string fileName) { _errorCount = 0; FrameRate = Configuration.Settings.General.CurrentFrameRate; var sb = new StringBuilder(); lines.ForEach(line => sb.AppendLine(line)); var xml = new XmlDocument { XmlResolver = null }; try { xml.LoadXml(sb.ToString().Trim()); foreach (XmlNode node in xml.SelectNodes("fcpxml/project/sequence/spine/gap")) { try { foreach (XmlNode title in node.SelectNodes("title")) { var textNodes = title.SelectNodes("text"); if (textNodes != null && textNodes.Count > 0) { Paragraph p = new Paragraph(); p.StartTime = DecodeTime(title.Attributes["offset"]); p.EndTime.TotalMilliseconds = p.StartTime.TotalMilliseconds + DecodeTime(title.Attributes["duration"]).TotalMilliseconds; var text = new StringBuilder(); foreach (XmlNode textNode in textNodes) { text.AppendLine(textNode.InnerText); } p.Text = text.ToString().Trim(); subtitle.Paragraphs.Add(p); } } } catch { _errorCount++; } } subtitle.Renumber(); } catch { _errorCount = 1; return; } } private static TimeCode DecodeTime(XmlAttribute duration) { // 220220/60000s if (duration != null) { var arr = duration.Value.TrimEnd('s').Split('/'); if (arr.Length == 2) { return TimeCode.FromSeconds(long.Parse(arr[0]) / double.Parse(arr[1])); } else if (arr.Length == 1) { return TimeCode.FromSeconds(float.Parse(arr[0])); } } return new TimeCode(); } } }