Learn how to turn video frames into PDF documents using Python with VID2PDFPro, complete with face and license plate anonymization. Perfect for creating reports, archives, or anonymized content.
Check out the full project on GitHub
.
Step 1: Install Dependencies
VID2PDFPro uses several Python libraries. Install them using pip:
pip install opencv-python pillow ttkbootstrap tkinterdnd2
opencv-python – For reading and processing video frames.
Pillow – To manipulate images and generate PDFs.
ttkbootstrap – For modern themed GUIs.
tkinterdnd2 – To enable drag-and-drop in Tkinter.
Step 2: Import Required Modules
We need several built-in and third-party modules:
import sys
import os
import threading
import queue
import time
from pathlib import Path
import cv2
from PIL import Image
import tkinter as tk
from tkinter import filedialog, messagebox
from tkinterdnd2 import TkinterDnD, DND_FILES
import ttkbootstrap as tb
from ttkbootstrap.widgets.scrolled import ScrolledText
threading & queue – For background processing and safe UI updates.
cv2 (OpenCV) – For reading video frames and anonymization.
Pillow (PIL) – To convert frames to images and PDFs.
tkinter & ttkbootstrap – For the GUI.
TkinterDnD – Adds drag-and-drop support.
Step 3: Create the Main App Class
We encapsulate the app in a class for organization:
class VID2PDFPro:
APP_NAME = "VID2PDF Pro"
APP_VERSION = "1.0"
PAGE_SIZES = ["Original", "A4", "Letter"]
def __init__(self):
self.root = TkinterDnD.Tk()
tb.Style("superhero")
self.root.title(f"{self.APP_NAME} v{self.APP_VERSION}")
self.root.geometry("1150x650")
self.video_path = tk.StringVar()
self.output_dir = tk.StringVar()
self.frame_interval = tk.IntVar(value=10)
self.pdf_dpi = tk.IntVar(value=200)
self.page_size = tk.StringVar(value="Original")
self.an_faces = tk.BooleanVar(value=True)
self.an_plates = tk.BooleanVar(value=True)
self.stop_event = threading.Event()
self.ui_queue = queue.Queue()
self.progress_var = tk.IntVar(value=0)
self.counter_var = tk.StringVar(value="Processed: 0 / 0")
self.eta_var = tk.StringVar(value="ETA: --:--")
# Load Haar cascades for face and plate detection
self.face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml")
self.plate_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_russian_plate_number.xml")
self._build_ui()
self.process_ui_queue()
Explanation:
TkinterDnD.Tk() – creates a GUI window that supports drag-and-drop.
ttkbootstrap.Style() – applies a theme.
tk.StringVar / tk.IntVar – bind GUI inputs to Python variables.
Haar cascades are pre-trained classifiers for face/license plate detection.
Step 4: Build the GUI
We create input fields, buttons, and a live progress log:
def _build_ui(self):
tb.Label(self.root, text=self.APP_NAME, font=("Segoe UI", 22, "bold")).pack(pady=(10, 2))
tb.Label(self.root, text="Video to PDF Extraction & Anonymization",
font=("Segoe UI", 10, "italic"), foreground="#9ca3af").pack(pady=(0, 10))
# Video input section
src_box = tb.Labelframe(self.root, text="Video Input (Drag & Drop Supported)", padding=10)
src_box.pack(fill="x", padx=10, pady=6)
self.video_entry = tb.Entry(src_box, textvariable=self.video_path)
self.video_entry.pack(side="left", fill="x", expand=True)
self.video_entry.drop_target_register(DND_FILES)
self.video_entry.dnd_bind("<<Drop>>", self.on_drop_video)
tb.Button(src_box, text="Browse", bootstyle="info",
command=lambda: self.video_path.set(
filedialog.askopenfilename(filetypes=[("Video","*.mp4 *.avi *.mov")]))).pack(side="left", padx=5)
Explanation:
Users can drag and drop videos or use a Browse button.
DND_FILES allows drag-and-drop of files directly into the entry box.
Step 5: Advanced Options
VID2PDFPro lets you anonymize faces and license plates:
adv = tb.Labelframe(self.root, text="Advanced", padding=10)
adv.pack(fill="x", padx=10, pady=6)
tb.Checkbutton(adv, text="Anonymize Faces", variable=self.an_faces, bootstyle="success").pack(side="left", padx=10)
tb.Checkbutton(adv, text="Anonymize Plates", variable=self.an_plates, bootstyle="success").pack(side="left", padx=10)
Checkboxes allow users to enable or disable face/plate anonymization.
Step 6: Anonymization Function
We blur faces and pixelate license plates:
def anonymize(self, frame):
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
if self.an_faces.get():
for x, y, w, h in self.face_cascade.detectMultiScale(gray, 1.2, 5):
roi = frame[y:y+h, x:x+w]
frame[y:y+h, x:x+w] = cv2.GaussianBlur(roi, (51,51), 0)
if self.an_plates.get():
for x, y, w, h in self.plate_cascade.detectMultiScale(gray, 1.1, 4):
roi = frame[y:y+h, x:x+w]
roi = cv2.resize(roi, (16,16))
roi = cv2.resize(roi, (w,h), interpolation=cv2.INTER_NEAREST)
frame[y:y+h, x:x+w] = roi
return frame
Explanation:
Faces → Gaussian blur
License plates → Pixelated for privacy
Controlled by checkboxes in the GUI.
Step 7: Extract Frames and Save as PDF
This function converts frames to PDF:
def extract_to_pdf(self):
video_file = self.video_path.get()
out_dir = self.output_dir.get()
if not video_file or not out_dir:
messagebox.showerror("Missing", "Select video and output folder")
return
self.stop_event.clear()
cap = cv2.VideoCapture(video_file)
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
pdf_path = os.path.join(out_dir,"output.pdf")
first_img = None
frames = []
idx = 0
processed = 0
while cap.isOpened():
if self.stop_event.is_set(): break
ret, frame = cap.read()
if not ret: break
if idx % self.frame_interval.get() == 0:
frame = self.anonymize(frame)
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
img = Image.fromarray(rgb)
if first_img is None: first_img = img
else: frames.append(img)
processed += 1
idx += 1
cap.release()
if first_img:
first_img.save(pdf_path, save_all=True, append_images=frames,
resolution=self.pdf_dpi.get(), optimize=self.pdf_compress.get())
messagebox.showinfo("Done", f"PDF created:\n{pdf_path}")
Key Points:
frame_interval controls which frames to extract.
Image.fromarray converts frames to PIL images.
save_all=True combines multiple images into a single PDF.
Step 8: Run the App
Finally, launch the GUI:
if __name__ == "__main__":
VID2PDFPro().run()
Click Start Extraction in the app, and it will create a PDF from your video frames.
Step 9: Learn More & Contribute
The full project with updates and advanced features is on GitHub:
You can explore:
Live progress updates
PDF page size options
Thread-safe background extraction
This tutorial is beginner-friendly because it splits the logic into small steps and explains each section clearly.

Top comments (0)