From 0a8f94f13b1dad011102194010c60d5692ebbaa1 Mon Sep 17 00:00:00 2001
From: Mike Boers <github@mikeboers.com>
Date: Sat, 29 Sep 2018 16:39:24 -0400
Subject: [PATCH 1/3] Pull renamed_attr from metatools.deprecate.

---
 av/deprecation.py | 59 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 59 insertions(+)
 create mode 100644 av/deprecation.py

diff --git a/av/deprecation.py b/av/deprecation.py
new file mode 100644
index 0000000..d8cfa6f
--- /dev/null
+++ b/av/deprecation.py
@@ -0,0 +1,59 @@
+import warnings
+
+
+class AttributeRenamedWarning(UserWarning):
+    pass   
+
+
+class renamed_attr(object):
+
+    """Proxy for renamed attributes (or methods) on classes.
+    Getting and setting values will be redirected to the provided name,
+    and warnings will be issues every time.
+
+    E.g.::
+
+        >>> class Example(object):
+        ... 
+        ...     new_value = 'something'
+        ...     old_value = renamed_attr('new_value')
+        ...     
+        ...     def new_func(self, a, b):
+        ...         return a + b
+        ...         
+        ...     old_func = renamed_attr('new_func')
+        >>> e = Example()
+        >>> e.old_value = 'else'
+        # AttributeRenamedWarning: Example.old_value renamed to new_value
+        >>> e.old_func(1, 2)
+        # AttributeRenamedWarning: Example.old_func renamed to new_func
+        3
+    
+    """
+
+    def __init__(self, new_name):
+        self.new_name = new_name
+        self._old_name = None # We haven't discovered it yet.
+
+    def old_name(self, cls):
+        if self._old_name is None:
+            for k, v in vars(cls).items():
+                if v is self:
+                    self._old_name = k
+                    break
+        return self._old_name
+
+    def __get__(self, instance, cls):
+        old_name = self.old_name(cls)
+        warnings.warn('%s.%s was renamed to %s' % (
+            cls.__name__, old_name, self.new_name,
+        ), AttributeRenamedWarning, stacklevel=2)
+        return getattr(instance if instance is not None else cls, self.new_name)
+
+    def __set__(self, instance, value):
+        old_name = self.old_name(instance.__class__)
+        warnings.warn('%s.%s was renamed to %s' % (
+            instance.__class__.__name__, old_name, self.new_name,
+        ), AttributeRenamedWarning, stacklevel=2)
+        setattr(instance, self.new_name, value)
+
-- 
GitLab


From c57184903a649c880542846e5b10b506e2d5c98e Mon Sep 17 00:00:00 2001
From: Mike Boers <github@mikeboers.com>
Date: Mon, 1 Oct 2018 15:18:32 -0400
Subject: [PATCH 2/3] Deprecation warnings are picked up by doctest.

---
 av/deprecation.py | 26 ++++++++++++++++++--------
 tests/common.py   | 10 +++++++++-
 2 files changed, 27 insertions(+), 9 deletions(-)

diff --git a/av/deprecation.py b/av/deprecation.py
index d8cfa6f..121ab9c 100644
--- a/av/deprecation.py
+++ b/av/deprecation.py
@@ -1,10 +1,17 @@
 import warnings
 
 
-class AttributeRenamedWarning(UserWarning):
+class AttributeRenamedWarning(DeprecationWarning):
     pass   
 
 
+# DeprecationWarning is not printed by default (unless in __main__). We
+# really want these to be seen, but also to use the "correct" base classes.
+# So we're putting a filter in place to show our warnings. The users can
+# turn them back off if they want.
+warnings.filterwarnings('default', '', AttributeRenamedWarning)
+
+
 class renamed_attr(object):
 
     """Proxy for renamed attributes (or methods) on classes.
@@ -22,18 +29,21 @@ class renamed_attr(object):
         ...         return a + b
         ...         
         ...     old_func = renamed_attr('new_func')
+
         >>> e = Example()
-        >>> e.old_value = 'else'
-        # AttributeRenamedWarning: Example.old_value renamed to new_value
-        >>> e.old_func(1, 2)
-        # AttributeRenamedWarning: Example.old_func renamed to new_func
+        
+        >>> e.old_value = 'else' # doctest: +ELLIPSIS
+        /... AttributeRenamedWarning: Example.old_value is deprecated; please use Example.new_value. ...
+
+        >>> e.old_func(1, 2) # doctest: +ELLIPSIS
+        /... AttributeRenamedWarning: Example.old_func is deprecated; please use Example.new_func. ...
         3
     
     """
 
     def __init__(self, new_name):
         self.new_name = new_name
-        self._old_name = None # We haven't discovered it yet.
+        self._old_name = None
 
     def old_name(self, cls):
         if self._old_name is None:
@@ -45,14 +55,14 @@ class renamed_attr(object):
 
     def __get__(self, instance, cls):
         old_name = self.old_name(cls)
-        warnings.warn('%s.%s was renamed to %s' % (
+        warnings.warn('{0}.{1} is deprecated; please use {0}.{2}.'.format(
             cls.__name__, old_name, self.new_name,
         ), AttributeRenamedWarning, stacklevel=2)
         return getattr(instance if instance is not None else cls, self.new_name)
 
     def __set__(self, instance, value):
         old_name = self.old_name(instance.__class__)
-        warnings.warn('%s.%s was renamed to %s' % (
+        warnings.warn('{0}.{1} is deprecated; please use {0}.{2}.'.format(
             instance.__class__.__name__, old_name, self.new_name,
         ), AttributeRenamedWarning, stacklevel=2)
         setattr(instance, self.new_name, value)
diff --git a/tests/common.py b/tests/common.py
index 721063d..8a0644b 100644
--- a/tests/common.py
+++ b/tests/common.py
@@ -5,10 +5,11 @@ from subprocess import check_call
 from unittest import TestCase as _Base
 import datetime
 import errno
+import functools
 import os
 import sys
-import functools
 import types
+import warnings
 
 from nose.plugins.skip import SkipTest
 
@@ -95,6 +96,13 @@ def sandboxed(*args, **kwargs):
     return path
 
 
+# Route all warnings to stdout so that doctest will work with them.
+def showwarning(message, category, filename, lineno, file=None, line=None):
+    msg = warnings.formatwarning(message, category, filename, lineno, line)
+    print(msg.rstrip())
+warnings.showwarning = showwarning
+
+
 class MethodLogger(object):
 
     def __init__(self, obj):
-- 
GitLab


From 07ff58bde32a41e9d791a2cce7b854275fc55c67 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jeremy=20Lain=C3=A9?= <jeremy.laine@m4x.org>
Date: Mon, 1 Oct 2018 21:13:52 +0200
Subject: [PATCH 3/3] Rename Frame.to_nd_array to Frame.to_ndarray

---
 CHANGELOG.rst                  |  5 +++
 av/audio/frame.pyx             |  5 ++-
 av/video/frame.pyx             |  5 ++-
 examples/average.py            |  4 +--
 examples/show_frames_opencv.py |  2 +-
 tests/test_audioframe.py       | 58 ++++++++++++++++++++++++++++++++++
 tests/test_videoframe.py       | 23 ++++++++++++--
 7 files changed, 94 insertions(+), 8 deletions(-)
 create mode 100644 tests/test_audioframe.py

diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 4200605..c7f68b1 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -6,6 +6,11 @@ we are using v0.x.y as our heavy development period, and will increment ``x``
 to signal a major change (i.e. backwards incompatibilities) and increment
 ``y`` as a minor change (i.e. backwards compatible features).
 
+v0.5.3
+------
+
+- Deprecate ``AudioFrame.to_nd_array()`` and ``VideoFrame.to_nd_array()`` in
+  favour of ``.to_ndarray()``.
 
 v0.5.2
 ------
diff --git a/av/audio/frame.pyx b/av/audio/frame.pyx
index d6ebe2f..0be0576 100644
--- a/av/audio/frame.pyx
+++ b/av/audio/frame.pyx
@@ -1,6 +1,7 @@
 from av.audio.format cimport get_audio_format
 from av.audio.layout cimport get_audio_layout
 from av.audio.plane cimport AudioPlane
+from av.deprecation import renamed_attr
 from av.utils cimport err_check
 
 
@@ -120,7 +121,7 @@ cdef class AudioFrame(Frame):
         def __set__(self, value):
             self.ptr.sample_rate = value
 
-    def to_nd_array(self, **kwargs):
+    def to_ndarray(self, **kwargs):
         """Get a numpy array of this frame.
         Any ``**kwargs`` are passed to :meth:`AudioFrame.reformat`.
         """
@@ -138,3 +139,5 @@ cdef class AudioFrame(Frame):
 
         # convert and return data
         return np.vstack(map(lambda x: np.frombuffer(x, dtype), self.planes))
+
+    to_nd_array = renamed_attr('to_ndarray')
diff --git a/av/video/frame.pyx b/av/video/frame.pyx
index 9f296bd..cc3ce1d 100644
--- a/av/video/frame.pyx
+++ b/av/video/frame.pyx
@@ -1,6 +1,7 @@
 from libc.stdint cimport uint8_t
 
 from av.bytesource cimport ByteSource, bytesource
+from av.deprecation import renamed_attr
 from av.enums cimport EnumType, define_enum
 from av.utils cimport err_check
 from av.video.format cimport get_video_format, VideoFormat
@@ -298,7 +299,7 @@ cdef class VideoFrame(Frame):
         from PIL import Image
         return Image.frombuffer("RGB", (self.width, self.height), self.reformat(format="rgb24", **kwargs).planes[0], "raw", "RGB", 0, 1)
 
-    def to_nd_array(self, **kwargs):
+    def to_ndarray(self, **kwargs):
         """Get a numpy array of this frame.
 
         Any ``**kwargs`` are passed to :meth:`VideoFrame.reformat`.
@@ -320,6 +321,8 @@ cdef class VideoFrame(Frame):
         else:
             raise ValueError("Cannot conveniently get numpy array from %s format" % frame.format.name)
 
+    to_nd_array = renamed_attr('to_ndarray')
+
     def to_qimage(self, **kwargs):
         """Get an RGB ``QImage`` of this frame.
 
diff --git a/examples/average.py b/examples/average.py
index 378db87..d32c40b 100644
--- a/examples/average.py
+++ b/examples/average.py
@@ -48,9 +48,9 @@ for src_path in args.path:
     for fi, frame in enumerate(frame_iter(video)):
 
         if sum_ is None:
-            sum_ = frame.to_nd_array().astype(float)
+            sum_ = frame.to_ndarray().astype(float)
         else:
-            sum_ += frame.to_nd_array().astype(float)
+            sum_ += frame.to_ndarray().astype(float)
 
     sum_ /= (fi + 1)
 
diff --git a/examples/show_frames_opencv.py b/examples/show_frames_opencv.py
index 1d5c070..c618afa 100644
--- a/examples/show_frames_opencv.py
+++ b/examples/show_frames_opencv.py
@@ -12,7 +12,7 @@ stream = next(s for s in video.streams if s.type == 'video')
 for packet in video.demux(stream):
     for frame in packet.decode():
         # some other formats gray16be, bgr24, rgb24
-        img = frame.to_nd_array(format='bgr24')
+        img = frame.to_ndarray(format='bgr24')
         cv2.imshow("Test", img)
 
     if cv2.waitKey(1) == 27:
diff --git a/tests/test_audioframe.py b/tests/test_audioframe.py
new file mode 100644
index 0000000..2bdc191
--- /dev/null
+++ b/tests/test_audioframe.py
@@ -0,0 +1,58 @@
+import warnings
+
+from av import AudioFrame
+from av.deprecation import AttributeRenamedWarning
+
+from .common import TestCase
+
+
+class TestAudioFrameConstructors(TestCase):
+
+    def test_null_constructor(self):
+        frame = AudioFrame()
+        self.assertEqual(frame.format.name, 's16')
+        self.assertEqual(frame.layout.name, 'stereo')
+        self.assertEqual(len(frame.planes), 0)
+        self.assertEqual(frame.samples, 0)
+
+    def test_manual_s16_mono_constructor(self):
+        frame = AudioFrame(format='s16', layout='mono', samples=160)
+        self.assertEqual(frame.format.name, 's16')
+        self.assertEqual(frame.layout.name, 'mono')
+        self.assertEqual(len(frame.planes), 1)
+        self.assertEqual(frame.samples, 160)
+
+    def test_manual_s16_stereo_constructor(self):
+        frame = AudioFrame(format='s16', layout='stereo', samples=160)
+        self.assertEqual(frame.format.name, 's16')
+        self.assertEqual(frame.layout.name, 'stereo')
+        self.assertEqual(len(frame.planes), 1)
+        self.assertEqual(frame.samples, 160)
+
+    def test_manual_s16p_stereo_constructor(self):
+        frame = AudioFrame(format='s16p', layout='stereo', samples=160)
+        self.assertEqual(frame.format.name, 's16p')
+        self.assertEqual(frame.layout.name, 'stereo')
+        self.assertEqual(len(frame.planes), 2)
+        self.assertEqual(frame.samples, 160)
+
+
+class TestAudioFrameConveniences(TestCase):
+
+    def test_basic_to_ndarray(self):
+        frame = AudioFrame(format='s16p', layout='stereo', samples=160)
+        array = frame.to_ndarray()
+        self.assertEqual(array.shape, (2, 160))
+
+    def test_basic_to_nd_array(self):
+        frame = AudioFrame(format='s16p', layout='stereo', samples=160)
+        with warnings.catch_warnings(record=True) as recorded:
+            array = frame.to_nd_array()
+        self.assertEqual(array.shape, (2, 160))
+
+        # check deprecation warning
+        self.assertEqual(len(recorded), 1)
+        self.assertEqual(recorded[0].category, AttributeRenamedWarning)
+        self.assertEqual(
+            str(recorded[0].message),
+            'AudioFrame.to_nd_array is deprecated; please use AudioFrame.to_ndarray.')
diff --git a/tests/test_videoframe.py b/tests/test_videoframe.py
index 54aa73f..56b1fe3 100644
--- a/tests/test_videoframe.py
+++ b/tests/test_videoframe.py
@@ -1,4 +1,9 @@
-from .common import *
+import warnings
+
+from av import VideoFrame
+from av.deprecation import AttributeRenamedWarning
+
+from .common import fate_png, is_py3, Image, SkipTest, TestCase
 
 
 class TestVideoFrameConstructors(TestCase):
@@ -108,11 +113,24 @@ class TestVideoFrameTransforms(TestCase):
 
 class TestVideoFrameConveniences(TestCase):
 
+    def test_basic_to_ndarray(self):
+        frame = VideoFrame(640, 480, 'rgb24')
+        array = frame.to_ndarray()
+        self.assertEqual(array.shape, (480, 640, 3))
+
     def test_basic_to_nd_array(self):
         frame = VideoFrame(640, 480, 'rgb24')
-        array = frame.to_nd_array()
+        with warnings.catch_warnings(record=True) as recorded:
+            array = frame.to_nd_array()
         self.assertEqual(array.shape, (480, 640, 3))
 
+        # check deprecation warning
+        self.assertEqual(len(recorded), 1)
+        self.assertEqual(recorded[0].category, AttributeRenamedWarning)
+        self.assertEqual(
+            str(recorded[0].message),
+            'VideoFrame.to_nd_array is deprecated; please use VideoFrame.to_ndarray.')
+
 
 class TestVideoFrameTiming(TestCase):
 
@@ -141,4 +159,3 @@ class TestVideoFrameReformat(TestCase):
         # I thought this was not allowed, but it seems to be.
         frame = VideoFrame(640, 480, 'yuv420p')
         frame2 = frame.reformat(src_colorspace=None, dst_colorspace='smpte240')
-
-- 
GitLab