72 lines
4.1 KiB
Python
72 lines
4.1 KiB
Python
"""생성기 함수를 재사용하지 않는 v0.1.1 인수 검사: 원문·이전 해시·PCM·FFmpeg."""
|
|
from array import array
|
|
import hashlib
|
|
import json
|
|
import math
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
import wave
|
|
|
|
root = Path(__file__).resolve().parents[2]
|
|
evidence = root / ".agent/evidence-v0.1.1"
|
|
manifest = json.loads((root / "public/audio/manifest.json").read_text())
|
|
baseline = json.loads((evidence / "baseline.json").read_text())
|
|
old = {entry["file"]: entry for entry in baseline["audio"]["files"]}
|
|
expected = json.loads(subprocess.check_output([
|
|
"node", "--experimental-strip-types", "--input-type=module", "-e",
|
|
"import {INTRO,PATTERNS,WORDS,renderSentence} from './src/content.ts'; "
|
|
"console.log(JSON.stringify([{file:'intro.wav',text:INTRO},"
|
|
"...PATTERNS.flatMap(p=>WORDS.map(w=>({file:p.id+'-'+w.id+'.wav',text:renderSentence(p,w)})))]));"
|
|
], cwd=root, text=True))
|
|
assert manifest["version"] == "0.1.1"
|
|
assert manifest["provider"] == "Microsoft Edge online TTS"
|
|
assert manifest["voice"] == "ko-KR-HyunsuMultilingualNeural"
|
|
assert manifest["rate"] == "-6%" and manifest["pitch"] == "-4Hz"
|
|
assert manifest["toolVersions"]["edge-tts"] == "7.3.1"
|
|
assert [{"file": e["file"], "text": e["text"]} for e in manifest["files"]] == expected
|
|
assert len(expected) == 101
|
|
assert {p.name for p in (root / "public/audio").iterdir()} == {e["file"] for e in expected} | {"manifest.json"}
|
|
rows = []
|
|
for entry in manifest["files"]:
|
|
path = root / "public/audio" / entry["file"]
|
|
data = path.read_bytes()
|
|
digest = hashlib.sha256(data).hexdigest()
|
|
assert digest == entry["sha256"] and digest != old[entry["file"]]["sha256"]
|
|
assert len(data) == entry["bytes"]
|
|
with wave.open(str(path), "rb") as stream:
|
|
assert (stream.getframerate(), stream.getnchannels(), stream.getsampwidth(), stream.getcomptype()) == (24000, 1, 2, "NONE")
|
|
duration = stream.getnframes()/24000
|
|
assert 1 <= duration <= 20 and duration == entry["duration"]
|
|
samples = array("h", stream.readframes(stream.getnframes()))
|
|
if sys.byteorder != "little":
|
|
samples.byteswap()
|
|
rms = math.sqrt(sum(x*x for x in samples)/len(samples))/32768
|
|
peak = max(abs(x) for x in samples)/32768
|
|
assert rms > 100/32768 and peak < 32767/32768
|
|
assert peak/rms*0.03*2 < 0.8
|
|
assert abs(entry["rms"]-rms) < 1e-12 and abs(entry["peak"]-peak) < 1e-12
|
|
probe = json.loads(subprocess.check_output([
|
|
"ffprobe", "-v", "error", "-show_entries",
|
|
"stream=codec_name,sample_fmt,sample_rate,channels,bits_per_sample,duration", "-of", "json", str(path)
|
|
], text=True))["streams"]
|
|
assert len(probe) == 1
|
|
stream = probe[0]
|
|
assert (stream["codec_name"], stream["sample_fmt"], stream["sample_rate"], stream["channels"], stream["bits_per_sample"]) == ("pcm_s16le", "s16", "24000", 1, 16)
|
|
assert abs(float(stream["duration"])-duration) < 1e-5
|
|
subprocess.run(["ffmpeg", "-v", "error", "-xerror", "-i", str(path), "-f", "null", "-"], check=True, capture_output=True)
|
|
rows.append({"file": path.name, "sha256": digest, "bytes": len(data), "duration": duration,
|
|
"rms": rms, "peak": peak, "ffprobe": stream, "newVsV01": True, "ffmpegDecoded": True})
|
|
print(f"PASS {path.name}: {duration:.3f}s, RMS={rms:.6f}, peak={peak:.6f}", flush=True)
|
|
assert len({r["sha256"] for r in rows}) == 101
|
|
assert sum(r["bytes"] for r in rows) < 48*1024*1024
|
|
for path, digest in baseline["historical"].items():
|
|
assert hashlib.sha256((root / path).read_bytes()).hexdigest() == digest, path
|
|
summary = {"count": len(rows), "newVsV01": 101, "uniqueHashes": 101, "bytes": sum(r["bytes"] for r in rows),
|
|
"durationRange": [min(r["duration"] for r in rows), max(r["duration"] for r in rows)],
|
|
"rmsRange": [min(r["rms"] for r in rows), max(r["rms"] for r in rows)],
|
|
"peakRange": [min(r["peak"] for r in rows), max(r["peak"] for r in rows)],
|
|
"historicalUnchanged": len(baseline["historical"]), "files": rows}
|
|
(evidence / "audio-integrity.json").write_text(json.dumps(summary, ensure_ascii=False, indent=2)+"\n")
|
|
print(json.dumps({k:v for k,v in summary.items() if k != "files"}, indent=2))
|