Created
October 15, 2026 08:36
-
-
Save hitode909/d0e39646099d82618efba24fd2786fd9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """Create an intentionally non-Exif-compliant JPEG with recursively nested JPEG thumbnails. | |
| Requires Pillow: python -m pip install Pillow | |
| Example: python generate_matryoshka_exif.py input.jpg output.jpg --sizes 160 96 48 | |
| """ | |
| import argparse | |
| import io | |
| import struct | |
| from pathlib import Path | |
| from PIL import Image | |
| def thumbnail(source: Image.Image, size: int, quality: int) -> bytes: | |
| image = source.copy().convert('RGB') | |
| image.thumbnail((size, size), Image.Resampling.LANCZOS) | |
| buf = io.BytesIO() | |
| image.save(buf, format='JPEG', quality=quality, optimize=True) | |
| return buf.getvalue() | |
| def exif_app1(child: bytes) -> bytes: | |
| # A minimal little-endian TIFF with an empty IFD0 and a 3-entry IFD1. | |
| tiff = bytearray(b'II' + struct.pack('<HI', 42, 8)) | |
| tiff += struct.pack('<HI', 0, 14) # IFD0: 0 entries, IFD1 at 14 | |
| tiff += struct.pack('<H', 3) # IFD1: 3 entries | |
| tiff += struct.pack('<HHI', 259, 3, 1) + struct.pack('<H', 6) + b'\0\0' | |
| tiff += struct.pack('<HHII', 513, 4, 1, 56) | |
| tiff += struct.pack('<HHII', 514, 4, 1, len(child)) | |
| tiff += struct.pack('<I', 0) | |
| assert len(tiff) == 56 | |
| payload = b'Exif\0\0' + tiff + child | |
| length = len(payload) + 2 | |
| if length > 65535: | |
| raise ValueError(f'APP1 requires {length} bytes (maximum 65535). ' | |
| 'Try smaller thumbnail sizes or lower quality.') | |
| return b'\xff\xe1' + struct.pack('>H', length) + payload | |
| def attach(jpeg: bytes, child: bytes) -> bytes: | |
| if not jpeg.startswith(b'\xff\xd8'): | |
| raise ValueError('Not a JPEG stream') | |
| return jpeg[:2] + exif_app1(child) + jpeg[2:] | |
| def main() -> None: | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument('input', type=Path) | |
| parser.add_argument('output', type=Path) | |
| parser.add_argument('--sizes', nargs='+', type=int, default=[160, 96, 48], | |
| help='thumbnail maximum dimensions outer to inner, default: 160 96 48') | |
| parser.add_argument('--qualities', nargs='+', type=int, default=[60, 55, 45], | |
| help='JPEG qualities outer to inner, default: 60 55 45') | |
| args = parser.parse_args() | |
| if not args.sizes or len(args.sizes) != len(args.qualities): | |
| parser.error('--sizes and --qualities must have the same nonzero count') | |
| if any(s <= 0 for s in args.sizes) or any(not 1 <= q <= 95 for q in args.qualities): | |
| parser.error('sizes must be positive; qualities must be 1..95') | |
| if args.input.resolve() == args.output.resolve(): | |
| parser.error('output must differ from input') | |
| with Image.open(args.input) as image: | |
| if image.format != 'JPEG': | |
| parser.error('input must be JPEG') | |
| source = image.copy() | |
| layers = [thumbnail(source, sz, q) for sz, q in zip(args.sizes, args.qualities)] | |
| nested = layers[-1] | |
| for parent in reversed(layers[:-1]): | |
| nested = attach(parent, nested) | |
| outer = args.input.read_bytes() | |
| result = attach(outer, nested) | |
| args.output.write_bytes(result) | |
| print(f'Created {args.output} ({len(result)} bytes; {len(layers)} nested thumbnails)') | |
| if __name__ == '__main__': | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment