aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller/MediaEncoding
diff options
context:
space:
mode:
authorJoshua M. Boniface <joshua@boniface.me>2021-08-18 02:46:59 -0400
committerGitHub <noreply@github.com>2021-08-18 02:46:59 -0400
commit72d3f7020ad80ce1a53eeae8c5d57abeb22a4679 (patch)
treedd43e663838cdc7d99a4af565523df58ae23c856 /MediaBrowser.Controller/MediaEncoding
parent7aef0fce444e6d8e06386553ec7ea1401a01bbb1 (diff)
parente5cbafdb6b47377052e0d638908ef96e30a997d6 (diff)
Merge branch 'master' into patch-2
Diffstat (limited to 'MediaBrowser.Controller/MediaEncoding')
-rw-r--r--MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs204
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs1772
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs485
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs282
-rw-r--r--MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs2
-rw-r--r--MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs9
-rw-r--r--MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs74
-rw-r--r--MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs10
-rw-r--r--MediaBrowser.Controller/MediaEncoding/ImageEncodingOptions.cs2
-rw-r--r--MediaBrowser.Controller/MediaEncoding/JobLogger.cs12
-rw-r--r--MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs39
-rw-r--r--MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs13
-rw-r--r--MediaBrowser.Controller/MediaEncoding/TranscodingJobType.cs23
13 files changed, 1779 insertions, 1148 deletions
diff --git a/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs
new file mode 100644
index 000000000..dd6f468da
--- /dev/null
+++ b/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs
@@ -0,0 +1,204 @@
+#nullable disable
+
+#pragma warning disable CS1591
+
+using System;
+using System.Collections.Generic;
+using MediaBrowser.Model.Dlna;
+
+namespace MediaBrowser.Controller.MediaEncoding
+{
+ public class BaseEncodingJobOptions
+ {
+ public BaseEncodingJobOptions()
+ {
+ EnableAutoStreamCopy = true;
+ AllowVideoStreamCopy = true;
+ AllowAudioStreamCopy = true;
+ Context = EncodingContext.Streaming;
+ StreamOptions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+ }
+
+ /// <summary>
+ /// Gets or sets the id.
+ /// </summary>
+ /// <value>The id.</value>
+ public Guid Id { get; set; }
+
+ public string MediaSourceId { get; set; }
+
+ public string DeviceId { get; set; }
+
+ public string Container { get; set; }
+
+ /// <summary>
+ /// Gets or sets the audio codec.
+ /// </summary>
+ /// <value>The audio codec.</value>
+ public string AudioCodec { get; set; }
+
+ public bool EnableAutoStreamCopy { get; set; }
+
+ public bool AllowVideoStreamCopy { get; set; }
+
+ public bool AllowAudioStreamCopy { get; set; }
+
+ public bool BreakOnNonKeyFrames { get; set; }
+
+ /// <summary>
+ /// Gets or sets the audio sample rate.
+ /// </summary>
+ /// <value>The audio sample rate.</value>
+ public int? AudioSampleRate { get; set; }
+
+ public int? MaxAudioBitDepth { get; set; }
+
+ /// <summary>
+ /// Gets or sets the audio bit rate.
+ /// </summary>
+ /// <value>The audio bit rate.</value>
+ public int? AudioBitRate { get; set; }
+
+ /// <summary>
+ /// Gets or sets the audio channels.
+ /// </summary>
+ /// <value>The audio channels.</value>
+ public int? AudioChannels { get; set; }
+
+ public int? MaxAudioChannels { get; set; }
+
+ public bool Static { get; set; }
+
+ /// <summary>
+ /// Gets or sets the profile.
+ /// </summary>
+ /// <value>The profile.</value>
+ public string Profile { get; set; }
+
+ /// <summary>
+ /// Gets or sets the level.
+ /// </summary>
+ /// <value>The level.</value>
+ public string Level { get; set; }
+
+ /// <summary>
+ /// Gets or sets the framerate.
+ /// </summary>
+ /// <value>The framerate.</value>
+ public float? Framerate { get; set; }
+
+ public float? MaxFramerate { get; set; }
+
+ public bool CopyTimestamps { get; set; }
+
+ /// <summary>
+ /// Gets or sets the start time ticks.
+ /// </summary>
+ /// <value>The start time ticks.</value>
+ public long? StartTimeTicks { get; set; }
+
+ /// <summary>
+ /// Gets or sets the width.
+ /// </summary>
+ /// <value>The width.</value>
+ public int? Width { get; set; }
+
+ /// <summary>
+ /// Gets or sets the height.
+ /// </summary>
+ /// <value>The height.</value>
+ public int? Height { get; set; }
+
+ /// <summary>
+ /// Gets or sets the width of the max.
+ /// </summary>
+ /// <value>The width of the max.</value>
+ public int? MaxWidth { get; set; }
+
+ /// <summary>
+ /// Gets or sets the height of the max.
+ /// </summary>
+ /// <value>The height of the max.</value>
+ public int? MaxHeight { get; set; }
+
+ /// <summary>
+ /// Gets or sets the video bit rate.
+ /// </summary>
+ /// <value>The video bit rate.</value>
+ public int? VideoBitRate { get; set; }
+
+ /// <summary>
+ /// Gets or sets the index of the subtitle stream.
+ /// </summary>
+ /// <value>The index of the subtitle stream.</value>
+ public int? SubtitleStreamIndex { get; set; }
+
+ public SubtitleDeliveryMethod SubtitleMethod { get; set; }
+
+ public int? MaxRefFrames { get; set; }
+
+ public int? MaxVideoBitDepth { get; set; }
+
+ public bool RequireAvc { get; set; }
+
+ public bool DeInterlace { get; set; }
+
+ public bool RequireNonAnamorphic { get; set; }
+
+ public int? TranscodingMaxAudioChannels { get; set; }
+
+ public int? CpuCoreLimit { get; set; }
+
+ public string LiveStreamId { get; set; }
+
+ public bool EnableMpegtsM2TsMode { get; set; }
+
+ /// <summary>
+ /// Gets or sets the video codec.
+ /// </summary>
+ /// <value>The video codec.</value>
+ public string VideoCodec { get; set; }
+
+ public string SubtitleCodec { get; set; }
+
+ public string TranscodeReasons { get; set; }
+
+ /// <summary>
+ /// Gets or sets the index of the audio stream.
+ /// </summary>
+ /// <value>The index of the audio stream.</value>
+ public int? AudioStreamIndex { get; set; }
+
+ /// <summary>
+ /// Gets or sets the index of the video stream.
+ /// </summary>
+ /// <value>The index of the video stream.</value>
+ public int? VideoStreamIndex { get; set; }
+
+ public EncodingContext Context { get; set; }
+
+ public Dictionary<string, string> StreamOptions { get; set; }
+
+ public string GetOption(string qualifier, string name)
+ {
+ var value = GetOption(qualifier + "-" + name);
+
+ if (string.IsNullOrEmpty(value))
+ {
+ value = GetOption(name);
+ }
+
+ return value;
+ }
+
+ public string GetOption(string name)
+ {
+ if (StreamOptions.TryGetValue(name, out var value))
+ {
+ return value;
+ }
+
+ return null;
+ }
+ }
+} \ No newline at end of file
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 550916f82..141bb91c5 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -5,30 +7,24 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
-using System.Runtime.InteropServices;
using System.Text;
+using System.Text.RegularExpressions;
using System.Threading;
using Jellyfin.Data.Enums;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Extensions;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
-using Microsoft.Extensions.Configuration;
namespace MediaBrowser.Controller.MediaEncoding
{
public class EncodingHelper
{
- private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+ private static readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IMediaEncoder _mediaEncoder;
- private readonly IFileSystem _fileSystem;
private readonly ISubtitleEncoder _subtitleEncoder;
- private readonly IConfiguration _configuration;
private static readonly string[] _videoProfiles = new[]
{
@@ -43,14 +39,10 @@ namespace MediaBrowser.Controller.MediaEncoding
public EncodingHelper(
IMediaEncoder mediaEncoder,
- IFileSystem fileSystem,
- ISubtitleEncoder subtitleEncoder,
- IConfiguration configuration)
+ ISubtitleEncoder subtitleEncoder)
{
_mediaEncoder = mediaEncoder;
- _fileSystem = fileSystem;
_subtitleEncoder = subtitleEncoder;
- _configuration = configuration;
}
public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
@@ -63,22 +55,22 @@ namespace MediaBrowser.Controller.MediaEncoding
{
// 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 expiremental anyway, it's not worth adding additional variables such as this.
+ // Since transcoding of folder rips is experimental anyway, it's not worth adding additional variables such as this.
if (state.VideoType == VideoType.VideoFile)
{
var hwType = encodingOptions.HardwareAccelerationType;
var codecMap = new Dictionary<string, string>(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"},
- {"vaapi", hwEncoder + "_vaapi"},
- {"videotoolbox", hwEncoder + "_videotoolbox"}
+ { "qsv", hwEncoder + "_qsv" },
+ { hwEncoder + "_qsv", hwEncoder + "_qsv" },
+ { "nvenc", hwEncoder + "_nvenc" },
+ { "amf", hwEncoder + "_amf" },
+ { "omx", hwEncoder + "_omx" },
+ { hwEncoder + "_v4l2m2m", hwEncoder + "_v4l2m2m" },
+ { "mediacodec", hwEncoder + "_mediacodec" },
+ { "vaapi", hwEncoder + "_vaapi" },
+ { "videotoolbox", hwEncoder + "_videotoolbox" }
};
if (!string.IsNullOrEmpty(hwType)
@@ -109,12 +101,66 @@ namespace MediaBrowser.Controller.MediaEncoding
}
return _mediaEncoder.SupportsHwaccel("vaapi");
+ }
+
+ private bool IsCudaSupported()
+ {
+ return _mediaEncoder.SupportsHwaccel("cuda")
+ && _mediaEncoder.SupportsFilter("scale_cuda", null)
+ && _mediaEncoder.SupportsFilter("yadif_cuda", null);
+ }
+ private bool IsTonemappingSupported(EncodingJobInfo state, EncodingOptions options)
+ {
+ var videoStream = state.VideoStream;
+ return IsColorDepth10(state)
+ && _mediaEncoder.SupportsHwaccel("opencl")
+ && options.EnableTonemapping
+ && string.Equals(videoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase);
+ }
+
+ private bool IsVppTonemappingSupported(EncodingJobInfo state, EncodingOptions options)
+ {
+ var videoStream = state.VideoStream;
+ if (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 IsColorDepth10(state)
+ && string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
+ && _mediaEncoder.SupportsHwaccel("vaapi")
+ && options.EnableVppTonemapping
+ && string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase);
+ }
+
+ // 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 IsColorDepth10(state)
+ && string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
+ && _mediaEncoder.SupportsHwaccel("vaapi")
+ && _mediaEncoder.SupportsHwaccel("qsv")
+ && options.EnableVppTonemapping
+ && string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase);
+ }
+
+ // Native VPP tonemapping may come to QSV in the future.
+ return false;
}
/// <summary>
/// Gets the name of the output video codec.
/// </summary>
+ /// <param name="state">Encording state.</param>
+ /// <param name="encodingOptions">Encoding options.</param>
+ /// <returns>Encoder string.</returns>
public string GetVideoEncoder(EncodingJobInfo state, EncodingOptions encodingOptions)
{
var codec = state.OutputVideoCodec;
@@ -248,7 +294,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return null;
}
- // Seeing reported failures here, not sure yet if this is related to specfying input format
+ // Seeing reported failures here, not sure yet if this is related to specifying input format
if (string.Equals(container, "m4v", StringComparison.OrdinalIgnoreCase))
{
return null;
@@ -260,9 +306,20 @@ namespace MediaBrowser.Controller.MediaEncoding
return null;
}
+ // ISO files don't have an ffmpeg format
+ if (string.Equals(container, "iso", StringComparison.OrdinalIgnoreCase))
+ {
+ return null;
+ }
+
return container;
}
+ /// <summary>
+ /// Gets decoder from a codec.
+ /// </summary>
+ /// <param name="codec">Codec to use.</param>
+ /// <returns>Decoder string.</returns>
public string GetDecoderFromCodec(string codec)
{
// For these need to find out the ffmpeg names
@@ -292,6 +349,8 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Infers the audio codec based on the url.
/// </summary>
+ /// <param name="container">Container to use.</param>
+ /// <returns>Codec string.</returns>
public string InferAudioCodec(string container)
{
var ext = "." + (container ?? string.Empty);
@@ -380,25 +439,9 @@ namespace MediaBrowser.Controller.MediaEncoding
public string GetInputPathArgument(EncodingJobInfo state)
{
- var protocol = state.InputProtocol;
var mediaPath = state.MediaPath ?? string.Empty;
- string[] inputPath;
- if (state.IsInputVideo
- && !(state.VideoType == VideoType.Iso && state.IsoMount == null))
- {
- inputPath = MediaEncoderHelpers.GetInputArgument(
- _fileSystem,
- mediaPath,
- state.IsoMount,
- state.PlayableStreamFileNames);
- }
- else
- {
- inputPath = new[] { mediaPath };
- }
-
- return _mediaEncoder.GetInputArgument(inputPath, protocol);
+ return _mediaEncoder.GetInputArgument(mediaPath, state.MediaSource);
}
/// <summary>
@@ -441,24 +484,39 @@ namespace MediaBrowser.Controller.MediaEncoding
return "libopus";
}
+ if (string.Equals(codec, "flac", StringComparison.OrdinalIgnoreCase))
+ {
+ // flac is experimental in mp4 muxer
+ return "flac -strict -2";
+ }
+
return codec.ToLowerInvariant();
}
/// <summary>
/// Gets the input argument.
/// </summary>
+ /// <param name="state">Encoding state.</param>
+ /// <param name="encodingOptions">Encoding options.</param>
+ /// <returns>Input arguments.</returns>
public string GetInputArgument(EncodingJobInfo state, EncodingOptions encodingOptions)
{
var arg = new StringBuilder();
var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions) ?? string.Empty;
var outputVideoCodec = GetVideoEncoder(state, encodingOptions) ?? string.Empty;
+ 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 isQsvDecoder = videoDecoder.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1;
var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1;
- var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
- var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
- var isMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
+ var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
+ var isCuvidHevcDecoder = videoDecoder.Contains("hevc_cuvid", StringComparison.OrdinalIgnoreCase);
+ var isWindows = OperatingSystem.IsWindows();
+ var isLinux = OperatingSystem.IsLinux();
+ var isMacOS = OperatingSystem.IsMacOS();
+ var isTonemappingSupported = IsTonemappingSupported(state, encodingOptions);
+ var isVppTonemappingSupported = IsVppTonemappingSupported(state, encodingOptions);
if (!IsCopyCodec(outputVideoCodec))
{
@@ -468,10 +526,23 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (isVaapiDecoder)
{
- arg.Append("-hwaccel_output_format vaapi ")
- .Append("-vaapi_device ")
- .Append(encodingOptions.VaapiDevice)
- .Append(' ');
+ if (isTonemappingSupported && !isVppTonemappingSupported)
+ {
+ arg.Append("-init_hw_device vaapi=va:")
+ .Append(encodingOptions.VaapiDevice)
+ .Append(' ')
+ .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(encodingOptions.VaapiDevice)
+ .Append(' ');
+ }
}
else if (!isVaapiDecoder && isVaapiEncoder)
{
@@ -479,6 +550,8 @@ namespace MediaBrowser.Controller.MediaEncoding
.Append(encodingOptions.VaapiDevice)
.Append(' ');
}
+
+ arg.Append("-autorotate 0 ");
}
if (state.IsVideoRequest
@@ -507,11 +580,47 @@ namespace MediaBrowser.Controller.MediaEncoding
arg.Append("-hwaccel qsv ");
}
}
+
// While using SW decoder
- else
+ else if (isSwDecoder)
{
arg.Append("-init_hw_device qsv=hw -filter_hw_device hw ");
}
+
+ // Hybrid VPP tonemapping with VAAPI
+ else if (isVaapiDecoder && isVppTonemappingSupported)
+ {
+ arg.Append("-init_hw_device vaapi=va:")
+ .Append(encodingOptions.VaapiDevice)
+ .Append(' ')
+ .Append("-init_hw_device qsv@va ")
+ .Append("-hwaccel_output_format vaapi ");
+ }
+
+ arg.Append("-autorotate 0 ");
+ }
+ }
+
+ if (state.IsVideoRequest
+ && string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)
+ && isNvdecDecoder)
+ {
+ // 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 ");
+ }
+
+ if (state.IsVideoRequest
+ && ((string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)
+ && (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder))
+ || (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)
+ && (isD3d11vaDecoder || isSwDecoder))))
+ {
+ if (isTonemappingSupported)
+ {
+ arg.Append("-init_hw_device opencl=ocl:")
+ .Append(encodingOptions.OpenclDevice)
+ .Append(' ')
+ .Append("-filter_hw_device ocl ");
}
}
@@ -551,7 +660,7 @@ namespace MediaBrowser.Controller.MediaEncoding
/// </summary>
/// <param name="stream">The stream.</param>
/// <returns><c>true</c> if the specified stream is H264; otherwise, <c>false</c>.</returns>
- public bool IsH264(MediaStream stream)
+ public static bool IsH264(MediaStream stream)
{
var codec = stream.Codec ?? string.Empty;
@@ -559,7 +668,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|| codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1;
}
- public bool IsH265(MediaStream stream)
+ public static bool IsH265(MediaStream stream)
{
var codec = stream.Codec ?? string.Empty;
@@ -567,10 +676,17 @@ namespace MediaBrowser.Controller.MediaEncoding
|| codec.IndexOf("hevc", StringComparison.OrdinalIgnoreCase) != -1;
}
- // TODO This is auto inserted into the mpegts mux so it might not be needed
- // https://www.ffmpeg.org/ffmpeg-bitstream-filters.html#h264_005fmp4toannexb
- public string GetBitStreamArgs(MediaStream stream)
+ public static bool IsAAC(MediaStream stream)
+ {
+ var codec = stream.Codec ?? string.Empty;
+
+ return codec.IndexOf("aac", StringComparison.OrdinalIgnoreCase) != -1;
+ }
+
+ public static string GetBitStreamArgs(MediaStream stream)
{
+ // TODO This is auto inserted into the mpegts mux so it might not be needed.
+ // https://www.ffmpeg.org/ffmpeg-bitstream-filters.html#h264_005fmp4toannexb
if (IsH264(stream))
{
return "-bsf:v h264_mp4toannexb";
@@ -579,12 +695,44 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return "-bsf:v hevc_mp4toannexb";
}
+ else if (IsAAC(stream))
+ {
+ // Convert adts header(mpegts) to asc header(mp4).
+ return "-bsf:a aac_adtstoasc";
+ }
else
{
return null;
}
}
+ public static string GetAudioBitStreamArguments(EncodingJobInfo state, string segmentContainer, string mediaSourceContainer)
+ {
+ var bitStreamArgs = string.Empty;
+ var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.');
+
+ // Apply aac_adtstoasc bitstream filter when media source is in mpegts.
+ if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase)
+ && (string.Equals(mediaSourceContainer, "mpegts", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(mediaSourceContainer, "hls", StringComparison.OrdinalIgnoreCase)))
+ {
+ bitStreamArgs = GetBitStreamArgs(state.AudioStream);
+ bitStreamArgs = string.IsNullOrEmpty(bitStreamArgs) ? string.Empty : " " + bitStreamArgs;
+ }
+
+ return bitStreamArgs;
+ }
+
+ public static string GetSegmentFileExtension(string segmentContainer)
+ {
+ if (!string.IsNullOrWhiteSpace(segmentContainer))
+ {
+ return "." + segmentContainer;
+ }
+
+ return ".ts";
+ }
+
public string GetVideoBitrateParam(EncodingJobInfo state, string videoCodec)
{
var bitrate = state.OutputVideoBitrate;
@@ -632,16 +780,30 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Empty;
}
- public string NormalizeTranscodingLevel(string videoCodec, string level)
+ public static string NormalizeTranscodingLevel(EncodingJobInfo state, string level)
{
- // Clients may direct play higher than level 41, but there's no reason to transcode higher
- if (double.TryParse(level, NumberStyles.Any, _usCulture, out double requestLevel)
- && requestLevel > 41
- && (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoCodec, "h265", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)))
+ if (double.TryParse(level, NumberStyles.Any, _usCulture, out double requestLevel))
{
- return "41";
+ if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase))
+ {
+ // Transcode to level 5.0 and lower for maximum compatibility.
+ // Level 5.0 is suitable for up to 4k 30fps hevc encoding, otherwise let the encoder to handle it.
+ // https://en.wikipedia.org/wiki/High_Efficiency_Video_Coding_tiers_and_levels
+ // MaxLumaSampleRate = 3840*2160*30 = 248832000 < 267386880.
+ if (requestLevel >= 150)
+ {
+ return "150";
+ }
+ }
+ else if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
+ {
+ // Clients may direct play higher than level 41, but there's no reason to transcode higher.
+ if (requestLevel >= 41)
+ {
+ return "41";
+ }
+ }
}
return level;
@@ -744,13 +906,130 @@ namespace MediaBrowser.Controller.MediaEncoding
return null;
}
+ public string GetHlsVideoKeyFrameArguments(
+ EncodingJobInfo state,
+ string codec,
+ int segmentLength,
+ bool isEventPlaylist,
+ int? startNumber)
+ {
+ var args = string.Empty;
+ var gopArg = string.Empty;
+ var keyFrameArg = string.Empty;
+ if (isEventPlaylist)
+ {
+ keyFrameArg = string.Format(
+ CultureInfo.InvariantCulture,
+ " -force_key_frames:0 \"expr:gte(t,n_forced*{0})\"",
+ segmentLength);
+ }
+ else if (startNumber.HasValue)
+ {
+ keyFrameArg = string.Format(
+ CultureInfo.InvariantCulture,
+ " -force_key_frames:0 \"expr:gte(t,{0}+n_forced*{1})\"",
+ startNumber.Value * segmentLength,
+ segmentLength);
+ }
+
+ var framerate = state.VideoStream?.RealFrameRate;
+ if (framerate.HasValue)
+ {
+ // This is to make sure keyframe interval is limited to our segment,
+ // as forcing keyframes is not enough.
+ // Example: we encoded half of desired length, then codec detected
+ // scene cut and inserted a keyframe; next forced keyframe would
+ // be created outside of segment, which breaks seeking.
+ // -sc_threshold 0 is used to prevent the hardware encoder from post processing to break the set keyframe.
+ gopArg = string.Format(
+ CultureInfo.InvariantCulture,
+ " -g:v:0 {0} -keyint_min:v:0 {0} -sc_threshold:v:0 0",
+ Math.Ceiling(segmentLength * framerate.Value));
+ }
+
+ // Unable to force key frames using these encoders, set key frames by GOP.
+ if (string.Equals(codec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codec, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codec, "h264_amf", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codec, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codec, "hevc_amf", StringComparison.OrdinalIgnoreCase))
+ {
+ args += gopArg;
+ }
+ else if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase))
+ {
+ args += " " + keyFrameArg;
+ }
+ else
+ {
+ args += " " + keyFrameArg + gopArg;
+ }
+
+ return args;
+ }
+
/// <summary>
/// Gets the video bitrate to specify on the command line.
/// </summary>
+ /// <param name="state">Encoding state.</param>
+ /// <param name="videoEncoder">Video encoder to use.</param>
+ /// <param name="encodingOptions">Encoding options.</param>
+ /// <param name="defaultPreset">Default present to use for encoding.</param>
+ /// <returns>Video bitrate.</returns>
public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, string defaultPreset)
{
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";
+ }
+ }
+ }
+
+ 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 isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase);
@@ -759,11 +1038,11 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (!string.IsNullOrEmpty(encodingOptions.EncoderPreset))
{
- param += "-preset " + encodingOptions.EncoderPreset;
+ param += " -preset " + encodingOptions.EncoderPreset;
}
else
{
- param += "-preset " + defaultPreset;
+ param += " -preset " + defaultPreset;
}
int encodeCrf = encodingOptions.H264Crf;
@@ -787,38 +1066,39 @@ namespace MediaBrowser.Controller.MediaEncoding
param += " -crf " + defaultCrf;
}
}
- else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)) // h264 (h264_qsv)
+ else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) // h264 (h264_qsv)
+ || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_qsv)
{
string[] valid_h264_qsv = { "veryslow", "slower", "slow", "medium", "fast", "faster", "veryfast" };
if (valid_h264_qsv.Contains(encodingOptions.EncoderPreset, StringComparer.OrdinalIgnoreCase))
{
- param += "-preset " + encodingOptions.EncoderPreset;
+ param += " -preset " + encodingOptions.EncoderPreset;
}
else
{
- param += "-preset 7";
+ param += " -preset 7";
}
param += " -look_ahead 0";
}
else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc)
- || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase))
+ || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_nvenc)
{
switch (encodingOptions.EncoderPreset)
{
case "veryslow":
- param += "-preset slow"; // lossless is only supported on maxwell and newer(2014+)
+ param += " -preset slow"; // lossless is only supported on maxwell and newer(2014+)
break;
case "slow":
case "slower":
- param += "-preset slow";
+ param += " -preset slow";
break;
case "medium":
- param += "-preset medium";
+ param += " -preset medium";
break;
case "fast":
@@ -826,27 +1106,27 @@ namespace MediaBrowser.Controller.MediaEncoding
case "veryfast":
case "superfast":
case "ultrafast":
- param += "-preset fast";
+ param += " -preset fast";
break;
default:
- param += "-preset default";
+ param += " -preset default";
break;
}
}
- else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
+ else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) // h264 (h264_amf)
+ || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_amf)
{
switch (encodingOptions.EncoderPreset)
{
case "veryslow":
case "slow":
case "slower":
- param += "-quality quality";
+ param += " -quality quality";
break;
case "medium":
- param += "-quality balanced";
+ param += " -quality balanced";
break;
case "fast":
@@ -854,13 +1134,31 @@ namespace MediaBrowser.Controller.MediaEncoding
case "veryfast":
case "superfast":
case "ultrafast":
- param += "-quality speed";
+ param += " -quality speed";
break;
default:
- param += "-quality speed";
+ param += " -quality speed";
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";
+ }
}
else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase)) // webm
{
@@ -882,7 +1180,9 @@ namespace MediaBrowser.Controller.MediaEncoding
profileScore = Math.Min(profileScore, 2);
// http://www.webmproject.org/docs/encoder-parameters/
- param += string.Format(CultureInfo.InvariantCulture, "-speed 16 -quality good -profile:v {0} -slices 8 -crf {1} -qmin {2} -qmax {3}",
+ param += string.Format(
+ CultureInfo.InvariantCulture,
+ " -speed 16 -quality good -profile:v {0} -slices 8 -crf {1} -qmin {2} -qmax {3}",
profileScore.ToString(_usCulture),
crf,
qmin,
@@ -890,15 +1190,15 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (string.Equals(videoEncoder, "mpeg4", StringComparison.OrdinalIgnoreCase))
{
- param += "-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2";
+ param += " -mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2";
}
else if (string.Equals(videoEncoder, "wmv2", StringComparison.OrdinalIgnoreCase)) // asf/wmv
{
- param += "-qmin 2";
+ param += " -qmin 2";
}
else if (string.Equals(videoEncoder, "msmpeg4", StringComparison.OrdinalIgnoreCase))
{
- param += "-mbd 2";
+ param += " -mbd 2";
}
param += GetVideoBitrateParam(state, videoEncoder);
@@ -910,25 +1210,88 @@ namespace MediaBrowser.Controller.MediaEncoding
}
var targetVideoCodec = state.ActualOutputVideoCodec;
+ if (string.Equals(targetVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(targetVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
+ {
+ targetVideoCodec = "hevc";
+ }
+
+ var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault() ?? string.Empty;
+ profile = Regex.Replace(profile, @"\s+", string.Empty);
- var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault();
+ // We only transcode to HEVC 8-bit for now, force Main Profile.
+ if (profile.Contains("main10", StringComparison.OrdinalIgnoreCase)
+ || profile.Contains("mainstill", StringComparison.OrdinalIgnoreCase))
+ {
+ profile = "main";
+ }
+
+ // Extended Profile is not supported by any known h264 encoders, force Main Profile.
+ if (profile.Contains("extended", StringComparison.OrdinalIgnoreCase))
+ {
+ profile = "main";
+ }
- // vaapi does not support Baseline profile, force Constrained Baseline in this case,
- // which is compatible (and ugly)
+ // Only libx264 support encoding H264 High 10 Profile, otherwise force High Profile.
+ if (!string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
+ && profile.Contains("high10", StringComparison.OrdinalIgnoreCase))
+ {
+ profile = "high";
+ }
+
+ // h264_vaapi does not support Baseline profile, force Constrained Baseline in this case,
+ // which is compatible (and ugly).
if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
- && profile != null
- && profile.IndexOf("baseline", StringComparison.OrdinalIgnoreCase) != -1)
+ && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
+ {
+ profile = "constrained_baseline";
+ }
+
+ // libx264, h264_qsv and h264_nvenc does not support Constrained Baseline profile, force Baseline in this case.
+ if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
+ && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
+ {
+ profile = "baseline";
+ }
+
+ // libx264, h264_qsv, h264_nvenc and h264_vaapi does not support Constrained High profile, force High in this case.
+ if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
+ && profile.Contains("high", StringComparison.OrdinalIgnoreCase))
+ {
+ profile = "high";
+ }
+
+ if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
+ && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
{
profile = "constrained_baseline";
}
+ if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
+ && profile.Contains("constrainedhigh", StringComparison.OrdinalIgnoreCase))
+ {
+ 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)
&& !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase))
{
// not supported by h264_omx
- param += " -profile:v " + profile;
+ param += " -profile:v:0 " + profile;
}
}
@@ -936,55 +1299,35 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.IsNullOrEmpty(level))
{
- level = NormalizeTranscodingLevel(state.OutputVideoCodec, level);
+ level = NormalizeTranscodingLevel(state, level);
- // h264_qsv and h264_nvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format
- // also needed for libx264 due to https://trac.ffmpeg.org/ticket/3307
+ // libx264, QSV, AMF, VAAPI can adjust the given level to match the output.
if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
- {
- switch (level)
- {
- case "30":
- param += " -level 3.0";
- break;
- case "31":
- param += " -level 3.1";
- break;
- case "32":
- param += " -level 3.2";
- break;
- case "40":
- param += " -level 4.0";
- break;
- case "41":
- param += " -level 4.1";
- break;
- case "42":
- param += " -level 4.2";
- break;
- case "50":
- param += " -level 5.0";
- break;
- case "51":
- param += " -level 5.1";
- break;
- case "52":
- param += " -level 5.2";
- break;
- default:
- param += " -level " + level;
- break;
+ || string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
+ {
+ param += " -level " + level;
+ }
+ 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))
+ {
+ param += " -level " + (hevcLevel / 3);
}
}
+ else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
+ {
+ 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))
{
- // nvenc doesn't decode with param -level set ?!
- // TODO:
+ // level option may cause NVENC to fail.
+ // NVENC cannot adjust the given level, just throw an error.
}
- else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase))
+ else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase)
+ || !string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
{
param += " -level " + level;
}
@@ -997,20 +1340,11 @@ namespace MediaBrowser.Controller.MediaEncoding
if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
{
- // todo
- }
-
- 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_v4l2m2m", StringComparison.OrdinalIgnoreCase))
- {
- param = "-pix_fmt yuv420p " + param;
- }
-
- if (string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase))
- {
- param = "-pix_fmt nv21 " + param;
+ // libx265 only accept level option in -x265-params.
+ // level option may cause libx265 to fail.
+ // libx265 cannot adjust the given level, just throw an error.
+ // TODO: set fine tuned params.
+ param += " -x265-params:0 no-info=1";
}
return param;
@@ -1073,7 +1407,8 @@ namespace MediaBrowser.Controller.MediaEncoding
var requestedProfile = requestedProfiles[0];
// strip spaces because they may be stripped out on the query string as well
- if (!string.IsNullOrEmpty(videoStream.Profile) && !requestedProfiles.Contains(videoStream.Profile.Replace(" ", ""), StringComparer.OrdinalIgnoreCase))
+ 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);
@@ -1289,7 +1624,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|| string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))
{
- return .5;
+ return .6;
}
return 1;
@@ -1323,27 +1658,55 @@ namespace MediaBrowser.Controller.MediaEncoding
public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream)
{
- if (request.AudioBitRate.HasValue)
- {
- // Don't encode any higher than this
- return Math.Min(384000, request.AudioBitRate.Value);
- }
-
- return null;
+ return GetAudioBitrateParam(request.AudioBitRate, request.AudioCodec, audioStream);
}
- public int? GetAudioBitrateParam(int? audioBitRate, MediaStream audioStream)
+ public int? GetAudioBitrateParam(int? audioBitRate, string audioCodec, MediaStream audioStream)
{
- if (audioBitRate.HasValue)
+ if (audioStream == null)
+ {
+ return null;
+ }
+
+ if (audioBitRate.HasValue && string.IsNullOrEmpty(audioCodec))
{
- // Don't encode any higher than this
return Math.Min(384000, audioBitRate.Value);
}
- return null;
+ if (audioBitRate.HasValue && !string.IsNullOrEmpty(audioCodec))
+ {
+ if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
+ {
+ if ((audioStream.Channels ?? 0) >= 6)
+ {
+ return Math.Min(640000, audioBitRate.Value);
+ }
+
+ return Math.Min(384000, audioBitRate.Value);
+ }
+
+ if (string.Equals(audioCodec, "flac", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(audioCodec, "alac", StringComparison.OrdinalIgnoreCase))
+ {
+ if ((audioStream.Channels ?? 0) >= 6)
+ {
+ return Math.Min(3584000, audioBitRate.Value);
+ }
+
+ return Math.Min(1536000, audioBitRate.Value);
+ }
+ }
+
+ // Empty bitrate area is not allow on iOS
+ // Default audio bitrate to 128K if it is not being requested
+ // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options
+ return 128000;
}
- public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions, bool isHls)
+ public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions)
{
var channels = state.OutputAudioChannels;
@@ -1374,7 +1737,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (filters.Count > 0)
{
- return "-af \"" + string.Join(",", filters) + "\"";
+ return " -af \"" + string.Join(',', filters) + "\"";
}
return string.Empty;
@@ -1389,6 +1752,11 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <returns>System.Nullable{System.Int32}.</returns>
public int? GetNumAudioChannelsParam(EncodingJobInfo state, MediaStream audioStream, string outputAudioCodec)
{
+ if (audioStream == null)
+ {
+ return null;
+ }
+
var request = state.BaseRequest;
var inputChannels = audioStream?.Channels;
@@ -1400,7 +1768,6 @@ namespace MediaBrowser.Controller.MediaEncoding
var codec = outputAudioCodec ?? string.Empty;
-
int? transcoderChannelLimit;
if (codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1)
{
@@ -1412,6 +1779,11 @@ namespace MediaBrowser.Controller.MediaEncoding
// libmp3lame currently only supports two channel output
transcoderChannelLimit = 2;
}
+ else if (codec.IndexOf("aac", StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ // aac is able to handle 8ch(7.1 layout)
+ transcoderChannelLimit = 8;
+ }
else
{
// If we don't have any media info then limit it to 6 to prevent encoding errors due to asking for too many channels
@@ -1440,6 +1812,16 @@ namespace MediaBrowser.Controller.MediaEncoding
: transcoderChannelLimit.Value;
}
+ // Avoid transcoding to audio channels other than 1ch, 2ch, 6ch (5.1 layout) and 8ch (7.1 layout).
+ // https://developer.apple.com/documentation/http_live_streaming/hls_authoring_specification_for_apple_devices
+ if (isTranscodingAudio
+ && state.TranscodingType != TranscodingJobType.Progressive
+ && resultChannels.HasValue
+ && ((resultChannels.Value > 2 && resultChannels.Value < 6) || resultChannels.Value == 7))
+ {
+ resultChannels = 2;
+ }
+
return resultChannels;
}
@@ -1599,8 +1981,12 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
- /// Gets the graphical subtitle param.
+ /// Gets the graphical subtitle parameter.
/// </summary>
+ /// <param name="state">Encoding state.</param>
+ /// <param name="options">Encoding options.</param>
+ /// <param name="outputVideoCodec">Video codec to use.</param>
+ /// <returns>Graphical subtitle parameter.</returns>
public string GetGraphicalSubtitleParam(
EncodingJobInfo state,
EncodingOptions options,
@@ -1611,70 +1997,30 @@ namespace MediaBrowser.Controller.MediaEncoding
var outputSizeParam = ReadOnlySpan<char>.Empty;
var request = state.BaseRequest;
- // Add resolution params, if specified
- if (request.Width.HasValue
- || request.Height.HasValue
- || request.MaxHeight.HasValue
- || request.MaxWidth.HasValue)
- {
- outputSizeParam = GetOutputSizeParam(state, options, outputVideoCodec).TrimEnd('"');
-
- // hwupload=extra_hw_frames=64,vpp_qsv (for overlay_qsv on linux)
- var index = outputSizeParam.IndexOf("hwupload=extra_hw_frames", StringComparison.OrdinalIgnoreCase);
- if (index != -1)
- {
- outputSizeParam = outputSizeParam.Slice(index);
- }
- else
- {
- // vpp_qsv
- index = outputSizeParam.IndexOf("vpp", StringComparison.OrdinalIgnoreCase);
- if (index != -1)
- {
- outputSizeParam = outputSizeParam.Slice(index);
- }
- else
- {
- // hwdownload,format=p010le (hardware decode + software encode for vaapi)
- index = outputSizeParam.IndexOf("hwdownload", StringComparison.OrdinalIgnoreCase);
- if (index != -1)
- {
- outputSizeParam = outputSizeParam.Slice(index);
- }
- else
- {
- // format=nv12|vaapi,hwupload,scale_vaapi
- index = outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase);
- if (index != -1)
- {
- outputSizeParam = outputSizeParam.Slice(index);
- }
- else
- {
- // yadif,scale=expr
- index = outputSizeParam.IndexOf("yadif", StringComparison.OrdinalIgnoreCase);
- if (index != -1)
- {
- outputSizeParam = outputSizeParam.Slice(index);
- }
- else
- {
- // scale=expr
- index = outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase);
- if (index != -1)
- {
- outputSizeParam = outputSizeParam.Slice(index);
- }
- }
- }
- }
- }
- }
- }
+ outputSizeParam = GetOutputSizeParamInternal(state, options, outputVideoCodec);
var videoSizeParam = string.Empty;
var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options) ?? string.Empty;
- var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
+ 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 isTonemappingSupported = IsTonemappingSupported(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);
+
+ // Tonemapping and burn-in graphical subtitles requires overlay_vaapi.
+ // But it's still in ffmpeg mailing list. Disable it for now.
+ if (isTonemappingSupportedOnVaapi && isTonemappingSupported && !isVppTonemappingSupported)
+ {
+ return GetOutputSizeParam(state, options, outputVideoCodec);
+ }
// Setup subtitle scaling
if (state.VideoStream != null && state.VideoStream.Width.HasValue && state.VideoStream.Height.HasValue)
@@ -1694,10 +2040,15 @@ namespace MediaBrowser.Controller.MediaEncoding
height.Value);
}
- // For QSV, feed it into hardware encoder now
- if (isLinux && string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
+ if (!string.IsNullOrEmpty(videoSizeParam)
+ && !(isTonemappingSupportedOnQsv && isVppTonemappingSupported))
{
- videoSizeParam += ",hwupload=extra_hw_frames=64";
+ // For QSV, feed it into hardware encoder now
+ if (isLinux && (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)))
+ {
+ videoSizeParam += ",hwupload=extra_hw_frames=64";
+ }
}
}
@@ -1716,28 +2067,35 @@ namespace MediaBrowser.Controller.MediaEncoding
: " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\"";
// When the input may or may not be hardware VAAPI decodable
- if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(outputVideoCodec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase))
{
/*
[base]: HW scaling video to OutputSize
[sub]: SW scaling subtitle to FixedOutputSize
[base][sub]: SW overlay
*/
- retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload[base];[base][sub]overlay,format=nv12,hwupload\"";
+ retStr = !outputSizeParam.IsEmpty
+ ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload[base];[base][sub]overlay,format=nv12,hwupload\""
+ : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]hwdownload[base];[base][sub]overlay,format=nv12,hwupload\"";
}
// 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, "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 = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\"";
+ retStr = !outputSizeParam.IsEmpty
+ ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\""
+ : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\"";
}
- else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
+ 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.
@@ -1745,12 +2103,22 @@ namespace MediaBrowser.Controller.MediaEncoding
with fixed frame size.
Currently only supports linux.
*/
- if (isLinux)
+ if (isTonemappingSupportedOnQsv && isVppTonemappingSupported)
{
- retStr = !outputSizeParam.IsEmpty ?
- " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_qsv\"" :
- " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\"";
+ 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}]{3}[base];[base][sub]overlay_qsv\""
+ : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\"";
+ }
+ }
+ else if (isNvdecDecoder && isNvencEncoder)
+ {
+ retStr = !outputSizeParam.IsEmpty
+ ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay,format=nv12|yuv420p,hwupload_cuda\""
+ : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay,format=nv12|yuv420p,hwupload_cuda\"";
}
return string.Format(
@@ -1763,7 +2131,7 @@ namespace MediaBrowser.Controller.MediaEncoding
videoSizeParam);
}
- private (int? width, int? height) GetFixedOutputSize(
+ public static (int? width, int? height) GetFixedOutputSize(
int? videoWidth,
int? videoHeight,
int? requestedWidth,
@@ -1781,8 +2149,8 @@ namespace MediaBrowser.Controller.MediaEncoding
return (null, null);
}
- decimal inputWidth = Convert.ToDecimal(videoWidth ?? requestedWidth);
- decimal inputHeight = Convert.ToDecimal(videoHeight ?? requestedHeight);
+ 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;
@@ -1801,7 +2169,9 @@ namespace MediaBrowser.Controller.MediaEncoding
return (Convert.ToInt32(outputWidth), Convert.ToInt32(outputHeight));
}
- public List<string> GetScalingFilters(EncodingJobInfo state,
+ public List<string> GetScalingFilters(
+ EncodingJobInfo state,
+ EncodingOptions options,
int? videoWidth,
int? videoHeight,
Video3DFormat? threedFormat,
@@ -1822,7 +2192,9 @@ namespace MediaBrowser.Controller.MediaEncoding
requestedMaxHeight);
if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoEncoder, "h264_qsv", 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)
{
@@ -1831,12 +2203,36 @@ namespace MediaBrowser.Controller.MediaEncoding
// 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);
+ 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 isTonemappingSupported = IsTonemappingSupported(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 && (isTonemappingSupported || 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
@@ -1847,22 +2243,69 @@ namespace MediaBrowser.Controller.MediaEncoding
filters.Add(
string.Format(
CultureInfo.InvariantCulture,
- "{0}=w={1}:h={2}:format=nv12{3}",
+ "{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));
}
- else
+
+ // 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}=format=nv12{1}",
+ "{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 isTonemappingSupported = IsTonemappingSupported(state, options);
+ var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase);
+ var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilter("scale_cuda", "Output format (default \"same\")");
+
+ var outputPixFmt = string.Empty;
+ if (isCudaFormatConversionSupported)
+ {
+ outputPixFmt = "format=nv12";
+ if (isTonemappingSupported && isTonemappingSupportedOnNvenc)
+ {
+ outputPixFmt = "format=p010";
+ }
+ }
+
+ 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));
+ }
+ else if (isCudaFormatConversionSupported)
+ {
+ filters.Add(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "scale_cuda={0}",
+ outputPixFmt));
+ }
+ }
else if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1
&& width.HasValue
&& height.HasValue)
@@ -2062,13 +2505,34 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
- /// If we're going to put a fixed size on the command line, this will calculate it.
+ /// Gets the output size parameter.
/// </summary>
+ /// <param name="state">Encoding state.</param>
+ /// <param name="options">Encoding options.</param>
+ /// <param name="outputVideoCodec">Video codec to use.</param>
+ /// <returns>The output size parameter.</returns>
public string GetOutputSizeParam(
EncodingJobInfo state,
EncodingOptions options,
string outputVideoCodec)
{
+ string filters = GetOutputSizeParamInternal(state, options, outputVideoCodec);
+ return string.IsNullOrEmpty(filters) ? string.Empty : " -vf \"" + filters + "\"";
+ }
+
+ /// <summary>
+ /// Gets the output size parameter.
+ /// If we're going to put a fixed size on the command line, this will calculate it.
+ /// </summary>
+ /// <param name="state">Encoding state.</param>
+ /// <param name="options">Encoding options.</param>
+ /// <param name="outputVideoCodec">Video codec to use.</param>
+ /// <returns>The output size parameter.</returns>
+ public string GetOutputSizeParamInternal(
+ EncodingJobInfo state,
+ EncodingOptions options,
+ string outputVideoCodec)
+ {
// http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/
var request = state.BaseRequest;
@@ -2080,34 +2544,181 @@ namespace MediaBrowser.Controller.MediaEncoding
var inputHeight = videoStream?.Height;
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 isNvdecH264Decoder = videoDecoder.IndexOf("h264_cuvid", 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 isLibX264Encoder = outputVideoCodec.IndexOf("libx264", StringComparison.OrdinalIgnoreCase) != -1;
- var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
-
+ var isLibX265Encoder = outputVideoCodec.IndexOf("libx265", StringComparison.OrdinalIgnoreCase) != -1;
+ var isLinux = OperatingSystem.IsLinux();
+ var isColorDepth10 = IsColorDepth10(state);
+ var isTonemappingSupported = IsTonemappingSupported(state, options);
+ var isVppTonemappingSupported = IsVppTonemappingSupported(state, options);
+ var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && (isNvdecDecoder || isCuvidHevcDecoder || 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 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;
- // When the input may or may not be hardware VAAPI decodable
- if (isVaapiH264Encoder)
+ // 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 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 || isTonemappingSupportedOnAmf || (isTonemappingSupportedOnVaapi && !isVppTonemappingSupported))
+ {
+ // Currently only with the use of NVENC decoder can we get a decent performance.
+ // Currently only the HEVC/H265 format is supported with NVDEC decoder.
+ // NVIDIA Pascal and Turing or higher are recommended.
+ // AMD Polaris and Vega or higher are recommended.
+ // Intel Kaby Lake or newer is required.
+ if (isTonemappingSupported)
+ {
+ var parameters = "tonemap_opencl=format=nv12:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:desat={1}:threshold={2}:peak={3}";
+
+ if (options.TonemappingParam != 0)
+ {
+ parameters += ":param={4}";
+ }
+
+ if (!string.Equals(options.TonemappingRange, "auto", StringComparison.OrdinalIgnoreCase))
+ {
+ parameters += ":range={5}";
+ }
+
+ 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));
+ }
+
+ // Convert to hardware pixel format p010 when using SW decoder.
+ filters.Add("format=p010");
+ }
+
+ if ((isDeinterlaceH264 || isDeinterlaceHevc) && isNvdecDecoder)
+ {
+ isCudaDeintInAdvance = true;
+ filters.Add(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "yadif_cuda={0}:-1:0",
+ doubleRateDeinterlace ? "1" : "0"));
+ }
+
+ if (isVaapiDecoder || isNvdecDecoder)
+ {
+ isScalingInAdvance = true;
+ filters.AddRange(
+ GetScalingFilters(
+ state,
+ options,
+ inputWidth,
+ inputHeight,
+ threeDFormat,
+ videoDecoder,
+ outputVideoCodec,
+ request.Width,
+ request.Height,
+ request.MaxWidth,
+ request.MaxHeight));
+ }
+
+ // hwmap the HDR data to opencl device by cl-va p010 interop.
+ if (isVaapiDecoder)
+ {
+ filters.Add("hwmap");
+ }
+
+ // convert cuda device data to p010 host data.
+ if (isNvdecDecoder)
+ {
+ filters.Add("hwdownload,format=p010");
+ }
+
+ if (isNvdecDecoder || isCuvidHevcDecoder || 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");
+ }
+
+ filters.Add(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ parameters,
+ options.TonemappingAlgorithm,
+ options.TonemappingDesat,
+ options.TonemappingThreshold,
+ options.TonemappingPeak,
+ options.TonemappingParam,
+ options.TonemappingRange));
+
+ if (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder || isD3d11vaDecoder)
+ {
+ filters.Add("hwdownload");
+ filters.Add("format=nv12");
+ }
+
+ if (isNvdecDecoder && isNvencEncoder)
+ {
+ isHwuploadCudaRequired = true;
+ }
+
+ if (isVaapiDecoder)
+ {
+ // Reverse the data route from opencl to vaapi.
+ filters.Add("hwmap=derive_device=vaapi:reverse=1");
+ }
+ }
+ }
+
+ // When the input may or may not be hardware VAAPI decodable.
+ if ((isVaapiH264Encoder || isVaapiHevcEncoder)
+ && !(isTonemappingSupportedOnVaapi && (isTonemappingSupported || isVppTonemappingSupported)))
{
filters.Add("format=nv12|vaapi");
filters.Add("hwupload");
}
- // When burning in graphical subtitles using overlay_qsv, upload videostream to the same qsv context
- else if (isLinux && hasGraphicalSubs && isQsvH264Encoder)
+ // When burning in graphical subtitles using overlay_qsv, upload videostream to the same qsv context.
+ else if (isLinux && hasGraphicalSubs && (isQsvH264Encoder || isQsvHevcEncoder)
+ && !(isTonemappingSupportedOnQsv && isVppTonemappingSupported))
{
filters.Add("hwupload=extra_hw_frames=64");
}
- // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
- else if (IsVaapiSupported(state) && isVaapiDecoder && isLibX264Encoder)
+ // 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 codec = videoStream.Codec.ToLowerInvariant();
- var isColorDepth10 = IsColorDepth10(state);
+ var codec = videoStream.Codec;
// Assert 10-bit hardware VAAPI decodable
if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
@@ -2131,59 +2742,153 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
- // Add hardware deinterlace filter before scaling filter
- if (state.DeInterlace("h264", true) || state.DeInterlace("avc", true))
+ // Add hardware deinterlace filter before scaling filter.
+ if (isDeinterlaceH264 || isDeinterlaceHevc)
{
- if (isVaapiH264Encoder)
+ if (isVaapiEncoder
+ || (isTonemappingSupportedOnQsv && isVppTonemappingSupported))
{
- filters.Add(string.Format(CultureInfo.InvariantCulture, "deinterlace_vaapi"));
+ filters.Add(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "deinterlace_vaapi=rate={0}",
+ doubleRateDeinterlace ? "field" : "frame"));
+ }
+ else if (isNvdecDecoder && !isCudaDeintInAdvance)
+ {
+ filters.Add(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "yadif_cuda={0}:-1:0",
+ doubleRateDeinterlace ? "1" : "0"));
}
}
- // Add software deinterlace filter before scaling filter
- if (state.DeInterlace("h264", true)
- || state.DeInterlace("avc", true)
- || state.DeInterlace("h265", true)
- || state.DeInterlace("hevc", true))
+ // Add software deinterlace filter before scaling filter.
+ if ((isDeinterlaceH264 || isDeinterlaceHevc)
+ && !isVaapiH264Encoder
+ && !isVaapiHevcEncoder
+ && !isQsvH264Encoder
+ && !isQsvHevcEncoder
+ && !isNvdecDecoder
+ && !isCuvidH264Decoder)
{
- string deintParam;
- var inputFramerate = videoStream?.RealFrameRate;
-
- // If it is already 60fps then it will create an output framerate that is much too high for roku and others to handle
- if (string.Equals(options.DeinterlaceMethod, "yadif_bob", StringComparison.OrdinalIgnoreCase) && (inputFramerate ?? 60) <= 30)
+ if (string.Equals(options.DeinterlaceMethod, "bwdif", StringComparison.OrdinalIgnoreCase))
{
- deintParam = "yadif=1:-1:0";
+ filters.Add(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "bwdif={0}:-1:0",
+ doubleRateDeinterlace ? "1" : "0"));
}
else
{
- deintParam = "yadif=0:-1:0";
+ filters.Add(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "yadif={0}:-1:0",
+ doubleRateDeinterlace ? "1" : "0"));
}
+ }
+
+ // Add scaling filter: scale_*=format=nv12 or scale_*=w=*:h=*:format=nv12 or scale=expr
+ if (!isScalingInAdvance)
+ {
+ filters.AddRange(
+ GetScalingFilters(
+ state,
+ options,
+ inputWidth,
+ inputHeight,
+ threeDFormat,
+ videoDecoder,
+ outputVideoCodec,
+ request.Width,
+ request.Height,
+ request.MaxWidth,
+ request.MaxHeight));
+ }
- if (!string.IsNullOrEmpty(deintParam))
+ // Add VPP tonemapping filter for VAAPI.
+ // Full hardware based video post processing, faster than OpenCL but lacks fine tuning options.
+ if ((isTonemappingSupportedOnVaapi || isTonemappingSupportedOnQsv)
+ && isVppTonemappingSupported)
+ {
+ filters.Add("tonemap_vaapi=format=nv12:transfer=bt709:matrix=bt709:primaries=bt709");
+ }
+
+ // Another case is when using Nvenc decoder.
+ if (isNvdecDecoder && !isTonemappingSupported)
+ {
+ var codec = videoStream.Codec;
+ var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilter("scale_cuda", "Output format (default \"same\")");
+
+ // 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 (!isVaapiH264Encoder && !isQsvH264Encoder && !isNvdecH264Decoder)
+ if (isCudaFormatConversionSupported)
+ {
+ if (isLibX264Encoder || isLibX265Encoder || hasSubs)
+ {
+ if (isNvencEncoder)
+ {
+ isHwuploadCudaRequired = true;
+ }
+
+ filters.Add("hwdownload");
+ filters.Add("format=nv12");
+ }
+ }
+ else
{
- filters.Add(deintParam);
+ // Download data from GPU to CPU as p010 format.
+ filters.Add("hwdownload");
+ filters.Add("format=p010");
+
+ // Cuda lacks of a pixel format converter.
+ if (isNvencEncoder)
+ {
+ isHwuploadCudaRequired = true;
+ filters.Add("format=yuv420p");
+ }
}
}
- }
- // Add scaling filter: scale_*=format=nv12 or scale_*=w=*:h=*:format=nv12 or scale=expr
- filters.AddRange(GetScalingFilters(state, inputWidth, inputHeight, threeDFormat, videoDecoder, outputVideoCodec, request.Width, request.Height, request.MaxWidth, request.MaxHeight));
+ // Assert 8-bit hardware decodable
+ else if (!isColorDepth10 && (isLibX264Encoder || isLibX265Encoder || hasSubs))
+ {
+ if (isNvencEncoder)
+ {
+ isHwuploadCudaRequired = true;
+ }
+
+ filters.Add("hwdownload");
+ filters.Add("format=nv12");
+ }
+ }
// Add parameters to use VAAPI with burn-in text subtitles (GH issue #642)
- if (isVaapiH264Encoder)
+ if (isVaapiH264Encoder
+ || isVaapiHevcEncoder
+ || (isTonemappingSupportedOnQsv && isVppTonemappingSupported))
{
if (hasTextSubs)
{
+ // Convert hw context from ocl to va.
+ // For tonemapping and text subs burn-in.
+ if (isTonemappingSupportedOnVaapi && isTonemappingSupported && !isVppTonemappingSupported)
+ {
+ filters.Add("scale_vaapi");
+ }
+
// Test passed on Intel and AMD gfx
filters.Add("hwmap=mode=read+write");
filters.Add("format=nv12");
}
}
- var output = string.Empty;
-
if (hasTextSubs)
{
var subParam = GetTextSubtitleParam(state);
@@ -2192,18 +2897,40 @@ namespace MediaBrowser.Controller.MediaEncoding
// 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)
+ if (isVaapiH264Encoder || isVaapiHevcEncoder)
{
filters.Add("hwmap");
}
+
+ if (isTonemappingSupportedOnQsv && isVppTonemappingSupported)
+ {
+ filters.Add("hwmap,format=vaapi");
+ }
+
+ if (isNvdecDecoder && isNvencEncoder)
+ {
+ isHwuploadCudaRequired = true;
+ }
+ }
+
+ // Interop the VAAPI data to QSV for hybrid tonemapping
+ if (isTonemappingSupportedOnQsv && isVppTonemappingSupported && !hasGraphicalSubs)
+ {
+ filters.Add("hwmap=derive_device=qsv,scale_qsv");
}
+ if (isHwuploadCudaRequired && !hasGraphicalSubs)
+ {
+ filters.Add("hwupload_cuda");
+ }
+
+ var output = string.Empty;
if (filters.Count > 0)
{
output += string.Format(
CultureInfo.InvariantCulture,
- " -vf \"{0}\"",
- string.Join(",", filters));
+ "{0}",
+ string.Join(',', filters));
}
return output;
@@ -2212,7 +2939,12 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Gets the number of threads.
/// </summary>
- public int GetNumberOfThreads(EncodingJobInfo state, EncodingOptions encodingOptions, string outputVideoCodec)
+ /// <param name="state">Encoding state.</param>
+ /// <param name="encodingOptions">Encoding options.</param>
+ /// <param name="outputVideoCodec">Video codec to use.</param>
+ /// <returns>Number of threads.</returns>
+#nullable enable
+ public static int GetNumberOfThreads(EncodingJobInfo? state, EncodingOptions encodingOptions, string? outputVideoCodec)
{
if (string.Equals(outputVideoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
{
@@ -2222,17 +2954,22 @@ namespace MediaBrowser.Controller.MediaEncoding
return Math.Max(Environment.ProcessorCount - 1, 1);
}
- var threads = state.BaseRequest.CpuCoreLimit ?? encodingOptions.EncodingThreadCount;
+ var threads = state?.BaseRequest.CpuCoreLimit ?? encodingOptions.EncodingThreadCount;
// Automatic
- if (threads <= 0 || threads >= Environment.ProcessorCount)
+ if (threads <= 0)
{
return 0;
}
+ else if (threads >= Environment.ProcessorCount)
+ {
+ return Environment.ProcessorCount;
+ }
return threads;
}
+#nullable disable
public void TryStreamCopy(EncodingJobInfo state)
{
if (state.VideoStream != null && CanStreamCopyVideo(state, state.VideoStream))
@@ -2267,18 +3004,10 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
- public string GetProbeSizeArgument(int numInputFiles)
- => numInputFiles > 1 ? "-probesize " + _configuration.GetFFmpegProbeSize() : string.Empty;
-
- public string GetAnalyzeDurationArgument(int numInputFiles)
- => numInputFiles > 1 ? "-analyzeduration " + _configuration.GetFFmpegAnalyzeDuration() : string.Empty;
-
public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions)
{
var inputModifier = string.Empty;
-
- var numInputFiles = state.PlayableStreamFileNames.Length > 0 ? state.PlayableStreamFileNames.Length : 1;
- var probeSizeArgument = GetProbeSizeArgument(numInputFiles);
+ var probeSizeArgument = string.Empty;
string analyzeDurationArgument;
if (state.MediaSource.AnalyzeDurationMs.HasValue)
@@ -2287,7 +3016,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else
{
- analyzeDurationArgument = GetAnalyzeDurationArgument(numInputFiles);
+ analyzeDurationArgument = string.Empty;
}
if (!string.IsNullOrEmpty(probeSizeArgument))
@@ -2397,6 +3126,11 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.DeInterlace("h264", true))
{
inputModifier += " -deint 1";
+
+ if (!encodingOptions.DeinterlaceDoubleRate || (videoStream?.AverageFrameRate ?? 60) > 30)
+ {
+ inputModifier += " -drop_second_field 1";
+ }
}
}
}
@@ -2433,9 +3167,9 @@ namespace MediaBrowser.Controller.MediaEncoding
return inputModifier;
}
-
public void AttachMediaSourceInfo(
EncodingJobInfo state,
+ EncodingOptions encodingOptions,
MediaSourceInfo mediaSource,
string requestedUrl)
{
@@ -2466,32 +3200,6 @@ namespace MediaBrowser.Controller.MediaEncoding
state.IsoType = mediaSource.IsoType;
- if (mediaSource.VideoType.HasValue)
- {
- state.VideoType = mediaSource.VideoType.Value;
-
- if (mediaSource.VideoType.Value == VideoType.BluRay || mediaSource.VideoType.Value == VideoType.Dvd)
- {
- state.PlayableStreamFileNames = Video.QueryPlayableStreamFiles(state.MediaPath, mediaSource.VideoType.Value).Select(Path.GetFileName).ToArray();
- }
- else if (mediaSource.VideoType.Value == VideoType.Iso && state.IsoType == IsoType.BluRay)
- {
- state.PlayableStreamFileNames = Video.QueryPlayableStreamFiles(state.MediaPath, VideoType.BluRay).Select(Path.GetFileName).ToArray();
- }
- else if (mediaSource.VideoType.Value == VideoType.Iso && state.IsoType == IsoType.Dvd)
- {
- state.PlayableStreamFileNames = Video.QueryPlayableStreamFiles(state.MediaPath, VideoType.Dvd).Select(Path.GetFileName).ToArray();
- }
- else
- {
- state.PlayableStreamFileNames = Array.Empty<string>();
- }
- }
- else
- {
- state.PlayableStreamFileNames = Array.Empty<string>();
- }
-
if (mediaSource.Timestamp.HasValue)
{
state.InputTimestamp = mediaSource.Timestamp.Value;
@@ -2502,8 +3210,8 @@ namespace MediaBrowser.Controller.MediaEncoding
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
if (state.ReadInputAtNativeFramerate
- || mediaSource.Protocol == MediaProtocol.File
- && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase))
+ || (mediaSource.Protocol == MediaProtocol.File
+ && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase)))
{
state.InputVideoSync = "-1";
state.InputAudioSync = "1";
@@ -2554,9 +3262,10 @@ namespace MediaBrowser.Controller.MediaEncoding
state.MediaSource = mediaSource;
var request = state.BaseRequest;
- if (!string.IsNullOrWhiteSpace(request.AudioCodec))
+ var supportedAudioCodecs = state.SupportedAudioCodecs;
+ if (request != null && supportedAudioCodecs != null && supportedAudioCodecs.Length > 0)
{
- var supportedAudioCodecsList = request.AudioCodec.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
+ var supportedAudioCodecsList = supportedAudioCodecs.ToList();
ShiftAudioCodecsIfNeeded(supportedAudioCodecsList, state.AudioStream);
@@ -2565,11 +3274,23 @@ namespace MediaBrowser.Controller.MediaEncoding
request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(i => _mediaEncoder.CanEncodeToAudioCodec(i))
?? state.SupportedAudioCodecs.FirstOrDefault();
}
+
+ var supportedVideoCodecs = state.SupportedVideoCodecs;
+ if (request != null && supportedVideoCodecs != null && supportedVideoCodecs.Length > 0)
+ {
+ var supportedVideoCodecsList = supportedVideoCodecs.ToList();
+
+ ShiftVideoCodecsIfNeeded(supportedVideoCodecsList, encodingOptions);
+
+ state.SupportedVideoCodecs = supportedVideoCodecsList.ToArray();
+
+ request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
+ }
}
private void ShiftAudioCodecsIfNeeded(List<string> audioCodecs, MediaStream audioStream)
{
- // Nothing to do here
+ // No need to shift if there is only one supported audio codec.
if (audioCodecs.Count < 2)
{
return;
@@ -2597,6 +3318,34 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
+ private void ShiftVideoCodecsIfNeeded(List<string> videoCodecs, EncodingOptions encodingOptions)
+ {
+ // Shift hevc/h265 to the end of list if hevc encoding is not allowed.
+ if (encodingOptions.AllowHevcEncoding)
+ {
+ return;
+ }
+
+ // No need to shift if there is only one supported video codec.
+ if (videoCodecs.Count < 2)
+ {
+ return;
+ }
+
+ var shiftVideoCodecs = new[] { "hevc", "h265" };
+ if (videoCodecs.All(i => shiftVideoCodecs.Contains(i, StringComparer.OrdinalIgnoreCase)))
+ {
+ return;
+ }
+
+ while (shiftVideoCodecs.Contains(videoCodecs[0], StringComparer.OrdinalIgnoreCase))
+ {
+ var removed = shiftVideoCodecs[0];
+ videoCodecs.RemoveAt(0);
+ videoCodecs.Add(removed);
+ }
+ }
+
private void NormalizeSubtitleEmbed(EncodingJobInfo state)
{
if (state.SubtitleStream == null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
@@ -2630,7 +3379,7 @@ namespace MediaBrowser.Controller.MediaEncoding
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 expiremental anyway, it's not worth adding additional variables such as this.
+ // Since transcoding of folder rips is experimental anyway, it's not worth adding additional variables such as this.
if (videoType != VideoType.VideoFile)
{
return null;
@@ -2653,63 +3402,32 @@ namespace MediaBrowser.Controller.MediaEncoding
return null;
}
+ // Hybrid VPP tonemapping with VAAPI
+ if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)
+ && IsVppTonemappingSupported(state, encodingOptions))
+ {
+ // 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":
- if (_mediaEncoder.SupportsDecoder("h264_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase))
- {
- // qsv decoder does not support 10-bit input
- if ((videoStream.BitDepth ?? 8) > 8)
- {
- encodingOptions.HardwareDecodingCodecs = Array.Empty<string>();
- return null;
- }
-
- return "-c:v h264_qsv";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "h264_qsv", "h264", isColorDepth10);
case "hevc":
case "h265":
- if (_mediaEncoder.SupportsDecoder("hevc_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase))
- {
- return (isColorDepth10 &&
- !encodingOptions.EnableDecodingColorDepth10Hevc) ? null : "-c:v hevc_qsv";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "hevc_qsv", "hevc", isColorDepth10);
case "mpeg2video":
- if (_mediaEncoder.SupportsDecoder("mpeg2_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v mpeg2_qsv";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "mpeg2_qsv", "mpeg2video", isColorDepth10);
case "vc1":
- if (_mediaEncoder.SupportsDecoder("vc1_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v vc1_qsv";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "vc1_qsv", "vc1", isColorDepth10);
case "vp8":
- if (_mediaEncoder.SupportsDecoder("vp8_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("vp8", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v vp8_qsv";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "vp8_qsv", "vp8", isColorDepth10);
case "vp9":
- if (_mediaEncoder.SupportsDecoder("vp9_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase))
- {
- return (isColorDepth10 &&
- !encodingOptions.EnableDecodingColorDepth10Vp9) ? null : "-c:v vp9_qsv";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "vp9_qsv", "vp9", isColorDepth10);
}
}
else if (string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
@@ -2718,57 +3436,34 @@ namespace MediaBrowser.Controller.MediaEncoding
{
case "avc":
case "h264":
- if (_mediaEncoder.SupportsDecoder("h264_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v h264_cuvid";
- }
-
- break;
+ return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported()
+ ? GetHwaccelType(state, encodingOptions, "h264", isColorDepth10)
+ : GetHwDecoderName(encodingOptions, "h264_cuvid", "h264", isColorDepth10);
case "hevc":
case "h265":
- if (_mediaEncoder.SupportsDecoder("hevc_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase))
- {
- return (isColorDepth10 &&
- !encodingOptions.EnableDecodingColorDepth10Hevc) ? null : "-c:v hevc_cuvid";
- }
-
- break;
+ return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported()
+ ? GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10)
+ : GetHwDecoderName(encodingOptions, "hevc_cuvid", "hevc", isColorDepth10);
case "mpeg2video":
- if (_mediaEncoder.SupportsDecoder("mpeg2_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v mpeg2_cuvid";
- }
-
- break;
+ return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported()
+ ? GetHwaccelType(state, encodingOptions, "mpeg2video", isColorDepth10)
+ : GetHwDecoderName(encodingOptions, "mpeg2_cuvid", "mpeg2video", isColorDepth10);
case "vc1":
- if (_mediaEncoder.SupportsDecoder("vc1_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v vc1_cuvid";
- }
-
- break;
+ return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported()
+ ? GetHwaccelType(state, encodingOptions, "vc1", isColorDepth10)
+ : GetHwDecoderName(encodingOptions, "vc1_cuvid", "vc1", isColorDepth10);
case "mpeg4":
- if (_mediaEncoder.SupportsDecoder("mpeg4_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v mpeg4_cuvid";
- }
-
- break;
+ return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported()
+ ? GetHwaccelType(state, encodingOptions, "mpeg4", isColorDepth10)
+ : GetHwDecoderName(encodingOptions, "mpeg4_cuvid", "mpeg4", isColorDepth10);
case "vp8":
- if (_mediaEncoder.SupportsDecoder("vp8_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("vp8", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v vp8_cuvid";
- }
-
- break;
+ return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported()
+ ? GetHwaccelType(state, encodingOptions, "vp8", isColorDepth10)
+ : GetHwDecoderName(encodingOptions, "vp8_cuvid", "vp8", isColorDepth10);
case "vp9":
- if (_mediaEncoder.SupportsDecoder("vp9_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase))
- {
- return (isColorDepth10 &&
- !encodingOptions.EnableDecodingColorDepth10Vp9) ? null : "-c:v vp9_cuvid";
- }
-
- break;
+ return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported()
+ ? GetHwaccelType(state, encodingOptions, "vp9", isColorDepth10)
+ : GetHwDecoderName(encodingOptions, "vp9_cuvid", "vp9", isColorDepth10);
}
}
else if (string.Equals(encodingOptions.HardwareAccelerationType, "mediacodec", StringComparison.OrdinalIgnoreCase))
@@ -2777,50 +3472,18 @@ namespace MediaBrowser.Controller.MediaEncoding
{
case "avc":
case "h264":
- if (_mediaEncoder.SupportsDecoder("h264_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v h264_mediacodec";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "h264_mediacodec", "h264", isColorDepth10);
case "hevc":
case "h265":
- if (_mediaEncoder.SupportsDecoder("hevc_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase))
- {
- return (isColorDepth10 &&
- !encodingOptions.EnableDecodingColorDepth10Hevc) ? null : "-c:v hevc_mediacodec";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "hevc_mediacodec", "hevc", isColorDepth10);
case "mpeg2video":
- if (_mediaEncoder.SupportsDecoder("mpeg2_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v mpeg2_mediacodec";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "mpeg2_mediacodec", "mpeg2video", isColorDepth10);
case "mpeg4":
- if (_mediaEncoder.SupportsDecoder("mpeg4_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v mpeg4_mediacodec";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "mpeg4_mediacodec", "mpeg4", isColorDepth10);
case "vp8":
- if (_mediaEncoder.SupportsDecoder("vp8_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("vp8", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v vp8_mediacodec";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "vp8_mediacodec", "vp8", isColorDepth10);
case "vp9":
- if (_mediaEncoder.SupportsDecoder("vp9_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase))
- {
- return (isColorDepth10 &&
- !encodingOptions.EnableDecodingColorDepth10Vp9) ? null : "-c:v vp9_mediacodec";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "vp9_mediacodec", "vp9", isColorDepth10);
}
}
else if (string.Equals(encodingOptions.HardwareAccelerationType, "omx", StringComparison.OrdinalIgnoreCase))
@@ -2829,33 +3492,13 @@ namespace MediaBrowser.Controller.MediaEncoding
{
case "avc":
case "h264":
- if (_mediaEncoder.SupportsDecoder("h264_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v h264_mmal";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "h264_mmal", "h264", isColorDepth10);
case "mpeg2video":
- if (_mediaEncoder.SupportsDecoder("mpeg2_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v mpeg2_mmal";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "mpeg2_mmal", "mpeg2video", isColorDepth10);
case "mpeg4":
- if (_mediaEncoder.SupportsDecoder("mpeg4_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v mpeg4_mmal";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "mpeg4_mmal", "mpeg4", isColorDepth10);
case "vc1":
- if (_mediaEncoder.SupportsDecoder("vc1_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v vc1_mmal";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "vc1_mmal", "vc1", isColorDepth10);
}
}
else if (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase))
@@ -2864,20 +3507,18 @@ namespace MediaBrowser.Controller.MediaEncoding
{
case "avc":
case "h264":
- return GetHwaccelType(state, encodingOptions, "h264");
+ return GetHwaccelType(state, encodingOptions, "h264", isColorDepth10);
case "hevc":
case "h265":
- return (isColorDepth10 &&
- !encodingOptions.EnableDecodingColorDepth10Hevc) ? null : GetHwaccelType(state, encodingOptions, "hevc");
+ return GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10);
case "mpeg2video":
- return GetHwaccelType(state, encodingOptions, "mpeg2video");
+ return GetHwaccelType(state, encodingOptions, "mpeg2video", isColorDepth10);
case "vc1":
- return GetHwaccelType(state, encodingOptions, "vc1");
+ return GetHwaccelType(state, encodingOptions, "vc1", isColorDepth10);
case "mpeg4":
- return GetHwaccelType(state, encodingOptions, "mpeg4");
+ return GetHwaccelType(state, encodingOptions, "mpeg4", isColorDepth10);
case "vp9":
- return (isColorDepth10 &&
- !encodingOptions.EnableDecodingColorDepth10Vp9) ? null : GetHwaccelType(state, encodingOptions, "vp9");
+ return GetHwaccelType(state, encodingOptions, "vp9", isColorDepth10);
}
}
else if (string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
@@ -2886,20 +3527,18 @@ namespace MediaBrowser.Controller.MediaEncoding
{
case "avc":
case "h264":
- return GetHwaccelType(state, encodingOptions, "h264");
+ return GetHwaccelType(state, encodingOptions, "h264", isColorDepth10);
case "hevc":
case "h265":
- return (isColorDepth10 &&
- !encodingOptions.EnableDecodingColorDepth10Hevc) ? null : GetHwaccelType(state, encodingOptions, "hevc");
+ return GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10);
case "mpeg2video":
- return GetHwaccelType(state, encodingOptions, "mpeg2video");
+ return GetHwaccelType(state, encodingOptions, "mpeg2video", isColorDepth10);
case "vc1":
- return GetHwaccelType(state, encodingOptions, "vc1");
+ return GetHwaccelType(state, encodingOptions, "vc1", isColorDepth10);
case "vp8":
- return GetHwaccelType(state, encodingOptions, "vp8");
+ return GetHwaccelType(state, encodingOptions, "vp8", isColorDepth10);
case "vp9":
- return (isColorDepth10 &&
- !encodingOptions.EnableDecodingColorDepth10Vp9) ? null : GetHwaccelType(state, encodingOptions, "vp9");
+ return GetHwaccelType(state, encodingOptions, "vp9", isColorDepth10);
}
}
else if (string.Equals(encodingOptions.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
@@ -2908,62 +3547,25 @@ namespace MediaBrowser.Controller.MediaEncoding
{
case "avc":
case "h264":
- if (_mediaEncoder.SupportsDecoder("h264_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v h264_opencl";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "h264_opencl", "h264", isColorDepth10);
case "hevc":
case "h265":
- if (_mediaEncoder.SupportsDecoder("hevc_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase))
- {
- return (isColorDepth10 &&
- !encodingOptions.EnableDecodingColorDepth10Hevc) ? null : "-c:v hevc_opencl";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "hevc_opencl", "hevc", isColorDepth10);
case "mpeg2video":
- if (_mediaEncoder.SupportsDecoder("mpeg2_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v mpeg2_opencl";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "mpeg2_opencl", "mpeg2video", isColorDepth10);
case "mpeg4":
- if (_mediaEncoder.SupportsDecoder("mpeg4_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v mpeg4_opencl";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "mpeg4_opencl", "mpeg4", isColorDepth10);
case "vc1":
- if (_mediaEncoder.SupportsDecoder("vc1_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v vc1_opencl";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "vc1_opencl", "vc1", isColorDepth10);
case "vp8":
- if (_mediaEncoder.SupportsDecoder("vp8_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v vp8_opencl";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "vp8_opencl", "vp8", isColorDepth10);
case "vp9":
- if (_mediaEncoder.SupportsDecoder("vp9_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase))
- {
- return (isColorDepth10 &&
- !encodingOptions.EnableDecodingColorDepth10Vp9) ? null : "-c:v vp9_opencl";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "vp9_opencl", "vp9", isColorDepth10);
}
}
}
- var whichCodec = videoStream.Codec.ToLowerInvariant();
+ var whichCodec = videoStream.Codec?.ToLowerInvariant();
switch (whichCodec)
{
case "avc":
@@ -2982,30 +3584,88 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
- /// Gets a hwaccel type to use as a hardware decoder(dxva/vaapi) depending on the system
+ /// Gets a hw decoder name.
+ /// </summary>
+ /// <param name="options">Encoding options.</param>
+ /// <param name="decoder">Decoder to use.</param>
+ /// <param name="videoCodec">Video codec to use.</param>
+ /// <param name="isColorDepth10">Specifies if color depth 10.</param>
+ /// <returns>Hardware decoder name.</returns>
+ 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;
+ }
+
+ /// <summary>
+ /// Gets a hwaccel type to use as a hardware decoder(dxva/vaapi) depending on the system.
/// </summary>
- public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec)
+ /// <param name="state">Encoding state.</param>
+ /// <param name="options">Encoding options.</param>
+ /// <param name="videoCodec">Video codec to use.</param>
+ /// <param name="isColorDepth10">Specifies if color depth 10.</param>
+ /// <returns>Hardware accelerator type.</returns>
+ public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec, bool isColorDepth10)
{
- var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
- var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
+ 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 ((isDxvaSupported || IsVaapiSupported(state)) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase))
+ if (isColorDepth10 && isCodecAvailable)
{
- if (isLinux)
+ if ((options.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Hevc)
+ || (options.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Vp9))
{
- return "-hwaccel vaapi";
+ return null;
}
+ }
- if (isWindows && isWindows8orLater)
+ 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))
{
- return "-hwaccel d3d11va";
+ if (isWindows && isWindows8orLater)
+ {
+ return "-hwaccel d3d11va";
+ }
+
+ if (isWindows && !isWindows8orLater)
+ {
+ return "-hwaccel dxva2";
+ }
}
+ }
- if (isWindows && !isWindows8orLater)
+ 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 dxva2";
+ return "-hwaccel cuda";
}
}
@@ -3078,7 +3738,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (flags.Count > 0)
{
- return " -fflags " + string.Join("", flags);
+ return " -fflags " + string.Join(string.Empty, flags);
}
return string.Empty;
@@ -3220,7 +3880,7 @@ namespace MediaBrowser.Controller.MediaEncoding
args += " -ar " + state.OutputAudioSampleRate.Value.ToString(_usCulture);
}
- args += " " + GetAudioFilterParam(state, encodingOptions, false);
+ args += GetAudioFilterParam(state, encodingOptions);
return args;
}
@@ -3261,7 +3921,7 @@ namespace MediaBrowser.Controller.MediaEncoding
GetInputArgument(state, encodingOptions),
threads,
" -vn",
- string.Join(" ", audioTranscodeParams),
+ string.Join(' ', audioTranscodeParams),
outputPath,
string.Empty,
string.Empty,
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
index 68bc502a0..fa9f40d60 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
@@ -1,4 +1,6 @@
-#pragma warning disable CS1591
+#nullable disable
+
+#pragma warning disable CS1591, SA1401
using System;
using System.Collections.Generic;
@@ -9,7 +11,6 @@ using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Session;
@@ -19,6 +20,44 @@ namespace MediaBrowser.Controller.MediaEncoding
// For now, a common base class until the API and MediaEncoding classes are unified
public class EncodingJobInfo
{
+ public int? OutputAudioBitrate;
+ public int? OutputAudioChannels;
+
+ private TranscodeReason[] _transcodeReasons = null;
+
+ public EncodingJobInfo(TranscodingJobType jobType)
+ {
+ TranscodingType = jobType;
+ RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+ SupportedAudioCodecs = Array.Empty<string>();
+ SupportedVideoCodecs = Array.Empty<string>();
+ SupportedSubtitleCodecs = Array.Empty<string>();
+ }
+
+ public TranscodeReason[] TranscodeReasons
+ {
+ get
+ {
+ if (_transcodeReasons == null)
+ {
+ if (BaseRequest.TranscodeReasons == null)
+ {
+ return Array.Empty<TranscodeReason>();
+ }
+
+ _transcodeReasons = BaseRequest.TranscodeReasons
+ .Split(',')
+ .Where(i => !string.IsNullOrEmpty(i))
+ .Select(v => (TranscodeReason)Enum.Parse(typeof(TranscodeReason), v, true))
+ .ToArray();
+ }
+
+ return _transcodeReasons;
+ }
+ }
+
+ public IProgress<double> Progress { get; set; }
+
public MediaStream VideoStream { get; set; }
public VideoType VideoType { get; set; }
@@ -33,10 +72,6 @@ namespace MediaBrowser.Controller.MediaEncoding
public bool IsInputVideo { get; set; }
- public IIsoMount IsoMount { get; set; }
-
- public string[] PlayableStreamFileNames { get; set; }
-
public string OutputAudioCodec { get; set; }
public int? OutputVideoBitrate { get; set; }
@@ -61,39 +96,6 @@ namespace MediaBrowser.Controller.MediaEncoding
public string MimeType { get; set; }
- public string GetMimeType(string outputPath, bool enableStreamDefault = true)
- {
- if (!string.IsNullOrEmpty(MimeType))
- {
- return MimeType;
- }
-
- return MimeTypes.GetMimeType(outputPath, enableStreamDefault);
- }
-
- private TranscodeReason[] _transcodeReasons = null;
- public TranscodeReason[] TranscodeReasons
- {
- get
- {
- if (_transcodeReasons == null)
- {
- if (BaseRequest.TranscodeReasons == null)
- {
- return Array.Empty<TranscodeReason>();
- }
-
- _transcodeReasons = BaseRequest.TranscodeReasons
- .Split(',')
- .Where(i => !string.IsNullOrEmpty(i))
- .Select(v => (TranscodeReason)Enum.Parse(typeof(TranscodeReason), v, true))
- .ToArray();
- }
-
- return _transcodeReasons;
- }
- }
-
public bool IgnoreInputDts => MediaSource.IgnoreDts;
public bool IgnoreInputIndex => MediaSource.IgnoreIndex;
@@ -146,192 +148,17 @@ namespace MediaBrowser.Controller.MediaEncoding
public BaseEncodingJobOptions BaseRequest { get; set; }
- public long? StartTimeTicks => BaseRequest.StartTimeTicks;
-
- public bool CopyTimestamps => BaseRequest.CopyTimestamps;
-
- public int? OutputAudioBitrate;
- public int? OutputAudioChannels;
-
- public bool DeInterlace(string videoCodec, bool forceDeinterlaceIfSourceIsInterlaced)
- {
- var videoStream = VideoStream;
- var isInputInterlaced = videoStream != null && videoStream.IsInterlaced;
-
- if (!isInputInterlaced)
- {
- return false;
- }
-
- // Support general param
- if (BaseRequest.DeInterlace)
- {
- return true;
- }
-
- if (!string.IsNullOrEmpty(videoCodec))
- {
- if (string.Equals(BaseRequest.GetOption(videoCodec, "deinterlace"), "true", StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- }
-
- return forceDeinterlaceIfSourceIsInterlaced && isInputInterlaced;
- }
-
- public string[] GetRequestedProfiles(string codec)
- {
- if (!string.IsNullOrEmpty(BaseRequest.Profile))
- {
- return BaseRequest.Profile.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries);
- }
-
- if (!string.IsNullOrEmpty(codec))
- {
- var profile = BaseRequest.GetOption(codec, "profile");
-
- if (!string.IsNullOrEmpty(profile))
- {
- return profile.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries);
- }
- }
-
- return Array.Empty<string>();
- }
-
- public string GetRequestedLevel(string codec)
- {
- if (!string.IsNullOrEmpty(BaseRequest.Level))
- {
- return BaseRequest.Level;
- }
-
- if (!string.IsNullOrEmpty(codec))
- {
- return BaseRequest.GetOption(codec, "level");
- }
-
- return null;
- }
-
- public int? GetRequestedMaxRefFrames(string codec)
- {
- if (BaseRequest.MaxRefFrames.HasValue)
- {
- return BaseRequest.MaxRefFrames;
- }
-
- if (!string.IsNullOrEmpty(codec))
- {
- var value = BaseRequest.GetOption(codec, "maxrefframes");
- if (!string.IsNullOrEmpty(value)
- && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
- {
- return result;
- }
- }
-
- return null;
- }
-
- public int? GetRequestedVideoBitDepth(string codec)
- {
- if (BaseRequest.MaxVideoBitDepth.HasValue)
- {
- return BaseRequest.MaxVideoBitDepth;
- }
-
- if (!string.IsNullOrEmpty(codec))
- {
- var value = BaseRequest.GetOption(codec, "videobitdepth");
- if (!string.IsNullOrEmpty(value)
- && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
- {
- return result;
- }
- }
-
- return null;
- }
-
- public int? GetRequestedAudioBitDepth(string codec)
- {
- if (BaseRequest.MaxAudioBitDepth.HasValue)
- {
- return BaseRequest.MaxAudioBitDepth;
- }
-
- if (!string.IsNullOrEmpty(codec))
- {
- var value = BaseRequest.GetOption(codec, "audiobitdepth");
- if (!string.IsNullOrEmpty(value)
- && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
- {
- return result;
- }
- }
-
- return null;
- }
-
- public int? GetRequestedAudioChannels(string codec)
- {
- if (BaseRequest.MaxAudioChannels.HasValue)
- {
- return BaseRequest.MaxAudioChannels;
- }
-
- if (BaseRequest.AudioChannels.HasValue)
- {
- return BaseRequest.AudioChannels;
- }
-
- if (!string.IsNullOrEmpty(codec))
- {
- var value = BaseRequest.GetOption(codec, "audiochannels");
- if (!string.IsNullOrEmpty(value)
- && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
- {
- return result;
- }
- }
-
- return null;
- }
-
public bool IsVideoRequest { get; set; }
public TranscodingJobType TranscodingType { get; set; }
- public EncodingJobInfo(TranscodingJobType jobType)
- {
- TranscodingType = jobType;
- RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
- PlayableStreamFileNames = Array.Empty<string>();
- SupportedAudioCodecs = Array.Empty<string>();
- SupportedVideoCodecs = Array.Empty<string>();
- SupportedSubtitleCodecs = Array.Empty<string>();
- }
+ public long? StartTimeTicks => BaseRequest.StartTimeTicks;
+
+ public bool CopyTimestamps => BaseRequest.CopyTimestamps;
public bool IsSegmentedLiveStream
=> TranscodingType != TranscodingJobType.Progressive && !RunTimeTicks.HasValue;
- public bool EnableBreakOnNonKeyFrames(string videoCodec)
- {
- if (TranscodingType != TranscodingJobType.Progressive)
- {
- if (IsSegmentedLiveStream)
- {
- return false;
- }
-
- return BaseRequest.BreakOnNonKeyFrames && EncodingHelper.IsCopyCodec(videoCodec);
- }
-
- return false;
- }
-
public int? TotalOutputBitrate => (OutputAudioBitrate ?? 0) + (OutputVideoBitrate ?? 0);
public int? OutputWidth
@@ -342,7 +169,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var size = new ImageDimensions(VideoStream.Width.Value, VideoStream.Height.Value);
- var newSize = DrawingUtils.Resize(size,
+ var newSize = DrawingUtils.Resize(
+ size,
BaseRequest.Width ?? 0,
BaseRequest.Height ?? 0,
BaseRequest.MaxWidth ?? 0,
@@ -368,7 +196,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var size = new ImageDimensions(VideoStream.Width.Value, VideoStream.Height.Value);
- var newSize = DrawingUtils.Resize(size,
+ var newSize = DrawingUtils.Resize(
+ size,
BaseRequest.Width ?? 0,
BaseRequest.Height ?? 0,
BaseRequest.MaxWidth ?? 0,
@@ -402,7 +231,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
// Don't exceed what the encoder supports
// Seeing issues of attempting to encode to 88200
- return Math.Min(44100, BaseRequest.AudioSampleRate.Value);
+ return BaseRequest.AudioSampleRate.Value;
}
return null;
@@ -427,7 +256,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
- /// Predicts the audio sample rate that will be in the output stream.
+ /// Gets the target video level.
/// </summary>
public double? TargetVideoLevel
{
@@ -450,7 +279,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
- /// Predicts the audio sample rate that will be in the output stream.
+ /// Gets the target video bit depth.
/// </summary>
public int? TargetVideoBitDepth
{
@@ -485,7 +314,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
- /// Predicts the audio sample rate that will be in the output stream.
+ /// Gets the target framerate.
/// </summary>
public float? TargetFramerate
{
@@ -517,7 +346,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
- /// Predicts the audio sample rate that will be in the output stream.
+ /// Gets the target packet length.
/// </summary>
public int? TargetPacketLength
{
@@ -533,7 +362,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
- /// Predicts the audio sample rate that will be in the output stream.
+ /// Gets the target video profile.
/// </summary>
public string TargetVideoProfile
{
@@ -586,6 +415,11 @@ namespace MediaBrowser.Controller.MediaEncoding
{
get
{
+ if (VideoStream == null)
+ {
+ return null;
+ }
+
if (EncodingHelper.IsCopyCodec(OutputVideoCodec))
{
return VideoStream?.Codec;
@@ -599,6 +433,11 @@ namespace MediaBrowser.Controller.MediaEncoding
{
get
{
+ if (AudioStream == null)
+ {
+ return null;
+ }
+
if (EncodingHelper.IsCopyCodec(OutputAudioCodec))
{
return AudioStream?.Codec;
@@ -668,6 +507,21 @@ namespace MediaBrowser.Controller.MediaEncoding
public int HlsListSize => 0;
+ public bool EnableBreakOnNonKeyFrames(string videoCodec)
+ {
+ if (TranscodingType != TranscodingJobType.Progressive)
+ {
+ if (IsSegmentedLiveStream)
+ {
+ return false;
+ }
+
+ return BaseRequest.BreakOnNonKeyFrames && EncodingHelper.IsCopyCodec(videoCodec);
+ }
+
+ return false;
+ }
+
private int? GetMediaStreamCount(MediaStreamType type, int limit)
{
var count = MediaSource.GetStreamCount(type);
@@ -680,30 +534,171 @@ namespace MediaBrowser.Controller.MediaEncoding
return count;
}
- public IProgress<double> Progress { get; set; }
+ public string GetMimeType(string outputPath, bool enableStreamDefault = true)
+ {
+ if (!string.IsNullOrEmpty(MimeType))
+ {
+ return MimeType;
+ }
+
+ return MimeTypes.GetMimeType(outputPath, enableStreamDefault);
+ }
+
+ public bool DeInterlace(string videoCodec, bool forceDeinterlaceIfSourceIsInterlaced)
+ {
+ var videoStream = VideoStream;
+ var isInputInterlaced = videoStream != null && videoStream.IsInterlaced;
+
+ if (!isInputInterlaced)
+ {
+ return false;
+ }
+
+ // Support general param
+ if (BaseRequest.DeInterlace)
+ {
+ return true;
+ }
+
+ if (!string.IsNullOrEmpty(videoCodec))
+ {
+ if (string.Equals(BaseRequest.GetOption(videoCodec, "deinterlace"), "true", StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ }
+
+ return forceDeinterlaceIfSourceIsInterlaced && isInputInterlaced;
+ }
+
+ public string[] GetRequestedProfiles(string codec)
+ {
+ if (!string.IsNullOrEmpty(BaseRequest.Profile))
+ {
+ return BaseRequest.Profile.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries);
+ }
+
+ if (!string.IsNullOrEmpty(codec))
+ {
+ var profile = BaseRequest.GetOption(codec, "profile");
+
+ if (!string.IsNullOrEmpty(profile))
+ {
+ return profile.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries);
+ }
+ }
+
+ return Array.Empty<string>();
+ }
+
+ public string GetRequestedLevel(string codec)
+ {
+ if (!string.IsNullOrEmpty(BaseRequest.Level))
+ {
+ return BaseRequest.Level;
+ }
+
+ if (!string.IsNullOrEmpty(codec))
+ {
+ return BaseRequest.GetOption(codec, "level");
+ }
+
+ return null;
+ }
+
+ public int? GetRequestedMaxRefFrames(string codec)
+ {
+ if (BaseRequest.MaxRefFrames.HasValue)
+ {
+ return BaseRequest.MaxRefFrames;
+ }
+
+ if (!string.IsNullOrEmpty(codec))
+ {
+ var value = BaseRequest.GetOption(codec, "maxrefframes");
+ if (!string.IsNullOrEmpty(value)
+ && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
+ {
+ return result;
+ }
+ }
+
+ return null;
+ }
+
+ public int? GetRequestedVideoBitDepth(string codec)
+ {
+ if (BaseRequest.MaxVideoBitDepth.HasValue)
+ {
+ return BaseRequest.MaxVideoBitDepth;
+ }
+
+ if (!string.IsNullOrEmpty(codec))
+ {
+ var value = BaseRequest.GetOption(codec, "videobitdepth");
+ if (!string.IsNullOrEmpty(value)
+ && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
+ {
+ return result;
+ }
+ }
+
+ return null;
+ }
+
+ public int? GetRequestedAudioBitDepth(string codec)
+ {
+ if (BaseRequest.MaxAudioBitDepth.HasValue)
+ {
+ return BaseRequest.MaxAudioBitDepth;
+ }
+
+ if (!string.IsNullOrEmpty(codec))
+ {
+ var value = BaseRequest.GetOption(codec, "audiobitdepth");
+ if (!string.IsNullOrEmpty(value)
+ && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
+ {
+ return result;
+ }
+ }
+
+ return null;
+ }
+
+ public int? GetRequestedAudioChannels(string codec)
+ {
+ if (!string.IsNullOrEmpty(codec))
+ {
+ var value = BaseRequest.GetOption(codec, "audiochannels");
+ if (!string.IsNullOrEmpty(value)
+ && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
+ {
+ return result;
+ }
+ }
+
+ if (BaseRequest.MaxAudioChannels.HasValue)
+ {
+ return BaseRequest.MaxAudioChannels;
+ }
+
+ if (BaseRequest.AudioChannels.HasValue)
+ {
+ return BaseRequest.AudioChannels;
+ }
+
+ if (BaseRequest.TranscodingMaxAudioChannels.HasValue)
+ {
+ return BaseRequest.TranscodingMaxAudioChannels;
+ }
+
+ return null;
+ }
public virtual void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
{
Progress.Report(percentComplete.Value);
}
}
-
- /// <summary>
- /// Enum TranscodingJobType.
- /// </summary>
- public enum TranscodingJobType
- {
- /// <summary>
- /// The progressive.
- /// </summary>
- Progressive,
- /// <summary>
- /// The HLS.
- /// </summary>
- Hls,
- /// <summary>
- /// The dash.
- /// </summary>
- Dash
- }
}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
deleted file mode 100644
index 4cbb63e46..000000000
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
+++ /dev/null
@@ -1,282 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Services;
-
-namespace MediaBrowser.Controller.MediaEncoding
-{
- public class EncodingJobOptions : BaseEncodingJobOptions
- {
- public string OutputDirectory { get; set; }
-
- public string ItemId { get; set; }
-
- public string TempDirectory { get; set; }
-
- public bool ReadInputAtNativeFramerate { get; set; }
-
- /// <summary>
- /// Gets a value indicating whether this instance has fixed resolution.
- /// </summary>
- /// <value><c>true</c> if this instance has fixed resolution; otherwise, <c>false</c>.</value>
- public bool HasFixedResolution => Width.HasValue || Height.HasValue;
-
- public DeviceProfile DeviceProfile { get; set; }
-
- public EncodingJobOptions(StreamInfo info, DeviceProfile deviceProfile)
- {
- Container = info.Container;
- StartTimeTicks = info.StartPositionTicks;
- MaxWidth = info.MaxWidth;
- MaxHeight = info.MaxHeight;
- MaxFramerate = info.MaxFramerate;
- Id = info.ItemId;
- MediaSourceId = info.MediaSourceId;
- AudioCodec = info.TargetAudioCodec.FirstOrDefault();
- MaxAudioChannels = info.GlobalMaxAudioChannels;
- AudioBitRate = info.AudioBitrate;
- AudioSampleRate = info.TargetAudioSampleRate;
- DeviceProfile = deviceProfile;
- VideoCodec = info.TargetVideoCodec.FirstOrDefault();
- VideoBitRate = info.VideoBitrate;
- AudioStreamIndex = info.AudioStreamIndex;
- SubtitleMethod = info.SubtitleDeliveryMethod;
- Context = info.Context;
- TranscodingMaxAudioChannels = info.TranscodingMaxAudioChannels;
-
- if (info.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External)
- {
- SubtitleStreamIndex = info.SubtitleStreamIndex;
- }
-
- StreamOptions = info.StreamOptions;
- }
- }
-
- // For now until api and media encoding layers are unified
- public class BaseEncodingJobOptions
- {
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public Guid Id { get; set; }
-
- [ApiMember(Name = "MediaSourceId", Description = "The media version id, if playing an alternate version", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string MediaSourceId { get; set; }
-
- [ApiMember(Name = "DeviceId", Description = "The device id of the client requesting. Used to stop encoding processes when needed.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string DeviceId { get; set; }
-
- [ApiMember(Name = "Container", Description = "Container", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string Container { get; set; }
-
- /// <summary>
- /// Gets or sets the audio codec.
- /// </summary>
- /// <value>The audio codec.</value>
- [ApiMember(Name = "AudioCodec", Description = "Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string AudioCodec { get; set; }
-
- [ApiMember(Name = "EnableAutoStreamCopy", Description = "Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool EnableAutoStreamCopy { get; set; }
-
- public bool AllowVideoStreamCopy { get; set; }
-
- public bool AllowAudioStreamCopy { get; set; }
-
- public bool BreakOnNonKeyFrames { get; set; }
-
- /// <summary>
- /// Gets or sets the audio sample rate.
- /// </summary>
- /// <value>The audio sample rate.</value>
- [ApiMember(Name = "AudioSampleRate", Description = "Optional. Specify a specific audio sample rate, e.g. 44100", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? AudioSampleRate { get; set; }
-
- public int? MaxAudioBitDepth { get; set; }
-
- /// <summary>
- /// Gets or sets the audio bit rate.
- /// </summary>
- /// <value>The audio bit rate.</value>
- [ApiMember(Name = "AudioBitRate", Description = "Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? AudioBitRate { get; set; }
-
- /// <summary>
- /// Gets or sets the audio channels.
- /// </summary>
- /// <value>The audio channels.</value>
- [ApiMember(Name = "AudioChannels", Description = "Optional. Specify a specific number of audio channels to encode to, e.g. 2", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? AudioChannels { get; set; }
-
- [ApiMember(Name = "MaxAudioChannels", Description = "Optional. Specify a maximum number of audio channels to encode to, e.g. 2", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? MaxAudioChannels { get; set; }
-
- [ApiMember(Name = "Static", Description = "Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool Static { get; set; }
-
- /// <summary>
- /// Gets or sets the profile.
- /// </summary>
- /// <value>The profile.</value>
- [ApiMember(Name = "Profile", Description = "Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string Profile { get; set; }
-
- /// <summary>
- /// Gets or sets the level.
- /// </summary>
- /// <value>The level.</value>
- [ApiMember(Name = "Level", Description = "Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string Level { get; set; }
-
- /// <summary>
- /// Gets or sets the framerate.
- /// </summary>
- /// <value>The framerate.</value>
- [ApiMember(Name = "Framerate", Description = "Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", IsRequired = false, DataType = "double", ParameterType = "query", Verb = "GET")]
- public float? Framerate { get; set; }
-
- [ApiMember(Name = "MaxFramerate", Description = "Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", IsRequired = false, DataType = "double", ParameterType = "query", Verb = "GET")]
- public float? MaxFramerate { get; set; }
-
- [ApiMember(Name = "CopyTimestamps", Description = "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool CopyTimestamps { get; set; }
-
- /// <summary>
- /// Gets or sets the start time ticks.
- /// </summary>
- /// <value>The start time ticks.</value>
- [ApiMember(Name = "StartTimeTicks", Description = "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public long? StartTimeTicks { get; set; }
-
- /// <summary>
- /// Gets or sets the width.
- /// </summary>
- /// <value>The width.</value>
- [ApiMember(Name = "Width", Description = "Optional. The fixed horizontal resolution of the encoded video.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? Width { get; set; }
-
- /// <summary>
- /// Gets or sets the height.
- /// </summary>
- /// <value>The height.</value>
- [ApiMember(Name = "Height", Description = "Optional. The fixed vertical resolution of the encoded video.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? Height { get; set; }
-
- /// <summary>
- /// Gets or sets the width of the max.
- /// </summary>
- /// <value>The width of the max.</value>
- [ApiMember(Name = "MaxWidth", Description = "Optional. The maximum horizontal resolution of the encoded video.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? MaxWidth { get; set; }
-
- /// <summary>
- /// Gets or sets the height of the max.
- /// </summary>
- /// <value>The height of the max.</value>
- [ApiMember(Name = "MaxHeight", Description = "Optional. The maximum vertical resolution of the encoded video.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? MaxHeight { get; set; }
-
- /// <summary>
- /// Gets or sets the video bit rate.
- /// </summary>
- /// <value>The video bit rate.</value>
- [ApiMember(Name = "VideoBitRate", Description = "Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? VideoBitRate { get; set; }
-
- /// <summary>
- /// Gets or sets the index of the subtitle stream.
- /// </summary>
- /// <value>The index of the subtitle stream.</value>
- [ApiMember(Name = "SubtitleStreamIndex", Description = "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? SubtitleStreamIndex { get; set; }
-
- [ApiMember(Name = "SubtitleMethod", Description = "Optional. Specify the subtitle delivery method.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public SubtitleDeliveryMethod SubtitleMethod { get; set; }
-
- [ApiMember(Name = "MaxRefFrames", Description = "Optional.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? MaxRefFrames { get; set; }
-
- [ApiMember(Name = "MaxVideoBitDepth", Description = "Optional.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? MaxVideoBitDepth { get; set; }
-
- public bool RequireAvc { get; set; }
-
- public bool DeInterlace { get; set; }
-
- public bool RequireNonAnamorphic { get; set; }
-
- public int? TranscodingMaxAudioChannels { get; set; }
-
- public int? CpuCoreLimit { get; set; }
-
- public string LiveStreamId { get; set; }
-
- public bool EnableMpegtsM2TsMode { get; set; }
-
- /// <summary>
- /// Gets or sets the video codec.
- /// </summary>
- /// <value>The video codec.</value>
- [ApiMember(Name = "VideoCodec", Description = "Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vpx, wmv.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string VideoCodec { get; set; }
-
- public string SubtitleCodec { get; set; }
-
- public string TranscodeReasons { get; set; }
-
- /// <summary>
- /// Gets or sets the index of the audio stream.
- /// </summary>
- /// <value>The index of the audio stream.</value>
- [ApiMember(Name = "AudioStreamIndex", Description = "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? AudioStreamIndex { get; set; }
-
- /// <summary>
- /// Gets or sets the index of the video stream.
- /// </summary>
- /// <value>The index of the video stream.</value>
- [ApiMember(Name = "VideoStreamIndex", Description = "Optional. The index of the video stream to use. If omitted the first video stream will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? VideoStreamIndex { get; set; }
-
- public EncodingContext Context { get; set; }
-
- public Dictionary<string, string> StreamOptions { get; set; }
-
- public string GetOption(string qualifier, string name)
- {
- var value = GetOption(qualifier + "-" + name);
-
- if (string.IsNullOrEmpty(value))
- {
- value = GetOption(name);
- }
-
- return value;
- }
-
- public string GetOption(string name)
- {
- if (StreamOptions.TryGetValue(name, out var value))
- {
- return value;
- }
-
- return null;
- }
-
- public BaseEncodingJobOptions()
- {
- EnableAutoStreamCopy = true;
- AllowVideoStreamCopy = true;
- AllowAudioStreamCopy = true;
- Context = EncodingContext.Streaming;
- StreamOptions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
- }
- }
-}
diff --git a/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs b/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs
index fbc827534..c38e7ec3b 100644
--- a/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs
+++ b/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System.IO;
diff --git a/MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs b/MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs
index 15a2580af..8ce40a58d 100644
--- a/MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs
+++ b/MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System.Collections.Generic;
@@ -14,6 +16,13 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Refreshes the chapter images.
/// </summary>
+ /// <param name="video">Video to use.</param>
+ /// <param name="directoryService">Directory service to use.</param>
+ /// <param name="chapters">Set of chapters to refresh.</param>
+ /// <param name="extractImages">Option to extract images.</param>
+ /// <param name="saveChapters">Option to save chapters.</param>
+ /// <param name="cancellationToken">CancellationToken to use for operation.</param>
+ /// <returns><c>true</c> if successful, <c>false</c> if not.</returns>
Task<bool> RefreshChapterImages(Video video, IDirectoryService directoryService, IReadOnlyList<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
index 17d6dc5d2..ff2456070 100644
--- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
+++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -5,8 +7,8 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.System;
@@ -18,7 +20,7 @@ namespace MediaBrowser.Controller.MediaEncoding
public interface IMediaEncoder : ITranscoderSupport
{
/// <summary>
- /// The location of the discovered FFmpeg tool.
+ /// Gets location of the discovered FFmpeg tool.
/// </summary>
FFmpegLocation EncoderLocation { get; }
@@ -50,6 +52,14 @@ namespace MediaBrowser.Controller.MediaEncoding
bool SupportsHwaccel(string hwaccel);
/// <summary>
+ /// Whether given filter is supported.
+ /// </summary>
+ /// <param name="filter">The filter.</param>
+ /// <param name="option">The option.</param>
+ /// <returns><c>true</c> if the filter is supported, <c>false</c> otherwise.</returns>
+ bool SupportsFilter(string filter, string option);
+
+ /// <summary>
/// Extracts the audio image.
/// </summary>
/// <param name="path">The path.</param>
@@ -61,17 +71,47 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Extracts the video image.
/// </summary>
- Task<string> ExtractVideoImage(string[] inputFiles, string container, MediaProtocol protocol, MediaStream videoStream, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken);
+ /// <param name="inputFile">Input file.</param>
+ /// <param name="container">Video container type.</param>
+ /// <param name="mediaSource">Media source information.</param>
+ /// <param name="videoStream">Media stream information.</param>
+ /// <param name="threedFormat">Video 3D format.</param>
+ /// <param name="offset">Time offset.</param>
+ /// <param name="cancellationToken">CancellationToken to use for operation.</param>
+ /// <returns>Location of video image.</returns>
+ Task<string> ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream videoStream, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken);
- Task<string> ExtractVideoImage(string[] inputFiles, string container, MediaProtocol protocol, MediaStream imageStream, int? imageStreamIndex, CancellationToken cancellationToken);
+ /// <summary>
+ /// Extracts the video image.
+ /// </summary>
+ /// <param name="inputFile">Input file.</param>
+ /// <param name="container">Video container type.</param>
+ /// <param name="mediaSource">Media source information.</param>
+ /// <param name="imageStream">Media stream information.</param>
+ /// <param name="imageStreamIndex">Index of the stream to extract from.</param>
+ /// <param name="cancellationToken">CancellationToken to use for operation.</param>
+ /// <returns>Location of video image.</returns>
+ Task<string> ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, CancellationToken cancellationToken);
/// <summary>
/// Extracts the video images on interval.
/// </summary>
- Task ExtractVideoImagesOnInterval(string[] inputFiles,
+ /// <param name="inputFile">Input file.</param>
+ /// <param name="container">Video container type.</param>
+ /// <param name="videoStream">Media stream information.</param>
+ /// <param name="mediaSource">Media source information.</param>
+ /// <param name="threedFormat">Video 3D format.</param>
+ /// <param name="interval">Time interval.</param>
+ /// <param name="targetDirectory">Directory to write images.</param>
+ /// <param name="filenamePrefix">Filename prefix to use.</param>
+ /// <param name="maxWidth">Maximum width of image.</param>
+ /// <param name="cancellationToken">CancellationToken to use for operation.</param>
+ /// <returns>A task.</returns>
+ Task ExtractVideoImagesOnInterval(
+ string inputFile,
string container,
MediaStream videoStream,
- MediaProtocol protocol,
+ MediaSourceInfo mediaSource,
Video3DFormat? threedFormat,
TimeSpan interval,
string targetDirectory,
@@ -90,10 +130,10 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Gets the input argument.
/// </summary>
- /// <param name="inputFiles">The input files.</param>
- /// <param name="protocol">The protocol.</param>
+ /// <param name="inputFile">The input file.</param>
+ /// <param name="mediaSource">The mediaSource.</param>
/// <returns>System.String.</returns>
- string GetInputArgument(IReadOnlyList<string> inputFiles, MediaProtocol protocol);
+ string GetInputArgument(string inputFile, MediaSourceInfo mediaSource);
/// <summary>
/// Gets the time parameter.
@@ -111,10 +151,24 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <returns>System.String.</returns>
string EscapeSubtitleFilterPath(string path);
+ /// <summary>
+ /// Sets the path to find FFmpeg.
+ /// </summary>
void SetFFmpegPath();
+ /// <summary>
+ /// Updates the encoder path.
+ /// </summary>
+ /// <param name="path">The path.</param>
+ /// <param name="pathType">The type of path.</param>
void UpdateEncoderPath(string path, string pathType);
- IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, IIsoMount isoMount, uint? titleNumber);
+ /// <summary>
+ /// Gets the primary playlist of .vob files.
+ /// </summary>
+ /// <param name="path">The to the .vob files.</param>
+ /// <param name="titleNumber">The title number to start with.</param>
+ /// <returns>A playlist.</returns>
+ IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, uint? titleNumber);
}
}
diff --git a/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs b/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs
index 6ebf7f159..4483cf708 100644
--- a/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs
+++ b/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System.IO;
@@ -13,6 +15,14 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Gets the subtitles.
/// </summary>
+ /// <param name="item">Item to use.</param>
+ /// <param name="mediaSourceId">Media source.</param>
+ /// <param name="subtitleStreamIndex">Subtitle stream to use.</param>
+ /// <param name="outputFormat">Output format to use.</param>
+ /// <param name="startTimeTicks">Start time.</param>
+ /// <param name="endTimeTicks">End time.</param>
+ /// <param name="preserveOriginalTimestamps">Option to preserve original timestamps.</param>
+ /// <param name="cancellationToken">The cancellation token for the operation.</param>
/// <returns>Task{Stream}.</returns>
Task<Stream> GetSubtitles(
BaseItem item,
diff --git a/MediaBrowser.Controller/MediaEncoding/ImageEncodingOptions.cs b/MediaBrowser.Controller/MediaEncoding/ImageEncodingOptions.cs
index e7b4c8c15..044ba6d33 100644
--- a/MediaBrowser.Controller/MediaEncoding/ImageEncodingOptions.cs
+++ b/MediaBrowser.Controller/MediaEncoding/ImageEncodingOptions.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
namespace MediaBrowser.Controller.MediaEncoding
diff --git a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
index ac520c5c4..b23c95112 100644
--- a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
+++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
@@ -1,10 +1,12 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
using System.Globalization;
using System.IO;
-using System.Linq;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
@@ -93,7 +95,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (part.StartsWith("fps=", StringComparison.OrdinalIgnoreCase))
{
- var rate = part.Split(new[] { '=' }, 2)[^1];
+ var rate = part.Split('=', 2)[^1];
if (float.TryParse(rate, NumberStyles.Any, _usCulture, out var val))
{
@@ -103,7 +105,7 @@ namespace MediaBrowser.Controller.MediaEncoding
else if (state.RunTimeTicks.HasValue &&
part.StartsWith("time=", StringComparison.OrdinalIgnoreCase))
{
- var time = part.Split(new[] { '=' }, 2).Last();
+ var time = part.Split('=', 2)[^1];
if (TimeSpan.TryParse(time, _usCulture, out var val))
{
@@ -116,7 +118,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (part.StartsWith("size=", StringComparison.OrdinalIgnoreCase))
{
- var size = part.Split(new[] { '=' }, 2).Last();
+ var size = part.Split('=', 2)[^1];
int? scale = null;
if (size.IndexOf("kb", StringComparison.OrdinalIgnoreCase) != -1)
@@ -135,7 +137,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (part.StartsWith("bitrate=", StringComparison.OrdinalIgnoreCase))
{
- var rate = part.Split(new[] { '=' }, 2).Last();
+ var rate = part.Split('=', 2)[^1];
int? scale = null;
if (rate.IndexOf("kbits/s", StringComparison.OrdinalIgnoreCase) != -1)
diff --git a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs
index ce53c23ad..841e7b287 100644
--- a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs
+++ b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs
@@ -1,11 +1,5 @@
#pragma warning disable CS1591
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using MediaBrowser.Model.IO;
-
namespace MediaBrowser.Controller.MediaEncoding
{
/// <summary>
@@ -13,38 +7,5 @@ namespace MediaBrowser.Controller.MediaEncoding
/// </summary>
public static class MediaEncoderHelpers
{
- /// <summary>
- /// Gets the input argument.
- /// </summary>
- /// <param name="fileSystem">The file system.</param>
- /// <param name="videoPath">The video path.</param>
- /// <param name="isoMount">The iso mount.</param>
- /// <param name="playableStreamFileNames">The playable stream file names.</param>
- /// <returns>string[].</returns>
- public static string[] GetInputArgument(IFileSystem fileSystem, string videoPath, IIsoMount isoMount, IReadOnlyCollection<string> playableStreamFileNames)
- {
- if (playableStreamFileNames.Count > 0)
- {
- if (isoMount == null)
- {
- return GetPlayableStreamFiles(fileSystem, videoPath, playableStreamFileNames);
- }
-
- return GetPlayableStreamFiles(fileSystem, isoMount.MountedPath, playableStreamFileNames);
- }
-
- return new[] { videoPath };
- }
-
- private static string[] GetPlayableStreamFiles(IFileSystem fileSystem, string rootPath, IEnumerable<string> filenames)
- {
- var allFiles = fileSystem
- .GetFilePaths(rootPath, true)
- .ToArray();
-
- return filenames.Select(name => allFiles.FirstOrDefault(f => string.Equals(Path.GetFileName(f), name, StringComparison.OrdinalIgnoreCase)))
- .Where(f => !string.IsNullOrEmpty(f))
- .ToArray();
- }
}
}
diff --git a/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs b/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs
index 59729de49..1dd8bcf31 100644
--- a/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs
+++ b/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs
@@ -1,9 +1,9 @@
+#nullable disable
+
#pragma warning disable CS1591
-using System;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.IO;
namespace MediaBrowser.Controller.MediaEncoding
{
@@ -14,14 +14,5 @@ namespace MediaBrowser.Controller.MediaEncoding
public bool ExtractChapters { get; set; }
public DlnaProfileType MediaType { get; set; }
-
- public IIsoMount MountedIso { get; set; }
-
- public string[] PlayableStreamFileNames { get; set; }
-
- public MediaInfoRequest()
- {
- PlayableStreamFileNames = Array.Empty<string>();
- }
}
}
diff --git a/MediaBrowser.Controller/MediaEncoding/TranscodingJobType.cs b/MediaBrowser.Controller/MediaEncoding/TranscodingJobType.cs
new file mode 100644
index 000000000..66b628371
--- /dev/null
+++ b/MediaBrowser.Controller/MediaEncoding/TranscodingJobType.cs
@@ -0,0 +1,23 @@
+namespace MediaBrowser.Controller.MediaEncoding
+{
+ /// <summary>
+ /// Enum TranscodingJobType.
+ /// </summary>
+ public enum TranscodingJobType
+ {
+ /// <summary>
+ /// The progressive.
+ /// </summary>
+ Progressive,
+
+ /// <summary>
+ /// The HLS.
+ /// </summary>
+ Hls,
+
+ /// <summary>
+ /// The dash.
+ /// </summary>
+ Dash
+ }
+} \ No newline at end of file