< Summary - Jellyfin

Information
Class: MediaBrowser.MediaEncoding.Encoder.EncoderValidator
Assembly: MediaBrowser.MediaEncoding
File(s): /srv/git/jellyfin/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
Line coverage
53%
Covered lines: 202
Uncovered lines: 179
Coverable lines: 381
Total lines: 697
Line coverage: 53%
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

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 =
 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 =
 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 =
 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<FilterOptionType, (string, string)> _filterOptionsDict = new Dictionary<Filte
 1154        {
 1155            { FilterOptionType.ScaleCudaFormat, ("scale_cuda", "format") },
 1156            { FilterOptionType.TonemapCudaName, ("tonemap_cuda", "GPU accelerated HDR to SDR tonemapping") },
 1157            { FilterOptionType.TonemapOpenclBt2390, ("tonemap_opencl", "bt2390") },
 1158            { FilterOptionType.OverlayOpenclFrameSync, ("overlay_opencl", "Action to take when encountering EOF from sec
 1159            { FilterOptionType.OverlayVaapiFrameSync, ("overlay_vaapi", "Action to take when encountering EOF from secon
 1160            { FilterOptionType.OverlayVulkanFrameSync, ("overlay_vulkan", "Action to take when encountering EOF from sec
 1161            { FilterOptionType.TransposeOpenclReversal, ("transpose_opencl", "rotate by half-turn") },
 1162            { FilterOptionType.OverlayOpenclAlphaFormat, ("overlay_opencl", "alpha_format") },
 1163            { FilterOptionType.OverlayCudaAlphaFormat, ("overlay_cuda", "alpha_format") }
 1164        };
 165
 1166        private static readonly Dictionary<BitStreamFilterOptionType, (string, string)> _bsfOptionsDict = new Dictionary
 1167        {
 1168            { BitStreamFilterOptionType.HevcMetadataRemoveDovi, ("hevc_metadata", "remove_dovi") },
 1169            { BitStreamFilterOptionType.HevcMetadataRemoveHdr10Plus, ("hevc_metadata", "remove_hdr10plus") },
 1170            { BitStreamFilterOptionType.Av1MetadataRemoveDovi, ("av1_metadata", "remove_dovi") },
 1171            { BitStreamFilterOptionType.Av1MetadataRemoveHdr10Plus, ("av1_metadata", "remove_hdr10plus") },
 1172            { BitStreamFilterOptionType.DoviRpuStrip, ("dovi_rpu", "strip") }
 1173        };
 174
 175        // These are the library versions that corresponds to our minimum ffmpeg version 4.4 according to the version ta
 176        // Refers to the versions in https://ffmpeg.org/download.html
 1177        private static readonly Dictionary<string, Version> _ffmpegMinimumLibraryVersions = new Dictionary<string, Versi
 1178        {
 1179            { "libavutil", new Version(56, 70) },
 1180            { "libavcodec", new Version(58, 134) },
 1181            { "libavformat", new Version(58, 76) },
 1182            { "libavdevice", new Version(58, 13) },
 1183            { "libavfilter", new Version(7, 110) },
 1184            { "libswscale", new Version(5, 9) },
 1185            { "libswresample", new Version(3, 9) },
 1186            { "libpostproc", new Version(55, 9) }
 1187        };
 188
 189        private readonly ILogger _logger;
 190
 191        private readonly string _encoderPath;
 192
 16193        private readonly Version _minFFmpegMultiThreadedCli = new Version(7, 0);
 194
 195        public EncoderValidator(ILogger logger, string encoderPath)
 196        {
 16197            _logger = logger;
 16198            _encoderPath = encoderPath;
 16199        }
 200
 201        private enum Codec
 202        {
 203            Encoder,
 204            Decoder
 205        }
 206
 207        // When changing this, also change the minimum library versions in _ffmpegMinimumLibraryVersions
 1208        public static Version MinVersion { get; } = new Version(4, 4);
 209
 1210        public static Version? MaxVersion { get; } = null;
 211
 212        [GeneratedRegex(@"^ffmpeg version n?((?:[0-9]+\.?)+)")]
 213        private static partial Regex FfmpegVersionRegex();
 214
 215        [GeneratedRegex(@"((?<name>lib\w+)\s+(?<major>[0-9]+)\.\s*(?<minor>[0-9]+))", RegexOptions.Multiline)]
 216        private static partial Regex LibraryRegex();
 217
 218        public bool ValidateVersion()
 219        {
 220            string output;
 221            try
 222            {
 0223                output = GetProcessOutput(_encoderPath, "-version", false, null);
 0224            }
 0225            catch (Exception ex)
 226            {
 0227                _logger.LogError(ex, "Error validating encoder");
 0228                return false;
 229            }
 230
 0231            if (string.IsNullOrWhiteSpace(output))
 232            {
 0233                _logger.LogError("FFmpeg validation: The process returned no result");
 0234                return false;
 235            }
 236
 0237            _logger.LogDebug("ffmpeg output: {Output}", output);
 238
 0239            return ValidateVersionInternal(output);
 0240        }
 241
 242        internal bool ValidateVersionInternal(string versionOutput)
 243        {
 8244            if (versionOutput.Contains("Libav developers", StringComparison.OrdinalIgnoreCase))
 245            {
 0246                _logger.LogError("FFmpeg validation: avconv instead of ffmpeg is not supported");
 0247                return false;
 248            }
 249
 250            // Work out what the version under test is
 8251            var version = GetFFmpegVersionInternal(versionOutput);
 252
 8253            _logger.LogInformation("Found ffmpeg version {Version}", version is not null ? version.ToString() : "unknown
 254
 8255            if (version is null)
 256            {
 1257                if (MaxVersion is not null) // Version is unknown
 258                {
 0259                    if (MinVersion == MaxVersion)
 260                    {
 0261                        _logger.LogWarning("FFmpeg validation: We recommend version {MinVersion}", MinVersion);
 262                    }
 263                    else
 264                    {
 0265                        _logger.LogWarning("FFmpeg validation: We recommend a minimum of {MinVersion} and maximum of {Ma
 266                    }
 267                }
 268                else
 269                {
 1270                    _logger.LogWarning("FFmpeg validation: We recommend minimum version {MinVersion}", MinVersion);
 271                }
 272
 1273                return false;
 274            }
 275
 7276            if (version < MinVersion) // Version is below what we recommend
 277            {
 1278                _logger.LogWarning("FFmpeg validation: The minimum recommended version is {MinVersion}", MinVersion);
 1279                return false;
 280            }
 281
 6282            if (MaxVersion is not null && version > MaxVersion) // Version is above what we recommend
 283            {
 0284                _logger.LogWarning("FFmpeg validation: The maximum recommended version is {MaxVersion}", MaxVersion);
 0285                return false;
 286            }
 287
 6288            return true;
 289        }
 290
 0291        public IEnumerable<string> GetDecoders() => GetCodecs(Codec.Decoder);
 292
 0293        public IEnumerable<string> GetEncoders() => GetCodecs(Codec.Encoder);
 294
 0295        public IEnumerable<string> GetHwaccels() => GetHwaccelTypes();
 296
 0297        public IEnumerable<string> GetFilters() => GetFFmpegFilters();
 298
 0299        public IDictionary<FilterOptionType, bool> GetFiltersWithOption() => _filterOptionsDict
 0300            .ToDictionary(item => item.Key, item => CheckFilterWithOption(item.Value.Item1, item.Value.Item2));
 301
 0302        public IDictionary<BitStreamFilterOptionType, bool> GetBitStreamFiltersWithOption() => _bsfOptionsDict
 0303            .ToDictionary(item => item.Key, item => CheckBitStreamFilterWithOption(item.Value.Item1, item.Value.Item2));
 304
 305        public Version? GetFFmpegVersion()
 306        {
 307            string output;
 308            try
 309            {
 0310                output = GetProcessOutput(_encoderPath, "-version", false, null);
 0311            }
 0312            catch (Exception ex)
 313            {
 0314                _logger.LogError(ex, "Error validating encoder");
 0315                return null;
 316            }
 317
 0318            if (string.IsNullOrWhiteSpace(output))
 319            {
 0320                _logger.LogError("FFmpeg validation: The process returned no result");
 0321                return null;
 322            }
 323
 0324            _logger.LogDebug("ffmpeg output: {Output}", output);
 325
 0326            return GetFFmpegVersionInternal(output);
 0327        }
 328
 329        /// <summary>
 330        /// Using the output from "ffmpeg -version" work out the FFmpeg version.
 331        /// For pre-built binaries the first line should contain a string like "ffmpeg version x.y", which is easy
 332        /// to parse. If this is not available, then we try to match known library versions to FFmpeg versions.
 333        /// If that fails then we test the libraries to determine if they're newer than our minimum versions.
 334        /// </summary>
 335        /// <param name="output">The output from "ffmpeg -version".</param>
 336        /// <returns>The FFmpeg version.</returns>
 337        internal Version? GetFFmpegVersionInternal(string output)
 338        {
 339            // For pre-built binaries the FFmpeg version should be mentioned at the very start of the output
 16340            var match = FfmpegVersionRegex().Match(output);
 341
 16342            if (match.Success)
 343            {
 12344                if (Version.TryParse(match.Groups[1].ValueSpan, out var result))
 345                {
 12346                    return result;
 347                }
 348            }
 349
 4350            var versionMap = GetFFmpegLibraryVersions(output);
 351
 4352            var allVersionsValidated = true;
 353
 72354            foreach (var minimumVersion in _ffmpegMinimumLibraryVersions)
 355            {
 32356                if (versionMap.TryGetValue(minimumVersion.Key, out var foundVersion))
 357                {
 32358                    if (foundVersion >= minimumVersion.Value)
 359                    {
 16360                        _logger.LogInformation("Found {Library} version {FoundVersion} ({MinimumVersion})", minimumVersi
 361                    }
 362                    else
 363                    {
 16364                        _logger.LogWarning("Found {Library} version {FoundVersion} lower than recommended version {Minim
 16365                        allVersionsValidated = false;
 366                    }
 367                }
 368                else
 369                {
 0370                    _logger.LogError("{Library} version not found", minimumVersion.Key);
 0371                    allVersionsValidated = false;
 372                }
 373            }
 374
 4375            return allVersionsValidated ? MinVersion : null;
 376        }
 377
 378        /// <summary>
 379        /// Grabs the library names and major.minor version numbers from the 'ffmpeg -version' output
 380        /// and condenses them on to one line.  Output format is "name1=major.minor,name2=major.minor,etc.".
 381        /// </summary>
 382        /// <param name="output">The 'ffmpeg -version' output.</param>
 383        /// <returns>The library names and major.minor version numbers.</returns>
 384        private static Dictionary<string, Version> GetFFmpegLibraryVersions(string output)
 385        {
 4386            var map = new Dictionary<string, Version>();
 387
 72388            foreach (Match match in LibraryRegex().Matches(output))
 389            {
 32390                var version = new Version(
 32391                    int.Parse(match.Groups["major"].ValueSpan, CultureInfo.InvariantCulture),
 32392                    int.Parse(match.Groups["minor"].ValueSpan, CultureInfo.InvariantCulture));
 393
 32394                map.Add(match.Groups["name"].Value, version);
 395            }
 396
 4397            return map;
 398        }
 399
 400        public bool CheckVaapiDeviceByDriverName(string driverName, string renderNodePath)
 401        {
 0402            if (!OperatingSystem.IsLinux())
 403            {
 0404                return false;
 405            }
 406
 0407            if (string.IsNullOrEmpty(driverName) || string.IsNullOrEmpty(renderNodePath))
 408            {
 0409                return false;
 410            }
 411
 412            try
 413            {
 0414                var output = GetProcessOutput(_encoderPath, "-v verbose -hide_banner -init_hw_device vaapi=va:" + render
 0415                return output.Contains(driverName, StringComparison.Ordinal);
 416            }
 0417            catch (Exception ex)
 418            {
 0419                _logger.LogError(ex, "Error detecting the given vaapi render node path");
 0420                return false;
 421            }
 0422        }
 423
 424        public bool CheckVulkanDrmDeviceByExtensionName(string renderNodePath, string[] vulkanExtensions)
 425        {
 0426            if (!OperatingSystem.IsLinux())
 427            {
 0428                return false;
 429            }
 430
 0431            if (string.IsNullOrEmpty(renderNodePath))
 432            {
 0433                return false;
 434            }
 435
 436            try
 437            {
 0438                var command = "-v verbose -hide_banner -init_hw_device drm=dr:" + renderNodePath + " -init_hw_device vul
 0439                var output = GetProcessOutput(_encoderPath, command, true, null);
 0440                foreach (string ext in vulkanExtensions)
 441                {
 0442                    if (!output.Contains(ext, StringComparison.Ordinal))
 443                    {
 0444                        return false;
 445                    }
 446                }
 447
 0448                return true;
 449            }
 0450            catch (Exception ex)
 451            {
 0452                _logger.LogError(ex, "Error detecting the given drm render node path");
 0453                return false;
 454            }
 0455        }
 456
 457        [SupportedOSPlatform("macos")]
 458        public bool CheckIsVideoToolboxAv1DecodeAvailable()
 459        {
 0460            return ApplePlatformHelper.HasAv1HardwareAccel(_logger);
 461        }
 462
 463        private IEnumerable<string> GetHwaccelTypes()
 464        {
 0465            string? output = null;
 466            try
 467            {
 0468                output = GetProcessOutput(_encoderPath, "-hwaccels", false, null);
 0469            }
 0470            catch (Exception ex)
 471            {
 0472                _logger.LogError(ex, "Error detecting available hwaccel types");
 0473            }
 474
 0475            if (string.IsNullOrWhiteSpace(output))
 476            {
 0477                return [];
 478            }
 479
 0480            var found = output.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries).Skip(1).Distinct().ToList();
 0481            _logger.LogInformation("Available hwaccel types: {Types}", found);
 482
 0483            return found;
 484        }
 485
 486        public bool CheckFilterWithOption(string filter, string option)
 487        {
 0488            if (string.IsNullOrEmpty(filter) || string.IsNullOrEmpty(option))
 489            {
 0490                return false;
 491            }
 492
 493            string output;
 494            try
 495            {
 0496                output = GetProcessOutput(_encoderPath, "-h filter=" + filter, false, null);
 0497            }
 0498            catch (Exception ex)
 499            {
 0500                _logger.LogError(ex, "Error detecting the given filter");
 0501                return false;
 502            }
 503
 0504            if (output.Contains("Filter " + filter, StringComparison.Ordinal))
 505            {
 0506                return output.Contains(option, StringComparison.Ordinal);
 507            }
 508
 0509            _logger.LogWarning("Filter: {Name} with option {Option} is not available", filter, option);
 510
 0511            return false;
 0512        }
 513
 514        public bool CheckBitStreamFilterWithOption(string filter, string option)
 515        {
 0516            if (string.IsNullOrEmpty(filter) || string.IsNullOrEmpty(option))
 517            {
 0518                return false;
 519            }
 520
 521            string output;
 522            try
 523            {
 0524                output = GetProcessOutput(_encoderPath, "-h bsf=" + filter, false, null);
 0525            }
 0526            catch (Exception ex)
 527            {
 0528                _logger.LogError(ex, "Error detecting the given bit stream filter");
 0529                return false;
 530            }
 531
 0532            if (output.Contains("Bit stream filter " + filter, StringComparison.Ordinal))
 533            {
 0534                return output.Contains(option, StringComparison.Ordinal);
 535            }
 536
 0537            _logger.LogWarning("Bit stream filter: {Name} with option {Option} is not available", filter, option);
 538
 0539            return false;
 0540        }
 541
 542        public bool CheckSupportedRuntimeKey(string keyDesc, Version? ffmpegVersion)
 543        {
 0544            if (string.IsNullOrEmpty(keyDesc))
 545            {
 0546                return false;
 547            }
 548
 549            string output;
 550            try
 551            {
 552                // With multi-threaded cli support, FFmpeg 7 is less sensitive to keyboard input
 0553                var duration = ffmpegVersion >= _minFFmpegMultiThreadedCli ? 10000 : 1000;
 0554                output = GetProcessOutput(_encoderPath, $"-hide_banner -f lavfi -i nullsrc=s=1x1:d={duration} -f null -"
 0555            }
 0556            catch (Exception ex)
 557            {
 0558                _logger.LogError(ex, "Error checking supported runtime key");
 0559                return false;
 560            }
 561
 0562            return output.Contains(keyDesc, StringComparison.Ordinal);
 0563        }
 564
 565        public bool CheckSupportedHwaccelFlag(string flag)
 566        {
 0567            return !string.IsNullOrEmpty(flag) && GetProcessExitCode(_encoderPath, $"-loglevel quiet -hwaccel_flags +{fl
 568        }
 569
 570        public bool CheckSupportedProberOption(string option, string proberPath)
 571        {
 0572            return !string.IsNullOrEmpty(option) && GetProcessExitCode(proberPath, $"-loglevel quiet -f lavfi -i nullsrc
 573        }
 574
 575        private IEnumerable<string> GetCodecs(Codec codec)
 576        {
 0577            string codecstr = codec == Codec.Encoder ? "encoders" : "decoders";
 578            string output;
 579            try
 580            {
 0581                output = GetProcessOutput(_encoderPath, "-" + codecstr, false, null);
 0582            }
 0583            catch (Exception ex)
 584            {
 0585                _logger.LogError(ex, "Error detecting available {Codec}", codecstr);
 0586                return [];
 587            }
 588
 0589            if (string.IsNullOrWhiteSpace(output))
 590            {
 0591                return [];
 592            }
 593
 0594            var required = codec == Codec.Encoder ? _requiredEncoders : _requiredDecoders;
 595
 0596            var found = CodecRegex()
 0597                .Matches(output)
 0598                .Select(x => x.Groups["codec"].Value)
 0599                .Where(x => required.Contains(x));
 600
 0601            _logger.LogInformation("Available {Codec}: {Codecs}", codecstr, found);
 602
 0603            return found;
 0604        }
 605
 606        private IEnumerable<string> GetFFmpegFilters()
 607        {
 608            string output;
 609            try
 610            {
 0611                output = GetProcessOutput(_encoderPath, "-filters", false, null);
 0612            }
 0613            catch (Exception ex)
 614            {
 0615                _logger.LogError(ex, "Error detecting available filters");
 0616                return [];
 617            }
 618
 0619            if (string.IsNullOrWhiteSpace(output))
 620            {
 0621                return [];
 622            }
 623
 0624            var found = FilterRegex()
 0625                .Matches(output)
 0626                .Select(x => x.Groups["filter"].Value)
 0627                .Where(x => _requiredFilters.Contains(x));
 628
 0629            _logger.LogInformation("Available filters: {Filters}", found);
 630
 0631            return found;
 0632        }
 633
 634        private string GetProcessOutput(string path, string arguments, bool readStdErr, string? testKey)
 635        {
 0636            var redirectStandardIn = !string.IsNullOrEmpty(testKey);
 0637            using (var process = new Process
 0638            {
 0639                StartInfo = new ProcessStartInfo(path, arguments)
 0640                {
 0641                    CreateNoWindow = true,
 0642                    UseShellExecute = false,
 0643                    WindowStyle = ProcessWindowStyle.Hidden,
 0644                    ErrorDialog = false,
 0645                    RedirectStandardInput = redirectStandardIn,
 0646                    RedirectStandardOutput = true,
 0647                    RedirectStandardError = true
 0648                }
 0649            })
 650            {
 0651                _logger.LogDebug("Running {Path} {Arguments}", path, arguments);
 652
 0653                process.Start();
 654
 0655                if (redirectStandardIn)
 656                {
 0657                    using var writer = process.StandardInput;
 0658                    writer.Write(testKey);
 659                }
 660
 0661                using var reader = readStdErr ? process.StandardError : process.StandardOutput;
 0662                return reader.ReadToEnd();
 663            }
 0664        }
 665
 666        private bool GetProcessExitCode(string path, string arguments)
 667        {
 0668            using var process = new Process();
 0669            process.StartInfo = new ProcessStartInfo(path, arguments)
 0670            {
 0671                CreateNoWindow = true,
 0672                UseShellExecute = false,
 0673                WindowStyle = ProcessWindowStyle.Hidden,
 0674                ErrorDialog = false
 0675            };
 0676            _logger.LogDebug("Running {Path} {Arguments}", path, arguments);
 677
 678            try
 679            {
 0680                process.Start();
 0681                process.WaitForExit();
 0682                return process.ExitCode == 0;
 683            }
 0684            catch (Exception ex)
 685            {
 0686                _logger.LogError("Running {Path} {Arguments} failed with exception {Exception}", path, arguments, ex.Mes
 0687                return false;
 688            }
 0689        }
 690
 691        [GeneratedRegex("^\\s\\S{6}\\s(?<codec>[\\w|-]+)\\s+.+$", RegexOptions.Multiline)]
 692        private static partial Regex CodecRegex();
 693
 694        [GeneratedRegex("^\\s\\S{3}\\s(?<filter>[\\w|-]+)\\s+.+$", RegexOptions.Multiline)]
 695        private static partial Regex FilterRegex();
 696    }
 697}