Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 16 additions & 14 deletions av/buffer.pyx → av/buffer.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
from cpython cimport PyBUF_WRITABLE, PyBuffer_FillInfo
from libc.string cimport memcpy
import cython
from cython.cimports.av.bytesource import ByteSource, bytesource
from cython.cimports.cpython import PyBUF_WRITABLE, PyBuffer_FillInfo
from cython.cimports.libc.string import memcpy

from av.bytesource cimport ByteSource, bytesource


cdef class Buffer:
@cython.cclass
class Buffer:
"""A base class for PyAV objects which support the buffer protocol, such
as :class:`.Packet` and :class:`.Plane`.

"""

cdef size_t _buffer_size(self):
@cython.cfunc
def _buffer_size(self) -> cython.size_t:
return 0

cdef void* _buffer_ptr(self):
return NULL
def _buffer_ptr(self) -> cython.p_void:
return cython.NULL

cdef bint _buffer_writable(self):
def _buffer_writable(self) -> cython.bint:
return True

def __getbuffer__(self, Py_buffer *view, int flags):
def __getbuffer__(self, view: cython.pointer[Py_buffer], flags: cython.int):
if flags & PyBUF_WRITABLE and not self._buffer_writable():
raise ValueError("buffer is not writable")

Expand All @@ -33,20 +35,20 @@ def buffer_size(self):
@property
def buffer_ptr(self):
"""The memory address of the buffer."""
return <size_t>self._buffer_ptr()
return cython.cast(cython.size_t, self._buffer_ptr())

def update(self, input):
"""Replace the data in this object with the given buffer.

Accepts anything that supports the `buffer protocol <https://docs.python.org/3/c-api/buffer.html>`_,
e.g. bytes, Numpy arrays, other :class:`Buffer` objects, etc..
e.g. bytes, NumPy arrays, other :class:`Buffer` objects, etc..

"""
if not self._buffer_writable():
raise ValueError("buffer is not writable")

cdef ByteSource source = bytesource(input)
cdef size_t size = self._buffer_size()
source: ByteSource = bytesource(input)
size: cython.size_t = self._buffer_size()

if source.length != size:
raise ValueError(f"got {source.length} bytes; need {size} bytes")
Expand Down
5 changes: 2 additions & 3 deletions av/subtitles/subtitle.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import cython
from cython.cimports.cpython import PyBuffer_FillInfo, PyBytes_FromString
from cython.cimports.libc.stdint import int64_t, uint64_t
from cython.cimports.libc.string import memcpy, strlen


Expand All @@ -10,7 +9,7 @@ def __dealloc__(self):
lib.avsubtitle_free(cython.address(self.struct))


_cinit_bypass_sentinel = object()
_cinit_bypass_sentinel = cython.declare(object, object())


@cython.cclass
Expand Down Expand Up @@ -293,7 +292,7 @@ def dialogue(self):
Extract the dialogue from the ass format. Strip comments.
"""
comma_count: cython.short = 0
i: uint64_t = 0
i: cython.Py_ssize_t = 0
state: cython.bint = False
ass_text: bytes = self.ass
char, next_char = cython.declare(cython.char)
Expand Down
143 changes: 84 additions & 59 deletions av/video/reformatter.pyx → av/video/reformatter.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
cimport libav as lib
from libc.stdint cimport uint8_t

from av.error cimport err_check
from av.video.format cimport VideoFormat
from av.video.frame cimport alloc_video_frame

from enum import IntEnum

import cython
import cython.cimports.libav as lib
from cython.cimports.av.error import err_check
from cython.cimports.av.video.format import VideoFormat
from cython.cimports.av.video.frame import alloc_video_frame


class Interpolation(IntEnum):
FAST_BILINEAR: "Fast bilinear" = lib.SWS_FAST_BILINEAR
Expand Down Expand Up @@ -38,6 +37,7 @@ class Colorspace(IntEnum):
smpte240m = lib.SWS_CS_SMPTE240M
default = lib.SWS_CS_DEFAULT


class ColorRange(IntEnum):
UNSPECIFIED: "Unspecified" = lib.AVCOL_RANGE_UNSPECIFIED
MPEG: "MPEG (limited) YUV range, 219*2^(n-8)" = lib.AVCOL_RANGE_MPEG
Expand All @@ -58,7 +58,8 @@ def _resolve_enum_value(value, enum_class, default):
raise ValueError(f"Cannot convert {value} to {enum_class.__name__}")


cdef class VideoReformatter:
@cython.cclass
class VideoReformatter:
"""An object for reformatting size and pixel format of :class:`.VideoFrame`.

It is most efficient to have a reformatter object for each set of parameters
Expand All @@ -67,13 +68,21 @@ def _resolve_enum_value(value, enum_class, default):
"""

def __dealloc__(self):
with nogil:
with cython.nogil:
lib.sws_freeContext(self.ptr)

def reformat(self, VideoFrame frame not None, width=None, height=None,
format=None, src_colorspace=None, dst_colorspace=None,
interpolation=None, src_color_range=None,
dst_color_range=None):
def reformat(
self,
frame: VideoFrame,
width=None,
height=None,
format=None,
src_colorspace=None,
dst_colorspace=None,
interpolation=None,
src_color_range=None,
dst_color_range=None,
):
"""Create a new :class:`VideoFrame` with the given width/height/format/colorspace.

Returns the same frame untouched if nothing needs to be done to it.
Expand All @@ -95,13 +104,24 @@ def reformat(self, VideoFrame frame not None, width=None, height=None,

"""

cdef VideoFormat video_format = VideoFormat(format if format is not None else frame.format)

cdef int c_src_colorspace = _resolve_enum_value(src_colorspace, Colorspace, frame.colorspace)
cdef int c_dst_colorspace = _resolve_enum_value(dst_colorspace, Colorspace, frame.colorspace)
cdef int c_interpolation = _resolve_enum_value(interpolation, Interpolation, int(Interpolation.BILINEAR))
cdef int c_src_color_range = _resolve_enum_value(src_color_range, ColorRange, 0)
cdef int c_dst_color_range = _resolve_enum_value(dst_color_range, ColorRange, 0)
video_format: VideoFormat = VideoFormat(
format if format is not None else frame.format
)
c_src_colorspace: cython.int = _resolve_enum_value(
src_colorspace, Colorspace, frame.colorspace
)
c_dst_colorspace: cython.int = _resolve_enum_value(
dst_colorspace, Colorspace, frame.colorspace
)
c_interpolation: cython.int = _resolve_enum_value(
interpolation, Interpolation, int(Interpolation.BILINEAR)
)
c_src_color_range: cython.int = _resolve_enum_value(
src_color_range, ColorRange, 0
)
c_dst_color_range: cython.int = _resolve_enum_value(
dst_color_range, ColorRange, 0
)

return self._reformat(
frame,
Expand All @@ -115,31 +135,39 @@ def reformat(self, VideoFrame frame not None, width=None, height=None,
c_dst_color_range,
)

cdef _reformat(self, VideoFrame frame, int width, int height,
lib.AVPixelFormat dst_format, int src_colorspace,
int dst_colorspace, int interpolation,
int src_color_range, int dst_color_range):

@cython.cfunc
def _reformat(
self,
frame: VideoFrame,
width: cython.int,
height: cython.int,
dst_format: lib.AVPixelFormat,
src_colorspace: cython.int,
dst_colorspace: cython.int,
interpolation: cython.int,
src_color_range: cython.int,
dst_color_range: cython.int,
):
if frame.ptr.format < 0:
raise ValueError("Frame does not have format set.")

# The definition of color range in pixfmt.h and swscale.h is different.
src_color_range = 1 if src_color_range == ColorRange.JPEG.value else 0
dst_color_range = 1 if dst_color_range == ColorRange.JPEG.value else 0

cdef lib.AVPixelFormat src_format = <lib.AVPixelFormat> frame.ptr.format
src_format = cython.cast(lib.AVPixelFormat, frame.ptr.format)

# Shortcut!
if (
dst_format == src_format and
width == frame.ptr.width and
height == frame.ptr.height and
dst_colorspace == src_colorspace and
src_color_range == dst_color_range
dst_format == src_format
and width == frame.ptr.width
and height == frame.ptr.height
and dst_colorspace == src_colorspace
and src_color_range == dst_color_range
):
return frame

with nogil:
with cython.nogil:
self.ptr = lib.sws_getCachedContext(
self.ptr,
frame.ptr.width,
Expand All @@ -149,44 +177,44 @@ def reformat(self, VideoFrame frame not None, width=None, height=None,
height,
dst_format,
interpolation,
NULL,
NULL,
NULL
cython.NULL,
cython.NULL,
cython.NULL,
)

# We want to change the colorspace/color_range transforms.
# We do that by grabbing all of the current settings, changing a
# We do that by grabbing all the current settings, changing a
# couple, and setting them all. We need a lot of state here.
cdef int *inv_tbl
cdef int *tbl
cdef int src_colorspace_range, dst_colorspace_range
cdef int brightness, contrast, saturation
cdef int ret
inv_tbl: cython.p_int
tbl: cython.p_int
src_colorspace_range: cython.int
dst_colorspace_range: cython.int
brightness: cython.int
contrast: cython.int
saturation: cython.int

if src_colorspace != dst_colorspace or src_color_range != dst_color_range:
with nogil:
with cython.nogil:
ret = lib.sws_getColorspaceDetails(
self.ptr,
&inv_tbl,
&src_colorspace_range,
&tbl,
&dst_colorspace_range,
&brightness,
&contrast,
&saturation
cython.address(inv_tbl),
cython.address(src_colorspace_range),
cython.address(tbl),
cython.address(dst_colorspace_range),
cython.address(brightness),
cython.address(contrast),
cython.address(saturation),
)

err_check(ret)

with nogil:
with cython.nogil:
# Grab the coefficients for the requested transforms.
# The inv_table brings us to linear, and `tbl` to the new space.
if src_colorspace != lib.SWS_CS_DEFAULT:
inv_tbl = lib.sws_getCoefficients(src_colorspace)
if dst_colorspace != lib.SWS_CS_DEFAULT:
tbl = lib.sws_getCoefficients(dst_colorspace)

# Apply!
ret = lib.sws_setColorspaceDetails(
self.ptr,
inv_tbl,
Expand All @@ -195,21 +223,18 @@ def reformat(self, VideoFrame frame not None, width=None, height=None,
dst_color_range,
brightness,
contrast,
saturation
saturation,
)

err_check(ret)

# Create a new VideoFrame.
cdef VideoFrame new_frame = alloc_video_frame()
new_frame: VideoFrame = alloc_video_frame()
new_frame._copy_internal_attributes(frame)
new_frame._init(dst_format, width, height)

# Finally, scale the image.
with nogil:
with cython.nogil:
lib.sws_scale(
self.ptr,
<const uint8_t *const *>frame.ptr.data,
frame.ptr.data,
frame.ptr.linesize,
0, # slice Y
frame.ptr.height,
Expand Down
Loading