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 --- .../MediaEncoding/EncodingHelper.cs | 42 +++++++++++----------- 1 file changed, 20 insertions(+), 22 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') 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)); } } -- cgit v1.2.3 From 997816443862a8db68f314a6d23ef076c9661c01 Mon Sep 17 00:00:00 2001 From: Jonas Resch Date: Mon, 22 Nov 2021 21:47:52 +0100 Subject: Add support for external audio files --- MediaBrowser.Controller/Entities/Video.cs | 6 + .../MediaEncoding/EncodingHelper.cs | 21 +- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 275 +++++++++++++++++++++ .../MediaInfo/FFProbeProvider.cs | 11 + .../MediaInfo/FFProbeVideoInfo.cs | 25 ++ 5 files changed, 334 insertions(+), 4 deletions(-) create mode 100644 MediaBrowser.Providers/MediaInfo/AudioResolver.cs (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index de42c67d3..56e955ada 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -97,6 +97,12 @@ namespace MediaBrowser.Controller.Entities /// The subtitle paths. public string[] SubtitleFiles { get; set; } + /// + /// Gets or sets the audio paths. + /// + /// The audio paths. + public string[] AudioFiles { get; set; } + /// /// Gets or sets a value indicating whether this instance has subtitles. /// diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 5715194b8..5326ecd20 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -678,6 +678,12 @@ namespace MediaBrowser.Controller.MediaEncoding arg.Append("-i ") .Append(GetInputPathArgument(state)); + if (state.AudioStream.IsExternal) + { + arg.Append(" -i ") + .Append(string.Format(CultureInfo.InvariantCulture, "file:\"{0}\"", state.AudioStream.Path)); + } + if (state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream) @@ -1999,10 +2005,17 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.AudioStream != null) { - args += string.Format( - CultureInfo.InvariantCulture, - " -map 0:{0}", - state.AudioStream.Index); + if (state.AudioStream.IsExternal) + { + args += " -map 1:a"; + } + else + { + args += string.Format( + CultureInfo.InvariantCulture, + " -map 0:{0}", + state.AudioStream.Index); + } } else { diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs new file mode 100644 index 000000000..fce2fa551 --- /dev/null +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -0,0 +1,275 @@ +#nullable disable + +#pragma warning disable CA1002, CS1591 + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.MediaInfo; + +namespace MediaBrowser.Providers.MediaInfo +{ + public class AudioResolver + { + private readonly ILocalizationManager _localization; + + private readonly IMediaEncoder _mediaEncoder; + + private readonly CancellationToken _cancellationToken; + + public AudioResolver(ILocalizationManager localization, IMediaEncoder mediaEncoder, CancellationToken cancellationToken = default) + { + _localization = localization; + _mediaEncoder = mediaEncoder; + _cancellationToken = cancellationToken; + } + + public List GetExternalAudioStreams( + Video video, + int startIndex, + IDirectoryService directoryService, + bool clearCache) + { + var streams = new List(); + + if (!video.IsFileProtocol) + { + return streams; + } + + AddExternalAudioStreams(streams, video.ContainingFolderPath, video.Path, startIndex, directoryService, clearCache); + + startIndex += streams.Count; + + string folder = video.GetInternalMetadataPath(); + + if (!Directory.Exists(folder)) + { + return streams; + } + + try + { + AddExternalAudioStreams(streams, folder, video.Path, startIndex, directoryService, clearCache); + } + catch (IOException) + { + } + + return streams; + } + + public IEnumerable GetExternalAudioFiles( + Video video, + IDirectoryService directoryService, + bool clearCache) + { + if (!video.IsFileProtocol) + { + yield break; + } + + var streams = GetExternalAudioStreams(video, 0, directoryService, clearCache); + + foreach (var stream in streams) + { + yield return stream.Path; + } + } + + public void AddExternalAudioStreams( + List streams, + string videoPath, + int startIndex, + IReadOnlyList files) + { + var videoFileNameWithoutExtension = NormalizeFilenameForAudioComparison(videoPath); + + for (var i = 0; i < files.Count; i++) + { + + var fullName = files[i]; + var extension = Path.GetExtension(fullName.AsSpan()); + if (!IsAudioExtension(extension)) + { + continue; + } + + Model.MediaInfo.MediaInfo mediaInfo = GetMediaInfo(fullName).Result; + MediaStream mediaStream = mediaInfo.MediaStreams.First(); + mediaStream.Index = startIndex++; + mediaStream.Type = MediaStreamType.Audio; + mediaStream.IsExternal = true; + mediaStream.Path = fullName; + mediaStream.IsDefault = false; + mediaStream.Title = null; + + var fileNameWithoutExtension = NormalizeFilenameForAudioComparison(fullName); + + // The audio filename must either be equal to the video filename or start with the video filename followed by a dot + if (videoFileNameWithoutExtension.Equals(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) + { + mediaStream.Path = fullName; + } + else if (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length + && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.' + && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) + { + + // Support xbmc naming conventions - 300.spanish.m4a + var languageSpan = fileNameWithoutExtension; + while (languageSpan.Length > 0) + { + var lastDot = languageSpan.LastIndexOf('.'); + var currentSlice = languageSpan[lastDot..]; + languageSpan = languageSpan[(lastDot + 1)..]; + break; + } + + // Try to translate to three character code + // Be flexible and check against both the full and three character versions + var language = languageSpan.ToString(); + var culture = _localization.FindLanguageInfo(language); + + language = culture == null ? language : culture.ThreeLetterISOLanguageName; + mediaStream.Language = language; + } + else + { + continue; + } + + mediaStream.Codec = extension.TrimStart('.').ToString().ToLowerInvariant(); + + streams.Add(mediaStream); + } + } + + private static bool IsAudioExtension(ReadOnlySpan extension) + { + String[] audioExtensions = new[] + { + ".nsv", + ".m4a", + ".flac", + ".aac", + ".strm", + ".pls", + ".rm", + ".mpa", + ".wav", + ".wma", + ".ogg", + ".opus", + ".mp3", + ".mp2", + ".mod", + ".amf", + ".669", + ".dmf", + ".dsm", + ".far", + ".gdm", + ".imf", + ".it", + ".m15", + ".med", + ".okt", + ".s3m", + ".stm", + ".sfx", + ".ult", + ".uni", + ".xm", + ".sid", + ".ac3", + ".dts", + ".cue", + ".aif", + ".aiff", + ".ape", + ".mac", + ".mpc", + ".mp+", + ".mpp", + ".shn", + ".wv", + ".nsf", + ".spc", + ".gym", + ".adplug", + ".adx", + ".dsp", + ".adp", + ".ymf", + ".ast", + ".afc", + ".hps", + ".xsp", + ".acc", + ".m4b", + ".oga", + ".dsf", + ".mka" + }; + + foreach (String audioExtension in audioExtensions) + { + if (extension.Equals(audioExtension, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } + + private Task GetMediaInfo(string path) + { + _cancellationToken.ThrowIfCancellationRequested(); + + return _mediaEncoder.GetMediaInfo( + new MediaInfoRequest + { + MediaType = DlnaProfileType.Audio, + MediaSource = new MediaSourceInfo + { + Path = path, + Protocol = MediaProtocol.File + } + }, + _cancellationToken); + } + + private static ReadOnlySpan NormalizeFilenameForAudioComparison(string filename) + { + // Try to account for sloppy file naming + filename = filename.Replace("_", string.Empty, StringComparison.Ordinal); + filename = filename.Replace(" ", string.Empty, StringComparison.Ordinal); + return Path.GetFileNameWithoutExtension(filename.AsSpan()); + } + + private void AddExternalAudioStreams( + List streams, + string folder, + string videoPath, + int startIndex, + IDirectoryService directoryService, + bool clearCache) + { + var files = directoryService.GetFilePaths(folder, clearCache, true); + + AddExternalAudioStreams(streams, videoPath, startIndex, files); + } + } +} diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index d4b5d8655..1e7fcf2d2 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -50,6 +50,8 @@ namespace MediaBrowser.Providers.MediaInfo private readonly IMediaSourceManager _mediaSourceManager; private readonly SubtitleResolver _subtitleResolver; + private readonly AudioResolver _audioResolver; + private readonly Task _cachedTask = Task.FromResult(ItemUpdateType.None); public FFProbeProvider( @@ -78,6 +80,7 @@ namespace MediaBrowser.Providers.MediaInfo _mediaSourceManager = mediaSourceManager; _subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager); + _audioResolver = new AudioResolver(BaseItem.LocalizationManager, mediaEncoder); } public string Name => "ffprobe"; @@ -111,6 +114,14 @@ namespace MediaBrowser.Providers.MediaInfo return true; } + if (item.SupportsLocalMetadata && video != null && !video.IsPlaceHolder + && !video.AudioFiles.SequenceEqual( + _audioResolver.GetExternalAudioFiles(video, directoryService, false), StringComparer.Ordinal)) + { + _logger.LogDebug("Refreshing {0} due to external audio change.", item.Path); + return true; + } + return false; } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 4ab15f60e..8db095416 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -214,6 +214,8 @@ namespace MediaBrowser.Providers.MediaInfo await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); + AddExternalAudio(video, mediaStreams, options, cancellationToken); + var libraryOptions = _libraryManager.GetLibraryOptions(video); if (mediaInfo != null) @@ -574,6 +576,29 @@ namespace MediaBrowser.Providers.MediaInfo currentStreams.AddRange(externalSubtitleStreams); } + /// + /// Adds the external audio. + /// + /// The video. + /// The current streams. + /// The refreshOptions. + /// The cancellation token. + private void AddExternalAudio( + Video video, + List currentStreams, + MetadataRefreshOptions options, + CancellationToken cancellationToken) + { + var audioResolver = new AudioResolver(_localization, _mediaEncoder, cancellationToken); + + var startIndex = currentStreams.Count == 0 ? 0 : (currentStreams.Select(i => i.Index).Max() + 1); + var externalAudioStreams = audioResolver.GetExternalAudioStreams(video, startIndex, options.DirectoryService, false); + + video.AudioFiles = externalAudioStreams.Select(i => i.Path).ToArray(); + + currentStreams.AddRange(externalAudioStreams); + } + /// /// Creates dummy chapters. /// -- cgit v1.2.3 From a68e58556c49ee9bc1c27fac696ffc9170c95e84 Mon Sep 17 00:00:00 2001 From: Jonas Resch Date: Sat, 27 Nov 2021 12:10:57 +0100 Subject: Implement code feedback - Rewrite AudioResolver - Use async & await instead of .Result - Add support for audio containers with multiple audio streams (e.g. mka) - Fix bug when using external subtitle and external audio streams at the same time --- .../MediaEncoding/EncodingHelper.cs | 21 +- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 271 ++++++--------------- .../MediaInfo/FFProbeProvider.cs | 16 +- .../MediaInfo/FFProbeVideoInfo.cs | 16 +- 4 files changed, 114 insertions(+), 210 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 5326ecd20..5695ee2db 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -678,12 +678,6 @@ namespace MediaBrowser.Controller.MediaEncoding arg.Append("-i ") .Append(GetInputPathArgument(state)); - if (state.AudioStream.IsExternal) - { - arg.Append(" -i ") - .Append(string.Format(CultureInfo.InvariantCulture, "file:\"{0}\"", state.AudioStream.Path)); - } - if (state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream) @@ -702,6 +696,12 @@ namespace MediaBrowser.Controller.MediaEncoding arg.Append(" -i \"").Append(subtitlePath).Append('\"'); } + if (state.AudioStream != null && state.AudioStream.IsExternal) + { + arg.Append(" -i ") + .Append(string.Format(CultureInfo.InvariantCulture, "file:\"{0}\"", state.AudioStream.Path)); + } + return arg.ToString(); } @@ -2007,7 +2007,14 @@ namespace MediaBrowser.Controller.MediaEncoding { if (state.AudioStream.IsExternal) { - args += " -map 1:a"; + int externalAudioMapIndex = state.SubtitleStream != null && state.SubtitleStream.IsExternal ? 2 : 1; + int externalAudioStream = state.MediaSource.MediaStreams.Where(i => i.Path == state.AudioStream.Path).ToList().IndexOf(state.AudioStream); + + args += string.Format( + CultureInfo.InvariantCulture, + " -map {0}:{1}", + externalAudioMapIndex, + externalAudioStream); } else { diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index fce2fa551..8d5f8d86e 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -1,13 +1,13 @@ -#nullable disable - #pragma warning disable CA1002, CS1591 using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Threading; using System.Threading.Tasks; +using Emby.Naming.Audio; +using Emby.Naming.Common; +using Jellyfin.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Providers; @@ -21,24 +21,15 @@ namespace MediaBrowser.Providers.MediaInfo { public class AudioResolver { - private readonly ILocalizationManager _localization; - - private readonly IMediaEncoder _mediaEncoder; - - private readonly CancellationToken _cancellationToken; - - public AudioResolver(ILocalizationManager localization, IMediaEncoder mediaEncoder, CancellationToken cancellationToken = default) - { - _localization = localization; - _mediaEncoder = mediaEncoder; - _cancellationToken = cancellationToken; - } - - public List GetExternalAudioStreams( + public async Task> GetExternalAudioStreams( Video video, int startIndex, IDirectoryService directoryService, - bool clearCache) + NamingOptions namingOptions, + bool clearCache, + ILocalizationManager localizationManager, + IMediaEncoder mediaEncoder, + CancellationToken cancellationToken) { var streams = new List(); @@ -47,198 +38,117 @@ namespace MediaBrowser.Providers.MediaInfo return streams; } - AddExternalAudioStreams(streams, video.ContainingFolderPath, video.Path, startIndex, directoryService, clearCache); - - startIndex += streams.Count; + List paths = GetExternalAudioFiles(video, directoryService, namingOptions, clearCache); - string folder = video.GetInternalMetadataPath(); - - if (!Directory.Exists(folder)) - { - return streams; - } - - try - { - AddExternalAudioStreams(streams, folder, video.Path, startIndex, directoryService, clearCache); - } - catch (IOException) - { - } + await AddExternalAudioStreams(streams, paths, startIndex, localizationManager, mediaEncoder, cancellationToken); return streams; } - public IEnumerable GetExternalAudioFiles( + public List GetExternalAudioFiles( Video video, IDirectoryService directoryService, + NamingOptions namingOptions, bool clearCache) { + List paths = new List(); + if (!video.IsFileProtocol) { - yield break; + return paths; } - var streams = GetExternalAudioStreams(video, 0, directoryService, clearCache); + paths.AddRange(GetAudioFilesFromFolder(video.ContainingFolderPath, video.Path, directoryService, namingOptions, clearCache)); + paths.AddRange(GetAudioFilesFromFolder(video.GetInternalMetadataPath(), video.Path, directoryService, namingOptions, clearCache)); - foreach (var stream in streams) - { - yield return stream.Path; - } + return paths; } - public void AddExternalAudioStreams( - List streams, - string videoPath, - int startIndex, - IReadOnlyList files) + private List GetAudioFilesFromFolder( + string folder, + string videoFileName, + IDirectoryService directoryService, + NamingOptions namingOptions, + bool clearCache) { - var videoFileNameWithoutExtension = NormalizeFilenameForAudioComparison(videoPath); + List paths = new List(); + string videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(videoFileName); - for (var i = 0; i < files.Count; i++) + if (!Directory.Exists(folder)) + { + return paths; + } + + var files = directoryService.GetFilePaths(folder, clearCache, true); + for (int i = 0; i < files.Count; i++) { + string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(files[i]); - var fullName = files[i]; - var extension = Path.GetExtension(fullName.AsSpan()); - if (!IsAudioExtension(extension)) + if (!AudioFileParser.IsAudioFile(files[i], namingOptions)) { continue; } - Model.MediaInfo.MediaInfo mediaInfo = GetMediaInfo(fullName).Result; - MediaStream mediaStream = mediaInfo.MediaStreams.First(); - mediaStream.Index = startIndex++; - mediaStream.Type = MediaStreamType.Audio; - mediaStream.IsExternal = true; - mediaStream.Path = fullName; - mediaStream.IsDefault = false; - mediaStream.Title = null; - - var fileNameWithoutExtension = NormalizeFilenameForAudioComparison(fullName); - // The audio filename must either be equal to the video filename or start with the video filename followed by a dot - if (videoFileNameWithoutExtension.Equals(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) - { - mediaStream.Path = fullName; - } - else if (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length + if (videoFileNameWithoutExtension.Equals(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase) || + (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.' - && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) + && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))) { - - // Support xbmc naming conventions - 300.spanish.m4a - var languageSpan = fileNameWithoutExtension; - while (languageSpan.Length > 0) - { - var lastDot = languageSpan.LastIndexOf('.'); - var currentSlice = languageSpan[lastDot..]; - languageSpan = languageSpan[(lastDot + 1)..]; - break; - } - - // Try to translate to three character code - // Be flexible and check against both the full and three character versions - var language = languageSpan.ToString(); - var culture = _localization.FindLanguageInfo(language); - - language = culture == null ? language : culture.ThreeLetterISOLanguageName; - mediaStream.Language = language; - } - else - { - continue; + paths.Add(files[i]); } - - mediaStream.Codec = extension.TrimStart('.').ToString().ToLowerInvariant(); - - streams.Add(mediaStream); } + + return paths; } - private static bool IsAudioExtension(ReadOnlySpan extension) + public async Task AddExternalAudioStreams( + List streams, + List paths, + int startIndex, + ILocalizationManager localizationManager, + IMediaEncoder mediaEncoder, + CancellationToken cancellationToken) { - String[] audioExtensions = new[] + foreach (string path in paths) { - ".nsv", - ".m4a", - ".flac", - ".aac", - ".strm", - ".pls", - ".rm", - ".mpa", - ".wav", - ".wma", - ".ogg", - ".opus", - ".mp3", - ".mp2", - ".mod", - ".amf", - ".669", - ".dmf", - ".dsm", - ".far", - ".gdm", - ".imf", - ".it", - ".m15", - ".med", - ".okt", - ".s3m", - ".stm", - ".sfx", - ".ult", - ".uni", - ".xm", - ".sid", - ".ac3", - ".dts", - ".cue", - ".aif", - ".aiff", - ".ape", - ".mac", - ".mpc", - ".mp+", - ".mpp", - ".shn", - ".wv", - ".nsf", - ".spc", - ".gym", - ".adplug", - ".adx", - ".dsp", - ".adp", - ".ymf", - ".ast", - ".afc", - ".hps", - ".xsp", - ".acc", - ".m4b", - ".oga", - ".dsf", - ".mka" - }; + string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path); + Model.MediaInfo.MediaInfo mediaInfo = await GetMediaInfo(path, mediaEncoder, cancellationToken); - foreach (String audioExtension in audioExtensions) - { - if (extension.Equals(audioExtension, StringComparison.OrdinalIgnoreCase)) + foreach (MediaStream mediaStream in mediaInfo.MediaStreams) { - return true; + mediaStream.Index = startIndex++; + mediaStream.Type = MediaStreamType.Audio; + mediaStream.IsExternal = true; + mediaStream.Path = path; + mediaStream.IsDefault = false; + mediaStream.Title = null; + + if (mediaStream.Language == null) + { + // Try to translate to three character code + // Be flexible and check against both the full and three character versions + var language = StringExtensions.RightPart(fileNameWithoutExtension, '.').ToString(); + + if (language != fileNameWithoutExtension) + { + var culture = localizationManager.FindLanguageInfo(language); + + language = culture == null ? language : culture.ThreeLetterISOLanguageName; + mediaStream.Language = language; + } + } + + streams.Add(mediaStream); } } - - return false; } - private Task GetMediaInfo(string path) + private Task GetMediaInfo(string path, IMediaEncoder mediaEncoder, CancellationToken cancellationToken) { - _cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); - return _mediaEncoder.GetMediaInfo( + return mediaEncoder.GetMediaInfo( new MediaInfoRequest { MediaType = DlnaProfileType.Audio, @@ -248,28 +158,7 @@ namespace MediaBrowser.Providers.MediaInfo Protocol = MediaProtocol.File } }, - _cancellationToken); - } - - private static ReadOnlySpan NormalizeFilenameForAudioComparison(string filename) - { - // Try to account for sloppy file naming - filename = filename.Replace("_", string.Empty, StringComparison.Ordinal); - filename = filename.Replace(" ", string.Empty, StringComparison.Ordinal); - return Path.GetFileNameWithoutExtension(filename.AsSpan()); - } - - private void AddExternalAudioStreams( - List streams, - string folder, - string videoPath, - int startIndex, - IDirectoryService directoryService, - bool clearCache) - { - var files = directoryService.GetFilePaths(folder, clearCache, true); - - AddExternalAudioStreams(streams, videoPath, startIndex, files); + cancellationToken); } } } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index 1e7fcf2d2..98909c94e 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Emby.Naming.Common; using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -50,10 +51,10 @@ namespace MediaBrowser.Providers.MediaInfo private readonly IMediaSourceManager _mediaSourceManager; private readonly SubtitleResolver _subtitleResolver; - private readonly AudioResolver _audioResolver; - private readonly Task _cachedTask = Task.FromResult(ItemUpdateType.None); + private readonly NamingOptions _namingOptions; + public FFProbeProvider( ILogger logger, IMediaSourceManager mediaSourceManager, @@ -65,7 +66,8 @@ namespace MediaBrowser.Providers.MediaInfo IServerConfigurationManager config, ISubtitleManager subtitleManager, IChapterManager chapterManager, - ILibraryManager libraryManager) + ILibraryManager libraryManager, + NamingOptions namingOptions) { _logger = logger; _mediaEncoder = mediaEncoder; @@ -78,9 +80,9 @@ namespace MediaBrowser.Providers.MediaInfo _chapterManager = chapterManager; _libraryManager = libraryManager; _mediaSourceManager = mediaSourceManager; + _namingOptions = namingOptions; _subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager); - _audioResolver = new AudioResolver(BaseItem.LocalizationManager, mediaEncoder); } public string Name => "ffprobe"; @@ -114,9 +116,10 @@ namespace MediaBrowser.Providers.MediaInfo return true; } + AudioResolver audioResolver = new AudioResolver(); if (item.SupportsLocalMetadata && video != null && !video.IsPlaceHolder && !video.AudioFiles.SequenceEqual( - _audioResolver.GetExternalAudioFiles(video, directoryService, false), StringComparer.Ordinal)) + audioResolver.GetExternalAudioFiles(video, directoryService, _namingOptions, false), StringComparer.Ordinal)) { _logger.LogDebug("Refreshing {0} due to external audio change.", item.Path); return true; @@ -199,7 +202,8 @@ namespace MediaBrowser.Providers.MediaInfo _config, _subtitleManager, _chapterManager, - _libraryManager); + _libraryManager, + _namingOptions); return prober.ProbeVideo(item, options, cancellationToken); } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 2d49e43ca..39950db70 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using DvdLib.Ifo; +using Emby.Naming.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Configuration; @@ -44,6 +45,7 @@ namespace MediaBrowser.Providers.MediaInfo private readonly ISubtitleManager _subtitleManager; private readonly IChapterManager _chapterManager; private readonly ILibraryManager _libraryManager; + private readonly NamingOptions _namingOptions; private readonly IMediaSourceManager _mediaSourceManager; private readonly long _dummyChapterDuration = TimeSpan.FromMinutes(5).Ticks; @@ -59,7 +61,8 @@ namespace MediaBrowser.Providers.MediaInfo IServerConfigurationManager config, ISubtitleManager subtitleManager, IChapterManager chapterManager, - ILibraryManager libraryManager) + ILibraryManager libraryManager, + NamingOptions namingOptions) { _logger = logger; _mediaEncoder = mediaEncoder; @@ -71,6 +74,7 @@ namespace MediaBrowser.Providers.MediaInfo _subtitleManager = subtitleManager; _chapterManager = chapterManager; _libraryManager = libraryManager; + _namingOptions = namingOptions; _mediaSourceManager = mediaSourceManager; } @@ -214,7 +218,7 @@ namespace MediaBrowser.Providers.MediaInfo await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); - AddExternalAudio(video, mediaStreams, options, cancellationToken); + await AddExternalAudio(video, mediaStreams, options, cancellationToken); var libraryOptions = _libraryManager.GetLibraryOptions(video); @@ -583,18 +587,18 @@ namespace MediaBrowser.Providers.MediaInfo /// The current streams. /// The refreshOptions. /// The cancellation token. - private void AddExternalAudio( + private async Task AddExternalAudio( Video video, List currentStreams, MetadataRefreshOptions options, CancellationToken cancellationToken) { - var audioResolver = new AudioResolver(_localization, _mediaEncoder, cancellationToken); + var audioResolver = new AudioResolver(); var startIndex = currentStreams.Count == 0 ? 0 : currentStreams.Max(i => i.Index) + 1; - var externalAudioStreams = audioResolver.GetExternalAudioStreams(video, startIndex, options.DirectoryService, false); + var externalAudioStreams = await audioResolver.GetExternalAudioStreams(video, startIndex, options.DirectoryService, _namingOptions, false, _localization, _mediaEncoder, cancellationToken); - video.AudioFiles = externalAudioStreams.Select(i => i.Path).ToArray(); + video.AudioFiles = externalAudioStreams.Select(i => i.Path).Distinct().ToArray(); currentStreams.AddRange(externalAudioStreams); } -- cgit v1.2.3 From 0894a6193f025a9cec5c735226a8487caa2bc66b Mon Sep 17 00:00:00 2001 From: Jonas Resch Date: Sun, 28 Nov 2021 14:03:52 +0100 Subject: Implement coding standards from 2nd code feedback --- .../MediaEncoding/EncodingHelper.cs | 3 +- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 138 +++++++++------------ .../MediaInfo/FFProbeProvider.cs | 15 ++- .../MediaInfo/FFProbeVideoInfo.cs | 21 ++-- 4 files changed, 78 insertions(+), 99 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 5695ee2db..571230312 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -698,8 +698,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.AudioStream != null && state.AudioStream.IsExternal) { - arg.Append(" -i ") - .Append(string.Format(CultureInfo.InvariantCulture, "file:\"{0}\"", state.AudioStream.Path)); + arg.Append(" -i \"").Append(state.AudioStream.Path).Append("\""); } return arg.ToString(); diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index ccac450f6..d23afdc3b 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -21,69 +21,93 @@ namespace MediaBrowser.Providers.MediaInfo { public class AudioResolver { - public async Task> GetExternalAudioStreams( + private readonly ILocalizationManager _localizationManager; + private readonly IMediaEncoder _mediaEncoder; + private readonly NamingOptions _namingOptions; + + public AudioResolver( + ILocalizationManager localizationManager, + IMediaEncoder mediaEncoder, + NamingOptions namingOptions) + { + _localizationManager = localizationManager; + _mediaEncoder = mediaEncoder; + _namingOptions = namingOptions; + } + + public async IAsyncEnumerable GetExternalAudioStreams( Video video, int startIndex, IDirectoryService directoryService, - NamingOptions namingOptions, bool clearCache, - ILocalizationManager localizationManager, - IMediaEncoder mediaEncoder, CancellationToken cancellationToken) { - var streams = new List(); - if (!video.IsFileProtocol) { - return streams; + yield break; } - List paths = GetExternalAudioFiles(video, directoryService, namingOptions, clearCache); + IEnumerable paths = GetExternalAudioFiles(video, directoryService, clearCache); + foreach (string path in paths) + { + string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path); + Model.MediaInfo.MediaInfo mediaInfo = await GetMediaInfo(path, cancellationToken); - await AddExternalAudioStreams(streams, paths, startIndex, localizationManager, mediaEncoder, cancellationToken).ConfigureAwait(false); + foreach (MediaStream mediaStream in mediaInfo.MediaStreams) + { + mediaStream.Index = startIndex++; + mediaStream.Type = MediaStreamType.Audio; + mediaStream.IsExternal = true; + mediaStream.Path = path; + mediaStream.IsDefault = false; + mediaStream.Title = null; - return streams; + if (string.IsNullOrEmpty(mediaStream.Language)) + { + // Try to translate to three character code + // Be flexible and check against both the full and three character versions + var language = StringExtensions.RightPart(fileNameWithoutExtension, '.').ToString(); + + if (language != fileNameWithoutExtension) + { + var culture = _localizationManager.FindLanguageInfo(language); + + language = culture == null ? language : culture.ThreeLetterISOLanguageName; + mediaStream.Language = language; + } + } + + yield return mediaStream; + } + } } public IEnumerable GetExternalAudioFiles( Video video, IDirectoryService directoryService, - NamingOptions namingOptions, bool clearCache) { - List paths = new List(); - if (!video.IsFileProtocol) { - return paths; + yield break; } - paths.AddRange(GetAudioFilesFromFolder(video.ContainingFolderPath, video.Path, directoryService, namingOptions, clearCache)); - - return paths; - } - - private List GetAudioFilesFromFolder( - string folder, - string videoFileName, - IDirectoryService directoryService, - NamingOptions namingOptions, - bool clearCache) - { - List paths = new List(); - string videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(videoFileName); - + // Check if video folder exists + string folder = video.ContainingFolderPath; if (!Directory.Exists(folder)) { - return paths; + yield break; } + string videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path); + var files = directoryService.GetFilePaths(folder, clearCache, true); for (int i = 0; i < files.Count; i++) { - string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(files[i]); + string file = files[i]; + string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file); - if (!AudioFileParser.IsAudioFile(files[i], namingOptions)) + if (!AudioFileParser.IsAudioFile(file, _namingOptions)) { continue; } @@ -94,60 +118,16 @@ namespace MediaBrowser.Providers.MediaInfo && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.' && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))) { - paths.Add(files[i]); - } - } - - return paths; - } - - public async Task AddExternalAudioStreams( - List streams, - List paths, - int startIndex, - ILocalizationManager localizationManager, - IMediaEncoder mediaEncoder, - CancellationToken cancellationToken) - { - foreach (string path in paths) - { - string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path); - Model.MediaInfo.MediaInfo mediaInfo = await GetMediaInfo(path, mediaEncoder, cancellationToken); - - foreach (MediaStream mediaStream in mediaInfo.MediaStreams) - { - mediaStream.Index = startIndex++; - mediaStream.Type = MediaStreamType.Audio; - mediaStream.IsExternal = true; - mediaStream.Path = path; - mediaStream.IsDefault = false; - mediaStream.Title = null; - - if (string.IsNullOrEmpty(mediaStream.Language)) - { - // Try to translate to three character code - // Be flexible and check against both the full and three character versions - var language = StringExtensions.RightPart(fileNameWithoutExtension, '.').ToString(); - - if (language != fileNameWithoutExtension) - { - var culture = localizationManager.FindLanguageInfo(language); - - language = culture == null ? language : culture.ThreeLetterISOLanguageName; - mediaStream.Language = language; - } - } - - streams.Add(mediaStream); + yield return file; } } } - private Task GetMediaInfo(string path, IMediaEncoder mediaEncoder, CancellationToken cancellationToken) + private Task GetMediaInfo(string path, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - return mediaEncoder.GetMediaInfo( + return _mediaEncoder.GetMediaInfo( new MediaInfoRequest { MediaType = DlnaProfileType.Audio, diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index 98909c94e..392641468 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -50,10 +50,9 @@ namespace MediaBrowser.Providers.MediaInfo private readonly ILibraryManager _libraryManager; private readonly IMediaSourceManager _mediaSourceManager; private readonly SubtitleResolver _subtitleResolver; - private readonly Task _cachedTask = Task.FromResult(ItemUpdateType.None); - private readonly NamingOptions _namingOptions; + private readonly AudioResolver _audioResolver; public FFProbeProvider( ILogger logger, @@ -83,6 +82,7 @@ namespace MediaBrowser.Providers.MediaInfo _namingOptions = namingOptions; _subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager); + _audioResolver = new AudioResolver(_localization, _mediaEncoder, namingOptions); } public string Name => "ffprobe"; @@ -102,7 +102,7 @@ namespace MediaBrowser.Providers.MediaInfo var file = directoryService.GetFile(path); if (file != null && file.LastWriteTimeUtc != item.DateModified) { - _logger.LogDebug("Refreshing {0} due to date modified timestamp change.", path); + _logger.LogDebug("Refreshing {ItemPath} due to date modified timestamp change.", path); return true; } } @@ -112,16 +112,15 @@ namespace MediaBrowser.Providers.MediaInfo && !video.SubtitleFiles.SequenceEqual( _subtitleResolver.GetExternalSubtitleFiles(video, directoryService, false), StringComparer.Ordinal)) { - _logger.LogDebug("Refreshing {0} due to external subtitles change.", item.Path); + _logger.LogDebug("Refreshing {ItemPath} due to external subtitles change.", item.Path); return true; } - AudioResolver audioResolver = new AudioResolver(); if (item.SupportsLocalMetadata && video != null && !video.IsPlaceHolder && !video.AudioFiles.SequenceEqual( - audioResolver.GetExternalAudioFiles(video, directoryService, _namingOptions, false), StringComparer.Ordinal)) + _audioResolver.GetExternalAudioFiles(video, directoryService, false), StringComparer.Ordinal)) { - _logger.LogDebug("Refreshing {0} due to external audio change.", item.Path); + _logger.LogDebug("Refreshing {ItemPath} due to external audio change.", item.Path); return true; } @@ -203,7 +202,7 @@ namespace MediaBrowser.Providers.MediaInfo _subtitleManager, _chapterManager, _libraryManager, - _namingOptions); + _audioResolver); return prober.ProbeVideo(item, options, cancellationToken); } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 745020599..b31f0ed23 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -10,7 +10,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using DvdLib.Ifo; -using Emby.Naming.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Configuration; @@ -45,7 +44,7 @@ namespace MediaBrowser.Providers.MediaInfo private readonly ISubtitleManager _subtitleManager; private readonly IChapterManager _chapterManager; private readonly ILibraryManager _libraryManager; - private readonly NamingOptions _namingOptions; + private readonly AudioResolver _audioResolver; private readonly IMediaSourceManager _mediaSourceManager; private readonly long _dummyChapterDuration = TimeSpan.FromMinutes(5).Ticks; @@ -62,7 +61,7 @@ namespace MediaBrowser.Providers.MediaInfo ISubtitleManager subtitleManager, IChapterManager chapterManager, ILibraryManager libraryManager, - NamingOptions namingOptions) + AudioResolver audioResolver) { _logger = logger; _mediaEncoder = mediaEncoder; @@ -74,7 +73,7 @@ namespace MediaBrowser.Providers.MediaInfo _subtitleManager = subtitleManager; _chapterManager = chapterManager; _libraryManager = libraryManager; - _namingOptions = namingOptions; + _audioResolver = audioResolver; _mediaSourceManager = mediaSourceManager; } @@ -218,7 +217,7 @@ namespace MediaBrowser.Providers.MediaInfo await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); - await AddExternalAudio(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); + await AddExternalAudio(video, mediaStreams, options, cancellationToken); var libraryOptions = _libraryManager.GetLibraryOptions(video); @@ -593,14 +592,16 @@ namespace MediaBrowser.Providers.MediaInfo MetadataRefreshOptions options, CancellationToken cancellationToken) { - var audioResolver = new AudioResolver(); - var startIndex = currentStreams.Count == 0 ? 0 : currentStreams.Max(i => i.Index) + 1; - var externalAudioStreams = await audioResolver.GetExternalAudioStreams(video, startIndex, options.DirectoryService, _namingOptions, false, _localization, _mediaEncoder, cancellationToken); + var externalAudioStreams = _audioResolver.GetExternalAudioStreams(video, startIndex, options.DirectoryService, false, cancellationToken); - video.AudioFiles = externalAudioStreams.Select(i => i.Path).Distinct().ToArray(); + await foreach (MediaStream externalAudioStream in externalAudioStreams) + { + currentStreams.Add(externalAudioStream); + } - currentStreams.AddRange(externalAudioStreams); + // Select all external audio file paths + video.AudioFiles = currentStreams.Where(i => i.Type == MediaStreamType.Audio && i.IsExternal).Select(i => i.Path).Distinct().ToArray(); } /// -- cgit v1.2.3 From 120828d8d03da08a55edfbce5bfe1463bf06eae3 Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Fri, 3 Dec 2021 19:18:43 +0100 Subject: Replace escaped quote string with quote character in MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs Co-authored-by: Cody Robibero --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 571230312..92b345f12 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -698,7 +698,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.AudioStream != null && state.AudioStream.IsExternal) { - arg.Append(" -i \"").Append(state.AudioStream.Path).Append("\""); + arg.Append(" -i \"").Append(state.AudioStream.Path).Append('"'); } return arg.ToString(); -- cgit v1.2.3 From 99a48554a618302b4fe70ef1fd3d7fd06096c70e Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Fri, 3 Dec 2021 19:19:22 +0100 Subject: Optimize calculation of external audio stream index in MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs Co-authored-by: Cody Robibero --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 92b345f12..91f6654bb 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2007,7 +2007,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.AudioStream.IsExternal) { int externalAudioMapIndex = state.SubtitleStream != null && state.SubtitleStream.IsExternal ? 2 : 1; - int externalAudioStream = state.MediaSource.MediaStreams.Where(i => i.Path == state.AudioStream.Path).ToList().IndexOf(state.AudioStream); + int externalAudioStream = state.MediaSource.MediaStreams.FindIndex(i => i.Path == state.AudioStream.Path); args += string.Format( CultureInfo.InvariantCulture, -- cgit v1.2.3 From d47811bdaf8bbfc74c8185ef0bff7490726828d9 Mon Sep 17 00:00:00 2001 From: Jonas Resch Date: Wed, 8 Dec 2021 10:17:25 +0100 Subject: Fix wrong ffmpeg map argument due to wrong calculation --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 91f6654bb..92b345f12 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2007,7 +2007,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.AudioStream.IsExternal) { int externalAudioMapIndex = state.SubtitleStream != null && state.SubtitleStream.IsExternal ? 2 : 1; - int externalAudioStream = state.MediaSource.MediaStreams.FindIndex(i => i.Path == state.AudioStream.Path); + int externalAudioStream = state.MediaSource.MediaStreams.Where(i => i.Path == state.AudioStream.Path).ToList().IndexOf(state.AudioStream); args += string.Format( CultureInfo.InvariantCulture, -- 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.Controller/MediaEncoding/EncodingHelper.cs') 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.Controller/MediaEncoding/EncodingHelper.cs') 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 ebbde383e8db2ac51fe6c00420ab56718d3e1b0d Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sun, 26 Dec 2021 11:08:43 -0700 Subject: Fix analysis issues --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index c139d9d40..8c5474539 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2670,7 +2670,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// Encoding state. /// Encoding options. /// Video encoder to use. - /// The tuple contains three lists: main, sub and overlay filters + /// The tuple contains three lists: main, sub and overlay filters. public (List, List, List) GetSwVidFilterChain( EncodingJobInfo state, EncodingOptions options, @@ -2750,7 +2750,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// Encoding state. /// Encoding options. /// Video encoder to use. - /// The tuple contains three lists: main, sub and overlay filters + /// The tuple contains three lists: main, sub and overlay filters. public (List, List, List) GetNvidiaVidFilterChain( EncodingJobInfo state, EncodingOptions options, @@ -2937,7 +2937,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// Encoding state. /// Encoding options. /// Video encoder to use. - /// The tuple contains three lists: main, sub and overlay filters + /// The tuple contains three lists: main, sub and overlay filters. public (List, List, List) GetAmdVidFilterChain( EncodingJobInfo state, EncodingOptions options, @@ -3135,7 +3135,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// Encoding state. /// Encoding options. /// Video encoder to use. - /// The tuple contains three lists: main, sub and overlay filters + /// The tuple contains three lists: main, sub and overlay filters. public (List, List, List) GetIntelVidFilterChain( EncodingJobInfo state, EncodingOptions options, @@ -3588,7 +3588,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// Encoding state. /// Encoding options. /// Video encoder to use. - /// The tuple contains three lists: main, sub and overlay filters + /// The tuple contains three lists: main, sub and overlay filters. public (List, List, List) GetVaapiVidFilterChain( EncodingJobInfo state, EncodingOptions options, -- 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.Controller/MediaEncoding/EncodingHelper.cs') 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