Hello community,

here is the log from the commit of package python-pydub for openSUSE:Factory 
checked in at 2018-07-17 09:42:11
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pydub (Old)
 and      /work/SRC/openSUSE:Factory/.python-pydub.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-pydub"

Tue Jul 17 09:42:11 2018 rev:2 rq:623065 version:0.22.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pydub/python-pydub.changes        
2018-05-15 10:33:20.164333417 +0200
+++ /work/SRC/openSUSE:Factory/.python-pydub.new/python-pydub.changes   
2018-07-17 09:43:33.637034268 +0200
@@ -1,0 +2,16 @@
+Wed Jul 11 17:18:18 UTC 2018 - alarr...@suse.com
+
+- Update to 0.22.1
+  * Fix pydub.utils.mediainfo_json() to work with newer,
+    backwards-incompatible versions of ffprobe/avprobe
+
+- Update to 0.22.0
+  * Adds support for audio with frame rates (sample rates) of 48k and higher
+    (requires scipy) (PR #262, fixes #134, #237, #209)
+  * Adds support for PEP 519 File Path protocol (PR #252)
+  * Fixes a few places where handles to temporary files are kept open (PR #280)
+  * Add the license file to the python package to aid other packaging projects
+    (PR #279, fixes #274)
+  * Big fix for pydub.silence.detect_silence() (PR #263)
+
+-------------------------------------------------------------------

Old:
----
  pydub-0.21.0.tar.gz

New:
----
  pydub-0.22.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-pydub.spec ++++++
--- /var/tmp/diff_new_pack.kHUTY0/_old  2018-07-17 09:43:34.029032831 +0200
+++ /var/tmp/diff_new_pack.kHUTY0/_new  2018-07-17 09:43:34.029032831 +0200
@@ -20,7 +20,7 @@
 # Test data missing
 %bcond_with     test
 Name:           python-pydub
-Version:        0.21.0
+Version:        0.22.1
 Release:        0
 Summary:        Manipulate audio with Python
 License:        MIT
@@ -31,12 +31,13 @@
 BuildRequires:  %{python_module devel}
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  fdupes
-BuildRequires:  ffmpeg
 BuildRequires:  python-rpm-macros
 %if %{with test}
 BuildRequires:  %{python_module scipy}
+BuildRequires:  ffmpeg
 %endif
-Recommends:     python-scipt
+Recommends:     python-scipy
+Requires:       ffmpeg
 BuildArch:      noarch
 
 %python_subpackages

++++++ pydub-0.21.0.tar.gz -> pydub-0.22.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pydub-0.21.0/PKG-INFO new/pydub-0.22.1/PKG-INFO
--- old/pydub-0.21.0/PKG-INFO   2018-02-22 15:07:37.000000000 +0100
+++ new/pydub-0.22.1/PKG-INFO   2018-06-16 00:47:27.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: pydub
-Version: 0.21.0
+Version: 0.22.1
 Summary: Manipulate audio with an simple and easy high level interface
 Home-page: http://pydub.com
 Author: James Robert
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pydub-0.21.0/pydub/audio_segment.py 
new/pydub-0.22.1/pydub/audio_segment.py
--- old/pydub-0.21.0/pydub/audio_segment.py     2018-02-22 15:06:07.000000000 
+0100
+++ new/pydub-0.22.1/pydub/audio_segment.py     2018-06-16 00:46:47.000000000 
+0200
@@ -8,7 +8,9 @@
 import sys
 import struct
 from .logging_utils import log_conversion, log_subprocess_output
+from .utils import mediainfo_json, fsdecode
 import base64
+from collections import namedtuple
 
 try:
     from StringIO import StringIO
@@ -69,6 +71,7 @@
         self.fset = func
         return self
 
+
 def classproperty(func):
     if not isinstance(func, (classmethod, staticmethod)):
         func = classmethod(func)
@@ -81,6 +84,66 @@
     "wave": "wav",
 }
 
+WavSubChunk = namedtuple('WavSubChunk', ['id', 'position', 'size'])
+WavData = namedtuple('WavData', ['audio_format', 'channels', 'sample_rate',
+                                 'bits_per_sample', 'raw_data'])
+
+
+def extract_wav_headers(data):
+    # def search_subchunk(data, subchunk_id):
+    pos = 12  # The size of the RIFF chunk descriptor
+    subchunks = []
+    while pos + 8 < len(data) and len(subchunks) < 10:
+        subchunk_id = data[pos:pos + 4]
+        subchunk_size = struct.unpack_from('<I', data[pos + 4:pos + 8])[0]
+        subchunks.append(WavSubChunk(subchunk_id, pos, subchunk_size))
+        if subchunk_id == b'data':
+            # 'data' is the last subchunk
+            break
+        pos += subchunk_size + 8
+
+    return subchunks
+
+
+def read_wav_audio(data, headers=None):
+    if not headers:
+        headers = extract_wav_headers(data)
+
+    fmt = [x for x in headers if x.id == b'fmt ']
+    if not fmt or fmt[0].size < 16:
+        raise CouldntDecodeError("Couldn't find fmt header in wav data")
+    fmt = fmt[0]
+    pos = fmt.position + 8
+    audio_format = struct.unpack_from('<H', data[pos:pos + 2])[0]
+    if audio_format != 1 and audio_format != 0xFFFE:
+        raise CouldntDecodeError("Unknown audio format 0x%X in wav data" %
+                                 audio_format)
+
+    channels = struct.unpack_from('<H', data[pos + 2:pos + 4])[0]
+    sample_rate = struct.unpack_from('<I', data[pos + 4:pos + 8])[0]
+    bits_per_sample = struct.unpack_from('<H', data[pos + 14:pos + 16])[0]
+
+    data_hdr = headers[-1]
+    if data_hdr.id != b'data':
+        raise CouldntDecodeError("Couldn't find data header in wav data")
+
+    pos = data_hdr.position + 8
+    return WavData(audio_format, channels, sample_rate, bits_per_sample,
+                   data[pos:pos + data_hdr.size])
+
+
+def fix_wav_headers(data):
+    headers = extract_wav_headers(data)
+    if not headers or headers[-1].id != b'data':
+        return
+
+    # Set the file size in the RIFF chunk descriptor
+    data[4:8] = struct.pack('<I', len(data) - 8)
+
+    # Set the data size in the data subchunk
+    pos = headers[-1].position
+    data[pos + 4:pos + 8] = struct.pack('<I', len(data) - pos - 8)
+
 
 class AudioSegment(object):
     """
@@ -146,25 +209,21 @@
                 data = data if isinstance(data, (basestring, bytes)) else 
data.read()
             except(OSError):
                 d = b''
-                reader = data.read(2**31-1)
+                reader = data.read(2 ** 31 - 1)
                 while reader:
                     d += reader
-                    reader = data.read(2**31-1)
+                    reader = data.read(2 ** 31 - 1)
                 data = d
 
-            raw = wave.open(StringIO(data), 'rb')
-
-            raw.rewind()
-            self.channels = raw.getnchannels()
-            self.sample_width = raw.getsampwidth()
-            self.frame_rate = raw.getframerate()
+            wav_data = read_wav_audio(data)
+            if not wav_data:
+                raise CouldntDecodeError("Couldn't read wav audio from data")
+
+            self.channels = wav_data.channels
+            self.sample_width = wav_data.bits_per_sample // 8
+            self.frame_rate = wav_data.sample_rate
             self.frame_width = self.channels * self.sample_width
-
-            raw.rewind()
-
-            # the "or b''" base case is a work-around for a python 3.4
-            # see https://github.com/jiaaro/pydub/pull/107
-            self._data = raw.readframes(float('inf')) or b''
+            self._data = wav_data.raw_data
 
         # Convert 24-bit audio to 32-bit audio.
         # (stdlib audioop and array modules do not support 24-bit data)
@@ -185,7 +244,6 @@
                 old_bytes = struct.pack(pack_fmt, b0, b1, b2)
                 byte_buffer.write(old_bytes)
 
-
             self._data = byte_buffer.getvalue()
             self.sample_width = 4
             self.frame_width = self.channels * self.sample_width
@@ -199,7 +257,6 @@
         """
         return self._data
 
-
     def get_array_of_samples(self):
         """
         returns the raw_data as an array of samples
@@ -232,7 +289,7 @@
         if isinstance(millisecond, slice):
             if millisecond.step:
                 return (
-                    self[i:i+millisecond.step]
+                    self[i:i + millisecond.step]
                     for i in xrange(*millisecond.indices(len(self)))
                 )
 
@@ -410,7 +467,8 @@
         segs = cls._sync(*mono_segments)
 
         if segs[0].channels != 1:
-            raise ValueError("AudioSegment.from_mono_audiosegments requires 
all arguments are mono AudioSegment instances")
+            raise ValueError(
+                "AudioSegment.from_mono_audiosegments requires all arguments 
are mono AudioSegment instances")
 
         channels = len(segs)
         sample_width = segs[0].sample_width
@@ -433,7 +491,7 @@
         )
 
     @classmethod
-    def from_file(cls, file, format=None, codec=None, parameters=None, 
**kwargs):
+    def from_file_using_temporary_files(cls, file, format=None, codec=None, 
parameters=None, **kwargs):
         orig_file = file
         file = _fd_or_path_or_tempfile(file, 'rb', tempfile=False)
 
@@ -447,11 +505,15 @@
                 return True
             if isinstance(orig_file, basestring):
                 return orig_file.lower().endswith(".{0}".format(f))
+            if isinstance(orig_file, bytes):
+                return 
orig_file.lower().endswith((".{0}".format(f)).encode('utf8'))
             return False
 
         if is_format("wav"):
             try:
-                return cls._from_safe_wav(file)
+                obj = cls._from_safe_wav(file)
+                file.close()
+                return obj
             except:
                 file.seek(0)
         elif is_format("raw") or is_format("pcm"):
@@ -464,7 +526,9 @@
                 'channels': channels,
                 'frame_width': channels * sample_width
             }
-            return cls(data=file.read(), metadata=metadata)
+            obj = cls(data=file.read(), metadata=metadata)
+            file.close()
+            return obj
 
         input_file = NamedTemporaryFile(mode='wb', delete=False)
         try:
@@ -472,13 +536,15 @@
         except(OSError):
             input_file.flush()
             input_file.close()
-            input_file = NamedTemporaryFile(mode='wb', delete=False, 
buffering=2**31-1)
-            file = open(orig_file, buffering=2**13-1, mode='rb')
-            reader = file.read(2**31-1)
+            input_file = NamedTemporaryFile(mode='wb', delete=False, 
buffering=2 ** 31 - 1)
+            file.close()
+            file = open(orig_file, buffering=2 ** 13 - 1, mode='rb')
+            reader = file.read(2 ** 31 - 1)
             while reader:
                 input_file.write(reader)
-                reader = file.read(2**31-1)
+                reader = file.read(2 ** 31 - 1)
         input_file.flush()
+        file.close()
 
         output = NamedTemporaryFile(mode="rb", delete=False)
 
@@ -517,7 +583,9 @@
 
         try:
             if p.returncode != 0:
-                raise CouldntDecodeError("Decoding failed. ffmpeg returned 
error code: {0}\n\nOutput from ffmpeg/avlib:\n\n{1}".format(p.returncode, 
p_err))
+                raise CouldntDecodeError(
+                    "Decoding failed. ffmpeg returned error code: 
{0}\n\nOutput from ffmpeg/avlib:\n\n{1}".format(
+                        p.returncode, p_err))
             obj = cls._from_safe_wav(output)
         finally:
             input_file.close()
@@ -528,6 +596,113 @@
         return obj
 
     @classmethod
+    def from_file(cls, file, format=None, codec=None, parameters=None, 
**kwargs):
+        orig_file = file
+        try:
+            filename = fsdecode(file)
+        except TypeError:
+            filename = None
+        file = _fd_or_path_or_tempfile(file, 'rb', tempfile=False)
+
+        if format:
+            format = format.lower()
+            format = AUDIO_FILE_EXT_ALIASES.get(format, format)
+
+        def is_format(f):
+            f = f.lower()
+            if format == f:
+                return True
+
+            if filename:
+                return filename.lower().endswith(".{0}".format(f))
+
+            return False
+
+        if is_format("wav"):
+            try:
+                return cls._from_safe_wav(file)
+            except:
+                file.seek(0)
+        elif is_format("raw") or is_format("pcm"):
+            sample_width = kwargs['sample_width']
+            frame_rate = kwargs['frame_rate']
+            channels = kwargs['channels']
+            metadata = {
+                'sample_width': sample_width,
+                'frame_rate': frame_rate,
+                'channels': channels,
+                'frame_width': channels * sample_width
+            }
+            return cls(data=file.read(), metadata=metadata)
+
+        conversion_command = [cls.converter,
+                              '-y',  # always overwrite existing files
+                              ]
+
+        # If format is not defined
+        # ffmpeg/avconv will detect it automatically
+        if format:
+            conversion_command += ["-f", format]
+
+        if codec:
+            # force audio decoder
+            conversion_command += ["-acodec", codec]
+
+        if filename:
+            conversion_command += ["-i", filename]
+            stdin_parameter = None
+            stdin_data = None
+        else:
+            conversion_command += ["-i", "-"]
+            stdin_parameter = subprocess.PIPE
+            stdin_data = file.read()
+
+        info = mediainfo_json(orig_file)
+        if info:
+            audio_streams = [x for x in info['streams']
+                             if x['codec_type'] == 'audio']
+            # This is a workaround for some ffprobe versions that always say
+            # that mp3/mp4/aac/webm/ogg files contain fltp samples
+            if (audio_streams[0].get('sample_fmt') == 'fltp' and
+                    (is_format("mp3") or is_format("mp4") or is_format("aac") 
or
+                     is_format("webm") or is_format("ogg"))):
+                bits_per_sample = 16
+            else:
+                bits_per_sample = audio_streams[0]['bits_per_sample']
+            acodec = 'pcm_s%dle' % bits_per_sample
+            conversion_command += ["-acodec", acodec]
+
+        conversion_command += [
+            "-vn",  # Drop any video streams if there are any
+            "-f", "wav",  # output options (filename last)
+            "-"
+        ]
+
+        if parameters is not None:
+            # extend arguments with arbitrary set
+            conversion_command.extend(parameters)
+
+        log_conversion(conversion_command)
+
+        p = subprocess.Popen(conversion_command, stdin=stdin_parameter,
+                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        p_out, p_err = p.communicate(input=stdin_data)
+
+        if p.returncode != 0 or len(p_out) == 0:
+            file.close()
+            raise CouldntDecodeError(
+                "Decoding failed. ffmpeg returned error code: {0}\n\nOutput 
from ffmpeg/avlib:\n\n{1}".format(
+                    p.returncode, p_err))
+
+        p_out = bytearray(p_out)
+        fix_wav_headers(p_out)
+        obj = cls._from_safe_wav(BytesIO(p_out))
+
+        file.close()
+
+        return obj
+
+    @classmethod
     def from_mp3(cls, file, parameters=None):
         return cls.from_file(file, 'mp3', parameters)
 
@@ -545,20 +720,25 @@
 
     @classmethod
     def from_raw(cls, file, **kwargs):
-        return cls.from_file(file, 'raw', sample_width=kwargs['sample_width'], 
frame_rate=kwargs['frame_rate'], channels=kwargs['channels'])
+        return cls.from_file(file, 'raw', sample_width=kwargs['sample_width'], 
frame_rate=kwargs['frame_rate'],
+                             channels=kwargs['channels'])
 
     @classmethod
     def _from_safe_wav(cls, file):
         file = _fd_or_path_or_tempfile(file, 'rb', tempfile=False)
         file.seek(0)
-        return cls(data=file)
+        obj = cls(data=file)
+        file.close()
+        return obj
 
-    def export(self, out_f=None, format='mp3', codec=None, bitrate=None, 
parameters=None, tags=None, id3v2_version='4', cover=None):
+    def export(self, out_f=None, format='mp3', codec=None, bitrate=None, 
parameters=None, tags=None, id3v2_version='4',
+               cover=None):
         """
         Export an AudioSegment to a file with given options
 
         out_f (string):
-            Path to destination audio file
+            Path to destination audio file. Also accepts os.PathLike objects on
+            python >= 3.6
 
         format (string)
             Format for destination audio file.
@@ -630,9 +810,10 @@
 
         if cover is not None:
             if cover.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', 
'.tif', '.tiff')) and format == "mp3":
-                conversion_command.extend(["-i" , cover, "-map", "0", "-map", 
"1", "-c:v", "mjpeg"])
+                conversion_command.extend(["-i", cover, "-map", "0", "-map", 
"1", "-c:v", "mjpeg"])
             else:
-                raise AttributeError("Currently cover images are only 
supported by MP3 files. The allowed image formats are: .tif, .jpg, .bmp, .jpeg 
and .png.")
+                raise AttributeError(
+                    "Currently cover images are only supported by MP3 files. 
The allowed image formats are: .tif, .jpg, .bmp, .jpeg and .png.")
 
         if codec is not None:
             # force audio encoder
@@ -661,7 +842,7 @@
                         raise InvalidID3TagVersion(
                             "id3v2_version not allowed, allowed versions: %s" 
% id3v2_allowed_versions)
                     conversion_command.extend([
-                        "-id3v2_version",  id3v2_version
+                        "-id3v2_version", id3v2_version
                     ])
 
         if sys.platform == 'darwin':
@@ -682,7 +863,9 @@
         log_subprocess_output(p_err)
 
         if p.returncode != 0:
-            raise CouldntEncodeError("Encoding failed. ffmpeg/avlib returned 
error code: {0}\n\nCommand:{1}\n\nOutput from 
ffmpeg/avlib:\n\n{2}".format(p.returncode, conversion_command, p_err))
+            raise CouldntEncodeError(
+                "Encoding failed. ffmpeg/avlib returned error code: 
{0}\n\nCommand:{1}\n\nOutput from ffmpeg/avlib:\n\n{2}".format(
+                    p.returncode, conversion_command, p_err))
 
         output.seek(0)
         out_f.write(output.read())
@@ -940,11 +1123,11 @@
             if gain_during_overlay:
                 seg1_overlaid = seg1[pos:pos + seg2_len]
                 seg1_adjusted_gain = audioop.mul(seg1_overlaid, 
self.sample_width,
-                                               
db_to_float(float(gain_during_overlay)))
+                                                 
db_to_float(float(gain_during_overlay)))
                 output.write(audioop.add(seg1_adjusted_gain, seg2, 
sample_width))
             else:
                 output.write(audioop.add(seg1[pos:pos + seg2_len], seg2,
-                                     sample_width))
+                                         sample_width))
             pos += seg2_len
 
             # dec times to break our while loop (eventually)
@@ -978,7 +1161,9 @@
         output.write(seg2[crossfade:]._data)
 
         output.seek(0)
-        return seg1._spawn(data=output)
+        obj = seg1._spawn(data=output)
+        output.close()
+        return obj
 
     def fade(self, to_gain=0, from_gain=0, start=None, end=None,
              duration=None):
@@ -1043,7 +1228,7 @@
 
         # fades longer than 100ms can use coarse fading (one gain step per ms),
         # shorter fades will have audible clicks so they use precise fading
-        #(one gain step per sample)
+        # (one gain step per sample)
         if duration > 100:
             scale_step = gain_delta / duration
 
@@ -1090,14 +1275,15 @@
         )
 
     def _repr_html_(self):
-            src = """
+        src = """
                     <audio controls>
                         <source src="data:audio/mpeg;base64,{base64}" 
type="audio/mpeg"/>
                         Your browser does not support the audio element.
                     </audio>
                   """
-            fh = self.export()
-            data = base64.b64encode(fh.read()).decode('ascii')
-            return src.format(base64=data)
+        fh = self.export()
+        data = base64.b64encode(fh.read()).decode('ascii')
+        return src.format(base64=data)
+
 
 from . import effects
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pydub-0.21.0/pydub/pyaudioop.py 
new/pydub-0.22.1/pydub/pyaudioop.py
--- old/pydub-0.21.0/pydub/pyaudioop.py 2017-01-06 14:35:20.000000000 +0100
+++ new/pydub-0.22.1/pydub/pyaudioop.py 2018-05-24 18:05:38.000000000 +0200
@@ -1,4 +1,9 @@
-import __builtin__
+try:
+    from __builtin__ import max as builtin_max
+    from __builtin__ import min as builtin_min
+except ImportError:
+    from builtins import max as builtin_max
+    from builtins import min as builtin_min
 import math
 import struct
 from fractions import gcd
@@ -79,7 +84,7 @@
 def _get_clipfn(size, signed=True):
     maxval = _get_maxval(size, signed)
     minval = _get_minval(size, signed)
-    return lambda val: __builtin__.max(min(val, maxval), minval)
+    return lambda val: builtin_max(min(val, maxval), minval)
 
 
 def _overflow(val, size, signed=True):
@@ -109,7 +114,7 @@
     if len(cp) == 0:
         return 0
 
-    return __builtin__.max(abs(sample) for sample in _get_samples(cp, size))
+    return builtin_max(abs(sample) for sample in _get_samples(cp, size))
 
 
 def minmax(cp, size):
@@ -117,8 +122,8 @@
 
     max_sample, min_sample = 0, 0
     for sample in _get_samples(cp, size):
-        max_sample = __builtin__.max(sample, max_sample)
-        min_sample = __builtin__.min(sample, min_sample)
+        max_sample = builtin_max(sample, max_sample)
+        min_sample = builtin_min(sample, min_sample)
 
     return min_sample, max_sample
 
@@ -542,4 +547,4 @@
 
 
 def adpcm2lin(cp, size, state):
-    raise NotImplementedError()
\ No newline at end of file
+    raise NotImplementedError()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pydub-0.21.0/pydub/silence.py 
new/pydub-0.22.1/pydub/silence.py
--- old/pydub-0.21.0/pydub/silence.py   2018-02-22 15:06:07.000000000 +0100
+++ new/pydub-0.22.1/pydub/silence.py   2018-05-24 18:04:53.000000000 +0200
@@ -42,7 +42,7 @@
     current_range_start = prev_i
 
     for silence_start_i in silence_starts:
-        continuous = (silence_start_i == prev_i + 1)
+        continuous = (silence_start_i == prev_i + seek_step)
 
         # sometimes two small blips are enough for one particular slice to be
         # non-silent, despite the silence all running together. Just combine
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pydub-0.21.0/pydub/utils.py 
new/pydub-0.22.1/pydub/utils.py
--- old/pydub-0.21.0/pydub/utils.py     2017-05-10 01:01:52.000000000 +0200
+++ new/pydub-0.22.1/pydub/utils.py     2018-06-16 00:46:47.000000000 +0200
@@ -1,10 +1,11 @@
 from __future__ import division
 
-from math import log, ceil, floor
+import json
 import os
 import re
-from subprocess import Popen, PIPE
 import sys
+from subprocess import Popen, PIPE
+from math import log, ceil
 from tempfile import TemporaryFile
 from warnings import warn
 
@@ -13,19 +14,16 @@
 except ImportError:
     import pyaudioop as audioop
 
-
 if sys.version_info >= (3, 0):
     basestring = str
 
-
-
 FRAME_WIDTHS = {
     8: 1,
     16: 2,
     32: 4,
 }
 ARRAY_TYPES = {
-    8:  "b",
+    8: "b",
     16: "h",
     32: "i",
 }
@@ -58,6 +56,14 @@
     if isinstance(fd, basestring):
         fd = open(fd, mode=mode)
 
+    try:
+        if isinstance(fd, os.PathLike):
+            fd = open(fd, mode=mode)
+    except AttributeError:
+        # module os has no attribute PathLike, so we're on python < 3.6.
+        # The protocol we're trying to support doesn't exist, so just pass.
+        pass
+
     return fd
 
 
@@ -69,7 +75,7 @@
     db = float(db)
     if using_amplitude:
         return 10 ** (db / 20)
-    else: # using power
+    else:  # using power
         return 10 ** (db / 10)
 
 
@@ -83,33 +89,28 @@
     # accept 2 values and use the ratio of val1 to val2
     if val2 is not None:
         ratio = ratio / val2
-    
+
     # special case for multiply-by-zero (convert to silence)
     if ratio == 0:
         return -float('inf')
 
     if using_amplitude:
         return 20 * log(ratio, 10)
-    else: # using power
+    else:  # using power
         return 10 * log(ratio, 10)
-    
+
 
 def register_pydub_effect(fn, name=None):
     """
     decorator for adding pydub effects to the AudioSegment objects.
-
     example use:
-
         @register_pydub_effect
         def normalize(audio_segment):
             ...
-
     or you can specify a name:
-
         @register_pydub_effect("normalize")
         def normalize_audio_segment(audio_segment):
             ...
-
     """
     if isinstance(fn, basestring):
         name = fn
@@ -127,7 +128,6 @@
     """
     Breaks an AudioSegment into chunks that are <chunk_length> milliseconds
     long.
-
     if chunk_length is 50 then you'll get a list of 50 millisecond long audio
     segments back (except the last one, which can be shorter)
     """
@@ -140,7 +140,7 @@
     """
     Mimics behavior of UNIX which command.
     """
-    #Add .exe program extension for windows support
+    # Add .exe program extension for windows support
     if os.name == "nt" and not program.endswith(".exe"):
         program += ".exe"
 
@@ -165,6 +165,7 @@
         warn("Couldn't find ffmpeg or avconv - defaulting to ffmpeg, but may 
not work", RuntimeWarning)
         return "ffmpeg"
 
+
 def get_player_name():
     """
     Return enconder default application for system, either avconv or ffmpeg
@@ -193,12 +194,123 @@
         return "ffprobe"
 
 
+def fsdecode(filename):
+    """Wrapper for os.fsdecode which was introduced in python 3.2 ."""
+
+    if sys.version_info >= (3, 2):
+        PathLikeTypes = (basestring, bytes)
+        if sys.version_info >= (3, 6):
+            PathLikeTypes += (os.PathLike,)
+        if isinstance(filename, PathLikeTypes):
+            return os.fsdecode(filename)
+    else:
+        if isinstance(filename, bytes):
+            return filename.decode(sys.getfilesystemencoding())
+        if isinstance(filename, basestring):
+            return filename
+
+    raise TypeError("type {0} not accepted by fsdecode".format(type(filename)))
+
+
+def get_extra_info(stderr):
+    """
+    avprobe sometimes gives more information on stderr than
+    on the json output. The information has to be extracted
+    from stderr of the format of:
+    '    Stream #0:0: Audio: flac, 88200 Hz, stereo, s32 (24 bit)'
+    or (macOS version):
+    '    Stream #0:0: Audio: vorbis'
+    '      44100 Hz, stereo, fltp, 320 kb/s'
+
+    :type stderr: str
+    :rtype: list of dict
+    """
+    extra_info = {}
+
+    re_stream = r'(?P<space_start> +)Stream 
#0[:\.](?P<stream_id>([0-9]+))(?P<content_0>.+)\n?((?P<space_end> 
+)(?P<content_1>.+))?'
+    for i in re.finditer(re_stream, stderr):
+        if i.group('space_end') is not None and len(i.group('space_start')) <= 
len(
+                i.group('space_end')):
+            content_line = ','.join([i.group('content_0'), 
i.group('content_1')])
+        else:
+            content_line = i.group('content_0')
+        tokens = [x.strip() for x in re.split('[:,]', content_line) if x]
+        extra_info[int(i.group('stream_id'))] = tokens
+    return extra_info
+
+
+def mediainfo_json(filepath):
+    """Return json dictionary with media info(codec, duration, size, 
bitrate...) from filepath
+    """
+    prober = get_prober_name()
+    command_args = [
+        "-v", "info",
+        "-show_format",
+        "-show_streams",
+    ]
+    try:
+        command_args += [fsdecode(filepath)]
+        stdin_parameter = None
+        stdin_data = None
+    except TypeError:
+        command_args += ["-"]
+        stdin_parameter = PIPE
+        file = _fd_or_path_or_tempfile(filepath, 'rb', tempfile=False)
+        file.seek(0)
+        stdin_data = file.read()
+
+    command = [prober, '-of', 'json'] + command_args
+    res = Popen(command, stdin=stdin_parameter, stdout=PIPE, stderr=PIPE)
+    output, stderr = res.communicate(input=stdin_data)
+    output = output.decode("utf-8", 'ignore')
+    stderr = stderr.decode("utf-8", 'ignore')
+
+    info = json.loads(output)
+
+    if not info:
+        # If ffprobe didn't give any information, just return it
+        # (for example, because the file doesn't exist)
+        return info
+
+    extra_info = get_extra_info(stderr)
+
+    audio_streams = [x for x in info['streams'] if x['codec_type'] == 'audio']
+    if len(audio_streams) == 0:
+        return info
+
+    # We just operate on the first audio stream in case there are more
+    stream = audio_streams[0]
+
+    def set_property(stream, prop, value):
+        if prop not in stream or stream[prop] == 0:
+            stream[prop] = value
+
+    for token in extra_info[stream['index']]:
+        m = re.match('([su]([0-9]{1,2})p?) \(([0-9]{1,2}) bit\)$', token)
+        m2 = re.match('([su]([0-9]{1,2})p?)( \(default\))?$', token)
+        if m:
+            set_property(stream, 'sample_fmt', m.group(1))
+            set_property(stream, 'bits_per_sample', int(m.group(2)))
+            set_property(stream, 'bits_per_raw_sample', int(m.group(3)))
+        elif m2:
+            set_property(stream, 'sample_fmt', m2.group(1))
+            set_property(stream, 'bits_per_sample', int(m2.group(2)))
+            set_property(stream, 'bits_per_raw_sample', int(m2.group(2)))
+        elif re.match('(flt)p?( \(default\))?$', token):
+            set_property(stream, 'sample_fmt', token)
+            set_property(stream, 'bits_per_sample', 32)
+            set_property(stream, 'bits_per_raw_sample', 32)
+        elif re.match('(dbl)p?( \(default\))?$', token):
+            set_property(stream, 'sample_fmt', token)
+            set_property(stream, 'bits_per_sample', 64)
+            set_property(stream, 'bits_per_raw_sample', 64)
+    return info
+
+
 def mediainfo(filepath):
     """Return dictionary with media info(codec, duration, size, bitrate...) 
from filepath
     """
 
-    from .audio_segment import AudioSegment
-
     prober = get_prober_name()
     command_args = [
         "-v", "quiet",
@@ -238,4 +350,4 @@
             else:
                 info[key] = value
 
-    return info
\ No newline at end of file
+    return info
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pydub-0.21.0/pydub.egg-info/PKG-INFO 
new/pydub-0.22.1/pydub.egg-info/PKG-INFO
--- old/pydub-0.21.0/pydub.egg-info/PKG-INFO    2018-02-22 15:07:37.000000000 
+0100
+++ new/pydub-0.22.1/pydub.egg-info/PKG-INFO    2018-06-16 00:47:27.000000000 
+0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: pydub
-Version: 0.21.0
+Version: 0.22.1
 Summary: Manipulate audio with an simple and easy high level interface
 Home-page: http://pydub.com
 Author: James Robert
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pydub-0.21.0/setup.py new/pydub-0.22.1/setup.py
--- old/pydub-0.21.0/setup.py   2018-02-22 15:06:52.000000000 +0100
+++ new/pydub-0.22.1/setup.py   2018-06-16 00:44:56.000000000 +0200
@@ -8,7 +8,7 @@
 
 setup(
     name='pydub',
-    version='0.21.0',
+    version='0.22.1',
     author='James Robert',
     author_email='jia...@gmail.com',
     description='Manipulate audio with an simple and easy high level 
interface',
@@ -17,6 +17,9 @@
     url='http://pydub.com',
     packages=['pydub'],
     long_description=__doc__,
+    package_data={
+        '': ['LICENSE'],
+    },
     classifiers=[
         'Development Status :: 5 - Production/Stable',
         'License :: OSI Approved :: MIT License',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pydub-0.21.0/test/test.py 
new/pydub-0.22.1/test/test.py
--- old/pydub-0.21.0/test/test.py       2018-02-22 15:06:07.000000000 +0100
+++ new/pydub-0.22.1/test/test.py       2018-06-16 00:46:47.000000000 +0200
@@ -3,14 +3,15 @@
 import sys
 import unittest
 from tempfile import (
-        NamedTemporaryFile,
-        mkdtemp,
-        gettempdir
+    NamedTemporaryFile,
+    mkdtemp,
+    gettempdir
 )
 import tempfile
 import struct
 
 from pydub import AudioSegment
+from pydub.audio_segment import extract_wav_headers
 from pydub.utils import (
     db_to_float,
     ratio_to_db,
@@ -55,6 +56,110 @@
         self.assertEqual(12, ratio_to_db(db_to_float(12, 
using_amplitude=False), using_amplitude=False))
 
 
+if sys.version_info >= (3, 6):
+    class PathLikeObjectTests(unittest.TestCase):
+
+        class MyPathLike:
+            def __init__(self, path):
+                self.path = path
+
+            def __fspath__(self):
+                return self.path
+
+        def setUp(self):
+            self.mp3_path_str = os.path.join(data_dir, 'test1.mp3')
+
+            from pathlib import Path
+            self.mp3_pathlib_path = Path(self.mp3_path_str)
+
+            self.mp3_path_like_str = self.MyPathLike(self.mp3_path_str)
+            self.mp3_path_like_bytes = 
self.MyPathLike(bytes(self.mp3_path_str, sys.getdefaultencoding()))
+
+        def test_audio_segment_from_pathlib_path(self):
+            seg1 = AudioSegment.from_file(self.mp3_path_str)
+            seg2 = AudioSegment.from_file(self.mp3_pathlib_path)
+
+            self.assertEqual(len(seg1), len(seg2))
+            self.assertEqual(seg1._data, seg2._data)
+            self.assertTrue(len(seg1) > 0)
+
+        def test_audio_segment_from_path_like_str(self):
+            seg1 = AudioSegment.from_file(self.mp3_path_str)
+            seg2 = AudioSegment.from_file(self.mp3_path_like_str)
+
+            self.assertEqual(len(seg1), len(seg2))
+            self.assertEqual(seg1._data, seg2._data)
+            self.assertTrue(len(seg1) > 0)
+
+        def test_audio_segment_from_path_like_bytes(self):
+            seg1 = AudioSegment.from_file(self.mp3_path_str)
+            seg2 = AudioSegment.from_file(self.mp3_path_like_bytes)
+
+            self.assertEqual(len(seg1), len(seg2))
+            self.assertEqual(seg1._data, seg2._data)
+            self.assertTrue(len(seg1) > 0)
+
+        def test_non_existant_pathlib_path(self):
+            from pathlib import Path
+            path = Path('this/path/should/not/exist/do/not/make/this/exist')
+            with self.assertRaises(FileNotFoundError):
+                _ = AudioSegment.from_file(path)
+
+            path = Path('')
+            # On Unicies this will raise a IsADirectoryError, on Windows this
+            # will result in a PermissionError. Both of these are subclasses of
+            # OSError. We aren't so much worried about the specific exception
+            # here, just that reading a file from an empty path is an error.
+            with self.assertRaises(OSError):
+                _ = AudioSegment.from_file(path)
+
+        def test_non_existant_path_like_str(self):
+            path = 
self.MyPathLike('this/path/should/not/exist/do/not/make/this/exist')
+            with self.assertRaises(FileNotFoundError):
+                _ = AudioSegment.from_file(path)
+
+            path = self.MyPathLike('')
+            with self.assertRaises(FileNotFoundError):
+                _ = AudioSegment.from_file(path)
+
+        def test_non_existant_path_like_bytes(self):
+            path = 
self.MyPathLike(bytes('this/path/should/not/exist/do/not/make/this/exist', 
sys.getdefaultencoding()))
+            with self.assertRaises(FileNotFoundError):
+                _ = AudioSegment.from_file(path)
+
+            path = self.MyPathLike(bytes('', sys.getdefaultencoding()))
+            with self.assertRaises(FileNotFoundError):
+                _ = AudioSegment.from_file(path)
+
+        def assertWithinRange(self, val, lower_bound, upper_bound):
+            self.assertTrue(lower_bound < val < upper_bound,
+                            "%s is not in the acceptable range: %s - %s" %
+                            (val, lower_bound, upper_bound))
+
+        def assertWithinTolerance(self, val, expected, tolerance=None,
+                                  percentage=None):
+            if percentage is not None:
+                tolerance = val * percentage
+            lower_bound = val - tolerance
+            upper_bound = val + tolerance
+            self.assertWithinRange(val, lower_bound, upper_bound)
+
+        def test_export_pathlib_path(self):
+            seg1 = AudioSegment.from_file(self.mp3_path_str)
+            from pathlib import Path
+            path = Path(tempfile.gettempdir()) / 'pydub-test-export-8ajds.mp3'
+            try:
+                seg1.export(path, format='mp3')
+                seg2 = AudioSegment.from_file(path, format='mp3')
+
+                self.assertTrue(len(seg1) > 0)
+                self.assertWithinTolerance(len(seg1),
+                                           len(seg2),
+                                           percentage=0.01)
+            finally:
+                os.unlink(path)
+
+
 class FileAccessTests(unittest.TestCase):
 
     def setUp(self):
@@ -79,6 +184,7 @@
     def setUp(self):
         global test1, test2, test3, testparty, testdcoffset
         if not test1:
+            a = os.path.join(data_dir, 'test1.mp3')
             test1 = AudioSegment.from_mp3(os.path.join(data_dir, 'test1.mp3'))
             test2 = AudioSegment.from_mp3(os.path.join(data_dir, 'test2.mp3'))
             test3 = AudioSegment.from_mp3(os.path.join(data_dir, 'test3.mp3'))
@@ -115,7 +221,8 @@
         self.assertWithinRange(val, lower_bound, upper_bound)
 
     def test_direct_instantiation_with_bytes(self):
-        seg = AudioSegment(b'RIFF\x28\x00\x00\x00WAVEfmt 
\x10\x00\x00\x00\x01\x00\x02\x00\x00}\x00\x00\x00\xf4\x01\x00\x04\x00\x10\x00data\x04\x00\x00\x00\x00\x00\x00\x00')
+        seg = AudioSegment(
+            b'RIFF\x28\x00\x00\x00WAVEfmt 
\x10\x00\x00\x00\x01\x00\x02\x00\x00}\x00\x00\x00\xf4\x01\x00\x04\x00\x10\x00data\x04\x00\x00\x00\x00\x00\x00\x00')
         self.assertEqual(seg.frame_count(), 1)
         self.assertEqual(seg.channels, 2)
         self.assertEqual(seg.sample_width, 2)
@@ -134,6 +241,31 @@
         # the data length should have grown by exactly 4:3 (24 bits turn into 
32 bits)
         self.assertEqual(len(seg24.raw_data) * 3, len24 * 4)
 
+    def test_192khz_audio(self):
+        test_files = [('test-192khz-16bit.wav', 16),
+                      ('test-192khz-24bit.wav', 32),
+                      ('test-192khz-32bit.flac', 32),
+                      ('test-192khz-32bit.wav', 32),
+                      ('test-192khz-64bit.wav', 64)]
+        base_file, bit_depth = test_files[0]
+        path = os.path.join(data_dir, base_file)
+        base = AudioSegment.from_file(path)
+
+        headers = extract_wav_headers(open(path, 'rb').read())
+        data16_size = headers[-1].size
+        self.assertEqual(len(base.raw_data), data16_size)
+        self.assertEqual(base.frame_rate, 192000)
+        self.assertEqual(base.sample_width, bit_depth / 8)
+
+        for test_file, bit_depth in test_files[1:]:
+            path = os.path.join(data_dir, test_file)
+            seg = AudioSegment.from_file(path)
+            self.assertEqual(seg.sample_width, bit_depth / 8)
+            self.assertEqual(seg.frame_rate, 192000)
+            self.assertEqual(len(seg.raw_data), len(base.raw_data) *
+                             seg.sample_width / base.sample_width)
+            self.assertEqual(seg.frame_rate, 192000)
+
     def test_concat(self):
         catted_audio = self.seg1 + self.seg2
 
@@ -348,7 +480,6 @@
         self.assertEqual(seg_lchannel.frame_count(), seg.frame_count())
         self.assertEqual(seg_rchannel.frame_count(), seg.frame_count())
 
-
     def test_apply_gain_stereo(self):
         seg = self.seg1
 
@@ -430,7 +561,8 @@
     def test_export_as_raw(self):
         seg = self.seg1
         exported_raw = seg.export(format='raw')
-        seg_exported_raw = AudioSegment.from_raw(exported_raw, 
sample_width=seg.sample_width, frame_rate=seg.frame_rate, channels = 
seg.channels)
+        seg_exported_raw = AudioSegment.from_raw(exported_raw, 
sample_width=seg.sample_width, frame_rate=seg.frame_rate,
+                                                 channels=seg.channels)
 
         self.assertWithinTolerance(len(seg_exported_raw),
                                    len(seg),
@@ -525,10 +657,12 @@
             if sys.platform == 'win32':
                 tmp_mp3_file.close()
 
-            seg.export(tmp_mp3_file.name)
+            fd = seg.export(tmp_mp3_file.name)
+            fd.close()
 
             for i in range(3):
-                
AudioSegment.from_mp3(tmp_mp3_file.name).export(tmp_mp3_file.name, "mp3")
+                fd = 
AudioSegment.from_mp3(tmp_mp3_file.name).export(tmp_mp3_file.name, "mp3")
+                fd.close()
 
             tmp_seg = AudioSegment.from_mp3(tmp_mp3_file.name)
             self.assertFalse(len(tmp_seg) < len(seg))
@@ -780,14 +914,12 @@
         else:
             raise Exception("AudioSegment.invert_phase() didn't catch a bad 
input (mono)")
 
-
         s_inv = s.invert_phase()
         self.assertFalse(s == s_inv)
         self.assertTrue(s.rms == s_inv.rms)
         self.assertTrue(s == s_inv.invert_phase())
 
-
-        s_inv_right = s.invert_phase(channels=(0,1))
+        s_inv_right = s.invert_phase(channels=(0, 1))
         left, right = s_inv_right.split_to_mono()
 
         self.assertFalse(s_mono == s_inv_right)
@@ -795,7 +927,7 @@
         self.assertTrue(left == s_mono)
         self.assertFalse(right == s_mono)
 
-        s_inv_left = s.invert_phase(channels=(1,0))
+        s_inv_left = s.invert_phase(channels=(1, 0))
         left, right = s_inv_left.split_to_mono()
 
         self.assertFalse(s_mono == s_inv_left)
@@ -803,7 +935,6 @@
         self.assertFalse(left == s_mono)
         self.assertTrue(right == s_mono)
 
-
     def test_max_dBFS(self):
         sine_0_dbfs = Sine(1000).to_audio_segment()
         sine_minus_3_dbfs = Sine(1000).to_audio_segment(volume=-3.0)
@@ -871,6 +1002,7 @@
         tempfile.tempdir = orig_tmpdir
         os.rmdir(new_tmpdir)
 
+
 class SilenceTests(unittest.TestCase):
 
     def setUp(self):
@@ -1013,7 +1145,7 @@
         self.assertRaises(OSError, func)
 
     def test_init_AudioSegment_data_buffer(self):
-        seg = AudioSegment(data = "\0" * 34, sample_width=2, frame_rate=4, 
channels=1)
+        seg = AudioSegment(data="\0" * 34, sample_width=2, frame_rate=4, 
channels=1)
 
         self.assertEqual(seg.duration_seconds, 4.25)
 
@@ -1021,25 +1153,20 @@
 
         self.assertEqual(seg.frame_rate, 4)
 
-
     def test_init_AudioSegment_data_buffer_with_missing_args_fails(self):
-
-        func = partial(AudioSegment, data = "\0" * 16, sample_width=2, 
frame_rate=2)
+        func = partial(AudioSegment, data="\0" * 16, sample_width=2, 
frame_rate=2)
         self.assertRaises(MissingAudioParameter, func)
 
-        func = partial(AudioSegment, data = "\0" * 16, sample_width=2, 
channels=1)
+        func = partial(AudioSegment, data="\0" * 16, sample_width=2, 
channels=1)
         self.assertRaises(MissingAudioParameter, func)
 
-        func = partial(AudioSegment, data = "\0" * 16, frame_rate=2, 
channels=1)
+        func = partial(AudioSegment, data="\0" * 16, frame_rate=2, channels=1)
         self.assertRaises(MissingAudioParameter, func)
 
-
     def test_init_AudioSegment_data_buffer_with_bad_values_fails(self):
-
-        func = partial(AudioSegment, data = "\0" * 14, sample_width=4, 
frame_rate=2, channels=1)
+        func = partial(AudioSegment, data="\0" * 14, sample_width=4, 
frame_rate=2, channels=1)
         self.assertRaises(ValueError, func)
 
-
     def test_exporting(self):
         seg = AudioSegment.from_wav(self.wave_file)
         exported = AudioSegment.from_wav(seg.export(format="wav"))
@@ -1084,8 +1211,6 @@
         self.assertAlmostEqual(less_treble.dBFS, s.dBFS, places=0)
 
 
-
-
 if __name__ == "__main__":
     import sys
 


Reply via email to