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
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ Fixes:
- ``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.
- Writing to a network URL no longer blocks every other Python thread, and ``timeout`` now applies to opening an output container. ``avio_open()``, ``avformat_write_header()``, ``av_write_trailer()``, and ``avio_closep()`` held the GIL, so an unreachable RTMP server froze the whole process, and the interrupt callback was only installed for demuxing, so nothing could end the wait. :meth:`.OutputContainer.close` now raises rather than freeing a context another thread is still muxing or closing. By :gh-user:`adrianrfreedman` in (:pr:`2412`).
- ``timeout`` now applies to muxing and closing an output container, not just to opening it. Only opening armed the interrupt callback, so a peer that accepted the connection and then stopped reading left ``av_interleaved_write_frame()`` and ``av_write_trailer()`` blocked forever. Each mux gets the full timeout, and a close shares one across writing the trailer and flushing, so neither can outlast it. Only unseekable outputs are covered: writing a seekable file takes as long as the file is big, so a deadline meant for a peer would abandon it part-written. By :gh-user:`adrianrfreedman` in (:pr:`2414`).
- :meth:`.HWAccel.create` now passes ``flags`` on to the context it creates. They were dropped, so ``av_hwdevice_ctx_create()`` always got ``0`` and CUDA's ``AV_CUDA_USE_PRIMARY_CONTEXT`` could not be requested through them.


18.X and Below
Expand Down
1 change: 0 additions & 1 deletion av/buffer.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,3 @@ class Buffer:
buffer_ptr: int
def update(self, input: bytes) -> None: ...
def __buffer__(self, flags: int) -> memoryview: ...
def __bytes__(self) -> bytes: ...
1 change: 1 addition & 0 deletions av/codec/hwaccel.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ def create(self, codec: Codec, for_encoding: bool = False) -> HWAccel:
device=self._device,
allow_software_fallback=self.allow_software_fallback,
options=self.options,
flags=self.flags,
is_hw_owned=self.is_hw_owned,
)
ret._initialize_hw_context(codec, for_encoding=for_encoding)
Expand Down
5 changes: 3 additions & 2 deletions av/codec/hwaccel.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,17 @@ class HWAccel:
options: dict[str, object]
flags: int
allow_software_fallback: bool
config: HWConfig | None

@property
def is_hw_owned(self) -> bool: ...
@property
def device_id(self) -> int: ...
def __init__(
self,
device_type: str | HWDeviceType,
device_type: str | int | HWDeviceType,
device: str | int | None = None,
allow_software_fallback: bool = False,
allow_software_fallback: bool = True,
options: dict[str, object] | None = None,
flags: int | None = None,
is_hw_owned: bool = False,
Expand Down
1 change: 0 additions & 1 deletion av/container/input.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,3 @@ class InputContainer(Container):
unsupported_frame_offset: bool = False,
unsupported_byte_offset: bool = False,
) -> None: ...
def flush_buffers(self) -> None: ...
2 changes: 1 addition & 1 deletion av/error.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,4 @@ class OSError(FFmpegError, builtins.OSError): ...
class PermissionError(FFmpegError, builtins.PermissionError): ...
class ProcessLookupError(FFmpegError, builtins.ProcessLookupError): ...
class TimeoutError(FFmpegError, builtins.TimeoutError): ...
class ArgumentError(FFmpegError): ...
class ArgumentError(FFmpegError, builtins.ValueError): ...
2 changes: 2 additions & 0 deletions av/filter/context.pyi
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from av.filter import Graph
from av.filter.filter import Filter
from av.filter.link import FilterContextPad
from av.frame import Frame

class FilterContext:
name: str | None
filter: Filter

@property
def inputs(self) -> tuple[FilterContextPad, ...]: ...
Expand Down
3 changes: 2 additions & 1 deletion av/subtitles/subtitle.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ class SubtitleSet:
def __iter__(self) -> Iterator[Subtitle]: ...
def __getitem__(self, i: int) -> Subtitle: ...

class Subtitle: ...
class Subtitle:
type: bytes

class BitmapSubtitle(Subtitle):
type: Literal[b"bitmap"]
Expand Down
2 changes: 2 additions & 0 deletions av/video/codeccontext.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ from av.rational import AVRational

from .format import VideoFormat
from .frame import VideoFrame
from .reformatter import VideoReformatter

class VideoCodecContext(CodecContext):
format: VideoFormat | None
width: int
height: int
bits_per_coded_sample: int
pix_fmt: str | None
reformatter: VideoReformatter | None
@property
def sw_format(self) -> VideoFormat | None: ...
@sw_format.setter
Expand Down
9 changes: 0 additions & 9 deletions av/video/stream.pyi
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from collections.abc import Iterator, Sequence
from fractions import Fraction
from typing import Literal

from av.codec.context import ThreadType
Expand Down Expand Up @@ -35,14 +34,6 @@ class VideoStream(Stream):
height: int
bits_per_coded_sample: int
pix_fmt: str | None
@property
def framerate(self) -> AVRational: ...
@framerate.setter
def framerate(self, value: AVRational | Fraction | int) -> None: ...
@property
def rate(self) -> AVRational: ...
@rate.setter
def rate(self, value: AVRational | Fraction | int) -> None: ...
gop_size: int
has_b_frames: bool
max_b_frames: int
Expand Down
19 changes: 19 additions & 0 deletions tests/test_decode.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,25 @@ def test_hardware_decode(self) -> None:

assert frame_count == video_stream.frames

def test_hardware_create_keeps_flags(self) -> None:
hwdevices_available = av.codec.hwaccel.hwdevices_available()
if "HWACCEL_DEVICE_TYPE" not in os.environ:
pytest.skip(
"Set the HWACCEL_DEVICE_TYPE to run this test. "
f"Options are {' '.join(hwdevices_available)}"
)

HWACCEL_DEVICE_TYPE = os.environ["HWACCEL_DEVICE_TYPE"]
assert HWACCEL_DEVICE_TYPE in hwdevices_available, (
f"{HWACCEL_DEVICE_TYPE} not available"
)

# AV_CUDA_USE_PRIMARY_CONTEXT. It is the only flag FFmpeg defines for
# device creation, and every other device type ignores it.
hwaccel = av.codec.hwaccel.HWAccel(device_type=HWACCEL_DEVICE_TYPE, flags=1)
created = hwaccel.create(av.Codec("h264", "r"))
assert created.flags == 1


@pytest.mark.parametrize("is_hw_owned", [False, True])
def test_hardware_decode_download_preserves_frame_props(is_hw_owned: bool) -> None:
Expand Down