diff options
| author | Luke Pulverenti <luke.pulverenti@gmail.com> | 2014-08-05 19:59:24 -0400 |
|---|---|---|
| committer | Luke Pulverenti <luke.pulverenti@gmail.com> | 2014-08-05 19:59:24 -0400 |
| commit | 3ba6364f259ea43979a88b2a83d64292119057dc (patch) | |
| tree | 5663fb9c54387e92a09d219488ad1af9276232aa /MediaBrowser.MediaEncoding | |
| parent | 7e25c857a551ce06025b3b85996aef7ed3c6571e (diff) | |
fixes #887 - Support ttml subtitle output
Diffstat (limited to 'MediaBrowser.MediaEncoding')
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); + } + } +} |
