From 1738df6c7007f70d25abe3443799725dcfb254f0 Mon Sep 17 00:00:00 2001 From: WyattBlue Date: Wed, 30 Sep 2026 17:43:14 -0400 Subject: [PATCH] Stop side data holding its frame in a reference cycle --- CHANGELOG.rst | 7 +++++-- av/frame.pxd | 2 -- av/frame.py | 4 +--- av/frame.pyi | 3 ++- av/sidedata/motionvectors.pxd | 1 - av/sidedata/motionvectors.py | 11 +---------- av/sidedata/motionvectors.pyi | 7 +++---- av/sidedata/sidedata.pyi | 2 -- 8 files changed, 12 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ad4b8813d..5b5c25697 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -29,11 +29,14 @@ We are operating with `semantic versioning `_. - $CHANGE by :gh-user:`mikeboers` in (:pr:`1`). -v19.0.1 -------- +v19.0.1 (Unreleased) +-------------------- Fixes: +- Reading :attr:`.Frame.side_data` no longer holds the frame in a reference cycle, which kept its picture buffer alive until gc ran. By :gh-user:`WyattBlue`. +- :class:`.MotionVectors` no longer memoizes the vectors it hands out, a cache that never evicted and put every one of them in a reference cycle with the side data, retaining 1640 objects for a single 640x360 frame. By :gh-user:`WyattBlue`. +- :attr:`.Stream.index_entries` is now a view built on access rather than an object allocated for every stream whether or not anything reads it, which also takes the stream out of a reference cycle with it. Fixes (:issue:`2425`) by :gh-user:`WyattBlue`. v19.0.0 diff --git a/av/frame.pxd b/av/frame.pxd index db37b56a3..8cb8aa528 100644 --- a/av/frame.pxd +++ b/av/frame.pxd @@ -1,7 +1,6 @@ cimport libav as lib from av.packet cimport Packet -from av.sidedata.sidedata cimport _SideDataContainer cdef class Frame: @@ -9,6 +8,5 @@ cdef class Frame: # We define our own time. cdef lib.AVRational _time_base cdef void _rebase_time(self, lib.AVRational) - cdef _SideDataContainer _side_data cdef void _copy_internal_attributes(self, Frame source, bint data_layout=?) cdef void _init_user_attributes(self) diff --git a/av/frame.py b/av/frame.py index 2dbe9642d..5abead282 100644 --- a/av/frame.py +++ b/av/frame.py @@ -181,9 +181,7 @@ def key_frame(self, v): @property def side_data(self): - if self._side_data is None: - self._side_data = SideDataContainer(self) - return self._side_data + return SideDataContainer(self) @property def metadata(self): diff --git a/av/frame.pyi b/av/frame.pyi index cd1d38d03..ef0729e37 100644 --- a/av/frame.pyi +++ b/av/frame.pyi @@ -11,7 +11,8 @@ class Frame: def time_base(self) -> AVRational: ... @time_base.setter def time_base(self, value: AVRational | Fraction | int) -> None: ... - side_data: SideDataContainer + @property + def side_data(self) -> SideDataContainer: ... opaque: object @property def metadata(self) -> dict[str, str]: ... diff --git a/av/sidedata/motionvectors.pxd b/av/sidedata/motionvectors.pxd index 078648325..1ee9a4039 100644 --- a/av/sidedata/motionvectors.pxd +++ b/av/sidedata/motionvectors.pxd @@ -5,7 +5,6 @@ from av.sidedata.sidedata cimport SideData cdef class MotionVectors(SideData): - cdef dict _vectors cdef Py_ssize_t _len diff --git a/av/sidedata/motionvectors.py b/av/sidedata/motionvectors.py index 0c3999309..f48aded22 100644 --- a/av/sidedata/motionvectors.py +++ b/av/sidedata/motionvectors.py @@ -11,7 +11,6 @@ class MotionVectors(SideData): def __init__(self, sentinel, frame: Frame, index: cython.int): SideData.__init__(self, sentinel, frame, index) - self._vectors = {} self._len = self.ptr.size // cython.sizeof(lib.AVMotionVector) def __repr__(self): @@ -29,15 +28,7 @@ def __getitem__(self, index: cython.Py_ssize_t): if index < 0 or index >= self._len: raise IndexError(index) - try: - return self._vectors[index] - except KeyError: - pass - - vector = self._vectors[index] = MotionVector( - _cinit_bypass_sentinel, self, index - ) - return vector + return MotionVector(_cinit_bypass_sentinel, self, index) def __iter__(self): """Iterate over all motion vectors.""" diff --git a/av/sidedata/motionvectors.pyi b/av/sidedata/motionvectors.pyi index 6b9971fb4..80690a4b1 100644 --- a/av/sidedata/motionvectors.pyi +++ b/av/sidedata/motionvectors.pyi @@ -1,14 +1,13 @@ -from typing import Any, overload +from collections.abc import Iterator +from typing import Any import numpy as np from .sidedata import SideData class MotionVectors(SideData): - @overload def __getitem__(self, index: int) -> MotionVector: ... - @overload - def __getitem__(self, index: slice) -> list[MotionVector]: ... + def __iter__(self) -> Iterator[MotionVector]: ... def __len__(self) -> int: ... def to_ndarray(self) -> np.ndarray[Any, Any]: ... diff --git a/av/sidedata/sidedata.pyi b/av/sidedata/sidedata.pyi index 7d60c9476..94f500b13 100644 --- a/av/sidedata/sidedata.pyi +++ b/av/sidedata/sidedata.pyi @@ -3,7 +3,6 @@ from enum import Enum from typing import ClassVar, cast, overload from av.buffer import Buffer -from av.frame import Frame class Type(Enum): PANSCAN = cast(ClassVar[Type], ...) @@ -43,7 +42,6 @@ class SideData(Buffer): type: Type class SideDataContainer(Mapping): - frame: Frame def __len__(self) -> int: ... def __iter__(self) -> Iterator[Type]: ... @overload