Files
product-vamp/generator/engraving/validation.py
T
khodowany 09656a9d60
ci / checks (push) Successful in 48m34s
Package
2026-09-30 19:46:37 -05:00

46 lines
2.0 KiB
Python

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