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

@ -17,8 +17,8 @@ from av.audio.stream import AudioStream
from av.bitstream import BitStreamFilterContext, bitstream_filters_available
from av.codec.codec import Codec, codecs_available
from av.codec.context import CodecContext
from av.codec.hwaccel import HWConfig
from av.container import open
from av.device import DeviceInfo, enumerate_input_devices, enumerate_output_devices
from av.format import ContainerFormat, formats_available
from av.packet import Packet
from av.error import * # noqa: F403; This is limited to exception types.
@ -45,6 +45,9 @@ __all__ = (
"codecs_available",
"CodecContext",
"open",
"DeviceInfo",
"enumerate_input_devices",
"enumerate_output_devices",
"ContainerFormat",
"formats_available",
"Packet",

Binary file not shown.

View file

@ -0,0 +1,9 @@
cdef extern from "libswscale/swscale.h" nogil:
cdef int swscale_version()
cdef char* swscale_configuration()
cdef char* swscale_license()
cdef extern from "libswresample/swresample.h" nogil:
cdef int swresample_version()
cdef char* swresample_configuration()
cdef char* swresample_license()

View file

@ -1,23 +1,23 @@
cimport libav as lib
import cython
import cython.cimports.libav as lib
# Initialise libraries.
lib.avformat_network_init()
lib.avdevice_register_all()
# Exports.
time_base = lib.AV_TIME_BASE
cdef decode_version(v):
@cython.cfunc
def decode_version(v):
if v < 0:
return (-1, -1, -1)
cdef int major = (v >> 16) & 0xff
cdef int minor = (v >> 8) & 0xff
cdef int micro = (v) & 0xff
major: cython.int = (v >> 16) & 0xFF
minor: cython.int = (v >> 8) & 0xFF
micro: cython.int = (v) & 0xFF
return (major, minor, micro)
# Return an informative version string.
# This usually is the actual release version number or a git commit
# description. This string has no fixed format and can change any time. It
@ -28,37 +28,37 @@ library_meta = {
"libavutil": dict(
version=decode_version(lib.avutil_version()),
configuration=lib.avutil_configuration(),
license=lib.avutil_license()
license=lib.avutil_license(),
),
"libavcodec": dict(
version=decode_version(lib.avcodec_version()),
configuration=lib.avcodec_configuration(),
license=lib.avcodec_license()
license=lib.avcodec_license(),
),
"libavformat": dict(
version=decode_version(lib.avformat_version()),
configuration=lib.avformat_configuration(),
license=lib.avformat_license()
license=lib.avformat_license(),
),
"libavdevice": dict(
version=decode_version(lib.avdevice_version()),
configuration=lib.avdevice_configuration(),
license=lib.avdevice_license()
license=lib.avdevice_license(),
),
"libavfilter": dict(
version=decode_version(lib.avfilter_version()),
configuration=lib.avfilter_configuration(),
license=lib.avfilter_license()
license=lib.avfilter_license(),
),
"libswscale": dict(
version=decode_version(lib.swscale_version()),
configuration=lib.swscale_configuration(),
license=lib.swscale_license()
version=decode_version(swscale_version()),
configuration=swscale_configuration(),
license=swscale_license(),
),
"libswresample": dict(
version=decode_version(lib.swresample_version()),
configuration=lib.swresample_configuration(),
license=lib.swresample_license()
version=decode_version(swresample_version()),
configuration=swresample_configuration(),
license=swresample_license(),
),
}

View file

@ -1 +1 @@
__version__ = "16.1.0"
__version__ = "17.1.0"

View file

@ -1,2 +1,2 @@
from .frame import AudioFrame
from .stream import AudioStream
from .frame import AudioFrame as AudioFrame
from .stream import AudioStream as AudioStream

View file

@ -7,10 +7,11 @@ from cython.cimports.av.frame import Frame
from cython.cimports.av.packet import Packet
@cython.final
@cython.cclass
class AudioCodecContext(CodecContext):
@cython.cfunc
def _prepare_frames_for_encode(self, input_frame: Frame | None):
def _prepare_frames_for_encode(self, input_frame: Frame | None) -> list:
frame: AudioFrame | None = input_frame
allow_var_frame_size: cython.bint = (
self.ptr.codec.capabilities & lib.AV_CODEC_CAP_VARIABLE_FRAME_SIZE

Binary file not shown.

View file

@ -1,8 +1,11 @@
from av.audio.frame cimport alloc_audio_frame
from av.error cimport err_check
import cython
from cython.cimports.av.audio.frame import alloc_audio_frame
from cython.cimports.av.error import err_check
cdef class AudioFifo:
@cython.final
@cython.cclass
class AudioFifo:
"""A simple audio sample FIFO (First In First Out) buffer."""
def __repr__(self):
@ -22,7 +25,8 @@ cdef class AudioFifo:
if self.ptr:
lib.av_audio_fifo_free(self.ptr)
cpdef write(self, AudioFrame frame):
@cython.ccall
def write(self, frame: AudioFrame | None):
"""write(frame)
Push a frame of samples into the queue.
@ -45,7 +49,6 @@ cdef class AudioFifo:
return
if not self.ptr:
# Hold onto a copy of the attributes of the first frame to populate
# output frames with.
self.template = alloc_audio_frame()
@ -60,9 +63,10 @@ cdef class AudioFifo:
self.pts_per_sample = 0
self.ptr = lib.av_audio_fifo_alloc(
<lib.AVSampleFormat>frame.ptr.format,
cython.cast(lib.AVSampleFormat, frame.ptr.format),
frame.layout.nb_channels,
frame.ptr.nb_samples * 2, # Just a default number of samples; it will adjust.
frame.ptr.nb_samples
* 2, # Just a default number of samples; it will adjust.
)
if not self.ptr:
@ -70,34 +74,43 @@ cdef class AudioFifo:
# Make sure nothing changed.
elif (
frame.ptr.format != self.template.ptr.format or
# TODO: frame.ptr.ch_layout != self.template.ptr.ch_layout or
frame.ptr.sample_rate != self.template.ptr.sample_rate or
(frame._time_base.num and self.template._time_base.num and (
frame._time_base.num != self.template._time_base.num or
frame._time_base.den != self.template._time_base.den
))
frame.ptr.format != self.template.ptr.format
or frame.ptr.sample_rate != self.template.ptr.sample_rate
or (
frame._time_base.num
and self.template._time_base.num
and (
frame._time_base.num != self.template._time_base.num
or frame._time_base.den != self.template._time_base.den
)
)
):
raise ValueError("Frame does not match AudioFifo parameters.")
# Assert that the PTS are what we expect.
cdef int64_t expected_pts
expected_pts = cython.declare(int64_t)
if self.pts_per_sample and frame.ptr.pts != lib.AV_NOPTS_VALUE:
expected_pts = <int64_t>(self.pts_per_sample * self.samples_written)
expected_pts = cython.cast(
int64_t, self.pts_per_sample * self.samples_written
)
if frame.ptr.pts != expected_pts:
raise ValueError(
"Frame.pts (%d) != expected (%d); fix or set to None." % (frame.ptr.pts, expected_pts)
"Frame.pts (%d) != expected (%d); fix or set to None."
% (frame.ptr.pts, expected_pts)
)
err_check(lib.av_audio_fifo_write(
self.ptr,
<void **>frame.ptr.extended_data,
frame.ptr.nb_samples,
))
err_check(
lib.av_audio_fifo_write(
self.ptr,
cython.cast(cython.pointer[cython.p_void], frame.ptr.extended_data),
frame.ptr.nb_samples,
)
)
self.samples_written += frame.ptr.nb_samples
cpdef read(self, int samples=0, bint partial=False):
@cython.ccall
def read(self, samples: cython.int = 0, partial: cython.bint = False):
"""read(samples=0, partial=False)
Read samples from the queue.
@ -115,7 +128,7 @@ cdef class AudioFifo:
if not self.ptr:
return
cdef int buffered_samples = lib.av_audio_fifo_size(self.ptr)
buffered_samples: cython.int = lib.av_audio_fifo_size(self.ptr)
if buffered_samples < 1:
return
@ -127,31 +140,35 @@ cdef class AudioFifo:
else:
return
cdef AudioFrame frame = alloc_audio_frame()
frame: AudioFrame = alloc_audio_frame()
frame._copy_internal_attributes(self.template)
frame._init(
<lib.AVSampleFormat>self.template.ptr.format,
<lib.AVChannelLayout>self.template.ptr.ch_layout,
cython.cast(lib.AVSampleFormat, self.template.ptr.format),
cython.cast(lib.AVChannelLayout, self.template.ptr.ch_layout),
samples,
1, # Align?
)
err_check(lib.av_audio_fifo_read(
self.ptr,
<void **>frame.ptr.extended_data,
samples,
))
err_check(
lib.av_audio_fifo_read(
self.ptr,
cython.cast(cython.pointer[cython.p_void], frame.ptr.extended_data),
samples,
)
)
if self.pts_per_sample:
frame.ptr.pts = <uint64_t>(self.pts_per_sample * self.samples_read)
frame.ptr.pts = cython.cast(
uint64_t, self.pts_per_sample * self.samples_read
)
else:
frame.ptr.pts = lib.AV_NOPTS_VALUE
self.samples_read += samples
return frame
cpdef read_many(self, int samples, bint partial=False):
@cython.ccall
def read_many(self, samples: cython.int, partial: cython.bint = False):
"""read_many(samples, partial=False)
Read as many frames as we can.
@ -162,8 +179,8 @@ cdef class AudioFifo:
"""
cdef AudioFrame frame
frames = []
frame: AudioFrame
frames: list = []
while True:
frame = self.read(samples, partial=partial)
if frame is not None:
@ -177,18 +194,26 @@ cdef class AudioFifo:
def format(self):
"""The :class:`.AudioFormat` of this FIFO."""
if not self.ptr:
raise AttributeError(f"'{__name__}.AudioFifo' object has no attribute 'format'")
raise AttributeError(
f"'{__name__}.AudioFifo' object has no attribute 'format'"
)
return self.template.format
@property
def layout(self):
"""The :class:`.AudioLayout` of this FIFO."""
if not self.ptr:
raise AttributeError(f"'{__name__}.AudioFifo' object has no attribute 'layout'")
raise AttributeError(
f"'{__name__}.AudioFifo' object has no attribute 'layout'"
)
return self.template.layout
@property
def sample_rate(self):
if not self.ptr:
raise AttributeError(f"'{__name__}.AudioFifo' object has no attribute 'sample_rate'")
raise AttributeError(
f"'{__name__}.AudioFifo' object has no attribute 'sample_rate'"
)
return self.template.sample_rate
@property

Binary file not shown.

View file

@ -2,8 +2,10 @@ import sys
import cython
container_format_postfix: str = "le" if sys.byteorder == "little" else "be"
_cinit_bypass_sentinel = object()
container_format_postfix = cython.declare(
str, "le" if sys.byteorder == "little" else "be"
)
_cinit_bypass_sentinel = cython.declare(object, object())
@cython.cfunc
@ -18,6 +20,7 @@ def get_audio_format(c_format: lib.AVSampleFormat) -> AudioFormat:
return format
@cython.final
@cython.cclass
class AudioFormat:
"""Descriptor of audio formats."""

Binary file not shown.

View file

@ -5,7 +5,7 @@ from cython.cimports.av.audio.plane import AudioPlane
from cython.cimports.av.error import err_check
from cython.cimports.av.utils import check_ndarray
_cinit_bypass_sentinel = object()
_cinit_bypass_sentinel = cython.declare(object, object())
@cython.cfunc
@ -27,6 +27,7 @@ format_dtypes = {
}
@cython.final
@cython.cclass
class AudioFrame(Frame):
"""A frame of audio."""

Binary file not shown.

View file

@ -3,6 +3,5 @@ cimport libav as lib
cdef class AudioLayout:
cdef lib.AVChannelLayout layout
cdef _init(self, lib.AVChannelLayout layout)
cdef AudioLayout get_audio_layout(lib.AVChannelLayout c_layout)

View file

@ -0,0 +1,109 @@
from dataclasses import dataclass
import cython
from cython.cimports import libav as lib
from cython.cimports.cpython.bytes import PyBytes_FromStringAndSize
@dataclass
class AudioChannel:
name: str
description: str
def __repr__(self):
return f"<av.AudioChannel '{self.name}' ({self.description})>"
_cinit_bypass_sentinel = cython.declare(object, object())
@cython.cfunc
def get_audio_layout(c_layout: lib.AVChannelLayout) -> AudioLayout:
"""Get an AudioLayout from Cython land."""
layout: AudioLayout = AudioLayout(_cinit_bypass_sentinel)
layout.layout = c_layout
return layout
@cython.final
@cython.cclass
class AudioLayout:
def __dealloc__(self):
lib.av_channel_layout_uninit(cython.address(self.layout))
def __cinit__(self, layout):
if layout is _cinit_bypass_sentinel:
return
if type(layout) is str:
ret = lib.av_channel_layout_from_string(cython.address(c_layout), layout)
if ret != 0:
raise ValueError(f"Invalid layout: {layout}")
elif isinstance(layout, AudioLayout):
c_layout = cython.cast(AudioLayout, layout).layout
else:
raise TypeError(
f"layout must be of type: string | av.AudioLayout, got {type(layout)}"
)
self.layout = c_layout
def __repr__(self):
return f"<av.{self.__class__.__name__} {self.name!r}>"
def __eq__(self, other):
if not isinstance(other, AudioLayout):
return False
c_other: lib.AVChannelLayout = cython.cast(AudioLayout, other).layout
return (
lib.av_channel_layout_compare(
cython.address(self.layout), cython.address(c_other)
)
== 0
)
@property
def nb_channels(self):
return self.layout.nb_channels
@property
def channels(self):
buf: cython.char[16]
buf2: cython.char[128]
results: list = []
for index in range(self.layout.nb_channels):
size = lib.av_channel_name(
buf,
cython.sizeof(buf),
lib.av_channel_layout_channel_from_index(
cython.address(self.layout), index
),
)
size2 = lib.av_channel_description(
buf2,
cython.sizeof(buf2),
lib.av_channel_layout_channel_from_index(
cython.address(self.layout), index
),
)
results.append(
AudioChannel(
PyBytes_FromStringAndSize(buf, size - 1).decode("utf-8"),
PyBytes_FromStringAndSize(buf2, size2 - 1).decode("utf-8"),
)
)
return tuple(results)
@property
def name(self) -> str:
"""The canonical name of the audio layout."""
layout_name: cython.char[129]
ret: cython.int = lib.av_channel_layout_describe(
cython.address(self.layout), layout_name, cython.sizeof(layout_name)
)
if ret < 0:
raise RuntimeError(f"Failed to get layout name: {ret}")
return layout_name

View file

@ -1,82 +0,0 @@
cimport libav as lib
from cpython.bytes cimport PyBytes_FromStringAndSize
from dataclasses import dataclass
@dataclass
class AudioChannel:
name: str
description: str
def __repr__(self):
return f"<av.AudioChannel '{self.name}' ({self.description})>"
cdef object _cinit_bypass_sentinel
cdef AudioLayout get_audio_layout(lib.AVChannelLayout c_layout):
"""Get an AudioLayout from Cython land."""
cdef AudioLayout layout = AudioLayout.__new__(AudioLayout, _cinit_bypass_sentinel)
layout._init(c_layout)
return layout
cdef class AudioLayout:
def __init__(self, layout):
if layout is _cinit_bypass_sentinel:
return
if type(layout) is str:
ret = lib.av_channel_layout_from_string(&c_layout, layout)
if ret != 0:
raise ValueError(f"Invalid layout: {layout}")
elif isinstance(layout, AudioLayout):
c_layout = (<AudioLayout>layout).layout
else:
raise TypeError(f"layout must be of type: string | av.AudioLayout, got {type(layout)}")
self._init(c_layout)
cdef _init(self, lib.AVChannelLayout layout):
self.layout = layout
def __repr__(self):
return f"<av.{self.__class__.__name__} {self.name!r}>"
def __eq__(self, other):
return isinstance(other, AudioLayout) and self.name == other.name and self.nb_channels == other.nb_channels
@property
def nb_channels(self):
return self.layout.nb_channels
@property
def channels(self):
cdef char buf[16]
cdef char buf2[128]
results = []
for index in range(self.layout.nb_channels):
size = lib.av_channel_name(buf, sizeof(buf), lib.av_channel_layout_channel_from_index(&self.layout, index)) - 1
size2 = lib.av_channel_description(buf2, sizeof(buf2), lib.av_channel_layout_channel_from_index(&self.layout, index)) - 1
results.append(
AudioChannel(
PyBytes_FromStringAndSize(buf, size).decode("utf-8"),
PyBytes_FromStringAndSize(buf2, size2).decode("utf-8"),
)
)
return tuple(results)
@property
def name(self) -> str:
"""The canonical name of the audio layout."""
cdef char layout_name[128]
cdef int ret
ret = lib.av_channel_layout_describe(&self.layout, layout_name, sizeof(layout_name))
if ret < 0:
raise RuntimeError(f"Failed to get layout name: {ret}")
return layout_name

Binary file not shown.

View file

@ -2,6 +2,7 @@ import cython
from cython.cimports.av.audio.frame import AudioFrame
@cython.final
@cython.cclass
class AudioPlane(Plane):
def __cinit__(self, frame: AudioFrame, index: cython.int):

Binary file not shown.

View file

@ -1,12 +1,12 @@
from errno import EAGAIN
import cython
from cython.cimports.av.filter.context import FilterContext
from cython.cimports.av.filter.graph import Graph
from av.error import FFmpegError
@cython.final
@cython.cclass
class AudioResampler:
"""AudioResampler(format=None, layout=None, rate=None)

Binary file not shown.

View file

@ -3,9 +3,12 @@ from cython.cimports.av.audio.frame import AudioFrame
from cython.cimports.av.packet import Packet
@cython.final
@cython.cclass
class AudioStream(Stream):
def __repr__(self):
if self.codec_context is None:
return f"<av.AudioStream #{self.index} audio/<nocodec> at 0x{id(self):x}>"
form = self.format.name if self.format else None
return (
f"<av.AudioStream #{self.index} {self.name} at {self.rate}Hz,"
@ -13,6 +16,10 @@ class AudioStream(Stream):
)
def __getattr__(self, name):
if self.codec_context is None:
raise AttributeError(
f"'{type(self).__name__}' object has no attribute '{name}'"
)
return getattr(self.codec_context, name)
@cython.ccall

Binary file not shown.

View file

@ -0,0 +1,116 @@
import cython
import cython.cimports.libav as lib
from cython.cimports.av.error import err_check
from cython.cimports.av.packet import Packet
from cython.cimports.av.stream import Stream
from cython.cimports.libc.errno import EAGAIN
@cython.final
@cython.cclass
class BitStreamFilterContext:
"""
Initializes a bitstream filter: a way to directly modify packet data.
Wraps :ffmpeg:`AVBSFContext`
:param Stream in_stream: A stream that defines the input codec for the bitfilter.
:param Stream out_stream: A stream whose codec is overwritten using the output parameters from the bitfilter.
"""
def __cinit__(
self,
filter_description,
in_stream: Stream | None = None,
out_stream: Stream | None = None,
):
res: cython.int
filter_str: cython.p_char = filter_description
with cython.nogil:
res = lib.av_bsf_list_parse_str(filter_str, cython.address(self.ptr))
err_check(res)
if in_stream is not None:
with cython.nogil:
res = lib.avcodec_parameters_copy(
self.ptr.par_in, in_stream.ptr.codecpar
)
err_check(res)
with cython.nogil:
res = lib.av_bsf_init(self.ptr)
err_check(res)
if out_stream is not None:
with cython.nogil:
res = lib.avcodec_parameters_copy(
out_stream.ptr.codecpar, self.ptr.par_out
)
err_check(res)
lib.avcodec_parameters_to_context(
out_stream.codec_context.ptr, out_stream.ptr.codecpar
)
def __dealloc__(self):
if self.ptr:
lib.av_bsf_free(cython.address(self.ptr))
@cython.ccall
def filter(self, packet: Packet | None = None):
"""
Processes a packet based on the filter_description set during initialization.
Multiple packets may be created.
:type: list[Packet]
"""
res: cython.int
new_packet: Packet
with cython.nogil:
res = lib.av_bsf_send_packet(
self.ptr, packet.ptr if packet is not None else cython.NULL
)
err_check(res)
output: list = []
while True:
new_packet = Packet()
with cython.nogil:
res = lib.av_bsf_receive_packet(self.ptr, new_packet.ptr)
if res == -EAGAIN or res == lib.AVERROR_EOF:
return output
err_check(res)
if res:
return output
output.append(new_packet)
@cython.ccall
def flush(self):
"""
Reset the internal state of the filter.
Should be called e.g. when seeking.
Can be used to make the filter usable again after draining it with EOF marker packet.
"""
lib.av_bsf_flush(self.ptr)
@cython.cfunc
def get_filter_names() -> set:
names: set = set()
ptr: cython.pointer[cython.const[lib.AVBitStreamFilter]]
opaque: cython.p_void = cython.NULL
while True:
ptr = lib.av_bsf_iterate(cython.address(opaque))
if ptr:
names.add(ptr.name)
else:
break
return names
bitstream_filters_available = get_filter_names()

View file

@ -1,95 +0,0 @@
cimport libav as lib
from libc.errno cimport EAGAIN
from av.error cimport err_check
from av.packet cimport Packet
from av.stream cimport Stream
cdef class BitStreamFilterContext:
"""
Initializes a bitstream filter: a way to directly modify packet data.
Wraps :ffmpeg:`AVBSFContext`
:param Stream in_stream: A stream that defines the input codec for the bitfilter.
:param Stream out_stream: A stream whose codec is overwritten using the output parameters from the bitfilter.
"""
def __cinit__(self, filter_description, Stream in_stream=None, Stream out_stream=None):
cdef int res
cdef char *filter_str = filter_description
with nogil:
res = lib.av_bsf_list_parse_str(filter_str, &self.ptr)
err_check(res)
if in_stream is not None:
with nogil:
res = lib.avcodec_parameters_copy(self.ptr.par_in, in_stream.ptr.codecpar)
err_check(res)
with nogil:
res = lib.av_bsf_init(self.ptr)
err_check(res)
if out_stream is not None:
with nogil:
res = lib.avcodec_parameters_copy(out_stream.ptr.codecpar, self.ptr.par_out)
err_check(res)
lib.avcodec_parameters_to_context(out_stream.codec_context.ptr, out_stream.ptr.codecpar)
def __dealloc__(self):
if self.ptr:
lib.av_bsf_free(&self.ptr)
cpdef filter(self, Packet packet=None):
"""
Processes a packet based on the filter_description set during initialization.
Multiple packets may be created.
:type: list[Packet]
"""
cdef int res
cdef Packet new_packet
with nogil:
res = lib.av_bsf_send_packet(self.ptr, packet.ptr if packet is not None else NULL)
err_check(res)
output = []
while True:
new_packet = Packet()
with nogil:
res = lib.av_bsf_receive_packet(self.ptr, new_packet.ptr)
if res == -EAGAIN or res == lib.AVERROR_EOF:
return output
err_check(res)
if res:
return output
output.append(new_packet)
cpdef flush(self):
"""
Reset the internal state of the filter.
Should be called e.g. when seeking.
Can be used to make the filter usable again after draining it with EOF marker packet.
"""
lib.av_bsf_flush(self.ptr)
cdef get_filter_names():
names = set()
cdef const lib.AVBitStreamFilter *ptr
cdef void *opaque = NULL
while True:
ptr = lib.av_bsf_iterate(&opaque)
if ptr:
names.add(ptr.name)
else:
break
return names
bitstream_filters_available = get_filter_names()

Binary file not shown.

View file

@ -1,6 +1,16 @@
from cpython.buffer cimport Py_buffer
cdef class ByteSource:
cdef object owner
cdef bint has_view
cdef Py_buffer view
cdef unsigned char *ptr
cdef size_t length
cdef ByteSource bytesource(object, bint allow_none=*)
cdef class Buffer:
cdef size_t _buffer_size(self)
cdef void* _buffer_ptr(self)
cdef bint _buffer_writable(self)

View file

@ -0,0 +1,100 @@
import cython
from cython.cimports.cpython import PyBUF_WRITABLE, PyBuffer_FillInfo
from cython.cimports.cpython.buffer import (
PyBUF_SIMPLE,
PyBuffer_Release,
PyObject_CheckBuffer,
PyObject_GetBuffer,
)
from cython.cimports.libc.string import memcpy
@cython.final
@cython.cclass
class ByteSource:
def __cinit__(self, owner):
self.owner = owner
try:
self.ptr = owner
except TypeError:
pass
else:
self.length = len(owner)
return
if PyObject_CheckBuffer(owner):
# Can very likely use PyBUF_ND instead of PyBUF_SIMPLE
res = PyObject_GetBuffer(owner, cython.address(self.view), PyBUF_SIMPLE)
if not res:
self.has_view = True
self.ptr = cython.cast(cython.p_uchar, self.view.buf)
self.length = self.view.len
return
raise TypeError("expected bytes, bytearray or memoryview")
def __dealloc__(self):
if self.has_view:
PyBuffer_Release(cython.address(self.view))
@cython.cfunc
def bytesource(obj, allow_none: cython.bint = False) -> ByteSource | None:
if allow_none and obj is None:
return None
elif isinstance(obj, ByteSource):
return obj
else:
return ByteSource(obj)
@cython.cclass
class Buffer:
"""A base class for PyAV objects which support the buffer protocol, such
as :class:`.Packet` and :class:`.Plane`.
"""
@cython.cfunc
def _buffer_size(self) -> cython.size_t:
return 0
def _buffer_ptr(self) -> cython.p_void:
return cython.NULL
def _buffer_writable(self) -> cython.bint:
return True
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")
PyBuffer_FillInfo(view, self, self._buffer_ptr(), self._buffer_size(), 0, flags)
@property
def buffer_size(self):
return self._buffer_size()
@property
def buffer_ptr(self):
"""The memory address of the buffer."""
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..
"""
if not self._buffer_writable():
raise ValueError("buffer is not writable")
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")
memcpy(self._buffer_ptr(), source.ptr, size)

View file

@ -1,54 +0,0 @@
from cpython cimport PyBUF_WRITABLE, PyBuffer_FillInfo
from libc.string cimport memcpy
from av.bytesource cimport ByteSource, bytesource
cdef 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):
return 0
cdef void* _buffer_ptr(self):
return NULL
cdef bint _buffer_writable(self):
return True
def __getbuffer__(self, Py_buffer *view, int flags):
if flags & PyBUF_WRITABLE and not self._buffer_writable():
raise ValueError("buffer is not writable")
PyBuffer_FillInfo(view, self, self._buffer_ptr(), self._buffer_size(), 0, flags)
@property
def buffer_size(self):
"""The size of the buffer in bytes."""
return self._buffer_size()
@property
def buffer_ptr(self):
"""The memory address of the buffer."""
return <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..
"""
if not self._buffer_writable():
raise ValueError("buffer is not writable")
cdef ByteSource source = bytesource(input)
cdef size_t size = self._buffer_size()
if source.length != size:
raise ValueError(f"got {source.length} bytes; need {size} bytes")
memcpy(self._buffer_ptr(), source.ptr, size)

View file

@ -1,14 +0,0 @@
from cpython.buffer cimport Py_buffer
cdef class ByteSource:
cdef object owner
cdef bint has_view
cdef Py_buffer view
cdef unsigned char *ptr
cdef size_t length
cdef ByteSource bytesource(object, bint allow_none=*)

View file

@ -1,43 +0,0 @@
from cpython.buffer cimport (
PyBUF_SIMPLE,
PyBuffer_Release,
PyObject_CheckBuffer,
PyObject_GetBuffer,
)
cdef class ByteSource:
def __cinit__(self, owner):
self.owner = owner
try:
self.ptr = owner
except TypeError:
pass
else:
self.length = len(owner)
return
if PyObject_CheckBuffer(owner):
# Can very likely use PyBUF_ND instead of PyBUF_SIMPLE
res = PyObject_GetBuffer(owner, &self.view, PyBUF_SIMPLE)
if not res:
self.has_view = True
self.ptr = <unsigned char *>self.view.buf
self.length = self.view.len
return
raise TypeError("expected bytes, bytearray or memoryview")
def __dealloc__(self):
if self.has_view:
PyBuffer_Release(&self.view)
cdef ByteSource bytesource(obj, bint allow_none=False):
if allow_none and obj is None:
return
elif isinstance(obj, ByteSource):
return obj
else:
return ByteSource(obj)

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: ...

View file

@ -1,3 +1,3 @@
from .core import Container, Flags, open
from .input import InputContainer
from .output import OutputContainer
from .input import InputContainer as InputContainer
from .output import OutputContainer as OutputContainer

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more