2020-08-04 14:20:52 +00:00
#pragma warning disable CS1591
2019-01-13 20:02:23 +00:00
using System ;
2018-12-14 09:40:55 +00:00
using System.Collections.Generic ;
2019-09-27 23:29:54 +00:00
using System.Diagnostics ;
2020-08-21 20:01:19 +00:00
using System.Globalization ;
2019-01-02 00:23:49 +00:00
using System.Linq ;
2019-01-02 09:48:10 +00:00
using System.Text.RegularExpressions ;
2018-12-14 23:48:06 +00:00
using Microsoft.Extensions.Logging ;
2018-12-14 09:40:55 +00:00
namespace MediaBrowser.MediaEncoding.Encoder
{
2023-05-22 20:48:09 +00:00
public partial class EncoderValidator
2018-12-14 09:40:55 +00:00
{
2020-06-01 05:10:15 +00:00
private static readonly string [ ] _requiredDecoders = new [ ]
2018-12-14 09:40:55 +00:00
{
2020-04-08 16:15:01 +00:00
"h264" ,
"hevc" ,
2021-12-01 16:49:50 +00:00
"vp8" ,
"libvpx" ,
"vp9" ,
"libvpx-vp9" ,
"av1" ,
"libdav1d" ,
2019-09-27 23:29:54 +00:00
"mpeg2video" ,
2020-04-08 16:15:01 +00:00
"mpeg4" ,
"msmpeg4" ,
2023-02-24 14:04:52 +00:00
"dca" ,
2020-04-08 16:15:01 +00:00
"ac3" ,
2024-07-17 16:35:40 +00:00
"ac4" ,
2020-04-08 16:15:01 +00:00
"aac" ,
"mp3" ,
2020-10-22 09:09:59 +00:00
"flac" ,
2023-02-24 14:04:52 +00:00
"truehd" ,
2019-09-27 23:29:54 +00:00
"h264_qsv" ,
"hevc_qsv" ,
"mpeg2_qsv" ,
"vc1_qsv" ,
2020-04-08 16:15:01 +00:00
"vp8_qsv" ,
"vp9_qsv" ,
2021-12-01 16:49:50 +00:00
"av1_qsv" ,
2019-09-27 23:29:54 +00:00
"h264_cuvid" ,
"hevc_cuvid" ,
2020-04-08 16:15:01 +00:00
"mpeg2_cuvid" ,
"vc1_cuvid" ,
"mpeg4_cuvid" ,
"vp8_cuvid" ,
"vp9_cuvid" ,
2023-12-20 05:58:09 +00:00
"av1_cuvid" ,
"h264_rkmpp" ,
"hevc_rkmpp" ,
"mpeg1_rkmpp" ,
"mpeg2_rkmpp" ,
"mpeg4_rkmpp" ,
"vp8_rkmpp" ,
"vp9_rkmpp" ,
"av1_rkmpp"
2019-09-27 23:29:54 +00:00
} ;
2018-12-14 09:40:55 +00:00
2020-06-01 05:10:15 +00:00
private static readonly string [ ] _requiredEncoders = new [ ]
2019-09-27 23:29:54 +00:00
{
"libx264" ,
"libx265" ,
2023-06-19 19:49:26 +00:00
"libsvtav1" ,
2019-09-27 23:29:54 +00:00
"aac" ,
2023-02-08 22:42:17 +00:00
"aac_at" ,
2020-03-30 07:53:49 +00:00
"libfdk_aac" ,
2020-04-08 16:15:01 +00:00
"ac3" ,
2024-04-08 13:42:47 +00:00
"alac" ,
2023-02-24 14:04:52 +00:00
"dca" ,
2019-09-27 23:29:54 +00:00
"libmp3lame" ,
"libopus" ,
"libvorbis" ,
2020-10-22 09:09:59 +00:00
"flac" ,
2023-02-24 14:04:52 +00:00
"truehd" ,
2019-09-27 23:29:54 +00:00
"srt" ,
2020-04-08 16:15:01 +00:00
"h264_amf" ,
"hevc_amf" ,
2023-06-19 19:49:26 +00:00
"av1_amf" ,
2019-09-27 23:29:54 +00:00
"h264_qsv" ,
"hevc_qsv" ,
2024-05-07 13:23:28 +00:00
"mjpeg_qsv" ,
2023-06-19 19:49:26 +00:00
"av1_qsv" ,
2020-04-08 16:15:01 +00:00
"h264_nvenc" ,
"hevc_nvenc" ,
2023-06-19 19:49:26 +00:00
"av1_nvenc" ,
2019-09-27 23:29:54 +00:00
"h264_vaapi" ,
"hevc_vaapi" ,
2023-06-19 19:49:26 +00:00
"av1_vaapi" ,
2024-05-07 13:23:28 +00:00
"mjpeg_vaapi" ,
2019-09-27 23:29:54 +00:00
"h264_v4l2m2m" ,
2020-05-26 15:41:38 +00:00
"h264_videotoolbox" ,
2023-12-20 05:58:09 +00:00
"hevc_videotoolbox" ,
2024-07-17 17:50:32 +00:00
"mjpeg_videotoolbox" ,
2023-12-20 05:58:09 +00:00
"h264_rkmpp" ,
"hevc_rkmpp"
2019-09-27 23:29:54 +00:00
} ;
2021-07-24 16:52:16 +00:00
private static readonly string [ ] _requiredFilters = new [ ]
{
2021-12-01 16:49:50 +00:00
// sw
"alphasrc" ,
"zscale" ,
2024-07-02 04:26:31 +00:00
"tonemapx" ,
2021-12-01 16:49:50 +00:00
// qsv
"scale_qsv" ,
"vpp_qsv" ,
"deinterlace_qsv" ,
"overlay_qsv" ,
// cuda
2021-07-24 16:52:16 +00:00
"scale_cuda" ,
"yadif_cuda" ,
2024-09-05 14:27:58 +00:00
"bwdif_cuda" ,
2021-07-24 16:52:16 +00:00
"tonemap_cuda" ,
2021-12-01 16:49:50 +00:00
"overlay_cuda" ,
2024-03-29 22:09:44 +00:00
"transpose_cuda" ,
2021-12-01 16:49:50 +00:00
"hwupload_cuda" ,
// opencl
"scale_opencl" ,
2021-07-24 16:52:16 +00:00
"tonemap_opencl" ,
2021-12-01 16:49:50 +00:00
"overlay_opencl" ,
2024-03-29 22:09:44 +00:00
"transpose_opencl" ,
2021-12-01 16:49:50 +00:00
// vaapi
"scale_vaapi" ,
"deinterlace_vaapi" ,
2021-07-24 16:52:16 +00:00
"tonemap_vaapi" ,
2022-06-17 16:01:37 +00:00
"procamp_vaapi" ,
2021-12-01 16:49:50 +00:00
"overlay_vaapi" ,
2024-03-29 22:09:44 +00:00
"transpose_vaapi" ,
2022-10-16 15:08:59 +00:00
"hwupload_vaapi" ,
// vulkan
"libplacebo" ,
"scale_vulkan" ,
2023-02-08 22:42:17 +00:00
"overlay_vulkan" ,
2024-03-29 22:09:44 +00:00
"transpose_vulkan" ,
"flip_vulkan" ,
2023-02-08 22:42:17 +00:00
// videotoolbox
2023-12-20 05:58:09 +00:00
"yadif_videotoolbox" ,
2024-02-15 13:52:41 +00:00
"scale_vt" ,
2024-03-29 22:09:44 +00:00
"transpose_vt" ,
2024-02-28 09:56:59 +00:00
"overlay_videotoolbox" ,
2024-03-24 14:20:05 +00:00
"tonemap_videotoolbox" ,
2023-12-20 05:58:09 +00:00
// rkrga
"scale_rkrga" ,
"vpp_rkrga" ,
"overlay_rkrga"
2021-07-24 16:52:16 +00:00
} ;
2023-11-14 19:21:34 +00:00
private static readonly Dictionary < int , string [ ] > _filterOptionsDict = new Dictionary < int , string [ ] >
2021-07-24 16:52:16 +00:00
{
{ 0 , new string [ ] { "scale_cuda" , "Output format (default \"same\")" } } ,
{ 1 , new string [ ] { "tonemap_cuda" , "GPU accelerated HDR to SDR tonemapping" } } ,
2021-12-01 16:49:50 +00:00
{ 2 , new string [ ] { "tonemap_opencl" , "bt2390" } } ,
{ 3 , new string [ ] { "overlay_opencl" , "Action to take when encountering EOF from secondary input" } } ,
2022-10-16 15:08:59 +00:00
{ 4 , new string [ ] { "overlay_vaapi" , "Action to take when encountering EOF from secondary input" } } ,
2024-03-29 22:09:44 +00:00
{ 5 , new string [ ] { "overlay_vulkan" , "Action to take when encountering EOF from secondary input" } } ,
{ 6 , new string [ ] { "transpose_opencl" , "rotate by half-turn" } }
2021-07-24 16:52:16 +00:00
} ;
2024-03-17 13:30:42 +00:00
// These are the library versions that corresponds to our minimum ffmpeg version 4.4 according to the version table below
// Refers to the versions in https://ffmpeg.org/download.html
2023-11-14 19:21:34 +00:00
private static readonly Dictionary < string , Version > _ffmpegMinimumLibraryVersions = new Dictionary < string , Version >
2020-06-01 05:10:15 +00:00
{
2024-03-17 13:30:42 +00:00
{ "libavutil" , new Version ( 56 , 70 ) } ,
{ "libavcodec" , new Version ( 58 , 134 ) } ,
{ "libavformat" , new Version ( 58 , 76 ) } ,
{ "libavdevice" , new Version ( 58 , 13 ) } ,
{ "libavfilter" , new Version ( 7 , 110 ) } ,
{ "libswscale" , new Version ( 5 , 9 ) } ,
{ "libswresample" , new Version ( 3 , 9 ) } ,
{ "libpostproc" , new Version ( 55 , 9 ) }
2020-06-01 05:10:15 +00:00
} ;
2019-09-27 23:29:54 +00:00
private readonly ILogger _logger ;
2018-12-14 09:40:55 +00:00
2019-09-27 23:29:54 +00:00
private readonly string _encoderPath ;
2018-12-14 09:40:55 +00:00
2024-08-05 15:01:30 +00:00
private readonly Version _minFFmpegMultiThreadedCli = new Version ( 7 , 0 ) ;
2021-04-04 21:02:28 +00:00
public EncoderValidator ( ILogger logger , string encoderPath )
2019-09-27 23:29:54 +00:00
{
_logger = logger ;
_encoderPath = encoderPath ;
2018-12-14 09:40:55 +00:00
}
2020-08-04 15:08:09 +00:00
private enum Codec
{
Encoder ,
Decoder
}
2020-08-20 15:16:09 +00:00
// When changing this, also change the minimum library versions in _ffmpegMinimumLibraryVersions
2024-03-17 13:30:42 +00:00
public static Version MinVersion { get ; } = new Version ( 4 , 4 ) ;
2019-09-27 23:29:54 +00:00
2021-05-20 20:10:19 +00:00
public static Version ? MaxVersion { get ; } = null ;
2019-09-27 23:29:54 +00:00
2023-05-22 20:48:09 +00:00
[GeneratedRegex(@"^ffmpeg version n?((?:[0-9] + \ . ? ) + ) ")]
private static partial Regex FfmpegVersionRegex ( ) ;
[GeneratedRegex(@"((?<name>lib\w+)\s+(?<major>[0-9] + ) \ . \ s * ( ? < minor > [ 0 - 9 ] + ) ) ", RegexOptions.Multiline)]
private static partial Regex LibraryRegex ( ) ;
2019-09-27 23:29:54 +00:00
public bool ValidateVersion ( )
2018-12-14 09:40:55 +00:00
{
2021-05-20 20:10:19 +00:00
string output ;
2018-12-14 09:40:55 +00:00
try
{
2022-09-24 03:09:35 +00:00
output = GetProcessOutput ( _encoderPath , "-version" , false , null ) ;
2018-12-14 09:40:55 +00:00
}
catch ( Exception ex )
{
2019-09-27 23:29:54 +00:00
_logger . LogError ( ex , "Error validating encoder" ) ;
2021-05-20 20:10:19 +00:00
return false ;
2018-12-14 09:40:55 +00:00
}
if ( string . IsNullOrWhiteSpace ( output ) )
{
2019-09-27 23:29:54 +00:00
_logger . LogError ( "FFmpeg validation: The process returned no result" ) ;
2018-12-14 09:40:55 +00:00
return false ;
}
2019-01-05 21:40:33 +00:00
_logger . LogDebug ( "ffmpeg output: {Output}" , output ) ;
2018-12-14 09:40:55 +00:00
2019-09-27 23:29:54 +00:00
return ValidateVersionInternal ( output ) ;
}
internal bool ValidateVersionInternal ( string versionOutput )
{
2023-11-14 19:21:34 +00:00
if ( versionOutput . Contains ( "Libav developers" , StringComparison . OrdinalIgnoreCase ) )
2018-12-14 09:40:55 +00:00
{
2019-09-27 23:29:54 +00:00
_logger . LogError ( "FFmpeg validation: avconv instead of ffmpeg is not supported" ) ;
2018-12-14 09:40:55 +00:00
return false ;
}
2019-02-14 22:01:09 +00:00
// Work out what the version under test is
2021-07-24 16:52:16 +00:00
var version = GetFFmpegVersionInternal ( versionOutput ) ;
2019-02-14 22:01:09 +00:00
2022-12-05 14:01:13 +00:00
_logger . LogInformation ( "Found ffmpeg version {Version}" , version is not null ? version . ToString ( ) : "unknown" ) ;
2019-02-15 23:51:22 +00:00
2022-12-05 14:00:20 +00:00
if ( version is null )
2019-09-27 23:29:54 +00:00
{
2022-12-05 14:01:13 +00:00
if ( MaxVersion is not null ) // Version is unknown
2019-02-14 22:01:09 +00:00
{
2019-09-29 11:41:24 +00:00
if ( MinVersion = = MaxVersion )
{
2020-06-01 05:10:15 +00:00
_logger . LogWarning ( "FFmpeg validation: We recommend version {MinVersion}" , MinVersion ) ;
2019-09-29 11:41:24 +00:00
}
else
{
2020-06-01 05:10:15 +00:00
_logger . LogWarning ( "FFmpeg validation: We recommend a minimum of {MinVersion} and maximum of {MaxVersion}" , MinVersion , MaxVersion ) ;
2019-09-29 11:41:24 +00:00
}
2019-02-14 22:01:09 +00:00
}
2020-06-01 05:10:15 +00:00
else
{
_logger . LogWarning ( "FFmpeg validation: We recommend minimum version {MinVersion}" , MinVersion ) ;
}
2019-09-27 23:29:54 +00:00
return false ;
}
2023-04-06 17:38:34 +00:00
if ( version < MinVersion ) // Version is below what we recommend
2019-09-27 23:29:54 +00:00
{
2020-06-01 05:10:15 +00:00
_logger . LogWarning ( "FFmpeg validation: The minimum recommended version is {MinVersion}" , MinVersion ) ;
2019-09-27 23:29:54 +00:00
return false ;
}
2023-04-06 17:38:34 +00:00
if ( MaxVersion is not null & & version > MaxVersion ) // Version is above what we recommend
2019-09-27 23:29:54 +00:00
{
2020-06-01 05:10:15 +00:00
_logger . LogWarning ( "FFmpeg validation: The maximum recommended version is {MaxVersion}" , MaxVersion ) ;
2019-09-27 23:29:54 +00:00
return false ;
2019-02-14 22:01:09 +00:00
}
2019-09-27 23:29:54 +00:00
return true ;
2019-02-14 22:01:09 +00:00
}
2019-09-27 23:29:54 +00:00
public IEnumerable < string > GetDecoders ( ) = > GetCodecs ( Codec . Decoder ) ;
public IEnumerable < string > GetEncoders ( ) = > GetCodecs ( Codec . Encoder ) ;
2020-04-08 16:15:01 +00:00
public IEnumerable < string > GetHwaccels ( ) = > GetHwaccelTypes ( ) ;
2021-07-24 16:52:16 +00:00
public IEnumerable < string > GetFilters ( ) = > GetFFmpegFilters ( ) ;
public IDictionary < int , bool > GetFiltersWithOption ( ) = > GetFFmpegFiltersWithOption ( ) ;
public Version ? GetFFmpegVersion ( )
{
string output ;
try
{
2022-09-24 03:09:35 +00:00
output = GetProcessOutput ( _encoderPath , "-version" , false , null ) ;
2021-07-24 16:52:16 +00:00
}
catch ( Exception ex )
{
_logger . LogError ( ex , "Error validating encoder" ) ;
return null ;
}
if ( string . IsNullOrWhiteSpace ( output ) )
{
_logger . LogError ( "FFmpeg validation: The process returned no result" ) ;
return null ;
}
_logger . LogDebug ( "ffmpeg output: {Output}" , output ) ;
return GetFFmpegVersionInternal ( output ) ;
}
2019-02-14 22:01:09 +00:00
/// <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
2020-06-01 05:10:15 +00:00
/// to parse. If this is not available, then we try to match known library versions to FFmpeg versions.
/// If that fails then we test the libraries to determine if they're newer than our minimum versions.
2019-02-14 22:01:09 +00:00
/// </summary>
2020-08-04 15:08:09 +00:00
/// <param name="output">The output from "ffmpeg -version".</param>
/// <returns>The FFmpeg version.</returns>
2021-07-24 16:52:16 +00:00
internal Version ? GetFFmpegVersionInternal ( string output )
2019-02-14 22:01:09 +00:00
{
// For pre-built binaries the FFmpeg version should be mentioned at the very start of the output
2023-05-22 20:48:09 +00:00
var match = FfmpegVersionRegex ( ) . Match ( output ) ;
2019-02-14 22:01:09 +00:00
if ( match . Success )
{
2023-02-17 14:00:06 +00:00
if ( Version . TryParse ( match . Groups [ 1 ] . ValueSpan , out var result ) )
2020-09-19 15:40:39 +00:00
{
return result ;
}
2019-02-14 22:01:09 +00:00
}
2020-06-01 05:10:15 +00:00
2020-08-20 15:16:09 +00:00
var versionMap = GetFFmpegLibraryVersions ( output ) ;
2020-06-01 05:10:15 +00:00
var allVersionsValidated = true ;
2019-02-14 22:01:09 +00:00
2020-06-01 05:10:15 +00:00
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 ;
}
2019-02-14 22:01:09 +00:00
}
2020-06-01 05:10:15 +00:00
return allVersionsValidated ? MinVersion : null ;
2019-02-14 22:01:09 +00:00
}
/// <summary>
/// Grabs the library names and major.minor version numbers from the 'ffmpeg -version' output
2020-08-04 15:08:09 +00:00
/// and condenses them on to one line. Output format is "name1=major.minor,name2=major.minor,etc.".
2019-02-14 22:01:09 +00:00
/// </summary>
2020-08-04 15:08:09 +00:00
/// <param name="output">The 'ffmpeg -version' output.</param>
/// <returns>The library names and major.minor version numbers.</returns>
2023-11-14 19:21:34 +00:00
private static Dictionary < string , Version > GetFFmpegLibraryVersions ( string output )
2019-02-14 22:01:09 +00:00
{
2020-06-15 13:10:59 +00:00
var map = new Dictionary < string , Version > ( ) ;
2020-06-01 05:10:15 +00:00
2023-05-22 20:48:09 +00:00
foreach ( Match match in LibraryRegex ( ) . Matches ( output ) )
2019-02-14 22:01:09 +00:00
{
2020-08-20 15:16:09 +00:00
var version = new Version (
2023-02-17 14:00:06 +00:00
int . Parse ( match . Groups [ "major" ] . ValueSpan , CultureInfo . InvariantCulture ) ,
int . Parse ( match . Groups [ "minor" ] . ValueSpan , CultureInfo . InvariantCulture ) ) ;
2020-06-15 13:10:59 +00:00
map . Add ( match . Groups [ "name" ] . Value , version ) ;
2018-12-14 09:40:55 +00:00
}
2020-08-20 15:16:09 +00:00
return map ;
2018-12-14 09:40:55 +00:00
}
2021-12-01 16:49:50 +00:00
public bool CheckVaapiDeviceByDriverName ( string driverName , string renderNodePath )
{
if ( ! OperatingSystem . IsLinux ( ) )
{
return false ;
}
if ( string . IsNullOrEmpty ( driverName ) | | string . IsNullOrEmpty ( renderNodePath ) )
{
return false ;
}
try
{
2022-09-24 03:09:35 +00:00
var output = GetProcessOutput ( _encoderPath , "-v verbose -hide_banner -init_hw_device vaapi=va:" + renderNodePath , true , null ) ;
2021-12-24 08:50:40 +00:00
return output . Contains ( driverName , StringComparison . Ordinal ) ;
2021-12-01 16:49:50 +00:00
}
catch ( Exception ex )
{
_logger . LogError ( ex , "Error detecting the given vaapi render node path" ) ;
return false ;
}
}
2022-10-16 15:08:59 +00:00
public bool CheckVulkanDrmDeviceByExtensionName ( string renderNodePath , string [ ] vulkanExtensions )
{
if ( ! OperatingSystem . IsLinux ( ) )
{
return false ;
}
if ( string . IsNullOrEmpty ( renderNodePath ) )
{
return false ;
}
try
{
var command = "-v verbose -hide_banner -init_hw_device drm=dr:" + renderNodePath + " -init_hw_device vulkan=vk@dr" ;
var output = GetProcessOutput ( _encoderPath , command , true , null ) ;
foreach ( string ext in vulkanExtensions )
{
if ( ! output . Contains ( ext , StringComparison . Ordinal ) )
{
return false ;
}
}
return true ;
}
catch ( Exception ex )
{
_logger . LogError ( ex , "Error detecting the given drm render node path" ) ;
return false ;
}
}
2020-04-08 16:15:01 +00:00
private IEnumerable < string > GetHwaccelTypes ( )
{
2021-05-20 20:10:19 +00:00
string? output = null ;
2020-04-08 16:15:01 +00:00
try
{
2022-09-24 03:09:35 +00:00
output = GetProcessOutput ( _encoderPath , "-hwaccels" , false , null ) ;
2020-04-08 16:15:01 +00:00
}
catch ( Exception ex )
{
_logger . LogError ( ex , "Error detecting available hwaccel types" ) ;
}
if ( string . IsNullOrWhiteSpace ( output ) )
{
return Enumerable . Empty < string > ( ) ;
}
2020-08-04 15:08:09 +00:00
var found = output . Split ( new char [ ] { '\r' , '\n' } , StringSplitOptions . RemoveEmptyEntries ) . Skip ( 1 ) . Distinct ( ) . ToList ( ) ;
2020-04-08 16:15:01 +00:00
_logger . LogInformation ( "Available hwaccel types: {Types}" , found ) ;
return found ;
}
2021-07-24 16:52:16 +00:00
public bool CheckFilterWithOption ( string filter , string option )
2021-01-26 19:20:53 +00:00
{
2021-07-24 16:52:16 +00:00
if ( string . IsNullOrEmpty ( filter ) | | string . IsNullOrEmpty ( option ) )
2021-01-26 19:20:53 +00:00
{
return false ;
}
2021-05-20 20:10:19 +00:00
string output ;
2021-01-26 19:20:53 +00:00
try
{
2022-09-24 03:09:35 +00:00
output = GetProcessOutput ( _encoderPath , "-h filter=" + filter , false , null ) ;
2021-01-26 19:20:53 +00:00
}
catch ( Exception ex )
{
_logger . LogError ( ex , "Error detecting the given filter" ) ;
2021-05-20 20:10:19 +00:00
return false ;
2021-01-26 19:20:53 +00:00
}
if ( output . Contains ( "Filter " + filter , StringComparison . Ordinal ) )
{
return output . Contains ( option , StringComparison . Ordinal ) ;
}
_logger . LogWarning ( "Filter: {Name} with option {Option} is not available" , filter , option ) ;
return false ;
}
2024-08-05 15:01:28 +00:00
public bool CheckSupportedRuntimeKey ( string keyDesc , Version ? ffmpegVersion )
2022-09-24 03:09:35 +00:00
{
if ( string . IsNullOrEmpty ( keyDesc ) )
{
return false ;
}
string output ;
try
{
2024-08-05 15:01:28 +00:00
// With multi-threaded cli support, FFmpeg 7 is less sensitive to keyboard input
var duration = ffmpegVersion > = _minFFmpegMultiThreadedCli ? 10000 : 1000 ;
output = GetProcessOutput ( _encoderPath , $"-hide_banner -f lavfi -i nullsrc=s=1x1:d={duration} -f null -" , true , "?" ) ;
2022-09-24 03:09:35 +00:00
}
catch ( Exception ex )
{
_logger . LogError ( ex , "Error checking supported runtime key" ) ;
return false ;
}
return output . Contains ( keyDesc , StringComparison . Ordinal ) ;
}
2024-07-17 17:50:32 +00:00
public bool CheckSupportedHwaccelFlag ( string flag )
{
return ! string . IsNullOrEmpty ( flag ) & & GetProcessExitCode ( _encoderPath , $"-loglevel quiet -hwaccel_flags +{flag} -hide_banner -f lavfi -i nullsrc=s=1x1:d=100 -f null -" ) ;
}
2019-09-27 23:29:54 +00:00
private IEnumerable < string > GetCodecs ( Codec codec )
2019-01-02 09:48:10 +00:00
{
string codecstr = codec = = Codec . Encoder ? "encoders" : "decoders" ;
2021-05-20 20:10:19 +00:00
string output ;
2019-01-02 09:48:10 +00:00
try
{
2022-09-24 03:09:35 +00:00
output = GetProcessOutput ( _encoderPath , "-" + codecstr , false , null ) ;
2019-01-02 09:48:10 +00:00
}
catch ( Exception ex )
{
_logger . LogError ( ex , "Error detecting available {Codec}" , codecstr ) ;
2021-05-20 20:10:19 +00:00
return Enumerable . Empty < string > ( ) ;
2019-01-02 09:48:10 +00:00
}
if ( string . IsNullOrWhiteSpace ( output ) )
{
return Enumerable . Empty < string > ( ) ;
}
2020-06-01 05:10:15 +00:00
var required = codec = = Codec . Encoder ? _requiredEncoders : _requiredDecoders ;
2019-01-02 09:48:10 +00:00
2023-10-07 23:16:00 +00:00
var found = CodecRegex ( )
. Matches ( output )
2019-01-02 09:48:10 +00:00
. Select ( x = > x . Groups [ "codec" ] . Value )
. Where ( x = > required . Contains ( x ) ) ;
_logger . LogInformation ( "Available {Codec}: {Codecs}" , codecstr , found ) ;
2019-01-02 00:01:36 +00:00
2018-12-14 09:40:55 +00:00
return found ;
}
2021-07-24 16:52:16 +00:00
private IEnumerable < string > GetFFmpegFilters ( )
{
string output ;
try
{
2022-09-24 03:09:35 +00:00
output = GetProcessOutput ( _encoderPath , "-filters" , false , null ) ;
2021-07-24 16:52:16 +00:00
}
catch ( Exception ex )
{
_logger . LogError ( ex , "Error detecting available filters" ) ;
return Enumerable . Empty < string > ( ) ;
}
if ( string . IsNullOrWhiteSpace ( output ) )
{
return Enumerable . Empty < string > ( ) ;
}
2023-10-07 23:16:00 +00:00
var found = FilterRegex ( )
. Matches ( output )
2021-07-24 16:52:16 +00:00
. Select ( x = > x . Groups [ "filter" ] . Value )
. Where ( x = > _requiredFilters . Contains ( x ) ) ;
_logger . LogInformation ( "Available filters: {Filters}" , found ) ;
return found ;
}
2023-11-14 19:21:34 +00:00
private Dictionary < int , bool > GetFFmpegFiltersWithOption ( )
2021-07-24 16:52:16 +00:00
{
2023-11-14 19:21:34 +00:00
Dictionary < int , bool > dict = new Dictionary < int , bool > ( ) ;
2021-07-24 16:52:16 +00:00
for ( int i = 0 ; i < _filterOptionsDict . Count ; i + + )
{
if ( _filterOptionsDict . TryGetValue ( i , out var val ) & & val . Length = = 2 )
{
dict . Add ( i , CheckFilterWithOption ( val [ 0 ] , val [ 1 ] ) ) ;
}
}
return dict ;
}
2022-09-24 03:09:35 +00:00
private string GetProcessOutput ( string path , string arguments , bool readStdErr , string? testKey )
2018-12-14 09:40:55 +00:00
{
2023-08-20 18:06:57 +00:00
var redirectStandardIn = ! string . IsNullOrEmpty ( testKey ) ;
using ( var process = new Process
2018-12-14 09:40:55 +00:00
{
2019-09-27 23:29:54 +00:00
StartInfo = new ProcessStartInfo ( path , arguments )
{
CreateNoWindow = true ,
UseShellExecute = false ,
WindowStyle = ProcessWindowStyle . Hidden ,
ErrorDialog = false ,
2023-08-20 18:06:57 +00:00
RedirectStandardInput = redirectStandardIn ,
2019-09-27 23:29:54 +00:00
RedirectStandardOutput = true ,
RedirectStandardError = true
}
} )
2018-12-14 09:40:55 +00:00
{
2019-09-27 23:29:54 +00:00
_logger . LogDebug ( "Running {Path} {Arguments}" , path , arguments ) ;
2018-12-14 09:40:55 +00:00
process . Start ( ) ;
2023-08-20 18:06:57 +00:00
if ( redirectStandardIn )
2022-09-24 03:09:35 +00:00
{
2023-08-20 18:06:57 +00:00
using var writer = process . StandardInput ;
writer . Write ( testKey ) ;
2022-09-24 03:09:35 +00:00
}
2023-08-20 18:06:57 +00:00
using var reader = readStdErr ? process . StandardError : process . StandardOutput ;
return reader . ReadToEnd ( ) ;
2018-12-14 09:40:55 +00:00
}
}
2023-10-07 23:16:00 +00:00
2024-07-17 17:50:32 +00:00
private bool GetProcessExitCode ( string path , string arguments )
{
using var process = new Process ( ) ;
process . StartInfo = new ProcessStartInfo ( path , arguments )
{
CreateNoWindow = true ,
UseShellExecute = false ,
WindowStyle = ProcessWindowStyle . Hidden ,
ErrorDialog = false
} ;
_logger . LogDebug ( "Running {Path} {Arguments}" , path , arguments ) ;
try
{
process . Start ( ) ;
process . WaitForExit ( ) ;
return process . ExitCode = = 0 ;
}
catch ( Exception ex )
{
_logger . LogError ( "Running {Path} {Arguments} failed with exception {Exception}" , path , arguments , ex . Message ) ;
return false ;
}
}
2023-10-07 23:16:00 +00:00
[GeneratedRegex("^\\s\\S{6}\\s(?<codec>[\\w|-] + ) \ \ s + . + $ ", RegexOptions.Multiline)]
private static partial Regex CodecRegex ( ) ;
[GeneratedRegex("^\\s\\S{3}\\s(?<filter>[\\w|-] + ) \ \ s + . + $ ", RegexOptions.Multiline)]
private static partial Regex FilterRegex ( ) ;
2018-12-14 09:40:55 +00:00
}
}