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.
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 numpyMain 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 videoTo 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.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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
