Initial version

git-svn-id: https://subtitleedit.googlecode.com/svn/trunk@14 99eadd0c-20b8-1223-b5c4-2a2b2df33de2
This commit is contained in:
niksedk 2010-10-12 11:22:15 +00:00
parent 976be2c6e6
commit 8f89e3146e
44 changed files with 8787 additions and 0 deletions

View File

@ -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<string> 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<string> 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("\"\"", "\"");
}
}
}

View File

@ -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<string> 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<string> 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;
}
}
}
}

View File

@ -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<string> 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<string> 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);
}
}
}

View File

@ -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
{
/// <summary>
/// EBU Subtitling data exchange format
/// </summary>
public class Ebu : SubtitleFormat
{
/// <summary>
/// GSI block (1024 bytes)
/// </summary>
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
}
}
}
/// <summary>
/// TTI block 128 bytes
/// </summary>
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<string> 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<string> 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<EbuTextTimingInformation> 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<EbuTextTimingInformation> list = new List<EbuTextTimingInformation>();
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;
}
}
}

View File

@ -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<string> 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<int>();
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<string> 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;
}
}
}

View File

@ -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<string> lines, string fileName)
{
int errors = 0;
List<string> trimmedLines = new List<string>();
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("<b>", "{Y:b}");
text = text.Replace("</b>", string.Empty);
text = text.Replace("<i>", "{Y:i}");
text = text.Replace("</i>", string.Empty);
text = text.Replace("<u>", "{Y:u}");
text = text.Replace("</u>", string.Empty);
sb.AppendLine(text);
}
return sb.ToString().Trim();
}
public override void LoadSubtitle(Subtitle subtitle, List<string> 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);
}
}
}

View File

@ -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<string> lines, string fileName)
{
var trimmedLines = new List<string>();
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("<b>","{Y:b}");
text = text.Replace("</b>", string.Empty);
text = text.Replace("<i>","{Y:i}");
text = text.Replace("</i>", string.Empty);
text = text.Replace("<u>","{Y:u}");
text = text.Replace("</u>", string.Empty);
sb.AppendLine(text);
}
return sb.ToString().Trim();
}
public override void LoadSubtitle(Subtitle subtitle, List<string> 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;
}
}
}

View File

@ -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<string> 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 =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + Environment.NewLine +
"<OpenDVT UUID=\"{" + guid + "\" ShortID=\"" + title + "\" Type=\"Deposition\" Version=\"1.3\">" + Environment.NewLine +
"<Information>" + Environment.NewLine +
" <Origination>" + Environment.NewLine +
" <ID>" + guid + "</ID> " + Environment.NewLine +
" <AppName>Subtitle Edit</AppName> " + Environment.NewLine +
" <AppVersion>2.9</AppVersion> " + Environment.NewLine +
" <VendorName>Nikse.dk</VendorName> " + Environment.NewLine +
" <VendorPhone></VendorPhone> " + Environment.NewLine +
" <VendorURL>www.nikse.dk.com</VendorURL> " + Environment.NewLine +
" </Origination>" + Environment.NewLine +
" <Case>" + Environment.NewLine +
" <MatterNumber /> " + Environment.NewLine +
" </Case>" + Environment.NewLine +
" <Deponent>" + Environment.NewLine +
" <FirstName></FirstName> " + Environment.NewLine +
" <LastName></LastName> " + Environment.NewLine +
" </Deponent>" + Environment.NewLine +
" <ReportingFirm>" + Environment.NewLine +
" <Name /> " + Environment.NewLine +
" </ReportingFirm>" + Environment.NewLine +
" <FirstPageNo>1</FirstPageNo> " + Environment.NewLine +
" <LastPageNo>3</LastPageNo> " + Environment.NewLine +
" <MaxLinesPerPage>25</MaxLinesPerPage> " + Environment.NewLine +
" <Volume>1</Volume> " + Environment.NewLine +
" <TakenOn>06/02/2010</TakenOn> " + Environment.NewLine +
" <TranscriptVerify></TranscriptVerify> " + Environment.NewLine +
" <PrintVerify></PrintVerify> " + Environment.NewLine +
" </Information>" + Environment.NewLine +
"<Lines Count=\"" + subtitle.Paragraphs.Count + "\">" + Environment.NewLine +
"</Lines>" + Environment.NewLine +
"<Streams Count=\"0\">" + Environment.NewLine +
"<Stream ID=\"0\">" + Environment.NewLine +
//"<URI>C:\Users\Eric\Desktop\Player Folder\Bing\Bing.mpg</URI>
//"<FileSize>52158464</FileSize>
//"<FileDate>06/02/2009 10:44:37</FileDate>
//"<DurationMs>166144</DurationMs>
//"<VolumeLabel>OS</VolumeLabel>
" </Stream>" + Environment.NewLine +
"</Streams>" + Environment.NewLine +
"</OpenDVT>";
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<string> 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);
}
}
}

View File

@ -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<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)
{
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 =
@"<SAMI>
<HEAD>
<TITLE>_TITLE_</TITLE>
<SAMIParam>
Metrics {time:ms;}
Spec {MSFT:1.0;}
</SAMIParam>
<STYLE TYPE=""text/css"">
<!--
P { font-family: Arial; font-weight: normal; color: white; background-color: black; text-align: center; }
_LANGUAGE-STYLE_
-->
</STYLE>
</HEAD>
<BODY>
<-- 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)
//<SYNC Start=65264><P>Let's go!
//<SYNC Start=66697><P><BR>
const string paragraphWriteFormat =
@"<SYNC Start={0}><P Class={3}>{2}</P></SYNC>
<SYNC Start={1}><P Class={3}>&nbsp;</P></SYNC>";
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, "<br />"), languageTag));
}
sb.AppendLine("</BODY>");
sb.AppendLine("</SAMI>");
return sb.ToString().Trim();
}
public override void LoadSubtitle(Subtitle subtitle, List<string> 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 = "<sync start=";
int syncStartPos = allInputLower.IndexOf(syncTag);
int index = syncStartPos + syncTag.Length;
var p = new Paragraph();
while (syncStartPos >= 0)
{
string millisecAsString = string.Empty;
while (index < allInput.Length && "0123456789".Contains(allInput[index].ToString()))
{
millisecAsString += allInput[index];
index++;
}
while (index < allInput.Length && allInput[index] != '>')
index++;
if (index < allInput.Length && allInput[index] == '>')
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() != "<P")
{
st--;
}
if (st > 0)
{
text = text.Substring(0, st) + text.Substring(sourceIndex);
}
int et = st;
while (et < text.Length - 5 && text.Substring(et, 3).ToUpper() != "<P>" && text.Substring(et, 4).ToUpper() != "</P>")
{
et++;
}
text = text.Substring(0, st) + text.Substring(et);
}
text.Replace(Environment.NewLine, " ");
text.Replace(" ", " ");
text = text.TrimEnd();
text = text.Replace("<BR>", Environment.NewLine);
text = text.Replace("<BR/>", Environment.NewLine);
text = text.Replace("<BR />", Environment.NewLine);
text = text.Replace("<br>", Environment.NewLine);
text = text.Replace("<br/>", Environment.NewLine);
text = text.Replace("<br />", 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("&nbsp;", 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);
}
}
}

View File

@ -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<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)
{
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<string> 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);
}
}
}

View File

@ -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<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)
{
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<string> 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);
}
}
}

View File

@ -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<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 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<string> lines, string fileName)
{
bool doRenum = false;
_paragraph = new Paragraph();
_expecting = ExpectingLine.Number;
_errorCount = 0;
subtitle.Paragraphs.Clear();
for (int i=0; i<lines.Count; i++)
{
string line = lines[i].TrimEnd();
string next = string.Empty;
if (i + 1 < lines.Count)
next = lines[i + 1];
// A new line is missing between two paragraphs (buggy srt file)
if (_expecting == ExpectingLine.Text && i + 1 < lines.Count &&
_paragraph != null && !string.IsNullOrEmpty(_paragraph.Text) && Utilities.IsInteger(line) &&
_regexTimeCodes.IsMatch(lines[i+1]))
{
ReadLine(subtitle, string.Empty, string.Empty);
}
if (_expecting == ExpectingLine.Number && _regexTimeCodes.IsMatch(line))
{
_expecting = ExpectingLine.TimeCodes;
doRenum = true;
}
ReadLine(subtitle, line, next);
}
if (_paragraph.Text.Trim().Length > 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;
}
}
}

View File

@ -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<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 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("<i>", @"{\i1}");
text = text.Replace("</i>", @"{\i0}");
text = text.Replace("<u>", @"{\u1}");
text = text.Replace("</u>", @"{\u0}");
text = text.Replace("<b>", @"{\b1}");
text = text.Replace("</b>", @"{\b0}");
if (text.Contains("<font "))
{
int start = text.IndexOf(@"<font ");
int end = text.IndexOf('>', start);
if (end > 0)
{
string fontTag = text.Substring(start + 4, end - (start + 4));
text = text.Remove(start, end - start + 1);
text = text.Replace("</font>", 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}", "<i>");
text = text.Replace(@"{\i0}", "</i>");
if (FixCommonErrors.CountTagInText(text, "<i>") > FixCommonErrors.CountTagInText(text, "</i>"))
text += "</i>";
text = text.Replace(@"{\u1}", "<u>");
text = text.Replace(@"{\u0}", "</u>");
if (FixCommonErrors.CountTagInText(text, "<u>") > FixCommonErrors.CountTagInText(text, "</u>"))
text += "</u>";
text = text.Replace(@"{\b1}", "<b>");
text = text.Replace(@"{\b0}", "</b>");
if (FixCommonErrors.CountTagInText(text, "<b>") > FixCommonErrors.CountTagInText(text, "</b>"))
text += "</b>";
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, "<font name=\"" + fontName + "\">");
text += "</font>";
}
}
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, "<font size=\"" + fontSize + "\">");
text += "</font>";
}
}
}
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, "<font color=\"" + color + "\">");
text += "</font>";
}
}
}
return text;
}
public override void LoadSubtitle(Subtitle subtitle, List<string> 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<string> AlternateExtensions
{
get
{
return new List<string>() { ".ass" };
}
}
}
}

View File

@ -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<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)
{
//[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<string> 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);
}
}
}

View File

@ -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<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 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("<i>", "{\\i1}");
text = text.Replace("</i>", "{\\i0}");
text = text.Replace("<b>", "{\\b1}'");
text = text.Replace("</b>", "{\\b0}");
text = text.Replace("<u>", "{\\u1}");
text = text.Replace("</u>", "{\\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<string> 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}", "<i>");
text = text.Replace("{\\i0}", "</i>");
text = text.Replace("{\\b1}", "<b>'");
text = text.Replace("{\\b0}", "</b>");
text = text.Replace("{\\u1}", "<u>");
text = text.Replace("{\\u0}", "</u>");
paragraph.Text = text;
subtitle.Paragraphs.Add(paragraph);
paragraph = new Paragraph();
expecting = ExpectingLine.TimeCodes;
}
}
}
}
subtitle.Renumber(1);
}
}
}

View File

@ -0,0 +1,112 @@
using System.Collections.Generic;
namespace Nikse.SubtitleEdit.Logic.SubtitleFormats
{
public abstract class SubtitleFormat
{
/// <summary>
/// Formats supported by Subtitle Edit
/// </summary>
public static IList<SubtitleFormat> AllSubtitleFormats
{
get
{
return new List<SubtitleFormat>
{
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<string> lines, string fileName);
abstract public string ToText(Subtitle subtitle, string title);
abstract public void LoadSubtitle(Subtitle subtitle, List<string> lines, string fileName);
public bool IsVobSubIndexFile
{
get { return Extension == new Idx().Extension; }
}
public virtual void RemoveNativeFormatting(Subtitle subtitle)
{
}
public virtual List<string> AlternateExtensions
{
get
{
return new List<string>();
}
}
}
}

View File

@ -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<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)
{
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<string> 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;
}
}
}
}

View File

@ -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<string> 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 =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + Environment.NewLine +
"<Subtitle/>";
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<string> 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);
}
}
}

View File

@ -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<string> 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 =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + Environment.NewLine +
"<tt xmlns=\"http://www.w3.org/2006/10/ttaf1\" xmlns:ttp=\"http://www.w3.org/2006/10/ttaf1#parameter\" ttp:timeBase=\"media\" xmlns:tts=\"http://www.w3.org/2006/10/ttaf1#style\" xml:lang=\"en\" xmlns:ttm=\"http://www.w3.org/2006/10/ttaf1#metadata\">" + Environment.NewLine +
" <head>" + Environment.NewLine +
" <metadata>" + Environment.NewLine +
" <ttm:title></ttm:title>" + Environment.NewLine +
" </metadata>" + Environment.NewLine +
" <styling>" + Environment.NewLine +
" <style id=\"s0\" tts:backgroundColor=\"black\" tts:fontStyle=\"normal\" tts:fontSize=\"16\" tts:fontFamily=\"sansSerif\" tts:color=\"white\" />" + Environment.NewLine +
" </styling>" + Environment.NewLine +
" </head>" + Environment.NewLine +
" <body tts:textAlign=\"center\" style=\"s0\">" + Environment.NewLine +
" <div />" + Environment.NewLine +
" </body>" + Environment.NewLine +
"</tt>";
XmlDocument xml = new XmlDocument();
xml.LoadXml(xmlStructure);
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xml.NameTable);
nsmgr.AddNamespace("ttaf1", "http://www.w3.org/2006/10/ttaf1");
nsmgr.AddNamespace("ttp", "http://www.w3.org/2006/10/ttaf1#parameter");
nsmgr.AddNamespace("tts", "http://www.w3.org/2006/10/ttaf1#style");
nsmgr.AddNamespace("ttm", "http://www.w3.org/2006/10/ttaf1#metadata");
XmlNode titleNode = xml.DocumentElement.SelectSingleNode("//ttaf1:head", nsmgr).FirstChild.FirstChild;
titleNode.InnerText = title;
XmlNode div = xml.DocumentElement.SelectSingleNode("//ttaf1:body", nsmgr).FirstChild;
int no = 0;
foreach (Paragraph p in subtitle.Paragraphs)
{
XmlNode paragraph = xml.CreateElement("p", "http://www.w3.org/2006/10/ttaf1");
string text = Utilities.RemoveHtmlTags(p.Text);
bool first = true;
foreach (string line in text.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries))
{
if (!first)
{
XmlNode br = xml.CreateElement("br", "http://www.w3.org/2006/10/ttaf1");
paragraph.AppendChild(br);
}
XmlNode textNode = xml.CreateTextNode(line);
paragraph.AppendChild(textNode);
first = false;
}
XmlAttribute start = xml.CreateAttribute("begin");
start.InnerText = ConvertToTimeString(p.StartTime);
paragraph.Attributes.Append(start);
XmlAttribute id = xml.CreateAttribute("id");
id.InnerText = "p" + no.ToString();
paragraph.Attributes.Append(id);
XmlAttribute end = xml.CreateAttribute("end");
end.InnerText = ConvertToTimeString(p.EndTime);
paragraph.Attributes.Append(end);
div.AppendChild(paragraph);
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<string> lines, string fileName)
{
_errorCount = 0;
StringBuilder sb = new StringBuilder();
lines.ForEach(line => sb.AppendLine(line));
XmlDocument xml = new XmlDocument();
xml.LoadXml(sb.ToString());
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xml.NameTable);
nsmgr.AddNamespace("ttaf1", xml.DocumentElement.NamespaceURI);
XmlNode div = xml.DocumentElement.SelectSingleNode("//ttaf1:body", nsmgr).FirstChild;
foreach (XmlNode node in div.ChildNodes)
{
try
{
StringBuilder pText = new StringBuilder();
foreach (XmlNode innerNode in node.ChildNodes)
{
switch (innerNode.Name.ToString())
{
case "br":
pText.AppendLine();
break;
default:
pText.Append(innerNode.InnerText);
break;
}
}
string start = node.Attributes["begin"].InnerText;
string end = node.Attributes["end"].InnerText;
subtitle.Paragraphs.Add(new Paragraph(GetTimeCode(start),GetTimeCode(end), pText.ToString()));
}
catch (Exception ex)
{
string s = ex.Message;
_errorCount++;
}
}
subtitle.Renumber(1);
}
private static TimeCode GetTimeCode(string s)
{
string[] parts = s.Split(new char[] { ':', '.', ',' });
TimeSpan ts = new TimeSpan(0, int.Parse(parts[0]), int.Parse(parts[1]), int.Parse(parts[2]), int.Parse(parts[3]));
return new TimeCode(ts);
}
}
}

View File

@ -0,0 +1,172 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
namespace Nikse.SubtitleEdit.Logic.SubtitleFormats
{
public class UnknownSubtitle1 : SubtitleFormat
{
enum ExpectingLine
{
TimeCodes,
Text
}
Paragraph _paragraph;
ExpectingLine _expecting = ExpectingLine.TimeCodes;
readonly Regex _regexTimeCodes = new Regex(@"^TIMEIN:\s*[0123456789-]+:[0123456789-]+:[0123456789-]+:[0123456789-]+\s*DURATION:\s*[0123456789-]+:[0123456789-]+\s*TIMEOUT:\s*[0123456789-]+:[0123456789-]+:[0123456789-]+:[0123456789-]+$", RegexOptions.Compiled);
public override string Extension
{
get { return ".txt"; }
}
public override string Name
{
get { return "Unknown1"; }
}
public override bool HasLineNumber
{
get { return false; }
}
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)
{
//TIMEIN: 01:00:01:09 DURATION: 01:20 TIMEOUT: --:--:--:--
//Broadcasting
//from an undisclosed location...
//TIMEIN: 01:00:04:12 DURATION: 04:25 TIMEOUT: 01:00:09:07
const string paragraphWriteFormat = "TIMEIN: {0}\tDURATION: {1}\tTIMEOUT: {2}\r\n{3}\r\n";
var sb = new StringBuilder();
foreach (Paragraph p in subtitle.Paragraphs)
{
string startTime = string.Format("{0:00}:{1:00}:{2:00}:{3:00}", p.StartTime.Hours, p.StartTime.Minutes, p.StartTime.Seconds, p.StartTime.Milliseconds / 10);
string duration = string.Format("{0:00}:{1:00}", p.Duration.Seconds, p.Duration.Milliseconds / 10);
string timeOut = string.Format("{0:00}:{1:00}:{2:00}:{3:00}", p.EndTime.Hours, p.EndTime.Minutes, p.EndTime.Seconds, p.EndTime.Milliseconds / 10);
sb.AppendLine(string.Format(paragraphWriteFormat, startTime, duration, timeOut, p.Text));
}
return sb.ToString().Trim();
}
public override void LoadSubtitle(Subtitle subtitle, List<string> lines, string fileName)
{
_paragraph = new Paragraph();
_expecting = ExpectingLine.TimeCodes;
_errorCount = 0;
subtitle.Paragraphs.Clear();
foreach (string line in lines)
{
ReadLine(subtitle, line);
}
if (_paragraph.Text.Trim().Length > 0)
subtitle.Paragraphs.Add(_paragraph);
subtitle.Renumber(1);
}
private void ReadLine(Subtitle subtitle, string line)
{
switch (_expecting)
{
case ExpectingLine.TimeCodes:
if (TryReadTimeCodesLine(line, _paragraph))
{
_paragraph.Text = string.Empty;
_expecting = ExpectingLine.Text;
}
else if (line.Trim().Length > 0)
{
_errorCount++;
_expecting = ExpectingLine.Text ; // 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 += line.TrimEnd();
}
else
{
subtitle.Paragraphs.Add(_paragraph);
_paragraph = new Paragraph();
_expecting = ExpectingLine.TimeCodes;
}
break;
}
}
private bool TryReadTimeCodesLine(string line, Paragraph paragraph)
{
line = line.Trim();
if (_regexTimeCodes.IsMatch(line))
{
//TIMEIN: 01:00:04:12 DURATION: 04:25 TIMEOUT: 01:00:09:07
string s = line.Replace("TIMEIN:", string.Empty).Replace("DURATION", string.Empty).Replace("TIMEOUT", string.Empty).Replace(" ", string.Empty).Replace("\t", string.Empty);
string[] parts = s.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]) * 10;
int durationSeconds = 0;
if (parts[4] != "-")
durationSeconds = int.Parse(parts[4]);
int durationMilliseconds = 0;
if (parts[5] != "--")
durationMilliseconds = int.Parse(parts[5]) * 10;
int endHours = 0;
if (parts[6] != "--")
endHours = int.Parse(parts[6]);
int endMinutes = 0;
if (parts[7] != "--")
endMinutes = int.Parse(parts[7]);
int endSeconds = 0;
if (parts[8] != "--")
endSeconds = int.Parse(parts[8]);
int endMilliseconds = 0;
if (parts[9] != "--")
endMilliseconds = int.Parse(parts[9]) * 10;
paragraph.StartTime = new TimeCode(startHours, startMinutes, startSeconds, startMilliseconds);
if (durationSeconds > 0 || durationMilliseconds > 0)
paragraph.EndTime.TotalMilliseconds = paragraph.StartTime.TotalMilliseconds + (durationSeconds * 1000 + durationMilliseconds);
else
paragraph.EndTime = new TimeCode(endHours, endMinutes, endSeconds, endMilliseconds);
return true;
}
catch
{
return false;
}
}
return false;
}
}
}

View File

@ -0,0 +1,159 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Nikse.SubtitleEdit.Logic.SubtitleFormats
{
//Subtitle number: 1
//Start time (or frames): 00:00:48,862:0000001222
//End time (or frames): 00:00:50,786:0000001270
//Subtitle text: In preajma lacului Razel,
public class UnknownSubtitle2 : SubtitleFormat
{
enum ExpectingLine
{
Number,
StartTime,
EndTime,
Text
}
Paragraph _paragraph;
ExpectingLine _expecting = ExpectingLine.Number;
public override string Extension
{
get { return ".txt"; }
}
public override string Name
{
get { return "Unknown2"; }
}
public override bool HasLineNumber
{
get { return false; }
}
public override bool IsTimeBased
{
get { return true; }
}
public override bool IsMine(List<string> lines, string fileName)
{
Subtitle subtitle = new Subtitle();
LoadSubtitle(subtitle, lines, fileName);
return subtitle.Paragraphs.Count > _errorCount;
}
public override string ToText(Subtitle subtitle, string title)
{
//Subtitle number: 1
//Start time (or frames): 00:00:48,862:0000001222
//End time (or frames): 00:00:50,786:0000001270
//Subtitle text: In preajma lacului Razel,
const string paragraphWriteFormat = "Subtitle number: {0}\r\nStart time (or frames): {1}\r\nEnd time (or frames): {2}\r\nSubtitle text: {3}\r\n";
StringBuilder sb = new StringBuilder();
foreach (Paragraph p in subtitle.Paragraphs)
{
string startTime = string.Format("{0:00}:{1:00}:{2:00},{3:00}:0000000000", p.StartTime.Hours, p.StartTime.Minutes, p.StartTime.Seconds, p.StartTime.Milliseconds / 10);
string timeOut = string.Format("{0:00}:{1:00}:{2:00},{3:00}:0000000000", p.EndTime.Hours, p.EndTime.Minutes, p.EndTime.Seconds, p.EndTime.Milliseconds / 10);
sb.AppendLine(string.Format(paragraphWriteFormat, p.Number, startTime, timeOut, p.Text.Replace(Environment.NewLine, "|")));
}
return sb.ToString().Trim();
}
public override void LoadSubtitle(Subtitle subtitle, List<string> lines, string fileName)
{
_paragraph = new Paragraph();
_expecting = ExpectingLine.Number;
_errorCount = 0;
subtitle.Paragraphs.Clear();
foreach (string line in lines)
{
ReadLine(subtitle, line);
}
if (_paragraph.Text.Trim().Length > 0)
subtitle.Paragraphs.Add(_paragraph);
subtitle.Renumber(1);
}
private void ReadLine(Subtitle subtitle, string line)
{
//Subtitle number: 1
//Start time (or frames): 00:00:48,862:0000001222
//End time (or frames): 00:00:50,786:0000001270
//Subtitle text: In preajma lacului Razel,
switch (_expecting)
{
case ExpectingLine.Number:
if (line.StartsWith("Subtitle number: "))
{
_expecting = ExpectingLine.StartTime;
}
break;
case ExpectingLine.StartTime:
if (line.StartsWith("Start time (or frames): "))
{
TryReadTimeCodesLine(line.Substring(23), _paragraph, true);
_expecting = ExpectingLine.EndTime;
}
break;
case ExpectingLine.EndTime:
if (line.StartsWith("End time (or frames): "))
{
TryReadTimeCodesLine(line.Substring(21), _paragraph, false);
_expecting = ExpectingLine.Text;
}
break;
case ExpectingLine.Text:
if (line.StartsWith("Subtitle text: "))
{
string text = line.Substring(14).Trim();
text = text.Replace("|", Environment.NewLine);
_paragraph.Text = text;
subtitle.Paragraphs.Add(_paragraph);
_paragraph = new Paragraph();
_expecting = ExpectingLine.Number;
}
break;
}
}
private static bool TryReadTimeCodesLine(string line, Paragraph paragraph, bool start)
{
line = line.Trim();
//00:00:48,862:0000001222
line = line.Replace(",", ":");
string[] parts = line.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]);
if (start)
paragraph.StartTime = new TimeCode(startHours, startMinutes, startSeconds, startMilliseconds);
else
paragraph.EndTime = new TimeCode(startHours, startMinutes, startSeconds, startMilliseconds);
return true;
}
catch
{
return false;
}
}
}
}

View File

@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Nikse.SubtitleEdit.Logic.SubtitleFormats
{
//Subtitle number: 1
//Start time (or frames): 00:00:48,862:0000001222
//End time (or frames): 00:00:50,786:0000001270
//Subtitle text: In preajma lacului Razel,
public class UnknownSubtitle3 : SubtitleFormat
{
public override string Extension
{
get { return ".txt"; }
}
public override string Name
{
get { return "Unknown3"; }
}
public override bool HasLineNumber
{
get { return false; }
}
public override bool IsTimeBased
{
get { return true; }
}
public override bool IsMine(List<string> lines, string fileName)
{
Subtitle subtitle = new Subtitle();
LoadSubtitle(subtitle, lines, fileName);
return subtitle.Paragraphs.Count > _errorCount;
}
public override string ToText(Subtitle subtitle, string title)
{
//150583||3968723||Rythme standard quatre-par-quatre.\~- Sûr... Accord d'entrée, D majeur?||
//155822||160350||Rob n'y connait rien en claviers. Il\~commence chaque chanson en D majeur||
const string paragraphWriteFormat = "{0}||{1}||{2}||";
StringBuilder sb = new StringBuilder();
foreach (Paragraph p in subtitle.Paragraphs)
{
sb.AppendLine(string.Format(paragraphWriteFormat, p.StartFrame, p.EndFrame, p.Text.Replace(Environment.NewLine, "\\~")));
}
return sb.ToString().Trim();
}
public override void LoadSubtitle(Subtitle subtitle, List<string> lines, string fileName)
{
foreach (string line in lines)
{
ReadLine(subtitle, line);
}
subtitle.Renumber(1);
}
private void ReadLine(Subtitle subtitle, string line)
{
// 150583||3968723||Rythme standard quatre-par-quatre.\~- Sûr... Accord d'entrée, D majeur?||
// 155822||160350||Rob n'y connait rien en claviers. Il\~commence chaque chanson en D majeur||
string[] parts = line.Split(new char[] {'|'}, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 3)
{
int start;
int end;
if (int.TryParse(parts[0], out start) && int.TryParse(parts[1], out end))
{
Paragraph p = new Paragraph(parts[2].Replace("\\~", Environment.NewLine), start, end);
subtitle.Paragraphs.Add(p);
}
else
{
_errorCount++;
}
}
else
{
_errorCount++;
}
}
}
}

View File

@ -0,0 +1,144 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
namespace Nikse.SubtitleEdit.Logic.SubtitleFormats
{
public class UnknownSubtitle4 : SubtitleFormat
{
enum ExpectingLine
{
TimeCodes,
Text
}
public override string Extension
{
get { return ".sub"; }
}
public override string Name
{
get { return "Unknown 4"; }
}
public override bool HasLineNumber
{
get { return false; }
}
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 paragraphWriteFormat = "{0:00}:{1:00}:{2:00}.{3:00}, {4:00}:{5:00}:{6:00}.{7:00}{8}{9}";
//00:00:07.00, 00:00:12.00
//Welche Auswirkung Mikroversicherungen auf unsere Klienten hat? Lassen wir sie für sich selber sprechen!
//
//00:00:22.00, 00:00:27.00
//Arme Menschen in Uganda leben oft in schlechten Unterkünften.
var sb = new StringBuilder();
foreach (Paragraph p in subtitle.Paragraphs)
{
string text = p.Text.Replace(Environment.NewLine, "|");
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<string> 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("|", Environment.NewLine);
text = line.Replace("[br]", Environment.NewLine);
text = line.Replace("<br>", Environment.NewLine);
text = line.Replace("<br />", Environment.NewLine);
text = text.Replace("{\\i1}", "<i>");
text = text.Replace("{\\i0}", "</i>");
text = text.Replace("{\\b1}", "<b>'");
text = text.Replace("{\\b0}", "</b>");
text = text.Replace("{\\u1}", "<u>");
text = text.Replace("{\\u0}", "</u>");
paragraph.Text = text;
subtitle.Paragraphs.Add(paragraph);
paragraph = new Paragraph();
expecting = ExpectingLine.TimeCodes;
}
}
}
}
subtitle.Renumber(1);
}
}
}

View File

@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
namespace Nikse.SubtitleEdit.Logic.SubtitleFormats
{
class UnknownSubtitle5 : SubtitleFormat
{
public override string Extension
{
get { return ".xml"; }
}
public override string Name
{
get { return "Unknown 5"; }
}
public override bool HasLineNumber
{
get { return false; }
}
public override bool IsTimeBased
{
get { return true; }
}
public override bool IsMine(List<string> 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 =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + Environment.NewLine +
"<transcript/>";
XmlDocument xml = new XmlDocument();
xml.LoadXml(xmlStructure);
foreach (Paragraph p in subtitle.Paragraphs)
{
XmlNode paragraph = xml.CreateElement("text");
XmlAttribute start = xml.CreateAttribute("start");
start.InnerText = string.Format("{0}", p.StartTime.TotalMilliseconds / 1000);
paragraph.Attributes.Append(start);
XmlAttribute duration = xml.CreateAttribute("dur");
duration.InnerText = string.Format("{0}", p.Duration.TotalMilliseconds / 1000);
paragraph.Attributes.Append(duration);
paragraph.InnerText = p.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<string> 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("text"))
{
try
{
string start = node.Attributes["start"].InnerText;
string end = node.Attributes["dur"].InnerText;
string text = node.InnerText;
subtitle.Paragraphs.Add(new Paragraph(text, Convert.ToDouble(start), Convert.ToDouble(start) + Convert.ToDouble(end)));
}
catch (Exception ex)
{
string s = ex.Message;
_errorCount++;
}
}
subtitle.Renumber(1);
}
}
}

View File

@ -0,0 +1,195 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
namespace Nikse.SubtitleEdit.Logic.SubtitleFormats
{
public class YouTubeSbv : SubtitleFormat
{
enum ExpectingLine
{
TimeCodes,
Text
}
Paragraph _paragraph;
ExpectingLine _expecting = ExpectingLine.TimeCodes;
readonly Regex _regexTimeCodes = new Regex(@"^-?\d+:-?\d+:-?\d+[:,.]-?\d+,\d+:-?\d+:-?\d+[:,.]-?\d+$", RegexOptions.Compiled);
public override string Extension
{
get { return ".sbv"; }
}
public override string Name
{
get { return "Youtube sbv"; }
}
public override bool HasLineNumber
{
get { return false; }
}
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 paragraphWriteFormat = "{0},{1}\r\n{2}\r\n\r\n";
var sb = new StringBuilder();
foreach (Paragraph p in subtitle.Paragraphs)
{
sb.Append(string.Format(paragraphWriteFormat, FormatTime(p.StartTime), FormatTime(p.EndTime), p.Text));
}
return sb.ToString().Trim();
}
private string FormatTime(TimeCode timeCode)
{
return string.Format("{0}:{1:00}:{2:00}.{3:000}", timeCode.Hours, timeCode.Minutes, timeCode.Seconds, timeCode.Milliseconds);
}
public override void LoadSubtitle(Subtitle subtitle, List<string> lines, string fileName)
{
//0:00:07.500,0:00:13.500
//In den Bergen über Musanze in Ruanda feiert die Trustbank (Kreditnehmer-Gruppe) "Trususanze" ihren Erfolg.
//0:00:14.000,0:00:17.000
//Indem sie ihre Zukunft einander anvertraut haben, haben sie sich
_paragraph = new Paragraph();
_expecting = ExpectingLine.TimeCodes;
_errorCount = 0;
subtitle.Paragraphs.Clear();
for (int i = 0; i < lines.Count; i++)
{
string line = lines[i].TrimEnd();
string next = string.Empty;
if (i + 1 < lines.Count)
next = lines[i + 1];
// A new line is missing between two paragraphs (buggy srt file)
if (_expecting == ExpectingLine.Text && i + 1 < lines.Count &&
_paragraph != null && !string.IsNullOrEmpty(_paragraph.Text) &&
_regexTimeCodes.IsMatch(lines[i]))
{
ReadLine(subtitle, string.Empty, string.Empty);
}
ReadLine(subtitle, line, next);
}
if (_paragraph.Text.Trim().Length > 0)
subtitle.Paragraphs.Add(_paragraph);
foreach (Paragraph p in subtitle.Paragraphs)
p.Text = p.Text.Replace(Environment.NewLine + Environment.NewLine, Environment.NewLine);
subtitle.Renumber(1);
}
private void ReadLine(Subtitle subtitle, string line, string next)
{
switch (_expecting)
{
case ExpectingLine.TimeCodes:
if (TryReadTimeCodesLine(line, _paragraph))
{
_paragraph.Text = string.Empty;
_expecting = ExpectingLine.Text;
}
else if (line.Trim().Length > 0)
{
_errorCount++;
_expecting = ExpectingLine.TimeCodes; // 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.TimeCodes;
}
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;
}
private string RemoveBadChars(string line)
{
line = line.Replace("\0", " ");
return line;
}
private bool TryReadTimeCodesLine(string line, Paragraph paragraph)
{
line = line.Replace(".", ":");
line = line.Replace("،", ",");
line = line.Replace("¡", ":");
if (_regexTimeCodes.IsMatch(line))
{
line = line.Replace(",", ":");
string[] parts = line.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;
}
}
}

View File

@ -0,0 +1,154 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
/// <copyright>
/// Giora Tamir (giora@gtamir.com), 2005
/// </copyright>
namespace Nikse.SubtitleEdit.Logic
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
unsafe struct AVIMAINHEADER
{ // 'avih'
public int dwMicroSecPerFrame;
public int dwMaxBytesPerSec;
public int dwPaddingGranularity;
public int dwFlags;
public int dwTotalFrames;
public int dwInitialFrames;
public int dwStreams;
public int dwSuggestedBufferSize;
public int dwWidth;
public int dwHeight;
public int dwReserved0;
public int dwReserved1;
public int dwReserved2;
public int dwReserved3;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct AVIEXTHEADER
{ // 'dmlh'
public int dwGrandFrames; // total number of frames in the file
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 244)]
public int[] dwFuture; // to be defined later
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct RECT
{
public short left;
public short top;
public short right;
public short bottom;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct AVISTREAMHEADER
{ // 'strh'
public int fccType; // stream type codes
public int fccHandler;
public int dwFlags;
public short wPriority;
public short wLanguage;
public int dwInitialFrames;
public int dwScale;
public int dwRate; // dwRate/dwScale is stream tick rate in ticks/sec
public int dwStart;
public int dwLength;
public int dwSuggestedBufferSize;
public int dwQuality;
public int dwSampleSize;
public RECT rcFrame;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct AVIOLDINDEXENTRY
{
public int dwChunkId;
public int dwFlags;
public int dwOffset; // offset of riff chunk header for the data
public int dwSize; // size of the data (excluding riff header size)
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct TIMECODE
{
public short wFrameRate;
public short wFrameFract;
public int cFrames;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct TIMECODEDATA
{
TIMECODE time;
public int dwSMPTEflags;
public int dwUser;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct WAVEFORMATEX
{
public short wFormatTag;
public short nChannels;
public int nSamplesPerSec;
public int nAvgBytesPerSec;
public short nBlockAlign;
public short wBitsPerSample;
public short cbSize;
}
static class AviRiffData
{
#region AVI constants
// AVIMAINHEADER flags
public const int AVIF_HASINDEX = 0x00000010; // Index at end of file?
public const int AVIF_MUSTUSEINDEX = 0x00000020;
public const int AVIF_ISINTERLEAVED = 0x00000100;
public const int AVIF_TRUSTCKTYPE = 0x00000800; // Use CKType to find key frames
public const int AVIF_WASCAPTUREFILE = 0x00010000;
public const int AVIF_COPYRIGHTED = 0x00020000;
// AVISTREAMINFO flags
public const int AVISF_DISABLED = 0x00000001;
public const int AVISF_VIDEO_PALCHANGES = 0x00010000;
// AVIOLDINDEXENTRY flags
public const int AVIIF_LIST = 0x00000001;
public const int AVIIF_KEYFRAME = 0x00000010;
public const int AVIIF_NO_TIME = 0x00000100;
public const int AVIIF_COMPRESSOR = 0x0FFF0000; // unused?
// TIMECODEDATA flags
public const int TIMECODE_SMPTE_BINARY_GROUP = 0x07;
public const int TIMECODE_SMPTE_COLOR_FRAME = 0x08;
// AVI stream FourCC codes
public static readonly int streamtypeVIDEO = RiffParser.ToFourCC("vids");
public static readonly int streamtypeAUDIO = RiffParser.ToFourCC("auds");
public static readonly int streamtypeMIDI = RiffParser.ToFourCC("mids");
public static readonly int streamtypeTEXT = RiffParser.ToFourCC("txts");
// AVI section FourCC codes
public static readonly int ckidAVIHeaderList = RiffParser.ToFourCC("hdrl");
public static readonly int ckidMainAVIHeader = RiffParser.ToFourCC("avih");
public static readonly int ckidODML = RiffParser.ToFourCC("odml");
public static readonly int ckidAVIExtHeader = RiffParser.ToFourCC("dmlh");
public static readonly int ckidAVIStreamList = RiffParser.ToFourCC("strl");
public static readonly int ckidAVIStreamHeader = RiffParser.ToFourCC("strh");
public static readonly int ckidStreamFormat = RiffParser.ToFourCC("strf");
public static readonly int ckidAVIOldIndex = RiffParser.ToFourCC("idx1");
public static readonly int ckidINFOList = RiffParser.ToFourCC("INFO");
public static readonly int ckidAVIISFT = RiffParser.ToFourCC("ISFT");
public static readonly int ckidMP3 = 0x0055;
public static readonly int ckidWaveFMT = RiffParser.ToFourCC("fmt ");
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,471 @@
using System;
using System.Collections;
using System.Text;
/// <copyright>
/// Giora Tamir (giora@gtamir.com), 2005
/// </copyright>
namespace Nikse.SubtitleEdit.Logic
{
/// <summary>
/// Summary description for RiffDecodeHeader.
/// </summary>
public class RiffDecodeHeader
{
#region private members
private RiffParser m_parser;
private double m_frameRate;
private int m_maxBitRate;
private int m_totalFrames;
private int m_numStreams;
private int m_width;
private int m_height;
private string m_isft;
private double m_vidDataRate;
private string m_vidHandler;
private double m_audDataRate;
private string m_audHandler;
private int m_numChannels;
private int m_samplesPerSec;
private int m_bitsPerSec;
private int m_bitsPerSample;
#endregion
#region public members
/// <summary>
/// Access the internal parser object
/// </summary>
public RiffParser Parser
{
get
{
return m_parser;
}
}
public double FrameRate
{
get
{
double rate = 0.0;
if (m_frameRate > 0.0)
rate = 1000000.0 / m_frameRate;
return rate;
}
}
public string MaxBitRate
{
get
{
return String.Format("{0:N} Kb/Sec", m_maxBitRate / 128);
}
}
public int TotalFrames
{
get
{
return m_totalFrames;
}
}
public double TotalMilliseconds
{
get
{
double totalTime = 0.0;
if (m_frameRate > 0.0)
{
totalTime = m_totalFrames * m_frameRate / 1000.0;
}
return totalTime;
}
}
public string NumStreams
{
get
{
return String.Format("Streams in file: {0:G}", m_numStreams);
}
}
public string FrameSize
{
get
{
return String.Format("{0:G} x {1:G} pixels per frame", m_width, m_height);
}
}
public int Width
{
get
{
return m_width;
}
}
public int Height
{
get
{
return m_height;
}
}
public string VideoDataRate
{
get
{
return String.Format("Video rate {0:N2} frames/Sec", m_vidDataRate);
}
}
public string AudioDataRate
{
get
{
return String.Format("Audio rate {0:N2} Kb/Sec", m_audDataRate / 1000.0);
}
}
public string VideoHandler
{
get
{
return m_vidHandler;
}
}
public string AudioHandler
{
get
{
return String.Format("Audio handler 4CC code: {0}", m_audHandler);
}
}
public string ISFT
{
get
{
return m_isft;
}
}
public string NumChannels
{
get
{
return String.Format("Audio channels: {0}", m_numChannels);
}
}
public string SamplesPerSec
{
get
{
return String.Format("Audio rate: {0:N0} Samples/Sec", m_samplesPerSec);
}
}
public string BitsPerSec
{
get
{
return String.Format("Audio rate: {0:N0} Bytes/Sec", m_bitsPerSec );
}
}
public string BitsPerSample
{
get
{
return String.Format("Audio data: {0:N0} bits/Sample", m_bitsPerSample);
}
}
#endregion
#region Constructor
public RiffDecodeHeader(RiffParser rp)
{
m_parser = rp;
}
private void Clear()
{
m_frameRate = 0;
m_height = 0;
m_maxBitRate = 0;
m_numStreams = 0;
m_totalFrames = 0;
m_width = 0;
m_isft = String.Empty;
m_vidDataRate = 0;
m_audDataRate = 0;
m_vidHandler = String.Empty;
m_audHandler = String.Empty;
m_numChannels = 0;
m_samplesPerSec = 0;
m_bitsPerSample = 0;
m_bitsPerSec = 0;
}
#endregion
#region Default element processing
/// <summary>
/// Defaulkt chunk handler - skip chunk data
/// </summary>
/// <param name="rp"></param>
/// <param name="FourCC"></param>
/// <param name="unpaddedLength"></param>
/// <param name="paddedLength"></param>
private static void ProcessChunk(RiffParser rp, int FourCC, int unpaddedLength, int paddedLength)
{
rp.SkipData(paddedLength);
}
/// <summary>
/// Default list element handler - skip the entire list
/// </summary>
/// <param name="rp"></param>
/// <param name="FourCC"></param>
/// <param name="length"></param>
private void ProcessList(RiffParser rp, int FourCC, int length)
{
rp.SkipData(length);
}
#endregion
#region Decode AVI
/// <summary>
/// Handle chunk elements found in the AVI file. Ignores unknown chunks and
/// </summary>
/// <param name="rp"></param>
/// <param name="FourCC"></param>
/// <param name="unpaddedLength"></param>
/// <param name="paddedLength"></param>
private void ProcessAVIChunk(RiffParser rp, int FourCC, int unpaddedLength, int paddedLength)
{
if (AviRiffData.ckidMainAVIHeader == FourCC)
{
// Main AVI header
DecodeAVIHeader(rp, unpaddedLength, paddedLength);
}
else if (AviRiffData.ckidAVIStreamHeader == FourCC)
{
// Stream header
DecodeAVIStream(rp, unpaddedLength, paddedLength);
}
else if (AviRiffData.ckidAVIISFT == FourCC)
{
Byte[] ba = new byte[paddedLength];
rp.ReadData(ba, 0, paddedLength);
StringBuilder sb = new StringBuilder(unpaddedLength);
for (int i = 0; i < unpaddedLength; ++i)
{
if (0 != ba[i]) sb.Append((char)ba[i]);
}
m_isft = sb.ToString();
}
else
{
// Unknon chunk - skip
rp.SkipData(paddedLength);
}
}
/// <summary>
/// Handle List elements found in the AVI file. Ignores unknown lists and recursively looks
/// at the content of known lists.
/// </summary>
/// <param name="rp"></param>
/// <param name="FourCC"></param>
/// <param name="length"></param>
private void ProcessAVIList(RiffParser rp, int FourCC, int length)
{
RiffParser.ProcessChunkElement pac = new RiffParser.ProcessChunkElement(ProcessAVIChunk);
RiffParser.ProcessListElement pal = new RiffParser.ProcessListElement(ProcessAVIList);
// Is this the header?
if ((AviRiffData.ckidAVIHeaderList == FourCC)
|| (AviRiffData.ckidAVIStreamList == FourCC)
|| (AviRiffData.ckidINFOList == FourCC))
{
while (length > 0)
{
if (false == rp.ReadElement(ref length, pac, pal)) break;
}
}
else
{
// Unknown lists - ignore
rp.SkipData(length);
}
}
public void ProcessMainAVI()
{
Clear();
int length = Parser.DataSize;
RiffParser.ProcessChunkElement pdc = new RiffParser.ProcessChunkElement(ProcessAVIChunk);
RiffParser.ProcessListElement pal = new RiffParser.ProcessListElement(ProcessAVIList);
while (length > 0)
{
if (false == Parser.ReadElement(ref length, pdc, pal)) break;
}
}
private unsafe void DecodeAVIHeader(RiffParser rp, int unpaddedLength, int length)
{
//if (length < sizeof(AVIMAINHEADER))
//{
// throw new RiffParserException(String.Format("Header size mismatch. Needed {0} but only have {1}",
// sizeof(AVIMAINHEADER), length));
//}
byte[] ba = new byte[length];
if (rp.ReadData(ba, 0, length) != length)
{
throw new RiffParserException("Problem reading AVI header.");
}
fixed (Byte* bp = &ba[0])
{
AVIMAINHEADER* avi = (AVIMAINHEADER*)bp;
m_frameRate = avi->dwMicroSecPerFrame;
m_height = avi->dwHeight;
m_maxBitRate = avi->dwMaxBytesPerSec;
m_numStreams = avi->dwStreams;
m_totalFrames = avi->dwTotalFrames;
m_width = avi->dwWidth;
}
}
private unsafe void DecodeAVIStream(RiffParser rp, int unpaddedLength, int length)
{
byte[] ba = new byte[length];
if (rp.ReadData(ba, 0, length) != length)
{
throw new RiffParserException("Problem reading AVI header.");
}
fixed (Byte* bp = &ba[0])
{
AVISTREAMHEADER* avi = (AVISTREAMHEADER*)bp;
if (AviRiffData.streamtypeVIDEO == avi->fccType)
{
m_vidHandler = RiffParser.FromFourCC(avi->fccHandler);
if (avi->dwScale > 0)
{
m_vidDataRate = (double)avi->dwRate / (double)avi->dwScale;
}
else
{
m_vidDataRate = 0.0;
}
}
else if (AviRiffData.streamtypeAUDIO == avi->fccType) {
if (AviRiffData.ckidMP3 == avi->fccHandler)
{
m_audHandler = "MP3";
}
else
{
m_audHandler = RiffParser.FromFourCC(avi->fccHandler);
}
if (avi->dwScale > 0)
{
m_audDataRate = 8.0 * (double)avi->dwRate / (double)avi->dwScale;
if (avi->dwSampleSize > 0)
{
m_audDataRate /= (double)avi->dwSampleSize;
}
}
else
{
m_audDataRate = 0.0;
}
}
}
}
#endregion
#region WAVE processing
private void ProcessWaveChunk(RiffParser rp, int FourCC, int unpaddedLength, int length)
{
// Is this a 'fmt' chunk?
if (AviRiffData.ckidWaveFMT == FourCC)
{
DecodeWave(rp, length);
}
else
{
rp.SkipData(length);
}
}
private unsafe void DecodeWave(RiffParser rp, int length)
{
byte[] ba = new byte[length];
rp.ReadData(ba, 0, length);
fixed(byte* bp = &ba[0])
{
WAVEFORMATEX* wave = (WAVEFORMATEX*)bp;
m_numChannels = wave->nChannels;
m_bitsPerSec = wave->nAvgBytesPerSec;
m_bitsPerSample = wave->wBitsPerSample;
m_samplesPerSec = wave->nSamplesPerSec;
}
}
public void ProcessMainWAVE()
{
Clear();
int length = Parser.DataSize;
RiffParser.ProcessChunkElement pdc = new RiffParser.ProcessChunkElement(ProcessWaveChunk);
RiffParser.ProcessListElement pal = new RiffParser.ProcessListElement(ProcessList);
while (length > 0)
{
if (false == Parser.ReadElement(ref length, pdc, pal)) break;
}
}
#endregion
}
}

View File

@ -0,0 +1,476 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
/// <copyright>
/// Giora Tamir (giora@gtamir.com), 2005
/// </copyright>
namespace Nikse.SubtitleEdit.Logic
{
#region RiffParserException
public class RiffParserException : ApplicationException
{
public RiffParserException()
: base()
{
}
public RiffParserException(string message)
: base(message)
{
}
public RiffParserException(string message, Exception inner)
: base(message, inner)
{
}
public RiffParserException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
#endregion
/// <summary>
/// Summary description for RiffParser
/// </summary>
public class RiffParser
{
#region CONSTANTS
public const int DWORDSIZE = 4;
public const int TWODWORDSSIZE = 8;
public static readonly string RIFF4CC = "RIFF";
public static readonly string RIFX4CC = "RIFX";
public static readonly string LIST4CC = "LIST";
// Known file types
public static readonly int ckidAVI = ToFourCC("AVI ");
public static readonly int ckidWAV = ToFourCC("WAVE");
public static readonly int ckidRMID = ToFourCC("RMID");
#endregion
#region private members
private string m_filename;
private string m_shortname;
private long m_filesize;
private int m_datasize;
private FileStream m_stream;
private int m_fileriff;
private int m_filetype;
// For non-thread-safe memory optimization
private byte[] m_eightBytes = new byte[TWODWORDSSIZE];
private byte[] m_fourBytes = new byte[DWORDSIZE];
#endregion
#region Delegates
/// <summary>
/// Method to be called when a list element is found
/// </summary>
/// <param name="FourCCType"></param>
/// <param name="length"></param>
public delegate void ProcessListElement(RiffParser rp, int FourCCType, int length);
/// <summary>
/// Method to be called when a chunk element is found
/// </summary>
/// <param name="FourCCType"></param>
/// <param name="unpaddedLength"></param>
/// <param name="paddedLength"></param>
public delegate void ProcessChunkElement(RiffParser rp, int FourCCType, int unpaddedLength, int paddedLength);
#endregion
#region public Members
/// <summary>
/// RIFF data segment size
/// </summary>
public int DataSize
{
get
{
return m_datasize;
}
}
/// <summary>
/// Current file name
/// </summary>
public string FileName
{
get
{
return m_filename;
}
}
/// <summary>
/// Current short (name only) file name
/// </summary>
public string ShortName
{
get
{
return m_shortname;
}
}
/// <summary>
/// Return the general file type (RIFF or RIFX);
/// </summary>
public int FileRIFF
{
get
{
return m_fileriff;
}
}
/// <summary>
/// Return the specific file type (AVI/WAV...)
/// </summary>
public int FileType
{
get
{
return m_filetype;
}
}
#endregion
public RiffParser()
{
}
/// <summary>
/// Determine if the file is a valid RIFF file
/// </summary>
/// <param name="filename">File to examine</param>
/// <returns>True if file is a RIFF file</returns>
public unsafe void OpenFile(string filename)
{
// Sanity check
if (null != m_stream) {
throw new RiffParserException("RIFF file already open " + FileName);
}
bool errorOccured = false;
// Opening a new file
try
{
FileInfo fi = new FileInfo(filename);
m_filename = fi.FullName;
m_shortname = fi.Name;
m_filesize = fi.Length;
fi = null;
//Console.WriteLine(ShortName + " is a valid file.");
// Read the RIFF header
m_stream = new FileStream(m_filename, FileMode.Open, FileAccess.Read, FileShare.Read);
int FourCC;
int datasize;
int fileType;
ReadTwoInts(out FourCC, out datasize);
ReadOneInt(out fileType);
m_fileriff = FourCC;
m_filetype = fileType;
// Check for a valid RIFF header
string riff = FromFourCC(FourCC);
if ((0 == String.Compare(riff, RIFF4CC))
|| (0 == String.Compare(riff, RIFX4CC)))
{
// Good header. Check size
//Console.WriteLine(ShortName + " has a valid type \"" + riff + "\"");
//Console.WriteLine(ShortName + " has a specific type of \"" + FromFourCC(fileType) + "\"");
m_datasize = datasize;
if (m_filesize >= m_datasize + TWODWORDSSIZE)
{
//Console.WriteLine(ShortName + " has a valid size");
}
else
{
m_stream.Close(); m_stream = null;
throw new RiffParserException("Error. Truncated file " + FileName);
}
}
else
{
m_stream.Close();
m_stream.Dispose();
m_stream = null;
throw new RiffParserException("Error. Not a valid RIFF file " + FileName);
}
}
catch (RiffParserException ex)
{
errorOccured = true;
throw ex;
}
catch (Exception exception)
{
errorOccured = true;
throw new RiffParserException("Error. Problem reading file " + FileName, exception);
}
finally
{
if (errorOccured && (null != m_stream))
{
m_stream.Close();
m_stream.Dispose();
m_stream = null;
}
}
}
/// <summary>
/// Read the next RIFF element invoking the correct delegate.
/// Returns true if an element can be read
/// </summary>
/// <param name="bytesleft">Reference to number of bytes left in the current list</param>
/// <param name="chunk">Method to invoke if a chunk is found</param>
/// <param name="list">Method to invoke if a list is found</param>
/// <returns></returns>
public bool ReadElement(ref int bytesleft, ProcessChunkElement chunk, ProcessListElement list)
{
// Are we done?
if (TWODWORDSSIZE > bytesleft)
{
return false;
}
//Console.WriteLine(m_stream.Position.ToString() + ", " + bytesleft.ToString());
// We have enough bytes, read
int FourCC;
int size;
ReadTwoInts(out FourCC, out size);
// Reduce bytes left
bytesleft -= TWODWORDSSIZE;
// Do we have enough bytes?
if (bytesleft < size)
{
// Skip the bad data and throw an exception
SkipData(bytesleft);
bytesleft = 0;
throw new RiffParserException("Element size mismatch for element " + FromFourCC(FourCC)
+ " need " + size.ToString() + " but have only " + bytesleft.ToString());
}
// Examine the element, is it a list or a chunk
string type = FromFourCC(FourCC);
if (0 == String.Compare(type, LIST4CC))
{
// We have a list
ReadOneInt(out FourCC);
if (null == list)
{
SkipData(size - 4);
}
else
{
// Invoke the list method
list(this, FourCC, size - 4);
}
// Adjust size
bytesleft -= size;
}
else
{
// Calculated padded size - padded to WORD boundary
int paddedSize = size;
if (0 != (size & 1)) ++paddedSize;
if (null == chunk)
{
SkipData(paddedSize);
}
else
{
chunk(this, FourCC, size, paddedSize);
}
// Adjust size
bytesleft -= paddedSize;
}
return true;
}
#region Stream access
/// <summary>
/// Non-thread-safe method to read two ints from the stream
/// </summary>
/// <param name="FourCC">Output FourCC int</param>
/// <param name="size">Output chunk/list size</param>
public unsafe void ReadTwoInts(out int FourCC, out int size)
{
try {
int readsize = m_stream.Read(m_eightBytes, 0, TWODWORDSSIZE);
if (TWODWORDSSIZE != readsize) {
throw new RiffParserException("Unable to read. Corrupt RIFF file " + FileName);
}
fixed (byte* bp = &m_eightBytes[0]) {
FourCC = *((int*)bp);
size = *((int*)(bp + DWORDSIZE));
}
}
catch (Exception ex)
{
throw new RiffParserException("Problem accessing RIFF file " + FileName, ex);
}
}
/// <summary>
/// Non-thread-safe read a single int from the stream
/// </summary>
/// <param name="FourCC">Output int</param>
public unsafe void ReadOneInt(out int FourCC)
{
try {
int readsize = m_stream.Read(m_fourBytes, 0, DWORDSIZE);
if (DWORDSIZE != readsize) {
throw new RiffParserException("Unable to read. Corrupt RIFF file " + FileName);
}
fixed (byte* bp = &m_fourBytes[0]) {
FourCC = *((int*)bp);
}
}
catch (Exception ex)
{
throw new RiffParserException("Problem accessing RIFF file " + FileName, ex);
}
}
/// <summary>
/// Skip the specified number of bytes
/// </summary>
/// <param name="skipBytes">Number of bytes to skip</param>
public void SkipData(int skipBytes)
{
try {
m_stream.Seek(skipBytes, SeekOrigin.Current);
}
catch (Exception ex)
{
throw new RiffParserException("Problem seeking in file " + FileName, ex);
}
}
/// <summary>
/// Read the specified length into the byte array at the specified
/// offset in the array
/// </summary>
/// <param name="data">Array of bytes to read into</param>
/// <param name="offset">Offset in the array to start from</param>
/// <param name="length">Number of bytes to read</param>
/// <returns>Number of bytes actually read</returns>
public int ReadData(Byte[] data, int offset, int length)
{
try {
return m_stream.Read(data, offset, length);
}
catch (Exception ex)
{
throw new RiffParserException("Problem reading data in file " + FileName, ex);
}
}
/// <summary>
/// Close the RIFF file
/// </summary>
public void CloseFile()
{
if (null != m_stream)
{
m_stream.Close();
m_stream = null;
}
}
#endregion
#region FourCC conversion methods
public static string FromFourCC(int FourCC)
{
char[] chars = new char[4];
chars[0] = (char)(FourCC & 0xFF);
chars[1] = (char)((FourCC >> 8) & 0xFF);
chars[2] = (char)((FourCC >> 16) & 0xFF);
chars[3] = (char)((FourCC >> 24) & 0xFF);
return new string(chars);
}
public static int ToFourCC(string FourCC)
{
if (FourCC.Length != 4)
{
throw new Exception("FourCC strings must be 4 characters long " + FourCC);
}
int result = ((int)FourCC[3]) << 24
| ((int)FourCC[2]) << 16
| ((int)FourCC[1]) << 8
| ((int)FourCC[0]);
return result;
}
public static int ToFourCC(char[] FourCC)
{
if (FourCC.Length != 4)
{
throw new Exception("FourCC char arrays must be 4 characters long " + new string(FourCC));
}
int result = ((int)FourCC[3]) << 24
| ((int)FourCC[2]) << 16
| ((int)FourCC[1]) << 8
| ((int)FourCC[0]);
return result;
}
public static int ToFourCC(char c0, char c1, char c2, char c3)
{
int result = ((int)c3) << 24
| ((int)c2) << 16
| ((int)c1) << 8
| ((int)c0);
return result;
}
#endregion
}
}

View File

@ -0,0 +1,16 @@

namespace Nikse.SubtitleEdit.Logic
{
public class VideoInfo
{
public int Width { get; set; }
public int Height { get; set; }
public double TotalMilliseconds { get; set; }
public double TotalSeconds { get; set; }
public double FramesPerSecond { get; set; }
public double TotalFrames { get; set; }
public string VideoCodec { get; set; }
public string FileType { get; set; }
public bool Success { get; set; }
}
}

View File

@ -0,0 +1,469 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Threading;
using System.IO;
namespace Nikse.SubtitleEdit.Logic.VideoPlayers
{
public class LibVlc11xDynamic : VideoPlayer
{
private System.Windows.Forms.Timer _videoLoadedTimer;
private System.Windows.Forms.Timer _videoEndTimer;
private IntPtr _libVlcDLL;
private IntPtr _libVlc;
private IntPtr _mediaPlayer;
// Win32 API functions for loading dlls dynamic
[DllImport("kernel32.dll")]
public static extern IntPtr LoadLibrary(string dllToLoad);
[DllImport("kernel32.dll")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
[DllImport("kernel32.dll")]
public static extern bool FreeLibrary(IntPtr hModule);
// LibVLC Core - http://www.videolan.org/developers/vlc/doc/doxygen/html/group__libvlc__core.html
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate IntPtr libvlc_new(int argc, [MarshalAs(UnmanagedType.LPArray)] string[] argv);
libvlc_new _libvlc_new;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate IntPtr libvlc_get_version();
libvlc_get_version _libvlc_get_version;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void libvlc_release(IntPtr libVlc);
libvlc_release _libvlc_release;
// LibVLC Media - http://www.videolan.org/developers/vlc/doc/doxygen/html/group__libvlc__media.html
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate IntPtr libvlc_media_new_path(IntPtr instance, byte[] input);
libvlc_media_new_path _libvlc_media_new_path;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate IntPtr libvlc_media_player_new_from_media(IntPtr media);
libvlc_media_player_new_from_media _libvlc_media_player_new_from_media;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void libvlc_media_release(IntPtr media);
libvlc_media_release _libvlc_media_release;
// LibVLC Video Controls - http://www.videolan.org/developers/vlc/doc/doxygen/html/group__libvlc__video.html#g8f55326b8b51aecb59d8b8a446c3f118
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void libvlc_video_get_size(IntPtr mediaPlayer, UInt32 number, out UInt32 x, out UInt32 y);
libvlc_video_get_size _libvlc_video_get_size;
//[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
//private delegate int libvlc_video_set_spu(IntPtr mediaPlayer, UInt32 index);
//libvlc_video_set_spu _libvlc_video_set_spu;
// LibVLC Audio Controls - http://www.videolan.org/developers/vlc/doc/doxygen/html/group__libvlc__audio.html
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int libvlc_audio_get_volume(IntPtr mediaPlayer);
libvlc_audio_get_volume _libvlc_audio_get_volume;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void libvlc_audio_set_volume(IntPtr mediaPlayer, int volume);
libvlc_audio_set_volume _libvlc_audio_set_volume;
// LibVLC Media Player - http://www.videolan.org/developers/vlc/doc/doxygen/html/group__libvlc__media__player.html
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void libvlc_media_player_play(IntPtr mediaPlayer);
libvlc_media_player_play _libvlc_media_player_play;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void libvlc_media_player_stop(IntPtr mediaPlayer);
libvlc_media_player_stop _libvlc_media_player_stop;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void libvlc_media_player_pause(IntPtr mediaPlayer);
libvlc_media_player_pause _libvlc_media_player_pause;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void libvlc_media_player_set_hwnd(IntPtr mediaPlayer, IntPtr windowsHandle);
libvlc_media_player_set_hwnd _libvlc_media_player_set_hwnd;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int libvlc_media_player_is_playing(IntPtr mediaPlayer);
libvlc_media_player_is_playing _libvlc_media_player_is_playing;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate Int64 libvlc_media_player_get_time(IntPtr mediaPlayer);
libvlc_media_player_get_time _libvlc_media_player_get_time;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void libvlc_media_player_set_time(IntPtr mediaPlayer, Int64 position);
libvlc_media_player_set_time _libvlc_media_player_set_time;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate float libvlc_media_player_get_fps(IntPtr mediaPlayer);
libvlc_media_player_get_fps _libvlc_media_player_get_fps;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate byte libvlc_media_player_get_state(IntPtr mediaPlayer);
libvlc_media_player_get_state _libvlc_media_player_get_state;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate Int64 libvlc_media_player_get_length(IntPtr mediaPlayer);
libvlc_media_player_get_length _libvlc_media_player_get_length;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void libvlc_media_list_player_release(IntPtr mediaPlayer);
libvlc_media_list_player_release _libvlc_media_list_player_release;
private object GetDllType(Type type, string name)
{
IntPtr address = GetProcAddress(_libVlcDLL, name);
if (address != IntPtr.Zero)
{
return Marshal.GetDelegateForFunctionPointer(address, type);
}
return null;
}
private void LoadLibVlcDynamic()
{
_libvlc_new = (libvlc_new)GetDllType(typeof(libvlc_new), "libvlc_new");
_libvlc_get_version = (libvlc_get_version)GetDllType(typeof(libvlc_get_version), "libvlc_get_version");
_libvlc_release = (libvlc_release)GetDllType(typeof(libvlc_release), "libvlc_release");
_libvlc_media_new_path = (libvlc_media_new_path)GetDllType(typeof(libvlc_media_new_path), "libvlc_media_new_path");
_libvlc_media_player_new_from_media = (libvlc_media_player_new_from_media)GetDllType(typeof(libvlc_media_player_new_from_media), "libvlc_media_player_new_from_media");
_libvlc_media_release = (libvlc_media_release)GetDllType(typeof(libvlc_media_release), "libvlc_media_release");
_libvlc_video_get_size = (libvlc_video_get_size)GetDllType(typeof(libvlc_video_get_size), "libvlc_video_get_size");
// _libvlc_video_set_spu = (libvlc_video_set_spu)GetDllType(typeof(libvlc_video_set_spu), "libvlc_video_set_spu");
_libvlc_audio_get_volume = (libvlc_audio_get_volume)GetDllType(typeof(libvlc_audio_get_volume), "libvlc_audio_get_volume");
_libvlc_audio_set_volume = (libvlc_audio_set_volume)GetDllType(typeof(libvlc_audio_set_volume), "libvlc_audio_set_volume");
_libvlc_media_player_play = (libvlc_media_player_play)GetDllType(typeof(libvlc_media_player_play), "libvlc_media_player_play");
_libvlc_media_player_stop = (libvlc_media_player_stop)GetDllType(typeof(libvlc_media_player_stop), "libvlc_media_player_stop");
_libvlc_media_player_pause = (libvlc_media_player_pause)GetDllType(typeof(libvlc_media_player_pause), "libvlc_media_player_pause");
_libvlc_media_player_set_hwnd = (libvlc_media_player_set_hwnd)GetDllType(typeof(libvlc_media_player_set_hwnd), "libvlc_media_player_set_hwnd");
_libvlc_media_player_is_playing = (libvlc_media_player_is_playing)GetDllType(typeof(libvlc_media_player_is_playing), "libvlc_media_player_is_playing");
_libvlc_media_player_get_time = (libvlc_media_player_get_time)GetDllType(typeof(libvlc_media_player_get_time), "libvlc_media_player_get_time");
_libvlc_media_player_set_time = (libvlc_media_player_set_time)GetDllType(typeof(libvlc_media_player_set_time), "libvlc_media_player_set_time");
_libvlc_media_player_get_fps = (libvlc_media_player_get_fps)GetDllType(typeof(libvlc_media_player_get_fps), "libvlc_media_player_get_fps");
_libvlc_media_player_get_state = (libvlc_media_player_get_state)GetDllType(typeof(libvlc_media_player_get_state), "libvlc_media_player_get_state");
_libvlc_media_player_get_length = (libvlc_media_player_get_length)GetDllType(typeof(libvlc_media_player_get_length), "libvlc_media_player_get_length");
_libvlc_media_list_player_release = (libvlc_media_list_player_release)GetDllType(typeof(libvlc_media_list_player_release), "libvlc_media_list_player_release");
}
private bool IsAllMethodsLoaded()
{
return _libvlc_new != null &&
_libvlc_get_version != null &&
_libvlc_release != null &&
_libvlc_media_new_path != null &&
_libvlc_media_player_new_from_media != null &&
_libvlc_media_release != null &&
_libvlc_video_get_size != null &&
_libvlc_audio_get_volume != null &&
_libvlc_audio_set_volume != null &&
_libvlc_media_player_play != null &&
_libvlc_media_player_stop != null &&
_libvlc_media_player_pause != null &&
_libvlc_media_player_set_hwnd != null &&
_libvlc_media_player_is_playing != null &&
_libvlc_media_player_get_time != null &&
_libvlc_media_player_set_time != null &&
_libvlc_media_player_get_fps != null &&
_libvlc_media_player_get_state != null &&
_libvlc_media_player_get_length != null &&
_libvlc_media_list_player_release != null;
}
public static bool IsInstalled
{
get
{
LibVlc11xDynamic vlc = new LibVlc11xDynamic();
try
{
vlc.Initialize(null, null, null, null);
return vlc.IsAllMethodsLoaded();
}
finally
{
vlc.DisposeVideoPlayer();
}
}
}
private static byte[] StringToCharPointer(string s)
{
return Encoding.UTF8.GetBytes(s + "\0");
}
public override string PlayerName
{
get { return "VLC Lib Dynamic"; }
}
public override int Volume
{
get
{
return _libvlc_audio_get_volume(_mediaPlayer);
}
set
{
_libvlc_audio_set_volume(_mediaPlayer, value);
}
}
public override double Duration
{
get
{
return _libvlc_media_player_get_length(_mediaPlayer) / 1000.0;
}
}
public override double CurrentPosition
{
get
{
return _libvlc_media_player_get_time(_mediaPlayer) / 1000.0;
}
set
{
_libvlc_media_player_set_time(_mediaPlayer, (long)(value * 1000.0));
}
}
public override void Play()
{
_libvlc_media_player_play(_mediaPlayer);
}
public override void Pause()
{
if (!IsPaused)
_libvlc_media_player_pause(_mediaPlayer);
}
public override void Stop()
{
_libvlc_media_player_stop(_mediaPlayer);
}
public override bool IsPaused
{
get
{
const int Paused = 4;
int state = _libvlc_media_player_get_state(_mediaPlayer);
return state == Paused;
}
}
public override bool IsPlaying
{
get
{
const int Playing = 3;
int state = _libvlc_media_player_get_state(_mediaPlayer);
return state == Playing;
}
}
public LibVlc11xDynamic MakeSecondMediaPlayer(System.Windows.Forms.Control ownerControl, string videoFileName, EventHandler onVideoLoaded, EventHandler onVideoEnded)
{
LibVlc11xDynamic newVlc = new LibVlc11xDynamic();
newVlc._libVlc = this._libVlc;
newVlc._libVlcDLL = this._libVlcDLL;
newVlc.LoadLibVlcDynamic();
newVlc.OnVideoLoaded = onVideoLoaded;
newVlc.OnVideoEnded = onVideoEnded;
if (!string.IsNullOrEmpty(videoFileName))
{
IntPtr media = _libvlc_media_new_path(_libVlc, Encoding.UTF8.GetBytes(videoFileName + "\0"));
newVlc._mediaPlayer = _libvlc_media_player_new_from_media(media);
_libvlc_media_release(media);
// Linux: libvlc_media_player_set_xdrawable (_mediaPlayer, xdrawable);
// Mac: libvlc_media_player_set_nsobject (_mediaPlayer, view);
_libvlc_media_player_set_hwnd(newVlc._mediaPlayer, ownerControl.Handle); // windows
if (onVideoEnded != null)
{
newVlc._videoEndTimer = new System.Windows.Forms.Timer { Interval = 500 };
newVlc._videoEndTimer.Tick += VideoEndTimerTick;
newVlc._videoEndTimer.Start();
}
_libvlc_media_player_play(newVlc._mediaPlayer);
newVlc._videoLoadedTimer = new System.Windows.Forms.Timer { Interval = 500 };
newVlc._videoLoadedTimer.Tick += new EventHandler(newVlc._videoLoadedTimer_Tick);
newVlc._videoLoadedTimer.Start();
}
return newVlc;
}
void _videoLoadedTimer_Tick(object sender, EventArgs e)
{
int i = 0;
while (!IsPlaying && i < 50)
{
System.Threading.Thread.Sleep(100);
i++;
}
_libvlc_media_player_pause(_mediaPlayer);
_videoLoadedTimer.Stop();
if (OnVideoLoaded != null)
OnVideoLoaded.Invoke(_mediaPlayer, new EventArgs());
}
public static string GetVlcPath(string fileName)
{
string path;
path = Path.Combine(Configuration.BaseDirectory, @"VLC\" + fileName);
if (File.Exists(path))
return path;
// XP via registry path
var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\VideoLAN\VLC");
if (key != null)
{
path = (string)key.GetValue("InstallDir");
if (path != null && Directory.Exists(path))
path = Path.Combine(path, fileName);
if (File.Exists(path))
return path;
}
// Winows 7 via registry path
key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Wow6432Node\VideoLAN\VLC");
if (key != null)
{
path = (string)key.GetValue("InstallDir");
if (path != null && Directory.Exists(path))
path = Path.Combine(path, fileName);
if (File.Exists(path))
return path;
}
path = Path.Combine(@"C:\Program Files (x86)\VideoLAN\VLC", fileName);
if (File.Exists(path))
return path;
path = Path.Combine(@"C:\Program Files\VideoLAN\VLC", fileName);
if (File.Exists(path))
return path;
path = Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"VideoLAN\VLC\" + fileName);
if (File.Exists(path))
return path;
return null;
}
public override void Initialize(System.Windows.Forms.Control ownerControl, string videoFileName, EventHandler onVideoLoaded, EventHandler onVideoEnded)
{
string dllFile = GetVlcPath("libvlc.dll");
if (File.Exists(dllFile))
{
Directory.SetCurrentDirectory(Path.GetDirectoryName(dllFile));
_libVlcDLL = LoadLibrary(dllFile);
LoadLibVlcDynamic();
}
else
return;
OnVideoLoaded = onVideoLoaded;
OnVideoEnded = onVideoEnded;
if (!string.IsNullOrEmpty(videoFileName))
{
string[] initParameters = new string[] { "--no-sub-autodetect-file" }; //, "--no-video-title-show" }; //TODO: Put in options/config file
_libVlc = _libvlc_new(initParameters.Length, initParameters);
IntPtr media = _libvlc_media_new_path(_libVlc, Encoding.UTF8.GetBytes(videoFileName + "\0"));
_mediaPlayer = _libvlc_media_player_new_from_media(media);
_libvlc_media_release(media);
// Linux: libvlc_media_player_set_xdrawable (_mediaPlayer, xdrawable);
// Mac: libvlc_media_player_set_nsobject (_mediaPlayer, view);
_libvlc_media_player_set_hwnd(_mediaPlayer, ownerControl.Handle); // windows
if (onVideoEnded != null)
{
_videoEndTimer = new System.Windows.Forms.Timer { Interval = 500 };
_videoEndTimer.Tick += VideoEndTimerTick;
_videoEndTimer.Start();
}
_libvlc_media_player_play(_mediaPlayer);
_videoLoadedTimer = new System.Windows.Forms.Timer { Interval = 500 };
_videoLoadedTimer.Tick += new EventHandler(_videoLoadedTimer_Tick);
_videoLoadedTimer.Start();
}
}
void VideoEndTimerTick(object sender, EventArgs e)
{
const int Ended = 6;
int state = _libvlc_media_player_get_state(_mediaPlayer);
if (state == Ended)
OnVideoLoaded.Invoke(_mediaPlayer, new EventArgs());
}
public override void DisposeVideoPlayer()
{
if (_videoLoadedTimer != null)
_videoLoadedTimer.Stop();
if (_videoEndTimer != null)
_videoEndTimer.Stop();
ThreadPool.QueueUserWorkItem(DisposeVLC, this);
}
private void DisposeVLC(object player)
{
try
{
if (_mediaPlayer != IntPtr.Zero)
{
_libvlc_media_player_stop(_mediaPlayer);
_libvlc_media_list_player_release(_mediaPlayer);
}
if (_libvlc_release != null && _libVlc != IntPtr.Zero)
_libvlc_release(_libVlc);
if (_libVlcDLL != IntPtr.Zero)
FreeLibrary(_libVlcDLL);
}
catch
{
}
}
public override event EventHandler OnVideoLoaded;
public override event EventHandler OnVideoEnded;
}
}

View File

@ -0,0 +1,332 @@
using System;
using System.Diagnostics;
using System.IO;
namespace Nikse.SubtitleEdit.Logic.VideoPlayers
{
public class MPlayer : VideoPlayer
{
private Process _mplayer;
private System.Windows.Forms.Timer timer;
private TimeSpan _lengthInSeconds;
private bool _paused;
private bool _loaded = false;
private bool _ended = false;
private bool _waitForChange = false;
public int Width { get; private set; }
public int Height { get; private set; }
public float FramesPerSecond { get; private set; }
public string VideoFormat { get; private set; }
public string VideoCodec { get; private set; }
public override string PlayerName
{
get { return "MPlayer"; }
}
float _volume;
public override int Volume
{
get
{
return (int)_volume;
}
set
{
if (value >= 0 && value <= 100)
{
_volume = value;
SetProperty("volume", value.ToString(), true);
}
}
}
public override double Duration
{
get { return _lengthInSeconds.TotalSeconds; }
}
double _timePosition;
public override double CurrentPosition
{
get
{
return _timePosition;
}
set
{
_mplayer.StandardInput.WriteLine("mute 1");
// _mplayer.StandardInput.WriteLine("pausing_keep_force speed_set 100");
_mplayer.StandardInput.Flush();
_mplayer.StandardInput.WriteLine("osd_show_text Seeking 5");
_mplayer.StandardInput.Flush();
double before = value - 10.0;
if (before < 0)
before = 0;
_mplayer.StandardInput.WriteLine(string.Format("pausing_keep seek {0:0.0} 2", before));
_mplayer.StandardInput.Flush();
GetProperty("tims_pos", true);
for (int i = 0; i < 100; i++)
{
System.Threading.Thread.Sleep(10);
System.Windows.Forms.Application.DoEvents();
}
double difference = value - _timePosition;
int addSteps = (int)(FramesPerSecond * difference);
if (addSteps > 0)
{
for (int i = 0; i < addSteps; i++)
{
_mplayer.StandardInput.WriteLine("frame_step");
}
_mplayer.StandardInput.Flush();
}
for (int i = 0; i < 100; i++)
{
System.Threading.Thread.Sleep(1);
System.Windows.Forms.Application.DoEvents();
}
//_mplayer.StandardInput.WriteLine("pausing_keep speed_set 1.0");
//_mplayer.StandardInput.Flush();
_mplayer.StandardInput.WriteLine("pausing_keep_force mute 0");
_mplayer.StandardInput.WriteLine("pausing_keep_force get_property time_pos");
_mplayer.StandardInput.Flush();
}
}
public override void Play()
{
SetProperty("pause", "1", false);
_paused = false;
}
public override void Pause()
{
_mplayer.StandardInput.WriteLine("pausing set_property pause 1");
_mplayer.StandardInput.Flush();
_paused = true;
}
public override void Stop()
{
_mplayer.StandardInput.WriteLine("pausing set_property time_pos 0");
_mplayer.StandardInput.Flush();
_paused = true;
}
public override bool IsPaused
{
get { return _paused; }
}
public override bool IsPlaying
{
get { return !_paused; }
}
public override void Initialize(System.Windows.Forms.Control ownerControl, string videoFileName, EventHandler onVideoLoaded, EventHandler onVideoEnded)
{
_loaded = false;
string mplayerExeName = GetMPlayerFileName;
if (!string.IsNullOrEmpty(mplayerExeName))
{
_mplayer = new Process();
_mplayer.StartInfo.FileName = mplayerExeName;
//vo options: gl, gl2, directx:noaccel
_mplayer.StartInfo.Arguments = "-slave -idle -quiet -osdlevel 0 -vsync -vo directx:noaccel -wid " + ownerControl.Handle.ToInt32() + " \"" + videoFileName + "\" ";
_mplayer.StartInfo.UseShellExecute = false;
_mplayer.StartInfo.RedirectStandardInput = true;
_mplayer.StartInfo.RedirectStandardOutput = true;
_mplayer.StartInfo.CreateNoWindow = true;
_mplayer.OutputDataReceived += new DataReceivedEventHandler(MPlayerOutputDataReceived);
_mplayer.Start();
_mplayer.StandardInput.NewLine = "\n";
_mplayer.BeginOutputReadLine(); // Async reading of output to prevent deadlock
// static properties
GetProperty("width", true);
GetProperty("height", true);
GetProperty("fps", true);
GetProperty("video_format", true);
GetProperty("video_codec", true);
GetProperty("length", true);
// semi static variable
GetProperty("volume", true);
// start timer to collect variable properties
timer = new System.Windows.Forms.Timer();
timer.Interval = 500;
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
OnVideoLoaded = onVideoLoaded;
OnVideoEnded = onVideoEnded;
}
}
void timer_Tick(object sender, EventArgs e)
{
// variable properties
_mplayer.StandardInput.WriteLine("pausing_keep_force get_property time_pos");
_mplayer.StandardInput.WriteLine("pausing_keep_force get_property pause");
_mplayer.StandardInput.Flush();
if (!_ended && OnVideoEnded != null && _lengthInSeconds.TotalSeconds == Duration)
{
_ended = true;
OnVideoEnded.Invoke(this, null);
}
else if (_lengthInSeconds.TotalSeconds < Duration)
{
_ended = false;
}
if (OnVideoLoaded != null && _loaded == true)
{
_loaded = false;
OnVideoLoaded.Invoke(this, null);
}
}
void MPlayerOutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data == null)
return;
if (e.Data.StartsWith("Playing "))
_loaded = true;
int indexOfEqual = e.Data.IndexOf("=");
if (indexOfEqual > 0 && indexOfEqual + 1 < e.Data.Length && e.Data.StartsWith("ANS_"))
{
string code = e.Data.Substring(0, indexOfEqual);
string value = e.Data.Substring(indexOfEqual + 1);
switch (code)
{
// Examples:
// ANS_time_pos=8.299958, ANS_width=624, ANS_height=352, ANS_fps=23.976025, ANS_video_format=1145656920, ANS_video_format=1145656920, ANS_video_codec=ffodivx,
// ANS_length=1351.600213, ANS_volume=100.000000
case "ANS_time_pos":
_timePosition = Convert.ToDouble(value);
break;
case "ANS_width":
Width = Convert.ToInt32(value);
break;
case "ANS_height":
Height = Convert.ToInt32(value);
break;
case "ANS_fps":
FramesPerSecond = (float)Convert.ToDouble(value);
break;
case "ANS_video_format":
VideoFormat = value;
break;
case "ANS_video_codec":
VideoCodec = value;
break;
case "ANS_length":
_lengthInSeconds = TimeSpan.FromSeconds(Convert.ToDouble(value));
break;
case "ANS_volume":
_volume = (float)Convert.ToDouble(value);
break;
case "ANS_pause":
_paused = value == "yes" || value == "1";
break;
}
_waitForChange = false;
}
}
private static string GetMPlayerFileName
{
get
{
string fileName = @"C:\Program Files (x86)\SMPlayer\mplayer\mplayer.exe";
if (File.Exists(fileName))
return fileName;
fileName = @"C:\Program Files (x86)\mplayer\mplayer.exe";
if (File.Exists(fileName))
return fileName;
fileName = @"C:\Program Files\mplayer\mplayer.exe";
if (File.Exists(fileName))
return fileName;
fileName = @"C:\Program Files\SMPlayer\mplayer\mplayer.exe";
if (File.Exists(fileName))
return fileName;
return null;
}
}
public static bool IsInstalled
{
get
{
return GetMPlayerFileName != null;
}
}
private void GetProperty(string propertyName, bool keepPause)
{
if (keepPause)
_mplayer.StandardInput.WriteLine("pausing_keep get_property " + propertyName);
else
_mplayer.StandardInput.WriteLine("get_property " + propertyName);
_mplayer.StandardInput.Flush();
}
private void SetProperty(string propertyName, string value, bool keepPause)
{
if (keepPause)
_mplayer.StandardInput.WriteLine("pausing_keep set_property " + propertyName + " " + value);
else
_mplayer.StandardInput.WriteLine("set_property " + propertyName + " " + value);
_mplayer.StandardInput.Flush();
UglySleep();
}
private void UglySleep()
{
_waitForChange = true;
int i = 0;
while (i < 100 && _waitForChange)
{
System.Windows.Forms.Application.DoEvents();
System.Threading.Thread.Sleep(2);
i++;
}
_waitForChange = false;
}
public override void DisposeVideoPlayer()
{
if (_mplayer != null)
{
_mplayer.OutputDataReceived -= MPlayerOutputDataReceived;
_mplayer.StandardInput.WriteLine("quit");
_mplayer.StandardInput.Flush();
}
}
public override event EventHandler OnVideoLoaded;
public override event EventHandler OnVideoEnded;
}
}

View File

@ -0,0 +1,163 @@
//using System;
//using System.ComponentModel;
//using System.Drawing;
//using System.Threading;
//using System.Windows.Forms;
//using Microsoft.DirectX.AudioVideoPlayback;
//namespace Nikse.SubtitleEdit.Logic.VideoPlayers
//{
// class ManagedDirectXPlayer : VideoPlayer
// {
// public override event EventHandler OnVideoLoaded;
// public override event EventHandler OnVideoEnded;
// private System.Windows.Forms.Timer _videoEndTimer;
// private BackgroundWorker _videoLoader;
// private Video _managedDirectXVideo;
// private Audio _audio; // each time Video.Audio is used a new instance is returned :( ...so we save it in a property :)
// // For more info see: http://blogs.msdn.com/toub/archive/2004/04/16/114630.aspx
// public override string PlayerName { get { return "Managed DirectX"; } }
// /// <summary>
// /// In DirectX -10000 is silent and 0 is full volume.
// /// Also, -3500 to 0 seems to be all you can hear! Not much use for -3500 to -9999...
// /// </summary>
// public override int Volume
// {
// get
// {
// if (_audio != null)
// {
// return (_audio.Volume / 35) + 100;
// }
// return 0;
// }
// set
// {
// if (_audio != null)
// {
// if (value == 0)
// _audio.Volume = -10000;
// else
// _audio.Volume = (value - 100) * 35;
// }
// }
// }
// public override double Duration
// {
// get { return _managedDirectXVideo.Duration; }
// }
// public override double CurrentPosition
// {
// get
// {
// return _managedDirectXVideo.CurrentPosition;
// }
// set
// {
// _managedDirectXVideo.CurrentPosition = value;
// }
// }
// public override void Play()
// {
// _managedDirectXVideo.Play();
// }
// public override void Pause()
// {
// _managedDirectXVideo.Pause();
// }
// public override void Stop()
// {
// _managedDirectXVideo.Stop();
// }
// public override bool IsPaused
// {
// get { return _managedDirectXVideo.State == StateFlags.Paused || _managedDirectXVideo.State == StateFlags.Stopped; }
// }
// public override bool IsPlaying
// {
// get { return _managedDirectXVideo.State == StateFlags.Running; }
// }
// public override void Initialize(Control ownerControl, string videoFileName, EventHandler onVideoLoaded, EventHandler onVideoEnded)
// {
// OnVideoLoaded = onVideoLoaded;
// OnVideoEnded = onVideoEnded;
// VideoFileName = videoFileName;
// _managedDirectXVideo = new Video(VideoFileName);
// _audio = _managedDirectXVideo.Audio;
// Size s = ownerControl.Size;
// _managedDirectXVideo.Owner = ownerControl;
// ownerControl.Size = s;
// _managedDirectXVideo.Play();
// if (OnVideoLoaded != null)
// {
// _videoLoader = new BackgroundWorker();
// _videoLoader.RunWorkerCompleted += VideoLoaderRunWorkerCompleted;
// _videoLoader.DoWork += VideoLoaderDoWork;
// _videoLoader.RunWorkerAsync();
// }
// _videoEndTimer = new System.Windows.Forms.Timer { Interval = 500 };
// _videoEndTimer.Tick += VideoEndTimerTick;
// _videoEndTimer.Start();
// }
// void VideoLoaderDoWork(object sender, DoWorkEventArgs e)
// {
// int i = 0;
// while (CurrentPosition < 1 && i < 100)
// {
// Application.DoEvents();
// Thread.Sleep(5);
// i++;
// }
// }
// void VideoLoaderRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
// {
// try
// {
// if (OnVideoLoaded != null)
// OnVideoLoaded.Invoke(_managedDirectXVideo, new EventArgs());
// }
// catch
// {
// }
// _videoEndTimer = null;
// }
// void VideoEndTimerTick(object sender, EventArgs e)
// {
// if (_managedDirectXVideo != null && !_managedDirectXVideo.Paused && CurrentPosition >= Duration)
// {
// if (OnVideoEnded != null)
// OnVideoEnded.Invoke(_managedDirectXVideo, new EventArgs());
// }
// }
// public override void DisposeVideoPlayer()
// {
// ThreadPool.QueueUserWorkItem(DisposeManagedDirectXVideo, _managedDirectXVideo);
// }
// private void DisposeManagedDirectXVideo(object player)
// {
// if (_audio != null)
// _audio.Dispose();
// if (player != null)
// ((IDisposable)player).Dispose();
// }
// }
//}

View File

@ -0,0 +1,259 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using QuartzTypeLib;
using System.ComponentModel;
namespace Nikse.SubtitleEdit.Logic.VideoPlayers
{
public class QuartsPlayer : VideoPlayer
{
public override event EventHandler OnVideoLoaded;
public override event EventHandler OnVideoEnded;
private QuartzTypeLib.IVideoWindow _quartzVideo;
private FilgraphManagerClass _quartzFilgraphManager;
private bool _isPaused;
private Control _owner;
private System.Windows.Forms.Timer _videoEndTimer;
private BackgroundWorker _videoLoader;
int _sourceWidth;
int _sourceHeight;
public override string PlayerName { get { return "DirectShow"; } }
/// <summary>
/// In DirectX -10000 is silent and 0 is full volume.
/// Also, -3500 to 0 seems to be all you can hear! Not much use for -3500 to -9999...
/// </summary>
public override int Volume
{
get
{
try
{
return (_quartzFilgraphManager.Volume / 35) + 100;
}
catch
{
return 0;
}
}
set
{
try
{
if (value == 0)
_quartzFilgraphManager.Volume = -10000;
else
_quartzFilgraphManager.Volume = (value - 100) * 35;
}
catch
{
}
}
}
public override double Duration
{
get { return _quartzFilgraphManager.Duration; }
}
public override double CurrentPosition
{
get { return _quartzFilgraphManager.CurrentPosition; }
set
{
if (value >= 0 && value <= Duration)
_quartzFilgraphManager.CurrentPosition = value;
}
}
public override void Play()
{
_quartzFilgraphManager.Run();
_isPaused = false;
}
public override void Pause()
{
_quartzFilgraphManager.Pause();
_isPaused = true;
}
public override void Stop()
{
_quartzFilgraphManager.Stop();
_isPaused = true;
}
public override bool IsPaused
{
get
{
return _isPaused;
}
}
public override bool IsPlaying
{
get
{
return !IsPaused;
}
}
public override void Initialize(Control ownerControl, string videoFileName, EventHandler onVideoLoaded, EventHandler onVideoEnded)
{
const int wsChild = 0x40000000;
OnVideoLoaded = onVideoLoaded;
OnVideoEnded = onVideoEnded;
VideoFileName = videoFileName;
_owner = ownerControl;
_quartzFilgraphManager = new FilgraphManagerClass();
_quartzFilgraphManager.RenderFile(VideoFileName);
_quartzVideo = _quartzFilgraphManager;
_quartzVideo.Owner = (int)ownerControl.Handle;
_quartzVideo.SetWindowPosition(0, 0, ownerControl.Width, ownerControl.Height);
_quartzVideo.WindowStyle = wsChild;
_quartzFilgraphManager.Run();
_quartzFilgraphManager.GetVideoSize(out _sourceWidth, out _sourceHeight);
_owner.Resize += OwnerControlResize;
if (OnVideoLoaded != null)
{
_videoLoader = new BackgroundWorker();
_videoLoader.RunWorkerCompleted += VideoLoaderRunWorkerCompleted;
_videoLoader.DoWork += VideoLoaderDoWork;
_videoLoader.RunWorkerAsync();
}
OwnerControlResize(this, null);
_videoEndTimer = new System.Windows.Forms.Timer { Interval = 500 };
_videoEndTimer.Tick += VideoEndTimerTick;
_videoEndTimer.Start();
_quartzVideo.MessageDrain = (int)ownerControl.Handle;
}
public static VideoInfo GetVideoInfo(string videoFileName)
{
var info = new VideoInfo { Success = false };
try
{
var quartzFilgraphManager = new FilgraphManagerClass();
quartzFilgraphManager.RenderFile(videoFileName);
int width;
int height;
quartzFilgraphManager.GetVideoSize(out width, out height);
info.Width = width;
info.Height = height;
if (quartzFilgraphManager.AvgTimePerFrame > 0)
info.FramesPerSecond = 1 / quartzFilgraphManager.AvgTimePerFrame;
info.Success = true;
info.TotalMilliseconds = quartzFilgraphManager.Duration * 1000;
info.TotalSeconds = quartzFilgraphManager.Duration;
info.TotalFrames = info.TotalSeconds * info.FramesPerSecond;
info.VideoCodec = string.Empty; // TODO... get real codec names from quartzFilgraphManager.FilterCollection;
Marshal.ReleaseComObject(quartzFilgraphManager);
}
catch
{
}
return info;
}
void VideoLoaderDoWork(object sender, DoWorkEventArgs e)
{
int i = 0;
while (CurrentPosition < 1 && i < 100)
{
Application.DoEvents();
Thread.Sleep(5);
i++;
}
}
void VideoLoaderRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (OnVideoLoaded != null)
{
try
{
OnVideoLoaded.Invoke(_quartzFilgraphManager, new EventArgs());
}
catch
{
}
}
_videoEndTimer = null;
}
void VideoEndTimerTick(object sender, EventArgs e)
{
if (_isPaused == false && _quartzFilgraphManager != null && CurrentPosition >= Duration)
{
_isPaused = true;
if (OnVideoEnded != null)
OnVideoEnded.Invoke(_quartzFilgraphManager, new EventArgs());
}
}
void OwnerControlResize(object sender, EventArgs e)
{
if (_quartzVideo == null)
return;
// calc new scaled size with correct aspect ratio
float factorX = _owner.Width / (float)_sourceWidth;
float factorY = _owner.Height / (float)_sourceHeight;
if (factorX > factorY)
{
_quartzVideo.Width = (int)(_sourceWidth * factorY);
_quartzVideo.Height = (int)(_sourceHeight * factorY);
}
else
{
_quartzVideo.Width = (int)(_sourceWidth * factorX);
_quartzVideo.Height = (int)(_sourceHeight * factorX);
}
_quartzVideo.Left = (_owner.Width - _quartzVideo.Width) / 2;
_quartzVideo.Top = (_owner.Height - _quartzVideo.Height) / 2;
}
public override void DisposeVideoPlayer()
{
ThreadPool.QueueUserWorkItem(DisposeQuarts, _quartzFilgraphManager);
}
private void DisposeQuarts(object player)
{
try
{
if (_quartzVideo != null)
_quartzVideo.Owner = -1;
}
catch
{
}
if (_quartzFilgraphManager != null)
{
_quartzFilgraphManager.Stop();
Marshal.ReleaseComObject(_quartzFilgraphManager);
}
_quartzFilgraphManager = null;
_quartzVideo = null;
}
}
}

View File

@ -0,0 +1,23 @@
using System;
using System.Windows.Forms;
namespace Nikse.SubtitleEdit.Logic.VideoPlayers
{
public abstract class VideoPlayer
{
public abstract string PlayerName { get; }
public string VideoFileName { get; protected set; }
public abstract int Volume { get; set; }
public abstract double Duration { get; }
public abstract double CurrentPosition { get; set; }
public abstract void Play();
public abstract void Pause();
public abstract void Stop();
public abstract bool IsPaused { get; }
public abstract bool IsPlaying { get; }
public abstract void Initialize(Control ownerControl, string videoFileName, EventHandler onVideoLoaded, EventHandler onVideoEnded);
public abstract void DisposeVideoPlayer();
public abstract event EventHandler OnVideoLoaded;
public abstract event EventHandler OnVideoEnded;
}
}

View File

@ -0,0 +1,114 @@
using System;
using System.Threading;
using System.Windows.Forms;
using AxWMPLib;
namespace Nikse.SubtitleEdit.Logic.VideoPlayers
{
public class WmpPlayer : VideoPlayer
{
public override event EventHandler OnVideoLoaded;
public override event EventHandler OnVideoEnded;
private AxWindowsMediaPlayer _axWindowsMediaPlayer;
public override string PlayerName { get { return "Windows Media Player"; } }
public override int Volume
{
get { return _axWindowsMediaPlayer.settings.volume; }
set { _axWindowsMediaPlayer.settings.volume = value; }
}
public override double Duration
{
get { return _axWindowsMediaPlayer.currentMedia.duration; }
}
public override double CurrentPosition
{
get { return _axWindowsMediaPlayer.Ctlcontrols.currentPosition; }
set { _axWindowsMediaPlayer.Ctlcontrols.currentPosition = value; }
}
public override void Play()
{
_axWindowsMediaPlayer.Ctlcontrols.play();
}
public override void Pause()
{
_axWindowsMediaPlayer.Ctlcontrols.pause();
}
public override void Stop()
{
_axWindowsMediaPlayer.Ctlcontrols.stop();
}
public override bool IsPaused
{
get { return _axWindowsMediaPlayer.playState == WMPLib.WMPPlayState.wmppsPaused; }
}
public override bool IsPlaying
{
get { return _axWindowsMediaPlayer.playState == WMPLib.WMPPlayState.wmppsPlaying; }
}
public override void Initialize(Control ownerControl, string videoFileName, EventHandler onVideoLoaded, EventHandler onVideoEnded)
{
OnVideoLoaded = onVideoLoaded;
OnVideoEnded = onVideoEnded;
VideoFileName = videoFileName;
_axWindowsMediaPlayer = new AxWindowsMediaPlayer();
ownerControl.Controls.Add(_axWindowsMediaPlayer);
_axWindowsMediaPlayer.Dock = DockStyle.Fill;
_axWindowsMediaPlayer.uiMode = "none";
_axWindowsMediaPlayer.enableContextMenu = false;
_axWindowsMediaPlayer.URL = videoFileName;
_axWindowsMediaPlayer.PlayStateChange += AxWindowsMediaPlayerStatusChange;
_axWindowsMediaPlayer.PlayStateChange += AxWindowsMediaPlayerEnded;
}
private void AxWindowsMediaPlayerStatusChange(object sender, _WMPOCXEvents_PlayStateChangeEvent e)
{
try
{
if (e.newState == (int) WMPLib.WMPPlayState.wmppsPlaying ||
e.newState == (int) WMPLib.WMPPlayState.wmppsMediaEnded)
{
_axWindowsMediaPlayer.PlayStateChange -= AxWindowsMediaPlayerStatusChange;
if (OnVideoLoaded != null)
OnVideoLoaded.Invoke(_axWindowsMediaPlayer, new EventArgs());
}
}
catch
{
}
}
private void AxWindowsMediaPlayerEnded(object sender, _WMPOCXEvents_PlayStateChangeEvent e)
{
try
{
if (e.newState == (int)WMPLib.WMPPlayState.wmppsMediaEnded && OnVideoEnded != null)
OnVideoEnded.Invoke(_axWindowsMediaPlayer, new EventArgs());
}
catch
{
}
}
public override void DisposeVideoPlayer()
{
ThreadPool.QueueUserWorkItem(DisposeAxWindowsMediaPlayer, _axWindowsMediaPlayer);
}
private static void DisposeAxWindowsMediaPlayer(object player)
{
if (player != null)
((IDisposable)player).Dispose();
}
}
}

318
src/Logic/VobSub/Helper.cs Normal file
View File

@ -0,0 +1,318 @@
using System;
using System.Text;
namespace Nikse.SubtitleEdit.Logic.VobSub
{
public static class Helper
{
#region Binary constants
public const int B00000000 = 0;
public const int B00000001 = 1;
public const int B00000010 = 2;
public const int B00000011 = 3;
public const int B00000100 = 4;
public const int B00000101 = 5;
public const int B00000110 = 6;
public const int B00000111 = 7;
public const int B00001000 = 8;
public const int B00001001 = 9;
public const int B00001010 = 10;
public const int B00001011 = 11;
public const int B00001100 = 12;
public const int B00001101 = 13;
public const int B00001110 = 14;
public const int B00001111 = 15;
public const int B00010000 = 16;
public const int B00010001 = 17;
public const int B00010010 = 18;
public const int B00010011 = 19;
public const int B00010100 = 20;
public const int B00010101 = 21;
public const int B00010110 = 22;
public const int B00010111 = 23;
public const int B00011000 = 24;
public const int B00011001 = 25;
public const int B00011010 = 26;
public const int B00011011 = 27;
public const int B00011100 = 28;
public const int B00011101 = 29;
public const int B00011110 = 30;
public const int B00011111 = 31;
public const int B00100000 = 32;
public const int B00100001 = 33;
public const int B00100010 = 34;
public const int B00100011 = 35;
public const int B00100100 = 36;
public const int B00100101 = 37;
public const int B00100110 = 38;
public const int B00100111 = 39;
public const int B00101000 = 40;
public const int B00101001 = 41;
public const int B00101010 = 42;
public const int B00101011 = 43;
public const int B00101100 = 44;
public const int B00101101 = 45;
public const int B00101110 = 46;
public const int B00101111 = 47;
public const int B00110000 = 48;
public const int B00110001 = 49;
public const int B00110010 = 50;
public const int B00110011 = 51;
public const int B00110100 = 52;
public const int B00110101 = 53;
public const int B00110110 = 54;
public const int B00110111 = 55;
public const int B00111000 = 56;
public const int B00111001 = 57;
public const int B00111010 = 58;
public const int B00111011 = 59;
public const int B00111100 = 60;
public const int B00111101 = 61;
public const int B00111110 = 62;
public const int B00111111 = 63;
public const int B01000000 = 64;
public const int B01000001 = 65;
public const int B01000010 = 66;
public const int B01000011 = 67;
public const int B01000100 = 68;
public const int B01000101 = 69;
public const int B01000110 = 70;
public const int B01000111 = 71;
public const int B01001000 = 72;
public const int B01001001 = 73;
public const int B01001010 = 74;
public const int B01001011 = 75;
public const int B01001100 = 76;
public const int B01001101 = 77;
public const int B01001110 = 78;
public const int B01001111 = 79;
public const int B01010000 = 80;
public const int B01010001 = 81;
public const int B01010010 = 82;
public const int B01010011 = 83;
public const int B01010100 = 84;
public const int B01010101 = 85;
public const int B01010110 = 86;
public const int B01010111 = 87;
public const int B01011000 = 88;
public const int B01011001 = 89;
public const int B01011010 = 90;
public const int B01011011 = 91;
public const int B01011100 = 92;
public const int B01011101 = 93;
public const int B01011110 = 94;
public const int B01011111 = 95;
public const int B01100000 = 96;
public const int B01100001 = 97;
public const int B01100010 = 98;
public const int B01100011 = 99;
public const int B01100100 = 100;
public const int B01100101 = 101;
public const int B01100110 = 102;
public const int B01100111 = 103;
public const int B01101000 = 104;
public const int B01101001 = 105;
public const int B01101010 = 106;
public const int B01101011 = 107;
public const int B01101100 = 108;
public const int B01101101 = 109;
public const int B01101110 = 110;
public const int B01101111 = 111;
public const int B01110000 = 112;
public const int B01110001 = 113;
public const int B01110010 = 114;
public const int B01110011 = 115;
public const int B01110100 = 116;
public const int B01110101 = 117;
public const int B01110110 = 118;
public const int B01110111 = 119;
public const int B01111000 = 120;
public const int B01111001 = 121;
public const int B01111010 = 122;
public const int B01111011 = 123;
public const int B01111100 = 124;
public const int B01111101 = 125;
public const int B01111110 = 126;
public const int B01111111 = 127;
public const int B10000000 = 128;
public const int B10000001 = 129;
public const int B10000010 = 130;
public const int B10000011 = 131;
public const int B10000100 = 132;
public const int B10000101 = 133;
public const int B10000110 = 134;
public const int B10000111 = 135;
public const int B10001000 = 136;
public const int B10001001 = 137;
public const int B10001010 = 138;
public const int B10001011 = 139;
public const int B10001100 = 140;
public const int B10001101 = 141;
public const int B10001110 = 142;
public const int B10001111 = 143;
public const int B10010000 = 144;
public const int B10010001 = 145;
public const int B10010010 = 146;
public const int B10010011 = 147;
public const int B10010100 = 148;
public const int B10010101 = 149;
public const int B10010110 = 150;
public const int B10010111 = 151;
public const int B10011000 = 152;
public const int B10011001 = 153;
public const int B10011010 = 154;
public const int B10011011 = 155;
public const int B10011100 = 156;
public const int B10011101 = 157;
public const int B10011110 = 158;
public const int B10011111 = 159;
public const int B10100000 = 160;
public const int B10100001 = 161;
public const int B10100010 = 162;
public const int B10100011 = 163;
public const int B10100100 = 164;
public const int B10100101 = 165;
public const int B10100110 = 166;
public const int B10100111 = 167;
public const int B10101000 = 168;
public const int B10101001 = 169;
public const int B10101010 = 170;
public const int B10101011 = 171;
public const int B10101100 = 172;
public const int B10101101 = 173;
public const int B10101110 = 174;
public const int B10101111 = 175;
public const int B10110000 = 176;
public const int B10110001 = 177;
public const int B10110010 = 178;
public const int B10110011 = 179;
public const int B10110100 = 180;
public const int B10110101 = 181;
public const int B10110110 = 182;
public const int B10110111 = 183;
public const int B10111000 = 184;
public const int B10111001 = 185;
public const int B10111010 = 186;
public const int B10111011 = 187;
public const int B10111100 = 188;
public const int B10111101 = 189;
public const int B10111110 = 190;
public const int B10111111 = 191;
public const int B11000000 = 192;
public const int B11000001 = 193;
public const int B11000010 = 194;
public const int B11000011 = 195;
public const int B11000100 = 196;
public const int B11000101 = 197;
public const int B11000110 = 198;
public const int B11000111 = 199;
public const int B11001000 = 200;
public const int B11001001 = 201;
public const int B11001010 = 202;
public const int B11001011 = 203;
public const int B11001100 = 204;
public const int B11001101 = 205;
public const int B11001110 = 206;
public const int B11001111 = 207;
public const int B11010000 = 208;
public const int B11010001 = 209;
public const int B11010010 = 210;
public const int B11010011 = 211;
public const int B11010100 = 212;
public const int B11010101 = 213;
public const int B11010110 = 214;
public const int B11010111 = 215;
public const int B11011000 = 216;
public const int B11011001 = 217;
public const int B11011010 = 218;
public const int B11011011 = 219;
public const int B11011100 = 220;
public const int B11011101 = 221;
public const int B11011110 = 222;
public const int B11011111 = 223;
public const int B11100000 = 224;
public const int B11100001 = 225;
public const int B11100010 = 226;
public const int B11100011 = 227;
public const int B11100100 = 228;
public const int B11100101 = 229;
public const int B11100110 = 230;
public const int B11100111 = 231;
public const int B11101000 = 232;
public const int B11101001 = 233;
public const int B11101010 = 234;
public const int B11101011 = 235;
public const int B11101100 = 236;
public const int B11101101 = 237;
public const int B11101110 = 238;
public const int B11101111 = 239;
public const int B11110000 = 240;
public const int B11110001 = 241;
public const int B11110010 = 242;
public const int B11110011 = 243;
public const int B11110100 = 244;
public const int B11110101 = 245;
public const int B11110110 = 246;
public const int B11110111 = 247;
public const int B11111000 = 248;
public const int B11111001 = 249;
public const int B11111010 = 250;
public const int B11111011 = 251;
public const int B11111100 = 252;
public const int B11111101 = 253;
public const int B11111110 = 254;
public const int B11111111 = 255;
#endregion
public static string IntToHex(UInt64 value, int digits)
{
return value.ToString("X").PadLeft(digits, '0');
}
public static string IntToHex(int value, int digits)
{
return value.ToString("X").PadLeft(digits, '0');
}
public static string IntToBin(long value, int digits)
{
return Convert.ToString(value, 2).PadLeft(digits, '0');
}
public static UInt32 GetEndian(byte[] buffer, int index, int count)
{
UInt32 result = 0;
for (int i = 0; i < count; i++)
result = (result << 8) + buffer[index + i];
return result;
}
/// <summary>
/// Get two bytes word stored in endian order
/// </summary>
/// <param name="buffer">Byte array</param>
/// <param name="index">Index in byte array</param>
/// <returns>Word as int</returns>
public static int GetEndianWord(byte[] buffer, int index)
{
if (index +1 < buffer.Length)
return (buffer[index] << 8) + buffer[index+1];
return 0;
}
public static string GetBinaryString(byte[] buffer, int index, int count)
{
var sb = new StringBuilder();
for (int i=0; i < count; i++)
sb.Append(Convert.ToString(buffer[index + i], 2).PadLeft(8, '0'));
return sb.ToString();
}
public static UInt64 GetUInt32FromBinaryString(string binaryValue)
{
return Convert.ToUInt32(binaryValue, 2);
}
}
}

100
src/Logic/VobSub/Idx.cs Normal file
View File

@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;
namespace Nikse.SubtitleEdit.Logic.VobSub
{
public class IdxParagraph
{
public TimeSpan StartTime { get; private set; }
public long FilePosition { get; private set; }
public IdxParagraph(TimeSpan startTime, long filePosition)
{
StartTime = startTime;
FilePosition = filePosition;
}
}
class Idx
{
public readonly List<IdxParagraph> IdxParagraphs = new List<IdxParagraph>();
public readonly List<Color> Palette = new List<Color>();
public readonly List<string> Languages = new List<string>();
public Idx(string fileName)
{
var timeCodeLinePattern = new Regex(@"^timestamp: \d+:\d+:\d+:\d+, filepos: [\dabcdefABCDEF]+$", RegexOptions.Compiled);
foreach (string line in File.ReadAllLines(fileName))
{
if (timeCodeLinePattern.IsMatch(line))
{
IdxParagraph p = GetTimeCodeAndFilePosition(line);
if (p != null)
IdxParagraphs.Add(p);
}
else if (line.ToLower().StartsWith("palette:") && line.Length > 10)
{
string s = line.Substring("palette:".Length + 1);
string[] colors = s.Split(", ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
foreach (string hex in colors)
{
if (hex.Length == 6)
Palette.Add(HexToColor(hex));
}
}
else if (line.ToLower().StartsWith("id:") && line.Length > 4)
{
//id: en, index: 1
//id: es, index: 2
string s = line.Substring("id:".Length + 1);
string[] parts = s.Split(":, ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
if (parts.Length > 0)
{
try
{
string twoLetterLanguageId = parts[0];
CultureInfo info = CultureInfo.GetCultureInfoByIetfLanguageTag(twoLetterLanguageId);
Languages.Add(string.Format("{0} (0x{1:x})", info.NativeName, Languages.Count + 32));
}
catch
{
}
}
}
}
}
private static Color HexToColor(string hex)
{
int r = Convert.ToInt32(hex.Substring(0, 2), 16);
int g = Convert.ToInt32(hex.Substring(2, 2), 16);
int b = Convert.ToInt32(hex.Substring(4, 2), 16);
return Color.FromArgb(r, g, b);
}
private static IdxParagraph GetTimeCodeAndFilePosition(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 IdxParagraph(new TimeSpan(0, hours, minutes, seconds, milliseconds),Convert.ToInt64(parts[6].Trim(), 16));
}
}
return null;
}
}
}

View File

@ -0,0 +1,37 @@
using System;
namespace Nikse.SubtitleEdit.Logic.VobSub
{
/// <summary>
/// http://www.mpucoder.com/DVD/packhdr.html
/// </summary>
public class Mpeg2Header
{
public const int Length = 14;
public readonly UInt32 StartCode;
public readonly byte PackIndentifier;
public readonly UInt64 SystemClockReferenceQuotient;
public readonly UInt64 ProgramMuxRate;
public readonly int PackStuffingLength;
public Mpeg2Header(byte[] buffer)
{
StartCode = Helper.GetEndian(buffer, 0, 3);
PackIndentifier = buffer[3];
string b4To9AsBinary = Helper.GetBinaryString(buffer, 4, 6);
b4To9AsBinary = b4To9AsBinary.Substring(2,3) + b4To9AsBinary.Substring(6,15) + b4To9AsBinary.Substring(22,15);
SystemClockReferenceQuotient = Helper.GetUInt32FromBinaryString(b4To9AsBinary);
string b8And9 = Helper.GetBinaryString(buffer, 8, 2);
b8And9 = b8And9.Substring(6, 9);
SystemClockReferenceQuotient = Helper.GetUInt32FromBinaryString(b8And9);
ProgramMuxRate = Helper.GetEndian(buffer, 10, 3) >> 2;
PackStuffingLength = buffer[13] & Helper.B00000111;
}
}
}

View File

@ -0,0 +1,94 @@
using System;
namespace Nikse.SubtitleEdit.Logic.VobSub
{
/// <summary>
/// http://www.mpucoder.com/DVD/pes-hdr.html
/// </summary>
public class PacketizedElementaryStream
{
public const int HeaderLength = 6;
public readonly UInt32 StartCode;
public readonly int StreamId;
public readonly int Length;
public readonly int ScramblingControl;
public readonly int Priority;
public readonly int DataAlignmentIndicator;
public readonly int Copyright;
public readonly int OriginalOrCopy;
public readonly int PresentationTimeStampDecodeTimeStampFlags;
public readonly int ElementaryStreamClockReferenceFlag;
public readonly int EsRateFlag;
public readonly int DsmTrickModeFlag;
public readonly int AdditionalCopyInfoFlag;
public readonly int CrcFlag;
public readonly int ExtensionFlag;
public readonly int HeaderDataLength;
public readonly UInt64? PresentationTimeStamp;
public readonly UInt64? DecodeTimeStamp;
public readonly int? SubPictureStreamId;
public readonly byte[] DataBuffer;
public PacketizedElementaryStream(byte[] buffer, int index)
{
StartCode = Helper.GetEndian(buffer, index + 0, 3);
StreamId = buffer[index + 3];
Length = Helper.GetEndianWord(buffer, index + 4);
ScramblingControl = (buffer[index + 6] >> 4) & Helper.B00000011;
Priority = buffer[index + 6] & Helper.B00001000;
DataAlignmentIndicator = buffer[index + 6] & Helper.B00000100;
Copyright = buffer[index + 6] & Helper.B00000010;
OriginalOrCopy = buffer[index + 6] & Helper.B00000001;
PresentationTimeStampDecodeTimeStampFlags = buffer[index + 7] >> 6;
ElementaryStreamClockReferenceFlag = buffer[index + 7] & Helper.B00100000;
EsRateFlag = buffer[index + 7] & Helper.B00010000;
DsmTrickModeFlag = buffer[index + 7] & Helper.B00001000;
AdditionalCopyInfoFlag = buffer[index + 7] & Helper.B00000100;
CrcFlag = buffer[index + 7] & Helper.B00001000;
ExtensionFlag = buffer[index + 7] & Helper.B00000010;
HeaderDataLength = buffer[index + 8];
if (StreamId == 0xBD)
{
int id = buffer[index + 9 + HeaderDataLength];
if (id >= 0x20 && id <= 0x40) // x3f 0r x40 ?
SubPictureStreamId = id;
}
int tempIndex = index + 9;
if (PresentationTimeStampDecodeTimeStampFlags == Helper.B00000010 ||
PresentationTimeStampDecodeTimeStampFlags == Helper.B00000011)
{
string bString = Helper.GetBinaryString(buffer, tempIndex, 5);
bString = bString.Substring(4, 3) + bString.Substring(8, 15) + bString.Substring(24, 15);
PresentationTimeStamp = Convert.ToUInt32(bString, 2);
tempIndex += 5;
}
if (PresentationTimeStampDecodeTimeStampFlags == Helper.B00000011)
{
string bString = Helper.GetBinaryString(buffer, tempIndex, 5);
bString = bString.Substring(4, 3) + bString.Substring(8, 15) + bString.Substring(24, 15);
DecodeTimeStamp = Convert.ToUInt64(bString, 2);
}
int dataIndex = index + HeaderDataLength + 24 - Mpeg2Header.Length;
int dataSize = Length - (4 + HeaderDataLength);
if (dataSize < 0 || (dataSize + dataIndex > buffer.Length)) // to fix bad subs...
{
dataSize = buffer.Length - dataIndex;
if (dataSize < 0)
return;
}
DataBuffer = new byte[dataSize];
Buffer.BlockCopy(buffer, dataIndex, DataBuffer, 0, dataSize);
}
}
}

View File

@ -0,0 +1,369 @@
using System;
using System.Collections.Generic;
using System.Drawing;
namespace Nikse.SubtitleEdit.Logic.VobSub
{
/// <summary>
/// Subtitle Picture - see http://www.mpucoder.com/DVD/spu.html for more info
/// </summary>
public class SubPicture
{
private enum DisplayControlCommand
{
ForcedStartDisplay = 0,
StartDisplay = 1,
StopDisplay = 2,
SetColor = 3,
SetContrast = 4,
SetDisplayArea = 5,
SetPixelDataAddress = 6,
ChangeColorAndContrast = 7,
End = 0xFF,
}
public readonly int SubPictureDateSize;
public TimeSpan Delay;
public int BufferSize { get { return _data.Length; } }
private readonly byte[] _data;
public Rectangle ImageDisplayArea;
public bool Forced { get; private set; }
public SubPicture(byte[] data)
{
_data = data;
SubPictureDateSize = Helper.GetEndianWord(_data, 0);
ParseDisplayControlCommands(false, null, null);
}
/// <summary>
/// Generates the current subtitle image
/// </summary>
/// <param name="colorLookupTable">The Color LookUp Table (CLUT), if null then only the four colors are used (should contain 16 elements if not null)</param>
/// <param name="background">Background color</param>
/// <param name="pattern">Color</param>
/// <param name="emphasis1">Color</param>
/// <param name="emphasis2">Color</param>
/// <returns>Subtitle image</returns>
public Bitmap GetBitmap(List<Color> colorLookupTable, Color background, Color pattern, Color emphasis1, Color emphasis2)
{
var fourColors = new List<Color> { background, pattern, emphasis1, emphasis2 };
return ParseDisplayControlCommands(true, colorLookupTable, fourColors);
}
private Bitmap ParseDisplayControlCommands(bool createBitmap, List<Color> colorLookUpTable, List<Color> fourColors)
{
ImageDisplayArea = new Rectangle();
Bitmap bmp = null;
var displayControlSequenceTableAddresses = new List<int>();
byte[] imageContrast = null;
int imageTopFieldDataAddress = 0;
int imageBottomFieldDataAddress = 0;
bool bitmapGenerated = false;
double largestDelay = -999999;
int displayControlSequenceTableAddress = Helper.GetEndianWord(_data, 2);
int lastDisplayControlSequenceTableAddress = 0;
displayControlSequenceTableAddresses.Add(displayControlSequenceTableAddress);
int commandIndex = 0;
while (displayControlSequenceTableAddress > lastDisplayControlSequenceTableAddress && displayControlSequenceTableAddress + 5 < _data.Length && commandIndex < _data.Length)
{
int delayBeforeExecute = Helper.GetEndianWord(_data, displayControlSequenceTableAddress);
commandIndex = displayControlSequenceTableAddress + 4;
int command = _data[commandIndex];
int numberOfCommands = 0;
while (command != 0xFF && numberOfCommands < 1000 && commandIndex < _data.Length)
{
numberOfCommands++;
switch (command)
{
case (int)DisplayControlCommand.ForcedStartDisplay: // 0
Forced = true;
commandIndex++;
break;
case (int)DisplayControlCommand.StartDisplay: // 1
commandIndex++;
break;
case (int)DisplayControlCommand.StopDisplay: // 2
Delay = TimeSpan.FromMilliseconds(((delayBeforeExecute << 10) + 1023) / 90.0);
if (createBitmap && Delay.TotalMilliseconds > largestDelay) // in case of more than one images, just use the one with the largest display time
{
largestDelay = Delay.TotalMilliseconds;
bmp = GenerateBitmap(ImageDisplayArea, imageTopFieldDataAddress, imageBottomFieldDataAddress, fourColors);
bitmapGenerated = true;
}
commandIndex++;
break;
case (int)DisplayControlCommand.SetColor: // 3
if (colorLookUpTable != null && fourColors.Count == 4)
{
byte[] imageColor = new[] { _data[commandIndex + 1], _data[commandIndex + 2] };
SetColor(fourColors, 3, imageColor[0] >> 4, colorLookUpTable);
SetColor(fourColors, 2, imageColor[0] & Helper.B00001111, colorLookUpTable);
SetColor(fourColors, 1, imageColor[1] >> 4, colorLookUpTable);
SetColor(fourColors, 0, imageColor[1] & Helper.B00001111, colorLookUpTable);
}
commandIndex += 3;
break;
case (int)DisplayControlCommand.SetContrast: // 4
imageContrast = new[] { _data[commandIndex + 1], _data[commandIndex + 2] };
commandIndex += 3;
break;
case (int)DisplayControlCommand.SetDisplayArea: // 5
if (_data.Length > commandIndex + 6)
{
string binary = Helper.GetBinaryString(_data, commandIndex + 1, 6);
int startingX = (int)Helper.GetUInt32FromBinaryString(binary.Substring(0, 12));
int endingX = (int)Helper.GetUInt32FromBinaryString(binary.Substring(12, 12));
int startingY = (int)Helper.GetUInt32FromBinaryString(binary.Substring(24, 12));
int endingY = (int)Helper.GetUInt32FromBinaryString(binary.Substring(36, 12));
ImageDisplayArea = new Rectangle(startingX, startingY, endingX - startingX, endingY - startingY);
}
commandIndex += 7;
break;
case (int)DisplayControlCommand.SetPixelDataAddress: // 6
imageTopFieldDataAddress = Helper.GetEndianWord(_data, commandIndex + 1);
imageBottomFieldDataAddress = Helper.GetEndianWord(_data, commandIndex + 3);
commandIndex += 5;
break;
case (int)DisplayControlCommand.ChangeColorAndContrast: // 7
commandIndex++;
//int parameterAreaSize = (int)Helper.GetEndian(_data, commandIndex, 2);
int parameterAreaSize = _data[commandIndex + 1]; // this should be enough??? (no larger than 255 bytes)
if (colorLookUpTable != null)
{
//TODO: set fourColors
}
commandIndex += parameterAreaSize;
break;
case (int)DisplayControlCommand.End: // FF (255) - Stop looping of Display Control Commands
break;
}
if (commandIndex >= _data.Length) // in case of bad files...
break;
command = _data[commandIndex];
}
lastDisplayControlSequenceTableAddress = displayControlSequenceTableAddress;
displayControlSequenceTableAddress = Helper.GetEndianWord(_data, displayControlSequenceTableAddress + 2);
}
if (createBitmap && !bitmapGenerated) // StopDisplay not needed (delay will be zero - should be just before start of next subtitle)
bmp = GenerateBitmap(ImageDisplayArea, imageTopFieldDataAddress, imageBottomFieldDataAddress, fourColors);
return bmp;
}
private static void SetColor(List<Color> fourColors, int fourColorIndex, int clutIndex, List<Color> colorLookUpTable)
{
if (clutIndex >= 0 && clutIndex < colorLookUpTable.Count && fourColorIndex > 0)
fourColors[fourColorIndex] = colorLookUpTable[clutIndex];
}
private Bitmap GenerateBitmap(Rectangle imageDisplayArea, int imageTopFieldDataAddress, int imageBottomFieldDataAddress, List<Color> fourColors)
{
var bmp = new Bitmap(imageDisplayArea.Width + 1, imageDisplayArea.Height + 1);
// Fill with background color
Graphics gr = Graphics.FromImage(bmp);
gr.FillRectangle(new SolidBrush(fourColors[0]), new Rectangle(0, 0, bmp.Width, bmp.Height));
var fastBmp = new FastBitmap(bmp);
fastBmp.LockImage();
GenerateBitmap(_data, fastBmp, 0, imageTopFieldDataAddress, fourColors);
GenerateBitmap(_data, fastBmp, 1, imageBottomFieldDataAddress, fourColors);
Bitmap cropped = CropBitmapAndUnlok(fastBmp, fourColors[0]);
bmp.Dispose();
return cropped;
}
private static Bitmap CropBitmapAndUnlok(FastBitmap bmp, Color backgroundColor)
{
int y = 0;
int x;
Color c = backgroundColor;
// Crop top
while (y < bmp.Height && IsBackgroundColor(c, backgroundColor))
{
c = bmp.GetPixel(0, y);
if (IsBackgroundColor(c, backgroundColor))
{
for (x = 1; x < bmp.Width; x++)
{
c = bmp.GetPixelNext();
if (!IsBackgroundColor(c, backgroundColor))
break;
}
}
if (IsBackgroundColor(c, backgroundColor))
y++;
}
int minY = y;
// Crop left
x = 0;
c = backgroundColor;
while (x < bmp.Width && IsBackgroundColor(c, backgroundColor))
{
for (y = minY; y < bmp.Height; y++)
{
c = bmp.GetPixel(x, y);
if (!IsBackgroundColor(c, backgroundColor))
break;
}
if (IsBackgroundColor(c, backgroundColor))
x++;
}
int minX = x;
// Crop bottom
y = bmp.Height-1;
c = backgroundColor;
while (y > minY && IsBackgroundColor(c, backgroundColor))
{
c = bmp.GetPixel(0, y);
if (IsBackgroundColor(c, backgroundColor))
{
for (x = 1; x < bmp.Width; x++)
{
c = bmp.GetPixelNext();
if (!IsBackgroundColor(c, backgroundColor))
break;
}
}
if (IsBackgroundColor(c, backgroundColor))
y--;
}
int maxY = y;
// Crop right
x = bmp.Width - 1;
c = backgroundColor;
while (x > minX && IsBackgroundColor(c, backgroundColor))
{
for (y = minY; y < bmp.Height; y++)
{
c = bmp.GetPixel(x, y);
if (!IsBackgroundColor(c, backgroundColor))
break;
}
if (IsBackgroundColor(c, backgroundColor))
x--;
}
int maxX = x;
bmp.UnlockImage();
Bitmap bmpImage = bmp.GetBitmap();
if (bmpImage.Width > 1 && bmpImage.Height > 1 && maxX - minX > 0 && maxY - minY > 0)
{
Bitmap bmpCrop = bmpImage.Clone(new Rectangle(minX, minY, maxX - minX, maxY - minY), bmpImage.PixelFormat);
return bmpCrop;
}
return (Bitmap)bmpImage.Clone();
}
private static bool IsBackgroundColor(Color c, Color backgroundColor)
{
return c.ToArgb() == backgroundColor.ToArgb() || c.ToArgb() == Color.FromArgb(0,0,0,0).ToArgb();
}
private static void GenerateBitmap(byte[] data, FastBitmap bmp, int startY, int dataAddress, List<Color> fourColors)
{
int index = 0;
bool onlyHalf = false;
int y = startY;
int x = 0;
while (y < bmp.Height && dataAddress + index + 2 < data.Length)
{
int runLength;
int color;
bool restOfLine;
index += DecodeRle(dataAddress + index, data, out color, out runLength, ref onlyHalf, out restOfLine);
if (restOfLine)
runLength = bmp.Width - x;
Color c = fourColors[color]; // set color via the four colors
for (int i = 0; i < runLength; i++, x++)
{
if (x >= bmp.Width-1)
{
if (y < bmp.Height && x < bmp.Width && c != fourColors[0])
bmp.SetPixel(x, y, c);
if (onlyHalf)
{
onlyHalf = false;
index++;
}
x = 0;
y += 2;
break;
}
if (y < bmp.Height && c != fourColors[0])
bmp.SetPixel(x, y, c);
}
}
}
private static int DecodeRle(int index, byte[] data, out int color, out int runLength, ref bool onlyHalf, out bool restOfLine)
{
//Value Bits n=length, c=color
//1-3 4 n n c c (half a byte)
//4-15 8 0 0 n n n n c c (one byte)
//16-63 12 0 0 0 0 n n n n n n c c (one and a half byte)
//64-255 16 0 0 0 0 0 0 n n n n n n n n c c (two bytes)
// When reaching EndOfLine, index is byte aligned (skip 4 bits if necessary)
restOfLine = false;
string binary2 = Helper.GetBinaryString(data, index, 3);
if (onlyHalf)
binary2 = binary2.Substring(4);
if (binary2.StartsWith("000000"))
{
runLength = (int)Helper.GetUInt32FromBinaryString(binary2.Substring(6, 8));
color = (int)Helper.GetUInt32FromBinaryString(binary2.Substring(14, 2));
if (runLength == 0)
{
// rest of line + skip 4 bits if Only half
restOfLine = true;
if (onlyHalf)
{
onlyHalf = false;
return 3;
}
}
return 2;
}
if (binary2.StartsWith("0000"))
{
runLength = (int)Helper.GetUInt32FromBinaryString(binary2.Substring(4, 6));
color = (int)Helper.GetUInt32FromBinaryString(binary2.Substring(10, 2));
if (onlyHalf)
{
onlyHalf = false;
return 2;
}
onlyHalf = true;
return 1;
}
if (binary2.StartsWith("00"))
{
runLength = (int)Helper.GetUInt32FromBinaryString(binary2.Substring(2, 4));
color = (int)Helper.GetUInt32FromBinaryString(binary2.Substring(6, 2));
return 1;
}
runLength = (int)Helper.GetUInt32FromBinaryString(binary2.Substring(0, 2));
color = (int)Helper.GetUInt32FromBinaryString(binary2.Substring(2, 2));
if (onlyHalf)
{
onlyHalf = false;
return 1;
}
onlyHalf = true;
return 0;
}
}
}

View File

@ -0,0 +1,21 @@
using System;
namespace Nikse.SubtitleEdit.Logic.VobSub
{
public class VobSubMergedPack
{
public SubPicture SubPicture { get; private set; }
public TimeSpan StartTime { get; private set; }
public TimeSpan EndTime { get; set; }
public int StreamId { get; private set; }
public IdxParagraph IdxLine { get; private set; }
public VobSubMergedPack(byte[] subPictureData, TimeSpan presentationTimeStamp, int streamId, IdxParagraph idxLine)
{
SubPicture = new SubPicture(subPictureData);
StartTime = presentationTimeStamp;
StreamId = streamId;
IdxLine = idxLine;
}
}
}

View File

@ -0,0 +1,36 @@

namespace Nikse.SubtitleEdit.Logic.VobSub
{
public class VobSubPack
{
public readonly PacketizedElementaryStream PacketizedElementaryStream;
public readonly Mpeg2Header Mpeg2Header;
public IdxParagraph IdxLine { get; private set; }
readonly byte[] _buffer;
public byte[] Buffer
{
get
{
return _buffer;
}
}
public VobSubPack(byte[] buffer, IdxParagraph idxLine)
{
_buffer = buffer;
IdxLine = idxLine;
if (VobSubParser.IsMpeg2PackHeader(buffer))
{
Mpeg2Header = new Mpeg2Header(buffer);
PacketizedElementaryStream = new PacketizedElementaryStream(buffer, Mpeg2Header.Length);
}
else if (VobSubParser.IsPrivateStream1(buffer, 0))
{
PacketizedElementaryStream = new PacketizedElementaryStream(buffer, 0);
}
}
}
}

View File

@ -0,0 +1,232 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
namespace Nikse.SubtitleEdit.Logic.VobSub
{
public class VobSubParser
{
public bool IsPal { get; private set; }
public List<VobSubPack> VobSubPacks { get; private set; }
public List<Color> IdxPalette = new List<Color>();
public List<string> IdxLanguages = new List<string>();
private const int PacketizedElementaryStreamMaximumLength = 2028;
public VobSubParser(bool isPal)
{
IsPal = isPal;
VobSubPacks = new List<VobSubPack>();
}
public void Open(string fileName)
{
var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
Open(fs);
fs.Close();
}
/// <summary>
/// Can be used with e.g. MemoryStream or FileStream
/// </summary>
/// <param name="ms"></param>
public void Open(Stream ms)
{
VobSubPacks = new List<VobSubPack>();
ms.Position = 0;
var buffer = new byte[0x800]; // 2048
long position = 0;
while (position < ms.Length)
{
ms.Seek(position, SeekOrigin.Begin);
ms.Read(buffer, 0, 0x0800);
if (IsSubtitlePack(buffer))
VobSubPacks.Add(new VobSubPack(buffer, null));
position += 0x800;
}
}
internal void OpenSubIdx(string vobSubFileName, string idxFileName)
{
VobSubPacks = new List<VobSubPack>();
if (!string.IsNullOrEmpty(idxFileName) && File.Exists(idxFileName))
{
var idx = new Idx(idxFileName);
IdxPalette = idx.Palette;
IdxLanguages = idx.Languages;
if (idx.IdxParagraphs.Count > 0)
{
var buffer = new byte[0x800]; // 2048
var fs = new FileStream(vobSubFileName, FileMode.Open, FileAccess.Read, FileShare.Read);
foreach (var p in idx.IdxParagraphs)
{
if (p.FilePosition + 100 < fs.Length)
{
long position = p.FilePosition;
fs.Seek(position, SeekOrigin.Begin);
fs.Read(buffer, 0, 0x0800);
if (IsSubtitlePack(buffer) || IsPrivateStream1(buffer, 0))
{
var vsp = new VobSubPack(buffer, p);
VobSubPacks.Add(vsp);
if (IsPrivateStream1(buffer, 0))
position += vsp.PacketizedElementaryStream.Length + 6;
else
position += 0x800;
while (vsp.PacketizedElementaryStream.Length == PacketizedElementaryStreamMaximumLength && position < fs.Length)
{
fs.Seek(position, SeekOrigin.Begin);
fs.Read(buffer, 0, 0x800);
vsp = new VobSubPack(buffer, p); // idx position?
VobSubPacks.Add(vsp);
if (IsPrivateStream1(buffer, 0))
position += vsp.PacketizedElementaryStream.Length + 6;
else
position += 0x800;
}
}
}
}
fs.Close();
return;
}
}
// No valid idx file found - just open like vob file
Open(vobSubFileName);
}
/// <summary>
/// Demultiplex multiplexed packs together each streamId at a time + removing bad packs + fixing displaytimes
/// </summary>
/// <returns>List of complete packs each with a complete sub image</returns>
public List<VobSubMergedPack> MergeVobSubPacks()
{
var list = new List<VobSubMergedPack>();
var pts = new TimeSpan();
var ms = new MemoryStream();
int streamId = 0;
bool continuation = false;
float ticksPerMillisecond = 90.000F;
if (!IsPal)
ticksPerMillisecond = 90.090F;
// get unique streamIds
var uniqueStreamIds = new List<int>();
foreach (var p in VobSubPacks)
{
if (p.PacketizedElementaryStream != null &&
p.PacketizedElementaryStream.SubPictureStreamId.HasValue &&
!uniqueStreamIds.Contains(p.PacketizedElementaryStream.SubPictureStreamId.Value))
uniqueStreamIds.Add(p.PacketizedElementaryStream.SubPictureStreamId.Value);
}
IdxParagraph lastIdxParagraph = null;
foreach (int uniqueStreamId in uniqueStreamIds) // packets must be merged in streamId order (so they don't get mixed)
{
foreach (var p in VobSubPacks)
{
if (p.PacketizedElementaryStream != null && p.PacketizedElementaryStream.SubPictureStreamId.HasValue &&
p.PacketizedElementaryStream.SubPictureStreamId.Value == uniqueStreamId)
{
if (!continuation)
{
if (ms.Length > 0)
list.Add(new VobSubMergedPack(ms.ToArray(), pts, streamId, lastIdxParagraph));
ms = new MemoryStream();
pts =
TimeSpan.FromMilliseconds(
Convert.ToDouble(p.PacketizedElementaryStream.PresentationTimeStamp/
ticksPerMillisecond)); //90000F * 1000)); (PAL)
streamId = p.PacketizedElementaryStream.SubPictureStreamId.Value;
}
lastIdxParagraph = p.IdxLine;
continuation = p.PacketizedElementaryStream.Length == PacketizedElementaryStreamMaximumLength;
ms.Write(p.PacketizedElementaryStream.DataBuffer, 0,
p.PacketizedElementaryStream.DataBuffer.Length);
}
}
if (ms.Length > 0)
{
list.Add(new VobSubMergedPack(ms.ToArray(), pts, streamId, lastIdxParagraph));
ms = new MemoryStream();
}
}
// Remove any bad packs
for (int i = list.Count - 1; i >= 0; i--)
{
VobSubMergedPack pack = list[i];
if (pack.SubPicture == null || pack.SubPicture.ImageDisplayArea.Width <= 1 || pack.SubPicture.ImageDisplayArea.Height <= 1)
list.RemoveAt(i);
}
// Fix subs with no duration (completely normal) or negative duration or duration > 10 seconds
for (int i=0; i<list.Count; i++)
{
VobSubMergedPack pack = list[i];
if (pack.SubPicture.Delay.TotalMilliseconds > 0)
pack.EndTime = pack.StartTime.Add(pack.SubPicture.Delay);
if (pack.EndTime < pack.StartTime || pack.EndTime.TotalSeconds - pack.StartTime.TotalSeconds > 10.0)
{
if (i + 1 < list.Count)
pack.EndTime = TimeSpan.FromMilliseconds(list[i].StartTime.TotalMilliseconds - 100);
else
pack.EndTime = TimeSpan.FromMilliseconds(pack.StartTime.TotalMilliseconds + 3000);
}
}
return list;
}
internal static bool IsMpeg2PackHeader(byte[] buffer)
{
return buffer.Length >= 3 &&
buffer[0] == 0 &&
buffer[1] == 0 &&
buffer[2] == 1 &&
buffer[3] == 0xba; // 0xba == 186 - MPEG-2 Pack Header
}
internal static bool IsPrivateStream1(byte[] buffer, int index)
{
return buffer.Length >= index + 3 &&
buffer[index + 0] == 0 &&
buffer[index + 1] == 0 &&
buffer[index + 2] == 1 &&
buffer[index + 3] == 0xbd; // 0xbd == 189 - MPEG-2 Private stream 1 (non MPEG audio, subpictures)
}
internal static bool IsPrivateStream2(byte[] buffer, int index)
{
return buffer.Length >= index + 3 &&
buffer[index + 0] == 0 &&
buffer[index + 1] == 0 &&
buffer[index + 2] == 1 &&
buffer[index + 3] == 0xbf;
}
internal static bool IsSubtitlePack(byte[] buffer)
{
if (IsMpeg2PackHeader(buffer) && IsPrivateStream1(buffer, Mpeg2Header.Length))
{
int pesHeaderDataLength = buffer[Mpeg2Header.Length + 8];
int streamId = buffer[Mpeg2Header.Length + 8 + 1 + pesHeaderDataLength];
if (streamId >= 0x20 && streamId <= 0x3f) // Subtitle IDs allowed (or x3f to x40?)
return true;
}
return false;
}
}
}