mirror of
https://github.com/SubtitleEdit/subtitleedit.git
synced 2024-11-22 03:02:35 +01:00
Initial version
git-svn-id: https://subtitleedit.googlecode.com/svn/trunk@14 99eadd0c-20b8-1223-b5c4-2a2b2df33de2
This commit is contained in:
parent
976be2c6e6
commit
8f89e3146e
106
src/Logic/SubtitleFormats/Csv.cs
Normal file
106
src/Logic/SubtitleFormats/Csv.cs
Normal 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("\"\"", "\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
122
src/Logic/SubtitleFormats/DvdStudioPro.cs
Normal file
122
src/Logic/SubtitleFormats/DvdStudioPro.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
150
src/Logic/SubtitleFormats/DvdSubtitle.cs
Normal file
150
src/Logic/SubtitleFormats/DvdSubtitle.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
234
src/Logic/SubtitleFormats/Ebu.cs
Normal file
234
src/Logic/SubtitleFormats/Ebu.cs
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
140
src/Logic/SubtitleFormats/Idx.cs
Normal file
140
src/Logic/SubtitleFormats/Idx.cs
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
151
src/Logic/SubtitleFormats/MPlayer2.cs
Normal file
151
src/Logic/SubtitleFormats/MPlayer2.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
171
src/Logic/SubtitleFormats/MicroDvd.cs
Normal file
171
src/Logic/SubtitleFormats/MicroDvd.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
208
src/Logic/SubtitleFormats/OpenDvt.cs
Normal file
208
src/Logic/SubtitleFormats/OpenDvt.cs
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
195
src/Logic/SubtitleFormats/Sami.cs
Normal file
195
src/Logic/SubtitleFormats/Sami.cs
Normal 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}> </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(" ", 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
99
src/Logic/SubtitleFormats/SonyDVDArchitect.cs
Normal file
99
src/Logic/SubtitleFormats/SonyDVDArchitect.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
130
src/Logic/SubtitleFormats/SonyDVDArchitectWithLineNumbers.cs
Normal file
130
src/Logic/SubtitleFormats/SonyDVDArchitectWithLineNumbers.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
241
src/Logic/SubtitleFormats/SubRip.cs
Normal file
241
src/Logic/SubtitleFormats/SubRip.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
307
src/Logic/SubtitleFormats/SubStationAlpha.cs
Normal file
307
src/Logic/SubtitleFormats/SubStationAlpha.cs
Normal 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" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
151
src/Logic/SubtitleFormats/SubViewer10.cs
Normal file
151
src/Logic/SubtitleFormats/SubViewer10.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
156
src/Logic/SubtitleFormats/SubViewer20.cs
Normal file
156
src/Logic/SubtitleFormats/SubViewer20.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
112
src/Logic/SubtitleFormats/SubtitleFormat.cs
Normal file
112
src/Logic/SubtitleFormats/SubtitleFormat.cs
Normal 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>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
107
src/Logic/SubtitleFormats/TMPlayer.cs
Normal file
107
src/Logic/SubtitleFormats/TMPlayer.cs
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
116
src/Logic/SubtitleFormats/TimeXml.cs
Normal file
116
src/Logic/SubtitleFormats/TimeXml.cs
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
192
src/Logic/SubtitleFormats/TimedText.cs
Normal file
192
src/Logic/SubtitleFormats/TimedText.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
172
src/Logic/SubtitleFormats/UnknownSubtitle1.cs
Normal file
172
src/Logic/SubtitleFormats/UnknownSubtitle1.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
159
src/Logic/SubtitleFormats/UnknownSubtitle2.cs
Normal file
159
src/Logic/SubtitleFormats/UnknownSubtitle2.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
93
src/Logic/SubtitleFormats/UnknownSubtitle3.cs
Normal file
93
src/Logic/SubtitleFormats/UnknownSubtitle3.cs
Normal 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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
144
src/Logic/SubtitleFormats/UnknownSubtitle4.cs
Normal file
144
src/Logic/SubtitleFormats/UnknownSubtitle4.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
111
src/Logic/SubtitleFormats/UnknownSubtitle5.cs
Normal file
111
src/Logic/SubtitleFormats/UnknownSubtitle5.cs
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
195
src/Logic/SubtitleFormats/YouTubeSbv.cs
Normal file
195
src/Logic/SubtitleFormats/YouTubeSbv.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
154
src/Logic/VideoFormats/AviRiffData.cs
Normal file
154
src/Logic/VideoFormats/AviRiffData.cs
Normal 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
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
1141
src/Logic/VideoFormats/Matroska.cs
Normal file
1141
src/Logic/VideoFormats/Matroska.cs
Normal file
File diff suppressed because it is too large
Load Diff
471
src/Logic/VideoFormats/RiffDecodeHeader.cs
Normal file
471
src/Logic/VideoFormats/RiffDecodeHeader.cs
Normal 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
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
476
src/Logic/VideoFormats/RiffParser.cs
Normal file
476
src/Logic/VideoFormats/RiffParser.cs
Normal 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
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
16
src/Logic/VideoFormats/VideoInfo.cs
Normal file
16
src/Logic/VideoFormats/VideoInfo.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
469
src/Logic/VideoPlayers/LibVlcDynamic.cs
Normal file
469
src/Logic/VideoPlayers/LibVlcDynamic.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
332
src/Logic/VideoPlayers/MPlayer.cs
Normal file
332
src/Logic/VideoPlayers/MPlayer.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
163
src/Logic/VideoPlayers/ManagedDirectXPlayer.cs
Normal file
163
src/Logic/VideoPlayers/ManagedDirectXPlayer.cs
Normal 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();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
259
src/Logic/VideoPlayers/QuartsPlayer.cs
Normal file
259
src/Logic/VideoPlayers/QuartsPlayer.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
src/Logic/VideoPlayers/VideoPlayer.cs
Normal file
23
src/Logic/VideoPlayers/VideoPlayer.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
114
src/Logic/VideoPlayers/WmpPlayer.cs
Normal file
114
src/Logic/VideoPlayers/WmpPlayer.cs
Normal 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
318
src/Logic/VobSub/Helper.cs
Normal 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
100
src/Logic/VobSub/Idx.cs
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
37
src/Logic/VobSub/Mpeg2Header.cs
Normal file
37
src/Logic/VobSub/Mpeg2Header.cs
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
94
src/Logic/VobSub/PacketizedElementaryStream.cs
Normal file
94
src/Logic/VobSub/PacketizedElementaryStream.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
369
src/Logic/VobSub/SubPicture.cs
Normal file
369
src/Logic/VobSub/SubPicture.cs
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
21
src/Logic/VobSub/VobSubMergedPack.cs
Normal file
21
src/Logic/VobSub/VobSubMergedPack.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
src/Logic/VobSub/VobSubPack.cs
Normal file
36
src/Logic/VobSub/VobSubPack.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
232
src/Logic/VobSub/VobSubParser.cs
Normal file
232
src/Logic/VobSub/VobSubParser.cs
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user