Pre-requisites
- Only Windows 7 and later. Prefer 64 bit.
- Visual Studio with C# - any DOTNET Framework.
- Get ffmpeg from here - https://ffmpeg.zeranoe.com/builds/
- You should be well versed with writing and tweaking code.
- Feel free to optimize the code!
Compilation Notes
- PREPEND_AUDIO_FILTER can be String.Empty
- IN_WAV is the name of the input wav file.
- OUT_IS_MONO can be false for stereo output.
- DUALMONO - read the documentation on ffmpeg pages or read this https://developers.google.com/assistant/tools/audio-loudness
- Create a new C# console application and paste the below code and compile.
- The compiled exe and ffmpeg.exe and IN_WAV should be in the same folder.
The Script
This is ready to copy and paste:
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace FFMPEGProducer { class Program { private const String DESIRED_I = "-16"; // desired TP private const String DESIRED_TP = "-1.5"; private const String DESIRED_LRA = "11"; // notice the comma at the end private const String PREPEND_AUDIO_FILTER = "treble=gain=3,bass=gain=-3,"; // input wave file private const String IN_WAV = "in.wav"; // name of the normalized output file private const String OUT_NORMALIZED_FILE = "out.wav"; // on one channel is output for speech or lecture private const bool OUT_IS_MONO = true; // mono/stereo correction // read documentation if you need it private const bool DUALMONO = false; static void Main(string[] args) { Program p = new Program(); // delete the output file if it exists File.Delete(OUT_NORMALIZED_FILE); // first pass String measured_I = String.Empty, measured_TP = String.Empty, measured_LRA = String.Empty, measured_thresh = String.Empty, offset = String.Empty; using (Process process = new Process()) { process.StartInfo.FileName = "ffmpeg.exe"; process.StartInfo.Arguments = String.Format("-i \"{0}\" -af \"{1}loudnorm=I={2}{5}:TP={3}:LRA={4}:print_format=summary\" {6} -f null -", IN_WAV, PREPEND_AUDIO_FILTER, DESIRED_I, DESIRED_TP, DESIRED_LRA, (DUALMONO ? ":dual_mono=true" : String.Empty), (OUT_IS_MONO ? " -ac 1 " : String.Empty)); process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; process.StartInfo.CreateNoWindow = true; process.ErrorDataReceived += (s, e) => { String strLine = e.Data; if (!String.IsNullOrEmpty(strLine)) { Console.WriteLine(strLine); if (strLine.StartsWith("Input Integrated:")) { measured_I = p.ExtractNumberFromFFMPEGLine(strLine); } else if (strLine.StartsWith("Input True")) { measured_TP = p.ExtractNumberFromFFMPEGLine(strLine); } else if (strLine.StartsWith("Input LRA:")) { measured_LRA = p.ExtractNumberFromFFMPEGLine(strLine); } else if (strLine.StartsWith("Input Threshold:")) { measured_thresh = p.ExtractNumberFromFFMPEGLine(strLine); } else if (strLine.StartsWith("Target")) { offset = p.ExtractNumberFromFFMPEGLine(strLine); } } }; process.Start(); process.BeginOutputReadLine(); process.BeginErrorReadLine(); process.WaitForExit(); } if (!String.IsNullOrEmpty(p.strError)) { Console.WriteLine("Fatal Error: " + p.strError); goto lblExit; } // second pass String cmd = String.Format("-i \"{0}\" -af \"{1}loudnorm=I={2}{5}:TP={3}:LRA={4}:measured_I={7}:measured_TP={8}:measured_LRA={9}:measured_thresh={10}:offset={11}:linear=true:print_format=summary\" {6} -y {12}", IN_WAV, PREPEND_AUDIO_FILTER, DESIRED_I, DESIRED_TP, DESIRED_LRA, (DUALMONO ? ":dual_mono=true" : String.Empty), (OUT_IS_MONO ? " -ac 1 " : String.Empty), measured_I, measured_TP, measured_LRA, measured_thresh, offset, OUT_NORMALIZED_FILE); Console.WriteLine(cmd); p.ExecuteCommand(cmd); lblExit: Console.WriteLine("Press enter to exit . . . "); Console.ReadLine(); } String strError; void ExecuteCommand(string command) { using (Process process = new Process()) { process.StartInfo.FileName = "ffmpeg.exe"; process.StartInfo.Arguments = command; process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; process.StartInfo.CreateNoWindow = true; process.OutputDataReceived += (s, e) => { String strLine = e.Data; if (!String.IsNullOrEmpty(strLine)) { Console.WriteLine(strLine); } }; process.ErrorDataReceived += (s, e) => { String strLine = e.Data; if (!String.IsNullOrEmpty(strLine)) { Console.WriteLine(strLine); } }; process.Start(); process.BeginOutputReadLine(); process.BeginErrorReadLine(); process.WaitForExit(); } } String ExtractNumberFromFFMPEGLine (String strLine) { StringBuilder sbNumber = new StringBuilder(); foreach (Char k in strLine) { if (char.IsDigit(k) || ('.' == k) || ('-' == k)) { sbNumber.Append(k); } } String ret = sbNumber.ToString(); try { float.Parse(ret); } catch { if (String.IsNullOrEmpty(strError)) { strError = "Failed parse " + strLine; } } return ret; } } }
Similar Posts
This Blog Post/Article "C# ffmpeg loudnorm 2 pass script" by Parveen is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.