#!/usr/bin/env python3
"""Photos of book/paper pages -> upright, flattened, pure black-and-white, print-ready.

usage: printready.py INPUT_DIR_OR_FILES... [-o OUT_DIR] [--threshold 170] [--dpi 200] [--no-rotate]

Output: OUT_DIR/page_NN.png (1-bit) and OUT_DIR/all_pages.pdf. Originals are never modified.
Needs: python3 with Pillow + numpy, and tesseract (with the osd data) for auto-rotation.
Deliberately does NOT crop or mask: every attempt to auto-remove the background cost real text.
"""
import argparse, glob, os, subprocess, sys
import numpy as np
from PIL import Image, ImageFilter, ImageOps

EXT = ('.jpg', '.jpeg', '.png', '.heic', '.tif', '.tiff', '.webp')
K = 4  # downscale factor for the (slow) background estimate


def collect(inputs):
    files = []
    for p in inputs:
        if os.path.isdir(p):
            files += sorted(f for f in glob.glob(os.path.join(p, '*')) if f.lower().endswith(EXT))
        else:
            files.append(p)
    return files


def rotation(path):
    """Clockwise degrees tesseract says the image needs (0/90/180/270)."""
    r = subprocess.run(['tesseract', path, '-', '--psm', '0'], capture_output=True, text=True).stdout
    for l in r.splitlines():
        if l.startswith('Rotate'):
            return int(l.split()[1])
    return 0


def process(path, rotate, thr):
    im = ImageOps.exif_transpose(Image.open(path)).convert('L')
    rot = rotation(path) if rotate else 0
    if rot:
        im = im.rotate(-rot, expand=True)  # PIL rotates counter-clockwise
    small = im.resize((im.width // K, im.height // K), Image.BILINEAR)
    # paper brightness map (max filter skips the ink, blur smooths) -> divide out shadows/uneven light
    bg = small.filter(ImageFilter.MaxFilter(9)).filter(ImageFilter.GaussianBlur(6)).resize(im.size, Image.BILINEAR)
    a = np.clip(np.asarray(im, np.float32) / np.maximum(np.asarray(bg, np.float32), 1), 0, 1)
    return Image.fromarray(((a * 255 > thr) * 255).astype(np.uint8)).convert('1'), rot


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument('inputs', nargs='+')
    ap.add_argument('-o', '--out')
    ap.add_argument('--threshold', type=int, default=170, help='0-255, higher = more ink (default 170)')
    ap.add_argument('--dpi', type=int, default=200)
    ap.add_argument('--no-rotate', action='store_true')
    a = ap.parse_args()
    files = collect(a.inputs)
    if not files:
        sys.exit('no images found')
    out = a.out or os.path.join(os.path.dirname(os.path.abspath(files[0])), 'print_ready')
    os.makedirs(out, exist_ok=True)
    pages = []
    for n, f in enumerate(files, 1):
        bw, rot = process(f, not a.no_rotate, a.threshold)
        bw.save(os.path.join(out, f'page_{n:02d}.png'), dpi=(a.dpi, a.dpi))
        pages.append(bw)
        print(f'OK {n:02d} rotated {rot} deg  {os.path.basename(f)}')
    pages[0].save(os.path.join(out, 'all_pages.pdf'), save_all=True, append_images=pages[1:], resolution=a.dpi)
    print(f'-> {out} ({len(pages)} pages + all_pages.pdf)')


if __name__ == '__main__':
    main()
