#!/usr/bin/env python3 # Copyright (c) 2026 Krzysztof Rudnicki. MIT licence. """Generating the media scripts/e2e.sh feeds to !add_photo. Split out of e2e_media.py at the 250-line cap. Nothing here is committed as a file: a fixture photo in this repository is exactly what the binary gate and the personal-data gate exist to prevent, so every sample is built at run time and carries *real* metadata -- a real APP1 Exif segment, a real QuickTime geotag, a real HEVC parameter set. A file with nothing to strip proves nothing. """ from __future__ import annotations import tempfile from pathlib import Path from typing import Final from signal_bot.external import run from signal_bot.media_strip import locate # Resolved the way the bot resolves it: Arch keeps exiftool under # /usr/bin/vendor_perl, which a non-login shell's PATH does not have, and a # fixture builder that cannot find the stripper's own tool proves nothing. EXIFTOOL: Final = locate() or "exiftool" # Warsaw, in an actual GPS tag. If any of this survives, the run must fail. LATITUDE: Final = "52.2297" LONGITUDE: Final = "21.0122" PROBE_MAKE: Final = "SecretPhoneModel" PROBE_CREATOR: Final = "A Real Person Name" # An SVG carrying both things exiftool cannot help with: a metadata block and # a whole embedded raster. Stored as-is, per the decision recorded in # DOCS-add-photo.md -- this exists so the e2e run *demonstrates* that hole # rather than leaving it to the prose. SVG_DOCUMENT: Final = ( '' '' f"{PROBE_CREATOR}" '' "" ).encode() # Not a media file in any sense. The sniffer must refuse it on the bytes. JUNK: Final = b"this is not a photo, it is a text file pretending to be one\n" class ToolError(RuntimeError): """An external tool this script needs did not do its job.""" def run_tool(command: list[str]) -> str: """Run a tool and return its stdout, failing loudly if it errors. Goes through signal_bot.external like everything else that starts a process, and turns its "ok=False" into an exception because this is a script: there is nothing to degrade to. """ result = run(command) if not result.ok: msg = f"{command[0]} failed: {result.stderr.strip()}" raise ToolError(msg) return result.stdout def _jpeg(workspace: Path, name: str, colour: str) -> bytes: """A JPEG with a real APP1 Exif segment: GPS, Make and an XMP creator.""" path = workspace / f"{name}.jpg" run_tool( [ "ffmpeg", "-loglevel", "error", "-f", "lavfi", "-i", f"color=c={colour}:s=48x48:d=1", "-frames:v", "1", str(path), "-y", ] ) run_tool( [ EXIFTOOL, "-q", "-overwrite_original", f"-GPSLatitude={LATITUDE}", "-GPSLatitudeRef=N", f"-GPSLongitude={LONGITUDE}", "-GPSLongitudeRef=E", f"-Make={PROBE_MAKE}", f"-XMP:Creator={PROBE_CREATOR}", str(path), ] ) return path.read_bytes() def _mp4(workspace: Path) -> bytes: """An MP4 geotagged the way a phone does it: a QuickTime atom, not GPS. This is the case a naive "-Exif:all -GPS:all -XMP:all" gate passes straight through, because exiftool files the atom under QuickTime. """ path = workspace / "clip.mp4" run_tool( [ "ffmpeg", "-loglevel", "error", "-f", "lavfi", "-i", "color=c=navy:s=48x48:d=1", "-c:v", "libx264", "-pix_fmt", "yuv420p", str(path), "-y", ] ) run_tool( [ EXIFTOOL, "-q", "-overwrite_original", f"-QuickTime:GPSCoordinates={LATITUDE} {LONGITUDE} 0", f"-XMP:Creator={PROBE_CREATOR}", str(path), ] ) return path.read_bytes() def _heic(workspace: Path) -> bytes: """A geotagged HEIC -- what an iPhone actually sends. Its HEVC parameter set survives the strip and is on the allowlist; the GPS and the Make must not survive, and verify_pack re-reads the stored file to prove it. """ path = workspace / "shot.heic" source = workspace / "heic-source.jpg" run_tool( [ "ffmpeg", "-loglevel", "error", "-f", "lavfi", "-i", "color=c=teal:s=48x48:d=1", "-frames:v", "1", str(source), "-y", ] ) run_tool(["heif-enc", str(source), "-o", str(path)]) run_tool( [ EXIFTOOL, "-q", "-overwrite_original", f"-GPSLatitude={LATITUDE}", "-GPSLatitudeRef=N", f"-Make={PROBE_MAKE}", f"-XMP:Creator={PROBE_CREATOR}", str(path), ] ) return path.read_bytes() def build() -> dict[str, bytes]: """Every attachment the scenarios reference, by the id they name.""" with tempfile.TemporaryDirectory(prefix="e2e-media-") as raw: workspace = Path(raw) return { "att-person": _jpeg(workspace, "person", "orange"), "att-custom": _jpeg(workspace, "custom", "purple"), "att-second": _jpeg(workspace, "second", "green"), "att-video": _mp4(workspace), "att-heic": _heic(workspace), "att-svg": SVG_DOCUMENT, "att-junk": JUNK, }