Quicket Bulk Ticket PDF Splitter

I had a scenario where I needed PDF from Quicket that had 20 tickets.

Problem: I needed each ticket as a separate JPG so I could send to those receiving them. Manual way would be to screenshot each ticket.

Solution: This python script split it up into JPG tickets but also names the tickets based on the ticket numbers in PDF.

import fitz  # PyMuPDF
import re
import os

# ───────────── CONFIGURATION ─────────────
pdf_path = r'D:/Event Tickets/AllTickets.pdf'  # path to your PDF
output_dir = r'D:/Event Tickets/'  # folder to save individual PNGs
split_ratio = 0.43  # fraction down the page to split (e.g. 0.43 = 43%)
dpi_scale = 2  # image resolution multiplier
bottom_margin = 10  # pixels to add below content when trimming
# ──────────────────────────────────────────

os.makedirs(output_dir, exist_ok=True)
doc = fitz.open(pdf_path)

for page_index, page in enumerate(doc, start=1):
    w, h = page.rect.width, page.rect.height
    y_split = h * split_ratio

    # Clip rect for top ticket
    top_rect = fitz.Rect(0, 0, w, y_split)

    # Collect spans & ticket‐number centers
    ticket_positions = {}
    bottom_span_ys = []  # for trimming
    for block in page.get_text("dict")["blocks"]:
        for line in block.get("lines", []):
            for span in line.get("spans", []):
                m = re.search(r"(QTK\d+)", span["text"])
                y0, y1 = span["bbox"][1], span["bbox"][3]
                center_y = (y0 + y1) / 2
                # record ticket numbers
                if m:
                    num = m.group(1)
                    if num not in ticket_positions:
                        ticket_positions[num] = center_y
                # record spans that lie in bottom half region
                if center_y > y_split:
                    bottom_span_ys.append(y1)

    # Expect exactly two tickets per page
    if len(ticket_positions) != 2:
        print(f"⚠️ Page {page_index}: found {len(ticket_positions)} tickets!")
        continue

    # Decide how far down to crop bottom ticket
    if bottom_span_ys:
        max_content_y = max(bottom_span_ys)
        bottom_end = min(max_content_y + bottom_margin, h)
    else:
        bottom_end = h  # fallback to full height

    # Clip rect for bottom ticket
    bottom_rect = fitz.Rect(0, y_split, w, bottom_end)

    # Sort tickets by vertical center to assign top/bottom
    sorted_tickets = sorted(ticket_positions.items(), key=lambda kv: kv[1])
    top_num, bottom_num = sorted_tickets[0][0], sorted_tickets[1][0]

    # Render & save top ticket
    pix_top = page.get_pixmap(matrix=fitz.Matrix(dpi_scale, dpi_scale), clip=top_rect)
    top_path = os.path.join(output_dir, f"{top_num}.png")
    pix_top.save(top_path)

    # Render & save bottom ticket (trimmed)
    pix_bot = page.get_pixmap(matrix=fitz.Matrix(dpi_scale, dpi_scale), clip=bottom_rect)
    bot_path = os.path.join(output_dir, f"{bottom_num}.png")
    pix_bot.save(bot_path)

doc.close()
print(f"Done! Saved images in: {output_dir}/")

Ready to simplify your digital operations?