Skip to content

Commit 20b7c08

Browse files
Bordacodexpre-commit-ci[bot]claude[bot]
authored
feat(video): require PyAV during cv2 transition (roboflow#2438)
- Add the PyAV-backed file-video and audio fallback to the compatibility layer. - Declare PyAV alongside OpenCV until the final dependency-removal integration. - _VideoWriter now rejects is_color=False (NotImplementedError) instead of silently dropping it, since the PyAV fallback only encodes 3-channel frames. - _mux_audio cleanup (container closes, temp-file removal) is now best-effort so a failing close/remove in finally can no longer mask the primary result or the original exception. - The subprocess used to validate the cv2-free fallback had no timeout; a hang (import deadlock, codec probe stall) could block the whole CI run. Added a 60s timeout so a hang fails fast with a clear traceback instead of an opaque suite-wide stall. - process_video(preserve_audio=True) docstring still described the old ffmpeg-based muxing; audio remuxing was reimplemented with PyAV and no longer requires an external ffmpeg executable. - get_video_frames_generator's documented webcam fallback (`_cv2.VideoCapture(0)`) silently fails under the PyAV backend: the BackendUnavailableError raised for integer sources was swallowed with no logging, so isOpened() just returns False with zero diagnostic signal. Doc note now states the limitation explicitly and the capture logs a warning instead of failing silently. --------- Co-authored-by: Codex <codex@openai.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
1 parent c349613 commit 20b7c08

8 files changed

Lines changed: 753 additions & 171 deletions

File tree

docs/changelog.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
description: "Full version history of the supervision Python library — release notes, breaking changes, new features, and deprecations for every version."
3-
date_modified: 2026-07-15
3+
date_modified: 2026-07-16
44
---
55

66
# Changelog
@@ -48,6 +48,11 @@ date_modified: 2026-07-15
4848
- Fixed: dataset IO/export edge cases now avoid mutating caller-owned `Detections` during `DetectionDataset` construction, reject non-integer and out-of-range class ids with a clear `ValueError`, load COCO annotations that omit optional `iscrowd`/`area` fields, expose `DetectionDataset.from_coco(use_iscrowd=...)` without changing the existing positional `show_progress` argument, export mask pixel area to COCO when no stored area is present, ignore folder-structure root clutter and non-image files inside class folders, and accept PIL-readable YOLO images such as RGBA or palette PNGs.
4949

5050
### Added
51+
- Added a cv2-free PyAV fallback for file-video capture, writing, frame seeking,
52+
metadata, and `process_video(preserve_audio=True)` audio remuxing. OpenCV remains
53+
the primary backend when available; `av>=14.2.0` is now required alongside
54+
OpenCV during the transition, with the later OpenCV-removal integration removing
55+
the OpenCV dependency.
5156
- Added a cv2-free Hershey text fallback covering all eight OpenCV font faces,
5257
italic variants, exact text metrics, and packaged glyph provenance. OpenCV
5358
remains the primary renderer when available; the fallback uses the private

pyproject.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ classifiers = [
4747
"Typing :: Typed",
4848
]
4949
dependencies = [
50+
"av>=14.2",
5051
"defusedxml>=0.7.1",
5152
"matplotlib>=3.6",
5253
"numpy>=1.21.2",
@@ -191,8 +192,12 @@ mypy_path = "src"
191192
explicit_package_bases = true
192193
ignore_missing_imports = false
193194
python_version = "3.10"
195+
warn_unused_ignores = true
194196
strict = true
195-
overrides = [ { module = [ "examples.*", "tests.*" ], ignore_errors = true } ]
197+
overrides = [
198+
{ module = [ "examples.*", "tests.*" ], ignore_errors = true },
199+
{ module = [ "supervision._cv2" ], warn_unused_ignores = false },
200+
]
196201

197202
[tool.pytest]
198203
ini_options.testpaths = [ "src", "tests" ]

src/supervision/_cv2/__init__.py

Lines changed: 43 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from __future__ import annotations
44

55
from supervision._cv2._color import _cvt_color, _merge, _split
6-
from supervision._cv2._common import BackendUnavailableError, _unavailable
6+
from supervision._cv2._common import BackendUnavailableError
77
from supervision._cv2._components import (
88
_connected_components,
99
_connected_components_with_stats,
@@ -40,6 +40,11 @@
4040
_get_rotation_matrix_2d,
4141
_warp_affine,
4242
)
43+
from supervision._cv2._video import (
44+
_video_writer_fourcc,
45+
_VideoCapture,
46+
_VideoWriter,
47+
)
4348
from supervision._cv2.constants import (
4449
_BORDER_CONSTANT,
4550
_CAP_PROP_FPS,
@@ -82,7 +87,7 @@
8287
_IS_CV2_AVAILABLE = True
8388

8489
if _IS_CV2_AVAILABLE:
85-
from cv2 import (
90+
from cv2 import ( # type: ignore[attr-defined]
8691
BORDER_CONSTANT,
8792
CAP_PROP_FPS,
8893
CAP_PROP_FRAME_COUNT,
@@ -116,7 +121,7 @@
116121
RETR_TREE,
117122
VideoCapture,
118123
VideoWriter,
119-
VideoWriter_fourcc,
124+
VideoWriter_fourcc, # type: ignore[attr-defined]
120125
addWeighted,
121126
approxPolyDP,
122127
blur,
@@ -185,39 +190,41 @@
185190
LINE_AA = _LINE_AA
186191
RETR_TREE = _RETR_TREE
187192

188-
VideoCapture = _unavailable
189-
VideoWriter = _unavailable
190-
VideoWriter_fourcc = _unavailable
191-
addWeighted = _add_weighted
192-
approxPolyDP = _approx_poly_dp
193-
blur = _blur
194-
circle = _circle
195-
connectedComponents = _connected_components
196-
connectedComponentsWithStats = _connected_components_with_stats
197-
contourArea = _contour_area
198-
convertScaleAbs = _convert_scale_abs
199-
copyMakeBorder = _copy_make_border
200-
cvtColor = _cvt_color
201-
distanceTransform = _distance_transform
202-
drawContours = _draw_contours
203-
ellipse = _ellipse
204-
fillPoly = _fill_poly
205-
findContours = _find_contours
206-
flip = _flip
207-
getRotationMatrix2D = _get_rotation_matrix_2d
208-
getTextSize = _get_text_size
209-
imread = _imread
210-
imwrite = _imwrite
211-
intersectConvexConvex = _intersect_convex_convex
212-
line = _line
213-
mean = _mean
214-
merge = _merge
215-
polylines = _polylines
216-
putText = _put_text
217-
rectangle = _rectangle
218-
resize = _resize
219-
split = _split
220-
warpAffine = _warp_affine
193+
# Fallback implementations when cv2 is not available. Suppress type errors because
194+
# fallback types differ from cv2 types, but are functionally equivalent.
195+
VideoCapture = _VideoCapture # type: ignore[assignment,misc]
196+
VideoWriter = _VideoWriter # type: ignore[assignment,misc]
197+
VideoWriter_fourcc = _video_writer_fourcc # type: ignore[assignment]
198+
addWeighted = _add_weighted # type: ignore[assignment]
199+
approxPolyDP = _approx_poly_dp # type: ignore[assignment]
200+
blur = _blur # type: ignore[assignment]
201+
circle = _circle # type: ignore[assignment]
202+
connectedComponents = _connected_components # type: ignore[assignment]
203+
connectedComponentsWithStats = _connected_components_with_stats # type: ignore[assignment]
204+
contourArea = _contour_area # type: ignore[assignment]
205+
convertScaleAbs = _convert_scale_abs # type: ignore[assignment]
206+
copyMakeBorder = _copy_make_border # type: ignore[assignment]
207+
cvtColor = _cvt_color # type: ignore[assignment]
208+
distanceTransform = _distance_transform # type: ignore[assignment]
209+
drawContours = _draw_contours # type: ignore[assignment]
210+
ellipse = _ellipse # type: ignore[assignment]
211+
fillPoly = _fill_poly # type: ignore[assignment]
212+
findContours = _find_contours # type: ignore[assignment]
213+
flip = _flip # type: ignore[assignment]
214+
getRotationMatrix2D = _get_rotation_matrix_2d # type: ignore[assignment]
215+
getTextSize = _get_text_size # type: ignore[assignment]
216+
imread = _imread # type: ignore[assignment]
217+
imwrite = _imwrite # type: ignore[assignment]
218+
intersectConvexConvex = _intersect_convex_convex # type: ignore[assignment]
219+
line = _line # type: ignore[assignment]
220+
mean = _mean # type: ignore[assignment]
221+
merge = _merge # type: ignore[assignment]
222+
polylines = _polylines # type: ignore[assignment]
223+
putText = _put_text # type: ignore[assignment]
224+
rectangle = _rectangle # type: ignore[assignment]
225+
resize = _resize # type: ignore[assignment]
226+
split = _split # type: ignore[assignment]
227+
warpAffine = _warp_affine # type: ignore[assignment]
221228

222229

223230
__all__ = [

0 commit comments

Comments
 (0)