pdf

PDF files: create, read, merge, fill, OCR, edit text.

  • pdf
  • documents
  • forms
  • ocr
  • text-extraction
  • reportlab
  • pypdf
  • pdfplumber
  • pymupdf
  • marker

Declared platforms: linux · macos · windows

Install
npx skills add 'https://github.com/NousResearch/hermes-agent/tree/main/skills/productivity/pdf'
Download bundle ↓
main · 24fd22bScanned 2026-09-15

Contributors

GitHub-linked commit authors for this SKILL.md at the saved revision. Co-authors and history before file renames are not included.

File history ↗
View on GitHub
← Back to SKILL.md
#!/usr/bin/env python3"""Extract page ranges from a PDF, optionally rotating and/or compressing pages."""from __future__ import annotations import argparseimport jsonimport sys  def parse_pages(spec: str, page_count: int) -> list[int]:    """Parse a 1-based page spec like '1-3,5,9-' into 0-based indices."""    indices: list[int] = []    for part in spec.split(","):        part = part.strip()        if not part:            continue        if "-" in part:            start_s, _, end_s = part.partition("-")            start = int(start_s) if start_s else 1            end = int(end_s) if end_s else page_count        else:            start = end = int(part)        if start < 1 or end > page_count or start > end:            raise ValueError(f"Page range {part!r} out of bounds (1-{page_count})")        indices.extend(range(start - 1, end))    return indices  def main() -> int:    for stream in (sys.stdout, sys.stderr):        try:            stream.reconfigure(encoding="utf-8")        except Exception:            pass    parser = argparse.ArgumentParser(        description="Split/extract pages from a PDF (pypdf). Pages are 1-based: '1-3,5,9-'.")    parser.add_argument("pdf", help="Input PDF path")    parser.add_argument("--pages", required=True, help="1-based page spec, e.g. '1-3,5,9-'")    parser.add_argument("-o", "--output", required=True, help="Output PDF path")    parser.add_argument("--rotate", type=int, default=0,                        help="Rotate extracted pages clockwise (multiple of 90)")    parser.add_argument("--compress", action="store_true",                        help="Deflate content streams (modest savings; does not recompress images)")    parser.add_argument("--password", help="Password if the input is encrypted")    args = parser.parse_args()     if args.rotate % 90 != 0:        print("Error: --rotate must be a multiple of 90", file=sys.stderr)        return 2     try:        from pypdf import PdfReader, PdfWriter    except ImportError:        print("Missing dependency: install with 'python3 -m pip install pypdf'", file=sys.stderr)        return 2     reader = PdfReader(args.pdf)    if reader.is_encrypted:        if args.password is None or not reader.decrypt(args.password):            print("Error: input is encrypted; pass --password", file=sys.stderr)            return 3    try:        indices = parse_pages(args.pages, len(reader.pages))    except ValueError as exc:        print(f"Error: {exc}", file=sys.stderr)        return 2     writer = PdfWriter()    for idx in indices:        page = reader.pages[idx]        if args.rotate:            page.rotate(args.rotate)        writer.add_page(page)    if args.compress:        for page in writer.pages:            page.compress_content_streams()    with open(args.output, "wb") as fh:        writer.write(fh)    print(json.dumps({"output": args.output, "page_count": len(indices), "rotated": args.rotate}))    return 0  if __name__ == "__main__":    sys.exit(main()) 
Referenced from SKILL.md