< 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: 200
Uncovered lines: 183
Coverable lines: 383
Total lines: 708
Line coverage: 52.2%
Branch coverage
26%
Covered branches: 23
Total branches: 88
Branch coverage: 26.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

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.RegularExpressions;
 10using MediaBrowser.Controller.MediaEncoding;
 11using Microsoft.Extensions.Logging;
 12
 13namespace MediaBrowser.MediaEncoding.Encoder
 14{
 15    public partial class EncoderValidator
 16    {
 117        private static readonly string[] _requiredDecoders = new[]
 118        {
 119            "h264",
 120            "hevc",
 121            "vp8",
 122            "libvpx",
 123            "vp9",
 124            "libvpx-vp9",
 125            "av1",
 126            "libdav1d",
 127            "mpeg2video",
 128            "mpeg4",
 129            "msmpeg4",
 130            "dca",
 131            "ac3",
 132            "ac4",
 133            "aac",
 134            "mp3",
 135            "flac",
 136            "truehd",
 137            "h264_qsv",
 138            "hevc_qsv",
 139            "mpeg2_qsv",
 140            "vc1_qsv",
 141            "vp8_qsv",
 142            "vp9_qsv",
 143            "av1_qsv",
 144            "h264_cuvid",
 145            "hevc_cuvid",
 146            "mpeg2_cuvid",
 147            "vc1_cuvid",
 148            "mpeg4_cuvid",
 149            "vp8_cuvid",
 150            "vp9_cuvid",
 151            "av1_cuvid",
 152            "h264_rkmpp",
 153            "hevc_rkmpp",
 154            "mpeg1_rkmpp",
 155            "mpeg2_rkmpp",
 156            "mpeg4_rkmpp",
 157            "vp8_rkmpp",
 158            "vp9_rkmpp",
 159            "av1_rkmpp"
 160        };
 61
 162        private static readonly string[] _requiredEncoders = new[]
 163        {
 164            "libx264",
 165            "libx265",
 166            "libsvtav1",
 167            "aac",
 168            "aac_at",
 169            "libfdk_aac",
 170            "ac3",
 171            "alac",
 172            "dca",
 173            "libmp3lame",
 174            "libopus",
 175            "libvorbis",
 176            "flac",
 177            "truehd",
 178            "srt",
 179            "h264_amf",
 180            "hevc_amf",
 181            "av1_amf",
 182            "h264_qsv",
 183            "hevc_qsv",
 184            "mjpeg_qsv",
 185            "av1_qsv",
 186            "h264_nvenc",
 187            "hevc_nvenc",
 188            "av1_nvenc",
 189            "h264_vaapi",
 190            "hevc_vaapi",
 191            "av1_vaapi",
 192            "mjpeg_vaapi",
 193            "h264_v4l2m2m",
 194            "h264_videotoolbox",
 195            "hevc_videotoolbox",
 196            "mjpeg_videotoolbox",
 197            "h264_rkmpp",
 198            "hevc_rkmpp",
 199            "mjpeg_rkmpp"
 1100        };
 101
 1102        private static readonly string[] _requiredFilters = new[]
 1103        {
 1104            // sw
 1105            "alphasrc",
 1106            "zscale",
 1107            "tonemapx",
 1108            // qsv
 1109            "scale_qsv",
 1110            "vpp_qsv",
 1111            "deinterlace_qsv",
 1112            "overlay_qsv",
 1113            // cuda
 1114            "scale_cuda",
 1115            "yadif_cuda",
 1116            "bwdif_cuda",
 1117            "tonemap_cuda",
 1118            "overlay_cuda",
 1119            "transpose_cuda",
 1120            "hwupload_cuda",
 1121            // opencl
 1122            "scale_opencl",
 1123            "tonemap_opencl",
 1124            "overlay_opencl",
 1125            "transpose_opencl",
 1126            // vaapi
 1127            "scale_vaapi",
 1128            "deinterlace_vaapi",
 1129            "tonemap_vaapi",
 1130            "procamp_vaapi",
 1131            "overlay_vaapi",
 1132            "transpose_vaapi",
 1133            "hwupload_vaapi",
 1134            // vulkan
 1135            "libplacebo",
 1136            "scale_vulkan",
 1137            "overlay_vulkan",
 1138            "transpose_vulkan",
 1139            "flip_vulkan",
 1140            // videotoolbox
 1141            "yadif_videotoolbox",
 1142            "bwdif_videotoolbox",
 1143            "scale_vt",
 1144            "transpose_vt",
 1145            "overlay_videotoolbox",
 1146            "tonemap_videotoolbox",
 1147            // rkrga
 1148            "scale_rkrga",
 1149            "vpp_rkrga",
 1150            "overlay_rkrga"
 1151        };
 152
 1153        private static readonly Dictionary<int, string[]> _filterOptionsDict = new Dictionary<int, string[]>
 1154        {
 1155            { 0, new string[] { "scale_cuda", "format" } },
 1156            { 1, new string[] { "tonemap_cuda", "GPU accelerated HDR to SDR tonemapping" } },
 1157            { 2, new string[] { "tonemap_opencl", "bt2390" } },
 1158            { 3, new string[] { "overlay_opencl", "Action to take when encountering EOF from secondary input" } },
 1159            { 4, new string[] { "overlay_vaapi", "Action to take when encountering EOF from secondary input" } },
 1160            { 5, new string[] { "overlay_vulkan", "Action to take when encountering EOF from secondary input" } },
 1161            { 6, new string[] { "transpose_opencl", "rotate by half-turn" } }
 1162        };
 163
 1164        private static readonly Dictionary<BitStreamFilterOptionType, (string, string)> _bsfOptionsDict = new Dictionary
 1165        {
 1166            { BitStreamFilterOptionType.HevcMetadataRemoveDovi, ("hevc_metadata", "remove_dovi") },
 1167            { BitStreamFilterOptionType.HevcMetadataRemoveHdr10Plus, ("hevc_metadata", "remove_hdr10plus") },
 1168            { BitStreamFilterOptionType.Av1MetadataRemoveDovi, ("av1_metadata", "remove_dovi") },
 1169            { BitStreamFilterOptionType.Av1MetadataRemoveHdr10Plus, ("av1_metadata", "remove_hdr10plus") },
 1170            { BitStreamFilterOptionType.DoviRpuStrip, ("dovi_rpu", "strip") }
 1171        };
 172
 173        // These are the library versions that corresponds to our minimum ffmpeg version 4.4 according to the version ta
 174        // Refers to the versions in https://ffmpeg.org/download.html
 1175        private static readonly Dictionary<string, Version> _ffmpegMinimumLibraryVersions = new Dictionary<string, Versi
 1176        {
 1177            { "libavutil", new Version(56, 70) },
 1178            { "libavcodec", new Version(58, 134) },
 1179            { "libavformat", new Version(58, 76) },
 1180            { "libavdevice", new Version(58, 13) },
 1181            { "libavfilter", new Version(7, 110) },
 1182            { "libswscale", new Version(5, 9) },
 1183            { "libswresample", new Version(3, 9) },
 1184            { "libpostproc", new Version(55, 9) }
 1185        };
 186
 187        private readonly ILogger _logger;
 188
 189        private readonly string _encoderPath;
 190
 16191        private readonly Version _minFFmpegMultiThreadedCli = new Version(7, 0);
 192
 193        public EncoderValidator(ILogger logger, string encoderPath)
 194        {
 16195            _logger = logger;
 16196            _encoderPath = encoderPath;
 16197        }
 198
 199        private enum Codec
 200        {
 201            Encoder,
 202            Decoder
 203        }
 204
 205        // When changing this, also change the minimum library versions in _ffmpegMinimumLibraryVersions
 1206        public static Version MinVersion { get; } = new Version(4, 4);
 207
 1208        public static Version? MaxVersion { get; } = null;
 209
 210        [GeneratedRegex(@"^ffmpeg version n?((?:[0-9]+\.?)+)")]
 211        private static partial Regex FfmpegVersionRegex();
 212
 213        [GeneratedRegex(@"((?<name>lib\w+)\s+(?<major>[0-9]+)\.\s*(?<minor>[0-9]+))", RegexOptions.Multiline)]
 214        private static partial Regex LibraryRegex();
 215
 216        public bool ValidateVersion()
 217        {
 218            string output;
 219            try
 220            {
 0221                output = GetProcessOutput(_encoderPath, "-version", false, null);
 0222            }
 0223            catch (Exception ex)
 224            {
 0225                _logger.LogError(ex, "Error validating encoder");
 0226                return false;
 227            }
 228
 0229            if (string.IsNullOrWhiteSpace(output))
 230            {
 0231                _logger.LogError("FFmpeg validation: The process returned no result");
 0232                return false;
 233            }
 234
 0235            _logger.LogDebug("ffmpeg output: {Output}", output);
 236
 0237            return ValidateVersionInternal(output);
 0238        }
 239
 240        internal bool ValidateVersionInternal(string versionOutput)
 241        {
 8242            if (versionOutput.Contains("Libav developers", StringComparison.OrdinalIgnoreCase))
 243            {
 0244                _logger.LogError("FFmpeg validation: avconv instead of ffmpeg is not supported");
 0245                return false;
 246            }
 247
 248            // Work out what the version under test is
 8249            var version = GetFFmpegVersionInternal(versionOutput);
 250
 8251            _logger.LogInformation("Found ffmpeg version {Version}", version is not null ? version.ToString() : "unknown
 252
 8253            if (version is null)
 254            {
 1255                if (MaxVersion is not null) // Version is unknown
 256                {
 0257                    if (MinVersion == MaxVersion)
 258                    {
 0259                        _logger.LogWarning("FFmpeg validation: We recommend version {MinVersion}", MinVersion);
 260                    }
 261                    else
 262                    {
 0263                        _logger.LogWarning("FFmpeg validation: We recommend a minimum of {MinVersion} and maximum of {Ma
 264                    }
 265                }
 266                else
 267                {
 1268                    _logger.LogWarning("FFmpeg validation: We recommend minimum version {MinVersion}", MinVersion);
 269                }
 270
 1271                return false;
 272            }
 273
 7274            if (version < MinVersion) // Version is below what we recommend
 275            {
 1276                _logger.LogWarning("FFmpeg validation: The minimum recommended version is {MinVersion}", MinVersion);
 1277                return false;
 278            }
 279
 6280            if (MaxVersion is not null && version > MaxVersion) // Version is above what we recommend
 281            {
 0282                _logger.LogWarning("FFmpeg validation: The maximum recommended version is {MaxVersion}", MaxVersion);
 0283                return false;
 284            }
 285
 6286            return true;
 287        }
 288
 0289        public IEnumerable<string> GetDecoders() => GetCodecs(Codec.Decoder);
 290
 0291        public IEnumerable<string> GetEncoders() => GetCodecs(Codec.Encoder);
 292
 0293        public IEnumerable<string> GetHwaccels() => GetHwaccelTypes();
 294
 0295        public IEnumerable<string> GetFilters() => GetFFmpegFilters();
 296
 0297        public IDictionary<int, bool> GetFiltersWithOption() => GetFFmpegFiltersWithOption();
 298
 0299        public IDictionary<BitStreamFilterOptionType, bool> GetBitStreamFiltersWithOption() => _bsfOptionsDict
 0300            .ToDictionary(item => item.Key, item => CheckBitStreamFilterWithOption(item.Value.Item1, item.Value.Item2));
 301
 302        public Version? GetFFmpegVersion()
 303        {
 304            string output;
 305            try
 306            {
 0307                output = GetProcessOutput(_encoderPath, "-version", false, null);
 0308            }
 0309            catch (Exception ex)
 310            {
 0311                _logger.LogError(ex, "Error validating encoder");
 0312                return null;
 313            }
 314
 0315            if (string.IsNullOrWhiteSpace(output))
 316            {
 0317                _logger.LogError("FFmpeg validation: The process returned no result");
 0318                return null;
 319            }
 320
 0321            _logger.LogDebug("ffmpeg output: {Output}", output);
 322
 0323            return GetFFmpegVersionInternal(output);
 0324        }
 325
 326        /// <summary>
 327        /// Using the output from "ffmpeg -version" work out the FFmpeg version.
 328        /// For pre-built binaries the first line should contain a string like "ffmpeg version x.y", which is easy
 329        /// to parse. If this is not available, then we try to match known library versions to FFmpeg versions.
 330        /// If that fails then we test the libraries to determine if they're newer than our minimum versions.
 331        /// </summary>
 332        /// <param name="output">The output from "ffmpeg -version".</param>
 333        /// <returns>The FFmpeg version.</returns>
 334        internal Version? GetFFmpegVersionInternal(string output)
 335        {
 336            // For pre-built binaries the FFmpeg version should be mentioned at the very start of the output
 16337            var match = FfmpegVersionRegex().Match(output);
 338
 16339            if (match.Success)
 340            {
 12341                if (Version.TryParse(match.Groups[1].ValueSpan, out var result))
 342                {
 12343                    return result;
 344                }
 345            }
 346
 4347            var versionMap = GetFFmpegLibraryVersions(output);
 348
 4349            var allVersionsValidated = true;
 350
 72351            foreach (var minimumVersion in _ffmpegMinimumLibraryVersions)
 352            {
 32353                if (versionMap.TryGetValue(minimumVersion.Key, out var foundVersion))
 354                {
 32355                    if (foundVersion >= minimumVersion.Value)
 356                    {
 16357                        _logger.LogInformation("Found {Library} version {FoundVersion} ({MinimumVersion})", minimumVersi
 358                    }
 359                    else
 360                    {
 16361                        _logger.LogWarning("Found {Library} version {FoundVersion} lower than recommended version {Minim
 16362                        allVersionsValidated = false;
 363                    }
 364                }
 365                else
 366                {
 0367                    _logger.LogError("{Library} version not found", minimumVersion.Key);
 0368                    allVersionsValidated = false;
 369                }
 370            }
 371
 4372            return allVersionsValidated ? MinVersion : null;
 373        }
 374
 375        /// <summary>
 376        /// Grabs the library names and major.minor version numbers from the 'ffmpeg -version' output
 377        /// and condenses them on to one line.  Output format is "name1=major.minor,name2=major.minor,etc.".
 378        /// </summary>
 379        /// <param name="output">The 'ffmpeg -version' output.</param>
 380        /// <returns>The library names and major.minor version numbers.</returns>
 381        private static Dictionary<string, Version> GetFFmpegLibraryVersions(string output)
 382        {
 4383            var map = new Dictionary<string, Version>();
 384
 72385            foreach (Match match in LibraryRegex().Matches(output))
 386            {
 32387                var version = new Version(
 32388                    int.Parse(match.Groups["major"].ValueSpan, CultureInfo.InvariantCulture),
 32389                    int.Parse(match.Groups["minor"].ValueSpan, CultureInfo.InvariantCulture));
 390
 32391                map.Add(match.Groups["name"].Value, version);
 392            }
 393
 4394            return map;
 395        }
 396
 397        public bool CheckVaapiDeviceByDriverName(string driverName, string renderNodePath)
 398        {
 0399            if (!OperatingSystem.IsLinux())
 400            {
 0401                return false;
 402            }
 403
 0404            if (string.IsNullOrEmpty(driverName) || string.IsNullOrEmpty(renderNodePath))
 405            {
 0406                return false;
 407            }
 408
 409            try
 410            {
 0411                var output = GetProcessOutput(_encoderPath, "-v verbose -hide_banner -init_hw_device vaapi=va:" + render
 0412                return output.Contains(driverName, StringComparison.Ordinal);
 413            }
 0414            catch (Exception ex)
 415            {
 0416                _logger.LogError(ex, "Error detecting the given vaapi render node path");
 0417                return false;
 418            }
 0419        }
 420
 421        public bool CheckVulkanDrmDeviceByExtensionName(string renderNodePath, string[] vulkanExtensions)
 422        {
 0423            if (!OperatingSystem.IsLinux())
 424            {
 0425                return false;
 426            }
 427
 0428            if (string.IsNullOrEmpty(renderNodePath))
 429            {
 0430                return false;
 431            }
 432
 433            try
 434            {
 0435                var command = "-v verbose -hide_banner -init_hw_device drm=dr:" + renderNodePath + " -init_hw_device vul
 0436                var output = GetProcessOutput(_encoderPath, command, true, null);
 0437                foreach (string ext in vulkanExtensions)
 438                {
 0439                    if (!output.Contains(ext, StringComparison.Ordinal))
 440                    {
 0441                        return false;
 442                    }
 443                }
 444
 0445                return true;
 446            }
 0447            catch (Exception ex)
 448            {
 0449                _logger.LogError(ex, "Error detecting the given drm render node path");
 0450                return false;
 451            }
 0452        }
 453
 454        [SupportedOSPlatform("macos")]
 455        public bool CheckIsVideoToolboxAv1DecodeAvailable()
 456        {
 0457            return ApplePlatformHelper.HasAv1HardwareAccel(_logger);
 458        }
 459
 460        private IEnumerable<string> GetHwaccelTypes()
 461        {
 0462            string? output = null;
 463            try
 464            {
 0465                output = GetProcessOutput(_encoderPath, "-hwaccels", false, null);
 0466            }
 0467            catch (Exception ex)
 468            {
 0469                _logger.LogError(ex, "Error detecting available hwaccel types");
 0470            }
 471
 0472            if (string.IsNullOrWhiteSpace(output))
 473            {
 0474                return Enumerable.Empty<string>();
 475            }
 476
 0477            var found = output.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Skip(1).Distinct(
 0478            _logger.LogInformation("Available hwaccel types: {Types}", found);
 479
 0480            return found;
 481        }
 482
 483        public bool CheckFilterWithOption(string filter, string option)
 484        {
 0485            if (string.IsNullOrEmpty(filter) || string.IsNullOrEmpty(option))
 486            {
 0487                return false;
 488            }
 489
 490            string output;
 491            try
 492            {
 0493                output = GetProcessOutput(_encoderPath, "-h filter=" + filter, false, null);
 0494            }
 0495            catch (Exception ex)
 496            {
 0497                _logger.LogError(ex, "Error detecting the given filter");
 0498                return false;
 499            }
 500
 0501            if (output.Contains("Filter " + filter, StringComparison.Ordinal))
 502            {
 0503                return output.Contains(option, StringComparison.Ordinal);
 504            }
 505
 0506            _logger.LogWarning("Filter: {Name} with option {Option} is not available", filter, option);
 507
 0508            return false;
 0509        }
 510
 511        public bool CheckBitStreamFilterWithOption(string filter, string option)
 512        {
 0513            if (string.IsNullOrEmpty(filter) || string.IsNullOrEmpty(option))
 514            {
 0515                return false;
 516            }
 517
 518            string output;
 519            try
 520            {
 0521                output = GetProcessOutput(_encoderPath, "-h bsf=" + filter, false, null);
 0522            }
 0523            catch (Exception ex)
 524            {
 0525                _logger.LogError(ex, "Error detecting the given bit stream filter");
 0526                return false;
 527            }
 528
 0529            if (output.Contains("Bit stream filter " + filter, StringComparison.Ordinal))
 530            {
 0531                return output.Contains(option, StringComparison.Ordinal);
 532            }
 533
 0534            _logger.LogWarning("Bit stream filter: {Name} with option {Option} is not available", filter, option);
 535
 0536            return false;
 0537        }
 538
 539        public bool CheckSupportedRuntimeKey(string keyDesc, Version? ffmpegVersion)
 540        {
 0541            if (string.IsNullOrEmpty(keyDesc))
 542            {
 0543                return false;
 544            }
 545
 546            string output;
 547            try
 548            {
 549                // With multi-threaded cli support, FFmpeg 7 is less sensitive to keyboard input
 0550                var duration = ffmpegVersion >= _minFFmpegMultiThreadedCli ? 10000 : 1000;
 0551                output = GetProcessOutput(_encoderPath, $"-hide_banner -f lavfi -i nullsrc=s=1x1:d={duration} -f null -"
 0552            }
 0553            catch (Exception ex)
 554            {
 0555                _logger.LogError(ex, "Error checking supported runtime key");
 0556                return false;
 557            }
 558
 0559            return output.Contains(keyDesc, StringComparison.Ordinal);
 0560        }
 561
 562        public bool CheckSupportedHwaccelFlag(string flag)
 563        {
 0564            return !string.IsNullOrEmpty(flag) && GetProcessExitCode(_encoderPath, $"-loglevel quiet -hwaccel_flags +{fl
 565        }
 566
 567        public bool CheckSupportedProberOption(string option, string proberPath)
 568        {
 0569            return !string.IsNullOrEmpty(option) && GetProcessExitCode(proberPath, $"-loglevel quiet -f lavfi -i nullsrc
 570        }
 571
 572        private IEnumerable<string> GetCodecs(Codec codec)
 573        {
 0574            string codecstr = codec == Codec.Encoder ? "encoders" : "decoders";
 575            string output;
 576            try
 577            {
 0578                output = GetProcessOutput(_encoderPath, "-" + codecstr, false, null);
 0579            }
 0580            catch (Exception ex)
 581            {
 0582                _logger.LogError(ex, "Error detecting available {Codec}", codecstr);
 0583                return Enumerable.Empty<string>();
 584            }
 585
 0586            if (string.IsNullOrWhiteSpace(output))
 587            {
 0588                return Enumerable.Empty<string>();
 589            }
 590
 0591            var required = codec == Codec.Encoder ? _requiredEncoders : _requiredDecoders;
 592
 0593            var found = CodecRegex()
 0594                .Matches(output)
 0595                .Select(x => x.Groups["codec"].Value)
 0596                .Where(x => required.Contains(x));
 597
 0598            _logger.LogInformation("Available {Codec}: {Codecs}", codecstr, found);
 599
 0600            return found;
 0601        }
 602
 603        private IEnumerable<string> GetFFmpegFilters()
 604        {
 605            string output;
 606            try
 607            {
 0608                output = GetProcessOutput(_encoderPath, "-filters", false, null);
 0609            }
 0610            catch (Exception ex)
 611            {
 0612                _logger.LogError(ex, "Error detecting available filters");
 0613                return Enumerable.Empty<string>();
 614            }
 615
 0616            if (string.IsNullOrWhiteSpace(output))
 617            {
 0618                return Enumerable.Empty<string>();
 619            }
 620
 0621            var found = FilterRegex()
 0622                .Matches(output)
 0623                .Select(x => x.Groups["filter"].Value)
 0624                .Where(x => _requiredFilters.Contains(x));
 625
 0626            _logger.LogInformation("Available filters: {Filters}", found);
 627
 0628            return found;
 0629        }
 630
 631        private Dictionary<int, bool> GetFFmpegFiltersWithOption()
 632        {
 0633            Dictionary<int, bool> dict = new Dictionary<int, bool>();
 0634            for (int i = 0; i < _filterOptionsDict.Count; i++)
 635            {
 0636                if (_filterOptionsDict.TryGetValue(i, out var val) && val.Length == 2)
 637                {
 0638                    dict.Add(i, CheckFilterWithOption(val[0], val[1]));
 639                }
 640            }
 641
 0642            return dict;
 643        }
 644
 645        private string GetProcessOutput(string path, string arguments, bool readStdErr, string? testKey)
 646        {
 0647            var redirectStandardIn = !string.IsNullOrEmpty(testKey);
 0648            using (var process = new Process
 0649            {
 0650                StartInfo = new ProcessStartInfo(path, arguments)
 0651                {
 0652                    CreateNoWindow = true,
 0653                    UseShellExecute = false,
 0654                    WindowStyle = ProcessWindowStyle.Hidden,
 0655                    ErrorDialog = false,
 0656                    RedirectStandardInput = redirectStandardIn,
 0657                    RedirectStandardOutput = true,
 0658                    RedirectStandardError = true
 0659                }
 0660            })
 661            {
 0662                _logger.LogDebug("Running {Path} {Arguments}", path, arguments);
 663
 0664                process.Start();
 665
 0666                if (redirectStandardIn)
 667                {
 0668                    using var writer = process.StandardInput;
 0669                    writer.Write(testKey);
 670                }
 671
 0672                using var reader = readStdErr ? process.StandardError : process.StandardOutput;
 0673                return reader.ReadToEnd();
 674            }
 0675        }
 676
 677        private bool GetProcessExitCode(string path, string arguments)
 678        {
 0679            using var process = new Process();
 0680            process.StartInfo = new ProcessStartInfo(path, arguments)
 0681            {
 0682                CreateNoWindow = true,
 0683                UseShellExecute = false,
 0684                WindowStyle = ProcessWindowStyle.Hidden,
 0685                ErrorDialog = false
 0686            };
 0687            _logger.LogDebug("Running {Path} {Arguments}", path, arguments);
 688
 689            try
 690            {
 0691                process.Start();
 0692                process.WaitForExit();
 0693                return process.ExitCode == 0;
 694            }
 0695            catch (Exception ex)
 696            {
 0697                _logger.LogError("Running {Path} {Arguments} failed with exception {Exception}", path, arguments, ex.Mes
 0698                return false;
 699            }
 0700        }
 701
 702        [GeneratedRegex("^\\s\\S{6}\\s(?<codec>[\\w|-]+)\\s+.+$", RegexOptions.Multiline)]
 703        private static partial Regex CodecRegex();
 704
 705        [GeneratedRegex("^\\s\\S{3}\\s(?<filter>[\\w|-]+)\\s+.+$", RegexOptions.Multiline)]
 706        private static partial Regex FilterRegex();
 707    }
 708}