FF-1688: Fixed audio normalize when file name has {

This commit is contained in:
John Andrews
2024-07-23 08:37:41 +12:00
parent 5edadf9556
commit 879215e251
5 changed files with 135 additions and 42 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -1,27 +1,46 @@
using FileFlows.VideoNodes.FfmpegBuilderNodes.Models;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json;
namespace FileFlows.VideoNodes.FfmpegBuilderNodes;
/// <summary>
/// Flow element that normalizes the audio
/// </summary>
public class FfmpegBuilderAudioNormalization : FfmpegBuilderNode
{
/// <inheritdoc />
public override string HelpUrl => "https://fileflows.com/docs/plugins/video-nodes/ffmpeg-builder/audio-normalization";
/// <inheritdoc />
public override string Icon => "fas fa-volume-up";
/// <inheritdoc />
public override int Outputs => 2;
/// <summary>
/// Gets or sets if all audio should be normalised
/// </summary>
[Boolean(1)]
public bool AllAudio { get; set; }
/// <summary>
/// Gets or sets if the audio should be normalised using two passes or if false, a single pass
/// </summary>
[Boolean(2)]
public bool TwoPass { get; set; }
/// <summary>
/// Gets or sets the pattern to match against the audio file
/// </summary>
[TextVariable(3)]
public string Pattern { get; set; }
/// <summary>
/// Gets or sets if the match should be inversed
/// </summary>
[Boolean(4)]
public bool NotMatching { get; set; }
/// <summary>
/// The loud norm target
/// </summary>
internal const string LOUDNORM_TARGET = "I=-24:LRA=7:TP=-2.0";
/// <inheritdoc />
@@ -87,45 +106,47 @@ public class FfmpegBuilderAudioNormalization : FfmpegBuilderNode
return normalizing ? 1 : 2;
}
/// <summary>
/// Do a two pass normalization against a file
/// </summary>
/// <param name="node">the encoding node</param>
/// <param name="args">the node parameters</param>
/// <param name="ffmpegExe">the FFmpeg executable</param>
/// <param name="audioIndex">the audio index in the file</param>
/// <param name="localFile">the local filename of the file</param>
/// <returns>the result of the normalization</returns>
public static Result<string> DoTwoPass(EncodingNode node, NodeParameters args, string ffmpegExe, int audioIndex, string localFile)
{
//-af loudnorm=I=-24:LRA=7:TP=-2.0"
string output;
var result = node.Encode(args, ffmpegExe, new List<string>
{
var result = node.Encode(args, ffmpegExe, [
"-hide_banner",
"-i", localFile,
"-strict", "-2", // allow experimental stuff
"-strict", "-2", // allow experimental stuff
"-map", "0:a:" + audioIndex,
"-af", "loudnorm=" + LOUDNORM_TARGET + ":print_format=json",
"-f", "null",
"-"
}, out output, updateWorkingFile: false, dontAddInputFile: true);
], out var output, updateWorkingFile: false, dontAddInputFile: true);
if (result == false)
return Result<string>.Fail("Failed to process audio track");
int index = output.LastIndexOf("{", StringComparison.Ordinal);
if (index == -1)
var loudNorm = ExtractParsedLoudnormJson(args.Logger, output);
if (loudNorm.Count == 0)
{
args.Logger?.WLog("No LoudNormStats found in:\n" + output);
return Result<string>.Fail("Failed to detected json in output");
string json = output[index..];
json = json.Substring(0, json.IndexOf("}", StringComparison.Ordinal) + 1);
if (string.IsNullOrEmpty(json))
return Result<string>.Fail("Failed to parse TwoPass json");
LoudNormStats? stats;
try
{
stats = JsonSerializer.Deserialize<LoudNormStats>(json);
}
catch (Exception ex)
{
args.Logger.ELog("Failed to parse JSON: " +ex.Message);
args.Logger.ELog("JSON:" + json);
return Result<string>.Fail("Failed to parse JSON output from FFmpeg");
}
if (stats.input_i == "-inf" || stats.input_lra == "-inf" || stats.input_tp == "-inf" || stats.input_thresh == "-inf" || stats.target_offset == "-inf")
LoudNormStats? stats = loudNorm.FirstOrDefault(x =>
{
if (x.input_i == "-inf" || x.input_lra == "-inf" || x.input_tp == "-inf" || x.input_thresh == "-inf" ||
x.target_offset == "-inf")
return false;
return true;
});
if (stats == null)
{
args.Logger?.WLog("-inf detected in loud norm two pass, falling back to single pass loud norm");
return $"loudnorm={LOUDNORM_TARGET}";
@@ -135,6 +156,41 @@ public class FfmpegBuilderAudioNormalization : FfmpegBuilderNode
return ar;
}
/// <summary>
/// Extracts Loud Norm Ststs object following the [Parsed_loudnorm log entries from the provided log data.
/// </summary>
/// <param name="logger">The logger to use</param>
/// <param name="log">The log as a string.</param>
/// <returns>A list of Loud Norm Stats objects.</returns>
static List<LoudNormStats> ExtractParsedLoudnormJson(ILogger logger, string log)
{
List<LoudNormStats> results = new ();
Regex regex = new Regex(@"\[Parsed_loudnorm.*?\]\s*{(.*?)}", RegexOptions.Singleline);
MatchCollection matches = regex.Matches(log);
foreach (Match match in matches)
{
string json = "{" + match.Groups[1].Value + "}";
try
{
var ln = JsonSerializer.Deserialize<LoudNormStats>(json);
if (ln != null)
results.Add(ln);
}
catch (Exception ex)
{
// Ignored
logger?.ELog("Failed to parse JSON: " + ex.Message);
logger?.ELog("JSON:" + json);
}
}
return results;
}
/// <summary>
/// Represents the loudness normalization statistics.
/// </summary>
private class LoudNormStats
{
/*
@@ -151,11 +207,31 @@ public class FfmpegBuilderAudioNormalization : FfmpegBuilderNode
"target_offset" : "0.25"
}
*/
public string input_i { get; set; }
public string input_tp { get; set; }
public string input_lra { get; set; }
public string input_thresh { get; set; }
public string target_offset { get; set; }
/// <summary>
/// Integrated loudness of the input in LUFS.
/// </summary>
public string input_i { get; init; }
/// <summary>
/// True peak of the input in dBTP.
/// </summary>
public string input_tp { get; init; }
/// <summary>
/// Loudness range of the input in LU.
/// </summary>
public string input_lra { get; init; }
/// <summary>
/// Threshold of the input in LUFS.
/// </summary>
public string input_thresh { get; init; }
/// <summary>
/// Target offset for normalization in LU.
/// </summary>
public string target_offset { get; init; }
}
}

View File

@@ -261,14 +261,12 @@ public class FfmpegBuilder_BasicTests : TestBase
[TestMethod]
public void FfmpegBuilder_AddAc3Aac_Normalize()
{
const string file = @"D:\videos\unprocessed\dummy.mkv";
var logger = new TestLogger();
const string ffmpeg = @"C:\utils\ffmpeg\ffmpeg.exe";
var vi = new VideoInfoHelper(ffmpeg, logger);
const string file = @"/home/john/src/ff-files/test-files/videos/basic {tvdb-71470}.mkv";
var vi = new VideoInfoHelper(FfmpegPath, Logger);
var vii = vi.Read(file);
var args = new NodeParameters(file, logger, false, string.Empty, null);
args.GetToolPathActual = (string tool) => ffmpeg;
args.TempPath = @"D:\videos\temp";
var args = new NodeParameters(file, Logger, false, string.Empty, new LocalFileService());
args.GetToolPathActual = (string tool) => FfmpegPath;
args.TempPath = @"/home/john/src/ff-files/temp";
args.Parameters.Add("VideoInfo", vii);
@@ -299,7 +297,7 @@ public class FfmpegBuilder_BasicTests : TestBase
ffAddAudio2.Execute(args);
FfmpegBuilderAudioNormalization ffAudioNormalize = new();
ffAudioNormalize.TwoPass = false;
ffAudioNormalize.TwoPass = true;
ffAudioNormalize.AllAudio = true;
ffAudioNormalize.PreExecute(args);
ffAudioNormalize.Execute(args);
@@ -308,7 +306,6 @@ public class FfmpegBuilder_BasicTests : TestBase
ffExecutor.PreExecute(args);
int result = ffExecutor.Execute(args);
string log = logger.ToString();
Assert.AreEqual(1, result);
}

View File

@@ -377,7 +377,27 @@ public class LocalFileService : IFileService
public Result<long> DirectorySize(string path)
{
throw new NotImplementedException();
if (string.IsNullOrWhiteSpace(path))
return 0;
if (File.Exists(path))
path = new FileInfo(path).Directory?.FullName ?? string.Empty;
if (string.IsNullOrWhiteSpace(path))
return 0;
if (Directory.Exists(path) == false)
return 0;
try
{
DirectoryInfo dir = new DirectoryInfo(path);
return dir.EnumerateFiles("*.*", SearchOption.AllDirectories).Sum(x => x.Length);
}
catch (Exception)
{
return 0;
}
}
public Result<bool> SetCreationTimeUtc(string path, DateTime date)