1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-21 19:33:03 +04:00

Merge pull request #28967 from IbrahimNamdari:add-video-context-manager

Python: add context manager compatibility for VideoCapture and VideoWriter #28967

### Description
This PR implements the context manager protocol (`with` statement support) for `cv2.VideoCapture` and `cv2.VideoWriter` objects in Python.

### Problem
Currently, these classes do not support the context manager protocol, leading to a `TypeError: ... object does not support the context manager protocol` when used with a `with` block. As reported in issue #28956, if an exception occurs before an explicit `.release()` call, the video resource or file remains locked in the system[cite: 298, 309].

### Identification & Motivation
By analyzing the issue and the Python binding structure of OpenCV, I identified that while the core logic resides in C++, the Pythonic behavior can be enhanced by injecting `__enter__` and `__exit__` methods during the module initialization. This ensures that resources are automatically and safely released even if the program execution is interrupted by an error[cite: 298, 325].

### Solution
I have modified `modules/python/package/cv2/__init__.py` to dynamically inject the necessary methods:
1. Defined `_video_enter` to return the object itself (standard context manager behavior).
2. Defined `_video_exit` to trigger the `.release()` method automatically.
3. Injected these methods into `VideoCapture` and `VideoWriter` classes immediately after the `bootstrap()` function to ensure the native C++ classes are correctly loaded into the global namespace.

Fixes #28956
This commit is contained in:
Ebrahim Namdari
2026-05-15 16:11:02 +03:30
committed by GitHub
parent 6018b0cf82
commit b29ea17666
2 changed files with 48 additions and 0 deletions
@@ -141,4 +141,34 @@ bool pyopencv_to(PyObject* obj, Ptr<cv::IStreamReader>& p, const ArgInfo&)
return false;
}
static PyObject* pycvVideoEnter(PyObject* self, PyObject* /*args*/, PyObject* /*kw*/) {
Py_INCREF(self);
return self;
}
static PyObject* pycvVideoCaptureExit(PyObject* self, PyObject* /*args*/, PyObject* /*kw*/) {
Ptr<cv::VideoCapture>* obj_getp = nullptr;
if (pyopencv_VideoCapture_getp(self, obj_getp) && obj_getp && *obj_getp) {
(*obj_getp)->release();
}
Py_RETURN_NONE;
}
static PyObject* pycvVideoWriterExit(PyObject* self, PyObject* /*args*/, PyObject* /*kw*/) {
Ptr<cv::VideoWriter>* obj_getp = nullptr;
if (pyopencv_VideoWriter_getp(self, obj_getp) && obj_getp && *obj_getp) {
(*obj_getp)->release();
}
Py_RETURN_NONE;
}
#define PYOPENCV_EXTRA_METHODS_VideoCapture \
{"__enter__", CV_PY_FN_WITH_KW(pycvVideoEnter), "Context manager enter"}, \
{"__exit__", CV_PY_FN_WITH_KW(pycvVideoCaptureExit), "Context manager exit"},
#define PYOPENCV_EXTRA_METHODS_VideoWriter \
{"__enter__", CV_PY_FN_WITH_KW(pycvVideoEnter), "Context manager enter"}, \
{"__exit__", CV_PY_FN_WITH_KW(pycvVideoWriterExit), "Context manager exit"},
#endif // HAVE_OPENCV_VIDEOIO
@@ -5,6 +5,7 @@ import numpy as np
import cv2 as cv
import io
import sys
import tempfile
from tests_common import NewOpenCVTests
@@ -87,5 +88,22 @@ class Bindings(NewOpenCVTests):
self.assertTrue(hasFrame)
self.assertEqual(frame.shape, (576, 768, 3))
def test_context_manager(self):
video_file = self.find_file("cv/video/768x576.avi")
with cv.VideoCapture(video_file) as cap:
self.assertTrue(cap.isOpened(), "VideoCapture should be opened within context manager")
with tempfile.NamedTemporaryFile(suffix='.avi') as tmp:
with cv.VideoWriter(tmp.name, cv.VideoWriter_fourcc(*'MJPG'), 25, (640, 480)) as writer:
self.assertTrue(isinstance(writer, cv.VideoWriter))
try:
with cv.VideoCapture(video_file) as cap:
self.assertTrue(cap.isOpened())
raise RuntimeError("Testing context manager exception safety")
except RuntimeError:
pass
if __name__ == '__main__':
NewOpenCVTests.bootstrap()