df2 / scripts /dump_ela_map.py
Mustafa Akcanca
Add trufor and ela
a5cb443
#!/usr/bin/env python3
"""
Dump ELA map to a PNG for visual inspection.
Usage:
python scripts/dump_ela_map.py --image path/to/image.jpg --out ela_map.png
All parameters match Sherloq defaults. Adjust as needed:
python scripts/dump_ela_map.py --image img.jpg --out ela.png --quality 75 --scale 50 --contrast 20
"""
import argparse
import base64
import json
import sys
from io import BytesIO
from pathlib import Path
from PIL import Image
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from src.tools.forensic import perform_ela # noqa: E402
def decode_and_save(ela_map: str, out_path: Path):
"""Decode data:image/png;base64,... to a file."""
if not ela_map or not ela_map.startswith("data:image/png;base64,"):
raise ValueError("ELA map missing or not a base64 PNG data URL.")
b64 = ela_map.split(",", 1)[1]
data = base64.b64decode(b64)
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_bytes(data)
def main():
parser = argparse.ArgumentParser(description="Dump ELA map to PNG (Sherloq-compatible).")
parser.add_argument("--image", required=True, help="Path to source image.")
parser.add_argument("--out", required=True, help="Output PNG path.")
# Defaults match Sherloq
parser.add_argument("--quality", type=int, default=75, help="JPEG recompression quality (1-100). Default: 75")
parser.add_argument("--max-size", type=int, default=0, help="Max side length (0=no resize). Default: 0")
parser.add_argument("--scale", type=int, default=50, help="Scale factor (1-100). Default: 50")
parser.add_argument("--contrast", type=int, default=20, help="Contrast adjustment %% (0-100). Default: 20")
parser.add_argument("--linear", action="store_true", help="Use linear mode (default: non-linear with sqrt)")
parser.add_argument("--grayscale", action="store_true", help="Output grayscale (default: color RGB)")
args = parser.parse_args()
payload = {
"path": args.image,
"quality": args.quality,
"max_size": args.max_size,
"scale": args.scale,
"contrast": args.contrast,
"linear": args.linear,
"grayscale": args.grayscale,
"return_map": True,
}
result = json.loads(perform_ela(json.dumps(payload)))
if result.get("status") != "completed":
raise RuntimeError(f"ELA failed: {result}")
ela_map = result.get("ela_map")
if ela_map is None:
raise RuntimeError("ELA map missing in result.")
decode_and_save(ela_map, Path(args.out))
reported_size = result.get("ela_map_size")
mode = "linear" if args.linear else "non-linear (sqrt)"
color = "grayscale" if args.grayscale else "color"
print(f"Saved ELA map to {args.out}")
print(f" Size: {reported_size}, Quality: {args.quality}, Scale: {args.scale}, Contrast: {args.contrast}%")
print(f" Mode: {mode}, Output: {color}")
if __name__ == "__main__":
main()