diff options
9 files changed, 94 insertions, 35 deletions
diff --git a/.github/ISSUE_TEMPLATE/issue report.yml b/.github/ISSUE_TEMPLATE/issue report.yml index b71b365f7..cfb5a6ec2 100644 --- a/.github/ISSUE_TEMPLATE/issue report.yml +++ b/.github/ISSUE_TEMPLATE/issue report.yml @@ -86,7 +86,7 @@ body: label: Jellyfin Server version description: What version of Jellyfin are you using? options: - - 10.9.9+ + - 10.9.10+ - Master - Unstable - Older* diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 91c2be87b..af8106c0a 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -34,7 +34,7 @@ jobs: --verbosity minimal - name: Merge code coverage results - uses: danielpalme/ReportGenerator-GitHub-Action@5808021ec4deecb0ab3da051d49b4ce65fcc20af # 5.3.8 + uses: danielpalme/ReportGenerator-GitHub-Action@e3af7259842d9c814021ea121f85526e0872b25f # v5.3.9 with: reports: "**/coverage.cobertura.xml" targetdir: "merged/" diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json index ce98979e6..865a1ef95 100644 --- a/Emby.Server.Implementations/Localization/Core/de.json +++ b/Emby.Server.Implementations/Localization/Core/de.json @@ -130,5 +130,7 @@ "TaskCleanCollectionsAndPlaylists": "Sammlungen und Playlisten aufräumen", "TaskCleanCollectionsAndPlaylistsDescription": "Lösche nicht mehr vorhandene Einträge aus den Sammlungen und Playlisten.", "TaskAudioNormalization": "Audio Normalisierung", - "TaskAudioNormalizationDescription": "Durchsucht Dateien nach Audionormalisierungsdaten." + "TaskAudioNormalizationDescription": "Durchsucht Dateien nach Audionormalisierungsdaten.", + "TaskDownloadMissingLyricsDescription": "Lädt Liedtexte herunter", + "TaskDownloadMissingLyrics": "Fehlende Liedtexte herunterladen" } diff --git a/Emby.Server.Implementations/Localization/Core/es-AR.json b/Emby.Server.Implementations/Localization/Core/es-AR.json index 9433da28b..b926d9d30 100644 --- a/Emby.Server.Implementations/Localization/Core/es-AR.json +++ b/Emby.Server.Implementations/Localization/Core/es-AR.json @@ -130,5 +130,7 @@ "TaskAudioNormalization": "Normalización de audio", "TaskAudioNormalizationDescription": "Escanea archivos en busca de datos de normalización de audio.", "TaskCleanCollectionsAndPlaylists": "Limpiar colecciones y listas de reproducción", - "TaskCleanCollectionsAndPlaylistsDescription": "Elimina elementos de colecciones y listas de reproducción que ya no existen." + "TaskCleanCollectionsAndPlaylistsDescription": "Elimina elementos de colecciones y listas de reproducción que ya no existen.", + "TaskDownloadMissingLyrics": "Descargar letra faltante", + "TaskDownloadMissingLyricsDescription": "Descarga letras de canciones" } diff --git a/Emby.Server.Implementations/Localization/Core/es.json b/Emby.Server.Implementations/Localization/Core/es.json index 13e007b4c..210ee4446 100644 --- a/Emby.Server.Implementations/Localization/Core/es.json +++ b/Emby.Server.Implementations/Localization/Core/es.json @@ -130,5 +130,7 @@ "TaskCleanCollectionsAndPlaylists": "Limpiar colecciones y listas de reproducción", "TaskCleanCollectionsAndPlaylistsDescription": "Elimina elementos de colecciones y listas de reproducción que ya no existen.", "TaskAudioNormalization": "Normalización de audio", - "TaskAudioNormalizationDescription": "Escanear archivos para obtener datos de normalización." + "TaskAudioNormalizationDescription": "Escanear archivos para obtener datos de normalización.", + "TaskDownloadMissingLyricsDescription": "Descargar letras para las canciones", + "TaskDownloadMissingLyrics": "Descargar letras faltantes" } diff --git a/Emby.Server.Implementations/Localization/Core/es_419.json b/Emby.Server.Implementations/Localization/Core/es_419.json index e7deefbb0..b458ed423 100644 --- a/Emby.Server.Implementations/Localization/Core/es_419.json +++ b/Emby.Server.Implementations/Localization/Core/es_419.json @@ -129,5 +129,7 @@ "TaskAudioNormalization": "Normalización de audio", "TaskCleanCollectionsAndPlaylistsDescription": "Quita elementos que ya no existen de colecciones y listas de reproducción.", "TaskAudioNormalizationDescription": "Analiza los archivos para normalizar el audio.", - "TaskCleanCollectionsAndPlaylists": "Limpieza de colecciones y listas de reproducción" + "TaskCleanCollectionsAndPlaylists": "Limpieza de colecciones y listas de reproducción", + "TaskDownloadMissingLyrics": "Descargar letra faltante", + "TaskDownloadMissingLyricsDescription": "Descarga letras de canciones" } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index db86a49f6..0530e2af0 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -67,6 +67,7 @@ namespace MediaBrowser.Controller.MediaEncoding private readonly Version _minFFmpegWorkingVtHwSurface = new Version(7, 0, 1); private readonly Version _minFFmpegDisplayRotationOption = new Version(6, 0); private readonly Version _minFFmpegAdvancedTonemapMode = new Version(7, 0, 1); + private readonly Version _minFFmpegAlteredVaVkInterop = new Version(7, 0, 1); private static readonly Regex _validationRegex = new(ValidationRegex, RegexOptions.Compiled); @@ -296,14 +297,12 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.VideoStream is null || !options.EnableTonemapping || GetVideoColorBitDepth(state) != 10 - || !_mediaEncoder.SupportsFilter("tonemapx") - || !(string.Equals(state.VideoStream?.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) || string.Equals(state.VideoStream?.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))) + || !_mediaEncoder.SupportsFilter("tonemapx")) { return false; } - return state.VideoStream.VideoRange == VideoRange.HDR - && state.VideoStream.VideoRangeType is VideoRangeType.HDR10 or VideoRangeType.HLG or VideoRangeType.DOVIWithHDR10 or VideoRangeType.DOVIWithHLG; + return state.VideoStream.VideoRange == VideoRange.HDR; } private bool IsHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options) @@ -3369,15 +3368,7 @@ namespace MediaBrowser.Controller.MediaEncoding algorithm = "clip"; } - tonemapArg = ":tonemapping=" + algorithm; - - if (string.Equals(mode, "max", StringComparison.OrdinalIgnoreCase) - || string.Equals(mode, "rgb", StringComparison.OrdinalIgnoreCase)) - { - tonemapArg += ":tonemapping_mode=" + mode; - } - - tonemapArg += ":peak_detect=0:color_primaries=bt709:color_trc=bt709:colorspace=bt709"; + 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)) @@ -3435,6 +3426,7 @@ namespace MediaBrowser.Controller.MediaEncoding var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); var doDeintH2645 = doDeintH264 || doDeintHevc; var doToneMap = IsSwTonemapAvailable(state, options); + var requireDoviReshaping = doToneMap && state.VideoStream.VideoRangeType == VideoRangeType.DOVI; var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; @@ -3472,11 +3464,13 @@ namespace MediaBrowser.Controller.MediaEncoding // sw scale mainFilters.Add(swScaleFilter); - // sw tonemap <= TODO: finish dovi tone mapping - + // sw tonemap if (doToneMap) { - var tonemapArgs = $"tonemapx=tonemap={options.TonemappingAlgorithm}:desat={options.TonemappingDesat}:peak={options.TonemappingPeak}:t=bt709:m=bt709:p=bt709:format={outFormat}"; + // tonemapx requires yuv420p10 input for dovi reshaping, let ffmpeg convert the frame when necessary + var tonemapFormat = requireDoviReshaping ? "yuv420p" : outFormat; + + var tonemapArgs = $"tonemapx=tonemap={options.TonemappingAlgorithm}:desat={options.TonemappingDesat}:peak={options.TonemappingPeak}:t=bt709:m=bt709:p=bt709:format={tonemapFormat}"; if (options.TonemappingParam != 0) { @@ -4803,8 +4797,34 @@ namespace MediaBrowser.Controller.MediaEncoding if (doVkTranspose || doVkTonemap || hasSubs) { // map from vaapi to vulkan/drm via interop (Polaris/gfx8+). - mainFilters.Add("hwmap=derive_device=vulkan"); - mainFilters.Add("format=vulkan"); + if (_mediaEncoder.EncoderVersion >= _minFFmpegAlteredVaVkInterop) + { + if (doVkTranspose || !_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier) + { + // disable the indirect va-drm-vk mapping since it's no longer reliable. + mainFilters.Add("hwmap=derive_device=drm"); + mainFilters.Add("format=drm_prime"); + mainFilters.Add("hwmap=derive_device=vulkan"); + mainFilters.Add("format=vulkan"); + + // workaround for libplacebo using the imported vulkan frame on gfx8. + if (!_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier) + { + mainFilters.Add("scale_vulkan"); + } + } + else if (doVkTonemap || hasSubs) + { + // non ad-hoc libplacebo also accepts drm_prime direct input. + mainFilters.Add("hwmap=derive_device=drm"); + mainFilters.Add("format=drm_prime"); + } + } + else // legacy va-vk mapping that works only in jellyfin-ffmpeg6 + { + mainFilters.Add("hwmap=derive_device=vulkan"); + mainFilters.Add("format=vulkan"); + } } else { @@ -4839,6 +4859,7 @@ namespace MediaBrowser.Controller.MediaEncoding { var libplaceboFilter = GetLibplaceboFilter(options, "bgra", doVkTonemap, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); mainFilters.Add(libplaceboFilter); + mainFilters.Add("format=vulkan"); } if (doVkTonemap && !hasSubs) @@ -5135,13 +5156,15 @@ namespace MediaBrowser.Controller.MediaEncoding return (null, null, null); } + // ReSharper disable once InconsistentNaming var isMacOS = OperatingSystem.IsMacOS(); var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty; + var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase); var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase); var isVtFullSupported = isMacOS && IsVideoToolboxFullSupported(); // legacy videotoolbox pipeline (disable hw filters) - if (!isVtEncoder + if (!(isVtEncoder || isVtDecoder) || !isVtFullSupported || !_mediaEncoder.SupportsFilter("alphasrc")) { @@ -5161,12 +5184,6 @@ namespace MediaBrowser.Controller.MediaEncoding var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase); var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase); - if (!isVtEncoder) - { - // should not happen. - return (null, null, null); - } - var inW = state.VideoStream?.Width; var inH = state.VideoStream?.Height; var reqW = state.BaseRequest.Width; @@ -5281,6 +5298,12 @@ namespace MediaBrowser.Controller.MediaEncoding if (usingHwSurface) { + if (!isVtEncoder) + { + mainFilters.Add("hwdownload"); + mainFilters.Add("format=nv12"); + } + return (mainFilters, subFilters, overlayFilters); } @@ -5294,6 +5317,12 @@ namespace MediaBrowser.Controller.MediaEncoding // this will pass-through automatically if in/out format matches. mainFilters.Insert(0, "hwupload"); mainFilters.Insert(0, "format=nv12|p010le|videotoolbox_vld"); + + if (!isVtEncoder) + { + mainFilters.Add("hwdownload"); + mainFilters.Add("format=nv12"); + } } return (mainFilters, subFilters, overlayFilters); diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index e36106e52..c767b4a51 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -66,6 +66,12 @@ namespace MediaBrowser.Controller.MediaEncoding /// <summary> /// Gets a value indicating whether the configured Vaapi device supports vulkan drm format modifier. /// </summary> + /// <value><c>true</c> if the Vaapi device supports vulkan drm format modifier, <c>false</c> otherwise.</value> + bool IsVaapiDeviceSupportVulkanDrmModifier { get; } + + /// <summary> + /// Gets a value indicating whether the configured Vaapi device supports vulkan drm interop via dma-buf. + /// </summary> /// <value><c>true</c> if the Vaapi device supports vulkan drm interop, <c>false</c> otherwise.</value> bool IsVaapiDeviceSupportVulkanDrmInterop { get; } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index ba7a14344..764230feb 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -12,6 +12,7 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using AsyncKeyedLock; +using Jellyfin.Data.Enums; using Jellyfin.Extensions; using Jellyfin.Extensions.Json; using Jellyfin.Extensions.Json.Converters; @@ -79,8 +80,14 @@ namespace MediaBrowser.MediaEncoding.Encoder private bool _isVaapiDeviceAmd = false; private bool _isVaapiDeviceInteliHD = false; private bool _isVaapiDeviceInteli965 = false; + private bool _isVaapiDeviceSupportVulkanDrmModifier = false; private bool _isVaapiDeviceSupportVulkanDrmInterop = false; + private static string[] _vulkanImageDrmFmtModifierExts = + { + "VK_EXT_image_drm_format_modifier", + }; + private static string[] _vulkanExternalMemoryDmaBufExts = { "VK_KHR_external_memory_fd", @@ -141,6 +148,9 @@ namespace MediaBrowser.MediaEncoding.Encoder public bool IsVaapiDeviceInteli965 => _isVaapiDeviceInteli965; /// <inheritdoc /> + public bool IsVaapiDeviceSupportVulkanDrmModifier => _isVaapiDeviceSupportVulkanDrmModifier; + + /// <inheritdoc /> public bool IsVaapiDeviceSupportVulkanDrmInterop => _isVaapiDeviceSupportVulkanDrmInterop; [GeneratedRegex(@"[^\/\\]+?(\.[^\/\\\n.]+)?$")] @@ -219,6 +229,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _isVaapiDeviceAmd = validator.CheckVaapiDeviceByDriverName("Mesa Gallium driver", options.VaapiDevice); _isVaapiDeviceInteliHD = validator.CheckVaapiDeviceByDriverName("Intel iHD driver", options.VaapiDevice); _isVaapiDeviceInteli965 = validator.CheckVaapiDeviceByDriverName("Intel i965 driver", options.VaapiDevice); + _isVaapiDeviceSupportVulkanDrmModifier = validator.CheckVulkanDrmDeviceByExtensionName(options.VaapiDevice, _vulkanImageDrmFmtModifierExts); _isVaapiDeviceSupportVulkanDrmInterop = validator.CheckVulkanDrmDeviceByExtensionName(options.VaapiDevice, _vulkanExternalMemoryDmaBufExts); if (_isVaapiDeviceAmd) @@ -234,6 +245,11 @@ namespace MediaBrowser.MediaEncoding.Encoder _logger.LogInformation("VAAPI device {RenderNodePath} is Intel GPU (i965)", options.VaapiDevice); } + if (_isVaapiDeviceSupportVulkanDrmModifier) + { + _logger.LogInformation("VAAPI device {RenderNodePath} supports Vulkan DRM modifier", options.VaapiDevice); + } + if (_isVaapiDeviceSupportVulkanDrmInterop) { _logger.LogInformation("VAAPI device {RenderNodePath} supports Vulkan DRM interop", options.VaapiDevice); @@ -665,18 +681,18 @@ namespace MediaBrowser.MediaEncoding.Encoder filters.Add("thumbnail=n=" + (useLargerBatchSize ? "50" : "24")); } - // Use SW tonemap on HDR10/HLG video stream only when the zscale or tonemapx filter is available. + // Use SW tonemap on HDR video stream only when the zscale or tonemapx filter is available. + // Only enable Dolby Vision tonemap when tonemapx is available var enableHdrExtraction = false; - if (string.Equals(videoStream?.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoStream?.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase)) + if (videoStream?.VideoRange == VideoRange.HDR) { if (SupportsFilter("tonemapx")) { enableHdrExtraction = true; filters.Add("tonemapx=tonemap=bt2390:desat=0:peak=100:t=bt709:m=bt709:p=bt709:format=yuv420p"); } - else if (SupportsFilter("zscale")) + else if (SupportsFilter("zscale") && videoStream.VideoRangeType != VideoRangeType.DOVI) { enableHdrExtraction = true; filters.Add("zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0:peak=100,zscale=t=bt709:m=bt709,format=yuv420p"); |
