Fundamentals 13 min read

How to Build a Python Screen Recorder with OpenCV, Pillow, and Keyboard Hotkeys

This article walks through creating a Python‑based screen recorder on Windows 10 using Pillow for screenshots, OpenCV for video encoding, NumPy for frame processing, and pynput to control recording via hotkeys, including steps to install dependencies, calculate optimal FPS, and save MP4 files.

Python Crawling & Data Mining
Python Crawling & Data Mining
Python Crawling & Data Mining
How to Build a Python Screen Recorder with OpenCV, Pillow, and Keyboard Hotkeys

Application Platform

Windows 10

Python 3.7

Screen Recording Overview

Screen recording is achieved by repeatedly capturing screen snapshots with PIL.ImageGrab, converting them to NumPy arrays, and writing them to a video file using OpenCV. Install the required packages first:

pip install Pillow
pip install opencv-python
pip install numpy

Main Recording Code

import numpy as np
from PIL import ImageGrab
import cv2

im = ImageGrab.grab()
width, high = im.size  # get screen dimensions
fourcc = cv2.VideoWriter_fourcc(*'I420')  # video codec
fps = 15  # frame rate
video = cv2.VideoWriter('test.avi', fourcc, fps, (width, high))
while True:  # start recording
    im = ImageGrab.grab()
    im_cv = cv2.cvtColor(np.array(im), cv2.COLOR_BGR2RGB)
    video.write(im_cv)
    if xx:  # break condition placeholder
        break
video.release()  # finalize video

To make control more convenient, the recording logic is wrapped in a thread class that listens for keyboard shortcuts.

Calculating Optimal FPS

def video_best_fps(self, path):
    """Get the optimal FPS for the recorded video on this computer"""
    video = cv2.VideoCapture(path)
    fps = video.get(cv2.CAP_PROP_FPS)
    count = video.get(cv2.CAP_PROP_FRAME_COUNT)
    self.best_fps = int(fps * ((int(count) / fps) / self.spend_time))
    video.release()

The method measures the actual frame rate and adjusts the target FPS accordingly. If the chosen FPS is higher than optimal, additional frames can be interpolated using NumPy and Numba for faster computation:

from numba import jit

@jit(nopython=True)
def average_n(x, y):
    """Calculate a simple average of two frames"""
    return ((x + y + y) // 3).astype(x.dtype)

Keyboard Hotkey Listener

from pynput import keyboard

def hotkey(self):
    """Listen for hotkeys to stop or abort recording"""
    with keyboard.Listener(on_press=self.on_press) as listener:
        listener.join()

def on_press(self, key):
    try:
        if key.char == 't':  # stop and save
            self.flag = True
        elif key.char == 'k':  # abort and delete
            self.flag = True
            self.kill = True
    except Exception as e:
        print(e)

Pressing t ends recording and saves the video; pressing k aborts recording and removes the file.

Saving MP4 Videos

The video codec for MP4 should be ('a', 'v', 'c', '1'). Download the appropriate OpenH264 DLL from Cisco, place it in the project folder, and run the code. Successful execution prints:

OpenH264 Video Codec provided by Cisco Systems, Inc.
OpenH264 codec installation
OpenH264 codec installation

Full Source Code

import time
from PIL import ImageGrab
import cv2
from pathlib import Path
import numpy as np
from numba import jit
from pynput import keyboard
from threading import Thread

@jit(nopython=True)
def average_n(x, y):
    """Numpy compute average frame"""
    return ((x + y + y) // 3).astype(x.dtype)

class ScreenshotVideo(Thread):
    def __init__(self, width, high, path='', fps=15):
        """Initialize parameters"""
        super().__init__()
        self.save_file = path
        self.best_fps = fps
        self.fps = fps
        self.width = width
        self.high = high
        self.spend_time = 1
        self.flag = False
        self.kill = False
        self.video = None

    def __call__(self, path):
        """Reload video path for reuse"""
        self.save_file = Path(path)
        self.video = self.init_videowriter(self.save_file)

    @staticmethod
    def screenshot():
        """Capture screen and return as NumPy array"""
        return np.array(ImageGrab.grab())

    @staticmethod
    def get_fourcc(name):
        """Video codec mapping"""
        fourcc_maps = {'.avi': 'I420', '.m4v': 'mp4v', '.mp4': 'avc1', '.ogv': 'THEO', '.flv': 'FLV1'}
        return fourcc_maps.get(name)

    def init_videowriter(self, path):
        """Create VideoWriter with proper codec"""
        if not path:
            raise Exception('Video path not set')
        path = Path(path) if isinstance(path, str) else path
        fourcc = cv2.VideoWriter_fourcc(*self.get_fourcc(path.suffix))
        return cv2.VideoWriter(path.as_posix(), fourcc, self.fps, (self.width, self.high))

    def video_record_doing(self, img):
        """Write a frame to video"""
        im_cv = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        self.video.write(im_cv)

    def video_record_end(self):
        """Finalize recording and optionally delete file"""
        self.video.release()
        cv2.destroyAllWindows()
        if self.save_file and self.kill:
            Path(self.save_file).unlink()

    def video_best_fps(self, path):
        """Calculate optimal FPS for this computer"""
        video = cv2.VideoCapture(path)
        fps = video.get(cv2.CAP_PROP_FPS)
        count = video.get(cv2.CAP_PROP_FRAME_COUNT)
        self.best_fps = int(fps * ((int(count) / fps) / self.spend_time))
        video.release()

    def pre_video_record(self):
        """Pre‑record to estimate best FPS"""
        self.video = self.init_videowriter('test.mp4')
        start_time = time.time()
        for _ in range(10):
            im = self.screenshot()
            self.video_record_doing(im)
        self.spend_time = round(time.time() - start_time, 4)
        self.video_record_end()
        time.sleep(2)
        self.video_best_fps('test.mp4')
        Path('test.mp4').unlink()

    def insert_frame_array(self, frame_list):
        """Enhance frame list using NumPy"""
        fps_n = round(self.fps / self.best_fps)
        if fps_n <= 0:
            return frame_list
        times = int(np.log2(fps_n))
        for _ in range(times):
            frame_list2 = map(average_n, [frame_list[0]] + frame_list[:-1], frame_list)
            frame_list = [[x, y] for x, y in zip(frame_list2, frame_list)]
            frame_list = [j for i in frame_list for j in i]
        return frame_list

    def frame2video_run(self):
        """Capture frames continuously and write to video"""
        self.video = self.init_videowriter(self.save_file)
        start_time = time.time()
        frame_list = []
        while True:
            frame_list.append(self.screenshot())
            if self.flag:
                break
        self.spend_time = round(time.time() - start_time, 4)
        if not self.kill:
            frame_list = self.insert_frame_array(frame_list)
            for im in frame_list:
                self.video_record_doing(im)
        self.video_record_end()

    def hotkey(self):
        """Hotkey listener"""
        with keyboard.Listener(on_press=self.on_press) as listener:
            listener.join()

    def on_press(self, key):
        try:
            if key.char == 't':
                self.flag = True
            elif key.char == 'k':
                self.flag = True
                self.kill = True
        except Exception as e:
            print(e)

    def run(self):
        Thread(target=self.hotkey, daemon=True).start()
        self.frame2video_run()

# Example usage
screen = ImageGrab.grab()
width, high = screen.size
video = ScreenshotVideo(width, high, fps=60)
video.pre_video_record()
video('test1.mp4')
video.run()

Conclusion

The script demonstrates how to capture the screen, process frames with NumPy, encode video with OpenCV, and control recording via keyboard hotkeys, providing a solid foundation for further feature development.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

PythonVideo processingOpenCVScreen RecordingpillowKeyboard Hotkeys
Python Crawling & Data Mining
Written by

Python Crawling & Data Mining

Life's short, I code in Python. This channel shares Python web crawling, data mining, analysis, processing, visualization, automated testing, DevOps, big data, AI, cloud computing, machine learning tools, resources, news, technical articles, tutorial videos and learning materials. Join us!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.