Upload forms often cap attachments at 2 MB or 5 MB, and a scanned document blows past that after a few pages. The reason is simple: each page of a scan is a large photo, usually stored at the scanner's full resolution.
TL;DR: Open the PDF with pypdf, downscale every page image, save it as JPEG at a lower quality, and write the file back out.
The script
import sys, os from pypdf import PdfReader, PdfWriter src, out = sys.argv[1], sys.argv[2] MAX_SIDE = 1700 # longest side in pixels after resizing QUALITY = 60 # JPEG quality writer = PdfWriter(clone_from=src) for page in writer.pages: for img in page.images: im = img.image if im.mode not in ("RGB", "L"): im = im.convert("RGB") w, h = im.size if max(w, h) > MAX_SIDE: s = MAX_SIDE / max(w, h) im = im.resize((round(w * s), round(h * s))) img.replace(im, quality=QUALITY) writer.compress_identical_objects(remove_duplicates=True, remove_unreferenced=True) writer.write(out) print(os.path.getsize(src) // 1024, "KB ->", os.path.getsize(out) // 1024, "KB") What it did on a real file
My test file was a three-page scanned contract, 7,994,634 bytes, with each page stored as a 2481 x 3509 image. After the script, the file was 194,222 bytes, still three pages. I cropped the first page image and the body text was clearly readable at 1202 x 1700 pixels. Tested with pypdf 6.20.1 and Pillow 12.3.0 on Windows.
Tuning
- If the result is still too large, lower
MAX_SIDEorQUALITYa step at a time and look at the page again after each change. - Check small print, stamps and signatures, not just the headline.
- A PDF exported from Word or another editor has few images to shrink, so expect much less change there.
- Keep the original. Re-encoding is lossy, and you cannot get the detail back from the small copy.
Written with AI assistance. The steps were tested by hand before publishing.
