pax_global_header00006660000000000000000000000064146470531120014515gustar00rootroot0000000000000052 comment=0b5e4dfeef351042ed94aff5b7b7c4510ba1aae9 PyAV-12.3.0/000077500000000000000000000000001464705311200124175ustar00rootroot00000000000000PyAV-12.3.0/.github/000077500000000000000000000000001464705311200137575ustar00rootroot00000000000000PyAV-12.3.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001464705311200161425ustar00rootroot00000000000000PyAV-12.3.0/.github/ISSUE_TEMPLATE/build-bug-report.md000066400000000000000000000026711464705311200216550ustar00rootroot00000000000000--- name: Build bug report about: Report on an issue while building or installing PyAV. title: "FOO does not build." labels: build assignees: '' --- **IMPORTANT:** Be sure to replace all template sections {{ like this }} or your issue may be discarded. ## Overview {{ A clear and concise description of what the bug is. }} ## Expected behavior {{ A clear and concise description of what you expected to happen. }} ## Actual behavior {{ A clear and concise description of what actually happened. }} Build report: ``` {{ Complete output of `python setup.py build`. Reports that do not show compiler commands will not be accepted (e.g. results from `pip install av`). }} ``` ## Investigation {{ What you did to isolate the problem. }} ## Reproduction {{ Steps to reproduce the behavior. }} ## Versions - OS: {{ e.g. macOS 10.13.6 }} - PyAV runtime: ``` {{ Complete output of `python -m av --version` if you can run it. }} ``` - PyAV build: ``` {{ Complete output of `python setup.py config --verbose`. }} ``` ## Research I have done the following: - [ ] Checked the [PyAV documentation](https://pyav.basswood-io.com) - [ ] Searched on [Google](https://www.google.com/search?q=pyav+how+do+I+foo) - [ ] Searched on [Stack Overflow](https://stackoverflow.com/search?q=pyav) - [ ] Looked through [old GitHub issues](https://github.com/PyAV-Org/PyAV/issues?&q=is%3Aissue) ## Additional context {{ Add any other context about the problem here. }} PyAV-12.3.0/.github/ISSUE_TEMPLATE/ffmpeg-feature-request.md000066400000000000000000000024461464705311200230550ustar00rootroot00000000000000--- name: FFmpeg feature request about: Request a feature of FFmpeg be exposed or supported by PyAV. title: "Allow FOO to BAR" labels: enhancement assignees: '' --- **IMPORTANT:** Be sure to replace all template sections {{ like this }} or your issue may be discarded. ## Overview {{ A clear and concise description of what the feature is. }} ## Existing FFmpeg API {{ Link to appropriate FFmpeg documentation, ideally the API doxygen files at https://ffmpeg.org/doxygen/trunk/ }} ## Expected PyAV API {{ A description of how you think PyAV should behave. }} Example: ``` {{ An example of how you think PyAV should behave. }} ``` ## Investigation {{ What you did to isolate the problem. }} ## Reproduction {{ Steps to reproduce the behavior. If the problem is media specific, include a link to it. Only send media that you have the rights to. }} ## Versions - OS: {{ e.g. macOS 10.13.6 }} - PyAV runtime: ``` {{ Complete output of `python -m av --version`. If this command won't run, you are likely dealing with the build issue and should use the appropriate template. }} ``` - PyAV build: ``` {{ Complete output of `python setup.py config --verbose`. }} ``` - FFmpeg: ``` {{ Complete output of `ffmpeg -version` }} ``` ## Additional context {{ Add any other context about the problem here. }} PyAV-12.3.0/.github/ISSUE_TEMPLATE/pyav-feature-request.md000066400000000000000000000011041464705311200225560ustar00rootroot00000000000000--- name: PyAV feature request about: Request a feature of PyAV that is not provided by FFmpeg. title: "Allow FOO to BAR" labels: enhancement assignees: '' --- **IMPORTANT:** Be sure to replace all template sections {{ like this }} or your issue may be discarded. ## Overview {{ A clear and concise description of what the feature is. }} ## Desired Behavior {{ A description of how you think PyAV should behave. }} ## Example API ``` {{ An example of how you think PyAV should behave. }} ``` ## Additional context {{ Add any other context about the problem here. }} PyAV-12.3.0/.github/ISSUE_TEMPLATE/runtime-bug-report.md000066400000000000000000000030221464705311200222300ustar00rootroot00000000000000--- name: Runtime bug report about: Report on an issue while running PyAV. title: "The FOO does not BAR." labels: bug assignees: '' --- **IMPORTANT:** Be sure to replace all template sections {{ like this }} or your issue may be discarded. ## Overview {{ A clear and concise description of what the bug is. }} ## Expected behavior {{ A clear and concise description of what you expected to happen. }} ## Actual behavior {{ A clear and concise description of what actually happened. }} Traceback: ``` {{ Include complete tracebacks if there are any exceptions. }} ``` ## Investigation {{ What you did to isolate the problem. }} ## Reproduction {{ Steps to reproduce the behavior. If the problem is media specific, include a link to it. Only send media that you have the rights to. }} ## Versions - OS: {{ e.g. macOS 10.13.6 }} - PyAV runtime: ``` {{ Complete output of `python -m av --version`. If this command won't run, you are likely dealing with the build issue and should use the appropriate template. }} ``` - PyAV build: ``` {{ Complete output of `python setup.py config --verbose`. }} ``` ## Research I have done the following: - [ ] Checked the [PyAV documentation](https://pyav.basswood-io.com) - [ ] Searched on [Google](https://www.google.com/search?q=pyav+how+do+I+foo) - [ ] Searched on [Stack Overflow](https://stackoverflow.com/search?q=pyav) - [ ] Looked through [old GitHub issues](https://github.com/PyAV-Org/PyAV/issues?&q=is%3Aissue) ## Additional context {{ Add any other context about the problem here. }} PyAV-12.3.0/.github/workflows/000077500000000000000000000000001464705311200160145ustar00rootroot00000000000000PyAV-12.3.0/.github/workflows/smoke.yml000066400000000000000000000101711464705311200176550ustar00rootroot00000000000000name: smoke on: push: branches: main paths-ignore: - '**.md' - '**.rst' - '**.txt' pull_request: branches: main paths-ignore: - '**.md' - '**.rst' - '**.txt' jobs: style: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Python uses: actions/setup-python@v5 with: python-version: "3.12" - name: Packages run: pip install -r tests/requirements.txt - 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-latest, python: "3.8", ffmpeg: "6.1", extras: true} - {os: ubuntu-latest, python: "3.8", ffmpeg: "6.0"} - {os: ubuntu-latest, python: pypy3.9, ffmpeg: "6.1"} - {os: macos-12, python: "3.8", ffmpeg: "6.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-latest) sudo apt-get update sudo apt-get install autoconf automake build-essential cmake \ libtool mercurial pkg-config texinfo wget yasm zlib1g-dev sudo apt-get install libass-dev libfreetype6-dev libjpeg-dev \ libtheora-dev libvorbis-dev libx264-dev if [[ "${{ matrix.config.extras }}" ]]; then sudo apt-get install doxygen fi ;; macos-12) brew install automake libtool nasm pkg-config shtool texi2html wget brew install libass libjpeg libpng libvorbis libvpx opus theora 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. scripts/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.8", ffmpeg: "6.1"} - {os: windows-latest, python: "3.8", ffmpeg: "6.0"} 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 \ 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 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 setup.py test PyAV-12.3.0/.github/workflows/tests.yml000066400000000000000000000067661464705311200177200ustar00rootroot00000000000000name: tests on: release: types: [published] workflow_dispatch: jobs: package-source: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.8" - name: Build source package run: | pip install cython python scripts/fetch-vendor.py --config-file scripts/ffmpeg-6.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-latest arch: aarch64 - os: ubuntu-latest arch: i686 - os: ubuntu-latest arch: x86_64 - os: windows-latest arch: AMD64 steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.8" - name: Set up QEMU if: matrix.os == 'ubuntu-latest' uses: docker/setup-qemu-action@v3 - name: Install packages if: matrix.os == 'macos-13' run: | brew update brew install pkg-config - name: Set deployment target if: matrix.os == 'macos-13' || matrix.os == 'macos-14' run: echo "MACOSX_DEPLOYMENT_TARGET=10.13" >> $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-6.1.json /tmp/vendor CIBW_BEFORE_BUILD_WINDOWS: python scripts\fetch-vendor.py --config-file scripts\ffmpeg-6.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: "*-musllinux*" CIBW_TEST_COMMAND: mv {project}/av {project}/av.disabled && python -m unittest discover -t {project} -s tests && mv {project}/av.disabled {project}/av CIBW_TEST_REQUIRES: numpy # skip tests when there are no binary wheels of numpy CIBW_TEST_SKIP: pp* *_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-12.3.0/.gitignore000066400000000000000000000005311464705311200144060ustar00rootroot00000000000000# 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-12.3.0/AUTHORS.py000066400000000000000000000056621464705311200141270ustar00rootroot00000000000000import 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-12.3.0/AUTHORS.rst000066400000000000000000000106371464705311200143050ustar00rootroot00000000000000Contributors ============ All contributors (by number of commits): - Mike Boers ; `@mikeboers `_ * Jeremy Lainé ; `@jlaine `_ - WyattBlue ; `@WyattBlue `_ - 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> - Alba Mendez - Mark Harfouche - Xinran Xu ; `@xxr3376 `_ - Dan Allan ; `@danielballan `_ - Dave Johansen - Moonsik Park - Santtu Keskinen - Christoph Rackwitz - Alireza Davoudi ; `@adavoudi `_ - Jonathan Drolet - Moritz Kassner ; `@mkassner `_ - Thomas A Caswell ; `@tacaswell `_ - Ulrik Mikaelsson ; `@rawler `_ - Wel C. van der - Will Patera ; `@willpatera `_ * 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 * Radek Senfeld ; `@radek-senfeld `_ * Benjamin Chrétien <2742231+bchretien@users.noreply.github.com> * Marc Mueller <30130371+cdce8p@users.noreply.github.com> * zzjjbb <31069326+zzjjbb@users.noreply.github.com> * Hanz <40712686+HanzCEO@users.noreply.github.com> * Joe Schiff <41972063+JoeSchiff@users.noreply.github.com> * Artturin * Ian Lee * Ryan Huang * Arthur Barros * Carlos Ruiz * David Plowman * Maxime Desroches * egao1980 * Eric Kalosa-Kenyon * elxy * Gemfield * Jonathan Martin * Johan Jeppsson Karlin * Philipp Klaus * 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-12.3.0/CHANGELOG.rst000066400000000000000000000552031464705311200144450ustar00rootroot00000000000000Changelog ========= 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. v12.3.0 ------- Features: - Support libav's `av_log_set_level` by @materight in (:issue:`1448`). - Add Graph.link_nodes by @WyattBlue in (:issue:`1449`). - Add default codec properties by @WyattBlue in (:issue:`1450`). - Remove the xvid and ass packages in ffmpeg binaries because they were unused by @WyattBlue in (:issue:`1462`). - Add supported_codecs property to OutputContainer by @WyattBlue in (:issue:`1465`). - Add text and dialogue property to AssSubtitle, remove TextSubtitle by @WyattBlue in (:issue:`1456`). Fixes: - Include libav headers in final distribution by @materight in (:issue:`1455`). - Fix segfault when calling subtitle_stream.decode() by @WyattBlue in (:issue:`1460`). - flushing subtitle decoder requires a new uninitialized packet by @moonsikpark in (:issue:`1461`). - Set default color range for VideoReformatter.format() by @elxy in (:issue:`1458`). - Resampler: format, layout accepts `str` `int` too by @WyattBlue in (:issue:`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 @skeskinen in (:issue:`1375`) (:issue:`1379`). - Expose CodecContext flush_buffers by @skeskinen in (:issue:`1382`). Fixes: - Fix type stubs, add missing type stubs. - Add S12M_TIMECODE by @WyattBlue in (:issue:`1381`). - Subtitle.text now returns bytes by @WyattBlue in (:issue:`1398`). - Allow packet.duration to be writable by @WyattBlue in (:issue:`1399`). - Remove deprecated `VideoStream.frame_rate` by @WyattBlue in (:issue:`1351`). - Build with Arm for PyPy now by @WyattBlue in (:issue:`1395`). - Fix #1378 by @WyattBlue in (:issue:`1400`). - setup.py: use PKG_CONFIG env var to get the pkg-config to use by @Artturin in (:issue:`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 @JoeSchiff in (:issue:`1256`). - Deprecate frame.index by @JoeSchiff in (:issue:`1218`). Features: - Allow using pathlib.Path for av.open by @WyattBlue in (:issue:`1231`). - Add `max_b_frames` property to CodecContext by @davidplowman in (:issue:`1119`). - Add `encode_lazy` method to CodecContext by @rawler in (:issue:`1092`). - Add `color_range` to CodecContext/Frame by @johanjeppsson in (:issue:`686`). - Set `time_base` for AudioResampler by @daveisfera in (:issue:`1209`). - Add support for ffmpeg's AVCodecContext::delay by @JoeSchiff in (:issue:`1279`). - Add `color_primaries`, `color_trc`, `colorspace` to VideoStream by @WyattBlue in (:issue:`1304`). - Add `bits_per_coded_sample` to VideoCodecContext by @rvanlaar in (:issue:`1203`). - AssSubtitle.ass now returns as bytes by @WyattBlue in (:issue:`1333`). - Expose DISPLAYMATRIX side data by @hyenal in (:issue:`1249`). Fixes: - Convert deprecated Cython extension class properties to decorator syntax by @JoeSchiff - Check None packet when setting time_base after decode by @philipnbbc in (:issue:`1281`). - Remove deprecated `Buffer.to_bytes` by @WyattBlue in (:issue:`1286`). - Remove deprecated `Packet.decode_one` by @WyattBlue in (:issue:`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 lanaguage 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 inconsistant ``pts``. - ``time_base`` use has been revisited across the codebase, and may not be converted bettween ``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 writeable. - ``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. - Agressively 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-12.3.0/LICENSE.txt000066400000000000000000000027411464705311200142460ustar00rootroot00000000000000Copyright 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-12.3.0/MANIFEST.in000066400000000000000000000003241464705311200141540ustar00rootroot00000000000000include *.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 recursive-include tests *.py PyAV-12.3.0/Makefile000066400000000000000000000012231464705311200140550ustar00rootroot00000000000000LDFLAGS ?= "" CFLAGS ?= "-O0" PYAV_PYTHON ?= python PYTHON := $(PYAV_PYTHON) .PHONY: default build clean fate-suite lint test default: build build: 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: black --check av examples tests setup.py flake8 av examples tests isort --check-only --diff av examples tests mypy av tests test: $(PYTHON) setup.py test PyAV-12.3.0/README.md000066400000000000000000000060611464705311200137010ustar00rootroot00000000000000PyAV ==== [![GitHub Test Status][github-tests-badge]][github-tests] [![Gitter Chat][gitter-badge]][gitter] [![Documentation][docs-badge]][docs] \ [![Python Package Index][pypi-badge]][pypi] [![Conda Forge][conda-badge]][conda] 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. 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 ------------ Due to the complexity of the dependencies, PyAV is not always the easiest Python package to install from source. Since release 8.0.0 binary wheels are provided on [PyPI][pypi] for Linux, Mac and Windows linked against a modern FFmpeg. You can install these wheels by running: ```bash pip install av ``` If you want to use your existing FFmpeg, the source version of PyAV is on [PyPI][pypi] too: ```bash pip install av --no-binary av ``` Installing from source is not supported on Windows. Alternative installation methods -------------------------------- 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. And if you want to build from the absolute source (POSIX only): ```bash git clone https://github.com/PyAV-Org/PyAV.git cd PyAV source scripts/activate.sh # Either install the testing dependencies: pip install --upgrade -r tests/requirements.txt # or have it all, including FFmpeg, built/installed for you: ./scripts/build-deps # Build PyAV. make pip install . ``` --- Have fun, [read the docs][docs], [come chat with us][gitter], 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 [gitter-badge]: https://img.shields.io/gitter/room/nwjs/nw.js.svg?logo=gitter&colorB=cc2b5e [gitter]: https://app.gitter.im/#/room/#PyAV-Org_User-Help:gitter.im [pypi-badge]: https://img.shields.io/pypi/v/av.svg?colorB=CCB39A [pypi]: https://pypi.org/project/av [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-12.3.0/av/000077500000000000000000000000001464705311200130255ustar00rootroot00000000000000PyAV-12.3.0/av/__init__.pxd000066400000000000000000000000001464705311200152670ustar00rootroot00000000000000PyAV-12.3.0/av/__init__.py000066400000000000000000000037221464705311200151420ustar00rootroot00000000000000import os import sys # Some Python versions distributed by Conda have a buggy `os.add_dll_directory` # which prevents binary wheels from finding the FFmpeg DLLs in the `av.libs` # directory. We work around this by adding `av.libs` to the PATH. if ( os.name == "nt" and sys.version_info[:2] in ((3, 8), (3, 9)) and os.path.exists(os.path.join(sys.base_prefix, "conda-meta")) ): os.environ["PATH"] = ( os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, "av.libs")) + os.pathsep + os.environ["PATH"] ) # MUST import the core before anything else in order to initalize the underlying # library that is being wrapped. from av._core import time_base, library_versions # Capture logging (by importing it). from av import logging # For convenience, IMPORT ALL OF THE THINGS (that are constructable by the user). from av.about import __version__ 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.bitstream import BitStreamFilterContext, bitstream_filters_available from av.codec.codec import Codec, codecs_available from av.codec.context import CodecContext 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.format import VideoFormat from av.video.frame import VideoFrame # Backwards compatibility AVError = FFmpegError # noqa: F405 def get_include() -> str: """ Returns the path to the `include` folder to be used when building extensions to av. """ # 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-12.3.0/av/__main__.py000066400000000000000000000021241464705311200151160ustar00rootroot00000000000000import argparse def main(): parser = argparse.ArgumentParser() parser.add_argument("--codecs", 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 = {} 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.codecs: from av.codec.codec import dump_codecs dump_codecs() if __name__ == "__main__": main() PyAV-12.3.0/av/_core.pyi000066400000000000000000000003421464705311200146360ustar00rootroot00000000000000from 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]] time_base: int PyAV-12.3.0/av/_core.pyx000066400000000000000000000032451464705311200146620ustar00rootroot00000000000000cimport 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) 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-12.3.0/av/about.py000066400000000000000000000000271464705311200145100ustar00rootroot00000000000000__version__ = "12.3.0" PyAV-12.3.0/av/audio/000077500000000000000000000000001464705311200141265ustar00rootroot00000000000000PyAV-12.3.0/av/audio/__init__.pxd000066400000000000000000000000001464705311200163700ustar00rootroot00000000000000PyAV-12.3.0/av/audio/__init__.py000066400000000000000000000000761464705311200162420ustar00rootroot00000000000000from .frame import AudioFrame from .stream import AudioStream PyAV-12.3.0/av/audio/codeccontext.pxd000066400000000000000000000005201464705311200173220ustar00rootroot00000000000000 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-12.3.0/av/audio/codeccontext.pyi000066400000000000000000000012141464705311200173310ustar00rootroot00000000000000from 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 AudioCodecContext(CodecContext): frame_size: int sample_rate: int rate: int channels: int channel_layout: int layout: AudioLayout format: AudioFormat type: Literal["audio"] 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-12.3.0/av/audio/codeccontext.pyx000066400000000000000000000073041464705311200173560ustar00rootroot00000000000000cimport 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.frame cimport Frame from av.packet cimport Packet cdef class AudioCodecContext(CodecContext): cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec): CodecContext._init(self, ptr, codec) # Sometimes there isn't a layout set, but there are a number of # channels. Assume it is the default layout. # TODO: Put this behind `not bare_metal`. # TODO: Do this more efficiently. if self.ptr.channels and not self.ptr.channel_layout: self.ptr.channel_layout = get_audio_layout(self.ptr.channels, 0).layout cdef _set_default_time_base(self): self.ptr.time_base.num = 1 self.ptr.time_base.den = self.ptr.sample_rate 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 # TODO: Integrate into AudioLayout. @property def channels(self): return self.ptr.channels @channels.setter def channels(self, value): self.ptr.channels = value self.ptr.channel_layout = lib.av_get_default_channel_layout(value) @property def channel_layout(self): return self.ptr.channel_layout @property def layout(self): """ The audio channel layout. :type: AudioLayout """ return get_audio_layout(self.ptr.channels, self.ptr.channel_layout) @layout.setter def layout(self, value): cdef AudioLayout layout = AudioLayout(value) self.ptr.channel_layout = layout.layout self.ptr.channels = layout.nb_channels @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-12.3.0/av/audio/fifo.pxd000066400000000000000000000007151464705311200155710ustar00rootroot00000000000000cimport 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-12.3.0/av/audio/fifo.pyi000066400000000000000000000010421464705311200155710ustar00rootroot00000000000000from .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: ... PyAV-12.3.0/av/audio/fifo.pyx000066400000000000000000000142461464705311200156220ustar00rootroot00000000000000from 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, len(frame.layout.channels), # TODO: Can we safely use frame.ptr.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 frame.ptr.channel_layout != self.template.ptr.channel_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.channel_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-12.3.0/av/audio/format.pxd000066400000000000000000000003131464705311200161300ustar00rootroot00000000000000cimport 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-12.3.0/av/audio/format.pyi000066400000000000000000000003541464705311200161430ustar00rootroot00000000000000class 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-12.3.0/av/audio/format.pyx000066400000000000000000000070641464705311200161670ustar00rootroot00000000000000import 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-12.3.0/av/audio/frame.pxd000066400000000000000000000013311464705311200157330ustar00rootroot00000000000000cimport 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, uint64_t layout, unsigned int nb_samples, unsigned int align) cdef _init_user_attributes(self) cdef AudioFrame alloc_audio_frame() PyAV-12.3.0/av/audio/frame.pyi000066400000000000000000000017361464705311200157520ustar00rootroot00000000000000from 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 AudioFrame(Frame): planes: tuple[AudioPlane, ...] samples: int sample_rate: int rate: int format: AudioFormat layout: AudioLayout 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-12.3.0/av/audio/frame.pyx000066400000000000000000000127621464705311200157720ustar00rootroot00000000000000from 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, check_ndarray_shape import warnings from av.deprecation import AVDeprecationWarning 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, uint64_t layout, unsigned int nb_samples, unsigned int align): self.ptr.nb_samples = nb_samples self.ptr.format = format self.ptr.channel_layout = layout # Sometimes this is called twice. Oh well. self._init_user_attributes() # Audio filters need AVFrame.channels to match number of channels from layout. self.ptr.channels = self.layout.nb_channels if self.layout.channels 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, len(self.layout.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, len(self.layout.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(0, self.ptr.channel_layout) self.format = get_audio_format(self.ptr.format) def __repr__(self): return ( f" 8: raise ValueError(f"no layout with {layout} channels") c_layout = default_layouts[layout] elif isinstance(layout, str): c_layout = lib.av_get_channel_layout(layout) elif isinstance(layout, AudioLayout): c_layout = (layout).layout else: raise TypeError("layout must be str or int") if not c_layout: raise ValueError(f"invalid channel layout: {layout}") self._init(c_layout) cdef _init(self, uint64_t layout): self.layout = layout self.nb_channels = lib.av_get_channel_layout_nb_channels(layout) # This just counts bits. self.channels = tuple(AudioChannel(self, i) for i in range(self.nb_channels)) def __repr__(self): return f"" @property def name(self): """The canonical name of the audio layout.""" cdef char out[32] # Passing 0 as number of channels... fix this later? lib.av_get_channel_layout_string(out, 32, 0, self.layout) return out cdef class AudioChannel: def __cinit__(self, AudioLayout layout, int index): self.channel = lib.av_channel_layout_extract_channel(layout.layout, index) def __repr__(self): return f"" @property def name(self): """The canonical name of the audio channel.""" return lib.av_get_channel_name(self.channel) @property def description(self): """A human description of the audio channel.""" return channel_descriptions.get(self.name) PyAV-12.3.0/av/audio/plane.pxd000066400000000000000000000002061464705311200157400ustar00rootroot00000000000000from av.plane cimport Plane cdef class AudioPlane(Plane): cdef readonly size_t buffer_size cdef size_t _buffer_size(self) PyAV-12.3.0/av/audio/plane.pyi000066400000000000000000000001121464705311200157420ustar00rootroot00000000000000from av.plane import Plane class AudioPlane(Plane): buffer_size: int PyAV-12.3.0/av/audio/plane.pyx000066400000000000000000000005151464705311200157700ustar00rootroot00000000000000from 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-12.3.0/av/audio/resampler.pxd000066400000000000000000000007501464705311200166370ustar00rootroot00000000000000from 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-12.3.0/av/audio/resampler.pyi000066400000000000000000000010361464705311200166430ustar00rootroot00000000000000from 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-12.3.0/av/audio/resampler.pyx000066400000000000000000000102461464705311200166650ustar00rootroot00000000000000cimport libav as lib from 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 = layout if isinstance(layout, AudioLayout) else 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.layout == self.layout.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=str(self.layout.layout)) abuffersink = self.graph.add("abuffersink") abuffer.link_to(aformat) aformat.link_to(abuffersink) self.graph.configure() if self.frame_size > 0: lib.av_buffersink_set_frame_size((abuffersink).ptr, self.frame_size) elif frame is not None: # Assert the settings are the same on consecutive frames. if ( frame.format.sample_fmt != self.template.format.sample_fmt or frame.layout.layout != self.template.layout.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.AVError as e: if e.errno != errno.EAGAIN: raise break return output PyAV-12.3.0/av/audio/stream.pxd000066400000000000000000000003211464705311200161320ustar00rootroot00000000000000from 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-12.3.0/av/audio/stream.pyi000066400000000000000000000011771464705311200161520ustar00rootroot00000000000000from 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 AudioStream(Stream): codec_context: AudioCodecContext # From codec context frame_size: int sample_rate: int rate: int channels: int channel_layout: int layout: AudioLayout format: AudioFormat type: Literal["audio"] def encode(self, frame: AudioFrame | None = None) -> list[Packet]: ... def decode(self, packet: Packet | None = None) -> list[AudioFrame]: ... PyAV-12.3.0/av/audio/stream.pyx000066400000000000000000000023001464705311200161560ustar00rootroot00000000000000from 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-12.3.0/av/bitstream.pxd000066400000000000000000000002701464705311200155330ustar00rootroot00000000000000cimport libav as lib from av.packet cimport Packet cdef class BitStreamFilterContext: cdef lib.AVBSFContext *ptr cpdef filter(self, Packet packet=?) cpdef flush(self) PyAV-12.3.0/av/bitstream.pyi000066400000000000000000000006051464705311200155430ustar00rootroot00000000000000from .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-12.3.0/av/bitstream.pyx000066400000000000000000000053601464705311200155650ustar00rootroot00000000000000cimport 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-12.3.0/av/buffer.pxd000066400000000000000000000001761464705311200150170ustar00rootroot00000000000000 cdef class Buffer: cdef size_t _buffer_size(self) cdef void* _buffer_ptr(self) cdef bint _buffer_writable(self) PyAV-12.3.0/av/buffer.pyi000066400000000000000000000004741464705311200150260ustar00rootroot00000000000000# 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-12.3.0/av/buffer.pyx000066400000000000000000000031421464705311200150400ustar00rootroot00000000000000from 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-12.3.0/av/bytesource.pxd000066400000000000000000000003611464705311200157260ustar00rootroot00000000000000from 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-12.3.0/av/bytesource.pyx000066400000000000000000000021071464705311200157530ustar00rootroot00000000000000from 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-12.3.0/av/codec/000077500000000000000000000000001464705311200141025ustar00rootroot00000000000000PyAV-12.3.0/av/codec/__init__.pxd000066400000000000000000000000001464705311200163440ustar00rootroot00000000000000PyAV-12.3.0/av/codec/__init__.py000066400000000000000000000001711464705311200162120ustar00rootroot00000000000000from .codec import Capabilities, Codec, Properties, codec_descriptor, codecs_available from .context import CodecContext PyAV-12.3.0/av/codec/codec.pxd000066400000000000000000000003451464705311200156760ustar00rootroot00000000000000cimport libav as lib cdef class Codec: cdef const lib.AVCodec *ptr cdef const lib.AVCodecDescriptor *desc cdef readonly bint is_encoder cdef _init(self, name=?) cdef Codec wrap_codec(const lib.AVCodec *ptr) PyAV-12.3.0/av/codec/codec.pyi000066400000000000000000000030231464705311200157000ustar00rootroot00000000000000from fractions import Fraction from typing import Literal from av.audio.format import AudioFormat from av.descriptor import Descriptor from av.enum import EnumFlag from av.video.format import VideoFormat from .context import CodecContext class Properties(EnumFlag): NONE: int INTRA_ONLY: int LOSSY: int LOSSLESS: int REORDER: int BITMAP_SUB: int TEXT_SUB: int class Capabilities(EnumFlag): NONE: int DARW_HORIZ_BAND: int DR1: int HWACCEL: int DELAY: int SMALL_LAST_FRAME: int HWACCEL_VDPAU: int SUBFRAMES: int EXPERIMENTAL: int CHANNEL_CONF: int NEG_LINESIZES: int FRAME_THREADS: int SLICE_THREADS: int PARAM_CHANGE: int AUTO_THREADS: int VARIABLE_FRAME_SIZE: int AVOID_PROBING: int HARDWARE: int HYBRID: int ENCODER_REORDERED_OPAQUE: int ENCODER_FLUSH: int class UnknownCodecError(ValueError): ... class Codec: is_decoder: bool descriptor: Descriptor name: str long_name: str type: Literal["video", "audio", "data", "subtitle", "attachment"] id: int frame_rates: list[Fraction] | None audio_rates: list[int] | None video_formats: list[VideoFormat] | None audio_formats: list[AudioFormat] | None properties: Properties capabilities: Capabilities def __init__(self, name: str, mode: Literal["r", "w"]) -> None: ... def create(self) -> CodecContext: ... class codec_descriptor: name: str options: tuple[int, ...] codecs_available: set[str] def dump_codecs() -> None: ... PyAV-12.3.0/av/codec/codec.pyx000066400000000000000000000352021464705311200157230ustar00rootroot00000000000000from av.audio.format cimport get_audio_format from av.descriptor cimport wrap_avclass from av.enum cimport define_enum from av.utils cimport avrational_to_fraction from av.video.format cimport get_video_format 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 Properties = define_enum("Properties", "av.codec", ( ("NONE", 0), ("INTRA_ONLY", lib.AV_CODEC_PROP_INTRA_ONLY, """Codec uses only intra compression. Video and audio codecs only."""), ("LOSSY", lib.AV_CODEC_PROP_LOSSY, """Codec supports lossy compression. Audio and video codecs only. Note: A codec may support both lossy and lossless compression modes."""), ("LOSSLESS", lib.AV_CODEC_PROP_LOSSLESS, """Codec supports lossless compression. Audio and video codecs only."""), ("REORDER", lib.AV_CODEC_PROP_REORDER, """Codec supports frame reordering. That is, the coded order (the order in which the encoded packets are output by the encoders / stored / input to the decoders) may be different from the presentation order of the corresponding frames. For codecs that do not have this property set, PTS and DTS should always be equal."""), ("BITMAP_SUB", lib.AV_CODEC_PROP_BITMAP_SUB, """Subtitle codec is bitmap based Decoded AVSubtitle data can be read from the AVSubtitleRect->pict field."""), ("TEXT_SUB", lib.AV_CODEC_PROP_TEXT_SUB, """Subtitle codec is text based. Decoded AVSubtitle data can be read from the AVSubtitleRect->ass field."""), ), is_flags=True) Capabilities = define_enum("Capabilities", "av.codec", ( ("NONE", 0), ("DRAW_HORIZ_BAND", lib.AV_CODEC_CAP_DRAW_HORIZ_BAND, """Decoder can use draw_horiz_band callback."""), ("DR1", lib.AV_CODEC_CAP_DR1, """Codec uses get_buffer() for allocating buffers and supports custom allocators. If not set, it might not use get_buffer() at all or use operations that assume the buffer was allocated by avcodec_default_get_buffer."""), ("HWACCEL", 1 << 4), ("DELAY", lib.AV_CODEC_CAP_DELAY, """Encoder or decoder requires flushing with NULL input at the end in order to give the complete and correct output. NOTE: If this flag is not set, the codec is guaranteed to never be fed with with NULL data. The user can still send NULL data to the public encode or decode function, but libavcodec will not pass it along to the codec unless this flag is set. Decoders: The decoder has a non-zero delay and needs to be fed with avpkt->data=NULL, avpkt->size=0 at the end to get the delayed data until the decoder no longer returns frames. Encoders: The encoder needs to be fed with NULL data at the end of encoding until the encoder no longer returns data. NOTE: For encoders implementing the AVCodec.encode2() function, setting this flag also means that the encoder must set the pts and duration for each output packet. If this flag is not set, the pts and duration will be determined by libavcodec from the input frame."""), ("SMALL_LAST_FRAME", lib.AV_CODEC_CAP_SMALL_LAST_FRAME, """Codec can be fed a final frame with a smaller size. This can be used to prevent truncation of the last audio samples."""), ("HWACCEL_VDPAU", 1 << 7), ("SUBFRAMES", lib.AV_CODEC_CAP_SUBFRAMES, """Codec can output multiple frames per AVPacket Normally demuxers return one frame at a time, demuxers which do not do are connected to a parser to split what they return into proper frames. This flag is reserved to the very rare category of codecs which have a bitstream that cannot be split into frames without timeconsuming operations like full decoding. Demuxers carrying such bitstreams thus may return multiple frames in a packet. This has many disadvantages like prohibiting stream copy in many cases thus it should only be considered as a last resort."""), ("EXPERIMENTAL", lib.AV_CODEC_CAP_EXPERIMENTAL, """Codec is experimental and is thus avoided in favor of non experimental encoders"""), ("CHANNEL_CONF", lib.AV_CODEC_CAP_CHANNEL_CONF, """Codec should fill in channel configuration and samplerate instead of container"""), ("NEG_LINESIZES", 1 << 11), ("FRAME_THREADS", lib.AV_CODEC_CAP_FRAME_THREADS, """Codec supports frame-level multithreading""",), ("SLICE_THREADS", lib.AV_CODEC_CAP_SLICE_THREADS, """Codec supports slice-based (or partition-based) multithreading."""), ("PARAM_CHANGE", lib.AV_CODEC_CAP_PARAM_CHANGE, """Codec supports changed parameters at any point."""), ("AUTO_THREADS", lib.AV_CODEC_CAP_OTHER_THREADS, """Codec supports multithreading through a method other than slice- or frame-level multithreading. Typically this marks wrappers around multithreading-capable external libraries."""), ("VARIABLE_FRAME_SIZE", lib.AV_CODEC_CAP_VARIABLE_FRAME_SIZE, """Audio encoder supports receiving a different number of samples in each call."""), ("AVOID_PROBING", lib.AV_CODEC_CAP_AVOID_PROBING, """Decoder is not a preferred choice for probing. This indicates that the decoder is not a good choice for probing. It could for example be an expensive to spin up hardware decoder, or it could simply not provide a lot of useful information about the stream. A decoder marked with this flag should only be used as last resort choice for probing."""), ("HARDWARE", lib.AV_CODEC_CAP_HARDWARE, """Codec is backed by a hardware implementation. Typically used to identify a non-hwaccel hardware decoder. For information about hwaccels, use avcodec_get_hw_config() instead."""), ("HYBRID", lib.AV_CODEC_CAP_HYBRID, """Codec is potentially backed by a hardware implementation, but not necessarily. This is used instead of AV_CODEC_CAP_HARDWARE, if the implementation provides some sort of internal fallback."""), ("ENCODER_REORDERED_OPAQUE", 1 << 20, # lib.AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE, # FFmpeg 4.2 """This codec takes the reordered_opaque field from input AVFrames and returns it in the corresponding field in AVCodecContext after encoding."""), ("ENCODER_FLUSH", 1 << 21, # lib.AV_CODEC_CAP_ENCODER_FLUSH # FFmpeg 4.3 """This encoder can be flushed using avcodec_flush_buffers(). If this flag is not set, the encoder must be closed and reopened to ensure that no frames remain pending."""), ), is_flags=True) 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 create(self): """Create a :class:`.CodecContext` for this codec.""" from .context import CodecContext return CodecContext.create(self) @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 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 # NOTE: there are some overlaps, which we defer to how `ffmpeg -codecs` # handles them (by prefering the capablity to the property). # Also, LOSSLESS and LOSSY don't have to agree. @Properties.property def properties(self): """Flag property of :class:`.Properties`""" return self.desc.props intra_only = properties.flag_property("INTRA_ONLY") lossy = properties.flag_property("LOSSY") # Defer to capability. lossless = properties.flag_property("LOSSLESS") # Defer to capability. reorder = properties.flag_property("REORDER") bitmap_sub = properties.flag_property("BITMAP_SUB") text_sub = properties.flag_property("TEXT_SUB") @Capabilities.property def capabilities(self): """Flag property of :class:`.Capabilities`""" return self.ptr.capabilities draw_horiz_band = capabilities.flag_property("DRAW_HORIZ_BAND") dr1 = capabilities.flag_property("DR1") hwaccel = capabilities.flag_property("HWACCEL") delay = capabilities.flag_property("DELAY") small_last_frame = capabilities.flag_property("SMALL_LAST_FRAME") hwaccel_vdpau = capabilities.flag_property("HWACCEL_VDPAU") subframes = capabilities.flag_property("SUBFRAMES") experimental = capabilities.flag_property("EXPERIMENTAL") channel_conf = capabilities.flag_property("CHANNEL_CONF") neg_linesizes = capabilities.flag_property("NEG_LINESIZES") frame_threads = capabilities.flag_property("FRAME_THREADS") slice_threads = capabilities.flag_property("SLICE_THREADS") param_change = capabilities.flag_property("PARAM_CHANGE") auto_threads = capabilities.flag_property("AUTO_THREADS") variable_frame_size = capabilities.flag_property("VARIABLE_FRAME_SIZE") avoid_probing = capabilities.flag_property("AVOID_PROBING") # intra_only = capabilities.flag_property("INTRA_ONLY") # Dupes. # lossless = capabilities.flag_property("LOSSLESS") # Dupes. hardware = capabilities.flag_property("HARDWARE") hybrid = capabilities.flag_property("HYBRID") encoder_reordered_opaque = capabilities.flag_property("ENCODER_REORDERED_OPAQUE") encoder_flush = capabilities.flag_property("ENCODER_FLUSH") 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}") PyAV-12.3.0/av/codec/context.pxd000066400000000000000000000043221464705311200163040ustar00rootroot00000000000000cimport libav as lib from libc.stdint cimport int64_t from av.bytesource cimport ByteSource from av.codec.codec cimport Codec 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) cdef readonly Codec codec cdef public dict options # Public API. cpdef open(self, bint strict=?) cpdef close(self, bint strict=?) cdef _set_default_time_base(self) # 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 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 recieving. 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) # 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*) PyAV-12.3.0/av/codec/context.pyi000066400000000000000000000033641464705311200163170ustar00rootroot00000000000000from fractions import Fraction from typing import Any, Literal from av.enum import EnumFlag, EnumItem from av.packet import Packet from .codec import Codec class ThreadType(EnumFlag): NONE: int FRAME: int SLICE: int AUTO: int class SkipType(EnumItem): NONE: int DEFAULT: int NONREF: int BIDIR: int NONINTRA: int NONKEY: int ALL: int class Flags(EnumFlag): NONE: int UNALIGNED: int QSCALE: int # 4MV OUTPUT_CORRUPT: int QPEL: int DROPCHANGED: int PASS1: int PASS2: int LOOP_FILTER: int GRAY: int PSNR: int INTERLACED_DCT: int LOW_DELAY: int GLOBAL_HEADER: int BITEXACT: int AC_PRED: int INTERLACED_ME: int CLOSED_GOP: int class Flags2(EnumFlag): NONE: int FAST: int NO_OUTPUT: int LOCAL_HEADER: int CHUNKS: int IGNORE_CROP: int SHOW_ALL: int EXPORT_MVS: int SKIP_MANUAL: int RO_FLUSH_NOOP: int class CodecContext: extradata: bytes | None is_open: bool is_encoder: bool is_decoder: bool name: str options: dict[str, str] type: Literal["video", "audio", "data", "subtitle", "attachment"] profile: str | None time_base: Fraction codec_tag: str bit_rate: int | None max_bit_rate: int | None bit_rate_tolerance: int thread_count: int thread_type: Any skip_frame: Any def open(self, strict: bool = True) -> None: ... def close(self, strict: bool = True) -> None: ... @staticmethod def create( codec: str | Codec, mode: Literal["r", "w"] | None = None ) -> CodecContext: ... def parse( self, raw_input: bytes | bytearray | memoryview | None = None ) -> list[Packet]: ... def flush_buffers(self) -> None: ... PyAV-12.3.0/av/codec/context.pyx000066400000000000000000000521071464705311200163350ustar00rootroot00000000000000import warnings cimport 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.enum cimport define_enum from av.error cimport err_check from av.packet cimport Packet from av.utils cimport avrational_to_fraction, to_avrational from av.deprecation import AVDeprecationWarning from av.dictionary import Dictionary cdef object _cinit_sentinel = object() cdef CodecContext wrap_codec_context(lib.AVCodecContext *c_ctx, const lib.AVCodec *c_codec): """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) return py_ctx ThreadType = define_enum("ThreadType", __name__, ( ("NONE", 0), ("FRAME", lib.FF_THREAD_FRAME, "Decode more than one frame at once"), ("SLICE", lib.FF_THREAD_SLICE, "Decode more than one part of a single frame at once"), ("AUTO", lib.FF_THREAD_SLICE | lib.FF_THREAD_FRAME, "Decode using both FRAME and SLICE methods."), ), is_flags=True) SkipType = define_enum("SkipType", __name__, ( ("NONE", lib.AVDISCARD_NONE, "Discard nothing"), ("DEFAULT", lib.AVDISCARD_DEFAULT, "Discard useless packets like 0 size packets in AVI"), ("NONREF", lib.AVDISCARD_NONREF, "Discard all non reference"), ("BIDIR", lib.AVDISCARD_BIDIR, "Discard all bidirectional frames"), ("NONINTRA", lib.AVDISCARD_NONINTRA, "Discard all non intra frames"), ("NONKEY", lib.AVDISCARD_NONKEY, "Discard all frames except keyframes"), ("ALL", lib.AVDISCARD_ALL, "Discard all"), )) Flags = define_enum("Flags", __name__, ( ("NONE", 0), ("UNALIGNED", lib.AV_CODEC_FLAG_UNALIGNED, "Allow decoders to produce frames with data planes that are not aligned to CPU requirements (e.g. due to cropping)." ), ("QSCALE", lib.AV_CODEC_FLAG_QSCALE, "Use fixed qscale."), ("4MV", lib.AV_CODEC_FLAG_4MV, "4 MV per MB allowed / advanced prediction for H.263."), ("OUTPUT_CORRUPT", lib.AV_CODEC_FLAG_OUTPUT_CORRUPT, "Output even those frames that might be corrupted."), ("QPEL", lib.AV_CODEC_FLAG_QPEL, "Use qpel MC."), ("DROPCHANGED", 1 << 5, "Don't output frames whose parameters differ from first decoded frame in stream." ), ("PASS1", lib.AV_CODEC_FLAG_PASS1, "Use internal 2pass ratecontrol in first pass mode."), ("PASS2", lib.AV_CODEC_FLAG_PASS2, "Use internal 2pass ratecontrol in second pass mode."), ("LOOP_FILTER", lib.AV_CODEC_FLAG_LOOP_FILTER, "loop filter."), ("GRAY", lib.AV_CODEC_FLAG_GRAY, "Only decode/encode grayscale."), ("PSNR", lib.AV_CODEC_FLAG_PSNR, "error[?] variables will be set during encoding."), ("INTERLACED_DCT", lib.AV_CODEC_FLAG_INTERLACED_DCT, "Use interlaced DCT."), ("LOW_DELAY", lib.AV_CODEC_FLAG_LOW_DELAY, "Force low delay."), ("GLOBAL_HEADER", lib.AV_CODEC_FLAG_GLOBAL_HEADER, "Place global headers in extradata instead of every keyframe." ), ("BITEXACT", lib.AV_CODEC_FLAG_BITEXACT, "Use only bitexact stuff (except (I)DCT)."), ("AC_PRED", lib.AV_CODEC_FLAG_AC_PRED, "H.263 advanced intra coding / MPEG-4 AC prediction"), ("INTERLACED_ME", lib.AV_CODEC_FLAG_INTERLACED_ME, "Interlaced motion estimation"), ("CLOSED_GOP", lib.AV_CODEC_FLAG_CLOSED_GOP), ), is_flags=True) Flags2 = define_enum("Flags2", __name__, ( ("NONE", 0), ("FAST", lib.AV_CODEC_FLAG2_FAST, """Allow non spec compliant speedup tricks."""), ("NO_OUTPUT", lib.AV_CODEC_FLAG2_NO_OUTPUT, """Skip bitstream encoding."""), ("LOCAL_HEADER", lib.AV_CODEC_FLAG2_LOCAL_HEADER, """Place global headers at every keyframe instead of in extradata."""), ("CHUNKS", lib.AV_CODEC_FLAG2_CHUNKS, """Input bitstream might be truncated at a packet boundaries instead of only at frame boundaries."""), ("IGNORE_CROP", lib.AV_CODEC_FLAG2_IGNORE_CROP, """Discard cropping information from SPS."""), ("SHOW_ALL", lib.AV_CODEC_FLAG2_SHOW_ALL, """Show all frames before the first keyframe"""), ("EXPORT_MVS", lib.AV_CODEC_FLAG2_EXPORT_MVS, """Export motion vectors through frame side data"""), ("SKIP_MANUAL", lib.AV_CODEC_FLAG2_SKIP_MANUAL, """Do not skip samples and export skip information as frame side data"""), ("RO_FLUSH_NOOP", lib.AV_CODEC_FLAG2_RO_FLUSH_NOOP, """Do not reset ASS ReadOrder field on flush (subtitles decoding)"""), ), is_flags=True) cdef class CodecContext: @staticmethod def create(codec, mode=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) 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. cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec): 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) # Set reasonable threading defaults. # count == 0 -> use as many threads as there are CPUs. # type == 2 -> thread within a frame. This does not change the API. self.ptr.thread_count = 0 self.ptr.thread_type = 2 def _get_flags(self): return self.ptr.flags def _set_flags(self, value): self.ptr.flags = value flags = Flags.property(_get_flags, _set_flags, "Flag property of :class:`.Flags`.") unaligned = flags.flag_property("UNALIGNED") qscale = flags.flag_property("QSCALE") four_mv = flags.flag_property("4MV") output_corrupt = flags.flag_property("OUTPUT_CORRUPT") qpel = flags.flag_property("QPEL") drop_changed = flags.flag_property("DROPCHANGED") pass1 = flags.flag_property("PASS1") pass2 = flags.flag_property("PASS2") loop_filter = flags.flag_property("LOOP_FILTER") gray = flags.flag_property("GRAY") psnr = flags.flag_property("PSNR") interlaced_dct = flags.flag_property("INTERLACED_DCT") low_delay = flags.flag_property("LOW_DELAY") global_header = flags.flag_property("GLOBAL_HEADER") bitexact = flags.flag_property("BITEXACT") ac_pred = flags.flag_property("AC_PRED") interlaced_me = flags.flag_property("INTERLACED_ME") closed_gop = flags.flag_property("CLOSED_GOP") def _get_flags2(self): return self.ptr.flags2 def _set_flags2(self, value): self.ptr.flags2 = value flags2 = Flags2.property(_get_flags2, _set_flags2, "Flag property of :class:`.Flags2`.") fast = flags2.flag_property("FAST") no_output = flags2.flag_property("NO_OUTPUT") local_header = flags2.flag_property("LOCAL_HEADER") chunks = flags2.flag_property("CHUNKS") ignore_crop = flags2.flag_property("IGNORE_CROP") show_all = flags2.flag_property("SHOW_ALL") export_mvs = flags2.flag_property("EXPORT_MVS") skip_manual = flags2.flag_property("SKIP_MANUAL") ro_flush_noop = flags2.flag_property("RO_FLUSH_NOOP") @property def extradata(self): if self.ptr.extradata_size > 0: return (self.ptr.extradata)[:self.ptr.extradata_size] else: return None @extradata.setter def extradata(self, data): if not self.is_decoder: raise ValueError("Can only set extradata for decoders.") 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_open(self): return lib.avcodec_is_open(self.ptr) @property def is_encoder(self): return lib.av_codec_is_encoder(self.ptr.codec) @property def is_decoder(self): return lib.av_codec_is_decoder(self.ptr.codec) cpdef open(self, bint strict=True): if lib.avcodec_is_open(self.ptr): if strict: raise ValueError("CodecContext is already open.") return cdef _Dictionary options = Dictionary() options.update(self.options or {}) # Assert we have a time_base for encoders. if not self.ptr.time_base.num and self.is_encoder: self._set_default_time_base() err_check(lib.avcodec_open2(self.ptr, self.codec.ptr, &options.ptr)) self.options = dict(options) cdef _set_default_time_base(self): self.ptr.time_base.num = 1 self.ptr.time_base.den = lib.AV_TIME_BASE cpdef close(self, bint strict=True): if not lib.avcodec_is_open(self.ptr): if strict: raise ValueError("CodecContext is already closed.") return err_check(lib.avcodec_close(self.ptr)) def __dealloc__(self): if self.ptr and self.extradata_set: lib.av_freep(&self.ptr.extradata) if self.ptr: lib.avcodec_close(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 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) 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) 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) if not res: self._next_frame = None 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) 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 profile(self): if self.ptr.codec and lib.av_get_profile_name(self.ptr.codec, self.ptr.profile): return lib.av_get_profile_name(self.ptr.codec, self.ptr.profile) @property def time_base(self): if self.is_decoder: warnings.warn( "Using CodecContext.time_base for decoders is deprecated.", AVDeprecationWarning ) return avrational_to_fraction(&self.ptr.time_base) @time_base.setter def time_base(self, value): if self.is_decoder: warnings.warn( "Using CodecContext.time_base for decoders is deprecated.", AVDeprecationWarning ) 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 ticks_per_frame(self): return self.ptr.ticks_per_frame @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 lib.avcodec_is_open(self.ptr): 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.get(self.ptr.thread_type, create=True) @thread_type.setter def thread_type(self, value): if lib.avcodec_is_open(self.ptr): raise RuntimeError("Cannot change thread_type after codec is open.") self.ptr.thread_type = ThreadType[value].value @property def skip_frame(self): """One of :class:`.SkipType`. Wraps :ffmpeg:`AVCodecContext.skip_frame`. """ return SkipType._get(self.ptr.skip_frame, create=True) @skip_frame.setter def skip_frame(self, value): self.ptr.skip_frame = SkipType[value].value @property def delay(self): """Codec delay. Wraps :ffmpeg:`AVCodecContext.delay`. """ return self.ptr.delay PyAV-12.3.0/av/container/000077500000000000000000000000001464705311200150075ustar00rootroot00000000000000PyAV-12.3.0/av/container/__init__.pxd000066400000000000000000000000001464705311200172510ustar00rootroot00000000000000PyAV-12.3.0/av/container/__init__.py000066400000000000000000000001571464705311200171230ustar00rootroot00000000000000from .core import Container, Flags, open from .input import InputContainer from .output import OutputContainer PyAV-12.3.0/av/container/__init__.pyi000066400000000000000000000000771464705311200172750ustar00rootroot00000000000000from .core import * from .input import * from .output import * PyAV-12.3.0/av/container/core.pxd000066400000000000000000000023231464705311200164540ustar00rootroot00000000000000cimport libav as lib 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 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-12.3.0/av/container/core.pyi000066400000000000000000000064751464705311200164760ustar00rootroot00000000000000from numbers import Real from pathlib import Path from types import TracebackType from typing import Any, Callable, Literal, Type, overload from av.enum import EnumFlag from av.format import ContainerFormat from .input import InputContainer from .output import OutputContainer from .streams import StreamContainer class Flags(EnumFlag): GENPTS: int IGNIDX: int NONBLOCK: int IGNDTS: int NOFILLIN: int NOPARSE: int NOBUFFER: int CUSTOM_IO: int DISCARD_CORRUPT: int FLUSH_PACKETS: int BITEXACT: int SORT_DTS: int FAST_SEEK: int SHORTEST: int AUTO_BSF: 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[str] streams: StreamContainer metadata: dict[str, str] open_timeout: Real | None read_timeout: Real | None def __enter__(self) -> Container: ... def __exit__( self, exc_type: Type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None, ) -> bool: ... def err_check(self, value: int) -> int: ... 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, ) -> 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, ) -> 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, ) -> 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, ) -> InputContainer | OutputContainer: ... PyAV-12.3.0/av/container/core.pyx000077500000000000000000000355771464705311200165250ustar00rootroot00000000000000from cython.operator cimport dereference from libc.stdint cimport int64_t import os import time from pathlib import Path cimport libav as lib 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.enum cimport define_enum 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 void pyav_io_close(lib.AVFormatContext *s, lib.AVIOContext *pb) noexcept nogil: with gil: pyav_io_close_gil(s, pb) cdef void pyav_io_close_gil(lib.AVFormatContext *s, lib.AVIOContext *pb) noexcept: cdef Container container try: container = dereference(s).opaque if container.open_files is not None and pb.opaque in container.open_files: pyio_close_custom_gil(pb) # Remove it from the container so that it can be deallocated del container.open_files[pb.opaque] else: pyio_close_gil(pb) except Exception as e: stash_exception() Flags = define_enum("Flags", __name__, ( ("GENPTS", lib.AVFMT_FLAG_GENPTS, "Generate missing pts even if it requires parsing future frames."), ("IGNIDX", lib.AVFMT_FLAG_IGNIDX, "Ignore index."), ("NONBLOCK", lib.AVFMT_FLAG_NONBLOCK, "Do not block when reading packets from input."), ("IGNDTS", lib.AVFMT_FLAG_IGNDTS, "Ignore DTS on frames that contain both DTS & PTS."), ("NOFILLIN", lib.AVFMT_FLAG_NOFILLIN, "Do not infer any values from other values, just return what is stored in the container."), ("NOPARSE", lib.AVFMT_FLAG_NOPARSE, """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."""), ("NOBUFFER", lib.AVFMT_FLAG_NOBUFFER, "Do not buffer frames when possible."), ("CUSTOM_IO", lib.AVFMT_FLAG_CUSTOM_IO, "The caller has supplied a custom AVIOContext, don't avio_close() it."), ("DISCARD_CORRUPT", lib.AVFMT_FLAG_DISCARD_CORRUPT, "Discard frames marked corrupted."), ("FLUSH_PACKETS", lib.AVFMT_FLAG_FLUSH_PACKETS, "Flush the AVIOContext every packet."), ("BITEXACT", lib.AVFMT_FLAG_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."""), ("SORT_DTS", lib.AVFMT_FLAG_SORT_DTS, "Try to interleave outputted packets by dts (using this flag can slow demuxing down)."), ("FAST_SEEK", lib.AVFMT_FLAG_FAST_SEEK, "Enable fast, but inaccurate seeks for some formats."), ("SHORTEST", lib.AVFMT_FLAG_SHORTEST, "Stop muxing when the shortest stream stops."), ("AUTO_BSF", lib.AVFMT_FLAG_AUTO_BSF, "Add bitstream filters as requested by the muxer."), ), is_flags=True) cdef class Container: def __cinit__(self, sentinel, file_, format_name, options, container_options, stream_options, 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.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 if format_name is not None: 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 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_close = 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") def _get_flags(self): self._assert_open() return self.ptr.flags def _set_flags(self, value): self._assert_open() self.ptr.flags = value flags = Flags.property( _get_flags, _set_flags, """Flags property of :class:`.Flags`""" ) gen_pts = flags.flag_property("GENPTS") ign_idx = flags.flag_property("IGNIDX") non_block = flags.flag_property("NONBLOCK") ign_dts = flags.flag_property("IGNDTS") no_fill_in = flags.flag_property("NOFILLIN") no_parse = flags.flag_property("NOPARSE") no_buffer = flags.flag_property("NOBUFFER") custom_io = flags.flag_property("CUSTOM_IO") discard_corrupt = flags.flag_property("DISCARD_CORRUPT") flush_packets = flags.flag_property("FLUSH_PACKETS") bit_exact = flags.flag_property("BITEXACT") sort_dts = flags.flag_property("SORT_DTS") fast_seek = flags.flag_property("FAST_SEEK") shortest = flags.flag_property("SHORTEST") auto_bsf = flags.flag_property("AUTO_BSF") 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, ): """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 :ref:`(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. :rtype: Container For devices (via ``libavdevice``), pass the name of the device to ``format``, e.g.:: >>> # Open webcam on OS X. >>> av.open(format='avfoundation', file='0') # 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, 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, metadata_encoding, metadata_errors, buffer_size, open_timeout, read_timeout, io_open, ) PyAV-12.3.0/av/container/input.pxd000066400000000000000000000002431464705311200166620ustar00rootroot00000000000000cimport libav as lib from av.container.core cimport Container from av.stream cimport Stream cdef class InputContainer(Container): cdef flush_buffers(self) PyAV-12.3.0/av/container/input.pyi000066400000000000000000000031121464705311200166660ustar00rootroot00000000000000from 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-12.3.0/av/container/input.pyx000066400000000000000000000245161464705311200167200ustar00rootroot00000000000000from 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): 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) 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) else: # no decoder is available py_codec_context = None self.streams.add_stream(wrap_stream(self, stream, py_codec_context)) 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-12.3.0/av/container/output.pxd000066400000000000000000000003641464705311200170670ustar00rootroot00000000000000cimport 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-12.3.0/av/container/output.pyi000066400000000000000000000016051464705311200170740ustar00rootroot00000000000000from fractions import Fraction from typing import Sequence from av.packet import Packet from av.stream import Stream from .core import Container class OutputContainer(Container): def __enter__(self) -> OutputContainer: ... def add_stream( self, codec_name: str | None = None, rate: Fraction | int | float | None = None, template: Stream | None = None, options: dict[str, str] | None = None, ) -> Stream: ... 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-12.3.0/av/container/output.pyx000066400000000000000000000231461464705311200171170ustar00rootroot00000000000000import logging import os 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): 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=None, object rate=None, Stream template=None, options=None, **kwargs): """add_stream(codec_name, rate=None) Create a new stream, and return it. :param str codec_name: The name of a codec. :param rate: The frame rate for video, and sample rate for audio. Examples for video include ``24``, ``23.976``, and ``Fraction(30000,1001)``. Examples for audio include ``48000`` and ``44100``. :param template: Copy codec from another :class:`~av.stream.Stream` instance. :param dict options: Stream options. :param \\**kwargs: Set attributes of the stream. :returns: The new :class:`~av.stream.Stream`. """ if (codec_name is None and template is None) or (codec_name is not None and template is not None): raise ValueError("needs one of codec_name or template") cdef const lib.AVCodec *codec cdef Codec codec_obj if codec_name is not None: codec_obj = codec_name if isinstance(codec_name, Codec) else Codec(codec_name, "w") else: if not template.codec_context: raise ValueError("template has no codec context") codec_obj = template.codec_context.codec 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_name!r} codec" ) # Create new stream in the AVFormatContext, set AVCodecContext values. lib.avformat_new_stream(self.ptr, codec) cdef lib.AVStream *stream = self.ptr.streams[self.ptr.nb_streams - 1] cdef lib.AVCodecContext *codec_context = lib.avcodec_alloc_context3(codec) # Copy from the template. if template is not None: 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 # Now lets set some more sane video defaults elif codec.type == lib.AVMEDIA_TYPE_VIDEO: codec_context.pix_fmt = lib.AV_PIX_FMT_YUV420P codec_context.width = 640 codec_context.height = 480 codec_context.bit_rate = 1024000 codec_context.bit_rate_tolerance = 128000 codec_context.ticks_per_frame = 1 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 = 128000 codec_context.bit_rate_tolerance = 32000 codec_context.sample_rate = rate or 48000 codec_context.channels = 2 codec_context.channel_layout = lib.AV_CH_LAYOUT_STEREO # 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) 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 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 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): for stream in self.streams: if stream.codec_context: stream.codec_context.close(strict=False) 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-12.3.0/av/container/pyio.pxd000066400000000000000000000013331464705311200165040ustar00rootroot00000000000000cimport 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, uint8_t *buf, int buf_size) noexcept nogil cdef int64_t pyio_seek(void *opaque, int64_t offset, int whence) noexcept nogil cdef void pyio_close_gil(lib.AVIOContext *pb) cdef void 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-12.3.0/av/container/pyio.pyx000066400000000000000000000122601464705311200165320ustar00rootroot00000000000000cimport 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, 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, 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 void pyio_close_gil(lib.AVIOContext *pb): try: lib.avio_close(pb) except Exception as e: stash_exception() cdef void 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 lib.avio_flush(pb) if self.fclose is not None: self.fclose() except Exception as e: stash_exception() PyAV-12.3.0/av/container/streams.pxd000066400000000000000000000004671464705311200172110ustar00rootroot00000000000000from av.stream cimport Stream 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 data cdef readonly tuple other cdef add_stream(self, Stream stream) PyAV-12.3.0/av/container/streams.pyi000066400000000000000000000017441464705311200172160ustar00rootroot00000000000000from typing import Iterator, overload 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, ...] data: tuple[DataStream, ...] other: tuple[Stream, ...] def __init__(self) -> None: ... def add_stream(self, stream: Stream) -> 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]: ... PyAV-12.3.0/av/container/streams.pyx000066400000000000000000000065701464705311200172370ustar00rootroot00000000000000 cimport 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 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.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_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[:] PyAV-12.3.0/av/data/000077500000000000000000000000001464705311200137365ustar00rootroot00000000000000PyAV-12.3.0/av/data/__init__.pxd000066400000000000000000000000001464705311200162000ustar00rootroot00000000000000PyAV-12.3.0/av/data/__init__.py000066400000000000000000000000001464705311200160350ustar00rootroot00000000000000PyAV-12.3.0/av/data/stream.pxd000066400000000000000000000001101464705311200157360ustar00rootroot00000000000000from av.stream cimport Stream cdef class DataStream(Stream): pass PyAV-12.3.0/av/data/stream.pyi000066400000000000000000000002051464705311200157510ustar00rootroot00000000000000from av.frame import Frame from av.packet import Packet from av.stream import Stream class DataStream(Stream): name: str | None PyAV-12.3.0/av/data/stream.pyx000066400000000000000000000006741464705311200160020ustar00rootroot00000000000000cimport 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-12.3.0/av/datasets.py000066400000000000000000000060261464705311200152130ustar00rootroot00000000000000import 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-12.3.0/av/deprecation.py000066400000000000000000000044111464705311200156740ustar00rootroot00000000000000import functools import warnings class AVDeprecationWarning(DeprecationWarning): pass class AttributeRenamedWarning(AVDeprecationWarning): pass class MethodDeprecationWarning(AVDeprecationWarning): pass # DeprecationWarning is not printed by default (unless in __main__). We # really want these to be seen, but also to use the "correct" base classes. # So we're putting a filter in place to show our warnings. The users can # turn them back off if they want. warnings.filterwarnings("default", "", AVDeprecationWarning) class renamed_attr: """Proxy for renamed attributes (or methods) on classes. Getting and setting values will be redirected to the provided name, and warnings will be issues every time. """ def __init__(self, new_name): self.new_name = new_name self._old_name = None def old_name(self, cls): if self._old_name is None: for k, v in vars(cls).items(): if v is self: self._old_name = k break return self._old_name def __get__(self, instance, cls): old_name = self.old_name(cls) warnings.warn( "{0}.{1} is deprecated; please use {0}.{2}.".format( cls.__name__, old_name, self.new_name, ), AttributeRenamedWarning, stacklevel=2, ) return getattr(instance if instance is not None else cls, self.new_name) def __set__(self, instance, value): old_name = self.old_name(instance.__class__) warnings.warn( "{0}.{1} is deprecated; please use {0}.{2}.".format( instance.__class__.__name__, old_name, self.new_name, ), AttributeRenamedWarning, stacklevel=2, ) setattr(instance, self.new_name, value) class method: def __init__(self, func): functools.update_wrapper(self, func, ("__name__", "__doc__")) self.func = func def __get__(self, instance, cls): warning = MethodDeprecationWarning( f"{cls.__name__}.{self.func.__name__} is deprecated." ) warnings.warn(warning, stacklevel=2) return self.func.__get__(instance, cls) PyAV-12.3.0/av/descriptor.pxd000066400000000000000000000010071464705311200157160ustar00rootroot00000000000000cimport 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-12.3.0/av/descriptor.pyi000066400000000000000000000001711464705311200157250ustar00rootroot00000000000000from typing import NoReturn from .option import Option class Descriptor: name: str options: tuple[Option, ...] PyAV-12.3.0/av/descriptor.pyx000066400000000000000000000043251464705311200157510ustar00rootroot00000000000000cimport 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-12.3.0/av/dictionary.pxd000066400000000000000000000002561464705311200157120ustar00rootroot00000000000000cimport libav as lib cdef class _Dictionary: cdef lib.AVDictionary *ptr cpdef _Dictionary copy(self) cdef _Dictionary wrap_dictionary(lib.AVDictionary *input_) PyAV-12.3.0/av/dictionary.pyi000066400000000000000000000006041464705311200157150ustar00rootroot00000000000000from 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-12.3.0/av/dictionary.pyx000066400000000000000000000030261464705311200157350ustar00rootroot00000000000000from 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-12.3.0/av/enum.pxd000066400000000000000000000001021464705311200144770ustar00rootroot00000000000000cpdef define_enum( name, module, items, bint is_flags=* ) PyAV-12.3.0/av/enum.pyi000066400000000000000000000035721464705311200145230ustar00rootroot00000000000000from typing import Any, Callable, Iterable, Literal, Sequence, overload class EnumType(type): def __init__( self, name: str, bases: tuple[type, ...], attrs: dict[str, Any], items: Iterable[tuple[str, Any, str | None, bool]], ) -> None: ... def _create( self, name: str, value: int, doc: str | None = None, by_value_only: bool = False ) -> None: ... def __len__(self) -> None: ... def __iter__(self) -> None: ... def __getitem__(self, key: str | int | EnumType) -> None: ... def _get(self, value: int, create: bool = False) -> None: ... def _get_multi_flags(self, value: int) -> None: ... def get( self, key: str | int | EnumType, default: int | None = None, create: bool = False, ) -> int | None: ... class EnumItem: name: str value: int def __int__(self) -> int: ... def __hash__(self) -> int: ... def __reduce__( self, ) -> tuple[Callable[[str, str, str], EnumItem], tuple[str, str, str]]: ... def __eq__(self, other: object) -> bool: ... def __ne__(self, other: object) -> bool: ... class EnumFlag(EnumItem): flags: tuple[EnumFlag] def __and__(self, other: object) -> EnumFlag: ... def __or__(self, other: object) -> EnumFlag: ... def __xor__(self, other: object) -> EnumFlag: ... def __invert__(self) -> bool: ... def __nonzero__(self) -> bool: ... @overload def define_enum( name: str, module: str, items: Sequence[tuple[str, int] | None], is_flags: Literal[True], ) -> EnumFlag: ... @overload def define_enum( name: str, module: str, items: Sequence[tuple[str, int] | None], is_flags: Literal[False], ) -> EnumItem: ... @overload def define_enum( name: str, module: str, items: Sequence[tuple[str, int] | None], is_flags: bool = False, ) -> EnumItem | EnumFlag: ... PyAV-12.3.0/av/enum.pyx000066400000000000000000000243571464705311200145460ustar00rootroot00000000000000""" PyAV provides enumeration and flag classes that are similar to the stdlib ``enum`` module that shipped with Python 3.4. PyAV's enums are a little more forgiving to preserve backwards compatibility with earlier PyAV patterns. e.g., they can be freely compared to strings or integers for names and values respectively. """ import copyreg 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 if not by_value_only: setattr(self, name, item) self._all.append(item) self._by_name[name] = 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 if issubclass(self, EnumFlag): return self._get_multi_flags(key) 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_multi_flags(self, long value): try: return self._by_value[value] except KeyError: pass flags = [] cdef long to_find = value for item in self: if item.value & to_find: flags.append(item) to_find = to_find ^ item.value if not to_find: break if to_find: raise KeyError(value) name = "|".join(f.name for f in flags) cdef EnumFlag combo = self._create(name, value, by_value_only=True) combo.flags = tuple(flags) return combo def get(self, key, default=None, create=False): try: return self[key] except KeyError: if create: return self._get(key, create=True) return default def property(self, *args, **kwargs): return EnumProperty(self, *args, **kwargs) def _unpickle(mod_name, cls_name, item_name): mod = __import__(mod_name, fromlist=["."]) cls = getattr(mod, cls_name) return cls[item_name] copyreg.constructor(_unpickle) cdef class EnumItem: """ Enumerations are when an attribute may only take on a single value at once, and they are represented as integers in the FFmpeg API. We associate names with each value that are easier to operate with. Consider :data:`av.codec.context.SkipType`, which is the type of the :attr:`CodecContext.skip_frame` attribute:: >>> fh = av.open(video_path) >>> cc = fh.streams.video[0].codec_context >>> # The skip_frame attribute has a name and value: >>> cc.skip_frame.name 'DEFAULT' >>> cc.skip_frame.value 0 >>> # You can compare it to strings and ints: >>> cc.skip_frame == 'DEFAULT' True >>> cc.skip_frame == 0 True >>> # You can assign strings and ints: >>> cc.skip_frame = 'NONKEY' >>> cc.skip_frame == 'NONKEY' True >>> cc.skip_frame == 32 True """ cdef readonly str name cdef readonly int value cdef Py_hash_t _hash 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 # This is not cdef because it doesn't work if it is. # We need to establish a hash that doesn't collide with anything that # would return true from `__eq__`. This is because these enums (vs # the stdlib ones) are weakly typed (they will compare against string # names and int values), and if we have the same hash AND are equal, # then they will be equivalent as keys in a dictionary, which is wierd. cdef Py_hash_t hash_ = value + 1 if hash_ == hash(name): hash_ += 1 self._hash = hash_ 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 def __hash__(self): return self._hash def __reduce__(self): return (_unpickle, (self.__class__.__module__, self.__class__.__name__, self.name)) def __eq__(self, other): if isinstance(other, str): if self.name == other: # The quick method. return True try: other_inst = self.__class__._by_name[other] except KeyError: raise ValueError( f"{self.__class__.__name__} does not have item named {other!r}" ) else: return self is other_inst if isinstance(other, int): if self.value == other: return True if other in self.__class__._by_value: return False raise ValueError( f"{self.__class__.__name__} does not have item valued {other}" ) if isinstance(other, self.__class__): return self is other raise TypeError( f"'==' not supported between {self.__class__.__name__} and {type(other).__name__}" ) def __ne__(self, other): return not (self == other) cdef class EnumFlag(EnumItem): """ Flags are sets of boolean attributes, which the FFmpeg API represents as individual bits in a larger integer which you manipulate with the bitwise operators. We associate names with each flag that are easier to operate with. Consider :data:`CodecContextFlags`, whis is the type of the :attr:`CodecContext.flags` attribute, and the set of boolean properties:: >>> fh = av.open(video_path) >>> cc = fh.streams.video[0].codec_context >>> cc.flags >>> # You can set flags via bitwise operations with the objects, names, or values: >>> cc.flags |= cc.flags.OUTPUT_CORRUPT >>> cc.flags |= 'GLOBAL_HEADER' >>> cc.flags >>> # You can test flags via bitwise operations with objects, names, or values: >>> bool(cc.flags & cc.flags.OUTPUT_CORRUPT) True >>> bool(cc.flags & 'QSCALE') False >>> # There are boolean properties for each flag: >>> cc.output_corrupt True >>> cc.qscale False >>> # You can set them: >>> cc.qscale = True >>> cc.flags """ cdef readonly tuple flags def __cinit__(self, sentinel, name, value, doc=None): self.flags = (self, ) def __and__(self, other): if not isinstance(other, int): other = self.__class__[other].value value = self.value & other return self.__class__._get_multi_flags(value) def __or__(self, other): if not isinstance(other, int): other = self.__class__[other].value value = self.value | other return self.__class__._get_multi_flags(value) def __xor__(self, other): if not isinstance(other, int): other = self.__class__[other].value value = self.value ^ other return self.__class__._get_multi_flags(value) def __invert__(self): # This can't result in a flag, but is helpful. return ~self.value def __nonzero__(self): return bool(self.value) cdef class EnumProperty: cdef object enum cdef object fget cdef object fset cdef public __doc__ def __init__(self, enum, fget, fset=None, doc=None): self.enum = enum self.fget = fget self.fset = fset self.__doc__ = doc or fget.__doc__ def setter(self, fset): self.fset = fset return self def __get__(self, inst, owner): if inst is not None: value = self.fget(inst) return self.enum.get(value, create=True) else: return self def __set__(self, inst, value): item = self.enum.get(value) self.fset(inst, item.value) def flag_property(self, name, doc=None): item = self.enum[name] cdef int item_value = item.value class Property(property): pass @Property def _property(inst): return bool(self.fget(inst) & item_value) if self.fset: @_property.setter def _property(inst, value): if value: flags = self.fget(inst) | item_value else: flags = self.fget(inst) & ~item_value self.fset(inst, flags) _property.__doc__ = doc or item.__doc__ _property._enum_item = item return _property cpdef define_enum(name, module, items, bint is_flags=False): if is_flags: base_cls = EnumFlag else: base_cls = EnumItem # Some items may be None if they correspond to an unsupported FFmpeg feature cls = EnumType(name, (base_cls, ), {"__module__": module}, [i for i in items if i is not None]) return cls PyAV-12.3.0/av/error.pxd000066400000000000000000000002071464705311200146720ustar00rootroot00000000000000 cdef int stash_exception(exc_info=*) cpdef int err_check(int res, filename=*) except -1 cpdef make_error(int res, filename=*, log=*) PyAV-12.3.0/av/error.pyi000066400000000000000000000075061464705311200147110ustar00rootroot00000000000000import builtins from .enum import EnumItem classes: dict[int, Exception] def code_to_tag(code: int) -> bytes: ... def tag_to_code(tag: bytes) -> int: ... def make_error( res: int, filename: str | None = None, log: tuple[int, tuple[int, str, str] | None] | None = None, ) -> None: ... class ErrorType(EnumItem): BSF_NOT_FOUND: int BUG: int BUFFER_TOO_SMALL: int DECODER_NOT_FOUND: int DEMUXER_NOT_FOUND: int ENCODER_NOT_FOUND: int EOF: int EXIT: int EXTERNAL: int FILTER_NOT_FOUND: int INVALIDDATA: int MUXER_NOT_FOUND: int OPTION_NOT_FOUND: int PATCHWELCOME: int PROTOCOL_NOT_FOUND: int UNKNOWN: int EXPERIMENTAL: int INPUT_CHANGED: int OUTPUT_CHANGED: int HTTP_BAD_REQUEST: int HTTP_UNAUTHORIZED: int HTTP_FORBIDDEN: int HTTP_NOT_FOUND: int HTTP_OTHER_4XX: int HTTP_SERVER_ERROR: int PYAV_CALLBACK: int tag: bytes class FFmpegError(Exception): errno: int strerror: str 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-12.3.0/av/error.pyx000066400000000000000000000242051464705311200147230ustar00rootroot00000000000000cimport libav as lib from av.logging cimport get_last_error import errno import os import sys import traceback from threading import local from av.enum import define_enum # 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:: type The :class:`av.error.ErrorType` enum value for the error type. .. attribute:: log The tuple from :func:`av.logging.get_last_log`, or ``None``. """ def __init__(self, code, message, filename=None, log=None): 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. self.type = ErrorType.get(code, create=True) @property def errno(self): return self.args[0] @property def strerror(self): return self.args[1] @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 = f"[Errno {self.errno}] {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), ) # The actual enum. ErrorType = define_enum("ErrorType", __name__, [x[:2] for x in _ffmpeg_specs]) # It has to be monkey-patched. ErrorType.__doc__ = """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. """ ErrorType.tag = property(lambda self: code_to_tag(self.value)) 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, dict(__module__=__name__)) # Register in builder. classes[code] = cls # Register in module. globals()[name] = cls __all__.append(name) # 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 raise make_error(res, filename, log) class UndefinedError(FFmpegError): """Fallback exception type in case FFmpeg returns an error we don't know about.""" pass cpdef make_error(int res, filename=None, log=None): cdef int code = -res cdef bytes py_buffer cdef char *c_buffer if code == c_PYAV_STASHED_ERROR: message = PYAV_STASHED_ERROR_message else: # Jump through some hoops due to Python 2 in same codebase. py_buffer = b"\0" * lib.AV_ERROR_MAX_STRING_SIZE c_buffer = py_buffer lib.av_strerror(res, c_buffer, lib.AV_ERROR_MAX_STRING_SIZE) py_buffer = c_buffer message = py_buffer.decode("latin1") # Default to the OS if we have no message; this should not get called. message = message or os.strerror(code) cls = classes.get(code, UndefinedError) return cls(code, message, filename, log) PyAV-12.3.0/av/filter/000077500000000000000000000000001464705311200143125ustar00rootroot00000000000000PyAV-12.3.0/av/filter/__init__.pxd000066400000000000000000000000001464705311200165540ustar00rootroot00000000000000PyAV-12.3.0/av/filter/__init__.py000066400000000000000000000001471464705311200164250ustar00rootroot00000000000000from .filter import Filter, FilterFlags, filter_descriptor, filters_available from .graph import Graph PyAV-12.3.0/av/filter/__init__.pyi000066400000000000000000000001021464705311200165650ustar00rootroot00000000000000from .context import * from .filter import * from .graph import * PyAV-12.3.0/av/filter/context.pxd000066400000000000000000000006041464705311200165130ustar00rootroot00000000000000cimport 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-12.3.0/av/filter/context.pyi000066400000000000000000000010301464705311200165130ustar00rootroot00000000000000from 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-12.3.0/av/filter/context.pyx000066400000000000000000000103631464705311200165430ustar00rootroot00000000000000import 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-12.3.0/av/filter/filter.pxd000066400000000000000000000003701464705311200163140ustar00rootroot00000000000000cimport 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-12.3.0/av/filter/filter.pyi000066400000000000000000000007041464705311200163230ustar00rootroot00000000000000from 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, ...] filters_available: set[str] PyAV-12.3.0/av/filter/filter.pyx000066400000000000000000000053131464705311200163430ustar00rootroot00000000000000cimport 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-12.3.0/av/filter/graph.pxd000066400000000000000000000010061464705311200161250ustar00rootroot00000000000000cimport 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-12.3.0/av/filter/graph.pyi000066400000000000000000000030571464705311200161430ustar00rootroot00000000000000from 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 | None = None, layout: AudioLayout | None = None, channels: int | None = None, name: str | None = None, time_base: Fraction | None = None, ) -> FilterContext: ... 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-12.3.0/av/filter/graph.pyx000066400000000000000000000166551464705311200161720ustar00rootroot00000000000000import 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 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-12.3.0/av/filter/link.pxd000066400000000000000000000005071464705311200157660ustar00rootroot00000000000000cimport 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-12.3.0/av/filter/link.pyi000066400000000000000000000001561464705311200157740ustar00rootroot00000000000000from .pad import FilterContextPad class FilterLink: input: FilterContextPad output: FilterContextPad PyAV-12.3.0/av/filter/link.pyx000066400000000000000000000030021464705311200160040ustar00rootroot00000000000000cimport 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-12.3.0/av/filter/pad.pxd000066400000000000000000000010061464705311200155700ustar00rootroot00000000000000cimport 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-12.3.0/av/filter/pad.pyi000066400000000000000000000003031464705311200155750ustar00rootroot00000000000000from .link import FilterLink class FilterPad: is_output: bool name: str type: str class FilterContextPad(FilterPad): link: FilterLink | None linked: FilterContextPad | None PyAV-12.3.0/av/filter/pad.pyx000066400000000000000000000053411464705311200156230ustar00rootroot00000000000000from 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-12.3.0/av/format.pxd000066400000000000000000000003521464705311200150320ustar00rootroot00000000000000cimport 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-12.3.0/av/format.pyi000066400000000000000000000021101464705311200150320ustar00rootroot00000000000000__all__ = ("ContainerFormat", "formats_available") from typing import Literal from .enum import EnumFlag class Flags(EnumFlag): NOFILE: int NEEDNUMBER: int SHOW_IDS: int GLOBALHEADER: int NOTIMESTAMPS: int GENERIC_INDEX: int TS_DISCONT: int VARIABLE_FPS: int NODIMENSIONS: int NOSTREAMS: int NOBINSEARCH: int NOGENSEARCH: int NO_BYTE_SEEK: int ALLOW_FLUSH: int TS_NONSTRICT: int TS_NEGATIVE: int SEEK_TO_PTS: int class ContainerFormat: def __init__(self, name: str, mode: Literal["r", "w"] | None = None) -> None: ... name: str long_name: str is_input: bool is_output: bool extensions: set[str] # flags no_file: int need_number: int show_ids: int global_header: int no_timestamps: int generic_index: int ts_discont: int variable_fps: int no_dimensions: int no_streams: int no_bin_search: int no_gen_search: int no_byte_seek: int allow_flush: int ts_nonstrict: int ts_negative: int seek_to_pts: int formats_available: set[str] PyAV-12.3.0/av/format.pyx000066400000000000000000000150621464705311200150630ustar00rootroot00000000000000cimport libav as lib from av.descriptor cimport wrap_avclass from av.enum cimport define_enum 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 Flags = define_enum("Flags", __name__, ( ("NOFILE", lib.AVFMT_NOFILE), ("NEEDNUMBER", lib.AVFMT_NEEDNUMBER, "Needs '%d' in filename."), ("SHOW_IDS", lib.AVFMT_SHOW_IDS, "Show format stream IDs numbers."), ("GLOBALHEADER", lib.AVFMT_GLOBALHEADER, "Format wants global header."), ("NOTIMESTAMPS", lib.AVFMT_NOTIMESTAMPS, "Format does not need / have any timestamps."), ("GENERIC_INDEX", lib.AVFMT_GENERIC_INDEX, "Use generic index building code."), ("TS_DISCONT", lib.AVFMT_TS_DISCONT, """Format allows timestamp discontinuities. Note, muxers always require valid (monotone) timestamps"""), ("VARIABLE_FPS", lib.AVFMT_VARIABLE_FPS, "Format allows variable fps."), ("NODIMENSIONS", lib.AVFMT_NODIMENSIONS, "Format does not need width/height"), ("NOSTREAMS", lib.AVFMT_NOSTREAMS, "Format does not require any streams"), ("NOBINSEARCH", lib.AVFMT_NOBINSEARCH, "Format does not allow to fall back on binary search via read_timestamp"), ("NOGENSEARCH", lib.AVFMT_NOGENSEARCH, "Format does not allow to fall back on generic search"), ("NO_BYTE_SEEK", lib.AVFMT_NO_BYTE_SEEK, "Format does not allow seeking by bytes"), ("ALLOW_FLUSH", lib.AVFMT_ALLOW_FLUSH, """Format allows flushing. If not set, the muxer will not receive a NULL packet in the write_packet function."""), ("TS_NONSTRICT", lib.AVFMT_TS_NONSTRICT, """Format does not require strictly increasing timestamps, but they must still be monotonic."""), ("TS_NEGATIVE", lib.AVFMT_TS_NEGATIVE, """Format allows muxing negative timestamps. 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", lib.AVFMT_SEEK_TO_PTS, "Seeking is based on PTS"), ), is_flags=True) 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-seperated, and so we need to remember which one # this was. self.name = name # Searches comma-seperated 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 @Flags.property def flags(self): return ( (self.iptr.flags if self.iptr else 0) | (self.optr.flags if self.optr else 0) ) no_file = flags.flag_property("NOFILE") need_number = flags.flag_property("NEEDNUMBER") show_ids = flags.flag_property("SHOW_IDS") global_header = flags.flag_property("GLOBALHEADER") no_timestamps = flags.flag_property("NOTIMESTAMPS") generic_index = flags.flag_property("GENERIC_INDEX") ts_discont = flags.flag_property("TS_DISCONT") variable_fps = flags.flag_property("VARIABLE_FPS") no_dimensions = flags.flag_property("NODIMENSIONS") no_streams = flags.flag_property("NOSTREAMS") no_bin_search = flags.flag_property("NOBINSEARCH") no_gen_search = flags.flag_property("NOGENSEARCH") no_byte_seek = flags.flag_property("NO_BYTE_SEEK") allow_flush = flags.flag_property("ALLOW_FLUSH") ts_nonstrict = flags.flag_property("TS_NONSTRICT") ts_negative = flags.flag_property("TS_NEGATIVE") seek_to_pts = flags.flag_property("SEEK_TO_PTS") 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-12.3.0/av/frame.pxd000066400000000000000000000006331464705311200146360ustar00rootroot00000000000000cimport 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-12.3.0/av/frame.pyi000066400000000000000000000003411464705311200146400ustar00rootroot00000000000000from fractions import Fraction class Frame: dts: int | None pts: int | None time: float | None time_base: Fraction is_corrupt: bool side_data: dict[str, str] def make_writable(self) -> None: ... PyAV-12.3.0/av/frame.pyx000066400000000000000000000105021464705311200146570ustar00rootroot00000000000000from av.error cimport err_check 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.channel_layout = source.ptr.channel_layout self.ptr.channels = source.ptr.channels 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 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) PyAV-12.3.0/av/logging.pxd000066400000000000000000000000301464705311200151610ustar00rootroot00000000000000 cpdef get_last_error() PyAV-12.3.0/av/logging.pyi000066400000000000000000000015651464705311200152050ustar00rootroot00000000000000from 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-12.3.0/av/logging.pyx000066400000000000000000000215401464705311200152170ustar00rootroot00000000000000""" 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 ~~~~~~~~~~~~~ """ from __future__ import absolute_import 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 lib.av_log(obj, level, "%s", message) 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-12.3.0/av/option.pxd000066400000000000000000000005561464705311200150600ustar00rootroot00000000000000cimport 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-12.3.0/av/option.pyi000066400000000000000000000017421464705311200150640ustar00rootroot00000000000000from .enum import EnumFlag, EnumItem class OptionType(EnumItem): FLAGS: int INT: int INT64: int DOUBLE: int FLOAT: int STRING: int RATIONAL: int BINARY: int DICT: int CONST: int IMAGE_SIZE: int PIXEL_FMT: int SAMPLE_FMT: int VIDEO_RATE: int DURATION: int COLOR: int CHANNEL_LAYOUT: int BOOL: int class OptionFlags(EnumFlag): ENCODING_PARAM: int DECODING_PARAM: int AUDIO_PARAM: int VIDEO_PARAM: int SUBTITLE_PARAM: int EXPORT: int READONLY: int FILTERING_PARAM: 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-12.3.0/av/option.pyx000066400000000000000000000124301464705311200150770ustar00rootroot00000000000000cimport libav as lib from av.enum cimport define_enum from av.utils cimport flag_in_bitfield 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 OptionType = define_enum("OptionType", __name__, ( ("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), # Added recently, and not yet used AFAICT. ("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_CHANNEL_LAYOUT), ("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_CHANNEL_LAYOUT, lib.AV_OPT_TYPE_BOOL, ) OptionFlags = define_enum("OptionFlags", __name__, ( ("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), ), is_flags=True) 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._get(self.ptr.type, create=True) @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-12.3.0/av/packet.pxd000066400000000000000000000006771464705311200150230ustar00rootroot00000000000000cimport 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-12.3.0/av/packet.pyi000066400000000000000000000010341464705311200150150ustar00rootroot00000000000000from 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 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-12.3.0/av/packet.pyx000066400000000000000000000126201464705311200150370ustar00rootroot00000000000000cimport libav as lib from av.bytesource cimport bytesource from av.error cimport err_check 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) PyAV-12.3.0/av/plane.pxd000066400000000000000000000003041464705311200146360ustar00rootroot00000000000000from 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-12.3.0/av/plane.pyi000066400000000000000000000002511464705311200146450ustar00rootroot00000000000000from .buffer import Buffer from .frame import Frame class Plane(Buffer): frame: Frame index: int def __init__(self, frame: Frame, index: int) -> None: ... PyAV-12.3.0/av/plane.pyx000066400000000000000000000010641464705311200146670ustar00rootroot00000000000000 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-12.3.0/av/py.typed000066400000000000000000000000001464705311200145120ustar00rootroot00000000000000PyAV-12.3.0/av/sidedata/000077500000000000000000000000001464705311200146035ustar00rootroot00000000000000PyAV-12.3.0/av/sidedata/__init__.pxd000066400000000000000000000000001464705311200170450ustar00rootroot00000000000000PyAV-12.3.0/av/sidedata/__init__.py000066400000000000000000000000001464705311200167020ustar00rootroot00000000000000PyAV-12.3.0/av/sidedata/motionvectors.pxd000066400000000000000000000004121464705311200202300ustar00rootroot00000000000000cimport 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-12.3.0/av/sidedata/motionvectors.pyi000066400000000000000000000011251464705311200202400ustar00rootroot00000000000000from 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-12.3.0/av/sidedata/motionvectors.pyx000066400000000000000000000051121464705311200202570ustar00rootroot00000000000000from 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-12.3.0/av/sidedata/sidedata.pxd000066400000000000000000000006311464705311200170760ustar00rootroot00000000000000 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 class _SideDataContainer: cdef Frame frame cdef list _by_index cdef dict _by_type PyAV-12.3.0/av/sidedata/sidedata.pyi000066400000000000000000000020151464705311200171020ustar00rootroot00000000000000from collections.abc import Mapping from typing import Iterator, Sequence, overload from av.buffer import Buffer from av.enum import EnumItem from av.frame import Frame class Type(EnumItem): PANSCAN: int A53_CC: int STEREO3D: int MATRIXENCODING: int DOWNMIX_INFO: int REPLAYGAIN: int DISPLAYMATRIX: int AFD: int MOTION_VECTORS: int SKIP_SAMPLES: int AUDIO_SERVICE_TYPE: int MASTERING_DISPLAY_METADATA: int GOP_TIMECODE: int SPHERICAL: int CONTENT_LIGHT_LEVEL: int ICC_PROFILE: int SEI_UNREGISTERED: int S12M_TIMECODE: int class SideData(Buffer): type: Type DISPLAYMATRIX: int class SideDataContainer(Mapping): frame: Frame def __len__(self) -> int: ... def __iter__(self) -> Iterator[SideData]: ... @overload def __getitem__(self, key: int) -> SideData: ... @overload def __getitem__(self, key: slice) -> Sequence[SideData]: ... @overload def __getitem__(self, key: int | slice) -> SideData | Sequence[SideData]: ... PyAV-12.3.0/av/sidedata/sidedata.pyx000066400000000000000000000057351464705311200171350ustar00rootroot00000000000000from av.enum cimport define_enum from collections.abc import Mapping from av.sidedata.motionvectors import MotionVectors cdef object _cinit_bypass_sentinel = object() Type = define_enum("Type", __name__, ( ("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), ("SEI_UNREGISTERED", lib.AV_FRAME_DATA_SEI_UNREGISTERED), ("S12M_TIMECODE", lib.AV_FRAME_DATA_S12M_TIMECODE), )) cdef SideData wrap_side_data(Frame frame, int index): cdef lib.AVFrameSideDataType type_ = frame.ptr.side_data[index].type if type_ == lib.AV_FRAME_DATA_MOTION_VECTORS: return MotionVectors(_cinit_bypass_sentinel, frame, index) else: return SideData(_cinit_bypass_sentinel, frame, index) 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.get(self.ptr.type) or 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] type_ = Type.get(key) return self._by_type[type_] class SideDataContainer(_SideDataContainer, Mapping): pass PyAV-12.3.0/av/stream.pxd000066400000000000000000000013631464705311200150400ustar00rootroot00000000000000cimport 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 cdef readonly int nb_side_data cdef readonly dict side_data # CodecContext attributes. cdef readonly CodecContext codec_context # Private API. cdef _init(self, Container, lib.AVStream*, CodecContext) cdef _finalize_for_output(self) cdef _get_side_data(self, lib.AVStream *stream) cdef _set_time_base(self, value) cdef _set_id(self, value) cdef Stream wrap_stream(Container, lib.AVStream*, CodecContext) PyAV-12.3.0/av/stream.pyi000066400000000000000000000014701464705311200150450ustar00rootroot00000000000000from fractions import Fraction from typing import Literal from .codec import CodecContext from .container import Container from .enum import EnumItem from .frame import Frame from .packet import Packet class SideData(EnumItem): DISPLAYMATRIX: int class Stream: name: str | None thread_type: Literal["NONE", "FRAME", "SLICE", "AUTO"] container: Container codec_context: CodecContext metadata: dict[str, str] id: int profile: str nb_side_data: int side_data: dict[str, str] 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 frames: int language: str | None type: Literal["video", "audio", "data", "subtitle", "attachment"] PyAV-12.3.0/av/stream.pyx000066400000000000000000000162001464705311200150610ustar00rootroot00000000000000import warnings cimport libav as lib from libc.stdint cimport int32_t from av.enum cimport define_enum 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, ) from av.deprecation import AVDeprecationWarning cdef object _cinit_bypass_sentinel = object() # If necessary more can be added from # https://ffmpeg.org/doxygen/trunk/group__lavc__packet.html#ga9a80bfcacc586b483a973272800edb97 SideData = define_enum("SideData", __name__, ( ("DISPLAYMATRIX", lib.AV_PKT_DATA_DISPLAYMATRIX, "Display Matrix"), )) 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_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.nb_side_data, self.side_data = self._get_side_data(stream) self.metadata = avdict_to_dict( stream.metadata, encoding=self.container.metadata_encoding, errors=self.container.metadata_errors, ) def __repr__(self): return ( f"'}/" f"{self.name or ''} at 0x{id(self):x}>" ) def __setattr__(self, name, value): if name == "id": self._set_id(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)) cdef _get_side_data(self, lib.AVStream *stream): # Get DISPLAYMATRIX SideData from a lib.AVStream object. # Returns: tuple[int, dict[str, Any]] nb_side_data = stream.nb_side_data side_data = {} for i in range(nb_side_data): # Based on: https://www.ffmpeg.org/doxygen/trunk/dump_8c_source.html#l00430 if stream.side_data[i].type == lib.AV_PKT_DATA_DISPLAYMATRIX: side_data["DISPLAYMATRIX"] = lib.av_display_rotation_get(stream.side_data[i].data) return nb_side_data, side_data @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 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: :class:`~fractions.Fraction` or ``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: :class:`int` or ``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: :class:`int` or ``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: :class:`int` """ return self.ptr.nb_frames @property def language(self): """ The language of the stream. :type: :class:`str` or ``None`` """ return self.metadata.get('language') @property def type(self): """ The type of the stream. Examples: ``'audio'``, ``'video'``, ``'subtitle'``. :type: str """ return lib.av_get_media_type_string(self.ptr.codecpar.codec_type) PyAV-12.3.0/av/subtitles/000077500000000000000000000000001464705311200150435ustar00rootroot00000000000000PyAV-12.3.0/av/subtitles/__init__.pxd000066400000000000000000000000001464705311200173050ustar00rootroot00000000000000PyAV-12.3.0/av/subtitles/__init__.py000066400000000000000000000000001464705311200171420ustar00rootroot00000000000000PyAV-12.3.0/av/subtitles/codeccontext.pxd000066400000000000000000000001451464705311200202420ustar00rootroot00000000000000from av.codec.context cimport CodecContext cdef class SubtitleCodecContext(CodecContext): pass PyAV-12.3.0/av/subtitles/codeccontext.pyi000066400000000000000000000002171464705311200202500ustar00rootroot00000000000000from typing import Literal from av.codec.context import CodecContext class SubtitleCodecContext(CodecContext): type: Literal["subtitle"] PyAV-12.3.0/av/subtitles/codeccontext.pyx000066400000000000000000000011711464705311200202670ustar00rootroot00000000000000cimport 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-12.3.0/av/subtitles/stream.pxd000066400000000000000000000002111464705311200170450ustar00rootroot00000000000000from av.packet cimport Packet from av.stream cimport Stream cdef class SubtitleStream(Stream): cpdef decode(self, Packet packet=?) PyAV-12.3.0/av/subtitles/stream.pyi000066400000000000000000000003241464705311200170600ustar00rootroot00000000000000from 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-12.3.0/av/subtitles/stream.pyx000066400000000000000000000012221464705311200170750ustar00rootroot00000000000000from 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-12.3.0/av/subtitles/subtitle.pxd000066400000000000000000000011201464705311200174050ustar00rootroot00000000000000cimport 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-12.3.0/av/subtitles/subtitle.pyi000066400000000000000000000014441464705311200174240ustar00rootroot00000000000000from 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-12.3.0/av/subtitles/subtitle.pyx000066400000000000000000000136661464705311200174540ustar00rootroot00000000000000from 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-12.3.0/av/utils.pxd000066400000000000000000000007711464705311200147070ustar00rootroot00000000000000cimport 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 object to_avrational(object value, lib.AVRational *input) cdef check_ndarray(object array, object dtype, int ndim) cdef check_ndarray_shape(object array, bint ok) cdef flag_in_bitfield(uint64_t bitfield, uint64_t flag) PyAV-12.3.0/av/utils.pyx000066400000000000000000000047551464705311200147420ustar00rootroot00000000000000from 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 object to_avrational(object value, lib.AVRational *input): if value is None: input.num = 0 input.den = 1 return if isinstance(value, Fraction): frac = value else: frac = Fraction(value) 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 check_ndarray_shape(object array, bint ok): """ Check a numpy array has the expected shape. """ if not ok: raise ValueError(f"Unexpected numpy array shape `{array.shape}`") 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 FFmpegError as AVError from .error import err_check PyAV-12.3.0/av/video/000077500000000000000000000000001464705311200141335ustar00rootroot00000000000000PyAV-12.3.0/av/video/__init__.pxd000066400000000000000000000000001464705311200163750ustar00rootroot00000000000000PyAV-12.3.0/av/video/__init__.py000066400000000000000000000000761464705311200162470ustar00rootroot00000000000000from .frame import VideoFrame from .stream import VideoStream PyAV-12.3.0/av/video/__init__.pyi000066400000000000000000000000761464705311200164200ustar00rootroot00000000000000from .frame import VideoFrame from .stream import VideoStream PyAV-12.3.0/av/video/codeccontext.pxd000066400000000000000000000007431464705311200173360ustar00rootroot00000000000000 from av.codec.context cimport CodecContext from av.video.format cimport VideoFormat from av.video.frame cimport VideoFrame from av.video.reformatter cimport VideoReformatter cdef class VideoCodecContext(CodecContext): 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-12.3.0/av/video/codeccontext.pyi000066400000000000000000000016171464705311200173450ustar00rootroot00000000000000from 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 width: int height: int bits_per_codec_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 coded_width: int coded_height: int color_range: int color_primaries: int color_trc: int colorspace: 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-12.3.0/av/video/codeccontext.pyx000066400000000000000000000166131464705311200173660ustar00rootroot00000000000000import warnings cimport libav as lib from libc.stdint cimport int64_t from av.codec.context cimport CodecContext 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 from av.deprecation import AVDeprecationWarning 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): CodecContext._init(self, ptr, codec) # TODO: Can this be `super`? self._build_format() self.encoded_frame_count = 0 cdef _set_default_time_base(self): 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 cdef _prepare_frames_for_encode(self, Frame input): if not input: return [None] cdef VideoFrame vframe = input # 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 _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): return self.ptr.width @width.setter def width(self, unsigned int value): self.ptr.width = value self._build_format() @property def height(self): 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 """ return self._format.name @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: warnings.warn( "Using VideoCodecContext.gop_size for decoders is deprecated.", AVDeprecationWarning ) return self.ptr.gop_size @gop_size.setter def gop_size(self, int value): if self.is_decoder: warnings.warn( "Using VideoCodecContext.gop_size for decoders is deprecated.", AVDeprecationWarning ) 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 PyAV-12.3.0/av/video/format.pxd000066400000000000000000000013271464705311200161430ustar00rootroot00000000000000cimport 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-12.3.0/av/video/format.pyi000066400000000000000000000011461464705311200161500ustar00rootroot00000000000000class 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 is_rgb: bool 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-12.3.0/av/video/format.pyx000066400000000000000000000132201464705311200161630ustar00rootroot00000000000000 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) 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-12.3.0/av/video/frame.pxd000066400000000000000000000011571464705311200157460ustar00rootroot00000000000000cimport 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-12.3.0/av/video/frame.pyi000066400000000000000000000032621464705311200157530ustar00rootroot00000000000000from typing import Any, Union import numpy as np from PIL import Image from av.enum import EnumItem 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]], ] class PictureType(EnumItem): NONE: int I: int P: int B: int S: int SI: int SP: int BI: int class VideoFrame(Frame): format: VideoFormat pts: int time: float planes: tuple[VideoPlane, ...] width: int height: int key_frame: bool interlaced_frame: bool pict_type: int colorspace: int color_range: 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: 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: ... def to_rgb(self, **kwargs: Any) -> VideoFrame: ... def to_image(self, **kwargs: Any) -> Image.Image: ... def to_ndarray(self, **kwargs: Any) -> _SupportedNDarray: ... @staticmethod def from_image(img: Image.Image) -> VideoFrame: ... @staticmethod def from_numpy_buffer( array: _SupportedNDarray, format: str = "rgb24" ) -> VideoFrame: ... @staticmethod def from_ndarray(array: _SupportedNDarray, format: str = "rgb24") -> VideoFrame: ... PyAV-12.3.0/av/video/frame.pyx000066400000000000000000000546011464705311200157750ustar00rootroot00000000000000import sys from libc.stdint cimport uint8_t from av.enum cimport define_enum from av.error cimport err_check from av.utils cimport check_ndarray, check_ndarray_shape from av.video.format cimport get_pix_fmt, get_video_format from av.video.plane cimport VideoPlane import warnings from av.deprecation import AVDeprecationWarning cdef object _cinit_bypass_sentinel 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) PictureType = define_enum("PictureType", __name__, ( ("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_array_to_plane(array, VideoPlane plane, unsigned int bytes_per_pixel): cdef bytes imgbytes = array.tobytes() cdef const uint8_t[:] i_buf = imgbytes 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) while i_pos < i_size: o_buf[o_pos:o_pos + i_stride] = i_buf[i_pos:i_pos + i_stride] i_pos += i_stride o_pos += o_stride 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 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 # Allocate the buffer for the video frame. # # 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 key_frame(self): """Is this frame a key frame? Wraps :ffmpeg:`AVFrame.key_frame`. """ return self.ptr.key_frame @property def interlaced_frame(self): """Is this frame an interlaced or progressive? Wraps :ffmpeg:`AVFrame.interlaced_frame`. """ return self.ptr.interlaced_frame @property def pict_type(self): """One of :class:`.PictureType`. Wraps :ffmpeg:`AVFrame.pict_type`. """ return PictureType.get(self.ptr.pict_type, create=True) @pict_type.setter def pict_type(self, value): self.ptr.pict_type = PictureType[value].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, **kwargs): """Get a numpy array of this frame. Any ``**kwargs`` are passed to :meth:`.VideoReformatter.reformat`. .. note:: Numpy must be installed. .. note:: For formats which return an array of ``uint16`, 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). """ cdef VideoFrame frame = self.reformat(**kwargs) import numpy as np if frame.format.name in ("yuv420p", "yuvj420p"): assert frame.width % 2 == 0 assert frame.height % 2 == 0 return np.hstack(( useful_array(frame.planes[0]), useful_array(frame.planes[1]), useful_array(frame.planes[2]) )).reshape(-1, frame.width) elif frame.format.name in ("yuv444p", "yuvj444p"): return np.hstack(( useful_array(frame.planes[0]), useful_array(frame.planes[1]), useful_array(frame.planes[2]) )).reshape(-1, frame.height, frame.width) elif frame.format.name == "yuyv422": assert frame.width % 2 == 0 assert frame.height % 2 == 0 return useful_array(frame.planes[0], 2).reshape(frame.height, frame.width, -1) elif frame.format.name == "gbrp": array = np.empty((frame.height, frame.width, 3), dtype="uint8") array[:, :, 0] = useful_array(frame.planes[2], 1).reshape(-1, frame.width) array[:, :, 1] = useful_array(frame.planes[0], 1).reshape(-1, frame.width) array[:, :, 2] = useful_array(frame.planes[1], 1).reshape(-1, frame.width) return array elif frame.format.name in ("gbrp10be", "gbrp12be", "gbrp14be", "gbrp16be", "gbrp10le", "gbrp12le", "gbrp14le", "gbrp16le"): array = np.empty((frame.height, frame.width, 3), dtype="uint16") array[:, :, 0] = useful_array(frame.planes[2], 2, "uint16").reshape(-1, frame.width) array[:, :, 1] = useful_array(frame.planes[0], 2, "uint16").reshape(-1, frame.width) array[:, :, 2] = useful_array(frame.planes[1], 2, "uint16").reshape(-1, frame.width) return byteswap_array(array, frame.format.name.endswith("be")) elif frame.format.name in ("gbrpf32be", "gbrpf32le"): array = np.empty((frame.height, frame.width, 3), dtype="float32") array[:, :, 0] = useful_array(frame.planes[2], 4, "float32").reshape(-1, frame.width) array[:, :, 1] = useful_array(frame.planes[0], 4, "float32").reshape(-1, frame.width) array[:, :, 2] = useful_array(frame.planes[1], 4, "float32").reshape(-1, frame.width) return byteswap_array(array, frame.format.name.endswith("be")) elif frame.format.name in ("rgb24", "bgr24"): return useful_array(frame.planes[0], 3).reshape(frame.height, frame.width, -1) elif frame.format.name in ("argb", "rgba", "abgr", "bgra"): return useful_array(frame.planes[0], 4).reshape(frame.height, frame.width, -1) elif frame.format.name in ("gray", "gray8", "rgb8", "bgr8"): return useful_array(frame.planes[0]).reshape(frame.height, frame.width) elif frame.format.name in ("gray16be", "gray16le"): return byteswap_array( useful_array(frame.planes[0], 2, "uint16").reshape(frame.height, frame.width), frame.format.name == "gray16be", ) elif frame.format.name in ("rgb48be", "rgb48le"): return byteswap_array( useful_array(frame.planes[0], 6, "uint16").reshape(frame.height, frame.width, -1), frame.format.name == "rgb48be", ) elif frame.format.name in ("rgba64be", "rgba64le"): return byteswap_array( useful_array(frame.planes[0], 8, "uint16").reshape(frame.height, frame.width, -1), frame.format.name == "rgba64be", ) elif 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 elif frame.format.name == "nv12": return np.hstack(( useful_array(frame.planes[0]), useful_array(frame.planes[1], 2) )).reshape(-1, frame.width) else: 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"): if format in ("rgb24", "bgr24"): check_ndarray(array, "uint8", 3) check_ndarray_shape(array, array.shape[2] == 3) height, width = array.shape[:2] elif format in ("gray", "gray8", "rgb8", "bgr8"): check_ndarray(array, "uint8", 2) height, width = array.shape[:2] 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, width = array.shape[:2] height = height // 6 * 4 else: raise ValueError(f"Conversion from numpy array with format `{format}` is not yet supported") if not array.flags["C_CONTIGUOUS"]: raise ValueError("provided array must be C_CONTIGUOUS") frame = alloc_video_frame() frame._image_fill_pointers_numpy(array, width, height, format) return frame def _image_fill_pointers_numpy(self, buffer, width, height, 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 res = lib.av_image_fill_linesizes( self.ptr.linesize, self.ptr.format, width, ) if res: err_check(res) 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"): """ Construct a frame from a numpy array. .. 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). """ 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 in ("yuv444p", "yuvj444p"): check_ndarray(array, "uint8", 3) check_ndarray_shape(array, array.shape[0] == 3) frame = VideoFrame(array.shape[2], array.shape[1], format) array = array.reshape(3, -1) copy_array_to_plane(array[0], frame.planes[0], 1) copy_array_to_plane(array[1], frame.planes[1], 1) copy_array_to_plane(array[2], frame.planes[2], 1) 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 == "gbrp": check_ndarray(array, "uint8", 3) check_ndarray_shape(array, array.shape[2] == 3) frame = VideoFrame(array.shape[1], array.shape[0], format) copy_array_to_plane(array[:, :, 1], frame.planes[0], 1) copy_array_to_plane(array[:, :, 2], frame.planes[1], 1) copy_array_to_plane(array[:, :, 0], frame.planes[2], 1) return frame elif format in ("gbrp10be", "gbrp12be", "gbrp14be", "gbrp16be", "gbrp10le", "gbrp12le", "gbrp14le", "gbrp16le"): 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[:, :, 1], format.endswith("be")), frame.planes[0], 2) copy_array_to_plane(byteswap_array(array[:, :, 2], format.endswith("be")), frame.planes[1], 2) copy_array_to_plane(byteswap_array(array[:, :, 0], format.endswith("be")), frame.planes[2], 2) return frame elif format in ("gbrpf32be", "gbrpf32le"): check_ndarray(array, "float32", 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[:, :, 1], format.endswith("be")), frame.planes[0], 4) copy_array_to_plane(byteswap_array(array[:, :, 2], format.endswith("be")), frame.planes[1], 4) copy_array_to_plane(byteswap_array(array[:, :, 0], format.endswith("be")), frame.planes[2], 4) return frame 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 ("gray", "gray8", "rgb8", "bgr8"): check_ndarray(array, "uint8", 2) elif format in ("gray16be", "gray16le"): check_ndarray(array, "uint16", 2) frame = VideoFrame(array.shape[1], array.shape[0], format) copy_array_to_plane(byteswap_array(array, format == "gray16be"), frame.planes[0], 2) return frame 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 == "rgb48be"), 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 == "rgba64be"), 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 def __getattribute__(self, attribute): # This method should be deleted when `frame.index` is removed if attribute == "index": warnings.warn("Using `frame.index` is deprecated.", AVDeprecationWarning) return Frame.__getattribute__(self, attribute) PyAV-12.3.0/av/video/plane.pxd000066400000000000000000000003011464705311200157410ustar00rootroot00000000000000from 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-12.3.0/av/video/plane.pyi000066400000000000000000000003371464705311200157600ustar00rootroot00000000000000from 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-12.3.0/av/video/plane.pyx000066400000000000000000000024161464705311200157770ustar00rootroot00000000000000from 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-12.3.0/av/video/reformatter.pxd000066400000000000000000000005651464705311200172100ustar00rootroot00000000000000cimport 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-12.3.0/av/video/reformatter.pyi000066400000000000000000000020311464705311200172040ustar00rootroot00000000000000from av.enum import EnumItem from .frame import VideoFrame class Interpolation(EnumItem): FAST_BILINEAER: int BILINEAR: int BICUBIC: int X: int POINT: int AREA: int BICUBLIN: int GAUSS: int SINC: int LANCZOS: int SPLINE: int class Colorspace(EnumItem): ITU709: int FCC: int ITU601: int ITU624: int SMPTE170M: int SMPTE240M: int DEFAULT: int itu709: int fcc: int itu601: int itu624: int smpte240: int default: int class ColorRange(EnumItem): UNSPECIFIED: int MPEG: int JPEG: int NB: int 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-12.3.0/av/video/reformatter.pyx000066400000000000000000000204571464705311200172370ustar00rootroot00000000000000cimport libav as lib from libc.stdint cimport uint8_t from av.enum cimport define_enum from av.error cimport err_check from av.video.format cimport VideoFormat from av.video.frame cimport alloc_video_frame Interpolation = define_enum("Interpolation", __name__, ( ("FAST_BILINEAR", lib.SWS_FAST_BILINEAR, "Fast bilinear"), ("BILINEAR", lib.SWS_BILINEAR, "Bilinear"), ("BICUBIC", lib.SWS_BICUBIC, "Bicubic"), ("X", lib.SWS_X, "Experimental"), ("POINT", lib.SWS_POINT, "Nearest neighbor / point"), ("AREA", lib.SWS_AREA, "Area averaging"), ("BICUBLIN", lib.SWS_BICUBLIN, "Luma bicubic / chroma bilinear"), ("GAUSS", lib.SWS_GAUSS, "Gaussian"), ("SINC", lib.SWS_SINC, "Sinc"), ("LANCZOS", lib.SWS_LANCZOS, "Lanczos"), ("SPLINE", lib.SWS_SPLINE, "Bicubic spline"), )) Colorspace = define_enum("Colorspace", __name__, ( ("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_SMPTE170M), ("smpte240", lib.SWS_CS_SMPTE240M), ("default", lib.SWS_CS_DEFAULT), )) ColorRange = define_enum("ColorRange", __name__, ( ("UNSPECIFIED", lib.AVCOL_RANGE_UNSPECIFIED, "Unspecified"), ("MPEG", lib.AVCOL_RANGE_MPEG, "MPEG (limited) YUV range, 219*2^(n-8)"), ("JPEG", lib.AVCOL_RANGE_JPEG, "JPEG (full) YUV range, 2^n-1"), ("NB", lib.AVCOL_RANGE_NB, "Not part of ABI"), )) 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 = (Colorspace[src_colorspace].value if src_colorspace is not None else frame.colorspace) cdef int c_dst_colorspace = (Colorspace[dst_colorspace].value if dst_colorspace is not None else frame.colorspace) cdef int c_interpolation = (Interpolation[interpolation] if interpolation is not None else Interpolation.BILINEAR).value cdef int c_src_color_range = (ColorRange[src_color_range].value if src_color_range is not None else 0) cdef int c_dst_color_range = (ColorRange[dst_color_range].value if dst_color_range is not None else 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 # Try and reuse existing SwsContextProxy # VideoStream.decode will copy its SwsContextProxy to VideoFrame # So all Video frames from the same VideoStream should have the same one 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-12.3.0/av/video/stream.pxd000066400000000000000000000003211464705311200161370ustar00rootroot00000000000000from 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-12.3.0/av/video/stream.pyi000066400000000000000000000021421464705311200161500ustar00rootroot00000000000000from fractions import Fraction from typing import Any, Iterator, Literal 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 thread_count: int thread_type: Any sample_aspect_ratio: Fraction | None display_aspect_ratio: Fraction | None codec_context: VideoCodecContext # from codec context format: VideoFormat width: int height: int bits_per_codec_sample: int pix_fmt: str | None framerate: Fraction rate: Fraction gop_size: int has_b_frames: bool coded_width: int coded_height: int color_range: int color_primaries: int color_trc: int colorspace: 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-12.3.0/av/video/stream.pyx000066400000000000000000000072251464705311200161760ustar00rootroot00000000000000cimport 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: :class:`~fractions.Fraction` or ``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: :class:`~fractions.Fraction` or ``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: :class:`~fractions.Fraction` or ``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: :class:`~fractions.Fraction` or ``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: :class:`~fractions.Fraction` or ``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-12.3.0/docs/000077500000000000000000000000001464705311200133475ustar00rootroot00000000000000PyAV-12.3.0/docs/Makefile000066400000000000000000000017321464705311200150120ustar00rootroot00000000000000 SPHINXOPTS = SPHINXBUILD = sphinx-build BUILDDIR = _build FFMPEGDIR = _ffmpeg ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(SPHINXOPTS) . .PHONY: clean html open upload default default: html TAGFILE := _build/doxygen/tagfile.xml $(TAGFILE) : git clone --depth=1 git://source.ffmpeg.org/ffmpeg.git $(FFMPEGDIR) ./generate-tagfile --library $(FFMPEGDIR) -o $(TAGFILE) 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 $@ clean: rm -rf $(BUILDDIR) $(FFMPEGDIR) html: $(RENDERED) $(TAGFILE) $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html test: PYAV_SKIP_DOXYLINK=1 $(SPHINXBUILD) -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-12.3.0/docs/_static/000077500000000000000000000000001464705311200147755ustar00rootroot00000000000000PyAV-12.3.0/docs/_static/custom.css000066400000000000000000000002511464705311200170170ustar00rootroot00000000000000 .ffmpeg-quicklink { float: right; clear: right; margin: 0; } .ffmpeg-quicklink:before { content: "["; } .ffmpeg-quicklink:after { content: "]"; } PyAV-12.3.0/docs/_static/examples/000077500000000000000000000000001464705311200166135ustar00rootroot00000000000000PyAV-12.3.0/docs/_static/examples/numpy/000077500000000000000000000000001464705311200177635ustar00rootroot00000000000000PyAV-12.3.0/docs/_static/examples/numpy/barcode.jpg000066400000000000000000000351631464705311200220740ustar00rootroot00000000000000JFIFC  !"$"$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-12.3.0/docs/_static/logo-250.png000066400000000000000000000304101464705311200167450ustar00rootroot00000000000000PNG  IHDR^ a cHRMz%u0`:o_F0IDATxw|SU&i-S"@P"2DBܢSTd AEAPAd+]J6mB>W|<|#t}| L:JBi{~` Vԅ*#F7&u{b? sbȚHz>T1+~kı qxg$zPDzI0wsDž) ukuF1!n\g0a[_|v{#/F&knWo<:׻ξj1: ?\6+݈29=\7o< 7_3dixrHm~T. Mt$!pԍ S4&17EdjUk5ZZ-Ţ W/++A$HGnvgI7nlqk0dq_Ns_ >΁B @u$Nq"/uyҋz)NbϧubgypY9PxH.p.=66k:->寏DrADkDSgƨקwTU"yaQ17ܛ<̃p+KMJuƼ7ۼ}zf=!:m[,V2е;Le*%U(@p&(j_--/s  ~>H^7RXۻŊbApgX  ';t3Hp z)<#ʃ;{ X,˜};6]KWm=2Eð [}$e0[={}>u]\Yم12HV'G{n1u/ƀ 'B1gDy!zs\)*5p,9pK_L_Sh(n[p![CǦq?~4}>ߺ?PWh9?M\'-{E{}Nav$l)zAErrg(Qm9<Ńk؍TͽyEZKg5䩡]~z?;t6z2 iި3 Rc *u?usWegnn&H~+b{}(- u\Q0r{u': Mw0o>tLDJT_kv_B=v;F=x+Gr0BL>~'d[&|-Qͺ(>+ʆSA]H% j i{YÀH!nA! ꌜIgҐ#[3_sńTT_\lBRFEƷ&tx{뭽Xaz1NCC2sϻRӒF۹D[lZn1M6覯YI>WJ˻He^;j=v>PP29cp#%l ! HaU!)}] ,6p ts[,7[N0 ;OA]k{v\ݐ o* LE*I3O~8Ӄs׀q. Z7ɸpR(V*(ah_m{oe[覄z$(qLEӇ5J> vuz>os`3]Kk73$VuG"[>w[DXx}盼v(VX( w~1MEfFAz,>In5x탚9yݧS<R)jᆖvhoaw/=q#)lUfw7?K&WdGT .pkEt(7??ӛuk%\M1~~!gOئAC$ trsh>i-FsJ呫0ڹF|Av85pmd|)Nu+Я2 }v>j fY튭:=:s@cwwnާY&vC$ړ+C\ό=;s8tD]u&gaR:DhY1+TpxV@k[]VHJq!/і }qV:r Jѭ`R$Ա;LC?k{Ûy>z) +@jV!o|N\$B(= +ᵀ{]/+p7rry> $טB9OXpH#M~pb`r~rL54X/5ϯ( ;:JV:{{L m #;B5empVKA[^NCuz QBv!aAUD/So3C]tb_;_an`Je;jc|6cms:3smPT bP);l> BF{FȘ4\Mjoh*]! dxڃ8|r5·u+O=̂_=yBR*sʁ$4aCY'w<'+- He~MWdT,qiDWݞy;2Ҝ.'շ6|GauNtQ((**eϝ4!kw@ʆ7@w ޱ0`NI=Nld0JQT{=XKPH^٬OÀ-yf`_ƒrp)}UzQxN`^Wb v$71QAbXP)\]TE;Z!l⸜.*д}s/R ,.[/+ѕ RN wmms9{EړP/z l~^?7 Be)C40֟'!8˭BwFu r ;/܈$8*S@;6Uu5 a+~وT|0v[M?)v?m7;q};_*߭ !:&I4m}d1]+;)c]wx PYBwmP㢤w" r w$1G^BM6V:TEX- ۃBm#MxEVAw Z z]Vr_oGf#yBKf&&\'^>AII}Yj)Ҥc5RW\xg8v‰hŝNLV< ]|h ĸ7elOt()b0=[yHs >qV\/=~'O6.2](dso JdJ"$ל$DhB}K>'SJ \$8QA WHi $Ք|G Vu9[oJ PTD/#c%4gGm&g{/|+V1w>}ڢC91A @Aq)*3sFwj>'KF"ܱۊI 4!X ײ 2r}6\t-ϙh{xwk;#y)\C@g0P2[#u6.߲J륕T !;IX㕯4#~oB#&4zٶɠ6[C')Ż{$`c 7C3a}.0538|V 6d%6N#\abBJuw2]cLU])8ZL}~ [o v*@{vbI֛Y݆Qa b̂ɛOC$LAxEi]+ۈv /p85Hϻn#}dZvs}޻eXҐb]ߎg9tnI9>@~]+Bkyq8 oCN]xۯ(@w]lkޒRWB9{1ࠔk0s Egg/i2M[K]Sɯ@cP7V@#*$!2Ir2ugx HjEN=IF2^q+/3øox+,EgSC!iP$_q]' 4xjSLv Q #̏"!"N9&h7 o 0> b&A`|c%•W#}m&g5[d$ØU]CzWGcvvg6VM$yk.QM\S'OI=bƭ mp|9h6rEgO6oios(_:8cbǗ99JbDirc4~w4~&~ m.R1C@dp%r4̊ɅQŹQ6_u%WWNly20D lˉYmy2 d%:=&v[bv:i%֌Vr}2ş &/ޭ6ZYi-fEhsuJ䄙sldxsOn), _N耕d,O'Q'T.W]'9[O뙕tX,WhǟW$^uvWe9 r$x*i#d"왫9HU L=G-]/tXqox)+XEbReɘ:z#EGZ'Ts%Aw=AhI~u6)oL+UMu{1O!LJj'\7I RWuumgxqWN܀{Uu ErreY=pLG1"rrQ:iݤUك?SN-_k{ JɅKVѸ;_|/4@M)(!К7Oؙw/Jn+}[Jq?Js9d~ 6(OrXh埃W#Ͷ8]r&\튰0ɩߨI ˉ}qA2ȉ:\T@CяoupO>9J.u$'yI͊Ll7룕 ޔ "Jy744_UN; rsS4"F˨l%ϦbAtj B Xjeͨq{Vuh oyN2ޓGX&@@9A.{2c4B?t/>4x~ߓBNo*ƞ8JNC| ykc/6‚ I*D"JEԬ!e_d댺>nЬО1-.~8硊ƛ6 eJحj OtMeӵjRj#ȡJ|LE Geod͝D L~/U`08*NtӜܕG,AZD)ۿ/[Zٻ}~;0-VOĻQek!A1kb_^KoOFm_-V JמƦ* gN4=s!j-.|8eIHn1;Q1]b_|~|>J:8 ȱT@յNU2mݐ*ߺ!w v@-.mv7AffPHR_ºwyu?w1KPfۈ9kͿzAvpRFˤa r$IwHIWfRX׬'uMI^b)VZC';^עu o]rXuMTD^0Ycv\QTNPg5!:zhXn+7l~V"'%c;1UZ-l{t?p<5[06z > @wKO>__2f/,Va)7j8vɉ& :]N#¿/5hK5D)|c$SҼGΙSkb/ *ХZ[@U!?;{Ҕ@*E)"/t_nX]Bsw%Jo-S-\+ԱUϽ9 RKlyC,t`PՅ~3RZjEˆyjג*_L!l7U. fV0YDܺ5vH }~ x#JCxt1^NQ kE%˞7hsqznG^i"ޢǯ vXr sFA wׇy}I*-i8sv( BJ;*]NЏ&mn8|mg3h oaKu gQ.~eFI `]((Ң3`>?N _on oSǠOmO*PT'/[vkkf80RV<׮{~Zq,T>R3BJZAa@R-Д_"yxEe --6Zbڥ" M4ťhJaޤq7 Q;I7sa0I.HmОUͺ7rx~@^$L:nCDRݎ}g^h)G%W isYȮEҿUaj*)oW((#XmZZVۉ (6ۯcz$/r'9,; ݃fWT!Qv.# aD~HCH9ܷ8wW1Vg(Ã2ڲF6f"PrPy?‘\fpѝIs> /KB@` Vnog[m &J$O7ѯM}l-|m5Y#.H!286,VPK+龴hg+';\vEi%m:+X.zz`v͉o;%8\~=vu\8Qǹ_ťL'WlY7KI~Zڀ+b/H?RF#9"3u(W.:z߇^g!FJjems$j+laR/!cPϭ᎞wu39v>&Q)yb&[#. T9ZeXg&K=}ϻfni+Hu؟pMZ؞ Iuȫv_j&\x&bmEMWl+.Rm˿;S\! MP"\QeR|4;̢MO$^X )vdvȋm2j~Aj甾H9**~ |#ӂHm$DPLɎO ?:y&K zޕV QEuO@kGrAh~yޏ\"}/"CUFnsKk&*Y`T6CEZ(+&)(i ]$}Đ+l^ =)U=:*3dI U]|"󺇫8NZ"%WRDr-u%iρ@HXiޕn CG5n5D \gK{:ܴLoO}hRńuy>Dmք+Cr٠bE|[ѭ}EL cTfw;\+(prޕlV-uxxuS+ˌ[2~>eւFӴrU_U t+%?Ti*(1i{3 q{n) z1~<&l v% i.I-%N ۤm:(̟j#U|)PIv؉U48T^x$RE]lU`;4l*Gm H%vyoUYL*Ԣ.$ڞ-V }Zd2nFV=WV#ߑkyp(G.u"p={pm!UG2>oqVpWͻJ{|IENDB`PyAV-12.3.0/docs/_themes/000077500000000000000000000000001464705311200147735ustar00rootroot00000000000000PyAV-12.3.0/docs/_themes/pyav/000077500000000000000000000000001464705311200157525ustar00rootroot00000000000000PyAV-12.3.0/docs/_themes/pyav/layout.html000066400000000000000000000004541464705311200201600ustar00rootroot00000000000000 {%- extends "basic/layout.html" %} {% block extrahead %} {% endblock %} {% block relbaritems %}
  • {% endblock %} PyAV-12.3.0/docs/_themes/pyav/theme.conf000066400000000000000000000000311464705311200177150ustar00rootroot00000000000000[theme] inherit = nature PyAV-12.3.0/docs/api/000077500000000000000000000000001464705311200141205ustar00rootroot00000000000000PyAV-12.3.0/docs/api/_globals.rst000066400000000000000000000000541464705311200164330ustar00rootroot00000000000000 Globals ======= .. autofunction:: av.open PyAV-12.3.0/docs/api/audio.rst000066400000000000000000000022111464705311200157470ustar00rootroot00000000000000 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-12.3.0/docs/api/bitstream.rst000066400000000000000000000002011464705311200166350ustar00rootroot00000000000000 Bitstream Filters ================= .. automodule:: av.bitstream .. autoclass:: BitStreamFilterContext :members: PyAV-12.3.0/docs/api/buffer.rst000066400000000000000000000001311464705311200161160ustar00rootroot00000000000000 Buffers ======= .. automodule:: av.buffer .. autoclass:: Buffer :members: PyAV-12.3.0/docs/api/codec.rst000066400000000000000000000057131464705311200157350ustar00rootroot00000000000000 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_*``). .. enumtable:: av.codec.codec.Properties :class: av.codec.codec.Codec .. 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``. .. enumtable:: av.codec.codec.Capabilities :class: av.codec.codec.Codec Contexts -------- .. currentmodule:: av.codec.context .. automodule:: av.codec.context .. autoclass:: CodecContext .. autoattribute:: CodecContext.codec .. autoattribute:: CodecContext.options .. automethod:: CodecContext.create .. automethod:: CodecContext.open .. automethod:: CodecContext.close 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.ticks_per_frame .. 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 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 Enums ~~~~~ .. 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 .. autoclass:: av.codec.context.SkipType .. enumtable:: av.codec.context.SkipType PyAV-12.3.0/docs/api/container.rst000066400000000000000000000025351464705311200166410ustar00rootroot00000000000000 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-12.3.0/docs/api/enum.rst000066400000000000000000000003311464705311200156130ustar00rootroot00000000000000 Enumerations and Flags ====================== .. currentmodule:: av.enum .. automodule:: av.enum .. _enums: Enumerations ------------ .. autoclass:: EnumItem .. _flags: Flags ----- .. autoclass:: EnumFlag PyAV-12.3.0/docs/api/error.rst000066400000000000000000000045571464705311200160160ustar00rootroot00000000000000Errors ====== .. currentmodule:: av.error .. _error_behaviour: General Behaviour ----------------- 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` and at a lower level via :ref:`error_types`. 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 `_ (and fall back onto ``OSError`` if using Python < 3.3). .. _error_types: Error Type Enumerations ----------------------- We provide :class:`av.error.ErrorType` as an enumeration of the various FFmpeg errors. To mimick the stdlib ``errno`` module, all enumeration values are available in the ``av.error`` module, e.g.:: try: do_something() except OSError as e: if e.errno != av.error.FILTER_NOT_FOUND: raise handle_error() .. autoclass:: av.error.ErrorType .. _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 effectivly shadows as much of the builtin exception heirarchy 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 Mapping Codes and Classes ------------------------- Here is how the classes line up with the error codes/enumerations: .. include:: ../_build/rst/api/error_table.rst PyAV-12.3.0/docs/api/error_table.py000066400000000000000000000011471464705311200167750ustar00rootroot00000000000000import av rows = [("Exception Class", "Code/Enum Name", "FFmpeg Error Message")] for code, cls in av.error.classes.items(): enum = av.error.ErrorType.get(code) if not enum: continue if enum.tag == b"PyAV": continue rows.append((f"``av.{cls.__name__}``", f"``av.error.{enum.name}``", enum.strerror)) lens = [max(len(row[i]) for row in rows) for i in range(len(rows[0]))] header = tuple("=" * x for x in lens) rows.insert(0, header) rows.insert(2, header) rows.append(header) for row in rows: print(" ".join("{:{}s}".format(cell, len_) for cell, len_ in zip(row, lens))) PyAV-12.3.0/docs/api/filter.rst000066400000000000000000000007361464705311200161450ustar00rootroot00000000000000Filters ======= .. 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-12.3.0/docs/api/frame.rst000066400000000000000000000001251464705311200157420ustar00rootroot00000000000000 Frames ====== .. automodule:: av.frame .. autoclass:: Frame :members: PyAV-12.3.0/docs/api/packet.rst000066400000000000000000000001311464705311200161140ustar00rootroot00000000000000 Packets ======= .. automodule:: av.packet .. autoclass:: Packet :members: PyAV-12.3.0/docs/api/plane.rst000066400000000000000000000001251464705311200157470ustar00rootroot00000000000000 Planes ====== .. automodule:: av.plane .. autoclass:: Plane :members: PyAV-12.3.0/docs/api/sidedata.rst000066400000000000000000000004741464705311200164350ustar00rootroot00000000000000 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-12.3.0/docs/api/stream.rst000066400000000000000000000023601464705311200161460ustar00rootroot00000000000000 Streams ======= Stream collections ------------------ .. currentmodule:: av.container.streams .. autoclass:: StreamContainer Dynamic Slicing ~~~~~~~~~~~~~~~ .. automethod:: StreamContainer.get 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.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-12.3.0/docs/api/subtitles.rst000066400000000000000000000006321464705311200166710ustar00rootroot00000000000000 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-12.3.0/docs/api/time.rst000066400000000000000000000073601464705311200156160ustar00rootroot00000000000000 .. _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-12.3.0/docs/api/utils.rst000066400000000000000000000002421464705311200160100ustar00rootroot00000000000000 Utilities ========= Logging ------- .. automodule:: av.logging :members: Other ----- .. automodule:: av.utils :members: .. autoclass:: AVError PyAV-12.3.0/docs/api/video.rst000066400000000000000000000041641464705311200157650ustar00rootroot00000000000000Video ===== 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 PyAV-12.3.0/docs/conf.py000066400000000000000000000231161464705311200146510ustar00rootroot00000000000000import logging import os import re import sys import xml.etree.ElementTree as etree from docutils import nodes from sphinx import addnodes from sphinx.util.docutils import SphinxDirective import sphinx logging.basicConfig() # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath("..")) # -- General configuration ----------------------------------------------------- # 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", ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix of source filenames. source_suffix = ".rst" # The master toctree document. master_doc = "index" # General information about the project. project = "PyAV" copyright = "2024, 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) # The full version, including alpha/beta/rc tags. release = about["__version__"] # The short X.Y version. version = release.split("-")[0] exclude_patterns = ["_build"] # The name of the Pygments (syntax highlighting) style to use. 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-250.png" # 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"] 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 = { "ffstruct": ("http://ffmpeg.org/doxygen/trunk/struct%s.html", "struct "), "issue": ("https://github.com/PyAV-Org/PyAV/issues/%s", "#"), "pr": ("https://github.com/PyAV-Org/PyAV/pull/%s", "#"), "gh-user": ("https://github.com/%s", "@"), } intersphinx_mapping = { "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() for name, item in enum._by_name.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 = item.__doc__ or "-" tbody += makerow(attr, name, value, doc) return [table] doxylink = {} ffmpeg_tagfile = os.path.abspath( os.path.join(__file__, "..", "_build", "doxygen", "tagfile.xml") ) if not os.path.exists(ffmpeg_tagfile): print("ERROR: Missing FFmpeg tagfile.") exit(1) doxylink["ffmpeg"] = (ffmpeg_tagfile, "https://ffmpeg.org/doxygen/trunk/") def doxylink_create_handler(app, file_name, url_base): print("Finding all names in Doxygen tagfile", file_name) doc = etree.parse(file_name) root = doc.getroot() parent_map = {} # ElementTree doesn't five us access to parents. urls = {} for node in root.findall(".//name/.."): for child in node: parent_map[child] = node kind = node.attrib["kind"] if kind not in ("function", "struct", "variable"): continue name = node.find("name").text if kind not in ("function",): parent = parent_map.get(node) parent_name = parent.find("name") if parent else None if parent_name is not None: name = f"{parent_name.text}.{name}" filenode = node.find("filename") if filenode is not None: url = filenode.text else: url = "{}#{}".format( node.find("anchorfile").text, node.find("anchor").text, ) urls.setdefault(kind, {})[name] = url def get_url(name): # These are all the kinds that seem to exist. for kind in ( "function", "struct", "variable", # These are struct members. # 'class', # 'define', # 'enumeration', # 'enumvalue', # 'file', # 'group', # 'page', # 'typedef', # 'union', ): try: return urls[kind][name] except KeyError: pass def _doxylink_handler(name, rawtext, text, lineno, inliner, options={}, content=[]): m = re.match(r"^(.+?)(?:<(.+?)>)?$", text) title, name = m.groups() name = name or title url = get_url(name) if not url: if name == "AVFrame.color_primaries": url = "structAVFrame.html#a59a3f830494f2ed1133103a1bc9481e7" elif name == "AVFrame.color_trc": url = "structAVFrame.html#ab09abb126e3922bc1d010cf044087939" else: print("ERROR: Could not find", name) exit(1) node = addnodes.literal_strong(title, title) if url: url = url_base + url node = nodes.reference("", "", node, refuri=url) return [node], [] return _doxylink_handler def setup(app): app.add_css_file("custom.css") app.add_directive("flagtable", EnumTable) app.add_directive("enumtable", EnumTable) app.add_directive("pyinclude", PyInclude) skip = os.environ.get("PYAV_SKIP_DOXYLINK") for role, (filename, url_base) in doxylink.items(): if skip: app.add_role(role, lambda *args: ([], [])) else: app.add_role(role, doxylink_create_handler(app, filename, url_base)) PyAV-12.3.0/docs/cookbook/000077500000000000000000000000001464705311200151555ustar00rootroot00000000000000PyAV-12.3.0/docs/cookbook/basics.rst000066400000000000000000000030741464705311200171570ustar00rootroot00000000000000Basics ====== 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 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/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. PyAV-12.3.0/docs/cookbook/numpy.rst000066400000000000000000000010651464705311200170610ustar00rootroot00000000000000Numpy ===== 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 http://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-12.3.0/docs/development/000077500000000000000000000000001464705311200156715ustar00rootroot00000000000000PyAV-12.3.0/docs/development/changelog.rst000066400000000000000000000001771464705311200203570ustar00rootroot00000000000000 .. It is all in the other file (that we want at the top-level of the repo). .. _changelog: .. include:: ../../CHANGELOG.rst PyAV-12.3.0/docs/development/contributors.rst000066400000000000000000000000411464705311200211530ustar00rootroot00000000000000 .. include:: ../../AUTHORS.rst PyAV-12.3.0/docs/development/includes.py000066400000000000000000000257151464705311200200630ustar00rootroot00000000000000import json import os import re import sys import xml.etree.ElementTree as etree from Cython.Compiler.Main import CompilationOptions, Context from Cython.Compiler.TreeFragment import parse_from_strings from Cython.Compiler.Visitor import TreeVisitor from Cython.Compiler import Nodes os.chdir(os.path.abspath(os.path.join(__file__, '..', '..', '..'))) class Visitor(TreeVisitor): def __init__(self, state=None): super(Visitor, self).__init__() self.state = dict(state or {}) self.events = [] def record_event(self, node, **kw): state = self.state.copy() state.update(**kw) state['node'] = node state['pos'] = node.pos state['end_pos'] = node.end_pos() self.events.append(state) def visit_Node(self, node): self.visitchildren(node) def visit_ModuleNode(self, node): self.state['module'] = node.full_module_name self.visitchildren(node) self.state.pop('module') def visit_CDefExternNode(self, node): self.state['extern_from'] = node.include_file self.visitchildren(node) self.state.pop('extern_from') def visit_CStructOrUnionDefNode(self, node): self.record_event(node, type='struct', name=node.name) self.state['struct'] = node.name self.visitchildren(node) self.state.pop('struct') def visit_CFuncDeclaratorNode(self, node): if isinstance(node.base, Nodes.CNameDeclaratorNode): self.record_event(node, type='function', name=node.base.name) else: self.visitchildren(node) def visit_CVarDefNode(self, node): if isinstance(node.declarators[0], Nodes.CNameDeclaratorNode): # Grab the type name. # TODO: Do a better job. type_ = node.base_type if hasattr(type_, 'name'): type_name = type_.name elif hasattr(type_, 'base_type'): type_name = type_.base_type.name else: type_name = str(type_) self.record_event(node, type='variable', name=node.declarators[0].name, vartype=type_name) else: self.visitchildren(node) def visit_CClassDefNode(self, node): self.state['class'] = node.class_name self.visitchildren(node) self.state.pop('class') def visit_PropertyNode(self, node): self.state['property'] = node.name self.visitchildren(node) self.state.pop('property') def visit_DefNode(self, node): self.state['function'] = node.name self.visitchildren(node) self.state.pop('function') def visit_AttributeNode(self, node): if getattr(node.obj, 'name', None) == 'lib': self.record_event(node, type='use', name=node.attribute) else: self.visitchildren(node) def extract(path, **kwargs): name = os.path.splitext(os.path.relpath(path))[0].replace('/', '.') options = CompilationOptions() options.include_path.append('include') options.language_level = 2 options.compiler_directives = dict( c_string_type='str', c_string_encoding='ascii', ) context = Context( options.include_path, options.compiler_directives, options.cplus, options.language_level, options=options, ) tree = parse_from_strings( name, open(path).read(), context, level='module_pxd' if path.endswith('.pxd') else None, **kwargs) extractor = Visitor({'file': path}) extractor.visit(tree) return extractor.events def iter_cython(path): '''Yield all ``.pyx`` and ``.pxd`` files in the given root.''' for dir_path, dir_names, file_names in os.walk(path): for file_name in file_names: if file_name.startswith('.'): continue if os.path.splitext(file_name)[1] not in ('.pyx', '.pxd'): continue yield os.path.join(dir_path, file_name) doxygen = {} doxygen_base = 'https://ffmpeg.org/doxygen/trunk' tagfile_path = 'docs/_build/doxygen/tagfile.xml' tagfile_json = tagfile_path + '.json' if os.path.exists(tagfile_json): print('Loading pre-parsed Doxygen tagfile:', tagfile_json, file=sys.stderr) doxygen = json.load(open(tagfile_json)) if not doxygen: print('Parsing Doxygen tagfile:', tagfile_path, file=sys.stderr) if not os.path.exists(tagfile_path): print(' MISSING!', file=sys.stderr) else: root = etree.parse(tagfile_path) def inspect_member(node, name_prefix=''): name = name_prefix + node.find('name').text anchorfile = node.find('anchorfile').text anchor = node.find('anchor').text url = '%s/%s#%s' % (doxygen_base, anchorfile, anchor) doxygen[name] = {'url': url} if node.attrib['kind'] == 'function': ret_type = node.find('type').text arglist = node.find('arglist').text sig = '%s %s%s' % (ret_type, name, arglist) doxygen[name]['sig'] = sig for struct in root.iter('compound'): if struct.attrib['kind'] != 'struct': continue name_prefix = struct.find('name').text + '.' for node in struct.iter('member'): inspect_member(node, name_prefix) for node in root.iter('member'): inspect_member(node) json.dump(doxygen, open(tagfile_json, 'w'), sort_keys=True, indent=4) print('Parsing Cython source for references...', file=sys.stderr) lib_references = {} for path in iter_cython('av'): try: events = extract(path) except Exception as e: print(" %s in %s" % (e.__class__.__name__, path), file=sys.stderr) print(" %s" % e, file=sys.stderr) continue for event in events: if event['type'] == 'use': lib_references.setdefault(event['name'], []).append(event) defs_by_extern = {} for path in iter_cython('include'): # This one has "include" directives, which is not supported when # parsing from a string. if path == 'include/libav.pxd': continue # Extract all #: comments from the source files. comments_by_line = {} for i, line in enumerate(open(path)): m = re.match(r'^\s*#: ?', line) if m: comment = line[m.end():].rstrip() comments_by_line[i + 1] = line[m.end():] # Extract Cython definitions from the source files. for event in extract(path): extern = event.get('extern_from') or path.replace('include/', '') defs_by_extern.setdefault(extern, []).append(event) # Collect comments above and below comments = event['_comments'] = [] line = event['pos'][1] - 1 while line in comments_by_line: comments.insert(0, comments_by_line.pop(line)) line -= 1 line = event['end_pos'][1] + 1 while line in comments_by_line: comments.append(comments_by_line.pop(line)) line += 1 # Figure out the Sphinx headline. if event['type'] == 'function': event['_sort_key'] = 2 sig = doxygen.get(event['name'], {}).get('sig') if sig: sig = re.sub(r'\).+', ')', sig) # strip trailer event['_headline'] = '.. c:function:: %s' % sig else: event['_headline'] = '.. c:function:: %s()' % event['name'] elif event['type'] == 'variable': struct = event.get('struct') if struct: event['_headline'] = '.. c:member:: %s %s' % (event['vartype'], event['name']) event['_sort_key'] = 1.1 else: event['_headline'] = '.. c:var:: %s' % event['name'] event['_sort_key'] = 3 elif event['type'] == 'struct': event['_headline'] = '.. c:type:: struct %s' % event['name'] event['_sort_key'] = 1 event['_doxygen_url'] = '%s/struct%s.html' % (doxygen_base, event['name']) else: print('Unknown event type %s' % event['type'], file=sys.stderr) name = event['name'] if event.get('struct'): name = '%s.%s' % (event['struct'], name) # Doxygen URLs event.setdefault('_doxygen_url', doxygen.get(name, {}).get('url')) # Find use references. ref_events = lib_references.get(name, []) if ref_events: ref_pairs = [] for ref in sorted(ref_events, key=lambda e: e['name']): chunks = [ ref.get('module'), ref.get('class'), ] chunks = filter(None, chunks) prefix = '.'.join(chunks) + '.' if chunks else '' if ref.get('property'): ref_pairs.append((ref['property'], ':attr:`%s%s`' % (prefix, ref['property']))) elif ref.get('function'): name = ref['function'] if name in ('__init__', '__cinit__', '__dealloc__'): ref_pairs.append((name, ':class:`%s%s <%s>`' % (prefix, name, prefix.rstrip('.')))) else: ref_pairs.append((name, ':func:`%s%s`' % (prefix, name))) else: continue unique_refs = event['_references'] = [] seen = set() for name, ref in sorted(ref_pairs): if name in seen: continue seen.add(name) unique_refs.append(ref) print(''' .. This file is generated by includes.py; any modifications will be destroyed! Wrapped C Types and Functions ============================= ''') for extern, events in sorted(defs_by_extern.items()): did_header = False for event in events: headline = event.get('_headline') comments = event.get('_comments') refs = event.get('_references', []) url = event.get('_doxygen_url') indent = ' ' if event.get('struct') else '' if not headline: continue if ( not filter(None, (x.strip() for x in comments if x.strip())) and not refs and event['type'] not in ('struct', ) ): pass if not did_header: print('``%s``' % extern) print('-' * (len(extern) + 4)) print() did_header = True if url: print() print(indent + '.. rst-class:: ffmpeg-quicklink') print() print(indent + ' `FFmpeg Docs <%s>`__' % url) print(indent + headline) print() if comments: for line in comments: print(indent + ' ' + line) print() if refs: print(indent + ' Referenced by: ', end='') for i, ref in enumerate(refs): print((', ' if i else '') + ref, end='') print('.') print() PyAV-12.3.0/docs/development/includes.rst000066400000000000000000000000651464705311200202320ustar00rootroot00000000000000 .. include:: ../_build/rst/development/includes.rst PyAV-12.3.0/docs/development/license.rst000066400000000000000000000003701464705311200200450ustar00rootroot00000000000000 .. 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-12.3.0/docs/generate-tagfile000077500000000000000000000013361464705311200165030ustar00rootroot00000000000000#!/usr/bin/env python import argparse import os import subprocess parser = argparse.ArgumentParser() parser.add_argument("-l", "--library", required=True) parser.add_argument("-o", "--output", required=True) args = parser.parse_args() output = os.path.abspath(args.output) outdir = os.path.dirname(output) if not os.path.exists(outdir): os.makedirs(outdir) proc = subprocess.Popen(["doxygen", "-"], stdin=subprocess.PIPE, cwd=args.library) proc.communicate( """ #@INCLUDE = doc/Doxyfile GENERATE_TAGFILE = {} GENERATE_HTML = no GENERATE_LATEX = no CASE_SENSE_NAMES = yes INPUT = libavcodec libavdevice libavfilter libavformat libavresample libavutil libswresample libswscale """.format( output ).encode() ) PyAV-12.3.0/docs/index.rst000066400000000000000000000042471464705311200152170ustar00rootroot00000000000000**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-12.3.0/docs/overview/000077500000000000000000000000001464705311200152155ustar00rootroot00000000000000PyAV-12.3.0/docs/overview/caveats.rst000066400000000000000000000054151464705311200174020ustar00rootroot00000000000000Caveats ======= .. _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, perfoming 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 actually about the underlying FFmpeg libraries comes with the caveat that we can not always be 100% on it. It is, unfortunately, often on the user the understand and deal with the edge cases. We encourage you to bring them to our attention via GitHub_ so that we can try to make PyAV deal with it, but we can't always make it work. 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 Gitter_ or open a feature request on GitHub_ (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-Interpeters --------------- 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 are able to 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 fh: # Do stuff with it. .. _FFmpeg: https://ffmpeg.org/ .. _Gitter: https://app.gitter.im/#/room/#PyAV-Org_User-Help:gitter.im .. _GitHub: https://github.com/PyAV-Org/pyav PyAV-12.3.0/docs/overview/installation.rst000066400000000000000000000056201464705311200204530ustar00rootroot00000000000000Installation ============ 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 Currently FFmpeg 6.1.1 is used with the following features enabled for all platforms: - fontconfig - gmp - libaom - libass - libbluray - libdav1d - libfreetype - libmp3lame - libopencore-amrnb - libopencore-amrwb - libopenjpeg - libopus - libspeex - libtwolame - libvorbis - libvpx - libx264 - libx265 - libxml2 - libxvid - lzma - zlib The following additional features are also enabled on Linux: - gnutls - libxcb 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 ``6.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 # Install basic requirements. pip install -r tests/requirements.txt # 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-12.3.0/examples/000077500000000000000000000000001464705311200142355ustar00rootroot00000000000000PyAV-12.3.0/examples/basics/000077500000000000000000000000001464705311200155015ustar00rootroot00000000000000PyAV-12.3.0/examples/basics/parse.py000066400000000000000000000021061464705311200171640ustar00rootroot00000000000000import 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-12.3.0/examples/basics/remux.py000066400000000000000000000012321464705311200172110ustar00rootroot00000000000000import av import av.datasets 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(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-12.3.0/examples/basics/save_keyframes.py000066400000000000000000000010631464705311200210570ustar00rootroot00000000000000import 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 frame in container.decode(stream): print(frame) # We use `frame.pts` as `frame.index` won't make must sense with the `skip_frame`. frame.to_image().save( "night-sky.{:04d}.jpg".format(frame.pts), quality=80, ) PyAV-12.3.0/examples/basics/thread_type.py000066400000000000000000000016551464705311200203720ustar00rootroot00000000000000import 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-12.3.0/examples/numpy/000077500000000000000000000000001464705311200154055ustar00rootroot00000000000000PyAV-12.3.0/examples/numpy/barcode.py000066400000000000000000000015471464705311200173650ustar00rootroot00000000000000import 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-12.3.0/examples/numpy/generate_video.py000066400000000000000000000015251464705311200207420ustar00rootroot00000000000000import 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-12.3.0/examples/numpy/generate_video_with_pts.py000066400000000000000000000056151464705311200226670ustar00rootroot00000000000000#!/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-12.3.0/include/000077500000000000000000000000001464705311200140425ustar00rootroot00000000000000PyAV-12.3.0/include/libav.pxd000066400000000000000000000014001464705311200156470ustar00rootroot00000000000000include "libavutil/avutil.pxd" include "libavutil/channel_layout.pxd" include "libavutil/dict.pxd" include "libavutil/error.pxd" include "libavutil/frame.pxd" include "libavutil/samplefmt.pxd" include "libavutil/motion_vector.pxd" include "libavcodec/avcodec.pxd" include "libavcodec/bsf.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-12.3.0/include/libavcodec/000077500000000000000000000000001464705311200161355ustar00rootroot00000000000000PyAV-12.3.0/include/libavcodec/avcodec.pxd000066400000000000000000000317721464705311200202700ustar00rootroot00000000000000from 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/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_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 cdef enum: FF_COMPLIANCE_VERY_STRICT FF_COMPLIANCE_STRICT FF_COMPLIANCE_NORMAL FF_COMPLIANCE_UNOFFICIAL FF_COMPLIANCE_EXPERIMENTAL cdef enum AVCodecID: AV_CODEC_ID_NONE AV_CODEC_ID_MPEG2VIDEO AV_CODEC_ID_MPEG1VIDEO 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 AVCodecDescriptor: AVCodecID id char *name char *long_name int props char **mime_types 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 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 ticks_per_frame int extradata_size uint8_t *extradata int delay AVCodec *codec # Video. int width int height int coded_width int coded_height AVPixelFormat 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 int channels int frame_size int channel_layout #: .. 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) # User Data void *opaque cdef AVCodecContext* avcodec_alloc_context3(AVCodec *codec) cdef void avcodec_free_context(AVCodecContext **ctx) cdef AVClass* avcodec_get_class() cdef struct AVCodecDescriptor: AVCodecID id AVMediaType type char *name char *long_name int props 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 avcodec_is_open(AVCodecContext *ctx ) cdef int avcodec_close(AVCodecContext *ctx) 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_QP_TABLE_PROPERTIES AV_FRAME_DATA_QP_TABLE_DATA AV_FRAME_DATA_SEI_UNREGISTERED AV_FRAME_DATA_S12M_TIMECODE 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 int key_frame # 0 or 1. AVPictureType pict_type int interlaced_frame # 0 or 1. int width int height int nb_side_data AVFrameSideData **side_data int nb_samples # Audio samples int sample_rate # Audio Sample rate int channels # Number of audio channels int channel_layout # Audio channel_layout int64_t pts int64_t pkt_dts int pkt_size uint8_t **base void *opaque 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 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 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-12.3.0/include/libavcodec/bsf.pxd000066400000000000000000000015761464705311200174350ustar00rootroot00000000000000 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-12.3.0/include/libavdevice/000077500000000000000000000000001464705311200163175ustar00rootroot00000000000000PyAV-12.3.0/include/libavdevice/avdevice.pxd000066400000000000000000000007211464705311200206220ustar00rootroot00000000000000 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-12.3.0/include/libavfilter/000077500000000000000000000000001464705311200163455ustar00rootroot00000000000000PyAV-12.3.0/include/libavfilter/avfilter.pxd000066400000000000000000000053271464705311200207050ustar00rootroot00000000000000 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-12.3.0/include/libavfilter/avfiltergraph.pxd000066400000000000000000000023761464705311200217300ustar00rootroot00000000000000 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-12.3.0/include/libavfilter/buffersink.pxd000066400000000000000000000002201464705311200212120ustar00rootroot00000000000000cdef extern from "libavfilter/buffersink.h" nogil: int av_buffersink_get_frame( AVFilterContext *ctx, AVFrame *frame ) PyAV-12.3.0/include/libavfilter/buffersrc.pxd000066400000000000000000000002261464705311200210430ustar00rootroot00000000000000cdef extern from "libavfilter/buffersrc.h" nogil: int av_buffersrc_write_frame( AVFilterContext *ctx, const AVFrame *frame ) PyAV-12.3.0/include/libavformat/000077500000000000000000000000001464705311200163505ustar00rootroot00000000000000PyAV-12.3.0/include/libavformat/avformat.pxd000066400000000000000000000177611464705311200207200ustar00rootroot00000000000000from 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 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 int nb_side_data AVPacketSideData *side_data # 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, 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 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 void *opaque int (*io_open)( AVFormatContext *s, AVIOContext **pb, const char *url, int flags, AVDictionary **options ) void (*io_close)( 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-12.3.0/include/libavutil/000077500000000000000000000000001464705311200160355ustar00rootroot00000000000000PyAV-12.3.0/include/libavutil/avutil.pxd000066400000000000000000000240651464705311200200650ustar00rootroot00000000000000from 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 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_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) cdef void av_get_channel_layout_string( char* buff, int buf_size, int nb_channels, uint64_t channel_layout ) # 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 # since FFmpeg 3.3 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_CHANNEL_LAYOUT 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-12.3.0/include/libavutil/channel_layout.pxd000066400000000000000000000006341464705311200215620ustar00rootroot00000000000000cdef 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-12.3.0/include/libavutil/dict.pxd000066400000000000000000000015001464705311200174710ustar00rootroot00000000000000cdef 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-12.3.0/include/libavutil/error.pxd000066400000000000000000000023751464705311200177120ustar00rootroot00000000000000cdef 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-12.3.0/include/libavutil/frame.pxd000066400000000000000000000013161464705311200176450ustar00rootroot00000000000000cdef 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-12.3.0/include/libavutil/motion_vector.pxd000066400000000000000000000007021464705311200214400ustar00rootroot00000000000000from 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-12.3.0/include/libavutil/samplefmt.pxd000066400000000000000000000031571464705311200205500ustar00rootroot00000000000000cdef 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-12.3.0/include/libswresample/000077500000000000000000000000001464705311200167135ustar00rootroot00000000000000PyAV-12.3.0/include/libswresample/swresample.pxd000066400000000000000000000020661464705311200216160ustar00rootroot00000000000000from 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-12.3.0/include/libswscale/000077500000000000000000000000001464705311200161725ustar00rootroot00000000000000PyAV-12.3.0/include/libswscale/swscale.pxd000066400000000000000000000044031464705311200203510ustar00rootroot00000000000000 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-12.3.0/pyproject.toml000066400000000000000000000005151464705311200153340ustar00rootroot00000000000000[build-system] requires = ["setuptools", "cython"] [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-12.3.0/scripts/000077500000000000000000000000001464705311200141065ustar00rootroot00000000000000PyAV-12.3.0/scripts/activate.sh000077500000000000000000000051241464705311200162470ustar00rootroot00000000000000#!/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 # Pull from command line argument. if [[ "$1" ]]; then PYAV_LIBRARY="$1" else PYAV_LIBRARY=ffmpeg-6.1.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-12.3.0/scripts/build000077500000000000000000000007071464705311200151370ustar00rootroot00000000000000#!/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_PYTHON" setup.py config build_ext --inplace || exit 1 PyAV-12.3.0/scripts/build-deps000077500000000000000000000024551464705311200160720ustar00rootroot00000000000000#!/bin/bash if [[ ! "$_PYAV_ACTIVATED" ]]; then export here="$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd)" source "$here/activate.sh" fi cd "$PYAV_ROOT" # Always try to install the Python dependencies they are cheap. $PYAV_PIP install --upgrade -r tests/requirements.txt # 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 $PYAV_LIBRARY; skipping re-build." exit 0 fi mkdir -p "$PYAV_LIBRARY_ROOT" mkdir -p "$PYAV_LIBRARY_PREFIX" 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-mmx \ --disable-optimizations \ --disable-static \ --disable-stripping \ --enable-debug=3 \ --enable-gpl \ --enable-libx264 \ --enable-libxml2 \ --enable-shared \ --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-12.3.0/scripts/fetch-vendor.py000066400000000000000000000040001464705311200170360ustar00rootroot00000000000000import 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) for url_template in config["urls"]: tarball_url = url_template.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-12.3.0/scripts/ffmpeg-6.0.json000066400000000000000000000001571464705311200165510ustar00rootroot00000000000000{ "urls": ["https://github.com/PyAV-Org/pyav-ffmpeg/releases/download/6.0.0-1/ffmpeg-{platform}.tar.gz"] } PyAV-12.3.0/scripts/ffmpeg-6.1.json000066400000000000000000000001571464705311200165520ustar00rootroot00000000000000{ "urls": ["https://github.com/PyAV-Org/pyav-ffmpeg/releases/download/6.1.1-5/ffmpeg-{platform}.tar.gz"] } PyAV-12.3.0/scripts/test000077500000000000000000000012611464705311200150130ustar00rootroot00000000000000#!/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 $? } if istest main; then $PYAV_PYTHON setup.py test 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-12.3.0/setup.py000066400000000000000000000173101464705311200141330ustar00rootroot00000000000000import argparse import os import pathlib import platform import re import shlex import subprocess import sys 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 new_embed_signature(self, sig, doc): # Strip any `self` parameters from the front. sig = re.sub(r"\(self(,\s+)?", "(", sig) # If they both start with the same signature; skip it. if sig and doc: new_name = sig.split("(")[0].strip() old_name = doc.split("(")[0].strip() if new_name == old_name: return doc if new_name.endswith("." + old_name): return doc return old_embed_signature(self, sig, doc) EmbedSignature._embed_signature = new_embed_signature def get_config_from_directory(ffmpeg_dir): """ Get distutils-compatible extension arguments for a specific directory. """ if not os.path.isdir(ffmpeg_dir): print("The specified ffmpeg directory does not exist") exit(1) include_dir = os.path.join(FFMPEG_DIR, "include") library_dir = os.path.join(FFMPEG_DIR, "lib") if not os.path.exists(include_dir): include_dir = FFMPEG_DIR if not os.path.exists(library_dir): library_dir = FFMPEG_DIR return { "include_dirs": [include_dir], "libraries": FFMPEG_LIBRARIES, "library_dirs": [library_dir], } def get_config_from_pkg_config(): """ Get distutils-compatible extension arguments using pkg-config. """ pkg_config = os.environ.get("PKG_CONFIG", "pkg-config") try: raw_cflags = subprocess.check_output( [pkg_config, "--cflags", "--libs"] + ["lib" + name for name in FFMPEG_LIBRARIES] ) except FileNotFoundError: print(f"{pkg_config} is required for building PyAV") exit(1) except subprocess.CalledProcessError: print(f"{pkg_config} could not find libraries {FFMPEG_LIBRARIES}") exit(1) known, unknown = parse_cflags(raw_cflags.decode("utf-8")) if unknown: print("pkg-config returned flags we don't understand: {}".format(unknown)) if "-pthread" in unknown: print("Building PyAV against static FFmpeg libraries is not supported.") exit(1) return known def parse_cflags(raw_flags): parser = argparse.ArgumentParser(add_help=False) parser.add_argument("-I", dest="include_dirs", action="append") parser.add_argument("-L", dest="library_dirs", action="append") parser.add_argument("-l", dest="libraries", action="append") parser.add_argument("-D", dest="define_macros", action="append") parser.add_argument("-R", dest="runtime_library_dirs", action="append") raw_args = shlex.split(raw_flags.strip()) args, unknown = parser.parse_known_args(raw_args) config = {k: v or [] for k, v in args.__dict__.items()} for i, x in enumerate(config["define_macros"]): parts = x.split("=", 1) value = x[1] or None if len(x) == 2 else None config["define_macros"][i] = (parts[0], value) return config, " ".join(shlex.quote(x) for x in unknown) # Parse command-line arguments. FFMPEG_DIR = None for i, arg in enumerate(sys.argv): if arg.startswith("--ffmpeg-dir="): FFMPEG_DIR = arg.split("=")[1] del sys.argv[i] # Do not cythonize or use pkg-config when cleaning. use_pkg_config = platform.system() != "Windows" if len(sys.argv) > 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": [], } # Construct the modules that we find in the "av" directory. ext_modules = [] 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=dict( c_string_type="str", c_string_encoding="ascii", embedsignature=True, language_level=2, ), build_dir="src", include_path=["include"], ) # Read package metadata about = {} about_file = os.path.join(os.path.dirname(__file__), "av", "about.py") with open(about_file, encoding="utf-8") as fp: exec(fp.read(), about) package_folders = pathlib.Path("av").glob("**/") package_data = { ".".join(pckg.parts): ["*.pxd", "*.pyi", "*.typed"] for pckg in package_folders } # Add include/ headers to av.include package_dir = { ".".join(["av", *pckg.parts]): str(pckg) for pckg in pathlib.Path("include").glob("**/") } package_data.update({pckg: ["*.pxd"] for pckg in package_dir}) with open("README.md") as f: long_description = f.read() setup( name="av", version=about["__version__"], description="Pythonic bindings for FFmpeg's libraries.", long_description=long_description, long_description_content_type="text/markdown", license="BSD", project_urls={ "Bug Reports": "https://github.com/PyAV-Org/PyAV/issues", "Documentation": "https://pyav.basswood-io.com", "Download": "https://pypi.org/project/av", }, author="Mike Boers", author_email="pyav@mikeboers.com", url="https://github.com/PyAV-Org/PyAV", packages=find_packages( exclude=["build*", "examples*", "scratchpad*", "tests*", "include*"] ) + list(package_dir.keys()), package_dir=package_dir, package_data=package_data, python_requires=">=3.8", zip_safe=False, ext_modules=ext_modules, test_suite="tests", entry_points={ "console_scripts": ["pyav = av.__main__:main"], }, 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.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Multimedia :: Sound/Audio", "Topic :: Multimedia :: Sound/Audio :: Conversion", "Topic :: Multimedia :: Video", "Topic :: Multimedia :: Video :: Conversion", ], ) PyAV-12.3.0/tests/000077500000000000000000000000001464705311200135615ustar00rootroot00000000000000PyAV-12.3.0/tests/__init__.py000066400000000000000000000000001464705311200156600ustar00rootroot00000000000000PyAV-12.3.0/tests/common.py000066400000000000000000000103171464705311200154250ustar00rootroot00000000000000import datetime import errno import functools import os import types from unittest import TestCase as _Base from av.datasets import fate as fate_suite try: import PIL # noqa has_pillow = True except ImportError: has_pillow = False is_windows = os.name == "nt" skip_tests = frozenset(os.environ.get("PYAV_SKIP_TESTS", "").split(",")) def 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(): return fate_suite("png1/55c99e750a5fd6_50314226.png") def sandboxed(*args, **kwargs): do_makedirs = kwargs.pop("makedirs", True) base = kwargs.pop("sandbox", None) timed = kwargs.pop("timed", False) if kwargs: raise TypeError("extra kwargs: %s" % ", ".join(sorted(kwargs))) path = os.path.join(_sandbox(timed=timed) if base is None else base, *args) if do_makedirs: makedirs(os.path.dirname(path)) return path # Decorator for running a test in the sandbox directory def run_in_sandbox(func): @functools.wraps(func) def _inner(self, *args, **kwargs): current_dir = os.getcwd() try: os.chdir(self.sandbox) return func(self, *args, **kwargs) finally: os.chdir(current_dir) return _inner class MethodLogger: def __init__(self, obj): self._obj = obj self._log = [] def __getattr__(self, name): value = getattr(self._obj, name) if isinstance( value, ( types.MethodType, types.FunctionType, types.BuiltinFunctionType, types.BuiltinMethodType, ), ): return functools.partial(self._method, name, value) else: self._log.append(("__getattr__", (name,), {})) return value def _method(self, name, meth, *args, **kwargs): self._log.append((name, args, kwargs)) return meth(*args, **kwargs) def _filter(self, type_): return [log for log in self._log if log[0] == type_] class TestCase(_Base): @classmethod def _sandbox(cls, timed=True): path = os.path.join(_sandbox(timed=timed), cls.__name__) makedirs(path) return path @property def sandbox(self): return self._sandbox(timed=True) def sandboxed(self, *args, **kwargs): kwargs.setdefault("sandbox", self.sandbox) kwargs.setdefault("timed", True) return sandboxed(*args, **kwargs) def assertNdarraysEqual(self, a, b): import numpy self.assertEqual(a.shape, b.shape) comparison = a == b if not comparison.all(): it = numpy.nditer(comparison, flags=["multi_index"]) msg = "" for equal in it: if not equal: msg += "- arrays differ at index %s; %s %s\n" % ( it.multi_index, a[it.multi_index], b[it.multi_index], ) self.fail("ndarrays contents differ\n%s" % msg) def assertImagesAlmostEqual(self, a, b, epsilon=0.1, *args): import PIL.ImageFilter as ImageFilter self.assertEqual(a.size, b.size, "sizes dont match") a = a.filter(ImageFilter.BLUR).getdata() b = b.filter(ImageFilter.BLUR).getdata() 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 if diff > epsilon: self.fail( "images differed by %s at index %d; %s %s" % (diff, i, ax, bx) ) PyAV-12.3.0/tests/requirements.txt000066400000000000000000000001231464705311200170410ustar00rootroot00000000000000cython numpy pillow black isort flake8 flake8-pyproject sphinx==5.1.0 mypy==1.10.0 PyAV-12.3.0/tests/test_audiofifo.py000066400000000000000000000066701464705311200171500ustar00rootroot00000000000000import av from .common import TestCase, fate_suite class TestAudioFifo(TestCase): def test_data(self): container = av.open(fate_suite("audio-reference/chorusnoise_2ch_44kHz_s16.wav")) stream = container.streams.audio[0] fifo = av.AudioFifo() input_ = [] output = [] for i, packet in enumerate(container.demux(stream)): for frame in packet.decode(): 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_ = b"".join(input_) output = b"".join(output) min_len = min(len(input_), len(output)) self.assertTrue(min_len > 10 * 512 * 2 * 2) self.assertTrue(input_[:min_len] == output[:min_len]) def test_pts_simple(self): fifo = av.AudioFifo() # ensure __repr__ does not crash self.assertTrue( str(fifo).startswith( " at 0x" ) ) oframe = fifo.read(512) self.assertTrue(oframe is not None) self.assertEqual(oframe.pts, 0) self.assertEqual(oframe.time_base, iframe.time_base) self.assertEqual(fifo.samples_written, 1024) self.assertEqual(fifo.samples_read, 512) self.assertEqual(fifo.pts_per_sample, 1.0) iframe.pts = 1024 fifo.write(iframe) oframe = fifo.read(512) self.assertTrue(oframe is not None) self.assertEqual(oframe.pts, 512) self.assertEqual(oframe.time_base, iframe.time_base) iframe.pts = 9999 # Wrong! self.assertRaises(ValueError, fifo.write, iframe) def test_pts_complex(self): fifo = av.AudioFifo() iframe = av.AudioFrame(samples=1024) iframe.pts = 0 iframe.sample_rate = 48000 iframe.time_base = "1/96000" fifo.write(iframe) iframe.pts = 2048 fifo.write(iframe) oframe = fifo.read_many(1024)[-1] self.assertEqual(oframe.pts, 2048) self.assertEqual(fifo.pts_per_sample, 2.0) def test_missing_sample_rate(self): fifo = av.AudioFifo() iframe = av.AudioFrame(samples=1024) iframe.pts = 0 iframe.time_base = "1/48000" fifo.write(iframe) oframe = fifo.read(512) self.assertTrue(oframe is not None) self.assertIsNone(oframe.pts) self.assertEqual(oframe.sample_rate, 0) self.assertEqual(oframe.time_base, iframe.time_base) def test_missing_time_base(self): fifo = av.AudioFifo() iframe = av.AudioFrame(samples=1024) iframe.pts = 0 iframe.sample_rate = 48000 fifo.write(iframe) oframe = fifo.read(512) self.assertTrue(oframe is not None) self.assertIsNone(oframe.pts) self.assertIsNone(oframe.time_base) self.assertEqual(oframe.sample_rate, iframe.sample_rate) PyAV-12.3.0/tests/test_audioformat.py000066400000000000000000000015041464705311200175040ustar00rootroot00000000000000import sys from av import AudioFormat from .common import TestCase postfix = "le" if sys.byteorder == "little" else "be" class TestAudioFormats(TestCase): def test_s16_inspection(self): fmt = AudioFormat("s16") self.assertEqual(fmt.name, "s16") self.assertFalse(fmt.is_planar) self.assertEqual(fmt.bits, 16) self.assertEqual(fmt.bytes, 2) self.assertEqual(fmt.container_name, "s16" + postfix) self.assertEqual(fmt.planar.name, "s16p") self.assertIs(fmt.packed, fmt) def test_s32p_inspection(self): fmt = AudioFormat("s32p") self.assertEqual(fmt.name, "s32p") self.assertTrue(fmt.is_planar) self.assertEqual(fmt.bits, 32) self.assertEqual(fmt.bytes, 4) self.assertRaises(ValueError, lambda: fmt.container_name) PyAV-12.3.0/tests/test_audioframe.py000066400000000000000000000202171464705311200173100ustar00rootroot00000000000000import numpy from av import AudioFrame from .common import TestCase class TestAudioFrameConstructors(TestCase): def test_null_constructor(self) -> None: frame = AudioFrame() self.assertEqual(frame.format.name, "s16") self.assertEqual(frame.layout.name, "stereo") self.assertEqual(len(frame.planes), 0) self.assertEqual(frame.samples, 0) def test_manual_flt_mono_constructor(self) -> None: frame = AudioFrame(format="flt", layout="mono", samples=160) self.assertEqual(frame.format.name, "flt") self.assertEqual(frame.layout.name, "mono") self.assertEqual(len(frame.planes), 1) self.assertEqual(frame.planes[0].buffer_size, 640) self.assertEqual(frame.samples, 160) def test_manual_flt_stereo_constructor(self) -> None: frame = AudioFrame(format="flt", layout="stereo", samples=160) self.assertEqual(frame.format.name, "flt") self.assertEqual(frame.layout.name, "stereo") self.assertEqual(len(frame.planes), 1) self.assertEqual(frame.planes[0].buffer_size, 1280) self.assertEqual(frame.samples, 160) def test_manual_fltp_stereo_constructor(self) -> None: frame = AudioFrame(format="fltp", layout="stereo", samples=160) self.assertEqual(frame.format.name, "fltp") self.assertEqual(frame.layout.name, "stereo") self.assertEqual(len(frame.planes), 2) self.assertEqual(frame.planes[0].buffer_size, 640) self.assertEqual(frame.planes[1].buffer_size, 640) self.assertEqual(frame.samples, 160) def test_manual_s16_mono_constructor(self) -> None: frame = AudioFrame(format="s16", layout="mono", samples=160) self.assertEqual(frame.format.name, "s16") self.assertEqual(frame.layout.name, "mono") self.assertEqual(len(frame.planes), 1) self.assertEqual(frame.planes[0].buffer_size, 320) self.assertEqual(frame.samples, 160) def test_manual_s16_mono_constructor_align_8(self) -> None: frame = AudioFrame(format="s16", layout="mono", samples=159, align=8) self.assertEqual(frame.format.name, "s16") self.assertEqual(frame.layout.name, "mono") self.assertEqual(len(frame.planes), 1) self.assertEqual(frame.planes[0].buffer_size, 320) self.assertEqual(frame.samples, 159) def test_manual_s16_stereo_constructor(self) -> None: frame = AudioFrame(format="s16", layout="stereo", samples=160) self.assertEqual(frame.format.name, "s16") self.assertEqual(frame.layout.name, "stereo") self.assertEqual(len(frame.planes), 1) self.assertEqual(frame.planes[0].buffer_size, 640) self.assertEqual(frame.samples, 160) def test_manual_s16p_stereo_constructor(self) -> None: frame = AudioFrame(format="s16p", layout="stereo", samples=160) self.assertEqual(frame.format.name, "s16p") self.assertEqual(frame.layout.name, "stereo") self.assertEqual(len(frame.planes), 2) self.assertEqual(frame.planes[0].buffer_size, 320) self.assertEqual(frame.planes[1].buffer_size, 320) self.assertEqual(frame.samples, 160) class TestAudioFrameConveniences(TestCase): def test_basic_to_ndarray(self) -> None: frame = AudioFrame(format="s16p", layout="stereo", samples=160) array = frame.to_ndarray() self.assertEqual(array.dtype, "i2") self.assertEqual(array.shape, (2, 160)) def test_ndarray_dbl(self): layouts = [ ("dbl", "mono", "f8", (1, 160)), ("dbl", "stereo", "f8", (1, 320)), ("dblp", "mono", "f8", (1, 160)), ("dblp", "stereo", "f8", (2, 160)), ] for format, layout, dtype, size in layouts: array = numpy.ndarray(shape=size, dtype=dtype) for i in range(size[0]): array[i][:] = numpy.random.rand(size[1]) frame = AudioFrame.from_ndarray(array, format=format, layout=layout) self.assertEqual(frame.format.name, format) self.assertEqual(frame.layout.name, layout) self.assertEqual(frame.samples, 160) self.assertNdarraysEqual(frame.to_ndarray(), array) def test_from_ndarray_value_error(self): # incorrect dtype array = numpy.ndarray(shape=(1, 160), dtype="f2") with self.assertRaises(ValueError) as cm: AudioFrame.from_ndarray(array, format="flt", layout="mono") self.assertEqual( str(cm.exception), "Expected numpy array with dtype `float32` but got `float16`", ) # incorrect number of dimensions array = numpy.ndarray(shape=(1, 160, 2), dtype="f4") with self.assertRaises(ValueError) as cm: AudioFrame.from_ndarray(array, format="flt", layout="mono") self.assertEqual( str(cm.exception), "Expected numpy array with ndim `2` but got `3`" ) # incorrect shape array = numpy.ndarray(shape=(2, 160), dtype="f4") with self.assertRaises(ValueError) as cm: AudioFrame.from_ndarray(array, format="flt", layout="mono") self.assertEqual(str(cm.exception), "Unexpected numpy array shape `(2, 160)`") def test_ndarray_flt(self): layouts = [ ("flt", "mono", "f4", (1, 160)), ("flt", "stereo", "f4", (1, 320)), ("fltp", "mono", "f4", (1, 160)), ("fltp", "stereo", "f4", (2, 160)), ] for format, layout, dtype, size in layouts: array = numpy.ndarray(shape=size, dtype=dtype) for i in range(size[0]): array[i][:] = numpy.random.rand(size[1]) frame = AudioFrame.from_ndarray(array, format=format, layout=layout) self.assertEqual(frame.format.name, format) self.assertEqual(frame.layout.name, layout) self.assertEqual(frame.samples, 160) self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_s16(self): layouts = [ ("s16", "mono", "i2", (1, 160)), ("s16", "stereo", "i2", (1, 320)), ("s16p", "mono", "i2", (1, 160)), ("s16p", "stereo", "i2", (2, 160)), ] for format, layout, dtype, size in layouts: array = numpy.random.randint(0, 256, size=size, dtype=dtype) frame = AudioFrame.from_ndarray(array, format=format, layout=layout) self.assertEqual(frame.format.name, format) self.assertEqual(frame.layout.name, layout) self.assertEqual(frame.samples, 160) self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_s16p_align_8(self): frame = AudioFrame(format="s16p", layout="stereo", samples=159, align=8) array = frame.to_ndarray() self.assertEqual(array.dtype, "i2") self.assertEqual(array.shape, (2, 159)) def test_ndarray_s32(self): layouts = [ ("s32", "mono", "i4", (1, 160)), ("s32", "stereo", "i4", (1, 320)), ("s32p", "mono", "i4", (1, 160)), ("s32p", "stereo", "i4", (2, 160)), ] for format, layout, dtype, size in layouts: array = numpy.random.randint(0, 256, size=size, dtype=dtype) frame = AudioFrame.from_ndarray(array, format=format, layout=layout) self.assertEqual(frame.format.name, format) self.assertEqual(frame.layout.name, layout) self.assertEqual(frame.samples, 160) self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_u8(self): layouts = [ ("u8", "mono", "u1", (1, 160)), ("u8", "stereo", "u1", (1, 320)), ("u8p", "mono", "u1", (1, 160)), ("u8p", "stereo", "u1", (2, 160)), ] for format, layout, dtype, size in layouts: array = numpy.random.randint(0, 256, size=size, dtype=dtype) frame = AudioFrame.from_ndarray(array, format=format, layout=layout) self.assertEqual(frame.format.name, format) self.assertEqual(frame.layout.name, layout) self.assertEqual(frame.samples, 160) self.assertNdarraysEqual(frame.to_ndarray(), array) PyAV-12.3.0/tests/test_audiolayout.py000066400000000000000000000032211464705311200175270ustar00rootroot00000000000000from av import AudioLayout from .common import TestCase class TestAudioLayout(TestCase): def test_stereo_from_str(self): layout = AudioLayout("stereo") self._test_stereo(layout) def test_stereo_from_int(self): layout = AudioLayout(2) self._test_stereo(layout) def test_stereo_from_layout(self): layout = AudioLayout("stereo") layout2 = AudioLayout(layout) self._test_stereo(layout2) def test_channel_counts(self): self.assertRaises(ValueError, AudioLayout, -1) self.assertRaises(ValueError, AudioLayout, 9) def _test_stereo(self, layout): self.assertEqual(layout.name, "stereo") self.assertEqual(len(layout.channels), 2) self.assertEqual(repr(layout), "") self.assertEqual(layout.channels[0].name, "FL") self.assertEqual(layout.channels[0].description, "front left") self.assertEqual( repr(layout.channels[0]), "" ) self.assertEqual(layout.channels[1].name, "FR") self.assertEqual(layout.channels[1].description, "front right") self.assertEqual( repr(layout.channels[1]), "" ) def test_defaults(self): for i, name in enumerate( """ mono stereo 2.1 4.0 5.0 5.1 6.1 7.1 """.strip().split() ): layout = AudioLayout(i + 1) self.assertEqual(layout.name, name) self.assertEqual(len(layout.channels), i + 1) PyAV-12.3.0/tests/test_audioresampler.py000066400000000000000000000212701464705311200202100ustar00rootroot00000000000000from fractions import Fraction from av import AudioFrame, AudioResampler from .common import TestCase class TestAudioResampler(TestCase): def test_flush_immediately(self): """ 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) self.assertEqual(len(oframes), 0) def test_identity_passthrough(self): """ 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) self.assertEqual(len(oframes), 1) self.assertIs(iframe, oframes[0]) # resample another frame iframe.pts = 1024 oframes = resampler.resample(iframe) self.assertEqual(len(oframes), 1) self.assertIs(iframe, oframes[0]) # flush oframes = resampler.resample(None) self.assertEqual(len(oframes), 0) def test_matching_passthrough(self): """ 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) self.assertEqual(len(oframes), 1) self.assertIs(iframe, oframes[0]) # resample another frame iframe.pts = 1024 oframes = resampler.resample(iframe) self.assertEqual(len(oframes), 1) self.assertIs(iframe, oframes[0]) # flush oframes = resampler.resample(None) self.assertEqual(len(oframes), 0) def test_pts_assertion_same_rate(self): resampler = AudioResampler("s16", "mono") # resample one frame iframe = AudioFrame("s16", "stereo", 1024) iframe.sample_rate = 48000 iframe.time_base = "1/48000" iframe.pts = 0 oframes = resampler.resample(iframe) self.assertEqual(len(oframes), 1) oframe = oframes[0] self.assertEqual(oframe.pts, 0) self.assertEqual(oframe.time_base, iframe.time_base) self.assertEqual(oframe.sample_rate, iframe.sample_rate) self.assertEqual(oframe.samples, iframe.samples) # resample another frame iframe.pts = 1024 oframes = resampler.resample(iframe) self.assertEqual(len(oframes), 1) oframe = oframes[0] self.assertEqual(oframe.pts, 1024) self.assertEqual(oframe.time_base, iframe.time_base) self.assertEqual(oframe.sample_rate, iframe.sample_rate) self.assertEqual(oframe.samples, iframe.samples) # resample another frame with a pts gap, do not raise exception iframe.pts = 9999 oframes = resampler.resample(iframe) self.assertEqual(len(oframes), 1) oframe = oframes[0] self.assertEqual(oframe.pts, 9999) self.assertEqual(oframe.time_base, iframe.time_base) self.assertEqual(oframe.sample_rate, iframe.sample_rate) self.assertEqual(oframe.samples, iframe.samples) # flush oframes = resampler.resample(None) self.assertEqual(len(oframes), 0) def test_pts_assertion_new_rate_up(self): resampler = AudioResampler("s16", "mono", 44100) # resample one frame iframe = AudioFrame("s16", "stereo", 1024) iframe.sample_rate = 48000 iframe.time_base = "1/48000" iframe.pts = 0 oframes = resampler.resample(iframe) self.assertEqual(len(oframes), 1) oframe = oframes[0] self.assertEqual(oframe.pts, 0) self.assertEqual(oframe.time_base, Fraction(1, 44100)) self.assertEqual(oframe.sample_rate, 44100) self.assertEqual(oframe.samples, 925) iframe = AudioFrame("s16", "stereo", 1024) iframe.sample_rate = 48000 iframe.time_base = "1/48000" iframe.pts = 1024 oframes = resampler.resample(iframe) self.assertEqual(len(oframes), 1) oframe = oframes[0] self.assertEqual(oframe.pts, 925) self.assertEqual(oframe.time_base, Fraction(1, 44100)) self.assertEqual(oframe.sample_rate, 44100) self.assertEqual(oframe.samples, 941) # flush oframes = resampler.resample(None) self.assertEqual(len(oframes), 1) oframe = oframes[0] self.assertEqual(oframe.pts, 941 + 925) self.assertEqual(oframe.time_base, Fraction(1, 44100)) self.assertEqual(oframe.sample_rate, 44100) self.assertEqual(oframe.samples, 15) def test_pts_assertion_new_rate_down(self): resampler = AudioResampler("s16", "mono", 48000) # resample one frame iframe = AudioFrame("s16", "stereo", 1024) iframe.sample_rate = 44100 iframe.time_base = "1/44100" iframe.pts = 0 oframes = resampler.resample(iframe) self.assertEqual(len(oframes), 1) oframe = oframes[0] self.assertEqual(oframe.pts, 0) self.assertEqual(oframe.time_base, Fraction(1, 48000)) self.assertEqual(oframe.sample_rate, 48000) self.assertEqual(oframe.samples, 1098) iframe = AudioFrame("s16", "stereo", 1024) iframe.sample_rate = 44100 iframe.time_base = "1/44100" iframe.pts = 1024 oframes = resampler.resample(iframe) self.assertEqual(len(oframes), 1) oframe = oframes[0] self.assertEqual(oframe.pts, 1098) self.assertEqual(oframe.time_base, Fraction(1, 48000)) self.assertEqual(oframe.sample_rate, 48000) self.assertEqual(oframe.samples, 1114) # flush oframes = resampler.resample(None) self.assertEqual(len(oframes), 1) oframe = oframes[0] self.assertEqual(oframe.pts, 1114 + 1098) self.assertEqual(oframe.time_base, Fraction(1, 48000)) self.assertEqual(oframe.sample_rate, 48000) self.assertEqual(oframe.samples, 18) def test_pts_assertion_new_rate_fltp(self): resampler = AudioResampler("fltp", "mono", 8000, 1024) # resample one frame iframe = AudioFrame("s16", "mono", 1024) iframe.sample_rate = 8000 iframe.time_base = "1/1000" iframe.pts = 0 oframes = resampler.resample(iframe) self.assertEqual(len(oframes), 1) oframe = oframes[0] self.assertEqual(oframe.pts, 0) self.assertEqual(oframe.time_base, Fraction(1, 8000)) self.assertEqual(oframe.sample_rate, 8000) self.assertEqual(oframe.samples, 1024) iframe = AudioFrame("s16", "mono", 1024) iframe.sample_rate = 8000 iframe.time_base = "1/1000" iframe.pts = 8192 oframes = resampler.resample(iframe) self.assertEqual(len(oframes), 1) oframe = oframes[0] self.assertEqual(oframe.pts, 65536) self.assertEqual(oframe.time_base, Fraction(1, 8000)) self.assertEqual(oframe.sample_rate, 8000) self.assertEqual(oframe.samples, 1024) # flush oframes = resampler.resample(None) self.assertEqual(len(oframes), 0) def test_pts_missing_time_base(self): resampler = AudioResampler("s16", "mono", 44100) # resample one frame iframe = AudioFrame("s16", "stereo", 1024) iframe.sample_rate = 48000 iframe.pts = 0 oframes = resampler.resample(iframe) self.assertEqual(len(oframes), 1) oframe = oframes[0] self.assertEqual(oframe.pts, 0) self.assertEqual(oframe.time_base, Fraction(1, 44100)) self.assertEqual(oframe.sample_rate, 44100) # flush oframes = resampler.resample(None) self.assertEqual(len(oframes), 1) oframe = oframes[0] self.assertEqual(oframe.pts, 925) self.assertEqual(oframe.time_base, Fraction(1, 44100)) self.assertEqual(oframe.sample_rate, 44100) self.assertEqual(oframe.samples, 16) def test_mismatched_input(self): """ 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 self.assertRaises(ValueError) as cm: resampler.resample(iframe) self.assertEqual( str(cm.exception), "Frame does not match AudioResampler setup." ) PyAV-12.3.0/tests/test_bitstream.py000066400000000000000000000070331464705311200171670ustar00rootroot00000000000000from __future__ import annotations 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" class TestBitStreamFilters(TestCase): def test_filters_availible(self) -> None: self.assertIn("h264_mp4toannexb", bitstream_filters_available) def test_filter_chomp(self) -> None: ctx = BitStreamFilterContext("chomp") src_packets: tuple[Packet, None] = (Packet(b"\x0012345\0\0\0"), None) self.assertEqual(bytes(src_packets[0]), b"\x0012345\0\0\0") result_packets = [] for p in src_packets: result_packets.extend(ctx.filter(p)) self.assertEqual(len(result_packets), 1) self.assertEqual(bytes(result_packets[0]), b"\x0012345") def test_filter_setts(self) -> 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)) self.assertEqual(len(result_packets), 2) self.assertEqual(result_packets[0].pts, 0) self.assertEqual(result_packets[1].pts, 1) def test_filter_h264_mp4toannexb(self) -> 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): self.assertFalse(is_annexb(p)) res_packets.extend(ctx.filter(p)) self.assertEqual(len(res_packets), stream.frames) for p in res_packets: self.assertTrue(is_annexb(p)) def test_filter_output_parameters(self) -> None: with av.open(fate_suite("h264/interlaced_crop.mp4"), "r") as container: stream = container.streams.video[0] self.assertFalse(is_annexb(stream.codec_context.extradata)) ctx = BitStreamFilterContext("h264_mp4toannexb", stream) self.assertFalse(is_annexb(stream.codec_context.extradata)) del ctx _ = BitStreamFilterContext("h264_mp4toannexb", stream, out_stream=stream) self.assertTrue(is_annexb(stream.codec_context.extradata)) def test_filter_flush(self) -> 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)) self.assertEqual(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 self.assertRaises(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)) self.assertEqual(len(res_packets), stream.frames * 2) PyAV-12.3.0/tests/test_codec.py000066400000000000000000000064731464705311200162610ustar00rootroot00000000000000import unittest from av import AudioFormat, Codec, VideoFormat, codecs_available from av.codec.codec import UnknownCodecError from .common import TestCase # some older ffmpeg versions have no native opus encoder try: opus_c = Codec("opus", "w") opus_encoder_missing = False except UnknownCodecError: opus_encoder_missing = True class TestCodecs(TestCase): def test_codec_bogus(self): with self.assertRaises(UnknownCodecError): Codec("bogus123") with self.assertRaises(UnknownCodecError): Codec("bogus123", "w") def test_codec_mpeg4_decoder(self): c = Codec("mpeg4") self.assertEqual(c.name, "mpeg4") self.assertEqual(c.long_name, "MPEG-4 part 2") self.assertEqual(c.type, "video") self.assertIn(c.id, (12, 13)) self.assertTrue(c.is_decoder) self.assertFalse(c.is_encoder) self.assertTrue(c.delay) # audio self.assertIsNone(c.audio_formats) self.assertIsNone(c.audio_rates) # video formats = c.video_formats self.assertTrue(formats) self.assertIsInstance(formats[0], VideoFormat) self.assertTrue(any(f.name == "yuv420p" for f in formats)) self.assertIsNone(c.frame_rates) def test_codec_mpeg4_encoder(self): c = Codec("mpeg4", "w") self.assertEqual(c.name, "mpeg4") self.assertEqual(c.long_name, "MPEG-4 part 2") self.assertEqual(c.type, "video") self.assertIn(c.id, (12, 13)) self.assertTrue(c.is_encoder) self.assertFalse(c.is_decoder) self.assertTrue(c.delay) # audio self.assertIsNone(c.audio_formats) self.assertIsNone(c.audio_rates) # video formats = c.video_formats self.assertTrue(formats) self.assertIsInstance(formats[0], VideoFormat) self.assertTrue(any(f.name == "yuv420p" for f in formats)) self.assertIsNone(c.frame_rates) def test_codec_opus_decoder(self): c = Codec("opus") self.assertEqual(c.name, "opus") self.assertEqual(c.long_name, "Opus") self.assertEqual(c.type, "audio") self.assertTrue(c.is_decoder) self.assertFalse(c.is_encoder) self.assertTrue(c.delay) # audio self.assertIsNone(c.audio_formats) self.assertIsNone(c.audio_rates) # video self.assertIsNone(c.video_formats) self.assertIsNone(c.frame_rates) @unittest.skipIf(opus_encoder_missing, "Opus encoder is not available") def test_codec_opus_encoder(self): c = Codec("opus", "w") self.assertIn(c.name, ("opus", "libopus")) self.assertIn(c.long_name, ("Opus", "libopus Opus")) self.assertEqual(c.type, "audio") self.assertTrue(c.is_encoder) self.assertFalse(c.is_decoder) self.assertTrue(c.delay) # audio formats = c.audio_formats self.assertTrue(formats) self.assertIsInstance(formats[0], AudioFormat) self.assertTrue(any(f.name in ["flt", "fltp"] for f in formats)) self.assertIsNotNone(c.audio_rates) self.assertIn(48000, c.audio_rates) # video self.assertIsNone(c.video_formats) self.assertIsNone(c.frame_rates) def test_codecs_available(self): self.assertTrue(codecs_available) PyAV-12.3.0/tests/test_codec_context.py000066400000000000000000000422011464705311200200120ustar00rootroot00000000000000import os import warnings from fractions import Fraction from unittest import SkipTest import av from av import AudioLayout, AudioResampler, Codec, Packet from av.codec.codec import UnknownCodecError from av.video.frame import PictureType from .common import TestCase, fate_suite def iter_frames(container, stream): for packet in container.demux(stream): for frame in packet.decode(): yield frame def iter_raw_frames(path, packet_sizes, ctx): 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() self.assertEqual(ctx.skip_frame.name, "DEFAULT") def test_codec_delay(self): with av.open(fate_suite("mkv/codec_delay_opus.mkv")) as container: self.assertEqual(container.streams.audio[0].codec_context.delay, 312) with av.open(fate_suite("h264/interlaced_crop.mp4")) as container: self.assertEqual(container.streams.video[0].codec_context.delay, 0) def test_codec_tag(self): ctx = Codec("mpeg4", "w").create() self.assertEqual(ctx.codec_tag, "\x00\x00\x00\x00") ctx.codec_tag = "xvid" self.assertEqual(ctx.codec_tag, "xvid") # wrong length with self.assertRaises(ValueError) as cm: ctx.codec_tag = "bob" self.assertEqual(str(cm.exception), "Codec tag should be a 4 character string.") # wrong type with self.assertRaises(ValueError) as cm: ctx.codec_tag = 123 self.assertEqual(str(cm.exception), "Codec tag should be a 4 character string.") with av.open(fate_suite("h264/interlaced_crop.mp4")) as container: self.assertEqual(container.streams[0].codec_tag, "avc1") def test_decoder_extradata(self): ctx = av.codec.Codec("h264", "r").create() self.assertEqual(ctx.extradata, None) self.assertEqual(ctx.extradata_size, 0) ctx.extradata = b"123" self.assertEqual(ctx.extradata, b"123") self.assertEqual(ctx.extradata_size, 3) ctx.extradata = b"54321" self.assertEqual(ctx.extradata, b"54321") self.assertEqual(ctx.extradata_size, 5) ctx.extradata = None self.assertEqual(ctx.extradata, None) self.assertEqual(ctx.extradata_size, 0) def test_decoder_gop_size(self): ctx = av.codec.Codec("h264", "r").create() with warnings.catch_warnings(record=True) as captured: self.assertIsInstance(ctx.gop_size, int) self.assertEqual( captured[0].message.args[0], "Using VideoCodecContext.gop_size for decoders is deprecated.", ) def test_decoder_timebase(self): ctx = av.codec.Codec("h264", "r").create() with warnings.catch_warnings(record=True) as captured: self.assertIsNone(ctx.time_base) self.assertEqual( captured[0].message.args[0], "Using CodecContext.time_base for decoders is deprecated.", ) with warnings.catch_warnings(record=True) as captured: ctx.time_base = Fraction(1, 25) self.assertEqual( captured[0].message.args[0], "Using CodecContext.time_base for decoders is deprecated.", ) def test_encoder_extradata(self): ctx = av.codec.Codec("h264", "w").create() self.assertEqual(ctx.extradata, None) self.assertEqual(ctx.extradata_size, 0) with self.assertRaises(ValueError) as cm: ctx.extradata = b"123" self.assertEqual(str(cm.exception), "Can only set extradata for decoders.") def test_encoder_pix_fmt(self): ctx = av.codec.Codec("h264", "w").create() # valid format ctx.pix_fmt = "yuv420p" self.assertEqual(ctx.pix_fmt, "yuv420p") # invalid format with self.assertRaises(ValueError) as cm: ctx.pix_fmt = "__unknown_pix_fmt" self.assertEqual(str(cm.exception), "not a pixel format: '__unknown_pix_fmt'") self.assertEqual(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 self.assertEqual(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 self.assertRaises(av.error.InvalidDataError): for packet in container.demux(stream): for frame in packet.decode(): pass with av.open(self.sandboxed("output.mov"), "w") as output: stream = output.add_stream("qtrle") with self.assertRaises(ValueError): stream.codec_context.bits_per_coded_sample = 32 def test_parse(self): # 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, path): 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) self.assertEqual(len(parsed_source), len(full_source)) self.assertEqual(full_source, parsed_source) class TestEncoding(TestCase): def test_encoding_png(self): self.image_sequence_encode("png") def test_encoding_mjpeg(self): self.image_sequence_encode("mjpeg") def test_encoding_tiff(self): self.image_sequence_encode("tiff") def image_sequence_encode(self, codec_name): try: codec = Codec(codec_name, "w") except UnknownCodecError: raise SkipTest() container = av.open(fate_suite("h264/interlaced_crop.mp4")) video_stream = container.streams.video[0] width = 640 height = 480 ctx = codec.create() pix_fmt = ctx.codec.video_formats[0].name ctx.width = width ctx.height = height ctx.time_base = video_stream.time_base ctx.pix_fmt = pix_fmt ctx.open() frame_count = 1 path_list = [] for frame in iter_frames(container, video_stream): new_frame = frame.reformat(width, height, pix_fmt) new_packets = ctx.encode(new_frame) self.assertEqual(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() 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] self.assertEqual(frame.width, width) self.assertEqual(frame.height, height) self.assertEqual(frame.format.name, pix_fmt) def test_encoding_h264(self): self.video_encoding("h264", {"crf": "19"}) def test_encoding_mpeg4(self): self.video_encoding("mpeg4") def test_encoding_xvid(self): self.video_encoding("mpeg4", codec_tag="xvid") def test_encoding_mpeg1video(self): self.video_encoding("mpeg1video") def test_encoding_dvvideo(self): options = {"pix_fmt": "yuv411p", "width": 720, "height": 480} self.video_encoding("dvvideo", options) def test_encoding_dnxhd(self): options = { "b": "90M", # bitrate "pix_fmt": "yuv422p", "width": 1920, "height": 1080, "time_base": "1001/30000", "max_frames": 5, } self.video_encoding("dnxhd", options) def video_encoding(self, codec_name, options={}, codec_tag=None): try: codec = Codec(codec_name, "w") except UnknownCodecError: raise SkipTest() container = av.open(fate_suite("h264/interlaced_crop.mp4")) video_stream = container.streams.video[0] 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() 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 # TODO if codec_tag: ctx.codec_tag = codec_tag ctx.open() path = self.sandboxed("encoder.%s" % codec_name) packet_sizes = [] frame_count = 0 with open(path, "wb") as f: for frame in iter_frames(container, 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() ctx.open() keyframe_indices = [] decoded_frame_count = 0 for frame in iter_raw_frames(path, packet_sizes, ctx): decoded_frame_count += 1 self.assertEqual(frame.width, width) self.assertEqual(frame.height, height) self.assertEqual(frame.format.name, pix_fmt) if frame.key_frame: keyframe_indices.append(decoded_frame_count) self.assertEqual(frame_count, decoded_frame_count) self.assertIsInstance( 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 ): raise SkipTest() for i in decoded_gop_sizes: self.assertEqual(i, gop_size) final_gop_size = decoded_frame_count - max(keyframe_indices) self.assertLessEqual(final_gop_size, gop_size) def test_encoding_pcm_s24le(self): self.audio_encoding("pcm_s24le") def test_encoding_aac(self): self.audio_encoding("aac") def test_encoding_mp2(self): self.audio_encoding("mp2") maxDiff = None def audio_encoding(self, codec_name): 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, channel_layout): try: codec = Codec(codec_name, "w") except UnknownCodecError: raise SkipTest() ctx = codec.create() if ctx.codec.experimental: raise SkipTest() sample_fmt = ctx.codec.audio_formats[-1].name sample_rate = 48000 channels = 2 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("encoder.%s" % codec_name) samples = 0 packet_sizes = [] pts_expected = [ 0, 1098, 2212, 3327, 4441, 5556, 6670, 7785, 8900, 10014, 11129, 12243, 13358, 14472, 15587, 16701, 17816, 18931, 20045, 21160, 22274, 23389, 24503, 25618, 26732, 27847, 28962, 30076, 31191, 32305, 33420, 34534, 35649, 36763, 37878, 38993, 40107, 41222, 42336, 43451, 44565, 45680, 46795, 47909, 49024, 50138, 51253, 52367, 53482, 54596, 55711, 56826, 57940, 59055, 60169, 61284, 62398, 63513, 64627, 65742, 66857, 67971, 69086, 70200, 71315, 72429, 73544, 74658, 75773, 76888, 78002, 79117, 80231, 81346, 82460, 83575, 84689, 85804, 86919, 88033, 89148, 90262, 91377, 92491, 93606, 94720, 95835, 96950, 98064, 99179, 100293, 101408, ] if codec_name == "aac": pts_expected_encoded = list((-1024 + n * 1024 for n in range(101))) elif codec_name == "mp2": pts_expected_encoded = list((-481 + n * 1152 for n in range(89))) else: pts_expected_encoded = pts_expected.copy() with open(path, "wb") as f: for frame in iter_frames(container, audio_stream): resampled_frames = resampler.resample(frame) for resampled_frame in resampled_frames: self.assertEqual(resampled_frame.pts, pts_expected.pop(0)) self.assertEqual(resampled_frame.time_base, Fraction(1, 48000)) samples += resampled_frame.samples for packet in ctx.encode(resampled_frame): self.assertEqual(packet.pts, pts_expected_encoded.pop(0)) self.assertEqual(packet.time_base, Fraction(1, 48000)) packet_sizes.append(packet.size) f.write(packet) for packet in ctx.encode(None): self.assertEqual(packet.pts, pts_expected_encoded.pop(0)) self.assertEqual(packet.time_base, Fraction(1, 48000)) packet_sizes.append(packet.size) f.write(packet) ctx = Codec(codec_name, "r").create() ctx.sample_rate = sample_rate ctx.format = sample_fmt ctx.layout = channel_layout ctx.open() result_samples = 0 # should have more asserts but not sure what to check # libav and ffmpeg give different results # so can really use checksums for frame in iter_raw_frames(path, packet_sizes, ctx): result_samples += frame.samples self.assertEqual(frame.sample_rate, sample_rate) self.assertEqual(len(frame.layout.channels), channels) PyAV-12.3.0/tests/test_colorspace.py000066400000000000000000000031201464705311200173200ustar00rootroot00000000000000import av from av.video.reformatter import ColorRange, Colorspace from .common import TestCase, fate_suite class TestColorSpace(TestCase): def test_penguin_joke(self) -> None: container = av.open( fate_suite("amv/MTV_high_res_320x240_sample_Penguin_Joke_MTV_from_WMV.amv") ) stream = container.streams.video[0] self.assertEqual(stream.codec_context.color_range, 2) self.assertEqual(stream.codec_context.color_range, ColorRange.JPEG) self.assertEqual(stream.codec_context.color_primaries, 2) self.assertEqual(stream.codec_context.color_trc, 2) self.assertEqual(stream.codec_context.colorspace, 5) self.assertEqual(stream.codec_context.colorspace, Colorspace.ITU601) for packet in container.demux(stream): for frame in packet.decode(): assert isinstance(frame, av.VideoFrame) self.assertEqual(frame.color_range, ColorRange.JPEG) # a.k.a "pc" self.assertEqual(frame.colorspace, Colorspace.ITU601) return def test_sky_timelapse(self) -> None: container = av.open( av.datasets.curated("pexels/time-lapse-video-of-night-sky-857195.mp4") ) stream = container.streams.video[0] self.assertEqual(stream.codec_context.color_range, 1) self.assertEqual(stream.codec_context.color_range, ColorRange.MPEG) self.assertEqual(stream.codec_context.color_primaries, 1) self.assertEqual(stream.codec_context.color_trc, 1) self.assertEqual(stream.codec_context.colorspace, 1) PyAV-12.3.0/tests/test_containerformat.py000066400000000000000000000051061464705311200203670ustar00rootroot00000000000000from av import ContainerFormat, formats_available, open from .common import TestCase class TestContainerFormats(TestCase): def test_matroska(self) -> None: with open("test.mkv", "w") as container: self.assertNotEqual(container.default_video_codec, "none") self.assertNotEqual(container.default_audio_codec, "none") self.assertEqual(container.default_subtitle_codec, "ass") self.assertIn("ass", container.supported_codecs) fmt = ContainerFormat("matroska") self.assertTrue(fmt.is_input) self.assertTrue(fmt.is_output) self.assertEqual(fmt.name, "matroska") self.assertEqual(fmt.long_name, "Matroska") self.assertIn("mkv", fmt.extensions) self.assertFalse(fmt.no_file) def test_mov(self) -> None: with open("test.mov", "w") as container: self.assertNotEqual(container.default_video_codec, "none") self.assertNotEqual(container.default_audio_codec, "none") self.assertEqual(container.default_subtitle_codec, "none") self.assertIn("h264", container.supported_codecs) fmt = ContainerFormat("mov") self.assertTrue(fmt.is_input) self.assertTrue(fmt.is_output) self.assertEqual(fmt.name, "mov") self.assertEqual(fmt.long_name, "QuickTime / MOV") self.assertIn("mov", fmt.extensions) self.assertFalse(fmt.no_file) def test_gif(self) -> None: with open("test.gif", "w") as container: self.assertEqual(container.default_video_codec, "gif") self.assertEqual(container.default_audio_codec, "none") self.assertEqual(container.default_subtitle_codec, "none") self.assertIn("gif", container.supported_codecs) def test_stream_segment(self) -> None: # This format goes by two names, check both. fmt = ContainerFormat("stream_segment") self.assertFalse(fmt.is_input) self.assertTrue(fmt.is_output) self.assertEqual(fmt.name, "stream_segment") self.assertEqual(fmt.long_name, "streaming segment muxer") self.assertEqual(fmt.extensions, set()) self.assertTrue(fmt.no_file) fmt = ContainerFormat("ssegment") self.assertFalse(fmt.is_input) self.assertTrue(fmt.is_output) self.assertEqual(fmt.name, "ssegment") self.assertEqual(fmt.long_name, "streaming segment muxer") self.assertEqual(fmt.extensions, set()) self.assertTrue(fmt.no_file) def test_formats_available(self) -> None: self.assertTrue(formats_available) PyAV-12.3.0/tests/test_decode.py000066400000000000000000000122611464705311200164170ustar00rootroot00000000000000from fractions import Fraction import av from .common import TestCase, fate_suite class TestDecode(TestCase): def test_decoded_video_frame_count(self): container = av.open(fate_suite("h264/interlaced_crop.mp4")) video_stream = next(s for s in container.streams if s.type == "video") self.assertIs(video_stream, container.streams.video[0]) frame_count = 0 for packet in container.demux(video_stream): for frame in packet.decode(): frame_count += 1 self.assertEqual(frame_count, video_stream.frames) def test_decode_audio_corrupt(self): # 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 self.assertEqual(packet_count, 1) self.assertEqual(frame_count, 0) def test_decode_audio_sample_count(self): 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") self.assertIs(audio_stream, container.streams.audio[0]) sample_count = 0 for packet in container.demux(audio_stream): for frame in packet.decode(): sample_count += frame.samples total_samples = ( audio_stream.duration * audio_stream.sample_rate.numerator ) / audio_stream.time_base.denominator self.assertEqual(sample_count, total_samples) def test_decoded_time_base(self): container = av.open(fate_suite("h264/interlaced_crop.mp4")) stream = container.streams.video[0] self.assertEqual(stream.time_base, Fraction(1, 25)) for packet in container.demux(stream): for frame in packet.decode(): self.assertEqual(packet.time_base, frame.time_base) self.assertEqual(stream.time_base, frame.time_base) return def test_decoded_motion_vectors(self): 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 packet in container.demux(stream): for frame in packet.decode(): vectors = frame.side_data.get("MOTION_VECTORS") if frame.key_frame: # Key frame don't have motion vectors assert vectors is None else: assert len(vectors) > 0 return def test_decoded_motion_vectors_no_flag(self): container = av.open(fate_suite("h264/interlaced_crop.mp4")) stream = container.streams.video[0] for packet in container.demux(stream): for frame in packet.decode(): vectors = frame.side_data.get("MOTION_VECTORS") if not frame.key_frame: assert vectors is None return def test_decode_video_corrupt(self): # 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 self.assertEqual(packet_count, 1) self.assertEqual(frame_count, 0) def test_decode_close_then_use(self): 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): container = av.open(fate_suite("h264/interlaced_crop.mp4")) video_stream = next(s for s in container.streams if s.type == "video") self.assertIs(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 self.assertLess(output_count, input_count) for frame in video_stream.decode(None): # The Frame._time_base is not set by PyAV self.assertIsNone(frame.time_base) output_count += 1 self.assertEqual(output_count, input_count) PyAV-12.3.0/tests/test_deprecation.py000066400000000000000000000026551464705311200174770ustar00rootroot00000000000000import warnings from av import deprecation from .common import TestCase class TestDeprecations(TestCase): def test_method(self): class Example: def __init__(self, x=100): self.x = x @deprecation.method def foo(self, a, b): return self.x + a + b obj = Example() with warnings.catch_warnings(record=True) as captured: self.assertEqual(obj.foo(20, b=3), 123) self.assertIn("Example.foo is deprecated", captured[0].message.args[0]) def test_renamed_attr(self): class Example: new_value = "foo" old_value = deprecation.renamed_attr("new_value") def new_func(self, a, b): return a + b old_func = deprecation.renamed_attr("new_func") obj = Example() with warnings.catch_warnings(record=True) as captured: self.assertEqual(obj.old_value, "foo") self.assertIn( "Example.old_value is deprecated", captured[0].message.args[0] ) obj.old_value = "bar" self.assertIn( "Example.old_value is deprecated", captured[1].message.args[0] ) with warnings.catch_warnings(record=True) as captured: self.assertEqual(obj.old_func(1, 2), 3) self.assertIn("Example.old_func is deprecated", captured[0].message.args[0]) PyAV-12.3.0/tests/test_dictionary.py000066400000000000000000000007271464705311200173450ustar00rootroot00000000000000from av.dictionary import Dictionary from .common import TestCase class TestDictionary(TestCase): def test_basics(self): d = Dictionary() d["key"] = "value" self.assertEqual(d["key"], "value") self.assertIn("key", d) self.assertEqual(len(d), 1) self.assertEqual(list(d), ["key"]) self.assertEqual(d.pop("key"), "value") self.assertRaises(KeyError, d.pop, "key") self.assertEqual(len(d), 0) PyAV-12.3.0/tests/test_doctests.py000066400000000000000000000027611464705311200170300ustar00rootroot00000000000000import 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-12.3.0/tests/test_encode.py000066400000000000000000000337321464705311200164370ustar00rootroot00000000000000import io import math from fractions import Fraction from typing import cast from unittest import SkipTest import numpy as np 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: raise SkipTest("Don't have Pillow") import PIL.Image as Image output.metadata["title"] = "container" output.metadata["key"] = "value" stream = cast(VideoStream, 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_, is_dash=False): # Now inspect it a little. self.assertEqual(len(input_.streams), 1) if is_dash: # FFmpeg 4.2 added parsing of the programme information and it is named "Title" if av.library_versions["libavformat"] >= (58, 28): self.assertTrue( input_.metadata.get("Title") == "container", input_.metadata ) else: self.assertEqual(input_.metadata.get("title"), "container", input_.metadata) self.assertEqual(input_.metadata.get("key"), 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 self.assertEqual(input_.duration, 2000000) expected_average_rate = 24 expected_duration = None expected_frames = 0 expected_id = 0 else: if av.library_versions["libavformat"] < (58, 76): # FFmpeg < 4.4 expected_average_rate = Fraction(1152, 47) expected_duration = 24064 else: # FFmpeg >= 4.4 expected_average_rate = 24 expected_duration = 24576 expected_frames = 48 expected_id = 1 # actual stream properties self.assertIsInstance(stream, VideoStream) self.assertEqual(stream.average_rate, expected_average_rate) self.assertEqual(stream.base_rate, 24) self.assertEqual(stream.duration, expected_duration) self.assertEqual(stream.guessed_rate, 24) self.assertEqual(stream.frames, expected_frames) self.assertEqual(stream.id, expected_id) self.assertEqual(stream.index, 0) self.assertEqual(stream.profile, "Simple Profile") self.assertEqual(stream.start_time, 0) self.assertEqual(stream.time_base, Fraction(1, 12288)) self.assertEqual(stream.type, "video") # codec context properties self.assertEqual(stream.codec.name, "mpeg4") self.assertEqual(stream.codec.long_name, "MPEG-4 part 2") self.assertEqual(stream.format.name, "yuv420p") self.assertEqual(stream.format.width, WIDTH) self.assertEqual(stream.format.height, HEIGHT) self.assertEqual(stream.ticks_per_frame, 1) class TestBasicVideoEncoding(TestCase): def test_default_options(self): with av.open(self.sandboxed("output.mov"), "w") as output: stream = output.add_stream("mpeg4") self.assertIn(stream, output.streams.video) self.assertEqual(stream.average_rate, Fraction(24, 1)) self.assertEqual(stream.time_base, None) # codec context properties self.assertEqual(stream.bit_rate, 1024000) self.assertEqual(stream.format.height, 480) self.assertEqual(stream.format.name, "yuv420p") self.assertEqual(stream.format.width, 640) self.assertEqual(stream.height, 480) self.assertEqual(stream.pix_fmt, "yuv420p") self.assertEqual(stream.ticks_per_frame, 1) self.assertEqual(stream.width, 640) def test_encoding(self): 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): path = self.sandboxed("video_with_pts.mov") with av.open(path, "w") as output: stream = output.add_stream("h264", 24) self.assertIn(stream, 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): self.assertEqual(packet.time_base, Fraction(1, 24)) output.mux(packet) for packet in stream.encode(None): self.assertEqual(packet.time_base, Fraction(1, 24)) output.mux(packet) def test_encoding_with_unicode_filename(self): 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): with av.open(self.sandboxed("output.mov"), "w") as output: stream = output.add_stream("mp2") self.assertIn(stream, output.streams.audio) self.assertEqual(stream.time_base, None) # codec context properties self.assertEqual(stream.bit_rate, 128000) self.assertEqual(stream.format.name, "s16") self.assertEqual(stream.sample_rate, 48000) self.assertEqual(stream.ticks_per_frame, 1) def test_transcode(self): 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" channels = 2 sample_fmt = "s16" stream = output.add_stream("mp2", sample_rate) self.assertIn(stream, output.streams.audio) ctx = stream.codec_context ctx.time_base = sample_rate ctx.sample_rate = sample_rate ctx.format = sample_fmt ctx.layout = channel_layout ctx.channels = channels 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: self.assertEqual(len(container.streams), 1) self.assertEqual( container.metadata.get("title"), "container", container.metadata ) self.assertEqual(container.metadata.get("key"), None) stream = container.streams[0] self.assertIsInstance(stream, AudioStream) # codec context properties self.assertEqual(stream.channels, channels) self.assertEqual(stream.format.name, "s16p") self.assertEqual(stream.sample_rate, sample_rate) class TestEncodeStreamSemantics(TestCase): def test_stream_index(self): with av.open(self.sandboxed("output.mov"), "w") as output: vstream = output.add_stream("mpeg4", 24) self.assertIn(vstream, output.streams.video) vstream.pix_fmt = "yuv420p" vstream.width = 320 vstream.height = 240 astream = output.add_stream("mp2", 48000) self.assertIn(astream, output.streams.audio) astream.channels = 2 astream.format = "s16" self.assertEqual(vstream.index, 0) self.assertEqual(astream.index, 1) vframe = VideoFrame(320, 240, "yuv420p") vpacket = vstream.encode(vframe)[0] self.assertIs(vpacket.stream, vstream) self.assertEqual(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 self.assertIs(apacket.stream, astream) self.assertEqual(apacket.stream_index, 1) def test_stream_audio_resample(self): 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] self.assertEqual(apacket.pts, pts_expected.pop(0)) self.assertEqual(apacket.time_base, Fraction(1, 8000)) apackets = astream.encode(None) if apackets: apacket = apackets[0] self.assertEqual(apacket.pts, pts_expected.pop(0)) self.assertEqual(apacket.time_base, Fraction(1, 8000)) def test_set_id_and_time_base(self): with av.open(self.sandboxed("output.mov"), "w") as output: stream = output.add_stream("mp2") self.assertIn(stream, output.streams.audio) # set id self.assertEqual(stream.id, 0) stream.id = 1 self.assertEqual(stream.id, 1) # set time_base self.assertEqual(stream.time_base, None) stream.time_base = Fraction(1, 48000) self.assertEqual(stream.time_base, Fraction(1, 48000)) def encode_file_with_max_b_frames(max_b_frames): """ 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): """ 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) stream = container.streams.video[0] max_b_frame_run = 0 b_frame_run = 0 for packet in container.demux(stream): for frame in packet.decode(): 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): """ 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) self.assertTrue(actual_max_b_frames <= max_b_frames) PyAV-12.3.0/tests/test_enums.py000066400000000000000000000137621464705311200163320ustar00rootroot00000000000000import pickle from av.enum import EnumType, define_enum from .common import TestCase # This must be at the top-level. PickleableFooBar = define_enum("PickleableFooBar", __name__, [("FOO", 1)]) class TestEnums(TestCase): def define_foobar(self, **kwargs): return define_enum("Foobar", __name__, (("FOO", 1), ("BAR", 2)), **kwargs) def test_basics(self): cls = self.define_foobar() self.assertIsInstance(cls, EnumType) foo = cls.FOO self.assertIsInstance(foo, cls) self.assertEqual(foo.name, "FOO") self.assertEqual(foo.value, 1) self.assertNotIsInstance(foo, PickleableFooBar) def test_access(self): cls = self.define_foobar() foo1 = cls.FOO foo2 = cls["FOO"] foo3 = cls[1] foo4 = cls[foo1] self.assertIs(foo1, foo2) self.assertIs(foo1, foo3) self.assertIs(foo1, foo4) self.assertIn(foo1, cls) self.assertIn("FOO", cls) self.assertIn(1, cls) self.assertRaises(KeyError, lambda: cls["not a foo"]) self.assertRaises(KeyError, lambda: cls[10]) self.assertRaises(TypeError, lambda: cls[()]) self.assertEqual(cls.get("FOO"), foo1) self.assertIs(cls.get("not a foo"), None) def test_casting(self): cls = self.define_foobar() foo = cls.FOO self.assertEqual(repr(foo), "") str_foo = str(foo) self.assertIsInstance(str_foo, str) self.assertEqual(str_foo, "FOO") int_foo = int(foo) self.assertIsInstance(int_foo, int) self.assertEqual(int_foo, 1) def test_iteration(self): cls = self.define_foobar() self.assertEqual(list(cls), [cls.FOO, cls.BAR]) def test_equality(self): cls = self.define_foobar() foo = cls.FOO bar = cls.BAR self.assertEqual(foo, "FOO") self.assertEqual(foo, 1) self.assertEqual(foo, foo) self.assertNotEqual(foo, "BAR") self.assertNotEqual(foo, 2) self.assertNotEqual(foo, bar) self.assertRaises(ValueError, lambda: foo == "not a foo") self.assertRaises(ValueError, lambda: foo == 10) self.assertRaises(TypeError, lambda: foo == ()) def test_as_key(self): cls = self.define_foobar() foo = cls.FOO d = {foo: "value"} self.assertEqual(d[foo], "value") self.assertIs(d.get("FOO"), None) self.assertIs(d.get(1), None) def test_pickleable(self): cls = PickleableFooBar foo = cls.FOO enc = pickle.dumps(foo) foo2 = pickle.loads(enc) self.assertIs(foo, foo2) def test_create_unknown(self): cls = self.define_foobar() baz = cls.get(3, create=True) self.assertEqual(baz.name, "FOOBAR_3") self.assertEqual(baz.value, 3) def test_multiple_names(self): cls = define_enum( "FFooBBar", __name__, ( ("FOO", 1), ("F", 1), ("BAR", 2), ("B", 2), ), ) self.assertIs(cls.F, cls.FOO) self.assertEqual(cls.F.name, "FOO") self.assertNotEqual(cls.F.name, "F") # This is actually the string. self.assertEqual(cls.F, "FOO") self.assertEqual(cls.F, "F") self.assertNotEqual(cls.F, "BAR") self.assertNotEqual(cls.F, "B") self.assertRaises(ValueError, lambda: cls.F == "x") def test_flag_basics(self): cls = define_enum( "FoobarAllFlags", __name__, dict(FOO=1, BAR=2, FOOBAR=3).items(), is_flags=True, ) foo = cls.FOO bar = cls.BAR foobar = foo | bar self.assertIs(foobar, cls.FOOBAR) foo2 = foobar & foo self.assertIs(foo2, foo) bar2 = foobar ^ foo self.assertIs(bar2, bar) bar3 = foobar & ~foo self.assertIs(bar3, bar) x = cls.FOO x |= cls.BAR self.assertIs(x, cls.FOOBAR) x = cls.FOOBAR x &= cls.FOO self.assertIs(x, cls.FOO) def test_multi_flags_basics(self): cls = self.define_foobar(is_flags=True) foo = cls.FOO bar = cls.BAR foobar = foo | bar self.assertEqual(foobar.name, "FOO|BAR") self.assertEqual(foobar.value, 3) self.assertEqual(foobar.flags, (foo, bar)) foobar2 = foo | bar foobar3 = cls[3] foobar4 = cls[foobar] self.assertIs(foobar, foobar2) self.assertIs(foobar, foobar3) self.assertIs(foobar, foobar4) self.assertRaises(KeyError, lambda: cls["FOO|BAR"]) self.assertEqual(len(cls), 2) # It didn't get bigger self.assertEqual(list(cls), [foo, bar]) def test_multi_flags_create_missing(self): cls = self.define_foobar(is_flags=True) foobar = cls[3] self.assertIs(foobar, cls.FOO | cls.BAR) self.assertRaises(KeyError, lambda: cls[4]) # Not FOO or BAR self.assertRaises(KeyError, lambda: cls[7]) # FOO and BAR and missing flag. def test_properties(self): Flags = self.define_foobar(is_flags=True) foobar = Flags.FOO | Flags.BAR class Class: def __init__(self, value): self.value = Flags[value].value @Flags.property def flags(self): return self.value @flags.setter def flags(self, value): self.value = value foo = flags.flag_property("FOO") bar = flags.flag_property("BAR") obj = Class("FOO") self.assertIs(obj.flags, Flags.FOO) self.assertTrue(obj.foo) self.assertFalse(obj.bar) obj.bar = True self.assertIs(obj.flags, foobar) self.assertTrue(obj.foo) self.assertTrue(obj.bar) obj.foo = False self.assertIs(obj.flags, Flags.BAR) self.assertFalse(obj.foo) self.assertTrue(obj.bar) PyAV-12.3.0/tests/test_errors.py000066400000000000000000000047001464705311200165070ustar00rootroot00000000000000import errno import traceback import av from .common import TestCase, is_windows class TestErrorBasics(TestCase): def test_stringify(self): for cls in (av.ValueError, av.FileNotFoundError, av.DecoderNotFoundError): e = cls(1, "foo") self.assertEqual(str(e), "[Errno 1] foo") self.assertEqual(repr(e), "{}(1, 'foo')".format(cls.__name__)) self.assertEqual( traceback.format_exception_only(cls, e)[-1], "{}{}: [Errno 1] foo\n".format( "av.error.", cls.__name__, ), ) for cls in (av.ValueError, av.FileNotFoundError, av.DecoderNotFoundError): e = cls(1, "foo", "bar.txt") self.assertEqual(str(e), "[Errno 1] foo: 'bar.txt'") self.assertEqual(repr(e), "{}(1, 'foo', 'bar.txt')".format(cls.__name__)) self.assertEqual( traceback.format_exception_only(cls, e)[-1], "{}{}: [Errno 1] foo: 'bar.txt'\n".format( "av.error.", cls.__name__, ), ) def test_bases(self): self.assertTrue(issubclass(av.ValueError, ValueError)) self.assertTrue(issubclass(av.ValueError, av.FFmpegError)) self.assertTrue(issubclass(av.FileNotFoundError, FileNotFoundError)) self.assertTrue(issubclass(av.FileNotFoundError, OSError)) self.assertTrue(issubclass(av.FileNotFoundError, av.FFmpegError)) def test_filenotfound(self): """Catch using builtin class on Python 3.3""" try: av.open("does not exist") except FileNotFoundError as e: self.assertEqual(e.errno, errno.ENOENT) if is_windows: self.assertTrue( e.strerror in ["Error number -2 occurred", "No such file or directory"] ) else: self.assertEqual(e.strerror, "No such file or directory") self.assertEqual(e.filename, "does not exist") else: self.fail("no exception raised") def test_buffertoosmall(self): """Throw an exception from an enum.""" try: av.error.err_check(-av.error.BUFFER_TOO_SMALL.value) except av.BufferTooSmallError as e: self.assertEqual(e.errno, av.error.BUFFER_TOO_SMALL.value) else: self.fail("no exception raised") PyAV-12.3.0/tests/test_file_probing.py000066400000000000000000000335041464705311200176360ustar00rootroot00000000000000from 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): self.assertEqual(self.file.bit_rate, 269558) self.assertEqual(self.file.duration, 6165333) self.assertEqual(str(self.file.format), "") self.assertEqual(self.file.format.name, "mpegts") self.assertEqual( self.file.format.long_name, "MPEG-TS (MPEG-2 Transport Stream)" ) self.assertEqual(self.file.metadata, {}) self.assertEqual(self.file.size, 207740) self.assertEqual(self.file.start_time, 1400000) self.assertEqual(len(self.file.streams), 1) def test_stream_probing(self): stream = self.file.streams[0] # check __repr__ self.assertTrue( str(stream).startswith( "") self.assertEqual(self.file.format.name, "flac") self.assertEqual(self.file.format.long_name, "raw FLAC") self.assertEqual(self.file.metadata, {}) self.assertEqual(self.file.size, 0) self.assertEqual(self.file.start_time, None) self.assertEqual(len(self.file.streams), 1) def test_stream_probing(self): stream = self.file.streams[0] # ensure __repr__ does not crash self.assertTrue( str(stream).startswith( "") self.assertEqual(self.file.format.name, "mxf") self.assertEqual(self.file.format.long_name, "MXF (Material eXchange Format)") self.assertEqual(self.file.size, 1453153) self.assertEqual(self.file.start_time, 0) self.assertEqual(len(self.file.streams), 4) for key, value, min_version in ( ("application_platform", "AAFSDK (MacOS X)", None), ("comment_Comments", "example comment", None), ( "comment_UNC Path", "/Users/mark/Desktop/dnxhr_tracknames_export.aaf", None, ), ("company_name", "Avid Technology, Inc.", None), ("generation_uid", "b6bcfcab-70ff-7331-c592-233869de11d2", None), ("material_package_name", "Example.new.04", None), ( "material_package_umid", "0x060A2B340101010101010F001300000057E19D16BA8202DB060E2B347F7F2A80", None, ), ("modification_date", "2016-09-20T20:33:26.000000Z", None), # Next one is FFmpeg >= 4.2. ( "operational_pattern_ul", "060e2b34.04010102.0d010201.10030000", {"libavformat": (58, 29)}, ), ("product_name", "Avid Media Composer 8.6.3.43955", None), ("product_uid", "acfbf03a-4f42-a231-d0b7-c06ecd3d4ad7", None), ("product_version", "Unknown version", None), ("project_name", "UHD", None), ("uid", "4482d537-4203-ea40-9e4e-08a22900dd39", None), ): if min_version and any( av.library_versions[name] < version for name, version in min_version.items() ): continue self.assertEqual(self.file.metadata.get(key), value) def test_stream_probing(self): stream = self.file.streams[0] # check __repr__ self.assertTrue(str(stream).startswith(" at ")) # actual stream properties self.assertEqual(stream.duration, 37537) self.assertEqual(stream.frames, 0) self.assertEqual(stream.id, 1) self.assertEqual(stream.index, 0) self.assertEqual(stream.language, None) self.assertEqual( stream.metadata, { "data_type": "video", "file_package_umid": "0x060A2B340101010101010F001300000057E19D16BA8302DB060E2B347F7F2A80", "track_name": "Base", }, ) self.assertEqual(stream.profile, None) self.assertEqual(stream.start_time, 0) self.assertEqual(stream.time_base, Fraction(1, 90000)) self.assertEqual(stream.type, "data") self.assertEqual(hasattr(stream, "codec"), False) class TestSubtitleProbe(TestCase): def setUp(self): self.file = av.open(fate_suite("sub/MovText_capability_tester.mp4")) def test_container_probing(self): self.assertEqual(self.file.bit_rate, 810) self.assertEqual(self.file.duration, 8140000) self.assertEqual( str(self.file.format), "" ) self.assertEqual(self.file.format.name, "mov,mp4,m4a,3gp,3g2,mj2") self.assertEqual(self.file.format.long_name, "QuickTime / MOV") self.assertEqual( self.file.metadata, { "compatible_brands": "isom", "creation_time": "2012-07-04T05:10:41.000000Z", "major_brand": "isom", "minor_version": "1", }, ) self.assertEqual(self.file.size, 825) self.assertEqual(self.file.start_time, None) self.assertEqual(len(self.file.streams), 1) def test_stream_probing(self): stream = self.file.streams[0] # check __repr__ self.assertTrue( str(stream).startswith("") self.assertEqual(self.file.format.name, "mpegts") self.assertEqual( self.file.format.long_name, "MPEG-TS (MPEG-2 Transport Stream)" ) self.assertEqual(self.file.metadata, {}) self.assertEqual(self.file.size, 800000) self.assertEqual(self.file.start_time, 22953408322) self.assertEqual(len(self.file.streams), 1) def test_stream_probing(self): stream = self.file.streams[0] # check __repr__ self.assertTrue( str(stream).startswith("") self.assertEqual(self.file.format.name, "h264") self.assertEqual(self.file.format.long_name, "raw H.264 video") self.assertEqual(self.file.size, 0) self.assertEqual(self.file.bit_rate, 0) self.assertEqual(self.file.duration, None) self.assertEqual(len(self.file.streams), 1) self.assertEqual(self.file.start_time, None) self.assertEqual(self.file.metadata, {}) def test_stream_probing(self): stream = self.file.streams[0] # ensure __repr__ does not crash self.assertTrue(str(stream).startswith(" 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() self.assertIsInstance(palette_frame, av.VideoFrame) self.assertEqual(palette_frame.width, 16) self.assertEqual(palette_frame.height, 16) PyAV-12.3.0/tests/test_logging.py000066400000000000000000000047161464705311200166300ustar00rootroot00000000000000import errno import logging import threading import av.error import av.logging from .common import TestCase def do_log(message): av.logging.log(av.logging.INFO, "test", message) class TestLogging(TestCase): def test_adapt_level(self): self.assertEqual(av.logging.adapt_level(av.logging.ERROR), logging.ERROR) self.assertEqual(av.logging.adapt_level(av.logging.WARNING), logging.WARNING) self.assertEqual( av.logging.adapt_level((av.logging.WARNING + av.logging.ERROR) // 2), logging.WARNING, ) def test_threaded_captures(self): 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() self.assertIn((av.logging.INFO, "test", "main"), logs) av.logging.set_level(None) def test_global_captures(self): 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() self.assertIn((av.logging.INFO, "test", "main"), logs) self.assertIn((av.logging.INFO, "test", "thread"), logs) av.logging.set_level(None) def test_repeats(self): 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"] self.assertEqual( 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(self): 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 OSError as e: self.assertEqual(e.log, log) else: self.fail() av.logging.set_level(None) PyAV-12.3.0/tests/test_open.py000066400000000000000000000020271464705311200161340ustar00rootroot00000000000000from pathlib import Path import av from .common import TestCase, fate_suite class TestOpen(TestCase): def test_path_input(self): path = Path(fate_suite("h264/interlaced_crop.mp4")) self.assertIsInstance(path, Path) container = av.open(path) self.assertIs(type(container), av.container.InputContainer) def test_str_input(self): path = fate_suite("h264/interlaced_crop.mp4") self.assertIs(type(path), str) container = av.open(path) self.assertIs(type(container), av.container.InputContainer) def test_path_output(self): path = Path(fate_suite("h264/interlaced_crop.mp4")) self.assertIsInstance(path, Path) container = av.open(path, "w") self.assertIs(type(container), av.container.OutputContainer) def test_str_output(self): path = fate_suite("h264/interlaced_crop.mp4") self.assertIs(type(path), str) container = av.open(path, "w") self.assertIs(type(container), av.container.OutputContainer) PyAV-12.3.0/tests/test_options.py000066400000000000000000000010671464705311200166710ustar00rootroot00000000000000from av import ContainerFormat from av.option import Option, OptionType from .common import TestCase class TestOptions(TestCase): def test_mov_options(self): mov = ContainerFormat("mov") options = mov.descriptor.options by_name = {opt.name: opt for opt in options} opt = by_name.get("use_absolute_path") self.assertIsInstance(opt, Option) self.assertEqual(opt.name, "use_absolute_path") # This was not a good option to actually test. self.assertIn(opt.type, (OptionType.BOOL, OptionType.INT)) PyAV-12.3.0/tests/test_packet.py000066400000000000000000000036671464705311200164550ustar00rootroot00000000000000import av from .common import TestCase, fate_suite class TestProperties(TestCase): def test_is_keyframe(self): 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): self.assertTrue(packet.is_keyframe) else: self.assertFalse(packet.is_keyframe) def test_is_corrupt(self): 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: self.assertTrue(packet.is_corrupt) else: self.assertFalse(packet.is_corrupt) def test_is_discard(self): 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: self.assertTrue(packet.is_discard) else: self.assertFalse(packet.is_discard) def test_is_disposable(self): 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: self.assertTrue(packet.is_disposable) else: self.assertFalse(packet.is_disposable) def test_set_duration(self): with av.open(fate_suite("h264/interlaced_crop.mp4")) as container: for packet in container.demux(): old_duration = packet.duration packet.duration += 10 self.assertEqual(packet.duration, old_duration + 10) PyAV-12.3.0/tests/test_python_io.py000066400000000000000000000235171464705311200172120ustar00rootroot00000000000000from io import BytesIO from unittest import SkipTest import av from .common import ( MethodLogger, TestCase, fate_png, fate_suite, has_pillow, run_in_sandbox, ) from .test_encode import assert_rgb_rotate, write_rgb_rotate 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): 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): return 123 def seekable(self): return False def writable(self): return False class WriteOnlyPipe(BytesIO): """ Buffer which behaves like a writable pipe. """ @property def name(self): return 123 def readable(self): return False def seekable(self): return False # 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): self._log = [] self._method_log = [] def __call__(self, *args, **kwargs): self._log.append((args, kwargs)) self._method_log.append(self.io_open(*args, **kwargs)) return self._method_log[-1] def io_open(self, url, flags, options): # 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("Unsupported io open mode {}".format(flags)) return MethodLogger(open(url, mode)) class TestPythonIO(TestCase): def test_basic_errors(self): self.assertRaises(Exception, av.open, None) self.assertRaises(Exception, av.open, None, "w") def test_reading_from_buffer(self): with open(fate_suite("mpeg2/mpeg2_field_encoding.ts"), "rb") as fh: buf = BytesIO(fh.read()) self.read(buf, seekable=True) def test_reading_from_buffer_no_seek(self): with open(fate_suite("mpeg2/mpeg2_field_encoding.ts"), "rb") as fh: buf = ReadOnlyBuffer(fh.read()) self.read(buf, seekable=False) def test_reading_from_file(self): with open(fate_suite("mpeg2/mpeg2_field_encoding.ts"), "rb") as fh: self.read(fh, seekable=True) def test_reading_from_pipe_readonly(self): with open(fate_suite("mpeg2/mpeg2_field_encoding.ts"), "rb") as fh: buf = ReadOnlyPipe(fh.read()) self.read(buf, seekable=False) def test_reading_from_write_readonly(self): with open(fate_suite("mpeg2/mpeg2_field_encoding.ts"), "rb") as fh: buf = WriteOnlyPipe(fh.read()) with self.assertRaises(ValueError) as cm: self.read(buf, seekable=False) self.assertEqual( str(cm.exception), "File object has no read() method, or readable() returned False.", ) def test_writing_to_buffer(self): buf = BytesIO() self.write(buf) # Check contents. self.assertTrue(buf.tell()) buf.seek(0) with av.open(buf) as container: assert_rgb_rotate(self, container) def test_writing_to_buffer_broken(self): buf = BrokenBuffer() with self.assertRaises(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): 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 self.assertRaises(OSError): container.close() @run_in_sandbox def test_writing_to_custom_io_dash(self): # 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 self.assertGreaterEqual(len(wrapped_custom_io._log), 3) self.assertGreaterEqual(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 ) self.assertTrue(all_write) # Check that all files were closed all_closed = all( method_log._filter("close") for method_log in wrapped_custom_io._method_log ) self.assertTrue(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): if not has_pillow: raise SkipTest() 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 frame_i 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 self.assertEqual(len(wrapped_custom_io._log), frame_count) self.assertEqual(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 ) self.assertTrue(all_write) # Check that all files were closed all_closed = all( method_log._filter("close") for method_log in wrapped_custom_io._method_log ) self.assertTrue(all_closed) # Check contents. with av.open(sequence_filename, "r", "image2") as container: self.assertEqual(len(container.streams), 1) stream = container.streams[0] self.assertIsInstance(stream, av.video.stream.VideoStream) self.assertEqual(stream.duration, frame_count) self.assertEqual(stream.type, "video") # codec context properties self.assertEqual(stream.codec.name, "png") self.assertEqual(stream.format.name, "rgb24") self.assertEqual(stream.format.width, width) self.assertEqual(stream.format.height, height) def test_writing_to_file(self) -> None: path = self.sandboxed("writing.mp4") with open(path, "wb") as fh: self.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 self.assertRaises(ValueError) as cm: self.write(buf) self.assertEqual( str(cm.exception), "File object has no write() method, or writable() returned False.", ) def test_writing_to_pipe_writeonly(self): av.logging.set_level(av.logging.VERBOSE) buf = WriteOnlyPipe() with self.assertRaises(ValueError) as cm: self.write(buf) self.assertIn( "[mp4] muxer does not support non seekable output", str(cm.exception), ) av.logging.set_level(None) def read(self, fh, seekable: bool = True) -> None: wrapped = MethodLogger(fh) with av.open(wrapped, "r") as container: self.assertEqual(container.format.name, "mpegts") self.assertEqual( container.format.long_name, "MPEG-TS (MPEG-2 Transport Stream)" ) self.assertEqual(len(container.streams), 1) if seekable: self.assertEqual(container.size, 800000) self.assertEqual(container.metadata, {}) # Check method calls. self.assertTrue(wrapped._filter("read")) if seekable: self.assertTrue(wrapped._filter("seek")) def write(self, fh): wrapped = MethodLogger(fh) with av.open(wrapped, "w", "mp4") as container: write_rgb_rotate(container) # Check method calls. self.assertTrue(wrapped._filter("write")) self.assertTrue(wrapped._filter("seek")) PyAV-12.3.0/tests/test_seek.py000066400000000000000000000122441464705311200161240ustar00rootroot00000000000000import unittest import av from .common import TestCase, fate_suite def timestamp_to_frame(timestamp, stream): fps = stream.average_rate time_base = stream.time_base start_time = stream.start_time frame = (timestamp - start_time) * float(time_base) * float(fps) return frame def step_forward(container, stream): for packet in container.demux(stream): for frame in packet.decode(): if frame: return frame class TestSeek(TestCase): def test_seek_float(self): container = av.open(fate_suite("h264/interlaced_crop.mp4")) self.assertRaises(TypeError, container.seek, 1.0) def test_seek_int64(self): # 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): 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 self.assertEqual(total_packet_count, seek_packet_count) def test_seek_middle(self): 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 middle container.seek(container.duration // 2) seek_packet_count = 0 for packet in container.demux(): seek_packet_count += 1 self.assertTrue(seek_packet_count < total_packet_count) def test_seek_end(self): container = av.open(fate_suite("h264/interlaced_crop.mp4")) # 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 self.assertTrue(seek_packet_count > 0) self.assertTrue(seek_packet_count < middle_packet_count) def test_decode_half(self): container = av.open(fate_suite("h264/interlaced_crop.mp4")) video_stream = next(s for s in container.streams if s.type == "video") total_frame_count = 0 # Count number of frames in video for packet in container.demux(video_stream): for frame in packet.decode(): total_frame_count += 1 self.assertEqual(video_stream.frames, total_frame_count) # set target frame to middle frame target_frame = int(total_frame_count / 2.0) 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 packet in container.demux(video_stream): for frame in packet.decode(): 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 self.assertEqual(frame_count, total_frame_count - target_frame) def test_stream_seek(self): container = av.open(fate_suite("h264/interlaced_crop.mp4")) video_stream = next(s for s in container.streams if s.type == "video") total_frame_count = 0 # Count number of frames in video for packet in container.demux(video_stream): for frame in packet.decode(): total_frame_count += 1 target_frame = int(total_frame_count / 2.0) time_base = float(video_stream.time_base) rate = float(video_stream.average_rate) target_sec = target_frame * 1 / 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 packet in container.demux(video_stream): for frame in packet.decode(): 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 self.assertEqual(frame_count, total_frame_count - target_frame) if __name__ == "__main__": unittest.main() PyAV-12.3.0/tests/test_streams.py000066400000000000000000000026701464705311200166550ustar00rootroot00000000000000import av from .common import TestCase, fate_suite class TestStreams(TestCase): def test_stream_tuples(self): 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"]) self.assertEqual(video_streams, container.streams.video) audio_streams = tuple([s for s in container.streams if s.type == "audio"]) self.assertEqual(audio_streams, container.streams.audio) def test_selection(self): container = av.open(fate_suite("h264/interlaced_crop.mp4")) video = container.streams.video[0] # audio_stream = container.streams.audio[0] # audio_streams = list(container.streams.audio[0:2]) self.assertEqual([video], container.streams.get(video=0)) self.assertEqual([video], container.streams.get(video=(0,))) # TODO: Find something in the fate suite with video, audio, and subtitles. def test_noside_data(self): container = av.open(fate_suite("h264/interlaced_crop.mp4")) video = container.streams.video[0] self.assertEqual(video.nb_side_data, 0) def test_side_data(self): container = av.open(fate_suite("mov/displaymatrix.mov")) video = container.streams.video[0] self.assertEqual(video.nb_side_data, 1) self.assertEqual(video.side_data["DISPLAYMATRIX"], -90.0) PyAV-12.3.0/tests/test_subtitles.py000066400000000000000000000044471464705311200172210ustar00rootroot00000000000000import av from av.subtitles.subtitle import AssSubtitle, BitmapSubtitle from .common import TestCase, fate_suite class TestSubtitle(TestCase): 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()) self.assertEqual(len(subs), 3) subset = subs[0] self.assertEqual(subset.format, 1) self.assertEqual(subset.pts, 970000) self.assertEqual(subset.start_display_time, 0) self.assertEqual(subset.end_display_time, 1570) sub = subset[0] self.assertIsInstance(sub, AssSubtitle) assert isinstance(sub, AssSubtitle) self.assertEqual(sub.type, b"ass") self.assertEqual(sub.text, b"") self.assertEqual(sub.ass, b"0,0,Default,,0,0,0,,- Test 1.\\N- Test 2.") self.assertEqual(sub.dialogue, b"- Test 1.\n- Test 2.") def test_vobsub(self): path = fate_suite("sub/vobsub.sub") subs = [] with av.open(path) as container: for packet in container.demux(): subs.extend(packet.decode()) self.assertEqual(len(subs), 43) subset = subs[0] self.assertEqual(subset.format, 0) self.assertEqual(subset.pts, 132499044) self.assertEqual(subset.start_display_time, 0) self.assertEqual(subset.end_display_time, 4960) sub = subset[0] self.assertIsInstance(sub, BitmapSubtitle) self.assertEqual(sub.type, b"bitmap") self.assertEqual(sub.x, 259) self.assertEqual(sub.y, 379) self.assertEqual(sub.width, 200) self.assertEqual(sub.height, 24) self.assertEqual(sub.nb_colors, 4) bms = sub.planes self.assertEqual(len(bms), 1) self.assertEqual(len(memoryview(bms[0])), 4800) 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()) self.assertEqual(len(subs), 3) PyAV-12.3.0/tests/test_timeout.py000066400000000000000000000040251464705311200166610ustar00rootroot00000000000000import 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, client_address): pass class SlowRequestHandler(BaseHTTPRequestHandler): def do_GET(self): 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, *args): pass class TestTimeout(TestCase): def setUp(cls): 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): cls._thread.join(1) # Can't wait forever or the tests will never exit. cls._server.server_close() def test_no_timeout(self): start = time.time() av.open("http://localhost:%d/mpeg2_field_encoding.ts" % PORT) duration = time.time() - start self.assertGreater(duration, DELAY) def test_open_timeout(self): with self.assertRaises(av.ExitError): start = time.time() av.open( "http://localhost:%d/mpeg2_field_encoding.ts" % PORT, timeout=TIMEOUT ) duration = time.time() - start self.assertLess(duration, DELAY) def test_open_timeout_2(self): with self.assertRaises(av.ExitError): start = time.time() av.open( "http://localhost:%d/mpeg2_field_encoding.ts" % PORT, timeout=(TIMEOUT, None), ) duration = time.time() - start self.assertLess(duration, DELAY) PyAV-12.3.0/tests/test_videoformat.py000066400000000000000000000074461464705311200175240ustar00rootroot00000000000000from 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) self.assertEqual(str(cm.exception), "not a pixel format: '__unknown_pix_fmt'") def test_rgb24_inspection(self): fmt = VideoFormat("rgb24", 640, 480) self.assertEqual(fmt.name, "rgb24") self.assertEqual(len(fmt.components), 3) self.assertFalse(fmt.is_planar) self.assertFalse(fmt.has_palette) self.assertTrue(fmt.is_rgb) self.assertEqual(fmt.chroma_width(), 640) self.assertEqual(fmt.chroma_height(), 480) self.assertEqual(fmt.chroma_width(1024), 1024) self.assertEqual(fmt.chroma_height(1024), 1024) for i in range(3): comp = fmt.components[i] self.assertEqual(comp.plane, 0) self.assertEqual(comp.bits, 8) self.assertFalse(comp.is_luma) self.assertFalse(comp.is_chroma) self.assertFalse(comp.is_alpha) self.assertEqual(comp.width, 640) self.assertEqual(comp.height, 480) def test_yuv420p_inspection(self): fmt = VideoFormat("yuv420p", 640, 480) self.assertEqual(fmt.name, "yuv420p") self.assertEqual(len(fmt.components), 3) self._test_yuv420(fmt) def _test_yuv420(self, fmt): self.assertTrue(fmt.is_planar) self.assertFalse(fmt.has_palette) self.assertFalse(fmt.is_rgb) self.assertEqual(fmt.chroma_width(), 320) self.assertEqual(fmt.chroma_height(), 240) self.assertEqual(fmt.chroma_width(1024), 512) self.assertEqual(fmt.chroma_height(1024), 512) for i, comp in enumerate(fmt.components): comp = fmt.components[i] self.assertEqual(comp.plane, i) self.assertEqual(comp.bits, 8) self.assertFalse(fmt.components[0].is_chroma) self.assertTrue(fmt.components[1].is_chroma) self.assertTrue(fmt.components[2].is_chroma) self.assertTrue(fmt.components[0].is_luma) self.assertFalse(fmt.components[1].is_luma) self.assertFalse(fmt.components[2].is_luma) self.assertFalse(fmt.components[0].is_alpha) self.assertFalse(fmt.components[1].is_alpha) self.assertFalse(fmt.components[2].is_alpha) self.assertEqual(fmt.components[0].width, 640) self.assertEqual(fmt.components[1].width, 320) self.assertEqual(fmt.components[2].width, 320) def test_yuva420p_inspection(self): fmt = VideoFormat("yuva420p", 640, 480) self.assertEqual(len(fmt.components), 4) self._test_yuv420(fmt) self.assertFalse(fmt.components[3].is_chroma) self.assertEqual(fmt.components[3].width, 640) def test_gray16be_inspection(self): fmt = VideoFormat("gray16be", 640, 480) self.assertEqual(fmt.name, "gray16be") self.assertEqual(len(fmt.components), 1) self.assertFalse(fmt.is_planar) self.assertFalse(fmt.has_palette) self.assertFalse(fmt.is_rgb) self.assertEqual(fmt.chroma_width(), 640) self.assertEqual(fmt.chroma_height(), 480) self.assertEqual(fmt.chroma_width(1024), 1024) self.assertEqual(fmt.chroma_height(1024), 1024) comp = fmt.components[0] self.assertEqual(comp.plane, 0) self.assertEqual(comp.bits, 16) self.assertTrue(comp.is_luma) self.assertFalse(comp.is_chroma) self.assertEqual(comp.width, 640) self.assertEqual(comp.height, 480) self.assertFalse(comp.is_alpha) def test_pal8_inspection(self): fmt = VideoFormat("pal8", 640, 480) self.assertEqual(len(fmt.components), 1) self.assertTrue(fmt.has_palette) PyAV-12.3.0/tests/test_videoframe.py000066400000000000000000000672751464705311200173340ustar00rootroot00000000000000from unittest import SkipTest import numpy from av import VideoFrame from .common import TestCase, fate_png, has_pillow class TestVideoFrameConstructors(TestCase): def test_invalid_pixel_format(self): with self.assertRaises(ValueError) as cm: VideoFrame(640, 480, "__unknown_pix_fmt") self.assertEqual(str(cm.exception), "not a pixel format: '__unknown_pix_fmt'") def test_null_constructor(self): frame = VideoFrame() self.assertEqual(frame.width, 0) self.assertEqual(frame.height, 0) self.assertEqual(frame.format.name, "yuv420p") def test_manual_yuv_constructor(self): frame = VideoFrame(640, 480, "yuv420p") self.assertEqual(frame.width, 640) self.assertEqual(frame.height, 480) self.assertEqual(frame.format.name, "yuv420p") def test_manual_rgb_constructor(self): frame = VideoFrame(640, 480, "rgb24") self.assertEqual(frame.width, 640) self.assertEqual(frame.height, 480) self.assertEqual(frame.format.name, "rgb24") class TestVideoFramePlanes(TestCase): def test_null_planes(self): frame = VideoFrame() # yuv420p self.assertEqual(len(frame.planes), 0) def test_yuv420p_planes(self): frame = VideoFrame(640, 480, "yuv420p") self.assertEqual(len(frame.planes), 3) self.assertEqual(frame.planes[0].width, 640) self.assertEqual(frame.planes[0].height, 480) self.assertEqual(frame.planes[0].line_size, 640) self.assertEqual(frame.planes[0].buffer_size, 640 * 480) for i in range(1, 3): self.assertEqual(frame.planes[i].width, 320) self.assertEqual(frame.planes[i].height, 240) self.assertEqual(frame.planes[i].line_size, 320) self.assertEqual(frame.planes[i].buffer_size, 320 * 240) def test_yuv420p_planes_align(self): # 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") self.assertEqual(len(frame.planes), 3) self.assertEqual(frame.planes[0].width, 318) self.assertEqual(frame.planes[0].height, 238) self.assertEqual(frame.planes[0].line_size, 320) self.assertEqual(frame.planes[0].buffer_size, 320 * 238) for i in range(1, 3): self.assertEqual(frame.planes[i].width, 159) self.assertEqual(frame.planes[i].height, 119) self.assertEqual(frame.planes[i].line_size, 160) self.assertEqual(frame.planes[i].buffer_size, 160 * 119) def test_rgb24_planes(self): frame = VideoFrame(640, 480, "rgb24") self.assertEqual(len(frame.planes), 1) self.assertEqual(frame.planes[0].width, 640) self.assertEqual(frame.planes[0].height, 480) self.assertEqual(frame.planes[0].line_size, 640 * 3) self.assertEqual(frame.planes[0].buffer_size, 640 * 480 * 3) class TestVideoFrameBuffers(TestCase): def test_memoryview_read(self): frame = VideoFrame(640, 480, "rgb24") frame.planes[0].update(b"01234" + (b"x" * (640 * 480 * 3 - 5))) mem = memoryview(frame.planes[0]) self.assertEqual(mem.ndim, 1) self.assertEqual(mem.shape, (640 * 480 * 3,)) self.assertFalse(mem.readonly) self.assertEqual(mem[1], 49) self.assertEqual(mem[:7], b"01234xx") mem[1] = 46 self.assertEqual(mem[:7], b"0.234xx") class TestVideoFrameImage(TestCase): def setUp(self): if not has_pillow: raise SkipTest() def test_roundtrip(self): import PIL.Image as Image image = Image.open(fate_png()) frame = VideoFrame.from_image(image) img = frame.to_image() img.save(self.sandboxed("roundtrip-high.jpg")) self.assertImagesAlmostEqual(image, img) def test_to_image_rgb24(self): 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() self.assertEqual(img.size, (width, height)) self.assertEqual(img.tobytes(), expected) def test_to_image_with_dimensions(self): frame = VideoFrame(640, 480, format="rgb24") img = frame.to_image(width=320, height=240) self.assertEqual(img.size, (320, 240)) class TestVideoFrameNdarray(TestCase): def assertPixelValue16(self, plane, expected, byteorder: str): view = memoryview(plane) if byteorder == "big": self.assertEqual(view[0], (expected >> 8) & 0xFF) self.assertEqual(view[1], expected & 0xFF) else: self.assertEqual(view[0], expected & 0xFF) self.assertEqual(view[1], (expected >> 8) & 0xFF) def test_basic_to_ndarray(self): frame = VideoFrame(640, 480, "rgb24") array = frame.to_ndarray() self.assertEqual(array.shape, (480, 640, 3)) def test_ndarray_gray(self): array = numpy.random.randint(0, 256, size=(480, 640), dtype=numpy.uint8) for format in ["gray", "gray8"]: frame = VideoFrame.from_ndarray(array, format=format) self.assertEqual(frame.width, 640) self.assertEqual(frame.height, 480) self.assertEqual(frame.format.name, "gray") self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gray_align(self): array = numpy.random.randint(0, 256, size=(238, 318), dtype=numpy.uint8) for format in ["gray", "gray8"]: frame = VideoFrame.from_ndarray(array, format=format) self.assertEqual(frame.width, 318) self.assertEqual(frame.height, 238) self.assertEqual(frame.format.name, "gray") self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_rgb(self): 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) self.assertEqual(frame.width, 640) self.assertEqual(frame.height, 480) self.assertEqual(frame.format.name, format) self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_rgb_align(self): 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) self.assertEqual(frame.width, 318) self.assertEqual(frame.height, 238) self.assertEqual(frame.format.name, format) self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_rgba(self): 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) self.assertEqual(frame.width, 640) self.assertEqual(frame.height, 480) self.assertEqual(frame.format.name, format) self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_rgba_align(self): 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) self.assertEqual(frame.width, 318) self.assertEqual(frame.height, 238) self.assertEqual(frame.format.name, format) self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gbrp(self): array = numpy.random.randint(0, 256, size=(480, 640, 3), dtype=numpy.uint8) frame = VideoFrame.from_ndarray(array, format="gbrp") self.assertEqual(frame.width, 640) self.assertEqual(frame.height, 480) self.assertEqual(frame.format.name, "gbrp") self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gbrp_align(self): array = numpy.random.randint(0, 256, size=(238, 318, 3), dtype=numpy.uint8) frame = VideoFrame.from_ndarray(array, format="gbrp") self.assertEqual(frame.width, 318) self.assertEqual(frame.height, 238) self.assertEqual(frame.format.name, "gbrp") self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gbrp10(self): 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) self.assertEqual(frame.width, 640) self.assertEqual(frame.height, 480) self.assertEqual(frame.format.name, format) self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gbrp10_align(self): 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) self.assertEqual(frame.width, 318) self.assertEqual(frame.height, 238) self.assertEqual(frame.format.name, format) self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gbrp12(self): 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) self.assertEqual(frame.width, 640) self.assertEqual(frame.height, 480) self.assertEqual(frame.format.name, format) self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gbrp12_align(self): 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) self.assertEqual(frame.width, 318) self.assertEqual(frame.height, 238) self.assertEqual(frame.format.name, format) self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gbrp14(self): 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) self.assertEqual(frame.width, 640) self.assertEqual(frame.height, 480) self.assertEqual(frame.format.name, format) self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gbrp14_align(self): 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) self.assertEqual(frame.width, 318) self.assertEqual(frame.height, 238) self.assertEqual(frame.format.name, format) self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gbrp16(self): 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) self.assertEqual(frame.width, 640) self.assertEqual(frame.height, 480) self.assertEqual(frame.format.name, format) self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gbrp16_align(self): 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) self.assertEqual(frame.width, 318) self.assertEqual(frame.height, 238) self.assertEqual(frame.format.name, format) self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gbrpf32(self): array = numpy.random.random_sample(size=(480, 640, 3)).astype(numpy.float32) for format in ["gbrpf32be", "gbrpf32le"]: frame = VideoFrame.from_ndarray(array, format=format) self.assertEqual(frame.width, 640) self.assertEqual(frame.height, 480) self.assertEqual(frame.format.name, format) self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gbrpf32_align(self): array = numpy.random.random_sample(size=(238, 318, 3)).astype(numpy.float32) for format in ["gbrpf32be", "gbrpf32le"]: frame = VideoFrame.from_ndarray(array, format=format) self.assertEqual(frame.width, 318) self.assertEqual(frame.height, 238) self.assertEqual(frame.format.name, format) self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_yuv420p(self): array = numpy.random.randint(0, 256, size=(720, 640), dtype=numpy.uint8) frame = VideoFrame.from_ndarray(array, format="yuv420p") self.assertEqual(frame.width, 640) self.assertEqual(frame.height, 480) self.assertEqual(frame.format.name, "yuv420p") self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_yuv420p_align(self): array = numpy.random.randint(0, 256, size=(357, 318), dtype=numpy.uint8) frame = VideoFrame.from_ndarray(array, format="yuv420p") self.assertEqual(frame.width, 318) self.assertEqual(frame.height, 238) self.assertEqual(frame.format.name, "yuv420p") self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_yuvj420p(self): array = numpy.random.randint(0, 256, size=(720, 640), dtype=numpy.uint8) frame = VideoFrame.from_ndarray(array, format="yuvj420p") self.assertEqual(frame.width, 640) self.assertEqual(frame.height, 480) self.assertEqual(frame.format.name, "yuvj420p") self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_yuyv422(self): array = numpy.random.randint(0, 256, size=(480, 640, 2), dtype=numpy.uint8) frame = VideoFrame.from_ndarray(array, format="yuyv422") self.assertEqual(frame.width, 640) self.assertEqual(frame.height, 480) self.assertEqual(frame.format.name, "yuyv422") self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_yuv444p(self): array = numpy.random.randint(0, 256, size=(3, 480, 640), dtype=numpy.uint8) frame = VideoFrame.from_ndarray(array, format="yuv444p") self.assertEqual(frame.width, 640) self.assertEqual(frame.height, 480) self.assertEqual(frame.format.name, "yuv444p") self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_yuvj444p(self): array = numpy.random.randint(0, 256, size=(3, 480, 640), dtype=numpy.uint8) frame = VideoFrame.from_ndarray(array, format="yuvj444p") self.assertEqual(frame.width, 640) self.assertEqual(frame.height, 480) self.assertEqual(frame.format.name, "yuvj444p") self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_yuyv422_align(self): array = numpy.random.randint(0, 256, size=(238, 318, 2), dtype=numpy.uint8) frame = VideoFrame.from_ndarray(array, format="yuyv422") self.assertEqual(frame.width, 318) self.assertEqual(frame.height, 238) self.assertEqual(frame.format.name, "yuyv422") self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_gray16be(self): array = numpy.random.randint(0, 65536, size=(480, 640), dtype=numpy.uint16) frame = VideoFrame.from_ndarray(array, format="gray16be") self.assertEqual(frame.width, 640) self.assertEqual(frame.height, 480) self.assertEqual(frame.format.name, "gray16be") self.assertNdarraysEqual(frame.to_ndarray(), array) # check endianness by examining value of first pixel self.assertPixelValue16(frame.planes[0], array[0][0], "big") def test_ndarray_gray16le(self): array = numpy.random.randint(0, 65536, size=(480, 640), dtype=numpy.uint16) frame = VideoFrame.from_ndarray(array, format="gray16le") self.assertEqual(frame.width, 640) self.assertEqual(frame.height, 480) self.assertEqual(frame.format.name, "gray16le") self.assertNdarraysEqual(frame.to_ndarray(), array) # check endianness by examining value of first pixel self.assertPixelValue16(frame.planes[0], array[0][0], "little") def test_ndarray_rgb48be(self): array = numpy.random.randint(0, 65536, size=(480, 640, 3), dtype=numpy.uint16) frame = VideoFrame.from_ndarray(array, format="rgb48be") self.assertEqual(frame.width, 640) self.assertEqual(frame.height, 480) self.assertEqual(frame.format.name, "rgb48be") self.assertNdarraysEqual(frame.to_ndarray(), array) # check endianness by examining red value of first pixel self.assertPixelValue16(frame.planes[0], array[0][0][0], "big") def test_ndarray_rgb48le(self): array = numpy.random.randint(0, 65536, size=(480, 640, 3), dtype=numpy.uint16) frame = VideoFrame.from_ndarray(array, format="rgb48le") self.assertEqual(frame.width, 640) self.assertEqual(frame.height, 480) self.assertEqual(frame.format.name, "rgb48le") self.assertNdarraysEqual(frame.to_ndarray(), array) # check endianness by examining red value of first pixel self.assertPixelValue16(frame.planes[0], array[0][0][0], "little") def test_ndarray_rgb48le_align(self): array = numpy.random.randint(0, 65536, size=(238, 318, 3), dtype=numpy.uint16) frame = VideoFrame.from_ndarray(array, format="rgb48le") self.assertEqual(frame.width, 318) self.assertEqual(frame.height, 238) self.assertEqual(frame.format.name, "rgb48le") self.assertNdarraysEqual(frame.to_ndarray(), array) # check endianness by examining red value of first pixel self.assertPixelValue16(frame.planes[0], array[0][0][0], "little") def test_ndarray_rgba64be(self): array = numpy.random.randint(0, 65536, size=(480, 640, 4), dtype=numpy.uint16) frame = VideoFrame.from_ndarray(array, format="rgba64be") self.assertEqual(frame.width, 640) self.assertEqual(frame.height, 480) self.assertEqual(frame.format.name, "rgba64be") self.assertNdarraysEqual(frame.to_ndarray(), array) # check endianness by examining red value of first pixel self.assertPixelValue16(frame.planes[0], array[0][0][0], "big") def test_ndarray_rgba64le(self): array = numpy.random.randint(0, 65536, size=(480, 640, 4), dtype=numpy.uint16) frame = VideoFrame.from_ndarray(array, format="rgba64le") self.assertEqual(frame.width, 640) self.assertEqual(frame.height, 480) self.assertEqual(frame.format.name, "rgba64le") self.assertNdarraysEqual(frame.to_ndarray(), array) # check endianness by examining red value of first pixel self.assertPixelValue16(frame.planes[0], array[0][0][0], "little") def test_ndarray_rgb8(self): array = numpy.random.randint(0, 256, size=(480, 640), dtype=numpy.uint8) frame = VideoFrame.from_ndarray(array, format="rgb8") self.assertEqual(frame.width, 640) self.assertEqual(frame.height, 480) self.assertEqual(frame.format.name, "rgb8") self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_bgr8(self): array = numpy.random.randint(0, 256, size=(480, 640), dtype=numpy.uint8) frame = VideoFrame.from_ndarray(array, format="bgr8") self.assertEqual(frame.width, 640) self.assertEqual(frame.height, 480) self.assertEqual(frame.format.name, "bgr8") self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_pal8(self): 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") self.assertEqual(frame.width, 640) self.assertEqual(frame.height, 480) self.assertEqual(frame.format.name, "pal8") returned = frame.to_ndarray() self.assertTrue((type(returned) is tuple) and len(returned) == 2) self.assertNdarraysEqual(returned[0], array) self.assertNdarraysEqual(returned[1], palette) def test_ndarray_nv12(self): array = numpy.random.randint(0, 256, size=(720, 640), dtype=numpy.uint8) frame = VideoFrame.from_ndarray(array, format="nv12") self.assertEqual(frame.width, 640) self.assertEqual(frame.height, 480) self.assertEqual(frame.format.name, "nv12") self.assertNdarraysEqual(frame.to_ndarray(), array) def test_ndarray_nv12_align(self): array = numpy.random.randint(0, 256, size=(357, 318), dtype=numpy.uint8) frame = VideoFrame.from_ndarray(array, format="nv12") self.assertEqual(frame.width, 318) self.assertEqual(frame.height, 238) self.assertEqual(frame.format.name, "nv12") self.assertNdarraysEqual(frame.to_ndarray(), array) class TestVideoFrameNumpyBuffer(TestCase): def test_shares_memory_gray(self): array = numpy.random.randint(0, 256, size=(357, 318), dtype=numpy.uint8) frame = VideoFrame.from_numpy_buffer(array, "gray") self.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 self.assertNdarraysEqual(frame.to_ndarray(), array) def test_shares_memory_gray8(self): array = numpy.random.randint(0, 256, size=(357, 318), dtype=numpy.uint8) frame = VideoFrame.from_numpy_buffer(array, "gray8") self.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 self.assertNdarraysEqual(frame.to_ndarray(), array) def test_shares_memory_rgb8(self): array = numpy.random.randint(0, 256, size=(357, 318), dtype=numpy.uint8) frame = VideoFrame.from_numpy_buffer(array, "rgb8") self.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 self.assertNdarraysEqual(frame.to_ndarray(), array) def test_shares_memory_bgr8(self): array = numpy.random.randint(0, 256, size=(357, 318), dtype=numpy.uint8) frame = VideoFrame.from_numpy_buffer(array, "bgr8") self.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 self.assertNdarraysEqual(frame.to_ndarray(), array) def test_shares_memory_rgb24(self): array = numpy.random.randint(0, 256, size=(357, 318, 3), dtype=numpy.uint8) frame = VideoFrame.from_numpy_buffer(array, "rgb24") self.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 self.assertNdarraysEqual(frame.to_ndarray(), array) def test_shares_memory_yuv420p(self): array = numpy.random.randint( 0, 256, size=(512 * 6 // 4, 256), dtype=numpy.uint8 ) frame = VideoFrame.from_numpy_buffer(array, "yuv420p") self.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 self.assertNdarraysEqual(frame.to_ndarray(), array) def test_shares_memory_yuvj420p(self): array = numpy.random.randint( 0, 256, size=(512 * 6 // 4, 256), dtype=numpy.uint8 ) frame = VideoFrame.from_numpy_buffer(array, "yuvj420p") self.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 self.assertNdarraysEqual(frame.to_ndarray(), array) def test_shares_memory_nv12(self): array = numpy.random.randint( 0, 256, size=(512 * 6 // 4, 256), dtype=numpy.uint8 ) frame = VideoFrame.from_numpy_buffer(array, "nv12") self.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 self.assertNdarraysEqual(frame.to_ndarray(), array) def test_shares_memory_bgr24(self): array = numpy.random.randint(0, 256, size=(357, 318, 3), dtype=numpy.uint8) frame = VideoFrame.from_numpy_buffer(array, "bgr24") self.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 self.assertNdarraysEqual(frame.to_ndarray(), array) class TestVideoFrameTiming(TestCase): def test_reformat_pts(self): frame = VideoFrame(640, 480, "rgb24") frame.pts = 123 frame.time_base = "456/1" # Just to be different. frame = frame.reformat(320, 240) self.assertEqual(frame.pts, 123) self.assertEqual(frame.time_base, 456) class TestVideoFrameReformat(TestCase): def test_reformat_identity(self): frame1 = VideoFrame(640, 480, "rgb24") frame2 = frame1.reformat(640, 480, "rgb24") self.assertIs(frame1, frame2) def test_reformat_colourspace(self): # This is allowed. frame = VideoFrame(640, 480, "rgb24") frame.reformat(src_colorspace=None, dst_colorspace="smpte240") # I thought this was not allowed, but it seems to be. frame = VideoFrame(640, 480, "yuv420p") frame.reformat(src_colorspace=None, dst_colorspace="smpte240") def test_reformat_pixel_format_align(self): 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") self.assertNdarraysEqual(frame_rgb.to_ndarray(), expected_rgb)