Skip to content

Commit

Permalink
Backport pull request #8087 from jellyfin/release-10.8.z
Browse files Browse the repository at this point in the history
feat: make subtitleeditparser generic

Authored-by: Claus Vium <cvium@users.noreply.github.com>

Merged-by: Bond-009 <bond.009@outlook.com>

Original-merge: 7323ccf
  • Loading branch information
joshuaboniface committed Aug 1, 2022
1 parent 3e24b89 commit 38eefbb
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 119 deletions.
4 changes: 3 additions & 1 deletion Emby.Server.Implementations/ApplicationHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
using MediaBrowser.Controller.TV;
using MediaBrowser.LocalMetadata.Savers;
using MediaBrowser.MediaEncoding.BdInfo;
using MediaBrowser.MediaEncoding.Subtitles;
using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Globalization;
Expand Down Expand Up @@ -634,7 +635,8 @@ protected virtual void RegisterServices(IServiceCollection serviceCollection)
serviceCollection.AddSingleton<IAuthService, AuthService>();
serviceCollection.AddSingleton<IQuickConnect, QuickConnectManager>();

serviceCollection.AddSingleton<ISubtitleEncoder, MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>();
serviceCollection.AddSingleton<ISubtitleParser, SubtitleEditParser>();
serviceCollection.AddSingleton<ISubtitleEncoder, SubtitleEncoder>();

serviceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>();

Expand Down
19 changes: 0 additions & 19 deletions MediaBrowser.MediaEncoding/Subtitles/AssParser.cs

This file was deleted.

12 changes: 9 additions & 3 deletions MediaBrowser.MediaEncoding/Subtitles/ISubtitleParser.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#pragma warning disable CS1591

using System.IO;
using System.Threading;
using MediaBrowser.Model.MediaInfo;

namespace MediaBrowser.MediaEncoding.Subtitles
Expand All @@ -12,8 +11,15 @@ public interface ISubtitleParser
/// Parses the specified stream.
/// </summary>
/// <param name="stream">The stream.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="fileExtension">The file extension.</param>
/// <returns>SubtitleTrackInfo.</returns>
SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken);
SubtitleTrackInfo Parse(Stream stream, string fileExtension);

/// <summary>
/// Determines whether the file extension is supported by the parser.
/// </summary>
/// <param name="fileExtension">The file extension.</param>
/// <returns>A value indicating whether the file extension is supported.</returns>
bool SupportsFileExtension(string fileExtension);
}
}
19 changes: 0 additions & 19 deletions MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs

This file was deleted.

19 changes: 0 additions & 19 deletions MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs

This file was deleted.

85 changes: 72 additions & 13 deletions MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs
Original file line number Diff line number Diff line change
@@ -1,44 +1,72 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Reflection;
using Jellyfin.Extensions;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;
using Nikse.SubtitleEdit.Core.Common;
using ILogger = Microsoft.Extensions.Logging.ILogger;
using Nikse.SubtitleEdit.Core.SubtitleFormats;
using SubtitleFormat = Nikse.SubtitleEdit.Core.SubtitleFormats.SubtitleFormat;

namespace MediaBrowser.MediaEncoding.Subtitles
{
/// <summary>
/// SubStation Alpha subtitle parser.
/// </summary>
/// <typeparam name="T">The <see cref="SubtitleFormat" />.</typeparam>
public abstract class SubtitleEditParser<T> : ISubtitleParser
where T : SubtitleFormat, new()
public class SubtitleEditParser : ISubtitleParser
{
private readonly ILogger _logger;
private readonly ILogger<SubtitleEditParser> _logger;
private readonly Dictionary<string, SubtitleFormat[]> _subtitleFormats;

/// <summary>
/// Initializes a new instance of the <see cref="SubtitleEditParser{T}"/> class.
/// Initializes a new instance of the <see cref="SubtitleEditParser"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
protected SubtitleEditParser(ILogger logger)
public SubtitleEditParser(ILogger<SubtitleEditParser> logger)
{
_logger = logger;
_subtitleFormats = GetSubtitleFormats()
.Where(subtitleFormat => !string.IsNullOrEmpty(subtitleFormat.Extension))
.GroupBy(subtitleFormat => subtitleFormat.Extension.TrimStart('.'), StringComparer.OrdinalIgnoreCase)
.ToDictionary(g => g.Key, g => g.ToArray(), StringComparer.OrdinalIgnoreCase);
}

/// <inheritdoc />
public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken)
public SubtitleTrackInfo Parse(Stream stream, string fileExtension)
{
var subtitle = new Subtitle();
var subRip = new T();
var lines = stream.ReadAllLines().ToList();
subRip.LoadSubtitle(subtitle, lines, "untitled");
if (subRip.ErrorCount > 0)

if (!_subtitleFormats.TryGetValue(fileExtension, out var subtitleFormats))
{
throw new ArgumentException($"Unsupported file extension: {fileExtension}", nameof(fileExtension));
}

foreach (var subtitleFormat in subtitleFormats)
{
_logger.LogError("{ErrorCount} errors encountered while parsing subtitle", subRip.ErrorCount);
_logger.LogDebug(
"Trying to parse '{FileExtension}' subtitle using the {SubtitleFormatParser} format parser",
fileExtension,
subtitleFormat.Name);
subtitleFormat.LoadSubtitle(subtitle, lines, fileExtension);
if (subtitleFormat.ErrorCount == 0)
{
break;
}

_logger.LogError(
"{ErrorCount} errors encountered while parsing '{FileExtension}' subtitle using the {SubtitleFormatParser} format parser",
subtitleFormat.ErrorCount,
fileExtension,
subtitleFormat.Name);
}

if (subtitle.Paragraphs.Count == 0)
{
throw new ArgumentException("Unsupported format: " + fileExtension);
}

var trackInfo = new SubtitleTrackInfo();
Expand All @@ -57,5 +85,36 @@ public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToke
trackInfo.TrackEvents = trackEvents;
return trackInfo;
}

/// <inheritdoc />
public bool SupportsFileExtension(string fileExtension)
=> _subtitleFormats.ContainsKey(fileExtension);

private IEnumerable<SubtitleFormat> GetSubtitleFormats()
{
var subtitleFormats = new List<SubtitleFormat>();
var assembly = typeof(SubtitleFormat).Assembly;

foreach (var type in assembly.GetTypes())
{
if (!type.IsSubclassOf(typeof(SubtitleFormat)) || type.IsAbstract)
{
continue;
}

try
{
// It shouldn't be null, but the exception is caught if it is
var subtitleFormat = (SubtitleFormat)Activator.CreateInstance(type, true)!;
subtitleFormats.Add(subtitleFormat);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to create instance of the subtitle format {SubtitleFormatType}", type.Name);
}
}

return subtitleFormats;
}
}
}
47 changes: 8 additions & 39 deletions MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public sealed class SubtitleEncoder : ISubtitleEncoder
private readonly IMediaEncoder _mediaEncoder;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly ISubtitleParser _subtitleParser;

/// <summary>
/// The _semaphoreLocks.
Expand All @@ -48,14 +49,16 @@ public SubtitleEncoder(
IFileSystem fileSystem,
IMediaEncoder mediaEncoder,
IHttpClientFactory httpClientFactory,
IMediaSourceManager mediaSourceManager)
IMediaSourceManager mediaSourceManager,
ISubtitleParser subtitleParser)
{
_logger = logger;
_appPaths = appPaths;
_fileSystem = fileSystem;
_mediaEncoder = mediaEncoder;
_httpClientFactory = httpClientFactory;
_mediaSourceManager = mediaSourceManager;
_subtitleParser = subtitleParser;
}

private string SubtitleCachePath => Path.Combine(_appPaths.DataPath, "subtitles");
Expand All @@ -73,8 +76,7 @@ private Stream ConvertSubtitles(

try
{
var reader = GetReader(inputFormat);
var trackInfo = reader.Parse(stream, cancellationToken);
var trackInfo = _subtitleParser.Parse(stream, inputFormat);

FilterEvents(trackInfo, startTimeTicks, endTimeTicks, preserveOriginalTimestamps);

Expand Down Expand Up @@ -233,7 +235,8 @@ await ExtractTextSubtitle(mediaSource, subtitleStream, outputCodec, outputPath,
var currentFormat = (Path.GetExtension(subtitleStream.Path) ?? subtitleStream.Codec)
.TrimStart('.');

if (!TryGetReader(currentFormat, out _))
// Fallback to ffmpeg conversion
if (!_subtitleParser.SupportsFileExtension(currentFormat))
{
// Convert
var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, ".srt");
Expand All @@ -243,44 +246,10 @@ await ExtractTextSubtitle(mediaSource, subtitleStream, outputCodec, outputPath,
return new SubtitleInfo(outputPath, MediaProtocol.File, "srt", true);
}

// It's possbile that the subtitleStream and mediaSource don't share the same protocol (e.g. .STRM file with local subs)
// It's possible that the subtitleStream and mediaSource don't share the same protocol (e.g. .STRM file with local subs)
return new SubtitleInfo(subtitleStream.Path, _mediaSourceManager.GetPathProtocol(subtitleStream.Path), currentFormat, true);
}

private bool TryGetReader(string format, [NotNullWhen(true)] out ISubtitleParser? value)
{
if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase))
{
value = new SrtParser(_logger);
return true;
}

if (string.Equals(format, SubtitleFormat.SSA, StringComparison.OrdinalIgnoreCase))
{
value = new SsaParser(_logger);
return true;
}

if (string.Equals(format, SubtitleFormat.ASS, StringComparison.OrdinalIgnoreCase))
{
value = new AssParser(_logger);
return true;
}

value = null;
return false;
}

private ISubtitleParser GetReader(string format)
{
if (TryGetReader(format, out var reader))
{
return reader;
}

throw new ArgumentException("Unsupported format: " + format);
}

private bool TryGetWriter(string format, [NotNullWhen(true)] out ISubtitleWriter? value)
{
if (string.Equals(format, SubtitleFormat.ASS, StringComparison.OrdinalIgnoreCase))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public void Parse_Valid_Success()
{
using (var stream = File.OpenRead("Test Data/example.ass"))
{
var parsed = new AssParser(new NullLogger<AssParser>()).Parse(stream, CancellationToken.None);
var parsed = new SubtitleEditParser(new NullLogger<SubtitleEditParser>()).Parse(stream, "ass");
Assert.Single(parsed.TrackEvents);
var trackEvent = parsed.TrackEvents[0];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public void Parse_Valid_Success()
{
using (var stream = File.OpenRead("Test Data/example.srt"))
{
var parsed = new SrtParser(new NullLogger<SrtParser>()).Parse(stream, CancellationToken.None);
var parsed = new SubtitleEditParser(new NullLogger<SubtitleEditParser>()).Parse(stream, "srt");
Assert.Equal(2, parsed.TrackEvents.Count);

var trackEvent1 = parsed.TrackEvents[0];
Expand All @@ -37,7 +37,7 @@ public void Parse_EmptyNewlineBetweenText_Success()
{
using (var stream = File.OpenRead("Test Data/example2.srt"))
{
var parsed = new SrtParser(new NullLogger<SrtParser>()).Parse(stream, CancellationToken.None);
var parsed = new SubtitleEditParser(new NullLogger<SubtitleEditParser>()).Parse(stream, "srt");
Assert.Equal(2, parsed.TrackEvents.Count);

var trackEvent1 = parsed.TrackEvents[0];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ namespace Jellyfin.MediaEncoding.Subtitles.Tests
{
public class SsaParserTests
{
private readonly SsaParser _parser = new SsaParser(new NullLogger<AssParser>());
private readonly SubtitleEditParser _parser = new SubtitleEditParser(new NullLogger<SubtitleEditParser>());

[Theory]
[MemberData(nameof(Parse_MultipleDialogues_TestData))]
public void Parse_MultipleDialogues_Success(string ssa, IReadOnlyList<SubtitleTrackEvent> expectedSubtitleTrackEvents)
{
using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(ssa)))
{
SubtitleTrackInfo subtitleTrackInfo = _parser.Parse(stream, CancellationToken.None);
SubtitleTrackInfo subtitleTrackInfo = _parser.Parse(stream, "ssa");

Assert.Equal(expectedSubtitleTrackEvents.Count, subtitleTrackInfo.TrackEvents.Count);

Expand Down Expand Up @@ -76,7 +76,7 @@ public void Parse_Valid_Success()
{
using (var stream = File.OpenRead("Test Data/example.ssa"))
{
var parsed = _parser.Parse(stream, CancellationToken.None);
var parsed = _parser.Parse(stream, "ssa");
Assert.Single(parsed.TrackEvents);
var trackEvent = parsed.TrackEvents[0];

Expand Down

0 comments on commit 38eefbb

Please sign in to comment.