File python-av-ffmpeg5-compatibility.patch of Package python-av
From 18704658487ea25e5202ac18438d836dfe65b9d0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jeremy=20Lain=C3=A9?= <jeremy.laine@m4x.org>
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 '<av.%s #%d %s/%s at 0x%x>' % (
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):