aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.MediaEncoding
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.MediaEncoding')
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs16
-rw-r--r--MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj3
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/AssParser.cs71
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs400
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs7
5 files changed, 438 insertions, 59 deletions
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index ce761ff9c..82d5e0344 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -70,22 +70,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
/// <summary>
- /// The _semaphoreLocks
- /// </summary>
- private readonly ConcurrentDictionary<string, SemaphoreSlim> _semaphoreLocks =
- new ConcurrentDictionary<string, SemaphoreSlim>();
-
- /// <summary>
- /// Gets the lock.
- /// </summary>
- /// <param name="filename">The filename.</param>
- /// <returns>System.Object.</returns>
- private SemaphoreSlim GetLock(string filename)
- {
- return _semaphoreLocks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1));
- }
-
- /// <summary>
/// Gets the media info.
/// </summary>
/// <param name="inputFiles">The input files.</param>
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index e3d9d905a..94e3575a1 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -63,6 +63,7 @@
<Compile Include="Subtitles\ISubtitleWriter.cs" />
<Compile Include="Subtitles\SrtParser.cs" />
<Compile Include="Subtitles\SrtWriter.cs" />
+ <Compile Include="Subtitles\AssParser.cs" />
<Compile Include="Subtitles\SsaParser.cs" />
<Compile Include="Subtitles\SubtitleEncoder.cs" />
<Compile Include="Subtitles\SubtitleTrackInfo.cs" />
@@ -96,4 +97,4 @@
<Target Name="AfterBuild">
</Target>
-->
-</Project>
+</Project> \ No newline at end of file
diff --git a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs
new file mode 100644
index 000000000..e5a727428
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Threading;
+
+namespace MediaBrowser.MediaEncoding.Subtitles
+{
+ public class AssParser : ISubtitleParser
+ {
+ private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+
+ public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken)
+ {
+ var trackInfo = new SubtitleTrackInfo();
+ var eventIndex = 1;
+ using (var reader = new StreamReader(stream))
+ {
+ string line;
+ while (reader.ReadLine() != "[Events]")
+ {}
+ var headers = ParseFieldHeaders(reader.ReadLine());
+
+ while ((line = reader.ReadLine()) != null)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ if (string.IsNullOrWhiteSpace(line))
+ {
+ continue;
+ }
+ if(line.StartsWith("["))
+ break;
+ if(string.IsNullOrEmpty(line))
+ continue;
+ var subEvent = new SubtitleTrackEvent { Id = eventIndex.ToString(_usCulture) };
+ eventIndex++;
+ var sections = line.Substring(10).Split(',');
+
+ subEvent.StartPositionTicks = GetTicks(sections[headers["Start"]]);
+ subEvent.EndPositionTicks = GetTicks(sections[headers["End"]]);
+ subEvent.Text = string.Join(",", sections.Skip(headers["Text"]));
+ subEvent.Text = Regex.Replace(subEvent.Text, @"\{(\\[\w]+\(?([\w\d]+,?)+\)?)+\}", string.Empty, RegexOptions.IgnoreCase);
+
+ trackInfo.TrackEvents.Add(subEvent);
+ }
+ }
+ return trackInfo;
+ }
+
+ long GetTicks(string time)
+ {
+ TimeSpan span;
+ return TimeSpan.TryParseExact(time, @"h\:mm\:ss\.ff", _usCulture, out span)
+ ? span.Ticks: 0;
+ }
+
+ private Dictionary<string,int> ParseFieldHeaders(string line) {
+ var fields = line.Substring(8).Split(',').Select(x=>x.Trim()).ToList();
+
+ var result = new Dictionary<string, int> {
+ {"Start", fields.IndexOf("Start")},
+ {"End", fields.IndexOf("End")},
+ {"Text", fields.IndexOf("Text")}
+ };
+ return result;
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs
index e21804f6c..4b11d55ce 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs
@@ -1,71 +1,391 @@
using System;
-using System.Collections.Generic;
-using System.Globalization;
using System.IO;
-using System.Linq;
-using System.Text.RegularExpressions;
+using System.Text;
using System.Threading;
namespace MediaBrowser.MediaEncoding.Subtitles
{
+ /// <summary>
+ /// Credit to https://github.com/SubtitleEdit/subtitleedit/blob/a299dc4407a31796364cc6ad83f0d3786194ba22/src/Logic/SubtitleFormats/SubStationAlpha.cs
+ /// </summary>
public class SsaParser : ISubtitleParser
{
- private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken)
{
var trackInfo = new SubtitleTrackInfo();
- var eventIndex = 1;
+
using (var reader = new StreamReader(stream))
{
+ bool eventsStarted = false;
+
+ string[] format = "Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text".Split(',');
+ int indexLayer = 0;
+ int indexStart = 1;
+ int indexEnd = 2;
+ int indexStyle = 3;
+ int indexName = 4;
+ int indexEffect = 8;
+ int indexText = 9;
+ int lineNumber = 0;
+
+ var header = new StringBuilder();
+
string line;
- while (reader.ReadLine() != "[Events]")
- {}
- var headers = ParseFieldHeaders(reader.ReadLine());
while ((line = reader.ReadLine()) != null)
{
cancellationToken.ThrowIfCancellationRequested();
-
- if (string.IsNullOrWhiteSpace(line))
+
+ lineNumber++;
+ if (!eventsStarted)
+ header.AppendLine(line);
+
+ if (line.Trim().ToLower() == "[events]")
{
- continue;
+ eventsStarted = true;
}
- if(line.StartsWith("["))
- break;
- if(string.IsNullOrEmpty(line))
- continue;
- var subEvent = new SubtitleTrackEvent { Id = eventIndex.ToString(_usCulture) };
- eventIndex++;
- var sections = line.Substring(10).Split(',');
-
- subEvent.StartPositionTicks = GetTicks(sections[headers["Start"]]);
- subEvent.EndPositionTicks = GetTicks(sections[headers["End"]]);
- subEvent.Text = string.Join(",", sections.Skip(headers["Text"]));
- subEvent.Text = Regex.Replace(subEvent.Text, @"\{(\\[\w]+\(?([\w\d]+,?)+\)?)+\}", string.Empty, RegexOptions.IgnoreCase);
-
- trackInfo.TrackEvents.Add(subEvent);
- }
+ else if (!string.IsNullOrEmpty(line) && line.Trim().StartsWith(";"))
+ {
+ // skip comment lines
+ }
+ else if (eventsStarted && line.Trim().Length > 0)
+ {
+ 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() == "layer")
+ indexLayer = i;
+ else 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 if (format[i].Trim().ToLower() == "effect")
+ indexEffect = i;
+ else if (format[i].Trim().ToLower() == "style")
+ indexStyle = i;
+ }
+ }
+ }
+ else if (!string.IsNullOrEmpty(s))
+ {
+ string text = string.Empty;
+ string start = string.Empty;
+ string end = string.Empty;
+ string style = string.Empty;
+ string layer = string.Empty;
+ string effect = string.Empty;
+ string name = 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 == indexLayer)
+ layer = splittedLine[i];
+ else if (i == indexEffect)
+ effect = splittedLine[i];
+ else if (i == indexText)
+ text = splittedLine[i];
+ else if (i == indexStyle)
+ style = splittedLine[i];
+ else if (i == indexName)
+ name = splittedLine[i];
+ else if (i > indexText)
+ text += "," + splittedLine[i];
+ }
+
+ try
+ {
+ var p = new SubtitleTrackEvent();
+
+ p.StartPositionTicks = GetTimeCodeFromString(start);
+ p.EndPositionTicks = GetTimeCodeFromString(end);
+ p.Text = GetFormattedText(text);
+
+ trackInfo.TrackEvents.Add(p);
+ }
+ catch
+ {
+ }
+ }
+ }
+ }
+
+ //if (header.Length > 0)
+ //subtitle.Header = header.ToString();
+
+ //subtitle.Renumber(1);
}
return trackInfo;
}
- long GetTicks(string time)
+ private static long GetTimeCodeFromString(string time)
+ {
+ // h:mm:ss.cc
+ string[] timeCode = time.Split(':', '.');
+ return new TimeSpan(0, int.Parse(timeCode[0]),
+ int.Parse(timeCode[1]),
+ int.Parse(timeCode[2]),
+ int.Parse(timeCode[3]) * 10).Ticks;
+ }
+
+ public static string GetFormattedText(string text)
+ {
+ text = text.Replace("\\N", Environment.NewLine).Replace("\\n", Environment.NewLine);
+ bool italic = false;
+
+ for (int i = 0; i < 10; i++) // just look ten times...
+ {
+ if (text.Contains(@"{\fn"))
+ {
+ int start = text.IndexOf(@"{\fn");
+ int end = text.IndexOf('}', start);
+ if (end > 0 && !text.Substring(start).StartsWith("{\\fn}"))
+ {
+ string fontName = text.Substring(start + 4, end - (start + 4));
+ string extraTags = string.Empty;
+ CheckAndAddSubTags(ref fontName, ref extraTags, out italic);
+ text = text.Remove(start, end - start + 1);
+ if (italic)
+ text = text.Insert(start, "<font face=\"" + fontName + "\"" + extraTags + "><i>");
+ else
+ text = text.Insert(start, "<font face=\"" + fontName + "\"" + extraTags + ">");
+
+ int indexOfEndTag = text.IndexOf("{\\fn}", start);
+ if (indexOfEndTag > 0)
+ text = text.Remove(indexOfEndTag, "{\\fn}".Length).Insert(indexOfEndTag, "</font>");
+ else
+ text += "</font>";
+ }
+ }
+
+ if (text.Contains(@"{\fs"))
+ {
+ int start = text.IndexOf(@"{\fs");
+ int end = text.IndexOf('}', start);
+ if (end > 0 && !text.Substring(start).StartsWith("{\\fs}"))
+ {
+ string fontSize = text.Substring(start + 4, end - (start + 4));
+ string extraTags = string.Empty;
+ CheckAndAddSubTags(ref fontSize, ref extraTags, out italic);
+ if (IsInteger(fontSize))
+ {
+ text = text.Remove(start, end - start + 1);
+ if (italic)
+ text = text.Insert(start, "<font size=\"" + fontSize + "\"" + extraTags + "><i>");
+ else
+ text = text.Insert(start, "<font size=\"" + fontSize + "\"" + extraTags + ">");
+
+ int indexOfEndTag = text.IndexOf("{\\fs}", start);
+ if (indexOfEndTag > 0)
+ text = text.Remove(indexOfEndTag, "{\\fs}".Length).Insert(indexOfEndTag, "</font>");
+ else
+ text += "</font>";
+ }
+ }
+ }
+
+ if (text.Contains(@"{\c"))
+ {
+ int start = text.IndexOf(@"{\c");
+ int end = text.IndexOf('}', start);
+ if (end > 0 && !text.Substring(start).StartsWith("{\\c}"))
+ {
+ string color = text.Substring(start + 4, end - (start + 4));
+ string extraTags = string.Empty;
+ CheckAndAddSubTags(ref color, ref extraTags, out italic);
+
+ color = color.Replace("&", string.Empty).TrimStart('H');
+ color = color.PadLeft(6, '0');
+
+ // switch to rrggbb from bbggrr
+ color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2);
+ color = color.ToLower();
+
+ text = text.Remove(start, end - start + 1);
+ if (italic)
+ text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + "><i>");
+ else
+ text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">");
+ int indexOfEndTag = text.IndexOf("{\\c}", start);
+ if (indexOfEndTag > 0)
+ text = text.Remove(indexOfEndTag, "{\\c}".Length).Insert(indexOfEndTag, "</font>");
+ else
+ text += "</font>";
+ }
+ }
+
+ if (text.Contains(@"{\1c")) // "1" specifices primary color
+ {
+ int start = text.IndexOf(@"{\1c");
+ int end = text.IndexOf('}', start);
+ if (end > 0 && !text.Substring(start).StartsWith("{\\1c}"))
+ {
+ string color = text.Substring(start + 5, end - (start + 5));
+ string extraTags = string.Empty;
+ CheckAndAddSubTags(ref color, ref extraTags, out italic);
+
+ color = color.Replace("&", string.Empty).TrimStart('H');
+ color = color.PadLeft(6, '0');
+
+ // switch to rrggbb from bbggrr
+ color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2);
+ color = color.ToLower();
+
+ text = text.Remove(start, end - start + 1);
+ if (italic)
+ text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + "><i>");
+ else
+ text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">");
+ text += "</font>";
+ }
+ }
+
+ }
+
+ text = text.Replace(@"{\i1}", "<i>");
+ text = text.Replace(@"{\i0}", "</i>");
+ text = text.Replace(@"{\i}", "</i>");
+ if (CountTagInText(text, "<i>") > CountTagInText(text, "</i>"))
+ text += "</i>";
+
+ text = text.Replace(@"{\u1}", "<u>");
+ text = text.Replace(@"{\u0}", "</u>");
+ text = text.Replace(@"{\u}", "</u>");
+ if (CountTagInText(text, "<u>") > CountTagInText(text, "</u>"))
+ text += "</u>";
+
+ text = text.Replace(@"{\b1}", "<b>");
+ text = text.Replace(@"{\b0}", "</b>");
+ text = text.Replace(@"{\b}", "</b>");
+ if (CountTagInText(text, "<b>") > CountTagInText(text, "</b>"))
+ text += "</b>";
+
+ return text;
+ }
+
+ private static bool IsInteger(string s)
{
- TimeSpan span;
- return TimeSpan.TryParseExact(time, @"h\:mm\:ss\.ff", _usCulture, out span)
- ? span.Ticks: 0;
+ int i;
+ if (int.TryParse(s, out i))
+ return true;
+ return false;
}
- private Dictionary<string,int> ParseFieldHeaders(string line) {
- var fields = line.Substring(8).Split(',').Select(x=>x.Trim()).ToList();
+ private static int CountTagInText(string text, string tag)
+ {
+ int count = 0;
+ int index = text.IndexOf(tag);
+ while (index >= 0)
+ {
+ count++;
+ if (index == text.Length)
+ return count;
+ index = text.IndexOf(tag, index + 1);
+ }
+ return count;
+ }
+
+ private static void CheckAndAddSubTags(ref string tagName, ref string extraTags, out bool italic)
+ {
+ italic = false;
+ int indexOfSPlit = tagName.IndexOf(@"\");
+ if (indexOfSPlit > 0)
+ {
+ string rest = tagName.Substring(indexOfSPlit).TrimStart('\\');
+ tagName = tagName.Remove(indexOfSPlit);
+
+ for (int i = 0; i < 10; i++)
+ {
+ if (rest.StartsWith("fs") && rest.Length > 2)
+ {
+ indexOfSPlit = rest.IndexOf(@"\");
+ string fontSize = rest;
+ if (indexOfSPlit > 0)
+ {
+ fontSize = rest.Substring(0, indexOfSPlit);
+ rest = rest.Substring(indexOfSPlit).TrimStart('\\');
+ }
+ else
+ {
+ rest = string.Empty;
+ }
+ extraTags += " size=\"" + fontSize.Substring(2) + "\"";
+ }
+ else if (rest.StartsWith("fn") && rest.Length > 2)
+ {
+ indexOfSPlit = rest.IndexOf(@"\");
+ string fontName = rest;
+ if (indexOfSPlit > 0)
+ {
+ fontName = rest.Substring(0, indexOfSPlit);
+ rest = rest.Substring(indexOfSPlit).TrimStart('\\');
+ }
+ else
+ {
+ rest = string.Empty;
+ }
+ extraTags += " face=\"" + fontName.Substring(2) + "\"";
+ }
+ else if (rest.StartsWith("c") && rest.Length > 2)
+ {
+ indexOfSPlit = rest.IndexOf(@"\");
+ string fontColor = rest;
+ if (indexOfSPlit > 0)
+ {
+ fontColor = rest.Substring(0, indexOfSPlit);
+ rest = rest.Substring(indexOfSPlit).TrimStart('\\');
+ }
+ else
+ {
+ rest = string.Empty;
+ }
+
+ string color = fontColor.Substring(2);
+ color = color.Replace("&", string.Empty).TrimStart('H');
+ color = color.PadLeft(6, '0');
+ // switch to rrggbb from bbggrr
+ color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2);
+ color = color.ToLower();
- var result = new Dictionary<string, int> {
- {"Start", fields.IndexOf("Start")},
- {"End", fields.IndexOf("End")},
- {"Text", fields.IndexOf("Text")}
- };
- return result;
+ extraTags += " color=\"" + color + "\"";
+ }
+ else if (rest.StartsWith("i1") && rest.Length > 1)
+ {
+ indexOfSPlit = rest.IndexOf(@"\");
+ italic = true;
+ if (indexOfSPlit > 0)
+ {
+ rest = rest.Substring(indexOfSPlit).TrimStart('\\');
+ }
+ else
+ {
+ rest = string.Empty;
+ }
+ }
+ else if (rest.Length > 0 && rest.Contains("\\"))
+ {
+ indexOfSPlit = rest.IndexOf(@"\");
+ rest = rest.Substring(indexOfSPlit).TrimStart('\\');
+ }
+ }
+ }
}
}
}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index 82e331dd8..c083cad4e 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -239,11 +239,14 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
return new SrtParser();
}
- if (string.Equals(format, SubtitleFormat.SSA, StringComparison.OrdinalIgnoreCase) ||
- string.Equals(format, SubtitleFormat.ASS, StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(format, SubtitleFormat.SSA, StringComparison.OrdinalIgnoreCase))
{
return new SsaParser();
}
+ if (string.Equals(format, SubtitleFormat.ASS, StringComparison.OrdinalIgnoreCase))
+ {
+ return new AssParser();
+ }
if (throwIfMissing)
{