Skip to content

Commit b880096

Browse files
Matthew Sillsjacob-meacham
Matthew Sills
authored andcommitted
Properly convert 24-bit audio to 32-bit
1 parent 49139ee commit b880096

File tree

2 files changed

+27
-7
lines changed

2 files changed

+27
-7
lines changed

pydub/audio_segment.py

+18-6
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@
66
from tempfile import TemporaryFile, NamedTemporaryFile
77
import wave
88
import sys
9+
import struct
910
from .logging_utils import log_conversion
1011

1112
try:
1213
from StringIO import StringIO
1314
except:
14-
from io import StringIO, BytesIO
15+
from io import StringIO
16+
17+
from io import BytesIO
1518

1619
try:
1720
from itertools import izip
@@ -150,15 +153,24 @@ def __init__(self, data=None, *args, **kwargs):
150153
# Convert 24-bit audio to 32-bit audio.
151154
# (stdlib audioop and array modules do not support 24-bit data)
152155
if self.sample_width == 3:
153-
# Pad each triplet of bytes with one more byte, either 0 or 0xFF.
154-
padding = {False: b'\x00', True: b'\xFF'}
155-
pad_byte = lambda b: bytes(b) + padding[b[-1] > b'\x7f'[0]]
156+
byte_buffer = BytesIO()
157+
158+
# Workaround for python 2 vs python 3. _data in 2.x are length-1 strings,
159+
# And in 3.x are ints.
160+
pack_fmt = 'BBB' if isinstance(self._data[0], int) else 'ccc'
156161

157-
i = iter(self._data)
158162
# This conversion maintains the 24 bit values. The values are
159163
# not scaled up to the 32 bit range. Other conversions could be
160164
# implemented.
161-
self._data = b''.join(pad_byte(t) for t in izip(i, i, i))
165+
i = iter(self._data)
166+
padding = {False: b'\x00', True: b'\xFF'}
167+
for b0, b1, b2 in izip(i, i, i):
168+
byte_buffer.write(padding[b2 > b'\x7f'[0]])
169+
old_bytes = struct.pack(pack_fmt, b0, b1, b2)
170+
byte_buffer.write(old_bytes)
171+
172+
173+
self._data = byte_buffer.getvalue()
162174
self.sample_width = 4
163175
self.frame_width = self.channels * self.sample_width
164176

test/test.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import os
44
import unittest
55
from tempfile import NamedTemporaryFile
6+
import struct
67

78
from pydub import AudioSegment
89
from pydub.utils import (
@@ -109,10 +110,17 @@ def test_direct_instantiation_with_bytes(self):
109110
self.assertEqual(seg.frame_rate, 32000)
110111

111112
def test_24_bit_audio(self):
112-
seg24 = AudioSegment._from_safe_wav(os.path.join(data_dir, 'test1-24bit.wav'))
113+
path24 = os.path.join(data_dir, 'test1-24bit.wav')
114+
seg24 = AudioSegment._from_safe_wav(path24)
115+
# The data length lies at bytes 40-44
116+
with open(path24, 'rb') as f:
117+
raw24 = f.read()
118+
len24 = struct.unpack("<L", raw24[40:44])[0]
113119

114120
# should have been converted to 32 bit
115121
self.assertEqual(seg24.sample_width, 4)
122+
# the data length should have grown by exactly 4:3 (24 bits turn into 32 bits)
123+
self.assertEqual(len(seg24.raw_data) * 3, len24 * 4)
116124

117125
def test_concat(self):
118126
catted_audio = self.seg1 + self.seg2

0 commit comments

Comments
 (0)