From 675a8a9ec91da47e37ace6161ba5a5a0e20a7839 Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Sat, 7 Sep 2024 19:22:31 +0200 Subject: Remove left-over network path references (#12446) --- MediaBrowser.Model/Configuration/MediaPathInfo.cs | 2 -- MediaBrowser.Model/Configuration/ServerConfiguration.cs | 2 -- 2 files changed, 4 deletions(-) (limited to 'MediaBrowser.Model/Configuration') diff --git a/MediaBrowser.Model/Configuration/MediaPathInfo.cs b/MediaBrowser.Model/Configuration/MediaPathInfo.cs index a7bc43590..25a5d5606 100644 --- a/MediaBrowser.Model/Configuration/MediaPathInfo.cs +++ b/MediaBrowser.Model/Configuration/MediaPathInfo.cs @@ -16,7 +16,5 @@ namespace MediaBrowser.Model.Configuration } public string Path { get; set; } - - public string? NetworkPath { get; set; } } } diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 52f7e53b8..5ad588200 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -96,8 +96,6 @@ public class ServerConfiguration : BaseApplicationConfiguration /// The metadata path. public string MetadataPath { get; set; } = string.Empty; - public string MetadataNetworkPath { get; set; } = string.Empty; - /// /// Gets or sets the preferred metadata language. /// -- cgit v1.2.3 From c56dbc1c4410e1b0ec31ca901809b6f627bbb6ed Mon Sep 17 00:00:00 2001 From: Tim Eisele Date: Sat, 7 Sep 2024 19:23:48 +0200 Subject: Enhance Trickplay (#11883) --- .../IO/ManagedFileSystem.cs | 38 +++-- .../Localization/Core/en-US.json | 4 +- Jellyfin.Api/Controllers/TrickplayController.cs | 7 +- .../Trickplay/TrickplayManager.cs | 183 +++++++++++++++++---- Jellyfin.Server/Migrations/MigrationRunner.cs | 1 + .../Migrations/Routines/MoveTrickplayFiles.cs | 73 ++++++++ .../Providers/MetadataRefreshOptions.cs | 7 + .../Trickplay/ITrickplayManager.cs | 34 +++- MediaBrowser.Model/Configuration/LibraryOptions.cs | 4 + MediaBrowser.Model/IO/IFileSystem.cs | 7 + .../Trickplay/TrickplayImagesTask.cs | 3 +- .../Trickplay/TrickplayMoveImagesTask.cs | 110 +++++++++++++ .../Trickplay/TrickplayProvider.cs | 6 +- 13 files changed, 423 insertions(+), 54 deletions(-) create mode 100644 Jellyfin.Server/Migrations/Routines/MoveTrickplayFiles.cs create mode 100644 MediaBrowser.Providers/Trickplay/TrickplayMoveImagesTask.cs (limited to 'MediaBrowser.Model/Configuration') diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 28bb29df8..4b68f21d5 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -149,6 +149,26 @@ namespace Emby.Server.Implementations.IO } } + /// + public void MoveDirectory(string source, string destination) + { + try + { + Directory.Move(source, destination); + } + catch (IOException) + { + // Cross device move requires a copy + Directory.CreateDirectory(destination); + foreach (string file in Directory.GetFiles(source)) + { + File.Copy(file, Path.Combine(destination, Path.GetFileName(file)), true); + } + + Directory.Delete(source, true); + } + } + /// /// Returns a object for the specified file or directory path. /// @@ -327,11 +347,7 @@ namespace Emby.Server.Implementations.IO } } - /// - /// Gets the creation time UTC. - /// - /// The path. - /// DateTime. + /// public virtual DateTime GetCreationTimeUtc(string path) { return GetCreationTimeUtc(GetFileSystemInfo(path)); @@ -368,11 +384,7 @@ namespace Emby.Server.Implementations.IO } } - /// - /// Gets the last write time UTC. - /// - /// The path. - /// DateTime. + /// public virtual DateTime GetLastWriteTimeUtc(string path) { return GetLastWriteTimeUtc(GetFileSystemInfo(path)); @@ -446,11 +458,7 @@ namespace Emby.Server.Implementations.IO File.SetAttributes(path, attributes); } - /// - /// Swaps the files. - /// - /// The file1. - /// The file2. + /// public virtual void SwapFiles(string file1, string file2) { ArgumentException.ThrowIfNullOrEmpty(file1); diff --git a/Emby.Server.Implementations/Localization/Core/en-US.json b/Emby.Server.Implementations/Localization/Core/en-US.json index d1410ef5e..d248fc303 100644 --- a/Emby.Server.Implementations/Localization/Core/en-US.json +++ b/Emby.Server.Implementations/Localization/Core/en-US.json @@ -131,5 +131,7 @@ "TaskKeyframeExtractor": "Keyframe Extractor", "TaskKeyframeExtractorDescription": "Extracts keyframes from video files to create more precise HLS playlists. This task may run for a long time.", "TaskCleanCollectionsAndPlaylists": "Clean up collections and playlists", - "TaskCleanCollectionsAndPlaylistsDescription": "Removes items from collections and playlists that no longer exist." + "TaskCleanCollectionsAndPlaylistsDescription": "Removes items from collections and playlists that no longer exist.", + "TaskMoveTrickplayImages": "Migrate Trickplay Image Location", + "TaskMoveTrickplayImagesDescription": "Moves existing trickplay files according to the library settings." } diff --git a/Jellyfin.Api/Controllers/TrickplayController.cs b/Jellyfin.Api/Controllers/TrickplayController.cs index 60d49af9e..c1ff0f340 100644 --- a/Jellyfin.Api/Controllers/TrickplayController.cs +++ b/Jellyfin.Api/Controllers/TrickplayController.cs @@ -80,7 +80,7 @@ public class TrickplayController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesImageFile] - public ActionResult GetTrickplayTileImage( + public async Task GetTrickplayTileImageAsync( [FromRoute, Required] Guid itemId, [FromRoute, Required] int width, [FromRoute, Required] int index, @@ -92,8 +92,9 @@ public class TrickplayController : BaseJellyfinApiController return NotFound(); } - var path = _trickplayManager.GetTrickplayTilePath(item, width, index); - if (System.IO.File.Exists(path)) + var saveWithMedia = _libraryManager.GetLibraryOptions(item).SaveTrickplayWithMedia; + var path = await _trickplayManager.GetTrickplayTilePathAsync(item, width, index, saveWithMedia).ConfigureAwait(false); + if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path)) { Response.Headers.ContentDisposition = "attachment"; return PhysicalFile(path, MediaTypeNames.Image.Jpeg); diff --git a/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs b/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs index bb32b7c20..861037c1f 100644 --- a/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs +++ b/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs @@ -76,7 +76,65 @@ public class TrickplayManager : ITrickplayManager } /// - public async Task RefreshTrickplayDataAsync(Video video, bool replace, CancellationToken cancellationToken) + public async Task MoveGeneratedTrickplayDataAsync(Video video, LibraryOptions? libraryOptions, CancellationToken cancellationToken) + { + var options = _config.Configuration.TrickplayOptions; + if (!CanGenerateTrickplay(video, options.Interval)) + { + return; + } + + var existingTrickplayResolutions = await GetTrickplayResolutions(video.Id).ConfigureAwait(false); + foreach (var resolution in existingTrickplayResolutions) + { + cancellationToken.ThrowIfCancellationRequested(); + var existingResolution = resolution.Key; + var tileWidth = resolution.Value.TileWidth; + var tileHeight = resolution.Value.TileHeight; + var shouldBeSavedWithMedia = libraryOptions is null ? false : libraryOptions.SaveTrickplayWithMedia; + var localOutputDir = GetTrickplayDirectory(video, tileWidth, tileHeight, existingResolution, false); + var mediaOutputDir = GetTrickplayDirectory(video, tileWidth, tileHeight, existingResolution, true); + if (shouldBeSavedWithMedia && Directory.Exists(localOutputDir)) + { + var localDirFiles = Directory.GetFiles(localOutputDir); + var mediaDirExists = Directory.Exists(mediaOutputDir); + if (localDirFiles.Length > 0 && ((mediaDirExists && Directory.GetFiles(mediaOutputDir).Length == 0) || !mediaDirExists)) + { + // Move images from local dir to media dir + MoveContent(localOutputDir, mediaOutputDir); + _logger.LogInformation("Moved trickplay images for {ItemName} to {Location}", video.Name, mediaOutputDir); + } + } + else if (Directory.Exists(mediaOutputDir)) + { + var mediaDirFiles = Directory.GetFiles(mediaOutputDir); + var localDirExists = Directory.Exists(localOutputDir); + if (mediaDirFiles.Length > 0 && ((localDirExists && Directory.GetFiles(localOutputDir).Length == 0) || !localDirExists)) + { + // Move images from media dir to local dir + MoveContent(mediaOutputDir, localOutputDir); + _logger.LogInformation("Moved trickplay images for {ItemName} to {Location}", video.Name, localOutputDir); + } + } + } + } + + private void MoveContent(string sourceFolder, string destinationFolder) + { + _fileSystem.MoveDirectory(sourceFolder, destinationFolder); + var parent = Directory.GetParent(sourceFolder); + if (parent is not null) + { + var parentContent = Directory.GetDirectories(parent.FullName); + if (parentContent.Length == 0) + { + Directory.Delete(parent.FullName); + } + } + } + + /// + public async Task RefreshTrickplayDataAsync(Video video, bool replace, LibraryOptions? libraryOptions, CancellationToken cancellationToken) { _logger.LogDebug("Trickplay refresh for {ItemId} (replace existing: {Replace})", video.Id, replace); @@ -95,6 +153,7 @@ public class TrickplayManager : ITrickplayManager replace, width, options, + libraryOptions, cancellationToken).ConfigureAwait(false); } } @@ -104,6 +163,7 @@ public class TrickplayManager : ITrickplayManager bool replace, int width, TrickplayOptions options, + LibraryOptions? libraryOptions, CancellationToken cancellationToken) { if (!CanGenerateTrickplay(video, options.Interval)) @@ -144,14 +204,53 @@ public class TrickplayManager : ITrickplayManager actualWidth = 2 * ((int)mediaSource.VideoStream.Width / 2); } - var outputDir = GetTrickplayDirectory(video, actualWidth); + var tileWidth = options.TileWidth; + var tileHeight = options.TileHeight; + var saveWithMedia = libraryOptions is null ? false : libraryOptions.SaveTrickplayWithMedia; + var outputDir = GetTrickplayDirectory(video, tileWidth, tileHeight, actualWidth, saveWithMedia); - if (!replace && Directory.Exists(outputDir) && (await GetTrickplayResolutions(video.Id).ConfigureAwait(false)).ContainsKey(actualWidth)) + // Import existing trickplay tiles + if (!replace && Directory.Exists(outputDir)) { - _logger.LogDebug("Found existing trickplay files for {ItemId}. Exiting", video.Id); - return; + var existingFiles = Directory.GetFiles(outputDir); + if (existingFiles.Length > 0) + { + var hasTrickplayResolution = await HasTrickplayResolutionAsync(video.Id, actualWidth).ConfigureAwait(false); + if (hasTrickplayResolution) + { + _logger.LogDebug("Found existing trickplay files for {ItemId}.", video.Id); + return; + } + + // Import tiles + var localTrickplayInfo = new TrickplayInfo + { + ItemId = video.Id, + Width = width, + Interval = options.Interval, + TileWidth = options.TileWidth, + TileHeight = options.TileHeight, + ThumbnailCount = existingFiles.Length, + Height = 0, + Bandwidth = 0 + }; + + foreach (var tile in existingFiles) + { + var image = _imageEncoder.GetImageSize(tile); + localTrickplayInfo.Height = Math.Max(localTrickplayInfo.Height, image.Height); + var bitrate = (int)Math.Ceiling((decimal)new FileInfo(tile).Length * 8 / localTrickplayInfo.TileWidth / localTrickplayInfo.TileHeight / (localTrickplayInfo.Interval / 1000)); + localTrickplayInfo.Bandwidth = Math.Max(localTrickplayInfo.Bandwidth, bitrate); + } + + await SaveTrickplayInfo(localTrickplayInfo).ConfigureAwait(false); + + _logger.LogDebug("Imported existing trickplay files for {ItemId}.", video.Id); + return; + } } + // Generate trickplay tiles var mediaStream = mediaSource.VideoStream; var container = mediaSource.Container; @@ -224,7 +323,7 @@ public class TrickplayManager : ITrickplayManager } /// - public TrickplayInfo CreateTiles(List images, int width, TrickplayOptions options, string outputDir) + public TrickplayInfo CreateTiles(IReadOnlyList images, int width, TrickplayOptions options, string outputDir) { if (images.Count == 0) { @@ -264,7 +363,7 @@ public class TrickplayManager : ITrickplayManager var tilePath = Path.Combine(workDir, $"{i}.jpg"); imageOptions.OutputPath = tilePath; - imageOptions.InputPaths = images.GetRange(i * thumbnailsPerTile, Math.Min(thumbnailsPerTile, images.Count - (i * thumbnailsPerTile))); + imageOptions.InputPaths = images.Skip(i * thumbnailsPerTile).Take(Math.Min(thumbnailsPerTile, images.Count - (i * thumbnailsPerTile))).ToList(); // Generate image and use returned height for tiles info var height = _imageEncoder.CreateTrickplayTile(imageOptions, options.JpegQuality, trickplayInfo.Width, trickplayInfo.Height != 0 ? trickplayInfo.Height : null); @@ -289,7 +388,7 @@ public class TrickplayManager : ITrickplayManager Directory.Delete(outputDir, true); } - MoveDirectory(workDir, outputDir); + _fileSystem.MoveDirectory(workDir, outputDir); return trickplayInfo; } @@ -355,6 +454,24 @@ public class TrickplayManager : ITrickplayManager return trickplayResolutions; } + /// + public async Task> GetTrickplayItemsAsync() + { + List trickplayItems; + + var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + trickplayItems = await dbContext.TrickplayInfos + .AsNoTracking() + .Select(i => i.ItemId) + .ToListAsync() + .ConfigureAwait(false); + } + + return trickplayItems; + } + /// public async Task SaveTrickplayInfo(TrickplayInfo info) { @@ -392,9 +509,15 @@ public class TrickplayManager : ITrickplayManager } /// - public string GetTrickplayTilePath(BaseItem item, int width, int index) + public async Task GetTrickplayTilePathAsync(BaseItem item, int width, int index, bool saveWithMedia) { - return Path.Combine(GetTrickplayDirectory(item, width), index + ".jpg"); + var trickplayResolutions = await GetTrickplayResolutions(item.Id).ConfigureAwait(false); + if (trickplayResolutions is not null && trickplayResolutions.TryGetValue(width, out var trickplayInfo)) + { + return Path.Combine(GetTrickplayDirectory(item, trickplayInfo.TileWidth, trickplayInfo.TileHeight, width, saveWithMedia), index + ".jpg"); + } + + return string.Empty; } /// @@ -470,29 +593,33 @@ public class TrickplayManager : ITrickplayManager return null; } - private string GetTrickplayDirectory(BaseItem item, int? width = null) + /// + public string GetTrickplayDirectory(BaseItem item, int tileWidth, int tileHeight, int width, bool saveWithMedia = false) { - var path = Path.Combine(item.GetInternalMetadataPath(), "trickplay"); - - return width.HasValue ? Path.Combine(path, width.Value.ToString(CultureInfo.InvariantCulture)) : path; + var path = saveWithMedia + ? Path.Combine(item.ContainingFolderPath, Path.ChangeExtension(item.Path, ".trickplay")) + : Path.Combine(item.GetInternalMetadataPath(), "trickplay"); + + var subdirectory = string.Format( + CultureInfo.InvariantCulture, + "{0} - {1}x{2}", + width.ToString(CultureInfo.InvariantCulture), + tileWidth.ToString(CultureInfo.InvariantCulture), + tileHeight.ToString(CultureInfo.InvariantCulture)); + + return Path.Combine(path, subdirectory); } - private void MoveDirectory(string source, string destination) + private async Task HasTrickplayResolutionAsync(Guid itemId, int width) { - try - { - Directory.Move(source, destination); - } - catch (IOException) + var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) { - // Cross device move requires a copy - Directory.CreateDirectory(destination); - foreach (string file in Directory.GetFiles(source)) - { - File.Copy(file, Path.Join(destination, Path.GetFileName(file)), true); - } - - Directory.Delete(source, true); + return await dbContext.TrickplayInfos + .AsNoTracking() + .Where(i => i.ItemId.Equals(itemId)) + .AnyAsync(i => i.Width == width) + .ConfigureAwait(false); } } } diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 81fecc9a1..8682f28e0 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -46,6 +46,7 @@ namespace Jellyfin.Server.Migrations typeof(Routines.AddDefaultCastReceivers), typeof(Routines.UpdateDefaultPluginRepository), typeof(Routines.FixAudioData), + typeof(Routines.MoveTrickplayFiles) }; /// diff --git a/Jellyfin.Server/Migrations/Routines/MoveTrickplayFiles.cs b/Jellyfin.Server/Migrations/Routines/MoveTrickplayFiles.cs new file mode 100644 index 000000000..e8abee95a --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/MoveTrickplayFiles.cs @@ -0,0 +1,73 @@ +using System; +using System.Globalization; +using System.IO; +using DiscUtils; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Trickplay; + +namespace Jellyfin.Server.Migrations.Routines; + +/// +/// Migration to move trickplay files to the new directory. +/// +public class MoveTrickplayFiles : IMigrationRoutine +{ + private readonly ITrickplayManager _trickplayManager; + private readonly IFileSystem _fileSystem; + private readonly ILibraryManager _libraryManager; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public MoveTrickplayFiles(ITrickplayManager trickplayManager, IFileSystem fileSystem, ILibraryManager libraryManager) + { + _trickplayManager = trickplayManager; + _fileSystem = fileSystem; + _libraryManager = libraryManager; + } + + /// + public Guid Id => new("4EF123D5-8EFF-4B0B-869D-3AED07A60E1B"); + + /// + public string Name => "MoveTrickplayFiles"; + + /// + public bool PerformOnNewInstall => true; + + /// + public void Perform() + { + var trickplayItems = _trickplayManager.GetTrickplayItemsAsync().GetAwaiter().GetResult(); + foreach (var itemId in trickplayItems) + { + var resolutions = _trickplayManager.GetTrickplayResolutions(itemId).GetAwaiter().GetResult(); + var item = _libraryManager.GetItemById(itemId); + if (item is null) + { + continue; + } + + foreach (var resolution in resolutions) + { + var oldPath = GetOldTrickplayDirectory(item, resolution.Key); + var newPath = _trickplayManager.GetTrickplayDirectory(item, resolution.Value.TileWidth, resolution.Value.TileHeight, resolution.Value.Width, false); + if (_fileSystem.DirectoryExists(oldPath)) + { + _fileSystem.MoveDirectory(oldPath, newPath); + } + } + } + } + + private string GetOldTrickplayDirectory(BaseItem item, int? width = null) + { + var path = Path.Combine(item.GetInternalMetadataPath(), "trickplay"); + + return width.HasValue ? Path.Combine(path, width.Value.ToString(CultureInfo.InvariantCulture)) : path; + } +} 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 /// public bool ReplaceAllMetadata { get; set; } + /// + /// Gets or sets a value indicating whether all existing trickplay images should be overwritten + /// when paired with MetadataRefreshMode=FullRefresh. + /// + public bool RegenerateTrickplay { get; set; } + public MetadataRefreshMode MetadataRefreshMode { get; set; } public RemoteSearchResult SearchResult { get; set; } diff --git a/MediaBrowser.Controller/Trickplay/ITrickplayManager.cs b/MediaBrowser.Controller/Trickplay/ITrickplayManager.cs index 0c41f3023..bda794aa6 100644 --- a/MediaBrowser.Controller/Trickplay/ITrickplayManager.cs +++ b/MediaBrowser.Controller/Trickplay/ITrickplayManager.cs @@ -18,9 +18,10 @@ public interface ITrickplayManager /// /// The video. /// Whether or not existing data should be replaced. + /// The library options. /// CancellationToken to use for operation. /// Task. - Task RefreshTrickplayDataAsync(Video video, bool replace, CancellationToken cancellationToken); + Task RefreshTrickplayDataAsync(Video video, bool replace, LibraryOptions? libraryOptions, CancellationToken cancellationToken); /// /// Creates trickplay tiles out of individual thumbnails. @@ -33,7 +34,7 @@ public interface ITrickplayManager /// /// The output directory will be DELETED and replaced if it already exists. /// - TrickplayInfo CreateTiles(List images, int width, TrickplayOptions options, string outputDir); + TrickplayInfo CreateTiles(IReadOnlyList images, int width, TrickplayOptions options, string outputDir); /// /// Get available trickplay resolutions and corresponding info. @@ -42,6 +43,12 @@ public interface ITrickplayManager /// Map of width resolutions to trickplay tiles info. Task> GetTrickplayResolutions(Guid itemId); + /// + /// Gets the item ids of all items with trickplay info. + /// + /// The list of item ids that have trickplay info. + public Task> GetTrickplayItemsAsync(); + /// /// Saves trickplay info. /// @@ -62,8 +69,29 @@ public interface ITrickplayManager /// The item. /// The width of a single thumbnail. /// The tile's index. + /// Whether or not the tile should be saved next to the media file. + /// The absolute path. + Task GetTrickplayTilePathAsync(BaseItem item, int width, int index, bool saveWithMedia); + + /// + /// Gets the path to a trickplay tile image. + /// + /// The item. + /// The amount of images for the tile width. + /// The amount of images for the tile height. + /// The width of a single thumbnail. + /// Whether or not the tile should be saved next to the media file. /// The absolute path. - string GetTrickplayTilePath(BaseItem item, int width, int index); + string GetTrickplayDirectory(BaseItem item, int tileWidth, int tileHeight, int width, bool saveWithMedia = false); + + /// + /// Migrates trickplay images between local and media directories. + /// + /// The video. + /// The library options. + /// CancellationToken to use for operation. + /// Task. + Task MoveGeneratedTrickplayDataAsync(Video video, LibraryOptions? libraryOptions, CancellationToken cancellationToken); /// /// Gets the trickplay HLS playlist. diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs index b0f5c2a11..688a6418d 100644 --- a/MediaBrowser.Model/Configuration/LibraryOptions.cs +++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs @@ -24,6 +24,7 @@ namespace MediaBrowser.Model.Configuration EnablePhotos = true; SaveSubtitlesWithMedia = true; SaveLyricsWithMedia = false; + SaveTrickplayWithMedia = false; PathInfos = Array.Empty(); EnableAutomaticSeriesGrouping = true; SeasonZeroDisplayName = "Specials"; @@ -99,6 +100,9 @@ namespace MediaBrowser.Model.Configuration [DefaultValue(false)] public bool SaveLyricsWithMedia { get; set; } + [DefaultValue(false)] + public bool SaveTrickplayWithMedia { get; set; } + public string[] DisabledLyricFetchers { get; set; } public string[] LyricFetcherOrder { get; set; } diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs index ec381d423..2085328dd 100644 --- a/MediaBrowser.Model/IO/IFileSystem.cs +++ b/MediaBrowser.Model/IO/IFileSystem.cs @@ -33,6 +33,13 @@ namespace MediaBrowser.Model.IO string MakeAbsolutePath(string folderPath, string filePath); + /// + /// Moves a directory to a new location. + /// + /// Source directory. + /// Destination directory. + void MoveDirectory(string source, string destination); + /// /// Returns a object for the specified file or directory path. /// diff --git a/MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs b/MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs index 90c2ff8dd..31c0eeb31 100644 --- a/MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs +++ b/MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs @@ -98,7 +98,8 @@ public class TrickplayImagesTask : IScheduledTask try { - await _trickplayManager.RefreshTrickplayDataAsync(video, false, cancellationToken).ConfigureAwait(false); + var libraryOptions = _libraryManager.GetLibraryOptions(video); + await _trickplayManager.RefreshTrickplayDataAsync(video, false, libraryOptions, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { diff --git a/MediaBrowser.Providers/Trickplay/TrickplayMoveImagesTask.cs b/MediaBrowser.Providers/Trickplay/TrickplayMoveImagesTask.cs new file mode 100644 index 000000000..c581fd26c --- /dev/null +++ b/MediaBrowser.Providers/Trickplay/TrickplayMoveImagesTask.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Trickplay; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; + +namespace MediaBrowser.Providers.Trickplay; + +/// +/// Class TrickplayMoveImagesTask. +/// +public class TrickplayMoveImagesTask : IScheduledTask +{ + private const int QueryPageLimit = 100; + + private readonly ILogger _logger; + private readonly ILibraryManager _libraryManager; + private readonly ILocalizationManager _localization; + private readonly ITrickplayManager _trickplayManager; + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The library manager. + /// The localization manager. + /// The trickplay manager. + public TrickplayMoveImagesTask( + ILogger logger, + ILibraryManager libraryManager, + ILocalizationManager localization, + ITrickplayManager trickplayManager) + { + _libraryManager = libraryManager; + _logger = logger; + _localization = localization; + _trickplayManager = trickplayManager; + } + + /// + public string Name => _localization.GetLocalizedString("TaskMoveTrickplayImages"); + + /// + public string Description => _localization.GetLocalizedString("TaskMoveTrickplayImagesDescription"); + + /// + public string Key => "MoveTrickplayImages"; + + /// + public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); + + /// + public IEnumerable GetDefaultTriggers() => []; + + /// + public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) + { + var trickplayItems = await _trickplayManager.GetTrickplayItemsAsync().ConfigureAwait(false); + var query = new InternalItemsQuery + { + MediaTypes = [MediaType.Video], + SourceTypes = [SourceType.Library], + IsVirtualItem = false, + IsFolder = false, + Recursive = true, + Limit = QueryPageLimit + }; + + var numberOfVideos = _libraryManager.GetCount(query); + + var startIndex = 0; + var numComplete = 0; + + while (startIndex < numberOfVideos) + { + query.StartIndex = startIndex; + var videos = _libraryManager.GetItemList(query).OfType public class MediaSegmentManager : IMediaSegmentManager { + private readonly ILogger _logger; private readonly IDbContextFactory _dbProvider; + private readonly IMediaSegmentProvider[] _segmentProviders; + private readonly ILibraryManager _libraryManager; /// /// Initializes a new instance of the class. /// + /// Logger. /// EFCore Database factory. - public MediaSegmentManager(IDbContextFactory dbProvider) + /// List of all media segment providers. + /// Library manager. + public MediaSegmentManager( + ILogger logger, + IDbContextFactory dbProvider, + IEnumerable segmentProviders, + ILibraryManager libraryManager) { + _logger = logger; _dbProvider = dbProvider; + + _segmentProviders = segmentProviders + .OrderBy(i => i is IHasOrder hasOrder ? hasOrder.Order : 0) + .ToArray(); + _libraryManager = libraryManager; + } + + /// + public async Task RunSegmentPluginProviders(BaseItem baseItem, bool overwrite, CancellationToken cancellationToken) + { + var libraryOptions = _libraryManager.GetLibraryOptions(baseItem); + var providers = _segmentProviders + .Where(e => !libraryOptions.DisabledMediaSegmentProviders.Contains(GetProviderId(e.Name))) + .OrderBy(i => + { + var index = libraryOptions.MediaSegmentProvideOrder.IndexOf(i.Name); + return index == -1 ? int.MaxValue : index; + }) + .ToList(); + + _logger.LogInformation("Start media segment extraction from providers with {CountProviders} enabled", providers.Count); + using var db = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false); + + if (!overwrite && (await db.MediaSegments.AnyAsync(e => e.ItemId.Equals(baseItem.Id), cancellationToken).ConfigureAwait(false))) + { + _logger.LogInformation("Skip {MediaPath} as it already contains media segments", baseItem.Path); + return; + } + + _logger.LogInformation("Clear existing Segments for {MediaPath}", baseItem.Path); + + await db.MediaSegments.Where(e => e.ItemId.Equals(baseItem.Id)).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false); + + // no need to recreate the request object every time. + var requestItem = new MediaSegmentGenerationRequest() { ItemId = baseItem.Id }; + + foreach (var provider in providers) + { + if (!await provider.Supports(baseItem).ConfigureAwait(false)) + { + _logger.LogDebug("Media Segment provider {ProviderName} does not support item with path {Path}", provider.Name, baseItem.Path); + continue; + } + + _logger.LogDebug("Run Media Segment provider {ProviderName}", provider.Name); + try + { + var segments = await provider.GetMediaSegments(requestItem, cancellationToken) + .ConfigureAwait(false); + + _logger.LogInformation("Media Segment provider {ProviderName} found {CountSegments} for {MediaPath}", provider.Name, segments.Count, baseItem.Path); + var providerId = GetProviderId(provider.Name); + foreach (var segment in segments) + { + segment.ItemId = baseItem.Id; + await CreateSegmentAsync(segment, providerId).ConfigureAwait(false); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Provider {ProviderName} failed to extract segments from {MediaPath}", provider.Name, baseItem.Path); + } + } } /// @@ -103,4 +186,21 @@ public class MediaSegmentManager : IMediaSegmentManager { return baseItem.MediaType is Data.Enums.MediaType.Video or Data.Enums.MediaType.Audio; } + + /// + public IEnumerable<(string Name, string Id)> GetSupportedProviders(BaseItem item) + { + if (item is not (Video or Audio)) + { + return []; + } + + return _segmentProviders + .Select(p => (p.Name, GetProviderId(p.Name))); + } + + private string GetProviderId(string name) + => name.ToLowerInvariant() + .GetMD5() + .ToString("N", CultureInfo.InvariantCulture); } 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; @@ -13,6 +14,15 @@ namespace MediaBrowser.Controller; /// public interface IMediaSegmentManager { + /// + /// Uses all segment providers enabled for the 's library to get the Media Segments. + /// + /// The Item to evaluate. + /// If set, will remove existing segments and replace it with new ones otherwise will check for existing segments and if found any, stops. + /// stop request token. + /// A task that indicates the Operation is finished. + Task RunSegmentPluginProviders(BaseItem baseItem, bool overwrite, CancellationToken cancellationToken); + /// /// Returns if this item supports media segments. /// @@ -50,4 +60,11 @@ public interface IMediaSegmentManager /// True if there are any segments stored for the item, otherwise false. /// TODO: this should be async but as the only caller BaseItem.GetVersionInfo isn't async, this is also not. Venson. bool HasSegments(Guid itemId); + + /// + /// Gets a list of all registered Segment Providers and their IDs. + /// + /// The media item that should be tested for providers. + /// A list of all providers for the tested item. + 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; + +/// +/// Provides methods for Obtaining the Media Segments from an Item. +/// +public interface IMediaSegmentProvider +{ + /// + /// Gets the provider name. + /// + string Name { get; } + + /// + /// Enumerates all Media Segments from an Media Item. + /// + /// Arguments to enumerate MediaSegments. + /// Abort token. + /// A list of all MediaSegments found from this provider. + Task> GetMediaSegments(MediaSegmentGenerationRequest request, CancellationToken cancellationToken); + + /// + /// Should return support state for the given item. + /// + /// The base item to extract segments from. + /// True if item is supported, otherwise false. + ValueTask Supports(BaseItem item); +} diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs index 688a6418d..90ac377f4 100644 --- a/MediaBrowser.Model/Configuration/LibraryOptions.cs +++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs @@ -11,6 +11,8 @@ namespace MediaBrowser.Model.Configuration { TypeOptions = Array.Empty(); DisabledSubtitleFetchers = Array.Empty(); + DisabledMediaSegmentProviders = Array.Empty(); + MediaSegmentProvideOrder = Array.Empty(); SubtitleFetcherOrder = Array.Empty(); DisabledLocalMetadataReaders = Array.Empty(); DisabledLyricFetchers = Array.Empty(); @@ -87,6 +89,10 @@ namespace MediaBrowser.Model.Configuration public string[] SubtitleFetcherOrder { get; set; } + public string[] DisabledMediaSegmentProviders { get; set; } + + public string[] MediaSegmentProvideOrder { get; set; } + public bool SkipSubtitlesIfEmbeddedSubtitlesPresent { get; set; } public bool SkipSubtitlesIfAudioTrackMatches { get; set; } diff --git a/MediaBrowser.Model/Configuration/MetadataPluginType.cs b/MediaBrowser.Model/Configuration/MetadataPluginType.cs index ef303726d..670d6e383 100644 --- a/MediaBrowser.Model/Configuration/MetadataPluginType.cs +++ b/MediaBrowser.Model/Configuration/MetadataPluginType.cs @@ -14,6 +14,7 @@ namespace MediaBrowser.Model.Configuration MetadataFetcher, MetadataSaver, SubtitleFetcher, - LyricFetcher + LyricFetcher, + MediaSegmentProvider } } diff --git a/MediaBrowser.Model/MediaSegments/MediaSegmentGenerationRequest.cs b/MediaBrowser.Model/MediaSegments/MediaSegmentGenerationRequest.cs new file mode 100644 index 000000000..8c1f44de8 --- /dev/null +++ b/MediaBrowser.Model/MediaSegments/MediaSegmentGenerationRequest.cs @@ -0,0 +1,14 @@ +using System; + +namespace MediaBrowser.Model; + +/// +/// Model containing the arguments for enumerating the requested media item. +/// +public record MediaSegmentGenerationRequest +{ + /// + /// Gets the Id to the BaseItem the segments should be extracted from. + /// + public Guid ItemId { get; init; } +} diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 60d89a51b..81a9af68b 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -62,7 +62,7 @@ namespace MediaBrowser.Providers.Manager private readonly CancellationTokenSource _disposeCancellationTokenSource = new(); private readonly PriorityQueue<(Guid ItemId, MetadataRefreshOptions RefreshOptions), RefreshPriority> _refreshQueue = new(); private readonly IMemoryCache _memoryCache; - + private readonly IMediaSegmentManager _mediaSegmentManager; private readonly AsyncKeyedLocker _imageSaveLock = new(o => { o.PoolSize = 20; @@ -92,6 +92,7 @@ namespace MediaBrowser.Providers.Manager /// The BaseItem manager. /// The lyric manager. /// The memory cache. + /// The media segment manager. public ProviderManager( IHttpClientFactory httpClientFactory, ISubtitleManager subtitleManager, @@ -103,7 +104,8 @@ namespace MediaBrowser.Providers.Manager ILibraryManager libraryManager, IBaseItemManager baseItemManager, ILyricManager lyricManager, - IMemoryCache memoryCache) + IMemoryCache memoryCache, + IMediaSegmentManager mediaSegmentManager) { _logger = logger; _httpClientFactory = httpClientFactory; @@ -116,6 +118,7 @@ namespace MediaBrowser.Providers.Manager _baseItemManager = baseItemManager; _lyricManager = lyricManager; _memoryCache = memoryCache; + _mediaSegmentManager = mediaSegmentManager; } /// @@ -572,6 +575,14 @@ namespace MediaBrowser.Providers.Manager Type = MetadataPluginType.LyricFetcher })); + // Media segment providers + var mediaSegmentProviders = _mediaSegmentManager.GetSupportedProviders(dummy); + pluginList.AddRange(mediaSegmentProviders.Select(i => new MetadataPlugin + { + Name = i.Name, + Type = MetadataPluginType.MediaSegmentProvider + })); + summary.Plugins = pluginList.ToArray(); var supportedImageTypes = imageProviders.OfType() diff --git a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs index cced2b1e2..c227883b5 100644 --- a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs +++ b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs @@ -574,7 +574,8 @@ namespace Jellyfin.Providers.Tests.Manager libraryManager.Object, baseItemManager!, Mock.Of(), - Mock.Of()); + Mock.Of(), + Mock.Of()); return providerManager; } -- cgit v1.2.3 From c6de7225b9f493bb18f5e7362d785ac8a71e5f32 Mon Sep 17 00:00:00 2001 From: gnattu Date: Sun, 8 Sep 2024 11:10:59 +0800 Subject: Add non-standard multi-value audio tag support (#12385) --- MediaBrowser.Model/Configuration/LibraryOptions.cs | 18 +++++++ .../MediaInfo/AudioFileProber.cs | 61 +++++++++++++++++++++- 2 files changed, 77 insertions(+), 2 deletions(-) (limited to 'MediaBrowser.Model/Configuration') diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs index 90ac377f4..04283cc9e 100644 --- a/MediaBrowser.Model/Configuration/LibraryOptions.cs +++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs @@ -7,6 +7,8 @@ namespace MediaBrowser.Model.Configuration { public class LibraryOptions { + private static readonly char[] _defaultTagDelimiters = ['/', '|', ';', '\\']; + public LibraryOptions() { TypeOptions = Array.Empty(); @@ -30,6 +32,11 @@ namespace MediaBrowser.Model.Configuration PathInfos = Array.Empty(); EnableAutomaticSeriesGrouping = true; SeasonZeroDisplayName = "Specials"; + + PreferNonstandardArtistsTag = false; + UseCustomTagDelimiters = false; + CustomTagDelimiters = _defaultTagDelimiters; + DelimiterWhitelist = Array.Empty(); } public bool Enabled { get; set; } = true; @@ -113,6 +120,17 @@ namespace MediaBrowser.Model.Configuration public string[] LyricFetcherOrder { get; set; } + [DefaultValue(false)] + public bool PreferNonstandardArtistsTag { get; set; } + + [DefaultValue(false)] + public bool UseCustomTagDelimiters { get; set; } + + [DefaultValue(typeof(LibraryOptions), nameof(_defaultTagDelimiters))] + public char[] CustomTagDelimiters { get; set; } + + public string[] DelimiterWhitelist { get; set; } + public bool AutomaticallyAddToCollection { get; set; } public EmbeddedSubtitleOptions AllowEmbeddedSubtitles { get; set; } diff --git a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs index 7e0773b6d..51ac558b8 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs @@ -6,7 +6,6 @@ using System.Threading; using System.Threading.Tasks; using ATL; using Jellyfin.Data.Enums; -using Jellyfin.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; @@ -158,6 +157,7 @@ namespace MediaBrowser.Providers.MediaInfo /// Whether to extract embedded lyrics to lrc file. private async Task FetchDataFromTags(Audio audio, Model.MediaInfo.MediaInfo mediaInfo, MetadataRefreshOptions options, bool tryExtractEmbeddedLyrics) { + var libraryOptions = _libraryManager.GetLibraryOptions(audio); Track track = new Track(audio.Path); // ATL will fall back to filename as title when it does not understand the metadata @@ -175,6 +175,12 @@ namespace MediaBrowser.Providers.MediaInfo { var people = new List(); var albumArtists = string.IsNullOrEmpty(track.AlbumArtist) ? mediaInfo.AlbumArtists : track.AlbumArtist.Split(InternalValueSeparator); + + if (libraryOptions.UseCustomTagDelimiters) + { + albumArtists = albumArtists.SelectMany(a => SplitWithCustomDelimiter(a, libraryOptions.CustomTagDelimiters, libraryOptions.DelimiterWhitelist)).ToArray(); + } + foreach (var albumArtist in albumArtists) { if (!string.IsNullOrEmpty(albumArtist)) @@ -187,7 +193,26 @@ namespace MediaBrowser.Providers.MediaInfo } } - var performers = string.IsNullOrEmpty(track.Artist) ? mediaInfo.Artists : track.Artist.Split(InternalValueSeparator); + string[]? performers = null; + if (libraryOptions.PreferNonstandardArtistsTag) + { + track.AdditionalFields.TryGetValue("ARTISTS", out var artistsTagString); + if (artistsTagString is not null) + { + performers = artistsTagString.Split(InternalValueSeparator); + } + } + + if (performers is null || performers.Length == 0) + { + performers = string.IsNullOrEmpty(track.Artist) ? mediaInfo.Artists : track.Artist.Split(InternalValueSeparator); + } + + if (libraryOptions.UseCustomTagDelimiters) + { + performers = performers.SelectMany(p => SplitWithCustomDelimiter(p, libraryOptions.CustomTagDelimiters, libraryOptions.DelimiterWhitelist)).ToArray(); + } + foreach (var performer in performers) { if (!string.IsNullOrEmpty(performer)) @@ -285,6 +310,12 @@ namespace MediaBrowser.Providers.MediaInfo if (!audio.LockedFields.Contains(MetadataField.Genres)) { var genres = string.IsNullOrEmpty(track.Genre) ? mediaInfo.Genres : track.Genre.Split(InternalValueSeparator).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); + + if (libraryOptions.UseCustomTagDelimiters) + { + genres = genres.SelectMany(g => SplitWithCustomDelimiter(g, libraryOptions.CustomTagDelimiters, libraryOptions.DelimiterWhitelist)).ToArray(); + } + audio.Genres = options.ReplaceAllMetadata || audio.Genres == null || audio.Genres.Length == 0 ? genres : audio.Genres; @@ -379,5 +410,31 @@ namespace MediaBrowser.Providers.MediaInfo currentStreams.Add(externalLyricFiles[0]); } } + + private List SplitWithCustomDelimiter(string val, char[] tagDelimiters, string[] whitelist) + { + var items = new List(); + var temp = val; + foreach (var whitelistItem in whitelist) + { + if (string.IsNullOrWhiteSpace(whitelistItem)) + { + continue; + } + + var originalTemp = temp; + temp = temp.Replace(whitelistItem, string.Empty, StringComparison.OrdinalIgnoreCase); + + if (!string.Equals(temp, originalTemp, StringComparison.OrdinalIgnoreCase)) + { + items.Add(whitelistItem); + } + } + + var items2 = temp.Split(tagDelimiters, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).DistinctNames(); + items.AddRange(items2); + + return items; + } } } -- cgit v1.2.3 From 0d85af019c6ebc855ab2d8e689abe72b995225b4 Mon Sep 17 00:00:00 2001 From: Tim Eisele Date: Mon, 9 Sep 2024 16:43:37 +0200 Subject: Use enums for encoding options (#12561) --- Jellyfin.Api/Controllers/DynamicHlsController.cs | 4 +- Jellyfin.Api/Controllers/VideosController.cs | 2 +- Jellyfin.Server/Migrations/MigrationRunner.cs | 3 +- .../CreateNetworkConfiguration.cs | 1 - .../PreStartupRoutines/MigrateEncodingOptions.cs | 245 +++++++++ .../MigrateMusicBrainzTimeout.cs | 10 +- .../MigrateNetworkConfiguration.cs | 85 +-- .../MediaEncoding/EncodingHelper.cs | 594 +++++++++------------ MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 17 +- .../Transcoding/TranscodeManager.cs | 7 +- .../Configuration/EncodingOptions.cs | 26 +- MediaBrowser.Model/Entities/DeinterlaceMethod.cs | 19 + MediaBrowser.Model/Entities/EncoderPreset.cs | 64 +++ .../Entities/HardwareAccelerationType.cs | 49 ++ .../Entities/TonemappingAlgorithm.cs | 49 ++ MediaBrowser.Model/Entities/TonemappingMode.cs | 34 ++ MediaBrowser.Model/Entities/TonemappingRange.cs | 24 + MediaBrowser.Model/Session/HardwareEncodingType.cs | 43 -- MediaBrowser.Model/Session/TranscodingInfo.cs | 78 ++- .../Playlist/DynamicHlsPlaylistGenerator.cs | 4 +- 20 files changed, 883 insertions(+), 475 deletions(-) create mode 100644 Jellyfin.Server/Migrations/PreStartupRoutines/MigrateEncodingOptions.cs create mode 100644 MediaBrowser.Model/Entities/DeinterlaceMethod.cs create mode 100644 MediaBrowser.Model/Entities/EncoderPreset.cs create mode 100644 MediaBrowser.Model/Entities/HardwareAccelerationType.cs create mode 100644 MediaBrowser.Model/Entities/TonemappingAlgorithm.cs create mode 100644 MediaBrowser.Model/Entities/TonemappingMode.cs create mode 100644 MediaBrowser.Model/Entities/TonemappingRange.cs delete mode 100644 MediaBrowser.Model/Session/HardwareEncodingType.cs (limited to 'MediaBrowser.Model/Configuration') diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index b9ef189e9..db1d86698 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -40,8 +40,8 @@ namespace Jellyfin.Api.Controllers; [Authorize] public class DynamicHlsController : BaseJellyfinApiController { - private const string DefaultVodEncoderPreset = "veryfast"; - private const string DefaultEventEncoderPreset = "superfast"; + private const EncoderPreset DefaultVodEncoderPreset = EncoderPreset.veryfast; + private const EncoderPreset DefaultEventEncoderPreset = EncoderPreset.superfast; private const TranscodingJobType TranscodingJobType = MediaBrowser.Controller.MediaEncoding.TranscodingJobType.Hls; private readonly Version _minFFmpegFlacInMp4 = new Version(6, 0); diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index effe7b021..8348fd937 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -482,7 +482,7 @@ public class VideosController : BaseJellyfinApiController // Need to start ffmpeg (because media can't be returned directly) var encodingOptions = _serverConfigurationManager.GetEncodingOptions(); - var ffmpegCommandLineArguments = _encodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, "superfast"); + var ffmpegCommandLineArguments = _encodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, EncoderPreset.superfast); return await FileStreamResponseHelpers.GetTranscodedFile( state, isHeadRequest, diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 8682f28e0..9d4441ac3 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -23,7 +23,8 @@ namespace Jellyfin.Server.Migrations { typeof(PreStartupRoutines.CreateNetworkConfiguration), typeof(PreStartupRoutines.MigrateMusicBrainzTimeout), - typeof(PreStartupRoutines.MigrateNetworkConfiguration) + typeof(PreStartupRoutines.MigrateNetworkConfiguration), + typeof(PreStartupRoutines.MigrateEncodingOptions) }; /// diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs index 139a6ec64..8462d0a8c 100644 --- a/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs +++ b/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs @@ -132,5 +132,4 @@ public class CreateNetworkConfiguration : IMigrationRoutine public string[] KnownProxies { get; set; } = Array.Empty(); } -#pragma warning restore } diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateEncodingOptions.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateEncodingOptions.cs new file mode 100644 index 000000000..61f5620dc --- /dev/null +++ b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateEncodingOptions.cs @@ -0,0 +1,245 @@ +using System; +using System.IO; +using System.Xml; +using System.Xml.Serialization; +using Emby.Server.Implementations; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Entities; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.PreStartupRoutines; + +/// +public class MigrateEncodingOptions : IMigrationRoutine +{ + private readonly ServerApplicationPaths _applicationPaths; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// An instance of . + /// An instance of the interface. + public MigrateEncodingOptions(ServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory) + { + _applicationPaths = applicationPaths; + _logger = loggerFactory.CreateLogger(); + } + + /// + public Guid Id => Guid.Parse("A8E61960-7726-4450-8F3D-82C12DAABBCB"); + + /// + public string Name => nameof(MigrateEncodingOptions); + + /// + public bool PerformOnNewInstall => false; + + /// + public void Perform() + { + string path = Path.Combine(_applicationPaths.ConfigurationDirectoryPath, "encoding.xml"); + var oldSerializer = new XmlSerializer(typeof(OldEncodingOptions), new XmlRootAttribute("EncodingOptions")); + OldEncodingOptions? oldConfig = null; + + try + { + using var xmlReader = XmlReader.Create(path); + oldConfig = (OldEncodingOptions?)oldSerializer.Deserialize(xmlReader); + } + catch (InvalidOperationException ex) + { + _logger.LogError(ex, "Migrate EncodingOptions deserialize Invalid Operation error"); + } + catch (Exception ex) + { + _logger.LogError(ex, "Migrate EncodingOptions deserialize error"); + } + + if (oldConfig is null) + { + return; + } + + var hardwareAccelerationType = HardwareAccelerationType.none; + if (Enum.TryParse(oldConfig.HardwareAccelerationType, true, out var parsedHardwareAccelerationType)) + { + hardwareAccelerationType = parsedHardwareAccelerationType; + } + + var tonemappingAlgorithm = TonemappingAlgorithm.none; + if (Enum.TryParse(oldConfig.TonemappingAlgorithm, true, out var parsedTonemappingAlgorithm)) + { + tonemappingAlgorithm = parsedTonemappingAlgorithm; + } + + var tonemappingMode = TonemappingMode.auto; + if (Enum.TryParse(oldConfig.TonemappingMode, true, out var parsedTonemappingMode)) + { + tonemappingMode = parsedTonemappingMode; + } + + var tonemappingRange = TonemappingRange.auto; + if (Enum.TryParse(oldConfig.TonemappingRange, true, out var parsedTonemappingRange)) + { + tonemappingRange = parsedTonemappingRange; + } + + var encoderPreset = EncoderPreset.superfast; + if (Enum.TryParse(oldConfig.TonemappingRange, true, out var parsedEncoderPreset)) + { + encoderPreset = parsedEncoderPreset; + } + + var deinterlaceMethod = DeinterlaceMethod.yadif; + if (Enum.TryParse(oldConfig.TonemappingRange, true, out var parsedDeinterlaceMethod)) + { + deinterlaceMethod = parsedDeinterlaceMethod; + } + + var encodingOptions = new EncodingOptions() + { + EncodingThreadCount = oldConfig.EncodingThreadCount, + TranscodingTempPath = oldConfig.TranscodingTempPath, + FallbackFontPath = oldConfig.FallbackFontPath, + EnableFallbackFont = oldConfig.EnableFallbackFont, + EnableAudioVbr = oldConfig.EnableAudioVbr, + DownMixAudioBoost = oldConfig.DownMixAudioBoost, + DownMixStereoAlgorithm = oldConfig.DownMixStereoAlgorithm, + MaxMuxingQueueSize = oldConfig.MaxMuxingQueueSize, + EnableThrottling = oldConfig.EnableThrottling, + ThrottleDelaySeconds = oldConfig.ThrottleDelaySeconds, + EnableSegmentDeletion = oldConfig.EnableSegmentDeletion, + SegmentKeepSeconds = oldConfig.SegmentKeepSeconds, + HardwareAccelerationType = hardwareAccelerationType, + EncoderAppPath = oldConfig.EncoderAppPath, + EncoderAppPathDisplay = oldConfig.EncoderAppPathDisplay, + VaapiDevice = oldConfig.VaapiDevice, + EnableTonemapping = oldConfig.EnableTonemapping, + EnableVppTonemapping = oldConfig.EnableVppTonemapping, + EnableVideoToolboxTonemapping = oldConfig.EnableVideoToolboxTonemapping, + TonemappingAlgorithm = tonemappingAlgorithm, + TonemappingMode = tonemappingMode, + TonemappingRange = tonemappingRange, + TonemappingDesat = oldConfig.TonemappingDesat, + TonemappingPeak = oldConfig.TonemappingPeak, + TonemappingParam = oldConfig.TonemappingParam, + VppTonemappingBrightness = oldConfig.VppTonemappingBrightness, + VppTonemappingContrast = oldConfig.VppTonemappingContrast, + H264Crf = oldConfig.H264Crf, + H265Crf = oldConfig.H265Crf, + EncoderPreset = encoderPreset, + DeinterlaceDoubleRate = oldConfig.DeinterlaceDoubleRate, + DeinterlaceMethod = deinterlaceMethod, + EnableDecodingColorDepth10Hevc = oldConfig.EnableDecodingColorDepth10Hevc, + EnableDecodingColorDepth10Vp9 = oldConfig.EnableDecodingColorDepth10Vp9, + EnableEnhancedNvdecDecoder = oldConfig.EnableEnhancedNvdecDecoder, + PreferSystemNativeHwDecoder = oldConfig.PreferSystemNativeHwDecoder, + EnableIntelLowPowerH264HwEncoder = oldConfig.EnableIntelLowPowerH264HwEncoder, + EnableIntelLowPowerHevcHwEncoder = oldConfig.EnableIntelLowPowerHevcHwEncoder, + EnableHardwareEncoding = oldConfig.EnableHardwareEncoding, + AllowHevcEncoding = oldConfig.AllowHevcEncoding, + AllowAv1Encoding = oldConfig.AllowAv1Encoding, + EnableSubtitleExtraction = oldConfig.EnableSubtitleExtraction, + HardwareDecodingCodecs = oldConfig.HardwareDecodingCodecs, + AllowOnDemandMetadataBasedKeyframeExtractionForExtensions = oldConfig.AllowOnDemandMetadataBasedKeyframeExtractionForExtensions + }; + + var newSerializer = new XmlSerializer(typeof(EncodingOptions)); + var xmlWriterSettings = new XmlWriterSettings { Indent = true }; + using var xmlWriter = XmlWriter.Create(path, xmlWriterSettings); + newSerializer.Serialize(xmlWriter, encodingOptions); + } + +#pragma warning disable + public sealed class OldEncodingOptions + { + public int EncodingThreadCount { get; set; } + + public string TranscodingTempPath { get; set; } + + public string FallbackFontPath { get; set; } + + public bool EnableFallbackFont { get; set; } + + public bool EnableAudioVbr { get; set; } + + public double DownMixAudioBoost { get; set; } + + public DownMixStereoAlgorithms DownMixStereoAlgorithm { get; set; } + + public int MaxMuxingQueueSize { get; set; } + + public bool EnableThrottling { get; set; } + + public int ThrottleDelaySeconds { get; set; } + + public bool EnableSegmentDeletion { get; set; } + + public int SegmentKeepSeconds { get; set; } + + public string HardwareAccelerationType { get; set; } + + public string EncoderAppPath { get; set; } + + public string EncoderAppPathDisplay { get; set; } + + public string VaapiDevice { get; set; } + + public bool EnableTonemapping { get; set; } + + public bool EnableVppTonemapping { get; set; } + + public bool EnableVideoToolboxTonemapping { get; set; } + + public string TonemappingAlgorithm { get; set; } + + public string TonemappingMode { get; set; } + + public string TonemappingRange { get; set; } + + public double TonemappingDesat { get; set; } + + public double TonemappingPeak { get; set; } + + public double TonemappingParam { get; set; } + + public double VppTonemappingBrightness { get; set; } + + public double VppTonemappingContrast { get; set; } + + public int H264Crf { get; set; } + + public int H265Crf { get; set; } + + public string EncoderPreset { get; set; } + + public bool DeinterlaceDoubleRate { get; set; } + + public string DeinterlaceMethod { get; set; } + + public bool EnableDecodingColorDepth10Hevc { get; set; } + + public bool EnableDecodingColorDepth10Vp9 { get; set; } + + public bool EnableEnhancedNvdecDecoder { get; set; } + + public bool PreferSystemNativeHwDecoder { get; set; } + + public bool EnableIntelLowPowerH264HwEncoder { get; set; } + + public bool EnableIntelLowPowerHevcHwEncoder { get; set; } + + public bool EnableHardwareEncoding { get; set; } + + public bool AllowHevcEncoding { get; set; } + + public bool AllowAv1Encoding { get; set; } + + public bool EnableSubtitleExtraction { get; set; } + + public string[] HardwareDecodingCodecs { get; set; } + + public string[] AllowOnDemandMetadataBasedKeyframeExtractionForExtensions { get; set; } + } +} diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs index 0544fe561..580282a5f 100644 --- a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs +++ b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs @@ -48,9 +48,11 @@ public class MigrateMusicBrainzTimeout : IMigrationRoutine if (oldPluginConfiguration is not null) { - var newPluginConfiguration = new PluginConfiguration(); - newPluginConfiguration.Server = oldPluginConfiguration.Server; - newPluginConfiguration.ReplaceArtistName = oldPluginConfiguration.ReplaceArtistName; + var newPluginConfiguration = new PluginConfiguration + { + Server = oldPluginConfiguration.Server, + ReplaceArtistName = oldPluginConfiguration.ReplaceArtistName + }; var newRateLimit = oldPluginConfiguration.RateLimit / 1000.0; newPluginConfiguration.RateLimit = newRateLimit < 1.0 ? 1.0 : newRateLimit; WriteNew(path, newPluginConfiguration); @@ -93,6 +95,4 @@ public class MigrateMusicBrainzTimeout : IMigrationRoutine public bool ReplaceArtistName { get; set; } } -#pragma warning restore - } diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateNetworkConfiguration.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateNetworkConfiguration.cs index d92c00991..49960f430 100644 --- a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateNetworkConfiguration.cs +++ b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateNetworkConfiguration.cs @@ -55,49 +55,53 @@ public class MigrateNetworkConfiguration : IMigrationRoutine _logger.LogError(ex, "Migrate NetworkConfiguration deserialize error"); } - if (oldNetworkConfiguration is not null) + if (oldNetworkConfiguration is null) { - // Migrate network config values to new config schema - var networkConfiguration = new NetworkConfiguration(); - networkConfiguration.AutoDiscovery = oldNetworkConfiguration.AutoDiscovery; - networkConfiguration.BaseUrl = oldNetworkConfiguration.BaseUrl; - networkConfiguration.CertificatePassword = oldNetworkConfiguration.CertificatePassword; - networkConfiguration.CertificatePath = oldNetworkConfiguration.CertificatePath; - networkConfiguration.EnableHttps = oldNetworkConfiguration.EnableHttps; - networkConfiguration.EnableIPv4 = oldNetworkConfiguration.EnableIPV4; - networkConfiguration.EnableIPv6 = oldNetworkConfiguration.EnableIPV6; - networkConfiguration.EnablePublishedServerUriByRequest = oldNetworkConfiguration.EnablePublishedServerUriByRequest; - networkConfiguration.EnableRemoteAccess = oldNetworkConfiguration.EnableRemoteAccess; - networkConfiguration.EnableUPnP = oldNetworkConfiguration.EnableUPnP; - networkConfiguration.IgnoreVirtualInterfaces = oldNetworkConfiguration.IgnoreVirtualInterfaces; - networkConfiguration.InternalHttpPort = oldNetworkConfiguration.HttpServerPortNumber; - networkConfiguration.InternalHttpsPort = oldNetworkConfiguration.HttpsPortNumber; - networkConfiguration.IsRemoteIPFilterBlacklist = oldNetworkConfiguration.IsRemoteIPFilterBlacklist; - networkConfiguration.KnownProxies = oldNetworkConfiguration.KnownProxies; - networkConfiguration.LocalNetworkAddresses = oldNetworkConfiguration.LocalNetworkAddresses; - networkConfiguration.LocalNetworkSubnets = oldNetworkConfiguration.LocalNetworkSubnets; - networkConfiguration.PublicHttpPort = oldNetworkConfiguration.PublicPort; - networkConfiguration.PublicHttpsPort = oldNetworkConfiguration.PublicHttpsPort; - networkConfiguration.PublishedServerUriBySubnet = oldNetworkConfiguration.PublishedServerUriBySubnet; - networkConfiguration.RemoteIPFilter = oldNetworkConfiguration.RemoteIPFilter; - networkConfiguration.RequireHttps = oldNetworkConfiguration.RequireHttps; - - // Migrate old virtual interface name schema - var oldVirtualInterfaceNames = oldNetworkConfiguration.VirtualInterfaceNames; - if (oldVirtualInterfaceNames.Equals("vEthernet*", StringComparison.OrdinalIgnoreCase)) - { - networkConfiguration.VirtualInterfaceNames = new string[] { "veth" }; - } - else - { - networkConfiguration.VirtualInterfaceNames = oldVirtualInterfaceNames.Replace("*", string.Empty, StringComparison.OrdinalIgnoreCase).Split(','); - } + return; + } - var networkConfigSerializer = new XmlSerializer(typeof(NetworkConfiguration)); - var xmlWriterSettings = new XmlWriterSettings { Indent = true }; - using var xmlWriter = XmlWriter.Create(path, xmlWriterSettings); - networkConfigSerializer.Serialize(xmlWriter, networkConfiguration); + // Migrate network config values to new config schema + var networkConfiguration = new NetworkConfiguration + { + AutoDiscovery = oldNetworkConfiguration.AutoDiscovery, + BaseUrl = oldNetworkConfiguration.BaseUrl, + CertificatePassword = oldNetworkConfiguration.CertificatePassword, + CertificatePath = oldNetworkConfiguration.CertificatePath, + EnableHttps = oldNetworkConfiguration.EnableHttps, + EnableIPv4 = oldNetworkConfiguration.EnableIPV4, + EnableIPv6 = oldNetworkConfiguration.EnableIPV6, + EnablePublishedServerUriByRequest = oldNetworkConfiguration.EnablePublishedServerUriByRequest, + EnableRemoteAccess = oldNetworkConfiguration.EnableRemoteAccess, + EnableUPnP = oldNetworkConfiguration.EnableUPnP, + IgnoreVirtualInterfaces = oldNetworkConfiguration.IgnoreVirtualInterfaces, + InternalHttpPort = oldNetworkConfiguration.HttpServerPortNumber, + InternalHttpsPort = oldNetworkConfiguration.HttpsPortNumber, + IsRemoteIPFilterBlacklist = oldNetworkConfiguration.IsRemoteIPFilterBlacklist, + KnownProxies = oldNetworkConfiguration.KnownProxies, + LocalNetworkAddresses = oldNetworkConfiguration.LocalNetworkAddresses, + LocalNetworkSubnets = oldNetworkConfiguration.LocalNetworkSubnets, + PublicHttpPort = oldNetworkConfiguration.PublicPort, + PublicHttpsPort = oldNetworkConfiguration.PublicHttpsPort, + PublishedServerUriBySubnet = oldNetworkConfiguration.PublishedServerUriBySubnet, + RemoteIPFilter = oldNetworkConfiguration.RemoteIPFilter, + RequireHttps = oldNetworkConfiguration.RequireHttps + }; + + // Migrate old virtual interface name schema + var oldVirtualInterfaceNames = oldNetworkConfiguration.VirtualInterfaceNames; + if (oldVirtualInterfaceNames.Equals("vEthernet*", StringComparison.OrdinalIgnoreCase)) + { + networkConfiguration.VirtualInterfaceNames = new string[] { "veth" }; } + else + { + networkConfiguration.VirtualInterfaceNames = oldVirtualInterfaceNames.Replace("*", string.Empty, StringComparison.OrdinalIgnoreCase).Split(','); + } + + var networkConfigSerializer = new XmlSerializer(typeof(NetworkConfiguration)); + var xmlWriterSettings = new XmlWriterSettings { Indent = true }; + using var xmlWriter = XmlWriter.Create(path, xmlWriterSettings); + networkConfigSerializer.Serialize(xmlWriter, networkConfiguration); } #pragma warning disable @@ -204,5 +208,4 @@ public class MigrateNetworkConfiguration : IMigrationRoutine public bool EnablePublishedServerUriByRequest { get; set; } = false; } -#pragma warning restore } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index d6ad7e2b3..fdc56652a 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -37,6 +37,8 @@ namespace MediaBrowser.Controller.MediaEncoding /// 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"; @@ -72,8 +74,8 @@ namespace MediaBrowser.Controller.MediaEncoding 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 +84,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 _mp4ContainerNames = new(StringComparer.OrdinalIgnoreCase) { @@ -107,8 +109,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 +125,22 @@ namespace MediaBrowser.Controller.MediaEncoding { "truehd", 6 }, }; - private static readonly string _defaultMjpegEncoder = "mjpeg"; - private static readonly Dictionary _mjpegCodecMap = new(StringComparer.OrdinalIgnoreCase) + private static readonly Dictionary _mjpegCodecMap = new() { - { "vaapi", _defaultMjpegEncoder + "_vaapi" }, - { "qsv", _defaultMjpegEncoder + "_qsv" }, - { "videotoolbox", _defaultMjpegEncoder + "_videotoolbox" } + { HardwareAccelerationType.vaapi, _defaultMjpegEncoder + "_vaapi" }, + { HardwareAccelerationType.qsv, _defaultMjpegEncoder + "_qsv" }, + { HardwareAccelerationType.videotoolbox, _defaultMjpegEncoder + "_videotoolbox" } }; - public static readonly string[] LosslessAudioCodecs = new string[] - { + public static readonly string[] LosslessAudioCodecs = + [ "alac", "ape", "flac", "mlp", "truehd", "wavpack" - }; + ]; public EncodingHelper( IApplicationPaths appPaths, @@ -176,18 +177,18 @@ namespace MediaBrowser.Controller.MediaEncoding { var hwType = encodingOptions.HardwareAccelerationType; - var codecMap = new Dictionary(StringComparer.OrdinalIgnoreCase) + var codecMap = new Dictionary() { - { "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 +206,7 @@ namespace MediaBrowser.Controller.MediaEncoding { var hwType = encodingOptions.HardwareAccelerationType; - if (!string.IsNullOrEmpty(hwType) + if (hwType != HardwareAccelerationType.none && encodingOptions.EnableHardwareEncoding && _mjpegCodecMap.TryGetValue(hwType, out var preferredEncoder) && _mediaEncoder.SupportsEncoder(preferredEncoder)) @@ -360,7 +361,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; @@ -970,7 +971,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")) { @@ -1044,7 +1045,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")) { @@ -1079,7 +1080,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()) { @@ -1098,7 +1099,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")) { @@ -1123,7 +1124,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")) { @@ -1140,7 +1141,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")) { @@ -1413,6 +1414,149 @@ 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) + { + param += " -preset " + encoderPreset.ToString().ToLowerInvariant(); + + 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]; + + if (valid_presets.Contains(encoderPreset)) + { + param += " -preset " + encodingOptions.EncoderPreset; + } + else + { + param += " -preset " + 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)) @@ -1625,7 +1769,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// Encoding options. /// Default present to use for encoding. /// Video bitrate. - 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; @@ -1640,7 +1784,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; @@ -1653,7 +1799,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()) { @@ -1700,204 +1846,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); @@ -3256,7 +3208,7 @@ namespace MediaBrowser.Controller.MediaEncoding 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"); } @@ -3265,8 +3217,7 @@ namespace MediaBrowser.Controller.MediaEncoding 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, @@ -3307,7 +3258,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 = options.TonemappingRange; + var rangeString = range.ToString().ToLowerInvariant(); if (string.Equals(hwTonemapSuffix, "vaapi", StringComparison.OrdinalIgnoreCase)) { @@ -3342,10 +3296,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) { @@ -3357,8 +3311,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}"; } @@ -3372,9 +3325,9 @@ namespace MediaBrowser.Controller.MediaEncoding algorithm, options.TonemappingPeak, options.TonemappingDesat, - options.TonemappingMode, + mode, options.TonemappingParam, - options.TonemappingRange); + rangeString); } public string GetLibplaceboFilter( @@ -3409,24 +3362,24 @@ namespace MediaBrowser.Controller.MediaEncoding if (doTonemap) { var algorithm = options.TonemappingAlgorithm; + var algorithmString = "clip"; var mode = options.TonemappingMode; var range = 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"; - 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(); } } @@ -3530,8 +3483,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}"; } @@ -3575,7 +3528,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); } @@ -3777,7 +3730,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); } @@ -3993,7 +3946,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); } @@ -4543,7 +4496,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); } @@ -5247,7 +5200,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); } @@ -5436,7 +5389,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); } @@ -5696,38 +5649,20 @@ namespace MediaBrowser.Controller.MediaEncoding List subFilters; List overlayFilters; - if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) + (mainFilters, subFilters, overlayFilters) = options.HardwareAccelerationType switch { - (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) = 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) @@ -5907,7 +5842,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); @@ -5919,10 +5856,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; @@ -5939,34 +5876,20 @@ 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)) + var decoder = hardwareAccelerationType switch { - return GetNvdecVidDecoder(state, options, videoStream, bitDepth); - } - - if (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)) - { - return GetAmfVidDecoder(state, options, videoStream, bitDepth); - } - - if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) - { - return GetVaapiVidDecoder(state, options, videoStream, bitDepth); - } - - if (string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) - { - return GetVideotoolboxVidDecoder(state, options, videoStream, bitDepth); - } + 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; } } @@ -5981,7 +5904,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // Avoid a second attempt if no hardware acceleration is being used - options.HardwareDecodingCodecs = Array.FindAll(options.HardwareDecodingCodecs, val => !string.Equals(val, whichCodec, StringComparison.OrdinalIgnoreCase)); + options.HardwareDecodingCodecs = options.HardwareDecodingCodecs.Where(c => !string.Equals(c, whichCodec, StringComparison.OrdinalIgnoreCase)).ToArray(); // leave blank so ffmpeg will decide return null; @@ -6062,6 +5985,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; @@ -6099,7 +6023,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // Intel qsv/d3d11va/vaapi - if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) + if (hardwareAccelerationType == HardwareAccelerationType.qsv) { if (options.PreferSystemNativeHwDecoder) { @@ -6125,7 +6049,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // Nvidia cuda - if (string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) + if (hardwareAccelerationType == HardwareAccelerationType.nvenc) { if (isCudaSupported && isCodecAvailable) { @@ -6142,7 +6066,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // Amd d3d11va - if (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)) + if (hardwareAccelerationType == HardwareAccelerationType.amf) { if (isD3d11Supported && isCodecAvailable) { @@ -6152,7 +6076,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // Vaapi - if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) + if (hardwareAccelerationType == HardwareAccelerationType.vaapi && isVaapiSupported && isCodecAvailable) { @@ -6161,7 +6085,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // Apple videotoolbox - if (string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase) + if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox && isVideotoolboxSupported && isCodecAvailable) { @@ -6169,7 +6093,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // Rockchip rkmpp - if (string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase) + if (hardwareAccelerationType == HardwareAccelerationType.rkmpp && isRkmppSupported && isCodecAvailable) { @@ -6185,7 +6109,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; } @@ -6254,7 +6178,7 @@ namespace MediaBrowser.Controller.MediaEncoding 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; } @@ -6319,7 +6243,7 @@ namespace MediaBrowser.Controller.MediaEncoding 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; } @@ -6375,7 +6299,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; } @@ -6437,7 +6361,7 @@ namespace MediaBrowser.Controller.MediaEncoding 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; } @@ -6485,7 +6409,7 @@ namespace MediaBrowser.Controller.MediaEncoding var isLinux = OperatingSystem.IsLinux(); if (!isLinux - || !string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase)) + || options.HardwareAccelerationType != HardwareAccelerationType.rkmpp) { return null; } @@ -6749,7 +6673,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)) @@ -6865,7 +6789,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(); } @@ -6991,7 +6915,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); @@ -7042,7 +6966,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; diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index a6a443f3d..caa9cb499 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -224,7 +224,7 @@ namespace MediaBrowser.MediaEncoding.Encoder if (OperatingSystem.IsLinux() && SupportsHwaccel("vaapi") && !string.IsNullOrEmpty(options.VaapiDevice) - && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) + && options.HardwareAccelerationType == HardwareAccelerationType.vaapi) { _isVaapiDeviceAmd = validator.CheckVaapiDeviceByDriverName("Mesa Gallium driver", options.VaapiDevice); _isVaapiDeviceInteliHD = validator.CheckVaapiDeviceByDriverName("Intel iHD driver", options.VaapiDevice); @@ -799,11 +799,12 @@ namespace MediaBrowser.MediaEncoding.Encoder if (allowHwAccel && enableKeyFrameOnlyExtraction) { - var supportsKeyFrameOnly = (string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && options.EnableEnhancedNvdecDecoder) - || (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && OperatingSystem.IsWindows()) - || (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && options.PreferSystemNativeHwDecoder) - || string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) - || string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase); + var hardwareAccelerationType = options.HardwareAccelerationType; + var supportsKeyFrameOnly = (hardwareAccelerationType == HardwareAccelerationType.nvenc && options.EnableEnhancedNvdecDecoder) + || (hardwareAccelerationType == HardwareAccelerationType.amf && OperatingSystem.IsWindows()) + || (hardwareAccelerationType == HardwareAccelerationType.qsv && options.PreferSystemNativeHwDecoder) + || hardwareAccelerationType == HardwareAccelerationType.vaapi + || hardwareAccelerationType == HardwareAccelerationType.videotoolbox; if (!supportsKeyFrameOnly) { // Disable hardware acceleration when the hardware decoder does not support keyframe only mode. @@ -817,7 +818,7 @@ namespace MediaBrowser.MediaEncoding.Encoder if (!allowHwAccel) { options.EnableHardwareEncoding = false; - options.HardwareAccelerationType = string.Empty; + options.HardwareAccelerationType = HardwareAccelerationType.none; options.EnableTonemapping = false; } @@ -861,7 +862,7 @@ namespace MediaBrowser.MediaEncoding.Encoder inputArg = "-threads " + threads + " " + inputArg; // HW accel args set a different input thread count, only set if disabled } - if (options.HardwareAccelerationType.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase) && _isLowPriorityHwDecodeSupported) + if (options.HardwareAccelerationType == HardwareAccelerationType.videotoolbox && _isLowPriorityHwDecodeSupported) { // VideoToolbox supports low priority decoding, which is useful for trickplay inputArg = "-hwaccel_flags +low_priority " + inputArg; diff --git a/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs index 42f355b05..57557d55c 100644 --- a/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs +++ b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs @@ -352,12 +352,7 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable { var audioCodec = state.ActualOutputAudioCodec; var videoCodec = state.ActualOutputVideoCodec; - var hardwareAccelerationTypeString = _serverConfigurationManager.GetEncodingOptions().HardwareAccelerationType; - HardwareEncodingType? hardwareAccelerationType = null; - if (Enum.TryParse(hardwareAccelerationTypeString, out var parsedHardwareAccelerationType)) - { - hardwareAccelerationType = parsedHardwareAccelerationType; - } + var hardwareAccelerationType = _serverConfigurationManager.GetEncodingOptions().HardwareAccelerationType; _sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo { diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index 4c5213d4e..d67a2479f 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1819 // XML serialization handles collections improperly, so we need to use arrays + #nullable disable using MediaBrowser.Model.Entities; @@ -30,9 +32,9 @@ public class EncodingOptions EnableTonemapping = false; EnableVppTonemapping = false; EnableVideoToolboxTonemapping = false; - TonemappingAlgorithm = "bt2390"; - TonemappingMode = "auto"; - TonemappingRange = "auto"; + TonemappingAlgorithm = TonemappingAlgorithm.bt2390; + TonemappingMode = TonemappingMode.auto; + TonemappingRange = TonemappingRange.auto; TonemappingDesat = 0; TonemappingPeak = 100; TonemappingParam = 0; @@ -41,7 +43,7 @@ public class EncodingOptions H264Crf = 23; H265Crf = 28; DeinterlaceDoubleRate = false; - DeinterlaceMethod = "yadif"; + DeinterlaceMethod = DeinterlaceMethod.yadif; EnableDecodingColorDepth10Hevc = true; EnableDecodingColorDepth10Vp9 = true; // Enhanced Nvdec or system native decoder is required for DoVi to SDR tone-mapping. @@ -53,8 +55,8 @@ public class EncodingOptions AllowHevcEncoding = false; AllowAv1Encoding = false; EnableSubtitleExtraction = true; - AllowOnDemandMetadataBasedKeyframeExtractionForExtensions = new[] { "mkv" }; - HardwareDecodingCodecs = new string[] { "h264", "vc1" }; + AllowOnDemandMetadataBasedKeyframeExtractionForExtensions = ["mkv"]; + HardwareDecodingCodecs = ["h264", "vc1"]; } /// @@ -120,7 +122,7 @@ public class EncodingOptions /// /// Gets or sets the hardware acceleration type. /// - public string HardwareAccelerationType { get; set; } + public HardwareAccelerationType HardwareAccelerationType { get; set; } /// /// Gets or sets the FFmpeg path as set by the user via the UI. @@ -160,17 +162,17 @@ public class EncodingOptions /// /// Gets or sets the tone-mapping algorithm. /// - public string TonemappingAlgorithm { get; set; } + public TonemappingAlgorithm TonemappingAlgorithm { get; set; } /// /// Gets or sets the tone-mapping mode. /// - public string TonemappingMode { get; set; } + public TonemappingMode TonemappingMode { get; set; } /// /// Gets or sets the tone-mapping range. /// - public string TonemappingRange { get; set; } + public TonemappingRange TonemappingRange { get; set; } /// /// Gets or sets the tone-mapping desaturation. @@ -210,7 +212,7 @@ public class EncodingOptions /// /// Gets or sets the encoder preset. /// - public string EncoderPreset { get; set; } + public EncoderPreset? EncoderPreset { get; set; } /// /// Gets or sets a value indicating whether the framerate is doubled when deinterlacing. @@ -220,7 +222,7 @@ public class EncodingOptions /// /// Gets or sets the deinterlace method. /// - public string DeinterlaceMethod { get; set; } + public DeinterlaceMethod DeinterlaceMethod { get; set; } /// /// Gets or sets a value indicating whether 10bit HEVC decoding is enabled. diff --git a/MediaBrowser.Model/Entities/DeinterlaceMethod.cs b/MediaBrowser.Model/Entities/DeinterlaceMethod.cs new file mode 100644 index 000000000..d05aac433 --- /dev/null +++ b/MediaBrowser.Model/Entities/DeinterlaceMethod.cs @@ -0,0 +1,19 @@ +#pragma warning disable SA1300 // Lowercase required for backwards compat. + +namespace MediaBrowser.Model.Entities; + +/// +/// Enum containing deinterlace methods. +/// +public enum DeinterlaceMethod +{ + /// + /// YADIF. + /// + yadif = 0, + + /// + /// BWDIF. + /// + bwdif = 1 +} diff --git a/MediaBrowser.Model/Entities/EncoderPreset.cs b/MediaBrowser.Model/Entities/EncoderPreset.cs new file mode 100644 index 000000000..74c071433 --- /dev/null +++ b/MediaBrowser.Model/Entities/EncoderPreset.cs @@ -0,0 +1,64 @@ +#pragma warning disable SA1300 // Lowercase required for backwards compat. + +namespace MediaBrowser.Model.Entities; + +/// +/// Enum containing encoder presets. +/// +public enum EncoderPreset +{ + /// + /// Auto preset. + /// + auto = 0, + + /// + /// Placebo preset. + /// + placebo = 1, + + /// + /// Veryslow preset. + /// + veryslow = 2, + + /// + /// Slower preset. + /// + slower = 3, + + /// + /// Slow preset. + /// + slow = 4, + + /// + /// Medium preset. + /// + medium = 5, + + /// + /// Fast preset. + /// + fast = 6, + + /// + /// Faster preset. + /// + faster = 7, + + /// + /// Veryfast preset. + /// + veryfast = 8, + + /// + /// Superfast preset. + /// + superfast = 9, + + /// + /// Ultrafast preset. + /// + ultrafast = 10 +} diff --git a/MediaBrowser.Model/Entities/HardwareAccelerationType.cs b/MediaBrowser.Model/Entities/HardwareAccelerationType.cs new file mode 100644 index 000000000..198a2e00f --- /dev/null +++ b/MediaBrowser.Model/Entities/HardwareAccelerationType.cs @@ -0,0 +1,49 @@ +#pragma warning disable SA1300 // Lowercase required for backwards compat. + +namespace MediaBrowser.Model.Entities; + +/// +/// Enum containing hardware acceleration types. +/// +public enum HardwareAccelerationType +{ + /// + /// Software accelleration. + /// + none = 0, + + /// + /// AMD AMF. + /// + amf = 1, + + /// + /// Intel Quick Sync Video. + /// + qsv = 2, + + /// + /// NVIDIA NVENC. + /// + nvenc = 3, + + /// + /// Video4Linux2 V4L2M2M. + /// + v4l2m2m = 4, + + /// + /// Video Acceleration API (VAAPI). + /// + vaapi = 5, + + /// + /// Video ToolBox. + /// + videotoolbox = 6, + + /// + /// Rockchip Media Process Platform (RKMPP). + /// + rkmpp = 7 +} diff --git a/MediaBrowser.Model/Entities/TonemappingAlgorithm.cs b/MediaBrowser.Model/Entities/TonemappingAlgorithm.cs new file mode 100644 index 000000000..488006e0b --- /dev/null +++ b/MediaBrowser.Model/Entities/TonemappingAlgorithm.cs @@ -0,0 +1,49 @@ +#pragma warning disable SA1300 // Lowercase required for backwards compat. + +namespace MediaBrowser.Model.Entities; + +/// +/// Enum containing tonemapping algorithms. +/// +public enum TonemappingAlgorithm +{ + /// + /// None. + /// + none = 0, + + /// + /// Clip. + /// + clip = 1, + + /// + /// Linear. + /// + linear = 2, + + /// + /// Gamma. + /// + gamma = 3, + + /// + /// Reinhard. + /// + reinhard = 4, + + /// + /// Hable. + /// + hable = 5, + + /// + /// Mobius. + /// + mobius = 6, + + /// + /// BT2390. + /// + bt2390 = 7 +} diff --git a/MediaBrowser.Model/Entities/TonemappingMode.cs b/MediaBrowser.Model/Entities/TonemappingMode.cs new file mode 100644 index 000000000..e10a0b4ad --- /dev/null +++ b/MediaBrowser.Model/Entities/TonemappingMode.cs @@ -0,0 +1,34 @@ +#pragma warning disable SA1300 // Lowercase required for backwards compat. + +namespace MediaBrowser.Model.Entities; + +/// +/// Enum containing tonemapping modes. +/// +public enum TonemappingMode +{ + /// + /// Auto. + /// + auto = 0, + + /// + /// Max. + /// + max = 1, + + /// + /// RGB. + /// + rgb = 2, + + /// + /// Lum. + /// + lum = 3, + + /// + /// ITP. + /// + itp = 4 +} diff --git a/MediaBrowser.Model/Entities/TonemappingRange.cs b/MediaBrowser.Model/Entities/TonemappingRange.cs new file mode 100644 index 000000000..b1446b81c --- /dev/null +++ b/MediaBrowser.Model/Entities/TonemappingRange.cs @@ -0,0 +1,24 @@ +#pragma warning disable SA1300 // Lowercase required for backwards compat. + +namespace MediaBrowser.Model.Entities; + +/// +/// Enum containing tonemapping ranges. +/// +public enum TonemappingRange +{ + /// + /// Auto. + /// + auto = 0, + + /// + /// TV. + /// + tv = 1, + + /// + /// PC. + /// + pc = 2 +} diff --git a/MediaBrowser.Model/Session/HardwareEncodingType.cs b/MediaBrowser.Model/Session/HardwareEncodingType.cs deleted file mode 100644 index cf424fef5..000000000 --- a/MediaBrowser.Model/Session/HardwareEncodingType.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace MediaBrowser.Model.Session -{ - /// - /// Enum HardwareEncodingType. - /// - public enum HardwareEncodingType - { - /// - /// AMD AMF. - /// - AMF = 0, - - /// - /// Intel Quick Sync Video. - /// - QSV = 1, - - /// - /// NVIDIA NVENC. - /// - NVENC = 2, - - /// - /// Video4Linux2 V4L2. - /// - V4L2M2M = 3, - - /// - /// Video Acceleration API (VAAPI). - /// - VAAPI = 4, - - /// - /// Video ToolBox. - /// - VideoToolBox = 5, - - /// - /// Rockchip Media Process Platform (RKMPP). - /// - RKMPP = 6 - } -} diff --git a/MediaBrowser.Model/Session/TranscodingInfo.cs b/MediaBrowser.Model/Session/TranscodingInfo.cs index 000cbd4c5..ae25267ac 100644 --- a/MediaBrowser.Model/Session/TranscodingInfo.cs +++ b/MediaBrowser.Model/Session/TranscodingInfo.cs @@ -1,34 +1,76 @@ #nullable disable -#pragma warning disable CS1591 -namespace MediaBrowser.Model.Session +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Model.Session; + +/// +/// Class holding information on a runnning transcode. +/// +public class TranscodingInfo { - public class TranscodingInfo - { - public string AudioCodec { get; set; } + /// + /// Gets or sets the thread count used for encoding. + /// + public string AudioCodec { get; set; } - public string VideoCodec { get; set; } + /// + /// Gets or sets the thread count used for encoding. + /// + public string VideoCodec { get; set; } - public string Container { get; set; } + /// + /// Gets or sets the thread count used for encoding. + /// + public string Container { get; set; } - public bool IsVideoDirect { get; set; } + /// + /// Gets or sets a value indicating whether the video is passed through. + /// + public bool IsVideoDirect { get; set; } - public bool IsAudioDirect { get; set; } + /// + /// Gets or sets a value indicating whether the audio is passed through. + /// + public bool IsAudioDirect { get; set; } - public int? Bitrate { get; set; } + /// + /// Gets or sets the bitrate. + /// + public int? Bitrate { get; set; } - public float? Framerate { get; set; } + /// + /// Gets or sets the framerate. + /// + public float? Framerate { get; set; } - public double? CompletionPercentage { get; set; } + /// + /// Gets or sets the completion percentage. + /// + public double? CompletionPercentage { get; set; } - public int? Width { get; set; } + /// + /// Gets or sets the video width. + /// + public int? Width { get; set; } - public int? Height { get; set; } + /// + /// Gets or sets the video height. + /// + public int? Height { get; set; } - public int? AudioChannels { get; set; } + /// + /// Gets or sets the audio channels. + /// + public int? AudioChannels { get; set; } - public HardwareEncodingType? HardwareAccelerationType { get; set; } + /// + /// Gets or sets the hardware acceleration type. + /// + public HardwareAccelerationType? HardwareAccelerationType { get; set; } - public TranscodeReason TranscodeReasons { get; set; } - } + /// + /// Gets or sets the transcode reasons. + /// + public TranscodeReason TranscodeReasons { get; set; } } diff --git a/src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs b/src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs index 9a023d7ed..1846ba26b 100644 --- a/src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs +++ b/src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs @@ -128,7 +128,7 @@ public class DynamicHlsPlaylistGenerator : IDynamicHlsPlaylistGenerator return false; } - internal static bool IsExtractionAllowedForFile(ReadOnlySpan filePath, string[] allowedExtensions) + internal static bool IsExtractionAllowedForFile(ReadOnlySpan filePath, IReadOnlyList allowedExtensions) { var extension = Path.GetExtension(filePath); if (extension.IsEmpty) @@ -138,7 +138,7 @@ public class DynamicHlsPlaylistGenerator : IDynamicHlsPlaylistGenerator // Remove the leading dot var extensionWithoutDot = extension[1..]; - for (var i = 0; i < allowedExtensions.Length; i++) + for (var i = 0; i < allowedExtensions.Count; i++) { var allowedExtension = allowedExtensions[i].AsSpan().TrimStart('.'); if (extensionWithoutDot.Equals(allowedExtension, StringComparison.OrdinalIgnoreCase)) -- cgit v1.2.3 From 93db8990d951649c1d25003e2859776ac80e7440 Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Thu, 19 Sep 2024 21:14:18 +0800 Subject: Enable HEVC RExt HW decoding for 4:2:2/4:4:4 content (#12664) --- .../MediaEncoding/EncodingHelper.cs | 175 ++++++++++++++++----- .../Configuration/EncodingOptions.cs | 12 ++ 2 files changed, 147 insertions(+), 40 deletions(-) (limited to 'MediaBrowser.Model/Configuration') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 88aa888a1..e26bcf21e 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -299,7 +299,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (state.VideoStream is null || !options.EnableTonemapping - || GetVideoColorBitDepth(state) != 10 + || GetVideoColorBitDepth(state) < 10 || !_mediaEncoder.SupportsFilter("tonemapx")) { return false; @@ -312,7 +312,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (state.VideoStream is null || !options.EnableTonemapping - || GetVideoColorBitDepth(state) != 10) + || GetVideoColorBitDepth(state) < 10) { return false; } @@ -354,7 +354,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (state.VideoStream is null || !options.EnableVppTonemapping - || GetVideoColorBitDepth(state) != 10) + || GetVideoColorBitDepth(state) < 10) { return false; } @@ -377,7 +377,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (state.VideoStream is null || !options.EnableVideoToolboxTonemapping - || GetVideoColorBitDepth(state) != 10) + || GetVideoColorBitDepth(state) < 10) { return false; } @@ -388,6 +388,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)); + } + /// /// Gets the name of the output video codec. /// @@ -3659,7 +3678,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); @@ -4091,6 +4111,8 @@ namespace MediaBrowser.Controller.MediaEncoding } else if (isD3d11vaDecoder || isQsvDecoder) { + var isRext = IsVideoStreamHevcRext(state); + var twoPassVppTonemap = isRext; var doVppProcamp = false; var procampParams = string.Empty; if (doVppTonemap) @@ -4100,21 +4122,21 @@ 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"; @@ -4127,7 +4149,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTonemap) { - hwScaleFilter += doVppProcamp ? procampParams : ":tonemap=1"; + hwScaleFilter += doVppProcamp ? procampParams : (twoPassVppTonemap ? string.Empty : ":tonemap=1"); } if (isD3d11vaDecoder) @@ -4151,7 +4173,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"); } @@ -4346,6 +4368,7 @@ namespace MediaBrowser.Controller.MediaEncoding else if (isVaapiDecoder || isQsvDecoder) { var hwFilterSuffix = isVaapiDecoder ? "vaapi" : "qsv"; + var isRext = IsVideoStreamHevcRext(state); // INPUT vaapi/qsv surface(vram) // hw deint @@ -4362,6 +4385,8 @@ namespace MediaBrowser.Controller.MediaEncoding } var outFormat = doOclTonemap ? ((isQsvDecoder && doVppTranspose) ? "p010" : string.Empty) : "nv12"; + outFormat = (doTonemap && isRext) ? "p010" : outFormat; + var swapOutputWandH = isQsvDecoder && doVppTranspose && swapWAndH; var hwScalePrefix = (isQsvDecoder && doVppTranspose) ? "vpp" : "scale"; var hwScaleFilter = GetHwScaleFilter(hwScalePrefix, hwFilterSuffix, outFormat, swapOutputWandH, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); @@ -4658,6 +4683,8 @@ namespace MediaBrowser.Controller.MediaEncoding } else if (isVaapiDecoder) { + var isRext = IsVideoStreamHevcRext(state); + // INPUT vaapi surface(vram) // hw deint if (doDeintH2645) @@ -4672,7 +4699,7 @@ 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); // allocate extra pool sizes for vaapi vpp @@ -5974,7 +6001,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) @@ -6050,17 +6081,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; @@ -6172,6 +6226,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) @@ -6200,12 +6262,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); @@ -6217,6 +6273,15 @@ 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; } @@ -6232,6 +6297,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) @@ -6265,12 +6335,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); @@ -6282,6 +6346,15 @@ 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; } @@ -6356,6 +6429,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) { @@ -6383,12 +6464,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); @@ -6400,6 +6475,15 @@ 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; } @@ -6414,6 +6498,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(); @@ -6434,15 +6526,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); } } diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index d67a2479f..2720c0bdf 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -46,6 +46,8 @@ public class EncodingOptions DeinterlaceMethod = DeinterlaceMethod.yadif; EnableDecodingColorDepth10Hevc = true; EnableDecodingColorDepth10Vp9 = true; + EnableDecodingColorDepth10HevcRext = false; + EnableDecodingColorDepth12HevcRext = false; // Enhanced Nvdec or system native decoder is required for DoVi to SDR tone-mapping. EnableEnhancedNvdecDecoder = true; PreferSystemNativeHwDecoder = true; @@ -234,6 +236,16 @@ public class EncodingOptions /// public bool EnableDecodingColorDepth10Vp9 { get; set; } + /// + /// Gets or sets a value indicating whether 8/10bit HEVC RExt decoding is enabled. + /// + public bool EnableDecodingColorDepth10HevcRext { get; set; } + + /// + /// Gets or sets a value indicating whether 12bit HEVC RExt decoding is enabled. + /// + public bool EnableDecodingColorDepth12HevcRext { get; set; } + /// /// Gets or sets a value indicating whether the enhanced NVDEC is enabled. /// -- cgit v1.2.3 From 00ca4abbe1138d880fca36e1f99da14e6fab252a Mon Sep 17 00:00:00 2001 From: gnattu Date: Tue, 24 Sep 2024 05:15:46 +0800 Subject: Sanitize CustomTagDelimiters server side The API requires an array type and does not support runtime generated default value. Use server side helper function to sanitize it into char. --- MediaBrowser.Model/Configuration/LibraryOptions.cs | 20 +++++++++++++++++--- MediaBrowser.Providers/MediaInfo/AudioFileProber.cs | 6 +++--- 2 files changed, 20 insertions(+), 6 deletions(-) (limited to 'MediaBrowser.Model/Configuration') diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs index 04283cc9e..b0fcc2d0a 100644 --- a/MediaBrowser.Model/Configuration/LibraryOptions.cs +++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs @@ -2,12 +2,13 @@ using System; using System.ComponentModel; +using System.Linq; namespace MediaBrowser.Model.Configuration { public class LibraryOptions { - private static readonly char[] _defaultTagDelimiters = ['/', '|', ';', '\\']; + private static readonly string[] _defaultTagDelimiters = ["/", "|", ";", "\\"]; public LibraryOptions() { @@ -126,8 +127,7 @@ namespace MediaBrowser.Model.Configuration [DefaultValue(false)] public bool UseCustomTagDelimiters { get; set; } - [DefaultValue(typeof(LibraryOptions), nameof(_defaultTagDelimiters))] - public char[] CustomTagDelimiters { get; set; } + public string[] CustomTagDelimiters { get; set; } public string[] DelimiterWhitelist { get; set; } @@ -149,5 +149,19 @@ namespace MediaBrowser.Model.Configuration return null; } + + public char[] GetCustomTagDelimiters() + { + return CustomTagDelimiters.Select(x => + { + var isChar = char.TryParse(x, out var c); + if (isChar) + { + return c; + } + + return null; + }).Where(x => x is not null).Select(x => x!.Value).ToArray(); + } } } diff --git a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs index 80bb1a514..2e0d21c6a 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs @@ -178,7 +178,7 @@ namespace MediaBrowser.Providers.MediaInfo if (libraryOptions.UseCustomTagDelimiters) { - albumArtists = albumArtists.SelectMany(a => SplitWithCustomDelimiter(a, libraryOptions.CustomTagDelimiters, libraryOptions.DelimiterWhitelist)).ToArray(); + albumArtists = albumArtists.SelectMany(a => SplitWithCustomDelimiter(a, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist)).ToArray(); } foreach (var albumArtist in albumArtists) @@ -210,7 +210,7 @@ namespace MediaBrowser.Providers.MediaInfo if (libraryOptions.UseCustomTagDelimiters) { - performers = performers.SelectMany(p => SplitWithCustomDelimiter(p, libraryOptions.CustomTagDelimiters, libraryOptions.DelimiterWhitelist)).ToArray(); + performers = performers.SelectMany(p => SplitWithCustomDelimiter(p, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist)).ToArray(); } foreach (var performer in performers) @@ -313,7 +313,7 @@ namespace MediaBrowser.Providers.MediaInfo if (libraryOptions.UseCustomTagDelimiters) { - genres = genres.SelectMany(g => SplitWithCustomDelimiter(g, libraryOptions.CustomTagDelimiters, libraryOptions.DelimiterWhitelist)).ToArray(); + genres = genres.SelectMany(g => SplitWithCustomDelimiter(g, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist)).ToArray(); } audio.Genres = options.ReplaceAllMetadata || audio.Genres is null || audio.Genres.Length == 0 -- cgit v1.2.3 From 0ffddacf11795b8a50606b6515c1dc6828ad8dd0 Mon Sep 17 00:00:00 2001 From: gnattu Date: Tue, 24 Sep 2024 12:36:05 +0800 Subject: Move GetCustomTagDelimiters to Extension --- MediaBrowser.Model/Configuration/LibraryOptions.cs | 14 ---------- .../Extensions/LibraryOptionsExtension.cs | 32 ++++++++++++++++++++++ .../MediaInfo/AudioFileProber.cs | 1 + 3 files changed, 33 insertions(+), 14 deletions(-) create mode 100644 MediaBrowser.Model/Extensions/LibraryOptionsExtension.cs (limited to 'MediaBrowser.Model/Configuration') diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs index b0fcc2d0a..6054ba34e 100644 --- a/MediaBrowser.Model/Configuration/LibraryOptions.cs +++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs @@ -149,19 +149,5 @@ namespace MediaBrowser.Model.Configuration return null; } - - public char[] GetCustomTagDelimiters() - { - return CustomTagDelimiters.Select(x => - { - var isChar = char.TryParse(x, out var c); - if (isChar) - { - return c; - } - - return null; - }).Where(x => x is not null).Select(x => x!.Value).ToArray(); - } } } diff --git a/MediaBrowser.Model/Extensions/LibraryOptionsExtension.cs b/MediaBrowser.Model/Extensions/LibraryOptionsExtension.cs new file mode 100644 index 000000000..4a814f22a --- /dev/null +++ b/MediaBrowser.Model/Extensions/LibraryOptionsExtension.cs @@ -0,0 +1,32 @@ +using System; +using System.Linq; +using MediaBrowser.Model.Configuration; + +namespace MediaBrowser.Model.Extensions; + +/// +/// Extensions for . +/// +public static class LibraryOptionsExtension +{ + /// + /// Get the custom tag delimiters. + /// + /// This LibraryOptions. + /// CustomTagDelimiters in char[]. + public static char[] GetCustomTagDelimiters(this LibraryOptions options) + { + ArgumentNullException.ThrowIfNull(options); + + return options.CustomTagDelimiters.Select(x => + { + var isChar = char.TryParse(x, out var c); + if (isChar) + { + return c; + } + + return null; + }).Where(x => x is not null).Select(x => x!.Value).ToArray(); + } +} diff --git a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs index 2e0d21c6a..cb233d31e 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs @@ -16,6 +16,7 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Extensions; using MediaBrowser.Model.MediaInfo; using Microsoft.Extensions.Logging; -- cgit v1.2.3