49 lines
3.2 KiB
Python
49 lines
3.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Verifica con herramientas INDEPENDIENTES (Pillow, ffprobe) los archivos que escribe Tahgibu.
|
|
Uso: mkdir -p /tmp/tgb-art && TGB_TEST_ARTIFACTS=/tmp/tgb-art <ruta>/test_io_files && python3 tools/verify-external.py /tmp/tgb-art"""
|
|
import base64, io, json, subprocess, sys, shutil
|
|
from PIL import Image, ImageSequence
|
|
|
|
d = sys.argv[1]
|
|
ok = True
|
|
def check(cond, msg):
|
|
global ok
|
|
print((" OK " if cond else " FALLO ") + msg)
|
|
ok &= bool(cond)
|
|
|
|
png0 = Image.open(f"{d}/muestra_f0.png"); png0.load()
|
|
check(png0.format == "PNG" and png0.mode == "RGBA" and png0.size == (48, 32), f"PNG exportado lo abre Pillow: {png0.format} {png0.mode} {png0.size}")
|
|
|
|
v2 = json.load(open(f"{d}/muestra_v2.tgb"))
|
|
check(v2["format"] == "TAHGIBU" and v2["version"] == 2 and v2["fps"] == 12 and (v2["width"], v2["height"]) == (48, 32), "cabecera formato 2 coincide con el ejemplo histórico")
|
|
v2_frames = [Image.open(io.BytesIO(base64.b64decode(f))).convert("RGBA") for f in v2["frames"]]
|
|
check(len(v2_frames) == 3 and all(f.size == (48, 32) for f in v2_frames), "formato 2: 3 fotogramas PNG válidos en Base64 (decodificados con base64+Pillow)")
|
|
check(list(png0.getdata()) == list(v2_frames[0].getdata()), "el PNG exportado == fotograma 0 del .tgb v2")
|
|
|
|
v3 = json.load(open(f"{d}/muestra_v3.tgb"))
|
|
check(v3["version"] == 3 and "tgb3" in v3 and len(v3["frames"]) == 3, "formato 3: 'frames' aplanados + bloque 'tgb3'")
|
|
v3_flat = [Image.open(io.BytesIO(base64.b64decode(f))).convert("RGBA") for f in v3["frames"]]
|
|
check(all(list(a.getdata()) == list(b.getdata()) for a, b in zip(v3_flat, v2_frames)), "formato 3: los fotogramas aplanados == formato 2")
|
|
imgs = [Image.open(io.BytesIO(base64.b64decode(s))) for s in v3["tgb3"]["images"]]
|
|
check(all(i.size == (48, 32) and i.mode == "RGBA" for i in imgs), f"formato 3: {len(imgs)} imágenes de celda válidas")
|
|
check([f["duration_ms"] for f in v3["tgb3"]["frames"]] == [100, 250, 40], "formato 3: duraciones por fotograma")
|
|
|
|
ap = Image.open(f"{d}/muestra.apng")
|
|
check(getattr(ap, "is_animated", False) and ap.n_frames == 3, f"APNG: Pillow lo reconoce animado con {getattr(ap, 'n_frames', '?')} fotogramas")
|
|
frames, durs = [], []
|
|
for fr in ImageSequence.Iterator(ap):
|
|
frames.append(fr.convert("RGBA").copy()); durs.append(fr.info.get("duration"))
|
|
check(durs == [100, 250, 40], f"APNG: duraciones {durs}")
|
|
check(all(list(a.getdata()) == list(b.getdata()) for a, b in zip(frames, v2_frames)), "APNG: cada fotograma == fotograma del proyecto (píxel a píxel)")
|
|
|
|
if shutil.which("ffprobe"):
|
|
out = subprocess.run(["ffprobe", "-v", "error", "-count_frames", "-select_streams", "v:0", "-show_entries", "stream=nb_read_frames,width,height,codec_name",
|
|
"-of", "json", f"{d}/muestra.apng"], capture_output=True, text=True)
|
|
info = json.loads(out.stdout or "{}").get("streams", [{}])[0]
|
|
check(out.returncode == 0 and info.get("codec_name") == "apng" and int(info.get("nb_read_frames", 0)) == 3,
|
|
f"ffprobe: codec={info.get('codec_name')} fotogramas={info.get('nb_read_frames')} {info.get('width')}x{info.get('height')}")
|
|
else:
|
|
print(" (ffprobe no disponible: omitido)")
|
|
print("RESULTADO:", "TODO CORRECTO" if ok else "HAY FALLOS")
|
|
sys.exit(0 if ok else 1)
|