< 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: 203
Uncovered lines: 186
Coverable lines: 389
Total lines: 709
Line coverage: 52.1%
Branch coverage
28%
Covered branches: 23
Total branches: 82
Branch coverage: 28%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/1/2026 - 12:13:05 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: 709 5/1/2026 - 12:13:05 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: 709

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
 18196        private readonly Version _minFFmpegMultiThreadedCli = new Version(7, 0);
 197
 198        public EncoderValidator(ILogger logger, string encoderPath)
 199        {
 18200            _logger = logger;
 18201            _encoderPath = encoderPath;
 18202        }
 203
 204        private enum Codec
 205        {
 206            Encoder,
 207            Decoder
 208        }
 209
 210        // When changing this, also change the minimum library versions in _ffmpegMinimumLibraryVersions
 1211        public static Version MinVersion { get; } = new Version(4, 4);
 212
 1213        public static Version? MaxVersion { get; } = null;
 214
 215        [GeneratedRegex(@"^ffmpeg version n?((?:[0-9]+\.?)+)")]
 216        private static partial Regex FfmpegVersionRegex();
 217
 218        [GeneratedRegex(@"((?<name>lib\w+)\s+(?<major>[0-9]+)\.\s*(?<minor>[0-9]+))", RegexOptions.Multiline)]
 219        private static partial Regex LibraryRegex();
 220
 221        public bool ValidateVersion()
 222        {
 223            string output;
 224            try
 225            {
 0226                output = GetProcessOutput(_encoderPath, "-version", false, null);
 0227            }
 0228            catch (Exception ex)
 229            {
 0230                _logger.LogError(ex, "Error validating encoder");
 0231                return false;
 232            }
 233
 0234            if (string.IsNullOrWhiteSpace(output))
 235            {
 0236                _logger.LogError("FFmpeg validation: The process returned no result");
 0237                return false;
 238            }
 239
 0240            _logger.LogDebug("ffmpeg output: {Output}", output);
 241
 0242            return ValidateVersionInternal(output);
 0243        }
 244
 245        internal bool ValidateVersionInternal(string versionOutput)
 246        {
 9247            if (versionOutput.Contains("Libav developers", StringComparison.OrdinalIgnoreCase))
 248            {
 0249                _logger.LogError("FFmpeg validation: avconv instead of ffmpeg is not supported");
 0250                return false;
 251            }
 252
 253            // Work out what the version under test is
 9254            var version = GetFFmpegVersionInternal(versionOutput);
 255
 9256            _logger.LogInformation("Found ffmpeg version {Version}", version is not null ? version.ToString() : "unknown
 257
 9258            if (version is null)
 259            {
 1260                if (MaxVersion is not null) // Version is unknown
 261                {
 0262                    if (MinVersion == MaxVersion)
 263                    {
 0264                        _logger.LogWarning("FFmpeg validation: We recommend version {MinVersion}", MinVersion);
 265                    }
 266                    else
 267                    {
 0268                        _logger.LogWarning("FFmpeg validation: We recommend a minimum of {MinVersion} and maximum of {Ma
 269                    }
 270                }
 271                else
 272                {
 1273                    _logger.LogWarning("FFmpeg validation: We recommend minimum version {MinVersion}", MinVersion);
 274                }
 275
 1276                return false;
 277            }
 278
 8279            if (version < MinVersion) // Version is below what we recommend
 280            {
 1281                _logger.LogWarning("FFmpeg validation: The minimum recommended version is {MinVersion}", MinVersion);
 1282                return false;
 283            }
 284
 7285            if (MaxVersion is not null && version > MaxVersion) // Version is above what we recommend
 286            {
 0287                _logger.LogWarning("FFmpeg validation: The maximum recommended version is {MaxVersion}", MaxVersion);
 0288                return false;
 289            }
 290
 7291            return true;
 292        }
 293
 0294        public IEnumerable<string> GetDecoders() => GetCodecs(Codec.Decoder);
 295
 0296        public IEnumerable<string> GetEncoders() => GetCodecs(Codec.Encoder);
 297
 0298        public IEnumerable<string> GetHwaccels() => GetHwaccelTypes();
 299
 0300        public IEnumerable<string> GetFilters() => GetFFmpegFilters();
 301
 0302        public IDictionary<FilterOptionType, bool> GetFiltersWithOption() => _filterOptionsDict
 0303            .ToDictionary(item => item.Key, item => CheckFilterWithOption(item.Value.Item1, item.Value.Item2));
 304
 0305        public IDictionary<BitStreamFilterOptionType, bool> GetBitStreamFiltersWithOption() => _bsfOptionsDict
 0306            .ToDictionary(item => item.Key, item => CheckBitStreamFilterWithOption(item.Value.Item1, item.Value.Item2));
 307
 308        public Version? GetFFmpegVersion()
 309        {
 310            string output;
 311            try
 312            {
 0313                output = GetProcessOutput(_encoderPath, "-version", false, null);
 0314            }
 0315            catch (Exception ex)
 316            {
 0317                _logger.LogError(ex, "Error validating encoder");
 0318                return null;
 319            }
 320
 0321            if (string.IsNullOrWhiteSpace(output))
 322            {
 0323                _logger.LogError("FFmpeg validation: The process returned no result");
 0324                return null;
 325            }
 326
 0327            _logger.LogDebug("ffmpeg output: {Output}", output);
 328
 0329            return GetFFmpegVersionInternal(output);
 0330        }
 331
 332        /// <summary>
 333        /// Using the output from "ffmpeg -version" work out the FFmpeg version.
 334        /// For pre-built binaries the first line should contain a string like "ffmpeg version x.y", which is easy
 335        /// to parse. If this is not available, then we try to match known library versions to FFmpeg versions.
 336        /// If that fails then we test the libraries to determine if they're newer than our minimum versions.
 337        /// </summary>
 338        /// <param name="output">The output from "ffmpeg -version".</param>
 339        /// <returns>The FFmpeg version.</returns>
 340        internal Version? GetFFmpegVersionInternal(string output)
 341        {
 342            // For pre-built binaries the FFmpeg version should be mentioned at the very start of the output
 18343            var match = FfmpegVersionRegex().Match(output);
 344
 18345            if (match.Success)
 346            {
 12347                if (Version.TryParse(match.Groups[1].ValueSpan, out var result))
 348                {
 12349                    return result;
 350                }
 351            }
 352
 6353            var versionMap = GetFFmpegLibraryVersions(output);
 354
 6355            var allVersionsValidated = true;
 356
 96357            foreach (var minimumVersion in _ffmpegMinimumLibraryVersions)
 358            {
 42359                if (versionMap.TryGetValue(minimumVersion.Key, out var foundVersion))
 360                {
 42361                    if (foundVersion >= minimumVersion.Value)
 362                    {
 28363                        _logger.LogInformation("Found {Library} version {FoundVersion} ({MinimumVersion})", minimumVersi
 364                    }
 365                    else
 366                    {
 14367                        _logger.LogWarning("Found {Library} version {FoundVersion} lower than recommended version {Minim
 14368                        allVersionsValidated = false;
 369                    }
 370                }
 371                else
 372                {
 0373                    _logger.LogError("{Library} version not found", minimumVersion.Key);
 0374                    allVersionsValidated = false;
 375                }
 376            }
 377
 6378            return allVersionsValidated ? MinVersion : null;
 379        }
 380
 381        /// <summary>
 382        /// Grabs the library names and major.minor version numbers from the 'ffmpeg -version' output
 383        /// and condenses them on to one line.  Output format is "name1=major.minor,name2=major.minor,etc.".
 384        /// </summary>
 385        /// <param name="output">The 'ffmpeg -version' output.</param>
 386        /// <returns>The library names and major.minor version numbers.</returns>
 387        private static Dictionary<string, Version> GetFFmpegLibraryVersions(string output)
 388        {
 6389            var map = new Dictionary<string, Version>();
 390
 104391            foreach (Match match in LibraryRegex().Matches(output))
 392            {
 46393                var version = new Version(
 46394                    int.Parse(match.Groups["major"].ValueSpan, CultureInfo.InvariantCulture),
 46395                    int.Parse(match.Groups["minor"].ValueSpan, CultureInfo.InvariantCulture));
 396
 46397                map.Add(match.Groups["name"].Value, version);
 398            }
 399
 6400            return map;
 401        }
 402
 403        public bool CheckVaapiDeviceByDriverName(string driverName, string renderNodePath)
 404        {
 0405            if (!OperatingSystem.IsLinux())
 406            {
 0407                return false;
 408            }
 409
 0410            if (string.IsNullOrEmpty(driverName) || string.IsNullOrEmpty(renderNodePath))
 411            {
 0412                return false;
 413            }
 414
 415            try
 416            {
 0417                var output = GetProcessOutput(_encoderPath, "-v verbose -hide_banner -init_hw_device vaapi=va:" + render
 0418                return output.Contains(driverName, StringComparison.Ordinal);
 419            }
 0420            catch (Exception ex)
 421            {
 0422                _logger.LogError(ex, "Error detecting the given vaapi render node path");
 0423                return false;
 424            }
 0425        }
 426
 427        public bool CheckVulkanDrmDeviceByExtensionName(string renderNodePath, string[] vulkanExtensions)
 428        {
 0429            if (!OperatingSystem.IsLinux())
 430            {
 0431                return false;
 432            }
 433
 0434            if (string.IsNullOrEmpty(renderNodePath))
 435            {
 0436                return false;
 437            }
 438
 439            try
 440            {
 0441                var command = "-v verbose -hide_banner -init_hw_device drm=dr:" + renderNodePath + " -init_hw_device vul
 0442                var output = GetProcessOutput(_encoderPath, command, true, null);
 0443                foreach (string ext in vulkanExtensions)
 444                {
 0445                    if (!output.Contains(ext, StringComparison.Ordinal))
 446                    {
 0447                        return false;
 448                    }
 449                }
 450
 0451                return true;
 452            }
 0453            catch (Exception ex)
 454            {
 0455                _logger.LogError(ex, "Error detecting the given drm render node path");
 0456                return false;
 457            }
 0458        }
 459
 460        [SupportedOSPlatform("macos")]
 461        public bool CheckIsVideoToolboxAv1DecodeAvailable()
 462        {
 0463            return ApplePlatformHelper.HasAv1HardwareAccel(_logger);
 464        }
 465
 466        private IEnumerable<string> GetHwaccelTypes()
 467        {
 0468            string? output = null;
 469            try
 470            {
 0471                output = GetProcessOutput(_encoderPath, "-hwaccels", false, null);
 0472            }
 0473            catch (Exception ex)
 474            {
 0475                _logger.LogError(ex, "Error detecting available hwaccel types");
 0476            }
 477
 0478            if (string.IsNullOrWhiteSpace(output))
 479            {
 0480                return [];
 481            }
 482
 0483            var found = output.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries).Skip(1).Distinct().ToList();
 0484            _logger.LogInformation("Available hwaccel types: {Types}", found);
 485
 0486            return found;
 487        }
 488
 489        public bool CheckFilterWithOption(string filter, string option)
 490        {
 0491            if (string.IsNullOrEmpty(filter) || string.IsNullOrEmpty(option))
 492            {
 0493                return false;
 494            }
 495
 496            string output;
 497            try
 498            {
 0499                output = GetProcessOutput(_encoderPath, "-h filter=" + filter, false, null);
 0500            }
 0501            catch (Exception ex)
 502            {
 0503                _logger.LogError(ex, "Error detecting the given filter");
 0504                return false;
 505            }
 506
 0507            if (output.Contains("Filter " + filter, StringComparison.Ordinal))
 508            {
 0509                return output.Contains(option, StringComparison.Ordinal);
 510            }
 511
 0512            _logger.LogWarning("Filter: {Name} with option {Option} is not available", filter, option);
 513
 0514            return false;
 0515        }
 516
 517        public bool CheckBitStreamFilterWithOption(string filter, string option)
 518        {
 0519            if (string.IsNullOrEmpty(filter) || string.IsNullOrEmpty(option))
 520            {
 0521                return false;
 522            }
 523
 524            string output;
 525            try
 526            {
 0527                output = GetProcessOutput(_encoderPath, "-h bsf=" + filter, false, null);
 0528            }
 0529            catch (Exception ex)
 530            {
 0531                _logger.LogError(ex, "Error detecting the given bit stream filter");
 0532                return false;
 533            }
 534
 0535            if (output.Contains("Bit stream filter " + filter, StringComparison.Ordinal))
 536            {
 0537                return output.Contains(option, StringComparison.Ordinal);
 538            }
 539
 0540            _logger.LogWarning("Bit stream filter: {Name} with option {Option} is not available", filter, option);
 541
 0542            return false;
 0543        }
 544
 545        public bool CheckSupportedRuntimeKey(string keyDesc, Version? ffmpegVersion)
 546        {
 0547            if (string.IsNullOrEmpty(keyDesc))
 548            {
 0549                return false;
 550            }
 551
 552            string output;
 553            try
 554            {
 555                // With multi-threaded cli support, FFmpeg 7 is less sensitive to keyboard input
 0556                var duration = ffmpegVersion >= _minFFmpegMultiThreadedCli ? 10000 : 1000;
 0557                output = GetProcessOutput(_encoderPath, $"-hide_banner -f lavfi -i nullsrc=s=1x1:d={duration} -f null -"
 0558            }
 0559            catch (Exception ex)
 560            {
 0561                _logger.LogError(ex, "Error checking supported runtime key");
 0562                return false;
 563            }
 564
 0565            return output.Contains(keyDesc, StringComparison.Ordinal);
 0566        }
 567
 568        public bool CheckSupportedHwaccelFlag(string flag)
 569        {
 0570            return !string.IsNullOrEmpty(flag) && GetProcessExitCode(_encoderPath, $"-loglevel quiet -hwaccel_flags +{fl
 571        }
 572
 573        public bool CheckSupportedProberOption(string option, string proberPath)
 574        {
 0575            return !string.IsNullOrEmpty(option) && GetProcessExitCode(proberPath, $"-loglevel quiet -f lavfi -i nullsrc
 576        }
 577
 578        private IEnumerable<string> GetCodecs(Codec codec)
 579        {
 0580            string codecstr = codec == Codec.Encoder ? "encoders" : "decoders";
 581            string output;
 582            try
 583            {
 0584                output = GetProcessOutput(_encoderPath, "-" + codecstr, false, null);
 0585            }
 0586            catch (Exception ex)
 587            {
 0588                _logger.LogError(ex, "Error detecting available {Codec}", codecstr);
 0589                return [];
 590            }
 591
 0592            if (string.IsNullOrWhiteSpace(output))
 593            {
 0594                return [];
 595            }
 596
 0597            var required = codec == Codec.Encoder ? _requiredEncoders : _requiredDecoders;
 598
 0599            var found = CodecRegex()
 0600                .Matches(output)
 0601                .Select(x => x.Groups["codec"].Value)
 0602                .Where(x => required.Contains(x));
 603
 0604            _logger.LogInformation("Available {Codec}: {Codecs}", codecstr, found);
 605
 0606            return found;
 0607        }
 608
 609        private IEnumerable<string> GetFFmpegFilters()
 610        {
 611            string output;
 612            try
 613            {
 0614                output = GetProcessOutput(_encoderPath, "-filters", false, null);
 0615            }
 0616            catch (Exception ex)
 617            {
 0618                _logger.LogError(ex, "Error detecting available filters");
 0619                return [];
 620            }
 621
 0622            if (string.IsNullOrWhiteSpace(output))
 623            {
 0624                return [];
 625            }
 626
 0627            var found = FilterRegex()
 0628                .Matches(output)
 0629                .Select(x => x.Groups["filter"].Value)
 0630                .Where(x => _requiredFilters.Contains(x));
 631
 0632            _logger.LogInformation("Available filters: {Filters}", found);
 633
 0634            return found;
 0635        }
 636
 637        private string GetProcessOutput(string path, string arguments, bool readStdErr, string? testKey)
 638        {
 0639            var redirectStandardIn = !string.IsNullOrEmpty(testKey);
 0640            using (var process = new Process
 0641            {
 0642                StartInfo = new ProcessStartInfo(path, arguments)
 0643                {
 0644                    CreateNoWindow = true,
 0645                    UseShellExecute = false,
 0646                    WindowStyle = ProcessWindowStyle.Hidden,
 0647                    ErrorDialog = false,
 0648                    RedirectStandardInput = redirectStandardIn,
 0649                    StandardOutputEncoding = Encoding.UTF8,
 0650                    RedirectStandardOutput = true,
 0651                    StandardErrorEncoding = Encoding.UTF8,
 0652                    RedirectStandardError = true
 0653                }
 0654            })
 655            {
 0656                _logger.LogDebug("Running {Path} {Arguments}", path, arguments);
 657
 0658                process.Start();
 659
 0660                if (redirectStandardIn)
 661                {
 0662                    using var writer = process.StandardInput;
 0663                    writer.Write(testKey);
 664                }
 665
 666                // Drain both streams concurrently to prevent pipe hanging, see #17429
 0667                using var standardOutput = process.StandardOutput;
 0668                using var standardError = process.StandardError;
 0669                var standardOutputTask = standardOutput.ReadToEndAsync();
 0670                var standardErrorTask = standardError.ReadToEndAsync();
 0671                process.WaitForExit();
 0672                Task.WaitAll(standardOutputTask, standardErrorTask);
 673
 0674                return (readStdErr ? standardErrorTask : standardOutputTask).GetAwaiter().GetResult();
 675            }
 0676        }
 677
 678        private bool GetProcessExitCode(string path, string arguments)
 679        {
 0680            using var process = new Process();
 0681            process.StartInfo = new ProcessStartInfo(path, arguments)
 0682            {
 0683                CreateNoWindow = true,
 0684                UseShellExecute = false,
 0685                WindowStyle = ProcessWindowStyle.Hidden,
 0686                ErrorDialog = false
 0687            };
 0688            _logger.LogDebug("Running {Path} {Arguments}", path, arguments);
 689
 690            try
 691            {
 0692                process.Start();
 0693                process.WaitForExit();
 0694                return process.ExitCode == 0;
 695            }
 0696            catch (Exception ex)
 697            {
 0698                _logger.LogError("Running {Path} {Arguments} failed with exception {Exception}", path, arguments, ex.Mes
 0699                return false;
 700            }
 0701        }
 702
 703        [GeneratedRegex("^\\s\\S{6}\\s(?<codec>[\\w|-]+)\\s+.+$", RegexOptions.Multiline)]
 704        private static partial Regex CodecRegex();
 705
 706        [GeneratedRegex("^\\s\\S{2,3}\\s(?<filter>[\\w|-]+)\\s+.+$", RegexOptions.Multiline)]
 707        private static partial Regex FilterRegex();
 708    }
 709}