Add the render pipeline and bundle export
Project state plus PDF in, finished slice images out. Slices are cut as polygons rather than row ranges, so a stepped cut yields a slice with a transparent notch instead of one that covers its neighbour. Masking paints white, which the ink-to-alpha step turns into full transparency — the same outcome the spec asks for, one step earlier. Scale normalises every slice to the median staff height before fitting the song to 1920px, so a rescanned page sits at the same note size as its neighbours. The cap only ever shrinks: a song narrower than 1920 stays narrower. Alpha quantisation rounds to 16 values spanning 0-255 inclusive. Flooring, as first written, capped full ink at 240 and left every note 6% transparent — caught by decoding an exported slice rather than by reading the code. Ketun joululaulu exports 24 slices at a uniform 1489px, under the cap and correctly not upscaled from its 200 DPI source; Feliz Navidad 20; Elaman nalka 18. Closes #21 Closes #22 Closes #23 Closes #24 Closes #25 Closes #27
This commit is contained in:
@@ -82,6 +82,35 @@ def _project(args: argparse.Namespace) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def _export(args: argparse.Namespace) -> int:
|
||||
from . import bundle
|
||||
from .project import Project, default_path
|
||||
|
||||
source = open_source(args.pdf, SourceType(args.type) if args.type else None)
|
||||
path = default_path(source.path)
|
||||
|
||||
if path.exists():
|
||||
project = Project.load(path)
|
||||
if project.source_changed():
|
||||
print("WARNING: the PDF has changed since these cuts were made")
|
||||
else:
|
||||
detections, heights = [], []
|
||||
for i in range(len(source)):
|
||||
gray = page_raster(source, i)
|
||||
detections.append(detect_page(gray))
|
||||
heights.append(gray.shape[0])
|
||||
project = Project.from_detection(source.path, detections, heights)
|
||||
print("no project file; exporting straight from detection")
|
||||
|
||||
out = Path(args.out) if args.out else source.path.with_suffix(".zip")
|
||||
bundle.write(project, source, out)
|
||||
size = out.stat().st_size
|
||||
slices = len(project.kept_slices())
|
||||
print(f"{out} {slices} slices, {size / 1024:.0f} KB ({size / max(slices, 1) / 1024:.1f} KB/slice)")
|
||||
source.close()
|
||||
return 0
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="noteman-slicer",
|
||||
@@ -113,6 +142,12 @@ def main(argv: list[str] | None = None) -> int:
|
||||
proj.add_argument("--type", choices=[t.value for t in SourceType])
|
||||
proj.set_defaults(func=_project)
|
||||
|
||||
exp = sub.add_parser("export", help="render the song and write a bundle")
|
||||
exp.add_argument("pdf")
|
||||
exp.add_argument("--out", help="output zip (default: alongside the PDF)")
|
||||
exp.add_argument("--type", choices=[t.value for t in SourceType])
|
||||
exp.set_defaults(func=_export)
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
return args.func(args)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user