aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.MediaEncoding
diff options
context:
space:
mode:
authorLuke Pulverenti <luke.pulverenti@gmail.com>2014-08-05 19:59:24 -0400
committerLuke Pulverenti <luke.pulverenti@gmail.com>2014-08-05 19:59:24 -0400
commit3ba6364f259ea43979a88b2a83d64292119057dc (patch)
tree5663fb9c54387e92a09d219488ad1af9276232aa /MediaBrowser.MediaEncoding
parent7e25c857a551ce06025b3b85996aef7ed3c6571e (diff)
fixes #887 - Support ttml subtitle output
Diffstat (limited to 'MediaBrowser.MediaEncoding')
-rw-r--r--MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj1
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs38
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs59
3 files changed, 88 insertions, 10 deletions
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index f11c261e2..12a03a876 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -66,6 +66,7 @@
<Compile Include="Subtitles\SsaParser.cs" />
<Compile Include="Subtitles\SubtitleEncoder.cs" />
<Compile Include="Subtitles\SubtitleTrackInfo.cs" />
+ <Compile Include="Subtitles\TtmlWriter.cs" />
<Compile Include="Subtitles\VttWriter.cs" />
</ItemGroup>
<ItemGroup>
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index ab9cd546a..82e331dd8 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -48,6 +48,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
string inputFormat,
string outputFormat,
long startTimeTicks,
+ long? endTimeTicks,
CancellationToken cancellationToken)
{
var ms = new MemoryStream();
@@ -56,6 +57,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
// Return the original without any conversions, if possible
if (startTimeTicks == 0 &&
+ !endTimeTicks.HasValue &&
string.Equals(inputFormat, outputFormat, StringComparison.OrdinalIgnoreCase))
{
await stream.CopyToAsync(ms, 81920, cancellationToken).ConfigureAwait(false);
@@ -64,7 +66,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
var trackInfo = await GetTrackInfo(stream, inputFormat, cancellationToken).ConfigureAwait(false);
- UpdateStartingPosition(trackInfo, startTimeTicks);
+ FilterEvents(trackInfo, startTimeTicks, endTimeTicks, false);
var writer = GetWriter(outputFormat);
@@ -81,19 +83,30 @@ namespace MediaBrowser.MediaEncoding.Subtitles
return ms;
}
- private void UpdateStartingPosition(SubtitleTrackInfo track, long startPositionTicks)
+ private void FilterEvents(SubtitleTrackInfo track, long startPositionTicks, long? endTimeTicks, bool preserveTimestamps)
{
- if (startPositionTicks == 0) return;
+ // Drop subs that are earlier than what we're looking for
+ track.TrackEvents = track.TrackEvents
+ .SkipWhile(i => (i.StartPositionTicks - startPositionTicks) < 0 || (i.EndPositionTicks - startPositionTicks) < 0)
+ .ToList();
- foreach (var trackEvent in track.TrackEvents)
+ if (endTimeTicks.HasValue)
{
- trackEvent.EndPositionTicks -= startPositionTicks;
- trackEvent.StartPositionTicks -= startPositionTicks;
+ var endTime = endTimeTicks.Value;
+
+ track.TrackEvents = track.TrackEvents
+ .TakeWhile(i => i.StartPositionTicks <= endTime)
+ .ToList();
}
- track.TrackEvents = track.TrackEvents
- .SkipWhile(i => i.StartPositionTicks < 0 || i.EndPositionTicks < 0)
- .ToList();
+ if (!preserveTimestamps)
+ {
+ foreach (var trackEvent in track.TrackEvents)
+ {
+ trackEvent.EndPositionTicks -= startPositionTicks;
+ trackEvent.StartPositionTicks -= startPositionTicks;
+ }
+ }
}
public async Task<Stream> GetSubtitles(string itemId,
@@ -101,6 +114,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
int subtitleStreamIndex,
string outputFormat,
long startTimeTicks,
+ long? endTimeTicks,
CancellationToken cancellationToken)
{
var subtitle = await GetSubtitleStream(itemId, mediaSourceId, subtitleStreamIndex, cancellationToken)
@@ -110,7 +124,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
var inputFormat = subtitle.Item2;
- return await ConvertSubtitles(stream, inputFormat, outputFormat, startTimeTicks, cancellationToken).ConfigureAwait(false);
+ return await ConvertSubtitles(stream, inputFormat, outputFormat, startTimeTicks, endTimeTicks, cancellationToken).ConfigureAwait(false);
}
}
@@ -254,6 +268,10 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
return new VttWriter();
}
+ if (string.Equals(format, SubtitleFormat.TTML, StringComparison.OrdinalIgnoreCase))
+ {
+ return new TtmlWriter();
+ }
throw new ArgumentException("Unsupported format: " + format);
}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs
new file mode 100644
index 000000000..a937175f0
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs
@@ -0,0 +1,59 @@
+using System;
+using System.IO;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading;
+
+namespace MediaBrowser.MediaEncoding.Subtitles
+{
+ public class TtmlWriter : ISubtitleWriter
+ {
+ public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken)
+ {
+ // Example: https://github.com/zmalltalker/ttml2vtt/blob/master/data/sample.xml
+ // Parser example: https://github.com/mozilla/popcorn-js/blob/master/parsers/parserTTML/popcorn.parserTTML.js
+
+ using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
+ {
+ writer.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
+ writer.WriteLine("<tt xmlns=\"http://www.w3.org/ns/ttml\" xmlns:tts=\"http://www.w3.org/2006/04/ttaf1#styling\" lang=\"no\">");
+
+ writer.WriteLine("<head>");
+ writer.WriteLine("<styling>");
+ writer.WriteLine("<style id=\"italic\" tts:fontStyle=\"italic\" />");
+ writer.WriteLine("<style id=\"left\" tts:textAlign=\"left\" />");
+ writer.WriteLine("<style id=\"center\" tts:textAlign=\"center\" />");
+ writer.WriteLine("<style id=\"right\" tts:textAlign=\"right\" />");
+ writer.WriteLine("</styling>");
+ writer.WriteLine("</head>");
+
+ writer.WriteLine("<body>");
+ writer.WriteLine("<div>");
+
+ foreach (var trackEvent in info.TrackEvents)
+ {
+ var text = trackEvent.Text;
+
+ text = Regex.Replace(text, @"\\N", "<br/>", RegexOptions.IgnoreCase);
+
+ writer.WriteLine("<p begin=\"{0}\" dur=\"{1}\">{2}</p>",
+ trackEvent.StartPositionTicks,
+ (trackEvent.EndPositionTicks - trackEvent.StartPositionTicks),
+ text);
+ }
+
+ writer.WriteLine("</div>");
+ writer.WriteLine("</body>");
+
+ writer.WriteLine("</tt>");
+ }
+ }
+
+ private string FormatTime(long ticks)
+ {
+ var time = TimeSpan.FromTicks(ticks);
+
+ return string.Format(@"{0:hh\:mm\:ss\,fff}", time);
+ }
+ }
+}