DEV Community

Mate Technologies
Mate Technologies

Posted on

Convert Videos to PDF with VID2PDFPro in Python

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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}")
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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:

🔗 VID2PDFPro Repository

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)