diff --git a/src/Logic/SubtitleFormats/Csv.cs b/src/Logic/SubtitleFormats/Csv.cs new file mode 100644 index 000000000..1b51f0ebe --- /dev/null +++ b/src/Logic/SubtitleFormats/Csv.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace Nikse.SubtitleEdit.Logic.SubtitleFormats +{ + public class Csv : SubtitleFormat + { + private string _seperator = ";"; + + public override string Extension + { + get { return ".csv"; } + } + + public override string Name + { + get { return "csv"; } + } + + public override bool HasLineNumber + { + get { return true; } + } + + public override bool IsTimeBased + { + get { return true; } + } + + public override bool IsMine(List lines, string fileName) + { + Regex csvLine = new Regex(@"^""?\d+""?" + _seperator + @"""?\d+""?" + _seperator + @"""?\d+""?" + _seperator + @"""?[^""]*""?$", RegexOptions.Compiled); + int fine = 0; + int failed = 0; + foreach (string line in lines) + { + if (csvLine.IsMatch(line)) + fine++; + else + failed++; + + } + return fine > failed; + } + + public override string ToText(Subtitle subtitle, string title) + { + string format = "{1}{0}{2}{0}{3}{0}\"{4}\""; + StringBuilder sb = new StringBuilder(); + sb.AppendLine(string.Format(format, _seperator, "Number", "Start time in milliseconds", "End time in milliseconds", "Text")); + foreach (Paragraph p in subtitle.Paragraphs) + { + sb.AppendLine(string.Format(format, _seperator, p.Number, p.StartTime.TotalMilliseconds, p.EndTime.TotalMilliseconds, p.Text.Replace(Environment.NewLine, "\n"))); + } + return sb.ToString().Trim(); + } + + public override void LoadSubtitle(Subtitle subtitle, List lines, string fileName) + { + Regex csvLine = new Regex(@"^""?\d+""?" + _seperator + @"""?\d+""?" + _seperator + @"""?\d+""?" + _seperator + @"""?[^""]*""?$", RegexOptions.Compiled); + _errorCount = 0; + + foreach (string line in lines) + { + if (csvLine.IsMatch(line)) + { + string[] parts = line.Split(_seperator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries); + if (parts.Length == 4) + try + { + int start = Convert.ToInt32(FixQuotes(parts[1])); + int end = Convert.ToInt32(FixQuotes(parts[2])); + string text = FixQuotes(parts[3]); + + subtitle.Paragraphs.Add(new Paragraph(text, start, end)); + } + catch + { + _errorCount++; + } + } + else + { + _errorCount++; + } + } + subtitle.Renumber(1); + } + + private static string FixQuotes(string text) + { + if (string.IsNullOrEmpty(text)) + return text; + + if (text.StartsWith("\"") && text.Length > 1) + text = text.Substring(1); + + if (text.EndsWith("\"") && text.Length > 1) + text = text.Substring(0, text.Length-2); + + return text.Replace("\"\"", "\""); + } + } +} diff --git a/src/Logic/SubtitleFormats/DvdStudioPro.cs b/src/Logic/SubtitleFormats/DvdStudioPro.cs new file mode 100644 index 000000000..0c7ac3a17 --- /dev/null +++ b/src/Logic/SubtitleFormats/DvdStudioPro.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace Nikse.SubtitleEdit.Logic.SubtitleFormats +{ + public class DvdStudioPro : SubtitleFormat + { + readonly Regex _regexTimeCodes = new Regex(@"^\d+:\d+:\d+:\d+\t,\t\d+:\d+:\d+:\d+\t,\t.*$", RegexOptions.Compiled); + + public override string Extension + { + get { return ".STL"; } + } + + public override string Name + { + get { return "DVD Studio Pro"; } + } + + public override bool HasLineNumber + { + get { return false; } + } + + public override bool IsTimeBased + { + get { return true; } + } + + public override bool IsMine(List lines, string fileName) + { + Subtitle subtitle = new Subtitle(); + LoadSubtitle(subtitle, lines, fileName); + return subtitle.Paragraphs.Count > _errorCount; + } + + public override string ToText(Subtitle subtitle, string title) + { + const string paragraphWriteFormat = "{0}\t,\t{1}\t,\t{2}\r\n"; + const string timeFormat = "{0:00}:{1:00}:{2:00}:{3:00}"; + const string header = @"$VertAlign = Bottom +$Bold = FALSE +$Underlined = FALSE +$Italic = 0 +$XOffset = 0 +$YOffset = -5 +$TextContrast = 15 +$Outline1Contrast = 15 +$Outline2Contrast = 13 +$BackgroundContrast = 0 +$ForceDisplay = FALSE +$FadeIn = 0 +$FadeOut = 0 +$HorzAlign = Center +"; + + StringBuilder sb = new StringBuilder(); + sb.AppendLine(header); + foreach (Paragraph p in subtitle.Paragraphs) + { + string startTime = string.Format(timeFormat, p.StartTime.Hours, p.StartTime.Minutes, p.StartTime.Seconds, (int)Math.Round((p.StartTime.Milliseconds / 10.0) / 4.0)); + string endTime = string.Format(timeFormat, p.EndTime.Hours, p.EndTime.Minutes, p.EndTime.Seconds, (int)Math.Round((p.EndTime.Milliseconds / 10.0) / 4.0)); + sb.Append(string.Format(paragraphWriteFormat, startTime, endTime, p.Text.Replace(Environment.NewLine, " | "))); + } + return sb.ToString().Trim(); + } + + public override void LoadSubtitle(Subtitle subtitle, List lines, string fileName) + { + _errorCount = 0; + int number = 0; + foreach (string line in lines) + { + if (line.Trim().Length > 0 && line[0] != '$') + { + if (_regexTimeCodes.Match(line).Success) + { + string[] threePart = line.Split(new[] { "\t,\t"}, StringSplitOptions.None); + Paragraph p = new Paragraph(); + if (threePart.Length == 3 && + GetTimeCode(p.StartTime, threePart[0]) && + GetTimeCode(p.EndTime, threePart[1])) + { + number++; + p.Number = number; + p.Text = threePart[2].TrimEnd().Replace(" | ", Environment.NewLine).Replace("|", Environment.NewLine); + subtitle.Paragraphs.Add(p); + } + } + else + { + _errorCount++; + } + } + else + { + _errorCount++; + } + } + } + + private static bool GetTimeCode(TimeCode timeCode, string timeString) + { + try + { + string[] timeParts = timeString.Split(':'); + timeCode.Hours = int.Parse(timeParts[0]); + timeCode.Minutes = int.Parse(timeParts[1]); + timeCode.Seconds = int.Parse(timeParts[2]); + int milliseconds = int.Parse(timeParts[3]); + timeCode.Milliseconds = (int)Math.Round(milliseconds * 4.0 * 10.0); + return true; + } + catch + { + return false; + } + } + } +} diff --git a/src/Logic/SubtitleFormats/DvdSubtitle.cs b/src/Logic/SubtitleFormats/DvdSubtitle.cs new file mode 100644 index 000000000..c52db089c --- /dev/null +++ b/src/Logic/SubtitleFormats/DvdSubtitle.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace Nikse.SubtitleEdit.Logic.SubtitleFormats +{ + public class DvdSubtitle : SubtitleFormat + { + public override string Extension + { + get { return ".sub"; } + } + + public override string Name + { + get { return "DVDSubtitle"; } + } + + public override bool HasLineNumber + { + get { return false; } + } + + public override bool IsTimeBased + { + get { return true; } + } + + public override bool IsMine(List lines, string fileName) + { + Subtitle subtitle = new Subtitle(); + LoadSubtitle(subtitle, lines, fileName); + return subtitle.Paragraphs.Count > _errorCount; + } + + public override string ToText(Subtitle subtitle, string title) + { + const string paragraphWriteFormat = "T {0}\r\n{1}\r\n"; + const string timeFormat = "{0:00}:{1:00}:{2:00}:{3:00}"; + const string header = @"{HEAD +DISCID= +DVDTITLE= +CODEPAGE=1250 +FORMAT=ASCII +LANG= +TITLE=1 +ORIGINAL=ORIGINAL +AUTHOR= +WEB= +INFO= +LICENSE= +}"; + + StringBuilder sb = new StringBuilder(); + sb.AppendLine(header); + foreach (Paragraph p in subtitle.Paragraphs) + { + int milliseconds = p.StartTime.Milliseconds / 10; + string time = string.Format(timeFormat, p.StartTime.Hours, p.StartTime.Minutes, p.StartTime.Seconds, milliseconds); + sb.AppendLine("{" + string.Format(paragraphWriteFormat, time, p.Text)+ "}"); + + milliseconds = p.EndTime.Milliseconds / 10; + time = string.Format(timeFormat, p.EndTime.Hours, p.EndTime.Minutes, p.EndTime.Seconds, milliseconds); + sb.AppendLine("{" + string.Format(paragraphWriteFormat, time, string.Empty) + "}"); + } + return sb.ToString().Trim(); + } + + public override void LoadSubtitle(Subtitle subtitle, List lines, string fileName) + { +//{T 00:03:14:27 +//Some text +//} + Regex regexTimeCodes = new Regex(@"^\{T\ \d+:\d+:\d+:\d+$", RegexOptions.Compiled); + bool textOn = false; + string text = string.Empty; + TimeSpan start = TimeSpan.FromMilliseconds(0); + TimeSpan end = TimeSpan.FromMilliseconds(0); + foreach (string line in lines) + { + if (textOn) + { + if (line.Trim() == "}") + { + Paragraph p = new Paragraph(); + p.Text = text; + p.StartTime = new TimeCode(start); + p.EndTime = new TimeCode(end); + + subtitle.Paragraphs.Add(p); + + text = string.Empty; + start = TimeSpan.FromMilliseconds(0); + end = TimeSpan.FromMilliseconds(0); + textOn = false; + } + else + { + if (text.Length == 0) + text = line; + else + text += Environment.NewLine + line; + } + } + else + { + if (regexTimeCodes.Match(line).Success) + { + try + { + textOn = true; + string[] arr = line.Substring(3).Trim().Split(':'); + if (arr.Length == 4) + { + int hours = int.Parse(arr[0]); + int minutes = int.Parse(arr[1]); + int seconds = int.Parse(arr[2]); + int milliseconds = int.Parse(arr[3]); + if (arr[3].Length == 2) + milliseconds *= 10; + start = new TimeSpan(0, hours, minutes, seconds, milliseconds); // FIXED timestamp - needed a zero as first parameter!! + } + } + catch + { + textOn = false; + _errorCount++; + } + } + } + } + + int index = 1; + foreach (Paragraph p in subtitle.Paragraphs) + { + Paragraph next = subtitle.GetParagraphOrDefault(index); + if (next != null) + { + p.EndTime.TotalMilliseconds = next.StartTime.TotalMilliseconds - 1; + } + index++; + } + + subtitle.RemoveEmptyLines(); + + subtitle.Renumber(1); + } + } +} diff --git a/src/Logic/SubtitleFormats/Ebu.cs b/src/Logic/SubtitleFormats/Ebu.cs new file mode 100644 index 000000000..de40b2967 --- /dev/null +++ b/src/Logic/SubtitleFormats/Ebu.cs @@ -0,0 +1,234 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using System.IO; + +namespace Nikse.SubtitleEdit.Logic.SubtitleFormats +{ + /// + /// EBU Subtitling data exchange format + /// + public class Ebu : SubtitleFormat + { + + /// + /// GSI block (1024 bytes) + /// + private class EbuGeneralSubtitleInformation + { + public string CodePageNumber { get; set; } // 0..2 + public string DiskFormatCode { get; set; } // 3..10 + public string DisplayStandardCode { get; set; } // 11 + public string CharacterCodeTableNumber { get; set; } // 12..13 + public string LanguageCode { get; set; } // 14..15 + public string OriginalProgrammeTitle { get; set; } // 16..47 + public string OriginalEpisodeTitle { get; set; } + public string TranslatedProgrammeTitle { get; set; } + public string TranslatedEpisodeTitle { get; set; } + public string TranslatorsName { get; set; } + public string TranslatorsContactDetails { get; set; } + public string SubtitleListReferenceCode { get; set; } + public string CreationDate { get; set; } + public string RevisionDate { get; set; } + public string RevisionNumber { get; set; } + public string TotalNumberOfTextAndTimingInformationBlocks { get; set; } + public string TotalNumberOfSubtitles { get; set; } + public string TotalNumberOfSubtitleGroups { get; set; } + public string MaximumNumberOfDisplayableCharactersInAnyTextRow { get; set; } + public string MaximumNumberOfDisplayableRows { get; set; } + public string TimeCodeStatus { get; set; } + public string TimeCodeStartOfProgramme { get; set; } + public string TimeCodeFirstInCue { get; set; } + public string TotalNumberOfDisks { get; set; } + public string DiskSequenceNumber { get; set; } + public string CountryOfOrigin { get; set; } + public string Publisher { get; set; } + public string EditorsName { get; set; } + public string EditorsContactDetails { get; set; } + public string SpareBytes { get; set; } + public string UserDefinedArea { get; set; } + + public double FrameRate + { + get + { + if (DiskFormatCode.StartsWith("STL25")) + return 25.0; + else + return 30.0; // should be DiskFormatcode STL30.01 + } + } + } + + /// + /// TTI block 128 bytes + /// + private class EbuTextTimingInformation + { + public string SubtitleGroupNumber { get; set; } + public string SubtitleNumber { get; set; } + public string ExtensionBlockNumber { get; set; } + public string CumulativeStatus { get; set; } + public int TimeCodeInHours { get; set; } + public int TimeCodeInMinutes { get; set; } + public int TimeCodeInSeconds { get; set; } + public int TimeCodeInMilliseconds { get; set; } + public int TimeCodeOutHours { get; set; } + public int TimeCodeOutMinutes { get; set; } + public int TimeCodeOutSeconds { get; set; } + public int TimeCodeOutMilliseconds { get; set; } + public string VerticalPosition { get; set; } + public string JustificationCode { get; set; } + public byte CommentFlag { get; set; } + public string TextField { get; set; } + } + + public override string Extension + { + get { return ".stl"; } + } + + public override string Name + { + get { return "EBU stl"; } + } + + public override bool HasLineNumber + { + get { return true; } + } + + public override bool IsTimeBased + { + get { return true; } + } + + public override bool IsMine(List lines, string fileName) + { + if (!string.IsNullOrEmpty(fileName) && File.Exists(fileName)) + { + FileInfo fi = new FileInfo(fileName); + if (fi.Length > 1024 + 128 && fi.Length < 1024000) // not too small or too big + { + byte[] buffer = File.ReadAllBytes(fileName); + EbuGeneralSubtitleInformation header = ReadHeader(buffer); + if (header.DiskFormatCode.StartsWith("STL25") || + header.DiskFormatCode.StartsWith("STL30")) + { + return Utilities.IsInteger(header.CodePageNumber) && + Utilities.IsInteger(header.TotalNumberOfSubtitles); + } + } + } + return false; + } + + public override string ToText(Subtitle subtitle, string title) + { + return "Not supported!"; + } + + public override void LoadSubtitle(Subtitle subtitle, List lines, string fileName) + { + subtitle.Paragraphs.Clear(); + byte[] buffer = File.ReadAllBytes(fileName); + EbuGeneralSubtitleInformation header = ReadHeader(buffer); + foreach (EbuTextTimingInformation tti in ReadTTI(buffer, header)) + { + Paragraph p = new Paragraph(); + p.Text = tti.TextField; + p.StartTime = new TimeCode(tti.TimeCodeInHours, tti.TimeCodeInMinutes, tti.TimeCodeInSeconds, tti.TimeCodeInMilliseconds); + p.EndTime = new TimeCode(tti.TimeCodeOutHours, tti.TimeCodeOutMinutes, tti.TimeCodeOutSeconds, tti.TimeCodeOutMilliseconds); + subtitle.Paragraphs.Add(p); + } + subtitle.Renumber(1); + } + + private EbuGeneralSubtitleInformation ReadHeader(byte[] buffer) + { + EbuGeneralSubtitleInformation header = new EbuGeneralSubtitleInformation(); + header.CodePageNumber = Encoding.ASCII.GetString(buffer, 0, 3); + header.DiskFormatCode = Encoding.ASCII.GetString(buffer, 3, 8); + header.DisplayStandardCode = Encoding.ASCII.GetString(buffer, 11, 1); + header.CharacterCodeTableNumber = Encoding.ASCII.GetString(buffer, 12, 2); + header.LanguageCode = Encoding.ASCII.GetString(buffer, 14, 2); + header.OriginalProgrammeTitle = Encoding.ASCII.GetString(buffer, 16, 32); + header.OriginalEpisodeTitle = Encoding.ASCII.GetString(buffer, 48, 32); + header.TranslatedProgrammeTitle = Encoding.ASCII.GetString(buffer, 80, 32); + header.TranslatedEpisodeTitle = Encoding.ASCII.GetString(buffer, 112, 32); + header.TranslatorsName = Encoding.ASCII.GetString(buffer, 144, 32); + header.TranslatorsContactDetails = Encoding.ASCII.GetString(buffer, 176, 32); + header.SubtitleListReferenceCode = Encoding.ASCII.GetString(buffer, 208, 16); + header.CreationDate = Encoding.ASCII.GetString(buffer, 224, 6); + header.RevisionDate = Encoding.ASCII.GetString(buffer, 230, 6); + header.RevisionNumber = Encoding.ASCII.GetString(buffer, 236, 2); + header.TotalNumberOfTextAndTimingInformationBlocks = Encoding.ASCII.GetString(buffer, 238, 5); + header.TotalNumberOfSubtitles = Encoding.ASCII.GetString(buffer, 243, 5); + header.TotalNumberOfSubtitleGroups = Encoding.ASCII.GetString(buffer, 248, 3); + header.MaximumNumberOfDisplayableCharactersInAnyTextRow = Encoding.ASCII.GetString(buffer, 251, 2); + header.MaximumNumberOfDisplayableRows = Encoding.ASCII.GetString(buffer, 253, 2); + header.TimeCodeStatus = Encoding.ASCII.GetString(buffer, 255, 1); + header.TimeCodeStartOfProgramme = Encoding.ASCII.GetString(buffer, 256, 8); + + return header; + } + + private IEnumerable ReadTTI(byte[] buffer, EbuGeneralSubtitleInformation header) + { + const int StartOfTTI = 1024; + const int TTISize = 128; + const byte TextFieldCRLF = 0x8A; + const byte TextFieldTerminator = 0x8F; + + Encoding encoding = Encoding.Default; + //try + //{ + // if (header.CharacterCodeTableNumber == "00") + // encoding = Encoding.GetEncoding("ISO-8859-1"); + // else + // encoding = Encoding.GetEncoding(int.Parse(header.CodePageNumber)); + //} + //catch + //{ + // // will fall-back to default encoding + //} + List list = new List(); + int index = StartOfTTI; + while (index + TTISize < buffer.Length) + { + var tti = new EbuTextTimingInformation(); + + tti.TimeCodeInHours = buffer[index + 5 + 0]; + tti.TimeCodeInMinutes = buffer[index + 5 + 1]; + tti.TimeCodeInSeconds = buffer[index + 5 + 2]; + tti.TimeCodeInMilliseconds = (int)(1000.0 / (header.FrameRate / buffer[index + 5 + 3])); + + tti.TimeCodeOutHours = buffer[index + 9 + 0]; + tti.TimeCodeOutMinutes = buffer[index + 9 + 1]; + tti.TimeCodeOutSeconds = buffer[index + 9 + 2]; + tti.TimeCodeOutMilliseconds = (int)(1000 / (header.FrameRate / buffer[index + 9 + 3])); + tti.CommentFlag = buffer[index + 15]; + + // text + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 112; i++) + { + if (buffer[index + 16 + i] == TextFieldCRLF) + sb.AppendLine(); + else if (buffer[index + 16 + i] == TextFieldTerminator) + break; + else + sb.Append(encoding.GetString(buffer, index+16+i, 1)); + } + tti.TextField = sb.ToString(); + + + index += TTISize; + list.Add(tti); + } + return list; + } + + } +} \ No newline at end of file diff --git a/src/Logic/SubtitleFormats/Idx.cs b/src/Logic/SubtitleFormats/Idx.cs new file mode 100644 index 000000000..85f1629d0 --- /dev/null +++ b/src/Logic/SubtitleFormats/Idx.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace Nikse.SubtitleEdit.Logic.SubtitleFormats +{ + //TODO: Working on added edit cababilities for idx files.... + public class Idx : SubtitleFormat + { + // timestamp: 00:00:01:401, filepos: 000000000 + readonly Regex _regexTimeCodes = new Regex(@"^timestamp: \d+:\d+:\d+:\d+, filepos: [\dabcdefABCDEF]+$", RegexOptions.Compiled); + + public Hashtable NonTimeCodes = new Hashtable(); + + public override string Extension + { + get { return ".idx"; } + } + + public override string Name + { + get { return "VobSub index file"; } + } + + public override bool HasLineNumber + { + get { return false; } + } + + public override bool IsTimeBased + { + get { return true; } + } + + public override bool IsMine(List lines, string fileName) + { + int subtitleCount = 0; + foreach (string line in lines) + { + if (line.StartsWith("timestamp: ")) + subtitleCount++; + } + return subtitleCount > 10; + } + + public override string ToText(Subtitle subtitle, string title) + { + // timestamp: 00:00:01:401, filepos: 000000000 + + const string paragraphWriteFormat = "timestamp: {0}, filepos: {1}"; + + var tempNonTimeCodes = new Hashtable(); + foreach (DictionaryEntry de in (subtitle.OriginalFormat as Idx).NonTimeCodes) + { + tempNonTimeCodes.Add(de.Key, de.Value); + } + + var sb = new StringBuilder(); + foreach (Paragraph p in subtitle.Paragraphs) + { + var removeList = new List(); + foreach (DictionaryEntry de in tempNonTimeCodes) + { + if (Convert.ToInt32(de.Key) < Convert.ToInt32(p.Text)) + { + sb.AppendLine(de.Value.ToString()); + removeList.Add(Convert.ToInt32(de.Key)); + } + } + + foreach (int key in removeList) + tempNonTimeCodes.Remove(key); + + sb.AppendLine(string.Format(paragraphWriteFormat, p.StartTime, p.Text)); + } + foreach (DictionaryEntry de in tempNonTimeCodes) + sb.AppendLine(de.Value.ToString()); + return sb.ToString().Trim(); + } + + public override void LoadSubtitle(Subtitle subtitle, List lines, string fileName) + { + _errorCount = 0; + + subtitle.Paragraphs.Clear(); + foreach (string line in lines) + { + if (_regexTimeCodes.IsMatch(line)) + { + Paragraph p = GetTimeCodes(line); + if (p != null) + subtitle.Paragraphs.Add(p); + else + _errorCount++; + } + else + { + int place; + if (subtitle.Paragraphs.Count == 0 || + !int.TryParse(subtitle.Paragraphs[subtitle.Paragraphs.Count-1].Text, out place)) + place = -1; + + if (NonTimeCodes.ContainsKey(place)) + NonTimeCodes[place] += Environment.NewLine + line; + else + NonTimeCodes.Add(place, line); + } + } + } + + private static Paragraph GetTimeCodes(string line) + { + // timestamp: 00:00:01:401, filepos: 000000000 + + string[] parts = line.Split(new[] { ',', ':' }, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length == 7) + { + int hours; + int minutes; + int seconds; + int milliseconds; + if (int.TryParse(parts[1], out hours) && + int.TryParse(parts[2], out minutes) && + int.TryParse(parts[3], out seconds) && + int.TryParse(parts[4], out milliseconds)) + { + return new Paragraph + { + StartTime = {TimeSpan = new TimeSpan(0, hours, minutes, seconds, milliseconds)}, + Text = parts[6] + }; + } + } + return null; + } + + } +} diff --git a/src/Logic/SubtitleFormats/MPlayer2.cs b/src/Logic/SubtitleFormats/MPlayer2.cs new file mode 100644 index 000000000..1dc8d3a45 --- /dev/null +++ b/src/Logic/SubtitleFormats/MPlayer2.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace Nikse.SubtitleEdit.Logic.SubtitleFormats +{ + public class MPlayer2 : SubtitleFormat + { + readonly Regex _regexMPlayer2Line = new Regex(@"^\[-?\d+]\[-?\d+].*$", RegexOptions.Compiled); + + public override string Extension + { + get { return ".mpl"; } + } + + public override string Name + { + get { return "MPlayer2"; } + } + + public override bool HasLineNumber + { + get { return false; } + } + + public override bool IsTimeBased + { + get { return true; } + } + + public override bool IsMine(List lines, string fileName) + { + int errors = 0; + List trimmedLines = new List(); + foreach (string line in lines) + { + if (line.Trim().Length > 0 && line.Contains("[")) + { + string s = RemoveIllegalSpacesAndFixEmptyCodes(line); + if (_regexMPlayer2Line.IsMatch(s)) + trimmedLines.Add(line); + else + errors++; + } + else + { + errors++; + } + } + + return trimmedLines.Count > errors; + } + + private string RemoveIllegalSpacesAndFixEmptyCodes(string line) + { + int index = line.IndexOf("]"); + if (index >= 0 && index < line.Length) + { + index = line.IndexOf("]", index + 1); + if (index >= 0 && index + 1 < line.Length) + { + if (line.IndexOf("[]") >= 0 && line.IndexOf("[]") < index) + { + line = line.Insert(line.IndexOf("[]") + 1, "0"); // set empty time codes to zero + index++; + } + + while (line.IndexOf(" ") >= 0 && line.IndexOf(" ") < index) + { + line = line.Remove(line.IndexOf(" "), 1); + index--; + } + } + } + return line; + } + + + public override string ToText(Subtitle subtitle, string title) + { + StringBuilder sb = new StringBuilder(); + foreach (Paragraph p in subtitle.Paragraphs) + { + sb.Append("["); + sb.Append(((int)(p.StartTime.TotalMilliseconds / 100)).ToString()); + sb.Append("]["); + sb.Append(((int)(p.EndTime.TotalMilliseconds / 100)).ToString()); + sb.Append("]"); + + string text = p.Text.Replace(Environment.NewLine, "|"); + text = text.Replace("", "{Y:b}"); + text = text.Replace("", string.Empty); + text = text.Replace("", "{Y:i}"); + text = text.Replace("", string.Empty); + text = text.Replace("", "{Y:u}"); + text = text.Replace("", string.Empty); + + sb.AppendLine(text); + } + return sb.ToString().Trim(); + } + + public override void LoadSubtitle(Subtitle subtitle, List lines, string fileName) + { + _errorCount = 0; + + foreach (string line in lines) + { + string s = RemoveIllegalSpacesAndFixEmptyCodes(line); + if (_regexMPlayer2Line.IsMatch(s)) + { + try + { + + int textIndex = s.LastIndexOf("]") + 1; + if (textIndex < s.Length) + { + string text = s.Substring(textIndex); + string temp = s.Substring(0, textIndex - 1); + string[] frames = temp.Replace("][", ":").Replace("[", string.Empty).Replace("]", string.Empty).Split(':'); + + double startSeconds = double.Parse(frames[0]) / 10; + double endSeconds = double.Parse(frames[1]) / 10; + + if (startSeconds == 0 && subtitle.Paragraphs.Count > 0) + { + startSeconds = (subtitle.Paragraphs[subtitle.Paragraphs.Count-1].EndTime.TotalMilliseconds / 1000) + 0.1; + } + if (endSeconds == 0) + { + endSeconds = startSeconds; + } + + subtitle.Paragraphs.Add(new Paragraph(text, startSeconds * 1000, endSeconds * 1000)); + } + } + catch + { + _errorCount++; + } + } + else + { + _errorCount++; + } + } + subtitle.Renumber(1); + } + } +} diff --git a/src/Logic/SubtitleFormats/MicroDvd.cs b/src/Logic/SubtitleFormats/MicroDvd.cs new file mode 100644 index 000000000..abeaa985c --- /dev/null +++ b/src/Logic/SubtitleFormats/MicroDvd.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace Nikse.SubtitleEdit.Logic.SubtitleFormats +{ + public class MicroDvd : SubtitleFormat + { + readonly Regex _regexMicroDvdLine = new Regex(@"^\{-?\d+}\{-?\d+}.*$", RegexOptions.Compiled); + + public override string Extension + { + get { return ".sub"; } + } + + public override string Name + { + get { return "MicroDVD"; } + } + + public override bool HasLineNumber + { + get { return false; } + } + + public override bool IsTimeBased + { + get { return false; } + } + + public override bool IsMine(List lines, string fileName) + { + var trimmedLines = new List(); + int errors = 0; + foreach (string line in lines) + { + if (line.Trim().Length > 0 && line.Contains("{")) + { + string s = RemoveIllegalSpacesAndFixEmptyCodes(line); + if (_regexMicroDvdLine.IsMatch(s)) + trimmedLines.Add(s); + else + errors++; + } + else + { + errors++; + } + } + + return trimmedLines.Count > errors; + } + + private string RemoveIllegalSpacesAndFixEmptyCodes(string line) + { + int index = line.IndexOf("}"); + if (index >= 0 && index < line.Length) + { + index = line.IndexOf("}", index+1); + if (index >= 0 && index +1 < line.Length) + { + if (line.IndexOf("{}") >= 0 && line.IndexOf("{}") < index) + { + line = line.Insert(line.IndexOf("{}") +1, "0"); // set empty time codes to zero + index++; + } + + while (line.IndexOf(" ") >= 0 && line.IndexOf(" ") < index) + { + line = line.Remove(line.IndexOf(" "), 1); + index--; + } + } + } + return line; + } + + public override string ToText(Subtitle subtitle, string title) + { + var sb = new StringBuilder(); + foreach (Paragraph p in subtitle.Paragraphs) + { + sb.Append("{"); + sb.Append(p.StartFrame.ToString()); + sb.Append("}{"); + sb.Append(p.EndFrame.ToString()); + sb.Append("}"); + + string text = p.Text.Replace(Environment.NewLine, "|"); + text = text.Replace("","{Y:b}"); + text = text.Replace("", string.Empty); + text = text.Replace("","{Y:i}"); + text = text.Replace("", string.Empty); + text = text.Replace("","{Y:u}"); + text = text.Replace("", string.Empty); + + sb.AppendLine(text); + } + return sb.ToString().Trim(); + } + + public override void LoadSubtitle(Subtitle subtitle, List lines, string fileName) + { + _errorCount = 0; + + foreach (string line in lines) + { + string s = RemoveIllegalSpacesAndFixEmptyCodes(line); + if (_regexMicroDvdLine.IsMatch(s)) + { + try + { + int textIndex = GetTextStartIndex(s); + if (textIndex < s.Length) + { + string text = s.Substring(textIndex); + string temp = s.Substring(0, textIndex - 1); + string[] frames = temp.Replace("}{", ":").Replace("{", string.Empty).Replace("}", string.Empty).Split(':'); + + int startFrame = int.Parse(frames[0]); + int endFrame = int.Parse(frames[1]); + + subtitle.Paragraphs.Add(new Paragraph(startFrame, endFrame, text.Replace("|", Environment.NewLine))); + + } + } + catch + { + _errorCount++; + } + } + else + { + _errorCount++; + } + } + + int i = 0; + foreach (Paragraph p in subtitle.Paragraphs) + { + Paragraph previous = subtitle.GetParagraphOrDefault(i - 1); + if (p.StartFrame == 0 && previous != null) + { + p.StartFrame = previous.EndFrame + 1; + } + if (p.EndFrame == 0) + { + p.EndFrame = p.StartFrame; + } + i++; + } + + subtitle.Renumber(1); + } + + private static int GetTextStartIndex(string line) + { + int i = 0; + int tagCount = 0; + while (i < line.Length && tagCount < 4) + { + if (line[i] == '{' || line[i] == '}') + tagCount++; + + i++; + } + return i; + } + } +} diff --git a/src/Logic/SubtitleFormats/OpenDvt.cs b/src/Logic/SubtitleFormats/OpenDvt.cs new file mode 100644 index 000000000..dc4d93b17 --- /dev/null +++ b/src/Logic/SubtitleFormats/OpenDvt.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Xml; + +namespace Nikse.SubtitleEdit.Logic.SubtitleFormats +{ + class OpenDvt : SubtitleFormat + { + public override string Extension + { + get { return ".xml"; } + } + + public override string Name + { + get { return "OpenDVT"; } + } + + public override bool HasLineNumber + { + get { return false; } + } + + public override bool IsTimeBased + { + get { return true; } + } + + public override bool IsMine(List lines, string fileName) + { + StringBuilder sb = new StringBuilder(); + lines.ForEach(line => sb.AppendLine(line)); + string xmlAsString = sb.ToString().Trim(); + if (xmlAsString.Contains("OpenDVT")) + { + try + { + XmlDocument xml = new XmlDocument(); + xml.LoadXml(xmlAsString); + int numberOfParagraphs = xml.DocumentElement.SelectSingleNode("Lines").ChildNodes.Count; + return numberOfParagraphs > 0; + } + catch (Exception ex) + { + string s = ex.Message; + return false; + } + } + return false; + } + + public override string ToText(Subtitle subtitle, string title) + { + string guid = new Guid().ToString(); + string xmlStructure = + "" + Environment.NewLine + + "" + Environment.NewLine + + "" + Environment.NewLine + + " " + Environment.NewLine + + " " + guid + " " + Environment.NewLine + + " Subtitle Edit " + Environment.NewLine + + " 2.9 " + Environment.NewLine + + " Nikse.dk " + Environment.NewLine + + " " + Environment.NewLine + + " www.nikse.dk.com " + Environment.NewLine + + " " + Environment.NewLine + + " " + Environment.NewLine + + " " + Environment.NewLine + + " " + Environment.NewLine + + " " + Environment.NewLine + + " " + Environment.NewLine + + " " + Environment.NewLine + + " " + Environment.NewLine + + " " + Environment.NewLine + + " " + Environment.NewLine + + " " + Environment.NewLine + + " 1 " + Environment.NewLine + + " 3 " + Environment.NewLine + + " 25 " + Environment.NewLine + + " 1 " + Environment.NewLine + + " 06/02/2010 " + Environment.NewLine + + " " + Environment.NewLine + + " " + Environment.NewLine + + " " + Environment.NewLine + + "" + Environment.NewLine + + "" + Environment.NewLine + + "" + Environment.NewLine + + "" + Environment.NewLine + + //"C:\Users\Eric\Desktop\Player Folder\Bing\Bing.mpg + //"52158464 + //"06/02/2009 10:44:37 + //"166144 + //"OS + " " + Environment.NewLine + + "" + Environment.NewLine + + ""; + + XmlDocument xml = new XmlDocument(); + xml.LoadXml(xmlStructure); + XmlNode lines = xml.DocumentElement.SelectSingleNode("Lines"); + int no = 0; + foreach (Paragraph p in subtitle.Paragraphs) + { + XmlNode line = xml.CreateElement("Line"); + + XmlAttribute id = xml.CreateAttribute("ID"); + id.InnerText = no.ToString(); + line.Attributes.Append(id); + + XmlNode stream = xml.CreateElement("Stream"); + stream.InnerText = "0"; + line.AppendChild(stream); + + + XmlNode timeMS = xml.CreateElement("TimeMs"); + timeMS.InnerText = p.StartTime.TotalMilliseconds.ToString(); + line.AppendChild(timeMS); + + XmlNode pageNo = xml.CreateElement("PageNo"); + pageNo.InnerText = "1"; + line.AppendChild(pageNo); + + XmlNode lineNo = xml.CreateElement("LineNo"); + lineNo.InnerText = "1"; + line.AppendChild(lineNo); + + XmlNode qa = xml.CreateElement("QA"); + qa.InnerText = "-"; + line.AppendChild(qa); + + XmlNode text = xml.CreateElement("Text"); + text.InnerText = p.Text; + line.AppendChild(text); + + lines.AppendChild(line); + + no++; + } + + MemoryStream ms = new MemoryStream(); + XmlTextWriter writer = new XmlTextWriter(ms, Encoding.UTF8); + writer.Formatting = Formatting.Indented; + xml.Save(writer); + return Encoding.UTF8.GetString(ms.ToArray()).Trim(); + } + + public override void LoadSubtitle(Subtitle subtitle, List lines, string fileName) + { + _errorCount = 0; + + StringBuilder sb = new StringBuilder(); + lines.ForEach(line => sb.AppendLine(line)); + XmlDocument xml = new XmlDocument(); + xml.LoadXml(sb.ToString()); + + XmlNode div = xml.DocumentElement.SelectSingleNode("Lines"); + foreach (XmlNode node in div.ChildNodes) + { + try + { + Paragraph p = new Paragraph(); + + XmlNode text = node.SelectSingleNode("Text"); + if (text != null) + { + StringBuilder pText = new StringBuilder(); + foreach (XmlNode innerNode in text.ChildNodes) + { + switch (innerNode.Name.ToString()) + { + case "br": + pText.AppendLine(); + break; + default: + pText.Append(innerNode.InnerText); + break; + } + } + p.Text = pText.ToString(); + } + + XmlNode timeMS = node.SelectSingleNode("TimeMs"); + if (timeMS != null) + { + string ms = timeMS.InnerText; + long milliseconds; + if (long.TryParse(ms, out milliseconds)) + p.StartTime = new TimeCode(TimeSpan.FromMilliseconds(milliseconds)); + } + p.EndTime = new TimeCode(TimeSpan.FromMilliseconds(p.StartTime.TotalMilliseconds + Utilities.GetDisplayMillisecondsFromText(p.Text))); + + subtitle.Paragraphs.Add(p); + } + catch (Exception ex) + { + string s = ex.Message; + _errorCount++; + } + } + subtitle.Renumber(1); + } + + } +} + + diff --git a/src/Logic/SubtitleFormats/Sami.cs b/src/Logic/SubtitleFormats/Sami.cs new file mode 100644 index 000000000..441c992b6 --- /dev/null +++ b/src/Logic/SubtitleFormats/Sami.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Globalization; + +namespace Nikse.SubtitleEdit.Logic.SubtitleFormats +{ + public class Sami : SubtitleFormat + { + public override string Extension + { + get { return ".smi"; } + } + + public override string Name + { + get { return "SAMI"; } + } + + public override bool HasLineNumber + { + get { return false; } + } + + public override bool IsTimeBased + { + get { return true; } + } + + public override bool IsMine(List lines, string fileName) + { + var subtitle = new Subtitle(); + LoadSubtitle(subtitle, lines, fileName); + return subtitle.Paragraphs.Count > _errorCount; + } + + public override string ToText(Subtitle subtitle, string title) + { + string language = Utilities.AutoDetectLanguageName("en_US", subtitle); + CultureInfo ci = CultureInfo.GetCultureInfo(language.Replace("_", "-")); + string languageTag = string.Format("{0}CC", language.Replace("_", string.Empty).ToUpper()); + string languageName = ci.EnglishName; + if (ci.Parent != null) + languageName = ci.Parent.EnglishName; + string languageStyle = string.Format(".{0} [ name: {1}; lang: {2} ; SAMIType: CC ; ]", languageTag, languageName, language.Replace("_", "-")); + languageStyle = languageStyle.Replace("[", "{").Replace("]", "}"); + + const string header = +@" + + +_TITLE_ + + + Metrics {time:ms;} + Spec {MSFT:1.0;} + + + + + + + + +<-- Open play menu, choose Captions and Subtiles, On if available --> +<-- Open tools menu, Security, Show local captions when present --> +"; + // Example text (start numbers are milliseconds) + //

Let's go! + //


+ + const string paragraphWriteFormat = +@"

{2}

+

 

"; + + var sb = new StringBuilder(); + sb.AppendLine(header.Replace("_TITLE_", title).Replace("_LANGUAGE-STYLE_", languageStyle)); + foreach (Paragraph p in subtitle.Paragraphs) + { + sb.AppendLine(string.Format(paragraphWriteFormat, p.StartTime.TotalMilliseconds, p.EndTime.TotalMilliseconds, p.Text.Replace(Environment.NewLine, "
"), languageTag)); + } + sb.AppendLine(""); + sb.AppendLine("
"); + return sb.ToString().Trim(); + } + + public override void LoadSubtitle(Subtitle subtitle, List lines, string fileName) + { + _errorCount = 0; + + var sb = new StringBuilder(); + foreach (string l in lines) + sb.AppendLine(l); + string allInput = sb.ToString(); + string allInputLower = allInput.ToLower(); + + const string syncTag = "') + index++; + + int syncEndPos = allInputLower.IndexOf(syncTag, index); + string text; + if (syncEndPos >= 0) + text = allInput.Substring(index, syncEndPos - index); + else + text = allInput.Substring(index); + + if (text.Contains("ID=\"Source\"") || text.Contains("ID=Source")) + { + int sourceIndex = text.IndexOf("ID=\"Source\""); + if (sourceIndex < 0) + sourceIndex = text.IndexOf("ID=Source"); + int st = sourceIndex -1; + while (st > 0 && text.Substring(st, 2).ToUpper() != " 0) + { + text = text.Substring(0, st) + text.Substring(sourceIndex); + } + int et = st; + while (et < text.Length - 5 && text.Substring(et, 3).ToUpper() != "

" && text.Substring(et, 4).ToUpper() != "

") + { + et++; + } + text = text.Substring(0, st) + text.Substring(et); + } + text.Replace(Environment.NewLine, " "); + text.Replace(" ", " "); + + text = text.TrimEnd(); + text = text.Replace("
", Environment.NewLine); + text = text.Replace("
", Environment.NewLine); + text = text.Replace("
", Environment.NewLine); + text = text.Replace("
", Environment.NewLine); + text = text.Replace("
", Environment.NewLine); + text = text.Replace("
", Environment.NewLine); + string cleanText = string.Empty; + bool tagOn = false; + for (int i = 0; i < text.Length; i++) + { // Remove html tags from text + if (text[i] == '<') + tagOn = true; + else if (text[i] == '>') + tagOn = false; + else if (!tagOn) + cleanText += text[i]; + } + cleanText = cleanText.Replace(" ", string.Empty).Replace("&NBSP;", string.Empty); + cleanText = cleanText.Trim(); + + if (!string.IsNullOrEmpty(p.Text)) + { + p.EndTime = new TimeCode(TimeSpan.FromMilliseconds(long.Parse(millisecAsString))); + subtitle.Paragraphs.Add(p); + p = new Paragraph(); + } + + p.Text = cleanText; + p.StartTime = new TimeCode(TimeSpan.FromMilliseconds(long.Parse(millisecAsString))); + + if (syncEndPos <= 0) + { + syncStartPos = -1; + } + else + { + syncStartPos = allInputLower.IndexOf(syncTag, syncEndPos); + index = syncStartPos + syncTag.Length; + } + } + subtitle.Renumber(1); + } + } +} diff --git a/src/Logic/SubtitleFormats/SonyDVDArchitect.cs b/src/Logic/SubtitleFormats/SonyDVDArchitect.cs new file mode 100644 index 000000000..524292ad3 --- /dev/null +++ b/src/Logic/SubtitleFormats/SonyDVDArchitect.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace Nikse.SubtitleEdit.Logic.SubtitleFormats +{ + public class SonyDVDArchitect : SubtitleFormat + { + public override string Extension + { + get { return ".sub"; } + } + + public override string Name + { + get { return "Sony DVDArchitect"; } + } + + public override bool HasLineNumber + { + get { return false; } + } + + public override bool IsTimeBased + { + get { return true; } + } + + public override bool IsMine(List lines, string fileName) + { + var subtitle = new Subtitle(); + LoadSubtitle(subtitle, lines, fileName); + return subtitle.Paragraphs.Count > _errorCount; + } + + public override string ToText(Subtitle subtitle, string title) + { + var sb = new StringBuilder(); + foreach (Paragraph p in subtitle.Paragraphs) + { + string text = Utilities.RemoveHtmlTags(p.Text); + text = text.Replace(Environment.NewLine, string.Empty); + sb.AppendLine(string.Format("{0:00}:{1:00}:{2:00}:{3:00} - {4:00}:{5:00}:{6:00}:{7:00} \t{8:00}", + p.StartTime.Hours, p.StartTime.Minutes, p.StartTime.Seconds, p.StartTime.Milliseconds / 10, + p.EndTime.Hours, p.EndTime.Minutes, p.EndTime.Seconds, p.EndTime.Milliseconds / 10, + text)); + } + return sb.ToString().Trim(); + } + + public override void LoadSubtitle(Subtitle subtitle, List lines, string fileName) + { // 00:04:10:92 - 00:04:13:32 Raise Yourself To Help Mankind + // 00:04:27:92 - 00:04:30:92 الجهة المتولية للمسئولية الاجتماعية لشركتنا. + + var regex = new Regex(@"^\d\d:\d\d:\d\d:\d\d[ ]+-[ ]+\d\d:\d\d:\d\d:\d\d", RegexOptions.Compiled); + _errorCount = 0; + foreach (string line in lines) + { + if (line.Trim().Length > 0) + { + bool success = false; + var match = regex.Match(line); + if (line.Length > 26 && match.Success) + { + string s = line.Substring(0, match.Length); + s = s.Replace(" - ", ":"); + s = s.Replace(" ", string.Empty); + string[] parts = s.Split(':'); + if (parts.Length == 8) + { + int hours = int.Parse(parts[0]); + int minutes = int.Parse(parts[1]); + int seconds = int.Parse(parts[2]); + int milliseconds = int.Parse(parts[3]) * 10; + var start = new TimeCode(hours, minutes, seconds, milliseconds); + + hours = int.Parse(parts[4]); + minutes = int.Parse(parts[5]); + seconds = int.Parse(parts[6]); + milliseconds = int.Parse(parts[7]) * 10; + var end = new TimeCode(hours, minutes, seconds, milliseconds); + + string text = line.Substring(match.Length).TrimStart(); + text = text.Replace("|", Environment.NewLine); + + var p = new Paragraph(start, end, text); + subtitle.Paragraphs.Add(p); + success = true; + } + } + if (!success) + _errorCount++; + } + } + subtitle.Renumber(1); + } + } +} diff --git a/src/Logic/SubtitleFormats/SonyDVDArchitectWithLineNumbers.cs b/src/Logic/SubtitleFormats/SonyDVDArchitectWithLineNumbers.cs new file mode 100644 index 000000000..1c888f401 --- /dev/null +++ b/src/Logic/SubtitleFormats/SonyDVDArchitectWithLineNumbers.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace Nikse.SubtitleEdit.Logic.SubtitleFormats +{ + public class SonyDVDArchitectWithLineNumbers : SubtitleFormat + { + public override string Extension + { + get { return ".sub"; } + } + + public override string Name + { + get { return "Sony DVDArchitect w. line#"; } + } + + public override bool HasLineNumber + { + get { return true; } + } + + public override bool IsTimeBased + { + get { return true; } + } + + public override bool IsMine(List lines, string fileName) + { + var subtitle = new Subtitle(); + LoadSubtitle(subtitle, lines, fileName); + return subtitle.Paragraphs.Count > _errorCount; + } + + public override string ToText(Subtitle subtitle, string title) + { + var sb = new StringBuilder(); + foreach (Paragraph p in subtitle.Paragraphs) + { + string text = Utilities.RemoveHtmlTags(p.Text); + text = text.Replace(Environment.NewLine, string.Empty); + sb.AppendLine(string.Format("{9:0000} {0:00}:{1:00}:{2:00}:{3:00} {4:00}:{5:00}:{6:00}:{7:00} \t{8:00}", + p.StartTime.Hours, p.StartTime.Minutes, p.StartTime.Seconds, p.StartTime.Milliseconds / 10, + p.EndTime.Hours, p.EndTime.Minutes, p.EndTime.Seconds, p.EndTime.Milliseconds / 10, + text, p.Number)); + } + return sb.ToString().Trim(); + } + + public override void LoadSubtitle(Subtitle subtitle, List lines, string fileName) + { // 00:04:10:92 - 00:04:13:32 Raise Yourself To Help Mankind + // 00:04:27:92 - 00:04:30:92 الجهة المتولية للمسئولية الاجتماعية لشركتنا. + + var regex = new Regex(@"^\d\d\d\d \d\d:\d\d:\d\d:\d\d \d\d:\d\d:\d\d:\d\d", RegexOptions.Compiled); + var regex1DigitMillisecs = new Regex(@"^\d\d\d\d \d\d\d:\d\d:\d\d:\d \d\d\d:\d\d:\d\d:\d", RegexOptions.Compiled); + _errorCount = 0; + foreach (string line in lines) + { + string s = line.Replace("\0", string.Empty); + if (s.Length > 0) + { + bool success = false; + var match = regex.Match(s); + var match1DigitMillisecs = regex1DigitMillisecs.Match(s); + if (s.Length > 31 && match.Success) + { + s = s.Substring(5, match.Length - 5).TrimStart(); + s = s.Replace(" ", ":"); + s = s.Replace(" ", string.Empty); + string[] parts = s.Split(':'); + if (parts.Length == 8) + { + int hours = int.Parse(parts[0]); + int minutes = int.Parse(parts[1]); + int seconds = int.Parse(parts[2]); + int milliseconds = int.Parse(parts[3]) * 10; + var start = new TimeCode(hours, minutes, seconds, milliseconds); + + hours = int.Parse(parts[4]); + minutes = int.Parse(parts[5]); + seconds = int.Parse(parts[6]); + milliseconds = int.Parse(parts[7]) * 10; + var end = new TimeCode(hours, minutes, seconds, milliseconds); + + string text = line.Replace("\0", string.Empty).Substring(match.Length).TrimStart(); + text = text.Replace("|", Environment.NewLine); + + var p = new Paragraph(start, end, text); + subtitle.Paragraphs.Add(p); + success = true; + } + } + else if (s.Length > 29 && match1DigitMillisecs.Success) + { + s = s.Substring(5, match1DigitMillisecs.Length - 5).TrimStart(); + s = s.Replace(" ", ":"); + s = s.Replace(" ", string.Empty); + string[] parts = s.Split(':'); + if (parts.Length == 8) + { + int hours = int.Parse(parts[0]); + int minutes = int.Parse(parts[1]); + int seconds = int.Parse(parts[2]); + int milliseconds = int.Parse(parts[3]) * 10; + var start = new TimeCode(hours, minutes, seconds, milliseconds); + + hours = int.Parse(parts[4]); + minutes = int.Parse(parts[5]); + seconds = int.Parse(parts[6]); + milliseconds = int.Parse(parts[7]) * 10; + var end = new TimeCode(hours, minutes, seconds, milliseconds); + + string text = line.Replace("\0", string.Empty).Substring(match1DigitMillisecs.Length).TrimStart(); + text = text.Replace("|", Environment.NewLine); + + var p = new Paragraph(start, end, text); + subtitle.Paragraphs.Add(p); + success = true; + } + } + if (!success) + _errorCount++; + } + } + subtitle.Renumber(1); + } + } +} diff --git a/src/Logic/SubtitleFormats/SubRip.cs b/src/Logic/SubtitleFormats/SubRip.cs new file mode 100644 index 000000000..5c9de8d4f --- /dev/null +++ b/src/Logic/SubtitleFormats/SubRip.cs @@ -0,0 +1,241 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace Nikse.SubtitleEdit.Logic.SubtitleFormats +{ + public class SubRip : SubtitleFormat + { + enum ExpectingLine + { + Number, + TimeCodes, + Text + } + + Paragraph _paragraph; + ExpectingLine _expecting = ExpectingLine.Number; + readonly Regex _regexTimeCodes = new Regex(@"^-?\d+:-?\d+:-?\d+[:,]-?\d+\s*-->\s*-?\d+:-?\d+:-?\d+[:,]-?\d+$", RegexOptions.Compiled); + readonly Regex _buggyTimeCodes = new Regex(@"^-?\d+:-?\d+:-?\d+¡-?\d+\s*-->\s*-?\d+:-?\d+:-?\d+¡-?\d+$", RegexOptions.Compiled); + + public override string Extension + { + get { return ".srt"; } + } + + public override string Name + { + get { return "SubRip"; } + } + + public override bool HasLineNumber + { + get { return true; } + } + + public override bool IsTimeBased + { + get { return true; } + } + + public override bool IsMine(List 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 paragraphWriteFormat = "{0}\r\n{1} --> {2}\r\n{3}\r\n\r\n"; + + var sb = new StringBuilder(); + foreach (Paragraph p in subtitle.Paragraphs) + { + sb.Append(string.Format(paragraphWriteFormat, p.Number, p.StartTime, p.EndTime, p.Text)); + } + return sb.ToString().Trim(); + } + + public override void LoadSubtitle(Subtitle subtitle, List lines, string fileName) + { + bool doRenum = false; + + _paragraph = new Paragraph(); + _expecting = ExpectingLine.Number; + _errorCount = 0; + + subtitle.Paragraphs.Clear(); + for (int i=0; i 0) + subtitle.Paragraphs.Add(_paragraph); + + foreach (Paragraph p in subtitle.Paragraphs) + p.Text = p.Text.Replace(Environment.NewLine + Environment.NewLine, Environment.NewLine); + + if (doRenum) + subtitle.Renumber(1); + } + + private void ReadLine(Subtitle subtitle, string line, string next) + { + switch (_expecting) + { + case ExpectingLine.Number: + if (Utilities.IsInteger(line)) + { + _paragraph.Number = int.Parse(line); + _expecting = ExpectingLine.TimeCodes; + } + else if (line.Trim().Length > 0) + { + _errorCount++; + } + break; + case ExpectingLine.TimeCodes: + if (TryReadTimeCodesLine(line, _paragraph)) + { + _paragraph.Text = string.Empty; + _expecting = ExpectingLine.Text; + } + else if (line.Trim().Length > 0) + { + _errorCount++; + _expecting = ExpectingLine.Number ; // lets go to next paragraph + } + break; + case ExpectingLine.Text: + if (line.Trim().Length > 0) + { + if (_paragraph.Text.Length > 0) + _paragraph.Text += Environment.NewLine; + _paragraph.Text += RemoveBadChars(line).TrimEnd(); + } + else if (IsText(next)) + { + if (_paragraph.Text.Length > 0) + _paragraph.Text += Environment.NewLine; + _paragraph.Text += RemoveBadChars(line).TrimEnd(); + } + else + { + subtitle.Paragraphs.Add(_paragraph); + _paragraph = new Paragraph(); + _expecting = ExpectingLine.Number; + } + break; + } + } + + private bool IsText(string text) + { + if (text.Trim().Length == 0) + return false; + + if (Utilities.IsInteger(text)) + return false; + + if (_regexTimeCodes.IsMatch(text)) + return false; + + return true; + + //string letters = Utilities.GetLetters(true, true, false); + //foreach (char ch in next) + //{ + // if (letters.Contains(ch.ToString())) + // return true; + //} + //return false; + + } + + private string RemoveBadChars(string line) + { + line = line.Replace("\0", " "); + + return line; + } + + private bool TryReadTimeCodesLine(string line, Paragraph paragraph) + { + line = line.Replace("،", ","); + + line = line.Trim(); + line = line.Replace(": ", ":"); // I've seen this + line = line.Replace(" :", ":"); + line = line.Replace(" ,", ","); + line = line.Replace(", ", ","); + + // Fix some badly formatted separator sequences - anything can happen if you manually edit ;) + line = line.Replace(" -> ", " --> "); // I've seen this + line = line.Replace(" - > ", " --> "); + line = line.Replace(" ->> ", " --> "); + line = line.Replace(" -- > ", " --> "); + line = line.Replace(" - -> ", " --> "); + line = line.Replace(" -->> ", " --> "); + + // Removed stuff after timecodes - like subtitle position + // - example of position info: 00:02:26,407 --> 00:02:31,356 X1:100 X2:100 Y1:100 Y2:100 + if (line.Length > 30 && line[30] == ' ') + line = line.Substring(0, 29); + + // Fix a few more cases of wrong time codes, seen this: 00.00.02,000 --> 00.00.04,000 + line = line.Replace('.', ':'); + if (line.Length >= 29 && ":;".Contains(line[8].ToString())) + line = line.Substring(0, 8) + ',' + line.Substring(8 + 1); + if (line.Length >= 29 && ":;".Contains(line[25].ToString())) + line = line.Substring(0, 25) + ',' + line.Substring(25 + 1); + + + if (_buggyTimeCodes.IsMatch(line)) // seen this in a few arabic subs: 00:00:05¡580 --> 00:01:00¡310 + line = line.Replace("¡", ","); + + if (_regexTimeCodes.IsMatch(line)) + { + string[] parts = line.Replace("-->", ":").Replace(" ", string.Empty).Split(':', ','); + try + { + int startHours = int.Parse(parts[0]); + int startMinutes = int.Parse(parts[1]); + int startSeconds = int.Parse(parts[2]); + int startMilliseconds = int.Parse(parts[3]); + int endHours = int.Parse(parts[4]); + int endMinutes = int.Parse(parts[5]); + int endSeconds = int.Parse(parts[6]); + int endMilliseconds = int.Parse(parts[7]); + paragraph.StartTime = new TimeCode(startHours, startMinutes, startSeconds, startMilliseconds); + paragraph.EndTime = new TimeCode(endHours, endMinutes, endSeconds, endMilliseconds); + return true; + } + catch + { + return false; + } + } + return false; + } + } +} diff --git a/src/Logic/SubtitleFormats/SubStationAlpha.cs b/src/Logic/SubtitleFormats/SubStationAlpha.cs new file mode 100644 index 000000000..8aba60596 --- /dev/null +++ b/src/Logic/SubtitleFormats/SubStationAlpha.cs @@ -0,0 +1,307 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Nikse.SubtitleEdit.Forms; + +namespace Nikse.SubtitleEdit.Logic.SubtitleFormats +{ + public class SubStationAlpha : SubtitleFormat + { + public override string Extension + { + get { return ".ssa"; } + } + + public override string Name + { + get { return "Sub Station Alpha"; } + } + + public override bool HasLineNumber + { + get { return false; } + } + + public override bool IsTimeBased + { + get { return true; } + } + + public override bool IsMine(List 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 header = +@"[Script Info] +; This is a Sub Station Alpha v4 script. +Title: {0} +ScriptType: v4.00 +Collisions: Normal +PlayDepth: 0 + +[V4 Styles] +Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding +Style: Default,{1},{2},{3},65535,65535,-2147483640,-1,0,1,3,0,2,30,30,30,0,0 + +[Events] +Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text"; + + const string timeCodeFormat = "{0}:{1:00}:{2:00}.{3:00}"; // h:mm:ss.cc + const string paragraphWriteFormat = "Dialogue: Marked=0,{0},{1},Default,NTP,0000,0000,0000,!Effect,{2}"; + + var sb = new StringBuilder(); + System.Drawing.Color fontColor = System.Drawing.Color.FromArgb(Configuration.Settings.SsaStyle.FontColorArgb); + sb.AppendLine(string.Format(header, + title, + Configuration.Settings.SsaStyle.FontName, + (int)Configuration.Settings.SsaStyle.FontSize, + System.Drawing.ColorTranslator.ToWin32(fontColor))); + + 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); + sb.AppendLine(string.Format(paragraphWriteFormat, start, end, FormatText(p))); + } + return sb.ToString().Trim(); + } + + private static string FormatText(Paragraph p) + { + string text = p.Text.Replace(Environment.NewLine, "\\N"); + text = text.Replace("", @"{\i1}"); + text = text.Replace("", @"{\i0}"); + text = text.Replace("", @"{\u1}"); + text = text.Replace("", @"{\u0}"); + text = text.Replace("", @"{\b1}"); + text = text.Replace("", @"{\b0}"); + if (text.Contains("', start); + if (end > 0) + { + string fontTag = text.Substring(start + 4, end - (start + 4)); + text = text.Remove(start, end - start + 1); + text = text.Replace("", string.Empty); + + 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, "color=\"", "\"", "c&H", "&}"); + fontTag = FormatTag(ref text, start, fontTag, "color='", "'", "c&H", "&}"); + } + } + return text; + } + + private static string FormatTag(ref string text, int start, string fontTag, string tag, string endSign, string ssaTagName, string endSsaTag) + { + if (fontTag.Contains(tag)) + { + int fontStart = fontTag.IndexOf(tag); + int fontEnd = fontTag.IndexOf(endSign, fontStart + tag.Length); + if (fontEnd > 0) + { + string subTag = fontTag.Substring(fontStart + tag.Length, fontEnd - (fontStart + tag.Length)); + if (tag.Contains("color")) + subTag = subTag.Replace("#", string.Empty); + fontTag = fontTag.Remove(fontStart, fontEnd - fontStart + 1); + text = text.Insert(start, @"{\" + ssaTagName + subTag + endSsaTag); + } + } + return fontTag; + } + + private static string GetFormattedText(string text) + { + text = text.Replace("\\N", Environment.NewLine).Replace("\\n", Environment.NewLine); + + text = text.Replace(@"{\i1}", ""); + text = text.Replace(@"{\i0}", ""); + if (FixCommonErrors.CountTagInText(text, "") > FixCommonErrors.CountTagInText(text, "")) + text += ""; + + text = text.Replace(@"{\u1}", ""); + text = text.Replace(@"{\u0}", ""); + if (FixCommonErrors.CountTagInText(text, "") > FixCommonErrors.CountTagInText(text, "")) + text += ""; + + text = text.Replace(@"{\b1}", ""); + text = text.Replace(@"{\b0}", ""); + if (FixCommonErrors.CountTagInText(text, "") > FixCommonErrors.CountTagInText(text, "")) + text += ""; + + for (int i = 0; i < 5; i++) // just look five times... + { + if (text.Contains(@"{\fn")) + { + int start = text.IndexOf(@"{\fn"); + int end = text.IndexOf('}', start); + if (end > 0) + { + string fontName = text.Substring(start + 4, end - (start + 4)); + text = text.Remove(start, end - start + 1); + text = text.Insert(start, ""); + text += ""; + } + } + + if (text.Contains(@"{\fs")) + { + int start = text.IndexOf(@"{\fs"); + int end = text.IndexOf('}', start); + if (end > 0) + { + string fontSize = text.Substring(start + 4, end - (start + 4)); + if (Utilities.IsInteger(fontSize)) + { + text = text.Remove(start, end - start + 1); + text = text.Insert(start, ""); + text += ""; + } + } + } + + if (text.Contains(@"{\c")) + { + int start = text.IndexOf(@"{\c"); + int end = text.IndexOf('}', start); + if (end > 0) + { + string color = text.Substring(start + 4, end - (start + 4)); + color = color.Replace("&", string.Empty).TrimStart('H'); + text = text.Remove(start, end - start + 1); + text = text.Insert(start, ""); + text += ""; + } + } + } + + return text; + } + + public override void LoadSubtitle(Subtitle subtitle, List lines, string fileName) + { + _errorCount = 0; + bool eventsStarted = false; + subtitle.Paragraphs.Clear(); + string[] format = "Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text".Split(','); + int indexStart = 1; + int indexEnd = 2; + int indexText = 9; + + foreach (string line in lines) + { + if (line.Trim().ToLower() == "[events]") + { + eventsStarted = true; + } + else if (eventsStarted) + { + string s = line.Trim().ToLower(); + if (s.StartsWith("format:")) + { + if (line.Length > 10) + { + format = line.ToLower().Substring(8).Split(','); + for (int i = 0; i < format.Length; i++) + { + if (format[i].Trim().ToLower() == "start") + indexStart = i; + else if (format[i].Trim().ToLower() == "end") + indexEnd = i; + else if (format[i].Trim().ToLower() == "text") + indexText = i; + } + } + } + else + { + string text = string.Empty; + string start = string.Empty; + string end = string.Empty; + + string[] splittedLine; + + if (s.StartsWith("dialogue:")) + splittedLine = line.Substring(10).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 == indexText) + text = splittedLine[i]; + else if (i > indexText) + text += splittedLine[i]; + } + + try + { + var p = new Paragraph(); + + p.StartTime = GetTimeCodeFromString(start); + p.EndTime = GetTimeCodeFromString(end); + p.Text = GetFormattedText(text); + subtitle.Paragraphs.Add(p); + } + catch + { + _errorCount++; + } + } + } + } + subtitle.Renumber(1); + } + + 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) + { + foreach (Paragraph p in subtitle.Paragraphs) + { + int indexOfBegin = p.Text.IndexOf("{"); + while (indexOfBegin >= 0 && p.Text.IndexOf("}") > indexOfBegin) + { + int indexOfEnd = p.Text.IndexOf("}"); + p.Text = p.Text.Remove(indexOfBegin, (indexOfEnd - indexOfBegin) +1); + + indexOfBegin = p.Text.IndexOf("{"); + } + + } + } + + public override List AlternateExtensions + { + get + { + return new List() { ".ass" }; + } + } + + + } +} diff --git a/src/Logic/SubtitleFormats/SubViewer10.cs b/src/Logic/SubtitleFormats/SubViewer10.cs new file mode 100644 index 000000000..a3ac809d1 --- /dev/null +++ b/src/Logic/SubtitleFormats/SubViewer10.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace Nikse.SubtitleEdit.Logic.SubtitleFormats +{ + public class SubViewer10 : SubtitleFormat + { + enum ExpectingLine + { + TimeStart, + Text, + TimeEnd, + } + + public override string Extension + { + get { return ".sub"; } + } + + public override string Name + { + get { return "SubViewer 1.0"; } + } + + public override bool HasLineNumber + { + get { return false; } + } + + public override bool IsTimeBased + { + get { return true; } + } + + public override bool IsMine(List lines, string fileName) + { + var subtitle = new Subtitle(); + LoadSubtitle(subtitle, lines, fileName); + return subtitle.Paragraphs.Count > _errorCount; + } + + public override string ToText(Subtitle subtitle, string title) + { +//[00:02:14] +//Yes a new line|Line number 2 +//[00:02:15] + string paragraphWriteFormat = "[{0:00}:{1:00}:{2:00}]" + Environment.NewLine + + "{3}" + Environment.NewLine + + "[{4:00}:{5:00}:{6:00}]"; + const string header = @"[TITLE] +{0} +[AUTHOR] + +[SOURCE] + +[PRG] + +[FILEPATH] + +[DELAY] +0 +[CD TRACK] +0 +[BEGIN] +******** START SCRIPT ******** +"; + const string footer = @"[end] +******** END SCRIPT ******** +"; + var sb = new StringBuilder(); + sb.Append(string.Format(header, title)); + foreach (Paragraph p in subtitle.Paragraphs) + { + string text = Utilities.RemoveHtmlTags(p.Text.Replace(Environment.NewLine, "|")); + + sb.AppendLine(string.Format(paragraphWriteFormat, + p.StartTime.Hours, + p.StartTime.Minutes, + p.StartTime.Seconds, + text, + p.EndTime.Hours, + p.EndTime.Minutes, + p.EndTime.Seconds)); + sb.AppendLine(); + } + sb.Append(footer); + return sb.ToString().Trim(); + } + + public override void LoadSubtitle(Subtitle subtitle, List lines, string fileName) + { + var regexTimeCode = new Regex(@"^\[\d\d:\d\d:\d\d\]$", RegexOptions.Compiled); + + var paragraph = new Paragraph(); + ExpectingLine expecting = ExpectingLine.TimeStart; + _errorCount = 0; + + subtitle.Paragraphs.Clear(); + foreach (string line in lines) + { + if (regexTimeCode.IsMatch(line)) + { + string[] parts = line.Split(new[] { ':', ']', '[', ' ' }, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length == 3) + { + try + { + int startHours = int.Parse(parts[0]); + int startMinutes = int.Parse(parts[1]); + int startSeconds = int.Parse(parts[2]); + var tc = new TimeCode(startHours, startMinutes, startSeconds, 0); + if (expecting == ExpectingLine.TimeStart) + { + paragraph = new Paragraph(); + paragraph.StartTime = tc; + expecting = ExpectingLine.Text; + } + else if (expecting == ExpectingLine.TimeEnd) + { + paragraph.EndTime = tc; + expecting = ExpectingLine.TimeStart; + subtitle.Paragraphs.Add(paragraph); + paragraph = new Paragraph(); + } + } + catch + { + _errorCount++; + expecting = ExpectingLine.TimeStart; + } + } + } + else + { + if (expecting == ExpectingLine.Text) + { + if (line.Length > 0) + { + string text = line.Replace("|", Environment.NewLine); + paragraph.Text = text; + expecting = ExpectingLine.TimeEnd; + } + } + } + } + subtitle.Renumber(1); + } + } +} diff --git a/src/Logic/SubtitleFormats/SubViewer20.cs b/src/Logic/SubtitleFormats/SubViewer20.cs new file mode 100644 index 000000000..74064b5e6 --- /dev/null +++ b/src/Logic/SubtitleFormats/SubViewer20.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace Nikse.SubtitleEdit.Logic.SubtitleFormats +{ + public class SubViewer20 : SubtitleFormat + { + enum ExpectingLine + { + TimeCodes, + Text + } + + public override string Extension + { + get { return ".sub"; } + } + + public override string Name + { + get { return "SubViewer 2.0"; } + } + + public override bool HasLineNumber + { + get { return false; } + } + + public override bool IsTimeBased + { + get { return true; } + } + + public override bool IsMine(List 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 paragraphWriteFormat = "{0:00}:{1:00}:{2:00}.{3:00},{4:00}:{5:00}:{6:00}.{7:00}{8}{9}"; + const string header = @"[INFORMATION] +[TITLE]{0} +[AUTHOR] +[SOURCE] +[PRG] +[FILEPATH] +[DELAY]0 +[CD TRACK]0 +[COMMENT] +[END INFORMATION] +[SUBTITLE] +[COLF]&H000000,[STYLE]bd,[SIZE]25,[FONT]Arial +"; + //00:00:06.61,00:00:13.75 + //text1[br]text2 + var sb = new StringBuilder(); + sb.Append(string.Format(header, title)); + foreach (Paragraph p in subtitle.Paragraphs) + { + string text = p.Text.Replace(Environment.NewLine, "[br]"); + text = text.Replace("", "{\\i1}"); + text = text.Replace("", "{\\i0}"); + text = text.Replace("", "{\\b1}'"); + text = text.Replace("", "{\\b0}"); + text = text.Replace("", "{\\u1}"); + text = text.Replace("", "{\\u0}"); + + sb.AppendLine(string.Format(paragraphWriteFormat, + p.StartTime.Hours, + p.StartTime.Minutes, + p.StartTime.Seconds, + RoundTo2Cifres(p.StartTime.Milliseconds), + p.EndTime.Hours, + p.EndTime.Minutes, + p.EndTime.Seconds, + RoundTo2Cifres(p.EndTime.Milliseconds), + Environment.NewLine, + text)); + sb.AppendLine(); + } + return sb.ToString().Trim(); + } + + private int RoundTo2Cifres(int milliseconds) + { + int rounded = (int)Math.Round((double)milliseconds / 10); + return rounded; + } + + public override void LoadSubtitle(Subtitle subtitle, List lines, string fileName) + { + var regexTimeCodes = new Regex(@"^\d\d:\d\d:\d\d.\d+,\d\d:\d\d:\d\d.\d+$", RegexOptions.Compiled); + + var paragraph = new Paragraph(); + ExpectingLine expecting = ExpectingLine.TimeCodes; + _errorCount = 0; + + subtitle.Paragraphs.Clear(); + foreach (string line in lines) + { + if (regexTimeCodes.IsMatch(line)) + { + string[] parts = line.Split(new[] { ':', ',', '.' }, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length == 8) + { + try + { + int startHours = int.Parse(parts[0]); + int startMinutes = int.Parse(parts[1]); + int startSeconds = int.Parse(parts[2]); + int startMilliseconds = int.Parse(parts[3]); + int endHours = int.Parse(parts[4]); + int endMinutes = int.Parse(parts[5]); + int endSeconds = int.Parse(parts[6]); + int endMilliseconds = int.Parse(parts[7]); + paragraph.StartTime = new TimeCode(startHours, startMinutes, startSeconds, startMilliseconds); + paragraph.EndTime = new TimeCode(endHours, endMinutes, endSeconds, endMilliseconds); + expecting = ExpectingLine.Text; + } + catch + { + expecting = ExpectingLine.TimeCodes; + } + } + } + else + { + if (expecting == ExpectingLine.Text) + { + if (line.Length > 0) + { + string text = line.Replace("[br]", Environment.NewLine); + text = text.Replace("{\\i1}", ""); + text = text.Replace("{\\i0}", ""); + text = text.Replace("{\\b1}", "'"); + text = text.Replace("{\\b0}", ""); + text = text.Replace("{\\u1}", ""); + text = text.Replace("{\\u0}", ""); + + paragraph.Text = text; + subtitle.Paragraphs.Add(paragraph); + paragraph = new Paragraph(); + expecting = ExpectingLine.TimeCodes; + } + } + } + } + subtitle.Renumber(1); + } + } +} diff --git a/src/Logic/SubtitleFormats/SubtitleFormat.cs b/src/Logic/SubtitleFormats/SubtitleFormat.cs new file mode 100644 index 000000000..f8168378d --- /dev/null +++ b/src/Logic/SubtitleFormats/SubtitleFormat.cs @@ -0,0 +1,112 @@ +using System.Collections.Generic; + +namespace Nikse.SubtitleEdit.Logic.SubtitleFormats +{ + public abstract class SubtitleFormat + { + + /// + /// Formats supported by Subtitle Edit + /// + public static IList AllSubtitleFormats + { + get + { + return new List + { + new SubRip(), + new DvdStudioPro(), + new DvdSubtitle(), +// new Ebu(), + new MicroDvd(), + new MPlayer2(), + new OpenDvt(), + new SonyDVDArchitect(), + new SonyDVDArchitectWithLineNumbers(), + new SubStationAlpha(), + new SubViewer10(), + new SubViewer20(), + new Sami(), + new TimedText(), + new TMPlayer(), + new YouTubeSbv(), + // new Idx(), + new UnknownSubtitle1(), + new UnknownSubtitle2(), + new UnknownSubtitle3(), + new UnknownSubtitle4(), + new UnknownSubtitle5(), + new Csv(), + new TimeXml(), + }; + } + } + + protected int _errorCount; + + abstract public string Extension + { + get; + } + + abstract public string Name + { + get; + } + + abstract public bool HasLineNumber + { + get; + } + + abstract public bool IsTimeBased + { + get; + } + + public bool IsFrameBased + { + get + { + return !IsTimeBased; + } + } + + public string FriendlyName + { + get + { + return string.Format("{0} ({1})", Name, Extension); + } + } + + public int ErrorCount + { + get { return _errorCount; } + } + + abstract public bool IsMine(List lines, string fileName); + + abstract public string ToText(Subtitle subtitle, string title); + + abstract public void LoadSubtitle(Subtitle subtitle, List lines, string fileName); + + public bool IsVobSubIndexFile + { + get { return Extension == new Idx().Extension; } + } + + public virtual void RemoveNativeFormatting(Subtitle subtitle) + { + } + + public virtual List AlternateExtensions + { + get + { + return new List(); + } + } + + } +} diff --git a/src/Logic/SubtitleFormats/TMPlayer.cs b/src/Logic/SubtitleFormats/TMPlayer.cs new file mode 100644 index 000000000..73e54b61b --- /dev/null +++ b/src/Logic/SubtitleFormats/TMPlayer.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace Nikse.SubtitleEdit.Logic.SubtitleFormats +{ + + public class TMPlayer : SubtitleFormat + { + public override string Extension + { + get { return ".txt"; } + } + + public override string Name + { + get { return "TMPlayer"; } + } + + public override bool HasLineNumber + { + get { return false; } + } + + public override bool IsTimeBased + { + get { return true; } + } + + public override bool IsMine(List lines, string fileName) + { + var subtitle = new Subtitle(); + LoadSubtitle(subtitle, lines, fileName); + return subtitle.Paragraphs.Count > _errorCount; + } + + public override string ToText(Subtitle subtitle, string title) + { + var sb = new StringBuilder(); + foreach (Paragraph p in subtitle.Paragraphs) + { + string text = Utilities.RemoveHtmlTags(p.Text); + text = text.Replace(Environment.NewLine, "|"); + sb.AppendLine(string.Format("{0:00}:{1:00}:{2:00}:{3}", p.StartTime.Hours, + p.StartTime.Minutes, + p.StartTime.Seconds, + text)); + } + return sb.ToString().Trim(); + } + + public override void LoadSubtitle(Subtitle subtitle, List lines, string fileName) + { // 0:02:36:You've returned to the village|after 2 years, Shekhar. + // 00:00:50:America has made my fortune. + var regex = new Regex(@"^\d+:\d\d:\d\d[: ].*$", RegexOptions.Compiled); // accept a " " instead of the last ":" too + _errorCount = 0; + foreach (string line in lines) + { + bool success = false; + if (regex.Match(line).Success) + { + string s = line; + if (line.Length > 9 && line[8] == ' ') + s = line.Substring(0, 8) + ":" + line.Substring(9); + string[] parts = s.Split(':'); + if (parts.Length > 3) + { + int hours = int.Parse(parts[0]); + int minutes = int.Parse(parts[1]); + int seconds = int.Parse(parts[2]); + string text = string.Empty; + for (int i = 3; i < parts.Length; i++ ) + { + if (text.Length == 0) + text = parts[i]; + else + text += ":" + parts[i]; + } + text = text.Replace("|", Environment.NewLine); + var start = new TimeCode(hours, minutes, seconds, 0); + double duration = Utilities.GetDisplayMillisecondsFromText(text) * 1.2; + var end = new TimeCode(TimeSpan.FromMilliseconds(start.TotalMilliseconds + duration)); + + var p = new Paragraph(start, end, text); + subtitle.Paragraphs.Add(p); + success = true; + } + } + if (!success) + _errorCount++; + } + + int index = 0; + foreach (Paragraph p in subtitle.Paragraphs) + { + Paragraph next = subtitle.GetParagraphOrDefault(index+1); + if (next != null && next.StartTime.TotalMilliseconds <= p.EndTime.TotalMilliseconds) + p.EndTime.TotalMilliseconds = next.StartTime.TotalMilliseconds - 1; + + index++; + p.Number = index; + } + + } + } +} diff --git a/src/Logic/SubtitleFormats/TimeXml.cs b/src/Logic/SubtitleFormats/TimeXml.cs new file mode 100644 index 000000000..99da0fbab --- /dev/null +++ b/src/Logic/SubtitleFormats/TimeXml.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Xml; + +namespace Nikse.SubtitleEdit.Logic.SubtitleFormats +{ + class TimeXml : SubtitleFormat + { + public override string Extension + { + get { return ".xml"; } + } + + public override string Name + { + get { return "Xml"; } + } + + public override bool HasLineNumber + { + get { return true; } + } + + public override bool IsTimeBased + { + get { return true; } + } + + public override bool IsMine(List lines, string fileName) + { + Subtitle subtitle = new Subtitle(); + this.LoadSubtitle(subtitle, lines, fileName); + return subtitle.Paragraphs.Count > 0; + } + + public override string ToText(Subtitle subtitle, string title) + { + string xmlStructure = + "" + Environment.NewLine + + ""; + + XmlDocument xml = new XmlDocument(); + xml.LoadXml(xmlStructure); + + foreach (Paragraph p in subtitle.Paragraphs) + { + XmlNode paragraph = xml.CreateElement("Paragraph"); + + XmlNode number = xml.CreateElement("Number"); + number.InnerText = p.Number.ToString(); + paragraph.AppendChild(number); + + XmlNode start = xml.CreateElement("StartMilliseconds"); + start.InnerText = p.StartTime.TotalMilliseconds.ToString(); + paragraph.AppendChild(start); + + XmlNode end = xml.CreateElement("EndMilliseconds"); + end.InnerText = p.EndTime.TotalMilliseconds.ToString(); + paragraph.AppendChild(end); + + XmlNode text = xml.CreateElement("Text"); + text.InnerText = Utilities.RemoveHtmlTags(p.Text); + paragraph.AppendChild(text); + + xml.DocumentElement.AppendChild(paragraph); + } + + MemoryStream ms = new MemoryStream(); + XmlTextWriter writer = new XmlTextWriter(ms, Encoding.UTF8); + writer.Formatting = Formatting.Indented; + xml.Save(writer); + return Encoding.UTF8.GetString(ms.ToArray()).Trim(); + } + + public override void LoadSubtitle(Subtitle subtitle, List lines, string fileName) + { + _errorCount = 0; + + StringBuilder sb = new StringBuilder(); + lines.ForEach(line => sb.AppendLine(line)); + XmlDocument xml = new XmlDocument(); + try + { + xml.LoadXml(sb.ToString()); + } + catch + { + _errorCount = 1; + return; + } + + foreach (XmlNode node in xml.DocumentElement.SelectNodes("Paragraph")) + { + try + { + string start = node.SelectSingleNode("StartMilliseconds").InnerText; + string end = node.SelectSingleNode("EndMilliseconds").InnerText; + string text = node.SelectSingleNode("Text").InnerText; + + subtitle.Paragraphs.Add(new Paragraph(text, Convert.ToDouble(start), Convert.ToDouble(end))); + } + catch (Exception ex) + { + string s = ex.Message; + _errorCount++; + } + } + subtitle.Renumber(1); + } + + } +} + + diff --git a/src/Logic/SubtitleFormats/TimedText.cs b/src/Logic/SubtitleFormats/TimedText.cs new file mode 100644 index 000000000..5d65991c8 --- /dev/null +++ b/src/Logic/SubtitleFormats/TimedText.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Xml; + +namespace Nikse.SubtitleEdit.Logic.SubtitleFormats +{ + class TimedText : SubtitleFormat + { + public override string Extension + { + get { return ".xml"; } + } + + public override string Name + { + get { return "Timed Text"; } + } + + public override bool HasLineNumber + { + get { return false; } + } + + public override bool IsTimeBased + { + get { return true; } + } + + public override bool IsMine(List lines, string fileName) + { + StringBuilder sb = new StringBuilder(); + lines.ForEach(line => sb.AppendLine(line)); + string xmlAsString = sb.ToString().Trim(); + if (xmlAsString.Contains("http://www.w3.org/") && + xmlAsString.Contains("/ttaf1")) + { + XmlDocument xml = new XmlDocument(); + try + { + xml.LoadXml(xmlAsString); + + XmlNamespaceManager nsmgr = new XmlNamespaceManager(xml.NameTable); + nsmgr.AddNamespace("ttaf1", xml.DocumentElement.NamespaceURI); + + XmlNode div = xml.DocumentElement.SelectSingleNode("//ttaf1:body", nsmgr).FirstChild; + int numberOfParagraphs = div.ChildNodes.Count; + return numberOfParagraphs > 0; + } + catch (Exception ex) + { + string s = ex.Message; + return false; + } + } + else + { + return false; + } + } + + private static string ConvertToTimeString(TimeCode time) + { + return string.Format("{0:00}:{1:00}:{2:00}.{3:00}", time.Hours, time.Minutes, time.Seconds, time.Milliseconds); + } + + public override string ToText(Subtitle subtitle, string title) + { + string xmlStructure = + "" + Environment.NewLine + + "" + Environment.NewLine + + " " + Environment.NewLine + + " " + Environment.NewLine + + " " + Environment.NewLine + + " " + Environment.NewLine + + " " + Environment.NewLine + + "