Skip to content

Commit d958365

Browse files
committed
Make sidedata/motionvectors pure
1 parent 0caf320 commit d958365

5 files changed

Lines changed: 193 additions & 113 deletions

File tree

av/sidedata/motionvectors.pxd

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,11 @@ from av.frame cimport Frame
44
from av.sidedata.sidedata cimport SideData
55

66

7-
cdef class _MotionVectors(SideData):
8-
7+
cdef class MotionVectors(SideData):
98
cdef dict _vectors
10-
cdef int _len
9+
cdef Py_ssize_t _len
1110

1211

1312
cdef class MotionVector:
14-
15-
cdef _MotionVectors parent
13+
cdef MotionVectors parent
1614
cdef lib.AVMotionVector *ptr

av/sidedata/motionvectors.py

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
from collections.abc import Sequence
2+
import cython
3+
from cython.cimports import libav as lib
4+
from cython.cimports.av.sidedata.sidedata import SideData
5+
6+
_cinit_bypass_sentinel = cython.declare(object, object())
7+
8+
9+
@cython.cclass
10+
class MotionVectors(SideData, Sequence):
11+
def __init__(self, sentinel, frame: Frame, index: cython.int):
12+
SideData.__init__(self, sentinel, frame, index)
13+
self._vectors = {}
14+
self._len = self.ptr.size // cython.sizeof(lib.AVMotionVector)
15+
16+
def __repr__(self):
17+
return (
18+
f"<av.sidedata.MotionVectors {self.ptr.size} bytes "
19+
f"of {len(self)} vectors at 0x{cython.cast(cython.uint, self.ptr.data):0x}>"
20+
)
21+
22+
def __len__(self):
23+
return self._len
24+
25+
def __getitem__(self, index):
26+
"""
27+
Get a motion vector by index.
28+
29+
Supports integer indexing and slicing.
30+
"""
31+
# Handle slicing
32+
if isinstance(index, slice):
33+
indices = range(*index.indices(self._len))
34+
return [self[i] for i in indices]
35+
36+
# Handle negative indexing
37+
if not isinstance(index, int):
38+
raise TypeError(
39+
f"indices must be integers or slices, not {type(index).__name__}"
40+
)
41+
42+
if index < 0:
43+
index += self._len
44+
45+
if index < 0 or index >= self._len:
46+
raise IndexError(
47+
f"index {index} out of range for MotionVectors of length {self._len}"
48+
)
49+
50+
# Return cached vector or create new one
51+
try:
52+
return self._vectors[index]
53+
except KeyError:
54+
vector = self._vectors[index] = MotionVector(
55+
_cinit_bypass_sentinel, self, index
56+
)
57+
return vector
58+
59+
def __iter__(self):
60+
"""Iterate over all motion vectors."""
61+
for i in range(self._len):
62+
yield self[i]
63+
64+
def to_ndarray(self):
65+
"""
66+
Convert motion vectors to a NumPy structured array.
67+
68+
Returns a NumPy array with fields corresponding to the AVMotionVector structure.
69+
"""
70+
import numpy as np
71+
72+
return np.frombuffer(
73+
self,
74+
dtype=np.dtype(
75+
[
76+
("source", "int32"),
77+
("w", "uint8"),
78+
("h", "uint8"),
79+
("src_x", "int16"),
80+
("src_y", "int16"),
81+
("dst_x", "int16"),
82+
("dst_y", "int16"),
83+
("flags", "uint64"),
84+
("motion_x", "int32"),
85+
("motion_y", "int32"),
86+
("motion_scale", "uint16"),
87+
],
88+
align=True,
89+
),
90+
)
91+
92+
@cython.cclass
93+
class MotionVector:
94+
"""
95+
Represents a single motion vector from video frame data.
96+
97+
Motion vectors describe the motion of a block of pixels between frames.
98+
"""
99+
100+
def __init__(self, sentinel, parent: MotionVectors, index: cython.int):
101+
if sentinel is not _cinit_bypass_sentinel:
102+
raise RuntimeError("cannot manually instantiate MotionVector")
103+
self.parent = parent
104+
base: cython.pointer[lib.AVMotionVector] = cython.cast(
105+
cython.pointer[lib.AVMotionVector], parent.ptr.data
106+
)
107+
self.ptr = base + index
108+
109+
def __repr__(self):
110+
return (
111+
f"<av.sidedata.MotionVector {self.w}x{self.h} "
112+
f"from ({self.src_x},{self.src_y}) to ({self.dst_x},{self.dst_y})>"
113+
)
114+
115+
def __eq__(self, other):
116+
"""Compare two motion vectors for equality."""
117+
if not isinstance(other, MotionVector):
118+
return NotImplemented
119+
return (
120+
self.source == other.source
121+
and self.w == other.w
122+
and self.h == other.h
123+
and self.src_x == other.src_x
124+
and self.src_y == other.src_y
125+
and self.dst_x == other.dst_x
126+
and self.dst_y == other.dst_y
127+
and self.motion_x == other.motion_x
128+
and self.motion_y == other.motion_y
129+
and self.motion_scale == other.motion_scale
130+
)
131+
132+
def __hash__(self):
133+
return hash(
134+
(
135+
self.source,
136+
self.w,
137+
self.h,
138+
self.src_x,
139+
self.src_y,
140+
self.dst_x,
141+
self.dst_y,
142+
self.motion_x,
143+
self.motion_y,
144+
self.motion_scale,
145+
)
146+
)
147+
148+
@property
149+
def source(self):
150+
return self.ptr.source
151+
152+
@property
153+
def w(self):
154+
return self.ptr.w
155+
156+
@property
157+
def h(self):
158+
return self.ptr.h
159+
160+
@property
161+
def src_x(self):
162+
return self.ptr.src_x
163+
164+
@property
165+
def src_y(self):
166+
return self.ptr.src_y
167+
168+
@property
169+
def dst_x(self):
170+
return self.ptr.dst_x
171+
172+
@property
173+
def dst_y(self):
174+
return self.ptr.dst_y
175+
176+
@property
177+
def motion_x(self):
178+
return self.ptr.motion_x
179+
180+
@property
181+
def motion_y(self):
182+
return self.ptr.motion_y
183+
184+
@property
185+
def motion_scale(self):
186+
return self.ptr.motion_scale

av/sidedata/motionvectors.pyi

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@ from .sidedata import SideData
66

77
class MotionVectors(SideData, Sequence[MotionVector]):
88
@overload
9-
def __getitem__(self, index: int): ...
9+
def __getitem__(self, index: int) -> MotionVector: ...
1010
@overload
11-
def __getitem__(self, index: slice): ...
12-
@overload
13-
def __getitem__(self, index: int | slice): ...
11+
def __getitem__(self, index: slice) -> list[MotionVector]: ...
1412
def __len__(self) -> int: ...
1513
def to_ndarray(self) -> np.ndarray[Any, Any]: ...
1614

av/sidedata/motionvectors.pyx

Lines changed: 0 additions & 104 deletions
This file was deleted.

av/video/frame.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,9 @@ def copy_bytes_to_plane(
158158
for row in range(start_row, end_row, step):
159159
i_pos = row * i_stride
160160
if flip_horizontal:
161+
i: cython.Py_ssize_t
161162
for i in range(0, i_stride, bytes_per_pixel):
163+
j: cython.Py_ssize_t
162164
for j in range(bytes_per_pixel):
163165
o_buf[o_pos + i + j] = i_buf[
164166
i_pos + i_stride - i - bytes_per_pixel + j

0 commit comments

Comments
 (0)