From bd12798a8435f4595a048bfa57e9de8ae57ccff4 Mon Sep 17 00:00:00 2001 From: user Date: Wed, 13 Jun 2018 21:12:50 +0300 Subject: [PATCH 1/9] Fix, replaced the choice of the key to get --- pydub/audio_segment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydub/audio_segment.py b/pydub/audio_segment.py index 278fdcf0..54cc5044 100644 --- a/pydub/audio_segment.py +++ b/pydub/audio_segment.py @@ -661,7 +661,7 @@ def is_format(f): 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]['sample_fmt'] == 'fltp' and + 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 From 06b99f1c80be177d66afd58df98eb6bab74a5b69 Mon Sep 17 00:00:00 2001 From: user Date: Thu, 14 Jun 2018 09:51:54 +0300 Subject: [PATCH 2/9] set default value Set default value in sample_fmt, bits_per_sample or bits_per_raw_sample --- pydub/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pydub/utils.py b/pydub/utils.py index 79efdd05..0920ea08 100644 --- a/pydub/utils.py +++ b/pydub/utils.py @@ -271,8 +271,8 @@ def mediainfo_json(filepath): return info # We just operate on the first audio stream in case there are more - stream = audio_streams[0] - + stream = {'sample_fmt': None, 'bits_per_sample': None, 'bits_per_raw_sample': None, **audio_streams[0]} + def set_property(stream, prop, value): if prop not in stream or stream[prop] == 0: stream[prop] = value From b7351473cd21ea93ca60816f9d81b7a8c7079ae0 Mon Sep 17 00:00:00 2001 From: users Date: Thu, 14 Jun 2018 10:21:35 +0300 Subject: [PATCH 3/9] fix json format --- pydub/utils.py | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/pydub/utils.py b/pydub/utils.py index 0920ea08..348ab63d 100644 --- a/pydub/utils.py +++ b/pydub/utils.py @@ -14,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", } @@ -78,7 +75,7 @@ def db_to_float(db, using_amplitude=True): db = float(db) if using_amplitude: return 10 ** (db / 20) - else: # using power + else: # using power return 10 ** (db / 10) @@ -92,33 +89,28 @@ def ratio_to_db(ratio, val2=None, using_amplitude=True): # 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 @@ -136,7 +128,6 @@ def make_chunks(audio_segment, chunk_length): """ Breaks an AudioSegment into chunks that are 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) """ @@ -149,7 +140,7 @@ def which(program): """ 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" @@ -174,6 +165,7 @@ def get_encoder_name(): 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 @@ -220,6 +212,19 @@ def fsdecode(filename): raise TypeError("type {0} not accepted by fsdecode".format(type(filename))) +def merge_two_dicts(x, y): + """ + Merge two dicts + + :type x: dict + :type y: dict + :rtype: dict + """ + z = x.copy() + z.update(y) + return z + + def mediainfo_json(filepath): """Return json dictionary with media info(codec, duration, size, bitrate...) from filepath """ @@ -271,8 +276,8 @@ def mediainfo_json(filepath): return info # We just operate on the first audio stream in case there are more - stream = {'sample_fmt': None, 'bits_per_sample': None, 'bits_per_raw_sample': None, **audio_streams[0]} - + stream = merge_two_dicts({'sample_fmt': None, 'bits_per_sample': None, 'bits_per_raw_sample': None}, audio_streams[0]) + def set_property(stream, prop, value): if prop not in stream or stream[prop] == 0: stream[prop] = value From 0ff1c54a97713546b143e71ff07a1b7ea911bf90 Mon Sep 17 00:00:00 2001 From: users Date: Thu, 14 Jun 2018 10:28:57 +0300 Subject: [PATCH 4/9] fix fun: `set_property` --- pydub/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydub/utils.py b/pydub/utils.py index 348ab63d..91001eae 100644 --- a/pydub/utils.py +++ b/pydub/utils.py @@ -279,7 +279,7 @@ def mediainfo_json(filepath): stream = merge_two_dicts({'sample_fmt': None, 'bits_per_sample': None, 'bits_per_raw_sample': None}, audio_streams[0]) def set_property(stream, prop, value): - if prop not in stream or stream[prop] == 0: + if prop not in stream or stream[prop] == 0 or stream[prop] is None: stream[prop] = value for token in extra_info[stream['index']]: From b257f44e61cd321ce96f68a562fa313ef4715006 Mon Sep 17 00:00:00 2001 From: users Date: Thu, 14 Jun 2018 11:39:01 +0300 Subject: [PATCH 5/9] debug in Linux --- pydub/audio_segment.py | 66 ++++++++++++++++++++++++------------------ pydub/utils.py | 11 +++++-- test/test.py | 46 +++++++++++++---------------- 3 files changed, 67 insertions(+), 56 deletions(-) diff --git a/pydub/audio_segment.py b/pydub/audio_segment.py index 54cc5044..90651abd 100644 --- a/pydub/audio_segment.py +++ b/pydub/audio_segment.py @@ -71,6 +71,7 @@ def setter(self, func): self.fset = func return self + def classproperty(func): if not isinstance(func, (classmethod, staticmethod)): func = classmethod(func) @@ -83,7 +84,6 @@ def classproperty(func): "wave": "wav", } - WavSubChunk = namedtuple('WavSubChunk', ['id', 'position', 'size']) WavData = namedtuple('WavData', ['audio_format', 'channels', 'sample_rate', 'bits_per_sample', 'raw_data']) @@ -209,10 +209,10 @@ def __init__(self, data=None, *args, **kwargs): 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 wav_data = read_wav_audio(data) @@ -257,7 +257,6 @@ def raw_data(self): """ return self._data - def get_array_of_samples(self): """ returns the raw_data as an array of samples @@ -290,7 +289,7 @@ def __getitem__(self, millisecond): 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))) ) @@ -468,7 +467,8 @@ def from_mono_audiosegments(cls, *mono_segments): 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 @@ -536,13 +536,13 @@ def is_format(f): except(OSError): input_file.flush() input_file.close() - input_file = NamedTemporaryFile(mode='wb', delete=False, buffering=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) + 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() @@ -583,7 +583,9 @@ def is_format(f): 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() @@ -662,8 +664,8 @@ def is_format(f): # 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"))): + (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'] @@ -688,7 +690,9 @@ def is_format(f): 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)) + 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) @@ -716,17 +720,19 @@ def from_wav(cls, file, parameters=None): @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) - obj = 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 @@ -804,9 +810,10 @@ def export(self, out_f=None, format='mp3', codec=None, bitrate=None, parameters= 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 @@ -835,7 +842,7 @@ def export(self, out_f=None, format='mp3', codec=None, bitrate=None, parameters= 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': @@ -856,7 +863,9 @@ def export(self, out_f=None, format='mp3', codec=None, bitrate=None, parameters= 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()) @@ -1114,11 +1123,11 @@ def overlay(self, seg, position=0, loop=False, times=None, gain_during_overlay=N 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) @@ -1219,7 +1228,7 @@ def fade(self, to_gain=0, from_gain=0, start=None, end=None, # 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 @@ -1266,14 +1275,15 @@ def reverse(self): ) def _repr_html_(self): - src = """ + src = """ """ - 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 --git a/pydub/utils.py b/pydub/utils.py index 91001eae..24ba26f3 100644 --- a/pydub/utils.py +++ b/pydub/utils.py @@ -263,6 +263,13 @@ def mediainfo_json(filepath): # from lines of the format of: # ' Stream #0:0: Audio: flac, 88200 Hz, stereo, s32 (24 bit)' extra_info = {} + + # TODO Debug in Linux (get stderr) + print('TODO Debug in Linux (get stderr)') + print('='* 100) + print(stderr) + print('='* 100) + for line in stderr.split("\n"): match = re.match(' *Stream #0[:\.]([0-9]+)(\(\w+\))?', line) if match: @@ -276,10 +283,10 @@ def mediainfo_json(filepath): return info # We just operate on the first audio stream in case there are more - stream = merge_two_dicts({'sample_fmt': None, 'bits_per_sample': None, 'bits_per_raw_sample': None}, audio_streams[0]) + stream = merge_two_dicts({'sample_fmt': 0, 'bits_per_sample': 0, 'bits_per_raw_sample': 0}, audio_streams[0]) def set_property(stream, prop, value): - if prop not in stream or stream[prop] == 0 or stream[prop] is None: + if prop not in stream or stream[prop] == 0: stream[prop] = value for token in extra_info[stream['index']]: diff --git a/test/test.py b/test/test.py index 377a2230..415f14c1 100644 --- a/test/test.py +++ b/test/test.py @@ -3,9 +3,9 @@ import sys import unittest from tempfile import ( - NamedTemporaryFile, - mkdtemp, - gettempdir + NamedTemporaryFile, + mkdtemp, + gettempdir ) import tempfile import struct @@ -55,12 +55,14 @@ def test_db_float_conversions(self): self.assertEqual(3, db_to_float(ratio_to_db(3, using_amplitude=False), using_amplitude=False)) 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 @@ -135,7 +137,7 @@ def assertWithinRange(self, val, lower_bound, upper_bound): (val, lower_bound, upper_bound)) def assertWithinTolerance(self, val, expected, tolerance=None, - percentage=None): + percentage=None): if percentage is not None: tolerance = val * percentage lower_bound = val - tolerance @@ -156,7 +158,7 @@ def test_export_pathlib_path(self): percentage=0.01) finally: os.unlink(path) - + class FileAccessTests(unittest.TestCase): @@ -182,6 +184,7 @@ class AudioSegmentTests(unittest.TestCase): 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')) @@ -218,7 +221,8 @@ def assertWithinTolerance(self, val, expected, tolerance=None, 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) @@ -476,7 +480,6 @@ def test_split_to_mono(self): 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 @@ -558,7 +561,8 @@ def test_export_as_wav(self): 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), @@ -716,7 +720,6 @@ def test_export_mp3_as_webm(self): AudioSegment.from_file(self.mp3_file_path).export(tmp_webm_file, format="webm") - def test_export_mp4_as_ogg(self): with NamedTemporaryFile('w+b', suffix='.ogg') as tmp_ogg_file: AudioSegment.from_file(self.mp4_file_path).export(tmp_ogg_file, @@ -911,14 +914,12 @@ def test_invert(self): 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) @@ -926,7 +927,7 @@ def test_invert(self): 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) @@ -934,7 +935,6 @@ def test_invert(self): 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) @@ -1002,6 +1002,7 @@ def test_from_file_clean_fail(self): tempfile.tempdir = orig_tmpdir os.rmdir(new_tmpdir) + class SilenceTests(unittest.TestCase): def setUp(self): @@ -1144,7 +1145,7 @@ def test_opening_mp3_file_fails(self): 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) @@ -1152,25 +1153,20 @@ def test_init_AudioSegment_data_buffer(self): 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")) @@ -1215,8 +1211,6 @@ def test_lowpass_filter_cutoff_frequency(self): self.assertAlmostEqual(less_treble.dBFS, s.dBFS, places=0) - - if __name__ == "__main__": import sys From 19b0ca217cd93c586e1652ad54ac58624019627b Mon Sep 17 00:00:00 2001 From: users Date: Thu, 14 Jun 2018 13:22:18 +0300 Subject: [PATCH 6/9] update re and parsing --- pydub/utils.py | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/pydub/utils.py b/pydub/utils.py index 24ba26f3..b3c0714c 100644 --- a/pydub/utils.py +++ b/pydub/utils.py @@ -1,6 +1,6 @@ from __future__ import division -from math import log, ceil, floor +from math import log, ceil import os import re from subprocess import Popen, PIPE @@ -263,27 +263,23 @@ def mediainfo_json(filepath): # from lines of the format of: # ' Stream #0:0: Audio: flac, 88200 Hz, stereo, s32 (24 bit)' extra_info = {} - - # TODO Debug in Linux (get stderr) - print('TODO Debug in Linux (get stderr)') - print('='* 100) - print(stderr) - print('='* 100) - - for line in stderr.split("\n"): - match = re.match(' *Stream #0[:\.]([0-9]+)(\(\w+\))?', line) - if match: - stream_id = int(match.group(1)) - tokens = [x.strip() - for x in re.split('[:,]', line[match.end():]) if x] - extra_info[stream_id] = tokens + + re_stream = r'(?P +)Stream #0[:\.](?P([0-9]+))(?P.+)\n?((?P +)(?P.+))?' + + for i in re.finditer(re_stream, stderr): + if len(i.group('space_start')) <= len(i.group('space_end')): + content_line = '{},{}'.format(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 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 = merge_two_dicts({'sample_fmt': 0, 'bits_per_sample': 0, 'bits_per_raw_sample': 0}, audio_streams[0]) + stream = audio_streams[0] def set_property(stream, prop, value): if prop not in stream or stream[prop] == 0: @@ -308,7 +304,6 @@ def set_property(stream, prop, value): set_property(stream, 'sample_fmt', token) set_property(stream, 'bits_per_sample', 64) set_property(stream, 'bits_per_raw_sample', 64) - return info From 37d7e3a0cecb81f7ab8eba3ed512c65d80aa2dd9 Mon Sep 17 00:00:00 2001 From: users Date: Thu, 14 Jun 2018 13:27:22 +0300 Subject: [PATCH 7/9] fix --- pydub/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydub/utils.py b/pydub/utils.py index b3c0714c..0ba62b75 100644 --- a/pydub/utils.py +++ b/pydub/utils.py @@ -267,7 +267,7 @@ def mediainfo_json(filepath): re_stream = r'(?P +)Stream #0[:\.](?P([0-9]+))(?P.+)\n?((?P +)(?P.+))?' for i in re.finditer(re_stream, stderr): - if len(i.group('space_start')) <= len(i.group('space_end')): + if i.group('space_end') is not None and len(i.group('space_start')) <= len(i.group('space_end')): content_line = '{},{}'.format(i.group('content_0'), i.group('content_1')) else: content_line = i.group('content_0') From f5792be90ef00a05c828622706b18950a0bfc2d2 Mon Sep 17 00:00:00 2001 From: users Date: Thu, 14 Jun 2018 14:55:30 +0300 Subject: [PATCH 8/9] refactoring and add macOS to travis --- .travis.yml | 4 ++++ pydub/utils.py | 54 +++++++++++++++++++++++++------------------------- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0be37e47..e4a5396a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,7 @@ +os: + - linux + - osx + sudo: required dist: trusty language: python diff --git a/pydub/utils.py b/pydub/utils.py index 0ba62b75..def1a72e 100644 --- a/pydub/utils.py +++ b/pydub/utils.py @@ -1,13 +1,13 @@ from __future__ import division -from math import log, ceil +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 -import json try: import audioop @@ -212,17 +212,31 @@ def fsdecode(filename): raise TypeError("type {0} not accepted by fsdecode".format(type(filename))) -def merge_two_dicts(x, y): +def get_extra_info(stderr): """ - Merge two dicts - - :type x: dict - :type y: dict - :rtype: dict + 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 """ - z = x.copy() - z.update(y) - return z + extra_info = {} + + re_stream = r'(?P +)Stream #0[:\.](?P([0-9]+))(?P.+)\n?((?P +)(?P.+))?' + 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): @@ -258,21 +272,7 @@ def mediainfo_json(filepath): # (for example, because the file doesn't exist) return info - # avprobe sometimes gives more information on stderr than - # on the json output. The information has to be extracted - # from lines of the format of: - # ' Stream #0:0: Audio: flac, 88200 Hz, stereo, s32 (24 bit)' - extra_info = {} - - re_stream = r'(?P +)Stream #0[:\.](?P([0-9]+))(?P.+)\n?((?P +)(?P.+))?' - - 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 = '{},{}'.format(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 + extra_info = get_extra_info(stderr) audio_streams = [x for x in info['streams'] if x['codec_type'] == 'audio'] if len(audio_streams) == 0: From 3dcbee71c1ce541f2b08002ca10702fcb3e1aee3 Mon Sep 17 00:00:00 2001 From: users Date: Thu, 14 Jun 2018 15:04:40 +0300 Subject: [PATCH 9/9] delete macOS --- .travis.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index e4a5396a..0be37e47 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,3 @@ -os: - - linux - - osx - sudo: required dist: trusty language: python