Beta/venv/lib/python3.12/site-packages/av/container/input.py

319 lines
12 KiB
Python
Raw Normal View History

2026-06-16 17:09:34 +00:00
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, uint8_t
from cython.cimports.libc.stdlib import free, malloc
@cython.cfunc
def close_input(self: InputContainer):
2026-02-06 22:23:20 +01:00
self.streams = StreamContainer()
2026-06-16 17:09:34 +00:00
with cython.nogil:
if self._myflag & 2:
2026-02-06 22:23:20 +01:00
# This causes `self.ptr` to be set to NULL.
2026-06-16 17:09:34 +00:00
lib.avformat_close_input(cython.address(self.ptr))
self._myflag &= ~2 # enum.input_was_opened = False
2026-02-06 22:23:20 +01:00
2026-06-16 17:09:34 +00:00
@cython.final
@cython.cclass
class InputContainer(Container):
2026-02-06 22:23:20 +01:00
def __cinit__(self, *args, **kwargs):
2026-06-16 17:09:34 +00:00
py_codec_context: CodecContext
i: cython.uint
stream: cython.pointer[lib.AVStream]
codec: cython.pointer[cython.const[lib.AVCodec]]
codec_context: cython.pointer[lib.AVCodecContext]
2026-02-06 22:23:20 +01:00
# If we have either the global `options`, or a `stream_options`, prepare
# a mashup of those options for each stream.
2026-06-16 17:09:34 +00:00
c_options: cython.pointer[cython.pointer[lib.AVDictionary]] = cython.NULL
base_dict: Dictionary
stream_dict: Dictionary
nb_streams_before: cython.uint = self.ptr.nb_streams
if self.stream_options and nb_streams_before == 0:
raise ValueError(
"stream_options were provided, but this format does not expose "
"its streams before avformat_find_stream_info (e.g. MPEG). "
"Per-stream options cannot be applied."
)
# Only allocate c_options when streams are already known.
if (self.options or self.stream_options) and nb_streams_before > 0:
2026-02-06 22:23:20 +01:00
base_dict = Dictionary(self.options)
2026-06-16 17:09:34 +00:00
c_options = cython.cast(
cython.pointer[cython.pointer[lib.AVDictionary]],
malloc(nb_streams_before * cython.sizeof(cython.p_void)),
)
for i in range(nb_streams_before):
c_options[i] = cython.NULL
2026-02-06 22:23:20 +01:00
if i < len(self.stream_options) and self.stream_options:
stream_dict = base_dict.copy()
stream_dict.update(self.stream_options[i])
2026-06-16 17:09:34 +00:00
lib.av_dict_copy(cython.address(c_options[i]), stream_dict.ptr, 0)
2026-02-06 22:23:20 +01:00
else:
2026-06-16 17:09:34 +00:00
lib.av_dict_copy(cython.address(c_options[i]), base_dict.ptr, 0)
2026-02-06 22:23:20 +01:00
self.set_timeout(self.open_timeout)
self.start_timeout()
2026-06-16 17:09:34 +00:00
with cython.nogil:
ret = lib.avformat_find_stream_info(self.ptr, c_options)
2026-02-06 22:23:20 +01:00
self.set_timeout(None)
self.err_check(ret)
if c_options:
2026-06-16 17:09:34 +00:00
for i in range(nb_streams_before):
lib.av_dict_free(cython.address(c_options[i]))
2026-02-06 22:23:20 +01:00
free(c_options)
at_least_one_accelerated_context = False
self.streams = StreamContainer()
for i in range(self.ptr.nb_streams):
stream = self.ptr.streams[i]
codec = lib.avcodec_find_decoder(stream.codecpar.codec_id)
if codec:
codec_context = lib.avcodec_alloc_context3(codec)
2026-06-16 17:09:34 +00:00
err_check(
lib.avcodec_parameters_to_context(codec_context, stream.codecpar)
)
2026-02-06 22:23:20 +01:00
codec_context.pkt_timebase = stream.time_base
2026-06-16 17:09:34 +00:00
py_codec_context = wrap_codec_context(
codec_context, codec, self.hwaccel
)
2026-02-06 22:23:20 +01:00
if py_codec_context.is_hwaccel:
at_least_one_accelerated_context = True
else:
# no decoder is available
py_codec_context = None
self.streams.add_stream(wrap_stream(self, stream, py_codec_context))
2026-06-16 17:09:34 +00:00
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"
)
2026-02-06 22:23:20 +01:00
2026-06-16 17:09:34 +00:00
self.metadata = avdict_to_dict(
self.ptr.metadata, self.metadata_encoding, self.metadata_errors
)
2026-02-06 22:23:20 +01:00
def __dealloc__(self):
close_input(self)
@property
def start_time(self):
self._assert_open()
if self.ptr.start_time != lib.AV_NOPTS_VALUE:
return self.ptr.start_time
@property
def duration(self):
self._assert_open()
if self.ptr.duration != lib.AV_NOPTS_VALUE:
return self.ptr.duration
@property
def bit_rate(self):
self._assert_open()
return self.ptr.bit_rate
@property
def size(self):
self._assert_open()
return lib.avio_size(self.ptr.pb)
def close(self):
close_input(self)
def demux(self, *args, **kwargs):
"""demux(streams=None, video=None, audio=None, subtitles=None, data=None)
Yields a series of :class:`.Packet` from the given set of :class:`.Stream`::
for packet in container.demux():
# Do something with `packet`, often:
for frame in packet.decode():
# Do something with `frame`.
.. seealso:: :meth:`.StreamContainer.get` for the interpretation of
the arguments.
.. note:: The last packets are dummy packets that when decoded will flush the buffers.
"""
self._assert_open()
2026-06-16 17:09:34 +00:00
streams: list[Stream] = self.streams.get(*args, **kwargs)
if self.ptr.nb_streams == 0:
return
include_stream: cython.pointer[uint8_t] = cython.cast(
cython.pointer[uint8_t],
malloc(self.ptr.nb_streams * cython.sizeof(uint8_t)),
)
if include_stream == cython.NULL:
2026-02-06 22:23:20 +01:00
raise MemoryError()
2026-06-16 17:09:34 +00:00
i: cython.uint
packet: Packet
read_packet: cython.pointer[lib.AVPacket]
ret: cython.int
2026-02-06 22:23:20 +01:00
self.set_timeout(self.read_timeout)
try:
for i in range(self.ptr.nb_streams):
2026-06-16 17:09:34 +00:00
include_stream[i] = 0
2026-02-06 22:23:20 +01:00
for stream in streams:
i = stream.index
if i >= self.ptr.nb_streams:
raise ValueError(f"stream index {i} out of range")
2026-06-16 17:09:34 +00:00
include_stream[i] = 1
# Pre-allocate a AVPacket that is reused as the read buffer.
with cython.nogil:
read_packet = lib.av_packet_alloc()
if read_packet == cython.NULL:
raise MemoryError("Could not allocate packet")
2026-02-06 22:23:20 +01:00
while True:
2026-06-16 17:09:34 +00:00
# Reset the read buffer
with cython.nogil:
lib.av_packet_unref(read_packet)
2026-02-06 22:23:20 +01:00
try:
self.start_timeout()
2026-06-16 17:09:34 +00:00
with cython.nogil:
ret = lib.av_read_frame(self.ptr, read_packet)
2026-02-06 22:23:20 +01:00
self.err_check(ret)
except EOFError:
break
2026-06-16 17:09:34 +00:00
if include_stream[read_packet.stream_index]:
2026-02-06 22:23:20 +01:00
# If AVFMTCTX_NOHEADER is set in ctx_flags, then new streams
# may also appear in av_read_frame().
# http://ffmpeg.org/doxygen/trunk/structAVFormatContext.html
# TODO: find better way to handle this
2026-06-16 17:09:34 +00:00
if read_packet.stream_index < len(self.streams):
# Move the encoded data out of the read buffer into a
# fresh Packet for the caller.
packet = Packet()
with cython.nogil:
lib.av_packet_move_ref(packet.ptr, read_packet)
2026-02-06 22:23:20 +01:00
packet._stream = self.streams[packet.ptr.stream_index]
# Keep track of this so that remuxing is easier.
packet.ptr.time_base = packet._stream.ptr.time_base
yield packet
# Flush!
for i in range(self.ptr.nb_streams):
if include_stream[i]:
packet = Packet()
packet._stream = self.streams[i]
packet.ptr.time_base = packet._stream.ptr.time_base
yield packet
finally:
self.set_timeout(None)
free(include_stream)
2026-06-16 17:09:34 +00:00
if read_packet != cython.NULL:
lib.av_packet_free(cython.address(read_packet))
2026-02-06 22:23:20 +01:00
def decode(self, *args, **kwargs):
"""decode(streams=None, video=None, audio=None, subtitles=None, data=None)
Yields a series of :class:`.Frame` from the given set of streams::
for frame in container.decode():
# Do something with `frame`.
.. seealso:: :meth:`.StreamContainer.get` for the interpretation of
the arguments.
"""
self._assert_open()
for packet in self.demux(*args, **kwargs):
for frame in packet.decode():
yield frame
def seek(
2026-06-16 17:09:34 +00:00
self,
offset,
*,
backward: cython.bint = True,
any_frame: cython.bint = False,
stream: Stream | None = None,
unsupported_frame_offset: cython.bint = False,
unsupported_byte_offset: cython.bint = False,
2026-02-06 22:23:20 +01:00
):
"""seek(offset, *, backward=True, any_frame=False, stream=None)
2026-06-16 17:09:34 +00:00
Seek to a (key)frame nearest to the given timestamp.
2026-02-06 22:23:20 +01:00
:param int offset: Time to seek to, expressed in``stream.time_base`` if ``stream``
is given, otherwise in :data:`av.time_base`.
:param bool backward: If there is not a (key)frame at the given offset,
look backwards for it.
:param bool any_frame: Seek to any frame, not just a keyframe.
:param Stream stream: The stream who's ``time_base`` the ``offset`` is in.
:param bool unsupported_frame_offset: ``offset`` is a frame
index instead of a time; not supported by any known format.
:param bool unsupported_byte_offset: ``offset`` is a byte
location in the file; not supported by any known format.
After seeking, packets that you demux should correspond (roughly) to
the position you requested.
In most cases, the defaults of ``backwards = True`` and ``any_frame = False``
are the best course of action, followed by you demuxing/decoding to
the position that you want. This is because to properly decode video frames
you need to start from the previous keyframe.
.. seealso:: :ffmpeg:`avformat_seek_file` for discussion of the flags.
"""
self._assert_open()
if not isinstance(offset, int):
raise TypeError("Container.seek only accepts integer offset.", type(offset))
2026-06-16 17:09:34 +00:00
c_offset: int64_t = offset
flags: cython.int = 0
ret: cython.int
2026-02-06 22:23:20 +01:00
if backward:
flags |= lib.AVSEEK_FLAG_BACKWARD
if any_frame:
flags |= lib.AVSEEK_FLAG_ANY
# If someone really wants (and to experiment), expose these.
if unsupported_frame_offset:
flags |= lib.AVSEEK_FLAG_FRAME
if unsupported_byte_offset:
flags |= lib.AVSEEK_FLAG_BYTE
2026-06-16 17:09:34 +00:00
stream_index: cython.int = stream.index if stream else -1
with cython.nogil:
2026-02-06 22:23:20 +01:00
ret = lib.av_seek_frame(self.ptr, stream_index, c_offset, flags)
err_check(ret)
self.flush_buffers()
2026-06-16 17:09:34 +00:00
@cython.cfunc
def flush_buffers(self):
2026-02-06 22:23:20 +01:00
self._assert_open()
2026-06-16 17:09:34 +00:00
stream: Stream
codec_context: CodecContext
2026-02-06 22:23:20 +01:00
for stream in self.streams:
codec_context = stream.codec_context
if codec_context:
codec_context.flush_buffers()