Implement proper FFmpeg version checking

Three routes to determine FFmpeg version:
1) Grab the 'ffmpeg version x.y' from from the -version output.  This should work for all pre-built binaries.
2) Compare the library versions against known contents of FFmpeg versions.  This is fallback aimed at custom builds.
3) Compare libavcodec version to determine if newer than latest known release.  This suggests user is running within latest/HEAD/master build.
pull/900/head
PloughPuff 5 years ago
parent 5d4bef5478
commit a6bde0943e

@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text.RegularExpressions;
using MediaBrowser.Model.Diagnostics;
@ -8,6 +8,71 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.MediaEncoding.Encoder
{
public class FFmpegVersion
{
private int _version;
private int _multi => 100;
private const int _unknown = 0;
private const int _experimental = -1;
public FFmpegVersion(int p1)
{
_version = p1;
}
public FFmpegVersion(string p1)
{
var match = Regex.Match(p1, @"(?<major>\d+)\.(?<minor>\d+)");
if (match.Groups["major"].Success && match.Groups["minor"].Success)
{
int major = int.Parse(match.Groups["major"].Value);
int minor = int.Parse(match.Groups["minor"].Value);
_version = (major * _multi) + minor;
}
}
public override string ToString()
{
switch (_version)
{
case _unknown:
return "Unknown";
case _experimental:
return "Experimental";
default:
string major = Convert.ToString(_version / _multi);
string minor = Convert.ToString(_version % _multi);
return string.Concat(major, ".", minor);
}
}
public bool Unknown()
{
return _version == _unknown;
}
public int Version()
{
return _version;
}
public bool Experimental()
{
return _version == _experimental;
}
public bool Below(FFmpegVersion checkAgainst)
{
return (_version > 0) && (_version < checkAgainst._version);
}
public bool Suitable(FFmpegVersion checkAgainst)
{
return (_version > 0) && (_version >= checkAgainst._version);
}
}
public class EncoderValidator
{
private readonly ILogger _logger;
@ -58,18 +123,127 @@ namespace MediaBrowser.MediaEncoding.Encoder
return false;
}
output = " " + output + " ";
// The minimum FFmpeg version required to run jellyfin successfully
FFmpegVersion required = new FFmpegVersion("4.0");
// Work out what the version under test is
FFmpegVersion underTest = GetFFmpegVersion(output);
for (var i = 2013; i <= 2015; i++)
if (logOutput)
{
var yearString = i.ToString(CultureInfo.InvariantCulture);
if (output.IndexOf(" " + yearString + " ", StringComparison.OrdinalIgnoreCase) != -1)
if (underTest.Unknown())
{
_logger.LogWarning("FFmpeg validation: Unknown version");
}
else if (underTest.Below(required))
{
return false;
_logger.LogWarning("FFmpeg validation: Found version {0} which is below the minimum recommended of {1}",
underTest.ToString(), required.ToString());
}
else if (underTest.Experimental())
{
_logger.LogWarning("FFmpeg validation: Unknown version: {0}?", underTest.ToString());
}
else
{
_logger.LogInformation("FFmpeg validation: Detected version {0}", underTest.ToString());
}
}
return underTest.Suitable(required);
}
/// <summary>
/// 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
/// 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
/// we have stored.
/// </summary>
/// <param name="output"></param>
/// <returns></returns>
static private FFmpegVersion GetFFmpegVersion(string 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 (\d+\.\d+)");
if (match.Success)
{
return new FFmpegVersion(match.Groups[1].Value);
}
else
{
// Try and use the individual library versions to determine a FFmpeg version
// This lookup table is to be maintained with the following command line:
// $ ./ffmpeg.exe -version | perl -ne ' print "$1=$2.$3," if /^(lib\w+)\s+(\d+)\.\s*(\d+)/'
ReadOnlyDictionary<FFmpegVersion, string> lut = new ReadOnlyDictionary<FFmpegVersion, string>
(new Dictionary<FFmpegVersion, string>
{
{ new FFmpegVersion("4.1"), "libavutil=56.22,libavcodec=58.35,libavformat=58.20,libavdevice=58.5,libavfilter=7.40,libswscale=5.3,libswresample=3.3,libpostproc=55.3," },
{ new FFmpegVersion("4.0"), "libavutil=56.14,libavcodec=58.18,libavformat=58.12,libavdevice=58.3,libavfilter=7.16,libswscale=5.1,libswresample=3.1,libpostproc=55.1," },
{ new FFmpegVersion("3.4"), "libavutil=55.78,libavcodec=57.107,libavformat=57.83,libavdevice=57.10,libavfilter=6.107,libswscale=4.8,libswresample=2.9,libpostproc=54.7," },
{ new FFmpegVersion("3.3"), "libavutil=55.58,libavcodec=57.89,libavformat=57.71,libavdevice=57.6,libavfilter=6.82,libswscale=4.6,libswresample=2.7,libpostproc=54.5," },
{ new FFmpegVersion("3.2"), "libavutil=55.34,libavcodec=57.64,libavformat=57.56,libavdevice=57.1,libavfilter=6.65,libswscale=4.2,libswresample=2.3,libpostproc=54.1," },
{ new FFmpegVersion("2.8"), "libavutil=54.31,libavcodec=56.60,libavformat=56.40,libavdevice=56.4,libavfilter=5.40,libswscale=3.1,libswresample=1.2,libpostproc=53.3," }
});
// Create a reduced version string and lookup key from dictionary
var reducedVersion = GetVersionString(output);
var found = lut.FirstOrDefault(x => x.Value == reducedVersion).Key;
if (found != null)
{
return found;
}
else
{
// Unknown version. Test the main libavcoder version in the candidate with the
// latest from the dictionary. If candidate is greater than dictionary chances are
// the user if running HEAD/master ffmpeg build (which is probably ok)
var firstElement = lut.FirstOrDefault();
var reqVer = Regex.Match(firstElement.Value, @"libavcodec=(\d+\.\d+)");
var gotVer = Regex.Match(reducedVersion, @"libavcodec=(\d+\.\d+)");
if (reqVer.Success && gotVer.Success)
{
var req = new FFmpegVersion(reqVer.Groups[1].Value);
var got = new FFmpegVersion(gotVer.Groups[1].Value);
// The library versions are not comparable with the FFmpeg version so must check
// candidate (got) against value from dictionary (req). Return Experimental if suitable
if( got.Suitable(req) )
{
return new FFmpegVersion(-1);
}
}
}
}
// Default to return Unknown
return new FFmpegVersion(0);
}
/// <summary>
/// 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."
/// </summary>
/// <param name="output"></param>
/// <returns></returns>
static private string GetVersionString(string output)
{
string pattern = @"((?<name>lib\w+)\s+(?<major>\d+)\.\s*(?<minor>\d+))";
RegexOptions options = RegexOptions.Multiline;
string rc = null;
foreach (Match m in Regex.Matches(output, pattern, options))
{
rc += string.Concat(m.Groups["name"], '=', m.Groups["major"], '.', m.Groups["minor"], ',');
}
return true;
return rc;
}
private static readonly string[] requiredDecoders = new[]

Loading…
Cancel
Save