diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 8c625539ab..aed0c14a29 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -841,16 +841,14 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(ChapterManager); MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder( - LoggerFactory, - JsonSerializer, - StartupOptions.FFmpegPath, + LoggerFactory.CreateLogger(), ServerConfigurationManager, FileSystemManager, - () => SubtitleEncoder, - () => MediaSourceManager, ProcessFactory, - 5000, - LocalizationManager); + LocalizationManager, + () => SubtitleEncoder, + _configuration, + StartupOptions.FFmpegPath); serviceCollection.AddSingleton(MediaEncoder); EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager); @@ -867,10 +865,19 @@ namespace Emby.Server.Implementations AuthService = new AuthService(LoggerFactory.CreateLogger(), authContext, ServerConfigurationManager, SessionManager, NetworkManager); serviceCollection.AddSingleton(AuthService); - SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, ProcessFactory); + SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder( + LibraryManager, + LoggerFactory.CreateLogger(), + ApplicationPaths, + FileSystemManager, + MediaEncoder, + HttpClient, + MediaSourceManager, + ProcessFactory); serviceCollection.AddSingleton(SubtitleEncoder); serviceCollection.AddSingleton(typeof(IResourceFileManager), typeof(ResourceFileManager)); + serviceCollection.AddSingleton(); _displayPreferencesRepository.Initialize(); diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index 62408ee703..2ea7ff6e91 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -1,13 +1,16 @@ using System.Collections.Generic; +using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; namespace Emby.Server.Implementations { public static class ConfigurationOptions { - public static readonly Dictionary Configuration = new Dictionary + public static Dictionary Configuration => new Dictionary { { "HttpListenerHost:DefaultRedirectPath", "web/index.html" }, - { "MusicBrainz:BaseUrl", "https://www.musicbrainz.org" } + { "MusicBrainz:BaseUrl", "https://www.musicbrainz.org" }, + { FfmpegProbeSizeKey, "1G" }, + { FfmpegAnalyzeDurationKey, "200M" } }; } } diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 4b6ba1fabb..115e07d576 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -29,7 +29,6 @@ - diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 5ac005b40b..712990a1ee 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -7,6 +7,7 @@ using System.Net; using System.Net.Security; using System.Reflection; using System.Runtime.InteropServices; +using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -145,6 +146,10 @@ namespace Jellyfin.Server ApplicationHost.LogEnvironmentInfo(_logger, appPaths); + // Make sure we have all the code pages we can get + // Ref: https://docs.microsoft.com/en-us/dotnet/api/system.text.codepagesencodingprovider.instance?view=netcore-3.0#remarks + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + // Increase the max http request limit // The default connection limit is 10 for ASP.NET hosted applications and 2 for all others. ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit); @@ -456,9 +461,9 @@ namespace Jellyfin.Server return new ConfigurationBuilder() .SetBasePath(appPaths.ConfigurationDirectoryPath) + .AddInMemoryCollection(ConfigurationOptions.Configuration) .AddJsonFile("logging.json", false, true) .AddEnvironmentVariables("JELLYFIN_") - .AddInMemoryCollection(ConfigurationOptions.Configuration) .Build(); } diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 1e9cd33136..1c83e1cbb9 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -63,8 +63,6 @@ namespace MediaBrowser.Api.Playback protected IDeviceManager DeviceManager { get; private set; } - protected ISubtitleEncoder SubtitleEncoder { get; private set; } - protected IMediaSourceManager MediaSourceManager { get; private set; } protected IJsonSerializer JsonSerializer { get; private set; } @@ -92,11 +90,11 @@ namespace MediaBrowser.Api.Playback IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, - ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IJsonSerializer jsonSerializer, - IAuthorizationContext authorizationContext) + IAuthorizationContext authorizationContext, + EncodingHelper encodingHelper) : base(logger, serverConfigurationManager, httpResultFactory) { UserManager = userManager; @@ -105,13 +103,12 @@ namespace MediaBrowser.Api.Playback MediaEncoder = mediaEncoder; FileSystem = fileSystem; DlnaManager = dlnaManager; - SubtitleEncoder = subtitleEncoder; DeviceManager = deviceManager; MediaSourceManager = mediaSourceManager; JsonSerializer = jsonSerializer; AuthorizationContext = authorizationContext; - EncodingHelper = new EncodingHelper(MediaEncoder, FileSystem, SubtitleEncoder); + EncodingHelper = encodingHelper; } /// @@ -148,8 +145,6 @@ namespace MediaBrowser.Api.Playback return Path.Combine(folder, filename + ext); } - protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); - protected virtual string GetDefaultEncoderPreset() { return "superfast"; diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 8fdc6fa497..5d0dc98ddc 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -34,26 +34,26 @@ namespace MediaBrowser.Api.Playback.Hls IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, - ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IJsonSerializer jsonSerializer, - IAuthorizationContext authorizationContext) - : base( - logger, - serverConfigurationManager, - httpResultFactory, - userManager, - libraryManager, - isoManager, - mediaEncoder, - fileSystem, - dlnaManager, - subtitleEncoder, - deviceManager, - mediaSourceManager, - jsonSerializer, - authorizationContext) + IAuthorizationContext authorizationContext, + EncodingHelper encodingHelper) + : base( + logger, + serverConfigurationManager, + httpResultFactory, + userManager, + libraryManager, + isoManager, + mediaEncoder, + fileSystem, + dlnaManager, + deviceManager, + mediaSourceManager, + jsonSerializer, + authorizationContext, + encodingHelper) { } diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 56758db963..0178f53af6 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -104,12 +104,12 @@ namespace MediaBrowser.Api.Playback.Hls IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, - ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext, - INetworkManager networkManager) + INetworkManager networkManager, + EncodingHelper encodingHelper) : base( logger, serverConfigurationManager, @@ -120,11 +120,11 @@ namespace MediaBrowser.Api.Playback.Hls mediaEncoder, fileSystem, dlnaManager, - subtitleEncoder, deviceManager, mediaSourceManager, jsonSerializer, - authorizationContext) + authorizationContext, + encodingHelper) { NetworkManager = networkManager; } diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index 6d12a1ccd6..d1c53c1c11 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -27,6 +27,39 @@ namespace MediaBrowser.Api.Playback.Hls [Authenticated] public class VideoHlsService : BaseHlsService { + public VideoHlsService( + ILogger logger, + IServerConfigurationManager serverConfigurationManager, + IHttpResultFactory httpResultFactory, + IUserManager userManager, + ILibraryManager libraryManager, + IIsoManager isoManager, + IMediaEncoder mediaEncoder, + IFileSystem fileSystem, + IDlnaManager dlnaManager, + IDeviceManager deviceManager, + IMediaSourceManager mediaSourceManager, + IJsonSerializer jsonSerializer, + IAuthorizationContext authorizationContext, + EncodingHelper encodingHelper) + : base( + logger, + serverConfigurationManager, + httpResultFactory, + userManager, + libraryManager, + isoManager, + mediaEncoder, + fileSystem, + dlnaManager, + deviceManager, + mediaSourceManager, + jsonSerializer, + authorizationContext, + encodingHelper) + { + } + public Task Get(GetLiveHlsStream request) { return ProcessRequestAsync(request, true); @@ -136,38 +169,5 @@ namespace MediaBrowser.Api.Playback.Hls return args; } - - public VideoHlsService( - ILogger logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IUserManager userManager, - ILibraryManager libraryManager, - IIsoManager isoManager, - IMediaEncoder mediaEncoder, - IFileSystem fileSystem, - IDlnaManager dlnaManager, - ISubtitleEncoder subtitleEncoder, - IDeviceManager deviceManager, - IMediaSourceManager mediaSourceManager, - IJsonSerializer jsonSerializer, - IAuthorizationContext authorizationContext) - : base( - logger, - serverConfigurationManager, - httpResultFactory, - userManager, - libraryManager, - isoManager, - mediaEncoder, - fileSystem, - dlnaManager, - subtitleEncoder, - deviceManager, - mediaSourceManager, - jsonSerializer, - authorizationContext) - { - } } } diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs index 11527007b5..8d1e3a3f23 100644 --- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs +++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs @@ -43,11 +43,11 @@ namespace MediaBrowser.Api.Playback.Progressive IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, - ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IJsonSerializer jsonSerializer, - IAuthorizationContext authorizationContext) + IAuthorizationContext authorizationContext, + EncodingHelper encodingHelper) : base( logger, serverConfigurationManager, @@ -59,11 +59,11 @@ namespace MediaBrowser.Api.Playback.Progressive mediaEncoder, fileSystem, dlnaManager, - subtitleEncoder, deviceManager, mediaSourceManager, jsonSerializer, - authorizationContext) + authorizationContext, + encodingHelper) { } diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 4ada90d095..ed30dbba61 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -38,11 +38,11 @@ namespace MediaBrowser.Api.Playback.Progressive IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, - ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IJsonSerializer jsonSerializer, - IAuthorizationContext authorizationContext) + IAuthorizationContext authorizationContext, + EncodingHelper encodingHelper) : base( logger, serverConfigurationManager, @@ -53,11 +53,11 @@ namespace MediaBrowser.Api.Playback.Progressive mediaEncoder, fileSystem, dlnaManager, - subtitleEncoder, deviceManager, mediaSourceManager, jsonSerializer, - authorizationContext) + authorizationContext, + encodingHelper) { HttpClient = httpClient; } diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index 56ec86cd47..4de81655ce 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -80,11 +80,11 @@ namespace MediaBrowser.Api.Playback.Progressive IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, - ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IJsonSerializer jsonSerializer, - IAuthorizationContext authorizationContext) + IAuthorizationContext authorizationContext, + EncodingHelper encodingHelper) : base( logger, serverConfigurationManager, @@ -96,11 +96,11 @@ namespace MediaBrowser.Api.Playback.Progressive mediaEncoder, fileSystem, dlnaManager, - subtitleEncoder, deviceManager, mediaSourceManager, jsonSerializer, - authorizationContext) + authorizationContext, + encodingHelper) { } diff --git a/MediaBrowser.Api/Playback/UniversalAudioService.cs b/MediaBrowser.Api/Playback/UniversalAudioService.cs index b1450e2cc8..9cba9df139 100644 --- a/MediaBrowser.Api/Playback/UniversalAudioService.cs +++ b/MediaBrowser.Api/Playback/UniversalAudioService.cs @@ -9,7 +9,6 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Net; @@ -75,6 +74,9 @@ namespace MediaBrowser.Api.Playback [Authenticated] public class UniversalAudioService : BaseApiService { + private readonly ILoggerFactory _loggerFactory; + private readonly EncodingHelper _encodingHelper; + public UniversalAudioService( ILogger logger, IServerConfigurationManager serverConfigurationManager, @@ -87,11 +89,11 @@ namespace MediaBrowser.Api.Playback IFileSystem fileSystem, IDlnaManager dlnaManager, IDeviceManager deviceManager, - ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext, - INetworkManager networkManager) + INetworkManager networkManager, + EncodingHelper encodingHelper) : base(logger, serverConfigurationManager, httpResultFactory) { HttpClient = httpClient; @@ -102,11 +104,11 @@ namespace MediaBrowser.Api.Playback FileSystem = fileSystem; DlnaManager = dlnaManager; DeviceManager = deviceManager; - SubtitleEncoder = subtitleEncoder; MediaSourceManager = mediaSourceManager; JsonSerializer = jsonSerializer; AuthorizationContext = authorizationContext; NetworkManager = networkManager; + _encodingHelper = encodingHelper; } protected IHttpClient HttpClient { get; private set; } @@ -117,7 +119,6 @@ namespace MediaBrowser.Api.Playback protected IFileSystem FileSystem { get; private set; } protected IDlnaManager DlnaManager { get; private set; } protected IDeviceManager DeviceManager { get; private set; } - protected ISubtitleEncoder SubtitleEncoder { get; private set; } protected IMediaSourceManager MediaSourceManager { get; private set; } protected IJsonSerializer JsonSerializer { get; private set; } protected IAuthorizationContext AuthorizationContext { get; private set; } @@ -287,12 +288,12 @@ namespace MediaBrowser.Api.Playback MediaEncoder, FileSystem, DlnaManager, - SubtitleEncoder, DeviceManager, MediaSourceManager, JsonSerializer, AuthorizationContext, - NetworkManager) + NetworkManager, + _encodingHelper) { Request = Request }; @@ -337,11 +338,11 @@ namespace MediaBrowser.Api.Playback MediaEncoder, FileSystem, DlnaManager, - SubtitleEncoder, DeviceManager, MediaSourceManager, JsonSerializer, - AuthorizationContext) + AuthorizationContext, + _encodingHelper) { Request = Request }; diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 60906bdb08..af4d227bc8 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -137,7 +137,7 @@ namespace MediaBrowser.Controller.Entities /// The video3 D format. public Video3DFormat? Video3DFormat { get; set; } - public string[] GetPlayableStreamFileNames(IMediaEncoder mediaEncoder) + public string[] GetPlayableStreamFileNames() { var videoType = VideoType; @@ -153,7 +153,8 @@ namespace MediaBrowser.Controller.Entities { return Array.Empty(); } - return mediaEncoder.GetPlayableStreamFileNames(Path, videoType); + + throw new NotImplementedException(); } /// diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs new file mode 100644 index 0000000000..76c9b4b26c --- /dev/null +++ b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs @@ -0,0 +1,36 @@ +using Microsoft.Extensions.Configuration; + +namespace MediaBrowser.Controller.Extensions +{ + /// + /// Configuration extensions for MediaBrowser.Controller. + /// + public static class ConfigurationExtensions + { + /// + /// The key for the FFmpeg probe size option. + /// + public const string FfmpegProbeSizeKey = "FFmpeg:probesize"; + + /// + /// The key for the FFmpeg analyse duration option. + /// + public const string FfmpegAnalyzeDurationKey = "FFmpeg:analyzeduration"; + + /// + /// Retrieves the FFmpeg probe size from the . + /// + /// This configuration. + /// The FFmpeg probe size option. + public static string GetFFmpegProbeSize(this IConfiguration configuration) + => configuration[FfmpegProbeSizeKey]; + + /// + /// Retrieves the FFmpeg analyse duration from the . + /// + /// This configuration. + /// The FFmpeg analyse duration option. + public static string GetFFmpegAnalyzeDuration(this IConfiguration configuration) + => configuration[FfmpegAnalyzeDurationKey]; + } +} diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 276eb71bcf..60c76ef7db 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -7,6 +7,10 @@ https://github.com/jellyfin/jellyfin + + + + diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 897e22018c..020f0553ed 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -6,12 +6,14 @@ using System.Linq; using System.Text; using System.Threading; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Extensions; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; +using Microsoft.Extensions.Configuration; namespace MediaBrowser.Controller.MediaEncoding { @@ -22,6 +24,7 @@ namespace MediaBrowser.Controller.MediaEncoding private readonly IMediaEncoder _mediaEncoder; private readonly IFileSystem _fileSystem; private readonly ISubtitleEncoder _subtitleEncoder; + private readonly IConfiguration _configuration; private static readonly string[] _videoProfiles = new[] { @@ -34,11 +37,16 @@ namespace MediaBrowser.Controller.MediaEncoding "ConstrainedHigh" }; - public EncodingHelper(IMediaEncoder mediaEncoder, IFileSystem fileSystem, ISubtitleEncoder subtitleEncoder) + public EncodingHelper( + IMediaEncoder mediaEncoder, + IFileSystem fileSystem, + ISubtitleEncoder subtitleEncoder, + IConfiguration configuration) { _mediaEncoder = mediaEncoder; _fileSystem = fileSystem; _subtitleEncoder = subtitleEncoder; + _configuration = configuration; } public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions) @@ -172,7 +180,7 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Empty; } - public string GetInputFormat(string container) + public static string GetInputFormat(string container) { if (string.IsNullOrEmpty(container)) { @@ -662,7 +670,11 @@ namespace MediaBrowser.Controller.MediaEncoding if (!string.IsNullOrEmpty(state.SubtitleStream.Language)) { - var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.SubtitleStream.Language, state.MediaSource.Protocol, CancellationToken.None).Result; + var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet( + subtitlePath, + state.SubtitleStream.Language, + state.MediaSource.Protocol, + CancellationToken.None).GetAwaiter().GetResult(); if (!string.IsNullOrEmpty(charenc)) { @@ -1948,7 +1960,7 @@ namespace MediaBrowser.Controller.MediaEncoding // If transcoding from 10 bit, transform colour spaces too if (!string.IsNullOrEmpty(videoStream.PixelFormat) && videoStream.PixelFormat.IndexOf("p10", StringComparison.OrdinalIgnoreCase) != -1 - && string.Equals(outputVideoCodec,"libx264", StringComparison.OrdinalIgnoreCase)) + && string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase)) { filters.Add("format=p010le"); filters.Add("format=nv12"); @@ -2011,7 +2023,9 @@ namespace MediaBrowser.Controller.MediaEncoding var output = string.Empty; - if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) + if (state.SubtitleStream != null + && state.SubtitleStream.IsTextSubtitleStream + && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) { var subParam = GetTextSubtitleParam(state); @@ -2100,11 +2114,11 @@ namespace MediaBrowser.Controller.MediaEncoding } } - public static string GetProbeSizeArgument(int numInputFiles) - => numInputFiles > 1 ? "-probesize 1G" : ""; + public string GetProbeSizeArgument(int numInputFiles) + => numInputFiles > 1 ? "-probesize " + _configuration.GetFFmpegProbeSize() : string.Empty; - public static string GetAnalyzeDurationArgument(int numInputFiles) - => numInputFiles > 1 ? "-analyzeduration 200M" : ""; + public string GetAnalyzeDurationArgument(int numInputFiles) + => numInputFiles > 1 ? "-analyzeduration " + _configuration.GetFFmpegAnalyzeDuration() : string.Empty; public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions) { diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index d032a849e7..37f0b11a74 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -15,6 +15,9 @@ namespace MediaBrowser.Controller.MediaEncoding /// public interface IMediaEncoder : ITranscoderSupport { + /// + /// The location of the discovered FFmpeg tool. + /// FFmpegLocation EncoderLocation { get; } /// @@ -97,7 +100,6 @@ namespace MediaBrowser.Controller.MediaEncoding void UpdateEncoderPath(string path, string pathType); bool SupportsEncoder(string encoder); - string[] GetPlayableStreamFileNames(string path, VideoType videoType); IEnumerable GetPrimaryPlaylistVobFiles(string path, IIsoMount isoMount, uint? titleNumber); } } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 04ff66991d..6bcd6cd46a 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -3,13 +3,13 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Text.Json; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.MediaEncoding.Probing; using MediaBrowser.Model.Configuration; @@ -19,9 +19,9 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Serialization; using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Configuration; namespace MediaBrowser.MediaEncoding.Encoder { @@ -31,55 +31,60 @@ namespace MediaBrowser.MediaEncoding.Encoder public class MediaEncoder : IMediaEncoder, IDisposable { /// - /// Gets the encoder path. + /// The default image extraction timeout in milliseconds. /// - /// The encoder path. - public string EncoderPath => FFmpegPath; - - /// - /// The location of the discovered FFmpeg tool. - /// - public FFmpegLocation EncoderLocation { get; private set; } + internal const int DefaultImageExtractionTimeout = 5000; private readonly ILogger _logger; - private readonly IJsonSerializer _jsonSerializer; - private string FFmpegPath; - private string FFprobePath; - protected readonly IServerConfigurationManager ConfigurationManager; - protected readonly IFileSystem FileSystem; - protected readonly Func SubtitleEncoder; - protected readonly Func MediaSourceManager; + private readonly IServerConfigurationManager _configurationManager; + private readonly IFileSystem _fileSystem; private readonly IProcessFactory _processFactory; - private readonly int DefaultImageExtractionTimeoutMs; - private readonly string StartupOptionFFmpegPath; + private readonly ILocalizationManager _localization; + private readonly Func _subtitleEncoder; + private readonly IConfiguration _configuration; + private readonly string _startupOptionFFmpegPath; private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(2, 2); + + private readonly object _runningProcessesLock = new object(); private readonly List _runningProcesses = new List(); - private readonly ILocalizationManager _localization; + + private EncodingHelper _encodingHelper; + + private string _ffmpegPath; + private string _ffprobePath; public MediaEncoder( - ILoggerFactory loggerFactory, - IJsonSerializer jsonSerializer, - string startupOptionsFFmpegPath, + ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, - Func subtitleEncoder, - Func mediaSourceManager, IProcessFactory processFactory, - int defaultImageExtractionTimeoutMs, - ILocalizationManager localization) - { - _logger = loggerFactory.CreateLogger(nameof(MediaEncoder)); - _jsonSerializer = jsonSerializer; - StartupOptionFFmpegPath = startupOptionsFFmpegPath; - ConfigurationManager = configurationManager; - FileSystem = fileSystem; - SubtitleEncoder = subtitleEncoder; + ILocalizationManager localization, + Func subtitleEncoder, + IConfiguration configuration, + string startupOptionsFFmpegPath) + { + _logger = logger; + _configurationManager = configurationManager; + _fileSystem = fileSystem; _processFactory = processFactory; - DefaultImageExtractionTimeoutMs = defaultImageExtractionTimeoutMs; _localization = localization; + _startupOptionFFmpegPath = startupOptionsFFmpegPath; + _subtitleEncoder = subtitleEncoder; + _configuration = configuration; } + private EncodingHelper EncodingHelper + => LazyInitializer.EnsureInitialized( + ref _encodingHelper, + () => new EncodingHelper(this, _fileSystem, _subtitleEncoder(), _configuration)); + + /// + public string EncoderPath => _ffmpegPath; + + /// + public FFmpegLocation EncoderLocation { get; private set; } + /// /// Run at startup or if the user removes a Custom path from transcode page. /// Sets global variables FFmpegPath. @@ -88,39 +93,39 @@ namespace MediaBrowser.MediaEncoding.Encoder public void SetFFmpegPath() { // 1) Custom path stored in config/encoding xml file under tag takes precedence - if (!ValidatePath(ConfigurationManager.GetConfiguration("encoding").EncoderAppPath, FFmpegLocation.Custom)) + if (!ValidatePath(_configurationManager.GetConfiguration("encoding").EncoderAppPath, FFmpegLocation.Custom)) { // 2) Check if the --ffmpeg CLI switch has been given - if (!ValidatePath(StartupOptionFFmpegPath, FFmpegLocation.SetByArgument)) + if (!ValidatePath(_startupOptionFFmpegPath, FFmpegLocation.SetByArgument)) { // 3) Search system $PATH environment variable for valid FFmpeg if (!ValidatePath(ExistsOnSystemPath("ffmpeg"), FFmpegLocation.System)) { EncoderLocation = FFmpegLocation.NotFound; - FFmpegPath = null; + _ffmpegPath = null; } } } // Write the FFmpeg path to the config/encoding.xml file as so it appears in UI - var config = ConfigurationManager.GetConfiguration("encoding"); - config.EncoderAppPathDisplay = FFmpegPath ?? string.Empty; - ConfigurationManager.SaveConfiguration("encoding", config); + var config = _configurationManager.GetConfiguration("encoding"); + config.EncoderAppPathDisplay = _ffmpegPath ?? string.Empty; + _configurationManager.SaveConfiguration("encoding", config); // Only if mpeg path is set, try and set path to probe - if (FFmpegPath != null) + if (_ffmpegPath != null) { // Determine a probe path from the mpeg path - FFprobePath = Regex.Replace(FFmpegPath, @"[^\/\\]+?(\.[^\/\\\n.]+)?$", @"ffprobe$1"); + _ffprobePath = Regex.Replace(_ffmpegPath, @"[^\/\\]+?(\.[^\/\\\n.]+)?$", @"ffprobe$1"); // Interrogate to understand what coders are supported - var validator = new EncoderValidator(_logger, FFmpegPath); + var validator = new EncoderValidator(_logger, _ffmpegPath); SetAvailableDecoders(validator.GetDecoders()); SetAvailableEncoders(validator.GetEncoders()); } - _logger.LogInformation("FFmpeg: {0}: {1}", EncoderLocation, FFmpegPath ?? string.Empty); + _logger.LogInformation("FFmpeg: {0}: {1}", EncoderLocation, _ffmpegPath ?? string.Empty); } /// @@ -160,9 +165,9 @@ namespace MediaBrowser.MediaEncoding.Encoder // Write the new ffmpeg path to the xml as // This ensures its not lost on next startup - var config = ConfigurationManager.GetConfiguration("encoding"); + var config = _configurationManager.GetConfiguration("encoding"); config.EncoderAppPath = newPath; - ConfigurationManager.SaveConfiguration("encoding", config); + _configurationManager.SaveConfiguration("encoding", config); // Trigger SetFFmpegPath so we validate the new path and setup probe path SetFFmpegPath(); @@ -193,7 +198,7 @@ namespace MediaBrowser.MediaEncoding.Encoder // ToDo - Enable the ffmpeg validator. At the moment any version can be used. rc = true; - FFmpegPath = path; + _ffmpegPath = path; EncoderLocation = location; } else @@ -209,7 +214,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { try { - var files = FileSystem.GetFilePaths(path); + var files = _fileSystem.GetFilePaths(path); var excludeExtensions = new[] { ".c" }; @@ -304,7 +309,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { var extractChapters = request.MediaType == DlnaProfileType.Video && request.ExtractChapters; - var inputFiles = MediaEncoderHelpers.GetInputArgument(FileSystem, request.MediaSource.Path, request.MountedIso, request.PlayableStreamFileNames); + var inputFiles = MediaEncoderHelpers.GetInputArgument(_fileSystem, request.MediaSource.Path, request.MountedIso, request.PlayableStreamFileNames); var probeSize = EncodingHelper.GetProbeSizeArgument(inputFiles.Length); string analyzeDuration; @@ -365,7 +370,7 @@ namespace MediaBrowser.MediaEncoding.Encoder // Must consume both or ffmpeg may hang due to deadlocks. See comments below. RedirectStandardOutput = true, - FileName = FFprobePath, + FileName = _ffprobePath, Arguments = args, @@ -383,7 +388,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _logger.LogDebug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); } - using (var processWrapper = new ProcessWrapper(process, this, _logger)) + using (var processWrapper = new ProcessWrapper(process, this)) { _logger.LogDebug("Starting ffprobe with args {Args}", args); StartProcess(processWrapper); @@ -391,7 +396,7 @@ namespace MediaBrowser.MediaEncoding.Encoder InternalMediaInfoResult result; try { - result = await _jsonSerializer.DeserializeFromStreamAsync( + result = await JsonSerializer.DeserializeAsync( process.StandardOutput.BaseStream).ConfigureAwait(false); } catch @@ -423,7 +428,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - return new ProbeResultNormalizer(_logger, FileSystem, _localization).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol); + return new ProbeResultNormalizer(_logger, _fileSystem, _localization).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol); } } @@ -486,7 +491,7 @@ namespace MediaBrowser.MediaEncoding.Encoder throw new ArgumentNullException(nameof(inputPath)); } - var tempExtractPath = Path.Combine(ConfigurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + ".jpg"); + var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + ".jpg"); Directory.CreateDirectory(Path.GetDirectoryName(tempExtractPath)); // apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar then scale to width 600. @@ -545,7 +550,6 @@ namespace MediaBrowser.MediaEncoding.Encoder args = string.Format("-ss {0} ", GetTimeParameter(offset.Value)) + args; } - var encodinghelper = new EncodingHelper(this, FileSystem, SubtitleEncoder()); if (videoStream != null) { /* fix @@ -559,7 +563,7 @@ namespace MediaBrowser.MediaEncoding.Encoder if (!string.IsNullOrWhiteSpace(container)) { - var inputFormat = encodinghelper.GetInputFormat(container); + var inputFormat = EncodingHelper.GetInputFormat(container); if (!string.IsNullOrWhiteSpace(inputFormat)) { args = "-f " + inputFormat + " " + args; @@ -570,7 +574,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { CreateNoWindow = true, UseShellExecute = false, - FileName = FFmpegPath, + FileName = _ffmpegPath, Arguments = args, IsHidden = true, ErrorDialog = false, @@ -579,7 +583,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _logger.LogDebug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); - using (var processWrapper = new ProcessWrapper(process, this, _logger)) + using (var processWrapper = new ProcessWrapper(process, this)) { bool ranToCompletion; @@ -588,10 +592,10 @@ namespace MediaBrowser.MediaEncoding.Encoder { StartProcess(processWrapper); - var timeoutMs = ConfigurationManager.Configuration.ImageExtractionTimeoutMs; + var timeoutMs = _configurationManager.Configuration.ImageExtractionTimeoutMs; if (timeoutMs <= 0) { - timeoutMs = DefaultImageExtractionTimeoutMs; + timeoutMs = DefaultImageExtractionTimeout; } ranToCompletion = await process.WaitForExitAsync(timeoutMs).ConfigureAwait(false); @@ -607,7 +611,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1; - var file = FileSystem.GetFileInfo(tempExtractPath); + var file = _fileSystem.GetFileInfo(tempExtractPath); if (exitCode == -1 || !file.Exists || file.Length == 0) { @@ -675,7 +679,6 @@ namespace MediaBrowser.MediaEncoding.Encoder args = analyzeDurationArgument + " " + args; } - var encodinghelper = new EncodingHelper(this, FileSystem, SubtitleEncoder()); if (videoStream != null) { /* fix @@ -689,7 +692,7 @@ namespace MediaBrowser.MediaEncoding.Encoder if (!string.IsNullOrWhiteSpace(container)) { - var inputFormat = encodinghelper.GetInputFormat(container); + var inputFormat = EncodingHelper.GetInputFormat(container); if (!string.IsNullOrWhiteSpace(inputFormat)) { args = "-f " + inputFormat + " " + args; @@ -700,7 +703,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { CreateNoWindow = true, UseShellExecute = false, - FileName = FFmpegPath, + FileName = _ffmpegPath, Arguments = args, IsHidden = true, ErrorDialog = false, @@ -713,7 +716,7 @@ namespace MediaBrowser.MediaEncoding.Encoder bool ranToCompletion = false; - using (var processWrapper = new ProcessWrapper(process, this, _logger)) + using (var processWrapper = new ProcessWrapper(process, this)) { try { @@ -736,10 +739,10 @@ namespace MediaBrowser.MediaEncoding.Encoder cancellationToken.ThrowIfCancellationRequested(); - var jpegCount = FileSystem.GetFilePaths(targetDirectory) + var jpegCount = _fileSystem.GetFilePaths(targetDirectory) .Count(i => string.Equals(Path.GetExtension(i), ".jpg", StringComparison.OrdinalIgnoreCase)); - isResponsive = (jpegCount > lastCount); + isResponsive = jpegCount > lastCount; lastCount = jpegCount; } @@ -770,7 +773,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { process.Process.Start(); - lock (_runningProcesses) + lock (_runningProcessesLock) { _runningProcesses.Add(process); } @@ -804,7 +807,7 @@ namespace MediaBrowser.MediaEncoding.Encoder private void StopProcesses() { List proceses; - lock (_runningProcesses) + lock (_runningProcessesLock) { proceses = _runningProcesses.ToList(); _runningProcesses.Clear(); @@ -827,12 +830,11 @@ namespace MediaBrowser.MediaEncoding.Encoder return path.Replace('\\', '/').Replace(":", "\\:").Replace("'", "'\\\\\\''"); } - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// + /// public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } /// @@ -852,11 +854,6 @@ namespace MediaBrowser.MediaEncoding.Encoder throw new NotImplementedException(); } - public string[] GetPlayableStreamFileNames(string path, VideoType videoType) - { - throw new NotImplementedException(); - } - public IEnumerable GetPrimaryPlaylistVobFiles(string path, IIsoMount isoMount, uint? titleNumber) { throw new NotImplementedException(); @@ -870,21 +867,24 @@ namespace MediaBrowser.MediaEncoding.Encoder private class ProcessWrapper : IDisposable { - public readonly IProcess Process; - public bool HasExited; - public int? ExitCode; private readonly MediaEncoder _mediaEncoder; - private readonly ILogger _logger; - public ProcessWrapper(IProcess process, MediaEncoder mediaEncoder, ILogger logger) + private bool _disposed = false; + + public ProcessWrapper(IProcess process, MediaEncoder mediaEncoder) { Process = process; _mediaEncoder = mediaEncoder; - _logger = logger; - Process.Exited += Process_Exited; + Process.Exited += OnProcessExited; } - void Process_Exited(object sender, EventArgs e) + public IProcess Process { get; } + + public bool HasExited { get; private set; } + + public int? ExitCode { get; private set; } + + void OnProcessExited(object sender, EventArgs e) { var process = (IProcess)sender; @@ -903,7 +903,7 @@ namespace MediaBrowser.MediaEncoding.Encoder private void DisposeProcess(IProcess process) { - lock (_mediaEncoder._runningProcesses) + lock (_mediaEncoder._runningProcessesLock) { _mediaEncoder._runningProcesses.Remove(this); } @@ -917,23 +917,18 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - private bool _disposed; - private readonly object _syncLock = new object(); public void Dispose() { - lock (_syncLock) + if (!_disposed) { - if (!_disposed) + if (Process != null) { - if (Process != null) - { - Process.Exited -= Process_Exited; - DisposeProcess(Process); - } + Process.Exited -= OnProcessExited; + DisposeProcess(Process); } - - _disposed = true; } + + _disposed = true; } } } diff --git a/MediaBrowser.MediaEncoding/Subtitles/ISubtitleWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/ISubtitleWriter.cs index 3401c2d670..dec714121d 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/ISubtitleWriter.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/ISubtitleWriter.cs @@ -5,7 +5,7 @@ using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.MediaEncoding.Subtitles { /// - /// Interface ISubtitleWriter + /// Interface ISubtitleWriter. /// public interface ISubtitleWriter { diff --git a/MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs index 8995fcfe1f..241ebc6df5 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs @@ -1,27 +1,39 @@ using System.IO; -using System.Text; +using System.Text.Json; using System.Threading; using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.MediaEncoding.Subtitles { + /// + /// JSON subtitle writer. + /// public class JsonWriter : ISubtitleWriter { - private readonly IJsonSerializer _json; - - public JsonWriter(IJsonSerializer json) - { - _json = json; - } - + /// public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken) { - using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) + using (var writer = new Utf8JsonWriter(stream)) { - var json = _json.SerializeToString(info); + var trackevents = info.TrackEvents; + writer.WriteStartArray("TrackEvents"); + + for (int i = 0; i < trackevents.Count; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + + var current = trackevents[i]; + writer.WriteStartObject(); + + writer.WriteString("Id", current.Id); + writer.WriteString("Text", current.Text); + writer.WriteNumber("StartPositionTicks", current.StartPositionTicks); + writer.WriteNumber("EndPositionTicks", current.EndPositionTicks); + + writer.WriteEndObject(); + } - writer.Write(json); + writer.WriteEndObject(); } } } diff --git a/MediaBrowser.MediaEncoding/Subtitles/SrtWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/SrtWriter.cs index 6f96a641e9..45b317b2ed 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SrtWriter.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SrtWriter.cs @@ -14,14 +14,19 @@ namespace MediaBrowser.MediaEncoding.Subtitles { using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) { - var index = 1; + var trackEvents = info.TrackEvents; - foreach (var trackEvent in info.TrackEvents) + for (int i = 0; i < trackEvents.Count; i++) { cancellationToken.ThrowIfCancellationRequested(); - writer.WriteLine(index.ToString(CultureInfo.InvariantCulture)); - writer.WriteLine(@"{0:hh\:mm\:ss\,fff} --> {1:hh\:mm\:ss\,fff}", TimeSpan.FromTicks(trackEvent.StartPositionTicks), TimeSpan.FromTicks(trackEvent.EndPositionTicks)); + var trackEvent = trackEvents[i]; + + writer.WriteLine((i + 1).ToString(CultureInfo.InvariantCulture)); + writer.WriteLine( + @"{0:hh\:mm\:ss\,fff} --> {1:hh\:mm\:ss\,fff}", + TimeSpan.FromTicks(trackEvent.StartPositionTicks), + TimeSpan.FromTicks(trackEvent.EndPositionTicks)); var text = trackEvent.Text; @@ -29,9 +34,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles text = Regex.Replace(text, @"\\n", " ", RegexOptions.IgnoreCase); writer.WriteLine(text); - writer.WriteLine(string.Empty); - - index++; + writer.WriteLine(); } } } diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index d5fa76c3ab..183d7566d4 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -17,7 +17,6 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; using UtfUnknown; @@ -30,28 +29,25 @@ namespace MediaBrowser.MediaEncoding.Subtitles private readonly IApplicationPaths _appPaths; private readonly IFileSystem _fileSystem; private readonly IMediaEncoder _mediaEncoder; - private readonly IJsonSerializer _json; private readonly IHttpClient _httpClient; private readonly IMediaSourceManager _mediaSourceManager; private readonly IProcessFactory _processFactory; public SubtitleEncoder( ILibraryManager libraryManager, - ILoggerFactory loggerFactory, + ILogger logger, IApplicationPaths appPaths, IFileSystem fileSystem, IMediaEncoder mediaEncoder, - IJsonSerializer json, IHttpClient httpClient, IMediaSourceManager mediaSourceManager, IProcessFactory processFactory) { _libraryManager = libraryManager; - _logger = loggerFactory.CreateLogger(nameof(SubtitleEncoder)); + _logger = logger; _appPaths = appPaths; _fileSystem = fileSystem; _mediaEncoder = mediaEncoder; - _json = json; _httpClient = httpClient; _mediaSourceManager = mediaSourceManager; _processFactory = processFactory; @@ -59,7 +55,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles private string SubtitleCachePath => Path.Combine(_appPaths.DataPath, "subtitles"); - private Stream ConvertSubtitles(Stream stream, + private Stream ConvertSubtitles( + Stream stream, string inputFormat, string outputFormat, long startTimeTicks, @@ -170,7 +167,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles && (mediaSource.VideoType.Value == VideoType.BluRay || mediaSource.VideoType.Value == VideoType.Dvd)) { var mediaSourceItem = (Video)_libraryManager.GetItemById(new Guid(mediaSource.Id)); - inputFiles = mediaSourceItem.GetPlayableStreamFileNames(_mediaEncoder); + inputFiles = mediaSourceItem.GetPlayableStreamFileNames(); } else { @@ -179,32 +176,27 @@ namespace MediaBrowser.MediaEncoding.Subtitles var fileInfo = await GetReadableFile(mediaSource.Path, inputFiles, mediaSource.Protocol, subtitleStream, cancellationToken).ConfigureAwait(false); - var stream = await GetSubtitleStream(fileInfo.Path, subtitleStream.Language, fileInfo.Protocol, fileInfo.IsExternal, cancellationToken).ConfigureAwait(false); + var stream = await GetSubtitleStream(fileInfo.Path, fileInfo.Protocol, fileInfo.IsExternal, cancellationToken).ConfigureAwait(false); return (stream, fileInfo.Format); } - private async Task GetSubtitleStream(string path, string language, MediaProtocol protocol, bool requiresCharset, CancellationToken cancellationToken) + private async Task GetSubtitleStream(string path, MediaProtocol protocol, bool requiresCharset, CancellationToken cancellationToken) { if (requiresCharset) { - var bytes = await GetBytes(path, protocol, cancellationToken).ConfigureAwait(false); - - var charset = CharsetDetector.DetectFromBytes(bytes).Detected?.EncodingName; - _logger.LogDebug("charset {CharSet} detected for {Path}", charset ?? "null", path); - - if (!string.IsNullOrEmpty(charset)) + using (var stream = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false)) { - // Make sure we have all the code pages we can get - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - using (var inputStream = new MemoryStream(bytes)) - using (var reader = new StreamReader(inputStream, Encoding.GetEncoding(charset))) + var result = CharsetDetector.DetectFromStream(stream).Detected; + + if (result != null) { - var text = await reader.ReadToEndAsync().ConfigureAwait(false); + _logger.LogDebug("charset {CharSet} detected for {Path}", result.EncodingName, path); - bytes = Encoding.UTF8.GetBytes(text); + using var reader = new StreamReader(stream, result.Encoding); + var text = await reader.ReadToEndAsync().ConfigureAwait(false); - return new MemoryStream(bytes); + return new MemoryStream(Encoding.UTF8.GetBytes(text)); } } } @@ -323,7 +315,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles if (string.Equals(format, "json", StringComparison.OrdinalIgnoreCase)) { - return new JsonWriter(_json); + return new JsonWriter(); } if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase)) { @@ -544,7 +536,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles { if (!File.Exists(outputPath)) { - await ExtractTextSubtitleInternal(_mediaEncoder.GetInputArgument(inputFiles, protocol), subtitleStreamIndex, outputCodec, outputPath, cancellationToken).ConfigureAwait(false); + await ExtractTextSubtitleInternal( + _mediaEncoder.GetInputArgument(inputFiles, protocol), + subtitleStreamIndex, + outputCodec, + outputPath, + cancellationToken).ConfigureAwait(false); } } finally @@ -572,8 +569,13 @@ namespace MediaBrowser.MediaEncoding.Subtitles Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); - var processArgs = string.Format("-i {0} -map 0:{1} -an -vn -c:s {2} \"{3}\"", inputPath, - subtitleStreamIndex, outputCodec, outputPath); + var processArgs = string.Format( + CultureInfo.InvariantCulture, + "-i {0} -map 0:{1} -an -vn -c:s {2} \"{3}\"", + inputPath, + subtitleStreamIndex, + outputCodec, + outputPath); var process = _processFactory.Create(new ProcessOptions { @@ -721,41 +723,38 @@ namespace MediaBrowser.MediaEncoding.Subtitles } } + /// public async Task GetSubtitleFileCharacterSet(string path, string language, MediaProtocol protocol, CancellationToken cancellationToken) { - var bytes = await GetBytes(path, protocol, cancellationToken).ConfigureAwait(false); - - var charset = CharsetDetector.DetectFromBytes(bytes).Detected?.EncodingName; + using (var stream = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false)) + { + var charset = CharsetDetector.DetectFromStream(stream).Detected?.EncodingName; - _logger.LogDebug("charset {0} detected for {Path}", charset ?? "null", path); + _logger.LogDebug("charset {0} detected for {Path}", charset ?? "null", path); - return charset; + return charset; + } } - private async Task GetBytes(string path, MediaProtocol protocol, CancellationToken cancellationToken) + private Task GetStream(string path, MediaProtocol protocol, CancellationToken cancellationToken) { - if (protocol == MediaProtocol.Http) + switch (protocol) { - var opts = new HttpRequestOptions() - { - Url = path, - CancellationToken = cancellationToken - }; - using (var file = await _httpClient.Get(opts).ConfigureAwait(false)) - using (var memoryStream = new MemoryStream()) - { - await file.CopyToAsync(memoryStream).ConfigureAwait(false); - memoryStream.Position = 0; + case MediaProtocol.Http: + var opts = new HttpRequestOptions() + { + Url = path, + CancellationToken = cancellationToken, + BufferContent = true + }; - return memoryStream.ToArray(); - } - } - if (protocol == MediaProtocol.File) - { - return File.ReadAllBytes(path); - } + return _httpClient.Get(opts); - throw new ArgumentOutOfRangeException(nameof(protocol)); + case MediaProtocol.File: + return Task.FromResult(File.OpenRead(path)); + default: + throw new ArgumentOutOfRangeException(nameof(protocol)); + } } } } diff --git a/MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs index cdaf949641..4f15bac496 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs @@ -49,12 +49,5 @@ namespace MediaBrowser.MediaEncoding.Subtitles writer.WriteLine(""); } } - - private string FormatTime(long ticks) - { - var time = TimeSpan.FromTicks(ticks); - - return string.Format(@"{0:hh\:mm\:ss\,fff}", time); - } } } diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 9de6fef619..cf6d9c2f6c 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -233,7 +233,6 @@ namespace MediaBrowser.Model.Configuration LocalNetworkSubnets = Array.Empty(); LocalNetworkAddresses = Array.Empty(); CodecsUsed = Array.Empty(); - ImageExtractionTimeoutMs = 0; PathSubstitutions = Array.Empty(); IgnoreVirtualInterfaces = false; EnableSimpleArtistDetection = true; diff --git a/MediaBrowser.Model/MediaInfo/SubtitleTrackInfo.cs b/MediaBrowser.Model/MediaInfo/SubtitleTrackInfo.cs index 962f4d2fe3..c382b20c9a 100644 --- a/MediaBrowser.Model/MediaInfo/SubtitleTrackInfo.cs +++ b/MediaBrowser.Model/MediaInfo/SubtitleTrackInfo.cs @@ -1,12 +1,15 @@ +using System; +using System.Collections.Generic; + namespace MediaBrowser.Model.MediaInfo { public class SubtitleTrackInfo { - public SubtitleTrackEvent[] TrackEvents { get; set; } + public IReadOnlyList TrackEvents { get; set; } public SubtitleTrackInfo() { - TrackEvents = new SubtitleTrackEvent[] { }; + TrackEvents = Array.Empty(); } } } diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index e0b23108f0..95b915b3d8 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -62,7 +62,11 @@ namespace MediaBrowser.Providers.MediaInfo { var protocol = item.PathProtocol ?? MediaProtocol.File; - var inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, item.Path, null, item.GetPlayableStreamFileNames(_mediaEncoder)); + var inputPath = MediaEncoderHelpers.GetInputArgument( + _fileSystem, + item.Path, + null, + item.GetPlayableStreamFileNames()); var mediaStreams = item.GetMediaStreams();