diff -Nru python-pydub-0.23.1/debian/changelog python-pydub-0.24.1/debian/changelog --- python-pydub-0.23.1/debian/changelog 2019-09-06 01:30:06.000000000 +0000 +++ python-pydub-0.24.1/debian/changelog 2021-01-13 00:48:26.000000000 +0000 @@ -1,3 +1,14 @@ +python-pydub (0.24.1-1) unstable; urgency=medium + + * New upstream release (0.24.1-1) + * Bump debhelper-compat to (= 13) + * Mark autopkgtest as superficial (Closes: #969860) + * debian/watch: Bump file standard version to 4 + * Bump Standards-Version to 4.5.1. No changes required + * debian/control: Document Rules-Requires root as 'no' + + -- Josue Ortega Tue, 12 Jan 2021 18:48:26 -0600 + python-pydub (0.23.1-2) unstable; urgency=medium * Drop Python 2 version diff -Nru python-pydub-0.23.1/debian/control python-pydub-0.24.1/debian/control --- python-pydub-0.23.1/debian/control 2019-09-06 01:30:06.000000000 +0000 +++ python-pydub-0.24.1/debian/control 2021-01-13 00:48:26.000000000 +0000 @@ -2,17 +2,18 @@ Maintainer: Josue Ortega Section: python Priority: optional -Build-Depends: debhelper-compat (= 12) +Build-Depends: debhelper-compat (= 13) Build-Depends-Indep: dh-python, python3-all, python3-setuptools, ffmpeg, X-Python3-Version: >= 3.6 -Standards-Version: 4.4.0 +Standards-Version: 4.5.1 Homepage: http://pydub.com/ Vcs-Browser: https://salsa.debian.org/debian/python-pydub Vcs-Git: https://salsa.debian.org/debian/python-pydub.git +Rules-Requires-Root: no Package: python3-pydub Architecture: all diff -Nru python-pydub-0.23.1/debian/tests/control python-pydub-0.24.1/debian/tests/control --- python-pydub-0.23.1/debian/tests/control 2019-09-06 01:30:06.000000000 +0000 +++ python-pydub-0.24.1/debian/tests/control 2021-01-13 00:48:26.000000000 +0000 @@ -1,2 +1,3 @@ Test-Command: python3 -c "from pydub import AudioSegment" Depends: ffmpeg, python3-pydub +Restrictions: superficial diff -Nru python-pydub-0.23.1/debian/watch python-pydub-0.24.1/debian/watch --- python-pydub-0.23.1/debian/watch 2019-09-06 01:30:06.000000000 +0000 +++ python-pydub-0.24.1/debian/watch 2021-01-13 00:48:26.000000000 +0000 @@ -1,3 +1,3 @@ -version=3 +version=4 opts=uversionmangle=s/(rc|a|b|c)/~$1/ \ https://pypi.debian.net/pydub/pydub-(.+)\.(?:zip|tgz|tbz|txz|(?:tar\.(?:gz|bz2|xz))) diff -Nru python-pydub-0.23.1/PKG-INFO python-pydub-0.24.1/PKG-INFO --- python-pydub-0.23.1/PKG-INFO 2019-01-22 17:02:26.000000000 +0000 +++ python-pydub-0.24.1/PKG-INFO 2020-06-03 15:23:55.875622000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: pydub -Version: 0.23.1 +Version: 0.24.1 Summary: Manipulate audio with an simple and easy high level interface Home-page: http://pydub.com Author: James Robert @@ -17,13 +17,13 @@ Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 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 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 Classifier: Intended Audience :: Developers Classifier: Operating System :: OS Independent Classifier: Topic :: Multimedia :: Sound/Audio diff -Nru python-pydub-0.23.1/pydub/audio_segment.py python-pydub-0.24.1/pydub/audio_segment.py --- python-pydub-0.23.1/pydub/audio_segment.py 2019-01-22 16:48:20.000000000 +0000 +++ python-pydub-0.24.1/pydub/audio_segment.py 2020-06-02 14:40:35.000000000 +0000 @@ -93,7 +93,7 @@ # 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: + while pos + 8 <= len(data) and len(subchunks) < 10: subchunk_id = data[pos:pos + 4] subchunk_size = struct.unpack_from(' 2**32: + raise CouldntDecodeError("Unable to process >4GB files") + # Set the file size in the RIFF chunk descriptor data[4:8] = struct.pack(' (s0,s1), (s1,s2), (s2, s3), ..." + a, b = itertools.tee(iterable) + next(b, None) + return zip(a, b) + + if isinstance(keep_silence, bool): + keep_silence = len(audio_segment) if keep_silence else 0 + + output_ranges = [ + [ start - keep_silence, end + keep_silence ] + for (start,end) + in detect_nonsilent(audio_segment, min_silence_len, silence_thresh, seek_step) + ] + + for range_i, range_ii in pairwise(output_ranges): + last_end = range_i[1] + next_start = range_ii[0] + if next_start < last_end: + range_i[1] = (last_end+next_start)//2 + range_ii[0] = range_i[1] + + return [ + audio_segment[ max(start,0) : min(end,len(audio_segment)) ] + for start,end in output_ranges + ] + + +def detect_leading_silence(sound, silence_threshold=-50.0, chunk_size=10): + ''' + sound is a pydub.AudioSegment + silence_threshold in dB + chunk_size in ms + iterate over chunks until you find the first one with sound + ''' + trim_ms = 0 # ms + assert chunk_size > 0 # to avoid infinite loop + while sound[trim_ms:trim_ms+chunk_size].dBFS < silence_threshold and trim_ms < len(sound): + trim_ms += chunk_size - chunks = [] - for start_i, end_i in not_silence_ranges: - start_i = max(0, start_i - keep_silence) - end_i += keep_silence + return trim_ms - chunks.append(audio_segment[start_i:end_i]) - return chunks diff -Nru python-pydub-0.23.1/pydub/utils.py python-pydub-0.24.1/pydub/utils.py --- python-pydub-0.23.1/pydub/utils.py 2018-06-15 22:46:47.000000000 +0000 +++ python-pydub-0.24.1/pydub/utils.py 2020-03-21 22:50:50.000000000 +0000 @@ -8,6 +8,7 @@ from math import log, ceil from tempfile import TemporaryFile from warnings import warn +from functools import wraps try: import audioop @@ -50,21 +51,25 @@ def _fd_or_path_or_tempfile(fd, mode='w+b', tempfile=True): + close_fd = False if fd is None and tempfile: fd = TemporaryFile(mode=mode) + close_fd = True if isinstance(fd, basestring): fd = open(fd, mode=mode) + close_fd = True try: if isinstance(fd, os.PathLike): fd = open(fd, mode=mode) + close_fd = True 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 + return fd, close_fd def db_to_float(db, using_amplitude=True): @@ -227,7 +232,7 @@ """ extra_info = {} - re_stream = r'(?P +)Stream #0[:\.](?P([0-9]+))(?P.+)\n?((?P +)(?P.+))?' + re_stream = r'(?P +)Stream #0[:\.](?P([0-9]+))(?P.+)\n?(?! *Stream)((?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')): @@ -239,7 +244,7 @@ return extra_info -def mediainfo_json(filepath): +def mediainfo_json(filepath, read_ahead_limit=-1): """Return json dictionary with media info(codec, duration, size, bitrate...) from filepath """ prober = get_prober_name() @@ -253,11 +258,17 @@ stdin_parameter = None stdin_data = None except TypeError: - command_args += ["-"] + if prober == 'ffprobe': + command_args += ["-read_ahead_limit", str(read_ahead_limit), + "cache:pipe:0"] + else: + command_args += ["-"] stdin_parameter = PIPE - file = _fd_or_path_or_tempfile(filepath, 'rb', tempfile=False) + file, close_file = _fd_or_path_or_tempfile(filepath, 'rb', tempfile=False) file.seek(0) stdin_data = file.read() + if close_file: + file.close() command = [prober, '-of', 'json'] + command_args res = Popen(command, stdin=stdin_parameter, stdout=PIPE, stderr=PIPE) @@ -351,3 +362,56 @@ info[key] = value return info + + +def cache_codecs(function): + cache = {} + + @wraps(function) + def wrapper(): + try: + return cache[0] + except: + cache[0] = function() + return cache[0] + + return wrapper + + +@cache_codecs +def get_supported_codecs(): + encoder = get_encoder_name() + command = [encoder, "-codecs"] + res = Popen(command, stdout=PIPE, stderr=PIPE) + output = res.communicate()[0].decode("utf-8") + if res.returncode != 0: + return [] + + if sys.platform == 'win32': + output = output.replace("\r", "") + + + rgx = re.compile(r"^([D.][E.][AVS.][I.][L.][S.]) (\w*) +(.*)") + decoders = set() + encoders = set() + for line in output.split('\n'): + match = rgx.match(line.strip()) + if not match: + continue + flags, codec, name = match.groups() + + if flags[0] == 'D': + decoders.add(codec) + + if flags[1] == 'E': + encoders.add(codec) + + return (decoders, encoders) + + +def get_supported_decoders(): + return get_supported_codecs()[0] + + +def get_supported_encoders(): + return get_supported_codecs()[1] diff -Nru python-pydub-0.23.1/pydub.egg-info/PKG-INFO python-pydub-0.24.1/pydub.egg-info/PKG-INFO --- python-pydub-0.23.1/pydub.egg-info/PKG-INFO 2019-01-22 17:02:26.000000000 +0000 +++ python-pydub-0.24.1/pydub.egg-info/PKG-INFO 2020-06-03 15:23:55.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: pydub -Version: 0.23.1 +Version: 0.24.1 Summary: Manipulate audio with an simple and easy high level interface Home-page: http://pydub.com Author: James Robert @@ -17,13 +17,13 @@ Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 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 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 Classifier: Intended Audience :: Developers Classifier: Operating System :: OS Independent Classifier: Topic :: Multimedia :: Sound/Audio diff -Nru python-pydub-0.23.1/setup.cfg python-pydub-0.24.1/setup.cfg --- python-pydub-0.23.1/setup.cfg 2019-01-22 17:02:26.000000000 +0000 +++ python-pydub-0.24.1/setup.cfg 2020-06-03 15:23:55.876051400 +0000 @@ -7,5 +7,4 @@ [egg_info] tag_build = tag_date = 0 -tag_svn_revision = 0 diff -Nru python-pydub-0.23.1/setup.py python-pydub-0.24.1/setup.py --- python-pydub-0.23.1/setup.py 2019-01-22 16:49:14.000000000 +0000 +++ python-pydub-0.24.1/setup.py 2020-06-03 15:22:46.000000000 +0000 @@ -8,7 +8,7 @@ setup( name='pydub', - version='0.23.1', + version='0.24.1', author='James Robert', author_email='jiaaro@gmail.com', description='Manipulate audio with an simple and easy high level interface', @@ -25,13 +25,13 @@ 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Intended Audience :: Developers', 'Operating System :: OS Independent', "Topic :: Multimedia :: Sound/Audio", diff -Nru python-pydub-0.23.1/test/test.py python-pydub-0.24.1/test/test.py --- python-pydub-0.23.1/test/test.py 2018-06-15 22:46:47.000000000 +0000 +++ python-pydub-0.24.1/test/test.py 2020-06-03 15:22:46.000000000 +0000 @@ -18,6 +18,8 @@ make_chunks, mediainfo, get_encoder_name, + get_supported_decoders, + get_supported_encoders, ) from pydub.exceptions import ( InvalidTag, @@ -28,6 +30,7 @@ ) from pydub.silence import ( detect_silence, + split_on_silence, ) from pydub.generators import ( Sine, @@ -132,16 +135,16 @@ _ = AudioSegment.from_file(path) def assertWithinRange(self, val, lower_bound, upper_bound): - self.assertTrue(lower_bound < val < 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 + tolerance = expected * percentage + lower_bound = expected - tolerance + upper_bound = expected + tolerance self.assertWithinRange(val, lower_bound, upper_bound) def test_export_pathlib_path(self): @@ -208,16 +211,16 @@ self.png_cover_path = os.path.join(data_dir, 'cover.png') def assertWithinRange(self, val, lower_bound, upper_bound): - self.assertTrue(lower_bound < val < 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 + tolerance = expected * percentage + lower_bound = expected - tolerance + upper_bound = expected + tolerance self.assertWithinRange(val, lower_bound, upper_bound) def test_direct_instantiation_with_bytes(self): @@ -241,6 +244,32 @@ # 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_8_bit_audio(self): + original_path = os.path.join(data_dir,'test1.wav') + original_segment = AudioSegment.from_file(original_path) + target_rms = original_segment.rms//2**8 + + path_with_8bits = os.path.join(data_dir,'test1-8bit.wav') + + def check_8bit_segment(segment): + self.assertWithinTolerance(segment.rms,target_rms,tolerance=0) + + # check reading directly + check_8bit_segment(AudioSegment.from_file(path_with_8bits)) + + # check using ffmpeg on it + with open(path_with_8bits,'rb') as file_8bit: + check_8bit_segment(AudioSegment.from_file(file_8bit)) + + # check conversion from higher-width sample + check_8bit_segment(AudioSegment.from_file(original_path).set_sample_width(1)) + + # check audio export + with NamedTemporaryFile('w+b', suffix='.wav') as tmp_file: + original_segment.set_sample_width(1).export(tmp_file,format='wav') + tmp_file.seek(0) + check_8bit_segment(AudioSegment.from_file(tmp_file)) + def test_192khz_audio(self): test_files = [('test-192khz-16bit.wav', 16), ('test-192khz-24bit.wav', 32), @@ -456,11 +485,23 @@ self.assertEqual(len(mono), len(self.seg2)) - monomp3 = AudioSegment.from_mp3(mono.export()) - self.assertWithinTolerance(len(monomp3), len(self.seg2), - percentage=0.01) + with NamedTemporaryFile('w+b', suffix='.mp3') as tmp_file: + if sys.platform == 'win32': + tmp_file.close() + + mono.export(tmp_file.name, 'mp3') + monomp3 = AudioSegment.from_mp3(tmp_file.name) + + self.assertWithinTolerance( + len(monomp3), + len(self.seg2), + tolerance=105 + ) + + if sys.platform == 'win32': + os.remove(tmp_file.name) - merged = monomp3.append(stereo, crossfade=100) + merged = mono.append(stereo, crossfade=100) self.assertWithinTolerance(len(merged), len(self.seg1) + len(self.seg2) - 100, tolerance=1) @@ -558,6 +599,27 @@ len(seg), percentage=0.01) + def test_export_as_wav_with_codec(self): + seg = self.seg1 + exported_wav = seg.export(format='wav', codec='pcm_s32le') + seg_exported_wav = AudioSegment.from_wav(exported_wav) + + self.assertWithinTolerance(len(seg_exported_wav), + len(seg), + percentage=0.01) + self.assertEqual(seg_exported_wav.sample_width, 4) + + def test_export_as_wav_with_parameters(self): + seg = self.seg1 + exported_wav = seg.export(format='wav', parameters=['-ar', '16000', '-ac', '1']) + seg_exported_wav = AudioSegment.from_wav(exported_wav) + + self.assertWithinTolerance(len(seg_exported_wav), + len(seg), + percentage=0.01) + self.assertEqual(seg_exported_wav.frame_rate, 16000) + self.assertEqual(seg_exported_wav.channels, 1) + def test_export_as_raw(self): seg = self.seg1 exported_raw = seg.export(format='raw') @@ -568,6 +630,16 @@ len(seg), percentage=0.01) + def test_export_as_raw_with_codec(self): + seg = self.seg1 + with self.assertRaises(AttributeError): + seg.export(format='raw', codec='pcm_s32le') + + def test_export_as_raw_with_parameters(self): + seg = self.seg1 + with self.assertRaises(AttributeError): + seg.export(format='raw', parameters=['-ar', '16000', '-ac', '1']) + def test_export_as_ogg(self): seg = self.seg1 exported_ogg = seg.export(format='ogg') @@ -665,11 +737,13 @@ fd.close() tmp_seg = AudioSegment.from_mp3(tmp_mp3_file.name) - self.assertFalse(len(tmp_seg) < len(seg)) + self.assertAlmostEqual(len(tmp_seg), len(seg), places=1) if sys.platform == 'win32': os.remove(tmp_mp3_file.name) + @unittest.skipUnless('aac' in get_supported_decoders(), + "Unsupported codecs") def test_formats(self): seg_m4a = AudioSegment.from_file( os.path.join(data_dir, 'format_test.m4a'), "m4a") @@ -688,6 +762,8 @@ wav = AudioSegment.from_wav(wav_file) self.assertEqual(wav.duration_seconds, self.seg1.duration_seconds) + @unittest.skipUnless('aac' in get_supported_decoders(), + "Unsupported codecs") def test_autodetect_format(self): aac_path = os.path.join(data_dir, 'wrong_extension.aac') fn = partial(AudioSegment.from_file, aac_path, "aac") @@ -720,21 +796,29 @@ AudioSegment.from_file(self.mp3_file_path).export(tmp_webm_file, format="webm") + @unittest.skipUnless('aac' in get_supported_decoders(), + "Unsupported codecs") 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, format="ogg") + @unittest.skipUnless('aac' in get_supported_decoders(), + "Unsupported codecs") def test_export_mp4_as_mp3(self): with NamedTemporaryFile('w+b', suffix='.mp3') as tmp_mp3_file: AudioSegment.from_file(self.mp4_file_path).export(tmp_mp3_file, format="mp3") + @unittest.skipUnless('aac' in get_supported_decoders(), + "Unsupported codecs") def test_export_mp4_as_wav(self): with NamedTemporaryFile('w+b', suffix='.wav') as tmp_wav_file: AudioSegment.from_file(self.mp4_file_path).export(tmp_wav_file, format="mp3") + @unittest.skipUnless('aac' in get_supported_decoders(), + "Unsupported codecs") def test_export_mp4_as_mp3_with_tags(self): with NamedTemporaryFile('w+b', suffix='.mp3') as tmp_mp3_file: tags_dict = { @@ -746,6 +830,8 @@ format="mp3", tags=tags_dict) + @unittest.skipUnless('aac' in get_supported_decoders(), + "Unsupported codecs") def test_export_mp4_as_mp3_with_tags_raises_exception_when_tags_are_not_a_dictionary(self): with NamedTemporaryFile('w+b', suffix='.mp3') as tmp_mp3_file: json = '{"title": "The Title You Want", "album": "Name of the Album", "artist": "Artist\'s name"}' @@ -754,6 +840,8 @@ format="mp3", tags=json) self.assertRaises(InvalidTag, func) + @unittest.skipUnless('aac' in get_supported_decoders(), + "Unsupported codecs") def test_export_mp4_as_mp3_with_tags_raises_exception_when_id3version_is_wrong(self): tags = {'artist': 'Artist', 'title': 'Title'} with NamedTemporaryFile('w+b', suffix='.mp3') as tmp_mp3_file: @@ -766,6 +854,8 @@ ) self.assertRaises(InvalidID3TagVersion, func) + @unittest.skipUnless('aac' in get_supported_decoders(), + "Unsupported codecs") def test_export_mp3_with_tags(self): tags = {'artist': 'Mozart', 'title': 'The Magic Flute'} @@ -862,14 +952,14 @@ speedup_seg = self.seg1.speedup(2.0) self.assertWithinTolerance( - len(self.seg1) / 2, len(speedup_seg), percentage=0.01) + len(self.seg1) / 2, len(speedup_seg), percentage=0.02) def test_dBFS(self): seg_8bit = self.seg1.set_sample_width(1) - self.assertWithinTolerance(seg_8bit.dBFS, -8.88, tolerance=0.01) - self.assertWithinTolerance(self.seg1.dBFS, -8.88, tolerance=0.01) - self.assertWithinTolerance(self.seg2.dBFS, -10.39, tolerance=0.01) - self.assertWithinTolerance(self.seg3.dBFS, -6.47, tolerance=0.01) + self.assertWithinTolerance(seg_8bit.dBFS, -18.06, tolerance=1.5) + self.assertWithinTolerance(self.seg1.dBFS, -17.76, tolerance=1.5) + self.assertWithinTolerance(self.seg2.dBFS, -20.78, tolerance=1.5) + self.assertWithinTolerance(self.seg3.dBFS, -12.94, tolerance=1.5) def test_compress(self): compressed = self.seg1.compress_dynamic_range() @@ -883,6 +973,8 @@ # average volume should be reduced self.assertTrue(compressed.rms < self.seg1.rms) + @unittest.skipUnless('aac' in get_supported_decoders(), + "Unsupported codecs") def test_exporting_to_ogg_uses_default_codec_when_codec_param_is_none(self): delete = sys.platform != 'win32' @@ -1015,6 +1107,20 @@ self.seg1 = test1wav self.seg4 = test4wav + def test_split_on_silence_complete_silence(self): + seg = AudioSegment.silent(5000) + self.assertEquals( split_on_silence(seg), [] ) + + def test_split_on_silence_test1(self): + self.assertEqual( + len(split_on_silence(self.seg1, min_silence_len=500, silence_thresh=-20)), + 3 + ) + def test_split_on_silence_no_silence(self): + splits = split_on_silence(self.seg1, min_silence_len=5000, silence_thresh=-200, keep_silence=True) + lens = [len(split) for split in splits] + self.assertEqual( lens, [len(self.seg1)] ) + def test_detect_completely_silent_segment(self): seg = AudioSegment.silent(5000) silent_ranges = detect_silence(seg, min_silence_len=1000, silence_thresh=-20) @@ -1084,6 +1190,7 @@ def setUp(self): self.wave_file = os.path.join(data_dir, 'test1.wav') self.wave24_file = os.path.join(data_dir, 'test1-24bit.wav') + self.wave_empty = os.path.join(data_dir, 'test1_empty.wav') self.mp3_file = os.path.join(data_dir, 'test1.mp3') self.raw_file = os.path.join(data_dir, 'test1.raw') AudioSegment.converter = "definitely-not-a-path-to-anything-asdjklqwop" @@ -1173,6 +1280,19 @@ self.assertEqual(len(exported), len(seg)) + def test_opening_empty_wav_file(self): + seg = AudioSegment.from_wav(self.wave_empty) + self.assertTrue(len(seg) == 0) + + seg = AudioSegment.from_file(self.wave_empty) + self.assertTrue(len(seg) == 0) + + seg = AudioSegment.from_file(self.wave_empty, "wav") + self.assertTrue(len(seg) == 0) + + seg = AudioSegment.from_file(self.wave_empty, format="wav") + self.assertTrue(len(seg) == 0) + class FilterTests(unittest.TestCase):