Some months ago, Ele (and I) decided to take a photoshoot. It was a nice experience, especially because the photographer was great at making a nice atmosphere. After the photoshoot, she sent us a preview of the 200 pictures organized in 4 pictures per page of a 50 pages PDF. We had to choose 20 for the actual album.
I would actually prefer 200 seperate pictures for making easier the selection.
For sure I could jot down a Python script that
- reads the pdf
- splits each page in 4 quadrants
- saves the quadrants as JPG files
If all went smooth, that could take 10-15 minutes, ignoring the possible issues of inverting width and height, selecting the right libraries for reading PDF, don’t make other silly mistakes along the way. Let’s say that from idea to final desired output I would estimate half hour.
Would I actually save enough time to justify writing the script instead of just having to choose the pictures directly from the pdf?
Couple of years ago, being this is a one-shot task, that would be a clear no.
But since we are in the era of LLMs, agents and copilots, I could vibe code a solution and have my neatly separated images in a minute.
My prompt was
Write python CLI app using uv package manager (embedding dependencies at the top of the script) that does the following steps: reads a pdf, split it in 4 symmetric quadrants, saves the quadrants as JPG files.
Asking for embedding dependencies at the top of the script avoids the pain to handle a structured project.
Some seconds later I was running uv run main.py photos.pdf
where main.py is
# /// script
# dependencies = [
# "pymupdf",
# "Pillow"
# ]
# ///
import fitz # PyMuPDF
from PIL import Image
import argparse
import os
from pathlib import Path
def split_page_into_quadrants(page, dpi=300):
"""Render page and split into 4 symmetric quadrants."""
zoom = dpi / 72 # 72 is default PDF DPI
matrix = fitz.Matrix(zoom, zoom)
pix = page.get_pixmap(matrix=matrix)
img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
width, height = img.size
mid_x = width // 2
mid_y = height // 2
quadrants = [
img.crop((0, 0, mid_x, mid_y)), # Top-left
img.crop((mid_x, 0, width, mid_y)), # Top-right
img.crop((0, mid_y, mid_x, height)), # Bottom-left
img.crop((mid_x, mid_y, width, height)), # Bottom-right
]
return quadrants
def process_pdf(input_pdf, output_dir, dpi):
doc = fitz.open(input_pdf)
output_path = Path(output_dir)
output_path.mkdir(parents=True, exist_ok=True)
for page_number, page in enumerate(doc):
quadrants = split_page_into_quadrants(page, dpi=dpi)
for i, quadrant in enumerate(quadrants):
output_file = output_path / f"page_{page_number+1}_quad_{i+1}.jpg"
quadrant.save(output_file, "JPEG", quality=95)
print(f"Processed {len(doc)} pages.")
print(f"Quadrants saved in: {output_path}")
def main():
parser = argparse.ArgumentParser(
description="Split each PDF page into 4 symmetric quadrants and save as JPG."
)
parser.add_argument("input_pdf", help="Path to input PDF file")
parser.add_argument(
"-o", "--output",
default="output_quadrants",
help="Output directory (default: output_quadrants)"
)
parser.add_argument(
"--dpi",
type=int,
default=300,
help="Rendering DPI (default: 300)"
)
args = parser.parse_args()
if not os.path.exists(args.input_pdf):
print("Error: Input PDF does not exist.")
return
process_pdf(args.input_pdf, args.output, args.dpi)
if __name__ == "__main__":
main()
Actually spending seconds to have scripts for one-off tasks, completely changes the domain of what it makes sense to build. No need to be precise in pinning down requirements and specifying defaults. Actually if I implemented it I would not care about the DPI. But since no one is going to reuse this code and I am just interested in the output I am fine with the result.
In the end the time saved was spent for choosing the last picture to keep (that would be dumb to delegate to AI) and writing the first draft of this post.