[Python-modules-commits] [audioread] 01/11: Import audioread_2.1.2.orig.tar.gz

Tristan Seligmann mithrandi at moszumanska.debian.org
Fri Apr 29 18:01:08 UTC 2016


This is an automated email from the git hooks/post-receive script.

mithrandi pushed a commit to branch master
in repository audioread.

commit 67fecf1d07566fa2fba6ef64a4ed11894f8b5dac
Author: Tristan Seligmann <mithrandi at debian.org>
Date:   Fri Apr 29 19:18:34 2016 +0200

    Import audioread_2.1.2.orig.tar.gz
---
 PKG-INFO              |  59 +++++++++++----
 README.rst            |  56 ++++++++++----
 audioread/__init__.py |  30 ++++++--
 audioread/ffdec.py    | 183 +++++++++++++++++++++++++++++++-------------
 audioread/gstdec.py   | 206 +++++++++++++++++++++++++++++++-------------------
 audioread/rawread.py  |  35 ++++++---
 decode.py             |   6 +-
 setup.py              |   7 +-
 8 files changed, 406 insertions(+), 176 deletions(-)

diff --git a/PKG-INFO b/PKG-INFO
index 94316fe..d741e99 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: audioread
-Version: 1.0.3
+Version: 2.1.2
 Summary: multi-library, cross-platform audio decoding
 Home-page: https://github.com/sampsyo/audioread
 Author: Adrian Sampson
@@ -15,11 +15,12 @@ Description: audioread
         Decode audio files using whichever backend is available. The library
         currently supports:
         
-        - `Gstreamer`_ via `gst-python`_.
+        - `Gstreamer`_ via `PyGObject`_.
         - `Core Audio`_ on Mac OS X via `ctypes`_. (PyObjC not required.)
         - `MAD`_ via the `pymad`_ bindings.
-        - `FFmpeg`_ via its command-line interface.
-        - The standard library `wave`_ and `aifc`_ modules (for WAV and AIFF files).
+        - `FFmpeg`_ or `Libav`_ via its command-line interface.
+        - The standard library `wave`_, `aifc`_, and `sunau`_ modules (for
+          uncompressed audio formats).
         
         .. _Gstreamer: http://gstreamer.freedesktop.org/
         .. _gst-python: http://gstreamer.freedesktop.org/modules/gst-python.html
@@ -28,8 +29,11 @@ Description: audioread
         .. _MAD: http://www.underbit.com/products/mad/
         .. _pymad: http://spacepants.org/src/pymad/
         .. _FFmpeg: http://ffmpeg.org/
+        .. _Libav: https://www.libav.org/
         .. _wave: http://docs.python.org/library/wave.html
         .. _aifc: http://docs.python.org/library/aifc.html
+        .. _sunau: http://docs.python.org/library/sunau.html
+        .. _PyGObject: https://wiki.gnome.org/Projects/PyGObject
         
         Use the library like so::
         
@@ -58,16 +62,7 @@ Description: audioread
         will be raised.
         
         Audioread is "universal" and supports both Python 2 (2.6+) and Python 3
-        (3.2+). The GStreamer backend currently only supports 2.x since gst-python is
-        2.x-only.
-        
-        Future Work
-        -----------
-        
-        Possible additional backends:
-        
-        -  PyOgg?
-        -  Other command-line tools?
+        (3.2+).
         
         Example
         -------
@@ -78,6 +73,39 @@ Description: audioread
         Version History
         ---------------
         
+        2.1.2
+          Fix a file descriptor leak when opening and closing many files using
+          GStreamer.
+        
+        2.1.1
+          Just fix ReST formatting in the README.
+        
+        2.1.0
+          The FFmpeg backend can now also use Libav's ``avconv`` command.
+          Fix a warning by requiring GStreamer >= 1.0.
+          Fix some Python 3 crashes with the new GStreamer backend (thanks to
+          @xix-xeaon).
+        
+        2.0.0
+          The GStreamer backend now uses GStreamer 1.x via the new
+          gobject-introspection API (and is compatible with Python 3).
+        
+        1.2.2
+          When running FFmpeg on Windows, disable its crash dialog. Thanks to
+          jcsaaddupuy.
+        
+        1.2.1
+          Fix an unhandled exception when opening non-raw audio files (thanks to
+          aostanin).
+          Fix Python 3 compatibility for the raw-file backend.
+        
+        1.2.0
+          Add support for FFmpeg on Windows (thanks to Jean-Christophe Saad-Dupuy).
+        
+        1.1.0
+          Add support for Sun/NeXT `Au files`_ via the standard-library ``sunau``
+          module (thanks to Dan Ellis).
+        
         1.0.3
           Use the rawread (standard-library) backend for .wav files.
         
@@ -129,6 +157,8 @@ Description: audioread
         0.1
           Initial release.
         
+        .. _Au files: http://en.wikipedia.org/wiki/Au_file_format
+        
         Et Cetera
         ---------
         
@@ -148,3 +178,4 @@ Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3.2
 Classifier: Programming Language :: Python :: 3.3
 Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
diff --git a/README.rst b/README.rst
index 1c2a2c3..b791c2f 100644
--- a/README.rst
+++ b/README.rst
@@ -7,11 +7,12 @@ audioread
 Decode audio files using whichever backend is available. The library
 currently supports:
 
-- `Gstreamer`_ via `gst-python`_.
+- `Gstreamer`_ via `PyGObject`_.
 - `Core Audio`_ on Mac OS X via `ctypes`_. (PyObjC not required.)
 - `MAD`_ via the `pymad`_ bindings.
-- `FFmpeg`_ via its command-line interface.
-- The standard library `wave`_ and `aifc`_ modules (for WAV and AIFF files).
+- `FFmpeg`_ or `Libav`_ via its command-line interface.
+- The standard library `wave`_, `aifc`_, and `sunau`_ modules (for
+  uncompressed audio formats).
 
 .. _Gstreamer: http://gstreamer.freedesktop.org/
 .. _gst-python: http://gstreamer.freedesktop.org/modules/gst-python.html
@@ -20,8 +21,11 @@ currently supports:
 .. _MAD: http://www.underbit.com/products/mad/
 .. _pymad: http://spacepants.org/src/pymad/
 .. _FFmpeg: http://ffmpeg.org/
+.. _Libav: https://www.libav.org/
 .. _wave: http://docs.python.org/library/wave.html
 .. _aifc: http://docs.python.org/library/aifc.html
+.. _sunau: http://docs.python.org/library/sunau.html
+.. _PyGObject: https://wiki.gnome.org/Projects/PyGObject
 
 Use the library like so::
 
@@ -50,16 +54,7 @@ unsupported by the backends; if the file doesn't exist, a standard ``IOError``
 will be raised.
 
 Audioread is "universal" and supports both Python 2 (2.6+) and Python 3
-(3.2+). The GStreamer backend currently only supports 2.x since gst-python is
-2.x-only.
-
-Future Work
------------
-
-Possible additional backends:
-
--  PyOgg?
--  Other command-line tools?
+(3.2+).
 
 Example
 -------
@@ -70,6 +65,39 @@ convert compressed audio files to WAV files.
 Version History
 ---------------
 
+2.1.2
+  Fix a file descriptor leak when opening and closing many files using
+  GStreamer.
+
+2.1.1
+  Just fix ReST formatting in the README.
+
+2.1.0
+  The FFmpeg backend can now also use Libav's ``avconv`` command.
+  Fix a warning by requiring GStreamer >= 1.0.
+  Fix some Python 3 crashes with the new GStreamer backend (thanks to
+  @xix-xeaon).
+
+2.0.0
+  The GStreamer backend now uses GStreamer 1.x via the new
+  gobject-introspection API (and is compatible with Python 3).
+
+1.2.2
+  When running FFmpeg on Windows, disable its crash dialog. Thanks to
+  jcsaaddupuy.
+
+1.2.1
+  Fix an unhandled exception when opening non-raw audio files (thanks to
+  aostanin).
+  Fix Python 3 compatibility for the raw-file backend.
+
+1.2.0
+  Add support for FFmpeg on Windows (thanks to Jean-Christophe Saad-Dupuy).
+
+1.1.0
+  Add support for Sun/NeXT `Au files`_ via the standard-library ``sunau``
+  module (thanks to Dan Ellis).
+
 1.0.3
   Use the rawread (standard-library) backend for .wav files.
 
@@ -121,6 +149,8 @@ Version History
 0.1
   Initial release.
 
+.. _Au files: http://en.wikipedia.org/wiki/Au_file_format
+
 Et Cetera
 ---------
 
diff --git a/audioread/__init__.py b/audioread/__init__.py
index 2a6e754..eecce32 100644
--- a/audioread/__init__.py
+++ b/audioread/__init__.py
@@ -8,30 +8,46 @@
 # distribute, sublicense, and/or sell copies of the Software, and to
 # permit persons to whom the Software is furnished to do so, subject to
 # the following conditions:
-# 
+#
 # The above copyright notice and this permission notice shall be
 # included in all copies or substantial portions of the Software.
 
 """Decode audio files."""
 
+
 class DecodeError(Exception):
     """The base exception class for all decoding errors raised by this
     package.
     """
 
+
 class NoBackendError(DecodeError):
     """The file could not be decoded by any backend. Either no backends
     are available or each available backend failed to decode the file.
     """
 
+
 def _gst_available():
-    """Determines whether pygstreamer is installed."""
+    """Determine whether Gstreamer and the Python GObject bindings are
+    installed.
+    """
     try:
-        import gst
+        import gi
+    except ImportError:
+        return False
+
+    try:
+        gi.require_version('Gst', '1.0')
+    except (ValueError, AttributeError):
+        return False
+
+    try:
+        from gi.repository import Gst  # noqa
     except ImportError:
         return False
-    else:
-        return True
+
+    return True
+
 
 def _ca_available():
     """Determines whether CoreAudio is available (i.e., we're running on
@@ -41,15 +57,17 @@ def _ca_available():
     lib = ctypes.util.find_library('AudioToolbox')
     return lib is not None
 
+
 def _mad_available():
     """Determines whether the pymad bindings are available."""
     try:
-        import mad
+        import mad  # noqa
     except ImportError:
         return False
     else:
         return True
 
+
 def audio_open(path):
     """Open an audio file using a library that is available on this
     system.
diff --git a/audioread/ffdec.py b/audioread/ffdec.py
index b9f4ff3..b6e0c4f 100644
--- a/audioread/ffdec.py
+++ b/audioread/ffdec.py
@@ -1,5 +1,5 @@
 # This file is part of audioread.
-# Copyright 2012, Adrian Sampson.
+# Copyright 2014, Adrian Sampson.
 #
 # Permission is hereby granted, free of charge, to any person obtaining
 # a copy of this software and associated documentation files (the
@@ -8,103 +8,178 @@
 # distribute, sublicense, and/or sell copies of the Software, and to
 # permit persons to whom the Software is furnished to do so, subject to
 # the following conditions:
-# 
+#
 # The above copyright notice and this permission notice shall be
 # included in all copies or substantial portions of the Software.
 
-"""Read audio data using the ffmpeg command line tools via a UNIX
-pipe.
+"""Read audio data using the ffmpeg command line tool via its standard
+output.
 """
+
+import sys
 import subprocess
 import re
 import threading
-import select
 import time
+try:
+    import queue
+except ImportError:
+    import Queue as queue
+
 from . import DecodeError
 
+COMMANDS = ('ffmpeg', 'avconv')
+
+
 class FFmpegError(DecodeError):
     pass
 
+
 class CommunicationError(FFmpegError):
     """Raised when the output of FFmpeg is not parseable."""
 
+
 class UnsupportedError(FFmpegError):
     """The file could not be decoded by FFmpeg."""
 
+
 class NotInstalledError(FFmpegError):
     """Could not find the ffmpeg binary."""
 
+
 class ReadTimeoutError(FFmpegError):
     """Reading from the ffmpeg command-line tool timed out."""
 
-class ReaderThread(threading.Thread):
-    """A thread that consumes data from a filehandle. This is used to ensure
-    that a buffer for an input stream never fills up.
+
+class QueueReaderThread(threading.Thread):
+    """A thread that consumes data from a filehandle and sends the data
+    over a Queue.
     """
-    # It may seem a little hacky, but this is the most straightforward &
-    # reliable way I can think of to do this. select() is sort of
-    # inefficient because it doesn't indicate how much is available to
-    # read -- so I end up reading character by character.
-    def __init__(self, fh, blocksize=1024):
-        super(ReaderThread, self).__init__()
+    def __init__(self, fh, blocksize=1024, discard=False):
+        super(QueueReaderThread, self).__init__()
         self.fh = fh
         self.blocksize = blocksize
         self.daemon = True
-        self.data = []
+        self.discard = discard
+        self.queue = None if discard else queue.Queue()
 
     def run(self):
         while True:
             data = self.fh.read(self.blocksize)
+            if not self.discard:
+                self.queue.put(data)
             if not data:
+                # Stream closed (EOF).
                 break
-            self.data.append(data)
+
+
+def popen_multiple(commands, command_args, *args, **kwargs):
+    """Like `subprocess.Popen`, but can try multiple commands in case
+    some are not available.
+
+    `commands` is an iterable of command names and `command_args` are
+    the rest of the arguments that, when appended to the command name,
+    make up the full first argument to `subprocess.Popen`. The
+    other positional and keyword arguments are passed through.
+    """
+    for i, command in enumerate(commands):
+        cmd = [command] + command_args
+        try:
+            return subprocess.Popen(cmd, *args, **kwargs)
+        except OSError:
+            if i == len(commands) - 1:
+                # No more commands to try.
+                raise
+
+
+# For Windows error switch management, we need a lock to keep the mode
+# adjustment atomic.
+windows_error_mode_lock = threading.Lock()
+
 
 class FFmpegAudioFile(object):
     """An audio file decoded by the ffmpeg command-line utility."""
-    def __init__(self, filename):
+    def __init__(self, filename, block_size=4096):
+        # On Windows, we need to disable the subprocess's crash dialog
+        # in case it dies. Passing SEM_NOGPFAULTERRORBOX to SetErrorMode
+        # disables this behavior.
+        windows = sys.platform.startswith("win")
+        if windows:
+            windows_error_mode_lock.acquire()
+            SEM_NOGPFAULTERRORBOX = 0x0002
+            import ctypes
+            # We call SetErrorMode in two steps to avoid overriding
+            # existing error mode.
+            previous_error_mode = \
+                ctypes.windll.kernel32.SetErrorMode(SEM_NOGPFAULTERRORBOX)
+            ctypes.windll.kernel32.SetErrorMode(
+                previous_error_mode | SEM_NOGPFAULTERRORBOX
+            )
+
         try:
-            self.proc = subprocess.Popen(
-                ['ffmpeg', '-i', filename, '-f', 's16le', '-'],
-                stdout=subprocess.PIPE, stderr=subprocess.PIPE
+            self.proc = popen_multiple(
+                COMMANDS,
+                ['-i', filename, '-f', 's16le', '-'],
+                stdout=subprocess.PIPE, stderr=subprocess.PIPE,
             )
+
         except OSError:
             raise NotInstalledError()
 
+        finally:
+            # Reset previous error mode on Windows. (We can change this
+            # back now because the flag was inherited by the subprocess;
+            # we don't need to keep it set in the parent process.)
+            if windows:
+                try:
+                    import ctypes
+                    ctypes.windll.kernel32.SetErrorMode(previous_error_mode)
+                finally:
+                    windows_error_mode_lock.release()
+
+        # Start another thread to consume the standard output of the
+        # process, which contains raw audio data.
+        self.stdout_reader = QueueReaderThread(self.proc.stdout, block_size)
+        self.stdout_reader.start()
+
         # Read relevant information from stderr.
         self._get_info()
 
         # Start a separate thread to read the rest of the data from
-        # stderr.
-        self.stderr_reader = ReaderThread(self.proc.stderr)
+        # stderr. This (a) avoids filling up the OS buffer and (b)
+        # collects the error output for diagnosis.
+        self.stderr_reader = QueueReaderThread(self.proc.stderr)
         self.stderr_reader.start()
 
-    def read_data(self, block_size=4096, timeout=10.0):
+    def read_data(self, timeout=10.0):
         """Read blocks of raw PCM data from the file."""
-        # Read from stdout on this thread.
+        # Read from stdout in a separate thread and consume data from
+        # the queue.
         start_time = time.time()
         while True:
             # Wait for data to be available or a timeout.
-            rready, _, xready = select.select((self.proc.stdout,),
-                                              (), (self.proc.stdout,),
-                                              timeout)
-            end_time = time.time()
-            if not rready and not xready:
-                if end_time - start_time >= timeout:
-                    # Nothing interesting has happened for a while --
-                    # FFmpeg is probably hanging.
-                    raise ReadTimeoutError(
-                        'ffmpeg output: %s' %
-                        ''.join(self.stderr_reader.data)
-                    )
+            data = None
+            try:
+                data = self.stdout_reader.queue.get(timeout=timeout)
+                if data:
+                    yield data
                 else:
-                    # Keep waiting.
-                    continue
-            start_time = end_time
-
-            data = self.proc.stdout.read(block_size)
-            if not data:
-                break
-            yield data
+                    # End of file.
+                    break
+            except queue.Empty:
+                # Queue read timed out.
+                end_time = time.time()
+                if not data:
+                    if end_time - start_time >= timeout:
+                        # Nothing interesting has happened for a while --
+                        # FFmpeg is probably hanging.
+                        raise ReadTimeoutError('ffmpeg output: {}'.format(
+                            ''.join(self.stderr_reader.queue.queue)
+                        ))
+                    else:
+                        start_time = end_time
+                        # Keep waiting.
+                        continue
 
     def _get_info(self):
         """Reads the tool's output from its stderr stream, extracts the
@@ -116,11 +191,11 @@ class FFmpegAudioFile(object):
             if not line:
                 # EOF and data not found.
                 raise CommunicationError("stream info not found")
-            
+
             # In Python 3, result of reading from stderr is bytes.
             if isinstance(line, bytes):
                 line = line.decode('utf8', 'ignore')
-                
+
             line = line.strip().lower()
 
             if 'no such file' in line:
@@ -166,10 +241,12 @@ class FFmpegAudioFile(object):
         )
         if match:
             durparts = list(map(int, match.groups()))
-            duration = durparts[0] * 60 * 60 + \
-                       durparts[1] * 60 + \
-                       durparts[2] + \
-                       float(durparts[3]) / 10
+            duration = (
+                durparts[0] * 60 * 60 +
+                durparts[1] * 60 +
+                durparts[2] +
+                float(durparts[3]) / 10
+            )
             self.duration = duration
         else:
             # No duration found.
@@ -177,11 +254,9 @@ class FFmpegAudioFile(object):
 
     def close(self):
         """Close the ffmpeg process used to perform the decoding."""
+        # Kill the process if it is still running.
         if hasattr(self, 'proc') and self.proc.returncode is None:
             self.proc.kill()
-            # Flush the stdout buffer (stderr already flushed).
-            stdout_reader = ReaderThread(self.proc.stdout)
-            stdout_reader.start()
             self.proc.wait()
 
     def __del__(self):
@@ -194,7 +269,7 @@ class FFmpegAudioFile(object):
     # Context manager.
     def __enter__(self):
         return self
+
     def __exit__(self, exc_type, exc_val, exc_tb):
         self.close()
         return False
-
diff --git a/audioread/gstdec.py b/audioread/gstdec.py
index fa8078f..c647a34 100644
--- a/audioread/gstdec.py
+++ b/audioread/gstdec.py
@@ -8,7 +8,7 @@
 # distribute, sublicense, and/or sell copies of the Software, and to
 # permit persons to whom the Software is furnished to do so, subject to
 # the following conditions:
-# 
+#
 # The above copyright notice and this permission notice shall be
 # included in all copies or substantial portions of the Software.
 
@@ -46,16 +46,28 @@ file:
     >>>     print f.duration
 """
 from __future__ import with_statement
+from __future__ import division
+
+import gi
+gi.require_version('Gst', '1.0')
+from gi.repository import GObject, Gst
 
-import gst
 import sys
-import gobject
 import threading
 import os
-import urllib
-import Queue
 from . import DecodeError
 
+try:
+    import queue
+except ImportError:
+    import Queue as queue
+
+try:
+    from urllib.parse import quote
+except ImportError:
+    from urllib import quote
+
+
 QUEUE_SIZE = 10
 BUFFER_SIZE = 10
 SENTINEL = '__GSTDEC_SENTINEL__'
@@ -66,6 +78,7 @@ SENTINEL = '__GSTDEC_SENTINEL__'
 class GStreamerError(DecodeError):
     pass
 
+
 class UnknownTypeError(GStreamerError):
     """Raised when Gstreamer can't decode the given file type."""
     def __init__(self, streaminfo):
@@ -74,10 +87,12 @@ class UnknownTypeError(GStreamerError):
         )
         self.streaminfo = streaminfo
 
+
 class FileReadError(GStreamerError):
     """Raised when the file can't be read at all."""
     pass
 
+
 class NoStreamError(GStreamerError):
     """Raised when the file was read successfully but no audio streams
     were found.
@@ -85,12 +100,14 @@ class NoStreamError(GStreamerError):
     def __init__(self):
         super(NoStreamError, self).__init__('no audio streams found')
 
+
 class MetadataMissingError(GStreamerError):
     """Raised when GStreamer fails to report stream metadata (duration,
     channels, or sample rate).
     """
     pass
 
+
 class IncompleteGStreamerError(GStreamerError):
     """Raised when necessary components of GStreamer (namely, the
     principal plugin packages) are missing.
@@ -105,7 +122,10 @@ class IncompleteGStreamerError(GStreamerError):
 
 _shared_loop_thread = None
 _loop_thread_lock = threading.RLock()
-gobject.threads_init()
+
+GObject.threads_init()
+Gst.init(None)
+
 def get_loop_thread():
     """Get the shared main-loop thread.
     """
@@ -116,15 +136,17 @@ def get_loop_thread():
             _shared_loop_thread = MainLoopThread()
             _shared_loop_thread.start()
         return _shared_loop_thread
+
+
 class MainLoopThread(threading.Thread):
     """A daemon thread encapsulating a Gobject main loop.
     """
-    def __init__(self):   
-        super(MainLoopThread, self).__init__()             
-        self.loop = gobject.MainLoop()
+    def __init__(self):
+        super(MainLoopThread, self).__init__()
+        self.loop = GObject.MainLoop()
         self.daemon = True
-        
-    def run(self):    
+
+    def run(self):
         self.loop.run()
 
 
@@ -133,18 +155,18 @@ class MainLoopThread(threading.Thread):
 class GstAudioFile(object):
     """Reads raw audio data from any audio file that Gstreamer
     knows how to decode.
-    
+
         >>> with GstAudioFile('something.mp3') as f:
         >>>     print f.samplerate
         >>>     print f.channels
         >>>     print f.duration
         >>>     for block in f:
         >>>         do_something(block)
-    
+
     Iterating the object yields blocks of 16-bit PCM data. Three
     pieces of stream information are also available: samplerate (in Hz),
     number of channels, and duration (in seconds).
-    
+
     It's very important that the client call close() when it's done
     with the object. Otherwise, the program is likely to hang on exit.
     Alternatively, of course, one can just use the file as a context
@@ -153,37 +175,39 @@ class GstAudioFile(object):
     def __init__(self, path):
         self.running = False
         self.finished = False
-        
+
         # Set up the Gstreamer pipeline.
-        self.pipeline = gst.Pipeline()
-        try:
-            self.dec = gst.element_factory_make("uridecodebin")
-            self.conv = gst.element_factory_make("audioconvert")
-            self.sink = gst.element_factory_make("appsink")
-        except gst.ElementNotFoundError:
+        self.pipeline = Gst.Pipeline()
+
+        self.dec = Gst.ElementFactory.make("uridecodebin", None)
+        self.conv = Gst.ElementFactory.make("audioconvert", None)
+        self.sink = Gst.ElementFactory.make("appsink", None)
+
+        if self.dec is None or self.conv is None or self.sink is None:
             # uridecodebin, audioconvert, or appsink is missing. We need
             # gst-plugins-base.
             raise IncompleteGStreamerError()
-        
+
         # Register for bus signals.
         bus = self.pipeline.get_bus()
         bus.add_signal_watch()
         bus.connect("message::eos", self._message)
         bus.connect("message::error", self._message)
-        
+
         # Configure the input.
-        uri = 'file://' + urllib.quote(os.path.abspath(path))
+        uri = 'file://' + quote(os.path.abspath(path))
         self.dec.set_property("uri", uri)
         # The callback to connect the input.
         self.dec.connect("pad-added", self._pad_added)
         self.dec.connect("no-more-pads", self._no_more_pads)
         # And a callback if decoding failes.
         self.dec.connect("unknown-type", self._unkown_type)
-        
+
         # Configure the output.
         # We want short integer data.
-        self.sink.set_property('caps',
-            gst.Caps('audio/x-raw-int, width=16, depth=16, signed=true')
+        self.sink.set_property(
+            'caps',
+            Gst.Caps.from_string('audio/x-raw, format=(string)S16LE'),
         )
         # TODO set endianness?
         # Set up the characteristics of the output. We don't want to
@@ -197,97 +221,103 @@ class GstAudioFile(object):
         self.sink.set_property('sync', False)
         # The callback to receive decoded data.
         self.sink.set_property('emit-signals', True)
-        self.sink.connect("new-buffer", self._new_buffer)
-        
+        self.sink.connect("new-sample", self._new_sample)
+
         # We'll need to know when the stream becomes ready and we get
         # its attributes. This semaphore will become available when the
         # caps are received. That way, when __init__() returns, the file
         # (and its attributes) will be ready for reading.
         self.ready_sem = threading.Semaphore(0)
-        self.caps_handler = self.sink.get_pad("sink").connect(
+        self.caps_handler = self.sink.get_static_pad("sink").connect(
             "notify::caps", self._notify_caps
         )
-        
+
         # Link up everything but the decoder (which must be linked only
         # when it becomes ready).
-        self.pipeline.add(self.dec, self.conv, self.sink)
+        self.pipeline.add(self.dec)
+        self.pipeline.add(self.conv)
+        self.pipeline.add(self.sink)
+
         self.conv.link(self.sink)
-        
+
         # Set up the queue for data and run the main thread.
-        self.queue = Queue.Queue(QUEUE_SIZE)
+        self.queue = queue.Queue(QUEUE_SIZE)
         self.thread = get_loop_thread()
-        
+
         # This wil get filled with an exception if opening fails.
         self.read_exc = None
-        
+
         # Return as soon as the stream is ready!
         self.running = True
         self.got_caps = False
-        self.pipeline.set_state(gst.STATE_PLAYING)
+        self.pipeline.set_state(Gst.State.PLAYING)
         self.ready_sem.acquire()
         if self.read_exc:
             # An error occurred before the stream became ready.
             self.close(True)
             raise self.read_exc
-    
-    
+
     # Gstreamer callbacks.
-    
+
     def _notify_caps(self, pad, args):
+        """The callback for the sinkpad's "notify::caps" signal.
+        """
         # The sink has started to receive data, so the stream is ready.
         # This also is our opportunity to read information about the
         # stream.
         self.got_caps = True
-        info = pad.get_negotiated_caps()[0]
-        
+        info = pad.get_current_caps().get_structure(0)
+
         # Stream attributes.
-        self.channels = info['channels']
-        self.samplerate = info['rate']
-        
+        self.channels = info.get_int('channels')[1]
+        self.samplerate = info.get_int('rate')[1]
+
         # Query duration.
-        q = gst.query_new_duration(gst.FORMAT_TIME)
-        if pad.get_peer().query(q):
-            # Success.
-            format, length = q.parse_duration()
-            if format == gst.FORMAT_TIME:
-                self.duration = float(length) / 1000000000
-            else:
-                self.read_exc = MetadataMissingError(
-                    'duration in unknown format'
-                )
+        success, length = pad.get_peer().query_duration(Gst.Format.TIME)
+        if success:
+            self.duration = length / 1000000000
         else:
-            # Query failed.
             self.read_exc = MetadataMissingError('duration not available')
-        
+
         # Allow constructor to complete.
         self.ready_sem.release()
-    
+
     _got_a_pad = False
+
     def _pad_added(self, element, pad):
+        """The callback for GstElement's "pad-added" signal.
+        """
         # Decoded data is ready. Connect up the decoder, finally.
-        name = pad.get_caps()[0].get_name()
-        if name.startswith('audio/x-raw-'):
-            nextpad = self.conv.get_pad('sink')
+        name = pad.query_caps(None).to_string()
+        if name.startswith('audio/x-raw'):
+            nextpad = self.conv.get_static_pad('sink')
             if not nextpad.is_linked():
                 self._got_a_pad = True
                 pad.link(nextpad)
-    
+
     def _no_more_pads(self, element):
+        """The callback for GstElement's "no-more-pads" signal.
+        """
         # Sent when the pads are done adding (i.e., there are no more
         # streams in the file). If we haven't gotten at least one
         # decodable stream, raise an exception.
         if not self._got_a_pad:
             self.read_exc = NoStreamError()
             self.ready_sem.release()  # No effect if we've already started.
-    
-    def _new_buffer(self, sink):
+
+    def _new_sample(self, sink):
+        """The callback for appsink's "new-sample" signal.
+        """
         if self.running:
             # New data is available from the pipeline! Dump it into our
             # queue (or possibly block if we're full).
-            buf = sink.emit('pull-buffer')
-            self.queue.put(str(buf))
-    
+            buf = sink.emit('pull-sample').get_buffer()
+            self.queue.put(buf.extract_dup(0, buf.get_size()))
+        return Gst.FlowReturn.OK
+
     def _unkown_type(self, uridecodebin, decodebin, caps):
+        """The callback for decodebin's "unknown-type" signal.
+        """
         # This is called *before* the stream becomes ready when the
         # file can't be read.
         streaminfo = caps.to_string()
@@ -296,10 +326,13 @@ class GstAudioFile(object):
             return
         self.read_exc = UnknownTypeError(streaminfo)
         self.ready_sem.release()
-    
+
     def _message(self, bus, message):
+        """The callback for GstBus's "message" signal (for two kinds of
+        messages).
+        """
         if not self.finished:
-            if message.type == gst.MESSAGE_EOS:
+            if message.type == Gst.MessageType.EOS:
                 # The file is done. Tell the consumer thread.
                 self.queue.put(SENTINEL)
                 if not self.got_caps:
@@ -308,7 +341,7 @@ class GstAudioFile(object):
                     self.read_exc = NoStreamError()
                     self.ready_sem.release()
 
-            elif message.type == gst.MESSAGE_ERROR:
+            elif message.type == Gst.MessageType.ERROR:
                 gerror, debug = message.parse_error()
                 if 'not-linked' in debug:
                     self.read_exc = NoStreamError()
@@ -317,8 +350,9 @@ class GstAudioFile(object):
                 else:
                     self.read_exc = FileReadError(debug)
                 self.ready_sem.release()
-    
+
     # Iteration.
+
     def next(self):
         # Wait for data from the Gstreamer callbacks.
         val = self.queue.get()
@@ -326,41 +360,61 @@ class GstAudioFile(object):
             # End of stream.
             raise StopIteration
         return val
+
+    # For Python 3 compatibility.
+    __next__ = next
+
     def __iter__(self):
         return self
-    
+
     # Cleanup.
     def close(self, force=False):
+        """Close the file and clean up associated resources.
+
+        Calling `close()` a second time has no effect.
+        """
         if self.running or force:
             self.running = False
             self.finished = True
 
+            # Unregister for signals, which we registered for above with
+            # `add_signal_watch`. (Without this, GStreamer leaks file
+            # descriptors.)
+            self.pipeline.get_bus().remove_signal_watch()
+
             # Stop reading the file.
             self.dec.set_property("uri", None)
             # Block spurious signals.
-            self.sink.get_pad("sink").disconnect(self.caps_handler)
+            self.sink.get_static_pad("sink").disconnect(self.caps_handler)
 
             # Make space in the output queue to let the decoder thread
             # finish. (Otherwise, the thread blocks on its enqueue and
             # the interpreter hangs.)
             try:
                 self.queue.get_nowait()
-            except Queue.Empty:
+            except queue.Empty:
... 199 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/audioread.git



More information about the Python-modules-commits mailing list