pax_global_header00006660000000000000000000000064147573422740014531gustar00rootroot0000000000000052 comment=bc0ca08670b2672fc7a7fc8c1c73f2803f5ff6ec PyAV-14.2.0/000077500000000000000000000000001475734227400124345ustar00rootroot00000000000000PyAV-14.2.0/.github/000077500000000000000000000000001475734227400137745ustar00rootroot00000000000000PyAV-14.2.0/.github/workflows/000077500000000000000000000000001475734227400160315ustar00rootroot00000000000000PyAV-14.2.0/.github/workflows/smoke.yml000066400000000000000000000074641475734227400177050ustar00rootroot00000000000000name: smoke on: push: branches: main paths-ignore: - '**.md' - '**.rst' - '**.txt' pull_request: branches: main paths-ignore: - '**.md' - '**.rst' - '**.txt' jobs: style: runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@v4 - name: Python uses: actions/setup-python@v5 with: python-version: "3.12" - name: Linters run: make lint nix: name: "py-${{ matrix.config.python }} lib-${{ matrix.config.ffmpeg }} ${{matrix.config.os}}" runs-on: ${{ matrix.config.os }} strategy: fail-fast: false matrix: config: - {os: ubuntu-24.04, python: "3.12", ffmpeg: "7.1", extras: true} - {os: ubuntu-24.04, python: "pypy3.10", ffmpeg: "7.1"} - {os: macos-14, python: "3.9", ffmpeg: "7.1"} env: PYAV_PYTHON: python${{ matrix.config.python }} PYAV_LIBRARY: ffmpeg-${{ matrix.config.ffmpeg }} steps: - uses: actions/checkout@v4 name: Checkout - name: Python ${{ matrix.config.python }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.config.python }} - name: OS Packages run: | case ${{ matrix.config.os }} in ubuntu-24.04) sudo apt-get update sudo apt-get install autoconf automake build-essential cmake \ libtool pkg-config nasm zlib1g-dev libvorbis-dev libx264-dev if [[ "${{ matrix.config.extras }}" ]]; then sudo apt-get install doxygen wget fi ;; macos-14) brew install automake libtool nasm libpng libvorbis libvpx opus x264 ;; esac - name: Pip and FFmpeg run: | . scripts/activate.sh ffmpeg-${{ matrix.config.ffmpeg }} scripts/build-deps - name: Build run: | . scripts/activate.sh ffmpeg-${{ matrix.config.ffmpeg }} scripts/build - name: Test run: | . scripts/activate.sh ffmpeg-${{ matrix.config.ffmpeg }} python -m av --version # Assert it can import. make test - name: Docs if: matrix.config.extras run: | . scripts/activate.sh ffmpeg-${{ matrix.config.ffmpeg }} make -C docs html - name: Doctest if: matrix.config.extras run: | . scripts/activate.sh ffmpeg-${{ matrix.config.ffmpeg }} make -C docs test - name: Examples if: matrix.config.extras run: | . scripts/activate.sh ffmpeg-${{ matrix.config.ffmpeg }} scripts/test examples windows: name: "py-${{ matrix.config.python }} lib-${{ matrix.config.ffmpeg }} ${{matrix.config.os}}" runs-on: ${{ matrix.config.os }} strategy: fail-fast: false matrix: config: - {os: windows-latest, python: "3.9", ffmpeg: "7.1"} steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Conda shell: bash run: | . $CONDA/etc/profile.d/conda.sh conda config --set always_yes true conda config --add channels conda-forge conda create -q -n pyav \ cython \ numpy \ pillow \ pytest \ python=${{ matrix.config.python }} \ setuptools - name: Build shell: bash run: | . $CONDA/etc/profile.d/conda.sh conda activate pyav python scripts\\fetch-vendor.py --config-file scripts\\ffmpeg-${{ matrix.config.ffmpeg }}.json $CONDA_PREFIX\\Library python scripts\\comptime.py ${{ matrix.config.ffmpeg }} python setup.py build_ext --inplace --ffmpeg-dir=$CONDA_PREFIX\\Library - name: Test shell: bash run: | . $CONDA/etc/profile.d/conda.sh conda activate pyav python -m pytest PyAV-14.2.0/.github/workflows/tests.yml000066400000000000000000000070201475734227400177150ustar00rootroot00000000000000name: tests on: release: types: [published] workflow_dispatch: jobs: package-source: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.13" - name: Build source package run: | pip install setuptools cython python scripts/fetch-vendor.py --config-file scripts/ffmpeg-7.1.json /tmp/vendor PKG_CONFIG_PATH=/tmp/vendor/lib/pkgconfig python setup.py sdist - name: Upload source package uses: actions/upload-artifact@v4 with: name: dist-source path: dist/ package-wheel: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: include: - os: macos-14 arch: arm64 - os: macos-13 arch: x86_64 - os: ubuntu-24.04-arm arch: aarch64 - os: ubuntu-24.04 arch: i686 - os: ubuntu-24.04 arch: x86_64 - os: windows-latest arch: AMD64 steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.13" - name: Install packages if: matrix.os == 'macos-13' run: | brew update brew install pkg-config - name: Set Minimum MacOS Target if: matrix.os == 'macos-13' || matrix.os == 'macos-14' run: | echo "MACOSX_DEPLOYMENT_TARGET=12.0" >> $GITHUB_ENV - name: Build wheels env: CIBW_ARCHS: ${{ matrix.arch }} CIBW_BEFORE_ALL_LINUX: yum install -y alsa-lib libxcb CIBW_BEFORE_BUILD: python scripts/fetch-vendor.py --config-file scripts/ffmpeg-7.1.json /tmp/vendor CIBW_BEFORE_BUILD_MACOS: python scripts/fetch-vendor.py --config-file scripts/ffmpeg-7.1.json /tmp/vendor CIBW_BEFORE_BUILD_WINDOWS: python scripts\fetch-vendor.py --config-file scripts\ffmpeg-7.1.json C:\cibw\vendor CIBW_ENVIRONMENT_LINUX: LD_LIBRARY_PATH=/tmp/vendor/lib:$LD_LIBRARY_PATH PKG_CONFIG_PATH=/tmp/vendor/lib/pkgconfig CIBW_ENVIRONMENT_MACOS: PKG_CONFIG_PATH=/tmp/vendor/lib/pkgconfig LDFLAGS=-headerpad_max_install_names CIBW_ENVIRONMENT_WINDOWS: INCLUDE=C:\\cibw\\vendor\\include LIB=C:\\cibw\\vendor\\lib PYAV_SKIP_TESTS=unicode_filename CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: delvewheel repair --add-path C:\cibw\vendor\bin -w {dest_dir} {wheel} CIBW_SKIP: "pp* *-musllinux*" CIBW_TEST_COMMAND: mv {project}/av {project}/av.disabled && python -m pytest {package}/tests && mv {project}/av.disabled {project}/av CIBW_TEST_REQUIRES: pytest numpy # skip tests when there are no binary wheels of numpy CIBW_TEST_SKIP: "*_i686" run: | pip install cibuildwheel delvewheel cibuildwheel --output-dir dist shell: bash - name: Upload wheels uses: actions/upload-artifact@v4 with: name: dist-${{ matrix.os }}-${{ matrix.arch }} path: dist/ publish: runs-on: ubuntu-latest needs: [package-source, package-wheel] steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 with: merge-multiple: true path: dist/ - name: Publish to PyPI if: github.event_name == 'release' && github.event.action == 'published' uses: pypa/gh-action-pypi-publish@release/v1 with: user: __token__ password: ${{ secrets.PYPI_TOKEN }} PyAV-14.2.0/.gitignore000066400000000000000000000005311475734227400144230ustar00rootroot00000000000000# General *~ .DS_Store .nfs.* ._* # Environment /.eggs /tmp /vendor /venv /venvs # Build products *.dll *.egg-info *.lib *.pyc *.so /*.sdf /*.sln /*.suo /av/**/*.exp /av/**/*.lib /av/**/*.pdb /av/**/*.pyd /build /dist /docs/_build /ipch /msvc-projects /src /docs/_ffmpeg # Testing. *.spyderproject .idea /sandbox /tests/assets /tests/samples PyAV-14.2.0/AUTHORS.py000066400000000000000000000060601475734227400141350ustar00rootroot00000000000000import math import subprocess print( """Contributors ============ All contributors (by number of commits): """ ) email_map = { # Maintainers. "git@mikeboers.com": "github@mikeboers.com", "mboers@keypics.com": "github@mikeboers.com", "mikeb@loftysky.com": "github@mikeboers.com", "mikeb@markmedia.co": "github@mikeboers.com", "westernx@mikeboers.com": "github@mikeboers.com", # Junk. "mark@mark-VirtualBox.(none)": None, # Aliases. "a.davoudi@aut.ac.ir": "davoudialireza@gmail.com", "tcaswell@bnl.gov": "tcaswell@gmail.com", "xxr3376@gmail.com": "xxr@megvii.com", "dallan@pha.jhu.edu": "daniel.b.allan@gmail.com", "61652821+laggykiller@users.noreply.github.com": "chaudominic2@gmail.com", } name_map = { "caspervdw@gmail.com": "Casper van der Wel", "daniel.b.allan@gmail.com": "Dan Allan", "mgoacolou@cls.fr": "Manuel Goacolou", "mindmark@gmail.com": "Mark Reid", "moritzkassner@gmail.com": "Moritz Kassner", "vidartf@gmail.com": "Vidar Tonaas Fauske", "xxr@megvii.com": "Xinran Xu", } github_map = { "billy.shambrook@gmail.com": "billyshambrook", "daniel.b.allan@gmail.com": "danielballan", "davoudialireza@gmail.com": "adavoudi", "github@mikeboers.com": "mikeboers", "jeremy.laine@m4x.org": "jlaine", "kalle.litterfeldt@gmail.com": "litterfeldt", "mindmark@gmail.com": "markreidvfx", "moritzkassner@gmail.com": "mkassner", "rush@logic.cz": "radek-senfeld", "self@brendanlong.com": "brendanlong", "tcaswell@gmail.com": "tacaswell", "ulrik.mikaelsson@magine.com": "rawler", "vidartf@gmail.com": "vidartf", "willpatera@gmail.com": "willpatera", "xxr@megvii.com": "xxr3376", "chaudominic2@gmail.com": "laggykiller", "wyattblue@auto-editor.com": "WyattBlue", } email_count = {} for line in ( subprocess.check_output(["git", "log", "--format=%aN,%aE"]).decode().splitlines() ): name, email = line.strip().rsplit(",", 1) email = email_map.get(email, email) if not email: continue names = name_map.setdefault(email, set()) if isinstance(names, set): names.add(name) email_count[email] = email_count.get(email, 0) + 1 last = None block_i = 0 for email, count in sorted(email_count.items(), key=lambda x: (-x[1], x[0])): # This is the natural log, because of course it should be. ;) order = int(math.log(count)) if last and last != order: block_i += 1 print() last = order names = name_map[email] if isinstance(names, set): name = ", ".join(sorted(names)) else: name = names github = github_map.get(email) # The '-' vs '*' is so that Sphinx treats them as different lists, and # introduces a gap bettween them. if github: print( "%s %s <%s>; `@%s `_" % ("-*"[block_i % 2], name, email, github, github) ) else: print( "%s %s <%s>" % ( "-*"[block_i % 2], name, email, ) ) PyAV-14.2.0/AUTHORS.rst000066400000000000000000000115611475734227400143170ustar00rootroot00000000000000Contributors ============ All contributors (by number of commits): - Mike Boers ; `@mikeboers `_ * WyattBlue ; `@WyattBlue `_ * Jeremy Lainé ; `@jlaine `_ - Mark Reid ; `@markreidvfx `_ * Vidar Tonaas Fauske ; `@vidartf `_ * laggykiller ; `@laggykiller `_ * Billy Shambrook ; `@billyshambrook `_ * Casper van der Wel * Philip de Nier * Tadas Dailyda * JoeUgly <41972063+JoeUgly@users.noreply.github.com> * Justin Wong <46082645+uvjustin@users.noreply.github.com> * Mark Harfouche - Alba Mendez - Dave Johansen - Xinran Xu ; `@xxr3376 `_ - Dan Allan ; `@danielballan `_ - Moonsik Park - Santtu Keskinen - Marc Mueller <30130371+cdce8p@users.noreply.github.com> - Christoph Rackwitz - David Plowman - Alireza Davoudi ; `@adavoudi `_ - Jonathan Drolet - Lukas Geiger - Matthew Lai - Moritz Kassner ; `@mkassner `_ - Thomas A Caswell ; `@tacaswell `_ - Ulrik Mikaelsson ; `@rawler `_ - Wel C. van der - Will Patera ; `@willpatera `_ - z-khan * Joe Schiff <41972063+JoeSchiff@users.noreply.github.com> * Dexer <73297572+DexerBR@users.noreply.github.com> * rutsh * Felix Vollmer * Santiago Castro * Christian Clauss * Ihor Liubymov * Johannes Erdfelt * Karl Litterfeldt ; `@litterfeldt `_ * Martin Larralde * Simon-Martin Schröder * Matteo Destro * mephi42 * Miles Kaufmann * Pablo Prietz * Andrew Wason * Radek Senfeld ; `@radek-senfeld `_ * robinechuca * Benjamin Chrétien <2742231+bchretien@users.noreply.github.com> * zzjjbb <31069326+zzjjbb@users.noreply.github.com> * davidplowman <38045873+davidplowman@users.noreply.github.com> * Hanz <40712686+HanzCEO@users.noreply.github.com> * Artturin * Ian Lee * Ryan Huang * Arthur Barros * Carlos Ruiz * Carlos Ruiz * Maxime Desroches * egao1980 * Eric Kalosa-Kenyon * elxy * Gemfield * henri-gasc * Jonathan Martin * Johan Jeppsson Karlin * Philipp Klaus * Kim Minjong * Matteo Destro * Mattias Wadman * Manuel Goacolou * Julian Schweizer * Ömer Sezgin Uğurlu * Orivej Desh * Philipp Krähenbühl * ramoncaldeira * Roland van Laar * Santiago Castro * Kengo Sawatsu * FirefoxMetzger * hyenal * Brendan Long ; `@brendanlong `_ * Семён Марьясин * Stephen.Y * Tom Flanagan * Tim O'Shea * Tim Ahpee * Jonas Tingeborn * Pino Toscano * Ulrik Mikaelsson * Vasiliy Kotov * Koichi Akabe * David Joy * Sviatoslav Sydorenko (Святослав Сидоренко) PyAV-14.2.0/CHANGELOG.rst000066400000000000000000000654001475734227400144620ustar00rootroot00000000000000Changelog ========= We are operating with `semantic versioning `_. .. Please try to update this file in the commits that make the changes. To make merging/rebasing easier, we don't manually break lines in here when they are too long, so any particular change is just one line. To make tracking easier, please add either ``closes #123`` or ``fixes #123`` to the first line of the commit message. There are more syntaxes at: . Note that they these tags will not actually close the issue/PR until they are merged into the "default" branch. v14.2.0 ------- Features: - Add support for external flags in hwacccel by :gh-user:`materight` in (:pr:`1751`). - Add Bayer pixel formats by :gh-user:`z-khan` in (:pr:`1755`). - Add support for yuv422p10le pix_fmt by :gh-user:`WyattBlue` in (:pr:`1767`). - Add ``supported_np_pix_fmts`` by :gh-user:`WyattBlue` in (:pr:`1766`). - Add ``Codec.canonical_name`` by :gh-user:`WyattBlue`. Misc: - Drop support for MacOS 11 by :gh-user:`WyattBlue` in (:pr:`1764`). v14.1.0 ------- Features: - Add hardware decoding by :gh-user:`matthewlai` and :gh-user:`WyattBlue` in (:pr:`1685`). - Add ``Stream.disposition`` and ``Disposition`` enum by :gh-user:`WyattBlue` in (:pr:`1720`). - Add ``VideoFrame.rotation`` by :gh-user:`lgeiger` in (:pr:`1675`). - Support grayf32le and gbrapf32le in numpy convertion by :gh-user:`robinechuca` in (:pr:`1712`). - Support yuv[a]p16 formats in numpy convertion by :gh-user:`robinechuca` in (:pr:`1722`). v14.0.1 ------- Fixes: - Include header files in source distribution by :gh-user:`hmaarrfk` in (:pr:`1662`). - Cleanup ``AVStream.side_data`` leftovers by :gh-user:`lgeiger` in (:pr:`1674`). - Address :issue:`1663` by :gh-user:`WyattBlue`. - Make ``mp3`` work with ``OutputContainer.add_stream_from_template()``. v14.0.0 ------- Major: - Drop FFmpeg 6. - Drop support for MacOS <11 in our binary wheels. - Deleted PyAV's custom Enum class in favor of Python's standard Enums. - Remove ``CodecContext.close()`` and ``Stream.side_data`` because it's deprecated in ffmpeg. - Remove ``AVError`` alias (use ``FFmpegError`` directly instead). - Remove the `template` arg from ``OutputContainer.add_stream()``. Features: - Add ``OutputContainer.add_stream_from_template()`` by :gh-user:`WyattBlue` and :gh-user:`cdce8p`. - Add ``OutputContainer.add_data_stream()`` by :gh-user:`WyattBlue`. - Add ``filter.loudnorm.stats()`` function that returns the stats of loudnorm for 2-pass filtering by :gh-user:`WyattBlue`. - Add ``qmin`` and ``qmax`` parameters to the ``VideoCodecContext`` by :gh-user:`davidplowman` in (:pr:`1618`). - Allow the profile of a codec to be set as well as queried by :gh-user:`davidplowman` in (:pr:`1625`). Fixes: - Make ``VideoFrame.from_numpy_buffer()`` support buffers with padding by :gh-user:`davidplowman` in (:pr:`1635`). - Correct ``Colorspace``'s lowercase enums. - Updated ``sidedata.Type`` enum. - Ensure streams in StreamContainer are released. Fixes :issue:`1599`. v13.1.0 ------- Features: - Allow passing Python objects around using `Frame.opaque` and `Packet.opaque` by :gh-user:`CarlosRDomin` and :gh-user:`WyattBlue` in (:pr:`1533`). - Allow extradata to be set by encoders by :gh-user:`daveisfera` in (:pr:`1526`). - Add getting ffmpeg version info string by :gh-user:`hmaarrfk` in (:pr:`1564`). Fixes: - Remove the `deprecation` module in anticipation of `PEP 702 `_. - Add type stubs to previously unannotated API sections. - Improve type stubs for both `mypy` and `mypy --strict`. - Permit only setting `time_base` with a Fraction, as mypy is unable to respect different types in getters vs setters. - Declare `write_packet` function as const by :gh-user:`hmaarrfk` in (:pr:`1517`). v13.0.0 ------- Major: - Drop FFmpeg 5, Support FFmpeg 7. - Drop Python 3.8, Support Python 3.13. - Update FFmpeg to 7.0.2 for the binary wheels. - Disallow initializing an AudioLayout object with an int. - Disallow accessing gop_size, timebase as a decoder (Raised deprecation warning before). - Remove `ticks_per_frame` property because it was deprecated in FFmpeg. Features: - Add AttachmentStream class. - Add `best()` method to StreamContainer. - Add `set_audio_frame_size()` method to Graph object. - Add `nb_channels` property to AudioLayout object. - Add `from_bytes()` method to VideoFrame object. Fixes: - Fix VideoCC's repl breaking when `self._format` is None. - Fix getting `pix_fmt` property when VideoCC's `self._format` is None. v12.3.0 ------- Features: - Support libav's `av_log_set_level` by :gh-user:`materight` in (:pr:`1448`). - Add Graph.link_nodes by :gh-user:`WyattBlue` in (:pr:`1449`). - Add default codec properties by :gh-user:`WyattBlue` in (:pr:`1450`). - Remove the xvid and ass packages in ffmpeg binaries because they were unused by :gh-user:`WyattBlue` in (:pr:`1462`). - Add supported_codecs property to OutputContainer by :gh-user:`WyattBlue` in (:pr:`1465`). - Add text and dialogue property to AssSubtitle, remove TextSubtitle by :gh-user:`WyattBlue` in (:pr:`1456`). Fixes: - Include libav headers in final distribution by :gh-user:`materight` in (:pr:`1455`). - Fix segfault when calling subtitle_stream.decode() by :gh-user:`WyattBlue` in (:pr:`1460`). - flushing subtitle decoder requires a new uninitialized packet by :gh-user:`moonsikpark` in (:pr:`1461`). - Set default color range for VideoReformatter.format() by :gh-user:`elxy` in (:pr:`1458`). - Resampler: format, layout accepts `str` `int` too by :gh-user:`WyattBlue` in (:pr:`1446`). v12.2.0 ------- Features: - Add a `make_writable` method to `Frame` instances (:issue:`1414`). - Use `av_guess_sample_aspect_ratio` to report sample and display aspect ratios. Fixes: - Fix a crash when assigning an `AudioLayout` to `AudioCodecContext.layout` (:issue:`1434`). - Remove a circular reference which caused `AudioSampler` to occupy memory until garbage collected (:issue:`1429`). - Fix more type stubs, remove incorrect `__init__.pyi`. v12.1.0 ------- Features: - Build binary wheels with webp support. - Allow disabling logs, disable logs by default. - Add bitstream filters by :gh-user:`skeskinen` in (:pr:`1379` :issue:`1375`). - Expose CodecContext flush_buffers by :gh-user:`skeskinen` in (:pr:`1382`). Fixes: - Fix type stubs, add missing type stubs. - Add S12M_TIMECODE by :gh-user:`WyattBlue` in (:pr:`1381`). - Subtitle.text now returns bytes by :gh-user:`WyattBlue` in (:pr:`1398`). - Allow packet.duration to be writable by :gh-user:`WyattBlue` in (:pr:`1399`). - Remove deprecated `VideoStream.frame_rate` by :gh-user:`WyattBlue` in (:pr:`1351`). - Build with Arm for PyPy now by :gh-user:`WyattBlue` in (:pr:`1395`). - Fix #1378 by :gh-user:`WyattBlue` in (:pr:`1400`). - setup.py: use PKG_CONFIG env var to get the pkg-config to use by :gh-user:`Artturin` in (:pr:`1387`). v12.0.0 ------- Major: - Add type hints. - Update FFmpeg to 6.1.1 for the binary wheels. - Update libraries for the binary wheels (notably dav1d to 1.4.1). - Deprecate VideoCodecContext.gop_size for decoders by :gh-user:`JoeSchiff` in (:pr:`1256`). - Deprecate frame.index by :gh-user:`JoeSchiff` in (:pr:`1218`). Features: - Allow using pathlib.Path for av.open by :gh-user:`WyattBlue` in (:pr:`1231`). - Add `max_b_frames` property to CodecContext by :gh-user:`davidplowman` in (:pr:`1119`). - Add `encode_lazy` method to CodecContext by :gh-user:`rawler` in (:pr:`1092`). - Add `color_range` to CodecContext/Frame by :gh-user:`johanjeppsson` in (:pr:`686`). - Set `time_base` for AudioResampler by :gh-user:`daveisfera` in (:issue:`1209`). - Add support for ffmpeg's AVCodecContext::delay by :gh-user:`JoeSchiff` in (:issue:`1279`). - Add `color_primaries`, `color_trc`, `colorspace` to VideoStream by :gh-user:`WyattBlue` in (:pr:`1304`). - Add `bits_per_coded_sample` to VideoCodecContext by :gh-user:`rvanlaar` in (:pr:`1203`). - AssSubtitle.ass now returns as bytes by :gh-user:`WyattBlue` in (:pr:`1333`). - Expose DISPLAYMATRIX side data by :gh-user:`hyenal` in (:pr:`1249`). Fixes: - Convert deprecated Cython extension class properties to decorator syntax by :gh-user:`JoeSchiff`. - Check None packet when setting time_base after decode by :gh-user:`philipnbbc` in (:pr:`1281`). - Remove deprecated `Buffer.to_bytes` by :gh-user:`WyattBlue` in (:pr:`1286`). - Remove deprecated `Packet.decode_one` by :gh-user:`WyattBlue` in (:pr:`1301`). v11.0.0 ------- Major: - Add support for FFmpeg 6.0, drop support for FFmpeg < 5.0. - Add support for Python 3.12, drop support for Python < 3.8. - Build binary wheels against libvpx 1.13.1 to fix CVE-2023-5217. - Build binary wheels against FFmpeg 6.0. Features: - Add support for the `ENCODER_FLUSH` encoder flag (:issue:`1067`). - Add VideoFrame ndarray operations for yuv444p/yuvj444p formats (:issue:`788`). - Add setters for `AVFrame.dts`, `AVPacket.is_keyframe` and `AVPacket.is_corrupt` (:issue:`1179`). Fixes: - Fix build using Cython 3 (:issue:`1140`). - Populate new streams with codec parameters (:issue:`1044`). - Explicitly set `python_requires` to avoid installing on incompatible Python (:issue:`1057`). - Make `AudioFifo.__repr__` safe before the first frame (:issue:`1130`). - Guard input container members against use after closes (:issue:`1137`). v10.0.0 ------- Major: - Add support for FFmpeg 5.0 and 5.1 (:issue:`817`). - Drop support for FFmpeg < 4.3. - Deprecate `CodecContext.time_base` for decoders (:issue:`966`). - Deprecate `VideoStream.framerate` and `VideoStream.rate` (:issue:`1005`). - Stop proxying `Codec` from `Stream` instances (:issue:`1037`). Features: - Update FFmpeg to 5.1.2 for the binary wheels. - Provide binary wheels for Python 3.11 (:issue:`1019`). - Add VideoFrame ndarray operations for gbrp formats (:issue:`986`). - Add VideoFrame ndarray operations for gbrpf32 formats (:issue:`1028`). - Add VideoFrame ndarray operations for nv12 format (:issue:`996`). Fixes: - Fix conversion to numpy array for multi-byte formats (:issue:`981`). - Safely iterate over filter pads (:issue:`1000`). v9.2.0 ------ Features: - Update binary wheels to enable libvpx support. - Add an `io_open` argument to `av.open` for multi-file custom I/O. - Add support for AV_FRAME_DATA_SEI_UNREGISTERED (:issue:`723`). - Ship .pxd files to allow other libraries to `cimport av` (:issue:`716`). Fixes: - Fix an `ImportError` when using Python 3.8/3.9 via Conda (:issue:`952`). - Fix a muxing memory leak which was introduced in v9.1.0 (:issue:`959`). v9.1.1 ------ Fixes: - Update binary wheels to update dependencies on Windows, disable ALSA on Linux. v9.1.0 ------ Features: - Add VideoFrame ndarray operations for rgb48be, rgb48le, rgb64be, rgb64le pixel formats. - Add VideoFrame ndarray operations for gray16be, gray16le pixel formats (:issue:`674`). - Make it possible to use av.open() on a pipe (:issue:`738`). - Use the "ASS without timings" format when decoding subtitles. Fixes: - Update binary wheels to fix security vulnerabilities (:issue:`921`) and enable ALSA on Linux (:issue:`941`). - Fix crash when closing an output container an encountering an I/O error (:issue:`613`). - Fix crash when probing corrupt raw format files (:issue:`590`). - Fix crash when manipulating streams with an unknown codec (:issue:`689`). - Remove obsolete KEEP_SIDE_DATA and MP4A_LATM flags which are gone in FFmpeg 5.0. - Deprecate `to_bytes()` method of Packet, Plane and SideData, use `bytes(packet)` instead. v9.0.2 ------ Minor: - Update FFmpeg to 4.4.1 for the binary wheels. - Fix framerate when writing video with FFmpeg 4.4 (:issue:`876`). v9.0.1 ------ Minor: - Update binary wheels to fix security vulnerabilities (:issue:`901`). v9.0.0 ------ Major: - Re-implement AudioResampler with aformat and buffersink (:issue:`761`). AudioResampler.resample() now returns a list of frames. - Remove deprecated methods: AudioFrame.to_nd_array, VideoFrame.to_nd_array and Stream.seek. Minor: - Provide binary wheels for macOS/arm64 and Linux/aarch64. - Simplify setup.py, require Cython. - Update the installation instructions in favor of PyPI. - Fix VideoFrame.to_image with height & width (:issue:`878`). - Fix setting Stream time_base (:issue:`784`). - Replace deprecated av_init_packet with av_packet_alloc (:issue:`872`). - Validate pixel format in VideoCodecContext.pix_fmt setter (:issue:`815`). - Fix AudioFrame ndarray conversion endianness (:issue:`833`). - Improve time_base support with filters (:issue:`765`). - Allow flushing filters by sending `None` (:issue:`886`). - Avoid unnecessary vsnprintf() calls in log_callback() (:issue:`877`). - Make Frame.from_ndarray raise ValueError instead of AssertionError. v8.1.0 ------ Minor: - Update FFmpeg to 4.3.2 for the binary wheels. - Provide binary wheels for Python 3.10 (:issue:`820`). - Stop providing binary wheels for end-of-life Python 3.6. - Fix args order in Frame.__repr__ (:issue:`749`). - Fix documentation to remove unavailable QUIET log level (:issue:`719`). - Expose codec_context.codec_tag (:issue:`741`). - Add example for encoding with a custom PTS (:issue:`725`). - Use av_packet_rescale_ts in Packet._rebase_time() (:issue:`737`). - Do not hardcode errno values in test suite (:issue:`729`). - Use av_guess_format for output container format (:issue:`691`). - Fix setting CodecContext.extradata (:issue:`658`, :issue:`740`). - Fix documentation code block indentation (:issue:`783`). - Fix link to Conda installation instructions (:issue:`782`). - Export AudioStream from av.audio (:issue:`775`). - Fix setting CodecContext.extradata (:issue:`801`). v8.0.3 ------ Minor: - Update FFmpeg to 4.3.1 for the binary wheels. v8.0.2 ------ Minor: - Enable GnuTLS support in the FFmpeg build used for binary wheels (:issue:`675`). - Make binary wheels compatible with Mac OS X 10.9+ (:issue:`662`). - Drop Python 2.x buffer protocol code. - Remove references to previous repository location. v8.0.1 ------ Minor: - Enable additional FFmpeg features in the binary wheels. - Use os.fsencode for both input and output file names (:issue:`600`). v8.0.0 ------ Major: - Drop support for Python 2 and Python 3.4. - Provide binary wheels for Linux, Mac and Windows. Minor: - Remove shims for obsolete FFmpeg versions (:issue:`588`). - Add yuvj420p format for :meth:`VideoFrame.from_ndarray` and :meth:`VideoFrame.to_ndarray` (:issue:`583`). - Add support for palette formats in :meth:`VideoFrame.from_ndarray` and :meth:`VideoFrame.to_ndarray` (:issue:`601`). - Fix Python 3.8 deprecation warning related to abstract base classes (:issue:`616`). - Remove ICC profiles from logos (:issue:`622`). Fixes: - Avoid infinite timeout in :func:`av.open` (:issue:`589`). v7.0.1 ------ Fixes: - Removed deprecated ``AV_FRAME_DATA_QP_TABLE_*`` enums. (:issue:`607`) v7.0.0 ------ Major: - Drop support for FFmpeg < 4.0. (:issue:`559`) - Introduce per-error exceptions, and mirror the builtin exception hierarchy. It is recommended to examine your error handling code, as common FFmpeg errors will result in `ValueError` baseclasses now. (:issue:`563`) - Data stream's `encode` and `decode` return empty lists instead of none allowing common API use patterns with data streams. - Remove ``whence`` parameter from :meth:`InputContainer.seek` as non-time seeking doesn't seem to actually be supported by any FFmpeg formats. Minor: - Users can disable the logging system to avoid lockups in sub-interpreters. (:issue:`545`) - Filters support audio in general, and a new :meth:`.Graph.add_abuffer`. (:issue:`562`) - :func:`av.open` supports `timeout` parameters. (:issue:`480` and :issue:`316`) - Expose :attr:`Stream.base_rate` and :attr:`Stream.guessed_rate`. (:issue:`564`) - :meth:`.VideoFrame.reformat` can specify interpolation. - Expose many sets of flags. Fixes: - Fix typing in :meth:`.CodecContext.parse` and make it more robust. - Fix wrong attribute in ByteSource. (:issue:`340`) - Remove exception that would break audio remuxing. (:issue:`537`) - Log messages include last FFmpeg error log in more helpful way. - Use AVCodecParameters so FFmpeg doesn't complain. (:issue:`222`) v6.2.0 ------ Major: - Allow :meth:`av.open` to be used as a context manager. - Fix compatibility with PyPy, the full test suite now passes. (:issue:`130`) Minor: - Add :meth:`.InputContainer.close` method. (:issue:`317`, :issue:`456`) - Ensure audio output gets flushes when using a FIFO. (:issue:`511`) - Make Python I/O buffer size configurable. (:issue:`512`) - Make :class:`.AudioFrame` and :class:`VideoFrame` more garbage-collector friendly by breaking a reference cycle. (:issue:`517`) Build: - Do not install the `scratchpad` package. v6.1.2 ------ Micro: - Fix a numpy deprecation warning in :meth:`.AudioFrame.to_ndarray`. v6.1.1 ------ Micro: - Fix alignment in :meth:`.VideoFrame.from_ndarray`. (:issue:`478`) - Fix error message in :meth:`.Buffer.update`. Build: - Fix more compiler warnings. v6.1.0 ------ Minor: - ``av.datasets`` for sample data that is pulled from either FFmpeg's FATE suite, or our documentation server. - :meth:`.InputContainer.seek` gets a ``stream`` argument to specify the ``time_base`` the requested ``offset`` is in. Micro: - Avoid infinite look in ``Stream.__getattr__``. (:issue:`450`) - Correctly handle Python I/O with no ``seek`` method. - Remove ``Datastream.seek`` override (:issue:`299`) Build: - Assert building against compatible FFmpeg. (:issue:`401`) - Lock down Cython language level to avoid build warnings. (:issue:`443`) Other: - Incremental improvements to docs and tests. - Examples directory will now always be runnable as-is, and embeded in the docs (in a copy-pastable form). v6.0.0 ------ Major: - Drop support for FFmpeg < 3.2. - Remove ``VideoFrame.to_qimage`` method, as it is too tied to PyQt4. (:issue:`424`) Minor: - Add support for all known sample formats in :meth:`.AudioFrame.to_ndarray` and add :meth:`.AudioFrame.to_ndarray`. (:issue:`422`) - Add support for more image formats in :meth:`.VideoFrame.to_ndarray` and :meth:`.VideoFrame.from_ndarray`. (:issue:`415`) Micro: - Fix a memory leak in :meth:`.OutputContainer.mux_one`. (:issue:`431`) - Ensure :meth:`.OutputContainer.close` is called at destruction. (:issue:`427`) - Fix a memory leak in :class:`.OutputContainer` initialisation. (:issue:`427`) - Make all video frames created by PyAV use 8-byte alignment. (:issue:`425`) - Behave properly in :meth:`.VideoFrame.to_image` and :meth:`.VideoFrame.from_image` when ``width != line_width``. (:issue:`425`) - Fix manipulations on video frames whose width does not match the line stride. (:issue:`423`) - Fix several :attr:`.Plane.line_size` misunderstandings. (:issue:`421`) - Consistently decode dictionary contents. (:issue:`414`) - Always use send/recv en/decoding mechanism. This removes the ``count`` parameter, which was not used in the send/recv pipeline. (:issue:`413`) - Remove various deprecated iterators. (:issue:`412`) - Fix a memory leak when using Python I/O. (:issue:`317`) - Make :meth:`.OutputContainer.mux_one` call `av_interleaved_write_frame` with the GIL released. Build: - Remove the "reflection" mechanism, and rely on FFmpeg version we build against to decide which methods to call. (:issue:`416`) - Fix many more ``const`` warnings. v0.x.y ------ .. note:: Below here we used ``v0.x.y``. We incremented ``x`` to signal a major change (i.e. backwards incompatibilities) and incremented ``y`` as a minor change (i.e. backwards compatible features). Once we wanted more subtlety and felt we had matured enough, we jumped past the implications of ``v1.0.0`` straight to ``v6.0.0`` (as if we had not been stuck in ``v0.x.y`` all along). v0.5.3 ------ Minor: - Expose :attr:`.VideoFrame.pict_type` as :class:`.PictureType` enum. (:pr:`402`) - Expose :attr:`.Codec.video_rates` and :attr:`.Codec.audio_rates`. (:pr:`381`) Patch: - Fix :attr:`.Packet.time_base` handling during flush. (:pr:`398`) - :meth:`.VideoFrame.reformat` can throw exceptions when requested colorspace transforms aren't possible. - Wrapping the stream object used to overwrite the ``pix_fmt`` attribute. (:pr:`390`) Runtime: - Deprecate ``VideoFrame.ptr`` in favour of :attr:`VideoFrame.buffer_ptr`. - Deprecate ``Plane.update_buffer()`` and ``Packet.update_buffer`` in favour of :meth:`.Plane.update`. (:pr:`407`) - Deprecate ``Plane.update_from_string()`` in favour of :meth:`.Plane.update`. (:pr:`407`) - Deprecate ``AudioFrame.to_nd_array()`` and ``VideoFrame.to_nd_array()`` in favour of :meth:`.AudioFrame.to_ndarray` and :meth:`.VideoFrame.to_ndarray`. (:pr:`404`) Build: - CI covers more cases, including macOS. (:pr:`373` and :pr:`399`) - Fix many compilation warnings. (:issue:`379`, :pr:`380`, :pr:`387`, and :pr:`388`) Docs: - Docstrings for many commonly used attributes. (:pr:`372` and :pr:`409`) v0.5.2 ------ Build: - Fixed Windows build, which broke in v0.5.1. - Compiler checks are not cached by default. This behaviour is retained if you ``source scripts/activate.sh`` to develop PyAV. (:issue:`256`) - Changed to ``PYAV_SETUP_REFLECT_DEBUG=1`` from ``PYAV_DEBUG_BUILD=1``. v0.5.1 ------ Build: - Set ``PYAV_DEBUG_BUILD=1`` to force a verbose reflection (mainly for being installed via ``pip``, which is why this is worth a release). v0.5.0 ------ Major: - Dropped support for Libav in general. (:issue:`110`) - No longer uses libavresample. Minor: - ``av.open`` has ``container_options`` and ``stream_options``. - ``Frame`` includes ``pts`` in ``repr``. Patch: - EnumItem's hash calculation no longer overflows. (:issue:`339`, :issue:`341` and :issue:`342`.) - Frame.time_base was not being set in most cases during decoding. (:issue:`364`) - CodecContext.options no longer needs to be manually initialized. - CodexContext.thread_type accepts its enums. v0.4.1 ------ Minor: - Add `Frame.interlaced_frame` to indicate if the frame is interlaced. (:issue:`327` by :gh-user:`MPGek`) - Add FLTP support to ``Frame.to_nd_array()``. (:issue:`288` by :gh-user:`rawler`) - Expose ``CodecContext.extradata`` for codecs that have extra data, e.g. Huffman tables. (:issue:`287` by :gh-user:`adavoudi`) Patch: - Packets retain their refcount after muxing. (:issue:`334`) - `Codec` construction is more robust to find more codecs. (:issue:`332` by :gh-user:`adavoudi`) - Refined frame corruption detection. (:issue:`291` by :gh-user:`Litterfeldt`) - Unicode filenames are okay. (:issue:`82`) v0.4.0 ------ Major: - ``CodecContext`` has taken over encoding/decoding, and can work in isolation of streams/containers. - ``Stream.encode`` returns a list of packets, instead of a single packet. - ``AudioFifo`` and ``AudioResampler`` will raise ``ValueError`` if input frames inconsistent ``pts``. - ``time_base`` use has been revisited across the codebase, and may not be converted between ``Stream.time_base`` and ``CodecContext.time_base`` at the same times in the transcoding pipeline. - ``CodecContext.rate`` has been removed, but proxied to ``VideoCodecContext.framerate`` and ``AudioCodecContext.sample_rate``. The definition is effectively inverted from the old one (i.e. for 24fps it used to be ``1/24`` and is now ``24/1``). - Fractions (e.g. ``time_base``, ``rate``) will be ``None`` if they are invalid. - ``InputContainer.seek`` and ``Stream.seek`` will raise TypeError if given a float, when previously they converted it from seconds. Minor: - Added ``Packet.is_keyframe`` and ``Packet.is_corrupt``. (:issue:`226`) - Many more ``time_base``, ``pts`` and other attributes are writable. - ``Option`` exposes much more of the API (but not get/set). (:issue:`243`) - Expose metadata encoding controls. (:issue:`250`) - Expose ``CodecContext.skip_frame``. (:issue:`259`) Patch: - Build doesn't fail if you don't have git installed. (:issue:`184`) - Developer environment works better with Python3. (:issue:`248`) - Fix Container deallocation resulting in segfaults. (:issue:`253`) v0.3.3 ------ Patch: - Fix segfault due to buffer overflow in handling of stream options. (:issue:`163` and :issue:`169`) - Fix segfault due to seek not properly checking if codecs were open before using avcodec_flush_buffers. (:issue:`201`) v0.3.2 ------ Minor: - Expose basics of avfilter via ``Filter``. - Add ``Packet.time_base``. - Add ``AudioFrame.to_nd_array`` to match same on ``VideoFrame``. - Update Windows build process. Patch: - Further improvements to the logging system. (:issue:`128`) v0.3.1 ------ Minor: - ``av.logging.set_log_after_shutdown`` renamed to ``set_print_after_shutdown`` - Repeating log messages will be skipped, much like ffmpeg's does by default Patch: - Fix memory leak in logging system when under heavy logging loads while threading. (:issue:`128` with help from :gh-user:`mkassner` and :gh-user:`ksze`) v0.3.0 ------ Major: - Python IO can write - Improve build system to use Python's C compiler for function detection; build system is much more robust - MSVC support. (:issue:`115` by :gh-user:`vidartf`) - Continuous integration on Windows via AppVeyor. (by :gh-user:`vidartf`) Minor: - Add ``Packet.decode_one()`` to skip packet flushing for codecs that would otherwise error - ``StreamContainer`` for easier selection of streams - Add buffer protocol support to Packet Patch: - Fix bug when using Python IO on files larger than 2GB. (:issue:`109` by :gh-user:`xxr3376`) - Fix usage of changed Pillow API Known Issues: - VideoFrame is suspected to leak memory in narrow cases on Linux. (:issue:`128`) v0.2.4 ------ - fix library search path for current Libav/Ubuntu 14.04. (:issue:`97`) - explicitly include all sources to combat 0.2.3 release problem. (:issue:`100`) v0.2.3 ------ .. warning:: There was an issue with the PyPI distribution in which it required Cython to be installed. Major: - Python IO. - Aggressively releases GIL - Add experimental Windows build. (:issue:`84`) Minor: - Several new Stream/Packet/Frame attributes Patch: - Fix segfault in audio handling. (:issue:`86` and :issue:`93`) - Fix use of PIL/Pillow API. (:issue:`85`) - Fix bad assumptions about plane counts. (:issue:`76`) v0.2.2 ------ - Cythonization in setup.py; mostly a development issue. - Fix for av.InputContainer.size over 2**31. v0.2.1 ------ - Python 3 compatibility! - Build process fails if missing libraries. - Fix linking of libavdevices. v0.2.0 ------ .. warning:: This version has an issue linking in libavdevices, and very likely will not work for you. It sure has been a long time since this was released, and there was a lot of arbitrary changes that come with us wrapping an API as we are discovering it. Changes include, but are not limited to: - Audio encoding. - Exposing planes and buffers. - Descriptors for channel layouts, video and audio formats, etc.. - Seeking. - Many many more properties on all of the objects. - Device support (e.g. webcams). v0.1.0 ------ - FIRST PUBLIC RELEASE! - Container/video/audio formats. - Audio layouts. - Decoding video/audio/subtitles. - Encoding video. - Audio FIFOs and resampling. PyAV-14.2.0/LICENSE.txt000066400000000000000000000027411475734227400142630ustar00rootroot00000000000000Copyright retained by original committers. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. PyAV-14.2.0/MANIFEST.in000066400000000000000000000003271475734227400141740ustar00rootroot00000000000000include *.txt *.md recursive-include av *.pyx *.pxd recursive-include docs *.rst *.py recursive-include examples *.py recursive-include include *.pxd *.h recursive-include src/av *.c *.h recursive-include tests *.pyPyAV-14.2.0/Makefile000066400000000000000000000016131475734227400140750ustar00rootroot00000000000000LDFLAGS ?= "" CFLAGS ?= "-O0 -Wno-incompatible-pointer-types -Wno-unreachable-code" PYAV_PYTHON ?= python PYAV_PIP ?= pip PYTHON := $(PYAV_PYTHON) PIP := $(PYAV_PIP) .PHONY: default build clean fate-suite lint test default: build build: $(PIP) install -U cython setuptools CFLAGS=$(CFLAGS) LDFLAGS=$(LDFLAGS) $(PYTHON) setup.py build_ext --inplace --debug clean: - find av -name '*.so' -delete - rm -rf build - rm -rf sandbox - rm -rf src - make -C docs clean fate-suite: # Grab ALL of the samples from the ffmpeg site. rsync -vrltLW rsync://fate-suite.ffmpeg.org/fate-suite/ tests/assets/fate-suite/ lint: $(PIP) install -U black isort flake8 flake8-pyproject pillow numpy mypy==1.15.0 pytest black --check av examples tests setup.py flake8 av isort --check-only --diff av examples tests mypy av tests test: $(PIP) install --upgrade cython numpy pillow pytest $(PYTHON) -m pytest PyAV-14.2.0/README.md000066400000000000000000000061071475734227400137170ustar00rootroot00000000000000PyAV ==== PyAV is a Pythonic binding for the [FFmpeg][ffmpeg] libraries. We aim to provide all of the power and control of the underlying library, but manage the gritty details as much as possible. --- [![GitHub Test Status][github-tests-badge]][github-tests] [![Documentation][docs-badge]][docs] [![Python Package Index][pypi-badge]][pypi] [![Conda Forge][conda-badge]][conda] 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). 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. But where you can't work without it, PyAV is a critical tool. Installation ------------ Binary wheels are provided on [PyPI][pypi] for Linux, MacOS and Windows linked against the latest stable version of ffmpeg. You can install these wheels by running: ```bash pip install av ``` Another way of installing PyAV is via [conda-forge][conda-forge]: ```bash conda install av -c conda-forge ``` See the [Conda install][conda-install] docs to get started with (mini)Conda. Alternative installation methods -------------------------------- Due to the complexity of the dependencies, PyAV is not always the easiest Python package to install from source. If you want to use your existing ffmpeg (must be the correct major version), the source version of PyAV is on [PyPI][pypi]: > [!WARNING] > You must be in a posix env, and have the correct version of ffmpeg installed on your system. ```bash pip install av --no-binary av ``` Installing From Source ---------------------- Here's how to build PyAV from source. You must use [MSYS2](https://www.msys2.org/) when using Windows. ```bash git clone https://github.com/PyAV-Org/PyAV.git cd PyAV source scripts/activate.sh # Build ffmpeg from source. You can skip this step # if ffmpeg is already installed. ./scripts/build-deps # Build PyAV make # Testing make test # Install globally deactivate pip install . ``` --- Have fun, [read the docs][docs], [come chat with us][discuss], and good luck! [conda-badge]: https://img.shields.io/conda/vn/conda-forge/av.svg?colorB=CCB39A [conda]: https://anaconda.org/conda-forge/av [docs-badge]: https://img.shields.io/badge/docs-on%20pyav.basswood--io.com-blue.svg [docs]: https://pyav.basswood-io.com [pypi-badge]: https://img.shields.io/pypi/v/av.svg?colorB=CCB39A [pypi]: https://pypi.org/project/av [discuss]: https://github.com/PyAV-Org/PyAV/discussions [github-tests-badge]: https://github.com/PyAV-Org/PyAV/workflows/tests/badge.svg [github-tests]: https://github.com/PyAV-Org/PyAV/actions?workflow=tests [github]: https://github.com/PyAV-Org/PyAV [ffmpeg]: https://ffmpeg.org/ [conda-forge]: https://conda-forge.github.io/ [conda-install]: https://docs.conda.io/projects/conda/en/latest/user-guide/install/index.html PyAV-14.2.0/av/000077500000000000000000000000001475734227400130425ustar00rootroot00000000000000PyAV-14.2.0/av/__init__.pxd000066400000000000000000000000001475734227400153040ustar00rootroot00000000000000PyAV-14.2.0/av/__init__.py000066400000000000000000000041121475734227400151510ustar00rootroot00000000000000# MUST import the core before anything else in order to initialize the underlying # library that is being wrapped. from av._core import time_base, library_versions, ffmpeg_version_info # Capture logging (by importing it). from av import logging # For convenience, import all common attributes. from av.about import __version__ from av.audio.codeccontext import AudioCodecContext from av.audio.fifo import AudioFifo from av.audio.format import AudioFormat from av.audio.frame import AudioFrame from av.audio.layout import AudioLayout from av.audio.resampler import AudioResampler from av.audio.stream import AudioStream from av.bitstream import BitStreamFilterContext, bitstream_filters_available from av.codec.codec import Codec, codecs_available from av.codec.context import CodecContext from av.codec.hwaccel import HWConfig from av.container import open from av.format import ContainerFormat, formats_available from av.packet import Packet from av.error import * # noqa: F403; This is limited to exception types. from av.video.codeccontext import VideoCodecContext from av.video.format import VideoFormat from av.video.frame import VideoFrame from av.video.stream import VideoStream __all__ = ( "__version__", "time_base", "ffmpeg_version_info", "library_versions", "AudioCodecContext", "AudioFifo", "AudioFormat", "AudioFrame", "AudioLayout", "AudioResampler", "AudioStream", "BitStreamFilterContext", "bitstream_filters_available", "Codec", "codecs_available", "CodecContext", "open", "ContainerFormat", "formats_available", "Packet", "VideoCodecContext", "VideoFormat", "VideoFrame", "VideoStream", ) def get_include() -> str: """ Returns the path to the `include` folder to be used when building extensions to av. """ import os # Installed package include_path = os.path.join(os.path.dirname(__file__), "include") if os.path.exists(include_path): return include_path # Running from source directory return os.path.join(os.path.dirname(__file__), os.pardir, "include") PyAV-14.2.0/av/__main__.py000066400000000000000000000030401475734227400151310ustar00rootroot00000000000000from __future__ import annotations import argparse def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("--codecs", action="store_true") parser.add_argument("--hwdevices", action="store_true") parser.add_argument("--hwconfigs", action="store_true") parser.add_argument("--version", action="store_true") args = parser.parse_args() if args.version: import av import av._core print(f"PyAV v{av.__version__}") by_config: dict = {} for libname, config in sorted(av._core.library_meta.items()): version = config["version"] if version[0] >= 0: by_config.setdefault( (config["configuration"], config["license"]), [] ).append((libname, config)) for (config, license), libs in sorted(by_config.items()): print("library configuration:", config) print("library license:", license) for libname, config in libs: version = config["version"] print(f"{libname:<13} {version[0]:3d}.{version[1]:3d}.{version[2]:3d}") if args.hwdevices: from av.codec.hwaccel import hwdevices_available print("Hardware device types:") for x in hwdevices_available(): print(" ", x) if args.hwconfigs: from av.codec.codec import dump_hwconfigs dump_hwconfigs() if args.codecs: from av.codec.codec import dump_codecs dump_codecs() if __name__ == "__main__": main() PyAV-14.2.0/av/_core.pyi000066400000000000000000000003731475734227400146570ustar00rootroot00000000000000from typing import TypedDict class _Meta(TypedDict): version: tuple[int, int, int] configuration: str license: str library_meta: dict[str, _Meta] library_versions: dict[str, tuple[int, int, int]] ffmpeg_version_info: str time_base: int PyAV-14.2.0/av/_core.pyx000066400000000000000000000036531475734227400147020ustar00rootroot00000000000000cimport libav as lib # Initialise libraries. lib.avformat_network_init() lib.avdevice_register_all() # Exports. time_base = lib.AV_TIME_BASE cdef decode_version(v): if v < 0: return (-1, -1, -1) cdef int major = (v >> 16) & 0xff cdef int minor = (v >> 8) & 0xff cdef int micro = (v) & 0xff return (major, minor, micro) # Return an informative version string. # This usually is the actual release version number or a git commit # description. This string has no fixed format and can change any time. It # should never be parsed by code. ffmpeg_version_info = lib.av_version_info() library_meta = { "libavutil": dict( version=decode_version(lib.avutil_version()), configuration=lib.avutil_configuration(), license=lib.avutil_license() ), "libavcodec": dict( version=decode_version(lib.avcodec_version()), configuration=lib.avcodec_configuration(), license=lib.avcodec_license() ), "libavformat": dict( version=decode_version(lib.avformat_version()), configuration=lib.avformat_configuration(), license=lib.avformat_license() ), "libavdevice": dict( version=decode_version(lib.avdevice_version()), configuration=lib.avdevice_configuration(), license=lib.avdevice_license() ), "libavfilter": dict( version=decode_version(lib.avfilter_version()), configuration=lib.avfilter_configuration(), license=lib.avfilter_license() ), "libswscale": dict( version=decode_version(lib.swscale_version()), configuration=lib.swscale_configuration(), license=lib.swscale_license() ), "libswresample": dict( version=decode_version(lib.swresample_version()), configuration=lib.swresample_configuration(), license=lib.swresample_license() ), } library_versions = {name: meta["version"] for name, meta in library_meta.items()} PyAV-14.2.0/av/about.py000066400000000000000000000000271475734227400145250ustar00rootroot00000000000000__version__ = "14.2.0" PyAV-14.2.0/av/attachments/000077500000000000000000000000001475734227400153555ustar00rootroot00000000000000PyAV-14.2.0/av/attachments/__init__.py000066400000000000000000000000001475734227400174540ustar00rootroot00000000000000PyAV-14.2.0/av/attachments/stream.pxd000066400000000000000000000001161475734227400173630ustar00rootroot00000000000000from av.stream cimport Stream cdef class AttachmentStream(Stream): pass PyAV-14.2.0/av/attachments/stream.pyi000066400000000000000000000002621475734227400173730ustar00rootroot00000000000000from typing import Literal from av.stream import Stream class AttachmentStream(Stream): type: Literal["attachment"] @property def mimetype(self) -> str | None: ... PyAV-14.2.0/av/attachments/stream.pyx000066400000000000000000000011761475734227400174170ustar00rootroot00000000000000from av.stream cimport Stream cdef class AttachmentStream(Stream): """ An :class:`AttachmentStream` represents a stream of attachment data within a media container. Typically used to attach font files that are referenced in ASS/SSA Subtitle Streams. """ @property def name(self): """ Returns the file name of the attachment. :rtype: str | None """ return self.metadata.get("filename") @property def mimetype(self): """ Returns the MIME type of the attachment. :rtype: str | None """ return self.metadata.get("mimetype") PyAV-14.2.0/av/audio/000077500000000000000000000000001475734227400141435ustar00rootroot00000000000000PyAV-14.2.0/av/audio/__init__.pxd000066400000000000000000000000001475734227400164050ustar00rootroot00000000000000PyAV-14.2.0/av/audio/__init__.py000066400000000000000000000000761475734227400162570ustar00rootroot00000000000000from .frame import AudioFrame from .stream import AudioStream PyAV-14.2.0/av/audio/__init__.pyi000066400000000000000000000001471475734227400164270ustar00rootroot00000000000000from .frame import AudioFrame from .stream import AudioStream __all__ = ("AudioFrame", "AudioStream") PyAV-14.2.0/av/audio/codeccontext.pxd000066400000000000000000000005161475734227400173440ustar00rootroot00000000000000 from av.audio.frame cimport AudioFrame from av.audio.resampler cimport AudioResampler from av.codec.context cimport CodecContext cdef class AudioCodecContext(CodecContext): # Hold onto the frames that we will decode until we have a full one. cdef AudioFrame next_frame # For encoding. cdef AudioResampler resampler PyAV-14.2.0/av/audio/codeccontext.pyi000066400000000000000000000017661475734227400173620ustar00rootroot00000000000000from typing import Iterator, Literal from av.codec.context import CodecContext from av.packet import Packet from .format import AudioFormat from .frame import AudioFrame from .layout import AudioLayout class _Format: def __get__(self, i: object | None, owner: type | None = None) -> AudioFormat: ... def __set__(self, instance: object, value: AudioFormat | str) -> None: ... class _Layout: def __get__(self, i: object | None, owner: type | None = None) -> AudioLayout: ... def __set__(self, instance: object, value: AudioLayout | str) -> None: ... class AudioCodecContext(CodecContext): frame_size: int sample_rate: int rate: int type: Literal["audio"] format: _Format layout: _Layout @property def channels(self) -> int: ... def encode(self, frame: AudioFrame | None = None) -> list[Packet]: ... def encode_lazy(self, frame: AudioFrame | None = None) -> Iterator[Packet]: ... def decode(self, packet: Packet | None = None) -> list[AudioFrame]: ... PyAV-14.2.0/av/audio/codeccontext.pyx000066400000000000000000000056621475734227400174000ustar00rootroot00000000000000cimport libav as lib from av.audio.format cimport AudioFormat, get_audio_format from av.audio.frame cimport AudioFrame, alloc_audio_frame from av.audio.layout cimport AudioLayout, get_audio_layout from av.codec.hwaccel cimport HWAccel from av.frame cimport Frame from av.packet cimport Packet cdef class AudioCodecContext(CodecContext): cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec, HWAccel hwaccel): CodecContext._init(self, ptr, codec, hwaccel) cdef _prepare_frames_for_encode(self, Frame input_frame): cdef AudioFrame frame = input_frame cdef bint allow_var_frame_size = self.ptr.codec.capabilities & lib.AV_CODEC_CAP_VARIABLE_FRAME_SIZE # Note that the resampler will simply return an input frame if there is # no resampling to be done. The control flow was just a little easier this way. if not self.resampler: self.resampler = AudioResampler( format=self.format, layout=self.layout, rate=self.ptr.sample_rate, frame_size=None if allow_var_frame_size else self.ptr.frame_size ) frames = self.resampler.resample(frame) # flush if input frame is None if input_frame is None: frames.append(None) return frames cdef Frame _alloc_next_frame(self): return alloc_audio_frame() cdef _setup_decoded_frame(self, Frame frame, Packet packet): CodecContext._setup_decoded_frame(self, frame, packet) cdef AudioFrame aframe = frame aframe._init_user_attributes() @property def frame_size(self): """ Number of samples per channel in an audio frame. :type: int """ return self.ptr.frame_size @property def sample_rate(self): """ Sample rate of the audio data, in samples per second. :type: int """ return self.ptr.sample_rate @sample_rate.setter def sample_rate(self, int value): self.ptr.sample_rate = value @property def rate(self): """Another name for :attr:`sample_rate`.""" return self.sample_rate @rate.setter def rate(self, value): self.sample_rate = value @property def channels(self): return self.layout.nb_channels @property def layout(self): """ The audio channel layout. :type: AudioLayout """ return get_audio_layout(self.ptr.ch_layout) @layout.setter def layout(self, value): cdef AudioLayout layout = AudioLayout(value) self.ptr.ch_layout = layout.layout @property def format(self): """ The audio sample format. :type: AudioFormat """ return get_audio_format(self.ptr.sample_fmt) @format.setter def format(self, value): cdef AudioFormat format = AudioFormat(value) self.ptr.sample_fmt = format.sample_fmt PyAV-14.2.0/av/audio/fifo.pxd000066400000000000000000000007151475734227400156060ustar00rootroot00000000000000cimport libav as lib from libc.stdint cimport int64_t, uint64_t from av.audio.frame cimport AudioFrame cdef class AudioFifo: cdef lib.AVAudioFifo *ptr cdef AudioFrame template cdef readonly uint64_t samples_written cdef readonly uint64_t samples_read cdef readonly double pts_per_sample cpdef write(self, AudioFrame frame) cpdef read(self, int samples=*, bint partial=*) cpdef read_many(self, int samples, bint partial=*) PyAV-14.2.0/av/audio/fifo.pyi000066400000000000000000000013101475734227400156040ustar00rootroot00000000000000from .format import AudioFormat from .frame import AudioFrame from .layout import AudioLayout class AudioFifo: def write(self, frame: AudioFrame) -> None: ... def read(self, samples: int = 0, partial: bool = False) -> AudioFrame | None: ... def read_many(self, samples: int, partial: bool = False) -> list[AudioFrame]: ... @property def format(self) -> AudioFormat: ... @property def layout(self) -> AudioLayout: ... @property def sample_rate(self) -> int: ... @property def samples(self) -> int: ... @property def samples_written(self) -> int: ... @property def samples_read(self) -> int: ... @property def pts_per_sample(self) -> float: ... PyAV-14.2.0/av/audio/fifo.pyx000066400000000000000000000142001475734227400156250ustar00rootroot00000000000000from av.audio.frame cimport alloc_audio_frame from av.error cimport err_check cdef class AudioFifo: """A simple audio sample FIFO (First In First Out) buffer.""" def __repr__(self): try: result = ( f"" ) except AttributeError: result = ( f"" ) return result def __dealloc__(self): if self.ptr: lib.av_audio_fifo_free(self.ptr) cpdef write(self, AudioFrame frame): """write(frame) Push a frame of samples into the queue. :param AudioFrame frame: The frame of samples to push. The FIFO will remember the attributes from the first frame, and use those to populate all output frames. If there is a :attr:`~.Frame.pts` and :attr:`~.Frame.time_base` and :attr:`~.AudioFrame.sample_rate`, then the FIFO will assert that the incoming timestamps are continuous. """ if frame is None: raise TypeError("AudioFifo must be given an AudioFrame.") if not frame.ptr.nb_samples: return if not self.ptr: # Hold onto a copy of the attributes of the first frame to populate # output frames with. self.template = alloc_audio_frame() self.template._copy_internal_attributes(frame) self.template._init_user_attributes() # Figure out our "time_base". if frame._time_base.num and frame.ptr.sample_rate: self.pts_per_sample = frame._time_base.den / float(frame._time_base.num) self.pts_per_sample /= frame.ptr.sample_rate else: self.pts_per_sample = 0 self.ptr = lib.av_audio_fifo_alloc( frame.ptr.format, frame.layout.nb_channels, frame.ptr.nb_samples * 2, # Just a default number of samples; it will adjust. ) if not self.ptr: raise RuntimeError("Could not allocate AVAudioFifo.") # Make sure nothing changed. elif ( frame.ptr.format != self.template.ptr.format or # TODO: frame.ptr.ch_layout != self.template.ptr.ch_layout or frame.ptr.sample_rate != self.template.ptr.sample_rate or (frame._time_base.num and self.template._time_base.num and ( frame._time_base.num != self.template._time_base.num or frame._time_base.den != self.template._time_base.den )) ): raise ValueError("Frame does not match AudioFifo parameters.") # Assert that the PTS are what we expect. cdef int64_t expected_pts if self.pts_per_sample and frame.ptr.pts != lib.AV_NOPTS_VALUE: expected_pts = (self.pts_per_sample * self.samples_written) if frame.ptr.pts != expected_pts: raise ValueError( "Frame.pts (%d) != expected (%d); fix or set to None." % (frame.ptr.pts, expected_pts) ) err_check(lib.av_audio_fifo_write( self.ptr, frame.ptr.extended_data, frame.ptr.nb_samples, )) self.samples_written += frame.ptr.nb_samples cpdef read(self, int samples=0, bint partial=False): """read(samples=0, partial=False) Read samples from the queue. :param int samples: The number of samples to pull; 0 gets all. :param bool partial: Allow returning less than requested. :returns: New :class:`AudioFrame` or ``None`` (if empty). If the incoming frames had valid a :attr:`~.Frame.time_base`, :attr:`~.AudioFrame.sample_rate` and :attr:`~.Frame.pts`, the returned frames will have accurate timing. """ if not self.ptr: return cdef int buffered_samples = lib.av_audio_fifo_size(self.ptr) if buffered_samples < 1: return samples = samples or buffered_samples if buffered_samples < samples: if partial: samples = buffered_samples else: return cdef AudioFrame frame = alloc_audio_frame() frame._copy_internal_attributes(self.template) frame._init( self.template.ptr.format, self.template.ptr.ch_layout, samples, 1, # Align? ) err_check(lib.av_audio_fifo_read( self.ptr, frame.ptr.extended_data, samples, )) if self.pts_per_sample: frame.ptr.pts = (self.pts_per_sample * self.samples_read) else: frame.ptr.pts = lib.AV_NOPTS_VALUE self.samples_read += samples return frame cpdef read_many(self, int samples, bint partial=False): """read_many(samples, partial=False) Read as many frames as we can. :param int samples: How large for the frames to be. :param bool partial: If we should return a partial frame. :returns: A ``list`` of :class:`AudioFrame`. """ cdef AudioFrame frame frames = [] while True: frame = self.read(samples, partial=partial) if frame is not None: frames.append(frame) else: break return frames @property def format(self): """The :class:`.AudioFormat` of this FIFO.""" return self.template.format @property def layout(self): """The :class:`.AudioLayout` of this FIFO.""" return self.template.layout @property def sample_rate(self): return self.template.sample_rate @property def samples(self): """Number of audio samples (per channel) in the buffer.""" return lib.av_audio_fifo_size(self.ptr) if self.ptr else 0 PyAV-14.2.0/av/audio/format.pxd000066400000000000000000000003131475734227400161450ustar00rootroot00000000000000cimport libav as lib cdef class AudioFormat: cdef lib.AVSampleFormat sample_fmt cdef _init(self, lib.AVSampleFormat sample_fmt) cdef AudioFormat get_audio_format(lib.AVSampleFormat format) PyAV-14.2.0/av/audio/format.pyi000066400000000000000000000003541475734227400161600ustar00rootroot00000000000000class AudioFormat: name: str bytes: int bits: int is_planar: bool is_packed: bool planar: AudioFormat packed: AudioFormat container_name: str def __init__(self, name: str | AudioFormat) -> None: ... PyAV-14.2.0/av/audio/format.pyx000066400000000000000000000070641475734227400162040ustar00rootroot00000000000000import sys cdef str container_format_postfix = "le" if sys.byteorder == "little" else "be" cdef object _cinit_bypass_sentinel cdef AudioFormat get_audio_format(lib.AVSampleFormat c_format): """Get an AudioFormat without going through a string.""" if c_format < 0: return None cdef AudioFormat format = AudioFormat.__new__(AudioFormat, _cinit_bypass_sentinel) format._init(c_format) return format cdef class AudioFormat: """Descriptor of audio formats.""" def __cinit__(self, name): if name is _cinit_bypass_sentinel: return cdef lib.AVSampleFormat sample_fmt if isinstance(name, AudioFormat): sample_fmt = (name).sample_fmt else: sample_fmt = lib.av_get_sample_fmt(name) if sample_fmt < 0: raise ValueError(f"Not a sample format: {name!r}") self._init(sample_fmt) cdef _init(self, lib.AVSampleFormat sample_fmt): self.sample_fmt = sample_fmt def __repr__(self): return f"" @property def name(self): """Canonical name of the sample format. >>> AudioFormat('s16p').name 's16p' """ return lib.av_get_sample_fmt_name(self.sample_fmt) @property def bytes(self): """Number of bytes per sample. >>> AudioFormat('s16p').bytes 2 """ return lib.av_get_bytes_per_sample(self.sample_fmt) @property def bits(self): """Number of bits per sample. >>> AudioFormat('s16p').bits 16 """ return lib.av_get_bytes_per_sample(self.sample_fmt) << 3 @property def is_planar(self): """Is this a planar format? Strictly opposite of :attr:`is_packed`. """ return bool(lib.av_sample_fmt_is_planar(self.sample_fmt)) @property def is_packed(self): """Is this a planar format? Strictly opposite of :attr:`is_planar`. """ return not lib.av_sample_fmt_is_planar(self.sample_fmt) @property def planar(self): """The planar variant of this format. Is itself when planar: >>> fmt = AudioFormat('s16p') >>> fmt.planar is fmt True """ if self.is_planar: return self return get_audio_format(lib.av_get_planar_sample_fmt(self.sample_fmt)) @property def packed(self): """The packed variant of this format. Is itself when packed: >>> fmt = AudioFormat('s16') >>> fmt.packed is fmt True """ if self.is_packed: return self return get_audio_format(lib.av_get_packed_sample_fmt(self.sample_fmt)) @property def container_name(self): """The name of a :class:`ContainerFormat` which directly accepts this data. :raises ValueError: when planar, since there are no such containers. """ if self.is_planar: raise ValueError("no planar container formats") if self.sample_fmt == lib.AV_SAMPLE_FMT_U8: return "u8" elif self.sample_fmt == lib.AV_SAMPLE_FMT_S16: return "s16" + container_format_postfix elif self.sample_fmt == lib.AV_SAMPLE_FMT_S32: return "s32" + container_format_postfix elif self.sample_fmt == lib.AV_SAMPLE_FMT_FLT: return "f32" + container_format_postfix elif self.sample_fmt == lib.AV_SAMPLE_FMT_DBL: return "f64" + container_format_postfix raise ValueError("unknown layout") PyAV-14.2.0/av/audio/frame.pxd000066400000000000000000000013431475734227400157530ustar00rootroot00000000000000cimport libav as lib from libc.stdint cimport uint8_t, uint64_t from av.audio.format cimport AudioFormat from av.audio.layout cimport AudioLayout from av.frame cimport Frame cdef class AudioFrame(Frame): # For raw storage of the frame's data; don't ever touch this. cdef uint8_t *_buffer cdef size_t _buffer_size cdef readonly AudioLayout layout """ The audio channel layout. :type: AudioLayout """ cdef readonly AudioFormat format """ The audio sample format. :type: AudioFormat """ cdef _init(self, lib.AVSampleFormat format, lib.AVChannelLayout layout, unsigned int nb_samples, unsigned int align) cdef _init_user_attributes(self) cdef AudioFrame alloc_audio_frame() PyAV-14.2.0/av/audio/frame.pyi000066400000000000000000000025021475734227400157570ustar00rootroot00000000000000from typing import Any, Union import numpy as np from av.frame import Frame from .format import AudioFormat from .layout import AudioLayout from .plane import AudioPlane format_dtypes: dict[str, str] _SupportedNDarray = Union[ np.ndarray[Any, np.dtype[np.float64]], # f8 np.ndarray[Any, np.dtype[np.float32]], # f4 np.ndarray[Any, np.dtype[np.int32]], # i4 np.ndarray[Any, np.dtype[np.int16]], # i2 np.ndarray[Any, np.dtype[np.uint8]], # u1 ] class _Format: def __get__(self, i: object | None, owner: type | None = None) -> AudioFormat: ... def __set__(self, instance: object, value: AudioFormat | str) -> None: ... class _Layout: def __get__(self, i: object | None, owner: type | None = None) -> AudioLayout: ... def __set__(self, instance: object, value: AudioLayout | str) -> None: ... class AudioFrame(Frame): planes: tuple[AudioPlane, ...] samples: int sample_rate: int rate: int format: _Format layout: _Layout def __init__( self, format: str = "s16", layout: str = "stereo", samples: int = 0, align: int = 1, ) -> None: ... @staticmethod def from_ndarray( array: _SupportedNDarray, format: str = "s16", layout: str = "stereo" ) -> AudioFrame: ... def to_ndarray(self) -> _SupportedNDarray: ... PyAV-14.2.0/av/audio/frame.pyx000066400000000000000000000126721475734227400160070ustar00rootroot00000000000000from av.audio.format cimport get_audio_format from av.audio.layout cimport get_audio_layout from av.audio.plane cimport AudioPlane from av.error cimport err_check from av.utils cimport check_ndarray cdef object _cinit_bypass_sentinel format_dtypes = { "dbl": "f8", "dblp": "f8", "flt": "f4", "fltp": "f4", "s16": "i2", "s16p": "i2", "s32": "i4", "s32p": "i4", "u8": "u1", "u8p": "u1", } cdef AudioFrame alloc_audio_frame(): """Get a mostly uninitialized AudioFrame. You MUST call AudioFrame._init(...) or AudioFrame._init_user_attributes() before exposing to the user. """ return AudioFrame.__new__(AudioFrame, _cinit_bypass_sentinel) cdef class AudioFrame(Frame): """A frame of audio.""" def __cinit__(self, format="s16", layout="stereo", samples=0, align=1): if format is _cinit_bypass_sentinel: return cdef AudioFormat cy_format = AudioFormat(format) cdef AudioLayout cy_layout = AudioLayout(layout) self._init(cy_format.sample_fmt, cy_layout.layout, samples, align) cdef _init(self, lib.AVSampleFormat format, lib.AVChannelLayout layout, unsigned int nb_samples, unsigned int align): self.ptr.nb_samples = nb_samples self.ptr.format = format self.ptr.ch_layout = layout # Sometimes this is called twice. Oh well. self._init_user_attributes() if self.layout.nb_channels != 0 and nb_samples: # Cleanup the old buffer. lib.av_freep(&self._buffer) # Get a new one. self._buffer_size = err_check(lib.av_samples_get_buffer_size( NULL, self.layout.nb_channels, nb_samples, format, align )) self._buffer = lib.av_malloc(self._buffer_size) if not self._buffer: raise MemoryError("cannot allocate AudioFrame buffer") # Connect the data pointers to the buffer. err_check(lib.avcodec_fill_audio_frame( self.ptr, self.layout.nb_channels, self.ptr.format, self._buffer, self._buffer_size, align )) def __dealloc__(self): lib.av_freep(&self._buffer) cdef _init_user_attributes(self): self.layout = get_audio_layout(self.ptr.ch_layout) self.format = get_audio_format(self.ptr.format) def __repr__(self): return ( f"" cdef object _cinit_bypass_sentinel cdef AudioLayout get_audio_layout(lib.AVChannelLayout c_layout): """Get an AudioLayout from Cython land.""" cdef AudioLayout layout = AudioLayout.__new__(AudioLayout, _cinit_bypass_sentinel) layout._init(c_layout) return layout cdef class AudioLayout: def __init__(self, layout): if layout is _cinit_bypass_sentinel: return if type(layout) is str: ret = lib.av_channel_layout_from_string(&c_layout, layout) if ret != 0: raise ValueError(f"Invalid layout: {layout}") elif isinstance(layout, AudioLayout): c_layout = (layout).layout else: raise TypeError(f"layout must be of type: string | av.AudioLayout, got {type(layout)}") self._init(c_layout) cdef _init(self, lib.AVChannelLayout layout): self.layout = layout def __repr__(self): return f"" def __eq__(self, other): return isinstance(other, AudioLayout) and self.name == other.name and self.nb_channels == other.nb_channels @property def nb_channels(self): return self.layout.nb_channels @property def channels(self): cdef lib.AVChannel channel cdef char buf[16] cdef char buf2[128] results = [] for index in range(self.layout.nb_channels): channel = lib.av_channel_layout_channel_from_index(&self.layout, index); size = lib.av_channel_name(buf, sizeof(buf), channel) - 1 size2 = lib.av_channel_description(buf2, sizeof(buf2), channel) - 1 results.append( AudioChannel( PyBytes_FromStringAndSize(buf, size).decode("utf-8"), PyBytes_FromStringAndSize(buf2, size2).decode("utf-8"), ) ) return tuple(results) @property def name(self) -> str: """The canonical name of the audio layout.""" cdef char layout_name[128] cdef int ret ret = lib.av_channel_layout_describe(&self.layout, layout_name, sizeof(layout_name)) if ret < 0: raise RuntimeError(f"Failed to get layout name: {ret}") return layout_namePyAV-14.2.0/av/audio/plane.pxd000066400000000000000000000002061475734227400157550ustar00rootroot00000000000000from av.plane cimport Plane cdef class AudioPlane(Plane): cdef readonly size_t buffer_size cdef size_t _buffer_size(self) PyAV-14.2.0/av/audio/plane.pyi000066400000000000000000000001121475734227400157570ustar00rootroot00000000000000from av.plane import Plane class AudioPlane(Plane): buffer_size: int PyAV-14.2.0/av/audio/plane.pyx000066400000000000000000000005151475734227400160050ustar00rootroot00000000000000from av.audio.frame cimport AudioFrame cdef class AudioPlane(Plane): def __cinit__(self, AudioFrame frame, int index): # Only the first linesize is ever populated, but it applies to every plane. self.buffer_size = self.frame.ptr.linesize[0] cdef size_t _buffer_size(self): return self.buffer_size PyAV-14.2.0/av/audio/resampler.pxd000066400000000000000000000007501475734227400166540ustar00rootroot00000000000000from av.audio.format cimport AudioFormat from av.audio.frame cimport AudioFrame from av.audio.layout cimport AudioLayout from av.filter.graph cimport Graph cdef class AudioResampler: cdef readonly bint is_passthrough cdef AudioFrame template # Destination descriptors cdef readonly AudioFormat format cdef readonly AudioLayout layout cdef readonly int rate cdef readonly unsigned int frame_size cdef Graph graph cpdef resample(self, AudioFrame) PyAV-14.2.0/av/audio/resampler.pyi000066400000000000000000000010361475734227400166600ustar00rootroot00000000000000from av.filter.graph import Graph from .format import AudioFormat from .frame import AudioFrame from .layout import AudioLayout class AudioResampler: rate: int frame_size: int format: AudioFormat graph: Graph | None def __init__( self, format: str | int | AudioFormat | None = None, layout: str | int | AudioLayout | None = None, rate: int | None = None, frame_size: int | None = None, ) -> None: ... def resample(self, frame: AudioFrame | None) -> list[AudioFrame]: ... PyAV-14.2.0/av/audio/resampler.pyx000066400000000000000000000076021475734227400167040ustar00rootroot00000000000000from av.filter.context cimport FilterContext import errno import av.filter cdef class AudioResampler: """AudioResampler(format=None, layout=None, rate=None) :param AudioFormat format: The target format, or string that parses to one (e.g. ``"s16"``). :param AudioLayout layout: The target layout, or an int/string that parses to one (e.g. ``"stereo"``). :param int rate: The target sample rate. """ def __cinit__(self, format=None, layout=None, rate=None, frame_size=None): if format is not None: self.format = format if isinstance(format, AudioFormat) else AudioFormat(format) if layout is not None: self.layout = AudioLayout(layout) self.rate = int(rate) if rate else 0 self.frame_size = int(frame_size) if frame_size else 0 self.graph = None cpdef resample(self, AudioFrame frame): """resample(frame) Convert the ``sample_rate``, ``channel_layout`` and/or ``format`` of a :class:`~.AudioFrame`. :param AudioFrame frame: The frame to convert or `None` to flush. :returns: A list of :class:`AudioFrame` in new parameters. If the nothing is to be done return the same frame as a single element list. """ # We don't have any input, so don't bother even setting up. if not self.graph and frame is None: return [] # Shortcut for passthrough. if self.is_passthrough: return [frame] # Take source settings from the first frame. if not self.graph: self.template = frame # Set some default descriptors. self.format = self.format or frame.format self.layout = self.layout or frame.layout self.rate = self.rate or frame.sample_rate # Check if we can passthrough or if there is actually work to do. if ( frame.format.sample_fmt == self.format.sample_fmt and frame.layout == self.layout and frame.sample_rate == self.rate and self.frame_size == 0 ): self.is_passthrough = True return [frame] # handle resampling with aformat filter # (similar to configure_output_audio_filter from ffmpeg) self.graph = av.filter.Graph() extra_args = {} if frame.time_base is not None: extra_args["time_base"] = str(frame.time_base) abuffer = self.graph.add( "abuffer", sample_rate=str(frame.sample_rate), sample_fmt=AudioFormat(frame.format).name, channel_layout=frame.layout.name, **extra_args, ) aformat = self.graph.add( "aformat", sample_rates=str(self.rate), sample_fmts=self.format.name, channel_layouts=self.layout.name, ) abuffersink = self.graph.add("abuffersink") abuffer.link_to(aformat) aformat.link_to(abuffersink) self.graph.configure() if self.frame_size > 0: self.graph.set_audio_frame_size(self.frame_size) if frame is not None: if ( frame.format.sample_fmt != self.template.format.sample_fmt or frame.layout != self.template.layout or frame.sample_rate != self.template.rate ): raise ValueError("Frame does not match AudioResampler setup.") self.graph.push(frame) output = [] while True: try: output.append(self.graph.pull()) except EOFError: break except av.FFmpegError as e: if e.errno != errno.EAGAIN: raise break return output PyAV-14.2.0/av/audio/stream.pxd000066400000000000000000000003211475734227400161470ustar00rootroot00000000000000from av.packet cimport Packet from av.stream cimport Stream from .frame cimport AudioFrame cdef class AudioStream(Stream): cpdef encode(self, AudioFrame frame=?) cpdef decode(self, Packet packet=?) PyAV-14.2.0/av/audio/stream.pyi000066400000000000000000000017351475734227400161670ustar00rootroot00000000000000from typing import Literal from av.packet import Packet from av.stream import Stream from .codeccontext import AudioCodecContext from .format import AudioFormat from .frame import AudioFrame from .layout import AudioLayout class _Format: def __get__(self, i: object | None, owner: type | None = None) -> AudioFormat: ... def __set__(self, instance: object, value: AudioFormat | str) -> None: ... class _Layout: def __get__(self, i: object | None, owner: type | None = None) -> AudioLayout: ... def __set__(self, instance: object, value: AudioLayout | str) -> None: ... class AudioStream(Stream): codec_context: AudioCodecContext def encode(self, frame: AudioFrame | None = None) -> list[Packet]: ... def decode(self, packet: Packet | None = None) -> list[AudioFrame]: ... # From codec context frame_size: int sample_rate: int bit_rate: int rate: int channels: int type: Literal["audio"] format: _Format layout: _Layout PyAV-14.2.0/av/audio/stream.pyx000066400000000000000000000023001475734227400161730ustar00rootroot00000000000000from av.packet cimport Packet from .frame cimport AudioFrame cdef class AudioStream(Stream): def __repr__(self): form = self.format.name if self.format else None return ( f"" ) def __getattr__(self, name): return getattr(self.codec_context, name) cpdef encode(self, AudioFrame frame=None): """ Encode an :class:`.AudioFrame` and return a list of :class:`.Packet`. :rtype: list[Packet] .. seealso:: This is mostly a passthrough to :meth:`.CodecContext.encode`. """ packets = self.codec_context.encode(frame) cdef Packet packet for packet in packets: packet._stream = self packet.ptr.stream_index = self.ptr.index return packets cpdef decode(self, Packet packet=None): """ Decode a :class:`.Packet` and return a list of :class:`.AudioFrame`. :rtype: list[AudioFrame] .. seealso:: This is a passthrough to :meth:`.CodecContext.decode`. """ return self.codec_context.decode(packet) PyAV-14.2.0/av/bitstream.pxd000066400000000000000000000002701475734227400155500ustar00rootroot00000000000000cimport libav as lib from av.packet cimport Packet cdef class BitStreamFilterContext: cdef lib.AVBSFContext *ptr cpdef filter(self, Packet packet=?) cpdef flush(self) PyAV-14.2.0/av/bitstream.pyi000066400000000000000000000006051475734227400155600ustar00rootroot00000000000000from .packet import Packet from .stream import Stream class BitStreamFilterContext: def __init__( self, filter_description: str | bytes, in_stream: Stream | None = None, out_stream: Stream | None = None, ): ... def filter(self, packet: Packet | None) -> list[Packet]: ... def flush(self) -> None: ... bitstream_filters_available: set[str] PyAV-14.2.0/av/bitstream.pyx000066400000000000000000000053601475734227400156020ustar00rootroot00000000000000cimport libav as lib from libc.errno cimport EAGAIN from av.error cimport err_check from av.packet cimport Packet from av.stream cimport Stream cdef class BitStreamFilterContext: """ Initializes a bitstream filter: a way to directly modify packet data. Wraps :ffmpeg:`AVBSFContext` :param Stream in_stream: A stream that defines the input codec for the bitfilter. :param Stream out_stream: A stream whose codec is overwritten using the output parameters from the bitfilter. """ def __cinit__(self, filter_description, Stream in_stream=None, Stream out_stream=None): cdef int res cdef char *filter_str = filter_description with nogil: res = lib.av_bsf_list_parse_str(filter_str, &self.ptr) err_check(res) if in_stream is not None: with nogil: res = lib.avcodec_parameters_copy(self.ptr.par_in, in_stream.ptr.codecpar) err_check(res) with nogil: res = lib.av_bsf_init(self.ptr) err_check(res) if out_stream is not None: with nogil: res = lib.avcodec_parameters_copy(out_stream.ptr.codecpar, self.ptr.par_out) err_check(res) lib.avcodec_parameters_to_context(out_stream.codec_context.ptr, out_stream.ptr.codecpar) def __dealloc__(self): if self.ptr: lib.av_bsf_free(&self.ptr) cpdef filter(self, Packet packet=None): """ Processes a packet based on the filter_description set during initialization. Multiple packets may be created. :type: list[Packet] """ cdef int res cdef Packet new_packet with nogil: res = lib.av_bsf_send_packet(self.ptr, packet.ptr if packet is not None else NULL) err_check(res) output = [] while True: new_packet = Packet() with nogil: res = lib.av_bsf_receive_packet(self.ptr, new_packet.ptr) if res == -EAGAIN or res == lib.AVERROR_EOF: return output err_check(res) if res: return output output.append(new_packet) cpdef flush(self): """ Reset the internal state of the filter. Should be called e.g. when seeking. Can be used to make the filter usable again after draining it with EOF marker packet. """ lib.av_bsf_flush(self.ptr) cdef get_filter_names(): names = set() cdef const lib.AVBitStreamFilter *ptr cdef void *opaque = NULL while True: ptr = lib.av_bsf_iterate(&opaque) if ptr: names.add(ptr.name) else: break return names bitstream_filters_available = get_filter_names() PyAV-14.2.0/av/buffer.pxd000066400000000000000000000001761475734227400150340ustar00rootroot00000000000000 cdef class Buffer: cdef size_t _buffer_size(self) cdef void* _buffer_ptr(self) cdef bint _buffer_writable(self) PyAV-14.2.0/av/buffer.pyi000066400000000000000000000004741475734227400150430ustar00rootroot00000000000000# When Python 3.12 becomes our lowest supported version, we could make this # class inherit `collections.abc.Buffer`. class Buffer: buffer_size: int buffer_ptr: int def update(self, input: bytes) -> None: ... def __buffer__(self, flags: int) -> memoryview: ... def __bytes__(self) -> bytes: ... PyAV-14.2.0/av/buffer.pyx000066400000000000000000000031421475734227400150550ustar00rootroot00000000000000from cpython cimport PyBUF_WRITABLE, PyBuffer_FillInfo from libc.string cimport memcpy from av.bytesource cimport ByteSource, bytesource cdef class Buffer: """A base class for PyAV objects which support the buffer protocol, such as :class:`.Packet` and :class:`.Plane`. """ cdef size_t _buffer_size(self): return 0 cdef void* _buffer_ptr(self): return NULL cdef bint _buffer_writable(self): return True def __getbuffer__(self, Py_buffer *view, int flags): if flags & PyBUF_WRITABLE and not self._buffer_writable(): raise ValueError("buffer is not writable") PyBuffer_FillInfo(view, self, self._buffer_ptr(), self._buffer_size(), 0, flags) @property def buffer_size(self): """The size of the buffer in bytes.""" return self._buffer_size() @property def buffer_ptr(self): """The memory address of the buffer.""" return self._buffer_ptr() def update(self, input): """Replace the data in this object with the given buffer. Accepts anything that supports the `buffer protocol `_, e.g. bytes, Numpy arrays, other :class:`Buffer` objects, etc.. """ if not self._buffer_writable(): raise ValueError("buffer is not writable") cdef ByteSource source = bytesource(input) cdef size_t size = self._buffer_size() if source.length != size: raise ValueError(f"got {source.length} bytes; need {size} bytes") memcpy(self._buffer_ptr(), source.ptr, size) PyAV-14.2.0/av/bytesource.pxd000066400000000000000000000003611475734227400157430ustar00rootroot00000000000000from cpython.buffer cimport Py_buffer cdef class ByteSource: cdef object owner cdef bint has_view cdef Py_buffer view cdef unsigned char *ptr cdef size_t length cdef ByteSource bytesource(object, bint allow_none=*) PyAV-14.2.0/av/bytesource.pyx000066400000000000000000000021071475734227400157700ustar00rootroot00000000000000from cpython.buffer cimport ( PyBUF_SIMPLE, PyBuffer_Release, PyObject_CheckBuffer, PyObject_GetBuffer, ) cdef class ByteSource: def __cinit__(self, owner): self.owner = owner try: self.ptr = owner except TypeError: pass else: self.length = len(owner) return if PyObject_CheckBuffer(owner): # Can very likely use PyBUF_ND instead of PyBUF_SIMPLE res = PyObject_GetBuffer(owner, &self.view, PyBUF_SIMPLE) if not res: self.has_view = True self.ptr = self.view.buf self.length = self.view.len return raise TypeError("expected bytes, bytearray or memoryview") def __dealloc__(self): if self.has_view: PyBuffer_Release(&self.view) cdef ByteSource bytesource(obj, bint allow_none=False): if allow_none and obj is None: return elif isinstance(obj, ByteSource): return obj else: return ByteSource(obj) PyAV-14.2.0/av/codec/000077500000000000000000000000001475734227400141175ustar00rootroot00000000000000PyAV-14.2.0/av/codec/__init__.pxd000066400000000000000000000000001475734227400163610ustar00rootroot00000000000000PyAV-14.2.0/av/codec/__init__.py000066400000000000000000000003771475734227400162370ustar00rootroot00000000000000from .codec import Capabilities, Codec, Properties, codec_descriptor, codecs_available from .context import CodecContext __all__ = ( "Capabilities", "Codec", "Properties", "codec_descriptor", "codecs_available", "CodecContext", ) PyAV-14.2.0/av/codec/codec.pxd000066400000000000000000000004071475734227400157120ustar00rootroot00000000000000cimport libav as lib cdef class Codec: cdef const lib.AVCodec *ptr cdef const lib.AVCodecDescriptor *desc cdef readonly bint is_encoder cdef tuple _hardware_configs cdef _init(self, name=?) cdef Codec wrap_codec(const lib.AVCodec *ptr) PyAV-14.2.0/av/codec/codec.pyi000066400000000000000000000067331475734227400157300ustar00rootroot00000000000000from enum import Flag, IntEnum from fractions import Fraction from typing import ClassVar, Literal, cast, overload from av.audio.codeccontext import AudioCodecContext from av.audio.format import AudioFormat from av.descriptor import Descriptor from av.subtitles.codeccontext import SubtitleCodecContext from av.video.codeccontext import VideoCodecContext from av.video.format import VideoFormat from .context import CodecContext class Properties(Flag): NONE = cast(ClassVar[Properties], ...) INTRA_ONLY = cast(ClassVar[Properties], ...) LOSSY = cast(ClassVar[Properties], ...) LOSSLESS = cast(ClassVar[Properties], ...) REORDER = cast(ClassVar[Properties], ...) BITMAP_SUB = cast(ClassVar[Properties], ...) TEXT_SUB = cast(ClassVar[Properties], ...) class Capabilities(IntEnum): none = cast(int, ...) draw_horiz_band = cast(int, ...) dr1 = cast(int, ...) hwaccel = cast(int, ...) delay = cast(int, ...) small_last_frame = cast(int, ...) hwaccel_vdpau = cast(int, ...) subframes = cast(int, ...) experimental = cast(int, ...) channel_conf = cast(int, ...) neg_linesizes = cast(int, ...) frame_threads = cast(int, ...) slice_threads = cast(int, ...) param_change = cast(int, ...) auto_threads = cast(int, ...) variable_frame_size = cast(int, ...) avoid_probing = cast(int, ...) hardware = cast(int, ...) hybrid = cast(int, ...) encoder_reordered_opaque = cast(int, ...) encoder_flush = cast(int, ...) encoder_recon_frame = cast(int, ...) class UnknownCodecError(ValueError): ... class Codec: @property def is_encoder(self) -> bool: ... @property def is_decoder(self) -> bool: ... @property def mode(self) -> Literal["r", "w"]: ... descriptor: Descriptor @property def name(self) -> str: ... @property def canonical_name(self) -> str: ... @property def long_name(self) -> str: ... @property def type(self) -> Literal["video", "audio", "data", "subtitle", "attachment"]: ... @property def id(self) -> int: ... frame_rates: list[Fraction] | None audio_rates: list[int] | None video_formats: list[VideoFormat] | None audio_formats: list[AudioFormat] | None @property def properties(self) -> int: ... @property def intra_only(self) -> bool: ... @property def lossy(self) -> bool: ... @property def lossless(self) -> bool: ... @property def reorder(self) -> bool: ... @property def bitmap_sub(self) -> bool: ... @property def text_sub(self) -> bool: ... @property def capabilities(self) -> int: ... @property def experimental(self) -> bool: ... @property def delay(self) -> bool: ... def __init__(self, name: str, mode: Literal["r", "w"] = "r") -> None: ... @overload def create(self, kind: Literal["video"]) -> VideoCodecContext: ... @overload def create(self, kind: Literal["audio"]) -> AudioCodecContext: ... @overload def create(self, kind: Literal["subtitle"]) -> SubtitleCodecContext: ... @overload def create(self, kind: None = None) -> CodecContext: ... @overload def create( self, kind: Literal["video", "audio", "subtitle"] | None = None ) -> ( VideoCodecContext | AudioCodecContext | SubtitleCodecContext | CodecContext ): ... class codec_descriptor: name: str options: tuple[int, ...] codecs_available: set[str] def dump_codecs() -> None: ... def dump_hwconfigs() -> None: ... PyAV-14.2.0/av/codec/codec.pyx000066400000000000000000000252341475734227400157440ustar00rootroot00000000000000cimport libav as lib from av.audio.format cimport get_audio_format from av.codec.hwaccel cimport wrap_hwconfig from av.descriptor cimport wrap_avclass from av.utils cimport avrational_to_fraction from av.video.format cimport get_video_format from enum import Flag, IntEnum cdef object _cinit_sentinel = object() cdef Codec wrap_codec(const lib.AVCodec *ptr): cdef Codec codec = Codec(_cinit_sentinel) codec.ptr = ptr codec.is_encoder = lib.av_codec_is_encoder(ptr) codec._init() return codec class Properties(Flag): NONE = 0 INTRA_ONLY = lib.AV_CODEC_PROP_INTRA_ONLY LOSSY = lib.AV_CODEC_PROP_LOSSY LOSSLESS = lib.AV_CODEC_PROP_LOSSLESS REORDER = lib.AV_CODEC_PROP_REORDER BITMAP_SUB = lib.AV_CODEC_PROP_BITMAP_SUB TEXT_SUB = lib.AV_CODEC_PROP_TEXT_SUB class Capabilities(IntEnum): none = 0 draw_horiz_band = lib.AV_CODEC_CAP_DRAW_HORIZ_BAND dr1 = lib.AV_CODEC_CAP_DR1 hwaccel = 1 << 4 delay = lib.AV_CODEC_CAP_DELAY small_last_frame = lib.AV_CODEC_CAP_SMALL_LAST_FRAME hwaccel_vdpau = 1 << 7 subframes = lib.AV_CODEC_CAP_SUBFRAMES experimental = lib.AV_CODEC_CAP_EXPERIMENTAL channel_conf = lib.AV_CODEC_CAP_CHANNEL_CONF neg_linesizes = 1 << 11 frame_threads = lib.AV_CODEC_CAP_FRAME_THREADS slice_threads = lib.AV_CODEC_CAP_SLICE_THREADS param_change = lib.AV_CODEC_CAP_PARAM_CHANGE auto_threads = lib.AV_CODEC_CAP_OTHER_THREADS variable_frame_size = lib.AV_CODEC_CAP_VARIABLE_FRAME_SIZE avoid_probing = lib.AV_CODEC_CAP_AVOID_PROBING hardware = lib.AV_CODEC_CAP_HARDWARE hybrid = lib.AV_CODEC_CAP_HYBRID encoder_reordered_opaque = 1 << 20 encoder_flush = 1 << 21 encoder_recon_frame = 1 << 22 class UnknownCodecError(ValueError): pass cdef class Codec: """Codec(name, mode='r') :param str name: The codec name. :param str mode: ``'r'`` for decoding or ``'w'`` for encoding. This object exposes information about an available codec, and an avenue to create a :class:`.CodecContext` to encode/decode directly. :: >>> codec = Codec('mpeg4', 'r') >>> codec.name 'mpeg4' >>> codec.type 'video' >>> codec.is_encoder False """ def __cinit__(self, name, mode="r"): if name is _cinit_sentinel: return if mode == "w": self.ptr = lib.avcodec_find_encoder_by_name(name) if not self.ptr: self.desc = lib.avcodec_descriptor_get_by_name(name) if self.desc: self.ptr = lib.avcodec_find_encoder(self.desc.id) elif mode == "r": self.ptr = lib.avcodec_find_decoder_by_name(name) if not self.ptr: self.desc = lib.avcodec_descriptor_get_by_name(name) if self.desc: self.ptr = lib.avcodec_find_decoder(self.desc.id) else: raise ValueError('Invalid mode; must be "r" or "w".', mode) self._init(name) # Sanity check. if (mode == "w") != self.is_encoder: raise RuntimeError("Found codec does not match mode.", name, mode) cdef _init(self, name=None): if not self.ptr: raise UnknownCodecError(name) if not self.desc: self.desc = lib.avcodec_descriptor_get(self.ptr.id) if not self.desc: raise RuntimeError("No codec descriptor for %r." % name) self.is_encoder = lib.av_codec_is_encoder(self.ptr) # Sanity check. if self.is_encoder and lib.av_codec_is_decoder(self.ptr): raise RuntimeError("%s is both encoder and decoder.") def __repr__(self): mode = self.mode return f"" def create(self, kind = None): """Create a :class:`.CodecContext` for this codec. :param str kind: Gives a hint to static type checkers for what exact CodecContext is used. """ from .context import CodecContext return CodecContext.create(self) @property def mode(self): return "w" if self.is_encoder else "r" @property def is_decoder(self): return not self.is_encoder @property def descriptor(self): return wrap_avclass(self.ptr.priv_class) @property def name(self): return self.ptr.name or "" @property def canonical_name(self): """ Returns the name of the codec, not a specific encoder. """ return lib.avcodec_get_name(self.ptr.id) @property def long_name(self): return self.ptr.long_name or "" @property def type(self): """ The media type of this codec. E.g: ``'audio'``, ``'video'``, ``'subtitle'``. """ return lib.av_get_media_type_string(self.ptr.type) @property def id(self): return self.ptr.id @property def frame_rates(self): """A list of supported frame rates (:class:`fractions.Fraction`), or ``None``.""" if not self.ptr.supported_framerates: return ret = [] cdef int i = 0 while self.ptr.supported_framerates[i].denum: ret.append(avrational_to_fraction(&self.ptr.supported_framerates[i])) i += 1 return ret @property def audio_rates(self): """A list of supported audio sample rates (``int``), or ``None``.""" if not self.ptr.supported_samplerates: return ret = [] cdef int i = 0 while self.ptr.supported_samplerates[i]: ret.append(self.ptr.supported_samplerates[i]) i += 1 return ret @property def video_formats(self): """A list of supported :class:`.VideoFormat`, or ``None``.""" if not self.ptr.pix_fmts: return ret = [] cdef int i = 0 while self.ptr.pix_fmts[i] != -1: ret.append(get_video_format(self.ptr.pix_fmts[i], 0, 0)) i += 1 return ret @property def audio_formats(self): """A list of supported :class:`.AudioFormat`, or ``None``.""" if not self.ptr.sample_fmts: return ret = [] cdef int i = 0 while self.ptr.sample_fmts[i] != -1: ret.append(get_audio_format(self.ptr.sample_fmts[i])) i += 1 return ret @property def hardware_configs(self): if self._hardware_configs: return self._hardware_configs ret = [] cdef int i = 0 cdef const lib.AVCodecHWConfig *ptr while True: ptr = lib.avcodec_get_hw_config(self.ptr, i) if not ptr: break ret.append(wrap_hwconfig(ptr)) i += 1 ret = tuple(ret) self._hardware_configs = ret return ret @property def properties(self): return self.desc.props @property def intra_only(self): return bool(self.desc.props & lib.AV_CODEC_PROP_INTRA_ONLY) @property def lossy(self): return bool(self.desc.props & lib.AV_CODEC_PROP_LOSSY) @property def lossless(self): return bool(self.desc.props & lib.AV_CODEC_PROP_LOSSLESS) @property def reorder(self): return bool(self.desc.props & lib.AV_CODEC_PROP_REORDER) @property def bitmap_sub(self): return bool(self.desc.props & lib.AV_CODEC_PROP_BITMAP_SUB) @property def text_sub(self): return bool(self.desc.props & lib.AV_CODEC_PROP_TEXT_SUB) @property def capabilities(self): """ Get the capabilities bitmask of the codec. This method returns an integer representing the codec capabilities bitmask, which can be used to check specific codec features by performing bitwise operations with the Capabilities enum values. :example: .. code-block:: python from av.codec import Codec, Capabilities codec = Codec("h264", "w") # Check if the codec can be fed a final frame with a smaller size. # This can be used to prevent truncation of the last audio samples. small_last_frame = bool(codec.capabilities & Capabilities.small_last_frame) :rtype: int """ return self.ptr.capabilities @property def experimental(self): """ Check if codec is experimental and is thus avoided in favor of non experimental encoders. :rtype: bool """ return bool(self.ptr.capabilities & lib.AV_CODEC_CAP_EXPERIMENTAL) @property def delay(self): """ If true, encoder or decoder requires flushing with `None` at the end in order to give the complete and correct output. :rtype: bool """ return bool(self.ptr.capabilities & lib.AV_CODEC_CAP_DELAY) cdef get_codec_names(): names = set() cdef const lib.AVCodec *ptr cdef void *opaque = NULL while True: ptr = lib.av_codec_iterate(&opaque) if ptr: names.add(ptr.name) else: break return names codecs_available = get_codec_names() codec_descriptor = wrap_avclass(lib.avcodec_get_class()) def dump_codecs(): """Print information about available codecs.""" print( """Codecs: D..... = Decoding supported .E.... = Encoding supported ..V... = Video codec ..A... = Audio codec ..S... = Subtitle codec ...I.. = Intra frame-only codec ....L. = Lossy compression .....S = Lossless compression ------""" ) for name in sorted(codecs_available): try: e_codec = Codec(name, "w") except ValueError: e_codec = None try: d_codec = Codec(name, "r") except ValueError: d_codec = None # TODO: Assert these always have the same properties. codec = e_codec or d_codec try: print( " %s%s%s%s%s%s %-18s %s" % ( ".D"[bool(d_codec)], ".E"[bool(e_codec)], codec.type[0].upper(), ".I"[codec.intra_only], ".L"[codec.lossy], ".S"[codec.lossless], codec.name, codec.long_name, ) ) except Exception as e: print(f"...... {codec.name:<18} ERROR: {e}") def dump_hwconfigs(): print("Hardware configs:") for name in sorted(codecs_available): try: codec = Codec(name, "r") except ValueError: continue configs = codec.hardware_configs if not configs: continue print(" ", codec.name) for config in configs: print(" ", config) PyAV-14.2.0/av/codec/context.pxd000066400000000000000000000045751475734227400163330ustar00rootroot00000000000000cimport libav as lib from libc.stdint cimport int64_t from av.bytesource cimport ByteSource from av.codec.codec cimport Codec from av.codec.hwaccel cimport HWAccel from av.frame cimport Frame from av.packet cimport Packet cdef class CodecContext: cdef lib.AVCodecContext *ptr # Whether AVCodecContext.extradata should be de-allocated upon destruction. cdef bint extradata_set # Used as a signal that this is within a stream, and also for us to access that # stream. This is set "manually" by the stream after constructing this object. cdef int stream_index cdef lib.AVCodecParserContext *parser cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec, HWAccel hwaccel) # Public API. cdef readonly bint is_open cdef readonly Codec codec cdef readonly HWAccel hwaccel cdef public dict options cpdef open(self, bint strict=?) # Wraps both versions of the transcode API, returning lists. cpdef encode(self, Frame frame=?) cpdef decode(self, Packet packet=?) cpdef flush_buffers(self) # Used by hardware-accelerated decode. cdef HWAccel hwaccel_ctx # Used by both transcode APIs to setup user-land objects. # TODO: Remove the `Packet` from `_setup_decoded_frame` (because flushing packets # are bogus). It should take all info it needs from the context and/or stream. cdef _prepare_and_time_rebase_frames_for_encode(self, Frame frame) cdef _prepare_frames_for_encode(self, Frame frame) cdef _setup_encoded_packet(self, Packet) cdef _setup_decoded_frame(self, Frame, Packet) # Implemented by base for the generic send/recv API. # Note that the user cannot send without receiving. This is because # `_prepare_frames_for_encode` may expand a frame into multiple (e.g. when # resampling audio to a higher rate but with fixed size frames), and the # send/recv buffer may be limited to a single frame. Ergo, we need to flush # the buffer as often as possible. cdef _recv_packet(self) cdef _send_packet_and_recv(self, Packet packet) cdef _recv_frame(self) cdef _transfer_hwframe(self, Frame frame) # Implemented by children for the generic send/recv API, so we have the # correct subclass of Frame. cdef Frame _next_frame cdef Frame _alloc_next_frame(self) cdef CodecContext wrap_codec_context(lib.AVCodecContext*, const lib.AVCodec*, HWAccel hwaccel) PyAV-14.2.0/av/codec/context.pyi000066400000000000000000000055231475734227400163330ustar00rootroot00000000000000from enum import Flag, IntEnum from fractions import Fraction from typing import ClassVar, Literal, cast from av.packet import Packet from .codec import Codec from .hwaccel import HWAccel class ThreadType(Flag): NONE = cast(ClassVar[ThreadType], ...) FRAME = cast(ClassVar[ThreadType], ...) SLICE = cast(ClassVar[ThreadType], ...) AUTO = cast(ClassVar[ThreadType], ...) def __get__(self, i: object | None, owner: type | None = None) -> ThreadType: ... def __set__(self, instance: object, value: int | str | ThreadType) -> None: ... class Flags(IntEnum): unaligned = cast(int, ...) qscale = cast(int, ...) four_mv = cast(int, ...) output_corrupt = cast(int, ...) qpel = cast(int, ...) drop_changed = cast(int, ...) recon_frame = cast(int, ...) copy_opaque = cast(int, ...) frame_duration = cast(int, ...) pass1 = cast(int, ...) pass2 = cast(int, ...) loop_filter = cast(int, ...) gray = cast(int, ...) psnr = cast(int, ...) interlaced_dct = cast(int, ...) low_delay = cast(int, ...) global_header = cast(int, ...) bitexact = cast(int, ...) ac_pred = cast(int, ...) interlaced_me = cast(int, ...) closed_gop = cast(int, ...) class Flags2(IntEnum): fast = cast(int, ...) no_output = cast(int, ...) local_header = cast(int, ...) chunks = cast(int, ...) ignore_crop = cast(int, ...) show_all = cast(int, ...) export_mvs = cast(int, ...) skip_manual = cast(int, ...) ro_flush_noop = cast(int, ...) class CodecContext: name: str type: Literal["video", "audio", "data", "subtitle", "attachment"] options: dict[str, str] profile: str | None @property def profiles(self) -> list[str]: ... extradata: bytes | None time_base: Fraction codec_tag: str bit_rate: int | None bit_rate_tolerance: int thread_count: int thread_type: ThreadType skip_frame: Literal[ "NONE", "DEFAULT", "NONREF", "BIDIR", "NONINTRA", "NONKEY", "ALL" ] flags: int qscale: bool copy_opaque: bool flags2: int @property def is_open(self) -> bool: ... @property def is_encoder(self) -> bool: ... @property def is_decoder(self) -> bool: ... @property def codec(self) -> Codec: ... @property def max_bit_rate(self) -> int | None: ... @property def delay(self) -> bool: ... @property def extradata_size(self) -> int: ... @property def is_hwaccel(self) -> bool: ... def open(self, strict: bool = True) -> None: ... @staticmethod def create( codec: str | Codec, mode: Literal["r", "w"] | None = None, hwaccel: HWAccel | None = None, ) -> CodecContext: ... def parse( self, raw_input: bytes | bytearray | memoryview | None = None ) -> list[Packet]: ... def flush_buffers(self) -> None: ... PyAV-14.2.0/av/codec/context.pyx000066400000000000000000000531321475734227400163510ustar00rootroot00000000000000cimport libav as lib from libc.errno cimport EAGAIN from libc.stdint cimport uint8_t from libc.string cimport memcpy from av.bytesource cimport ByteSource, bytesource from av.codec.codec cimport Codec, wrap_codec from av.dictionary cimport _Dictionary from av.error cimport err_check from av.packet cimport Packet from av.utils cimport avrational_to_fraction, to_avrational from enum import Flag, IntEnum from av.dictionary import Dictionary cdef object _cinit_sentinel = object() cdef CodecContext wrap_codec_context(lib.AVCodecContext *c_ctx, const lib.AVCodec *c_codec, HWAccel hwaccel): """Build an av.CodecContext for an existing AVCodecContext.""" cdef CodecContext py_ctx if c_ctx.codec_type == lib.AVMEDIA_TYPE_VIDEO: from av.video.codeccontext import VideoCodecContext py_ctx = VideoCodecContext(_cinit_sentinel) elif c_ctx.codec_type == lib.AVMEDIA_TYPE_AUDIO: from av.audio.codeccontext import AudioCodecContext py_ctx = AudioCodecContext(_cinit_sentinel) elif c_ctx.codec_type == lib.AVMEDIA_TYPE_SUBTITLE: from av.subtitles.codeccontext import SubtitleCodecContext py_ctx = SubtitleCodecContext(_cinit_sentinel) else: py_ctx = CodecContext(_cinit_sentinel) py_ctx._init(c_ctx, c_codec, hwaccel) return py_ctx class ThreadType(Flag): NONE = 0 FRAME: "Decode more than one frame at once" = lib.FF_THREAD_FRAME SLICE: "Decode more than one part of a single frame at once" = lib.FF_THREAD_SLICE AUTO: "Decode using both FRAME and SLICE methods." = lib.FF_THREAD_SLICE | lib.FF_THREAD_FRAME class Flags(IntEnum): unaligned = lib.AV_CODEC_FLAG_UNALIGNED qscale = lib.AV_CODEC_FLAG_QSCALE four_mv = lib.AV_CODEC_FLAG_4MV output_corrupt = lib.AV_CODEC_FLAG_OUTPUT_CORRUPT qpel = lib.AV_CODEC_FLAG_QPEL drop_changed = 1 << 5 recon_frame = lib.AV_CODEC_FLAG_RECON_FRAME copy_opaque = lib.AV_CODEC_FLAG_COPY_OPAQUE frame_duration = lib.AV_CODEC_FLAG_FRAME_DURATION pass1 = lib.AV_CODEC_FLAG_PASS1 pass2 = lib.AV_CODEC_FLAG_PASS2 loop_filter = lib.AV_CODEC_FLAG_LOOP_FILTER gray = lib.AV_CODEC_FLAG_GRAY psnr = lib.AV_CODEC_FLAG_PSNR interlaced_dct = lib.AV_CODEC_FLAG_INTERLACED_DCT low_delay = lib.AV_CODEC_FLAG_LOW_DELAY global_header = lib.AV_CODEC_FLAG_GLOBAL_HEADER bitexact = lib.AV_CODEC_FLAG_BITEXACT ac_pred = lib.AV_CODEC_FLAG_AC_PRED interlaced_me = lib.AV_CODEC_FLAG_INTERLACED_ME closed_gop = lib.AV_CODEC_FLAG_CLOSED_GOP class Flags2(IntEnum): fast = lib.AV_CODEC_FLAG2_FAST no_output = lib.AV_CODEC_FLAG2_NO_OUTPUT local_header = lib.AV_CODEC_FLAG2_LOCAL_HEADER chunks = lib.AV_CODEC_FLAG2_CHUNKS ignore_crop = lib.AV_CODEC_FLAG2_IGNORE_CROP show_all = lib.AV_CODEC_FLAG2_SHOW_ALL export_mvs = lib.AV_CODEC_FLAG2_EXPORT_MVS skip_manual = lib.AV_CODEC_FLAG2_SKIP_MANUAL ro_flush_noop = lib.AV_CODEC_FLAG2_RO_FLUSH_NOOP cdef class CodecContext: @staticmethod def create(codec, mode=None, hwaccel=None): cdef Codec cy_codec = codec if isinstance(codec, Codec) else Codec(codec, mode) cdef lib.AVCodecContext *c_ctx = lib.avcodec_alloc_context3(cy_codec.ptr) return wrap_codec_context(c_ctx, cy_codec.ptr, hwaccel) def __cinit__(self, sentinel=None, *args, **kwargs): if sentinel is not _cinit_sentinel: raise RuntimeError("Cannot instantiate CodecContext") self.options = {} self.stream_index = -1 # This is set by the container immediately. self.is_open = False cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec, HWAccel hwaccel): self.ptr = ptr if self.ptr.codec and codec and self.ptr.codec != codec: raise RuntimeError("Wrapping CodecContext with mismatched codec.") self.codec = wrap_codec(codec if codec != NULL else self.ptr.codec) self.hwaccel = hwaccel # Set reasonable threading defaults. self.ptr.thread_count = 0 # use as many threads as there are CPUs. self.ptr.thread_type = 0x02 # thread within a frame. Does not change the API. @property def flags(self): """ Get and set the flags bitmask of CodecContext. :rtype: int """ return self.ptr.flags @flags.setter def flags(self, int value): self.ptr.flags = value @property def qscale(self): """ Use fixed qscale. :rtype: bool """ return bool(self.ptr.flags & lib.AV_CODEC_FLAG_QSCALE) @qscale.setter def qscale(self, value): if value: self.ptr.flags |= lib.AV_CODEC_FLAG_QSCALE else: self.ptr.flags &= ~lib.AV_CODEC_FLAG_QSCALE @property def copy_opaque(self): return bool(self.ptr.flags & lib.AV_CODEC_FLAG_COPY_OPAQUE) @copy_opaque.setter def copy_opaque(self, value): if value: self.ptr.flags |= lib.AV_CODEC_FLAG_COPY_OPAQUE else: self.ptr.flags &= ~lib.AV_CODEC_FLAG_COPY_OPAQUE @property def flags2(self): """ Get and set the flags2 bitmask of CodecContext. :rtype: int """ return self.ptr.flags2 @flags2.setter def flags2(self, int value): self.ptr.flags2 = value @property def extradata(self): if self.ptr is NULL: return None if self.ptr.extradata_size > 0: return (self.ptr.extradata)[:self.ptr.extradata_size] return None @extradata.setter def extradata(self, data): if data is None: lib.av_freep(&self.ptr.extradata) self.ptr.extradata_size = 0 else: source = bytesource(data) self.ptr.extradata = lib.av_realloc(self.ptr.extradata, source.length + lib.AV_INPUT_BUFFER_PADDING_SIZE) if not self.ptr.extradata: raise MemoryError("Cannot allocate extradata") memcpy(self.ptr.extradata, source.ptr, source.length) self.ptr.extradata_size = source.length self.extradata_set = True @property def extradata_size(self): return self.ptr.extradata_size @property def is_encoder(self): if self.ptr is NULL: return False return lib.av_codec_is_encoder(self.ptr.codec) @property def is_decoder(self): if self.ptr is NULL: return False return lib.av_codec_is_decoder(self.ptr.codec) cpdef open(self, bint strict=True): if self.is_open: if strict: raise ValueError("CodecContext is already open.") return cdef _Dictionary options = Dictionary() options.update(self.options or {}) if not self.ptr.time_base.num and self.is_encoder: if self.type == "video": self.ptr.time_base.num = self.ptr.framerate.den or 1 self.ptr.time_base.den = self.ptr.framerate.num or lib.AV_TIME_BASE elif self.type == "audio": self.ptr.time_base.num = 1 self.ptr.time_base.den = self.ptr.sample_rate else: self.ptr.time_base.num = 1 self.ptr.time_base.den = lib.AV_TIME_BASE err_check(lib.avcodec_open2(self.ptr, self.codec.ptr, &options.ptr), "avcodec_open2(" + self.codec.name + ")") self.is_open = True self.options = dict(options) def __dealloc__(self): if self.ptr and self.extradata_set: lib.av_freep(&self.ptr.extradata) if self.ptr: lib.avcodec_free_context(&self.ptr) if self.parser: lib.av_parser_close(self.parser) def __repr__(self): _type = self.type or "" name = self.name or "" return f"" def parse(self, raw_input=None): """Split up a byte stream into list of :class:`.Packet`. This is only effectively splitting up a byte stream, and does no actual interpretation of the data. It will return all packets that are fully contained within the given input, and will buffer partial packets until they are complete. :param ByteSource raw_input: A chunk of a byte-stream to process. Anything that can be turned into a :class:`.ByteSource` is fine. ``None`` or empty inputs will flush the parser's buffers. :return: ``list`` of :class:`.Packet` newly available. """ if not self.parser: self.parser = lib.av_parser_init(self.codec.ptr.id) if not self.parser: raise ValueError(f"No parser for {self.codec.name}") cdef ByteSource source = bytesource(raw_input, allow_none=True) cdef unsigned char *in_data = source.ptr if source is not None else NULL cdef int in_size = source.length if source is not None else 0 cdef unsigned char *out_data cdef int out_size cdef int consumed cdef Packet packet = None packets = [] while True: with nogil: consumed = lib.av_parser_parse2( self.parser, self.ptr, &out_data, &out_size, in_data, in_size, lib.AV_NOPTS_VALUE, lib.AV_NOPTS_VALUE, 0 ) err_check(consumed) if out_size: # We copy the data immediately, as we have yet to figure out # the expected lifetime of the buffer we get back. All of the # examples decode it immediately. # # We've also tried: # packet = Packet() # packet.data = out_data # packet.size = out_size # packet.source = source # # ... but this results in corruption. packet = Packet(out_size) memcpy(packet.ptr.data, out_data, out_size) packets.append(packet) if not in_size: # This was a flush. Only one packet should ever be returned. break in_data += consumed in_size -= consumed if not in_size: break return packets @property def is_hwaccel(self): """ Returns ``True`` if this codec context is hardware accelerated, ``False`` otherwise. """ return self.hwaccel_ctx is not None def _send_frame_and_recv(self, Frame frame): cdef Packet packet cdef int res with nogil: res = lib.avcodec_send_frame(self.ptr, frame.ptr if frame is not None else NULL) err_check(res, "avcodec_send_frame()") packet = self._recv_packet() while packet: yield packet packet = self._recv_packet() cdef _send_packet_and_recv(self, Packet packet): cdef Frame frame cdef int res with nogil: res = lib.avcodec_send_packet(self.ptr, packet.ptr if packet is not None else NULL) err_check(res, "avcodec_send_packet()") out = [] while True: frame = self._recv_frame() if frame: out.append(frame) else: break return out cdef _prepare_frames_for_encode(self, Frame frame): return [frame] cdef Frame _alloc_next_frame(self): raise NotImplementedError("Base CodecContext cannot decode.") cdef _recv_frame(self): if not self._next_frame: self._next_frame = self._alloc_next_frame() cdef Frame frame = self._next_frame cdef int res with nogil: res = lib.avcodec_receive_frame(self.ptr, frame.ptr) if res == -EAGAIN or res == lib.AVERROR_EOF: return err_check(res, "avcodec_receive_frame()") frame = self._transfer_hwframe(frame) if not res: self._next_frame = None return frame cdef _transfer_hwframe(self, Frame frame): return frame cdef _recv_packet(self): cdef Packet packet = Packet() cdef int res with nogil: res = lib.avcodec_receive_packet(self.ptr, packet.ptr) if res == -EAGAIN or res == lib.AVERROR_EOF: return err_check(res, "avcodec_receive_packet()") if not res: return packet cdef _prepare_and_time_rebase_frames_for_encode(self, Frame frame): if self.ptr.codec_type not in [lib.AVMEDIA_TYPE_VIDEO, lib.AVMEDIA_TYPE_AUDIO]: raise NotImplementedError("Encoding is only supported for audio and video.") self.open(strict=False) frames = self._prepare_frames_for_encode(frame) # Assert the frames are in our time base. # TODO: Don't mutate time. for frame in frames: if frame is not None: frame._rebase_time(self.ptr.time_base) return frames cpdef encode(self, Frame frame=None): """Encode a list of :class:`.Packet` from the given :class:`.Frame`.""" res = [] for frame in self._prepare_and_time_rebase_frames_for_encode(frame): for packet in self._send_frame_and_recv(frame): self._setup_encoded_packet(packet) res.append(packet) return res def encode_lazy(self, Frame frame=None): for frame in self._prepare_and_time_rebase_frames_for_encode(frame): for packet in self._send_frame_and_recv(frame): self._setup_encoded_packet(packet) yield packet cdef _setup_encoded_packet(self, Packet packet): # We coerced the frame's time_base into the CodecContext's during encoding, # and FFmpeg copied the frame's pts/dts to the packet, so keep track of # this time_base in case the frame needs to be muxed to a container with # a different time_base. # # NOTE: if the CodecContext's time_base is altered during encoding, all bets # are off! packet._time_base = self.ptr.time_base cpdef decode(self, Packet packet=None): """Decode a list of :class:`.Frame` from the given :class:`.Packet`. If the packet is None, the buffers will be flushed. This is useful if you do not want the library to automatically re-order frames for you (if they are encoded with a codec that has B-frames). """ if not self.codec.ptr: raise ValueError("cannot decode unknown codec") self.open(strict=False) res = [] for frame in self._send_packet_and_recv(packet): if isinstance(frame, Frame): self._setup_decoded_frame(frame, packet) res.append(frame) return res cpdef flush_buffers(self): """Reset the internal codec state and discard all internal buffers. Should be called before you start decoding from a new position e.g. when seeking or when switching to a different stream. """ if self.is_open: with nogil: lib.avcodec_flush_buffers(self.ptr) cdef _setup_decoded_frame(self, Frame frame, Packet packet): # Propagate our manual times. # While decoding, frame times are in stream time_base, which PyAV # is carrying around. # TODO: Somehow get this from the stream so we can not pass the # packet here (because flushing packets are bogus). if packet is not None: frame._time_base = packet._time_base @property def name(self): return self.codec.name @property def type(self): return self.codec.type @property def profiles(self): """ List the available profiles for this stream. :type: list[str] """ ret = [] if not self.ptr.codec or not self.codec.desc or not self.codec.desc.profiles: return ret # Profiles are always listed in the codec descriptor, but not necessarily in # the codec itself. So use the descriptor here. desc = self.codec.desc cdef int i = 0 while desc.profiles[i].profile != lib.FF_PROFILE_UNKNOWN: ret.append(desc.profiles[i].name) i += 1 return ret @property def profile(self): if not self.ptr.codec or not self.codec.desc or not self.codec.desc.profiles: return # Profiles are always listed in the codec descriptor, but not necessarily in # the codec itself. So use the descriptor here. desc = self.codec.desc cdef int i = 0 while desc.profiles[i].profile != lib.FF_PROFILE_UNKNOWN: if desc.profiles[i].profile == self.ptr.profile: return desc.profiles[i].name i += 1 @profile.setter def profile(self, value): if not self.codec or not self.codec.desc or not self.codec.desc.profiles: return # Profiles are always listed in the codec descriptor, but not necessarily in # the codec itself. So use the descriptor here. desc = self.codec.desc cdef int i = 0 while desc.profiles[i].profile != lib.FF_PROFILE_UNKNOWN: if desc.profiles[i].name == value: self.ptr.profile = desc.profiles[i].profile return i += 1 @property def time_base(self): if self.is_decoder: raise RuntimeError("Cannot access 'time_base' as a decoder") return avrational_to_fraction(&self.ptr.time_base) @time_base.setter def time_base(self, value): if self.is_decoder: raise RuntimeError("Cannot access 'time_base' as a decoder") to_avrational(value, &self.ptr.time_base) @property def codec_tag(self): return self.ptr.codec_tag.to_bytes(4, byteorder="little", signed=False).decode( encoding="ascii") @codec_tag.setter def codec_tag(self, value): if isinstance(value, str) and len(value) == 4: self.ptr.codec_tag = int.from_bytes(value.encode(encoding="ascii"), byteorder="little", signed=False) else: raise ValueError("Codec tag should be a 4 character string.") @property def bit_rate(self): return self.ptr.bit_rate if self.ptr.bit_rate > 0 else None @bit_rate.setter def bit_rate(self, int value): self.ptr.bit_rate = value @property def max_bit_rate(self): if self.ptr.rc_max_rate > 0: return self.ptr.rc_max_rate else: return None @property def bit_rate_tolerance(self): self.ptr.bit_rate_tolerance @bit_rate_tolerance.setter def bit_rate_tolerance(self, int value): self.ptr.bit_rate_tolerance = value @property def thread_count(self): """How many threads to use; 0 means auto. Wraps :ffmpeg:`AVCodecContext.thread_count`. """ return self.ptr.thread_count @thread_count.setter def thread_count(self, int value): if self.is_open: raise RuntimeError("Cannot change thread_count after codec is open.") self.ptr.thread_count = value @property def thread_type(self): """One of :class:`.ThreadType`. Wraps :ffmpeg:`AVCodecContext.thread_type`. """ return ThreadType(self.ptr.thread_type) @thread_type.setter def thread_type(self, value): if self.is_open: raise RuntimeError("Cannot change thread_type after codec is open.") if type(value) is int: self.ptr.thread_type = value elif type(value) is str: self.ptr.thread_type = ThreadType[value].value else: self.ptr.thread_type = value.value @property def skip_frame(self): """Returns one of the following str literals: "NONE" Discard nothing "DEFAULT" Discard useless packets like 0 size packets in AVI "NONREF" Discard all non reference "BIDIR" Discard all bidirectional frames "NONINTRA" Discard all non intra frames "NONKEY Discard all frames except keyframes "ALL" Discard all Wraps :ffmpeg:`AVCodecContext.skip_frame`. """ value = self.ptr.skip_frame if value == lib.AVDISCARD_NONE: return "NONE" if value == lib.AVDISCARD_DEFAULT: return "DEFAULT" if value == lib.AVDISCARD_NONREF: return "NONREF" if value == lib.AVDISCARD_BIDIR: return "BIDIR" if value == lib.AVDISCARD_NONINTRA: return "NONINTRA" if value == lib.AVDISCARD_NONKEY: return "NONKEY" if value == lib.AVDISCARD_ALL: return "ALL" return f"{value}" @skip_frame.setter def skip_frame(self, value): if value == "NONE": self.ptr.skip_frame = lib.AVDISCARD_NONE elif value == "DEFAULT": self.ptr.skip_frame = lib.AVDISCARD_DEFAULT elif value == "NONREF": self.ptr.skip_frame = lib.AVDISCARD_NONREF elif value == "BIDIR": self.ptr.skip_frame = lib.AVDISCARD_BIDIR elif value == "NONINTRA": self.ptr.skip_frame = lib.AVDISCARD_NONINTRA elif value == "NONKEY": self.ptr.skip_frame = lib.AVDISCARD_NONKEY elif value == "ALL": self.ptr.skip_frame = lib.AVDISCARD_ALL else: raise ValueError("Invalid skip_frame type") @property def delay(self): """Codec delay. Wraps :ffmpeg:`AVCodecContext.delay`. """ return self.ptr.delay PyAV-14.2.0/av/codec/hwaccel.pxd000066400000000000000000000007761475734227400162540ustar00rootroot00000000000000cimport libav as lib from av.codec.codec cimport Codec cdef class HWConfig: cdef object __weakref__ cdef lib.AVCodecHWConfig *ptr cdef void _init(self, lib.AVCodecHWConfig *ptr) cdef HWConfig wrap_hwconfig(lib.AVCodecHWConfig *ptr) cdef class HWAccel: cdef int _device_type cdef str _device cdef readonly Codec codec cdef readonly HWConfig config cdef lib.AVBufferRef *ptr cdef public bint allow_software_fallback cdef public dict options cdef public int flags PyAV-14.2.0/av/codec/hwaccel.pyi000066400000000000000000000024451475734227400162550ustar00rootroot00000000000000from enum import IntEnum from typing import cast from av.codec.codec import Codec from av.video.format import VideoFormat class HWDeviceType(IntEnum): none = cast(int, ...) vdpau = cast(int, ...) cuda = cast(int, ...) vaapi = cast(int, ...) dxva2 = cast(int, ...) qsv = cast(int, ...) videotoolbox = cast(int, ...) d3d11va = cast(int, ...) drm = cast(int, ...) opencl = cast(int, ...) mediacodec = cast(int, ...) vulkan = cast(int, ...) d3d12va = cast(int, ...) class HWConfigMethod(IntEnum): none = cast(int, ...) hw_device_ctx = cast(int, ...) hw_frame_ctx = cast(int, ...) internal = cast(int, ...) ad_hoc = cast(int, ...) class HWConfig: @property def device_type(self) -> HWDeviceType: ... @property def format(self) -> VideoFormat: ... @property def methods(self) -> HWConfigMethod: ... @property def is_supported(self) -> bool: ... class HWAccel: def __init__( self, device_type: str | HWDeviceType, device: str | None = None, allow_software_fallback: bool = False, options: dict[str, object] | None = None, flags: int | None = None, ) -> None: ... def create(self, codec: Codec) -> HWAccel: ... def hwdevices_available() -> list[str]: ... PyAV-14.2.0/av/codec/hwaccel.pyx000066400000000000000000000115571475734227400163000ustar00rootroot00000000000000import weakref from enum import IntEnum cimport libav as lib from av.codec.codec cimport Codec from av.dictionary cimport _Dictionary from av.error cimport err_check from av.video.format cimport get_video_format from av.dictionary import Dictionary class HWDeviceType(IntEnum): none = lib.AV_HWDEVICE_TYPE_NONE vdpau = lib.AV_HWDEVICE_TYPE_VDPAU cuda = lib.AV_HWDEVICE_TYPE_CUDA vaapi = lib.AV_HWDEVICE_TYPE_VAAPI dxva2 = lib.AV_HWDEVICE_TYPE_DXVA2 qsv = lib.AV_HWDEVICE_TYPE_QSV videotoolbox = lib.AV_HWDEVICE_TYPE_VIDEOTOOLBOX d3d11va = lib.AV_HWDEVICE_TYPE_D3D11VA drm = lib.AV_HWDEVICE_TYPE_DRM opencl = lib.AV_HWDEVICE_TYPE_OPENCL mediacodec = lib.AV_HWDEVICE_TYPE_MEDIACODEC vulkan = lib.AV_HWDEVICE_TYPE_VULKAN d3d12va = lib.AV_HWDEVICE_TYPE_D3D12VA class HWConfigMethod(IntEnum): none = 0 hw_device_ctx = lib.AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX # This is the only one we support. hw_frame_ctx = lib.AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX internal = lib.AV_CODEC_HW_CONFIG_METHOD_INTERNAL ad_hoc = lib.AV_CODEC_HW_CONFIG_METHOD_AD_HOC cdef object _cinit_sentinel = object() cdef object _singletons = weakref.WeakValueDictionary() cdef HWConfig wrap_hwconfig(lib.AVCodecHWConfig *ptr): try: return _singletons[ptr] except KeyError: pass cdef HWConfig config = HWConfig(_cinit_sentinel) config._init(ptr) _singletons[ptr] = config return config cdef class HWConfig: def __init__(self, sentinel): if sentinel is not _cinit_sentinel: raise RuntimeError("Cannot instantiate CodecContext") cdef void _init(self, lib.AVCodecHWConfig *ptr): self.ptr = ptr def __repr__(self): return ( f"self.ptr:x}>" ) @property def device_type(self): return HWDeviceType(self.ptr.device_type) @property def format(self): return get_video_format(self.ptr.pix_fmt, 0, 0) @property def methods(self): return HWConfigMethod(self.ptr.methods) @property def is_supported(self): return bool(self.ptr.methods & lib.AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) cpdef hwdevices_available(): result = [] cdef lib.AVHWDeviceType x = lib.AV_HWDEVICE_TYPE_NONE while True: x = lib.av_hwdevice_iterate_types(x) if x == lib.AV_HWDEVICE_TYPE_NONE: break result.append(lib.av_hwdevice_get_type_name(HWDeviceType(x))) return result cdef class HWAccel: def __init__(self, device_type, device=None, allow_software_fallback=True, options=None, flags=None): if isinstance(device_type, HWDeviceType): self._device_type = device_type elif isinstance(device_type, str): self._device_type = int(lib.av_hwdevice_find_type_by_name(device_type)) elif isinstance(device_type, int): self._device_type = device_type else: raise ValueError("Unknown type for device_type") self._device = device self.allow_software_fallback = allow_software_fallback self.options = {} if not options else dict(options) self.flags = 0 if not flags else flags self.ptr = NULL self.config = None def _initialize_hw_context(self, Codec codec not None): cdef HWConfig config for config in codec.hardware_configs: if not (config.ptr.methods & lib.AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX): continue if self._device_type and config.device_type != self._device_type: continue break else: raise NotImplementedError(f"No supported hardware config for {codec}") self.config = config cdef char *c_device = NULL if self._device: device_bytes = self._device.encode() c_device = device_bytes cdef _Dictionary c_options = Dictionary(self.options) err_check( lib.av_hwdevice_ctx_create( &self.ptr, config.ptr.device_type, c_device, c_options.ptr, self.flags ) ) def create(self, Codec codec not None): """Create a new hardware accelerator context with the given codec""" if self.ptr: raise RuntimeError("Hardware context already initialized") ret = HWAccel( device_type=self._device_type, device=self._device, allow_software_fallback=self.allow_software_fallback, options=self.options ) ret._initialize_hw_context(codec) return ret def __dealloc__(self): if self.ptr: lib.av_buffer_unref(&self.ptr) PyAV-14.2.0/av/container/000077500000000000000000000000001475734227400150245ustar00rootroot00000000000000PyAV-14.2.0/av/container/__init__.pxd000066400000000000000000000000001475734227400172660ustar00rootroot00000000000000PyAV-14.2.0/av/container/__init__.py000066400000000000000000000001571475734227400171400ustar00rootroot00000000000000from .core import Container, Flags, open from .input import InputContainer from .output import OutputContainer PyAV-14.2.0/av/container/__init__.pyi000066400000000000000000000000771475734227400173120ustar00rootroot00000000000000from .core import * from .input import * from .output import * PyAV-14.2.0/av/container/core.pxd000066400000000000000000000024231475734227400164720ustar00rootroot00000000000000cimport libav as lib from av.codec.hwaccel cimport HWAccel from av.container.pyio cimport PyIOFile from av.container.streams cimport StreamContainer from av.dictionary cimport _Dictionary from av.format cimport ContainerFormat from av.stream cimport Stream # Interrupt callback information, times are in seconds. ctypedef struct timeout_info: double start_time double timeout cdef class Container: cdef readonly bint writeable cdef lib.AVFormatContext *ptr cdef readonly object name cdef readonly str metadata_encoding cdef readonly str metadata_errors cdef readonly PyIOFile file cdef int buffer_size cdef bint input_was_opened cdef readonly object io_open cdef readonly object open_files cdef readonly ContainerFormat format cdef readonly dict options cdef readonly dict container_options cdef readonly list stream_options cdef HWAccel hwaccel cdef readonly StreamContainer streams cdef readonly dict metadata # Private API. cdef _assert_open(self) cdef int err_check(self, int value) except -1 # Timeouts cdef readonly object open_timeout cdef readonly object read_timeout cdef timeout_info interrupt_callback_info cdef set_timeout(self, object) cdef start_timeout(self) PyAV-14.2.0/av/container/core.pyi000066400000000000000000000117511475734227400165040ustar00rootroot00000000000000from enum import Flag, IntEnum from fractions import Fraction from pathlib import Path from types import TracebackType from typing import Any, Callable, ClassVar, Literal, Type, cast, overload from av.codec.hwaccel import HWAccel from av.format import ContainerFormat from .input import InputContainer from .output import OutputContainer from .streams import StreamContainer Real = int | float | Fraction class Flags(Flag): gen_pts = cast(ClassVar[Flags], ...) ign_idx = cast(ClassVar[Flags], ...) non_block = cast(ClassVar[Flags], ...) ign_dts = cast(ClassVar[Flags], ...) no_fillin = cast(ClassVar[Flags], ...) no_parse = cast(ClassVar[Flags], ...) no_buffer = cast(ClassVar[Flags], ...) custom_io = cast(ClassVar[Flags], ...) discard_corrupt = cast(ClassVar[Flags], ...) flush_packets = cast(ClassVar[Flags], ...) bitexact = cast(ClassVar[Flags], ...) sort_dts = cast(ClassVar[Flags], ...) fast_seek = cast(ClassVar[Flags], ...) shortest = cast(ClassVar[Flags], ...) auto_bsf = cast(ClassVar[Flags], ...) class AudioCodec(IntEnum): none = cast(int, ...) pcm_alaw = cast(int, ...) pcm_bluray = cast(int, ...) pcm_dvd = cast(int, ...) pcm_f16le = cast(int, ...) pcm_f24le = cast(int, ...) pcm_f32be = cast(int, ...) pcm_f32le = cast(int, ...) pcm_f64be = cast(int, ...) pcm_f64le = cast(int, ...) pcm_lxf = cast(int, ...) pcm_mulaw = cast(int, ...) pcm_s16be = cast(int, ...) pcm_s16be_planar = cast(int, ...) pcm_s16le = cast(int, ...) pcm_s16le_planar = cast(int, ...) pcm_s24be = cast(int, ...) pcm_s24daud = cast(int, ...) pcm_s24le = cast(int, ...) pcm_s24le_planar = cast(int, ...) pcm_s32be = cast(int, ...) pcm_s32le = cast(int, ...) pcm_s32le_planar = cast(int, ...) pcm_s64be = cast(int, ...) pcm_s64le = cast(int, ...) pcm_s8 = cast(int, ...) pcm_s8_planar = cast(int, ...) pcm_u16be = cast(int, ...) pcm_u16le = cast(int, ...) pcm_u24be = cast(int, ...) pcm_u24le = cast(int, ...) pcm_u32be = cast(int, ...) pcm_u32le = cast(int, ...) pcm_u8 = cast(int, ...) pcm_vidc = cast(int, ...) class Container: writeable: bool name: str metadata_encoding: str metadata_errors: str file: Any buffer_size: int input_was_opened: bool io_open: Any open_files: Any format: ContainerFormat options: dict[str, str] container_options: dict[str, str] stream_options: list[dict[str, str]] streams: StreamContainer metadata: dict[str, str] open_timeout: Real | None read_timeout: Real | None flags: int def __enter__(self) -> Container: ... def __exit__( self, exc_type: Type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None, ) -> bool: ... def set_timeout(self, timeout: Real | None) -> None: ... def start_timeout(self) -> None: ... @overload def open( file: Any, mode: Literal["r"], format: str | None = None, options: dict[str, str] | None = None, container_options: dict[str, str] | None = None, stream_options: list[str] | None = None, metadata_encoding: str = "utf-8", metadata_errors: str = "strict", buffer_size: int = 32768, timeout: Real | None | tuple[Real | None, Real | None] = None, io_open: Callable[..., Any] | None = None, hwaccel: HWAccel | None = None, ) -> InputContainer: ... @overload def open( file: str | Path, mode: Literal["r"] | None = None, format: str | None = None, options: dict[str, str] | None = None, container_options: dict[str, str] | None = None, stream_options: list[str] | None = None, metadata_encoding: str = "utf-8", metadata_errors: str = "strict", buffer_size: int = 32768, timeout: Real | None | tuple[Real | None, Real | None] = None, io_open: Callable[..., Any] | None = None, hwaccel: HWAccel | None = None, ) -> InputContainer: ... @overload def open( file: Any, mode: Literal["w"], format: str | None = None, options: dict[str, str] | None = None, container_options: dict[str, str] | None = None, stream_options: list[str] | None = None, metadata_encoding: str = "utf-8", metadata_errors: str = "strict", buffer_size: int = 32768, timeout: Real | None | tuple[Real | None, Real | None] = None, io_open: Callable[..., Any] | None = None, hwaccel: HWAccel | None = None, ) -> OutputContainer: ... @overload def open( file: Any, mode: Literal["r", "w"] | None = None, format: str | None = None, options: dict[str, str] | None = None, container_options: dict[str, str] | None = None, stream_options: list[str] | None = None, metadata_encoding: str = "utf-8", metadata_errors: str = "strict", buffer_size: int = 32768, timeout: Real | None | tuple[Real | None, Real | None] = None, io_open: Callable[..., Any] | None = None, hwaccel: HWAccel | None = None, ) -> InputContainer | OutputContainer: ... PyAV-14.2.0/av/container/core.pyx000077500000000000000000000414241475734227400165260ustar00rootroot00000000000000from cython.operator cimport dereference from libc.stdint cimport int64_t import os import time from enum import Flag, IntEnum from pathlib import Path cimport libav as lib from av.codec.hwaccel cimport HWAccel from av.container.core cimport timeout_info from av.container.input cimport InputContainer from av.container.output cimport OutputContainer from av.container.pyio cimport pyio_close_custom_gil, pyio_close_gil from av.error cimport err_check, stash_exception from av.format cimport build_container_format from av.utils cimport avdict_to_dict from av.dictionary import Dictionary from av.logging import Capture as LogCapture cdef object _cinit_sentinel = object() # We want to use the monotonic clock if it is available. cdef object clock = getattr(time, "monotonic", time.time) cdef int interrupt_cb (void *p) noexcept nogil: cdef timeout_info info = dereference( p) if info.timeout < 0: # timeout < 0 means no timeout return 0 cdef double current_time with gil: current_time = clock() # Check if the clock has been changed. if current_time < info.start_time: # Raise this when we get back to Python. stash_exception((RuntimeError, RuntimeError("Clock has been changed to before timeout start"), None)) return 1 if current_time > info.start_time + info.timeout: return 1 return 0 cdef int pyav_io_open(lib.AVFormatContext *s, lib.AVIOContext **pb, const char *url, int flags, lib.AVDictionary **options) noexcept nogil: with gil: return pyav_io_open_gil(s, pb, url, flags, options) cdef int pyav_io_open_gil(lib.AVFormatContext *s, lib.AVIOContext **pb, const char *url, int flags, lib.AVDictionary **options) noexcept: cdef Container container cdef object file cdef PyIOFile pyio_file try: container = dereference(s).opaque if options is not NULL: options_dict = avdict_to_dict( dereference(options), encoding=container.metadata_encoding, errors=container.metadata_errors ) else: options_dict = {} file = container.io_open( url if url is not NULL else "", flags, options_dict ) pyio_file = PyIOFile( file, container.buffer_size, (flags & lib.AVIO_FLAG_WRITE) != 0 ) # Add it to the container to avoid it being deallocated container.open_files[pyio_file.iocontext.opaque] = pyio_file pb[0] = pyio_file.iocontext return 0 except Exception as e: return stash_exception() cdef int pyav_io_close(lib.AVFormatContext *s, lib.AVIOContext *pb) noexcept nogil: with gil: return pyav_io_close_gil(s, pb) cdef int pyav_io_close_gil(lib.AVFormatContext *s, lib.AVIOContext *pb) noexcept: cdef Container container cdef int result = 0 try: container = dereference(s).opaque if container.open_files is not None and pb.opaque in container.open_files: result = pyio_close_custom_gil(pb) # Remove it from the container so that it can be deallocated del container.open_files[pb.opaque] else: result = pyio_close_gil(pb) except Exception as e: stash_exception() result = lib.AVERROR_UNKNOWN # Or another appropriate error code return result class Flags(Flag): gen_pts: "Generate missing pts even if it requires parsing future frames." = lib.AVFMT_FLAG_GENPTS ign_idx: "Ignore index." = lib.AVFMT_FLAG_IGNIDX non_block: "Do not block when reading packets from input." = lib.AVFMT_FLAG_NONBLOCK ign_dts: "Ignore DTS on frames that contain both DTS & PTS." = lib.AVFMT_FLAG_IGNDTS no_fillin: "Do not infer any values from other values, just return what is stored in the container." = lib.AVFMT_FLAG_NOFILLIN no_parse: "Do not use AVParsers, you also must set AVFMT_FLAG_NOFILLIN as the fillin code works on frames and no parsing -> no frames. Also seeking to frames can not work if parsing to find frame boundaries has been disabled." = lib.AVFMT_FLAG_NOPARSE no_buffer: "Do not buffer frames when possible." = lib.AVFMT_FLAG_NOBUFFER custom_io: "The caller has supplied a custom AVIOContext, don't avio_close() it." = lib.AVFMT_FLAG_CUSTOM_IO discard_corrupt: "Discard frames marked corrupted." = lib.AVFMT_FLAG_DISCARD_CORRUPT flush_packets: "Flush the AVIOContext every packet." = lib.AVFMT_FLAG_FLUSH_PACKETS bitexact: "When muxing, try to avoid writing any random/volatile data to the output. This includes any random IDs, real-time timestamps/dates, muxer version, etc. This flag is mainly intended for testing." = lib.AVFMT_FLAG_BITEXACT sort_dts: "Try to interleave outputted packets by dts (using this flag can slow demuxing down)." = lib.AVFMT_FLAG_SORT_DTS fast_seek: "Enable fast, but inaccurate seeks for some formats." = lib.AVFMT_FLAG_FAST_SEEK shortest: "Stop muxing when the shortest stream stops." = lib.AVFMT_FLAG_SHORTEST auto_bsf: "Add bitstream filters as requested by the muxer." = lib.AVFMT_FLAG_AUTO_BSF class AudioCodec(IntEnum): """Enumeration for audio codec IDs.""" none = lib.AV_CODEC_ID_NONE # No codec. pcm_alaw = lib.AV_CODEC_ID_PCM_ALAW # PCM A-law. pcm_bluray = lib.AV_CODEC_ID_PCM_BLURAY # PCM Blu-ray. pcm_dvd = lib.AV_CODEC_ID_PCM_DVD # PCM DVD. pcm_f16le = lib.AV_CODEC_ID_PCM_F16LE # PCM F16 little-endian. pcm_f24le = lib.AV_CODEC_ID_PCM_F24LE # PCM F24 little-endian. pcm_f32be = lib.AV_CODEC_ID_PCM_F32BE # PCM F32 big-endian. pcm_f32le = lib.AV_CODEC_ID_PCM_F32LE # PCM F32 little-endian. pcm_f64be = lib.AV_CODEC_ID_PCM_F64BE # PCM F64 big-endian. pcm_f64le = lib.AV_CODEC_ID_PCM_F64LE # PCM F64 little-endian. pcm_lxf = lib.AV_CODEC_ID_PCM_LXF # PCM LXF. pcm_mulaw = lib.AV_CODEC_ID_PCM_MULAW # PCM μ-law. pcm_s16be = lib.AV_CODEC_ID_PCM_S16BE # PCM signed 16-bit big-endian. pcm_s16be_planar = lib.AV_CODEC_ID_PCM_S16BE_PLANAR # PCM signed 16-bit big-endian planar. pcm_s16le = lib.AV_CODEC_ID_PCM_S16LE # PCM signed 16-bit little-endian. pcm_s16le_planar = lib.AV_CODEC_ID_PCM_S16LE_PLANAR # PCM signed 16-bit little-endian planar. pcm_s24be = lib.AV_CODEC_ID_PCM_S24BE # PCM signed 24-bit big-endian. pcm_s24daud = lib.AV_CODEC_ID_PCM_S24DAUD # PCM signed 24-bit D-Cinema audio. pcm_s24le = lib.AV_CODEC_ID_PCM_S24LE # PCM signed 24-bit little-endian. pcm_s24le_planar = lib.AV_CODEC_ID_PCM_S24LE_PLANAR # PCM signed 24-bit little-endian planar. pcm_s32be = lib.AV_CODEC_ID_PCM_S32BE # PCM signed 32-bit big-endian. pcm_s32le = lib.AV_CODEC_ID_PCM_S32LE # PCM signed 32-bit little-endian. pcm_s32le_planar = lib.AV_CODEC_ID_PCM_S32LE_PLANAR # PCM signed 32-bit little-endian planar. pcm_s64be = lib.AV_CODEC_ID_PCM_S64BE # PCM signed 64-bit big-endian. pcm_s64le = lib.AV_CODEC_ID_PCM_S64LE # PCM signed 64-bit little-endian. pcm_s8 = lib.AV_CODEC_ID_PCM_S8 # PCM signed 8-bit. pcm_s8_planar = lib.AV_CODEC_ID_PCM_S8_PLANAR # PCM signed 8-bit planar. pcm_u16be = lib.AV_CODEC_ID_PCM_U16BE # PCM unsigned 16-bit big-endian. pcm_u16le = lib.AV_CODEC_ID_PCM_U16LE # PCM unsigned 16-bit little-endian. pcm_u24be = lib.AV_CODEC_ID_PCM_U24BE # PCM unsigned 24-bit big-endian. pcm_u24le = lib.AV_CODEC_ID_PCM_U24LE # PCM unsigned 24-bit little-endian. pcm_u32be = lib.AV_CODEC_ID_PCM_U32BE # PCM unsigned 32-bit big-endian. pcm_u32le = lib.AV_CODEC_ID_PCM_U32LE # PCM unsigned 32-bit little-endian. pcm_u8 = lib.AV_CODEC_ID_PCM_U8 # PCM unsigned 8-bit. pcm_vidc = lib.AV_CODEC_ID_PCM_VIDC # PCM VIDC. cdef class Container: def __cinit__(self, sentinel, file_, format_name, options, container_options, stream_options, hwaccel, metadata_encoding, metadata_errors, buffer_size, open_timeout, read_timeout, io_open): if sentinel is not _cinit_sentinel: raise RuntimeError("cannot construct base Container") self.writeable = isinstance(self, OutputContainer) if not self.writeable and not isinstance(self, InputContainer): raise RuntimeError("Container cannot be directly extended.") if isinstance(file_, str): self.name = file_ else: self.name = str(getattr(file_, "name", "")) self.options = dict(options or ()) self.container_options = dict(container_options or ()) self.stream_options = [dict(x) for x in stream_options or ()] self.hwaccel = hwaccel self.metadata_encoding = metadata_encoding self.metadata_errors = metadata_errors self.open_timeout = open_timeout self.read_timeout = read_timeout self.buffer_size = buffer_size self.io_open = io_open acodec = None # no audio codec specified if format_name is not None: if ":" in format_name: format_name, acodec = format_name.split(":") self.format = ContainerFormat(format_name) self.input_was_opened = False cdef int res cdef bytes name_obj = os.fsencode(self.name) cdef char *name = name_obj cdef lib.AVOutputFormat *ofmt if self.writeable: ofmt = self.format.optr if self.format else lib.av_guess_format(NULL, name, NULL) if ofmt == NULL: raise ValueError("Could not determine output format") with nogil: # This does not actually open the file. res = lib.avformat_alloc_output_context2( &self.ptr, ofmt, NULL, name, ) self.err_check(res) else: # We need the context before we open the input AND setup Python IO. self.ptr = lib.avformat_alloc_context() # Setup interrupt callback if self.open_timeout is not None or self.read_timeout is not None: self.ptr.interrupt_callback.callback = interrupt_cb self.ptr.interrupt_callback.opaque = &self.interrupt_callback_info if acodec is not None: self.ptr.audio_codec_id = getattr(AudioCodec, acodec) self.ptr.flags |= lib.AVFMT_FLAG_GENPTS self.ptr.opaque = self # Setup Python IO. self.open_files = {} if not isinstance(file_, basestring): self.file = PyIOFile(file_, buffer_size, self.writeable) self.ptr.pb = self.file.iocontext if io_open is not None: self.ptr.io_open = pyav_io_open self.ptr.io_close2 = pyav_io_close self.ptr.flags |= lib.AVFMT_FLAG_CUSTOM_IO cdef lib.AVInputFormat *ifmt cdef _Dictionary c_options if not self.writeable: ifmt = self.format.iptr if self.format else NULL c_options = Dictionary(self.options, self.container_options) self.set_timeout(self.open_timeout) self.start_timeout() with nogil: res = lib.avformat_open_input(&self.ptr, name, ifmt, &c_options.ptr) self.set_timeout(None) self.err_check(res) self.input_was_opened = True if format_name is None: self.format = build_container_format(self.ptr.iformat, self.ptr.oformat) def __dealloc__(self): with nogil: lib.avformat_free_context(self.ptr) def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.close() def __repr__(self): return f"" cdef int err_check(self, int value) except -1: return err_check(value, filename=self.name) def dumps_format(self): self._assert_open() with LogCapture() as logs: lib.av_dump_format(self.ptr, 0, "", isinstance(self, OutputContainer)) return "".join(log[2] for log in logs) cdef set_timeout(self, timeout): if timeout is None: self.interrupt_callback_info.timeout = -1.0 else: self.interrupt_callback_info.timeout = timeout cdef start_timeout(self): self.interrupt_callback_info.start_time = clock() cdef _assert_open(self): if self.ptr == NULL: raise AssertionError("Container is not open") @property def flags(self): self._assert_open() return self.ptr.flags @flags.setter def flags(self, int value): self._assert_open() self.ptr.flags = value def open( file, mode=None, format=None, options=None, container_options=None, stream_options=None, metadata_encoding="utf-8", metadata_errors="strict", buffer_size=32768, timeout=None, io_open=None, hwaccel=None ): """open(file, mode='r', **kwargs) Main entrypoint to opening files/streams. :param str file: The file to open, which can be either a string or a file-like object. :param str mode: ``"r"`` for reading and ``"w"`` for writing. :param str format: Specific format to use. Defaults to autodect. :param dict options: Options to pass to the container and all streams. :param dict container_options: Options to pass to the container. :param list stream_options: Options to pass to each stream. :param str metadata_encoding: Encoding to use when reading or writing file metadata. Defaults to ``"utf-8"``. :param str metadata_errors: Specifies how to handle encoding errors; behaves like ``str.encode`` parameter. Defaults to ``"strict"``. :param int buffer_size: Size of buffer for Python input/output operations in bytes. Honored only when ``file`` is a file-like object. Defaults to 32768 (32k). :param timeout: How many seconds to wait for data before giving up, as a float, or a ``(open timeout, read timeout)`` tuple. :param callable io_open: Custom I/O callable for opening files/streams. This option is intended for formats that need to open additional file-like objects to ``file`` using custom I/O. The callable signature is ``io_open(url: str, flags: int, options: dict)``, where ``url`` is the url to open, ``flags`` is a combination of AVIO_FLAG_* and ``options`` is a dictionary of additional options. The callable should return a file-like object. :param HWAccel hwaccel: Optional settings for hardware-accelerated decoding. :rtype: Container For devices (via ``libavdevice``), pass the name of the device to ``format``, e.g.:: >>> # Open webcam on MacOS. >>> av.open('0', format='avfoundation') # doctest: +SKIP For DASH and custom I/O using ``io_open``, add a protocol prefix to the ``file`` to prevent the DASH encoder defaulting to the file protocol and using temporary files. The custom I/O callable can be used to remove the protocol prefix to reveal the actual name for creating the file-like object. E.g.:: >>> av.open("customprotocol://manifest.mpd", "w", io_open=custom_io) # doctest: +SKIP .. seealso:: :ref:`garbage_collection` More information on using input and output devices is available on the `FFmpeg website `_. """ if not (mode is None or (isinstance(mode, str) and mode == "r" or mode == "w")): raise ValueError(f"mode must be 'r', 'w', or None, got: {mode}") if isinstance(file, str): pass elif isinstance(file, Path): file = f"{file}" elif mode is None: mode = getattr(file, "mode", None) if mode is None: mode = "r" if isinstance(timeout, tuple): if not len(timeout) == 2: raise ValueError("timeout must be `float` or `tuple[float, float]`") open_timeout, read_timeout = timeout else: open_timeout = timeout read_timeout = timeout if mode.startswith("r"): return InputContainer(_cinit_sentinel, file, format, options, container_options, stream_options, hwaccel, metadata_encoding, metadata_errors, buffer_size, open_timeout, read_timeout, io_open, ) if stream_options: raise ValueError( "Provide stream options via Container.add_stream(..., options={})." ) return OutputContainer(_cinit_sentinel, file, format, options, container_options, stream_options, None, metadata_encoding, metadata_errors, buffer_size, open_timeout, read_timeout, io_open, ) PyAV-14.2.0/av/container/input.pxd000066400000000000000000000002431475734227400166770ustar00rootroot00000000000000cimport libav as lib from av.container.core cimport Container from av.stream cimport Stream cdef class InputContainer(Container): cdef flush_buffers(self) PyAV-14.2.0/av/container/input.pyi000066400000000000000000000031121475734227400167030ustar00rootroot00000000000000from typing import Any, Iterator, overload from av.audio.frame import AudioFrame from av.audio.stream import AudioStream from av.packet import Packet from av.stream import Stream from av.subtitles.stream import SubtitleStream from av.subtitles.subtitle import SubtitleSet from av.video.frame import VideoFrame from av.video.stream import VideoStream from .core import Container class InputContainer(Container): start_time: int duration: int | None bit_rate: int size: int def __enter__(self) -> InputContainer: ... def close(self) -> None: ... def demux(self, *args: Any, **kwargs: Any) -> Iterator[Packet]: ... @overload def decode(self, video: int) -> Iterator[VideoFrame]: ... @overload def decode(self, audio: int) -> Iterator[AudioFrame]: ... @overload def decode(self, subtitles: int) -> Iterator[SubtitleSet]: ... @overload def decode(self, *args: VideoStream) -> Iterator[VideoFrame]: ... @overload def decode(self, *args: AudioStream) -> Iterator[AudioFrame]: ... @overload def decode(self, *args: SubtitleStream) -> Iterator[SubtitleSet]: ... @overload def decode( self, *args: Any, **kwargs: Any ) -> Iterator[VideoFrame | AudioFrame | SubtitleSet]: ... def seek( self, offset: int, *, backward: bool = True, any_frame: bool = False, stream: Stream | VideoStream | AudioStream | None = None, unsupported_frame_offset: bool = False, unsupported_byte_offset: bool = False, ) -> None: ... def flush_buffers(self) -> None: ... PyAV-14.2.0/av/container/input.pyx000066400000000000000000000253631475734227400167360ustar00rootroot00000000000000from libc.stdint cimport int64_t from libc.stdlib cimport free, malloc from av.codec.context cimport CodecContext, wrap_codec_context from av.container.streams cimport StreamContainer from av.dictionary cimport _Dictionary from av.error cimport err_check from av.packet cimport Packet from av.stream cimport Stream, wrap_stream from av.utils cimport avdict_to_dict from av.dictionary import Dictionary cdef close_input(InputContainer self): self.streams = StreamContainer() if self.input_was_opened: with nogil: # This causes `self.ptr` to be set to NULL. lib.avformat_close_input(&self.ptr) self.input_was_opened = False cdef class InputContainer(Container): def __cinit__(self, *args, **kwargs): cdef CodecContext py_codec_context cdef unsigned int i cdef lib.AVStream *stream cdef lib.AVCodec *codec cdef lib.AVCodecContext *codec_context # If we have either the global `options`, or a `stream_options`, prepare # a mashup of those options for each stream. cdef lib.AVDictionary **c_options = NULL cdef _Dictionary base_dict, stream_dict if self.options or self.stream_options: base_dict = Dictionary(self.options) c_options = malloc(self.ptr.nb_streams * sizeof(void*)) for i in range(self.ptr.nb_streams): c_options[i] = NULL if i < len(self.stream_options) and self.stream_options: stream_dict = base_dict.copy() stream_dict.update(self.stream_options[i]) lib.av_dict_copy(&c_options[i], stream_dict.ptr, 0) else: lib.av_dict_copy(&c_options[i], base_dict.ptr, 0) self.set_timeout(self.open_timeout) self.start_timeout() with nogil: # This peeks are the first few frames to: # - set stream.disposition from codec.audio_service_type (not exposed); # - set stream.codec.bits_per_coded_sample; # - set stream.duration; # - set stream.start_time; # - set stream.r_frame_rate to average value; # - open and closes codecs with the options provided. ret = lib.avformat_find_stream_info( self.ptr, c_options ) self.set_timeout(None) self.err_check(ret) # Cleanup all of our options. if c_options: for i in range(self.ptr.nb_streams): lib.av_dict_free(&c_options[i]) free(c_options) at_least_one_accelerated_context = False self.streams = StreamContainer() for i in range(self.ptr.nb_streams): stream = self.ptr.streams[i] codec = lib.avcodec_find_decoder(stream.codecpar.codec_id) if codec: # allocate and initialise decoder codec_context = lib.avcodec_alloc_context3(codec) err_check(lib.avcodec_parameters_to_context(codec_context, stream.codecpar)) codec_context.pkt_timebase = stream.time_base py_codec_context = wrap_codec_context(codec_context, codec, self.hwaccel) if py_codec_context.is_hwaccel: at_least_one_accelerated_context = True else: # no decoder is available py_codec_context = None self.streams.add_stream(wrap_stream(self, stream, py_codec_context)) if self.hwaccel and not self.hwaccel.allow_software_fallback and not at_least_one_accelerated_context: raise RuntimeError("Hardware accelerated decode requested but no stream is compatible") self.metadata = avdict_to_dict(self.ptr.metadata, self.metadata_encoding, self.metadata_errors) def __dealloc__(self): close_input(self) @property def start_time(self): self._assert_open() if self.ptr.start_time != lib.AV_NOPTS_VALUE: return self.ptr.start_time @property def duration(self): self._assert_open() if self.ptr.duration != lib.AV_NOPTS_VALUE: return self.ptr.duration @property def bit_rate(self): self._assert_open() return self.ptr.bit_rate @property def size(self): self._assert_open() return lib.avio_size(self.ptr.pb) def close(self): close_input(self) def demux(self, *args, **kwargs): """demux(streams=None, video=None, audio=None, subtitles=None, data=None) Yields a series of :class:`.Packet` from the given set of :class:`.Stream`:: for packet in container.demux(): # Do something with `packet`, often: for frame in packet.decode(): # Do something with `frame`. .. seealso:: :meth:`.StreamContainer.get` for the interpretation of the arguments. .. note:: The last packets are dummy packets that when decoded will flush the buffers. """ self._assert_open() # For whatever reason, Cython does not like us directly passing kwargs # from one method to another. Without kwargs, it ends up passing a # NULL reference, which segfaults. So we force it to do something with it. # This is likely a bug in Cython; see https://github.com/cython/cython/issues/2166 # (and others). id(kwargs) streams = self.streams.get(*args, **kwargs) cdef bint *include_stream = malloc(self.ptr.nb_streams * sizeof(bint)) if include_stream == NULL: raise MemoryError() cdef unsigned int i cdef Packet packet cdef int ret self.set_timeout(self.read_timeout) try: for i in range(self.ptr.nb_streams): include_stream[i] = False for stream in streams: i = stream.index if i >= self.ptr.nb_streams: raise ValueError(f"stream index {i} out of range") include_stream[i] = True while True: packet = Packet() try: self.start_timeout() with nogil: ret = lib.av_read_frame(self.ptr, packet.ptr) self.err_check(ret) except EOFError: break if include_stream[packet.ptr.stream_index]: # If AVFMTCTX_NOHEADER is set in ctx_flags, then new streams # may also appear in av_read_frame(). # http://ffmpeg.org/doxygen/trunk/structAVFormatContext.html # TODO: find better way to handle this if packet.ptr.stream_index < len(self.streams): packet._stream = self.streams[packet.ptr.stream_index] # Keep track of this so that remuxing is easier. packet._time_base = packet._stream.ptr.time_base yield packet # Flush! for i in range(self.ptr.nb_streams): if include_stream[i]: packet = Packet() packet._stream = self.streams[i] packet._time_base = packet._stream.ptr.time_base yield packet finally: self.set_timeout(None) free(include_stream) def decode(self, *args, **kwargs): """decode(streams=None, video=None, audio=None, subtitles=None, data=None) Yields a series of :class:`.Frame` from the given set of streams:: for frame in container.decode(): # Do something with `frame`. .. seealso:: :meth:`.StreamContainer.get` for the interpretation of the arguments. """ self._assert_open() id(kwargs) # Avoid Cython bug; see demux(). for packet in self.demux(*args, **kwargs): for frame in packet.decode(): yield frame def seek( self, offset, *, bint backward=True, bint any_frame=False, Stream stream=None, bint unsupported_frame_offset=False, bint unsupported_byte_offset=False ): """seek(offset, *, backward=True, any_frame=False, stream=None) Seek to a (key)frame nearsest to the given timestamp. :param int offset: Time to seek to, expressed in``stream.time_base`` if ``stream`` is given, otherwise in :data:`av.time_base`. :param bool backward: If there is not a (key)frame at the given offset, look backwards for it. :param bool any_frame: Seek to any frame, not just a keyframe. :param Stream stream: The stream who's ``time_base`` the ``offset`` is in. :param bool unsupported_frame_offset: ``offset`` is a frame index instead of a time; not supported by any known format. :param bool unsupported_byte_offset: ``offset`` is a byte location in the file; not supported by any known format. After seeking, packets that you demux should correspond (roughly) to the position you requested. In most cases, the defaults of ``backwards = True`` and ``any_frame = False`` are the best course of action, followed by you demuxing/decoding to the position that you want. This is becase to properly decode video frames you need to start from the previous keyframe. .. seealso:: :ffmpeg:`avformat_seek_file` for discussion of the flags. """ self._assert_open() # We used to take floats here and assume they were in seconds. This # was super confusing, so lets go in the complete opposite direction # and reject non-ints. if not isinstance(offset, int): raise TypeError("Container.seek only accepts integer offset.", type(offset)) cdef int64_t c_offset = offset cdef int flags = 0 cdef int ret if backward: flags |= lib.AVSEEK_FLAG_BACKWARD if any_frame: flags |= lib.AVSEEK_FLAG_ANY # If someone really wants (and to experiment), expose these. if unsupported_frame_offset: flags |= lib.AVSEEK_FLAG_FRAME if unsupported_byte_offset: flags |= lib.AVSEEK_FLAG_BYTE cdef int stream_index = stream.index if stream else -1 with nogil: ret = lib.av_seek_frame(self.ptr, stream_index, c_offset, flags) err_check(ret) self.flush_buffers() cdef flush_buffers(self): self._assert_open() cdef Stream stream cdef CodecContext codec_context for stream in self.streams: codec_context = stream.codec_context if codec_context: codec_context.flush_buffers() PyAV-14.2.0/av/container/output.pxd000066400000000000000000000003641475734227400171040ustar00rootroot00000000000000cimport libav as lib from av.container.core cimport Container from av.stream cimport Stream cdef class OutputContainer(Container): cdef bint _started cdef bint _done cdef lib.AVPacket *packet_ptr cpdef start_encoding(self) PyAV-14.2.0/av/container/output.pyi000066400000000000000000000036361475734227400171170ustar00rootroot00000000000000from fractions import Fraction from typing import Literal, Sequence, TypeVar, Union, overload from av.audio.stream import AudioStream from av.data.stream import DataStream from av.packet import Packet from av.stream import Stream from av.subtitles.stream import SubtitleStream from av.video.stream import VideoStream from .core import Container _StreamT = TypeVar("_StreamT", bound=Union[VideoStream, AudioStream, SubtitleStream]) class OutputContainer(Container): def __enter__(self) -> OutputContainer: ... @overload def add_stream( self, codec_name: Literal["pcm_s16le", "aac", "mp3", "mp2"], rate: int | None = None, options: dict[str, str] | None = None, **kwargs, ) -> AudioStream: ... @overload def add_stream( self, codec_name: Literal["h264", "hevc", "mpeg4", "png", "gif", "qtrle"], rate: Fraction | int | None = None, options: dict[str, str] | None = None, **kwargs, ) -> VideoStream: ... @overload def add_stream( self, codec_name: str, rate: Fraction | int | None = None, options: dict[str, str] | None = None, **kwargs, ) -> VideoStream | AudioStream | SubtitleStream: ... def add_stream_from_template( self, template: _StreamT, opaque: bool | None = None, **kwargs ) -> _StreamT: ... def add_data_stream( self, codec_name: str | None = None, options: dict[str, str] | None = None ) -> DataStream: ... def start_encoding(self) -> None: ... def close(self) -> None: ... def mux(self, packets: Packet | Sequence[Packet]) -> None: ... def mux_one(self, packet: Packet) -> None: ... @property def default_video_codec(self) -> str: ... @property def default_audio_codec(self) -> str: ... @property def default_subtitle_codec(self) -> str: ... @property def supported_codecs(self) -> set[str]: ... PyAV-14.2.0/av/container/output.pyx000066400000000000000000000340071475734227400171320ustar00rootroot00000000000000import logging import os from fractions import Fraction cimport libav as lib from av.codec.codec cimport Codec from av.codec.context cimport CodecContext, wrap_codec_context from av.container.streams cimport StreamContainer from av.dictionary cimport _Dictionary from av.error cimport err_check from av.packet cimport Packet from av.stream cimport Stream, wrap_stream from av.utils cimport dict_to_avdict, to_avrational from av.dictionary import Dictionary log = logging.getLogger(__name__) cdef close_output(OutputContainer self): self.streams = StreamContainer() if self._started and not self._done: # We must only ever call av_write_trailer *once*, otherwise we get a # segmentation fault. Therefore no matter whether it succeeds or not # we must absolutely set self._done. try: self.err_check(lib.av_write_trailer(self.ptr)) finally: if self.file is None and not (self.ptr.oformat.flags & lib.AVFMT_NOFILE): lib.avio_closep(&self.ptr.pb) self._done = True cdef class OutputContainer(Container): def __cinit__(self, *args, **kwargs): self.streams = StreamContainer() self.metadata = {} with nogil: self.packet_ptr = lib.av_packet_alloc() def __dealloc__(self): close_output(self) with nogil: lib.av_packet_free(&self.packet_ptr) def add_stream(self, codec_name, rate=None, dict options=None, **kwargs): """add_stream(codec_name, rate=None) Creates a new stream from a codec name and returns it. Supports video, audio, and subtitle streams. :param codec_name: The name of a codec. :type codec_name: str :param dict options: Stream options. :param \\**kwargs: Set attributes for the stream. :rtype: The new :class:`~av.stream.Stream`. """ cdef Codec codec_obj = Codec(codec_name, "w") cdef const lib.AVCodec *codec = codec_obj.ptr # Assert that this format supports the requested codec. if not lib.avformat_query_codec(self.ptr.oformat, codec.id, lib.FF_COMPLIANCE_NORMAL): raise ValueError( f"{self.format.name!r} format does not support {codec_obj.name!r} codec" ) # Create new stream in the AVFormatContext, set AVCodecContext values. cdef lib.AVStream *stream = lib.avformat_new_stream(self.ptr, codec) cdef lib.AVCodecContext *codec_context = lib.avcodec_alloc_context3(codec) # Now lets set some more sane video defaults if codec.type == lib.AVMEDIA_TYPE_VIDEO: codec_context.pix_fmt = lib.AV_PIX_FMT_YUV420P codec_context.width = kwargs.pop("width", 640) codec_context.height = kwargs.pop("height", 480) codec_context.bit_rate = kwargs.pop("bit_rate", 0) codec_context.bit_rate_tolerance = kwargs.pop("bit_rate_tolerance", 128000) try: to_avrational(kwargs.pop("time_base"), &codec_context.time_base) except KeyError: pass to_avrational(rate or 24, &codec_context.framerate) stream.avg_frame_rate = codec_context.framerate stream.time_base = codec_context.time_base # Some sane audio defaults elif codec.type == lib.AVMEDIA_TYPE_AUDIO: codec_context.sample_fmt = codec.sample_fmts[0] codec_context.bit_rate = kwargs.pop("bit_rate", 0) codec_context.bit_rate_tolerance = kwargs.pop("bit_rate_tolerance", 32000) try: to_avrational(kwargs.pop("time_base"), &codec_context.time_base) except KeyError: pass if rate is None: codec_context.sample_rate = 48000 elif type(rate) is int: codec_context.sample_rate = rate else: raise TypeError("audio stream `rate` must be: int | None") stream.time_base = codec_context.time_base lib.av_channel_layout_default(&codec_context.ch_layout, 2) # Some formats want stream headers to be separate if self.ptr.oformat.flags & lib.AVFMT_GLOBALHEADER: codec_context.flags |= lib.AV_CODEC_FLAG_GLOBAL_HEADER # Initialise stream codec parameters to populate the codec type. # # Subsequent changes to the codec context will be applied just before # encoding starts in `start_encoding()`. err_check(lib.avcodec_parameters_from_context(stream.codecpar, codec_context)) # Construct the user-land stream cdef CodecContext py_codec_context = wrap_codec_context(codec_context, codec, None) cdef Stream py_stream = wrap_stream(self, stream, py_codec_context) self.streams.add_stream(py_stream) if options: py_stream.options.update(options) for k, v in kwargs.items(): setattr(py_stream, k, v) return py_stream def add_stream_from_template(self, Stream template not None, opaque=None, **kwargs): """ Creates a new stream from a template. Supports video, audio, and subtitle streams. :param template: Copy codec from another :class:`~av.stream.Stream` instance. :param opaque: If True, copy opaque data from the template's codec context. :param \\**kwargs: Set attributes for the stream. :rtype: The new :class:`~av.stream.Stream`. """ cdef const lib.AVCodec *codec cdef Codec codec_obj if opaque is None: opaque = template.type != "video" if opaque: # Copy ctx from template. codec_obj = template.codec_context.codec else: # Construct new codec object. codec_obj = Codec(template.codec_context.codec.name, "w") codec = codec_obj.ptr # Assert that this format supports the requested codec. if not lib.avformat_query_codec(self.ptr.oformat, codec.id, lib.FF_COMPLIANCE_NORMAL): raise ValueError( f"{self.format.name!r} format does not support {codec_obj.name!r} codec" ) # Create new stream in the AVFormatContext, set AVCodecContext values. cdef lib.AVStream *stream = lib.avformat_new_stream(self.ptr, codec) cdef lib.AVCodecContext *codec_context = lib.avcodec_alloc_context3(codec) err_check(lib.avcodec_parameters_to_context(codec_context, template.ptr.codecpar)) # Reset the codec tag assuming we are remuxing. codec_context.codec_tag = 0 # Some formats want stream headers to be separate if self.ptr.oformat.flags & lib.AVFMT_GLOBALHEADER: codec_context.flags |= lib.AV_CODEC_FLAG_GLOBAL_HEADER # Initialize stream codec parameters to populate the codec type. Subsequent changes to # the codec context will be applied just before encoding starts in `start_encoding()`. err_check(lib.avcodec_parameters_from_context(stream.codecpar, codec_context)) # Construct the user-land stream cdef CodecContext py_codec_context = wrap_codec_context(codec_context, codec, None) cdef Stream py_stream = wrap_stream(self, stream, py_codec_context) self.streams.add_stream(py_stream) for k, v in kwargs.items(): setattr(py_stream, k, v) return py_stream def add_data_stream(self, codec_name=None, dict options=None): """add_data_stream(codec_name=None) Creates a new data stream and returns it. :param codec_name: Optional name of the data codec (e.g. 'klv') :type codec_name: str | None :param dict options: Stream options. :rtype: The new :class:`~av.data.stream.DataStream`. """ cdef const lib.AVCodec *codec = NULL if codec_name is not None: codec = lib.avcodec_find_encoder_by_name(codec_name.encode()) if codec == NULL: raise ValueError(f"Unknown data codec: {codec_name}") # Assert that this format supports the requested codec if not lib.avformat_query_codec(self.ptr.oformat, codec.id, lib.FF_COMPLIANCE_NORMAL): raise ValueError( f"{self.format.name!r} format does not support {codec_name!r} codec" ) # Create new stream in the AVFormatContext cdef lib.AVStream *stream = lib.avformat_new_stream(self.ptr, codec) if stream == NULL: raise MemoryError("Could not allocate stream") # Set up codec context if we have a codec cdef lib.AVCodecContext *codec_context = NULL if codec != NULL: codec_context = lib.avcodec_alloc_context3(codec) if codec_context == NULL: raise MemoryError("Could not allocate codec context") # Some formats want stream headers to be separate if self.ptr.oformat.flags & lib.AVFMT_GLOBALHEADER: codec_context.flags |= lib.AV_CODEC_FLAG_GLOBAL_HEADER # Initialize stream codec parameters err_check(lib.avcodec_parameters_from_context(stream.codecpar, codec_context)) else: # For raw data streams, just set the codec type stream.codecpar.codec_type = lib.AVMEDIA_TYPE_DATA # Construct the user-land stream cdef CodecContext py_codec_context = None if codec_context != NULL: py_codec_context = wrap_codec_context(codec_context, codec, None) cdef Stream py_stream = wrap_stream(self, stream, py_codec_context) self.streams.add_stream(py_stream) if options: py_stream.options.update(options) return py_stream cpdef start_encoding(self): """Write the file header! Called automatically.""" if self._started: return # TODO: This does NOT handle options coming from 3 sources. # This is only a rough approximation of what would be cool to do. used_options = set() # Finalize and open all streams. cdef Stream stream for stream in self.streams: ctx = stream.codec_context # Skip codec context handling for data streams without codecs if ctx is None: if stream.type != "data": raise ValueError(f"Stream {stream.index} has no codec context") continue if not ctx.is_open: for k, v in self.options.items(): ctx.options.setdefault(k, v) ctx.open() # Track option consumption. for k in self.options: if k not in ctx.options: used_options.add(k) stream._finalize_for_output() # Open the output file, if needed. cdef bytes name_obj = os.fsencode(self.name if self.file is None else "") cdef char *name = name_obj if self.ptr.pb == NULL and not self.ptr.oformat.flags & lib.AVFMT_NOFILE: err_check(lib.avio_open(&self.ptr.pb, name, lib.AVIO_FLAG_WRITE)) # Copy the metadata dict. dict_to_avdict( &self.ptr.metadata, self.metadata, encoding=self.metadata_encoding, errors=self.metadata_errors ) cdef _Dictionary all_options = Dictionary(self.options, self.container_options) cdef _Dictionary options = all_options.copy() self.err_check(lib.avformat_write_header(self.ptr, &options.ptr)) # Track option usage... for k in all_options: if k not in options: used_options.add(k) # ... and warn if any weren't used. unused_options = {k: v for k, v in self.options.items() if k not in used_options} if unused_options: log.warning("Some options were not used: %s" % unused_options) self._started = True @property def supported_codecs(self): """ Returns a set of all codecs this format supports. """ result = set() cdef const lib.AVCodec *codec = NULL cdef void *opaque = NULL while True: codec = lib.av_codec_iterate(&opaque) if codec == NULL: break if lib.avformat_query_codec(self.ptr.oformat, codec.id, lib.FF_COMPLIANCE_NORMAL) == 1: result.add(codec.name) return result @property def default_video_codec(self): """ Returns the default video codec this container recommends. """ return lib.avcodec_get_name(self.format.optr.video_codec) @property def default_audio_codec(self): """ Returns the default audio codec this container recommends. """ return lib.avcodec_get_name(self.format.optr.audio_codec) @property def default_subtitle_codec(self): """ Returns the default subtitle codec this container recommends. """ return lib.avcodec_get_name(self.format.optr.subtitle_codec) def close(self): close_output(self) def mux(self, packets): # We accept either a Packet, or a sequence of packets. This should # smooth out the transition to the new encode API which returns a # sequence of packets. if isinstance(packets, Packet): self.mux_one(packets) else: for packet in packets: self.mux_one(packet) def mux_one(self, Packet packet not None): self.start_encoding() # Assert the packet is in stream time. if packet.ptr.stream_index < 0 or packet.ptr.stream_index >= self.ptr.nb_streams: raise ValueError("Bad Packet stream_index.") cdef lib.AVStream *stream = self.ptr.streams[packet.ptr.stream_index] packet._rebase_time(stream.time_base) # Make another reference to the packet, as av_interleaved_write_frame # takes ownership of the reference. self.err_check(lib.av_packet_ref(self.packet_ptr, packet.ptr)) cdef int ret with nogil: ret = lib.av_interleaved_write_frame(self.ptr, self.packet_ptr) self.err_check(ret) PyAV-14.2.0/av/container/pyio.pxd000066400000000000000000000013311475734227400165170ustar00rootroot00000000000000cimport libav as lib from libc.stdint cimport int64_t, uint8_t cdef int pyio_read(void *opaque, uint8_t *buf, int buf_size) noexcept nogil cdef int pyio_write(void *opaque, const uint8_t *buf, int buf_size) noexcept nogil cdef int64_t pyio_seek(void *opaque, int64_t offset, int whence) noexcept nogil cdef int pyio_close_gil(lib.AVIOContext *pb) cdef int pyio_close_custom_gil(lib.AVIOContext *pb) cdef class PyIOFile: # File-like source. cdef readonly object file cdef object fread cdef object fwrite cdef object fseek cdef object ftell cdef object fclose # Custom IO for above. cdef lib.AVIOContext *iocontext cdef unsigned char *buffer cdef long pos cdef bint pos_is_valid PyAV-14.2.0/av/container/pyio.pyx000066400000000000000000000123501475734227400165470ustar00rootroot00000000000000cimport libav as lib from libc.string cimport memcpy from av.error cimport stash_exception ctypedef int64_t (*seek_func_t)(void *opaque, int64_t offset, int whence) noexcept nogil cdef class PyIOFile: def __cinit__(self, file, buffer_size, writeable=None): self.file = file cdef seek_func_t seek_func = NULL readable = getattr(self.file, "readable", None) writable = getattr(self.file, "writable", None) seekable = getattr(self.file, "seekable", None) self.fread = getattr(self.file, "read", None) self.fwrite = getattr(self.file, "write", None) self.fseek = getattr(self.file, "seek", None) self.ftell = getattr(self.file, "tell", None) self.fclose = getattr(self.file, "close", None) # To be seekable the file object must have `seek` and `tell` methods. # If it also has a `seekable` method, it must return True. if ( self.fseek is not None and self.ftell is not None and (seekable is None or seekable()) ): seek_func = pyio_seek if writeable is None: writeable = self.fwrite is not None if writeable: if self.fwrite is None or (writable is not None and not writable()): raise ValueError("File object has no write() method, or writable() returned False.") else: if self.fread is None or (readable is not None and not readable()): raise ValueError("File object has no read() method, or readable() returned False.") self.pos = 0 self.pos_is_valid = True # This is effectively the maximum size of reads. self.buffer = lib.av_malloc(buffer_size) self.iocontext = lib.avio_alloc_context( self.buffer, buffer_size, writeable, self, # User data. pyio_read, pyio_write, seek_func ) if seek_func: self.iocontext.seekable = lib.AVIO_SEEKABLE_NORMAL self.iocontext.max_packet_size = buffer_size def __dealloc__(self): with nogil: # FFmpeg will not release custom input, so it's up to us to free it. # Do not touch our original buffer as it may have been freed and replaced. if self.iocontext: lib.av_freep(&self.iocontext.buffer) lib.av_freep(&self.iocontext) # We likely errored badly if we got here, and so are still # responsible for our buffer. else: lib.av_freep(&self.buffer) cdef int pyio_read(void *opaque, uint8_t *buf, int buf_size) noexcept nogil: with gil: return pyio_read_gil(opaque, buf, buf_size) cdef int pyio_read_gil(void *opaque, uint8_t *buf, int buf_size) noexcept: cdef PyIOFile self cdef bytes res try: self = opaque res = self.fread(buf_size) memcpy(buf, res, len(res)) self.pos += len(res) if not res: return lib.AVERROR_EOF return len(res) except Exception as e: return stash_exception() cdef int pyio_write(void *opaque, const uint8_t *buf, int buf_size) noexcept nogil: with gil: return pyio_write_gil(opaque, buf, buf_size) cdef int pyio_write_gil(void *opaque, const uint8_t *buf, int buf_size) noexcept: cdef PyIOFile self cdef bytes bytes_to_write cdef int bytes_written try: self = opaque bytes_to_write = buf[:buf_size] ret_value = self.fwrite(bytes_to_write) bytes_written = ret_value if isinstance(ret_value, int) else buf_size self.pos += bytes_written return bytes_written except Exception as e: return stash_exception() cdef int64_t pyio_seek(void *opaque, int64_t offset, int whence) noexcept nogil: # Seek takes the standard flags, but also a ad-hoc one which means that # the library wants to know how large the file is. We are generally # allowed to ignore this. if whence == lib.AVSEEK_SIZE: return -1 with gil: return pyio_seek_gil(opaque, offset, whence) cdef int64_t pyio_seek_gil(void *opaque, int64_t offset, int whence): cdef PyIOFile self try: self = opaque res = self.fseek(offset, whence) # Track the position for the user. if whence == 0: self.pos = offset elif whence == 1: self.pos += offset else: self.pos_is_valid = False if res is None: if self.pos_is_valid: res = self.pos else: res = self.ftell() return res except Exception as e: return stash_exception() cdef int pyio_close_gil(lib.AVIOContext *pb): try: return lib.avio_close(pb) except Exception as e: stash_exception() cdef int pyio_close_custom_gil(lib.AVIOContext *pb): cdef PyIOFile self try: self = pb.opaque # Flush bytes in the AVIOContext buffers to the custom I/O result = lib.avio_flush(pb) if self.fclose is not None: self.fclose() return 0 except Exception as e: stash_exception() PyAV-14.2.0/av/container/streams.pxd000066400000000000000000000010021475734227400172100ustar00rootroot00000000000000cimport libav as lib from av.stream cimport Stream from .core cimport Container cdef class StreamContainer: cdef list _streams # For the different types. cdef readonly tuple video cdef readonly tuple audio cdef readonly tuple subtitles cdef readonly tuple attachments cdef readonly tuple data cdef readonly tuple other cdef add_stream(self, Stream stream) cdef int _get_best_stream_index(self, Container container, lib.AVMediaType type_enum, Stream related) noexcept PyAV-14.2.0/av/container/streams.pyi000066400000000000000000000023151475734227400172260ustar00rootroot00000000000000from typing import Iterator, Literal, overload from av.attachments.stream import AttachmentStream from av.audio.stream import AudioStream from av.data.stream import DataStream from av.stream import Stream from av.subtitles.stream import SubtitleStream from av.video.stream import VideoStream class StreamContainer: video: tuple[VideoStream, ...] audio: tuple[AudioStream, ...] subtitles: tuple[SubtitleStream, ...] attachments: tuple[AttachmentStream, ...] data: tuple[DataStream, ...] other: tuple[Stream, ...] def __init__(self) -> None: ... def __len__(self) -> int: ... def __iter__(self) -> Iterator[Stream]: ... @overload def __getitem__(self, index: int) -> Stream: ... @overload def __getitem__(self, index: slice) -> list[Stream]: ... @overload def __getitem__(self, index: int | slice) -> Stream | list[Stream]: ... def get( self, *args: int | Stream | dict[str, int | tuple[int, ...]], **kwargs: int | tuple[int, ...], ) -> list[Stream]: ... def best( self, type: Literal["video", "audio", "subtitle", "data", "attachment"], /, related: Stream | None = None, ) -> Stream | None: ... PyAV-14.2.0/av/container/streams.pyx000066400000000000000000000124001475734227400172410ustar00rootroot00000000000000cimport libav as lib def _flatten(input_): for x in input_: if isinstance(x, (tuple, list)): for y in _flatten(x): yield y else: yield x cdef lib.AVMediaType _get_media_type_enum(str type): if type == "video": return lib.AVMEDIA_TYPE_VIDEO elif type == "audio": return lib.AVMEDIA_TYPE_AUDIO elif type == "subtitle": return lib.AVMEDIA_TYPE_SUBTITLE elif type == "attachment": return lib.AVMEDIA_TYPE_ATTACHMENT elif type == "data": return lib.AVMEDIA_TYPE_DATA else: raise ValueError(f"Invalid stream type: {type}") cdef class StreamContainer: """ A tuple-like container of :class:`Stream`. :: # There are a few ways to pulling out streams. first = container.streams[0] video = container.streams.video[0] audio = container.streams.get(audio=(0, 1)) """ def __cinit__(self): self._streams = [] self.video = () self.audio = () self.subtitles = () self.data = () self.attachments = () self.other = () cdef add_stream(self, Stream stream): assert stream.ptr.index == len(self._streams) self._streams.append(stream) if stream.ptr.codecpar.codec_type == lib.AVMEDIA_TYPE_VIDEO: self.video = self.video + (stream, ) elif stream.ptr.codecpar.codec_type == lib.AVMEDIA_TYPE_AUDIO: self.audio = self.audio + (stream, ) elif stream.ptr.codecpar.codec_type == lib.AVMEDIA_TYPE_SUBTITLE: self.subtitles = self.subtitles + (stream, ) elif stream.ptr.codecpar.codec_type == lib.AVMEDIA_TYPE_ATTACHMENT: self.attachments = self.attachments + (stream, ) elif stream.ptr.codecpar.codec_type == lib.AVMEDIA_TYPE_DATA: self.data = self.data + (stream, ) else: self.other = self.other + (stream, ) # Basic tuple interface. def __len__(self): return len(self._streams) def __iter__(self): return iter(self._streams) def __getitem__(self, index): if isinstance(index, int): return self.get(index)[0] else: return self.get(index) def get(self, *args, **kwargs): """get(streams=None, video=None, audio=None, subtitles=None, data=None) Get a selection of :class:`.Stream` as a ``list``. Positional arguments may be ``int`` (which is an index into the streams), or ``list`` or ``tuple`` of those:: # Get the first channel. streams.get(0) # Get the first two audio channels. streams.get(audio=(0, 1)) Keyword arguments (or dicts as positional arguments) as interpreted as ``(stream_type, index_value_or_set)`` pairs:: # Get the first video channel. streams.get(video=0) # or streams.get({'video': 0}) :class:`.Stream` objects are passed through untouched. If nothing is selected, then all streams are returned. """ selection = [] for x in _flatten((args, kwargs)): if x is None: pass elif isinstance(x, Stream): selection.append(x) elif isinstance(x, int): selection.append(self._streams[x]) elif isinstance(x, dict): for type_, indices in x.items(): if type_ == "streams": # For compatibility with the pseudo signature streams = self._streams else: streams = getattr(self, type_) if not isinstance(indices, (tuple, list)): indices = [indices] for i in indices: selection.append(streams[i]) else: raise TypeError("Argument must be Stream or int.", type(x)) return selection or self._streams[:] cdef int _get_best_stream_index(self, Container container, lib.AVMediaType type_enum, Stream related) noexcept: cdef int stream_index if related is None: stream_index = lib.av_find_best_stream(container.ptr, type_enum, -1, -1, NULL, 0) else: stream_index = lib.av_find_best_stream(container.ptr, type_enum, -1, related.ptr.index, NULL, 0) return stream_index def best(self, str type, /, Stream related = None): """best(type: Literal["video", "audio", "subtitle", "attachment", "data"], /, related: Stream | None) Finds the "best" stream in the file. Wraps :ffmpeg:`av_find_best_stream`. Example:: stream = container.streams.best("video") :param type: The type of stream to find :param related: A related stream to use as a reference (optional) :return: The best stream of the specified type :rtype: Stream | None """ cdef type_enum = _get_media_type_enum(type) if len(self._streams) == 0: return None cdef container = self._streams[0].container cdef int stream_index = self._get_best_stream_index(container, type_enum, related) if stream_index < 0: return None return self._streams[stream_index] PyAV-14.2.0/av/data/000077500000000000000000000000001475734227400137535ustar00rootroot00000000000000PyAV-14.2.0/av/data/__init__.pxd000066400000000000000000000000001475734227400162150ustar00rootroot00000000000000PyAV-14.2.0/av/data/__init__.py000066400000000000000000000000001475734227400160520ustar00rootroot00000000000000PyAV-14.2.0/av/data/stream.pxd000066400000000000000000000001101475734227400157530ustar00rootroot00000000000000from av.stream cimport Stream cdef class DataStream(Stream): pass PyAV-14.2.0/av/data/stream.pyi000066400000000000000000000002051475734227400157660ustar00rootroot00000000000000from av.frame import Frame from av.packet import Packet from av.stream import Stream class DataStream(Stream): name: str | None PyAV-14.2.0/av/data/stream.pyx000066400000000000000000000006741475734227400160170ustar00rootroot00000000000000cimport libav as lib cdef class DataStream(Stream): def __repr__(self): return ( f"'} at 0x{id(self):x}>" ) @property def name(self): cdef const lib.AVCodecDescriptor *desc = lib.avcodec_descriptor_get(self.ptr.codecpar.codec_id) if desc == NULL: return None return desc.name PyAV-14.2.0/av/datasets.py000066400000000000000000000060261475734227400152300ustar00rootroot00000000000000import errno import logging import os import sys from typing import Iterator from urllib.request import urlopen log = logging.getLogger(__name__) def iter_data_dirs(check_writable: bool = False) -> Iterator[str]: try: yield os.environ["PYAV_TESTDATA_DIR"] except KeyError: pass if os.name == "nt": yield os.path.join(sys.prefix, "pyav", "datasets") return bases = [ "/usr/local/share", "/usr/local/lib", "/usr/share", "/usr/lib", ] # Prefer the local virtualenv. if hasattr(sys, "real_prefix"): bases.insert(0, sys.prefix) for base in bases: dir_ = os.path.join(base, "pyav", "datasets") if check_writable: if os.path.exists(dir_): if not os.access(dir_, os.W_OK): continue else: if not os.access(base, os.W_OK): continue yield dir_ yield os.path.join(os.path.expanduser("~"), ".pyav", "datasets") def cached_download(url: str, name: str) -> str: """Download the data at a URL, and cache it under the given name. The file is stored under `pyav/test` with the given name in the directory :envvar:`PYAV_TESTDATA_DIR`, or the first that is writeable of: - the current virtualenv - ``/usr/local/share`` - ``/usr/local/lib`` - ``/usr/share`` - ``/usr/lib`` - the user's home """ clean_name = os.path.normpath(name) if clean_name != name: raise ValueError(f"{name} is not normalized.") for dir_ in iter_data_dirs(): path = os.path.join(dir_, name) if os.path.exists(path): return path dir_ = next(iter_data_dirs(True)) path = os.path.join(dir_, name) log.info(f"Downloading {url} to {path}") response = urlopen(url) if response.getcode() != 200: raise ValueError(f"HTTP {response.getcode()}") dir_ = os.path.dirname(path) try: os.makedirs(dir_) except OSError as e: if e.errno != errno.EEXIST: raise tmp_path = path + ".tmp" with open(tmp_path, "wb") as fh: while True: chunk = response.read(8196) if chunk: fh.write(chunk) else: break os.rename(tmp_path, path) return path def fate(name: str) -> str: """Download and return a path to a sample from the FFmpeg test suite. Data is handled by :func:`cached_download`. See the `FFmpeg Automated Test Environment `_ """ return cached_download( "http://fate.ffmpeg.org/fate-suite/" + name, os.path.join("fate-suite", name.replace("/", os.path.sep)), ) def curated(name: str) -> str: """Download and return a path to a sample that is curated by the PyAV developers. Data is handled by :func:`cached_download`. """ return cached_download( "https://pyav.org/datasets/" + name, os.path.join("pyav-curated", name.replace("/", os.path.sep)), ) PyAV-14.2.0/av/descriptor.pxd000066400000000000000000000010071475734227400157330ustar00rootroot00000000000000cimport libav as lib cdef class Descriptor: # These are present as: # - AVCodecContext.av_class (same as avcodec_get_class()) # - AVFormatContext.av_class (same as avformat_get_class()) # - AVFilterContext.av_class (same as avfilter_get_class()) # - AVCodec.priv_class # - AVOutputFormat.priv_class # - AVInputFormat.priv_class # - AVFilter.priv_class cdef const lib.AVClass *ptr cdef object _options # Option list cache. cdef Descriptor wrap_avclass(const lib.AVClass*) PyAV-14.2.0/av/descriptor.pyi000066400000000000000000000001711475734227400157420ustar00rootroot00000000000000from typing import NoReturn from .option import Option class Descriptor: name: str options: tuple[Option, ...] PyAV-14.2.0/av/descriptor.pyx000066400000000000000000000043251475734227400157660ustar00rootroot00000000000000cimport libav as lib from .option cimport Option, OptionChoice, wrap_option, wrap_option_choice cdef object _cinit_sentinel = object() cdef Descriptor wrap_avclass(const lib.AVClass *ptr): if ptr == NULL: return None cdef Descriptor obj = Descriptor(_cinit_sentinel) obj.ptr = ptr return obj cdef class Descriptor: def __cinit__(self, sentinel): if sentinel is not _cinit_sentinel: raise RuntimeError("Cannot construct av.Descriptor") @property def name(self): return self.ptr.class_name if self.ptr.class_name else None @property def options(self): cdef const lib.AVOption *ptr = self.ptr.option cdef const lib.AVOption *choice_ptr cdef Option option cdef OptionChoice option_choice cdef bint choice_is_default if self._options is None: options = [] ptr = self.ptr.option while ptr != NULL and ptr.name != NULL: if ptr.type == lib.AV_OPT_TYPE_CONST: ptr += 1 continue choices = [] if ptr.unit != NULL: # option has choices (matching const options) choice_ptr = self.ptr.option while choice_ptr != NULL and choice_ptr.name != NULL: if choice_ptr.type != lib.AV_OPT_TYPE_CONST or choice_ptr.unit != ptr.unit: choice_ptr += 1 continue choice_is_default = (choice_ptr.default_val.i64 == ptr.default_val.i64 or ptr.type == lib.AV_OPT_TYPE_FLAGS and choice_ptr.default_val.i64 & ptr.default_val.i64) option_choice = wrap_option_choice(choice_ptr, choice_is_default) choices.append(option_choice) choice_ptr += 1 option = wrap_option(tuple(choices), ptr) options.append(option) ptr += 1 self._options = tuple(options) return self._options def __repr__(self): return f"<{self.__class__.__name__} {self.name} at 0x{id(self):x}>" PyAV-14.2.0/av/dictionary.pxd000066400000000000000000000002561475734227400157270ustar00rootroot00000000000000cimport libav as lib cdef class _Dictionary: cdef lib.AVDictionary *ptr cpdef _Dictionary copy(self) cdef _Dictionary wrap_dictionary(lib.AVDictionary *input_) PyAV-14.2.0/av/dictionary.pyi000066400000000000000000000006041475734227400157320ustar00rootroot00000000000000from collections.abc import MutableMapping from typing import Iterator class Dictionary(MutableMapping[str, str]): def __getitem__(self, key: str) -> str: ... def __setitem__(self, key: str, value: str) -> None: ... def __delitem__(self, key: str) -> None: ... def __len__(self) -> int: ... def __iter__(self) -> Iterator[str]: ... def __repr__(self) -> str: ... PyAV-14.2.0/av/dictionary.pyx000066400000000000000000000030261475734227400157520ustar00rootroot00000000000000from collections.abc import MutableMapping from av.error cimport err_check cdef class _Dictionary: def __cinit__(self, *args, **kwargs): for arg in args: self.update(arg) if kwargs: self.update(kwargs) def __dealloc__(self): if self.ptr != NULL: lib.av_dict_free(&self.ptr) def __getitem__(self, str key): cdef lib.AVDictionaryEntry *element = lib.av_dict_get(self.ptr, key, NULL, 0) if element != NULL: return element.value else: raise KeyError(key) def __setitem__(self, str key, str value): err_check(lib.av_dict_set(&self.ptr, key, value, 0)) def __delitem__(self, str key): err_check(lib.av_dict_set(&self.ptr, key, NULL, 0)) def __len__(self): return err_check(lib.av_dict_count(self.ptr)) def __iter__(self): cdef lib.AVDictionaryEntry *element = NULL while True: element = lib.av_dict_get(self.ptr, "", element, lib.AV_DICT_IGNORE_SUFFIX) if element == NULL: break yield element.key def __repr__(self): return f"av.Dictionary({dict(self)!r})" cpdef _Dictionary copy(self): cdef _Dictionary other = Dictionary() lib.av_dict_copy(&other.ptr, self.ptr, 0) return other class Dictionary(_Dictionary, MutableMapping): pass cdef _Dictionary wrap_dictionary(lib.AVDictionary *input_): cdef _Dictionary output = Dictionary() output.ptr = input_ return output PyAV-14.2.0/av/error.pxd000066400000000000000000000001311475734227400147030ustar00rootroot00000000000000 cdef int stash_exception(exc_info=*) cpdef int err_check(int res, filename=*) except -1 PyAV-14.2.0/av/error.pyi000066400000000000000000000062001475734227400147140ustar00rootroot00000000000000import builtins from enum import Enum classes: dict[int, Exception] def code_to_tag(code: int) -> bytes: ... def tag_to_code(tag: bytes) -> int: ... def err_check(res: int, filename: str | None = None) -> int: ... class FFmpegError(Exception): errno: int | None strerror: str | None filename: str log: tuple[int, tuple[int, str, str] | None] def __init__( self, code: int, message: str, filename: str | None = None, log: tuple[int, tuple[int, str, str] | None] | None = None, ) -> None: ... class LookupError(FFmpegError): ... class HTTPError(FFmpegError): ... class HTTPClientError(FFmpegError): ... class UndefinedError(FFmpegError): ... class InvalidDataError(FFmpegError, builtins.ValueError): ... class BugError(FFmpegError, builtins.RuntimeError): ... class BufferTooSmallError(FFmpegError, builtins.ValueError): ... class BSFNotFoundError(LookupError): ... class DecoderNotFoundError(LookupError): ... class DemuxerNotFoundError(LookupError): ... class EncoderNotFoundError(LookupError): ... class ExitError(FFmpegError): ... class ExternalError(FFmpegError): ... class FilterNotFoundError(LookupError): ... class MuxerNotFoundError(LookupError): ... class OptionNotFoundError(LookupError): ... class PatchWelcomeError(FFmpegError): ... class ProtocolNotFoundError(LookupError): ... class UnknownError(FFmpegError): ... class ExperimentalError(FFmpegError): ... class InputChangedError(FFmpegError): ... class OutputChangedError(FFmpegError): ... class HTTPBadRequestError(HTTPClientError): ... class HTTPUnauthorizedError(HTTPClientError): ... class HTTPForbiddenError(HTTPClientError): ... class HTTPNotFoundError(HTTPClientError): ... class HTTPOtherClientError(HTTPClientError): ... class HTTPServerError(HTTPError): ... class PyAVCallbackError(FFmpegError, builtins.RuntimeError): ... class BrokenPipeError(FFmpegError, builtins.BrokenPipeError): ... class ChildProcessError(FFmpegError, builtins.ChildProcessError): ... class ConnectionAbortedError(FFmpegError, builtins.ConnectionAbortedError): ... class ConnectionRefusedError(FFmpegError, builtins.ConnectionRefusedError): ... class ConnectionResetError(FFmpegError, builtins.ConnectionResetError): ... class BlockingIOError(FFmpegError, builtins.BlockingIOError): ... class EOFError(FFmpegError, builtins.EOFError): ... class FileExistsError(FFmpegError, builtins.FileExistsError): ... class FileNotFoundError(FFmpegError, builtins.FileNotFoundError): ... class InterruptedError(FFmpegError, builtins.InterruptedError): ... class IsADirectoryError(FFmpegError, builtins.IsADirectoryError): ... class MemoryError(FFmpegError, builtins.MemoryError): ... class NotADirectoryError(FFmpegError, builtins.NotADirectoryError): ... class NotImplementedError(FFmpegError, builtins.NotImplementedError): ... class OverflowError(FFmpegError, builtins.OverflowError): ... class OSError(FFmpegError, builtins.OSError): ... class PermissionError(FFmpegError, builtins.PermissionError): ... class ProcessLookupError(FFmpegError, builtins.ProcessLookupError): ... class TimeoutError(FFmpegError, builtins.TimeoutError): ... class ValueError(FFmpegError, builtins.ValueError): ... PyAV-14.2.0/av/error.pyx000066400000000000000000000301671475734227400147440ustar00rootroot00000000000000cimport libav as lib from libc.stdlib cimport free, malloc from av.logging cimport get_last_error import errno import os import sys import traceback from threading import local # Will get extended with all of the exceptions. __all__ = [ "ErrorType", "FFmpegError", "LookupError", "HTTPError", "HTTPClientError", "UndefinedError", ] cpdef code_to_tag(int code): """Convert an integer error code into 4-byte tag. >>> code_to_tag(1953719668) b'test' """ return bytes(( code & 0xff, (code >> 8) & 0xff, (code >> 16) & 0xff, (code >> 24) & 0xff, )) cpdef tag_to_code(bytes tag): """Convert a 4-byte error tag into an integer code. >>> tag_to_code(b'test') 1953719668 """ if len(tag) != 4: raise ValueError("Error tags are 4 bytes.") return ( (tag[0]) + (tag[1] << 8) + (tag[2] << 16) + (tag[3] << 24) ) class FFmpegError(Exception): """Exception class for errors from within FFmpeg. .. attribute:: errno FFmpeg's integer error code. .. attribute:: strerror FFmpeg's error message. .. attribute:: filename The filename that was being operated on (if available). .. attribute:: log The tuple from :func:`av.logging.get_last_log`, or ``None``. """ def __init__(self, code, message, filename=None, log=None): self.errno = code self.strerror = message args = [code, message] if filename or log: args.append(filename) if log: args.append(log) super(FFmpegError, self).__init__(*args) self.args = tuple(args) # FileNotFoundError/etc. only pulls 2 args. @property def filename(self): try: return self.args[2] except IndexError: pass @property def log(self): try: return self.args[3] except IndexError: pass def __str__(self): msg = "" if self.errno is not None: msg = f"{msg}[Errno {self.errno}] " if self.strerror is not None: msg = f"{msg}{self.strerror}" if self.filename: msg = f"{msg}: {self.filename!r}" if self.log: msg = f"{msg}; last error log: [{self.log[1].strip()}] {self.log[2].strip()}" return msg # Our custom error, used in callbacks. cdef int c_PYAV_STASHED_ERROR = tag_to_code(b"PyAV") cdef str PYAV_STASHED_ERROR_message = "Error in PyAV callback" # Bases for the FFmpeg-based exceptions. class LookupError(FFmpegError, LookupError): pass class HTTPError(FFmpegError): pass class HTTPClientError(FFmpegError): pass # Tuples of (enum_name, enum_value, exc_name, exc_base). _ffmpeg_specs = ( ("BSF_NOT_FOUND", -lib.AVERROR_BSF_NOT_FOUND, "BSFNotFoundError", LookupError), ("BUG", -lib.AVERROR_BUG, None, RuntimeError), ("BUFFER_TOO_SMALL", -lib.AVERROR_BUFFER_TOO_SMALL, None, ValueError), ("DECODER_NOT_FOUND", -lib.AVERROR_DECODER_NOT_FOUND, None, LookupError), ("DEMUXER_NOT_FOUND", -lib.AVERROR_DEMUXER_NOT_FOUND, None, LookupError), ("ENCODER_NOT_FOUND", -lib.AVERROR_ENCODER_NOT_FOUND, None, LookupError), ("EOF", -lib.AVERROR_EOF, "EOFError", EOFError), ("EXIT", -lib.AVERROR_EXIT, None, None), ("EXTERNAL", -lib.AVERROR_EXTERNAL, None, None), ("FILTER_NOT_FOUND", -lib.AVERROR_FILTER_NOT_FOUND, None, LookupError), ("INVALIDDATA", -lib.AVERROR_INVALIDDATA, "InvalidDataError", ValueError), ("MUXER_NOT_FOUND", -lib.AVERROR_MUXER_NOT_FOUND, None, LookupError), ("OPTION_NOT_FOUND", -lib.AVERROR_OPTION_NOT_FOUND, None, LookupError), ("PATCHWELCOME", -lib.AVERROR_PATCHWELCOME, "PatchWelcomeError", None), ("PROTOCOL_NOT_FOUND", -lib.AVERROR_PROTOCOL_NOT_FOUND, None, LookupError), ("UNKNOWN", -lib.AVERROR_UNKNOWN, None, None), ("EXPERIMENTAL", -lib.AVERROR_EXPERIMENTAL, None, None), ("INPUT_CHANGED", -lib.AVERROR_INPUT_CHANGED, None, None), ("OUTPUT_CHANGED", -lib.AVERROR_OUTPUT_CHANGED, None, None), ("HTTP_BAD_REQUEST", -lib.AVERROR_HTTP_BAD_REQUEST, "HTTPBadRequestError", HTTPClientError), ("HTTP_UNAUTHORIZED", -lib.AVERROR_HTTP_UNAUTHORIZED, "HTTPUnauthorizedError", HTTPClientError), ("HTTP_FORBIDDEN", -lib.AVERROR_HTTP_FORBIDDEN, "HTTPForbiddenError", HTTPClientError), ("HTTP_NOT_FOUND", -lib.AVERROR_HTTP_NOT_FOUND, "HTTPNotFoundError", HTTPClientError), ("HTTP_OTHER_4XX", -lib.AVERROR_HTTP_OTHER_4XX, "HTTPOtherClientError", HTTPClientError), ("HTTP_SERVER_ERROR", -lib.AVERROR_HTTP_SERVER_ERROR, "HTTPServerError", HTTPError), ("PYAV_CALLBACK", c_PYAV_STASHED_ERROR, "PyAVCallbackError", RuntimeError), ) cdef sentinel = object() class EnumType(type): def __new__(mcl, name, bases, attrs, *args): # Just adapting the method signature. return super().__new__(mcl, name, bases, attrs) def __init__(self, name, bases, attrs, items): self._by_name = {} self._by_value = {} self._all = [] for spec in items: self._create(*spec) def _create(self, name, value, doc=None, by_value_only=False): # We only have one instance per value. try: item = self._by_value[value] except KeyError: item = self(sentinel, name, value, doc) self._by_value[value] = item return item def __len__(self): return len(self._all) def __iter__(self): return iter(self._all) def __getitem__(self, key): if isinstance(key, str): return self._by_name[key] if isinstance(key, int): try: return self._by_value[key] except KeyError: pass raise KeyError(key) if isinstance(key, self): return key raise TypeError(f"{self.__name__} indices must be str, int, or itself") def _get(self, long value, bint create=False): try: return self._by_value[value] except KeyError: pass if not create: return return self._create(f"{self.__name__.upper()}_{value}", value, by_value_only=True) def get(self, key, default=None, create=False): try: return self[key] except KeyError: if create: return self._get(key, create=True) return default cdef class EnumItem: """An enumeration of FFmpeg's error types. .. attribute:: tag The FFmpeg byte tag for the error. .. attribute:: strerror The error message that would be returned. """ cdef readonly str name cdef readonly int value def __cinit__(self, sentinel_, str name, int value, doc=None): if sentinel_ is not sentinel: raise RuntimeError(f"Cannot instantiate {self.__class__.__name__}.") self.name = name self.value = value self.__doc__ = doc def __repr__(self): return f"<{self.__class__.__module__}.{self.__class__.__name__}:{self.name}(0x{self.value:x})>" def __str__(self): return self.name def __int__(self): return self.value @property def tag(self): return code_to_tag(self.value) ErrorType = EnumType("ErrorType", (EnumItem, ), {"__module__": __name__}, [x[:2] for x in _ffmpeg_specs]) for enum in ErrorType: # Mimick the errno module. globals()[enum.name] = enum if enum.value == c_PYAV_STASHED_ERROR: enum.strerror = PYAV_STASHED_ERROR_message else: enum.strerror = lib.av_err2str(-enum.value) # Mimick the builtin exception types. # See https://www.python.org/dev/peps/pep-3151/#new-exception-classes # Use the named ones we have, otherwise default to OSError for anything in errno. # See this command for the count of POSIX codes used: # # egrep -IR 'AVERROR\(E[A-Z]+\)' vendor/ffmpeg-4.2 |\ # sed -E 's/.*AVERROR\((E[A-Z]+)\).*/\1/' | \ # sort | uniq -c # # The biggest ones that don't map to PEP 3151 builtins: # # 2106 EINVAL -> ValueError # 649 EIO -> IOError (if it is distinct from OSError) # 4080 ENOMEM -> MemoryError # 340 ENOSYS -> NotImplementedError # 35 ERANGE -> OverflowError classes = {} def _extend_builtin(name, codes): base = getattr(__builtins__, name, OSError) cls = type(name, (FFmpegError, base), dict(__module__=__name__)) # Register in builder. for code in codes: classes[code] = cls # Register in module. globals()[name] = cls __all__.append(name) return cls # PEP 3151 builtins. _extend_builtin("PermissionError", (errno.EACCES, errno.EPERM)) _extend_builtin("BlockingIOError", (errno.EAGAIN, errno.EALREADY, errno.EINPROGRESS, errno.EWOULDBLOCK)) _extend_builtin("ChildProcessError", (errno.ECHILD, )) _extend_builtin("ConnectionAbortedError", (errno.ECONNABORTED, )) _extend_builtin("ConnectionRefusedError", (errno.ECONNREFUSED, )) _extend_builtin("ConnectionResetError", (errno.ECONNRESET, )) _extend_builtin("FileExistsError", (errno.EEXIST, )) _extend_builtin("InterruptedError", (errno.EINTR, )) _extend_builtin("IsADirectoryError", (errno.EISDIR, )) _extend_builtin("FileNotFoundError", (errno.ENOENT, )) _extend_builtin("NotADirectoryError", (errno.ENOTDIR, )) _extend_builtin("BrokenPipeError", (errno.EPIPE, errno.ESHUTDOWN)) _extend_builtin("ProcessLookupError", (errno.ESRCH, )) _extend_builtin("TimeoutError", (errno.ETIMEDOUT, )) # Other obvious ones. _extend_builtin("ValueError", (errno.EINVAL, )) _extend_builtin("MemoryError", (errno.ENOMEM, )) _extend_builtin("NotImplementedError", (errno.ENOSYS, )) _extend_builtin("OverflowError", (errno.ERANGE, )) # The rest of them (for now) _extend_builtin("OSError", [code for code in errno.errorcode if code not in classes]) # Classes for the FFmpeg errors. for enum_name, code, name, base in _ffmpeg_specs: name = name or enum_name.title().replace("_", "") + "Error" if base is None: bases = (FFmpegError,) elif issubclass(base, FFmpegError): bases = (base,) else: bases = (FFmpegError, base) cls = type(name, bases, {"__module__": __name__}) # Register in builder. classes[code] = cls # Register in module. globals()[name] = cls __all__.append(name) del _ffmpeg_specs # Storage for stashing. cdef object _local = local() cdef int _err_count = 0 cdef int stash_exception(exc_info=None): global _err_count existing = getattr(_local, "exc_info", None) if existing is not None: print >> sys.stderr, "PyAV library exception being dropped:" traceback.print_exception(*existing) _err_count -= 1 # Balance out the +=1 that is coming. exc_info = exc_info or sys.exc_info() _local.exc_info = exc_info if exc_info: _err_count += 1 return -c_PYAV_STASHED_ERROR cdef int _last_log_count = 0 cpdef int err_check(int res, filename=None) except -1: """Raise appropriate exceptions from library return code.""" global _err_count global _last_log_count # Check for stashed exceptions. if _err_count: exc_info = getattr(_local, "exc_info", None) if exc_info is not None: _err_count -= 1 _local.exc_info = None raise exc_info[0], exc_info[1], exc_info[2] if res >= 0: return res # Grab details from the last log. log_count, last_log = get_last_error() if log_count > _last_log_count: _last_log_count = log_count log = last_log else: log = None cdef int code = -res cdef char* error_buffer = malloc(lib.AV_ERROR_MAX_STRING_SIZE * sizeof(char)) if error_buffer == NULL: raise MemoryError() try: if code == c_PYAV_STASHED_ERROR: message = PYAV_STASHED_ERROR_message else: lib.av_strerror(res, error_buffer, lib.AV_ERROR_MAX_STRING_SIZE) # Fallback to OS error string if no message message = error_buffer or os.strerror(code) cls = classes.get(code, UndefinedError) raise cls(code, message, filename, log) finally: free(error_buffer) class UndefinedError(FFmpegError): """Fallback exception type in case FFmpeg returns an error we don't know about.""" pass PyAV-14.2.0/av/filter/000077500000000000000000000000001475734227400143275ustar00rootroot00000000000000PyAV-14.2.0/av/filter/__init__.pxd000066400000000000000000000000001475734227400165710ustar00rootroot00000000000000PyAV-14.2.0/av/filter/__init__.py000066400000000000000000000002031475734227400164330ustar00rootroot00000000000000from .filter import Filter, FilterFlags, filter_descriptor, filters_available from .graph import Graph from .loudnorm import stats PyAV-14.2.0/av/filter/__init__.pyi000066400000000000000000000001321475734227400166050ustar00rootroot00000000000000from .context import * from .filter import * from .graph import * from .loudnorm import * PyAV-14.2.0/av/filter/context.pxd000066400000000000000000000006041475734227400165300ustar00rootroot00000000000000cimport libav as lib from av.filter.filter cimport Filter from av.filter.graph cimport Graph cdef class FilterContext: cdef lib.AVFilterContext *ptr cdef readonly object _graph cdef readonly Filter filter cdef object _inputs cdef object _outputs cdef bint inited cdef FilterContext wrap_filter_context(Graph graph, Filter filter, lib.AVFilterContext *ptr) PyAV-14.2.0/av/filter/context.pyi000066400000000000000000000010301475734227400165300ustar00rootroot00000000000000from av.filter import Graph from av.frame import Frame from .pad import FilterContextPad class FilterContext: name: str | None inputs: tuple[FilterContextPad, ...] outputs: tuple[FilterContextPad, ...] def init(self, args: str | None = None, **kwargs: str | None) -> None: ... def link_to( self, input_: FilterContext, output_idx: int = 0, input_idx: int = 0 ) -> None: ... @property def graph(self) -> Graph: ... def push(self, frame: Frame) -> None: ... def pull(self) -> Frame: ... PyAV-14.2.0/av/filter/context.pyx000066400000000000000000000103631475734227400165600ustar00rootroot00000000000000import weakref from av.audio.frame cimport alloc_audio_frame from av.dictionary cimport _Dictionary from av.dictionary import Dictionary from av.error cimport err_check from av.filter.pad cimport alloc_filter_pads from av.frame cimport Frame from av.utils cimport avrational_to_fraction from av.video.frame cimport alloc_video_frame cdef object _cinit_sentinel = object() cdef FilterContext wrap_filter_context(Graph graph, Filter filter, lib.AVFilterContext *ptr): cdef FilterContext self = FilterContext(_cinit_sentinel) self._graph = weakref.ref(graph) self.filter = filter self.ptr = ptr return self cdef class FilterContext: def __cinit__(self, sentinel): if sentinel is not _cinit_sentinel: raise RuntimeError("cannot construct FilterContext") def __repr__(self): if self.ptr != NULL: name = repr(self.ptr.name) if self.ptr.name != NULL else "" else: name = "None" parent = self.filter.ptr.name if self.filter and self.filter.ptr != NULL else None return f"" @property def name(self): if self.ptr.name != NULL: return self.ptr.name @property def inputs(self): if self._inputs is None: self._inputs = alloc_filter_pads(self.filter, self.ptr.input_pads, True, self) return self._inputs @property def outputs(self): if self._outputs is None: self._outputs = alloc_filter_pads(self.filter, self.ptr.output_pads, False, self) return self._outputs def init(self, args=None, **kwargs): if self.inited: raise ValueError("already inited") if args and kwargs: raise ValueError("cannot init from args and kwargs") cdef _Dictionary dict_ = None cdef char *c_args = NULL if args or not kwargs: if args: c_args = args err_check(lib.avfilter_init_str(self.ptr, c_args)) else: dict_ = Dictionary(kwargs) err_check(lib.avfilter_init_dict(self.ptr, &dict_.ptr)) self.inited = True if dict_: raise ValueError(f"unused config: {', '.join(sorted(dict_))}") def link_to(self, FilterContext input_, int output_idx=0, int input_idx=0): err_check(lib.avfilter_link(self.ptr, output_idx, input_.ptr, input_idx)) @property def graph(self): if (graph := self._graph()): return graph else: raise RuntimeError("graph is unallocated") def push(self, Frame frame): cdef int res if frame is None: with nogil: res = lib.av_buffersrc_write_frame(self.ptr, NULL) err_check(res) return elif self.filter.name in ("abuffer", "buffer"): with nogil: res = lib.av_buffersrc_write_frame(self.ptr, frame.ptr) err_check(res) return # Delegate to the input. if len(self.inputs) != 1: raise ValueError( f"cannot delegate push without single input; found {len(self.inputs)}" ) if not self.inputs[0].link: raise ValueError("cannot delegate push without linked input") self.inputs[0].linked.context.push(frame) def pull(self): cdef Frame frame cdef int res if self.filter.name == "buffersink": frame = alloc_video_frame() elif self.filter.name == "abuffersink": frame = alloc_audio_frame() else: # Delegate to the output. if len(self.outputs) != 1: raise ValueError( f"cannot delegate pull without single output; found {len(self.outputs)}" ) if not self.outputs[0].link: raise ValueError("cannot delegate pull without linked output") return self.outputs[0].linked.context.pull() self.graph.configure() with nogil: res = lib.av_buffersink_get_frame(self.ptr, frame.ptr) err_check(res) frame._init_user_attributes() frame.time_base = avrational_to_fraction(&self.ptr.inputs[0].time_base) return frame PyAV-14.2.0/av/filter/filter.pxd000066400000000000000000000003701475734227400163310ustar00rootroot00000000000000cimport libav as lib from av.descriptor cimport Descriptor cdef class Filter: cdef const lib.AVFilter *ptr cdef object _inputs cdef object _outputs cdef Descriptor _descriptor cdef Filter wrap_filter(const lib.AVFilter *ptr) PyAV-14.2.0/av/filter/filter.pyi000066400000000000000000000007641475734227400163460ustar00rootroot00000000000000from av.descriptor import Descriptor from av.option import Option from .pad import FilterPad class Filter: name: str description: str descriptor: Descriptor options: tuple[Option, ...] | None flags: int dynamic_inputs: bool dynamic_outputs: bool timeline_support: bool slice_threads: bool command_support: bool inputs: tuple[FilterPad, ...] outputs: tuple[FilterPad, ...] def __init__(self, name: str) -> None: ... filters_available: set[str] PyAV-14.2.0/av/filter/filter.pyx000066400000000000000000000053131475734227400163600ustar00rootroot00000000000000cimport libav as lib from av.descriptor cimport wrap_avclass from av.filter.pad cimport alloc_filter_pads cdef object _cinit_sentinel = object() cdef Filter wrap_filter(const lib.AVFilter *ptr): cdef Filter filter_ = Filter(_cinit_sentinel) filter_.ptr = ptr return filter_ cpdef enum FilterFlags: DYNAMIC_INPUTS = lib.AVFILTER_FLAG_DYNAMIC_INPUTS DYNAMIC_OUTPUTS = lib.AVFILTER_FLAG_DYNAMIC_OUTPUTS SLICE_THREADS = lib.AVFILTER_FLAG_SLICE_THREADS SUPPORT_TIMELINE_GENERIC = lib.AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC SUPPORT_TIMELINE_INTERNAL = lib.AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL cdef class Filter: def __cinit__(self, name): if name is _cinit_sentinel: return if not isinstance(name, str): raise TypeError("takes a filter name as a string") self.ptr = lib.avfilter_get_by_name(name) if not self.ptr: raise ValueError(f"no filter {name}") @property def descriptor(self): if self._descriptor is None: self._descriptor = wrap_avclass(self.ptr.priv_class) return self._descriptor @property def options(self): if self.descriptor is None: return return self.descriptor.options @property def name(self): return self.ptr.name @property def description(self): return self.ptr.description @property def flags(self): return self.ptr.flags @property def dynamic_inputs(self): return bool(self.ptr.flags & lib.AVFILTER_FLAG_DYNAMIC_INPUTS) @property def dynamic_outputs(self): return bool(self.ptr.flags & lib.AVFILTER_FLAG_DYNAMIC_OUTPUTS) @property def timeline_support(self): return bool(self.ptr.flags & lib.AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC) @property def slice_threads(self): return bool(self.ptr.flags & lib.AVFILTER_FLAG_SLICE_THREADS) @property def command_support(self): return self.ptr.process_command != NULL @property def inputs(self): if self._inputs is None: self._inputs = alloc_filter_pads(self, self.ptr.inputs, True) return self._inputs @property def outputs(self): if self._outputs is None: self._outputs = alloc_filter_pads(self, self.ptr.outputs, False) return self._outputs cdef get_filter_names(): names = set() cdef const lib.AVFilter *ptr cdef void *opaque = NULL while True: ptr = lib.av_filter_iterate(&opaque) if ptr: names.add(ptr.name) else: break return names filters_available = get_filter_names() filter_descriptor = wrap_avclass(lib.avfilter_get_class()) PyAV-14.2.0/av/filter/graph.pxd000066400000000000000000000010061475734227400161420ustar00rootroot00000000000000cimport libav as lib from av.filter.context cimport FilterContext cdef class Graph: cdef object __weakref__ cdef lib.AVFilterGraph *ptr cdef readonly bint configured cpdef configure(self, bint auto_buffer=*, bint force=*) cdef dict _name_counts cdef str _get_unique_name(self, str name) cdef _register_context(self, FilterContext) cdef _auto_register(self) cdef int _nb_filters_seen cdef dict _context_by_ptr cdef dict _context_by_name cdef dict _context_by_type PyAV-14.2.0/av/filter/graph.pyi000066400000000000000000000031741475734227400161600ustar00rootroot00000000000000from fractions import Fraction from typing import Any from av.audio.format import AudioFormat from av.audio.frame import AudioFrame from av.audio.layout import AudioLayout from av.audio.stream import AudioStream from av.video.format import VideoFormat from av.video.frame import VideoFrame from av.video.stream import VideoStream from .context import FilterContext from .filter import Filter class Graph: configured: bool def __init__(self) -> None: ... def configure(self, auto_buffer: bool = True, force: bool = False) -> None: ... def link_nodes(self, *nodes: FilterContext) -> Graph: ... def add( self, filter: str | Filter, args: Any = None, **kwargs: str ) -> FilterContext: ... def add_buffer( self, template: VideoStream | None = None, width: int | None = None, height: int | None = None, format: VideoFormat | None = None, name: str | None = None, time_base: Fraction | None = None, ) -> FilterContext: ... def add_abuffer( self, template: AudioStream | None = None, sample_rate: int | None = None, format: AudioFormat | str | None = None, layout: AudioLayout | str | None = None, channels: int | None = None, name: str | None = None, time_base: Fraction | None = None, ) -> FilterContext: ... def set_audio_frame_size(self, frame_size: int) -> None: ... def push(self, frame: None | AudioFrame | VideoFrame) -> None: ... def pull(self) -> VideoFrame | AudioFrame: ... def vpush(self, frame: VideoFrame | None) -> None: ... def vpull(self) -> VideoFrame: ... PyAV-14.2.0/av/filter/graph.pyx000066400000000000000000000200301475734227400161650ustar00rootroot00000000000000import warnings from fractions import Fraction from av.audio.format cimport AudioFormat from av.audio.frame cimport AudioFrame from av.audio.layout cimport AudioLayout from av.error cimport err_check from av.filter.context cimport FilterContext, wrap_filter_context from av.filter.filter cimport Filter, wrap_filter from av.video.format cimport VideoFormat from av.video.frame cimport VideoFrame cdef class Graph: def __cinit__(self): self.ptr = lib.avfilter_graph_alloc() self.configured = False self._name_counts = {} self._nb_filters_seen = 0 self._context_by_ptr = {} self._context_by_name = {} self._context_by_type = {} def __dealloc__(self): if self.ptr: # This frees the graph, filter contexts, links, etc.. lib.avfilter_graph_free(&self.ptr) cdef str _get_unique_name(self, str name): count = self._name_counts.get(name, 0) self._name_counts[name] = count + 1 if count: return "%s_%s" % (name, count) else: return name cpdef configure(self, bint auto_buffer=True, bint force=False): if self.configured and not force: return err_check(lib.avfilter_graph_config(self.ptr, NULL)) self.configured = True # We get auto-inserted stuff here. self._auto_register() def link_nodes(self, *nodes): """ Links nodes together for simple filter graphs. """ for c, n in zip(nodes, nodes[1:]): c.link_to(n) return self def add(self, filter, args=None, **kwargs): cdef Filter cy_filter if isinstance(filter, str): cy_filter = Filter(filter) elif isinstance(filter, Filter): cy_filter = filter else: raise TypeError("filter must be a string or Filter") cdef str name = self._get_unique_name(kwargs.pop("name", None) or cy_filter.name) cdef lib.AVFilterContext *ptr = lib.avfilter_graph_alloc_filter(self.ptr, cy_filter.ptr, name) if not ptr: raise RuntimeError("Could not allocate AVFilterContext") # Manually construct this context (so we can return it). cdef FilterContext ctx = wrap_filter_context(self, cy_filter, ptr) ctx.init(args, **kwargs) self._register_context(ctx) # There might have been automatic contexts added (e.g. resamplers, # fifos, and scalers). It is more likely to see them after the graph # is configured, but we want to be safe. self._auto_register() return ctx cdef _register_context(self, FilterContext ctx): self._context_by_ptr[ctx.ptr] = ctx self._context_by_name[ctx.ptr.name] = ctx self._context_by_type.setdefault(ctx.filter.ptr.name, []).append(ctx) cdef _auto_register(self): cdef int i cdef lib.AVFilterContext *c_ctx cdef Filter filter_ cdef FilterContext py_ctx # We assume that filters are never removed from the graph. At this # point we don't expose that in the API, so we should be okay... for i in range(self._nb_filters_seen, self.ptr.nb_filters): c_ctx = self.ptr.filters[i] if c_ctx in self._context_by_ptr: continue filter_ = wrap_filter(c_ctx.filter) py_ctx = wrap_filter_context(self, filter_, c_ctx) self._register_context(py_ctx) self._nb_filters_seen = self.ptr.nb_filters def add_buffer(self, template=None, width=None, height=None, format=None, name=None, time_base=None): if template is not None: if width is None: width = template.width if height is None: height = template.height if format is None: format = template.format if time_base is None: time_base = template.time_base if width is None: raise ValueError("missing width") if height is None: raise ValueError("missing height") if format is None: raise ValueError("missing format") if time_base is None: warnings.warn("missing time_base. Guessing 1/1000 time base. " "This is deprecated and may be removed in future releases.", DeprecationWarning) time_base = Fraction(1, 1000) return self.add( "buffer", name=name, video_size=f"{width}x{height}", pix_fmt=str(int(VideoFormat(format))), time_base=str(time_base), pixel_aspect="1/1", ) def add_abuffer(self, template=None, sample_rate=None, format=None, layout=None, channels=None, name=None, time_base=None): """ Convenience method for adding `abuffer `_. """ if template is not None: if sample_rate is None: sample_rate = template.sample_rate if format is None: format = template.format if layout is None: layout = template.layout.name if channels is None: channels = template.channels if time_base is None: time_base = template.time_base if sample_rate is None: raise ValueError("missing sample_rate") if format is None: raise ValueError("missing format") if layout is None and channels is None: raise ValueError("missing layout or channels") if time_base is None: time_base = Fraction(1, sample_rate) kwargs = dict( sample_rate=str(sample_rate), sample_fmt=AudioFormat(format).name, time_base=str(time_base), ) if layout: kwargs["channel_layout"] = AudioLayout(layout).name if channels: kwargs["channels"] = str(channels) return self.add("abuffer", name=name, **kwargs) def set_audio_frame_size(self, frame_size): """ Set the audio frame size for the graphs `abuffersink`. See `av_buffersink_set_frame_size `_. """ if not self.configured: raise ValueError("graph not configured") sinks = self._context_by_type.get("abuffersink", []) if not sinks: raise ValueError("missing abuffersink filter") for sink in sinks: lib.av_buffersink_set_frame_size((sink).ptr, frame_size) def push(self, frame): if frame is None: contexts = self._context_by_type.get("buffer", []) + self._context_by_type.get("abuffer", []) elif isinstance(frame, VideoFrame): contexts = self._context_by_type.get("buffer", []) elif isinstance(frame, AudioFrame): contexts = self._context_by_type.get("abuffer", []) else: raise ValueError(f"can only AudioFrame, VideoFrame or None; got {type(frame)}") for ctx in contexts: ctx.push(frame) def vpush(self, VideoFrame frame): """Like `push`, but only for VideoFrames.""" for ctx in self._context_by_type.get("buffer", []): ctx.push(frame) # TODO: Test complex filter graphs, add `at: int = 0` arg to pull() and vpull(). def pull(self): vsinks = self._context_by_type.get("buffersink", []) asinks = self._context_by_type.get("abuffersink", []) nsinks = len(vsinks) + len(asinks) if nsinks != 1: raise ValueError(f"can only auto-pull with single sink; found {nsinks}") return (vsinks or asinks)[0].pull() def vpull(self): """Like `pull`, but only for VideoFrames.""" vsinks = self._context_by_type.get("buffersink", []) nsinks = len(vsinks) if nsinks != 1: raise ValueError(f"can only auto-pull with single sink; found {nsinks}") return vsinks[0].pull() PyAV-14.2.0/av/filter/link.pxd000066400000000000000000000005071475734227400160030ustar00rootroot00000000000000cimport libav as lib from av.filter.graph cimport Graph from av.filter.pad cimport FilterContextPad cdef class FilterLink: cdef readonly Graph graph cdef lib.AVFilterLink *ptr cdef FilterContextPad _input cdef FilterContextPad _output cdef FilterLink wrap_filter_link(Graph graph, lib.AVFilterLink *ptr) PyAV-14.2.0/av/filter/link.pyi000066400000000000000000000001561475734227400160110ustar00rootroot00000000000000from .pad import FilterContextPad class FilterLink: input: FilterContextPad output: FilterContextPad PyAV-14.2.0/av/filter/link.pyx000066400000000000000000000030021475734227400160210ustar00rootroot00000000000000cimport libav as lib from av.filter.graph cimport Graph cdef _cinit_sentinel = object() cdef class FilterLink: def __cinit__(self, sentinel): if sentinel is not _cinit_sentinel: raise RuntimeError("cannot instantiate FilterLink") @property def input(self): if self._input: return self._input cdef lib.AVFilterContext *cctx = self.ptr.src cdef unsigned int i for i in range(cctx.nb_outputs): if self.ptr == cctx.outputs[i]: break else: raise RuntimeError("could not find link in context") ctx = self.graph._context_by_ptr[cctx] self._input = ctx.outputs[i] return self._input @property def output(self): if self._output: return self._output cdef lib.AVFilterContext *cctx = self.ptr.dst cdef unsigned int i for i in range(cctx.nb_inputs): if self.ptr == cctx.inputs[i]: break else: raise RuntimeError("could not find link in context") try: ctx = self.graph._context_by_ptr[cctx] except KeyError: raise RuntimeError("could not find context in graph", (cctx.name, cctx.filter.name)) self._output = ctx.inputs[i] return self._output cdef FilterLink wrap_filter_link(Graph graph, lib.AVFilterLink *ptr): cdef FilterLink link = FilterLink(_cinit_sentinel) link.graph = graph link.ptr = ptr return link PyAV-14.2.0/av/filter/loudnorm.pxd000066400000000000000000000001441475734227400167020ustar00rootroot00000000000000from av.audio.stream cimport AudioStream cpdef bytes stats(str loudnorm_args, AudioStream stream) PyAV-14.2.0/av/filter/loudnorm.pyi000066400000000000000000000001521475734227400167070ustar00rootroot00000000000000from av.audio.stream import AudioStream def stats(loudnorm_args: str, stream: AudioStream) -> bytes: ... PyAV-14.2.0/av/filter/loudnorm.pyx000066400000000000000000000035161475734227400167350ustar00rootroot00000000000000# av/filter/loudnorm.pyx cimport libav as lib from cpython.bytes cimport PyBytes_FromString from libc.stdlib cimport free from av.audio.codeccontext cimport AudioCodecContext from av.audio.stream cimport AudioStream from av.container.core cimport Container from av.stream cimport Stream from av.logging import get_level, set_level cdef extern from "libavcodec/avcodec.h": ctypedef struct AVCodecContext: pass cdef extern from "libavformat/avformat.h": ctypedef struct AVFormatContext: pass cdef extern from "loudnorm_impl.h": char* loudnorm_get_stats( AVFormatContext* fmt_ctx, int audio_stream_index, const char* loudnorm_args ) nogil cpdef bytes stats(str loudnorm_args, AudioStream stream): """ Get loudnorm statistics for an audio stream. Args: loudnorm_args (str): Arguments for the loudnorm filter (e.g. "i=-24.0:lra=7.0:tp=-2.0") stream (AudioStream): Input audio stream to analyze Returns: bytes: JSON string containing the loudnorm statistics """ if "print_format=json" not in loudnorm_args: loudnorm_args = loudnorm_args + ":print_format=json" cdef Container container = stream.container cdef AVFormatContext* format_ptr = container.ptr container.ptr = NULL # Prevent double-free cdef int stream_index = stream.index cdef bytes py_args = loudnorm_args.encode("utf-8") cdef const char* c_args = py_args cdef char* result # Save log level since C function overwrite it. level = get_level() with nogil: result = loudnorm_get_stats(format_ptr, stream_index, c_args) if result == NULL: raise RuntimeError("Failed to get loudnorm stats") py_result = result[:] # Make a copy of the string free(result) # Free the C string set_level(level) return py_result PyAV-14.2.0/av/filter/loudnorm_impl.c000066400000000000000000000140341475734227400173550ustar00rootroot00000000000000#include #include #include #include #include #include #ifdef _WIN32 #include #else #include #endif #ifdef _WIN32 static CRITICAL_SECTION json_mutex; static CONDITION_VARIABLE json_cond; static int mutex_initialized = 0; #else static pthread_mutex_t json_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t json_cond = PTHREAD_COND_INITIALIZER; #endif static char json_buffer[2048] = {0}; static int json_captured = 0; // Custom logging callback static void logging_callback(void *ptr, int level, const char *fmt, va_list vl) { char line[2048]; vsnprintf(line, sizeof(line), fmt, vl); const char *json_start = strstr(line, "{"); if (json_start) { #ifdef _WIN32 EnterCriticalSection(&json_mutex); #else pthread_mutex_lock(&json_mutex); #endif strncpy(json_buffer, json_start, sizeof(json_buffer) - 1); json_captured = 1; #ifdef _WIN32 WakeConditionVariable(&json_cond); LeaveCriticalSection(&json_mutex); #else pthread_cond_signal(&json_cond); pthread_mutex_unlock(&json_mutex); #endif } } char* loudnorm_get_stats( AVFormatContext* fmt_ctx, int audio_stream_index, const char* loudnorm_args ) { char* result = NULL; json_captured = 0; // Reset the captured flag memset(json_buffer, 0, sizeof(json_buffer)); // Clear the buffer #ifdef _WIN32 // Initialize synchronization objects if needed if (!mutex_initialized) { InitializeCriticalSection(&json_mutex); InitializeConditionVariable(&json_cond); mutex_initialized = 1; } #endif av_log_set_callback(logging_callback); AVFilterGraph *filter_graph = NULL; AVFilterContext *src_ctx = NULL, *sink_ctx = NULL, *loudnorm_ctx = NULL; AVCodec *codec = NULL; AVCodecContext *codec_ctx = NULL; int ret; AVCodecParameters *codecpar = fmt_ctx->streams[audio_stream_index]->codecpar; codec = (AVCodec *)avcodec_find_decoder(codecpar->codec_id); codec_ctx = avcodec_alloc_context3(codec); avcodec_parameters_to_context(codec_ctx, codecpar); avcodec_open2(codec_ctx, codec, NULL); char ch_layout_str[64]; av_channel_layout_describe(&codecpar->ch_layout, ch_layout_str, sizeof(ch_layout_str)); filter_graph = avfilter_graph_alloc(); char args[512]; snprintf(args, sizeof(args), "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=%s", fmt_ctx->streams[audio_stream_index]->time_base.num, fmt_ctx->streams[audio_stream_index]->time_base.den, codecpar->sample_rate, av_get_sample_fmt_name(codec_ctx->sample_fmt), ch_layout_str); avfilter_graph_create_filter(&src_ctx, avfilter_get_by_name("abuffer"), "src", args, NULL, filter_graph); avfilter_graph_create_filter(&sink_ctx, avfilter_get_by_name("abuffersink"), "sink", NULL, NULL, filter_graph); avfilter_graph_create_filter(&loudnorm_ctx, avfilter_get_by_name("loudnorm"), "loudnorm", loudnorm_args, NULL, filter_graph); avfilter_link(src_ctx, 0, loudnorm_ctx, 0); avfilter_link(loudnorm_ctx, 0, sink_ctx, 0); avfilter_graph_config(filter_graph, NULL); AVPacket *packet = av_packet_alloc(); AVFrame *frame = av_frame_alloc(); AVFrame *filt_frame = av_frame_alloc(); while ((ret = av_read_frame(fmt_ctx, packet)) >= 0) { if (packet->stream_index != audio_stream_index) { av_packet_unref(packet); continue; } ret = avcodec_send_packet(codec_ctx, packet); if (ret < 0) { av_packet_unref(packet); continue; } while (ret >= 0) { ret = avcodec_receive_frame(codec_ctx, frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break; if (ret < 0) goto end; ret = av_buffersrc_add_frame_flags(src_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF); if (ret < 0) goto end; while (1) { ret = av_buffersink_get_frame(sink_ctx, filt_frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break; if (ret < 0) goto end; av_frame_unref(filt_frame); } } av_packet_unref(packet); } // Flush decoder avcodec_send_packet(codec_ctx, NULL); while (avcodec_receive_frame(codec_ctx, frame) >= 0) { ret = av_buffersrc_add_frame(src_ctx, frame); if (ret < 0) goto end; } // Flush filter ret = av_buffersrc_add_frame(src_ctx, NULL); if (ret < 0) goto end; while (av_buffersink_get_frame(sink_ctx, filt_frame) >= 0) { av_frame_unref(filt_frame); } // Pushes graph avfilter_graph_free(&filter_graph); end: avcodec_free_context(&codec_ctx); avformat_close_input(&fmt_ctx); av_frame_free(&filt_frame); av_frame_free(&frame); av_packet_free(&packet); #ifdef _WIN32 EnterCriticalSection(&json_mutex); while (!json_captured) { if (!SleepConditionVariableCS(&json_cond, &json_mutex, 5000)) { // 5 second timeout fprintf(stderr, "Timeout waiting for JSON data\n"); break; } } if (json_captured) { result = _strdup(json_buffer); // Use _strdup on Windows } LeaveCriticalSection(&json_mutex); #else struct timespec timeout; clock_gettime(CLOCK_REALTIME, &timeout); timeout.tv_sec += 5; // 5 second timeout pthread_mutex_lock(&json_mutex); while (json_captured == 0) { int ret = pthread_cond_timedwait(&json_cond, &json_mutex, &timeout); if (ret == ETIMEDOUT) { fprintf(stderr, "Timeout waiting for JSON data\n"); break; } } if (json_captured) { result = strdup(json_buffer); } pthread_mutex_unlock(&json_mutex); #endif av_log_set_callback(av_log_default_callback); return result; } PyAV-14.2.0/av/filter/loudnorm_impl.h000066400000000000000000000003601475734227400173570ustar00rootroot00000000000000#ifndef AV_FILTER_LOUDNORM_H #define AV_FILTER_LOUDNORM_H #include char* loudnorm_get_stats( AVFormatContext* fmt_ctx, int audio_stream_index, const char* loudnorm_args ); #endif // AV_FILTER_LOUDNORM_HPyAV-14.2.0/av/filter/pad.pxd000066400000000000000000000010061475734227400156050ustar00rootroot00000000000000cimport libav as lib from av.filter.context cimport FilterContext from av.filter.filter cimport Filter from av.filter.link cimport FilterLink cdef class FilterPad: cdef readonly Filter filter cdef readonly FilterContext context cdef readonly bint is_input cdef readonly int index cdef const lib.AVFilterPad *base_ptr cdef class FilterContextPad(FilterPad): cdef FilterLink _link cdef tuple alloc_filter_pads(Filter, const lib.AVFilterPad *ptr, bint is_input, FilterContext context=?) PyAV-14.2.0/av/filter/pad.pyi000066400000000000000000000003031475734227400156120ustar00rootroot00000000000000from .link import FilterLink class FilterPad: is_output: bool name: str type: str class FilterContextPad(FilterPad): link: FilterLink | None linked: FilterContextPad | None PyAV-14.2.0/av/filter/pad.pyx000066400000000000000000000053411475734227400156400ustar00rootroot00000000000000from av.filter.link cimport wrap_filter_link cdef object _cinit_sentinel = object() cdef class FilterPad: def __cinit__(self, sentinel): if sentinel is not _cinit_sentinel: raise RuntimeError("cannot construct FilterPad") def __repr__(self): _filter = self.filter.name _io = "inputs" if self.is_input else "outputs" return f"" @property def is_output(self): return not self.is_input @property def name(self): return lib.avfilter_pad_get_name(self.base_ptr, self.index) @property def type(self): """ The media type of this filter pad. Examples: `'audio'`, `'video'`, `'subtitle'`. :type: str """ return lib.av_get_media_type_string(lib.avfilter_pad_get_type(self.base_ptr, self.index)) cdef class FilterContextPad(FilterPad): def __repr__(self): _filter = self.filter.name _io = "inputs" if self.is_input else "outputs" context = self.context.name return f"" @property def link(self): if self._link: return self._link cdef lib.AVFilterLink **links = self.context.ptr.inputs if self.is_input else self.context.ptr.outputs cdef lib.AVFilterLink *link = links[self.index] if not link: return self._link = wrap_filter_link(self.context.graph, link) return self._link @property def linked(self): cdef FilterLink link = self.link if link: return link.input if self.is_input else link.output cdef tuple alloc_filter_pads(Filter filter, const lib.AVFilterPad *ptr, bint is_input, FilterContext context=None): if not ptr: return () pads = [] # We need to be careful and check our bounds if we know what they are, # since the arrays on a AVFilterContext are not NULL terminated. cdef int i = 0 cdef int count if context is None: # This is a custom function defined using a macro in avfilter.pxd. Its usage # can be changed after we stop supporting FFmpeg < 5.0. count = lib.pyav_get_num_pads(filter.ptr, not is_input, ptr) else: count = (context.ptr.nb_inputs if is_input else context.ptr.nb_outputs) cdef FilterPad pad while (i < count): pad = FilterPad(_cinit_sentinel) if context is None else FilterContextPad(_cinit_sentinel) pads.append(pad) pad.filter = filter pad.context = context pad.is_input = is_input pad.base_ptr = ptr pad.index = i i += 1 return tuple(pads) PyAV-14.2.0/av/format.pxd000066400000000000000000000003521475734227400150470ustar00rootroot00000000000000cimport libav as lib cdef class ContainerFormat: cdef readonly str name cdef lib.AVInputFormat *iptr cdef lib.AVOutputFormat *optr cdef ContainerFormat build_container_format(lib.AVInputFormat*, lib.AVOutputFormat*) PyAV-14.2.0/av/format.pyi000066400000000000000000000025731475734227400150640ustar00rootroot00000000000000__all__ = ("Flags", "ContainerFormat", "formats_available") from enum import Flag from typing import ClassVar, Literal, cast class Flags(Flag): no_file = cast(ClassVar[Flags], ...) need_number = cast(ClassVar[Flags], ...) show_ids = cast(ClassVar[Flags], ...) global_header = cast(ClassVar[Flags], ...) no_timestamps = cast(ClassVar[Flags], ...) generic_index = cast(ClassVar[Flags], ...) ts_discont = cast(ClassVar[Flags], ...) variable_fps = cast(ClassVar[Flags], ...) no_dimensions = cast(ClassVar[Flags], ...) no_streams = cast(ClassVar[Flags], ...) no_bin_search = cast(ClassVar[Flags], ...) no_gen_search = cast(ClassVar[Flags], ...) no_byte_seek = cast(ClassVar[Flags], ...) allow_flush = cast(ClassVar[Flags], ...) ts_nonstrict = cast(ClassVar[Flags], ...) ts_negative = cast(ClassVar[Flags], ...) seek_to_pts = cast(ClassVar[Flags], ...) class ContainerFormat: def __init__(self, name: str, mode: Literal["r", "w"] | None = None) -> None: ... @property def name(self) -> str: ... @property def long_name(self) -> str: ... @property def is_input(self) -> bool: ... @property def is_output(self) -> bool: ... @property def extensions(self) -> set[str]: ... @property def flags(self) -> int: ... @property def no_file(self) -> bool: ... formats_available: set[str] PyAV-14.2.0/av/format.pyx000066400000000000000000000131131475734227400150730ustar00rootroot00000000000000cimport libav as lib from av.descriptor cimport wrap_avclass from enum import Flag cdef object _cinit_bypass_sentinel = object() cdef ContainerFormat build_container_format(lib.AVInputFormat* iptr, lib.AVOutputFormat* optr): if not iptr and not optr: raise ValueError("needs input format or output format") cdef ContainerFormat format = ContainerFormat.__new__(ContainerFormat, _cinit_bypass_sentinel) format.iptr = iptr format.optr = optr format.name = optr.name if optr else iptr.name return format class Flags(Flag): no_file = lib.AVFMT_NOFILE need_number: "Needs '%d' in filename." = lib.AVFMT_NEEDNUMBER show_ids: "Show format stream IDs numbers." = lib.AVFMT_SHOW_IDS global_header: "Format wants global header." = lib.AVFMT_GLOBALHEADER no_timestamps: "Format does not need / have any timestamps." = lib.AVFMT_NOTIMESTAMPS generic_index: "Use generic index building code." = lib.AVFMT_GENERIC_INDEX ts_discont: "Format allows timestamp discontinuities" = lib.AVFMT_TS_DISCONT variable_fps: "Format allows variable fps." = lib.AVFMT_VARIABLE_FPS no_dimensions: "Format does not need width/height" = lib.AVFMT_NODIMENSIONS no_streams: "Format does not require any streams" = lib.AVFMT_NOSTREAMS no_bin_search: "Format does not allow to fall back on binary search via read_timestamp" = lib.AVFMT_NOBINSEARCH no_gen_search: "Format does not allow to fall back on generic search" = lib.AVFMT_NOGENSEARCH no_byte_seek: "Format does not allow seeking by bytes" = lib.AVFMT_NO_BYTE_SEEK allow_flush: "Format allows flushing. If not set, the muxer will not receive a NULL packet in the write_packet function." = lib.AVFMT_ALLOW_FLUSH ts_nonstrict: "Format does not require strictly increasing timestamps, but they must still be monotonic." = lib.AVFMT_TS_NONSTRICT ts_negative: "Format allows muxing negative timestamps." = lib.AVFMT_TS_NEGATIVE # If not set the timestamp will be shifted in `av_write_frame()` and `av_interleaved_write_frame()` # so they start from 0. The user or muxer can override this through AVFormatContext.avoid_negative_ts seek_to_pts: "Seeking is based on PTS" = lib.AVFMT_SEEK_TO_PTS cdef class ContainerFormat: """Descriptor of a container format. :param str name: The name of the format. :param str mode: ``'r'`` or ``'w'`` for input and output formats; defaults to None which will grab either. """ def __cinit__(self, name, mode=None): if name is _cinit_bypass_sentinel: return # We need to hold onto the original name because AVInputFormat.name is # actually comma-separated, and so we need to remember which one this was. self.name = name # Searches comma-separated names. if mode is None or mode == "r": self.iptr = lib.av_find_input_format(name) if mode is None or mode == "w": self.optr = lib.av_guess_format(name, NULL, NULL) if not self.iptr and not self.optr: raise ValueError(f"no container format {name!r}") def __repr__(self): return f"" @property def descriptor(self): if self.iptr: return wrap_avclass(self.iptr.priv_class) else: return wrap_avclass(self.optr.priv_class) @property def options(self): return self.descriptor.options @property def input(self): """An input-only view of this format.""" if self.iptr == NULL: return None elif self.optr == NULL: return self else: return build_container_format(self.iptr, NULL) @property def output(self): """An output-only view of this format.""" if self.optr == NULL: return None elif self.iptr == NULL: return self else: return build_container_format(NULL, self.optr) @property def is_input(self): return self.iptr != NULL @property def is_output(self): return self.optr != NULL @property def long_name(self): # We prefer the output names since the inputs may represent # multiple formats. return self.optr.long_name if self.optr else self.iptr.long_name @property def extensions(self): cdef set exts = set() if self.iptr and self.iptr.extensions: exts.update(self.iptr.extensions.split(",")) if self.optr and self.optr.extensions: exts.update(self.optr.extensions.split(",")) return exts @property def flags(self): """ Get the flags bitmask for the format. :rtype: int """ return ( (self.iptr.flags if self.iptr else 0) | (self.optr.flags if self.optr else 0) ) @property def no_file(self): return bool(self.flags & lib.AVFMT_NOFILE) cdef get_output_format_names(): names = set() cdef const lib.AVOutputFormat *ptr cdef void *opaque = NULL while True: ptr = lib.av_muxer_iterate(&opaque) if ptr: names.add(ptr.name) else: break return names cdef get_input_format_names(): names = set() cdef const lib.AVInputFormat *ptr cdef void *opaque = NULL while True: ptr = lib.av_demuxer_iterate(&opaque) if ptr: names.add(ptr.name) else: break return names formats_available = get_output_format_names() formats_available.update(get_input_format_names()) format_descriptor = wrap_avclass(lib.avformat_get_class()) PyAV-14.2.0/av/frame.pxd000066400000000000000000000006331475734227400146530ustar00rootroot00000000000000cimport libav as lib from av.packet cimport Packet from av.sidedata.sidedata cimport _SideDataContainer cdef class Frame: cdef lib.AVFrame *ptr # We define our own time. cdef lib.AVRational _time_base cdef _rebase_time(self, lib.AVRational) cdef _SideDataContainer _side_data cdef _copy_internal_attributes(self, Frame source, bint data_layout=?) cdef _init_user_attributes(self) PyAV-14.2.0/av/frame.pyi000066400000000000000000000007731475734227400146660ustar00rootroot00000000000000from fractions import Fraction from typing import TypedDict from av.sidedata.motionvectors import MotionVectors class SideData(TypedDict, total=False): MOTION_VECTORS: MotionVectors class Frame: dts: int | None pts: int | None time_base: Fraction side_data: SideData opaque: object @property def time(self) -> float | None: ... @property def is_corrupt(self) -> bool: ... @property def key_frame(self) -> bool: ... def make_writable(self) -> None: ... PyAV-14.2.0/av/frame.pyx000066400000000000000000000115131475734227400146770ustar00rootroot00000000000000from av.error cimport err_check from av.opaque cimport opaque_container from av.utils cimport avrational_to_fraction, to_avrational from av.sidedata.sidedata import SideDataContainer cdef class Frame: """ Base class for audio and video frames. See also :class:`~av.audio.frame.AudioFrame` and :class:`~av.video.frame.VideoFrame`. """ def __cinit__(self, *args, **kwargs): with nogil: self.ptr = lib.av_frame_alloc() def __dealloc__(self): with nogil: # This calls av_frame_unref, and then frees the pointer. # Thats it. lib.av_frame_free(&self.ptr) def __repr__(self): return f"av.{self.__class__.__name__} pts={self.pts} at 0x{id(self):x}>" cdef _copy_internal_attributes(self, Frame source, bint data_layout=True): """Mimic another frame.""" self._time_base = source._time_base lib.av_frame_copy_props(self.ptr, source.ptr) if data_layout: # TODO: Assert we don't have any data yet. self.ptr.format = source.ptr.format self.ptr.width = source.ptr.width self.ptr.height = source.ptr.height self.ptr.ch_layout = source.ptr.ch_layout cdef _init_user_attributes(self): pass # Dummy to match the API of the others. cdef _rebase_time(self, lib.AVRational dst): if not dst.num: raise ValueError("Cannot rebase to zero time.") if not self._time_base.num: self._time_base = dst return if self._time_base.num == dst.num and self._time_base.den == dst.den: return if self.ptr.pts != lib.AV_NOPTS_VALUE: self.ptr.pts = lib.av_rescale_q(self.ptr.pts, self._time_base, dst) self._time_base = dst @property def dts(self): """ The decoding timestamp copied from the :class:`~av.packet.Packet` that triggered returning this frame in :attr:`time_base` units. (if frame threading isn't used) This is also the Presentation time of this frame calculated from only :attr:`.Packet.dts` values without pts values. :type: int """ if self.ptr.pkt_dts == lib.AV_NOPTS_VALUE: return None return self.ptr.pkt_dts @dts.setter def dts(self, value): if value is None: self.ptr.pkt_dts = lib.AV_NOPTS_VALUE else: self.ptr.pkt_dts = value @property def pts(self): """ The presentation timestamp in :attr:`time_base` units for this frame. This is the time at which the frame should be shown to the user. :type: int """ if self.ptr.pts == lib.AV_NOPTS_VALUE: return None return self.ptr.pts @pts.setter def pts(self, value): if value is None: self.ptr.pts = lib.AV_NOPTS_VALUE else: self.ptr.pts = value @property def time(self): """ The presentation time in seconds for this frame. This is the time at which the frame should be shown to the user. :type: float """ if self.ptr.pts == lib.AV_NOPTS_VALUE: return None else: return float(self.ptr.pts) * self._time_base.num / self._time_base.den @property def time_base(self): """ The unit of time (in fractional seconds) in which timestamps are expressed. :type: fractions.Fraction """ if self._time_base.num: return avrational_to_fraction(&self._time_base) @time_base.setter def time_base(self, value): to_avrational(value, &self._time_base) @property def is_corrupt(self): """ Is this frame corrupt? :type: bool """ return self.ptr.decode_error_flags != 0 or bool(self.ptr.flags & lib.AV_FRAME_FLAG_CORRUPT) @property def key_frame(self): """Is this frame a key frame? Wraps :ffmpeg:`AVFrame.key_frame`. """ return bool(self.ptr.flags & lib.AV_FRAME_FLAG_KEY) @property def side_data(self): if self._side_data is None: self._side_data = SideDataContainer(self) return self._side_data def make_writable(self): """ Ensures that the frame data is writable. Copy the data to new buffer if it is not. This is a wrapper around :ffmpeg:`av_frame_make_writable`. """ cdef int ret ret = lib.av_frame_make_writable(self.ptr) err_check(ret) @property def opaque(self): if self.ptr.opaque_ref is not NULL: return opaque_container.get( self.ptr.opaque_ref.data) @opaque.setter def opaque(self, v): lib.av_buffer_unref(&self.ptr.opaque_ref) if v is None: return self.ptr.opaque_ref = opaque_container.add(v) PyAV-14.2.0/av/logging.pxd000066400000000000000000000000301475734227400151760ustar00rootroot00000000000000 cpdef get_last_error() PyAV-14.2.0/av/logging.pyi000066400000000000000000000015651475734227400152220ustar00rootroot00000000000000from typing import Any, Callable PANIC: int FATAL: int ERROR: int WARNING: int INFO: int VERBOSE: int DEBUG: int TRACE: int CRITICAL: int def adapt_level(level: int) -> int: ... def get_level() -> int | None: ... def set_level(level: int | None) -> None: ... def set_libav_level(level: int) -> None: ... def restore_default_callback() -> None: ... def get_skip_repeated() -> bool: ... def set_skip_repeated(v: bool) -> None: ... def get_last_error() -> tuple[int, tuple[int, str, str] | None]: ... def log(level: int, name: str, message: str) -> None: ... class Capture: logs: list[tuple[int, str, str]] def __init__(self, local: bool = True) -> None: ... def __enter__(self) -> list[tuple[int, str, str]]: ... def __exit__( self, type_: type | None, value: Exception | None, traceback: Callable[..., Any] | None, ) -> None: ... PyAV-14.2.0/av/logging.pyx000066400000000000000000000215751475734227400152440ustar00rootroot00000000000000""" FFmpeg has a logging system that it uses extensively. It's very noisy so PyAV turns it off by default. This, unfortunately has the effect of making raised errors having less detailed messages. It's therefore recommended to use VERBOSE when developing. .. _enable_logging: Enabling Logging ~~~~~~~~~~~~~~~~~ You can hook the logging system with Python by setting the log level:: import av av.logging.set_level(av.logging.VERBOSE) PyAV hooks into that system to translate FFmpeg logs into Python's `logging system `_. If you are not already using Python's logging system, you can initialize it quickly with:: import logging logging.basicConfig() Note that handling logs with Python sometimes doesn't play nice multi-threads workflows. An alternative is :func:`restore_default_callback`. This will restores FFmpeg's logging default system, which prints to the terminal. Like with setting the log level to ``None``, this may also result in raised errors having less detailed messages. API Reference ~~~~~~~~~~~~~ """ cimport libav as lib from libc.stdio cimport fprintf, stderr from libc.stdlib cimport free, malloc import logging import sys from threading import Lock, get_ident # Library levels. PANIC = lib.AV_LOG_PANIC # 0 FATAL = lib.AV_LOG_FATAL # 8 ERROR = lib.AV_LOG_ERROR WARNING = lib.AV_LOG_WARNING INFO = lib.AV_LOG_INFO VERBOSE = lib.AV_LOG_VERBOSE DEBUG = lib.AV_LOG_DEBUG TRACE = lib.AV_LOG_TRACE # Mimicking stdlib. CRITICAL = FATAL cpdef adapt_level(int level): """Convert a library log level to a Python log level.""" if level <= lib.AV_LOG_FATAL: # Includes PANIC return 50 # logging.CRITICAL elif level <= lib.AV_LOG_ERROR: return 40 # logging.ERROR elif level <= lib.AV_LOG_WARNING: return 30 # logging.WARNING elif level <= lib.AV_LOG_INFO: return 20 # logging.INFO elif level <= lib.AV_LOG_VERBOSE: return 10 # logging.DEBUG elif level <= lib.AV_LOG_DEBUG: return 5 # Lower than any logging constant. else: # lib.AV_LOG_TRACE return 1 cdef object level_threshold = None # ... but lets limit ourselves to WARNING (assuming nobody already did this). if "libav" not in logging.Logger.manager.loggerDict: logging.getLogger("libav").setLevel(logging.WARNING) def get_level(): """Returns the current log level. See :func:`set_level`.""" return level_threshold def set_level(level): """set_level(level) Sets PyAV's log level. It can be set to constants available in this module: ``PANIC``, ``FATAL``, ``ERROR``, ``WARNING``, ``INFO``, ``VERBOSE``, ``DEBUG``, or ``None`` (the default). PyAV defaults to totally ignoring all ffmpeg logs. This has the side effect of making certain Exceptions have no messages. It's therefore recommended to use: av.logging.set_level(av.logging.VERBOSE) When developing your application. """ global level_threshold if level is None: level_threshold = level lib.av_log_set_callback(nolog_callback) elif type(level) is int: level_threshold = level lib.av_log_set_callback(log_callback) else: raise ValueError("level must be: int | None") def set_libav_level(level): """Set libav's log level. It can be set to constants available in this module: ``PANIC``, ``FATAL``, ``ERROR``, ``WARNING``, ``INFO``, ``VERBOSE``, ``DEBUG``. When PyAV logging is disabled, setting this will change the level of the logs printed to the terminal. """ lib.av_log_set_level(level) def restore_default_callback(): """Revert back to FFmpeg's log callback, which prints to the terminal.""" lib.av_log_set_callback(lib.av_log_default_callback) cdef bint skip_repeated = True cdef skip_lock = Lock() cdef object last_log = None cdef int skip_count = 0 def get_skip_repeated(): """Will identical logs be emitted?""" return skip_repeated def set_skip_repeated(v): """Set if identical logs will be emitted""" global skip_repeated skip_repeated = bool(v) # For error reporting. cdef object last_error = None cdef int error_count = 0 cpdef get_last_error(): """Get the last log that was at least ``ERROR``.""" if error_count: with skip_lock: return error_count, last_error else: return 0, None cdef global_captures = [] cdef thread_captures = {} cdef class Capture: """A context manager for capturing logs. :param bool local: Should logs from all threads be captured, or just one this object is constructed in? e.g.:: with Capture() as logs: # Do something. for log in logs: print(log.message) """ cdef readonly list logs cdef list captures def __init__(self, bint local=True): self.logs = [] if local: self.captures = thread_captures.setdefault(get_ident(), []) else: self.captures = global_captures def __enter__(self): self.captures.append(self.logs) return self.logs def __exit__(self, type_, value, traceback): self.captures.pop(-1) cdef struct log_context: lib.AVClass *class_ const char *name cdef const char *log_context_name(void *ptr) noexcept nogil: cdef log_context *obj = ptr return obj.name cdef lib.AVClass log_class log_class.item_name = log_context_name cpdef log(int level, str name, str message): """Send a log through the library logging system. This is mostly for testing. """ cdef log_context *obj = malloc(sizeof(log_context)) obj.class_ = &log_class obj.name = name cdef bytes message_bytes = message.encode("utf-8") lib.av_log(obj, level, "%s", message_bytes) free(obj) cdef log_callback_gil(int level, const char *c_name, const char *c_message): global error_count global skip_count global last_log global last_error name = c_name if c_name is not NULL else "" message = (c_message).decode("utf8", "backslashreplace") log = (level, name, message) # We have to filter it ourselves, but we will still process it in general so # it is available to our error handling. # Note that FFmpeg's levels are backwards from Python's. cdef bint is_interesting = level <= level_threshold # Skip messages which are identical to the previous. # TODO: Be smarter about threads. cdef bint is_repeated = False cdef object repeat_log = None with skip_lock: if is_interesting: is_repeated = skip_repeated and last_log == log if is_repeated: skip_count += 1 elif skip_count: # Now that we have hit the end of the repeat cycle, tally up how many. if skip_count == 1: repeat_log = last_log else: repeat_log = ( last_log[0], last_log[1], "%s (repeated %d more times)" % (last_log[2], skip_count) ) skip_count = 0 last_log = log # Hold onto errors for err_check. if level == lib.AV_LOG_ERROR: error_count += 1 last_error = log if repeat_log is not None: log_callback_emit(repeat_log) if is_interesting and not is_repeated: log_callback_emit(log) cdef log_callback_emit(log): lib_level, name, message = log captures = thread_captures.get(get_ident()) or global_captures if captures: captures[-1].append(log) return py_level = adapt_level(lib_level) logger_name = "libav." + name if name else "libav.generic" logger = logging.getLogger(logger_name) logger.log(py_level, message.strip()) cdef void log_callback(void *ptr, int level, const char *format, lib.va_list args) noexcept nogil: cdef bint inited = lib.Py_IsInitialized() if not inited: return with gil: if level > level_threshold and level != lib.AV_LOG_ERROR: return # Format the message. cdef char message[1024] lib.vsnprintf(message, 1023, format, args) # Get the name. cdef const char *name = NULL cdef lib.AVClass *cls = (ptr)[0] if ptr else NULL if cls and cls.item_name: name = cls.item_name(ptr) with gil: try: log_callback_gil(level, name, message) except Exception as e: fprintf(stderr, "av.logging: exception while handling %s[%d]: %s\n", name, level, message) # For some reason lib.PyErr_PrintEx(0) won't work. exc, type_, tb = sys.exc_info() lib.PyErr_Display(exc, type_, tb) cdef void nolog_callback(void *ptr, int level, const char *format, lib.va_list args) noexcept nogil: pass lib.av_log_set_callback(nolog_callback) PyAV-14.2.0/av/opaque.pxd000066400000000000000000000003551475734227400150540ustar00rootroot00000000000000cimport libav as lib cdef class OpaqueContainer: cdef dict _by_name cdef lib.AVBufferRef *add(self, object v) cdef object get(self, bytes name) cdef object pop(self, bytes name) cdef OpaqueContainer opaque_container PyAV-14.2.0/av/opaque.pyx000066400000000000000000000015011475734227400150730ustar00rootroot00000000000000cimport libav as lib from libc.stdint cimport uint8_t from uuid import uuid4 cdef void key_free(void *opaque, uint8_t *data) noexcept nogil: cdef char *name = data with gil: opaque_container.pop(name) cdef class OpaqueContainer: """A container that holds references to Python objects, indexed by uuid""" def __cinit__(self): self._by_name = {} cdef lib.AVBufferRef *add(self, v): cdef bytes uuid = str(uuid4()).encode("utf-8") cdef lib.AVBufferRef *ref = lib.av_buffer_create(uuid, len(uuid), &key_free, NULL, 0) self._by_name[uuid] = v return ref cdef object get(self, bytes name): return self._by_name.get(name) cdef object pop(self, bytes name): return self._by_name.pop(name) cdef opaque_container = OpaqueContainer() PyAV-14.2.0/av/option.pxd000066400000000000000000000005561475734227400150750ustar00rootroot00000000000000cimport libav as lib cdef class BaseOption: cdef const lib.AVOption *ptr cdef class Option(BaseOption): cdef readonly tuple choices cdef class OptionChoice(BaseOption): cdef readonly bint is_default cdef Option wrap_option(tuple choices, const lib.AVOption *ptr) cdef OptionChoice wrap_option_choice(const lib.AVOption *ptr, bint is_default) PyAV-14.2.0/av/option.pyi000066400000000000000000000024411475734227400150760ustar00rootroot00000000000000from enum import Enum, Flag from typing import cast class OptionType(Enum): FLAGS = cast(int, ...) INT = cast(int, ...) INT64 = cast(int, ...) DOUBLE = cast(int, ...) FLOAT = cast(int, ...) STRING = cast(int, ...) RATIONAL = cast(int, ...) BINARY = cast(int, ...) DICT = cast(int, ...) CONST = cast(int, ...) IMAGE_SIZE = cast(int, ...) PIXEL_FMT = cast(int, ...) SAMPLE_FMT = cast(int, ...) VIDEO_RATE = cast(int, ...) DURATION = cast(int, ...) COLOR = cast(int, ...) CHANNEL_LAYOUT = cast(int, ...) BOOL = cast(int, ...) class OptionFlags(Flag): ENCODING_PARAM = cast(int, ...) DECODING_PARAM = cast(int, ...) AUDIO_PARAM = cast(int, ...) VIDEO_PARAM = cast(int, ...) SUBTITLE_PARAM = cast(int, ...) EXPORT = cast(int, ...) READONLY = cast(int, ...) FILTERING_PARAM = cast(int, ...) class BaseOption: name: str help: str flags: int is_encoding_param: bool is_decoding_param: bool is_audio_param: bool is_video_param: bool is_subtitle_param: bool is_export: bool is_readonly: bool is_filtering_param: bool class Option(BaseOption): type: OptionType offset: int default: int min: int max: int class OptionChoice(BaseOption): value: int PyAV-14.2.0/av/option.pyx000066400000000000000000000120221475734227400151110ustar00rootroot00000000000000cimport libav as lib from av.utils cimport flag_in_bitfield from enum import Enum, Flag cdef object _cinit_sentinel = object() cdef Option wrap_option(tuple choices, const lib.AVOption *ptr): if ptr == NULL: return None cdef Option obj = Option(_cinit_sentinel) obj.ptr = ptr obj.choices = choices return obj class OptionType(Enum): FLAGS = lib.AV_OPT_TYPE_FLAGS INT = lib.AV_OPT_TYPE_INT INT64 = lib.AV_OPT_TYPE_INT64 DOUBLE = lib.AV_OPT_TYPE_DOUBLE FLOAT = lib.AV_OPT_TYPE_FLOAT STRING = lib.AV_OPT_TYPE_STRING RATIONAL = lib.AV_OPT_TYPE_RATIONAL BINARY = lib.AV_OPT_TYPE_BINARY DICT = lib.AV_OPT_TYPE_DICT UINT64 = lib.AV_OPT_TYPE_UINT64 CONST = lib.AV_OPT_TYPE_CONST IMAGE_SIZE = lib.AV_OPT_TYPE_IMAGE_SIZE PIXEL_FMT = lib.AV_OPT_TYPE_PIXEL_FMT SAMPLE_FMT = lib.AV_OPT_TYPE_SAMPLE_FMT VIDEO_RATE = lib.AV_OPT_TYPE_VIDEO_RATE DURATION = lib.AV_OPT_TYPE_DURATION COLOR = lib.AV_OPT_TYPE_COLOR CHANNEL_LAYOUT = lib.AV_OPT_TYPE_CHLAYOUT BOOL = lib.AV_OPT_TYPE_BOOL cdef tuple _INT_TYPES = ( lib.AV_OPT_TYPE_FLAGS, lib.AV_OPT_TYPE_INT, lib.AV_OPT_TYPE_INT64, lib.AV_OPT_TYPE_PIXEL_FMT, lib.AV_OPT_TYPE_SAMPLE_FMT, lib.AV_OPT_TYPE_DURATION, lib.AV_OPT_TYPE_CHLAYOUT, lib.AV_OPT_TYPE_BOOL, ) class OptionFlags(Flag): ENCODING_PARAM = lib.AV_OPT_FLAG_ENCODING_PARAM DECODING_PARAM = lib.AV_OPT_FLAG_DECODING_PARAM AUDIO_PARAM = lib.AV_OPT_FLAG_AUDIO_PARAM VIDEO_PARAM = lib.AV_OPT_FLAG_VIDEO_PARAM SUBTITLE_PARAM = lib.AV_OPT_FLAG_SUBTITLE_PARAM EXPORT = lib.AV_OPT_FLAG_EXPORT READONLY = lib.AV_OPT_FLAG_READONLY FILTERING_PARAM = lib.AV_OPT_FLAG_FILTERING_PARAM cdef class BaseOption: def __cinit__(self, sentinel): if sentinel is not _cinit_sentinel: raise RuntimeError(f"Cannot construct av.{self.__class__.__name__}") @property def name(self): return self.ptr.name @property def help(self): return self.ptr.help if self.ptr.help != NULL else "" @property def flags(self): return self.ptr.flags # Option flags @property def is_encoding_param(self): return flag_in_bitfield(self.ptr.flags, lib.AV_OPT_FLAG_ENCODING_PARAM) @property def is_decoding_param(self): return flag_in_bitfield(self.ptr.flags, lib.AV_OPT_FLAG_DECODING_PARAM) @property def is_audio_param(self): return flag_in_bitfield(self.ptr.flags, lib.AV_OPT_FLAG_AUDIO_PARAM) @property def is_video_param(self): return flag_in_bitfield(self.ptr.flags, lib.AV_OPT_FLAG_VIDEO_PARAM) @property def is_subtitle_param(self): return flag_in_bitfield(self.ptr.flags, lib.AV_OPT_FLAG_SUBTITLE_PARAM) @property def is_export(self): return flag_in_bitfield(self.ptr.flags, lib.AV_OPT_FLAG_EXPORT) @property def is_readonly(self): return flag_in_bitfield(self.ptr.flags, lib.AV_OPT_FLAG_READONLY) @property def is_filtering_param(self): return flag_in_bitfield(self.ptr.flags, lib.AV_OPT_FLAG_FILTERING_PARAM) cdef class Option(BaseOption): @property def type(self): return OptionType(self.ptr.type) @property def offset(self): """ This can be used to find aliases of an option. Options in a particular descriptor with the same offset are aliases. """ return self.ptr.offset @property def default(self): if self.ptr.type in _INT_TYPES: return self.ptr.default_val.i64 if self.ptr.type in (lib.AV_OPT_TYPE_DOUBLE, lib.AV_OPT_TYPE_FLOAT, lib.AV_OPT_TYPE_RATIONAL): return self.ptr.default_val.dbl if self.ptr.type in (lib.AV_OPT_TYPE_STRING, lib.AV_OPT_TYPE_BINARY, lib.AV_OPT_TYPE_IMAGE_SIZE, lib.AV_OPT_TYPE_VIDEO_RATE, lib.AV_OPT_TYPE_COLOR): return self.ptr.default_val.str if self.ptr.default_val.str != NULL else "" def _norm_range(self, value): if self.ptr.type in _INT_TYPES: return int(value) return value @property def min(self): return self._norm_range(self.ptr.min) @property def max(self): return self._norm_range(self.ptr.max) def __repr__(self): return ( f"" ) cdef OptionChoice wrap_option_choice(const lib.AVOption *ptr, bint is_default): if ptr == NULL: return None cdef OptionChoice obj = OptionChoice(_cinit_sentinel) obj.ptr = ptr obj.is_default = is_default return obj cdef class OptionChoice(BaseOption): """ Represents AV_OPT_TYPE_CONST options which are essentially choices of non-const option with same unit. """ @property def value(self): return self.ptr.default_val.i64 def __repr__(self): return f"" PyAV-14.2.0/av/packet.pxd000066400000000000000000000006771475734227400150400ustar00rootroot00000000000000cimport libav as lib from av.buffer cimport Buffer from av.bytesource cimport ByteSource from av.stream cimport Stream cdef class Packet(Buffer): cdef lib.AVPacket* ptr cdef Stream _stream # We track our own time. cdef lib.AVRational _time_base cdef _rebase_time(self, lib.AVRational) # Hold onto the original reference. cdef ByteSource source cdef size_t _buffer_size(self) cdef void* _buffer_ptr(self) PyAV-14.2.0/av/packet.pyi000066400000000000000000000010571475734227400150370ustar00rootroot00000000000000from fractions import Fraction from av.subtitles.subtitle import SubtitleSet from .buffer import Buffer from .stream import Stream class Packet(Buffer): stream: Stream stream_index: int time_base: Fraction pts: int | None dts: int pos: int | None size: int duration: int | None opaque: object is_keyframe: bool is_corrupt: bool is_discard: bool is_trusted: bool is_disposable: bool def __init__(self, input: int | bytes | None = None) -> None: ... def decode(self) -> list[SubtitleSet]: ... PyAV-14.2.0/av/packet.pyx000066400000000000000000000134221475734227400150550ustar00rootroot00000000000000cimport libav as lib from av.bytesource cimport bytesource from av.error cimport err_check from av.opaque cimport opaque_container from av.utils cimport avrational_to_fraction, to_avrational cdef class Packet(Buffer): """A packet of encoded data within a :class:`~av.format.Stream`. This may, or may not include a complete object within a stream. :meth:`decode` must be called to extract encoded data. """ def __cinit__(self, input=None): with nogil: self.ptr = lib.av_packet_alloc() def __init__(self, input=None): cdef size_t size = 0 cdef ByteSource source = None if input is None: return if isinstance(input, int): size = input else: source = bytesource(input) size = source.length if size: err_check(lib.av_new_packet(self.ptr, size)) if source is not None: self.update(source) # TODO: Hold onto the source, and copy its pointer # instead of its data. # self.source = source def __dealloc__(self): with nogil: lib.av_packet_free(&self.ptr) def __repr__(self): stream = self._stream.index if self._stream else 0 return ( f"" ) # Buffer protocol. cdef size_t _buffer_size(self): return self.ptr.size cdef void* _buffer_ptr(self): return self.ptr.data cdef _rebase_time(self, lib.AVRational dst): if not dst.num: raise ValueError("Cannot rebase to zero time.") if not self._time_base.num: self._time_base = dst return if self._time_base.num == dst.num and self._time_base.den == dst.den: return lib.av_packet_rescale_ts(self.ptr, self._time_base, dst) self._time_base = dst def decode(self): """ Send the packet's data to the decoder and return a list of :class:`.AudioFrame`, :class:`.VideoFrame` or :class:`.SubtitleSet`. """ return self._stream.decode(self) @property def stream_index(self): return self.ptr.stream_index @property def stream(self): """ The :class:`Stream` this packet was demuxed from. """ return self._stream @stream.setter def stream(self, Stream stream): self._stream = stream self.ptr.stream_index = stream.ptr.index @property def time_base(self): """ The unit of time (in fractional seconds) in which timestamps are expressed. :type: fractions.Fraction """ return avrational_to_fraction(&self._time_base) @time_base.setter def time_base(self, value): to_avrational(value, &self._time_base) @property def pts(self): """ The presentation timestamp in :attr:`time_base` units for this packet. This is the time at which the packet should be shown to the user. :type: int """ if self.ptr.pts != lib.AV_NOPTS_VALUE: return self.ptr.pts @pts.setter def pts(self, v): if v is None: self.ptr.pts = lib.AV_NOPTS_VALUE else: self.ptr.pts = v @property def dts(self): """ The decoding timestamp in :attr:`time_base` units for this packet. :type: int """ if self.ptr.dts != lib.AV_NOPTS_VALUE: return self.ptr.dts @dts.setter def dts(self, v): if v is None: self.ptr.dts = lib.AV_NOPTS_VALUE else: self.ptr.dts = v @property def pos(self): """ The byte position of this packet within the :class:`.Stream`. Returns `None` if it is not known. :type: int """ if self.ptr.pos != -1: return self.ptr.pos @property def size(self): """ The size in bytes of this packet's data. :type: int """ return self.ptr.size @property def duration(self): """ The duration in :attr:`time_base` units for this packet. Returns `None` if it is not known. :type: int """ if self.ptr.duration != lib.AV_NOPTS_VALUE: return self.ptr.duration @duration.setter def duration(self, v): if v is None: self.ptr.duration = lib.AV_NOPTS_VALUE else: self.ptr.duration = v @property def is_keyframe(self): return bool(self.ptr.flags & lib.AV_PKT_FLAG_KEY) @is_keyframe.setter def is_keyframe(self, v): if v: self.ptr.flags |= lib.AV_PKT_FLAG_KEY else: self.ptr.flags &= ~(lib.AV_PKT_FLAG_KEY) @property def is_corrupt(self): return bool(self.ptr.flags & lib.AV_PKT_FLAG_CORRUPT) @is_corrupt.setter def is_corrupt(self, v): if v: self.ptr.flags |= lib.AV_PKT_FLAG_CORRUPT else: self.ptr.flags &= ~(lib.AV_PKT_FLAG_CORRUPT) @property def is_discard(self): return bool(self.ptr.flags & lib.AV_PKT_FLAG_DISCARD) @property def is_trusted(self): return bool(self.ptr.flags & lib.AV_PKT_FLAG_TRUSTED) @property def is_disposable(self): return bool(self.ptr.flags & lib.AV_PKT_FLAG_DISPOSABLE) @property def opaque(self): if self.ptr.opaque_ref is not NULL: return opaque_container.get( self.ptr.opaque_ref.data) @opaque.setter def opaque(self, v): lib.av_buffer_unref(&self.ptr.opaque_ref) if v is None: return self.ptr.opaque_ref = opaque_container.add(v) PyAV-14.2.0/av/plane.pxd000066400000000000000000000003041475734227400146530ustar00rootroot00000000000000from av.buffer cimport Buffer from av.frame cimport Frame cdef class Plane(Buffer): cdef Frame frame cdef int index cdef size_t _buffer_size(self) cdef void* _buffer_ptr(self) PyAV-14.2.0/av/plane.pyi000066400000000000000000000002511475734227400146620ustar00rootroot00000000000000from .buffer import Buffer from .frame import Frame class Plane(Buffer): frame: Frame index: int def __init__(self, frame: Frame, index: int) -> None: ... PyAV-14.2.0/av/plane.pyx000066400000000000000000000010641475734227400147040ustar00rootroot00000000000000 cdef class Plane(Buffer): """ Base class for audio and video planes. See also :class:`~av.audio.plane.AudioPlane` and :class:`~av.video.plane.VideoPlane`. """ def __cinit__(self, Frame frame, int index): self.frame = frame self.index = index def __repr__(self): return ( f"" ) cdef void* _buffer_ptr(self): return self.frame.ptr.extended_data[self.index] PyAV-14.2.0/av/py.typed000066400000000000000000000000001475734227400145270ustar00rootroot00000000000000PyAV-14.2.0/av/sidedata/000077500000000000000000000000001475734227400146205ustar00rootroot00000000000000PyAV-14.2.0/av/sidedata/__init__.pxd000066400000000000000000000000001475734227400170620ustar00rootroot00000000000000PyAV-14.2.0/av/sidedata/__init__.py000066400000000000000000000000001475734227400167170ustar00rootroot00000000000000PyAV-14.2.0/av/sidedata/motionvectors.pxd000066400000000000000000000004121475734227400202450ustar00rootroot00000000000000cimport libav as lib from av.frame cimport Frame from av.sidedata.sidedata cimport SideData cdef class _MotionVectors(SideData): cdef dict _vectors cdef int _len cdef class MotionVector: cdef _MotionVectors parent cdef lib.AVMotionVector *ptr PyAV-14.2.0/av/sidedata/motionvectors.pyi000066400000000000000000000011251475734227400202550ustar00rootroot00000000000000from typing import Any, Sequence, overload import numpy as np from .sidedata import SideData class MotionVectors(SideData, Sequence[MotionVector]): @overload def __getitem__(self, index: int): ... @overload def __getitem__(self, index: slice): ... @overload def __getitem__(self, index: int | slice): ... def __len__(self) -> int: ... def to_ndarray(self) -> np.ndarray[Any, Any]: ... class MotionVector: source: int w: int h: int src_x: int src_y: int dst_x: int dst_y: int motion_x: int motion_y: int motion_scale: int PyAV-14.2.0/av/sidedata/motionvectors.pyx000066400000000000000000000051121475734227400202740ustar00rootroot00000000000000from collections.abc import Sequence cdef object _cinit_bypass_sentinel = object() # Cython doesn't let us inherit from the abstract Sequence, so we will subclass # it later. cdef class _MotionVectors(SideData): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._vectors = {} self._len = self.ptr.size // sizeof(lib.AVMotionVector) def __repr__(self): return f"self.ptr.data:0x}" def __getitem__(self, int index): try: return self._vectors[index] except KeyError: pass if index >= self._len: raise IndexError(index) vector = self._vectors[index] = MotionVector(_cinit_bypass_sentinel, self, index) return vector def __len__(self): return self._len def to_ndarray(self): import numpy as np return np.frombuffer(self, dtype=np.dtype([ ("source", "int32"), ("w", "uint8"), ("h", "uint8"), ("src_x", "int16"), ("src_y", "int16"), ("dst_x", "int16"), ("dst_y", "int16"), ("flags", "uint64"), ("motion_x", "int32"), ("motion_y", "int32"), ("motion_scale", "uint16"), ], align=True)) class MotionVectors(_MotionVectors, Sequence): pass cdef class MotionVector: def __init__(self, sentinel, _MotionVectors parent, int index): if sentinel is not _cinit_bypass_sentinel: raise RuntimeError("cannot manually instatiate MotionVector") self.parent = parent cdef lib.AVMotionVector *base = parent.ptr.data self.ptr = base + index def __repr__(self): return f"" @property def source(self): return self.ptr.source @property def w(self): return self.ptr.w @property def h(self): return self.ptr.h @property def src_x(self): return self.ptr.src_x @property def src_y(self): return self.ptr.src_y @property def dst_x(self): return self.ptr.dst_x @property def dst_y(self): return self.ptr.dst_y @property def motion_x(self): return self.ptr.motion_x @property def motion_y(self): return self.ptr.motion_y @property def motion_scale(self): return self.ptr.motion_scale PyAV-14.2.0/av/sidedata/sidedata.pxd000066400000000000000000000007051475734227400171150ustar00rootroot00000000000000 cimport libav as lib from av.buffer cimport Buffer from av.dictionary cimport _Dictionary, wrap_dictionary from av.frame cimport Frame cdef class SideData(Buffer): cdef Frame frame cdef lib.AVFrameSideData *ptr cdef _Dictionary metadata cdef SideData wrap_side_data(Frame frame, int index) cdef int get_display_rotation(Frame frame) cdef class _SideDataContainer: cdef Frame frame cdef list _by_index cdef dict _by_type PyAV-14.2.0/av/sidedata/sidedata.pyi000066400000000000000000000036611475734227400171270ustar00rootroot00000000000000from collections.abc import Mapping from enum import Enum from typing import ClassVar, Iterator, Sequence, cast, overload from av.buffer import Buffer from av.frame import Frame class Type(Enum): PANSCAN = cast(ClassVar[Type], ...) A53_CC = cast(ClassVar[Type], ...) STEREO3D = cast(ClassVar[Type], ...) MATRIXENCODING = cast(ClassVar[Type], ...) DOWNMIX_INFO = cast(ClassVar[Type], ...) REPLAYGAIN = cast(ClassVar[Type], ...) DISPLAYMATRIX = cast(ClassVar[Type], ...) AFD = cast(ClassVar[Type], ...) MOTION_VECTORS = cast(ClassVar[Type], ...) SKIP_SAMPLES = cast(ClassVar[Type], ...) AUDIO_SERVICE_TYPE = cast(ClassVar[Type], ...) MASTERING_DISPLAY_METADATA = cast(ClassVar[Type], ...) GOP_TIMECODE = cast(ClassVar[Type], ...) SPHERICAL = cast(ClassVar[Type], ...) CONTENT_LIGHT_LEVEL = cast(ClassVar[Type], ...) ICC_PROFILE = cast(ClassVar[Type], ...) S12M_TIMECODE = cast(ClassVar[Type], ...) DYNAMIC_HDR_PLUS = cast(ClassVar[Type], ...) REGIONS_OF_INTEREST = cast(ClassVar[Type], ...) VIDEO_ENC_PARAMS = cast(ClassVar[Type], ...) SEI_UNREGISTERED = cast(ClassVar[Type], ...) FILM_GRAIN_PARAMS = cast(ClassVar[Type], ...) DETECTION_BBOXES = cast(ClassVar[Type], ...) DOVI_RPU_BUFFER = cast(ClassVar[Type], ...) DOVI_METADATA = cast(ClassVar[Type], ...) DYNAMIC_HDR_VIVID = cast(ClassVar[Type], ...) AMBIENT_VIEWING_ENVIRONMENT = cast(ClassVar[Type], ...) VIDEO_HINT = cast(ClassVar[Type], ...) class SideData(Buffer): type: Type class SideDataContainer(Mapping): frame: Frame def __len__(self) -> int: ... def __iter__(self) -> Iterator[SideData]: ... @overload def __getitem__(self, key: str | int | Type) -> SideData: ... @overload def __getitem__(self, key: slice) -> Sequence[SideData]: ... @overload def __getitem__( self, key: str | int | Type | slice ) -> SideData | Sequence[SideData]: ... PyAV-14.2.0/av/sidedata/sidedata.pyx000066400000000000000000000077321475734227400171510ustar00rootroot00000000000000from libc.stdint cimport int32_t from collections.abc import Mapping from enum import Enum from av.sidedata.motionvectors import MotionVectors cdef object _cinit_bypass_sentinel = object() class Type(Enum): """ Enum class representing different types of frame data in audio/video processing. Values are mapped to corresponding AV_FRAME_DATA constants from FFmpeg. From: https://github.com/FFmpeg/FFmpeg/blob/master/libavutil/frame.h """ PANSCAN = lib.AV_FRAME_DATA_PANSCAN A53_CC = lib.AV_FRAME_DATA_A53_CC STEREO3D = lib.AV_FRAME_DATA_STEREO3D MATRIXENCODING = lib.AV_FRAME_DATA_MATRIXENCODING DOWNMIX_INFO = lib.AV_FRAME_DATA_DOWNMIX_INFO REPLAYGAIN = lib.AV_FRAME_DATA_REPLAYGAIN DISPLAYMATRIX = lib.AV_FRAME_DATA_DISPLAYMATRIX AFD = lib.AV_FRAME_DATA_AFD MOTION_VECTORS = lib.AV_FRAME_DATA_MOTION_VECTORS SKIP_SAMPLES = lib.AV_FRAME_DATA_SKIP_SAMPLES AUDIO_SERVICE_TYPE = lib.AV_FRAME_DATA_AUDIO_SERVICE_TYPE MASTERING_DISPLAY_METADATA = lib.AV_FRAME_DATA_MASTERING_DISPLAY_METADATA GOP_TIMECODE = lib.AV_FRAME_DATA_GOP_TIMECODE SPHERICAL = lib.AV_FRAME_DATA_SPHERICAL CONTENT_LIGHT_LEVEL = lib.AV_FRAME_DATA_CONTENT_LIGHT_LEVEL ICC_PROFILE = lib.AV_FRAME_DATA_ICC_PROFILE S12M_TIMECODE = lib.AV_FRAME_DATA_S12M_TIMECODE DYNAMIC_HDR_PLUS = lib.AV_FRAME_DATA_DYNAMIC_HDR_PLUS REGIONS_OF_INTEREST = lib.AV_FRAME_DATA_REGIONS_OF_INTEREST VIDEO_ENC_PARAMS = lib.AV_FRAME_DATA_VIDEO_ENC_PARAMS SEI_UNREGISTERED = lib.AV_FRAME_DATA_SEI_UNREGISTERED FILM_GRAIN_PARAMS = lib.AV_FRAME_DATA_FILM_GRAIN_PARAMS DETECTION_BBOXES = lib.AV_FRAME_DATA_DETECTION_BBOXES DOVI_RPU_BUFFER = lib.AV_FRAME_DATA_DOVI_RPU_BUFFER DOVI_METADATA = lib.AV_FRAME_DATA_DOVI_METADATA DYNAMIC_HDR_VIVID = lib.AV_FRAME_DATA_DYNAMIC_HDR_VIVID AMBIENT_VIEWING_ENVIRONMENT = lib.AV_FRAME_DATA_AMBIENT_VIEWING_ENVIRONMENT VIDEO_HINT = lib.AV_FRAME_DATA_VIDEO_HINT cdef SideData wrap_side_data(Frame frame, int index): if frame.ptr.side_data[index].type == lib.AV_FRAME_DATA_MOTION_VECTORS: return MotionVectors(_cinit_bypass_sentinel, frame, index) else: return SideData(_cinit_bypass_sentinel, frame, index) cdef int get_display_rotation(Frame frame): for i in range(frame.ptr.nb_side_data): if frame.ptr.side_data[i].type == lib.AV_FRAME_DATA_DISPLAYMATRIX: return int(lib.av_display_rotation_get(frame.ptr.side_data[i].data)) return 0 cdef class SideData(Buffer): def __init__(self, sentinel, Frame frame, int index): if sentinel is not _cinit_bypass_sentinel: raise RuntimeError("cannot manually instatiate SideData") self.frame = frame self.ptr = frame.ptr.side_data[index] self.metadata = wrap_dictionary(self.ptr.metadata) cdef size_t _buffer_size(self): return self.ptr.size cdef void* _buffer_ptr(self): return self.ptr.data cdef bint _buffer_writable(self): return False def __repr__(self): return f"self.ptr.data:0x}>" @property def type(self): return Type(self.ptr.type) cdef class _SideDataContainer: def __init__(self, Frame frame): self.frame = frame self._by_index = [] self._by_type = {} cdef int i cdef SideData data for i in range(self.frame.ptr.nb_side_data): data = wrap_side_data(frame, i) self._by_index.append(data) self._by_type[data.type] = data def __len__(self): return len(self._by_index) def __iter__(self): return iter(self._by_index) def __getitem__(self, key): if isinstance(key, int): return self._by_index[key] if isinstance(key, str): return self._by_type[Type[key]] return self._by_type[key] class SideDataContainer(_SideDataContainer, Mapping): pass PyAV-14.2.0/av/stream.pxd000066400000000000000000000011731475734227400150540ustar00rootroot00000000000000cimport libav as lib from av.codec.context cimport CodecContext from av.container.core cimport Container from av.frame cimport Frame from av.packet cimport Packet cdef class Stream: cdef lib.AVStream *ptr # Stream attributes. cdef readonly Container container cdef readonly dict metadata # CodecContext attributes. cdef readonly CodecContext codec_context # Private API. cdef _init(self, Container, lib.AVStream*, CodecContext) cdef _finalize_for_output(self) cdef _set_time_base(self, value) cdef _set_id(self, value) cdef Stream wrap_stream(Container, lib.AVStream*, CodecContext) PyAV-14.2.0/av/stream.pyi000066400000000000000000000024551475734227400150660ustar00rootroot00000000000000from enum import Flag from fractions import Fraction from typing import Literal, cast from .codec import Codec, CodecContext from .container import Container class Disposition(Flag): default = cast(int, ...) dub = cast(int, ...) original = cast(int, ...) comment = cast(int, ...) lyrics = cast(int, ...) karaoke = cast(int, ...) forced = cast(int, ...) hearing_impaired = cast(int, ...) visual_impaired = cast(int, ...) clean_effects = cast(int, ...) attached_pic = cast(int, ...) timed_thumbnails = cast(int, ...) non_diegetic = cast(int, ...) captions = cast(int, ...) descriptions = cast(int, ...) metadata = cast(int, ...) dependent = cast(int, ...) still_image = cast(int, ...) multilayer = cast(int, ...) class Stream: name: str | None container: Container codec: Codec codec_context: CodecContext metadata: dict[str, str] id: int profiles: list[str] profile: str | None index: int time_base: Fraction | None average_rate: Fraction | None base_rate: Fraction | None guessed_rate: Fraction | None start_time: int | None duration: int | None disposition: Disposition frames: int language: str | None type: Literal["video", "audio", "data", "subtitle", "attachment"] PyAV-14.2.0/av/stream.pyx000066400000000000000000000163041475734227400151030ustar00rootroot00000000000000cimport libav as lib from enum import Flag from av.error cimport err_check from av.packet cimport Packet from av.utils cimport ( avdict_to_dict, avrational_to_fraction, dict_to_avdict, to_avrational, ) class Disposition(Flag): default = 1 << 0 dub = 1 << 1 original = 1 << 2 comment = 1 << 3 lyrics = 1 << 4 karaoke = 1 << 5 forced = 1 << 6 hearing_impaired = 1 << 7 visual_impaired = 1 << 8 clean_effects = 1 << 9 attached_pic = 1 << 10 timed_thumbnails = 1 << 11 non_diegetic = 1 << 12 captions = 1 << 16 descriptions = 1 << 17 metadata = 1 << 18 dependent = 1 << 19 still_image = 1 << 20 multilayer = 1 << 21 cdef object _cinit_bypass_sentinel = object() cdef Stream wrap_stream(Container container, lib.AVStream *c_stream, CodecContext codec_context): """Build an av.Stream for an existing AVStream. The AVStream MUST be fully constructed and ready for use before this is called. """ # This better be the right one... assert container.ptr.streams[c_stream.index] == c_stream cdef Stream py_stream if c_stream.codecpar.codec_type == lib.AVMEDIA_TYPE_VIDEO: from av.video.stream import VideoStream py_stream = VideoStream.__new__(VideoStream, _cinit_bypass_sentinel) elif c_stream.codecpar.codec_type == lib.AVMEDIA_TYPE_AUDIO: from av.audio.stream import AudioStream py_stream = AudioStream.__new__(AudioStream, _cinit_bypass_sentinel) elif c_stream.codecpar.codec_type == lib.AVMEDIA_TYPE_SUBTITLE: from av.subtitles.stream import SubtitleStream py_stream = SubtitleStream.__new__(SubtitleStream, _cinit_bypass_sentinel) elif c_stream.codecpar.codec_type == lib.AVMEDIA_TYPE_ATTACHMENT: from av.attachments.stream import AttachmentStream py_stream = AttachmentStream.__new__(AttachmentStream, _cinit_bypass_sentinel) elif c_stream.codecpar.codec_type == lib.AVMEDIA_TYPE_DATA: from av.data.stream import DataStream py_stream = DataStream.__new__(DataStream, _cinit_bypass_sentinel) else: py_stream = Stream.__new__(Stream, _cinit_bypass_sentinel) py_stream._init(container, c_stream, codec_context) return py_stream cdef class Stream: """ A single stream of audio, video or subtitles within a :class:`.Container`. :: >>> fh = av.open(video_path) >>> stream = fh.streams.video[0] >>> stream This encapsulates a :class:`.CodecContext`, located at :attr:`Stream.codec_context`. Attribute access is passed through to that context when attributes are missing on the stream itself. E.g. ``stream.options`` will be the options on the context. """ def __cinit__(self, name): if name is _cinit_bypass_sentinel: return raise RuntimeError("cannot manually instantiate Stream") cdef _init(self, Container container, lib.AVStream *stream, CodecContext codec_context): self.container = container self.ptr = stream self.codec_context = codec_context if self.codec_context: self.codec_context.stream_index = stream.index self.metadata = avdict_to_dict( stream.metadata, encoding=self.container.metadata_encoding, errors=self.container.metadata_errors, ) def __repr__(self): name = getattr(self, "name", None) return ( f"'}/" f"{name or ''} at 0x{id(self):x}>" ) def __setattr__(self, name, value): if name == "id": self._set_id(value) return if name == "disposition": self.ptr.disposition = value return # Convenience setter for codec context properties. if self.codec_context is not None: setattr(self.codec_context, name, value) if name == "time_base": self._set_time_base(value) cdef _finalize_for_output(self): dict_to_avdict( &self.ptr.metadata, self.metadata, encoding=self.container.metadata_encoding, errors=self.container.metadata_errors, ) if not self.ptr.time_base.num: self.ptr.time_base = self.codec_context.ptr.time_base # It prefers if we pass it parameters via this other object. # Lets just copy what we want. err_check(lib.avcodec_parameters_from_context(self.ptr.codecpar, self.codec_context.ptr)) @property def id(self): """ The format-specific ID of this stream. :type: int """ return self.ptr.id cdef _set_id(self, value): """ Setter used by __setattr__ for the id property. """ if value is None: self.ptr.id = 0 else: self.ptr.id = value @property def profiles(self): """ List the available profiles for this stream. :type: list[str] """ if self.codec_context: return self.codec_context.profiles else: return [] @property def profile(self): """ The profile of this stream. :type: str """ if self.codec_context: return self.codec_context.profile else: return None @property def index(self): """ The index of this stream in its :class:`.Container`. :type: int """ return self.ptr.index @property def time_base(self): """ The unit of time (in fractional seconds) in which timestamps are expressed. :type: fractions.Fraction | None """ return avrational_to_fraction(&self.ptr.time_base) cdef _set_time_base(self, value): """ Setter used by __setattr__ for the time_base property. """ to_avrational(value, &self.ptr.time_base) @property def start_time(self): """ The presentation timestamp in :attr:`time_base` units of the first frame in this stream. :type: int | None """ if self.ptr.start_time != lib.AV_NOPTS_VALUE: return self.ptr.start_time @property def duration(self): """ The duration of this stream in :attr:`time_base` units. :type: int | None """ if self.ptr.duration != lib.AV_NOPTS_VALUE: return self.ptr.duration @property def frames(self): """ The number of frames this stream contains. Returns ``0`` if it is not known. :type: int """ return self.ptr.nb_frames @property def language(self): """ The language of the stream. :type: str | None """ return self.metadata.get("language") @property def disposition(self): return Disposition(self.ptr.disposition) @property def type(self): """ The type of the stream. :type: Literal["audio", "video", "subtitle", "data", "attachment"] """ return lib.av_get_media_type_string(self.ptr.codecpar.codec_type) PyAV-14.2.0/av/subtitles/000077500000000000000000000000001475734227400150605ustar00rootroot00000000000000PyAV-14.2.0/av/subtitles/__init__.pxd000066400000000000000000000000001475734227400173220ustar00rootroot00000000000000PyAV-14.2.0/av/subtitles/__init__.py000066400000000000000000000000001475734227400171570ustar00rootroot00000000000000PyAV-14.2.0/av/subtitles/codeccontext.pxd000066400000000000000000000001451475734227400202570ustar00rootroot00000000000000from av.codec.context cimport CodecContext cdef class SubtitleCodecContext(CodecContext): pass PyAV-14.2.0/av/subtitles/codeccontext.pyi000066400000000000000000000002171475734227400202650ustar00rootroot00000000000000from typing import Literal from av.codec.context import CodecContext class SubtitleCodecContext(CodecContext): type: Literal["subtitle"] PyAV-14.2.0/av/subtitles/codeccontext.pyx000066400000000000000000000011711475734227400203040ustar00rootroot00000000000000cimport libav as lib from av.error cimport err_check from av.packet cimport Packet from av.subtitles.subtitle cimport SubtitleProxy, SubtitleSet cdef class SubtitleCodecContext(CodecContext): cdef _send_packet_and_recv(self, Packet packet): if packet is None: raise RuntimeError("packet cannot be None") cdef SubtitleProxy proxy = SubtitleProxy() cdef int got_frame = 0 err_check( lib.avcodec_decode_subtitle2(self.ptr, &proxy.struct, &got_frame, packet.ptr) ) if got_frame: return [SubtitleSet(proxy)] else: return [] PyAV-14.2.0/av/subtitles/stream.pxd000066400000000000000000000002111475734227400170620ustar00rootroot00000000000000from av.packet cimport Packet from av.stream cimport Stream cdef class SubtitleStream(Stream): cpdef decode(self, Packet packet=?) PyAV-14.2.0/av/subtitles/stream.pyi000066400000000000000000000003241475734227400170750ustar00rootroot00000000000000from av.packet import Packet from av.stream import Stream from av.subtitles.subtitle import SubtitleSet class SubtitleStream(Stream): def decode(self, packet: Packet | None = None) -> list[SubtitleSet]: ... PyAV-14.2.0/av/subtitles/stream.pyx000066400000000000000000000012221475734227400171120ustar00rootroot00000000000000from av.packet cimport Packet from av.stream cimport Stream cdef class SubtitleStream(Stream): """ A :class:`SubtitleStream` can contain many :class:`SubtitleSet` objects accessible via decoding. """ def __getattr__(self, name): return getattr(self.codec_context, name) cpdef decode(self, Packet packet=None): """ Decode a :class:`.Packet` and return a list of :class:`.SubtitleSet`. :rtype: list[SubtitleSet] .. seealso:: This is a passthrough to :meth:`.CodecContext.decode`. """ if not packet: packet = Packet() return self.codec_context.decode(packet) PyAV-14.2.0/av/subtitles/subtitle.pxd000066400000000000000000000011201475734227400174220ustar00rootroot00000000000000cimport libav as lib cdef class SubtitleProxy: cdef lib.AVSubtitle struct cdef class SubtitleSet: cdef SubtitleProxy proxy cdef readonly tuple rects cdef class Subtitle: cdef SubtitleProxy proxy cdef lib.AVSubtitleRect *ptr cdef readonly bytes type cdef class TextSubtitle(Subtitle): pass cdef class ASSSubtitle(Subtitle): pass cdef class BitmapSubtitle(Subtitle): cdef readonly planes cdef class BitmapSubtitlePlane: cdef readonly BitmapSubtitle subtitle cdef readonly int index cdef readonly long buffer_size cdef void *_buffer PyAV-14.2.0/av/subtitles/subtitle.pyi000066400000000000000000000014441475734227400174410ustar00rootroot00000000000000from typing import Iterator, Literal class SubtitleSet: format: int start_display_time: int end_display_time: int pts: int rects: tuple[Subtitle] def __len__(self) -> int: ... def __iter__(self) -> Iterator[Subtitle]: ... def __getitem__(self, i: int) -> Subtitle: ... class Subtitle: ... class BitmapSubtitle(Subtitle): type: Literal[b"bitmap"] x: int y: int width: int height: int nb_colors: int planes: tuple[BitmapSubtitlePlane, ...] class BitmapSubtitlePlane: subtitle: BitmapSubtitle index: int buffer_size: int class AssSubtitle(Subtitle): type: Literal[b"ass", b"text"] @property def ass(self) -> bytes: ... @property def dialogue(self) -> bytes: ... @property def text(self) -> bytes: ... PyAV-14.2.0/av/subtitles/subtitle.pyx000066400000000000000000000136661475734227400174710ustar00rootroot00000000000000from cpython cimport PyBuffer_FillInfo cdef extern from "Python.h": bytes PyBytes_FromString(char*) cdef class SubtitleProxy: def __dealloc__(self): lib.avsubtitle_free(&self.struct) cdef class SubtitleSet: """ A :class:`SubtitleSet` can contain many :class:`Subtitle` objects. """ def __cinit__(self, SubtitleProxy proxy): self.proxy = proxy cdef int i self.rects = tuple(build_subtitle(self, i) for i in range(self.proxy.struct.num_rects)) def __repr__(self): return f"<{self.__class__.__module__}.{self.__class__.__name__} at 0x{id(self):x}>" @property def format(self): return self.proxy.struct.format @property def start_display_time(self): return self.proxy.struct.start_display_time @property def end_display_time(self): return self.proxy.struct.end_display_time @property def pts(self): return self.proxy.struct.pts def __len__(self): return len(self.rects) def __iter__(self): return iter(self.rects) def __getitem__(self, i): return self.rects[i] cdef Subtitle build_subtitle(SubtitleSet subtitle, int index): """Build an av.Stream for an existing AVStream. The AVStream MUST be fully constructed and ready for use before this is called. """ if index < 0 or index >= subtitle.proxy.struct.num_rects: raise ValueError("subtitle rect index out of range") cdef lib.AVSubtitleRect *ptr = subtitle.proxy.struct.rects[index] if ptr.type == lib.SUBTITLE_BITMAP: return BitmapSubtitle(subtitle, index) elif ptr.type == lib.SUBTITLE_ASS or ptr.type == lib.SUBTITLE_TEXT: return AssSubtitle(subtitle, index) else: raise ValueError("unknown subtitle type %r" % ptr.type) cdef class Subtitle: """ An abstract base class for each concrete type of subtitle. Wraps :ffmpeg:`AVSubtitleRect` """ def __cinit__(self, SubtitleSet subtitle, int index): if index < 0 or index >= subtitle.proxy.struct.num_rects: raise ValueError("subtitle rect index out of range") self.proxy = subtitle.proxy self.ptr = self.proxy.struct.rects[index] if self.ptr.type == lib.SUBTITLE_NONE: self.type = b"none" elif self.ptr.type == lib.SUBTITLE_BITMAP: self.type = b"bitmap" elif self.ptr.type == lib.SUBTITLE_TEXT: self.type = b"text" elif self.ptr.type == lib.SUBTITLE_ASS: self.type = b"ass" else: raise ValueError(f"unknown subtitle type {self.ptr.type!r}") def __repr__(self): return f"<{self.__class__.__module__}.{self.__class__.__name__} at 0x{id(self):x}>" cdef class BitmapSubtitle(Subtitle): def __cinit__(self, SubtitleSet subtitle, int index): self.planes = tuple( BitmapSubtitlePlane(self, i) for i in range(4) if self.ptr.linesize[i] ) def __repr__(self): return ( f"<{self.__class__.__module__}.{self.__class__.__name__} " f"{self.width}x{self.height} at {self.x},{self.y}; at 0x{id(self):x}>" ) @property def x(self): return self.ptr.x @property def y(self): return self.ptr.y @property def width(self): return self.ptr.w @property def height(self): return self.ptr.h @property def nb_colors(self): return self.ptr.nb_colors def __len__(self): return len(self.planes) def __iter__(self): return iter(self.planes) def __getitem__(self, i): return self.planes[i] cdef class BitmapSubtitlePlane: def __cinit__(self, BitmapSubtitle subtitle, int index): if index >= 4: raise ValueError("BitmapSubtitles have only 4 planes") if not subtitle.ptr.linesize[index]: raise ValueError("plane does not exist") self.subtitle = subtitle self.index = index self.buffer_size = subtitle.ptr.w * subtitle.ptr.h self._buffer = subtitle.ptr.data[index] # New-style buffer support. def __getbuffer__(self, Py_buffer *view, int flags): PyBuffer_FillInfo(view, self, self._buffer, self.buffer_size, 0, flags) cdef class AssSubtitle(Subtitle): """ Represents an ASS/Text subtitle format, as opposed to a bitmap Subtitle format. """ def __repr__(self): return ( f"<{self.__class__.__module__}.{self.__class__.__name__} " f"{self.text!r} at 0x{id(self):x}>" ) @property def ass(self): """ Returns the subtitle in the ASS/SSA format. Used by the vast majority of subtitle formats. """ if self.ptr.ass is not NULL: return PyBytes_FromString(self.ptr.ass) return b"" @property def dialogue(self): """ Extract the dialogue from the ass format. Strip comments. """ comma_count = 0 i = 0 cdef bytes ass_text = self.ass cdef bytes result = b"" while comma_count < 8 and i < len(ass_text): if bytes([ass_text[i]]) == b",": comma_count += 1 i += 1 state = False while i < len(ass_text): char = bytes([ass_text[i]]) next_char = b"" if i + 1 >= len(ass_text) else bytes([ass_text[i + 1]]) if char == b"\\" and next_char == b"N": result += b"\n" i += 2 continue if not state: if char == b"{" and next_char != b"\\": state = True else: result += char elif char == b"}": state = False i += 1 return result @property def text(self): """ Rarely used attribute. You're probably looking for dialogue. """ if self.ptr.text is not NULL: return PyBytes_FromString(self.ptr.text) return b"" PyAV-14.2.0/av/utils.pxd000066400000000000000000000007041475734227400147200ustar00rootroot00000000000000cimport libav as lib from libc.stdint cimport uint64_t cdef dict avdict_to_dict(lib.AVDictionary *input, str encoding, str errors) cdef dict_to_avdict(lib.AVDictionary **dst, dict src, str encoding, str errors) cdef object avrational_to_fraction(const lib.AVRational *input) cdef void to_avrational(object frac, lib.AVRational *input) cdef check_ndarray(object array, object dtype, int ndim) cdef flag_in_bitfield(uint64_t bitfield, uint64_t flag) PyAV-14.2.0/av/utils.pyx000066400000000000000000000040771475734227400147540ustar00rootroot00000000000000from libc.stdint cimport uint64_t from fractions import Fraction cimport libav as lib from av.error cimport err_check # === DICTIONARIES === # ==================== cdef _decode(char *s, encoding, errors): return (s).decode(encoding, errors) cdef bytes _encode(s, encoding, errors): return s.encode(encoding, errors) cdef dict avdict_to_dict(lib.AVDictionary *input, str encoding, str errors): cdef lib.AVDictionaryEntry *element = NULL cdef dict output = {} while True: element = lib.av_dict_get(input, "", element, lib.AV_DICT_IGNORE_SUFFIX) if element == NULL: break output[_decode(element.key, encoding, errors)] = _decode(element.value, encoding, errors) return output cdef dict_to_avdict(lib.AVDictionary **dst, dict src, str encoding, str errors): lib.av_dict_free(dst) for key, value in src.items(): err_check( lib.av_dict_set( dst, _encode(key, encoding, errors), _encode(value, encoding, errors), 0 ) ) # === FRACTIONS === # ================= cdef object avrational_to_fraction(const lib.AVRational *input): if input.num and input.den: return Fraction(input.num, input.den) cdef void to_avrational(object frac, lib.AVRational *input): input.num = frac.numerator input.den = frac.denominator # === OTHER === # ============= cdef check_ndarray(object array, object dtype, int ndim): """ Check a numpy array has the expected data type and number of dimensions. """ if array.dtype != dtype: raise ValueError(f"Expected numpy array with dtype `{dtype}` but got `{array.dtype}`") if array.ndim != ndim: raise ValueError(f"Expected numpy array with ndim `{ndim}` but got `{array.ndim}`") cdef flag_in_bitfield(uint64_t bitfield, uint64_t flag): # Not every flag exists in every version of FFMpeg, so we define them to 0. if not flag: return None return bool(bitfield & flag) # === BACKWARDS COMPAT === from .error import err_check PyAV-14.2.0/av/video/000077500000000000000000000000001475734227400141505ustar00rootroot00000000000000PyAV-14.2.0/av/video/__init__.pxd000066400000000000000000000000001475734227400164120ustar00rootroot00000000000000PyAV-14.2.0/av/video/__init__.py000066400000000000000000000000761475734227400162640ustar00rootroot00000000000000from .frame import VideoFrame from .stream import VideoStream PyAV-14.2.0/av/video/__init__.pyi000066400000000000000000000001471475734227400164340ustar00rootroot00000000000000from .frame import VideoFrame from .stream import VideoStream __all__ = ("VideoFrame", "VideoStream") PyAV-14.2.0/av/video/codeccontext.pxd000066400000000000000000000020441475734227400173470ustar00rootroot00000000000000cimport libav as lib from av.codec.context cimport CodecContext from av.video.format cimport VideoFormat from av.video.frame cimport VideoFrame from av.video.reformatter cimport VideoReformatter # The get_format callback in AVCodecContext is called by the decoder to pick a format out of a list. # When we want accelerated decoding, we need to figure out ahead of time what the format should be, # and find a way to pass that into our callback so we can return it to the decoder. We use the 'opaque' # user data field in AVCodecContext for that. This is the struct we store a pointer to in that field. cdef struct AVCodecPrivateData: lib.AVPixelFormat hardware_pix_fmt bint allow_software_fallback cdef class VideoCodecContext(CodecContext): cdef AVCodecPrivateData _private_data cdef VideoFormat _format cdef _build_format(self) cdef int last_w cdef int last_h cdef readonly VideoReformatter reformatter # For encoding. cdef readonly int encoded_frame_count # For decoding. cdef VideoFrame next_frame PyAV-14.2.0/av/video/codeccontext.pyi000066400000000000000000000017101475734227400173540ustar00rootroot00000000000000from fractions import Fraction from typing import Iterator, Literal from av.codec.context import CodecContext from av.packet import Packet from .format import VideoFormat from .frame import VideoFrame class VideoCodecContext(CodecContext): format: VideoFormat | None width: int height: int bits_per_coded_sample: int pix_fmt: str | None framerate: Fraction rate: Fraction gop_size: int sample_aspect_ratio: Fraction | None display_aspect_ratio: Fraction | None has_b_frames: bool max_b_frames: int coded_width: int coded_height: int color_range: int color_primaries: int color_trc: int colorspace: int qmin: int qmax: int type: Literal["video"] def encode(self, frame: VideoFrame | None = None) -> list[Packet]: ... def encode_lazy(self, frame: VideoFrame | None = None) -> Iterator[Packet]: ... def decode(self, packet: Packet | None = None) -> list[VideoFrame]: ... PyAV-14.2.0/av/video/codeccontext.pyx000066400000000000000000000251031475734227400173750ustar00rootroot00000000000000cimport libav as lib from libc.stdint cimport int64_t from av.codec.context cimport CodecContext from av.codec.hwaccel cimport HWAccel, HWConfig from av.error cimport err_check from av.frame cimport Frame from av.packet cimport Packet from av.utils cimport avrational_to_fraction, to_avrational from av.video.format cimport VideoFormat, get_pix_fmt, get_video_format from av.video.frame cimport VideoFrame, alloc_video_frame from av.video.reformatter cimport VideoReformatter cdef lib.AVPixelFormat _get_hw_format(lib.AVCodecContext *ctx, const lib.AVPixelFormat *pix_fmts) noexcept: # In the case where we requested accelerated decoding, the decoder first calls this function # with a list that includes both the hardware format and software formats. # First we try to pick the hardware format if it's in the list. # However, if the decoder fails to initialize the hardware, it will call this function again, # with only software formats in pix_fmts. We return ctx->sw_pix_fmt regardless in this case, # because that should be in the candidate list. If not, we are out of ideas anyways. cdef AVCodecPrivateData* private_data = ctx.opaque i = 0 while pix_fmts[i] != -1: if pix_fmts[i] == private_data.hardware_pix_fmt: return pix_fmts[i] i += 1 return ctx.sw_pix_fmt if private_data.allow_software_fallback else lib.AV_PIX_FMT_NONE cdef class VideoCodecContext(CodecContext): def __cinit__(self, *args, **kwargs): self.last_w = 0 self.last_h = 0 cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec, HWAccel hwaccel): CodecContext._init(self, ptr, codec, hwaccel) # TODO: Can this be `super`? if hwaccel is not None: try: self.hwaccel_ctx = hwaccel.create(self.codec) self.ptr.hw_device_ctx = lib.av_buffer_ref(self.hwaccel_ctx.ptr) self.ptr.pix_fmt = self.hwaccel_ctx.config.ptr.pix_fmt self.ptr.get_format = _get_hw_format self._private_data.hardware_pix_fmt = self.hwaccel_ctx.config.ptr.pix_fmt self._private_data.allow_software_fallback = self.hwaccel.allow_software_fallback self.ptr.opaque = &self._private_data except NotImplementedError: # Some streams may not have a hardware decoder. For example, many action # cam videos have a low resolution mjpeg stream, which is usually not # compatible with hardware decoders. # The user may have passed in a hwaccel because they want to decode the main # stream with it, so we shouldn't abort even if we find a stream that can't # be HW decoded. # If the user wants to make sure hwaccel is actually used, they can check with the # is_hwaccel() function on each stream's codec context. self.hwaccel_ctx = None self._build_format() self.encoded_frame_count = 0 cdef _prepare_frames_for_encode(self, Frame input): if not input: return [None] cdef VideoFrame vframe = input if self._format is None: raise ValueError("self._format is None, cannot encode") # Reformat if it doesn't match. if ( vframe.format.pix_fmt != self._format.pix_fmt or vframe.width != self.ptr.width or vframe.height != self.ptr.height ): if not self.reformatter: self.reformatter = VideoReformatter() vframe = self.reformatter.reformat( vframe, self.ptr.width, self.ptr.height, self._format ) # There is no pts, so create one. if vframe.ptr.pts == lib.AV_NOPTS_VALUE: vframe.ptr.pts = self.encoded_frame_count self.encoded_frame_count += 1 return [vframe] cdef Frame _alloc_next_frame(self): return alloc_video_frame() cdef _setup_decoded_frame(self, Frame frame, Packet packet): CodecContext._setup_decoded_frame(self, frame, packet) cdef VideoFrame vframe = frame vframe._init_user_attributes() cdef _transfer_hwframe(self, Frame frame): if self.hwaccel_ctx is None: return frame if frame.ptr.format != self.hwaccel_ctx.config.ptr.pix_fmt: # If we get a software frame, that means we are in software fallback mode, and don't actually # need to transfer. return frame cdef Frame frame_sw frame_sw = self._alloc_next_frame() err_check(lib.av_hwframe_transfer_data(frame_sw.ptr, frame.ptr, 0)) # TODO: Is there anything else to transfer?! frame_sw.pts = frame.pts return frame_sw cdef _build_format(self): self._format = get_video_format(self.ptr.pix_fmt, self.ptr.width, self.ptr.height) @property def format(self): return self._format @format.setter def format(self, VideoFormat format): self.ptr.pix_fmt = format.pix_fmt self.ptr.width = format.width self.ptr.height = format.height self._build_format() # Kinda wasteful. @property def width(self): if self.ptr is NULL: return 0 return self.ptr.width @width.setter def width(self, unsigned int value): self.ptr.width = value self._build_format() @property def height(self): if self.ptr is NULL: return 0 return self.ptr.height @height.setter def height(self, unsigned int value): self.ptr.height = value self._build_format() @property def bits_per_coded_sample(self): """ The number of bits per sample in the codedwords. It's mandatory for this to be set for some formats to decode properly. Wraps :ffmpeg:`AVCodecContext.bits_per_coded_sample`. :type: int """ return self.ptr.bits_per_coded_sample @bits_per_coded_sample.setter def bits_per_coded_sample(self, int value): if self.is_encoder: raise ValueError("Not supported for encoders") self.ptr.bits_per_coded_sample = value self._build_format() @property def pix_fmt(self): """ The pixel format's name. :type: str | None """ return getattr(self._format, "name", None) @pix_fmt.setter def pix_fmt(self, value): self.ptr.pix_fmt = get_pix_fmt(value) self._build_format() @property def framerate(self): """ The frame rate, in frames per second. :type: fractions.Fraction """ return avrational_to_fraction(&self.ptr.framerate) @framerate.setter def framerate(self, value): to_avrational(value, &self.ptr.framerate) @property def rate(self): """Another name for :attr:`framerate`.""" return self.framerate @rate.setter def rate(self, value): self.framerate = value @property def gop_size(self): """ Sets the number of frames between keyframes. Used only for encoding. :type: int """ if self.is_decoder: raise RuntimeError("Cannnot access 'gop_size' as a decoder") return self.ptr.gop_size @gop_size.setter def gop_size(self, int value): if self.is_decoder: raise RuntimeError("Cannnot access 'gop_size' as a decoder") self.ptr.gop_size = value @property def sample_aspect_ratio(self): return avrational_to_fraction(&self.ptr.sample_aspect_ratio) @sample_aspect_ratio.setter def sample_aspect_ratio(self, value): to_avrational(value, &self.ptr.sample_aspect_ratio) @property def display_aspect_ratio(self): cdef lib.AVRational dar lib.av_reduce( &dar.num, &dar.den, self.ptr.width * self.ptr.sample_aspect_ratio.num, self.ptr.height * self.ptr.sample_aspect_ratio.den, 1024*1024) return avrational_to_fraction(&dar) @property def has_b_frames(self): """ :type: bool """ return bool(self.ptr.has_b_frames) @property def coded_width(self): """ :type: int """ return self.ptr.coded_width @property def coded_height(self): """ :type: int """ return self.ptr.coded_height @property def color_range(self): """ Describes the signal range of the colorspace. Wraps :ffmpeg:`AVFrame.color_range`. :type: int """ return self.ptr.color_range @color_range.setter def color_range(self, value): self.ptr.color_range = value @property def color_primaries(self): """ Describes the RGB/XYZ matrix of the colorspace. Wraps :ffmpeg:`AVFrame.color_primaries`. :type: int """ return self.ptr.color_primaries @color_primaries.setter def color_primaries(self, value): self.ptr.color_primaries = value @property def color_trc(self): """ Describes the linearization function (a.k.a. transformation characteristics) of the colorspace. Wraps :ffmpeg:`AVFrame.color_trc`. :type: int """ return self.ptr.color_trc @color_trc.setter def color_trc(self, value): self.ptr.color_trc = value @property def colorspace(self): """ Describes the YUV/RGB transformation matrix of the colorspace. Wraps :ffmpeg:`AVFrame.colorspace`. :type: int """ return self.ptr.colorspace @colorspace.setter def colorspace(self, value): self.ptr.colorspace = value @property def max_b_frames(self): """ The maximum run of consecutive B frames when encoding a video. :type: int """ return self.ptr.max_b_frames @max_b_frames.setter def max_b_frames(self, value): self.ptr.max_b_frames = value @property def qmin(self): """ The minimum quantiser value of an encoded stream. Wraps :ffmpeg:`AVCodecContext.qmin`. :type: int """ return self.ptr.qmin @qmin.setter def qmin(self, value): self.ptr.qmin = value @property def qmax(self): """ The maximum quantiser value of an encoded stream. Wraps :ffmpeg:`AVCodecContext.qmax`. :type: int """ return self.ptr.qmax @qmax.setter def qmax(self, value): self.ptr.qmax = value PyAV-14.2.0/av/video/format.pxd000066400000000000000000000013271475734227400161600ustar00rootroot00000000000000cimport libav as lib cdef class VideoFormat: cdef lib.AVPixelFormat pix_fmt cdef const lib.AVPixFmtDescriptor *ptr cdef readonly unsigned int width, height cdef readonly tuple components cdef _init(self, lib.AVPixelFormat pix_fmt, unsigned int width, unsigned int height) cpdef chroma_width(self, int luma_width=?) cpdef chroma_height(self, int luma_height=?) cdef class VideoFormatComponent: cdef VideoFormat format cdef readonly unsigned int index cdef const lib.AVComponentDescriptor *ptr cdef VideoFormat get_video_format(lib.AVPixelFormat c_format, unsigned int width, unsigned int height) cdef lib.AVPixelFormat get_pix_fmt(const char *name) except lib.AV_PIX_FMT_NONE PyAV-14.2.0/av/video/format.pyi000066400000000000000000000014071475734227400161650ustar00rootroot00000000000000class VideoFormat: name: str bits_per_pixel: int padded_bits_per_pixel: int is_big_endian: bool has_palette: bool is_bit_stream: bool is_planar: bool @property def is_rgb(self) -> bool: ... @property def is_bayer(self) -> bool: ... width: int height: int components: tuple[VideoFormatComponent, ...] def __init__(self, name: str, width: int = 0, height: int = 0) -> None: ... def chroma_width(self, luma_width: int = 0) -> int: ... def chroma_height(self, luma_height: int = 0) -> int: ... class VideoFormatComponent: plane: int bits: int is_alpha: bool is_luma: bool is_chroma: bool width: int height: int def __init__(self, format: VideoFormat, index: int) -> None: ... PyAV-14.2.0/av/video/format.pyx000066400000000000000000000134571475734227400162140ustar00rootroot00000000000000 cdef object _cinit_bypass_sentinel = object() cdef VideoFormat get_video_format(lib.AVPixelFormat c_format, unsigned int width, unsigned int height): if c_format == lib.AV_PIX_FMT_NONE: return None cdef VideoFormat format = VideoFormat.__new__(VideoFormat, _cinit_bypass_sentinel) format._init(c_format, width, height) return format cdef lib.AVPixelFormat get_pix_fmt(const char *name) except lib.AV_PIX_FMT_NONE: """Wrapper for lib.av_get_pix_fmt with error checking.""" cdef lib.AVPixelFormat pix_fmt = lib.av_get_pix_fmt(name) if pix_fmt == lib.AV_PIX_FMT_NONE: raise ValueError("not a pixel format: %r" % name) return pix_fmt cdef class VideoFormat: """ >>> format = VideoFormat('rgb24') >>> format.name 'rgb24' """ def __cinit__(self, name, width=0, height=0): if name is _cinit_bypass_sentinel: return cdef VideoFormat other if isinstance(name, VideoFormat): other = name self._init(other.pix_fmt, width or other.width, height or other.height) return cdef lib.AVPixelFormat pix_fmt = get_pix_fmt(name) self._init(pix_fmt, width, height) cdef _init(self, lib.AVPixelFormat pix_fmt, unsigned int width, unsigned int height): self.pix_fmt = pix_fmt self.ptr = lib.av_pix_fmt_desc_get(pix_fmt) self.width = width self.height = height self.components = tuple( VideoFormatComponent(self, i) for i in range(self.ptr.nb_components) ) def __repr__(self): if self.width or self.height: return f"" else: return f"" def __int__(self): return int(self.pix_fmt) @property def name(self): """Canonical name of the pixel format.""" return self.ptr.name @property def bits_per_pixel(self): return lib.av_get_bits_per_pixel(self.ptr) @property def padded_bits_per_pixel(self): return lib.av_get_padded_bits_per_pixel(self.ptr) @property def is_big_endian(self): """Pixel format is big-endian.""" return bool(self.ptr.flags & lib.AV_PIX_FMT_FLAG_BE) @property def has_palette(self): """Pixel format has a palette in data[1], values are indexes in this palette.""" return bool(self.ptr.flags & lib.AV_PIX_FMT_FLAG_PAL) @property def is_bit_stream(self): """All values of a component are bit-wise packed end to end.""" return bool(self.ptr.flags & lib.AV_PIX_FMT_FLAG_BITSTREAM) # Skipping PIX_FMT_HWACCEL # """Pixel format is an HW accelerated format.""" @property def is_planar(self): """At least one pixel component is not in the first data plane.""" return bool(self.ptr.flags & lib.AV_PIX_FMT_FLAG_PLANAR) @property def is_rgb(self): """The pixel format contains RGB-like data (as opposed to YUV/grayscale).""" return bool(self.ptr.flags & lib.AV_PIX_FMT_FLAG_RGB) @property def is_bayer(self): """The pixel format contains Bayer data.""" return bool(self.ptr.flags & lib.AV_PIX_FMT_FLAG_BAYER) cpdef chroma_width(self, int luma_width=0): """chroma_width(luma_width=0) Width of a chroma plane relative to a luma plane. :param int luma_width: Width of the luma plane; defaults to ``self.width``. """ luma_width = luma_width or self.width return -((-luma_width) >> self.ptr.log2_chroma_w) if luma_width else 0 cpdef chroma_height(self, int luma_height=0): """chroma_height(luma_height=0) Height of a chroma plane relative to a luma plane. :param int luma_height: Height of the luma plane; defaults to ``self.height``. """ luma_height = luma_height or self.height return -((-luma_height) >> self.ptr.log2_chroma_h) if luma_height else 0 cdef class VideoFormatComponent: def __cinit__(self, VideoFormat format, size_t index): self.format = format self.index = index self.ptr = &format.ptr.comp[index] @property def plane(self): """The index of the plane which contains this component.""" return self.ptr.plane @property def bits(self): """Number of bits in the component.""" return self.ptr.depth @property def is_alpha(self): """Is this component an alpha channel?""" return ((self.index == 1 and self.format.ptr.nb_components == 2) or (self.index == 3 and self.format.ptr.nb_components == 4)) @property def is_luma(self): """Is this compoment a luma channel?""" return self.index == 0 and ( self.format.ptr.nb_components == 1 or self.format.ptr.nb_components == 2 or not self.format.is_rgb ) @property def is_chroma(self): """Is this component a chroma channel?""" return (self.index == 1 or self.index == 2) and (self.format.ptr.log2_chroma_w or self.format.ptr.log2_chroma_h) @property def width(self): """The width of this component's plane. Requires the parent :class:`VideoFormat` to have a width. """ return self.format.chroma_width() if self.is_chroma else self.format.width @property def height(self): """The height of this component's plane. Requires the parent :class:`VideoFormat` to have a height. """ return self.format.chroma_height() if self.is_chroma else self.format.height names = set() cdef const lib.AVPixFmtDescriptor *desc = NULL while True: desc = lib.av_pix_fmt_desc_next(desc) if not desc: break names.add(desc.name) PyAV-14.2.0/av/video/frame.pxd000066400000000000000000000011551475734227400157610ustar00rootroot00000000000000cimport libav as lib from libc.stdint cimport uint8_t from av.frame cimport Frame from av.video.format cimport VideoFormat from av.video.reformatter cimport VideoReformatter cdef class VideoFrame(Frame): # This is the buffer that is used to back everything in the AVFrame. # We don't ever actually access it directly. cdef uint8_t *_buffer cdef object _np_buffer cdef VideoReformatter reformatter cdef readonly VideoFormat format cdef _init(self, lib.AVPixelFormat format, unsigned int width, unsigned int height) cdef _init_user_attributes(self) cdef VideoFrame alloc_video_frame() PyAV-14.2.0/av/video/frame.pyi000066400000000000000000000043001475734227400157620ustar00rootroot00000000000000from enum import IntEnum from typing import Any, ClassVar, Union import numpy as np from PIL import Image from av.frame import Frame from .format import VideoFormat from .plane import VideoPlane _SupportedNDarray = Union[ np.ndarray[Any, np.dtype[np.uint8]], np.ndarray[Any, np.dtype[np.uint16]], np.ndarray[Any, np.dtype[np.float32]], ] supported_np_pix_fmts: set[str] class PictureType(IntEnum): NONE = 0 I = 1 P = 2 B = 3 S = 4 SI = 5 SP = 6 BI = 7 class VideoFrame(Frame): format: VideoFormat pts: int planes: tuple[VideoPlane, ...] pict_type: int colorspace: int color_range: int @property def time(self) -> float: ... @property def width(self) -> int: ... @property def height(self) -> int: ... @property def interlaced_frame(self) -> bool: ... @property def rotation(self) -> int: ... def __init__( self, width: int = 0, height: int = 0, format: str = "yuv420p" ) -> None: ... def reformat( self, width: int | None = None, height: int | None = None, format: str | None = None, src_colorspace: str | int | None = None, dst_colorspace: str | int | None = None, interpolation: int | str | None = None, src_color_range: int | str | None = None, dst_color_range: int | str | None = None, ) -> VideoFrame: ... def to_rgb(self, **kwargs: Any) -> VideoFrame: ... def to_image(self, **kwargs: Any) -> Image.Image: ... def to_ndarray( self, channel_last: bool = False, **kwargs: Any ) -> _SupportedNDarray: ... @staticmethod def from_image(img: Image.Image) -> VideoFrame: ... @staticmethod def from_numpy_buffer( array: _SupportedNDarray, format: str = "rgb24", width: int = 0 ) -> VideoFrame: ... @staticmethod def from_ndarray( array: _SupportedNDarray, format: str = "rgb24", channel_last: bool = False ) -> VideoFrame: ... @staticmethod def from_bytes( data: bytes, width: int, height: int, format: str = "rgba", flip_horizontal: bool = False, flip_vertical: bool = False, ) -> VideoFrame: ... PyAV-14.2.0/av/video/frame.pyx000066400000000000000000000721101475734227400160050ustar00rootroot00000000000000import sys from enum import IntEnum from libc.stdint cimport uint8_t from av.error cimport err_check from av.sidedata.sidedata cimport get_display_rotation from av.utils cimport check_ndarray from av.video.format cimport get_pix_fmt, get_video_format from av.video.plane cimport VideoPlane cdef object _cinit_bypass_sentinel # `pix_fmt`s supported by Frame.to_ndarray() and Frame.from_ndarray() supported_np_pix_fmts = { "abgr", "argb", "bayer_bggr16be", "bayer_bggr16le", "bayer_bggr8", "bayer_gbrg16be", "bayer_gbrg16le", "bayer_gbrg8", "bayer_grbg16be", "bayer_grbg16le", "bayer_grbg8", "bayer_rggb16be", "bayer_rggb16le", "bayer_rggb8", "bgr24", "bgr8", "bgra", "gbrapf32be", "gbrapf32le", "gbrp", "gbrp10be", "gbrp10le", "gbrp12be", "gbrp12le", "gbrp14be", "gbrp14le", "gbrp16be", "gbrp16le", "gbrpf32be", "gbrpf32le", "gray", "gray16be", "gray16le", "gray8", "grayf32be", "grayf32le", "nv12", "pal8", "rgb24", "rgb48be", "rgb48le", "rgb8", "rgba", "rgba64be", "rgba64le", "yuv420p", "yuv422p10le", "yuv444p", "yuv444p16be", "yuv444p16le", "yuva444p16be", "yuva444p16le", "yuvj420p", "yuvj444p", "yuyv422", } cdef VideoFrame alloc_video_frame(): """Get a mostly uninitialized VideoFrame. You MUST call VideoFrame._init(...) or VideoFrame._init_user_attributes() before exposing to the user. """ return VideoFrame.__new__(VideoFrame, _cinit_bypass_sentinel) class PictureType(IntEnum): NONE = lib.AV_PICTURE_TYPE_NONE # Undefined I = lib.AV_PICTURE_TYPE_I # Intra P = lib.AV_PICTURE_TYPE_P # Predicted B = lib.AV_PICTURE_TYPE_B # Bi-directional predicted S = lib.AV_PICTURE_TYPE_S # S(GMC)-VOP MPEG-4 SI = lib.AV_PICTURE_TYPE_SI # Switching intra SP = lib.AV_PICTURE_TYPE_SP # Switching predicted BI = lib.AV_PICTURE_TYPE_BI # BI type cdef byteswap_array(array, bint big_endian): if (sys.byteorder == "big") != big_endian: return array.byteswap() else: return array cdef copy_bytes_to_plane(img_bytes, VideoPlane plane, unsigned int bytes_per_pixel, bint flip_horizontal, bint flip_vertical): cdef const uint8_t[:] i_buf = img_bytes cdef size_t i_pos = 0 cdef size_t i_stride = plane.width * bytes_per_pixel cdef size_t i_size = plane.height * i_stride cdef uint8_t[:] o_buf = plane cdef size_t o_pos = 0 cdef size_t o_stride = abs(plane.line_size) cdef int start_row, end_row, step if flip_vertical: start_row = plane.height - 1 end_row = -1 step = -1 else: start_row = 0 end_row = plane.height step = 1 cdef int i, j for row in range(start_row, end_row, step): i_pos = row * i_stride if flip_horizontal: for i in range(0, i_stride, bytes_per_pixel): for j in range(bytes_per_pixel): o_buf[o_pos + i + j] = i_buf[i_pos + i_stride - i - bytes_per_pixel + j] else: o_buf[o_pos:o_pos + i_stride] = i_buf[i_pos:i_pos + i_stride] o_pos += o_stride cdef copy_array_to_plane(array, VideoPlane plane, unsigned int bytes_per_pixel): cdef bytes imgbytes = array.tobytes() copy_bytes_to_plane(imgbytes, plane, bytes_per_pixel, False, False) cdef useful_array(VideoPlane plane, unsigned int bytes_per_pixel=1, str dtype="uint8"): """ Return the useful part of the VideoPlane as a single dimensional array. We are simply discarding any padding which was added for alignment. """ import numpy as np cdef size_t total_line_size = abs(plane.line_size) cdef size_t useful_line_size = plane.width * bytes_per_pixel arr = np.frombuffer(plane, np.uint8) if total_line_size != useful_line_size: arr = arr.reshape(-1, total_line_size)[:, 0:useful_line_size].reshape(-1) return arr.view(np.dtype(dtype)) cdef check_ndarray_shape(object array, bint ok): if not ok: raise ValueError(f"Unexpected numpy array shape `{array.shape}`") cdef class VideoFrame(Frame): def __cinit__(self, width=0, height=0, format="yuv420p"): if width is _cinit_bypass_sentinel: return cdef lib.AVPixelFormat c_format = get_pix_fmt(format) self._init(c_format, width, height) cdef _init(self, lib.AVPixelFormat format, unsigned int width, unsigned int height): cdef int res = 0 with nogil: self.ptr.width = width self.ptr.height = height self.ptr.format = format # We enforce aligned buffers, otherwise `sws_scale` can perform # poorly or even cause out-of-bounds reads and writes. if width and height: res = lib.av_image_alloc( self.ptr.data, self.ptr.linesize, width, height, format, 16 ) self._buffer = self.ptr.data[0] if res: err_check(res) self._init_user_attributes() cdef _init_user_attributes(self): self.format = get_video_format(self.ptr.format, self.ptr.width, self.ptr.height) def __dealloc__(self): # The `self._buffer` member is only set if *we* allocated the buffer in `_init`, # as opposed to a buffer allocated by a decoder. lib.av_freep(&self._buffer) # Let go of the reference from the numpy buffers if we made one self._np_buffer = None def __repr__(self): return ( f"" ) @property def planes(self): """ A tuple of :class:`.VideoPlane` objects. """ # We need to detect which planes actually exist, but also contrain # ourselves to the maximum plane count (as determined only by VideoFrames # so far), in case the library implementation does not set the last # plane to NULL. cdef int max_plane_count = 0 for i in range(self.format.ptr.nb_components): count = self.format.ptr.comp[i].plane + 1 if max_plane_count < count: max_plane_count = count if self.format.name == "pal8": max_plane_count = 2 cdef int plane_count = 0 while plane_count < max_plane_count and self.ptr.extended_data[plane_count]: plane_count += 1 return tuple([VideoPlane(self, i) for i in range(plane_count)]) @property def width(self): """Width of the image, in pixels.""" return self.ptr.width @property def height(self): """Height of the image, in pixels.""" return self.ptr.height @property def rotation(self): """The rotation component of the `DISPLAYMATRIX` transformation matrix. Returns: int: The angle (in degrees) by which the transformation rotates the frame counterclockwise. The angle will be in range [-180, 180]. """ return get_display_rotation(self) @property def interlaced_frame(self): """Is this frame an interlaced or progressive?""" return bool(self.ptr.flags & lib.AV_FRAME_FLAG_INTERLACED) @property def pict_type(self): """Returns an integer that corresponds to the PictureType enum. Wraps :ffmpeg:`AVFrame.pict_type` :type: int """ return self.ptr.pict_type @pict_type.setter def pict_type(self, value): self.ptr.pict_type = value @property def colorspace(self): """Colorspace of frame. Wraps :ffmpeg:`AVFrame.colorspace`. """ return self.ptr.colorspace @colorspace.setter def colorspace(self, value): self.ptr.colorspace = value @property def color_range(self): """Color range of frame. Wraps :ffmpeg:`AVFrame.color_range`. """ return self.ptr.color_range @color_range.setter def color_range(self, value): self.ptr.color_range = value def reformat(self, *args, **kwargs): """reformat(width=None, height=None, format=None, src_colorspace=None, dst_colorspace=None, interpolation=None) Create a new :class:`VideoFrame` with the given width/height/format/colorspace. .. seealso:: :meth:`.VideoReformatter.reformat` for arguments. """ if not self.reformatter: self.reformatter = VideoReformatter() return self.reformatter.reformat(self, *args, **kwargs) def to_rgb(self, **kwargs): """Get an RGB version of this frame. Any ``**kwargs`` are passed to :meth:`.VideoReformatter.reformat`. >>> frame = VideoFrame(1920, 1080) >>> frame.format.name 'yuv420p' >>> frame.to_rgb().format.name 'rgb24' """ return self.reformat(format="rgb24", **kwargs) def to_image(self, **kwargs): """Get an RGB ``PIL.Image`` of this frame. Any ``**kwargs`` are passed to :meth:`.VideoReformatter.reformat`. .. note:: PIL or Pillow must be installed. """ from PIL import Image cdef VideoPlane plane = self.reformat(format="rgb24", **kwargs).planes[0] cdef const uint8_t[:] i_buf = plane cdef size_t i_pos = 0 cdef size_t i_stride = plane.line_size cdef size_t o_pos = 0 cdef size_t o_stride = plane.width * 3 cdef size_t o_size = plane.height * o_stride cdef bytearray o_buf = bytearray(o_size) while o_pos < o_size: o_buf[o_pos:o_pos + o_stride] = i_buf[i_pos:i_pos + o_stride] i_pos += i_stride o_pos += o_stride return Image.frombytes("RGB", (plane.width, plane.height), bytes(o_buf), "raw", "RGB", 0, 1) def to_ndarray(self, channel_last=False, **kwargs): """Get a numpy array of this frame. Any ``**kwargs`` are passed to :meth:`.VideoReformatter.reformat`. The array returned is generally of dimension (height, width, channels). :param bool channel_last: If True, the shape of array will be (height, width, channels) rather than (channels, height, width) for the "yuv444p" and "yuvj444p" formats. .. note:: Numpy must be installed. .. note:: For formats which return an array of ``uint16`` or ``float32``, the samples will be in the system's native byte order. .. note:: For ``pal8``, an ``(image, palette)`` tuple will be returned, with the palette being in ARGB (PyAV will swap bytes if needed). .. note:: For ``gbrp`` formats, channels are flipped to RGB order. """ cdef VideoFrame frame = self.reformat(**kwargs) import numpy as np # check size if frame.format.name in {"yuv420p", "yuvj420p", "yuyv422", "yuv422p10le"}: assert frame.width % 2 == 0, "the width has to be even for this pixel format" assert frame.height % 2 == 0, "the height has to be even for this pixel format" # cases planes are simply concatenated in shape (height, width, channels) itemsize, dtype = { "abgr": (4, "uint8"), "argb": (4, "uint8"), "bayer_bggr8": (1, "uint8"), "bayer_gbrg8": (1, "uint8"), "bayer_grbg8": (1, "uint8"), "bayer_rggb8": (1, "uint8"), "bayer_bggr16le": (2, "uint16"), "bayer_bggr16be": (2, "uint16"), "bayer_gbrg16le": (2, "uint16"), "bayer_gbrg16be": (2, "uint16"), "bayer_grbg16le": (2, "uint16"), "bayer_grbg16be": (2, "uint16"), "bayer_rggb16le": (2, "uint16"), "bayer_rggb16be": (2, "uint16"), "bgr24": (3, "uint8"), "bgr8": (1, "uint8"), "bgra": (4, "uint8"), "gbrapf32be": (4, "float32"), "gbrapf32le": (4, "float32"), "gbrp": (1, "uint8"), "gbrp10be": (2, "uint16"), "gbrp10le": (2, "uint16"), "gbrp12be": (2, "uint16"), "gbrp12le": (2, "uint16"), "gbrp14be": (2, "uint16"), "gbrp14le": (2, "uint16"), "gbrp16be": (2, "uint16"), "gbrp16le": (2, "uint16"), "gbrpf32be": (4, "float32"), "gbrpf32le": (4, "float32"), "gray": (1, "uint8"), "gray16be": (2, "uint16"), "gray16le": (2, "uint16"), "gray8": (1, "uint8"), "grayf32be": (4, "float32"), "grayf32le": (4, "float32"), "rgb24": (3, "uint8"), "rgb48be": (6, "uint16"), "rgb48le": (6, "uint16"), "rgb8": (1, "uint8"), "rgba": (4, "uint8"), "rgba64be": (8, "uint16"), "rgba64le": (8, "uint16"), "yuv444p": (1, "uint8"), "yuv444p16be": (2, "uint16"), "yuv444p16le": (2, "uint16"), "yuva444p16be": (2, "uint16"), "yuva444p16le": (2, "uint16"), "yuvj444p": (1, "uint8"), "yuyv422": (2, "uint8"), }.get(frame.format.name, (None, None)) if itemsize is not None: layers = [ useful_array(plan, itemsize, dtype) .reshape(frame.height, frame.width, -1) for plan in frame.planes ] if len(layers) == 1: # shortcut, avoid memory copy array = layers[0] else: # general case array = np.concatenate(layers, axis=2) array = byteswap_array(array, frame.format.name.endswith("be")) if array.shape[2] == 1: # skip last channel for gray images return array.squeeze(2) if frame.format.name.startswith("gbr"): # gbr -> rgb buffer = array[:, :, 0].copy() array[:, :, 0] = array[:, :, 2] array[:, :, 2] = array[:, :, 1] array[:, :, 1] = buffer if not channel_last and frame.format.name in {"yuv444p", "yuvj444p"}: array = np.moveaxis(array, 2, 0) return array # special cases if frame.format.name in {"yuv420p", "yuvj420p"}: return np.hstack([ useful_array(frame.planes[0]), useful_array(frame.planes[1]), useful_array(frame.planes[2]), ]).reshape(-1, frame.width) if frame.format.name == "yuv422p10le": # Read planes as uint16 at their original width y = useful_array(frame.planes[0], 2, "uint16").reshape(frame.height, frame.width) u = useful_array(frame.planes[1], 2, "uint16").reshape(frame.height, frame.width // 2) v = useful_array(frame.planes[2], 2, "uint16").reshape(frame.height, frame.width // 2) # Double the width of U and V by repeating each value u_full = np.repeat(u, 2, axis=1) v_full = np.repeat(v, 2, axis=1) if channel_last: return np.stack([y, u_full, v_full], axis=2) return np.stack([y, u_full, v_full], axis=0) if frame.format.name == "pal8": image = useful_array(frame.planes[0]).reshape(frame.height, frame.width) palette = np.frombuffer(frame.planes[1], "i4").astype(">i4").reshape(-1, 1).view(np.uint8) return image, palette if frame.format.name == "nv12": return np.hstack([ useful_array(frame.planes[0]), useful_array(frame.planes[1], 2), ]).reshape(-1, frame.width) raise ValueError( f"Conversion to numpy array with format `{frame.format.name}` is not yet supported" ) @staticmethod def from_image(img): """ Construct a frame from a ``PIL.Image``. """ if img.mode != "RGB": img = img.convert("RGB") cdef VideoFrame frame = VideoFrame(img.size[0], img.size[1], "rgb24") copy_array_to_plane(img, frame.planes[0], 3) return frame @staticmethod def from_numpy_buffer(array, format="rgb24", width=0): # Usually the width of the array is the same as the width of the image. But sometimes # this is not possible, for example with yuv420p images that have padding. These are # awkward because the UV rows at the bottom have padding bytes in the middle of the # row as well as at the end. To cope with these, callers need to be able to pass the # actual width to us. height = array.shape[0] if not width: width = array.shape[1] if format in ("rgb24", "bgr24"): check_ndarray(array, "uint8", 3) check_ndarray_shape(array, array.shape[2] == 3) if array.strides[1:] != (3, 1): raise ValueError("provided array does not have C_CONTIGUOUS rows") linesizes = (array.strides[0], ) elif format in ("rgba", "bgra"): check_ndarray(array, "uint8", 3) check_ndarray_shape(array, array.shape[2] == 4) if array.strides[1:] != (4, 1): raise ValueError("provided array does not have C_CONTIGUOUS rows") linesizes = (array.strides[0], ) elif format in ("gray", "gray8", "rgb8", "bgr8"): check_ndarray(array, "uint8", 2) if array.strides[1] != 1: raise ValueError("provided array does not have C_CONTIGUOUS rows") linesizes = (array.strides[0], ) elif format in ("yuv420p", "yuvj420p", "nv12"): check_ndarray(array, "uint8", 2) check_ndarray_shape(array, array.shape[0] % 3 == 0) check_ndarray_shape(array, array.shape[1] % 2 == 0) height = height // 6 * 4 if array.strides[1] != 1: raise ValueError("provided array does not have C_CONTIGUOUS rows") if format in ("yuv420p", "yuvj420p"): # For YUV420 planar formats, the UV plane stride is always half the Y stride. linesizes = (array.strides[0], array.strides[0] // 2, array.strides[0] // 2) else: # Planes where U and V are interleaved have the same stride as Y. linesizes = (array.strides[0], array.strides[0]) elif format in {"bayer_bggr8", "bayer_rggb8", "bayer_gbrg8", "bayer_grbg8","bayer_bggr16le", "bayer_rggb16le", "bayer_gbrg16le", "bayer_grbg16le","bayer_bggr16be", "bayer_rggb16be", "bayer_gbrg16be", "bayer_grbg16be"}: check_ndarray(array, "uint8" if format.endswith("8") else "uint16", 2) if array.strides[1] != (1 if format.endswith("8") else 2): raise ValueError("provided array does not have C_CONTIGUOUS rows") linesizes = (array.strides[0],) else: raise ValueError(f"Conversion from numpy array with format `{format}` is not yet supported") frame = alloc_video_frame() frame._image_fill_pointers_numpy(array, width, height, linesizes, format) return frame def _image_fill_pointers_numpy(self, buffer, width, height, linesizes, format): cdef lib.AVPixelFormat c_format cdef uint8_t * c_ptr cdef size_t c_data # If you want to use the numpy notation # then you need to include the following two lines at the top of the file # cimport numpy as cnp # cnp.import_array() # And add the numpy include directories to the setup.py files # hint np.get_include() # cdef cnp.ndarray[ # dtype=cnp.uint8_t, ndim=1, # negative_indices=False, mode='c'] c_buffer # c_buffer = buffer.reshape(-1) # c_ptr = &c_buffer[0] # c_ptr = ((buffer.ctypes.data)) # Using buffer.ctypes.data helps avoid any kind of # usage of the c-api from numpy, which avoid the need to add numpy # as a compile time dependency # Without this double cast, you get an error that looks like # c_ptr = (buffer.ctypes.data) # TypeError: expected bytes, int found c_data = buffer.ctypes.data c_ptr = (c_data) c_format = get_pix_fmt(format) lib.av_freep(&self._buffer) # Hold on to a reference for the numpy buffer # so that it doesn't get accidentally garbage collected self._np_buffer = buffer self.ptr.format = c_format self.ptr.width = width self.ptr.height = height for i, linesize in enumerate(linesizes): self.ptr.linesize[i] = linesize res = lib.av_image_fill_pointers( self.ptr.data, self.ptr.format, self.ptr.height, c_ptr, self.ptr.linesize, ) if res: err_check(res) self._init_user_attributes() @staticmethod def from_ndarray(array, format="rgb24", channel_last=False): """ Construct a frame from a numpy array. :param bool channel_last: If False (default), the shape for the yuv444p and yuvj444p is given by (channels, height, width) rather than (height, width, channels). .. note:: For formats which expect an array of ``uint16``, the samples must be in the system's native byte order. .. note:: for ``pal8``, an ``(image, palette)`` pair must be passed. `palette` must have shape (256, 4) and is given in ARGB format (PyAV will swap bytes if needed). .. note:: for ``gbrp`` formats, channels are assumed to be given in RGB order. """ import numpy as np # case layers are concatenated channels, itemsize, dtype = { "yuv444p": (3, 1, "uint8"), "yuvj444p": (3, 1, "uint8"), "gbrp": (3, 1, "uint8"), "gbrp10be": (3, 2, "uint16"), "gbrp12be": (3, 2, "uint16"), "gbrp14be": (3, 2, "uint16"), "gbrp16be": (3, 2, "uint16"), "gbrp10le": (3, 2, "uint16"), "gbrp12le": (3, 2, "uint16"), "gbrp14le": (3, 2, "uint16"), "gbrp16le": (3, 2, "uint16"), "gbrpf32be": (3, 4, "float32"), "gbrpf32le": (3, 4, "float32"), "gray": (1, 1, "uint8"), "gray8": (1, 1, "uint8"), "rgb8": (1, 1, "uint8"), "bgr8": (1, 1, "uint8"), "gray16be": (1, 2, "uint16"), "gray16le": (1, 2, "uint16"), "grayf32be": (1, 4, "float32"), "grayf32le": (1, 4, "float32"), "gbrapf32be": (4, 4, "float32"), "gbrapf32le": (4, 4, "float32"), "yuv444p16be": (3, 2, "uint16"), "yuv444p16le": (3, 2, "uint16"), "yuva444p16be": (4, 2, "uint16"), "yuva444p16le": (4, 2, "uint16"), "bayer_bggr8": (1, 1, "uint8"), "bayer_rggb8": (1, 1, "uint8"), "bayer_grbg8": (1, 1, "uint8"), "bayer_gbrg8": (1, 1, "uint8"), "bayer_bggr16be": (1, 2, "uint16"), "bayer_bggr16le": (1, 2, "uint16"), "bayer_rggb16be": (1, 2, "uint16"), "bayer_rggb16le": (1, 2, "uint16"), "bayer_grbg16be": (1, 2, "uint16"), "bayer_grbg16le": (1, 2, "uint16"), "bayer_gbrg16be": (1, 2, "uint16"), "bayer_gbrg16le": (1, 2, "uint16"), }.get(format, (None, None, None)) if channels is not None: if array.ndim == 2: # (height, width) -> (height, width, 1) array = array[:, :, None] check_ndarray(array, dtype, 3) if not channel_last and format in {"yuv444p", "yuvj444p"}: array = np.moveaxis(array, 0, 2) # (channels, h, w) -> (h, w, channels) check_ndarray_shape(array, array.shape[2] == channels) array = byteswap_array(array, format.endswith("be")) frame = VideoFrame(array.shape[1], array.shape[0], format) if frame.format.name.startswith("gbr"): # rgb -> gbr array = np.concatenate([ # not inplace to avoid bad surprises array[:, :, 1:3], array[:, :, 0:1], array[:, :, 3:], ], axis=2) for i in range(channels): copy_array_to_plane(array[:, :, i], frame.planes[i], itemsize) return frame # other cases if format == "pal8": array, palette = array check_ndarray(array, "uint8", 2) check_ndarray(palette, "uint8", 2) check_ndarray_shape(palette, palette.shape == (256, 4)) frame = VideoFrame(array.shape[1], array.shape[0], format) copy_array_to_plane(array, frame.planes[0], 1) frame.planes[1].update(palette.view(">i4").astype("i4").tobytes()) return frame elif format in {"yuv420p", "yuvj420p"}: check_ndarray(array, "uint8", 2) check_ndarray_shape(array, array.shape[0] % 3 == 0) check_ndarray_shape(array, array.shape[1] % 2 == 0) frame = VideoFrame(array.shape[1], (array.shape[0] * 2) // 3, format) u_start = frame.width * frame.height v_start = 5 * u_start // 4 flat = array.reshape(-1) copy_array_to_plane(flat[0:u_start], frame.planes[0], 1) copy_array_to_plane(flat[u_start:v_start], frame.planes[1], 1) copy_array_to_plane(flat[v_start:], frame.planes[2], 1) return frame elif format == "yuv422p10le": if not isinstance(array, np.ndarray) or array.dtype != np.uint16: raise ValueError("Array must be uint16 type") # Convert to channel-first if needed if channel_last and array.shape[2] == 3: array = np.moveaxis(array, 2, 0) elif not (array.shape[0] == 3): raise ValueError("Array must have shape (3, height, width) or (height, width, 3)") height, width = array.shape[1:] if width % 2 != 0 or height % 2 != 0: raise ValueError("Width and height must be even") frame = VideoFrame(width, height, format) copy_array_to_plane(array[0], frame.planes[0], 2) # Subsample U and V by taking every other column u = array[1, :, ::2].copy() # Need copy to ensure C-contiguous v = array[2, :, ::2].copy() # Need copy to ensure C-contiguous copy_array_to_plane(u, frame.planes[1], 2) copy_array_to_plane(v, frame.planes[2], 2) return frame elif format == "yuyv422": check_ndarray(array, "uint8", 3) check_ndarray_shape(array, array.shape[0] % 2 == 0) check_ndarray_shape(array, array.shape[1] % 2 == 0) check_ndarray_shape(array, array.shape[2] == 2) elif format in {"rgb24", "bgr24"}: check_ndarray(array, "uint8", 3) check_ndarray_shape(array, array.shape[2] == 3) elif format in {"argb", "rgba", "abgr", "bgra"}: check_ndarray(array, "uint8", 3) check_ndarray_shape(array, array.shape[2] == 4) elif format in {"rgb48be", "rgb48le"}: check_ndarray(array, "uint16", 3) check_ndarray_shape(array, array.shape[2] == 3) frame = VideoFrame(array.shape[1], array.shape[0], format) copy_array_to_plane(byteswap_array(array, format.endswith("be")), frame.planes[0], 6) return frame elif format in {"rgba64be", "rgba64le"}: check_ndarray(array, "uint16", 3) check_ndarray_shape(array, array.shape[2] == 4) frame = VideoFrame(array.shape[1], array.shape[0], format) copy_array_to_plane(byteswap_array(array, format.endswith("be")), frame.planes[0], 8) return frame elif format == "nv12": check_ndarray(array, "uint8", 2) check_ndarray_shape(array, array.shape[0] % 3 == 0) check_ndarray_shape(array, array.shape[1] % 2 == 0) frame = VideoFrame(array.shape[1], (array.shape[0] * 2) // 3, format) uv_start = frame.width * frame.height flat = array.reshape(-1) copy_array_to_plane(flat[:uv_start], frame.planes[0], 1) copy_array_to_plane(flat[uv_start:], frame.planes[1], 2) return frame else: raise ValueError(f"Conversion from numpy array with format `{format}` is not yet supported") frame = VideoFrame(array.shape[1], array.shape[0], format) copy_array_to_plane(array, frame.planes[0], 1 if array.ndim == 2 else array.shape[2]) return frame @staticmethod def from_bytes(img_bytes: bytes, width: int, height: int, format="rgba", flip_horizontal=False, flip_vertical=False): frame = VideoFrame(width, height, format) if format == "rgba": copy_bytes_to_plane(img_bytes, frame.planes[0], 4, flip_horizontal, flip_vertical) elif format in ("bayer_bggr8", "bayer_rggb8", "bayer_gbrg8", "bayer_grbg8","bayer_bggr16le", "bayer_rggb16le", "bayer_gbrg16le", "bayer_grbg16le","bayer_bggr16be", "bayer_rggb16be", "bayer_gbrg16be", "bayer_grbg16be"): copy_bytes_to_plane(img_bytes, frame.planes[0], 1 if format.endswith("8") else 2, flip_horizontal, flip_vertical) else: raise NotImplementedError(f"Format '{format}' is not supported.") return frame PyAV-14.2.0/av/video/plane.pxd000066400000000000000000000003011475734227400157560ustar00rootroot00000000000000from av.plane cimport Plane from av.video.format cimport VideoFormatComponent cdef class VideoPlane(Plane): cdef readonly size_t buffer_size cdef readonly unsigned int width, height PyAV-14.2.0/av/video/plane.pyi000066400000000000000000000003371475734227400157750ustar00rootroot00000000000000from av.plane import Plane from .frame import VideoFrame class VideoPlane(Plane): line_size: int width: int height: int buffer_size: int def __init__(self, frame: VideoFrame, index: int) -> None: ... PyAV-14.2.0/av/video/plane.pyx000066400000000000000000000024161475734227400160140ustar00rootroot00000000000000from av.video.frame cimport VideoFrame cdef class VideoPlane(Plane): def __cinit__(self, VideoFrame frame, int index): # The palette plane has no associated component or linesize; set fields manually if frame.format.name == "pal8" and index == 1: self.width = 256 self.height = 1 self.buffer_size = 256 * 4 return for i in range(frame.format.ptr.nb_components): if frame.format.ptr.comp[i].plane == index: component = frame.format.components[i] self.width = component.width self.height = component.height break else: raise RuntimeError(f"could not find plane {index} of {frame.format!r}") # Sometimes, linesize is negative (and that is meaningful). We are only # insisting that the buffer size be based on the extent of linesize, and # ignore it's direction. self.buffer_size = abs(self.frame.ptr.linesize[self.index]) * self.height cdef size_t _buffer_size(self): return self.buffer_size @property def line_size(self): """ Bytes per horizontal line in this plane. :type: int """ return self.frame.ptr.linesize[self.index] PyAV-14.2.0/av/video/reformatter.pxd000066400000000000000000000005651475734227400172250ustar00rootroot00000000000000cimport libav as lib from av.video.frame cimport VideoFrame cdef class VideoReformatter: cdef lib.SwsContext *ptr cdef _reformat(self, VideoFrame frame, int width, int height, lib.AVPixelFormat format, int src_colorspace, int dst_colorspace, int interpolation, int src_color_range, int dst_color_range) PyAV-14.2.0/av/video/reformatter.pyi000066400000000000000000000025461475734227400172340ustar00rootroot00000000000000from enum import IntEnum from typing import cast from .frame import VideoFrame class Interpolation(IntEnum): FAST_BILINEAER = cast(int, ...) BILINEAR = cast(int, ...) BICUBIC = cast(int, ...) X = cast(int, ...) POINT = cast(int, ...) AREA = cast(int, ...) BICUBLIN = cast(int, ...) GAUSS = cast(int, ...) SINC = cast(int, ...) LANCZOS = cast(int, ...) SPLINE = cast(int, ...) class Colorspace(IntEnum): ITU709 = cast(int, ...) FCC = cast(int, ...) ITU601 = cast(int, ...) ITU624 = cast(int, ...) SMPTE170M = cast(int, ...) SMPTE240M = cast(int, ...) DEFAULT = cast(int, ...) itu709 = cast(int, ...) fcc = cast(int, ...) itu601 = cast(int, ...) itu624 = cast(int, ...) smpte170m = cast(int, ...) smpte240m = cast(int, ...) default = cast(int, ...) class ColorRange(IntEnum): UNSPECIFIED = 0 MPEG = 1 JPEG = 2 NB = 3 class VideoReformatter: def reformat( self, frame: VideoFrame, width: int | None = None, height: int | None = None, format: str | None = None, src_colorspace: int | None = None, dst_colorspace: int | None = None, interpolation: int | str | None = None, src_color_range: int | str | None = None, dst_color_range: int | str | None = None, ) -> VideoFrame: ... PyAV-14.2.0/av/video/reformatter.pyx000066400000000000000000000203431475734227400172460ustar00rootroot00000000000000cimport libav as lib from libc.stdint cimport uint8_t from av.error cimport err_check from av.video.format cimport VideoFormat from av.video.frame cimport alloc_video_frame from enum import IntEnum class Interpolation(IntEnum): FAST_BILINEAR: "Fast bilinear" = lib.SWS_FAST_BILINEAR BILINEAR: "Bilinear" = lib.SWS_BILINEAR BICUBIC: "Bicubic" = lib.SWS_BICUBIC X: "Experimental" = lib.SWS_X POINT: "Nearest neighbor / point" = lib.SWS_POINT AREA: "Area averaging" = lib.SWS_AREA BICUBLIN: "Luma bicubic / chroma bilinear" = lib.SWS_BICUBLIN GAUSS: "Gaussian" = lib.SWS_GAUSS SINC: "Sinc" = lib.SWS_SINC LANCZOS: "Bicubic spline" = lib.SWS_LANCZOS class Colorspace(IntEnum): ITU709 = lib.SWS_CS_ITU709 FCC = lib.SWS_CS_FCC ITU601 = lib.SWS_CS_ITU601 ITU624 = lib.SWS_CS_ITU624 SMPTE170M = lib.SWS_CS_SMPTE170M SMPTE240M = lib.SWS_CS_SMPTE240M DEFAULT = lib.SWS_CS_DEFAULT # Lowercase for b/c. itu709 = lib.SWS_CS_ITU709 fcc = lib.SWS_CS_FCC itu601 = lib.SWS_CS_ITU601 itu624 = lib.SWS_CS_ITU624 smpte170m = lib.SWS_CS_SMPTE170M smpte240m = lib.SWS_CS_SMPTE240M default = lib.SWS_CS_DEFAULT class ColorRange(IntEnum): UNSPECIFIED: "Unspecified" = lib.AVCOL_RANGE_UNSPECIFIED MPEG: "MPEG (limited) YUV range, 219*2^(n-8)" = lib.AVCOL_RANGE_MPEG JPEG: "JPEG (full) YUV range, 2^n-1" = lib.AVCOL_RANGE_JPEG NB: "Not part of ABI" = lib.AVCOL_RANGE_NB def _resolve_enum_value(value, enum_class, default): # Helper function to resolve enum values from different input types. if value is None: return default if isinstance(value, enum_class): return value.value if isinstance(value, int): return value if isinstance(value, str): return enum_class[value].value raise ValueError(f"Cannot convert {value} to {enum_class.__name__}") cdef class VideoReformatter: """An object for reformatting size and pixel format of :class:`.VideoFrame`. It is most efficient to have a reformatter object for each set of parameters you will use as calling :meth:`reformat` will reconfigure the internal object. """ def __dealloc__(self): with nogil: lib.sws_freeContext(self.ptr) def reformat(self, VideoFrame frame not None, width=None, height=None, format=None, src_colorspace=None, dst_colorspace=None, interpolation=None, src_color_range=None, dst_color_range=None): """Create a new :class:`VideoFrame` with the given width/height/format/colorspace. Returns the same frame untouched if nothing needs to be done to it. :param int width: New width, or ``None`` for the same width. :param int height: New height, or ``None`` for the same height. :param format: New format, or ``None`` for the same format. :type format: :class:`.VideoFormat` or ``str`` :param src_colorspace: Current colorspace, or ``None`` for the frame colorspace. :type src_colorspace: :class:`Colorspace` or ``str`` :param dst_colorspace: Desired colorspace, or ``None`` for the frame colorspace. :type dst_colorspace: :class:`Colorspace` or ``str`` :param interpolation: The interpolation method to use, or ``None`` for ``BILINEAR``. :type interpolation: :class:`Interpolation` or ``str`` :param src_color_range: Current color range, or ``None`` for the ``UNSPECIFIED``. :type src_color_range: :class:`color range` or ``str`` :param dst_color_range: Desired color range, or ``None`` for the ``UNSPECIFIED``. :type dst_color_range: :class:`color range` or ``str`` """ cdef VideoFormat video_format = VideoFormat(format if format is not None else frame.format) cdef int c_src_colorspace = _resolve_enum_value(src_colorspace, Colorspace, frame.colorspace) cdef int c_dst_colorspace = _resolve_enum_value(dst_colorspace, Colorspace, frame.colorspace) cdef int c_interpolation = _resolve_enum_value(interpolation, Interpolation, int(Interpolation.BILINEAR)) cdef int c_src_color_range = _resolve_enum_value(src_color_range, ColorRange, 0) cdef int c_dst_color_range = _resolve_enum_value(dst_color_range, ColorRange, 0) return self._reformat( frame, width or frame.ptr.width, height or frame.ptr.height, video_format.pix_fmt, c_src_colorspace, c_dst_colorspace, c_interpolation, c_src_color_range, c_dst_color_range, ) cdef _reformat(self, VideoFrame frame, int width, int height, lib.AVPixelFormat dst_format, int src_colorspace, int dst_colorspace, int interpolation, int src_color_range, int dst_color_range): if frame.ptr.format < 0: raise ValueError("Frame does not have format set.") # The definition of color range in pixfmt.h and swscale.h is different. src_color_range = 1 if src_color_range == ColorRange.JPEG.value else 0 dst_color_range = 1 if dst_color_range == ColorRange.JPEG.value else 0 cdef lib.AVPixelFormat src_format = frame.ptr.format # Shortcut! if ( dst_format == src_format and width == frame.ptr.width and height == frame.ptr.height and dst_colorspace == src_colorspace and src_color_range == dst_color_range ): return frame with nogil: self.ptr = lib.sws_getCachedContext( self.ptr, frame.ptr.width, frame.ptr.height, src_format, width, height, dst_format, interpolation, NULL, NULL, NULL ) # We want to change the colorspace/color_range transforms. # We do that by grabbing all of the current settings, changing a # couple, and setting them all. We need a lot of state here. cdef const int *inv_tbl cdef const int *tbl cdef int src_colorspace_range, dst_colorspace_range cdef int brightness, contrast, saturation cdef int ret if src_colorspace != dst_colorspace or src_color_range != dst_color_range: with nogil: # Casts for const-ness, because Cython isn't expressive enough. ret = lib.sws_getColorspaceDetails( self.ptr, &inv_tbl, &src_colorspace_range, &tbl, &dst_colorspace_range, &brightness, &contrast, &saturation ) err_check(ret) with nogil: # Grab the coefficients for the requested transforms. # The inv_table brings us to linear, and `tbl` to the new space. if src_colorspace != lib.SWS_CS_DEFAULT: inv_tbl = lib.sws_getCoefficients(src_colorspace) if dst_colorspace != lib.SWS_CS_DEFAULT: tbl = lib.sws_getCoefficients(dst_colorspace) # Apply! ret = lib.sws_setColorspaceDetails( self.ptr, inv_tbl, src_color_range, tbl, dst_color_range, brightness, contrast, saturation ) err_check(ret) # Create a new VideoFrame. cdef VideoFrame new_frame = alloc_video_frame() new_frame._copy_internal_attributes(frame) new_frame._init(dst_format, width, height) # Finally, scale the image. with nogil: lib.sws_scale( self.ptr, # Cast for const-ness, because Cython isn't expressive enough. frame.ptr.data, frame.ptr.linesize, 0, # slice Y frame.ptr.height, new_frame.ptr.data, new_frame.ptr.linesize, ) return new_frame PyAV-14.2.0/av/video/stream.pxd000066400000000000000000000003211475734227400161540ustar00rootroot00000000000000from av.packet cimport Packet from av.stream cimport Stream from .frame cimport VideoFrame cdef class VideoStream(Stream): cpdef encode(self, VideoFrame frame=?) cpdef decode(self, Packet packet=?) PyAV-14.2.0/av/video/stream.pyi000066400000000000000000000022431475734227400161670ustar00rootroot00000000000000from fractions import Fraction from typing import Iterator, Literal from av.codec.context import ThreadType from av.packet import Packet from av.stream import Stream from .codeccontext import VideoCodecContext from .format import VideoFormat from .frame import VideoFrame class VideoStream(Stream): bit_rate: int | None max_bit_rate: int | None bit_rate_tolerance: int sample_aspect_ratio: Fraction | None display_aspect_ratio: Fraction | None codec_context: VideoCodecContext def encode(self, frame: VideoFrame | None = None) -> list[Packet]: ... def encode_lazy(self, frame: VideoFrame | None = None) -> Iterator[Packet]: ... def decode(self, packet: Packet | None = None) -> list[VideoFrame]: ... # from codec context format: VideoFormat thread_count: int thread_type: ThreadType width: int height: int bits_per_coded_sample: int pix_fmt: str | None framerate: Fraction rate: Fraction gop_size: int has_b_frames: bool max_b_frames: int coded_width: int coded_height: int color_range: int color_primaries: int color_trc: int colorspace: int type: Literal["video"] PyAV-14.2.0/av/video/stream.pyx000066400000000000000000000071121475734227400162060ustar00rootroot00000000000000cimport libav as lib from av.packet cimport Packet from av.utils cimport avrational_to_fraction, to_avrational from .frame cimport VideoFrame cdef class VideoStream(Stream): def __repr__(self): return ( f"" ) def __getattr__(self, name): if name in ("framerate", "rate"): raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'") return getattr(self.codec_context, name) cpdef encode(self, VideoFrame frame=None): """ Encode an :class:`.VideoFrame` and return a list of :class:`.Packet`. :rtype: list[Packet] .. seealso:: This is mostly a passthrough to :meth:`.CodecContext.encode`. """ packets = self.codec_context.encode(frame) cdef Packet packet for packet in packets: packet._stream = self packet.ptr.stream_index = self.ptr.index return packets cpdef decode(self, Packet packet=None): """ Decode a :class:`.Packet` and return a list of :class:`.VideoFrame`. :rtype: list[VideoFrame] .. seealso:: This is a passthrough to :meth:`.CodecContext.decode`. """ return self.codec_context.decode(packet) @property def average_rate(self): """ The average frame rate of this video stream. This is calculated when the file is opened by looking at the first few frames and averaging their rate. :type: fractions.Fraction | None """ return avrational_to_fraction(&self.ptr.avg_frame_rate) @property def base_rate(self): """ The base frame rate of this stream. This is calculated as the lowest framerate at which the timestamps of frames can be represented accurately. See :ffmpeg:`AVStream.r_frame_rate` for more. :type: fractions.Fraction | None """ return avrational_to_fraction(&self.ptr.r_frame_rate) @property def guessed_rate(self): """The guessed frame rate of this stream. This is a wrapper around :ffmpeg:`av_guess_frame_rate`, and uses multiple heuristics to decide what is "the" frame rate. :type: fractions.Fraction | None """ # The two NULL arguments aren't used in FFmpeg >= 4.0 cdef lib.AVRational val = lib.av_guess_frame_rate(NULL, self.ptr, NULL) return avrational_to_fraction(&val) @property def sample_aspect_ratio(self): """The guessed sample aspect ratio (SAR) of this stream. This is a wrapper around :ffmpeg:`av_guess_sample_aspect_ratio`, and uses multiple heuristics to decide what is "the" sample aspect ratio. :type: fractions.Fraction | None """ cdef lib.AVRational sar = lib.av_guess_sample_aspect_ratio(self.container.ptr, self.ptr, NULL) return avrational_to_fraction(&sar) @property def display_aspect_ratio(self): """The guessed display aspect ratio (DAR) of this stream. This is calculated from :meth:`.VideoStream.guessed_sample_aspect_ratio`. :type: fractions.Fraction | None """ cdef lib.AVRational dar lib.av_reduce( &dar.num, &dar.den, self.format.width * self.sample_aspect_ratio.num, self.format.height * self.sample_aspect_ratio.den, 1024*1024) return avrational_to_fraction(&dar) PyAV-14.2.0/docs/000077500000000000000000000000001475734227400133645ustar00rootroot00000000000000PyAV-14.2.0/docs/Makefile000066400000000000000000000014321475734227400150240ustar00rootroot00000000000000SPHINXOPTS = BUILDDIR = _build PYAV_PIP ?= pip PIP := $(PYAV_PIP) ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(SPHINXOPTS) . .PHONY: clean html open upload default default: html TEMPLATES := $(wildcard api/*.py development/*.py) RENDERED := $(TEMPLATES:%.py=_build/rst/%.rst) _build/rst/%.rst: %.py $(TAGFILE) $(shell find ../include ../av -name '*.pyx' -or -name '*.pxd') @ mkdir -p $(@D) python $< > $@.tmp mv $@.tmp $@ html: $(RENDERED) $(PIP) install -U sphinx sphinx-copybutton rm -rf $(BUILDDIR) sphinx-build -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html test: PYAV_SKIP_DOXYLINK=1 sphinx-build -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest open: open _build/html/index.html upload: rsync -avxP --delete _build/html/ root@basswood-io.com:/var/www/pyav/docs/develop PyAV-14.2.0/docs/_static/000077500000000000000000000000001475734227400150125ustar00rootroot00000000000000PyAV-14.2.0/docs/_static/custom.css000066400000000000000000000005201475734227400170330ustar00rootroot00000000000000body { text-rendering: optimizeLegibility; } .sphinxsidebarwrapper { overflow: scroll; } .ffmpeg-quicklink { float: right; clear: right; margin: 0; } .ffmpeg-quicklink:before { content: "["; } .ffmpeg-quicklink:after { content: "]"; } .body a, .document a { text-decoration: underline !important; } PyAV-14.2.0/docs/_static/examples/000077500000000000000000000000001475734227400166305ustar00rootroot00000000000000PyAV-14.2.0/docs/_static/examples/numpy/000077500000000000000000000000001475734227400200005ustar00rootroot00000000000000PyAV-14.2.0/docs/_static/examples/numpy/barcode.jpg000066400000000000000000000351631475734227400221110ustar00rootroot00000000000000JFIFC  !"$"$C " }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?Q0?xokIׯU"ĝ=#Y.K;};{{W[E2S+M3}jMҬE3+/@h3h"(K0*=!ҥ+HVGIR)E|zS_bJEH6QҸGjB8ڔFM)_g!U /)sJO/گ@XW!@"^dO(p+|oҭl!*'=*يEJ*KNXU)\=)OڟӱT5U)/ENvD>_y~¥})vJF: Nni6W(=4=[GUCeO,z _*HlJ #ҭe5nTPkm=؛DcҐ8UԆ>VT=4]1RyBGg=(1 "1RaIqүEP1{QU]SJO(zUS G=[*=)6,_(zRǥY+F=Xghm=(ǥYKS~Xxh'ivQaCҐW|O.Q\a!U:9B+Q{ y~rS%O*z{B5qtRB5}/s^\wBʜgA*c+򩦎jRaIڋXuog&Þs=H-R>,q%)\i !SVfNbIy>ԾO thNjԑ@~n?rj>4\|=`毈 8A+Qڜ!!+CjӀQڥQ-R%J})F>re4~c?ʮ lTVE/{Qp*V(DBJQ LIY}^=*D\t 5zvWBoCTgzz'ZDfFgl/CBpM @!-H">Jޑ;jAfG;ˤۃM4KLJ3Ij1V۰f-E)3s4?jpDQjUAO =*Ql>kv;qUdK*>YRn_QTlF}( NXSw|d6Gl7qFM2B4nAbȣJb!?jҋ)BagANHu"G w1HeA~th@?ИN?A84q֘gB%`1ҘM0!q&MFj.=xHI?Eއ#[ӢTN?gWȾwOFy}*9oES?Hw#EƟBMUiP!^*%e1 (ڭ(ܔVbo7;rz:y ?ʕV(aHmǵY,e=)4+*JRy^jp:ʥpK?)V#*ڞ(\4/jaj(OZA9cFOinަ{Pm|kv [׊oE.i&[ץ$p6O"]Wbb[ӄMks/E/xa?n4gJ-!&0_COX;_>4L,p?ʴCMǏ?Ù8=4iiK=(cF^&g=j=(Qf>eH=_iA9cӿ4AO٦#z}(ToO֩) &Qҵ^;Z1^#Zs#4[I/ޚm"}i0R1 il[$SOMSz>.:"_jiY*I/{Ԇ]1RsUJLaf9 MUj=֤&OZ(;iKIƦj 37b jC$4sLɉzO0ni Ia߻͒yt4_cD>>!vdiCOPm?vv%O_S?HVOw;ݩGUނʚ7'_VxjO*oCERQG'r{QƽS5= /خcG0jNn4B4˃'?J4)KҋXKin(8O߆zЍkU$`?CB53R9)ǟ?BޚŶ<.zcJxSUCoJMϞ' 46x@Y&3s<>q{1ANmC1Nik) ιbuVK(W}?*>y }I≾qA_ϼBgbM4"zS=Q:O>;#O_AJteL4:wCVfqGU &d qRD6l ܟJt[_f֩?;횿dŵij},i-E'-cx枷xLW. y'_ʡKO GiBkR }}-KEgYS6U8)'Kˏ/EBaЗ^[ߗZre&\vFqv ??*7Of\fdRM9H4h+E'eU?-bqrcK̍Rd~ T#j~!:ʘtuᮄ>R!{6sͣ.>ᦝvJ 1}??3O!{6s'G=4d75}}4}4{D͞$M f g&OKO}koyG)#B; ҽ+6s~ʘ*qg4gQ**zpOz~SRZKjOOҏhffy*l3u?ʮX?Joiٛ{{RugSjSp[3~TLRoWCt0GQGsYR~TCh[i̝=)"ܿ]lnGOJlJX~U>خCo~T_QR0ݿ瑣͏U{>|0O[Gk)?/h;cEرc~S>Wq }S=SQSߥ/hS+5=S 1=c[)q~c(AwuG}:~9_o{j-v!<c~Ch.{ߐ?4=O?6o.=]on?*_SOռGz>@a?4N'֠iOҟb~pOV4Rfhrk[{h]I<>S}mvag?򟘤&Oԍ?ÙT?Hl|6?D1Y92_У2C} şEÀGf?>2L)G?BCo.Q{}RV9H]"GdԀh#]}mvռs˟?J"{DN{C(Dh/ga$7^c~_T+'+]n,|pf?A>?.@Sn%V݇}h>\gXZC5ZAvQSqNVvU:z'iDe}i[8/ҫ~ݏy1∗gpz.2;{ EΡ=Uuzp)D>±Ρ=J.5* C{G۵ oL">$1tW-u ~翨9ΔCNH~rٿZzj7ٿZ9<PΜ7O]J?4{?1C #ι^w3K~|MhG?soC}{=@QoOf~4 zzalX{}{r>Ύ:_ι_~t}~.F[UX }prtb}W󧭲u ߝ8j^ߝ'N}Sc2Ow~k:?:-FqԺSW<;xy)оB{Χ?iS'Xsߙjw+Cĩ}}$X}k{ƣ|~}?kOf1-țߙMoZ$ ޞ=oKq}t>K>v~|_g_ޞZK=}>iq3VKXncS y?~}PO_z!Z3*F_WWjB:cqʕ_oc\o=/j㇐xuydkl:ƚd'VֿJ>4ۿGգ݇gv|FӿO汾ց >>/F?ӨcySLUv\a?~&?=?~&z0*O#}V}r_ʍږo׬7*5[T߯ڶ?_׬oLAG(;쿕Uߑ4_O׬}__{?'g=_e oe _/QXwaF͟TZ߯ʲL('(;g#[߼*i=$Gi)d?B>>:?I~Ii=$ghqBBSϲ4νkߣQk=$_f3' >L;#O~zI~*p _d _!G)Nf6}6|k]_,oЍ|kUkg\uzxݭ`Pj h)GS/˧kTTzʏhgx5jN_?Ushպ^RuFs<C`k]dҕr:=+`*@UEdf:1?yҵCSUb6,tjTӢn0iPVegǟi`.irlf4`/.AG0fh6 kM]I?*~'1c38VS@َu Q0NޔشJ`ԐycKڕa7ޓ4稭((9ӎ:os`]$x)6Gڇ!4. icǥ?hk{SOmӢH[΃?ٽ:RfE寥>tdSxLcҝ tH,̈я:{dh>b\ti$D?֭2Yt>x]U&b%xW)7v>eקR%'K@~T1p?v@:šڕ?փU"L*VF>XmN3SUա6gg.>:zdX񦶫g7V =(6 ?:Huk0HͲ`ݦV?:CZ~_ִFMqJ4܎ڵ:oֈLgjM]57?GLݿ̛i(: 魭ȿQ E#X︴!삨>}(܈cZeZdyJOz=q)Jb$M0s!))_Ntݨӌl(S(ӗq;S[NܨT7c R`iC T#@w.Lt -sjxeן?(n~V~.e//z_EP>&OR??/(-2>Z_+0LSOۍʀ5WML}Ki~^J?(6*.#XiOEbG_J?)k޿,w֟#px*ܧ\8߭ ?j.GO ~tߵ&s&R}'~j,#L~(\j}d>V.c[ֹEԇBi(,2G^ZQqZV > `)EFH=vs~>vGk>Vh;_G֐FqgV_a՗+2;gX~tèG/\;꫎gT\}Otu4M\dHHzo;Sf^kmDMMIT;F+:L:4;^>ܸ^[Q=UR>wn/#3ޏ:ָu'Qͨ\~U!3kSs}qS>qUZ;U/8OާO_zz*8Z&KGzo^{ϐ\1O_j6jh-FopO̽+km{>ֿZw {Ux=WO~fo#6ޏs.9#j>7ΆթvO}iy}/od2\N^!hbkktMVwk_6^5}7Vߞ=pmmhժ:gyu Inpm?~CYߚq ~X:Yߚ٦\0$h=>팭:kH޿q2jCȟddnykӓGoΟ;rA29ȮJuF??k{6u!֚drڬ:?"͝PA\ڐmO#:B)N"6M?xhBgLd'\ԏ ԛ?+:'^i<:5ݛj/̃O0zH5|z /Z翴O-G2 xή|j-G,)2V@0XNJ$8OƗ,m4ao_֐Z.6CXm{pO0G?ڥ~\,zX;st|ξӕzU;gO_k9=RV;z U@q- J.zRzSRȹ4i?aJ^=)sB])1SG)\Q%7Gސ7{,RU:CM0`ݷZV?"[ҪLG}*OʛLSMf,3ҘXgZfmԟjګnZ7-UY['=2ԊYɚEJ5))XIB5Si2Edje}Z2#f\tݩf:S~_JW*w҇){1EaJsӱN Pg?ʕb2ғq &cFyUhǥC?G)_7S{r<ޟM5piòd֭̓2ZdOo;F8!3c4N:UKD QZڣ`s׭S2hɦS0>ßz#C!Cf\M1ԤJBjd4|)Oi5Wd45'ڪ솑_a>c#ҐKEb~Gӷ=*hHIJc! O!cV0W֫i ֐)V FM1JwB!IJC zSoQ俥'wFKi 2g.+M֗ɓȓo>ϭ;zQ? }iwZ_!ͼѶf.QϯF)i6Q֍L+Fz9~4yLǹ,NyWn/߿5j*簛'1ց0p1ҕG+Ha6K{ v=z{RJ^=E 5,r~=XG*]e!ep4 sIIzQz w -,3ڑ\|J4Fq0 qci'4 Z#&KFj-zRԩ/D"Y\bQWYxR$ֹtBM-3O4#XSwz -XV;R~T3))7/!R1ښtSHڕw~R)P*E ,()er1 =4Q[ԁQ߽*6fOXSRXن* ؜FhÚiR>FlL{>3QU: = }h.}E0UI2[֚]iِ}GHQ}E7zSKj2mO_֐b{R_Z SҘU3Қd zvdKqҐLCO6/ !fɹcR|X޵\s+&OւrˢH8֟(^3)>Xrw+) SҐOW.SMyn-URn-y(ܧ7-e}URo¹gOAUѾp/V((/W((l сPyLT^ee!jIދ& ѿޝTF8Zi5Lxj͡o(0҉h1{4SMR҉)r1pHɥOTĔe.y$r ,oR5"S'V-Iޟ [֍poW'HLCI=4Q 4j!NifC4hdh7}*M FzԨU{ժ$.+.:ԨTոOW%.n\M,l6S/ZHwҩ\dui7qUcwir14}ꇞh:=:43sBUggcKٲ42(+8i>hL=4KZO0z7&?d$Q) .O2I'^-AEƛZa{U36inj2]Bԗ QWg1qZ("FY7 qޘA1Vsd9RMF*2yQ%i֚}j"I>i>zUby,i {) ;Ɛ-TK`Ihɦi Cɤ9%O^M=iX8M$ѷސz}i dsM2SJiǥ1 M擏J? ]n) ;vMƓbqh!4%&M&M)ME/q@ E/P!QPqF)dPqF)xȠތS)3L&hf4+Z+|ߟ󧫌 Vz:Ɣ1CRV\5.zT&E; 7 z(b[ PvzEK)q=J (h# GBE;_h9QW"?:(zpaE.%&.T~EGpܞz9Gp2y~tQOR`MpE&K,lR{co`'@n.ێXAf+*3sf=[9ŕ^-pbn@ ~FZnw >De54#GonF >Io;_.]4`#RкN9Kܬա&d'ܹw6'fP]gI3#rߤԀ~2SLn(+/Mbt >ywP@q#n`,4{ۡ[7u\1鳗]JlǨO[ꘂv/u 炻UCdV-4*mI_1*~3ؐa`ҍY'Ʒ X|P~\"ʜ#F}Q3- - 5ȯzbP㙘aUm#}+:-6GqRzv/A̭S֫ޤ.0,8X[m9 8#*g:-'<$z%,ٖQcT]1/95{45Ű;G\ 9FF.S^7-y1,DJ{7N47C} > >\ѿ~Of<\ '˥v5{q~/ʆ՟4i;>NF}ξ4wԪ>ޑ6ITT6CRtĹiiٰpV_ɠ/R^ŬEc کlPQaQ}얤>]@YT[>=as.t%E\<R{r2FOc?+!,?.N<A/?CPAw_.|r! p+8ۑ3Q׆ԑF+yҌ'.=J^yHAoLe鄃x,Ģtr8䢜Э .iRRjJ2z۴a; y}cG-=a~|fzdA'O,j: Hwx#D;%5'U2sȿ{*UwE>+]»3, ̐HRWLeFH3Q}ИK=dјc & E}ʿN *_ځKU |gd~!W#kG У=/Vhmcz$g&"[ҜvF:ze_u}>=5Ms/?À }Xi{ H7Pv4:Oy ]E},mm'c98b_rhQ!i3ӇZ+_@y; f"I՝&@WQ3Z]5F׃Ti Fbo8;k@1u Sn`JZ!wgJ4#=-j+V1]$';0kȔ Ղg}M8]:_V5\`S_mKF$)ƭSnGhT؍sQWָOW-F$ G ܟ`*0ܰ]9COjwx[+g@~h߹x3e#  hܻ|HIENDB`PyAV-14.2.0/docs/_static/logo.webp000066400000000000000000000100721475734227400166310ustar00rootroot00000000000000RIFF2WEBPVP8X AzALPH) Vk[H@B,A $dvorED۶Z4a~yK?d>%zse軇Ne[HRinVy\IڡWŒH@&q룪:a.8 C|CxlGSV6NտUɪmloePfĥc]'[J'~h2ezWd}F5+1,WYJ>샕#/JJ=RxN@`V5Æj5z7]mٝF{$c_- W. T>"C+%۟^PWQU5thഋ6הe M ˣ'r 6WwX:5fURǵ -)O2.ރ6AaE^-Sxڧp(/hDJv IxfK4Ved *3Q" ;~s0''`h~灍Ž]8;Nœ *3uR9R8\$ V>S,yS|q:ɪԪ9BV < -,rU܄ (3*Jp]2¾d^Z E5@Zq]79̏fv~JX¿A98ذ.K@])Leֻ_v mwUCY P~x73˝ ?OZ.aPK@_ +(GX?c3aTM< |׺6iuq>E$Lhk\?Yhs,eZ2#q Nf 2a9N@m;퐔H˕d-SHU& ;ֹtE#t/Ivd<^SQTT`Ӳt r S &BU76ol cٜƋE5ۛ$#ؽQא]g. m<\XYFQ3bP_\CN Ӭ7%}QAa_k fp?/;-WU"D$! X "5uh\C΋yh֨rafϊڀsD!!0£C|5dmǻBAi^B-Cu g4e 5u朔d ᑫmṔB:Cat"]@-eƖN:{9`#I=ٸٍD\T➅_CΞ~,$S sxԾ ZC SzL:pL9 s kup!F!as?f7޶ˤcniŝ>de]uA UuG]xk2$>ڏ_sK IycKTٶj$Z49L~j> !l JcFv ,C6GM %cNH]$YQA$V51^CGhS٠ G+IIfyI7. G9ܙCHMײ<.ۀ4p?N')m֐9{@* ҊpEN:je(YOzԓF[$.)v'=D/ъw-@ \jnڼ 5vR7DǀT-]|UjtddPN{;ivcpBmc "\TS Dc󒪮ZniRI>3%x\:b'=1Sy=[g (R|JۜNռuY"h,UobF> n&ż 4ZV?_^D'P*U*tZL'za(,ov˪h{61KGn(#gZҸ7Ǯ!3obfyd@W5_5(*L 9YWL(50tr?GmDdG%`ӎ"F@WF$+qY5d 7]_8<'Ɓh:2+'}*M v_UnM_7.R(ϞM2#|~=lb&ت"+o5pzN7VI0%x59LE=\ϒAO\~jg*YFs!;]BK.@,[^LK$oARܓsU۩W44q+1IKZڿxS]ى1;57]M'힔\Q=VFl7.w݂ vAݿ+|?oW9z<]9B2 ]0$sh/Qnı]-.3gVP8 "*B{>HK"  M.'r?kw۠a z=+)'zyh|~~k^ќ@9XYC7ہI/;ԚmE* ElvϺcmDzhXӽp*Q" +.D]_Tt ڊ=xC]ٽ] {f$ Y-}N}P'd^6RmSj(܏#sDs 7Xy >foK6GO{Aw 2\c/I(lwSf{qȼ޴S:*1o+k4ܦtABe6שBؿsgye?W+0=̔|X _$wbԬ%Q[P NG([__~w]^"aqYk6*^2.kquQTY Vu"/ /Jeu!G'ɻ!oot=J@,u=E_n.GļۻPшY+PvXH7ЉVN-V{O`>SE].l[i85 .+ϥH2p22m Bc_-E!@~V15(v Z5%̊B2I]!C_a_ 8w$HS0{FH,wdר󓮪1*o^iWy"A jM5ۗ[07K{$>&= "'J2"ͬF٬;A }Q,yAj;5z&]I0?֒brg|Y]@Z PCFęeZpe zT$ Q1p^أTLyAUCvQ8:Yʀs!i: gBfq/ϐTt߁@s)p㳅-j4Nڂ.e\@j :؍J+PΥOh+bG-*g(ۺ F2X}L f( " 5T  ߦ*Z=PyAV-14.2.0/docs/_themes/000077500000000000000000000000001475734227400150105ustar00rootroot00000000000000PyAV-14.2.0/docs/_themes/pyav/000077500000000000000000000000001475734227400157675ustar00rootroot00000000000000PyAV-14.2.0/docs/_themes/pyav/layout.html000066400000000000000000000004541475734227400201750ustar00rootroot00000000000000 {%- extends "basic/layout.html" %} {% block extrahead %} {% endblock %} {% block relbaritems %}
  • {% endblock %} PyAV-14.2.0/docs/_themes/pyav/theme.conf000066400000000000000000000000311475734227400177320ustar00rootroot00000000000000[theme] inherit = nature PyAV-14.2.0/docs/api/000077500000000000000000000000001475734227400141355ustar00rootroot00000000000000PyAV-14.2.0/docs/api/_globals.rst000066400000000000000000000000541475734227400164500ustar00rootroot00000000000000 Globals ======= .. autofunction:: av.open PyAV-14.2.0/docs/api/attachments.rst000066400000000000000000000001671475734227400172060ustar00rootroot00000000000000 Attachments =========== .. automodule:: av.attachments.stream .. autoclass:: AttachmentStream :members: PyAV-14.2.0/docs/api/audio.rst000066400000000000000000000022111475734227400157640ustar00rootroot00000000000000 Audio ===== Audio Streams ------------- .. automodule:: av.audio.stream .. autoclass:: AudioStream :members: Audio Context ------------- .. automodule:: av.audio.codeccontext .. autoclass:: AudioCodecContext :members: :exclude-members: channel_layout, channels Audio Formats ------------- .. automodule:: av.audio.format .. autoclass:: AudioFormat :members: Audio Layouts ------------- .. automodule:: av.audio.layout .. autoclass:: AudioLayout :members: .. autoclass:: AudioChannel :members: Audio Frames ------------ .. automodule:: av.audio.frame .. autoclass:: AudioFrame :members: :exclude-members: to_nd_array Audio FIFOs ----------- .. automodule:: av.audio.fifo .. autoclass:: AudioFifo :members: :exclude-members: write, read, read_many .. automethod:: write .. automethod:: read .. automethod:: read_many Audio Resamplers ---------------- .. automodule:: av.audio.resampler .. autoclass:: AudioResampler :members: :exclude-members: resample .. automethod:: resample PyAV-14.2.0/docs/api/bitstream.rst000066400000000000000000000002011475734227400166520ustar00rootroot00000000000000 Bitstream Filters ================= .. automodule:: av.bitstream .. autoclass:: BitStreamFilterContext :members: PyAV-14.2.0/docs/api/buffer.rst000066400000000000000000000001311475734227400161330ustar00rootroot00000000000000 Buffers ======= .. automodule:: av.buffer .. autoclass:: Buffer :members: PyAV-14.2.0/docs/api/codec.rst000066400000000000000000000051711475734227400157500ustar00rootroot00000000000000 Codecs ====== Descriptors ----------- .. currentmodule:: av.codec .. automodule:: av.codec .. autoclass:: Codec .. automethod:: Codec.create .. autoattribute:: Codec.is_decoder .. autoattribute:: Codec.is_encoder .. autoattribute:: Codec.descriptor .. autoattribute:: Codec.name .. autoattribute:: Codec.long_name .. autoattribute:: Codec.type .. autoattribute:: Codec.id .. autoattribute:: Codec.frame_rates .. autoattribute:: Codec.audio_rates .. autoattribute:: Codec.video_formats .. autoattribute:: Codec.audio_formats Flags ~~~~~ .. autoattribute:: Codec.properties .. autoclass:: Properties Wraps :ffmpeg:`AVCodecDescriptor.props` (``AV_CODEC_PROP_*``). .. autoattribute:: Codec.capabilities .. autoclass:: Capabilities Wraps :ffmpeg:`AVCodec.capabilities` (``AV_CODEC_CAP_*``). Note that ``ffmpeg -codecs`` prefers the properties versions of ``INTRA_ONLY`` and ``LOSSLESS``. Contexts -------- .. currentmodule:: av.codec.context .. automodule:: av.codec.context .. autoclass:: CodecContext .. autoattribute:: CodecContext.codec .. autoattribute:: CodecContext.options .. automethod:: CodecContext.create .. automethod:: CodecContext.open Attributes ~~~~~~~~~~ .. autoattribute:: CodecContext.is_open .. autoattribute:: CodecContext.is_encoder .. autoattribute:: CodecContext.is_decoder .. autoattribute:: CodecContext.name .. autoattribute:: CodecContext.type .. autoattribute:: CodecContext.profile .. autoattribute:: CodecContext.time_base .. autoattribute:: CodecContext.bit_rate .. autoattribute:: CodecContext.bit_rate_tolerance .. autoattribute:: CodecContext.max_bit_rate .. autoattribute:: CodecContext.thread_count .. autoattribute:: CodecContext.thread_type .. autoattribute:: CodecContext.skip_frame .. autoattribute:: CodecContext.extradata .. autoattribute:: CodecContext.extradata_size Transcoding ~~~~~~~~~~~ .. automethod:: CodecContext.parse .. automethod:: CodecContext.encode .. automethod:: CodecContext.decode .. automethod:: CodecContext.flush_buffers Enums and Flags ~~~~~~~~~~~~~~~ .. autoattribute:: CodecContext.flags .. autoclass:: av.codec.context.Flags .. enumtable:: av.codec.context:Flags :class: av.codec.context:CodecContext .. autoattribute:: CodecContext.flags2 .. autoclass:: av.codec.context.Flags2 .. enumtable:: av.codec.context:Flags2 :class: av.codec.context:CodecContext .. autoclass:: av.codec.context.ThreadType Which multithreading methods to use. Use of FF_THREAD_FRAME will increase decoding delay by one frame per thread, so clients which cannot provide future frames should not use it. .. enumtable:: av.codec.context.ThreadType PyAV-14.2.0/docs/api/container.rst000066400000000000000000000025351475734227400166560ustar00rootroot00000000000000 Containers ========== Generic ------- .. currentmodule:: av.container .. automodule:: av.container .. autoclass:: Container .. attribute:: options .. attribute:: container_options .. attribute:: stream_options .. attribute:: metadata_encoding .. attribute:: metadata_errors .. attribute:: open_timeout .. attribute:: read_timeout Flags ~~~~~ .. attribute:: av.container.Container.flags .. class:: av.container.Flags Wraps :ffmpeg:`AVFormatContext.flags`. .. enumtable:: av.container.core:Flags :class: av.container.core:Container Input Containers ---------------- .. autoclass:: InputContainer :members: Output Containers ----------------- .. autoclass:: OutputContainer :members: Formats ------- .. currentmodule:: av.format .. automodule:: av.format .. autoclass:: ContainerFormat .. autoattribute:: ContainerFormat.name .. autoattribute:: ContainerFormat.long_name .. autoattribute:: ContainerFormat.options .. autoattribute:: ContainerFormat.input .. autoattribute:: ContainerFormat.output .. autoattribute:: ContainerFormat.is_input .. autoattribute:: ContainerFormat.is_output .. autoattribute:: ContainerFormat.extensions Flags ~~~~~ .. autoattribute:: ContainerFormat.flags .. autoclass:: av.format.Flags .. enumtable:: av.format.Flags :class: av.format.ContainerFormat PyAV-14.2.0/docs/api/error.rst000066400000000000000000000032361475734227400160240ustar00rootroot00000000000000Errors ====== .. currentmodule:: av.error .. _error_behaviour: General Behavior ----------------- When PyAV encounters an FFmpeg error, it raises an appropriate exception. FFmpeg has a couple dozen of its own error types which we represent via :ref:`error_classes`. FFmpeg will also return more typical errors such as ``ENOENT`` or ``EAGAIN``, which we do our best to translate to extensions of the builtin exceptions as defined by `PEP 3151 `_. .. _error_classes: Error Exception Classes ----------------------- PyAV raises the typical builtin exceptions within its own codebase, but things get a little more complex when it comes to translating FFmpeg errors. There are two competing ideas that have influenced the final design: 1. We want every exception that originates within FFmpeg to inherit from a common :class:`.FFmpegError` exception; 2. We want to use the builtin exceptions whenever possible. As such, PyAV effectively shadows as much of the builtin exception hierarchy as it requires, extending from both the builtins and from :class:`FFmpegError`. Therefore, an argument error within FFmpeg will raise a ``av.error.ValueError``, which can be caught via either :class:`FFmpegError` or ``ValueError``. All of these exceptions expose the typical ``errno`` and ``strerror`` attributes (even ``ValueError`` which doesn't typically), as well as some PyAV extensions such as :attr:`FFmpegError.log`. All of these exceptions are available on the top-level ``av`` package, e.g.:: try: do_something() except av.FilterNotFoundError: handle_error() .. autoclass:: av.FFmpegError PyAV-14.2.0/docs/api/filter.rst000066400000000000000000000007361475734227400161620ustar00rootroot00000000000000Filters ======= .. automodule:: av.filter.filter .. autoclass:: Filter :members: .. automodule:: av.filter.graph .. autoclass:: Graph :members: .. automodule:: av.filter.context .. autoclass:: FilterContext :members: .. automodule:: av.filter.link .. autoclass:: FilterLink :members: .. automodule:: av.filter.pad .. autoclass:: FilterPad :members: .. autoclass:: FilterContextPad :members: PyAV-14.2.0/docs/api/frame.rst000066400000000000000000000001251475734227400157570ustar00rootroot00000000000000 Frames ====== .. automodule:: av.frame .. autoclass:: Frame :members: PyAV-14.2.0/docs/api/packet.rst000066400000000000000000000001311475734227400161310ustar00rootroot00000000000000 Packets ======= .. automodule:: av.packet .. autoclass:: Packet :members: PyAV-14.2.0/docs/api/plane.rst000066400000000000000000000001251475734227400157640ustar00rootroot00000000000000 Planes ====== .. automodule:: av.plane .. autoclass:: Plane :members: PyAV-14.2.0/docs/api/sidedata.rst000066400000000000000000000004741475734227400164520ustar00rootroot00000000000000 Side Data ========= .. automodule:: av.sidedata.sidedata .. autoclass:: SideData :members: .. autoclass:: av.sidedata.sidedata.Type .. enumtable:: av.sidedata.sidedata.Type Motion Vectors -------------- .. automodule:: av.sidedata.motionvectors .. autoclass:: MotionVectors :members: PyAV-14.2.0/docs/api/stream.rst000066400000000000000000000025551475734227400161710ustar00rootroot00000000000000 Streams ======= Stream collections ------------------ .. currentmodule:: av.container.streams .. autoclass:: StreamContainer Dynamic Slicing ~~~~~~~~~~~~~~~ .. automethod:: StreamContainer.get .. automethod:: StreamContainer.best Typed Collections ~~~~~~~~~~~~~~~~~ These attributes are preferred for readability if you don't need the dynamic capabilities of :meth:`.get`: .. attribute:: StreamContainer.video A tuple of :class:`VideoStream`. .. attribute:: StreamContainer.audio A tuple of :class:`AudioStream`. .. attribute:: StreamContainer.subtitles A tuple of :class:`SubtitleStream`. .. attribute:: StreamContainer.attachments A tuple of :class:`AttachmentStream`. .. attribute:: StreamContainer.data A tuple of :class:`DataStream`. .. attribute:: StreamContainer.other A tuple of :class:`Stream` Streams ------- .. currentmodule:: av.stream .. autoclass:: Stream Basics ~~~~~~ .. autoattribute:: Stream.type .. autoattribute:: Stream.codec_context .. autoattribute:: Stream.id .. autoattribute:: Stream.index Timing ~~~~~~ .. seealso:: :ref:`time` for a discussion of time in general. .. autoattribute:: Stream.time_base .. autoattribute:: Stream.start_time .. autoattribute:: Stream.duration .. autoattribute:: Stream.frames Others ~~~~~~ .. autoattribute:: Stream.profile .. autoattribute:: Stream.language PyAV-14.2.0/docs/api/subtitles.rst000066400000000000000000000006321475734227400167060ustar00rootroot00000000000000 Subtitles =========== .. automodule:: av.subtitles.stream .. autoclass:: SubtitleStream :members: .. automodule:: av.subtitles.subtitle .. autoclass:: SubtitleSet :members: .. autoclass:: Subtitle :members: .. autoclass:: AssSubtitle :members: .. autoclass:: BitmapSubtitle :members: .. autoclass:: BitmapSubtitlePlane :members: PyAV-14.2.0/docs/api/time.rst000066400000000000000000000073601475734227400156330ustar00rootroot00000000000000 .. _time: Time ==== Overview -------- Time is expressed as integer multiples of arbitrary units of time called a ``time_base``. There are different contexts that have different time bases: :class:`.Stream` has :attr:`.Stream.time_base`, :class:`.CodecContext` has :attr:`.CodecContext.time_base`, and :class:`.Container` has :data:`av.TIME_BASE`. .. testsetup:: import av path = av.datasets.curated('pexels/time-lapse-video-of-night-sky-857195.mp4') def get_nth_packet_and_frame(fh, skip): for p in fh.demux(): for f in p.decode(): if not skip: return p, f skip -= 1 .. doctest:: >>> fh = av.open(path) >>> video = fh.streams.video[0] >>> video.time_base Fraction(1, 25) Attributes that represent time on those objects will be in that object's ``time_base``: .. doctest:: >>> video.duration 168 >>> float(video.duration * video.time_base) 6.72 :class:`.Packet` has a :attr:`.Packet.pts` and :attr:`.Packet.dts` ("presentation" and "decode" time stamps), and :class:`.Frame` has a :attr:`.Frame.pts` ("presentation" time stamp). Both have a ``time_base`` attribute, but it defaults to the time base of the object that handles them. For packets that is streams. For frames it is streams when decoding, and codec contexts when encoding (which is strange, but it is what it is). In many cases a stream has a time base of ``1 / frame_rate``, and then its frames have incrementing integers for times (0, 1, 2, etc.). Those frames take place at ``pts * time_base`` or ``0 / frame_rate``, ``1 / frame_rate``, ``2 / frame_rate``, etc.. .. doctest:: >>> p, f = get_nth_packet_and_frame(fh, skip=1) >>> p.time_base Fraction(1, 25) >>> p.dts 1 >>> f.time_base Fraction(1, 25) >>> f.pts 1 For convenience, :attr:`.Frame.time` is a ``float`` in seconds: .. doctest:: >>> f.time 0.04 FFmpeg Internals ---------------- .. note:: Time in FFmpeg is not 100% clear to us (see :ref:`authority_of_docs`). At times the FFmpeg documentation and canonical seeming posts in the forums appear contradictory. We've experimented with it, and what follows is the picture that we are operating under. Both :ffmpeg:`AVStream` and :ffmpeg:`AVCodecContext` have a ``time_base`` member. However, they are used for different purposes, and (this author finds) it is too easy to abstract the concept too far. When there is no ``time_base`` (such as on :ffmpeg:`AVFormatContext`), there is an implicit ``time_base`` of ``1/AV_TIME_BASE``. Encoding ........ For encoding, you (the PyAV developer / FFmpeg "user") must set :ffmpeg:`AVCodecContext.time_base`, ideally to the inverse of the frame rate (or so the library docs say to do if your frame rate is fixed; we're not sure what to do if it is not fixed), and you may set :ffmpeg:`AVStream.time_base` as a hint to the muxer. After you open all the codecs and call :ffmpeg:`avformat_write_header`, the stream time base may change, and you must respect it. We don't know if the codec time base may change, so we will make the safer assumption that it may and respect it as well. You then prepare :ffmpeg:`AVFrame.pts` in :ffmpeg:`AVCodecContext.time_base`. The encoded :ffmpeg:`AVPacket.pts` is simply copied from the frame by the library, and so is still in the codec's time base. You must rescale it to :ffmpeg:`AVStream.time_base` before muxing (as all stream operations assume the packet time is in stream time base). Decoding ........ Everything is in :ffmpeg:`AVStream.time_base` because we don't have to rebase it into codec time base (as it generally seems to be the case that :ffmpeg:`AVCodecContext` doesn't really care about your timing; I wish there was a way to assert this without reading every codec). PyAV-14.2.0/docs/api/utils.rst000066400000000000000000000002061475734227400160250ustar00rootroot00000000000000 Utilities ========= Logging ------- .. automodule:: av.logging :members: Other ----- .. automodule:: av.utils :members: PyAV-14.2.0/docs/api/video.rst000066400000000000000000000043711475734227400160020ustar00rootroot00000000000000Video ===== Video Streams ------------- .. automodule:: av.video.stream .. autoclass:: VideoStream :members: Video Codecs ------------- .. automodule:: av.video.codeccontext .. autoclass:: VideoCodecContext :members: Video Formats ------------- .. automodule:: av.video.format .. autoclass:: VideoFormat :members: .. autoclass:: VideoFormatComponent :members: Video Frames ------------ .. automodule:: av.video.frame .. autoclass:: VideoFrame A single video frame. :param int width: The width of the frame. :param int height: The height of the frame. :param format: The format of the frame. :type format: :class:`VideoFormat` or ``str``. >>> frame = VideoFrame(1920, 1080, 'rgb24') Structural ~~~~~~~~~~ .. autoattribute:: VideoFrame.width .. autoattribute:: VideoFrame.height .. attribute:: VideoFrame.format The :class:`.VideoFormat` of the frame. .. autoattribute:: VideoFrame.planes Types ~~~~~ .. autoattribute:: VideoFrame.key_frame .. autoattribute:: VideoFrame.interlaced_frame .. autoattribute:: VideoFrame.pict_type .. autoclass:: av.video.frame.PictureType Wraps ``AVPictureType`` (``AV_PICTURE_TYPE_*``). .. enumtable:: av.video.frame.PictureType Conversions ~~~~~~~~~~~ .. automethod:: VideoFrame.reformat .. automethod:: VideoFrame.to_rgb .. automethod:: VideoFrame.to_image .. automethod:: VideoFrame.to_ndarray .. automethod:: VideoFrame.from_image .. automethod:: VideoFrame.from_ndarray Video Planes ------------- .. automodule:: av.video.plane .. autoclass:: VideoPlane :members: Video Reformatters ------------------ .. automodule:: av.video.reformatter .. autoclass:: VideoReformatter .. automethod:: reformat Enums ~~~~~ .. autoclass:: av.video.reformatter.Interpolation Wraps the ``SWS_*`` flags. .. enumtable:: av.video.reformatter.Interpolation .. autoclass:: av.video.reformatter.Colorspace Wraps the ``SWS_CS_*`` flags. There is a bit of overlap in these names which comes from FFmpeg and backards compatibility. .. enumtable:: av.video.reformatter.Colorspace .. autoclass:: av.video.reformatter.ColorRange Wraps the ``AVCOL*`` flags. .. enumtable:: av.video.reformatter.ColorRange PyAV-14.2.0/docs/conf.py000066400000000000000000000210171475734227400146640ustar00rootroot00000000000000import os import re import sys import sphinx from docutils import nodes from sphinx.util.docutils import SphinxDirective sys.path.insert(0, os.path.abspath("..")) # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.intersphinx", "sphinx.ext.todo", "sphinx.ext.coverage", "sphinx.ext.viewcode", "sphinx.ext.extlinks", "sphinx.ext.doctest", "sphinx_copybutton", # Add copy button to code blocks ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] source_suffix = ".rst" master_doc = "index" project = "PyAV" copyright = "2025, The PyAV Team" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. about = {} with open("../av/about.py") as fp: exec(fp.read(), about) release = about["__version__"] version = release.split("-")[0] exclude_patterns = ["_build"] pygments_style = "sphinx" # -- Options for HTML output --------------------------------------------------- html_theme = "pyav" html_theme_path = [os.path.abspath(os.path.join(__file__, "..", "_themes"))] # The name of an image file (relative to this directory) to place at the top # of the sidebar. html_logo = "_static/logo.webp" # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. html_favicon = "_static/favicon.png" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] html_theme_options = { "sidebarwidth": "250px", } doctest_global_setup = """ import errno import os import av from av.datasets import fate, fate as fate_suite, curated from tests import common from tests.common import sandboxed as _sandboxed def sandboxed(*args, **kwargs): kwargs['timed'] = True return _sandboxed('docs', *args, **kwargs) _cwd = os.getcwd() here = sandboxed('__cwd__') try: os.makedirs(here) except OSError as e: if e.errno != errno.EEXIST: raise os.chdir(here) video_path = curated('pexels/time-lapse-video-of-night-sky-857195.mp4') """ doctest_global_cleanup = "os.chdir(_cwd)" doctest_test_doctest_blocks = "" extlinks = { "issue": ("https://github.com/PyAV-Org/PyAV/issues/%s", "#%s"), "pr": ("https://github.com/PyAV-Org/PyAV/pull/%s", "#%s"), "gh-user": ("https://github.com/%s", "@%s"), } intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} autodoc_member_order = "bysource" autodoc_default_options = { "undoc-members": True, "show-inheritance": True, } todo_include_todos = True class PyInclude(SphinxDirective): has_content = True def run(self): source = "\n".join(self.content) output = [] def write(*content, sep=" ", end="\n"): output.append(sep.join(map(str, content)) + end) namespace = dict(write=write) exec(compile(source, "", "exec"), namespace, namespace) output = "".join(output).splitlines() self.state_machine.insert_input(output, "blah") return [] # [nodes.literal('hello', repr(content))] def load_entrypoint(name): parts = name.split(":") if len(parts) == 1: parts = name.rsplit(".", 1) mod_name, attrs = parts attrs = attrs.split(".") try: obj = __import__(mod_name, fromlist=["."]) except ImportError as e: print("Error while importing.", (name, mod_name, attrs, e)) raise for attr in attrs: obj = getattr(obj, attr) return obj class EnumTable(SphinxDirective): required_arguments = 1 option_spec = { "class": lambda x: x, } def run(self): cls_ep = self.options.get("class") cls = load_entrypoint(cls_ep) if cls_ep else None enum = load_entrypoint(self.arguments[0]) properties = {} if cls is not None: for name, value in vars(cls).items(): if isinstance(value, property): try: item = value._enum_item except AttributeError: pass else: if isinstance(item, enum): properties[item] = name colwidths = [15, 15, 5, 65] if cls else [15, 5, 75] ncols = len(colwidths) table = nodes.table() tgroup = nodes.tgroup(cols=ncols) table += tgroup for width in colwidths: tgroup += nodes.colspec(colwidth=width) thead = nodes.thead() tgroup += thead tbody = nodes.tbody() tgroup += tbody def makerow(*texts): row = nodes.row() for text in texts: if text is None: continue row += nodes.entry("", nodes.paragraph("", str(text))) return row thead += makerow( f"{cls.__name__} Attribute" if cls else None, f"{enum.__name__} Name", "Flag Value", "Meaning in FFmpeg", ) seen = set() enum_items = [ (name, item) for name, item in vars(enum).items() if isinstance(item, enum) ] for name, item in enum_items: if name.lower() in seen: continue seen.add(name.lower()) try: attr = properties[item] except KeyError: if cls: continue attr = None value = f"0x{item.value:X}" doc = enum.__annotations__.get(name, "---")[1:-1] tbody += makerow(attr, name, value, doc) return [table] def ffmpeg_role(name, rawtext, text, lineno, inliner, options={}, content=[]): """ Custom role for FFmpeg API links. Converts :ffmpeg:`AVSomething` into proper FFmpeg API documentation links. """ base_url = "https://ffmpeg.org/doxygen/7.0/struct{}.html" try: struct_name, member = text.split(".") except Exception: struct_name = None if struct_name is None: fragment = { "avformat_seek_file": "group__lavf__decoding.html#ga3b40fc8d2fda6992ae6ea2567d71ba30", "av_find_best_stream": "avformat_8c.html#a8d4609a8f685ad894c1503ffd1b610b4", "av_frame_make_writable": "group__lavu__frame.html#gadd5417c06f5a6b419b0dbd8f0ff363fd", "avformat_write_header": "group__lavf__encoding.html#ga18b7b10bb5b94c4842de18166bc677cb", "av_guess_frame_rate": "group__lavf__misc.html#ga698e6aa73caa9616851092e2be15875d", "av_guess_sample_aspect_ratio": "group__lavf__misc.html#gafa6fbfe5c1bf6792fd6e33475b6056bd", }.get(text, f"struct{text}.html") url = "https://ffmpeg.org/doxygen/7.0/" + fragment else: fragment = { "AVCodecContext.thread_count": "#aa852b6227d0778b62e9cc4034ad3720c", "AVCodecContext.thread_type": "#a7651614f4309122981d70e06a4b42fcb", "AVCodecContext.skip_frame": "#af869b808363998c80adf7df6a944a5a6", "AVCodecContext.qmin": "#a3f63bc9141e25bf7f1cda0cef7cd4a60", "AVCodecContext.qmax": "#ab015db3b7fcd227193a7c17283914187", "AVCodec.capabilities": "#af51f7ff3dac8b730f46b9713e49a2518", "AVCodecDescriptor.props": "#a9949288403a12812cd6e3892ac45f40f", "AVCodecContext.bits_per_coded_sample": "#a3866500f51fabfa90faeae894c6e955c", "AVFrame.color_range": "#a853afbad220bbc58549b4860732a3aa5", "AVFrame.color_primaries": "#a59a3f830494f2ed1133103a1bc9481e7", "AVFrame.color_trc": "#ab09abb126e3922bc1d010cf044087939", "AVFrame.colorspace": "#a9262c231f1f64869439b4fe587fe1710", }.get(text, f"#{member}") url = base_url.format(struct_name) + fragment node = nodes.reference(rawtext, text, refuri=url, **options) return [node], [] def setup(app): app.add_css_file("custom.css") app.add_role("ffmpeg", ffmpeg_role) app.add_directive("flagtable", EnumTable) app.add_directive("enumtable", EnumTable) app.add_directive("pyinclude", PyInclude) return { "version": "1.0", "parallel_read_safe": True, "parallel_write_safe": True, } PyAV-14.2.0/docs/cookbook/000077500000000000000000000000001475734227400151725ustar00rootroot00000000000000PyAV-14.2.0/docs/cookbook/audio.rst000066400000000000000000000002501475734227400170220ustar00rootroot00000000000000Audio ===== Filters ------- Increase the audio speed by applying the atempo filter. The speed is increased by 2. .. literalinclude:: ../../examples/audio/atempo.py PyAV-14.2.0/docs/cookbook/basics.rst000066400000000000000000000034111475734227400171670ustar00rootroot00000000000000Basics ====== Here are some common things to do without digging too deep into the mechanics. Saving Keyframes ---------------- If you just want to look at keyframes, you can set :attr:`.CodecContext.skip_frame` to speed up the process: .. literalinclude:: ../../examples/basics/save_keyframes.py Remuxing -------- 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. .. literalinclude:: ../../examples/basics/remux.py Parsing ------- 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. .. literalinclude:: ../../examples/basics/parse.py Threading --------- 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. This is faster than no threading, but is not as fast as we can go. 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: .. literalinclude:: ../../examples/basics/thread_type.py On the author's machine, the second pass decodes ~5 times faster. Recording the Screen -------------------- .. literalinclude:: ../../examples/basics/record_screen.py Recording a Facecam ------------------- .. literalinclude:: ../../examples/basics/record_facecam.py PyAV-14.2.0/docs/cookbook/numpy.rst000066400000000000000000000010661475734227400170770ustar00rootroot00000000000000Numpy ===== Video Barcode ------------- 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. See https://moviebarcode.tumblr.com/ for examples from Hollywood movies, and here is an example from a sunset timelapse: .. image:: ../_static/examples/numpy/barcode.jpg The code that created this: .. literalinclude:: ../../examples/numpy/barcode.py Generating Video ---------------- .. literalinclude:: ../../examples/numpy/generate_video.py PyAV-14.2.0/docs/cookbook/subtitles.rst000066400000000000000000000004601475734227400177420ustar00rootroot00000000000000Subtitles ========= Remuxing -------- 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. .. literalinclude:: ../../examples/subtitles/remux.py PyAV-14.2.0/docs/development/000077500000000000000000000000001475734227400157065ustar00rootroot00000000000000PyAV-14.2.0/docs/development/changelog.rst000066400000000000000000000001771475734227400203740ustar00rootroot00000000000000 .. It is all in the other file (that we want at the top-level of the repo). .. _changelog: .. include:: ../../CHANGELOG.rst PyAV-14.2.0/docs/development/contributors.rst000066400000000000000000000000411475734227400211700ustar00rootroot00000000000000 .. include:: ../../AUTHORS.rst PyAV-14.2.0/docs/development/license.rst000066400000000000000000000003701475734227400200620ustar00rootroot00000000000000 .. It is all in the other file (that we want at the top-level of the repo). .. _license: License ======= From `LICENSE.txt `_: .. literalinclude:: ../../LICENSE.txt :language: text PyAV-14.2.0/docs/index.rst000066400000000000000000000042471475734227400152340ustar00rootroot00000000000000**PyAV** Documentation ====================== **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. 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). 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. But where you can't work without it, PyAV is a critical tool. Currently we provide: - ``libavformat``: :class:`containers <.Container>`, audio/video/subtitle :class:`streams <.Stream>`, :class:`packets <.Packet>`; - ``libavdevice`` (by specifying a format to containers); - ``libavcodec``: :class:`.Codec`, :class:`.CodecContext`, :class:`.BitStreamFilterContext`, audio/video :class:`frames <.Frame>`, :class:`data planes <.Plane>`, :class:`subtitles <.Subtitle>`; - ``libavfilter``: :class:`.Filter`, :class:`.Graph`; - ``libswscale``: :class:`.VideoReformatter`; - ``libswresample``: :class:`.AudioResampler`; - and a few more utilities. .. _FFmpeg: https://ffmpeg.org/ Basic Demo ---------- .. testsetup:: path_to_video = common.fate_png() # We don't need a full QT here. .. testcode:: import av av.logging.set_level(av.logging.VERBOSE) container = av.open(path_to_video) for index, frame in enumerate(container.decode(video=0)): frame.to_image().save(f"frame-{index:04d}.jpg") Overview -------- .. toctree:: :glob: :maxdepth: 2 overview/* Cookbook -------- .. toctree:: :glob: :maxdepth: 2 cookbook/* Reference --------- .. toctree:: :glob: :maxdepth: 2 api/* Development ----------- .. toctree:: :glob: :maxdepth: 1 development/* Indices and Tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` PyAV-14.2.0/docs/overview/000077500000000000000000000000001475734227400152325ustar00rootroot00000000000000PyAV-14.2.0/docs/overview/caveats.rst000066400000000000000000000052121475734227400174120ustar00rootroot00000000000000Caveats ======= .. _authority_of_docs: Authority of Documentation -------------------------- 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. 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. Unsupported Features -------------------- 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 `__. Sub-Interpreters ---------------- 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. 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. The two main features that can cause lockups are: 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. 2. Logging. If you have logging enabled (disabled by default), those log messages could cause lockups when using threads. .. _garbage_collection: Garbage Collection ------------------ 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. Until we resolve this issue, you should explicitly call :meth:`.Container.close` or use the container as a context manager:: with av.open(path) as container: # Do stuff with it. .. _FFmpeg: https://ffmpeg.org/ .. _GitHub: https://github.com/PyAV-Org/PyAV PyAV-14.2.0/docs/overview/installation.rst000066400000000000000000000047011475734227400204670ustar00rootroot00000000000000Installation ============ Binary wheels ------------- 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: .. code-block:: bash pip install av Conda ----- Another way to install PyAV is via `conda-forge `_:: conda install av -c conda-forge See the `Conda quick install `_ docs to get started with (mini)Conda. Bring your own FFmpeg --------------------- 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: .. code-block:: bash pip install av --no-binary av PyAV depends upon several libraries from FFmpeg: - ``libavcodec`` - ``libavdevice`` - ``libavfilter`` - ``libavformat`` - ``libavutil`` - ``libswresample`` - ``libswscale`` and a few other tools in general: - ``pkg-config`` - Python's development headers MacOS ^^^^^ On **MacOS**, Homebrew_ saves the day:: brew install ffmpeg pkg-config .. _homebrew: http://brew.sh/ Ubuntu >= 18.04 LTS ^^^^^^^^^^^^^^^^^^^ On **Ubuntu 18.04 LTS** everything can come from the default sources:: # General dependencies sudo apt-get install -y python-dev pkg-config # Library components sudo apt-get install -y \ libavformat-dev libavcodec-dev libavdevice-dev \ libavutil-dev libswscale-dev libswresample-dev libavfilter-dev Windows ^^^^^^^ It is possible to build PyAV on Windows without Conda by installing FFmpeg yourself, e.g. from the `shared and dev packages `_. Unpack them somewhere (like ``C:\ffmpeg``), and then :ref:`tell PyAV where they are located `. Building from the latest source ------------------------------- :: # Get PyAV from GitHub. git clone https://github.com/PyAV-Org/PyAV.git cd PyAV # Prep a virtualenv. source scripts/activate.sh # Optionally build FFmpeg. ./scripts/build-deps # Build PyAV. make On **MacOS** you may have issues with regards to Python expecting gcc but finding clang. Try to export the following before installation:: export ARCHFLAGS=-Wno-error=unused-command-line-argument-hard-error-in-future .. _build_on_windows: On **Windows** you must indicate the location of your FFmpeg, e.g.:: python setup.py build --ffmpeg-dir=C:\ffmpeg PyAV-14.2.0/examples/000077500000000000000000000000001475734227400142525ustar00rootroot00000000000000PyAV-14.2.0/examples/audio/000077500000000000000000000000001475734227400153535ustar00rootroot00000000000000PyAV-14.2.0/examples/audio/atempo.py000066400000000000000000000014621475734227400172150ustar00rootroot00000000000000import av av.logging.set_level(av.logging.VERBOSE) input_file = av.open("input.wav") output_file = av.open("output.wav", mode="w") input_stream = input_file.streams.audio[0] output_stream = output_file.add_stream("pcm_s16le", rate=input_stream.rate) graph = av.filter.Graph() graph.link_nodes( graph.add_abuffer(template=input_stream), graph.add("atempo", "2.0"), graph.add("abuffersink"), ).configure() for frame in input_file.decode(input_stream): graph.push(frame) while True: try: for packet in output_stream.encode(graph.pull()): output_file.mux(packet) except (av.BlockingIOError, av.EOFError): break # Flush the stream for packet in output_stream.encode(None): output_file.mux(packet) input_file.close() output_file.close() PyAV-14.2.0/examples/basics/000077500000000000000000000000001475734227400155165ustar00rootroot00000000000000PyAV-14.2.0/examples/basics/hw_decode.py000066400000000000000000000044361475734227400200200ustar00rootroot00000000000000import os import time import av import av.datasets from av.codec.hwaccel import HWAccel, hwdevices_available # What accelerator to use. # Recommendations: # Windows: # - d3d11va (Direct3D 11) # * available with built-in ffmpeg in PyAV binary wheels, and gives access to # all decoders, but performance may not be as good as vendor native interfaces. # - cuda (NVIDIA NVDEC), qsv (Intel QuickSync) # * may be faster than d3d11va, but requires custom ffmpeg built with those libraries. # Linux (all options require custom FFmpeg): # - vaapi (Intel, AMD) # - cuda (NVIDIA) # Mac: # - videotoolbox # * available with built-in ffmpeg in PyAV binary wheels, and gives access to # all accelerators available on Macs. This is the only option on MacOS. HW_DEVICE = os.environ["HW_DEVICE"] if "HW_DEVICE" in os.environ else None if "TEST_FILE_PATH" in os.environ: test_file_path = os.environ["TEST_FILE_PATH"] else: test_file_path = av.datasets.curated( "pexels/time-lapse-video-of-night-sky-857195.mp4" ) if HW_DEVICE is None: print(f"Please set HW_DEVICE. Options are: {hwdevices_available()}") exit() assert HW_DEVICE in hwdevices_available(), f"{HW_DEVICE} not available." print("Decoding in software (auto threading)...") container = av.open(test_file_path) container.streams.video[0].thread_type = "AUTO" start_time = time.time() frame_count = 0 for packet in container.demux(video=0): for _ in packet.decode(): frame_count += 1 sw_time = time.time() - start_time sw_fps = frame_count / sw_time assert frame_count == container.streams.video[0].frames container.close() print( f"Decoded with software in {sw_time:.2f}s ({sw_fps:.2f} fps).\n" f"Decoding with {HW_DEVICE}" ) hwaccel = HWAccel(device_type=HW_DEVICE, allow_software_fallback=False) # Note the additional argument here. container = av.open(test_file_path, hwaccel=hwaccel) start_time = time.time() frame_count = 0 for packet in container.demux(video=0): for _ in packet.decode(): frame_count += 1 hw_time = time.time() - start_time hw_fps = frame_count / hw_time assert frame_count == container.streams.video[0].frames container.close() print(f"Decoded with {HW_DEVICE} in {hw_time:.2f}s ({hw_fps:.2f} fps).") PyAV-14.2.0/examples/basics/parse.py000066400000000000000000000021061475734227400172010ustar00rootroot00000000000000import os import subprocess import av import av.datasets # We want an H.264 stream in the Annex B byte-stream format. # We haven't exposed bitstream filters yet, so we're gonna use the `ffmpeg` CLI. h264_path = "night-sky.h264" if not os.path.exists(h264_path): subprocess.check_call( [ "ffmpeg", "-i", av.datasets.curated("pexels/time-lapse-video-of-night-sky-857195.mp4"), "-vcodec", "copy", "-an", "-bsf:v", "h264_mp4toannexb", h264_path, ] ) fh = open(h264_path, "rb") codec = av.CodecContext.create("h264", "r") while True: chunk = fh.read(1 << 16) packets = codec.parse(chunk) print("Parsed {} packets from {} bytes:".format(len(packets), len(chunk))) for packet in packets: print(" ", packet) frames = codec.decode(packet) for frame in frames: print(" ", frame) # We wait until the end to bail so that the last empty `buf` flushes # the parser. if not chunk: break PyAV-14.2.0/examples/basics/record_facecam.py000066400000000000000000000017471475734227400210160ustar00rootroot00000000000000import av av.logging.set_level(av.logging.VERBOSE) """ This is written for MacOS. Other platforms will need to init `input_` differently. You may need to change the file "0". Use this command to list all devices: ffmpeg -f avfoundation -list_devices true -i "" """ input_ = av.open( "0", format="avfoundation", container_options={"framerate": "30", "video_size": "1920x1080"}, ) output = av.open("out.mkv", "w") output_stream = output.add_stream("h264", rate=30) output_stream.width = input_.streams.video[0].width output_stream.height = input_.streams.video[0].height output_stream.pix_fmt = "yuv420p" try: while True: try: for frame in input_.decode(video=0): packet = output_stream.encode(frame) output.mux(packet) except av.BlockingIOError: pass except KeyboardInterrupt: print("Recording stopped by user") packet = output_stream.encode(None) output.mux(packet) input_.close() output.close() PyAV-14.2.0/examples/basics/record_screen.py000066400000000000000000000014551475734227400207120ustar00rootroot00000000000000import av av.logging.set_level(av.logging.VERBOSE) """ This is written for MacOS. Other platforms will need a different file, format pair. You may need to change the file "1". Use this command to list all devices: ffmpeg -f avfoundation -list_devices true -i "" """ input_ = av.open("1", format="avfoundation") output = av.open("out.mkv", "w") output_stream = output.add_stream("h264", rate=30) output_stream.width = input_.streams.video[0].width output_stream.height = input_.streams.video[0].height output_stream.pix_fmt = "yuv420p" try: for frame in input_.decode(video=0): packet = output_stream.encode(frame) output.mux(packet) except KeyboardInterrupt: print("Recording stopped by user") packet = output_stream.encode(None) output.mux(packet) input_.close() output.close() PyAV-14.2.0/examples/basics/remux.py000066400000000000000000000013111475734227400172240ustar00rootroot00000000000000import av import av.datasets av.logging.set_level(av.logging.VERBOSE) input_ = av.open(av.datasets.curated("pexels/time-lapse-video-of-night-sky-857195.mp4")) output = av.open("remuxed.mkv", "w") # Make an output stream using the input as a template. This copies the stream # setup from one to the other. in_stream = input_.streams.video[0] out_stream = output.add_stream_from_template(in_stream) for packet in input_.demux(in_stream): print(packet) # We need to skip the "flushing" packets that `demux` generates. if packet.dts is None: continue # We need to assign the packet to the new stream. packet.stream = out_stream output.mux(packet) input_.close() output.close() PyAV-14.2.0/examples/basics/save_keyframes.py000066400000000000000000000006621475734227400211000ustar00rootroot00000000000000import av import av.datasets content = av.datasets.curated("pexels/time-lapse-video-of-night-sky-857195.mp4") with av.open(content) as container: # Signal that we only want to look at keyframes. stream = container.streams.video[0] stream.codec_context.skip_frame = "NONKEY" for i, frame in enumerate(container.decode(stream)): print(frame) frame.to_image().save(f"night-sky.{i:04d}.jpg", quality=80) PyAV-14.2.0/examples/basics/thread_type.py000066400000000000000000000016551475734227400204070ustar00rootroot00000000000000import time import av import av.datasets print("Decoding with default (slice) threading...") container = av.open( av.datasets.curated("pexels/time-lapse-video-of-night-sky-857195.mp4") ) start_time = time.time() for packet in container.demux(): print(packet) for frame in packet.decode(): print(frame) default_time = time.time() - start_time container.close() print("Decoding with auto threading...") container = av.open( av.datasets.curated("pexels/time-lapse-video-of-night-sky-857195.mp4") ) # !!! This is the only difference. container.streams.video[0].thread_type = "AUTO" start_time = time.time() for packet in container.demux(): print(packet) for frame in packet.decode(): print(frame) auto_time = time.time() - start_time container.close() print("Decoded with default threading in {:.2f}s.".format(default_time)) print("Decoded with auto threading in {:.2f}s.".format(auto_time)) PyAV-14.2.0/examples/numpy/000077500000000000000000000000001475734227400154225ustar00rootroot00000000000000PyAV-14.2.0/examples/numpy/barcode.py000066400000000000000000000015471475734227400174020ustar00rootroot00000000000000import numpy as np from PIL import Image import av import av.datasets container = av.open( av.datasets.curated("pexels/time-lapse-video-of-sunset-by-the-sea-854400.mp4") ) container.streams.video[0].thread_type = "AUTO" # Go faster! columns = [] for frame in container.decode(video=0): print(frame) array = frame.to_ndarray(format="rgb24") # Collapse down to a column. column = array.mean(axis=1) # Convert to bytes, as the `mean` turned our array into floats. column = column.clip(0, 255).astype("uint8") # Get us in the right shape for the `hstack` below. column = column.reshape(-1, 1, 3) columns.append(column) # Close the file, free memory container.close() full_array = np.hstack(columns) full_img = Image.fromarray(full_array, "RGB") full_img = full_img.resize((800, 200)) full_img.save("barcode.jpg", quality=85) PyAV-14.2.0/examples/numpy/generate_video.py000066400000000000000000000015251475734227400207570ustar00rootroot00000000000000import numpy as np import av duration = 4 fps = 24 total_frames = duration * fps container = av.open("test.mp4", mode="w") stream = container.add_stream("mpeg4", rate=fps) stream.width = 480 stream.height = 320 stream.pix_fmt = "yuv420p" for frame_i in range(total_frames): img = np.empty((480, 320, 3)) img[:, :, 0] = 0.5 + 0.5 * np.sin(2 * np.pi * (0 / 3 + frame_i / total_frames)) img[:, :, 1] = 0.5 + 0.5 * np.sin(2 * np.pi * (1 / 3 + frame_i / total_frames)) img[:, :, 2] = 0.5 + 0.5 * np.sin(2 * np.pi * (2 / 3 + frame_i / total_frames)) img = np.round(255 * img).astype(np.uint8) frame = av.VideoFrame.from_ndarray(img, format="rgb24") for packet in stream.encode(frame): container.mux(packet) # Flush stream for packet in stream.encode(): container.mux(packet) # Close the file container.close() PyAV-14.2.0/examples/numpy/generate_video_with_pts.py000066400000000000000000000056151475734227400227040ustar00rootroot00000000000000#!/usr/bin/env python3 import colorsys from fractions import Fraction import numpy as np import av (width, height) = (640, 360) total_frames = 20 fps = 30 container = av.open("generate_video_with_pts.mp4", mode="w") stream = container.add_stream("mpeg4", rate=fps) # alibi frame rate stream.width = width stream.height = height stream.pix_fmt = "yuv420p" # ffmpeg time is complicated # more at https://github.com/PyAV-Org/PyAV/blob/main/docs/api/time.rst # our situation is the "encoding" one # this is independent of the "fps" you give above # 1/1000 means milliseconds (and you can use that, no problem) # 1/2 means half a second (would be okay for the delays we use below) # 1/30 means ~33 milliseconds # you should use the least fraction that makes sense for you stream.codec_context.time_base = Fraction(1, fps) # this says when to show the next frame # (increment by how long the current frame will be shown) my_pts = 0 # [seconds] # below we'll calculate that into our chosen time base # we'll keep this frame around to draw on this persistently # you can also redraw into a new object every time but you needn't the_canvas = np.zeros((height, width, 3), dtype=np.uint8) the_canvas[:, :] = (32, 32, 32) # some dark gray background because why not block_w2 = int(0.5 * width / total_frames * 0.75) block_h2 = int(0.5 * height / 4) for frame_i in range(total_frames): # move around the color wheel (hue) nice_color = colorsys.hsv_to_rgb(frame_i / total_frames, 1.0, 1.0) nice_color = (np.array(nice_color) * 255).astype(np.uint8) # draw blocks of a progress bar cx = int(width / total_frames * (frame_i + 0.5)) cy = int(height / 2) the_canvas[cy - block_h2 : cy + block_h2, cx - block_w2 : cx + block_w2] = ( nice_color ) frame = av.VideoFrame.from_ndarray(the_canvas, format="rgb24") # seconds -> counts of time_base frame.pts = int(round(my_pts / stream.codec_context.time_base)) # increment by display time to pre-determine next frame's PTS my_pts += 1.0 if ((frame_i // 3) % 2 == 0) else 0.5 # yes, the last frame has no "duration" because nothing follows it # frames don't have duration, only a PTS for packet in stream.encode(frame): container.mux(packet) # finish it with a blank frame, so the "last" frame actually gets shown for some time # this black frame will probably be shown for 1/fps time # at least, that is the analysis of ffprobe the_canvas[:] = 0 frame = av.VideoFrame.from_ndarray(the_canvas, format="rgb24") frame.pts = int(round(my_pts / stream.codec_context.time_base)) for packet in stream.encode(frame): container.mux(packet) # the time should now be 15.5 + 1/30 = 15.533 # without that last black frame, the real last frame gets shown for 1/30 # so that video would have been 14.5 + 1/30 = 14.533 seconds long # Flush stream for packet in stream.encode(): container.mux(packet) # Close the file container.close() PyAV-14.2.0/examples/subtitles/000077500000000000000000000000001475734227400162705ustar00rootroot00000000000000PyAV-14.2.0/examples/subtitles/remux.py000066400000000000000000000010471475734227400200040ustar00rootroot00000000000000import av av.logging.set_level(av.logging.VERBOSE) input_ = av.open("resources/webvtt.mkv") output = av.open("remuxed.vtt", "w") in_stream = input_.streams.subtitles[0] out_stream = output.add_stream_from_template(in_stream) for packet in input_.demux(in_stream): if packet.dts is None: continue packet.stream = out_stream output.mux(packet) input_.close() output.close() print("Remuxing done") with av.open("remuxed.vtt") as f: for subset in f.decode(subtitles=0): for sub in subset: print(sub.ass) PyAV-14.2.0/include/000077500000000000000000000000001475734227400140575ustar00rootroot00000000000000PyAV-14.2.0/include/libav.pxd000066400000000000000000000015421475734227400156730ustar00rootroot00000000000000include "libavutil/avutil.pxd" include "libavutil/buffer.pxd" include "libavutil/channel_layout.pxd" include "libavutil/dict.pxd" include "libavutil/error.pxd" include "libavutil/frame.pxd" include "libavutil/hwcontext.pxd" include "libavutil/samplefmt.pxd" include "libavutil/motion_vector.pxd" include "libavcodec/avcodec.pxd" include "libavcodec/bsf.pxd" include "libavcodec/hwaccel.pxd" include "libavdevice/avdevice.pxd" include "libavformat/avformat.pxd" include "libswresample/swresample.pxd" include "libswscale/swscale.pxd" include "libavfilter/avfilter.pxd" include "libavfilter/avfiltergraph.pxd" include "libavfilter/buffersink.pxd" include "libavfilter/buffersrc.pxd" cdef extern from "stdio.h" nogil: cdef int snprintf(char *output, int n, const char *format, ...) cdef int vsnprintf(char *output, int n, const char *format, va_list args) PyAV-14.2.0/include/libavcodec/000077500000000000000000000000001475734227400161525ustar00rootroot00000000000000PyAV-14.2.0/include/libavcodec/avcodec.pxd000066400000000000000000000404661475734227400203050ustar00rootroot00000000000000from libc.stdint cimport int8_t, int64_t, uint16_t, uint32_t cdef extern from "libavcodec/codec.h": struct AVCodecTag: pass cdef extern from "libavcodec/codec_id.h": AVCodecID av_codec_get_id(const AVCodecTag *const *tags, uint32_t tag) cdef extern from "libavcodec/packet.h" nogil: AVPacketSideData* av_packet_side_data_new( AVPacketSideData **sides, int *nb_sides, AVPacketSideDataType type, size_t size, int free_opaque ) cdef extern from "libavutil/channel_layout.h": ctypedef enum AVChannelOrder: AV_CHANNEL_ORDER_UNSPEC AV_CHANNEL_ORDER_NATIVE AV_CHANNEL_ORDER_CUSTOM AV_CHANNEL_ORDER_AMBISONIC ctypedef enum AVChannel: AV_CHAN_NONE = -1 AV_CHAN_FRONT_LEFT AV_CHAN_FRONT_RIGHT AV_CHAN_FRONT_CENTER # ... other channel enum values ... ctypedef struct AVChannelCustom: AVChannel id char name[16] void *opaque ctypedef struct AVChannelLayout: AVChannelOrder order int nb_channels uint64_t mask # union: # uint64_t mask # AVChannelCustom *map void *opaque int av_channel_layout_default(AVChannelLayout *ch_layout, int nb_channels) int av_channel_layout_from_mask(AVChannelLayout *channel_layout, uint64_t mask) int av_channel_layout_from_string(AVChannelLayout *channel_layout, const char *str) void av_channel_layout_uninit(AVChannelLayout *channel_layout) int av_channel_layout_copy(AVChannelLayout *dst, const AVChannelLayout *src) int av_channel_layout_describe(const AVChannelLayout *channel_layout, char *buf, size_t buf_size) int av_channel_name(char *buf, size_t buf_size, AVChannel channel_id) int av_channel_description(char *buf, size_t buf_size, AVChannel channel_id) AVChannel av_channel_layout_channel_from_index(AVChannelLayout *channel_layout, unsigned int idx) cdef extern from "libavcodec/avcodec.h" nogil: cdef set pyav_get_available_codecs() cdef int avcodec_version() cdef char* avcodec_configuration() cdef char* avcodec_license() cdef size_t AV_INPUT_BUFFER_PADDING_SIZE cdef int64_t AV_NOPTS_VALUE # AVCodecDescriptor.props cdef enum: AV_CODEC_PROP_INTRA_ONLY AV_CODEC_PROP_LOSSY AV_CODEC_PROP_LOSSLESS AV_CODEC_PROP_REORDER AV_CODEC_PROP_BITMAP_SUB AV_CODEC_PROP_TEXT_SUB # AVCodec.capabilities cdef enum: AV_CODEC_CAP_DRAW_HORIZ_BAND AV_CODEC_CAP_DR1 # AV_CODEC_CAP_HWACCEL AV_CODEC_CAP_DELAY AV_CODEC_CAP_SMALL_LAST_FRAME # AV_CODEC_CAP_HWACCEL_VDPAU AV_CODEC_CAP_SUBFRAMES AV_CODEC_CAP_EXPERIMENTAL AV_CODEC_CAP_CHANNEL_CONF # AV_CODEC_CAP_NEG_LINESIZES AV_CODEC_CAP_FRAME_THREADS AV_CODEC_CAP_SLICE_THREADS AV_CODEC_CAP_PARAM_CHANGE AV_CODEC_CAP_OTHER_THREADS AV_CODEC_CAP_VARIABLE_FRAME_SIZE AV_CODEC_CAP_AVOID_PROBING AV_CODEC_CAP_HARDWARE AV_CODEC_CAP_HYBRID AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE cdef enum: FF_THREAD_FRAME FF_THREAD_SLICE cdef enum: AV_CODEC_FLAG_UNALIGNED AV_CODEC_FLAG_QSCALE AV_CODEC_FLAG_4MV AV_CODEC_FLAG_OUTPUT_CORRUPT AV_CODEC_FLAG_QPEL AV_CODEC_FLAG_DROPCHANGED AV_CODEC_FLAG_RECON_FRAME AV_CODEC_FLAG_COPY_OPAQUE AV_CODEC_FLAG_FRAME_DURATION AV_CODEC_FLAG_PASS1 AV_CODEC_FLAG_PASS2 AV_CODEC_FLAG_LOOP_FILTER AV_CODEC_FLAG_GRAY AV_CODEC_FLAG_PSNR AV_CODEC_FLAG_INTERLACED_DCT AV_CODEC_FLAG_LOW_DELAY AV_CODEC_FLAG_GLOBAL_HEADER AV_CODEC_FLAG_BITEXACT AV_CODEC_FLAG_AC_PRED AV_CODEC_FLAG_INTERLACED_ME AV_CODEC_FLAG_CLOSED_GOP cdef enum: AV_CODEC_FLAG2_FAST AV_CODEC_FLAG2_NO_OUTPUT AV_CODEC_FLAG2_LOCAL_HEADER AV_CODEC_FLAG2_CHUNKS AV_CODEC_FLAG2_IGNORE_CROP AV_CODEC_FLAG2_SHOW_ALL AV_CODEC_FLAG2_EXPORT_MVS AV_CODEC_FLAG2_SKIP_MANUAL AV_CODEC_FLAG2_RO_FLUSH_NOOP cdef enum: AV_PKT_FLAG_KEY AV_PKT_FLAG_CORRUPT AV_PKT_FLAG_DISCARD AV_PKT_FLAG_TRUSTED AV_PKT_FLAG_DISPOSABLE cdef enum: AV_FRAME_FLAG_CORRUPT AV_FRAME_FLAG_KEY AV_FRAME_FLAG_DISCARD AV_FRAME_FLAG_INTERLACED cdef enum: FF_COMPLIANCE_VERY_STRICT FF_COMPLIANCE_STRICT FF_COMPLIANCE_NORMAL FF_COMPLIANCE_UNOFFICIAL FF_COMPLIANCE_EXPERIMENTAL cdef enum: FF_PROFILE_UNKNOWN = -99 cdef enum AVCodecID: AV_CODEC_ID_NONE AV_CODEC_ID_MPEG2VIDEO AV_CODEC_ID_MPEG1VIDEO AV_CODEC_ID_PCM_ALAW AV_CODEC_ID_PCM_BLURAY AV_CODEC_ID_PCM_DVD AV_CODEC_ID_PCM_F16LE AV_CODEC_ID_PCM_F24LE AV_CODEC_ID_PCM_F32BE AV_CODEC_ID_PCM_F32LE AV_CODEC_ID_PCM_F64BE AV_CODEC_ID_PCM_F64LE AV_CODEC_ID_PCM_LXF AV_CODEC_ID_PCM_MULAW AV_CODEC_ID_PCM_S16BE AV_CODEC_ID_PCM_S16BE_PLANAR AV_CODEC_ID_PCM_S16LE AV_CODEC_ID_PCM_S16LE_PLANAR AV_CODEC_ID_PCM_S24BE AV_CODEC_ID_PCM_S24DAUD AV_CODEC_ID_PCM_S24LE AV_CODEC_ID_PCM_S24LE_PLANAR AV_CODEC_ID_PCM_S32BE AV_CODEC_ID_PCM_S32LE AV_CODEC_ID_PCM_S32LE_PLANAR AV_CODEC_ID_PCM_S64BE AV_CODEC_ID_PCM_S64LE AV_CODEC_ID_PCM_S8 AV_CODEC_ID_PCM_S8_PLANAR AV_CODEC_ID_PCM_U16BE AV_CODEC_ID_PCM_U16LE AV_CODEC_ID_PCM_U24BE AV_CODEC_ID_PCM_U24LE AV_CODEC_ID_PCM_U32BE AV_CODEC_ID_PCM_U32LE AV_CODEC_ID_PCM_U8 AV_CODEC_ID_PCM_VIDC cdef enum AVDiscard: AVDISCARD_NONE AVDISCARD_DEFAULT AVDISCARD_NONREF AVDISCARD_BIDIR AVDISCARD_NONINTRA AVDISCARD_NONKEY AVDISCARD_ALL cdef struct AVCodec: char *name char *long_name AVMediaType type AVCodecID id int capabilities AVRational* supported_framerates AVSampleFormat* sample_fmts AVPixelFormat* pix_fmts int* supported_samplerates AVClass *priv_class cdef int av_codec_is_encoder(AVCodec*) cdef int av_codec_is_decoder(AVCodec*) cdef struct AVProfile: int profile char *name cdef struct AVCodecDescriptor: AVCodecID id char *name char *long_name int props char **mime_types AVProfile *profiles AVCodecDescriptor* avcodec_descriptor_get(AVCodecID) cdef struct AVCodecContext: AVClass *av_class AVMediaType codec_type char codec_name[32] unsigned int codec_tag AVCodecID codec_id int flags int flags2 int thread_count int thread_type int profile AVDiscard skip_frame AVFrame* coded_frame void* opaque int bit_rate int bit_rate_tolerance int mb_decision int bits_per_coded_sample int global_quality int compression_level int qmin int qmax int rc_max_rate int rc_min_rate int rc_buffer_size float rc_max_available_vbv_use float rc_min_vbv_overflow_use AVRational framerate AVRational pkt_timebase AVRational time_base int extradata_size uint8_t *extradata int delay AVCodec *codec # Video. int width int height int coded_width int coded_height AVPixelFormat pix_fmt AVPixelFormat sw_pix_fmt AVRational sample_aspect_ratio int gop_size # The number of pictures in a group of pictures, or 0 for intra_only. int max_b_frames int has_b_frames AVColorRange color_range AVColorPrimaries color_primaries AVColorTransferCharacteristic color_trc AVColorSpace colorspace # Audio. AVSampleFormat sample_fmt int sample_rate AVChannelLayout ch_layout int frame_size #: .. todo:: ``get_buffer`` is deprecated for get_buffer2 in newer versions of FFmpeg. int get_buffer(AVCodecContext *ctx, AVFrame *frame) void release_buffer(AVCodecContext *ctx, AVFrame *frame) # Hardware acceleration AVHWAccel *hwaccel AVBufferRef *hw_device_ctx AVPixelFormat (*get_format)(AVCodecContext *s, const AVPixelFormat *fmt) # User Data void *opaque cdef AVCodecContext* avcodec_alloc_context3(AVCodec *codec) cdef void avcodec_free_context(AVCodecContext **ctx) cdef AVClass* avcodec_get_class() cdef AVCodec* avcodec_find_decoder(AVCodecID id) cdef AVCodec* avcodec_find_encoder(AVCodecID id) cdef AVCodec* avcodec_find_decoder_by_name(char *name) cdef AVCodec* avcodec_find_encoder_by_name(char *name) cdef const AVCodec* av_codec_iterate(void **opaque) cdef AVCodecDescriptor* avcodec_descriptor_get (AVCodecID id) cdef AVCodecDescriptor* avcodec_descriptor_get_by_name (char *name) cdef char* avcodec_get_name(AVCodecID id) cdef char* av_get_profile_name(AVCodec *codec, int profile) cdef int avcodec_open2( AVCodecContext *ctx, AVCodec *codec, AVDictionary **options, ) cdef int AV_NUM_DATA_POINTERS cdef enum AVPacketSideDataType: AV_PKT_DATA_PALETTE AV_PKT_DATA_NEW_EXTRADATA AV_PKT_DATA_PARAM_CHANGE AV_PKT_DATA_H263_MB_INFO AV_PKT_DATA_REPLAYGAIN AV_PKT_DATA_DISPLAYMATRIX AV_PKT_DATA_STEREO3D AV_PKT_DATA_AUDIO_SERVICE_TYPE AV_PKT_DATA_QUALITY_STATS AV_PKT_DATA_FALLBACK_TRACK AV_PKT_DATA_CPB_PROPERTIES AV_PKT_DATA_SKIP_SAMPLES AV_PKT_DATA_JP_DUALMONO AV_PKT_DATA_STRINGS_METADATA AV_PKT_DATA_SUBTITLE_POSITION AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL AV_PKT_DATA_WEBVTT_IDENTIFIER AV_PKT_DATA_WEBVTT_SETTINGS AV_PKT_DATA_METADATA_UPDATE AV_PKT_DATA_MPEGTS_STREAM_ID AV_PKT_DATA_MASTERING_DISPLAY_METADATA AV_PKT_DATA_SPHERICAL AV_PKT_DATA_CONTENT_LIGHT_LEVEL AV_PKT_DATA_A53_CC AV_PKT_DATA_ENCRYPTION_INIT_INFO AV_PKT_DATA_ENCRYPTION_INFO AV_PKT_DATA_AFD AV_PKT_DATA_PRFT AV_PKT_DATA_ICC_PROFILE AV_PKT_DATA_DOVI_CONF AV_PKT_DATA_S12M_TIMECODE AV_PKT_DATA_DYNAMIC_HDR10_PLUS AV_PKT_DATA_NB cdef struct AVPacketSideData: uint8_t *data; size_t size; AVPacketSideDataType type; cdef enum AVFrameSideDataType: AV_FRAME_DATA_PANSCAN AV_FRAME_DATA_A53_CC AV_FRAME_DATA_STEREO3D AV_FRAME_DATA_MATRIXENCODING AV_FRAME_DATA_DOWNMIX_INFO AV_FRAME_DATA_REPLAYGAIN AV_FRAME_DATA_DISPLAYMATRIX AV_FRAME_DATA_AFD AV_FRAME_DATA_MOTION_VECTORS AV_FRAME_DATA_SKIP_SAMPLES AV_FRAME_DATA_AUDIO_SERVICE_TYPE AV_FRAME_DATA_MASTERING_DISPLAY_METADATA AV_FRAME_DATA_GOP_TIMECODE AV_FRAME_DATA_SPHERICAL AV_FRAME_DATA_CONTENT_LIGHT_LEVEL AV_FRAME_DATA_ICC_PROFILE AV_FRAME_DATA_S12M_TIMECODE AV_FRAME_DATA_DYNAMIC_HDR_PLUS AV_FRAME_DATA_REGIONS_OF_INTEREST AV_FRAME_DATA_VIDEO_ENC_PARAMS AV_FRAME_DATA_SEI_UNREGISTERED AV_FRAME_DATA_FILM_GRAIN_PARAMS AV_FRAME_DATA_DETECTION_BBOXES AV_FRAME_DATA_DOVI_RPU_BUFFER AV_FRAME_DATA_DOVI_METADATA AV_FRAME_DATA_DYNAMIC_HDR_VIVID AV_FRAME_DATA_AMBIENT_VIEWING_ENVIRONMENT AV_FRAME_DATA_VIDEO_HINT cdef struct AVFrameSideData: AVFrameSideDataType type uint8_t *data int size AVDictionary *metadata # See: http://ffmpeg.org/doxygen/trunk/structAVFrame.html cdef struct AVFrame: uint8_t *data[4] int linesize[4] uint8_t **extended_data int format # Should be AVPixelFormat or AVSampleFormat AVPictureType pict_type int width int height int nb_side_data AVFrameSideData **side_data int nb_samples int sample_rate AVChannelLayout ch_layout int64_t pts int64_t pkt_dts int pkt_size uint8_t **base void *opaque AVBufferRef *opaque_ref AVDictionary *metadata int flags int decode_error_flags AVColorRange color_range AVColorPrimaries color_primaries AVColorTransferCharacteristic color_trc AVColorSpace colorspace cdef AVFrame* avcodec_alloc_frame() cdef struct AVPacket: int64_t pts int64_t dts uint8_t *data int size int stream_index int flags int duration int64_t pos void *opaque AVBufferRef *opaque_ref cdef int avcodec_fill_audio_frame( AVFrame *frame, int nb_channels, AVSampleFormat sample_fmt, uint8_t *buf, int buf_size, int align ) cdef void avcodec_free_frame(AVFrame **frame) cdef AVPacket* av_packet_alloc() cdef void av_packet_free(AVPacket **) cdef int av_new_packet(AVPacket*, int) cdef int av_packet_ref(AVPacket *dst, const AVPacket *src) cdef void av_packet_rescale_ts(AVPacket *pkt, AVRational src_tb, AVRational dst_tb) cdef enum AVSubtitleType: SUBTITLE_NONE SUBTITLE_BITMAP SUBTITLE_TEXT SUBTITLE_ASS cdef struct AVSubtitleRect: int x int y int w int h int nb_colors uint8_t *data[4] int linesize[4] AVSubtitleType type char *text char *ass int flags cdef struct AVSubtitle: uint16_t format uint32_t start_display_time uint32_t end_display_time unsigned int num_rects AVSubtitleRect **rects int64_t pts cdef int avcodec_decode_subtitle2( AVCodecContext *ctx, AVSubtitle *sub, int *done, AVPacket *pkt, ) cdef int avcodec_encode_subtitle( AVCodecContext *avctx, uint8_t *buf, int buf_size, AVSubtitle *sub ) cdef void avsubtitle_free(AVSubtitle*) cdef void avcodec_get_frame_defaults(AVFrame* frame) cdef void avcodec_flush_buffers(AVCodecContext *ctx) # TODO: avcodec_default_get_buffer is deprecated for avcodec_default_get_buffer2 in newer versions of FFmpeg cdef int avcodec_default_get_buffer(AVCodecContext *ctx, AVFrame *frame) cdef void avcodec_default_release_buffer(AVCodecContext *ctx, AVFrame *frame) # === New-style Transcoding cdef int avcodec_send_packet(AVCodecContext *avctx, AVPacket *packet) cdef int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame) cdef int avcodec_send_frame(AVCodecContext *avctx, AVFrame *frame) cdef int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt) # === Parsers cdef struct AVCodecParser: int codec_ids[5] cdef AVCodecParser* av_parser_next(AVCodecParser *c) cdef struct AVCodecParserContext: pass cdef AVCodecParserContext *av_parser_init(int codec_id) cdef int av_parser_parse2( AVCodecParserContext *s, AVCodecContext *avctx, uint8_t **poutbuf, int *poutbuf_size, const uint8_t *buf, int buf_size, int64_t pts, int64_t dts, int64_t pos ) cdef int av_parser_change( AVCodecParserContext *s, AVCodecContext *avctx, uint8_t **poutbuf, int *poutbuf_size, const uint8_t *buf, int buf_size, int keyframe ) cdef void av_parser_close(AVCodecParserContext *s) cdef struct AVCodecParameters: AVMediaType codec_type AVCodecID codec_id AVPacketSideData *coded_side_data int nb_coded_side_data uint8_t *extradata int extradata_size cdef int avcodec_parameters_copy( AVCodecParameters *dst, const AVCodecParameters *src ) cdef int avcodec_parameters_from_context( AVCodecParameters *par, const AVCodecContext *codec, ) cdef int avcodec_parameters_to_context( AVCodecContext *codec, const AVCodecParameters *par ) PyAV-14.2.0/include/libavcodec/bsf.pxd000066400000000000000000000015761475734227400174520ustar00rootroot00000000000000 cdef extern from "libavcodec/bsf.h" nogil: cdef struct AVBitStreamFilter: const char *name AVCodecID *codec_ids cdef struct AVCodecParameters: pass cdef struct AVBSFContext: const AVBitStreamFilter *filter const AVCodecParameters *par_in const AVCodecParameters *par_out cdef const AVBitStreamFilter* av_bsf_get_by_name(const char *name) cdef int av_bsf_list_parse_str( const char *str, AVBSFContext **bsf ) cdef int av_bsf_init(AVBSFContext *ctx) cdef void av_bsf_free(AVBSFContext **ctx) cdef AVBitStreamFilter* av_bsf_iterate(void **opaque) cdef int av_bsf_send_packet( AVBSFContext *ctx, AVPacket *pkt ) cdef int av_bsf_receive_packet( AVBSFContext *ctx, AVPacket *pkt ) cdef void av_bsf_flush( AVBSFContext *ctx ) PyAV-14.2.0/include/libavcodec/hwaccel.pxd000066400000000000000000000012171475734227400202760ustar00rootroot00000000000000cdef extern from "libavcodec/avcodec.h" nogil: cdef enum: AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX, AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX, AV_CODEC_HW_CONFIG_METHOD_INTERNAL, AV_CODEC_HW_CONFIG_METHOD_AD_HOC, cdef struct AVCodecHWConfig: AVPixelFormat pix_fmt int methods AVHWDeviceType device_type cdef const AVCodecHWConfig* avcodec_get_hw_config(const AVCodec *codec, int index) cdef enum: AV_HWACCEL_CODEC_CAP_EXPERIMENTAL cdef struct AVHWAccel: char *name AVMediaType type AVCodecID id AVPixelFormat pix_fmt int capabilities PyAV-14.2.0/include/libavdevice/000077500000000000000000000000001475734227400163345ustar00rootroot00000000000000PyAV-14.2.0/include/libavdevice/avdevice.pxd000066400000000000000000000007211475734227400206370ustar00rootroot00000000000000 cdef extern from "libavdevice/avdevice.h" nogil: cdef int avdevice_version() cdef char* avdevice_configuration() cdef char* avdevice_license() void avdevice_register_all() AVInputFormat * av_input_audio_device_next(AVInputFormat *d) AVInputFormat * av_input_video_device_next(AVInputFormat *d) AVOutputFormat * av_output_audio_device_next(AVOutputFormat *d) AVOutputFormat * av_output_video_device_next(AVOutputFormat *d) PyAV-14.2.0/include/libavfilter/000077500000000000000000000000001475734227400163625ustar00rootroot00000000000000PyAV-14.2.0/include/libavfilter/avfilter.pxd000066400000000000000000000053271475734227400207220ustar00rootroot00000000000000 cdef extern from "libavfilter/avfilter.h" nogil: """ #if (LIBAVFILTER_VERSION_INT >= 525156) // avfilter_filter_pad_count is available since version 8.3.100 of libavfilter (FFmpeg 5.0) #define _avfilter_get_num_pads(filter, is_output, pads) (avfilter_filter_pad_count(filter, is_output)) #else // avfilter_filter_pad_count has been deprecated as of version 8.3.100 of libavfilter (FFmpeg 5.0) #define _avfilter_get_num_pads(filter, is_output, pads) (avfilter_pad_count(pads)) #endif """ cdef int avfilter_version() cdef char* avfilter_configuration() cdef char* avfilter_license() cdef struct AVFilterPad: # This struct is opaque. pass const char* avfilter_pad_get_name(const AVFilterPad *pads, int index) AVMediaType avfilter_pad_get_type(const AVFilterPad *pads, int index) int pyav_get_num_pads "_avfilter_get_num_pads" (const AVFilter *filter, int is_output, const AVFilterPad *pads) cdef struct AVFilter: AVClass *priv_class const char *name const char *description const int flags const AVFilterPad *inputs const AVFilterPad *outputs int (*process_command)(AVFilterContext *, const char *cmd, const char *arg, char *res, int res_len, int flags) cdef enum: AVFILTER_FLAG_DYNAMIC_INPUTS AVFILTER_FLAG_DYNAMIC_OUTPUTS AVFILTER_FLAG_SLICE_THREADS AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL cdef AVFilter* avfilter_get_by_name(const char *name) cdef const AVFilter* av_filter_iterate(void **opaque) cdef struct AVFilterLink # Defined later. cdef struct AVFilterContext: AVClass *av_class AVFilter *filter char *name unsigned int nb_inputs AVFilterPad *input_pads AVFilterLink **inputs unsigned int nb_outputs AVFilterPad *output_pads AVFilterLink **outputs cdef int avfilter_init_str(AVFilterContext *ctx, const char *args) cdef int avfilter_init_dict(AVFilterContext *ctx, AVDictionary **options) cdef void avfilter_free(AVFilterContext*) cdef AVClass* avfilter_get_class() cdef struct AVFilterLink: AVFilterContext *src AVFilterPad *srcpad AVFilterContext *dst AVFilterPad *dstpad AVMediaType Type int w int h AVRational sample_aspect_ratio uint64_t channel_layout int sample_rate int format AVRational time_base # custom cdef set pyav_get_available_filters() cdef extern from "libavfilter/buffersink.h" nogil: cdef void av_buffersink_set_frame_size(AVFilterContext *ctx, unsigned frame_size) PyAV-14.2.0/include/libavfilter/avfiltergraph.pxd000066400000000000000000000023761475734227400217450ustar00rootroot00000000000000 cdef extern from "libavfilter/avfilter.h" nogil: cdef struct AVFilterGraph: int nb_filters AVFilterContext **filters cdef struct AVFilterInOut: char *name AVFilterContext *filter_ctx int pad_idx AVFilterInOut *next cdef AVFilterGraph* avfilter_graph_alloc() cdef void avfilter_graph_free(AVFilterGraph **ptr) cdef int avfilter_graph_parse2( AVFilterGraph *graph, const char *filter_str, AVFilterInOut **inputs, AVFilterInOut **outputs ) cdef AVFilterContext* avfilter_graph_alloc_filter( AVFilterGraph *graph, const AVFilter *filter, const char *name ) cdef int avfilter_graph_create_filter( AVFilterContext **filt_ctx, AVFilter *filt, const char *name, const char *args, void *opaque, AVFilterGraph *graph_ctx ) cdef int avfilter_link( AVFilterContext *src, unsigned int srcpad, AVFilterContext *dst, unsigned int dstpad ) cdef int avfilter_graph_config(AVFilterGraph *graph, void *logctx) cdef char* avfilter_graph_dump(AVFilterGraph *graph, const char *options) cdef void avfilter_inout_free(AVFilterInOut **inout_list) PyAV-14.2.0/include/libavfilter/buffersink.pxd000066400000000000000000000002201475734227400212270ustar00rootroot00000000000000cdef extern from "libavfilter/buffersink.h" nogil: int av_buffersink_get_frame( AVFilterContext *ctx, AVFrame *frame ) PyAV-14.2.0/include/libavfilter/buffersrc.pxd000066400000000000000000000002261475734227400210600ustar00rootroot00000000000000cdef extern from "libavfilter/buffersrc.h" nogil: int av_buffersrc_write_frame( AVFilterContext *ctx, const AVFrame *frame ) PyAV-14.2.0/include/libavformat/000077500000000000000000000000001475734227400163655ustar00rootroot00000000000000PyAV-14.2.0/include/libavformat/avformat.pxd000066400000000000000000000202711475734227400207230ustar00rootroot00000000000000from libc.stdint cimport int64_t, uint64_t cdef extern from "libavformat/avformat.h" nogil: cdef int avformat_version() cdef char* avformat_configuration() cdef char* avformat_license() cdef void avformat_network_init() cdef int64_t INT64_MIN cdef int AV_TIME_BASE cdef int AVSEEK_FLAG_BACKWARD cdef int AVSEEK_FLAG_BYTE cdef int AVSEEK_FLAG_ANY cdef int AVSEEK_FLAG_FRAME cdef int AVIO_FLAG_WRITE cdef enum AVMediaType: AVMEDIA_TYPE_UNKNOWN AVMEDIA_TYPE_VIDEO AVMEDIA_TYPE_AUDIO AVMEDIA_TYPE_DATA AVMEDIA_TYPE_SUBTITLE AVMEDIA_TYPE_ATTACHMENT AVMEDIA_TYPE_NB cdef struct AVStream: int index int id int disposition AVCodecParameters *codecpar AVRational time_base int64_t start_time int64_t duration int64_t nb_frames int64_t cur_dts AVDictionary *metadata AVRational avg_frame_rate AVRational r_frame_rate AVRational sample_aspect_ratio # http://ffmpeg.org/doxygen/trunk/structAVIOContext.html cdef struct AVIOContext: unsigned char* buffer int buffer_size int write_flag int direct int seekable int max_packet_size void *opaque # http://ffmpeg.org/doxygen/trunk/structAVIOInterruptCB.html cdef struct AVIOInterruptCB: int (*callback)(void*) void *opaque cdef int AVIO_FLAG_DIRECT cdef int AVIO_SEEKABLE_NORMAL cdef int SEEK_SET cdef int SEEK_CUR cdef int SEEK_END cdef int AVSEEK_SIZE cdef AVIOContext* avio_alloc_context( unsigned char *buffer, int buffer_size, int write_flag, void *opaque, int(*read_packet)(void *opaque, uint8_t *buf, int buf_size), int(*write_packet)(void *opaque, const uint8_t *buf, int buf_size), int64_t(*seek)(void *opaque, int64_t offset, int whence) ) # http://ffmpeg.org/doxygen/trunk/structAVInputFormat.html cdef struct AVInputFormat: const char *name const char *long_name const char *extensions int flags # const AVCodecTag* const *codec_tag const AVClass *priv_class cdef struct AVProbeData: unsigned char *buf int buf_size const char *filename cdef AVInputFormat* av_probe_input_format( AVProbeData *pd, int is_opened ) # http://ffmpeg.org/doxygen/trunk/structAVOutputFormat.html cdef struct AVOutputFormat: const char *name const char *long_name const char *extensions AVCodecID video_codec AVCodecID audio_codec AVCodecID subtitle_codec int flags # const AVCodecTag* const *codec_tag const AVClass *priv_class int avformat_query_codec(const AVOutputFormat *oformat, AVCodecID codec_id, int std_compliance) # AVInputFormat.flags and AVOutputFormat.flags cdef enum: AVFMT_NOFILE AVFMT_NEEDNUMBER AVFMT_SHOW_IDS AVFMT_GLOBALHEADER AVFMT_NOTIMESTAMPS AVFMT_GENERIC_INDEX AVFMT_TS_DISCONT AVFMT_VARIABLE_FPS AVFMT_NODIMENSIONS AVFMT_NOSTREAMS AVFMT_NOBINSEARCH AVFMT_NOGENSEARCH AVFMT_NO_BYTE_SEEK AVFMT_ALLOW_FLUSH AVFMT_TS_NONSTRICT AVFMT_TS_NEGATIVE AVFMT_SEEK_TO_PTS # AVFormatContext.flags cdef enum: AVFMT_FLAG_GENPTS AVFMT_FLAG_IGNIDX AVFMT_FLAG_NONBLOCK AVFMT_FLAG_IGNDTS AVFMT_FLAG_NOFILLIN AVFMT_FLAG_NOPARSE AVFMT_FLAG_NOBUFFER AVFMT_FLAG_CUSTOM_IO AVFMT_FLAG_DISCARD_CORRUPT AVFMT_FLAG_FLUSH_PACKETS AVFMT_FLAG_BITEXACT AVFMT_FLAG_SORT_DTS AVFMT_FLAG_FAST_SEEK AVFMT_FLAG_SHORTEST AVFMT_FLAG_AUTO_BSF cdef int av_probe_input_buffer( AVIOContext *pb, AVInputFormat **fmt, const char *filename, void *logctx, unsigned int offset, unsigned int max_probe_size ) cdef int av_find_best_stream( AVFormatContext *ic, AVMediaType type, int wanted_stream_nb, int related_stream, AVCodec **decoder_ret, int flags ) cdef AVInputFormat* av_find_input_format(const char *name) # http://ffmpeg.org/doxygen/trunk/structAVFormatContext.html cdef struct AVFormatContext: # Streams. unsigned int nb_streams AVStream **streams AVInputFormat *iformat AVOutputFormat *oformat AVIOContext *pb AVIOInterruptCB interrupt_callback AVDictionary *metadata char filename int64_t start_time int64_t duration int bit_rate int flags int64_t max_analyze_duration AVCodecID audio_codec_id void *opaque int (*io_open)( AVFormatContext *s, AVIOContext **pb, const char *url, int flags, AVDictionary **options ) int (*io_close2)( AVFormatContext *s, AVIOContext *pb ) cdef AVFormatContext* avformat_alloc_context() # .. c:function:: avformat_open_input(...) # # Options are passed via :func:`av.open`. # # .. seealso:: FFmpeg's docs: :ffmpeg:`avformat_open_input` # cdef int avformat_open_input( AVFormatContext **ctx, # NULL will allocate for you. char *filename, AVInputFormat *format, # Can be NULL. AVDictionary **options # Can be NULL. ) cdef int avformat_close_input(AVFormatContext **ctx) # .. c:function:: avformat_write_header(...) # # Options are passed via :func:`av.open`; called in # :meth:`av.container.OutputContainer.start_encoding`. # # .. seealso:: FFmpeg's docs: :ffmpeg:`avformat_write_header` # cdef int avformat_write_header( AVFormatContext *ctx, AVDictionary **options # Can be NULL ) cdef int av_write_trailer(AVFormatContext *ctx) cdef int av_interleaved_write_frame( AVFormatContext *ctx, AVPacket *pkt ) cdef int av_write_frame( AVFormatContext *ctx, AVPacket *pkt ) cdef int avio_open( AVIOContext **s, char *url, int flags ) cdef int64_t avio_size( AVIOContext *s ) cdef AVOutputFormat* av_guess_format( char *short_name, char *filename, char *mime_type ) cdef int avformat_query_codec( AVOutputFormat *ofmt, AVCodecID codec_id, int std_compliance ) cdef void avio_flush(AVIOContext *s) cdef int avio_close(AVIOContext *s) cdef int avio_closep(AVIOContext **s) cdef int avformat_find_stream_info( AVFormatContext *ctx, AVDictionary **options, # Can be NULL. ) cdef AVStream* avformat_new_stream( AVFormatContext *ctx, AVCodec *c ) cdef int avformat_alloc_output_context2( AVFormatContext **ctx, AVOutputFormat *oformat, char *format_name, char *filename ) cdef int avformat_free_context(AVFormatContext *ctx) cdef AVClass* avformat_get_class() cdef void av_dump_format( AVFormatContext *ctx, int index, char *url, int is_output, ) cdef int av_read_frame( AVFormatContext *ctx, AVPacket *packet, ) cdef int av_seek_frame( AVFormatContext *ctx, int stream_index, int64_t timestamp, int flags ) cdef int avformat_seek_file( AVFormatContext *ctx, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags ) cdef AVRational av_guess_frame_rate( AVFormatContext *ctx, AVStream *stream, AVFrame *frame ) cdef AVRational av_guess_sample_aspect_ratio( AVFormatContext *ctx, AVStream *stream, AVFrame *frame ) cdef const AVInputFormat* av_demuxer_iterate(void **opaque) cdef const AVOutputFormat* av_muxer_iterate(void **opaque) # custom cdef set pyav_get_available_formats() PyAV-14.2.0/include/libavutil/000077500000000000000000000000001475734227400160525ustar00rootroot00000000000000PyAV-14.2.0/include/libavutil/avutil.pxd000066400000000000000000000237011475734227400200760ustar00rootroot00000000000000from libc.stdint cimport int64_t, uint8_t, uint64_t, int32_t cdef extern from "libavutil/mathematics.h" nogil: pass cdef extern from "libavutil/display.h" nogil: cdef double av_display_rotation_get(const int32_t matrix[9]) cdef extern from "libavutil/rational.h" nogil: cdef int av_reduce(int *dst_num, int *dst_den, int64_t num, int64_t den, int64_t max) cdef extern from "libavutil/avutil.h" nogil: cdef const char* av_version_info() cdef int avutil_version() cdef char* avutil_configuration() cdef char* avutil_license() cdef enum AVPictureType: AV_PICTURE_TYPE_NONE AV_PICTURE_TYPE_I AV_PICTURE_TYPE_P AV_PICTURE_TYPE_B AV_PICTURE_TYPE_S AV_PICTURE_TYPE_SI AV_PICTURE_TYPE_SP AV_PICTURE_TYPE_BI cdef enum AVPixelFormat: AV_PIX_FMT_NONE AV_PIX_FMT_YUV420P AV_PIX_FMT_RGBA AV_PIX_FMT_RGB24 PIX_FMT_RGB24 PIX_FMT_RGBA cdef enum AVRounding: AV_ROUND_ZERO AV_ROUND_INF AV_ROUND_DOWN AV_ROUND_UP AV_ROUND_NEAR_INF # This is nice, but only in FFMpeg: # AV_ROUND_PASS_MINMAX cdef enum AVColorSpace: AVCOL_SPC_RGB AVCOL_SPC_BT709 AVCOL_SPC_UNSPECIFIED AVCOL_SPC_RESERVED AVCOL_SPC_FCC AVCOL_SPC_BT470BG AVCOL_SPC_SMPTE170M AVCOL_SPC_SMPTE240M AVCOL_SPC_YCOCG AVCOL_SPC_BT2020_NCL AVCOL_SPC_BT2020_CL AVCOL_SPC_NB cdef enum AVColorRange: AVCOL_RANGE_UNSPECIFIED AVCOL_RANGE_MPEG AVCOL_RANGE_JPEG AVCOL_RANGE_NB cdef enum AVColorPrimaries: AVCOL_PRI_RESERVED0 AVCOL_PRI_BT709 AVCOL_PRI_UNSPECIFIED AVCOL_PRI_RESERVED AVCOL_PRI_BT470M AVCOL_PRI_BT470BG AVCOL_PRI_SMPTE170M AVCOL_PRI_SMPTE240M AVCOL_PRI_FILM AVCOL_PRI_BT2020 AVCOL_PRI_SMPTE428 AVCOL_PRI_SMPTEST428_1 AVCOL_PRI_SMPTE431 AVCOL_PRI_SMPTE432 AVCOL_PRI_EBU3213 AVCOL_PRI_JEDEC_P22 AVCOL_PRI_NB cdef enum AVColorTransferCharacteristic: AVCOL_TRC_RESERVED0 AVCOL_TRC_BT709 AVCOL_TRC_UNSPECIFIED AVCOL_TRC_RESERVED AVCOL_TRC_GAMMA22 AVCOL_TRC_GAMMA28 AVCOL_TRC_SMPTE170M AVCOL_TRC_SMPTE240M AVCOL_TRC_LINEAR AVCOL_TRC_LOG AVCOL_TRC_LOG_SQRT AVCOL_TRC_IEC61966_2_4 AVCOL_TRC_BT1361_ECG AVCOL_TRC_IEC61966_2_1 AVCOL_TRC_BT2020_10 AVCOL_TRC_BT2020_12 AVCOL_TRC_SMPTE2084 AVCOL_TRC_SMPTEST2084 AVCOL_TRC_SMPTE428 AVCOL_TRC_SMPTEST428_1 AVCOL_TRC_ARIB_STD_B67 AVCOL_TRC_NB cdef double M_PI cdef void* av_malloc(size_t size) cdef void *av_calloc(size_t nmemb, size_t size) cdef void *av_realloc(void *ptr, size_t size) cdef void av_freep(void *ptr) cdef int av_get_bytes_per_sample(AVSampleFormat sample_fmt) cdef int av_samples_get_buffer_size( int *linesize, int nb_channels, int nb_samples, AVSampleFormat sample_fmt, int align ) # See: http://ffmpeg.org/doxygen/trunk/structAVRational.html ctypedef struct AVRational: int num int den cdef AVRational AV_TIME_BASE_Q # Rescales from one time base to another cdef int64_t av_rescale_q( int64_t a, # time stamp AVRational bq, # source time base AVRational cq # target time base ) # Rescale a 64-bit integer with specified rounding. # A simple a*b/c isn't possible as it can overflow cdef int64_t av_rescale_rnd( int64_t a, int64_t b, int64_t c, int r # should be AVRounding, but then we can't use bitwise logic. ) cdef int64_t av_rescale_q_rnd( int64_t a, AVRational bq, AVRational cq, int r # should be AVRounding, but then we can't use bitwise logic. ) cdef int64_t av_rescale( int64_t a, int64_t b, int64_t c ) cdef char* av_strdup(char *s) cdef int av_opt_set_int( void *obj, char *name, int64_t value, int search_flags ) cdef const char* av_get_media_type_string(AVMediaType media_type) cdef extern from "libavutil/pixdesc.h" nogil: # See: http://ffmpeg.org/doxygen/trunk/structAVComponentDescriptor.html cdef struct AVComponentDescriptor: unsigned int plane unsigned int step unsigned int offset unsigned int shift unsigned int depth cdef enum AVPixFmtFlags: AV_PIX_FMT_FLAG_BE AV_PIX_FMT_FLAG_PAL AV_PIX_FMT_FLAG_BITSTREAM AV_PIX_FMT_FLAG_HWACCEL AV_PIX_FMT_FLAG_PLANAR AV_PIX_FMT_FLAG_RGB AV_PIX_FMT_FLAG_PSEUDOPAL AV_PIX_FMT_FLAG_ALPHA AV_PIX_FMT_FLAG_BAYER AV_PIX_FMT_FLAG_FLOAT # See: http://ffmpeg.org/doxygen/trunk/structAVPixFmtDescriptor.html cdef struct AVPixFmtDescriptor: const char *name uint8_t nb_components uint8_t log2_chroma_w uint8_t log2_chroma_h uint8_t flags AVComponentDescriptor comp[4] cdef AVPixFmtDescriptor* av_pix_fmt_desc_get(AVPixelFormat pix_fmt) cdef AVPixFmtDescriptor* av_pix_fmt_desc_next(AVPixFmtDescriptor *prev) cdef char * av_get_pix_fmt_name(AVPixelFormat pix_fmt) cdef AVPixelFormat av_get_pix_fmt(char* name) int av_get_bits_per_pixel(AVPixFmtDescriptor *pixdesc) int av_get_padded_bits_per_pixel(AVPixFmtDescriptor *pixdesc) cdef extern from "libavutil/channel_layout.h" nogil: # Layouts. cdef uint64_t av_get_channel_layout(char* name) cdef int av_get_channel_layout_nb_channels(uint64_t channel_layout) cdef int64_t av_get_default_channel_layout(int nb_channels) # Channels. cdef uint64_t av_channel_layout_extract_channel(uint64_t layout, int index) cdef char* av_get_channel_name(uint64_t channel) cdef char* av_get_channel_description(uint64_t channel) cdef extern from "libavutil/audio_fifo.h" nogil: cdef struct AVAudioFifo: pass cdef void av_audio_fifo_free(AVAudioFifo *af) cdef AVAudioFifo* av_audio_fifo_alloc( AVSampleFormat sample_fmt, int channels, int nb_samples ) cdef int av_audio_fifo_write( AVAudioFifo *af, void **data, int nb_samples ) cdef int av_audio_fifo_read( AVAudioFifo *af, void **data, int nb_samples ) cdef int av_audio_fifo_size(AVAudioFifo *af) cdef int av_audio_fifo_space (AVAudioFifo *af) cdef extern from "stdarg.h" nogil: # For logging. Should really be in another PXD. ctypedef struct va_list: pass cdef extern from "Python.h" nogil: # For logging. See av/logging.pyx for an explanation. cdef int Py_AddPendingCall(void *, void *) void PyErr_PrintEx(int set_sys_last_vars) int Py_IsInitialized() void PyErr_Display(object, object, object) cdef extern from "libavutil/opt.h" nogil: cdef enum AVOptionType: AV_OPT_TYPE_FLAGS AV_OPT_TYPE_INT AV_OPT_TYPE_INT64 AV_OPT_TYPE_DOUBLE AV_OPT_TYPE_FLOAT AV_OPT_TYPE_STRING AV_OPT_TYPE_RATIONAL AV_OPT_TYPE_BINARY AV_OPT_TYPE_DICT AV_OPT_TYPE_UINT64 AV_OPT_TYPE_CONST AV_OPT_TYPE_IMAGE_SIZE AV_OPT_TYPE_PIXEL_FMT AV_OPT_TYPE_SAMPLE_FMT AV_OPT_TYPE_VIDEO_RATE AV_OPT_TYPE_DURATION AV_OPT_TYPE_COLOR AV_OPT_TYPE_CHLAYOUT AV_OPT_TYPE_BOOL cdef struct AVOption_default_val: int64_t i64 double dbl const char *str AVRational q cdef enum: AV_OPT_FLAG_ENCODING_PARAM AV_OPT_FLAG_DECODING_PARAM AV_OPT_FLAG_AUDIO_PARAM AV_OPT_FLAG_VIDEO_PARAM AV_OPT_FLAG_SUBTITLE_PARAM AV_OPT_FLAG_EXPORT AV_OPT_FLAG_READONLY AV_OPT_FLAG_FILTERING_PARAM cdef struct AVOption: const char *name const char *help AVOptionType type int offset AVOption_default_val default_val double min double max int flags const char *unit cdef extern from "libavutil/imgutils.h" nogil: cdef int av_image_alloc( uint8_t *pointers[4], int linesizes[4], int width, int height, AVPixelFormat pix_fmt, int align ) cdef int av_image_fill_pointers( uint8_t *pointers[4], AVPixelFormat pix_fmt, int height, uint8_t *ptr, const int linesizes[4] ) cdef int av_image_fill_linesizes( int linesizes[4], AVPixelFormat pix_fmt, int width, ) cdef extern from "libavutil/log.h" nogil: cdef enum AVClassCategory: AV_CLASS_CATEGORY_NA AV_CLASS_CATEGORY_INPUT AV_CLASS_CATEGORY_OUTPUT AV_CLASS_CATEGORY_MUXER AV_CLASS_CATEGORY_DEMUXER AV_CLASS_CATEGORY_ENCODER AV_CLASS_CATEGORY_DECODER AV_CLASS_CATEGORY_FILTER AV_CLASS_CATEGORY_BITSTREAM_FILTER AV_CLASS_CATEGORY_SWSCALER AV_CLASS_CATEGORY_SWRESAMPLER AV_CLASS_CATEGORY_NB cdef struct AVClass: const char *class_name const char *(*item_name)(void*) nogil AVClassCategory category int parent_log_context_offset const AVOption *option cdef enum: AV_LOG_QUIET AV_LOG_PANIC AV_LOG_FATAL AV_LOG_ERROR AV_LOG_WARNING AV_LOG_INFO AV_LOG_VERBOSE AV_LOG_DEBUG AV_LOG_TRACE AV_LOG_MAX_OFFSET # Send a log. void av_log(void *ptr, int level, const char *fmt, ...) # Get the logs. ctypedef void(*av_log_callback)(void *, int, const char *, va_list) void av_log_default_callback(void *, int, const char *, va_list) void av_log_set_callback (av_log_callback callback) void av_log_set_level(int level) PyAV-14.2.0/include/libavutil/buffer.pxd000066400000000000000000000011121475734227400200330ustar00rootroot00000000000000from libc.stdint cimport intptr_t, uint8_t cdef extern from "libavutil/buffer.h" nogil: AVBufferRef *av_buffer_create(uint8_t *data, size_t size, void (*free)(void *opaque, uint8_t *data), void *opaque, int flags) AVBufferRef* av_buffer_ref(AVBufferRef *buf) void av_buffer_unref(AVBufferRef **buf) cdef struct AVBuffer: uint8_t *data int size intptr_t refcount void (*free)(void *opaque, uint8_t *data) void *opaque int flags cdef struct AVBufferRef: AVBuffer *buffer uint8_t *data int size PyAV-14.2.0/include/libavutil/channel_layout.pxd000066400000000000000000000006341475734227400215770ustar00rootroot00000000000000cdef extern from "libavutil/channel_layout.h" nogil: # This is not a comprehensive list. cdef uint64_t AV_CH_LAYOUT_MONO cdef uint64_t AV_CH_LAYOUT_STEREO cdef uint64_t AV_CH_LAYOUT_2POINT1 cdef uint64_t AV_CH_LAYOUT_4POINT0 cdef uint64_t AV_CH_LAYOUT_5POINT0_BACK cdef uint64_t AV_CH_LAYOUT_5POINT1_BACK cdef uint64_t AV_CH_LAYOUT_6POINT1 cdef uint64_t AV_CH_LAYOUT_7POINT1 PyAV-14.2.0/include/libavutil/dict.pxd000066400000000000000000000015001475734227400175060ustar00rootroot00000000000000cdef extern from "libavutil/dict.h" nogil: # See: http://ffmpeg.org/doxygen/trunk/structAVDictionary.html ctypedef struct AVDictionary: pass cdef void av_dict_free(AVDictionary **) # See: http://ffmpeg.org/doxygen/trunk/structAVDictionaryEntry.html ctypedef struct AVDictionaryEntry: char *key char *value cdef int AV_DICT_IGNORE_SUFFIX cdef AVDictionaryEntry* av_dict_get( AVDictionary *dict, char *key, AVDictionaryEntry *prev, int flags, ) cdef int av_dict_set( AVDictionary **pm, const char *key, const char *value, int flags ) cdef int av_dict_count( AVDictionary *m ) cdef int av_dict_copy( AVDictionary **dst, AVDictionary *src, int flags ) PyAV-14.2.0/include/libavutil/error.pxd000066400000000000000000000023751475734227400177270ustar00rootroot00000000000000cdef extern from "libavutil/error.h" nogil: # Not actually from here, but whatever. cdef int ENOMEM cdef int EAGAIN cdef int AVERROR_BSF_NOT_FOUND cdef int AVERROR_BUG cdef int AVERROR_BUFFER_TOO_SMALL cdef int AVERROR_DECODER_NOT_FOUND cdef int AVERROR_DEMUXER_NOT_FOUND cdef int AVERROR_ENCODER_NOT_FOUND cdef int AVERROR_EOF cdef int AVERROR_EXIT cdef int AVERROR_EXTERNAL cdef int AVERROR_FILTER_NOT_FOUND cdef int AVERROR_INVALIDDATA cdef int AVERROR_MUXER_NOT_FOUND cdef int AVERROR_OPTION_NOT_FOUND cdef int AVERROR_PATCHWELCOME cdef int AVERROR_PROTOCOL_NOT_FOUND cdef int AVERROR_UNKNOWN cdef int AVERROR_EXPERIMENTAL cdef int AVERROR_INPUT_CHANGED cdef int AVERROR_OUTPUT_CHANGED cdef int AVERROR_HTTP_BAD_REQUEST cdef int AVERROR_HTTP_UNAUTHORIZED cdef int AVERROR_HTTP_FORBIDDEN cdef int AVERROR_HTTP_NOT_FOUND cdef int AVERROR_HTTP_OTHER_4XX cdef int AVERROR_HTTP_SERVER_ERROR cdef int AVERROR_NOMEM "AVERROR(ENOMEM)" # cdef int FFERRTAG(int, int, int, int) cdef int AVERROR(int error) cdef int AV_ERROR_MAX_STRING_SIZE cdef int av_strerror(int errno, char *output, size_t output_size) cdef char* av_err2str(int errnum) PyAV-14.2.0/include/libavutil/frame.pxd000066400000000000000000000013161475734227400176620ustar00rootroot00000000000000cdef extern from "libavutil/frame.h" nogil: cdef AVFrame* av_frame_alloc() cdef void av_frame_free(AVFrame**) cdef int av_frame_ref(AVFrame *dst, const AVFrame *src) cdef AVFrame* av_frame_clone(const AVFrame *src) cdef void av_frame_unref(AVFrame *frame) cdef void av_frame_move_ref(AVFrame *dst, AVFrame *src) cdef int av_frame_get_buffer(AVFrame *frame, int align) cdef int av_frame_is_writable(AVFrame *frame) cdef int av_frame_make_writable(AVFrame *frame) cdef int av_frame_copy(AVFrame *dst, const AVFrame *src) cdef int av_frame_copy_props(AVFrame *dst, const AVFrame *src) cdef AVFrameSideData* av_frame_get_side_data(AVFrame *frame, AVFrameSideDataType type) PyAV-14.2.0/include/libavutil/hwcontext.pxd000066400000000000000000000016301475734227400206120ustar00rootroot00000000000000cdef extern from "libavutil/hwcontext.h" nogil: enum AVHWDeviceType: AV_HWDEVICE_TYPE_NONE AV_HWDEVICE_TYPE_VDPAU AV_HWDEVICE_TYPE_CUDA AV_HWDEVICE_TYPE_VAAPI AV_HWDEVICE_TYPE_DXVA2 AV_HWDEVICE_TYPE_QSV AV_HWDEVICE_TYPE_VIDEOTOOLBOX AV_HWDEVICE_TYPE_D3D11VA AV_HWDEVICE_TYPE_DRM AV_HWDEVICE_TYPE_OPENCL AV_HWDEVICE_TYPE_MEDIACODEC AV_HWDEVICE_TYPE_VULKAN AV_HWDEVICE_TYPE_D3D12VA cdef int av_hwdevice_ctx_create(AVBufferRef **device_ctx, AVHWDeviceType type, const char *device, AVDictionary *opts, int flags) cdef AVHWDeviceType av_hwdevice_find_type_by_name(const char *name) cdef const char *av_hwdevice_get_type_name(AVHWDeviceType type) cdef AVHWDeviceType av_hwdevice_iterate_types(AVHWDeviceType prev) cdef int av_hwframe_transfer_data(AVFrame *dst, const AVFrame *src, int flags) PyAV-14.2.0/include/libavutil/motion_vector.pxd000066400000000000000000000007021475734227400214550ustar00rootroot00000000000000from libc.stdint cimport int16_t, int32_t, uint8_t, uint16_t, uint64_t cdef extern from "libavutil/motion_vector.h" nogil: cdef struct AVMotionVector: int32_t source uint8_t w uint8_t h int16_t src_x int16_t src_y int16_t dst_x int16_t dst_y uint64_t flags int32_t motion_x int32_t motion_y uint16_t motion_scale PyAV-14.2.0/include/libavutil/samplefmt.pxd000066400000000000000000000031571475734227400205650ustar00rootroot00000000000000cdef extern from "libavutil/samplefmt.h" nogil: cdef enum AVSampleFormat: AV_SAMPLE_FMT_NONE AV_SAMPLE_FMT_U8 AV_SAMPLE_FMT_S16 AV_SAMPLE_FMT_S32 AV_SAMPLE_FMT_FLT AV_SAMPLE_FMT_DBL AV_SAMPLE_FMT_U8P AV_SAMPLE_FMT_S16P AV_SAMPLE_FMT_S32P AV_SAMPLE_FMT_FLTP AV_SAMPLE_FMT_DBLP AV_SAMPLE_FMT_NB # Number. # Find by name. cdef AVSampleFormat av_get_sample_fmt(char* name) # Inspection. cdef char * av_get_sample_fmt_name(AVSampleFormat sample_fmt) cdef int av_get_bytes_per_sample(AVSampleFormat sample_fmt) cdef int av_sample_fmt_is_planar(AVSampleFormat sample_fmt) # Alternative forms. cdef AVSampleFormat av_get_packed_sample_fmt(AVSampleFormat sample_fmt) cdef AVSampleFormat av_get_planar_sample_fmt(AVSampleFormat sample_fmt) cdef int av_samples_alloc( uint8_t** audio_data, int* linesize, int nb_channels, int nb_samples, AVSampleFormat sample_fmt, int align ) cdef int av_samples_get_buffer_size( int *linesize, int nb_channels, int nb_samples, AVSampleFormat sample_fmt, int align ) cdef int av_samples_fill_arrays( uint8_t **audio_data, int *linesize, const uint8_t *buf, int nb_channels, int nb_samples, AVSampleFormat sample_fmt, int align ) cdef int av_samples_set_silence( uint8_t **audio_data, int offset, int nb_samples, int nb_channels, AVSampleFormat sample_fmt ) PyAV-14.2.0/include/libswresample/000077500000000000000000000000001475734227400167305ustar00rootroot00000000000000PyAV-14.2.0/include/libswresample/swresample.pxd000066400000000000000000000020661475734227400216330ustar00rootroot00000000000000from libc.stdint cimport int64_t, uint8_t cdef extern from "libswresample/swresample.h" nogil: cdef int swresample_version() cdef char* swresample_configuration() cdef char* swresample_license() cdef struct SwrContext: pass cdef SwrContext* swr_alloc_set_opts( SwrContext *ctx, int64_t out_ch_layout, AVSampleFormat out_sample_fmt, int out_sample_rate, int64_t in_ch_layout, AVSampleFormat in_sample_fmt, int in_sample_rate, int log_offset, void *log_ctx # logging context, can be NULL ) cdef int swr_convert( SwrContext *ctx, uint8_t ** out_buffer, int out_count, uint8_t **in_buffer, int in_count ) # Gets the delay the next input sample will # experience relative to the next output sample. cdef int64_t swr_get_delay(SwrContext *s, int64_t base) cdef SwrContext* swr_alloc() cdef int swr_init(SwrContext* ctx) cdef void swr_free(SwrContext **ctx) cdef void swr_close(SwrContext *ctx) PyAV-14.2.0/include/libswscale/000077500000000000000000000000001475734227400162075ustar00rootroot00000000000000PyAV-14.2.0/include/libswscale/swscale.pxd000066400000000000000000000044031475734227400203660ustar00rootroot00000000000000 cdef extern from "libswscale/swscale.h" nogil: cdef int swscale_version() cdef char* swscale_configuration() cdef char* swscale_license() # See: http://ffmpeg.org/doxygen/trunk/structSwsContext.html cdef struct SwsContext: pass # See: http://ffmpeg.org/doxygen/trunk/structSwsFilter.html cdef struct SwsFilter: pass # Flags. cdef int SWS_FAST_BILINEAR cdef int SWS_BILINEAR cdef int SWS_BICUBIC cdef int SWS_X cdef int SWS_POINT cdef int SWS_AREA cdef int SWS_BICUBLIN cdef int SWS_GAUSS cdef int SWS_SINC cdef int SWS_LANCZOS cdef int SWS_SPLINE cdef int SWS_CS_ITU709 cdef int SWS_CS_FCC cdef int SWS_CS_ITU601 cdef int SWS_CS_ITU624 cdef int SWS_CS_SMPTE170M cdef int SWS_CS_SMPTE240M cdef int SWS_CS_DEFAULT cdef SwsContext* sws_getContext( int src_width, int src_height, AVPixelFormat src_format, int dst_width, int dst_height, AVPixelFormat dst_format, int flags, SwsFilter *src_filter, SwsFilter *dst_filter, double *param, ) cdef int sws_scale( SwsContext *ctx, unsigned char **src_slice, int *src_stride, int src_slice_y, int src_slice_h, unsigned char **dst_slice, int *dst_stride, ) cdef void sws_freeContext(SwsContext *ctx) cdef SwsContext *sws_getCachedContext( SwsContext *context, int src_width, int src_height, AVPixelFormat src_format, int dst_width, int dst_height, AVPixelFormat dst_format, int flags, SwsFilter *src_filter, SwsFilter *dst_filter, double *param, ) cdef int* sws_getCoefficients(int colorspace) cdef int sws_getColorspaceDetails( SwsContext *context, int **inv_table, int *srcRange, int **table, int *dstRange, int *brightness, int *contrast, int *saturation ) cdef int sws_setColorspaceDetails( SwsContext *context, const int inv_table[4], int srcRange, const int table[4], int dstRange, int brightness, int contrast, int saturation ) PyAV-14.2.0/pyproject.toml000066400000000000000000000035211475734227400153510ustar00rootroot00000000000000[build-system] requires = ["setuptools>61", "cython>=3,<4"] [project] name = "av" description = "Pythonic bindings for FFmpeg's libraries." readme = "README.md" license = {text = "BSD-3-Clause"} authors = [ {name = "WyattBlue", email = "wyattblue@auto-editor.com"}, {name = "Jeremy Lainé", email = "jeremy.laine@m4x.org"}, ] requires-python = ">=3.9" classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Natural Language :: English", "Operating System :: MacOS :: MacOS X", "Operating System :: POSIX", "Operating System :: Unix", "Operating System :: Microsoft :: Windows", "Programming Language :: Cython", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Multimedia :: Sound/Audio", "Topic :: Multimedia :: Sound/Audio :: Conversion", "Topic :: Multimedia :: Video", "Topic :: Multimedia :: Video :: Conversion", ] dynamic = ["version"] [tool.setuptools] zip-safe = false [tool.setuptools.dynamic] version = {attr = "av.about.__version__"} [project.urls] "Bug Tracker" = "https://github.com/PyAV-Org/PyAV/discussions/new?category=4-bugs" "Source Code" = "https://github.com/PyAV-Org/PyAV" homepage = "https://pyav.basswood-io.com" [project.scripts] "pyav" = "av.__main__:main" [tool.isort] profile = "black" known_first_party = ["av"] skip = ["av/__init__.py"] [tool.flake8] filename = ["*.py", "*.pyx", "*.pxd"] ignore = ["E203", "W503"] max-line-length = 142 per-file-ignores = [ "__init__.py:E402,F401", "*.pyx,*.pxd:E211,E225,E227,E402,E999", ]PyAV-14.2.0/scripts/000077500000000000000000000000001475734227400141235ustar00rootroot00000000000000PyAV-14.2.0/scripts/activate.sh000077500000000000000000000053101475734227400162610ustar00rootroot00000000000000#!/bin/bash # Make sure this is sourced. if [[ "$0" == "${BASH_SOURCE[0]}" ]]; then echo This must be sourced. exit 1 fi export PYAV_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.."; pwd)" if [[ ! "$PYAV_LIBRARY" ]]; then if [[ "$1" ]]; then if [[ "$1" == ffmpeg-* ]]; then PYAV_LIBRARY="$1" else echo "Error: PYAV_LIBRARY must start with 'ffmpeg-'" >&2 return 1 fi else PYAV_LIBRARY=ffmpeg-7.1 echo "No \$PYAV_LIBRARY set; defaulting to $PYAV_LIBRARY" fi fi export PYAV_LIBRARY if [[ ! "$PYAV_PYTHON" ]]; then PYAV_PYTHON="${PYAV_PYTHON-python3}" echo 'No $PYAV_PYTHON set; defaulting to python3.' fi # Hack for PyPy on GitHub Actions. # This is because PYAV_PYTHON is constructed from "python${{ matrix.config.python }}" # resulting in "pythonpypy3", which won't work. # It would be nice to clean this up, but I want it to work ASAP. if [[ "$PYAV_PYTHON" == *pypy* ]]; then PYAV_PYTHON=python fi export PYAV_PYTHON export PYAV_PIP="${PYAV_PIP-$PYAV_PYTHON -m pip}" if [[ "$GITHUB_ACTION" ]]; then # GitHub has a very self-contained environment. Lets just work in that. echo "We're on CI, so not setting up another virtualenv." else export PYAV_VENV_NAME="$(uname -s).$(uname -r).$("$PYAV_PYTHON" -c ' import sys import platform print("{}{}.{}".format(platform.python_implementation().lower(), *sys.version_info[:2])) ')" export PYAV_VENV="$PYAV_ROOT/venvs/$PYAV_VENV_NAME" if [[ ! -e "$PYAV_VENV/bin/python" ]]; then mkdir -p "$PYAV_VENV" virtualenv -p "$PYAV_PYTHON" "$PYAV_VENV" "$PYAV_VENV/bin/pip" install --upgrade pip setuptools fi if [[ -e "$PYAV_VENV/bin/activate" ]]; then source "$PYAV_VENV/bin/activate" else # Not a virtualenv (perhaps a debug Python); lets manually "activate" it. PATH="$PYAV_VENV/bin:$PATH" fi fi # Just a flag so that we know this was supposedly run. export _PYAV_ACTIVATED=1 if [[ ! "$PYAV_LIBRARY_BUILD_ROOT" && -d /vagrant ]]; then # On Vagrant, building the library in the shared directory causes some # problems, so we move it to the user's home. PYAV_LIBRARY_ROOT="/home/vagrant/vendor" fi export PYAV_LIBRARY_ROOT="${PYAV_LIBRARY_ROOT-$PYAV_ROOT/vendor}" export PYAV_LIBRARY_BUILD="${PYAV_LIBRARY_BUILD-$PYAV_LIBRARY_ROOT/build}" export PYAV_LIBRARY_PREFIX="$PYAV_LIBRARY_BUILD/$PYAV_LIBRARY" export PATH="$PYAV_LIBRARY_PREFIX/bin:$PATH" export PYTHONPATH="$PYAV_ROOT:$PYTHONPATH" export PKG_CONFIG_PATH="$PYAV_LIBRARY_PREFIX/lib/pkgconfig:$PKG_CONFIG_PATH" export LD_LIBRARY_PATH="$PYAV_LIBRARY_PREFIX/lib:$LD_LIBRARY_PATH" export DYLD_LIBRARY_PATH="$PYAV_LIBRARY_PREFIX/lib:$DYLD_LIBRARY_PATH" PyAV-14.2.0/scripts/build000077500000000000000000000010361475734227400151500ustar00rootroot00000000000000#!/bin/bash if [[ ! "$_PYAV_ACTIVATED" ]]; then export here="$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd)" source "$here/activate.sh" fi cd "$PYAV_ROOT" export PATH="$PYAV_VENV/vendor/$PYAV_LIBRARY_SLUG/bin:$PATH" env | grep PYAV | sort echo echo PKG_CONFIG_PATH: $PKG_CONFIG_PATH echo LD_LIBRARY_PATH: $LD_LIBRARY_PATH echo which ffmpeg || exit 2 ffmpeg -version || exit 3 echo $PYAV_PIP install -U cython setuptools 2> /dev/null "$PYAV_PYTHON" scripts/comptime.py "$PYAV_PYTHON" setup.py config build_ext --inplace || exit 1 PyAV-14.2.0/scripts/build-deps000077500000000000000000000042021475734227400160770ustar00rootroot00000000000000#!/bin/bash if [[ ! "$_PYAV_ACTIVATED" ]]; then export here="$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd)" source "$here/activate.sh" fi cd "$PYAV_ROOT" # Skip the rest of the build if it already exists. if [[ -e "$PYAV_LIBRARY_PREFIX/bin/ffmpeg" ]]; then echo "We have a cached build of ffmpeg-$PYAV_LIBRARY; skipping re-build." exit 0 fi mkdir -p "$PYAV_LIBRARY_ROOT" mkdir -p "$PYAV_LIBRARY_PREFIX" # Add CUDA support if available CONFFLAGS_NVIDIA="" if [[ -e /usr/local/cuda ]]; then # Get Nvidia headers for ffmpeg cd $PYAV_LIBRARY_ROOT if [[ ! -e "$PYAV_LIBRARY_ROOT/nv-codec-headers" ]]; then git clone https://github.com/FFmpeg/nv-codec-headers.git cd nv-codec-headers make -j4 make PREFIX="$PYAV_LIBRARY_PREFIX" install fi PKG_CONFIG_PATH="/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH" CONFFLAGS_NVIDIA="--enable-cuda \ --enable-cuvid \ --enable-nvenc \ --enable-nonfree \ --enable-libnpp \ --extra-cflags=-I/usr/local/cuda/include \ --extra-ldflags=-L/usr/local/cuda/lib64" else echo "WARNING: Did not find cuda libraries in /usr/local/cuda..." echo " Building without NVIDIA NVENC/NVDEC support" fi cd "$PYAV_LIBRARY_ROOT" # Download and expand the source. if [[ ! -d $PYAV_LIBRARY ]]; then url="https://ffmpeg.org/releases/$PYAV_LIBRARY.tar.gz" echo Downloading $url wget --no-check-certificate "$url" || exit 1 tar -xzf $PYAV_LIBRARY.tar.gz rm $PYAV_LIBRARY.tar.gz echo fi cd $PYAV_LIBRARY echo ./configure ./configure \ --disable-doc \ --disable-static \ --disable-stripping \ --enable-debug=3 \ --enable-gpl \ --enable-version3 \ --enable-libx264 \ --enable-libxml2 \ --enable-shared \ --enable-sse \ --enable-avx \ --enable-avx2 \ $CONFFLAGS_NVIDIA \ --prefix="$PYAV_LIBRARY_PREFIX" \ || exit 2 echo echo make make -j4 || exit 3 echo echo make install make install || exit 4 echo echo Build products: cd ~ find "$PYAV_LIBRARY_PREFIX" -name '*libav*' PyAV-14.2.0/scripts/comptime.py000066400000000000000000000014041475734227400163110ustar00rootroot00000000000000import os import sys def replace_in_file(file_path): try: with open(file_path, "r", encoding="utf-8") as file: content = file.read() modified_content = content.replace("# [FFMPEG6] ", "") with open(file_path, "w") as file: file.write(modified_content) except UnicodeDecodeError: pass def process_directory(directory): for root, dirs, files in os.walk(directory): for file in files: file_path = os.path.join(root, file) replace_in_file(file_path) version = os.environ.get("PYAV_LIBRARY") if version is None: is_6 = sys.argv[1].startswith("6") else: is_6 = version.startswith("ffmpeg-6") if is_6: process_directory("av") process_directory("include") PyAV-14.2.0/scripts/fetch-vendor.py000066400000000000000000000036451475734227400170710ustar00rootroot00000000000000import argparse import logging import json import os import platform import struct import subprocess def get_platform(): system = platform.system() machine = platform.machine() if system == "Linux": return f"manylinux_{machine}" elif system == "Darwin": # cibuildwheel sets ARCHFLAGS: # https://github.com/pypa/cibuildwheel/blob/5255155bc57eb6224354356df648dc42e31a0028/cibuildwheel/macos.py#L207-L220 if "ARCHFLAGS" in os.environ: machine = os.environ["ARCHFLAGS"].split()[1] return f"macosx_{machine}" elif system == "Windows": if struct.calcsize("P") * 8 == 64: return "win_amd64" else: return "win32" else: raise Exception(f"Unsupported system {system}") parser = argparse.ArgumentParser(description="Fetch and extract tarballs") parser.add_argument("destination_dir") parser.add_argument("--cache-dir", default="tarballs") parser.add_argument("--config-file", default=os.path.splitext(__file__)[0] + ".json") args = parser.parse_args() logging.basicConfig(level=logging.INFO) with open(args.config_file) as fp: config = json.load(fp) # ensure destination directory exists logging.info(f"Creating directory {args.destination_dir}") if not os.path.exists(args.destination_dir): os.makedirs(args.destination_dir) tarball_url = config["url"].replace("{platform}", get_platform()) # download tarball tarball_name = tarball_url.split("/")[-1] tarball_file = os.path.join(args.cache_dir, tarball_name) if not os.path.exists(tarball_file): logging.info(f"Downloading {tarball_url}") if not os.path.exists(args.cache_dir): os.mkdir(args.cache_dir) subprocess.check_call( ["curl", "--location", "--output", tarball_file, "--silent", tarball_url] ) # extract tarball logging.info(f"Extracting {tarball_name}") subprocess.check_call(["tar", "-C", args.destination_dir, "-xf", tarball_file]) PyAV-14.2.0/scripts/ffmpeg-7.0.json000066400000000000000000000001531475734227400165630ustar00rootroot00000000000000{ "url": "https://github.com/PyAV-Org/pyav-ffmpeg/releases/download/7.0.2-1/ffmpeg-{platform}.tar.gz" }PyAV-14.2.0/scripts/ffmpeg-7.1.json000066400000000000000000000001521475734227400165630ustar00rootroot00000000000000{ "url": "https://github.com/PyAV-Org/pyav-ffmpeg/releases/download/7.1-4/ffmpeg-{platform}.tar.gz" } PyAV-14.2.0/scripts/test000077500000000000000000000014301475734227400150260ustar00rootroot00000000000000#!/bin/bash # Exit as soon as something errors. set -e if [[ ! "$_PYAV_ACTIVATED" ]]; then export here="$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd)" source "$here/activate.sh" fi cd "$PYAV_ROOT" TESTSUITE="${1-main}" istest() { [[ "$TESTSUITE" == all || "$TESTSUITE" == "$1" ]] return $? } $PYAV_PYTHON -c "import av; print(f'PyAV: {av.__version__}'); print(f'FFMPEG: {av.ffmpeg_version_info}')" if istest main; then $PYAV_PYTHON -m pytest fi if istest examples; then for name in $(find examples -name '*.py'); do echo echo === $name cd "$PYAV_ROOT" mkdir -p "sandbox/$1" cd "sandbox/$1" if ! python "$PYAV_ROOT/$name"; then echo FAILED $name with code $? exit $? fi done fi PyAV-14.2.0/setup.py000066400000000000000000000167311475734227400141560ustar00rootroot00000000000000import argparse import os import pathlib import platform import re import shlex import subprocess import sys from time import sleep def is_virtualenv(): return sys.base_prefix != sys.prefix if platform.system() == "Darwin": major_version = int(platform.mac_ver()[0].split(".")[0]) if major_version < 12: print( "\033[1;91mWarning!\033[0m You are using an EOL, unsupported, and out-of-date OS." ) sleep(3) print( "\n\033[1;91mWarning!\033[0m You are installing from source.\n" "It is \033[1;37mEXPECTED\033[0m that it will fail. You are \033[1;37mREQUIRED\033[0m" " to use ffmpeg 7.\nYou \033[1;37mMUST\033[0m have Cython, pkg-config, and a C compiler.\n" ) if os.getenv("GITHUB_ACTIONS") == "true" or is_virtualenv(): pass else: print("\033[1;91mWarning!\033[0m You are not using a virtual environment") from Cython.Build import cythonize from Cython.Compiler.AutoDocTransforms import EmbedSignature from setuptools import Extension, find_packages, setup FFMPEG_LIBRARIES = [ "avformat", "avcodec", "avdevice", "avutil", "avfilter", "swscale", "swresample", ] # Monkey-patch Cython to not overwrite embedded signatures. old_embed_signature = EmbedSignature._embed_signature def insert_enum_in_generated_files(source): # Work around Cython failing to add `enum` to `AVChannel` type. # TODO: Make Cython bug report if source.endswith(".c"): with open(source, "r") as file: content = file.read() # Replace "AVChannel __pyx_v_channel;" with "enum AVChannel __pyx_v_channel;" modified_content = re.sub( r"\b(? 1 and sys.argv[1] == "clean": cythonize = lambda ext, **kwargs: [ext] use_pkg_config = False # Locate ffmpeg libraries and headers. if FFMPEG_DIR is not None: extension_extra = get_config_from_directory(FFMPEG_DIR) elif use_pkg_config: extension_extra = get_config_from_pkg_config() else: extension_extra = { "include_dirs": [], "libraries": FFMPEG_LIBRARIES, "library_dirs": [], } loudnorm_extension = Extension( "av.filter.loudnorm", sources=[ "av/filter/loudnorm.pyx", "av/filter/loudnorm_impl.c", ], include_dirs=["av/filter"] + extension_extra["include_dirs"], libraries=extension_extra["libraries"], library_dirs=extension_extra["library_dirs"], ) # Add the cythonized loudnorm extension to ext_modules ext_modules = cythonize( loudnorm_extension, compiler_directives={ "c_string_type": "str", "c_string_encoding": "ascii", "embedsignature": True, "language_level": 3, }, build_dir="src", include_path=["include"], ) for dirname, dirnames, filenames in os.walk("av"): for filename in filenames: # We are looking for Cython sources. if filename.startswith(".") or os.path.splitext(filename)[1] != ".pyx": continue pyx_path = os.path.join(dirname, filename) base = os.path.splitext(pyx_path)[0] # Need to be a little careful because Windows will accept / or \ # (where os.sep will be \ on Windows). mod_name = base.replace("/", ".").replace(os.sep, ".") # Cythonize the module. ext_modules += cythonize( Extension( mod_name, include_dirs=extension_extra["include_dirs"], libraries=extension_extra["libraries"], library_dirs=extension_extra["library_dirs"], sources=[pyx_path], ), compiler_directives={ "c_string_type": "str", "c_string_encoding": "ascii", "embedsignature": True, "language_level": 3, }, build_dir="src", include_path=["include"], ) for ext in ext_modules: for cfile in ext.sources: insert_enum_in_generated_files(cfile) package_folders = pathlib.Path("av").glob("**/") package_data = { ".".join(pckg.parts): ["*.pxd", "*.pyi", "*.typed"] for pckg in package_folders } setup( packages=find_packages(include=["av*"]), package_data=package_data, ext_modules=ext_modules, ) PyAV-14.2.0/tests/000077500000000000000000000000001475734227400135765ustar00rootroot00000000000000PyAV-14.2.0/tests/__init__.py000066400000000000000000000000001475734227400156750ustar00rootroot00000000000000PyAV-14.2.0/tests/common.py000066400000000000000000000073361475734227400154510ustar00rootroot00000000000000from __future__ import annotations import datetime import errno import functools import os import types import typing from typing import TYPE_CHECKING from unittest import TestCase as _Base import numpy as np from av.datasets import fate as fate_suite try: import PIL # noqa has_pillow = True except ImportError: has_pillow = False if TYPE_CHECKING: from typing import Any, Callable, TypeVar from PIL.Image import Image T = TypeVar("T") __all__ = ("fate_suite",) is_windows = os.name == "nt" skip_tests = frozenset(os.environ.get("PYAV_SKIP_TESTS", "").split(",")) def safe_makedirs(path: str) -> None: try: os.makedirs(path) except OSError as e: if e.errno != errno.EEXIST: raise _start_time = datetime.datetime.now() def _sandbox(timed: bool = False) -> str: root = os.path.abspath(os.path.join(__file__, "..", "..", "sandbox")) if timed: sandbox = os.path.join(root, _start_time.strftime("%Y%m%d-%H%M%S")) else: sandbox = root if not os.path.exists(sandbox): os.makedirs(sandbox) return sandbox def asset(*args: str) -> str: adir = os.path.dirname(__file__) return os.path.abspath(os.path.join(adir, "assets", *args)) # Store all of the sample data here. os.environ["PYAV_TESTDATA_DIR"] = asset() def fate_png() -> str: return fate_suite("png1/55c99e750a5fd6_50314226.png") def sandboxed( *args: str, makedirs: bool = True, sandbox: str | None = None, timed: bool = False ) -> str: path = os.path.join(_sandbox(timed) if sandbox is None else sandbox, *args) if makedirs: safe_makedirs(os.path.dirname(path)) return path # Decorator for running a test in the sandbox directory def run_in_sandbox(func: Callable[..., T]) -> Callable[..., T]: @functools.wraps(func) def _inner(self: Any, *args: Any, **kwargs: Any) -> T: current_dir = os.getcwd() try: os.chdir(self.sandbox) return func(self, *args, **kwargs) finally: os.chdir(current_dir) return _inner def assertNdarraysEqual(a: np.ndarray, b: np.ndarray) -> None: assert a.shape == b.shape comparison = a == b if not comparison.all(): it = np.nditer(comparison, flags=["multi_index"]) msg = "" for equal in it: if not equal: msg += "- arrays differ at index {}; {} {}\n".format( it.multi_index, a[it.multi_index], b[it.multi_index], ) assert False, f"ndarrays contents differ\n{msg}" @typing.no_type_check def assertImagesAlmostEqual(a: Image, b: Image, epsilon: float = 0.1) -> None: import PIL.ImageFilter as ImageFilter assert a.size == b.size a = a.filter(ImageFilter.BLUR).getdata() b = b.filter(ImageFilter.BLUR).getdata() for i, ax, bx in zip(range(len(a)), a, b): diff = sum(abs(ac / 256 - bc / 256) for ac, bc in zip(ax, bx)) / 3 assert diff < epsilon, f"images differed by {diff} at index {i}; {ax} {bx}" class TestCase(_Base): @classmethod def _sandbox(cls, timed: bool = True) -> str: path = os.path.join(_sandbox(timed=timed), cls.__name__) safe_makedirs(path) return path @property def sandbox(self) -> str: return self._sandbox(timed=True) def sandboxed( self, *args: str, makedirs: bool = True, timed: bool = True, sandbox: str | None = None, ) -> str: if sandbox is None: return sandboxed( *args, makedirs=makedirs, timed=timed, sandbox=self.sandbox ) else: return sandboxed(*args, makedirs=makedirs, timed=timed, sandbox=sandbox) PyAV-14.2.0/tests/test_audiofifo.py000066400000000000000000000062501475734227400171570ustar00rootroot00000000000000from fractions import Fraction import av from .common import TestCase, fate_suite class TestAudioFifo(TestCase): def test_data(self) -> None: container = av.open(fate_suite("audio-reference/chorusnoise_2ch_44kHz_s16.wav")) stream = container.streams.audio[0] fifo = av.AudioFifo() input_ = [] output = [] for i, frame in enumerate(container.decode(stream)): input_.append(bytes(frame.planes[0])) fifo.write(frame) for frame in fifo.read_many(512, partial=i == 10): output.append(bytes(frame.planes[0])) if i == 10: break input_bytes = b"".join(input_) output_bytes = b"".join(output) min_len = min(len(input_bytes), len(output_bytes)) assert min_len > 10 * 512 * 2 * 2 assert input_bytes[:min_len] == output_bytes[:min_len] def test_pts_simple(self) -> None: fifo = av.AudioFifo() assert str(fifo).startswith( " at 0x" ) oframe = fifo.read(512) assert oframe is not None assert oframe.pts == 0 assert oframe.time_base == iframe.time_base assert fifo.samples_written == 1024 assert fifo.samples_read == 512 assert fifo.pts_per_sample == 1.0 iframe.pts = 1024 fifo.write(iframe) oframe = fifo.read(512) assert oframe is not None assert oframe.pts == 512 assert oframe.time_base == iframe.time_base iframe.pts = 9999 # Wrong! self.assertRaises(ValueError, fifo.write, iframe) def test_pts_complex(self) -> None: fifo = av.AudioFifo() iframe = av.AudioFrame(samples=1024) iframe.pts = 0 iframe.sample_rate = 48000 iframe.time_base = Fraction("1/96000") fifo.write(iframe) iframe.pts = 2048 fifo.write(iframe) oframe = fifo.read_many(1024)[-1] assert oframe.pts == 2048 assert fifo.pts_per_sample == 2.0 def test_missing_sample_rate(self) -> None: fifo = av.AudioFifo() iframe = av.AudioFrame(samples=1024) iframe.pts = 0 iframe.time_base = Fraction("1/48000") fifo.write(iframe) oframe = fifo.read(512) assert oframe is not None assert oframe.pts is None assert oframe.sample_rate == 0 assert oframe.time_base == iframe.time_base def test_missing_time_base(self) -> None: fifo = av.AudioFifo() iframe = av.AudioFrame(samples=1024) iframe.pts = 0 iframe.sample_rate = 48000 fifo.write(iframe) oframe = fifo.read(512) assert oframe is not None assert oframe.pts is None and oframe.time_base is None assert oframe.sample_rate == iframe.sample_rate PyAV-14.2.0/tests/test_audioformat.py000066400000000000000000000011761475734227400175260ustar00rootroot00000000000000import sys import pytest from av import AudioFormat def test_s16_inspection() -> None: fmt = AudioFormat("s16") postfix = "le" if sys.byteorder == "little" else "be" assert fmt.name == "s16" assert not fmt.is_planar assert fmt.bits == 16 assert fmt.bytes == 2 assert fmt.container_name == "s16" + postfix assert fmt.planar.name == "s16p" assert fmt.packed is fmt def test_s32p_inspection() -> None: fmt = AudioFormat("s32p") assert fmt.name == "s32p" assert fmt.is_planar assert fmt.bits == 32 assert fmt.bytes == 4 pytest.raises(ValueError, lambda: fmt.container_name) PyAV-14.2.0/tests/test_audioframe.py000066400000000000000000000152621475734227400173310ustar00rootroot00000000000000from re import escape import numpy as np import pytest from av import AudioFrame from .common import assertNdarraysEqual def test_null_constructor() -> None: frame = AudioFrame() assert frame.format.name == "s16" assert frame.layout.name == "stereo" assert len(frame.planes) == 0 assert frame.samples == 0 def test_manual_flt_mono_constructor() -> None: frame = AudioFrame(format="flt", layout="mono", samples=160) assert frame.format.name == "flt" assert frame.layout.name == "mono" assert len(frame.planes) == 1 assert frame.planes[0].buffer_size == 640 assert frame.samples == 160 def test_manual_flt_stereo_constructor() -> None: frame = AudioFrame(format="flt", layout="stereo", samples=160) assert frame.format.name == "flt" assert frame.layout.name == "stereo" assert len(frame.planes) == 1 assert frame.planes[0].buffer_size == 1280 assert frame.samples == 160 def test_manual_fltp_stereo_constructor() -> None: frame = AudioFrame(format="fltp", layout="stereo", samples=160) assert frame.format.name == "fltp" assert frame.layout.name == "stereo" assert len(frame.planes) == 2 assert frame.planes[0].buffer_size == 640 assert frame.planes[1].buffer_size == 640 assert frame.samples == 160 def test_manual_s16_mono_constructor() -> None: frame = AudioFrame(format="s16", layout="mono", samples=160) assert frame.format.name == "s16" assert frame.layout.name == "mono" assert len(frame.planes) == 1 assert frame.planes[0].buffer_size == 320 assert frame.samples == 160 def test_manual_s16_mono_constructor_align_8() -> None: frame = AudioFrame(format="s16", layout="mono", samples=159, align=8) assert frame.format.name == "s16" assert frame.layout.name == "mono" assert len(frame.planes) == 1 assert frame.planes[0].buffer_size == 320 assert frame.samples == 159 def test_manual_s16_stereo_constructor() -> None: frame = AudioFrame(format="s16", layout="stereo", samples=160) assert frame.format.name == "s16" assert frame.layout.name == "stereo" assert len(frame.planes) == 1 assert frame.planes[0].buffer_size == 640 assert frame.samples == 160 def test_manual_s16p_stereo_constructor() -> None: frame = AudioFrame(format="s16p", layout="stereo", samples=160) assert frame.format.name == "s16p" assert frame.layout.name == "stereo" assert len(frame.planes) == 2 assert frame.planes[0].buffer_size == 320 assert frame.planes[1].buffer_size == 320 assert frame.samples == 160 def test_basic_to_ndarray() -> None: frame = AudioFrame(format="s16p", layout="stereo", samples=160) array = frame.to_ndarray() assert array.dtype == "i2" assert array.shape == (2, 160) def test_ndarray_dbl() -> None: layouts = [ ("dbl", "mono", (1, 160)), ("dbl", "stereo", (1, 320)), ("dblp", "mono", (1, 160)), ("dblp", "stereo", (2, 160)), ] for format, layout, size in layouts: array = np.zeros(shape=size, dtype="f8") for i in range(size[0]): array[i][:] = np.random.rand(size[1]) frame = AudioFrame.from_ndarray(array, format=format, layout=layout) assert frame.format.name == format assert frame.layout.name == layout assert frame.samples == 160 assertNdarraysEqual(frame.to_ndarray(), array) def test_from_ndarray_value_error() -> None: # incorrect dtype array = np.zeros(shape=(1, 160), dtype="f2") with pytest.raises( ValueError, match="Expected numpy array with dtype `float32` but got `float16`" ) as cm: AudioFrame.from_ndarray(array, format="flt", layout="mono") # incorrect number of dimensions array2 = np.zeros(shape=(1, 160, 2), dtype="f4") with pytest.raises( ValueError, match="Expected numpy array with ndim `2` but got `3`" ) as cm: AudioFrame.from_ndarray(array2, format="flt", layout="mono") # incorrect shape array = np.zeros(shape=(2, 160), dtype="f4") with pytest.raises( ValueError, match=escape("Expected packed `array.shape[0]` to equal `1` but got `2`"), ) as cm: AudioFrame.from_ndarray(array, format="flt", layout="mono") def test_ndarray_flt() -> None: layouts = [ ("flt", "mono", (1, 160)), ("flt", "stereo", (1, 320)), ("fltp", "mono", (1, 160)), ("fltp", "stereo", (2, 160)), ] for format, layout, size in layouts: array: np.ndarray = np.zeros(shape=size, dtype="f4") for i in range(size[0]): array[i][:] = np.random.rand(size[1]) frame = AudioFrame.from_ndarray(array, format=format, layout=layout) assert frame.format.name == format assert frame.layout.name == layout assert frame.samples == 160 assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_s16() -> None: layouts = [ ("s16", "mono", (1, 160)), ("s16", "stereo", (1, 320)), ("s16p", "mono", (1, 160)), ("s16p", "stereo", (2, 160)), ] for format, layout, size in layouts: array = np.random.randint(0, 256, size=size, dtype="i2") frame = AudioFrame.from_ndarray(array, format=format, layout=layout) assert frame.format.name == format assert frame.layout.name == layout assert frame.samples == 160 assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_s16p_align_8() -> None: frame = AudioFrame(format="s16p", layout="stereo", samples=159, align=8) array = frame.to_ndarray() assert array.dtype == "i2" assert array.shape == (2, 159) def test_ndarray_s32() -> None: layouts = [ ("s32", "mono", (1, 160)), ("s32", "stereo", (1, 320)), ("s32p", "mono", (1, 160)), ("s32p", "stereo", (2, 160)), ] for format, layout, size in layouts: array = np.random.randint(0, 256, size=size, dtype="i4") frame = AudioFrame.from_ndarray(array, format=format, layout=layout) assert frame.format.name == format assert frame.layout.name == layout assert frame.samples == 160 assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_u8() -> None: layouts = [ ("u8", "mono", (1, 160)), ("u8", "stereo", (1, 320)), ("u8p", "mono", (1, 160)), ("u8p", "stereo", (2, 160)), ] for format, layout, size in layouts: array = np.random.randint(0, 256, size=size, dtype="u1") frame = AudioFrame.from_ndarray(array, format=format, layout=layout) assert frame.format.name == format assert frame.layout.name == layout assert frame.samples == 160 assertNdarraysEqual(frame.to_ndarray(), array) PyAV-14.2.0/tests/test_audiolayout.py000066400000000000000000000014771475734227400175570ustar00rootroot00000000000000from av import AudioLayout def _test_stereo(layout: AudioLayout) -> None: assert layout.name == "stereo" assert layout.nb_channels == 2 assert repr(layout) == "" # Re-enable when FFmpeg 6.0 is dropped. # assert layout.channels[0].name == "FL" # assert layout.channels[0].description == "front left" # assert repr(layout.channels[0]) == "" # assert layout.channels[1].name == "FR" # assert layout.channels[1].description == "front right" # assert repr(layout.channels[1]) == "" def test_stereo_from_str() -> None: layout = AudioLayout("stereo") _test_stereo(layout) def test_stereo_from_layout() -> None: layout2 = AudioLayout(AudioLayout("stereo")) _test_stereo(layout2) PyAV-14.2.0/tests/test_audioresampler.py000066400000000000000000000164531475734227400202340ustar00rootroot00000000000000from fractions import Fraction import pytest import av from av import AudioFrame, AudioResampler def test_flush_immediately() -> None: """ If we flush the resampler before passing any input, it returns a `None` frame without setting up the graph. """ resampler = AudioResampler() # flush oframes = resampler.resample(None) assert len(oframes) == 0 def test_identity_passthrough() -> None: """ If we don't ask it to do anything, it won't. """ resampler = AudioResampler() # resample one frame iframe = AudioFrame("s16", "stereo", 1024) oframes = resampler.resample(iframe) assert len(oframes) == 1 assert iframe is oframes[0] # resample another frame iframe.pts = 1024 oframes = resampler.resample(iframe) assert len(oframes) == 1 assert iframe is oframes[0] # flush oframes = resampler.resample(None) assert len(oframes) == 0 def test_matching_passthrough() -> None: """ If the frames match, it won't do anything. """ resampler = AudioResampler("s16", "stereo") # resample one frame iframe = AudioFrame("s16", "stereo", 1024) oframes = resampler.resample(iframe) assert len(oframes) == 1 assert iframe is oframes[0] # resample another frame iframe.pts = 1024 oframes = resampler.resample(iframe) assert len(oframes) == 1 assert iframe is oframes[0] # flush oframes = resampler.resample(None) assert len(oframes) == 0 def test_pts_assertion_same_rate() -> None: av.logging.set_level(av.logging.VERBOSE) resampler = AudioResampler("s16", "mono") # resample one frame iframe = AudioFrame("s16", "stereo", 1024) iframe.sample_rate = 48000 iframe.time_base = Fraction(1, 48000) iframe.pts = 0 oframes = resampler.resample(iframe) assert len(oframes) == 1 oframe = oframes[0] assert oframe.pts == 0 assert oframe.time_base == iframe.time_base assert oframe.sample_rate == iframe.sample_rate assert oframe.samples == iframe.samples # resample another frame iframe.pts = 1024 oframes = resampler.resample(iframe) assert len(oframes) == 1 oframe = oframes[0] assert oframe.pts == 1024 assert oframe.time_base == iframe.time_base assert oframe.sample_rate == iframe.sample_rate assert oframe.samples == iframe.samples # resample another frame with a pts gap, do not raise exception iframe.pts = 9999 oframes = resampler.resample(iframe) assert len(oframes) == 1 oframe = oframes[0] assert oframe.pts == 9999 assert oframe.time_base == iframe.time_base assert oframe.sample_rate == iframe.sample_rate assert oframe.samples == iframe.samples # flush oframes = resampler.resample(None) assert len(oframes) == 0 av.logging.set_level(None) def test_pts_assertion_new_rate_up() -> None: resampler = AudioResampler("s16", "mono", 44100) # resample one frame iframe = AudioFrame("s16", "stereo", 1024) iframe.sample_rate = 48000 iframe.time_base = Fraction(1, 48000) iframe.pts = 0 oframes = resampler.resample(iframe) assert len(oframes) == 1 oframe = oframes[0] assert oframe.pts == 0 assert oframe.time_base == Fraction(1, 44100) assert oframe.sample_rate == 44100 assert oframe.samples == 925 iframe = AudioFrame("s16", "stereo", 1024) iframe.sample_rate = 48000 iframe.time_base = Fraction(1, 48000) iframe.pts = 1024 oframes = resampler.resample(iframe) assert len(oframes) == 1 oframe = oframes[0] assert oframe.pts == 925 assert oframe.time_base == Fraction(1, 44100) assert oframe.sample_rate == 44100 assert oframe.samples == 941 # flush oframes = resampler.resample(None) assert len(oframes) == 1 oframe = oframes[0] assert oframe.pts == 941 + 925 assert oframe.time_base == Fraction(1, 44100) assert oframe.sample_rate == 44100 assert oframe.samples == 15 def test_pts_assertion_new_rate_down() -> None: resampler = AudioResampler("s16", "mono", 48000) # resample one frame iframe = AudioFrame("s16", "stereo", 1024) iframe.sample_rate = 44100 iframe.time_base = Fraction(1, 44100) iframe.pts = 0 oframes = resampler.resample(iframe) assert len(oframes) == 1 oframe = oframes[0] assert oframe.pts == 0 assert oframe.time_base == Fraction(1, 48000) assert oframe.sample_rate == 48000 assert oframe.samples == 1098 iframe = AudioFrame("s16", "stereo", 1024) iframe.sample_rate = 44100 iframe.time_base = Fraction(1, 44100) iframe.pts = 1024 oframes = resampler.resample(iframe) assert len(oframes) == 1 oframe = oframes[0] assert oframe.pts == 1098 assert oframe.time_base == Fraction(1, 48000) assert oframe.sample_rate == 48000 assert oframe.samples == 1114 # flush oframes = resampler.resample(None) assert len(oframes) == 1 oframe = oframes[0] assert oframe.pts == 1114 + 1098 assert oframe.time_base == Fraction(1, 48000) assert oframe.sample_rate == 48000 assert oframe.samples == 18 def test_pts_assertion_new_rate_fltp() -> None: resampler = AudioResampler("fltp", "mono", 8000, 1024) # resample one frame iframe = AudioFrame("s16", "mono", 1024) iframe.sample_rate = 8000 iframe.time_base = Fraction(1, 1000) iframe.pts = 0 oframes = resampler.resample(iframe) assert len(oframes) == 1 oframe = oframes[0] assert oframe.pts == 0 assert oframe.time_base == Fraction(1, 8000) assert oframe.sample_rate == 8000 assert oframe.samples == 1024 iframe = AudioFrame("s16", "mono", 1024) iframe.sample_rate = 8000 iframe.time_base = Fraction(1, 1000) iframe.pts = 8192 oframes = resampler.resample(iframe) assert len(oframes) == 1 oframe = oframes[0] assert oframe.pts == 65536 assert oframe.time_base == Fraction(1, 8000) assert oframe.sample_rate == 8000 assert oframe.samples == 1024 # flush oframes = resampler.resample(None) assert len(oframes) == 0 def test_pts_missing_time_base() -> None: resampler = AudioResampler("s16", "mono", 44100) # resample one frame iframe = AudioFrame("s16", "stereo", 1024) iframe.sample_rate = 48000 iframe.pts = 0 oframes = resampler.resample(iframe) assert len(oframes) == 1 oframe = oframes[0] assert oframe.pts == 0 assert oframe.time_base == Fraction(1, 44100) assert oframe.sample_rate == 44100 # flush oframes = resampler.resample(None) assert len(oframes) == 1 oframe = oframes[0] assert oframe.pts == 925 assert oframe.time_base == Fraction(1, 44100) assert oframe.sample_rate == 44100 assert oframe.samples == 16 def test_mismatched_input() -> None: """ Consecutive frames must have the same layout, sample format and sample rate. """ resampler = AudioResampler("s16", "mono", 44100) # resample one frame iframe = AudioFrame("s16", "stereo", 1024) iframe.sample_rate = 48000 resampler.resample(iframe) # resample another frame with a sample format iframe = AudioFrame("s16", "mono", 1024) iframe.sample_rate = 48000 with pytest.raises( ValueError, match="Frame does not match AudioResampler setup." ) as cm: resampler.resample(iframe) PyAV-14.2.0/tests/test_bitstream.py000066400000000000000000000061551475734227400172100ustar00rootroot00000000000000from __future__ import annotations import pytest import av from av import Packet from av.bitstream import BitStreamFilterContext, bitstream_filters_available from .common import TestCase, fate_suite def is_annexb(packet: Packet | bytes | None) -> bool: if packet is None: return False data = bytes(packet) return data[:3] == b"\0\0\x01" or data[:4] == b"\0\0\0\x01" def test_filters_availible() -> None: assert "h264_mp4toannexb" in bitstream_filters_available def test_filter_chomp() -> None: ctx = BitStreamFilterContext("chomp") src_packets: tuple[Packet, None] = (Packet(b"\x0012345\0\0\0"), None) assert bytes(src_packets[0]) == b"\x0012345\0\0\0" result_packets = [] for p in src_packets: result_packets.extend(ctx.filter(p)) assert len(result_packets) == 1 assert bytes(result_packets[0]) == b"\x0012345" def test_filter_setts() -> None: ctx = BitStreamFilterContext("setts=pts=N") ctx2 = BitStreamFilterContext(b"setts=pts=N") del ctx2 p1 = Packet(b"\0") p1.pts = 42 p2 = Packet(b"\0") p2.pts = 50 src_packets = [p1, p2, None] result_packets: list[Packet] = [] for p in src_packets: result_packets.extend(ctx.filter(p)) assert len(result_packets) == 2 assert result_packets[0].pts == 0 assert result_packets[1].pts == 1 def test_filter_h264_mp4toannexb() -> None: with av.open(fate_suite("h264/interlaced_crop.mp4"), "r") as container: stream = container.streams.video[0] ctx = BitStreamFilterContext("h264_mp4toannexb", stream) res_packets = [] for p in container.demux(stream): assert not is_annexb(p) res_packets.extend(ctx.filter(p)) assert len(res_packets) == stream.frames for p in res_packets: assert is_annexb(p) def test_filter_output_parameters() -> None: with av.open(fate_suite("h264/interlaced_crop.mp4"), "r") as container: stream = container.streams.video[0] assert not is_annexb(stream.codec_context.extradata) ctx = BitStreamFilterContext("h264_mp4toannexb", stream) assert not is_annexb(stream.codec_context.extradata) del ctx _ = BitStreamFilterContext("h264_mp4toannexb", stream, out_stream=stream) assert is_annexb(stream.codec_context.extradata) def test_filter_flush() -> None: with av.open(fate_suite("h264/interlaced_crop.mp4"), "r") as container: stream = container.streams.video[0] ctx = BitStreamFilterContext("h264_mp4toannexb", stream) res_packets = [] for p in container.demux(stream): res_packets.extend(ctx.filter(p)) assert len(res_packets) == stream.frames container.seek(0) # Without flushing, we expect to get an error: "A non-NULL packet sent after an EOF." with pytest.raises(ValueError): for p in container.demux(stream): ctx.filter(p) ctx.flush() container.seek(0) for p in container.demux(stream): res_packets.extend(ctx.filter(p)) assert len(res_packets) == stream.frames * 2 PyAV-14.2.0/tests/test_codec.py000066400000000000000000000044561475734227400162750ustar00rootroot00000000000000import pytest from av import AudioFormat, Codec, VideoFormat, codecs_available from av.codec.codec import UnknownCodecError def test_codec_bogus() -> None: with pytest.raises(UnknownCodecError): Codec("bogus123") with pytest.raises(UnknownCodecError): Codec("bogus123", "w") def test_codec_mpeg4_decoder() -> None: c = Codec("mpeg4") assert c.name == "mpeg4" assert c.long_name == "MPEG-4 part 2" assert c.type == "video" assert c.id in (12, 13) assert c.is_decoder assert not c.is_encoder assert c.delay assert c.audio_formats is None and c.audio_rates is None # formats = c.video_formats # assert formats # assert isinstance(formats[0], VideoFormat) # assert any(f.name == "yuv420p" for f in formats) assert c.frame_rates is None def test_codec_mpeg4_encoder() -> None: c = Codec("mpeg4", "w") assert c.name == "mpeg4" assert c.long_name == "MPEG-4 part 2" assert c.type == "video" assert c.id in (12, 13) assert c.is_encoder assert not c.is_decoder assert c.delay assert c.audio_formats is None and c.audio_rates is None formats = c.video_formats assert formats assert isinstance(formats[0], VideoFormat) assert any(f.name == "yuv420p" for f in formats) assert c.frame_rates is None def test_codec_opus_decoder() -> None: c = Codec("opus") assert c.name == "opus" assert c.long_name == "Opus" assert c.type == "audio" assert c.is_decoder assert not c.is_encoder assert c.delay assert c.audio_formats is None and c.audio_rates is None assert c.video_formats is None and c.frame_rates is None def test_codec_opus_encoder() -> None: c = Codec("opus", "w") assert c.name in ("opus", "libopus") assert c.canonical_name == "opus" assert c.long_name in ("Opus", "libopus Opus") assert c.type == "audio" assert c.is_encoder assert not c.is_decoder assert c.delay # audio formats = c.audio_formats assert formats assert isinstance(formats[0], AudioFormat) assert any(f.name in ("flt", "fltp") for f in formats) assert c.audio_rates is not None assert 48000 in c.audio_rates assert c.video_formats is None and c.frame_rates is None def test_codecs_available() -> None: assert codecs_available PyAV-14.2.0/tests/test_codec_context.py000066400000000000000000000355101475734227400200340ustar00rootroot00000000000000from __future__ import annotations import os from fractions import Fraction from typing import Iterator, TypedDict, overload import pytest import av from av import ( AudioCodecContext, AudioFrame, AudioLayout, AudioResampler, Codec, Packet, VideoCodecContext, VideoFrame, ) from av.codec.codec import UnknownCodecError from av.video.frame import PictureType from .common import TestCase, fate_suite class Options(TypedDict, total=False): b: str crf: str pix_fmt: str width: int height: int max_frames: int time_base: Fraction gop_size: int @overload def iter_raw_frames( path: str, packet_sizes: list[int], ctx: VideoCodecContext ) -> Iterator[VideoFrame]: ... @overload def iter_raw_frames( path: str, packet_sizes: list[int], ctx: AudioCodecContext ) -> Iterator[AudioFrame]: ... def iter_raw_frames( path: str, packet_sizes: list[int], ctx: VideoCodecContext | AudioCodecContext ) -> Iterator[VideoFrame | AudioFrame]: with open(path, "rb") as f: for i, size in enumerate(packet_sizes): packet = Packet(size) read_size = f.readinto(packet) assert size assert read_size == size if not read_size: break for frame in ctx.decode(packet): yield frame while True: try: frames = ctx.decode(None) except EOFError: break for frame in frames: yield frame if not frames: break class TestCodecContext(TestCase): def test_skip_frame_default(self): ctx = Codec("png", "w").create() assert ctx.skip_frame == "DEFAULT" def test_codec_delay(self) -> None: with av.open(fate_suite("mkv/codec_delay_opus.mkv")) as container: assert container.streams.audio[0].codec_context.delay == 312 with av.open(fate_suite("h264/interlaced_crop.mp4")) as container: assert container.streams.video[0].codec_context.delay == 0 def test_codec_tag(self): ctx = Codec("mpeg4", "w").create() assert ctx.codec_tag == "\x00\x00\x00\x00" ctx.codec_tag = "xvid" assert ctx.codec_tag == "xvid" # wrong length with pytest.raises( ValueError, match="Codec tag should be a 4 character string" ): ctx.codec_tag = "bob" # wrong type with pytest.raises( ValueError, match="Codec tag should be a 4 character string" ): ctx.codec_tag = 123 with av.open(fate_suite("h264/interlaced_crop.mp4")) as container: assert container.streams[0].codec_tag == "avc1" def test_decoder_extradata(self): ctx = av.codec.Codec("h264", "r").create() assert ctx.extradata is None assert ctx.extradata_size == 0 ctx.extradata = b"123" assert ctx.extradata == b"123" assert ctx.extradata_size == 3 ctx.extradata = b"54321" assert ctx.extradata == b"54321" assert ctx.extradata_size == 5 ctx.extradata = None assert ctx.extradata is None assert ctx.extradata_size == 0 def test_decoder_gop_size(self) -> None: ctx = av.codec.Codec("h264", "r").create("video") with pytest.raises(RuntimeError): ctx.gop_size def test_decoder_timebase(self) -> None: ctx = av.codec.Codec("h264", "r").create() with pytest.raises(RuntimeError): ctx.time_base with pytest.raises(RuntimeError): ctx.time_base = Fraction(1, 25) def test_encoder_extradata(self) -> None: ctx = av.codec.Codec("h264", "w").create() assert ctx.extradata is None assert ctx.extradata_size == 0 ctx.extradata = b"123" assert ctx.extradata == b"123" assert ctx.extradata_size == 3 def test_encoder_pix_fmt(self) -> None: ctx = av.codec.Codec("h264", "w").create("video") # valid format ctx.pix_fmt = "yuv420p" assert ctx.pix_fmt == "yuv420p" # invalid format with self.assertRaises(ValueError) as cm: ctx.pix_fmt = "__unknown_pix_fmt" assert str(cm.exception) == "not a pixel format: '__unknown_pix_fmt'" assert ctx.pix_fmt == "yuv420p" def test_bits_per_coded_sample(self): with av.open(fate_suite("qtrle/aletrek-rle.mov")) as container: stream = container.streams.video[0] stream.bits_per_coded_sample = 32 for packet in container.demux(stream): for frame in packet.decode(): pass assert isinstance(packet.stream, av.VideoStream) assert packet.stream.bits_per_coded_sample == 32 with av.open(fate_suite("qtrle/aletrek-rle.mov")) as container: stream = container.streams.video[0] stream.bits_per_coded_sample = 31 with pytest.raises(av.error.InvalidDataError): for _ in container.decode(stream): pass with av.open(self.sandboxed("output.mov"), "w") as output: stream = output.add_stream("qtrle") with pytest.raises(ValueError): stream.codec_context.bits_per_coded_sample = 32 def test_parse(self) -> None: # This one parses into a single packet. self._assert_parse("mpeg4", fate_suite("h264/interlaced_crop.mp4")) # This one parses into many small packets. self._assert_parse("mpeg2video", fate_suite("mpeg2/mpeg2_field_encoding.ts")) def _assert_parse(self, codec_name: str, path: str) -> None: fh = av.open(path) packets = [] for packet in fh.demux(video=0): packets.append(packet) full_source = b"".join(bytes(p) for p in packets) for size in 1024, 8192, 65535: ctx = Codec(codec_name).create() packets = [] for i in range(0, len(full_source), size): block = full_source[i : i + size] packets.extend(ctx.parse(block)) packets.extend(ctx.parse()) parsed_source = b"".join(bytes(p) for p in packets) assert len(parsed_source) == len(full_source) assert full_source == parsed_source class TestEncoding(TestCase): def test_encoding_png(self) -> None: self.image_sequence_encode("png") def test_encoding_mjpeg(self) -> None: self.image_sequence_encode("mjpeg") def test_encoding_tiff(self) -> None: self.image_sequence_encode("tiff") def image_sequence_encode(self, codec_name: str) -> None: try: codec = Codec(codec_name, "w") except UnknownCodecError: pytest.skip(f"Unknown codec: {codec_name}") container = av.open(fate_suite("h264/interlaced_crop.mp4")) video_stream = container.streams.video[0] width = 640 height = 480 ctx = codec.create("video") assert ctx.codec.video_formats pix_fmt = ctx.codec.video_formats[0].name ctx.width = width ctx.height = height assert video_stream.time_base is not None ctx.time_base = video_stream.time_base ctx.pix_fmt = pix_fmt ctx.open() frame_count = 1 path_list = [] for frame in container.decode(video_stream): new_frame = frame.reformat(width, height, pix_fmt) new_packets = ctx.encode(new_frame) assert len(new_packets) == 1 new_packet = new_packets[0] path = self.sandboxed( "%s/encoder.%04d.%s" % ( codec_name, frame_count, codec_name if codec_name != "mjpeg" else "jpg", ) ) path_list.append(path) with open(path, "wb") as f: f.write(new_packet) frame_count += 1 if frame_count > 5: break ctx = av.Codec(codec_name, "r").create("video") for path in path_list: with open(path, "rb") as f: size = os.fstat(f.fileno()).st_size packet = Packet(size) size = f.readinto(packet) frame = ctx.decode(packet)[0] assert frame.width == width assert frame.height == height assert frame.format.name == pix_fmt def test_encoding_h264(self) -> None: self.video_encoding("h264", {"crf": "19"}) def test_encoding_mpeg4(self) -> None: self.video_encoding("mpeg4") def test_encoding_xvid(self) -> None: self.video_encoding("mpeg4", codec_tag="xvid") def test_encoding_mpeg1video(self) -> None: self.video_encoding("mpeg1video") def test_encoding_dvvideo(self) -> None: options: Options = {"pix_fmt": "yuv411p", "width": 720, "height": 480} self.video_encoding("dvvideo", options) def test_encoding_dnxhd(self) -> None: options: Options = { "b": "90M", # bitrate "pix_fmt": "yuv422p", "width": 1920, "height": 1080, "time_base": Fraction(1001, 30_000), "max_frames": 5, } self.video_encoding("dnxhd", options) def video_encoding( self, codec_name: str, options: Options = {}, codec_tag: str | None = None, ) -> None: try: codec = Codec(codec_name, "w") except UnknownCodecError: pytest.skip(f"Unknown codec: {codec_name}") container = av.open(fate_suite("h264/interlaced_crop.mp4")) video_stream = container.streams.video[0] assert video_stream.time_base is not None pix_fmt = options.pop("pix_fmt", "yuv420p") width = options.pop("width", 640) height = options.pop("height", 480) max_frames = options.pop("max_frames", 50) time_base = options.pop("time_base", video_stream.time_base) gop_size = options.pop("gop_size", 20) ctx = codec.create("video") ctx.width = width ctx.height = height ctx.time_base = time_base ctx.framerate = 1 / ctx.time_base ctx.pix_fmt = pix_fmt ctx.gop_size = gop_size ctx.options = options # type: ignore if codec_tag: ctx.codec_tag = codec_tag ctx.open() path = self.sandboxed(f"encoder.{codec_name}") packet_sizes = [] frame_count = 0 with open(path, "wb") as f: for frame in container.decode(video_stream): new_frame = frame.reformat(width, height, pix_fmt) # reset the picture type new_frame.pict_type = PictureType.NONE for packet in ctx.encode(new_frame): packet_sizes.append(packet.size) f.write(packet) frame_count += 1 if frame_count >= max_frames: break for packet in ctx.encode(None): packet_sizes.append(packet.size) f.write(packet) dec_codec_name = codec_name if codec_name == "libx264": dec_codec_name = "h264" ctx = av.Codec(dec_codec_name, "r").create("video") ctx.open() keyframe_indices = [] decoded_frame_count = 0 for frame in iter_raw_frames(path, packet_sizes, ctx): decoded_frame_count += 1 assert frame.width == width assert frame.height == height assert frame.format.name == pix_fmt if frame.key_frame: keyframe_indices.append(decoded_frame_count) assert frame_count == decoded_frame_count assert isinstance( all(keyframe_index for keyframe_index in keyframe_indices), int ) decoded_gop_sizes = [ j - i for i, j in zip(keyframe_indices[:-1], keyframe_indices[1:]) ] if codec_name in ("dvvideo", "dnxhd") and all( i == 1 for i in decoded_gop_sizes ): pytest.skip() for i in decoded_gop_sizes: assert i == gop_size final_gop_size = decoded_frame_count - max(keyframe_indices) assert final_gop_size < gop_size def test_encoding_pcm_s24le(self) -> None: self.audio_encoding("pcm_s24le") def test_encoding_aac(self) -> None: self.audio_encoding("aac") def test_encoding_mp2(self) -> None: self.audio_encoding("mp2") def audio_encoding(self, codec_name: str) -> None: self._audio_encoding(codec_name=codec_name, channel_layout="stereo") self._audio_encoding( codec_name=codec_name, channel_layout=AudioLayout("stereo") ) def _audio_encoding( self, *, codec_name: str, channel_layout: str | AudioLayout ) -> None: try: codec = Codec(codec_name, "w") except UnknownCodecError: pytest.skip(f"Unknown codec: {codec_name}") ctx = codec.create(kind="audio") if ctx.codec.experimental: pytest.skip(f"Experimental codec: {codec_name}") assert ctx.codec.audio_formats sample_fmt = ctx.codec.audio_formats[-1].name sample_rate = 48000 ctx.time_base = Fraction(1) / sample_rate ctx.sample_rate = sample_rate ctx.format = sample_fmt ctx.layout = channel_layout ctx.open() resampler = AudioResampler(sample_fmt, channel_layout, sample_rate) container = av.open(fate_suite("audio-reference/chorusnoise_2ch_44kHz_s16.wav")) audio_stream = container.streams.audio[0] path = self.sandboxed(f"encoder.{codec_name}") samples = 0 packet_sizes = [] with open(path, "wb") as f: for frame in container.decode(audio_stream): resampled_frames = resampler.resample(frame) for resampled_frame in resampled_frames: assert resampled_frame.time_base == Fraction(1, 48000) samples += resampled_frame.samples for packet in ctx.encode(resampled_frame): assert packet.time_base == Fraction(1, 48000) packet_sizes.append(packet.size) f.write(packet) for packet in ctx.encode(None): assert packet.time_base == Fraction(1, 48000) packet_sizes.append(packet.size) f.write(packet) ctx = Codec(codec_name, "r").create("audio") ctx.sample_rate = sample_rate ctx.format = sample_fmt ctx.layout = channel_layout ctx.open() result_samples = 0 for frame in iter_raw_frames(path, packet_sizes, ctx): result_samples += frame.samples assert frame.sample_rate == sample_rate assert frame.layout.nb_channels == 2 PyAV-14.2.0/tests/test_colorspace.py000066400000000000000000000024301475734227400173400ustar00rootroot00000000000000import av from av.video.reformatter import ColorRange, Colorspace from .common import fate_suite def test_penguin_joke() -> None: container = av.open( fate_suite("amv/MTV_high_res_320x240_sample_Penguin_Joke_MTV_from_WMV.amv") ) stream = container.streams.video[0] assert stream.codec_context.color_range == 2 assert stream.codec_context.color_range == ColorRange.JPEG assert stream.codec_context.color_primaries == 2 assert stream.codec_context.color_trc == 2 assert stream.codec_context.colorspace == 5 assert stream.codec_context.colorspace == Colorspace.ITU601 for frame in container.decode(stream): assert frame.color_range == ColorRange.JPEG # a.k.a "pc" assert frame.colorspace == Colorspace.ITU601 return def test_sky_timelapse() -> None: container = av.open( av.datasets.curated("pexels/time-lapse-video-of-night-sky-857195.mp4") ) stream = container.streams.video[0] assert stream.disposition == av.stream.Disposition.default assert stream.codec_context.color_range == 1 assert stream.codec_context.color_range == ColorRange.MPEG assert stream.codec_context.color_primaries == 1 assert stream.codec_context.color_trc == 1 assert stream.codec_context.colorspace == 1 PyAV-14.2.0/tests/test_containerformat.py000066400000000000000000000037031475734227400204050ustar00rootroot00000000000000from av import ContainerFormat, formats_available, open def test_matroska() -> None: with open("test.mkv", "w") as container: assert container.default_video_codec != "none" assert container.default_audio_codec != "none" assert container.default_subtitle_codec == "ass" assert "ass" in container.supported_codecs fmt = ContainerFormat("matroska") assert fmt.is_input and fmt.is_output assert fmt.name == "matroska" assert fmt.long_name == "Matroska" assert "mkv" in fmt.extensions assert not fmt.no_file def test_mov() -> None: with open("test.mov", "w") as container: assert container.default_video_codec != "none" assert container.default_audio_codec != "none" assert container.default_subtitle_codec == "none" assert "h264" in container.supported_codecs fmt = ContainerFormat("mov") assert fmt.is_input and fmt.is_output assert fmt.name == "mov" assert fmt.long_name == "QuickTime / MOV" assert "mov" in fmt.extensions assert not fmt.no_file def test_gif() -> None: with open("test.gif", "w") as container: assert container.default_video_codec == "gif" assert container.default_audio_codec == "none" assert container.default_subtitle_codec == "none" assert "gif" in container.supported_codecs def test_stream_segment() -> None: # This format goes by two names, check both. fmt = ContainerFormat("stream_segment") assert not fmt.is_input and fmt.is_output assert fmt.name == "stream_segment" assert fmt.long_name == "streaming segment muxer" assert fmt.extensions == set() assert fmt.no_file fmt = ContainerFormat("ssegment") assert not fmt.is_input and fmt.is_output assert fmt.name == "ssegment" assert fmt.long_name == "streaming segment muxer" assert fmt.extensions == set() assert fmt.no_file def test_formats_available() -> None: assert formats_available PyAV-14.2.0/tests/test_decode.py000066400000000000000000000177641475734227400164510ustar00rootroot00000000000000import functools import os import pathlib from fractions import Fraction import numpy as np import pytest import av from .common import TestCase, fate_suite @functools.cache def make_h264_test_video(path: str) -> None: """Generates a black H264 test video with two streams for testing hardware decoding.""" # We generate a file here that's designed to be as compatible as possible with hardware # encoders. Hardware encoders are sometimes very picky and the errors we get are often # opaque, so there is nothing much we (PyAV) can do. The user needs to figure that out # if they want to use hwaccel. We only want to test the PyAV plumbing here. # Our video is H264, 1280x720p (note that some decoders have a minimum resolution limit), 24fps, # 8-bit yuv420p. pathlib.Path(path).parent.mkdir(parents=True, exist_ok=True) output_container = av.open(path, "w") streams = [] for _ in range(2): stream = output_container.add_stream("libx264", rate=24) assert isinstance(stream, av.VideoStream) stream.width = 1280 stream.height = 720 stream.pix_fmt = "yuv420p" streams.append(stream) for _ in range(24): frame = av.VideoFrame.from_ndarray( np.zeros((720, 1280, 3), dtype=np.uint8), format="rgb24" ) for stream in streams: for packet in stream.encode(frame): output_container.mux(packet) for stream in streams: for packet in stream.encode(): output_container.mux(packet) output_container.close() class TestDecode(TestCase): def test_decoded_video_frame_count(self) -> None: container = av.open(fate_suite("h264/interlaced_crop.mp4")) video_stream = next(s for s in container.streams if s.type == "video") assert video_stream is container.streams.video[0] frame_count = 0 for frame in container.decode(video_stream): frame_count += 1 assert frame_count == video_stream.frames def test_decode_audio_corrupt(self) -> None: # write an empty file path = self.sandboxed("empty.flac") with open(path, "wb"): pass packet_count = 0 frame_count = 0 with av.open(path) as container: for packet in container.demux(audio=0): for frame in packet.decode(): frame_count += 1 packet_count += 1 assert packet_count == 1 assert frame_count == 0 def test_decode_audio_sample_count(self) -> None: container = av.open(fate_suite("audio-reference/chorusnoise_2ch_44kHz_s16.wav")) audio_stream = next(s for s in container.streams if s.type == "audio") assert audio_stream is container.streams.audio[0] assert isinstance(audio_stream, av.AudioStream) sample_count = 0 for frame in container.decode(audio_stream): sample_count += frame.samples assert audio_stream.duration is not None assert audio_stream.time_base is not None total_samples = ( audio_stream.duration * audio_stream.sample_rate.numerator / audio_stream.time_base.denominator ) assert sample_count == total_samples def test_decoded_time_base(self) -> None: container = av.open(fate_suite("h264/interlaced_crop.mp4")) stream = container.streams.video[0] assert stream.time_base == Fraction(1, 25) for packet in container.demux(stream): for frame in packet.decode(): assert not isinstance(frame, av.subtitles.subtitle.SubtitleSet) assert packet.time_base == frame.time_base assert stream.time_base == frame.time_base return def test_decoded_motion_vectors(self) -> None: container = av.open(fate_suite("h264/interlaced_crop.mp4")) stream = container.streams.video[0] codec_context = stream.codec_context codec_context.options = {"flags2": "+export_mvs"} for frame in container.decode(stream): vectors = frame.side_data.get("MOTION_VECTORS") if frame.key_frame: # Key frame don't have motion vectors assert vectors is None else: assert vectors is not None and len(vectors) > 0 return def test_decoded_motion_vectors_no_flag(self) -> None: container = av.open(fate_suite("h264/interlaced_crop.mp4")) stream = container.streams.video[0] for frame in container.decode(stream): vectors = frame.side_data.get("MOTION_VECTORS") if not frame.key_frame: assert vectors is None return def test_decode_video_corrupt(self) -> None: # write an empty file path = self.sandboxed("empty.h264") with open(path, "wb"): pass packet_count = 0 frame_count = 0 with av.open(path) as container: for packet in container.demux(video=0): for frame in packet.decode(): frame_count += 1 packet_count += 1 assert packet_count == 1 assert frame_count == 0 def test_decode_close_then_use(self) -> None: container = av.open(fate_suite("h264/interlaced_crop.mp4")) container.close() # Check accessing every attribute either works or raises # an `AssertionError`. for attr in dir(container): with self.subTest(attr=attr): try: getattr(container, attr) except AssertionError: pass def test_flush_decoded_video_frame_count(self) -> None: container = av.open(fate_suite("h264/interlaced_crop.mp4")) video_stream = container.streams.video[0] # Decode the first GOP, which requires a flush to get all frames have_keyframe = False input_count = 0 output_count = 0 for packet in container.demux(video_stream): if packet.is_keyframe: if have_keyframe: break have_keyframe = True input_count += 1 for frame in video_stream.decode(packet): output_count += 1 # Check the test works as expected and requires a flush assert output_count < input_count for frame in video_stream.decode(None): # The Frame._time_base is not set by PyAV assert frame.time_base is None output_count += 1 assert output_count == input_count def test_no_side_data(self) -> None: container = av.open(fate_suite("h264/interlaced_crop.mp4")) frame = next(container.decode(video=0)) assert frame.rotation == 0 def test_side_data(self) -> None: container = av.open(fate_suite("mov/displaymatrix.mov")) frame = next(container.decode(video=0)) assert frame.rotation == -90 def test_hardware_decode(self) -> None: hwdevices_available = av.codec.hwaccel.hwdevices_available() if "HWACCEL_DEVICE_TYPE" not in os.environ: pytest.skip( "Set the HWACCEL_DEVICE_TYPE to run this test. " f"Options are {' '.join(hwdevices_available)}" ) HWACCEL_DEVICE_TYPE = os.environ["HWACCEL_DEVICE_TYPE"] assert ( HWACCEL_DEVICE_TYPE in hwdevices_available ), f"{HWACCEL_DEVICE_TYPE} not available" test_video_path = "tests/assets/black.mp4" make_h264_test_video(test_video_path) hwaccel = av.codec.hwaccel.HWAccel( device_type=HWACCEL_DEVICE_TYPE, allow_software_fallback=False ) container = av.open(test_video_path, hwaccel=hwaccel) video_stream = container.streams.video[0] assert video_stream.codec_context.is_hwaccel frame_count = 0 for frame in container.decode(video_stream): frame_count += 1 assert frame_count == video_stream.frames PyAV-14.2.0/tests/test_dictionary.py000066400000000000000000000005211475734227400173520ustar00rootroot00000000000000import pytest from av.dictionary import Dictionary def test_dictionary() -> None: d = Dictionary() d["key"] = "value" assert d["key"] == "value" assert "key" in d assert len(d) == 1 assert list(d) == ["key"] assert d.pop("key") == "value" pytest.raises(KeyError, d.pop, "key") assert len(d) == 0 PyAV-14.2.0/tests/test_doctests.py000066400000000000000000000027611475734227400170450ustar00rootroot00000000000000import doctest import pkgutil import re from unittest import TestCase import av import av.datasets def fix_doctests(suite): for case in suite._tests: # Add some more flags. case._dt_optionflags = ( (case._dt_optionflags or 0) | doctest.IGNORE_EXCEPTION_DETAIL | doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE ) case._dt_test.globs["av"] = av case._dt_test.globs["video_path"] = av.datasets.curated( "pexels/time-lapse-video-of-night-sky-857195.mp4" ) for example in case._dt_test.examples: # Remove b prefix from strings. if example.want.startswith("b'"): example.want = example.want[1:] def register_doctests(mod): if isinstance(mod, str): mod = __import__(mod, fromlist=[""]) try: suite = doctest.DocTestSuite(mod) except ValueError: return fix_doctests(suite) cls_name = "Test" + "".join(x.title() for x in mod.__name__.split(".")) cls = type(cls_name, (TestCase,), {}) for test in suite._tests: def func(self): return test.runTest() name = str("test_" + re.sub("[^a-zA-Z0-9]+", "_", test.id()).strip("_")) func.__name__ = name setattr(cls, name, func) globals()[cls_name] = cls for importer, mod_name, ispkg in pkgutil.walk_packages( path=av.__path__, prefix=av.__name__ + ".", onerror=lambda x: None ): register_doctests(mod_name) PyAV-14.2.0/tests/test_encode.py000066400000000000000000000406231475734227400164510ustar00rootroot00000000000000import io import math from fractions import Fraction import numpy as np import pytest import av from av import AudioFrame, VideoFrame from av.audio.stream import AudioStream from av.video.stream import VideoStream from .common import TestCase, fate_suite, has_pillow WIDTH = 320 HEIGHT = 240 DURATION = 48 def write_rgb_rotate(output: av.container.OutputContainer) -> None: if not has_pillow: pytest.skip() import PIL.Image as Image output.metadata["title"] = "container" output.metadata["key"] = "value" stream = output.add_stream("mpeg4", 24) stream.width = WIDTH stream.height = HEIGHT stream.pix_fmt = "yuv420p" for frame_i in range(DURATION): frame = VideoFrame(WIDTH, HEIGHT, "rgb24") image = Image.new( "RGB", (WIDTH, HEIGHT), ( int(255 * (0.5 + 0.5 * math.sin(frame_i / DURATION * 2 * math.pi))), int( 255 * ( 0.5 + 0.5 * math.sin(frame_i / DURATION * 2 * math.pi + 2 / 3 * math.pi) ) ), int( 255 * ( 0.5 + 0.5 * math.sin(frame_i / DURATION * 2 * math.pi + 4 / 3 * math.pi) ) ), ), ) frame.planes[0].update(image.tobytes()) for packet in stream.encode_lazy(frame): output.mux(packet) for packet in stream.encode_lazy(None): output.mux(packet) def assert_rgb_rotate( self, input_: av.container.InputContainer, is_dash: bool = False ) -> None: # Now inspect it a little. assert len(input_.streams) == 1 assert input_.metadata.get("Title" if is_dash else "title") == "container" assert input_.metadata.get("key") is None stream = input_.streams[0] if is_dash: # The DASH format doesn't provide a duration for the stream # and so the container duration (micro seconds) is checked instead assert input_.duration == 2000000 expected_average_rate = 24 expected_duration = None expected_frames = 0 expected_id = 0 else: expected_average_rate = 24 expected_duration = 24576 expected_frames = 48 expected_id = 1 # actual stream properties assert isinstance(stream, VideoStream) assert stream.average_rate == expected_average_rate assert stream.base_rate == 24 assert stream.duration == expected_duration assert stream.guessed_rate == 24 assert stream.frames == expected_frames assert stream.id == expected_id assert stream.index == 0 assert stream.profile == "Simple Profile" assert stream.start_time == 0 assert stream.time_base == Fraction(1, 12288) assert stream.type == "video" # codec context properties assert stream.codec.name == "mpeg4" assert stream.codec.long_name == "MPEG-4 part 2" assert stream.format.name == "yuv420p" assert stream.format.width == WIDTH assert stream.format.height == HEIGHT class TestBasicVideoEncoding(TestCase): def test_default_options(self) -> None: with av.open(self.sandboxed("output.mov"), "w") as output: stream = output.add_stream("mpeg4") assert stream in output.streams.video assert stream.average_rate == Fraction(24, 1) assert stream.time_base is None # codec context properties assert stream.format.height == 480 assert stream.format.name == "yuv420p" assert stream.format.width == 640 assert stream.height == 480 assert stream.pix_fmt == "yuv420p" assert stream.width == 640 def test_encoding(self) -> None: path = self.sandboxed("rgb_rotate.mov") with av.open(path, "w") as output: write_rgb_rotate(output) with av.open(path) as input: assert_rgb_rotate(self, input) def test_encoding_with_pts(self) -> None: path = self.sandboxed("video_with_pts.mov") with av.open(path, "w") as output: stream = output.add_stream("h264", 24) assert stream in output.streams.video stream.width = WIDTH stream.height = HEIGHT stream.pix_fmt = "yuv420p" for i in range(DURATION): frame = VideoFrame(WIDTH, HEIGHT, "rgb24") frame.pts = i * 2000 frame.time_base = Fraction(1, 48000) for packet in stream.encode(frame): assert packet.time_base == Fraction(1, 24) output.mux(packet) for packet in stream.encode(None): assert packet.time_base == Fraction(1, 24) output.mux(packet) def test_encoding_with_unicode_filename(self) -> None: path = self.sandboxed("¢∞§¶•ªº.mov") with av.open(path, "w") as output: write_rgb_rotate(output) with av.open(path) as input: assert_rgb_rotate(self, input) class TestBasicAudioEncoding(TestCase): def test_default_options(self) -> None: with av.open(self.sandboxed("output.mov"), "w") as output: stream = output.add_stream("mp2") assert stream in output.streams.audio assert stream.time_base is None # codec context properties assert stream.format.name == "s16" assert stream.sample_rate == 48000 def test_transcode(self) -> None: path = self.sandboxed("audio_transcode.mov") with av.open(path, "w") as output: output.metadata["title"] = "container" output.metadata["key"] = "value" sample_rate = 48000 channel_layout = "stereo" sample_fmt = "s16" stream = output.add_stream("mp2", sample_rate) assert stream in output.streams.audio ctx = stream.codec_context ctx.sample_rate = sample_rate stream.format = sample_fmt ctx.layout = channel_layout with av.open( fate_suite("audio-reference/chorusnoise_2ch_44kHz_s16.wav") ) as src: for frame in src.decode(audio=0): for packet in stream.encode(frame): output.mux(packet) for packet in stream.encode(None): output.mux(packet) with av.open(path) as container: assert len(container.streams) == 1 assert container.metadata.get("title") == "container" assert container.metadata.get("key") is None assert isinstance(container.streams[0], AudioStream) stream = container.streams[0] # codec context properties assert stream.format.name == "s16p" assert stream.sample_rate == sample_rate class TestSubtitleEncoding: def test_subtitle_muxing(self) -> None: input_ = av.open(fate_suite("sub/MovText_capability_tester.mp4")) in_stream = input_.streams.subtitles[0] output_bytes = io.BytesIO() output = av.open(output_bytes, "w", format="mp4") out_stream = output.add_stream_from_template(in_stream) for packet in input_.demux(in_stream): if packet.dts is None: continue packet.stream = out_stream output.mux(packet) output.close() output_bytes.seek(0) assert output_bytes.getvalue().startswith( b"\x00\x00\x00\x1cftypisom\x00\x00\x02\x00isomiso2mp41\x00\x00\x00\x08free" ) class TestEncodeStreamSemantics(TestCase): def test_stream_index(self) -> None: with av.open(self.sandboxed("output.mov"), "w") as output: vstream = output.add_stream("mpeg4", 24) assert vstream in output.streams.video vstream.pix_fmt = "yuv420p" vstream.width = 320 vstream.height = 240 astream = output.add_stream("mp2", 48000) assert astream in output.streams.audio astream.layout = "stereo" astream.format = "s16" assert vstream.index == 0 assert astream.index == 1 vframe = VideoFrame(320, 240, "yuv420p") vpacket = vstream.encode(vframe)[0] assert vpacket.stream is vstream assert vpacket.stream_index == 0 for i in range(10): if astream.frame_size != 0: frame_size = astream.frame_size else: # decoder didn't indicate constant frame size frame_size = 1000 aframe = AudioFrame("s16", "stereo", samples=frame_size) aframe.sample_rate = 48000 apackets = astream.encode(aframe) if apackets: apacket = apackets[0] break assert apacket.stream is astream assert apacket.stream_index == 1 def test_stream_audio_resample(self) -> None: with av.open(self.sandboxed("output.mov"), "w") as output: vstream = output.add_stream("mpeg4", 24) vstream.pix_fmt = "yuv420p" vstream.width = 320 vstream.height = 240 astream = output.add_stream("aac", sample_rate=8000, layout="mono") frame_size = 512 pts_expected = [-1024, 0, 512, 1024, 1536, 2048, 2560] pts = 0 for i in range(15): aframe = AudioFrame("s16", "mono", samples=frame_size) aframe.sample_rate = 8000 aframe.time_base = Fraction(1, 1000) aframe.pts = pts aframe.dts = pts pts += 32 apackets = astream.encode(aframe) if apackets: apacket = apackets[0] assert apacket.pts == pts_expected.pop(0) assert apacket.time_base == Fraction(1, 8000) apackets = astream.encode(None) if apackets: apacket = apackets[0] assert apacket.pts == pts_expected.pop(0) assert apacket.time_base == Fraction(1, 8000) def test_set_id_and_time_base(self) -> None: with av.open(self.sandboxed("output.mov"), "w") as output: stream = output.add_stream("mp2") assert stream in output.streams.audio # set id assert stream.id == 0 stream.id = 1 assert stream.id == 1 # set time_base assert stream.time_base is None stream.time_base = Fraction(1, 48000) assert stream.time_base == Fraction(1, 48000) def encode_file_with_max_b_frames(max_b_frames: int) -> io.BytesIO: """ Create an encoded video file (or file-like object) with the given maximum run of B frames. max_b_frames: non-negative integer which is the maximum allowed run of consecutive B frames. Returns: a file-like object. """ # Create a video file that is entirely arbitrary, but with the passed # max_b_frames parameter. file = io.BytesIO() container = av.open(file, mode="w", format="mp4") stream = container.add_stream("h264", rate=30) stream.width = 640 stream.height = 480 stream.pix_fmt = "yuv420p" stream.codec_context.gop_size = 15 stream.codec_context.max_b_frames = max_b_frames for i in range(50): array = np.empty((stream.height, stream.width, 3), dtype=np.uint8) # This appears to hit a complexity "sweet spot" that makes the codec # want to use B frames. array[:, :] = (i, 0, 255 - i) frame = av.VideoFrame.from_ndarray(array, format="rgb24") for packet in stream.encode(frame): container.mux(packet) for packet in stream.encode(): container.mux(packet) container.close() file.seek(0) return file def max_b_frame_run_in_file(file: io.BytesIO) -> int: """ Count the maximum run of B frames in a file (or file-like object). file: the file or file-like object in which to count the maximum run of B frames. The file should contain just one video stream. Returns: non-negative integer which is the maximum B frame run length. """ container = av.open(file, "r") stream = container.streams.video[0] max_b_frame_run = 0 b_frame_run = 0 for frame in container.decode(stream): if frame.pict_type == av.video.frame.PictureType.B: b_frame_run += 1 else: max_b_frame_run = max(max_b_frame_run, b_frame_run) b_frame_run = 0 # Outside chance that the longest run was at the end of the file. max_b_frame_run = max(max_b_frame_run, b_frame_run) container.close() return max_b_frame_run class TestMaxBFrameEncoding(TestCase): def test_max_b_frames(self) -> None: """ Test that we never get longer runs of B frames than we asked for with the max_b_frames property. """ for max_b_frames in range(4): file = encode_file_with_max_b_frames(max_b_frames) actual_max_b_frames = max_b_frame_run_in_file(file) assert actual_max_b_frames <= max_b_frames def encode_frames_with_qminmax(frames: list, shape: tuple, qminmax: tuple) -> int: """ Encode a video with the given quantiser limits, and return how many enocded bytes we made in total. frames: the frames to encode shape: the (numpy) shape of the video frames qminmax: two integers with 1 <= qmin <= 31 giving the min and max quantiser. Returns: total length of the encoded bytes. """ if av.codec.Codec("h264", "w").name != "libx264": pytest.skip() file = io.BytesIO() container = av.open(file, mode="w", format="mp4") stream = container.add_stream("h264", rate=30) stream.height, stream.width, _ = shape stream.pix_fmt = "yuv420p" stream.codec_context.gop_size = 15 stream.codec_context.qmin, stream.codec_context.qmax = qminmax bytes_encoded = 0 for frame in frames: for packet in stream.encode(frame): bytes_encoded += packet.size for packet in stream.encode(): bytes_encoded += packet.size container.close() return bytes_encoded class TestQminQmaxEncoding(TestCase): def test_qmin_qmax(self) -> None: """ Test that we can set the min and max quantisers, and the encoder is reacting correctly to them. Can't see a way to get hold of the quantisers in a decoded video, so instead we'll encode the same frames with decreasing quantisers, and check that the file size increases (by a noticeable factor) each time. """ # Make a random - but repeatable - 10 frame video sequence. np.random.seed(0) frames = [] shape = (480, 640, 3) for _ in range(10): frames.append( av.VideoFrame.from_ndarray( np.random.randint(0, 256, shape, dtype=np.uint8), format="rgb24" ) ) # Get the size of the encoded output for different quantisers. quantisers = ((31, 31), (15, 15), (1, 1)) sizes = [ encode_frames_with_qminmax(frames, shape, qminmax) for qminmax in quantisers ] factor = 1.3 # insist at least 30% larger each time assert all(small * factor < large for small, large in zip(sizes, sizes[1:])) class TestProfiles(TestCase): def test_profiles(self) -> None: """ Test that we can set different encoder profiles. """ # Let's try a video and an audio codec. file = io.BytesIO() codecs = ( ("h264", 30), ("aac", 48000), ) for codec_name, rate in codecs: print("Testing:", codec_name) container = av.open(file, mode="w", format="mp4") stream = container.add_stream(codec_name, rate=rate) assert len(stream.profiles) >= 1 # check that we're testing something! # It should be enough to test setting and retrieving the code. That means # libav has recognised the profile and set it correctly. for profile in stream.profiles: stream.profile = profile print("Set", profile, "got", stream.profile) assert stream.profile == profile PyAV-14.2.0/tests/test_errors.py000066400000000000000000000037131475734227400165270ustar00rootroot00000000000000import errno from traceback import format_exception_only import av from .common import is_windows def test_stringify() -> None: for cls in (av.ValueError, av.FileNotFoundError, av.DecoderNotFoundError): e = cls(1, "foo") assert f"{e}" == "[Errno 1] foo" assert f"{e!r}" == f"{cls.__name__}(1, 'foo')" assert ( format_exception_only(cls, e)[-1] == f"av.error.{cls.__name__}: [Errno 1] foo\n" ) for cls in (av.ValueError, av.FileNotFoundError, av.DecoderNotFoundError): e = cls(1, "foo", "bar.txt") assert f"{e}" == "[Errno 1] foo: 'bar.txt'" assert f"{e!r}" == f"{cls.__name__}(1, 'foo', 'bar.txt')" assert ( format_exception_only(cls, e)[-1] == f"av.error.{cls.__name__}: [Errno 1] foo: 'bar.txt'\n" ) def test_bases() -> None: assert issubclass(av.ValueError, ValueError) assert issubclass(av.ValueError, av.FFmpegError) assert issubclass(av.FileNotFoundError, FileNotFoundError) assert issubclass(av.FileNotFoundError, OSError) assert issubclass(av.FileNotFoundError, av.FFmpegError) def test_filenotfound(): """Catch using builtin class on Python 3.3""" try: av.open("does not exist") except FileNotFoundError as e: assert e.errno == errno.ENOENT if is_windows: assert e.strerror in ( "Error number -2 occurred", "No such file or directory", ) else: assert e.strerror == "No such file or directory" assert e.filename == "does not exist" else: assert False, "No exception raised!" def test_buffertoosmall() -> None: """Throw an exception from an enum.""" BUFFER_TOO_SMALL = 1397118274 try: av.error.err_check(-BUFFER_TOO_SMALL) except av.error.BufferTooSmallError as e: assert e.errno == BUFFER_TOO_SMALL else: assert False, "No exception raised!" PyAV-14.2.0/tests/test_file_probing.py000066400000000000000000000267461475734227400176650ustar00rootroot00000000000000from fractions import Fraction import av from .common import TestCase, fate_suite class TestAudioProbe(TestCase): def setUp(self): self.file = av.open(fate_suite("aac/latm_stereo_to_51.ts")) def test_container_probing(self) -> None: assert self.file.bit_rate == 269558 assert self.file.duration == 6165333 assert str(self.file.format) == "" assert self.file.format.name == "mpegts" assert self.file.format.long_name == "MPEG-TS (MPEG-2 Transport Stream)" assert self.file.metadata == {} assert self.file.size == 207740 assert self.file.start_time == 1400000 assert len(self.file.streams) == 1 def test_stream_probing(self) -> None: stream = self.file.streams[0] assert isinstance(stream, av.AudioStream) assert str(stream).startswith( " None: # write an empty file path = self.sandboxed("empty.flac") with open(path, "wb"): pass self.file = av.open(path, "r") def test_container_probing(self) -> None: assert self.file.bit_rate == 0 assert self.file.duration is None assert str(self.file.format) == "" assert self.file.format.name == "flac" assert self.file.format.long_name == "raw FLAC" assert self.file.metadata == {} assert self.file.size == 0 assert self.file.start_time is None assert len(self.file.streams) == 1 def test_stream_probing(self) -> None: stream = self.file.streams[0] assert isinstance(stream, av.AudioStream) assert str(stream).startswith( " None: self.file = av.open(fate_suite("mxf/track_01_v02.mxf")) def test_container_probing(self) -> None: assert self.file.bit_rate == 27872687 assert self.file.duration == 417083 assert str(self.file.format) == "" assert self.file.format.name == "mxf" assert self.file.format.long_name == "MXF (Material eXchange Format)" assert self.file.size == 1453153 assert self.file.start_time == 0 assert len(self.file.streams) == 4 for key, value in ( ("application_platform", "AAFSDK (MacOS X)"), ("comment_Comments", "example comment"), ("comment_UNC Path", "/Users/mark/Desktop/dnxhr_tracknames_export.aaf"), ("company_name", "Avid Technology, Inc."), ("generation_uid", "b6bcfcab-70ff-7331-c592-233869de11d2"), ("material_package_name", "Example.new.04"), ( "material_package_umid", "0x060A2B340101010101010F001300000057E19D16BA8202DB060E2B347F7F2A80", ), ("modification_date", "2016-09-20T20:33:26.000000Z"), ("operational_pattern_ul", "060e2b34.04010102.0d010201.10030000"), ("product_name", "Avid Media Composer 8.6.3.43955"), ("product_uid", "acfbf03a-4f42-a231-d0b7-c06ecd3d4ad7"), ("product_version", "Unknown version"), ("project_name", "UHD"), ("uid", "4482d537-4203-ea40-9e4e-08a22900dd39"), ): assert self.file.metadata.get(key) == value def test_stream_probing(self) -> None: stream = self.file.streams[0] assert str(stream).startswith(" at ") assert stream.duration == 37537 assert stream.frames == 0 assert stream.id == 1 assert stream.index == 0 assert stream.language is None assert stream.metadata == { "data_type": "video", "file_package_umid": "0x060A2B340101010101010F001300000057E19D16BA8302DB060E2B347F7F2A80", "track_name": "Base", } assert stream.profile is None assert stream.start_time == 0 assert stream.time_base == Fraction(1, 90000) assert stream.type == "data" assert not hasattr(stream, "codec") class TestSubtitleProbe(TestCase): def setUp(self) -> None: self.file = av.open(fate_suite("sub/MovText_capability_tester.mp4")) def test_container_probing(self) -> None: assert self.file.bit_rate == 810 assert self.file.duration == 8140000 assert str(self.file.format) == "" assert self.file.format.name == "mov,mp4,m4a,3gp,3g2,mj2" assert self.file.format.long_name == "QuickTime / MOV" assert self.file.metadata == { "compatible_brands": "isom", "creation_time": "2012-07-04T05:10:41.000000Z", "major_brand": "isom", "minor_version": "1", } assert self.file.size == 825 assert self.file.start_time is None assert len(self.file.streams) == 1 def test_stream_probing(self) -> None: stream = self.file.streams[0] assert str(stream).startswith(" None: self.file = av.open(fate_suite("mpeg2/mpeg2_field_encoding.ts")) def test_container_probing(self) -> None: assert self.file.bit_rate == 3950617 assert self.file.duration == 1620000 assert str(self.file.format) == "" assert self.file.format.name == "mpegts" assert self.file.format.long_name == "MPEG-TS (MPEG-2 Transport Stream)" assert self.file.metadata == {} assert self.file.size == 800000 assert self.file.start_time == 22953408322 assert len(self.file.streams) == 1 def test_stream_probing(self) -> None: stream = self.file.streams[0] assert isinstance(stream, av.video.stream.VideoStream) assert str(stream).startswith( " None: path = self.sandboxed("empty.h264") with open(path, "wb"): pass self.file = av.open(path) def test_container_probing(self) -> None: assert str(self.file.format) == "" assert self.file.format.name == "h264" assert self.file.format.long_name == "raw H.264 video" assert self.file.size == 0 assert self.file.bit_rate == 0 assert self.file.duration is None assert len(self.file.streams) == 1 assert self.file.start_time is None assert self.file.metadata == {} def test_stream_probing(self) -> None: stream = self.file.streams[0] assert isinstance(stream, av.VideoStream) assert str(stream).startswith(" AudioFrame: """ Generate audio frame representing part of the sinusoidal wave """ frame = AudioFrame(format=input_format, layout=layout, samples=frame_size) frame.sample_rate = sample_rate frame.pts = frame_num * frame_size for i in range(frame.layout.nb_channels): data = np.zeros(frame_size, dtype=format_dtypes[input_format]) for j in range(frame_size): data[j] = np.sin(2 * np.pi * (frame_num + j) * (i + 1) / float(frame_size)) frame.planes[i].update(data) # type: ignore return frame def pull_until_blocked(graph: Graph) -> list[av.VideoFrame]: frames: list[av.VideoFrame] = [] while True: try: frames.append(graph.vpull()) except av.FFmpegError as e: if e.errno != errno.EAGAIN: raise return frames class TestFilters(TestCase): def test_filter_descriptor(self) -> None: f = Filter("testsrc") assert f.name == "testsrc" assert f.description == "Generate test pattern." assert not f.dynamic_inputs assert len(f.inputs) == 0 assert not f.dynamic_outputs assert len(f.outputs) == 1 assert f.outputs[0].name == "default" assert f.outputs[0].type == "video" def test_dynamic_filter_descriptor(self): f = Filter("split") assert not f.dynamic_inputs assert len(f.inputs) == 1 assert f.dynamic_outputs assert len(f.outputs) == 0 def test_generator_graph(self): graph = Graph() src = graph.add("testsrc") lutrgb = graph.add( "lutrgb", "r=maxval+minval-val:g=maxval+minval-val:b=maxval+minval-val", name="invert", ) sink = graph.add("buffersink") src.link_to(lutrgb) lutrgb.link_to(sink) # pads and links assert src.outputs[0].link.output is lutrgb.inputs[0] assert lutrgb.inputs[0].link.input is src.outputs[0] frame = sink.pull() assert isinstance(frame, VideoFrame) if has_pillow: frame.to_image().save(self.sandboxed("mandelbrot2.png")) def test_auto_find_sink(self) -> None: graph = Graph() src = graph.add("testsrc") src.link_to(graph.add("buffersink")) graph.configure() frame = graph.vpull() if has_pillow: frame.to_image().save(self.sandboxed("mandelbrot3.png")) def test_delegate_sink(self) -> None: graph = Graph() src = graph.add("testsrc") src.link_to(graph.add("buffersink")) graph.configure() frame = src.pull() assert isinstance(frame, av.VideoFrame) if has_pillow: frame.to_image().save(self.sandboxed("mandelbrot4.png")) def test_audio_buffer_sink(self): graph = Graph() audio_buffer = graph.add_abuffer( format="fltp", sample_rate=48000, layout="stereo", time_base=Fraction(1, 48000), ) audio_buffer.link_to(graph.add("abuffersink")) graph.configure() try: graph.pull() except OSError as e: # we haven't pushed any input so expect no frames / EAGAIN if e.errno != errno.EAGAIN: raise def test_audio_buffer_resample(self) -> None: graph = Graph() graph.link_nodes( graph.add_abuffer( format="fltp", sample_rate=48000, layout="stereo", time_base=Fraction(1, 48000), ), graph.add( "aformat", "sample_fmts=s16:sample_rates=44100:channel_layouts=stereo" ), graph.add("abuffersink"), ).configure() graph.push( generate_audio_frame( 0, input_format="fltp", layout="stereo", sample_rate=48000 ) ) out_frame = graph.pull() assert isinstance(out_frame, av.AudioFrame) assert out_frame.format.name == "s16" assert out_frame.layout.name == "stereo" assert out_frame.sample_rate == 44100 def test_audio_buffer_frame_size(self): graph = Graph() graph.link_nodes( graph.add_abuffer( format="fltp", sample_rate=48000, layout="stereo", time_base=Fraction(1, 48000), ), graph.add("abuffersink"), ).configure() graph.set_audio_frame_size(256) graph.push( generate_audio_frame( 0, input_format="fltp", layout="stereo", sample_rate=48000, frame_size=1024, ) ) out_frame = graph.pull() assert out_frame.sample_rate == 48000 assert out_frame.samples == 256 def test_audio_buffer_volume_filter(self): graph = Graph() graph.link_nodes( graph.add_abuffer( format="fltp", sample_rate=48000, layout="stereo", time_base=Fraction(1, 48000), ), graph.add("volume", volume="0.5"), graph.add("abuffersink"), ).configure() input_frame = generate_audio_frame( 0, input_format="fltp", layout="stereo", sample_rate=48000 ) graph.push(input_frame) out_frame = graph.pull() assert out_frame.format.name == "fltp" assert out_frame.layout.name == "stereo" assert out_frame.sample_rate == 48000 input_data = input_frame.to_ndarray() output_data = out_frame.to_ndarray() assert np.allclose(input_data * 0.5, output_data) def test_video_buffer(self): input_container = av.open(format="lavfi", file="color=c=pink:duration=1:r=30") input_video_stream = input_container.streams.video[0] graph = av.filter.Graph() buffer = graph.add_buffer(template=input_video_stream) bwdif = graph.add("bwdif", "send_field:tff:all") buffersink = graph.add("buffersink") buffer.link_to(bwdif) bwdif.link_to(buffersink) graph.configure() for frame in input_container.decode(): assert frame.time_base == Fraction(1, 30) graph.vpush(frame) filtered_frames = pull_until_blocked(graph) if frame.pts == 0: # no output for the first input frame assert len(filtered_frames) == 0 else: # we expect two filtered frames per input frame assert len(filtered_frames) == 2 assert filtered_frames[0].pts == (frame.pts - 1) * 2 assert filtered_frames[0].time_base == Fraction(1, 60) assert filtered_frames[1].pts == (frame.pts - 1) * 2 + 1 assert filtered_frames[1].time_base == Fraction(1, 60) def test_EOF(self) -> None: input_container = av.open(format="lavfi", file="color=c=pink:duration=1:r=30") video_stream = input_container.streams.video[0] graph = av.filter.Graph() video_in = graph.add_buffer(template=video_stream) palette_gen_filter = graph.add("palettegen") video_out = graph.add("buffersink") video_in.link_to(palette_gen_filter) palette_gen_filter.link_to(video_out) graph.configure() for frame in input_container.decode(video=0): graph.vpush(frame) graph.vpush(None) # if we do not push None, we get a BlockingIOError palette_frame = graph.vpull() assert isinstance(palette_frame, av.VideoFrame) assert palette_frame.width == 16 assert palette_frame.height == 16 PyAV-14.2.0/tests/test_logging.py000066400000000000000000000041651475734227400166430ustar00rootroot00000000000000import errno import logging import threading import av.error import av.logging def do_log(message: str) -> None: av.logging.log(av.logging.INFO, "test", message) def test_adapt_level() -> None: assert av.logging.adapt_level(av.logging.ERROR) == logging.ERROR assert av.logging.adapt_level(av.logging.WARNING) == logging.WARNING assert ( av.logging.adapt_level((av.logging.WARNING + av.logging.ERROR) // 2) == logging.WARNING ) def test_threaded_captures() -> None: av.logging.set_level(av.logging.VERBOSE) with av.logging.Capture(local=True) as logs: do_log("main") thread = threading.Thread(target=do_log, args=("thread",)) thread.start() thread.join() assert (av.logging.INFO, "test", "main") in logs av.logging.set_level(None) def test_global_captures() -> None: av.logging.set_level(av.logging.VERBOSE) with av.logging.Capture(local=False) as logs: do_log("main") thread = threading.Thread(target=do_log, args=("thread",)) thread.start() thread.join() assert (av.logging.INFO, "test", "main") in logs assert (av.logging.INFO, "test", "thread") in logs av.logging.set_level(None) def test_repeats() -> None: av.logging.set_level(av.logging.VERBOSE) with av.logging.Capture() as logs: do_log("foo") do_log("foo") do_log("bar") do_log("bar") do_log("bar") do_log("baz") logs = [log for log in logs if log[1] == "test"] assert logs == [ (av.logging.INFO, "test", "foo"), (av.logging.INFO, "test", "foo"), (av.logging.INFO, "test", "bar"), (av.logging.INFO, "test", "bar (repeated 2 more times)"), (av.logging.INFO, "test", "baz"), ] av.logging.set_level(None) def test_error() -> None: av.logging.set_level(av.logging.VERBOSE) log = (av.logging.ERROR, "test", "This is a test.") av.logging.log(*log) try: av.error.err_check(-errno.EPERM) except av.error.PermissionError as e: assert e.log == log else: assert False av.logging.set_level(None) PyAV-14.2.0/tests/test_open.py000066400000000000000000000016121475734227400161500ustar00rootroot00000000000000from pathlib import Path import av from .common import fate_suite def test_path_input() -> None: path = Path(fate_suite("h264/interlaced_crop.mp4")) assert isinstance(path, Path) container = av.open(path) assert type(container) is av.container.InputContainer def test_str_input() -> None: path = fate_suite("h264/interlaced_crop.mp4") assert type(path) is str container = av.open(path) assert type(container) is av.container.InputContainer def test_path_output() -> None: path = Path(fate_suite("h264/interlaced_crop.mp4")) assert isinstance(path, Path) container = av.open(path, "w") assert type(container) is av.container.OutputContainer def test_str_output() -> None: path = fate_suite("h264/interlaced_crop.mp4") assert type(path) is str container = av.open(path, "w") assert type(container) is av.container.OutputContainer PyAV-14.2.0/tests/test_options.py000066400000000000000000000010241475734227400166770ustar00rootroot00000000000000from av import ContainerFormat from av.option import Option, OptionType class TestOptions: def test_mov_options(self) -> None: mov = ContainerFormat("mov") options = mov.descriptor.options # type: ignore by_name = {opt.name: opt for opt in options} opt = by_name.get("use_absolute_path") assert isinstance(opt, Option) assert opt.name == "use_absolute_path" # This was not a good option to actually test. assert opt.type in (OptionType.BOOL, OptionType.INT) PyAV-14.2.0/tests/test_packet.py000066400000000000000000000036611475734227400164640ustar00rootroot00000000000000import av from .common import fate_suite class TestProperties: def test_is_keyframe(self) -> None: with av.open(fate_suite("h264/interlaced_crop.mp4")) as container: stream = container.streams.video[0] for i, packet in enumerate(container.demux(stream)): if i in (0, 21, 45, 69, 93, 117): assert packet.is_keyframe else: assert not packet.is_keyframe def test_is_corrupt(self) -> None: with av.open(fate_suite("mov/white_zombie_scrunch-part.mov")) as container: stream = container.streams.video[0] for i, packet in enumerate(container.demux(stream)): if i == 65: assert packet.is_corrupt else: assert not packet.is_corrupt def test_is_discard(self) -> None: with av.open(fate_suite("mov/mov-1elist-ends-last-bframe.mov")) as container: stream = container.streams.video[0] for i, packet in enumerate(container.demux(stream)): if i == 46: assert packet.is_discard else: assert not packet.is_discard def test_is_disposable(self) -> None: with av.open(fate_suite("hap/HAPQA_NoSnappy_127x1.mov")) as container: stream = container.streams.video[0] for i, packet in enumerate(container.demux(stream)): if i == 0: assert packet.is_disposable else: assert not packet.is_disposable def test_set_duration(self) -> None: with av.open(fate_suite("h264/interlaced_crop.mp4")) as container: for packet in container.demux(): assert packet.duration is not None old_duration = packet.duration packet.duration += 10 assert packet.duration == old_duration + 10 PyAV-14.2.0/tests/test_python_io.py000066400000000000000000000252331475734227400172240ustar00rootroot00000000000000from __future__ import annotations import functools import io import types from io import BytesIO from re import escape from typing import TYPE_CHECKING import pytest import av from .common import TestCase, fate_png, fate_suite, has_pillow, run_in_sandbox from .test_encode import assert_rgb_rotate, write_rgb_rotate if TYPE_CHECKING: from collections.abc import Callable class MethodLogger: def __init__(self, obj: object) -> None: self._obj = obj self._log: list[tuple[str, object]] = [] def __getattr__(self, name: str) -> object: def _method(name: str, meth: Callable, *args, **kwargs) -> object: self._log.append((name, args)) return meth(*args, **kwargs) value = getattr(self._obj, name) if isinstance( value, ( types.MethodType, types.FunctionType, types.BuiltinFunctionType, types.BuiltinMethodType, ), ): return functools.partial(_method, name, value) else: self._log.append(("__getattr__", (name,))) return value def _filter(self, type_: str) -> list[tuple[str, object]]: return [log for log in self._log if log[0] == type_] class BrokenBuffer(BytesIO): """ Buffer which can be "broken" to simulate an I/O error. """ broken = False def write(self, data): if self.broken: raise OSError("It's broken") else: return super().write(data) class ReadOnlyBuffer: """ Minimal buffer which *only* implements the read() method. """ def __init__(self, data) -> None: self.data = data def read(self, n): data = self.data[0:n] self.data = self.data[n:] return data class ReadOnlyPipe(BytesIO): """ Buffer which behaves like a readable pipe. """ @property def name(self) -> int: return 123 def seekable(self) -> bool: return False def writable(self) -> bool: return False class WriteOnlyPipe(BytesIO): """ Buffer which behaves like a writable pipe. """ @property def name(self) -> int: return 123 def readable(self) -> bool: return False def seekable(self) -> bool: return False def read( fh: io.BufferedReader | BytesIO | ReadOnlyBuffer, seekable: bool = True ) -> None: wrapped = MethodLogger(fh) with av.open(wrapped, "r") as container: assert container.format.name == "mpegts" assert container.format.long_name == "MPEG-TS (MPEG-2 Transport Stream)" assert len(container.streams) == 1 if seekable: assert container.size == 800000 assert container.metadata == {} # Check method calls. assert wrapped._filter("read") if seekable: assert wrapped._filter("seek") def write(fh: io.BufferedWriter | BytesIO) -> None: wrapped = MethodLogger(fh) with av.open(wrapped, "w", "mp4") as container: write_rgb_rotate(container) # Check method calls. assert wrapped._filter("write") assert wrapped._filter("seek") # Using a custom protocol will avoid the DASH muxer detecting or defaulting to a # file: protocol and enabling the use of temporary files and renaming. CUSTOM_IO_PROTOCOL = "pyavtest://" class CustomIOLogger: """Log calls to open a file as well as method calls on the files""" def __init__(self) -> None: self._log: list[tuple[object, dict]] = [] self._method_log: list[MethodLogger] = [] def __call__(self, *args, **kwargs) -> MethodLogger: self._log.append((args, kwargs)) self._method_log.append(self.io_open(*args, **kwargs)) return self._method_log[-1] def io_open(self, url: str, flags, options: object) -> MethodLogger: # Remove the protocol prefix to reveal the local filename if CUSTOM_IO_PROTOCOL in url: url = url.split(CUSTOM_IO_PROTOCOL, 1)[1] if (flags & 3) == 3: mode = "r+b" elif (flags & 1) == 1: mode = "rb" elif (flags & 2) == 2: mode = "wb" else: raise RuntimeError(f"Unsupported io open mode {flags}") return MethodLogger(open(url, mode)) class TestPythonIO(TestCase): def test_basic_errors(self) -> None: self.assertRaises(Exception, av.open, None) self.assertRaises(Exception, av.open, None, "w") def test_reading_from_buffer(self) -> None: with open(fate_suite("mpeg2/mpeg2_field_encoding.ts"), "rb") as fh: buf = BytesIO(fh.read()) read(buf, seekable=True) def test_reading_from_buffer_no_seek(self) -> None: with open(fate_suite("mpeg2/mpeg2_field_encoding.ts"), "rb") as fh: buf = ReadOnlyBuffer(fh.read()) read(buf, seekable=False) def test_reading_from_file(self) -> None: with open(fate_suite("mpeg2/mpeg2_field_encoding.ts"), "rb") as fh: read(fh, seekable=True) def test_reading_from_pipe_readonly(self) -> None: with open(fate_suite("mpeg2/mpeg2_field_encoding.ts"), "rb") as fh: buf = ReadOnlyPipe(fh.read()) read(buf, seekable=False) def test_reading_from_write_readonly(self) -> None: with open(fate_suite("mpeg2/mpeg2_field_encoding.ts"), "rb") as fh: buf = WriteOnlyPipe(fh.read()) msg = escape("File object has no read() method, or readable() returned False.") with pytest.raises(ValueError, match=msg): read(buf, seekable=False) def test_writing_to_buffer(self) -> None: buf = BytesIO() write(buf) # Check contents. assert buf.tell() buf.seek(0) with av.open(buf, "r") as container: assert_rgb_rotate(self, container) def test_writing_to_buffer_broken(self) -> None: buf = BrokenBuffer() with pytest.raises(OSError): with av.open(buf, "w", "mp4") as container: write_rgb_rotate(container) # break I/O buf.broken = True def test_writing_to_buffer_broken_with_close(self) -> None: buf = BrokenBuffer() with av.open(buf, "w", "mp4") as container: write_rgb_rotate(container) # break I/O buf.broken = True # try to close file with pytest.raises(OSError): container.close() @run_in_sandbox def test_writing_to_custom_io_dash(self) -> None: # Custom I/O that opens file and logs calls wrapped_custom_io = CustomIOLogger() output_filename = "custom_io_output.mpd" # Write a DASH package using the custom IO. Prefix the name with CUSTOM_IO_PROTOCOL to # avoid temporary file and renaming. with av.open( CUSTOM_IO_PROTOCOL + output_filename, "w", io_open=wrapped_custom_io ) as container: write_rgb_rotate(container) # Check that at least 3 files were opened using the custom IO: # "output_filename", init-stream0.m4s and chunk-stream-0x.m4s assert len(wrapped_custom_io._log) >= 3 assert len(wrapped_custom_io._method_log) >= 3 # Check that all files were written to all_write = all( method_log._filter("write") for method_log in wrapped_custom_io._method_log ) assert all_write # Check that all files were closed all_closed = all( method_log._filter("close") for method_log in wrapped_custom_io._method_log ) assert all_closed # Check contents. # Note that the dash demuxer doesn't support custom I/O. with av.open(output_filename, "r") as container: assert_rgb_rotate(self, container, is_dash=True) def test_writing_to_custom_io_image2(self) -> None: if not has_pillow: pytest.skip() import PIL.Image as Image # Custom I/O that opens file and logs calls wrapped_custom_io = CustomIOLogger() image = Image.open(fate_png()) input_frame = av.VideoFrame.from_image(image) frame_count = 10 sequence_filename = self.sandboxed("test%d.png") width = 160 height = 90 # Write a PNG image sequence using the custom IO with av.open( sequence_filename, "w", "image2", io_open=wrapped_custom_io ) as output: stream = output.add_stream("png") stream.width = width stream.height = height stream.pix_fmt = "rgb24" for _ in range(frame_count): for packet in stream.encode(input_frame): output.mux(packet) # Check that "frame_count" files were opened using the custom IO assert len(wrapped_custom_io._log) == frame_count assert len(wrapped_custom_io._method_log) == frame_count # Check that all files were written to all_write = all( method_log._filter("write") for method_log in wrapped_custom_io._method_log ) assert all_write # Check that all files were closed all_closed = all( method_log._filter("close") for method_log in wrapped_custom_io._method_log ) assert all_closed # Check contents. with av.open(sequence_filename, "r", "image2") as container: assert len(container.streams) == 1 assert isinstance(container.streams[0], av.video.stream.VideoStream) stream = container.streams[0] assert stream.duration == frame_count assert stream.type == "video" # codec context properties assert stream.codec.name == "png" assert stream.format.name == "rgb24" assert stream.format.width == width assert stream.format.height == height def test_writing_to_file(self) -> None: path = self.sandboxed("writing.mp4") with open(path, "wb") as fh: write(fh) # Check contents. with av.open(path) as container: assert_rgb_rotate(self, container) def test_writing_to_pipe_readonly(self) -> None: buf = ReadOnlyPipe() with pytest.raises( ValueError, match=escape( "File object has no write() method, or writable() returned False." ), ) as cm: write(buf) def test_writing_to_pipe_writeonly(self) -> None: av.logging.set_level(av.logging.VERBOSE) buf = WriteOnlyPipe() with pytest.raises( ValueError, match=escape("[mp4] muxer does not support non seekable output") ): write(buf) av.logging.set_level(None) PyAV-14.2.0/tests/test_seek.py000066400000000000000000000117251475734227400161440ustar00rootroot00000000000000import av from .common import TestCase, fate_suite def timestamp_to_frame(timestamp: int, stream: av.video.stream.VideoStream) -> float: fps = stream.average_rate time_base = stream.time_base start_time = stream.start_time assert time_base is not None and start_time is not None and fps is not None return (timestamp - start_time) * float(time_base) * float(fps) class TestSeek(TestCase): def test_seek_float(self) -> None: container = av.open(fate_suite("h264/interlaced_crop.mp4")) self.assertRaises(TypeError, container.seek, 1.0) def test_seek_int64(self) -> None: # Assert that it accepts large values. # Issue 251 pointed this out. container = av.open(fate_suite("h264/interlaced_crop.mp4")) container.seek(2**32) def test_seek_start(self) -> None: container = av.open(fate_suite("h264/interlaced_crop.mp4")) # count all the packets total_packet_count = 0 for packet in container.demux(): total_packet_count += 1 # seek to beginning container.seek(-1) # count packets again seek_packet_count = 0 for packet in container.demux(): seek_packet_count += 1 assert total_packet_count == seek_packet_count def test_seek_middle(self) -> None: container = av.open(fate_suite("h264/interlaced_crop.mp4")) assert container.duration is not None # count all the packets total_packet_count = 0 for packet in container.demux(): total_packet_count += 1 # seek to middle container.seek(container.duration // 2) seek_packet_count = 0 for packet in container.demux(): seek_packet_count += 1 assert seek_packet_count < total_packet_count def test_seek_end(self) -> None: container = av.open(fate_suite("h264/interlaced_crop.mp4")) assert container.duration is not None # seek to middle container.seek(container.duration // 2) middle_packet_count = 0 for packet in container.demux(): middle_packet_count += 1 # you can't really seek to to end but you can to the last keyframe container.seek(container.duration) seek_packet_count = 0 for packet in container.demux(): seek_packet_count += 1 # there should be some packet because we're seeking to the last keyframe assert seek_packet_count > 0 assert seek_packet_count < middle_packet_count def test_decode_half(self) -> None: container = av.open(fate_suite("h264/interlaced_crop.mp4")) video_stream = container.streams.video[0] total_frame_count = 0 for frame in container.decode(video_stream): total_frame_count += 1 assert video_stream.frames == total_frame_count assert video_stream.average_rate is not None # set target frame to middle frame target_frame = total_frame_count // 2 target_timestamp = int( (target_frame * av.time_base) / video_stream.average_rate ) # should seek to nearest keyframe before target_timestamp container.seek(target_timestamp) current_frame = None frame_count = 0 for frame in container.decode(video_stream): if current_frame is None: current_frame = timestamp_to_frame(frame.pts, video_stream) else: current_frame += 1 # start counting once we reach the target frame if current_frame is not None and current_frame >= target_frame: frame_count += 1 assert frame_count == total_frame_count - target_frame def test_stream_seek(self) -> None: container = av.open(fate_suite("h264/interlaced_crop.mp4")) video_stream = container.streams.video[0] assert video_stream.time_base is not None assert video_stream.start_time is not None assert video_stream.average_rate is not None total_frame_count = 0 for frame in container.decode(video_stream): total_frame_count += 1 target_frame = total_frame_count // 2 time_base = float(video_stream.time_base) target_sec = target_frame * 1 / float(video_stream.average_rate) target_timestamp = int(target_sec / time_base) + video_stream.start_time container.seek(target_timestamp, stream=video_stream) current_frame = None frame_count = 0 for frame in container.decode(video_stream): if current_frame is None: assert frame.pts is not None current_frame = timestamp_to_frame(frame.pts, video_stream) else: current_frame += 1 # start counting once we reach the target frame if current_frame is not None and current_frame >= target_frame: frame_count += 1 assert frame_count == total_frame_count - target_frame PyAV-14.2.0/tests/test_streams.py000066400000000000000000000117531475734227400166740ustar00rootroot00000000000000import os from fractions import Fraction import pytest import av from .common import fate_suite class TestStreams: @pytest.fixture(autouse=True) def cleanup(self): yield for file in ("data.ts", "out.mkv"): if os.path.exists(file): os.remove(file) def test_stream_tuples(self) -> None: for fate_name in ("h264/interlaced_crop.mp4",): container = av.open(fate_suite(fate_name)) video_streams = tuple([s for s in container.streams if s.type == "video"]) assert video_streams == container.streams.video audio_streams = tuple([s for s in container.streams if s.type == "audio"]) assert audio_streams == container.streams.audio def test_loudnorm(self) -> None: container = av.open( fate_suite("amv/MTV_high_res_320x240_sample_Penguin_Joke_MTV_from_WMV.amv") ) audio = container.streams.audio[0] stats = av.filter.loudnorm.stats("i=-24.0:lra=7.0:tp=-2.0", audio) assert isinstance(stats, bytes) and len(stats) > 30 assert b"inf" not in stats assert b'"input_i"' in stats def test_selection(self) -> None: container = av.open( fate_suite("amv/MTV_high_res_320x240_sample_Penguin_Joke_MTV_from_WMV.amv") ) video = container.streams.video[0] video.thread_type = av.codec.context.ThreadType.AUTO assert video.thread_type == av.codec.context.ThreadType.AUTO video.thread_type = 0x03 assert video.thread_type == av.codec.context.ThreadType.AUTO video.thread_type = "AUTO" assert video.thread_type == av.codec.context.ThreadType.AUTO audio = container.streams.audio[0] assert [video] == container.streams.get(video=0) assert [video] == container.streams.get(video=(0,)) assert video == container.streams.best("video") assert audio == container.streams.best("audio") container = av.open(fate_suite("sub/MovText_capability_tester.mp4")) subtitle = container.streams.subtitles[0] assert subtitle == container.streams.best("subtitle") container = av.open(fate_suite("mxf/track_01_v02.mxf")) data = container.streams.data[0] assert data == container.streams.best("data") def test_printing_video_stream(self) -> None: input_ = av.open( fate_suite("amv/MTV_high_res_320x240_sample_Penguin_Joke_MTV_from_WMV.amv") ) container = av.open("out.mkv", "w") video_stream = container.add_stream("h264", rate=30) encoder = video_stream.codec.name video_stream.width = input_.streams.video[0].width video_stream.height = input_.streams.video[0].height video_stream.pix_fmt = "yuv420p" for frame in input_.decode(video=0): container.mux(video_stream.encode(frame)) break repr = f"{video_stream}" assert repr.startswith(f"") container.close() input_.close() def test_printing_video_stream2(self) -> None: input_ = av.open(fate_suite("h264/interlaced_crop.mp4")) input_stream = input_.streams.video[0] container = av.open("out.mkv", "w") video_stream = container.add_stream_from_template(input_stream) encoder = video_stream.codec.name for frame in input_.decode(video=0): container.mux(video_stream.encode(frame)) break repr = f"{video_stream}" assert repr.startswith(f"") container.close() input_.close() def test_data_stream(self) -> None: # First test writing and reading a simple data stream container1 = av.open("data.ts", "w") data_stream = container1.add_data_stream() test_data = [b"test data 1", b"test data 2", b"test data 3"] for i, data_ in enumerate(test_data): packet = av.Packet(data_) packet.pts = i packet.stream = data_stream container1.mux(packet) container1.close() # Test reading back the data stream container = av.open("data.ts") # Test best stream selection data = container.streams.best("data") assert data == container.streams.data[0] # Test get method assert [data] == container.streams.get(data=0) assert [data] == container.streams.get(data=(0,)) # Verify we can read back all the packets, ignoring empty ones packets = [p for p in container.demux(data) if bytes(p)] assert len(packets) == len(test_data) for packet, original_data in zip(packets, test_data): assert bytes(packet) == original_data # Test string representation repr = f"{data_stream}" assert repr.startswith("") container.close() PyAV-14.2.0/tests/test_subtitles.py000066400000000000000000000040471475734227400172320ustar00rootroot00000000000000import av from av.subtitles.subtitle import AssSubtitle, BitmapSubtitle from .common import fate_suite class TestSubtitle: def test_movtext(self) -> None: path = fate_suite("sub/MovText_capability_tester.mp4") subs = [] with av.open(path) as container: for packet in container.demux(): subs.extend(packet.decode()) assert len(subs) == 3 subset = subs[0] assert subset.format == 1 assert subset.pts == 970000 assert subset.start_display_time == 0 assert subset.end_display_time == 1570 sub = subset[0] assert isinstance(sub, AssSubtitle) assert sub.type == b"ass" assert sub.text == b"" assert sub.ass == b"0,0,Default,,0,0,0,,- Test 1.\\N- Test 2." assert sub.dialogue == b"- Test 1.\n- Test 2." def test_vobsub(self) -> None: path = fate_suite("sub/vobsub.sub") subs = [] with av.open(path) as container: for packet in container.demux(): subs.extend(packet.decode()) assert len(subs) == 43 subset = subs[0] assert subset.format == 0 assert subset.pts == 132499044 assert subset.start_display_time == 0 assert subset.end_display_time == 4960 sub = subset[0] assert isinstance(sub, BitmapSubtitle) assert sub.type == b"bitmap" assert sub.x == 259 assert sub.y == 379 assert sub.width == 200 assert sub.height == 24 assert sub.nb_colors == 4 bms = sub.planes assert len(bms) == 1 assert len(memoryview(bms[0])) == 4800 # type: ignore def test_subtitle_flush(self) -> None: path = fate_suite("sub/MovText_capability_tester.mp4") subs = [] with av.open(path) as container: stream = container.streams.subtitles[0] for packet in container.demux(stream): subs.extend(stream.decode(packet)) subs.extend(stream.decode()) assert len(subs) == 3 PyAV-14.2.0/tests/test_timeout.py000066400000000000000000000040651475734227400167020ustar00rootroot00000000000000import threading import time from http.server import BaseHTTPRequestHandler from socketserver import TCPServer import av from .common import TestCase, fate_suite PORT = 8002 CONTENT = open(fate_suite("mpeg2/mpeg2_field_encoding.ts"), "rb").read() # Needs to be long enough for all host OSes to deal. TIMEOUT = 0.25 DELAY = 4 * TIMEOUT class HttpServer(TCPServer): allow_reuse_address = True def handle_error(self, request: object, client_address: object) -> None: pass class SlowRequestHandler(BaseHTTPRequestHandler): def do_GET(self) -> None: time.sleep(DELAY) self.send_response(200) self.send_header("Content-Length", str(len(CONTENT))) self.end_headers() self.wfile.write(CONTENT) def log_message(self, format: object, *args: object) -> None: pass class TestTimeout(TestCase): def setUp(cls) -> None: cls._server = HttpServer(("", PORT), SlowRequestHandler) cls._thread = threading.Thread(target=cls._server.handle_request) cls._thread.daemon = True # Make sure the tests will exit. cls._thread.start() def tearDown(cls) -> None: cls._thread.join(1) # Can't wait forever or the tests will never exit. cls._server.server_close() def test_no_timeout(self) -> None: start = time.time() av.open(f"http://localhost:{PORT}/mpeg2_field_encoding.ts") duration = time.time() - start assert duration > DELAY def test_open_timeout(self) -> None: with self.assertRaises(av.ExitError): start = time.time() av.open(f"http://localhost:{PORT}/mpeg2_field_encoding.ts", timeout=TIMEOUT) duration = time.time() - start assert duration < DELAY def test_open_timeout_2(self) -> None: with self.assertRaises(av.ExitError): start = time.time() av.open( f"http://localhost:{PORT}/mpeg2_field_encoding.ts", timeout=(TIMEOUT, None), ) duration = time.time() - start assert duration < DELAY PyAV-14.2.0/tests/test_videoformat.py000066400000000000000000000065351475734227400175370ustar00rootroot00000000000000from av import VideoFormat from .common import TestCase class TestVideoFormats(TestCase): def test_invalid_pixel_format(self): with self.assertRaises(ValueError) as cm: VideoFormat("__unknown_pix_fmt", 640, 480) assert str(cm.exception) == "not a pixel format: '__unknown_pix_fmt'" def test_rgb24_inspection(self) -> None: fmt = VideoFormat("rgb24", 640, 480) assert fmt.name == "rgb24" assert len(fmt.components) == 3 assert not fmt.is_planar assert not fmt.has_palette assert fmt.is_rgb assert fmt.chroma_width() == 640 assert fmt.chroma_height() == 480 assert fmt.chroma_width(1024) == 1024 assert fmt.chroma_height(1024) == 1024 for i in range(3): comp = fmt.components[i] assert comp.plane == 0 assert comp.bits == 8 assert not comp.is_luma assert not comp.is_chroma assert not comp.is_alpha assert comp.width == 640 assert comp.height == 480 def test_yuv420p_inspection(self) -> None: fmt = VideoFormat("yuv420p", 640, 480) assert fmt.name == "yuv420p" assert len(fmt.components) == 3 self._test_yuv420(fmt) def _test_yuv420(self, fmt: VideoFormat) -> None: assert fmt.is_planar assert not fmt.has_palette assert not fmt.is_rgb assert fmt.chroma_width() == 320 assert fmt.chroma_height() == 240 assert fmt.chroma_width(1024) == 512 assert fmt.chroma_height(1024) == 512 for i, comp in enumerate(fmt.components): comp = fmt.components[i] assert comp.plane == i assert comp.bits == 8 assert not fmt.components[0].is_chroma assert fmt.components[1].is_chroma assert fmt.components[2].is_chroma assert fmt.components[0].is_luma assert not fmt.components[1].is_luma assert not fmt.components[2].is_luma assert not fmt.components[0].is_alpha assert not fmt.components[1].is_alpha assert not fmt.components[2].is_alpha assert fmt.components[0].width == 640 assert fmt.components[1].width == 320 assert fmt.components[2].width == 320 def test_yuva420p_inspection(self) -> None: fmt = VideoFormat("yuva420p", 640, 480) assert len(fmt.components) == 4 self._test_yuv420(fmt) assert not fmt.components[3].is_chroma assert fmt.components[3].width == 640 def test_gray16be_inspection(self) -> None: fmt = VideoFormat("gray16be", 640, 480) assert fmt.name == "gray16be" assert len(fmt.components) == 1 assert not fmt.is_planar assert not fmt.has_palette assert not fmt.is_rgb assert fmt.chroma_width() == 640 assert fmt.chroma_height() == 480 assert fmt.chroma_width(1024) == 1024 assert fmt.chroma_height(1024) == 1024 comp = fmt.components[0] assert comp.plane == 0 assert comp.bits == 16 assert comp.is_luma assert not comp.is_chroma assert comp.width == 640 assert comp.height == 480 assert not comp.is_alpha def test_pal8_inspection(self) -> None: fmt = VideoFormat("pal8", 640, 480) assert len(fmt.components) == 1 assert fmt.has_palette PyAV-14.2.0/tests/test_videoframe.py000066400000000000000000001160361475734227400173370ustar00rootroot00000000000000import time from fractions import Fraction from unittest import SkipTest import numpy import pytest import av from av import VideoFrame from av.video.reformatter import ColorRange, Colorspace, Interpolation from .common import ( TestCase, assertImagesAlmostEqual, assertNdarraysEqual, fate_png, fate_suite, has_pillow, ) def assertPixelValue16(plane, expected, byteorder: str) -> None: view = memoryview(plane) if byteorder == "big": assert view[0] == (expected >> 8 & 0xFF) assert view[1] == expected & 0xFF else: assert view[0] == expected & 0xFF assert view[1] == (expected >> 8 & 0xFF) def test_opaque() -> None: with av.open(fate_suite("h264/interlaced_crop.mp4")) as container: video_stream = container.streams.video[0] ctx = video_stream.codec_context ctx.flags |= av.codec.context.Flags.copy_opaque assert video_stream.codec_context.copy_opaque for packet_idx, packet in enumerate(container.demux()): packet.opaque = (time.time(), packet_idx) for frame in packet.decode(): assert isinstance(frame, av.frame.Frame) if frame.opaque is None: continue assert type(frame.opaque) is tuple and len(frame.opaque) == 2 def test_invalid_pixel_format() -> None: with pytest.raises(ValueError, match="not a pixel format: '__unknown_pix_fmt'"): VideoFrame(640, 480, "__unknown_pix_fmt") def test_null_constructor() -> None: frame = VideoFrame() assert frame.width == 0 assert frame.height == 0 assert frame.format.name == "yuv420p" def test_manual_yuv_constructor() -> None: frame = VideoFrame(640, 480, "yuv420p") assert frame.width == 640 assert frame.height == 480 assert frame.format.name == "yuv420p" def test_manual_rgb_constructor() -> None: frame = VideoFrame(640, 480, "rgb24") assert frame.width == 640 assert frame.height == 480 assert frame.format.name == "rgb24" def test_null_planes() -> None: frame = VideoFrame() # yuv420p assert len(frame.planes) == 0 def test_yuv420p_planes() -> None: frame = VideoFrame(640, 480, "yuv420p") assert len(frame.planes) == 3 assert frame.planes[0].width == 640 assert frame.planes[0].height == 480 assert frame.planes[0].line_size == 640 assert frame.planes[0].buffer_size == 640 * 480 for i in range(1, 3): assert frame.planes[i].width == 320 assert frame.planes[i].height == 240 assert frame.planes[i].line_size == 320 assert frame.planes[i].buffer_size == 320 * 240 def test_yuv420p_planes_align() -> None: # If we request 8-byte alignment for a width which is not a multiple of 8, # the line sizes are larger than the plane width. frame = VideoFrame(318, 238, "yuv420p") assert len(frame.planes) == 3 assert frame.planes[0].width == 318 assert frame.planes[0].height == 238 assert frame.planes[0].line_size == 320 assert frame.planes[0].buffer_size == 320 * 238 for i in range(1, 3): assert frame.planes[i].width == 159 assert frame.planes[i].height == 119 assert frame.planes[i].line_size == 160 assert frame.planes[i].buffer_size == 160 * 119 def test_rgb24_planes() -> None: frame = VideoFrame(640, 480, "rgb24") assert len(frame.planes) == 1 assert frame.planes[0].width == 640 assert frame.planes[0].height == 480 assert frame.planes[0].line_size == 640 * 3 assert frame.planes[0].buffer_size == 640 * 480 * 3 def test_memoryview_read() -> None: frame = VideoFrame(640, 480, "rgb24") frame.planes[0].update(b"01234" + (b"x" * (640 * 480 * 3 - 5))) mem = memoryview(frame.planes[0]) assert mem.ndim == 1 assert mem.shape == (640 * 480 * 3,) assert not mem.readonly assert mem[1] == 49 assert mem[:7] == b"01234xx" mem[1] = 46 assert mem[:7] == b"0.234xx" class TestVideoFrameImage(TestCase): def setUp(self) -> None: if not has_pillow: pytest.skip() def test_roundtrip(self) -> None: import PIL.Image as Image image = Image.open(fate_png()) frame = VideoFrame.from_image(image) img = frame.to_image() img.save(self.sandboxed("roundtrip-high.jpg")) assertImagesAlmostEqual(image, img) def test_interpolation(self) -> None: import PIL.Image as Image image = Image.open(fate_png()) frame = VideoFrame.from_image(image) assert frame.width == 330 and frame.height == 330 img = frame.to_image(width=200, height=100, interpolation=Interpolation.BICUBIC) assert img.width == 200 and img.height == 100 img = frame.to_image(width=200, height=100, interpolation="BICUBIC") assert img.width == 200 and img.height == 100 img = frame.to_image( width=200, height=100, interpolation=int(Interpolation.BICUBIC) ) assert img.width == 200 and img.height == 100 def test_to_image_rgb24(self) -> None: sizes = [(318, 238), (320, 240), (500, 500)] for width, height in sizes: frame = VideoFrame(width, height, format="rgb24") # fill video frame data for plane in frame.planes: ba = bytearray(plane.buffer_size) pos = 0 for row in range(height): for i in range(plane.line_size): ba[pos] = i % 256 pos += 1 plane.update(ba) # construct expected image data expected = bytearray(height * width * 3) pos = 0 for row in range(height): for i in range(width * 3): expected[pos] = i % 256 pos += 1 img = frame.to_image() assert img.size == (width, height) assert img.tobytes() == expected def test_basic_to_ndarray() -> None: array = VideoFrame(640, 480, "rgb24").to_ndarray() assert array.shape == (480, 640, 3) def test_to_image_with_dimensions() -> None: if not has_pillow: pytest.skip() img = VideoFrame(640, 480, format="rgb24").to_image(width=320, height=240) assert img.size == (320, 240) def test_ndarray_gray() -> None: array = numpy.random.randint(0, 256, size=(480, 640), dtype=numpy.uint8) for format in ("gray", "gray8"): frame = VideoFrame.from_ndarray(array, format=format) assert format in av.video.frame.supported_np_pix_fmts assert frame.width == 640 and frame.height == 480 assert frame.format.name == "gray" assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gray_align() -> None: array = numpy.random.randint(0, 256, size=(238, 318), dtype=numpy.uint8) for format in ("gray", "gray8"): frame = VideoFrame.from_ndarray(array, format=format) assert format in av.video.frame.supported_np_pix_fmts assert frame.width == 318 and frame.height == 238 assert frame.format.name == "gray" assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_grayf32() -> None: array = numpy.random.random_sample(size=(480, 640)).astype(numpy.float32) for format in ("grayf32be", "grayf32le"): frame = VideoFrame.from_ndarray(array, format=format) assert format in av.video.frame.supported_np_pix_fmts assert frame.width == 640 and frame.height == 480 assert frame.format.name == format assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_grayf32_align() -> None: array = numpy.random.random_sample(size=(238, 318)).astype(numpy.float32) for format in ("grayf32be", "grayf32le"): frame = VideoFrame.from_ndarray(array, format=format) assert format in av.video.frame.supported_np_pix_fmts assert frame.width == 318 and frame.height == 238 assert frame.format.name == format assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_rgb() -> None: array = numpy.random.randint(0, 256, size=(480, 640, 3), dtype=numpy.uint8) for format in ("rgb24", "bgr24"): frame = VideoFrame.from_ndarray(array, format=format) assert format in av.video.frame.supported_np_pix_fmts assert frame.width == 640 and frame.height == 480 assert frame.format.name == format assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_rgb_align() -> None: array = numpy.random.randint(0, 256, size=(238, 318, 3), dtype=numpy.uint8) for format in ("rgb24", "bgr24"): frame = VideoFrame.from_ndarray(array, format=format) assert format in av.video.frame.supported_np_pix_fmts assert frame.width == 318 and frame.height == 238 assert frame.format.name == format assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_rgba() -> None: array = numpy.random.randint(0, 256, size=(480, 640, 4), dtype=numpy.uint8) for format in ("argb", "rgba", "abgr", "bgra"): frame = VideoFrame.from_ndarray(array, format=format) assert format in av.video.frame.supported_np_pix_fmts assert frame.width == 640 and frame.height == 480 assert frame.format.name == format assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_rgba_align() -> None: array = numpy.random.randint(0, 256, size=(238, 318, 4), dtype=numpy.uint8) for format in ("argb", "rgba", "abgr", "bgra"): frame = VideoFrame.from_ndarray(array, format=format) assert format in av.video.frame.supported_np_pix_fmts assert frame.width == 318 and frame.height == 238 assert frame.format.name == format assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gbrp() -> None: array = numpy.random.randint(0, 256, size=(480, 640, 3), dtype=numpy.uint8) frame = VideoFrame.from_ndarray(array, format="gbrp") assert frame.width == 640 and frame.height == 480 assert frame.format.name == "gbrp" assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gbrp_align() -> None: array = numpy.random.randint(0, 256, size=(238, 318, 3), dtype=numpy.uint8) frame = VideoFrame.from_ndarray(array, format="gbrp") assert frame.width == 318 and frame.height == 238 assert frame.format.name == "gbrp" assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gbrp10() -> None: array = numpy.random.randint(0, 1024, size=(480, 640, 3), dtype=numpy.uint16) for format in ("gbrp10be", "gbrp10le"): frame = VideoFrame.from_ndarray(array, format=format) assert format in av.video.frame.supported_np_pix_fmts assert frame.width == 640 and frame.height == 480 assert frame.format.name == format assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gbrp10_align() -> None: array = numpy.random.randint(0, 1024, size=(238, 318, 3), dtype=numpy.uint16) for format in ("gbrp10be", "gbrp10le"): frame = VideoFrame.from_ndarray(array, format=format) assert format in av.video.frame.supported_np_pix_fmts assert frame.width == 318 and frame.height == 238 assert frame.format.name == format assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gbrp12() -> None: array = numpy.random.randint(0, 4096, size=(480, 640, 3), dtype=numpy.uint16) for format in ("gbrp12be", "gbrp12le"): frame = VideoFrame.from_ndarray(array, format=format) assert format in av.video.frame.supported_np_pix_fmts assert frame.width == 640 and frame.height == 480 assert frame.format.name == format assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gbrp12_align() -> None: array = numpy.random.randint(0, 4096, size=(238, 318, 3), dtype=numpy.uint16) for format in ("gbrp12be", "gbrp12le"): frame = VideoFrame.from_ndarray(array, format=format) assert format in av.video.frame.supported_np_pix_fmts assert frame.width == 318 and frame.height == 238 assert frame.format.name == format assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gbrp14() -> None: array = numpy.random.randint(0, 16384, size=(480, 640, 3), dtype=numpy.uint16) for format in ("gbrp14be", "gbrp14le"): frame = VideoFrame.from_ndarray(array, format=format) assert format in av.video.frame.supported_np_pix_fmts assert frame.width == 640 and frame.height == 480 assert frame.format.name == format assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gbrp14_align() -> None: array = numpy.random.randint(0, 16384, size=(238, 318, 3), dtype=numpy.uint16) for format in ("gbrp14be", "gbrp14le"): frame = VideoFrame.from_ndarray(array, format=format) assert format in av.video.frame.supported_np_pix_fmts assert frame.width == 318 and frame.height == 238 assert frame.format.name == format assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gbrp16() -> None: array = numpy.random.randint(0, 65536, size=(480, 640, 3), dtype=numpy.uint16) for format in ("gbrp16be", "gbrp16le"): frame = VideoFrame.from_ndarray(array, format=format) assert format in av.video.frame.supported_np_pix_fmts assert frame.width == 640 and frame.height == 480 assert frame.format.name == format assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gbrp16_align() -> None: array = numpy.random.randint(0, 65536, size=(238, 318, 3), dtype=numpy.uint16) for format in ("gbrp16be", "gbrp16le"): frame = VideoFrame.from_ndarray(array, format=format) assert format in av.video.frame.supported_np_pix_fmts assert frame.width == 318 and frame.height == 238 assert frame.format.name == format assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gbrpf32() -> None: array = numpy.random.random_sample(size=(480, 640, 3)).astype(numpy.float32) for format in ("gbrpf32be", "gbrpf32le"): frame = VideoFrame.from_ndarray(array, format=format) assert frame.width == 640 and frame.height == 480 assert frame.format.name == format assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gbrpf32_align() -> None: array = numpy.random.random_sample(size=(238, 318, 3)).astype(numpy.float32) for format in ["gbrpf32be", "gbrpf32le"]: frame = VideoFrame.from_ndarray(array, format=format) assert frame.width == 318 and frame.height == 238 assert frame.format.name == format assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gbrapf32() -> None: array = numpy.random.random_sample(size=(480, 640, 4)).astype(numpy.float32) for format in ("gbrapf32be", "gbrapf32le"): frame = VideoFrame.from_ndarray(array, format=format) assert frame.width == 640 and frame.height == 480 assert frame.format.name == format assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gbrapf32_allign() -> None: array = numpy.random.random_sample(size=(238, 318, 4)).astype(numpy.float32) for format in ("gbrapf32be", "gbrapf32le"): frame = VideoFrame.from_ndarray(array, format=format) assert frame.width == 318 and frame.height == 238 assert frame.format.name == format assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_yuv420p() -> None: array = numpy.random.randint(0, 256, size=(720, 640), dtype=numpy.uint8) frame = VideoFrame.from_ndarray(array, format="yuv420p") assert frame.width == 640 and frame.height == 480 assert frame.format.name == "yuv420p" assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_yuv420p_align() -> None: array = numpy.random.randint(0, 256, size=(357, 318), dtype=numpy.uint8) frame = VideoFrame.from_ndarray(array, format="yuv420p") assert frame.width == 318 and frame.height == 238 assert frame.format.name == "yuv420p" assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_yuvj420p() -> None: array = numpy.random.randint(0, 256, size=(720, 640), dtype=numpy.uint8) frame = VideoFrame.from_ndarray(array, format="yuvj420p") assert frame.width == 640 and frame.height == 480 assert frame.format.name == "yuvj420p" assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_yuyv422() -> None: array = numpy.random.randint(0, 256, size=(480, 640, 2), dtype=numpy.uint8) frame = VideoFrame.from_ndarray(array, format="yuyv422") assert frame.width == 640 and frame.height == 480 assert frame.format.name == "yuyv422" assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_yuv444p() -> None: array = numpy.random.randint(0, 256, size=(3, 480, 640), dtype=numpy.uint8) frame = VideoFrame.from_ndarray(array, format="yuv444p") assert frame.width == 640 and frame.height == 480 assert frame.format.name == "yuv444p" assertNdarraysEqual(frame.to_ndarray(), array) array = numpy.random.randint(0, 256, size=(3, 480, 640), dtype=numpy.uint8) frame = VideoFrame.from_ndarray(array, channel_last=False, format="yuv444p") assert frame.width == 640 and frame.height == 480 assert frame.format.name == "yuv444p" assertNdarraysEqual(frame.to_ndarray(channel_last=False), array) assert array.shape != frame.to_ndarray(channel_last=True).shape assert ( frame.to_ndarray(channel_last=False).shape != frame.to_ndarray(channel_last=True).shape ) def test_ndarray_yuvj444p() -> None: array = numpy.random.randint(0, 256, size=(3, 480, 640), dtype=numpy.uint8) frame = VideoFrame.from_ndarray(array, format="yuvj444p") assert frame.width == 640 and frame.height == 480 assert frame.format.name == "yuvj444p" assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_yuv444p16() -> None: array = numpy.random.randint(0, 65536, size=(480, 640, 3), dtype=numpy.uint16) for format in ("yuv444p16be", "yuv444p16le"): frame = VideoFrame.from_ndarray(array, format=format) assert frame.width == 640 and frame.height == 480 assert frame.format.name == format assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_yuv422p10le() -> None: array = numpy.random.randint(0, 65536, size=(3, 480, 640), dtype=numpy.uint16) for format in ("yuv422p10le",): frame = VideoFrame.from_ndarray(array, format=format) assert frame.width == 640 and frame.height == 480 assert frame.format.name == format assert format in av.video.frame.supported_np_pix_fmts def test_ndarray_yuv444p16_align() -> None: array = numpy.random.randint(0, 65536, size=(238, 318, 3), dtype=numpy.uint16) for format in ("yuv444p16be", "yuv444p16le"): frame = VideoFrame.from_ndarray(array, format=format) assert frame.width == 318 and frame.height == 238 assert frame.format.name == format assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_yuva444p16() -> None: array = numpy.random.randint(0, 65536, size=(480, 640, 4), dtype=numpy.uint16) for format in ("yuva444p16be", "yuva444p16le"): frame = VideoFrame.from_ndarray(array, format=format) assert frame.width == 640 and frame.height == 480 assert frame.format.name == format assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_yuva444p16_align() -> None: array = numpy.random.randint(0, 65536, size=(238, 318, 4), dtype=numpy.uint16) for format in ("yuva444p16be", "yuva444p16le"): frame = VideoFrame.from_ndarray(array, format=format) assert frame.width == 318 and frame.height == 238 assert frame.format.name == format assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_yuyv422_align() -> None: array = numpy.random.randint(0, 256, size=(238, 318, 2), dtype=numpy.uint8) frame = VideoFrame.from_ndarray(array, format="yuyv422") assert frame.width == 318 and frame.height == 238 assert frame.format.name == "yuyv422" assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gray16be() -> None: array = numpy.random.randint(0, 65536, size=(480, 640), dtype=numpy.uint16) frame = VideoFrame.from_ndarray(array, format="gray16be") assert frame.width == 640 and frame.height == 480 assert frame.format.name == "gray16be" assertNdarraysEqual(frame.to_ndarray(), array) # check endianness by examining value of first pixel assertPixelValue16(frame.planes[0], array[0][0], "big") def test_ndarray_gray16le() -> None: array = numpy.random.randint(0, 65536, size=(480, 640), dtype=numpy.uint16) frame = VideoFrame.from_ndarray(array, format="gray16le") assert frame.width == 640 and frame.height == 480 assert frame.format.name == "gray16le" assertNdarraysEqual(frame.to_ndarray(), array) # check endianness by examining value of first pixel assertPixelValue16(frame.planes[0], array[0][0], "little") def test_ndarray_rgb48be() -> None: array = numpy.random.randint(0, 65536, size=(480, 640, 3), dtype=numpy.uint16) frame = VideoFrame.from_ndarray(array, format="rgb48be") assert frame.width == 640 and frame.height == 480 assert frame.format.name == "rgb48be" assertNdarraysEqual(frame.to_ndarray(), array) # check endianness by examining red value of first pixel assertPixelValue16(frame.planes[0], array[0][0][0], "big") def test_ndarray_rgb48le() -> None: array = numpy.random.randint(0, 65536, size=(480, 640, 3), dtype=numpy.uint16) frame = VideoFrame.from_ndarray(array, format="rgb48le") assert frame.width == 640 and frame.height == 480 assert frame.format.name == "rgb48le" assertNdarraysEqual(frame.to_ndarray(), array) # check endianness by examining red value of first pixel assertPixelValue16(frame.planes[0], array[0][0][0], "little") def test_ndarray_rgb48le_align() -> None: array = numpy.random.randint(0, 65536, size=(238, 318, 3), dtype=numpy.uint16) frame = VideoFrame.from_ndarray(array, format="rgb48le") assert frame.width == 318 and frame.height == 238 assert frame.format.name == "rgb48le" assertNdarraysEqual(frame.to_ndarray(), array) # check endianness by examining red value of first pixel assertPixelValue16(frame.planes[0], array[0][0][0], "little") def test_ndarray_rgba64be() -> None: array = numpy.random.randint(0, 65536, size=(480, 640, 4), dtype=numpy.uint16) frame = VideoFrame.from_ndarray(array, format="rgba64be") assert frame.width == 640 and frame.height == 480 assert frame.format.name == "rgba64be" assertNdarraysEqual(frame.to_ndarray(), array) # check endianness by examining red value of first pixel assertPixelValue16(frame.planes[0], array[0][0][0], "big") def test_ndarray_rgba64le() -> None: array = numpy.random.randint(0, 65536, size=(480, 640, 4), dtype=numpy.uint16) frame = VideoFrame.from_ndarray(array, format="rgba64le") assert frame.width == 640 and frame.height == 480 assert frame.format.name == "rgba64le" assertNdarraysEqual(frame.to_ndarray(), array) # check endianness by examining red value of first pixel assertPixelValue16(frame.planes[0], array[0][0][0], "little") def test_ndarray_rgb8() -> None: array = numpy.random.randint(0, 256, size=(480, 640), dtype=numpy.uint8) frame = VideoFrame.from_ndarray(array, format="rgb8") assert frame.width == 640 and frame.height == 480 assert frame.format.name == "rgb8" assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_bgr8() -> None: array = numpy.random.randint(0, 256, size=(480, 640), dtype=numpy.uint8) frame = VideoFrame.from_ndarray(array, format="bgr8") assert frame.width == 640 and frame.height == 480 assert frame.format.name == "bgr8" assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_pal8(): array = numpy.random.randint(0, 256, size=(480, 640), dtype=numpy.uint8) palette = numpy.random.randint(0, 256, size=(256, 4), dtype=numpy.uint8) frame = VideoFrame.from_ndarray((array, palette), format="pal8") assert frame.width == 640 and frame.height == 480 assert frame.format.name == "pal8" assert frame.format.name in av.video.frame.supported_np_pix_fmts returned = frame.to_ndarray() assert type(returned) is tuple and len(returned) == 2 assertNdarraysEqual(returned[0], array) assertNdarraysEqual(returned[1], palette) def test_ndarray_nv12() -> None: array = numpy.random.randint(0, 256, size=(720, 640), dtype=numpy.uint8) frame = VideoFrame.from_ndarray(array, format="nv12") assert frame.width == 640 and frame.height == 480 assert frame.format.name == "nv12" assert frame.format.name in av.video.frame.supported_np_pix_fmts assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_nv12_align() -> None: array = numpy.random.randint(0, 256, size=(357, 318), dtype=numpy.uint8) frame = VideoFrame.from_ndarray(array, format="nv12") assert frame.width == 318 and frame.height == 238 assert frame.format.name == "nv12" assertNdarraysEqual(frame.to_ndarray(), array) def test_shares_memory_gray() -> None: array = numpy.random.randint(0, 256, size=(357, 318), dtype=numpy.uint8) frame = VideoFrame.from_numpy_buffer(array, "gray") assertNdarraysEqual(frame.to_ndarray(), array) # overwrite the array, the contents thereof array[...] = numpy.random.randint(0, 256, size=(357, 318), dtype=numpy.uint8) # Make sure the frame reflects that assertNdarraysEqual(frame.to_ndarray(), array) # repeat the test, but with an array that is not fully contiguous, though the # pixels in a row are array = numpy.random.randint(0, 256, size=(357, 318), dtype=numpy.uint8) array = array[:, :300] assert not array.data.c_contiguous frame = VideoFrame.from_numpy_buffer(array, "gray") assertNdarraysEqual(frame.to_ndarray(), array) # overwrite the array, the contents thereof array[...] = numpy.random.randint(0, 256, size=array.shape, dtype=numpy.uint8) # Make sure the frame reflects that assertNdarraysEqual(frame.to_ndarray(), array) def test_shares_memory_gray8() -> None: array = numpy.random.randint(0, 256, size=(357, 318), dtype=numpy.uint8) frame = VideoFrame.from_numpy_buffer(array, "gray8") assertNdarraysEqual(frame.to_ndarray(), array) # overwrite the array, the contents thereof array[...] = numpy.random.randint(0, 256, size=(357, 318), dtype=numpy.uint8) # Make sure the frame reflects that assertNdarraysEqual(frame.to_ndarray(), array) # repeat the test, but with an array that is not fully contiguous, though the # pixels in a row are array = numpy.random.randint(0, 256, size=(357, 318), dtype=numpy.uint8) array = array[:, :300] assert not array.data.c_contiguous frame = VideoFrame.from_numpy_buffer(array, "gray8") assertNdarraysEqual(frame.to_ndarray(), array) # overwrite the array, the contents thereof array[...] = numpy.random.randint(0, 256, size=array.shape, dtype=numpy.uint8) # Make sure the frame reflects that assertNdarraysEqual(frame.to_ndarray(), array) def test_shares_memory_rgb8() -> None: array = numpy.random.randint(0, 256, size=(357, 318), dtype=numpy.uint8) frame = VideoFrame.from_numpy_buffer(array, "rgb8") assertNdarraysEqual(frame.to_ndarray(), array) # overwrite the array, the contents thereof array[...] = numpy.random.randint(0, 256, size=(357, 318), dtype=numpy.uint8) # Make sure the frame reflects that assertNdarraysEqual(frame.to_ndarray(), array) # repeat the test, but with an array that is not fully contiguous, though the # pixels in a row are array = numpy.random.randint(0, 256, size=(357, 318), dtype=numpy.uint8) array = array[:, :300] assert not array.data.c_contiguous frame = VideoFrame.from_numpy_buffer(array, "rgb8") assertNdarraysEqual(frame.to_ndarray(), array) # overwrite the array, the contents thereof array[...] = numpy.random.randint(0, 256, size=array.shape, dtype=numpy.uint8) # Make sure the frame reflects that assertNdarraysEqual(frame.to_ndarray(), array) def test_shares_memory_bgr8() -> None: array = numpy.random.randint(0, 256, size=(357, 318), dtype=numpy.uint8) frame = VideoFrame.from_numpy_buffer(array, "bgr8") assertNdarraysEqual(frame.to_ndarray(), array) # overwrite the array, the contents thereof array[...] = numpy.random.randint(0, 256, size=(357, 318), dtype=numpy.uint8) # Make sure the frame reflects that assertNdarraysEqual(frame.to_ndarray(), array) # repeat the test, but with an array that is not fully contiguous, though the # pixels in a row are array = numpy.random.randint(0, 256, size=(357, 318), dtype=numpy.uint8) array = array[:, :300] assert not array.data.c_contiguous frame = VideoFrame.from_numpy_buffer(array, "bgr8") assertNdarraysEqual(frame.to_ndarray(), array) # overwrite the array, the contents thereof array[...] = numpy.random.randint(0, 256, size=array.shape, dtype=numpy.uint8) # Make sure the frame reflects that assertNdarraysEqual(frame.to_ndarray(), array) def test_shares_memory_rgb24() -> None: array = numpy.random.randint(0, 256, size=(357, 318, 3), dtype=numpy.uint8) frame = VideoFrame.from_numpy_buffer(array, "rgb24") assertNdarraysEqual(frame.to_ndarray(), array) # overwrite the array, the contents thereof array[...] = numpy.random.randint(0, 256, size=(357, 318, 3), dtype=numpy.uint8) # Make sure the frame reflects that assertNdarraysEqual(frame.to_ndarray(), array) # repeat the test, but with an array that is not fully contiguous, though the # pixels in a row are array = numpy.random.randint(0, 256, size=(357, 318, 3), dtype=numpy.uint8) array = array[:, :300, :] assert not array.data.c_contiguous frame = VideoFrame.from_numpy_buffer(array, "rgb24") assertNdarraysEqual(frame.to_ndarray(), array) # overwrite the array, the contents thereof array[...] = numpy.random.randint(0, 256, size=array.shape, dtype=numpy.uint8) # Make sure the frame reflects that assertNdarraysEqual(frame.to_ndarray(), array) def test_shares_memory_rgba() -> None: array = numpy.random.randint(0, 256, size=(357, 318, 4), dtype=numpy.uint8) frame = VideoFrame.from_numpy_buffer(array, "rgba") assertNdarraysEqual(frame.to_ndarray(), array) # overwrite the array, the contents thereof array[...] = numpy.random.randint(0, 256, size=(357, 318, 4), dtype=numpy.uint8) # Make sure the frame reflects that assertNdarraysEqual(frame.to_ndarray(), array) # repeat the test, but with an array that is not fully contiguous, though the # pixels in a row are array = numpy.random.randint(0, 256, size=(357, 318, 4), dtype=numpy.uint8) array = array[:, :300, :] assert not array.data.c_contiguous frame = VideoFrame.from_numpy_buffer(array, "rgba") assertNdarraysEqual(frame.to_ndarray(), array) # overwrite the array, the contents thereof array[...] = numpy.random.randint(0, 256, size=array.shape, dtype=numpy.uint8) # Make sure the frame reflects that assertNdarraysEqual(frame.to_ndarray(), array) def test_shares_memory_yuv420p() -> None: array = numpy.random.randint(0, 256, size=(512 * 6 // 4, 256), dtype=numpy.uint8) frame = VideoFrame.from_numpy_buffer(array, "yuv420p") assertNdarraysEqual(frame.to_ndarray(), array) # overwrite the array, the contents thereof array[...] = numpy.random.randint(0, 256, size=array.shape, dtype=numpy.uint8) # Make sure the frame reflects that assertNdarraysEqual(frame.to_ndarray(), array) # repeat the test, but with an array where there are some padding bytes # note that the uv rows have half the padding in the middle of a row, and the # other half at the end height = 512 stride = 256 width = 200 array = numpy.random.randint( 0, 256, size=(height * 6 // 4, stride), dtype=numpy.uint8 ) uv_width = width // 2 uv_stride = stride // 2 # compare carefully, avoiding all the padding bytes which to_ndarray strips out frame = VideoFrame.from_numpy_buffer(array, "yuv420p", width=width) frame_array = frame.to_ndarray() assertNdarraysEqual(frame_array[:height, :width], array[:height, :width]) assertNdarraysEqual(frame_array[height:, :uv_width], array[height:, :uv_width]) assertNdarraysEqual( frame_array[height:, uv_width:], array[height:, uv_stride : uv_stride + uv_width], ) # overwrite the array, and check the shared frame buffer changed too! array[...] = numpy.random.randint(0, 256, size=array.shape, dtype=numpy.uint8) frame_array = frame.to_ndarray() assertNdarraysEqual(frame_array[:height, :width], array[:height, :width]) assertNdarraysEqual(frame_array[height:, :uv_width], array[height:, :uv_width]) assertNdarraysEqual( frame_array[height:, uv_width:], array[height:, uv_stride : uv_stride + uv_width], ) def test_shares_memory_yuvj420p() -> None: array = numpy.random.randint(0, 256, size=(512 * 6 // 4, 256), dtype=numpy.uint8) frame = VideoFrame.from_numpy_buffer(array, "yuvj420p") assertNdarraysEqual(frame.to_ndarray(), array) # overwrite the array, the contents thereof array[...] = numpy.random.randint(0, 256, size=array.shape, dtype=numpy.uint8) # Make sure the frame reflects that assertNdarraysEqual(frame.to_ndarray(), array) # repeat the test with padding, just as we did in the yuv420p case height = 512 stride = 256 width = 200 array = numpy.random.randint( 0, 256, size=(height * 6 // 4, stride), dtype=numpy.uint8 ) uv_width = width // 2 uv_stride = stride // 2 # compare carefully, avoiding all the padding bytes which to_ndarray strips out frame = VideoFrame.from_numpy_buffer(array, "yuvj420p", width=width) frame_array = frame.to_ndarray() assertNdarraysEqual(frame_array[:height, :width], array[:height, :width]) assertNdarraysEqual(frame_array[height:, :uv_width], array[height:, :uv_width]) assertNdarraysEqual( frame_array[height:, uv_width:], array[height:, uv_stride : uv_stride + uv_width], ) # overwrite the array, and check the shared frame buffer changed too! array[...] = numpy.random.randint(0, 256, size=array.shape, dtype=numpy.uint8) frame_array = frame.to_ndarray() assertNdarraysEqual(frame_array[:height, :width], array[:height, :width]) assertNdarraysEqual(frame_array[height:, :uv_width], array[height:, :uv_width]) assertNdarraysEqual( frame_array[height:, uv_width:], array[height:, uv_stride : uv_stride + uv_width], ) def test_shares_memory_nv12() -> None: array = numpy.random.randint(0, 256, size=(512 * 6 // 4, 256), dtype=numpy.uint8) frame = VideoFrame.from_numpy_buffer(array, "nv12") assertNdarraysEqual(frame.to_ndarray(), array) # overwrite the array, the contents thereof array[...] = numpy.random.randint(0, 256, size=array.shape, dtype=numpy.uint8) # Make sure the frame reflects that assertNdarraysEqual(frame.to_ndarray(), array) # repeat the test, but with an array that is not fully contiguous, though the # pixels in a row are array = numpy.random.randint(0, 256, size=(512 * 6 // 4, 256), dtype=numpy.uint8) array = array[:, :200] assert not array.data.c_contiguous frame = VideoFrame.from_numpy_buffer(array, "nv12") assertNdarraysEqual(frame.to_ndarray(), array) # overwrite the array, the contents thereof array[...] = numpy.random.randint(0, 256, size=array.shape, dtype=numpy.uint8) # Make sure the frame reflects that assertNdarraysEqual(frame.to_ndarray(), array) def test_shares_memory_bgr24() -> None: array = numpy.random.randint(0, 256, size=(357, 318, 3), dtype=numpy.uint8) frame = VideoFrame.from_numpy_buffer(array, "bgr24") assertNdarraysEqual(frame.to_ndarray(), array) # overwrite the array, the contents thereof array[...] = numpy.random.randint(0, 256, size=(357, 318, 3), dtype=numpy.uint8) # Make sure the frame reflects that assertNdarraysEqual(frame.to_ndarray(), array) # repeat the test, but with an array that is not fully contiguous, though the # pixels in a row are array = numpy.random.randint(0, 256, size=(357, 318, 3), dtype=numpy.uint8) array = array[:, :300, :] assert not array.data.c_contiguous frame = VideoFrame.from_numpy_buffer(array, "bgr24") assertNdarraysEqual(frame.to_ndarray(), array) # overwrite the array, the contents thereof array[...] = numpy.random.randint(0, 256, size=array.shape, dtype=numpy.uint8) # Make sure the frame reflects that assertNdarraysEqual(frame.to_ndarray(), array) def test_shares_memory_bgra() -> None: array = numpy.random.randint(0, 256, size=(357, 318, 4), dtype=numpy.uint8) frame = VideoFrame.from_numpy_buffer(array, "bgra") assertNdarraysEqual(frame.to_ndarray(), array) # overwrite the array, the contents thereof array[...] = numpy.random.randint(0, 256, size=(357, 318, 4), dtype=numpy.uint8) # Make sure the frame reflects that assertNdarraysEqual(frame.to_ndarray(), array) # repeat the test, but with an array that is not fully contiguous, though the # pixels in a row are array = numpy.random.randint(0, 256, size=(357, 318, 4), dtype=numpy.uint8) array = array[:, :300, :] assert not array.data.c_contiguous frame = VideoFrame.from_numpy_buffer(array, "bgra") assertNdarraysEqual(frame.to_ndarray(), array) # overwrite the array, the contents thereof array[...] = numpy.random.randint(0, 256, size=array.shape, dtype=numpy.uint8) # Make sure the frame reflects that assertNdarraysEqual(frame.to_ndarray(), array) def test_reformat_pts() -> None: frame = VideoFrame(640, 480, "rgb24") frame.pts = 123 frame.time_base = Fraction("456/1") frame = frame.reformat(320, 240) assert frame.pts == 123 and frame.time_base == 456 def test_reformat_identity() -> None: frame1 = VideoFrame(640, 480, "rgb24") frame2 = frame1.reformat(640, 480, "rgb24") assert frame1 is frame2 def test_reformat_colorspace() -> None: frame = VideoFrame(640, 480, "rgb24") frame.reformat(src_colorspace=None, dst_colorspace="smpte240m") frame = VideoFrame(640, 480, "rgb24") frame.reformat(src_colorspace=None, dst_colorspace=Colorspace.smpte240m) frame = VideoFrame(640, 480, "yuv420p") frame.reformat(src_colorspace=None, dst_colorspace="smpte240m") frame = VideoFrame(640, 480, "rgb24") frame.colorspace = Colorspace.smpte240m assert frame.colorspace == int(Colorspace.smpte240m) assert frame.colorspace == Colorspace.smpte240m def test_reformat_pixel_format_align() -> None: height = 480 for width in range(2, 258, 2): frame_yuv = VideoFrame(width, height, "yuv420p") for plane in frame_yuv.planes: plane.update(b"\xff" * plane.buffer_size) expected_rgb = numpy.zeros(shape=(height, width, 3), dtype=numpy.uint8) expected_rgb[:, :, 0] = 255 expected_rgb[:, :, 1] = 124 expected_rgb[:, :, 2] = 255 frame_rgb = frame_yuv.reformat(format="rgb24") assertNdarraysEqual(frame_rgb.to_ndarray(), expected_rgb)