Skip to content

Commit 548b00e

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

5 files changed

Lines changed: 171 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: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
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: cython.Py_ssize_t):
26+
try:
27+
return self._vectors[index]
28+
except KeyError:
29+
pass
30+
31+
if index >= self._len:
32+
raise IndexError(index)
33+
34+
vector = self._vectors[index] = MotionVector(_cinit_bypass_sentinel, self, index)
35+
return vector
36+
37+
def __iter__(self):
38+
"""Iterate over all motion vectors."""
39+
for i in range(self._len):
40+
yield self[i]
41+
42+
def to_ndarray(self):
43+
"""
44+
Convert motion vectors to a NumPy structured array.
45+
46+
Returns a NumPy array with fields corresponding to the AVMotionVector structure.
47+
"""
48+
import numpy as np
49+
50+
return np.frombuffer(
51+
self,
52+
dtype=np.dtype(
53+
[
54+
("source", "int32"),
55+
("w", "uint8"),
56+
("h", "uint8"),
57+
("src_x", "int16"),
58+
("src_y", "int16"),
59+
("dst_x", "int16"),
60+
("dst_y", "int16"),
61+
("flags", "uint64"),
62+
("motion_x", "int32"),
63+
("motion_y", "int32"),
64+
("motion_scale", "uint16"),
65+
],
66+
align=True,
67+
),
68+
)
69+
70+
@cython.cclass
71+
class MotionVector:
72+
"""
73+
Represents a single motion vector from video frame data.
74+
75+
Motion vectors describe the motion of a block of pixels between frames.
76+
"""
77+
78+
def __init__(self, sentinel, parent: MotionVectors, index: cython.int):
79+
if sentinel is not _cinit_bypass_sentinel:
80+
raise RuntimeError("cannot manually instantiate MotionVector")
81+
self.parent = parent
82+
base: cython.pointer[lib.AVMotionVector] = cython.cast(
83+
cython.pointer[lib.AVMotionVector], parent.ptr.data
84+
)
85+
self.ptr = base + index
86+
87+
def __repr__(self):
88+
return (
89+
f"<av.sidedata.MotionVector {self.w}x{self.h} "
90+
f"from ({self.src_x},{self.src_y}) to ({self.dst_x},{self.dst_y})>"
91+
)
92+
93+
def __eq__(self, other):
94+
"""Compare two motion vectors for equality."""
95+
if not isinstance(other, MotionVector):
96+
return NotImplemented
97+
return (
98+
self.source == other.source
99+
and self.w == other.w
100+
and self.h == other.h
101+
and self.src_x == other.src_x
102+
and self.src_y == other.src_y
103+
and self.dst_x == other.dst_x
104+
and self.dst_y == other.dst_y
105+
and self.motion_x == other.motion_x
106+
and self.motion_y == other.motion_y
107+
and self.motion_scale == other.motion_scale
108+
)
109+
110+
def __hash__(self):
111+
return hash(
112+
(
113+
self.source,
114+
self.w,
115+
self.h,
116+
self.src_x,
117+
self.src_y,
118+
self.dst_x,
119+
self.dst_y,
120+
self.motion_x,
121+
self.motion_y,
122+
self.motion_scale,
123+
)
124+
)
125+
126+
@property
127+
def source(self):
128+
return self.ptr.source
129+
130+
@property
131+
def w(self):
132+
return self.ptr.w
133+
134+
@property
135+
def h(self):
136+
return self.ptr.h
137+
138+
@property
139+
def src_x(self):
140+
return self.ptr.src_x
141+
142+
@property
143+
def src_y(self):
144+
return self.ptr.src_y
145+
146+
@property
147+
def dst_x(self):
148+
return self.ptr.dst_x
149+
150+
@property
151+
def dst_y(self):
152+
return self.ptr.dst_y
153+
154+
@property
155+
def motion_x(self):
156+
return self.ptr.motion_x
157+
158+
@property
159+
def motion_y(self):
160+
return self.ptr.motion_y
161+
162+
@property
163+
def motion_scale(self):
164+
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)