FF-1332 - added new flow element audio bitrate matches

This commit is contained in:
John Andrews
2024-02-15 08:41:12 +13:00
parent b9ab8f18e6
commit f2662a838a
8 changed files with 276 additions and 58 deletions

View File

@@ -56,19 +56,23 @@ public class CreateAudioBook: AudioNode
/// <returns>the output to call next, -1 to abort flow, 0 to end flow</returns>
public override int Execute(NodeParameters args)
{
var ffmpeg = GetFFmpeg(args);
if (string.IsNullOrEmpty(ffmpeg))
var ffmpegExeResult = GetFFmpeg(args);
if (ffmpegExeResult.Failed(out string ffmpegError))
{
args.FailureReason = "FFmpeg not found";
args.FailureReason = ffmpegError;
args.Logger?.ELog(ffmpegError);
return -1;
}
string ffmpeg = ffmpegExeResult.Value;
var ffprobe = GetFFprobe(args);
if (string.IsNullOrEmpty(ffprobe))
var ffprobeResult = GetFFprobe(args);
if (ffmpegExeResult.Failed(out string ffprobeError))
{
args.FailureReason = "FFprobe not found";
args.FailureReason = ffprobeError;
args.Logger?.ELog(ffprobeError);
return -1;
}
string ffprobe = ffprobeResult.Value;
var dir = args.IsDirectory ? args.WorkingFile : FileHelper.GetDirectory(args.WorkingFile);

View File

@@ -7,6 +7,20 @@
"1": "Audio file from library"
}
},
"AudioBitrateMatches": {
"Label": "Audio Bitrate Matches",
"Description": "Check if an audio bitrate matches the constraints.",
"Fields": {
"Match": "Match",
"BitrateKilobytes": "Bitrate",
"BitrateKilobytes-Suffix": "Kilobytes",
"BitrateKilobytes-Help": "The bitrate in kilobytes to run the match against."
},
"Outputs": {
"1": "Does match",
"2": "Does not match"
}
},
"AudioFileNormalization": {
"Description": "Normalizes an audio file using two passes of FFMPEGs loudnorm filter",
"Outputs": {

View File

@@ -39,19 +39,23 @@ namespace FileFlows.AudioNodes
public override int Execute(NodeParameters args)
{
string ffmpegExe = GetFFmpeg(args);
if (string.IsNullOrEmpty(ffmpegExe))
var ffmpegExeResult = GetFFmpeg(args);
if (ffmpegExeResult.Failed(out string ffmpegError))
{
args.FailureReason = "FFmpeg not found";
args.FailureReason = ffmpegError;
args.Logger?.ELog(ffmpegError);
return -1;
}
string ffprobe = GetFFprobe(args);
if (string.IsNullOrEmpty(ffprobe))
string ffmpegExe = ffmpegExeResult.Value;
var ffprobeResult = GetFFprobe(args);
if (ffmpegExeResult.Failed(out string ffprobeError))
{
args.FailureReason = "FFprobe not found";
args.FailureReason = ffprobeError;
args.Logger?.ELog(ffprobeError);
return -1;
}
string ffprobe = ffprobeResult.Value;
if (args.FileService.FileCreationTimeUtc(args.WorkingFile).Success(out DateTime createTime))
@@ -66,9 +70,15 @@ namespace FileFlows.AudioNodes
return 1;
var AudioInfo = GetAudioInfo(args);
if (AudioInfo.Failed(out string error))
{
args.FailureReason = error;
args.Logger?.ELog(error);
return -1;
}
if (string.IsNullOrEmpty(AudioInfo.Codec) == false)
args.RecordStatistic("CODEC", AudioInfo.Codec);
if (string.IsNullOrEmpty(AudioInfo.Value.Codec) == false)
args.RecordStatistic("CODEC", AudioInfo.Value.Codec);
return 0;
}

View File

@@ -0,0 +1,123 @@
namespace FileFlows.AudioNodes;
/// <summary>
/// Checks if a audio files bitrate matches the condition
/// </summary>
public class AudioBitrateMatches : AudioNode
{
/// <inheritdoc />
public override int Inputs => 1;
/// <inheritdoc />
public override int Outputs => 2;
/// <inheritdoc />
public override string HelpUrl => "https://fileflows.com/docs/plugins/audio-nodes/audio-bitrate-matches";
/// <inheritdoc />
public override FlowElementType Type => FlowElementType.Logic;
/// <inheritdoc />
public override string Icon => "fas fa-question";
internal const string MATCH_GREATER_THAN = ">";
internal const string MATCH_LESS_THAN = "<";
internal const string MATCH_EQUALS = "=";
internal const string MATCH_NOT_EQUALS = "!=";
internal const string MATCH_GREATER_THAN_OR_EQUAL = ">=";
internal const string MATCH_LESS_THAN_OR_EQUAL = "<=";
/// <summary>
/// Gets or sets the method to match
/// </summary>
[Select(nameof(MatchOptions), 1)]
public string Match { get; set; }
private static List<ListOption> _MatchOptions;
/// <summary>
/// Gets the match options
/// </summary>
public static List<ListOption> MatchOptions
{
get
{
if (_MatchOptions == null)
{
_MatchOptions = new List<ListOption>
{
new () { Label = "Equals", Value = MATCH_EQUALS},
new () { Label = "Not Equals", Value = MATCH_NOT_EQUALS},
new () { Label = "Less Than", Value = MATCH_LESS_THAN},
new () { Label = "Less Than Or Equal", Value = MATCH_LESS_THAN_OR_EQUAL},
new () { Label = "Greater Than", Value = MATCH_GREATER_THAN},
new () { Label = "Greater Than Or Equal", Value = MATCH_GREATER_THAN_OR_EQUAL},
};
}
return _MatchOptions;
}
}
/// <summary>
/// Gets or sets the bitrate value to check
/// </summary>
[NumberInt(2)]
public int BitrateKilobytes { get; set; }
/// <inheritdoc />
public override int Execute(NodeParameters args)
{
var audioInfoResult = GetAudioInfo(args);
if (audioInfoResult.Failed(out string error))
{
args.Logger?.ELog(error);
args.FailureReason = error;
return -1;
}
var audioInfo = audioInfoResult.Value;
var bitrate = audioInfo.Bitrate;
long expected = BitrateKilobytes * 1000;
return DoMatch(args.Logger, Match, bitrate, expected) ? 1 : 2;
}
/// <summary>
/// Executes the match check
/// </summary>
/// <param name="logger">the logger</param>
/// <param name="match">the match to test</param>
/// <param name="bitrate">the actual bitrate</param>
/// <param name="expected">the expected bitrate</param>
/// <returns>true if matches, otherwise false</returns>
internal static bool DoMatch(ILogger logger, string match, long bitrate, long expected)
{
bool matches = false;
switch (match)
{
case MATCH_EQUALS:
matches = bitrate == expected;
break;
case MATCH_NOT_EQUALS:
matches = bitrate != expected;
break;
case MATCH_LESS_THAN:
matches = bitrate < expected;
break;
case MATCH_LESS_THAN_OR_EQUAL:
matches = bitrate <= expected;
break;
case MATCH_GREATER_THAN:
matches = bitrate > expected;
break;
case MATCH_GREATER_THAN_OR_EQUAL:
matches = bitrate >= expected;
break;
}
if(matches)
logger?.ILog($"Bitrate matches expected: {bitrate} {match} {expected}");
else
logger?.ILog($"Bitrate does not match expected: {bitrate} {match} {expected}");
return matches;
}
}

View File

@@ -20,13 +20,24 @@ public class AudioFileNormalization : AudioNode
{
try
{
string ffmpegExe = GetFFmpeg(args);
if (string.IsNullOrEmpty(ffmpegExe))
var ffmpegExeResult = GetFFmpeg(args);
if (ffmpegExeResult.Failed(out string ffmpegError))
{
args.FailureReason = ffmpegError;
args.Logger?.ELog(ffmpegError);
return -1;
}
string ffmpegExe = ffmpegExeResult.Value;
AudioInfo AudioInfo = GetAudioInfo(args);
if (AudioInfo == null)
var audioInfoResult = GetAudioInfo(args);
if (audioInfoResult.Failed(out string error))
{
args.Logger?.ELog(error);
args.FailureReason = error;
return -1;
}
AudioInfo AudioInfo = audioInfoResult.Value;
List<string> ffArgs = new List<string>();

View File

@@ -21,36 +21,36 @@ namespace FileFlows.AudioNodes
return true;
}
protected string GetFFmpeg(NodeParameters args)
/// <summary>
/// Gets the FFmpeg location
/// </summary>
/// <param name="args">the node parameters</param>
/// <returns>the FFmpeg location</returns>
protected Result<string> GetFFmpeg(NodeParameters args)
{
string ffmpeg = args.GetToolPath("FFMpeg");
if (string.IsNullOrEmpty(ffmpeg))
{
args.Logger.ELog("FFmpeg tool not found.");
return "";
}
return Result<string>.Fail("FFmpeg tool not found.");
var fileInfo = new System.IO.FileInfo(ffmpeg);
if (fileInfo.Exists == false)
{
args.Logger.ELog("FFmpeg tool configured by ffmpeg file does not exist.");
return "";
}
Result<string>.Fail("FFmpeg tool configured by ffmpeg file does not exist.");
return fileInfo.FullName;
}
protected string GetFFprobe(NodeParameters args)
/// <summary>
/// Gets the FFprobe location
/// </summary>
/// <param name="args">the node parameters</param>
/// <returns>the FFprobe location</returns>
protected Result<string> GetFFprobe(NodeParameters args)
{
string ffmpeg = args.GetToolPath("FFprobe");
if (string.IsNullOrEmpty(ffmpeg))
{
args.Logger.ELog("FFprobe tool not found.");
return "";
}
return Result<string>.Fail("FFprobe tool not found.");
var fileInfo = new System.IO.FileInfo(ffmpeg);
if (fileInfo.Exists == false)
{
args.Logger.ELog("FFprobe tool configured by ffmpeg file does not exist.");
return "";
}
return Result<string>.Fail("FFprobe tool configured by ffmpeg file does not exist.");
return fileInfo.FullName;
}
@@ -122,20 +122,18 @@ namespace FileFlows.AudioNodes
return;
dict.Add(name, value);
}
protected AudioInfo GetAudioInfo(NodeParameters args)
/// <summary>
/// Gets the audio information previously read into the flow
/// </summary>
/// <param name="args">the node parameters</param>
/// <returns>the audio information</returns>
protected Result<AudioInfo> GetAudioInfo(NodeParameters args)
{
if (args.Parameters.ContainsKey(Audio_INFO) == false)
{
args.Logger.WLog("No codec information loaded, use a 'Audio File' node first");
return null;
}
var result = args.Parameters[Audio_INFO] as AudioInfo;
if (result == null)
{
args.Logger.WLog("AudioInfo not found for file");
return null;
}
return Result<AudioInfo>.Fail("No codec information loaded, use a 'Audio File' node first");
if (args.Parameters[Audio_INFO] is AudioInfo result == false)
return Result<AudioInfo>.Fail("AudioInfo not found for file");
return result;
}

View File

@@ -1,6 +1,4 @@
using FileFlows.AudioNodes.Helpers;
using FileFlows.Plugin;
using FileFlows.Plugin.Attributes;
namespace FileFlows.AudioNodes
{
@@ -99,9 +97,15 @@ namespace FileFlows.AudioNodes
public override int Execute(NodeParameters args)
{
AudioInfo AudioInfo = GetAudioInfo(args);
if (AudioInfo == null)
var audioInfoResult = GetAudioInfo(args);
if (audioInfoResult.Failed(out string error))
{
args.Logger?.ELog(error);
args.FailureReason = error;
return -1;
}
AudioInfo AudioInfo = audioInfoResult.Value;
string ffmpegExe = GetFFmpeg(args);
if (string.IsNullOrEmpty(ffmpegExe))
@@ -122,7 +126,7 @@ namespace FileFlows.AudioNodes
protected long GetSourceBitrate(NodeParameters args)
{
var info = GetAudioInfo(args);
var info = GetAudioInfo(args).Value;
return info.Bitrate;
}
@@ -307,13 +311,23 @@ namespace FileFlows.AudioNodes
if (AudioInfo == null)
return -1;
string ffmpegExe = GetFFmpeg(args);
if (string.IsNullOrEmpty(ffmpegExe))
var ffmpegExeResult = GetFFmpeg(args);
if (ffmpegExeResult.Failed(out string ffmpegError))
{
args.FailureReason = ffmpegError;
args.Logger?.ELog(ffmpegError);
return -1;
}
string ffmpegExe = ffmpegExeResult.Value;
string ffprobe = GetFFprobe(args);
if (string.IsNullOrEmpty(ffprobe))
var ffprobeResult = GetFFprobe(args);
if (ffmpegExeResult.Failed(out string ffprobeError))
{
args.FailureReason = ffprobeError;
args.Logger?.ELog(ffprobeError);
return -1;
}
string ffprobe = ffprobeResult.Value;
if(Normalize == false && AudioInfo.Codec?.ToLower() == Extension?.ToLower())
{

View File

@@ -0,0 +1,44 @@
#if(DEBUG)
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text.Json;
namespace FileFlows.AudioNodes.Tests;
[TestClass]
public class AudioBitrateMatchesTests
{
const string file = @"/home/john/Music/test/test.mp3";
readonly string ffmpegExe = (OperatingSystem.IsLinux() ? "/usr/bin/ffmpeg" : @"C:\utils\ffmpeg\ffmpeg.exe");
readonly string ffprobe = (OperatingSystem.IsLinux() ? "/usr/bin/ffprobe" : @"C:\utils\ffmpeg\ffprobe.exe");
[TestMethod]
public void AudioInfo_SplitTrack()
{
var logger = new TestLogger();
Assert.IsFalse(AudioBitrateMatches.DoMatch(logger, AudioBitrateMatches.MATCH_GREATER_THAN, 90, 100));
Assert.IsFalse(AudioBitrateMatches.DoMatch(logger, AudioBitrateMatches.MATCH_GREATER_THAN, 100, 100));
Assert.IsFalse(AudioBitrateMatches.DoMatch(logger, AudioBitrateMatches.MATCH_GREATER_THAN_OR_EQUAL, 90, 100));
Assert.IsFalse(AudioBitrateMatches.DoMatch(logger, AudioBitrateMatches.MATCH_GREATER_THAN_OR_EQUAL, 99, 100));
Assert.IsFalse(AudioBitrateMatches.DoMatch(logger, AudioBitrateMatches.MATCH_LESS_THAN, 110, 100));
Assert.IsFalse(AudioBitrateMatches.DoMatch(logger, AudioBitrateMatches.MATCH_LESS_THAN, 100, 100));
Assert.IsFalse(AudioBitrateMatches.DoMatch(logger, AudioBitrateMatches.MATCH_LESS_THAN_OR_EQUAL, 110, 100));
Assert.IsFalse(AudioBitrateMatches.DoMatch(logger, AudioBitrateMatches.MATCH_LESS_THAN_OR_EQUAL, 101, 100));
Assert.IsFalse(AudioBitrateMatches.DoMatch(logger, AudioBitrateMatches.MATCH_NOT_EQUALS, 100, 100));
Assert.IsFalse(AudioBitrateMatches.DoMatch(logger, AudioBitrateMatches.MATCH_EQUALS, 90, 100));
Assert.IsTrue(AudioBitrateMatches.DoMatch(logger, AudioBitrateMatches.MATCH_GREATER_THAN, 110, 100));
Assert.IsTrue(AudioBitrateMatches.DoMatch(logger, AudioBitrateMatches.MATCH_GREATER_THAN, 101, 100));
Assert.IsTrue(AudioBitrateMatches.DoMatch(logger, AudioBitrateMatches.MATCH_GREATER_THAN_OR_EQUAL, 110, 100));
Assert.IsTrue(AudioBitrateMatches.DoMatch(logger, AudioBitrateMatches.MATCH_GREATER_THAN_OR_EQUAL, 100, 100));
Assert.IsTrue(AudioBitrateMatches.DoMatch(logger, AudioBitrateMatches.MATCH_LESS_THAN, 90, 100));
Assert.IsTrue(AudioBitrateMatches.DoMatch(logger, AudioBitrateMatches.MATCH_LESS_THAN, 99, 100));
Assert.IsTrue(AudioBitrateMatches.DoMatch(logger, AudioBitrateMatches.MATCH_LESS_THAN_OR_EQUAL, 90, 100));
Assert.IsTrue(AudioBitrateMatches.DoMatch(logger, AudioBitrateMatches.MATCH_LESS_THAN_OR_EQUAL, 100, 100));
Assert.IsTrue(AudioBitrateMatches.DoMatch(logger, AudioBitrateMatches.MATCH_NOT_EQUALS, 99, 100));
Assert.IsTrue(AudioBitrateMatches.DoMatch(logger, AudioBitrateMatches.MATCH_EQUALS, 100, 100));
}
}
#endif