"""생성기 함수를 재사용하지 않는 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))