diff --git a/av/container/core.pyi b/av/container/core.pyi index c33f3a4dc..862f0d326 100644 --- a/av/container/core.pyi +++ b/av/container/core.pyi @@ -4,6 +4,7 @@ from types import TracebackType from typing import Any, Callable, Literal, Type, overload from av.enum import EnumFlag +from av.format import ContainerFormat from .input import InputContainer from .output import OutputContainer @@ -36,7 +37,7 @@ class Container: input_was_opened: bool io_open: Any open_files: Any - format: str | None + format: ContainerFormat options: dict[str, str] container_options: dict[str, str] stream_options: list[str] diff --git a/av/container/output.pyi b/av/container/output.pyi index 0400dadbb..6df32c897 100644 --- a/av/container/output.pyi +++ b/av/container/output.pyi @@ -17,5 +17,5 @@ class OutputContainer(Container): ) -> Stream: ... def start_encoding(self) -> None: ... def close(self) -> None: ... - def mux(self, packets: Sequence[Packet]) -> None: ... + def mux(self, packets: Packet | Sequence[Packet]) -> None: ... def mux_one(self, packet: Packet) -> None: ... diff --git a/av/format.pyi b/av/format.pyi index 874920928..d2aef4764 100644 --- a/av/format.pyi +++ b/av/format.pyi @@ -22,9 +22,10 @@ class Flags(EnumFlag): SEEK_TO_PTS: int class ContainerFormat: + name: str + long_name: str is_input: bool is_output: bool - long_name: str # flags no_file: int diff --git a/av/video/plane.pyi b/av/video/plane.pyi index 9bc1f0d77..e4a0a206c 100644 --- a/av/video/plane.pyi +++ b/av/video/plane.pyi @@ -1,6 +1,8 @@ +from av.plane import Plane + from .frame import VideoFrame -class VideoPlane: +class VideoPlane(Plane): line_size: int width: int height: int diff --git a/av/video/stream.pyi b/av/video/stream.pyi index 7ff1a4034..4b78efd06 100644 --- a/av/video/stream.pyi +++ b/av/video/stream.pyi @@ -1,5 +1,5 @@ from fractions import Fraction -from typing import Any, Literal +from typing import Any, Iterator, Literal from av.packet import Packet from av.stream import Stream @@ -36,4 +36,5 @@ class VideoStream(Stream): type: Literal["video"] def encode(self, frame: VideoFrame | None = None) -> list[Packet]: ... + def encode_lazy(self, frame: VideoFrame | None = None) -> Iterator[Packet]: ... def decode(self, packet: Packet | None = None) -> list[VideoFrame]: ... diff --git a/tests/common.py b/tests/common.py index e2b5f4e16..6c2b2d2e1 100644 --- a/tests/common.py +++ b/tests/common.py @@ -8,17 +8,18 @@ from av.datasets import fate as fate_suite try: - import PIL.Image as Image - import PIL.ImageFilter as ImageFilter + import PIL # noqa + + has_pillow = True except ImportError: - Image = ImageFilter = None + has_pillow = False is_windows = os.name == "nt" skip_tests = frozenset(os.environ.get("PYAV_SKIP_TESTS", "").split(",")) -def makedirs(path): +def makedirs(path: str) -> None: try: os.makedirs(path) except OSError as e: @@ -29,23 +30,21 @@ def makedirs(path): _start_time = datetime.datetime.now() -def _sandbox(timed=False): +def _sandbox(timed: bool = False) -> str: root = os.path.abspath(os.path.join(__file__, "..", "..", "sandbox")) - sandbox = ( - os.path.join( - root, - _start_time.strftime("%Y%m%d-%H%M%S"), - ) - if timed - else root - ) + if timed: + sandbox = os.path.join(root, _start_time.strftime("%Y%m%d-%H%M%S")) + else: + sandbox = root + if not os.path.exists(sandbox): os.makedirs(sandbox) + return sandbox -def asset(*args): +def asset(*args: str) -> str: adir = os.path.dirname(__file__) return os.path.abspath(os.path.join(adir, "assets", *args)) @@ -148,6 +147,8 @@ def assertNdarraysEqual(self, a, b): self.fail("ndarrays contents differ\n%s" % msg) def assertImagesAlmostEqual(self, a, b, epsilon=0.1, *args): + import PIL.ImageFilter as ImageFilter + self.assertEqual(a.size, b.size, "sizes dont match") a = a.filter(ImageFilter.BLUR).getdata() b = b.filter(ImageFilter.BLUR).getdata() diff --git a/tests/test_colorspace.py b/tests/test_colorspace.py index 99c9f2fa2..571e7bd2b 100644 --- a/tests/test_colorspace.py +++ b/tests/test_colorspace.py @@ -5,7 +5,7 @@ class TestColorSpace(TestCase): - def test_penguin_joke(self): + def test_penguin_joke(self) -> None: container = av.open( fate_suite("amv/MTV_high_res_320x240_sample_Penguin_Joke_MTV_from_WMV.amv") ) @@ -26,7 +26,7 @@ def test_penguin_joke(self): self.assertEqual(frame.colorspace, Colorspace.ITU601) return - def test_sky_timelapse(self): + def test_sky_timelapse(self) -> None: container = av.open( av.datasets.curated("pexels/time-lapse-video-of-night-sky-857195.mp4") ) diff --git a/tests/test_encode.py b/tests/test_encode.py index d0939470a..e4336c735 100644 --- a/tests/test_encode.py +++ b/tests/test_encode.py @@ -1,6 +1,7 @@ import io import math from fractions import Fraction +from typing import cast from unittest import SkipTest import numpy as np @@ -10,21 +11,23 @@ from av.audio.stream import AudioStream from av.video.stream import VideoStream -from .common import Image, TestCase, fate_suite +from .common import TestCase, fate_suite, has_pillow WIDTH = 320 HEIGHT = 240 DURATION = 48 -def write_rgb_rotate(output): - if not Image: - raise SkipTest() +def write_rgb_rotate(output: av.container.OutputContainer) -> None: + if not has_pillow: + raise SkipTest("Don't have Pillow") + + import PIL.Image as Image output.metadata["title"] = "container" output.metadata["key"] = "value" - stream = output.add_stream("mpeg4", 24) + stream = cast(VideoStream, output.add_stream("mpeg4", 24)) stream.width = WIDTH stream.height = HEIGHT stream.pix_fmt = "yuv420p" diff --git a/tests/test_enums.py b/tests/test_enums.py index 39bf856ad..8e4839a77 100644 --- a/tests/test_enums.py +++ b/tests/test_enums.py @@ -10,15 +10,7 @@ class TestEnums(TestCase): def define_foobar(self, **kwargs): - return define_enum( - "Foobar", - __name__, - ( - ("FOO", 1), - ("BAR", 2), - ), - **kwargs - ) + return define_enum("Foobar", __name__, (("FOO", 1), ("BAR", 2)), **kwargs) def test_basics(self): cls = self.define_foobar() diff --git a/tests/test_filters.py b/tests/test_filters.py index dd090fe1d..326ef7fb9 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -1,6 +1,5 @@ import errno from fractions import Fraction -from unittest import SkipTest import numpy as np @@ -9,7 +8,7 @@ from av.audio.frame import format_dtypes from av.filter import Filter, Graph -from .common import Image, TestCase, fate_suite +from .common import TestCase, has_pillow def generate_audio_frame( @@ -86,7 +85,7 @@ def test_generator_graph(self): frame = sink.pull() self.assertIsInstance(frame, VideoFrame) - if Image: + if has_pillow: frame.to_image().save(self.sandboxed("mandelbrot2.png")) def test_auto_find_sink(self): @@ -97,7 +96,7 @@ def test_auto_find_sink(self): frame = graph.pull() - if Image: + if has_pillow: frame.to_image().save(self.sandboxed("mandelbrot3.png")) def test_delegate_sink(self): @@ -108,43 +107,9 @@ def test_delegate_sink(self): frame = src.pull() - if Image: + if has_pillow: frame.to_image().save(self.sandboxed("mandelbrot4.png")) - def test_haldclut_graph(self): - raise SkipTest() - - graph = Graph() - - img = Image.open(fate_suite("png1/lena-rgb24.png")) - frame = VideoFrame.from_image(img) - img_source = graph.add_buffer(frame) - - hald_img = Image.open("hald_7.png") - hald_frame = VideoFrame.from_image(hald_img) - hald_source = graph.add_buffer(hald_frame) - - hald_filter = graph.add("haldclut") - - sink = graph.add("buffersink") - - img_source.link(0, hald_filter, 0) - hald_source.link(0, hald_filter, 1) - hald_filter.link(0, sink, 0) - graph.config() - - self.assertIs(img_source.outputs[0].linked_to, hald_filter.inputs[0]) - self.assertIs(hald_source.outputs[0].linked_to, hald_filter.inputs[1]) - self.assertIs(hald_filter.outputs[0].linked_to, sink.inputs[0]) - - hald_source.push(hald_frame) - - img_source.push(frame) - - frame = sink.pull() - self.assertIsInstance(frame, VideoFrame) - frame.to_image().save(self.sandboxed("filtered.png")) - def test_audio_buffer_sink(self): graph = Graph() audio_buffer = graph.add_abuffer( diff --git a/tests/test_python_io.py b/tests/test_python_io.py index 84e94d7a3..42b8dd132 100644 --- a/tests/test_python_io.py +++ b/tests/test_python_io.py @@ -3,7 +3,14 @@ import av -from .common import Image, MethodLogger, TestCase, fate_png, fate_suite, run_in_sandbox +from .common import ( + MethodLogger, + TestCase, + fate_png, + fate_suite, + has_pillow, + run_in_sandbox, +) from .test_encode import assert_rgb_rotate, write_rgb_rotate @@ -206,9 +213,11 @@ def test_writing_to_custom_io_dash(self): assert_rgb_rotate(self, container, is_dash=True) def test_writing_to_custom_io_image2(self): - if not Image: + if not has_pillow: raise SkipTest() + import PIL.Image as Image + # Custom I/O that opens file and logs calls wrapped_custom_io = CustomIOLogger() @@ -263,7 +272,7 @@ def test_writing_to_custom_io_image2(self): self.assertEqual(stream.format.width, width) self.assertEqual(stream.format.height, height) - def test_writing_to_file(self): + def test_writing_to_file(self) -> None: path = self.sandboxed("writing.mp4") with open(path, "wb") as fh: @@ -273,7 +282,7 @@ def test_writing_to_file(self): with av.open(path) as container: assert_rgb_rotate(self, container) - def test_writing_to_pipe_readonly(self): + def test_writing_to_pipe_readonly(self) -> None: buf = ReadOnlyPipe() with self.assertRaises(ValueError) as cm: self.write(buf) @@ -291,10 +300,10 @@ def test_writing_to_pipe_writeonly(self): str(cm.exception), ) - def read(self, fh, seekable=True): + def read(self, fh, seekable: bool = True) -> None: wrapped = MethodLogger(fh) - with av.open(wrapped) as container: + with av.open(wrapped, "r") as container: self.assertEqual(container.format.name, "mpegts") self.assertEqual( container.format.long_name, "MPEG-TS (MPEG-2 Transport Stream)" diff --git a/tests/test_videoframe.py b/tests/test_videoframe.py index 32f6c6cd3..28540ccc3 100644 --- a/tests/test_videoframe.py +++ b/tests/test_videoframe.py @@ -4,7 +4,7 @@ from av import VideoFrame -from .common import Image, TestCase, fate_png +from .common import TestCase, fate_png, has_pillow class TestVideoFrameConstructors(TestCase): @@ -90,10 +90,12 @@ def test_memoryview_read(self): class TestVideoFrameImage(TestCase): def setUp(self): - if not Image: + if not has_pillow: raise SkipTest() def test_roundtrip(self): + import PIL.Image as Image + image = Image.open(fate_png()) frame = VideoFrame.from_image(image) img = frame.to_image()