add read me

This commit is contained in:
2026-01-09 10:28:44 +11:00
commit edaf914b73
13417 changed files with 2952119 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
from .codec import Capabilities, Codec, Properties, codec_descriptor, codecs_available
from .context import CodecContext
__all__ = (
"Capabilities",
"Codec",
"Properties",
"codec_descriptor",
"codecs_available",
"CodecContext",
)

View File

@@ -0,0 +1,15 @@
cimport libav as lib
cdef class Codec:
cdef const lib.AVCodec *ptr
cdef const lib.AVCodecDescriptor *desc
cdef readonly bint is_encoder
cdef tuple _hardware_configs
cdef _init(self, name=?)
cdef Codec wrap_codec(const lib.AVCodec *ptr)

View File

@@ -0,0 +1,115 @@
from enum import Flag, IntEnum
from fractions import Fraction
from typing import ClassVar, Literal, 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
from .context import CodecContext
class Properties(Flag):
NONE = cast(ClassVar[Properties], ...)
INTRA_ONLY = cast(ClassVar[Properties], ...)
LOSSY = cast(ClassVar[Properties], ...)
LOSSLESS = cast(ClassVar[Properties], ...)
REORDER = cast(ClassVar[Properties], ...)
BITMAP_SUB = cast(ClassVar[Properties], ...)
TEXT_SUB = cast(ClassVar[Properties], ...)
class Capabilities(IntEnum):
none = cast(int, ...)
draw_horiz_band = cast(int, ...)
dr1 = cast(int, ...)
hwaccel = cast(int, ...)
delay = cast(int, ...)
small_last_frame = cast(int, ...)
hwaccel_vdpau = cast(int, ...)
subframes = cast(int, ...)
experimental = cast(int, ...)
channel_conf = cast(int, ...)
neg_linesizes = cast(int, ...)
frame_threads = cast(int, ...)
slice_threads = cast(int, ...)
param_change = cast(int, ...)
auto_threads = cast(int, ...)
variable_frame_size = cast(int, ...)
avoid_probing = cast(int, ...)
hardware = cast(int, ...)
hybrid = cast(int, ...)
encoder_reordered_opaque = cast(int, ...)
encoder_flush = cast(int, ...)
encoder_recon_frame = cast(int, ...)
class UnknownCodecError(ValueError): ...
class Codec:
@property
def is_encoder(self) -> bool: ...
@property
def is_decoder(self) -> bool: ...
@property
def mode(self) -> Literal["r", "w"]: ...
descriptor: Descriptor
@property
def name(self) -> str: ...
@property
def canonical_name(self) -> str: ...
@property
def long_name(self) -> str: ...
@property
def type(self) -> Literal["video", "audio", "data", "subtitle", "attachment"]: ...
@property
def id(self) -> int: ...
frame_rates: list[Fraction] | None
audio_rates: list[int] | None
video_formats: list[VideoFormat] | None
audio_formats: list[AudioFormat] | None
@property
def properties(self) -> int: ...
@property
def intra_only(self) -> bool: ...
@property
def lossy(self) -> bool: ...
@property
def lossless(self) -> bool: ...
@property
def reorder(self) -> bool: ...
@property
def bitmap_sub(self) -> bool: ...
@property
def text_sub(self) -> bool: ...
@property
def capabilities(self) -> int: ...
@property
def experimental(self) -> bool: ...
@property
def delay(self) -> bool: ...
def __init__(self, name: str, mode: Literal["r", "w"] = "r") -> None: ...
@overload
def create(self, kind: Literal["video"]) -> VideoCodecContext: ...
@overload
def create(self, kind: Literal["audio"]) -> AudioCodecContext: ...
@overload
def create(self, kind: Literal["subtitle"]) -> SubtitleCodecContext: ...
@overload
def create(self, kind: None = None) -> CodecContext: ...
@overload
def create(
self, kind: Literal["video", "audio", "subtitle"] | None = None
) -> (
VideoCodecContext | AudioCodecContext | SubtitleCodecContext | CodecContext
): ...
class codec_descriptor:
name: str
options: tuple[int, ...]
codecs_available: set[str]
def dump_codecs() -> None: ...
def dump_hwconfigs() -> None: ...

View File

@@ -0,0 +1,389 @@
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 get_video_format
from enum import Flag, IntEnum
cdef object _cinit_sentinel = object()
cdef Codec wrap_codec(const lib.AVCodec *ptr):
cdef 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
LOSSY = lib.AV_CODEC_PROP_LOSSY
LOSSLESS = lib.AV_CODEC_PROP_LOSSLESS
REORDER = lib.AV_CODEC_PROP_REORDER
BITMAP_SUB = lib.AV_CODEC_PROP_BITMAP_SUB
TEXT_SUB = lib.AV_CODEC_PROP_TEXT_SUB
class Capabilities(IntEnum):
none = 0
draw_horiz_band = lib.AV_CODEC_CAP_DRAW_HORIZ_BAND
dr1 = lib.AV_CODEC_CAP_DR1
hwaccel = 1 << 4
delay = lib.AV_CODEC_CAP_DELAY
small_last_frame = lib.AV_CODEC_CAP_SMALL_LAST_FRAME
hwaccel_vdpau = 1 << 7
experimental = lib.AV_CODEC_CAP_EXPERIMENTAL
channel_conf = lib.AV_CODEC_CAP_CHANNEL_CONF
neg_linesizes = 1 << 11
frame_threads = lib.AV_CODEC_CAP_FRAME_THREADS
slice_threads = lib.AV_CODEC_CAP_SLICE_THREADS
param_change = lib.AV_CODEC_CAP_PARAM_CHANGE
auto_threads = lib.AV_CODEC_CAP_OTHER_THREADS
variable_frame_size = lib.AV_CODEC_CAP_VARIABLE_FRAME_SIZE
avoid_probing = lib.AV_CODEC_CAP_AVOID_PROBING
hardware = lib.AV_CODEC_CAP_HARDWARE
hybrid = lib.AV_CODEC_CAP_HYBRID
encoder_reordered_opaque = 1 << 20
encoder_flush = 1 << 21
encoder_recon_frame = 1 << 22
class UnknownCodecError(ValueError):
pass
cdef class Codec:
"""Codec(name, mode='r')
:param str name: The codec name.
:param str mode: ``'r'`` for decoding or ``'w'`` for encoding.
This object exposes information about an available codec, and an avenue to
create a :class:`.CodecContext` to encode/decode directly.
::
>>> codec = Codec('mpeg4', 'r')
>>> codec.name
'mpeg4'
>>> codec.type
'video'
>>> codec.is_encoder
False
"""
def __cinit__(self, name, mode="r"):
if name is _cinit_sentinel:
return
if mode == "w":
self.ptr = lib.avcodec_find_encoder_by_name(name)
if not self.ptr:
self.desc = lib.avcodec_descriptor_get_by_name(name)
if self.desc:
self.ptr = lib.avcodec_find_encoder(self.desc.id)
elif mode == "r":
self.ptr = lib.avcodec_find_decoder_by_name(name)
if not self.ptr:
self.desc = lib.avcodec_descriptor_get_by_name(name)
if self.desc:
self.ptr = lib.avcodec_find_decoder(self.desc.id)
else:
raise ValueError('Invalid mode; must be "r" or "w".', mode)
self._init(name)
# Sanity check.
if (mode == "w") != self.is_encoder:
raise RuntimeError("Found codec does not match mode.", name, mode)
cdef _init(self, name=None):
if not self.ptr:
raise UnknownCodecError(name)
if not self.desc:
self.desc = lib.avcodec_descriptor_get(self.ptr.id)
if not self.desc:
raise RuntimeError("No codec descriptor for %r." % name)
self.is_encoder = lib.av_codec_is_encoder(self.ptr)
# Sanity check.
if self.is_encoder and lib.av_codec_is_decoder(self.ptr):
raise RuntimeError("%s is both encoder and decoder.")
def __repr__(self):
mode = self.mode
return f"<av.{self.__class__.__name__} {self.name} {mode=}>"
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
def mode(self):
return "w" if self.is_encoder else "r"
@property
def is_decoder(self):
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 ""
@property
def canonical_name(self):
"""
Returns the name of the codec, not a specific encoder.
"""
return lib.avcodec_get_name(self.ptr.id)
@property
def long_name(self): return self.ptr.long_name or ""
@property
def type(self):
"""
The media type of this codec.
E.g: ``'audio'``, ``'video'``, ``'subtitle'``.
"""
return lib.av_get_media_type_string(self.ptr.type)
@property
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:
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
@property
def audio_rates(self):
"""A list of supported audio sample rates (``int``), or ``None``."""
if not self.ptr.supported_samplerates:
return
ret = []
cdef int i = 0
while self.ptr.supported_samplerates[i]:
ret.append(self.ptr.supported_samplerates[i])
i += 1
return ret
@property
def video_formats(self):
"""A list of supported :class:`.VideoFormat`, or ``None``."""
if not self.ptr.pix_fmts:
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
@property
def audio_formats(self):
"""A list of supported :class:`.AudioFormat`, or ``None``."""
if not self.ptr.sample_fmts:
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
@property
def hardware_configs(self):
if self._hardware_configs:
return self._hardware_configs
ret = []
cdef int i = 0
cdef const lib.AVCodecHWConfig *ptr
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
@property
def properties(self):
return self.desc.props
@property
def intra_only(self):
return bool(self.desc.props & lib.AV_CODEC_PROP_INTRA_ONLY)
@property
def lossy(self):
return bool(self.desc.props & lib.AV_CODEC_PROP_LOSSY)
@property
def lossless(self):
return bool(self.desc.props & lib.AV_CODEC_PROP_LOSSLESS)
@property
def reorder(self):
return bool(self.desc.props & lib.AV_CODEC_PROP_REORDER)
@property
def bitmap_sub(self):
return bool(self.desc.props & lib.AV_CODEC_PROP_BITMAP_SUB)
@property
def text_sub(self):
return bool(self.desc.props & lib.AV_CODEC_PROP_TEXT_SUB)
@property
def capabilities(self):
"""
Get the capabilities bitmask of the codec.
This method returns an integer representing the codec capabilities bitmask,
which can be used to check specific codec features by performing bitwise
operations with the Capabilities enum values.
:example:
.. code-block:: python
from av.codec import Codec, Capabilities
codec = Codec("h264", "w")
# Check if the codec can be fed a final frame with a smaller size.
# This can be used to prevent truncation of the last audio samples.
small_last_frame = bool(codec.capabilities & Capabilities.small_last_frame)
:rtype: int
"""
return self.ptr.capabilities
@property
def experimental(self):
"""
Check if codec is experimental and is thus avoided in favor of non experimental encoders.
:rtype: bool
"""
return bool(self.ptr.capabilities & lib.AV_CODEC_CAP_EXPERIMENTAL)
@property
def delay(self):
"""
If true, encoder or decoder requires flushing with `None` at the end in order to give the complete and correct output.
:rtype: bool
"""
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
while True:
ptr = lib.av_codec_iterate(&opaque)
if ptr:
names.add(ptr.name)
else:
break
return names
codecs_available = get_codec_names()
codec_descriptor = wrap_avclass(lib.avcodec_get_class())
def dump_codecs():
"""Print information about available codecs."""
print(
"""Codecs:
D..... = Decoding supported
.E.... = Encoding supported
..V... = Video codec
..A... = Audio codec
..S... = Subtitle codec
...I.. = Intra frame-only codec
....L. = Lossy compression
.....S = Lossless compression
------"""
)
for name in sorted(codecs_available):
try:
e_codec = Codec(name, "w")
except ValueError:
e_codec = None
try:
d_codec = Codec(name, "r")
except ValueError:
d_codec = None
# TODO: Assert these always have the same properties.
codec = e_codec or d_codec
try:
print(
" %s%s%s%s%s%s %-18s %s"
% (
".D"[bool(d_codec)],
".E"[bool(e_codec)],
codec.type[0].upper(),
".I"[codec.intra_only],
".L"[codec.lossy],
".S"[codec.lossless],
codec.name,
codec.long_name,
)
)
except Exception as e:
print(f"...... {codec.name:<18} ERROR: {e}")
def dump_hwconfigs():
print("Hardware configs:")
for name in sorted(codecs_available):
try:
codec = Codec(name, "r")
except ValueError:
continue
configs = codec.hardware_configs
if not configs:
continue
print(" ", codec.name)
for config in configs:
print(" ", config)

View File

@@ -0,0 +1,64 @@
cimport libav as lib
from libc.stdint cimport int64_t
from av.bytesource cimport ByteSource
from av.codec.codec cimport Codec
from av.codec.hwaccel cimport HWAccel
from av.frame cimport Frame
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
cdef lib.AVCodecParserContext *parser
cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec, HWAccel hwaccel)
# Public API.
cdef readonly bint is_open
cdef readonly Codec codec
cdef readonly HWAccel hwaccel
cdef public dict options
cpdef open(self, bint strict=?)
# Wraps both versions of the transcode API, returning lists.
cpdef encode(self, Frame frame=?)
cpdef decode(self, Packet packet=?)
cpdef flush_buffers(self)
# Used by hardware-accelerated decode.
cdef HWAccel hwaccel_ctx
# 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 _setup_encoded_packet(self, Packet)
cdef _setup_decoded_frame(self, Frame, Packet)
# Implemented by base for the generic send/recv API.
# Note that the user cannot send without receiving. This is because
# `_prepare_frames_for_encode` may expand a frame into multiple (e.g. when
# resampling audio to a higher rate but with fixed size frames), and the
# 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)
# Implemented by children for the generic send/recv API, so we have the
# correct subclass of Frame.
cdef Frame _next_frame
cdef Frame _alloc_next_frame(self)
cdef CodecContext wrap_codec_context(lib.AVCodecContext*, const lib.AVCodec*, HWAccel hwaccel)

View File

@@ -0,0 +1,117 @@
from enum import Flag, IntEnum
from fractions import Fraction
from typing import ClassVar, Literal, cast, overload
from av.audio import _AudioCodecName
from av.audio.codeccontext import AudioCodecContext
from av.packet import Packet
from av.video import _VideoCodecName
from av.video.codeccontext import VideoCodecContext
from .codec import Codec
from .hwaccel import HWAccel
class ThreadType(Flag):
NONE = cast(ClassVar[ThreadType], ...)
FRAME = cast(ClassVar[ThreadType], ...)
SLICE = cast(ClassVar[ThreadType], ...)
AUTO = cast(ClassVar[ThreadType], ...)
def __get__(self, i: object | None, owner: type | None = None) -> ThreadType: ...
def __set__(self, instance: object, value: int | str | ThreadType) -> None: ...
class Flags(IntEnum):
unaligned = cast(int, ...)
qscale = cast(int, ...)
four_mv = cast(int, ...)
output_corrupt = cast(int, ...)
qpel = cast(int, ...)
recon_frame = cast(int, ...)
copy_opaque = cast(int, ...)
frame_duration = cast(int, ...)
pass1 = cast(int, ...)
pass2 = cast(int, ...)
loop_filter = cast(int, ...)
gray = cast(int, ...)
psnr = cast(int, ...)
interlaced_dct = cast(int, ...)
low_delay = cast(int, ...)
global_header = cast(int, ...)
bitexact = cast(int, ...)
ac_pred = cast(int, ...)
interlaced_me = cast(int, ...)
closed_gop = cast(int, ...)
class Flags2(IntEnum):
fast = cast(int, ...)
no_output = cast(int, ...)
local_header = cast(int, ...)
chunks = cast(int, ...)
ignore_crop = cast(int, ...)
show_all = cast(int, ...)
export_mvs = cast(int, ...)
skip_manual = cast(int, ...)
ro_flush_noop = cast(int, ...)
class CodecContext:
name: str
type: Literal["video", "audio", "data", "subtitle", "attachment"]
options: dict[str, str]
profile: str | None
@property
def profiles(self) -> list[str]: ...
extradata: bytes | None
time_base: Fraction
codec_tag: str
bit_rate: int | None
bit_rate_tolerance: int
thread_count: int
thread_type: ThreadType
skip_frame: Literal[
"NONE", "DEFAULT", "NONREF", "BIDIR", "NONINTRA", "NONKEY", "ALL"
]
flags: int
qscale: bool
copy_opaque: bool
flags2: int
@property
def is_open(self) -> bool: ...
@property
def is_encoder(self) -> bool: ...
@property
def is_decoder(self) -> bool: ...
@property
def codec(self) -> Codec: ...
@property
def max_bit_rate(self) -> int | None: ...
@property
def delay(self) -> bool: ...
@property
def extradata_size(self) -> int: ...
@property
def is_hwaccel(self) -> bool: ...
def open(self, strict: bool = True) -> None: ...
@overload
@staticmethod
def create(
codec: _AudioCodecName,
mode: Literal["r", "w"] | None = None,
hwaccel: HWAccel | None = None,
) -> AudioCodecContext: ...
@overload
@staticmethod
def create(
codec: _VideoCodecName,
mode: Literal["r", "w"] | None = None,
hwaccel: HWAccel | None = None,
) -> VideoCodecContext: ...
@overload
@staticmethod
def create(
codec: str | Codec,
mode: Literal["r", "w"] | None = None,
hwaccel: HWAccel | None = None,
) -> CodecContext: ...
def parse(
self, raw_input: bytes | bytearray | memoryview | None = None
) -> list[Packet]: ...
def flush_buffers(self) -> None: ...

View File

@@ -0,0 +1,671 @@
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
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
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)
py_ctx._init(c_ctx, c_codec, hwaccel)
return py_ctx
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
class Flags(IntEnum):
unaligned = lib.AV_CODEC_FLAG_UNALIGNED
qscale = lib.AV_CODEC_FLAG_QSCALE
four_mv = lib.AV_CODEC_FLAG_4MV
output_corrupt = lib.AV_CODEC_FLAG_OUTPUT_CORRUPT
qpel = lib.AV_CODEC_FLAG_QPEL
recon_frame = lib.AV_CODEC_FLAG_RECON_FRAME
copy_opaque = lib.AV_CODEC_FLAG_COPY_OPAQUE
frame_duration = lib.AV_CODEC_FLAG_FRAME_DURATION
pass1 = lib.AV_CODEC_FLAG_PASS1
pass2 = lib.AV_CODEC_FLAG_PASS2
loop_filter = lib.AV_CODEC_FLAG_LOOP_FILTER
gray = lib.AV_CODEC_FLAG_GRAY
psnr = lib.AV_CODEC_FLAG_PSNR
interlaced_dct = lib.AV_CODEC_FLAG_INTERLACED_DCT
low_delay = lib.AV_CODEC_FLAG_LOW_DELAY
global_header = lib.AV_CODEC_FLAG_GLOBAL_HEADER
bitexact = lib.AV_CODEC_FLAG_BITEXACT
ac_pred = lib.AV_CODEC_FLAG_AC_PRED
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
local_header = lib.AV_CODEC_FLAG2_LOCAL_HEADER
chunks = lib.AV_CODEC_FLAG2_CHUNKS
ignore_crop = lib.AV_CODEC_FLAG2_IGNORE_CROP
show_all = lib.AV_CODEC_FLAG2_SHOW_ALL
export_mvs = lib.AV_CODEC_FLAG2_EXPORT_MVS
skip_manual = lib.AV_CODEC_FLAG2_SKIP_MANUAL
ro_flush_noop = lib.AV_CODEC_FLAG2_RO_FLUSH_NOOP
cdef 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)
return wrap_codec_context(c_ctx, cy_codec.ptr, hwaccel)
def __cinit__(self, sentinel=None, *args, **kwargs):
if sentinel is not _cinit_sentinel:
raise RuntimeError("Cannot instantiate CodecContext")
self.options = {}
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):
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.hwaccel = hwaccel
# Set reasonable threading defaults.
self.ptr.thread_count = 0 # use as many threads as there are CPUs.
self.ptr.thread_type = 0x02 # thread within a frame. Does not change the API.
@property
def flags(self):
"""
Get and set the flags bitmask of CodecContext.
:rtype: int
"""
return self.ptr.flags
@flags.setter
def flags(self, int value):
self.ptr.flags = value
@property
def qscale(self):
"""
Use fixed qscale.
:rtype: bool
"""
return bool(self.ptr.flags & lib.AV_CODEC_FLAG_QSCALE)
@qscale.setter
def qscale(self, value):
if value:
self.ptr.flags |= lib.AV_CODEC_FLAG_QSCALE
else:
self.ptr.flags &= ~lib.AV_CODEC_FLAG_QSCALE
@property
def copy_opaque(self):
return bool(self.ptr.flags & lib.AV_CODEC_FLAG_COPY_OPAQUE)
@copy_opaque.setter
def copy_opaque(self, value):
if value:
self.ptr.flags |= lib.AV_CODEC_FLAG_COPY_OPAQUE
else:
self.ptr.flags &= ~lib.AV_CODEC_FLAG_COPY_OPAQUE
@property
def flags2(self):
"""
Get and set the flags2 bitmask of CodecContext.
:rtype: int
"""
return self.ptr.flags2
@flags2.setter
def flags2(self, int value):
self.ptr.flags2 = value
@property
def extradata(self):
if self.ptr is NULL:
return None
if self.ptr.extradata_size > 0:
return <bytes>(<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)
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)
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):
return self.ptr.extradata_size
@property
def is_encoder(self):
if self.ptr is NULL:
return False
return lib.av_codec_is_encoder(self.ptr.codec)
@property
def is_decoder(self):
if self.ptr is NULL:
return False
return lib.av_codec_is_decoder(self.ptr.codec)
cpdef open(self, bint strict=True):
if self.is_open:
if strict:
raise ValueError("CodecContext is already open.")
return
cdef _Dictionary options = Dictionary()
options.update(self.options or {})
if not self.ptr.time_base.num and self.is_encoder:
if self.type == "video":
self.ptr.time_base.num = self.ptr.framerate.den or 1
self.ptr.time_base.den = self.ptr.framerate.num or lib.AV_TIME_BASE
elif self.type == "audio":
self.ptr.time_base.num = 1
self.ptr.time_base.den = self.ptr.sample_rate
else:
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 + ")")
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)
if self.parser:
lib.av_parser_close(self.parser)
def __repr__(self):
_type = self.type or "<notype>"
name = self.name or "<nocodec>"
return f"<av.{self.__class__.__name__} {_type}/{name} at 0x{id(self):x}>"
def parse(self, raw_input=None):
"""Split up a byte stream into list of :class:`.Packet`.
This is only effectively splitting up a byte stream, and does no
actual interpretation of the data.
It will return all packets that are fully contained within the given
input, and will buffer partial packets until they are complete.
: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.
:return: ``list`` of :class:`.Packet` newly available.
"""
if not self.parser:
self.parser = lib.av_parser_init(self.codec.ptr.id)
if not self.parser:
raise ValueError(f"No parser for {self.codec.name}")
cdef ByteSource source = 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
cdef unsigned char *out_data
cdef int out_size
cdef int consumed
cdef Packet packet = None
packets = []
while True:
with 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
)
err_check(consumed)
if out_size:
# We copy the data immediately, as we have yet to figure out
# the expected lifetime of the buffer we get back. All of the
# examples decode it immediately.
#
# We've also tried:
# packet = Packet()
# packet.data = out_data
# packet.size = out_size
# packet.source = source
#
# ... but this results in corruption.
packet = Packet(out_size)
memcpy(packet.ptr.data, out_data, out_size)
packets.append(packet)
if not in_size:
# This was a flush. Only one packet should ever be returned.
break
in_data += consumed
in_size -= consumed
if not in_size:
break
return packets
@property
def is_hwaccel(self):
"""
Returns ``True`` if this codec context is hardware accelerated, ``False`` otherwise.
"""
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)
err_check(res, "avcodec_send_frame()")
packet = self._recv_packet()
while packet:
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):
return [frame]
cdef Frame _alloc_next_frame(self):
raise NotImplementedError("Base CodecContext cannot decode.")
cdef _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:
res = lib.avcodec_receive_frame(self.ptr, frame.ptr)
if res == -EAGAIN or res == lib.AVERROR_EOF:
return
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):
return frame
cdef _recv_packet(self):
cdef Packet packet = Packet()
cdef int res
with 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()")
if not res:
return packet
cdef _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.")
self.open(strict=False)
frames = self._prepare_frames_for_encode(frame)
# Assert the frames are in our time base.
# TODO: Don't mutate time.
for frame in frames:
if frame is not None:
frame._rebase_time(self.ptr.time_base)
return frames
cpdef encode(self, Frame frame=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):
for packet in self._send_frame_and_recv(frame):
self._setup_encoded_packet(packet)
res.append(packet)
return res
def encode_lazy(self, Frame frame=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):
# 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
# a different time_base.
#
# NOTE: if the CodecContext's time_base is altered during encoding, all bets
# are off!
packet.ptr.time_base = self.ptr.time_base
cpdef decode(self, Packet packet=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).
"""
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
cpdef 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.
when seeking or when switching to a different stream.
"""
if self.is_open:
with nogil:
lib.avcodec_flush_buffers(self.ptr)
cdef _setup_decoded_frame(self, Frame frame, Packet packet):
# Propagate our manual times.
# While decoding, frame times are in stream time_base, which PyAV
# is carrying around.
# TODO: Somehow get this from the stream so we can not pass the
# packet here (because flushing packets are bogus).
if packet is not None:
frame._time_base = packet.ptr.time_base
@property
def name(self):
return self.codec.name
@property
def type(self):
return self.codec.type
@property
def profiles(self):
"""
List the available profiles for this stream.
:type: list[str]
"""
ret = []
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:
ret.append(desc.profiles[i].name)
i += 1
return ret
@property
def profile(self):
if not self.ptr.codec or not self.codec.desc or not self.codec.desc.profiles:
return
# 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:
if desc.profiles[i].profile == self.ptr.profile:
return desc.profiles[i].name
i += 1
@profile.setter
def profile(self, value):
if not self.codec or not self.codec.desc or not self.codec.desc.profiles:
return
# 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:
if desc.profiles[i].name == value:
self.ptr.profile = desc.profiles[i].profile
return
i += 1
@property
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)
@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)
@property
def codec_tag(self):
return self.ptr.codec_tag.to_bytes(4, byteorder="little", signed=False).decode(
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)
else:
raise ValueError("Codec tag should be a 4 character string.")
@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):
self.ptr.bit_rate = value
@property
def max_bit_rate(self):
if self.ptr.rc_max_rate > 0:
return self.ptr.rc_max_rate
else:
return None
@property
def bit_rate_tolerance(self):
self.ptr.bit_rate_tolerance
@bit_rate_tolerance.setter
def bit_rate_tolerance(self, int value):
self.ptr.bit_rate_tolerance = value
@property
def thread_count(self):
"""How many threads to use; 0 means auto.
Wraps :ffmpeg:`AVCodecContext.thread_count`.
"""
return self.ptr.thread_count
@thread_count.setter
def thread_count(self, int value):
if self.is_open:
raise RuntimeError("Cannot change thread_count after codec is open.")
self.ptr.thread_count = value
@property
def thread_type(self):
"""One of :class:`.ThreadType`.
Wraps :ffmpeg:`AVCodecContext.thread_type`.
"""
return ThreadType(self.ptr.thread_type)
@thread_type.setter
def thread_type(self, value):
if self.is_open:
raise RuntimeError("Cannot change thread_type after codec is open.")
if type(value) is int:
self.ptr.thread_type = value
elif type(value) is str:
self.ptr.thread_type = ThreadType[value].value
else:
self.ptr.thread_type = value.value
@property
def skip_frame(self):
"""Returns one of the following str literals:
"NONE" Discard nothing
"DEFAULT" Discard useless packets like 0 size packets in AVI
"NONREF" Discard all non reference
"BIDIR" Discard all bidirectional frames
"NONINTRA" Discard all non intra frames
"NONKEY Discard all frames except keyframes
"ALL" Discard all
Wraps :ffmpeg:`AVCodecContext.skip_frame`.
"""
value = self.ptr.skip_frame
if value == lib.AVDISCARD_NONE:
return "NONE"
if value == lib.AVDISCARD_DEFAULT:
return "DEFAULT"
if value == lib.AVDISCARD_NONREF:
return "NONREF"
if value == lib.AVDISCARD_BIDIR:
return "BIDIR"
if value == lib.AVDISCARD_NONINTRA:
return "NONINTRA"
if value == lib.AVDISCARD_NONKEY:
return "NONKEY"
if value == lib.AVDISCARD_ALL:
return "ALL"
return f"{value}"
@skip_frame.setter
def skip_frame(self, value):
if value == "NONE":
self.ptr.skip_frame = lib.AVDISCARD_NONE
elif value == "DEFAULT":
self.ptr.skip_frame = lib.AVDISCARD_DEFAULT
elif value == "NONREF":
self.ptr.skip_frame = lib.AVDISCARD_NONREF
elif value == "BIDIR":
self.ptr.skip_frame = lib.AVDISCARD_BIDIR
elif value == "NONINTRA":
self.ptr.skip_frame = lib.AVDISCARD_NONINTRA
elif value == "NONKEY":
self.ptr.skip_frame = lib.AVDISCARD_NONKEY
elif value == "ALL":
self.ptr.skip_frame = lib.AVDISCARD_ALL
else:
raise ValueError("Invalid skip_frame type")
@property
def delay(self):
"""Codec delay.
Wraps :ffmpeg:`AVCodecContext.delay`.
"""
return self.ptr.delay

View File

@@ -0,0 +1,21 @@
cimport libav as lib
from av.codec.codec cimport Codec
cdef class HWConfig:
cdef object __weakref__
cdef lib.AVCodecHWConfig *ptr
cdef void _init(self, lib.AVCodecHWConfig *ptr)
cdef HWConfig wrap_hwconfig(lib.AVCodecHWConfig *ptr)
cdef class HWAccel:
cdef int _device_type
cdef str _device
cdef readonly Codec codec
cdef readonly HWConfig config
cdef lib.AVBufferRef *ptr
cdef public bint allow_software_fallback
cdef public dict options
cdef public int flags

View File

@@ -0,0 +1,50 @@
from enum import IntEnum
from typing import cast
from av.codec.codec import Codec
from av.video.format import VideoFormat
class HWDeviceType(IntEnum):
none = cast(int, ...)
vdpau = cast(int, ...)
cuda = cast(int, ...)
vaapi = cast(int, ...)
dxva2 = cast(int, ...)
qsv = cast(int, ...)
videotoolbox = cast(int, ...)
d3d11va = cast(int, ...)
drm = cast(int, ...)
opencl = cast(int, ...)
mediacodec = cast(int, ...)
vulkan = cast(int, ...)
d3d12va = cast(int, ...)
class HWConfigMethod(IntEnum):
none = cast(int, ...)
hw_device_ctx = cast(int, ...)
hw_frame_ctx = cast(int, ...)
internal = cast(int, ...)
ad_hoc = cast(int, ...)
class HWConfig:
@property
def device_type(self) -> HWDeviceType: ...
@property
def format(self) -> VideoFormat: ...
@property
def methods(self) -> HWConfigMethod: ...
@property
def is_supported(self) -> bool: ...
class HWAccel:
def __init__(
self,
device_type: str | HWDeviceType,
device: str | None = None,
allow_software_fallback: bool = False,
options: dict[str, object] | None = None,
flags: int | None = None,
) -> None: ...
def create(self, codec: Codec) -> HWAccel: ...
def hwdevices_available() -> list[str]: ...

View File

@@ -0,0 +1,159 @@
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
class HWDeviceType(IntEnum):
none = lib.AV_HWDEVICE_TYPE_NONE
vdpau = lib.AV_HWDEVICE_TYPE_VDPAU
cuda = lib.AV_HWDEVICE_TYPE_CUDA
vaapi = lib.AV_HWDEVICE_TYPE_VAAPI
dxva2 = lib.AV_HWDEVICE_TYPE_DXVA2
qsv = lib.AV_HWDEVICE_TYPE_QSV
videotoolbox = lib.AV_HWDEVICE_TYPE_VIDEOTOOLBOX
d3d11va = lib.AV_HWDEVICE_TYPE_D3D11VA
drm = lib.AV_HWDEVICE_TYPE_DRM
opencl = lib.AV_HWDEVICE_TYPE_OPENCL
mediacodec = lib.AV_HWDEVICE_TYPE_MEDIACODEC
vulkan = lib.AV_HWDEVICE_TYPE_VULKAN
d3d12va = lib.AV_HWDEVICE_TYPE_D3D12VA
amf = 13 # FFmpeg >=8
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_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()
cdef HWConfig wrap_hwconfig(lib.AVCodecHWConfig *ptr):
try:
return _singletons[<int>ptr]
except KeyError:
pass
cdef HWConfig config = HWConfig(_cinit_sentinel)
config._init(ptr)
_singletons[<int>ptr] = config
return config
cdef class HWConfig:
def __init__(self, sentinel):
if sentinel is not _cinit_sentinel:
raise RuntimeError("Cannot instantiate CodecContext")
cdef void _init(self, lib.AVCodecHWConfig *ptr):
self.ptr = ptr
def __repr__(self):
return (
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}>"
)
@property
def device_type(self):
return HWDeviceType(self.ptr.device_type)
@property
def format(self):
return get_video_format(self.ptr.pix_fmt, 0, 0)
@property
def methods(self):
return HWConfigMethod(self.ptr.methods)
@property
def is_supported(self):
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
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):
if isinstance(device_type, HWDeviceType):
self._device_type = device_type
elif isinstance(device_type, str):
self._device_type = int(lib.av_hwdevice_find_type_by_name(device_type))
elif isinstance(device_type, int):
self._device_type = device_type
else:
raise ValueError("Unknown type for device_type")
self._device = device
self.allow_software_fallback = allow_software_fallback
self.options = {} if not options else dict(options)
self.flags = 0 if not flags else flags
self.ptr = NULL
self.config = None
def _initialize_hw_context(self, Codec codec not None):
cdef HWConfig config
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:
raise NotImplementedError(f"No supported hardware config for {codec}")
self.config = config
cdef char *c_device = NULL
if self._device:
device_bytes = self._device.encode()
c_device = device_bytes
cdef _Dictionary c_options = Dictionary(self.options)
err_check(
lib.av_hwdevice_ctx_create(
&self.ptr, config.ptr.device_type, c_device, c_options.ptr, self.flags
)
)
def create(self, Codec codec not None):
"""Create a new hardware accelerator context with the given codec"""
if self.ptr:
raise RuntimeError("Hardware context already initialized")
ret = HWAccel(
device_type=self._device_type,
device=self._device,
allow_software_fallback=self.allow_software_fallback,
options=self.options
)
ret._initialize_hw_context(codec)
return ret
def __dealloc__(self):
if self.ptr:
lib.av_buffer_unref(&self.ptr)