From 213ff6af68f53f4ed1a3a9435c0ea84ec31edf2c 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 +++++++++++++++------------ 1 file changed, 80 insertions(+), 61 deletions(-) rename av/container/{input.pyx => input.py} (71%) 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