< Summary - Jellyfin

Information
Class: MediaBrowser.MediaEncoding.Encoder.EncoderValidator
Assembly: MediaBrowser.MediaEncoding
File(s): /srv/git/jellyfin/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
Line coverage
52%
Covered lines: 202
Uncovered lines: 185
Coverable lines: 387
Total lines: 707
Line coverage: 52.1%
Branch coverage
28%
Covered branches: 23
Total branches: 80
Branch coverage: 28.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/16/2026 - 12:15:55 AM Line coverage: 53.2% (204/383) Branch coverage: 28% (23/82) Total lines: 6997/18/2026 - 12:15:19 AM Line coverage: 52.9% (204/385) Branch coverage: 28% (23/82) Total lines: 7027/21/2026 - 12:16:33 AM Line coverage: 52.8% (203/384) Branch coverage: 28% (23/82) Total lines: 7017/26/2026 - 12:17:51 AM Line coverage: 52.1% (203/389) Branch coverage: 28% (23/82) Total lines: 70910/29/2026 - 12:17:11 AM Line coverage: 52.1% (202/387) Branch coverage: 28.7% (23/80) Total lines: 707 5/16/2026 - 12:15:55 AM Line coverage: 53.2% (204/383) Branch coverage: 28% (23/82) Total lines: 6997/18/2026 - 12:15:19 AM Line coverage: 52.9% (204/385) Branch coverage: 28% (23/82) Total lines: 7027/21/2026 - 12:16:33 AM Line coverage: 52.8% (203/384) Branch coverage: 28% (23/82) Total lines: 7017/26/2026 - 12:17:51 AM Line coverage: 52.1% (203/389) Branch coverage: 28% (23/82) Total lines: 70910/29/2026 - 12:17:11 AM Line coverage: 52.1% (202/387) Branch coverage: 28.7% (23/80) Total lines: 707

Coverage delta

Coverage delta 1 -1

Metrics

File(s)

/srv/git/jellyfin/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs

#LineLine coverage
 1#pragma warning disable CS1591
 2
 3using System;
 4using System.Collections.Generic;
 5using System.Diagnostics;
 6using System.Globalization;
 7using System.Linq;
 8using System.Runtime.Versioning;
 9using System.Text;
 10using System.Text.RegularExpressions;
 11using System.Threading.Tasks;
 12using MediaBrowser.Controller.MediaEncoding;
 13using Microsoft.Extensions.Logging;
 14
 15namespace MediaBrowser.MediaEncoding.Encoder
 16{
 17    public partial class EncoderValidator
 18    {
 119        private static readonly string[] _requiredDecoders =
 120        [
 121            "h264",
 122            "hevc",
 123            "vp8",
 124            "libvpx",
 125            "vp9",
 126            "libvpx-vp9",
 127            "av1",
 128            "libdav1d",
 129            "mpeg2video",
 130            "mpeg4",
 131            "msmpeg4",
 132            "dca",
 133            "ac3",
 134            "ac4",
 135            "aac",
 136            "mp3",
 137            "flac",
 138            "truehd",
 139            "h264_qsv",
 140            "hevc_qsv",
 141            "mpeg2_qsv",
 142            "vc1_qsv",
 143            "vp8_qsv",
 144            "vp9_qsv",
 145            "av1_qsv",
 146            "h264_cuvid",
 147            "hevc_cuvid",
 148            "mpeg2_cuvid",
 149            "vc1_cuvid",
 150            "mpeg4_cuvid",
 151            "vp8_cuvid",
 152            "vp9_cuvid",
 153            "av1_cuvid",
 154            "h264_rkmpp",
 155            "hevc_rkmpp",
 156            "mpeg1_rkmpp",
 157            "mpeg2_rkmpp",
 158            "mpeg4_rkmpp",
 159            "vp8_rkmpp",
 160            "vp9_rkmpp",
 161            "av1_rkmpp"
 162        ];
 63
 164        private static readonly string[] _requiredEncoders =
 165        [
 166            "libx264",
 167            "libx265",
 168            "libsvtav1",
 169            "aac",
 170            "aac_at",
 171            "libfdk_aac",
 172            "ac3",
 173            "alac",
 174            "dca",
 175            "libmp3lame",
 176            "libopus",
 177            "libvorbis",
 178            "flac",
 179            "truehd",
 180            "srt",
 181            "h264_amf",
 182            "hevc_amf",
 183            "av1_amf",
 184            "h264_qsv",
 185            "hevc_qsv",
 186            "mjpeg_qsv",
 187            "av1_qsv",
 188            "h264_nvenc",
 189            "hevc_nvenc",
 190            "av1_nvenc",
 191            "h264_vaapi",
 192            "hevc_vaapi",
 193            "av1_vaapi",
 194            "mjpeg_vaapi",
 195            "h264_v4l2m2m",
 196            "h264_videotoolbox",
 197            "hevc_videotoolbox",
 198            "mjpeg_videotoolbox",
 199            "h264_rkmpp",
 1100            "hevc_rkmpp",
 1101            "mjpeg_rkmpp"
 1102        ];
 103
 1104        private static readonly string[] _requiredFilters =
 1105        [
 1106            // sw
 1107            "alphasrc",
 1108            "zscale",
 1109            "tonemapx",
 1110            // qsv
 1111            "scale_qsv",
 1112            "vpp_qsv",
 1113            "deinterlace_qsv",
 1114            "overlay_qsv",
 1115            // cuda
 1116            "scale_cuda",
 1117            "yadif_cuda",
 1118            "bwdif_cuda",
 1119            "tonemap_cuda",
 1120            "overlay_cuda",
 1121            "transpose_cuda",
 1122            "hwupload_cuda",
 1123            // opencl
 1124            "scale_opencl",
 1125            "tonemap_opencl",
 1126            "overlay_opencl",
 1127            "transpose_opencl",
 1128            "yadif_opencl",
 1129            "bwdif_opencl",
 1130            // vaapi
 1131            "scale_vaapi",
 1132            "deinterlace_vaapi",
 1133            "tonemap_vaapi",
 1134            "procamp_vaapi",
 1135            "overlay_vaapi",
 1136            "transpose_vaapi",
 1137            "hwupload_vaapi",
 1138            // vulkan
 1139            "libplacebo",
 1140            "scale_vulkan",
 1141            "overlay_vulkan",
 1142            "transpose_vulkan",
 1143            "flip_vulkan",
 1144            // videotoolbox
 1145            "yadif_videotoolbox",
 1146            "bwdif_videotoolbox",
 1147            "scale_vt",
 1148            "transpose_vt",
 1149            "overlay_videotoolbox",
 1150            "tonemap_videotoolbox",
 1151            // rkrga
 1152            "scale_rkrga",
 1153            "vpp_rkrga",
 1154            "overlay_rkrga"
 1155        ];
 156
 1157        private static readonly Dictionary<FilterOptionType, (string, string)> _filterOptionsDict = new Dictionary<Filte
 1158        {
 1159            { FilterOptionType.ScaleCudaFormat, ("scale_cuda", "format") },
 1160            { FilterOptionType.TonemapCudaName, ("tonemap_cuda", "GPU accelerated HDR to SDR tonemapping") },
 1161            { FilterOptionType.TonemapOpenclBt2390, ("tonemap_opencl", "bt2390") },
 1162            { FilterOptionType.OverlayOpenclFrameSync, ("overlay_opencl", "Action to take when encountering EOF from sec
 1163            { FilterOptionType.OverlayVaapiFrameSync, ("overlay_vaapi", "Action to take when encountering EOF from secon
 1164            { FilterOptionType.OverlayVulkanFrameSync, ("overlay_vulkan", "Action to take when encountering EOF from sec
 1165            { FilterOptionType.TransposeOpenclReversal, ("transpose_opencl", "rotate by half-turn") },
 1166            { FilterOptionType.OverlayOpenclAlphaFormat, ("overlay_opencl", "alpha_format") },
 1167            { FilterOptionType.OverlayCudaAlphaFormat, ("overlay_cuda", "alpha_format") }
 1168        };
 169
 1170        private static readonly Dictionary<BitStreamFilterOptionType, (string, string)> _bsfOptionsDict = new Dictionary
 1171        {
 1172            { BitStreamFilterOptionType.HevcMetadataRemoveDovi, ("hevc_metadata", "remove_dovi") },
 1173            { BitStreamFilterOptionType.HevcMetadataRemoveHdr10Plus, ("hevc_metadata", "remove_hdr10plus") },
 1174            { BitStreamFilterOptionType.Av1MetadataRemoveDovi, ("av1_metadata", "remove_dovi") },
 1175            { BitStreamFilterOptionType.Av1MetadataRemoveHdr10Plus, ("av1_metadata", "remove_hdr10plus") },
 1176            { BitStreamFilterOptionType.DoviRpuStrip, ("dovi_rpu", "strip") }
 1177        };
 178
 179        // These are the library versions that corresponds to our minimum ffmpeg version 4.4 according to the version ta
 180        // Refers to the versions in https://ffmpeg.org/download.html
 1181        private static readonly Dictionary<string, Version> _ffmpegMinimumLibraryVersions = new Dictionary<string, Versi
 1182        {
 1183            { "libavutil", new Version(56, 70) },
 1184            { "libavcodec", new Version(58, 134) },
 1185            { "libavformat", new Version(58, 76) },
 1186            { "libavdevice", new Version(58, 13) },
 1187            { "libavfilter", new Version(7, 110) },
 1188            { "libswscale", new Version(5, 9) },
 1189            { "libswresample", new Version(3, 9) }
 1190        };
 191
 192        private readonly ILogger _logger;
 193
 194        private readonly string _encoderPath;
 195
 196        public EncoderValidator(ILogger logger, string encoderPath)
 197        {
 18198            _logger = logger;
 18199            _encoderPath = encoderPath;
 18200        }
 201
 202        private enum Codec
 203        {
 204            Encoder,
 205            Decoder
 206        }
 207
 208        // When changing this, also change the minimum library versions in _ffmpegMinimumLibraryVersions
 1209        public static Version MinVersion { get; } = new Version(4, 4);
 210
 1211        public static Version? MaxVersion { get; } = null;
 212
 213        [GeneratedRegex(@"^ffmpeg version n?((?:[0-9]+\.?)+)")]
 214        private static partial Regex FfmpegVersionRegex();
 215
 216        [GeneratedRegex(@"((?<name>lib\w+)\s+(?<major>[0-9]+)\.\s*(?<minor>[0-9]+))", RegexOptions.Multiline)]
 217        private static partial Regex LibraryRegex();
 218
 219        public bool ValidateVersion()
 220        {
 221            string output;
 222            try
 223            {
 0224                output = GetProcessOutput(_encoderPath, "-version", false, null);
 0225            }
 0226            catch (Exception ex)
 227            {
 0228                _logger.LogError(ex, "Error validating encoder");
 0229                return false;
 230            }
 231
 0232            if (string.IsNullOrWhiteSpace(output))
 233            {
 0234                _logger.LogError("FFmpeg validation: The process returned no result");
 0235                return false;
 236            }
 237
 0238            _logger.LogDebug("ffmpeg output: {Output}", output);
 239
 0240            return ValidateVersionInternal(output);
 0241        }
 242
 243        internal bool ValidateVersionInternal(string versionOutput)
 244        {
 9245            if (versionOutput.Contains("Libav developers", StringComparison.OrdinalIgnoreCase))
 246            {
 0247                _logger.LogError("FFmpeg validation: avconv instead of ffmpeg is not supported");
 0248                return false;
 249            }
 250
 251            // Work out what the version under test is
 9252            var version = GetFFmpegVersionInternal(versionOutput);
 253
 9254            _logger.LogInformation("Found ffmpeg version {Version}", version is not null ? version.ToString() : "unknown
 255
 9256            if (version is null)
 257            {
 1258                if (MaxVersion is not null) // Version is unknown
 259                {
 0260                    if (MinVersion == MaxVersion)
 261                    {
 0262                        _logger.LogWarning("FFmpeg validation: We recommend version {MinVersion}", MinVersion);
 263                    }
 264                    else
 265                    {
 0266                        _logger.LogWarning("FFmpeg validation: We recommend a minimum of {MinVersion} and maximum of {Ma
 267                    }
 268                }
 269                else
 270                {
 1271                    _logger.LogWarning("FFmpeg validation: We recommend minimum version {MinVersion}", MinVersion);
 272                }
 273
 1274                return false;
 275            }
 276
 8277            if (version < MinVersion) // Version is below what we recommend
 278            {
 1279                _logger.LogWarning("FFmpeg validation: The minimum recommended version is {MinVersion}", MinVersion);
 1280                return false;
 281            }
 282
 7283            if (MaxVersion is not null && version > MaxVersion) // Version is above what we recommend
 284            {
 0285                _logger.LogWarning("FFmpeg validation: The maximum recommended version is {MaxVersion}", MaxVersion);
 0286                return false;
 287            }
 288
 7289            return true;
 290        }
 291
 0292        public IEnumerable<string> GetDecoders() => GetCodecs(Codec.Decoder);
 293
 0294        public IEnumerable<string> GetEncoders() => GetCodecs(Codec.Encoder);
 295
 0296        public IEnumerable<string> GetHwaccels() => GetHwaccelTypes();
 297
 0298        public IEnumerable<string> GetFilters() => GetFFmpegFilters();
 299
 0300        public IDictionary<FilterOptionType, bool> GetFiltersWithOption() => _filterOptionsDict
 0301            .ToDictionary(item => item.Key, item => CheckFilterWithOption(item.Value.Item1, item.Value.Item2));
 302
 0303        public IDictionary<BitStreamFilterOptionType, bool> GetBitStreamFiltersWithOption() => _bsfOptionsDict
 0304            .ToDictionary(item => item.Key, item => CheckBitStreamFilterWithOption(item.Value.Item1, item.Value.Item2));
 305
 306        public Version? GetFFmpegVersion()
 307        {
 308            string output;
 309            try
 310            {
 0311                output = GetProcessOutput(_encoderPath, "-version", false, null);
 0312            }
 0313            catch (Exception ex)
 314            {
 0315                _logger.LogError(ex, "Error validating encoder");
 0316                return null;
 317            }
 318
 0319            if (string.IsNullOrWhiteSpace(output))
 320            {
 0321                _logger.LogError("FFmpeg validation: The process returned no result");
 0322                return null;
 323            }
 324
 0325            _logger.LogDebug("ffmpeg output: {Output}", output);
 326
 0327            return GetFFmpegVersionInternal(output);
 0328        }
 329
 330        /// <summary>
 331        /// Using the output from "ffmpeg -version" work out the FFmpeg version.
 332        /// For pre-built binaries the first line should contain a string like "ffmpeg version x.y", which is easy
 333        /// to parse. If this is not available, then we try to match known library versions to FFmpeg versions.
 334        /// If that fails then we test the libraries to determine if they're newer than our minimum versions.
 335        /// </summary>
 336        /// <param name="output">The output from "ffmpeg -version".</param>
 337        /// <returns>The FFmpeg version.</returns>
 338        internal Version? GetFFmpegVersionInternal(string output)
 339        {
 340            // For pre-built binaries the FFmpeg version should be mentioned at the very start of the output
 18341            var match = FfmpegVersionRegex().Match(output);
 342
 18343            if (match.Success)
 344            {
 12345                if (Version.TryParse(match.Groups[1].ValueSpan, out var result))
 346                {
 12347                    return result;
 348                }
 349            }
 350
 6351            var versionMap = GetFFmpegLibraryVersions(output);
 352
 6353            var allVersionsValidated = true;
 354
 96355            foreach (var minimumVersion in _ffmpegMinimumLibraryVersions)
 356            {
 42357                if (versionMap.TryGetValue(minimumVersion.Key, out var foundVersion))
 358                {
 42359                    if (foundVersion >= minimumVersion.Value)
 360                    {
 28361                        _logger.LogInformation("Found {Library} version {FoundVersion} ({MinimumVersion})", minimumVersi
 362                    }
 363                    else
 364                    {
 14365                        _logger.LogWarning("Found {Library} version {FoundVersion} lower than recommended version {Minim
 14366                        allVersionsValidated = false;
 367                    }
 368                }
 369                else
 370                {
 0371                    _logger.LogError("{Library} version not found", minimumVersion.Key);
 0372                    allVersionsValidated = false;
 373                }
 374            }
 375
 6376            return allVersionsValidated ? MinVersion : null;
 377        }
 378
 379        /// <summary>
 380        /// Grabs the library names and major.minor version numbers from the 'ffmpeg -version' output
 381        /// and condenses them on to one line.  Output format is "name1=major.minor,name2=major.minor,etc.".
 382        /// </summary>
 383        /// <param name="output">The 'ffmpeg -version' output.</param>
 384        /// <returns>The library names and major.minor version numbers.</returns>
 385        private static Dictionary<string, Version> GetFFmpegLibraryVersions(string output)
 386        {
 6387            var map = new Dictionary<string, Version>();
 388
 104389            foreach (Match match in LibraryRegex().Matches(output))
 390            {
 46391                var version = new Version(
 46392                    int.Parse(match.Groups["major"].ValueSpan, CultureInfo.InvariantCulture),
 46393                    int.Parse(match.Groups["minor"].ValueSpan, CultureInfo.InvariantCulture));
 394
 46395                map.Add(match.Groups["name"].Value, version);
 396            }
 397
 6398            return map;
 399        }
 400
 401        public bool CheckVaapiDeviceByDriverName(string driverName, string renderNodePath)
 402        {
 0403            if (!OperatingSystem.IsLinux())
 404            {
 0405                return false;
 406            }
 407
 0408            if (string.IsNullOrEmpty(driverName) || string.IsNullOrEmpty(renderNodePath))
 409            {
 0410                return false;
 411            }
 412
 413            try
 414            {
 0415                var output = GetProcessOutput(_encoderPath, "-v verbose -hide_banner -init_hw_device vaapi=va:" + render
 0416                return output.Contains(driverName, StringComparison.Ordinal);
 417            }
 0418            catch (Exception ex)
 419            {
 0420                _logger.LogError(ex, "Error detecting the given vaapi render node path");
 0421                return false;
 422            }
 0423        }
 424
 425        public bool CheckVulkanDrmDeviceByExtensionName(string renderNodePath, string[] vulkanExtensions)
 426        {
 0427            if (!OperatingSystem.IsLinux())
 428            {
 0429                return false;
 430            }
 431
 0432            if (string.IsNullOrEmpty(renderNodePath))
 433            {
 0434                return false;
 435            }
 436
 437            try
 438            {
 0439                var command = "-v verbose -hide_banner -init_hw_device drm=dr:" + renderNodePath + " -init_hw_device vul
 0440                var output = GetProcessOutput(_encoderPath, command, true, null);
 0441                foreach (string ext in vulkanExtensions)
 442                {
 0443                    if (!output.Contains(ext, StringComparison.Ordinal))
 444                    {
 0445                        return false;
 446                    }
 447                }
 448
 0449                return true;
 450            }
 0451            catch (Exception ex)
 452            {
 0453                _logger.LogError(ex, "Error detecting the given drm render node path");
 0454                return false;
 455            }
 0456        }
 457
 458        [SupportedOSPlatform("macos")]
 459        public bool CheckIsVideoToolboxAv1DecodeAvailable()
 460        {
 0461            return ApplePlatformHelper.HasAv1HardwareAccel(_logger);
 462        }
 463
 464        private IEnumerable<string> GetHwaccelTypes()
 465        {
 0466            string? output = null;
 467            try
 468            {
 0469                output = GetProcessOutput(_encoderPath, "-hwaccels", false, null);
 0470            }
 0471            catch (Exception ex)
 472            {
 0473                _logger.LogError(ex, "Error detecting available hwaccel types");
 0474            }
 475
 0476            if (string.IsNullOrWhiteSpace(output))
 477            {
 0478                return [];
 479            }
 480
 0481            var found = output.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries).Skip(1).Distinct().ToList();
 0482            _logger.LogInformation("Available hwaccel types: {Types}", found);
 483
 0484            return found;
 485        }
 486
 487        public bool CheckFilterWithOption(string filter, string option)
 488        {
 0489            if (string.IsNullOrEmpty(filter) || string.IsNullOrEmpty(option))
 490            {
 0491                return false;
 492            }
 493
 494            string output;
 495            try
 496            {
 0497                output = GetProcessOutput(_encoderPath, "-h filter=" + filter, false, null);
 0498            }
 0499            catch (Exception ex)
 500            {
 0501                _logger.LogError(ex, "Error detecting the given filter");
 0502                return false;
 503            }
 504
 0505            if (output.Contains("Filter " + filter, StringComparison.Ordinal))
 506            {
 0507                return output.Contains(option, StringComparison.Ordinal);
 508            }
 509
 0510            _logger.LogWarning("Filter: {Name} with option {Option} is not available", filter, option);
 511
 0512            return false;
 0513        }
 514
 515        public bool CheckBitStreamFilterWithOption(string filter, string option)
 516        {
 0517            if (string.IsNullOrEmpty(filter) || string.IsNullOrEmpty(option))
 518            {
 0519                return false;
 520            }
 521
 522            string output;
 523            try
 524            {
 0525                output = GetProcessOutput(_encoderPath, "-h bsf=" + filter, false, null);
 0526            }
 0527            catch (Exception ex)
 528            {
 0529                _logger.LogError(ex, "Error detecting the given bit stream filter");
 0530                return false;
 531            }
 532
 0533            if (output.Contains("Bit stream filter " + filter, StringComparison.Ordinal))
 534            {
 0535                return output.Contains(option, StringComparison.Ordinal);
 536            }
 537
 0538            _logger.LogWarning("Bit stream filter: {Name} with option {Option} is not available", filter, option);
 539
 0540            return false;
 0541        }
 542
 543        public bool CheckSupportedRuntimeKey(string keyDesc, Version? ffmpegVersion)
 544        {
 0545            if (string.IsNullOrEmpty(keyDesc))
 546            {
 0547                return false;
 548            }
 549
 550            string output;
 551            try
 552            {
 553                // Start a dummy encode of 1x1@1fps. Send '?' to stdin to get the help/keybind text, followed by 'q' to 
 554                // As a safeguard in case 'q' doesn't stop the job, the dummy input has a max duration of 5 (realtime) s
 0555                output = GetProcessOutput(_encoderPath, $"-hide_banner -re -f lavfi -i nullsrc=s=1x1:r=1:d=5 -f null -",
 0556            }
 0557            catch (Exception ex)
 558            {
 0559                _logger.LogError(ex, "Error checking supported runtime key");
 0560                return false;
 561            }
 562
 0563            return output.Contains(keyDesc, StringComparison.Ordinal);
 0564        }
 565
 566        public bool CheckSupportedHwaccelFlag(string flag)
 567        {
 0568            return !string.IsNullOrEmpty(flag) && GetProcessExitCode(_encoderPath, $"-loglevel quiet -hwaccel_flags +{fl
 569        }
 570
 571        public bool CheckSupportedProberOption(string option, string proberPath)
 572        {
 0573            return !string.IsNullOrEmpty(option) && GetProcessExitCode(proberPath, $"-loglevel quiet -f lavfi -i nullsrc
 574        }
 575
 576        private IEnumerable<string> GetCodecs(Codec codec)
 577        {
 0578            string codecstr = codec == Codec.Encoder ? "encoders" : "decoders";
 579            string output;
 580            try
 581            {
 0582                output = GetProcessOutput(_encoderPath, "-" + codecstr, false, null);
 0583            }
 0584            catch (Exception ex)
 585            {
 0586                _logger.LogError(ex, "Error detecting available {Codec}", codecstr);
 0587                return [];
 588            }
 589
 0590            if (string.IsNullOrWhiteSpace(output))
 591            {
 0592                return [];
 593            }
 594
 0595            var required = codec == Codec.Encoder ? _requiredEncoders : _requiredDecoders;
 596
 0597            var found = CodecRegex()
 0598                .Matches(output)
 0599                .Select(x => x.Groups["codec"].Value)
 0600                .Where(x => required.Contains(x));
 601
 0602            _logger.LogInformation("Available {Codec}: {Codecs}", codecstr, found);
 603
 0604            return found;
 0605        }
 606
 607        private IEnumerable<string> GetFFmpegFilters()
 608        {
 609            string output;
 610            try
 611            {
 0612                output = GetProcessOutput(_encoderPath, "-filters", false, null);
 0613            }
 0614            catch (Exception ex)
 615            {
 0616                _logger.LogError(ex, "Error detecting available filters");
 0617                return [];
 618            }
 619
 0620            if (string.IsNullOrWhiteSpace(output))
 621            {
 0622                return [];
 623            }
 624
 0625            var found = FilterRegex()
 0626                .Matches(output)
 0627                .Select(x => x.Groups["filter"].Value)
 0628                .Where(x => _requiredFilters.Contains(x));
 629
 0630            _logger.LogInformation("Available filters: {Filters}", found);
 631
 0632            return found;
 0633        }
 634
 635        private string GetProcessOutput(string path, string arguments, bool readStdErr, string? testKey)
 636        {
 0637            var redirectStandardIn = !string.IsNullOrEmpty(testKey);
 0638            using (var process = new Process
 0639            {
 0640                StartInfo = new ProcessStartInfo(path, arguments)
 0641                {
 0642                    CreateNoWindow = true,
 0643                    UseShellExecute = false,
 0644                    WindowStyle = ProcessWindowStyle.Hidden,
 0645                    ErrorDialog = false,
 0646                    RedirectStandardInput = redirectStandardIn,
 0647                    StandardOutputEncoding = Encoding.UTF8,
 0648                    RedirectStandardOutput = true,
 0649                    StandardErrorEncoding = Encoding.UTF8,
 0650                    RedirectStandardError = true
 0651                }
 0652            })
 653            {
 0654                _logger.LogDebug("Running {Path} {Arguments}", path, arguments);
 655
 0656                process.Start();
 657
 0658                if (redirectStandardIn)
 659                {
 0660                    using var writer = process.StandardInput;
 0661                    writer.Write(testKey);
 662                }
 663
 664                // Drain both streams concurrently to prevent pipe hanging, see #17429
 0665                using var standardOutput = process.StandardOutput;
 0666                using var standardError = process.StandardError;
 0667                var standardOutputTask = standardOutput.ReadToEndAsync();
 0668                var standardErrorTask = standardError.ReadToEndAsync();
 0669                process.WaitForExit();
 0670                Task.WaitAll(standardOutputTask, standardErrorTask);
 671
 0672                return (readStdErr ? standardErrorTask : standardOutputTask).GetAwaiter().GetResult();
 673            }
 0674        }
 675
 676        private bool GetProcessExitCode(string path, string arguments)
 677        {
 0678            using var process = new Process();
 0679            process.StartInfo = new ProcessStartInfo(path, arguments)
 0680            {
 0681                CreateNoWindow = true,
 0682                UseShellExecute = false,
 0683                WindowStyle = ProcessWindowStyle.Hidden,
 0684                ErrorDialog = false
 0685            };
 0686            _logger.LogDebug("Running {Path} {Arguments}", path, arguments);
 687
 688            try
 689            {
 0690                process.Start();
 0691                process.WaitForExit();
 0692                return process.ExitCode == 0;
 693            }
 0694            catch (Exception ex)
 695            {
 0696                _logger.LogError("Running {Path} {Arguments} failed with exception {Exception}", path, arguments, ex.Mes
 0697                return false;
 698            }
 0699        }
 700
 701        [GeneratedRegex("^\\s\\S{6}\\s(?<codec>[\\w|-]+)\\s+.+$", RegexOptions.Multiline)]
 702        private static partial Regex CodecRegex();
 703
 704        [GeneratedRegex("^\\s\\S{2,3}\\s(?<filter>[\\w|-]+)\\s+.+$", RegexOptions.Multiline)]
 705        private static partial Regex FilterRegex();
 706    }
 707}