From 62be2a2ea95d1ea44989dc82c402aea8b43f404a Mon Sep 17 00:00:00 2001 From: sleepycatcoding <131554884+sleepycatcoding@users.noreply.github.com> Date: Sun, 23 Apr 2023 14:33:01 +0300 Subject: Fix subtitle encoder if webvtt is requested --- MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MediaBrowser.MediaEncoding') diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 794906c3b..a41e0b7e9 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -293,7 +293,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles return true; } - if (string.Equals(format, SubtitleFormat.VTT, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(format, SubtitleFormat.VTT, StringComparison.OrdinalIgnoreCase) || string.Equals(format, SubtitleFormat.WEBVTT, StringComparison.OrdinalIgnoreCase)) { value = new VttWriter(); return true; -- cgit v1.2.3 From 5b71cd8af9a4937ed72bfd50d44a2ad33d9edf76 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Thu, 3 Aug 2023 06:24:02 +0800 Subject: Expand AMD VA-API Vulkan filtering support to Polaris/gfx8 ROCm OpenCL runtime is not needed anymore when using HDR tone-mapping on Polaris/gfx8. This change requires jellyfin-ffmpeg5 5.1.3-4 or jellyfin-ffmpeg6 6.0-5 or newer versions. Signed-off-by: nyanmisaka --- .../MediaEncoding/EncodingHelper.cs | 22 ++++++++-------------- .../MediaEncoding/IMediaEncoder.cs | 4 ++-- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 14 ++++++-------- 3 files changed, 16 insertions(+), 24 deletions(-) (limited to 'MediaBrowser.MediaEncoding') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index a702e1003..a2a20c2ad 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -37,7 +37,7 @@ namespace MediaBrowser.Controller.MediaEncoding private readonly IMediaEncoder _mediaEncoder; private readonly ISubtitleEncoder _subtitleEncoder; private readonly IConfiguration _config; - private readonly Version _minKernelVersionAmdVkFmtModifier = new Version(5, 15); + // i915 hang was fixed by linux 6.2 (3f882f2) private readonly Version _minKerneli915Hang = new Version(5, 18); private readonly Version _maxKerneli915Hang = new Version(6, 1, 3); @@ -892,8 +892,7 @@ namespace MediaBrowser.Controller.MediaEncoding else if (_mediaEncoder.IsVaapiDeviceAmd) { if (IsVulkanFullSupported() - && _mediaEncoder.IsVaapiDeviceSupportVulkanFmtModifier - && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier) + && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop) { args.Append(GetDrmDeviceArgs(options.VaapiDevice, DrmAlias)); args.Append(GetVaapiDeviceArgs(null, null, null, DrmAlias, VaapiAlias)); @@ -4205,14 +4204,13 @@ namespace MediaBrowser.Controller.MediaEncoding // prefered vaapi + vulkan filters pipeline if (_mediaEncoder.IsVaapiDeviceAmd && isVaapiVkSupported - && _mediaEncoder.IsVaapiDeviceSupportVulkanFmtModifier - && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier) + && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop) { - // AMD radeonsi path(Vega/gfx9+, kernel>=5.15), with extra vulkan tonemap and overlay support. + // AMD radeonsi path(targeting Polaris/gfx8+), with extra vulkan tonemap and overlay support. return GetAmdVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder); } - // Intel i965 and Amd radeonsi/r600 path(Polaris/gfx8-), only featuring scale and deinterlace support. + // Intel i965 and Amd legacy driver path, only featuring scale and deinterlace support. return GetVaapiLimitedVidFiltersPrefered(state, options, vidDecoder, vidEncoder); } @@ -4484,7 +4482,7 @@ namespace MediaBrowser.Controller.MediaEncoding // INPUT vaapi surface(vram) if (doVkTonemap || hasSubs) { - // map from vaapi to vulkan/drm via interop (Vega/gfx9+). + // map from vaapi to vulkan/drm via interop (Polaris/gfx8+). mainFilters.Add("hwmap=derive_device=vulkan"); mainFilters.Add("format=vulkan"); } @@ -4513,9 +4511,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (doVkTonemap && !hasSubs) { // OUTPUT vaapi(nv12) surface(vram) - // map from vulkan/drm to vaapi via interop (Vega/gfx9+). - mainFilters.Add("hwmap=derive_device=drm"); - mainFilters.Add("format=drm_prime"); + // map from vulkan/drm to vaapi via interop (Polaris/gfx8+). mainFilters.Add("hwmap=derive_device=vaapi"); mainFilters.Add("format=vaapi"); @@ -4581,9 +4577,7 @@ namespace MediaBrowser.Controller.MediaEncoding else if (isVaapiEncoder) { // OUTPUT vaapi(nv12) surface(vram) - // map from vulkan/drm to vaapi via interop (Vega/gfx9+). - overlayFilters.Add("hwmap=derive_device=drm"); - overlayFilters.Add("format=drm_prime"); + // map from vulkan/drm to vaapi via interop (Polaris/gfx8+). overlayFilters.Add("hwmap=derive_device=vaapi"); overlayFilters.Add("format=vaapi"); diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index f830b9f29..4114dea4f 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -64,8 +64,8 @@ namespace MediaBrowser.Controller.MediaEncoding /// /// Gets a value indicating whether the configured Vaapi device supports vulkan drm format modifier. /// - /// true if the Vaapi device supports vulkan drm format modifier, false otherwise. - bool IsVaapiDeviceSupportVulkanFmtModifier { get; } + /// true if the Vaapi device supports vulkan drm interop, false otherwise. + bool IsVaapiDeviceSupportVulkanDrmInterop { get; } /// /// Whether given encoder codec is supported. diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 0885fbe92..9d6cdf728 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -76,12 +76,10 @@ namespace MediaBrowser.MediaEncoding.Encoder private bool _isVaapiDeviceAmd = false; private bool _isVaapiDeviceInteliHD = false; private bool _isVaapiDeviceInteli965 = false; - private bool _isVaapiDeviceSupportVulkanFmtModifier = false; + private bool _isVaapiDeviceSupportVulkanDrmInterop = false; - private static string[] _vulkanFmtModifierExts = + private static string[] _vulkanExternalMemoryDmaBufExts = { - "VK_KHR_sampler_ycbcr_conversion", - "VK_EXT_image_drm_format_modifier", "VK_KHR_external_memory_fd", "VK_EXT_external_memory_dma_buf", "VK_KHR_external_semaphore_fd", @@ -140,7 +138,7 @@ namespace MediaBrowser.MediaEncoding.Encoder public bool IsVaapiDeviceInteli965 => _isVaapiDeviceInteli965; /// - public bool IsVaapiDeviceSupportVulkanFmtModifier => _isVaapiDeviceSupportVulkanFmtModifier; + public bool IsVaapiDeviceSupportVulkanDrmInterop => _isVaapiDeviceSupportVulkanDrmInterop; [GeneratedRegex(@"[^\/\\]+?(\.[^\/\\\n.]+)?$")] private static partial Regex FfprobePathRegex(); @@ -204,7 +202,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); - _isVaapiDeviceSupportVulkanFmtModifier = validator.CheckVulkanDrmDeviceByExtensionName(options.VaapiDevice, _vulkanFmtModifierExts); + _isVaapiDeviceSupportVulkanDrmInterop = validator.CheckVulkanDrmDeviceByExtensionName(options.VaapiDevice, _vulkanExternalMemoryDmaBufExts); if (_isVaapiDeviceAmd) { @@ -219,9 +217,9 @@ namespace MediaBrowser.MediaEncoding.Encoder _logger.LogInformation("VAAPI device {RenderNodePath} is Intel GPU (i965)", options.VaapiDevice); } - if (_isVaapiDeviceSupportVulkanFmtModifier) + if (_isVaapiDeviceSupportVulkanDrmInterop) { - _logger.LogInformation("VAAPI device {RenderNodePath} supports Vulkan DRM format modifier", options.VaapiDevice); + _logger.LogInformation("VAAPI device {RenderNodePath} supports Vulkan DRM interop", options.VaapiDevice); } } } -- cgit v1.2.3 From 956e3dab43413798909a85a958231c3a16ac7b7f Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 20 Aug 2023 20:06:57 +0200 Subject: fix: accessing Standard* of a Process requires manually disposing them afterwards (#10125) --- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 2 +- MediaBrowser.Controller/MediaEncoding/JobLogger.cs | 4 +- .../Encoder/EncoderValidator.cs | 13 +++-- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 3 +- .../MediaInfo/AudioFileProber.cs | 3 +- .../FfProbe/FfProbeKeyframeExtractor.cs | 67 +++++++++++----------- 6 files changed, 50 insertions(+), 42 deletions(-) (limited to 'MediaBrowser.MediaEncoding') diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index cee8e0f9b..73ebb396d 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -620,7 +620,7 @@ public class TranscodingJobHelper : IDisposable state.TranscodingJob = transcodingJob; // Important - don't await the log task or we won't be able to kill FFmpeg when the user stops playback - _ = new JobLogger(_logger).StartStreamingLog(state, process.StandardError.BaseStream, logStream); + _ = new JobLogger(_logger).StartStreamingLog(state, process.StandardError, logStream); // Wait for the file to exist before proceeding var ffmpegTargetFile = state.WaitForPath ?? outputPath; diff --git a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs index 3b34af4e9..3d288b9f8 100644 --- a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs +++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs @@ -20,12 +20,12 @@ namespace MediaBrowser.Controller.MediaEncoding _logger = logger; } - public async Task StartStreamingLog(EncodingJobInfo state, Stream source, Stream target) + public async Task StartStreamingLog(EncodingJobInfo state, StreamReader reader, Stream target) { try { using (target) - using (var reader = new StreamReader(source)) + using (reader) { while (!reader.EndOfStream && reader.BaseStream.CanRead) { diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index 38118ed0e..db119ce5c 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -553,7 +553,8 @@ namespace MediaBrowser.MediaEncoding.Encoder private string GetProcessOutput(string path, string arguments, bool readStdErr, string? testKey) { - using (var process = new Process() + var redirectStandardIn = !string.IsNullOrEmpty(testKey); + using (var process = new Process { StartInfo = new ProcessStartInfo(path, arguments) { @@ -561,7 +562,7 @@ namespace MediaBrowser.MediaEncoding.Encoder UseShellExecute = false, WindowStyle = ProcessWindowStyle.Hidden, ErrorDialog = false, - RedirectStandardInput = !string.IsNullOrEmpty(testKey), + RedirectStandardInput = redirectStandardIn, RedirectStandardOutput = true, RedirectStandardError = true } @@ -571,12 +572,14 @@ namespace MediaBrowser.MediaEncoding.Encoder process.Start(); - if (!string.IsNullOrEmpty(testKey)) + if (redirectStandardIn) { - process.StandardInput.Write(testKey); + using var writer = process.StandardInput; + writer.Write(testKey); } - return readStdErr ? process.StandardError.ReadToEnd() : process.StandardOutput.ReadToEnd(); + using var reader = readStdErr ? process.StandardError : process.StandardOutput; + return reader.ReadToEnd(); } } } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 9d6cdf728..346e97ae1 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -511,7 +511,8 @@ namespace MediaBrowser.MediaEncoding.Encoder using (var processWrapper = new ProcessWrapper(process, this)) { StartProcess(processWrapper); - await process.StandardOutput.BaseStream.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false); + using var reader = process.StandardOutput; + await reader.BaseStream.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false); memoryStream.Seek(0, SeekOrigin.Begin); InternalMediaInfoResult result; try diff --git a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs index 9bcb1c39b..44f998742 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs @@ -130,7 +130,8 @@ namespace MediaBrowser.Providers.MediaInfo throw; } - output = await process.StandardError.ReadToEndAsync(cancellationToken).ConfigureAwait(false); + using var reader = process.StandardError; + output = await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); MatchCollection split = LUFSRegex().Matches(output); diff --git a/src/Jellyfin.MediaEncoding.Keyframes/FfProbe/FfProbeKeyframeExtractor.cs b/src/Jellyfin.MediaEncoding.Keyframes/FfProbe/FfProbeKeyframeExtractor.cs index febe9516a..479e6ffdc 100644 --- a/src/Jellyfin.MediaEncoding.Keyframes/FfProbe/FfProbeKeyframeExtractor.cs +++ b/src/Jellyfin.MediaEncoding.Keyframes/FfProbe/FfProbeKeyframeExtractor.cs @@ -68,51 +68,54 @@ public static class FfProbeKeyframeExtractor double streamDuration = 0; double formatDuration = 0; - while (!reader.EndOfStream) + using (reader) { - var line = reader.ReadLine().AsSpan(); - if (line.IsEmpty) + while (!reader.EndOfStream) { - continue; - } + var line = reader.ReadLine().AsSpan(); + if (line.IsEmpty) + { + continue; + } - var firstComma = line.IndexOf(','); - var lineType = line[..firstComma]; - var rest = line[(firstComma + 1)..]; - if (lineType.Equals("packet", StringComparison.OrdinalIgnoreCase)) - { - // Split time and flags from the packet line. Example line: packet,7169.079000,K_ - var secondComma = rest.IndexOf(','); - var ptsTime = rest[..secondComma]; - var flags = rest[(secondComma + 1)..]; - if (flags.StartsWith("K_")) + var firstComma = line.IndexOf(','); + var lineType = line[..firstComma]; + var rest = line[(firstComma + 1)..]; + if (lineType.Equals("packet", StringComparison.OrdinalIgnoreCase)) { - if (double.TryParse(ptsTime, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var keyframe)) + // Split time and flags from the packet line. Example line: packet,7169.079000,K_ + var secondComma = rest.IndexOf(','); + var ptsTime = rest[..secondComma]; + var flags = rest[(secondComma + 1)..]; + if (flags.StartsWith("K_")) { - // Have to manually convert to ticks to avoid rounding errors as TimeSpan is only precise down to 1 ms when converting double. - keyframes.Add(Convert.ToInt64(keyframe * TimeSpan.TicksPerSecond)); + if (double.TryParse(ptsTime, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var keyframe)) + { + // Have to manually convert to ticks to avoid rounding errors as TimeSpan is only precise down to 1 ms when converting double. + keyframes.Add(Convert.ToInt64(keyframe * TimeSpan.TicksPerSecond)); + } } } - } - else if (lineType.Equals("stream", StringComparison.OrdinalIgnoreCase)) - { - if (double.TryParse(rest, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var streamDurationResult)) + else if (lineType.Equals("stream", StringComparison.OrdinalIgnoreCase)) { - streamDuration = streamDurationResult; + if (double.TryParse(rest, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var streamDurationResult)) + { + streamDuration = streamDurationResult; + } } - } - else if (lineType.Equals("format", StringComparison.OrdinalIgnoreCase)) - { - if (double.TryParse(rest, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var formatDurationResult)) + else if (lineType.Equals("format", StringComparison.OrdinalIgnoreCase)) { - formatDuration = formatDurationResult; + if (double.TryParse(rest, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var formatDurationResult)) + { + formatDuration = formatDurationResult; + } } } - } - // Prefer the stream duration as it should be more accurate - var duration = streamDuration > 0 ? streamDuration : formatDuration; + // Prefer the stream duration as it should be more accurate + var duration = streamDuration > 0 ? streamDuration : formatDuration; - return new KeyframeData(TimeSpan.FromSeconds(duration).Ticks, keyframes); + return new KeyframeData(TimeSpan.FromSeconds(duration).Ticks, keyframes); + } } } -- cgit v1.2.3 From debbfaa502cae91212f2386d237616ad2a671a88 Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Thu, 31 Aug 2023 07:19:52 +0800 Subject: Fix MJPEG video is recognized as embedded image (#10173) fixes 1d729b2 Signed-off-by: nyanmisaka --- MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs | 8 +++++--- MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs | 4 +++- .../MediaInfo/EmbeddedImageProviderTests.cs | 4 +++- 3 files changed, 11 insertions(+), 5 deletions(-) (limited to 'MediaBrowser.MediaEncoding') diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index aeb08cea3..3055e6cde 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -762,9 +762,11 @@ namespace MediaBrowser.MediaEncoding.Probing && !string.Equals(streamInfo.FieldOrder, "progressive", StringComparison.OrdinalIgnoreCase); if (isAudio - || string.Equals(stream.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase) - || string.Equals(stream.Codec, "gif", StringComparison.OrdinalIgnoreCase) - || string.Equals(stream.Codec, "png", StringComparison.OrdinalIgnoreCase)) + && (string.Equals(stream.Codec, "bmp", StringComparison.OrdinalIgnoreCase) + || string.Equals(stream.Codec, "gif", StringComparison.OrdinalIgnoreCase) + || string.Equals(stream.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase) + || string.Equals(stream.Codec, "png", StringComparison.OrdinalIgnoreCase) + || string.Equals(stream.Codec, "webp", StringComparison.OrdinalIgnoreCase))) { stream.Type = MediaStreamType.EmbeddedImage; } diff --git a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs index f58f5f7a3..c24f4e2fc 100644 --- a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs @@ -177,9 +177,11 @@ namespace MediaBrowser.Providers.MediaInfo var format = imageStream.Codec switch { + "bmp" => ImageFormat.Bmp, + "gif" => ImageFormat.Gif, "mjpeg" => ImageFormat.Jpg, "png" => ImageFormat.Png, - "gif" => ImageFormat.Gif, + "webp" => ImageFormat.Webp, _ => ImageFormat.Jpg }; diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs index 6b2d9021c..2bc686a33 100644 --- a/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs @@ -98,9 +98,11 @@ namespace Jellyfin.Providers.Tests.MediaInfo [InlineData(null, null, 1, ImageType.Primary, ImageFormat.Jpg)] // no label, finds primary [InlineData("backdrop", null, 2, ImageType.Backdrop, ImageFormat.Jpg)] // uses label to find index 2, not just pulling first stream [InlineData("cover", null, 2, ImageType.Primary, ImageFormat.Jpg)] // uses label to find index 2, not just pulling first stream + [InlineData(null, "bmp", 1, ImageType.Primary, ImageFormat.Bmp)] + [InlineData(null, "gif", 1, ImageType.Primary, ImageFormat.Gif)] [InlineData(null, "mjpeg", 1, ImageType.Primary, ImageFormat.Jpg)] [InlineData(null, "png", 1, ImageType.Primary, ImageFormat.Png)] - [InlineData(null, "gif", 1, ImageType.Primary, ImageFormat.Gif)] + [InlineData(null, "webp", 1, ImageType.Primary, ImageFormat.Webp)] public async void GetImage_Embedded_ReturnsCorrectSelection(string label, string? codec, int targetIndex, ImageType type, ImageFormat? expectedFormat) { var streams = new List(); -- cgit v1.2.3