Files
searchi/tools/media-eval/prepare.py
T

62 lines
3.7 KiB
Python
Raw Normal View History

"""Prepare real CPU-decoded media candidates after verifying frozen labels.
No generated captions, placeholder vectors, or model calls occur in preparation.
"""
import argparse
import hashlib
import json
from pathlib import Path
import subprocess
def verify(root):
fixture_path = root / "fixture.json"
lock = json.loads((root / "labels.lock.json").read_text())
assert hashlib.sha256(fixture_path.read_bytes()).hexdigest() == lock["fixture_sha256"], "Frozen labels changed"
fixture = json.loads(fixture_path.read_text(encoding="utf-8"))
for asset in fixture["assets"]:
path = (root / asset["path"]).resolve()
assert path.is_relative_to(root.resolve()), "Asset escaped corpus root"
assert path.stat().st_size == asset["bytes"], asset["id"]
assert hashlib.sha256(path.read_bytes()).hexdigest() == asset["sha256"], asset["id"]
return fixture
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--root", type=Path, default=Path("fixtures/media"))
parser.add_argument("--ffmpeg", type=Path, required=True)
parser.add_argument("--ffprobe", type=Path, required=True)
args = parser.parse_args()
root = args.root.resolve()
fixture = verify(root)
derived = root / "derived"
derived.mkdir(exist_ok=True)
candidates = []
for asset in fixture["assets"]:
source = root / asset["path"]
if asset["kind"] == "image":
candidates.append(dict(asset=asset["id"], path=str(source), modality="image", token_budget=140))
continue
probe = json.loads(subprocess.check_output([str(args.ffprobe), "-v", "error", "-show_format", "-show_streams", "-of", "json", str(source)], text=True))
duration = float(probe["format"]["duration"])
if asset["kind"] == "video":
for stamp in range(0, int(duration) + 1, 2):
output = derived / f"{asset['id']}-frame-{stamp:04}.png"
subprocess.run([str(args.ffmpeg), "-hide_banner", "-loglevel", "error", "-hwaccel", "none", "-threads", "1", "-ss", str(stamp), "-i", str(source), "-frames:v", "1", "-vf", "scale=480:-1", "-threads", "1", "-y", str(output)], check=True)
if output.is_file():
candidates.append(dict(asset=asset["id"], path=str(output), modality="image", token_budget=70, start_s=stamp, end_s=min(stamp + 2, duration)))
if any(stream["codec_type"] == "audio" for stream in probe["streams"]):
for stamp in range(0, int(duration) + 1, 30):
output = derived / f"{asset['id']}-audio-{stamp:04}.f32"
subprocess.run([str(args.ffmpeg), "-hide_banner", "-loglevel", "error", "-hwaccel", "none", "-threads", "1", "-ss", str(stamp), "-i", str(source), "-t", "30", "-vn", "-ac", "1", "-ar", "16000", "-f", "f32le", "-threads", "1", "-y", str(output)], check=True)
if output.stat().st_size:
assert output.stat().st_size <= 480000 * 4, "Audio exceeds worker budget"
candidates.append(dict(asset=asset["id"], path=str(output), modality="audio", start_s=stamp, end_s=min(stamp + 30, duration)))
manifest = dict(fixture_sha256=hashlib.sha256((root / "fixture.json").read_bytes()).hexdigest(), ffmpeg_version=subprocess.check_output([str(args.ffmpeg), "-version"], text=True).splitlines()[0], policy="CPU-only dense reference: photos140/video70 tokens, uniform2s frames, mono16kHz30s PCM", candidates=candidates)
(derived / "candidates.json").write_text(json.dumps(manifest, indent=2), encoding="utf-8")
print(json.dumps(dict(candidates=len(candidates), images=sum(item["modality"] == "image" for item in candidates), audio=sum(item["modality"] == "audio" for item in candidates))))
if __name__ == "__main__":
main()