Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Use OpenCV when you need a straightforward Python loop: open the file with cv2.VideoCapture, call read() until its success flag is false, and write each decoded frame with cv2.imwrite(). The loop below extracts every frame without relying only on the file’s reported frame count.
import cv2
from pathlib import Path
video_path = "input.mp4"
out_dir = Path("frames")
out_dir.mkdir(parents=True, exist_ok=True)
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
raise RuntimeError(f"Could not open {video_path}")
index = 0
try:
while True:
ok, frame = cap.read()
if not ok:
break
if not cv2.imwrite(str(out_dir / f"frame_{index:06d}.jpg"), frame):
raise IOError(f"Could not write frame {index}")
index += 1
finally:
cap.release()
print(f"Wrote {index} frames to {out_dir}")
Install OpenCV with python -m pip install opencv-python. OpenCV documents read() as acquisition plus decoding, with a false flag when no frame is grabbed; its VideoCapture reference also covers opening and releasing captures.
Install the library and verify the input
Create an isolated environment if this is a project rather than a one-off script:
python -m venv .venv
# macOS/Linux
source .venv/bin/activate
# Windows PowerShell: .venvScriptsActivate.ps1
python -m pip install --upgrade pip
python -m pip install opencv-python
Use a real path, not a directory or a URL that OpenCV cannot open through your installed backend. Always test cap.isOpened() before entering the loop. A failed open commonly means the path is wrong, the file is inaccessible, or the installed OpenCV build lacks a usable backend for that media.
#1 Best Overall
Extract every frame with OpenCV
The complete example in the introduction writes sequential JPEG files named frame_000000.jpg, frame_000001.jpg, and so on. The six-digit padding keeps directory listings in numeric order. The counter records frames that were actually decoded, not merely the container’s advertised count.
Why the success flag controls the loop
read() returns a pair: a Boolean indicating whether a frame was obtained and the decoded image. Stop on False. Metadata properties such as frame count can be useful for progress displays, but they are not a safe replacement for checking the return value at end of file. OpenCV’s API and video-I/O property documentation describe these position and capture properties, while also noting that behavior depends on the selected backend: VideoCapture and video-I/O flags.
Choose an output format
Change the suffix passed to imwrite to select a supported encoder, for example .png for lossless output or .webp where your build provides that encoder. JPEG is usually much smaller, but it is lossy. Check the Boolean result from imwrite if failed writes must be detected immediately.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Clear out junk files and repair common Windows errors3Scan for outdated or missing drivers - takes under a minuteSave only selected frames
Every Nth decoded frame
To sample a video while reading it sequentially, increment the counter for every decoded frame and write only when the counter meets your interval. This avoids retaining all images in memory.
import cv2
from pathlib import Path
video_path = "input.mp4"
out_dir = Path("sampled")
out_dir.mkdir(exist_ok=True)
step = 10 # save frames 0, 10, 20, ...
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
raise RuntimeError(f"Could not open {video_path}")
index = 0
saved = 0
try:
while True:
ok, frame = cap.read()
if not ok:
break
if index % step == 0:
destination = out_dir / f"frame_{index:06d}.jpg"
if not cv2.imwrite(str(destination), frame):
raise IOError(f"Could not write {destination}")
saved += 1
index += 1
finally:
cap.release()
print(f"Decoded {index} frames and saved {saved}")
This is the right pattern for requests such as “every 10 frames” or for processing a live-style stream while intentionally skipping intermediate images. It still decodes each intervening frame, because the loop must advance through the stream.
Rank #2
Sample by elapsed time
If you need one image approximately every few seconds, use a time target while iterating rather than assuming that a nominal frame rate is exact:
import cv2
from pathlib import Path
cap = cv2.VideoCapture("input.mp4")
if not cap.isOpened():
raise RuntimeError("Could not open input.mp4")
out_dir = Path("time_samples")
out_dir.mkdir(exist_ok=True)
interval_seconds = 2.0
next_capture = 0.0
index = 0
saved = 0
try:
while True:
ok, frame = cap.read()
if not ok:
break
position_ms = cap.get(cv2.CAP_PROP_POS_MSEC)
position_seconds = position_ms / 1000.0
if position_seconds >= next_capture:
cv2.imwrite(str(out_dir / f"t_{position_seconds:010.3f}.jpg"), frame)
saved += 1
next_capture += interval_seconds
index += 1
finally:
cap.release()
print(f"Decoded {index}; saved {saved} time samples")
Position properties are backend-dependent. Treat the resulting timestamp as an estimate for sampling, not proof that every format supports frame-perfect seeking.
Free tools Windows power users keep installed
One-click scans. No signup required.
Capture one frame at a requested timestamp
OpenCV exposes frame and time-position properties, but exact seeking can vary with the media, codec and backend. If a precise FFmpeg-oriented operation is central to your workflow, ffmpegio documents both single-image timestamp capture and multi-frame reads.
python -m pip install ffmpegio
import ffmpegio
# Capture the image nearest 4 minutes, 25.3 seconds.
image = ffmpegio.image.read("input.mp4", ss="4:25.3")
image.tofile("at-time.raw") # choose an appropriate image conversion for your pipeline
The ffmpegio documentation shows the timestamp form ffmpegio.image.read(..., ss='4:25.3'). For a sequence, its video reader accepts a start position and frame count and returns a frame rate plus a NumPy array; see the ffmpegio 0.11.0 documentation for the exact return values and supported arguments. In production, use its documented image-writing helpers or convert the returned array with Pillow/OpenCV rather than writing raw bytes unless your consumer expects that format.
Use PyAV when you need FFmpeg-level control
PyAV exposes containers, streams, packets and decoded frames directly. Its basic workflow is to open a container, select a video stream, decode frames and convert each frame to a PIL image or NumPy array.
python -m pip install av pillow numpy
import av
from pathlib import Path
out_dir = Path("pyav_frames")
out_dir.mkdir(exist_ok=True)
with av.open("input.mp4") as container:
stream = container.streams.video[0]
for index, frame in enumerate(container.decode(stream)):
image = frame.to_image() # PIL Image; Pillow is required
image.save(out_dir / f"frame_{index:06d}.png")
VideoFrame.to_image() creates a PIL image and to_ndarray() creates a NumPy array; those conversions require their respective dependencies. The PyAV 18.1.0 documentation contains the decoding example and API details. Choose PyAV when packet, stream or codec control matters more than the smallest possible loop.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Other useful Python interfaces
| Library | Best fit | Important constraint |
|---|---|---|
| OpenCV | Sequential decode, image processing and saving in one familiar API. | Seeking and available codecs depend on the backend and build; see the video-I/O flags. |
| PyAV | Direct FFmpeg container, stream, packet and frame access; PIL or NumPy conversion. | Install Pillow or NumPy for those conversions. |
| imageio-ffmpeg | Generator-style frame reads through an FFmpeg subprocess. | read_frames() accepts filenames rather than file-like objects, and frames move through pipes; details are in the project documentation. |
| ffmpegio | Timestamp image capture and requested multi-frame NumPy reads. | Requires an FFmpeg-oriented setup and follows the arguments documented at ffmpegio. |
| ImageIO | A higher-level iteration interface, including its PyAV video plugin. | Plugin and backend availability follow the installed ImageIO setup; see the video examples. |
Decide by access pattern (sequential versus timestamp), desired representation (OpenCV array, PIL image or NumPy array), how much FFmpeg control you need, and which backend you can install and reproduce.
Performance, memory and reliability
- Stream instead of accumulating: write or process each frame inside the decode loop. Holding thousands of full-resolution arrays can exhaust memory.
- Reduce output volume: save every Nth frame or sample by time when you do not need every image. The decoder still has to advance through skipped frames in a sequential loop.
- Separate decode from expensive processing: if resizing, OCR or neural inference is slow, queue bounded batches rather than allowing unlimited frames to accumulate.
- Keep the capture lifecycle explicit: check
isOpened(), usetry/finally, and callrelease()even when writing or processing raises an exception. - Do not promise universal seek precision: container indexes, codecs and backends differ. Validate timestamps on representative files before building a frame-accurate editor around random access.
- Plan disk space: lossless PNG output and full-resolution extraction can be much larger than the source video’s compressed stream. Use a temporary directory and monitor free space for long videos.
Troubleshooting common failures
“Could not open” or isOpened() is false
Print the absolute path and verify permissions. Try a file known to play locally. If only a particular codec fails, compare another OpenCV build or use PyAV/FFmpeg-based tooling; the available documentation does not establish one codec matrix that works on every operating system.
The loop stops before the expected number of frames
The reported frame count may be approximate or unavailable for the selected backend. The authoritative event for this loop is read() returning false. If decoding fails mid-file, test the same input with another backend and inspect the media with your normal FFmpeg utilities.
Timestamp selection is slightly early or late
Random access is not guaranteed to be frame-perfect across formats and backends. Decode sequentially and select based on observed positions when you need deterministic sampling, or use ffmpegio’s documented timestamp operation and verify its result on your media.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Images are missing or the output directory is empty
Create the directory before the loop, use a writable path, and check the Boolean returned by cv2.imwrite. A successful decode does not guarantee a successful write.
Memory usage keeps growing
Do not append every frame to a Python list. Process and release each image, save only the required samples, and keep any work queue bounded.
Colors look wrong after conversion
Keep track of the representation expected by the next library. OpenCV’s processing pipeline and PIL/NumPy conversions have different conventions; convert once at the boundary and avoid unnecessary round trips.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
ScreenshotNeo is for capturing webpages, not decoding a local video file. If the “video” you need is actually a page or an online visual you want rendered, its API can return a screenshot with one GET request. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. It also provides an MCP server with take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →See the ScreenshotNeo API documentation for parameters and response handling.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
Every plan includes the full feature set. The free tier includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account if webpage capture is the part of your workflow you want to automate.
Best Value
Which approach should you choose?
- Choose OpenCV for the default sequential “decode, process, save” script.
- Choose PyAV when you need FFmpeg objects or direct PIL/NumPy frame conversion.
- Choose ffmpegio for documented timestamp and multi-frame array operations.
- Choose imageio-ffmpeg or ImageIO when a generator or higher-level iteration API fits your application.
Whichever library you select, validate the actual media and backend you will deploy, stop on a failed decode, release resources, and avoid assuming that metadata or seeking behaves identically for every file.
Frequently Asked Questions
Can I extract frames from an MP4 every 10 frames?
Yes. Decode sequentially with OpenCV, increment a counter for every successful read(), and save when index % 10 == 0.
How do I get a frame at an exact time?
Use a documented timestamp operation such as ffmpegio.image.read(..., ss="4:25.3"), then verify the result because seek precision depends on the media and backend.
Should I use OpenCV or PyAV?
OpenCV is the simpler default for sequential processing. PyAV is a better fit when direct FFmpeg stream/container access or PIL/NumPy frame conversion is central.
Why does my script decode fewer frames than the metadata count?
Metadata counts are backend-dependent. Treat the read() success flag as the loop’s end-of-file signal and investigate any mid-file decode failure with another backend.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

