aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller
diff options
context:
space:
mode:
authorJPVenson <github@jpb.email>2024-10-08 09:34:34 +0000
committerJPVenson <github@jpb.email>2024-10-08 09:34:34 +0000
commitd3a3d9fce3b891eb0be274a0cdc45a989e557652 (patch)
treebd232ef477c259f1fafa204016f6efd4dcb8691f /MediaBrowser.Controller
parentee1bdf4e222125ed7382165fd7e09599ca4bd4aa (diff)
parentaaf20592bb0bbdf4f0f0d99fed091758e68ae850 (diff)
Merge remote-tracking branch 'jellyfinorigin/master' into feature/EFUserData
Diffstat (limited to 'MediaBrowser.Controller')
-rw-r--r--MediaBrowser.Controller/Authentication/AuthenticationResult.cs33
-rw-r--r--MediaBrowser.Controller/Devices/IDeviceManager.cs150
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs26
-rw-r--r--MediaBrowser.Controller/Entities/MediaSourceWidthComparator.cs56
-rw-r--r--MediaBrowser.Controller/Entities/TV/Episode.cs5
-rw-r--r--MediaBrowser.Controller/Entities/UserViewBuilder.cs2
-rw-r--r--MediaBrowser.Controller/Events/Authentication/AuthenticationResultEventArgs.cs3
-rw-r--r--MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs18
-rw-r--r--MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs2
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs1181
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs2
-rw-r--r--MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs9
-rw-r--r--MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs17
-rw-r--r--MediaBrowser.Controller/MediaSegements/IMediaSegmentProvider.cs36
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SessionsMessage.cs5
-rw-r--r--MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs7
-rw-r--r--MediaBrowser.Controller/Session/ISessionManager.cs12
-rw-r--r--MediaBrowser.Controller/Session/SessionInfo.cs120
-rw-r--r--MediaBrowser.Controller/Trickplay/ITrickplayManager.cs36
19 files changed, 1121 insertions, 599 deletions
diff --git a/MediaBrowser.Controller/Authentication/AuthenticationResult.cs b/MediaBrowser.Controller/Authentication/AuthenticationResult.cs
index 635e4eb3d..daf4d9631 100644
--- a/MediaBrowser.Controller/Authentication/AuthenticationResult.cs
+++ b/MediaBrowser.Controller/Authentication/AuthenticationResult.cs
@@ -1,20 +1,31 @@
#nullable disable
-#pragma warning disable CS1591
-
-using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Dto;
-namespace MediaBrowser.Controller.Authentication
+namespace MediaBrowser.Controller.Authentication;
+
+/// <summary>
+/// A class representing an authentication result.
+/// </summary>
+public class AuthenticationResult
{
- public class AuthenticationResult
- {
- public UserDto User { get; set; }
+ /// <summary>
+ /// Gets or sets the user.
+ /// </summary>
+ public UserDto User { get; set; }
- public SessionInfo SessionInfo { get; set; }
+ /// <summary>
+ /// Gets or sets the session info.
+ /// </summary>
+ public SessionInfoDto SessionInfo { get; set; }
- public string AccessToken { get; set; }
+ /// <summary>
+ /// Gets or sets the access token.
+ /// </summary>
+ public string AccessToken { get; set; }
- public string ServerId { get; set; }
- }
+ /// <summary>
+ /// Gets or sets the server id.
+ /// </summary>
+ public string ServerId { get; set; }
}
diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs
index 5566421cb..cade53d99 100644
--- a/MediaBrowser.Controller/Devices/IDeviceManager.cs
+++ b/MediaBrowser.Controller/Devices/IDeviceManager.cs
@@ -1,81 +1,117 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
using System;
using System.Threading.Tasks;
+using Jellyfin.Data.Dtos;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Entities.Security;
using Jellyfin.Data.Events;
using Jellyfin.Data.Queries;
using MediaBrowser.Model.Devices;
+using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Session;
-namespace MediaBrowser.Controller.Devices
+namespace MediaBrowser.Controller.Devices;
+
+/// <summary>
+/// Device manager interface.
+/// </summary>
+public interface IDeviceManager
{
- public interface IDeviceManager
- {
- event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated;
+ /// <summary>
+ /// Event handler for updated device options.
+ /// </summary>
+ event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated;
+
+ /// <summary>
+ /// Creates a new device.
+ /// </summary>
+ /// <param name="device">The device to create.</param>
+ /// <returns>A <see cref="Task{Device}"/> representing the creation of the device.</returns>
+ Task<Device> CreateDevice(Device device);
- /// <summary>
- /// Creates a new device.
- /// </summary>
- /// <param name="device">The device to create.</param>
- /// <returns>A <see cref="Task{Device}"/> representing the creation of the device.</returns>
- Task<Device> CreateDevice(Device device);
+ /// <summary>
+ /// Saves the capabilities.
+ /// </summary>
+ /// <param name="deviceId">The device id.</param>
+ /// <param name="capabilities">The capabilities.</param>
+ void SaveCapabilities(string deviceId, ClientCapabilities capabilities);
- /// <summary>
- /// Saves the capabilities.
- /// </summary>
- /// <param name="deviceId">The device id.</param>
- /// <param name="capabilities">The capabilities.</param>
- void SaveCapabilities(string deviceId, ClientCapabilities capabilities);
+ /// <summary>
+ /// Gets the capabilities.
+ /// </summary>
+ /// <param name="deviceId">The device id.</param>
+ /// <returns>ClientCapabilities.</returns>
+ ClientCapabilities GetCapabilities(string? deviceId);
- /// <summary>
- /// Gets the capabilities.
- /// </summary>
- /// <param name="deviceId">The device id.</param>
- /// <returns>ClientCapabilities.</returns>
- ClientCapabilities GetCapabilities(string deviceId);
+ /// <summary>
+ /// Gets the device information.
+ /// </summary>
+ /// <param name="id">The identifier.</param>
+ /// <returns>DeviceInfoDto.</returns>
+ DeviceInfoDto? GetDevice(string id);
- /// <summary>
- /// Gets the device information.
- /// </summary>
- /// <param name="id">The identifier.</param>
- /// <returns>DeviceInfo.</returns>
- DeviceInfo GetDevice(string id);
+ /// <summary>
+ /// Gets devices based on the provided query.
+ /// </summary>
+ /// <param name="query">The device query.</param>
+ /// <returns>A <see cref="Task{QueryResult}"/> representing the retrieval of the devices.</returns>
+ QueryResult<Device> GetDevices(DeviceQuery query);
- /// <summary>
- /// Gets devices based on the provided query.
- /// </summary>
- /// <param name="query">The device query.</param>
- /// <returns>A <see cref="Task{QueryResult}"/> representing the retrieval of the devices.</returns>
- QueryResult<Device> GetDevices(DeviceQuery query);
+ /// <summary>
+ /// Gets device infromation based on the provided query.
+ /// </summary>
+ /// <param name="query">The device query.</param>
+ /// <returns>A <see cref="Task{QueryResult}"/> representing the retrieval of the device information.</returns>
+ QueryResult<DeviceInfo> GetDeviceInfos(DeviceQuery query);
- QueryResult<DeviceInfo> GetDeviceInfos(DeviceQuery query);
+ /// <summary>
+ /// Gets the device information.
+ /// </summary>
+ /// <param name="userId">The user's id, or <c>null</c>.</param>
+ /// <returns>IEnumerable&lt;DeviceInfoDto&gt;.</returns>
+ QueryResult<DeviceInfoDto> GetDevicesForUser(Guid? userId);
- /// <summary>
- /// Gets the devices.
- /// </summary>
- /// <param name="userId">The user's id, or <c>null</c>.</param>
- /// <returns>IEnumerable&lt;DeviceInfo&gt;.</returns>
- QueryResult<DeviceInfo> GetDevicesForUser(Guid? userId);
+ /// <summary>
+ /// Deletes a device.
+ /// </summary>
+ /// <param name="device">The device.</param>
+ /// <returns>A <see cref="Task"/> representing the deletion of the device.</returns>
+ Task DeleteDevice(Device device);
- Task DeleteDevice(Device device);
+ /// <summary>
+ /// Updates a device.
+ /// </summary>
+ /// <param name="device">The device.</param>
+ /// <returns>A <see cref="Task"/> representing the update of the device.</returns>
+ Task UpdateDevice(Device device);
- Task UpdateDevice(Device device);
+ /// <summary>
+ /// Determines whether this instance [can access device] the specified user identifier.
+ /// </summary>
+ /// <param name="user">The user to test.</param>
+ /// <param name="deviceId">The device id to test.</param>
+ /// <returns>Whether the user can access the device.</returns>
+ bool CanAccessDevice(User user, string deviceId);
- /// <summary>
- /// Determines whether this instance [can access device] the specified user identifier.
- /// </summary>
- /// <param name="user">The user to test.</param>
- /// <param name="deviceId">The device id to test.</param>
- /// <returns>Whether the user can access the device.</returns>
- bool CanAccessDevice(User user, string deviceId);
+ /// <summary>
+ /// Updates the options of a device.
+ /// </summary>
+ /// <param name="deviceId">The device id.</param>
+ /// <param name="deviceName">The device name.</param>
+ /// <returns>A <see cref="Task"/> representing the update of the device options.</returns>
+ Task UpdateDeviceOptions(string deviceId, string? deviceName);
- Task UpdateDeviceOptions(string deviceId, string deviceName);
+ /// <summary>
+ /// Gets the options of a device.
+ /// </summary>
+ /// <param name="deviceId">The device id.</param>
+ /// <returns><see cref="DeviceOptions"/> of the device.</returns>
+ DeviceOptionsDto? GetDeviceOptions(string deviceId);
- DeviceOptions GetDeviceOptions(string deviceId);
- }
+ /// <summary>
+ /// Gets the dto for client capabilites.
+ /// </summary>
+ /// <param name="capabilities">The client capabilities.</param>
+ /// <returns><see cref="ClientCapabilitiesDto"/> of the device.</returns>
+ ClientCapabilitiesDto ToClientCapabilitiesDto(ClientCapabilities capabilities);
}
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 125f8f225..eb605f6c8 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -1087,12 +1087,7 @@ namespace MediaBrowser.Controller.Entities
return 1;
}).ThenBy(i => i.Video3DFormat.HasValue ? 1 : 0)
- .ThenByDescending(i =>
- {
- var stream = i.VideoStream;
-
- return stream is null || stream.Width is null ? 0 : stream.Width.Value;
- })
+ .ThenByDescending(i => i, new MediaSourceWidthComparator())
.ToList();
}
@@ -1185,28 +1180,29 @@ namespace MediaBrowser.Controller.Entities
return info;
}
- private string GetMediaSourceName(BaseItem item)
+ internal string GetMediaSourceName(BaseItem item)
{
var terms = new List<string>();
var path = item.Path;
if (item.IsFileProtocol && !string.IsNullOrEmpty(path))
{
+ var displayName = System.IO.Path.GetFileNameWithoutExtension(path);
if (HasLocalAlternateVersions)
{
- var displayName = System.IO.Path.GetFileNameWithoutExtension(path)
- .Replace(System.IO.Path.GetFileName(ContainingFolderPath), string.Empty, StringComparison.OrdinalIgnoreCase)
- .TrimStart(new char[] { ' ', '-' });
-
- if (!string.IsNullOrEmpty(displayName))
+ var containingFolderName = System.IO.Path.GetFileName(ContainingFolderPath);
+ if (displayName.Length > containingFolderName.Length && displayName.StartsWith(containingFolderName, StringComparison.OrdinalIgnoreCase))
{
- terms.Add(displayName);
+ var name = displayName.AsSpan(containingFolderName.Length).TrimStart([' ', '-']);
+ if (!name.IsWhiteSpace())
+ {
+ terms.Add(name.ToString());
+ }
}
}
if (terms.Count == 0)
{
- var displayName = System.IO.Path.GetFileNameWithoutExtension(path);
terms.Add(displayName);
}
}
@@ -1612,7 +1608,7 @@ namespace MediaBrowser.Controller.Entities
}
var parent = GetParents().FirstOrDefault() ?? this;
- if (parent is UserRootFolder or AggregateFolder)
+ if (parent is UserRootFolder or AggregateFolder or UserView)
{
return true;
}
diff --git a/MediaBrowser.Controller/Entities/MediaSourceWidthComparator.cs b/MediaBrowser.Controller/Entities/MediaSourceWidthComparator.cs
new file mode 100644
index 000000000..0224577a4
--- /dev/null
+++ b/MediaBrowser.Controller/Entities/MediaSourceWidthComparator.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.Intrinsics.X86;
+using MediaBrowser.Model.Dto;
+
+namespace MediaBrowser.Controller.Entities;
+
+/// <summary>
+/// Compare MediaSource of the same file by Video width <see cref="IComparer{T}" />.
+/// </summary>
+public class MediaSourceWidthComparator : IComparer<MediaSourceInfo>
+{
+ /// <inheritdoc />
+ public int Compare(MediaSourceInfo? x, MediaSourceInfo? y)
+ {
+ if (x is null && y is null)
+ {
+ return 0;
+ }
+
+ if (x is null)
+ {
+ return -1;
+ }
+
+ if (y is null)
+ {
+ return 1;
+ }
+
+ if (string.Equals(x.Path, y.Path, StringComparison.OrdinalIgnoreCase))
+ {
+ if (x.VideoStream is null && y.VideoStream is null)
+ {
+ return 0;
+ }
+
+ if (x.VideoStream is null)
+ {
+ return -1;
+ }
+
+ if (y.VideoStream is null)
+ {
+ return 1;
+ }
+
+ var xWidth = x.VideoStream.Width ?? 0;
+ var yWidth = y.VideoStream.Width ?? 0;
+
+ return xWidth - yWidth;
+ }
+
+ return 0;
+ }
+}
diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs
index 5c54f014c..46bad3f3b 100644
--- a/MediaBrowser.Controller/Entities/TV/Episode.cs
+++ b/MediaBrowser.Controller/Entities/TV/Episode.cs
@@ -180,10 +180,7 @@ namespace MediaBrowser.Controller.Entities.TV
}
public string FindSeriesPresentationUniqueKey()
- {
- var series = Series;
- return series is null ? null : series.PresentationUniqueKey;
- }
+ => Series?.PresentationUniqueKey;
public string FindSeasonName()
{
diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
index 2fda7ee6f..420349f35 100644
--- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs
+++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
@@ -430,8 +430,6 @@ namespace MediaBrowser.Controller.Entities
InternalItemsQuery query,
ILibraryManager libraryManager)
{
- var user = query.User;
-
// This must be the last filter
if (!query.AdjacentTo.IsNullOrEmpty())
{
diff --git a/MediaBrowser.Controller/Events/Authentication/AuthenticationResultEventArgs.cs b/MediaBrowser.Controller/Events/Authentication/AuthenticationResultEventArgs.cs
index 357ef9406..1542c58b3 100644
--- a/MediaBrowser.Controller/Events/Authentication/AuthenticationResultEventArgs.cs
+++ b/MediaBrowser.Controller/Events/Authentication/AuthenticationResultEventArgs.cs
@@ -1,6 +1,5 @@
using System;
using MediaBrowser.Controller.Authentication;
-using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Dto;
namespace MediaBrowser.Controller.Events.Authentication;
@@ -29,7 +28,7 @@ public class AuthenticationResultEventArgs : EventArgs
/// <summary>
/// Gets or sets the session information.
/// </summary>
- public SessionInfo? SessionInfo { get; set; }
+ public SessionInfoDto? SessionInfo { get; set; }
/// <summary>
/// Gets or sets the server id.
diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs
index 0aaf4fcd9..f8049cd48 100644
--- a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs
+++ b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs
@@ -40,6 +40,11 @@ namespace MediaBrowser.Controller.Extensions
public const string FfmpegAnalyzeDurationKey = "FFmpeg:analyzeduration";
/// <summary>
+ /// The key for the FFmpeg image extraction performance tradeoff option.
+ /// </summary>
+ public const string FfmpegImgExtractPerfTradeoffKey = "FFmpeg:imgExtractPerfTradeoff";
+
+ /// <summary>
/// The key for the FFmpeg path option.
/// </summary>
public const string FfmpegPathKey = "ffmpeg";
@@ -70,6 +75,11 @@ namespace MediaBrowser.Controller.Extensions
public const string SqliteCacheSizeKey = "sqlite:cacheSize";
/// <summary>
+ /// The key for a setting that indicates whether the application should detect network status change.
+ /// </summary>
+ public const string DetectNetworkChangeKey = "DetectNetworkChange";
+
+ /// <summary>
/// Gets a value indicating whether the application should host static web content from the <see cref="IConfiguration"/>.
/// </summary>
/// <param name="configuration">The configuration to retrieve the value from.</param>
@@ -103,6 +113,14 @@ namespace MediaBrowser.Controller.Extensions
=> configuration.GetValue<bool>(FfmpegSkipValidationKey);
/// <summary>
+ /// Gets a value indicating whether the server should trade off for performance during FFmpeg image extraction.
+ /// </summary>
+ /// <param name="configuration">The configuration to read the setting from.</param>
+ /// <returns><c>true</c> if the server should trade off for performance during FFmpeg image extraction, otherwise <c>false</c>.</returns>
+ public static bool GetFFmpegImgExtractPerfTradeoff(this IConfiguration configuration)
+ => configuration.GetValue<bool>(FfmpegImgExtractPerfTradeoffKey);
+
+ /// <summary>
/// Gets a value indicating whether playlists should allow duplicate entries from the <see cref="IConfiguration"/>.
/// </summary>
/// <param name="configuration">The configuration to read the setting from.</param>
diff --git a/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs
index f77186e25..20f51ddb7 100644
--- a/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs
+++ b/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs
@@ -193,6 +193,8 @@ namespace MediaBrowser.Controller.MediaEncoding
public bool EnableAudioVbrEncoding { get; set; }
+ public bool AlwaysBurnInSubtitleWhenTranscoding { get; set; }
+
public string GetOption(string qualifier, string name)
{
var value = GetOption(qualifier + "-" + name);
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 24cd141dc..c120e08fa 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -37,6 +37,8 @@ namespace MediaBrowser.Controller.MediaEncoding
/// </summary>
public const string ValidationRegex = @"^[a-zA-Z0-9\-\._,|]{0,40}$";
+ private const string _defaultMjpegEncoder = "mjpeg";
+
private const string QsvAlias = "qs";
private const string VaapiAlias = "va";
private const string D3d11vaAlias = "dx11";
@@ -69,11 +71,14 @@ namespace MediaBrowser.Controller.MediaEncoding
private readonly Version _minFFmpegAdvancedTonemapMode = new Version(7, 0, 1);
private readonly Version _minFFmpegAlteredVaVkInterop = new Version(7, 0, 1);
private readonly Version _minFFmpegQsvVppTonemapOption = new Version(7, 0, 1);
+ private readonly Version _minFFmpegQsvVppOutRangeOption = new Version(7, 0, 1);
+ private readonly Version _minFFmpegVaapiDeviceVendorId = new Version(7, 0, 1);
+ private readonly Version _minFFmpegQsvVppScaleModeOption = new Version(6, 0);
private static readonly Regex _validationRegex = new(ValidationRegex, RegexOptions.Compiled);
- private static readonly string[] _videoProfilesH264 = new[]
- {
+ private static readonly string[] _videoProfilesH264 =
+ [
"ConstrainedBaseline",
"Baseline",
"Extended",
@@ -82,20 +87,20 @@ namespace MediaBrowser.Controller.MediaEncoding
"ProgressiveHigh",
"ConstrainedHigh",
"High10"
- };
+ ];
- private static readonly string[] _videoProfilesH265 = new[]
- {
+ private static readonly string[] _videoProfilesH265 =
+ [
"Main",
"Main10"
- };
+ ];
- private static readonly string[] _videoProfilesAv1 = new[]
- {
+ private static readonly string[] _videoProfilesAv1 =
+ [
"Main",
"High",
"Professional",
- };
+ ];
private static readonly HashSet<string> _mp4ContainerNames = new(StringComparer.OrdinalIgnoreCase)
{
@@ -107,8 +112,8 @@ namespace MediaBrowser.Controller.MediaEncoding
"m4v",
};
- private static readonly string[] _legacyTonemapModes = new[] { "max", "rgb" };
- private static readonly string[] _advancedTonemapModes = new[] { "lum", "itp" };
+ private static readonly TonemappingMode[] _legacyTonemapModes = [TonemappingMode.max, TonemappingMode.rgb];
+ private static readonly TonemappingMode[] _advancedTonemapModes = [TonemappingMode.lum, TonemappingMode.itp];
// Set max transcoding channels for encoders that can't handle more than a set amount of channels
// AAC, FLAC, ALAC, libopus, libvorbis encoders all support at least 8 channels
@@ -123,23 +128,23 @@ namespace MediaBrowser.Controller.MediaEncoding
{ "truehd", 6 },
};
- private static readonly string _defaultMjpegEncoder = "mjpeg";
- private static readonly Dictionary<string, string> _mjpegCodecMap = new(StringComparer.OrdinalIgnoreCase)
+ private static readonly Dictionary<HardwareAccelerationType, string> _mjpegCodecMap = new()
{
- { "vaapi", _defaultMjpegEncoder + "_vaapi" },
- { "qsv", _defaultMjpegEncoder + "_qsv" },
- { "videotoolbox", _defaultMjpegEncoder + "_videotoolbox" }
+ { HardwareAccelerationType.vaapi, _defaultMjpegEncoder + "_vaapi" },
+ { HardwareAccelerationType.qsv, _defaultMjpegEncoder + "_qsv" },
+ { HardwareAccelerationType.videotoolbox, _defaultMjpegEncoder + "_videotoolbox" },
+ { HardwareAccelerationType.rkmpp, _defaultMjpegEncoder + "_rkmpp" }
};
- public static readonly string[] LosslessAudioCodecs = new string[]
- {
+ public static readonly string[] LosslessAudioCodecs =
+ [
"alac",
"ape",
"flac",
"mlp",
"truehd",
"wavpack"
- };
+ ];
public EncodingHelper(
IApplicationPaths appPaths,
@@ -176,18 +181,18 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var hwType = encodingOptions.HardwareAccelerationType;
- var codecMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
+ var codecMap = new Dictionary<HardwareAccelerationType, string>()
{
- { "amf", hwEncoder + "_amf" },
- { "nvenc", hwEncoder + "_nvenc" },
- { "qsv", hwEncoder + "_qsv" },
- { "vaapi", hwEncoder + "_vaapi" },
- { "videotoolbox", hwEncoder + "_videotoolbox" },
- { "v4l2m2m", hwEncoder + "_v4l2m2m" },
- { "rkmpp", hwEncoder + "_rkmpp" },
+ { HardwareAccelerationType.amf, hwEncoder + "_amf" },
+ { HardwareAccelerationType.nvenc, hwEncoder + "_nvenc" },
+ { HardwareAccelerationType.qsv, hwEncoder + "_qsv" },
+ { HardwareAccelerationType.vaapi, hwEncoder + "_vaapi" },
+ { HardwareAccelerationType.videotoolbox, hwEncoder + "_videotoolbox" },
+ { HardwareAccelerationType.v4l2m2m, hwEncoder + "_v4l2m2m" },
+ { HardwareAccelerationType.rkmpp, hwEncoder + "_rkmpp" },
};
- if (!string.IsNullOrEmpty(hwType)
+ if (hwType != HardwareAccelerationType.none
&& encodingOptions.EnableHardwareEncoding
&& codecMap.TryGetValue(hwType, out var preferredEncoder)
&& _mediaEncoder.SupportsEncoder(preferredEncoder))
@@ -205,7 +210,15 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var hwType = encodingOptions.HardwareAccelerationType;
- if (!string.IsNullOrEmpty(hwType)
+ // Only Intel has VA-API MJPEG encoder
+ if (hwType == HardwareAccelerationType.vaapi
+ && !(_mediaEncoder.IsVaapiDeviceInteliHD
+ || _mediaEncoder.IsVaapiDeviceInteli965))
+ {
+ return _defaultMjpegEncoder;
+ }
+
+ if (hwType != HardwareAccelerationType.none
&& encodingOptions.EnableHardwareEncoding
&& _mjpegCodecMap.TryGetValue(hwType, out var preferredEncoder)
&& _mediaEncoder.SupportsEncoder(preferredEncoder))
@@ -297,7 +310,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (state.VideoStream is null
|| !options.EnableTonemapping
- || GetVideoColorBitDepth(state) != 10
+ || GetVideoColorBitDepth(state) < 10
|| !_mediaEncoder.SupportsFilter("tonemapx"))
{
return false;
@@ -310,13 +323,12 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (state.VideoStream is null
|| !options.EnableTonemapping
- || GetVideoColorBitDepth(state) != 10)
+ || GetVideoColorBitDepth(state) < 10)
{
return false;
}
- if (string.Equals(state.VideoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
- && state.VideoStream.VideoRange == VideoRange.HDR
+ if (state.VideoStream.VideoRange == VideoRange.HDR
&& state.VideoStream.VideoRangeType == VideoRangeType.DOVI)
{
// Only native SW decoder and HW accelerator can parse dovi rpu.
@@ -353,7 +365,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (state.VideoStream is null
|| !options.EnableVppTonemapping
- || GetVideoColorBitDepth(state) != 10)
+ || GetVideoColorBitDepth(state) < 10)
{
return false;
}
@@ -361,7 +373,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// prefer 'tonemap_vaapi' over 'vpp_qsv' on Linux for supporting Gen9/KBLx.
// 'vpp_qsv' requires VPL, which is only supported on Gen12/TGLx and newer.
if (OperatingSystem.IsWindows()
- && string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)
+ && options.HardwareAccelerationType == HardwareAccelerationType.qsv
&& _mediaEncoder.EncoderVersion < _minFFmpegQsvVppTonemapOption)
{
return false;
@@ -376,7 +388,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (state.VideoStream is null
|| !options.EnableVideoToolboxTonemapping
- || GetVideoColorBitDepth(state) != 10)
+ || GetVideoColorBitDepth(state) < 10)
{
return false;
}
@@ -387,6 +399,25 @@ namespace MediaBrowser.Controller.MediaEncoding
&& state.VideoStream.VideoRangeType is VideoRangeType.HDR10 or VideoRangeType.HLG or VideoRangeType.HDR10Plus or VideoRangeType.DOVIWithHDR10 or VideoRangeType.DOVIWithHLG;
}
+ private bool IsVideoStreamHevcRext(EncodingJobInfo state)
+ {
+ var videoStream = state.VideoStream;
+ if (videoStream is null)
+ {
+ return false;
+ }
+
+ return string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
+ && (string.Equals(videoStream.Profile, "Rext", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.PixelFormat, "yuv422p", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.PixelFormat, "yuv422p10le", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.PixelFormat, "yuv422p12le", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase));
+ }
+
/// <summary>
/// Gets the name of the output video codec.
/// </summary>
@@ -851,13 +882,15 @@ namespace MediaBrowser.Controller.MediaEncoding
options);
}
- private string GetVaapiDeviceArgs(string renderNodePath, string driver, string kernelDriver, string srcDeviceAlias, string alias)
+ private string GetVaapiDeviceArgs(string renderNodePath, string driver, string kernelDriver, string vendorId, string srcDeviceAlias, string alias)
{
alias ??= VaapiAlias;
+ var haveVendorId = !string.IsNullOrEmpty(vendorId)
+ && _mediaEncoder.EncoderVersion >= _minFFmpegVaapiDeviceVendorId;
- // 'renderNodePath' has higher priority than 'kernelDriver'
+ // Priority: 'renderNodePath' > 'vendorId' > 'kernelDriver'
var driverOpts = string.IsNullOrEmpty(renderNodePath)
- ? (string.IsNullOrEmpty(kernelDriver) ? string.Empty : ",kernel_driver=" + kernelDriver)
+ ? (haveVendorId ? $",vendor_id={vendorId}" : (string.IsNullOrEmpty(kernelDriver) ? string.Empty : $",kernel_driver={kernelDriver}"))
: renderNodePath;
// 'driver' behaves similarly to env LIBVA_DRIVER_NAME
@@ -892,7 +925,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (OperatingSystem.IsLinux())
{
// derive qsv from vaapi device
- return GetVaapiDeviceArgs(renderNodePath, "iHD", "i915", null, VaapiAlias) + arg + "@" + VaapiAlias;
+ return GetVaapiDeviceArgs(renderNodePath, "iHD", "i915", "0x8086", null, VaapiAlias) + arg + "@" + VaapiAlias;
}
if (OperatingSystem.IsWindows())
@@ -921,7 +954,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
// DVBSUB uses the fixed canvas size 720x576
if (state.SubtitleStream is not null
- && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
+ && ShouldEncodeSubtitle(state)
&& !state.SubtitleStream.IsTextSubtitleStream
&& !string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
{
@@ -971,7 +1004,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
var isHwTonemapAvailable = IsHwTonemapAvailable(state, options);
- if (string.Equals(optHwaccelType, "vaapi", StringComparison.OrdinalIgnoreCase))
+ if (optHwaccelType == HardwareAccelerationType.vaapi)
{
if (!isLinux || !_mediaEncoder.SupportsHwaccel("vaapi"))
{
@@ -987,14 +1020,14 @@ namespace MediaBrowser.Controller.MediaEncoding
if (_mediaEncoder.IsVaapiDeviceInteliHD)
{
- args.Append(GetVaapiDeviceArgs(options.VaapiDevice, "iHD", null, null, VaapiAlias));
+ args.Append(GetVaapiDeviceArgs(options.VaapiDevice, "iHD", null, null, null, VaapiAlias));
}
else if (_mediaEncoder.IsVaapiDeviceInteli965)
{
// Only override i965 since it has lower priority than iHD in libva lookup.
Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME", "i965");
Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME_JELLYFIN", "i965");
- args.Append(GetVaapiDeviceArgs(options.VaapiDevice, "i965", null, null, VaapiAlias));
+ args.Append(GetVaapiDeviceArgs(options.VaapiDevice, "i965", null, null, null, VaapiAlias));
}
var filterDevArgs = string.Empty;
@@ -1018,7 +1051,7 @@ namespace MediaBrowser.Controller.MediaEncoding
&& Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
{
args.Append(GetDrmDeviceArgs(options.VaapiDevice, DrmAlias));
- args.Append(GetVaapiDeviceArgs(null, null, null, DrmAlias, VaapiAlias));
+ args.Append(GetVaapiDeviceArgs(null, null, null, null, DrmAlias, VaapiAlias));
args.Append(GetVulkanDeviceArgs(0, null, DrmAlias, VulkanAlias));
// libplacebo wants an explicitly set vulkan filter device.
@@ -1026,7 +1059,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else
{
- args.Append(GetVaapiDeviceArgs(options.VaapiDevice, null, null, null, VaapiAlias));
+ args.Append(GetVaapiDeviceArgs(options.VaapiDevice, null, null, null, null, VaapiAlias));
filterDevArgs = GetFilterHwDeviceArgs(VaapiAlias);
if (doOclTonemap)
@@ -1045,7 +1078,7 @@ namespace MediaBrowser.Controller.MediaEncoding
args.Append(filterDevArgs);
}
- else if (string.Equals(optHwaccelType, "qsv", StringComparison.OrdinalIgnoreCase))
+ else if (optHwaccelType == HardwareAccelerationType.qsv)
{
if ((!isLinux && !isWindows) || !_mediaEncoder.SupportsHwaccel("qsv"))
{
@@ -1080,7 +1113,7 @@ namespace MediaBrowser.Controller.MediaEncoding
args.Append(filterDevArgs);
}
- else if (string.Equals(optHwaccelType, "nvenc", StringComparison.OrdinalIgnoreCase))
+ else if (optHwaccelType == HardwareAccelerationType.nvenc)
{
if ((!isLinux && !isWindows) || !IsCudaFullSupported())
{
@@ -1099,7 +1132,7 @@ namespace MediaBrowser.Controller.MediaEncoding
args.Append(GetCudaDeviceArgs(0, CudaAlias))
.Append(GetFilterHwDeviceArgs(CudaAlias));
}
- else if (string.Equals(optHwaccelType, "amf", StringComparison.OrdinalIgnoreCase))
+ else if (optHwaccelType == HardwareAccelerationType.amf)
{
if (!isWindows || !_mediaEncoder.SupportsHwaccel("d3d11va"))
{
@@ -1124,7 +1157,7 @@ namespace MediaBrowser.Controller.MediaEncoding
args.Append(filterDevArgs);
}
- else if (string.Equals(optHwaccelType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
+ else if (optHwaccelType == HardwareAccelerationType.videotoolbox)
{
if (!isMacOS || !_mediaEncoder.SupportsHwaccel("videotoolbox"))
{
@@ -1141,7 +1174,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// videotoolbox hw filter does not require device selection
args.Append(GetVideoToolboxDeviceArgs(VideotoolboxAlias));
}
- else if (string.Equals(optHwaccelType, "rkmpp", StringComparison.OrdinalIgnoreCase))
+ else if (optHwaccelType == HardwareAccelerationType.rkmpp)
{
if (!isLinux || !_mediaEncoder.SupportsHwaccel("rkmpp"))
{
@@ -1220,7 +1253,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// sub2video for external graphical subtitles
if (state.SubtitleStream is not null
- && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
+ && ShouldEncodeSubtitle(state)
&& !state.SubtitleStream.IsTextSubtitleStream
&& state.SubtitleStream.IsExternal)
{
@@ -1414,6 +1447,148 @@ namespace MediaBrowser.Controller.MediaEncoding
return FormattableString.Invariant($" -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
}
+ private string GetEncoderParam(EncoderPreset? preset, EncoderPreset defaultPreset, EncodingOptions encodingOptions, string videoEncoder, bool isLibX265)
+ {
+ var param = string.Empty;
+ var encoderPreset = preset ?? defaultPreset;
+ if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || isLibX265)
+ {
+ var presetString = encoderPreset switch
+ {
+ EncoderPreset.auto => EncoderPreset.veryfast.ToString().ToLowerInvariant(),
+ _ => encoderPreset.ToString().ToLowerInvariant()
+ };
+
+ param += " -preset " + presetString;
+
+ int encodeCrf = encodingOptions.H264Crf;
+ if (isLibX265)
+ {
+ encodeCrf = encodingOptions.H265Crf;
+ }
+
+ if (encodeCrf >= 0 && encodeCrf <= 51)
+ {
+ param += " -crf " + encodeCrf.ToString(CultureInfo.InvariantCulture);
+ }
+ else
+ {
+ string defaultCrf = "23";
+ if (isLibX265)
+ {
+ defaultCrf = "28";
+ }
+
+ param += " -crf " + defaultCrf;
+ }
+ }
+ else if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase))
+ {
+ // Default to use the recommended preset 10.
+ // Omit presets < 5, which are too slow for on the fly encoding.
+ // https://gitlab.com/AOMediaCodec/SVT-AV1/-/blob/master/Docs/Ffmpeg.md
+ param += encoderPreset switch
+ {
+ EncoderPreset.veryslow => " -preset 5",
+ EncoderPreset.slower => " -preset 6",
+ EncoderPreset.slow => " -preset 7",
+ EncoderPreset.medium => " -preset 8",
+ EncoderPreset.fast => " -preset 9",
+ EncoderPreset.faster => " -preset 10",
+ EncoderPreset.veryfast => " -preset 11",
+ EncoderPreset.superfast => " -preset 12",
+ EncoderPreset.ultrafast => " -preset 13",
+ _ => " -preset 10"
+ };
+ }
+ else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
+ {
+ // -compression_level is not reliable on AMD.
+ if (_mediaEncoder.IsVaapiDeviceInteliHD)
+ {
+ param += encoderPreset switch
+ {
+ EncoderPreset.veryslow => " -compression_level 1",
+ EncoderPreset.slower => " -compression_level 2",
+ EncoderPreset.slow => " -compression_level 3",
+ EncoderPreset.medium => " -compression_level 4",
+ EncoderPreset.fast => " -compression_level 5",
+ EncoderPreset.faster => " -compression_level 6",
+ EncoderPreset.veryfast => " -compression_level 7",
+ EncoderPreset.superfast => " -compression_level 7",
+ EncoderPreset.ultrafast => " -compression_level 7",
+ _ => string.Empty
+ };
+ }
+ }
+ else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) // h264 (h264_qsv)
+ || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase) // hevc (hevc_qsv)
+ || string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)) // av1 (av1_qsv)
+ {
+ EncoderPreset[] valid_presets = [EncoderPreset.veryslow, EncoderPreset.slower, EncoderPreset.slow, EncoderPreset.medium, EncoderPreset.fast, EncoderPreset.faster, EncoderPreset.veryfast];
+
+ param += " -preset " + (valid_presets.Contains(encoderPreset) ? encoderPreset : EncoderPreset.veryfast).ToString().ToLowerInvariant();
+ }
+ else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc)
+ || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) // hevc (hevc_nvenc)
+ || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase) // av1 (av1_nvenc)
+ )
+ {
+ param += encoderPreset switch
+ {
+ EncoderPreset.veryslow => " -preset p7",
+ EncoderPreset.slower => " -preset p6",
+ EncoderPreset.slow => " -preset p5",
+ EncoderPreset.medium => " -preset p4",
+ EncoderPreset.fast => " -preset p3",
+ EncoderPreset.faster => " -preset p2",
+ _ => " -preset p1"
+ };
+ }
+ else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) // h264 (h264_amf)
+ || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) // hevc (hevc_amf)
+ || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase) // av1 (av1_amf)
+ )
+ {
+ param += encoderPreset switch
+ {
+ EncoderPreset.veryslow => " -quality quality",
+ EncoderPreset.slower => " -quality quality",
+ EncoderPreset.slow => " -quality quality",
+ EncoderPreset.medium => " -quality balanced",
+ _ => " -quality speed"
+ };
+
+ if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase))
+ {
+ param += " -header_insertion_mode gop";
+ }
+
+ if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
+ {
+ param += " -gops_per_idr 1";
+ }
+ }
+ else if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase) // h264 (h264_videotoolbox)
+ || string.Equals(videoEncoder, "hevc_videotoolbox", StringComparison.OrdinalIgnoreCase) // hevc (hevc_videotoolbox)
+ )
+ {
+ param += encoderPreset switch
+ {
+ EncoderPreset.veryslow => " -prio_speed 0",
+ EncoderPreset.slower => " -prio_speed 0",
+ EncoderPreset.slow => " -prio_speed 0",
+ EncoderPreset.medium => " -prio_speed 0",
+ _ => " -prio_speed 1"
+ };
+ }
+
+ return param;
+ }
+
public static string NormalizeTranscodingLevel(EncodingJobInfo state, string level)
{
if (double.TryParse(level, CultureInfo.InvariantCulture, out double requestLevel))
@@ -1508,13 +1683,15 @@ namespace MediaBrowser.Controller.MediaEncoding
setPtsParam);
}
- var mediaPath = state.MediaPath ?? string.Empty;
+ var subtitlePath = _subtitleEncoder.GetSubtitleFilePath(
+ state.SubtitleStream,
+ state.MediaSource,
+ CancellationToken.None).GetAwaiter().GetResult();
return string.Format(
CultureInfo.InvariantCulture,
- "subtitles=f='{0}':si={1}{2}{3}{4}{5}",
- _mediaEncoder.EscapeSubtitleFilterPath(mediaPath),
- state.InternalSubtitleStreamOffset.ToString(CultureInfo.InvariantCulture),
+ "subtitles=f='{0}'{1}{2}{3}{4}",
+ _mediaEncoder.EscapeSubtitleFilterPath(subtitlePath),
alphaParam,
sub2videoParam,
fontParam,
@@ -1534,7 +1711,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (maxrate.HasValue && state.VideoStream is not null)
{
- var contentRate = state.VideoStream.AverageFrameRate ?? state.VideoStream.RealFrameRate;
+ var contentRate = state.VideoStream.ReferenceFrameRate;
if (contentRate.HasValue && contentRate.Value > maxrate.Value)
{
@@ -1626,7 +1803,7 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <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)
+ public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, EncoderPreset defaultPreset)
{
var param = string.Empty;
@@ -1641,7 +1818,9 @@ namespace MediaBrowser.Controller.MediaEncoding
// https://github.com/intel/media-driver/issues/1456
var enableWaFori915Hang = false;
- if (string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
+ var hardwareAccelerationType = encodingOptions.HardwareAccelerationType;
+
+ if (hardwareAccelerationType == HardwareAccelerationType.vaapi)
{
var isIntelVaapiDriver = _mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965;
@@ -1654,7 +1833,7 @@ namespace MediaBrowser.Controller.MediaEncoding
intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder && isIntelVaapiDriver;
}
}
- else if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
+ else if (hardwareAccelerationType == HardwareAccelerationType.qsv)
{
if (OperatingSystem.IsLinux())
{
@@ -1701,204 +1880,10 @@ namespace MediaBrowser.Controller.MediaEncoding
param += " -async_depth 1";
}
- var isVc1 = string.Equals(state.VideoStream?.Codec, "vc1", StringComparison.OrdinalIgnoreCase);
var isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase);
+ var encodingPreset = encodingOptions.EncoderPreset;
- if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || isLibX265)
- {
- if (!string.IsNullOrEmpty(encodingOptions.EncoderPreset))
- {
- param += " -preset " + encodingOptions.EncoderPreset;
- }
- else
- {
- param += " -preset " + defaultPreset;
- }
-
- int encodeCrf = encodingOptions.H264Crf;
- if (isLibX265)
- {
- encodeCrf = encodingOptions.H265Crf;
- }
-
- if (encodeCrf >= 0 && encodeCrf <= 51)
- {
- param += " -crf " + encodeCrf.ToString(CultureInfo.InvariantCulture);
- }
- else
- {
- string defaultCrf = "23";
- if (isLibX265)
- {
- defaultCrf = "28";
- }
-
- param += " -crf " + defaultCrf;
- }
- }
- else if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase))
- {
- // Default to use the recommended preset 10.
- // Omit presets < 5, which are too slow for on the fly encoding.
- // https://gitlab.com/AOMediaCodec/SVT-AV1/-/blob/master/Docs/Ffmpeg.md
- param += encodingOptions.EncoderPreset switch
- {
- "veryslow" => " -preset 5",
- "slower" => " -preset 6",
- "slow" => " -preset 7",
- "medium" => " -preset 8",
- "fast" => " -preset 9",
- "faster" => " -preset 10",
- "veryfast" => " -preset 11",
- "superfast" => " -preset 12",
- "ultrafast" => " -preset 13",
- _ => " -preset 10"
- };
- }
- else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
- {
- // -compression_level is not reliable on AMD.
- if (_mediaEncoder.IsVaapiDeviceInteliHD)
- {
- param += encodingOptions.EncoderPreset switch
- {
- "veryslow" => " -compression_level 1",
- "slower" => " -compression_level 2",
- "slow" => " -compression_level 3",
- "medium" => " -compression_level 4",
- "fast" => " -compression_level 5",
- "faster" => " -compression_level 6",
- "veryfast" => " -compression_level 7",
- "superfast" => " -compression_level 7",
- "ultrafast" => " -compression_level 7",
- _ => string.Empty
- };
- }
- }
- else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) // h264 (h264_qsv)
- || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase) // hevc (hevc_qsv)
- || string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)) // av1 (av1_qsv)
- {
- string[] valid_presets = { "veryslow", "slower", "slow", "medium", "fast", "faster", "veryfast" };
-
- if (valid_presets.Contains(encodingOptions.EncoderPreset, StringComparison.OrdinalIgnoreCase))
- {
- param += " -preset " + encodingOptions.EncoderPreset;
- }
- else
- {
- param += " -preset veryfast";
- }
- }
- else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc)
- || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) // hevc (hevc_nvenc)
- || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase)) // av1 (av1_nvenc)
- {
- switch (encodingOptions.EncoderPreset)
- {
- case "veryslow":
- param += " -preset p7";
- break;
-
- case "slower":
- param += " -preset p6";
- break;
-
- case "slow":
- param += " -preset p5";
- break;
-
- case "medium":
- param += " -preset p4";
- break;
-
- case "fast":
- param += " -preset p3";
- break;
-
- case "faster":
- param += " -preset p2";
- break;
-
- case "veryfast":
- case "superfast":
- case "ultrafast":
- param += " -preset p1";
- break;
-
- default:
- param += " -preset p1";
- break;
- }
- }
- else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) // h264 (h264_amf)
- || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) // hevc (hevc_amf)
- || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase)) // av1 (av1_amf)
- {
- switch (encodingOptions.EncoderPreset)
- {
- case "veryslow":
- case "slower":
- case "slow":
- param += " -quality quality";
- break;
-
- case "medium":
- param += " -quality balanced";
- break;
-
- case "fast":
- case "faster":
- case "veryfast":
- case "superfast":
- case "ultrafast":
- param += " -quality speed";
- break;
-
- default:
- param += " -quality speed";
- break;
- }
-
- if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase))
- {
- param += " -header_insertion_mode gop";
- }
-
- if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
- {
- param += " -gops_per_idr 1";
- }
- }
- else if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase) // h264 (h264_videotoolbox)
- || string.Equals(videoEncoder, "hevc_videotoolbox", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_videotoolbox)
- {
- switch (encodingOptions.EncoderPreset)
- {
- case "veryslow":
- case "slower":
- case "slow":
- case "medium":
- param += " -prio_speed 0";
- break;
-
- case "fast":
- case "faster":
- case "veryfast":
- case "superfast":
- case "ultrafast":
- param += " -prio_speed 1";
- break;
-
- default:
- param += " -prio_speed 1";
- break;
- }
- }
-
+ param += GetEncoderParam(encodingPreset, defaultPreset, encodingOptions, videoEncoder, isLibX265);
param += GetVideoBitrateParam(state, videoEncoder);
var framerate = GetFramerateParam(state);
@@ -1915,7 +1900,26 @@ namespace MediaBrowser.Controller.MediaEncoding
}
var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault() ?? string.Empty;
- profile = WhiteSpaceRegex().Replace(profile, string.Empty);
+ profile = WhiteSpaceRegex().Replace(profile, string.Empty).ToLowerInvariant();
+
+ var videoProfiles = Array.Empty<string>();
+ if (string.Equals("h264", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
+ {
+ videoProfiles = _videoProfilesH264;
+ }
+ else if (string.Equals("hevc", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
+ {
+ videoProfiles = _videoProfilesH265;
+ }
+ else if (string.Equals("av1", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
+ {
+ videoProfiles = _videoProfilesAv1;
+ }
+
+ if (!videoProfiles.Contains(profile, StringComparison.OrdinalIgnoreCase))
+ {
+ profile = string.Empty;
+ }
// We only transcode to HEVC 8-bit for now, force Main Profile.
if (profile.Contains("main10", StringComparison.OrdinalIgnoreCase)
@@ -2218,7 +2222,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var requestedFramerate = request.MaxFramerate ?? request.Framerate;
if (requestedFramerate.HasValue)
{
- var videoFrameRate = videoStream.AverageFrameRate ?? videoStream.RealFrameRate;
+ var videoFrameRate = videoStream.ReferenceFrameRate;
if (!videoFrameRate.HasValue || videoFrameRate.Value > requestedFramerate.Value)
{
@@ -2415,7 +2419,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return 1;
}
- private static int ScaleBitrate(int bitrate, string inputVideoCodec, string outputVideoCodec)
+ public static int ScaleBitrate(int bitrate, string inputVideoCodec, string outputVideoCodec)
{
var inputScaleFactor = GetVideoBitrateScaleFactor(inputVideoCodec);
var outputScaleFactor = GetVideoBitrateScaleFactor(outputVideoCodec);
@@ -2439,6 +2443,12 @@ namespace MediaBrowser.Controller.MediaEncoding
{
scaleFactor = Math.Max(scaleFactor, 2);
}
+ else if (bitrate >= 30000000)
+ {
+ // Don't scale beyond 30Mbps, it is hardly visually noticeable for most codecs with our prefer speed encoding
+ // and will cause extremely high bitrate to be used for av1->h264 transcoding that will overload clients and encoders
+ scaleFactor = 1;
+ }
return Convert.ToInt32(scaleFactor * bitrate);
}
@@ -2569,7 +2579,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
var isCopyingTimestamps = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive;
- if (state.SubtitleStream is not null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode && !isCopyingTimestamps)
+ if (state.SubtitleStream is not null && state.SubtitleStream.IsTextSubtitleStream && ShouldEncodeSubtitle(state) && !isCopyingTimestamps)
{
var seconds = TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds;
@@ -2770,7 +2780,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.AudioStream.IsExternal)
{
bool hasExternalGraphicsSubs = state.SubtitleStream is not null
- && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
+ && ShouldEncodeSubtitle(state)
&& state.SubtitleStream.IsExternal
&& !state.SubtitleStream.IsTextSubtitleStream;
int externalAudioMapIndex = hasExternalGraphicsSubs ? 2 : 1;
@@ -3014,6 +3024,8 @@ namespace MediaBrowser.Controller.MediaEncoding
public static string GetGraphicalSubPreProcessFilters(
int? videoWidth,
int? videoHeight,
+ int? subtitleWidth,
+ int? subtitleHeight,
int? requestedWidth,
int? requestedHeight,
int? requestedMaxWidth,
@@ -3027,16 +3039,37 @@ namespace MediaBrowser.Controller.MediaEncoding
requestedMaxWidth,
requestedMaxHeight);
- if (outWidth.HasValue && outHeight.HasValue)
+ if (!outWidth.HasValue
+ || !outHeight.HasValue
+ || outWidth.Value <= 0
+ || outHeight.Value <= 0)
{
- return string.Format(
- CultureInfo.InvariantCulture,
- @"scale,scale=-1:{1}:fast_bilinear,crop,pad=max({0}\,iw):max({1}\,ih):(ow-iw)/2:(oh-ih)/2:black@0,crop={0}:{1}",
- outWidth.Value,
- outHeight.Value);
+ return string.Empty;
}
- return string.Empty;
+ // Automatically add padding based on subtitle input
+ var filters = @"scale,scale=-1:{1}:fast_bilinear,crop,pad=max({0}\,iw):max({1}\,ih):(ow-iw)/2:(oh-ih)/2:black@0,crop={0}:{1}";
+
+ if (subtitleWidth.HasValue
+ && subtitleHeight.HasValue
+ && subtitleWidth.Value > 0
+ && subtitleHeight.Value > 0)
+ {
+ var videoDar = (double)outWidth.Value / outHeight.Value;
+ var subtitleDar = (double)subtitleWidth.Value / subtitleHeight.Value;
+
+ // No need to add padding when DAR is the same -> 1080p PGSSUB on 2160p video
+ if (Math.Abs(videoDar - subtitleDar) < 0.01f)
+ {
+ filters = @"scale,scale={0}:{1}:fast_bilinear";
+ }
+ }
+
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ filters,
+ outWidth.Value,
+ outHeight.Value);
}
public static string GetAlphaSrcFilter(
@@ -3234,21 +3267,20 @@ namespace MediaBrowser.Controller.MediaEncoding
public static string GetSwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options)
{
- var doubleRateDeint = options.DeinterlaceDoubleRate && state.VideoStream?.AverageFrameRate <= 30;
+ var doubleRateDeint = options.DeinterlaceDoubleRate && state.VideoStream?.ReferenceFrameRate <= 30;
return string.Format(
CultureInfo.InvariantCulture,
"{0}={1}:-1:0",
- string.Equals(options.DeinterlaceMethod, "bwdif", StringComparison.OrdinalIgnoreCase) ? "bwdif" : "yadif",
+ options.DeinterlaceMethod.ToString().ToLowerInvariant(),
doubleRateDeint ? "1" : "0");
}
public string GetHwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options, string hwDeintSuffix)
{
- var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.AverageFrameRate ?? 60) <= 30;
+ var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30;
if (hwDeintSuffix.Contains("cuda", StringComparison.OrdinalIgnoreCase))
{
- var useBwdif = string.Equals(options.DeinterlaceMethod, "bwdif", StringComparison.OrdinalIgnoreCase)
- && _mediaEncoder.SupportsFilter("bwdif_cuda");
+ var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif && _mediaEncoder.SupportsFilter("bwdif_cuda");
return string.Format(
CultureInfo.InvariantCulture,
@@ -3272,16 +3304,19 @@ namespace MediaBrowser.Controller.MediaEncoding
if (hwDeintSuffix.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase))
{
+ var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif && _mediaEncoder.SupportsFilter("bwdif_videotoolbox");
+
return string.Format(
CultureInfo.InvariantCulture,
- "yadif_videotoolbox={0}:-1:0",
+ "{0}_videotoolbox={1}:-1:0",
+ useBwdif ? "bwdif" : "yadif",
doubleRateDeint ? "1" : "0");
}
return string.Empty;
}
- public string GetHwTonemapFilter(EncodingOptions options, string hwTonemapSuffix, string videoFormat)
+ private string GetHwTonemapFilter(EncodingOptions options, string hwTonemapSuffix, string videoFormat, bool forceFullRange)
{
if (string.IsNullOrEmpty(hwTonemapSuffix))
{
@@ -3289,7 +3324,10 @@ namespace MediaBrowser.Controller.MediaEncoding
}
var args = string.Empty;
- var algorithm = options.TonemappingAlgorithm;
+ var algorithm = options.TonemappingAlgorithm.ToString().ToLowerInvariant();
+ var mode = options.TonemappingMode.ToString().ToLowerInvariant();
+ var range = forceFullRange ? TonemappingRange.pc : options.TonemappingRange;
+ var rangeString = range.ToString().ToLowerInvariant();
if (string.Equals(hwTonemapSuffix, "vaapi", StringComparison.OrdinalIgnoreCase))
{
@@ -3324,10 +3362,10 @@ namespace MediaBrowser.Controller.MediaEncoding
args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709:tonemap={2}:peak={3}:desat={4}";
var useLegacyTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegOclCuTonemapMode
- && _legacyTonemapModes.Contains(options.TonemappingMode, StringComparison.OrdinalIgnoreCase);
+ && _legacyTonemapModes.Contains(options.TonemappingMode);
var useAdvancedTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegAdvancedTonemapMode
- && _advancedTonemapModes.Contains(options.TonemappingMode, StringComparison.OrdinalIgnoreCase);
+ && _advancedTonemapModes.Contains(options.TonemappingMode);
if (useLegacyTonemapModes || useAdvancedTonemapModes)
{
@@ -3339,8 +3377,7 @@ namespace MediaBrowser.Controller.MediaEncoding
args += ":param={6}";
}
- if (string.Equals(options.TonemappingRange, "tv", StringComparison.OrdinalIgnoreCase)
- || string.Equals(options.TonemappingRange, "pc", StringComparison.OrdinalIgnoreCase))
+ if (range == TonemappingRange.tv || range == TonemappingRange.pc)
{
args += ":range={7}";
}
@@ -3354,12 +3391,12 @@ namespace MediaBrowser.Controller.MediaEncoding
algorithm,
options.TonemappingPeak,
options.TonemappingDesat,
- options.TonemappingMode,
+ mode,
options.TonemappingParam,
- options.TonemappingRange);
+ rangeString);
}
- public string GetLibplaceboFilter(
+ private string GetLibplaceboFilter(
EncodingOptions options,
string videoFormat,
bool doTonemap,
@@ -3368,7 +3405,8 @@ namespace MediaBrowser.Controller.MediaEncoding
int? requestedWidth,
int? requestedHeight,
int? requestedMaxWidth,
- int? requestedMaxHeight)
+ int? requestedMaxHeight,
+ bool forceFullRange)
{
var (outWidth, outHeight) = GetFixedOutputSize(
videoWidth,
@@ -3391,24 +3429,24 @@ namespace MediaBrowser.Controller.MediaEncoding
if (doTonemap)
{
var algorithm = options.TonemappingAlgorithm;
+ var algorithmString = "clip";
var mode = options.TonemappingMode;
- var range = options.TonemappingRange;
+ var range = forceFullRange ? TonemappingRange.pc : options.TonemappingRange;
- if (string.Equals(algorithm, "bt2390", StringComparison.OrdinalIgnoreCase))
+ if (algorithm == TonemappingAlgorithm.bt2390)
{
- algorithm = "bt.2390";
+ algorithmString = "bt.2390";
}
- else if (string.Equals(algorithm, "none", StringComparison.OrdinalIgnoreCase))
+ else if (algorithm != TonemappingAlgorithm.none)
{
- algorithm = "clip";
+ algorithmString = algorithm.ToString().ToLowerInvariant();
}
- tonemapArg = ":tonemapping=" + algorithm + ":peak_detect=0:color_primaries=bt709:color_trc=bt709:colorspace=bt709";
+ tonemapArg = $":tonemapping={algorithmString}:peak_detect=0:color_primaries=bt709:color_trc=bt709:colorspace=bt709";
- if (string.Equals(range, "tv", StringComparison.OrdinalIgnoreCase)
- || string.Equals(range, "pc", StringComparison.OrdinalIgnoreCase))
+ if (range == TonemappingRange.tv || range == TonemappingRange.pc)
{
- tonemapArg += ":range=" + range;
+ tonemapArg += ":range=" + range.ToString().ToLowerInvariant();
}
}
@@ -3463,7 +3501,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var doToneMap = IsSwTonemapAvailable(state, options);
var requireDoviReshaping = doToneMap && state.VideoStream.VideoRangeType == VideoRangeType.DOVI;
- var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
@@ -3512,8 +3550,8 @@ namespace MediaBrowser.Controller.MediaEncoding
tonemapArgs += $":param={options.TonemappingParam}";
}
- if (string.Equals(options.TonemappingRange, "tv", StringComparison.OrdinalIgnoreCase)
- || string.Equals(options.TonemappingRange, "pc", StringComparison.OrdinalIgnoreCase))
+ var range = options.TonemappingRange;
+ if (range == TonemappingRange.tv || range == TonemappingRange.pc)
{
tonemapArgs += $":range={options.TonemappingRange}";
}
@@ -3537,7 +3575,9 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (hasGraphicalSubs)
{
- var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
+ var subW = state.SubtitleStream?.Width;
+ var subH = state.SubtitleStream?.Height;
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subPreProcFilters);
overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
}
@@ -3557,7 +3597,7 @@ namespace MediaBrowser.Controller.MediaEncoding
EncodingOptions options,
string vidEncoder)
{
- if (!string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
+ if (options.HardwareAccelerationType != HardwareAccelerationType.nvenc)
{
return (null, null, null);
}
@@ -3596,20 +3636,23 @@ namespace MediaBrowser.Controller.MediaEncoding
var isNvencEncoder = vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
var isSwEncoder = !isNvencEncoder;
+ var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
var isCuInCuOut = isNvDecoder && isNvencEncoder;
- var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.AverageFrameRate ?? 60) <= 30;
+ var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30;
var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
var doDeintH2645 = doDeintH264 || doDeintHevc;
var doCuTonemap = IsHwTonemapAvailable(state, options);
- var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
&& (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
+ var subW = state.SubtitleStream?.Width;
+ var subH = state.SubtitleStream?.Height;
var rotation = state.VideoStream?.Rotation ?? 0;
var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
@@ -3662,7 +3705,8 @@ namespace MediaBrowser.Controller.MediaEncoding
mainFilters.Add($"transpose_cuda=dir={tranposeDir}");
}
- var outFormat = doCuTonemap ? string.Empty : "yuv420p";
+ var isRext = IsVideoStreamHevcRext(state);
+ var outFormat = doCuTonemap ? (isRext ? "p010" : string.Empty) : "yuv420p";
var hwScaleFilter = GetHwScaleFilter("scale", "cuda", outFormat, false, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
// hw scale
mainFilters.Add(hwScaleFilter);
@@ -3671,7 +3715,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// hw tonemap
if (doCuTonemap)
{
- var tonemapFilter = GetHwTonemapFilter(options, "cuda", "yuv420p");
+ var tonemapFilter = GetHwTonemapFilter(options, "cuda", "yuv420p", isMjpegEncoder);
mainFilters.Add(tonemapFilter);
}
@@ -3713,7 +3757,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subPreProcFilters);
subFilters.Add("format=yuva420p");
}
@@ -3738,7 +3782,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subPreProcFilters);
overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
}
@@ -3759,7 +3803,7 @@ namespace MediaBrowser.Controller.MediaEncoding
EncodingOptions options,
string vidEncoder)
{
- if (!string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase))
+ if (options.HardwareAccelerationType != HardwareAccelerationType.amf)
{
return (null, null, null);
}
@@ -3800,6 +3844,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var isAmfEncoder = vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
var isSwEncoder = !isAmfEncoder;
+ var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
var isDxInDxOut = isD3d11vaDecoder && isAmfEncoder;
var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
@@ -3807,12 +3852,14 @@ namespace MediaBrowser.Controller.MediaEncoding
var doDeintH2645 = doDeintH264 || doDeintHevc;
var doOclTonemap = IsHwTonemapAvailable(state, options);
- var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
&& (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
+ var subW = state.SubtitleStream?.Width;
+ var subH = state.SubtitleStream?.Height;
var rotation = state.VideoStream?.Rotation ?? 0;
var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
@@ -3877,7 +3924,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// hw tonemap
if (doOclTonemap)
{
- var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12");
+ var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
mainFilters.Add(tonemapFilter);
}
@@ -3927,7 +3974,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subPreProcFilters);
subFilters.Add("format=yuva420p");
}
@@ -3954,7 +4001,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subPreProcFilters);
overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
}
@@ -3975,7 +4022,7 @@ namespace MediaBrowser.Controller.MediaEncoding
EncodingOptions options,
string vidEncoder)
{
- if (!string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
+ if (options.HardwareAccelerationType != HardwareAccelerationType.qsv)
{
return (null, null, null);
}
@@ -4036,6 +4083,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var isHwDecoder = isD3d11vaDecoder || isQsvDecoder;
var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
var isSwEncoder = !isQsvEncoder;
+ var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
@@ -4045,12 +4093,14 @@ namespace MediaBrowser.Controller.MediaEncoding
var doOclTonemap = !doVppTonemap && IsHwTonemapAvailable(state, options);
var doTonemap = doVppTonemap || doOclTonemap;
- var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
&& (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
+ var subW = state.SubtitleStream?.Width;
+ var subH = state.SubtitleStream?.Height;
var rotation = state.VideoStream?.Rotation ?? 0;
var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
@@ -4076,6 +4126,12 @@ namespace MediaBrowser.Controller.MediaEncoding
var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
+ if (isMjpegEncoder && !doOclTonemap)
+ {
+ // sw decoder + hw mjpeg encoder
+ swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_range=pc";
+ }
+
// sw scale
mainFilters.Add(swScaleFilter);
mainFilters.Add($"format={outFormat}");
@@ -4090,6 +4146,12 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (isD3d11vaDecoder || isQsvDecoder)
{
+ var isRext = IsVideoStreamHevcRext(state);
+ var twoPassVppTonemap = isRext;
+ var doVppFullRangeOut = isMjpegEncoder
+ && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
+ var doVppScaleModeHq = isMjpegEncoder
+ && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
var doVppProcamp = false;
var procampParams = string.Empty;
if (doVppTonemap)
@@ -4099,34 +4161,39 @@ namespace MediaBrowser.Controller.MediaEncoding
&& options.VppTonemappingBrightness <= 100)
{
procampParams += $":brightness={options.VppTonemappingBrightness}";
- doVppProcamp = true;
+ twoPassVppTonemap = doVppProcamp = true;
}
if (options.VppTonemappingContrast > 1
&& options.VppTonemappingContrast <= 10)
{
procampParams += $":contrast={options.VppTonemappingContrast}";
- doVppProcamp = true;
+ twoPassVppTonemap = doVppProcamp = true;
}
procampParams += doVppProcamp ? ":procamp=1:async_depth=2" : string.Empty;
}
- var outFormat = doOclTonemap ? (doVppTranspose ? "p010" : string.Empty) : "nv12";
- outFormat = (doVppTonemap && doVppProcamp) ? "p010" : outFormat;
+ var outFormat = doOclTonemap ? ((doVppTranspose || isRext) ? "p010" : string.Empty) : "nv12";
+ outFormat = twoPassVppTonemap ? "p010" : outFormat;
var swapOutputWandH = doVppTranspose && swapWAndH;
- var hwScalePrefix = (doVppTranspose || doVppTonemap) ? "vpp" : "scale";
- var hwScaleFilter = GetHwScaleFilter(hwScalePrefix, "qsv", outFormat, swapOutputWandH, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
+ var hwScaleFilter = GetHwScaleFilter("vpp", "qsv", outFormat, swapOutputWandH, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTranspose)
{
hwScaleFilter += $":transpose={tranposeDir}";
}
+ if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
+ {
+ hwScaleFilter += (doVppFullRangeOut && !doOclTonemap) ? ":out_range=pc" : string.Empty;
+ hwScaleFilter += doVppScaleModeHq ? ":scale_mode=hq" : string.Empty;
+ }
+
if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTonemap)
{
- hwScaleFilter += doVppProcamp ? procampParams : ":tonemap=1";
+ hwScaleFilter += doVppProcamp ? procampParams : (twoPassVppTonemap ? string.Empty : ":tonemap=1");
}
if (isD3d11vaDecoder)
@@ -4150,7 +4217,7 @@ namespace MediaBrowser.Controller.MediaEncoding
mainFilters.Add(hwScaleFilter);
// hw tonemap(w/ procamp)
- if (doVppTonemap && doVppProcamp)
+ if (doVppTonemap && twoPassVppTonemap)
{
mainFilters.Add("vpp_qsv=tonemap=1:format=nv12:async_depth=2");
}
@@ -4171,7 +4238,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// hw tonemap
if (doOclTonemap)
{
- var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12");
+ var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
mainFilters.Add(tonemapFilter);
}
@@ -4223,7 +4290,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (hasGraphicalSubs)
{
// overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
- var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, 1080);
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW, 1080);
subFilters.Add(subPreProcFilters);
subFilters.Add("format=bgra");
}
@@ -4259,7 +4326,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subPreProcFilters);
overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
}
@@ -4288,6 +4355,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var isHwDecoder = isVaapiDecoder || isQsvDecoder;
var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
var isSwEncoder = !isQsvEncoder;
+ var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
@@ -4297,12 +4365,14 @@ namespace MediaBrowser.Controller.MediaEncoding
var doTonemap = doVaVppTonemap || doOclTonemap;
var doDeintH2645 = doDeintH264 || doDeintHevc;
- var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
&& (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
+ var subW = state.SubtitleStream?.Width;
+ var subH = state.SubtitleStream?.Height;
var rotation = state.VideoStream?.Rotation ?? 0;
var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
@@ -4328,6 +4398,12 @@ namespace MediaBrowser.Controller.MediaEncoding
var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
+ if (isMjpegEncoder && !doOclTonemap)
+ {
+ // sw decoder + hw mjpeg encoder
+ swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_range=pc";
+ }
+
// sw scale
mainFilters.Add(swScaleFilter);
mainFilters.Add($"format={outFormat}");
@@ -4343,6 +4419,11 @@ namespace MediaBrowser.Controller.MediaEncoding
else if (isVaapiDecoder || isQsvDecoder)
{
var hwFilterSuffix = isVaapiDecoder ? "vaapi" : "qsv";
+ var isRext = IsVideoStreamHevcRext(state);
+ var doVppFullRangeOut = isMjpegEncoder
+ && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
+ var doVppScaleModeHq = isMjpegEncoder
+ && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
// INPUT vaapi/qsv surface(vram)
// hw deint
@@ -4358,9 +4439,9 @@ namespace MediaBrowser.Controller.MediaEncoding
mainFilters.Add($"transpose_vaapi=dir={tranposeDir}");
}
- var outFormat = doOclTonemap ? ((isQsvDecoder && doVppTranspose) ? "p010" : string.Empty) : "nv12";
+ var outFormat = doTonemap ? (((isQsvDecoder && doVppTranspose) || isRext) ? "p010" : string.Empty) : "nv12";
var swapOutputWandH = isQsvDecoder && doVppTranspose && swapWAndH;
- var hwScalePrefix = (isQsvDecoder && doVppTranspose) ? "vpp" : "scale";
+ var hwScalePrefix = isQsvDecoder ? "vpp" : "scale";
var hwScaleFilter = GetHwScaleFilter(hwScalePrefix, hwFilterSuffix, outFormat, swapOutputWandH, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
if (!string.IsNullOrEmpty(hwScaleFilter) && isQsvDecoder && doVppTranspose)
@@ -4368,6 +4449,12 @@ namespace MediaBrowser.Controller.MediaEncoding
hwScaleFilter += $":transpose={tranposeDir}";
}
+ if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
+ {
+ hwScaleFilter += ((isQsvDecoder && !doVppFullRangeOut) || doOclTonemap) ? string.Empty : ":out_range=pc";
+ hwScaleFilter += isQsvDecoder ? (doVppScaleModeHq ? ":scale_mode=hq" : string.Empty) : ":mode=hq";
+ }
+
// allocate extra pool sizes for vaapi vpp scale
if (!string.IsNullOrEmpty(hwScaleFilter) && isVaapiDecoder)
{
@@ -4388,7 +4475,7 @@ namespace MediaBrowser.Controller.MediaEncoding
mainFilters.Add("format=vaapi");
}
- var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12");
+ var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12", isMjpegEncoder);
mainFilters.Add(tonemapFilter);
if (isQsvDecoder)
@@ -4408,7 +4495,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// ocl tonemap
if (doOclTonemap)
{
- var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12");
+ var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
mainFilters.Add(tonemapFilter);
}
@@ -4469,7 +4556,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (hasGraphicalSubs)
{
// overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
- var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, 1080);
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW, 1080);
subFilters.Add(subPreProcFilters);
subFilters.Add("format=bgra");
}
@@ -4504,7 +4591,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subPreProcFilters);
overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
}
@@ -4525,7 +4612,7 @@ namespace MediaBrowser.Controller.MediaEncoding
EncodingOptions options,
string vidEncoder)
{
- if (!string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
+ if (options.HardwareAccelerationType != HardwareAccelerationType.vaapi)
{
return (null, null, null);
}
@@ -4599,6 +4686,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
var isSwEncoder = !isVaapiEncoder;
+ var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
@@ -4608,12 +4696,14 @@ namespace MediaBrowser.Controller.MediaEncoding
var doTonemap = doVaVppTonemap || doOclTonemap;
var doDeintH2645 = doDeintH264 || doDeintHevc;
- var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
&& (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
+ var subW = state.SubtitleStream?.Width;
+ var subH = state.SubtitleStream?.Height;
var rotation = state.VideoStream?.Rotation ?? 0;
var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
@@ -4639,6 +4729,12 @@ namespace MediaBrowser.Controller.MediaEncoding
var outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
+ if (isMjpegEncoder && !doOclTonemap)
+ {
+ // sw decoder + hw mjpeg encoder
+ swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_range=pc";
+ }
+
// sw scale
mainFilters.Add(swScaleFilter);
mainFilters.Add($"format={outFormat}");
@@ -4653,6 +4749,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (isVaapiDecoder)
{
+ var isRext = IsVideoStreamHevcRext(state);
+
// INPUT vaapi surface(vram)
// hw deint
if (doDeintH2645)
@@ -4667,9 +4765,15 @@ namespace MediaBrowser.Controller.MediaEncoding
mainFilters.Add($"transpose_vaapi=dir={tranposeDir}");
}
- var outFormat = doTonemap ? string.Empty : "nv12";
+ var outFormat = doTonemap ? (isRext ? "p010" : string.Empty) : "nv12";
var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
+ if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
+ {
+ hwScaleFilter += doOclTonemap ? string.Empty : ":out_range=pc";
+ hwScaleFilter += ":mode=hq";
+ }
+
// allocate extra pool sizes for vaapi vpp
if (!string.IsNullOrEmpty(hwScaleFilter))
{
@@ -4683,7 +4787,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// vaapi vpp tonemap
if (doVaVppTonemap && isVaapiDecoder)
{
- var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12");
+ var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12", isMjpegEncoder);
mainFilters.Add(tonemapFilter);
}
@@ -4696,7 +4800,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// ocl tonemap
if (doOclTonemap)
{
- var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12");
+ var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
mainFilters.Add(tonemapFilter);
}
@@ -4755,7 +4859,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (hasGraphicalSubs)
{
// overlay_vaapi can handle overlay scaling, setup a smaller height to reduce transfer overhead
- var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, 1080);
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW, 1080);
subFilters.Add(subPreProcFilters);
subFilters.Add("format=bgra");
}
@@ -4788,7 +4892,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subPreProcFilters);
overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
@@ -4820,13 +4924,14 @@ namespace MediaBrowser.Controller.MediaEncoding
var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
var isSwEncoder = !isVaapiEncoder;
+ var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
var doVkTonemap = IsVulkanHwTonemapAvailable(state, options);
var doDeintH2645 = doDeintH264 || doDeintHevc;
- var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
@@ -4915,6 +5020,12 @@ namespace MediaBrowser.Controller.MediaEncoding
// hw scale
var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", "nv12", false, inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+
+ if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder && !doVkTonemap)
+ {
+ hwScaleFilter += ":out_range=pc:mode=hq";
+ }
+
mainFilters.Add(hwScaleFilter);
}
}
@@ -4935,7 +5046,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// vk libplacebo
if (doVkTonemap || hasSubs)
{
- var libplaceboFilter = GetLibplaceboFilter(options, "bgra", doVkTonemap, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
+ var libplaceboFilter = GetLibplaceboFilter(options, "bgra", doVkTonemap, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, isMjpegEncoder);
mainFilters.Add(libplaceboFilter);
mainFilters.Add("format=vulkan");
}
@@ -4980,7 +5091,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
+ var subW = state.SubtitleStream?.Width;
+ var subH = state.SubtitleStream?.Height;
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subPreProcFilters);
subFilters.Add("format=bgra");
}
@@ -5048,6 +5161,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
var isSwEncoder = !isVaapiEncoder;
+ var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
var isi965Driver = _mediaEncoder.IsVaapiDeviceInteli965;
var isAmdDriver = _mediaEncoder.IsVaapiDeviceAmd;
@@ -5057,7 +5171,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var doDeintH2645 = doDeintH264 || doDeintHevc;
var doOclTonemap = IsHwTonemapAvailable(state, options);
- var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
@@ -5084,6 +5198,12 @@ namespace MediaBrowser.Controller.MediaEncoding
outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
+ if (isMjpegEncoder && !doOclTonemap)
+ {
+ // sw decoder + hw mjpeg encoder
+ swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_range=pc";
+ }
+
// sw scale
mainFilters.Add(swScaleFilter);
mainFilters.Add("format=" + outFormat);
@@ -5109,6 +5229,12 @@ namespace MediaBrowser.Controller.MediaEncoding
outFormat = doOclTonemap ? string.Empty : "nv12";
var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
+ {
+ hwScaleFilter += doOclTonemap ? string.Empty : ":out_range=pc";
+ hwScaleFilter += ":mode=hq";
+ }
+
// allocate extra pool sizes for vaapi vpp
if (!string.IsNullOrEmpty(hwScaleFilter))
{
@@ -5137,7 +5263,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// ocl tonemap
if (doOclTonemap)
{
- var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12");
+ var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
mainFilters.Add(tonemapFilter);
}
@@ -5203,7 +5329,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
+ var subW = state.SubtitleStream?.Width;
+ var subH = state.SubtitleStream?.Height;
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subPreProcFilters);
overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
@@ -5229,7 +5357,7 @@ namespace MediaBrowser.Controller.MediaEncoding
EncodingOptions options,
string vidEncoder)
{
- if (!string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
+ if (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox)
{
return (null, null, null);
}
@@ -5261,6 +5389,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
+ var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
var inW = state.VideoStream?.Width;
var inH = state.VideoStream?.Height;
@@ -5303,7 +5432,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var hwScaleFilter = GetHwScaleFilter("scale", "vt", scaleFormat, false, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
- var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
@@ -5342,7 +5471,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// Metal tonemap
if (doMetalTonemap)
{
- var tonemapFilter = GetHwTonemapFilter(options, "videotoolbox", "nv12");
+ var tonemapFilter = GetHwTonemapFilter(options, "videotoolbox", "nv12", isMjpegEncoder);
mainFilters.Add(tonemapFilter);
}
@@ -5354,7 +5483,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
+ var subW = state.SubtitleStream?.Width;
+ var subH = state.SubtitleStream?.Height;
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subPreProcFilters);
subFilters.Add("format=bgra");
}
@@ -5418,7 +5549,7 @@ namespace MediaBrowser.Controller.MediaEncoding
EncodingOptions options,
string vidEncoder)
{
- if (!string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase))
+ if (options.HardwareAccelerationType != HardwareAccelerationType.rkmpp)
{
return (null, null, null);
}
@@ -5463,19 +5594,25 @@ namespace MediaBrowser.Controller.MediaEncoding
var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
var isSwDecoder = !isRkmppDecoder;
var isSwEncoder = !isRkmppEncoder;
+ var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
var isDrmInDrmOut = isRkmppDecoder && isRkmppEncoder;
+ var isEncoderSupportAfbc = isRkmppEncoder
+ && (vidEncoder.Contains("h264", StringComparison.OrdinalIgnoreCase)
+ || vidEncoder.Contains("hevc", StringComparison.OrdinalIgnoreCase));
var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
var doDeintH2645 = doDeintH264 || doDeintHevc;
var doOclTonemap = IsHwTonemapAvailable(state, options);
- var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream != null && ShouldEncodeSubtitle(state);
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
&& (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
+ var subW = state.SubtitleStream?.Width;
+ var subH = state.SubtitleStream?.Height;
var rotation = state.VideoStream?.Rotation ?? 0;
var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
@@ -5501,6 +5638,12 @@ namespace MediaBrowser.Controller.MediaEncoding
var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
+ if (isMjpegEncoder && !doOclTonemap)
+ {
+ // sw decoder + hw mjpeg encoder
+ swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_range=pc";
+ }
+
if (!string.IsNullOrEmpty(swScaleFilter))
{
swScaleFilter += ":flags=fast_bilinear";
@@ -5522,18 +5665,26 @@ namespace MediaBrowser.Controller.MediaEncoding
{
// INPUT rkmpp/drm surface(gem/dma-heap)
- var isFullAfbcPipeline = isDrmInDrmOut && !doOclTonemap;
+ var isFullAfbcPipeline = isEncoderSupportAfbc && isDrmInDrmOut && !doOclTonemap;
var swapOutputWandH = doRkVppTranspose && swapWAndH;
- var outFormat = doOclTonemap ? "p010" : "nv12";
- var hwScalePrefix = doRkVppTranspose ? "vpp" : "scale";
- var hwScaleFilter = GetHwScaleFilter(hwScalePrefix, "rkrga", outFormat, swapOutputWandH, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
- var hwScaleFilter2 = GetHwScaleFilter(hwScalePrefix, "rkrga", string.Empty, swapOutputWandH, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
+ var outFormat = doOclTonemap ? "p010" : (isMjpegEncoder ? "bgra" : "nv12"); // RGA only support full range in rgb fmts
+ var hwScaleFilter = GetHwScaleFilter("vpp", "rkrga", outFormat, swapOutputWandH, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
+ var doScaling = GetHwScaleFilter("vpp", "rkrga", string.Empty, swapOutputWandH, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
if (!hasSubs
|| doRkVppTranspose
|| !isFullAfbcPipeline
- || !string.IsNullOrEmpty(hwScaleFilter2))
+ || !string.IsNullOrEmpty(doScaling))
{
+ // RGA3 hardware only support (1/8 ~ 8) scaling in each blit operation,
+ // but in Trickplay there's a case: (3840/320 == 12), enable 2pass for it
+ if (!string.IsNullOrEmpty(doScaling)
+ && !IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f))
+ {
+ var hwScaleFilterFirstPass = $"scale_rkrga=w=iw/7.9:h=ih/7.9:format={outFormat}:afbc=1";
+ mainFilters.Add(hwScaleFilterFirstPass);
+ }
+
if (!string.IsNullOrEmpty(hwScaleFilter) && doRkVppTranspose)
{
hwScaleFilter += $":transpose={tranposeDir}";
@@ -5553,19 +5704,13 @@ namespace MediaBrowser.Controller.MediaEncoding
if (doOclTonemap && isRkmppDecoder)
{
// map from rkmpp/drm to opencl via drm-opencl interop.
- mainFilters.Add("hwmap=derive_device=opencl:mode=read");
+ mainFilters.Add("hwmap=derive_device=opencl");
}
// ocl tonemap
if (doOclTonemap)
{
- var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12");
- // enable tradeoffs for performance
- if (!string.IsNullOrEmpty(tonemapFilter))
- {
- tonemapFilter += ":tradeoff=1";
- }
-
+ var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
mainFilters.Add(tonemapFilter);
}
@@ -5602,7 +5747,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
// OUTPUT drm(nv12) surface(gem/dma-heap)
// reverse-mapping via drm-opencl interop.
- mainFilters.Add("hwmap=derive_device=rkmpp:mode=write:reverse=1");
+ mainFilters.Add("hwmap=derive_device=rkmpp:reverse=1");
mainFilters.Add("format=drm_prime");
}
}
@@ -5616,7 +5761,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subPreProcFilters);
subFilters.Add("format=bgra");
}
@@ -5636,14 +5781,20 @@ namespace MediaBrowser.Controller.MediaEncoding
subFilters.Add("hwupload=derive_device=rkmpp");
// try enabling AFBC to save DDR bandwidth
- overlayFilters.Add("overlay_rkrga=eof_action=pass:repeatlast=0:format=nv12:afbc=1");
+ var hwOverlayFilter = "overlay_rkrga=eof_action=pass:repeatlast=0:format=nv12";
+ if (isEncoderSupportAfbc)
+ {
+ hwOverlayFilter += ":afbc=1";
+ }
+
+ overlayFilters.Add(hwOverlayFilter);
}
}
else if (memoryOutput)
{
if (hasGraphicalSubs)
{
- var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subPreProcFilters);
overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
}
@@ -5670,7 +5821,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Empty;
}
- var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
@@ -5678,38 +5829,20 @@ namespace MediaBrowser.Controller.MediaEncoding
List<string> subFilters;
List<string> overlayFilters;
- if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
- {
- (mainFilters, subFilters, overlayFilters) = GetVaapiVidFilterChain(state, options, outputVideoCodec);
- }
- else if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
- {
- (mainFilters, subFilters, overlayFilters) = GetIntelVidFilterChain(state, options, outputVideoCodec);
- }
- else if (string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
- {
- (mainFilters, subFilters, overlayFilters) = GetNvidiaVidFilterChain(state, options, outputVideoCodec);
- }
- else if (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase))
- {
- (mainFilters, subFilters, overlayFilters) = GetAmdVidFilterChain(state, options, outputVideoCodec);
- }
- else if (string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
+ (mainFilters, subFilters, overlayFilters) = options.HardwareAccelerationType switch
{
- (mainFilters, subFilters, overlayFilters) = GetAppleVidFilterChain(state, options, outputVideoCodec);
- }
- else if (string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase))
- {
- (mainFilters, subFilters, overlayFilters) = GetRkmppVidFilterChain(state, options, outputVideoCodec);
- }
- else
- {
- (mainFilters, subFilters, overlayFilters) = GetSwVidFilterChain(state, options, outputVideoCodec);
- }
+ HardwareAccelerationType.vaapi => GetVaapiVidFilterChain(state, options, outputVideoCodec),
+ HardwareAccelerationType.amf => GetAmdVidFilterChain(state, options, outputVideoCodec),
+ HardwareAccelerationType.qsv => GetIntelVidFilterChain(state, options, outputVideoCodec),
+ HardwareAccelerationType.nvenc => GetNvidiaVidFilterChain(state, options, outputVideoCodec),
+ HardwareAccelerationType.videotoolbox => GetAppleVidFilterChain(state, options, outputVideoCodec),
+ HardwareAccelerationType.rkmpp => GetRkmppVidFilterChain(state, options, outputVideoCodec),
+ _ => GetSwVidFilterChain(state, options, outputVideoCodec),
+ };
- mainFilters?.RemoveAll(filter => string.IsNullOrEmpty(filter));
- subFilters?.RemoveAll(filter => string.IsNullOrEmpty(filter));
- overlayFilters?.RemoveAll(filter => string.IsNullOrEmpty(filter));
+ mainFilters?.RemoveAll(string.IsNullOrEmpty);
+ subFilters?.RemoveAll(string.IsNullOrEmpty);
+ overlayFilters?.RemoveAll(string.IsNullOrEmpty);
var framerate = GetFramerateParam(state);
if (framerate.HasValue)
@@ -5889,7 +6022,9 @@ namespace MediaBrowser.Controller.MediaEncoding
return null;
}
- if (!string.IsNullOrEmpty(videoStream.Codec) && !string.IsNullOrEmpty(options.HardwareAccelerationType))
+ var hardwareAccelerationType = options.HardwareAccelerationType;
+
+ if (!string.IsNullOrEmpty(videoStream.Codec) && hardwareAccelerationType != HardwareAccelerationType.none)
{
var bitDepth = GetVideoColorBitDepth(state);
@@ -5901,10 +6036,10 @@ namespace MediaBrowser.Controller.MediaEncoding
|| string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase)))
{
// RKMPP has H.264 Hi10P decoder
- bool hasHardwareHi10P = string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase);
+ bool hasHardwareHi10P = hardwareAccelerationType == HardwareAccelerationType.rkmpp;
// VideoToolbox on Apple Silicon has H.264 Hi10P mode enabled after macOS 14.6
- if (string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
+ if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox)
{
var ver = Environment.OSVersion.Version;
var arch = RuntimeInformation.OSArchitecture;
@@ -5921,50 +6056,23 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
- if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
- {
- return GetQsvHwVidDecoder(state, options, videoStream, bitDepth);
- }
-
- if (string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
- {
- return GetNvdecVidDecoder(state, options, videoStream, bitDepth);
- }
-
- if (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase))
- {
- return GetAmfVidDecoder(state, options, videoStream, bitDepth);
- }
-
- if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
- {
- return GetVaapiVidDecoder(state, options, videoStream, bitDepth);
- }
-
- if (string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
+ var decoder = hardwareAccelerationType switch
{
- return GetVideotoolboxVidDecoder(state, options, videoStream, bitDepth);
- }
+ HardwareAccelerationType.vaapi => GetVaapiVidDecoder(state, options, videoStream, bitDepth),
+ HardwareAccelerationType.amf => GetAmfVidDecoder(state, options, videoStream, bitDepth),
+ HardwareAccelerationType.qsv => GetQsvHwVidDecoder(state, options, videoStream, bitDepth),
+ HardwareAccelerationType.nvenc => GetNvdecVidDecoder(state, options, videoStream, bitDepth),
+ HardwareAccelerationType.videotoolbox => GetVideotoolboxVidDecoder(state, options, videoStream, bitDepth),
+ HardwareAccelerationType.rkmpp => GetRkmppVidDecoder(state, options, videoStream, bitDepth),
+ _ => string.Empty
+ };
- if (string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase))
+ if (!string.IsNullOrEmpty(decoder))
{
- return GetRkmppVidDecoder(state, options, videoStream, bitDepth);
+ return decoder;
}
}
- var whichCodec = videoStream.Codec;
- if (string.Equals(whichCodec, "avc", StringComparison.OrdinalIgnoreCase))
- {
- whichCodec = "h264";
- }
- else if (string.Equals(whichCodec, "h265", StringComparison.OrdinalIgnoreCase))
- {
- whichCodec = "hevc";
- }
-
- // Avoid a second attempt if no hardware acceleration is being used
- options.HardwareDecodingCodecs = Array.FindAll(options.HardwareDecodingCodecs, val => !string.Equals(val, whichCodec, StringComparison.OrdinalIgnoreCase));
-
// leave blank so ffmpeg will decide
return null;
}
@@ -5988,7 +6096,11 @@ namespace MediaBrowser.Controller.MediaEncoding
var decoderName = decoderPrefix + '_' + decoderSuffix;
var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoderName) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparison.OrdinalIgnoreCase);
- if (bitDepth == 10 && isCodecAvailable)
+
+ // VideoToolbox decoders have built-in SW fallback
+ if (bitDepth == 10
+ && isCodecAvailable
+ && (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox))
{
if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
&& options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase)
@@ -6044,6 +6156,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var isVideotoolboxSupported = isMacOS && _mediaEncoder.SupportsHwaccel("videotoolbox");
var isRkmppSupported = isLinux && IsRkmppFullSupported();
var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparison.OrdinalIgnoreCase);
+ var hardwareAccelerationType = options.HardwareAccelerationType;
var ffmpegVersion = _mediaEncoder.EncoderVersion;
@@ -6063,17 +6176,40 @@ namespace MediaBrowser.Controller.MediaEncoding
&& ffmpegVersion >= _minFFmpegDisplayRotationOption;
var stripRotationDataArgs = stripRotationData ? " -display_rotation 0" : string.Empty;
- if (bitDepth == 10 && isCodecAvailable)
+ // VideoToolbox decoders have built-in SW fallback
+ if (isCodecAvailable
+ && (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox))
{
if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
- && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase)
- && !options.EnableDecodingColorDepth10Hevc)
+ && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase))
{
- return null;
+ if (IsVideoStreamHevcRext(state))
+ {
+ if (bitDepth <= 10 && !options.EnableDecodingColorDepth10HevcRext)
+ {
+ return null;
+ }
+
+ if (bitDepth == 12 && !options.EnableDecodingColorDepth12HevcRext)
+ {
+ return null;
+ }
+
+ if (hardwareAccelerationType == HardwareAccelerationType.vaapi
+ && !_mediaEncoder.IsVaapiDeviceInteliHD)
+ {
+ return null;
+ }
+ }
+ else if (bitDepth == 10 && !options.EnableDecodingColorDepth10Hevc)
+ {
+ return null;
+ }
}
if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
&& options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
+ && bitDepth == 10
&& !options.EnableDecodingColorDepth10Vp9)
{
return null;
@@ -6081,7 +6217,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// Intel qsv/d3d11va/vaapi
- if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
+ if (hardwareAccelerationType == HardwareAccelerationType.qsv)
{
if (options.PreferSystemNativeHwDecoder)
{
@@ -6107,7 +6243,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// Nvidia cuda
- if (string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
+ if (hardwareAccelerationType == HardwareAccelerationType.nvenc)
{
if (isCudaSupported && isCodecAvailable)
{
@@ -6124,7 +6260,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// Amd d3d11va
- if (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase))
+ if (hardwareAccelerationType == HardwareAccelerationType.amf)
{
if (isD3d11Supported && isCodecAvailable)
{
@@ -6134,7 +6270,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// Vaapi
- if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
+ if (hardwareAccelerationType == HardwareAccelerationType.vaapi
&& isVaapiSupported
&& isCodecAvailable)
{
@@ -6143,7 +6279,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// Apple videotoolbox
- if (string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)
+ if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox
&& isVideotoolboxSupported
&& isCodecAvailable)
{
@@ -6151,7 +6287,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// Rockchip rkmpp
- if (string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase)
+ if (hardwareAccelerationType == HardwareAccelerationType.rkmpp
&& isRkmppSupported
&& isCodecAvailable)
{
@@ -6167,7 +6303,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var isLinux = OperatingSystem.IsLinux();
if ((!isWindows && !isLinux)
- || !string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
+ || options.HardwareAccelerationType != HardwareAccelerationType.qsv)
{
return null;
}
@@ -6185,6 +6321,14 @@ namespace MediaBrowser.Controller.MediaEncoding
var is8bitSwFormatsQsv = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
|| string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8_10bitSwFormatsQsv = is8bitSwFormatsQsv || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+ var is8_10_12bitSwFormatsQsv = is8_10bitSwFormatsQsv
+ || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
// TODO: add more 8/10bit and 4:4:4 formats for Qsv after finishing the ffcheck tool
if (is8bitSwFormatsQsv)
@@ -6213,12 +6357,6 @@ namespace MediaBrowser.Controller.MediaEncoding
if (is8_10bitSwFormatsQsv)
{
- if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
- {
- return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc", "qsv", "hevc", bitDepth);
- }
-
if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
{
return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9", "qsv", "vp9", bitDepth);
@@ -6230,13 +6368,22 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
+ if (is8_10_12bitSwFormatsQsv)
+ {
+ if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc", "qsv", "hevc", bitDepth);
+ }
+ }
+
return null;
}
public string GetNvdecVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth)
{
if ((!OperatingSystem.IsWindows() && !OperatingSystem.IsLinux())
- || !string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
+ || options.HardwareAccelerationType != HardwareAccelerationType.nvenc)
{
return null;
}
@@ -6245,6 +6392,11 @@ namespace MediaBrowser.Controller.MediaEncoding
var is8bitSwFormatsNvdec = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
|| string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8_10bitSwFormatsNvdec = is8bitSwFormatsNvdec || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+ var is8_10_12bitSwFormatsNvdec = is8_10bitSwFormatsNvdec
+ || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
// TODO: add more 8/10/12bit and 4:4:4 formats for Nvdec after finishing the ffcheck tool
if (is8bitSwFormatsNvdec)
@@ -6278,12 +6430,6 @@ namespace MediaBrowser.Controller.MediaEncoding
if (is8_10bitSwFormatsNvdec)
{
- if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
- || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
- {
- return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc", "cuvid", "hevc", bitDepth);
- }
-
if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
{
return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9", "cuvid", "vp9", bitDepth);
@@ -6295,13 +6441,22 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
+ if (is8_10_12bitSwFormatsNvdec)
+ {
+ if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc", "cuvid", "hevc", bitDepth);
+ }
+ }
+
return null;
}
public string GetAmfVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth)
{
if (!OperatingSystem.IsWindows()
- || !string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase))
+ || options.HardwareAccelerationType != HardwareAccelerationType.amf)
{
return null;
}
@@ -6357,7 +6512,7 @@ namespace MediaBrowser.Controller.MediaEncoding
public string GetVaapiVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth)
{
if (!OperatingSystem.IsLinux()
- || !string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
+ || options.HardwareAccelerationType != HardwareAccelerationType.vaapi)
{
return null;
}
@@ -6369,6 +6524,14 @@ namespace MediaBrowser.Controller.MediaEncoding
var is8bitSwFormatsVaapi = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
|| string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8_10bitSwFormatsVaapi = is8bitSwFormatsVaapi || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+ var is8_10_12bitSwFormatsVaapi = is8_10bitSwFormatsVaapi
+ || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
if (is8bitSwFormatsVaapi)
{
@@ -6396,12 +6559,6 @@ namespace MediaBrowser.Controller.MediaEncoding
if (is8_10bitSwFormatsVaapi)
{
- if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
- || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
- {
- return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
- }
-
if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
{
return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
@@ -6413,13 +6570,22 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
+ if (is8_10_12bitSwFormatsVaapi)
+ {
+ if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
+ }
+ }
+
return null;
}
public string GetVideotoolboxVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth)
{
if (!OperatingSystem.IsMacOS()
- || !string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
+ || options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox)
{
return null;
}
@@ -6427,6 +6593,14 @@ namespace MediaBrowser.Controller.MediaEncoding
var is8bitSwFormatsVt = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
|| string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8_10bitSwFormatsVt = is8bitSwFormatsVt || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+ var is8_10_12bitSwFormatsVt = is8_10bitSwFormatsVt
+ || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
// The related patches make videotoolbox hardware surface working is only available in jellyfin-ffmpeg 7.0.1 at the moment.
bool useHwSurface = (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface) && IsVideoToolboxFullSupported();
@@ -6447,15 +6621,18 @@ namespace MediaBrowser.Controller.MediaEncoding
return GetHwaccelType(state, options, "h264", bitDepth, useHwSurface);
}
- if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
- || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
{
- return GetHwaccelType(state, options, "hevc", bitDepth, useHwSurface);
+ return GetHwaccelType(state, options, "vp9", bitDepth, useHwSurface);
}
+ }
- if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ if (is8_10_12bitSwFormatsVt)
+ {
+ if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
{
- return GetHwaccelType(state, options, "vp9", bitDepth, useHwSurface);
+ return GetHwaccelType(state, options, "hevc", bitDepth, useHwSurface);
}
}
@@ -6467,7 +6644,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var isLinux = OperatingSystem.IsLinux();
if (!isLinux
- || !string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase))
+ || options.HardwareAccelerationType != HardwareAccelerationType.rkmpp)
{
return null;
}
@@ -6680,7 +6857,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.IsNullOrEmpty(state.InputVideoSync))
{
- inputModifier += " -vsync " + state.InputVideoSync;
+ inputModifier += GetVideoSyncOption(state.InputVideoSync, _mediaEncoder.EncoderVersion);
}
if (state.ReadInputAtNativeFramerate && state.InputProtocol != MediaProtocol.Rtsp)
@@ -6731,7 +6908,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.IsVideoRequest)
{
- if (!string.IsNullOrEmpty(state.InputContainer) && state.VideoType == VideoType.VideoFile && string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType))
+ if (!string.IsNullOrEmpty(state.InputContainer) && state.VideoType == VideoType.VideoFile && encodingOptions.HardwareAccelerationType != HardwareAccelerationType.none)
{
var inputFormat = GetInputFormat(state.InputContainer);
if (!string.IsNullOrEmpty(inputFormat))
@@ -6847,7 +7024,7 @@ namespace MediaBrowser.Controller.MediaEncoding
state.SupportedAudioCodecs = supportedAudioCodecsList.ToArray();
- request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(i => _mediaEncoder.CanEncodeToAudioCodec(i))
+ request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(_mediaEncoder.CanEncodeToAudioCodec)
?? state.SupportedAudioCodecs.FirstOrDefault();
}
@@ -6973,7 +7150,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return " -codec:s:0 " + codec + " -disposition:s:0 default";
}
- public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string defaultPreset)
+ public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, EncoderPreset defaultPreset)
{
// Get the output codec name
var videoCodec = GetVideoEncoder(state, encodingOptions);
@@ -7024,7 +7201,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Empty;
}
- public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoCodec, string defaultPreset)
+ public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoCodec, EncoderPreset defaultPreset)
{
var args = "-codec:v:0 " + videoCodec;
@@ -7065,7 +7242,7 @@ namespace MediaBrowser.Controller.MediaEncoding
args += keyFrameArg;
- var hasGraphicalSubs = state.SubtitleStream is not null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasGraphicalSubs = state.SubtitleStream is not null && !state.SubtitleStream.IsTextSubtitleStream && ShouldEncodeSubtitle(state);
var hasCopyTs = false;
@@ -7103,7 +7280,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.IsNullOrEmpty(state.OutputVideoSync))
{
- args += " -vsync " + state.OutputVideoSync;
+ args += GetVideoSyncOption(state.OutputVideoSync, _mediaEncoder.EncoderVersion);
}
args += GetOutputFFlags(state);
@@ -7270,5 +7447,39 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
}
+
+ private static bool ShouldEncodeSubtitle(EncodingJobInfo state)
+ {
+ return state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
+ || (state.BaseRequest.AlwaysBurnInSubtitleWhenTranscoding && !IsCopyCodec(state.OutputVideoCodec));
+ }
+
+ public static string GetVideoSyncOption(string videoSync, Version encoderVersion)
+ {
+ if (string.IsNullOrEmpty(videoSync))
+ {
+ return string.Empty;
+ }
+
+ if (encoderVersion >= new Version(5, 1))
+ {
+ if (int.TryParse(videoSync, CultureInfo.InvariantCulture, out var vsync))
+ {
+ return vsync switch
+ {
+ -1 => " -fps_mode auto",
+ 0 => " -fps_mode passthrough",
+ 1 => " -fps_mode cfr",
+ 2 => " -fps_mode vfr",
+ _ => string.Empty
+ };
+ }
+
+ return string.Empty;
+ }
+
+ // -vsync is deprecated in FFmpeg 5.1 and will be removed in the future.
+ return $" -vsync {videoSync}";
+ }
}
}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
index 72df7151d..caa312987 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
@@ -305,7 +305,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (BaseRequest.Static
|| EncodingHelper.IsCopyCodec(OutputVideoCodec))
{
- return VideoStream is null ? null : (VideoStream.AverageFrameRate ?? VideoStream.RealFrameRate);
+ return VideoStream?.ReferenceFrameRate;
}
return BaseRequest.MaxFramerate ?? BaseRequest.Framerate;
diff --git a/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs b/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs
index 5bf83a9e3..9bf27b3b2 100644
--- a/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs
+++ b/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs
@@ -44,5 +44,14 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>System.String.</returns>
Task<string> GetSubtitleFileCharacterSet(MediaStream subtitleStream, string language, MediaSourceInfo mediaSource, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Gets the path to a subtitle file.
+ /// </summary>
+ /// <param name="subtitleStream">The subtitle stream.</param>
+ /// <param name="mediaSource">The media source.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>System.String.</returns>
+ Task<string> GetSubtitleFilePath(MediaStream subtitleStream, MediaSourceInfo mediaSource, CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs b/MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs
index 67384f6f6..010d7edb4 100644
--- a/MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs
+++ b/MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
@@ -14,6 +15,15 @@ namespace MediaBrowser.Controller;
public interface IMediaSegmentManager
{
/// <summary>
+ /// Uses all segment providers enabled for the <see cref="BaseItem"/>'s library to get the Media Segments.
+ /// </summary>
+ /// <param name="baseItem">The Item to evaluate.</param>
+ /// <param name="overwrite">If set, will remove existing segments and replace it with new ones otherwise will check for existing segments and if found any, stops.</param>
+ /// <param name="cancellationToken">stop request token.</param>
+ /// <returns>A task that indicates the Operation is finished.</returns>
+ Task RunSegmentPluginProviders(BaseItem baseItem, bool overwrite, CancellationToken cancellationToken);
+
+ /// <summary>
/// Returns if this item supports media segments.
/// </summary>
/// <param name="baseItem">The base Item to check.</param>
@@ -50,4 +60,11 @@ public interface IMediaSegmentManager
/// <returns>True if there are any segments stored for the item, otherwise false.</returns>
/// TODO: this should be async but as the only caller BaseItem.GetVersionInfo isn't async, this is also not. Venson.
bool HasSegments(Guid itemId);
+
+ /// <summary>
+ /// Gets a list of all registered Segment Providers and their IDs.
+ /// </summary>
+ /// <param name="item">The media item that should be tested for providers.</param>
+ /// <returns>A list of all providers for the tested item.</returns>
+ IEnumerable<(string Name, string Id)> GetSupportedProviders(BaseItem item);
}
diff --git a/MediaBrowser.Controller/MediaSegements/IMediaSegmentProvider.cs b/MediaBrowser.Controller/MediaSegements/IMediaSegmentProvider.cs
new file mode 100644
index 000000000..39bb58bef
--- /dev/null
+++ b/MediaBrowser.Controller/MediaSegements/IMediaSegmentProvider.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model;
+using MediaBrowser.Model.MediaSegments;
+
+namespace MediaBrowser.Controller;
+
+/// <summary>
+/// Provides methods for Obtaining the Media Segments from an Item.
+/// </summary>
+public interface IMediaSegmentProvider
+{
+ /// <summary>
+ /// Gets the provider name.
+ /// </summary>
+ string Name { get; }
+
+ /// <summary>
+ /// Enumerates all Media Segments from an Media Item.
+ /// </summary>
+ /// <param name="request">Arguments to enumerate MediaSegments.</param>
+ /// <param name="cancellationToken">Abort token.</param>
+ /// <returns>A list of all MediaSegments found from this provider.</returns>
+ Task<IReadOnlyList<MediaSegmentDto>> GetMediaSegments(MediaSegmentGenerationRequest request, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Should return support state for the given item.
+ /// </summary>
+ /// <param name="item">The base item to extract segments from.</param>
+ /// <returns>True if item is supported, otherwise false.</returns>
+ ValueTask<bool> Supports(BaseItem item);
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SessionsMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SessionsMessage.cs
index 3504831b8..833074541 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SessionsMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SessionsMessage.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Session;
namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
@@ -8,13 +9,13 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// <summary>
/// Sessions message.
/// </summary>
-public class SessionsMessage : OutboundWebSocketMessage<IReadOnlyList<SessionInfo>>
+public class SessionsMessage : OutboundWebSocketMessage<IReadOnlyList<SessionInfoDto>>
{
/// <summary>
/// Initializes a new instance of the <see cref="SessionsMessage"/> class.
/// </summary>
/// <param name="data">Session info.</param>
- public SessionsMessage(IReadOnlyList<SessionInfo> data)
+ public SessionsMessage(IReadOnlyList<SessionInfoDto> data)
: base(data)
{
}
diff --git a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs
index 9e91a8bcd..0bab2a6b9 100644
--- a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs
+++ b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs
@@ -29,6 +29,7 @@ namespace MediaBrowser.Controller.Providers
IsAutomated = copy.IsAutomated;
ImageRefreshMode = copy.ImageRefreshMode;
ReplaceAllImages = copy.ReplaceAllImages;
+ RegenerateTrickplay = copy.RegenerateTrickplay;
ReplaceImages = copy.ReplaceImages;
SearchResult = copy.SearchResult;
RemoveOldMetadata = copy.RemoveOldMetadata;
@@ -47,6 +48,12 @@ namespace MediaBrowser.Controller.Providers
/// </summary>
public bool ReplaceAllMetadata { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether all existing trickplay images should be overwritten
+ /// when paired with MetadataRefreshMode=FullRefresh.
+ /// </summary>
+ public bool RegenerateTrickplay { get; set; }
+
public MetadataRefreshMode MetadataRefreshMode { get; set; }
public RemoteSearchResult SearchResult { get; set; }
diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs
index 5a47236f9..462a62455 100644
--- a/MediaBrowser.Controller/Session/ISessionManager.cs
+++ b/MediaBrowser.Controller/Session/ISessionManager.cs
@@ -9,6 +9,7 @@ using System.Threading.Tasks;
using Jellyfin.Data.Entities.Security;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Session;
using MediaBrowser.Model.SyncPlay;
@@ -293,6 +294,17 @@ namespace MediaBrowser.Controller.Session
SessionInfo GetSession(string deviceId, string client, string version);
/// <summary>
+ /// Gets all sessions available to a user.
+ /// </summary>
+ /// <param name="userId">The session identifier.</param>
+ /// <param name="deviceId">The device id.</param>
+ /// <param name="activeWithinSeconds">Active within session limit.</param>
+ /// <param name="controllableUserToCheck">Filter for sessions remote controllable for this user.</param>
+ /// <param name="isApiKey">Is the request authenticated with API key.</param>
+ /// <returns>IReadOnlyList{SessionInfoDto}.</returns>
+ IReadOnlyList<SessionInfoDto> GetSessions(Guid userId, string deviceId, int? activeWithinSeconds, Guid? controllableUserToCheck, bool isApiKey);
+
+ /// <summary>
/// Gets the session by authentication token.
/// </summary>
/// <param name="token">The token.</param>
diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs
index 9e3358818..3ba1bfce4 100644
--- a/MediaBrowser.Controller/Session/SessionInfo.cs
+++ b/MediaBrowser.Controller/Session/SessionInfo.cs
@@ -1,7 +1,5 @@
#nullable disable
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
using System.Linq;
@@ -27,28 +25,45 @@ namespace MediaBrowser.Controller.Session
private readonly ISessionManager _sessionManager;
private readonly ILogger _logger;
- private readonly object _progressLock = new object();
+ private readonly object _progressLock = new();
private Timer _progressTimer;
private PlaybackProgressInfo _lastProgressInfo;
- private bool _disposed = false;
+ private bool _disposed;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SessionInfo"/> class.
+ /// </summary>
+ /// <param name="sessionManager">Instance of <see cref="ISessionManager"/> interface.</param>
+ /// <param name="logger">Instance of <see cref="ILogger"/> interface.</param>
public SessionInfo(ISessionManager sessionManager, ILogger logger)
{
_sessionManager = sessionManager;
_logger = logger;
- AdditionalUsers = Array.Empty<SessionUserInfo>();
+ AdditionalUsers = [];
PlayState = new PlayerStateInfo();
- SessionControllers = Array.Empty<ISessionController>();
- NowPlayingQueue = Array.Empty<QueueItem>();
- NowPlayingQueueFullItems = Array.Empty<BaseItemDto>();
+ SessionControllers = [];
+ NowPlayingQueue = [];
+ NowPlayingQueueFullItems = [];
}
+ /// <summary>
+ /// Gets or sets the play state.
+ /// </summary>
+ /// <value>The play state.</value>
public PlayerStateInfo PlayState { get; set; }
- public SessionUserInfo[] AdditionalUsers { get; set; }
+ /// <summary>
+ /// Gets or sets the additional users.
+ /// </summary>
+ /// <value>The additional users.</value>
+ public IReadOnlyList<SessionUserInfo> AdditionalUsers { get; set; }
+ /// <summary>
+ /// Gets or sets the client capabilities.
+ /// </summary>
+ /// <value>The client capabilities.</value>
public ClientCapabilities Capabilities { get; set; }
/// <summary>
@@ -67,7 +82,7 @@ namespace MediaBrowser.Controller.Session
{
if (Capabilities is null)
{
- return Array.Empty<MediaType>();
+ return [];
}
return Capabilities.PlayableMediaTypes;
@@ -134,9 +149,17 @@ namespace MediaBrowser.Controller.Session
/// <value>The now playing item.</value>
public BaseItemDto NowPlayingItem { get; set; }
+ /// <summary>
+ /// Gets or sets the now playing queue full items.
+ /// </summary>
+ /// <value>The now playing queue full items.</value>
[JsonIgnore]
public BaseItem FullNowPlayingItem { get; set; }
+ /// <summary>
+ /// Gets or sets the now viewing item.
+ /// </summary>
+ /// <value>The now viewing item.</value>
public BaseItemDto NowViewingItem { get; set; }
/// <summary>
@@ -156,8 +179,12 @@ namespace MediaBrowser.Controller.Session
/// </summary>
/// <value>The session controller.</value>
[JsonIgnore]
- public ISessionController[] SessionControllers { get; set; }
+ public IReadOnlyList<ISessionController> SessionControllers { get; set; }
+ /// <summary>
+ /// Gets or sets the transcoding info.
+ /// </summary>
+ /// <value>The transcoding info.</value>
public TranscodingInfo TranscodingInfo { get; set; }
/// <summary>
@@ -177,7 +204,7 @@ namespace MediaBrowser.Controller.Session
}
}
- if (controllers.Length > 0)
+ if (controllers.Count > 0)
{
return false;
}
@@ -186,6 +213,10 @@ namespace MediaBrowser.Controller.Session
}
}
+ /// <summary>
+ /// Gets a value indicating whether the session supports media control.
+ /// </summary>
+ /// <value><c>true</c> if this session supports media control; otherwise, <c>false</c>.</value>
public bool SupportsMediaControl
{
get
@@ -208,6 +239,10 @@ namespace MediaBrowser.Controller.Session
}
}
+ /// <summary>
+ /// Gets a value indicating whether the session supports remote control.
+ /// </summary>
+ /// <value><c>true</c> if this session supports remote control; otherwise, <c>false</c>.</value>
public bool SupportsRemoteControl
{
get
@@ -230,16 +265,40 @@ namespace MediaBrowser.Controller.Session
}
}
+ /// <summary>
+ /// Gets or sets the now playing queue.
+ /// </summary>
+ /// <value>The now playing queue.</value>
public IReadOnlyList<QueueItem> NowPlayingQueue { get; set; }
+ /// <summary>
+ /// Gets or sets the now playing queue full items.
+ /// </summary>
+ /// <value>The now playing queue full items.</value>
public IReadOnlyList<BaseItemDto> NowPlayingQueueFullItems { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether the session has a custom device name.
+ /// </summary>
+ /// <value><c>true</c> if this session has a custom device name; otherwise, <c>false</c>.</value>
public bool HasCustomDeviceName { get; set; }
+ /// <summary>
+ /// Gets or sets the playlist item id.
+ /// </summary>
+ /// <value>The splaylist item id.</value>
public string PlaylistItemId { get; set; }
+ /// <summary>
+ /// Gets or sets the server id.
+ /// </summary>
+ /// <value>The server id.</value>
public string ServerId { get; set; }
+ /// <summary>
+ /// Gets or sets the user primary image tag.
+ /// </summary>
+ /// <value>The user primary image tag.</value>
public string UserPrimaryImageTag { get; set; }
/// <summary>
@@ -247,8 +306,14 @@ namespace MediaBrowser.Controller.Session
/// </summary>
/// <value>The supported commands.</value>
public IReadOnlyList<GeneralCommandType> SupportedCommands
- => Capabilities is null ? Array.Empty<GeneralCommandType>() : Capabilities.SupportedCommands;
+ => Capabilities is null ? [] : Capabilities.SupportedCommands;
+ /// <summary>
+ /// Ensures a controller of type exists.
+ /// </summary>
+ /// <typeparam name="T">Class to register.</typeparam>
+ /// <param name="factory">The factory.</param>
+ /// <returns>Tuple{ISessionController, bool}.</returns>
public Tuple<ISessionController, bool> EnsureController<T>(Func<SessionInfo, ISessionController> factory)
{
var controllers = SessionControllers.ToList();
@@ -261,18 +326,27 @@ namespace MediaBrowser.Controller.Session
}
var newController = factory(this);
- _logger.LogDebug("Creating new {0}", newController.GetType().Name);
+ _logger.LogDebug("Creating new {Factory}", newController.GetType().Name);
controllers.Add(newController);
- SessionControllers = controllers.ToArray();
+ SessionControllers = [.. controllers];
return new Tuple<ISessionController, bool>(newController, true);
}
+ /// <summary>
+ /// Adds a controller to the session.
+ /// </summary>
+ /// <param name="controller">The controller.</param>
public void AddController(ISessionController controller)
{
- SessionControllers = [..SessionControllers, controller];
+ SessionControllers = [.. SessionControllers, controller];
}
+ /// <summary>
+ /// Gets a value indicating whether the session contains a user.
+ /// </summary>
+ /// <param name="userId">The user id to check.</param>
+ /// <returns><c>true</c> if this session contains the user; otherwise, <c>false</c>.</returns>
public bool ContainsUser(Guid userId)
{
if (UserId.Equals(userId))
@@ -291,6 +365,11 @@ namespace MediaBrowser.Controller.Session
return false;
}
+ /// <summary>
+ /// Starts automatic progressing.
+ /// </summary>
+ /// <param name="progressInfo">The playback progress info.</param>
+ /// <value>The supported commands.</value>
public void StartAutomaticProgress(PlaybackProgressInfo progressInfo)
{
if (_disposed)
@@ -359,6 +438,9 @@ namespace MediaBrowser.Controller.Session
}
}
+ /// <summary>
+ /// Stops automatic progressing.
+ /// </summary>
public void StopAutomaticProgress()
{
lock (_progressLock)
@@ -373,6 +455,10 @@ namespace MediaBrowser.Controller.Session
}
}
+ /// <summary>
+ /// Disposes the instance async.
+ /// </summary>
+ /// <returns>ValueTask.</returns>
public async ValueTask DisposeAsync()
{
_disposed = true;
@@ -380,7 +466,7 @@ namespace MediaBrowser.Controller.Session
StopAutomaticProgress();
var controllers = SessionControllers.ToList();
- SessionControllers = Array.Empty<ISessionController>();
+ SessionControllers = [];
foreach (var controller in controllers)
{
diff --git a/MediaBrowser.Controller/Trickplay/ITrickplayManager.cs b/MediaBrowser.Controller/Trickplay/ITrickplayManager.cs
index 0c41f3023..800317800 100644
--- a/MediaBrowser.Controller/Trickplay/ITrickplayManager.cs
+++ b/MediaBrowser.Controller/Trickplay/ITrickplayManager.cs
@@ -18,9 +18,10 @@ public interface ITrickplayManager
/// </summary>
/// <param name="video">The video.</param>
/// <param name="replace">Whether or not existing data should be replaced.</param>
+ /// <param name="libraryOptions">The library options.</param>
/// <param name="cancellationToken">CancellationToken to use for operation.</param>
/// <returns>Task.</returns>
- Task RefreshTrickplayDataAsync(Video video, bool replace, CancellationToken cancellationToken);
+ Task RefreshTrickplayDataAsync(Video video, bool replace, LibraryOptions? libraryOptions, CancellationToken cancellationToken);
/// <summary>
/// Creates trickplay tiles out of individual thumbnails.
@@ -33,7 +34,7 @@ public interface ITrickplayManager
/// <remarks>
/// The output directory will be DELETED and replaced if it already exists.
/// </remarks>
- TrickplayInfo CreateTiles(List<string> images, int width, TrickplayOptions options, string outputDir);
+ TrickplayInfo CreateTiles(IReadOnlyList<string> images, int width, TrickplayOptions options, string outputDir);
/// <summary>
/// Get available trickplay resolutions and corresponding info.
@@ -43,6 +44,14 @@ public interface ITrickplayManager
Task<Dictionary<int, TrickplayInfo>> GetTrickplayResolutions(Guid itemId);
/// <summary>
+ /// Gets the item ids of all items with trickplay info.
+ /// </summary>
+ /// <param name="limit">The limit of items to return.</param>
+ /// <param name="offset">The offset to start the query at.</param>
+ /// <returns>The list of item ids that have trickplay info.</returns>
+ Task<IReadOnlyList<TrickplayInfo>> GetTrickplayItemsAsync(int limit, int offset);
+
+ /// <summary>
/// Saves trickplay info.
/// </summary>
/// <param name="info">The trickplay info.</param>
@@ -62,8 +71,29 @@ public interface ITrickplayManager
/// <param name="item">The item.</param>
/// <param name="width">The width of a single thumbnail.</param>
/// <param name="index">The tile's index.</param>
+ /// <param name="saveWithMedia">Whether or not the tile should be saved next to the media file.</param>
+ /// <returns>The absolute path.</returns>
+ Task<string> GetTrickplayTilePathAsync(BaseItem item, int width, int index, bool saveWithMedia);
+
+ /// <summary>
+ /// Gets the path to a trickplay tile image.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="tileWidth">The amount of images for the tile width.</param>
+ /// <param name="tileHeight">The amount of images for the tile height.</param>
+ /// <param name="width">The width of a single thumbnail.</param>
+ /// <param name="saveWithMedia">Whether or not the tile should be saved next to the media file.</param>
/// <returns>The absolute path.</returns>
- string GetTrickplayTilePath(BaseItem item, int width, int index);
+ string GetTrickplayDirectory(BaseItem item, int tileWidth, int tileHeight, int width, bool saveWithMedia = false);
+
+ /// <summary>
+ /// Migrates trickplay images between local and media directories.
+ /// </summary>
+ /// <param name="video">The video.</param>
+ /// <param name="libraryOptions">The library options.</param>
+ /// <param name="cancellationToken">CancellationToken to use for operation.</param>
+ /// <returns>Task.</returns>
+ Task MoveGeneratedTrickplayDataAsync(Video video, LibraryOptions? libraryOptions, CancellationToken cancellationToken);
/// <summary>
/// Gets the trickplay HLS playlist.