78 lines
No EOL
4.1 KiB
C#
78 lines
No EOL
4.1 KiB
C#
using System;
|
|
using System.Globalization;
|
|
|
|
namespace Paca.Core;
|
|
|
|
/// <summary>
|
|
/// Predefined dynamic range compression presets for dialogue clarity and evening listening.
|
|
/// </summary>
|
|
public enum CompressorPreset
|
|
{
|
|
None,
|
|
NightListening,
|
|
PodcastSpeech,
|
|
BroadcastStandard
|
|
}
|
|
|
|
/// <summary>
|
|
/// Intelligent audio dynamic range compression (DRC) and peak limiter filter generator.
|
|
/// Balances quiet speech and loud sound effects (gunfire, explosions) for clear, non-fatiguing audio.
|
|
/// </summary>
|
|
public static class AudioDynamicRangeCompressor
|
|
{
|
|
/// <summary>
|
|
/// Builds the ffmpeg acompressor and alimiter filter chain for the given preset.
|
|
/// </summary>
|
|
public static string BuildFilter(CompressorPreset preset)
|
|
{
|
|
return preset switch
|
|
{
|
|
CompressorPreset.NightListening => "acompressor=threshold=-24dB:ratio=6:attack=5:release=100:makeup=6dB,alimiter=limit=-1dB",
|
|
CompressorPreset.PodcastSpeech => "acompressor=threshold=-20dB:ratio=4:attack=10:release=150:makeup=4dB,alimiter=limit=-1dB",
|
|
CompressorPreset.BroadcastStandard => "acompressor=threshold=-18dB:ratio=8:attack=2:release=80:makeup=8dB,alimiter=limit=0dB",
|
|
_ => "anull"
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Builds custom compressor filter chain with granular acoustic parameters.
|
|
/// </summary>
|
|
public static string BuildCustomFilter(double thresholdDb, double ratio, double attackMs, double releaseMs, double makeupDb, double limitPeakDb)
|
|
{
|
|
if (double.IsNaN(thresholdDb) || double.IsInfinity(thresholdDb))
|
|
throw new ArgumentOutOfRangeException(nameof(thresholdDb), "Threshold must be a finite number.");
|
|
if (double.IsNaN(ratio) || double.IsInfinity(ratio) || ratio <= 0)
|
|
throw new ArgumentOutOfRangeException(nameof(ratio), "Compression ratio must be a positive finite number.");
|
|
if (double.IsNaN(attackMs) || double.IsInfinity(attackMs) || attackMs < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(attackMs), "Attack time must be a non-negative finite number.");
|
|
if (double.IsNaN(releaseMs) || double.IsInfinity(releaseMs) || releaseMs < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(releaseMs), "Release time must be a non-negative finite number.");
|
|
if (double.IsNaN(makeupDb) || double.IsInfinity(makeupDb))
|
|
throw new ArgumentOutOfRangeException(nameof(makeupDb), "Makeup gain must be a finite number.");
|
|
if (double.IsNaN(limitPeakDb) || double.IsInfinity(limitPeakDb))
|
|
throw new ArgumentOutOfRangeException(nameof(limitPeakDb), "Peak limiter must be a finite number.");
|
|
|
|
var inv = CultureInfo.InvariantCulture;
|
|
return $"acompressor=threshold={thresholdDb.ToString("0.##", inv)}dB:ratio={ratio.ToString("0.##", inv)}:attack={attackMs.ToString("0.##", inv)}:release={releaseMs.ToString("0.##", inv)}:makeup={makeupDb.ToString("0.##", inv)}dB,alimiter=limit={limitPeakDb.ToString("0.##", inv)}dB";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Builds full ffmpeg command line arguments for applying DRC to video or audio files.
|
|
/// </summary>
|
|
public static string BuildFfmpegArgs(string inputPath, string outputPath, CompressorPreset preset, bool copyVideo = true, int audioBitrateKbps = 192)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(inputPath))
|
|
throw new ArgumentException("Input path cannot be null or empty.", nameof(inputPath));
|
|
if (string.IsNullOrWhiteSpace(outputPath))
|
|
throw new ArgumentException("Output path cannot be null or empty.", nameof(outputPath));
|
|
if (string.Equals(inputPath.Trim(), outputPath.Trim(), StringComparison.OrdinalIgnoreCase))
|
|
throw new ArgumentException("Input and output paths cannot be identical.", nameof(outputPath));
|
|
|
|
if (audioBitrateKbps <= 0)
|
|
audioBitrateKbps = 192;
|
|
|
|
string vCodec = copyVideo ? "-c:v copy" : "-c:v libx264 -crf 20 -preset fast";
|
|
var filter = BuildFilter(preset);
|
|
return $"-hide_banner -y -i \"{inputPath}\" {vCodec} -af \"{filter}\" -c:a aac -b:a {audioBitrateKbps}k \"{outputPath}\"";
|
|
}
|
|
} |