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.
Top comments (0)