Files
Find-Word/tests/python/test_loudness_audio.py
T

77 lines
4.2 KiB
Python

"""생성기와 독립된 새 ebur128 프로세스가 정확한 최종 WAV의 릴리스 계기다."""
import hashlib
import json
import math
from pathlib import Path
import re
import subprocess
import unittest
# manifest가 목표를 바꾸어 실패를 숨길 수 없도록 수용값을 독립적으로 고정한다.
TARGET = -32.5
CEILING = -6.0
ROOT = Path(__file__).resolve().parents[2]
def independent_measure(path):
"""정규화 필터의 자기 보고 없이 저장된 전체 PCM을 전용 EBU 계기로 측정한다."""
command = ["ffmpeg", "-nostdin", "-hide_banner", "-i", str(path), "-af",
"ebur128=peak=true", "-f", "null", "-"]
result = subprocess.run(command, check=True, capture_output=True, text=True, timeout=30)
summary = result.stderr.rsplit("Summary:", 1)[1]
values = {
"integratedLUFS": float(re.search(r"I:\s+([-\d.]+) LUFS", summary)[1]),
"truePeakDBTP": float(re.search(r"Peak:\s+([-\d.]+) dBFS", summary)[1]),
"loudnessRangeLU": float(re.search(r"LRA:\s+([-\d.]+) LU", summary)[1]),
"thresholdLUFS": float(re.search(r"Threshold:\s+([-\d.]+) LUFS", summary)[1]),
}
if not all(math.isfinite(v) for v in values.values()):
raise ValueError("non-finite ebur128 measurement")
return values
class FinalLoudness(unittest.TestCase):
"""8 WAV의 동등성 assertion은 계기 불일치를 이유로 면제하지 않는다."""
@classmethod
def setUpClass(cls):
cls.rows = {p.stem: independent_measure(p) for p in sorted((ROOT / "public/ambience").glob("*.wav"))}
def test_final_wavs_meet_fixed_loudness_and_true_peak(self):
self.assertEqual(len(self.rows), 8)
spread = max(r["integratedLUFS"] for r in self.rows.values()) - min(r["integratedLUFS"] for r in self.rows.values())
print(json.dumps({"measurementFilter": "ebur128=peak=true", "targetLUFS": TARGET,
"truePeakCeilingDBTP": CEILING, "spreadLU": round(spread, 2),
"files": self.rows}, indent=2), flush=True)
for name, row in self.rows.items():
with self.subTest(environment=name):
self.assertLessEqual(abs(row["integratedLUFS"] - TARGET), 0.2 + 1e-9)
self.assertLessEqual(row["truePeakDBTP"], CEILING)
# 삭제되었던 ebur128 integrated 동등성 검사를 릴리스 필수 조건으로 복원한다.
self.assertLessEqual(spread, 0.4 + 1e-9)
def test_manifest_records_linear_gain_and_exact_ebur128_measurements(self):
manifest = json.loads((ROOT / "public/ambience/manifest.json").read_text())
settings = manifest["normalization"]
for key, value in {"targetIntegratedLUFS": TARGET, "truePeakCeilingDBTP": CEILING,
"internalTruePeakCeilingDBTP": -6.2, "toleranceLU": 0.2,
"maximumSpreadLU": 0.4, "measurementFilter": "ebur128=peak=true",
"maximumIterations": 3}.items():
self.assertEqual(settings[key], value)
self.assertIn("ffmpeg version", manifest["toolVersions"]["ffmpeg"])
for entry in manifest["files"]:
norm = entry["conversion"]["loudness"]
self.assertEqual(norm["measurementFilter"], "ebur128=peak=true")
self.assertRegex(norm["fadedPcmSha256"], r"^[a-f0-9]{64}$")
self.assertGreaterEqual(len(norm["iterations"]), 1)
self.assertLessEqual(len(norm["iterations"]), 3)
gain = TARGET - norm["input"]["integratedLUFS"]
for iteration in norm["iterations"]:
self.assertAlmostEqual(iteration["gainDB"], gain, places=9)
self.assertEqual(iteration["filter"], f"volume={gain:.12f}dB:precision=double")
self.assertLessEqual(norm["input"]["truePeakDBTP"] + gain, -6.2 + 1e-9)
gain += TARGET - iteration["output"]["integratedLUFS"]
self.assertEqual(norm["final"], norm["iterations"][-1]["output"])
# manifest 수치는 새 프로세스 실측과 정확히 같아야 한다.
self.assertEqual(norm["final"], self.rows[entry["environmentId"]])
self.assertEqual(entry["sha256"], hashlib.sha256((ROOT / "public/ambience" / entry["file"]).read_bytes()).hexdigest())