Merge pull request #1941 from Bond-009/mediaencoding

Make probesize and analyzeduration configurable and simplify circular dependencies
pull/2170/head
dkanada 5 years ago committed by GitHub
commit bc7cbfb21a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -841,16 +841,14 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton(ChapterManager); serviceCollection.AddSingleton(ChapterManager);
MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder( MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder(
LoggerFactory, LoggerFactory.CreateLogger<MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(),
JsonSerializer,
StartupOptions.FFmpegPath,
ServerConfigurationManager, ServerConfigurationManager,
FileSystemManager, FileSystemManager,
() => SubtitleEncoder,
() => MediaSourceManager,
ProcessFactory, ProcessFactory,
5000, LocalizationManager,
LocalizationManager); () => SubtitleEncoder,
_configuration,
StartupOptions.FFmpegPath);
serviceCollection.AddSingleton(MediaEncoder); serviceCollection.AddSingleton(MediaEncoder);
EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager); EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager);
@ -867,10 +865,19 @@ namespace Emby.Server.Implementations
AuthService = new AuthService(LoggerFactory.CreateLogger<AuthService>(), authContext, ServerConfigurationManager, SessionManager, NetworkManager); AuthService = new AuthService(LoggerFactory.CreateLogger<AuthService>(), authContext, ServerConfigurationManager, SessionManager, NetworkManager);
serviceCollection.AddSingleton(AuthService); 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<MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>(),
ApplicationPaths,
FileSystemManager,
MediaEncoder,
HttpClient,
MediaSourceManager,
ProcessFactory);
serviceCollection.AddSingleton(SubtitleEncoder); serviceCollection.AddSingleton(SubtitleEncoder);
serviceCollection.AddSingleton(typeof(IResourceFileManager), typeof(ResourceFileManager)); serviceCollection.AddSingleton(typeof(IResourceFileManager), typeof(ResourceFileManager));
serviceCollection.AddSingleton<EncodingHelper>();
_displayPreferencesRepository.Initialize(); _displayPreferencesRepository.Initialize();

@ -1,13 +1,16 @@
using System.Collections.Generic; using System.Collections.Generic;
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
namespace Emby.Server.Implementations namespace Emby.Server.Implementations
{ {
public static class ConfigurationOptions public static class ConfigurationOptions
{ {
public static readonly Dictionary<string, string> Configuration = new Dictionary<string, string> public static Dictionary<string, string> Configuration => new Dictionary<string, string>
{ {
{ "HttpListenerHost:DefaultRedirectPath", "web/index.html" }, { "HttpListenerHost:DefaultRedirectPath", "web/index.html" },
{ "MusicBrainz:BaseUrl", "https://www.musicbrainz.org" } { "MusicBrainz:BaseUrl", "https://www.musicbrainz.org" },
{ FfmpegProbeSizeKey, "1G" },
{ FfmpegAnalyzeDurationKey, "200M" }
}; };
} }
} }

@ -29,7 +29,6 @@
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" /> <PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.1" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.0.1" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.0.1" />

@ -7,6 +7,7 @@ using System.Net;
using System.Net.Security; using System.Net.Security;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -145,6 +146,10 @@ namespace Jellyfin.Server
ApplicationHost.LogEnvironmentInfo(_logger, appPaths); 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 // Increase the max http request limit
// The default connection limit is 10 for ASP.NET hosted applications and 2 for all others. // The default connection limit is 10 for ASP.NET hosted applications and 2 for all others.
ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit); ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit);
@ -456,9 +461,9 @@ namespace Jellyfin.Server
return new ConfigurationBuilder() return new ConfigurationBuilder()
.SetBasePath(appPaths.ConfigurationDirectoryPath) .SetBasePath(appPaths.ConfigurationDirectoryPath)
.AddInMemoryCollection(ConfigurationOptions.Configuration)
.AddJsonFile("logging.json", false, true) .AddJsonFile("logging.json", false, true)
.AddEnvironmentVariables("JELLYFIN_") .AddEnvironmentVariables("JELLYFIN_")
.AddInMemoryCollection(ConfigurationOptions.Configuration)
.Build(); .Build();
} }

@ -63,8 +63,6 @@ namespace MediaBrowser.Api.Playback
protected IDeviceManager DeviceManager { get; private set; } protected IDeviceManager DeviceManager { get; private set; }
protected ISubtitleEncoder SubtitleEncoder { get; private set; }
protected IMediaSourceManager MediaSourceManager { get; private set; } protected IMediaSourceManager MediaSourceManager { get; private set; }
protected IJsonSerializer JsonSerializer { get; private set; } protected IJsonSerializer JsonSerializer { get; private set; }
@ -92,11 +90,11 @@ namespace MediaBrowser.Api.Playback
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
IFileSystem fileSystem, IFileSystem fileSystem,
IDlnaManager dlnaManager, IDlnaManager dlnaManager,
ISubtitleEncoder subtitleEncoder,
IDeviceManager deviceManager, IDeviceManager deviceManager,
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
IJsonSerializer jsonSerializer, IJsonSerializer jsonSerializer,
IAuthorizationContext authorizationContext) IAuthorizationContext authorizationContext,
EncodingHelper encodingHelper)
: base(logger, serverConfigurationManager, httpResultFactory) : base(logger, serverConfigurationManager, httpResultFactory)
{ {
UserManager = userManager; UserManager = userManager;
@ -105,13 +103,12 @@ namespace MediaBrowser.Api.Playback
MediaEncoder = mediaEncoder; MediaEncoder = mediaEncoder;
FileSystem = fileSystem; FileSystem = fileSystem;
DlnaManager = dlnaManager; DlnaManager = dlnaManager;
SubtitleEncoder = subtitleEncoder;
DeviceManager = deviceManager; DeviceManager = deviceManager;
MediaSourceManager = mediaSourceManager; MediaSourceManager = mediaSourceManager;
JsonSerializer = jsonSerializer; JsonSerializer = jsonSerializer;
AuthorizationContext = authorizationContext; AuthorizationContext = authorizationContext;
EncodingHelper = new EncodingHelper(MediaEncoder, FileSystem, SubtitleEncoder); EncodingHelper = encodingHelper;
} }
/// <summary> /// <summary>
@ -148,8 +145,6 @@ namespace MediaBrowser.Api.Playback
return Path.Combine(folder, filename + ext); return Path.Combine(folder, filename + ext);
} }
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
protected virtual string GetDefaultEncoderPreset() protected virtual string GetDefaultEncoderPreset()
{ {
return "superfast"; return "superfast";

@ -34,26 +34,26 @@ namespace MediaBrowser.Api.Playback.Hls
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
IFileSystem fileSystem, IFileSystem fileSystem,
IDlnaManager dlnaManager, IDlnaManager dlnaManager,
ISubtitleEncoder subtitleEncoder,
IDeviceManager deviceManager, IDeviceManager deviceManager,
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
IJsonSerializer jsonSerializer, IJsonSerializer jsonSerializer,
IAuthorizationContext authorizationContext) IAuthorizationContext authorizationContext,
: base( EncodingHelper encodingHelper)
logger, : base(
serverConfigurationManager, logger,
httpResultFactory, serverConfigurationManager,
userManager, httpResultFactory,
libraryManager, userManager,
isoManager, libraryManager,
mediaEncoder, isoManager,
fileSystem, mediaEncoder,
dlnaManager, fileSystem,
subtitleEncoder, dlnaManager,
deviceManager, deviceManager,
mediaSourceManager, mediaSourceManager,
jsonSerializer, jsonSerializer,
authorizationContext) authorizationContext,
encodingHelper)
{ {
} }

@ -104,12 +104,12 @@ namespace MediaBrowser.Api.Playback.Hls
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
IFileSystem fileSystem, IFileSystem fileSystem,
IDlnaManager dlnaManager, IDlnaManager dlnaManager,
ISubtitleEncoder subtitleEncoder,
IDeviceManager deviceManager, IDeviceManager deviceManager,
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
IJsonSerializer jsonSerializer, IJsonSerializer jsonSerializer,
IAuthorizationContext authorizationContext, IAuthorizationContext authorizationContext,
INetworkManager networkManager) INetworkManager networkManager,
EncodingHelper encodingHelper)
: base( : base(
logger, logger,
serverConfigurationManager, serverConfigurationManager,
@ -120,11 +120,11 @@ namespace MediaBrowser.Api.Playback.Hls
mediaEncoder, mediaEncoder,
fileSystem, fileSystem,
dlnaManager, dlnaManager,
subtitleEncoder,
deviceManager, deviceManager,
mediaSourceManager, mediaSourceManager,
jsonSerializer, jsonSerializer,
authorizationContext) authorizationContext,
encodingHelper)
{ {
NetworkManager = networkManager; NetworkManager = networkManager;
} }

@ -27,6 +27,39 @@ namespace MediaBrowser.Api.Playback.Hls
[Authenticated] [Authenticated]
public class VideoHlsService : BaseHlsService public class VideoHlsService : BaseHlsService
{ {
public VideoHlsService(
ILogger<VideoHlsService> 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<object> Get(GetLiveHlsStream request) public Task<object> Get(GetLiveHlsStream request)
{ {
return ProcessRequestAsync(request, true); return ProcessRequestAsync(request, true);
@ -136,38 +169,5 @@ namespace MediaBrowser.Api.Playback.Hls
return args; return args;
} }
public VideoHlsService(
ILogger<VideoHlsService> 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)
{
}
} }
} }

@ -43,11 +43,11 @@ namespace MediaBrowser.Api.Playback.Progressive
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
IFileSystem fileSystem, IFileSystem fileSystem,
IDlnaManager dlnaManager, IDlnaManager dlnaManager,
ISubtitleEncoder subtitleEncoder,
IDeviceManager deviceManager, IDeviceManager deviceManager,
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
IJsonSerializer jsonSerializer, IJsonSerializer jsonSerializer,
IAuthorizationContext authorizationContext) IAuthorizationContext authorizationContext,
EncodingHelper encodingHelper)
: base( : base(
logger, logger,
serverConfigurationManager, serverConfigurationManager,
@ -59,11 +59,11 @@ namespace MediaBrowser.Api.Playback.Progressive
mediaEncoder, mediaEncoder,
fileSystem, fileSystem,
dlnaManager, dlnaManager,
subtitleEncoder,
deviceManager, deviceManager,
mediaSourceManager, mediaSourceManager,
jsonSerializer, jsonSerializer,
authorizationContext) authorizationContext,
encodingHelper)
{ {
} }

@ -38,11 +38,11 @@ namespace MediaBrowser.Api.Playback.Progressive
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
IFileSystem fileSystem, IFileSystem fileSystem,
IDlnaManager dlnaManager, IDlnaManager dlnaManager,
ISubtitleEncoder subtitleEncoder,
IDeviceManager deviceManager, IDeviceManager deviceManager,
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
IJsonSerializer jsonSerializer, IJsonSerializer jsonSerializer,
IAuthorizationContext authorizationContext) IAuthorizationContext authorizationContext,
EncodingHelper encodingHelper)
: base( : base(
logger, logger,
serverConfigurationManager, serverConfigurationManager,
@ -53,11 +53,11 @@ namespace MediaBrowser.Api.Playback.Progressive
mediaEncoder, mediaEncoder,
fileSystem, fileSystem,
dlnaManager, dlnaManager,
subtitleEncoder,
deviceManager, deviceManager,
mediaSourceManager, mediaSourceManager,
jsonSerializer, jsonSerializer,
authorizationContext) authorizationContext,
encodingHelper)
{ {
HttpClient = httpClient; HttpClient = httpClient;
} }

@ -80,11 +80,11 @@ namespace MediaBrowser.Api.Playback.Progressive
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
IFileSystem fileSystem, IFileSystem fileSystem,
IDlnaManager dlnaManager, IDlnaManager dlnaManager,
ISubtitleEncoder subtitleEncoder,
IDeviceManager deviceManager, IDeviceManager deviceManager,
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
IJsonSerializer jsonSerializer, IJsonSerializer jsonSerializer,
IAuthorizationContext authorizationContext) IAuthorizationContext authorizationContext,
EncodingHelper encodingHelper)
: base( : base(
logger, logger,
serverConfigurationManager, serverConfigurationManager,
@ -96,11 +96,11 @@ namespace MediaBrowser.Api.Playback.Progressive
mediaEncoder, mediaEncoder,
fileSystem, fileSystem,
dlnaManager, dlnaManager,
subtitleEncoder,
deviceManager, deviceManager,
mediaSourceManager, mediaSourceManager,
jsonSerializer, jsonSerializer,
authorizationContext) authorizationContext,
encodingHelper)
{ {
} }

@ -9,7 +9,6 @@ using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
@ -75,6 +74,9 @@ namespace MediaBrowser.Api.Playback
[Authenticated] [Authenticated]
public class UniversalAudioService : BaseApiService public class UniversalAudioService : BaseApiService
{ {
private readonly ILoggerFactory _loggerFactory;
private readonly EncodingHelper _encodingHelper;
public UniversalAudioService( public UniversalAudioService(
ILogger<UniversalAudioService> logger, ILogger<UniversalAudioService> logger,
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
@ -87,11 +89,11 @@ namespace MediaBrowser.Api.Playback
IFileSystem fileSystem, IFileSystem fileSystem,
IDlnaManager dlnaManager, IDlnaManager dlnaManager,
IDeviceManager deviceManager, IDeviceManager deviceManager,
ISubtitleEncoder subtitleEncoder,
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
IJsonSerializer jsonSerializer, IJsonSerializer jsonSerializer,
IAuthorizationContext authorizationContext, IAuthorizationContext authorizationContext,
INetworkManager networkManager) INetworkManager networkManager,
EncodingHelper encodingHelper)
: base(logger, serverConfigurationManager, httpResultFactory) : base(logger, serverConfigurationManager, httpResultFactory)
{ {
HttpClient = httpClient; HttpClient = httpClient;
@ -102,11 +104,11 @@ namespace MediaBrowser.Api.Playback
FileSystem = fileSystem; FileSystem = fileSystem;
DlnaManager = dlnaManager; DlnaManager = dlnaManager;
DeviceManager = deviceManager; DeviceManager = deviceManager;
SubtitleEncoder = subtitleEncoder;
MediaSourceManager = mediaSourceManager; MediaSourceManager = mediaSourceManager;
JsonSerializer = jsonSerializer; JsonSerializer = jsonSerializer;
AuthorizationContext = authorizationContext; AuthorizationContext = authorizationContext;
NetworkManager = networkManager; NetworkManager = networkManager;
_encodingHelper = encodingHelper;
} }
protected IHttpClient HttpClient { get; private set; } protected IHttpClient HttpClient { get; private set; }
@ -117,7 +119,6 @@ namespace MediaBrowser.Api.Playback
protected IFileSystem FileSystem { get; private set; } protected IFileSystem FileSystem { get; private set; }
protected IDlnaManager DlnaManager { get; private set; } protected IDlnaManager DlnaManager { get; private set; }
protected IDeviceManager DeviceManager { get; private set; } protected IDeviceManager DeviceManager { get; private set; }
protected ISubtitleEncoder SubtitleEncoder { get; private set; }
protected IMediaSourceManager MediaSourceManager { get; private set; } protected IMediaSourceManager MediaSourceManager { get; private set; }
protected IJsonSerializer JsonSerializer { get; private set; } protected IJsonSerializer JsonSerializer { get; private set; }
protected IAuthorizationContext AuthorizationContext { get; private set; } protected IAuthorizationContext AuthorizationContext { get; private set; }
@ -287,12 +288,12 @@ namespace MediaBrowser.Api.Playback
MediaEncoder, MediaEncoder,
FileSystem, FileSystem,
DlnaManager, DlnaManager,
SubtitleEncoder,
DeviceManager, DeviceManager,
MediaSourceManager, MediaSourceManager,
JsonSerializer, JsonSerializer,
AuthorizationContext, AuthorizationContext,
NetworkManager) NetworkManager,
_encodingHelper)
{ {
Request = Request Request = Request
}; };
@ -337,11 +338,11 @@ namespace MediaBrowser.Api.Playback
MediaEncoder, MediaEncoder,
FileSystem, FileSystem,
DlnaManager, DlnaManager,
SubtitleEncoder,
DeviceManager, DeviceManager,
MediaSourceManager, MediaSourceManager,
JsonSerializer, JsonSerializer,
AuthorizationContext) AuthorizationContext,
_encodingHelper)
{ {
Request = Request Request = Request
}; };

@ -137,7 +137,7 @@ namespace MediaBrowser.Controller.Entities
/// <value>The video3 D format.</value> /// <value>The video3 D format.</value>
public Video3DFormat? Video3DFormat { get; set; } public Video3DFormat? Video3DFormat { get; set; }
public string[] GetPlayableStreamFileNames(IMediaEncoder mediaEncoder) public string[] GetPlayableStreamFileNames()
{ {
var videoType = VideoType; var videoType = VideoType;
@ -153,7 +153,8 @@ namespace MediaBrowser.Controller.Entities
{ {
return Array.Empty<string>(); return Array.Empty<string>();
} }
return mediaEncoder.GetPlayableStreamFileNames(Path, videoType);
throw new NotImplementedException();
} }
/// <summary> /// <summary>

@ -0,0 +1,36 @@
using Microsoft.Extensions.Configuration;
namespace MediaBrowser.Controller.Extensions
{
/// <summary>
/// Configuration extensions for <c>MediaBrowser.Controller</c>.
/// </summary>
public static class ConfigurationExtensions
{
/// <summary>
/// The key for the FFmpeg probe size option.
/// </summary>
public const string FfmpegProbeSizeKey = "FFmpeg:probesize";
/// <summary>
/// The key for the FFmpeg analyse duration option.
/// </summary>
public const string FfmpegAnalyzeDurationKey = "FFmpeg:analyzeduration";
/// <summary>
/// Retrieves the FFmpeg probe size from the <see cref="IConfiguration" />.
/// </summary>
/// <param name="configuration">This configuration.</param>
/// <returns>The FFmpeg probe size option.</returns>
public static string GetFFmpegProbeSize(this IConfiguration configuration)
=> configuration[FfmpegProbeSizeKey];
/// <summary>
/// Retrieves the FFmpeg analyse duration from the <see cref="IConfiguration" />.
/// </summary>
/// <param name="configuration">This configuration.</param>
/// <returns>The FFmpeg analyse duration option.</returns>
public static string GetFFmpegAnalyzeDuration(this IConfiguration configuration)
=> configuration[FfmpegAnalyzeDurationKey];
}
}

@ -7,6 +7,10 @@
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.0.0" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" /> <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" /> <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />

@ -6,12 +6,14 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Configuration;
namespace MediaBrowser.Controller.MediaEncoding namespace MediaBrowser.Controller.MediaEncoding
{ {
@ -22,6 +24,7 @@ namespace MediaBrowser.Controller.MediaEncoding
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly ISubtitleEncoder _subtitleEncoder; private readonly ISubtitleEncoder _subtitleEncoder;
private readonly IConfiguration _configuration;
private static readonly string[] _videoProfiles = new[] private static readonly string[] _videoProfiles = new[]
{ {
@ -34,11 +37,16 @@ namespace MediaBrowser.Controller.MediaEncoding
"ConstrainedHigh" "ConstrainedHigh"
}; };
public EncodingHelper(IMediaEncoder mediaEncoder, IFileSystem fileSystem, ISubtitleEncoder subtitleEncoder) public EncodingHelper(
IMediaEncoder mediaEncoder,
IFileSystem fileSystem,
ISubtitleEncoder subtitleEncoder,
IConfiguration configuration)
{ {
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_subtitleEncoder = subtitleEncoder; _subtitleEncoder = subtitleEncoder;
_configuration = configuration;
} }
public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions) public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
@ -172,7 +180,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Empty; return string.Empty;
} }
public string GetInputFormat(string container) public static string GetInputFormat(string container)
{ {
if (string.IsNullOrEmpty(container)) if (string.IsNullOrEmpty(container))
{ {
@ -662,7 +670,11 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.IsNullOrEmpty(state.SubtitleStream.Language)) 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)) if (!string.IsNullOrEmpty(charenc))
{ {
@ -1948,7 +1960,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// If transcoding from 10 bit, transform colour spaces too // If transcoding from 10 bit, transform colour spaces too
if (!string.IsNullOrEmpty(videoStream.PixelFormat) if (!string.IsNullOrEmpty(videoStream.PixelFormat)
&& videoStream.PixelFormat.IndexOf("p10", StringComparison.OrdinalIgnoreCase) != -1 && 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=p010le");
filters.Add("format=nv12"); filters.Add("format=nv12");
@ -2011,7 +2023,9 @@ namespace MediaBrowser.Controller.MediaEncoding
var output = string.Empty; 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); var subParam = GetTextSubtitleParam(state);
@ -2100,11 +2114,11 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
} }
public static string GetProbeSizeArgument(int numInputFiles) public string GetProbeSizeArgument(int numInputFiles)
=> numInputFiles > 1 ? "-probesize 1G" : ""; => numInputFiles > 1 ? "-probesize " + _configuration.GetFFmpegProbeSize() : string.Empty;
public static string GetAnalyzeDurationArgument(int numInputFiles) public string GetAnalyzeDurationArgument(int numInputFiles)
=> numInputFiles > 1 ? "-analyzeduration 200M" : ""; => numInputFiles > 1 ? "-analyzeduration " + _configuration.GetFFmpegAnalyzeDuration() : string.Empty;
public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions) public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions)
{ {

@ -15,6 +15,9 @@ namespace MediaBrowser.Controller.MediaEncoding
/// </summary> /// </summary>
public interface IMediaEncoder : ITranscoderSupport public interface IMediaEncoder : ITranscoderSupport
{ {
/// <summary>
/// The location of the discovered FFmpeg tool.
/// </summary>
FFmpegLocation EncoderLocation { get; } FFmpegLocation EncoderLocation { get; }
/// <summary> /// <summary>
@ -97,7 +100,6 @@ namespace MediaBrowser.Controller.MediaEncoding
void UpdateEncoderPath(string path, string pathType); void UpdateEncoderPath(string path, string pathType);
bool SupportsEncoder(string encoder); bool SupportsEncoder(string encoder);
string[] GetPlayableStreamFileNames(string path, VideoType videoType);
IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, IIsoMount isoMount, uint? titleNumber); IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, IIsoMount isoMount, uint? titleNumber);
} }
} }

@ -3,13 +3,13 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text.Json;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.MediaEncoding.Probing; using MediaBrowser.MediaEncoding.Probing;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
@ -19,9 +19,9 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.System; using MediaBrowser.Model.System;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration;
namespace MediaBrowser.MediaEncoding.Encoder namespace MediaBrowser.MediaEncoding.Encoder
{ {
@ -31,55 +31,60 @@ namespace MediaBrowser.MediaEncoding.Encoder
public class MediaEncoder : IMediaEncoder, IDisposable public class MediaEncoder : IMediaEncoder, IDisposable
{ {
/// <summary> /// <summary>
/// Gets the encoder path. /// The default image extraction timeout in milliseconds.
/// </summary> /// </summary>
/// <value>The encoder path.</value> internal const int DefaultImageExtractionTimeout = 5000;
public string EncoderPath => FFmpegPath;
/// <summary>
/// The location of the discovered FFmpeg tool.
/// </summary>
public FFmpegLocation EncoderLocation { get; private set; }
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IJsonSerializer _jsonSerializer; private readonly IServerConfigurationManager _configurationManager;
private string FFmpegPath; private readonly IFileSystem _fileSystem;
private string FFprobePath;
protected readonly IServerConfigurationManager ConfigurationManager;
protected readonly IFileSystem FileSystem;
protected readonly Func<ISubtitleEncoder> SubtitleEncoder;
protected readonly Func<IMediaSourceManager> MediaSourceManager;
private readonly IProcessFactory _processFactory; private readonly IProcessFactory _processFactory;
private readonly int DefaultImageExtractionTimeoutMs; private readonly ILocalizationManager _localization;
private readonly string StartupOptionFFmpegPath; private readonly Func<ISubtitleEncoder> _subtitleEncoder;
private readonly IConfiguration _configuration;
private readonly string _startupOptionFFmpegPath;
private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(2, 2); private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(2, 2);
private readonly object _runningProcessesLock = new object();
private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>(); private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
private readonly ILocalizationManager _localization;
private EncodingHelper _encodingHelper;
private string _ffmpegPath;
private string _ffprobePath;
public MediaEncoder( public MediaEncoder(
ILoggerFactory loggerFactory, ILogger<MediaEncoder> logger,
IJsonSerializer jsonSerializer,
string startupOptionsFFmpegPath,
IServerConfigurationManager configurationManager, IServerConfigurationManager configurationManager,
IFileSystem fileSystem, IFileSystem fileSystem,
Func<ISubtitleEncoder> subtitleEncoder,
Func<IMediaSourceManager> mediaSourceManager,
IProcessFactory processFactory, IProcessFactory processFactory,
int defaultImageExtractionTimeoutMs, ILocalizationManager localization,
ILocalizationManager localization) Func<ISubtitleEncoder> subtitleEncoder,
{ IConfiguration configuration,
_logger = loggerFactory.CreateLogger(nameof(MediaEncoder)); string startupOptionsFFmpegPath)
_jsonSerializer = jsonSerializer; {
StartupOptionFFmpegPath = startupOptionsFFmpegPath; _logger = logger;
ConfigurationManager = configurationManager; _configurationManager = configurationManager;
FileSystem = fileSystem; _fileSystem = fileSystem;
SubtitleEncoder = subtitleEncoder;
_processFactory = processFactory; _processFactory = processFactory;
DefaultImageExtractionTimeoutMs = defaultImageExtractionTimeoutMs;
_localization = localization; _localization = localization;
_startupOptionFFmpegPath = startupOptionsFFmpegPath;
_subtitleEncoder = subtitleEncoder;
_configuration = configuration;
} }
private EncodingHelper EncodingHelper
=> LazyInitializer.EnsureInitialized(
ref _encodingHelper,
() => new EncodingHelper(this, _fileSystem, _subtitleEncoder(), _configuration));
/// <inheritdoc />
public string EncoderPath => _ffmpegPath;
/// <inheritdoc />
public FFmpegLocation EncoderLocation { get; private set; }
/// <summary> /// <summary>
/// Run at startup or if the user removes a Custom path from transcode page. /// Run at startup or if the user removes a Custom path from transcode page.
/// Sets global variables FFmpegPath. /// Sets global variables FFmpegPath.
@ -88,39 +93,39 @@ namespace MediaBrowser.MediaEncoding.Encoder
public void SetFFmpegPath() public void SetFFmpegPath()
{ {
// 1) Custom path stored in config/encoding xml file under tag <EncoderAppPath> takes precedence // 1) Custom path stored in config/encoding xml file under tag <EncoderAppPath> takes precedence
if (!ValidatePath(ConfigurationManager.GetConfiguration<EncodingOptions>("encoding").EncoderAppPath, FFmpegLocation.Custom)) if (!ValidatePath(_configurationManager.GetConfiguration<EncodingOptions>("encoding").EncoderAppPath, FFmpegLocation.Custom))
{ {
// 2) Check if the --ffmpeg CLI switch has been given // 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 // 3) Search system $PATH environment variable for valid FFmpeg
if (!ValidatePath(ExistsOnSystemPath("ffmpeg"), FFmpegLocation.System)) if (!ValidatePath(ExistsOnSystemPath("ffmpeg"), FFmpegLocation.System))
{ {
EncoderLocation = FFmpegLocation.NotFound; EncoderLocation = FFmpegLocation.NotFound;
FFmpegPath = null; _ffmpegPath = null;
} }
} }
} }
// Write the FFmpeg path to the config/encoding.xml file as <EncoderAppPathDisplay> so it appears in UI // Write the FFmpeg path to the config/encoding.xml file as <EncoderAppPathDisplay> so it appears in UI
var config = ConfigurationManager.GetConfiguration<EncodingOptions>("encoding"); var config = _configurationManager.GetConfiguration<EncodingOptions>("encoding");
config.EncoderAppPathDisplay = FFmpegPath ?? string.Empty; config.EncoderAppPathDisplay = _ffmpegPath ?? string.Empty;
ConfigurationManager.SaveConfiguration("encoding", config); _configurationManager.SaveConfiguration("encoding", config);
// Only if mpeg path is set, try and set path to probe // 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 // 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 // Interrogate to understand what coders are supported
var validator = new EncoderValidator(_logger, FFmpegPath); var validator = new EncoderValidator(_logger, _ffmpegPath);
SetAvailableDecoders(validator.GetDecoders()); SetAvailableDecoders(validator.GetDecoders());
SetAvailableEncoders(validator.GetEncoders()); SetAvailableEncoders(validator.GetEncoders());
} }
_logger.LogInformation("FFmpeg: {0}: {1}", EncoderLocation, FFmpegPath ?? string.Empty); _logger.LogInformation("FFmpeg: {0}: {1}", EncoderLocation, _ffmpegPath ?? string.Empty);
} }
/// <summary> /// <summary>
@ -160,9 +165,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
// Write the new ffmpeg path to the xml as <EncoderAppPath> // Write the new ffmpeg path to the xml as <EncoderAppPath>
// This ensures its not lost on next startup // This ensures its not lost on next startup
var config = ConfigurationManager.GetConfiguration<EncodingOptions>("encoding"); var config = _configurationManager.GetConfiguration<EncodingOptions>("encoding");
config.EncoderAppPath = newPath; config.EncoderAppPath = newPath;
ConfigurationManager.SaveConfiguration("encoding", config); _configurationManager.SaveConfiguration("encoding", config);
// Trigger SetFFmpegPath so we validate the new path and setup probe path // Trigger SetFFmpegPath so we validate the new path and setup probe path
SetFFmpegPath(); SetFFmpegPath();
@ -193,7 +198,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
// ToDo - Enable the ffmpeg validator. At the moment any version can be used. // ToDo - Enable the ffmpeg validator. At the moment any version can be used.
rc = true; rc = true;
FFmpegPath = path; _ffmpegPath = path;
EncoderLocation = location; EncoderLocation = location;
} }
else else
@ -209,7 +214,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{ {
try try
{ {
var files = FileSystem.GetFilePaths(path); var files = _fileSystem.GetFilePaths(path);
var excludeExtensions = new[] { ".c" }; var excludeExtensions = new[] { ".c" };
@ -304,7 +309,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{ {
var extractChapters = request.MediaType == DlnaProfileType.Video && request.ExtractChapters; 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); var probeSize = EncodingHelper.GetProbeSizeArgument(inputFiles.Length);
string analyzeDuration; string analyzeDuration;
@ -365,7 +370,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
// Must consume both or ffmpeg may hang due to deadlocks. See comments below. // Must consume both or ffmpeg may hang due to deadlocks. See comments below.
RedirectStandardOutput = true, RedirectStandardOutput = true,
FileName = FFprobePath, FileName = _ffprobePath,
Arguments = args, Arguments = args,
@ -383,7 +388,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
_logger.LogDebug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); _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); _logger.LogDebug("Starting ffprobe with args {Args}", args);
StartProcess(processWrapper); StartProcess(processWrapper);
@ -391,7 +396,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
InternalMediaInfoResult result; InternalMediaInfoResult result;
try try
{ {
result = await _jsonSerializer.DeserializeFromStreamAsync<InternalMediaInfoResult>( result = await JsonSerializer.DeserializeAsync<InternalMediaInfoResult>(
process.StandardOutput.BaseStream).ConfigureAwait(false); process.StandardOutput.BaseStream).ConfigureAwait(false);
} }
catch 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)); 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)); 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. // 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; args = string.Format("-ss {0} ", GetTimeParameter(offset.Value)) + args;
} }
var encodinghelper = new EncodingHelper(this, FileSystem, SubtitleEncoder());
if (videoStream != null) if (videoStream != null)
{ {
/* fix /* fix
@ -559,7 +563,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (!string.IsNullOrWhiteSpace(container)) if (!string.IsNullOrWhiteSpace(container))
{ {
var inputFormat = encodinghelper.GetInputFormat(container); var inputFormat = EncodingHelper.GetInputFormat(container);
if (!string.IsNullOrWhiteSpace(inputFormat)) if (!string.IsNullOrWhiteSpace(inputFormat))
{ {
args = "-f " + inputFormat + " " + args; args = "-f " + inputFormat + " " + args;
@ -570,7 +574,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{ {
CreateNoWindow = true, CreateNoWindow = true,
UseShellExecute = false, UseShellExecute = false,
FileName = FFmpegPath, FileName = _ffmpegPath,
Arguments = args, Arguments = args,
IsHidden = true, IsHidden = true,
ErrorDialog = false, ErrorDialog = false,
@ -579,7 +583,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
_logger.LogDebug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); _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; bool ranToCompletion;
@ -588,10 +592,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
{ {
StartProcess(processWrapper); StartProcess(processWrapper);
var timeoutMs = ConfigurationManager.Configuration.ImageExtractionTimeoutMs; var timeoutMs = _configurationManager.Configuration.ImageExtractionTimeoutMs;
if (timeoutMs <= 0) if (timeoutMs <= 0)
{ {
timeoutMs = DefaultImageExtractionTimeoutMs; timeoutMs = DefaultImageExtractionTimeout;
} }
ranToCompletion = await process.WaitForExitAsync(timeoutMs).ConfigureAwait(false); ranToCompletion = await process.WaitForExitAsync(timeoutMs).ConfigureAwait(false);
@ -607,7 +611,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
} }
var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1; 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) if (exitCode == -1 || !file.Exists || file.Length == 0)
{ {
@ -675,7 +679,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
args = analyzeDurationArgument + " " + args; args = analyzeDurationArgument + " " + args;
} }
var encodinghelper = new EncodingHelper(this, FileSystem, SubtitleEncoder());
if (videoStream != null) if (videoStream != null)
{ {
/* fix /* fix
@ -689,7 +692,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (!string.IsNullOrWhiteSpace(container)) if (!string.IsNullOrWhiteSpace(container))
{ {
var inputFormat = encodinghelper.GetInputFormat(container); var inputFormat = EncodingHelper.GetInputFormat(container);
if (!string.IsNullOrWhiteSpace(inputFormat)) if (!string.IsNullOrWhiteSpace(inputFormat))
{ {
args = "-f " + inputFormat + " " + args; args = "-f " + inputFormat + " " + args;
@ -700,7 +703,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{ {
CreateNoWindow = true, CreateNoWindow = true,
UseShellExecute = false, UseShellExecute = false,
FileName = FFmpegPath, FileName = _ffmpegPath,
Arguments = args, Arguments = args,
IsHidden = true, IsHidden = true,
ErrorDialog = false, ErrorDialog = false,
@ -713,7 +716,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
bool ranToCompletion = false; bool ranToCompletion = false;
using (var processWrapper = new ProcessWrapper(process, this, _logger)) using (var processWrapper = new ProcessWrapper(process, this))
{ {
try try
{ {
@ -736,10 +739,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
var jpegCount = FileSystem.GetFilePaths(targetDirectory) var jpegCount = _fileSystem.GetFilePaths(targetDirectory)
.Count(i => string.Equals(Path.GetExtension(i), ".jpg", StringComparison.OrdinalIgnoreCase)); .Count(i => string.Equals(Path.GetExtension(i), ".jpg", StringComparison.OrdinalIgnoreCase));
isResponsive = (jpegCount > lastCount); isResponsive = jpegCount > lastCount;
lastCount = jpegCount; lastCount = jpegCount;
} }
@ -770,7 +773,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{ {
process.Process.Start(); process.Process.Start();
lock (_runningProcesses) lock (_runningProcessesLock)
{ {
_runningProcesses.Add(process); _runningProcesses.Add(process);
} }
@ -804,7 +807,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
private void StopProcesses() private void StopProcesses()
{ {
List<ProcessWrapper> proceses; List<ProcessWrapper> proceses;
lock (_runningProcesses) lock (_runningProcessesLock)
{ {
proceses = _runningProcesses.ToList(); proceses = _runningProcesses.ToList();
_runningProcesses.Clear(); _runningProcesses.Clear();
@ -827,12 +830,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
return path.Replace('\\', '/').Replace(":", "\\:").Replace("'", "'\\\\\\''"); return path.Replace('\\', '/').Replace(":", "\\:").Replace("'", "'\\\\\\''");
} }
/// <summary> /// <inheritdoc />
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose() public void Dispose()
{ {
Dispose(true); Dispose(true);
GC.SuppressFinalize(this);
} }
/// <summary> /// <summary>
@ -852,11 +854,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
throw new NotImplementedException(); throw new NotImplementedException();
} }
public string[] GetPlayableStreamFileNames(string path, VideoType videoType)
{
throw new NotImplementedException();
}
public IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, IIsoMount isoMount, uint? titleNumber) public IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, IIsoMount isoMount, uint? titleNumber)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
@ -870,21 +867,24 @@ namespace MediaBrowser.MediaEncoding.Encoder
private class ProcessWrapper : IDisposable private class ProcessWrapper : IDisposable
{ {
public readonly IProcess Process;
public bool HasExited;
public int? ExitCode;
private readonly MediaEncoder _mediaEncoder; 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; Process = process;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_logger = logger; Process.Exited += OnProcessExited;
Process.Exited += Process_Exited;
} }
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; var process = (IProcess)sender;
@ -903,7 +903,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
private void DisposeProcess(IProcess process) private void DisposeProcess(IProcess process)
{ {
lock (_mediaEncoder._runningProcesses) lock (_mediaEncoder._runningProcessesLock)
{ {
_mediaEncoder._runningProcesses.Remove(this); _mediaEncoder._runningProcesses.Remove(this);
} }
@ -917,23 +917,18 @@ namespace MediaBrowser.MediaEncoding.Encoder
} }
} }
private bool _disposed;
private readonly object _syncLock = new object();
public void Dispose() public void Dispose()
{ {
lock (_syncLock) if (!_disposed)
{ {
if (!_disposed) if (Process != null)
{ {
if (Process != null) Process.Exited -= OnProcessExited;
{ DisposeProcess(Process);
Process.Exited -= Process_Exited;
DisposeProcess(Process);
}
} }
_disposed = true;
} }
_disposed = true;
} }
} }
} }

@ -5,7 +5,7 @@ using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.MediaEncoding.Subtitles namespace MediaBrowser.MediaEncoding.Subtitles
{ {
/// <summary> /// <summary>
/// Interface ISubtitleWriter /// Interface ISubtitleWriter.
/// </summary> /// </summary>
public interface ISubtitleWriter public interface ISubtitleWriter
{ {

@ -1,27 +1,39 @@
using System.IO; using System.IO;
using System.Text; using System.Text.Json;
using System.Threading; using System.Threading;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Serialization;
namespace MediaBrowser.MediaEncoding.Subtitles namespace MediaBrowser.MediaEncoding.Subtitles
{ {
/// <summary>
/// JSON subtitle writer.
/// </summary>
public class JsonWriter : ISubtitleWriter public class JsonWriter : ISubtitleWriter
{ {
private readonly IJsonSerializer _json; /// <inheritdoc />
public JsonWriter(IJsonSerializer json)
{
_json = json;
}
public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken) 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();
} }
} }
} }

@ -14,14 +14,19 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{ {
using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) 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(); cancellationToken.ThrowIfCancellationRequested();
writer.WriteLine(index.ToString(CultureInfo.InvariantCulture)); var trackEvent = trackEvents[i];
writer.WriteLine(@"{0:hh\:mm\:ss\,fff} --> {1:hh\:mm\:ss\,fff}", TimeSpan.FromTicks(trackEvent.StartPositionTicks), TimeSpan.FromTicks(trackEvent.EndPositionTicks));
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; var text = trackEvent.Text;
@ -29,9 +34,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
text = Regex.Replace(text, @"\\n", " ", RegexOptions.IgnoreCase); text = Regex.Replace(text, @"\\n", " ", RegexOptions.IgnoreCase);
writer.WriteLine(text); writer.WriteLine(text);
writer.WriteLine(string.Empty); writer.WriteLine();
index++;
} }
} }
} }

@ -17,7 +17,6 @@ using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using UtfUnknown; using UtfUnknown;
@ -30,28 +29,25 @@ namespace MediaBrowser.MediaEncoding.Subtitles
private readonly IApplicationPaths _appPaths; private readonly IApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly IJsonSerializer _json;
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
private readonly IProcessFactory _processFactory; private readonly IProcessFactory _processFactory;
public SubtitleEncoder( public SubtitleEncoder(
ILibraryManager libraryManager, ILibraryManager libraryManager,
ILoggerFactory loggerFactory, ILogger<SubtitleEncoder> logger,
IApplicationPaths appPaths, IApplicationPaths appPaths,
IFileSystem fileSystem, IFileSystem fileSystem,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
IJsonSerializer json,
IHttpClient httpClient, IHttpClient httpClient,
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
IProcessFactory processFactory) IProcessFactory processFactory)
{ {
_libraryManager = libraryManager; _libraryManager = libraryManager;
_logger = loggerFactory.CreateLogger(nameof(SubtitleEncoder)); _logger = logger;
_appPaths = appPaths; _appPaths = appPaths;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_json = json;
_httpClient = httpClient; _httpClient = httpClient;
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_processFactory = processFactory; _processFactory = processFactory;
@ -59,7 +55,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
private string SubtitleCachePath => Path.Combine(_appPaths.DataPath, "subtitles"); private string SubtitleCachePath => Path.Combine(_appPaths.DataPath, "subtitles");
private Stream ConvertSubtitles(Stream stream, private Stream ConvertSubtitles(
Stream stream,
string inputFormat, string inputFormat,
string outputFormat, string outputFormat,
long startTimeTicks, long startTimeTicks,
@ -170,7 +167,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
&& (mediaSource.VideoType.Value == VideoType.BluRay || mediaSource.VideoType.Value == VideoType.Dvd)) && (mediaSource.VideoType.Value == VideoType.BluRay || mediaSource.VideoType.Value == VideoType.Dvd))
{ {
var mediaSourceItem = (Video)_libraryManager.GetItemById(new Guid(mediaSource.Id)); var mediaSourceItem = (Video)_libraryManager.GetItemById(new Guid(mediaSource.Id));
inputFiles = mediaSourceItem.GetPlayableStreamFileNames(_mediaEncoder); inputFiles = mediaSourceItem.GetPlayableStreamFileNames();
} }
else else
{ {
@ -179,32 +176,27 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var fileInfo = await GetReadableFile(mediaSource.Path, inputFiles, mediaSource.Protocol, subtitleStream, cancellationToken).ConfigureAwait(false); 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); return (stream, fileInfo.Format);
} }
private async Task<Stream> GetSubtitleStream(string path, string language, MediaProtocol protocol, bool requiresCharset, CancellationToken cancellationToken) private async Task<Stream> GetSubtitleStream(string path, MediaProtocol protocol, bool requiresCharset, CancellationToken cancellationToken)
{ {
if (requiresCharset) if (requiresCharset)
{ {
var bytes = await GetBytes(path, protocol, cancellationToken).ConfigureAwait(false); using (var stream = await GetStream(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))
{ {
// Make sure we have all the code pages we can get var result = CharsetDetector.DetectFromStream(stream).Detected;
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
using (var inputStream = new MemoryStream(bytes)) if (result != null)
using (var reader = new StreamReader(inputStream, Encoding.GetEncoding(charset)))
{ {
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)) if (string.Equals(format, "json", StringComparison.OrdinalIgnoreCase))
{ {
return new JsonWriter(_json); return new JsonWriter();
} }
if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase)) if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase))
{ {
@ -544,7 +536,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{ {
if (!File.Exists(outputPath)) 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 finally
@ -572,8 +569,13 @@ namespace MediaBrowser.MediaEncoding.Subtitles
Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
var processArgs = string.Format("-i {0} -map 0:{1} -an -vn -c:s {2} \"{3}\"", inputPath, var processArgs = string.Format(
subtitleStreamIndex, outputCodec, outputPath); CultureInfo.InvariantCulture,
"-i {0} -map 0:{1} -an -vn -c:s {2} \"{3}\"",
inputPath,
subtitleStreamIndex,
outputCodec,
outputPath);
var process = _processFactory.Create(new ProcessOptions var process = _processFactory.Create(new ProcessOptions
{ {
@ -721,41 +723,38 @@ namespace MediaBrowser.MediaEncoding.Subtitles
} }
} }
/// <inheritdoc />
public async Task<string> GetSubtitleFileCharacterSet(string path, string language, MediaProtocol protocol, CancellationToken cancellationToken) public async Task<string> GetSubtitleFileCharacterSet(string path, string language, MediaProtocol protocol, CancellationToken cancellationToken)
{ {
var bytes = await GetBytes(path, protocol, cancellationToken).ConfigureAwait(false); using (var stream = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false))
{
var charset = CharsetDetector.DetectFromBytes(bytes).Detected?.EncodingName; 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<byte[]> GetBytes(string path, MediaProtocol protocol, CancellationToken cancellationToken) private Task<Stream> GetStream(string path, MediaProtocol protocol, CancellationToken cancellationToken)
{ {
if (protocol == MediaProtocol.Http) switch (protocol)
{ {
var opts = new HttpRequestOptions() case MediaProtocol.Http:
{ var opts = new HttpRequestOptions()
Url = path, {
CancellationToken = cancellationToken Url = path,
}; CancellationToken = cancellationToken,
using (var file = await _httpClient.Get(opts).ConfigureAwait(false)) BufferContent = true
using (var memoryStream = new MemoryStream()) };
{
await file.CopyToAsync(memoryStream).ConfigureAwait(false);
memoryStream.Position = 0;
return memoryStream.ToArray(); return _httpClient.Get(opts);
}
}
if (protocol == MediaProtocol.File)
{
return File.ReadAllBytes(path);
}
throw new ArgumentOutOfRangeException(nameof(protocol)); case MediaProtocol.File:
return Task.FromResult<Stream>(File.OpenRead(path));
default:
throw new ArgumentOutOfRangeException(nameof(protocol));
}
} }
} }
} }

@ -49,12 +49,5 @@ namespace MediaBrowser.MediaEncoding.Subtitles
writer.WriteLine("</tt>"); writer.WriteLine("</tt>");
} }
} }
private string FormatTime(long ticks)
{
var time = TimeSpan.FromTicks(ticks);
return string.Format(@"{0:hh\:mm\:ss\,fff}", time);
}
} }
} }

@ -233,7 +233,6 @@ namespace MediaBrowser.Model.Configuration
LocalNetworkSubnets = Array.Empty<string>(); LocalNetworkSubnets = Array.Empty<string>();
LocalNetworkAddresses = Array.Empty<string>(); LocalNetworkAddresses = Array.Empty<string>();
CodecsUsed = Array.Empty<string>(); CodecsUsed = Array.Empty<string>();
ImageExtractionTimeoutMs = 0;
PathSubstitutions = Array.Empty<PathSubstitution>(); PathSubstitutions = Array.Empty<PathSubstitution>();
IgnoreVirtualInterfaces = false; IgnoreVirtualInterfaces = false;
EnableSimpleArtistDetection = true; EnableSimpleArtistDetection = true;

@ -1,12 +1,15 @@
using System;
using System.Collections.Generic;
namespace MediaBrowser.Model.MediaInfo namespace MediaBrowser.Model.MediaInfo
{ {
public class SubtitleTrackInfo public class SubtitleTrackInfo
{ {
public SubtitleTrackEvent[] TrackEvents { get; set; } public IReadOnlyList<SubtitleTrackEvent> TrackEvents { get; set; }
public SubtitleTrackInfo() public SubtitleTrackInfo()
{ {
TrackEvents = new SubtitleTrackEvent[] { }; TrackEvents = Array.Empty<SubtitleTrackEvent>();
} }
} }
} }

@ -62,7 +62,11 @@ namespace MediaBrowser.Providers.MediaInfo
{ {
var protocol = item.PathProtocol ?? MediaProtocol.File; 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 = var mediaStreams =
item.GetMediaStreams(); item.GetMediaStreams();

Loading…
Cancel
Save