|
|
|
|
|
""" |
|
|
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 |
|
|
|
|
|
|
|
|
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.") |
|
|
|
|
|
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() |
|
|
|