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
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ v19.0.3 (Unreleased)
Fixes:

- Support slicing ``Container.streams``, which raised ``TypeError`` despite being typed.
- ``HWConfig.methods`` no longer raises ``ValueError`` for configs that support several methods; ``HWConfigMethod`` is now an ``IntFlag``.
- ``CodecContext.is_encoder``, ``CodecContext.is_decoder``, ``Container.input_was_opened``, and ``VideoFormatComponent.is_chroma`` now return ``bool`` instead of ``int``.
- ``Frame.time_base`` returns ``AVRational(0, 1)`` instead of ``None`` when unset, like other ``AVRational`` properties.
- Fix type stubs that did not match runtime: ``average_rate``, ``base_rate``, and ``guessed_rate`` are only on ``VideoStream``; ``codec``, ``codec_tag``, and ``options`` are only on audio, video, and subtitle streams; ``Stream.codec_context`` may be ``None``; ``AudioStream.bit_rate``, ``InputContainer.start_time``, ``VideoFrame.time``, and ``FilterPad.context`` may be ``None``; ``Codec.hardware_configs`` is a tuple; ``CodecContext.delay`` is an ``int``; ``AudioStream.max_bit_rate`` is added; and the inaccessible ``Plane.frame``, ``Plane.index``, and ``Container.buffer_size`` are removed.

v19.0.2
-------
Expand Down
2 changes: 1 addition & 1 deletion av/audio/resampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def resample(self, frame: AudioFrame | None) -> list[AudioFrame]:
self.graph = Graph()
self.graph.threads = self.threads
extra_args = {}
if frame.time_base is not None:
if frame.time_base:
extra_args["time_base"] = f"{frame.time_base}"

abuffer = self.graph.add(
Expand Down
7 changes: 6 additions & 1 deletion av/audio/stream.pyi
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from collections.abc import Iterator
from typing import Literal

from av.codec.codec import Codec
from av.packet import Packet
from av.stream import Stream

Expand All @@ -24,9 +25,13 @@ class AudioStream(Stream):
def decode(self, packet: Packet | None = None) -> list[AudioFrame]: ...

# From codec context
codec: Codec
codec_tag: str
options: dict[str, str]
frame_size: int
sample_rate: int
bit_rate: int
bit_rate: int | None
max_bit_rate: int | None
rate: int
channels: int
type: Literal["audio"]
Expand Down
2 changes: 1 addition & 1 deletion av/codec/codec.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class Codec:
audio_rates: list[int] | None
video_formats: list[VideoFormat] | None
audio_formats: list[AudioFormat] | None
hardware_configs: list[HWConfig]
hardware_configs: tuple[HWConfig, ...]

@property
def properties(self) -> int: ...
Expand Down
4 changes: 2 additions & 2 deletions av/codec/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,13 +392,13 @@ def extradata_size(self):
def is_encoder(self):
if self.ptr is cython.NULL:
return False
return lib.av_codec_is_encoder(self.ptr.codec)
return bool(lib.av_codec_is_encoder(self.ptr.codec))

@property
def is_decoder(self):
if self.ptr is cython.NULL:
return False
return lib.av_codec_is_decoder(self.ptr.codec)
return bool(lib.av_codec_is_decoder(self.ptr.codec))

@property
def codec(self):
Expand Down
2 changes: 1 addition & 1 deletion av/codec/context.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ class CodecContext:
@property
def decoded_side_data(self) -> dict[FrameSideDataType, bytes]: ...
@property
def delay(self) -> bool: ...
def delay(self) -> int: ...
@property
def extradata_size(self) -> int: ...
@property
Expand Down
4 changes: 2 additions & 2 deletions av/codec/hwaccel.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import weakref
from enum import IntEnum
from enum import IntEnum, IntFlag

import cython
import cython.cimports.libav as lib
Expand Down Expand Up @@ -27,7 +27,7 @@ class HWDeviceType(IntEnum):
ohcodec = lib.AV_HWDEVICE_TYPE_OHCODEC


class HWConfigMethod(IntEnum):
class HWConfigMethod(IntFlag):
none = 0
hw_device_ctx = (
lib.AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX
Expand Down
4 changes: 2 additions & 2 deletions av/codec/hwaccel.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from enum import IntEnum
from enum import IntEnum, IntFlag
from typing import cast

from av.codec.codec import Codec
Expand All @@ -21,7 +21,7 @@ class HWDeviceType(IntEnum):
amf = cast(int, ...)
ohcodec = cast(int, ...)

class HWConfigMethod(IntEnum):
class HWConfigMethod(IntFlag):
none = cast(int, ...)
hw_device_ctx = cast(int, ...)
hw_frame_ctx = cast(int, ...)
Expand Down
2 changes: 1 addition & 1 deletion av/container/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ def flags(self, value: cython.int):

@property
def input_was_opened(self):
return self._myflag & 2
return bool(self._myflag & 2)

@property
def metadata(self) -> dict:
Expand Down
1 change: 0 additions & 1 deletion av/container/core.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ class Chapter(TypedDict):
class Container:
name: str
file: Any
buffer_size: int
io_open: Any
open_files: Any
format: ContainerFormat
Expand Down
2 changes: 1 addition & 1 deletion av/container/input.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ from .core import Container

@final
class InputContainer(Container):
start_time: int
start_time: int | None
start_time_realtime: int | None
duration: int | None
bit_rate: int
Expand Down
4 changes: 2 additions & 2 deletions av/filter/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ def add_buffer(
raise ValueError("missing height")
if format is None:
raise ValueError("missing format")
if time_base is None:
if not time_base:
warnings.warn(
"missing time_base. Guessing 1/1000 time base. "
"This is deprecated and may be removed in future releases.",
Expand Down Expand Up @@ -231,7 +231,7 @@ def add_abuffer(
raise ValueError("missing format")
if layout is None and channels is None:
raise ValueError("missing layout or channels")
if time_base is None:
if not time_base:
time_base = AVRational(1, sample_rate)

kwargs = {
Expand Down
3 changes: 2 additions & 1 deletion av/filter/link.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ from av.filter.graph import Graph

class FilterPad:
filter: Filter
context: FilterContext
context: FilterContext | None
is_input: bool
index: int
def __eq__(self, other: object) -> bool: ...
Expand All @@ -17,6 +17,7 @@ class FilterPad:
def type(self) -> str: ...

class FilterContextPad(FilterPad):
context: FilterContext
@property
def link(self) -> FilterLink | None: ...
@property
Expand Down
9 changes: 6 additions & 3 deletions av/frame.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
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.rational import AVRational, from_avrational
from cython.cimports.av.utils import (
avdict_to_dict,
to_avrational,
Expand Down Expand Up @@ -146,10 +146,13 @@ def time_base(self):
"""
The unit of time (in fractional seconds) in which timestamps are expressed.

``AVRational(0, 1)`` if unset.

:type: AVRational
"""
if self._time_base.num:
return from_avrational(self._time_base)
if not self._time_base.num:
return AVRational(0, 1)
return from_avrational(self._time_base)

@time_base.setter
def time_base(self, value):
Expand Down
3 changes: 0 additions & 3 deletions av/plane.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,4 @@ from .buffer import Buffer
from .frame import Frame

class Plane(Buffer):
frame: Frame
index: int

def __init__(self, frame: Frame, index: int) -> None: ...
14 changes: 4 additions & 10 deletions av/stream.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ from typing import Literal, cast

from av.rational import AVRational

from .codec import Codec, CodecContext
from .codec import CodecContext
from .container import Container
from .index import IndexEntries

Expand Down Expand Up @@ -41,23 +41,18 @@ class Discard(IntEnum):
class Stream:
name: str | None
container: Container
codec: Codec
codec_context: CodecContext
codec_context: CodecContext | None
metadata: dict[str, str]
@property
def index_entries(self) -> IndexEntries: ...
id: int
profiles: list[str]
profile: str | None
index: int
options: dict[str, object]
@property
def time_base(self) -> AVRational: ...
@time_base.setter
def time_base(self, value: AVRational | Fraction | int) -> None: ...
average_rate: AVRational
base_rate: AVRational
guessed_rate: AVRational
start_time: int | None
duration: int | None
disposition: Disposition
Expand All @@ -66,14 +61,13 @@ class Stream:
language: str | None
type: Literal["video", "audio", "data", "subtitle", "attachment", "unknown"]

# From context
codec_tag: str

class DataStream(Stream):
codec_context: None
type: Literal["data"]
name: str | None

class AttachmentStream(Stream):
codec_context: None
type: Literal["attachment"]
@property
def mimetype(self) -> str | None: ...
Expand Down
6 changes: 6 additions & 0 deletions av/subtitles/stream.pyi
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import final

from av.codec.codec import Codec
from av.packet import Packet
from av.stream import Stream
from av.subtitles.subtitle import AssSubtitle, BitmapSubtitle, SubtitleSet
Expand All @@ -9,6 +10,11 @@ from .codeccontext import SubtitleCodecContext
@final
class SubtitleStream(Stream):
codec_context: SubtitleCodecContext

# From codec context
codec: Codec
codec_tag: str
options: dict[str, str]
def decode(
self, packet: Packet | None = None
) -> list[AssSubtitle] | list[BitmapSubtitle]: ...
Expand Down
2 changes: 1 addition & 1 deletion av/video/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ def is_luma(self):
@property
def is_chroma(self):
"""Is this component a chroma channel?"""
return (self.index == 1 or self.index == 2) and (
return (self.index == 1 or self.index == 2) and bool(
self.format.ptr.log2_chroma_w or self.format.ptr.log2_chroma_h
)

Expand Down
2 changes: 1 addition & 1 deletion av/video/frame.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class VideoFrame(Frame):
@property
def sw_format(self) -> VideoFormat | None: ...
@property
def time(self) -> float: ...
def time(self) -> float | None: ...
@property
def width(self) -> int: ...
@property
Expand Down
8 changes: 8 additions & 0 deletions av/video/stream.pyi
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from collections.abc import Iterator, Sequence
from typing import Literal

from av.codec.codec import Codec
from av.codec.context import ThreadType
from av.packet import Packet
from av.rational import AVRational
Expand All @@ -26,7 +27,14 @@ class VideoStream(Stream):
self, degrees: float, hflip: bool = ..., vflip: bool = ...
) -> None: ...

average_rate: AVRational
base_rate: AVRational
guessed_rate: AVRational

# from codec context
codec: Codec
codec_tag: str
options: dict[str, str]
format: VideoFormat
thread_count: int
thread_type: ThreadType
Expand Down
2 changes: 1 addition & 1 deletion tests/test_audiofifo.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,5 +113,5 @@ def test_missing_time_base(self) -> None:
oframe = fifo.read(512)

assert oframe is not None
assert oframe.pts is None and oframe.time_base is None
assert oframe.pts is None and not oframe.time_base
assert oframe.sample_rate == iframe.sample_rate
14 changes: 14 additions & 0 deletions tests/test_codec.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from av import AudioFormat, Codec, VideoFormat, codecs_available
from av.codec import PixFmtLoss, find_best_pix_fmt_of_list
from av.codec.codec import UnknownCodecError
from av.codec.hwaccel import HWConfigMethod


def test_codec_bogus() -> None:
Expand Down Expand Up @@ -165,3 +166,16 @@ def test_find_best_pix_fmt_of_list_loss_flags() -> None:
_, loss = find_best_pix_fmt_of_list(["gray"], "rgb24")
assert isinstance(loss, PixFmtLoss)
assert loss & PixFmtLoss.CHROMA


def test_codec_hardware_config_methods() -> None:
# methods is a bitmask; a config supporting several of them must not raise.
for name in codecs_available:
for mode in ("r", "w"):
try:
codec = Codec(name, mode)
except UnknownCodecError:
continue
assert isinstance(codec.hardware_configs, tuple)
for config in codec.hardware_configs:
assert isinstance(config.methods, HWConfigMethod)
1 change: 1 addition & 0 deletions tests/test_decode.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ def test_decode_stream_without_codec_context(self) -> None:
def test_mux_stream_without_codec_context(self) -> None:
with av.open(io.BytesIO(), "w", format="mp4") as output:
stream = output.add_mux_stream("h264", width=16, height=16)
assert isinstance(stream, av.VideoStream)
assert stream.codec_context is None
with pytest.raises(av.EncoderNotFoundError):
stream.encode(None)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_file_probing.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ def test_container_probing(self) -> None:
assert len(self.file.streams) == 1

def test_stream_probing(self) -> None:
stream = self.file.streams[0]
stream = self.file.streams.subtitles[0]

assert str(stream).startswith("<av.SubtitleStream #0 subtitle/mov_text at ")

Expand Down
1 change: 1 addition & 0 deletions tests/test_remux.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def test_add_mux_stream_video() -> None:
out_stream = output.add_mux_stream(
in_stream.codec_context.name, width=width, height=height
)
assert isinstance(out_stream, av.VideoStream)
assert out_stream.codec_context is None
assert out_stream.type == "video"

Expand Down
Loading