From b6a86e57a7db189b02c5ba4f8cbc8327ae2295f6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jeremy=20Lain=C3=A9?= <jeremy.laine@m4x.org>
Date: Mon, 28 Mar 2022 19:33:15 +0200
Subject: [PATCH] [codec context] deprecate CodecContext.time_base for decoders

The FFmpeg API docs state that using AVCodecContext.time_base for
decoders is deprecated and AVCodecContext.framerate should be used
instead.
---
 av/codec/context.pyx        | 17 +++++++++++++++--
 docs/api/time.rst           |  3 ---
 tests/test_codec_context.py | 19 ++++++++++++++++++-
 tests/test_decode.py        |  5 +++--
 4 files changed, 36 insertions(+), 8 deletions(-)

diff --git a/av/codec/context.pyx b/av/codec/context.pyx
index fd3b26f..c9f5177 100644
--- a/av/codec/context.pyx
+++ b/av/codec/context.pyx
@@ -1,3 +1,5 @@
+import warnings
+
 from libc.errno cimport EAGAIN
 from libc.stdint cimport int64_t, uint8_t
 from libc.string cimport memcpy
@@ -11,6 +13,7 @@ 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
 
 
@@ -282,8 +285,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))
@@ -551,9 +554,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/docs/api/time.rst b/docs/api/time.rst
index c0234e0..35e4cfc 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/tests/test_codec_context.py b/tests/test_codec_context.py
index ca94336..a62c05c 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 @@ class TestCodecContext(TestCase):
         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)
@@ -357,7 +375,6 @@ class TestEncoding(TestCase):
                 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
diff --git a/tests/test_decode.py b/tests/test_decode.py
index b4e13c1..7090101 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
@@ -59,9 +61,8 @@ class TestDecode(TestCase):
 
         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():
-- 
GitLab