mirror of
				https://github.com/jellyfin/jellyfin.git
				synced 2025-11-04 03:27:21 -05:00 
			
		
		
		
	Merge pull request #3216 from rotvel/feature/ffmpeg-version-check
Try harder at detecting ffmpeg version and enable the validation
This commit is contained in:
		
						commit
						eda29dbccb
					
				@ -87,7 +87,19 @@ namespace MediaBrowser.MediaEncoding.Encoder
 | 
				
			|||||||
            "hevc_videotoolbox"
 | 
					            "hevc_videotoolbox"
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Try and use the individual library versions to determine a FFmpeg version
 | 
					        // These are the library versions that corresponds to our minimum ffmpeg version 4.x according to the version table below
 | 
				
			||||||
 | 
					        private static readonly IReadOnlyDictionary<string, Version> _ffmpegMinimumLibraryVersions = new Dictionary<string, Version>
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            { "libavutil", new Version(56, 14) },
 | 
				
			||||||
 | 
					            { "libavcodec", new Version(58, 18) },
 | 
				
			||||||
 | 
					            { "libavformat", new Version(58, 12) },
 | 
				
			||||||
 | 
					            { "libavdevice", new Version(58, 3) },
 | 
				
			||||||
 | 
					            { "libavfilter", new Version(7, 16) },
 | 
				
			||||||
 | 
					            { "libswscale", new Version(5, 1) },
 | 
				
			||||||
 | 
					            { "libswresample", new Version(3, 1) },
 | 
				
			||||||
 | 
					            { "libpostproc", new Version(55, 1) }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // This lookup table is to be maintained with the following command line:
 | 
					        // This lookup table is to be maintained with the following command line:
 | 
				
			||||||
        // $ ffmpeg -version | perl -ne ' print "$1=$2.$3," if /^(lib\w+)\s+(\d+)\.\s*(\d+)/'
 | 
					        // $ ffmpeg -version | perl -ne ' print "$1=$2.$3," if /^(lib\w+)\s+(\d+)\.\s*(\d+)/'
 | 
				
			||||||
        private static readonly IReadOnlyDictionary<string, Version> _ffmpegVersionMap = new Dictionary<string, Version>
 | 
					        private static readonly IReadOnlyDictionary<string, Version> _ffmpegVersionMap = new Dictionary<string, Version>
 | 
				
			||||||
@ -156,32 +168,36 @@ namespace MediaBrowser.MediaEncoding.Encoder
 | 
				
			|||||||
            // Work out what the version under test is
 | 
					            // Work out what the version under test is
 | 
				
			||||||
            var version = GetFFmpegVersion(versionOutput);
 | 
					            var version = GetFFmpegVersion(versionOutput);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            _logger.LogInformation("Found ffmpeg version {0}", version != null ? version.ToString() : "unknown");
 | 
					            _logger.LogInformation("Found ffmpeg version {Version}", version != null ? version.ToString() : "unknown");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (version == null)
 | 
					            if (version == null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                if (MinVersion != null && MaxVersion != null) // Version is unknown
 | 
					                if (MaxVersion != null) // Version is unknown
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    if (MinVersion == MaxVersion)
 | 
					                    if (MinVersion == MaxVersion)
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        _logger.LogWarning("FFmpeg validation: We recommend ffmpeg version {0}", MinVersion);
 | 
					                        _logger.LogWarning("FFmpeg validation: We recommend version {MinVersion}", MinVersion);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    else
 | 
					                    else
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        _logger.LogWarning("FFmpeg validation: We recommend a minimum of {0} and maximum of {1}", MinVersion, MaxVersion);
 | 
					                        _logger.LogWarning("FFmpeg validation: We recommend a minimum of {MinVersion} and maximum of {MaxVersion}", MinVersion, MaxVersion);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    _logger.LogWarning("FFmpeg validation: We recommend minimum version {MinVersion}", MinVersion);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            else if (MinVersion != null && version < MinVersion) // Version is below what we recommend
 | 
					            else if (version < MinVersion) // Version is below what we recommend
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                _logger.LogWarning("FFmpeg validation: The minimum recommended ffmpeg version is {0}", MinVersion);
 | 
					                _logger.LogWarning("FFmpeg validation: The minimum recommended version is {MinVersion}", MinVersion);
 | 
				
			||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            else if (MaxVersion != null && version > MaxVersion) // Version is above what we recommend
 | 
					            else if (MaxVersion != null && version > MaxVersion) // Version is above what we recommend
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                _logger.LogWarning("FFmpeg validation: The maximum recommended ffmpeg version is {0}", MaxVersion);
 | 
					                _logger.LogWarning("FFmpeg validation: The maximum recommended version is {MaxVersion}", MaxVersion);
 | 
				
			||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -197,13 +213,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
 | 
				
			|||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Using the output from "ffmpeg -version" work out the FFmpeg version.
 | 
					        /// Using the output from "ffmpeg -version" work out the FFmpeg version.
 | 
				
			||||||
        /// For pre-built binaries the first line should contain a string like "ffmpeg version x.y", which is easy
 | 
					        /// For pre-built binaries the first line should contain a string like "ffmpeg version x.y", which is easy
 | 
				
			||||||
        /// to parse.  If this is not available, then we try to match known library versions to FFmpeg versions.
 | 
					        /// to parse. If this is not available, then we try to match known library versions to FFmpeg versions.
 | 
				
			||||||
        /// If that fails then we use one of the main libraries to determine if it's new/older than the latest
 | 
					        /// If that fails then we test the libraries to determine if they're newer than our minimum versions.
 | 
				
			||||||
        /// we have stored.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        /// <param name="output">The output from "ffmpeg -version".</param>
 | 
					        /// <param name="output">The output from "ffmpeg -version".</param>
 | 
				
			||||||
        /// <returns>The FFmpeg version.</returns>
 | 
					        /// <returns>The FFmpeg version.</returns>
 | 
				
			||||||
        internal static Version GetFFmpegVersion(string output)
 | 
					        internal Version GetFFmpegVersion(string output)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            // For pre-built binaries the FFmpeg version should be mentioned at the very start of the output
 | 
					            // For pre-built binaries the FFmpeg version should be mentioned at the very start of the output
 | 
				
			||||||
            var match = Regex.Match(output, @"^ffmpeg version n?((?:[0-9]+\.?)+)");
 | 
					            var match = Regex.Match(output, @"^ffmpeg version n?((?:[0-9]+\.?)+)");
 | 
				
			||||||
@ -214,37 +229,87 @@ namespace MediaBrowser.MediaEncoding.Encoder
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            else
 | 
					            else
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                // Create a reduced version string and lookup key from dictionary
 | 
					                if (!TryGetFFmpegLibraryVersions(output, out string versionString, out IReadOnlyDictionary<string, Version> versionMap))
 | 
				
			||||||
                var reducedVersion = GetLibrariesVersionString(output);
 | 
					                {
 | 
				
			||||||
 | 
					                    _logger.LogError("No ffmpeg library versions found");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // Try to lookup the string and return Key, otherwise if not found returns null
 | 
					                    return null;
 | 
				
			||||||
                return _ffmpegVersionMap.TryGetValue(reducedVersion, out Version version) ? version : null;
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // First try to lookup the full version string
 | 
				
			||||||
 | 
					                if (_ffmpegVersionMap.TryGetValue(versionString, out Version version))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    return version;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Then try to test for minimum library versions
 | 
				
			||||||
 | 
					                return TestMinimumFFmpegLibraryVersions(versionMap);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private Version TestMinimumFFmpegLibraryVersions(IReadOnlyDictionary<string, Version> versionMap)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var allVersionsValidated = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            foreach (var minimumVersion in _ffmpegMinimumLibraryVersions)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (versionMap.TryGetValue(minimumVersion.Key, out var foundVersion))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    if (foundVersion >= minimumVersion.Value)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        _logger.LogInformation("Found {Library} version {FoundVersion} ({MinimumVersion})", minimumVersion.Key, foundVersion, minimumVersion.Value);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    else
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        _logger.LogWarning("Found {Library} version {FoundVersion} lower than recommended version {MinimumVersion}", minimumVersion.Key, foundVersion, minimumVersion.Value);
 | 
				
			||||||
 | 
					                        allVersionsValidated = false;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    _logger.LogError("{Library} version not found", minimumVersion.Key);
 | 
				
			||||||
 | 
					                    allVersionsValidated = false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return allVersionsValidated ? MinVersion : null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Grabs the library names and major.minor version numbers from the 'ffmpeg -version' output
 | 
					        /// Grabs the library names and major.minor version numbers from the 'ffmpeg -version' output
 | 
				
			||||||
        /// and condenses them on to one line.  Output format is "name1=major.minor,name2=major.minor,etc.".
 | 
					        /// and condenses them on to one line.  Output format is "name1=major.minor,name2=major.minor,etc.".
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        /// <param name="output">The 'ffmpeg -version' output.</param>
 | 
					        /// <param name="output">The 'ffmpeg -version' output.</param>
 | 
				
			||||||
        /// <returns>The library names and major.minor version numbers.</returns>
 | 
					        /// <returns>The library names and major.minor version numbers.</returns>
 | 
				
			||||||
        private static string GetLibrariesVersionString(string output)
 | 
					        private static bool TryGetFFmpegLibraryVersions(string output, out string versionString, out IReadOnlyDictionary<string, Version> versionMap)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var rc = new StringBuilder(144);
 | 
					            var sb = new StringBuilder(144);
 | 
				
			||||||
            foreach (Match m in Regex.Matches(
 | 
					
 | 
				
			||||||
 | 
					            var map = new Dictionary<string, Version>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            foreach (Match match in Regex.Matches(
 | 
				
			||||||
                output,
 | 
					                output,
 | 
				
			||||||
                @"((?<name>lib\w+)\s+(?<major>[0-9]+)\.\s*(?<minor>[0-9]+))",
 | 
					                @"((?<name>lib\w+)\s+(?<major>[0-9]+)\.\s*(?<minor>[0-9]+))",
 | 
				
			||||||
                RegexOptions.Multiline))
 | 
					                RegexOptions.Multiline))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                rc.Append(m.Groups["name"])
 | 
					                sb.Append(match.Groups["name"])
 | 
				
			||||||
                    .Append('=')
 | 
					                    .Append('=')
 | 
				
			||||||
                    .Append(m.Groups["major"])
 | 
					                    .Append(match.Groups["major"])
 | 
				
			||||||
                    .Append('.')
 | 
					                    .Append('.')
 | 
				
			||||||
                    .Append(m.Groups["minor"])
 | 
					                    .Append(match.Groups["minor"])
 | 
				
			||||||
                    .Append(',');
 | 
					                    .Append(',');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                var str = $"{match.Groups["major"]}.{match.Groups["minor"]}";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                var version = Version.Parse(str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                map.Add(match.Groups["name"].Value, version);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return rc.Length == 0 ? null : rc.ToString();
 | 
					            versionString = sb.ToString();
 | 
				
			||||||
 | 
					            versionMap = map;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return sb.Length > 0;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private IEnumerable<string> GetHwaccelTypes()
 | 
					        private IEnumerable<string> GetHwaccelTypes()
 | 
				
			||||||
 | 
				
			|||||||
@ -196,9 +196,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
 | 
				
			|||||||
                        _logger.LogWarning("FFmpeg: {Location}: Failed version check: {Path}", location, path);
 | 
					                        _logger.LogWarning("FFmpeg: {Location}: Failed version check: {Path}", location, path);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    // ToDo - Enable the ffmpeg validator.  At the moment any version can be used.
 | 
					 | 
				
			||||||
                    rc = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    _ffmpegPath = path;
 | 
					                    _ffmpegPath = path;
 | 
				
			||||||
                    EncoderLocation = location;
 | 
					                    EncoderLocation = location;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,8 @@ namespace Jellyfin.MediaEncoding.Tests
 | 
				
			|||||||
        [ClassData(typeof(GetFFmpegVersionTestData))]
 | 
					        [ClassData(typeof(GetFFmpegVersionTestData))]
 | 
				
			||||||
        public void GetFFmpegVersionTest(string versionOutput, Version? version)
 | 
					        public void GetFFmpegVersionTest(string versionOutput, Version? version)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Assert.Equal(version, EncoderValidator.GetFFmpegVersion(versionOutput));
 | 
					            var val = new EncoderValidator(new NullLogger<EncoderValidatorTests>());
 | 
				
			||||||
 | 
					            Assert.Equal(version, val.GetFFmpegVersion(versionOutput));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Theory]
 | 
					        [Theory]
 | 
				
			||||||
@ -22,7 +23,7 @@ namespace Jellyfin.MediaEncoding.Tests
 | 
				
			|||||||
        [InlineData(EncoderValidatorTestsData.FFmpegV42Output, true)]
 | 
					        [InlineData(EncoderValidatorTestsData.FFmpegV42Output, true)]
 | 
				
			||||||
        [InlineData(EncoderValidatorTestsData.FFmpegV414Output, true)]
 | 
					        [InlineData(EncoderValidatorTestsData.FFmpegV414Output, true)]
 | 
				
			||||||
        [InlineData(EncoderValidatorTestsData.FFmpegV404Output, true)]
 | 
					        [InlineData(EncoderValidatorTestsData.FFmpegV404Output, true)]
 | 
				
			||||||
        [InlineData(EncoderValidatorTestsData.FFmpegGitUnknownOutput, false)]
 | 
					        [InlineData(EncoderValidatorTestsData.FFmpegGitUnknownOutput, true)]
 | 
				
			||||||
        public void ValidateVersionInternalTest(string versionOutput, bool valid)
 | 
					        public void ValidateVersionInternalTest(string versionOutput, bool valid)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var val = new EncoderValidator(new NullLogger<EncoderValidatorTests>());
 | 
					            var val = new EncoderValidator(new NullLogger<EncoderValidatorTests>());
 | 
				
			||||||
@ -38,7 +39,7 @@ namespace Jellyfin.MediaEncoding.Tests
 | 
				
			|||||||
                yield return new object?[] { EncoderValidatorTestsData.FFmpegV42Output, new Version(4, 2) };
 | 
					                yield return new object?[] { EncoderValidatorTestsData.FFmpegV42Output, new Version(4, 2) };
 | 
				
			||||||
                yield return new object?[] { EncoderValidatorTestsData.FFmpegV414Output, new Version(4, 1, 4) };
 | 
					                yield return new object?[] { EncoderValidatorTestsData.FFmpegV414Output, new Version(4, 1, 4) };
 | 
				
			||||||
                yield return new object?[] { EncoderValidatorTestsData.FFmpegV404Output, new Version(4, 0, 4) };
 | 
					                yield return new object?[] { EncoderValidatorTestsData.FFmpegV404Output, new Version(4, 0, 4) };
 | 
				
			||||||
                yield return new object?[] { EncoderValidatorTestsData.FFmpegGitUnknownOutput, null };
 | 
					                yield return new object?[] { EncoderValidatorTestsData.FFmpegGitUnknownOutput, new Version(4, 0) };
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
 | 
					            IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user