+14
-9
@@ -2,9 +2,10 @@ from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import pathlib
|
||||
import re
|
||||
import sqlite3
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
APPLICATION_ID = 0x56414D50
|
||||
COMMON_COLUMNS = """
|
||||
@@ -15,10 +16,11 @@ COMMON_COLUMNS = """
|
||||
source_path TEXT NOT NULL UNIQUE CHECK(length(source_path) > 0),
|
||||
source_json TEXT NOT NULL CHECK(json_valid(source_json)),
|
||||
measure_count INTEGER NOT NULL CHECK(measure_count > 0),
|
||||
total_pages INTEGER NOT NULL CHECK(total_pages BETWEEN 1 AND 10000)
|
||||
total_pages INTEGER NOT NULL CHECK(total_pages BETWEEN 1 AND 10000),
|
||||
title_image TEXT NOT NULL CHECK(length(title_image) = 64 AND title_image NOT GLOB '*[^0-9a-f]*')
|
||||
"""
|
||||
|
||||
def metadata(path: Path, root: Path) -> dict:
|
||||
def metadata(path: pathlib.Path, root: pathlib.Path) -> dict:
|
||||
if path.is_symlink() or not path.resolve().is_relative_to(root.resolve()):
|
||||
raise ValueError('score escapes the asset root')
|
||||
text = path.read_text(encoding='utf-8')
|
||||
@@ -33,14 +35,17 @@ def metadata(path: Path, root: Path) -> dict:
|
||||
if not isinstance(meta[name], str):
|
||||
raise ValueError(f'{name} must be a string')
|
||||
key = meta['key_signature']
|
||||
title_image = presentation.get('title_image')
|
||||
if not isinstance(title_image, str) or not re.fullmatch('[0-9a-f]{64}', title_image):
|
||||
raise ValueError('presentation requires a generated Pico title image; regenerate assets')
|
||||
if not isinstance(staff['name'], str):
|
||||
raise ValueError('staff name must be a string')
|
||||
relative = path.relative_to(root)
|
||||
return dict(id=path.stem, title=meta['title'], composer=meta['composer'], key_signature=key,
|
||||
source_path=relative.as_posix(), source_json=text,
|
||||
measure_count=len(staff['measures']), total_pages=len(presentation['pages']), part=staff['name'])
|
||||
measure_count=len(staff['measures']), total_pages=len(presentation['pages']), title_image=title_image, part=staff['name'])
|
||||
|
||||
def populate(connection: sqlite3.Connection, root: Path) -> None:
|
||||
def populate(connection: sqlite3.Connection, root: pathlib.Path) -> None:
|
||||
if not root.is_dir():
|
||||
raise ValueError(f'{root}: score asset directory not found')
|
||||
connection.executescript(f'''
|
||||
@@ -81,13 +86,13 @@ def populate(connection: sqlite3.Connection, root: Path) -> None:
|
||||
if connection.execute('PRAGMA integrity_check').fetchall() != [('ok',)]:
|
||||
raise ValueError('catalog integrity check failed')
|
||||
|
||||
def build_catalog(root: Path, destination: Path | None = None) -> Path:
|
||||
def build_catalog(root: pathlib.Path, destination: pathlib.Path | None = None) -> pathlib.Path:
|
||||
if not root.is_dir():
|
||||
raise ValueError(f'{root}: score asset directory not found')
|
||||
destination = destination or root / 'catalog.db'
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
with tempfile.TemporaryDirectory(prefix='.vamp-catalog-', dir=destination.parent) as temporary:
|
||||
staged = Path(temporary) / 'catalog.db'
|
||||
staged = pathlib.Path(temporary) / 'catalog.db'
|
||||
connection = sqlite3.connect(staged)
|
||||
try:
|
||||
populate(connection, root)
|
||||
@@ -98,7 +103,7 @@ def build_catalog(root: Path, destination: Path | None = None) -> Path:
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='Compile generated score bundles into a read-only runtime catalog.')
|
||||
parser.add_argument('assets', type=Path)
|
||||
parser.add_argument('destination', type=Path, nargs='?')
|
||||
parser.add_argument('assets', type=pathlib.Path)
|
||||
parser.add_argument('destination', type=pathlib.Path, nargs='?')
|
||||
arguments = parser.parse_args()
|
||||
build_catalog(arguments.assets, arguments.destination)
|
||||
|
||||
Reference in New Issue
Block a user