diff --git a/python-av-ffmpeg5-compatibility.patch b/python-av-ffmpeg5-compatibility.patch new file mode 100644 index 0000000..95af6ea --- /dev/null +++ b/python-av-ffmpeg5-compatibility.patch @@ -0,0 +1,746 @@ +From 18704658487ea25e5202ac18438d836dfe65b9d0 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jeremy=20Lain=C3=A9?= +Date: Fri, 11 Mar 2022 16:30:43 +0100 +Subject: [PATCH] [streams] stop using deprecated Stream.codec, it's gone in + FFmpeg 5 + +We now allocate and populate an AVCodecContext ourselves. +avcodec_copy_context is also gone, so stop using it. + +We relax the Stream.average_rate tests for older FFmpeg, as the videos +output by these older FFmpeg's seem to give a slightly wrong FPS since +the switch to our own AVCodecContext. +--- + av/codec/context.pxd | 5 +- + av/codec/context.pyx | 7 ++- + av/container/input.pyx | 38 +++++++++----- + av/container/output.pyx | 28 ++++------- + av/container/streams.pyx | 10 ++-- + av/data/stream.pyx | 2 +- + av/packet.pyx | 2 +- + av/stream.pxd | 10 ++-- + av/stream.pyx | 85 +++++++++++++------------------- + av/video/stream.pyx | 4 +- + include/libavcodec/avcodec.pxd | 14 ++++-- + include/libavformat/avformat.pxd | 1 - + tests/common.py | 1 + + tests/test_codec_context.py | 4 +- + tests/test_encode.py | 50 +++++++++++++++---- + 15 files changed, 143 insertions(+), 118 deletions(-) + +diff --git a/av/codec/context.pxd b/av/codec/context.pxd +index d9b6906f9..387cb7de4 100644 +--- a/av/codec/context.pxd ++++ b/av/codec/context.pxd +@@ -11,9 +11,6 @@ cdef class CodecContext(object): + + 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 c9f5177c1..5c8314615 100644 +--- a/av/codec/context.pyx ++++ b/av/codec/context.pyx +@@ -20,7 +20,7 @@ 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 +@@ -38,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 +@@ -147,7 +146,7 @@ cdef class CodecContext(object): + 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: +@@ -307,7 +306,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: +diff --git a/av/container/input.pyx b/av/container/input.pyx +index e0c7dcc22..e508f16f4 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..a454e121e 100644 +--- a/av/container/output.pyx ++++ b/av/container/output.pyx +@@ -3,12 +3,13 @@ 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 + +@@ -64,14 +65,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 +80,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 +98,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 +@@ -126,7 +117,8 @@ cdef class OutputContainer(Container): + codec_context.flags |= lib.AV_CODEC_FLAG_GLOBAL_HEADER + + # 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/streams.pyx b/av/container/streams.pyx +index 4ed2223d4..eb85d9ff3 100644 +--- a/av/container/streams.pyx ++++ b/av/container/streams.pyx +@@ -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/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/stream.pxd b/av/stream.pxd +index 4a3cab488..5ad3b965e 100644 +--- a/av/stream.pxd ++++ b/av/stream.pxd +@@ -8,24 +8,20 @@ from av.packet cimport Packet + + + cdef class Stream(object): ++ 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..73cb3504d 100644 +--- a/av/stream.pyx ++++ b/av/stream.pyx +@@ -17,7 +17,7 @@ from av.utils cimport ( + 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,22 +30,22 @@ 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 + + +@@ -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__, +@@ -137,17 +121,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 +149,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 +174,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 +192,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 +203,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 +213,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 +233,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 +247,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 +260,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 +271,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 +282,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 +293,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 +314,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/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/include/libavcodec/avcodec.pxd b/include/libavcodec/avcodec.pxd +index 8c0a9685b..1e6111808 100644 +--- a/include/libavcodec/avcodec.pxd ++++ b/include/libavcodec/avcodec.pxd +@@ -194,6 +194,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 + +@@ -237,7 +238,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 +455,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/libavformat/avformat.pxd b/include/libavformat/avformat.pxd +index 0a33cf9f6..ed3e503f5 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 +diff --git a/tests/common.py b/tests/common.py +index 5d1bf74cc..a49b7bec2 100644 +--- a/tests/common.py ++++ b/tests/common.py +@@ -82,6 +82,7 @@ def _inner(self, *args, **kwargs): + return func(self, *args, **kwargs) + finally: + os.chdir(current_dir) ++ + return _inner + + +diff --git a/tests/test_codec_context.py b/tests/test_codec_context.py +index a62c05c4e..7087804f7 100644 +--- a/tests/test_codec_context.py ++++ b/tests/test_codec_context.py +@@ -180,7 +180,7 @@ 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() + +@@ -262,7 +262,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 +diff --git a/tests/test_encode.py b/tests/test_encode.py +index 018c6ac31..7c5d0353f 100644 +--- a/tests/test_encode.py ++++ b/tests/test_encode.py +@@ -70,27 +70,59 @@ def assert_rgb_rotate(self, input_, is_dash=False): + 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 properties ++ self.assertEqual(stream.name, "mpeg4") ++ self.assertEqual(stream.long_name, "MPEG-4 part 2") ++ ++ # codec context properties + self.assertEqual(stream.format.name, "yuv420p") + self.assertEqual(stream.format.width, WIDTH) + self.assertEqual(stream.format.height, HEIGHT) ++ self.assertEqual(stream.rate, None) ++ self.assertEqual(stream.ticks_per_frame, 1) + + + class TestBasicVideoEncoding(TestCase): diff --git a/python-av.changes b/python-av.changes index d170c25..f8f3dc3 100644 --- a/python-av.changes +++ b/python-av.changes @@ -1,3 +1,10 @@ +------------------------------------------------------------------- +Mon Aug 8 21:41:40 UTC 2022 - Atri Bhattacharya + +- Add python-av-ffmpeg5-compatibility.patch to drop references to + symbols in ffmpeg4 and dropped from ffmpeg5 to allow building + against ffmpeg5; patch taken from upstream git commit. + ------------------------------------------------------------------- Mon May 2 21:45:05 UTC 2022 - Ferdinand Thiessen diff --git a/python-av.spec b/python-av.spec index 65f25be..95a48dd 100644 --- a/python-av.spec +++ b/python-av.spec @@ -25,6 +25,8 @@ Summary: Python bindings for FFmpeg's libraries License: BSD-3-Clause URL: https://github.com/PyAV-Org/PyAV Source: https://files.pythonhosted.org/packages/source/a/av/av-%{version}.tar.gz +# PATCH-FIX-UPSTREAM gh#PyAV-Org/PyAV#817 badshah400@gmail.com -- Add ffmpeg5 support, patch taken from upstream git +Patch0: https://github.com/PyAV-Org/PyAV/commit/18704658487ea25e5202ac18438d836dfe65b9d0.patch#/python-av-ffmpeg5-compatibility.patch BuildRequires: %{python_module Cython} BuildRequires: %{python_module devel} BuildRequires: %{python_module numpy} @@ -46,7 +48,7 @@ Requires(postun):update-alternatives Pythonic bindings for FFmpeg's libraries. %prep -%setup -q -n av-%{version} +%autosetup -p1 -n av-%{version} # doctests and timeout require network to setup tests rm tests/test_doctests.py tests/test_timeout.py