SubtitleEdit/libse/SubtitleFormats/Xif.cs

238 lines
11 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Xml;
namespace Nikse.SubtitleEdit.Core.SubtitleFormats
{
class Xif : SubtitleFormat
{
public override string Extension
{
get { return ".xif"; }
}
public override string Name
{
get { return "XIF"; }
}
public override bool IsTimeBased
{
get { return true; }
}
public override bool IsMine(List<string> lines, string fileName)
{
var subtitle = new Subtitle();
LoadSubtitle(subtitle, lines, fileName);
return subtitle.Paragraphs.Count > _errorCount;
}
public override string ToText(Subtitle subtitle, string title)
{
const string xmpTemplate = @"<XIF version='1.0' filename='file:///filename.xif'>
<FileHeader>
<OriginatorSoftware Name='FileConverter' Version='1.01'/>
<GlobalFileInfo ProgrammeName='NAME' Format='Open' Language='English' StartTime='00:00:00:00' StopTime='00:00:00:00' TotalLength='00:00:00:00' ProgrammeTitle='TITLE' NumberOfCaptions='405' RevisionNumber='0' CountryIndex='0' CharacterCodeTable='0' MaxColumns='0' EpisodeNumber='0' BlinkOption='yes' CaptionService='0' CurrentVideo='2' Videosize='0' BaseOffset='0' TopOffset='0' DefaultSave='0' DisplaySafwWidth='576' SafeAreaWidthPercent='80' DisplaySafeHoriz='0' SafeAreaHorizPos='0' CurrentStyle='None' StyleDate='None' StyleTime='None' SAOLeft='72' SAORight='0' VideoStartTC='00:00:00:00' SpellLanguage='English' CGConfig='0'/>
<ReadingSpeeds>
<ReadingSpeed ID='0' WordsPerMinute='180' CharsPerMinute='720' CharsPerSecond='12' PTS='WPM'/>
</ReadingSpeeds>
<Canvases>
<Canvas ID='0' Width='720' Height='576' SAWidth='576' SAHeight='460' SATop='57' SALeft='72'/>
</Canvases>
<FormattingInfo TabWidth='3'/>
<TimingStandards>
<TimingStandard ID='0' Type='SMPTE25'/>
</TimingStandards>
<Fonts>
<Font ID='0' Type='True Type' Typeface='Courier New' Height='0' AstonFountSize='185' Ascent='268' Descent='97' IntLead='43'/>
<Font ID='1' Type='True Type' Typeface='Courier New' Height='0' AstonFountSize='185' Ascent='268' Descent='97' IntLead='43'/>
<Font ID='2' Type='True Type' Typeface='Hairy Helix' Height='0' AstonFountSize='12' Ascent='15' Descent='3' IntLead='2'/>
<Font ID='3' Type='True Type' Typeface='Hairy Helix Italics' Height='0' AstonFountSize='12' Ascent='15' Descent='3' IntLead='2'/>
<Font ID='4' Type='True Type' Typeface='Arial' Height='35' AstonFountSize='22' Ascent='28' Descent='7' IntLead='5'/>
<Font ID='5' Type='True Type' Typeface='Arial Narrow' Height='35' AstonFountSize='25' Ascent='30' Descent='5' IntLead='0'/>
<Font/>
</Fonts>
<Colours>
<Colour ID='0' value='0x000000'/>
<Colour ID='1' value='0xFF0000'/>
<Colour ID='2' value='0x00FF00'/>
<Colour ID='3' value='0xFFFF00'/>
<Colour ID='4' value='0x0000FF'/>
<Colour ID='5' value='0xFF00FF'/>
<Colour ID='6' value='0x00FFFF'/>
<Colour ID='7' value='0xFFFFFF'/>
</Colours>
<Threads>
<Thread ID='0' CanvasID='0' TimingStandardID='0' ReadingSpeedID='0' Medium='Open' Name='Default'/>
</Threads>
<WordLists/>
<TxStreams>
<TxStream ID='0' Active='yes'/>
</TxStreams>
</FileHeader>
<FileBody>
<ProgrammeBlock NoOfAssociatedIDs='0'>
<SceneBaseBlock NewsID='0' Position='0' NewsRoomInternalID='0'/>
</ProgrammeBlock>
<StoryBlock TimingType='Manual'>
<SceneBaseBlock NewsID='0' Position='0' NewsRoomInternalID='0'/>
</StoryBlock>
</FileBody>
</XIF>";
const string paragraphTemplate = @"
<ContentBlock TXStreamID='0' uid='B4EB0AE5-B82F-48ea-9FD4-C4194D004041'>
<ThreadedObject ID='0' Type='BasicSubtitle' VPos='448'>
<Note/>
<TimingObject>
<TimeIn value='10:00:03:06'/>
<TimeOut value='10:00:05:09'/>
</TimingObject>
<Content Override='0' RaisedRowUp='0' RaisedRowDown='0' DirectionState='0' PositionState='1' ConfidenceValue='0'>
<SubtitleText>
<Paragraph Type='Open' Justification='Centre' LineLimit='15' CharactersPerRow='99' Alignment='None' Language='ENG' VideoLanguage='ENG' Xpercent='0.0' Ypercent='0.0'>
<StartState Foreground='7' Background='0' Flash='No' Underline='No' Italic='No' Bold='No' Indent='0' SBOnOff='Yes' FontID='4'/>
<Boxing Colour='0' Transparency='0' Type='None'/>
<AntiAliasing Style='Default'/>
</Paragraph>
</SubtitleText>
</Content>
</ThreadedObject>
</ContentBlock>";
var xml = new XmlDocument();
var lastTimeCode = new TimeCode(0);
if (subtitle.Paragraphs.Count > 0)
{
lastTimeCode = subtitle.Paragraphs[subtitle.Paragraphs.Count - 1].StartTime;
}
if (string.IsNullOrWhiteSpace(title))
title = "Unknown";
xml.LoadXml(xmpTemplate.Replace('\'', '"'));
var globalFileInfoNode = xml.DocumentElement.SelectSingleNode("FileHeader/GlobalFileInfo");
globalFileInfoNode.Attributes["ProgrammeName"].InnerText = title;
globalFileInfoNode.Attributes["ProgrammeTitle"].InnerText = title;
globalFileInfoNode.Attributes["StopTime"].InnerText = lastTimeCode.ToHHMMSSFF();
globalFileInfoNode.Attributes["NumberOfCaptions"].InnerText = subtitle.Paragraphs.Count.ToString(CultureInfo.InvariantCulture);
var fileBodyNode = xml.DocumentElement.SelectSingleNode("FileBody");
foreach (Paragraph p in subtitle.Paragraphs)
{
XmlNode content = xml.CreateElement("ContentBlock");
content.InnerXml = paragraphTemplate;
content.SelectSingleNode("ContentBlock/ThreadedObject/TimingObject/TimeIn").InnerText = p.StartTime.ToHHMMSSFF();
content.SelectSingleNode("ContentBlock/ThreadedObject/TimingObject/TimeOut").InnerText = p.EndTime.ToHHMMSSFF();
var paragraphNode = content.SelectSingleNode("ContentBlock/ThreadedObject/Content/SubtitleText/Paragraph");
var lines = HtmlUtil.RemoveHtmlTags(p.Text, true).SplitToLines();
for (int i = 1; i < lines.Length + 1; i++)
{
var rowNode = xml.CreateElement("Row");
var attrNumber = xml.CreateAttribute("Number");
attrNumber.InnerText = i.ToString(CultureInfo.InvariantCulture);
rowNode.Attributes.Append(attrNumber);
var attrJust = xml.CreateAttribute("JustificationOverride");
attrJust.InnerText = "Centre";
rowNode.Attributes.Append(attrJust);
var attrHighlight = xml.CreateAttribute("Highlight");
attrHighlight.InnerText = "0";
rowNode.Attributes.Append(attrHighlight);
paragraphNode.AppendChild(rowNode);
}
for (int index = 0; index < lines.Length; index++)
{
var line = lines[index];
if (index > 0)
{
var hardReturnNode = xml.CreateElement("HardReturn");
paragraphNode.AppendChild(hardReturnNode);
}
var foregroundColorNode = xml.CreateElement("ForegroundColour");
var attrColor = xml.CreateAttribute("Colour");
attrColor.InnerText = "7";
foregroundColorNode.Attributes.Append(attrColor);
paragraphNode.AppendChild(foregroundColorNode);
var textNode = xml.CreateElement("Text");
textNode.InnerText = line;
paragraphNode.AppendChild(textNode);
}
fileBodyNode.AppendChild(content.SelectSingleNode("ContentBlock"));
}
return ToUtf8XmlString(xml, true).Replace(" xmlns=\"\"", string.Empty);
}
public override void LoadSubtitle(Subtitle subtitle, List<string> lines, string fileName)
{
_errorCount = 0;
var sb = new StringBuilder();
lines.ForEach(line => sb.AppendLine(line));
var xmlAsText = sb.ToString().Trim();
if (!xmlAsText.Contains("<XIF") || !xmlAsText.Contains("<SubtitleText"))
{
return;
}
try
{
var xml = new XmlDocument { XmlResolver = null };
xml.LoadXml(xmlAsText);
foreach (XmlNode node in xml.DocumentElement.SelectNodes("FileBody/ContentBlock"))
{
try
{
var timeCodeIn = DecodeTimeCode(node.SelectSingleNode("ThreadedObject/TimingObject/TimeIn").Attributes["value"].InnerText);
var timeCodeOut = DecodeTimeCode(node.SelectSingleNode("ThreadedObject/TimingObject/TimeOut").Attributes["value"].InnerText);
sb.Clear();
foreach (XmlNode paragraphNode in node.SelectSingleNode("ThreadedObject/Content/SubtitleText/Paragraph").ChildNodes)
{
if (paragraphNode.Name == "Text")
sb.Append(" " + paragraphNode.InnerText);
else if (paragraphNode.Name == "HardReturn")
sb.AppendLine();
}
var p = new Paragraph(timeCodeIn, timeCodeOut, sb.ToString().Replace(" ", " ").Trim());
subtitle.Paragraphs.Add(p);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
_errorCount++;
}
}
subtitle.Renumber();
}
catch (Exception)
{
_errorCount++;
}
}
private static TimeCode DecodeTimeCode(string timeCode)
{
//00:00:07:12
var parts = timeCode.Split(':');
var hour = int.Parse(parts[0]);
var minutes = int.Parse(parts[1]);
var seconds = int.Parse(parts[2]);
var frames = int.Parse(parts[3]);
return new TimeCode(hour, minutes, seconds, FramesToMillisecondsMax999(frames));
}
}
}