├── .github └── workflows │ ├── smoke.yml │ └── tests.yml ├── .gitignore ├── AUTHORS.py ├── AUTHORS.rst ├── CHANGELOG.rst ├── LICENSE.txt ├── MANIFEST.in ├── Makefile ├── README.md ├── av ├── __init__.pxd ├── __init__.py ├── __main__.py ├── _core.pyi ├── _core.pyx ├── about.py ├── attachments │ ├── __init__.py │ ├── stream.pxd │ ├── stream.pyi │ └── stream.pyx ├── audio │ ├── __init__.pxd │ ├── __init__.py │ ├── __init__.pyi │ ├── codeccontext.pxd │ ├── codeccontext.py │ ├── codeccontext.pyi │ ├── fifo.pxd │ ├── fifo.pyi │ ├── fifo.pyx │ ├── format.pxd │ ├── format.pyi │ ├── format.pyx │ ├── frame.pxd │ ├── frame.pyi │ ├── frame.pyx │ ├── layout.pxd │ ├── layout.pyi │ ├── layout.pyx │ ├── plane.pxd │ ├── plane.pyi │ ├── plane.pyx │ ├── resampler.pxd │ ├── resampler.pyi │ ├── resampler.pyx │ ├── stream.pxd │ ├── stream.pyi │ └── stream.pyx ├── bitstream.pxd ├── bitstream.pyi ├── bitstream.pyx ├── buffer.pxd ├── buffer.pyi ├── buffer.pyx ├── bytesource.pxd ├── bytesource.pyx ├── codec │ ├── __init__.pxd │ ├── __init__.py │ ├── codec.pxd │ ├── codec.pyi │ ├── codec.pyx │ ├── context.pxd │ ├── context.pyi │ ├── context.pyx │ ├── hwaccel.pxd │ ├── hwaccel.pyi │ └── hwaccel.pyx ├── container │ ├── __init__.pxd │ ├── __init__.py │ ├── __init__.pyi │ ├── core.pxd │ ├── core.pyi │ ├── core.pyx │ ├── input.pxd │ ├── input.pyi │ ├── input.pyx │ ├── output.pxd │ ├── output.py │ ├── output.pyi │ ├── pyio.pxd │ ├── pyio.pyx │ ├── streams.pxd │ ├── streams.pyi │ └── streams.pyx ├── data │ ├── __init__.pxd │ ├── __init__.py │ ├── stream.pxd │ ├── stream.pyi │ └── stream.pyx ├── datasets.py ├── descriptor.pxd ├── descriptor.pyi ├── descriptor.pyx ├── dictionary.pxd ├── dictionary.pyi ├── dictionary.pyx ├── error.pxd ├── error.pyi ├── error.pyx ├── filter │ ├── __init__.pxd │ ├── __init__.py │ ├── __init__.pyi │ ├── context.pxd │ ├── context.pyi │ ├── context.pyx │ ├── filter.pxd │ ├── filter.pyi │ ├── filter.pyx │ ├── graph.pxd │ ├── graph.pyi │ ├── graph.pyx │ ├── link.pxd │ ├── link.pyi │ ├── link.pyx │ ├── loudnorm.pxd │ ├── loudnorm.py │ ├── loudnorm.pyi │ ├── loudnorm_impl.c │ ├── loudnorm_impl.h │ ├── pad.pxd │ ├── pad.pyi │ └── pad.pyx ├── format.pxd ├── format.pyi ├── format.pyx ├── frame.pxd ├── frame.pyi ├── frame.pyx ├── logging.pxd ├── logging.pyi ├── logging.pyx ├── opaque.pxd ├── opaque.pyx ├── option.pxd ├── option.pyi ├── option.pyx ├── packet.pxd ├── packet.py ├── packet.pyi ├── plane.pxd ├── plane.pyi ├── plane.pyx ├── py.typed ├── sidedata │ ├── __init__.pxd │ ├── __init__.py │ ├── motionvectors.pxd │ ├── motionvectors.pyi │ ├── motionvectors.pyx │ ├── sidedata.pxd │ ├── sidedata.pyi │ └── sidedata.pyx ├── stream.pxd ├── stream.pyi ├── stream.pyx ├── subtitles │ ├── __init__.pxd │ ├── __init__.py │ ├── codeccontext.pxd │ ├── codeccontext.pyi │ ├── codeccontext.pyx │ ├── stream.pxd │ ├── stream.pyi │ ├── stream.pyx │ ├── subtitle.pxd │ ├── subtitle.pyi │ └── subtitle.pyx ├── utils.pxd ├── utils.pyx └── video │ ├── __init__.pxd │ ├── __init__.py │ ├── __init__.pyi │ ├── codeccontext.pxd │ ├── codeccontext.pyi │ ├── codeccontext.pyx │ ├── format.pxd │ ├── format.pyi │ ├── format.pyx │ ├── frame.pxd │ ├── frame.pyi │ ├── frame.pyx │ ├── plane.pxd │ ├── plane.pyi │ ├── plane.pyx │ ├── reformatter.pxd │ ├── reformatter.pyi │ ├── reformatter.pyx │ ├── stream.pxd │ ├── stream.pyi │ └── stream.pyx ├── docs ├── Makefile ├── _static │ ├── custom.css │ ├── examples │ │ └── numpy │ │ │ └── barcode.jpg │ ├── favicon.png │ └── logo.webp ├── _themes │ └── pyav │ │ ├── layout.html │ │ └── theme.conf ├── api │ ├── _globals.rst │ ├── attachments.rst │ ├── audio.rst │ ├── bitstream.rst │ ├── buffer.rst │ ├── codec.rst │ ├── container.rst │ ├── error.rst │ ├── filter.rst │ ├── frame.rst │ ├── packet.rst │ ├── plane.rst │ ├── sidedata.rst │ ├── stream.rst │ ├── subtitles.rst │ ├── time.rst │ ├── utils.rst │ └── video.rst ├── conf.py ├── cookbook │ ├── audio.rst │ ├── basics.rst │ ├── numpy.rst │ └── subtitles.rst ├── development │ ├── changelog.rst │ ├── contributors.rst │ └── license.rst ├── index.rst └── overview │ ├── caveats.rst │ └── installation.rst ├── examples ├── audio │ └── atempo.py ├── basics │ ├── hw_decode.py │ ├── parse.py │ ├── record_facecam.py │ ├── record_screen.py │ ├── remux.py │ ├── save_keyframes.py │ └── thread_type.py ├── numpy │ ├── barcode.py │ ├── generate_video.py │ └── generate_video_with_pts.py └── subtitles │ └── remux.py ├── include ├── libav.pxd ├── libavcodec │ ├── avcodec.pxd │ ├── bsf.pxd │ └── hwaccel.pxd ├── libavdevice │ └── avdevice.pxd ├── libavfilter │ ├── avfilter.pxd │ ├── avfiltergraph.pxd │ ├── buffersink.pxd │ └── buffersrc.pxd ├── libavformat │ └── avformat.pxd ├── libavutil │ ├── avutil.pxd │ ├── buffer.pxd │ ├── channel_layout.pxd │ ├── dict.pxd │ ├── error.pxd │ ├── frame.pxd │ ├── hwcontext.pxd │ ├── motion_vector.pxd │ └── samplefmt.pxd ├── libswresample │ └── swresample.pxd └── libswscale │ └── swscale.pxd ├── pyproject.toml ├── scripts ├── activate.sh ├── build ├── build-deps ├── comptime.py ├── fetch-vendor.py ├── ffmpeg-7.0.json ├── ffmpeg-7.1.json └── test ├── setup.py └── tests ├── __init__.py ├── common.py ├── test_audiofifo.py ├── test_audioformat.py ├── test_audioframe.py ├── test_audiolayout.py ├── test_audioresampler.py ├── test_bitstream.py ├── test_codec.py ├── test_codec_context.py ├── test_colorspace.py ├── test_containerformat.py ├── test_decode.py ├── test_dictionary.py ├── test_doctests.py ├── test_encode.py ├── test_errors.py ├── test_file_probing.py ├── test_filters.py ├── test_logging.py ├── test_open.py ├── test_options.py ├── test_packet.py ├── test_python_io.py ├── test_remux.py ├── test_seek.py ├── test_streams.py ├── test_subtitles.py ├── test_timeout.py ├── test_videoformat.py └── test_videoframe.py /.gitignore: -------------------------------------------------------------------------------- 1 | # General 2 | *~ 3 | .DS_Store 4 | .nfs.* 5 | ._* 6 | 7 | # Environment 8 | /.eggs 9 | /tmp 10 | /vendor 11 | /venv 12 | /venvs 13 | 14 | # Build products 15 | *.dll 16 | *.egg-info 17 | *.lib 18 | *.pyc 19 | *.so 20 | /*.sdf 21 | /*.sln 22 | /*.suo 23 | /av/**/*.exp 24 | /av/**/*.lib 25 | /av/**/*.pdb 26 | /av/**/*.pyd 27 | /build 28 | /dist 29 | /docs/_build 30 | /ipch 31 | /msvc-projects 32 | /src 33 | /docs/_ffmpeg 34 | 35 | # Testing. 36 | *.spyderproject 37 | .idea 38 | /sandbox 39 | /tests/assets 40 | /tests/samples 41 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright retained by original committers. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | * Redistributions of source code must retain the above copyright 6 | notice, this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above copyright 8 | notice, this list of conditions and the following disclaimer in the 9 | documentation and/or other materials provided with the distribution. 10 | * Neither the name of the project nor the names of its contributors may be 11 | used to endorse or promote products derived from this software without 12 | specific prior written permission. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, 18 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 19 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 22 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 23 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt *.md 2 | recursive-include av *.pyx *.pxd 3 | recursive-include docs *.rst *.py 4 | recursive-include examples *.py 5 | recursive-include include *.pxd *.h 6 | recursive-include src/av *.c *.h 7 | recursive-include tests *.py -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | LDFLAGS ?= "" 2 | CFLAGS ?= "-O0 -Wno-incompatible-pointer-types -Wno-unreachable-code" 3 | 4 | PYAV_PYTHON ?= python 5 | PYAV_PIP ?= pip 6 | PYTHON := $(PYAV_PYTHON) 7 | PIP := $(PYAV_PIP) 8 | 9 | 10 | .PHONY: default build clean fate-suite lint test 11 | 12 | default: build 13 | 14 | 15 | build: 16 | $(PIP) install -U --pre cython setuptools 17 | CFLAGS=$(CFLAGS) LDFLAGS=$(LDFLAGS) $(PYTHON) setup.py build_ext --inplace --debug 18 | 19 | clean: 20 | - find av -name '*.so' -delete 21 | - rm -rf build 22 | - rm -rf sandbox 23 | - rm -rf src 24 | - make -C docs clean 25 | 26 | fate-suite: 27 | # Grab ALL of the samples from the ffmpeg site. 28 | rsync -vrltLW rsync://fate-suite.ffmpeg.org/fate-suite/ tests/assets/fate-suite/ 29 | 30 | lint: 31 | $(PIP) install -U ruff isort pillow numpy mypy==1.15.0 pytest 32 | ruff format --check av examples tests setup.py 33 | isort --check-only --diff av examples tests 34 | mypy av tests 35 | 36 | test: 37 | $(PIP) install --upgrade cython numpy pillow pytest 38 | $(PYTHON) -m pytest 39 | -------------------------------------------------------------------------------- /av/__init__.pxd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyAV-Org/PyAV/633f237a6916d17b85fe937f95e28af651fbaf0e/av/__init__.pxd -------------------------------------------------------------------------------- /av/__init__.py: -------------------------------------------------------------------------------- 1 | # MUST import the core before anything else in order to initialize the underlying 2 | # library that is being wrapped. 3 | from av._core import time_base, library_versions, ffmpeg_version_info 4 | 5 | # Capture logging (by importing it). 6 | from av import logging 7 | 8 | # For convenience, import all common attributes. 9 | from av.about import __version__ 10 | from av.audio.codeccontext import AudioCodecContext 11 | from av.audio.fifo import AudioFifo 12 | from av.audio.format import AudioFormat 13 | from av.audio.frame import AudioFrame 14 | from av.audio.layout import AudioLayout 15 | from av.audio.resampler import AudioResampler 16 | from av.audio.stream import AudioStream 17 | from av.bitstream import BitStreamFilterContext, bitstream_filters_available 18 | from av.codec.codec import Codec, codecs_available 19 | from av.codec.context import CodecContext 20 | from av.codec.hwaccel import HWConfig 21 | from av.container import open 22 | from av.format import ContainerFormat, formats_available 23 | from av.packet import Packet 24 | from av.error import * # noqa: F403; This is limited to exception types. 25 | from av.video.codeccontext import VideoCodecContext 26 | from av.video.format import VideoFormat 27 | from av.video.frame import VideoFrame 28 | from av.video.stream import VideoStream 29 | 30 | __all__ = ( 31 | "__version__", 32 | "time_base", 33 | "ffmpeg_version_info", 34 | "library_versions", 35 | "AudioCodecContext", 36 | "AudioFifo", 37 | "AudioFormat", 38 | "AudioFrame", 39 | "AudioLayout", 40 | "AudioResampler", 41 | "AudioStream", 42 | "BitStreamFilterContext", 43 | "bitstream_filters_available", 44 | "Codec", 45 | "codecs_available", 46 | "CodecContext", 47 | "open", 48 | "ContainerFormat", 49 | "formats_available", 50 | "Packet", 51 | "VideoCodecContext", 52 | "VideoFormat", 53 | "VideoFrame", 54 | "VideoStream", 55 | ) 56 | 57 | 58 | def get_include() -> str: 59 | """ 60 | Returns the path to the `include` folder to be used when building extensions to av. 61 | """ 62 | import os 63 | 64 | # Installed package 65 | include_path = os.path.join(os.path.dirname(__file__), "include") 66 | if os.path.exists(include_path): 67 | return include_path 68 | # Running from source directory 69 | return os.path.join(os.path.dirname(__file__), os.pardir, "include") 70 | -------------------------------------------------------------------------------- /av/__main__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | 5 | 6 | def main() -> None: 7 | parser = argparse.ArgumentParser() 8 | parser.add_argument("--codecs", action="store_true") 9 | parser.add_argument("--hwdevices", action="store_true") 10 | parser.add_argument("--hwconfigs", action="store_true") 11 | parser.add_argument("--version", action="store_true") 12 | args = parser.parse_args() 13 | 14 | if args.version: 15 | import av 16 | import av._core 17 | 18 | print(f"PyAV v{av.__version__}") 19 | 20 | by_config: dict = {} 21 | for libname, config in sorted(av._core.library_meta.items()): 22 | version = config["version"] 23 | if version[0] >= 0: 24 | by_config.setdefault( 25 | (config["configuration"], config["license"]), [] 26 | ).append((libname, config)) 27 | 28 | for (config, license), libs in sorted(by_config.items()): 29 | print("library configuration:", config) 30 | print("library license:", license) 31 | for libname, config in libs: 32 | version = config["version"] 33 | print(f"{libname:<13} {version[0]:3d}.{version[1]:3d}.{version[2]:3d}") 34 | 35 | if args.hwdevices: 36 | from av.codec.hwaccel import hwdevices_available 37 | 38 | print("Hardware device types:") 39 | for x in hwdevices_available(): 40 | print(" ", x) 41 | 42 | if args.hwconfigs: 43 | from av.codec.codec import dump_hwconfigs 44 | 45 | dump_hwconfigs() 46 | 47 | if args.codecs: 48 | from av.codec.codec import dump_codecs 49 | 50 | dump_codecs() 51 | 52 | 53 | if __name__ == "__main__": 54 | main() 55 | -------------------------------------------------------------------------------- /av/_core.pyi: -------------------------------------------------------------------------------- 1 | from typing import TypedDict 2 | 3 | class _Meta(TypedDict): 4 | version: tuple[int, int, int] 5 | configuration: str 6 | license: str 7 | 8 | library_meta: dict[str, _Meta] 9 | library_versions: dict[str, tuple[int, int, int]] 10 | ffmpeg_version_info: str 11 | 12 | time_base: int 13 | -------------------------------------------------------------------------------- /av/_core.pyx: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | # Initialise libraries. 4 | lib.avformat_network_init() 5 | lib.avdevice_register_all() 6 | 7 | # Exports. 8 | time_base = lib.AV_TIME_BASE 9 | 10 | 11 | cdef decode_version(v): 12 | if v < 0: 13 | return (-1, -1, -1) 14 | 15 | cdef int major = (v >> 16) & 0xff 16 | cdef int minor = (v >> 8) & 0xff 17 | cdef int micro = (v) & 0xff 18 | 19 | return (major, minor, micro) 20 | 21 | # Return an informative version string. 22 | # This usually is the actual release version number or a git commit 23 | # description. This string has no fixed format and can change any time. It 24 | # should never be parsed by code. 25 | ffmpeg_version_info = lib.av_version_info() 26 | 27 | library_meta = { 28 | "libavutil": dict( 29 | version=decode_version(lib.avutil_version()), 30 | configuration=lib.avutil_configuration(), 31 | license=lib.avutil_license() 32 | ), 33 | "libavcodec": dict( 34 | version=decode_version(lib.avcodec_version()), 35 | configuration=lib.avcodec_configuration(), 36 | license=lib.avcodec_license() 37 | ), 38 | "libavformat": dict( 39 | version=decode_version(lib.avformat_version()), 40 | configuration=lib.avformat_configuration(), 41 | license=lib.avformat_license() 42 | ), 43 | "libavdevice": dict( 44 | version=decode_version(lib.avdevice_version()), 45 | configuration=lib.avdevice_configuration(), 46 | license=lib.avdevice_license() 47 | ), 48 | "libavfilter": dict( 49 | version=decode_version(lib.avfilter_version()), 50 | configuration=lib.avfilter_configuration(), 51 | license=lib.avfilter_license() 52 | ), 53 | "libswscale": dict( 54 | version=decode_version(lib.swscale_version()), 55 | configuration=lib.swscale_configuration(), 56 | license=lib.swscale_license() 57 | ), 58 | "libswresample": dict( 59 | version=decode_version(lib.swresample_version()), 60 | configuration=lib.swresample_configuration(), 61 | license=lib.swresample_license() 62 | ), 63 | } 64 | 65 | library_versions = {name: meta["version"] for name, meta in library_meta.items()} 66 | -------------------------------------------------------------------------------- /av/about.py: -------------------------------------------------------------------------------- 1 | __version__ = "14.4.0" 2 | -------------------------------------------------------------------------------- /av/attachments/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyAV-Org/PyAV/633f237a6916d17b85fe937f95e28af651fbaf0e/av/attachments/__init__.py -------------------------------------------------------------------------------- /av/attachments/stream.pxd: -------------------------------------------------------------------------------- 1 | from av.stream cimport Stream 2 | 3 | 4 | cdef class AttachmentStream(Stream): 5 | pass 6 | -------------------------------------------------------------------------------- /av/attachments/stream.pyi: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from av.stream import Stream 4 | 5 | class AttachmentStream(Stream): 6 | type: Literal["attachment"] 7 | @property 8 | def mimetype(self) -> str | None: ... 9 | -------------------------------------------------------------------------------- /av/attachments/stream.pyx: -------------------------------------------------------------------------------- 1 | from av.stream cimport Stream 2 | 3 | 4 | cdef class AttachmentStream(Stream): 5 | """ 6 | An :class:`AttachmentStream` represents a stream of attachment data within a media container. 7 | Typically used to attach font files that are referenced in ASS/SSA Subtitle Streams. 8 | """ 9 | 10 | @property 11 | def name(self): 12 | """ 13 | Returns the file name of the attachment. 14 | 15 | :rtype: str | None 16 | """ 17 | return self.metadata.get("filename") 18 | 19 | @property 20 | def mimetype(self): 21 | """ 22 | Returns the MIME type of the attachment. 23 | 24 | :rtype: str | None 25 | """ 26 | return self.metadata.get("mimetype") 27 | -------------------------------------------------------------------------------- /av/audio/__init__.pxd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyAV-Org/PyAV/633f237a6916d17b85fe937f95e28af651fbaf0e/av/audio/__init__.pxd -------------------------------------------------------------------------------- /av/audio/__init__.py: -------------------------------------------------------------------------------- 1 | from .frame import AudioFrame 2 | from .stream import AudioStream 3 | -------------------------------------------------------------------------------- /av/audio/__init__.pyi: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from .frame import AudioFrame 4 | from .stream import AudioStream 5 | 6 | _AudioCodecName = Literal[ 7 | "aac", 8 | "libopus", 9 | "mp2", 10 | "mp3", 11 | "pcm_alaw", 12 | "pcm_mulaw", 13 | "pcm_s16le", 14 | ] 15 | 16 | __all__ = ("AudioFrame", "AudioStream") 17 | -------------------------------------------------------------------------------- /av/audio/codeccontext.pxd: -------------------------------------------------------------------------------- 1 | 2 | from av.audio.frame cimport AudioFrame 3 | from av.audio.resampler cimport AudioResampler 4 | from av.codec.context cimport CodecContext 5 | 6 | 7 | cdef class AudioCodecContext(CodecContext): 8 | # Hold onto the frames that we will decode until we have a full one. 9 | cdef AudioFrame next_frame 10 | # For encoding. 11 | cdef AudioResampler resampler 12 | -------------------------------------------------------------------------------- /av/audio/codeccontext.pyi: -------------------------------------------------------------------------------- 1 | from typing import Iterator, Literal 2 | 3 | from av.codec.context import CodecContext 4 | from av.packet import Packet 5 | 6 | from .format import AudioFormat 7 | from .frame import AudioFrame 8 | from .layout import AudioLayout 9 | 10 | class _Format: 11 | def __get__(self, i: object | None, owner: type | None = None) -> AudioFormat: ... 12 | def __set__(self, instance: object, value: AudioFormat | str) -> None: ... 13 | 14 | class _Layout: 15 | def __get__(self, i: object | None, owner: type | None = None) -> AudioLayout: ... 16 | def __set__(self, instance: object, value: AudioLayout | str) -> None: ... 17 | 18 | class AudioCodecContext(CodecContext): 19 | frame_size: int 20 | sample_rate: int 21 | rate: int 22 | type: Literal["audio"] 23 | format: _Format 24 | layout: _Layout 25 | @property 26 | def channels(self) -> int: ... 27 | def encode(self, frame: AudioFrame | None = None) -> list[Packet]: ... 28 | def encode_lazy(self, frame: AudioFrame | None = None) -> Iterator[Packet]: ... 29 | def decode(self, packet: Packet | None = None) -> list[AudioFrame]: ... 30 | -------------------------------------------------------------------------------- /av/audio/fifo.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | from libc.stdint cimport int64_t, uint64_t 3 | 4 | from av.audio.frame cimport AudioFrame 5 | 6 | 7 | cdef class AudioFifo: 8 | 9 | cdef lib.AVAudioFifo *ptr 10 | 11 | cdef AudioFrame template 12 | 13 | cdef readonly uint64_t samples_written 14 | cdef readonly uint64_t samples_read 15 | cdef readonly double pts_per_sample 16 | 17 | cpdef write(self, AudioFrame frame) 18 | cpdef read(self, int samples=*, bint partial=*) 19 | cpdef read_many(self, int samples, bint partial=*) 20 | -------------------------------------------------------------------------------- /av/audio/fifo.pyi: -------------------------------------------------------------------------------- 1 | from .format import AudioFormat 2 | from .frame import AudioFrame 3 | from .layout import AudioLayout 4 | 5 | class AudioFifo: 6 | def write(self, frame: AudioFrame) -> None: ... 7 | def read(self, samples: int = 0, partial: bool = False) -> AudioFrame | None: ... 8 | def read_many(self, samples: int, partial: bool = False) -> list[AudioFrame]: ... 9 | @property 10 | def format(self) -> AudioFormat: ... 11 | @property 12 | def layout(self) -> AudioLayout: ... 13 | @property 14 | def sample_rate(self) -> int: ... 15 | @property 16 | def samples(self) -> int: ... 17 | @property 18 | def samples_written(self) -> int: ... 19 | @property 20 | def samples_read(self) -> int: ... 21 | @property 22 | def pts_per_sample(self) -> float: ... 23 | -------------------------------------------------------------------------------- /av/audio/format.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | 4 | cdef class AudioFormat: 5 | 6 | cdef lib.AVSampleFormat sample_fmt 7 | 8 | cdef _init(self, lib.AVSampleFormat sample_fmt) 9 | 10 | 11 | cdef AudioFormat get_audio_format(lib.AVSampleFormat format) 12 | -------------------------------------------------------------------------------- /av/audio/format.pyi: -------------------------------------------------------------------------------- 1 | class AudioFormat: 2 | name: str 3 | bytes: int 4 | bits: int 5 | is_planar: bool 6 | is_packed: bool 7 | planar: AudioFormat 8 | packed: AudioFormat 9 | container_name: str 10 | 11 | def __init__(self, name: str | AudioFormat) -> None: ... 12 | -------------------------------------------------------------------------------- /av/audio/frame.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | from libc.stdint cimport uint8_t, uint64_t 3 | 4 | from av.audio.format cimport AudioFormat 5 | from av.audio.layout cimport AudioLayout 6 | from av.frame cimport Frame 7 | 8 | 9 | cdef class AudioFrame(Frame): 10 | # For raw storage of the frame's data; don't ever touch this. 11 | cdef uint8_t *_buffer 12 | cdef size_t _buffer_size 13 | 14 | cdef readonly AudioLayout layout 15 | """ 16 | The audio channel layout. 17 | 18 | :type: AudioLayout 19 | """ 20 | 21 | cdef readonly AudioFormat format 22 | """ 23 | The audio sample format. 24 | 25 | :type: AudioFormat 26 | """ 27 | 28 | cdef _init(self, lib.AVSampleFormat format, lib.AVChannelLayout layout, unsigned int nb_samples, unsigned int align) 29 | cdef _init_user_attributes(self) 30 | 31 | cdef AudioFrame alloc_audio_frame() 32 | -------------------------------------------------------------------------------- /av/audio/frame.pyi: -------------------------------------------------------------------------------- 1 | from typing import Any, Union 2 | 3 | import numpy as np 4 | 5 | from av.frame import Frame 6 | 7 | from .format import AudioFormat 8 | from .layout import AudioLayout 9 | from .plane import AudioPlane 10 | 11 | format_dtypes: dict[str, str] 12 | _SupportedNDarray = Union[ 13 | np.ndarray[Any, np.dtype[np.float64]], # f8 14 | np.ndarray[Any, np.dtype[np.float32]], # f4 15 | np.ndarray[Any, np.dtype[np.int32]], # i4 16 | np.ndarray[Any, np.dtype[np.int16]], # i2 17 | np.ndarray[Any, np.dtype[np.uint8]], # u1 18 | ] 19 | 20 | class _Format: 21 | def __get__(self, i: object | None, owner: type | None = None) -> AudioFormat: ... 22 | def __set__(self, instance: object, value: AudioFormat | str) -> None: ... 23 | 24 | class _Layout: 25 | def __get__(self, i: object | None, owner: type | None = None) -> AudioLayout: ... 26 | def __set__(self, instance: object, value: AudioLayout | str) -> None: ... 27 | 28 | class AudioFrame(Frame): 29 | planes: tuple[AudioPlane, ...] 30 | samples: int 31 | sample_rate: int 32 | rate: int 33 | format: _Format 34 | layout: _Layout 35 | 36 | def __init__( 37 | self, 38 | format: str = "s16", 39 | layout: str = "stereo", 40 | samples: int = 0, 41 | align: int = 1, 42 | ) -> None: ... 43 | @staticmethod 44 | def from_ndarray( 45 | array: _SupportedNDarray, format: str = "s16", layout: str = "stereo" 46 | ) -> AudioFrame: ... 47 | def to_ndarray(self) -> _SupportedNDarray: ... 48 | -------------------------------------------------------------------------------- /av/audio/layout.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | 4 | cdef class AudioLayout: 5 | cdef lib.AVChannelLayout layout 6 | cdef _init(self, lib.AVChannelLayout layout) 7 | 8 | cdef AudioLayout get_audio_layout(lib.AVChannelLayout c_layout) 9 | -------------------------------------------------------------------------------- /av/audio/layout.pyi: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | class AudioLayout: 4 | name: str 5 | nb_channels: int 6 | channels: tuple[AudioChannel, ...] 7 | def __init__(self, layout: str | AudioLayout): ... 8 | 9 | @dataclass 10 | class AudioChannel: 11 | name: str 12 | description: str 13 | -------------------------------------------------------------------------------- /av/audio/layout.pyx: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | from cpython.bytes cimport PyBytes_FromStringAndSize 3 | 4 | from dataclasses import dataclass 5 | 6 | 7 | @dataclass 8 | class AudioChannel: 9 | name: str 10 | description: str 11 | 12 | def __repr__(self): 13 | return f"" 14 | 15 | cdef object _cinit_bypass_sentinel 16 | 17 | cdef AudioLayout get_audio_layout(lib.AVChannelLayout c_layout): 18 | """Get an AudioLayout from Cython land.""" 19 | cdef AudioLayout layout = AudioLayout.__new__(AudioLayout, _cinit_bypass_sentinel) 20 | layout._init(c_layout) 21 | return layout 22 | 23 | 24 | cdef class AudioLayout: 25 | def __init__(self, layout): 26 | if layout is _cinit_bypass_sentinel: 27 | return 28 | 29 | if type(layout) is str: 30 | ret = lib.av_channel_layout_from_string(&c_layout, layout) 31 | if ret != 0: 32 | raise ValueError(f"Invalid layout: {layout}") 33 | elif isinstance(layout, AudioLayout): 34 | c_layout = (layout).layout 35 | else: 36 | raise TypeError(f"layout must be of type: string | av.AudioLayout, got {type(layout)}") 37 | 38 | self._init(c_layout) 39 | 40 | cdef _init(self, lib.AVChannelLayout layout): 41 | self.layout = layout 42 | 43 | def __repr__(self): 44 | return f"" 45 | 46 | def __eq__(self, other): 47 | return isinstance(other, AudioLayout) and self.name == other.name and self.nb_channels == other.nb_channels 48 | 49 | @property 50 | def nb_channels(self): 51 | return self.layout.nb_channels 52 | 53 | @property 54 | def channels(self): 55 | cdef lib.AVChannel channel 56 | cdef char buf[16] 57 | cdef char buf2[128] 58 | 59 | results = [] 60 | 61 | for index in range(self.layout.nb_channels): 62 | channel = lib.av_channel_layout_channel_from_index(&self.layout, index); 63 | size = lib.av_channel_name(buf, sizeof(buf), channel) - 1 64 | size2 = lib.av_channel_description(buf2, sizeof(buf2), channel) - 1 65 | results.append( 66 | AudioChannel( 67 | PyBytes_FromStringAndSize(buf, size).decode("utf-8"), 68 | PyBytes_FromStringAndSize(buf2, size2).decode("utf-8"), 69 | ) 70 | ) 71 | 72 | return tuple(results) 73 | 74 | @property 75 | def name(self) -> str: 76 | """The canonical name of the audio layout.""" 77 | cdef char layout_name[128] 78 | cdef int ret 79 | 80 | ret = lib.av_channel_layout_describe(&self.layout, layout_name, sizeof(layout_name)) 81 | if ret < 0: 82 | raise RuntimeError(f"Failed to get layout name: {ret}") 83 | 84 | return layout_name -------------------------------------------------------------------------------- /av/audio/plane.pxd: -------------------------------------------------------------------------------- 1 | from av.plane cimport Plane 2 | 3 | 4 | cdef class AudioPlane(Plane): 5 | 6 | cdef readonly size_t buffer_size 7 | 8 | cdef size_t _buffer_size(self) 9 | -------------------------------------------------------------------------------- /av/audio/plane.pyi: -------------------------------------------------------------------------------- 1 | from av.plane import Plane 2 | 3 | class AudioPlane(Plane): 4 | buffer_size: int 5 | -------------------------------------------------------------------------------- /av/audio/plane.pyx: -------------------------------------------------------------------------------- 1 | from av.audio.frame cimport AudioFrame 2 | 3 | 4 | cdef class AudioPlane(Plane): 5 | 6 | def __cinit__(self, AudioFrame frame, int index): 7 | # Only the first linesize is ever populated, but it applies to every plane. 8 | self.buffer_size = self.frame.ptr.linesize[0] 9 | 10 | cdef size_t _buffer_size(self): 11 | return self.buffer_size 12 | -------------------------------------------------------------------------------- /av/audio/resampler.pxd: -------------------------------------------------------------------------------- 1 | from av.audio.format cimport AudioFormat 2 | from av.audio.frame cimport AudioFrame 3 | from av.audio.layout cimport AudioLayout 4 | from av.filter.graph cimport Graph 5 | 6 | 7 | cdef class AudioResampler: 8 | 9 | cdef readonly bint is_passthrough 10 | 11 | cdef AudioFrame template 12 | 13 | # Destination descriptors 14 | cdef readonly AudioFormat format 15 | cdef readonly AudioLayout layout 16 | cdef readonly int rate 17 | cdef readonly unsigned int frame_size 18 | 19 | cdef Graph graph 20 | 21 | cpdef resample(self, AudioFrame) 22 | -------------------------------------------------------------------------------- /av/audio/resampler.pyi: -------------------------------------------------------------------------------- 1 | from av.filter.graph import Graph 2 | 3 | from .format import AudioFormat 4 | from .frame import AudioFrame 5 | from .layout import AudioLayout 6 | 7 | class AudioResampler: 8 | rate: int 9 | frame_size: int 10 | format: AudioFormat 11 | graph: Graph | None 12 | 13 | def __init__( 14 | self, 15 | format: str | int | AudioFormat | None = None, 16 | layout: str | int | AudioLayout | None = None, 17 | rate: int | None = None, 18 | frame_size: int | None = None, 19 | ) -> None: ... 20 | def resample(self, frame: AudioFrame | None) -> list[AudioFrame]: ... 21 | -------------------------------------------------------------------------------- /av/audio/stream.pxd: -------------------------------------------------------------------------------- 1 | from av.packet cimport Packet 2 | from av.stream cimport Stream 3 | 4 | from .frame cimport AudioFrame 5 | 6 | 7 | cdef class AudioStream(Stream): 8 | cpdef encode(self, AudioFrame frame=?) 9 | cpdef decode(self, Packet packet=?) 10 | -------------------------------------------------------------------------------- /av/audio/stream.pyi: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from av.packet import Packet 4 | from av.stream import Stream 5 | 6 | from .codeccontext import AudioCodecContext 7 | from .format import AudioFormat 8 | from .frame import AudioFrame 9 | from .layout import AudioLayout 10 | 11 | class _Format: 12 | def __get__(self, i: object | None, owner: type | None = None) -> AudioFormat: ... 13 | def __set__(self, instance: object, value: AudioFormat | str) -> None: ... 14 | 15 | class _Layout: 16 | def __get__(self, i: object | None, owner: type | None = None) -> AudioLayout: ... 17 | def __set__(self, instance: object, value: AudioLayout | str) -> None: ... 18 | 19 | class AudioStream(Stream): 20 | codec_context: AudioCodecContext 21 | def encode(self, frame: AudioFrame | None = None) -> list[Packet]: ... 22 | def decode(self, packet: Packet | None = None) -> list[AudioFrame]: ... 23 | 24 | # From codec context 25 | frame_size: int 26 | sample_rate: int 27 | bit_rate: int 28 | rate: int 29 | channels: int 30 | type: Literal["audio"] 31 | format: _Format 32 | layout: _Layout 33 | -------------------------------------------------------------------------------- /av/audio/stream.pyx: -------------------------------------------------------------------------------- 1 | from av.packet cimport Packet 2 | 3 | from .frame cimport AudioFrame 4 | 5 | 6 | cdef class AudioStream(Stream): 7 | def __repr__(self): 8 | form = self.format.name if self.format else None 9 | return ( 10 | f"" 12 | ) 13 | 14 | def __getattr__(self, name): 15 | return getattr(self.codec_context, name) 16 | 17 | cpdef encode(self, AudioFrame frame=None): 18 | """ 19 | Encode an :class:`.AudioFrame` and return a list of :class:`.Packet`. 20 | 21 | :rtype: list[Packet] 22 | 23 | .. seealso:: This is mostly a passthrough to :meth:`.CodecContext.encode`. 24 | """ 25 | 26 | packets = self.codec_context.encode(frame) 27 | cdef Packet packet 28 | for packet in packets: 29 | packet._stream = self 30 | packet.ptr.stream_index = self.ptr.index 31 | 32 | return packets 33 | 34 | cpdef decode(self, Packet packet=None): 35 | """ 36 | Decode a :class:`.Packet` and return a list of :class:`.AudioFrame`. 37 | 38 | :rtype: list[AudioFrame] 39 | 40 | .. seealso:: This is a passthrough to :meth:`.CodecContext.decode`. 41 | """ 42 | 43 | return self.codec_context.decode(packet) 44 | -------------------------------------------------------------------------------- /av/bitstream.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | from av.packet cimport Packet 4 | 5 | 6 | cdef class BitStreamFilterContext: 7 | 8 | cdef lib.AVBSFContext *ptr 9 | 10 | cpdef filter(self, Packet packet=?) 11 | cpdef flush(self) 12 | -------------------------------------------------------------------------------- /av/bitstream.pyi: -------------------------------------------------------------------------------- 1 | from .packet import Packet 2 | from .stream import Stream 3 | 4 | class BitStreamFilterContext: 5 | def __init__( 6 | self, 7 | filter_description: str | bytes, 8 | in_stream: Stream | None = None, 9 | out_stream: Stream | None = None, 10 | ): ... 11 | def filter(self, packet: Packet | None) -> list[Packet]: ... 12 | def flush(self) -> None: ... 13 | 14 | bitstream_filters_available: set[str] 15 | -------------------------------------------------------------------------------- /av/bitstream.pyx: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | from libc.errno cimport EAGAIN 3 | 4 | from av.error cimport err_check 5 | from av.packet cimport Packet 6 | from av.stream cimport Stream 7 | 8 | 9 | cdef class BitStreamFilterContext: 10 | """ 11 | Initializes a bitstream filter: a way to directly modify packet data. 12 | 13 | Wraps :ffmpeg:`AVBSFContext` 14 | 15 | :param Stream in_stream: A stream that defines the input codec for the bitfilter. 16 | :param Stream out_stream: A stream whose codec is overwritten using the output parameters from the bitfilter. 17 | """ 18 | def __cinit__(self, filter_description, Stream in_stream=None, Stream out_stream=None): 19 | cdef int res 20 | cdef char *filter_str = filter_description 21 | 22 | with nogil: 23 | res = lib.av_bsf_list_parse_str(filter_str, &self.ptr) 24 | err_check(res) 25 | 26 | if in_stream is not None: 27 | with nogil: 28 | res = lib.avcodec_parameters_copy(self.ptr.par_in, in_stream.ptr.codecpar) 29 | err_check(res) 30 | 31 | with nogil: 32 | res = lib.av_bsf_init(self.ptr) 33 | err_check(res) 34 | 35 | if out_stream is not None: 36 | with nogil: 37 | res = lib.avcodec_parameters_copy(out_stream.ptr.codecpar, self.ptr.par_out) 38 | err_check(res) 39 | lib.avcodec_parameters_to_context(out_stream.codec_context.ptr, out_stream.ptr.codecpar) 40 | 41 | def __dealloc__(self): 42 | if self.ptr: 43 | lib.av_bsf_free(&self.ptr) 44 | 45 | cpdef filter(self, Packet packet=None): 46 | """ 47 | Processes a packet based on the filter_description set during initialization. 48 | Multiple packets may be created. 49 | 50 | :type: list[Packet] 51 | """ 52 | cdef int res 53 | cdef Packet new_packet 54 | 55 | with nogil: 56 | res = lib.av_bsf_send_packet(self.ptr, packet.ptr if packet is not None else NULL) 57 | err_check(res) 58 | 59 | output = [] 60 | while True: 61 | new_packet = Packet() 62 | with nogil: 63 | res = lib.av_bsf_receive_packet(self.ptr, new_packet.ptr) 64 | 65 | if res == -EAGAIN or res == lib.AVERROR_EOF: 66 | return output 67 | 68 | err_check(res) 69 | if res: 70 | return output 71 | 72 | output.append(new_packet) 73 | 74 | cpdef flush(self): 75 | """ 76 | Reset the internal state of the filter. 77 | Should be called e.g. when seeking. 78 | Can be used to make the filter usable again after draining it with EOF marker packet. 79 | """ 80 | lib.av_bsf_flush(self.ptr) 81 | 82 | cdef get_filter_names(): 83 | names = set() 84 | cdef const lib.AVBitStreamFilter *ptr 85 | cdef void *opaque = NULL 86 | while True: 87 | ptr = lib.av_bsf_iterate(&opaque) 88 | if ptr: 89 | names.add(ptr.name) 90 | else: 91 | break 92 | 93 | return names 94 | 95 | bitstream_filters_available = get_filter_names() 96 | -------------------------------------------------------------------------------- /av/buffer.pxd: -------------------------------------------------------------------------------- 1 | 2 | cdef class Buffer: 3 | 4 | cdef size_t _buffer_size(self) 5 | cdef void* _buffer_ptr(self) 6 | cdef bint _buffer_writable(self) 7 | -------------------------------------------------------------------------------- /av/buffer.pyi: -------------------------------------------------------------------------------- 1 | # When Python 3.12 becomes our lowest supported version, we could make this 2 | # class inherit `collections.abc.Buffer`. 3 | 4 | class Buffer: 5 | buffer_size: int 6 | buffer_ptr: int 7 | def update(self, input: bytes) -> None: ... 8 | def __buffer__(self, flags: int) -> memoryview: ... 9 | def __bytes__(self) -> bytes: ... 10 | -------------------------------------------------------------------------------- /av/buffer.pyx: -------------------------------------------------------------------------------- 1 | from cpython cimport PyBUF_WRITABLE, PyBuffer_FillInfo 2 | from libc.string cimport memcpy 3 | 4 | from av.bytesource cimport ByteSource, bytesource 5 | 6 | 7 | cdef class Buffer: 8 | """A base class for PyAV objects which support the buffer protocol, such 9 | as :class:`.Packet` and :class:`.Plane`. 10 | 11 | """ 12 | 13 | cdef size_t _buffer_size(self): 14 | return 0 15 | 16 | cdef void* _buffer_ptr(self): 17 | return NULL 18 | 19 | cdef bint _buffer_writable(self): 20 | return True 21 | 22 | def __getbuffer__(self, Py_buffer *view, int flags): 23 | if flags & PyBUF_WRITABLE and not self._buffer_writable(): 24 | raise ValueError("buffer is not writable") 25 | 26 | PyBuffer_FillInfo(view, self, self._buffer_ptr(), self._buffer_size(), 0, flags) 27 | 28 | @property 29 | def buffer_size(self): 30 | """The size of the buffer in bytes.""" 31 | return self._buffer_size() 32 | 33 | @property 34 | def buffer_ptr(self): 35 | """The memory address of the buffer.""" 36 | return self._buffer_ptr() 37 | 38 | def update(self, input): 39 | """Replace the data in this object with the given buffer. 40 | 41 | Accepts anything that supports the `buffer protocol `_, 42 | e.g. bytes, Numpy arrays, other :class:`Buffer` objects, etc.. 43 | 44 | """ 45 | if not self._buffer_writable(): 46 | raise ValueError("buffer is not writable") 47 | 48 | cdef ByteSource source = bytesource(input) 49 | cdef size_t size = self._buffer_size() 50 | 51 | if source.length != size: 52 | raise ValueError(f"got {source.length} bytes; need {size} bytes") 53 | 54 | memcpy(self._buffer_ptr(), source.ptr, size) 55 | -------------------------------------------------------------------------------- /av/bytesource.pxd: -------------------------------------------------------------------------------- 1 | from cpython.buffer cimport Py_buffer 2 | 3 | 4 | cdef class ByteSource: 5 | 6 | cdef object owner 7 | 8 | cdef bint has_view 9 | cdef Py_buffer view 10 | 11 | cdef unsigned char *ptr 12 | cdef size_t length 13 | 14 | cdef ByteSource bytesource(object, bint allow_none=*) 15 | -------------------------------------------------------------------------------- /av/bytesource.pyx: -------------------------------------------------------------------------------- 1 | from cpython.buffer cimport ( 2 | PyBUF_SIMPLE, 3 | PyBuffer_Release, 4 | PyObject_CheckBuffer, 5 | PyObject_GetBuffer, 6 | ) 7 | 8 | 9 | cdef class ByteSource: 10 | def __cinit__(self, owner): 11 | self.owner = owner 12 | 13 | try: 14 | self.ptr = owner 15 | except TypeError: 16 | pass 17 | else: 18 | self.length = len(owner) 19 | return 20 | 21 | if PyObject_CheckBuffer(owner): 22 | # Can very likely use PyBUF_ND instead of PyBUF_SIMPLE 23 | res = PyObject_GetBuffer(owner, &self.view, PyBUF_SIMPLE) 24 | if not res: 25 | self.has_view = True 26 | self.ptr = self.view.buf 27 | self.length = self.view.len 28 | return 29 | 30 | raise TypeError("expected bytes, bytearray or memoryview") 31 | 32 | def __dealloc__(self): 33 | if self.has_view: 34 | PyBuffer_Release(&self.view) 35 | 36 | 37 | cdef ByteSource bytesource(obj, bint allow_none=False): 38 | if allow_none and obj is None: 39 | return 40 | elif isinstance(obj, ByteSource): 41 | return obj 42 | else: 43 | return ByteSource(obj) 44 | -------------------------------------------------------------------------------- /av/codec/__init__.pxd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyAV-Org/PyAV/633f237a6916d17b85fe937f95e28af651fbaf0e/av/codec/__init__.pxd -------------------------------------------------------------------------------- /av/codec/__init__.py: -------------------------------------------------------------------------------- 1 | from .codec import Capabilities, Codec, Properties, codec_descriptor, codecs_available 2 | from .context import CodecContext 3 | 4 | __all__ = ( 5 | "Capabilities", 6 | "Codec", 7 | "Properties", 8 | "codec_descriptor", 9 | "codecs_available", 10 | "CodecContext", 11 | ) 12 | -------------------------------------------------------------------------------- /av/codec/codec.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | 4 | cdef class Codec: 5 | 6 | cdef const lib.AVCodec *ptr 7 | cdef const lib.AVCodecDescriptor *desc 8 | cdef readonly bint is_encoder 9 | 10 | cdef tuple _hardware_configs 11 | 12 | cdef _init(self, name=?) 13 | 14 | 15 | cdef Codec wrap_codec(const lib.AVCodec *ptr) 16 | -------------------------------------------------------------------------------- /av/codec/context.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | from libc.stdint cimport int64_t 3 | 4 | from av.bytesource cimport ByteSource 5 | from av.codec.codec cimport Codec 6 | from av.codec.hwaccel cimport HWAccel 7 | from av.frame cimport Frame 8 | from av.packet cimport Packet 9 | 10 | 11 | cdef class CodecContext: 12 | cdef lib.AVCodecContext *ptr 13 | 14 | # Whether AVCodecContext.extradata should be de-allocated upon destruction. 15 | cdef bint extradata_set 16 | 17 | # Used as a signal that this is within a stream, and also for us to access that 18 | # stream. This is set "manually" by the stream after constructing this object. 19 | cdef int stream_index 20 | 21 | cdef lib.AVCodecParserContext *parser 22 | cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec, HWAccel hwaccel) 23 | 24 | # Public API. 25 | cdef readonly bint is_open 26 | cdef readonly Codec codec 27 | cdef readonly HWAccel hwaccel 28 | cdef public dict options 29 | cpdef open(self, bint strict=?) 30 | 31 | # Wraps both versions of the transcode API, returning lists. 32 | cpdef encode(self, Frame frame=?) 33 | cpdef decode(self, Packet packet=?) 34 | cpdef flush_buffers(self) 35 | 36 | # Used by hardware-accelerated decode. 37 | cdef HWAccel hwaccel_ctx 38 | 39 | # Used by both transcode APIs to setup user-land objects. 40 | # TODO: Remove the `Packet` from `_setup_decoded_frame` (because flushing packets 41 | # are bogus). It should take all info it needs from the context and/or stream. 42 | cdef _prepare_and_time_rebase_frames_for_encode(self, Frame frame) 43 | cdef _prepare_frames_for_encode(self, Frame frame) 44 | cdef _setup_encoded_packet(self, Packet) 45 | cdef _setup_decoded_frame(self, Frame, Packet) 46 | 47 | # Implemented by base for the generic send/recv API. 48 | # Note that the user cannot send without receiving. This is because 49 | # `_prepare_frames_for_encode` may expand a frame into multiple (e.g. when 50 | # resampling audio to a higher rate but with fixed size frames), and the 51 | # send/recv buffer may be limited to a single frame. Ergo, we need to flush 52 | # the buffer as often as possible. 53 | cdef _recv_packet(self) 54 | cdef _send_packet_and_recv(self, Packet packet) 55 | cdef _recv_frame(self) 56 | 57 | cdef _transfer_hwframe(self, Frame frame) 58 | 59 | # Implemented by children for the generic send/recv API, so we have the 60 | # correct subclass of Frame. 61 | cdef Frame _next_frame 62 | cdef Frame _alloc_next_frame(self) 63 | 64 | cdef CodecContext wrap_codec_context(lib.AVCodecContext*, const lib.AVCodec*, HWAccel hwaccel) 65 | -------------------------------------------------------------------------------- /av/codec/hwaccel.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | from av.codec.codec cimport Codec 4 | 5 | 6 | cdef class HWConfig: 7 | cdef object __weakref__ 8 | cdef lib.AVCodecHWConfig *ptr 9 | cdef void _init(self, lib.AVCodecHWConfig *ptr) 10 | 11 | cdef HWConfig wrap_hwconfig(lib.AVCodecHWConfig *ptr) 12 | 13 | cdef class HWAccel: 14 | cdef int _device_type 15 | cdef str _device 16 | cdef readonly Codec codec 17 | cdef readonly HWConfig config 18 | cdef lib.AVBufferRef *ptr 19 | cdef public bint allow_software_fallback 20 | cdef public dict options 21 | cdef public int flags 22 | -------------------------------------------------------------------------------- /av/codec/hwaccel.pyi: -------------------------------------------------------------------------------- 1 | from enum import IntEnum 2 | from typing import cast 3 | 4 | from av.codec.codec import Codec 5 | from av.video.format import VideoFormat 6 | 7 | class HWDeviceType(IntEnum): 8 | none = cast(int, ...) 9 | vdpau = cast(int, ...) 10 | cuda = cast(int, ...) 11 | vaapi = cast(int, ...) 12 | dxva2 = cast(int, ...) 13 | qsv = cast(int, ...) 14 | videotoolbox = cast(int, ...) 15 | d3d11va = cast(int, ...) 16 | drm = cast(int, ...) 17 | opencl = cast(int, ...) 18 | mediacodec = cast(int, ...) 19 | vulkan = cast(int, ...) 20 | d3d12va = cast(int, ...) 21 | 22 | class HWConfigMethod(IntEnum): 23 | none = cast(int, ...) 24 | hw_device_ctx = cast(int, ...) 25 | hw_frame_ctx = cast(int, ...) 26 | internal = cast(int, ...) 27 | ad_hoc = cast(int, ...) 28 | 29 | class HWConfig: 30 | @property 31 | def device_type(self) -> HWDeviceType: ... 32 | @property 33 | def format(self) -> VideoFormat: ... 34 | @property 35 | def methods(self) -> HWConfigMethod: ... 36 | @property 37 | def is_supported(self) -> bool: ... 38 | 39 | class HWAccel: 40 | def __init__( 41 | self, 42 | device_type: str | HWDeviceType, 43 | device: str | None = None, 44 | allow_software_fallback: bool = False, 45 | options: dict[str, object] | None = None, 46 | flags: int | None = None, 47 | ) -> None: ... 48 | def create(self, codec: Codec) -> HWAccel: ... 49 | 50 | def hwdevices_available() -> list[str]: ... 51 | -------------------------------------------------------------------------------- /av/container/__init__.pxd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyAV-Org/PyAV/633f237a6916d17b85fe937f95e28af651fbaf0e/av/container/__init__.pxd -------------------------------------------------------------------------------- /av/container/__init__.py: -------------------------------------------------------------------------------- 1 | from .core import Container, Flags, open 2 | from .input import InputContainer 3 | from .output import OutputContainer 4 | -------------------------------------------------------------------------------- /av/container/__init__.pyi: -------------------------------------------------------------------------------- 1 | from .core import * 2 | from .input import * 3 | from .output import * 4 | -------------------------------------------------------------------------------- /av/container/core.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | from av.codec.hwaccel cimport HWAccel 4 | from av.container.pyio cimport PyIOFile 5 | from av.container.streams cimport StreamContainer 6 | from av.dictionary cimport _Dictionary 7 | from av.format cimport ContainerFormat 8 | from av.stream cimport Stream 9 | 10 | # Interrupt callback information, times are in seconds. 11 | ctypedef struct timeout_info: 12 | double start_time 13 | double timeout 14 | 15 | 16 | cdef class Container: 17 | 18 | cdef readonly bint writeable 19 | cdef lib.AVFormatContext *ptr 20 | 21 | cdef readonly object name 22 | cdef readonly str metadata_encoding 23 | cdef readonly str metadata_errors 24 | 25 | cdef readonly PyIOFile file 26 | cdef int buffer_size 27 | cdef bint input_was_opened 28 | cdef readonly object io_open 29 | cdef readonly object open_files 30 | 31 | cdef readonly ContainerFormat format 32 | 33 | cdef readonly dict options 34 | cdef readonly dict container_options 35 | cdef readonly list stream_options 36 | 37 | cdef HWAccel hwaccel 38 | 39 | cdef readonly StreamContainer streams 40 | cdef readonly dict metadata 41 | 42 | # Private API. 43 | cdef _assert_open(self) 44 | cdef int err_check(self, int value) except -1 45 | 46 | # Timeouts 47 | cdef readonly object open_timeout 48 | cdef readonly object read_timeout 49 | cdef timeout_info interrupt_callback_info 50 | cdef set_timeout(self, object) 51 | cdef start_timeout(self) 52 | -------------------------------------------------------------------------------- /av/container/input.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | from av.container.core cimport Container 4 | from av.stream cimport Stream 5 | 6 | 7 | cdef class InputContainer(Container): 8 | 9 | cdef flush_buffers(self) 10 | -------------------------------------------------------------------------------- /av/container/input.pyi: -------------------------------------------------------------------------------- 1 | from typing import Any, Iterator, overload 2 | 3 | from av.audio.frame import AudioFrame 4 | from av.audio.stream import AudioStream 5 | from av.packet import Packet 6 | from av.stream import Stream 7 | from av.subtitles.stream import SubtitleStream 8 | from av.subtitles.subtitle import SubtitleSet 9 | from av.video.frame import VideoFrame 10 | from av.video.stream import VideoStream 11 | 12 | from .core import Container 13 | 14 | class InputContainer(Container): 15 | start_time: int 16 | duration: int | None 17 | bit_rate: int 18 | size: int 19 | 20 | def __enter__(self) -> InputContainer: ... 21 | def close(self) -> None: ... 22 | def demux(self, *args: Any, **kwargs: Any) -> Iterator[Packet]: ... 23 | @overload 24 | def decode(self, video: int) -> Iterator[VideoFrame]: ... 25 | @overload 26 | def decode(self, audio: int) -> Iterator[AudioFrame]: ... 27 | @overload 28 | def decode(self, subtitles: int) -> Iterator[SubtitleSet]: ... 29 | @overload 30 | def decode(self, *args: VideoStream) -> Iterator[VideoFrame]: ... 31 | @overload 32 | def decode(self, *args: AudioStream) -> Iterator[AudioFrame]: ... 33 | @overload 34 | def decode(self, *args: SubtitleStream) -> Iterator[SubtitleSet]: ... 35 | @overload 36 | def decode( 37 | self, *args: Any, **kwargs: Any 38 | ) -> Iterator[VideoFrame | AudioFrame | SubtitleSet]: ... 39 | def seek( 40 | self, 41 | offset: int, 42 | *, 43 | backward: bool = True, 44 | any_frame: bool = False, 45 | stream: Stream | VideoStream | AudioStream | None = None, 46 | unsupported_frame_offset: bool = False, 47 | unsupported_byte_offset: bool = False, 48 | ) -> None: ... 49 | def flush_buffers(self) -> None: ... 50 | -------------------------------------------------------------------------------- /av/container/output.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | from av.container.core cimport Container 4 | from av.stream cimport Stream 5 | 6 | 7 | cdef class OutputContainer(Container): 8 | cdef bint _started 9 | cdef bint _done 10 | cdef lib.AVPacket *packet_ptr 11 | 12 | cpdef start_encoding(self) 13 | -------------------------------------------------------------------------------- /av/container/output.pyi: -------------------------------------------------------------------------------- 1 | from fractions import Fraction 2 | from typing import Sequence, TypeVar, Union, overload 3 | 4 | from av.audio import _AudioCodecName 5 | from av.audio.stream import AudioStream 6 | from av.data.stream import DataStream 7 | from av.packet import Packet 8 | from av.subtitles.stream import SubtitleStream 9 | from av.video import _VideoCodecName 10 | from av.video.stream import VideoStream 11 | 12 | from .core import Container 13 | 14 | _StreamT = TypeVar("_StreamT", bound=Union[VideoStream, AudioStream, SubtitleStream]) 15 | 16 | class OutputContainer(Container): 17 | def __enter__(self) -> OutputContainer: ... 18 | @overload 19 | def add_stream( 20 | self, 21 | codec_name: _AudioCodecName, 22 | rate: int | None = None, 23 | options: dict[str, str] | None = None, 24 | **kwargs, 25 | ) -> AudioStream: ... 26 | @overload 27 | def add_stream( 28 | self, 29 | codec_name: _VideoCodecName, 30 | rate: Fraction | int | None = None, 31 | options: dict[str, str] | None = None, 32 | **kwargs, 33 | ) -> VideoStream: ... 34 | @overload 35 | def add_stream( 36 | self, 37 | codec_name: str, 38 | rate: Fraction | int | None = None, 39 | options: dict[str, str] | None = None, 40 | **kwargs, 41 | ) -> VideoStream | AudioStream | SubtitleStream: ... 42 | def add_stream_from_template( 43 | self, template: _StreamT, opaque: bool | None = None, **kwargs 44 | ) -> _StreamT: ... 45 | def add_data_stream( 46 | self, codec_name: str | None = None, options: dict[str, str] | None = None 47 | ) -> DataStream: ... 48 | def start_encoding(self) -> None: ... 49 | def close(self) -> None: ... 50 | def mux(self, packets: Packet | Sequence[Packet]) -> None: ... 51 | def mux_one(self, packet: Packet) -> None: ... 52 | @property 53 | def default_video_codec(self) -> str: ... 54 | @property 55 | def default_audio_codec(self) -> str: ... 56 | @property 57 | def default_subtitle_codec(self) -> str: ... 58 | @property 59 | def supported_codecs(self) -> set[str]: ... 60 | -------------------------------------------------------------------------------- /av/container/pyio.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | from libc.stdint cimport int64_t, uint8_t 3 | 4 | 5 | cdef int pyio_read(void *opaque, uint8_t *buf, int buf_size) noexcept nogil 6 | cdef int pyio_write(void *opaque, const uint8_t *buf, int buf_size) noexcept nogil 7 | cdef int64_t pyio_seek(void *opaque, int64_t offset, int whence) noexcept nogil 8 | cdef int pyio_close_gil(lib.AVIOContext *pb) 9 | cdef int pyio_close_custom_gil(lib.AVIOContext *pb) 10 | 11 | cdef class PyIOFile: 12 | # File-like source. 13 | cdef readonly object file 14 | cdef object fread 15 | cdef object fwrite 16 | cdef object fseek 17 | cdef object ftell 18 | cdef object fclose 19 | 20 | # Custom IO for above. 21 | cdef lib.AVIOContext *iocontext 22 | cdef unsigned char *buffer 23 | cdef long pos 24 | cdef bint pos_is_valid 25 | -------------------------------------------------------------------------------- /av/container/streams.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | from av.stream cimport Stream 4 | 5 | from .core cimport Container 6 | 7 | 8 | cdef class StreamContainer: 9 | cdef list _streams 10 | 11 | # For the different types. 12 | cdef readonly tuple video 13 | cdef readonly tuple audio 14 | cdef readonly tuple subtitles 15 | cdef readonly tuple attachments 16 | cdef readonly tuple data 17 | cdef readonly tuple other 18 | 19 | cdef add_stream(self, Stream stream) 20 | cdef int _get_best_stream_index(self, Container container, lib.AVMediaType type_enum, Stream related) noexcept 21 | 22 | -------------------------------------------------------------------------------- /av/container/streams.pyi: -------------------------------------------------------------------------------- 1 | from typing import Iterator, Literal, overload 2 | 3 | from av.attachments.stream import AttachmentStream 4 | from av.audio.stream import AudioStream 5 | from av.data.stream import DataStream 6 | from av.stream import Stream 7 | from av.subtitles.stream import SubtitleStream 8 | from av.video.stream import VideoStream 9 | 10 | class StreamContainer: 11 | video: tuple[VideoStream, ...] 12 | audio: tuple[AudioStream, ...] 13 | subtitles: tuple[SubtitleStream, ...] 14 | attachments: tuple[AttachmentStream, ...] 15 | data: tuple[DataStream, ...] 16 | other: tuple[Stream, ...] 17 | 18 | def __init__(self) -> None: ... 19 | def __len__(self) -> int: ... 20 | def __iter__(self) -> Iterator[Stream]: ... 21 | @overload 22 | def __getitem__(self, index: int) -> Stream: ... 23 | @overload 24 | def __getitem__(self, index: slice) -> list[Stream]: ... 25 | @overload 26 | def __getitem__(self, index: int | slice) -> Stream | list[Stream]: ... 27 | def get( 28 | self, 29 | *args: int | Stream | dict[str, int | tuple[int, ...]], 30 | **kwargs: int | tuple[int, ...], 31 | ) -> list[Stream]: ... 32 | def best( 33 | self, 34 | type: Literal["video", "audio", "subtitle", "data", "attachment"], 35 | /, 36 | related: Stream | None = None, 37 | ) -> Stream | None: ... 38 | -------------------------------------------------------------------------------- /av/data/__init__.pxd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyAV-Org/PyAV/633f237a6916d17b85fe937f95e28af651fbaf0e/av/data/__init__.pxd -------------------------------------------------------------------------------- /av/data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyAV-Org/PyAV/633f237a6916d17b85fe937f95e28af651fbaf0e/av/data/__init__.py -------------------------------------------------------------------------------- /av/data/stream.pxd: -------------------------------------------------------------------------------- 1 | from av.stream cimport Stream 2 | 3 | 4 | cdef class DataStream(Stream): 5 | pass 6 | -------------------------------------------------------------------------------- /av/data/stream.pyi: -------------------------------------------------------------------------------- 1 | from av.frame import Frame 2 | from av.packet import Packet 3 | from av.stream import Stream 4 | 5 | class DataStream(Stream): 6 | name: str | None 7 | -------------------------------------------------------------------------------- /av/data/stream.pyx: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | 4 | cdef class DataStream(Stream): 5 | def __repr__(self): 6 | return ( 7 | f"'} at 0x{id(self):x}>" 9 | ) 10 | 11 | @property 12 | def name(self): 13 | cdef const lib.AVCodecDescriptor *desc = lib.avcodec_descriptor_get(self.ptr.codecpar.codec_id) 14 | if desc == NULL: 15 | return None 16 | return desc.name 17 | -------------------------------------------------------------------------------- /av/descriptor.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | 4 | cdef class Descriptor: 5 | 6 | # These are present as: 7 | # - AVCodecContext.av_class (same as avcodec_get_class()) 8 | # - AVFormatContext.av_class (same as avformat_get_class()) 9 | # - AVFilterContext.av_class (same as avfilter_get_class()) 10 | # - AVCodec.priv_class 11 | # - AVOutputFormat.priv_class 12 | # - AVInputFormat.priv_class 13 | # - AVFilter.priv_class 14 | 15 | cdef const lib.AVClass *ptr 16 | 17 | cdef object _options # Option list cache. 18 | 19 | 20 | cdef Descriptor wrap_avclass(const lib.AVClass*) 21 | -------------------------------------------------------------------------------- /av/descriptor.pyi: -------------------------------------------------------------------------------- 1 | from typing import NoReturn 2 | 3 | from .option import Option 4 | 5 | class Descriptor: 6 | name: str 7 | options: tuple[Option, ...] 8 | -------------------------------------------------------------------------------- /av/descriptor.pyx: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | from .option cimport Option, OptionChoice, wrap_option, wrap_option_choice 4 | 5 | 6 | cdef object _cinit_sentinel = object() 7 | 8 | cdef Descriptor wrap_avclass(const lib.AVClass *ptr): 9 | if ptr == NULL: 10 | return None 11 | cdef Descriptor obj = Descriptor(_cinit_sentinel) 12 | obj.ptr = ptr 13 | return obj 14 | 15 | 16 | cdef class Descriptor: 17 | def __cinit__(self, sentinel): 18 | if sentinel is not _cinit_sentinel: 19 | raise RuntimeError("Cannot construct av.Descriptor") 20 | 21 | @property 22 | def name(self): 23 | return self.ptr.class_name if self.ptr.class_name else None 24 | 25 | @property 26 | def options(self): 27 | cdef const lib.AVOption *ptr = self.ptr.option 28 | cdef const lib.AVOption *choice_ptr 29 | cdef Option option 30 | cdef OptionChoice option_choice 31 | cdef bint choice_is_default 32 | if self._options is None: 33 | options = [] 34 | ptr = self.ptr.option 35 | while ptr != NULL and ptr.name != NULL: 36 | if ptr.type == lib.AV_OPT_TYPE_CONST: 37 | ptr += 1 38 | continue 39 | choices = [] 40 | if ptr.unit != NULL: # option has choices (matching const options) 41 | choice_ptr = self.ptr.option 42 | while choice_ptr != NULL and choice_ptr.name != NULL: 43 | if choice_ptr.type != lib.AV_OPT_TYPE_CONST or choice_ptr.unit != ptr.unit: 44 | choice_ptr += 1 45 | continue 46 | choice_is_default = (choice_ptr.default_val.i64 == ptr.default_val.i64 or 47 | ptr.type == lib.AV_OPT_TYPE_FLAGS and 48 | choice_ptr.default_val.i64 & ptr.default_val.i64) 49 | option_choice = wrap_option_choice(choice_ptr, choice_is_default) 50 | choices.append(option_choice) 51 | choice_ptr += 1 52 | option = wrap_option(tuple(choices), ptr) 53 | options.append(option) 54 | ptr += 1 55 | self._options = tuple(options) 56 | return self._options 57 | 58 | def __repr__(self): 59 | return f"<{self.__class__.__name__} {self.name} at 0x{id(self):x}>" 60 | -------------------------------------------------------------------------------- /av/dictionary.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | 4 | cdef class _Dictionary: 5 | 6 | cdef lib.AVDictionary *ptr 7 | 8 | cpdef _Dictionary copy(self) 9 | 10 | 11 | cdef _Dictionary wrap_dictionary(lib.AVDictionary *input_) 12 | -------------------------------------------------------------------------------- /av/dictionary.pyi: -------------------------------------------------------------------------------- 1 | from collections.abc import MutableMapping 2 | from typing import Iterator 3 | 4 | class Dictionary(MutableMapping[str, str]): 5 | def __getitem__(self, key: str) -> str: ... 6 | def __setitem__(self, key: str, value: str) -> None: ... 7 | def __delitem__(self, key: str) -> None: ... 8 | def __len__(self) -> int: ... 9 | def __iter__(self) -> Iterator[str]: ... 10 | def __repr__(self) -> str: ... 11 | -------------------------------------------------------------------------------- /av/dictionary.pyx: -------------------------------------------------------------------------------- 1 | from collections.abc import MutableMapping 2 | 3 | from av.error cimport err_check 4 | 5 | 6 | cdef class _Dictionary: 7 | def __cinit__(self, *args, **kwargs): 8 | for arg in args: 9 | self.update(arg) 10 | if kwargs: 11 | self.update(kwargs) 12 | 13 | def __dealloc__(self): 14 | if self.ptr != NULL: 15 | lib.av_dict_free(&self.ptr) 16 | 17 | def __getitem__(self, str key): 18 | cdef lib.AVDictionaryEntry *element = lib.av_dict_get(self.ptr, key, NULL, 0) 19 | if element != NULL: 20 | return element.value 21 | else: 22 | raise KeyError(key) 23 | 24 | def __setitem__(self, str key, str value): 25 | err_check(lib.av_dict_set(&self.ptr, key, value, 0)) 26 | 27 | def __delitem__(self, str key): 28 | err_check(lib.av_dict_set(&self.ptr, key, NULL, 0)) 29 | 30 | def __len__(self): 31 | return err_check(lib.av_dict_count(self.ptr)) 32 | 33 | def __iter__(self): 34 | cdef lib.AVDictionaryEntry *element = NULL 35 | while True: 36 | element = lib.av_dict_get(self.ptr, "", element, lib.AV_DICT_IGNORE_SUFFIX) 37 | if element == NULL: 38 | break 39 | yield element.key 40 | 41 | def __repr__(self): 42 | return f"av.Dictionary({dict(self)!r})" 43 | 44 | cpdef _Dictionary copy(self): 45 | cdef _Dictionary other = Dictionary() 46 | lib.av_dict_copy(&other.ptr, self.ptr, 0) 47 | return other 48 | 49 | 50 | class Dictionary(_Dictionary, MutableMapping): 51 | pass 52 | 53 | 54 | cdef _Dictionary wrap_dictionary(lib.AVDictionary *input_): 55 | cdef _Dictionary output = Dictionary() 56 | output.ptr = input_ 57 | return output 58 | -------------------------------------------------------------------------------- /av/error.pxd: -------------------------------------------------------------------------------- 1 | 2 | cdef int stash_exception(exc_info=*) 3 | cpdef int err_check(int res, filename=*) except -1 4 | -------------------------------------------------------------------------------- /av/filter/__init__.pxd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyAV-Org/PyAV/633f237a6916d17b85fe937f95e28af651fbaf0e/av/filter/__init__.pxd -------------------------------------------------------------------------------- /av/filter/__init__.py: -------------------------------------------------------------------------------- 1 | from .filter import Filter, FilterFlags, filter_descriptor, filters_available 2 | from .graph import Graph 3 | from .loudnorm import stats 4 | -------------------------------------------------------------------------------- /av/filter/__init__.pyi: -------------------------------------------------------------------------------- 1 | from .context import * 2 | from .filter import * 3 | from .graph import * 4 | from .loudnorm import * 5 | -------------------------------------------------------------------------------- /av/filter/context.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | from av.filter.filter cimport Filter 4 | from av.filter.graph cimport Graph 5 | 6 | 7 | cdef class FilterContext: 8 | 9 | cdef lib.AVFilterContext *ptr 10 | cdef readonly object _graph 11 | cdef readonly Filter filter 12 | 13 | cdef object _inputs 14 | cdef object _outputs 15 | 16 | cdef bint inited 17 | 18 | 19 | cdef FilterContext wrap_filter_context(Graph graph, Filter filter, lib.AVFilterContext *ptr) 20 | -------------------------------------------------------------------------------- /av/filter/context.pyi: -------------------------------------------------------------------------------- 1 | from av.filter import Graph 2 | from av.frame import Frame 3 | 4 | from .pad import FilterContextPad 5 | 6 | class FilterContext: 7 | name: str | None 8 | inputs: tuple[FilterContextPad, ...] 9 | outputs: tuple[FilterContextPad, ...] 10 | 11 | def init(self, args: str | None = None, **kwargs: str | None) -> None: ... 12 | def link_to( 13 | self, input_: FilterContext, output_idx: int = 0, input_idx: int = 0 14 | ) -> None: ... 15 | @property 16 | def graph(self) -> Graph: ... 17 | def push(self, frame: Frame) -> None: ... 18 | def pull(self) -> Frame: ... 19 | -------------------------------------------------------------------------------- /av/filter/filter.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | from av.descriptor cimport Descriptor 4 | 5 | 6 | cdef class Filter: 7 | 8 | cdef const lib.AVFilter *ptr 9 | 10 | cdef object _inputs 11 | cdef object _outputs 12 | cdef Descriptor _descriptor 13 | 14 | 15 | cdef Filter wrap_filter(const lib.AVFilter *ptr) 16 | -------------------------------------------------------------------------------- /av/filter/filter.pyi: -------------------------------------------------------------------------------- 1 | from av.descriptor import Descriptor 2 | from av.option import Option 3 | 4 | from .pad import FilterPad 5 | 6 | class Filter: 7 | name: str 8 | description: str 9 | 10 | descriptor: Descriptor 11 | options: tuple[Option, ...] | None 12 | flags: int 13 | dynamic_inputs: bool 14 | dynamic_outputs: bool 15 | timeline_support: bool 16 | slice_threads: bool 17 | command_support: bool 18 | inputs: tuple[FilterPad, ...] 19 | outputs: tuple[FilterPad, ...] 20 | 21 | def __init__(self, name: str) -> None: ... 22 | 23 | filters_available: set[str] 24 | -------------------------------------------------------------------------------- /av/filter/graph.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | from av.filter.context cimport FilterContext 4 | 5 | 6 | cdef class Graph: 7 | cdef object __weakref__ 8 | 9 | cdef lib.AVFilterGraph *ptr 10 | 11 | cdef readonly bint configured 12 | cpdef configure(self, bint auto_buffer=*, bint force=*) 13 | 14 | cdef dict _name_counts 15 | cdef str _get_unique_name(self, str name) 16 | 17 | cdef _register_context(self, FilterContext) 18 | cdef _auto_register(self) 19 | cdef int _nb_filters_seen 20 | cdef dict _context_by_ptr 21 | cdef dict _context_by_name 22 | cdef dict _context_by_type 23 | -------------------------------------------------------------------------------- /av/filter/graph.pyi: -------------------------------------------------------------------------------- 1 | from fractions import Fraction 2 | from typing import Any 3 | 4 | from av.audio.format import AudioFormat 5 | from av.audio.frame import AudioFrame 6 | from av.audio.layout import AudioLayout 7 | from av.audio.stream import AudioStream 8 | from av.video.format import VideoFormat 9 | from av.video.frame import VideoFrame 10 | from av.video.stream import VideoStream 11 | 12 | from .context import FilterContext 13 | from .filter import Filter 14 | 15 | class Graph: 16 | configured: bool 17 | 18 | def __init__(self) -> None: ... 19 | def configure(self, auto_buffer: bool = True, force: bool = False) -> None: ... 20 | def link_nodes(self, *nodes: FilterContext) -> Graph: ... 21 | def add( 22 | self, filter: str | Filter, args: Any = None, **kwargs: str 23 | ) -> FilterContext: ... 24 | def add_buffer( 25 | self, 26 | template: VideoStream | None = None, 27 | width: int | None = None, 28 | height: int | None = None, 29 | format: VideoFormat | None = None, 30 | name: str | None = None, 31 | time_base: Fraction | None = None, 32 | ) -> FilterContext: ... 33 | def add_abuffer( 34 | self, 35 | template: AudioStream | None = None, 36 | sample_rate: int | None = None, 37 | format: AudioFormat | str | None = None, 38 | layout: AudioLayout | str | None = None, 39 | channels: int | None = None, 40 | name: str | None = None, 41 | time_base: Fraction | None = None, 42 | ) -> FilterContext: ... 43 | def set_audio_frame_size(self, frame_size: int) -> None: ... 44 | def push(self, frame: None | AudioFrame | VideoFrame) -> None: ... 45 | def pull(self) -> VideoFrame | AudioFrame: ... 46 | def vpush(self, frame: VideoFrame | None) -> None: ... 47 | def vpull(self) -> VideoFrame: ... 48 | -------------------------------------------------------------------------------- /av/filter/link.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | from av.filter.graph cimport Graph 4 | from av.filter.pad cimport FilterContextPad 5 | 6 | 7 | cdef class FilterLink: 8 | 9 | cdef readonly Graph graph 10 | cdef lib.AVFilterLink *ptr 11 | 12 | cdef FilterContextPad _input 13 | cdef FilterContextPad _output 14 | 15 | 16 | cdef FilterLink wrap_filter_link(Graph graph, lib.AVFilterLink *ptr) 17 | -------------------------------------------------------------------------------- /av/filter/link.pyi: -------------------------------------------------------------------------------- 1 | from .pad import FilterContextPad 2 | 3 | class FilterLink: 4 | input: FilterContextPad 5 | output: FilterContextPad 6 | -------------------------------------------------------------------------------- /av/filter/link.pyx: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | from av.filter.graph cimport Graph 4 | 5 | 6 | cdef _cinit_sentinel = object() 7 | 8 | 9 | cdef class FilterLink: 10 | 11 | def __cinit__(self, sentinel): 12 | if sentinel is not _cinit_sentinel: 13 | raise RuntimeError("cannot instantiate FilterLink") 14 | 15 | @property 16 | def input(self): 17 | if self._input: 18 | return self._input 19 | cdef lib.AVFilterContext *cctx = self.ptr.src 20 | cdef unsigned int i 21 | for i in range(cctx.nb_outputs): 22 | if self.ptr == cctx.outputs[i]: 23 | break 24 | else: 25 | raise RuntimeError("could not find link in context") 26 | ctx = self.graph._context_by_ptr[cctx] 27 | self._input = ctx.outputs[i] 28 | return self._input 29 | 30 | @property 31 | def output(self): 32 | if self._output: 33 | return self._output 34 | cdef lib.AVFilterContext *cctx = self.ptr.dst 35 | cdef unsigned int i 36 | for i in range(cctx.nb_inputs): 37 | if self.ptr == cctx.inputs[i]: 38 | break 39 | else: 40 | raise RuntimeError("could not find link in context") 41 | try: 42 | ctx = self.graph._context_by_ptr[cctx] 43 | except KeyError: 44 | raise RuntimeError("could not find context in graph", (cctx.name, cctx.filter.name)) 45 | self._output = ctx.inputs[i] 46 | return self._output 47 | 48 | 49 | cdef FilterLink wrap_filter_link(Graph graph, lib.AVFilterLink *ptr): 50 | cdef FilterLink link = FilterLink(_cinit_sentinel) 51 | link.graph = graph 52 | link.ptr = ptr 53 | return link 54 | -------------------------------------------------------------------------------- /av/filter/loudnorm.pxd: -------------------------------------------------------------------------------- 1 | from av.audio.stream cimport AudioStream 2 | 3 | 4 | cdef extern from "libavcodec/avcodec.h": 5 | ctypedef struct AVCodecContext: 6 | pass 7 | 8 | cdef extern from "libavformat/avformat.h": 9 | ctypedef struct AVFormatContext: 10 | pass 11 | 12 | cdef extern from "loudnorm_impl.h": 13 | char* loudnorm_get_stats( 14 | AVFormatContext* fmt_ctx, 15 | int audio_stream_index, 16 | const char* loudnorm_args 17 | ) nogil 18 | 19 | cpdef bytes stats(str loudnorm_args, AudioStream stream) 20 | -------------------------------------------------------------------------------- /av/filter/loudnorm.py: -------------------------------------------------------------------------------- 1 | import cython 2 | from cython.cimports.av.audio.stream import AudioStream 3 | from cython.cimports.av.container.core import Container 4 | from cython.cimports.libc.stdlib import free 5 | 6 | from av.logging import get_level, set_level 7 | 8 | 9 | @cython.ccall 10 | def stats(loudnorm_args: str, stream: AudioStream) -> bytes: 11 | """ 12 | Get loudnorm statistics for an audio stream. 13 | 14 | Args: 15 | loudnorm_args (str): Arguments for the loudnorm filter (e.g. "i=-24.0:lra=7.0:tp=-2.0") 16 | stream (AudioStream): Input audio stream to analyze 17 | 18 | Returns: 19 | bytes: JSON string containing the loudnorm statistics 20 | """ 21 | 22 | if "print_format=json" not in loudnorm_args: 23 | loudnorm_args = loudnorm_args + ":print_format=json" 24 | 25 | container: Container = stream.container 26 | format_ptr: cython.pointer[AVFormatContext] = container.ptr 27 | container.ptr = cython.NULL # Prevent double-free 28 | 29 | stream_index: cython.int = stream.index 30 | py_args: bytes = loudnorm_args.encode("utf-8") 31 | c_args: cython.p_const_char = py_args 32 | result: cython.p_char 33 | 34 | # Save log level since C function overwrite it. 35 | level = get_level() 36 | 37 | with cython.nogil: 38 | result = loudnorm_get_stats(format_ptr, stream_index, c_args) 39 | 40 | if result == cython.NULL: 41 | raise RuntimeError("Failed to get loudnorm stats") 42 | 43 | py_result = result[:] # Make a copy of the string 44 | free(result) # Free the C string 45 | 46 | set_level(level) 47 | 48 | return py_result 49 | -------------------------------------------------------------------------------- /av/filter/loudnorm.pyi: -------------------------------------------------------------------------------- 1 | from av.audio.stream import AudioStream 2 | 3 | def stats(loudnorm_args: str, stream: AudioStream) -> bytes: ... 4 | -------------------------------------------------------------------------------- /av/filter/loudnorm_impl.h: -------------------------------------------------------------------------------- 1 | #ifndef AV_FILTER_LOUDNORM_H 2 | #define AV_FILTER_LOUDNORM_H 3 | 4 | #include 5 | 6 | char* loudnorm_get_stats( 7 | AVFormatContext* fmt_ctx, 8 | int audio_stream_index, 9 | const char* loudnorm_args 10 | ); 11 | 12 | #endif // AV_FILTER_LOUDNORM_H -------------------------------------------------------------------------------- /av/filter/pad.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | from av.filter.context cimport FilterContext 4 | from av.filter.filter cimport Filter 5 | from av.filter.link cimport FilterLink 6 | 7 | 8 | cdef class FilterPad: 9 | 10 | cdef readonly Filter filter 11 | cdef readonly FilterContext context 12 | cdef readonly bint is_input 13 | cdef readonly int index 14 | 15 | cdef const lib.AVFilterPad *base_ptr 16 | 17 | 18 | cdef class FilterContextPad(FilterPad): 19 | 20 | cdef FilterLink _link 21 | 22 | 23 | cdef tuple alloc_filter_pads(Filter, const lib.AVFilterPad *ptr, bint is_input, FilterContext context=?) 24 | -------------------------------------------------------------------------------- /av/filter/pad.pyi: -------------------------------------------------------------------------------- 1 | from .link import FilterLink 2 | 3 | class FilterPad: 4 | is_output: bool 5 | name: str 6 | type: str 7 | 8 | class FilterContextPad(FilterPad): 9 | link: FilterLink | None 10 | linked: FilterContextPad | None 11 | -------------------------------------------------------------------------------- /av/filter/pad.pyx: -------------------------------------------------------------------------------- 1 | from av.filter.link cimport wrap_filter_link 2 | 3 | 4 | cdef object _cinit_sentinel = object() 5 | 6 | 7 | cdef class FilterPad: 8 | def __cinit__(self, sentinel): 9 | if sentinel is not _cinit_sentinel: 10 | raise RuntimeError("cannot construct FilterPad") 11 | 12 | def __repr__(self): 13 | _filter = self.filter.name 14 | _io = "inputs" if self.is_input else "outputs" 15 | 16 | return f"" 17 | 18 | @property 19 | def is_output(self): 20 | return not self.is_input 21 | 22 | @property 23 | def name(self): 24 | return lib.avfilter_pad_get_name(self.base_ptr, self.index) 25 | 26 | @property 27 | def type(self): 28 | """ 29 | The media type of this filter pad. 30 | 31 | Examples: `'audio'`, `'video'`, `'subtitle'`. 32 | 33 | :type: str 34 | """ 35 | return lib.av_get_media_type_string(lib.avfilter_pad_get_type(self.base_ptr, self.index)) 36 | 37 | 38 | cdef class FilterContextPad(FilterPad): 39 | def __repr__(self): 40 | _filter = self.filter.name 41 | _io = "inputs" if self.is_input else "outputs" 42 | context = self.context.name 43 | 44 | return f"" 45 | 46 | @property 47 | def link(self): 48 | if self._link: 49 | return self._link 50 | cdef lib.AVFilterLink **links = self.context.ptr.inputs if self.is_input else self.context.ptr.outputs 51 | cdef lib.AVFilterLink *link = links[self.index] 52 | if not link: 53 | return 54 | self._link = wrap_filter_link(self.context.graph, link) 55 | return self._link 56 | 57 | @property 58 | def linked(self): 59 | cdef FilterLink link = self.link 60 | if link: 61 | return link.input if self.is_input else link.output 62 | 63 | 64 | cdef tuple alloc_filter_pads(Filter filter, const lib.AVFilterPad *ptr, bint is_input, FilterContext context=None): 65 | if not ptr: 66 | return () 67 | 68 | pads = [] 69 | 70 | # We need to be careful and check our bounds if we know what they are, 71 | # since the arrays on a AVFilterContext are not NULL terminated. 72 | cdef int i = 0 73 | cdef int count 74 | if context is None: 75 | # This is a custom function defined using a macro in avfilter.pxd. Its usage 76 | # can be changed after we stop supporting FFmpeg < 5.0. 77 | count = lib.pyav_get_num_pads(filter.ptr, not is_input, ptr) 78 | else: 79 | count = (context.ptr.nb_inputs if is_input else context.ptr.nb_outputs) 80 | 81 | cdef FilterPad pad 82 | while (i < count): 83 | pad = FilterPad(_cinit_sentinel) if context is None else FilterContextPad(_cinit_sentinel) 84 | pads.append(pad) 85 | pad.filter = filter 86 | pad.context = context 87 | pad.is_input = is_input 88 | pad.base_ptr = ptr 89 | pad.index = i 90 | i += 1 91 | 92 | return tuple(pads) 93 | -------------------------------------------------------------------------------- /av/format.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | 4 | cdef class ContainerFormat: 5 | 6 | cdef readonly str name 7 | 8 | cdef lib.AVInputFormat *iptr 9 | cdef lib.AVOutputFormat *optr 10 | 11 | 12 | cdef ContainerFormat build_container_format(lib.AVInputFormat*, lib.AVOutputFormat*) 13 | -------------------------------------------------------------------------------- /av/format.pyi: -------------------------------------------------------------------------------- 1 | __all__ = ("Flags", "ContainerFormat", "formats_available") 2 | 3 | from enum import Flag 4 | from typing import ClassVar, Literal, cast 5 | 6 | class Flags(Flag): 7 | no_file = cast(ClassVar[Flags], ...) 8 | need_number = cast(ClassVar[Flags], ...) 9 | show_ids = cast(ClassVar[Flags], ...) 10 | global_header = cast(ClassVar[Flags], ...) 11 | no_timestamps = cast(ClassVar[Flags], ...) 12 | generic_index = cast(ClassVar[Flags], ...) 13 | ts_discont = cast(ClassVar[Flags], ...) 14 | variable_fps = cast(ClassVar[Flags], ...) 15 | no_dimensions = cast(ClassVar[Flags], ...) 16 | no_streams = cast(ClassVar[Flags], ...) 17 | no_bin_search = cast(ClassVar[Flags], ...) 18 | no_gen_search = cast(ClassVar[Flags], ...) 19 | no_byte_seek = cast(ClassVar[Flags], ...) 20 | allow_flush = cast(ClassVar[Flags], ...) 21 | ts_nonstrict = cast(ClassVar[Flags], ...) 22 | ts_negative = cast(ClassVar[Flags], ...) 23 | seek_to_pts = cast(ClassVar[Flags], ...) 24 | 25 | class ContainerFormat: 26 | def __init__(self, name: str, mode: Literal["r", "w"] | None = None) -> None: ... 27 | @property 28 | def name(self) -> str: ... 29 | @property 30 | def long_name(self) -> str: ... 31 | @property 32 | def is_input(self) -> bool: ... 33 | @property 34 | def is_output(self) -> bool: ... 35 | @property 36 | def extensions(self) -> set[str]: ... 37 | @property 38 | def flags(self) -> int: ... 39 | @property 40 | def no_file(self) -> bool: ... 41 | 42 | formats_available: set[str] 43 | -------------------------------------------------------------------------------- /av/frame.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | from av.packet cimport Packet 4 | from av.sidedata.sidedata cimport _SideDataContainer 5 | 6 | 7 | cdef class Frame: 8 | cdef lib.AVFrame *ptr 9 | # We define our own time. 10 | cdef lib.AVRational _time_base 11 | cdef _rebase_time(self, lib.AVRational) 12 | cdef _SideDataContainer _side_data 13 | cdef _copy_internal_attributes(self, Frame source, bint data_layout=?) 14 | cdef _init_user_attributes(self) 15 | -------------------------------------------------------------------------------- /av/frame.pyi: -------------------------------------------------------------------------------- 1 | from fractions import Fraction 2 | from typing import TypedDict 3 | 4 | from av.sidedata.motionvectors import MotionVectors 5 | 6 | class SideData(TypedDict, total=False): 7 | MOTION_VECTORS: MotionVectors 8 | 9 | class Frame: 10 | dts: int | None 11 | pts: int | None 12 | duration: int | None 13 | time_base: Fraction 14 | side_data: SideData 15 | opaque: object 16 | @property 17 | def time(self) -> float | None: ... 18 | @property 19 | def is_corrupt(self) -> bool: ... 20 | @property 21 | def key_frame(self) -> bool: ... 22 | def make_writable(self) -> None: ... 23 | -------------------------------------------------------------------------------- /av/logging.pxd: -------------------------------------------------------------------------------- 1 | 2 | cpdef get_last_error() 3 | -------------------------------------------------------------------------------- /av/logging.pyi: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable 2 | 3 | PANIC: int 4 | FATAL: int 5 | ERROR: int 6 | WARNING: int 7 | INFO: int 8 | VERBOSE: int 9 | DEBUG: int 10 | TRACE: int 11 | CRITICAL: int 12 | 13 | def adapt_level(level: int) -> int: ... 14 | def get_level() -> int | None: ... 15 | def set_level(level: int | None) -> None: ... 16 | def set_libav_level(level: int) -> None: ... 17 | def restore_default_callback() -> None: ... 18 | def get_skip_repeated() -> bool: ... 19 | def set_skip_repeated(v: bool) -> None: ... 20 | def get_last_error() -> tuple[int, tuple[int, str, str] | None]: ... 21 | def log(level: int, name: str, message: str) -> None: ... 22 | 23 | class Capture: 24 | logs: list[tuple[int, str, str]] 25 | 26 | def __init__(self, local: bool = True) -> None: ... 27 | def __enter__(self) -> list[tuple[int, str, str]]: ... 28 | def __exit__( 29 | self, 30 | type_: type | None, 31 | value: Exception | None, 32 | traceback: Callable[..., Any] | None, 33 | ) -> None: ... 34 | -------------------------------------------------------------------------------- /av/opaque.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | 4 | cdef class OpaqueContainer: 5 | cdef dict _by_name 6 | 7 | cdef lib.AVBufferRef *add(self, object v) 8 | cdef object get(self, bytes name) 9 | cdef object pop(self, bytes name) 10 | 11 | 12 | cdef OpaqueContainer opaque_container 13 | -------------------------------------------------------------------------------- /av/opaque.pyx: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | from libc.stdint cimport uint8_t 3 | 4 | from uuid import uuid4 5 | 6 | 7 | cdef void key_free(void *opaque, uint8_t *data) noexcept nogil: 8 | cdef char *name = data 9 | with gil: 10 | opaque_container.pop(name) 11 | 12 | 13 | cdef class OpaqueContainer: 14 | """A container that holds references to Python objects, indexed by uuid""" 15 | 16 | def __cinit__(self): 17 | self._by_name = {} 18 | 19 | cdef lib.AVBufferRef *add(self, v): 20 | cdef bytes uuid = str(uuid4()).encode("utf-8") 21 | cdef lib.AVBufferRef *ref = lib.av_buffer_create(uuid, len(uuid), &key_free, NULL, 0) 22 | self._by_name[uuid] = v 23 | return ref 24 | 25 | cdef object get(self, bytes name): 26 | return self._by_name.get(name) 27 | 28 | cdef object pop(self, bytes name): 29 | return self._by_name.pop(name) 30 | 31 | 32 | cdef opaque_container = OpaqueContainer() 33 | -------------------------------------------------------------------------------- /av/option.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | 4 | cdef class BaseOption: 5 | 6 | cdef const lib.AVOption *ptr 7 | 8 | 9 | cdef class Option(BaseOption): 10 | 11 | cdef readonly tuple choices 12 | 13 | 14 | cdef class OptionChoice(BaseOption): 15 | 16 | cdef readonly bint is_default 17 | 18 | 19 | cdef Option wrap_option(tuple choices, const lib.AVOption *ptr) 20 | 21 | cdef OptionChoice wrap_option_choice(const lib.AVOption *ptr, bint is_default) 22 | -------------------------------------------------------------------------------- /av/option.pyi: -------------------------------------------------------------------------------- 1 | from enum import Enum, Flag 2 | from typing import cast 3 | 4 | class OptionType(Enum): 5 | FLAGS = cast(int, ...) 6 | INT = cast(int, ...) 7 | INT64 = cast(int, ...) 8 | DOUBLE = cast(int, ...) 9 | FLOAT = cast(int, ...) 10 | STRING = cast(int, ...) 11 | RATIONAL = cast(int, ...) 12 | BINARY = cast(int, ...) 13 | DICT = cast(int, ...) 14 | CONST = cast(int, ...) 15 | IMAGE_SIZE = cast(int, ...) 16 | PIXEL_FMT = cast(int, ...) 17 | SAMPLE_FMT = cast(int, ...) 18 | VIDEO_RATE = cast(int, ...) 19 | DURATION = cast(int, ...) 20 | COLOR = cast(int, ...) 21 | CHANNEL_LAYOUT = cast(int, ...) 22 | BOOL = cast(int, ...) 23 | 24 | class OptionFlags(Flag): 25 | ENCODING_PARAM = cast(int, ...) 26 | DECODING_PARAM = cast(int, ...) 27 | AUDIO_PARAM = cast(int, ...) 28 | VIDEO_PARAM = cast(int, ...) 29 | SUBTITLE_PARAM = cast(int, ...) 30 | EXPORT = cast(int, ...) 31 | READONLY = cast(int, ...) 32 | FILTERING_PARAM = cast(int, ...) 33 | 34 | class BaseOption: 35 | name: str 36 | help: str 37 | flags: int 38 | is_encoding_param: bool 39 | is_decoding_param: bool 40 | is_audio_param: bool 41 | is_video_param: bool 42 | is_subtitle_param: bool 43 | is_export: bool 44 | is_readonly: bool 45 | is_filtering_param: bool 46 | 47 | class Option(BaseOption): 48 | type: OptionType 49 | offset: int 50 | default: int 51 | min: int 52 | max: int 53 | 54 | class OptionChoice(BaseOption): 55 | value: int 56 | -------------------------------------------------------------------------------- /av/packet.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | from av.buffer cimport Buffer 4 | from av.bytesource cimport ByteSource 5 | from av.stream cimport Stream 6 | 7 | 8 | cdef class Packet(Buffer): 9 | 10 | cdef lib.AVPacket* ptr 11 | 12 | cdef Stream _stream 13 | 14 | # We track our own time. 15 | cdef lib.AVRational _time_base 16 | cdef _rebase_time(self, lib.AVRational) 17 | 18 | # Hold onto the original reference. 19 | cdef ByteSource source 20 | cdef size_t _buffer_size(self) 21 | cdef void* _buffer_ptr(self) 22 | -------------------------------------------------------------------------------- /av/packet.pyi: -------------------------------------------------------------------------------- 1 | from fractions import Fraction 2 | 3 | from av.subtitles.subtitle import SubtitleSet 4 | 5 | from .buffer import Buffer 6 | from .stream import Stream 7 | 8 | class Packet(Buffer): 9 | stream: Stream 10 | stream_index: int 11 | time_base: Fraction 12 | pts: int | None 13 | dts: int | None 14 | pos: int | None 15 | size: int 16 | duration: int | None 17 | opaque: object 18 | is_keyframe: bool 19 | is_corrupt: bool 20 | is_discard: bool 21 | is_trusted: bool 22 | is_disposable: bool 23 | 24 | def __init__(self, input: int | bytes | None = None) -> None: ... 25 | def decode(self) -> list[SubtitleSet]: ... 26 | -------------------------------------------------------------------------------- /av/plane.pxd: -------------------------------------------------------------------------------- 1 | from av.buffer cimport Buffer 2 | from av.frame cimport Frame 3 | 4 | 5 | cdef class Plane(Buffer): 6 | 7 | cdef Frame frame 8 | cdef int index 9 | 10 | cdef size_t _buffer_size(self) 11 | cdef void* _buffer_ptr(self) 12 | -------------------------------------------------------------------------------- /av/plane.pyi: -------------------------------------------------------------------------------- 1 | from .buffer import Buffer 2 | from .frame import Frame 3 | 4 | class Plane(Buffer): 5 | frame: Frame 6 | index: int 7 | 8 | def __init__(self, frame: Frame, index: int) -> None: ... 9 | -------------------------------------------------------------------------------- /av/plane.pyx: -------------------------------------------------------------------------------- 1 | 2 | cdef class Plane(Buffer): 3 | """ 4 | Base class for audio and video planes. 5 | 6 | See also :class:`~av.audio.plane.AudioPlane` and :class:`~av.video.plane.VideoPlane`. 7 | """ 8 | 9 | def __cinit__(self, Frame frame, int index): 10 | self.frame = frame 11 | self.index = index 12 | 13 | def __repr__(self): 14 | return ( 15 | f"" 17 | ) 18 | 19 | cdef void* _buffer_ptr(self): 20 | return self.frame.ptr.extended_data[self.index] 21 | -------------------------------------------------------------------------------- /av/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyAV-Org/PyAV/633f237a6916d17b85fe937f95e28af651fbaf0e/av/py.typed -------------------------------------------------------------------------------- /av/sidedata/__init__.pxd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyAV-Org/PyAV/633f237a6916d17b85fe937f95e28af651fbaf0e/av/sidedata/__init__.pxd -------------------------------------------------------------------------------- /av/sidedata/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyAV-Org/PyAV/633f237a6916d17b85fe937f95e28af651fbaf0e/av/sidedata/__init__.py -------------------------------------------------------------------------------- /av/sidedata/motionvectors.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | from av.frame cimport Frame 4 | from av.sidedata.sidedata cimport SideData 5 | 6 | 7 | cdef class _MotionVectors(SideData): 8 | 9 | cdef dict _vectors 10 | cdef int _len 11 | 12 | 13 | cdef class MotionVector: 14 | 15 | cdef _MotionVectors parent 16 | cdef lib.AVMotionVector *ptr 17 | -------------------------------------------------------------------------------- /av/sidedata/motionvectors.pyi: -------------------------------------------------------------------------------- 1 | from typing import Any, Sequence, overload 2 | 3 | import numpy as np 4 | 5 | from .sidedata import SideData 6 | 7 | class MotionVectors(SideData, Sequence[MotionVector]): 8 | @overload 9 | def __getitem__(self, index: int): ... 10 | @overload 11 | def __getitem__(self, index: slice): ... 12 | @overload 13 | def __getitem__(self, index: int | slice): ... 14 | def __len__(self) -> int: ... 15 | def to_ndarray(self) -> np.ndarray[Any, Any]: ... 16 | 17 | class MotionVector: 18 | source: int 19 | w: int 20 | h: int 21 | src_x: int 22 | src_y: int 23 | dst_x: int 24 | dst_y: int 25 | motion_x: int 26 | motion_y: int 27 | motion_scale: int 28 | -------------------------------------------------------------------------------- /av/sidedata/sidedata.pxd: -------------------------------------------------------------------------------- 1 | 2 | cimport libav as lib 3 | 4 | from av.buffer cimport Buffer 5 | from av.dictionary cimport _Dictionary, wrap_dictionary 6 | from av.frame cimport Frame 7 | 8 | 9 | cdef class SideData(Buffer): 10 | cdef Frame frame 11 | cdef lib.AVFrameSideData *ptr 12 | cdef _Dictionary metadata 13 | 14 | 15 | cdef SideData wrap_side_data(Frame frame, int index) 16 | 17 | cdef int get_display_rotation(Frame frame) 18 | 19 | cdef class _SideDataContainer: 20 | cdef Frame frame 21 | 22 | cdef list _by_index 23 | cdef dict _by_type 24 | -------------------------------------------------------------------------------- /av/sidedata/sidedata.pyi: -------------------------------------------------------------------------------- 1 | from collections.abc import Mapping 2 | from enum import Enum 3 | from typing import ClassVar, Iterator, Sequence, cast, overload 4 | 5 | from av.buffer import Buffer 6 | from av.frame import Frame 7 | 8 | class Type(Enum): 9 | PANSCAN = cast(ClassVar[Type], ...) 10 | A53_CC = cast(ClassVar[Type], ...) 11 | STEREO3D = cast(ClassVar[Type], ...) 12 | MATRIXENCODING = cast(ClassVar[Type], ...) 13 | DOWNMIX_INFO = cast(ClassVar[Type], ...) 14 | REPLAYGAIN = cast(ClassVar[Type], ...) 15 | DISPLAYMATRIX = cast(ClassVar[Type], ...) 16 | AFD = cast(ClassVar[Type], ...) 17 | MOTION_VECTORS = cast(ClassVar[Type], ...) 18 | SKIP_SAMPLES = cast(ClassVar[Type], ...) 19 | AUDIO_SERVICE_TYPE = cast(ClassVar[Type], ...) 20 | MASTERING_DISPLAY_METADATA = cast(ClassVar[Type], ...) 21 | GOP_TIMECODE = cast(ClassVar[Type], ...) 22 | SPHERICAL = cast(ClassVar[Type], ...) 23 | CONTENT_LIGHT_LEVEL = cast(ClassVar[Type], ...) 24 | ICC_PROFILE = cast(ClassVar[Type], ...) 25 | S12M_TIMECODE = cast(ClassVar[Type], ...) 26 | DYNAMIC_HDR_PLUS = cast(ClassVar[Type], ...) 27 | REGIONS_OF_INTEREST = cast(ClassVar[Type], ...) 28 | VIDEO_ENC_PARAMS = cast(ClassVar[Type], ...) 29 | SEI_UNREGISTERED = cast(ClassVar[Type], ...) 30 | FILM_GRAIN_PARAMS = cast(ClassVar[Type], ...) 31 | DETECTION_BBOXES = cast(ClassVar[Type], ...) 32 | DOVI_RPU_BUFFER = cast(ClassVar[Type], ...) 33 | DOVI_METADATA = cast(ClassVar[Type], ...) 34 | DYNAMIC_HDR_VIVID = cast(ClassVar[Type], ...) 35 | AMBIENT_VIEWING_ENVIRONMENT = cast(ClassVar[Type], ...) 36 | VIDEO_HINT = cast(ClassVar[Type], ...) 37 | 38 | class SideData(Buffer): 39 | type: Type 40 | 41 | class SideDataContainer(Mapping): 42 | frame: Frame 43 | def __len__(self) -> int: ... 44 | def __iter__(self) -> Iterator[SideData]: ... 45 | @overload 46 | def __getitem__(self, key: str | int | Type) -> SideData: ... 47 | @overload 48 | def __getitem__(self, key: slice) -> Sequence[SideData]: ... 49 | @overload 50 | def __getitem__( 51 | self, key: str | int | Type | slice 52 | ) -> SideData | Sequence[SideData]: ... 53 | -------------------------------------------------------------------------------- /av/stream.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | from av.codec.context cimport CodecContext 4 | from av.container.core cimport Container 5 | from av.frame cimport Frame 6 | from av.packet cimport Packet 7 | 8 | 9 | cdef class Stream: 10 | cdef lib.AVStream *ptr 11 | 12 | # Stream attributes. 13 | cdef readonly Container container 14 | cdef readonly dict metadata 15 | 16 | # CodecContext attributes. 17 | cdef readonly CodecContext codec_context 18 | 19 | # Private API. 20 | cdef _init(self, Container, lib.AVStream*, CodecContext) 21 | cdef _finalize_for_output(self) 22 | cdef _set_time_base(self, value) 23 | cdef _set_id(self, value) 24 | 25 | 26 | cdef Stream wrap_stream(Container, lib.AVStream*, CodecContext) 27 | -------------------------------------------------------------------------------- /av/stream.pyi: -------------------------------------------------------------------------------- 1 | from enum import Flag 2 | from fractions import Fraction 3 | from typing import Literal, cast 4 | 5 | from .codec import Codec, CodecContext 6 | from .container import Container 7 | 8 | class Disposition(Flag): 9 | default = cast(int, ...) 10 | dub = cast(int, ...) 11 | original = cast(int, ...) 12 | comment = cast(int, ...) 13 | lyrics = cast(int, ...) 14 | karaoke = cast(int, ...) 15 | forced = cast(int, ...) 16 | hearing_impaired = cast(int, ...) 17 | visual_impaired = cast(int, ...) 18 | clean_effects = cast(int, ...) 19 | attached_pic = cast(int, ...) 20 | timed_thumbnails = cast(int, ...) 21 | non_diegetic = cast(int, ...) 22 | captions = cast(int, ...) 23 | descriptions = cast(int, ...) 24 | metadata = cast(int, ...) 25 | dependent = cast(int, ...) 26 | still_image = cast(int, ...) 27 | multilayer = cast(int, ...) 28 | 29 | class Stream: 30 | name: str | None 31 | container: Container 32 | codec: Codec 33 | codec_context: CodecContext 34 | metadata: dict[str, str] 35 | id: int 36 | profiles: list[str] 37 | profile: str | None 38 | index: int 39 | time_base: Fraction | None 40 | average_rate: Fraction | None 41 | base_rate: Fraction | None 42 | guessed_rate: Fraction | None 43 | start_time: int | None 44 | duration: int | None 45 | disposition: Disposition 46 | frames: int 47 | language: str | None 48 | type: Literal["video", "audio", "data", "subtitle", "attachment"] 49 | -------------------------------------------------------------------------------- /av/subtitles/__init__.pxd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyAV-Org/PyAV/633f237a6916d17b85fe937f95e28af651fbaf0e/av/subtitles/__init__.pxd -------------------------------------------------------------------------------- /av/subtitles/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyAV-Org/PyAV/633f237a6916d17b85fe937f95e28af651fbaf0e/av/subtitles/__init__.py -------------------------------------------------------------------------------- /av/subtitles/codeccontext.pxd: -------------------------------------------------------------------------------- 1 | from av.codec.context cimport CodecContext 2 | 3 | 4 | cdef class SubtitleCodecContext(CodecContext): 5 | pass 6 | -------------------------------------------------------------------------------- /av/subtitles/codeccontext.pyi: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from av.codec.context import CodecContext 4 | 5 | class SubtitleCodecContext(CodecContext): 6 | type: Literal["subtitle"] 7 | -------------------------------------------------------------------------------- /av/subtitles/codeccontext.pyx: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | from av.error cimport err_check 4 | from av.packet cimport Packet 5 | from av.subtitles.subtitle cimport SubtitleProxy, SubtitleSet 6 | 7 | 8 | cdef class SubtitleCodecContext(CodecContext): 9 | cdef _send_packet_and_recv(self, Packet packet): 10 | if packet is None: 11 | raise RuntimeError("packet cannot be None") 12 | 13 | cdef SubtitleProxy proxy = SubtitleProxy() 14 | cdef int got_frame = 0 15 | 16 | err_check( 17 | lib.avcodec_decode_subtitle2(self.ptr, &proxy.struct, &got_frame, packet.ptr) 18 | ) 19 | 20 | if got_frame: 21 | return [SubtitleSet(proxy)] 22 | else: 23 | return [] 24 | -------------------------------------------------------------------------------- /av/subtitles/stream.pxd: -------------------------------------------------------------------------------- 1 | from av.packet cimport Packet 2 | from av.stream cimport Stream 3 | 4 | 5 | cdef class SubtitleStream(Stream): 6 | cpdef decode(self, Packet packet=?) 7 | -------------------------------------------------------------------------------- /av/subtitles/stream.pyi: -------------------------------------------------------------------------------- 1 | from av.packet import Packet 2 | from av.stream import Stream 3 | from av.subtitles.subtitle import SubtitleSet 4 | 5 | class SubtitleStream(Stream): 6 | def decode(self, packet: Packet | None = None) -> list[SubtitleSet]: ... 7 | -------------------------------------------------------------------------------- /av/subtitles/stream.pyx: -------------------------------------------------------------------------------- 1 | from av.packet cimport Packet 2 | from av.stream cimport Stream 3 | 4 | 5 | cdef class SubtitleStream(Stream): 6 | """ 7 | A :class:`SubtitleStream` can contain many :class:`SubtitleSet` objects accessible via decoding. 8 | """ 9 | def __getattr__(self, name): 10 | return getattr(self.codec_context, name) 11 | 12 | cpdef decode(self, Packet packet=None): 13 | """ 14 | Decode a :class:`.Packet` and return a list of :class:`.SubtitleSet`. 15 | 16 | :rtype: list[SubtitleSet] 17 | 18 | .. seealso:: This is a passthrough to :meth:`.CodecContext.decode`. 19 | """ 20 | if not packet: 21 | packet = Packet() 22 | 23 | return self.codec_context.decode(packet) 24 | -------------------------------------------------------------------------------- /av/subtitles/subtitle.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | 4 | cdef class SubtitleProxy: 5 | cdef lib.AVSubtitle struct 6 | 7 | 8 | cdef class SubtitleSet: 9 | cdef SubtitleProxy proxy 10 | cdef readonly tuple rects 11 | 12 | 13 | cdef class Subtitle: 14 | cdef SubtitleProxy proxy 15 | cdef lib.AVSubtitleRect *ptr 16 | cdef readonly bytes type 17 | 18 | cdef class TextSubtitle(Subtitle): 19 | pass 20 | 21 | cdef class ASSSubtitle(Subtitle): 22 | pass 23 | 24 | cdef class BitmapSubtitle(Subtitle): 25 | cdef readonly planes 26 | 27 | cdef class BitmapSubtitlePlane: 28 | cdef readonly BitmapSubtitle subtitle 29 | cdef readonly int index 30 | cdef readonly long buffer_size 31 | cdef void *_buffer 32 | -------------------------------------------------------------------------------- /av/subtitles/subtitle.pyi: -------------------------------------------------------------------------------- 1 | from typing import Iterator, Literal 2 | 3 | class SubtitleSet: 4 | format: int 5 | start_display_time: int 6 | end_display_time: int 7 | pts: int 8 | rects: tuple[Subtitle] 9 | 10 | def __len__(self) -> int: ... 11 | def __iter__(self) -> Iterator[Subtitle]: ... 12 | def __getitem__(self, i: int) -> Subtitle: ... 13 | 14 | class Subtitle: ... 15 | 16 | class BitmapSubtitle(Subtitle): 17 | type: Literal[b"bitmap"] 18 | x: int 19 | y: int 20 | width: int 21 | height: int 22 | nb_colors: int 23 | planes: tuple[BitmapSubtitlePlane, ...] 24 | 25 | class BitmapSubtitlePlane: 26 | subtitle: BitmapSubtitle 27 | index: int 28 | buffer_size: int 29 | 30 | class AssSubtitle(Subtitle): 31 | type: Literal[b"ass", b"text"] 32 | @property 33 | def ass(self) -> bytes: ... 34 | @property 35 | def dialogue(self) -> bytes: ... 36 | @property 37 | def text(self) -> bytes: ... 38 | -------------------------------------------------------------------------------- /av/utils.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | from libc.stdint cimport uint64_t 3 | 4 | 5 | cdef dict avdict_to_dict(lib.AVDictionary *input, str encoding, str errors) 6 | cdef dict_to_avdict(lib.AVDictionary **dst, dict src, str encoding, str errors) 7 | 8 | cdef object avrational_to_fraction(const lib.AVRational *input) 9 | cdef void to_avrational(object frac, lib.AVRational *input) 10 | 11 | cdef check_ndarray(object array, object dtype, int ndim) 12 | cdef flag_in_bitfield(uint64_t bitfield, uint64_t flag) 13 | -------------------------------------------------------------------------------- /av/utils.pyx: -------------------------------------------------------------------------------- 1 | from libc.stdint cimport uint64_t 2 | 3 | from fractions import Fraction 4 | 5 | cimport libav as lib 6 | 7 | from av.error cimport err_check 8 | 9 | # === DICTIONARIES === 10 | # ==================== 11 | 12 | cdef _decode(char *s, encoding, errors): 13 | return (s).decode(encoding, errors) 14 | 15 | cdef bytes _encode(s, encoding, errors): 16 | return s.encode(encoding, errors) 17 | 18 | cdef dict avdict_to_dict(lib.AVDictionary *input, str encoding, str errors): 19 | cdef lib.AVDictionaryEntry *element = NULL 20 | cdef dict output = {} 21 | while True: 22 | element = lib.av_dict_get(input, "", element, lib.AV_DICT_IGNORE_SUFFIX) 23 | if element == NULL: 24 | break 25 | output[_decode(element.key, encoding, errors)] = _decode(element.value, encoding, errors) 26 | return output 27 | 28 | 29 | cdef dict_to_avdict(lib.AVDictionary **dst, dict src, str encoding, str errors): 30 | lib.av_dict_free(dst) 31 | for key, value in src.items(): 32 | err_check( 33 | lib.av_dict_set( 34 | dst, 35 | _encode(key, encoding, errors), 36 | _encode(value, encoding, errors), 37 | 0 38 | ) 39 | ) 40 | 41 | 42 | # === FRACTIONS === 43 | # ================= 44 | 45 | cdef object avrational_to_fraction(const lib.AVRational *input): 46 | if input.num and input.den: 47 | return Fraction(input.num, input.den) 48 | 49 | 50 | cdef void to_avrational(object frac, lib.AVRational *input): 51 | input.num = frac.numerator 52 | input.den = frac.denominator 53 | 54 | 55 | # === OTHER === 56 | # ============= 57 | 58 | 59 | cdef check_ndarray(object array, object dtype, int ndim): 60 | """ 61 | Check a numpy array has the expected data type and number of dimensions. 62 | """ 63 | if array.dtype != dtype: 64 | raise ValueError(f"Expected numpy array with dtype `{dtype}` but got `{array.dtype}`") 65 | if array.ndim != ndim: 66 | raise ValueError(f"Expected numpy array with ndim `{ndim}` but got `{array.ndim}`") 67 | 68 | 69 | cdef flag_in_bitfield(uint64_t bitfield, uint64_t flag): 70 | # Not every flag exists in every version of FFMpeg, so we define them to 0. 71 | if not flag: 72 | return None 73 | return bool(bitfield & flag) 74 | 75 | 76 | # === BACKWARDS COMPAT === 77 | 78 | from .error import err_check 79 | -------------------------------------------------------------------------------- /av/video/__init__.pxd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyAV-Org/PyAV/633f237a6916d17b85fe937f95e28af651fbaf0e/av/video/__init__.pxd -------------------------------------------------------------------------------- /av/video/__init__.py: -------------------------------------------------------------------------------- 1 | from .frame import VideoFrame 2 | from .stream import VideoStream 3 | -------------------------------------------------------------------------------- /av/video/__init__.pyi: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from .frame import VideoFrame 4 | from .stream import VideoStream 5 | 6 | _VideoCodecName = Literal[ 7 | "gif", 8 | "h264", 9 | "hevc", 10 | "libvpx", 11 | "libx264", 12 | "mpeg4", 13 | "png", 14 | "qtrle", 15 | ] 16 | 17 | __all__ = ("VideoFrame", "VideoStream") 18 | -------------------------------------------------------------------------------- /av/video/codeccontext.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | from av.codec.context cimport CodecContext 4 | from av.video.format cimport VideoFormat 5 | from av.video.frame cimport VideoFrame 6 | from av.video.reformatter cimport VideoReformatter 7 | 8 | 9 | # The get_format callback in AVCodecContext is called by the decoder to pick a format out of a list. 10 | # When we want accelerated decoding, we need to figure out ahead of time what the format should be, 11 | # and find a way to pass that into our callback so we can return it to the decoder. We use the 'opaque' 12 | # user data field in AVCodecContext for that. This is the struct we store a pointer to in that field. 13 | cdef struct AVCodecPrivateData: 14 | lib.AVPixelFormat hardware_pix_fmt 15 | bint allow_software_fallback 16 | 17 | 18 | cdef class VideoCodecContext(CodecContext): 19 | 20 | cdef AVCodecPrivateData _private_data 21 | 22 | cdef VideoFormat _format 23 | cdef _build_format(self) 24 | 25 | cdef int last_w 26 | cdef int last_h 27 | cdef readonly VideoReformatter reformatter 28 | 29 | # For encoding. 30 | cdef readonly int encoded_frame_count 31 | 32 | # For decoding. 33 | cdef VideoFrame next_frame 34 | -------------------------------------------------------------------------------- /av/video/codeccontext.pyi: -------------------------------------------------------------------------------- 1 | from fractions import Fraction 2 | from typing import Iterator, Literal 3 | 4 | from av.codec.context import CodecContext 5 | from av.packet import Packet 6 | 7 | from .format import VideoFormat 8 | from .frame import VideoFrame 9 | 10 | class VideoCodecContext(CodecContext): 11 | format: VideoFormat | None 12 | width: int 13 | height: int 14 | bits_per_coded_sample: int 15 | pix_fmt: str | None 16 | framerate: Fraction 17 | rate: Fraction 18 | gop_size: int 19 | sample_aspect_ratio: Fraction | None 20 | display_aspect_ratio: Fraction | None 21 | has_b_frames: bool 22 | max_b_frames: int 23 | coded_width: int 24 | coded_height: int 25 | color_range: int 26 | color_primaries: int 27 | color_trc: int 28 | colorspace: int 29 | qmin: int 30 | qmax: int 31 | type: Literal["video"] 32 | 33 | def encode(self, frame: VideoFrame | None = None) -> list[Packet]: ... 34 | def encode_lazy(self, frame: VideoFrame | None = None) -> Iterator[Packet]: ... 35 | def decode(self, packet: Packet | None = None) -> list[VideoFrame]: ... 36 | -------------------------------------------------------------------------------- /av/video/format.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | 4 | cdef class VideoFormat: 5 | 6 | cdef lib.AVPixelFormat pix_fmt 7 | cdef const lib.AVPixFmtDescriptor *ptr 8 | cdef readonly unsigned int width, height 9 | 10 | cdef readonly tuple components 11 | 12 | cdef _init(self, lib.AVPixelFormat pix_fmt, unsigned int width, unsigned int height) 13 | 14 | cpdef chroma_width(self, int luma_width=?) 15 | cpdef chroma_height(self, int luma_height=?) 16 | 17 | 18 | cdef class VideoFormatComponent: 19 | 20 | cdef VideoFormat format 21 | cdef readonly unsigned int index 22 | cdef const lib.AVComponentDescriptor *ptr 23 | 24 | 25 | cdef VideoFormat get_video_format(lib.AVPixelFormat c_format, unsigned int width, unsigned int height) 26 | 27 | cdef lib.AVPixelFormat get_pix_fmt(const char *name) except lib.AV_PIX_FMT_NONE 28 | -------------------------------------------------------------------------------- /av/video/format.pyi: -------------------------------------------------------------------------------- 1 | class VideoFormat: 2 | name: str 3 | bits_per_pixel: int 4 | padded_bits_per_pixel: int 5 | is_big_endian: bool 6 | has_palette: bool 7 | is_bit_stream: bool 8 | is_planar: bool 9 | @property 10 | def is_rgb(self) -> bool: ... 11 | @property 12 | def is_bayer(self) -> bool: ... 13 | width: int 14 | height: int 15 | components: tuple[VideoFormatComponent, ...] 16 | 17 | def __init__(self, name: str, width: int = 0, height: int = 0) -> None: ... 18 | def chroma_width(self, luma_width: int = 0) -> int: ... 19 | def chroma_height(self, luma_height: int = 0) -> int: ... 20 | 21 | class VideoFormatComponent: 22 | plane: int 23 | bits: int 24 | is_alpha: bool 25 | is_luma: bool 26 | is_chroma: bool 27 | width: int 28 | height: int 29 | 30 | def __init__(self, format: VideoFormat, index: int) -> None: ... 31 | -------------------------------------------------------------------------------- /av/video/frame.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | from libc.stdint cimport uint8_t 3 | 4 | from av.frame cimport Frame 5 | from av.video.format cimport VideoFormat 6 | from av.video.reformatter cimport VideoReformatter 7 | 8 | 9 | cdef class VideoFrame(Frame): 10 | # This is the buffer that is used to back everything in the AVFrame. 11 | # We don't ever actually access it directly. 12 | cdef uint8_t *_buffer 13 | cdef object _np_buffer 14 | 15 | cdef VideoReformatter reformatter 16 | cdef readonly VideoFormat format 17 | 18 | cdef _init(self, lib.AVPixelFormat format, unsigned int width, unsigned int height) 19 | cdef _init_user_attributes(self) 20 | 21 | cdef VideoFrame alloc_video_frame() 22 | -------------------------------------------------------------------------------- /av/video/frame.pyi: -------------------------------------------------------------------------------- 1 | from enum import IntEnum 2 | from typing import Any, ClassVar, Union 3 | 4 | import numpy as np 5 | from PIL import Image 6 | 7 | from av.frame import Frame 8 | 9 | from .format import VideoFormat 10 | from .plane import VideoPlane 11 | 12 | _SupportedNDarray = Union[ 13 | np.ndarray[Any, np.dtype[np.uint8]], 14 | np.ndarray[Any, np.dtype[np.uint16]], 15 | np.ndarray[Any, np.dtype[np.float32]], 16 | ] 17 | 18 | supported_np_pix_fmts: set[str] 19 | 20 | class PictureType(IntEnum): 21 | NONE = 0 22 | I = 1 23 | P = 2 24 | B = 3 25 | S = 4 26 | SI = 5 27 | SP = 6 28 | BI = 7 29 | 30 | class VideoFrame(Frame): 31 | format: VideoFormat 32 | pts: int 33 | duration: int 34 | planes: tuple[VideoPlane, ...] 35 | pict_type: int 36 | colorspace: int 37 | color_range: int 38 | 39 | @property 40 | def time(self) -> float: ... 41 | @property 42 | def width(self) -> int: ... 43 | @property 44 | def height(self) -> int: ... 45 | @property 46 | def interlaced_frame(self) -> bool: ... 47 | @property 48 | def rotation(self) -> int: ... 49 | def __init__( 50 | self, width: int = 0, height: int = 0, format: str = "yuv420p" 51 | ) -> None: ... 52 | def reformat( 53 | self, 54 | width: int | None = None, 55 | height: int | None = None, 56 | format: str | None = None, 57 | src_colorspace: str | int | None = None, 58 | dst_colorspace: str | int | None = None, 59 | interpolation: int | str | None = None, 60 | src_color_range: int | str | None = None, 61 | dst_color_range: int | str | None = None, 62 | ) -> VideoFrame: ... 63 | def to_rgb(self, **kwargs: Any) -> VideoFrame: ... 64 | def to_image(self, **kwargs: Any) -> Image.Image: ... 65 | def to_ndarray( 66 | self, channel_last: bool = False, **kwargs: Any 67 | ) -> _SupportedNDarray: ... 68 | @staticmethod 69 | def from_image(img: Image.Image) -> VideoFrame: ... 70 | @staticmethod 71 | def from_numpy_buffer( 72 | array: _SupportedNDarray, format: str = "rgb24", width: int = 0 73 | ) -> VideoFrame: ... 74 | @staticmethod 75 | def from_ndarray( 76 | array: _SupportedNDarray, format: str = "rgb24", channel_last: bool = False 77 | ) -> VideoFrame: ... 78 | @staticmethod 79 | def from_bytes( 80 | data: bytes, 81 | width: int, 82 | height: int, 83 | format: str = "rgba", 84 | flip_horizontal: bool = False, 85 | flip_vertical: bool = False, 86 | ) -> VideoFrame: ... 87 | -------------------------------------------------------------------------------- /av/video/plane.pxd: -------------------------------------------------------------------------------- 1 | from av.plane cimport Plane 2 | from av.video.format cimport VideoFormatComponent 3 | 4 | 5 | cdef class VideoPlane(Plane): 6 | 7 | cdef readonly size_t buffer_size 8 | cdef readonly unsigned int width, height 9 | -------------------------------------------------------------------------------- /av/video/plane.pyi: -------------------------------------------------------------------------------- 1 | from av.plane import Plane 2 | 3 | from .frame import VideoFrame 4 | 5 | class VideoPlane(Plane): 6 | line_size: int 7 | width: int 8 | height: int 9 | buffer_size: int 10 | 11 | def __init__(self, frame: VideoFrame, index: int) -> None: ... 12 | -------------------------------------------------------------------------------- /av/video/plane.pyx: -------------------------------------------------------------------------------- 1 | from av.video.frame cimport VideoFrame 2 | 3 | 4 | cdef class VideoPlane(Plane): 5 | def __cinit__(self, VideoFrame frame, int index): 6 | # The palette plane has no associated component or linesize; set fields manually 7 | if frame.format.name == "pal8" and index == 1: 8 | self.width = 256 9 | self.height = 1 10 | self.buffer_size = 256 * 4 11 | return 12 | 13 | for i in range(frame.format.ptr.nb_components): 14 | if frame.format.ptr.comp[i].plane == index: 15 | component = frame.format.components[i] 16 | self.width = component.width 17 | self.height = component.height 18 | break 19 | else: 20 | raise RuntimeError(f"could not find plane {index} of {frame.format!r}") 21 | 22 | # Sometimes, linesize is negative (and that is meaningful). We are only 23 | # insisting that the buffer size be based on the extent of linesize, and 24 | # ignore it's direction. 25 | self.buffer_size = abs(self.frame.ptr.linesize[self.index]) * self.height 26 | 27 | cdef size_t _buffer_size(self): 28 | return self.buffer_size 29 | 30 | @property 31 | def line_size(self): 32 | """ 33 | Bytes per horizontal line in this plane. 34 | 35 | :type: int 36 | """ 37 | return self.frame.ptr.linesize[self.index] 38 | -------------------------------------------------------------------------------- /av/video/reformatter.pxd: -------------------------------------------------------------------------------- 1 | cimport libav as lib 2 | 3 | from av.video.frame cimport VideoFrame 4 | 5 | 6 | cdef class VideoReformatter: 7 | 8 | cdef lib.SwsContext *ptr 9 | 10 | cdef _reformat(self, VideoFrame frame, int width, int height, 11 | lib.AVPixelFormat format, int src_colorspace, 12 | int dst_colorspace, int interpolation, 13 | int src_color_range, int dst_color_range) 14 | -------------------------------------------------------------------------------- /av/video/reformatter.pyi: -------------------------------------------------------------------------------- 1 | from enum import IntEnum 2 | from typing import cast 3 | 4 | from .frame import VideoFrame 5 | 6 | class Interpolation(IntEnum): 7 | FAST_BILINEAER = cast(int, ...) 8 | BILINEAR = cast(int, ...) 9 | BICUBIC = cast(int, ...) 10 | X = cast(int, ...) 11 | POINT = cast(int, ...) 12 | AREA = cast(int, ...) 13 | BICUBLIN = cast(int, ...) 14 | GAUSS = cast(int, ...) 15 | SINC = cast(int, ...) 16 | LANCZOS = cast(int, ...) 17 | SPLINE = cast(int, ...) 18 | 19 | class Colorspace(IntEnum): 20 | ITU709 = cast(int, ...) 21 | FCC = cast(int, ...) 22 | ITU601 = cast(int, ...) 23 | ITU624 = cast(int, ...) 24 | SMPTE170M = cast(int, ...) 25 | SMPTE240M = cast(int, ...) 26 | DEFAULT = cast(int, ...) 27 | itu709 = cast(int, ...) 28 | fcc = cast(int, ...) 29 | itu601 = cast(int, ...) 30 | itu624 = cast(int, ...) 31 | smpte170m = cast(int, ...) 32 | smpte240m = cast(int, ...) 33 | default = cast(int, ...) 34 | 35 | class ColorRange(IntEnum): 36 | UNSPECIFIED = 0 37 | MPEG = 1 38 | JPEG = 2 39 | NB = 3 40 | 41 | class VideoReformatter: 42 | def reformat( 43 | self, 44 | frame: VideoFrame, 45 | width: int | None = None, 46 | height: int | None = None, 47 | format: str | None = None, 48 | src_colorspace: int | None = None, 49 | dst_colorspace: int | None = None, 50 | interpolation: int | str | None = None, 51 | src_color_range: int | str | None = None, 52 | dst_color_range: int | str | None = None, 53 | ) -> VideoFrame: ... 54 | -------------------------------------------------------------------------------- /av/video/stream.pxd: -------------------------------------------------------------------------------- 1 | from av.packet cimport Packet 2 | from av.stream cimport Stream 3 | 4 | from .frame cimport VideoFrame 5 | 6 | 7 | cdef class VideoStream(Stream): 8 | cpdef encode(self, VideoFrame frame=?) 9 | cpdef decode(self, Packet packet=?) 10 | -------------------------------------------------------------------------------- /av/video/stream.pyi: -------------------------------------------------------------------------------- 1 | from fractions import Fraction 2 | from typing import Iterator, Literal 3 | 4 | from av.codec.context import ThreadType 5 | from av.packet import Packet 6 | from av.stream import Stream 7 | 8 | from .codeccontext import VideoCodecContext 9 | from .format import VideoFormat 10 | from .frame import VideoFrame 11 | 12 | class VideoStream(Stream): 13 | bit_rate: int | None 14 | max_bit_rate: int | None 15 | bit_rate_tolerance: int 16 | sample_aspect_ratio: Fraction | None 17 | display_aspect_ratio: Fraction | None 18 | codec_context: VideoCodecContext 19 | 20 | def encode(self, frame: VideoFrame | None = None) -> list[Packet]: ... 21 | def encode_lazy(self, frame: VideoFrame | None = None) -> Iterator[Packet]: ... 22 | def decode(self, packet: Packet | None = None) -> list[VideoFrame]: ... 23 | 24 | # from codec context 25 | format: VideoFormat 26 | thread_count: int 27 | thread_type: ThreadType 28 | width: int 29 | height: int 30 | bits_per_coded_sample: int 31 | pix_fmt: str | None 32 | framerate: Fraction 33 | rate: Fraction 34 | gop_size: int 35 | has_b_frames: bool 36 | max_b_frames: int 37 | coded_width: int 38 | coded_height: int 39 | color_range: int 40 | color_primaries: int 41 | color_trc: int 42 | colorspace: int 43 | type: Literal["video"] 44 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | SPHINXOPTS = 2 | BUILDDIR = _build 3 | PYAV_PIP ?= pip 4 | PIP := $(PYAV_PIP) 5 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(SPHINXOPTS) . 6 | 7 | .PHONY: clean html open upload default 8 | 9 | default: html 10 | 11 | TEMPLATES := $(wildcard api/*.py development/*.py) 12 | RENDERED := $(TEMPLATES:%.py=_build/rst/%.rst) 13 | 14 | _build/rst/%.rst: %.py $(TAGFILE) $(shell find ../include ../av -name '*.pyx' -or -name '*.pxd') 15 | @ mkdir -p $(@D) 16 | python $< > $@.tmp 17 | mv $@.tmp $@ 18 | 19 | html: $(RENDERED) 20 | $(PIP) install -U sphinx sphinx-copybutton 21 | rm -rf $(BUILDDIR) 22 | sphinx-build -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 23 | 24 | test: 25 | PYAV_SKIP_DOXYLINK=1 sphinx-build -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 26 | 27 | open: 28 | open _build/html/index.html 29 | 30 | upload: 31 | rsync -avxP --delete _build/html/ root@basswood-io.com:/var/www/pyav/docs/develop 32 | 33 | -------------------------------------------------------------------------------- /docs/_static/custom.css: -------------------------------------------------------------------------------- 1 | body { 2 | text-rendering: optimizeLegibility; 3 | } 4 | 5 | .sphinxsidebarwrapper { 6 | overflow: scroll; 7 | } 8 | 9 | .ffmpeg-quicklink { 10 | float: right; 11 | clear: right; 12 | margin: 0; 13 | } 14 | 15 | .ffmpeg-quicklink:before { 16 | content: "["; 17 | } 18 | 19 | .ffmpeg-quicklink:after { 20 | content: "]"; 21 | } 22 | 23 | .body a, .document a { 24 | text-decoration: underline !important; 25 | } 26 | -------------------------------------------------------------------------------- /docs/_static/examples/numpy/barcode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyAV-Org/PyAV/633f237a6916d17b85fe937f95e28af651fbaf0e/docs/_static/examples/numpy/barcode.jpg -------------------------------------------------------------------------------- /docs/_static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyAV-Org/PyAV/633f237a6916d17b85fe937f95e28af651fbaf0e/docs/_static/favicon.png -------------------------------------------------------------------------------- /docs/_static/logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyAV-Org/PyAV/633f237a6916d17b85fe937f95e28af651fbaf0e/docs/_static/logo.webp -------------------------------------------------------------------------------- /docs/_themes/pyav/layout.html: -------------------------------------------------------------------------------- 1 | 2 | {%- extends "basic/layout.html" %} 3 | 4 | {% block extrahead %} 5 | 6 | 7 | {% endblock %} 8 | 9 | {% block relbaritems %} 10 |
  • 11 | {% endblock %} 12 | 13 | -------------------------------------------------------------------------------- /docs/_themes/pyav/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = nature 3 | -------------------------------------------------------------------------------- /docs/api/_globals.rst: -------------------------------------------------------------------------------- 1 | 2 | Globals 3 | ======= 4 | 5 | .. autofunction:: av.open 6 | -------------------------------------------------------------------------------- /docs/api/attachments.rst: -------------------------------------------------------------------------------- 1 | 2 | Attachments 3 | =========== 4 | 5 | .. automodule:: av.attachments.stream 6 | 7 | .. autoclass:: AttachmentStream 8 | :members: 9 | -------------------------------------------------------------------------------- /docs/api/audio.rst: -------------------------------------------------------------------------------- 1 | 2 | Audio 3 | ===== 4 | 5 | Audio Streams 6 | ------------- 7 | 8 | .. automodule:: av.audio.stream 9 | 10 | .. autoclass:: AudioStream 11 | :members: 12 | 13 | Audio Context 14 | ------------- 15 | 16 | .. automodule:: av.audio.codeccontext 17 | 18 | .. autoclass:: AudioCodecContext 19 | :members: 20 | :exclude-members: channel_layout, channels 21 | 22 | Audio Formats 23 | ------------- 24 | 25 | .. automodule:: av.audio.format 26 | 27 | .. autoclass:: AudioFormat 28 | :members: 29 | 30 | Audio Layouts 31 | ------------- 32 | 33 | .. automodule:: av.audio.layout 34 | 35 | .. autoclass:: AudioLayout 36 | :members: 37 | 38 | .. autoclass:: AudioChannel 39 | :members: 40 | 41 | Audio Frames 42 | ------------ 43 | 44 | .. automodule:: av.audio.frame 45 | 46 | .. autoclass:: AudioFrame 47 | :members: 48 | :exclude-members: to_nd_array 49 | 50 | Audio FIFOs 51 | ----------- 52 | 53 | .. automodule:: av.audio.fifo 54 | 55 | .. autoclass:: AudioFifo 56 | :members: 57 | :exclude-members: write, read, read_many 58 | 59 | .. automethod:: write 60 | .. automethod:: read 61 | .. automethod:: read_many 62 | 63 | Audio Resamplers 64 | ---------------- 65 | 66 | .. automodule:: av.audio.resampler 67 | 68 | .. autoclass:: AudioResampler 69 | :members: 70 | :exclude-members: resample 71 | 72 | .. automethod:: resample 73 | -------------------------------------------------------------------------------- /docs/api/bitstream.rst: -------------------------------------------------------------------------------- 1 | 2 | Bitstream Filters 3 | ================= 4 | 5 | .. automodule:: av.bitstream 6 | 7 | .. autoclass:: BitStreamFilterContext 8 | :members: 9 | 10 | -------------------------------------------------------------------------------- /docs/api/buffer.rst: -------------------------------------------------------------------------------- 1 | 2 | Buffers 3 | ======= 4 | 5 | .. automodule:: av.buffer 6 | 7 | .. autoclass:: Buffer 8 | :members: 9 | -------------------------------------------------------------------------------- /docs/api/container.rst: -------------------------------------------------------------------------------- 1 | 2 | Containers 3 | ========== 4 | 5 | 6 | Generic 7 | ------- 8 | 9 | .. currentmodule:: av.container 10 | 11 | .. automodule:: av.container 12 | 13 | .. autoclass:: Container 14 | 15 | .. attribute:: options 16 | .. attribute:: container_options 17 | .. attribute:: stream_options 18 | .. attribute:: metadata_encoding 19 | .. attribute:: metadata_errors 20 | .. attribute:: open_timeout 21 | .. attribute:: read_timeout 22 | 23 | 24 | Flags 25 | ~~~~~ 26 | 27 | .. attribute:: av.container.Container.flags 28 | 29 | .. class:: av.container.Flags 30 | 31 | Wraps :ffmpeg:`AVFormatContext.flags`. 32 | 33 | .. enumtable:: av.container.core:Flags 34 | :class: av.container.core:Container 35 | 36 | 37 | Input Containers 38 | ---------------- 39 | 40 | .. autoclass:: InputContainer 41 | :members: 42 | 43 | 44 | Output Containers 45 | ----------------- 46 | 47 | .. autoclass:: OutputContainer 48 | :members: 49 | 50 | 51 | Formats 52 | ------- 53 | 54 | .. currentmodule:: av.format 55 | 56 | .. automodule:: av.format 57 | 58 | .. autoclass:: ContainerFormat 59 | 60 | .. autoattribute:: ContainerFormat.name 61 | .. autoattribute:: ContainerFormat.long_name 62 | 63 | .. autoattribute:: ContainerFormat.options 64 | .. autoattribute:: ContainerFormat.input 65 | .. autoattribute:: ContainerFormat.output 66 | .. autoattribute:: ContainerFormat.is_input 67 | .. autoattribute:: ContainerFormat.is_output 68 | .. autoattribute:: ContainerFormat.extensions 69 | 70 | Flags 71 | ~~~~~ 72 | 73 | .. autoattribute:: ContainerFormat.flags 74 | 75 | .. autoclass:: av.format.Flags 76 | 77 | .. enumtable:: av.format.Flags 78 | :class: av.format.ContainerFormat 79 | 80 | -------------------------------------------------------------------------------- /docs/api/error.rst: -------------------------------------------------------------------------------- 1 | Errors 2 | ====== 3 | 4 | .. currentmodule:: av.error 5 | 6 | .. _error_behaviour: 7 | 8 | General Behavior 9 | ----------------- 10 | 11 | When PyAV encounters an FFmpeg error, it raises an appropriate exception. 12 | 13 | FFmpeg has a couple dozen of its own error types which we represent via 14 | :ref:`error_classes`. 15 | 16 | FFmpeg will also return more typical errors such as ``ENOENT`` or ``EAGAIN``, 17 | which we do our best to translate to extensions of the builtin exceptions 18 | as defined by 19 | `PEP 3151 `_. 20 | 21 | 22 | .. _error_classes: 23 | 24 | Error Exception Classes 25 | ----------------------- 26 | 27 | PyAV raises the typical builtin exceptions within its own codebase, but things 28 | get a little more complex when it comes to translating FFmpeg errors. 29 | 30 | There are two competing ideas that have influenced the final design: 31 | 32 | 1. We want every exception that originates within FFmpeg to inherit from a common 33 | :class:`.FFmpegError` exception; 34 | 35 | 2. We want to use the builtin exceptions whenever possible. 36 | 37 | As such, PyAV effectively shadows as much of the builtin exception hierarchy as 38 | it requires, extending from both the builtins and from :class:`FFmpegError`. 39 | 40 | Therefore, an argument error within FFmpeg will raise a ``av.error.ValueError``, which 41 | can be caught via either :class:`FFmpegError` or ``ValueError``. All of these 42 | exceptions expose the typical ``errno`` and ``strerror`` attributes (even 43 | ``ValueError`` which doesn't typically), as well as some PyAV extensions such 44 | as :attr:`FFmpegError.log`. 45 | 46 | All of these exceptions are available on the top-level ``av`` package, e.g.:: 47 | 48 | try: 49 | do_something() 50 | except av.FilterNotFoundError: 51 | handle_error() 52 | 53 | 54 | .. autoclass:: av.FFmpegError 55 | 56 | -------------------------------------------------------------------------------- /docs/api/filter.rst: -------------------------------------------------------------------------------- 1 | Filters 2 | ======= 3 | 4 | .. automodule:: av.filter.filter 5 | 6 | .. autoclass:: Filter 7 | :members: 8 | 9 | 10 | .. automodule:: av.filter.graph 11 | 12 | .. autoclass:: Graph 13 | :members: 14 | 15 | 16 | .. automodule:: av.filter.context 17 | 18 | .. autoclass:: FilterContext 19 | :members: 20 | 21 | 22 | .. automodule:: av.filter.link 23 | 24 | .. autoclass:: FilterLink 25 | :members: 26 | 27 | 28 | .. automodule:: av.filter.pad 29 | 30 | .. autoclass:: FilterPad 31 | :members: 32 | 33 | .. autoclass:: FilterContextPad 34 | :members: 35 | -------------------------------------------------------------------------------- /docs/api/frame.rst: -------------------------------------------------------------------------------- 1 | 2 | Frames 3 | ====== 4 | 5 | .. automodule:: av.frame 6 | 7 | .. autoclass:: Frame 8 | :members: 9 | -------------------------------------------------------------------------------- /docs/api/packet.rst: -------------------------------------------------------------------------------- 1 | 2 | Packets 3 | ======= 4 | 5 | .. automodule:: av.packet 6 | 7 | .. autoclass:: Packet 8 | :members: 9 | -------------------------------------------------------------------------------- /docs/api/plane.rst: -------------------------------------------------------------------------------- 1 | 2 | Planes 3 | ====== 4 | 5 | .. automodule:: av.plane 6 | 7 | .. autoclass:: Plane 8 | :members: 9 | -------------------------------------------------------------------------------- /docs/api/sidedata.rst: -------------------------------------------------------------------------------- 1 | 2 | Side Data 3 | ========= 4 | 5 | .. automodule:: av.sidedata.sidedata 6 | 7 | .. autoclass:: SideData 8 | :members: 9 | 10 | .. autoclass:: av.sidedata.sidedata.Type 11 | .. enumtable:: av.sidedata.sidedata.Type 12 | 13 | 14 | Motion Vectors 15 | -------------- 16 | 17 | .. automodule:: av.sidedata.motionvectors 18 | 19 | .. autoclass:: MotionVectors 20 | :members: 21 | -------------------------------------------------------------------------------- /docs/api/stream.rst: -------------------------------------------------------------------------------- 1 | 2 | Streams 3 | ======= 4 | 5 | 6 | Stream collections 7 | ------------------ 8 | 9 | .. currentmodule:: av.container.streams 10 | 11 | .. autoclass:: StreamContainer 12 | 13 | 14 | Dynamic Slicing 15 | ~~~~~~~~~~~~~~~ 16 | 17 | .. automethod:: StreamContainer.get 18 | 19 | .. automethod:: StreamContainer.best 20 | 21 | 22 | Typed Collections 23 | ~~~~~~~~~~~~~~~~~ 24 | 25 | These attributes are preferred for readability if you don't need the 26 | dynamic capabilities of :meth:`.get`: 27 | 28 | .. attribute:: StreamContainer.video 29 | 30 | A tuple of :class:`VideoStream`. 31 | 32 | .. attribute:: StreamContainer.audio 33 | 34 | A tuple of :class:`AudioStream`. 35 | 36 | .. attribute:: StreamContainer.subtitles 37 | 38 | A tuple of :class:`SubtitleStream`. 39 | 40 | .. attribute:: StreamContainer.attachments 41 | 42 | A tuple of :class:`AttachmentStream`. 43 | 44 | .. attribute:: StreamContainer.data 45 | 46 | A tuple of :class:`DataStream`. 47 | 48 | .. attribute:: StreamContainer.other 49 | 50 | A tuple of :class:`Stream` 51 | 52 | 53 | Streams 54 | ------- 55 | 56 | .. currentmodule:: av.stream 57 | 58 | .. autoclass:: Stream 59 | 60 | 61 | Basics 62 | ~~~~~~ 63 | 64 | 65 | .. autoattribute:: Stream.type 66 | 67 | .. autoattribute:: Stream.codec_context 68 | 69 | .. autoattribute:: Stream.id 70 | 71 | .. autoattribute:: Stream.index 72 | 73 | 74 | Timing 75 | ~~~~~~ 76 | 77 | .. seealso:: :ref:`time` for a discussion of time in general. 78 | 79 | .. autoattribute:: Stream.time_base 80 | 81 | .. autoattribute:: Stream.start_time 82 | 83 | .. autoattribute:: Stream.duration 84 | 85 | .. autoattribute:: Stream.frames 86 | 87 | 88 | Others 89 | ~~~~~~ 90 | 91 | .. autoattribute:: Stream.profile 92 | 93 | .. autoattribute:: Stream.language 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /docs/api/subtitles.rst: -------------------------------------------------------------------------------- 1 | 2 | Subtitles 3 | =========== 4 | 5 | .. automodule:: av.subtitles.stream 6 | 7 | .. autoclass:: SubtitleStream 8 | :members: 9 | 10 | .. automodule:: av.subtitles.subtitle 11 | 12 | .. autoclass:: SubtitleSet 13 | :members: 14 | 15 | .. autoclass:: Subtitle 16 | :members: 17 | 18 | .. autoclass:: AssSubtitle 19 | :members: 20 | 21 | .. autoclass:: BitmapSubtitle 22 | :members: 23 | 24 | .. autoclass:: BitmapSubtitlePlane 25 | :members: 26 | -------------------------------------------------------------------------------- /docs/api/utils.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | Utilities 4 | ========= 5 | 6 | Logging 7 | ------- 8 | 9 | .. automodule:: av.logging 10 | :members: 11 | 12 | Other 13 | ----- 14 | 15 | .. automodule:: av.utils 16 | :members: 17 | -------------------------------------------------------------------------------- /docs/api/video.rst: -------------------------------------------------------------------------------- 1 | Video 2 | ===== 3 | 4 | Video Streams 5 | ------------- 6 | 7 | .. automodule:: av.video.stream 8 | 9 | .. autoclass:: VideoStream 10 | :members: 11 | 12 | Video Codecs 13 | ------------- 14 | 15 | .. automodule:: av.video.codeccontext 16 | 17 | .. autoclass:: VideoCodecContext 18 | :members: 19 | 20 | Video Formats 21 | ------------- 22 | 23 | .. automodule:: av.video.format 24 | 25 | .. autoclass:: VideoFormat 26 | :members: 27 | 28 | .. autoclass:: VideoFormatComponent 29 | :members: 30 | 31 | Video Frames 32 | ------------ 33 | 34 | .. automodule:: av.video.frame 35 | 36 | .. autoclass:: VideoFrame 37 | 38 | A single video frame. 39 | 40 | :param int width: The width of the frame. 41 | :param int height: The height of the frame. 42 | :param format: The format of the frame. 43 | :type format: :class:`VideoFormat` or ``str``. 44 | 45 | >>> frame = VideoFrame(1920, 1080, 'rgb24') 46 | 47 | Structural 48 | ~~~~~~~~~~ 49 | 50 | .. autoattribute:: VideoFrame.width 51 | .. autoattribute:: VideoFrame.height 52 | .. attribute:: VideoFrame.format 53 | 54 | The :class:`.VideoFormat` of the frame. 55 | 56 | .. autoattribute:: VideoFrame.planes 57 | 58 | Types 59 | ~~~~~ 60 | 61 | .. autoattribute:: VideoFrame.key_frame 62 | .. autoattribute:: VideoFrame.interlaced_frame 63 | .. autoattribute:: VideoFrame.pict_type 64 | 65 | .. autoclass:: av.video.frame.PictureType 66 | 67 | Wraps ``AVPictureType`` (``AV_PICTURE_TYPE_*``). 68 | 69 | .. enumtable:: av.video.frame.PictureType 70 | 71 | 72 | Conversions 73 | ~~~~~~~~~~~ 74 | 75 | .. automethod:: VideoFrame.reformat 76 | 77 | .. automethod:: VideoFrame.to_rgb 78 | .. automethod:: VideoFrame.to_image 79 | .. automethod:: VideoFrame.to_ndarray 80 | 81 | .. automethod:: VideoFrame.from_image 82 | .. automethod:: VideoFrame.from_ndarray 83 | 84 | 85 | 86 | Video Planes 87 | ------------- 88 | 89 | .. automodule:: av.video.plane 90 | 91 | .. autoclass:: VideoPlane 92 | :members: 93 | 94 | 95 | Video Reformatters 96 | ------------------ 97 | 98 | .. automodule:: av.video.reformatter 99 | 100 | .. autoclass:: VideoReformatter 101 | 102 | .. automethod:: reformat 103 | 104 | Enums 105 | ~~~~~ 106 | 107 | .. autoclass:: av.video.reformatter.Interpolation 108 | 109 | Wraps the ``SWS_*`` flags. 110 | 111 | .. enumtable:: av.video.reformatter.Interpolation 112 | 113 | .. autoclass:: av.video.reformatter.Colorspace 114 | 115 | Wraps the ``SWS_CS_*`` flags. There is a bit of overlap in 116 | these names which comes from FFmpeg and backards compatibility. 117 | 118 | .. enumtable:: av.video.reformatter.Colorspace 119 | 120 | .. autoclass:: av.video.reformatter.ColorRange 121 | 122 | Wraps the ``AVCOL*`` flags. 123 | 124 | .. enumtable:: av.video.reformatter.ColorRange 125 | 126 | -------------------------------------------------------------------------------- /docs/cookbook/audio.rst: -------------------------------------------------------------------------------- 1 | Audio 2 | ===== 3 | 4 | 5 | Filters 6 | ------- 7 | 8 | Increase the audio speed by applying the atempo filter. The speed is increased by 2. 9 | 10 | .. literalinclude:: ../../examples/audio/atempo.py 11 | -------------------------------------------------------------------------------- /docs/cookbook/basics.rst: -------------------------------------------------------------------------------- 1 | Basics 2 | ====== 3 | 4 | Here are some common things to do without digging too deep into the mechanics. 5 | 6 | 7 | Saving Keyframes 8 | ---------------- 9 | 10 | If you just want to look at keyframes, you can set :attr:`.CodecContext.skip_frame` to speed up the process: 11 | 12 | .. literalinclude:: ../../examples/basics/save_keyframes.py 13 | 14 | 15 | Remuxing 16 | -------- 17 | 18 | Remuxing is copying audio/video data from one container to the other without transcoding. By doing so, the data does not suffer any generational loss, and is the full quality that it was in the source container. 19 | 20 | .. literalinclude:: ../../examples/basics/remux.py 21 | 22 | 23 | Parsing 24 | ------- 25 | 26 | Sometimes we have a raw stream of data, and we need to split it into packets before working with it. We can use :meth:`.CodecContext.parse` to do this. 27 | 28 | .. literalinclude:: ../../examples/basics/parse.py 29 | 30 | 31 | Threading 32 | --------- 33 | 34 | By default, codec contexts will decode with :data:`~av.codec.context.ThreadType.SLICE` threading. This allows multiple threads to cooperate to decode any given frame. 35 | 36 | This is faster than no threading, but is not as fast as we can go. 37 | 38 | Also enabling :data:`~av.codec.context.ThreadType.FRAME` (or :data:`~av.codec.context.ThreadType.AUTO`) threading allows multiple threads to decode independent frames. This is not enabled by default because it does change the API a bit: you will get a much larger "delay" between starting the decode of a packet and getting it's results. Take a look at the output of this sample to see what we mean: 39 | 40 | .. literalinclude:: ../../examples/basics/thread_type.py 41 | 42 | On the author's machine, the second pass decodes ~5 times faster. 43 | 44 | 45 | Recording the Screen 46 | -------------------- 47 | 48 | .. literalinclude:: ../../examples/basics/record_screen.py 49 | 50 | 51 | Recording a Facecam 52 | ------------------- 53 | 54 | .. literalinclude:: ../../examples/basics/record_facecam.py 55 | 56 | -------------------------------------------------------------------------------- /docs/cookbook/numpy.rst: -------------------------------------------------------------------------------- 1 | Numpy 2 | ===== 3 | 4 | 5 | Video Barcode 6 | ------------- 7 | 8 | A video barcode shows the change in colour and tone over time. Time is represented on the horizontal axis, while the vertical remains the vertical direction in the image. 9 | 10 | See https://moviebarcode.tumblr.com/ for examples from Hollywood movies, and here is an example from a sunset timelapse: 11 | 12 | .. image:: ../_static/examples/numpy/barcode.jpg 13 | 14 | The code that created this: 15 | 16 | .. literalinclude:: ../../examples/numpy/barcode.py 17 | 18 | 19 | Generating Video 20 | ---------------- 21 | 22 | .. literalinclude:: ../../examples/numpy/generate_video.py 23 | 24 | 25 | -------------------------------------------------------------------------------- /docs/cookbook/subtitles.rst: -------------------------------------------------------------------------------- 1 | Subtitles 2 | ========= 3 | 4 | 5 | Remuxing 6 | -------- 7 | 8 | Remuxing is copying stream(s) from one container to the other without transcoding it. By doing so, the data does not suffer any generational loss, and is the full quality that it was in the source container. 9 | 10 | .. literalinclude:: ../../examples/subtitles/remux.py 11 | -------------------------------------------------------------------------------- /docs/development/changelog.rst: -------------------------------------------------------------------------------- 1 | 2 | .. It is all in the other file (that we want at the top-level of the repo). 3 | 4 | .. _changelog: 5 | 6 | .. include:: ../../CHANGELOG.rst 7 | -------------------------------------------------------------------------------- /docs/development/contributors.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | .. include:: ../../AUTHORS.rst 4 | -------------------------------------------------------------------------------- /docs/development/license.rst: -------------------------------------------------------------------------------- 1 | 2 | .. It is all in the other file (that we want at the top-level of the repo). 3 | 4 | .. _license: 5 | 6 | License 7 | ======= 8 | 9 | From `LICENSE.txt `_: 10 | 11 | .. literalinclude:: ../../LICENSE.txt 12 | :language: text 13 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | **PyAV** Documentation 2 | ====================== 3 | 4 | **PyAV** is a Pythonic binding for FFmpeg_. We aim to provide all of the power and control of the underlying library, but manage the gritty details as much as possible. 5 | 6 | PyAV is for direct and precise access to your media via containers, streams, packets, codecs, and frames. It exposes a few transformations of that data, and helps you get your data to/from other packages (e.g. Numpy and Pillow). 7 | 8 | This power does come with some responsibility as working with media is horrendously complicated and PyAV can't abstract it away or make all the best decisions for you. If the ``ffmpeg`` command does the job without you bending over backwards, PyAV is likely going to be more of a hindrance than a help. 9 | 10 | But where you can't work without it, PyAV is a critical tool. 11 | 12 | Currently we provide: 13 | 14 | - ``libavformat``: 15 | :class:`containers <.Container>`, 16 | audio/video/subtitle :class:`streams <.Stream>`, 17 | :class:`packets <.Packet>`; 18 | 19 | - ``libavdevice`` (by specifying a format to containers); 20 | 21 | - ``libavcodec``: 22 | :class:`.Codec`, 23 | :class:`.CodecContext`, 24 | :class:`.BitStreamFilterContext`, 25 | audio/video :class:`frames <.Frame>`, 26 | :class:`data planes <.Plane>`, 27 | :class:`subtitles <.Subtitle>`; 28 | 29 | - ``libavfilter``: 30 | :class:`.Filter`, 31 | :class:`.Graph`; 32 | 33 | - ``libswscale``: 34 | :class:`.VideoReformatter`; 35 | 36 | - ``libswresample``: 37 | :class:`.AudioResampler`; 38 | 39 | - and a few more utilities. 40 | 41 | .. _FFmpeg: https://ffmpeg.org/ 42 | 43 | 44 | Basic Demo 45 | ---------- 46 | 47 | .. testsetup:: 48 | 49 | path_to_video = common.fate_png() # We don't need a full QT here. 50 | 51 | 52 | .. testcode:: 53 | 54 | import av 55 | 56 | av.logging.set_level(av.logging.VERBOSE) 57 | container = av.open(path_to_video) 58 | 59 | for index, frame in enumerate(container.decode(video=0)): 60 | frame.to_image().save(f"frame-{index:04d}.jpg") 61 | 62 | 63 | Overview 64 | -------- 65 | 66 | .. toctree:: 67 | :glob: 68 | :maxdepth: 2 69 | 70 | overview/* 71 | 72 | 73 | Cookbook 74 | -------- 75 | 76 | .. toctree:: 77 | :glob: 78 | :maxdepth: 2 79 | 80 | cookbook/* 81 | 82 | 83 | Reference 84 | --------- 85 | 86 | .. toctree:: 87 | :glob: 88 | :maxdepth: 2 89 | 90 | api/* 91 | 92 | 93 | Development 94 | ----------- 95 | 96 | .. toctree:: 97 | :glob: 98 | :maxdepth: 1 99 | 100 | development/* 101 | 102 | 103 | Indices and Tables 104 | ================== 105 | * :ref:`genindex` 106 | * :ref:`modindex` 107 | * :ref:`search` 108 | -------------------------------------------------------------------------------- /docs/overview/caveats.rst: -------------------------------------------------------------------------------- 1 | Caveats 2 | ======= 3 | 4 | .. _authority_of_docs: 5 | 6 | Authority of Documentation 7 | -------------------------- 8 | 9 | FFmpeg_ is extremely complex, and the PyAV developers have not been successful in making it 100% clear to themselves in all aspects. Our understanding of how it works and how to work with it is via reading the docs, digging through the source, performing experiments, and hearing from users where PyAV isn't doing the right thing. 10 | 11 | Only where this documentation is about the mechanics of PyAV can it be considered authoritative. Anywhere that we discuss something that is about the underlying FFmpeg libraries comes with the caveat that we can not be 100% sure on it. It is, unfortunately, often on the user to understand and deal with edge cases. We encourage you to bring them to our attention via GitHub_ so that we can try to make PyAV deal with it. 12 | 13 | 14 | Unsupported Features 15 | -------------------- 16 | 17 | Our goal is to provide all of the features that make sense for the contexts that PyAV would be used in. If there is something missing, please reach out on GitHub_ or open a feature request (or even better a pull request). Your request will be more likely to be addressed if you can point to the relevant `FFmpeg API documentation `__. 18 | 19 | 20 | Sub-Interpreters 21 | ---------------- 22 | 23 | Since we rely upon C callbacks in a few locations, PyAV is not fully compatible with sub-interpreters. Users have experienced lockups in WSGI web applications, for example. 24 | 25 | This is due to the ``PyGILState_Ensure`` calls made by Cython in a C callback from FFmpeg. If this is called in a thread that was not started by Python, it is very likely to break. There is no current instrumentation to detect such events. 26 | 27 | The two main features that can cause lockups are: 28 | 29 | 1. Python IO (passing a file-like object to ``av.open``). While this is in theory possible, so far it seems like the callbacks are made in the calling thread, and so are safe. 30 | 31 | 2. Logging. If you have logging enabled (disabled by default), those log messages could cause lockups when using threads. 32 | 33 | 34 | .. _garbage_collection: 35 | 36 | Garbage Collection 37 | ------------------ 38 | 39 | PyAV currently has a number of reference cycles that make it more difficult for the garbage collector than we would like. In some circumstances (usually tight loops involving opening many containers), a :class:`.Container` will not auto-close until many a few thousand have built-up. 40 | 41 | Until we resolve this issue, you should explicitly call :meth:`.Container.close` or use the container as a context manager:: 42 | 43 | with av.open(path) as container: 44 | # Do stuff with it. 45 | 46 | 47 | .. _FFmpeg: https://ffmpeg.org/ 48 | .. _GitHub: https://github.com/PyAV-Org/PyAV 49 | -------------------------------------------------------------------------------- /docs/overview/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | Binary wheels 5 | ------------- 6 | 7 | Binary wheels are provided on PyPI for Linux, MacOS, and Windows linked against FFmpeg. The most straight-forward way to install PyAV is to run: 8 | 9 | .. code-block:: bash 10 | 11 | pip install av 12 | 13 | 14 | Conda 15 | ----- 16 | 17 | Another way to install PyAV is via `conda-forge `_:: 18 | 19 | conda install av -c conda-forge 20 | 21 | See the `Conda quick install `_ docs to get started with (mini)Conda. 22 | 23 | 24 | Bring your own FFmpeg 25 | --------------------- 26 | 27 | PyAV can also be compiled against your own build of FFmpeg (version ``7.0`` or higher). You can force installing PyAV from source by running: 28 | 29 | .. code-block:: bash 30 | pip install av --no-binary av 31 | PyAV depends upon several libraries from FFmpeg: 32 | 33 | - ``libavcodec`` 34 | - ``libavdevice`` 35 | - ``libavfilter`` 36 | - ``libavformat`` 37 | - ``libavutil`` 38 | - ``libswresample`` 39 | - ``libswscale`` 40 | 41 | and a few other tools in general: 42 | 43 | - ``pkg-config`` 44 | - Python's development headers 45 | 46 | 47 | MacOS 48 | ^^^^^ 49 | 50 | On **MacOS**, Homebrew_ saves the day:: 51 | 52 | brew install ffmpeg pkg-config 53 | 54 | .. _homebrew: http://brew.sh/ 55 | 56 | 57 | Ubuntu >= 18.04 LTS 58 | ^^^^^^^^^^^^^^^^^^^ 59 | 60 | On **Ubuntu 18.04 LTS** everything can come from the default sources:: 61 | 62 | # General dependencies 63 | sudo apt-get install -y python-dev pkg-config 64 | 65 | # Library components 66 | sudo apt-get install -y \ 67 | libavformat-dev libavcodec-dev libavdevice-dev \ 68 | libavutil-dev libswscale-dev libswresample-dev libavfilter-dev 69 | 70 | 71 | Windows 72 | ^^^^^^^ 73 | 74 | It is possible to build PyAV on Windows without Conda by installing FFmpeg yourself, e.g. from the `shared and dev packages `_. 75 | 76 | Unpack them somewhere (like ``C:\ffmpeg``), and then :ref:`tell PyAV where they are located `. 77 | 78 | 79 | Building from the latest source 80 | ------------------------------- 81 | 82 | :: 83 | 84 | # Get PyAV from GitHub. 85 | git clone https://github.com/PyAV-Org/PyAV.git 86 | cd PyAV 87 | 88 | # Prep a virtualenv. 89 | source scripts/activate.sh 90 | 91 | # Optionally build FFmpeg. 92 | ./scripts/build-deps 93 | 94 | # Build PyAV. 95 | make 96 | 97 | On **MacOS** you may have issues with regards to Python expecting gcc but finding clang. Try to export the following before installation:: 98 | 99 | export ARCHFLAGS=-Wno-error=unused-command-line-argument-hard-error-in-future 100 | 101 | 102 | .. _build_on_windows: 103 | 104 | On **Windows** you must indicate the location of your FFmpeg, e.g.:: 105 | 106 | python setup.py build --ffmpeg-dir=C:\ffmpeg 107 | -------------------------------------------------------------------------------- /examples/audio/atempo.py: -------------------------------------------------------------------------------- 1 | import av 2 | 3 | av.logging.set_level(av.logging.VERBOSE) 4 | 5 | input_file = av.open("input.wav") 6 | output_file = av.open("output.wav", mode="w") 7 | 8 | input_stream = input_file.streams.audio[0] 9 | output_stream = output_file.add_stream("pcm_s16le", rate=input_stream.rate) 10 | 11 | graph = av.filter.Graph() 12 | graph.link_nodes( 13 | graph.add_abuffer(template=input_stream), 14 | graph.add("atempo", "2.0"), 15 | graph.add("abuffersink"), 16 | ).configure() 17 | 18 | for frame in input_file.decode(input_stream): 19 | graph.push(frame) 20 | while True: 21 | try: 22 | for packet in output_stream.encode(graph.pull()): 23 | output_file.mux(packet) 24 | except (av.BlockingIOError, av.EOFError): 25 | break 26 | 27 | # Flush the stream 28 | for packet in output_stream.encode(None): 29 | output_file.mux(packet) 30 | 31 | input_file.close() 32 | output_file.close() 33 | -------------------------------------------------------------------------------- /examples/basics/hw_decode.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | 4 | import av 5 | import av.datasets 6 | from av.codec.hwaccel import HWAccel, hwdevices_available 7 | 8 | # What accelerator to use. 9 | # Recommendations: 10 | # Windows: 11 | # - d3d11va (Direct3D 11) 12 | # * available with built-in ffmpeg in PyAV binary wheels, and gives access to 13 | # all decoders, but performance may not be as good as vendor native interfaces. 14 | # - cuda (NVIDIA NVDEC), qsv (Intel QuickSync) 15 | # * may be faster than d3d11va, but requires custom ffmpeg built with those libraries. 16 | # Linux (all options require custom FFmpeg): 17 | # - vaapi (Intel, AMD) 18 | # - cuda (NVIDIA) 19 | # Mac: 20 | # - videotoolbox 21 | # * available with built-in ffmpeg in PyAV binary wheels, and gives access to 22 | # all accelerators available on Macs. This is the only option on MacOS. 23 | 24 | HW_DEVICE = os.environ["HW_DEVICE"] if "HW_DEVICE" in os.environ else None 25 | 26 | if "TEST_FILE_PATH" in os.environ: 27 | test_file_path = os.environ["TEST_FILE_PATH"] 28 | else: 29 | test_file_path = av.datasets.curated( 30 | "pexels/time-lapse-video-of-night-sky-857195.mp4" 31 | ) 32 | 33 | if HW_DEVICE is None: 34 | print(f"Please set HW_DEVICE. Options are: {hwdevices_available()}") 35 | exit() 36 | 37 | assert HW_DEVICE in hwdevices_available(), f"{HW_DEVICE} not available." 38 | 39 | print("Decoding in software (auto threading)...") 40 | 41 | container = av.open(test_file_path) 42 | 43 | container.streams.video[0].thread_type = "AUTO" 44 | 45 | start_time = time.time() 46 | frame_count = 0 47 | for packet in container.demux(video=0): 48 | for _ in packet.decode(): 49 | frame_count += 1 50 | 51 | sw_time = time.time() - start_time 52 | sw_fps = frame_count / sw_time 53 | assert frame_count == container.streams.video[0].frames 54 | container.close() 55 | 56 | print( 57 | f"Decoded with software in {sw_time:.2f}s ({sw_fps:.2f} fps).\n" 58 | f"Decoding with {HW_DEVICE}" 59 | ) 60 | 61 | hwaccel = HWAccel(device_type=HW_DEVICE, allow_software_fallback=False) 62 | 63 | # Note the additional argument here. 64 | container = av.open(test_file_path, hwaccel=hwaccel) 65 | 66 | start_time = time.time() 67 | frame_count = 0 68 | for packet in container.demux(video=0): 69 | for _ in packet.decode(): 70 | frame_count += 1 71 | 72 | hw_time = time.time() - start_time 73 | hw_fps = frame_count / hw_time 74 | assert frame_count == container.streams.video[0].frames 75 | container.close() 76 | 77 | print(f"Decoded with {HW_DEVICE} in {hw_time:.2f}s ({hw_fps:.2f} fps).") 78 | -------------------------------------------------------------------------------- /examples/basics/parse.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | 4 | import av 5 | import av.datasets 6 | 7 | # We want an H.264 stream in the Annex B byte-stream format. 8 | # We haven't exposed bitstream filters yet, so we're gonna use the `ffmpeg` CLI. 9 | h264_path = "night-sky.h264" 10 | if not os.path.exists(h264_path): 11 | subprocess.check_call( 12 | [ 13 | "ffmpeg", 14 | "-i", 15 | av.datasets.curated("pexels/time-lapse-video-of-night-sky-857195.mp4"), 16 | "-vcodec", 17 | "copy", 18 | "-an", 19 | "-bsf:v", 20 | "h264_mp4toannexb", 21 | h264_path, 22 | ] 23 | ) 24 | 25 | 26 | fh = open(h264_path, "rb") 27 | 28 | codec = av.CodecContext.create("h264", "r") 29 | 30 | while True: 31 | chunk = fh.read(1 << 16) 32 | 33 | packets = codec.parse(chunk) 34 | print("Parsed {} packets from {} bytes:".format(len(packets), len(chunk))) 35 | 36 | for packet in packets: 37 | print(" ", packet) 38 | 39 | frames = codec.decode(packet) 40 | for frame in frames: 41 | print(" ", frame) 42 | 43 | # We wait until the end to bail so that the last empty `buf` flushes 44 | # the parser. 45 | if not chunk: 46 | break 47 | -------------------------------------------------------------------------------- /examples/basics/record_facecam.py: -------------------------------------------------------------------------------- 1 | import av 2 | 3 | av.logging.set_level(av.logging.VERBOSE) 4 | 5 | 6 | """ 7 | This is written for MacOS. Other platforms will need to init `input_` differently. 8 | You may need to change the file "0". Use this command to list all devices: 9 | 10 | ffmpeg -f avfoundation -list_devices true -i "" 11 | 12 | """ 13 | 14 | input_ = av.open( 15 | "0", 16 | format="avfoundation", 17 | container_options={"framerate": "30", "video_size": "1920x1080"}, 18 | ) 19 | output = av.open("out.mkv", "w") 20 | 21 | # Prefer x264, but use Apple hardware if not available. 22 | try: 23 | encoder = av.Codec("libx264", "w").name 24 | except av.FFmpegError: 25 | encoder = "h264_videotoolbox" 26 | 27 | output_stream = output.add_stream(encoder, rate=30) 28 | output_stream.width = input_.streams.video[0].width 29 | output_stream.height = input_.streams.video[0].height 30 | output_stream.pix_fmt = "yuv420p" 31 | 32 | try: 33 | while True: 34 | try: 35 | for frame in input_.decode(video=0): 36 | packet = output_stream.encode(frame) 37 | output.mux(packet) 38 | except av.BlockingIOError: 39 | pass 40 | except KeyboardInterrupt: 41 | print("Recording stopped by user") 42 | 43 | packet = output_stream.encode(None) 44 | output.mux(packet) 45 | 46 | input_.close() 47 | output.close() 48 | -------------------------------------------------------------------------------- /examples/basics/record_screen.py: -------------------------------------------------------------------------------- 1 | import av 2 | 3 | av.logging.set_level(av.logging.VERBOSE) 4 | 5 | """ 6 | This is written for MacOS. Other platforms will need a different file, format pair. 7 | You may need to change the file "1". Use this command to list all devices: 8 | 9 | ffmpeg -f avfoundation -list_devices true -i "" 10 | 11 | """ 12 | 13 | input_ = av.open("1", format="avfoundation") 14 | output = av.open("out.mkv", "w") 15 | 16 | # Prefer x264, but use Apple hardware if not available. 17 | try: 18 | encoder = av.Codec("libx264", "w").name 19 | except av.FFmpegError: 20 | encoder = "h264_videotoolbox" 21 | 22 | output_stream = output.add_stream(encoder, rate=30) 23 | output_stream.width = input_.streams.video[0].width 24 | output_stream.height = input_.streams.video[0].height 25 | output_stream.pix_fmt = "yuv420p" 26 | 27 | try: 28 | while True: 29 | try: 30 | for frame in input_.decode(video=0): 31 | packet = output_stream.encode(frame) 32 | output.mux(packet) 33 | except av.BlockingIOError: 34 | pass 35 | except KeyboardInterrupt: 36 | print("Recording stopped by user") 37 | 38 | packet = output_stream.encode(None) 39 | output.mux(packet) 40 | 41 | input_.close() 42 | output.close() 43 | -------------------------------------------------------------------------------- /examples/basics/remux.py: -------------------------------------------------------------------------------- 1 | import av 2 | import av.datasets 3 | 4 | av.logging.set_level(av.logging.VERBOSE) 5 | 6 | input_ = av.open(av.datasets.curated("pexels/time-lapse-video-of-night-sky-857195.mp4")) 7 | output = av.open("remuxed.mkv", "w") 8 | 9 | # Make an output stream using the input as a template. This copies the stream 10 | # setup from one to the other. 11 | in_stream = input_.streams.video[0] 12 | out_stream = output.add_stream_from_template(in_stream) 13 | 14 | for packet in input_.demux(in_stream): 15 | print(packet) 16 | 17 | # We need to skip the "flushing" packets that `demux` generates. 18 | if packet.dts is None: 19 | continue 20 | 21 | # We need to assign the packet to the new stream. 22 | packet.stream = out_stream 23 | 24 | output.mux(packet) 25 | 26 | input_.close() 27 | output.close() 28 | -------------------------------------------------------------------------------- /examples/basics/save_keyframes.py: -------------------------------------------------------------------------------- 1 | import av 2 | import av.datasets 3 | 4 | content = av.datasets.curated("pexels/time-lapse-video-of-night-sky-857195.mp4") 5 | with av.open(content) as container: 6 | # Signal that we only want to look at keyframes. 7 | stream = container.streams.video[0] 8 | stream.codec_context.skip_frame = "NONKEY" 9 | 10 | for i, frame in enumerate(container.decode(stream)): 11 | print(frame) 12 | frame.to_image().save(f"night-sky.{i:04d}.jpg", quality=80) 13 | -------------------------------------------------------------------------------- /examples/basics/thread_type.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import av 4 | import av.datasets 5 | 6 | print("Decoding with default (slice) threading...") 7 | 8 | container = av.open( 9 | av.datasets.curated("pexels/time-lapse-video-of-night-sky-857195.mp4") 10 | ) 11 | 12 | start_time = time.time() 13 | for packet in container.demux(): 14 | print(packet) 15 | for frame in packet.decode(): 16 | print(frame) 17 | 18 | default_time = time.time() - start_time 19 | container.close() 20 | 21 | 22 | print("Decoding with auto threading...") 23 | 24 | container = av.open( 25 | av.datasets.curated("pexels/time-lapse-video-of-night-sky-857195.mp4") 26 | ) 27 | 28 | # !!! This is the only difference. 29 | container.streams.video[0].thread_type = "AUTO" 30 | 31 | start_time = time.time() 32 | for packet in container.demux(): 33 | print(packet) 34 | for frame in packet.decode(): 35 | print(frame) 36 | 37 | auto_time = time.time() - start_time 38 | container.close() 39 | 40 | 41 | print("Decoded with default threading in {:.2f}s.".format(default_time)) 42 | print("Decoded with auto threading in {:.2f}s.".format(auto_time)) 43 | -------------------------------------------------------------------------------- /examples/numpy/barcode.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from PIL import Image 3 | 4 | import av 5 | import av.datasets 6 | 7 | container = av.open( 8 | av.datasets.curated("pexels/time-lapse-video-of-sunset-by-the-sea-854400.mp4") 9 | ) 10 | container.streams.video[0].thread_type = "AUTO" # Go faster! 11 | 12 | columns = [] 13 | for frame in container.decode(video=0): 14 | print(frame) 15 | array = frame.to_ndarray(format="rgb24") 16 | 17 | # Collapse down to a column. 18 | column = array.mean(axis=1) 19 | 20 | # Convert to bytes, as the `mean` turned our array into floats. 21 | column = column.clip(0, 255).astype("uint8") 22 | 23 | # Get us in the right shape for the `hstack` below. 24 | column = column.reshape(-1, 1, 3) 25 | 26 | columns.append(column) 27 | 28 | # Close the file, free memory 29 | container.close() 30 | 31 | full_array = np.hstack(columns) 32 | full_img = Image.fromarray(full_array, "RGB") 33 | full_img = full_img.resize((800, 200)) 34 | full_img.save("barcode.jpg", quality=85) 35 | -------------------------------------------------------------------------------- /examples/numpy/generate_video.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import av 4 | 5 | duration = 4 6 | fps = 24 7 | total_frames = duration * fps 8 | 9 | container = av.open("test.mp4", mode="w") 10 | 11 | stream = container.add_stream("mpeg4", rate=fps) 12 | stream.width = 480 13 | stream.height = 320 14 | stream.pix_fmt = "yuv420p" 15 | 16 | for frame_i in range(total_frames): 17 | img = np.empty((480, 320, 3)) 18 | img[:, :, 0] = 0.5 + 0.5 * np.sin(2 * np.pi * (0 / 3 + frame_i / total_frames)) 19 | img[:, :, 1] = 0.5 + 0.5 * np.sin(2 * np.pi * (1 / 3 + frame_i / total_frames)) 20 | img[:, :, 2] = 0.5 + 0.5 * np.sin(2 * np.pi * (2 / 3 + frame_i / total_frames)) 21 | 22 | img = np.round(255 * img).astype(np.uint8) 23 | 24 | frame = av.VideoFrame.from_ndarray(img, format="rgb24") 25 | for packet in stream.encode(frame): 26 | container.mux(packet) 27 | 28 | # Flush stream 29 | for packet in stream.encode(): 30 | container.mux(packet) 31 | 32 | # Close the file 33 | container.close() 34 | -------------------------------------------------------------------------------- /examples/subtitles/remux.py: -------------------------------------------------------------------------------- 1 | import av 2 | 3 | av.logging.set_level(av.logging.VERBOSE) 4 | 5 | input_ = av.open("resources/webvtt.mkv") 6 | output = av.open("remuxed.vtt", "w") 7 | 8 | in_stream = input_.streams.subtitles[0] 9 | out_stream = output.add_stream_from_template(in_stream) 10 | 11 | for packet in input_.demux(in_stream): 12 | if packet.dts is None: 13 | continue 14 | packet.stream = out_stream 15 | output.mux(packet) 16 | 17 | input_.close() 18 | output.close() 19 | 20 | print("Remuxing done") 21 | 22 | with av.open("remuxed.vtt") as f: 23 | for subset in f.decode(subtitles=0): 24 | for sub in subset: 25 | print(sub.ass) 26 | -------------------------------------------------------------------------------- /include/libav.pxd: -------------------------------------------------------------------------------- 1 | include "libavutil/avutil.pxd" 2 | include "libavutil/buffer.pxd" 3 | include "libavutil/channel_layout.pxd" 4 | include "libavutil/dict.pxd" 5 | include "libavutil/error.pxd" 6 | include "libavutil/frame.pxd" 7 | include "libavutil/hwcontext.pxd" 8 | include "libavutil/samplefmt.pxd" 9 | include "libavutil/motion_vector.pxd" 10 | 11 | include "libavcodec/avcodec.pxd" 12 | include "libavcodec/bsf.pxd" 13 | include "libavcodec/hwaccel.pxd" 14 | 15 | include "libavdevice/avdevice.pxd" 16 | include "libavformat/avformat.pxd" 17 | include "libswresample/swresample.pxd" 18 | include "libswscale/swscale.pxd" 19 | 20 | include "libavfilter/avfilter.pxd" 21 | include "libavfilter/avfiltergraph.pxd" 22 | include "libavfilter/buffersink.pxd" 23 | include "libavfilter/buffersrc.pxd" 24 | 25 | 26 | cdef extern from "stdio.h" nogil: 27 | cdef int snprintf(char *output, int n, const char *format, ...) 28 | cdef int vsnprintf(char *output, int n, const char *format, va_list args) 29 | -------------------------------------------------------------------------------- /include/libavcodec/bsf.pxd: -------------------------------------------------------------------------------- 1 | 2 | cdef extern from "libavcodec/bsf.h" nogil: 3 | 4 | cdef struct AVBitStreamFilter: 5 | const char *name 6 | AVCodecID *codec_ids 7 | 8 | cdef struct AVCodecParameters: 9 | pass 10 | 11 | cdef struct AVBSFContext: 12 | const AVBitStreamFilter *filter 13 | const AVCodecParameters *par_in 14 | const AVCodecParameters *par_out 15 | 16 | cdef const AVBitStreamFilter* av_bsf_get_by_name(const char *name) 17 | 18 | cdef int av_bsf_list_parse_str( 19 | const char *str, 20 | AVBSFContext **bsf 21 | ) 22 | 23 | cdef int av_bsf_init(AVBSFContext *ctx) 24 | cdef void av_bsf_free(AVBSFContext **ctx) 25 | 26 | cdef AVBitStreamFilter* av_bsf_iterate(void **opaque) 27 | 28 | cdef int av_bsf_send_packet( 29 | AVBSFContext *ctx, 30 | AVPacket *pkt 31 | ) 32 | 33 | cdef int av_bsf_receive_packet( 34 | AVBSFContext *ctx, 35 | AVPacket *pkt 36 | ) 37 | 38 | cdef void av_bsf_flush( 39 | AVBSFContext *ctx 40 | ) 41 | -------------------------------------------------------------------------------- /include/libavcodec/hwaccel.pxd: -------------------------------------------------------------------------------- 1 | cdef extern from "libavcodec/avcodec.h" nogil: 2 | cdef enum: 3 | AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX, 4 | AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX, 5 | AV_CODEC_HW_CONFIG_METHOD_INTERNAL, 6 | AV_CODEC_HW_CONFIG_METHOD_AD_HOC, 7 | cdef struct AVCodecHWConfig: 8 | AVPixelFormat pix_fmt 9 | int methods 10 | AVHWDeviceType device_type 11 | cdef const AVCodecHWConfig* avcodec_get_hw_config(const AVCodec *codec, int index) 12 | cdef enum: 13 | AV_HWACCEL_CODEC_CAP_EXPERIMENTAL 14 | cdef struct AVHWAccel: 15 | char *name 16 | AVMediaType type 17 | AVCodecID id 18 | AVPixelFormat pix_fmt 19 | int capabilities 20 | -------------------------------------------------------------------------------- /include/libavdevice/avdevice.pxd: -------------------------------------------------------------------------------- 1 | 2 | cdef extern from "libavdevice/avdevice.h" nogil: 3 | 4 | cdef int avdevice_version() 5 | cdef char* avdevice_configuration() 6 | cdef char* avdevice_license() 7 | void avdevice_register_all() 8 | 9 | AVInputFormat * av_input_audio_device_next(AVInputFormat *d) 10 | AVInputFormat * av_input_video_device_next(AVInputFormat *d) 11 | AVOutputFormat * av_output_audio_device_next(AVOutputFormat *d) 12 | AVOutputFormat * av_output_video_device_next(AVOutputFormat *d) 13 | -------------------------------------------------------------------------------- /include/libavfilter/avfilter.pxd: -------------------------------------------------------------------------------- 1 | 2 | cdef extern from "libavfilter/avfilter.h" nogil: 3 | """ 4 | #if (LIBAVFILTER_VERSION_INT >= 525156) 5 | // avfilter_filter_pad_count is available since version 8.3.100 of libavfilter (FFmpeg 5.0) 6 | #define _avfilter_get_num_pads(filter, is_output, pads) (avfilter_filter_pad_count(filter, is_output)) 7 | #else 8 | // avfilter_filter_pad_count has been deprecated as of version 8.3.100 of libavfilter (FFmpeg 5.0) 9 | #define _avfilter_get_num_pads(filter, is_output, pads) (avfilter_pad_count(pads)) 10 | #endif 11 | """ 12 | cdef int avfilter_version() 13 | cdef char* avfilter_configuration() 14 | cdef char* avfilter_license() 15 | 16 | cdef struct AVFilterPad: 17 | # This struct is opaque. 18 | pass 19 | 20 | const char* avfilter_pad_get_name(const AVFilterPad *pads, int index) 21 | AVMediaType avfilter_pad_get_type(const AVFilterPad *pads, int index) 22 | 23 | int pyav_get_num_pads "_avfilter_get_num_pads" (const AVFilter *filter, int is_output, const AVFilterPad *pads) 24 | 25 | cdef struct AVFilter: 26 | 27 | AVClass *priv_class 28 | 29 | const char *name 30 | const char *description 31 | 32 | const int flags 33 | 34 | const AVFilterPad *inputs 35 | const AVFilterPad *outputs 36 | int (*process_command)(AVFilterContext *, const char *cmd, const char *arg, char *res, int res_len, int flags) 37 | 38 | cdef enum: 39 | AVFILTER_FLAG_DYNAMIC_INPUTS 40 | AVFILTER_FLAG_DYNAMIC_OUTPUTS 41 | AVFILTER_FLAG_SLICE_THREADS 42 | AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC 43 | AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL 44 | 45 | cdef AVFilter* avfilter_get_by_name(const char *name) 46 | cdef const AVFilter* av_filter_iterate(void **opaque) 47 | 48 | cdef struct AVFilterLink # Defined later. 49 | 50 | cdef struct AVFilterContext: 51 | 52 | AVClass *av_class 53 | AVFilter *filter 54 | 55 | char *name 56 | 57 | unsigned int nb_inputs 58 | AVFilterPad *input_pads 59 | AVFilterLink **inputs 60 | 61 | unsigned int nb_outputs 62 | AVFilterPad *output_pads 63 | AVFilterLink **outputs 64 | 65 | cdef int avfilter_init_str(AVFilterContext *ctx, const char *args) 66 | cdef int avfilter_init_dict(AVFilterContext *ctx, AVDictionary **options) 67 | cdef void avfilter_free(AVFilterContext*) 68 | cdef AVClass* avfilter_get_class() 69 | 70 | cdef struct AVFilterLink: 71 | 72 | AVFilterContext *src 73 | AVFilterPad *srcpad 74 | AVFilterContext *dst 75 | AVFilterPad *dstpad 76 | 77 | AVMediaType Type 78 | int w 79 | int h 80 | AVRational sample_aspect_ratio 81 | uint64_t channel_layout 82 | int sample_rate 83 | int format 84 | AVRational time_base 85 | 86 | # custom 87 | cdef set pyav_get_available_filters() 88 | 89 | 90 | cdef extern from "libavfilter/buffersink.h" nogil: 91 | cdef void av_buffersink_set_frame_size(AVFilterContext *ctx, unsigned frame_size) 92 | -------------------------------------------------------------------------------- /include/libavfilter/avfiltergraph.pxd: -------------------------------------------------------------------------------- 1 | 2 | cdef extern from "libavfilter/avfilter.h" nogil: 3 | 4 | cdef struct AVFilterGraph: 5 | int nb_filters 6 | AVFilterContext **filters 7 | 8 | cdef struct AVFilterInOut: 9 | char *name 10 | AVFilterContext *filter_ctx 11 | int pad_idx 12 | AVFilterInOut *next 13 | 14 | cdef AVFilterGraph* avfilter_graph_alloc() 15 | cdef void avfilter_graph_free(AVFilterGraph **ptr) 16 | 17 | cdef int avfilter_graph_parse2( 18 | AVFilterGraph *graph, 19 | const char *filter_str, 20 | AVFilterInOut **inputs, 21 | AVFilterInOut **outputs 22 | ) 23 | 24 | cdef AVFilterContext* avfilter_graph_alloc_filter( 25 | AVFilterGraph *graph, 26 | const AVFilter *filter, 27 | const char *name 28 | ) 29 | 30 | cdef int avfilter_graph_create_filter( 31 | AVFilterContext **filt_ctx, 32 | AVFilter *filt, 33 | const char *name, 34 | const char *args, 35 | void *opaque, 36 | AVFilterGraph *graph_ctx 37 | ) 38 | 39 | cdef int avfilter_link( 40 | AVFilterContext *src, 41 | unsigned int srcpad, 42 | AVFilterContext *dst, 43 | unsigned int dstpad 44 | ) 45 | 46 | cdef int avfilter_graph_config(AVFilterGraph *graph, void *logctx) 47 | 48 | cdef char* avfilter_graph_dump(AVFilterGraph *graph, const char *options) 49 | 50 | cdef void avfilter_inout_free(AVFilterInOut **inout_list) 51 | -------------------------------------------------------------------------------- /include/libavfilter/buffersink.pxd: -------------------------------------------------------------------------------- 1 | cdef extern from "libavfilter/buffersink.h" nogil: 2 | 3 | int av_buffersink_get_frame( 4 | AVFilterContext *ctx, 5 | AVFrame *frame 6 | ) 7 | -------------------------------------------------------------------------------- /include/libavfilter/buffersrc.pxd: -------------------------------------------------------------------------------- 1 | cdef extern from "libavfilter/buffersrc.h" nogil: 2 | 3 | int av_buffersrc_write_frame( 4 | AVFilterContext *ctx, 5 | const AVFrame *frame 6 | ) 7 | -------------------------------------------------------------------------------- /include/libavutil/buffer.pxd: -------------------------------------------------------------------------------- 1 | from libc.stdint cimport intptr_t, uint8_t 2 | 3 | cdef extern from "libavutil/buffer.h" nogil: 4 | AVBufferRef *av_buffer_create(uint8_t *data, size_t size, void (*free)(void *opaque, uint8_t *data), void *opaque, int flags) 5 | AVBufferRef* av_buffer_ref(AVBufferRef *buf) 6 | void av_buffer_unref(AVBufferRef **buf) 7 | 8 | cdef struct AVBuffer: 9 | uint8_t *data 10 | int size 11 | intptr_t refcount 12 | void (*free)(void *opaque, uint8_t *data) 13 | void *opaque 14 | int flags 15 | cdef struct AVBufferRef: 16 | AVBuffer *buffer 17 | uint8_t *data 18 | int size 19 | -------------------------------------------------------------------------------- /include/libavutil/channel_layout.pxd: -------------------------------------------------------------------------------- 1 | cdef extern from "libavutil/channel_layout.h" nogil: 2 | 3 | # This is not a comprehensive list. 4 | cdef uint64_t AV_CH_LAYOUT_MONO 5 | cdef uint64_t AV_CH_LAYOUT_STEREO 6 | cdef uint64_t AV_CH_LAYOUT_2POINT1 7 | cdef uint64_t AV_CH_LAYOUT_4POINT0 8 | cdef uint64_t AV_CH_LAYOUT_5POINT0_BACK 9 | cdef uint64_t AV_CH_LAYOUT_5POINT1_BACK 10 | cdef uint64_t AV_CH_LAYOUT_6POINT1 11 | cdef uint64_t AV_CH_LAYOUT_7POINT1 12 | -------------------------------------------------------------------------------- /include/libavutil/dict.pxd: -------------------------------------------------------------------------------- 1 | cdef extern from "libavutil/dict.h" nogil: 2 | 3 | # See: http://ffmpeg.org/doxygen/trunk/structAVDictionary.html 4 | ctypedef struct AVDictionary: 5 | pass 6 | 7 | cdef void av_dict_free(AVDictionary **) 8 | 9 | # See: http://ffmpeg.org/doxygen/trunk/structAVDictionaryEntry.html 10 | ctypedef struct AVDictionaryEntry: 11 | char *key 12 | char *value 13 | 14 | cdef int AV_DICT_IGNORE_SUFFIX 15 | 16 | cdef AVDictionaryEntry* av_dict_get( 17 | AVDictionary *dict, 18 | char *key, 19 | AVDictionaryEntry *prev, 20 | int flags, 21 | ) 22 | 23 | cdef int av_dict_set( 24 | AVDictionary **pm, 25 | const char *key, 26 | const char *value, 27 | int flags 28 | ) 29 | 30 | cdef int av_dict_count( 31 | AVDictionary *m 32 | ) 33 | 34 | cdef int av_dict_copy( 35 | AVDictionary **dst, 36 | AVDictionary *src, 37 | int flags 38 | ) 39 | -------------------------------------------------------------------------------- /include/libavutil/error.pxd: -------------------------------------------------------------------------------- 1 | cdef extern from "libavutil/error.h" nogil: 2 | 3 | # Not actually from here, but whatever. 4 | cdef int ENOMEM 5 | cdef int EAGAIN 6 | 7 | cdef int AVERROR_BSF_NOT_FOUND 8 | cdef int AVERROR_BUG 9 | cdef int AVERROR_BUFFER_TOO_SMALL 10 | cdef int AVERROR_DECODER_NOT_FOUND 11 | cdef int AVERROR_DEMUXER_NOT_FOUND 12 | cdef int AVERROR_ENCODER_NOT_FOUND 13 | cdef int AVERROR_EOF 14 | cdef int AVERROR_EXIT 15 | cdef int AVERROR_EXTERNAL 16 | cdef int AVERROR_FILTER_NOT_FOUND 17 | cdef int AVERROR_INVALIDDATA 18 | cdef int AVERROR_MUXER_NOT_FOUND 19 | cdef int AVERROR_OPTION_NOT_FOUND 20 | cdef int AVERROR_PATCHWELCOME 21 | cdef int AVERROR_PROTOCOL_NOT_FOUND 22 | cdef int AVERROR_UNKNOWN 23 | cdef int AVERROR_EXPERIMENTAL 24 | cdef int AVERROR_INPUT_CHANGED 25 | cdef int AVERROR_OUTPUT_CHANGED 26 | 27 | cdef int AVERROR_HTTP_BAD_REQUEST 28 | cdef int AVERROR_HTTP_UNAUTHORIZED 29 | cdef int AVERROR_HTTP_FORBIDDEN 30 | cdef int AVERROR_HTTP_NOT_FOUND 31 | cdef int AVERROR_HTTP_OTHER_4XX 32 | cdef int AVERROR_HTTP_SERVER_ERROR 33 | 34 | cdef int AVERROR_NOMEM "AVERROR(ENOMEM)" 35 | 36 | # cdef int FFERRTAG(int, int, int, int) 37 | 38 | cdef int AVERROR(int error) 39 | 40 | cdef int AV_ERROR_MAX_STRING_SIZE 41 | 42 | cdef int av_strerror(int errno, char *output, size_t output_size) 43 | cdef char* av_err2str(int errnum) 44 | -------------------------------------------------------------------------------- /include/libavutil/frame.pxd: -------------------------------------------------------------------------------- 1 | cdef extern from "libavutil/frame.h" nogil: 2 | 3 | cdef AVFrame* av_frame_alloc() 4 | cdef void av_frame_free(AVFrame**) 5 | cdef int av_frame_ref(AVFrame *dst, const AVFrame *src) 6 | cdef AVFrame* av_frame_clone(const AVFrame *src) 7 | cdef void av_frame_unref(AVFrame *frame) 8 | cdef void av_frame_move_ref(AVFrame *dst, AVFrame *src) 9 | cdef int av_frame_get_buffer(AVFrame *frame, int align) 10 | cdef int av_frame_is_writable(AVFrame *frame) 11 | cdef int av_frame_make_writable(AVFrame *frame) 12 | cdef int av_frame_copy(AVFrame *dst, const AVFrame *src) 13 | cdef int av_frame_copy_props(AVFrame *dst, const AVFrame *src) 14 | cdef AVFrameSideData* av_frame_get_side_data(AVFrame *frame, AVFrameSideDataType type) 15 | -------------------------------------------------------------------------------- /include/libavutil/hwcontext.pxd: -------------------------------------------------------------------------------- 1 | cdef extern from "libavutil/hwcontext.h" nogil: 2 | 3 | enum AVHWDeviceType: 4 | AV_HWDEVICE_TYPE_NONE 5 | AV_HWDEVICE_TYPE_VDPAU 6 | AV_HWDEVICE_TYPE_CUDA 7 | AV_HWDEVICE_TYPE_VAAPI 8 | AV_HWDEVICE_TYPE_DXVA2 9 | AV_HWDEVICE_TYPE_QSV 10 | AV_HWDEVICE_TYPE_VIDEOTOOLBOX 11 | AV_HWDEVICE_TYPE_D3D11VA 12 | AV_HWDEVICE_TYPE_DRM 13 | AV_HWDEVICE_TYPE_OPENCL 14 | AV_HWDEVICE_TYPE_MEDIACODEC 15 | AV_HWDEVICE_TYPE_VULKAN 16 | AV_HWDEVICE_TYPE_D3D12VA 17 | 18 | cdef int av_hwdevice_ctx_create(AVBufferRef **device_ctx, AVHWDeviceType type, const char *device, AVDictionary *opts, int flags) 19 | 20 | cdef AVHWDeviceType av_hwdevice_find_type_by_name(const char *name) 21 | cdef const char *av_hwdevice_get_type_name(AVHWDeviceType type) 22 | cdef AVHWDeviceType av_hwdevice_iterate_types(AVHWDeviceType prev) 23 | 24 | cdef int av_hwframe_transfer_data(AVFrame *dst, const AVFrame *src, int flags) 25 | -------------------------------------------------------------------------------- /include/libavutil/motion_vector.pxd: -------------------------------------------------------------------------------- 1 | from libc.stdint cimport int16_t, int32_t, uint8_t, uint16_t, uint64_t 2 | 3 | 4 | cdef extern from "libavutil/motion_vector.h" nogil: 5 | 6 | cdef struct AVMotionVector: 7 | int32_t source 8 | uint8_t w 9 | uint8_t h 10 | int16_t src_x 11 | int16_t src_y 12 | int16_t dst_x 13 | int16_t dst_y 14 | uint64_t flags 15 | int32_t motion_x 16 | int32_t motion_y 17 | uint16_t motion_scale 18 | -------------------------------------------------------------------------------- /include/libavutil/samplefmt.pxd: -------------------------------------------------------------------------------- 1 | cdef extern from "libavutil/samplefmt.h" nogil: 2 | 3 | cdef enum AVSampleFormat: 4 | AV_SAMPLE_FMT_NONE 5 | AV_SAMPLE_FMT_U8 6 | AV_SAMPLE_FMT_S16 7 | AV_SAMPLE_FMT_S32 8 | AV_SAMPLE_FMT_FLT 9 | AV_SAMPLE_FMT_DBL 10 | AV_SAMPLE_FMT_U8P 11 | AV_SAMPLE_FMT_S16P 12 | AV_SAMPLE_FMT_S32P 13 | AV_SAMPLE_FMT_FLTP 14 | AV_SAMPLE_FMT_DBLP 15 | AV_SAMPLE_FMT_NB # Number. 16 | 17 | # Find by name. 18 | cdef AVSampleFormat av_get_sample_fmt(char* name) 19 | 20 | # Inspection. 21 | cdef char * av_get_sample_fmt_name(AVSampleFormat sample_fmt) 22 | cdef int av_get_bytes_per_sample(AVSampleFormat sample_fmt) 23 | cdef int av_sample_fmt_is_planar(AVSampleFormat sample_fmt) 24 | 25 | # Alternative forms. 26 | cdef AVSampleFormat av_get_packed_sample_fmt(AVSampleFormat sample_fmt) 27 | cdef AVSampleFormat av_get_planar_sample_fmt(AVSampleFormat sample_fmt) 28 | 29 | cdef int av_samples_alloc( 30 | uint8_t** audio_data, 31 | int* linesize, 32 | int nb_channels, 33 | int nb_samples, 34 | AVSampleFormat sample_fmt, 35 | int align 36 | ) 37 | 38 | cdef int av_samples_get_buffer_size( 39 | int *linesize, 40 | int nb_channels, 41 | int nb_samples, 42 | AVSampleFormat sample_fmt, 43 | int align 44 | ) 45 | 46 | cdef int av_samples_fill_arrays( 47 | uint8_t **audio_data, 48 | int *linesize, 49 | const uint8_t *buf, 50 | int nb_channels, 51 | int nb_samples, 52 | AVSampleFormat sample_fmt, 53 | int align 54 | ) 55 | 56 | cdef int av_samples_set_silence( 57 | uint8_t **audio_data, 58 | int offset, 59 | int nb_samples, 60 | int nb_channels, 61 | AVSampleFormat sample_fmt 62 | ) 63 | -------------------------------------------------------------------------------- /include/libswresample/swresample.pxd: -------------------------------------------------------------------------------- 1 | from libc.stdint cimport int64_t, uint8_t 2 | 3 | 4 | cdef extern from "libswresample/swresample.h" nogil: 5 | 6 | cdef int swresample_version() 7 | cdef char* swresample_configuration() 8 | cdef char* swresample_license() 9 | 10 | cdef struct SwrContext: 11 | pass 12 | 13 | cdef SwrContext* swr_alloc_set_opts( 14 | SwrContext *ctx, 15 | int64_t out_ch_layout, 16 | AVSampleFormat out_sample_fmt, 17 | int out_sample_rate, 18 | int64_t in_ch_layout, 19 | AVSampleFormat in_sample_fmt, 20 | int in_sample_rate, 21 | int log_offset, 22 | void *log_ctx # logging context, can be NULL 23 | ) 24 | 25 | cdef int swr_convert( 26 | SwrContext *ctx, 27 | uint8_t ** out_buffer, 28 | int out_count, 29 | uint8_t **in_buffer, 30 | int in_count 31 | ) 32 | # Gets the delay the next input sample will 33 | # experience relative to the next output sample. 34 | cdef int64_t swr_get_delay(SwrContext *s, int64_t base) 35 | 36 | cdef SwrContext* swr_alloc() 37 | cdef int swr_init(SwrContext* ctx) 38 | cdef void swr_free(SwrContext **ctx) 39 | cdef void swr_close(SwrContext *ctx) 40 | -------------------------------------------------------------------------------- /include/libswscale/swscale.pxd: -------------------------------------------------------------------------------- 1 | 2 | cdef extern from "libswscale/swscale.h" nogil: 3 | 4 | cdef int swscale_version() 5 | cdef char* swscale_configuration() 6 | cdef char* swscale_license() 7 | 8 | # See: http://ffmpeg.org/doxygen/trunk/structSwsContext.html 9 | cdef struct SwsContext: 10 | pass 11 | 12 | # See: http://ffmpeg.org/doxygen/trunk/structSwsFilter.html 13 | cdef struct SwsFilter: 14 | pass 15 | 16 | # Flags. 17 | cdef int SWS_FAST_BILINEAR 18 | cdef int SWS_BILINEAR 19 | cdef int SWS_BICUBIC 20 | cdef int SWS_X 21 | cdef int SWS_POINT 22 | cdef int SWS_AREA 23 | cdef int SWS_BICUBLIN 24 | cdef int SWS_GAUSS 25 | cdef int SWS_SINC 26 | cdef int SWS_LANCZOS 27 | cdef int SWS_SPLINE 28 | 29 | cdef int SWS_CS_ITU709 30 | cdef int SWS_CS_FCC 31 | cdef int SWS_CS_ITU601 32 | cdef int SWS_CS_ITU624 33 | cdef int SWS_CS_SMPTE170M 34 | cdef int SWS_CS_SMPTE240M 35 | cdef int SWS_CS_DEFAULT 36 | 37 | cdef SwsContext* sws_getContext( 38 | int src_width, 39 | int src_height, 40 | AVPixelFormat src_format, 41 | int dst_width, 42 | int dst_height, 43 | AVPixelFormat dst_format, 44 | int flags, 45 | SwsFilter *src_filter, 46 | SwsFilter *dst_filter, 47 | double *param, 48 | ) 49 | 50 | cdef int sws_scale( 51 | SwsContext *ctx, 52 | unsigned char **src_slice, 53 | int *src_stride, 54 | int src_slice_y, 55 | int src_slice_h, 56 | unsigned char **dst_slice, 57 | int *dst_stride, 58 | ) 59 | 60 | cdef void sws_freeContext(SwsContext *ctx) 61 | 62 | cdef SwsContext *sws_getCachedContext( 63 | SwsContext *context, 64 | int src_width, 65 | int src_height, 66 | AVPixelFormat src_format, 67 | int dst_width, 68 | int dst_height, 69 | AVPixelFormat dst_format, 70 | int flags, 71 | SwsFilter *src_filter, 72 | SwsFilter *dst_filter, 73 | double *param, 74 | ) 75 | 76 | cdef int* sws_getCoefficients(int colorspace) 77 | 78 | cdef int sws_getColorspaceDetails( 79 | SwsContext *context, 80 | int **inv_table, 81 | int *srcRange, 82 | int **table, 83 | int *dstRange, 84 | int *brightness, 85 | int *contrast, 86 | int *saturation 87 | ) 88 | 89 | cdef int sws_setColorspaceDetails( 90 | SwsContext *context, 91 | const int inv_table[4], 92 | int srcRange, 93 | const int table[4], 94 | int dstRange, 95 | int brightness, 96 | int contrast, 97 | int saturation 98 | ) 99 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=77.0", "cython>=3.1.0a1,<4"] 3 | 4 | [project] 5 | name = "av" 6 | description = "Pythonic bindings for FFmpeg's libraries." 7 | readme = "README.md" 8 | license = "BSD-3-Clause" 9 | authors = [ 10 | {name = "WyattBlue", email = "wyattblue@auto-editor.com"}, 11 | {name = "Jeremy Lainé", email = "jeremy.laine@m4x.org"}, 12 | ] 13 | requires-python = ">=3.9" 14 | classifiers = [ 15 | "Development Status :: 5 - Production/Stable", 16 | "Intended Audience :: Developers", 17 | "Natural Language :: English", 18 | "Operating System :: MacOS :: MacOS X", 19 | "Operating System :: POSIX", 20 | "Operating System :: Unix", 21 | "Operating System :: Microsoft :: Windows", 22 | "Programming Language :: Cython", 23 | "Programming Language :: Python :: 3.9", 24 | "Programming Language :: Python :: 3.10", 25 | "Programming Language :: Python :: 3.11", 26 | "Programming Language :: Python :: 3.12", 27 | "Programming Language :: Python :: 3.13", 28 | "Topic :: Software Development :: Libraries :: Python Modules", 29 | "Topic :: Multimedia :: Sound/Audio", 30 | "Topic :: Multimedia :: Sound/Audio :: Conversion", 31 | "Topic :: Multimedia :: Video", 32 | "Topic :: Multimedia :: Video :: Conversion", 33 | ] 34 | dynamic = ["version"] 35 | 36 | [tool.setuptools] 37 | zip-safe = false 38 | 39 | [tool.setuptools.dynamic] 40 | version = {attr = "av.about.__version__"} 41 | 42 | [project.urls] 43 | "Bug Tracker" = "https://github.com/PyAV-Org/PyAV/discussions/new?category=4-bugs" 44 | "Source Code" = "https://github.com/PyAV-Org/PyAV" 45 | homepage = "https://pyav.basswood-io.com" 46 | 47 | [project.scripts] 48 | "pyav" = "av.__main__:main" 49 | 50 | [tool.isort] 51 | profile = "black" 52 | known_first_party = ["av"] 53 | skip = ["av/__init__.py"] 54 | -------------------------------------------------------------------------------- /scripts/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ ! "$_PYAV_ACTIVATED" ]]; then 4 | export here="$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd)" 5 | source "$here/activate.sh" 6 | fi 7 | 8 | cd "$PYAV_ROOT" 9 | 10 | export PATH="$PYAV_VENV/vendor/$PYAV_LIBRARY_SLUG/bin:$PATH" 11 | 12 | 13 | env | grep PYAV | sort 14 | echo 15 | 16 | echo PKG_CONFIG_PATH: $PKG_CONFIG_PATH 17 | echo LD_LIBRARY_PATH: $LD_LIBRARY_PATH 18 | echo 19 | 20 | which ffmpeg || exit 2 21 | ffmpeg -version || exit 3 22 | echo 23 | 24 | $PYAV_PIP install -U --pre cython setuptools 2> /dev/null 25 | "$PYAV_PYTHON" scripts/comptime.py 26 | "$PYAV_PYTHON" setup.py config build_ext --inplace || exit 1 27 | -------------------------------------------------------------------------------- /scripts/build-deps: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ ! "$_PYAV_ACTIVATED" ]]; then 4 | export here="$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd)" 5 | source "$here/activate.sh" 6 | fi 7 | 8 | cd "$PYAV_ROOT" 9 | 10 | # Skip the rest of the build if it already exists. 11 | if [[ -e "$PYAV_LIBRARY_PREFIX/bin/ffmpeg" ]]; then 12 | echo "We have a cached build of ffmpeg-$PYAV_LIBRARY; skipping re-build." 13 | exit 0 14 | fi 15 | 16 | mkdir -p "$PYAV_LIBRARY_ROOT" 17 | mkdir -p "$PYAV_LIBRARY_PREFIX" 18 | 19 | # Add CUDA support if available 20 | CONFFLAGS_NVIDIA="" 21 | if [[ -e /usr/local/cuda ]]; then 22 | # Get Nvidia headers for ffmpeg 23 | cd $PYAV_LIBRARY_ROOT 24 | if [[ ! -e "$PYAV_LIBRARY_ROOT/nv-codec-headers" ]]; then 25 | git clone https://github.com/FFmpeg/nv-codec-headers.git 26 | cd nv-codec-headers 27 | make -j4 28 | make PREFIX="$PYAV_LIBRARY_PREFIX" install 29 | fi 30 | 31 | PKG_CONFIG_PATH="/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH" 32 | CONFFLAGS_NVIDIA="--enable-cuda-nvcc \ 33 | --enable-nonfree \ 34 | --enable-libnpp \ 35 | --extra-cflags=-I/usr/local/cuda/include \ 36 | --extra-ldflags=-L/usr/local/cuda/lib64" 37 | else 38 | echo "WARNING: Did not find cuda libraries in /usr/local/cuda..." 39 | echo " Building without NVIDIA NVENC/NVDEC support" 40 | fi 41 | 42 | cd "$PYAV_LIBRARY_ROOT" 43 | 44 | # Download and expand the source. 45 | if [[ ! -d $PYAV_LIBRARY ]]; then 46 | url="https://ffmpeg.org/releases/$PYAV_LIBRARY.tar.gz" 47 | echo Downloading $url 48 | curl "$url" --output ${PYAV_LIBRARY}.tar.gz || exit 1 49 | tar -xzf $PYAV_LIBRARY.tar.gz 50 | rm $PYAV_LIBRARY.tar.gz 51 | echo 52 | fi 53 | cd $PYAV_LIBRARY 54 | 55 | echo ./configure 56 | ./configure \ 57 | --disable-doc \ 58 | --disable-static \ 59 | --disable-stripping \ 60 | --enable-debug=3 \ 61 | --enable-gpl \ 62 | --enable-version3 \ 63 | --enable-libx264 \ 64 | --enable-libxml2 \ 65 | --enable-shared \ 66 | --enable-sse \ 67 | --enable-avx \ 68 | --enable-avx2 \ 69 | $CONFFLAGS_NVIDIA \ 70 | --prefix="$PYAV_LIBRARY_PREFIX" \ 71 | || exit 2 72 | echo 73 | 74 | echo make 75 | make -j4 || exit 3 76 | echo 77 | 78 | echo make install 79 | make install || exit 4 80 | echo 81 | 82 | echo Build products: 83 | cd ~ 84 | find "$PYAV_LIBRARY_PREFIX" -name '*libav*' 85 | -------------------------------------------------------------------------------- /scripts/comptime.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | def replace_in_file(file_path): 5 | try: 6 | with open(file_path, "r", encoding="utf-8") as file: 7 | content = file.read() 8 | 9 | modified_content = content.replace("# [FFMPEG6] ", "") 10 | 11 | with open(file_path, "w") as file: 12 | file.write(modified_content) 13 | except UnicodeDecodeError: 14 | pass 15 | 16 | 17 | def process_directory(directory): 18 | for root, dirs, files in os.walk(directory): 19 | for file in files: 20 | file_path = os.path.join(root, file) 21 | replace_in_file(file_path) 22 | 23 | 24 | version = os.environ.get("PYAV_LIBRARY") 25 | if version is None: 26 | is_6 = sys.argv[1].startswith("6") 27 | else: 28 | is_6 = version.startswith("ffmpeg-6") 29 | 30 | if is_6: 31 | process_directory("av") 32 | process_directory("include") 33 | -------------------------------------------------------------------------------- /scripts/fetch-vendor.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import logging 3 | import json 4 | import os 5 | import platform 6 | import struct 7 | import subprocess 8 | 9 | 10 | def get_platform(): 11 | system = platform.system() 12 | machine = platform.machine() 13 | if system == "Linux": 14 | if platform.libc_ver()[0] == "glibc": 15 | return f"manylinux_{machine}" 16 | else: 17 | return f"musllinux_{machine}" 18 | elif system == "Darwin": 19 | # cibuildwheel sets ARCHFLAGS: 20 | # https://github.com/pypa/cibuildwheel/blob/5255155bc57eb6224354356df648dc42e31a0028/cibuildwheel/macos.py#L207-L220 21 | if "ARCHFLAGS" in os.environ: 22 | machine = os.environ["ARCHFLAGS"].split()[1] 23 | return f"macosx_{machine}" 24 | elif system == "Windows": 25 | if struct.calcsize("P") * 8 == 64: 26 | return "win_amd64" 27 | else: 28 | return "win32" 29 | else: 30 | raise Exception(f"Unsupported system {system}") 31 | 32 | 33 | parser = argparse.ArgumentParser(description="Fetch and extract tarballs") 34 | parser.add_argument("destination_dir") 35 | parser.add_argument("--cache-dir", default="tarballs") 36 | parser.add_argument("--config-file", default=os.path.splitext(__file__)[0] + ".json") 37 | args = parser.parse_args() 38 | logging.basicConfig(level=logging.INFO) 39 | 40 | with open(args.config_file) as fp: 41 | config = json.load(fp) 42 | 43 | # ensure destination directory exists 44 | logging.info(f"Creating directory {args.destination_dir}") 45 | if not os.path.exists(args.destination_dir): 46 | os.makedirs(args.destination_dir) 47 | 48 | tarball_url = config["url"].replace("{platform}", get_platform()) 49 | 50 | # download tarball 51 | tarball_name = tarball_url.split("/")[-1] 52 | tarball_file = os.path.join(args.cache_dir, tarball_name) 53 | if not os.path.exists(tarball_file): 54 | logging.info(f"Downloading {tarball_url}") 55 | if not os.path.exists(args.cache_dir): 56 | os.mkdir(args.cache_dir) 57 | subprocess.check_call( 58 | ["curl", "--location", "--output", tarball_file, "--silent", tarball_url] 59 | ) 60 | 61 | # extract tarball 62 | logging.info(f"Extracting {tarball_name}") 63 | subprocess.check_call(["tar", "-C", args.destination_dir, "-xf", tarball_file]) 64 | -------------------------------------------------------------------------------- /scripts/ffmpeg-7.0.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://github.com/PyAV-Org/pyav-ffmpeg/releases/download/7.0.2-1/ffmpeg-{platform}.tar.gz" 3 | } -------------------------------------------------------------------------------- /scripts/ffmpeg-7.1.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://github.com/PyAV-Org/pyav-ffmpeg/releases/download/7.1.1-4/ffmpeg-{platform}.tar.gz" 3 | } 4 | -------------------------------------------------------------------------------- /scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit as soon as something errors. 4 | set -e 5 | 6 | if [[ ! "$_PYAV_ACTIVATED" ]]; then 7 | export here="$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd)" 8 | source "$here/activate.sh" 9 | fi 10 | 11 | cd "$PYAV_ROOT" 12 | 13 | 14 | TESTSUITE="${1-main}" 15 | 16 | istest() { 17 | [[ "$TESTSUITE" == all || "$TESTSUITE" == "$1" ]] 18 | return $? 19 | } 20 | 21 | $PYAV_PYTHON -c "import av; print(f'PyAV: {av.__version__}'); print(f'FFMPEG: {av.ffmpeg_version_info}')" 22 | 23 | if istest main; then 24 | $PYAV_PYTHON -m pytest 25 | fi 26 | 27 | if istest examples; then 28 | for name in $(find examples -name '*.py'); do 29 | echo 30 | echo === $name 31 | cd "$PYAV_ROOT" 32 | mkdir -p "sandbox/$1" 33 | cd "sandbox/$1" 34 | if ! python "$PYAV_ROOT/$name"; then 35 | echo FAILED $name with code $? 36 | exit $? 37 | fi 38 | done 39 | fi 40 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyAV-Org/PyAV/633f237a6916d17b85fe937f95e28af651fbaf0e/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_audioformat.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import pytest 4 | 5 | from av import AudioFormat 6 | 7 | 8 | def test_s16_inspection() -> None: 9 | fmt = AudioFormat("s16") 10 | postfix = "le" if sys.byteorder == "little" else "be" 11 | 12 | assert fmt.name == "s16" 13 | assert not fmt.is_planar 14 | assert fmt.bits == 16 15 | assert fmt.bytes == 2 16 | assert fmt.container_name == "s16" + postfix 17 | assert fmt.planar.name == "s16p" 18 | assert fmt.packed is fmt 19 | 20 | 21 | def test_s32p_inspection() -> None: 22 | fmt = AudioFormat("s32p") 23 | assert fmt.name == "s32p" 24 | assert fmt.is_planar 25 | assert fmt.bits == 32 26 | assert fmt.bytes == 4 27 | 28 | pytest.raises(ValueError, lambda: fmt.container_name) 29 | -------------------------------------------------------------------------------- /tests/test_audiolayout.py: -------------------------------------------------------------------------------- 1 | from av import AudioLayout 2 | 3 | 4 | def _test_stereo(layout: AudioLayout) -> None: 5 | assert layout.name == "stereo" 6 | assert layout.nb_channels == 2 7 | assert repr(layout) == "" 8 | 9 | # Re-enable when FFmpeg 6.0 is dropped. 10 | 11 | # assert layout.channels[0].name == "FL" 12 | # assert layout.channels[0].description == "front left" 13 | # assert repr(layout.channels[0]) == "" 14 | # assert layout.channels[1].name == "FR" 15 | # assert layout.channels[1].description == "front right" 16 | # assert repr(layout.channels[1]) == "" 17 | 18 | 19 | def test_stereo_from_str() -> None: 20 | layout = AudioLayout("stereo") 21 | _test_stereo(layout) 22 | 23 | 24 | def test_stereo_from_layout() -> None: 25 | layout2 = AudioLayout(AudioLayout("stereo")) 26 | _test_stereo(layout2) 27 | -------------------------------------------------------------------------------- /tests/test_codec.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from av import AudioFormat, Codec, VideoFormat, codecs_available 4 | from av.codec.codec import UnknownCodecError 5 | 6 | 7 | def test_codec_bogus() -> None: 8 | with pytest.raises(UnknownCodecError): 9 | Codec("bogus123") 10 | with pytest.raises(UnknownCodecError): 11 | Codec("bogus123", "w") 12 | 13 | 14 | def test_codec_mpeg4_decoder() -> None: 15 | c = Codec("mpeg4") 16 | 17 | assert c.name == "mpeg4" 18 | assert c.long_name == "MPEG-4 part 2" 19 | assert c.type == "video" 20 | assert c.id in (12, 13) 21 | assert c.is_decoder 22 | assert not c.is_encoder 23 | assert c.delay 24 | 25 | assert c.audio_formats is None and c.audio_rates is None 26 | 27 | # formats = c.video_formats 28 | # assert formats 29 | # assert isinstance(formats[0], VideoFormat) 30 | # assert any(f.name == "yuv420p" for f in formats) 31 | 32 | assert c.frame_rates is None 33 | 34 | 35 | def test_codec_mpeg4_encoder() -> None: 36 | c = Codec("mpeg4", "w") 37 | assert c.name == "mpeg4" 38 | assert c.long_name == "MPEG-4 part 2" 39 | assert c.type == "video" 40 | assert c.id in (12, 13) 41 | assert c.is_encoder 42 | assert not c.is_decoder 43 | assert c.delay 44 | 45 | assert c.audio_formats is None and c.audio_rates is None 46 | 47 | formats = c.video_formats 48 | assert formats 49 | assert isinstance(formats[0], VideoFormat) 50 | assert any(f.name == "yuv420p" for f in formats) 51 | assert c.frame_rates is None 52 | 53 | 54 | def test_codec_opus_decoder() -> None: 55 | c = Codec("opus") 56 | 57 | assert c.name == "opus" 58 | assert c.long_name == "Opus" 59 | assert c.type == "audio" 60 | assert c.is_decoder 61 | assert not c.is_encoder 62 | assert c.delay 63 | 64 | assert c.audio_formats is None and c.audio_rates is None 65 | assert c.video_formats is None and c.frame_rates is None 66 | 67 | 68 | def test_codec_opus_encoder() -> None: 69 | c = Codec("opus", "w") 70 | assert c.name in ("opus", "libopus") 71 | assert c.canonical_name == "opus" 72 | assert c.long_name in ("Opus", "libopus Opus") 73 | assert c.type == "audio" 74 | assert c.is_encoder 75 | assert not c.is_decoder 76 | assert c.delay 77 | 78 | # audio 79 | formats = c.audio_formats 80 | assert formats 81 | assert isinstance(formats[0], AudioFormat) 82 | assert any(f.name in ("flt", "fltp") for f in formats) 83 | 84 | assert c.audio_rates is not None 85 | assert 48000 in c.audio_rates 86 | 87 | assert c.video_formats is None and c.frame_rates is None 88 | 89 | 90 | def test_codecs_available() -> None: 91 | assert codecs_available 92 | -------------------------------------------------------------------------------- /tests/test_colorspace.py: -------------------------------------------------------------------------------- 1 | import av 2 | from av.video.reformatter import ColorRange, Colorspace 3 | 4 | from .common import fate_suite 5 | 6 | 7 | def test_penguin_joke() -> None: 8 | container = av.open( 9 | fate_suite("amv/MTV_high_res_320x240_sample_Penguin_Joke_MTV_from_WMV.amv") 10 | ) 11 | stream = container.streams.video[0] 12 | 13 | assert stream.codec_context.color_range == 2 14 | assert stream.codec_context.color_range == ColorRange.JPEG 15 | 16 | assert stream.codec_context.color_primaries == 2 17 | assert stream.codec_context.color_trc == 2 18 | 19 | assert stream.codec_context.colorspace == 5 20 | assert stream.codec_context.colorspace == Colorspace.ITU601 21 | 22 | for frame in container.decode(stream): 23 | assert frame.color_range == ColorRange.JPEG # a.k.a "pc" 24 | assert frame.colorspace == Colorspace.ITU601 25 | return 26 | 27 | 28 | def test_sky_timelapse() -> None: 29 | container = av.open( 30 | av.datasets.curated("pexels/time-lapse-video-of-night-sky-857195.mp4") 31 | ) 32 | stream = container.streams.video[0] 33 | 34 | assert stream.disposition == av.stream.Disposition.default 35 | 36 | assert stream.codec_context.color_range == 1 37 | assert stream.codec_context.color_range == ColorRange.MPEG 38 | assert stream.codec_context.color_primaries == 1 39 | assert stream.codec_context.color_trc == 1 40 | assert stream.codec_context.colorspace == 1 41 | -------------------------------------------------------------------------------- /tests/test_containerformat.py: -------------------------------------------------------------------------------- 1 | from av import ContainerFormat, formats_available, open 2 | 3 | 4 | def test_matroska() -> None: 5 | with open("test.mkv", "w") as container: 6 | assert container.default_video_codec != "none" 7 | assert container.default_audio_codec != "none" 8 | assert container.default_subtitle_codec == "ass" 9 | assert "ass" in container.supported_codecs 10 | 11 | fmt = ContainerFormat("matroska") 12 | assert fmt.is_input and fmt.is_output 13 | assert fmt.name == "matroska" 14 | assert fmt.long_name == "Matroska" 15 | assert "mkv" in fmt.extensions 16 | assert not fmt.no_file 17 | 18 | 19 | def test_mov() -> None: 20 | with open("test.mov", "w") as container: 21 | assert container.default_video_codec != "none" 22 | assert container.default_audio_codec != "none" 23 | assert container.default_subtitle_codec == "none" 24 | assert "h264" in container.supported_codecs 25 | 26 | fmt = ContainerFormat("mov") 27 | assert fmt.is_input and fmt.is_output 28 | assert fmt.name == "mov" 29 | assert fmt.long_name == "QuickTime / MOV" 30 | assert "mov" in fmt.extensions 31 | assert not fmt.no_file 32 | 33 | 34 | def test_gif() -> None: 35 | with open("test.gif", "w") as container: 36 | assert container.default_video_codec == "gif" 37 | assert container.default_audio_codec == "none" 38 | assert container.default_subtitle_codec == "none" 39 | assert "gif" in container.supported_codecs 40 | 41 | 42 | def test_stream_segment() -> None: 43 | # This format goes by two names, check both. 44 | fmt = ContainerFormat("stream_segment") 45 | assert not fmt.is_input and fmt.is_output 46 | assert fmt.name == "stream_segment" 47 | assert fmt.long_name == "streaming segment muxer" 48 | assert fmt.extensions == set() 49 | assert fmt.no_file 50 | 51 | fmt = ContainerFormat("ssegment") 52 | assert not fmt.is_input and fmt.is_output 53 | assert fmt.name == "ssegment" 54 | assert fmt.long_name == "streaming segment muxer" 55 | assert fmt.extensions == set() 56 | assert fmt.no_file 57 | 58 | 59 | def test_formats_available() -> None: 60 | assert formats_available 61 | -------------------------------------------------------------------------------- /tests/test_dictionary.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from av.dictionary import Dictionary 4 | 5 | 6 | def test_dictionary() -> None: 7 | d = Dictionary() 8 | d["key"] = "value" 9 | 10 | assert d["key"] == "value" 11 | assert "key" in d 12 | assert len(d) == 1 13 | assert list(d) == ["key"] 14 | 15 | assert d.pop("key") == "value" 16 | pytest.raises(KeyError, d.pop, "key") 17 | assert len(d) == 0 18 | -------------------------------------------------------------------------------- /tests/test_doctests.py: -------------------------------------------------------------------------------- 1 | import doctest 2 | import pkgutil 3 | import re 4 | from unittest import TestCase 5 | 6 | import av 7 | import av.datasets 8 | 9 | 10 | def fix_doctests(suite): 11 | for case in suite._tests: 12 | # Add some more flags. 13 | case._dt_optionflags = ( 14 | (case._dt_optionflags or 0) 15 | | doctest.IGNORE_EXCEPTION_DETAIL 16 | | doctest.ELLIPSIS 17 | | doctest.NORMALIZE_WHITESPACE 18 | ) 19 | 20 | case._dt_test.globs["av"] = av 21 | case._dt_test.globs["video_path"] = av.datasets.curated( 22 | "pexels/time-lapse-video-of-night-sky-857195.mp4" 23 | ) 24 | 25 | for example in case._dt_test.examples: 26 | # Remove b prefix from strings. 27 | if example.want.startswith("b'"): 28 | example.want = example.want[1:] 29 | 30 | 31 | def register_doctests(mod): 32 | if isinstance(mod, str): 33 | mod = __import__(mod, fromlist=[""]) 34 | 35 | try: 36 | suite = doctest.DocTestSuite(mod) 37 | except ValueError: 38 | return 39 | 40 | fix_doctests(suite) 41 | 42 | cls_name = "Test" + "".join(x.title() for x in mod.__name__.split(".")) 43 | cls = type(cls_name, (TestCase,), {}) 44 | 45 | for test in suite._tests: 46 | 47 | def func(self): 48 | return test.runTest() 49 | 50 | name = str("test_" + re.sub("[^a-zA-Z0-9]+", "_", test.id()).strip("_")) 51 | func.__name__ = name 52 | setattr(cls, name, func) 53 | 54 | globals()[cls_name] = cls 55 | 56 | 57 | for importer, mod_name, ispkg in pkgutil.walk_packages( 58 | path=av.__path__, prefix=av.__name__ + ".", onerror=lambda x: None 59 | ): 60 | register_doctests(mod_name) 61 | -------------------------------------------------------------------------------- /tests/test_errors.py: -------------------------------------------------------------------------------- 1 | import errno 2 | from traceback import format_exception_only 3 | 4 | import av 5 | 6 | from .common import is_windows 7 | 8 | 9 | def test_stringify() -> None: 10 | for cls in (av.ValueError, av.FileNotFoundError, av.DecoderNotFoundError): 11 | e = cls(1, "foo") 12 | assert f"{e}" == "[Errno 1] foo" 13 | assert f"{e!r}" == f"{cls.__name__}(1, 'foo')" 14 | assert ( 15 | format_exception_only(cls, e)[-1] 16 | == f"av.error.{cls.__name__}: [Errno 1] foo\n" 17 | ) 18 | 19 | for cls in (av.ValueError, av.FileNotFoundError, av.DecoderNotFoundError): 20 | e = cls(1, "foo", "bar.txt") 21 | assert f"{e}" == "[Errno 1] foo: 'bar.txt'" 22 | assert f"{e!r}" == f"{cls.__name__}(1, 'foo', 'bar.txt')" 23 | assert ( 24 | format_exception_only(cls, e)[-1] 25 | == f"av.error.{cls.__name__}: [Errno 1] foo: 'bar.txt'\n" 26 | ) 27 | 28 | 29 | def test_bases() -> None: 30 | assert issubclass(av.ValueError, ValueError) 31 | assert issubclass(av.ValueError, av.FFmpegError) 32 | 33 | assert issubclass(av.FileNotFoundError, FileNotFoundError) 34 | assert issubclass(av.FileNotFoundError, OSError) 35 | assert issubclass(av.FileNotFoundError, av.FFmpegError) 36 | 37 | 38 | def test_filenotfound(): 39 | """Catch using builtin class on Python 3.3""" 40 | try: 41 | av.open("does not exist") 42 | except FileNotFoundError as e: 43 | assert e.errno == errno.ENOENT 44 | if is_windows: 45 | assert e.strerror in ( 46 | "Error number -2 occurred", 47 | "No such file or directory", 48 | ) 49 | else: 50 | assert e.strerror == "No such file or directory" 51 | assert e.filename == "does not exist" 52 | else: 53 | assert False, "No exception raised!" 54 | 55 | 56 | def test_buffertoosmall() -> None: 57 | """Throw an exception from an enum.""" 58 | 59 | BUFFER_TOO_SMALL = 1397118274 60 | try: 61 | av.error.err_check(-BUFFER_TOO_SMALL) 62 | except av.error.BufferTooSmallError as e: 63 | assert e.errno == BUFFER_TOO_SMALL 64 | else: 65 | assert False, "No exception raised!" 66 | -------------------------------------------------------------------------------- /tests/test_logging.py: -------------------------------------------------------------------------------- 1 | import errno 2 | import logging 3 | import threading 4 | 5 | import av.error 6 | import av.logging 7 | 8 | 9 | def do_log(message: str) -> None: 10 | av.logging.log(av.logging.INFO, "test", message) 11 | 12 | 13 | def test_adapt_level() -> None: 14 | assert av.logging.adapt_level(av.logging.ERROR) == logging.ERROR 15 | assert av.logging.adapt_level(av.logging.WARNING) == logging.WARNING 16 | assert ( 17 | av.logging.adapt_level((av.logging.WARNING + av.logging.ERROR) // 2) 18 | == logging.WARNING 19 | ) 20 | 21 | 22 | def test_threaded_captures() -> None: 23 | av.logging.set_level(av.logging.VERBOSE) 24 | 25 | with av.logging.Capture(local=True) as logs: 26 | do_log("main") 27 | thread = threading.Thread(target=do_log, args=("thread",)) 28 | thread.start() 29 | thread.join() 30 | 31 | assert (av.logging.INFO, "test", "main") in logs 32 | av.logging.set_level(None) 33 | 34 | 35 | def test_global_captures() -> None: 36 | av.logging.set_level(av.logging.VERBOSE) 37 | 38 | with av.logging.Capture(local=False) as logs: 39 | do_log("main") 40 | thread = threading.Thread(target=do_log, args=("thread",)) 41 | thread.start() 42 | thread.join() 43 | 44 | assert (av.logging.INFO, "test", "main") in logs 45 | assert (av.logging.INFO, "test", "thread") in logs 46 | av.logging.set_level(None) 47 | 48 | 49 | def test_repeats() -> None: 50 | av.logging.set_level(av.logging.VERBOSE) 51 | 52 | with av.logging.Capture() as logs: 53 | do_log("foo") 54 | do_log("foo") 55 | do_log("bar") 56 | do_log("bar") 57 | do_log("bar") 58 | do_log("baz") 59 | 60 | logs = [log for log in logs if log[1] == "test"] 61 | 62 | assert logs == [ 63 | (av.logging.INFO, "test", "foo"), 64 | (av.logging.INFO, "test", "foo"), 65 | (av.logging.INFO, "test", "bar"), 66 | (av.logging.INFO, "test", "bar (repeated 2 more times)"), 67 | (av.logging.INFO, "test", "baz"), 68 | ] 69 | 70 | av.logging.set_level(None) 71 | 72 | 73 | def test_error() -> None: 74 | av.logging.set_level(av.logging.VERBOSE) 75 | 76 | log = (av.logging.ERROR, "test", "This is a test.") 77 | av.logging.log(*log) 78 | try: 79 | av.error.err_check(-errno.EPERM) 80 | except av.error.PermissionError as e: 81 | assert e.log == log 82 | else: 83 | assert False 84 | 85 | av.logging.set_level(None) 86 | -------------------------------------------------------------------------------- /tests/test_open.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import av 4 | 5 | from .common import fate_suite 6 | 7 | 8 | def test_path_input() -> None: 9 | path = Path(fate_suite("h264/interlaced_crop.mp4")) 10 | assert isinstance(path, Path) 11 | 12 | container = av.open(path) 13 | assert type(container) is av.container.InputContainer 14 | 15 | 16 | def test_str_input() -> None: 17 | path = fate_suite("h264/interlaced_crop.mp4") 18 | assert type(path) is str 19 | 20 | container = av.open(path) 21 | assert type(container) is av.container.InputContainer 22 | 23 | 24 | def test_path_output() -> None: 25 | path = Path(fate_suite("h264/interlaced_crop.mp4")) 26 | assert isinstance(path, Path) 27 | 28 | container = av.open(path, "w") 29 | assert type(container) is av.container.OutputContainer 30 | 31 | 32 | def test_str_output() -> None: 33 | path = fate_suite("h264/interlaced_crop.mp4") 34 | assert type(path) is str 35 | 36 | container = av.open(path, "w") 37 | assert type(container) is av.container.OutputContainer 38 | -------------------------------------------------------------------------------- /tests/test_options.py: -------------------------------------------------------------------------------- 1 | from av import ContainerFormat 2 | from av.option import Option, OptionType 3 | 4 | 5 | class TestOptions: 6 | def test_mov_options(self) -> None: 7 | mov = ContainerFormat("mov") 8 | options = mov.descriptor.options # type: ignore 9 | by_name = {opt.name: opt for opt in options} 10 | 11 | opt = by_name.get("use_absolute_path") 12 | 13 | assert isinstance(opt, Option) 14 | assert opt.name == "use_absolute_path" 15 | 16 | # This was not a good option to actually test. 17 | assert opt.type in (OptionType.BOOL, OptionType.INT) 18 | -------------------------------------------------------------------------------- /tests/test_packet.py: -------------------------------------------------------------------------------- 1 | import av 2 | 3 | from .common import fate_suite 4 | 5 | 6 | class TestProperties: 7 | def test_is_keyframe(self) -> None: 8 | with av.open(fate_suite("h264/interlaced_crop.mp4")) as container: 9 | stream = container.streams.video[0] 10 | for i, packet in enumerate(container.demux(stream)): 11 | if i in (0, 21, 45, 69, 93, 117): 12 | assert packet.is_keyframe 13 | else: 14 | assert not packet.is_keyframe 15 | 16 | def test_is_corrupt(self) -> None: 17 | with av.open(fate_suite("mov/white_zombie_scrunch-part.mov")) as container: 18 | stream = container.streams.video[0] 19 | for i, packet in enumerate(container.demux(stream)): 20 | if i == 65: 21 | assert packet.is_corrupt 22 | else: 23 | assert not packet.is_corrupt 24 | 25 | def test_is_discard(self) -> None: 26 | with av.open(fate_suite("mov/mov-1elist-ends-last-bframe.mov")) as container: 27 | stream = container.streams.video[0] 28 | for i, packet in enumerate(container.demux(stream)): 29 | if i == 46: 30 | assert packet.is_discard 31 | else: 32 | assert not packet.is_discard 33 | 34 | def test_is_disposable(self) -> None: 35 | with av.open(fate_suite("hap/HAPQA_NoSnappy_127x1.mov")) as container: 36 | stream = container.streams.video[0] 37 | for i, packet in enumerate(container.demux(stream)): 38 | if i == 0: 39 | assert packet.is_disposable 40 | else: 41 | assert not packet.is_disposable 42 | 43 | def test_set_duration(self) -> None: 44 | with av.open(fate_suite("h264/interlaced_crop.mp4")) as container: 45 | for packet in container.demux(): 46 | assert packet.duration is not None 47 | old_duration = packet.duration 48 | packet.duration += 10 49 | 50 | assert packet.duration == old_duration + 10 51 | -------------------------------------------------------------------------------- /tests/test_remux.py: -------------------------------------------------------------------------------- 1 | import av 2 | import av.datasets 3 | 4 | 5 | def test_video_remux() -> None: 6 | input_path = av.datasets.curated("pexels/time-lapse-video-of-night-sky-857195.mp4") 7 | input_ = av.open(input_path) 8 | output = av.open("remuxed.mkv", "w") 9 | 10 | in_stream = input_.streams.video[0] 11 | out_stream = output.add_stream_from_template(in_stream) 12 | 13 | for packet in input_.demux(in_stream): 14 | if packet.dts is None: 15 | continue 16 | 17 | packet.stream = out_stream 18 | output.mux(packet) 19 | 20 | input_.close() 21 | output.close() 22 | 23 | with av.open("remuxed.mkv") as container: 24 | # Assert output is a valid media file 25 | assert len(container.streams.video) == 1 26 | assert len(container.streams.audio) == 0 27 | assert container.streams.video[0].codec.name == "h264" 28 | 29 | packet_count = 0 30 | for packet in container.demux(video=0): 31 | packet_count += 1 32 | 33 | assert packet_count > 50 34 | -------------------------------------------------------------------------------- /tests/test_subtitles.py: -------------------------------------------------------------------------------- 1 | import av 2 | from av.subtitles.subtitle import AssSubtitle, BitmapSubtitle 3 | 4 | from .common import fate_suite 5 | 6 | 7 | class TestSubtitle: 8 | def test_movtext(self) -> None: 9 | path = fate_suite("sub/MovText_capability_tester.mp4") 10 | 11 | subs = [] 12 | with av.open(path) as container: 13 | for packet in container.demux(): 14 | subs.extend(packet.decode()) 15 | 16 | assert len(subs) == 3 17 | 18 | subset = subs[0] 19 | assert subset.format == 1 20 | assert subset.pts == 970000 21 | assert subset.start_display_time == 0 22 | assert subset.end_display_time == 1570 23 | 24 | sub = subset[0] 25 | assert isinstance(sub, AssSubtitle) 26 | assert sub.type == b"ass" 27 | assert sub.text == b"" 28 | assert sub.ass == b"0,0,Default,,0,0,0,,- Test 1.\\N- Test 2." 29 | assert sub.dialogue == b"- Test 1.\n- Test 2." 30 | 31 | def test_vobsub(self) -> None: 32 | path = fate_suite("sub/vobsub.sub") 33 | 34 | subs = [] 35 | with av.open(path) as container: 36 | for packet in container.demux(): 37 | subs.extend(packet.decode()) 38 | 39 | assert len(subs) == 43 40 | 41 | subset = subs[0] 42 | assert subset.format == 0 43 | assert subset.pts == 132499044 44 | assert subset.start_display_time == 0 45 | assert subset.end_display_time == 4960 46 | 47 | sub = subset[0] 48 | assert isinstance(sub, BitmapSubtitle) 49 | assert sub.type == b"bitmap" 50 | assert sub.x == 259 51 | assert sub.y == 379 52 | assert sub.width == 200 53 | assert sub.height == 24 54 | assert sub.nb_colors == 4 55 | 56 | bms = sub.planes 57 | assert len(bms) == 1 58 | assert len(memoryview(bms[0])) == 4800 # type: ignore 59 | 60 | def test_subtitle_flush(self) -> None: 61 | path = fate_suite("sub/MovText_capability_tester.mp4") 62 | 63 | subs = [] 64 | with av.open(path) as container: 65 | stream = container.streams.subtitles[0] 66 | for packet in container.demux(stream): 67 | subs.extend(stream.decode(packet)) 68 | subs.extend(stream.decode()) 69 | 70 | assert len(subs) == 3 71 | -------------------------------------------------------------------------------- /tests/test_timeout.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import time 3 | from http.server import BaseHTTPRequestHandler 4 | from socketserver import TCPServer 5 | 6 | import av 7 | 8 | from .common import TestCase, fate_suite 9 | 10 | PORT = 8002 11 | CONTENT = open(fate_suite("mpeg2/mpeg2_field_encoding.ts"), "rb").read() 12 | # Needs to be long enough for all host OSes to deal. 13 | TIMEOUT = 0.25 14 | DELAY = 4 * TIMEOUT 15 | 16 | 17 | class HttpServer(TCPServer): 18 | allow_reuse_address = True 19 | 20 | def handle_error(self, request: object, client_address: object) -> None: 21 | pass 22 | 23 | 24 | class SlowRequestHandler(BaseHTTPRequestHandler): 25 | def do_GET(self) -> None: 26 | time.sleep(DELAY) 27 | self.send_response(200) 28 | self.send_header("Content-Length", str(len(CONTENT))) 29 | self.end_headers() 30 | self.wfile.write(CONTENT) 31 | 32 | def log_message(self, format: object, *args: object) -> None: 33 | pass 34 | 35 | 36 | class TestTimeout(TestCase): 37 | def setUp(cls) -> None: 38 | cls._server = HttpServer(("", PORT), SlowRequestHandler) 39 | cls._thread = threading.Thread(target=cls._server.handle_request) 40 | cls._thread.daemon = True # Make sure the tests will exit. 41 | cls._thread.start() 42 | 43 | def tearDown(cls) -> None: 44 | cls._thread.join(1) # Can't wait forever or the tests will never exit. 45 | cls._server.server_close() 46 | 47 | def test_no_timeout(self) -> None: 48 | start = time.time() 49 | av.open(f"http://localhost:{PORT}/mpeg2_field_encoding.ts") 50 | duration = time.time() - start 51 | assert duration > DELAY 52 | 53 | def test_open_timeout(self) -> None: 54 | with self.assertRaises(av.ExitError): 55 | start = time.time() 56 | av.open(f"http://localhost:{PORT}/mpeg2_field_encoding.ts", timeout=TIMEOUT) 57 | 58 | duration = time.time() - start 59 | assert duration < DELAY 60 | 61 | def test_open_timeout_2(self) -> None: 62 | with self.assertRaises(av.ExitError): 63 | start = time.time() 64 | av.open( 65 | f"http://localhost:{PORT}/mpeg2_field_encoding.ts", 66 | timeout=(TIMEOUT, None), 67 | ) 68 | 69 | duration = time.time() - start 70 | assert duration < DELAY 71 | --------------------------------------------------------------------------------