using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Xml;
namespace Nikse.SubtitleEdit.Core.SubtitleFormats
{
// - Mom, when you were my age
what did you want to do?
public class FinalCutProXml : SubtitleFormat
{
public override string Extension => ".xml";
public override string Name => "Final Cut Pro Xml";
public static string GetFrameRateAsString()
{
if (Configuration.Settings.General.CurrentFrameRate < 24)
{
return "24"; // ntsc 23.976
}
if (Configuration.Settings.General.CurrentFrameRate < 25)
{
return "24";
}
if (Configuration.Settings.General.CurrentFrameRate < 29)
{
return "25";
}
if (Configuration.Settings.General.CurrentFrameRate < 30)
{
return "30"; // ntsc 29.97
}
if (Configuration.Settings.General.CurrentFrameRate < 40)
{
return "30";
}
if (Configuration.Settings.General.CurrentFrameRate < 60)
{
return "60"; // ntsc 59.94
}
return "60";
}
public static string GetNtsc()
{
if (Configuration.Settings.General.CurrentFrameRate < 24)
{
return "TRUE"; // ntsc 23.976
}
if (Configuration.Settings.General.CurrentFrameRate < 25)
{
return "FALSE";
}
return "TRUE";
}
public override string ToText(Subtitle subtitle, string title)
{
int duration = 0;
if (subtitle.Paragraphs.Count > 0)
{
duration = (int)Math.Round(subtitle.Paragraphs[subtitle.Paragraphs.Count - 1].EndTime.TotalSeconds * Configuration.Settings.General.CurrentFrameRate);
}
string xmlStructure =
"" + Environment.NewLine +
"" + Environment.NewLine +
"" + Environment.NewLine +
@" 5B3B0C07-9A9D-42AA-872C-C953923F97D8
add
X
" + duration + @"
" + GetNtsc() + @"
" + GetFrameRateAsString() + @"
" + GetNtsc() + @"
" + GetFrameRateAsString() + @"
00:00:00:00
0
NDF
0
" + duration + @"
";
string xmlTrackStructure =
@"
Outline Text
3000
" + GetNtsc() + @"
" + GetFrameRateAsString() + @"
1380
1474
8228
8322
TRUE
FALSE
black
Outline Text1
Outline Text
Outline Text
Text
generator
video
part1
Text Settings
str
Text
[TEXT]
font
Font
[FONTNAME]
style
Style
1
4
Plain
1
Bold
2
Italic
3
Bold/Italic
4
[FONTSTYLE]
align
Alignment
1
3
Left
1
Center
2
Right
3
2
size
Size
0
200
[FONTSIZE]
track
Tracking
0
100
1
lead
Leading
-100
100
0
aspect
Aspect
0
4
1
linewidth
Line Width
0
200
20
linesoft
Line Softness
0
100
5
textopacity
Text Opacity
0
100
100
center
Center
0.00833333
0.390741
textcolor
Text Color
255
255
255
255
supertext
Text Graphic
linecolor
Line Color
255
0
0
0
part2
Background Settings
xscale
Horizontal Size
0
200
0
yscale
Vertical Size
0
200
0
xoffset
Horizontal Offset
-100
100
0
yoffset
Vertical Offset
-100
100
0
backcolor
Back Color
255
255
255
255
superback
Back Graphic
crop
Crop
FALSE
autokern
Auto Kerning
TRUE
video
";
if (string.IsNullOrEmpty(title))
{
title = "Subtitle Edit subtitle";
}
var xml = new XmlDocument();
xml.LoadXml(xmlStructure);
xml.DocumentElement.SelectSingleNode("sequence").Attributes["id"].Value = title;
xml.DocumentElement.SelectSingleNode("sequence/name").InnerText = title;
xml.DocumentElement.SelectSingleNode("sequence/uuid").InnerText = Guid.NewGuid().ToString().ToUpperInvariant();
if (!string.IsNullOrEmpty(subtitle.Header))
{
var header = new XmlDocument();
try
{
header.LoadXml(subtitle.Header);
var node = header.DocumentElement.SelectSingleNode("sequence/uuid");
if (node != null)
{
xml.DocumentElement.SelectSingleNode("sequence/uuid").InnerText = node.InnerText;
}
}
catch
{
// ignored
}
}
XmlNode trackNode = xml.DocumentElement.SelectSingleNode("sequence/media/video/track");
const string newLine = "_____@___";
int number = 1;
foreach (Paragraph p in subtitle.Paragraphs)
{
XmlNode generatorItem = xml.CreateElement("generatoritem");
string fontStyle = "1"; //1==plain
var s = HtmlUtil.RemoveOpenCloseTags(p.Text, HtmlUtil.TagFont).Trim();
if ((s.StartsWith("") && s.EndsWith("")) || (s.StartsWith("") && s.EndsWith("")))
{
fontStyle = "4"; //4==bold/italic
}
else if (s.StartsWith("") && s.EndsWith(""))
{
fontStyle = "3"; //3==italic
}
generatorItem.InnerXml = xmlTrackStructure.Replace("[NUMBER]", number.ToString()).Replace("[FONTSTYLE]", fontStyle).
Replace("[FONTSIZE]", Configuration.Settings.SubtitleSettings.FcpFontSize.ToString(CultureInfo.InvariantCulture)).
Replace("[FONTNAME]", Configuration.Settings.SubtitleSettings.FcpFontName).
Replace("[NUMBER]", number.ToString(CultureInfo.InvariantCulture));
double frameRate = Configuration.Settings.General.CurrentFrameRate;
XmlNode start = generatorItem.SelectSingleNode("generatoritem/start");
start.InnerText = ((int)Math.Round(p.StartTime.TotalSeconds * frameRate)).ToString();
XmlNode end = generatorItem.SelectSingleNode("generatoritem/end");
end.InnerText = ((int)Math.Round(p.EndTime.TotalSeconds * frameRate)).ToString();
XmlNode text = generatorItem.SelectSingleNode("generatoritem/effect/parameter[parameterid='str']/value");
text.InnerText = HtmlUtil.RemoveHtmlTags(p.Text);
text.InnerXml = text.InnerXml.Replace(Environment.NewLine, newLine);
trackNode.AppendChild(generatorItem.SelectSingleNode("generatoritem"));
number++;
}
string xmlAsText = ToUtf8XmlString(xml);
xmlAsText = xmlAsText.Replace("xmeml[]", "xmeml");
xmlAsText = xmlAsText.Replace(newLine, "
");
return xmlAsText;
}
public override void LoadSubtitle(Subtitle subtitle, List lines, string fileName)
{
_errorCount = 0;
var 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());
var header = new XmlDocument { XmlResolver = null };
header.LoadXml(sb.ToString());
if (header.SelectSingleNode("sequence/media/video/track") != null)
{
header.RemoveChild(header.SelectSingleNode("sequence/media/video/track"));
}
subtitle.Header = header.OuterXml;
if (xml.DocumentElement.SelectSingleNode("sequence/rate") != null && xml.DocumentElement.SelectSingleNode("sequence/rate/timebase") != null)
{
try
{
frameRate = double.Parse(xml.DocumentElement.SelectSingleNode("sequence/rate/timebase").InnerText);
}
catch
{
frameRate = Configuration.Settings.General.CurrentFrameRate;
}
}
foreach (XmlNode node in xml.SelectNodes("//video/track"))
{
try
{
foreach (XmlNode generatorItemNode in node.SelectNodes("generatoritem"))
{
XmlNode rate = generatorItemNode.SelectSingleNode("rate");
if (rate != null)
{
XmlNode timebase = rate.SelectSingleNode("timebase");
if (timebase != null)
{
frameRate = double.Parse(timebase.InnerText);
}
}
double startFrame = 0;
double endFrame = 0;
XmlNode startNode = generatorItemNode.SelectSingleNode("start");
if (startNode != null)
{
startFrame = double.Parse(startNode.InnerText);
}
XmlNode endNode = generatorItemNode.SelectSingleNode("end");
if (endNode != null)
{
endFrame = double.Parse(endNode.InnerText);
}
string text = string.Empty;
foreach (XmlNode parameterNode in generatorItemNode.SelectNodes("effect/parameter[parameterid='str']"))
{
XmlNode valueNode = parameterNode.SelectSingleNode("value");
if (valueNode != null)
{
text += valueNode.InnerText;
}
}
if (text.Length == 0)
{
foreach (XmlNode parameterNode in generatorItemNode.SelectNodes("effect/parameter[parameterid='str1']"))
{
XmlNode valueNode = parameterNode.SelectSingleNode("value");
if (valueNode != null)
{
text += valueNode.InnerText;
}
}
}
if (text.Length == 0)
{
foreach (XmlNode parameterNode in generatorItemNode.SelectNodes("effect/parameter[parameterid='str2']"))
{
XmlNode valueNode = parameterNode.SelectSingleNode("value");
if (valueNode != null)
{
text += valueNode.InnerText;
}
}
}
if (text.Length == 0)
{
foreach (XmlNode parameterNode in generatorItemNode.SelectNodes("effect/parameter[parameterid='sourcetext']"))
{
XmlNode valueNode = parameterNode.SelectSingleNode("value");
if (valueNode != null)
{
text += valueNode.InnerText;
}
}
}
if (text.Length == 0)
{
foreach (XmlNode parameterNode in generatorItemNode.SelectNodes("effect/parameter[parameterid='text']"))
{
XmlNode valueNode = parameterNode.SelectSingleNode("value");
if (valueNode != null)
{
text += valueNode.InnerText;
}
}
}
bool italic = false;
bool bold = false;
foreach (XmlNode parameterNode in generatorItemNode.SelectNodes("effect/parameter[parameterid='style']"))
{
XmlNode valueNode = parameterNode.SelectSingleNode("value");
var valueEntries = parameterNode.SelectNodes("valuelist/valueentry");
if (valueNode != null)
{
int no;
if (int.TryParse(valueNode.InnerText, out no))
{
no--;
if (no < valueEntries.Count)
{
var styleNameNode = valueEntries[no].SelectSingleNode("name");
if (styleNameNode != null)
{
string styleName = styleNameNode.InnerText.ToLowerInvariant().Trim();
italic = styleName == "italic" || styleName == "bold/italic";
bold = styleName == "bold" || styleName == "bold/italic";
}
}
}
}
}
if (!bold && !italic)
{
foreach (XmlNode parameterNode in generatorItemNode.SelectNodes("effect/parameter[parameterid='fontstyle']"))
{
XmlNode valueNode = parameterNode.SelectSingleNode("value");
var valueEntries = parameterNode.SelectNodes("valuelist/valueentry");
if (valueNode != null)
{
int no;
if (int.TryParse(valueNode.InnerText, out no))
{
no--;
if (no < valueEntries.Count)
{
var styleNameNode = valueEntries[no].SelectSingleNode("name");
if (styleNameNode != null)
{
string styleName = styleNameNode.InnerText.ToLowerInvariant().Trim();
italic = styleName == "italic" || styleName == "bold/italic";
bold = styleName == "bold" || styleName == "bold/italic";
}
}
}
}
}
}
if (text.Length > 0)
{
if (!text.Contains(Environment.NewLine))
{
text = text.Replace("\r", Environment.NewLine);
}
if (bold)
{
text = "" + text + "";
}
if (italic)
{
text = "" + text + "";
}
subtitle.Paragraphs.Add(new Paragraph(text, Convert.ToDouble((startFrame / frameRate) * 1000), Convert.ToDouble((endFrame / frameRate) * 1000)));
}
}
}
catch
{
_errorCount++;
}
}
subtitle.Renumber();
}
catch
{
_errorCount = 1;
return;
}
Configuration.Settings.General.CurrentFrameRate = frameRate;
}
}
}