Browse Source

allow older ffmpeg for testing (#1485)

* allow older ffmpeg for testing

* use proper option name
pull/1486/head
Jason Dove 2 years ago committed by GitHub
parent
commit
0550c60a78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs
  2. 7
      ErsatzTV.FFmpeg/Capabilities/FFmpegCapabilities.cs
  3. 42
      ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs
  4. 1
      ErsatzTV.FFmpeg/Capabilities/IFFmpegCapabilities.cs
  5. 22
      ErsatzTV.FFmpeg/InputOption/ReadrateInputOption.cs
  6. 8
      ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs

1
ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs

@ -497,6 +497,7 @@ public class PipelineBuilderBaseTests
new System.Collections.Generic.HashSet<string>(), new System.Collections.Generic.HashSet<string>(),
new System.Collections.Generic.HashSet<string>(), new System.Collections.Generic.HashSet<string>(),
new System.Collections.Generic.HashSet<string>(), new System.Collections.Generic.HashSet<string>(),
new System.Collections.Generic.HashSet<string>(),
new System.Collections.Generic.HashSet<string>()) new System.Collections.Generic.HashSet<string>())
{ {
} }

7
ErsatzTV.FFmpeg/Capabilities/FFmpegCapabilities.cs

@ -9,17 +9,20 @@ public class FFmpegCapabilities : IFFmpegCapabilities
private readonly IReadOnlySet<string> _ffmpegDecoders; private readonly IReadOnlySet<string> _ffmpegDecoders;
private readonly IReadOnlySet<string> _ffmpegEncoders; private readonly IReadOnlySet<string> _ffmpegEncoders;
private readonly IReadOnlySet<string> _ffmpegFilters; private readonly IReadOnlySet<string> _ffmpegFilters;
private readonly IReadOnlySet<string> _ffmpegOptions;
public FFmpegCapabilities( public FFmpegCapabilities(
IReadOnlySet<string> ffmpegHardwareAccelerations, IReadOnlySet<string> ffmpegHardwareAccelerations,
IReadOnlySet<string> ffmpegDecoders, IReadOnlySet<string> ffmpegDecoders,
IReadOnlySet<string> ffmpegFilters, IReadOnlySet<string> ffmpegFilters,
IReadOnlySet<string> ffmpegEncoders) IReadOnlySet<string> ffmpegEncoders,
IReadOnlySet<string> ffmpegOptions)
{ {
_ffmpegHardwareAccelerations = ffmpegHardwareAccelerations; _ffmpegHardwareAccelerations = ffmpegHardwareAccelerations;
_ffmpegDecoders = ffmpegDecoders; _ffmpegDecoders = ffmpegDecoders;
_ffmpegFilters = ffmpegFilters; _ffmpegFilters = ffmpegFilters;
_ffmpegEncoders = ffmpegEncoders; _ffmpegEncoders = ffmpegEncoders;
_ffmpegOptions = ffmpegOptions;
} }
public bool HasHardwareAcceleration(HardwareAccelerationMode hardwareAccelerationMode) public bool HasHardwareAcceleration(HardwareAccelerationMode hardwareAccelerationMode)
@ -43,6 +46,8 @@ public class FFmpegCapabilities : IFFmpegCapabilities
public bool HasFilter(string filter) => _ffmpegFilters.Contains(filter); public bool HasFilter(string filter) => _ffmpegFilters.Contains(filter);
public bool HasOption(string ffmpegOption) => _ffmpegOptions.Contains(ffmpegOption);
public Option<IDecoder> SoftwareDecoderForVideoFormat(string videoFormat) => public Option<IDecoder> SoftwareDecoderForVideoFormat(string videoFormat) =>
videoFormat switch videoFormat switch
{ {

42
ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs

@ -46,8 +46,14 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
IReadOnlySet<string> ffmpegDecoders = await GetFFmpegCapabilities(ffmpegPath, "decoders", ParseFFmpegLine); IReadOnlySet<string> ffmpegDecoders = await GetFFmpegCapabilities(ffmpegPath, "decoders", ParseFFmpegLine);
IReadOnlySet<string> ffmpegFilters = await GetFFmpegCapabilities(ffmpegPath, "filters", ParseFFmpegLine); IReadOnlySet<string> ffmpegFilters = await GetFFmpegCapabilities(ffmpegPath, "filters", ParseFFmpegLine);
IReadOnlySet<string> ffmpegEncoders = await GetFFmpegCapabilities(ffmpegPath, "encoders", ParseFFmpegLine); IReadOnlySet<string> ffmpegEncoders = await GetFFmpegCapabilities(ffmpegPath, "encoders", ParseFFmpegLine);
IReadOnlySet<string> ffmpegOptions = await GetFFmpegOptions(ffmpegPath);
return new FFmpegCapabilities(ffmpegHardwareAccelerations, ffmpegDecoders, ffmpegFilters, ffmpegEncoders);
return new FFmpegCapabilities(
ffmpegHardwareAccelerations,
ffmpegDecoders,
ffmpegFilters,
ffmpegEncoders,
ffmpegOptions);
} }
public async Task<IHardwareCapabilities> GetHardwareCapabilities( public async Task<IHardwareCapabilities> GetHardwareCapabilities(
@ -185,6 +191,31 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
.Bind(l => parseLine(l)) .Bind(l => parseLine(l))
.ToImmutableHashSet(); .ToImmutableHashSet();
} }
private async Task<IReadOnlySet<string>> GetFFmpegOptions(string ffmpegPath)
{
var cacheKey = string.Format(CultureInfo.InvariantCulture, FFmpegCapabilitiesCacheKeyFormat, "options");
if (_memoryCache.TryGetValue(cacheKey, out IReadOnlySet<string>? cachedCapabilities) &&
cachedCapabilities is not null)
{
return cachedCapabilities;
}
string[] arguments = { "-hide_banner", "-h", "long" };
BufferedCommandResult result = await Cli.Wrap(ffmpegPath)
.WithArguments(arguments)
.WithValidation(CommandResultValidation.None)
.ExecuteBufferedAsync(Encoding.UTF8);
string output = string.IsNullOrWhiteSpace(result.StandardOutput)
? result.StandardError
: result.StandardOutput;
return output.Split("\n").Map(s => s.Trim())
.Bind(l => ParseFFmpegOptionLine(l))
.ToImmutableHashSet();
}
private static Option<string> ParseFFmpegAccelLine(string input) private static Option<string> ParseFFmpegAccelLine(string input)
{ {
@ -200,6 +231,13 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
return match.Success ? match.Groups[1].Value : Option<string>.None; return match.Success ? match.Groups[1].Value : Option<string>.None;
} }
private static Option<string> ParseFFmpegOptionLine(string input)
{
const string PATTERN = @"^-([a-z_]+)\s+.*";
Match match = Regex.Match(input, PATTERN);
return match.Success ? match.Groups[1].Value : Option<string>.None;
}
private async Task<IHardwareCapabilities> GetVaapiCapabilities( private async Task<IHardwareCapabilities> GetVaapiCapabilities(
Option<string> vaapiDriver, Option<string> vaapiDriver,
Option<string> vaapiDevice) Option<string> vaapiDevice)

1
ErsatzTV.FFmpeg/Capabilities/IFFmpegCapabilities.cs

@ -8,5 +8,6 @@ public interface IFFmpegCapabilities
bool HasDecoder(string decoder); bool HasDecoder(string decoder);
bool HasEncoder(string encoder); bool HasEncoder(string encoder);
bool HasFilter(string filter); bool HasFilter(string filter);
bool HasOption(string ffmpegOption);
Option<IDecoder> SoftwareDecoderForVideoFormat(string videoFormat); Option<IDecoder> SoftwareDecoderForVideoFormat(string videoFormat);
} }

22
ErsatzTV.FFmpeg/InputOption/ReadrateInputOption.cs

@ -1,17 +1,26 @@
using System.Globalization; using System.Globalization;
using ErsatzTV.FFmpeg.Capabilities;
using ErsatzTV.FFmpeg.Environment; using ErsatzTV.FFmpeg.Environment;
using Microsoft.Extensions.Logging;
namespace ErsatzTV.FFmpeg.InputOption; namespace ErsatzTV.FFmpeg.InputOption;
public class ReadrateInputOption : IInputOption public class ReadrateInputOption : IInputOption
{ {
private readonly IFFmpegCapabilities _ffmpegCapabilities;
private readonly int _initialBurstSeconds; private readonly int _initialBurstSeconds;
private readonly ILogger _logger;
public ReadrateInputOption(int initialBurstSeconds = 0) public ReadrateInputOption(
IFFmpegCapabilities ffmpegCapabilities,
int initialBurstSeconds,
ILogger logger)
{ {
_ffmpegCapabilities = ffmpegCapabilities;
_initialBurstSeconds = initialBurstSeconds; _initialBurstSeconds = initialBurstSeconds;
_logger = logger;
} }
public IList<EnvironmentVariable> EnvironmentVariables => Array.Empty<EnvironmentVariable>(); public IList<EnvironmentVariable> EnvironmentVariables => Array.Empty<EnvironmentVariable>();
public IList<string> GlobalOptions => Array.Empty<string>(); public IList<string> GlobalOptions => Array.Empty<string>();
@ -22,6 +31,15 @@ public class ReadrateInputOption : IInputOption
if (_initialBurstSeconds > 0) if (_initialBurstSeconds > 0)
{ {
if (!_ffmpegCapabilities.HasOption("readrate_initial_burst"))
{
_logger.LogWarning(
"FFmpeg is missing {Option} option; unable to transcode faster than realtime",
"readrate_initial_burst");
return result;
}
result.AddRange( result.AddRange(
new[] new[]
{ {

8
ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs

@ -84,7 +84,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
}; };
concatInputFile.AddOption(new ConcatInputFormat()); concatInputFile.AddOption(new ConcatInputFormat());
concatInputFile.AddOption(new ReadrateInputOption()); concatInputFile.AddOption(new ReadrateInputOption(_ffmpegCapabilities, 0, _logger));
concatInputFile.AddOption(new InfiniteLoopInputOption(HardwareAccelerationMode.None)); concatInputFile.AddOption(new InfiniteLoopInputOption(HardwareAccelerationMode.None));
foreach (int threadCount in ffmpegState.ThreadCount) foreach (int threadCount in ffmpegState.ThreadCount)
@ -130,7 +130,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
new EncoderCopyAll() new EncoderCopyAll()
}; };
concatInputFile.AddOption(new ReadrateInputOption()); concatInputFile.AddOption(new ReadrateInputOption(_ffmpegCapabilities, 0, _logger));
SetMetadataServiceProvider(ffmpegState, pipelineSteps); SetMetadataServiceProvider(ffmpegState, pipelineSteps);
SetMetadataServiceName(ffmpegState, pipelineSteps); SetMetadataServiceName(ffmpegState, pipelineSteps);
@ -640,8 +640,8 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
}; };
} }
_audioInputFile.Iter(a => a.AddOption(new ReadrateInputOption(initialBurst))); _audioInputFile.Iter(a => a.AddOption(new ReadrateInputOption(_ffmpegCapabilities, initialBurst, _logger)));
videoInputFile.AddOption(new ReadrateInputOption(initialBurst)); videoInputFile.AddOption(new ReadrateInputOption(_ffmpegCapabilities, initialBurst, _logger));
} }
private static void SetStillImageInfiniteLoop( private static void SetStillImageInfiniteLoop(

Loading…
Cancel
Save