test(core/browser/platform): 26 extreme RED/GREEN TDD cycles and full regression hardening (3,235 PASS)
Some checks failed
release / test (push) Failing after 14m5s
release / windows (push) Has been cancelled
release / android (push) Has been cancelled
release / linux (push) Has been cancelled
release / macos (push) Has been cancelled

This commit is contained in:
Yun Chan 2026-09-18 23:32:32 +09:00
parent bcd242691f
commit 8677853822
100 changed files with 4814 additions and 333 deletions

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Globalization;
namespace Paca.Core;
@ -39,6 +39,19 @@ public static class AudioDynamicRangeCompressor
/// </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";
}
@ -48,6 +61,16 @@ public static class AudioDynamicRangeCompressor
/// </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}\"";