from __future__ import annotations import hashlib import json from pathlib import Path import shutil from PIL import Image, ImageChops from generator.engraving.fonts import Fonts from generator.engraving.layout import layout_score from generator.engraving.raster import rasterize_page from generator.engraving.types import EngravingError, Style ENGINE_NAME = 'Vamp Python' ENGINE_VERSION = '1.0.0' PAGE_WIDTH = 976 PAGE_HEIGHT = 484 def check_page(path: Path) -> None: with Image.open(path) as image: image.load() if image.format != 'PNG' or image.size != (PAGE_WIDTH, PAGE_HEIGHT): raise EngravingError(f'{path.name}: expected a {PAGE_WIDTH}x{PAGE_HEIGHT} PNG page') white = Image.new('RGBA', image.size, 'white') white.alpha_composite(image.convert('RGBA')) ink = ImageChops.difference(white.convert('RGB'), Image.new('RGB', image.size, 'white')).convert('L').point(lambda x: 255 if x > 32 else 0) bounds = ink.getbbox() if not bounds: raise EngravingError(f'{path.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 EngravingError(f'{path.name}: notation reaches the page edge; refusing potentially clipped output') def engrave_tree(root: Path, fonts: Path) -> None: paths = sorted(root.rglob('*.json')) if not paths: return family = Fonts(fonts) style = Style() for path in paths: document = json.loads(path.read_text(encoding='utf-8')) try: layout = layout_score(document, style, family) destination = path.with_suffix('.pages') if destination.exists(): shutil.rmtree(destination) destination.mkdir() entries = [] for page in layout.pages: name = f'{page.number:04}.png' target = destination / name rasterize_page(page, family).save(target, format='PNG') check_page(target) entries.append({'path': destination.name + '/' + name, 'sha256': hashlib.sha256(target.read_bytes()).hexdigest()}) music = {k: v for k, v in document.items() if k != 'schema_version'} bundle = {'schema_version': 3, 'music': music, 'presentation': {'engine': ENGINE_NAME, 'engine_version': ENGINE_VERSION, 'style_sha256': layout.fingerprint, 'width': PAGE_WIDTH, 'height': PAGE_HEIGHT, 'pages': entries}} path.write_text(json.dumps(bundle, indent=2, ensure_ascii=False) + '\n', encoding='utf-8') except ValueError as error: raise EngravingError(f'{path.relative_to(root)}: {error}') from error