Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,20 @@ Major:

- Drop support for Python 3.11. Binary wheels are now built for Python 3.12 and later.
- Remove the undocumented ``CodecContext.hwaccel`` attribute. It held the ``HWAccel`` settings object passed in, not the live device context; use ``CodecContext.is_hwaccel`` to check whether hardware acceleration is in use.
- Rational attributes (``time_base``, ``average_rate``, ``base_rate``, ``guessed_rate``, ``framerate``, ``rate``, ``sample_aspect_ratio``, and ``display_aspect_ratio``) now return :class:`av.AVRational` rather than ``fractions.Fraction``, and are never ``None``: an unset value is the falsy ``AVRational(0, 1)``. Test them with ``if not stream.time_base:`` instead of ``is None``. Setters still accept a ``fractions.Fraction``.
- Remove ``Capabilities.hwaccel``, ``Capabilities.hwaccel_vdpau``, and ``Capabilities.neg_linesizes``, none of which FFmpeg defines any more.

Features:

- ``av.dump_codecs()`` now lists every codec FFmpeg knows of rather than only those with an encoder or a decoder, so data and attachment codecs appear, matching ``ffmpeg -codecs``. Its legend gains the ``..D...`` and ``..T...`` media types.
- ``ContainerFormat.fixed_framesize`` reports whether a format wants fixed size audio frames.
- Enums gained the members FFmpeg has since added: ``Properties.FIELDS``, ``Properties.ENHANCEMENT``, ``PixFmtLoss.EXCESS_RESOLUTION``, ``PixFmtLoss.EXCESS_DEPTH``, ``Flags2.icc_profiles``, ``format.Flags.experimental``, ``Interpolation.STRICT``, ``Interpolation.UNSTABLE``, ``ColorTrc.V_LOG``, ``ColorPrimaries.V_GAMUT``, and the ``LCEVC``, ``VIEW_ID``, ``THREE_D_REFERENCE_DISPLAYS``, and ``EXIF`` members of ``sidedata.Type``.

Fixes:

- ``av.dump_codecs()`` no longer drops the canonical names ``h264``, ``hevc``, ``av1``, ``dirac``, and ``ilbc``, each of which was overwritten by the row of whichever encoder it resolved to.
- ``Frame.side_data`` now satisfies the ``Mapping`` protocol it advertises: iteration yields :class:`~av.sidedata.sidedata.Type` keys, so ``items()``, ``keys()``, and ``values()`` work instead of raising ``KeyError``. Values remain reachable positionally by an integer or, for the first time, a slice. Its type stub was a ``TypedDict`` with a single literal key, and is now ``SideDataContainer``.
- Fix crashes from indexes that were turned into C pointer arithmetic without being range checked. ``MotionVectors[i]`` only checked the upper bound, so a negative index read off the front of the buffer (``mvs[-1]`` now returns the last vector, as with any sequence); ``VideoFormatComponent`` and ``AudioPlane`` accepted any index at all; and ``BitmapSubtitlePlane`` and ``VideoBlockParams`` were missing their lower bounds.
- Frames returned by flushing a codec context directly (``CodecContext.decode()`` with no packet) now carry the stream's ``time_base`` instead of ``None``.
- ``VideoFrame.reformat()`` (and so ``to_ndarray(format=...)``, ``to_rgb()``, ``to_image()``) now shares one ``SwsContext`` per thread instead of allocating one per frame. FFmpeg 8's swscale retains megabytes of graph state per context, which showed up as large RSS growth when many frames were alive at once.

Expand Down
6 changes: 6 additions & 0 deletions av/audio/plane.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
@cython.cclass
class AudioPlane(Plane):
def __cinit__(self, frame: AudioFrame, index: cython.int):
nb_planes: cython.int = (
frame.layout.nb_channels if frame.format.is_planar else 1
)
if index < 0 or index >= nb_planes:
raise ValueError(f"plane index {index} out of range for {nb_planes} planes")

# Only the first linesize is ever populated, but it applies to every plane.
self.buffer_size = self.frame.ptr.linesize[0]

Expand Down
1 change: 0 additions & 1 deletion av/codec/codec.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from cython.cimports.av.audio.format import get_audio_format
from cython.cimports.av.codec.hwaccel import HWConfig, wrap_hwconfig
from cython.cimports.av.rational import from_avrational
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

Expand Down
5 changes: 3 additions & 2 deletions av/codec/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
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.av.rational import from_avrational
from cython.cimports.av.utils import to_avrational
from cython.cimports.libc.errno import EAGAIN
from cython.cimports.libc.stdint import uint8_t
from cython.cimports.libc.string import memcpy, strcmp
Expand Down Expand Up @@ -857,7 +858,7 @@ def level(self, value: cython.int):
def time_base(self):
if self.is_decoder:
raise RuntimeError("Cannot access 'time_base' as a decoder")
return avrational_to_fraction(cython.address(self.ptr.time_base))
return from_avrational(self.ptr.time_base)

@time_base.setter
def time_base(self, value):
Expand Down
6 changes: 5 additions & 1 deletion av/codec/context.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ 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.rational import AVRational
from av.subtitles import _SubtitleCodecName
from av.subtitles.codeccontext import SubtitleCodecContext
from av.video import _VideoCodecName
Expand Down Expand Up @@ -125,7 +126,10 @@ class CodecContext:
@property
def profiles(self) -> list[str]: ...
extradata: bytes | None
time_base: Fraction
@property
def time_base(self) -> AVRational: ...
@time_base.setter
def time_base(self, value: AVRational | Fraction | int) -> None: ...
codec_tag: str
global_quality: int
bit_rate: int | None
Expand Down
4 changes: 2 additions & 2 deletions av/container/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
from cython.cimports.av.container.pyio import pyio_close_custom_gil, pyio_close_gil
from cython.cimports.av.error import err_check, stash_exception
from cython.cimports.av.format import build_container_format
from cython.cimports.av.rational import from_avrational
from cython.cimports.av.utils import (
avdict_to_dict,
avrational_to_fraction,
dict_to_avdict,
to_avrational,
)
Expand Down Expand Up @@ -433,7 +433,7 @@ def chapters(self):
"id": ch.id,
"start": ch.start,
"end": ch.end,
"time_base": avrational_to_fraction(cython.address(ch.time_base)),
"time_base": from_avrational(ch.time_base),
"metadata": avdict_to_dict(
ch.metadata, self.metadata_encoding, self.metadata_errors
),
Expand Down
5 changes: 3 additions & 2 deletions av/container/core.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ from typing import Any, ClassVar, Literal, Self, TypedDict, cast, overload

from av.codec.hwaccel import HWAccel
from av.format import ContainerFormat
from av.rational import AVRational

from .input import InputContainer
from .output import OutputContainer
from .streams import StreamContainer

Real = int | float | Fraction
Real = int | float | Fraction | AVRational

class Flags(Flag):
gen_pts = cast(ClassVar[Flags], ...)
Expand Down Expand Up @@ -72,7 +73,7 @@ class Chapter(TypedDict):
id: int
start: int
end: int
time_base: Fraction | None
time_base: AVRational
metadata: dict[str, str]

class Container:
Expand Down
9 changes: 5 additions & 4 deletions av/container/output.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ from av.audio import _AudioCodecName
from av.audio.stream import AudioStream
from av.codec.hwaccel import HWAccel
from av.packet import Packet
from av.rational import AVRational
from av.stream import AttachmentStream, DataStream, Stream
from av.subtitles import _SubtitleCodecName
from av.subtitles.stream import SubtitleStream
Expand All @@ -29,7 +30,7 @@ class OutputContainer(Container):
def add_stream(
self,
codec_name: _VideoCodecName,
rate: Fraction | int | None = None,
rate: AVRational | Fraction | int | None = None,
options: dict[str, str] | None = None,
hwaccel: HWAccel | None = None,
**kwargs,
Expand All @@ -38,23 +39,23 @@ class OutputContainer(Container):
def add_stream(
self,
codec_name: _SubtitleCodecName,
rate: Fraction | int | None = None,
rate: AVRational | Fraction | int | None = None,
options: dict[str, str] | None = None,
**kwargs,
) -> SubtitleStream: ...
@overload
def add_stream(
self,
codec_name: str,
rate: Fraction | int | None = None,
rate: AVRational | Fraction | int | None = None,
options: dict[str, str] | None = None,
hwaccel: HWAccel | None = None,
**kwargs,
) -> VideoStream | AudioStream | SubtitleStream: ...
def add_mux_stream(
self,
codec_name: str,
rate: Fraction | int | None = None,
rate: AVRational | Fraction | int | None = None,
**kwargs,
) -> Stream: ...
def add_stream_from_template(
Expand Down
6 changes: 2 additions & 4 deletions av/filter/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from cython.cimports.av.error import err_check
from cython.cimports.av.filter.link import alloc_filter_pads
from cython.cimports.av.frame import Frame
from cython.cimports.av.utils import avrational_to_fraction
from cython.cimports.av.rational import from_avrational
from cython.cimports.av.video.frame import alloc_video_frame

_cinit_sentinel = cython.declare(object, object())
Expand Down Expand Up @@ -162,9 +162,7 @@ def pull(self):
err_check(res)

frame._init_user_attributes()
frame.time_base = avrational_to_fraction(
cython.address(self.ptr.inputs[0].time_base)
)
frame.time_base = from_avrational(self.ptr.inputs[0].time_base)
return frame

def process_command(
Expand Down
6 changes: 3 additions & 3 deletions av/filter/graph.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import warnings
from fractions import Fraction

import cython
from cython.cimports.av.audio.format import AudioFormat
Expand All @@ -8,6 +7,7 @@
from cython.cimports.av.error import err_check
from cython.cimports.av.filter.context import FilterContext, wrap_filter_context
from cython.cimports.av.filter.filter import Filter, wrap_filter
from cython.cimports.av.rational import AVRational
from cython.cimports.av.video.format import VideoFormat
from cython.cimports.av.video.frame import VideoFrame

Expand Down Expand Up @@ -160,7 +160,7 @@ def add_buffer(
"This is deprecated and may be removed in future releases.",
DeprecationWarning,
)
time_base = Fraction(1, 1000)
time_base = AVRational(1, 1000)

return self.add(
"buffer",
Expand Down Expand Up @@ -204,7 +204,7 @@ def add_abuffer(
if layout is None and channels is None:
raise ValueError("missing layout or channels")
if time_base is None:
time_base = Fraction(1, sample_rate)
time_base = AVRational(1, sample_rate)

kwargs = {
"sample_rate": f"{sample_rate}",
Expand Down
5 changes: 3 additions & 2 deletions av/filter/graph.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ from av.audio.format import AudioFormat
from av.audio.frame import AudioFrame
from av.audio.layout import AudioLayout
from av.audio.stream import AudioStream
from av.rational import AVRational
from av.video.format import VideoFormat
from av.video.frame import VideoFrame
from av.video.stream import VideoStream
Expand All @@ -29,7 +30,7 @@ class Graph:
height: int | None = None,
format: VideoFormat | None = None,
name: str | None = None,
time_base: Fraction | None = None,
time_base: AVRational | Fraction | None = None,
) -> FilterContext: ...
def add_abuffer(
self,
Expand All @@ -39,7 +40,7 @@ class Graph:
layout: AudioLayout | str | None = None,
channels: int | None = None,
name: str | None = None,
time_base: Fraction | None = None,
time_base: AVRational | Fraction | None = None,
) -> FilterContext: ...
def set_audio_frame_size(self, frame_size: int) -> None: ...
def push(self, frame: None | AudioFrame | VideoFrame, at: int = -1) -> None: ...
Expand Down
6 changes: 3 additions & 3 deletions av/frame.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import cython
from cython.cimports.av.error import err_check
from cython.cimports.av.opaque import opaque_container
from cython.cimports.av.rational import from_avrational
from cython.cimports.av.utils import (
avdict_to_dict,
avrational_to_fraction,
to_avrational,
)

Expand Down Expand Up @@ -141,10 +141,10 @@ def time_base(self):
"""
The unit of time (in fractional seconds) in which timestamps are expressed.

:type: fractions.Fraction | None
:type: AVRational
"""
if self._time_base.num:
return avrational_to_fraction(cython.address(self._time_base))
return from_avrational(self._time_base)

@time_base.setter
def time_base(self, value):
Expand Down
14 changes: 7 additions & 7 deletions av/frame.pyi
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
from fractions import Fraction
from typing import TypedDict

from av.sidedata.motionvectors import MotionVectors

class SideData(TypedDict, total=False):
MOTION_VECTORS: MotionVectors
from av.rational import AVRational
from av.sidedata.sidedata import SideDataContainer

class Frame:
dts: int | None
pts: int | None
duration: int
time_base: Fraction | None
side_data: SideData
@property
def time_base(self) -> AVRational: ...
@time_base.setter
def time_base(self, value: AVRational | Fraction | int) -> None: ...
side_data: SideDataContainer
opaque: object
@property
def metadata(self) -> dict[str, str]: ...
Expand Down
9 changes: 4 additions & 5 deletions av/packet.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@
from cython.cimports.av.buffer import Buffer, ByteSource, bytesource
from cython.cimports.av.error import err_check
from cython.cimports.av.opaque import opaque_container
from cython.cimports.av.utils import avrational_to_fraction, to_avrational
from cython.cimports.av.rational import AVRational, from_avrational
from cython.cimports.av.utils import to_avrational
from cython.cimports.cpython.ref import Py_DECREF, Py_INCREF
from cython.cimports.libc.stdint import uint8_t
from cython.cimports.libc.string import memcpy

from av.rational import AVRational

# Check https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/packet.h#L41
# for new additions in the future ffmpeg releases
# Note: the order must follow that of the AVPacketSideDataType enum def
Expand Down Expand Up @@ -338,9 +337,9 @@ def time_base(self):
"""
The unit of time (in fractional seconds) in which timestamps are expressed.

:type: fractions.Fraction
:type: AVRational
"""
return avrational_to_fraction(cython.address(self.ptr.time_base))
return from_avrational(self.ptr.time_base)

@time_base.setter
def time_base(self, value):
Expand Down
5 changes: 4 additions & 1 deletion av/packet.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ StreamT = TypeVar("StreamT", bound=Stream)
class Packet(Buffer, Generic[StreamT]):
stream: StreamT
stream_index: int
time_base: Fraction
@property
def time_base(self) -> AVRational: ...
@time_base.setter
def time_base(self, value: AVRational | Fraction | int) -> None: ...
pts: int | None
dts: int | None
pos: int | None
Expand Down
22 changes: 17 additions & 5 deletions av/rational.pyi
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from fractions import Fraction
from numbers import Rational
from typing import Any
from typing import Any, overload

class AVRational:
num: int
Expand All @@ -20,10 +20,22 @@ class AVRational:
def __ge__(self, other: Any) -> bool: ...
def __neg__(self) -> AVRational: ...
def __mul__(self, other: Any) -> AVRational | Fraction | float: ...
def __rmul__(self, other: Any) -> Fraction | float: ...
@overload
def __rmul__(self, other: int | Fraction) -> Fraction: ...
@overload
def __rmul__(self, other: float) -> float: ...
def __truediv__(self, other: Any) -> AVRational | Fraction | float: ...
def __rtruediv__(self, other: Any) -> Fraction | float: ...
@overload
def __rtruediv__(self, other: int | Fraction) -> Fraction: ...
@overload
def __rtruediv__(self, other: float) -> float: ...
def __add__(self, other: Any) -> AVRational | Fraction | float: ...
def __radd__(self, other: Any) -> Fraction | float: ...
@overload
def __radd__(self, other: int | Fraction) -> Fraction: ...
@overload
def __radd__(self, other: float) -> float: ...
def __sub__(self, other: Any) -> AVRational | Fraction | float: ...
def __rsub__(self, other: Any) -> Fraction | float: ...
@overload
def __rsub__(self, other: int | Fraction) -> Fraction: ...
@overload
def __rsub__(self, other: float) -> float: ...
3 changes: 3 additions & 0 deletions av/sidedata/encparams.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ def qp_map(self):
@cython.cclass
class VideoBlockParams:
def __init__(self, video_enc_params: VideoEncParams, idx: cython.int) -> None:
if idx < 0 or idx >= video_enc_params.nb_blocks:
raise ValueError("Expected idx in range [0, nb_blocks)")

base: cython.pointer[uint8_t] = cython.cast(
cython.pointer[uint8_t], video_enc_params.ptr.data
)
Expand Down
Loading
Loading