mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-04-15 23:02:18 +01:00
fixes #887 - Support ttml subtitle output
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
59
MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs
Normal file
59
MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user