From 3ba6364f259ea43979a88b2a83d64292119057dc Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 5 Aug 2014 19:59:24 -0400 Subject: fixes #887 - Support ttml subtitle output --- .../MediaBrowser.MediaEncoding.csproj | 1 + .../Subtitles/SubtitleEncoder.cs | 38 ++++++++++---- MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs | 59 ++++++++++++++++++++++ 3 files changed, 88 insertions(+), 10 deletions(-) create mode 100644 MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs (limited to 'MediaBrowser.MediaEncoding') 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 @@ + 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 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(""); + writer.WriteLine(""); + + writer.WriteLine(""); + writer.WriteLine(""); + writer.WriteLine("