From 126da94020385952cbba08dad76a575452b76aa8 Mon Sep 17 00:00:00 2001 From: cvium Date: Mon, 25 Jul 2022 09:47:21 +0200 Subject: [PATCH] use reflection to get all subtitle formats without causing libse configuration loading --- .../ApplicationHost.cs | 4 +- .../Subtitles/ISubtitleParser.cs | 7 ++ .../Subtitles/SubtitleEditParser.cs | 118 +++++++++++++++++- .../Subtitles/SubtitleEncoder.cs | 29 ++--- 4 files changed, 131 insertions(+), 27 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 32289625fe..2f47d43984 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -83,6 +83,7 @@ using MediaBrowser.Controller.SyncPlay; 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; @@ -634,7 +635,8 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); diff --git a/MediaBrowser.MediaEncoding/Subtitles/ISubtitleParser.cs b/MediaBrowser.MediaEncoding/Subtitles/ISubtitleParser.cs index 66d1776a48..bd13437fb6 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/ISubtitleParser.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/ISubtitleParser.cs @@ -14,5 +14,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles /// The file extension. /// SubtitleTrackInfo. SubtitleTrackInfo Parse(Stream stream, string fileExtension); + + /// + /// Determines whether the file extension is supported by the parser. + /// + /// The file extension. + /// A value indicating whether the file extension is supported. + bool SupportsFileExtension(string fileExtension); } } diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs index 4e6403e986..c94242b4b9 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs @@ -1,12 +1,14 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +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 @@ -16,15 +18,20 @@ namespace MediaBrowser.MediaEncoding.Subtitles /// public class SubtitleEditParser : ISubtitleParser { - private readonly ILogger _logger; + private readonly ILogger _logger; + private readonly Dictionary _subtitleFormats; /// /// Initializes a new instance of the class. /// /// The logger. - public SubtitleEditParser(ILogger logger) + public SubtitleEditParser(ILogger 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); } /// @@ -33,16 +40,28 @@ namespace MediaBrowser.MediaEncoding.Subtitles var subtitle = new Subtitle(); var lines = stream.ReadAllLines().ToList(); - var subtitleFormats = SubtitleFormat.AllSubtitleFormats.Where(asf => asf.Extension.Equals(fileExtension, StringComparison.OrdinalIgnoreCase)); + if (!_subtitleFormats.TryGetValue(fileExtension, out var subtitleFormats)) + { + throw new ArgumentException($"Unsupported file extension: {fileExtension}", nameof(fileExtension)); + } + foreach (var subtitleFormat in subtitleFormats) { + _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 subtitle", subtitleFormat.ErrorCount); + _logger.LogError( + "{ErrorCount} errors encountered while parsing '{FileExtension}' subtitle using the {SubtitleFormatParser} format parser", + subtitleFormat.ErrorCount, + fileExtension, + subtitleFormat.Name); } if (subtitle.Paragraphs.Count == 0) @@ -66,5 +85,94 @@ namespace MediaBrowser.MediaEncoding.Subtitles trackInfo.TrackEvents = trackEvents; return trackInfo; } + + /// + public bool SupportsFileExtension(string fileExtension) + => _subtitleFormats.ContainsKey(fileExtension); + + private IEnumerable GetSubtitleFormats() + { + var subtitleFormats = new List(); + var assembly = Assembly.GetAssembly(typeof(SubtitleFormat)); + if (assembly == null) + { + _logger.LogError("Missing assembly containing {SubtitleFormatName}", nameof(SubtitleFormat)); + return GetFallbackSubtitleFormats(); + } + + foreach (var type in assembly.GetTypes()) + { + if (!type.IsSubclassOf(typeof(SubtitleFormat))) + { + 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; + } + + private static IEnumerable GetFallbackSubtitleFormats() + => new SubtitleFormat[] + { + // Preferred and likely more common formats + new SubRip(), + new WebVTT(), + new WebVTTFileWithLineNumber(), + new AdvancedSubStationAlpha(), + new SubStationAlpha(), + new Sami(), + new SamiAvDicPlayer(), + new SamiModern(), + new SamiYouTube(), + new DvdSubtitle(), + new DvdSubtitleSystem(), + new JsonAeneas(), + new JsonTed(), + new Json(), + new JsonType2(), + new JsonType3(), + new JsonType4(), + new JsonType5(), + new JsonType6(), + new JsonType7(), + new JsonType8(), + new JsonType8b(), + new JsonType9(), + new JsonType10(), + new JsonType11(), + new JsonType12(), + new JsonType13(), + new JsonType14(), + new JsonType15(), + new JsonType16(), + new JsonType17(), + new JsonType18(), + new JsonType19(), + new JsonType20(), + new ItunesTimedText(), + new FLVCoreCuePoints(), + new Csv(), + new Csv2(), + new Csv3(), + new Csv4(), + new Csv5(), + new Ebu(), + new NetflixImsc11Japanese(), + new NetflixTimedText(), + new QuickTimeText(), + new RealTime(), + new SmpteTt2052() + }; } } diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index a0709d1e7b..50c4d92103 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -35,6 +35,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles private readonly IMediaEncoder _mediaEncoder; private readonly IHttpClientFactory _httpClientFactory; private readonly IMediaSourceManager _mediaSourceManager; + private readonly ISubtitleParser _subtitleParser; /// /// The _semaphoreLocks. @@ -48,7 +49,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles IFileSystem fileSystem, IMediaEncoder mediaEncoder, IHttpClientFactory httpClientFactory, - IMediaSourceManager mediaSourceManager) + IMediaSourceManager mediaSourceManager, + ISubtitleParser subtitleParser) { _logger = logger; _appPaths = appPaths; @@ -56,6 +58,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles _mediaEncoder = mediaEncoder; _httpClientFactory = httpClientFactory; _mediaSourceManager = mediaSourceManager; + _subtitleParser = subtitleParser; } private string SubtitleCachePath => Path.Combine(_appPaths.DataPath, "subtitles"); @@ -73,8 +76,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles try { - var reader = GetReader(inputFormat); - var trackInfo = reader.Parse(stream, $".{inputFormat}"); + var trackInfo = _subtitleParser.Parse(stream, inputFormat); FilterEvents(trackInfo, startTimeTicks, endTimeTicks, preserveOriginalTimestamps); @@ -233,7 +235,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles 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"); @@ -243,26 +246,10 @@ namespace MediaBrowser.MediaEncoding.Subtitles 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) - { - value = new SubtitleEditParser(_logger); - return true; - } - - 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))