From 95f344722ce34dde75408bce4138fe47b7eef6d0 Mon Sep 17 00:00:00 2001 From: cvium Date: Sat, 11 Sep 2021 16:20:49 +0200 Subject: Don't set ffmpeg path from null to its Display value + add 404 --- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'MediaBrowser.MediaEncoding/Encoder') diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 4cbd1bbc8..057293b66 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -151,6 +151,16 @@ namespace MediaBrowser.MediaEncoding.Encoder /// The path type. public void UpdateEncoderPath(string path, string pathType) { + var config = _configurationManager.GetEncodingOptions(); + + // Filesystem may not be case insensitive, but EncoderAppPathDisplay should always point to a valid file? + if (string.IsNullOrEmpty(config.EncoderAppPath) + && string.Equals(config.EncoderAppPathDisplay, path, StringComparison.OrdinalIgnoreCase)) + { + _logger.LogDebug("Existing ffmpeg path is empty and the new path is the same as {EncoderAppPathDisplay}. Skipping", nameof(config.EncoderAppPathDisplay)); + return; + } + string newPath; _logger.LogInformation("Attempting to update encoder path to {Path}. pathType: {PathType}", path ?? string.Empty, pathType ?? string.Empty); @@ -183,9 +193,14 @@ namespace MediaBrowser.MediaEncoding.Encoder } } + // Don't save an invalid path + if (!File.Exists(path)) + { + throw new FileNotFoundException(); + } + // Write the new ffmpeg path to the xml as // This ensures its not lost on next startup - var config = _configurationManager.GetEncodingOptions(); config.EncoderAppPath = newPath; _configurationManager.SaveConfiguration("encoding", config); -- cgit v1.2.3 From a353081ea33b3f635e9472371f228bab63669347 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 21 Sep 2021 22:54:11 +0200 Subject: Update MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs --- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 6 ------ 1 file changed, 6 deletions(-) (limited to 'MediaBrowser.MediaEncoding/Encoder') diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 057293b66..22ed9a8eb 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -193,12 +193,6 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - // Don't save an invalid path - if (!File.Exists(path)) - { - throw new FileNotFoundException(); - } - // Write the new ffmpeg path to the xml as // This ensures its not lost on next startup config.EncoderAppPath = newPath; -- cgit v1.2.3 From 9234e5bf80194e45acac25c60cb76f401bffaf96 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sun, 26 Sep 2021 08:14:36 -0600 Subject: Remove all instances of en-US culture --- Emby.Dlna/Didl/DidlBuilder.cs | 28 +++++++-------- Emby.Dlna/Eventing/DlnaEventManager.cs | 8 ++--- Emby.Dlna/PlayTo/Device.cs | 12 +++---- Emby.Dlna/PlayTo/PlayToController.cs | 8 ++--- Emby.Dlna/PlayTo/SsdpHttpClient.cs | 8 ++--- Emby.Dlna/Server/DescriptionXmlBuilder.cs | 5 ++- .../MediaEncoder/EncodingManager.cs | 3 +- Jellyfin.Api/Controllers/ImageController.cs | 2 +- Jellyfin.Api/Controllers/SubtitleController.cs | 2 +- MediaBrowser.Controller/Entities/Year.cs | 4 +-- .../MediaEncoding/EncodingHelper.cs | 42 +++++++++++----------- MediaBrowser.Controller/MediaEncoding/JobLogger.cs | 11 +++--- .../Images/LocalImageProvider.cs | 4 +-- .../Parsers/BaseItemXmlParser.cs | 8 ++--- MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs | 12 +++---- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 11 ++---- .../Probing/ProbeResultNormalizer.cs | 27 +++++++------- .../Globalization/ILocalizationManager.cs | 4 +-- MediaBrowser.Providers/Manager/ImageSaver.cs | 18 +++++----- .../Plugins/Omdb/OmdbProvider.cs | 13 ++++--- MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs | 10 +++--- .../Parsers/EpisodeNfoParser.cs | 10 +++--- .../Savers/EpisodeNfoSaver.cs | 18 +++++----- 23 files changed, 116 insertions(+), 152 deletions(-) (limited to 'MediaBrowser.MediaEncoding/Encoder') diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index c00078499..0a84f30c4 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -41,8 +41,6 @@ namespace Emby.Dlna.Didl private const string NsUpnp = "urn:schemas-upnp-org:metadata-1-0/upnp/"; private const string NsDlna = "urn:schemas-dlna-org:metadata-1-0/"; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - private readonly DeviceProfile _profile; private readonly IImageProcessor _imageProcessor; private readonly string _serverAddress; @@ -317,7 +315,7 @@ namespace Emby.Dlna.Didl if (mediaSource.RunTimeTicks.HasValue) { - writer.WriteAttributeString("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture)); + writer.WriteAttributeString("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", CultureInfo.InvariantCulture)); } if (filter.Contains("res@size")) @@ -328,7 +326,7 @@ namespace Emby.Dlna.Didl if (size.HasValue) { - writer.WriteAttributeString("size", size.Value.ToString(_usCulture)); + writer.WriteAttributeString("size", size.Value.ToString(CultureInfo.InvariantCulture)); } } } @@ -342,7 +340,7 @@ namespace Emby.Dlna.Didl if (targetChannels.HasValue) { - writer.WriteAttributeString("nrAudioChannels", targetChannels.Value.ToString(_usCulture)); + writer.WriteAttributeString("nrAudioChannels", targetChannels.Value.ToString(CultureInfo.InvariantCulture)); } if (filter.Contains("res@resolution")) @@ -361,12 +359,12 @@ namespace Emby.Dlna.Didl if (targetSampleRate.HasValue) { - writer.WriteAttributeString("sampleFrequency", targetSampleRate.Value.ToString(_usCulture)); + writer.WriteAttributeString("sampleFrequency", targetSampleRate.Value.ToString(CultureInfo.InvariantCulture)); } if (totalBitrate.HasValue) { - writer.WriteAttributeString("bitrate", totalBitrate.Value.ToString(_usCulture)); + writer.WriteAttributeString("bitrate", totalBitrate.Value.ToString(CultureInfo.InvariantCulture)); } var mediaProfile = _profile.GetVideoMediaProfile( @@ -552,7 +550,7 @@ namespace Emby.Dlna.Didl if (mediaSource.RunTimeTicks.HasValue) { - writer.WriteAttributeString("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture)); + writer.WriteAttributeString("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", CultureInfo.InvariantCulture)); } if (filter.Contains("res@size")) @@ -563,7 +561,7 @@ namespace Emby.Dlna.Didl if (size.HasValue) { - writer.WriteAttributeString("size", size.Value.ToString(_usCulture)); + writer.WriteAttributeString("size", size.Value.ToString(CultureInfo.InvariantCulture)); } } } @@ -575,17 +573,17 @@ namespace Emby.Dlna.Didl if (targetChannels.HasValue) { - writer.WriteAttributeString("nrAudioChannels", targetChannels.Value.ToString(_usCulture)); + writer.WriteAttributeString("nrAudioChannels", targetChannels.Value.ToString(CultureInfo.InvariantCulture)); } if (targetSampleRate.HasValue) { - writer.WriteAttributeString("sampleFrequency", targetSampleRate.Value.ToString(_usCulture)); + writer.WriteAttributeString("sampleFrequency", targetSampleRate.Value.ToString(CultureInfo.InvariantCulture)); } if (targetAudioBitrate.HasValue) { - writer.WriteAttributeString("bitrate", targetAudioBitrate.Value.ToString(_usCulture)); + writer.WriteAttributeString("bitrate", targetAudioBitrate.Value.ToString(CultureInfo.InvariantCulture)); } var mediaProfile = _profile.GetAudioMediaProfile( @@ -639,7 +637,7 @@ namespace Emby.Dlna.Didl writer.WriteAttributeString("restricted", "1"); writer.WriteAttributeString("searchable", "1"); - writer.WriteAttributeString("childCount", childCount.ToString(_usCulture)); + writer.WriteAttributeString("childCount", childCount.ToString(CultureInfo.InvariantCulture)); var clientId = GetClientId(folder, stubType); @@ -931,11 +929,11 @@ namespace Emby.Dlna.Didl if (item.IndexNumber.HasValue) { - AddValue(writer, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(_usCulture), NsUpnp); + AddValue(writer, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(CultureInfo.InvariantCulture), NsUpnp); if (item is Episode) { - AddValue(writer, "upnp", "episodeNumber", item.IndexNumber.Value.ToString(_usCulture), NsUpnp); + AddValue(writer, "upnp", "episodeNumber", item.IndexNumber.Value.ToString(CultureInfo.InvariantCulture), NsUpnp); } } } diff --git a/Emby.Dlna/Eventing/DlnaEventManager.cs b/Emby.Dlna/Eventing/DlnaEventManager.cs index b39bd5ce9..d17e23871 100644 --- a/Emby.Dlna/Eventing/DlnaEventManager.cs +++ b/Emby.Dlna/Eventing/DlnaEventManager.cs @@ -26,8 +26,6 @@ namespace Emby.Dlna.Eventing private readonly ILogger _logger; private readonly IHttpClientFactory _httpClientFactory; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - public DlnaEventManager(ILogger logger, IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; @@ -83,7 +81,7 @@ namespace Emby.Dlna.Eventing if (!string.IsNullOrEmpty(header)) { // Starts with SECOND- - if (int.TryParse(header.AsSpan().RightPart('-'), NumberStyles.Integer, _usCulture, out var val)) + if (int.TryParse(header.AsSpan().RightPart('-'), NumberStyles.Integer, CultureInfo.InvariantCulture, out var val)) { return val; } @@ -106,7 +104,7 @@ namespace Emby.Dlna.Eventing var response = new EventSubscriptionResponse(string.Empty, "text/plain"); response.Headers["SID"] = subscriptionId; - response.Headers["TIMEOUT"] = string.IsNullOrEmpty(requestedTimeoutString) ? ("SECOND-" + timeoutSeconds.ToString(_usCulture)) : requestedTimeoutString; + response.Headers["TIMEOUT"] = string.IsNullOrEmpty(requestedTimeoutString) ? ("SECOND-" + timeoutSeconds.ToString(CultureInfo.InvariantCulture)) : requestedTimeoutString; return response; } @@ -163,7 +161,7 @@ namespace Emby.Dlna.Eventing options.Headers.TryAddWithoutValidation("NT", subscription.NotificationType); options.Headers.TryAddWithoutValidation("NTS", "upnp:propchange"); options.Headers.TryAddWithoutValidation("SID", subscription.Id); - options.Headers.TryAddWithoutValidation("SEQ", subscription.TriggerCount.ToString(_usCulture)); + options.Headers.TryAddWithoutValidation("SEQ", subscription.TriggerCount.ToString(CultureInfo.InvariantCulture)); try { diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs index 11fcd81cf..0b2288000 100644 --- a/Emby.Dlna/PlayTo/Device.cs +++ b/Emby.Dlna/PlayTo/Device.cs @@ -20,8 +20,6 @@ namespace Emby.Dlna.PlayTo { public class Device : IDisposable { - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - private readonly IHttpClientFactory _httpClientFactory; private readonly ILogger _logger; @@ -640,7 +638,7 @@ namespace Emby.Dlna.PlayTo return; } - Volume = int.Parse(volumeValue, UsCulture); + Volume = int.Parse(volumeValue, CultureInfo.InvariantCulture); if (Volume > 0) { @@ -842,7 +840,7 @@ namespace Emby.Dlna.PlayTo if (!string.IsNullOrWhiteSpace(duration) && !string.Equals(duration, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase)) { - Duration = TimeSpan.Parse(duration, UsCulture); + Duration = TimeSpan.Parse(duration, CultureInfo.InvariantCulture); } else { @@ -854,7 +852,7 @@ namespace Emby.Dlna.PlayTo if (!string.IsNullOrWhiteSpace(position) && !string.Equals(position, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase)) { - Position = TimeSpan.Parse(position, UsCulture); + Position = TimeSpan.Parse(position, CultureInfo.InvariantCulture); } var track = result.Document.Descendants("TrackMetaData").FirstOrDefault(); @@ -1194,8 +1192,8 @@ namespace Emby.Dlna.PlayTo var depth = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("depth")); var url = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("url")); - var widthValue = int.Parse(width, NumberStyles.Integer, UsCulture); - var heightValue = int.Parse(height, NumberStyles.Integer, UsCulture); + var widthValue = int.Parse(width, NumberStyles.Integer, CultureInfo.InvariantCulture); + var heightValue = int.Parse(height, NumberStyles.Integer, CultureInfo.InvariantCulture); return new DeviceIcon { diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 0e49fd2c0..f25d8017e 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -30,8 +30,6 @@ namespace Emby.Dlna.PlayTo { public class PlayToController : ISessionController, IDisposable { - private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US")); - private readonly SessionInfo _session; private readonly ISessionManager _sessionManager; private readonly ILibraryManager _libraryManager; @@ -716,7 +714,7 @@ namespace Emby.Dlna.PlayTo case GeneralCommandType.SetAudioStreamIndex: if (command.Arguments.TryGetValue("Index", out string index)) { - if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val)) + if (int.TryParse(index, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val)) { return SetAudioStreamIndex(val); } @@ -728,7 +726,7 @@ namespace Emby.Dlna.PlayTo case GeneralCommandType.SetSubtitleStreamIndex: if (command.Arguments.TryGetValue("Index", out index)) { - if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val)) + if (int.TryParse(index, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val)) { return SetSubtitleStreamIndex(val); } @@ -740,7 +738,7 @@ namespace Emby.Dlna.PlayTo case GeneralCommandType.SetVolume: if (command.Arguments.TryGetValue("Volume", out string vol)) { - if (int.TryParse(vol, NumberStyles.Integer, _usCulture, out var volume)) + if (int.TryParse(vol, NumberStyles.Integer, CultureInfo.InvariantCulture, out var volume)) { return _device.SetVolume(volume, cancellationToken); } diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs index 4b92fbff4..cade7b4c2 100644 --- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs +++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs @@ -20,8 +20,6 @@ namespace Emby.Dlna.PlayTo private const string USERAGENT = "Microsoft-Windows/6.2 UPnP/1.0 Microsoft-DLNA DLNADOC/1.50"; private const string FriendlyName = "Jellyfin"; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - private readonly IHttpClientFactory _httpClientFactory; public SsdpHttpClient(IHttpClientFactory httpClientFactory) @@ -80,10 +78,10 @@ namespace Emby.Dlna.PlayTo { using var options = new HttpRequestMessage(new HttpMethod("SUBSCRIBE"), url); options.Headers.UserAgent.ParseAdd(USERAGENT); - options.Headers.TryAddWithoutValidation("HOST", ip + ":" + port.ToString(_usCulture)); - options.Headers.TryAddWithoutValidation("CALLBACK", "<" + localIp + ":" + eventport.ToString(_usCulture) + ">"); + options.Headers.TryAddWithoutValidation("HOST", ip + ":" + port.ToString(CultureInfo.InvariantCulture)); + options.Headers.TryAddWithoutValidation("CALLBACK", "<" + localIp + ":" + eventport.ToString(CultureInfo.InvariantCulture) + ">"); options.Headers.TryAddWithoutValidation("NT", "upnp:event"); - options.Headers.TryAddWithoutValidation("TIMEOUT", "Second-" + timeOut.ToString(_usCulture)); + options.Headers.TryAddWithoutValidation("TIMEOUT", "Second-" + timeOut.ToString(CultureInfo.InvariantCulture)); using var response = await _httpClientFactory.CreateClient(NamedClient.Default) .SendAsync(options, HttpCompletionOption.ResponseHeadersRead) diff --git a/Emby.Dlna/Server/DescriptionXmlBuilder.cs b/Emby.Dlna/Server/DescriptionXmlBuilder.cs index 09525aae4..80a45f2b2 100644 --- a/Emby.Dlna/Server/DescriptionXmlBuilder.cs +++ b/Emby.Dlna/Server/DescriptionXmlBuilder.cs @@ -15,7 +15,6 @@ namespace Emby.Dlna.Server { private readonly DeviceProfile _profile; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly string _serverUdn; private readonly string _serverAddress; private readonly string _serverName; @@ -193,10 +192,10 @@ namespace Emby.Dlna.Server .Append(SecurityElement.Escape(icon.MimeType ?? string.Empty)) .Append(""); builder.Append("") - .Append(SecurityElement.Escape(icon.Width.ToString(_usCulture))) + .Append(SecurityElement.Escape(icon.Width.ToString(CultureInfo.InvariantCulture))) .Append(""); builder.Append("") - .Append(SecurityElement.Escape(icon.Height.ToString(_usCulture))) + .Append(SecurityElement.Escape(icon.Height.ToString(CultureInfo.InvariantCulture))) .Append(""); builder.Append("") .Append(SecurityElement.Escape(icon.Depth ?? string.Empty)) diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index 8aaa1f7bb..ac6606d39 100644 --- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -23,7 +23,6 @@ namespace Emby.Server.Implementations.MediaEncoder { public class EncodingManager : IEncodingManager { - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly IFileSystem _fileSystem; private readonly ILogger _logger; private readonly IMediaEncoder _encoder; @@ -193,7 +192,7 @@ namespace Emby.Server.Implementations.MediaEncoder private string GetChapterImagePath(Video video, long chapterPositionTicks) { - var filename = video.DateModified.Ticks.ToString(_usCulture) + "_" + chapterPositionTicks.ToString(_usCulture) + ".jpg"; + var filename = video.DateModified.Ticks.ToString(CultureInfo.InvariantCulture) + "_" + chapterPositionTicks.ToString(CultureInfo.InvariantCulture) + ".jpg"; return Path.Combine(GetChapterImagesPath(video), filename); } diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index b1c860d61..86933074d 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -2007,7 +2007,7 @@ namespace Jellyfin.Api.Controllers Response.Headers.Add(HeaderNames.CacheControl, "public"); } - Response.Headers.Add(HeaderNames.LastModified, dateImageModified.ToUniversalTime().ToString("ddd, dd MMM yyyy HH:mm:ss \"GMT\"", new CultureInfo("en-US", false))); + Response.Headers.Add(HeaderNames.LastModified, dateImageModified.ToUniversalTime().ToString("ddd, dd MMM yyyy HH:mm:ss \"GMT\"", CultureInfo.InvariantCulture)); // if the image was not modified since "ifModifiedSinceHeader"-header, return a HTTP status code 304 not modified if (!(dateImageModified > ifModifiedSinceHeader) && cacheDuration.HasValue) diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs index 11f67ee89..1849dd047 100644 --- a/Jellyfin.Api/Controllers/SubtitleController.cs +++ b/Jellyfin.Api/Controllers/SubtitleController.cs @@ -376,7 +376,7 @@ namespace Jellyfin.Api.Controllers var endPositionTicks = Math.Min(runtime, positionTicks + segmentLengthTicks); var url = string.Format( - CultureInfo.CurrentCulture, + CultureInfo.InvariantCulture, "stream.vtt?CopyTimestamps=true&AddVttTimeMap=true&StartPositionTicks={0}&EndPositionTicks={1}&api_key={2}", positionTicks.ToString(CultureInfo.InvariantCulture), endPositionTicks.ToString(CultureInfo.InvariantCulture), diff --git a/MediaBrowser.Controller/Entities/Year.cs b/MediaBrowser.Controller/Entities/Year.cs index 0853200dd..afdaf448b 100644 --- a/MediaBrowser.Controller/Entities/Year.cs +++ b/MediaBrowser.Controller/Entities/Year.cs @@ -57,9 +57,7 @@ namespace MediaBrowser.Controller.Entities public IList GetTaggedItems(InternalItemsQuery query) { - var usCulture = new CultureInfo("en-US"); - - if (!int.TryParse(Name, NumberStyles.Integer, usCulture, out var year)) + if (!int.TryParse(Name, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year)) { return new List(); } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index bdb379332..5715194b8 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -21,8 +21,6 @@ namespace MediaBrowser.Controller.MediaEncoding { public class EncodingHelper { - private static readonly CultureInfo _usCulture = new CultureInfo("en-US"); - private readonly IMediaEncoder _mediaEncoder; private readonly ISubtitleEncoder _subtitleEncoder; @@ -816,7 +814,7 @@ namespace MediaBrowser.Controller.MediaEncoding public static string NormalizeTranscodingLevel(EncodingJobInfo state, string level) { - if (double.TryParse(level, NumberStyles.Any, _usCulture, out double requestLevel)) + if (double.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out double requestLevel)) { if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase) || string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)) @@ -911,7 +909,7 @@ namespace MediaBrowser.Controller.MediaEncoding CultureInfo.InvariantCulture, "subtitles='{0}:si={1}'{2}", _mediaEncoder.EscapeSubtitleFilterPath(mediaPath), - state.InternalSubtitleStreamOffset.ToString(_usCulture), + state.InternalSubtitleStreamOffset.ToString(CultureInfo.InvariantCulture), // fallbackFontParam, setPtsParam); } @@ -1217,7 +1215,7 @@ namespace MediaBrowser.Controller.MediaEncoding param += string.Format( CultureInfo.InvariantCulture, " -speed 16 -quality good -profile:v {0} -slices 8 -crf {1} -qmin {2} -qmax {3}", - profileScore.ToString(_usCulture), + profileScore.ToString(CultureInfo.InvariantCulture), crf, qmin, qmax); @@ -1289,7 +1287,7 @@ namespace MediaBrowser.Controller.MediaEncoding var framerate = GetFramerateParam(state); if (framerate.HasValue) { - param += string.Format(CultureInfo.InvariantCulture, " -r {0}", framerate.Value.ToString(_usCulture)); + param += string.Format(CultureInfo.InvariantCulture, " -r {0}", framerate.Value.ToString(CultureInfo.InvariantCulture)); } var targetVideoCodec = state.ActualOutputVideoCodec; @@ -1393,7 +1391,7 @@ namespace MediaBrowser.Controller.MediaEncoding else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase)) { // hevc_qsv use -level 51 instead of -level 153. - if (double.TryParse(level, NumberStyles.Any, _usCulture, out double hevcLevel)) + if (double.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out double hevcLevel)) { param += " -level " + (hevcLevel / 3); } @@ -1555,7 +1553,7 @@ namespace MediaBrowser.Controller.MediaEncoding // If a specific level was requested, the source must match or be less than var level = state.GetRequestedLevel(videoStream.Codec); if (!string.IsNullOrEmpty(level) - && double.TryParse(level, NumberStyles.Any, _usCulture, out var requestLevel)) + && double.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out var requestLevel)) { if (!videoStream.Level.HasValue) { @@ -1803,7 +1801,7 @@ namespace MediaBrowser.Controller.MediaEncoding && state.AudioStream.Channels.Value > 5 && !encodingOptions.DownMixAudioBoost.Equals(1)) { - filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(_usCulture)); + filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(CultureInfo.InvariantCulture)); } var isCopyingTimestamps = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive; @@ -2434,8 +2432,8 @@ namespace MediaBrowser.Controller.MediaEncoding { if (isExynosV4L2) { - var widthParam = requestedWidth.Value.ToString(_usCulture); - var heightParam = requestedHeight.Value.ToString(_usCulture); + var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture); + var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture); filters.Add( string.Format( @@ -2453,8 +2451,8 @@ namespace MediaBrowser.Controller.MediaEncoding // If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size else if (requestedMaxWidth.HasValue && requestedMaxHeight.HasValue) { - var maxWidthParam = requestedMaxWidth.Value.ToString(_usCulture); - var maxHeightParam = requestedMaxHeight.Value.ToString(_usCulture); + var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture); + var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture); if (isExynosV4L2) { @@ -2486,7 +2484,7 @@ namespace MediaBrowser.Controller.MediaEncoding } else { - var widthParam = requestedWidth.Value.ToString(_usCulture); + var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture); filters.Add( string.Format( @@ -2499,7 +2497,7 @@ namespace MediaBrowser.Controller.MediaEncoding // If a fixed height was requested else if (requestedHeight.HasValue) { - var heightParam = requestedHeight.Value.ToString(_usCulture); + var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture); if (isExynosV4L2) { @@ -2522,7 +2520,7 @@ namespace MediaBrowser.Controller.MediaEncoding // If a max width was requested else if (requestedMaxWidth.HasValue) { - var maxWidthParam = requestedMaxWidth.Value.ToString(_usCulture); + var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture); if (isExynosV4L2) { @@ -2545,7 +2543,7 @@ namespace MediaBrowser.Controller.MediaEncoding // If a max height was requested else if (requestedMaxHeight.HasValue) { - var maxHeightParam = requestedMaxHeight.Value.ToString(_usCulture); + var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture); if (isExynosV4L2) { @@ -4122,12 +4120,12 @@ namespace MediaBrowser.Controller.MediaEncoding if (bitrate.HasValue) { - args += " -ab " + bitrate.Value.ToString(_usCulture); + args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture); } if (state.OutputAudioSampleRate.HasValue) { - args += " -ar " + state.OutputAudioSampleRate.Value.ToString(_usCulture); + args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture); } args += GetAudioFilterParam(state, encodingOptions); @@ -4143,12 +4141,12 @@ namespace MediaBrowser.Controller.MediaEncoding if (bitrate.HasValue) { - audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(_usCulture)); + audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture)); } if (state.OutputAudioChannels.HasValue) { - audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(_usCulture)); + audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture)); } // opus will fail on 44100 @@ -4156,7 +4154,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (state.OutputAudioSampleRate.HasValue) { - audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(_usCulture)); + audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture)); } } diff --git a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs index c4ddc5618..933f440ac 100644 --- a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs +++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs @@ -13,7 +13,6 @@ namespace MediaBrowser.Controller.MediaEncoding { public class JobLogger { - private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US")); private readonly ILogger _logger; public JobLogger(ILogger logger) @@ -87,7 +86,7 @@ namespace MediaBrowser.Controller.MediaEncoding { var rate = parts[i + 1]; - if (float.TryParse(rate, NumberStyles.Any, _usCulture, out var val)) + if (float.TryParse(rate, NumberStyles.Any, CultureInfo.InvariantCulture, out var val)) { framerate = val; } @@ -96,7 +95,7 @@ namespace MediaBrowser.Controller.MediaEncoding { var rate = part.Split('=', 2)[^1]; - if (float.TryParse(rate, NumberStyles.Any, _usCulture, out var val)) + if (float.TryParse(rate, NumberStyles.Any, CultureInfo.InvariantCulture, out var val)) { framerate = val; } @@ -106,7 +105,7 @@ namespace MediaBrowser.Controller.MediaEncoding { var time = part.Split('=', 2)[^1]; - if (TimeSpan.TryParse(time, _usCulture, out var val)) + if (TimeSpan.TryParse(time, CultureInfo.InvariantCulture, out var val)) { var currentMs = startMs + val.TotalMilliseconds; @@ -128,7 +127,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (scale.HasValue) { - if (long.TryParse(size, NumberStyles.Any, _usCulture, out var val)) + if (long.TryParse(size, NumberStyles.Any, CultureInfo.InvariantCulture, out var val)) { bytesTranscoded = val * scale.Value; } @@ -147,7 +146,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (scale.HasValue) { - if (float.TryParse(rate, NumberStyles.Any, _usCulture, out var val)) + if (float.TryParse(rate, NumberStyles.Any, CultureInfo.InvariantCulture, out var val)) { bitRate = (int)Math.Ceiling(val * scale.Value); } diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs index b7398880e..988581df9 100644 --- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs @@ -60,8 +60,6 @@ namespace MediaBrowser.LocalMetadata.Images private readonly IFileSystem _fileSystem; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - /// /// Initializes a new instance of the class. /// @@ -434,7 +432,7 @@ namespace MediaBrowser.LocalMetadata.Images var seasonMarker = seasonNumber.Value == 0 ? "-specials" - : seasonNumber.Value.ToString("00", _usCulture); + : seasonNumber.Value.ToString("00", CultureInfo.InvariantCulture); // Get this one directly from the file system since we have to go up a level if (!string.Equals(prefix, seasonMarker, StringComparison.OrdinalIgnoreCase)) diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs index 7c9e681d6..5a36c1663 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs @@ -21,8 +21,6 @@ namespace MediaBrowser.LocalMetadata.Parsers public class BaseItemXmlParser where T : BaseItem { - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - private Dictionary? _validProviderIds; /// @@ -180,7 +178,7 @@ namespace MediaBrowser.LocalMetadata.Parsers if (!string.IsNullOrEmpty(text)) { - if (float.TryParse(text, NumberStyles.Any, _usCulture, out var value)) + if (float.TryParse(text, NumberStyles.Any, CultureInfo.InvariantCulture, out var value)) { item.CriticRating = value; } @@ -332,7 +330,7 @@ namespace MediaBrowser.LocalMetadata.Parsers if (!string.IsNullOrWhiteSpace(text)) { - if (int.TryParse(text.AsSpan().LeftPart(' '), NumberStyles.Integer, _usCulture, out var runtime)) + if (int.TryParse(text.AsSpan().LeftPart(' '), NumberStyles.Integer, CultureInfo.InvariantCulture, out var runtime)) { item.RunTimeTicks = TimeSpan.FromMinutes(runtime).Ticks; } @@ -1095,7 +1093,7 @@ namespace MediaBrowser.LocalMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { - if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var intVal)) + if (int.TryParse(val, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intVal)) { sortOrder = intVal; } diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs index 6a3896eb6..6f66fd61b 100644 --- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs @@ -25,8 +25,6 @@ namespace MediaBrowser.LocalMetadata.Savers /// public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss"; - private static readonly CultureInfo _usCulture = new CultureInfo("en-US"); - /// /// Initializes a new instance of the class. /// @@ -205,7 +203,7 @@ namespace MediaBrowser.LocalMetadata.Savers if (item.CriticRating.HasValue) { - writer.WriteElementString("CriticRating", item.CriticRating.Value.ToString(_usCulture)); + writer.WriteElementString("CriticRating", item.CriticRating.Value.ToString(CultureInfo.InvariantCulture)); } if (!string.IsNullOrEmpty(item.Overview)) @@ -289,12 +287,12 @@ namespace MediaBrowser.LocalMetadata.Savers if (item.CommunityRating.HasValue) { - writer.WriteElementString("Rating", item.CommunityRating.Value.ToString(_usCulture)); + writer.WriteElementString("Rating", item.CommunityRating.Value.ToString(CultureInfo.InvariantCulture)); } if (item.ProductionYear.HasValue && item is not Person) { - writer.WriteElementString("ProductionYear", item.ProductionYear.Value.ToString(_usCulture)); + writer.WriteElementString("ProductionYear", item.ProductionYear.Value.ToString(CultureInfo.InvariantCulture)); } if (item is IHasAspectRatio hasAspectRatio) @@ -322,7 +320,7 @@ namespace MediaBrowser.LocalMetadata.Savers { var timespan = TimeSpan.FromTicks(runTimeTicks.Value); - writer.WriteElementString("RunningTime", Math.Floor(timespan.TotalMinutes).ToString(_usCulture)); + writer.WriteElementString("RunningTime", Math.Floor(timespan.TotalMinutes).ToString(CultureInfo.InvariantCulture)); } if (item.ProviderIds != null) @@ -395,7 +393,7 @@ namespace MediaBrowser.LocalMetadata.Savers if (person.SortOrder.HasValue) { - writer.WriteElementString("SortOrder", person.SortOrder.Value.ToString(_usCulture)); + writer.WriteElementString("SortOrder", person.SortOrder.Value.ToString(CultureInfo.InvariantCulture)); } writer.WriteEndElement(); diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 4cbd1bbc8..06fe95ce8 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -43,11 +43,6 @@ namespace MediaBrowser.MediaEncoding.Encoder /// internal const int DefaultHdrImageExtractionTimeout = 20000; - /// - /// The us culture. - /// - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - private readonly ILogger _logger; private readonly IServerConfigurationManager _configurationManager; private readonly IFileSystem _fileSystem; @@ -687,7 +682,7 @@ namespace MediaBrowser.MediaEncoding.Encoder public string GetTimeParameter(TimeSpan time) { - return time.ToString(@"hh\:mm\:ss\.fff", _usCulture); + return time.ToString(@"hh\:mm\:ss\.fff", CultureInfo.InvariantCulture); } public async Task ExtractVideoImagesOnInterval( @@ -704,11 +699,11 @@ namespace MediaBrowser.MediaEncoding.Encoder { var inputArgument = GetInputArgument(inputFile, mediaSource); - var vf = "fps=fps=1/" + interval.TotalSeconds.ToString(_usCulture); + var vf = "fps=fps=1/" + interval.TotalSeconds.ToString(CultureInfo.InvariantCulture); if (maxWidth.HasValue) { - var maxWidthParam = maxWidth.Value.ToString(_usCulture); + var maxWidthParam = maxWidth.Value.ToString(CultureInfo.InvariantCulture); vf += string.Format(CultureInfo.InvariantCulture, ",scale=min(iw\\,{0}):trunc(ow/dar/2)*2", maxWidthParam); } diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 26f629a31..9ed6c264e 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -30,7 +30,6 @@ namespace MediaBrowser.MediaEncoding.Probing private static readonly Regex _performerPattern = new (@"(?.*) \((?.*)\)"); - private readonly CultureInfo _usCulture = new ("en-US"); private readonly ILogger _logger; private readonly ILocalizationManager _localization; @@ -83,7 +82,7 @@ namespace MediaBrowser.MediaEncoding.Probing if (!string.IsNullOrEmpty(data.Format.BitRate)) { - if (int.TryParse(data.Format.BitRate, NumberStyles.Any, _usCulture, out var value)) + if (int.TryParse(data.Format.BitRate, NumberStyles.Any, CultureInfo.InvariantCulture, out var value)) { info.Bitrate = value; } @@ -191,7 +190,7 @@ namespace MediaBrowser.MediaEncoding.Probing if (data.Format != null && !string.IsNullOrEmpty(data.Format.Duration)) { - info.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.Format.Duration, _usCulture)).Ticks; + info.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.Format.Duration, CultureInfo.InvariantCulture)).Ticks; } FetchWtvInfo(info, data); @@ -673,7 +672,7 @@ namespace MediaBrowser.MediaEncoding.Probing if (!string.IsNullOrEmpty(streamInfo.SampleRate)) { - if (int.TryParse(streamInfo.SampleRate, NumberStyles.Any, _usCulture, out var value)) + if (int.TryParse(streamInfo.SampleRate, NumberStyles.Any, CultureInfo.InvariantCulture, out var value)) { stream.SampleRate = value; } @@ -802,7 +801,7 @@ namespace MediaBrowser.MediaEncoding.Probing if (!string.IsNullOrEmpty(streamInfo.BitRate)) { - if (int.TryParse(streamInfo.BitRate, NumberStyles.Any, _usCulture, out var value)) + if (int.TryParse(streamInfo.BitRate, NumberStyles.Any, CultureInfo.InvariantCulture, out var value)) { bitrate = value; } @@ -815,7 +814,7 @@ namespace MediaBrowser.MediaEncoding.Probing && (stream.Type == MediaStreamType.Video || (isAudio && stream.Type == MediaStreamType.Audio))) { // If the stream info doesn't have a bitrate get the value from the media format info - if (int.TryParse(formatInfo.BitRate, NumberStyles.Any, _usCulture, out var value)) + if (int.TryParse(formatInfo.BitRate, NumberStyles.Any, CultureInfo.InvariantCulture, out var value)) { bitrate = value; } @@ -921,8 +920,8 @@ namespace MediaBrowser.MediaEncoding.Probing var parts = (original ?? string.Empty).Split(':'); if (!(parts.Length == 2 && - int.TryParse(parts[0], NumberStyles.Any, _usCulture, out var width) && - int.TryParse(parts[1], NumberStyles.Any, _usCulture, out var height) && + int.TryParse(parts[0], NumberStyles.Any, CultureInfo.InvariantCulture, out var width) && + int.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out var height) && width > 0 && height > 0)) { @@ -1008,11 +1007,11 @@ namespace MediaBrowser.MediaEncoding.Probing if (parts.Length == 2) { - result = float.Parse(parts[0], _usCulture) / float.Parse(parts[1], _usCulture); + result = float.Parse(parts[0], CultureInfo.InvariantCulture) / float.Parse(parts[1], CultureInfo.InvariantCulture); } else { - result = float.Parse(parts[0], _usCulture); + result = float.Parse(parts[0], CultureInfo.InvariantCulture); } return float.IsNaN(result) ? null : result; @@ -1039,7 +1038,7 @@ namespace MediaBrowser.MediaEncoding.Probing // If we got something, parse it if (!string.IsNullOrEmpty(duration)) { - data.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, _usCulture)).Ticks; + data.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, CultureInfo.InvariantCulture)).Ticks; } } @@ -1101,7 +1100,7 @@ namespace MediaBrowser.MediaEncoding.Probing return; } - info.Size = string.IsNullOrEmpty(data.Format.Size) ? null : long.Parse(data.Format.Size, _usCulture); + info.Size = string.IsNullOrEmpty(data.Format.Size) ? null : long.Parse(data.Format.Size, CultureInfo.InvariantCulture); } private void SetAudioInfoFromTags(MediaInfo audio, IReadOnlyDictionary tags) @@ -1144,7 +1143,7 @@ namespace MediaBrowser.MediaEncoding.Probing { Name = match.Groups["name"].Value, Type = PersonType.Actor, - Role = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(match.Groups["instrument"].Value) + Role = CultureInfo.InvariantCulture.TextInfo.ToTitleCase(match.Groups["instrument"].Value) }); } } @@ -1443,7 +1442,7 @@ namespace MediaBrowser.MediaEncoding.Probing .ToArray(); } - if (tags.TryGetValue("WM/OriginalReleaseTime", out var year) && int.TryParse(year, NumberStyles.Integer, _usCulture, out var parsedYear)) + if (tags.TryGetValue("WM/OriginalReleaseTime", out var year) && int.TryParse(year, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedYear)) { video.ProductionYear = parsedYear; } diff --git a/MediaBrowser.Model/Globalization/ILocalizationManager.cs b/MediaBrowser.Model/Globalization/ILocalizationManager.cs index b213e7aa0..406d32cde 100644 --- a/MediaBrowser.Model/Globalization/ILocalizationManager.cs +++ b/MediaBrowser.Model/Globalization/ILocalizationManager.cs @@ -56,10 +56,10 @@ namespace MediaBrowser.Model.Globalization IEnumerable GetLocalizationOptions(); /// - /// Returns the correct for the given language. + /// Returns the correct for the given language. /// /// The language. - /// The correct for the given language. + /// The correct for the given language. CultureDto? FindLanguageInfo(string language); } } diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 7d259a9d3..4b05edd67 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -29,8 +29,6 @@ namespace MediaBrowser.Providers.Manager /// public class ImageSaver { - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - /// /// The _config. /// @@ -377,7 +375,7 @@ namespace MediaBrowser.Providers.Manager var seasonMarker = season.IndexNumber.Value == 0 ? "-specials" - : season.IndexNumber.Value.ToString("00", UsCulture); + : season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture); var imageFilename = "season" + seasonMarker + "-landscape" + extension; @@ -400,7 +398,7 @@ namespace MediaBrowser.Providers.Manager var seasonMarker = season.IndexNumber.Value == 0 ? "-specials" - : season.IndexNumber.Value.ToString("00", UsCulture); + : season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture); var imageFilename = "season" + seasonMarker + "-banner" + extension; @@ -495,12 +493,12 @@ namespace MediaBrowser.Providers.Manager var filenames = images.Select(i => Path.GetFileNameWithoutExtension(i.Path)).ToList(); var current = 1; - while (filenames.Contains(numberedIndexPrefix + current.ToString(UsCulture), StringComparer.OrdinalIgnoreCase)) + while (filenames.Contains(numberedIndexPrefix + current.ToString(CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) { current++; } - return numberedIndexPrefix + current.ToString(UsCulture); + return numberedIndexPrefix + current.ToString(CultureInfo.InvariantCulture); } /// @@ -539,7 +537,7 @@ namespace MediaBrowser.Providers.Manager var seasonMarker = season.IndexNumber.Value == 0 ? "-specials" - : season.IndexNumber.Value.ToString("00", UsCulture); + : season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture); var imageFilename = "season" + seasonMarker + "-fanart" + extension; @@ -556,7 +554,7 @@ namespace MediaBrowser.Providers.Manager if (item.IsInMixedFolder) { - return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart" + outputIndex.ToString(UsCulture), extension) }; + return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart" + outputIndex.ToString(CultureInfo.InvariantCulture), extension) }; } var extraFanartFilename = GetBackdropSaveFilename(item.GetImages(ImageType.Backdrop), "fanart", "fanart", outputIndex); @@ -568,7 +566,7 @@ namespace MediaBrowser.Providers.Manager if (EnableExtraThumbsDuplication) { - list.Add(Path.Combine(item.ContainingFolderPath, "extrathumbs", "thumb" + outputIndex.ToString(UsCulture) + extension)); + list.Add(Path.Combine(item.ContainingFolderPath, "extrathumbs", "thumb" + outputIndex.ToString(CultureInfo.InvariantCulture) + extension)); } return list.ToArray(); @@ -582,7 +580,7 @@ namespace MediaBrowser.Providers.Manager var seasonMarker = season.IndexNumber.Value == 0 ? "-specials" - : season.IndexNumber.Value.ToString("00", UsCulture); + : season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture); var imageFilename = "season" + seasonMarker + "-poster" + extension; diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs index 479ae0f39..b2bc58eea 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs @@ -26,7 +26,6 @@ namespace MediaBrowser.Providers.Plugins.Omdb private readonly IFileSystem _fileSystem; private readonly IServerConfigurationManager _configurationManager; private readonly IHttpClientFactory _httpClientFactory; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly IApplicationHost _appHost; private readonly JsonSerializerOptions _jsonOptions; @@ -79,7 +78,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb } if (!string.IsNullOrEmpty(result.Year) && result.Year.Length >= 4 - && int.TryParse(result.Year.AsSpan().Slice(0, 4), NumberStyles.Number, _usCulture, out var year) + && int.TryParse(result.Year.AsSpan().Slice(0, 4), NumberStyles.Number, CultureInfo.InvariantCulture, out var year) && year >= 0) { item.ProductionYear = year; @@ -93,14 +92,14 @@ namespace MediaBrowser.Providers.Plugins.Omdb } if (!string.IsNullOrEmpty(result.imdbVotes) - && int.TryParse(result.imdbVotes, NumberStyles.Number, _usCulture, out var voteCount) + && int.TryParse(result.imdbVotes, NumberStyles.Number, CultureInfo.InvariantCulture, out var voteCount) && voteCount >= 0) { // item.VoteCount = voteCount; } if (!string.IsNullOrEmpty(result.imdbRating) - && float.TryParse(result.imdbRating, NumberStyles.Any, _usCulture, out var imdbRating) + && float.TryParse(result.imdbRating, NumberStyles.Any, CultureInfo.InvariantCulture, out var imdbRating) && imdbRating >= 0) { item.CommunityRating = imdbRating; @@ -191,7 +190,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb } if (!string.IsNullOrEmpty(result.Year) && result.Year.Length >= 4 - && int.TryParse(result.Year.AsSpan().Slice(0, 4), NumberStyles.Number, _usCulture, out var year) + && int.TryParse(result.Year.AsSpan().Slice(0, 4), NumberStyles.Number, CultureInfo.InvariantCulture, out var year) && year >= 0) { item.ProductionYear = year; @@ -205,14 +204,14 @@ namespace MediaBrowser.Providers.Plugins.Omdb } if (!string.IsNullOrEmpty(result.imdbVotes) - && int.TryParse(result.imdbVotes, NumberStyles.Number, _usCulture, out var voteCount) + && int.TryParse(result.imdbVotes, NumberStyles.Number, CultureInfo.InvariantCulture, out var voteCount) && voteCount >= 0) { // item.VoteCount = voteCount; } if (!string.IsNullOrEmpty(result.imdbRating) - && float.TryParse(result.imdbRating, NumberStyles.Any, _usCulture, out var imdbRating) + && float.TryParse(result.imdbRating, NumberStyles.Any, CultureInfo.InvariantCulture, out var imdbRating) && imdbRating >= 0) { item.CommunityRating = imdbRating; diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index f7f4ea065..9d558b6ce 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -58,8 +58,6 @@ namespace MediaBrowser.XbmcMetadata.Parsers _directoryService = directoryService; } - protected CultureInfo UsCulture { get; } = new CultureInfo("en-US"); - /// /// Gets the logger. /// @@ -309,7 +307,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrEmpty(text)) { - if (float.TryParse(text, NumberStyles.Any, UsCulture, out var value)) + if (float.TryParse(text, NumberStyles.Any, CultureInfo.InvariantCulture, out var value)) { item.CriticRating = value; } @@ -370,7 +368,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val) && userData != null) { - if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var count)) + if (int.TryParse(val, NumberStyles.Integer, CultureInfo.InvariantCulture, out var count)) { userData.PlayCount = count; } @@ -475,7 +473,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(text)) { - if (int.TryParse(text.AsSpan().LeftPart(' '), NumberStyles.Integer, UsCulture, out var runtime)) + if (int.TryParse(text.AsSpan().LeftPart(' '), NumberStyles.Integer, CultureInfo.InvariantCulture, out var runtime)) { item.RunTimeTicks = TimeSpan.FromMinutes(runtime).Ticks; } @@ -1265,7 +1263,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { - if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var intVal)) + if (int.TryParse(val, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intVal)) { sortOrder = intVal; } diff --git a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs index 3a305024e..d2f349ad7 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs @@ -163,7 +163,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { // int.TryParse is local aware, so it can be problematic, force us culture - if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var rval)) + if (int.TryParse(val, NumberStyles.Integer, CultureInfo.InvariantCulture, out var rval)) { item.AirsBeforeEpisodeNumber = rval; } @@ -179,7 +179,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { // int.TryParse is local aware, so it can be problematic, force us culture - if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var rval)) + if (int.TryParse(val, NumberStyles.Integer, CultureInfo.InvariantCulture, out var rval)) { item.AirsAfterSeasonNumber = rval; } @@ -195,7 +195,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { // int.TryParse is local aware, so it can be problematic, force us culture - if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var rval)) + if (int.TryParse(val, NumberStyles.Integer, CultureInfo.InvariantCulture, out var rval)) { item.AirsBeforeSeasonNumber = rval; } @@ -211,7 +211,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { // int.TryParse is local aware, so it can be problematic, force us culture - if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var rval)) + if (int.TryParse(val, NumberStyles.Integer, CultureInfo.InvariantCulture, out var rval)) { item.AirsBeforeSeasonNumber = rval; } @@ -227,7 +227,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { // int.TryParse is local aware, so it can be problematic, force us culture - if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var rval)) + if (int.TryParse(val, NumberStyles.Integer, CultureInfo.InvariantCulture, out var rval)) { item.AirsBeforeEpisodeNumber = rval; } diff --git a/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs index 62f80e81b..2cd3fdf02 100644 --- a/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs @@ -17,8 +17,6 @@ namespace MediaBrowser.XbmcMetadata.Savers /// public class EpisodeNfoSaver : BaseNfoSaver { - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - /// /// Initializes a new instance of the class. /// @@ -60,17 +58,17 @@ namespace MediaBrowser.XbmcMetadata.Savers if (episode.IndexNumber.HasValue) { - writer.WriteElementString("episode", episode.IndexNumber.Value.ToString(_usCulture)); + writer.WriteElementString("episode", episode.IndexNumber.Value.ToString(CultureInfo.InvariantCulture)); } if (episode.IndexNumberEnd.HasValue) { - writer.WriteElementString("episodenumberend", episode.IndexNumberEnd.Value.ToString(_usCulture)); + writer.WriteElementString("episodenumberend", episode.IndexNumberEnd.Value.ToString(CultureInfo.InvariantCulture)); } if (episode.ParentIndexNumber.HasValue) { - writer.WriteElementString("season", episode.ParentIndexNumber.Value.ToString(_usCulture)); + writer.WriteElementString("season", episode.ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture)); } if (episode.PremiereDate.HasValue) @@ -84,28 +82,28 @@ namespace MediaBrowser.XbmcMetadata.Savers { if (episode.AirsAfterSeasonNumber.HasValue && episode.AirsAfterSeasonNumber.Value != -1) { - writer.WriteElementString("airsafter_season", episode.AirsAfterSeasonNumber.Value.ToString(_usCulture)); + writer.WriteElementString("airsafter_season", episode.AirsAfterSeasonNumber.Value.ToString(CultureInfo.InvariantCulture)); } if (episode.AirsBeforeEpisodeNumber.HasValue && episode.AirsBeforeEpisodeNumber.Value != -1) { - writer.WriteElementString("airsbefore_episode", episode.AirsBeforeEpisodeNumber.Value.ToString(_usCulture)); + writer.WriteElementString("airsbefore_episode", episode.AirsBeforeEpisodeNumber.Value.ToString(CultureInfo.InvariantCulture)); } if (episode.AirsBeforeSeasonNumber.HasValue && episode.AirsBeforeSeasonNumber.Value != -1) { - writer.WriteElementString("airsbefore_season", episode.AirsBeforeSeasonNumber.Value.ToString(_usCulture)); + writer.WriteElementString("airsbefore_season", episode.AirsBeforeSeasonNumber.Value.ToString(CultureInfo.InvariantCulture)); } if (episode.AirsBeforeEpisodeNumber.HasValue && episode.AirsBeforeEpisodeNumber.Value != -1) { - writer.WriteElementString("displayepisode", episode.AirsBeforeEpisodeNumber.Value.ToString(_usCulture)); + writer.WriteElementString("displayepisode", episode.AirsBeforeEpisodeNumber.Value.ToString(CultureInfo.InvariantCulture)); } var specialSeason = episode.AiredSeasonNumber; if (specialSeason.HasValue && specialSeason.Value != -1) { - writer.WriteElementString("displayseason", specialSeason.Value.ToString(_usCulture)); + writer.WriteElementString("displayseason", specialSeason.Value.ToString(CultureInfo.InvariantCulture)); } } } -- cgit v1.2.3 From c26e6d89b43cfd81c5735ae1a2ecd47cc43fda90 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Sun, 10 Oct 2021 19:48:11 +0300 Subject: Move FfmpegException to MediaBrowser.Common --- MediaBrowser.Common/FfmpegException.cs | 39 ++++++++++++++++++++++ MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 1 + MediaBrowser.MediaEncoding/FfmpegException.cs | 39 ---------------------- .../Subtitles/SubtitleEncoder.cs | 1 + 4 files changed, 41 insertions(+), 39 deletions(-) create mode 100644 MediaBrowser.Common/FfmpegException.cs delete mode 100644 MediaBrowser.MediaEncoding/FfmpegException.cs (limited to 'MediaBrowser.MediaEncoding/Encoder') diff --git a/MediaBrowser.Common/FfmpegException.cs b/MediaBrowser.Common/FfmpegException.cs new file mode 100644 index 000000000..be420196d --- /dev/null +++ b/MediaBrowser.Common/FfmpegException.cs @@ -0,0 +1,39 @@ +using System; + +namespace MediaBrowser.Common +{ + /// + /// Represents errors that occur during interaction with FFmpeg. + /// + public class FfmpegException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public FfmpegException() + { + } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. + public FfmpegException(string message) : base(message) + { + } + + /// + /// Initializes a new instance of the class with a specified error message and a + /// reference to the inner exception that is the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if + /// no inner exception is specified. + /// + public FfmpegException(string message, Exception innerException) + : base(message, innerException) + { + } + } +} diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 06fe95ce8..3dbbeda8a 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -12,6 +12,7 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Jellyfin.Extensions.Json; +using MediaBrowser.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; diff --git a/MediaBrowser.MediaEncoding/FfmpegException.cs b/MediaBrowser.MediaEncoding/FfmpegException.cs deleted file mode 100644 index 1697fd33a..000000000 --- a/MediaBrowser.MediaEncoding/FfmpegException.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; - -namespace MediaBrowser.MediaEncoding -{ - /// - /// Represents errors that occur during interaction with FFmpeg. - /// - public class FfmpegException : Exception - { - /// - /// Initializes a new instance of the class. - /// - public FfmpegException() - { - } - - /// - /// Initializes a new instance of the class with a specified error message. - /// - /// The message that describes the error. - public FfmpegException(string message) : base(message) - { - } - - /// - /// Initializes a new instance of the class with a specified error message and a - /// reference to the inner exception that is the cause of this exception. - /// - /// The error message that explains the reason for the exception. - /// - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if - /// no inner exception is specified. - /// - public FfmpegException(string message, Exception innerException) - : base(message, innerException) - { - } - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index bfc17904c..2b2de2ff6 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -11,6 +11,7 @@ using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; -- cgit v1.2.3 From 8d70cc2dde81aad0f484a2f0d0c5b90e6e1b97cd Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Thu, 7 Oct 2021 22:37:59 +0200 Subject: Add support for non-jpg image extractions --- .../MediaEncoding/IMediaEncoder.cs | 3 +- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 21 ++++---- .../Probing/ProbeResultNormalizer.cs | 18 +++---- .../MediaInfo/EmbeddedImageProvider.cs | 60 ++++++++++++++++------ 4 files changed, 62 insertions(+), 40 deletions(-) (limited to 'MediaBrowser.MediaEncoding/Encoder') diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index c5522bc3c..638588560 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -95,9 +95,10 @@ namespace MediaBrowser.Controller.MediaEncoding /// Media source information. /// Media stream information. /// Index of the stream to extract from. + /// The extension of the file to write. /// CancellationToken to use for operation. /// Location of video image. - Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, CancellationToken cancellationToken); + Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, string outputExtension, CancellationToken cancellationToken); /// /// Extracts the video images on interval. diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 06fe95ce8..30bc7125d 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -468,17 +468,17 @@ namespace MediaBrowser.MediaEncoding.Encoder Protocol = MediaProtocol.File }; - return ExtractImage(path, null, null, imageStreamIndex, mediaSource, true, null, null, cancellationToken); + return ExtractImage(path, null, null, imageStreamIndex, mediaSource, true, null, null, "jpg", cancellationToken); } public Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream videoStream, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken) { - return ExtractImage(inputFile, container, videoStream, null, mediaSource, false, threedFormat, offset, cancellationToken); + return ExtractImage(inputFile, container, videoStream, null, mediaSource, false, threedFormat, offset, "jpg", cancellationToken); } - public Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, CancellationToken cancellationToken) + public Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, string outputExtension, CancellationToken cancellationToken) { - return ExtractImage(inputFile, container, imageStream, imageStreamIndex, mediaSource, false, null, null, cancellationToken); + return ExtractImage(inputFile, container, imageStream, imageStreamIndex, mediaSource, false, null, null, outputExtension, cancellationToken); } private async Task ExtractImage( @@ -490,6 +490,7 @@ namespace MediaBrowser.MediaEncoding.Encoder bool isAudio, Video3DFormat? threedFormat, TimeSpan? offset, + string outputExtension, CancellationToken cancellationToken) { var inputArgument = GetInputArgument(inputFile, mediaSource); @@ -499,7 +500,7 @@ namespace MediaBrowser.MediaEncoding.Encoder // The failure of HDR extraction usually occurs when using custom ffmpeg that does not contain the zscale filter. try { - return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, true, cancellationToken).ConfigureAwait(false); + return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, true, outputExtension, cancellationToken).ConfigureAwait(false); } catch (ArgumentException) { @@ -512,7 +513,7 @@ namespace MediaBrowser.MediaEncoding.Encoder try { - return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, true, cancellationToken).ConfigureAwait(false); + return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, true, outputExtension, cancellationToken).ConfigureAwait(false); } catch (ArgumentException) { @@ -525,7 +526,7 @@ namespace MediaBrowser.MediaEncoding.Encoder try { - return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, false, cancellationToken).ConfigureAwait(false); + return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, false, outputExtension, cancellationToken).ConfigureAwait(false); } catch (ArgumentException) { @@ -537,17 +538,17 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, false, cancellationToken).ConfigureAwait(false); + return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, false, outputExtension, cancellationToken).ConfigureAwait(false); } - private async Task ExtractImageInternal(string inputPath, string container, MediaStream videoStream, int? imageStreamIndex, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, bool allowTonemap, CancellationToken cancellationToken) + private async Task ExtractImageInternal(string inputPath, string container, MediaStream videoStream, int? imageStreamIndex, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, bool allowTonemap, string outputExtension, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(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() + "." + outputExtension); 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. diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 5f6539495..775689095 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -582,7 +582,8 @@ namespace MediaBrowser.MediaEncoding.Probing /// MediaAttachments. private MediaAttachment GetMediaAttachment(MediaStreamInfo streamInfo) { - if (!string.Equals(streamInfo.CodecType, "attachment", StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(streamInfo.CodecType, "attachment", StringComparison.OrdinalIgnoreCase) && + !(streamInfo.Disposition != null && streamInfo.Disposition.GetValueOrDefault("attached_pic") == 1)) { return null; } @@ -735,15 +736,14 @@ namespace MediaBrowser.MediaEncoding.Probing else if (string.Equals(stream.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase)) { // How to differentiate between video and embedded image? - // check disposition, alternately: presence of codec tag, also embedded images have high (unusual) framerates - if ((streamInfo.Disposition != null && streamInfo.Disposition.GetValueOrDefault("attached_pic") == 1) || - string.IsNullOrWhiteSpace(stream.CodecTag)) + // The only difference I've seen thus far is presence of codec tag, also embedded images have high (unusual) framerates + if (!string.IsNullOrWhiteSpace(stream.CodecTag)) { - stream.Type = MediaStreamType.EmbeddedImage; + stream.Type = MediaStreamType.Video; } else { - stream.Type = MediaStreamType.Video; + stream.Type = MediaStreamType.EmbeddedImage; } } else @@ -812,12 +812,6 @@ namespace MediaBrowser.MediaEncoding.Probing { stream.ColorPrimaries = streamInfo.ColorPrimaries; } - - // workaround for mkv attached_pics losing filename due to being classified as video based on codec - if (stream.Type == MediaStreamType.EmbeddedImage && streamInfo.Tags != null && string.IsNullOrEmpty(stream.Comment)) - { - stream.Comment = GetDictionaryValue(streamInfo.Tags, "filename"); - } } else { diff --git a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs index 11fdfcdf9..ad95cdb06 100644 --- a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -15,6 +16,7 @@ using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Net; using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.MediaInfo @@ -115,25 +117,49 @@ namespace MediaBrowser.Providers.MediaInfo Protocol = item.PathProtocol ?? MediaProtocol.File, }; - string[] imageFileNames; - switch (type) + string[] imageFileNames = type switch { - case ImageType.Backdrop: - imageFileNames = _backdropImageFileNames; - break; - case ImageType.Logo: - imageFileNames = _logoImageFileNames; - break; - case ImageType.Primary: - default: - imageFileNames = _primaryImageFileNames; - break; + ImageType.Primary => _primaryImageFileNames, + ImageType.Backdrop => _backdropImageFileNames, + ImageType.Logo => _logoImageFileNames, + _ => _primaryImageFileNames + }; + + // Try attachments first + var attachmentSources = item.GetMediaSources(false).SelectMany(source => source.MediaAttachments).ToList(); + var attachmentStream = attachmentSources + .Where(stream => !string.IsNullOrEmpty(stream.FileName)) + .First(stream => imageFileNames.Any(name => stream.FileName.Contains(name, StringComparison.OrdinalIgnoreCase))); + + if (attachmentStream != null) + { + var extension = (string.IsNullOrEmpty(attachmentStream.MimeType) ? + Path.GetExtension(attachmentStream.FileName) : + MimeTypes.ToExtension(attachmentStream.MimeType)) ?? "jpg"; + + string extractedAttachmentPath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, null, attachmentStream.Index, extension, cancellationToken).ConfigureAwait(false); + + ImageFormat format = extension switch + { + "bmp" => ImageFormat.Bmp, + "gif" => ImageFormat.Gif, + "jpg" => ImageFormat.Jpg, + "png" => ImageFormat.Png, + "webp" => ImageFormat.Webp, + _ => ImageFormat.Jpg + }; + + return new DynamicImageResponse + { + Format = format, + HasImage = true, + Path = extractedAttachmentPath, + Protocol = MediaProtocol.File + }; } - var imageStreams = - item.GetMediaStreams() - .Where(i => i.Type == MediaStreamType.EmbeddedImage) - .ToList(); + // Fall back to EmbeddedImage streams + var imageStreams = item.GetMediaStreams().FindAll(i => i.Type == MediaStreamType.EmbeddedImage); if (!imageStreams.Any()) { @@ -160,7 +186,7 @@ namespace MediaBrowser.Providers.MediaInfo } } - string extractedImagePath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.Index, cancellationToken).ConfigureAwait(false); + string extractedImagePath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.Index, "jpg", cancellationToken).ConfigureAwait(false); return new DynamicImageResponse { -- cgit v1.2.3 From e3eee10d05e9ecc7e3fac1f8fdad92329d38a4db Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Mon, 11 Oct 2021 12:34:18 +0200 Subject: Add image provider tests and clean up --- .../MediaEncoding/IMediaEncoder.cs | 2 +- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 6 +- .../MediaInfo/EmbeddedImageProvider.cs | 41 ++-- .../MediaInfo/VideoImageProvider.cs | 9 +- .../MediaInfo/EmbeddedImageProviderTests.cs | 211 +++++++++++++++++++++ .../MediaInfo/VideoImageProviderTests.cs | 168 ++++++++++++++++ 6 files changed, 408 insertions(+), 29 deletions(-) create mode 100644 tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs create mode 100644 tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs (limited to 'MediaBrowser.MediaEncoding/Encoder') diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 638588560..e6511ca8d 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -95,7 +95,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// Media source information. /// Media stream information. /// Index of the stream to extract from. - /// The extension of the file to write. + /// The extension of the file to write, including the '.'. /// CancellationToken to use for operation. /// Location of video image. Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, string outputExtension, CancellationToken cancellationToken); diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 30bc7125d..dac2c6a26 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -468,12 +468,12 @@ namespace MediaBrowser.MediaEncoding.Encoder Protocol = MediaProtocol.File }; - return ExtractImage(path, null, null, imageStreamIndex, mediaSource, true, null, null, "jpg", cancellationToken); + return ExtractImage(path, null, null, imageStreamIndex, mediaSource, true, null, null, ".jpg", cancellationToken); } public Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream videoStream, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken) { - return ExtractImage(inputFile, container, videoStream, null, mediaSource, false, threedFormat, offset, "jpg", cancellationToken); + return ExtractImage(inputFile, container, videoStream, null, mediaSource, false, threedFormat, offset, ".jpg", cancellationToken); } public Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, string outputExtension, CancellationToken cancellationToken) @@ -548,7 +548,7 @@ namespace MediaBrowser.MediaEncoding.Encoder throw new ArgumentNullException(nameof(inputPath)); } - var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + "." + outputExtension); + var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + outputExtension); 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. diff --git a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs index ad95cdb06..df87f2d49 100644 --- a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs @@ -1,9 +1,7 @@ -#nullable enable #pragma warning disable CS1591 using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.IO; using System.Linq; using System.Threading; @@ -17,7 +15,6 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Net; -using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.MediaInfo { @@ -48,12 +45,10 @@ namespace MediaBrowser.Providers.MediaInfo }; private readonly IMediaEncoder _mediaEncoder; - private readonly ILogger _logger; - public EmbeddedImageProvider(IMediaEncoder mediaEncoder, ILogger logger) + public EmbeddedImageProvider(IMediaEncoder mediaEncoder) { _mediaEncoder = mediaEncoder; - _logger = logger; } /// @@ -84,7 +79,7 @@ namespace MediaBrowser.Providers.MediaInfo }; } - return ImmutableList.Empty; + return new List(); } /// @@ -98,13 +93,6 @@ namespace MediaBrowser.Providers.MediaInfo return Task.FromResult(new DynamicImageResponse { HasImage = false }); } - // Can't extract if we didn't find any video streams in the file - if (!video.DefaultVideoStreamIndex.HasValue) - { - _logger.LogInformation("Skipping image extraction due to missing DefaultVideoStreamIndex for {Path}.", video.Path ?? string.Empty); - return Task.FromResult(new DynamicImageResponse { HasImage = false }); - } - return GetEmbeddedImage(video, type, cancellationToken); } @@ -128,24 +116,29 @@ namespace MediaBrowser.Providers.MediaInfo // Try attachments first var attachmentSources = item.GetMediaSources(false).SelectMany(source => source.MediaAttachments).ToList(); var attachmentStream = attachmentSources - .Where(stream => !string.IsNullOrEmpty(stream.FileName)) - .First(stream => imageFileNames.Any(name => stream.FileName.Contains(name, StringComparison.OrdinalIgnoreCase))); + .Where(attachment => !string.IsNullOrEmpty(attachment.FileName)) + .FirstOrDefault(attachment => imageFileNames.Any(name => attachment.FileName.Contains(name, StringComparison.OrdinalIgnoreCase))); if (attachmentStream != null) { - var extension = (string.IsNullOrEmpty(attachmentStream.MimeType) ? + var extension = string.IsNullOrEmpty(attachmentStream.MimeType) ? Path.GetExtension(attachmentStream.FileName) : - MimeTypes.ToExtension(attachmentStream.MimeType)) ?? "jpg"; + MimeTypes.ToExtension(attachmentStream.MimeType); + + if (string.IsNullOrEmpty(extension)) + { + extension = ".jpg"; + } string extractedAttachmentPath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, null, attachmentStream.Index, extension, cancellationToken).ConfigureAwait(false); ImageFormat format = extension switch { - "bmp" => ImageFormat.Bmp, - "gif" => ImageFormat.Gif, - "jpg" => ImageFormat.Jpg, - "png" => ImageFormat.Png, - "webp" => ImageFormat.Webp, + ".bmp" => ImageFormat.Bmp, + ".gif" => ImageFormat.Gif, + ".jpg" => ImageFormat.Jpg, + ".png" => ImageFormat.Png, + ".webp" => ImageFormat.Webp, _ => ImageFormat.Jpg }; @@ -170,7 +163,7 @@ namespace MediaBrowser.Providers.MediaInfo // Extract first stream containing an element of imageFileNames var imageStream = imageStreams .Where(stream => !string.IsNullOrEmpty(stream.Comment)) - .First(stream => imageFileNames.Any(name => stream.Comment.Contains(name, StringComparison.OrdinalIgnoreCase))); + .FirstOrDefault(stream => imageFileNames.Any(name => stream.Comment.Contains(name, StringComparison.OrdinalIgnoreCase))); // Primary type only: default to first image if none found by label if (imageStream == null) diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index 8f2009950..60739f156 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -81,7 +81,14 @@ namespace MediaBrowser.Providers.MediaInfo ? TimeSpan.FromTicks(item.RunTimeTicks.Value / 10) : TimeSpan.FromSeconds(10); - var videoStream = item.GetMediaStreams().FirstOrDefault(i => i.Type == MediaStreamType.Video); + var videoStream = item.GetDefaultVideoStream() ?? item.GetMediaStreams().FirstOrDefault(i => i.Type == MediaStreamType.Video); + + if (videoStream == null) + { + _logger.LogInformation("Skipping image extraction: no video stream found for {Path}.", item.Path ?? string.Empty); + return new DynamicImageResponse { HasImage = false }; + } + string extractedImagePath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, videoStream, item.Video3DFormat, imageOffset, cancellationToken).ConfigureAwait(false); return new DynamicImageResponse diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs new file mode 100644 index 000000000..fcea1532a --- /dev/null +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs @@ -0,0 +1,211 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.Drawing; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Providers.MediaInfo; +using Moq; +using Xunit; + +namespace Jellyfin.Providers.Tests.MediaInfo +{ + public class EmbeddedImageProviderTests + { + public static TheoryData GetSupportedImages_Empty_TestData => + new () + { + new AudioBook(), + new BoxSet(), + new Series(), + new Season(), + }; + + public static TheoryData> GetSupportedImages_Populated_TestData => + new TheoryData> + { + { new Episode(), new List { ImageType.Primary } }, + { new Movie(), new List { ImageType.Logo, ImageType.Backdrop, ImageType.Primary } }, + }; + + private EmbeddedImageProvider GetEmbeddedImageProvider(IMediaEncoder? mediaEncoder) + { + return new EmbeddedImageProvider(mediaEncoder); + } + + [Theory] + [MemberData(nameof(GetSupportedImages_Empty_TestData))] + public void GetSupportedImages_Empty(BaseItem item) + { + var embeddedImageProvider = GetEmbeddedImageProvider(null); + Assert.False(embeddedImageProvider.GetSupportedImages(item).Any()); + } + + [Theory] + [MemberData(nameof(GetSupportedImages_Populated_TestData))] + public void GetSupportedImages_Populated(BaseItem item, IEnumerable expected) + { + var embeddedImageProvider = GetEmbeddedImageProvider(null); + var actual = embeddedImageProvider.GetSupportedImages(item); + Assert.Equal(expected.OrderBy(i => i.ToString()), actual.OrderBy(i => i.ToString())); + } + + [Fact] + public async void GetImage_Empty_NoStreams() + { + var embeddedImageProvider = GetEmbeddedImageProvider(null); + + var input = new Mock(); + input.Setup(movie => movie.GetMediaSources(It.IsAny())) + .Returns(new List()); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List()); + + var actual = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actual); + Assert.False(actual.HasImage); + } + + [Fact] + public async void GetImage_Empty_NoLabeledAttachments() + { + var embeddedImageProvider = GetEmbeddedImageProvider(null); + + var input = new Mock(); + // add an attachment without a filename - has a list to look through but finds nothing + input.Setup(movie => movie.GetMediaSources(It.IsAny())) + .Returns(new List { new () { MediaAttachments = new List { new () } } }); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List()); + + var actual = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actual); + Assert.False(actual.HasImage); + } + + [Fact] + public async void GetImage_Empty_NoEmbeddedLabeledBackdrop() + { + var embeddedImageProvider = GetEmbeddedImageProvider(null); + + var input = new Mock(); + input.Setup(movie => movie.GetMediaSources(It.IsAny())) + .Returns(new List()); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List { new () { Type = MediaStreamType.EmbeddedImage } }); + + var actual = await embeddedImageProvider.GetImage(input.Object, ImageType.Backdrop, CancellationToken.None); + Assert.NotNull(actual); + Assert.False(actual.HasImage); + } + + [Fact] + public async void GetImage_Attached() + { + // first tests file extension detection, second uses mimetype, third defaults to jpg + MediaAttachment sampleAttachment1 = new () { FileName = "clearlogo.png", Index = 1 }; + MediaAttachment sampleAttachment2 = new () { FileName = "backdrop", MimeType = "image/bmp", Index = 2 }; + MediaAttachment sampleAttachment3 = new () { FileName = "poster", Index = 3 }; + string targetPath1 = "path1.png"; + string targetPath2 = "path2.bmp"; + string targetPath3 = "path2.jpg"; + + var mediaEncoder = new Mock(MockBehavior.Strict); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), 1, ".png", CancellationToken.None)) + .Returns(Task.FromResult(targetPath1)); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), 2, ".bmp", CancellationToken.None)) + .Returns(Task.FromResult(targetPath2)); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), 3, ".jpg", CancellationToken.None)) + .Returns(Task.FromResult(targetPath3)); + var embeddedImageProvider = GetEmbeddedImageProvider(mediaEncoder.Object); + + var input = new Mock(); + input.Setup(movie => movie.GetMediaSources(It.IsAny())) + .Returns(new List { new () { MediaAttachments = new List { sampleAttachment1, sampleAttachment2, sampleAttachment3 } } }); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List()); + + var actualLogo = await embeddedImageProvider.GetImage(input.Object, ImageType.Logo, CancellationToken.None); + Assert.NotNull(actualLogo); + Assert.True(actualLogo.HasImage); + Assert.Equal(targetPath1, actualLogo.Path); + Assert.Equal(ImageFormat.Png, actualLogo.Format); + + var actualBackdrop = await embeddedImageProvider.GetImage(input.Object, ImageType.Backdrop, CancellationToken.None); + Assert.NotNull(actualBackdrop); + Assert.True(actualBackdrop.HasImage); + Assert.Equal(targetPath2, actualBackdrop.Path); + Assert.Equal(ImageFormat.Bmp, actualBackdrop.Format); + + var actualPrimary = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actualPrimary); + Assert.True(actualPrimary.HasImage); + Assert.Equal(targetPath3, actualPrimary.Path); + Assert.Equal(ImageFormat.Jpg, actualPrimary.Format); + } + + [Fact] + public async void GetImage_EmbeddedDefault() + { + MediaStream sampleStream = new () { Type = MediaStreamType.EmbeddedImage, Index = 1 }; + string targetPath = "path"; + + var mediaEncoder = new Mock(MockBehavior.Strict); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream, 1, "jpg", CancellationToken.None)) + .Returns(Task.FromResult(targetPath)); + var embeddedImageProvider = GetEmbeddedImageProvider(mediaEncoder.Object); + + var input = new Mock(); + input.Setup(movie => movie.GetMediaSources(It.IsAny())) + .Returns(new List()); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List() { sampleStream }); + + var actual = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actual); + Assert.True(actual.HasImage); + Assert.Equal(targetPath, actual.Path); + Assert.Equal(ImageFormat.Jpg, actual.Format); + } + + [Fact] + public async void GetImage_EmbeddedSelection() + { + // primary is second stream to ensure it's not defaulting, backdrop is first + MediaStream sampleStream1 = new () { Type = MediaStreamType.EmbeddedImage, Index = 1, Comment = "backdrop" }; + MediaStream sampleStream2 = new () { Type = MediaStreamType.EmbeddedImage, Index = 2, Comment = "cover" }; + string targetPath1 = "path1.jpg"; + string targetPath2 = "path2.jpg"; + + var mediaEncoder = new Mock(MockBehavior.Strict); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream1, 1, "jpg", CancellationToken.None)) + .Returns(Task.FromResult(targetPath1)); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream2, 2, "jpg", CancellationToken.None)) + .Returns(Task.FromResult(targetPath2)); + var embeddedImageProvider = GetEmbeddedImageProvider(mediaEncoder.Object); + + var input = new Mock(); + input.Setup(movie => movie.GetMediaSources(It.IsAny())) + .Returns(new List()); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List { sampleStream1, sampleStream2 }); + + var actualPrimary = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actualPrimary); + Assert.True(actualPrimary.HasImage); + Assert.Equal(targetPath2, actualPrimary.Path); + Assert.Equal(ImageFormat.Jpg, actualPrimary.Format); + + var actualBackdrop = await embeddedImageProvider.GetImage(input.Object, ImageType.Backdrop, CancellationToken.None); + Assert.NotNull(actualBackdrop); + Assert.True(actualBackdrop.HasImage); + Assert.Equal(targetPath1, actualBackdrop.Path); + Assert.Equal(ImageFormat.Jpg, actualBackdrop.Format); + } + } +} diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs new file mode 100644 index 000000000..9a5cd79bb --- /dev/null +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.Drawing; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Providers.MediaInfo; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using Xunit; + +namespace Jellyfin.Providers.Tests.MediaInfo +{ + public class VideoImageProviderTests + { + private VideoImageProvider GetVideoImageProvider(IMediaEncoder? mediaEncoder) + { + // strict to ensure this isn't accidentally used where a prepared mock is intended + mediaEncoder ??= new Mock(MockBehavior.Strict).Object; + return new VideoImageProvider(mediaEncoder, new NullLogger()); + } + + [Fact] + public async void GetImage_Empty_IsPlaceholder() + { + var videoImageProvider = GetVideoImageProvider(null); + + var input = new Mock(); + input.Object.IsPlaceHolder = true; + + var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actual); + Assert.False(actual.HasImage); + } + + [Fact] + public async void GetImage_Empty_NoDefaultVideoStream() + { + var videoImageProvider = GetVideoImageProvider(null); + + var input = new Mock(); + + var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actual); + Assert.False(actual.HasImage); + } + + [Fact] + public async void GetImage_Empty_DefaultSet_NoVideoStream() + { + var videoImageProvider = GetVideoImageProvider(null); + + var input = new Mock(); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List()); + // set a default index but don't put anything there (invalid input, but provider shouldn't break) + input.Object.DefaultVideoStreamIndex = 1; + + var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actual); + Assert.False(actual.HasImage); + } + + [Fact] + public async void GetImage_Extract_DefaultStream() + { + MediaStream firstStream = new () { Type = MediaStreamType.Video, Index = 0 }; + MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 1 }; + string targetPath = "path.jpg"; + + var mediaEncoder = new Mock(MockBehavior.Strict); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), firstStream, It.IsAny(), It.IsAny(), CancellationToken.None)) + .Returns(Task.FromResult("wrong stream called!")); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), targetStream, It.IsAny(), It.IsAny(), CancellationToken.None)) + .Returns(Task.FromResult(targetPath)); + var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object); + + var input = new Mock(); + input.Setup(movie => movie.GetDefaultVideoStream()) + .Returns(targetStream); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List() { firstStream, targetStream }); + input.Object.DefaultVideoStreamIndex = 1; + + var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actual); + Assert.True(actual.HasImage); + Assert.Equal(targetPath, actual.Path); + Assert.Equal(ImageFormat.Jpg, actual.Format); + } + + [Fact] + public async void GetImage_Extract_FallbackToFirstVideoStream() + { + MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 0 }; + string targetPath = "path.jpg"; + + var mediaEncoder = new Mock(MockBehavior.Strict); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), targetStream, It.IsAny(), It.IsAny(), CancellationToken.None)) + .Returns(Task.FromResult(targetPath)); + var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object); + + var input = new Mock(); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List() { targetStream }); + // default must be set, ensure a stream is still found if not pointed at a video + input.Object.DefaultVideoStreamIndex = 5; + + var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actual); + Assert.True(actual.HasImage); + Assert.Equal(targetPath, actual.Path); + Assert.Equal(ImageFormat.Jpg, actual.Format); + } + + [Fact] + public async void GetImage_Time_Default() + { + MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 0 }; + + TimeSpan? actualTimeSpan = null; + var mediaEncoder = new Mock(MockBehavior.Strict); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), CancellationToken.None)) + .Callback((_, _, _, _, _, timeSpan, _) => actualTimeSpan = timeSpan) + .Returns(Task.FromResult("path")); + var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object); + + var input = new Mock(); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List() { targetStream }); + // default must be set + input.Object.DefaultVideoStreamIndex = 0; + + // not testing return, just verifying what gets requested for time span + await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + + Assert.Equal(TimeSpan.FromSeconds(10), actualTimeSpan); + } + + [Fact] + public async void GetImage_Time_Calculated() + { + MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 0 }; + + TimeSpan? actualTimeSpan = null; + var mediaEncoder = new Mock(MockBehavior.Strict); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), CancellationToken.None)) + .Callback((_, _, _, _, _, timeSpan, _) => actualTimeSpan = timeSpan) + .Returns(Task.FromResult("path")); + var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object); + + var input = new Mock(); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List() { targetStream }); + // default must be set + input.Object.DefaultVideoStreamIndex = 0; + input.Object.RunTimeTicks = 5000; + + // not testing return, just verifying what gets requested for time span + await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + + Assert.Equal(TimeSpan.FromTicks(500), actualTimeSpan); + } + } +} -- cgit v1.2.3 From 31baea072a1ed25346437150f27a93916cb4cca0 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Mon, 11 Oct 2021 21:25:12 +0200 Subject: Address review comments Clean up style Fix references in class summaries Combine Where+FirstOrDefault queries Break up large method, long lines Add validation on file extension Apply test naming conventions Extract mock of Movie class, comment on why not mocking interface Co-authored-by: Cody Robibero Co-authored-by: Claus Vium --- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 9 ++ .../Probing/ProbeResultNormalizer.cs | 4 +- .../MediaInfo/EmbeddedImageProvider.cs | 95 +++++++------ .../MediaInfo/VideoImageProvider.cs | 10 +- .../MediaInfo/EmbeddedImageProviderTests.cs | 155 +++++++++++---------- .../MediaInfo/VideoImageProviderTests.cs | 108 +++++++------- 6 files changed, 208 insertions(+), 173 deletions(-) (limited to 'MediaBrowser.MediaEncoding/Encoder') diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index dac2c6a26..102b4f943 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -548,6 +548,15 @@ namespace MediaBrowser.MediaEncoding.Encoder throw new ArgumentNullException(nameof(inputPath)); } + if (string.IsNullOrEmpty(outputExtension)) + { + outputExtension = ".jpg"; + } + else if (outputExtension[0] != '.') + { + outputExtension = "." + outputExtension; + } + var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + outputExtension); Directory.CreateDirectory(Path.GetDirectoryName(tempExtractPath)); diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 775689095..9279cb220 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -582,8 +582,8 @@ namespace MediaBrowser.MediaEncoding.Probing /// MediaAttachments. private MediaAttachment GetMediaAttachment(MediaStreamInfo streamInfo) { - if (!string.Equals(streamInfo.CodecType, "attachment", StringComparison.OrdinalIgnoreCase) && - !(streamInfo.Disposition != null && streamInfo.Disposition.GetValueOrDefault("attached_pic") == 1)) + if (!string.Equals(streamInfo.CodecType, "attachment", StringComparison.OrdinalIgnoreCase) + && streamInfo.Disposition?.GetValueOrDefault("attached_pic") != 1) { return null; } diff --git a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs index df87f2d49..1d9d1e02a 100644 --- a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.IO; @@ -19,7 +17,7 @@ using MediaBrowser.Model.Net; namespace MediaBrowser.Providers.MediaInfo { /// - /// Uses ffmpeg to extract embedded images. + /// Uses to extract embedded images. /// public class EmbeddedImageProvider : IDynamicImageProvider, IHasOrder { @@ -46,6 +44,10 @@ namespace MediaBrowser.Providers.MediaInfo private readonly IMediaEncoder _mediaEncoder; + /// + /// Initializes a new instance of the class. + /// + /// The media encoder for extracting attached/embedded images. public EmbeddedImageProvider(IMediaEncoder mediaEncoder) { _mediaEncoder = mediaEncoder; @@ -65,13 +67,13 @@ namespace MediaBrowser.Providers.MediaInfo { if (item is Episode) { - return new List + return new[] { ImageType.Primary, }; } - return new List + return new[] { ImageType.Primary, ImageType.Backdrop, @@ -79,7 +81,7 @@ namespace MediaBrowser.Providers.MediaInfo }; } - return new List(); + return Array.Empty(); } /// @@ -114,47 +116,20 @@ namespace MediaBrowser.Providers.MediaInfo }; // Try attachments first - var attachmentSources = item.GetMediaSources(false).SelectMany(source => source.MediaAttachments).ToList(); - var attachmentStream = attachmentSources - .Where(attachment => !string.IsNullOrEmpty(attachment.FileName)) - .FirstOrDefault(attachment => imageFileNames.Any(name => attachment.FileName.Contains(name, StringComparison.OrdinalIgnoreCase))); + var attachmentStream = item.GetMediaSources(false) + .SelectMany(source => source.MediaAttachments) + .FirstOrDefault(attachment => !string.IsNullOrEmpty(attachment.FileName) + && imageFileNames.Any(name => attachment.FileName.Contains(name, StringComparison.OrdinalIgnoreCase))); if (attachmentStream != null) { - var extension = string.IsNullOrEmpty(attachmentStream.MimeType) ? - Path.GetExtension(attachmentStream.FileName) : - MimeTypes.ToExtension(attachmentStream.MimeType); - - if (string.IsNullOrEmpty(extension)) - { - extension = ".jpg"; - } - - string extractedAttachmentPath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, null, attachmentStream.Index, extension, cancellationToken).ConfigureAwait(false); - - ImageFormat format = extension switch - { - ".bmp" => ImageFormat.Bmp, - ".gif" => ImageFormat.Gif, - ".jpg" => ImageFormat.Jpg, - ".png" => ImageFormat.Png, - ".webp" => ImageFormat.Webp, - _ => ImageFormat.Jpg - }; - - return new DynamicImageResponse - { - Format = format, - HasImage = true, - Path = extractedAttachmentPath, - Protocol = MediaProtocol.File - }; + return await ExtractAttachment(item, cancellationToken, attachmentStream, mediaSource); } // Fall back to EmbeddedImage streams var imageStreams = item.GetMediaStreams().FindAll(i => i.Type == MediaStreamType.EmbeddedImage); - if (!imageStreams.Any()) + if (imageStreams.Count == 0) { // Can't extract if we don't have any EmbeddedImage streams return new DynamicImageResponse { HasImage = false }; @@ -162,8 +137,8 @@ namespace MediaBrowser.Providers.MediaInfo // Extract first stream containing an element of imageFileNames var imageStream = imageStreams - .Where(stream => !string.IsNullOrEmpty(stream.Comment)) - .FirstOrDefault(stream => imageFileNames.Any(name => stream.Comment.Contains(name, StringComparison.OrdinalIgnoreCase))); + .FirstOrDefault(stream => !string.IsNullOrEmpty(stream.Comment) + && imageFileNames.Any(name => stream.Comment.Contains(name, StringComparison.OrdinalIgnoreCase))); // Primary type only: default to first image if none found by label if (imageStream == null) @@ -179,7 +154,9 @@ namespace MediaBrowser.Providers.MediaInfo } } - string extractedImagePath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.Index, "jpg", cancellationToken).ConfigureAwait(false); + string extractedImagePath = + await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.Index, ".jpg", cancellationToken) + .ConfigureAwait(false); return new DynamicImageResponse { @@ -190,6 +167,40 @@ namespace MediaBrowser.Providers.MediaInfo }; } + private async Task ExtractAttachment(Video item, CancellationToken cancellationToken, MediaAttachment attachmentStream, MediaSourceInfo mediaSource) + { + var extension = string.IsNullOrEmpty(attachmentStream.MimeType) + ? Path.GetExtension(attachmentStream.FileName) + : MimeTypes.ToExtension(attachmentStream.MimeType); + + if (string.IsNullOrEmpty(extension)) + { + extension = ".jpg"; + } + + string extractedAttachmentPath = + await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, null, attachmentStream.Index, extension, cancellationToken) + .ConfigureAwait(false); + + ImageFormat format = extension switch + { + ".bmp" => ImageFormat.Bmp, + ".gif" => ImageFormat.Gif, + ".jpg" => ImageFormat.Jpg, + ".png" => ImageFormat.Png, + ".webp" => ImageFormat.Webp, + _ => ImageFormat.Jpg + }; + + return new DynamicImageResponse + { + Format = format, + HasImage = true, + Path = extractedAttachmentPath, + Protocol = MediaProtocol.File + }; + } + /// public bool Supports(BaseItem item) { diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index 60739f156..d226182c0 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -1,6 +1,3 @@ -#nullable enable -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Linq; @@ -18,13 +15,18 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.MediaInfo { /// - /// Uses ffmpeg to create still images from the main video. + /// Uses to create still images from the main video. /// public class VideoImageProvider : IDynamicImageProvider, IHasOrder { private readonly IMediaEncoder _mediaEncoder; private readonly ILogger _logger; + /// + /// Initializes a new instance of the class. + /// + /// The media encoder for capturing images. + /// The logger. public VideoImageProvider(IMediaEncoder mediaEncoder, ILogger logger) { _mediaEncoder = mediaEncoder; diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs index fcea1532a..b194e3885 100644 --- a/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs @@ -17,38 +17,37 @@ namespace Jellyfin.Providers.Tests.MediaInfo { public class EmbeddedImageProviderTests { - public static TheoryData GetSupportedImages_Empty_TestData => - new () + private static TheoryData GetSupportedImages_UnsupportedBaseItems_ReturnsEmpty_TestData() + { + return new () { new AudioBook(), new BoxSet(), new Series(), new Season(), }; - - public static TheoryData> GetSupportedImages_Populated_TestData => - new TheoryData> - { - { new Episode(), new List { ImageType.Primary } }, - { new Movie(), new List { ImageType.Logo, ImageType.Backdrop, ImageType.Primary } }, - }; - - private EmbeddedImageProvider GetEmbeddedImageProvider(IMediaEncoder? mediaEncoder) - { - return new EmbeddedImageProvider(mediaEncoder); } [Theory] - [MemberData(nameof(GetSupportedImages_Empty_TestData))] - public void GetSupportedImages_Empty(BaseItem item) + [MemberData(nameof(GetSupportedImages_UnsupportedBaseItems_ReturnsEmpty_TestData))] + public void GetSupportedImages_UnsupportedBaseItems_ReturnsEmpty(BaseItem item) { var embeddedImageProvider = GetEmbeddedImageProvider(null); - Assert.False(embeddedImageProvider.GetSupportedImages(item).Any()); + Assert.Empty(embeddedImageProvider.GetSupportedImages(item)); + } + + private static TheoryData> GetSupportedImages_SupportedBaseItems_ReturnsPopulated_TestData() + { + return new TheoryData> + { + { new Episode(), new List { ImageType.Primary } }, + { new Movie(), new List { ImageType.Logo, ImageType.Backdrop, ImageType.Primary } }, + }; } [Theory] - [MemberData(nameof(GetSupportedImages_Populated_TestData))] - public void GetSupportedImages_Populated(BaseItem item, IEnumerable expected) + [MemberData(nameof(GetSupportedImages_SupportedBaseItems_ReturnsPopulated_TestData))] + public void GetSupportedImages_SupportedBaseItems_ReturnsPopulated(BaseItem item, IEnumerable expected) { var embeddedImageProvider = GetEmbeddedImageProvider(null); var actual = embeddedImageProvider.GetSupportedImages(item); @@ -56,56 +55,34 @@ namespace Jellyfin.Providers.Tests.MediaInfo } [Fact] - public async void GetImage_Empty_NoStreams() + public async void GetImage_InputWithNoStreams_ReturnsNoImage() { var embeddedImageProvider = GetEmbeddedImageProvider(null); - var input = new Mock(); - input.Setup(movie => movie.GetMediaSources(It.IsAny())) - .Returns(new List()); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List()); + var input = GetMovie(new List(), new List()); - var actual = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actual = await embeddedImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actual); Assert.False(actual.HasImage); } [Fact] - public async void GetImage_Empty_NoLabeledAttachments() + public async void GetImage_InputWithUnlabeledAttachments_ReturnsNoImage() { var embeddedImageProvider = GetEmbeddedImageProvider(null); - var input = new Mock(); // add an attachment without a filename - has a list to look through but finds nothing - input.Setup(movie => movie.GetMediaSources(It.IsAny())) - .Returns(new List { new () { MediaAttachments = new List { new () } } }); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List()); + var input = GetMovie( + new List { new () }, + new List()); - var actual = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actual = await embeddedImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actual); Assert.False(actual.HasImage); } [Fact] - public async void GetImage_Empty_NoEmbeddedLabeledBackdrop() - { - var embeddedImageProvider = GetEmbeddedImageProvider(null); - - var input = new Mock(); - input.Setup(movie => movie.GetMediaSources(It.IsAny())) - .Returns(new List()); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List { new () { Type = MediaStreamType.EmbeddedImage } }); - - var actual = await embeddedImageProvider.GetImage(input.Object, ImageType.Backdrop, CancellationToken.None); - Assert.NotNull(actual); - Assert.False(actual.HasImage); - } - - [Fact] - public async void GetImage_Attached() + public async void GetImage_InputWithLabeledAttachments_ReturnsCorrectSelection() { // first tests file extension detection, second uses mimetype, third defaults to jpg MediaAttachment sampleAttachment1 = new () { FileName = "clearlogo.png", Index = 1 }; @@ -124,25 +101,23 @@ namespace Jellyfin.Providers.Tests.MediaInfo .Returns(Task.FromResult(targetPath3)); var embeddedImageProvider = GetEmbeddedImageProvider(mediaEncoder.Object); - var input = new Mock(); - input.Setup(movie => movie.GetMediaSources(It.IsAny())) - .Returns(new List { new () { MediaAttachments = new List { sampleAttachment1, sampleAttachment2, sampleAttachment3 } } }); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List()); + var input = GetMovie( + new List { sampleAttachment1, sampleAttachment2, sampleAttachment3 }, + new List()); - var actualLogo = await embeddedImageProvider.GetImage(input.Object, ImageType.Logo, CancellationToken.None); + var actualLogo = await embeddedImageProvider.GetImage(input, ImageType.Logo, CancellationToken.None); Assert.NotNull(actualLogo); Assert.True(actualLogo.HasImage); Assert.Equal(targetPath1, actualLogo.Path); Assert.Equal(ImageFormat.Png, actualLogo.Format); - var actualBackdrop = await embeddedImageProvider.GetImage(input.Object, ImageType.Backdrop, CancellationToken.None); + var actualBackdrop = await embeddedImageProvider.GetImage(input, ImageType.Backdrop, CancellationToken.None); Assert.NotNull(actualBackdrop); Assert.True(actualBackdrop.HasImage); Assert.Equal(targetPath2, actualBackdrop.Path); Assert.Equal(ImageFormat.Bmp, actualBackdrop.Format); - var actualPrimary = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actualPrimary = await embeddedImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actualPrimary); Assert.True(actualPrimary.HasImage); Assert.Equal(targetPath3, actualPrimary.Path); @@ -150,23 +125,35 @@ namespace Jellyfin.Providers.Tests.MediaInfo } [Fact] - public async void GetImage_EmbeddedDefault() + public async void GetImage_InputWithUnlabeledEmbeddedImages_BackdropReturnsNoImage() + { + var embeddedImageProvider = GetEmbeddedImageProvider(null); + + var input = GetMovie( + new List(), + new List { new () { Type = MediaStreamType.EmbeddedImage } }); + + var actual = await embeddedImageProvider.GetImage(input, ImageType.Backdrop, CancellationToken.None); + Assert.NotNull(actual); + Assert.False(actual.HasImage); + } + + [Fact] + public async void GetImage_InputWithUnlabeledEmbeddedImages_PrimaryReturnsImage() { MediaStream sampleStream = new () { Type = MediaStreamType.EmbeddedImage, Index = 1 }; string targetPath = "path"; var mediaEncoder = new Mock(MockBehavior.Strict); - mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream, 1, "jpg", CancellationToken.None)) + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream, 1, ".jpg", CancellationToken.None)) .Returns(Task.FromResult(targetPath)); var embeddedImageProvider = GetEmbeddedImageProvider(mediaEncoder.Object); - var input = new Mock(); - input.Setup(movie => movie.GetMediaSources(It.IsAny())) - .Returns(new List()); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List() { sampleStream }); + var input = GetMovie( + new List(), + new List { sampleStream }); - var actual = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actual = await embeddedImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actual); Assert.True(actual.HasImage); Assert.Equal(targetPath, actual.Path); @@ -174,7 +161,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo } [Fact] - public async void GetImage_EmbeddedSelection() + public async void GetImage_InputWithLabeledEmbeddedImages_ReturnsCorrectSelection() { // primary is second stream to ensure it's not defaulting, backdrop is first MediaStream sampleStream1 = new () { Type = MediaStreamType.EmbeddedImage, Index = 1, Comment = "backdrop" }; @@ -183,29 +170,47 @@ namespace Jellyfin.Providers.Tests.MediaInfo string targetPath2 = "path2.jpg"; var mediaEncoder = new Mock(MockBehavior.Strict); - mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream1, 1, "jpg", CancellationToken.None)) + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream1, 1, ".jpg", CancellationToken.None)) .Returns(Task.FromResult(targetPath1)); - mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream2, 2, "jpg", CancellationToken.None)) + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream2, 2, ".jpg", CancellationToken.None)) .Returns(Task.FromResult(targetPath2)); var embeddedImageProvider = GetEmbeddedImageProvider(mediaEncoder.Object); - var input = new Mock(); - input.Setup(movie => movie.GetMediaSources(It.IsAny())) - .Returns(new List()); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List { sampleStream1, sampleStream2 }); + var input = GetMovie( + new List(), + new List { sampleStream1, sampleStream2 }); - var actualPrimary = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actualPrimary = await embeddedImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actualPrimary); Assert.True(actualPrimary.HasImage); Assert.Equal(targetPath2, actualPrimary.Path); Assert.Equal(ImageFormat.Jpg, actualPrimary.Format); - var actualBackdrop = await embeddedImageProvider.GetImage(input.Object, ImageType.Backdrop, CancellationToken.None); + var actualBackdrop = await embeddedImageProvider.GetImage(input, ImageType.Backdrop, CancellationToken.None); Assert.NotNull(actualBackdrop); Assert.True(actualBackdrop.HasImage); Assert.Equal(targetPath1, actualBackdrop.Path); Assert.Equal(ImageFormat.Jpg, actualBackdrop.Format); } + + private static EmbeddedImageProvider GetEmbeddedImageProvider(IMediaEncoder? mediaEncoder) + { + return new EmbeddedImageProvider(mediaEncoder); + } + + private static Movie GetMovie(List mediaAttachments, List mediaStreams) + { + // Mocking IMediaSourceManager GetMediaAttachments and GetMediaStreams instead of mocking Movie works, but + // has concurrency problems between this and VideoImageProviderTests due to BaseItem.MediaSourceManager + // being static + var movie = new Mock(); + + movie.Setup(item => item.GetMediaSources(It.IsAny())) + .Returns(new List { new () { MediaAttachments = mediaAttachments } } ); + movie.Setup(item => item.GetMediaStreams()) + .Returns(mediaStreams); + + return movie.Object; + } } } diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs index 9a5cd79bb..0f51a2b8f 100644 --- a/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs @@ -16,56 +16,51 @@ namespace Jellyfin.Providers.Tests.MediaInfo { public class VideoImageProviderTests { - private VideoImageProvider GetVideoImageProvider(IMediaEncoder? mediaEncoder) - { - // strict to ensure this isn't accidentally used where a prepared mock is intended - mediaEncoder ??= new Mock(MockBehavior.Strict).Object; - return new VideoImageProvider(mediaEncoder, new NullLogger()); - } - [Fact] - public async void GetImage_Empty_IsPlaceholder() + public async void GetImage_InputIsPlaceholder_ReturnsNoImage() { var videoImageProvider = GetVideoImageProvider(null); - var input = new Mock(); - input.Object.IsPlaceHolder = true; + var input = new Movie + { + IsPlaceHolder = true + }; - var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actual = await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actual); Assert.False(actual.HasImage); } [Fact] - public async void GetImage_Empty_NoDefaultVideoStream() + public async void GetImage_NoDefaultVideoStream_ReturnsNoImage() { var videoImageProvider = GetVideoImageProvider(null); - var input = new Mock(); + var input = new Movie + { + DefaultVideoStreamIndex = null + }; - var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actual = await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actual); Assert.False(actual.HasImage); } [Fact] - public async void GetImage_Empty_DefaultSet_NoVideoStream() + public async void GetImage_DefaultSetButNoVideoStream_ReturnsNoImage() { var videoImageProvider = GetVideoImageProvider(null); - var input = new Mock(); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List()); // set a default index but don't put anything there (invalid input, but provider shouldn't break) - input.Object.DefaultVideoStreamIndex = 1; + var input = GetMovie(0, null, new List()); - var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actual = await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actual); Assert.False(actual.HasImage); } [Fact] - public async void GetImage_Extract_DefaultStream() + public async void GetImage_DefaultSetMultipleVideoStreams_ReturnsDefaultStreamImage() { MediaStream firstStream = new () { Type = MediaStreamType.Video, Index = 0 }; MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 1 }; @@ -78,14 +73,9 @@ namespace Jellyfin.Providers.Tests.MediaInfo .Returns(Task.FromResult(targetPath)); var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object); - var input = new Mock(); - input.Setup(movie => movie.GetDefaultVideoStream()) - .Returns(targetStream); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List() { firstStream, targetStream }); - input.Object.DefaultVideoStreamIndex = 1; + var input = GetMovie(1, targetStream, new List { firstStream, targetStream } ); - var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actual = await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actual); Assert.True(actual.HasImage); Assert.Equal(targetPath, actual.Path); @@ -93,7 +83,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo } [Fact] - public async void GetImage_Extract_FallbackToFirstVideoStream() + public async void GetImage_InvalidDefaultSingleVideoStream_ReturnsFirstVideoStreamImage() { MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 0 }; string targetPath = "path.jpg"; @@ -103,13 +93,10 @@ namespace Jellyfin.Providers.Tests.MediaInfo .Returns(Task.FromResult(targetPath)); var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object); - var input = new Mock(); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List() { targetStream }); - // default must be set, ensure a stream is still found if not pointed at a video - input.Object.DefaultVideoStreamIndex = 5; + // provide query results for default (empty) and all streams (populated) + var input = GetMovie(5, null, new List { targetStream }); - var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actual = await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actual); Assert.True(actual.HasImage); Assert.Equal(targetPath, actual.Path); @@ -117,10 +104,12 @@ namespace Jellyfin.Providers.Tests.MediaInfo } [Fact] - public async void GetImage_Time_Default() + public async void GetImage_NoTimeSpanSet_CallsEncoderWithDefaultTime() { MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 0 }; + // use a callback to catch the actual value + // provides more information on failure than verifying a specific input was called on the mock TimeSpan? actualTimeSpan = null; var mediaEncoder = new Mock(MockBehavior.Strict); mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), CancellationToken.None)) @@ -128,20 +117,16 @@ namespace Jellyfin.Providers.Tests.MediaInfo .Returns(Task.FromResult("path")); var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object); - var input = new Mock(); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List() { targetStream }); - // default must be set - input.Object.DefaultVideoStreamIndex = 0; + var input = GetMovie(0, targetStream, new List { targetStream }); // not testing return, just verifying what gets requested for time span - await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.Equal(TimeSpan.FromSeconds(10), actualTimeSpan); } [Fact] - public async void GetImage_Time_Calculated() + public async void GetImage_TimeSpanSet_CallsEncoderWithCalculatedTime() { MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 0 }; @@ -152,17 +137,40 @@ namespace Jellyfin.Providers.Tests.MediaInfo .Returns(Task.FromResult("path")); var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object); - var input = new Mock(); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List() { targetStream }); - // default must be set - input.Object.DefaultVideoStreamIndex = 0; - input.Object.RunTimeTicks = 5000; + var input = GetMovie(0, targetStream, new List { targetStream }); + input.RunTimeTicks = 5000; // not testing return, just verifying what gets requested for time span - await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.Equal(TimeSpan.FromTicks(500), actualTimeSpan); } + + private static VideoImageProvider GetVideoImageProvider(IMediaEncoder? mediaEncoder) + { + // strict to ensure this isn't accidentally used where a prepared mock is intended + mediaEncoder ??= new Mock(MockBehavior.Strict).Object; + return new VideoImageProvider(mediaEncoder, new NullLogger()); + } + + private static Movie GetMovie(int defaultVideoStreamIndex, MediaStream? defaultStream, List mediaStreams) + { + // Mocking IMediaSourceManager GetMediaStreams instead of mocking Movie works, but has concurrency problems + // between this and EmbeddedImageProviderTests due to BaseItem.MediaSourceManager being static + var movie = new Mock + { + Object = + { + DefaultVideoStreamIndex = defaultVideoStreamIndex + } + }; + + movie.Setup(item => item.GetDefaultVideoStream()) + .Returns(defaultStream!); + movie.Setup(item => item.GetMediaStreams()) + .Returns(mediaStreams); + + return movie.Object; + } } } -- cgit v1.2.3 From 1d19a5be617c191a731b76e556fae1e395eb3788 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 9 Nov 2021 22:29:33 +0100 Subject: Fix some warnings down to 580 --- Emby.Dlna/DlnaManager.cs | 19 +--- Emby.Dlna/Main/DlnaEntryPoint.cs | 7 +- Emby.Server.Implementations/Dto/DtoService.cs | 7 +- Emby.Server.Implementations/IO/LibraryMonitor.cs | 2 +- .../Library/LibraryManager.cs | 3 +- .../Library/Resolvers/Audio/MusicAlbumResolver.cs | 2 +- .../LiveTv/EmbyTV/EmbyTV.cs | 2 +- .../LiveTv/EmbyTV/EncodedRecorder.cs | 5 +- .../ScheduledTasks/ScheduledTaskWorker.cs | 10 +- .../Updates/InstallationManager.cs | 2 +- Jellyfin.Api/Controllers/DynamicHlsController.cs | 2 +- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 6 +- .../Models/PlaybackDtos/TranscodingThrottler.cs | 2 +- MediaBrowser.Controller/Entities/Folder.cs | 4 +- .../MediaEncoding/IMediaEncoder.cs | 26 ----- .../Parsers/BaseItemXmlParser.cs | 2 +- .../Attachments/AttachmentExtractor.cs | 7 +- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 117 +-------------------- .../Subtitles/SubtitleEncoder.cs | 11 +- .../Parsers/SeriesNfoParser.cs | 2 +- jellyfin.ruleset | 4 + 21 files changed, 38 insertions(+), 204 deletions(-) (limited to 'MediaBrowser.MediaEncoding/Encoder') diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index f37d2d7d7..277a0e678 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -112,7 +112,7 @@ namespace Emby.Dlna if (profile == null) { - LogUnmatchedProfile(deviceInfo); + _logger.LogInformation("No matching device profile found. The default will need to be used. \n{@Profile}", deviceInfo); } else { @@ -122,23 +122,6 @@ namespace Emby.Dlna return profile; } - private void LogUnmatchedProfile(DeviceIdentification profile) - { - var builder = new StringBuilder(); - - builder.AppendLine("No matching device profile found. The default will need to be used."); - builder.Append("FriendlyName: ").AppendLine(profile.FriendlyName); - builder.Append("Manufacturer: ").AppendLine(profile.Manufacturer); - builder.Append("ManufacturerUrl: ").AppendLine(profile.ManufacturerUrl); - builder.Append("ModelDescription: ").AppendLine(profile.ModelDescription); - builder.Append("ModelName: ").AppendLine(profile.ModelName); - builder.Append("ModelNumber: ").AppendLine(profile.ModelNumber); - builder.Append("ModelUrl: ").AppendLine(profile.ModelUrl); - builder.Append("SerialNumber: ").AppendLine(profile.SerialNumber); - - _logger.LogInformation(builder.ToString()); - } - /// /// Attempts to match a device with a profile. /// Rules: diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 8e89d9ae6..722428c73 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -218,11 +218,6 @@ namespace Emby.Dlna.Main } } - private void LogMessage(string msg) - { - _logger.LogDebug(msg); - } - private void StartDeviceDiscovery(ISsdpCommunicationsServer communicationsServer) { try @@ -272,7 +267,7 @@ namespace Emby.Dlna.Main Environment.OSVersion.VersionString, _config.GetDlnaConfiguration().SendOnlyMatchedHost) { - LogFunction = LogMessage, + LogFunction = (msg) => _logger.LogDebug("{Msg}", msg), SupportPnpRootDevice = false }; diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index c6b32a52c..67ecd04e0 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -134,14 +134,11 @@ namespace Emby.Server.Implementations.Dto var dto = GetBaseItemDtoInternal(item, options, user, owner); if (item is LiveTvChannel tvChannel) { - var list = new List<(BaseItemDto, LiveTvChannel)>(1) { (dto, tvChannel) }; - LivetvManager.AddChannelInfo(list, options, user); + LivetvManager.AddChannelInfo(new[] { (dto, tvChannel) }, options, user); } else if (item is LiveTvProgram) { - var list = new List<(BaseItem, BaseItemDto)>(1) { (item, dto) }; - var task = LivetvManager.AddInfoToProgramDto(list, options.Fields, user); - Task.WaitAll(task); + LivetvManager.AddInfoToProgramDto(new[] { (item, dto) }, options.Fields, user).GetAwaiter().GetResult(); } if (item is IItemByName itemByName diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs index 7ebc800b9..b525f5a2f 100644 --- a/Emby.Server.Implementations/IO/LibraryMonitor.cs +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -267,7 +267,7 @@ namespace Emby.Server.Implementations.IO if (_fileSystemWatchers.TryAdd(path, newWatcher)) { newWatcher.EnableRaisingEvents = true; - _logger.LogInformation("Watching directory " + path); + _logger.LogInformation("Watching directory {Path}", path); } else { diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 2dbb569c6..559da7f5c 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -333,8 +333,7 @@ namespace Emby.Server.Implementations.Library { try { - var task = BaseItem.ChannelManager.DeleteItem(item); - Task.WaitAll(task); + BaseItem.ChannelManager.DeleteItem(item).GetAwaiter().GetResult(); } catch (ArgumentException) { diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index 60720dd2f..9e3f62276 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -151,7 +151,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio { if (parser.IsMultiPart(path)) { - logger.LogDebug("Found multi-disc folder: " + path); + logger.LogDebug("Found multi-disc folder: {Path}", path); Interlocked.Increment(ref discSubfolderCount); } else diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 367f3cb9e..644f9050d 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -957,7 +957,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV public async Task GetChannelStreamWithDirectStreamProvider(string channelId, string streamId, List currentLiveStreams, CancellationToken cancellationToken) { - _logger.LogInformation("Streaming Channel " + channelId); + _logger.LogInformation("Streaming Channel {Id}", channelId); var result = string.IsNullOrEmpty(streamId) ? null : diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 8688688e9..5726d7158 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -87,8 +87,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV ErrorDialog = false }; - var commandLineLogMessage = processStartInfo.FileName + " " + processStartInfo.Arguments; - _logger.LogInformation(commandLineLogMessage); + _logger.LogInformation("{Filename} {Arguments}", processStartInfo.FileName, processStartInfo.Arguments); var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "record-transcode-" + Guid.NewGuid() + ".txt"); Directory.CreateDirectory(Path.GetDirectoryName(logFilePath)); @@ -97,7 +96,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _logFileStream = new FileStream(logFilePath, FileMode.CreateNew, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); await JsonSerializer.SerializeAsync(_logFileStream, mediaSource, _jsonOptions, cancellationToken).ConfigureAwait(false); - await _logFileStream.WriteAsync(Encoding.UTF8.GetBytes(Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine), cancellationToken).ConfigureAwait(false); + await _logFileStream.WriteAsync(Encoding.UTF8.GetBytes(Environment.NewLine + Environment.NewLine + processStartInfo.FileName + " " + processStartInfo.Arguments + Environment.NewLine + Environment.NewLine), cancellationToken).ConfigureAwait(false); _process = new Process { diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index f2cdfeb16..21a7f4f5f 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -638,7 +638,7 @@ namespace Emby.Server.Implementations.ScheduledTasks { try { - _logger.LogInformation(Name + ": Cancelling"); + _logger.LogInformation("{Name}: Cancelling", Name); token.Cancel(); } catch (Exception ex) @@ -652,16 +652,16 @@ namespace Emby.Server.Implementations.ScheduledTasks { try { - _logger.LogInformation(Name + ": Waiting on Task"); + _logger.LogInformation("{Name}: Waiting on Task", Name); var exited = task.Wait(2000); if (exited) { - _logger.LogInformation(Name + ": Task exited"); + _logger.LogInformation("{Name}: Task exited", Name); } else { - _logger.LogInformation(Name + ": Timed out waiting for task to stop"); + _logger.LogInformation("{Name}: Timed out waiting for task to stop", Name); } } catch (Exception ex) @@ -674,7 +674,7 @@ namespace Emby.Server.Implementations.ScheduledTasks { try { - _logger.LogDebug(Name + ": Disposing CancellationToken"); + _logger.LogDebug("{Name}: Disposing CancellationToken", Name); token.Dispose(); } catch (Exception ex) diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 4a022c5db..ef95ebf94 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -571,7 +571,7 @@ namespace Emby.Server.Implementations.Updates ?? _pluginManager.Plugins.FirstOrDefault(p => p.Name.Equals(package.Name, StringComparison.OrdinalIgnoreCase) && p.Version.Equals(package.Version)); await PerformPackageInstallation(package, plugin?.Manifest.Status ?? PluginStatus.Active, cancellationToken).ConfigureAwait(false); - _logger.LogInformation(plugin == null ? "New plugin installed: {PluginName} {PluginVersion}" : "Plugin updated: {PluginName} {PluginVersion}", package.Name, package.Version); + _logger.LogInformation("Plugin {Action}: {PluginName} {PluginVersion}", plugin == null ? "installed" : "updated", package.Name, package.Version); return plugin != null; } diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 049fd503b..caa3d2368 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1391,7 +1391,7 @@ namespace Jellyfin.Api.Controllers } else { - _logger.LogError("Invalid HLS segment container: " + segmentFormat); + _logger.LogError("Invalid HLS segment container: {SegmentFormat}", segmentFormat); } var maxMuxingQueueSize = _encodingOptions.MaxMuxingQueueSize > 128 diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index f435bbf00..9d80070eb 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -543,8 +543,7 @@ namespace Jellyfin.Api.Helpers state, cancellationTokenSource); - var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments; - _logger.LogInformation(commandLineLogMessage); + _logger.LogInformation("{Filename} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments); var logFilePrefix = "FFmpeg.Transcode-"; if (state.VideoRequest != null @@ -562,8 +561,9 @@ namespace Jellyfin.Api.Helpers // FFmpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory. Stream logStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); + var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments; var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(request.Path + Environment.NewLine + Environment.NewLine + JsonSerializer.Serialize(state.MediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine); - await logStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false); + await logStream.WriteAsync(commandLineLogMessageBytes, cancellationTokenSource.Token).ConfigureAwait(false); process.Exited += (sender, args) => OnFfMpegProcessExited(process, transcodingJob, state); diff --git a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs b/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs index 7b32d76ba..0136d9f86 100644 --- a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs +++ b/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs @@ -197,7 +197,7 @@ namespace Jellyfin.Api.Models.PlaybackDtos } } - _logger.LogDebug("No throttle data for " + path); + _logger.LogDebug("No throttle data for {Path}", path); return false; } diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index ffd1c7f0a..ec1ebaabe 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -425,7 +425,7 @@ namespace MediaBrowser.Controller.Entities { if (item.IsFileProtocol) { - Logger.LogDebug("Removed item: " + item.Path); + Logger.LogDebug("Removed item: {Path}", item.Path); item.SetParent(null); LibraryManager.DeleteItem(item, new DeleteOptions { DeleteFileLocation = false }, this, false); @@ -807,7 +807,7 @@ namespace MediaBrowser.Controller.Entities { if (this is not ICollectionFolder) { - Logger.LogDebug("Query requires post-filtering due to LinkedChildren. Type: " + GetType().Name); + Logger.LogDebug("{Type}: Query requires post-filtering due to LinkedChildren.", GetType().Name); return true; } } diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index e6511ca8d..7d62fb6e1 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -100,32 +100,6 @@ namespace MediaBrowser.Controller.MediaEncoding /// Location of video image. Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, string outputExtension, CancellationToken cancellationToken); - /// - /// Extracts the video images on interval. - /// - /// Input file. - /// Video container type. - /// Media stream information. - /// Media source information. - /// Video 3D format. - /// Time interval. - /// Directory to write images. - /// Filename prefix to use. - /// Maximum width of image. - /// CancellationToken to use for operation. - /// A task. - Task ExtractVideoImagesOnInterval( - string inputFile, - string container, - MediaStream videoStream, - MediaSourceInfo mediaSource, - Video3DFormat? threedFormat, - TimeSpan interval, - string targetDirectory, - string filenamePrefix, - int? maxWidth, - CancellationToken cancellationToken); - /// /// Gets the media info. /// diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs index 80eb45423..777fe6774 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs @@ -149,7 +149,7 @@ namespace MediaBrowser.LocalMetadata.Parsers } else { - Logger.LogWarning("Invalid Added value found: " + val); + Logger.LogWarning("Invalid Added value found: {Value}", val); } } diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index a524aeaa9..9ebc0d0cf 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -223,11 +223,10 @@ namespace MediaBrowser.MediaEncoding.Attachments if (failed) { - var msg = $"ffmpeg attachment extraction failed for {inputPath} to {outputPath}"; + _logger.LogError("ffmpeg attachment extraction failed for {InputPath} to {OutputPath}", inputPath, outputPath); - _logger.LogError(msg); - - throw new InvalidOperationException(msg); + throw new InvalidOperationException( + string.Format(CultureInfo.InvariantCulture, "ffmpeg attachment extraction failed for {0} to {1}", inputPath, outputPath)); } else { diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index fbc7ba72f..a2bac7b49 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -682,11 +682,9 @@ namespace MediaBrowser.MediaEncoding.Encoder if (exitCode == -1 || !file.Exists || file.Length == 0) { - var msg = string.Format(CultureInfo.InvariantCulture, "ffmpeg image extraction failed for {0}", inputPath); + _logger.LogError("ffmpeg image extraction failed for {Path}", inputPath); - _logger.LogError(msg); - - throw new FfmpegException(msg); + throw new FfmpegException(string.Format(CultureInfo.InvariantCulture, "ffmpeg image extraction failed for {0}", inputPath)); } return tempExtractPath; @@ -705,117 +703,6 @@ namespace MediaBrowser.MediaEncoding.Encoder return time.ToString(@"hh\:mm\:ss\.fff", CultureInfo.InvariantCulture); } - public async Task ExtractVideoImagesOnInterval( - string inputFile, - string container, - MediaStream videoStream, - MediaSourceInfo mediaSource, - Video3DFormat? threedFormat, - TimeSpan interval, - string targetDirectory, - string filenamePrefix, - int? maxWidth, - CancellationToken cancellationToken) - { - var inputArgument = GetInputArgument(inputFile, mediaSource); - - var vf = "fps=fps=1/" + interval.TotalSeconds.ToString(CultureInfo.InvariantCulture); - - if (maxWidth.HasValue) - { - var maxWidthParam = maxWidth.Value.ToString(CultureInfo.InvariantCulture); - - vf += string.Format(CultureInfo.InvariantCulture, ",scale=min(iw\\,{0}):trunc(ow/dar/2)*2", maxWidthParam); - } - - Directory.CreateDirectory(targetDirectory); - var outputPath = Path.Combine(targetDirectory, filenamePrefix + "%05d.jpg"); - - var args = string.Format(CultureInfo.InvariantCulture, "-i {0} -threads {3} -v quiet {2} -f image2 \"{1}\"", inputArgument, outputPath, vf, _threads); - - if (!string.IsNullOrWhiteSpace(container)) - { - var inputFormat = EncodingHelper.GetInputFormat(container); - if (!string.IsNullOrWhiteSpace(inputFormat)) - { - args = "-f " + inputFormat + " " + args; - } - } - - var processStartInfo = new ProcessStartInfo - { - CreateNoWindow = true, - UseShellExecute = false, - FileName = _ffmpegPath, - Arguments = args, - WindowStyle = ProcessWindowStyle.Hidden, - ErrorDialog = false - }; - - _logger.LogInformation(processStartInfo.FileName + " " + processStartInfo.Arguments); - - await _thumbnailResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - - bool ranToCompletion = false; - - var process = new Process - { - StartInfo = processStartInfo, - EnableRaisingEvents = true - }; - using (var processWrapper = new ProcessWrapper(process, this)) - { - try - { - StartProcess(processWrapper); - - // Need to give ffmpeg enough time to make all the thumbnails, which could be a while, - // but we still need to detect if the process hangs. - // Making the assumption that as long as new jpegs are showing up, everything is good. - - bool isResponsive = true; - int lastCount = 0; - - while (isResponsive) - { - if (await process.WaitForExitAsync(TimeSpan.FromSeconds(30)).ConfigureAwait(false)) - { - ranToCompletion = true; - break; - } - - cancellationToken.ThrowIfCancellationRequested(); - - var jpegCount = _fileSystem.GetFilePaths(targetDirectory) - .Count(i => string.Equals(Path.GetExtension(i), ".jpg", StringComparison.OrdinalIgnoreCase)); - - isResponsive = jpegCount > lastCount; - lastCount = jpegCount; - } - - if (!ranToCompletion) - { - StopProcess(processWrapper, 1000); - } - } - finally - { - _thumbnailResourcePool.Release(); - } - - var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1; - - if (exitCode == -1) - { - var msg = string.Format(CultureInfo.InvariantCulture, "ffmpeg image extraction failed for {0}", inputArgument); - - _logger.LogError(msg); - - throw new FfmpegException(msg); - } - } - } - private void StartProcess(ProcessWrapper process) { process.Process.Start(); diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 2b2de2ff6..89365a516 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -636,17 +636,14 @@ namespace MediaBrowser.MediaEncoding.Subtitles if (failed) { - var msg = $"ffmpeg subtitle extraction failed for {inputPath} to {outputPath}"; + _logger.LogError("ffmpeg subtitle extraction failed for {InputPath} to {OutputPath}", inputPath, outputPath); - _logger.LogError(msg); - - throw new FfmpegException(msg); + throw new FfmpegException( + string.Format(CultureInfo.InvariantCulture, "ffmpeg subtitle extraction failed for {0} to {1}", inputPath, outputPath)); } else { - var msg = $"ffmpeg subtitle extraction completed for {inputPath} to {outputPath}"; - - _logger.LogInformation(msg); + _logger.LogInformation("ffmpeg subtitle extraction completed for {InputPath} to {OutputPath}", inputPath, outputPath); } if (string.Equals(outputCodec, "ass", StringComparison.OrdinalIgnoreCase)) diff --git a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs index 2c893ac9f..3011d65a6 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs @@ -103,7 +103,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers } else { - Logger.LogInformation("Unrecognized series status: " + status); + Logger.LogInformation("Unrecognized series status: {Status}", status); } } diff --git a/jellyfin.ruleset b/jellyfin.ruleset index 3bced438c..e14c1c427 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -44,9 +44,13 @@ + + + + -- cgit v1.2.3 From f73a7a6ed8554a188809c955ddccb48445f4dd71 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Fri, 12 Nov 2021 16:11:15 +0100 Subject: Use ImageFormat instead of string for extension --- .../MediaEncoding/IMediaEncoder.cs | 5 +-- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 36 ++++++++++++---------- .../MediaInfo/EmbeddedImageProvider.cs | 13 ++++---- .../MediaInfo/EmbeddedImageProviderTests.cs | 18 +++++------ 4 files changed, 38 insertions(+), 34 deletions(-) (limited to 'MediaBrowser.MediaEncoding/Encoder') diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 7d62fb6e1..1418e583e 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.MediaInfo; @@ -95,10 +96,10 @@ namespace MediaBrowser.Controller.MediaEncoding /// Media source information. /// Media stream information. /// Index of the stream to extract from. - /// The extension of the file to write, including the '.'. + /// The format of the file to write. /// CancellationToken to use for operation. /// Location of video image. - Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, string outputExtension, CancellationToken cancellationToken); + Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, ImageFormat? targetFormat, CancellationToken cancellationToken); /// /// Gets the media info. diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index a2bac7b49..1c97a1982 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -19,6 +19,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.MediaEncoding.Probing; using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; @@ -478,17 +479,17 @@ namespace MediaBrowser.MediaEncoding.Encoder Protocol = MediaProtocol.File }; - return ExtractImage(path, null, null, imageStreamIndex, mediaSource, true, null, null, ".jpg", cancellationToken); + return ExtractImage(path, null, null, imageStreamIndex, mediaSource, true, null, null, ImageFormat.Jpg, cancellationToken); } public Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream videoStream, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken) { - return ExtractImage(inputFile, container, videoStream, null, mediaSource, false, threedFormat, offset, ".jpg", cancellationToken); + return ExtractImage(inputFile, container, videoStream, null, mediaSource, false, threedFormat, offset, ImageFormat.Jpg, cancellationToken); } - public Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, string outputExtension, CancellationToken cancellationToken) + public Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, ImageFormat? targetFormat, CancellationToken cancellationToken) { - return ExtractImage(inputFile, container, imageStream, imageStreamIndex, mediaSource, false, null, null, outputExtension, cancellationToken); + return ExtractImage(inputFile, container, imageStream, imageStreamIndex, mediaSource, false, null, null, targetFormat, cancellationToken); } private async Task ExtractImage( @@ -500,7 +501,7 @@ namespace MediaBrowser.MediaEncoding.Encoder bool isAudio, Video3DFormat? threedFormat, TimeSpan? offset, - string outputExtension, + ImageFormat? targetFormat, CancellationToken cancellationToken) { var inputArgument = GetInputArgument(inputFile, mediaSource); @@ -510,7 +511,7 @@ namespace MediaBrowser.MediaEncoding.Encoder // The failure of HDR extraction usually occurs when using custom ffmpeg that does not contain the zscale filter. try { - return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, true, outputExtension, cancellationToken).ConfigureAwait(false); + return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, true, targetFormat, cancellationToken).ConfigureAwait(false); } catch (ArgumentException) { @@ -523,7 +524,7 @@ namespace MediaBrowser.MediaEncoding.Encoder try { - return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, true, outputExtension, cancellationToken).ConfigureAwait(false); + return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, true, targetFormat, cancellationToken).ConfigureAwait(false); } catch (ArgumentException) { @@ -536,7 +537,7 @@ namespace MediaBrowser.MediaEncoding.Encoder try { - return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, false, outputExtension, cancellationToken).ConfigureAwait(false); + return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, false, targetFormat, cancellationToken).ConfigureAwait(false); } catch (ArgumentException) { @@ -548,24 +549,25 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, false, outputExtension, cancellationToken).ConfigureAwait(false); + return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, false, targetFormat, cancellationToken).ConfigureAwait(false); } - private async Task ExtractImageInternal(string inputPath, string container, MediaStream videoStream, int? imageStreamIndex, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, bool allowTonemap, string outputExtension, CancellationToken cancellationToken) + private async Task ExtractImageInternal(string inputPath, string container, MediaStream videoStream, int? imageStreamIndex, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, bool allowTonemap, ImageFormat? targetFormat, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(inputPath)) { throw new ArgumentNullException(nameof(inputPath)); } - if (string.IsNullOrEmpty(outputExtension)) + var outputExtension = targetFormat switch { - outputExtension = ".jpg"; - } - else if (outputExtension[0] != '.') - { - outputExtension = "." + outputExtension; - } + ImageFormat.Bmp => ".bmp", + ImageFormat.Gif => ".gif", + ImageFormat.Jpg => ".jpg", + ImageFormat.Png => ".png", + ImageFormat.Webp => ".webp", + _ => ".jpg" + }; var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + outputExtension); Directory.CreateDirectory(Path.GetDirectoryName(tempExtractPath)); diff --git a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs index ca0e72e49..79189416e 100644 --- a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs @@ -156,13 +156,14 @@ namespace MediaBrowser.Providers.MediaInfo } } + var format = ImageFormat.Jpg; string extractedImagePath = - await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.Index, ".jpg", cancellationToken) + await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.Index, format, cancellationToken) .ConfigureAwait(false); return new DynamicImageResponse { - Format = ImageFormat.Jpg, + Format = format, HasImage = true, Path = extractedImagePath, Protocol = MediaProtocol.File @@ -180,10 +181,6 @@ namespace MediaBrowser.Providers.MediaInfo extension = ".jpg"; } - string extractedAttachmentPath = - await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, null, attachmentStream.Index, extension, cancellationToken) - .ConfigureAwait(false); - ImageFormat format = extension switch { ".bmp" => ImageFormat.Bmp, @@ -194,6 +191,10 @@ namespace MediaBrowser.Providers.MediaInfo _ => ImageFormat.Jpg }; + string extractedAttachmentPath = + await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, null, attachmentStream.Index, format, cancellationToken) + .ConfigureAwait(false); + return new DynamicImageResponse { Format = format, diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs index 19391ba68..b6d6c3b25 100644 --- a/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs @@ -57,7 +57,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo for (int i = 1; i <= targetIndex; i++) { var name = i == targetIndex ? filename : "unmatched"; - attachments.Add(new() + attachments.Add(new () { FileName = name, MimeType = mimetype, @@ -66,8 +66,8 @@ namespace Jellyfin.Providers.Tests.MediaInfo } var mediaEncoder = new Mock(MockBehavior.Strict); - mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((_, _, _, _, index, ext, _) => Task.FromResult(pathPrefix + index + ext)); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((_, _, _, _, index, ext, _) => Task.FromResult(pathPrefix + index + "." + ext)); var embeddedImageProvider = new EmbeddedImageProvider(mediaEncoder.Object); var input = GetMovie(attachments, new List()); @@ -81,7 +81,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo else { Assert.True(actual.HasImage); - Assert.Equal(pathPrefix + targetIndex + "." + format, actual.Path, StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(pathPrefix + targetIndex + "." + format, actual.Path, StringComparer.OrdinalIgnoreCase); Assert.Equal(format, actual.Format); } } @@ -97,7 +97,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo for (int i = 1; i <= targetIndex; i++) { var comment = i == targetIndex ? label : "unmatched"; - streams.Add(new() + streams.Add(new () { Type = MediaStreamType.EmbeddedImage, Index = i, @@ -107,11 +107,11 @@ namespace Jellyfin.Providers.Tests.MediaInfo var pathPrefix = "path"; var mediaEncoder = new Mock(MockBehavior.Strict); - mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((_, _, _, stream, index, ext, _) => + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((_, _, _, stream, index, ext, _) => { Assert.Equal(streams[index - 1], stream); - return Task.FromResult(pathPrefix + index + ext); + return Task.FromResult(pathPrefix + index + "." + ext); }); var embeddedImageProvider = new EmbeddedImageProvider(mediaEncoder.Object); @@ -122,7 +122,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo Assert.Equal(hasImage, actual.HasImage); if (hasImage) { - Assert.Equal(pathPrefix + targetIndex + ".jpg", actual.Path); + Assert.Equal(pathPrefix + targetIndex + ".jpg", actual.Path, StringComparer.OrdinalIgnoreCase); Assert.Equal(ImageFormat.Jpg, actual.Format); } } -- cgit v1.2.3 From 4b9c84c52e884e6d35d9bfdc41cbfc04f77b156c Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Thu, 2 Dec 2021 00:49:50 +0800 Subject: EncodingHelper hwaccel pipelines refactor separate the HW pipeline according to HWA method for maintainability. --- Jellyfin.Api/Controllers/DynamicHlsController.cs | 22 +- Jellyfin.Api/Controllers/VideoHlsController.cs | 24 +- .../MediaEncoding/EncodingHelper.cs | 4195 +++++++++++++------- .../MediaEncoding/FilterOptionType.cs | 12 +- .../MediaEncoding/IMediaEncoder.cs | 18 + .../Encoder/EncoderValidator.cs | 82 +- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 51 +- .../Probing/ProbeResultNormalizer.cs | 27 +- .../Configuration/EncodingOptions.cs | 14 +- 9 files changed, 2841 insertions(+), 1604 deletions(-) (limited to 'MediaBrowser.MediaEncoding/Encoder') diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index caa3d2368..769f3372d 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1567,24 +1567,18 @@ namespace Jellyfin.Api.Controllers // args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0"; - var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; - - if (hasGraphicalSubs) - { - // Graphical subs overlay and resolution params. - args += _encodingHelper.GetGraphicalSubtitleParam(state, _encodingOptions, codec); - } - else - { - // Resolution params. - args += _encodingHelper.GetOutputSizeParam(state, _encodingOptions, codec); - } + // video processing filters. + args += _encodingHelper.GetVideoProcessingFilterParam(state, _encodingOptions, codec); // -start_at_zero is necessary to use with -ss when seeking, // otherwise the target position cannot be determined. - if (!(state.SubtitleStream != null && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)) + if (state.SubtitleStream != null) { - args += " -start_at_zero"; + // Disable start_at_zero for external graphical subs + if (!(state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)) + { + args += " -start_at_zero"; + } } // args += " -flags -global_header"; diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs index ef25db8c9..2f9565497 100644 --- a/Jellyfin.Api/Controllers/VideoHlsController.cs +++ b/Jellyfin.Api/Controllers/VideoHlsController.cs @@ -552,22 +552,18 @@ namespace Jellyfin.Api.Controllers args += " -bf 0"; } - var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + // video processing filters. + args += _encodingHelper.GetVideoProcessingFilterParam(state, _encodingOptions, codec); - if (hasGraphicalSubs) + // -start_at_zero is necessary to use with -ss when seeking, + // otherwise the target position cannot be determined. + if (state.SubtitleStream != null) { - // Graphical subs overlay and resolution params. - args += _encodingHelper.GetGraphicalSubtitleParam(state, _encodingOptions, codec); - } - else - { - // Resolution params. - args += _encodingHelper.GetOutputSizeParam(state, _encodingOptions, codec); - } - - if (state.SubtitleStream == null || !state.SubtitleStream.IsExternal || state.SubtitleStream.IsTextSubtitleStream) - { - args += " -start_at_zero"; + // Disable start_at_zero for external graphical subs + if (!(state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)) + { + args += " -start_at_zero"; + } } } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 5715194b8..75a36d815 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -24,7 +24,7 @@ namespace MediaBrowser.Controller.MediaEncoding private readonly IMediaEncoder _mediaEncoder; private readonly ISubtitleEncoder _subtitleEncoder; - private static readonly string[] _videoProfiles = new[] + private static readonly string[] _videoProfilesH264 = new[] { "ConstrainedBaseline", "Baseline", @@ -32,10 +32,22 @@ namespace MediaBrowser.Controller.MediaEncoding "Main", "High", "ProgressiveHigh", - "ConstrainedHigh" + "ConstrainedHigh", + "High10" }; - private static readonly Version _minVersionForCudaOverlay = new Version(4, 4); + private static readonly string[] _videoProfilesH265 = new[] + { + "Main", + "Main10" + }; + + private static readonly string _qsvAlias = "qs"; + private static readonly string _vaapiAlias = "va"; + private static readonly string _d3d11vaAlias = "dx11"; + private static readonly string _videotoolboxAlias = "vt"; + private static readonly string _openclAlias = "ocl"; + private static readonly string _cudaAlias = "cu"; public EncodingHelper( IMediaEncoder mediaEncoder, @@ -62,15 +74,13 @@ namespace MediaBrowser.Controller.MediaEncoding var codecMap = new Dictionary(StringComparer.OrdinalIgnoreCase) { - { "qsv", hwEncoder + "_qsv" }, - { hwEncoder + "_qsv", hwEncoder + "_qsv" }, - { "nvenc", hwEncoder + "_nvenc" }, { "amf", hwEncoder + "_amf" }, - { "omx", hwEncoder + "_omx" }, - { hwEncoder + "_v4l2m2m", hwEncoder + "_v4l2m2m" }, - { "mediacodec", hwEncoder + "_mediacodec" }, + { "nvenc", hwEncoder + "_nvenc" }, + { "qsv", hwEncoder + "_qsv" }, { "vaapi", hwEncoder + "_vaapi" }, - { "videotoolbox", hwEncoder + "_videotoolbox" } + { "videotoolbox", hwEncoder + "_videotoolbox" }, + { "v4l2m2m", hwEncoder + "_v4l2m2m" }, + { "omx", hwEncoder + "_omx" }, }; if (!string.IsNullOrEmpty(hwType) @@ -91,11 +101,9 @@ namespace MediaBrowser.Controller.MediaEncoding private bool IsVaapiSupported(EncodingJobInfo state) { - var videoStream = state.VideoStream; - // vaapi will throw an error with this input // [vaapi @ 0x7faed8000960] No VAAPI support for codec mpeg4 profile -99. - if (string.Equals(videoStream?.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(state.VideoStream?.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase)) { return false; } @@ -103,82 +111,59 @@ namespace MediaBrowser.Controller.MediaEncoding return _mediaEncoder.SupportsHwaccel("vaapi"); } - private bool IsCudaSupported() + private bool IsVaapiFullSupported() { - return _mediaEncoder.SupportsHwaccel("cuda") - && _mediaEncoder.SupportsFilter("scale_cuda") - && _mediaEncoder.SupportsFilter("yadif_cuda") - && _mediaEncoder.SupportsFilter("hwupload_cuda"); + return _mediaEncoder.SupportsHwaccel("vaapi") + && _mediaEncoder.SupportsFilter("scale_vaapi") + && _mediaEncoder.SupportsFilter("deinterlace_vaapi") + && _mediaEncoder.SupportsFilter("tonemap_vaapi") + && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayVaapiFrameSync) + && _mediaEncoder.SupportsFilter("hwupload_vaapi"); } - private bool IsOpenclTonemappingSupported(EncodingJobInfo state, EncodingOptions options) + private bool IsOpenclFullSupported() { - var videoStream = state.VideoStream; - if (videoStream == null) - { - return false; - } + return _mediaEncoder.SupportsHwaccel("opencl") + && _mediaEncoder.SupportsFilter("scale_opencl") + && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapOpenclBt2390) + && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayOpenclFrameSync); + } - return options.EnableTonemapping - && (string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoStream.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase)) - && IsColorDepth10(state) - && _mediaEncoder.SupportsHwaccel("opencl") - && _mediaEncoder.SupportsFilter("tonemap_opencl"); + private bool IsCudaFullSupported() + { + return _mediaEncoder.SupportsHwaccel("cuda") + && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat) + && _mediaEncoder.SupportsFilter("yadif_cuda") + && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapCudaName) + && _mediaEncoder.SupportsFilter("overlay_cuda") + && _mediaEncoder.SupportsFilter("hwupload_cuda"); } - private bool IsCudaTonemappingSupported(EncodingJobInfo state, EncodingOptions options) + private bool IsHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options) { - var videoStream = state.VideoStream; - if (videoStream == null) + if (state.VideoStream == null) { return false; } return options.EnableTonemapping - && (string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoStream.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase)) - && IsColorDepth10(state) - && _mediaEncoder.SupportsHwaccel("cuda") - && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapCudaName); + && (string.Equals(state.VideoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) + || string.Equals(state.VideoStream.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase)) + && GetVideoColorBitDepth(state) == 10; } - private bool IsVppTonemappingSupported(EncodingJobInfo state, EncodingOptions options) + private bool IsVaapiVppTonemapAvailable(EncodingJobInfo state, EncodingOptions options) { - var videoStream = state.VideoStream; - if (videoStream == null) + if (state.VideoStream == null) { - // Remote stream doesn't have media info, disable vpp tonemapping. return false; } - var codec = videoStream.Codec; - if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) - { - // Limited to HEVC for now since the filter doesn't accept master data from VP9. - return options.EnableVppTonemapping - && string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) - && IsColorDepth10(state) - && string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) - && _mediaEncoder.SupportsHwaccel("vaapi") - && _mediaEncoder.SupportsFilter("tonemap_vaapi"); - } - - // Hybrid VPP tonemapping for QSV with VAAPI - if (OperatingSystem.IsLinux() && string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) - { - // Limited to HEVC for now since the filter doesn't accept master data from VP9. - return options.EnableVppTonemapping - && string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) - && IsColorDepth10(state) - && string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) - && _mediaEncoder.SupportsHwaccel("vaapi") - && _mediaEncoder.SupportsFilter("tonemap_vaapi") - && _mediaEncoder.SupportsHwaccel("qsv"); - } - // Native VPP tonemapping may come to QSV in the future. - return false; + + return options.EnableVppTonemapping + && string.Equals(state.VideoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) + && GetVideoColorBitDepth(state) == 10; } /// @@ -463,11 +448,20 @@ namespace MediaBrowser.Controller.MediaEncoding return "copy"; } - public int GetVideoProfileScore(string profile) + public int GetVideoProfileScore(string videoCodec, string videoProfile) { // strip spaces because they may be stripped out on the query string - profile = profile.Replace(" ", string.Empty, StringComparison.Ordinal); - return Array.FindIndex(_videoProfiles, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreCase)); + string profile = videoProfile.Replace(" ", string.Empty, StringComparison.Ordinal); + if (string.Equals("h264", videoCodec, StringComparison.OrdinalIgnoreCase)) + { + return Array.FindIndex(_videoProfilesH264, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreCase)); + } + else if (string.Equals("hevc", videoCodec, StringComparison.OrdinalIgnoreCase)) + { + return Array.FindIndex(_videoProfilesH265, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreCase)); + } + + return -1; } public string GetInputPathArgument(EncodingJobInfo state) @@ -526,161 +520,359 @@ namespace MediaBrowser.Controller.MediaEncoding return codec.ToLowerInvariant(); } + public string GetVideoToolboxDeviceArgs(string alias) + { + alias ??= _videotoolboxAlias; + + // device selection in vt is not supported. + return " -init_hw_device videotoolbox=" + alias; + } + + public string GetCudaDeviceArgs(int deviceIndex, string alias) + { + alias ??= _cudaAlias; + deviceIndex = deviceIndex >= 0 + ? deviceIndex + : 0; + + return string.Format( + CultureInfo.InvariantCulture, + " -init_hw_device cuda={0}:{1}", + alias, deviceIndex); + } + + public string GetOpenclDeviceArgs(int deviceIndex, string deviceVendorName, string srcDeviceAlias, string alias) + { + alias ??= _openclAlias; + deviceIndex = deviceIndex >= 0 + ? deviceIndex + : 0; + var vendorOpts = !string.IsNullOrEmpty(deviceVendorName) + ? (":." + deviceIndex + ",device_vendor=\"" + deviceVendorName + "\"") + : ":0.0"; + var options = !string.IsNullOrEmpty(srcDeviceAlias) + ? ("@" + srcDeviceAlias) + : vendorOpts; + + return string.Format( + CultureInfo.InvariantCulture, + " -init_hw_device opencl={0}{1}", + alias, options); + } + + public string GetD3d11vaDeviceArgs(int deviceIndex, string deviceVendorId, string alias) + { + alias ??= _d3d11vaAlias; + deviceIndex = deviceIndex >= 0 ? deviceIndex : 0; + var options = !string.IsNullOrEmpty(deviceVendorId) + ? (",vendor=" + deviceVendorId) + : Convert.ToString(deviceIndex, CultureInfo.InvariantCulture); + + return string.Format( + CultureInfo.InvariantCulture, + " -init_hw_device d3d11va={0}:{1}", + alias, options); + } + + public string GetVaapiDeviceArgs(string renderNodePath, string kernelDriver, string driver, string alias) + { + alias ??= _vaapiAlias; + renderNodePath = renderNodePath ?? "/dev/dri/renderD128"; + var options = (!string.IsNullOrEmpty(kernelDriver) && !string.IsNullOrEmpty(driver)) + ? (",kernel_driver=" + kernelDriver + ",driver=" + driver) + : renderNodePath; + + return string.Format( + CultureInfo.InvariantCulture, + " -init_hw_device vaapi={0}:{1}", + alias, options); + } + + public string GetQsvDeviceArgs(string alias) + { + var arg = " -init_hw_device qsv=" + (alias ?? _qsvAlias); + var args = new StringBuilder(); + var isWindows = OperatingSystem.IsWindows(); + var isLinux = OperatingSystem.IsLinux(); + if (isLinux) + { + // derive qsv from vaapi device + string srcAlias = _vaapiAlias; + args.Append(GetVaapiDeviceArgs(null, "i915", "iHD", srcAlias)) + .Append(arg + "@" + srcAlias); + } + else if (isWindows) + { + // derive qsv from d3d11va device + string srcAlias = _d3d11vaAlias; + args.Append(GetD3d11vaDeviceArgs(0, "0x8086", srcAlias)) + .Append(arg + "@" + srcAlias); + } + else + { + return null; + } + + return args.ToString(); + } + + public string GetFilterHwDeviceArgs(string alias) + { + return !string.IsNullOrEmpty(alias) + ? (" -filter_hw_device " + alias) + : string.Empty; + } + + public string GetGraphicalSubCanvasSize(EncodingJobInfo state) + { + if (state.SubtitleStream != null + && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode + && !state.SubtitleStream.IsTextSubtitleStream) + { + var inW = state.VideoStream?.Width; + var inH = state.VideoStream?.Height; + var reqW = state.BaseRequest.Width; + var reqH = state.BaseRequest.Height; + var reqMaxW = state.BaseRequest.MaxWidth; + var reqMaxH = state.BaseRequest.MaxHeight; + + // setup a relative small canvas_size for overlay_qsv to reduce transfer overhead + var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, 1080); + + if (overlayW.HasValue && overlayH.HasValue) + { + return string.Format( + CultureInfo.InvariantCulture, + " -canvas_size {0}x{1}", + overlayW.Value, + overlayH.Value); + } + } + + return string.Empty; + } + /// - /// Gets the input argument. + /// Gets the input video hwaccel argument. /// /// Encoding state. /// Encoding options. - /// Input arguments. - public string GetInputArgument(EncodingJobInfo state, EncodingOptions options) + /// Input video hwaccel arguments. + public string GetInputVideoHwaccelArgs(EncodingJobInfo state, EncodingOptions options) { - var arg = new StringBuilder(); - var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options) ?? string.Empty; - var outputVideoCodec = GetVideoEncoder(state, options) ?? string.Empty; + if (!state.IsVideoRequest) + { + return null; + } + + var vidEncoder = GetVideoEncoder(state, options) ?? string.Empty; + if (IsCopyCodec(vidEncoder)) + { + return null; + } + + var args = new StringBuilder(); var isWindows = OperatingSystem.IsWindows(); var isLinux = OperatingSystem.IsLinux(); var isMacOS = OperatingSystem.IsMacOS(); + var optHwaccelType = options.HardwareAccelerationType; + var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty; #pragma warning disable CA1508 // Defaults to string.Empty - var isSwDecoder = string.IsNullOrEmpty(videoDecoder); + var isSwVidDecoder = string.IsNullOrEmpty(vidDecoder); #pragma warning restore CA1508 - var isD3d11vaDecoder = videoDecoder.IndexOf("d3d11va", StringComparison.OrdinalIgnoreCase) != -1; - var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; - var isVaapiEncoder = outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; - var isQsvDecoder = videoDecoder.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; - var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; - var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase); - var isCuvidHevcDecoder = videoDecoder.Contains("hevc_cuvid", StringComparison.OrdinalIgnoreCase); - var isCuvidVp9Decoder = videoDecoder.Contains("vp9_cuvid", StringComparison.OrdinalIgnoreCase); - var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options); - var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); - var isCudaTonemappingSupported = IsCudaTonemappingSupported(state, options); - - if (!IsCopyCodec(outputVideoCodec)) - { - if (state.IsVideoRequest - && _mediaEncoder.SupportsHwaccel("vaapi") - && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) - { - if (isVaapiDecoder) + var isHwTonemapAvailable = IsHwTonemapAvailable(state, options); + + if (string.Equals(optHwaccelType, "vaapi", StringComparison.OrdinalIgnoreCase)) + { + if (!isLinux || !_mediaEncoder.SupportsHwaccel("vaapi")) + { + return string.Empty; + } + + var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase); + var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase); + if (!isVaapiDecoder && !isVaapiEncoder) + { + return string.Empty; + } + + var vaArgs = GetVaapiDeviceArgs(options.VaapiDevice, null, null, _vaapiAlias); + var filterDevArgs = GetFilterHwDeviceArgs(_vaapiAlias); + + if (isHwTonemapAvailable && IsOpenclFullSupported()) + { + if (_mediaEncoder.IsVaapiDeviceInteliHD() || _mediaEncoder.IsVaapiDeviceInteli965()) { - if (isOpenclTonemappingSupported && !isVppTonemappingSupported) + if (!isVaapiDecoder) { - arg.Append("-init_hw_device vaapi=va:") - .Append(options.VaapiDevice) - .Append(" -init_hw_device opencl=ocl@va ") - .Append("-hwaccel_device va ") - .Append("-hwaccel_output_format vaapi ") - .Append("-filter_hw_device ocl "); - } - else - { - arg.Append("-hwaccel_output_format vaapi ") - .Append("-vaapi_device ") - .Append(options.VaapiDevice) - .Append(' '); + vaArgs += GetOpenclDeviceArgs(0, null, _vaapiAlias, _openclAlias); + filterDevArgs = GetFilterHwDeviceArgs(_openclAlias); } } - else if (!isVaapiDecoder && isVaapiEncoder) + else if (_mediaEncoder.IsVaapiDeviceAmd()) + { + vaArgs += GetOpenclDeviceArgs(0, "Advanced Micro Devices", null, _openclAlias); + filterDevArgs = GetFilterHwDeviceArgs(_openclAlias); + } + else { - arg.Append("-vaapi_device ") - .Append(options.VaapiDevice) - .Append(' '); + vaArgs += GetOpenclDeviceArgs(0, null, null, _openclAlias); + filterDevArgs = GetFilterHwDeviceArgs(_openclAlias); } + } - arg.Append("-autorotate 0 "); + args.Append(vaArgs) + .Append(filterDevArgs); + } + else if (string.Equals(optHwaccelType, "qsv", StringComparison.OrdinalIgnoreCase)) + { + if ((!isLinux && !isWindows) || !_mediaEncoder.SupportsHwaccel("qsv")) + { + return string.Empty; } - if (state.IsVideoRequest - && string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) + var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase); + var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase); + var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase); + var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase); + var isHwDecoder = isQsvDecoder || isVaapiDecoder || isD3d11vaDecoder; + if (!isHwDecoder && !isQsvEncoder) { - var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + return string.Empty; + } - if (isQsvEncoder) + var qsvArgs = GetQsvDeviceArgs(_qsvAlias); + var filterDevArgs = GetFilterHwDeviceArgs(_qsvAlias); + // child device used by qsv. + if (_mediaEncoder.SupportsHwaccel("vaapi") || _mediaEncoder.SupportsHwaccel("d3d11va")) + { + if (isHwTonemapAvailable && IsOpenclFullSupported()) { - if (isQsvDecoder) + var srcAlias = isLinux ? _vaapiAlias : _d3d11vaAlias; + qsvArgs += GetOpenclDeviceArgs(0, null, srcAlias, _openclAlias); + if (!isHwDecoder) { - if (isLinux) - { - if (hasGraphicalSubs) - { - arg.Append("-init_hw_device qsv=hw -filter_hw_device hw "); - } - else - { - arg.Append("-hwaccel qsv "); - } - } - - if (isWindows) - { - arg.Append("-hwaccel qsv "); - } + filterDevArgs = GetFilterHwDeviceArgs(_openclAlias); } + } + } - // While using SW decoder - else if (isSwDecoder) - { - arg.Append("-init_hw_device qsv=hw -filter_hw_device hw "); - } + args.Append(qsvArgs) + .Append(filterDevArgs); + } + else if (string.Equals(optHwaccelType, "nvenc", StringComparison.OrdinalIgnoreCase)) + { + if ((!isLinux && !isWindows) || !IsCudaFullSupported()) + { + return string.Empty; + } - // Hybrid VPP tonemapping with VAAPI - else if (isVaapiDecoder && isVppTonemappingSupported) - { - arg.Append("-init_hw_device vaapi=va:") - .Append(options.VaapiDevice) - .Append(" -init_hw_device qsv@va ") - .Append("-hwaccel_output_format vaapi "); - } + var isCuvidDecoder = vidDecoder.Contains("cuvid", StringComparison.OrdinalIgnoreCase); + var isNvdecDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase); + var isNvencEncoder = vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase); + var isHwDecoder = isNvdecDecoder || isCuvidDecoder; + if (!isHwDecoder && !isNvencEncoder) + { + return string.Empty; + } - arg.Append("-autorotate 0 "); - } + var cuArgs = GetCudaDeviceArgs(0, _cudaAlias); + var filterDevArgs = GetFilterHwDeviceArgs(_cudaAlias); + + // workaround for "No decoder surfaces left" error, + // but will increase vram usage. https://trac.ffmpeg.org/ticket/7562 + var extraHwFramesArgs = " -extra_hw_frames 3"; + + args.Append(cuArgs) + .Append(filterDevArgs) + .Append(extraHwFramesArgs); + } + else if (string.Equals(optHwaccelType, "amf", StringComparison.OrdinalIgnoreCase)) + { + if (!isWindows || !_mediaEncoder.SupportsHwaccel("d3d11va")) + { + return string.Empty; } - if (state.IsVideoRequest - && string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) - && isNvdecDecoder) + var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase); + var isAmfEncoder = vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase); + if (!isD3d11vaDecoder && !isAmfEncoder) { - // Fix for 'No decoder surfaces left' error. https://trac.ffmpeg.org/ticket/7562 - arg.Append("-hwaccel_output_format cuda -extra_hw_frames 3 -autorotate 0 "); + return string.Empty; } - if (state.IsVideoRequest - && string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) - && (isNvdecDecoder || isCuvidHevcDecoder || isCuvidVp9Decoder || isSwDecoder)) + // no dxva video processor hw filter. + var dx11Args = GetD3d11vaDeviceArgs(0, "0x1002", _d3d11vaAlias); + var filterDevArgs = string.Empty; + if (IsOpenclFullSupported()) { - if (!isCudaTonemappingSupported && isOpenclTonemappingSupported) - { - arg.Append("-init_hw_device opencl=ocl:") - .Append(options.OpenclDevice) - .Append(" -filter_hw_device ocl "); - } + dx11Args += GetOpenclDeviceArgs(0, null, _d3d11vaAlias, _openclAlias); + filterDevArgs = GetFilterHwDeviceArgs(_openclAlias); } - if (state.IsVideoRequest - && string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) - && (isD3d11vaDecoder || isSwDecoder)) + args.Append(dx11Args) + .Append(filterDevArgs); + } + else if (string.Equals(optHwaccelType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) + { + if (!isMacOS || !_mediaEncoder.SupportsHwaccel("videotoolbox")) { - if (isOpenclTonemappingSupported) - { - arg.Append("-init_hw_device opencl=ocl:") - .Append(options.OpenclDevice) - .Append(" -filter_hw_device ocl "); - } + return string.Empty; } - if (state.IsVideoRequest - && string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) + var isVideotoolboxDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase); + var isVideotoolboxEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase); + if (!isVideotoolboxDecoder && !isVideotoolboxEncoder) { - arg.Append("-hwaccel videotoolbox "); + return string.Empty; } + + // no videotoolbox hw filter. + var vtArgs = GetVideoToolboxDeviceArgs(_videotoolboxAlias); + args.Append(vtArgs); } - arg.Append("-i ") + if (!string.IsNullOrEmpty(vidDecoder)) + { + args.Append(vidDecoder); + } + + // hw transpose filters should be added manually. + args.Append(" -autorotate 0"); + + return args.ToString().Trim(); + } + + /// + /// Gets the input argument. + /// + /// Encoding state. + /// Encoding options. + /// Input arguments. + public string GetInputArgument(EncodingJobInfo state, EncodingOptions options) + { + var arg = new StringBuilder(); + var inputVidHwaccelArgs = GetInputVideoHwaccelArgs(state, options); + + if (!string.IsNullOrEmpty(inputVidHwaccelArgs)) + { + arg.Append(inputVidHwaccelArgs); + } + + var canvasArgs = GetGraphicalSubCanvasSize(state); + if (!string.IsNullOrEmpty(canvasArgs)) + { + arg.Append(canvasArgs); + } + + arg.Append(" -i ") .Append(GetInputPathArgument(state)); + // sub2video for external graphical subtitles if (state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode - && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream) + && !state.SubtitleStream.IsTextSubtitleStream + && state.SubtitleStream.IsExternal) { var subtitlePath = state.SubtitleStream.Path; @@ -693,7 +885,19 @@ namespace MediaBrowser.Controller.MediaEncoding } } - arg.Append(" -i \"").Append(subtitlePath).Append('\"'); + // Also seek the external subtitles stream. + var seekSubParam = GetFastSeekCommandLineParameter(state, options); + if (!string.IsNullOrEmpty(seekSubParam)) + { + arg.Append(' ').Append(seekSubParam); + } + + if (!string.IsNullOrEmpty(canvasArgs)) + { + arg.Append(canvasArgs); + } + + arg.Append(" -i file:\"").Append(subtitlePath).Append('\"'); } return arg.ToString(); @@ -809,6 +1013,26 @@ namespace MediaBrowser.Controller.MediaEncoding return FormattableString.Invariant($" -maxrate {bitrate} -bufsize {bufsize}"); } + if (string.Equals(videoCodec, "h264_amf", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoCodec, "hevc_amf", StringComparison.OrdinalIgnoreCase)) + { + return FormattableString.Invariant($" -qmin 18 -qmax 32 -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}"); + } + + if (string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoCodec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) + { + // VBR in i965 driver may result in pixelated output. + if (_mediaEncoder.IsVaapiDeviceInteli965()) + { + return FormattableString.Invariant($" -rc_mode CBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}"); + } + else + { + return FormattableString.Invariant($" -rc_mode VBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}"); + } + } + return FormattableString.Invariant($" -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}"); } @@ -845,8 +1069,10 @@ namespace MediaBrowser.Controller.MediaEncoding /// Gets the text subtitle param. /// /// The state. + /// Enable alpha processing. + /// Enable sub2video mode. /// System.String. - public string GetTextSubtitleParam(EncodingJobInfo state) + public string GetTextSubtitlesFilter(EncodingJobInfo state, bool enableAlpha, bool enableSub2video) { var seconds = Math.Round(TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds); @@ -855,6 +1081,9 @@ namespace MediaBrowser.Controller.MediaEncoding ? string.Empty : string.Format(CultureInfo.InvariantCulture, ",setpts=PTS -{0}/TB", seconds); + var alphaParam = enableAlpha ? (":alpha=" + Convert.ToInt32(enableAlpha)) : string.Empty; + var sub2videoParam = enableSub2video ? (":sub2video=" + Convert.ToInt32(enableSub2video)) : string.Empty; + // TODO // var fallbackFontPath = Path.Combine(_appPaths.ProgramDataPath, "fonts", "DroidSansFallback.ttf"); // string fallbackFontParam = string.Empty; @@ -876,7 +1105,6 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.SubtitleStream.IsExternal) { var subtitlePath = state.SubtitleStream.Path; - var charsetParam = string.Empty; if (!string.IsNullOrEmpty(state.SubtitleStream.Language)) @@ -896,9 +1124,11 @@ namespace MediaBrowser.Controller.MediaEncoding // TODO: Perhaps also use original_size=1920x800 ?? return string.Format( CultureInfo.InvariantCulture, - "subtitles=filename='{0}'{1}{2}", + "subtitles=f='{0}'{1}{2}{3}{4}", _mediaEncoder.EscapeSubtitleFilterPath(subtitlePath), charsetParam, + alphaParam, + sub2videoParam, // fallbackFontParam, setPtsParam); } @@ -907,9 +1137,11 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Format( CultureInfo.InvariantCulture, - "subtitles='{0}:si={1}'{2}", + "subtitles='{0}:si={1}{2}{3}'{4}", _mediaEncoder.EscapeSubtitleFilterPath(mediaPath), state.InternalSubtitleStreamOffset.ToString(CultureInfo.InvariantCulture), + alphaParam, + sub2videoParam, // fallbackFontParam, setPtsParam); } @@ -994,11 +1226,11 @@ namespace MediaBrowser.Controller.MediaEncoding || string.Equals(codec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) { - args += " " + keyFrameArg; + args += keyFrameArg; } else { - args += " " + keyFrameArg + gopArg; + args += keyFrameArg + gopArg; } return args; @@ -1016,54 +1248,49 @@ namespace MediaBrowser.Controller.MediaEncoding { var param = string.Empty; - if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) - && !string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) - && !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) - && !string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) - && !string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) - && !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase) - && !string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase) - && !string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase) - && !string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) - && !string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) - { - param += " -pix_fmt yuv420p"; - } - - if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) - { - var videoStream = state.VideoStream; - var isColorDepth10 = IsColorDepth10(state); - var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions) ?? string.Empty; - var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase); - - if (!isNvdecDecoder) - { - if (isColorDepth10 - && _mediaEncoder.SupportsHwaccel("opencl") - && encodingOptions.EnableTonemapping - && !string.IsNullOrEmpty(videoStream.VideoRange) - && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase)) - { - param += " -pix_fmt nv12"; - } - else - { - param += " -pix_fmt yuv420p"; - } + // Tutorials: Enable Intel GuC / HuC firmware loading for Low Power Encoding. + // https://01.org/linuxgraphics/downloads/firmware + // https://wiki.archlinux.org/title/intel_graphics#Enable_GuC_/_HuC_firmware_loading + // Intel Low Power Encoding can save unnecessary CPU-GPU synchronization, + // which will reduce overhead in performance intensive tasks such as 4k transcoding and tonemapping. + var intelLowPowerHwEncoding = false; + + if (string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) + { + var isIntelVaapiDriver = _mediaEncoder.IsVaapiDeviceInteliHD() || _mediaEncoder.IsVaapiDeviceInteli965(); + + if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + { + intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder && isIntelVaapiDriver; + } + else if (string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) + { + intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder && isIntelVaapiDriver; + } + } + else if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) + { + if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)) + { + intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder; + } + else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase)) + { + intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder; } } + if (intelLowPowerHwEncoding) + { + param += " -low_power 1"; + } + if (string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) { param += " -pix_fmt nv21"; } - var isVc1 = state.VideoStream != null && - string.Equals(state.VideoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase); + var isVc1 = string.Equals(state.VideoStream.Codec ?? string.Empty, "vc1", StringComparison.OrdinalIgnoreCase); var isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase); if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || isLibX265) @@ -1174,19 +1401,6 @@ namespace MediaBrowser.Controller.MediaEncoding break; } - var videoStream = state.VideoStream; - var isColorDepth10 = IsColorDepth10(state); - - if (isColorDepth10 - && _mediaEncoder.SupportsHwaccel("opencl") - && encodingOptions.EnableTonemapping - && !string.IsNullOrEmpty(videoStream.VideoRange) - && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase)) - { - // Enhance workload when tone mapping with AMF on some APUs - param += " -preanalysis true"; - } - if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) { param += " -header_insertion_mode gop -gops_per_idr 1"; @@ -1359,13 +1573,6 @@ namespace MediaBrowser.Controller.MediaEncoding profile = "constrained_high"; } - // Currently hevc_amf only support encoding HEVC Main Profile, otherwise force Main Profile. - if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) - && profile.Contains("main10", StringComparison.OrdinalIgnoreCase)) - { - profile = "main"; - } - if (!string.IsNullOrEmpty(profile)) { if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) @@ -1382,7 +1589,7 @@ namespace MediaBrowser.Controller.MediaEncoding { level = NormalizeTranscodingLevel(state, level); - // libx264, QSV, AMF, VAAPI can adjust the given level to match the output. + // libx264, QSV, AMF can adjust the given level to match the output. if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) || string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)) { @@ -1407,6 +1614,11 @@ namespace MediaBrowser.Controller.MediaEncoding // level option may cause NVENC to fail. // NVENC cannot adjust the given level, just throw an error. } + else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) + { + // level option may cause corrupted frames on AMD VAAPI. + } else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) || !string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) { @@ -1491,8 +1703,8 @@ namespace MediaBrowser.Controller.MediaEncoding if (!string.IsNullOrEmpty(videoStream.Profile) && !requestedProfiles.Contains(videoStream.Profile.Replace(" ", string.Empty, StringComparison.Ordinal), StringComparer.OrdinalIgnoreCase)) { - var currentScore = GetVideoProfileScore(videoStream.Profile); - var requestedScore = GetVideoProfileScore(requestedProfile); + var currentScore = GetVideoProfileScore(videoStream.Codec, videoStream.Profile); + var requestedScore = GetVideoProfileScore(videoStream.Codec, requestedProfile); if (currentScore == -1 || currentScore > requestedScore) { @@ -1703,7 +1915,8 @@ namespace MediaBrowser.Controller.MediaEncoding { if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) - || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase)) + || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase)) { return .6; } @@ -1940,19 +2153,35 @@ namespace MediaBrowser.Controller.MediaEncoding /// /// Gets the fast seek command line parameter. /// - /// The request. + /// The state. + /// The options. /// System.String. /// The fast seek command line parameter. - public string GetFastSeekCommandLineParameter(BaseEncodingJobOptions request) + public string GetFastSeekCommandLineParameter(EncodingJobInfo state, EncodingOptions options) { - var time = request.StartTimeTicks ?? 0; + var time = state.BaseRequest.StartTimeTicks ?? 0; + var seekParam = string.Empty; if (time > 0) { - return string.Format(CultureInfo.InvariantCulture, "-ss {0}", _mediaEncoder.GetTimeParameter(time)); + seekParam += string.Format(CultureInfo.InvariantCulture, "-ss {0}", _mediaEncoder.GetTimeParameter(time)); + + if (state.IsVideoRequest) + { + var outputVideoCodec = GetVideoEncoder(state, options); + + // Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking + if (!string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase) + && state.TranscodingType != TranscodingJobType.Progressive + && !state.EnableBreakOnNonKeyFrames(outputVideoCodec) + && (state.BaseRequest.StartTimeTicks ?? 0) > 0) + { + seekParam += " -noaccurate_seek"; + } + } } - return string.Empty; + return seekParam; } /// @@ -2061,180 +2290,84 @@ namespace MediaBrowser.Controller.MediaEncoding return returnFirstIfNoIndex ? streams.FirstOrDefault() : null; } - /// - /// Gets the graphical subtitle parameter. - /// - /// Encoding state. - /// Encoding options. - /// Video codec to use. - /// Graphical subtitle parameter. - public string GetGraphicalSubtitleParam( - EncodingJobInfo state, - EncodingOptions options, - string outputVideoCodec) + public static (int? width, int? height) GetFixedOutputSize( + int? videoWidth, + int? videoHeight, + int? requestedWidth, + int? requestedHeight, + int? requestedMaxWidth, + int? requestedMaxHeight) { - outputVideoCodec ??= string.Empty; - - var outputSizeParam = ReadOnlySpan.Empty; - var request = state.BaseRequest; - - outputSizeParam = GetOutputSizeParamInternal(state, options, outputVideoCodec); - - var videoSizeParam = string.Empty; - var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options) ?? string.Empty; - var isLinux = OperatingSystem.IsLinux(); - - var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; - var isVaapiH264Encoder = outputVideoCodec.IndexOf("h264_vaapi", StringComparison.OrdinalIgnoreCase) != -1; - var isVaapiHevcEncoder = outputVideoCodec.IndexOf("hevc_vaapi", StringComparison.OrdinalIgnoreCase) != -1; - var isQsvH264Encoder = outputVideoCodec.Contains("h264_qsv", StringComparison.OrdinalIgnoreCase); - var isQsvHevcEncoder = outputVideoCodec.Contains("hevc_qsv", StringComparison.OrdinalIgnoreCase); - var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase); - var isNvencEncoder = outputVideoCodec.Contains("nvenc", StringComparison.OrdinalIgnoreCase); - var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder); - var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder); - var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options); - var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); - - var mediaEncoderVersion = _mediaEncoder.GetMediaEncoderVersion(); - var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= _minVersionForCudaOverlay; - var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat); - - // Tonemapping and burn-in graphical subtitles requires overlay_vaapi. - // But it's still in ffmpeg mailing list. Disable it for now. - if (isTonemappingSupportedOnVaapi && isOpenclTonemappingSupported && !isVppTonemappingSupported) + if (!videoWidth.HasValue && !requestedWidth.HasValue) { - return GetOutputSizeParam(state, options, outputVideoCodec); + return (null, null); } - // Setup subtitle scaling - if (state.VideoStream != null && state.VideoStream.Width.HasValue && state.VideoStream.Height.HasValue) + if (!videoHeight.HasValue && !requestedHeight.HasValue) { - // Adjust the size of graphical subtitles to fit the video stream. - var videoStream = state.VideoStream; - var inputWidth = videoStream.Width; - var inputHeight = videoStream.Height; - var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight); - - if (width.HasValue && height.HasValue) - { - videoSizeParam = string.Format( - CultureInfo.InvariantCulture, - "scale={0}x{1}", - width.Value, - height.Value); - } + return (null, null); + } - if (!string.IsNullOrEmpty(videoSizeParam) - && !(isTonemappingSupportedOnQsv && isVppTonemappingSupported)) - { - // upload graphical subtitle to QSV - if (isLinux && (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) - || string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase))) - { - videoSizeParam += ",hwupload=extra_hw_frames=64"; - } - } + decimal inputWidth = Convert.ToDecimal(videoWidth ?? requestedWidth, CultureInfo.InvariantCulture); + decimal inputHeight = Convert.ToDecimal(videoHeight ?? requestedHeight, CultureInfo.InvariantCulture); + decimal outputWidth = requestedWidth.HasValue ? Convert.ToDecimal(requestedWidth.Value) : inputWidth; + decimal outputHeight = requestedHeight.HasValue ? Convert.ToDecimal(requestedHeight.Value) : inputHeight; + decimal maximumWidth = requestedMaxWidth.HasValue ? Convert.ToDecimal(requestedMaxWidth.Value) : outputWidth; + decimal maximumHeight = requestedMaxHeight.HasValue ? Convert.ToDecimal(requestedMaxHeight.Value) : outputHeight; - if (!string.IsNullOrEmpty(videoSizeParam)) - { - // upload graphical subtitle to cuda - if (isNvdecDecoder && isNvencEncoder && isCudaOverlaySupported && isCudaFormatConversionSupported) - { - videoSizeParam += ",hwupload_cuda"; - } - } + if (outputWidth > maximumWidth || outputHeight > maximumHeight) + { + var scale = Math.Min(maximumWidth / outputWidth, maximumHeight / outputHeight); + outputWidth = Math.Min(maximumWidth, Math.Truncate(outputWidth * scale)); + outputHeight = Math.Min(maximumHeight, Math.Truncate(outputHeight * scale)); } - var mapPrefix = state.SubtitleStream.IsExternal ? - 1 : - 0; + outputWidth = 2 * Math.Truncate(outputWidth / 2); + outputHeight = 2 * Math.Truncate(outputHeight / 2); - var subtitleStreamIndex = state.SubtitleStream.IsExternal - ? 0 - : state.SubtitleStream.Index; + return (Convert.ToInt32(outputWidth), Convert.ToInt32(outputHeight)); + } - // Setup default filtergraph utilizing FFMpeg overlay() and FFMpeg scale() (see the return of this function for index reference) - // Always put the scaler before the overlay for better performance - var retStr = outputSizeParam.IsEmpty - ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\"" - : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\""; + public static string GetHwScaleFilter( + string hwScaleSuffix, + string videoFormat, + int? videoWidth, + int? videoHeight, + int? requestedWidth, + int? requestedHeight, + int? requestedMaxWidth, + int? requestedMaxHeight) + { + var (outWidth, outHeight) = GetFixedOutputSize(videoWidth, videoHeight, + requestedWidth, requestedHeight, + requestedMaxWidth, requestedMaxHeight); + var isFormatFixed = !string.IsNullOrEmpty(videoFormat); + var isSizeFixed = !videoWidth.HasValue + || outWidth.Value != videoWidth.Value + || !videoHeight.HasValue + || outHeight.Value != videoHeight.Value; - // When the input may or may not be hardware VAAPI decodable - if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) - || string.Equals(outputVideoCodec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) + var arg1 = isSizeFixed ? ("=w=" + outWidth.Value + ":h=" + outHeight.Value) : string.Empty; + var arg2 = isFormatFixed ? ("format=" + videoFormat) : string.Empty; + if (isFormatFixed) { - /* - [base]: HW scaling video to OutputSize - [sub]: SW scaling subtitle to FixedOutputSize - [base][sub]: SW overlay - */ - retStr = outputSizeParam.IsEmpty - ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]hwdownload[base];[base][sub]overlay,format=nv12,hwupload\"" - : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload[base];[base][sub]overlay,format=nv12,hwupload\""; + arg2 = (isSizeFixed ? ':' : '=') + arg2; } - // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first - else if (_mediaEncoder.SupportsHwaccel("vaapi") && videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1 - && (string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase) - || string.Equals(outputVideoCodec, "libx265", StringComparison.OrdinalIgnoreCase))) - { - /* - [base]: SW scaling video to OutputSize - [sub]: SW scaling subtitle to FixedOutputSize - [base][sub]: SW overlay - */ - retStr = outputSizeParam.IsEmpty - ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\"" - : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\""; - } - else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) - || string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)) - { - /* - QSV in FFMpeg can now setup hardware overlay for transcodes. - For software decoding and hardware encoding option, frames must be hwuploaded into hardware - with fixed frame size. - Currently only supports linux. - */ - if (isTonemappingSupportedOnQsv && isVppTonemappingSupported) - { - retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload,format=nv12[base];[base][sub]overlay\""; - } - else if (isLinux) - { - retStr = outputSizeParam.IsEmpty - ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\"" - : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_qsv\""; - } - } - else if (isNvdecDecoder && isNvencEncoder) + if (!string.IsNullOrEmpty(hwScaleSuffix) && (isSizeFixed || isFormatFixed)) { - if (isCudaOverlaySupported && isCudaFormatConversionSupported) - { - retStr = outputSizeParam.IsEmpty - ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]scale_cuda=format=yuv420p[base];[base][sub]overlay_cuda\"" - : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_cuda\""; - } - else - { - retStr = outputSizeParam.IsEmpty - ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay,format=nv12|yuv420p,hwupload_cuda\"" - : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay,format=nv12|yuv420p,hwupload_cuda\""; - } + return string.Format( + CultureInfo.InvariantCulture, + "scale_{0}{1}{2}", + hwScaleSuffix, + arg1, + arg2); } - return string.Format( - CultureInfo.InvariantCulture, - retStr, - mapPrefix, - subtitleStreamIndex, - state.VideoStream.Index, - outputSizeParam.ToString(), - videoSizeParam); + return string.Empty; } - public static (int? width, int? height) GetFixedOutputSize( + public static string GetCustomSwScaleFilter( int? videoWidth, int? videoHeight, int? requestedWidth, @@ -2242,332 +2375,165 @@ namespace MediaBrowser.Controller.MediaEncoding int? requestedMaxWidth, int? requestedMaxHeight) { - if (!videoWidth.HasValue && !requestedWidth.HasValue) - { - return (null, null); - } - - if (!videoHeight.HasValue && !requestedHeight.HasValue) + var (outWidth, outHeight) = GetFixedOutputSize(videoWidth, videoHeight, + requestedWidth, requestedHeight, + requestedMaxWidth, requestedMaxHeight); + if (outWidth.HasValue && outHeight.HasValue) { - return (null, null); + return string.Format( + CultureInfo.InvariantCulture, + "scale=s={0}x{1}:flags=fast_bilinear", + outWidth.Value, + outHeight.Value); } - decimal inputWidth = Convert.ToDecimal(videoWidth ?? requestedWidth, CultureInfo.InvariantCulture); - decimal inputHeight = Convert.ToDecimal(videoHeight ?? requestedHeight, CultureInfo.InvariantCulture); - decimal outputWidth = requestedWidth.HasValue ? Convert.ToDecimal(requestedWidth.Value) : inputWidth; - decimal outputHeight = requestedHeight.HasValue ? Convert.ToDecimal(requestedHeight.Value) : inputHeight; - decimal maximumWidth = requestedMaxWidth.HasValue ? Convert.ToDecimal(requestedMaxWidth.Value) : outputWidth; - decimal maximumHeight = requestedMaxHeight.HasValue ? Convert.ToDecimal(requestedMaxHeight.Value) : outputHeight; + return string.Empty; + } - if (outputWidth > maximumWidth || outputHeight > maximumHeight) + public static string GetAlphaSrcFilter( + EncodingJobInfo state, + int? videoWidth, + int? videoHeight, + int? requestedWidth, + int? requestedHeight, + int? requestedMaxWidth, + int? requestedMaxHeight, + int? framerate) + { + var reqTicks = state.BaseRequest.StartTimeTicks ?? 0; + var startTime = TimeSpan.FromTicks(reqTicks).ToString(@"hh\\\:mm\\\:ss\\\.fff", CultureInfo.InvariantCulture); + var (outWidth, outHeight) = GetFixedOutputSize(videoWidth, videoHeight, + requestedWidth, requestedHeight, + requestedMaxWidth, requestedMaxHeight); + if (outWidth.HasValue && outHeight.HasValue) { - var scale = Math.Min(maximumWidth / outputWidth, maximumHeight / outputHeight); - outputWidth = Math.Min(maximumWidth, Math.Truncate(outputWidth * scale)); - outputHeight = Math.Min(maximumHeight, Math.Truncate(outputHeight * scale)); + return string.Format( + CultureInfo.InvariantCulture, + "alphasrc=s={0}x{1}:r={2}:start='{3}'", + outWidth.Value, + outHeight.Value, + framerate ?? 10, + reqTicks > 0 ? startTime : 0); } - outputWidth = 2 * Math.Truncate(outputWidth / 2); - outputHeight = 2 * Math.Truncate(outputHeight / 2); - - return (Convert.ToInt32(outputWidth), Convert.ToInt32(outputHeight)); + return string.Empty; } - public List GetScalingFilters( + public static List GetSwScaleFilter( EncodingJobInfo state, EncodingOptions options, + string videoEncoder, int? videoWidth, int? videoHeight, Video3DFormat? threedFormat, - string videoDecoder, - string videoEncoder, int? requestedWidth, int? requestedHeight, int? requestedMaxWidth, int? requestedMaxHeight) { var filters = new List(); - var (width, height) = GetFixedOutputSize( - videoWidth, - videoHeight, - requestedWidth, - requestedHeight, - requestedMaxWidth, - requestedMaxHeight); - - if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase)) - && width.HasValue - && height.HasValue) - { - // Given the input dimensions (inputWidth, inputHeight), determine the output dimensions - // (outputWidth, outputHeight). The user may request precise output dimensions or maximum - // output dimensions. Output dimensions are guaranteed to be even. - var outputWidth = width.Value; - var outputHeight = height.Value; - var qsv_or_vaapi = string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase); - var isDeintEnabled = state.DeInterlace("h264", true) - || state.DeInterlace("avc", true) - || state.DeInterlace("h265", true) - || state.DeInterlace("hevc", true); - - var isVaapiDecoder = videoDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase); - var isVaapiH264Encoder = videoEncoder.Contains("h264_vaapi", StringComparison.OrdinalIgnoreCase); - var isVaapiHevcEncoder = videoEncoder.Contains("hevc_vaapi", StringComparison.OrdinalIgnoreCase); - var isQsvH264Encoder = videoEncoder.Contains("h264_qsv", StringComparison.OrdinalIgnoreCase); - var isQsvHevcEncoder = videoEncoder.Contains("hevc_qsv", StringComparison.OrdinalIgnoreCase); - var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options); - var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); - var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder); - var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder); - var isP010PixFmtRequired = (isTonemappingSupportedOnVaapi && (isOpenclTonemappingSupported || isVppTonemappingSupported)) - || (isTonemappingSupportedOnQsv && isVppTonemappingSupported); - - var outputPixFmt = "format=nv12"; - if (isP010PixFmtRequired) - { - outputPixFmt = "format=p010"; - } - - if (isTonemappingSupportedOnQsv && isVppTonemappingSupported) - { - qsv_or_vaapi = false; - } - - if (!videoWidth.HasValue - || outputWidth != videoWidth.Value - || !videoHeight.HasValue - || outputHeight != videoHeight.Value) - { - // Force nv12 pixel format to enable 10-bit to 8-bit colour conversion. - // use vpp_qsv filter to avoid green bar when the fixed output size is requested. - filters.Add( - string.Format( - CultureInfo.InvariantCulture, - "{0}=w={1}:h={2}{3}{4}", - qsv_or_vaapi ? "vpp_qsv" : "scale_vaapi", - outputWidth, - outputHeight, - ":" + outputPixFmt, - (qsv_or_vaapi && isDeintEnabled) ? ":deinterlace=1" : string.Empty)); - } - - // Assert 10-bit is P010 so as we can avoid the extra scaler to get a bit more fps on high res HDR videos. - else if (!isP010PixFmtRequired) - { - filters.Add( - string.Format( - CultureInfo.InvariantCulture, - "{0}={1}{2}", - qsv_or_vaapi ? "vpp_qsv" : "scale_vaapi", - outputPixFmt, - (qsv_or_vaapi && isDeintEnabled) ? ":deinterlace=1" : string.Empty)); - } - } - else if ((videoDecoder ?? string.Empty).Contains("cuda", StringComparison.OrdinalIgnoreCase) - && width.HasValue - && height.HasValue) - { - var outputWidth = width.Value; - var outputHeight = height.Value; - - var isNvencEncoder = videoEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase); - var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options); - var isCudaTonemappingSupported = IsCudaTonemappingSupported(state, options); - var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase); - var mediaEncoderVersion = _mediaEncoder.GetMediaEncoderVersion(); - var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= _minVersionForCudaOverlay; - var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat); - var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + var isV4l2 = string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase); + var scaleVal = isV4l2 ? 64 : 2; - var outputPixFmt = string.Empty; - if (isCudaFormatConversionSupported) + // If fixed dimensions were supplied + if (requestedWidth.HasValue && requestedHeight.HasValue) + { + if (isV4l2) { - outputPixFmt = (hasGraphicalSubs && isCudaOverlaySupported && isNvencEncoder) - ? "format=yuv420p" - : "format=nv12"; - if ((isOpenclTonemappingSupported || isCudaTonemappingSupported) - && isTonemappingSupportedOnNvenc) - { - outputPixFmt = "format=p010"; - } - } + var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture); + var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture); - if (!videoWidth.HasValue - || outputWidth != videoWidth.Value - || !videoHeight.HasValue - || outputHeight != videoHeight.Value) - { filters.Add( string.Format( CultureInfo.InvariantCulture, - "scale_cuda=w={0}:h={1}{2}", - outputWidth, - outputHeight, - isCudaFormatConversionSupported ? (":" + outputPixFmt) : string.Empty)); + "scale=trunc({0}/64)*64:trunc({1}/2)*2", + widthParam, + heightParam)); } - else if (isCudaFormatConversionSupported) + else { - filters.Add( - string.Format( - CultureInfo.InvariantCulture, - "scale_cuda={0}", - outputPixFmt)); + filters.Add(GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, requestedHeight.Value)); } } - else if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1 - && width.HasValue - && height.HasValue) - { - // Nothing to do, it's handled as an input resize filter - } - else + + // If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size + else if (requestedMaxWidth.HasValue && requestedMaxHeight.HasValue) { - var isExynosV4L2 = string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase); + var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture); + var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture); - // If fixed dimensions were supplied - if (requestedWidth.HasValue && requestedHeight.HasValue) - { - if (isExynosV4L2) - { - var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture); - var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture); - - filters.Add( - string.Format( - CultureInfo.InvariantCulture, - "scale=trunc({0}/64)*64:trunc({1}/2)*2", - widthParam, - heightParam)); - } - else - { - filters.Add(GetFixedSizeScalingFilter(threedFormat, requestedWidth.Value, requestedHeight.Value)); - } - } + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/{2})*{2}:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", + maxWidthParam, + maxHeightParam, + scaleVal)); + } - // If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size - else if (requestedMaxWidth.HasValue && requestedMaxHeight.HasValue) + // If a fixed width was requested + else if (requestedWidth.HasValue) + { + if (threedFormat.HasValue) { - var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture); - var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture); - - if (isExynosV4L2) - { - filters.Add( - string.Format( - CultureInfo.InvariantCulture, - "scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/64)*64:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", - maxWidthParam, - maxHeightParam)); - } - else - { - filters.Add( - string.Format( - CultureInfo.InvariantCulture, - "scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/2)*2:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", - maxWidthParam, - maxHeightParam)); - } + // This method can handle 0 being passed in for the requested height + filters.Add(GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, 0)); } - - // If a fixed width was requested - else if (requestedWidth.HasValue) + else { - if (threedFormat.HasValue) - { - // This method can handle 0 being passed in for the requested height - filters.Add(GetFixedSizeScalingFilter(threedFormat, requestedWidth.Value, 0)); - } - else - { - var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture); + var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture); - filters.Add( - string.Format( - CultureInfo.InvariantCulture, - "scale={0}:trunc(ow/a/2)*2", - widthParam)); - } + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "scale={0}:trunc(ow/a/2)*2", + widthParam)); } + } - // If a fixed height was requested - else if (requestedHeight.HasValue) - { - var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture); + // If a fixed height was requested + else if (requestedHeight.HasValue) + { + var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture); - if (isExynosV4L2) - { - filters.Add( - string.Format( - CultureInfo.InvariantCulture, - "scale=trunc(oh*a/64)*64:{0}", - heightParam)); - } - else - { - filters.Add( - string.Format( - CultureInfo.InvariantCulture, - "scale=trunc(oh*a/2)*2:{0}", - heightParam)); - } - } + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "scale=trunc(oh*a/{1})*{1}:{0}", + heightParam, + scaleVal)); + } - // If a max width was requested - else if (requestedMaxWidth.HasValue) - { - var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture); + // If a max width was requested + else if (requestedMaxWidth.HasValue) + { + var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture); - if (isExynosV4L2) - { - filters.Add( - string.Format( - CultureInfo.InvariantCulture, - "scale=trunc(min(max(iw\\,ih*dar)\\,{0})/64)*64:trunc(ow/dar/2)*2", - maxWidthParam)); - } - else - { - filters.Add( - string.Format( - CultureInfo.InvariantCulture, - "scale=trunc(min(max(iw\\,ih*dar)\\,{0})/2)*2:trunc(ow/dar/2)*2", - maxWidthParam)); - } - } + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "scale=trunc(min(max(iw\\,ih*dar)\\,{0})/{1})*{1}:trunc(ow/dar/2)*2", + maxWidthParam, + scaleVal)); + } - // If a max height was requested - else if (requestedMaxHeight.HasValue) - { - var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture); + // If a max height was requested + else if (requestedMaxHeight.HasValue) + { + var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture); - if (isExynosV4L2) - { - filters.Add( - string.Format( - CultureInfo.InvariantCulture, - "scale=trunc(oh*a/64)*64:min(max(iw/dar\\,ih)\\,{0})", - maxHeightParam)); - } - else - { - filters.Add( - string.Format( - CultureInfo.InvariantCulture, - "scale=trunc(oh*a/2)*2:min(max(iw/dar\\,ih)\\,{0})", - maxHeightParam)); - } - } + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "scale=trunc(oh*a/{1})*{1}:min(max(iw/dar\\,ih)\\,{0})", + maxHeightParam, + scaleVal)); } return filters; } - private string GetFixedSizeScalingFilter(Video3DFormat? threedFormat, int requestedWidth, int requestedHeight) + private static string GetFixedSwScaleFilter(Video3DFormat? threedFormat, int requestedWidth, int requestedHeight) { var widthParam = requestedWidth.ToString(CultureInfo.InvariantCulture); var heightParam = requestedHeight.ToString(CultureInfo.InvariantCulture); @@ -2615,572 +2581,2144 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Format(CultureInfo.InvariantCulture, filter, widthParam, heightParam); } - /// - /// Gets the output size parameter. - /// - /// Encoding state. - /// Encoding options. - /// Video codec to use. - /// The output size parameter. - public string GetOutputSizeParam( - EncodingJobInfo state, - EncodingOptions options, - string outputVideoCodec) + public static string GetSwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options) + { + var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.AverageFrameRate ?? 60) <= 30; + if (string.Equals(options.DeinterlaceMethod, "bwdif", StringComparison.OrdinalIgnoreCase)) + { + return string.Format( + CultureInfo.InvariantCulture, + "bwdif={0}:-1:0", + doubleRateDeint ? "1" : "0"); + } + else + { + return string.Format( + CultureInfo.InvariantCulture, + "yadif={0}:-1:0", + doubleRateDeint ? "1" : "0"); + } + } + + public static string GetHwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options, string hwDeintSuffix) + { + var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.AverageFrameRate ?? 60) <= 30; + if (hwDeintSuffix.Contains("cuda", StringComparison.OrdinalIgnoreCase)) + { + return string.Format( + CultureInfo.InvariantCulture, + "yadif_cuda={0}:-1:0", + doubleRateDeint ? "1" : "0"); + } + else if (hwDeintSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase)) + { + return string.Format( + CultureInfo.InvariantCulture, + "deinterlace_vaapi=rate={0}", + doubleRateDeint ? "field" : "frame"); + } + else if (hwDeintSuffix.Contains("qsv", StringComparison.OrdinalIgnoreCase)) + { + return "deinterlace_qsv=mode=2"; + } + + return string.Empty; + } + + public static string GetHwTonemapFilter(EncodingOptions options, string hwTonemapSuffix, string videoFormat) { - string filters = GetOutputSizeParamInternal(state, options, outputVideoCodec); - return string.IsNullOrEmpty(filters) ? string.Empty : " -vf \"" + filters + "\""; + if (string.IsNullOrEmpty(hwTonemapSuffix)) + { + return string.Empty; + } + + var args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709"; + + if (!hwTonemapSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase)) + { + args += ":tonemap={2}:peak={3}:desat={4}"; + + if (options.TonemappingParam != 0) + { + args += ":param={5}"; + } + + if (!string.Equals(options.TonemappingRange, "auto", StringComparison.OrdinalIgnoreCase)) + { + args += ":range={6}"; + } + } + + return string.Format( + CultureInfo.InvariantCulture, + args, + hwTonemapSuffix, + videoFormat ?? "nv12", + options.TonemappingAlgorithm, + options.TonemappingPeak, + options.TonemappingDesat, + options.TonemappingParam, + options.TonemappingRange); } /// - /// Gets the output size parameter. - /// If we're going to put a fixed size on the command line, this will calculate it. + /// Gets the parameter of software filter chain. /// /// Encoding state. /// Encoding options. - /// Video codec to use. - /// The output size parameter. - public string GetOutputSizeParamInternal( + /// Video encoder to use. + /// The tuple contains three lists: main, sub and overlay filters + public Tuple, List, List> GetSwVidFilterChain( EncodingJobInfo state, EncodingOptions options, - string outputVideoCodec) + string vidEncoder) { - // http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/ - - var request = state.BaseRequest; - var videoStream = state.VideoStream; - var filters = new List(); - - var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options) ?? string.Empty; - var inputWidth = videoStream?.Width; - var inputHeight = videoStream?.Height; + var inW = state.VideoStream?.Width; + var inH = state.VideoStream?.Height; + var reqW = state.BaseRequest.Width; + var reqH = state.BaseRequest.Height; + var reqMaxW = state.BaseRequest.MaxWidth; + var reqMaxH = state.BaseRequest.MaxHeight; var threeDFormat = state.MediaSource.Video3DFormat; - var isSwDecoder = string.IsNullOrEmpty(videoDecoder); - var isD3d11vaDecoder = videoDecoder.IndexOf("d3d11va", StringComparison.OrdinalIgnoreCase) != -1; - var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; - var isVaapiEncoder = outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; - var isVaapiH264Encoder = outputVideoCodec.IndexOf("h264_vaapi", StringComparison.OrdinalIgnoreCase) != -1; - var isVaapiHevcEncoder = outputVideoCodec.IndexOf("hevc_vaapi", StringComparison.OrdinalIgnoreCase) != -1; - var isQsvH264Encoder = outputVideoCodec.IndexOf("h264_qsv", StringComparison.OrdinalIgnoreCase) != -1; - var isQsvHevcEncoder = outputVideoCodec.IndexOf("hevc_qsv", StringComparison.OrdinalIgnoreCase) != -1; - var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase); - var isNvencEncoder = outputVideoCodec.Contains("nvenc", StringComparison.OrdinalIgnoreCase); - var isCuvidH264Decoder = videoDecoder.Contains("h264_cuvid", StringComparison.OrdinalIgnoreCase); - var isCuvidHevcDecoder = videoDecoder.Contains("hevc_cuvid", StringComparison.OrdinalIgnoreCase); - var isCuvidVp9Decoder = videoDecoder.Contains("vp9_cuvid", StringComparison.OrdinalIgnoreCase); - var isLibX264Encoder = outputVideoCodec.IndexOf("libx264", StringComparison.OrdinalIgnoreCase) != -1; - var isLibX265Encoder = outputVideoCodec.IndexOf("libx265", StringComparison.OrdinalIgnoreCase) != -1; - var isLinux = OperatingSystem.IsLinux(); - var isColorDepth10 = IsColorDepth10(state); - - var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && (isNvdecDecoder || isCuvidHevcDecoder || isCuvidVp9Decoder || isSwDecoder); - var isTonemappingSupportedOnAmf = string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && (isD3d11vaDecoder || isSwDecoder); - var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder); - var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder); - var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options); - var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); - var isCudaTonemappingSupported = IsCudaTonemappingSupported(state, options); - var mediaEncoderVersion = _mediaEncoder.GetMediaEncoderVersion(); - var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= _minVersionForCudaOverlay; + var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty; + var isSwDecoder = string.IsNullOrEmpty(vidDecoder); + var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase); + + var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); + var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); + var doDeintH2645 = doDeintH264 || doDeintHevc; var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; - var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; - var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; - - // If double rate deinterlacing is enabled and the input framerate is 30fps or below, otherwise the output framerate will be too high for many devices - var doubleRateDeinterlace = options.DeinterlaceDoubleRate && (videoStream?.AverageFrameRate ?? 60) <= 30; - - var isScalingInAdvance = false; - var isCudaDeintInAdvance = false; - var isHwuploadCudaRequired = false; - var isNoTonemapFilterApplied = true; - var isDeinterlaceH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); - var isDeinterlaceHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); - - // Add OpenCL tonemapping filter for NVENC/AMF/VAAPI. - if ((isTonemappingSupportedOnNvenc && !isCudaTonemappingSupported) || isTonemappingSupportedOnAmf || (isTonemappingSupportedOnVaapi && !isVppTonemappingSupported)) - { - // NVIDIA Pascal and Turing or higher are recommended. - // AMD Polaris and Vega or higher are recommended. - // Intel Kaby Lake or newer is required. - if (isOpenclTonemappingSupported) - { - isNoTonemapFilterApplied = false; - var inputHdrParams = GetInputHdrParams(videoStream.ColorTransfer); - if (!string.IsNullOrEmpty(inputHdrParams)) - { - filters.Add(inputHdrParams); - } + var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; + var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream; - var parameters = "tonemap_opencl=format=nv12:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:desat={1}:threshold={2}:peak={3}"; + /* Make main filters for video stream */ + var mainFilters = new List(); - if (options.TonemappingParam != 0) - { - parameters += ":param={4}"; - } + mainFilters.Add(GetOverwriteColorPropertiesParam(state, false)); - if (!string.Equals(options.TonemappingRange, "auto", StringComparison.OrdinalIgnoreCase)) - { - parameters += ":range={5}"; - } + // INPUT sw surface(memory/copy-back from vram) + var outFormat = isSwDecoder ? "yuv420p" : "nv12"; + var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); + if (isVaapiEncoder) + { + outFormat = "nv12"; + } + // sw scale + mainFilters.AddRange(swScaleFilter); + mainFilters.Add("format=" + outFormat); - if (isSwDecoder || isD3d11vaDecoder) - { - isScalingInAdvance = true; - // Add zscale filter before tone mapping filter for performance. - var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight); - if (width.HasValue && height.HasValue) - { - filters.Add( - string.Format( - CultureInfo.InvariantCulture, - "zscale=s={0}x{1}", - width.Value, - height.Value)); - } + if (doDeintH2645) + { + var deintFilter = GetSwDeinterlaceFilter(state, options); + // sw deint + mainFilters.Add(deintFilter); + } - // Convert to hardware pixel format p010 when using SW decoder. - filters.Add("format=p010"); - } + // sw tonemap <= TODO: finsh the fast tonemap filter - if ((isDeinterlaceH264 || isDeinterlaceHevc) && isNvdecDecoder) - { - isCudaDeintInAdvance = true; - filters.Add( - string.Format( - CultureInfo.InvariantCulture, - "yadif_cuda={0}:-1:0", - doubleRateDeinterlace ? "1" : "0")); - } + // OUTPUT yuv420p/nv12 surface(memory) - if (isVaapiDecoder || isNvdecDecoder) - { - isScalingInAdvance = true; - filters.AddRange( - GetScalingFilters( - state, - options, - inputWidth, - inputHeight, - threeDFormat, - videoDecoder, - outputVideoCodec, - request.Width, - request.Height, - request.MaxWidth, - request.MaxHeight)); - } + /* Make sub and overlay filters for subtitle stream */ + var subFilters = new List(); + var overlayFilters = new List(); + if (hasTextSubs) + { + // subtitles=f='*.ass':alpha=0 + var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false); + mainFilters.Add(textSubtitlesFilter); + } + else if (hasGraphicalSubs) + { + // [0:s]scale=s=1280x720 + var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subSwScaleFilter); + overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0"); + } - // hwmap the HDR data to opencl device by cl-va p010 interop. - if (isVaapiDecoder) - { - filters.Add("hwmap"); - } + return new Tuple, List, List>(mainFilters, subFilters, overlayFilters); + } - // convert cuda device data to p010 host data. - if (isNvdecDecoder) - { - filters.Add("hwdownload,format=p010"); - } + /// + /// Gets the parameter of Nvidia NVENC filter chain. + /// + /// Encoding state. + /// Encoding options. + /// Video encoder to use. + /// The tuple contains three lists: main, sub and overlay filters + public Tuple, List, List> GetNvidiaVidFilterChain( + EncodingJobInfo state, + EncodingOptions options, + string vidEncoder) + { + if (!string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) + { + return new Tuple, List, List>(null, null, null); + } - if (isNvdecDecoder - || isCuvidHevcDecoder - || isCuvidVp9Decoder - || isSwDecoder - || isD3d11vaDecoder) - { - // Upload the HDR10 or HLG data to the OpenCL device, - // use tonemap_opencl filter for tone mapping, - // and then download the SDR data to memory. - filters.Add("hwupload"); - } + var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty; + var isSwDecoder = string.IsNullOrEmpty(vidDecoder); + var isSwEncoder = !vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase); - // Fallback to hable if bt2390 is chosen but not supported in tonemap_opencl. - var isBt2390SupportedInOpenclTonemap = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapOpenclBt2390); - if (string.Equals(options.TonemappingAlgorithm, "bt2390", StringComparison.OrdinalIgnoreCase) - && !isBt2390SupportedInOpenclTonemap) - { - options.TonemappingAlgorithm = "hable"; - } + // legacy cuvid(resize/deint/sw) pipeline(copy-back) + if ((isSwDecoder && isSwEncoder) + || !IsCudaFullSupported() + || !options.EnableEnhancedNvdecDecoder + || !_mediaEncoder.SupportsFilter("alphasrc")) + { + return GetSwVidFilterChain(state, options, vidEncoder); + } - filters.Add( - string.Format( - CultureInfo.InvariantCulture, - parameters, - options.TonemappingAlgorithm, - options.TonemappingDesat, - options.TonemappingThreshold, - options.TonemappingPeak, - options.TonemappingParam, - options.TonemappingRange)); - - if (isNvdecDecoder - || isCuvidHevcDecoder - || isCuvidVp9Decoder - || isSwDecoder - || isD3d11vaDecoder) - { - filters.Add("hwdownload"); - filters.Add("format=nv12"); - } + // prefered nvdec + cuda filters + nvenc pipeline + return GetNvidiaVidFiltersPrefered(state, options, vidDecoder, vidEncoder); + } - if (isNvdecDecoder && isNvencEncoder) - { - isHwuploadCudaRequired = true; - } + public Tuple, List, List> GetNvidiaVidFiltersPrefered( + EncodingJobInfo state, + EncodingOptions options, + string vidDecoder, + string vidEncoder) + { + var inW = state.VideoStream?.Width; + var inH = state.VideoStream?.Height; + var reqW = state.BaseRequest.Width; + var reqH = state.BaseRequest.Height; + var reqMaxW = state.BaseRequest.MaxWidth; + var reqMaxH = state.BaseRequest.MaxHeight; + var threeDFormat = state.MediaSource.Video3DFormat; - if (isVaapiDecoder) - { - // Reverse the data route from opencl to vaapi. - filters.Add("hwmap=derive_device=vaapi:reverse=1"); - } + var isNvdecDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase); + var isNvencEncoder = vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase); + var isSwDecoder = string.IsNullOrEmpty(vidDecoder); + var isSwEncoder = !isNvencEncoder; + var isCuInCuOut = isNvdecDecoder && isNvencEncoder; - var outputSdrParams = GetOutputSdrParams(options.TonemappingRange); - if (!string.IsNullOrEmpty(outputSdrParams)) - { - filters.Add(outputSdrParams); - } + var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.AverageFrameRate ?? 60) <= 30; + var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); + var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); + var doDeintH2645 = doDeintH264 || doDeintHevc; + var doCuTonemap = IsHwTonemapAvailable(state, options); + + var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; + var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream; + var hasAssSubs = hasSubs + && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) + || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase)); + + /* Make main filters for video stream */ + var mainFilters = new List(); + + mainFilters.Add(GetOverwriteColorPropertiesParam(state, doCuTonemap)); + + if (isSwDecoder) + { + // INPUT sw surface(memory) + var outFormat = doCuTonemap ? "yuv420p10" : "yuv420p"; + var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); + // sw scale + mainFilters.AddRange(swScaleFilter); + mainFilters.Add("format=" + outFormat); + // sw deint + if (doDeintH2645) + { + var swDeintFilter = GetSwDeinterlaceFilter(state, options); + mainFilters.Add(swDeintFilter); + } + // sw => hw + if (doCuTonemap) + { + mainFilters.Add("hwupload"); } } - - // When the input may or may not be hardware VAAPI decodable. - if ((isVaapiH264Encoder || isVaapiHevcEncoder) - && !(isTonemappingSupportedOnVaapi && (isOpenclTonemappingSupported || isVppTonemappingSupported))) + if (isNvdecDecoder) { - filters.Add("format=nv12|vaapi"); - filters.Add("hwupload"); + // INPUT cuda surface(vram) + var outFormat = doCuTonemap ? string.Empty : "yuv420p"; + var hwScaleFilter = GetHwScaleFilter("cuda", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH); + // hw scale + mainFilters.Add(hwScaleFilter); + // hw deint + if (doDeintH2645) + { + var deintFilter = GetHwDeinterlaceFilter(state, options, "cuda"); + mainFilters.Add(deintFilter); + } } - // When burning in graphical subtitles using overlay_qsv, upload videostream to the same qsv context. - else if (isLinux && hasGraphicalSubs && (isQsvH264Encoder || isQsvHevcEncoder) - && !(isTonemappingSupportedOnQsv && isVppTonemappingSupported)) + // hw tonemap + if (doCuTonemap) { - filters.Add("hwupload=extra_hw_frames=64"); + var tonemapFilter = GetHwTonemapFilter(options, "cuda", "yuv420p"); + mainFilters.Add(tonemapFilter); } - // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first. - else if ((IsVaapiSupported(state) && isVaapiDecoder) && (isLibX264Encoder || isLibX265Encoder) - && !(isTonemappingSupportedOnQsv && isVppTonemappingSupported)) + var memoryOutput = false; + var isUploadForOclTonemap = isSwDecoder && doCuTonemap; + if ((isNvdecDecoder && isSwEncoder) || isUploadForOclTonemap) { - var codec = videoStream.Codec; + memoryOutput = true; - // Assert 10-bit hardware VAAPI decodable - if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) - || string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) - || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))) - { - /* - Download data from GPU to CPU as p010le format. - Colorspace conversion is unnecessary here as libx264 will handle it. - If this step is missing, it will fail on AMD but not on intel. - */ - filters.Add("hwdownload"); - filters.Add("format=p010le"); - } + // OUTPUT yuv420p surface(memory) + mainFilters.Add("hwdownload"); + mainFilters.Add("format=yuv420p"); + } + + // OUTPUT yuv420p surface(memory) + if (isSwDecoder && isNvencEncoder) + { + memoryOutput = true; + } - // Assert 8-bit hardware VAAPI decodable - else if (!isColorDepth10) + if (memoryOutput) + { + // text subtitles + if (hasTextSubs) { - filters.Add("hwdownload"); - filters.Add("format=nv12"); + var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false); + mainFilters.Add(textSubtitlesFilter); } } - // Add hardware deinterlace filter before scaling filter. - if (isDeinterlaceH264 || isDeinterlaceHevc) + // OUTPUT cuda(yuv420p) surface(vram) + + /* Make sub and overlay filters for subtitle stream */ + var subFilters = new List(); + var overlayFilters = new List(); + if (isCuInCuOut) { - if (isVaapiEncoder - || (isTonemappingSupportedOnQsv && isVppTonemappingSupported)) + if (hasSubs) { - filters.Add( - string.Format( - CultureInfo.InvariantCulture, - "deinterlace_vaapi=rate={0}", - doubleRateDeinterlace ? "field" : "frame")); + if (hasGraphicalSubs) + { + // scale=s=1280x720,format=yuva420p,hwupload + var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subSwScaleFilter); + subFilters.Add("format=yuva420p"); + } + else if (hasTextSubs) + { + // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload + var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, hasAssSubs ? 10 : 5); + var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true); + subFilters.Add(alphaSrcFilter); + subFilters.Add("format=yuva420p"); + subFilters.Add(subTextSubtitlesFilter); + } + + subFilters.Add("hwupload"); + overlayFilters.Add("overlay_cuda=eof_action=endall:shortest=1:repeatlast=0"); } - else if (isNvdecDecoder && !isCudaDeintInAdvance) + } + else + { + if (hasGraphicalSubs) { - filters.Add( - string.Format( - CultureInfo.InvariantCulture, - "yadif_cuda={0}:-1:0", - doubleRateDeinterlace ? "1" : "0")); + var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subSwScaleFilter); + overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0"); } } - // Add software deinterlace filter before scaling filter. - if ((isDeinterlaceH264 || isDeinterlaceHevc) - && !isVaapiH264Encoder - && !isVaapiHevcEncoder - && !isQsvH264Encoder - && !isQsvHevcEncoder - && !isNvdecDecoder - && !isCuvidH264Decoder) + return new Tuple, List, List>(mainFilters, subFilters, overlayFilters); + } + + /// + /// Gets the parameter of AMD AMF filter chain. + /// + /// Encoding state. + /// Encoding options. + /// Video encoder to use. + /// The tuple contains three lists: main, sub and overlay filters + public Tuple, List, List> GetAmdVidFilterChain( + EncodingJobInfo state, + EncodingOptions options, + string vidEncoder) + { + if (!string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)) + { + return new Tuple, List, List>(null, null, null); + } + + var isWindows = OperatingSystem.IsWindows(); + var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty; + var isSwDecoder = string.IsNullOrEmpty(vidDecoder); + var isSwEncoder = !vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase); + var isAmfDx11OclSupported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va") && IsOpenclFullSupported(); + + // legacy d3d11va pipeline(copy-back) + if ((isSwDecoder && isSwEncoder) + || !isAmfDx11OclSupported + || !_mediaEncoder.SupportsFilter("alphasrc")) + { + return GetSwVidFilterChain(state, options, vidEncoder); + } + + // prefered d3d11va + opencl filters + amf pipeline + return GetAmdDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder); + } + + public Tuple, List, List> GetAmdDx11VidFiltersPrefered( + EncodingJobInfo state, + EncodingOptions options, + string vidDecoder, + string vidEncoder) + { + var inW = state.VideoStream?.Width; + var inH = state.VideoStream?.Height; + var reqW = state.BaseRequest.Width; + var reqH = state.BaseRequest.Height; + var reqMaxW = state.BaseRequest.MaxWidth; + var reqMaxH = state.BaseRequest.MaxHeight; + var threeDFormat = state.MediaSource.Video3DFormat; + + var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase); + var isAmfEncoder = vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase); + var isSwDecoder = string.IsNullOrEmpty(vidDecoder); + var isSwEncoder = !isAmfEncoder; + var isDxInDxOut = isD3d11vaDecoder && isAmfEncoder; + + var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); + var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); + var doDeintH2645 = doDeintH264 || doDeintHevc; + var doOclTonemap = IsHwTonemapAvailable(state, options); + + var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; + var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream; + var hasAssSubs = hasSubs + && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) + || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase)); + + /* Make main filters for video stream */ + var mainFilters = new List(); + + mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap)); + + if (isSwDecoder) { - if (string.Equals(options.DeinterlaceMethod, "bwdif", StringComparison.OrdinalIgnoreCase)) + // INPUT sw surface(memory) + var outFormat = doOclTonemap ? "yuv420p10" : "yuv420p"; + var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); + // sw scale + mainFilters.AddRange(swScaleFilter); + mainFilters.Add("format=" + outFormat); + // sw deint + if (doDeintH2645) { - filters.Add( - string.Format( - CultureInfo.InvariantCulture, - "bwdif={0}:-1:0", - doubleRateDeinterlace ? "1" : "0")); + var swDeintFilter = GetSwDeinterlaceFilter(state, options); + mainFilters.Add(swDeintFilter); } - else + + // keep video at memory except ocl tonemap, + // since the overhead caused by hwupload >>> using sw filter. + // sw => hw + if (doOclTonemap) { - filters.Add( - string.Format( - CultureInfo.InvariantCulture, - "yadif={0}:-1:0", - doubleRateDeinterlace ? "1" : "0")); + mainFilters.Add("hwupload"); } } - // Add scaling filter: scale_*=format=nv12 or scale_*=w=*:h=*:format=nv12 or scale=expr - if (!isScalingInAdvance) + if (isD3d11vaDecoder) { - filters.AddRange( - GetScalingFilters( - state, - options, - inputWidth, - inputHeight, - threeDFormat, - videoDecoder, - outputVideoCodec, - request.Width, - request.Height, - request.MaxWidth, - request.MaxHeight)); + // INPUT d3d11 surface(vram) + // map from d3d11va to opencl via d3d11-opencl interop. + mainFilters.Add("hwmap=derive_device=opencl"); + var outFormat = doOclTonemap ? string.Empty : "nv12"; + var hwScaleFilter = GetHwScaleFilter("opencl", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH); + // hw scale + mainFilters.Add(hwScaleFilter); + + // hw deint <= TODO: finsh the 'yadif_opencl' filter } - // Add Cuda tonemapping filter. - if (isNvdecDecoder && isCudaTonemappingSupported) + // hw tonemap + if (doOclTonemap) { - isNoTonemapFilterApplied = false; - var inputHdrParams = GetInputHdrParams(videoStream.ColorTransfer); - if (!string.IsNullOrEmpty(inputHdrParams)) - { - filters.Add(inputHdrParams); - } + var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12"); + mainFilters.Add(tonemapFilter); + } - var parameters = (hasGraphicalSubs && isCudaOverlaySupported && isNvencEncoder) - ? "tonemap_cuda=format=yuv420p:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:peak={1}:desat={2}" - : "tonemap_cuda=format=nv12:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:peak={1}:desat={2}"; + var memoryOutput = false; + var isUploadForOclTonemap = isSwDecoder && doOclTonemap; + if ((isD3d11vaDecoder && isSwEncoder) || isUploadForOclTonemap) + { + memoryOutput = true; - if (options.TonemappingParam != 0) - { - parameters += ":param={3}"; - } + // OUTPUT nv12 surface(memory) + // prefer hwmap to hwdownload on opencl. + var hwTransferFilter = hasGraphicalSubs ? "hwdownload" : "hwmap"; + mainFilters.Add(hwTransferFilter); + mainFilters.Add("format=nv12"); + } - if (!string.Equals(options.TonemappingRange, "auto", StringComparison.OrdinalIgnoreCase)) + // OUTPUT yuv420p surface + if (isSwDecoder && isAmfEncoder) + { + memoryOutput = true; + } + + if (memoryOutput) + { + // text subtitles + if (hasTextSubs) { - parameters += ":range={4}"; + var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false); + mainFilters.Add(textSubtitlesFilter); } + } - filters.Add( - string.Format( - CultureInfo.InvariantCulture, - parameters, - options.TonemappingAlgorithm, - options.TonemappingPeak, - options.TonemappingDesat, - options.TonemappingParam, - options.TonemappingRange)); - - if (isLibX264Encoder - || isLibX265Encoder - || hasTextSubs - || (hasGraphicalSubs && !isCudaOverlaySupported && isNvencEncoder)) - { - if (isNvencEncoder) + if (isDxInDxOut && !hasSubs) + { + // OUTPUT d3d11(nv12) surface(vram) + // reverse-mapping via d3d11-opencl interop. + mainFilters.Add("hwmap=derive_device=d3d11va:reverse=1"); + mainFilters.Add("format=d3d11"); + } + + /* Make sub and overlay filters for subtitle stream */ + var subFilters = new List(); + var overlayFilters = new List(); + if (isDxInDxOut) + { + if (hasSubs) + { + if (hasGraphicalSubs) { - isHwuploadCudaRequired = true; + // scale=s=1280x720,format=yuva420p,hwupload + var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subSwScaleFilter); + subFilters.Add("format=yuva420p"); + } + else if (hasTextSubs) + { + // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload + var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, hasAssSubs ? 10 : 5); + var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true); + subFilters.Add(alphaSrcFilter); + subFilters.Add("format=yuva420p"); + subFilters.Add(subTextSubtitlesFilter); } - filters.Add("hwdownload"); - filters.Add("format=nv12"); + subFilters.Add("hwupload"); + overlayFilters.Add("overlay_opencl=eof_action=endall:shortest=1:repeatlast=0"); + overlayFilters.Add("hwmap=derive_device=d3d11va:reverse=1"); + overlayFilters.Add("format=d3d11"); } - - var outputSdrParams = GetOutputSdrParams(options.TonemappingRange); - if (!string.IsNullOrEmpty(outputSdrParams)) + } + else if (memoryOutput) + { + if (hasGraphicalSubs) { - filters.Add(outputSdrParams); + var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subSwScaleFilter); + overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0"); } } - // Add VPP tonemapping filter for VAAPI. - // Full hardware based video post processing, faster than OpenCL but lacks fine tuning options. - if ((isTonemappingSupportedOnVaapi || isTonemappingSupportedOnQsv) - && isVppTonemappingSupported) + return new Tuple, List, List>(mainFilters, subFilters, overlayFilters); + } + + /// + /// Gets the parameter of Intel QSV filter chain. + /// + /// Encoding state. + /// Encoding options. + /// Video encoder to use. + /// The tuple contains three lists: main, sub and overlay filters + public Tuple, List, List> GetIntelVidFilterChain( + EncodingJobInfo state, + EncodingOptions options, + string vidEncoder) + { + if (!string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) { - filters.Add("tonemap_vaapi=format=nv12:transfer=bt709:matrix=bt709:primaries=bt709"); + return new Tuple, List, List>(null, null, null); } - // Another case is when using Nvenc decoder. - if (isNvdecDecoder && !isOpenclTonemappingSupported && !isCudaTonemappingSupported) + var isWindows = OperatingSystem.IsWindows(); + var isLinux = OperatingSystem.IsLinux(); + var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty; + var isSwDecoder = string.IsNullOrEmpty(vidDecoder); + var isSwEncoder = !vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase); + var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported(); + var isIntelDx11OclSupported = isWindows + && _mediaEncoder.SupportsHwaccel("d3d11va") + && isQsvOclSupported; + var isIntelVaapiOclSupported = isLinux + && IsVaapiSupported(state) + && isQsvOclSupported; + + // legacy qsv pipeline(copy-back) + if ((isSwDecoder && isSwEncoder) + || (!isIntelVaapiOclSupported && !isIntelDx11OclSupported) + || !_mediaEncoder.SupportsFilter("alphasrc")) { - var codec = videoStream.Codec; - var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat); + return GetSwVidFilterChain(state, options, vidEncoder); + } - // Assert 10-bit hardware decodable - if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) - || string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) - || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))) - { - if (isCudaFormatConversionSupported) - { - if (isLibX264Encoder - || isLibX265Encoder - || hasTextSubs - || (hasGraphicalSubs && !isCudaOverlaySupported && isNvencEncoder)) - { - if (isNvencEncoder) - { - isHwuploadCudaRequired = true; - } + // prefered qsv(vaapi) + opencl filters pipeline + if (isIntelVaapiOclSupported) + { + return GetIntelQsvVaapiVidFiltersPrefered(state, options, vidDecoder, vidEncoder); + } - filters.Add("hwdownload"); - filters.Add("format=nv12"); - } - } - else - { - // Download data from GPU to CPU as p010 format. - filters.Add("hwdownload"); - filters.Add("format=p010"); + // prefered qsv(d3d11) + opencl filters pipeline + if (isIntelDx11OclSupported) + { + return GetIntelQsvDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder); + } - // Cuda lacks of a pixel format converter. - if (isNvencEncoder) - { - isHwuploadCudaRequired = true; - filters.Add("format=yuv420p"); - } - } + return new Tuple, List, List>(null, null, null); + } + + public Tuple, List, List> GetIntelQsvDx11VidFiltersPrefered( + EncodingJobInfo state, + EncodingOptions options, + string vidDecoder, + string vidEncoder) + { + var inW = state.VideoStream?.Width; + var inH = state.VideoStream?.Height; + var reqW = state.BaseRequest.Width; + var reqH = state.BaseRequest.Height; + var reqMaxW = state.BaseRequest.MaxWidth; + var reqMaxH = state.BaseRequest.MaxHeight; + var threeDFormat = state.MediaSource.Video3DFormat; + + var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase); + var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase); + var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase); + var isHwDecoder = isD3d11vaDecoder || isQsvDecoder; + var isSwDecoder = string.IsNullOrEmpty(vidDecoder); + var isSwEncoder = !isQsvEncoder; + var isQsvInQsvOut = isHwDecoder && isQsvEncoder; + + var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); + var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); + var doDeintH2645 = doDeintH264 || doDeintHevc; + var doOclTonemap = IsHwTonemapAvailable(state, options); + + var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; + var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream; + var hasAssSubs = hasSubs + && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) + || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase)); + + /* Make main filters for video stream */ + var mainFilters = new List(); + + mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap)); + + if (isSwDecoder) + { + // INPUT sw surface(memory) + var outFormat = doOclTonemap ? "yuv420p10" : "yuv420p"; + var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); + // sw scale + mainFilters.AddRange(swScaleFilter); + mainFilters.Add("format=" + outFormat); + // sw deint + if (doDeintH2645) + { + var swDeintFilter = GetSwDeinterlaceFilter(state, options); + mainFilters.Add(swDeintFilter); } - // Assert 8-bit hardware decodable - else if (!isColorDepth10 - && (isLibX264Encoder - || isLibX265Encoder - || hasTextSubs - || (hasGraphicalSubs && !isCudaOverlaySupported && isNvencEncoder))) + // keep video at memory except ocl tonemap, + // since the overhead caused by hwupload >>> using sw filter. + // sw => hw + if (doOclTonemap) + { + mainFilters.Add("hwupload"); + } + } + else if (isD3d11vaDecoder || isQsvDecoder) + { + var outFormat = doOclTonemap ? string.Empty : "nv12"; + var hwScaleFilter = GetHwScaleFilter("qsv", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH); + + if (isD3d11vaDecoder) { - if (isNvencEncoder) + if (!string.IsNullOrEmpty(hwScaleFilter) || doDeintH2645) { - isHwuploadCudaRequired = true; + // INPUT d3d11 surface(vram) + // map from d3d11va to qsv. + mainFilters.Add("hwmap=derive_device=qsv"); } + } - filters.Add("hwdownload"); - filters.Add("format=nv12"); + // hw scale + mainFilters.Add(hwScaleFilter); + + // hw deint + if (doDeintH2645) + { + var deintFilter = GetHwDeinterlaceFilter(state, options, "qsv"); + mainFilters.Add(deintFilter); } } - // Add parameters to use VAAPI with burn-in text subtitles (GH issue #642) - if (isVaapiH264Encoder - || isVaapiHevcEncoder - || (isTonemappingSupportedOnQsv && isVppTonemappingSupported)) + if (doOclTonemap && isHwDecoder) + { + // map from qsv to opencl via qsv(d3d11)-opencl interop. + mainFilters.Add("hwmap=derive_device=opencl"); + } + + // hw tonemap + if (doOclTonemap) + { + var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12"); + mainFilters.Add(tonemapFilter); + } + + var memoryOutput = false; + var isUploadForOclTonemap = isSwDecoder && doOclTonemap; + var isHwmapUsable = isSwEncoder && doOclTonemap; + if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap) + { + memoryOutput = true; + + // OUTPUT nv12 surface(memory) + // prefer hwmap to hwdownload on opencl. + // qsv hwmap is not fully implemented for the time being. + mainFilters.Add(isHwmapUsable ? "hwmap" : "hwdownload"); + mainFilters.Add("format=nv12"); + } + + // OUTPUT nv12 surface(memory) + if (isSwDecoder && isQsvEncoder) + { + memoryOutput = true; + } + + if (memoryOutput) { + // text subtitles if (hasTextSubs) { - // Convert hw context from ocl to va. - // For tonemapping and text subs burn-in. - if (isTonemappingSupportedOnVaapi && isOpenclTonemappingSupported && !isVppTonemappingSupported) + var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false); + mainFilters.Add(textSubtitlesFilter); + } + } + + if (isQsvInQsvOut && doOclTonemap) + { + // OUTPUT qsv(nv12) surface(vram) + // reverse-mapping via qsv(d3d11)-opencl interop. + mainFilters.Add("hwmap=derive_device=qsv:reverse=1:extra_hw_frames=16"); + mainFilters.Add("format=qsv"); + } + + /* Make sub and overlay filters for subtitle stream */ + var subFilters = new List(); + var overlayFilters = new List(); + if (isQsvInQsvOut) + { + if (hasSubs) + { + if (hasGraphicalSubs) + { + // scale,format=bgra,hwupload + // overlay_qsv can handle overlay scaling, + // add a dummy scale filter to pair with -canvas_size. + subFilters.Add("scale=flags=fast_bilinear"); + subFilters.Add("format=bgra"); + } + else if (hasTextSubs) { - filters.Add("scale_vaapi"); + // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload + var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, hasAssSubs ? 10 : 5); + var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true); + subFilters.Add(alphaSrcFilter); + subFilters.Add("format=bgra"); + subFilters.Add(subTextSubtitlesFilter); } - // Test passed on Intel and AMD gfx - filters.Add("hwmap=mode=read+write"); - filters.Add("format=nv12"); + // qsv requires a fixed pool size. + subFilters.Add("hwupload=extra_hw_frames=32"); + + var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var overlaySize = (overlayW.HasValue && overlayH.HasValue) + ? (":w=" + overlayW.Value + ":h=" + overlayH.Value) + : string.Empty; + var overlayQsvFilter = string.Format( + CultureInfo.InvariantCulture, + "overlay_qsv=eof_action=endall:shortest=1:repeatlast=0{0}", + overlaySize); + overlayFilters.Add(overlayQsvFilter); } } - - if (hasTextSubs) + else if (memoryOutput) { - var subParam = GetTextSubtitleParam(state); + if (hasGraphicalSubs) + { + var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subSwScaleFilter); + overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0"); + } + } + + return new Tuple, List, List>(mainFilters, subFilters, overlayFilters); + } + + public Tuple, List, List> GetIntelQsvVaapiVidFiltersPrefered( + EncodingJobInfo state, + EncodingOptions options, + string vidDecoder, + string vidEncoder) + { + var inW = state.VideoStream?.Width; + var inH = state.VideoStream?.Height; + var reqW = state.BaseRequest.Width; + var reqH = state.BaseRequest.Height; + var reqMaxW = state.BaseRequest.MaxWidth; + var reqMaxH = state.BaseRequest.MaxHeight; + var threeDFormat = state.MediaSource.Video3DFormat; + + var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase); + var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase); + var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase); + var isHwDecoder = isVaapiDecoder || isQsvDecoder; + var isSwDecoder = string.IsNullOrEmpty(vidDecoder); + var isSwEncoder = !isQsvEncoder; + var isQsvInQsvOut = isHwDecoder && isQsvEncoder; + + var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); + var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); + var doVaVppTonemap = IsVaapiVppTonemapAvailable(state, options); + var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options); + var doTonemap = doVaVppTonemap || doOclTonemap; + var doDeintH2645 = doDeintH264 || doDeintHevc; + + var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; + var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream; + var hasAssSubs = hasSubs + && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) + || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase)); + + /* Make main filters for video stream */ + var mainFilters = new List(); - filters.Add(subParam); + mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap)); - // Ensure proper filters are passed to ffmpeg in case of hardware acceleration via VA-API - // Reference: https://trac.ffmpeg.org/wiki/Hardware/VAAPI - if (isVaapiH264Encoder || isVaapiHevcEncoder) + if (isSwDecoder) + { + // INPUT sw surface(memory) + var outFormat = doOclTonemap ? "yuv420p10" : "yuv420p"; + var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); + // sw scale + mainFilters.AddRange(swScaleFilter); + mainFilters.Add("format=" + outFormat); + // sw deint + if (doDeintH2645) { - filters.Add("hwmap"); + var swDeintFilter = GetSwDeinterlaceFilter(state, options); + mainFilters.Add(swDeintFilter); } - if (isTonemappingSupportedOnQsv && isVppTonemappingSupported) - { - filters.Add("hwmap,format=vaapi"); - } + // keep video at memory except ocl tonemap, + // since the overhead caused by hwupload >>> using sw filter. + // sw => hw + if (doOclTonemap) + { + mainFilters.Add("hwupload"); + } + } + else if (isVaapiDecoder || isQsvDecoder) + { + // INPUT vaapi/qsv surface(vram) + var outFormat = doTonemap ? string.Empty : "nv12"; + var hwScaleFilter = GetHwScaleFilter(isVaapiDecoder ? "vaapi" : "qsv", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH); + // hw scale + mainFilters.Add(hwScaleFilter); + } + + // hw deint + if (doDeintH2645 && isHwDecoder) + { + var deintFilter = string.Empty; + if (isVaapiDecoder) + { + deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi"); + } + else if (isQsvDecoder) + { + deintFilter = GetHwDeinterlaceFilter(state, options, "qsv"); + } + + mainFilters.Add(deintFilter); + } + + // vaapi vpp tonemap + if (doVaVppTonemap && isHwDecoder) + { + if (isQsvDecoder) + { + // map from qsv to vaapi. + mainFilters.Add("hwmap=derive_device=vaapi"); + } + + var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12"); + mainFilters.Add(tonemapFilter); + + if (isQsvDecoder) + { + // map from vaapi to qsv. + mainFilters.Add("hwmap=derive_device=qsv"); + } + } + + if (doOclTonemap && isHwDecoder) + { + // map from qsv to opencl via qsv(vaapi)-opencl interop. + mainFilters.Add("hwmap=derive_device=opencl"); + } + + // ocl tonemap + if (doOclTonemap) + { + var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12"); + mainFilters.Add(tonemapFilter); + } + + var memoryOutput = false; + var isUploadForOclTonemap = isSwDecoder && doOclTonemap; + var isHwmapUsable = isSwEncoder && (doOclTonemap || isVaapiDecoder); + if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap) + { + memoryOutput = true; + + // OUTPUT nv12 surface(memory) + // prefer hwmap to hwdownload on opencl/vaapi. + // qsv hwmap is not fully implemented for the time being. + mainFilters.Add(isHwmapUsable ? "hwmap" : "hwdownload"); + mainFilters.Add("format=nv12"); + } + + // OUTPUT nv12 surface(memory) + if (isSwDecoder && isQsvEncoder) + { + memoryOutput = true; + } + + if (memoryOutput) + { + // text subtitles + if (hasTextSubs) + { + var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false); + mainFilters.Add(textSubtitlesFilter); + } + } + + if (isQsvInQsvOut) + { + if (doOclTonemap) + { + // OUTPUT qsv(nv12) surface(vram) + // reverse-mapping via qsv(vaapi)-opencl interop. + mainFilters.Add("hwmap=derive_device=qsv:reverse=1:extra_hw_frames=16"); + mainFilters.Add("format=qsv"); + } + else if (isVaapiDecoder) + { + mainFilters.Add("hwmap=derive_device=qsv"); + mainFilters.Add("format=qsv"); + } + } + + /* Make sub and overlay filters for subtitle stream */ + var subFilters = new List(); + var overlayFilters = new List(); + if (isQsvInQsvOut) + { + if (hasSubs) + { + if (hasGraphicalSubs) + { + subFilters.Add("scale=flags=fast_bilinear"); + subFilters.Add("format=bgra"); + } + else if (hasTextSubs) + { + var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, hasAssSubs ? 10 : 5); + var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true); + subFilters.Add(alphaSrcFilter); + subFilters.Add("format=bgra"); + subFilters.Add(subTextSubtitlesFilter); + } + + // qsv requires a fixed pool size. + subFilters.Add("hwupload=extra_hw_frames=32"); + + var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var overlaySize = (overlayW.HasValue && overlayH.HasValue) + ? (":w=" + overlayW.Value + ":h=" + overlayH.Value) + : string.Empty; + var overlayQsvFilter = string.Format( + CultureInfo.InvariantCulture, + "overlay_qsv=eof_action=endall:shortest=1:repeatlast=0{0}", + overlaySize); + overlayFilters.Add(overlayQsvFilter); + } + } + else if (memoryOutput) + { + if (hasGraphicalSubs) + { + var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subSwScaleFilter); + overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0"); + } + } + + return new Tuple, List, List>(mainFilters, subFilters, overlayFilters); + } + + /// + /// Gets the parameter of Intel/AMD VAAPI filter chain. + /// + /// Encoding state. + /// Encoding options. + /// Video encoder to use. + /// The tuple contains three lists: main, sub and overlay filters + public Tuple, List, List> GetVaapiVidFilterChain( + EncodingJobInfo state, + EncodingOptions options, + string vidEncoder) + { + if (!string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) + { + return new Tuple, List, List>(null, null, null); + } + + var isLinux = OperatingSystem.IsLinux(); + var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty; + var isSwDecoder = string.IsNullOrEmpty(vidDecoder); + var isSwEncoder = !vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase); + var isVaapiOclSupported = isLinux && IsVaapiSupported(state) && IsVaapiFullSupported() && IsOpenclFullSupported(); + + // legacy vaapi pipeline(copy-back) + if ((isSwDecoder && isSwEncoder) + || !isVaapiOclSupported + || !_mediaEncoder.SupportsFilter("alphasrc")) + { + var swFilterChain = GetSwVidFilterChain(state, options, vidEncoder); + + if (!isSwEncoder) + { + var newfilters = new List(); + var noOverlay = swFilterChain.Item3.Count == 0; + newfilters.AddRange(noOverlay ? swFilterChain.Item1 : swFilterChain.Item3); + newfilters.Add("hwupload"); + + var mainFilters = noOverlay ? newfilters : swFilterChain.Item1; + var overlayFilters = noOverlay ? swFilterChain.Item3 : newfilters; + return new Tuple, List, List>(mainFilters, swFilterChain.Item2, overlayFilters); + } + + return swFilterChain; + } + + // prefered vaapi + opencl filters pipeline + if (_mediaEncoder.IsVaapiDeviceInteliHD()) + { + // Intel iHD path, with extra vpp tonemap and overlay support. + return GetVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder); + } + + // Intel i965 and Amd radeonsi/r600 path, only featuring scale and deinterlace support. + return GetVaapiLimitedVidFiltersPrefered(state, options, vidDecoder, vidEncoder); + } + + public Tuple, List, List> GetVaapiFullVidFiltersPrefered( + EncodingJobInfo state, + EncodingOptions options, + string vidDecoder, + string vidEncoder) + { + var inW = state.VideoStream?.Width; + var inH = state.VideoStream?.Height; + var reqW = state.BaseRequest.Width; + var reqH = state.BaseRequest.Height; + var reqMaxW = state.BaseRequest.MaxWidth; + var reqMaxH = state.BaseRequest.MaxHeight; + var threeDFormat = state.MediaSource.Video3DFormat; + + var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase); + var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase); + var isSwDecoder = string.IsNullOrEmpty(vidDecoder); + var isSwEncoder = !isVaapiEncoder; + var isVaInVaOut = isVaapiDecoder && isVaapiEncoder; + + var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); + var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); + var doVaVppTonemap = isVaapiDecoder && IsVaapiVppTonemapAvailable(state, options); + var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options); + var doTonemap = doVaVppTonemap || doOclTonemap; + var doDeintH2645 = doDeintH264 || doDeintHevc; + + var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; + var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream; + var hasAssSubs = hasSubs + && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) + || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase)); + + /* Make main filters for video stream */ + var mainFilters = new List(); + + mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap)); + + if (isSwDecoder) + { + // INPUT sw surface(memory) + var outFormat = doOclTonemap ? "yuv420p10" : "nv12"; + var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); + // sw scale + mainFilters.AddRange(swScaleFilter); + mainFilters.Add("format=" + outFormat); + // sw deint + if (doDeintH2645) + { + var swDeintFilter = GetSwDeinterlaceFilter(state, options); + mainFilters.Add(swDeintFilter); + } + + // keep video at memory except ocl tonemap, + // since the overhead caused by hwupload >>> using sw filter. + // sw => hw + if (doOclTonemap) + { + mainFilters.Add("hwupload"); + } + } + else if (isVaapiDecoder) + { + // INPUT vaapi surface(vram) + var outFormat = doTonemap ? string.Empty : "nv12"; + var hwScaleFilter = GetHwScaleFilter("vaapi", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH); + // hw scale + mainFilters.Add(hwScaleFilter); + } + + // hw deint + if (doDeintH2645 && isVaapiDecoder) + { + var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi"); + mainFilters.Add(deintFilter); + } + + // vaapi vpp tonemap + if (doVaVppTonemap && isVaapiDecoder) + { + var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12"); + mainFilters.Add(tonemapFilter); + } + + if (doOclTonemap && isVaapiDecoder) + { + // map from vaapi to opencl via vaapi-opencl interop(Intel only). + mainFilters.Add("hwmap=derive_device=opencl"); + } + + // ocl tonemap + if (doOclTonemap) + { + var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12"); + mainFilters.Add(tonemapFilter); + } + + if (doOclTonemap && isVaInVaOut) + { + // OUTPUT vaapi(nv12) surface(vram) + // reverse-mapping via vaapi-opencl interop. + mainFilters.Add("hwmap=derive_device=vaapi:reverse=1"); + mainFilters.Add("format=vaapi"); + } + + var memoryOutput = false; + var isUploadForOclTonemap = isSwDecoder && doOclTonemap; + var isHwmapNotUsable = isUploadForOclTonemap && isVaapiEncoder; + if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap) + { + memoryOutput = true; + + // OUTPUT nv12 surface(memory) + // prefer hwmap to hwdownload on opencl/vaapi. + mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap"); + mainFilters.Add("format=nv12"); + } + + // OUTPUT nv12 surface(memory) + if (isSwDecoder && isVaapiEncoder) + { + memoryOutput = true; + } + + if (memoryOutput) + { + // text subtitles + if (hasTextSubs) + { + var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false); + mainFilters.Add(textSubtitlesFilter); + } + } + + if (memoryOutput && isVaapiEncoder) + { + if (!hasGraphicalSubs) + { + mainFilters.Add("hwupload_vaapi"); + } + } + + /* Make sub and overlay filters for subtitle stream */ + var subFilters = new List(); + var overlayFilters = new List(); + if (isVaInVaOut) + { + if (hasSubs) + { + if (hasGraphicalSubs) + { + subFilters.Add("scale=flags=fast_bilinear"); + subFilters.Add("format=bgra"); + } + else if (hasTextSubs) + { + var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, hasAssSubs ? 10 : 5); + var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true); + subFilters.Add(alphaSrcFilter); + subFilters.Add("format=bgra"); + subFilters.Add(subTextSubtitlesFilter); + } + + subFilters.Add("hwupload"); + + var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var overlaySize = (overlayW.HasValue && overlayH.HasValue) + ? (":w=" + overlayW.Value + ":h=" + overlayH.Value) + : string.Empty; + var overlayVaapiFilter = string.Format( + CultureInfo.InvariantCulture, + "overlay_vaapi=eof_action=endall:shortest=1:repeatlast=0{0}", + overlaySize); + overlayFilters.Add(overlayVaapiFilter); + } + } + else if (memoryOutput) + { + if (hasGraphicalSubs) + { + var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subSwScaleFilter); + overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0"); + + if (isVaapiEncoder) + { + overlayFilters.Add("hwupload_vaapi"); + } + } + } + + return new Tuple, List, List>(mainFilters, subFilters, overlayFilters); + } + + public Tuple, List, List> GetVaapiLimitedVidFiltersPrefered( + EncodingJobInfo state, + EncodingOptions options, + string vidDecoder, + string vidEncoder) + { + var inW = state.VideoStream?.Width; + var inH = state.VideoStream?.Height; + var reqW = state.BaseRequest.Width; + var reqH = state.BaseRequest.Height; + var reqMaxW = state.BaseRequest.MaxWidth; + var reqMaxH = state.BaseRequest.MaxHeight; + var threeDFormat = state.MediaSource.Video3DFormat; + + var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase); + var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase); + var isSwDecoder = string.IsNullOrEmpty(vidDecoder); + var isSwEncoder = !isVaapiEncoder; + var isVaInVaOut = isVaapiDecoder && isVaapiEncoder; + var isi965Driver = _mediaEncoder.IsVaapiDeviceInteli965(); + var isAmdDriver = _mediaEncoder.IsVaapiDeviceAmd(); + + var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); + var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); + var doDeintH2645 = doDeintH264 || doDeintHevc; + var doOclTonemap = IsHwTonemapAvailable(state, options); + + var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; + var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream; + + /* Make main filters for video stream */ + var mainFilters = new List(); + + mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap)); + + var outFormat = string.Empty; + if (isSwDecoder) + { + // INPUT sw surface(memory) + outFormat = doOclTonemap ? "yuv420p10" : "nv12"; + var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); + // sw scale + mainFilters.AddRange(swScaleFilter); + mainFilters.Add("format=" + outFormat); + // sw deint + if (doDeintH2645) + { + var swDeintFilter = GetSwDeinterlaceFilter(state, options); + mainFilters.Add(swDeintFilter); + } + + // keep video at memory except ocl tonemap, + // since the overhead caused by hwupload >>> using sw filter. + // sw => hw + if (doOclTonemap) + { + mainFilters.Add("hwupload"); + } + } + else if (isVaapiDecoder) + { + // INPUT vaapi surface(vram) + outFormat = doOclTonemap ? string.Empty : "nv12"; + var hwScaleFilter = GetHwScaleFilter("vaapi", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH); + // hw scale + mainFilters.Add(hwScaleFilter); + } + + // hw deint + if (doDeintH2645 && isVaapiDecoder) + { + var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi"); + mainFilters.Add(deintFilter); + } + + if (doOclTonemap && isVaapiDecoder) + { + if (isi965Driver) + { + // map from vaapi to opencl via vaapi-opencl interop(Intel only). + mainFilters.Add("hwmap=derive_device=opencl"); + } + else + { + mainFilters.Add("hwdownload"); + mainFilters.Add("format=p010le"); + mainFilters.Add("hwupload"); + } + } + + // ocl tonemap + if (doOclTonemap) + { + var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12"); + mainFilters.Add(tonemapFilter); + } + + if (doOclTonemap && isVaInVaOut) + { + if (isi965Driver) + { + // OUTPUT vaapi(nv12) surface(vram) + // reverse-mapping via vaapi-opencl interop. + mainFilters.Add("hwmap=derive_device=vaapi:reverse=1"); + mainFilters.Add("format=vaapi"); + } + } + + var memoryOutput = false; + var isUploadForOclTonemap = doOclTonemap && (isSwDecoder || (isVaapiDecoder && !isi965Driver)); + var isHwmapNotUsable = hasGraphicalSubs || isUploadForOclTonemap; + var isHwmapForSubs = hasSubs && isVaapiDecoder; + var isHwUnmapForTextSubs = hasTextSubs && isVaInVaOut && !isUploadForOclTonemap; + if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap || isHwmapForSubs) + { + memoryOutput = true; + + // OUTPUT nv12 surface(memory) + // prefer hwmap to hwdownload on opencl/vaapi. + mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap"); + mainFilters.Add("format=nv12"); + } + + // OUTPUT nv12 surface(memory) + if (isSwDecoder && isVaapiEncoder) + { + memoryOutput = true; + } + + if (memoryOutput) + { + // text subtitles + if (hasTextSubs) + { + var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false); + mainFilters.Add(textSubtitlesFilter); + } + } + + if (isHwUnmapForTextSubs) + { + mainFilters.Add("hwmap"); + mainFilters.Add("format=vaapi"); + } + else if (memoryOutput && isVaapiEncoder) + { + if (!hasGraphicalSubs) + { + mainFilters.Add("hwupload_vaapi"); + } + } + + /* Make sub and overlay filters for subtitle stream */ + var subFilters = new List(); + var overlayFilters = new List(); + if (memoryOutput) + { + if (hasGraphicalSubs) + { + var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subSwScaleFilter); + overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0"); + + if (isVaapiEncoder) + { + overlayFilters.Add("hwupload_vaapi"); + } + } + } + + return new Tuple, List, List>(mainFilters, subFilters, overlayFilters); + } + + /// + /// Gets the parameter of video processing filters. + /// + /// Encoding state. + /// Encoding options. + /// Video codec to use. + /// The video processing filters parameter. + public string GetVideoProcessingFilterParam( + EncodingJobInfo state, + EncodingOptions options, + string outputVideoCodec) + { + var videoStream = state.VideoStream; + if (videoStream == null) + { + return string.Empty; + } + + var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; + var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream; + + Tuple, List, List> filterChain = null; + + if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) + { + filterChain = GetVaapiVidFilterChain(state, options, outputVideoCodec); + } + else if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) + { + filterChain = GetIntelVidFilterChain(state, options, outputVideoCodec); + } + else if (string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) + { + filterChain = GetNvidiaVidFilterChain(state, options, outputVideoCodec); + } + else if (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)) + { + filterChain = GetAmdVidFilterChain(state, options, outputVideoCodec); + } + else + { + filterChain = GetSwVidFilterChain(state, options, outputVideoCodec); + } + + var mainFilters = filterChain.Item1; + mainFilters.RemoveAll(filter => string.IsNullOrEmpty(filter)); + + var subFilters = filterChain.Item2; + subFilters.RemoveAll(filter => string.IsNullOrEmpty(filter)); + + var overlayFilters = filterChain.Item3; + overlayFilters.RemoveAll(filter => string.IsNullOrEmpty(filter)); + + var mainStr = string.Empty; + if (mainFilters.Count > 0) + { + mainStr = string.Format( + CultureInfo.InvariantCulture, + "{0}", + string.Join(',', mainFilters)); + } + + if (overlayFilters.Count == 0) + { + // -vf "scale..." + return string.IsNullOrEmpty(mainStr) ? string.Empty : " -vf \"" + mainStr + "\""; + } + else if (overlayFilters.Count > 0 && subFilters.Count > 0 && state.SubtitleStream != null) + { + // overlay graphical/text subtitles + var subStr = string.Format( + CultureInfo.InvariantCulture, + "{0}", + string.Join(',', subFilters)); + + var overlayStr = string.Format( + CultureInfo.InvariantCulture, + "{0}", + string.Join(',', overlayFilters)); + + var mapPrefix = state.SubtitleStream.IsExternal + ? 1 + : 0; + + var subtitleStreamIndex = state.SubtitleStream.IsExternal + ? 0 + : state.SubtitleStream.Index; + + if (hasSubs) + { + // -filter_complex "[0:s]scale=s[sub]..." + var filterStr = string.IsNullOrEmpty(mainStr) + ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]{5}\"" + : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\""; + + if (hasTextSubs) + { + filterStr = string.IsNullOrEmpty(mainStr) + ? " -filter_complex \"{4}[sub];[0:{2}][sub]{5}\"" + : " -filter_complex \"{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\""; + } + + return string.Format( + CultureInfo.InvariantCulture, + filterStr, + mapPrefix, + subtitleStreamIndex, + state.VideoStream.Index, + mainStr, + subStr, + overlayStr); + } + } + + return string.Empty; + } + + public string GetOverwriteColorPropertiesParam(EncodingJobInfo state, bool isTonemapAvailable) + { + if (isTonemapAvailable) + { + return GetInputHdrParam(state.VideoStream?.ColorTransfer); + } + else + { + return GetOutputSdrParam(null); + } + } + + public string GetInputHdrParam(string colorTransfer) + { + if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase)) + { + // HLG + return "setparams=color_primaries=bt2020:color_trc=arib-std-b67:colorspace=bt2020nc"; + } + else + { + // HDR10 + return "setparams=color_primaries=bt2020:color_trc=smpte2084:colorspace=bt2020nc"; + } + } + + public string GetOutputSdrParam(string tonemappingRange) + { + // SDR + if (string.Equals(tonemappingRange, "tv", StringComparison.OrdinalIgnoreCase)) + { + return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=tv"; + } + + if (string.Equals(tonemappingRange, "pc", StringComparison.OrdinalIgnoreCase)) + { + return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=pc"; + } + + return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709"; + } + + public static int GetVideoColorBitDepth(EncodingJobInfo state) + { + var videoStream = state.VideoStream; + if (videoStream != null) + { + if (videoStream.BitDepth.HasValue) + { + return videoStream.BitDepth.Value; + } + else if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase)) + { + return 8; + } + else if (string.Equals(videoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase)) + { + return 10; + } + else if (string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase)) + { + return 12; + } + else + { + return 8; + } + } + + return 0; + } + + /// + /// Gets the ffmpeg option string for the hardware accelerated video decoder. + /// + /// The encoding job info. + /// The encoding options. + /// The option string or null if none available. + protected string GetHardwareVideoDecoder(EncodingJobInfo state, EncodingOptions options) + { + var videoStream = state.VideoStream; + if (videoStream == null) + { + return null; + } + + // Only use alternative encoders for video files. + var videoType = state.MediaSource.VideoType ?? VideoType.VideoFile; + if (videoType != VideoType.VideoFile) + { + return null; + } + + if (IsCopyCodec(state.OutputVideoCodec)) + { + return null; + } + + if (!string.IsNullOrEmpty(videoStream.Codec) && !string.IsNullOrEmpty(options.HardwareAccelerationType)) + { + var bitDepth = GetVideoColorBitDepth(state); + + // Only HEVC, VP9 and AV1 formats have 10-bit hardware decoder support now. + if (bitDepth == 10 && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))) + { + return null; + } + + if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) + { + return GetQsvHwVidDecoder(state, options, videoStream, bitDepth); + } + + if (string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) + { + return GetNvdecVidDecoder(state, options, videoStream, bitDepth); + } + + if (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)) + { + return GetAmfVidDecoder(state, options, videoStream, bitDepth); + } + + if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) + { + return GetVaapiVidDecoder(state, options, videoStream, bitDepth); + } + + if (string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) + { + return GetVideotoolboxVidDecoder(state, options, videoStream, bitDepth); + } + + if (string.Equals(options.HardwareAccelerationType, "omx", StringComparison.OrdinalIgnoreCase)) + { + return GetOmxVidDecoder(state, options, videoStream, bitDepth); + } + } + + var whichCodec = videoStream.Codec?.ToLowerInvariant(); + switch (whichCodec) + { + case "avc": + whichCodec = "h264"; + break; + case "h265": + whichCodec = "hevc"; + break; + } + + // Avoid a second attempt if no hardware acceleration is being used + options.HardwareDecodingCodecs = options.HardwareDecodingCodecs.Where(val => val != whichCodec).ToArray(); + + // leave blank so ffmpeg will decide + return null; + } + + /// + /// Gets a hw decoder name. + /// + /// Encoding options. + /// Decoder prefix. + /// Decoder suffix. + /// Video codec to use. + /// Video color bit depth. + /// Hardware decoder name. + public string GetHwDecoderName(EncodingOptions options, string decoderPrefix, string decoderSuffix, string videoCodec, int bitDepth) + { + if (string.IsNullOrEmpty(decoderPrefix) || string.IsNullOrEmpty(decoderSuffix)) + { + return null; + } + + var decoderName = decoderPrefix + '_' + decoderSuffix; + + var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoderName) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase); + if (bitDepth == 10 && isCodecAvailable) + { + if ((options.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Hevc) + || (options.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Vp9)) + { + return null; + } + } + + if (string.Equals(decoderSuffix, "cuvid", StringComparison.OrdinalIgnoreCase) && options.EnableEnhancedNvdecDecoder) + { + return null; + } + + if (string.Equals(decoderSuffix, "qsv", StringComparison.OrdinalIgnoreCase) && options.PreferSystemNativeHwDecoder) + { + return null; + } + + return isCodecAvailable ? (" -c:v " + decoderName) : null; + } + + /// + /// Gets a hwaccel type to use as a hardware decoder depending on the system. + /// + /// Encoding state. + /// Encoding options. + /// Video codec to use. + /// Video color bit depth. + /// Specifies if output hw surface. + /// Hardware accelerator type. + public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec, int bitDepth, bool outputHwSurface) + { + var isWindows = OperatingSystem.IsWindows(); + var isLinux = OperatingSystem.IsLinux(); + var isMacOS = OperatingSystem.IsMacOS(); + var isD3d11Supported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va"); + var isVaapiSupported = isLinux && IsVaapiSupported(state); + var isCudaSupported = (isLinux || isWindows) && IsCudaFullSupported(); + var isQsvSupported = (isLinux || isWindows) && _mediaEncoder.SupportsHwaccel("qsv"); + var isVideotoolboxSupported = isMacOS && _mediaEncoder.SupportsHwaccel("videotoolbox"); + var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase); + + if (bitDepth == 10 && isCodecAvailable) + { + if ((options.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Hevc) + || (options.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Vp9)) + { + return null; + } + } + + // Intel qsv/d3d11va/vaapi + if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) + { + if (options.PreferSystemNativeHwDecoder) + { + if (isVaapiSupported && isCodecAvailable) + { + return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi" : string.Empty); + } + + if (isD3d11Supported && isCodecAvailable) + { + return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty); + } + } + else + { + if (isQsvSupported && isCodecAvailable) + { + return " -hwaccel qsv" + (outputHwSurface ? " -hwaccel_output_format qsv" : string.Empty); + } + } + } + + // Nvidia cuda + if (string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) + { + if (options.EnableEnhancedNvdecDecoder && isCudaSupported && isCodecAvailable) + { + return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda" : string.Empty); + } + } + + // Amd d3d11va + if (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)) + { + if (isD3d11Supported && isCodecAvailable) + { + return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty); + } + } + + // Vaapi + if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) + { + if (isVaapiSupported && isCodecAvailable) + { + return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi" : string.Empty); + } + } + + if (string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) + { + if (isVideotoolboxSupported && isCodecAvailable) + { + return " -hwaccel videotoolbox" + (outputHwSurface ? " -hwaccel_output_format videotoolbox_vld" : string.Empty); + } + } + + return null; + } + + public string GetQsvHwVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth) + { + var isWindows = OperatingSystem.IsWindows(); + var isLinux = OperatingSystem.IsLinux(); + + if (!isWindows && !isLinux) + { + return null; + } + + var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported(); + var isIntelDx11OclSupported = isWindows + && _mediaEncoder.SupportsHwaccel("d3d11va") + && isQsvOclSupported; + var isIntelVaapiOclSupported = isLinux + && IsVaapiSupported(state) + && isQsvOclSupported; + var hwSurface = (isIntelDx11OclSupported || isIntelVaapiOclSupported) + && _mediaEncoder.SupportsFilter("alphasrc"); + + var _8bitSwFormatsQsv = new List { "yuv420p", }; + var _8_10bitSwFormatsQsv = new List { "yuv420p", "yuv420p10le", }; + // TODO: add more 8/10bit and 4:4:4 formats for Qsv after finshing the ffcheck tool + + if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) + { + switch (videoStream.Codec.ToLowerInvariant()) + { + case "avc": + case "h264": + return _8bitSwFormatsQsv.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264", "qsv", "h264", bitDepth) + : string.Empty; + case "hevc": + case "h265": + return _8_10bitSwFormatsQsv.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc", "qsv", "hevc", bitDepth) + : string.Empty; + case "mpeg2video": + return _8bitSwFormatsQsv.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options, "mpeg2", "qsv", "mpeg2video", bitDepth) + : string.Empty; + case "vc1": + return _8bitSwFormatsQsv.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1", "qsv", "vc1", bitDepth) + : string.Empty; + case "vp8": + return _8bitSwFormatsQsv.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8", "qsv", "vp8", bitDepth) + : string.Empty; + case "vp9": + return _8_10bitSwFormatsQsv.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9", "qsv", "vp9", bitDepth) + : string.Empty; + case "av1": + return _8_10bitSwFormatsQsv.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1", "qsv", "av1", bitDepth) + : string.Empty; + } + } + + return null; + } + + public string GetNvdecVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth) + { + if (!OperatingSystem.IsWindows() && !OperatingSystem.IsLinux()) + { + return null; + } - if (isNvdecDecoder && isNvencEncoder) - { - isHwuploadCudaRequired = true; + var hwSurface = IsCudaFullSupported() + && options.EnableEnhancedNvdecDecoder + && _mediaEncoder.SupportsFilter("alphasrc"); + var _8bitSwFormatsNvdec = new List { "yuv420p", }; + var _8_10bitSwFormatsNvdec = new List { "yuv420p", "yuv420p10le", }; + // TODO: add more 8/10/12bit and 4:4:4 formats for Nvdec after finshing the ffcheck tool + + if (string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) + { + switch (videoStream.Codec.ToLowerInvariant()) + { + case "avc": + case "h264": + return _8bitSwFormatsNvdec.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264", "cuvid", "h264", bitDepth) + : string.Empty; + case "hevc": + case "h265": + return _8_10bitSwFormatsNvdec.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc", "cuvid", "hevc", bitDepth) + : string.Empty; + case "mpeg2video": + return _8bitSwFormatsNvdec.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options, "mpeg2", "cuvid", "mpeg2video", bitDepth) + : string.Empty; + case "vc1": + return _8bitSwFormatsNvdec.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1", "cuvid", "vc1", bitDepth) + : string.Empty; + case "mpeg4": + return _8bitSwFormatsNvdec.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface) + GetHwDecoderName(options, "mpeg4", "cuvid", "mpeg4", bitDepth) + : string.Empty; + case "vp8": + return _8bitSwFormatsNvdec.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8", "cuvid", "vp8", bitDepth) + : string.Empty; + case "vp9": + return _8_10bitSwFormatsNvdec.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9", "cuvid", "vp9", bitDepth) + : string.Empty; + case "av1": + return _8_10bitSwFormatsNvdec.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1", "cuvid", "av1", bitDepth) + : string.Empty; } } - // Interop the VAAPI data to QSV for hybrid tonemapping - if (isTonemappingSupportedOnQsv && isVppTonemappingSupported && !hasGraphicalSubs) + return null; + } + + public string GetAmfVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth) + { + if (!OperatingSystem.IsWindows()) { - filters.Add("hwmap=derive_device=qsv,scale_qsv"); + return null; } - if (isHwuploadCudaRequired && !hasGraphicalSubs) + var hwSurface = _mediaEncoder.SupportsHwaccel("d3d11va") + && IsOpenclFullSupported() + && _mediaEncoder.SupportsFilter("alphasrc"); + var _8bitSwFormatsAmf = new List { "yuv420p", }; + var _8_10bitSwFormatsAmf = new List { "yuv420p", "yuv420p10le", }; + + if (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)) { - filters.Add("hwupload_cuda"); + switch (videoStream.Codec.ToLowerInvariant()) + { + case "avc": + case "h264": + return _8bitSwFormatsAmf.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + : string.Empty; + case "hevc": + case "h265": + return _8_10bitSwFormatsAmf.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + : string.Empty; + case "mpeg2video": + return _8bitSwFormatsAmf.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + : string.Empty; + case "vc1": + return _8bitSwFormatsAmf.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + : string.Empty; + case "mpeg4": + return _8bitSwFormatsAmf.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface) + : string.Empty; + case "vp9": + return _8_10bitSwFormatsAmf.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + : string.Empty; + case "av1": + return _8_10bitSwFormatsAmf.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + : string.Empty; + } } - // If no tonemap filter is applied, - // tag the video range as SDR to prevent the encoder from encoding HDR video. - if (isNoTonemapFilterApplied) + return null; + } + + public string GetVaapiVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth) + { + if (!OperatingSystem.IsLinux()) { - var outputSdrParams = GetOutputSdrParams(null); - if (!string.IsNullOrEmpty(outputSdrParams)) - { - filters.Add(outputSdrParams); - } + return null; } - var output = string.Empty; - if (filters.Count > 0) + var hwSurface = IsVaapiSupported(state) + && IsVaapiFullSupported() + && IsOpenclFullSupported() + && _mediaEncoder.SupportsFilter("alphasrc"); + var _8bitSwFormatsVaapi = new List { "yuv420p", }; + var _8_10bitSwFormatsVaapi = new List { "yuv420p", "yuv420p10le", }; + + if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) { - output += string.Format( - CultureInfo.InvariantCulture, - "{0}", - string.Join(',', filters)); + switch (videoStream.Codec.ToLowerInvariant()) + { + case "avc": + case "h264": + return _8bitSwFormatsVaapi.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + : string.Empty; + case "hevc": + case "h265": + return _8_10bitSwFormatsVaapi.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + : string.Empty; + case "mpeg2video": + return _8bitSwFormatsVaapi.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + : string.Empty; + case "vc1": + return _8bitSwFormatsVaapi.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + : string.Empty; + case "vp8": + return _8bitSwFormatsVaapi.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + : string.Empty; + case "vp9": + return _8_10bitSwFormatsVaapi.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + : string.Empty; + case "av1": + return _8_10bitSwFormatsVaapi.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + : string.Empty; + } } - return output; + return null; } - public static string GetInputHdrParams(string colorTransfer) + public string GetVideotoolboxVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth) { - if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase)) + if (!OperatingSystem.IsMacOS()) { - // HLG - return "setparams=color_primaries=bt2020:color_trc=arib-std-b67:colorspace=bt2020nc"; + return null; } - else + + var _8bitSwFormatsVt = new List { "yuv420p", }; + var _8_10bitSwFormatsVt = new List { "yuv420p", "yuv420p10le", }; + + if (string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) { - // HDR10 - return "setparams=color_primaries=bt2020:color_trc=smpte2084:colorspace=bt2020nc"; + switch (videoStream.Codec.ToLowerInvariant()) + { + case "avc": + case "h264": + return _8bitSwFormatsVt.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "h264", bitDepth, false) + : string.Empty; + case "hevc": + case "h265": + return _8_10bitSwFormatsVt.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "hevc", bitDepth, false) + : string.Empty; + case "mpeg2video": + return _8bitSwFormatsVt.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "mpeg2video", bitDepth, false) + : string.Empty; + case "mpeg4": + return _8_10bitSwFormatsVt.Contains(videoStream.PixelFormat) + ? GetHwaccelType(state, options, "mpeg4", bitDepth, false) + : string.Empty; + } } + + return null; } - public static string GetOutputSdrParams(string tonemappingRange) + public string GetOmxVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth) { - // SDR - if (string.Equals(tonemappingRange, "tv", StringComparison.OrdinalIgnoreCase)) + if (!OperatingSystem.IsLinux()) { - return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=tv"; + return null; } - if (string.Equals(tonemappingRange, "pc", StringComparison.OrdinalIgnoreCase)) + var _8bitSwFormatsOmx = new List { "yuv420p", }; + + if (string.Equals(options.HardwareAccelerationType, "omx", StringComparison.OrdinalIgnoreCase)) { - return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=pc"; + switch (videoStream.Codec.ToLowerInvariant()) + { + case "avc": + case "h264": + return _8bitSwFormatsOmx.Contains(videoStream.PixelFormat) + ? GetHwDecoderName(options, "h264", "mmal", "h264", bitDepth) + : string.Empty; + case "mpeg2video": + return _8bitSwFormatsOmx.Contains(videoStream.PixelFormat) + ? GetHwDecoderName(options, "mpeg2", "mmal", "mpeg2video", bitDepth) + : string.Empty; + case "mpeg4": + return _8bitSwFormatsOmx.Contains(videoStream.PixelFormat) + ? GetHwDecoderName(options, "mpeg4", "mmal", "mpeg4", bitDepth) + : string.Empty; + case "vc1": + return _8bitSwFormatsOmx.Contains(videoStream.PixelFormat) + ? GetHwDecoderName(options, "vc1", "mmal", "vc1", bitDepth) + : string.Empty; + } } - return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709"; + return null; } /// @@ -3283,7 +4821,7 @@ namespace MediaBrowser.Controller.MediaEncoding inputModifier = inputModifier.Trim(); - inputModifier += " " + GetFastSeekCommandLineParameter(state.BaseRequest); + inputModifier += " " + GetFastSeekCommandLineParameter(state, encodingOptions); inputModifier = inputModifier.Trim(); if (state.InputProtocol == MediaProtocol.Rtsp) @@ -3337,61 +4875,8 @@ namespace MediaBrowser.Controller.MediaEncoding inputModifier += " -fflags " + string.Join(string.Empty, flags); } - var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions); - - if (!string.IsNullOrEmpty(videoDecoder)) - { - inputModifier += " " + videoDecoder; - - if (!IsCopyCodec(state.OutputVideoCodec) - && videoDecoder.Contains("cuvid", StringComparison.OrdinalIgnoreCase)) - { - var videoStream = state.VideoStream; - var inputWidth = videoStream?.Width; - var inputHeight = videoStream?.Height; - var request = state.BaseRequest; - - var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight); - - if (videoDecoder.Contains("cuvid", StringComparison.OrdinalIgnoreCase) - && width.HasValue - && height.HasValue) - { - if (width.HasValue && height.HasValue) - { - inputModifier += string.Format( - CultureInfo.InvariantCulture, - " -resize {0}x{1}", - width.Value, - height.Value); - } - - if (state.DeInterlace("h264", true)) - { - inputModifier += " -deint 1"; - - if (!encodingOptions.DeinterlaceDoubleRate || (videoStream?.AverageFrameRate ?? 60) > 30) - { - inputModifier += " -drop_second_field 1"; - } - } - } - } - } - if (state.IsVideoRequest) { - var outputVideoCodec = GetVideoEncoder(state, encodingOptions); - - // Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking - if (!string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase) - && state.TranscodingType != TranscodingJobType.Progressive - && !state.EnableBreakOnNonKeyFrames(outputVideoCodec) - && (state.BaseRequest.StartTimeTicks ?? 0) > 0) - { - inputModifier += " -noaccurate_seek"; - } - if (!string.IsNullOrEmpty(state.InputContainer) && state.VideoType == VideoType.VideoFile && string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType)) { var inputFormat = GetInputFormat(state.InputContainer); @@ -3604,322 +5089,6 @@ namespace MediaBrowser.Controller.MediaEncoding } } - /// - /// Gets the ffmpeg option string for the hardware accelerated video decoder. - /// - /// The encoding job info. - /// The encoding options. - /// The option string or null if none available. - protected string GetHardwareAcceleratedVideoDecoder(EncodingJobInfo state, EncodingOptions encodingOptions) - { - var videoStream = state.VideoStream; - - if (videoStream == null) - { - return null; - } - - var videoType = state.MediaSource.VideoType ?? VideoType.VideoFile; - // Only use alternative encoders for video files. - // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully - // Since transcoding of folder rips is experimental anyway, it's not worth adding additional variables such as this. - if (videoType != VideoType.VideoFile) - { - return null; - } - - if (IsCopyCodec(state.OutputVideoCodec)) - { - return null; - } - - if (!string.IsNullOrEmpty(videoStream.Codec) && !string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType)) - { - var isColorDepth10 = IsColorDepth10(state); - - // Only hevc and vp9 formats have 10-bit hardware decoder support now. - if (isColorDepth10 && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))) - { - return null; - } - - // Hybrid VPP tonemapping with VAAPI - if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) - && IsVppTonemappingSupported(state, encodingOptions)) - { - var outputVideoCodec = GetVideoEncoder(state, encodingOptions) ?? string.Empty; - var isQsvEncoder = outputVideoCodec.Contains("qsv", StringComparison.OrdinalIgnoreCase); - if (isQsvEncoder) - { - // Since tonemap_vaapi only support HEVC for now, no need to check the codec again. - return GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10); - } - } - - if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) - { - switch (videoStream.Codec.ToLowerInvariant()) - { - case "avc": - case "h264": - return GetHwDecoderName(encodingOptions, "h264_qsv", "h264", isColorDepth10); - case "hevc": - case "h265": - return GetHwDecoderName(encodingOptions, "hevc_qsv", "hevc", isColorDepth10); - case "mpeg2video": - return GetHwDecoderName(encodingOptions, "mpeg2_qsv", "mpeg2video", isColorDepth10); - case "vc1": - return GetHwDecoderName(encodingOptions, "vc1_qsv", "vc1", isColorDepth10); - case "vp8": - return GetHwDecoderName(encodingOptions, "vp8_qsv", "vp8", isColorDepth10); - case "vp9": - return GetHwDecoderName(encodingOptions, "vp9_qsv", "vp9", isColorDepth10); - } - } - else if (string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) - { - switch (videoStream.Codec.ToLowerInvariant()) - { - case "avc": - case "h264": - return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported() - ? GetHwaccelType(state, encodingOptions, "h264", isColorDepth10) - : GetHwDecoderName(encodingOptions, "h264_cuvid", "h264", isColorDepth10); - case "hevc": - case "h265": - return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported() - ? GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10) - : GetHwDecoderName(encodingOptions, "hevc_cuvid", "hevc", isColorDepth10); - case "mpeg2video": - return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported() - ? GetHwaccelType(state, encodingOptions, "mpeg2video", isColorDepth10) - : GetHwDecoderName(encodingOptions, "mpeg2_cuvid", "mpeg2video", isColorDepth10); - case "vc1": - return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported() - ? GetHwaccelType(state, encodingOptions, "vc1", isColorDepth10) - : GetHwDecoderName(encodingOptions, "vc1_cuvid", "vc1", isColorDepth10); - case "mpeg4": - return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported() - ? GetHwaccelType(state, encodingOptions, "mpeg4", isColorDepth10) - : GetHwDecoderName(encodingOptions, "mpeg4_cuvid", "mpeg4", isColorDepth10); - case "vp8": - return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported() - ? GetHwaccelType(state, encodingOptions, "vp8", isColorDepth10) - : GetHwDecoderName(encodingOptions, "vp8_cuvid", "vp8", isColorDepth10); - case "vp9": - return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported() - ? GetHwaccelType(state, encodingOptions, "vp9", isColorDepth10) - : GetHwDecoderName(encodingOptions, "vp9_cuvid", "vp9", isColorDepth10); - } - } - else if (string.Equals(encodingOptions.HardwareAccelerationType, "mediacodec", StringComparison.OrdinalIgnoreCase)) - { - switch (videoStream.Codec.ToLowerInvariant()) - { - case "avc": - case "h264": - return GetHwDecoderName(encodingOptions, "h264_mediacodec", "h264", isColorDepth10); - case "hevc": - case "h265": - return GetHwDecoderName(encodingOptions, "hevc_mediacodec", "hevc", isColorDepth10); - case "mpeg2video": - return GetHwDecoderName(encodingOptions, "mpeg2_mediacodec", "mpeg2video", isColorDepth10); - case "mpeg4": - return GetHwDecoderName(encodingOptions, "mpeg4_mediacodec", "mpeg4", isColorDepth10); - case "vp8": - return GetHwDecoderName(encodingOptions, "vp8_mediacodec", "vp8", isColorDepth10); - case "vp9": - return GetHwDecoderName(encodingOptions, "vp9_mediacodec", "vp9", isColorDepth10); - } - } - else if (string.Equals(encodingOptions.HardwareAccelerationType, "omx", StringComparison.OrdinalIgnoreCase)) - { - switch (videoStream.Codec.ToLowerInvariant()) - { - case "avc": - case "h264": - return GetHwDecoderName(encodingOptions, "h264_mmal", "h264", isColorDepth10); - case "mpeg2video": - return GetHwDecoderName(encodingOptions, "mpeg2_mmal", "mpeg2video", isColorDepth10); - case "mpeg4": - return GetHwDecoderName(encodingOptions, "mpeg4_mmal", "mpeg4", isColorDepth10); - case "vc1": - return GetHwDecoderName(encodingOptions, "vc1_mmal", "vc1", isColorDepth10); - } - } - else if (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)) - { - switch (videoStream.Codec.ToLowerInvariant()) - { - case "avc": - case "h264": - return GetHwaccelType(state, encodingOptions, "h264", isColorDepth10); - case "hevc": - case "h265": - return GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10); - case "mpeg2video": - return GetHwaccelType(state, encodingOptions, "mpeg2video", isColorDepth10); - case "vc1": - return GetHwaccelType(state, encodingOptions, "vc1", isColorDepth10); - case "mpeg4": - return GetHwaccelType(state, encodingOptions, "mpeg4", isColorDepth10); - case "vp9": - return GetHwaccelType(state, encodingOptions, "vp9", isColorDepth10); - } - } - else if (string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) - { - switch (videoStream.Codec.ToLowerInvariant()) - { - case "avc": - case "h264": - return GetHwaccelType(state, encodingOptions, "h264", isColorDepth10); - case "hevc": - case "h265": - return GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10); - case "mpeg2video": - return GetHwaccelType(state, encodingOptions, "mpeg2video", isColorDepth10); - case "vc1": - return GetHwaccelType(state, encodingOptions, "vc1", isColorDepth10); - case "vp8": - return GetHwaccelType(state, encodingOptions, "vp8", isColorDepth10); - case "vp9": - return GetHwaccelType(state, encodingOptions, "vp9", isColorDepth10); - } - } - else if (string.Equals(encodingOptions.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) - { - switch (videoStream.Codec.ToLowerInvariant()) - { - case "avc": - case "h264": - return GetHwDecoderName(encodingOptions, "h264_opencl", "h264", isColorDepth10); - case "hevc": - case "h265": - return GetHwDecoderName(encodingOptions, "hevc_opencl", "hevc", isColorDepth10); - case "mpeg2video": - return GetHwDecoderName(encodingOptions, "mpeg2_opencl", "mpeg2video", isColorDepth10); - case "mpeg4": - return GetHwDecoderName(encodingOptions, "mpeg4_opencl", "mpeg4", isColorDepth10); - case "vc1": - return GetHwDecoderName(encodingOptions, "vc1_opencl", "vc1", isColorDepth10); - case "vp8": - return GetHwDecoderName(encodingOptions, "vp8_opencl", "vp8", isColorDepth10); - case "vp9": - return GetHwDecoderName(encodingOptions, "vp9_opencl", "vp9", isColorDepth10); - } - } - } - - var whichCodec = videoStream.Codec?.ToLowerInvariant(); - switch (whichCodec) - { - case "avc": - whichCodec = "h264"; - break; - case "h265": - whichCodec = "hevc"; - break; - } - - // Avoid a second attempt if no hardware acceleration is being used - encodingOptions.HardwareDecodingCodecs = encodingOptions.HardwareDecodingCodecs.Where(val => val != whichCodec).ToArray(); - - // leave blank so ffmpeg will decide - return null; - } - - /// - /// Gets a hw decoder name. - /// - /// Encoding options. - /// Decoder to use. - /// Video codec to use. - /// Specifies if color depth 10. - /// Hardware decoder name. - public string GetHwDecoderName(EncodingOptions options, string decoder, string videoCodec, bool isColorDepth10) - { - var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoder) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase); - if (isColorDepth10 && isCodecAvailable) - { - if ((options.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Hevc) - || (options.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Vp9)) - { - return null; - } - } - - return isCodecAvailable ? ("-c:v " + decoder) : null; - } - - /// - /// Gets a hwaccel type to use as a hardware decoder(dxva/vaapi) depending on the system. - /// - /// Encoding state. - /// Encoding options. - /// Video codec to use. - /// Specifies if color depth 10. - /// Hardware accelerator type. - public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec, bool isColorDepth10) - { - var isWindows = OperatingSystem.IsWindows(); - var isLinux = OperatingSystem.IsLinux(); - var isWindows8orLater = Environment.OSVersion.Version.Major > 6 || (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1); - var isDxvaSupported = _mediaEncoder.SupportsHwaccel("dxva2") || _mediaEncoder.SupportsHwaccel("d3d11va"); - var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase); - - if (isColorDepth10 && isCodecAvailable) - { - if ((options.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Hevc) - || (options.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Vp9)) - { - return null; - } - } - - if (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)) - { - // Currently there is no AMF decoder on Linux, only have h264 encoder. - if (isDxvaSupported && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase)) - { - if (isWindows && isWindows8orLater) - { - return "-hwaccel d3d11va"; - } - - if (isWindows && !isWindows8orLater) - { - return "-hwaccel dxva2"; - } - } - } - - if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) - || (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) - && IsVppTonemappingSupported(state, options))) - { - if (IsVaapiSupported(state) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase)) - { - if (isLinux) - { - return "-hwaccel vaapi"; - } - } - } - - if (string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) - { - if (options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase)) - { - return "-hwaccel cuda"; - } - } - - return null; - } - public string GetSubtitleEmbedArguments(EncodingJobInfo state) { if (state.SubtitleStream == null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed) @@ -4037,25 +5206,13 @@ namespace MediaBrowser.Controller.MediaEncoding var hasCopyTs = false; - // Add resolution params, if specified - if (!hasGraphicalSubs) - { - var outputSizeParam = GetOutputSizeParam(state, encodingOptions, videoCodec); - - args += outputSizeParam; - - hasCopyTs = outputSizeParam.IndexOf("copyts", StringComparison.OrdinalIgnoreCase) != -1; - } + // video processing filters. + var videoProcessParam = GetVideoProcessingFilterParam(state, encodingOptions, videoCodec); - // This is for graphical subs - if (hasGraphicalSubs) - { - var graphicalSubtitleParam = GetGraphicalSubtitleParam(state, encodingOptions, videoCodec); + args += videoProcessParam; - args += graphicalSubtitleParam; + hasCopyTs = videoProcessParam.IndexOf("copyts", StringComparison.OrdinalIgnoreCase) != -1; - hasCopyTs = graphicalSubtitleParam.IndexOf("copyts", StringComparison.OrdinalIgnoreCase) != -1; - } if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps) { @@ -4180,41 +5337,5 @@ namespace MediaBrowser.Controller.MediaEncoding { return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase); } - - public static bool IsColorDepth10(EncodingJobInfo state) - { - var result = false; - var videoStream = state.VideoStream; - - if (videoStream != null) - { - if (videoStream.BitDepth.HasValue) - { - return videoStream.BitDepth.Value == 10; - } - - if (!string.IsNullOrEmpty(videoStream.PixelFormat)) - { - result = videoStream.PixelFormat.Contains("p10", StringComparison.OrdinalIgnoreCase); - if (result) - { - return true; - } - } - - if (!string.IsNullOrEmpty(videoStream.Profile)) - { - result = videoStream.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase) - || videoStream.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase) - || videoStream.Profile.Contains("Profile 2", StringComparison.OrdinalIgnoreCase); - if (result) - { - return true; - } - } - } - - return result; - } } } diff --git a/MediaBrowser.Controller/MediaEncoding/FilterOptionType.cs b/MediaBrowser.Controller/MediaEncoding/FilterOptionType.cs index 7ce707b19..a4869cb67 100644 --- a/MediaBrowser.Controller/MediaEncoding/FilterOptionType.cs +++ b/MediaBrowser.Controller/MediaEncoding/FilterOptionType.cs @@ -18,6 +18,16 @@ namespace MediaBrowser.Controller.MediaEncoding /// /// The tonemap_opencl_bt2390. /// - TonemapOpenclBt2390 = 2 + TonemapOpenclBt2390 = 2, + + /// + /// The overlay_opencl_framesync. + /// + OverlayOpenclFrameSync = 3, + + /// + /// The overlay_vaapi_framesync. + /// + OverlayVaapiFrameSync = 4 } } diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 1418e583e..6a7f38c0e 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -60,6 +60,24 @@ namespace MediaBrowser.Controller.MediaEncoding /// true if the filter is supported, false otherwise. bool SupportsFilterWithOption(FilterOptionType option); + /// + /// Whether the configured Vaapi device is from AMD(radeonsi/r600 Mesa driver). + /// + /// true if the Vaapi device is an AMD(radeonsi/r600 Mesa driver) GPU, false otherwise. + bool IsVaapiDeviceAmd(); + + /// + /// Whether the configured Vaapi device is from Intel(iHD driver). + /// + /// true if the Vaapi device is an Intel(iHD driver) GPU, false otherwise. + bool IsVaapiDeviceInteliHD(); + + /// + /// Whether the configured Vaapi device is from Intel(legacy i965 driver). + /// + /// true if the Vaapi device is an Intel(legacy i965 driver) GPU, false otherwise. + bool IsVaapiDeviceInteli965(); + /// /// Get the version of media encoder. /// diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index 60a2d39e5..871e7d57d 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -16,6 +16,12 @@ namespace MediaBrowser.MediaEncoding.Encoder { "h264", "hevc", + "vp8", + "libvpx", + "vp9", + "libvpx-vp9", + "av1", + "libdav1d", "mpeg2video", "mpeg4", "msmpeg4", @@ -30,6 +36,7 @@ namespace MediaBrowser.MediaEncoding.Encoder "vc1_qsv", "vp8_qsv", "vp9_qsv", + "av1_qsv", "h264_cuvid", "hevc_cuvid", "mpeg2_cuvid", @@ -37,16 +44,11 @@ namespace MediaBrowser.MediaEncoding.Encoder "mpeg4_cuvid", "vp8_cuvid", "vp9_cuvid", + "av1_cuvid", "h264_mmal", "mpeg2_mmal", "mpeg4_mmal", "vc1_mmal", - "h264_mediacodec", - "hevc_mediacodec", - "mpeg2_mediacodec", - "mpeg4_mediacodec", - "vp8_mediacodec", - "vp9_mediacodec", "h264_opencl", "hevc_opencl", "mpeg2_opencl", @@ -89,20 +91,39 @@ namespace MediaBrowser.MediaEncoding.Encoder private static readonly string[] _requiredFilters = new[] { + // sw + "alphasrc", + "zscale", + // qsv + "scale_qsv", + "vpp_qsv", + "deinterlace_qsv", + "overlay_qsv", + // cuda "scale_cuda", "yadif_cuda", - "hwupload_cuda", - "overlay_cuda", "tonemap_cuda", + "overlay_cuda", + "hwupload_cuda", + // opencl + "scale_opencl", "tonemap_opencl", + "overlay_opencl", + // vaapi + "scale_vaapi", + "deinterlace_vaapi", "tonemap_vaapi", + "overlay_vaapi", + "hwupload_vaapi" }; private static readonly IReadOnlyDictionary _filterOptionsDict = new Dictionary { { 0, new string[] { "scale_cuda", "Output format (default \"same\")" } }, { 1, new string[] { "tonemap_cuda", "GPU accelerated HDR to SDR tonemapping" } }, - { 2, new string[] { "tonemap_opencl", "bt2390" } } + { 2, new string[] { "tonemap_opencl", "bt2390" } }, + { 3, new string[] { "overlay_opencl", "Action to take when encountering EOF from secondary input" } }, + { 4, new string[] { "overlay_vaapi", "Action to take when encountering EOF from secondary input" } } }; // These are the library versions that corresponds to our minimum ffmpeg version 4.x according to the version table below @@ -144,7 +165,7 @@ namespace MediaBrowser.MediaEncoding.Encoder string output; try { - output = GetProcessOutput(_encoderPath, "-version"); + output = GetProcessOutput(_encoderPath, "-version", false); } catch (Exception ex) { @@ -225,7 +246,7 @@ namespace MediaBrowser.MediaEncoding.Encoder string output; try { - output = GetProcessOutput(_encoderPath, "-version"); + output = GetProcessOutput(_encoderPath, "-version", false); } catch (Exception ex) { @@ -318,12 +339,38 @@ namespace MediaBrowser.MediaEncoding.Encoder return map; } + public bool CheckVaapiDeviceByDriverName(string driverName, string renderNodePath) + { + if (!OperatingSystem.IsLinux()) + { + return false; + } + + if (string.IsNullOrEmpty(driverName) || string.IsNullOrEmpty(renderNodePath)) + { + return false; + } + + string output; + try + { + output = GetProcessOutput(_encoderPath, "-v verbose -hide_banner -init_hw_device vaapi=va:" + renderNodePath, true); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error detecting the given vaapi render node path"); + return false; + } + + return output.Contains(driverName, StringComparison.Ordinal); + } + private IEnumerable GetHwaccelTypes() { string? output = null; try { - output = GetProcessOutput(_encoderPath, "-hwaccels"); + output = GetProcessOutput(_encoderPath, "-hwaccels", false); } catch (Exception ex) { @@ -351,7 +398,7 @@ namespace MediaBrowser.MediaEncoding.Encoder string output; try { - output = GetProcessOutput(_encoderPath, "-h filter=" + filter); + output = GetProcessOutput(_encoderPath, "-h filter=" + filter, false); } catch (Exception ex) { @@ -375,7 +422,7 @@ namespace MediaBrowser.MediaEncoding.Encoder string output; try { - output = GetProcessOutput(_encoderPath, "-" + codecstr); + output = GetProcessOutput(_encoderPath, "-" + codecstr, false); } catch (Exception ex) { @@ -406,7 +453,7 @@ namespace MediaBrowser.MediaEncoding.Encoder string output; try { - output = GetProcessOutput(_encoderPath, "-filters"); + output = GetProcessOutput(_encoderPath, "-filters", false); } catch (Exception ex) { @@ -444,7 +491,7 @@ namespace MediaBrowser.MediaEncoding.Encoder return dict; } - private string GetProcessOutput(string path, string arguments) + private string GetProcessOutput(string path, string arguments, bool readStdErr) { using (var process = new Process() { @@ -455,7 +502,6 @@ namespace MediaBrowser.MediaEncoding.Encoder WindowStyle = ProcessWindowStyle.Hidden, ErrorDialog = false, RedirectStandardOutput = true, - // ffmpeg uses stderr to log info, don't show this RedirectStandardError = true } }) @@ -464,7 +510,7 @@ namespace MediaBrowser.MediaEncoding.Encoder process.Start(); - return process.StandardOutput.ReadToEnd(); + return readStdErr ? process.StandardError.ReadToEnd() : process.StandardOutput.ReadToEnd(); } } } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 1c97a1982..7b7bb8100 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -65,6 +65,10 @@ namespace MediaBrowser.MediaEncoding.Encoder private List _filters = new List(); private IDictionary _filtersWithOption = new Dictionary(); + private bool _isVaapiDeviceAmd = false; + private bool _isVaapiDeviceInteliHD = false; + private bool _isVaapiDeviceInteli965 = false; + private Version _ffmpegVersion = null; private string _ffmpegPath = string.Empty; private string _ffprobePath; @@ -114,9 +118,9 @@ namespace MediaBrowser.MediaEncoding.Encoder } // Write the FFmpeg path to the config/encoding.xml file as so it appears in UI - var config = _configurationManager.GetEncodingOptions(); - config.EncoderAppPathDisplay = _ffmpegPath ?? string.Empty; - _configurationManager.SaveConfiguration("encoding", config); + var options = _configurationManager.GetEncodingOptions(); + options.EncoderAppPathDisplay = _ffmpegPath ?? string.Empty; + _configurationManager.SaveConfiguration("encoding", options); // Only if mpeg path is set, try and set path to probe if (_ffmpegPath != null) @@ -134,7 +138,31 @@ namespace MediaBrowser.MediaEncoding.Encoder SetAvailableHwaccels(validator.GetHwaccels()); SetMediaEncoderVersion(validator); - _threads = EncodingHelper.GetNumberOfThreads(null, _configurationManager.GetEncodingOptions(), null); + options = _configurationManager.GetEncodingOptions(); + _threads = EncodingHelper.GetNumberOfThreads(null, options, null); + + // Check the Vaapi device vendor + if (OperatingSystem.IsLinux() + && SupportsHwaccel("vaapi") + && !string.IsNullOrEmpty(options.VaapiDevice) + && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) + { + _isVaapiDeviceAmd = validator.CheckVaapiDeviceByDriverName("Mesa Gallium driver", options.VaapiDevice); + _isVaapiDeviceInteliHD = validator.CheckVaapiDeviceByDriverName("Intel iHD driver", options.VaapiDevice); + _isVaapiDeviceInteli965 = validator.CheckVaapiDeviceByDriverName("Intel i965 driver", options.VaapiDevice); + if (_isVaapiDeviceAmd) + { + _logger.LogInformation("VAAPI device {RenderNodePath} is AMD GPU", options.VaapiDevice); + } + else if (_isVaapiDeviceInteliHD) + { + _logger.LogInformation("VAAPI device {RenderNodePath} is Intel GPU (iHD)", options.VaapiDevice); + } + else if (_isVaapiDeviceInteli965) + { + _logger.LogInformation("VAAPI device {RenderNodePath} is Intel GPU (i965)", options.VaapiDevice); + } + } } _logger.LogInformation("FFmpeg: {FfmpegPath}", _ffmpegPath ?? string.Empty); @@ -301,6 +329,21 @@ namespace MediaBrowser.MediaEncoding.Encoder return false; } + public bool IsVaapiDeviceAmd() + { + return _isVaapiDeviceAmd; + } + + public bool IsVaapiDeviceInteliHD() + { + return _isVaapiDeviceInteliHD; + } + + public bool IsVaapiDeviceInteli965() + { + return _isVaapiDeviceInteli965; + } + public Version GetMediaEncoderVersion() { return _ffmpegVersion; diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 32ff1dee6..24577e499 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -764,18 +764,23 @@ namespace MediaBrowser.MediaEncoding.Probing if (!stream.BitDepth.HasValue) { - if (!string.IsNullOrEmpty(streamInfo.PixelFormat) - && streamInfo.PixelFormat.Contains("p10", StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrEmpty(streamInfo.PixelFormat)) { - stream.BitDepth = 10; - } - - if (!string.IsNullOrEmpty(streamInfo.Profile) - && (streamInfo.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase) - || streamInfo.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase) - || streamInfo.Profile.Contains("Profile 2", StringComparison.OrdinalIgnoreCase))) - { - stream.BitDepth = 10; + if (string.Equals(streamInfo.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase) + || string.Equals(streamInfo.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase)) + { + stream.BitDepth = 8; + } + else if (string.Equals(streamInfo.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase) + || string.Equals(streamInfo.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase)) + { + stream.BitDepth = 10; + } + else if (string.Equals(streamInfo.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase) + || string.Equals(streamInfo.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase)) + { + stream.BitDepth = 12; + } } } diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index 365bbeef6..ea7b24347 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -16,9 +16,6 @@ namespace MediaBrowser.Model.Configuration // This is a DRM device that is almost guaranteed to be there on every intel platform, // plus it's the default one in ffmpeg if you don't specify anything VaapiDevice = "/dev/dri/renderD128"; - // This is the OpenCL device that is used for tonemapping. - // The left side of the dot is the platform number, and the right side is the device number on the platform. - OpenclDevice = "0.0"; EnableTonemapping = false; EnableVppTonemapping = false; TonemappingAlgorithm = "hable"; @@ -34,6 +31,9 @@ namespace MediaBrowser.Model.Configuration EnableDecodingColorDepth10Hevc = true; EnableDecodingColorDepth10Vp9 = true; EnableEnhancedNvdecDecoder = true; + PreferSystemNativeHwDecoder = true; + EnableIntelLowPowerH264HwEncoder = false; + EnableIntelLowPowerHevcHwEncoder = false; EnableHardwareEncoding = true; AllowHevcEncoding = false; EnableSubtitleExtraction = true; @@ -70,8 +70,6 @@ namespace MediaBrowser.Model.Configuration public string VaapiDevice { get; set; } - public string OpenclDevice { get; set; } - public bool EnableTonemapping { get; set; } public bool EnableVppTonemapping { get; set; } @@ -104,6 +102,12 @@ namespace MediaBrowser.Model.Configuration public bool EnableEnhancedNvdecDecoder { get; set; } + public bool PreferSystemNativeHwDecoder { get; set; } + + public bool EnableIntelLowPowerH264HwEncoder { get; set; } + + public bool EnableIntelLowPowerHevcHwEncoder { get; set; } + public bool EnableHardwareEncoding { get; set; } public bool AllowHevcEncoding { get; set; } -- cgit v1.2.3 From b2d85a02c21fb22c3a164a6d8e23c2b5beacb324 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Fri, 24 Dec 2021 16:50:40 +0800 Subject: Apply suggestions from code review Co-authored-by: Cody Robibero Co-authored-by: Claus Vium Co-authored-by: Bond_009 --- .../MediaEncoding/EncodingHelper.cs | 1063 ++++++++++---------- .../MediaEncoding/EncodingJobInfo.cs | 18 +- .../MediaEncoding/IMediaEncoder.cs | 48 +- .../Encoder/EncoderValidator.cs | 6 +- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 25 +- .../Probing/ProbeResultNormalizer.cs | 4 +- .../Configuration/EncodingOptions.cs | 2 +- 7 files changed, 575 insertions(+), 591 deletions(-) (limited to 'MediaBrowser.MediaEncoding/Encoder') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 75a36d815..1da578462 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -42,12 +42,12 @@ namespace MediaBrowser.Controller.MediaEncoding "Main10" }; - private static readonly string _qsvAlias = "qs"; - private static readonly string _vaapiAlias = "va"; - private static readonly string _d3d11vaAlias = "dx11"; - private static readonly string _videotoolboxAlias = "vt"; - private static readonly string _openclAlias = "ocl"; - private static readonly string _cudaAlias = "cu"; + private const string QsvAlias = "qs"; + private const string VaapiAlias = "va"; + private const string D3d11vaAlias = "dx11"; + private const string VideotoolboxAlias = "vt"; + private const string OpenclAlias = "ocl"; + private const string CudaAlias = "cu"; public EncodingHelper( IMediaEncoder mediaEncoder, @@ -520,17 +520,17 @@ namespace MediaBrowser.Controller.MediaEncoding return codec.ToLowerInvariant(); } - public string GetVideoToolboxDeviceArgs(string alias) + private string GetVideoToolboxDeviceArgs(string alias) { - alias ??= _videotoolboxAlias; + alias ??= VideotoolboxAlias; // device selection in vt is not supported. return " -init_hw_device videotoolbox=" + alias; } - public string GetCudaDeviceArgs(int deviceIndex, string alias) + private string GetCudaDeviceArgs(int deviceIndex, string alias) { - alias ??= _cudaAlias; + alias ??= CudaAlias; deviceIndex = deviceIndex >= 0 ? deviceIndex : 0; @@ -538,89 +538,83 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Format( CultureInfo.InvariantCulture, " -init_hw_device cuda={0}:{1}", - alias, deviceIndex); + alias, + deviceIndex); } - public string GetOpenclDeviceArgs(int deviceIndex, string deviceVendorName, string srcDeviceAlias, string alias) + private string GetOpenclDeviceArgs(int deviceIndex, string deviceVendorName, string srcDeviceAlias, string alias) { - alias ??= _openclAlias; + alias ??= OpenclAlias; deviceIndex = deviceIndex >= 0 ? deviceIndex : 0; - var vendorOpts = !string.IsNullOrEmpty(deviceVendorName) - ? (":." + deviceIndex + ",device_vendor=\"" + deviceVendorName + "\"") - : ":0.0"; - var options = !string.IsNullOrEmpty(srcDeviceAlias) - ? ("@" + srcDeviceAlias) - : vendorOpts; + var vendorOpts = string.IsNullOrEmpty(deviceVendorName) + ? ":0.0" + : ":." + deviceIndex + ",device_vendor=\"" + deviceVendorName + "\""; + var options = string.IsNullOrEmpty(srcDeviceAlias) + ? vendorOpts + : "@" + srcDeviceAlias; return string.Format( CultureInfo.InvariantCulture, " -init_hw_device opencl={0}{1}", - alias, options); + alias, + options); } - public string GetD3d11vaDeviceArgs(int deviceIndex, string deviceVendorId, string alias) + private string GetD3d11vaDeviceArgs(int deviceIndex, string deviceVendorId, string alias) { - alias ??= _d3d11vaAlias; + alias ??= D3d11vaAlias; deviceIndex = deviceIndex >= 0 ? deviceIndex : 0; - var options = !string.IsNullOrEmpty(deviceVendorId) - ? (",vendor=" + deviceVendorId) - : Convert.ToString(deviceIndex, CultureInfo.InvariantCulture); + var options = string.IsNullOrEmpty(deviceVendorId) + ? deviceIndex.ToString(CultureInfo.InvariantCulture) + : ",vendor=" + deviceVendorId; return string.Format( CultureInfo.InvariantCulture, " -init_hw_device d3d11va={0}:{1}", - alias, options); + alias, + options); } - public string GetVaapiDeviceArgs(string renderNodePath, string kernelDriver, string driver, string alias) + private string GetVaapiDeviceArgs(string renderNodePath, string kernelDriver, string driver, string alias) { - alias ??= _vaapiAlias; + alias ??= VaapiAlias; renderNodePath = renderNodePath ?? "/dev/dri/renderD128"; - var options = (!string.IsNullOrEmpty(kernelDriver) && !string.IsNullOrEmpty(driver)) - ? (",kernel_driver=" + kernelDriver + ",driver=" + driver) - : renderNodePath; + var options = string.IsNullOrEmpty(kernelDriver) || string.IsNullOrEmpty(driver) + ? renderNodePath + : ",kernel_driver=" + kernelDriver + ",driver=" + driver; return string.Format( CultureInfo.InvariantCulture, " -init_hw_device vaapi={0}:{1}", - alias, options); + alias, + options); } - public string GetQsvDeviceArgs(string alias) + private string GetQsvDeviceArgs(string alias) { - var arg = " -init_hw_device qsv=" + (alias ?? _qsvAlias); - var args = new StringBuilder(); - var isWindows = OperatingSystem.IsWindows(); - var isLinux = OperatingSystem.IsLinux(); - if (isLinux) + var arg = " -init_hw_device qsv=" + (alias ?? QsvAlias); + if (OperatingSystem.IsLinux()) { // derive qsv from vaapi device - string srcAlias = _vaapiAlias; - args.Append(GetVaapiDeviceArgs(null, "i915", "iHD", srcAlias)) - .Append(arg + "@" + srcAlias); + return GetVaapiDeviceArgs(null, "i915", "iHD", VaapiAlias) + arg + "@" + VaapiAlias; } - else if (isWindows) + + if (OperatingSystem.IsWindows()) { // derive qsv from d3d11va device - string srcAlias = _d3d11vaAlias; - args.Append(GetD3d11vaDeviceArgs(0, "0x8086", srcAlias)) - .Append(arg + "@" + srcAlias); - } - else - { - return null; + return GetD3d11vaDeviceArgs(0, "0x8086", D3d11vaAlias) + arg + "@" + D3d11vaAlias; } - return args.ToString(); + return null; } - public string GetFilterHwDeviceArgs(string alias) + private string GetFilterHwDeviceArgs(string alias) { - return !string.IsNullOrEmpty(alias) - ? (" -filter_hw_device " + alias) - : string.Empty; + return string.IsNullOrEmpty(alias) + ? string.Empty + : " -filter_hw_device " + alias; } public string GetGraphicalSubCanvasSize(EncodingJobInfo state) @@ -636,7 +630,7 @@ namespace MediaBrowser.Controller.MediaEncoding var reqMaxW = state.BaseRequest.MaxWidth; var reqMaxH = state.BaseRequest.MaxHeight; - // setup a relative small canvas_size for overlay_qsv to reduce transfer overhead + // setup a relative small canvas_size for overlay_qsv/vaapi to reduce transfer overhead var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, 1080); if (overlayW.HasValue && overlayH.HasValue) @@ -662,13 +656,13 @@ namespace MediaBrowser.Controller.MediaEncoding { if (!state.IsVideoRequest) { - return null; + return string.Empty; } var vidEncoder = GetVideoEncoder(state, options) ?? string.Empty; if (IsCopyCodec(vidEncoder)) { - return null; + return string.Empty; } var args = new StringBuilder(); @@ -677,9 +671,6 @@ namespace MediaBrowser.Controller.MediaEncoding var isMacOS = OperatingSystem.IsMacOS(); var optHwaccelType = options.HardwareAccelerationType; var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty; -#pragma warning disable CA1508 // Defaults to string.Empty - var isSwVidDecoder = string.IsNullOrEmpty(vidDecoder); -#pragma warning restore CA1508 var isHwTonemapAvailable = IsHwTonemapAvailable(state, options); if (string.Equals(optHwaccelType, "vaapi", StringComparison.OrdinalIgnoreCase)) @@ -696,33 +687,32 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Empty; } - var vaArgs = GetVaapiDeviceArgs(options.VaapiDevice, null, null, _vaapiAlias); - var filterDevArgs = GetFilterHwDeviceArgs(_vaapiAlias); + args.Append(GetVaapiDeviceArgs(options.VaapiDevice, null, null, VaapiAlias)); + var filterDevArgs = GetFilterHwDeviceArgs(VaapiAlias); if (isHwTonemapAvailable && IsOpenclFullSupported()) { - if (_mediaEncoder.IsVaapiDeviceInteliHD() || _mediaEncoder.IsVaapiDeviceInteli965()) + if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965) { if (!isVaapiDecoder) { - vaArgs += GetOpenclDeviceArgs(0, null, _vaapiAlias, _openclAlias); - filterDevArgs = GetFilterHwDeviceArgs(_openclAlias); + args.Append(GetOpenclDeviceArgs(0, null, VaapiAlias, OpenclAlias)); + filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias); } } - else if (_mediaEncoder.IsVaapiDeviceAmd()) + else if (_mediaEncoder.IsVaapiDeviceAmd) { - vaArgs += GetOpenclDeviceArgs(0, "Advanced Micro Devices", null, _openclAlias); - filterDevArgs = GetFilterHwDeviceArgs(_openclAlias); + args.Append(GetOpenclDeviceArgs(0, "Advanced Micro Devices", null, OpenclAlias)); + filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias); } else { - vaArgs += GetOpenclDeviceArgs(0, null, null, _openclAlias); - filterDevArgs = GetFilterHwDeviceArgs(_openclAlias); + args.Append(GetOpenclDeviceArgs(0, null, null, OpenclAlias)); + filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias); } } - args.Append(vaArgs) - .Append(filterDevArgs); + args.Append(filterDevArgs); } else if (string.Equals(optHwaccelType, "qsv", StringComparison.OrdinalIgnoreCase)) { @@ -741,24 +731,23 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Empty; } - var qsvArgs = GetQsvDeviceArgs(_qsvAlias); - var filterDevArgs = GetFilterHwDeviceArgs(_qsvAlias); + args.Append(GetQsvDeviceArgs(QsvAlias)); + var filterDevArgs = GetFilterHwDeviceArgs(QsvAlias); // child device used by qsv. if (_mediaEncoder.SupportsHwaccel("vaapi") || _mediaEncoder.SupportsHwaccel("d3d11va")) { if (isHwTonemapAvailable && IsOpenclFullSupported()) { - var srcAlias = isLinux ? _vaapiAlias : _d3d11vaAlias; - qsvArgs += GetOpenclDeviceArgs(0, null, srcAlias, _openclAlias); + var srcAlias = isLinux ? VaapiAlias : D3d11vaAlias; + args.Append(GetOpenclDeviceArgs(0, null, srcAlias, OpenclAlias)); if (!isHwDecoder) { - filterDevArgs = GetFilterHwDeviceArgs(_openclAlias); + filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias); } } } - args.Append(qsvArgs) - .Append(filterDevArgs); + args.Append(filterDevArgs); } else if (string.Equals(optHwaccelType, "nvenc", StringComparison.OrdinalIgnoreCase)) { @@ -776,16 +765,12 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Empty; } - var cuArgs = GetCudaDeviceArgs(0, _cudaAlias); - var filterDevArgs = GetFilterHwDeviceArgs(_cudaAlias); + args.Append(GetCudaDeviceArgs(0, CudaAlias)) + .Append(GetFilterHwDeviceArgs(CudaAlias)); // workaround for "No decoder surfaces left" error, // but will increase vram usage. https://trac.ffmpeg.org/ticket/7562 - var extraHwFramesArgs = " -extra_hw_frames 3"; - - args.Append(cuArgs) - .Append(filterDevArgs) - .Append(extraHwFramesArgs); + args.Append(" -extra_hw_frames 3"); } else if (string.Equals(optHwaccelType, "amf", StringComparison.OrdinalIgnoreCase)) { @@ -802,16 +787,15 @@ namespace MediaBrowser.Controller.MediaEncoding } // no dxva video processor hw filter. - var dx11Args = GetD3d11vaDeviceArgs(0, "0x1002", _d3d11vaAlias); + args.Append(GetD3d11vaDeviceArgs(0, "0x1002", D3d11vaAlias)); var filterDevArgs = string.Empty; if (IsOpenclFullSupported()) { - dx11Args += GetOpenclDeviceArgs(0, null, _d3d11vaAlias, _openclAlias); - filterDevArgs = GetFilterHwDeviceArgs(_openclAlias); + args.Append(GetOpenclDeviceArgs(0, null, D3d11vaAlias, OpenclAlias)); + filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias); } - args.Append(dx11Args) - .Append(filterDevArgs); + args.Append(filterDevArgs); } else if (string.Equals(optHwaccelType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) { @@ -828,8 +812,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // no videotoolbox hw filter. - var vtArgs = GetVideoToolboxDeviceArgs(_videotoolboxAlias); - args.Append(vtArgs); + args.Append(GetVideoToolboxDeviceArgs(VideotoolboxAlias)); } if (!string.IsNullOrEmpty(vidDecoder)) @@ -1023,7 +1006,7 @@ namespace MediaBrowser.Controller.MediaEncoding || string.Equals(videoCodec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) { // VBR in i965 driver may result in pixelated output. - if (_mediaEncoder.IsVaapiDeviceInteli965()) + if (_mediaEncoder.IsVaapiDeviceInteli965) { return FormattableString.Invariant($" -rc_mode CBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}"); } @@ -1081,8 +1064,8 @@ namespace MediaBrowser.Controller.MediaEncoding ? string.Empty : string.Format(CultureInfo.InvariantCulture, ",setpts=PTS -{0}/TB", seconds); - var alphaParam = enableAlpha ? (":alpha=" + Convert.ToInt32(enableAlpha)) : string.Empty; - var sub2videoParam = enableSub2video ? (":sub2video=" + Convert.ToInt32(enableSub2video)) : string.Empty; + var alphaParam = enableAlpha ? ":alpha=1" : string.Empty; + var sub2videoParam = enableSub2video ? ":sub2video=1" : string.Empty; // TODO // var fallbackFontPath = Path.Combine(_appPaths.ProgramDataPath, "fonts", "DroidSansFallback.ttf"); @@ -1257,7 +1240,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) { - var isIntelVaapiDriver = _mediaEncoder.IsVaapiDeviceInteliHD() || _mediaEncoder.IsVaapiDeviceInteli965(); + var isIntelVaapiDriver = _mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965; if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) { @@ -1290,7 +1273,7 @@ namespace MediaBrowser.Controller.MediaEncoding param += " -pix_fmt nv21"; } - var isVc1 = string.Equals(state.VideoStream.Codec ?? string.Empty, "vc1", StringComparison.OrdinalIgnoreCase); + var isVc1 = string.Equals(state.VideoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase); var isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase); if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || isLibX265) @@ -1609,14 +1592,12 @@ namespace MediaBrowser.Controller.MediaEncoding param += " -level " + level; } else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) + || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) { // level option may cause NVENC to fail. // NVENC cannot adjust the given level, just throw an error. - } - else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) - { // level option may cause corrupted frames on AMD VAAPI. } else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) @@ -2308,24 +2289,28 @@ namespace MediaBrowser.Controller.MediaEncoding return (null, null); } - decimal inputWidth = Convert.ToDecimal(videoWidth ?? requestedWidth, CultureInfo.InvariantCulture); - decimal inputHeight = Convert.ToDecimal(videoHeight ?? requestedHeight, CultureInfo.InvariantCulture); - decimal outputWidth = requestedWidth.HasValue ? Convert.ToDecimal(requestedWidth.Value) : inputWidth; - decimal outputHeight = requestedHeight.HasValue ? Convert.ToDecimal(requestedHeight.Value) : inputHeight; - decimal maximumWidth = requestedMaxWidth.HasValue ? Convert.ToDecimal(requestedMaxWidth.Value) : outputWidth; - decimal maximumHeight = requestedMaxHeight.HasValue ? Convert.ToDecimal(requestedMaxHeight.Value) : outputHeight; + int inputWidth = Convert.ToInt32(videoWidth ?? requestedWidth, CultureInfo.InvariantCulture); + int inputHeight = Convert.ToInt32(videoHeight ?? requestedHeight, CultureInfo.InvariantCulture); + int outputWidth = requestedWidth ?? inputWidth; + int outputHeight = requestedHeight ?? inputHeight; + + // Don't transcode video to bigger than 4k when using HW. + int maximumWidth = Math.Min(requestedMaxWidth ?? outputWidth, 4096); + int maximumHeight = Math.Min(requestedMaxHeight ?? outputHeight, 4096); if (outputWidth > maximumWidth || outputHeight > maximumHeight) { - var scale = Math.Min(maximumWidth / outputWidth, maximumHeight / outputHeight); - outputWidth = Math.Min(maximumWidth, Math.Truncate(outputWidth * scale)); - outputHeight = Math.Min(maximumHeight, Math.Truncate(outputHeight * scale)); + var scaleW = (double)maximumWidth / (double)outputWidth; + var scaleH = (double)maximumHeight / (double)outputHeight; + var scale = Math.Min(scaleW, scaleH); + outputWidth = Math.Min(maximumWidth, (int)(outputWidth * scale)); + outputHeight = Math.Min(maximumHeight, (int)(outputHeight * scale)); } - outputWidth = 2 * Math.Truncate(outputWidth / 2); - outputHeight = 2 * Math.Truncate(outputHeight / 2); + outputWidth = 2 * (outputWidth / 2); + outputHeight = 2 * (outputHeight / 2); - return (Convert.ToInt32(outputWidth), Convert.ToInt32(outputHeight)); + return (outputWidth, outputHeight); } public static string GetHwScaleFilter( @@ -2338,9 +2323,14 @@ namespace MediaBrowser.Controller.MediaEncoding int? requestedMaxWidth, int? requestedMaxHeight) { - var (outWidth, outHeight) = GetFixedOutputSize(videoWidth, videoHeight, - requestedWidth, requestedHeight, - requestedMaxWidth, requestedMaxHeight); + var (outWidth, outHeight) = GetFixedOutputSize( + videoWidth, + videoHeight, + requestedWidth, + requestedHeight, + requestedMaxWidth, + requestedMaxHeight); + var isFormatFixed = !string.IsNullOrEmpty(videoFormat); var isSizeFixed = !videoWidth.HasValue || outWidth.Value != videoWidth.Value @@ -2375,9 +2365,14 @@ namespace MediaBrowser.Controller.MediaEncoding int? requestedMaxWidth, int? requestedMaxHeight) { - var (outWidth, outHeight) = GetFixedOutputSize(videoWidth, videoHeight, - requestedWidth, requestedHeight, - requestedMaxWidth, requestedMaxHeight); + var (outWidth, outHeight) = GetFixedOutputSize( + videoWidth, + videoHeight, + requestedWidth, + requestedHeight, + requestedMaxWidth, + requestedMaxHeight); + if (outWidth.HasValue && outHeight.HasValue) { return string.Format( @@ -2402,9 +2397,14 @@ namespace MediaBrowser.Controller.MediaEncoding { var reqTicks = state.BaseRequest.StartTimeTicks ?? 0; var startTime = TimeSpan.FromTicks(reqTicks).ToString(@"hh\\\:mm\\\:ss\\\.fff", CultureInfo.InvariantCulture); - var (outWidth, outHeight) = GetFixedOutputSize(videoWidth, videoHeight, - requestedWidth, requestedHeight, - requestedMaxWidth, requestedMaxHeight); + var (outWidth, outHeight) = GetFixedOutputSize( + videoWidth, + videoHeight, + requestedWidth, + requestedHeight, + requestedMaxWidth, + requestedMaxHeight); + if (outWidth.HasValue && outHeight.HasValue) { return string.Format( @@ -2419,7 +2419,7 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Empty; } - public static List GetSwScaleFilter( + public static string GetSwScaleFilter( EncodingJobInfo state, EncodingOptions options, string videoEncoder, @@ -2431,7 +2431,6 @@ namespace MediaBrowser.Controller.MediaEncoding int? requestedMaxWidth, int? requestedMaxHeight) { - var filters = new List(); var isV4l2 = string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase); var scaleVal = isV4l2 ? 64 : 2; @@ -2443,16 +2442,15 @@ namespace MediaBrowser.Controller.MediaEncoding var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture); var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture); - filters.Add( - string.Format( + return string.Format( CultureInfo.InvariantCulture, "scale=trunc({0}/64)*64:trunc({1}/2)*2", widthParam, - heightParam)); + heightParam); } else { - filters.Add(GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, requestedHeight.Value)); + return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, requestedHeight.Value); } } @@ -2462,13 +2460,12 @@ namespace MediaBrowser.Controller.MediaEncoding var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture); var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture); - filters.Add( - string.Format( + return string.Format( CultureInfo.InvariantCulture, "scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/{2})*{2}:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", maxWidthParam, maxHeightParam, - scaleVal)); + scaleVal); } // If a fixed width was requested @@ -2477,17 +2474,16 @@ namespace MediaBrowser.Controller.MediaEncoding if (threedFormat.HasValue) { // This method can handle 0 being passed in for the requested height - filters.Add(GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, 0)); + return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, 0); } else { var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture); - filters.Add( - string.Format( + return string.Format( CultureInfo.InvariantCulture, "scale={0}:trunc(ow/a/2)*2", - widthParam)); + widthParam); } } @@ -2496,12 +2492,11 @@ namespace MediaBrowser.Controller.MediaEncoding { var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture); - filters.Add( - string.Format( + return string.Format( CultureInfo.InvariantCulture, "scale=trunc(oh*a/{1})*{1}:{0}", heightParam, - scaleVal)); + scaleVal); } // If a max width was requested @@ -2509,12 +2504,11 @@ namespace MediaBrowser.Controller.MediaEncoding { var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture); - filters.Add( - string.Format( + return string.Format( CultureInfo.InvariantCulture, "scale=trunc(min(max(iw\\,ih*dar)\\,{0})/{1})*{1}:trunc(ow/dar/2)*2", maxWidthParam, - scaleVal)); + scaleVal); } // If a max height was requested @@ -2522,15 +2516,14 @@ namespace MediaBrowser.Controller.MediaEncoding { var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture); - filters.Add( - string.Format( + return string.Format( CultureInfo.InvariantCulture, "scale=trunc(oh*a/{1})*{1}:min(max(iw/dar\\,ih)\\,{0})", maxHeightParam, - scaleVal)); + scaleVal); } - return filters; + return string.Empty; } private static string GetFixedSwScaleFilter(Video3DFormat? threedFormat, int requestedWidth, int requestedHeight) @@ -2583,21 +2576,12 @@ namespace MediaBrowser.Controller.MediaEncoding public static string GetSwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options) { - var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.AverageFrameRate ?? 60) <= 30; - if (string.Equals(options.DeinterlaceMethod, "bwdif", StringComparison.OrdinalIgnoreCase)) - { - return string.Format( - CultureInfo.InvariantCulture, - "bwdif={0}:-1:0", - doubleRateDeint ? "1" : "0"); - } - else - { - return string.Format( - CultureInfo.InvariantCulture, - "yadif={0}:-1:0", - doubleRateDeint ? "1" : "0"); - } + var doubleRateDeint = options.DeinterlaceDoubleRate && state.VideoStream?.AverageFrameRate <= 30; + return string.Format( + CultureInfo.InvariantCulture, + "{0}={1}:-1:0", + string.Equals(options.DeinterlaceMethod, "bwdif", StringComparison.OrdinalIgnoreCase) ? "bwdif" : "yadif", + doubleRateDeint ? "1" : "0"); } public static string GetHwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options, string hwDeintSuffix) @@ -2668,7 +2652,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// Encoding options. /// Video encoder to use. /// The tuple contains three lists: main, sub and overlay filters - public Tuple, List, List> GetSwVidFilterChain( + public (List, List, List) GetSwVidFilterChain( EncodingJobInfo state, EncodingOptions options, string vidEncoder) @@ -2699,23 +2683,24 @@ namespace MediaBrowser.Controller.MediaEncoding mainFilters.Add(GetOverwriteColorPropertiesParam(state, false)); // INPUT sw surface(memory/copy-back from vram) + // sw deint + if (doDeintH2645) + { + var deintFilter = GetSwDeinterlaceFilter(state, options); + mainFilters.Add(deintFilter); + } + var outFormat = isSwDecoder ? "yuv420p" : "nv12"; var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); if (isVaapiEncoder) { outFormat = "nv12"; } + // sw scale - mainFilters.AddRange(swScaleFilter); + mainFilters.Add(swScaleFilter); mainFilters.Add("format=" + outFormat); - if (doDeintH2645) - { - var deintFilter = GetSwDeinterlaceFilter(state, options); - // sw deint - mainFilters.Add(deintFilter); - } - // sw tonemap <= TODO: finsh the fast tonemap filter // OUTPUT yuv420p/nv12 surface(memory) @@ -2737,7 +2722,7 @@ namespace MediaBrowser.Controller.MediaEncoding overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0"); } - return new Tuple, List, List>(mainFilters, subFilters, overlayFilters); + return (mainFilters, subFilters, overlayFilters); } /// @@ -2747,14 +2732,14 @@ namespace MediaBrowser.Controller.MediaEncoding /// Encoding options. /// Video encoder to use. /// The tuple contains three lists: main, sub and overlay filters - public Tuple, List, List> GetNvidiaVidFilterChain( + public (List, List, List) GetNvidiaVidFilterChain( EncodingJobInfo state, EncodingOptions options, string vidEncoder) { if (!string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) { - return new Tuple, List, List>(null, null, null); + return (null, null, null); } var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty; @@ -2774,7 +2759,7 @@ namespace MediaBrowser.Controller.MediaEncoding return GetNvidiaVidFiltersPrefered(state, options, vidDecoder, vidEncoder); } - public Tuple, List, List> GetNvidiaVidFiltersPrefered( + public (List, List, List) GetNvidiaVidFiltersPrefered( EncodingJobInfo state, EncodingOptions options, string vidDecoder, @@ -2815,17 +2800,19 @@ namespace MediaBrowser.Controller.MediaEncoding if (isSwDecoder) { // INPUT sw surface(memory) - var outFormat = doCuTonemap ? "yuv420p10" : "yuv420p"; - var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); - // sw scale - mainFilters.AddRange(swScaleFilter); - mainFilters.Add("format=" + outFormat); // sw deint if (doDeintH2645) { var swDeintFilter = GetSwDeinterlaceFilter(state, options); mainFilters.Add(swDeintFilter); } + + var outFormat = doCuTonemap ? "yuv420p10le" : "yuv420p"; + var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); + // sw scale + mainFilters.Add(swScaleFilter); + mainFilters.Add("format=" + outFormat); + // sw => hw if (doCuTonemap) { @@ -2835,16 +2822,17 @@ namespace MediaBrowser.Controller.MediaEncoding if (isNvdecDecoder) { // INPUT cuda surface(vram) - var outFormat = doCuTonemap ? string.Empty : "yuv420p"; - var hwScaleFilter = GetHwScaleFilter("cuda", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH); - // hw scale - mainFilters.Add(hwScaleFilter); // hw deint if (doDeintH2645) { var deintFilter = GetHwDeinterlaceFilter(state, options, "cuda"); mainFilters.Add(deintFilter); } + + var outFormat = doCuTonemap ? string.Empty : "yuv420p"; + var hwScaleFilter = GetHwScaleFilter("cuda", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH); + // hw scale + mainFilters.Add(hwScaleFilter); } // hw tonemap @@ -2921,7 +2909,7 @@ namespace MediaBrowser.Controller.MediaEncoding } } - return new Tuple, List, List>(mainFilters, subFilters, overlayFilters); + return (mainFilters, subFilters, overlayFilters); } /// @@ -2931,14 +2919,14 @@ namespace MediaBrowser.Controller.MediaEncoding /// Encoding options. /// Video encoder to use. /// The tuple contains three lists: main, sub and overlay filters - public Tuple, List, List> GetAmdVidFilterChain( + public (List, List, List) GetAmdVidFilterChain( EncodingJobInfo state, EncodingOptions options, string vidEncoder) { if (!string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)) { - return new Tuple, List, List>(null, null, null); + return (null, null, null); } var isWindows = OperatingSystem.IsWindows(); @@ -2959,7 +2947,7 @@ namespace MediaBrowser.Controller.MediaEncoding return GetAmdDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder); } - public Tuple, List, List> GetAmdDx11VidFiltersPrefered( + public (List, List, List) GetAmdDx11VidFiltersPrefered( EncodingJobInfo state, EncodingOptions options, string vidDecoder, @@ -2999,11 +2987,6 @@ namespace MediaBrowser.Controller.MediaEncoding if (isSwDecoder) { // INPUT sw surface(memory) - var outFormat = doOclTonemap ? "yuv420p10" : "yuv420p"; - var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); - // sw scale - mainFilters.AddRange(swScaleFilter); - mainFilters.Add("format=" + outFormat); // sw deint if (doDeintH2645) { @@ -3011,6 +2994,12 @@ namespace MediaBrowser.Controller.MediaEncoding mainFilters.Add(swDeintFilter); } + var outFormat = doOclTonemap ? "yuv420p10le" : "yuv420p"; + var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); + // sw scale + mainFilters.Add(swScaleFilter); + mainFilters.Add("format=" + outFormat); + // keep video at memory except ocl tonemap, // since the overhead caused by hwupload >>> using sw filter. // sw => hw @@ -3025,12 +3014,13 @@ namespace MediaBrowser.Controller.MediaEncoding // INPUT d3d11 surface(vram) // map from d3d11va to opencl via d3d11-opencl interop. mainFilters.Add("hwmap=derive_device=opencl"); + + // hw deint <= TODO: finsh the 'yadif_opencl' filter + var outFormat = doOclTonemap ? string.Empty : "nv12"; var hwScaleFilter = GetHwScaleFilter("opencl", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH); // hw scale mainFilters.Add(hwScaleFilter); - - // hw deint <= TODO: finsh the 'yadif_opencl' filter } // hw tonemap @@ -3117,7 +3107,7 @@ namespace MediaBrowser.Controller.MediaEncoding } } - return new Tuple, List, List>(mainFilters, subFilters, overlayFilters); + return (mainFilters, subFilters, overlayFilters); } /// @@ -3127,14 +3117,14 @@ namespace MediaBrowser.Controller.MediaEncoding /// Encoding options. /// Video encoder to use. /// The tuple contains three lists: main, sub and overlay filters - public Tuple, List, List> GetIntelVidFilterChain( + public (List, List, List) GetIntelVidFilterChain( EncodingJobInfo state, EncodingOptions options, string vidEncoder) { if (!string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) { - return new Tuple, List, List>(null, null, null); + return (null, null, null); } var isWindows = OperatingSystem.IsWindows(); @@ -3170,10 +3160,10 @@ namespace MediaBrowser.Controller.MediaEncoding return GetIntelQsvDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder); } - return new Tuple, List, List>(null, null, null); + return (null, null, null); } - public Tuple, List, List> GetIntelQsvDx11VidFiltersPrefered( + public (List, List, List) GetIntelQsvDx11VidFiltersPrefered( EncodingJobInfo state, EncodingOptions options, string vidDecoder, @@ -3215,11 +3205,6 @@ namespace MediaBrowser.Controller.MediaEncoding if (isSwDecoder) { // INPUT sw surface(memory) - var outFormat = doOclTonemap ? "yuv420p10" : "yuv420p"; - var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); - // sw scale - mainFilters.AddRange(swScaleFilter); - mainFilters.Add("format=" + outFormat); // sw deint if (doDeintH2645) { @@ -3227,6 +3212,12 @@ namespace MediaBrowser.Controller.MediaEncoding mainFilters.Add(swDeintFilter); } + var outFormat = doOclTonemap ? "yuv420p10le" : "yuv420p"; + var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); + // sw scale + mainFilters.Add(swScaleFilter); + mainFilters.Add("format=" + outFormat); + // keep video at memory except ocl tonemap, // since the overhead caused by hwupload >>> using sw filter. // sw => hw @@ -3250,15 +3241,15 @@ namespace MediaBrowser.Controller.MediaEncoding } } - // hw scale - mainFilters.Add(hwScaleFilter); - // hw deint if (doDeintH2645) { var deintFilter = GetHwDeinterlaceFilter(state, options, "qsv"); mainFilters.Add(deintFilter); } + + // hw scale + mainFilters.Add(hwScaleFilter); } if (doOclTonemap && isHwDecoder) @@ -3308,7 +3299,7 @@ namespace MediaBrowser.Controller.MediaEncoding { // OUTPUT qsv(nv12) surface(vram) // reverse-mapping via qsv(d3d11)-opencl interop. - mainFilters.Add("hwmap=derive_device=qsv:reverse=1:extra_hw_frames=16"); + mainFilters.Add("hwmap=derive_device=qsv:reverse=1"); mainFilters.Add("format=qsv"); } @@ -3361,10 +3352,10 @@ namespace MediaBrowser.Controller.MediaEncoding } } - return new Tuple, List, List>(mainFilters, subFilters, overlayFilters); + return (mainFilters, subFilters, overlayFilters); } - public Tuple, List, List> GetIntelQsvVaapiVidFiltersPrefered( + public (List, List, List) GetIntelQsvVaapiVidFiltersPrefered( EncodingJobInfo state, EncodingOptions options, string vidDecoder, @@ -3408,11 +3399,6 @@ namespace MediaBrowser.Controller.MediaEncoding if (isSwDecoder) { // INPUT sw surface(memory) - var outFormat = doOclTonemap ? "yuv420p10" : "yuv420p"; - var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); - // sw scale - mainFilters.AddRange(swScaleFilter); - mainFilters.Add("format=" + outFormat); // sw deint if (doDeintH2645) { @@ -3420,6 +3406,12 @@ namespace MediaBrowser.Controller.MediaEncoding mainFilters.Add(swDeintFilter); } + var outFormat = doOclTonemap ? "yuv420p10le" : "yuv420p"; + var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); + // sw scale + mainFilters.Add(swScaleFilter); + mainFilters.Add("format=" + outFormat); + // keep video at memory except ocl tonemap, // since the overhead caused by hwupload >>> using sw filter. // sw => hw @@ -3431,28 +3423,19 @@ namespace MediaBrowser.Controller.MediaEncoding else if (isVaapiDecoder || isQsvDecoder) { // INPUT vaapi/qsv surface(vram) + // hw deint + if (doDeintH2645) + { + var deintFilter = GetHwDeinterlaceFilter(state, options, isVaapiDecoder ? "vaapi" : "qsv"); + mainFilters.Add(deintFilter); + } + var outFormat = doTonemap ? string.Empty : "nv12"; var hwScaleFilter = GetHwScaleFilter(isVaapiDecoder ? "vaapi" : "qsv", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH); // hw scale mainFilters.Add(hwScaleFilter); } - // hw deint - if (doDeintH2645 && isHwDecoder) - { - var deintFilter = string.Empty; - if (isVaapiDecoder) - { - deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi"); - } - else if (isQsvDecoder) - { - deintFilter = GetHwDeinterlaceFilter(state, options, "qsv"); - } - - mainFilters.Add(deintFilter); - } - // vaapi vpp tonemap if (doVaVppTonemap && isHwDecoder) { @@ -3521,6 +3504,7 @@ namespace MediaBrowser.Controller.MediaEncoding { // OUTPUT qsv(nv12) surface(vram) // reverse-mapping via qsv(vaapi)-opencl interop. + // add extra pool size to avoid the 'cannot allocate memory' error on hevc_qsv. mainFilters.Add("hwmap=derive_device=qsv:reverse=1:extra_hw_frames=16"); mainFilters.Add("format=qsv"); } @@ -3576,7 +3560,7 @@ namespace MediaBrowser.Controller.MediaEncoding } } - return new Tuple, List, List>(mainFilters, subFilters, overlayFilters); + return (mainFilters, subFilters, overlayFilters); } /// @@ -3586,14 +3570,14 @@ namespace MediaBrowser.Controller.MediaEncoding /// Encoding options. /// Video encoder to use. /// The tuple contains three lists: main, sub and overlay filters - public Tuple, List, List> GetVaapiVidFilterChain( + public (List, List, List) GetVaapiVidFilterChain( EncodingJobInfo state, EncodingOptions options, string vidEncoder) { if (!string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) { - return new Tuple, List, List>(null, null, null); + return (null, null, null); } var isLinux = OperatingSystem.IsLinux(); @@ -3618,14 +3602,14 @@ namespace MediaBrowser.Controller.MediaEncoding var mainFilters = noOverlay ? newfilters : swFilterChain.Item1; var overlayFilters = noOverlay ? swFilterChain.Item3 : newfilters; - return new Tuple, List, List>(mainFilters, swFilterChain.Item2, overlayFilters); + return (mainFilters, swFilterChain.Item2, overlayFilters); } return swFilterChain; } // prefered vaapi + opencl filters pipeline - if (_mediaEncoder.IsVaapiDeviceInteliHD()) + if (_mediaEncoder.IsVaapiDeviceInteliHD) { // Intel iHD path, with extra vpp tonemap and overlay support. return GetVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder); @@ -3635,7 +3619,7 @@ namespace MediaBrowser.Controller.MediaEncoding return GetVaapiLimitedVidFiltersPrefered(state, options, vidDecoder, vidEncoder); } - public Tuple, List, List> GetVaapiFullVidFiltersPrefered( + public (List, List, List) GetVaapiFullVidFiltersPrefered( EncodingJobInfo state, EncodingOptions options, string vidDecoder, @@ -3677,11 +3661,6 @@ namespace MediaBrowser.Controller.MediaEncoding if (isSwDecoder) { // INPUT sw surface(memory) - var outFormat = doOclTonemap ? "yuv420p10" : "nv12"; - var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); - // sw scale - mainFilters.AddRange(swScaleFilter); - mainFilters.Add("format=" + outFormat); // sw deint if (doDeintH2645) { @@ -3689,6 +3668,12 @@ namespace MediaBrowser.Controller.MediaEncoding mainFilters.Add(swDeintFilter); } + var outFormat = doOclTonemap ? "yuv420p10le" : "nv12"; + var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); + // sw scale + mainFilters.Add(swScaleFilter); + mainFilters.Add("format=" + outFormat); + // keep video at memory except ocl tonemap, // since the overhead caused by hwupload >>> using sw filter. // sw => hw @@ -3700,19 +3685,19 @@ namespace MediaBrowser.Controller.MediaEncoding else if (isVaapiDecoder) { // INPUT vaapi surface(vram) + // hw deint + if (doDeintH2645) + { + var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi"); + mainFilters.Add(deintFilter); + } + var outFormat = doTonemap ? string.Empty : "nv12"; var hwScaleFilter = GetHwScaleFilter("vaapi", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH); // hw scale mainFilters.Add(hwScaleFilter); } - // hw deint - if (doDeintH2645 && isVaapiDecoder) - { - var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi"); - mainFilters.Add(deintFilter); - } - // vaapi vpp tonemap if (doVaVppTonemap && isVaapiDecoder) { @@ -3827,10 +3812,10 @@ namespace MediaBrowser.Controller.MediaEncoding } } - return new Tuple, List, List>(mainFilters, subFilters, overlayFilters); + return (mainFilters, subFilters, overlayFilters); } - public Tuple, List, List> GetVaapiLimitedVidFiltersPrefered( + public (List, List, List) GetVaapiLimitedVidFiltersPrefered( EncodingJobInfo state, EncodingOptions options, string vidDecoder, @@ -3849,8 +3834,8 @@ namespace MediaBrowser.Controller.MediaEncoding var isSwDecoder = string.IsNullOrEmpty(vidDecoder); var isSwEncoder = !isVaapiEncoder; var isVaInVaOut = isVaapiDecoder && isVaapiEncoder; - var isi965Driver = _mediaEncoder.IsVaapiDeviceInteli965(); - var isAmdDriver = _mediaEncoder.IsVaapiDeviceAmd(); + var isi965Driver = _mediaEncoder.IsVaapiDeviceInteli965; + var isAmdDriver = _mediaEncoder.IsVaapiDeviceAmd; var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); @@ -3870,11 +3855,6 @@ namespace MediaBrowser.Controller.MediaEncoding if (isSwDecoder) { // INPUT sw surface(memory) - outFormat = doOclTonemap ? "yuv420p10" : "nv12"; - var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); - // sw scale - mainFilters.AddRange(swScaleFilter); - mainFilters.Add("format=" + outFormat); // sw deint if (doDeintH2645) { @@ -3882,6 +3862,12 @@ namespace MediaBrowser.Controller.MediaEncoding mainFilters.Add(swDeintFilter); } + outFormat = doOclTonemap ? "yuv420p10le" : "nv12"; + var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); + // sw scale + mainFilters.Add(swScaleFilter); + mainFilters.Add("format=" + outFormat); + // keep video at memory except ocl tonemap, // since the overhead caused by hwupload >>> using sw filter. // sw => hw @@ -3893,19 +3879,19 @@ namespace MediaBrowser.Controller.MediaEncoding else if (isVaapiDecoder) { // INPUT vaapi surface(vram) + // hw deint + if (doDeintH2645) + { + var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi"); + mainFilters.Add(deintFilter); + } + outFormat = doOclTonemap ? string.Empty : "nv12"; var hwScaleFilter = GetHwScaleFilter("vaapi", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH); // hw scale mainFilters.Add(hwScaleFilter); } - // hw deint - if (doDeintH2645 && isVaapiDecoder) - { - var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi"); - mainFilters.Add(deintFilter); - } - if (doOclTonemap && isVaapiDecoder) { if (isi965Driver) @@ -4001,7 +3987,7 @@ namespace MediaBrowser.Controller.MediaEncoding } } - return new Tuple, List, List>(mainFilters, subFilters, overlayFilters); + return (mainFilters, subFilters, overlayFilters); } /// @@ -4026,40 +4012,37 @@ namespace MediaBrowser.Controller.MediaEncoding var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream; - Tuple, List, List> filterChain = null; + List mainFilters; + List subFilters; + List overlayFilters; if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) { - filterChain = GetVaapiVidFilterChain(state, options, outputVideoCodec); + (mainFilters, subFilters, overlayFilters) = GetVaapiVidFilterChain(state, options, outputVideoCodec); } else if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) { - filterChain = GetIntelVidFilterChain(state, options, outputVideoCodec); + (mainFilters, subFilters, overlayFilters) = GetIntelVidFilterChain(state, options, outputVideoCodec); } else if (string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) { - filterChain = GetNvidiaVidFilterChain(state, options, outputVideoCodec); + (mainFilters, subFilters, overlayFilters) = GetNvidiaVidFilterChain(state, options, outputVideoCodec); } else if (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)) { - filterChain = GetAmdVidFilterChain(state, options, outputVideoCodec); + (mainFilters, subFilters, overlayFilters) = GetAmdVidFilterChain(state, options, outputVideoCodec); } else { - filterChain = GetSwVidFilterChain(state, options, outputVideoCodec); + (mainFilters, subFilters, overlayFilters) = GetSwVidFilterChain(state, options, outputVideoCodec); } - var mainFilters = filterChain.Item1; - mainFilters.RemoveAll(filter => string.IsNullOrEmpty(filter)); - - var subFilters = filterChain.Item2; - subFilters.RemoveAll(filter => string.IsNullOrEmpty(filter)); - - var overlayFilters = filterChain.Item3; - overlayFilters.RemoveAll(filter => string.IsNullOrEmpty(filter)); + mainFilters?.RemoveAll(filter => string.IsNullOrEmpty(filter)); + subFilters?.RemoveAll(filter => string.IsNullOrEmpty(filter)); + overlayFilters?.RemoveAll(filter => string.IsNullOrEmpty(filter)); var mainStr = string.Empty; - if (mainFilters.Count > 0) + if (mainFilters?.Count > 0) { mainStr = string.Format( CultureInfo.InvariantCulture, @@ -4067,12 +4050,15 @@ namespace MediaBrowser.Controller.MediaEncoding string.Join(',', mainFilters)); } - if (overlayFilters.Count == 0) + if (overlayFilters?.Count == 0) { // -vf "scale..." return string.IsNullOrEmpty(mainStr) ? string.Empty : " -vf \"" + mainStr + "\""; } - else if (overlayFilters.Count > 0 && subFilters.Count > 0 && state.SubtitleStream != null) + + if (overlayFilters?.Count > 0 + && subFilters?.Count > 0 + && state.SubtitleStream != null) { // overlay graphical/text subtitles var subStr = string.Format( @@ -4085,10 +4071,8 @@ namespace MediaBrowser.Controller.MediaEncoding "{0}", string.Join(',', overlayFilters)); - var mapPrefix = state.SubtitleStream.IsExternal - ? 1 - : 0; + var mapPrefix = Convert.ToInt32(state.SubtitleStream.IsExternal); var subtitleStreamIndex = state.SubtitleStream.IsExternal ? 0 : state.SubtitleStream.Index; @@ -4128,10 +4112,8 @@ namespace MediaBrowser.Controller.MediaEncoding { return GetInputHdrParam(state.VideoStream?.ColorTransfer); } - else - { - return GetOutputSdrParam(null); - } + + return GetOutputSdrParam(null); } public string GetInputHdrParam(string colorTransfer) @@ -4141,11 +4123,9 @@ namespace MediaBrowser.Controller.MediaEncoding // HLG return "setparams=color_primaries=bt2020:color_trc=arib-std-b67:colorspace=bt2020nc"; } - else - { - // HDR10 - return "setparams=color_primaries=bt2020:color_trc=smpte2084:colorspace=bt2020nc"; - } + + // HDR10 + return "setparams=color_primaries=bt2020:color_trc=smpte2084:colorspace=bt2020nc"; } public string GetOutputSdrParam(string tonemappingRange) @@ -4174,17 +4154,17 @@ namespace MediaBrowser.Controller.MediaEncoding return videoStream.BitDepth.Value; } else if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase)) + || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase)) { return 8; } else if (string.Equals(videoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase)) + || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase)) { return 10; } else if (string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase)) + || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase)) { return 12; } @@ -4228,10 +4208,11 @@ namespace MediaBrowser.Controller.MediaEncoding var bitDepth = GetVideoColorBitDepth(state); // Only HEVC, VP9 and AV1 formats have 10-bit hardware decoder support now. - if (bitDepth == 10 && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))) + if (bitDepth == 10 + && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))) { return null; } @@ -4267,19 +4248,18 @@ namespace MediaBrowser.Controller.MediaEncoding } } - var whichCodec = videoStream.Codec?.ToLowerInvariant(); - switch (whichCodec) + var whichCodec = videoStream.Codec; + if (string.Equals(whichCodec, "avc", StringComparison.OrdinalIgnoreCase)) { - case "avc": - whichCodec = "h264"; - break; - case "h265": - whichCodec = "hevc"; - break; + whichCodec = "h264"; + } + else if (string.Equals(whichCodec, "h265", StringComparison.OrdinalIgnoreCase)) + { + whichCodec = "hevc"; } // Avoid a second attempt if no hardware acceleration is being used - options.HardwareDecodingCodecs = options.HardwareDecodingCodecs.Where(val => val != whichCodec).ToArray(); + options.HardwareDecodingCodecs = Array.FindAll(options.HardwareDecodingCodecs, val => !string.Equals(val, whichCodec, StringComparison.OrdinalIgnoreCase)); // leave blank so ffmpeg will decide return null; @@ -4347,6 +4327,9 @@ namespace MediaBrowser.Controller.MediaEncoding var isVideotoolboxSupported = isMacOS && _mediaEncoder.SupportsHwaccel("videotoolbox"); var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase); + // Set the av1 codec explicitly to trigger hw accelerator, otherwise libdav1d will be used. + var isAv1 = string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase); + if (bitDepth == 10 && isCodecAvailable) { if ((options.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Hevc) @@ -4363,12 +4346,12 @@ namespace MediaBrowser.Controller.MediaEncoding { if (isVaapiSupported && isCodecAvailable) { - return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi" : string.Empty); + return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty); } if (isD3d11Supported && isCodecAvailable) { - return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty); + return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty); } } else @@ -4385,7 +4368,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (options.EnableEnhancedNvdecDecoder && isCudaSupported && isCodecAvailable) { - return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda" : string.Empty); + return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty); } } @@ -4394,25 +4377,23 @@ namespace MediaBrowser.Controller.MediaEncoding { if (isD3d11Supported && isCodecAvailable) { - return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty); + return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty); } } // Vaapi - if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) + && isVaapiSupported + && isCodecAvailable) { - if (isVaapiSupported && isCodecAvailable) - { - return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi" : string.Empty); - } + return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty); } - if (string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase) + && isVideotoolboxSupported + && isCodecAvailable) { - if (isVideotoolboxSupported && isCodecAvailable) - { - return " -hwaccel videotoolbox" + (outputHwSurface ? " -hwaccel_output_format videotoolbox_vld" : string.Empty); - } + return " -hwaccel videotoolbox" + (outputHwSurface ? " -hwaccel_output_format videotoolbox_vld" : string.Empty); } return null; @@ -4423,7 +4404,8 @@ namespace MediaBrowser.Controller.MediaEncoding var isWindows = OperatingSystem.IsWindows(); var isLinux = OperatingSystem.IsLinux(); - if (!isWindows && !isLinux) + if ((!isWindows && !isLinux) + || !string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) { return null; } @@ -4438,44 +4420,50 @@ namespace MediaBrowser.Controller.MediaEncoding var hwSurface = (isIntelDx11OclSupported || isIntelVaapiOclSupported) && _mediaEncoder.SupportsFilter("alphasrc"); - var _8bitSwFormatsQsv = new List { "yuv420p", }; - var _8_10bitSwFormatsQsv = new List { "yuv420p", "yuv420p10le", }; - // TODO: add more 8/10bit and 4:4:4 formats for Qsv after finshing the ffcheck tool + var is8bitSwFormatsQsv = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase); + var is8_10bitSwFormatsQsv = is8bitSwFormatsQsv || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase); + // TODO: add more 8/10bit and 4:4:4 formats for Qsv after finishing the ffcheck tool - if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) + if (is8bitSwFormatsQsv) { - switch (videoStream.Codec.ToLowerInvariant()) - { - case "avc": - case "h264": - return _8bitSwFormatsQsv.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264", "qsv", "h264", bitDepth) - : string.Empty; - case "hevc": - case "h265": - return _8_10bitSwFormatsQsv.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc", "qsv", "hevc", bitDepth) - : string.Empty; - case "mpeg2video": - return _8bitSwFormatsQsv.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options, "mpeg2", "qsv", "mpeg2video", bitDepth) - : string.Empty; - case "vc1": - return _8bitSwFormatsQsv.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1", "qsv", "vc1", bitDepth) - : string.Empty; - case "vp8": - return _8bitSwFormatsQsv.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8", "qsv", "vp8", bitDepth) - : string.Empty; - case "vp9": - return _8_10bitSwFormatsQsv.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9", "qsv", "vp9", bitDepth) - : string.Empty; - case "av1": - return _8_10bitSwFormatsQsv.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1", "qsv", "av1", bitDepth) - : string.Empty; + if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264", "qsv", "h264", bitDepth); + } + + if (string.Equals(videoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1", "qsv", "vc1", bitDepth); + } + + if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8", "qsv", "vp8", bitDepth); + } + + if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options, "mpeg2", "qsv", "mpeg2video", bitDepth); + } + } + + if (is8_10bitSwFormatsQsv) + { + if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc", "qsv", "hevc", bitDepth); + } + + if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9", "qsv", "vp9", bitDepth); + } + + if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1", "qsv", "av1", bitDepth); } } @@ -4484,7 +4472,8 @@ namespace MediaBrowser.Controller.MediaEncoding public string GetNvdecVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth) { - if (!OperatingSystem.IsWindows() && !OperatingSystem.IsLinux()) + if ((!OperatingSystem.IsWindows() && !OperatingSystem.IsLinux()) + || !string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) { return null; } @@ -4492,48 +4481,55 @@ namespace MediaBrowser.Controller.MediaEncoding var hwSurface = IsCudaFullSupported() && options.EnableEnhancedNvdecDecoder && _mediaEncoder.SupportsFilter("alphasrc"); - var _8bitSwFormatsNvdec = new List { "yuv420p", }; - var _8_10bitSwFormatsNvdec = new List { "yuv420p", "yuv420p10le", }; - // TODO: add more 8/10/12bit and 4:4:4 formats for Nvdec after finshing the ffcheck tool + var is8bitSwFormatsNvdec = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase); + var is8_10bitSwFormatsNvdec = is8bitSwFormatsNvdec || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase); + // TODO: add more 8/10/12bit and 4:4:4 formats for Nvdec after finishing the ffcheck tool - if (string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) + if (is8bitSwFormatsNvdec) { - switch (videoStream.Codec.ToLowerInvariant()) - { - case "avc": - case "h264": - return _8bitSwFormatsNvdec.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264", "cuvid", "h264", bitDepth) - : string.Empty; - case "hevc": - case "h265": - return _8_10bitSwFormatsNvdec.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc", "cuvid", "hevc", bitDepth) - : string.Empty; - case "mpeg2video": - return _8bitSwFormatsNvdec.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options, "mpeg2", "cuvid", "mpeg2video", bitDepth) - : string.Empty; - case "vc1": - return _8bitSwFormatsNvdec.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1", "cuvid", "vc1", bitDepth) - : string.Empty; - case "mpeg4": - return _8bitSwFormatsNvdec.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface) + GetHwDecoderName(options, "mpeg4", "cuvid", "mpeg4", bitDepth) - : string.Empty; - case "vp8": - return _8bitSwFormatsNvdec.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8", "cuvid", "vp8", bitDepth) - : string.Empty; - case "vp9": - return _8_10bitSwFormatsNvdec.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9", "cuvid", "vp9", bitDepth) - : string.Empty; - case "av1": - return _8_10bitSwFormatsNvdec.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1", "cuvid", "av1", bitDepth) - : string.Empty; + if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase) + || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264", "cuvid", "h264", bitDepth); + } + + if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options, "mpeg2", "cuvid", "mpeg2video", bitDepth); + } + + if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1", "cuvid", "vc1", bitDepth); + } + + if (string.Equals("mpeg4", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface) + GetHwDecoderName(options, "mpeg4", "cuvid", "mpeg4", bitDepth); + } + + if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8", "cuvid", "vp8", bitDepth); + } + } + + if (is8_10bitSwFormatsNvdec) + { + if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase) + || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc", "cuvid", "hevc", bitDepth); + } + + if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9", "cuvid", "vp9", bitDepth); + } + + if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1", "cuvid", "av1", bitDepth); } } @@ -4542,7 +4538,8 @@ namespace MediaBrowser.Controller.MediaEncoding public string GetAmfVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth) { - if (!OperatingSystem.IsWindows()) + if (!OperatingSystem.IsWindows() + || !string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)) { return null; } @@ -4550,43 +4547,49 @@ namespace MediaBrowser.Controller.MediaEncoding var hwSurface = _mediaEncoder.SupportsHwaccel("d3d11va") && IsOpenclFullSupported() && _mediaEncoder.SupportsFilter("alphasrc"); - var _8bitSwFormatsAmf = new List { "yuv420p", }; - var _8_10bitSwFormatsAmf = new List { "yuv420p", "yuv420p10le", }; + var is8bitSwFormatsAmf = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase); + var is8_10bitSwFormatsAmf = is8bitSwFormatsAmf || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase); - if (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)) + if (is8bitSwFormatsAmf) { - switch (videoStream.Codec.ToLowerInvariant()) - { - case "avc": - case "h264": - return _8bitSwFormatsAmf.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "h264", bitDepth, hwSurface) - : string.Empty; - case "hevc": - case "h265": - return _8_10bitSwFormatsAmf.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) - : string.Empty; - case "mpeg2video": - return _8bitSwFormatsAmf.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) - : string.Empty; - case "vc1": - return _8bitSwFormatsAmf.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) - : string.Empty; - case "mpeg4": - return _8bitSwFormatsAmf.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface) - : string.Empty; - case "vp9": - return _8_10bitSwFormatsAmf.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) - : string.Empty; - case "av1": - return _8_10bitSwFormatsAmf.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "av1", bitDepth, hwSurface) - : string.Empty; + if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase) + || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "h264", bitDepth, hwSurface); + } + + if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface); + } + + if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface); + } + + if (string.Equals("mpeg4", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface); + } + } + + if (is8_10bitSwFormatsAmf) + { + if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase) + || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface); + } + + if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface); + } + + if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "av1", bitDepth, hwSurface); } } @@ -4595,7 +4598,8 @@ namespace MediaBrowser.Controller.MediaEncoding public string GetVaapiVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth) { - if (!OperatingSystem.IsLinux()) + if (!OperatingSystem.IsLinux() + || !string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) { return null; } @@ -4604,43 +4608,49 @@ namespace MediaBrowser.Controller.MediaEncoding && IsVaapiFullSupported() && IsOpenclFullSupported() && _mediaEncoder.SupportsFilter("alphasrc"); - var _8bitSwFormatsVaapi = new List { "yuv420p", }; - var _8_10bitSwFormatsVaapi = new List { "yuv420p", "yuv420p10le", }; + var is8bitSwFormatsVaapi = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase); + var is8_10bitSwFormatsVaapi = is8bitSwFormatsVaapi || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase); - if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) + if (is8bitSwFormatsVaapi) + { + if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase) + || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "h264", bitDepth, hwSurface); + } + + if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface); + } + + if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface); + } + + if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface); + } + } + + if (is8_10bitSwFormatsVaapi) { - switch (videoStream.Codec.ToLowerInvariant()) - { - case "avc": - case "h264": - return _8bitSwFormatsVaapi.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "h264", bitDepth, hwSurface) - : string.Empty; - case "hevc": - case "h265": - return _8_10bitSwFormatsVaapi.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) - : string.Empty; - case "mpeg2video": - return _8bitSwFormatsVaapi.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) - : string.Empty; - case "vc1": - return _8bitSwFormatsVaapi.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) - : string.Empty; - case "vp8": - return _8bitSwFormatsVaapi.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) - : string.Empty; - case "vp9": - return _8_10bitSwFormatsVaapi.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) - : string.Empty; - case "av1": - return _8_10bitSwFormatsVaapi.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "av1", bitDepth, hwSurface) - : string.Empty; + if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase) + || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface); + } + + if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface); + } + + if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "av1", bitDepth, hwSurface); } } @@ -4649,36 +4659,45 @@ namespace MediaBrowser.Controller.MediaEncoding public string GetVideotoolboxVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth) { - if (!OperatingSystem.IsMacOS()) + if (!OperatingSystem.IsMacOS() + || !string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) { return null; } - var _8bitSwFormatsVt = new List { "yuv420p", }; - var _8_10bitSwFormatsVt = new List { "yuv420p", "yuv420p10le", }; + var is8bitSwFormatsVt = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase); + var is8_10bitSwFormatsVt = is8bitSwFormatsVt || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase); - if (string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) + if (is8bitSwFormatsVt) { - switch (videoStream.Codec.ToLowerInvariant()) + if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase) + || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "h264", bitDepth, false); + } + + if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "mpeg2video", bitDepth, false); + } + + if (string.Equals("mpeg4", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) { - case "avc": - case "h264": - return _8bitSwFormatsVt.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "h264", bitDepth, false) - : string.Empty; - case "hevc": - case "h265": - return _8_10bitSwFormatsVt.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "hevc", bitDepth, false) - : string.Empty; - case "mpeg2video": - return _8bitSwFormatsVt.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "mpeg2video", bitDepth, false) - : string.Empty; - case "mpeg4": - return _8_10bitSwFormatsVt.Contains(videoStream.PixelFormat) - ? GetHwaccelType(state, options, "mpeg4", bitDepth, false) - : string.Empty; + return GetHwaccelType(state, options, "mpeg4", bitDepth, false); + } + } + + if (is8_10bitSwFormatsVt) + { + if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase) + || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "hevc", bitDepth, false); + } + + if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "vp9", bitDepth, false); } } @@ -4687,34 +4706,35 @@ namespace MediaBrowser.Controller.MediaEncoding public string GetOmxVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth) { - if (!OperatingSystem.IsLinux()) + if (!OperatingSystem.IsLinux() + || !string.Equals(options.HardwareAccelerationType, "omx", StringComparison.OrdinalIgnoreCase)) { return null; } - var _8bitSwFormatsOmx = new List { "yuv420p", }; + var is8bitSwFormatsOmx = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase); - if (string.Equals(options.HardwareAccelerationType, "omx", StringComparison.OrdinalIgnoreCase)) + if (is8bitSwFormatsOmx) { - switch (videoStream.Codec.ToLowerInvariant()) + if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase) + || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwDecoderName(options, "h264", "mmal", "h264", bitDepth); + } + + if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwDecoderName(options, "mpeg2", "mmal", "mpeg2video", bitDepth); + } + + if (string.Equals("mpeg4", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) { - case "avc": - case "h264": - return _8bitSwFormatsOmx.Contains(videoStream.PixelFormat) - ? GetHwDecoderName(options, "h264", "mmal", "h264", bitDepth) - : string.Empty; - case "mpeg2video": - return _8bitSwFormatsOmx.Contains(videoStream.PixelFormat) - ? GetHwDecoderName(options, "mpeg2", "mmal", "mpeg2video", bitDepth) - : string.Empty; - case "mpeg4": - return _8bitSwFormatsOmx.Contains(videoStream.PixelFormat) - ? GetHwDecoderName(options, "mpeg4", "mmal", "mpeg4", bitDepth) - : string.Empty; - case "vc1": - return _8bitSwFormatsOmx.Contains(videoStream.PixelFormat) - ? GetHwDecoderName(options, "vc1", "mmal", "vc1", bitDepth) - : string.Empty; + return GetHwDecoderName(options, "mpeg4", "mmal", "mpeg4", bitDepth); + } + + if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwDecoderName(options, "vc1", "mmal", "vc1", bitDepth); } } @@ -5211,8 +5231,7 @@ namespace MediaBrowser.Controller.MediaEncoding args += videoProcessParam; - hasCopyTs = videoProcessParam.IndexOf("copyts", StringComparison.OrdinalIgnoreCase) != -1; - + hasCopyTs = videoProcessParam.Contains("copyts", StringComparison.OrdinalIgnoreCase); if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps) { diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index e92c4a08a..c4affa567 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -110,23 +110,7 @@ namespace MediaBrowser.Controller.MediaEncoding public string OutputContainer { get; set; } - public string OutputVideoSync - { - get - { - // For live tv + in progress recordings - if (string.Equals(InputContainer, "mpegts", StringComparison.OrdinalIgnoreCase) - || string.Equals(InputContainer, "ts", StringComparison.OrdinalIgnoreCase)) - { - if (!MediaSource.RunTimeTicks.HasValue) - { - return "cfr"; - } - } - - return "-1"; - } - } + public string OutputVideoSync { get; set; } public string AlbumCoverPath { get; set; } diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 6a7f38c0e..27d618a3f 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -25,6 +25,30 @@ namespace MediaBrowser.Controller.MediaEncoding /// The encoder path. string EncoderPath { get; } + /// + /// Gets the version of encoder. + /// + /// The version of encoder. + Version EncoderVersion { get; } + + /// + /// Whether the configured Vaapi device is from AMD(radeonsi/r600 Mesa driver). + /// + /// true if the Vaapi device is an AMD(radeonsi/r600 Mesa driver) GPU, false otherwise. + bool IsVaapiDeviceAmd { get; } + + /// + /// Whether the configured Vaapi device is from Intel(iHD driver). + /// + /// true if the Vaapi device is an Intel(iHD driver) GPU, false otherwise. + bool IsVaapiDeviceInteliHD { get; } + + /// + /// Whether the configured Vaapi device is from Intel(legacy i965 driver). + /// + /// true if the Vaapi device is an Intel(legacy i965 driver) GPU, false otherwise. + bool IsVaapiDeviceInteli965 { get; } + /// /// Whether given encoder codec is supported. /// @@ -60,30 +84,6 @@ namespace MediaBrowser.Controller.MediaEncoding /// true if the filter is supported, false otherwise. bool SupportsFilterWithOption(FilterOptionType option); - /// - /// Whether the configured Vaapi device is from AMD(radeonsi/r600 Mesa driver). - /// - /// true if the Vaapi device is an AMD(radeonsi/r600 Mesa driver) GPU, false otherwise. - bool IsVaapiDeviceAmd(); - - /// - /// Whether the configured Vaapi device is from Intel(iHD driver). - /// - /// true if the Vaapi device is an Intel(iHD driver) GPU, false otherwise. - bool IsVaapiDeviceInteliHD(); - - /// - /// Whether the configured Vaapi device is from Intel(legacy i965 driver). - /// - /// true if the Vaapi device is an Intel(legacy i965 driver) GPU, false otherwise. - bool IsVaapiDeviceInteli965(); - - /// - /// Get the version of media encoder. - /// - /// The version of media encoder. - Version GetMediaEncoderVersion(); - /// /// Extracts the audio image. /// diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index 871e7d57d..fe3069934 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -351,18 +351,16 @@ namespace MediaBrowser.MediaEncoding.Encoder return false; } - string output; try { - output = GetProcessOutput(_encoderPath, "-v verbose -hide_banner -init_hw_device vaapi=va:" + renderNodePath, true); + var output = GetProcessOutput(_encoderPath, "-v verbose -hide_banner -init_hw_device vaapi=va:" + renderNodePath, true); + return output.Contains(driverName, StringComparison.Ordinal); } catch (Exception ex) { _logger.LogError(ex, "Error detecting the given vaapi render node path"); return false; } - - return output.Contains(driverName, StringComparison.Ordinal); } private IEnumerable GetHwaccelTypes() diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 7b7bb8100..65f9f1149 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -91,6 +91,10 @@ namespace MediaBrowser.MediaEncoding.Encoder /// public string EncoderPath => _ffmpegPath; + public Version EncoderVersion => _ffmpegVersion; + public bool IsVaapiDeviceAmd => _isVaapiDeviceAmd; + public bool IsVaapiDeviceInteliHD => _isVaapiDeviceInteliHD; + public bool IsVaapiDeviceInteli965 => _isVaapiDeviceInteli965; /// /// Run at startup or if the user removes a Custom path from transcode page. @@ -138,7 +142,6 @@ namespace MediaBrowser.MediaEncoding.Encoder SetAvailableHwaccels(validator.GetHwaccels()); SetMediaEncoderVersion(validator); - options = _configurationManager.GetEncodingOptions(); _threads = EncodingHelper.GetNumberOfThreads(null, options, null); // Check the Vaapi device vendor @@ -329,26 +332,6 @@ namespace MediaBrowser.MediaEncoding.Encoder return false; } - public bool IsVaapiDeviceAmd() - { - return _isVaapiDeviceAmd; - } - - public bool IsVaapiDeviceInteliHD() - { - return _isVaapiDeviceInteliHD; - } - - public bool IsVaapiDeviceInteli965() - { - return _isVaapiDeviceInteli965; - } - - public Version GetMediaEncoderVersion() - { - return _ffmpegVersion; - } - public bool CanEncodeToAudioCodec(string codec) { if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase)) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 24577e499..5c37bd506 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -772,12 +772,12 @@ namespace MediaBrowser.MediaEncoding.Probing stream.BitDepth = 8; } else if (string.Equals(streamInfo.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase) - || string.Equals(streamInfo.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase)) + || string.Equals(streamInfo.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase)) { stream.BitDepth = 10; } else if (string.Equals(streamInfo.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase) - || string.Equals(streamInfo.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase)) + || string.Equals(streamInfo.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase)) { stream.BitDepth = 12; } diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index ea7b24347..d0ded99ea 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -18,7 +18,7 @@ namespace MediaBrowser.Model.Configuration VaapiDevice = "/dev/dri/renderD128"; EnableTonemapping = false; EnableVppTonemapping = false; - TonemappingAlgorithm = "hable"; + TonemappingAlgorithm = "bt2390"; TonemappingRange = "auto"; TonemappingDesat = 0; TonemappingThreshold = 0.8; -- cgit v1.2.3 From 7db753d2471e6c8e0b003c75c1d40fc7b2f396da Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Thu, 23 Dec 2021 22:59:39 +0800 Subject: reduce tonemap cpu usage, add deint and AR support in thumbnails Co-authored-by: Orry Verducci --- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 92 ++++++++-------------- 1 file changed, 31 insertions(+), 61 deletions(-) (limited to 'MediaBrowser.MediaEncoding/Encoder') diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 65f9f1149..fce71bf1a 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -534,36 +534,9 @@ namespace MediaBrowser.MediaEncoding.Encoder if (!isAudio) { - // The failure of HDR extraction usually occurs when using custom ffmpeg that does not contain the zscale filter. try { - return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, true, targetFormat, cancellationToken).ConfigureAwait(false); - } - catch (ArgumentException) - { - throw; - } - catch (Exception ex) - { - _logger.LogError(ex, "I-frame or HDR image extraction failed, will attempt with I-frame extraction disabled. Input: {Arguments}", inputArgument); - } - - try - { - return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, true, targetFormat, cancellationToken).ConfigureAwait(false); - } - catch (ArgumentException) - { - throw; - } - catch (Exception ex) - { - _logger.LogError(ex, "HDR image extraction failed, will fallback to SDR image extraction. Input: {Arguments}", inputArgument); - } - - try - { - return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, false, targetFormat, cancellationToken).ConfigureAwait(false); + return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, targetFormat, cancellationToken).ConfigureAwait(false); } catch (ArgumentException) { @@ -575,10 +548,10 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, false, targetFormat, cancellationToken).ConfigureAwait(false); + return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, targetFormat, cancellationToken).ConfigureAwait(false); } - private async Task ExtractImageInternal(string inputPath, string container, MediaStream videoStream, int? imageStreamIndex, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, bool allowTonemap, ImageFormat? targetFormat, CancellationToken cancellationToken) + private async Task ExtractImageInternal(string inputPath, string container, MediaStream videoStream, int? imageStreamIndex, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, ImageFormat? targetFormat, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(inputPath)) { @@ -598,36 +571,32 @@ namespace MediaBrowser.MediaEncoding.Encoder var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + outputExtension); Directory.CreateDirectory(Path.GetDirectoryName(tempExtractPath)); + // deint -> scale -> thumbnail -> tonemap. + // put the SW tonemap right after the thumbnail to do it only once to reduce cpu usage. + var filters = new List(); + + // deinterlace using bwdif algorithm for video stream. + if (videoStream != null && videoStream.IsInterlaced) + { + filters.Add("bwdif=0:-1:0"); + } + // apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar. // This filter chain may have adverse effects on recorded tv thumbnails if ar changes during presentation ex. commercials @ diff ar - var vf = threedFormat switch + var scaler = threedFormat switch { // hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars we may have made. Work out the correct height based on the display aspect it will maintain the aspect where -1 in this case (3d) may not. - Video3DFormat.HalfSideBySide => "-vf crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1", + Video3DFormat.HalfSideBySide => "crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1", // fsbs crop width in half,set the display aspect,crop out any black bars we may have made - Video3DFormat.FullSideBySide => "-vf crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1", + Video3DFormat.FullSideBySide => "crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1", // htab crop heigh in half,scale to correct size, set the display aspect,crop out any black bars we may have made - Video3DFormat.HalfTopAndBottom => "-vf crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1", + Video3DFormat.HalfTopAndBottom => "crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1", // ftab crop heigt in half, set the display aspect,crop out any black bars we may have made - Video3DFormat.FullTopAndBottom => "-vf crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1", - _ => string.Empty + Video3DFormat.FullTopAndBottom => "crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1", + _ => "scale=trunc(iw*sar):ih" }; - var mapArg = imageStreamIndex.HasValue ? (" -map 0:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty; - - var enableHdrExtraction = allowTonemap && string.Equals(videoStream?.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase); - if (enableHdrExtraction) - { - string tonemapFilters = "zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0:peak=100,zscale=t=bt709:m=bt709,format=yuv420p"; - if (vf.Length == 0) - { - vf = "-vf " + tonemapFilters; - } - else - { - vf += "," + tonemapFilters; - } - } + filters.Add(scaler); // Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case. // mpegts need larger batch size otherwise the corrupted thumbnail will be created. Larger batch size will lower the processing speed. @@ -635,18 +604,19 @@ namespace MediaBrowser.MediaEncoding.Encoder if (enableThumbnail) { var useLargerBatchSize = string.Equals("mpegts", container, StringComparison.OrdinalIgnoreCase); - var batchSize = useLargerBatchSize ? "50" : "24"; - if (string.IsNullOrEmpty(vf)) - { - vf = "-vf thumbnail=" + batchSize; - } - else - { - vf += ",thumbnail=" + batchSize; - } + filters.Add("thumbnail=n=" + (useLargerBatchSize ? "50" : "24")); } - var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 {2} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, _threads); + // Use SW tonemap on HDR video stream only when the zscale filter is available. + var enableHdrExtraction = string.Equals(videoStream?.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase) && SupportsFilter("zscale"); + if (enableHdrExtraction) + { + filters.Add("zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0:peak=100,zscale=t=bt709:m=bt709,format=yuv420p"); + } + + var vf = string.Join(',', filters); + var mapArg = imageStreamIndex.HasValue ? (" -map 0:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty; + var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 -vf {2} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, _threads); if (offset.HasValue) { -- cgit v1.2.3 From 7bfc6b5679308d3ec816e6f34b0dd54cf0cbd07c Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Fri, 24 Dec 2021 14:18:24 -0700 Subject: Remove more warnings --- Emby.Drawing/ImageProcessor.cs | 10 +- Emby.Naming/TV/SeasonPathParser.cs | 6 +- .../Channels/ChannelManager.cs | 8 +- .../Collections/CollectionManager.cs | 2 +- .../Data/SqliteItemRepository.cs | 118 +++++++-------------- .../EntryPoints/LibraryChangedNotifier.cs | 1 + Emby.Server.Implementations/IO/StreamHelper.cs | 20 ++-- .../Images/CollectionFolderImageProvider.cs | 4 +- .../Library/LibraryManager.cs | 14 +-- .../Library/MediaSourceManager.cs | 7 +- .../Library/SearchEngine.cs | 2 +- .../LiveTv/Listings/SchedulesDirect.cs | 2 + .../LiveTv/LiveTvManager.cs | 37 +++---- .../HdHomerun/HdHomerunChannelCommands.cs | 2 +- .../TunerHosts/HdHomerun/HdHomerunManager.cs | 4 +- .../TunerHosts/HdHomerun/HdHomerunUdpStream.cs | 2 +- .../HdHomerun/IHdHomerunChannelCommands.cs | 2 +- .../HdHomerun/LegacyHdHomerunChannelCommands.cs | 2 +- .../Playlists/PlaylistManager.cs | 2 +- Emby.Server.Implementations/TV/TVSeriesManager.cs | 6 +- .../Updates/InstallationManager.cs | 6 +- Jellyfin.Api/Controllers/FilterController.cs | 8 +- Jellyfin.Api/Controllers/ItemsController.cs | 2 +- Jellyfin.Api/Helpers/RequestHelpers.cs | 2 +- .../Users/UserManager.cs | 14 +-- .../CreateNetworkConfiguration.cs | 4 +- MediaBrowser.Common/Net/IPNetAddress.cs | 2 +- MediaBrowser.Common/Net/IPObject.cs | 2 +- MediaBrowser.Controller/Drawing/IImageProcessor.cs | 2 +- MediaBrowser.Controller/Entities/Audio/Audio.cs | 2 +- MediaBrowser.Controller/Entities/BaseItem.cs | 4 +- .../Entities/InternalItemsQuery.cs | 4 +- MediaBrowser.Controller/Entities/Video.cs | 2 +- MediaBrowser.Controller/Library/ILibraryManager.cs | 14 +-- MediaBrowser.Controller/LiveTv/ILiveTvManager.cs | 4 +- .../MediaEncoding/EncodingHelper.cs | 50 ++++----- .../MediaEncoding/IAttachmentExtractor.cs | 2 +- .../MediaEncoding/IMediaEncoder.cs | 6 +- MediaBrowser.Controller/MediaEncoding/JobLogger.cs | 2 +- .../Persistence/IItemRepository.cs | 12 +-- .../Providers/MetadataResult.cs | 6 +- .../Attachments/AttachmentExtractor.cs | 2 +- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 4 + .../Subtitles/SubtitleEncoder.cs | 10 +- MediaBrowser.Model/Dlna/StreamBuilder.cs | 26 ++--- MediaBrowser.Model/Session/GeneralCommandType.cs | 4 +- MediaBrowser.Providers/Manager/MetadataService.cs | 4 +- .../MusicBrainz/MusicBrainzAlbumProvider.cs | 10 +- MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs | 2 +- .../Parsers/MovieNfoParserTests.cs | 33 +++--- 50 files changed, 231 insertions(+), 265 deletions(-) (limited to 'MediaBrowser.MediaEncoding/Encoder') diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index c9847aa2d..b530c6942 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -101,7 +101,7 @@ namespace Emby.Drawing public async Task ProcessImage(ImageProcessingOptions options, Stream toStream) { var file = await ProcessImage(options).ConfigureAwait(false); - using (var fileStream = AsyncFile.OpenRead(file.path)) + using (var fileStream = AsyncFile.OpenRead(file.Path)) { await fileStream.CopyToAsync(toStream).ConfigureAwait(false); } @@ -116,7 +116,7 @@ namespace Emby.Drawing => _transparentImageTypes.Contains(Path.GetExtension(path)); /// - public async Task<(string path, string? mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options) + public async Task<(string Path, string? MimeType, DateTime DateModified)> ProcessImage(ImageProcessingOptions options) { ItemImageInfo originalImage = options.Image; BaseItem item = options.Item; @@ -135,14 +135,14 @@ namespace Emby.Drawing } var supportedImageInfo = await GetSupportedImage(originalImagePath, dateModified).ConfigureAwait(false); - originalImagePath = supportedImageInfo.path; + originalImagePath = supportedImageInfo.Path; if (!File.Exists(originalImagePath)) { return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); } - dateModified = supportedImageInfo.dateModified; + dateModified = supportedImageInfo.DateModified; bool requiresTransparency = _transparentImageTypes.Contains(Path.GetExtension(originalImagePath)); bool autoOrient = false; @@ -436,7 +436,7 @@ namespace Emby.Drawing .ToString("N", CultureInfo.InvariantCulture); } - private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified) + private async Task<(string Path, DateTime DateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified) { var inputFormat = Path.GetExtension(originalImagePath) .TrimStart('.') diff --git a/Emby.Naming/TV/SeasonPathParser.cs b/Emby.Naming/TV/SeasonPathParser.cs index 6236f86c4..fc9ee8e56 100644 --- a/Emby.Naming/TV/SeasonPathParser.cs +++ b/Emby.Naming/TV/SeasonPathParser.cs @@ -55,7 +55,7 @@ namespace Emby.Naming.TV /// if set to true [support special aliases]. /// if set to true [support numeric season folders]. /// System.Nullable{System.Int32}. - private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPath( + private static (int? SeasonNumber, bool IsSeasonFolder) GetSeasonNumberFromPath( string path, bool supportSpecialAliases, bool supportNumericSeasonFolders) @@ -99,7 +99,7 @@ namespace Emby.Naming.TV if (filename.Contains(name, StringComparison.OrdinalIgnoreCase)) { var result = GetSeasonNumberFromPathSubstring(filename.Replace(name, " ", StringComparison.OrdinalIgnoreCase)); - if (result.seasonNumber.HasValue) + if (result.SeasonNumber.HasValue) { return result; } @@ -142,7 +142,7 @@ namespace Emby.Naming.TV /// /// The path. /// System.Nullable{System.Int32}. - private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPathSubstring(ReadOnlySpan path) + private static (int? SeasonNumber, bool IsSeasonFolder) GetSeasonNumberFromPathSubstring(ReadOnlySpan path) { var numericStart = -1; var length = 0; diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 8702691d1..43c8a451b 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -130,16 +130,14 @@ namespace Emby.Server.Implementations.Channels var internalChannel = _libraryManager.GetItemById(item.ChannelId); if (internalChannel == null) { - throw new ArgumentException(); + throw new ArgumentException(nameof(item.ChannelId)); } var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id)); - var supportsDelete = channel as ISupportsDelete; - - if (supportsDelete == null) + if (channel is not ISupportsDelete supportsDelete) { - throw new ArgumentException(); + throw new ArgumentException(nameof(channel)); } return supportsDelete.DeleteItem(item.ExternalId, CancellationToken.None); diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index 79ef70fff..b5b8fea65 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -140,7 +140,7 @@ namespace Emby.Server.Implementations.Collections if (parentFolder == null) { - throw new ArgumentException(); + throw new ArgumentException(nameof(parentFolder)); } var path = Path.Combine(parentFolder.Path, folderName); diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index a6b48b212..47d10b0ac 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -248,40 +248,6 @@ namespace Emby.Server.Implementations.Data BaseItemKind.AudioBook }; - private static readonly Type[] _knownTypes = - { - typeof(LiveTvProgram), - typeof(LiveTvChannel), - typeof(Series), - typeof(Audio), - typeof(MusicAlbum), - typeof(MusicArtist), - typeof(MusicGenre), - typeof(MusicVideo), - typeof(Movie), - typeof(Playlist), - typeof(AudioBook), - typeof(Trailer), - typeof(BoxSet), - typeof(Episode), - typeof(Season), - typeof(Series), - typeof(Book), - typeof(CollectionFolder), - typeof(Folder), - typeof(Genre), - typeof(Person), - typeof(Photo), - typeof(PhotoAlbum), - typeof(Studio), - typeof(UserRootFolder), - typeof(UserView), - typeof(Video), - typeof(Year), - typeof(Channel), - typeof(AggregateFolder) - }; - private static readonly Dictionary _baseItemKindNames = new() { { BaseItemKind.AggregateFolder, typeof(AggregateFolder).FullName }, @@ -688,13 +654,13 @@ namespace Emby.Server.Implementations.Data connection.RunInTransaction( db => { - SaveItemsInTranscation(db, tuples); + SaveItemsInTransaction(db, tuples); }, TransactionMode); } } - private void SaveItemsInTranscation(IDatabaseConnection db, IEnumerable<(BaseItem, List, BaseItem, string, List)> tuples) + private void SaveItemsInTransaction(IDatabaseConnection db, IEnumerable<(BaseItem Item, List AncestorIds, BaseItem TopParent, string UserDataKey, List InheritedTags)> tuples) { var statements = PrepareAll(db, new string[] { @@ -713,17 +679,17 @@ namespace Emby.Server.Implementations.Data saveItemStatement.Reset(); } - var item = tuple.Item1; - var topParent = tuple.Item3; - var userDataKey = tuple.Item4; + var item = tuple.Item; + var topParent = tuple.TopParent; + var userDataKey = tuple.UserDataKey; SaveItem(item, topParent, userDataKey, saveItemStatement); - var inheritedTags = tuple.Item5; + var inheritedTags = tuple.InheritedTags; if (item.SupportsAncestors) { - UpdateAncestors(item.Id, tuple.Item2, db, deleteAncestorsStatement); + UpdateAncestors(item.Id, tuple.AncestorIds, db, deleteAncestorsStatement); } UpdateItemValues(item.Id, GetItemValuesToSave(item, inheritedTags), db); @@ -2201,7 +2167,7 @@ namespace Emby.Server.Implementations.Data return false; } - var sortingFields = new HashSet(query.OrderBy.Select(i => i.Item1), StringComparer.OrdinalIgnoreCase); + var sortingFields = new HashSet(query.OrderBy.Select(i => i.OrderBy), StringComparer.OrdinalIgnoreCase); return sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked) || sortingFields.Contains(ItemSortBy.IsPlayed) @@ -3049,88 +3015,86 @@ namespace Emby.Server.Implementations.Data return " ORDER BY " + string.Join(',', orderBy.Select(i => { - var columnMap = MapOrderByField(i.Item1, query); - - var sortOrder = i.Item2 == SortOrder.Ascending ? "ASC" : "DESC"; + var columnMap = MapOrderByField(i.OrderBy, query); - return columnMap.Item1 + " " + sortOrder; + return columnMap.SortBy + " " + columnMap.SortOrder; })); } - private (string, bool) MapOrderByField(string name, InternalItemsQuery query) + private (string SortBy, SortOrder SortOrder) MapOrderByField(string name, InternalItemsQuery query) { if (string.Equals(name, ItemSortBy.AirTime, StringComparison.OrdinalIgnoreCase)) { // TODO - return ("SortName", false); + return ("SortName", SortOrder.Descending); } else if (string.Equals(name, ItemSortBy.Runtime, StringComparison.OrdinalIgnoreCase)) { - return ("RuntimeTicks", false); + return ("RuntimeTicks", SortOrder.Descending); } else if (string.Equals(name, ItemSortBy.Random, StringComparison.OrdinalIgnoreCase)) { - return ("RANDOM()", false); + return ("RANDOM()", SortOrder.Descending); } else if (string.Equals(name, ItemSortBy.DatePlayed, StringComparison.OrdinalIgnoreCase)) { if (query.GroupBySeriesPresentationUniqueKey) { - return ("MAX(LastPlayedDate)", false); + return ("MAX(LastPlayedDate)", SortOrder.Descending); } - return ("LastPlayedDate", false); + return ("LastPlayedDate", SortOrder.Descending); } else if (string.Equals(name, ItemSortBy.PlayCount, StringComparison.OrdinalIgnoreCase)) { - return ("PlayCount", false); + return ("PlayCount", SortOrder.Descending); } else if (string.Equals(name, ItemSortBy.IsFavoriteOrLiked, StringComparison.OrdinalIgnoreCase)) { - return ("(Select Case When IsFavorite is null Then 0 Else IsFavorite End )", true); + return ("(Select Case When IsFavorite is null Then 0 Else IsFavorite End )", SortOrder.Ascending); } else if (string.Equals(name, ItemSortBy.IsFolder, StringComparison.OrdinalIgnoreCase)) { - return ("IsFolder", true); + return ("IsFolder", SortOrder.Ascending); } else if (string.Equals(name, ItemSortBy.IsPlayed, StringComparison.OrdinalIgnoreCase)) { - return ("played", true); + return ("played", SortOrder.Ascending); } else if (string.Equals(name, ItemSortBy.IsUnplayed, StringComparison.OrdinalIgnoreCase)) { - return ("played", false); + return ("played", SortOrder.Descending); } else if (string.Equals(name, ItemSortBy.DateLastContentAdded, StringComparison.OrdinalIgnoreCase)) { - return ("DateLastMediaAdded", false); + return ("DateLastMediaAdded", SortOrder.Descending); } else if (string.Equals(name, ItemSortBy.Artist, StringComparison.OrdinalIgnoreCase)) { - return ("(select CleanValue from itemvalues where ItemId=Guid and Type=0 LIMIT 1)", false); + return ("(select CleanValue from itemvalues where ItemId=Guid and Type=0 LIMIT 1)", SortOrder.Descending); } else if (string.Equals(name, ItemSortBy.AlbumArtist, StringComparison.OrdinalIgnoreCase)) { - return ("(select CleanValue from itemvalues where ItemId=Guid and Type=1 LIMIT 1)", false); + return ("(select CleanValue from itemvalues where ItemId=Guid and Type=1 LIMIT 1)", SortOrder.Descending); } else if (string.Equals(name, ItemSortBy.OfficialRating, StringComparison.OrdinalIgnoreCase)) { - return ("InheritedParentalRatingValue", false); + return ("InheritedParentalRatingValue", SortOrder.Descending); } else if (string.Equals(name, ItemSortBy.Studio, StringComparison.OrdinalIgnoreCase)) { - return ("(select CleanValue from itemvalues where ItemId=Guid and Type=3 LIMIT 1)", false); + return ("(select CleanValue from itemvalues where ItemId=Guid and Type=3 LIMIT 1)", SortOrder.Descending); } else if (string.Equals(name, ItemSortBy.SeriesDatePlayed, StringComparison.OrdinalIgnoreCase)) { - return ("(Select MAX(LastPlayedDate) from TypedBaseItems B" + GetJoinUserDataText(query) + " where Played=1 and B.SeriesPresentationUniqueKey=A.PresentationUniqueKey)", false); + return ("(Select MAX(LastPlayedDate) from TypedBaseItems B" + GetJoinUserDataText(query) + " where Played=1 and B.SeriesPresentationUniqueKey=A.PresentationUniqueKey)", SortOrder.Descending); } else if (string.Equals(name, ItemSortBy.SeriesSortName, StringComparison.OrdinalIgnoreCase)) { - return ("SeriesName", false); + return ("SeriesName", SortOrder.Descending); } - return (name, false); + return (name, SortOrder.Descending); } public List GetItemIdsList(InternalItemsQuery query) @@ -5230,32 +5194,32 @@ AND Type = @InternalPersonType)"); } } - public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query) + public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery query) { return GetItemValues(query, new[] { 0, 1 }, typeof(MusicArtist).FullName); } - public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query) + public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery query) { return GetItemValues(query, new[] { 0 }, typeof(MusicArtist).FullName); } - public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query) + public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery query) { return GetItemValues(query, new[] { 1 }, typeof(MusicArtist).FullName); } - public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query) + public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery query) { return GetItemValues(query, new[] { 3 }, typeof(Studio).FullName); } - public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query) + public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery query) { return GetItemValues(query, new[] { 2 }, typeof(Genre).FullName); } - public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query) + public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery query) { return GetItemValues(query, new[] { 2 }, typeof(MusicGenre).FullName); } @@ -5351,7 +5315,7 @@ AND Type = @InternalPersonType)"); return list; } - private QueryResult<(BaseItem, ItemCounts)> GetItemValues(InternalItemsQuery query, int[] itemValueTypes, string returnType) + private QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetItemValues(InternalItemsQuery query, int[] itemValueTypes, string returnType) { if (query == null) { @@ -5676,7 +5640,7 @@ AND Type = @InternalPersonType)"); return counts; } - private List<(int, string)> GetItemValuesToSave(BaseItem item, List inheritedTags) + private List<(int MagicNumber, string Value)> GetItemValuesToSave(BaseItem item, List inheritedTags) { var list = new List<(int, string)>(); @@ -5701,7 +5665,7 @@ AND Type = @InternalPersonType)"); return list; } - private void UpdateItemValues(Guid itemId, List<(int, string)> values, IDatabaseConnection db) + private void UpdateItemValues(Guid itemId, List<(int MagicNumber, string Value)> values, IDatabaseConnection db) { if (itemId.Equals(Guid.Empty)) { @@ -5723,7 +5687,7 @@ AND Type = @InternalPersonType)"); InsertItemValues(guidBlob, values, db); } - private void InsertItemValues(byte[] idBlob, List<(int, string)> values, IDatabaseConnection db) + private void InsertItemValues(byte[] idBlob, List<(int MagicNumber, string Value)> values, IDatabaseConnection db) { const int Limit = 100; var startIndex = 0; @@ -5755,7 +5719,7 @@ AND Type = @InternalPersonType)"); var currentValueInfo = values[i]; - var itemValue = currentValueInfo.Item2; + var itemValue = currentValueInfo.Value; // Don't save if invalid if (string.IsNullOrWhiteSpace(itemValue)) @@ -5763,7 +5727,7 @@ AND Type = @InternalPersonType)"); continue; } - statement.TryBind("@Type" + index, currentValueInfo.Item1); + statement.TryBind("@Type" + index, currentValueInfo.MagicNumber); statement.TryBind("@Value" + index, itemValue); statement.TryBind("@CleanValue" + index, GetCleanValue(itemValue)); } diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index 4a5f72327..d43996c69 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -464,6 +464,7 @@ namespace Emby.Server.Implementations.EntryPoints public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } /// diff --git a/Emby.Server.Implementations/IO/StreamHelper.cs b/Emby.Server.Implementations/IO/StreamHelper.cs index e4f5f4cf0..f55c16d6d 100644 --- a/Emby.Server.Implementations/IO/StreamHelper.cs +++ b/Emby.Server.Implementations/IO/StreamHelper.cs @@ -17,11 +17,11 @@ namespace Emby.Server.Implementations.IO try { int read; - while ((read = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) + while ((read = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0) { cancellationToken.ThrowIfCancellationRequested(); - await destination.WriteAsync(buffer, 0, read, cancellationToken).ConfigureAwait(false); + await destination.WriteAsync(buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false); if (onStarted != null) { @@ -44,11 +44,11 @@ namespace Emby.Server.Implementations.IO if (emptyReadLimit <= 0) { int read; - while ((read = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) + while ((read = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0) { cancellationToken.ThrowIfCancellationRequested(); - await destination.WriteAsync(buffer, 0, read, cancellationToken).ConfigureAwait(false); + await destination.WriteAsync(buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false); } return; @@ -60,7 +60,7 @@ namespace Emby.Server.Implementations.IO { cancellationToken.ThrowIfCancellationRequested(); - var bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); + var bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); if (bytesRead == 0) { @@ -71,7 +71,7 @@ namespace Emby.Server.Implementations.IO { eofCount = 0; - await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); + await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false); } } } @@ -88,13 +88,13 @@ namespace Emby.Server.Implementations.IO { int bytesRead; - while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) + while ((bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0) { var bytesToWrite = Math.Min(bytesRead, copyLength); if (bytesToWrite > 0) { - await destination.WriteAsync(buffer, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); + await destination.WriteAsync(buffer.AsMemory(0, Convert.ToInt32(bytesToWrite)), cancellationToken).ConfigureAwait(false); } copyLength -= bytesToWrite; @@ -137,9 +137,9 @@ namespace Emby.Server.Implementations.IO int bytesRead; int totalBytesRead = 0; - while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) + while ((bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0) { - await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); + await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false); totalBytesRead += bytesRead; } diff --git a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs index 7e12ebb08..7958eb8f5 100644 --- a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs +++ b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs @@ -68,9 +68,9 @@ namespace Emby.Server.Implementations.Images DtoOptions = new DtoOptions(false), ImageTypes = new ImageType[] { ImageType.Primary }, Limit = 8, - OrderBy = new ValueTuple[] + OrderBy = new[] { - new ValueTuple(ItemSortBy.Random, SortOrder.Ascending) + (ItemSortBy.Random, SortOrder.Ascending) }, IncludeItemTypes = includeItemTypes }); diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index e30fa7097..0fa779a0a 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1373,7 +1373,7 @@ namespace Emby.Server.Implementations.Library return _itemRepository.GetItemIdsList(query); } - public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query) + public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery query) { if (query.User != null) { @@ -1384,7 +1384,7 @@ namespace Emby.Server.Implementations.Library return _itemRepository.GetStudios(query); } - public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query) + public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery query) { if (query.User != null) { @@ -1395,7 +1395,7 @@ namespace Emby.Server.Implementations.Library return _itemRepository.GetGenres(query); } - public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query) + public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery query) { if (query.User != null) { @@ -1406,7 +1406,7 @@ namespace Emby.Server.Implementations.Library return _itemRepository.GetMusicGenres(query); } - public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query) + public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery query) { if (query.User != null) { @@ -1417,7 +1417,7 @@ namespace Emby.Server.Implementations.Library return _itemRepository.GetAllArtists(query); } - public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query) + public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery query) { if (query.User != null) { @@ -1458,7 +1458,7 @@ namespace Emby.Server.Implementations.Library } } - public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query) + public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery query) { if (query.User != null) { @@ -1757,7 +1757,7 @@ namespace Emby.Server.Implementations.Library return orderedItems ?? items; } - public IEnumerable Sort(IEnumerable items, User user, IEnumerable> orderBy) + public IEnumerable Sort(IEnumerable items, User user, IEnumerable<(string OrderBy, SortOrder SortOrder)> orderBy) { var isFirst = true; diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 972d4ebbb..a414e7e16 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -464,12 +464,11 @@ namespace Emby.Server.Implementations.Library try { - var tuple = GetProvider(request.OpenToken); - var provider = tuple.Item1; + var (provider, keyId) = GetProvider(request.OpenToken); var currentLiveStreams = _openStreams.Values.ToList(); - liveStream = await provider.OpenMediaSource(tuple.Item2, currentLiveStreams, cancellationToken).ConfigureAwait(false); + liveStream = await provider.OpenMediaSource(keyId, currentLiveStreams, cancellationToken).ConfigureAwait(false); mediaSource = liveStream.MediaSource; @@ -829,7 +828,7 @@ namespace Emby.Server.Implementations.Library } } - private (IMediaSourceProvider, string) GetProvider(string key) + private (IMediaSourceProvider MediaSourceProvider, string KeyId) GetProvider(string key) { if (string.IsNullOrEmpty(key)) { diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index 4aacf7774..55911933a 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -190,7 +190,7 @@ namespace Emby.Server.Implementations.Library searchQuery.ParentId = Guid.Empty; searchQuery.IncludeItemsByName = true; searchQuery.IncludeItemTypes = Array.Empty(); - mediaItems = _libraryManager.GetAllArtists(searchQuery).Items.Select(i => i.Item1).ToList(); + mediaItems = _libraryManager.GetAllArtists(searchQuery).Items.Select(i => i.Item).ToList(); } else { diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 2fbd47bc7..a8440102d 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -643,7 +643,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings CancellationToken cancellationToken) { using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/token"); +#pragma warning disable CA5350 // SchedulesDirect is always SHA1. var hashedPasswordBytes = SHA1.HashData(Encoding.ASCII.GetBytes(password)); +#pragma warning restore CA5350 // TODO: remove ToLower when Convert.ToHexString supports lowercase // Schedules Direct requires the hex to be lowercase string hashedPassword = Convert.ToHexString(hashedPasswordBytes).ToLowerInvariant(); diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 047d8e98c..aa3598c8b 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -207,7 +207,7 @@ namespace Emby.Server.Implementations.LiveTv orderBy.Insert(0, (ItemSortBy.IsFavoriteOrLiked, SortOrder.Descending)); } - if (!internalQuery.OrderBy.Any(i => string.Equals(i.Item1, ItemSortBy.SortName, StringComparison.OrdinalIgnoreCase))) + if (!internalQuery.OrderBy.Any(i => string.Equals(i.OrderBy, ItemSortBy.SortName, StringComparison.OrdinalIgnoreCase))) { orderBy.Add((ItemSortBy.SortName, SortOrder.Ascending)); } @@ -520,7 +520,7 @@ namespace Emby.Server.Implementations.LiveTv return item; } - private (LiveTvProgram item, bool isNew, bool isUpdated) GetProgram(ProgramInfo info, Dictionary allExistingPrograms, LiveTvChannel channel) + private (LiveTvProgram Item, bool IsNew, bool IsUpdated) GetProgram(ProgramInfo info, Dictionary allExistingPrograms, LiveTvChannel channel) { var id = _tvDtoService.GetInternalProgramId(info.Id); @@ -779,9 +779,9 @@ namespace Emby.Server.Implementations.LiveTv var dto = _dtoService.GetBaseItemDto(program, new DtoOptions(), user); - var list = new List> + var list = new List<(BaseItemDto ItemDto, string ExternalId, string ExternalSeriesId)> { - new Tuple(dto, program.ExternalId, program.ExternalSeriesId) + (dto, program.ExternalId, program.ExternalSeriesId) }; await AddRecordingInfo(list, cancellationToken).ConfigureAwait(false); @@ -976,16 +976,16 @@ namespace Emby.Server.Implementations.LiveTv return score; } - private async Task AddRecordingInfo(IEnumerable> programs, CancellationToken cancellationToken) + private async Task AddRecordingInfo(IEnumerable<(BaseItemDto ItemDto, string ExternalId, string ExternalSeriesId)> programs, CancellationToken cancellationToken) { IReadOnlyList timerList = null; IReadOnlyList seriesTimerList = null; foreach (var programTuple in programs) { - var program = programTuple.Item1; - var externalProgramId = programTuple.Item2; - string externalSeriesId = programTuple.Item3; + var program = programTuple.ItemDto; + var externalProgramId = programTuple.ExternalId; + string externalSeriesId = programTuple.ExternalSeriesId; timerList ??= (await GetTimersInternal(new TimerQuery(), cancellationToken).ConfigureAwait(false)).Items; @@ -1186,13 +1186,13 @@ namespace Emby.Server.Implementations.LiveTv foreach (var program in channelPrograms) { var programTuple = GetProgram(program, existingPrograms, currentChannel); - var programItem = programTuple.item; + var programItem = programTuple.Item; - if (programTuple.isNew) + if (programTuple.IsNew) { newPrograms.Add(programItem); } - else if (programTuple.isUpdated) + else if (programTuple.IsUpdated) { updatedPrograms.Add(programItem); } @@ -1423,9 +1423,9 @@ namespace Emby.Server.Implementations.LiveTv return result; } - public Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> programs, IReadOnlyList fields, User user = null) + public Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem Item, BaseItemDto ItemDto)> programs, IReadOnlyList fields, User user = null) { - var programTuples = new List>(); + var programTuples = new List<(BaseItemDto Dto, string ExternalId, string ExternalSeriesId)>(); var hasChannelImage = fields.Contains(ItemFields.ChannelImage); var hasChannelInfo = fields.Contains(ItemFields.ChannelInfo); @@ -1461,7 +1461,7 @@ namespace Emby.Server.Implementations.LiveTv } } - programTuples.Add(new Tuple(dto, program.ExternalId, program.ExternalSeriesId)); + programTuples.Add((dto, program.ExternalId, program.ExternalSeriesId)); } return AddRecordingInfo(programTuples, CancellationToken.None); @@ -1868,11 +1868,11 @@ namespace Emby.Server.Implementations.LiveTv return _libraryManager.GetItemById(internalChannelId); } - public void AddChannelInfo(IReadOnlyCollection<(BaseItemDto, LiveTvChannel)> items, DtoOptions options, User user) + public void AddChannelInfo(IReadOnlyCollection<(BaseItemDto ItemDto, LiveTvChannel Channel)> items, DtoOptions options, User user) { var now = DateTime.UtcNow; - var channelIds = items.Select(i => i.Item2.Id).Distinct().ToArray(); + var channelIds = items.Select(i => i.Channel.Id).Distinct().ToArray(); var programs = options.AddCurrentProgram ? _libraryManager.GetItemList(new InternalItemsQuery(user) { @@ -1893,11 +1893,8 @@ namespace Emby.Server.Implementations.LiveTv var addCurrentProgram = options.AddCurrentProgram; - foreach (var tuple in items) + foreach (var (dto, channel) in items) { - var dto = tuple.Item1; - var channel = tuple.Item2; - dto.Number = channel.Number; dto.ChannelNumber = channel.Number; dto.ChannelType = channel.ChannelType; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunChannelCommands.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunChannelCommands.cs index 069b4fab6..aae33503f 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunChannelCommands.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunChannelCommands.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun _profile = profile; } - public IEnumerable<(string, string)> GetCommands() + public IEnumerable<(string CommandName, string CommandValue)> GetCommands() { if (!string.IsNullOrEmpty(_channel)) { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index f009c77cf..f1a6ef344 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -114,7 +114,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun foreach (var command in commands.GetCommands()) { - var channelMsgLen = WriteSetMessage(buffer, i, command.Item1, command.Item2, lockKeyValue); + var channelMsgLen = WriteSetMessage(buffer, i, command.CommandName, command.CommandValue, lockKeyValue); await stream.WriteAsync(buffer.AsMemory(0, channelMsgLen), cancellationToken).ConfigureAwait(false); receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); @@ -167,7 +167,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { foreach (var command in commandList) { - var channelMsgLen = WriteSetMessage(buffer, _activeTuner, command.Item1, command.Item2, _lockkey); + var channelMsgLen = WriteSetMessage(buffer, _activeTuner, command.CommandName, command.CommandValue, _lockkey); await stream.WriteAsync(buffer.AsMemory(0, channelMsgLen), cancellationToken).ConfigureAwait(false); int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index d2f033439..9ed0d8d73 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -212,7 +212,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun if (read > 0) { - fileStream.Write(buffer, RtpHeaderBytes, read); + await fileStream.WriteAsync(buffer.AsMemory(RtpHeaderBytes, read), linkedSource.Token).ConfigureAwait(false); } if (!resolved) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/IHdHomerunChannelCommands.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/IHdHomerunChannelCommands.cs index 153354932..11bd40ab1 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/IHdHomerunChannelCommands.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/IHdHomerunChannelCommands.cs @@ -6,6 +6,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { public interface IHdHomerunChannelCommands { - IEnumerable<(string, string)> GetCommands(); + IEnumerable<(string CommandName, string CommandValue)> GetCommands(); } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/LegacyHdHomerunChannelCommands.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/LegacyHdHomerunChannelCommands.cs index 26627b8aa..80d9d0724 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/LegacyHdHomerunChannelCommands.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/LegacyHdHomerunChannelCommands.cs @@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } } - public IEnumerable<(string, string)> GetCommands() + public IEnumerable<(string CommandName, string CommandValue)> GetCommands() { if (!string.IsNullOrEmpty(_channel)) { diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 9481e26f7..02df2fffe 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -72,7 +72,7 @@ namespace Emby.Server.Implementations.Playlists var parentFolder = GetPlaylistsFolder(Guid.Empty); if (parentFolder == null) { - throw new ArgumentException(); + throw new ArgumentException(nameof(parentFolder)); } if (string.IsNullOrEmpty(options.MediaType)) diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index 5dbe9f44d..c994ffc90 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -117,7 +117,7 @@ namespace Emby.Server.Implementations.TV new InternalItemsQuery(user) { IncludeItemTypes = new[] { BaseItemKind.Episode }, - OrderBy = new[] { new ValueTuple(ItemSortBy.DatePlayed, SortOrder.Descending) }, + OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending) }, SeriesPresentationUniqueKey = presentationUniqueKey, Limit = limit, DtoOptions = new DtoOptions { Fields = new[] { ItemFields.SeriesPresentationUniqueKey }, EnableImages = false }, @@ -193,7 +193,7 @@ namespace Emby.Server.Implementations.TV AncestorWithPresentationUniqueKey = null, SeriesPresentationUniqueKey = seriesKey, IncludeItemTypes = new[] { BaseItemKind.Episode }, - OrderBy = new[] { new ValueTuple(ItemSortBy.SortName, SortOrder.Descending) }, + OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Descending) }, IsPlayed = true, Limit = 1, ParentIndexNumberNotEquals = 0, @@ -211,7 +211,7 @@ namespace Emby.Server.Implementations.TV AncestorWithPresentationUniqueKey = null, SeriesPresentationUniqueKey = seriesKey, IncludeItemTypes = new[] { BaseItemKind.Episode }, - OrderBy = new[] { new ValueTuple(ItemSortBy.SortName, SortOrder.Ascending) }, + OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }, Limit = 1, IsPlayed = false, IsVirtualItem = false, diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 24d592525..5eb4c9ffa 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -52,7 +52,7 @@ namespace Emby.Server.Implementations.Updates /// /// The current installations. /// - private readonly List<(InstallationInfo info, CancellationTokenSource token)> _currentInstallations; + private readonly List<(InstallationInfo Info, CancellationTokenSource Token)> _currentInstallations; /// /// The completed installations. @@ -399,13 +399,13 @@ namespace Emby.Server.Implementations.Updates { lock (_currentInstallationsLock) { - var install = _currentInstallations.Find(x => x.info.Id == id); + var install = _currentInstallations.Find(x => x.Info.Id == id); if (install == default((InstallationInfo, CancellationTokenSource))) { return false; } - install.token.Cancel(); + install.Token.Cancel(); _currentInstallations.Remove(install); return true; } diff --git a/Jellyfin.Api/Controllers/FilterController.cs b/Jellyfin.Api/Controllers/FilterController.cs index e170436d1..a4f12666d 100644 --- a/Jellyfin.Api/Controllers/FilterController.cs +++ b/Jellyfin.Api/Controllers/FilterController.cs @@ -197,16 +197,16 @@ namespace Jellyfin.Api.Controllers { filters.Genres = _libraryManager.GetMusicGenres(genreQuery).Items.Select(i => new NameGuidPair { - Name = i.Item1.Name, - Id = i.Item1.Id + Name = i.Item.Name, + Id = i.Item.Id }).ToArray(); } else { filters.Genres = _libraryManager.GetGenres(genreQuery).Items.Select(i => new NameGuidPair { - Name = i.Item1.Name, - Id = i.Item1.Id + Name = i.Item.Name, + Id = i.Item.Id }).ToArray(); } diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 65c0662d2..1b938f4d5 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -484,7 +484,7 @@ namespace Jellyfin.Api.Controllers // Albums by artist if (query.ArtistIds.Length > 0 && query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes[0] == BaseItemKind.MusicAlbum) { - query.OrderBy = new[] { new ValueTuple(ItemSortBy.ProductionYear, SortOrder.Descending), new ValueTuple(ItemSortBy.SortName, SortOrder.Ascending) }; + query.OrderBy = new[] { (ItemSortBy.ProductionYear, SortOrder.Descending), (ItemSortBy.SortName, SortOrder.Ascending) }; } } diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs index ca8bc0bdd..1471f5a24 100644 --- a/Jellyfin.Api/Helpers/RequestHelpers.cs +++ b/Jellyfin.Api/Helpers/RequestHelpers.cs @@ -30,7 +30,7 @@ namespace Jellyfin.Api.Helpers { if (sortBy.Count == 0) { - return Array.Empty>(); + return Array.Empty<(string, SortOrder)>(); } var result = new (string, SortOrder)[sortBy.Count]; diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 3d0a51ff6..c41b343c7 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -394,12 +394,12 @@ namespace Jellyfin.Server.Implementations.Users var user = Users.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase)); var authResult = await AuthenticateLocalUser(username, password, user, remoteEndPoint) .ConfigureAwait(false); - var authenticationProvider = authResult.authenticationProvider; - var success = authResult.success; + var authenticationProvider = authResult.AuthenticationProvider; + var success = authResult.Success; if (user == null) { - string updatedUsername = authResult.username; + string updatedUsername = authResult.Username; if (success && authenticationProvider != null @@ -785,7 +785,7 @@ namespace Jellyfin.Server.Implementations.Users return providers; } - private async Task<(IAuthenticationProvider? authenticationProvider, string username, bool success)> AuthenticateLocalUser( + private async Task<(IAuthenticationProvider? AuthenticationProvider, string Username, bool Success)> AuthenticateLocalUser( string username, string password, User? user, @@ -798,8 +798,8 @@ namespace Jellyfin.Server.Implementations.Users { var providerAuthResult = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false); - var updatedUsername = providerAuthResult.username; - success = providerAuthResult.success; + var updatedUsername = providerAuthResult.Username; + success = providerAuthResult.Success; if (success) { @@ -822,7 +822,7 @@ namespace Jellyfin.Server.Implementations.Users return (authenticationProvider, username, success); } - private async Task<(string username, bool success)> AuthenticateWithProvider( + private async Task<(string Username, bool Success)> AuthenticateWithProvider( IAuthenticationProvider provider, string username, string password, diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs index a951f751e..5e601ca84 100644 --- a/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs +++ b/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs @@ -53,7 +53,7 @@ public class CreateNetworkConfiguration : IMigrationRoutine networkConfigSerializer.Serialize(xmlWriter, networkSettings); } -#pragma warning disable CS1591 +#pragma warning disable public sealed class OldNetworkConfiguration { public const int DefaultHttpPort = 8096; @@ -134,5 +134,5 @@ public class CreateNetworkConfiguration : IMigrationRoutine public string[] KnownProxies { get; set; } = Array.Empty(); } -#pragma warning restore CS1591 +#pragma warning restore } diff --git a/MediaBrowser.Common/Net/IPNetAddress.cs b/MediaBrowser.Common/Net/IPNetAddress.cs index f6e3971bf..f1428d4be 100644 --- a/MediaBrowser.Common/Net/IPNetAddress.cs +++ b/MediaBrowser.Common/Net/IPNetAddress.cs @@ -195,7 +195,7 @@ namespace MediaBrowser.Common.Net return NetworkAddress.PrefixLength <= netaddrObj.PrefixLength; } - var altAddress = NetworkAddressOf(netaddrObj.Address, PrefixLength).address; + var altAddress = NetworkAddressOf(netaddrObj.Address, PrefixLength).Address; return NetworkAddress.Address.Equals(altAddress); } diff --git a/MediaBrowser.Common/Net/IPObject.cs b/MediaBrowser.Common/Net/IPObject.cs index 2612268fd..bd5368882 100644 --- a/MediaBrowser.Common/Net/IPObject.cs +++ b/MediaBrowser.Common/Net/IPObject.cs @@ -53,7 +53,7 @@ namespace MediaBrowser.Common.Net /// IP Address to convert. /// Subnet prefix. /// IPAddress. - public static (IPAddress address, byte prefixLength) NetworkAddressOf(IPAddress address, byte prefixLength) + public static (IPAddress Address, byte PrefixLength) NetworkAddressOf(IPAddress address, byte prefixLength) { if (address == null) { diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index 7ca0e851b..03882a0b9 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -75,7 +75,7 @@ namespace MediaBrowser.Controller.Drawing /// /// The options. /// Task. - Task<(string path, string? mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options); + Task<(string Path, string? MimeType, DateTime DateModified)> ProcessImage(ImageProcessingOptions options); /// /// Gets the supported image output formats. diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index 9d0187c8c..29f7bf92b 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -135,7 +135,7 @@ namespace MediaBrowser.Controller.Entities.Audio return info; } - protected override IEnumerable<(BaseItem, MediaSourceType)> GetAllItemsForMediaSources() + protected override IEnumerable<(BaseItem Item, MediaSourceType MediaSourceType)> GetAllItemsForMediaSources() => new[] { ((BaseItem)this, MediaSourceType.Default) }; } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index f5dd82548..915971adc 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1069,7 +1069,7 @@ namespace MediaBrowser.Controller.Entities } var list = GetAllItemsForMediaSources(); - var result = list.Select(i => GetVersionInfo(enablePathSubstitution, i.Item1, i.Item2)).ToList(); + var result = list.Select(i => GetVersionInfo(enablePathSubstitution, i.Item, i.MediaSourceType)).ToList(); if (IsActiveRecording()) { @@ -1097,7 +1097,7 @@ namespace MediaBrowser.Controller.Entities .ToList(); } - protected virtual IEnumerable<(BaseItem, MediaSourceType)> GetAllItemsForMediaSources() + protected virtual IEnumerable<(BaseItem Item, MediaSourceType MediaSourceType)> GetAllItemsForMediaSources() { return Enumerable.Empty<(BaseItem, MediaSourceType)>(); } diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index f06b5c787..db1697c79 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -38,7 +38,7 @@ namespace MediaBrowser.Controller.Entities MediaTypes = Array.Empty(); MinSimilarityScore = 20; OfficialRatings = Array.Empty(); - OrderBy = Array.Empty>(); + OrderBy = Array.Empty<(string, SortOrder)>(); PersonIds = Array.Empty(); PersonTypes = Array.Empty(); PresetViews = Array.Empty(); @@ -271,7 +271,7 @@ namespace MediaBrowser.Controller.Entities public bool? HasChapterImages { get; set; } - public IReadOnlyList<(string, SortOrder)> OrderBy { get; set; } + public IReadOnlyList<(string OrderBy, SortOrder SortOrder)> OrderBy { get; set; } public DateTime? MinDateCreated { get; set; } diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 4f7614f96..3e125602a 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -517,7 +517,7 @@ namespace MediaBrowser.Controller.Entities }).FirstOrDefault(); } - protected override IEnumerable<(BaseItem, MediaSourceType)> GetAllItemsForMediaSources() + protected override IEnumerable<(BaseItem Item, MediaSourceType MediaSourceType)> GetAllItemsForMediaSources() { var list = new List<(BaseItem, MediaSourceType)> { diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index eba92695e..8db528330 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -212,7 +212,7 @@ namespace MediaBrowser.Controller.Library /// IEnumerable{BaseItem}. IEnumerable Sort(IEnumerable items, User user, IEnumerable sortBy, SortOrder sortOrder); - IEnumerable Sort(IEnumerable items, User user, IEnumerable> orderBy); + IEnumerable Sort(IEnumerable items, User user, IEnumerable<(string OrderBy, SortOrder SortOrder)> orderBy); /// /// Gets the user root folder. @@ -573,17 +573,17 @@ namespace MediaBrowser.Controller.Library void RemoveMediaPath(string virtualFolderName, string mediaPath); - QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query); + QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery query); - QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query); + QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery query); - QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query); + QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery query); - QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query); + QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery query); - QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query); + QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery query); - QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query); + QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery query); int GetCount(InternalItemsQuery query); diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index dbd18165d..6dc5665b2 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -251,7 +251,7 @@ namespace MediaBrowser.Controller.LiveTv /// The fields. /// The user. /// Task. - Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> programs, IReadOnlyList fields, User user = null); + Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem Item, BaseItemDto ItemDto)> programs, IReadOnlyList fields, User user = null); /// /// Saves the tuner host. @@ -292,7 +292,7 @@ namespace MediaBrowser.Controller.LiveTv /// The items. /// The options. /// The user. - void AddChannelInfo(IReadOnlyCollection<(BaseItemDto, LiveTvChannel)> items, DtoOptions options, User user); + void AddChannelInfo(IReadOnlyCollection<(BaseItemDto ItemDto, LiveTvChannel Channel)> items, DtoOptions options, User user); Task> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken); diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 8c5474539..d6f69a150 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -21,6 +21,13 @@ namespace MediaBrowser.Controller.MediaEncoding { public class EncodingHelper { + private const string QsvAlias = "qs"; + private const string VaapiAlias = "va"; + private const string D3d11vaAlias = "dx11"; + private const string VideotoolboxAlias = "vt"; + private const string OpenclAlias = "ocl"; + private const string CudaAlias = "cu"; + private readonly IMediaEncoder _mediaEncoder; private readonly ISubtitleEncoder _subtitleEncoder; @@ -42,13 +49,6 @@ namespace MediaBrowser.Controller.MediaEncoding "Main10" }; - private const string QsvAlias = "qs"; - private const string VaapiAlias = "va"; - private const string D3d11vaAlias = "dx11"; - private const string VideotoolboxAlias = "vt"; - private const string OpenclAlias = "ocl"; - private const string CudaAlias = "cu"; - public EncodingHelper( IMediaEncoder mediaEncoder, ISubtitleEncoder subtitleEncoder) @@ -2290,7 +2290,7 @@ namespace MediaBrowser.Controller.MediaEncoding return returnFirstIfNoIndex ? streams.FirstOrDefault() : null; } - public static (int? width, int? height) GetFixedOutputSize( + public static (int? Width, int? Height) GetFixedOutputSize( int? videoWidth, int? videoHeight, int? requestedWidth, @@ -2671,7 +2671,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// Encoding options. /// Video encoder to use. /// The tuple contains three lists: main, sub and overlay filters. - public (List, List, List) GetSwVidFilterChain( + public (List MainFilters, List SubFilters, List OverlayFilters) GetSwVidFilterChain( EncodingJobInfo state, EncodingOptions options, string vidEncoder) @@ -2751,7 +2751,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// Encoding options. /// Video encoder to use. /// The tuple contains three lists: main, sub and overlay filters. - public (List, List, List) GetNvidiaVidFilterChain( + public (List MainFilters, List SubFilters, List OverlayFilters) GetNvidiaVidFilterChain( EncodingJobInfo state, EncodingOptions options, string vidEncoder) @@ -2778,7 +2778,7 @@ namespace MediaBrowser.Controller.MediaEncoding return GetNvidiaVidFiltersPrefered(state, options, vidDecoder, vidEncoder); } - public (List, List, List) GetNvidiaVidFiltersPrefered( + public (List MainFilters, List SubFilters, List OverlayFilters) GetNvidiaVidFiltersPrefered( EncodingJobInfo state, EncodingOptions options, string vidDecoder, @@ -2838,6 +2838,7 @@ namespace MediaBrowser.Controller.MediaEncoding mainFilters.Add("hwupload"); } } + if (isNvdecDecoder) { // INPUT cuda surface(vram) @@ -2938,7 +2939,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// Encoding options. /// Video encoder to use. /// The tuple contains three lists: main, sub and overlay filters. - public (List, List, List) GetAmdVidFilterChain( + public (List MainFilters, List SubFilters, List OverlayFilters) GetAmdVidFilterChain( EncodingJobInfo state, EncodingOptions options, string vidEncoder) @@ -2966,7 +2967,7 @@ namespace MediaBrowser.Controller.MediaEncoding return GetAmdDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder); } - public (List, List, List) GetAmdDx11VidFiltersPrefered( + public (List MainFilters, List SubFilters, List OverlayFilters) GetAmdDx11VidFiltersPrefered( EncodingJobInfo state, EncodingOptions options, string vidDecoder, @@ -3136,7 +3137,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// Encoding options. /// Video encoder to use. /// The tuple contains three lists: main, sub and overlay filters. - public (List, List, List) GetIntelVidFilterChain( + public (List MainFilters, List SubFilters, List OverlayFilters) GetIntelVidFilterChain( EncodingJobInfo state, EncodingOptions options, string vidEncoder) @@ -3182,7 +3183,7 @@ namespace MediaBrowser.Controller.MediaEncoding return (null, null, null); } - public (List, List, List) GetIntelQsvDx11VidFiltersPrefered( + public (List MainFilters, List SubFilters, List OverlayFilters) GetIntelQsvDx11VidFiltersPrefered( EncodingJobInfo state, EncodingOptions options, string vidDecoder, @@ -3374,7 +3375,7 @@ namespace MediaBrowser.Controller.MediaEncoding return (mainFilters, subFilters, overlayFilters); } - public (List, List, List) GetIntelQsvVaapiVidFiltersPrefered( + public (List MainFilters, List SubFilters, List OverlayFilters) GetIntelQsvVaapiVidFiltersPrefered( EncodingJobInfo state, EncodingOptions options, string vidDecoder, @@ -3589,7 +3590,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// Encoding options. /// Video encoder to use. /// The tuple contains three lists: main, sub and overlay filters. - public (List, List, List) GetVaapiVidFilterChain( + public (List MainFilters, List SubFilters, List OverlayFilters) GetVaapiVidFilterChain( EncodingJobInfo state, EncodingOptions options, string vidEncoder) @@ -3615,13 +3616,13 @@ namespace MediaBrowser.Controller.MediaEncoding if (!isSwEncoder) { var newfilters = new List(); - var noOverlay = swFilterChain.Item3.Count == 0; - newfilters.AddRange(noOverlay ? swFilterChain.Item1 : swFilterChain.Item3); + var noOverlay = swFilterChain.OverlayFilters.Count == 0; + newfilters.AddRange(noOverlay ? swFilterChain.MainFilters : swFilterChain.OverlayFilters); newfilters.Add("hwupload"); - var mainFilters = noOverlay ? newfilters : swFilterChain.Item1; - var overlayFilters = noOverlay ? swFilterChain.Item3 : newfilters; - return (mainFilters, swFilterChain.Item2, overlayFilters); + var mainFilters = noOverlay ? newfilters : swFilterChain.MainFilters; + var overlayFilters = noOverlay ? swFilterChain.OverlayFilters : newfilters; + return (mainFilters, swFilterChain.SubFilters, overlayFilters); } return swFilterChain; @@ -3638,7 +3639,7 @@ namespace MediaBrowser.Controller.MediaEncoding return GetVaapiLimitedVidFiltersPrefered(state, options, vidDecoder, vidEncoder); } - public (List, List, List) GetVaapiFullVidFiltersPrefered( + public (List MainFilters, List SubFilters, List OverlayFilters) GetVaapiFullVidFiltersPrefered( EncodingJobInfo state, EncodingOptions options, string vidDecoder, @@ -3834,7 +3835,7 @@ namespace MediaBrowser.Controller.MediaEncoding return (mainFilters, subFilters, overlayFilters); } - public (List, List, List) GetVaapiLimitedVidFiltersPrefered( + public (List MainFilters, List SubFilters, List OverlayFilters) GetVaapiLimitedVidFiltersPrefered( EncodingJobInfo state, EncodingOptions options, string vidDecoder, @@ -4090,7 +4091,6 @@ namespace MediaBrowser.Controller.MediaEncoding "{0}", string.Join(',', overlayFilters)); - var mapPrefix = Convert.ToInt32(state.SubtitleStream.IsExternal); var subtitleStreamIndex = state.SubtitleStream.IsExternal ? 0 diff --git a/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs b/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs index c38e7ec3b..4e7e26624 100644 --- a/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs +++ b/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs @@ -12,7 +12,7 @@ namespace MediaBrowser.Controller.MediaEncoding { public interface IAttachmentExtractor { - Task<(MediaAttachment attachment, Stream stream)> GetAttachment( + Task<(MediaAttachment Attachment, Stream Stream)> GetAttachment( BaseItem item, string mediaSourceId, int attachmentStreamIndex, diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 27d618a3f..fd3eb8105 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -32,19 +32,19 @@ namespace MediaBrowser.Controller.MediaEncoding Version EncoderVersion { get; } /// - /// Whether the configured Vaapi device is from AMD(radeonsi/r600 Mesa driver). + /// Gets a value indicating whether the configured Vaapi device is from AMD(radeonsi/r600 Mesa driver). /// /// true if the Vaapi device is an AMD(radeonsi/r600 Mesa driver) GPU, false otherwise. bool IsVaapiDeviceAmd { get; } /// - /// Whether the configured Vaapi device is from Intel(iHD driver). + /// Gets a value indicating whether the configured Vaapi device is from Intel(iHD driver). /// /// true if the Vaapi device is an Intel(iHD driver) GPU, false otherwise. bool IsVaapiDeviceInteliHD { get; } /// - /// Whether the configured Vaapi device is from Intel(legacy i965 driver). + /// Gets a value indicating whether the configured Vaapi device is from Intel(legacy i965 driver). /// /// true if the Vaapi device is an Intel(legacy i965 driver) GPU, false otherwise. bool IsVaapiDeviceInteli965 { get; } diff --git a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs index 933f440ac..8b2837ee3 100644 --- a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs +++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs @@ -41,7 +41,7 @@ namespace MediaBrowser.Controller.MediaEncoding break; } - await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); + await target.WriteAsync(bytes).ConfigureAwait(false); // Check again, the stream could have been closed if (!target.CanWrite) diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs index a084f9196..837bf0bb2 100644 --- a/MediaBrowser.Controller/Persistence/IItemRepository.cs +++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs @@ -161,17 +161,17 @@ namespace MediaBrowser.Controller.Persistence int GetCount(InternalItemsQuery query); - QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query); + QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery query); - QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query); + QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery query); - QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query); + QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery query); - QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query); + QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery query); - QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query); + QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery query); - QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query); + QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery query); List GetMusicGenreNames(); diff --git a/MediaBrowser.Controller/Providers/MetadataResult.cs b/MediaBrowser.Controller/Providers/MetadataResult.cs index 2085ae4ad..58a0fa2a9 100644 --- a/MediaBrowser.Controller/Providers/MetadataResult.cs +++ b/MediaBrowser.Controller/Providers/MetadataResult.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Controller.Providers { // Images aren't always used so the allocation is a waste a lot of the time private List _images; - private List<(string url, ImageType type)> _remoteImages; + private List<(string Url, ImageType Type)> _remoteImages; public MetadataResult() { @@ -27,9 +27,9 @@ namespace MediaBrowser.Controller.Providers set => _images = value; } - public List<(string url, ImageType type)> RemoteImages + public List<(string Url, ImageType Type)> RemoteImages { - get => _remoteImages ??= new List<(string url, ImageType type)>(); + get => _remoteImages ??= new List<(string Url, ImageType Type)>(); set => _remoteImages = value; } diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index 9ebc0d0cf..3fd4cd731 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -50,7 +50,7 @@ namespace MediaBrowser.MediaEncoding.Attachments } /// - public async Task<(MediaAttachment attachment, Stream stream)> GetAttachment(BaseItem item, string mediaSourceId, int attachmentStreamIndex, CancellationToken cancellationToken) + public async Task<(MediaAttachment Attachment, Stream Stream)> GetAttachment(BaseItem item, string mediaSourceId, int attachmentStreamIndex, CancellationToken cancellationToken) { if (item == null) { diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index fce71bf1a..e1643ea43 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -91,9 +91,13 @@ namespace MediaBrowser.MediaEncoding.Encoder /// public string EncoderPath => _ffmpegPath; + public Version EncoderVersion => _ffmpegVersion; + public bool IsVaapiDeviceAmd => _isVaapiDeviceAmd; + public bool IsVaapiDeviceInteliHD => _isVaapiDeviceInteliHD; + public bool IsVaapiDeviceInteli965 => _isVaapiDeviceInteli965; /// diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 89365a516..5b1ec8041 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -139,28 +139,28 @@ namespace MediaBrowser.MediaEncoding.Subtitles var subtitle = await GetSubtitleStream(mediaSource, subtitleStream, cancellationToken) .ConfigureAwait(false); - var inputFormat = subtitle.format; + var inputFormat = subtitle.Format; // Return the original if we don't have any way of converting it if (!TryGetWriter(outputFormat, out var writer)) { - return subtitle.stream; + return subtitle.Stream; } // Return the original if the same format is being requested // Character encoding was already handled in GetSubtitleStream if (string.Equals(inputFormat, outputFormat, StringComparison.OrdinalIgnoreCase)) { - return subtitle.stream; + return subtitle.Stream; } - using (var stream = subtitle.stream) + using (var stream = subtitle.Stream) { return ConvertSubtitles(stream, inputFormat, outputFormat, startTimeTicks, endTimeTicks, preserveOriginalTimestamps, cancellationToken); } } - private async Task<(Stream stream, string format)> GetSubtitleStream( + private async Task<(Stream Stream, string Format)> GetSubtitleStream( MediaSourceInfo mediaSource, MediaStream subtitleStream, CancellationToken cancellationToken) diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index a19017215..84a6f6abe 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -289,8 +289,8 @@ namespace MediaBrowser.Model.Dlna var directPlayInfo = GetAudioDirectPlayMethods(item, audioStream, options); - var directPlayMethods = directPlayInfo.Item1; - var transcodeReasons = directPlayInfo.Item2.ToList(); + var directPlayMethods = directPlayInfo.PlayMethods; + var transcodeReasons = directPlayInfo.TranscodeReasons.ToList(); int? inputAudioChannels = audioStream?.Channels; int? inputAudioBitrate = audioStream?.BitDepth; @@ -448,7 +448,7 @@ namespace MediaBrowser.Model.Dlna return options.GetMaxBitrate(isAudio); } - private (IEnumerable, IEnumerable) GetAudioDirectPlayMethods(MediaSourceInfo item, MediaStream audioStream, AudioOptions options) + private (IEnumerable PlayMethods, IEnumerable TranscodeReasons) GetAudioDirectPlayMethods(MediaSourceInfo item, MediaStream audioStream, AudioOptions options) { DirectPlayProfile directPlayProfile = options.Profile.DirectPlayProfiles .FirstOrDefault(x => x.Type == DlnaProfileType.Audio && IsAudioDirectPlaySupported(x, item, audioStream)); @@ -679,8 +679,8 @@ namespace MediaBrowser.Model.Dlna // TODO: This doesn't account for situations where the device is able to handle the media's bitrate, but the connection isn't fast enough var directPlayEligibilityResult = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true) ?? 0, subtitleStream, audioStream, options, PlayMethod.DirectPlay); var directStreamEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false) ?? 0, subtitleStream, audioStream, options, PlayMethod.DirectStream); - bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult.directPlay); - bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directStreamEligibilityResult.directPlay); + bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult.DirectPlay); + bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directStreamEligibilityResult.DirectPlay); _logger.LogDebug( "Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}", @@ -695,7 +695,7 @@ namespace MediaBrowser.Model.Dlna { // See if it can be direct played var directPlayInfo = GetVideoDirectPlayProfile(options, item, videoStream, audioStream, isEligibleForDirectStream); - var directPlay = directPlayInfo.playMethod; + var directPlay = directPlayInfo.PlayMethod; if (directPlay != null) { @@ -713,17 +713,17 @@ namespace MediaBrowser.Model.Dlna return playlistItem; } - transcodeReasons.AddRange(directPlayInfo.transcodeReasons); + transcodeReasons.AddRange(directPlayInfo.TranscodeReasons); } - if (directPlayEligibilityResult.reason.HasValue) + if (directPlayEligibilityResult.Reason.HasValue) { - transcodeReasons.Add(directPlayEligibilityResult.reason.Value); + transcodeReasons.Add(directPlayEligibilityResult.Reason.Value); } - if (directStreamEligibilityResult.reason.HasValue) + if (directStreamEligibilityResult.Reason.HasValue) { - transcodeReasons.Add(directStreamEligibilityResult.reason.Value); + transcodeReasons.Add(directStreamEligibilityResult.Reason.Value); } // Can't direct play, find the transcoding profile @@ -1000,7 +1000,7 @@ namespace MediaBrowser.Model.Dlna return 7168000; } - private (PlayMethod? playMethod, List transcodeReasons) GetVideoDirectPlayProfile( + private (PlayMethod? PlayMethod, List TranscodeReasons) GetVideoDirectPlayProfile( VideoOptions options, MediaSourceInfo mediaSource, MediaStream videoStream, @@ -1209,7 +1209,7 @@ namespace MediaBrowser.Model.Dlna mediaSource.Path ?? "Unknown path"); } - private (bool directPlay, TranscodeReason? reason) IsEligibleForDirectPlay( + private (bool DirectPlay, TranscodeReason? Reason) IsEligibleForDirectPlay( MediaSourceInfo item, long maxBitrate, MediaStream subtitleStream, diff --git a/MediaBrowser.Model/Session/GeneralCommandType.cs b/MediaBrowser.Model/Session/GeneralCommandType.cs index c58fa9a6b..166a6b441 100644 --- a/MediaBrowser.Model/Session/GeneralCommandType.cs +++ b/MediaBrowser.Model/Session/GeneralCommandType.cs @@ -39,7 +39,6 @@ namespace MediaBrowser.Model.Session SetRepeatMode = 29, ChannelUp = 30, ChannelDown = 31, - SetMaxStreamingBitrate = 31, Guide = 32, ToggleStats = 33, PlayMediaSource = 34, @@ -48,6 +47,7 @@ namespace MediaBrowser.Model.Session PlayState = 37, PlayNext = 38, ToggleOsdMenu = 39, - Play = 40 + Play = 40, + SetMaxStreamingBitrate = 41 } } diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 0af76f75a..94045b38b 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -682,12 +682,12 @@ namespace MediaBrowser.Providers.Manager { try { - await ProviderManager.SaveImage(item, remoteImage.url, remoteImage.type, null, cancellationToken).ConfigureAwait(false); + await ProviderManager.SaveImage(item, remoteImage.Url, remoteImage.Type, null, cancellationToken).ConfigureAwait(false); refreshResult.UpdateType |= ItemUpdateType.ImageUpdate; } catch (HttpRequestException ex) { - Logger.LogError(ex, "Could not save {ImageType} image: {Url}", Enum.GetName(remoteImage.type), remoteImage.url); + Logger.LogError(ex, "Could not save {ImageType} image: {Url}", Enum.GetName(remoteImage.Type), remoteImage.Url); } } diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs index 8a32cb07c..5ae5ff3be 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs @@ -303,7 +303,7 @@ namespace MediaBrowser.Providers.Music return ReleaseResult.Parse(reader).FirstOrDefault(); } - private static (string, string) ParseArtistCredit(XmlReader reader) + private static (string Name, string ArtistId) ParseArtistCredit(XmlReader reader) { reader.MoveToContent(); reader.Read(); @@ -345,7 +345,7 @@ namespace MediaBrowser.Providers.Music return default; } - private static (string, string) ParseArtistNameCredit(XmlReader reader) + private static (string Name, string ArtistId) ParseArtistNameCredit(XmlReader reader) { reader.MoveToContent(); reader.Read(); @@ -388,7 +388,7 @@ namespace MediaBrowser.Providers.Music return (null, null); } - private static (string name, string id) ParseArtistArtistCredit(XmlReader reader, string artistId) + private static (string Name, string ArtistId) ParseArtistArtistCredit(XmlReader reader, string artistId) { reader.MoveToContent(); reader.Read(); @@ -628,7 +628,7 @@ namespace MediaBrowser.Providers.Music public string Overview; public int? Year; - public List> Artists = new List>(); + public List<(string, string)> Artists = new(); public static IEnumerable Parse(XmlReader reader) { @@ -776,7 +776,7 @@ namespace MediaBrowser.Providers.Music using var subReader = reader.ReadSubtree(); var artist = ParseArtistCredit(subReader); - if (!string.IsNullOrEmpty(artist.Item1)) + if (!string.IsNullOrEmpty(artist.Name)) { result.Artists.Add(artist); } diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index bcf9a8366..007101868 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -872,7 +872,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers else { // only allow one item of each type - if (itemResult.RemoteImages.Any(x => x.type == imageType)) + if (itemResult.RemoteImages.Any(x => x.Type == imageType)) { return; } diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs index 7ea45d14d..7c9952030 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs +++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using Jellyfin.Data.Entities; @@ -157,33 +158,33 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers // Images Assert.Equal(7, result.RemoteImages.Count); - var posters = result.RemoteImages.Where(x => x.type == ImageType.Primary).ToList(); + var posters = result.RemoteImages.Where(x => x.Type == ImageType.Primary).ToList(); Assert.Single(posters); - Assert.Equal("http://image.tmdb.org/t/p/original/9rtrRGeRnL0JKtu9IMBWsmlmmZz.jpg", posters[0].url); + Assert.Equal("http://image.tmdb.org/t/p/original/9rtrRGeRnL0JKtu9IMBWsmlmmZz.jpg", posters[0].Url); - var logos = result.RemoteImages.Where(x => x.type == ImageType.Logo).ToList(); + var logos = result.RemoteImages.Where(x => x.Type == ImageType.Logo).ToList(); Assert.Single(logos); - Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-5865bf95cbadb.png", logos[0].url); + Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-5865bf95cbadb.png", logos[0].Url); - var banners = result.RemoteImages.Where(x => x.type == ImageType.Banner).ToList(); + var banners = result.RemoteImages.Where(x => x.Type == ImageType.Banner).ToList(); Assert.Single(banners); - Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviebanner/justice-league-586017e95adbd.jpg", banners[0].url); + Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviebanner/justice-league-586017e95adbd.jpg", banners[0].Url); - var thumbs = result.RemoteImages.Where(x => x.type == ImageType.Thumb).ToList(); + var thumbs = result.RemoteImages.Where(x => x.Type == ImageType.Thumb).ToList(); Assert.Single(thumbs); - Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviethumb/justice-league-585fb155c3743.jpg", thumbs[0].url); + Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviethumb/justice-league-585fb155c3743.jpg", thumbs[0].Url); - var art = result.RemoteImages.Where(x => x.type == ImageType.Art).ToList(); + var art = result.RemoteImages.Where(x => x.Type == ImageType.Art).ToList(); Assert.Single(art); - Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/hdmovieclearart/justice-league-5865c23193041.png", art[0].url); + Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/hdmovieclearart/justice-league-5865c23193041.png", art[0].Url); - var discArt = result.RemoteImages.Where(x => x.type == ImageType.Disc).ToList(); + var discArt = result.RemoteImages.Where(x => x.Type == ImageType.Disc).ToList(); Assert.Single(discArt); - Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a3af26360617.png", discArt[0].url); + Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a3af26360617.png", discArt[0].Url); - var backdrop = result.RemoteImages.Where(x => x.type == ImageType.Backdrop).ToList(); + var backdrop = result.RemoteImages.Where(x => x.Type == ImageType.Backdrop).ToList(); Assert.Single(backdrop); - Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5793f518c6d6e.jpg", backdrop[0].url); + Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5793f518c6d6e.jpg", backdrop[0].Url); // Local Image - contains only one item depending on operating system Assert.Single(result.Images); @@ -216,8 +217,8 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers _parser.Fetch(result, "Test Data/Fanart.nfo", CancellationToken.None); - Assert.Single(result.RemoteImages.Where(x => x.type == ImageType.Backdrop)); - Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5a5332c7b5e77.jpg", result.RemoteImages.First(x => x.type == ImageType.Backdrop).url); + Assert.Single(result.RemoteImages.Where(x => x.Type == ImageType.Backdrop)); + Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5a5332c7b5e77.jpg", result.RemoteImages.First(x => x.Type == ImageType.Backdrop).Url); } [Fact] -- cgit v1.2.3