scripts/pdf_page_image.py
scripts/pdf_page_image.pyBrowse 21 files
885 tokens
3,589 bytes
Token encoding: o200k_base
Snapshot 24fd22b
← Back to SKILL.md
1#!/usr/bin/env python32"""Export PDF pages as PNG images at a chosen DPI.3 4Rasterizer fallback chain: pypdfium2 (pip) -> pdftoppm (poppler-utils).5When neither is available, exits 0 with {"rendered": false, "missing": [...]}6so callers can branch instead of crashing.7 8Typical uses: visual verification with a vision model, and exporting9image-only (scanned) pages for hand-off to the references/ocr-extraction.md in this skill.10"""11from __future__ import annotations12 13import argparse14import json15import sys16from pathlib import Path17 18 19def parse_pages(spec: str, page_count: int) -> list[int]:20 """'1-3,5,9-' (1-based, inclusive) -> sorted page list."""21 pages: set[int] = set()22 for part in spec.split(","):23 part = part.strip()24 if not part:25 continue26 if "-" in part:27 start_s, _, end_s = part.partition("-")28 start = int(start_s) if start_s else 129 end = int(end_s) if end_s else page_count30 pages.update(range(start, end + 1))31 else:32 pages.add(int(part))33 bad = [p for p in pages if not 1 <= p <= page_count]34 if bad:35 raise ValueError(f"pages out of range 1-{page_count}: {sorted(bad)}")36 return sorted(pages)37 38 39def main() -> int:40 for stream in (sys.stdout, sys.stderr):41 try:42 stream.reconfigure(encoding="utf-8")43 except Exception:44 pass45 parser = argparse.ArgumentParser(description="Export PDF pages as PNG images.")46 parser.add_argument("pdf", help="Input PDF path")47 parser.add_argument("--pages", default="1-", help="1-based ranges, e.g. '1-3,5' (default: all)")48 parser.add_argument("--dpi", type=int, default=150, help="Render DPI (default 150)")49 parser.add_argument("--out-dir", required=True, help="Directory for PNG files")50 parser.add_argument("--prefix", default="page", help="Output filename prefix (default 'page')")51 parser.add_argument("--password", help="Password for encrypted PDFs")52 args = parser.parse_args()53 54 sys.path.insert(0, str(Path(__file__).resolve().parent))55 import _raster56 57 if not _raster.available_backends():58 json.dump({"rendered": False, "missing": _raster.missing_hints()}, sys.stdout)59 print()60 return 061 62 try:63 from pypdf import PdfReader64 except ImportError:65 print("Missing dependency: install with 'python3 -m pip install pypdf'", file=sys.stderr)66 return 267 reader = PdfReader(args.pdf)68 if reader.is_encrypted:69 if args.password is None or not reader.decrypt(args.password):70 print("File is encrypted; pass --password.", file=sys.stderr)71 return 372 page_count = len(reader.pages)73 74 try:75 pages = parse_pages(args.pages, page_count)76 except ValueError as exc:77 print(f"Error: {exc}", file=sys.stderr)78 return 479 80 out_dir = Path(args.out_dir)81 out_dir.mkdir(parents=True, exist_ok=True)82 files = []83 for pageno in pages:84 img = _raster.rasterize_page(args.pdf, pageno, dpi=args.dpi, password=args.password)85 if img is None:86 json.dump({"rendered": False, "missing": _raster.missing_hints()}, sys.stdout)87 print()88 return 089 out_path = out_dir / f"{args.prefix}{pageno:03d}.png"90 img.save(out_path)91 files.append(str(out_path))92 json.dump({"rendered": True, "dpi": args.dpi, "page_count": page_count,93 "files": files}, sys.stdout, ensure_ascii=False, indent=2)94 print()95 return 096 97 98if __name__ == "__main__":99 sys.exit(main())100