diff --git a/dejavu/decoder.py b/dejavu/decoder.py index e2e2d33..830b8f7 100755 --- a/dejavu/decoder.py +++ b/dejavu/decoder.py @@ -2,7 +2,8 @@ import os import fnmatch import numpy as np from pydub import AudioSegment - +from pydub.utils import audioop +import wavio def find_files(path, extensions): # Allow both with ".mp3" and without "mp3" to be used for extensions @@ -18,7 +19,8 @@ def find_files(path, extensions): def read(filename, limit=None): """ Reads any file supported by pydub (ffmpeg) and returns the data contained - within. + within. If file reading fails due to input being a 24-bit wav file, + wavio is used as a backup. Can be optionally limited to a certain amount of seconds from the start of the file by specifying the `limit` parameter. This is the amount of @@ -26,18 +28,34 @@ def read(filename, limit=None): returns: (channels, samplerate) """ - audiofile = AudioSegment.from_file(filename) + # pydub does not support 24-bit wav files, use wavio when this occurs + try: + audiofile = AudioSegment.from_file(filename) - if limit: - audiofile = audiofile[:limit * 1000] + if limit: + audiofile = audiofile[:limit * 1000] - data = np.fromstring(audiofile._data, np.int16) + data = np.fromstring(audiofile._data, np.int16) - channels = [] - for chn in xrange(audiofile.channels): - channels.append(data[chn::audiofile.channels]) + channels = [] + for chn in xrange(audiofile.channels): + channels.append(data[chn::audiofile.channels]) - return channels, audiofile.frame_rate + fs = audiofile.frame_rate + except audioop.error: + fs, _, audiofile = wavio.readwav(filename) + + if limit: + audiofile = audiofile[:limit * 1000] + + audiofile = audiofile.T + audiofile = audiofile.astype(np.int16) + + channels = [] + for chn in audiofile: + channels.append(chn) + + return channels, fs def path_to_songname(path): diff --git a/dejavu/wavio.py b/dejavu/wavio.py new file mode 100644 index 0000000..e8d1fc3 --- /dev/null +++ b/dejavu/wavio.py @@ -0,0 +1,121 @@ +# wavio.py +# Author: Warren Weckesser +# License: BSD 3-Clause (http://opensource.org/licenses/BSD-3-Clause) +# Synopsis: A Python module for reading and writing 24 bit WAV files. +# Github: github.com/WarrenWeckesser/wavio + +import wave as _wave +import numpy as _np + + +def _wav2array(nchannels, sampwidth, data): + """data must be the string containing the bytes from the wav file.""" + num_samples, remainder = divmod(len(data), sampwidth * nchannels) + if remainder > 0: + raise ValueError('The length of data is not a multiple of ' + 'sampwidth * num_channels.') + if sampwidth > 4: + raise ValueError("sampwidth must not be greater than 4.") + + if sampwidth == 3: + a = _np.empty((num_samples, nchannels, 4), dtype=_np.uint8) + raw_bytes = _np.fromstring(data, dtype=_np.uint8) + a[:, :, :sampwidth] = raw_bytes.reshape(-1, nchannels, sampwidth) + a[:, :, sampwidth:] = (a[:, :, sampwidth - 1:sampwidth] >> 7) * 255 + result = a.view('>> rate = 22050 # samples per second + >>> T = 3 # sample duration (seconds) + >>> f = 440.0 # sound frequency (Hz) + >>> t = np.linspace(0, T, T*rate, endpoint=False) + >>> x = (2**23 - 1) * np.sin(2 * np.pi * f * t) + >>> writewav24("sine24.wav", rate, x) + + """ + a32 = _np.asarray(data, dtype=_np.int32) + if a32.ndim == 1: + # Convert to a 2D array with a single column. + a32.shape = a32.shape + (1,) + # By shifting first 0 bits, then 8, then 16, the resulting output + # is 24 bit little-endian. + a8 = (a32.reshape(a32.shape + (1,)) >> _np.array([0, 8, 16])) & 255 + wavdata = a8.astype(_np.uint8).tostring() + + w = _wave.open(filename, 'wb') + w.setnchannels(a32.shape[1]) + w.setsampwidth(3) + w.setframerate(rate) + w.writeframes(wavdata) + w.close()