diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml index d3ea994cf..c88cccec4 100644 --- a/.github/workflows/issues.yml +++ b/.github/workflows/issues.yml @@ -7,7 +7,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v5 + - uses: actions/stale@v8 with: stale-issue-label: stale stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.' diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b5e62d012..7931282cf 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,15 +1,19 @@ name: tests - -on: [push, pull_request] - +on: + push: + paths-ignore: + - '**.md' + - '**.rst' + - '**.txt' + pull_request: + paths-ignore: + - '**.md' + - '**.rst' + - '**.txt' jobs: - - style: - name: "${{ matrix.config.suite }}" runs-on: ubuntu-latest - strategy: matrix: config: @@ -19,17 +23,16 @@ jobs: env: PYAV_PYTHON: python3 - PYAV_LIBRARY: ffmpeg-4.0 # doesn't matter + PYAV_LIBRARY: ffmpeg-4.3 # doesn't matter steps: - - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 name: Checkout - name: Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v4 with: - python-version: 3.7 + python-version: 3.8 - name: Environment run: env | sort @@ -45,36 +48,29 @@ jobs: . scripts/activate.sh ./scripts/test ${{ matrix.config.suite }} - 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.7, ffmpeg: "4.4", extras: true} - - {os: ubuntu-latest, python: 3.7, ffmpeg: "4.3"} - - {os: ubuntu-latest, python: 3.7, ffmpeg: "4.2"} - - {os: ubuntu-latest, python: 3.7, ffmpeg: "4.1"} - - {os: ubuntu-latest, python: 3.7, ffmpeg: "4.0"} - - {os: ubuntu-latest, python: pypy3, ffmpeg: "4.4"} - - {os: macos-latest, python: 3.7, ffmpeg: "4.4"} + - {os: ubuntu-latest, python: 3.8, ffmpeg: "6.0", extras: true} + - {os: ubuntu-latest, python: 3.8, ffmpeg: "5.1"} + - {os: ubuntu-latest, python: 3.8, ffmpeg: "5.0"} + - {os: ubuntu-latest, python: pypy3.9, ffmpeg: "5.0"} + - {os: macos-latest, python: 3.8, ffmpeg: "5.0"} env: PYAV_PYTHON: python${{ matrix.config.python }} PYAV_LIBRARY: ffmpeg-${{ matrix.config.ffmpeg }} steps: - - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 name: Checkout - name: Python ${{ matrix.config.python }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.config.python }} @@ -139,24 +135,19 @@ jobs: scripts/test sdist 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.7, ffmpeg: "4.3"} - - {os: windows-latest, python: 3.7, ffmpeg: "4.2"} - - {os: windows-latest, python: 3.7, ffmpeg: "4.1"} - - {os: windows-latest, python: 3.7, ffmpeg: "4.0"} + - {os: windows-latest, python: 3.8, ffmpeg: "5.1"} + - {os: windows-latest, python: 3.8, ffmpeg: "5.0"} steps: - - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up Conda shell: bash @@ -166,17 +157,24 @@ jobs: conda config --add channels conda-forge conda create -q -n pyav \ cython \ - ffmpeg=${{ matrix.config.ffmpeg }} \ numpy \ pillow \ python=${{ matrix.config.python }} \ setuptools + if [[ "${{ matrix.config.ffmpeg }}" == "5.1" ]]; then + curl -L -o ffmpeg.tar.gz https://github.com/PyAV-Org/pyav-ffmpeg/releases/download/5.1.2-1/ffmpeg-win_amd64.tar.gz + elif [[ "${{ matrix.config.ffmpeg }}" == "5.0" ]]; then + curl -L -o ffmpeg.tar.gz https://github.com/PyAV-Org/pyav-ffmpeg/releases/download/5.0.1-1/ffmpeg-win_amd64.tar.gz + else + exit 1 + fi - name: Build shell: bash run: | . $CONDA/etc/profile.d/conda.sh conda activate pyav + tar -xf ffmpeg.tar.gz -C $CONDA_PREFIX/Library/ python setup.py build_ext --inplace --ffmpeg-dir=$CONDA_PREFIX/Library - name: Test @@ -189,17 +187,17 @@ jobs: package-source: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v1 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 with: - python-version: 3.7 + python-version: 3.8 - name: Build source package run: | pip install cython python scripts/fetch-vendor.py /tmp/vendor PKG_CONFIG_PATH=/tmp/vendor/lib/pkgconfig python setup.py sdist - name: Upload source package - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: name: dist path: dist/ @@ -223,13 +221,13 @@ jobs: - os: windows-latest arch: AMD64 steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v1 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 with: - python-version: 3.7 + python-version: 3.8 - name: Set up QEMU if: matrix.os == 'ubuntu-latest' - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v3 - name: Install packages if: matrix.os == 'macos-latest' run: | @@ -249,13 +247,13 @@ jobs: 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: cp37-* pp* *_i686 + CIBW_TEST_SKIP: pp* *_i686 run: | pip install cibuildwheel delvewheel cibuildwheel --output-dir dist shell: bash - name: Upload wheels - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: name: dist path: dist/ @@ -264,14 +262,14 @@ jobs: runs-on: ubuntu-latest needs: [package-source, package-wheel] steps: - - uses: actions/checkout@v2 - - uses: actions/download-artifact@v1 + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v3 with: name: dist path: dist/ - name: Publish to PyPI if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/') - uses: pypa/gh-action-pypi-publish@master + uses: pypa/gh-action-pypi-publish@release/v1 with: user: __token__ password: ${{ secrets.PYPI_TOKEN }} diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d64fe2b7f..af5416bce 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,6 +16,45 @@ We are operating with `semantic versioning `_. Note that they these tags will not actually close the issue/PR until they are merged into the "default" branch. +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 ------ diff --git a/av/__init__.py b/av/__init__.py index 237cb8b94..8c87e17cc 100644 --- a/av/__init__.py +++ b/av/__init__.py @@ -1,10 +1,18 @@ -# Add the native FFMPEG and MinGW libraries to executable path, so that the -# AV pyd files can find them. import os +import sys -if os.name == "nt": +# 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.dirname(__file__)) + os.pathsep + 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 diff --git a/av/__main__.py b/av/__main__.py index 8c57e2dd9..b5718ba8b 100644 --- a/av/__main__.py +++ b/av/__main__.py @@ -2,7 +2,6 @@ def main(): - parser = argparse.ArgumentParser() parser.add_argument("--codecs", action="store_true") parser.add_argument("--version", action="store_true") @@ -11,7 +10,6 @@ def main(): # --- if args.version: - import av import av._core diff --git a/av/about.py b/av/about.py index ee502a28b..9158871f9 100644 --- a/av/about.py +++ b/av/about.py @@ -1 +1 @@ -__version__ = "9.1.1" +__version__ = "10.0.0" diff --git a/av/audio/codeccontext.pyx b/av/audio/codeccontext.pyx index c81a49dd8..8446fbcd0 100644 --- a/av/audio/codeccontext.pyx +++ b/av/audio/codeccontext.pyx @@ -3,7 +3,6 @@ cimport 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.error cimport err_check from av.frame cimport Frame from av.packet cimport Packet diff --git a/av/audio/fifo.pyx b/av/audio/fifo.pyx index 6d1d17bd7..9f92f1ff2 100644 --- a/av/audio/fifo.pyx +++ b/av/audio/fifo.pyx @@ -1,6 +1,4 @@ -from av.audio.format cimport get_audio_format from av.audio.frame cimport alloc_audio_frame -from av.audio.layout cimport get_audio_layout from av.error cimport err_check @@ -9,14 +7,21 @@ cdef class AudioFifo: """A simple audio sample FIFO (First In First Out) buffer.""" def __repr__(self): - return '' % ( - self.__class__.__name__, - self.samples, - self.sample_rate, - self.layout, - self.format, - id(self), - ) + try: + result = '' % ( + self.__class__.__name__, + self.samples, + self.sample_rate, + self.layout, + self.format, + id(self), + ) + except AttributeError: + result = '' % ( + self.__class__.__name__, + id(self), + ) + return result def __dealloc__(self): if self.ptr: diff --git a/av/audio/format.pxd b/av/audio/format.pxd index 5090f6886..4160aa85b 100644 --- a/av/audio/format.pxd +++ b/av/audio/format.pxd @@ -1,7 +1,7 @@ cimport libav as lib -cdef class AudioFormat(object): +cdef class AudioFormat: cdef lib.AVSampleFormat sample_fmt diff --git a/av/audio/format.pyx b/av/audio/format.pyx index f2eb72b5b..4c7cd1cdc 100644 --- a/av/audio/format.pyx +++ b/av/audio/format.pyx @@ -17,7 +17,7 @@ cdef AudioFormat get_audio_format(lib.AVSampleFormat c_format): return format -cdef class AudioFormat(object): +cdef class AudioFormat: """Descriptor of audio formats.""" diff --git a/av/audio/frame.pyx b/av/audio/frame.pyx index 97de3cc53..b97d5e043 100644 --- a/av/audio/frame.pyx +++ b/av/audio/frame.pyx @@ -56,7 +56,6 @@ cdef class AudioFrame(Frame): # Audio filters need AVFrame.channels to match number of channels from layout. self.ptr.channels = self.layout.nb_channels - cdef size_t buffer_size if self.layout.channels and nb_samples: # Cleanup the old buffer. diff --git a/av/audio/layout.pxd b/av/audio/layout.pxd index 60c8c953d..c46b9c741 100644 --- a/av/audio/layout.pxd +++ b/av/audio/layout.pxd @@ -1,7 +1,7 @@ from libc.stdint cimport uint64_t -cdef class AudioLayout(object): +cdef class AudioLayout: # The layout for FFMpeg; this is essentially a bitmask of channels. cdef uint64_t layout @@ -17,7 +17,7 @@ cdef class AudioLayout(object): cdef _init(self, uint64_t layout) -cdef class AudioChannel(object): +cdef class AudioChannel: # The channel for FFmpeg. cdef uint64_t channel diff --git a/av/audio/layout.pyx b/av/audio/layout.pyx index d4871553b..84801f47f 100644 --- a/av/audio/layout.pyx +++ b/av/audio/layout.pyx @@ -64,7 +64,7 @@ cdef dict channel_descriptions = { } -cdef class AudioLayout(object): +cdef class AudioLayout: def __init__(self, layout): @@ -105,7 +105,7 @@ cdef class AudioLayout(object): return out -cdef class AudioChannel(object): +cdef class AudioChannel: def __cinit__(self, AudioLayout layout, int index): self.channel = lib.av_channel_layout_extract_channel(layout.layout, index) diff --git a/av/audio/plane.pyx b/av/audio/plane.pyx index 50fe0aa59..92c508cbd 100644 --- a/av/audio/plane.pyx +++ b/av/audio/plane.pyx @@ -1,5 +1,3 @@ -cimport libav as lib - from av.audio.frame cimport AudioFrame diff --git a/av/audio/resampler.pxd b/av/audio/resampler.pxd index 4fe78b54a..d3601403d 100644 --- a/av/audio/resampler.pxd +++ b/av/audio/resampler.pxd @@ -4,7 +4,7 @@ from av.audio.layout cimport AudioLayout from av.filter.graph cimport Graph -cdef class AudioResampler(object): +cdef class AudioResampler: cdef readonly bint is_passthrough diff --git a/av/audio/resampler.pyx b/av/audio/resampler.pyx index b1c6c0aad..1214da317 100644 --- a/av/audio/resampler.pyx +++ b/av/audio/resampler.pyx @@ -7,7 +7,7 @@ import errno import av.filter -cdef class AudioResampler(object): +cdef class AudioResampler: """AudioResampler(format=None, layout=None, rate=None) diff --git a/av/buffer.pxd b/av/buffer.pxd index 199d2cc8b..cfab07ca0 100644 --- a/av/buffer.pxd +++ b/av/buffer.pxd @@ -1,5 +1,5 @@ -cdef class Buffer(object): +cdef class Buffer: cdef size_t _buffer_size(self) cdef void* _buffer_ptr(self) diff --git a/av/buffer.pyx b/av/buffer.pyx index 8176e1565..5affc81f0 100644 --- a/av/buffer.pyx +++ b/av/buffer.pyx @@ -5,7 +5,7 @@ from av import deprecation from av.bytesource cimport ByteSource, bytesource -cdef class Buffer(object): +cdef class Buffer: """A base class for PyAV objects which support the buffer protocol, such as :class:`.Packet` and :class:`.Plane`. diff --git a/av/bytesource.pxd b/av/bytesource.pxd index 68a6cca0f..050baab35 100644 --- a/av/bytesource.pxd +++ b/av/bytesource.pxd @@ -1,7 +1,7 @@ from cpython.buffer cimport Py_buffer -cdef class ByteSource(object): +cdef class ByteSource: cdef object owner diff --git a/av/bytesource.pyx b/av/bytesource.pyx index fd29bcf06..ec4138e72 100644 --- a/av/bytesource.pyx +++ b/av/bytesource.pyx @@ -6,7 +6,7 @@ from cpython.buffer cimport ( ) -cdef class ByteSource(object): +cdef class ByteSource: def __cinit__(self, owner): self.owner = owner diff --git a/av/codec/codec.pxd b/av/codec/codec.pxd index 173f0ef18..b9925df13 100644 --- a/av/codec/codec.pxd +++ b/av/codec/codec.pxd @@ -1,7 +1,7 @@ cimport libav as lib -cdef class Codec(object): +cdef class Codec: cdef const lib.AVCodec *ptr cdef const lib.AVCodecDescriptor *desc diff --git a/av/codec/codec.pyx b/av/codec/codec.pyx index 4bbbcf369..a7002acc2 100644 --- a/av/codec/codec.pyx +++ b/av/codec/codec.pyx @@ -1,7 +1,7 @@ from 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, flag_in_bitfield +from av.utils cimport avrational_to_fraction from av.video.format cimport get_video_format @@ -52,7 +52,6 @@ Capabilities = define_enum('Capabilities', 'av.codec', ( """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."""), - ('TRUNCATED', lib.AV_CODEC_CAP_TRUNCATED), ('HWACCEL', 1 << 4), ('DELAY', lib.AV_CODEC_CAP_DELAY, """Encoder or decoder requires flushing with NULL input at the end in order to @@ -102,8 +101,10 @@ Capabilities = define_enum('Capabilities', 'av.codec', ( """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_AUTO_THREADS, - """Codec supports avctx->thread_count == 0 (auto)."""), + ('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, @@ -114,10 +115,6 @@ Capabilities = define_enum('Capabilities', 'av.codec', ( the stream. A decoder marked with this flag should only be used as last resort choice for probing."""), - ('INTRA_ONLY', lib.AV_CODEC_CAP_INTRA_ONLY, - """Codec is intra only."""), - ('LOSSLESS', lib.AV_CODEC_CAP_LOSSLESS, - """Codec is lossless."""), ('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 @@ -130,6 +127,10 @@ Capabilities = define_enum('Capabilities', 'av.codec', ( """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) @@ -137,7 +138,7 @@ class UnknownCodecError(ValueError): pass -cdef class Codec(object): +cdef class Codec: """Codec(name, mode='r') @@ -308,7 +309,6 @@ cdef class Codec(object): draw_horiz_band = capabilities.flag_property('DRAW_HORIZ_BAND') dr1 = capabilities.flag_property('DR1') - truncated = capabilities.flag_property('TRUNCATED') hwaccel = capabilities.flag_property('HWACCEL') delay = capabilities.flag_property('DELAY') small_last_frame = capabilities.flag_property('SMALL_LAST_FRAME') @@ -328,6 +328,7 @@ cdef class Codec(object): 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(): diff --git a/av/codec/context.pxd b/av/codec/context.pxd index d9b6906f9..6cc8bd899 100644 --- a/av/codec/context.pxd +++ b/av/codec/context.pxd @@ -7,13 +7,10 @@ from av.frame cimport Frame from av.packet cimport Packet -cdef class CodecContext(object): +cdef class CodecContext: cdef lib.AVCodecContext *ptr - # Whether the AVCodecContext should be de-allocated upon destruction. - cdef bint allocated - # Whether AVCodecContext.extradata should be de-allocated upon destruction. cdef bint extradata_set @@ -64,4 +61,4 @@ cdef class CodecContext(object): cdef Frame _alloc_next_frame(self) -cdef CodecContext wrap_codec_context(lib.AVCodecContext*, const lib.AVCodec*, bint allocated) +cdef CodecContext wrap_codec_context(lib.AVCodecContext*, const lib.AVCodec*) diff --git a/av/codec/context.pyx b/av/codec/context.pyx index fd3b26fe7..bc8b35d57 100644 --- a/av/codec/context.pyx +++ b/av/codec/context.pyx @@ -1,5 +1,7 @@ +import warnings + from libc.errno cimport EAGAIN -from libc.stdint cimport int64_t, uint8_t +from libc.stdint cimport uint8_t from libc.string cimport memcpy cimport libav as lib @@ -11,13 +13,14 @@ 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, bint allocated): +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 @@ -35,7 +38,6 @@ cdef CodecContext wrap_codec_context(lib.AVCodecContext *c_ctx, const lib.AVCode else: py_ctx = CodecContext(_cinit_sentinel) - py_ctx.allocated = allocated py_ctx._init(c_ctx, c_codec) return py_ctx @@ -94,9 +96,6 @@ Flags = define_enum('Flags', __name__, ( """Only decode/encode grayscale."""), ('PSNR', lib.AV_CODEC_FLAG_PSNR, """error[?] variables will be set during encoding."""), - ('TRUNCATED', lib.AV_CODEC_FLAG_TRUNCATED, - """Input bitstream might be truncated at a random location - instead of only at frame boundaries."""), ('INTERLACED_DCT', lib.AV_CODEC_FLAG_INTERLACED_DCT, """Use interlaced DCT."""), ('LOW_DELAY', lib.AV_CODEC_FLAG_LOW_DELAY, @@ -120,8 +119,6 @@ Flags2 = define_enum('Flags2', __name__, ( """Skip bitstream encoding."""), ('LOCAL_HEADER', lib.AV_CODEC_FLAG2_LOCAL_HEADER, """Place global headers at every keyframe instead of in extradata."""), - ('DROP_FRAME_TIMECODE', lib.AV_CODEC_FLAG2_DROP_FRAME_TIMECODE, - """Timecode is in drop frame format. DEPRECATED!!!!"""), ('CHUNKS', lib.AV_CODEC_FLAG2_CHUNKS, """Input bitstream might be truncated at a packet boundaries instead of only at frame boundaries."""), @@ -138,13 +135,13 @@ Flags2 = define_enum('Flags2', __name__, ( ), is_flags=True) -cdef class CodecContext(object): +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, True) + return wrap_codec_context(c_ctx, cy_codec.ptr) def __cinit__(self, sentinel=None, *args, **kwargs): if sentinel is not _cinit_sentinel: @@ -166,10 +163,6 @@ cdef class CodecContext(object): self.ptr.thread_count = 0 self.ptr.thread_type = 2 - # Use "ass" format for subtitles (default as of FFmpeg 5.0), not the - # deprecated "ass_with_timings" formats. - self.ptr.sub_text_format = 0 - def _get_flags(self): return self.ptr.flags @@ -193,7 +186,6 @@ cdef class CodecContext(object): loop_filter = flags.flag_property('LOOP_FILTER') gray = flags.flag_property('GRAY') psnr = flags.flag_property('PSNR') - truncated = flags.flag_property('TRUNCATED') interlaced_dct = flags.flag_property('INTERLACED_DCT') low_delay = flags.flag_property('LOW_DELAY') global_header = flags.flag_property('GLOBAL_HEADER') @@ -217,7 +209,6 @@ cdef class CodecContext(object): fast = flags2.flag_property('FAST') no_output = flags2.flag_property('NO_OUTPUT') local_header = flags2.flag_property('LOCAL_HEADER') - drop_frame_timecode = flags2.flag_property('DROP_FRAME_TIMECODE') chunks = flags2.flag_property('CHUNKS') ignore_crop = flags2.flag_property('IGNORE_CROP') show_all = flags2.flag_property('SHOW_ALL') @@ -282,8 +273,8 @@ cdef class CodecContext(object): cdef _Dictionary options = Dictionary() options.update(self.options or {}) - # Assert we have a time_base. - if not self.ptr.time_base.num: + # 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)) @@ -304,7 +295,7 @@ cdef class CodecContext(object): def __dealloc__(self): if self.ptr and self.extradata_set: lib.av_freep(&self.ptr.extradata) - if self.ptr and self.allocated: + if self.ptr: lib.avcodec_close(self.ptr) lib.avcodec_free_context(&self.ptr) if self.parser: @@ -551,9 +542,19 @@ cdef class CodecContext(object): property time_base: def __get__(self): + if self.is_decoder: + warnings.warn( + "Using CodecContext.time_base for decoders is deprecated.", + AVDeprecationWarning + ) return avrational_to_fraction(&self.ptr.time_base) def __set__(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 codec_tag: diff --git a/av/container/core.pxd b/av/container/core.pxd index 198c96fa8..fb7c3b511 100644 --- a/av/container/core.pxd +++ b/av/container/core.pxd @@ -13,7 +13,7 @@ ctypedef struct timeout_info: double timeout -cdef class Container(object): +cdef class Container: cdef readonly bint writeable cdef lib.AVFormatContext *ptr diff --git a/av/container/core.pyx b/av/container/core.pyx index d21893c43..5b1711a53 100755 --- a/av/container/core.pyx +++ b/av/container/core.pyx @@ -1,6 +1,5 @@ from cython.operator cimport dereference from libc.stdint cimport int64_t -from libc.stdlib cimport free, malloc import os import time @@ -20,16 +19,13 @@ from av.dictionary import Dictionary from av.logging import Capture as LogCapture -ctypedef int64_t (*seek_func_t)(void *opaque, int64_t offset, int whence) nogil - - 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) nogil: +cdef int interrupt_cb (void *p) noexcept nogil: cdef timeout_info info = dereference( p) if info.timeout < 0: # timeout < 0 means no timeout @@ -56,7 +52,7 @@ cdef int pyav_io_open(lib.AVFormatContext *s, lib.AVIOContext **pb, const char *url, int flags, - lib.AVDictionary **options) nogil: + lib.AVDictionary **options) noexcept nogil: with gil: return pyav_io_open_gil(s, pb, url, flags, options) @@ -65,7 +61,7 @@ cdef int pyav_io_open_gil(lib.AVFormatContext *s, lib.AVIOContext **pb, const char *url, int flags, - lib.AVDictionary **options): + lib.AVDictionary **options) noexcept: cdef Container container cdef object file cdef PyIOFile pyio_file @@ -104,13 +100,13 @@ cdef int pyav_io_open_gil(lib.AVFormatContext *s, cdef void pyav_io_close(lib.AVFormatContext *s, - lib.AVIOContext *pb) nogil: + 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): + lib.AVIOContext *pb) noexcept: cdef Container container try: container = dereference(s).opaque @@ -157,8 +153,6 @@ Flags = define_enum('Flags', __name__, ( 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)."), - ('PRIV_OPT', lib.AVFMT_FLAG_PRIV_OPT, - "Enable use of private options by delaying codec open (this could be made default once all code is converted)."), ('FAST_SEEK', lib.AVFMT_FLAG_FAST_SEEK, "Enable fast, but inaccurate seeks for some formats."), ('SHORTEST', lib.AVFMT_FLAG_SHORTEST, @@ -168,7 +162,7 @@ Flags = define_enum('Flags', __name__, ( ), is_flags=True) -cdef class Container(object): +cdef class Container: def __cinit__(self, sentinel, file_, format_name, options, container_options, stream_options, @@ -209,7 +203,6 @@ cdef class Container(object): cdef bytes name_obj = os.fsencode(self.name) cdef char *name = name_obj - cdef seek_func_t seek_func = NULL cdef lib.AVOutputFormat *ofmt if self.writeable: @@ -329,7 +322,6 @@ cdef class Container(object): flush_packets = flags.flag_property('FLUSH_PACKETS') bit_exact = flags.flag_property('BITEXACT') sort_dts = flags.flag_property('SORT_DTS') - priv_opt = flags.flag_property('PRIV_OPT') fast_seek = flags.flag_property('FAST_SEEK') shortest = flags.flag_property('SHORTEST') auto_bsf = flags.flag_property('AUTO_BSF') @@ -365,6 +357,7 @@ def open(file, mode=None, format=None, options=None, ``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.:: diff --git a/av/container/input.pyx b/av/container/input.pyx index e0c7dcc22..80cd8f783 100644 --- a/av/container/input.pyx +++ b/av/container/input.pyx @@ -1,6 +1,7 @@ from 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 @@ -22,7 +23,11 @@ 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. @@ -65,7 +70,18 @@ cdef class InputContainer(Container): self.streams = StreamContainer() for i in range(self.ptr.nb_streams): - self.streams.add_stream(wrap_stream(self, self.ptr.streams[i])) + 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) @@ -155,7 +171,7 @@ cdef class InputContainer(Container): 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._stream.time_base + packet._time_base = packet._stream.ptr.time_base yield packet # Flush! @@ -163,7 +179,7 @@ cdef class InputContainer(Container): if include_stream[i]: packet = Packet() packet._stream = self.streams[i] - packet._time_base = packet._stream._stream.time_base + packet._time_base = packet._stream.ptr.time_base yield packet finally: @@ -254,11 +270,11 @@ cdef class InputContainer(Container): self.flush_buffers() cdef flush_buffers(self): - cdef unsigned int i - cdef lib.AVStream *stream - - with nogil: - for i in range(self.ptr.nb_streams): - stream = self.ptr.streams[i] - if stream.codec and stream.codec.codec and stream.codec.codec_id != lib.AV_CODEC_ID_NONE: - lib.avcodec_flush_buffers(stream.codec) + cdef Stream stream + cdef CodecContext codec_context + + for stream in self.streams: + codec_context = stream.codec_context + if codec_context and codec_context.is_open: + with nogil: + lib.avcodec_flush_buffers(codec_context.ptr) diff --git a/av/container/output.pyx b/av/container/output.pyx index 621ac8f18..788b3214d 100644 --- a/av/container/output.pyx +++ b/av/container/output.pyx @@ -1,14 +1,14 @@ -from fractions import Fraction import logging import os 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 +from av.utils cimport dict_to_avdict, to_avrational from av.dictionary import Dictionary @@ -52,6 +52,9 @@ cdef class OutputContainer(Container): 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`. """ @@ -64,14 +67,11 @@ cdef class OutputContainer(Container): if codec_name is not None: codec_obj = codec_name if isinstance(codec_name, Codec) else Codec(codec_name, 'w') - codec = codec_obj.ptr - else: - if not template._codec: - raise ValueError("template has no codec") - if not template._codec_context: + if not template.codec_context: raise ValueError("template has no codec context") - codec = template._codec + codec_obj = template.codec_context.codec + codec = codec_obj.ptr # Assert that this format supports the requested codec. if not lib.avformat_query_codec( @@ -82,16 +82,13 @@ cdef class OutputContainer(Container): raise ValueError("%r format does not support %r codec" % (self.format.name, codec_name)) # Create new stream in the AVFormatContext, set AVCodecContext values. - # As of last check, avformat_new_stream only calls avcodec_alloc_context3 to create - # the context, but doesn't modify it in any other way. Ergo, we can allow CodecContext - # to finish initializing it. lib.avformat_new_stream(self.ptr, codec) cdef lib.AVStream *stream = self.ptr.streams[self.ptr.nb_streams - 1] - cdef lib.AVCodecContext *codec_context = stream.codec # For readability. + cdef lib.AVCodecContext *codec_context = lib.avcodec_alloc_context3(codec) # Copy from the template. if template is not None: - lib.avcodec_copy_context(codec_context, template._codec_context) + 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 @@ -103,11 +100,7 @@ cdef class OutputContainer(Container): codec_context.bit_rate = 1024000 codec_context.bit_rate_tolerance = 128000 codec_context.ticks_per_frame = 1 - - rate = Fraction(rate or 24) - - codec_context.framerate.num = rate.numerator - codec_context.framerate.den = rate.denominator + to_avrational(rate or 24, &codec_context.framerate) stream.avg_frame_rate = codec_context.framerate stream.time_base = codec_context.time_base @@ -125,8 +118,15 @@ cdef class OutputContainer(Container): 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 Stream py_stream = wrap_stream(self, 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: diff --git a/av/container/pyio.pxd b/av/container/pyio.pxd index b2a593b14..e93a11dc8 100644 --- a/av/container/pyio.pxd +++ b/av/container/pyio.pxd @@ -2,18 +2,18 @@ from libc.stdint cimport int64_t, uint8_t cimport libav as lib -cdef int pyio_read(void *opaque, uint8_t *buf, int buf_size) nogil +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) 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) 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(object): +cdef class PyIOFile: # File-like source. cdef readonly object file diff --git a/av/container/pyio.pyx b/av/container/pyio.pyx index 17d977f3e..ed79b44f8 100644 --- a/av/container/pyio.pyx +++ b/av/container/pyio.pyx @@ -4,10 +4,10 @@ cimport libav as lib from av.error cimport stash_exception -ctypedef int64_t (*seek_func_t)(void *opaque, int64_t offset, int whence) nogil +ctypedef int64_t (*seek_func_t)(void *opaque, int64_t offset, int whence) noexcept nogil -cdef class PyIOFile(object): +cdef class PyIOFile: def __cinit__(self, file, buffer_size, writeable=None): @@ -76,11 +76,11 @@ cdef class PyIOFile(object): lib.av_freep(&self.buffer) -cdef int pyio_read(void *opaque, uint8_t *buf, int buf_size) nogil: +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): +cdef int pyio_read_gil(void *opaque, uint8_t *buf, int buf_size) noexcept: cdef PyIOFile self cdef bytes res try: @@ -95,11 +95,11 @@ cdef int pyio_read_gil(void *opaque, uint8_t *buf, int buf_size): return stash_exception() -cdef int pyio_write(void *opaque, uint8_t *buf, int buf_size) nogil: +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): +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 @@ -114,7 +114,7 @@ cdef int pyio_write_gil(void *opaque, uint8_t *buf, int buf_size): return stash_exception() -cdef int64_t pyio_seek(void *opaque, int64_t offset, int whence) nogil: +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. diff --git a/av/container/streams.pxd b/av/container/streams.pxd index 2ae69d84b..bf217d7c6 100644 --- a/av/container/streams.pxd +++ b/av/container/streams.pxd @@ -1,7 +1,7 @@ from av.stream cimport Stream -cdef class StreamContainer(object): +cdef class StreamContainer: cdef list _streams diff --git a/av/container/streams.pyx b/av/container/streams.pyx index 4ed2223d4..8de28a13e 100644 --- a/av/container/streams.pyx +++ b/av/container/streams.pyx @@ -11,7 +11,7 @@ def _flatten(input_): yield x -cdef class StreamContainer(object): +cdef class StreamContainer: """ @@ -37,16 +37,16 @@ cdef class StreamContainer(object): cdef add_stream(self, Stream stream): - assert stream._stream.index == len(self._streams) + assert stream.ptr.index == len(self._streams) self._streams.append(stream) - if stream._codec_context.codec_type == lib.AVMEDIA_TYPE_VIDEO: + if stream.ptr.codecpar.codec_type == lib.AVMEDIA_TYPE_VIDEO: self.video = self.video + (stream, ) - elif stream._codec_context.codec_type == lib.AVMEDIA_TYPE_AUDIO: + elif stream.ptr.codecpar.codec_type == lib.AVMEDIA_TYPE_AUDIO: self.audio = self.audio + (stream, ) - elif stream._codec_context.codec_type == lib.AVMEDIA_TYPE_SUBTITLE: + elif stream.ptr.codecpar.codec_type == lib.AVMEDIA_TYPE_SUBTITLE: self.subtitles = self.subtitles + (stream, ) - elif stream._codec_context.codec_type == lib.AVMEDIA_TYPE_DATA: + elif stream.ptr.codecpar.codec_type == lib.AVMEDIA_TYPE_DATA: self.data = self.data + (stream, ) else: self.other = self.other + (stream, ) diff --git a/av/data/stream.pyx b/av/data/stream.pyx index 698242c51..c019961d0 100644 --- a/av/data/stream.pyx +++ b/av/data/stream.pyx @@ -20,7 +20,7 @@ cdef class DataStream(Stream): property name: def __get__(self): - cdef const lib.AVCodecDescriptor *desc = lib.avcodec_descriptor_get(self._codec_context.codec_id) + cdef const lib.AVCodecDescriptor *desc = lib.avcodec_descriptor_get(self.ptr.codecpar.codec_id) if desc == NULL: return None return desc.name diff --git a/av/datasets.py b/av/datasets.py index 3324ce0bc..b3a5eed85 100644 --- a/av/datasets.py +++ b/av/datasets.py @@ -9,7 +9,6 @@ def iter_data_dirs(check_writable=False): - try: yield os.environ["PYAV_TESTDATA_DIR"] except KeyError: @@ -45,7 +44,6 @@ def iter_data_dirs(check_writable=False): def cached_download(url, name): - """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 diff --git a/av/deprecation.py b/av/deprecation.py index 1e0cbb317..f36d2fe6f 100644 --- a/av/deprecation.py +++ b/av/deprecation.py @@ -21,7 +21,7 @@ class MethodDeprecationWarning(AVDeprecationWarning): warnings.filterwarnings("default", "", AVDeprecationWarning) -class renamed_attr(object): +class renamed_attr: """Proxy for renamed attributes (or methods) on classes. Getting and setting values will be redirected to the provided name, @@ -68,7 +68,7 @@ def __set__(self, instance, value): setattr(instance, self.new_name, value) -class method(object): +class method: def __init__(self, func): functools.update_wrapper(self, func, ("__name__", "__doc__")) self.func = func diff --git a/av/descriptor.pxd b/av/descriptor.pxd index 98b039c5d..404f646af 100644 --- a/av/descriptor.pxd +++ b/av/descriptor.pxd @@ -1,7 +1,7 @@ cimport libav as lib -cdef class Descriptor(object): +cdef class Descriptor: # These are present as: # - AVCodecContext.av_class (same as avcodec_get_class()) diff --git a/av/descriptor.pyx b/av/descriptor.pyx index d945b0ac6..8debfa35e 100644 --- a/av/descriptor.pyx +++ b/av/descriptor.pyx @@ -13,7 +13,7 @@ cdef Descriptor wrap_avclass(const lib.AVClass *ptr): return obj -cdef class Descriptor(object): +cdef class Descriptor: def __cinit__(self, sentinel): if sentinel is not _cinit_sentinel: diff --git a/av/dictionary.pxd b/av/dictionary.pxd index 84cb24068..1c59df448 100644 --- a/av/dictionary.pxd +++ b/av/dictionary.pxd @@ -1,7 +1,7 @@ cimport libav as lib -cdef class _Dictionary(object): +cdef class _Dictionary: cdef lib.AVDictionary *ptr diff --git a/av/dictionary.pyx b/av/dictionary.pyx index d88ccebcd..3ebc09b89 100644 --- a/av/dictionary.pyx +++ b/av/dictionary.pyx @@ -3,7 +3,7 @@ from collections.abc import MutableMapping from av.error cimport err_check -cdef class _Dictionary(object): +cdef class _Dictionary: def __cinit__(self, *args, **kwargs): for arg in args: diff --git a/av/enum.pyx b/av/enum.pyx index 85f1b0748..522948bbb 100644 --- a/av/enum.pyx +++ b/av/enum.pyx @@ -9,9 +9,7 @@ integers for names and values respectively. """ -from collections import OrderedDict import copyreg -import sys cdef sentinel = object() @@ -135,7 +133,7 @@ def _unpickle(mod_name, cls_name, item_name): copyreg.constructor(_unpickle) -cdef class EnumItem(object): +cdef class EnumItem: """ Enumerations are when an attribute may only take on a single value at once, and @@ -322,7 +320,7 @@ cdef class EnumFlag(EnumItem): return bool(self.value) -cdef class EnumProperty(object): +cdef class EnumProperty: cdef object enum cdef object fget diff --git a/av/filter/context.pxd b/av/filter/context.pxd index 3c69185b2..18954fbdd 100644 --- a/av/filter/context.pxd +++ b/av/filter/context.pxd @@ -4,7 +4,7 @@ from av.filter.filter cimport Filter from av.filter.graph cimport Graph -cdef class FilterContext(object): +cdef class FilterContext: cdef lib.AVFilterContext *ptr cdef readonly Graph graph diff --git a/av/filter/context.pyx b/av/filter/context.pyx index 4b7eaed08..4505c7cd3 100644 --- a/av/filter/context.pyx +++ b/av/filter/context.pyx @@ -1,13 +1,11 @@ -from libc.string cimport memcpy - -from av.audio.frame cimport AudioFrame, alloc_audio_frame +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 VideoFrame, alloc_video_frame +from av.video.frame cimport alloc_video_frame cdef object _cinit_sentinel = object() @@ -21,7 +19,7 @@ cdef FilterContext wrap_filter_context(Graph graph, Filter filter, lib.AVFilterC return self -cdef class FilterContext(object): +cdef class FilterContext: def __cinit__(self, sentinel): if sentinel is not _cinit_sentinel: diff --git a/av/filter/filter.pxd b/av/filter/filter.pxd index e3d937e7c..27501ae57 100644 --- a/av/filter/filter.pxd +++ b/av/filter/filter.pxd @@ -3,7 +3,7 @@ cimport libav as lib from av.descriptor cimport Descriptor -cdef class Filter(object): +cdef class Filter: cdef const lib.AVFilter *ptr diff --git a/av/filter/filter.pyx b/av/filter/filter.pyx index f5b5f1eee..57090a47d 100644 --- a/av/filter/filter.pyx +++ b/av/filter/filter.pyx @@ -21,7 +21,7 @@ cpdef enum FilterFlags: SUPPORT_TIMELINE_INTERNAL = lib.AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL -cdef class Filter(object): +cdef class Filter: def __cinit__(self, name): if name is _cinit_sentinel: diff --git a/av/filter/graph.pxd b/av/filter/graph.pxd index e01536527..c9226749c 100644 --- a/av/filter/graph.pxd +++ b/av/filter/graph.pxd @@ -3,7 +3,7 @@ cimport libav as lib from av.filter.context cimport FilterContext -cdef class Graph(object): +cdef class Graph: cdef lib.AVFilterGraph *ptr diff --git a/av/filter/graph.pyx b/av/filter/graph.pyx index bcb49f788..bf71fac9e 100644 --- a/av/filter/graph.pyx +++ b/av/filter/graph.pyx @@ -11,7 +11,7 @@ from av.video.format cimport VideoFormat from av.video.frame cimport VideoFrame -cdef class Graph(object): +cdef class Graph: def __cinit__(self): diff --git a/av/filter/link.pxd b/av/filter/link.pxd index 699838c38..a6a4b1c09 100644 --- a/av/filter/link.pxd +++ b/av/filter/link.pxd @@ -4,7 +4,7 @@ from av.filter.graph cimport Graph from av.filter.pad cimport FilterContextPad -cdef class FilterLink(object): +cdef class FilterLink: cdef readonly Graph graph cdef lib.AVFilterLink *ptr diff --git a/av/filter/link.pyx b/av/filter/link.pyx index 62e6ff8bc..c99e27f3b 100644 --- a/av/filter/link.pyx +++ b/av/filter/link.pyx @@ -6,7 +6,7 @@ from av.filter.graph cimport Graph cdef _cinit_sentinel = object() -cdef class FilterLink(object): +cdef class FilterLink: def __cinit__(self, sentinel): if sentinel is not _cinit_sentinel: diff --git a/av/filter/pad.pxd b/av/filter/pad.pxd index 088c0b0c0..15ac950fc 100644 --- a/av/filter/pad.pxd +++ b/av/filter/pad.pxd @@ -5,7 +5,7 @@ from av.filter.filter cimport Filter from av.filter.link cimport FilterLink -cdef class FilterPad(object): +cdef class FilterPad: cdef readonly Filter filter cdef readonly FilterContext context diff --git a/av/filter/pad.pyx b/av/filter/pad.pyx index 9b2c6b853..482b2fc36 100644 --- a/av/filter/pad.pyx +++ b/av/filter/pad.pyx @@ -4,7 +4,7 @@ from av.filter.link cimport wrap_filter_link cdef object _cinit_sentinel = object() -cdef class FilterPad(object): +cdef class FilterPad: def __cinit__(self, sentinel): if sentinel is not _cinit_sentinel: @@ -80,10 +80,16 @@ cdef tuple alloc_filter_pads(Filter filter, const lib.AVFilterPad *ptr, bint is_ # 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 = (context.ptr.nb_inputs if is_input else context.ptr.nb_outputs) if context is not None else -1 + 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 or count < 0) and lib.avfilter_pad_get_name(ptr, i): + while (i < count): pad = FilterPad(_cinit_sentinel) if context is None else FilterContextPad(_cinit_sentinel) pads.append(pad) pad.filter = filter diff --git a/av/format.pxd b/av/format.pxd index 8165daa4e..31cac50aa 100644 --- a/av/format.pxd +++ b/av/format.pxd @@ -1,7 +1,7 @@ cimport libav as lib -cdef class ContainerFormat(object): +cdef class ContainerFormat: cdef readonly str name diff --git a/av/format.pyx b/av/format.pyx index 05a5402a8..7afe4416f 100644 --- a/av/format.pyx +++ b/av/format.pyx @@ -59,7 +59,7 @@ Flags = define_enum('Flags', __name__, ( ), is_flags=True) -cdef class ContainerFormat(object): +cdef class ContainerFormat: """Descriptor of a container format. diff --git a/av/frame.pxd b/av/frame.pxd index e0d5b4280..34c302536 100644 --- a/av/frame.pxd +++ b/av/frame.pxd @@ -4,7 +4,7 @@ from av.packet cimport Packet from av.sidedata.sidedata cimport _SideDataContainer -cdef class Frame(object): +cdef class Frame: cdef lib.AVFrame *ptr diff --git a/av/frame.pyx b/av/frame.pyx index 3717ddc81..b98624cc5 100644 --- a/av/frame.pyx +++ b/av/frame.pyx @@ -1,11 +1,9 @@ from av.utils cimport avrational_to_fraction, to_avrational -from fractions import Fraction - from av.sidedata.sidedata import SideDataContainer -cdef class Frame(object): +cdef class Frame: """ Base class for audio and video frames. diff --git a/av/logging.pyx b/av/logging.pyx index 1bdb7fab7..131b9a69f 100644 --- a/av/logging.pyx +++ b/av/logging.pyx @@ -32,7 +32,7 @@ API Reference from __future__ import absolute_import -from libc.stdio cimport fprintf, printf, stderr +from libc.stdio cimport fprintf, stderr from libc.stdlib cimport free, malloc cimport libav as lib @@ -168,7 +168,7 @@ cpdef get_last_error(): cdef global_captures = [] cdef thread_captures = {} -cdef class Capture(object): +cdef class Capture: """A context manager for capturing logs. @@ -208,7 +208,7 @@ cdef struct log_context: lib.AVClass *class_ const char *name -cdef const char *log_context_name(void *ptr) nogil: +cdef const char *log_context_name(void *ptr) noexcept nogil: cdef log_context *obj = ptr return obj.name @@ -229,7 +229,7 @@ cpdef log(int level, str name, str message): free(obj) -cdef void log_callback(void *ptr, int level, const char *format, lib.va_list args) nogil: +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 and not print_after_shutdown: diff --git a/av/option.pxd b/av/option.pxd index e455bff03..9087b811c 100644 --- a/av/option.pxd +++ b/av/option.pxd @@ -1,7 +1,7 @@ cimport libav as lib -cdef class BaseOption(object): +cdef class BaseOption: cdef const lib.AVOption *ptr diff --git a/av/option.pyx b/av/option.pyx index 24f945bf2..731a6d508 100644 --- a/av/option.pyx +++ b/av/option.pyx @@ -59,7 +59,7 @@ OptionFlags = define_enum('OptionFlags', __name__, ( ('FILTERING_PARAM', lib.AV_OPT_FLAG_FILTERING_PARAM), ), is_flags=True) -cdef class BaseOption(object): +cdef class BaseOption: def __cinit__(self, sentinel): if sentinel is not _cinit_sentinel: diff --git a/av/packet.pyx b/av/packet.pyx index fae970ee3..0687b2237 100644 --- a/av/packet.pyx +++ b/av/packet.pyx @@ -112,7 +112,7 @@ cdef class Packet(Buffer): def __set__(self, Stream stream): self._stream = stream - self.ptr.stream_index = stream._stream.index + self.ptr.stream_index = stream.ptr.index property time_base: """ diff --git a/av/sidedata/motionvectors.pxd b/av/sidedata/motionvectors.pxd index 3b7f88bc1..993c5e283 100644 --- a/av/sidedata/motionvectors.pxd +++ b/av/sidedata/motionvectors.pxd @@ -10,7 +10,7 @@ cdef class _MotionVectors(SideData): cdef int _len -cdef class MotionVector(object): +cdef class MotionVector: cdef _MotionVectors parent cdef lib.AVMotionVector *ptr diff --git a/av/sidedata/motionvectors.pyx b/av/sidedata/motionvectors.pyx index 35d0e2f33..f1e12d56f 100644 --- a/av/sidedata/motionvectors.pyx +++ b/av/sidedata/motionvectors.pyx @@ -53,7 +53,7 @@ class MotionVectors(_MotionVectors, Sequence): pass -cdef class MotionVector(object): +cdef class MotionVector: def __init__(self, sentinel, _MotionVectors parent, int index): if sentinel is not _cinit_bypass_sentinel: diff --git a/av/sidedata/sidedata.pxd b/av/sidedata/sidedata.pxd index f30d8fef7..ac58f1477 100644 --- a/av/sidedata/sidedata.pxd +++ b/av/sidedata/sidedata.pxd @@ -15,7 +15,7 @@ cdef class SideData(Buffer): cdef SideData wrap_side_data(Frame frame, int index) -cdef class _SideDataContainer(object): +cdef class _SideDataContainer: cdef Frame frame diff --git a/av/sidedata/sidedata.pyx b/av/sidedata/sidedata.pyx index ec7de5997..17bb869cc 100644 --- a/av/sidedata/sidedata.pyx +++ b/av/sidedata/sidedata.pyx @@ -70,7 +70,7 @@ cdef class SideData(Buffer): return Type.get(self.ptr.type) or self.ptr.type -cdef class _SideDataContainer(object): +cdef class _SideDataContainer: def __init__(self, Frame frame): diff --git a/av/stream.pxd b/av/stream.pxd index 4a3cab488..c847f641e 100644 --- a/av/stream.pxd +++ b/av/stream.pxd @@ -1,4 +1,3 @@ -from libc.stdint cimport int64_t cimport libav as lib from av.codec.context cimport CodecContext @@ -7,25 +6,21 @@ from av.frame cimport Frame from av.packet cimport Packet -cdef class Stream(object): +cdef class Stream: + cdef lib.AVStream *ptr # Stream attributes. cdef readonly Container container - - cdef lib.AVStream *_stream cdef readonly dict metadata # CodecContext attributes. - cdef lib.AVCodecContext *_codec_context - cdef const lib.AVCodec *_codec - cdef readonly CodecContext codec_context # Private API. - cdef _init(self, Container, lib.AVStream*) + cdef _init(self, Container, lib.AVStream*, CodecContext) cdef _finalize_for_output(self) cdef _set_time_base(self, value) cdef _set_id(self, value) -cdef Stream wrap_stream(Container, lib.AVStream*) +cdef Stream wrap_stream(Container, lib.AVStream*, CodecContext) diff --git a/av/stream.pyx b/av/stream.pyx index cbab9dde1..f9b6d7ec5 100644 --- a/av/stream.pyx +++ b/av/stream.pyx @@ -1,9 +1,7 @@ -from cpython cimport PyWeakref_NewRef -from libc.stdint cimport int64_t, uint8_t -from libc.string cimport memcpy +import warnings + cimport libav as lib -from av.codec.context cimport wrap_codec_context from av.error cimport err_check from av.packet cimport Packet from av.utils cimport ( @@ -13,11 +11,13 @@ from av.utils cimport ( to_avrational ) +from av.deprecation import AVDeprecationWarning + cdef object _cinit_bypass_sentinel = object() -cdef Stream wrap_stream(Container container, lib.AVStream *c_stream): +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 @@ -30,26 +30,26 @@ cdef Stream wrap_stream(Container container, lib.AVStream *c_stream): cdef Stream py_stream - if c_stream.codec.codec_type == lib.AVMEDIA_TYPE_VIDEO: + 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.codec.codec_type == lib.AVMEDIA_TYPE_AUDIO: + 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.codec.codec_type == lib.AVMEDIA_TYPE_SUBTITLE: + 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.codec.codec_type == lib.AVMEDIA_TYPE_DATA: + 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) + py_stream._init(container, c_stream, codec_context) return py_stream -cdef class Stream(object): +cdef class Stream: """ A single stream of audio, video or subtitles within a :class:`.Container`. @@ -69,14 +69,15 @@ cdef class Stream(object): def __cinit__(self, name): if name is _cinit_bypass_sentinel: return - raise RuntimeError('cannot manually instatiate Stream') - - cdef _init(self, Container container, lib.AVStream *stream): + raise RuntimeError('cannot manually instantiate Stream') + cdef _init(self, Container container, lib.AVStream *stream, CodecContext codec_context): self.container = container - self._stream = stream + self.ptr = stream - self._codec_context = stream.codec + self.codec_context = codec_context + if self.codec_context: + self.codec_context.stream_index = stream.index self.metadata = avdict_to_dict( stream.metadata, @@ -84,23 +85,6 @@ cdef class Stream(object): errors=self.container.metadata_errors, ) - # This is an input container! - if self.container.ptr.iformat: - - # Find the codec. - self._codec = lib.avcodec_find_decoder(self._codec_context.codec_id) - if not self._codec: - # TODO: Setup a dummy CodecContext. - self.codec_context = None - return - - # This is an output container! - else: - self._codec = self._codec_context.codec - - self.codec_context = wrap_codec_context(self._codec_context, self._codec, False) - self.codec_context.stream_index = stream.index - def __repr__(self): return '' % ( self.__class__.__name__, @@ -111,23 +95,24 @@ cdef class Stream(object): ) def __getattr__(self, name): - # avoid an infinite loop for unsupported codecs - if self.codec_context is None: - return - - try: + # Deprecate framerate pass-through as it is not always set. + # See: https://github.com/PyAV-Org/PyAV/issues/1005 + if self.ptr.codecpar.codec_type == lib.AVMEDIA_TYPE_VIDEO and name in ("framerate", "rate"): + warnings.warn( + "VideoStream.%s is deprecated as it is not always set; please use VideoStream.average_rate." % name, + AVDeprecationWarning + ) + + # Convenience getter for codec context properties. + if self.codec_context is not None: return getattr(self.codec_context, name) - except AttributeError: - try: - return getattr(self.codec_context.codec, name) - except AttributeError: - raise AttributeError(name) 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) @@ -137,17 +122,17 @@ cdef class Stream(object): cdef _finalize_for_output(self): dict_to_avdict( - &self._stream.metadata, self.metadata, + &self.ptr.metadata, self.metadata, encoding=self.container.metadata_encoding, errors=self.container.metadata_errors, ) - if not self._stream.time_base.num: - self._stream.time_base = self._codec_context.time_base + 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._stream.codecpar, self._stream.codec)) + err_check(lib.avcodec_parameters_from_context(self.ptr.codecpar, self.codec_context.ptr)) def encode(self, frame=None): """ @@ -165,7 +150,7 @@ cdef class Stream(object): cdef Packet packet for packet in packets: packet._stream = self - packet.ptr.stream_index = self._stream.index + packet.ptr.stream_index = self.ptr.index return packets def decode(self, packet=None): @@ -190,16 +175,16 @@ cdef class Stream(object): """ def __get__(self): - return self._stream.id + return self.ptr.id cdef _set_id(self, value): """ Setter used by __setattr__ for the id property. """ if value is None: - self._stream.id = 0 + self.ptr.id = 0 else: - self._stream.id = value + self.ptr.id = value property profile: """ @@ -208,8 +193,8 @@ cdef class Stream(object): :type: str """ def __get__(self): - if self._codec and lib.av_get_profile_name(self._codec, self._codec_context.profile): - return lib.av_get_profile_name(self._codec, self._codec_context.profile) + if self.codec_context: + return self.codec_context.profile else: return None @@ -219,7 +204,7 @@ cdef class Stream(object): :type: int """ - def __get__(self): return self._stream.index + def __get__(self): return self.ptr.index property time_base: """ @@ -229,13 +214,13 @@ cdef class Stream(object): """ def __get__(self): - return avrational_to_fraction(&self._stream.time_base) + 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._stream.time_base) + to_avrational(value, &self.ptr.time_base) property average_rate: """ @@ -249,7 +234,7 @@ cdef class Stream(object): """ def __get__(self): - return avrational_to_fraction(&self._stream.avg_frame_rate) + return avrational_to_fraction(&self.ptr.avg_frame_rate) property base_rate: """ @@ -263,7 +248,7 @@ cdef class Stream(object): """ def __get__(self): - return avrational_to_fraction(&self._stream.r_frame_rate) + return avrational_to_fraction(&self.ptr.r_frame_rate) property guessed_rate: """The guessed frame rate of this stream. @@ -276,7 +261,7 @@ cdef class Stream(object): """ def __get__(self): # The two NULL arguments aren't used in FFmpeg >= 4.0 - cdef lib.AVRational val = lib.av_guess_frame_rate(NULL, self._stream, NULL) + cdef lib.AVRational val = lib.av_guess_frame_rate(NULL, self.ptr, NULL) return avrational_to_fraction(&val) property start_time: @@ -287,8 +272,8 @@ cdef class Stream(object): :type: :class:`int` or ``None`` """ def __get__(self): - if self._stream.start_time != lib.AV_NOPTS_VALUE: - return self._stream.start_time + if self.ptr.start_time != lib.AV_NOPTS_VALUE: + return self.ptr.start_time property duration: """ @@ -298,8 +283,8 @@ cdef class Stream(object): """ def __get__(self): - if self._stream.duration != lib.AV_NOPTS_VALUE: - return self._stream.duration + if self.ptr.duration != lib.AV_NOPTS_VALUE: + return self.ptr.duration property frames: """ @@ -309,7 +294,8 @@ cdef class Stream(object): :type: :class:`int` """ - def __get__(self): return self._stream.nb_frames + def __get__(self): + return self.ptr.nb_frames property language: """ @@ -329,4 +315,4 @@ cdef class Stream(object): :type: str """ - return lib.av_get_media_type_string(self._codec_context.codec_type) + return lib.av_get_media_type_string(self.ptr.codecpar.codec_type) diff --git a/av/subtitles/codeccontext.pyx b/av/subtitles/codeccontext.pyx index a120fc3a5..c3f433abe 100644 --- a/av/subtitles/codeccontext.pyx +++ b/av/subtitles/codeccontext.pyx @@ -1,7 +1,6 @@ cimport libav as lib from av.error cimport err_check -from av.frame cimport Frame from av.packet cimport Packet from av.subtitles.subtitle cimport SubtitleProxy, SubtitleSet diff --git a/av/subtitles/subtitle.pxd b/av/subtitles/subtitle.pxd index ae7bb44b9..e9003ab9b 100644 --- a/av/subtitles/subtitle.pxd +++ b/av/subtitles/subtitle.pxd @@ -3,19 +3,19 @@ cimport libav as lib from av.packet cimport Packet -cdef class SubtitleProxy(object): +cdef class SubtitleProxy: cdef lib.AVSubtitle struct -cdef class SubtitleSet(object): +cdef class SubtitleSet: cdef readonly Packet packet cdef SubtitleProxy proxy cdef readonly tuple rects -cdef class Subtitle(object): +cdef class Subtitle: cdef SubtitleProxy proxy cdef lib.AVSubtitleRect *ptr @@ -31,7 +31,7 @@ cdef class BitmapSubtitle(Subtitle): cdef readonly planes -cdef class BitmapSubtitlePlane(object): +cdef class BitmapSubtitlePlane: cdef readonly BitmapSubtitle subtitle cdef readonly int index diff --git a/av/subtitles/subtitle.pyx b/av/subtitles/subtitle.pyx index 2f22bb0be..1f0e4319a 100644 --- a/av/subtitles/subtitle.pyx +++ b/av/subtitles/subtitle.pyx @@ -1,12 +1,12 @@ from cpython cimport PyBuffer_FillInfo -cdef class SubtitleProxy(object): +cdef class SubtitleProxy: def __dealloc__(self): lib.avsubtitle_free(&self.struct) -cdef class SubtitleSet(object): +cdef class SubtitleSet: def __cinit__(self, SubtitleProxy proxy): self.proxy = proxy @@ -63,7 +63,7 @@ cdef Subtitle build_subtitle(SubtitleSet subtitle, int index): raise ValueError('unknown subtitle type %r' % ptr.type) -cdef class Subtitle(object): +cdef class Subtitle: def __cinit__(self, SubtitleSet subtitle, int index): if index < 0 or index >= subtitle.proxy.struct.num_rects: @@ -131,7 +131,7 @@ cdef class BitmapSubtitle(Subtitle): return self.planes[i] -cdef class BitmapSubtitlePlane(object): +cdef class BitmapSubtitlePlane: def __cinit__(self, BitmapSubtitle subtitle, int index): diff --git a/av/utils.pxd b/av/utils.pxd index 0c943de81..bc5d56927 100644 --- a/av/utils.pxd +++ b/av/utils.pxd @@ -1,4 +1,4 @@ -from libc.stdint cimport int64_t, uint8_t, uint64_t +from libc.stdint cimport uint64_t cimport libav as lib diff --git a/av/utils.pyx b/av/utils.pyx index 1894ce7ab..1b25a38fe 100644 --- a/av/utils.pyx +++ b/av/utils.pyx @@ -1,4 +1,4 @@ -from libc.stdint cimport int64_t, uint8_t, uint64_t +from libc.stdint cimport uint64_t from fractions import Fraction diff --git a/av/video/codeccontext.pyx b/av/video/codeccontext.pyx index 8dac3b3fe..33efc5d54 100644 --- a/av/video/codeccontext.pyx +++ b/av/video/codeccontext.pyx @@ -2,7 +2,6 @@ from libc.stdint cimport int64_t cimport libav as lib from av.codec.context cimport CodecContext -from av.error cimport err_check from av.frame cimport Frame from av.packet cimport Packet from av.utils cimport avrational_to_fraction, to_avrational diff --git a/av/video/format.pxd b/av/video/format.pxd index 372821666..a2efa9d1d 100644 --- a/av/video/format.pxd +++ b/av/video/format.pxd @@ -1,7 +1,7 @@ cimport libav as lib -cdef class VideoFormat(object): +cdef class VideoFormat: cdef lib.AVPixelFormat pix_fmt cdef const lib.AVPixFmtDescriptor *ptr @@ -15,7 +15,7 @@ cdef class VideoFormat(object): cpdef chroma_height(self, int luma_height=?) -cdef class VideoFormatComponent(object): +cdef class VideoFormatComponent: cdef VideoFormat format cdef readonly unsigned int index @@ -24,4 +24,4 @@ cdef class VideoFormatComponent(object): 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 \ No newline at end of file +cdef lib.AVPixelFormat get_pix_fmt(const char *name) except lib.AV_PIX_FMT_NONE diff --git a/av/video/format.pyx b/av/video/format.pyx index b96658272..602ec5275 100644 --- a/av/video/format.pyx +++ b/av/video/format.pyx @@ -19,7 +19,7 @@ cdef lib.AVPixelFormat get_pix_fmt(const char *name) except lib.AV_PIX_FMT_NONE: return pix_fmt -cdef class VideoFormat(object): +cdef class VideoFormat: """ >>> format = VideoFormat('rgb24') @@ -118,7 +118,7 @@ cdef class VideoFormat(object): return -((-luma_height) >> self.ptr.log2_chroma_h) if luma_height else 0 -cdef class VideoFormatComponent(object): +cdef class VideoFormatComponent: def __cinit__(self, VideoFormat format, size_t index): self.format = format diff --git a/av/video/frame.pxd b/av/video/frame.pxd index a08da1ecc..709775e55 100644 --- a/av/video/frame.pxd +++ b/av/video/frame.pxd @@ -1,4 +1,4 @@ -from libc.stdint cimport int16_t, int32_t, uint8_t, uint16_t, uint64_t +from libc.stdint cimport uint8_t cimport libav as lib from av.frame cimport Frame diff --git a/av/video/frame.pyx b/av/video/frame.pyx index 6a3add3e1..971b88ccc 100644 --- a/av/video/frame.pyx +++ b/av/video/frame.pyx @@ -5,7 +5,7 @@ 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 VideoFormat, get_pix_fmt, get_video_format +from av.video.format cimport get_pix_fmt, get_video_format from av.video.plane cimport VideoPlane @@ -66,10 +66,10 @@ cdef useful_array(VideoPlane plane, unsigned int bytes_per_pixel=1, str dtype='u 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.dtype(dtype)) + 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 + return arr.view(np.dtype(dtype)) cdef class VideoFrame(Frame): @@ -269,10 +269,34 @@ cdef class VideoFrame(Frame): 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'): @@ -298,6 +322,11 @@ cdef class VideoFrame(Frame): 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('Conversion to numpy array with format `%s` is not yet supported' % frame.format.name) @@ -349,11 +378,48 @@ cdef class VideoFrame(Frame): 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) @@ -379,6 +445,17 @@ cdef class VideoFrame(Frame): 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('Conversion from numpy array with format `%s` is not yet supported' % format) diff --git a/av/video/reformatter.pxd b/av/video/reformatter.pxd index 25135c27a..ee7467898 100644 --- a/av/video/reformatter.pxd +++ b/av/video/reformatter.pxd @@ -3,7 +3,7 @@ cimport libav as lib from av.video.frame cimport VideoFrame -cdef class VideoReformatter(object): +cdef class VideoReformatter: cdef lib.SwsContext *ptr diff --git a/av/video/reformatter.pyx b/av/video/reformatter.pyx index 3ad995fb9..1d3f08065 100644 --- a/av/video/reformatter.pyx +++ b/av/video/reformatter.pyx @@ -42,7 +42,7 @@ Colorspace = define_enum('Colorspace', __name__, ( )) -cdef class VideoReformatter(object): +cdef class VideoReformatter: """An object for reformatting size and pixel format of :class:`.VideoFrame`. diff --git a/av/video/stream.pyx b/av/video/stream.pyx index 70b8f3209..8694b63ba 100644 --- a/av/video/stream.pyx +++ b/av/video/stream.pyx @@ -6,7 +6,7 @@ cdef class VideoStream(Stream): self.index, self.name, self.format.name if self.format else None, - self._codec_context.width, - self._codec_context.height, + self.codec_context.width, + self.codec_context.height, id(self), ) diff --git a/docs/api/time.rst b/docs/api/time.rst index c0234e0f6..35e4cfc85 100644 --- a/docs/api/time.rst +++ b/docs/api/time.rst @@ -29,9 +29,6 @@ Time is expressed as integer multiples of arbitrary units of time called a ``tim >>> video.time_base Fraction(1, 25) - >>> video.codec_context.time_base - Fraction(1, 50) - Attributes that represent time on those objects will be in that object's ``time_base``: .. doctest:: diff --git a/docs/development/includes.py b/docs/development/includes.py index b60629fda..8f350a81e 100644 --- a/docs/development/includes.py +++ b/docs/development/includes.py @@ -5,7 +5,7 @@ import xml.etree.ElementTree as etree -from Cython.Compiler.Main import compile_single, CompilationOptions +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 @@ -107,9 +107,16 @@ def extract(path, **kwargs): c_string_encoding='ascii', ) - context = options.create_context() + context = Context( + options.include_path, + options.compiler_directives, + options.cplus, + options.language_level, + options=options, + ) - tree = parse_from_strings(name, open(path).read(), context, + tree = parse_from_strings( + name, open(path).read(), context, level='module_pxd' if path.endswith('.pxd') else None, **kwargs) diff --git a/docs/overview/installation.rst b/docs/overview/installation.rst index b019e1e30..72afe8d52 100644 --- a/docs/overview/installation.rst +++ b/docs/overview/installation.rst @@ -11,11 +11,10 @@ Since release 8.0.0 binary wheels are provided on PyPI for Linux, Mac and Window pip install av -Currently FFmpeg 4.4.1 is used with the following features enabled for all platforms: +Currently FFmpeg 5.1.2 is used with the following features enabled for all platforms: - fontconfig - gmp -- gnutls - libaom - libass - libbluray @@ -38,6 +37,11 @@ Currently FFmpeg 4.4.1 is used with the following features enabled for all platf - lzma - zlib +The following additional features are also enabled on Linux: + +- gnutls +- libxcb + Conda ----- @@ -52,7 +56,7 @@ See the `Conda quick install `_ docs t Bring your own FFmpeg --------------------- -PyAV can also be compiled against your own build of FFmpeg ((version ``4.0`` or higher). You can force installing PyAV from source by running: +PyAV can also be compiled against your own build of FFmpeg ((version ``5.0`` or higher). You can force installing PyAV from source by running: .. code-block:: bash diff --git a/examples/basics/parse.py b/examples/basics/parse.py index 2d313cb3f..42a8af160 100644 --- a/examples/basics/parse.py +++ b/examples/basics/parse.py @@ -29,14 +29,12 @@ 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) diff --git a/examples/basics/remux.py b/examples/basics/remux.py index c4779f4e9..feb25cbcd 100644 --- a/examples/basics/remux.py +++ b/examples/basics/remux.py @@ -11,7 +11,6 @@ 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. diff --git a/examples/basics/save_keyframes.py b/examples/basics/save_keyframes.py index 1169c153a..8d31aa621 100644 --- a/examples/basics/save_keyframes.py +++ b/examples/basics/save_keyframes.py @@ -9,7 +9,6 @@ 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`. diff --git a/examples/numpy/barcode.py b/examples/numpy/barcode.py index 64c13e198..2a3dfff0d 100644 --- a/examples/numpy/barcode.py +++ b/examples/numpy/barcode.py @@ -12,7 +12,6 @@ columns = [] for frame in container.decode(video=0): - print(frame) array = frame.to_ndarray(format="rgb24") diff --git a/examples/numpy/generate_video.py b/examples/numpy/generate_video.py index e0c9a9997..250a0bcc1 100644 --- a/examples/numpy/generate_video.py +++ b/examples/numpy/generate_video.py @@ -15,14 +15,12 @@ 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) - img = np.clip(img, 0, 255) frame = av.VideoFrame.from_ndarray(img, format="rgb24") for packet in stream.encode(frame): diff --git a/examples/numpy/generate_video_with_pts.py b/examples/numpy/generate_video_with_pts.py index 58ca547e2..c570a07d2 100644 --- a/examples/numpy/generate_video_with_pts.py +++ b/examples/numpy/generate_video_with_pts.py @@ -43,7 +43,6 @@ 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) diff --git a/include/libavcodec/avcodec.pxd b/include/libavcodec/avcodec.pxd index 8c0a9685b..11d968b1a 100644 --- a/include/libavcodec/avcodec.pxd +++ b/include/libavcodec/avcodec.pxd @@ -1,9 +1,4 @@ -from libc.stdint cimport ( - uint8_t, int8_t, - uint16_t, int16_t, - uint32_t, int32_t, - uint64_t, int64_t -) +from libc.stdint cimport int8_t, int64_t, uint16_t, uint32_t cdef extern from "libavcodec/avcodec.h" nogil: @@ -39,7 +34,6 @@ cdef extern from "libavcodec/avcodec.h" nogil: cdef enum: AV_CODEC_CAP_DRAW_HORIZ_BAND AV_CODEC_CAP_DR1 - AV_CODEC_CAP_TRUNCATED # AV_CODEC_CAP_HWACCEL AV_CODEC_CAP_DELAY AV_CODEC_CAP_SMALL_LAST_FRAME @@ -51,11 +45,9 @@ cdef extern from "libavcodec/avcodec.h" nogil: AV_CODEC_CAP_FRAME_THREADS AV_CODEC_CAP_SLICE_THREADS AV_CODEC_CAP_PARAM_CHANGE - AV_CODEC_CAP_AUTO_THREADS + AV_CODEC_CAP_OTHER_THREADS AV_CODEC_CAP_VARIABLE_FRAME_SIZE AV_CODEC_CAP_AVOID_PROBING - AV_CODEC_CAP_INTRA_ONLY - AV_CODEC_CAP_LOSSLESS AV_CODEC_CAP_HARDWARE AV_CODEC_CAP_HYBRID AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE @@ -76,7 +68,6 @@ cdef extern from "libavcodec/avcodec.h" nogil: AV_CODEC_FLAG_LOOP_FILTER AV_CODEC_FLAG_GRAY AV_CODEC_FLAG_PSNR - AV_CODEC_FLAG_TRUNCATED AV_CODEC_FLAG_INTERLACED_DCT AV_CODEC_FLAG_LOW_DELAY AV_CODEC_FLAG_GLOBAL_HEADER @@ -89,7 +80,6 @@ cdef extern from "libavcodec/avcodec.h" nogil: AV_CODEC_FLAG2_FAST AV_CODEC_FLAG2_NO_OUTPUT AV_CODEC_FLAG2_LOCAL_HEADER - AV_CODEC_FLAG2_DROP_FRAME_TIMECODE AV_CODEC_FLAG2_CHUNKS AV_CODEC_FLAG2_IGNORE_CROP AV_CODEC_FLAG2_SHOW_ALL @@ -194,6 +184,7 @@ cdef extern from "libavcodec/avcodec.h" nogil: float rc_min_vbv_overflow_use AVRational framerate + AVRational pkt_timebase AVRational time_base int ticks_per_frame @@ -223,9 +214,6 @@ cdef extern from "libavcodec/avcodec.h" nogil: int frame_size int channel_layout - # Subtitles. - int sub_text_format - #: .. 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) @@ -237,7 +225,6 @@ cdef extern from "libavcodec/avcodec.h" nogil: cdef void avcodec_free_context(AVCodecContext **ctx) cdef AVClass* avcodec_get_class() - cdef int avcodec_copy_context(AVCodecContext *dst, const AVCodecContext *src) cdef struct AVCodecDescriptor: AVCodecID id @@ -455,10 +442,18 @@ cdef extern from "libavcodec/avcodec.h" nogil: cdef struct AVCodecParameters: - pass + 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 + ) diff --git a/include/libavfilter/avfilter.pxd b/include/libavfilter/avfilter.pxd index 41e0e1d1f..e1fd42f45 100644 --- a/include/libavfilter/avfilter.pxd +++ b/include/libavfilter/avfilter.pxd @@ -1,6 +1,14 @@ 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() @@ -12,6 +20,8 @@ cdef extern from "libavfilter/avfilter.h" nogil: 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 diff --git a/include/libavformat/avformat.pxd b/include/libavformat/avformat.pxd index 0a33cf9f6..06029d9f9 100644 --- a/include/libavformat/avformat.pxd +++ b/include/libavformat/avformat.pxd @@ -33,7 +33,6 @@ cdef extern from "libavformat/avformat.h" nogil: int index int id - AVCodecContext *codec AVCodecParameters *codecpar AVRational time_base @@ -147,7 +146,6 @@ cdef extern from "libavformat/avformat.h" nogil: AVFMT_FLAG_FLUSH_PACKETS AVFMT_FLAG_BITEXACT AVFMT_FLAG_SORT_DTS - AVFMT_FLAG_PRIV_OPT AVFMT_FLAG_FAST_SEEK AVFMT_FLAG_SHORTEST AVFMT_FLAG_AUTO_BSF diff --git a/include/libavutil/motion_vector.pxd b/include/libavutil/motion_vector.pxd index bcf61c0d1..457d7149e 100644 --- a/include/libavutil/motion_vector.pxd +++ b/include/libavutil/motion_vector.pxd @@ -1,9 +1,4 @@ -from libc.stdint cimport ( - uint8_t, int8_t, - uint16_t, int16_t, - uint32_t, int32_t, - uint64_t, int64_t -) +from libc.stdint cimport int16_t, int32_t, uint8_t, uint16_t, uint64_t cdef extern from "libavutil/motion_vector.h" nogil: diff --git a/include/libswresample/swresample.pxd b/include/libswresample/swresample.pxd index 703310139..d76b777a3 100644 --- a/include/libswresample/swresample.pxd +++ b/include/libswresample/swresample.pxd @@ -1,5 +1,6 @@ from libc.stdint cimport int64_t, uint8_t + cdef extern from "libswresample/swresample.h" nogil: cdef int swresample_version() diff --git a/scratchpad/audio.py b/scratchpad/audio.py index c5a79f9c0..addae0915 100644 --- a/scratchpad/audio.py +++ b/scratchpad/audio.py @@ -13,7 +13,7 @@ def print_data(frame): data = bytes(plane) print('\tPLANE %d, %d bytes' % (i, len(data))) data = data.encode('hex') - for i in xrange(0, len(data), 128): + for i in range(0, len(data), 128): print('\t\t\t%s' % data[i:i + 128]) diff --git a/scratchpad/cctx_encode.py b/scratchpad/cctx_encode.py index 7885c578e..165203c1c 100644 --- a/scratchpad/cctx_encode.py +++ b/scratchpad/cctx_encode.py @@ -1,16 +1,15 @@ import logging -from PIL import Image, ImageFont, ImageDraw +from PIL import Image, ImageDraw, ImageFont -logging.basicConfig() - -import av from av.codec import CodecContext from av.video import VideoFrame - from tests.common import fate_suite +logging.basicConfig() + + cc = CodecContext.create('flv', 'w') print(cc) @@ -18,8 +17,7 @@ font = ImageFont.truetype("/System/Library/Fonts/Menlo.ttc", 15) - -fh = open('test.flv', 'w') +fh = open('test.flv', 'wb') for i in range(30): @@ -35,7 +33,7 @@ packet = cc.encode(frame) print(' ', packet) - fh.write(str(buffer(packet))) + fh.write(bytes(packet)) print('Flushing...') @@ -44,6 +42,6 @@ if not packet: break print(' ', packet) - fh.write(str(buffer(packet))) + fh.write(bytes(packet)) print('Done!') diff --git a/scratchpad/decode.py b/scratchpad/decode.py index d2dfcc580..edfb413b7 100644 --- a/scratchpad/decode.py +++ b/scratchpad/decode.py @@ -155,7 +155,7 @@ def format_time(time, time_base): data = bytes(plane) print('\t\t\tPLANE %d, %d bytes' % (i, len(data))) data = data.encode('hex') - for i in xrange(0, len(data), 128): + for i in range(0, len(data), 128): print('\t\t\t%s' % data[i:i + 128]) if args.count and frame_count >= args.count: diff --git a/scratchpad/resource_use.py b/scratchpad/resource_use.py index b61fc3930..3387b0d70 100644 --- a/scratchpad/resource_use.py +++ b/scratchpad/resource_use.py @@ -24,7 +24,7 @@ def format_bytes(n): usage = [] -for round_ in xrange(args.count): +for round_ in range(args.count): print('Round %d/%d:' % (round_ + 1, args.count)) @@ -55,7 +55,7 @@ def format_bytes(n): usage.append(resource.getrusage(resource.RUSAGE_SELF)) -for i in xrange(len(usage) - 1): +for i in range(len(usage) - 1): before = usage[i] after = usage[i + 1] print('%s (%s)' % (format_bytes(after.ru_maxrss), format_bytes(after.ru_maxrss - before.ru_maxrss))) diff --git a/scratchpad/seekmany.py b/scratchpad/seekmany.py index f117e658e..0b37e5118 100644 --- a/scratchpad/seekmany.py +++ b/scratchpad/seekmany.py @@ -27,7 +27,7 @@ def iter_frames(): for frame in packet.decode(): yield frame -for i in xrange(steps): +for i in range(steps): time = real_duration * i / steps min_time = time - tolerance diff --git a/scripts/activate.sh b/scripts/activate.sh index bbb440185..167266549 100755 --- a/scripts/activate.sh +++ b/scripts/activate.sh @@ -14,7 +14,7 @@ if [[ ! "$PYAV_LIBRARY" ]]; then if [[ "$1" ]]; then PYAV_LIBRARY="$1" else - PYAV_LIBRARY=ffmpeg-4.2 + PYAV_LIBRARY=ffmpeg-6.0 echo "No \$PYAV_LIBRARY set; defaulting to $PYAV_LIBRARY" fi fi diff --git a/scripts/fetch-vendor.json b/scripts/fetch-vendor.json index 36ec3473b..2a02f285b 100644 --- a/scripts/fetch-vendor.json +++ b/scripts/fetch-vendor.json @@ -1,3 +1,3 @@ { - "urls": ["https://github.com/PyAV-Org/pyav-ffmpeg/releases/download/4.4.1-6/ffmpeg-{platform}.tar.gz"] + "urls": ["https://github.com/PyAV-Org/pyav-ffmpeg/releases/download/5.1.2-1/ffmpeg-{platform}.tar.gz"] } diff --git a/scripts/test b/scripts/test index 0ed7eb862..806011859 100755 --- a/scripts/test +++ b/scripts/test @@ -19,7 +19,7 @@ istest() { } if istest black; then - $PYAV_PYTHON -m black av examples tests + $PYAV_PYTHON -m black --check av examples tests fi if istest flake8; then diff --git a/setup.py b/setup.py index ec234f4a0..b9e9ba2d4 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,6 @@ def new_embed_signature(self, sig, doc): - # Strip any `self` parameters from the front. sig = re.sub(r"\(self(,\s+)?", "(", sig) @@ -192,6 +191,7 @@ def parse_cflags(raw_flags): url="https://github.com/PyAV-Org/PyAV", packages=find_packages(exclude=["build*", "examples*", "scratchpad*", "tests*"]), package_data=package_data, + python_requires=">=3.8", zip_safe=False, ext_modules=ext_modules, test_suite="tests", @@ -210,10 +210,11 @@ def parse_cflags(raw_flags): "Operating System :: Unix", "Operating System :: Microsoft :: Windows", "Programming Language :: Cython", - "Programming Language :: Python :: 3.7", "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", diff --git a/tests/common.py b/tests/common.py index 5d1bf74cc..5b12ca0d9 100644 --- a/tests/common.py +++ b/tests/common.py @@ -3,7 +3,6 @@ import errno import functools import os -import sys import types from av.datasets import fate as fate_suite @@ -82,10 +81,11 @@ def _inner(self, *args, **kwargs): return func(self, *args, **kwargs) finally: os.chdir(current_dir) + return _inner -class MethodLogger(object): +class MethodLogger: def __init__(self, obj): self._obj = obj self._log = [] @@ -158,42 +158,3 @@ def assertImagesAlmostEqual(self, a, b, epsilon=0.1, *args): self.fail( "images differed by %s at index %d; %s %s" % (diff, i, ax, bx) ) - - # Add some of the unittest methods that we love from 2.7. - if sys.version_info < (2, 7): - - def assertIs(self, a, b, msg=None): - if a is not b: - self.fail( - msg - or "%r at 0x%x is not %r at 0x%x; %r is not %r" - % (type(a), id(a), type(b), id(b), a, b) - ) - - def assertIsNot(self, a, b, msg=None): - if a is b: - self.fail(msg or "both are %r at 0x%x; %r" % (type(a), id(a), a)) - - def assertIsNone(self, x, msg=None): - if x is not None: - self.fail(msg or "is not None; %r" % x) - - def assertIsNotNone(self, x, msg=None): - if x is None: - self.fail(msg or "is None; %r" % x) - - def assertIn(self, a, b, msg=None): - if a not in b: - self.fail(msg or "%r not in %r" % (a, b)) - - def assertNotIn(self, a, b, msg=None): - if a in b: - self.fail(msg or "%r in %r" % (a, b)) - - def assertIsInstance(self, instance, types, msg=None): - if not isinstance(instance, types): - self.fail(msg or "not an instance of %r; %r" % (types, instance)) - - def assertNotIsInstance(self, instance, types, msg=None): - if isinstance(instance, types): - self.fail(msg or "is an instance of %r; %r" % (types, instance)) diff --git a/tests/test_audiofifo.py b/tests/test_audiofifo.py index f04995b89..0cbb4acc4 100644 --- a/tests/test_audiofifo.py +++ b/tests/test_audiofifo.py @@ -5,7 +5,6 @@ class TestAudioFifo(TestCase): def test_data(self): - container = av.open(fate_suite("audio-reference/chorusnoise_2ch_44kHz_s16.wav")) stream = container.streams.audio[0] @@ -31,9 +30,15 @@ def test_data(self): 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) @@ -61,7 +73,6 @@ def test_pts_simple(self): self.assertRaises(ValueError, fifo.write, iframe) def test_pts_complex(self): - fifo = av.AudioFifo() iframe = av.AudioFrame(samples=1024) @@ -79,7 +90,6 @@ def test_pts_complex(self): self.assertEqual(fifo.pts_per_sample, 2.0) def test_missing_sample_rate(self): - fifo = av.AudioFifo() iframe = av.AudioFrame(samples=1024) @@ -96,7 +106,6 @@ def test_missing_sample_rate(self): self.assertEqual(oframe.time_base, iframe.time_base) def test_missing_time_base(self): - fifo = av.AudioFifo() iframe = av.AudioFrame(samples=1024) diff --git a/tests/test_audioresampler.py b/tests/test_audioresampler.py index fe1907c14..9b66968c1 100644 --- a/tests/test_audioresampler.py +++ b/tests/test_audioresampler.py @@ -69,7 +69,6 @@ def test_matching_passthrough(self): self.assertEqual(len(oframes), 0) def test_pts_assertion_same_rate(self): - resampler = AudioResampler("s16", "mono") # resample one frame @@ -115,7 +114,6 @@ def test_pts_assertion_same_rate(self): self.assertEqual(len(oframes), 0) def test_pts_assertion_new_rate(self): - resampler = AudioResampler("s16", "mono", 44100) # resample one frame @@ -144,7 +142,6 @@ def test_pts_assertion_new_rate(self): self.assertEqual(oframe.samples, 16) def test_pts_missing_time_base(self): - resampler = AudioResampler("s16", "mono", 44100) # resample one frame diff --git a/tests/test_codec_context.py b/tests/test_codec_context.py index 27fcda5c9..c9938d8ee 100644 --- a/tests/test_codec_context.py +++ b/tests/test_codec_context.py @@ -1,6 +1,7 @@ from fractions import Fraction from unittest import SkipTest import os +import warnings from av import AudioResampler, Codec, Packet from av.codec.codec import UnknownCodecError @@ -79,6 +80,23 @@ def test_decoder_extradata(self): self.assertEqual(ctx.extradata, None) self.assertEqual(ctx.extradata_size, 0) + 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) @@ -102,7 +120,6 @@ def test_encoder_pix_fmt(self): self.assertEqual(ctx.pix_fmt, "yuv420p") def test_parse(self): - # This one parses into a single packet. self._assert_parse("mpeg4", fate_suite("h264/interlaced_crop.mp4")) @@ -110,7 +127,6 @@ def test_parse(self): 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): @@ -119,7 +135,6 @@ def _assert_parse(self, codec_name, path): full_source = b"".join(bytes(p) for p in packets) for size in 1024, 8192, 65535: - ctx = Codec(codec_name).create() packets = [] @@ -144,7 +159,6 @@ 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: @@ -162,14 +176,13 @@ def image_sequence_encode(self, codec_name): ctx.width = width ctx.height = height - ctx.time_base = video_stream.codec_context.time_base + 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) @@ -231,7 +244,6 @@ def test_encoding_dnxhd(self): self.video_encoding("dnxhd", options) def video_encoding(self, codec_name, options={}, codec_tag=None): - try: codec = Codec(codec_name, "w") except UnknownCodecError: @@ -244,7 +256,7 @@ def video_encoding(self, codec_name, options={}, codec_tag=None): 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.codec_context.time_base) + time_base = options.pop("time_base", video_stream.time_base) ctx = codec.create() ctx.width = width @@ -262,9 +274,7 @@ def video_encoding(self, codec_name, options={}, codec_tag=None): 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 @@ -304,7 +314,6 @@ def test_encoding_mp2(self): self.audio_encoding("mp2") def audio_encoding(self, codec_name): - try: codec = Codec(codec_name, "w") except UnknownCodecError: @@ -339,7 +348,6 @@ def audio_encoding(self, codec_name): 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: samples += resampled_frame.samples @@ -353,7 +361,6 @@ def audio_encoding(self, codec_name): f.write(packet) ctx = Codec(codec_name, "r").create() - ctx.time_base = Fraction(1) / sample_rate ctx.sample_rate = sample_rate ctx.format = sample_fmt ctx.layout = channel_layout @@ -367,5 +374,5 @@ def audio_encoding(self, codec_name): # so can really use checksums for frame in iter_raw_frames(path, packet_sizes, ctx): result_samples += frame.samples - self.assertEqual(frame.rate, sample_rate) + self.assertEqual(frame.sample_rate, sample_rate) self.assertEqual(len(frame.layout.channels), channels) diff --git a/tests/test_decode.py b/tests/test_decode.py index b4e13c183..564ea24cd 100644 --- a/tests/test_decode.py +++ b/tests/test_decode.py @@ -1,3 +1,5 @@ +from fractions import Fraction + import av from .common import TestCase, fate_suite @@ -5,7 +7,6 @@ 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") @@ -38,7 +39,6 @@ def test_decode_audio_corrupt(self): 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") @@ -51,17 +51,15 @@ def test_decode_audio_sample_count(self): sample_count += frame.samples total_samples = ( - audio_stream.duration * audio_stream.rate.numerator + 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] - codec_context = stream.codec_context - self.assertNotEqual(stream.time_base, codec_context.time_base) + self.assertEqual(stream.time_base, Fraction(1, 25)) for packet in container.demux(stream): for frame in packet.decode(): @@ -70,7 +68,6 @@ def test_decoded_time_base(self): 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 @@ -87,7 +84,6 @@ def test_decoded_motion_vectors(self): return def test_decoded_motion_vectors_no_flag(self): - container = av.open(fate_suite("h264/interlaced_crop.mp4")) stream = container.streams.video[0] diff --git a/tests/test_deprecation.py b/tests/test_deprecation.py index abdc79f8e..f8857ab73 100644 --- a/tests/test_deprecation.py +++ b/tests/test_deprecation.py @@ -7,7 +7,7 @@ class TestDeprecations(TestCase): def test_method(self): - class Example(object): + class Example: def __init__(self, x=100): self.x = x @@ -22,8 +22,7 @@ def foo(self, a, b): self.assertIn("Example.foo is deprecated", captured[0].message.args[0]) def test_renamed_attr(self): - class Example(object): - + class Example: new_value = "foo" old_value = deprecation.renamed_attr("new_value") @@ -35,7 +34,6 @@ def new_func(self, a, b): 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] diff --git a/tests/test_dictionary.py b/tests/test_dictionary.py index 4e2c4995e..a1c2f80d8 100644 --- a/tests/test_dictionary.py +++ b/tests/test_dictionary.py @@ -5,7 +5,6 @@ class TestDictionary(TestCase): def test_basics(self): - d = Dictionary() d["key"] = "value" diff --git a/tests/test_doctests.py b/tests/test_doctests.py index c2144eab1..1449355bd 100644 --- a/tests/test_doctests.py +++ b/tests/test_doctests.py @@ -4,12 +4,11 @@ import re 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) @@ -24,14 +23,12 @@ def fix_doctests(suite): ) 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=[""]) diff --git a/tests/test_encode.py b/tests/test_encode.py index 4ad5b203e..ca950e686 100644 --- a/tests/test_encode.py +++ b/tests/test_encode.py @@ -16,7 +16,6 @@ def write_rgb_rotate(output): - if not Image: raise SkipTest() @@ -29,7 +28,6 @@ def write_rgb_rotate(output): stream.pix_fmt = "yuv420p" for frame_i in range(DURATION): - frame = VideoFrame(WIDTH, HEIGHT, "rgb24") image = Image.new( "RGB", @@ -64,48 +62,79 @@ def write_rgb_rotate(output): 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) + 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] - self.assertIsInstance(stream, VideoStream) - self.assertEqual(stream.type, "video") - self.assertEqual(stream.name, "mpeg4") - self.assertEqual( - stream.average_rate, 24 - ) # Only because we constructed is precisely. - self.assertEqual(stream.rate, Fraction(24, 1)) + 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: - self.assertEqual(stream.time_base * stream.duration, 2) + 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.rate, Fraction(24, 1)) self.assertEqual(stream.ticks_per_frame, 1) - self.assertEqual(stream.time_base, None) self.assertEqual(stream.width, 640) def test_encoding(self): @@ -121,6 +150,7 @@ def test_encoding_with_pts(self): 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" @@ -151,11 +181,14 @@ 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.rate, 48000) + self.assertEqual(stream.sample_rate, 48000) self.assertEqual(stream.ticks_per_frame, 1) - self.assertEqual(stream.time_base, None) def test_transcode(self): path = self.sandboxed("audio_transcode.mov") @@ -170,6 +203,7 @@ def test_transcode(self): 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 @@ -197,20 +231,24 @@ def test_transcode(self): stream = container.streams[0] self.assertIsInstance(stream, AudioStream) - self.assertEqual(stream.codec_context.sample_rate, sample_rate) - self.assertEqual(stream.codec_context.format.name, "s16p") - self.assertEqual(stream.codec_context.channels, channels) + + # 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" @@ -230,7 +268,7 @@ def test_stream_index(self): # decoder didn't indicate constant frame size frame_size = 1000 aframe = AudioFrame("s16", "stereo", samples=frame_size) - aframe.rate = 48000 + aframe.sample_rate = 48000 apackets = astream.encode(aframe) if apackets: apacket = apackets[0] @@ -242,6 +280,7 @@ def test_stream_index(self): 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) diff --git a/tests/test_enums.py b/tests/test_enums.py index c22e659fb..bc8385f5e 100644 --- a/tests/test_enums.py +++ b/tests/test_enums.py @@ -22,7 +22,6 @@ def define_foobar(self, **kwargs): ) def test_basics(self): - cls = self.define_foobar() self.assertIsInstance(cls, EnumType) @@ -36,7 +35,6 @@ def test_basics(self): self.assertNotIsInstance(foo, PickleableFooBar) def test_access(self): - cls = self.define_foobar() foo1 = cls.FOO foo2 = cls["FOO"] @@ -58,7 +56,6 @@ def test_access(self): self.assertIs(cls.get("not a foo"), None) def test_casting(self): - cls = self.define_foobar() foo = cls.FOO @@ -77,7 +74,6 @@ def test_iteration(self): self.assertEqual(list(cls), [cls.FOO, cls.BAR]) def test_equality(self): - cls = self.define_foobar() foo = cls.FOO bar = cls.BAR @@ -94,7 +90,6 @@ def test_equality(self): self.assertRaises(TypeError, lambda: foo == ()) def test_as_key(self): - cls = self.define_foobar() foo = cls.FOO @@ -104,7 +99,6 @@ def test_as_key(self): self.assertIs(d.get(1), None) def test_pickleable(self): - cls = PickleableFooBar foo = cls.FOO @@ -115,7 +109,6 @@ def test_pickleable(self): self.assertIs(foo, foo2) def test_create_unknown(self): - cls = self.define_foobar() baz = cls.get(3, create=True) @@ -123,7 +116,6 @@ def test_create_unknown(self): self.assertEqual(baz.value, 3) def test_multiple_names(self): - cls = define_enum( "FFooBBar", __name__, @@ -147,7 +139,6 @@ def test_multiple_names(self): self.assertRaises(ValueError, lambda: cls.F == "x") def test_flag_basics(self): - cls = define_enum( "FoobarAllFlags", __name__, @@ -178,7 +169,6 @@ def test_flag_basics(self): self.assertIs(x, cls.FOO) def test_multi_flags_basics(self): - cls = self.define_foobar(is_flags=True) foo = cls.FOO @@ -202,7 +192,6 @@ def test_multi_flags_basics(self): self.assertEqual(list(cls), [foo, bar]) def test_multi_flags_create_missing(self): - cls = self.define_foobar(is_flags=True) foobar = cls[3] @@ -212,11 +201,10 @@ def test_multi_flags_create_missing(self): 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(object): + class Class: def __init__(self, value): self.value = Flags[value].value diff --git a/tests/test_errors.py b/tests/test_errors.py index 55d969999..c7dc1729e 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -8,7 +8,6 @@ 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") @@ -34,7 +33,6 @@ def test_stringify(self): ) def test_bases(self): - self.assertTrue(issubclass(av.ValueError, ValueError)) self.assertTrue(issubclass(av.ValueError, av.FFmpegError)) diff --git a/tests/test_file_probing.py b/tests/test_file_probing.py index 69b356cb5..c67f7fb8e 100644 --- a/tests/test_file_probing.py +++ b/tests/test_file_probing.py @@ -1,4 +1,5 @@ from fractions import Fraction +import warnings import av @@ -25,6 +26,13 @@ def test_container_probing(self): def test_stream_probing(self): stream = self.file.streams[0] + # check __repr__ + self.assertTrue( + str(stream).startswith( + " at ")) + # actual stream properties self.assertEqual(stream.average_rate, None) self.assertEqual(stream.base_rate, None) @@ -194,9 +201,8 @@ def test_stream_probing(self): self.assertEqual(stream.time_base, Fraction(1, 90000)) self.assertEqual(stream.type, "data") - # codec properties - self.assertEqual(stream.name, None) - self.assertEqual(stream.long_name, None) + # codec context properties + self.assertEqual(stream.codec, None) class TestSubtitleProbe(TestCase): @@ -227,6 +233,11 @@ def test_container_probing(self): def test_stream_probing(self): stream = self.file.streams[0] + # check __repr__ + self.assertTrue( + str(stream).startswith("