from __future__ import annotations import hashlib import io import pathlib import re import PIL.Image import PIL.ImageChops import generator.engraving.types import generator.theme ENGINE_NAME = 'Vamp Python' PAGE_WIDTH = 976 PAGE_HEIGHT = 484 def check_content(data: bytes, name: str) -> None: with PIL.Image.open(io.BytesIO(data)) as image: if image.format != 'PNG' or image.size != (PAGE_WIDTH, PAGE_HEIGHT): raise generator.engraving.types.EngravingError(f'{name}: expected a {PAGE_WIDTH}x{PAGE_HEIGHT} PNG page') image.load() background = PIL.Image.new('RGB', image.size, generator.theme.rgb('background')) composited = background.convert('RGBA') composited.alpha_composite(image.convert('RGBA')) ink = PIL.ImageChops.difference(composited.convert('RGB'), background).convert('L').point(lambda x: 255 if x > 32 else 0) bounds = ink.getbbox() if not bounds: raise generator.engraving.types.EngravingError(f'{name}: engraver produced a blank page') if bounds[0] < 4 or bounds[1] < 4 or bounds[2] > PAGE_WIDTH - 4 or bounds[3] > PAGE_HEIGHT - 4: raise generator.engraving.types.EngravingError(f'{name}: notation reaches the page edge; refusing potentially clipped output') def check_page(path: pathlib.Path, validated_pages: set[str] | None = None, *, expected_sha256: str | None = None) -> str: # Trust content, never a filename or a caller-supplied checksum alone. if path.is_symlink() or not path.is_file(): raise generator.engraving.types.EngravingError(f'{path.name}: expected a regular PNG file') data = path.read_bytes() digest = hashlib.sha256(data).hexdigest() if expected_sha256 is not None and (not isinstance(expected_sha256, str) or not re.fullmatch('[0-9a-f]{64}', expected_sha256) or digest != expected_sha256): raise generator.engraving.types.EngravingError(f'{path.name}: score page checksum mismatch') if validated_pages is None or digest not in validated_pages: check_content(data, path.name) if validated_pages is not None: validated_pages.add(digest) return digest