Voice et bot modif

This commit is contained in:
pi 2026-06-16 17:09:34 +00:00
parent 189d56026b
commit 7333a22bcd
10774 changed files with 634644 additions and 933308 deletions

View file

@ -2,7 +2,6 @@ from .codec import (
Capabilities,
Codec,
Properties,
codec_descriptor,
codecs_available,
find_best_pix_fmt_of_list,
)
@ -12,7 +11,6 @@ __all__ = (
"Capabilities",
"Codec",
"Properties",
"codec_descriptor",
"codecs_available",
"find_best_pix_fmt_of_list",
"CodecContext",

Binary file not shown.

View file

@ -1,24 +1,25 @@
cimport libav as lib
from av.audio.format cimport get_audio_format
from av.codec.hwaccel cimport wrap_hwconfig
from av.descriptor cimport wrap_avclass
from av.utils cimport avrational_to_fraction
from av.video.format cimport VideoFormat, get_pix_fmt, get_video_format
from enum import Flag, IntEnum
from libc.stdlib cimport free, malloc
import cython
from cython.cimports import libav as lib
from cython.cimports.av.audio.format import get_audio_format
from cython.cimports.av.codec.hwaccel import wrap_hwconfig
from cython.cimports.av.utils import avrational_to_fraction
from cython.cimports.av.video.format import VideoFormat, get_pix_fmt, get_video_format
from cython.cimports.libc.stdlib import free, malloc
_cinit_sentinel = cython.declare(object, object())
cdef object _cinit_sentinel = object()
cdef Codec wrap_codec(const lib.AVCodec *ptr):
cdef Codec codec = Codec(_cinit_sentinel)
@cython.cfunc
def wrap_codec(ptr: cython.pointer[cython.const[lib.AVCodec]]) -> Codec:
codec: Codec = Codec(_cinit_sentinel)
codec.ptr = ptr
codec.is_encoder = lib.av_codec_is_encoder(ptr)
codec._init()
return codec
class Properties(Flag):
NONE = 0
INTRA_ONLY = lib.AV_CODEC_PROP_INTRA_ONLY
@ -57,7 +58,9 @@ class UnknownCodecError(ValueError):
pass
cdef class Codec:
@cython.final
@cython.cclass
class Codec:
"""Codec(name, mode='r')
:param str name: The codec name.
@ -105,7 +108,8 @@ cdef class Codec:
if (mode == "w") != self.is_encoder:
raise RuntimeError("Found codec does not match mode.", name, mode)
cdef _init(self, name=None):
@cython.cfunc
def _init(self, name=None):
if not self.ptr:
raise UnknownCodecError(name)
@ -124,12 +128,13 @@ cdef class Codec:
mode = self.mode
return f"<av.{self.__class__.__name__} {self.name} {mode=}>"
def create(self, kind = None):
def create(self, kind=None):
"""Create a :class:`.CodecContext` for this codec.
:param str kind: Gives a hint to static type checkers for what exact CodecContext is used.
"""
from .context import CodecContext
return CodecContext.create(self)
@property
@ -141,10 +146,8 @@ cdef class Codec:
return not self.is_encoder
@property
def descriptor(self): return wrap_avclass(self.ptr.priv_class)
@property
def name(self): return self.ptr.name or ""
def name(self):
return self.ptr.name or ""
@property
def canonical_name(self):
@ -154,7 +157,8 @@ cdef class Codec:
return lib.avcodec_get_name(self.ptr.id)
@property
def long_name(self): return self.ptr.long_name or ""
def long_name(self):
return self.ptr.long_name or ""
@property
def type(self):
@ -164,79 +168,100 @@ cdef class Codec:
E.g: ``'audio'``, ``'video'``, ``'subtitle'``.
"""
return lib.av_get_media_type_string(self.ptr.type)
media_type = lib.av_get_media_type_string(self.ptr.type)
return "unknown" if media_type == cython.NULL else media_type
@property
def id(self): return self.ptr.id
def id(self):
return self.ptr.id
@property
def frame_rates(self):
"""A list of supported frame rates (:class:`fractions.Fraction`), or ``None``."""
if not self.ptr.supported_framerates:
out: cython.pointer[cython.const[cython.void]] = cython.NULL
num: cython.int = 0
lib.avcodec_get_supported_config(
cython.NULL,
self.ptr,
lib.AV_CODEC_CONFIG_FRAME_RATE,
0,
cython.address(out),
cython.address(num),
)
if not out:
return
ret = []
cdef int i = 0
while self.ptr.supported_framerates[i].denum:
ret.append(avrational_to_fraction(&self.ptr.supported_framerates[i]))
i += 1
return ret
rates = cython.cast(cython.pointer[lib.AVRational], out)
return [avrational_to_fraction(cython.address(rates[i])) for i in range(num)]
@property
def audio_rates(self):
"""A list of supported audio sample rates (``int``), or ``None``."""
if not self.ptr.supported_samplerates:
out: cython.pointer[cython.const[cython.void]] = cython.NULL
num: cython.int = 0
lib.avcodec_get_supported_config(
cython.NULL,
self.ptr,
lib.AV_CODEC_CONFIG_SAMPLE_RATE,
0,
cython.address(out),
cython.address(num),
)
if not out:
return
ret = []
cdef int i = 0
while self.ptr.supported_samplerates[i]:
ret.append(self.ptr.supported_samplerates[i])
i += 1
return ret
rates = cython.cast(cython.pointer[cython.int], out)
return [rates[i] for i in range(num)]
@property
def video_formats(self):
"""A list of supported :class:`.VideoFormat`, or ``None``."""
if not self.ptr.pix_fmts:
out: cython.pointer[cython.const[cython.void]] = cython.NULL
num: cython.int = 0
lib.avcodec_get_supported_config(
cython.NULL,
self.ptr,
lib.AV_CODEC_CONFIG_PIX_FORMAT,
0,
cython.address(out),
cython.address(num),
)
if not out:
return
ret = []
cdef int i = 0
while self.ptr.pix_fmts[i] != -1:
ret.append(get_video_format(self.ptr.pix_fmts[i], 0, 0))
i += 1
return ret
fmts = cython.cast(cython.pointer[lib.AVPixelFormat], out)
return [get_video_format(fmts[i], 0, 0) for i in range(num)]
@property
def audio_formats(self):
"""A list of supported :class:`.AudioFormat`, or ``None``."""
if not self.ptr.sample_fmts:
out: cython.pointer[cython.const[cython.void]] = cython.NULL
num: cython.int = 0
lib.avcodec_get_supported_config(
cython.NULL,
self.ptr,
lib.AV_CODEC_CONFIG_SAMPLE_FORMAT,
0,
cython.address(out),
cython.address(num),
)
if not out:
return
ret = []
cdef int i = 0
while self.ptr.sample_fmts[i] != -1:
ret.append(get_audio_format(self.ptr.sample_fmts[i]))
i += 1
return ret
fmts = cython.cast(cython.pointer[lib.AVSampleFormat], out)
return [get_audio_format(fmts[i]) for i in range(num)]
@property
def hardware_configs(self):
if self._hardware_configs:
return self._hardware_configs
ret = []
cdef int i = 0
cdef const lib.AVCodecHWConfig *ptr
ret: list = []
i: cython.int = 0
ptr: cython.pointer[cython.const[lib.AVCodecHWConfig]]
while True:
ptr = lib.avcodec_get_hw_config(self.ptr, i)
if not ptr:
break
ret.append(wrap_hwconfig(ptr))
i += 1
ret = tuple(ret)
self._hardware_configs = ret
return ret
self._hardware_configs = tuple(ret)
return self._hardware_configs
@property
def properties(self):
@ -309,12 +334,14 @@ cdef class Codec:
"""
return bool(self.ptr.capabilities & lib.AV_CODEC_CAP_DELAY)
cdef get_codec_names():
names = set()
cdef const lib.AVCodec *ptr
cdef void *opaque = NULL
@cython.cfunc
def get_codec_names():
names: cython.set = set()
ptr = cython.declare(cython.pointer[cython.const[lib.AVCodec]])
opaque: cython.p_void = cython.NULL
while True:
ptr = lib.av_codec_iterate(&opaque)
ptr = lib.av_codec_iterate(cython.address(opaque))
if ptr:
names.add(ptr.name)
else:
@ -323,7 +350,6 @@ cdef get_codec_names():
codecs_available = get_codec_names()
codec_descriptor = wrap_avclass(lib.avcodec_get_class())
def dump_codecs():
@ -373,6 +399,7 @@ def dump_codecs():
except Exception as e:
print(f"...... {codec.name:<18} ERROR: {e}")
def dump_hwconfigs():
print("Hardware configs:")
for name in sorted(codecs_available):
@ -402,13 +429,13 @@ def find_best_pix_fmt_of_list(pix_fmts, src_pix_fmt, has_alpha=False):
:return: (best_format, loss)
:rtype: (VideoFormat | None, int)
"""
cdef lib.AVPixelFormat src
cdef lib.AVPixelFormat best
cdef lib.AVPixelFormat *c_list = NULL
cdef Py_ssize_t n
cdef Py_ssize_t i
cdef object item
cdef int c_loss
src: lib.AVPixelFormat
best: lib.AVPixelFormat
c_list: cython.pointer[lib.AVPixelFormat] = cython.NULL
n: cython.Py_ssize_t
i: cython.Py_ssize_t
item: object
c_loss: cython.int
if pix_fmts is None:
raise TypeError("pix_fmts must not be None")
@ -418,29 +445,32 @@ def find_best_pix_fmt_of_list(pix_fmts, src_pix_fmt, has_alpha=False):
return None, 0
if isinstance(src_pix_fmt, VideoFormat):
src = (<VideoFormat>src_pix_fmt).pix_fmt
src = cython.cast(VideoFormat, src_pix_fmt).pix_fmt
else:
src = get_pix_fmt(<str>src_pix_fmt)
src = get_pix_fmt(cython.cast(str, src_pix_fmt))
n = len(pix_fmts)
c_list = <lib.AVPixelFormat *>malloc((n + 1) * sizeof(lib.AVPixelFormat))
if c_list == NULL:
c_list = cython.cast(
cython.pointer[lib.AVPixelFormat],
malloc((n + 1) * cython.sizeof(lib.AVPixelFormat)),
)
if c_list == cython.NULL:
raise MemoryError()
try:
for i in range(n):
item = pix_fmts[i]
if isinstance(item, VideoFormat):
c_list[i] = (<VideoFormat>item).pix_fmt
c_list[i] = cython.cast(VideoFormat, item).pix_fmt
else:
c_list[i] = get_pix_fmt(<str>item)
c_list[i] = get_pix_fmt(cython.cast(str, item))
c_list[n] = lib.AV_PIX_FMT_NONE
c_loss = 0
best = lib.avcodec_find_best_pix_fmt_of_list(
c_list, src, 1 if has_alpha else 0, &c_loss
c_list, src, 1 if has_alpha else 0, cython.address(c_loss)
)
return get_video_format(best, 0, 0), c_loss
finally:
if c_list != NULL:
if c_list != cython.NULL:
free(c_list)

View file

@ -4,7 +4,6 @@ from typing import ClassVar, Literal, Sequence, cast, overload
from av.audio.codeccontext import AudioCodecContext
from av.audio.format import AudioFormat
from av.descriptor import Descriptor
from av.subtitles.codeccontext import SubtitleCodecContext
from av.video.codeccontext import VideoCodecContext
from av.video.format import VideoFormat
@ -53,7 +52,6 @@ class Codec:
def is_decoder(self) -> bool: ...
@property
def mode(self) -> Literal["r", "w"]: ...
descriptor: Descriptor
@property
def name(self) -> str: ...
@property
@ -61,7 +59,9 @@ class Codec:
@property
def long_name(self) -> str: ...
@property
def type(self) -> Literal["video", "audio", "data", "subtitle", "attachment"]: ...
def type(
self,
) -> Literal["video", "audio", "data", "subtitle", "attachment", "unknown"]: ...
@property
def id(self) -> int: ...
frame_rates: list[Fraction] | None
@ -105,10 +105,6 @@ class Codec:
VideoCodecContext | AudioCodecContext | SubtitleCodecContext | CodecContext
): ...
class codec_descriptor:
name: str
options: tuple[int, ...]
codecs_available: set[str]
def dump_codecs() -> None: ...

Binary file not shown.

View file

@ -1,7 +1,7 @@
cimport libav as lib
from libc.stdint cimport int64_t
from libc.stdint cimport int64_t, uint8_t
from av.bytesource cimport ByteSource
from av.buffer cimport ByteSource
from av.codec.codec cimport Codec
from av.codec.hwaccel cimport HWAccel
from av.frame cimport Frame
@ -11,9 +11,6 @@ from av.packet cimport Packet
cdef class CodecContext:
cdef lib.AVCodecContext *ptr
# Whether AVCodecContext.extradata should be de-allocated upon destruction.
cdef bint extradata_set
# Used as a signal that this is within a stream, and also for us to access that
# stream. This is set "manually" by the stream after constructing this object.
cdef int stream_index
@ -36,11 +33,15 @@ cdef class CodecContext:
# Used by hardware-accelerated decode.
cdef HWAccel hwaccel_ctx
cdef uint8_t _ctxflags # ctxEnum: template_initialized
# True when created via add_stream_from_template(); start_encoding() skips
# avcodec_open2() and lets encode()/decode() open the codec lazily if needed.
# Used by both transcode APIs to setup user-land objects.
# TODO: Remove the `Packet` from `_setup_decoded_frame` (because flushing packets
# are bogus). It should take all info it needs from the context and/or stream.
cdef _prepare_and_time_rebase_frames_for_encode(self, Frame frame)
cdef _prepare_frames_for_encode(self, Frame frame)
cdef list _prepare_frames_for_encode(self, Frame frame)
cdef _setup_encoded_packet(self, Packet)
cdef _setup_decoded_frame(self, Frame, Packet)
@ -51,7 +52,6 @@ cdef class CodecContext:
# send/recv buffer may be limited to a single frame. Ergo, we need to flush
# the buffer as often as possible.
cdef _recv_packet(self)
cdef _send_packet_and_recv(self, Packet packet)
cdef _recv_frame(self)
cdef _transfer_hwframe(self, Frame frame)

View file

@ -1,36 +1,40 @@
cimport libav as lib
from libc.errno cimport EAGAIN
from libc.stdint cimport uint8_t
from libc.string cimport memcpy
from av.bytesource cimport ByteSource, bytesource
from av.codec.codec cimport Codec, wrap_codec
from av.dictionary cimport _Dictionary
from av.error cimport err_check
from av.packet cimport Packet
from av.utils cimport avrational_to_fraction, to_avrational
from enum import Flag, IntEnum
from av.dictionary import Dictionary
import cython
from cython.cimports import libav as lib
from cython.cimports.av.buffer import ByteSource, bytesource
from cython.cimports.av.codec.codec import Codec, wrap_codec
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.utils import avrational_to_fraction, to_avrational
from cython.cimports.libc.errno import EAGAIN
from cython.cimports.libc.stdint import uint8_t
from cython.cimports.libc.string import memcpy
_cinit_sentinel = cython.declare(object, object())
cdef object _cinit_sentinel = object()
cdef CodecContext wrap_codec_context(lib.AVCodecContext *c_ctx, const lib.AVCodec *c_codec, HWAccel hwaccel):
"""Build an av.CodecContext for an existing AVCodecContext."""
cdef CodecContext py_ctx
@cython.cfunc
def wrap_codec_context(
c_ctx: cython.pointer[lib.AVCodecContext],
c_codec: cython.pointer[cython.const[lib.AVCodec]],
hwaccel: HWAccel,
) -> CodecContext:
"""Build an bv.CodecContext for an existing AVCodecContext."""
py_ctx: CodecContext
if c_ctx.codec_type == lib.AVMEDIA_TYPE_VIDEO:
from av.video.codeccontext import VideoCodecContext
py_ctx = VideoCodecContext(_cinit_sentinel)
elif c_ctx.codec_type == lib.AVMEDIA_TYPE_AUDIO:
from av.audio.codeccontext import AudioCodecContext
py_ctx = AudioCodecContext(_cinit_sentinel)
elif c_ctx.codec_type == lib.AVMEDIA_TYPE_SUBTITLE:
from av.subtitles.codeccontext import SubtitleCodecContext
py_ctx = SubtitleCodecContext(_cinit_sentinel)
else:
py_ctx = CodecContext(_cinit_sentinel)
@ -44,7 +48,10 @@ class ThreadType(Flag):
NONE = 0
FRAME: "Decode more than one frame at once" = lib.FF_THREAD_FRAME
SLICE: "Decode more than one part of a single frame at once" = lib.FF_THREAD_SLICE
AUTO: "Decode using both FRAME and SLICE methods." = lib.FF_THREAD_SLICE | lib.FF_THREAD_FRAME
AUTO: "Decode using both FRAME and SLICE methods." = (
lib.FF_THREAD_SLICE | lib.FF_THREAD_FRAME
)
class Flags(IntEnum):
unaligned = lib.AV_CODEC_FLAG_UNALIGNED
@ -68,6 +75,7 @@ class Flags(IntEnum):
interlaced_me = lib.AV_CODEC_FLAG_INTERLACED_ME
closed_gop = lib.AV_CODEC_FLAG_CLOSED_GOP
class Flags2(IntEnum):
fast = lib.AV_CODEC_FLAG2_FAST
no_output = lib.AV_CODEC_FLAG2_NO_OUTPUT
@ -80,11 +88,14 @@ class Flags2(IntEnum):
ro_flush_noop = lib.AV_CODEC_FLAG2_RO_FLUSH_NOOP
cdef class CodecContext:
@cython.cclass
class CodecContext:
@staticmethod
def create(codec, mode=None, hwaccel=None):
cdef Codec cy_codec = codec if isinstance(codec, Codec) else Codec(codec, mode)
cdef lib.AVCodecContext *c_ctx = lib.avcodec_alloc_context3(cy_codec.ptr)
cy_codec: Codec = codec if isinstance(codec, Codec) else Codec(codec, mode)
c_ctx: cython.pointer[lib.AVCodecContext] = lib.avcodec_alloc_context3(
cy_codec.ptr
)
return wrap_codec_context(c_ctx, cy_codec.ptr, hwaccel)
def __cinit__(self, sentinel=None, *args, **kwargs):
@ -95,11 +106,17 @@ cdef class CodecContext:
self.stream_index = -1 # This is set by the container immediately.
self.is_open = False
cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec, HWAccel hwaccel):
@cython.cfunc
def _init(
self,
ptr: cython.pointer[lib.AVCodecContext],
codec: cython.pointer[cython.const[lib.AVCodec]],
hwaccel: HWAccel,
):
self.ptr = ptr
if self.ptr.codec and codec and self.ptr.codec != codec:
raise RuntimeError("Wrapping CodecContext with mismatched codec.")
self.codec = wrap_codec(codec if codec != NULL else self.ptr.codec)
self.codec = wrap_codec(codec if codec != cython.NULL else self.ptr.codec)
self.hwaccel = hwaccel
# Set reasonable threading defaults.
@ -116,7 +133,7 @@ cdef class CodecContext:
return self.ptr.flags
@flags.setter
def flags(self, int value):
def flags(self, value: cython.int):
self.ptr.flags = value
@property
@ -156,30 +173,39 @@ cdef class CodecContext:
return self.ptr.flags2
@flags2.setter
def flags2(self, int value):
def flags2(self, value: cython.int):
self.ptr.flags2 = value
@property
def extradata(self):
if self.ptr is NULL:
if self.ptr is cython.NULL:
return None
if self.ptr.extradata_size > 0:
return <bytes>(<uint8_t*>self.ptr.extradata)[:self.ptr.extradata_size]
return cython.cast(
bytes,
cython.cast(cython.pointer[uint8_t], self.ptr.extradata)[
: self.ptr.extradata_size
],
)
return None
@extradata.setter
def extradata(self, data):
if data is None:
lib.av_freep(&self.ptr.extradata)
lib.av_freep(cython.address(self.ptr.extradata))
self.ptr.extradata_size = 0
else:
source = bytesource(data)
self.ptr.extradata = <uint8_t*>lib.av_realloc(self.ptr.extradata, source.length + lib.AV_INPUT_BUFFER_PADDING_SIZE)
self.ptr.extradata = cython.cast(
cython.pointer[uint8_t],
lib.av_realloc(
self.ptr.extradata, source.length + lib.AV_INPUT_BUFFER_PADDING_SIZE
),
)
if not self.ptr.extradata:
raise MemoryError("Cannot allocate extradata")
memcpy(self.ptr.extradata, source.ptr, source.length)
self.ptr.extradata_size = source.length
self.extradata_set = True
@property
def extradata_size(self):
@ -187,23 +213,24 @@ cdef class CodecContext:
@property
def is_encoder(self):
if self.ptr is NULL:
if self.ptr is cython.NULL:
return False
return lib.av_codec_is_encoder(self.ptr.codec)
@property
def is_decoder(self):
if self.ptr is NULL:
if self.ptr is cython.NULL:
return False
return lib.av_codec_is_decoder(self.ptr.codec)
cpdef open(self, bint strict=True):
@cython.ccall
def open(self, strict: cython.bint = True):
if self.is_open:
if strict:
raise ValueError("CodecContext is already open.")
return
cdef _Dictionary options = Dictionary()
options: Dictionary = Dictionary()
options.update(self.options or {})
if not self.ptr.time_base.num and self.is_encoder:
@ -217,15 +244,17 @@ cdef class CodecContext:
self.ptr.time_base.num = 1
self.ptr.time_base.den = lib.AV_TIME_BASE
err_check(lib.avcodec_open2(self.ptr, self.codec.ptr, &options.ptr), "avcodec_open2(" + self.codec.name + ")")
err_check(
lib.avcodec_open2(self.ptr, self.codec.ptr, cython.address(options.ptr)),
f'avcodec_open2("{self.codec.name}", {self.options})',
)
self.is_open = True
self.options = dict(options)
def __dealloc__(self):
if self.ptr and self.extradata_set:
lib.av_freep(&self.ptr.extradata)
if self.ptr:
lib.avcodec_free_context(&self.ptr)
lib.av_freep(cython.address(self.ptr.extradata))
lib.avcodec_free_context(cython.address(self.ptr))
if self.parser:
lib.av_parser_close(self.parser)
@ -243,6 +272,10 @@ cdef class CodecContext:
It will return all packets that are fully contained within the given
input, and will buffer partial packets until they are complete.
Any timing information the parser is able to infer (``pts``, ``dts``,
``duration``, ``pos`` and the keyframe flag) is assigned onto the
returned packets. Fields the parser cannot determine are left unset.
:param ByteSource raw_input: A chunk of a byte-stream to process.
Anything that can be turned into a :class:`.ByteSource` is fine.
``None`` or empty inputs will flush the parser's buffers.
@ -256,27 +289,29 @@ cdef class CodecContext:
if not self.parser:
raise ValueError(f"No parser for {self.codec.name}")
cdef ByteSource source = bytesource(raw_input, allow_none=True)
source: ByteSource = bytesource(raw_input, allow_none=True)
cdef unsigned char *in_data = source.ptr if source is not None else NULL
cdef int in_size = source.length if source is not None else 0
in_data: cython.p_uchar = source.ptr if source is not None else cython.NULL
in_size: cython.int = source.length if source is not None else 0
cdef unsigned char *out_data
cdef int out_size
cdef int consumed
cdef Packet packet = None
packets = []
out_data: cython.p_uchar
out_size: cython.int
consumed: cython.int
packet: Packet = None
packets: list = []
while True:
with nogil:
with cython.nogil:
consumed = lib.av_parser_parse2(
self.parser,
self.ptr,
&out_data, &out_size,
in_data, in_size,
lib.AV_NOPTS_VALUE, lib.AV_NOPTS_VALUE,
0
cython.address(out_data),
cython.address(out_size),
in_data,
in_size,
lib.AV_NOPTS_VALUE,
lib.AV_NOPTS_VALUE,
0,
)
err_check(consumed)
@ -296,6 +331,16 @@ cdef class CodecContext:
packet = Packet(out_size)
memcpy(packet.ptr.data, out_data, out_size)
# Propagate the timing information the parser inferred for
# this frame onto the packet (mirrors FFmpeg's parse_packet).
packet.ptr.pts = self.parser.pts
packet.ptr.dts = self.parser.dts
packet.ptr.pos = self.parser.pos
if self.parser.duration:
packet.ptr.duration = self.parser.duration
if self.parser.key_frame == 1:
packet.ptr.flags |= lib.AV_PKT_FLAG_KEY
packets.append(packet)
if not in_size:
@ -317,12 +362,13 @@ cdef class CodecContext:
"""
return self.hwaccel_ctx is not None
def _send_frame_and_recv(self, Frame frame):
cdef Packet packet
cdef int res
with nogil:
res = lib.avcodec_send_frame(self.ptr, frame.ptr if frame is not None else NULL)
def _send_frame_and_recv(self, frame: Frame | None):
packet: Packet
res: cython.int
with cython.nogil:
res = lib.avcodec_send_frame(
self.ptr, frame.ptr if frame is not None else cython.NULL
)
err_check(res, "avcodec_send_frame()")
packet = self._recv_packet()
@ -330,65 +376,56 @@ cdef class CodecContext:
yield packet
packet = self._recv_packet()
cdef _send_packet_and_recv(self, Packet packet):
cdef Frame frame
cdef int res
with nogil:
res = lib.avcodec_send_packet(self.ptr, packet.ptr if packet is not None else NULL)
err_check(res, "avcodec_send_packet()")
out = []
while True:
frame = self._recv_frame()
if frame:
out.append(frame)
else:
break
return out
cdef _prepare_frames_for_encode(self, Frame frame):
@cython.cfunc
def _prepare_frames_for_encode(self, frame: Frame | None) -> list:
return [frame]
cdef Frame _alloc_next_frame(self):
@cython.cfunc
def _alloc_next_frame(self) -> Frame:
raise NotImplementedError("Base CodecContext cannot decode.")
cdef _recv_frame(self):
@cython.cfunc
def _recv_frame(self):
if not self._next_frame:
self._next_frame = self._alloc_next_frame()
cdef Frame frame = self._next_frame
cdef int res
with nogil:
frame: Frame = self._next_frame
res: cython.int
with cython.nogil:
res = lib.avcodec_receive_frame(self.ptr, frame.ptr)
if res == -EAGAIN or res == lib.AVERROR_EOF:
return
err_check(res, "avcodec_receive_frame()")
err_check(res, "avcodec_receive_frame()")
frame = self._transfer_hwframe(frame)
if not res:
self._next_frame = None
return frame
cdef _transfer_hwframe(self, Frame frame):
@cython.cfunc
def _transfer_hwframe(self, frame: Frame):
return frame
cdef _recv_packet(self):
cdef Packet packet = Packet()
@cython.cfunc
def _recv_packet(self):
packet: Packet = Packet()
res: cython.int
cdef int res
with nogil:
with cython.nogil:
res = lib.avcodec_receive_packet(self.ptr, packet.ptr)
if res == -EAGAIN or res == lib.AVERROR_EOF:
return
err_check(res, "avcodec_receive_packet()")
err_check(res, "avcodec_receive_packet()")
if not res:
return packet
cdef _prepare_and_time_rebase_frames_for_encode(self, Frame frame):
@cython.cfunc
def _prepare_and_time_rebase_frames_for_encode(self, frame: Frame):
if self.ptr.codec_type not in [lib.AVMEDIA_TYPE_VIDEO, lib.AVMEDIA_TYPE_AUDIO]:
raise NotImplementedError("Encoding is only supported for audio and video.")
@ -404,7 +441,8 @@ cdef class CodecContext:
return frames
cpdef encode(self, Frame frame=None):
@cython.ccall
def encode(self, frame: Frame | None = None):
"""Encode a list of :class:`.Packet` from the given :class:`.Frame`."""
res = []
for frame in self._prepare_and_time_rebase_frames_for_encode(frame):
@ -413,13 +451,14 @@ cdef class CodecContext:
res.append(packet)
return res
def encode_lazy(self, Frame frame=None):
def encode_lazy(self, frame: Frame | None = None):
for frame in self._prepare_and_time_rebase_frames_for_encode(frame):
for packet in self._send_frame_and_recv(frame):
self._setup_encoded_packet(packet)
yield packet
cdef _setup_encoded_packet(self, Packet packet):
@cython.cfunc
def _setup_encoded_packet(self, packet: Packet):
# We coerced the frame's time_base into the CodecContext's during encoding,
# and FFmpeg copied the frame's pts/dts to the packet, so keep track of
# this time_base in case the frame needs to be muxed to a container with
@ -429,28 +468,45 @@ cdef class CodecContext:
# are off!
packet.ptr.time_base = self.ptr.time_base
cpdef decode(self, Packet packet=None):
@cython.ccall
def decode(self, packet: Packet | None = None):
"""Decode a list of :class:`.Frame` from the given :class:`.Packet`.
If the packet is None, the buffers will be flushed. This is useful if
you do not want the library to automatically re-order frames for you
(if they are encoded with a codec that has B-frames).
"""
.. warning::
This method is **not thread-safe**. Calling :meth:`decode` concurrently
from multiple threads on the same :class:`CodecContext` will corrupt
internal FFmpeg state and likely cause a crash (segfault). FFmpeg 8.1
enforces this more strictly than earlier releases. If you need to decode
from multiple threads, give each thread its own :class:`CodecContext`.
"""
if not self.codec.ptr:
raise ValueError("cannot decode unknown codec")
self.open(strict=False)
res = []
for frame in self._send_packet_and_recv(packet):
if isinstance(frame, Frame):
self._setup_decoded_frame(frame, packet)
res.append(frame)
return res
res: cython.int
with cython.nogil:
res = lib.avcodec_send_packet(
self.ptr, packet.ptr if packet is not None else cython.NULL
)
err_check(res, "avcodec_send_packet()")
cpdef flush_buffers(self):
out: list = []
frame = self._recv_frame()
while frame:
self._setup_decoded_frame(frame, packet)
out.append(frame)
frame = self._recv_frame()
return out
@cython.ccall
def flush_buffers(self):
"""Reset the internal codec state and discard all internal buffers.
Should be called before you start decoding from a new position e.g.
@ -458,10 +514,11 @@ cdef class CodecContext:
"""
if self.is_open:
with nogil:
with cython.nogil:
lib.avcodec_flush_buffers(self.ptr)
cdef _setup_decoded_frame(self, Frame frame, Packet packet):
@cython.cfunc
def _setup_decoded_frame(self, frame: Frame, packet: Packet | None):
# Propagate our manual times.
# While decoding, frame times are in stream time_base, which PyAV
# is carrying around.
@ -485,15 +542,15 @@ cdef class CodecContext:
:type: list[str]
"""
ret = []
ret: list = []
if not self.ptr.codec or not self.codec.desc or not self.codec.desc.profiles:
return ret
# Profiles are always listed in the codec descriptor, but not necessarily in
# the codec itself. So use the descriptor here.
desc = self.codec.desc
cdef int i = 0
while desc.profiles[i].profile != lib.AV_PROFILE_UNKNOWN:
i: cython.int = 0
while desc.profiles[i].profile != lib.AV_PROFILE_UNKNOWN:
ret.append(desc.profiles[i].name)
i += 1
@ -507,8 +564,8 @@ cdef class CodecContext:
# Profiles are always listed in the codec descriptor, but not necessarily in
# the codec itself. So use the descriptor here.
desc = self.codec.desc
cdef int i = 0
while desc.profiles[i].profile != lib.AV_PROFILE_UNKNOWN:
i: cython.int = 0
while desc.profiles[i].profile != lib.AV_PROFILE_UNKNOWN:
if desc.profiles[i].profile == self.ptr.profile:
return desc.profiles[i].name
i += 1
@ -521,7 +578,7 @@ cdef class CodecContext:
# Profiles are always listed in the codec descriptor, but not necessarily in
# the codec itself. So use the descriptor here.
desc = self.codec.desc
cdef int i = 0
i: cython.int = 0
while desc.profiles[i].profile != lib.AV_PROFILE_UNKNOWN:
if desc.profiles[i].name == value:
self.ptr.profile = desc.profiles[i].profile
@ -532,33 +589,52 @@ cdef class CodecContext:
def time_base(self):
if self.is_decoder:
raise RuntimeError("Cannot access 'time_base' as a decoder")
return avrational_to_fraction(&self.ptr.time_base)
return avrational_to_fraction(cython.address(self.ptr.time_base))
@time_base.setter
def time_base(self, value):
if self.is_decoder:
raise RuntimeError("Cannot access 'time_base' as a decoder")
to_avrational(value, &self.ptr.time_base)
to_avrational(value, cython.address(self.ptr.time_base))
@property
def codec_tag(self):
return self.ptr.codec_tag.to_bytes(4, byteorder="little", signed=False).decode(
encoding="ascii")
encoding="ascii"
)
@codec_tag.setter
def codec_tag(self, value):
if isinstance(value, str) and len(value) == 4:
self.ptr.codec_tag = int.from_bytes(value.encode(encoding="ascii"),
byteorder="little", signed=False)
self.ptr.codec_tag = int.from_bytes(
value.encode(encoding="ascii"), byteorder="little", signed=False
)
else:
raise ValueError("Codec tag should be a 4 character string.")
@property
@cython.cdivision(True)
def global_quality(self):
"""Global quality for codecs which cannot change it per frame.
Stored internally in lambda units; this property converts to/from
QP units using ``FF_QP2LAMBDA``.
Wraps :ffmpeg:`AVCodecContext.global_quality`.
"""
return self.ptr.global_quality // lib.FF_QP2LAMBDA
@global_quality.setter
def global_quality(self, value: cython.int):
self.ptr.global_quality = value * lib.FF_QP2LAMBDA
@property
def bit_rate(self):
return self.ptr.bit_rate if self.ptr.bit_rate > 0 else None
@bit_rate.setter
def bit_rate(self, int value):
def bit_rate(self, value: cython.int):
self.ptr.bit_rate = value
@property
@ -573,7 +649,7 @@ cdef class CodecContext:
self.ptr.bit_rate_tolerance
@bit_rate_tolerance.setter
def bit_rate_tolerance(self, int value):
def bit_rate_tolerance(self, value: cython.int):
self.ptr.bit_rate_tolerance = value
@property
@ -586,7 +662,7 @@ cdef class CodecContext:
return self.ptr.thread_count
@thread_count.setter
def thread_count(self, int value):
def thread_count(self, value: cython.int):
if self.is_open:
raise RuntimeError("Cannot change thread_count after codec is open.")
self.ptr.thread_count = value

View file

@ -62,6 +62,7 @@ class CodecContext:
extradata: bytes | None
time_base: Fraction
codec_tag: str
global_quality: int
bit_rate: int | None
bit_rate_tolerance: int
thread_count: int

Binary file not shown.

View file

@ -5,10 +5,10 @@ from av.codec.codec cimport Codec
cdef class HWConfig:
cdef object __weakref__
cdef lib.AVCodecHWConfig *ptr
cdef void _init(self, lib.AVCodecHWConfig *ptr)
cdef const lib.AVCodecHWConfig *ptr
cdef void _init(self, const lib.AVCodecHWConfig *ptr)
cdef HWConfig wrap_hwconfig(lib.AVCodecHWConfig *ptr)
cdef HWConfig wrap_hwconfig(const lib.AVCodecHWConfig *ptr)
cdef class HWAccel:
cdef int _device_type
@ -16,6 +16,8 @@ cdef class HWAccel:
cdef readonly Codec codec
cdef readonly HWConfig config
cdef lib.AVBufferRef *ptr
cdef readonly int device_id
cdef readonly bint is_hw_owned
cdef public bint allow_software_fallback
cdef public dict options
cdef public int flags

View file

@ -1,14 +1,12 @@
import weakref
from enum import IntEnum
cimport libav as lib
from av.codec.codec cimport Codec
from av.dictionary cimport _Dictionary
from av.error cimport err_check
from av.video.format cimport get_video_format
from av.dictionary import Dictionary
import cython
import cython.cimports.libav as lib
from cython.cimports.av.codec.codec import Codec
from cython.cimports.av.dictionary import Dictionary
from cython.cimports.av.error import err_check
from cython.cimports.av.video.format import get_video_format
class HWDeviceType(IntEnum):
@ -29,34 +27,44 @@ class HWDeviceType(IntEnum):
ohcodec = 14
# TODO: When ffmpeg major is changed, check this enum.
class HWConfigMethod(IntEnum):
none = 0
hw_device_ctx = lib.AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX # This is the only one we support.
hw_device_ctx = (
lib.AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX
) # This is the only one we support.
hw_frame_ctx = lib.AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX
internal = lib.AV_CODEC_HW_CONFIG_METHOD_INTERNAL
ad_hoc = lib.AV_CODEC_HW_CONFIG_METHOD_AD_HOC
cdef object _cinit_sentinel = object()
cdef object _singletons = weakref.WeakValueDictionary()
_cinit_sentinel = cython.declare(object, object())
_singletons = cython.declare(object, weakref.WeakValueDictionary())
cdef HWConfig wrap_hwconfig(lib.AVCodecHWConfig *ptr):
@cython.cfunc
def wrap_hwconfig(ptr: cython.pointer[cython.const[lib.AVCodecHWConfig]]) -> HWConfig:
try:
return _singletons[<int>ptr]
return _singletons[cython.cast(cython.Py_ssize_t, ptr)]
except KeyError:
pass
cdef HWConfig config = HWConfig(_cinit_sentinel)
config: HWConfig = HWConfig(_cinit_sentinel)
config._init(ptr)
_singletons[<int>ptr] = config
_singletons[cython.cast(cython.Py_ssize_t, ptr)] = config
return config
cdef class HWConfig:
@cython.final
@cython.cclass
class HWConfig:
def __init__(self, sentinel):
if sentinel is not _cinit_sentinel:
raise RuntimeError("Cannot instantiate CodecContext")
cdef void _init(self, lib.AVCodecHWConfig *ptr):
@cython.cfunc
def _init(
self, ptr: cython.pointer[cython.const[lib.AVCodecHWConfig]]
) -> cython.void:
self.ptr = ptr
def __repr__(self):
@ -64,7 +72,7 @@ cdef class HWConfig:
f"<av.{self.__class__.__name__} "
f"device_type={lib.av_hwdevice_get_type_name(self.device_type)} "
f"format={self.format.name if self.format else None} "
f"is_supported={self.is_supported} at 0x{<int>self.ptr:x}>"
f"is_supported={self.is_supported} at 0x{cython.cast(cython.Py_ssize_t, self.ptr):x}>"
)
@property
@ -84,21 +92,30 @@ cdef class HWConfig:
return bool(self.ptr.methods & lib.AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)
cpdef hwdevices_available():
result = []
cdef lib.AVHWDeviceType x = lib.AV_HWDEVICE_TYPE_NONE
@cython.ccall
def hwdevices_available():
result: list = []
x: lib.AVHWDeviceType = lib.AV_HWDEVICE_TYPE_NONE
while True:
x = lib.av_hwdevice_iterate_types(x)
if x == lib.AV_HWDEVICE_TYPE_NONE:
break
result.append(lib.av_hwdevice_get_type_name(HWDeviceType(x)))
return result
cdef class HWAccel:
def __init__(self, device_type, device=None, allow_software_fallback=True, options=None, flags=None):
@cython.final
@cython.cclass
class HWAccel:
def __init__(
self,
device_type,
device=None,
allow_software_fallback=True,
options=None,
flags=None,
is_hw_owned=False,
):
if isinstance(device_type, HWDeviceType):
self._device_type = device_type
elif isinstance(device_type, str):
@ -108,39 +125,50 @@ cdef class HWAccel:
else:
raise ValueError("Unknown type for device_type")
self._device = device
self.is_hw_owned = is_hw_owned
self.device_id = 0
if self._device_type == HWDeviceType.cuda and device:
self.device_id = int(device)
self._device = None if device is None else f"{device}"
self.allow_software_fallback = allow_software_fallback
self.options = {} if not options else dict(options)
if self._device_type == HWDeviceType.cuda and self.is_hw_owned:
self.options.setdefault("primary_ctx", "1")
self.flags = 0 if not flags else flags
self.ptr = NULL
self.ptr = cython.NULL
self.config = None
def _initialize_hw_context(self, Codec codec not None):
cdef HWConfig config
def _initialize_hw_context(self, codec: Codec):
config: HWConfig
for config in codec.hardware_configs:
if not (config.ptr.methods & lib.AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX):
continue
if self._device_type and config.device_type != self._device_type:
continue
break
else:
else: # nobreak
raise NotImplementedError(f"No supported hardware config for {codec}")
self.config = config
cdef char *c_device = NULL
c_device: cython.p_char = cython.NULL
if self._device:
device_bytes = self._device.encode()
c_device = device_bytes
cdef _Dictionary c_options = Dictionary(self.options)
c_options: Dictionary = Dictionary(self.options)
err_check(
lib.av_hwdevice_ctx_create(
&self.ptr, config.ptr.device_type, c_device, c_options.ptr, self.flags
cython.address(self.ptr),
config.ptr.device_type,
c_device,
c_options.ptr,
self.flags,
)
)
def create(self, Codec codec not None):
def create(self, codec: Codec) -> HWAccel:
"""Create a new hardware accelerator context with the given codec"""
if self.ptr:
raise RuntimeError("Hardware context already initialized")
@ -149,11 +177,12 @@ cdef class HWAccel:
device_type=self._device_type,
device=self._device,
allow_software_fallback=self.allow_software_fallback,
options=self.options
options=self.options,
is_hw_owned=self.is_hw_owned,
)
ret._initialize_hw_context(codec)
return ret
def __dealloc__(self):
if self.ptr:
lib.av_buffer_unref(&self.ptr)
lib.av_buffer_unref(cython.address(self.ptr))

View file

@ -37,13 +37,20 @@ class HWConfig:
def is_supported(self) -> bool: ...
class HWAccel:
options: dict[str, object]
@property
def is_hw_owned(self) -> bool: ...
@property
def device_id(self) -> int: ...
def __init__(
self,
device_type: str | HWDeviceType,
device: str | None = None,
device: str | int | None = None,
allow_software_fallback: bool = False,
options: dict[str, object] | None = None,
flags: int | None = None,
is_hw_owned: bool = False,
) -> None: ...
def create(self, codec: Codec) -> HWAccel: ...