Added FFMPEG Builder Video Bitrate. Added ability to adjust probe size

This commit is contained in:
John Andrews
2022-04-26 21:37:12 +12:00
parent 00745eae04
commit b5812009a1
9 changed files with 243 additions and 13 deletions

View File

@@ -62,6 +62,9 @@ namespace FileFlows.VideoNodes.FfmpegBuilderNodes
else
model.InputFiles[0] = args.WorkingFile;
startArgs.AddRange(new[] {
"-probesize", VideoInfoHelper.ProbeSize + "M"
});
if (HardwareDecoding)
{

View File

@@ -50,7 +50,17 @@
_OptionalEncodingParameters = value ?? new List<string>();
}
}
public override bool HasChange => EncodingParameters.Any() || Filter.Any();
private List<string> _AdditionalParameters = new List<string>();
public List<string> AdditionalParameters
{
get => _AdditionalParameters;
set
{
_AdditionalParameters = value ?? new List<string>();
}
}
public override bool HasChange => EncodingParameters.Any() || Filter.Any() || AdditionalParameters.Any();
public override string[] GetParameters(int outputIndex)
{
@@ -58,7 +68,7 @@
return new string[] { };
var results = new List<string> { "-map", "0:v:" + outputIndex };
if (Filter.Any() == false && EncodingParameters.Any() == false)
if (Filter.Any() == false && EncodingParameters.Any() == false && AdditionalParameters.Any() == false)
{
results.Add("-c:v:" + Stream.TypeIndex);
results.Add("copy");
@@ -78,6 +88,8 @@
// we need to set this codec since a filter will be applied, so we cant copy it.
//results.Add("copy");
}
if (AdditionalParameters.Any())
results.AddRange(AdditionalParameters.Select(x => x.Replace("{index}", outputIndex.ToString())));
if (Filter.Any() || OptionalFilter.Any())
{

View File

@@ -0,0 +1,54 @@
namespace FileFlows.VideoNodes.FfmpegBuilderNodes;
public class FfmpegBuilderVideoBitrate : FfmpegBuilderNode
{
public override string HelpUrl => "https://github.com/revenz/FileFlows/wiki/FFMPEG-Builder:-Video-Bitrate";
/// <summary>
/// Gets or sets the bitrate in K
/// </summary>
[NumberInt(1)]
[DefaultValue(3000)]
public float Bitrate { get; set; }
public override int Execute(NodeParameters args)
{
base.Init(args);
var video = Model.VideoStreams?.Where(x => x.Deleted == false)?.FirstOrDefault();
if (video?.Stream == null)
{
args.Logger?.ELog("No video stream found");
return -1;
}
if(Bitrate < 0)
{
args.Logger?.ELog("Minimum birate not set");
return -1;
}
int minimum = (int)(Bitrate * 0.75f);
int maximum = (int)(Bitrate * 1.25f);
float currentBitrate = (int)(video.Stream.Bitrate / 1024f);
if(currentBitrate <= 0)
{
// need to work it out
currentBitrate = (float)(args.WorkingFileSize / video.Stream.Duration.TotalSeconds);
// rough estimate of 75% of the file is video
currentBitrate *= 0.75f;
}
video.AdditionalParameters.AddRange(new[]
{
"-b:v:{index}", Bitrate + "k",
"-minrate", minimum + "k",
"-maxrate", maximum + "k",
"-bufsize", currentBitrate + "k"
});
return 1;
}
}

View File

@@ -9,6 +9,13 @@ namespace FileFlows.VideoNodes
public override int Outputs => 1;
public override FlowElementType Type => FlowElementType.Input;
public override bool NoEditorOnAdd => true;
[DefaultValue(25)]
[NumberInt(1)]
[Range(5, 1000)]
public int ProbeSize { get; set; }
private Dictionary<string, object> _Variables;
public override Dictionary<string, object> Variables => _Variables;
public VideoFile()
@@ -48,6 +55,8 @@ namespace FileFlows.VideoNodes
if (string.IsNullOrEmpty(ffmpegExe))
return -1;
VideoInfoHelper.ProbeSize = this.ProbeSize;
try
{

View File

@@ -148,6 +148,7 @@ public class FfmpegBuilder_AddAudioTests
Assert.AreEqual("AAC", best.Codec);
Assert.AreEqual(2f, best.Channels);
}
[TestMethod]
public void FfmpegBuilder_AddAudio_DtsMono()
{

View File

@@ -813,6 +813,107 @@ namespace FileFlows.VideoNodes.Tests.FfmpegBuilderTests
Assert.AreEqual(1, result);
}
[TestMethod]
public void FfmpegBuilder_VideoBitrate()
{
const string file = @"D:\videos\unprocessed\basic.mkv";
var logger = new TestLogger();
const string ffmpeg = @"C:\utils\ffmpeg\ffmpeg.exe";
var vi = new VideoInfoHelper(ffmpeg, logger);
var vii = vi.Read(file);
var args = new NodeParameters(file, logger, false, string.Empty);
args.GetToolPathActual = (string tool) => ffmpeg;
args.TempPath = @"D:\videos\temp";
args.Parameters.Add("VideoInfo", vii);
FfmpegBuilderStart ffStart = new();
Assert.AreEqual(1, ffStart.Execute(args));
FfmpegBuilderVideoBitrate ffBitrate = new();
ffBitrate.Bitrate = 1_000;
ffBitrate.Execute(args);
FfmpegBuilderExecutor ffExecutor = new();
ffExecutor.HardwareDecoding = true;
int result = ffExecutor.Execute(args);
string log = logger.ToString();
Assert.AreEqual(1, result);
}
[TestMethod]
public void FfmpegBuilder_VideoCodecAndBitrate()
{
const string file = @"D:\videos\unprocessed\basic.mkv";
var logger = new TestLogger();
const string ffmpeg = @"C:\utils\ffmpeg\ffmpeg.exe";
var vi = new VideoInfoHelper(ffmpeg, logger);
var vii = vi.Read(file);
var args = new NodeParameters(file, logger, false, string.Empty);
args.GetToolPathActual = (string tool) => ffmpeg;
args.TempPath = @"D:\videos\temp";
args.Parameters.Add("VideoInfo", vii);
FfmpegBuilderStart ffStart = new();
Assert.AreEqual(1, ffStart.Execute(args));
FfmpegBuilderVideoCodec ffEncode = new();
ffEncode.VideoCodec = "h264";
ffEncode.Force = true;
ffEncode.Execute(args);
FfmpegBuilderVideoBitrate ffBitrate = new();
ffBitrate.Bitrate = 1_000;
ffBitrate.Execute(args);
FfmpegBuilderExecutor ffExecutor = new();
ffExecutor.HardwareDecoding = true;
int result = ffExecutor.Execute(args);
string log = logger.ToString();
Assert.AreEqual(1, result);
}
[TestMethod]
public void FfmpegBuilder_FF43()
{
const string file = @"D:\videos\testfiles\ff-43.ts";
var logger = new TestLogger();
const string ffmpeg = @"C:\utils\ffmpeg\ffmpeg.exe";
var vi = new VideoInfoHelper(ffmpeg, logger);
var vii = vi.Read(file);
var args = new NodeParameters(file, logger, false, string.Empty);
args.GetToolPathActual = (string tool) => ffmpeg;
args.TempPath = @"D:\videos\temp";
args.Parameters.Add("VideoInfo", vii);
FfmpegBuilderStart ffStart = new();
Assert.AreEqual(1, ffStart.Execute(args));
FfmpegBuilderAudioTrackRemover ffRemoveAudio = new();
ffRemoveAudio.RemoveAll = true;
ffRemoveAudio.Execute(args);
FfmpegBuilderAudioAddTrack ffAddAudio = new();
ffAddAudio.Codec = "ac3";
ffAddAudio.Index = 0;
ffAddAudio.Execute(args);
FfmpegBuilderAudioAddTrack ffAddAudio2 = new();
ffAddAudio2.Codec = "aac";
ffAddAudio2.Index = 1;
ffAddAudio2.Execute(args);
FfmpegBuilderExecutor ffExecutor = new();
int result = ffExecutor.Execute(args);
string log = logger.ToString();
Assert.AreEqual(1, result);
}
}
}

View File

@@ -16,6 +16,21 @@ namespace FileFlows.VideoNodes
static Regex rgxDuration2 = new Regex(@"(?<=((^[\s]+Duration:[\s])))([\d]+:?)+\.[\d]{1,7}", RegexOptions.Multiline);
static Regex rgxAudioSampleRate = new Regex(@"(?<=((,|\s)))[\d]+(?=([\s]?hz))", RegexOptions.IgnoreCase);
static int _ProbeSize = 25;
internal static int ProbeSize
{
get => _ProbeSize;
set
{
if (value < 5)
_ProbeSize = 5;
else if (value > 1000)
_ProbeSize = 1000;
else
_ProbeSize = value;
}
}
public VideoInfoHelper(string ffMpegExe, ILogger logger)
{
this.ffMpegExe = ffMpegExe;
@@ -48,9 +63,16 @@ namespace FileFlows.VideoNodes
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.ArgumentList.Add("-hide_banner");
process.StartInfo.ArgumentList.Add("-i");
process.StartInfo.ArgumentList.Add(filename);
foreach (var arg in new[]
{
"-hide_banner",
"-probesize", ProbeSize + "M",
"-i",
filename,
})
{
process.StartInfo.ArgumentList.Add(arg);
}
process.Start();
string output = process.StandardError.ReadToEnd();
output = output.Replace("At least one output file must be specified", string.Empty).Trim();
@@ -243,14 +265,25 @@ namespace FileFlows.VideoNodes
audio.TypeIndex = int.Parse(Regex.Match(line, @"#([\d]+):([\d]+)").Groups[2].Value) - 1;
audio.Codec = parts[0].Substring(parts[0].IndexOf("Audio: ") + "Audio: ".Length).Trim().Split(' ').First().ToLower() ?? "";
audio.Language = Regex.Match(line, @"(?<=(Stream\s#[\d]+:[\d]+)\()[^\)]+").Value?.ToLower() ?? "";
//Logger.ILog("codec: " + vs.Codec);
if (parts[2] == "stereo")
audio.Channels = 2;
else if (parts[2] == "mono")
audio.Channels = 1;
else if (Regex.IsMatch(parts[2], @"^[\d]+(\.[\d]+)?"))
if (info.IndexOf("0 channels") >= 0)
{
audio.Channels = float.Parse(Regex.Match(parts[2], @"^[\d]+(\.[\d]+)?").Value);
audio.Channels = 0;
}
else
{
try
{
//Logger.ILog("codec: " + vs.Codec);
if (parts[2] == "stereo")
audio.Channels = 2;
else if (parts[2] == "mono")
audio.Channels = 1;
else if (Regex.IsMatch(parts[2], @"^[\d]+(\.[\d]+)?"))
{
audio.Channels = float.Parse(Regex.Match(parts[2], @"^[\d]+(\.[\d]+)?").Value);
}
}
catch (Exception) { }
}
var match = rgxAudioSampleRate.Match(info);

View File

@@ -119,6 +119,11 @@
"Description": "An input video file that has had its VideoInformation read and can be processed",
"Outputs": {
"1": "Video file from library"
},
"Fields": {
"ProbeSize": "Probe Size",
"ProbeSize-Suffix": "MB",
"ProbeSize-Help": "The probe size to use in FFMPEG when executing."
}
},
"DetectBlackBars": {
@@ -373,6 +378,18 @@
"1": "FFMPEG Builder video streams set to encode in 10 Bit"
}
},
"FfmpegBuilderVideoBitrate": {
"Label": "FFMPEG Builder: Video Bitrate",
"Description": "Sets FFMPEG Builder to encode the video given the bitrate",
"Outputs": {
"1": "FFMPEG Builder video streams updated"
},
"Fields": {
"Bitrate": "Bitrate",
"Bitrate-Suffix": "KB",
"Bitrate-Help": "The target bitrate of the video in kilobytes"
}
},
"FfmpegBuilderVideoCodec": {
"Label": "FFMPEG Builder: Video Codec",
"Description": "Sets FFMPEG Builder to encode the video streams in the specified codec",

View File

@@ -185,7 +185,7 @@ namespace FileFlows.VideoNodes
}).Result;
if (cmd.ExitCode != 0 || string.IsNullOrWhiteSpace(cmd.Output) == false)
{
args.Logger?.WLog($"Cant prcoess '{encodingParams}': {cmd.Output ?? ""}");
args.Logger?.WLog($"Cant process '{encodingParams}': {cmd.Output ?? ""}");
return false;
}
return true;