From 01fb4c86bb053e7ed378fe0f9a511be45e4c49ff Mon Sep 17 00:00:00 2001 From: WyattBlue Date: Wed, 21 Jan 2026 12:55:36 -0500 Subject: [PATCH] Make container/input pure --- av/container/{input.pyx => input.py} | 141 +++++++++++++++------------ av/filter/{link.pyx => link.py} | 82 ++++++++++------ 2 files changed, 130 insertions(+), 93 deletions(-) rename av/container/{input.pyx => input.py} (71%) rename av/filter/{link.pyx => link.py} (57%) diff --git a/av/container/input.pyx b/av/container/input.py similarity index 71% rename from av/container/input.pyx rename to av/container/input.py index 56af7b05c..682158997 100644 --- a/av/container/input.pyx +++ b/av/container/input.py @@ -1,53 +1,59 @@ -from libc.stdint cimport int64_t -from libc.stdlib cimport free, malloc - -from av.codec.context cimport CodecContext, wrap_codec_context -from av.container.streams cimport StreamContainer -from av.dictionary cimport _Dictionary -from av.error cimport err_check -from av.packet cimport Packet -from av.stream cimport Stream, wrap_stream -from av.utils cimport avdict_to_dict +import cython +from cython.cimports.av.codec.context import CodecContext, wrap_codec_context +from cython.cimports.av.container.streams import StreamContainer +from cython.cimports.av.dictionary import _Dictionary +from cython.cimports.av.error import err_check +from cython.cimports.av.packet import Packet +from cython.cimports.av.stream import Stream, wrap_stream +from cython.cimports.av.utils import avdict_to_dict +from cython.cimports.libc.stdint import int64_t +from cython.cimports.libc.stdlib import free, malloc from av.dictionary import Dictionary -cdef close_input(InputContainer self): +@cython.cfunc +def close_input(self: InputContainer): self.streams = StreamContainer() if self.input_was_opened: - with nogil: + with cython.nogil: # This causes `self.ptr` to be set to NULL. - lib.avformat_close_input(&self.ptr) + lib.avformat_close_input(cython.address(self.ptr)) self.input_was_opened = False -cdef class InputContainer(Container): +@cython.cclass +class InputContainer(Container): def __cinit__(self, *args, **kwargs): - cdef CodecContext py_codec_context - cdef unsigned int i - cdef lib.AVStream *stream - cdef lib.AVCodec *codec - cdef lib.AVCodecContext *codec_context + py_codec_context: CodecContext + i: cython.uint + stream: cython.pointer[lib.AVStream] + codec: cython.pointer[lib.AVCodec] + codec_context: cython.pointer[lib.AVCodecContext] # If we have either the global `options`, or a `stream_options`, prepare # a mashup of those options for each stream. - cdef lib.AVDictionary **c_options = NULL - cdef _Dictionary base_dict, stream_dict + c_options: cython.pointer[cython.pointer[lib.AVDictionary]] = cython.NULL + base_dict: _Dictionary + stream_dict: _Dictionary if self.options or self.stream_options: base_dict = Dictionary(self.options) - c_options = malloc(self.ptr.nb_streams * sizeof(void*)) + c_options = cython.cast( + cython.pointer[cython.pointer[lib.AVDictionary]], + malloc(self.ptr.nb_streams * cython.sizeof(cython.p_void)), + ) for i in range(self.ptr.nb_streams): - c_options[i] = NULL + c_options[i] = cython.NULL if i < len(self.stream_options) and self.stream_options: stream_dict = base_dict.copy() stream_dict.update(self.stream_options[i]) - lib.av_dict_copy(&c_options[i], stream_dict.ptr, 0) + lib.av_dict_copy(cython.address(c_options[i]), stream_dict.ptr, 0) else: - lib.av_dict_copy(&c_options[i], base_dict.ptr, 0) + lib.av_dict_copy(cython.address(c_options[i]), base_dict.ptr, 0) self.set_timeout(self.open_timeout) self.start_timeout() - with nogil: + with cython.nogil: # This peeks are the first few frames to: # - set stream.disposition from codec.audio_service_type (not exposed); # - set stream.codec.bits_per_coded_sample; @@ -55,17 +61,14 @@ def __cinit__(self, *args, **kwargs): # - set stream.start_time; # - set stream.r_frame_rate to average value; # - open and closes codecs with the options provided. - ret = lib.avformat_find_stream_info( - self.ptr, - c_options - ) + ret = lib.avformat_find_stream_info(self.ptr, c_options) self.set_timeout(None) self.err_check(ret) - # Cleanup all of our options. + # Clean up all of our options. if c_options: for i in range(self.ptr.nb_streams): - lib.av_dict_free(&c_options[i]) + lib.av_dict_free(cython.address(c_options[i])) free(c_options) at_least_one_accelerated_context = False @@ -75,11 +78,15 @@ def __cinit__(self, *args, **kwargs): stream = self.ptr.streams[i] codec = lib.avcodec_find_decoder(stream.codecpar.codec_id) if codec: - # allocate and initialise decoder + # allocate and initialize decoder codec_context = lib.avcodec_alloc_context3(codec) - err_check(lib.avcodec_parameters_to_context(codec_context, stream.codecpar)) + err_check( + lib.avcodec_parameters_to_context(codec_context, stream.codecpar) + ) codec_context.pkt_timebase = stream.time_base - py_codec_context = wrap_codec_context(codec_context, codec, self.hwaccel) + py_codec_context = wrap_codec_context( + codec_context, codec, self.hwaccel + ) if py_codec_context.is_hwaccel: at_least_one_accelerated_context = True else: @@ -87,10 +94,18 @@ def __cinit__(self, *args, **kwargs): py_codec_context = None self.streams.add_stream(wrap_stream(self, stream, py_codec_context)) - if self.hwaccel and not self.hwaccel.allow_software_fallback and not at_least_one_accelerated_context: - raise RuntimeError("Hardware accelerated decode requested but no stream is compatible") + if ( + self.hwaccel + and not self.hwaccel.allow_software_fallback + and not at_least_one_accelerated_context + ): + raise RuntimeError( + "Hardware accelerated decode requested but no stream is compatible" + ) - self.metadata = avdict_to_dict(self.ptr.metadata, self.metadata_encoding, self.metadata_errors) + self.metadata = avdict_to_dict( + self.ptr.metadata, self.metadata_encoding, self.metadata_errors + ) def __dealloc__(self): close_input(self) @@ -141,19 +156,20 @@ def demux(self, *args, **kwargs): # For whatever reason, Cython does not like us directly passing kwargs # from one method to another. Without kwargs, it ends up passing a # NULL reference, which segfaults. So we force it to do something with it. - # This is likely a bug in Cython; see https://github.com/cython/cython/issues/2166 - # (and others). + # This is a bug in Cython; see https://github.com/cython/cython/issues/2166 id(kwargs) streams = self.streams.get(*args, **kwargs) - - cdef bint *include_stream = malloc(self.ptr.nb_streams * sizeof(bint)) - if include_stream == NULL: + include_stream: cython.pointer[cython.bint] = cython.cast( + cython.pointer[cython.bint], + malloc(self.ptr.nb_streams * cython.sizeof(bint)), + ) + if include_stream == cython.NULL: raise MemoryError() - cdef unsigned int i - cdef Packet packet - cdef int ret + i: cython.uint + packet: Packet + ret: cython.int self.set_timeout(self.read_timeout) try: @@ -169,7 +185,7 @@ def demux(self, *args, **kwargs): packet = Packet() try: self.start_timeout() - with nogil: + with cython.nogil: ret = lib.av_read_frame(self.ptr, packet.ptr) self.err_check(ret) except EOFError: @@ -217,8 +233,14 @@ def decode(self, *args, **kwargs): yield frame def seek( - self, offset, *, bint backward=True, bint any_frame=False, Stream stream=None, - bint unsupported_frame_offset=False, bint unsupported_byte_offset=False + self, + offset, + *, + backward: bint = True, + any_frame: bint = False, + stream: Stream | None = None, + unsupported_frame_offset: bint = False, + unsupported_byte_offset: bint = False, ): """seek(offset, *, backward=True, any_frame=False, stream=None) @@ -249,16 +271,12 @@ def seek( """ self._assert_open() - # We used to take floats here and assume they were in seconds. This - # was super confusing, so lets go in the complete opposite direction - # and reject non-ints. if not isinstance(offset, int): raise TypeError("Container.seek only accepts integer offset.", type(offset)) - cdef int64_t c_offset = offset - - cdef int flags = 0 - cdef int ret + c_offset: int64_t = offset + flags: cython.int = 0 + ret: cython.int if backward: flags |= lib.AVSEEK_FLAG_BACKWARD @@ -271,18 +289,19 @@ def seek( if unsupported_byte_offset: flags |= lib.AVSEEK_FLAG_BYTE - cdef int stream_index = stream.index if stream else -1 - with nogil: + stream_index: cython.int = stream.index if stream else -1 + with cython.nogil: ret = lib.av_seek_frame(self.ptr, stream_index, c_offset, flags) err_check(ret) self.flush_buffers() - cdef flush_buffers(self): + @cython.cfunc + def flush_buffers(self): self._assert_open() - cdef Stream stream - cdef CodecContext codec_context + stream: Stream + codec_context: CodecContext for stream in self.streams: codec_context = stream.codec_context diff --git a/av/filter/link.pyx b/av/filter/link.py similarity index 57% rename from av/filter/link.pyx rename to av/filter/link.py index 905082d15..7914ba2b1 100644 --- a/av/filter/link.pyx +++ b/av/filter/link.py @@ -1,12 +1,12 @@ -cimport libav as lib +import cython +import cython.cimports.libav as lib +from cython.cimports.av.filter.graph import Graph -from av.filter.graph cimport Graph +_cinit_sentinel = cython.declare(object, object()) -cdef _cinit_sentinel = object() - - -cdef class FilterLink: +@cython.cclass +class FilterLink: def __cinit__(self, sentinel): if sentinel is not _cinit_sentinel: raise RuntimeError("cannot instantiate FilterLink") @@ -15,14 +15,14 @@ def __cinit__(self, sentinel): def input(self): if self._input: return self._input - cdef lib.AVFilterContext *cctx = self.ptr.src - cdef unsigned int i + cctx: cython.pointer[lib.AVFilterContext] = self.ptr.src + i: cython.Py_ssize_t for i in range(cctx.nb_outputs): if self.ptr == cctx.outputs[i]: break - else: + else: # nobreak raise RuntimeError("could not find link in context") - ctx = self.graph._context_by_ptr[cctx] + ctx = self.graph._context_by_ptr[cython.cast(cython.long, cctx)] self._input = ctx.outputs[i] return self._input @@ -30,30 +30,33 @@ def input(self): def output(self): if self._output: return self._output - cdef lib.AVFilterContext *cctx = self.ptr.dst - cdef unsigned int i + cctx: cython.pointer[lib.AVFilterContext] = self.ptr.dst + i: cython.Py_ssize_t for i in range(cctx.nb_inputs): if self.ptr == cctx.inputs[i]: break else: raise RuntimeError("could not find link in context") try: - ctx = self.graph._context_by_ptr[cctx] + ctx = self.graph._context_by_ptr[cython.cast(cython.long, cctx)] except KeyError: - raise RuntimeError("could not find context in graph", (cctx.name, cctx.filter.name)) + raise RuntimeError( + "could not find context in graph", (cctx.name, cctx.filter.name) + ) self._output = ctx.inputs[i] return self._output -cdef FilterLink wrap_filter_link(Graph graph, lib.AVFilterLink *ptr): - cdef FilterLink link = FilterLink(_cinit_sentinel) +@cython.cfunc +def wrap_filter_link(graph: Graph, ptr: cython.pointer[lib.AVFilterLink]) -> FilterLink: + link: FilterLink = FilterLink(_cinit_sentinel) link.graph = graph link.ptr = ptr return link - -cdef class FilterPad: +@cython.cclass +class FilterPad: def __cinit__(self, sentinel): if sentinel is not _cinit_sentinel: raise RuntimeError("cannot construct FilterPad") @@ -62,7 +65,9 @@ def __repr__(self): _filter = self.filter.name _io = "inputs" if self.is_input else "outputs" - return f"" + return ( + f"" + ) @property def is_output(self): @@ -73,7 +78,8 @@ def name(self): return lib.avfilter_pad_get_name(self.base_ptr, self.index) -cdef class FilterContextPad(FilterPad): +@cython.cclass +class FilterContextPad(FilterPad): def __repr__(self): _filter = self.filter.name _io = "inputs" if self.is_input else "outputs" @@ -85,8 +91,10 @@ def __repr__(self): def link(self): if self._link: return self._link - cdef lib.AVFilterLink **links = self.context.ptr.inputs if self.is_input else self.context.ptr.outputs - cdef lib.AVFilterLink *link = links[self.index] + links: cython.pointer[cython.pointer[lib.AVFilterLink]] = ( + self.context.ptr.inputs if self.is_input else self.context.ptr.outputs + ) + link: cython.pointer[lib.AVFilterLink] = links[self.index] if not link: return self._link = wrap_filter_link(self.context.graph, link) @@ -94,29 +102,39 @@ def link(self): @property def linked(self): - cdef FilterLink link = self.link + link: FilterLink = self.link if link: return link.input if self.is_input else link.output -cdef tuple alloc_filter_pads(Filter filter, const lib.AVFilterPad *ptr, bint is_input, FilterContext context=None): +@cython.cfunc +def alloc_filter_pads( + filter: Filter, + ptr: cython.pointer[cython.const[lib.AVFilterPad]], + is_input: cython.bint, + context: FilterContext | None = None, +) -> tuple: if not ptr: return () - pads = [] + pads: list = [] # We need to be careful and check our bounds if we know what they are, # since the arrays on a AVFilterContext are not NULL terminated. - cdef int i = 0 - cdef int count + i: cython.int = 0 + count: cython.int if context is None: count = lib.avfilter_filter_pad_count(filter.ptr, not is_input) else: - count = (context.ptr.nb_inputs if is_input else context.ptr.nb_outputs) - - cdef FilterPad pad - while (i < count): - pad = FilterPad(_cinit_sentinel) if context is None else FilterContextPad(_cinit_sentinel) + count = context.ptr.nb_inputs if is_input else context.ptr.nb_outputs + + pad: FilterPad + while i < count: + pad = ( + FilterPad(_cinit_sentinel) + if context is None + else FilterContextPad(_cinit_sentinel) + ) pads.append(pad) pad.filter = filter pad.context = context