This commit is contained in:
2026-07-22 15:28:35 -04:00
parent 755fa0632c
commit dd42e8fc58
5 changed files with 91 additions and 18 deletions

View File

@ -17,13 +17,17 @@ that maximizes each printed photo's area on a fixed 8.5x11 letter sheet
a strip has few photos and a wide page fills better
(e.g. 1 photo per shoot, or one big photo).
The date is printed only inside the footer. Sheets are saved with 300-DPI
metadata so their physical print size is well-defined (a full letter page)
for CUPS or any other program they are printed from.
The date is printed only inside the footer.
Each sheet is saved twice: a JPEG (300-DPI metadata, handy for viewing or
reprinting from other apps) and a PDF twin whose page geometry is exactly a
300-DPI letter page. `print_sheet` sends the PDF, so the print pipeline
rasterizes exactly what was composed - no image-DPI guessing, no warping.
"""
import subprocess
from datetime import datetime
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
@ -234,24 +238,34 @@ def build_sheet(photo_paths, out_path, footer_cfg=None,
# Stamp 300 DPI into the file: without it the sheet has no defined
# physical size and the print pipeline has to guess (and may stretch).
sheet.save(out_path, quality=95, dpi=(300, 300))
# PDF twin: page size is exactly a 300-DPI letter page, so printing it
# reproduces the composed geometry 1:1 (PIL maps pixels at `resolution`
# DPI onto the PDF page).
sheet.save(Path(out_path).with_suffix(".pdf"), "PDF", resolution=300.0)
return out_path
def print_sheet(path, printer=""):
"""Send the sheet to the printer via lp. Returns (ok, message).
fit-to-page scales uniformly, so the sheet's aspect ratio is preserved.
Passing the sheet's orientation explicitly keeps a landscape sheet from
being rotated or squeezed by driver defaults.
Prefers the sheet's PDF twin (exact page geometry) when it exists, and
falls back to the image itself. fit-to-page scales uniformly, so the
sheet's aspect ratio is preserved. For plain images we also pass the
orientation explicitly so a landscape sheet is not rotated or squeezed
by driver defaults; a PDF carries its orientation in its page size.
"""
with Image.open(path) as im:
sheet_w, sheet_h = im.size
orientation = "landscape" if sheet_w > sheet_h else "portrait"
cmd = ["lp", "-o", "media=letter", "-o", "fit-to-page",
"-o", orientation]
path = Path(path)
job = path.with_suffix(".pdf")
job = job if job.exists() else path
cmd = ["lp", "-o", "media=letter", "-o", "fit-to-page"]
if job.suffix.lower() != ".pdf":
with Image.open(job) as im:
sheet_w, sheet_h = im.size
cmd += ["-o", "landscape" if sheet_w > sheet_h else "portrait"]
if printer:
cmd += ["-d", printer]
cmd.append(str(path))
cmd.append(str(job))
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
except (OSError, subprocess.TimeoutExpired) as e: