mirror of
https://github.com/revenz/FileFlowsPlugins.git
synced 2026-02-26 19:18:26 -06:00
Merge branch 'develop'
# Conflicts: # VideoNodes/Tests/FfmpegBuilderTests/FfmpegBuilder_MetadataTests.cs
This commit is contained in:
@@ -97,27 +97,32 @@ public class FfmpegBuilderAudioAddTrack : FfmpegBuilderNode
|
||||
|
||||
var audio = new FfmpegAudioStream();
|
||||
|
||||
#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
|
||||
var bestAudio = Model.AudioStreams.Where(x => System.Text.Json.JsonSerializer.Serialize(x.Stream).ToLower().Contains("commentary") == false)
|
||||
#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
|
||||
.OrderBy(x =>
|
||||
var bestAudio = GetBestAudioTrack(args, Model.AudioStreams.Select(x => x.Stream));
|
||||
if (bestAudio == null)
|
||||
{
|
||||
if (Language != string.Empty)
|
||||
{
|
||||
args.Logger?.ILog("Language: " + x.Stream.Language, x);
|
||||
if (string.IsNullOrEmpty(x.Stream.Language))
|
||||
return 50; // no language specified
|
||||
if (x.Stream.Language?.ToLower() != Language)
|
||||
return 100; // low priority not the desired language
|
||||
}
|
||||
return 0;
|
||||
})
|
||||
.ThenByDescending(x => x.Stream.Channels)
|
||||
.ThenBy(x => x.Index)
|
||||
.FirstOrDefault();
|
||||
audio.Stream = bestAudio.Stream;
|
||||
args.Logger.WLog("No source audio track found");
|
||||
return -1;
|
||||
}
|
||||
|
||||
audio.EncodingParameters.AddRange(GetNewAudioTrackParameters("0:a:" + (bestAudio.Stream.TypeIndex)));
|
||||
audio.Stream = bestAudio;
|
||||
|
||||
bool directCopy = false;
|
||||
if(bestAudio.Codec.ToLower() == this.Codec.ToLower())
|
||||
{
|
||||
if(this.Channels == 0 || this.Channels == bestAudio.Channels)
|
||||
{
|
||||
directCopy = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (directCopy)
|
||||
{
|
||||
args.Logger?.ILog($"Source audio is already in appropriate format, just copying that track: {bestAudio.IndexString}, Channels: {bestAudio.Channels}, Codec: {bestAudio.Codec}");
|
||||
}
|
||||
else
|
||||
{
|
||||
audio.EncodingParameters.AddRange(GetNewAudioTrackParameters("0:a:" + (bestAudio.TypeIndex)));
|
||||
}
|
||||
if (Index > Model.AudioStreams.Count - 1)
|
||||
Model.AudioStreams.Add(audio);
|
||||
else
|
||||
@@ -126,6 +131,54 @@ public class FfmpegBuilderAudioAddTrack : FfmpegBuilderNode
|
||||
return 1;
|
||||
}
|
||||
|
||||
internal AudioStream GetBestAudioTrack(NodeParameters args, IEnumerable<AudioStream> streams)
|
||||
{
|
||||
#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
|
||||
var bestAudio = streams.Where(x => System.Text.Json.JsonSerializer.Serialize(x).ToLower().Contains("commentary") == false)
|
||||
#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
|
||||
.OrderBy(x =>
|
||||
{
|
||||
if (Language != string.Empty)
|
||||
{
|
||||
args.Logger?.ILog("Language: " + x.Language, x);
|
||||
if (string.IsNullOrEmpty(x.Language))
|
||||
return 50; // no language specified
|
||||
if (x.Language.ToLower() != Language)
|
||||
return 100; // low priority not the desired language
|
||||
}
|
||||
return 0;
|
||||
})
|
||||
.ThenByDescending(x => {
|
||||
if(this.Channels == 2)
|
||||
{
|
||||
if (x.Channels == 2)
|
||||
return 1_000_000_000;
|
||||
// compare codecs
|
||||
if (x.Codec?.ToLower() == this.Codec?.ToLower())
|
||||
return 1_000_000;
|
||||
}
|
||||
if(this.Channels == 1)
|
||||
{
|
||||
if (x.Channels == 1)
|
||||
return 1_000_000_000;
|
||||
if (x.Channels <= 2.1f)
|
||||
return 5_000_000;
|
||||
if (x.Codec?.ToLower() == this.Codec?.ToLower())
|
||||
return 1_000_000;
|
||||
}
|
||||
|
||||
// now we want best channels, but to prefer matching codec
|
||||
if (x.Codec?.ToLower() == this.Codec?.ToLower())
|
||||
{
|
||||
return 1_000 + x.Channels;
|
||||
}
|
||||
return x.Channels;
|
||||
})
|
||||
.ThenBy(x => x.Index)
|
||||
.FirstOrDefault();
|
||||
return bestAudio;
|
||||
}
|
||||
|
||||
|
||||
private string[] GetNewAudioTrackParameters(string source)
|
||||
{
|
||||
|
||||
@@ -10,6 +10,12 @@ namespace FileFlows.VideoNodes.FfmpegBuilderNodes
|
||||
public override int Outputs => 2;
|
||||
public override FlowElementType Type => FlowElementType.BuildEnd;
|
||||
|
||||
public override bool NoEditorOnAdd => true;
|
||||
|
||||
[DefaultValue(true)]
|
||||
[Boolean(1)]
|
||||
public bool HardwareDecoding { get; set; }
|
||||
|
||||
public override int Execute(NodeParameters args)
|
||||
{
|
||||
this.Init(args);
|
||||
@@ -56,7 +62,16 @@ namespace FileFlows.VideoNodes.FfmpegBuilderNodes
|
||||
else
|
||||
model.InputFiles[0] = args.WorkingFile;
|
||||
|
||||
foreach(string file in model.InputFiles)
|
||||
startArgs.AddRange(new[] {
|
||||
"-probesize", VideoInfoHelper.ProbeSize + "M"
|
||||
});
|
||||
|
||||
if (HardwareDecoding)
|
||||
{
|
||||
startArgs.AddRange(GetHardwareDecodingArgs());
|
||||
}
|
||||
|
||||
foreach (string file in model.InputFiles)
|
||||
{
|
||||
startArgs.Add("-i");
|
||||
startArgs.Add(file);
|
||||
@@ -70,5 +85,39 @@ namespace FileFlows.VideoNodes.FfmpegBuilderNodes
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
internal string[] GetHardwareDecodingArgs()
|
||||
{
|
||||
string testFile = Path.Combine(args.TempPath, Guid.NewGuid() + ".hwtest.mkv");
|
||||
foreach(var hw in new [] { "cuda", "dxva2", "qsv", "d3d11va", "opencl" })
|
||||
{
|
||||
// ffmpeg -y -hwaccel qsvf -f lavfi -i color=color=red -frames:v 10 test.mkv
|
||||
try
|
||||
{
|
||||
var result = args.Execute(new ExecuteArgs
|
||||
{
|
||||
Command = ffmpegExe,
|
||||
ArgumentList = new[]
|
||||
{
|
||||
"-y",
|
||||
"-hwaccel", hw,
|
||||
"-f", "lavfi",
|
||||
"-i", "color=color=red",
|
||||
"-frames:v", "10",
|
||||
testFile
|
||||
}
|
||||
});
|
||||
if (result.ExitCode == 0)
|
||||
{
|
||||
args.Logger?.ILog("Supported hardware decoding detected: " + hw);
|
||||
return new[] { "-hwaccel", hw };
|
||||
}
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
|
||||
args.Logger?.ILog("No hardware decoding availble");
|
||||
return new string[] { };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
#if(DEBUG)
|
||||
|
||||
using FileFlows.VideoNodes.FfmpegBuilderNodes;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using VideoNodes.Tests;
|
||||
|
||||
namespace FileFlows.VideoNodes.Tests.FfmpegBuilderTests;
|
||||
|
||||
[TestClass]
|
||||
public class FfmpegBuilder_AddAudioTests
|
||||
{
|
||||
VideoInfo vii;
|
||||
NodeParameters args;
|
||||
private void Prepare()
|
||||
{
|
||||
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);
|
||||
vii = vi.Read(file);
|
||||
vii.AudioStreams = new List<AudioStream>
|
||||
{
|
||||
new AudioStream
|
||||
{
|
||||
Index = 2,
|
||||
IndexString = "0:a:0",
|
||||
Language = "en",
|
||||
Codec = "AC3",
|
||||
Channels = 5.1f
|
||||
},
|
||||
new AudioStream
|
||||
{
|
||||
Index = 3,
|
||||
IndexString = "0:a:1",
|
||||
Language = "en",
|
||||
Codec = "AAC",
|
||||
Channels = 2
|
||||
},
|
||||
new AudioStream
|
||||
{
|
||||
Index = 4,
|
||||
IndexString = "0:a:3",
|
||||
Language = "en",
|
||||
Codec = "AAC",
|
||||
Channels = 2
|
||||
},
|
||||
new AudioStream
|
||||
{
|
||||
Index = 5,
|
||||
IndexString = "0:a:4",
|
||||
Language = "en",
|
||||
Codec = "AAC",
|
||||
Channels = 5.1f
|
||||
}
|
||||
};
|
||||
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));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FfmpegBuilder_AddAudio_AacStereo()
|
||||
{
|
||||
Prepare();
|
||||
|
||||
FfmpegBuilderAudioAddTrack ffAddAudio = new();
|
||||
ffAddAudio.Codec = "aac";
|
||||
ffAddAudio.Channels = 2;
|
||||
var best = ffAddAudio.GetBestAudioTrack(args, vii.AudioStreams);
|
||||
|
||||
Assert.IsNotNull(best);
|
||||
|
||||
Assert.AreEqual(3, best.Index);
|
||||
Assert.AreEqual("AAC", best.Codec);
|
||||
Assert.AreEqual(2f, best.Channels);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FfmpegBuilder_AddAudio_AacSameAsSource()
|
||||
{
|
||||
Prepare();
|
||||
|
||||
FfmpegBuilderAudioAddTrack ffAddAudio = new();
|
||||
ffAddAudio.Codec = "aac";
|
||||
ffAddAudio.Channels = 0;
|
||||
var best = ffAddAudio.GetBestAudioTrack(args, vii.AudioStreams);
|
||||
|
||||
Assert.IsNotNull(best);
|
||||
|
||||
Assert.AreEqual(5, best.Index);
|
||||
Assert.AreEqual("AAC", best.Codec);
|
||||
Assert.AreEqual(5.1f, best.Channels);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FfmpegBuilder_AddAudio_Ac3SameAsSource()
|
||||
{
|
||||
Prepare();
|
||||
|
||||
FfmpegBuilderAudioAddTrack ffAddAudio = new();
|
||||
ffAddAudio.Codec = "ac3";
|
||||
ffAddAudio.Channels = 0;
|
||||
ffAddAudio.Index = 1;
|
||||
var best = ffAddAudio.GetBestAudioTrack(args, vii.AudioStreams);
|
||||
|
||||
Assert.IsNotNull(best);
|
||||
|
||||
Assert.AreEqual(2, best.Index);
|
||||
Assert.AreEqual("AC3", best.Codec);
|
||||
Assert.AreEqual(5.1f, best.Channels);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FfmpegBuilder_AddAudio_DtsSame()
|
||||
{
|
||||
Prepare();
|
||||
|
||||
FfmpegBuilderAudioAddTrack ffAddAudio = new();
|
||||
ffAddAudio.Codec = "dts";
|
||||
ffAddAudio.Channels = 0;
|
||||
var best = ffAddAudio.GetBestAudioTrack(args, vii.AudioStreams);
|
||||
|
||||
Assert.IsNotNull(best);
|
||||
|
||||
Assert.AreEqual(2, best.Index);
|
||||
Assert.AreEqual("AC3", best.Codec);
|
||||
Assert.AreEqual(5.1f, best.Channels);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FfmpegBuilder_AddAudio_DtsStereo()
|
||||
{
|
||||
Prepare();
|
||||
|
||||
FfmpegBuilderAudioAddTrack ffAddAudio = new();
|
||||
ffAddAudio.Codec = "dts";
|
||||
ffAddAudio.Channels = 2;
|
||||
var best = ffAddAudio.GetBestAudioTrack(args, vii.AudioStreams);
|
||||
|
||||
Assert.IsNotNull(best);
|
||||
|
||||
Assert.AreEqual(3, best.Index);
|
||||
Assert.AreEqual("AAC", best.Codec);
|
||||
Assert.AreEqual(2f, best.Channels);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FfmpegBuilder_AddAudio_DtsMono()
|
||||
{
|
||||
Prepare();
|
||||
|
||||
FfmpegBuilderAudioAddTrack ffAddAudio = new();
|
||||
ffAddAudio.Codec = "dts";
|
||||
ffAddAudio.Channels = 1;
|
||||
var best = ffAddAudio.GetBestAudioTrack(args, vii.AudioStreams);
|
||||
|
||||
Assert.IsNotNull(best);
|
||||
|
||||
Assert.AreEqual(3, best.Index);
|
||||
Assert.AreEqual("AAC", best.Codec);
|
||||
Assert.AreEqual(2f, best.Channels);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -776,6 +776,144 @@ namespace FileFlows.VideoNodes.Tests.FfmpegBuilderTests
|
||||
string log = logger.ToString();
|
||||
Assert.AreEqual(1, result);
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void FfmpegBuilder_HardwareDecoding()
|
||||
{
|
||||
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.Execute(args);
|
||||
|
||||
|
||||
FfmpegBuilderAudioAddTrack ffAddAudio = new();
|
||||
ffAddAudio.Codec = "ac3";
|
||||
ffAddAudio.Index = 0;
|
||||
ffAddAudio.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_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
// #if(DEBUG)
|
||||
//
|
||||
// using FileFlows.VideoNodes.FfmpegBuilderNodes;
|
||||
// using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
// using VideoNodes.Tests;
|
||||
//
|
||||
// namespace FileFlows.VideoNodes.Tests.FfmpegBuilderTests
|
||||
// {
|
||||
// [TestClass]
|
||||
// public class FfmpegBuilder_MetadataTests
|
||||
// {
|
||||
// [TestMethod]
|
||||
// public void FfmpegBuilder_MetadataJson()
|
||||
// {
|
||||
// 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);
|
||||
// VideoMetadata md = System.Text.Json.JsonSerializer.Deserialize<VideoMetadata>(File.ReadAllText(@"D:\videos\metadata.json"));
|
||||
// args.Variables.Add("VideoMetadata", md);
|
||||
// args.GetToolPathActual = (string tool) => ffmpeg;
|
||||
// args.TempPath = @"D:\videos\temp";
|
||||
// args.Parameters.Add("VideoInfo", vii);
|
||||
//
|
||||
//
|
||||
// FfmpegBuilderStart ffStart = new ();
|
||||
// Assert.AreEqual(1, ffStart.Execute(args));
|
||||
//
|
||||
//
|
||||
// FfmpegBuilderVideoMetadata ffMetadata = new();
|
||||
// Assert.AreEqual(1, ffMetadata.Execute(args));
|
||||
//
|
||||
// FfmpegBuilderExecutor ffExecutor = new();
|
||||
// int result = ffExecutor.Execute(args);
|
||||
//
|
||||
// string log = logger.ToString();
|
||||
// Assert.AreEqual(1, result);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #endif
|
||||
//#if(DEBUG)
|
||||
|
||||
//using FileFlows.VideoNodes.FfmpegBuilderNodes;
|
||||
//using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
//using VideoNodes.Tests;
|
||||
|
||||
//namespace FileFlows.VideoNodes.Tests.FfmpegBuilderTests
|
||||
//{
|
||||
// [TestClass]
|
||||
// public class FfmpegBuilder_MetadataTests
|
||||
// {
|
||||
// [TestMethod]
|
||||
// public void FfmpegBuilder_MetadataJson()
|
||||
// {
|
||||
// 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);
|
||||
// VideoMetadata md = System.Text.Json.JsonSerializer.Deserialize<VideoMetadata>(File.ReadAllText(@"D:\videos\metadata.json"));
|
||||
// args.Variables.Add("VideoMetadata", md);
|
||||
// args.GetToolPathActual = (string tool) => ffmpeg;
|
||||
// args.TempPath = @"D:\videos\temp";
|
||||
// args.Parameters.Add("VideoInfo", vii);
|
||||
|
||||
|
||||
// FfmpegBuilderStart ffStart = new ();
|
||||
// Assert.AreEqual(1, ffStart.Execute(args));
|
||||
|
||||
|
||||
// FfmpegBuilderVideoMetadata ffMetadata = new();
|
||||
// Assert.AreEqual(1, ffMetadata.Execute(args));
|
||||
|
||||
// FfmpegBuilderExecutor ffExecutor = new();
|
||||
// int result = ffExecutor.Execute(args);
|
||||
|
||||
// string log = logger.ToString();
|
||||
// Assert.AreEqual(1, result);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
//#endif
|
||||
@@ -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);
|
||||
|
||||
@@ -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": {
|
||||
@@ -157,7 +162,11 @@
|
||||
"1": "FFMPEG Builder ran successfully and created new temporary file",
|
||||
"2": "No changes detected in FFMPEG Builder, file not created"
|
||||
},
|
||||
"Description": "Executes a FFMPEG Builder command created by other FFMPEG Builder nodes."
|
||||
"Description": "Executes a FFMPEG Builder command created by other FFMPEG Builder nodes.",
|
||||
"Fields": {
|
||||
"HardwareDecoding": "Hardware Decoding",
|
||||
"HardwareDecoding-Help": "If the executor should attempt to use hardware decoding. If not available the execution will proceed just without hardware decoding enabled."
|
||||
}
|
||||
},
|
||||
"FfmpegBuilderAudioAddTrack": {
|
||||
"Label": "FFMPEG Builder: Audio Add Track",
|
||||
@@ -369,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",
|
||||
|
||||
@@ -101,7 +101,7 @@ namespace FileFlows.VideoNodes
|
||||
if(vidparams.ToLower() == "hevc" || vidparams.ToLower() == "h265")
|
||||
{
|
||||
// try find best hevc encoder
|
||||
foreach(string vidparam in new [] { "hevc_nvenc -preset hq", "hevc_qsv -load_plugin hevc_hw", "hevc_amf", "hevc_vaapi" })
|
||||
foreach(string vidparam in new [] { "hevc_nvenc -preset hq", "hevc_qsv -global_quality 28 -load_plugin hevc_hw", "hevc_amf", "hevc_vaapi" })
|
||||
{
|
||||
bool canProcess = CanProcessEncoder(ffmpeg, vidparam);
|
||||
if (canProcess)
|
||||
@@ -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;
|
||||
|
||||
@@ -23,7 +23,7 @@ $output = $output | Resolve-Path
|
||||
Remove-Item Builds -Recurse -ErrorAction SilentlyContinue
|
||||
|
||||
$revision = (git rev-list --count --first-parent HEAD) -join "`n"
|
||||
$version = "0.5.3.$revision"
|
||||
$version = "0.5.4.$revision"
|
||||
|
||||
$json = "[`n"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user