Python OpenCV: Write and Process Video
Learn how to read, process, and write video files with Python OpenCV using VideoCapture and VideoWriter, including codec selection and common pitfalls.
To write and process video with Python OpenCV, you build a loop that reads frames from a VideoCapture object, applies a transformation, and passes the result to a VideoWriter. This article covers the full pipeline: setting up the loop correctly, choosing a codec, and avoiding the timing and format mistakes that produce broken or empty output files.
The Core Loop: Reading and Writing Frames
The foundation of any video processing script is the frame loop. VideoCapture opens a video file and exposes frames one at a time, while VideoWriter receives processed frames and encodes them into an output container.
import cv2 capture = cv2.VideoCapture("input.mp4") fourcc = cv2.VideoWriter_fourcc(*"mp4v") writer = cv2.VideoWriter("output.mp4", fourcc, 30.0, (1920, 1080)) while True: ret, frame = capture.read() if not ret: break processed = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) processed_bgr = cv2.cvtColor(processed, cv2.COLOR_GRAY2BGR) writer.write(processed_bgr) capture.release() writer.release()
The read() method returns a tuple: a boolean that indicates whether a frame was successfully decoded, and the frame itself. When ret is False, the video has ended or a decoding error occurred, and the loop must break. The release() calls are important: writer.release() flushes buffered frames to disk, and capture.release() frees the input file handle.
By default, VideoWriter expects a three-channel BGR frame. If your processing step produces a grayscale image, convert it back to BGR before calling write(); otherwise the output file can be corrupt or the writer may raise an error.
Choosing a Codec with FourCC
The codec is specified by a FourCC code, a four-character identifier that tells the writer which encoder to use. VideoWriter relies on the codecs available through the video backend compiled into OpenCV, typically FFmpeg. This means the set of available FourCC codes depends on your OpenCV build and your operating system.
Common choices:
| FourCC | Container | Typical use |
|---|---|---|
mp4v | MP4 | Broad compatibility, MPEG-4 Part 2 |
avc1 | MP4 | H.264, smaller files, wider player support |
XVID | AVI | Legacy AVI workflows |
MJPG | AVI/MP4 | Motion JPEG, fast but large |
The FourCC is created with cv2.VideoWriter_fourcc(*"mp4v"). The unpacking operator splits the string into four characters, which is the expected argument form.
If you request a codec that is not available, VideoWriter.isOpened() may return False, and any frames written afterward are silently discarded. Always check this flag after constructing the writer:
writer = cv2.VideoWriter("output.mp4", fourcc, 30.0, (1920, 1080)) if not writer.isOpened(): raise RuntimeError("Could not open the video writer with the requested codec")
Matching Frame Rate and Frame Size
VideoWriter does not resample frames. It writes exactly the frames you pass, at the frame rate you declare in the constructor. If the actual processing rate differs from the declared rate, the output video will play at the wrong speed.
The frame size passed to VideoWriter must match the dimensions of every frame you write. A common mistake is reading the input size from capture.get(cv2.CAP_PROP_FRAME_WIDTH) but then resizing frames before writing them, which produces a runtime error or a corrupt file.
width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT)) fps = capture.get(cv2.CAP_PROP_FPS) writer = cv2.VideoWriter("output.mp4", fourcc, fps, (width, height))
For a typical constant-frame-rate source, using the input's own fps and dimensions guarantees the output matches the source timing, which is what most processing pipelines want.
A Complete Processing Pipeline Example
The following example reads a video, applies a blur and a color shift, and writes the result. It also checks whether the input and output files can be opened.
import cv2 def process_video(input_path, output_path): capture = cv2.VideoCapture(input_path) if not capture.isOpened(): raise RuntimeError(f"Cannot open {input_path}") width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT)) fps = capture.get(cv2.CAP_PROP_FPS) fourcc = cv2.VideoWriter_fourcc(*"mp4v") writer = cv2.VideoWriter(output_path, fourcc, fps, (width, height)) if not writer.isOpened(): capture.release() raise RuntimeError(f"Cannot write {output_path}") frame_count = 0 while True: ret, frame = capture.read() if not ret: break blurred = cv2.GaussianBlur(frame, (15, 15), 0) shifted = cv2.convertScaleAbs(blurred, alpha=1.1, beta=10) writer.write(shifted) frame_count += 1 capture.release() writer.release() return frame_count if __name__ == "__main__": count = process_video("input.mp4", "output.mp4") print(f"Processed {count} frames")
The convertScaleAbs call applies a brightness and contrast adjustment without changing the channel count, so the frame remains valid BGR input for the writer. This pattern — read, transform, write — is the same for most per-frame operations, whether object detection, color grading, or resizing, as long as the final frame size matches the writer's configured size.
Common Failure Modes and Their Causes
The most frequent problems in OpenCV video writing are not logic errors but environment and format mismatches.
Empty output file. The writer was created with a codec the system does not support, or isOpened() was never checked. The loop may have run, but every frame was silently discarded.
Video plays too fast or too slow. The declared frame rate in the VideoWriter constructor does not match the actual frame rate of the source. If you process a 30 FPS source but declare 60 FPS, the output plays at double speed.
Runtime error on write(). The frame dimensions or channel count do not match what the writer expects. This happens after resizing or after converting to grayscale without converting back to BGR.
Corrupt output that plays in some players but not others. The container extension and the codec are mismatched, such as writing an H.264 stream into an AVI container. Some players tolerate this, others do not.
The writer opens but produces a file of zero bytes. This occurs when the loop breaks immediately because read() returns False on the first call, usually because the input path is wrong or the file is not a valid video.
Performance: Where Processing Time Goes
Video processing is I/O-bound and CPU-bound at the same time. The frame loop reads a compressed frame, decodes it, runs your transformation, encodes the result, and writes it to disk. In many pipelines, the transformation is the slowest stage.
For per-frame operations, avoid reallocating large arrays inside the loop when it is easy to reuse buffers. Some OpenCV functions accept a destination buffer with a dst argument; cv2.GaussianBlur, cv2.addWeighted, and cv2.convertScaleAbs are examples. If you preallocate that buffer with the same shape and type as the frame, repeated calls avoid creating a new array each time. This is a micro-optimization, so apply it only after profiling shows allocation overhead matters.
Real-time processing is a different constraint. If your transformation takes longer than one frame interval, the loop falls behind and the output video will have gaps or the process will buffer frames in memory. For offline processing this is irrelevant; for live camera feeds you need to decouple capture and processing, typically with a queue and a separate worker thread.
When to Use VideoWriter Instead of an Image Sequence
For long-running processing jobs, writing individual frames as PNG or JPEG files and assembling them later is sometimes more robust than writing a video directly. A crash mid-run leaves you with all the frames up to that point, whereas a partially written video file is often unusable.
The tradeoff is disk space and assembly time. PNG frames are much larger than a compressed video, and you need a second pass with cv2.VideoWriter or FFmpeg to combine them. For short clips or when you need to inspect intermediate frames, the image sequence is more practical. For anything that will be delivered as a video, write directly with VideoWriter and keep the processing pipeline idempotent so you can rerun it after a failure.