Skip to content

Commit 2c0b062

Browse files
committed
Merge branch 'master' of https://github.com/jiaaro/pydub
2 parents 3af2b9a + e7a8780 commit 2c0b062

File tree

3 files changed

+97
-86
lines changed

3 files changed

+97
-86
lines changed

pydub/audio_segment.py

Lines changed: 39 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ def setter(self, func):
7171
self.fset = func
7272
return self
7373

74+
7475
def classproperty(func):
7576
if not isinstance(func, (classmethod, staticmethod)):
7677
func = classmethod(func)
@@ -83,7 +84,6 @@ def classproperty(func):
8384
"wave": "wav",
8485
}
8586

86-
8787
WavSubChunk = namedtuple('WavSubChunk', ['id', 'position', 'size'])
8888
WavData = namedtuple('WavData', ['audio_format', 'channels', 'sample_rate',
8989
'bits_per_sample', 'raw_data'])
@@ -209,10 +209,10 @@ def __init__(self, data=None, *args, **kwargs):
209209
data = data if isinstance(data, (basestring, bytes)) else data.read()
210210
except(OSError):
211211
d = b''
212-
reader = data.read(2**31-1)
212+
reader = data.read(2 ** 31 - 1)
213213
while reader:
214214
d += reader
215-
reader = data.read(2**31-1)
215+
reader = data.read(2 ** 31 - 1)
216216
data = d
217217

218218
wav_data = read_wav_audio(data)
@@ -257,7 +257,6 @@ def raw_data(self):
257257
"""
258258
return self._data
259259

260-
261260
def get_array_of_samples(self):
262261
"""
263262
returns the raw_data as an array of samples
@@ -290,7 +289,7 @@ def __getitem__(self, millisecond):
290289
if isinstance(millisecond, slice):
291290
if millisecond.step:
292291
return (
293-
self[i:i+millisecond.step]
292+
self[i:i + millisecond.step]
294293
for i in xrange(*millisecond.indices(len(self)))
295294
)
296295

@@ -468,7 +467,8 @@ def from_mono_audiosegments(cls, *mono_segments):
468467
segs = cls._sync(*mono_segments)
469468

470469
if segs[0].channels != 1:
471-
raise ValueError("AudioSegment.from_mono_audiosegments requires all arguments are mono AudioSegment instances")
470+
raise ValueError(
471+
"AudioSegment.from_mono_audiosegments requires all arguments are mono AudioSegment instances")
472472

473473
channels = len(segs)
474474
sample_width = segs[0].sample_width
@@ -536,13 +536,13 @@ def is_format(f):
536536
except(OSError):
537537
input_file.flush()
538538
input_file.close()
539-
input_file = NamedTemporaryFile(mode='wb', delete=False, buffering=2**31-1)
539+
input_file = NamedTemporaryFile(mode='wb', delete=False, buffering=2 ** 31 - 1)
540540
file.close()
541-
file = open(orig_file, buffering=2**13-1, mode='rb')
542-
reader = file.read(2**31-1)
541+
file = open(orig_file, buffering=2 ** 13 - 1, mode='rb')
542+
reader = file.read(2 ** 31 - 1)
543543
while reader:
544544
input_file.write(reader)
545-
reader = file.read(2**31-1)
545+
reader = file.read(2 ** 31 - 1)
546546
input_file.flush()
547547
file.close()
548548

@@ -583,7 +583,9 @@ def is_format(f):
583583

584584
try:
585585
if p.returncode != 0:
586-
raise CouldntDecodeError("Decoding failed. ffmpeg returned error code: {0}\n\nOutput from ffmpeg/avlib:\n\n{1}".format(p.returncode, p_err))
586+
raise CouldntDecodeError(
587+
"Decoding failed. ffmpeg returned error code: {0}\n\nOutput from ffmpeg/avlib:\n\n{1}".format(
588+
p.returncode, p_err))
587589
obj = cls._from_safe_wav(output)
588590
finally:
589591
input_file.close()
@@ -661,9 +663,9 @@ def is_format(f):
661663
if x['codec_type'] == 'audio']
662664
# This is a workaround for some ffprobe versions that always say
663665
# that mp3/mp4/aac/webm/ogg files contain fltp samples
664-
if (audio_streams[0]['sample_fmt'] == 'fltp' and
665-
(is_format("mp3") or is_format("mp4") or is_format("aac") or
666-
is_format("webm") or is_format("ogg"))):
666+
if (audio_streams[0].get('sample_fmt') == 'fltp' and
667+
(is_format("mp3") or is_format("mp4") or is_format("aac") or
668+
is_format("webm") or is_format("ogg"))):
667669
bits_per_sample = 16
668670
else:
669671
bits_per_sample = audio_streams[0]['bits_per_sample']
@@ -688,7 +690,9 @@ def is_format(f):
688690

689691
if p.returncode != 0 or len(p_out) == 0:
690692
file.close()
691-
raise CouldntDecodeError("Decoding failed. ffmpeg returned error code: {0}\n\nOutput from ffmpeg/avlib:\n\n{1}".format(p.returncode, p_err))
693+
raise CouldntDecodeError(
694+
"Decoding failed. ffmpeg returned error code: {0}\n\nOutput from ffmpeg/avlib:\n\n{1}".format(
695+
p.returncode, p_err))
692696

693697
p_out = bytearray(p_out)
694698
fix_wav_headers(p_out)
@@ -716,17 +720,19 @@ def from_wav(cls, file, parameters=None):
716720

717721
@classmethod
718722
def from_raw(cls, file, **kwargs):
719-
return cls.from_file(file, 'raw', sample_width=kwargs['sample_width'], frame_rate=kwargs['frame_rate'], channels=kwargs['channels'])
723+
return cls.from_file(file, 'raw', sample_width=kwargs['sample_width'], frame_rate=kwargs['frame_rate'],
724+
channels=kwargs['channels'])
720725

721726
@classmethod
722727
def _from_safe_wav(cls, file):
723728
file = _fd_or_path_or_tempfile(file, 'rb', tempfile=False)
724729
file.seek(0)
725-
obj = cls(data=file);
730+
obj = cls(data=file)
726731
file.close()
727732
return obj
728733

729-
def export(self, out_f=None, format='mp3', codec=None, bitrate=None, parameters=None, tags=None, id3v2_version='4', cover=None):
734+
def export(self, out_f=None, format='mp3', codec=None, bitrate=None, parameters=None, tags=None, id3v2_version='4',
735+
cover=None):
730736
"""
731737
Export an AudioSegment to a file with given options
732738
@@ -804,9 +810,10 @@ def export(self, out_f=None, format='mp3', codec=None, bitrate=None, parameters=
804810

805811
if cover is not None:
806812
if cover.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tif', '.tiff')) and format == "mp3":
807-
conversion_command.extend(["-i" , cover, "-map", "0", "-map", "1", "-c:v", "mjpeg"])
813+
conversion_command.extend(["-i", cover, "-map", "0", "-map", "1", "-c:v", "mjpeg"])
808814
else:
809-
raise AttributeError("Currently cover images are only supported by MP3 files. The allowed image formats are: .tif, .jpg, .bmp, .jpeg and .png.")
815+
raise AttributeError(
816+
"Currently cover images are only supported by MP3 files. The allowed image formats are: .tif, .jpg, .bmp, .jpeg and .png.")
810817

811818
if codec is not None:
812819
# force audio encoder
@@ -835,7 +842,7 @@ def export(self, out_f=None, format='mp3', codec=None, bitrate=None, parameters=
835842
raise InvalidID3TagVersion(
836843
"id3v2_version not allowed, allowed versions: %s" % id3v2_allowed_versions)
837844
conversion_command.extend([
838-
"-id3v2_version", id3v2_version
845+
"-id3v2_version", id3v2_version
839846
])
840847

841848
if sys.platform == 'darwin':
@@ -856,7 +863,9 @@ def export(self, out_f=None, format='mp3', codec=None, bitrate=None, parameters=
856863
log_subprocess_output(p_err)
857864

858865
if p.returncode != 0:
859-
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))
866+
raise CouldntEncodeError(
867+
"Encoding failed. ffmpeg/avlib returned error code: {0}\n\nCommand:{1}\n\nOutput from ffmpeg/avlib:\n\n{2}".format(
868+
p.returncode, conversion_command, p_err))
860869

861870
output.seek(0)
862871
out_f.write(output.read())
@@ -1114,11 +1123,11 @@ def overlay(self, seg, position=0, loop=False, times=None, gain_during_overlay=N
11141123
if gain_during_overlay:
11151124
seg1_overlaid = seg1[pos:pos + seg2_len]
11161125
seg1_adjusted_gain = audioop.mul(seg1_overlaid, self.sample_width,
1117-
db_to_float(float(gain_during_overlay)))
1126+
db_to_float(float(gain_during_overlay)))
11181127
output.write(audioop.add(seg1_adjusted_gain, seg2, sample_width))
11191128
else:
11201129
output.write(audioop.add(seg1[pos:pos + seg2_len], seg2,
1121-
sample_width))
1130+
sample_width))
11221131
pos += seg2_len
11231132

11241133
# 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,
12191228

12201229
# fades longer than 100ms can use coarse fading (one gain step per ms),
12211230
# shorter fades will have audible clicks so they use precise fading
1222-
#(one gain step per sample)
1231+
# (one gain step per sample)
12231232
if duration > 100:
12241233
scale_step = gain_delta / duration
12251234

@@ -1266,14 +1275,15 @@ def reverse(self):
12661275
)
12671276

12681277
def _repr_html_(self):
1269-
src = """
1278+
src = """
12701279
<audio controls>
12711280
<source src="data:audio/mpeg;base64,{base64}" type="audio/mpeg"/>
12721281
Your browser does not support the audio element.
12731282
</audio>
12741283
"""
1275-
fh = self.export()
1276-
data = base64.b64encode(fh.read()).decode('ascii')
1277-
return src.format(base64=data)
1284+
fh = self.export()
1285+
data = base64.b64encode(fh.read()).decode('ascii')
1286+
return src.format(base64=data)
1287+
12781288

12791289
from . import effects

pydub/utils.py

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,29 @@
11
from __future__ import division
22

3-
from math import log, ceil, floor
3+
import json
44
import os
55
import re
6-
from subprocess import Popen, PIPE
76
import sys
7+
from subprocess import Popen, PIPE
8+
from math import log, ceil
89
from tempfile import TemporaryFile
910
from warnings import warn
10-
import json
1111

1212
try:
1313
import audioop
1414
except ImportError:
1515
import pyaudioop as audioop
1616

17-
1817
if sys.version_info >= (3, 0):
1918
basestring = str
2019

21-
22-
2320
FRAME_WIDTHS = {
2421
8: 1,
2522
16: 2,
2623
32: 4,
2724
}
2825
ARRAY_TYPES = {
29-
8: "b",
26+
8: "b",
3027
16: "h",
3128
32: "i",
3229
}
@@ -78,7 +75,7 @@ def db_to_float(db, using_amplitude=True):
7875
db = float(db)
7976
if using_amplitude:
8077
return 10 ** (db / 20)
81-
else: # using power
78+
else: # using power
8279
return 10 ** (db / 10)
8380

8481

@@ -92,33 +89,28 @@ def ratio_to_db(ratio, val2=None, using_amplitude=True):
9289
# accept 2 values and use the ratio of val1 to val2
9390
if val2 is not None:
9491
ratio = ratio / val2
95-
92+
9693
# special case for multiply-by-zero (convert to silence)
9794
if ratio == 0:
9895
return -float('inf')
9996

10097
if using_amplitude:
10198
return 20 * log(ratio, 10)
102-
else: # using power
99+
else: # using power
103100
return 10 * log(ratio, 10)
104-
101+
105102

106103
def register_pydub_effect(fn, name=None):
107104
"""
108105
decorator for adding pydub effects to the AudioSegment objects.
109-
110106
example use:
111-
112107
@register_pydub_effect
113108
def normalize(audio_segment):
114109
...
115-
116110
or you can specify a name:
117-
118111
@register_pydub_effect("normalize")
119112
def normalize_audio_segment(audio_segment):
120113
...
121-
122114
"""
123115
if isinstance(fn, basestring):
124116
name = fn
@@ -136,7 +128,6 @@ def make_chunks(audio_segment, chunk_length):
136128
"""
137129
Breaks an AudioSegment into chunks that are <chunk_length> milliseconds
138130
long.
139-
140131
if chunk_length is 50 then you'll get a list of 50 millisecond long audio
141132
segments back (except the last one, which can be shorter)
142133
"""
@@ -149,7 +140,7 @@ def which(program):
149140
"""
150141
Mimics behavior of UNIX which command.
151142
"""
152-
#Add .exe program extension for windows support
143+
# Add .exe program extension for windows support
153144
if os.name == "nt" and not program.endswith(".exe"):
154145
program += ".exe"
155146

@@ -174,6 +165,7 @@ def get_encoder_name():
174165
warn("Couldn't find ffmpeg or avconv - defaulting to ffmpeg, but may not work", RuntimeWarning)
175166
return "ffmpeg"
176167

168+
177169
def get_player_name():
178170
"""
179171
Return enconder default application for system, either avconv or ffmpeg
@@ -220,6 +212,33 @@ def fsdecode(filename):
220212
raise TypeError("type {0} not accepted by fsdecode".format(type(filename)))
221213

222214

215+
def get_extra_info(stderr):
216+
"""
217+
avprobe sometimes gives more information on stderr than
218+
on the json output. The information has to be extracted
219+
from stderr of the format of:
220+
' Stream #0:0: Audio: flac, 88200 Hz, stereo, s32 (24 bit)'
221+
or (macOS version):
222+
' Stream #0:0: Audio: vorbis'
223+
' 44100 Hz, stereo, fltp, 320 kb/s'
224+
225+
:type stderr: str
226+
:rtype: list of dict
227+
"""
228+
extra_info = {}
229+
230+
re_stream = r'(?P<space_start> +)Stream #0[:\.](?P<stream_id>([0-9]+))(?P<content_0>.+)\n?((?P<space_end> +)(?P<content_1>.+))?'
231+
for i in re.finditer(re_stream, stderr):
232+
if i.group('space_end') is not None and len(i.group('space_start')) <= len(
233+
i.group('space_end')):
234+
content_line = ','.join([i.group('content_0'), i.group('content_1')])
235+
else:
236+
content_line = i.group('content_0')
237+
tokens = [x.strip() for x in re.split('[:,]', content_line) if x]
238+
extra_info[int(i.group('stream_id'))] = tokens
239+
return extra_info
240+
241+
223242
def mediainfo_json(filepath):
224243
"""Return json dictionary with media info(codec, duration, size, bitrate...) from filepath
225244
"""
@@ -253,18 +272,7 @@ def mediainfo_json(filepath):
253272
# (for example, because the file doesn't exist)
254273
return info
255274

256-
# avprobe sometimes gives more information on stderr than
257-
# on the json output. The information has to be extracted
258-
# from lines of the format of:
259-
# ' Stream #0:0: Audio: flac, 88200 Hz, stereo, s32 (24 bit)'
260-
extra_info = {}
261-
for line in stderr.split("\n"):
262-
match = re.match(' *Stream #0[:\.]([0-9]+)(\(\w+\))?', line)
263-
if match:
264-
stream_id = int(match.group(1))
265-
tokens = [x.strip()
266-
for x in re.split('[:,]', line[match.end():]) if x]
267-
extra_info[stream_id] = tokens
275+
extra_info = get_extra_info(stderr)
268276

269277
audio_streams = [x for x in info['streams'] if x['codec_type'] == 'audio']
270278
if len(audio_streams) == 0:
@@ -296,7 +304,6 @@ def set_property(stream, prop, value):
296304
set_property(stream, 'sample_fmt', token)
297305
set_property(stream, 'bits_per_sample', 64)
298306
set_property(stream, 'bits_per_raw_sample', 64)
299-
300307
return info
301308

302309

0 commit comments

Comments
 (0)