diff --git a/Lib/aifc.py b/Lib/aifc.py index 5254987e22bc167..bf02cc6f696ebb7 100644 --- a/Lib/aifc.py +++ b/Lib/aifc.py @@ -144,6 +144,8 @@ warnings._deprecated(__name__, remove=(3, 13)) +AIFC_ENCODING_LINEAR_PCM = 1 + class Error(Exception): pass @@ -400,6 +402,9 @@ def getsampwidth(self): def getframerate(self): return self._framerate + def getencoding(self): + return AIFC_ENCODING_LINEAR_PCM + def getcomptype(self): return self._comptype @@ -675,6 +680,13 @@ def setnframes(self, nframes): def getnframes(self): return self._nframeswritten + def getencoding(self): + return AIFC_ENCODING_LINEAR_PCM + + def setencoding(self, encoding): + if encoding != AIFC_ENCODING_LINEAR_PCM: + raise Error("Unsupported encoding") + def setcomptype(self, comptype, compname): if self._nframeswritten: raise Error('cannot change parameters after starting to write') diff --git a/Lib/sunau.py b/Lib/sunau.py index c6caab959abd0cd..7951bd3eef42c73 100644 --- a/Lib/sunau.py +++ b/Lib/sunau.py @@ -259,6 +259,9 @@ def getcompname(self): else: return 'not compressed' + def getencoding(self): + return self._encoding + def getparams(self): return _sunau_params(self.getnchannels(), self.getsampwidth(), self.getframerate(), self.getnframes(), @@ -342,6 +345,7 @@ def initfp(self, file): self._datawritten = 0 self._datalength = 0 self._info = b'' + self._encoding = AUDIO_FILE_ENCODING_MULAW_8 self._comptype = 'ULAW' # default is U-law def setnchannels(self, nchannels): @@ -362,6 +366,7 @@ def setsampwidth(self, sampwidth): if sampwidth not in (1, 2, 3, 4): raise Error('bad sample width') self._sampwidth = sampwidth + self._update_encoding() def getsampwidth(self): if not self._framerate: @@ -405,6 +410,31 @@ def getcompname(self): else: return 'not compressed' + def setencoding(self, encoding): + if self._nframeswritten: + raise Error('cannot change parameters after starting to write') + if encoding == AUDIO_FILE_ENCODING_LINEAR_8: + self.setcomptype('NONE', None) + self.setsampwidth(1) + elif encoding == AUDIO_FILE_ENCODING_LINEAR_16: + self.setcomptype('NONE', None) + self.setsampwidth(2) + elif encoding == AUDIO_FILE_ENCODING_LINEAR_24: + self.setcomptype('NONE', None) + self.setsampwidth(3) + elif encoding == AUDIO_FILE_ENCODING_LINEAR_32: + self.setcomptype('NONE', None) + self.setsampwidth(4) + elif encoding == AUDIO_FILE_ENCODING_MULAW_8: + self.setcomptype('ULAW', None) + self.setsampwidth(2) + else: + raise Error('unsupported encoding %r', encoding) + assert self._encoding == encoding + + def getencoding(self): + return self._encoding + def setparams(self, params): nchannels, sampwidth, framerate, nframes, comptype, compname = params self.setnchannels(nchannels) @@ -458,38 +488,40 @@ def close(self): # # private methods # - - def _ensure_header_written(self): - if not self._nframeswritten: - if not self._nchannels: - raise Error('# of channels not specified') - if not self._sampwidth: - raise Error('sample width not specified') - if not self._framerate: - raise Error('frame rate not specified') - self._write_header() - - def _write_header(self): + def _update_encoding(self): if self._comptype == 'NONE': if self._sampwidth == 1: - encoding = AUDIO_FILE_ENCODING_LINEAR_8 + self._encoding = AUDIO_FILE_ENCODING_LINEAR_8 self._framesize = 1 elif self._sampwidth == 2: - encoding = AUDIO_FILE_ENCODING_LINEAR_16 + self._encoding = AUDIO_FILE_ENCODING_LINEAR_16 self._framesize = 2 elif self._sampwidth == 3: - encoding = AUDIO_FILE_ENCODING_LINEAR_24 + self._encoding = AUDIO_FILE_ENCODING_LINEAR_24 self._framesize = 3 elif self._sampwidth == 4: - encoding = AUDIO_FILE_ENCODING_LINEAR_32 + self._encoding = AUDIO_FILE_ENCODING_LINEAR_32 self._framesize = 4 else: raise Error('internal error') elif self._comptype == 'ULAW': - encoding = AUDIO_FILE_ENCODING_MULAW_8 + self._encoding = AUDIO_FILE_ENCODING_MULAW_8 self._framesize = 1 else: raise Error('internal error') + + def _ensure_header_written(self): + if not self._nframeswritten: + if not self._nchannels: + raise Error('# of channels not specified') + if not self._sampwidth: + raise Error('sample width not specified') + if not self._framerate: + raise Error('frame rate not specified') + self._write_header() + + def _write_header(self): + self._update_encoding() self._framesize = self._framesize * self._nchannels _write_u32(self._file, AUDIO_FILE_MAGIC) header_size = 25 + len(self._info) @@ -505,7 +537,7 @@ def _write_header(self): self._form_length_pos = None _write_u32(self._file, length) self._datalength = length - _write_u32(self._file, encoding) + _write_u32(self._file, self._encoding) _write_u32(self._file, self._framerate) _write_u32(self._file, self._nchannels) self._file.write(self._info) diff --git a/Lib/test/audiodata/pluck-float32.wav b/Lib/test/audiodata/pluck-float32.wav new file mode 100644 index 000000000000000..2030fb16d6e3bd7 Binary files /dev/null and b/Lib/test/audiodata/pluck-float32.wav differ diff --git a/Lib/test/audiotests.py b/Lib/test/audiotests.py index 9d6c4cc2b4b02c2..9c40a460e75c964 100644 --- a/Lib/test/audiotests.py +++ b/Lib/test/audiotests.py @@ -27,13 +27,14 @@ def tearDown(self): unlink(TESTFN) def check_params(self, f, nchannels, sampwidth, framerate, nframes, - comptype, compname): + comptype, compname, encoding): self.assertEqual(f.getnchannels(), nchannels) self.assertEqual(f.getsampwidth(), sampwidth) self.assertEqual(f.getframerate(), framerate) self.assertEqual(f.getnframes(), nframes) self.assertEqual(f.getcomptype(), comptype) self.assertEqual(f.getcompname(), compname) + self.assertEqual(f.getencoding(), encoding) params = f.getparams() self.assertEqual(params, @@ -51,13 +52,17 @@ def check_params(self, f, nchannels, sampwidth, framerate, nframes, class AudioWriteTests(AudioTests): + readonly = False def create_file(self, testfile): + if self.readonly: + self.skipTest('Read only file format') f = self.fout = self.module.open(testfile, 'wb') f.setnchannels(self.nchannels) f.setsampwidth(self.sampwidth) f.setframerate(self.framerate) f.setcomptype(self.comptype, self.compname) + f.setencoding(self.encoding) return f def check_file(self, testfile, nframes, frames): @@ -67,13 +72,14 @@ def check_file(self, testfile, nframes, frames): self.assertEqual(f.getframerate(), self.framerate) self.assertEqual(f.getnframes(), nframes) self.assertEqual(f.readframes(nframes), frames) + self.assertEqual(f.getencoding(), self.encoding) def test_write_params(self): f = self.create_file(TESTFN) f.setnframes(self.nframes) f.writeframes(self.frames) self.check_params(f, self.nchannels, self.sampwidth, self.framerate, - self.nframes, self.comptype, self.compname) + self.nframes, self.comptype, self.compname, self.encoding) f.close() def test_write_context_manager_calls_close(self): @@ -257,7 +263,7 @@ def test_read_params(self): f = self.f = self.module.open(self.sndfilepath) #self.assertEqual(f.getfp().name, self.sndfilepath) self.check_params(f, self.nchannels, self.sampwidth, self.framerate, - self.sndfilenframes, self.comptype, self.compname) + self.sndfilenframes, self.comptype, self.compname, self.encoding) def test_close(self): with open(self.sndfilepath, 'rb') as testfile: diff --git a/Lib/test/test_aifc.py b/Lib/test/test_aifc.py index d3863d4915d449a..22d7ace5955130a 100644 --- a/Lib/test/test_aifc.py +++ b/Lib/test/test_aifc.py @@ -18,6 +18,7 @@ class AifcTest(audiotests.AudioWriteTests, module = aifc close_fd = True test_unseekable_read = None + encoding = aifc.AIFC_ENCODING_LINEAR_PCM class AifcPCM8Test(AifcTest, unittest.TestCase): diff --git a/Lib/test/test_sunau.py b/Lib/test/test_sunau.py index 40408b01eda9ac5..29e9bbd18d1d882 100644 --- a/Lib/test/test_sunau.py +++ b/Lib/test/test_sunau.py @@ -2,6 +2,7 @@ from test import audiotests import io import struct +import sunau import sys from test.support import warnings_helper @@ -21,6 +22,7 @@ class SunauPCM8Test(SunauTest, unittest.TestCase): sampwidth = 1 framerate = 11025 nframes = 48 + encoding = sunau.AUDIO_FILE_ENCODING_LINEAR_8 comptype = 'NONE' compname = 'not compressed' frames = bytes.fromhex("""\ @@ -38,6 +40,7 @@ class SunauPCM16Test(SunauTest, unittest.TestCase): sampwidth = 2 framerate = 11025 nframes = 48 + encoding = sunau.AUDIO_FILE_ENCODING_LINEAR_16 comptype = 'NONE' compname = 'not compressed' frames = bytes.fromhex("""\ @@ -57,6 +60,7 @@ class SunauPCM24Test(SunauTest, unittest.TestCase): sampwidth = 3 framerate = 11025 nframes = 48 + encoding = sunau.AUDIO_FILE_ENCODING_LINEAR_24 comptype = 'NONE' compname = 'not compressed' frames = bytes.fromhex("""\ @@ -82,6 +86,7 @@ class SunauPCM32Test(SunauTest, unittest.TestCase): sampwidth = 4 framerate = 11025 nframes = 48 + encoding = sunau.AUDIO_FILE_ENCODING_LINEAR_32 comptype = 'NONE' compname = 'not compressed' frames = bytes.fromhex("""\ @@ -107,6 +112,7 @@ class SunauULAWTest(SunauTest, unittest.TestCase): sampwidth = 2 framerate = 11025 nframes = 48 + encoding = sunau.AUDIO_FILE_ENCODING_MULAW_8 comptype = 'ULAW' compname = 'CCITT G.711 u-law' frames = bytes.fromhex("""\ diff --git a/Lib/test/test_wave.py b/Lib/test/test_wave.py index 6c3362857fc2ba2..6a4548e026a4d6e 100644 --- a/Lib/test/test_wave.py +++ b/Lib/test/test_wave.py @@ -19,6 +19,7 @@ class WavePCM8Test(WaveTest, unittest.TestCase): sampwidth = 1 framerate = 11025 nframes = 48 + encoding = wave.WAVE_FORMAT_PCM comptype = 'NONE' compname = 'not compressed' frames = bytes.fromhex("""\ @@ -36,6 +37,7 @@ class WavePCM16Test(WaveTest, unittest.TestCase): sampwidth = 2 framerate = 11025 nframes = 48 + encoding = wave.WAVE_FORMAT_PCM comptype = 'NONE' compname = 'not compressed' frames = bytes.fromhex("""\ @@ -57,6 +59,7 @@ class WavePCM24Test(WaveTest, unittest.TestCase): sampwidth = 3 framerate = 11025 nframes = 48 + encoding = wave.WAVE_FORMAT_PCM comptype = 'NONE' compname = 'not compressed' frames = bytes.fromhex("""\ @@ -84,6 +87,8 @@ class WavePCM24ExtTest(WaveTest, unittest.TestCase): sampwidth = 3 framerate = 11025 nframes = 48 + encoding = wave.WAVE_FORMAT_EXTENSIBLE + readonly = True # Writing EXTENSIBLE wave format is not supported. comptype = 'NONE' compname = 'not compressed' frames = bytes.fromhex("""\ @@ -111,6 +116,7 @@ class WavePCM32Test(WaveTest, unittest.TestCase): sampwidth = 4 framerate = 11025 nframes = 48 + encoding = wave.WAVE_FORMAT_PCM comptype = 'NONE' compname = 'not compressed' frames = bytes.fromhex("""\ @@ -131,9 +137,34 @@ class WavePCM32Test(WaveTest, unittest.TestCase): frames = wave._byteswap(frames, 4) +class WaveIeeeFloatingPointTest(WaveTest, unittest.TestCase): + sndfilename = 'pluck-float32.wav' + sndfilenframes = 3307 + nchannels = 2 + sampwidth = 4 + framerate = 11025 + nframes = 48 + encoding = wave.WAVE_FORMAT_IEEE_FLOAT + comptype = 'NONE' + compname = 'not compressed' + frames = bytes.fromhex("""\ + 60598B3C001423BA 1FB4163F8054FA3B 0E4FC43E80C51D3D 53467EBF4030843D \ + FC84D0BE304C563D 3053113F40BEFC3C B72F00BFC03E583C E0FEDA3C805142BC \ + 54510FBFE02638BD 569F16BF40FDCABD C060A63EECA421BE 3CE5523E2C3349BE \ + 0C2E10BE14725BBE 5268E7BEDC3B6CBE 985AE03D80497ABE B4B606BEECB67EBE \ + B0B12E3FC87C6CBE 005519BD4C0F3EBE F8BD1B3EECDF03BE 924E9FBE588D8DBD \ + D4E150BF501711BD B079A0BD20FBFBBC 5863863D40760CBD 0E3C83BE40E217BD \ + 04FF0B3EF07839BD E29AFB3E80A714BD B91007BFE042D3BC B5AD4D3F80CDA0BB \ + 1AB1C3BEB04E023D D33A063FC0A8973D 8012F9BEE074EC3D 7341223FD415153E \ + D80409BE04A63A3E 00F27BBFBC25333E 0000803FFC29223E 000080BF38A7143E \ + 3638133F283BEB3D 7C6E253F00CADB3D 686A02BE88FDF53D 920CC7BE28E1FB3D \ + 185B5ABED8A2CE3D 5189463FC8A7A53D E88F8C3DF0FFA13D 1CE6AE3EE0A0B03D \ + DF90223F184EE43D 376768BF2CD8093E 281612BF60B3EE3D 2F26083F88B4A53D \ + """) + class MiscTestCase(unittest.TestCase): def test__all__(self): - not_exported = {'WAVE_FORMAT_PCM', 'WAVE_FORMAT_EXTENSIBLE', 'KSDATAFORMAT_SUBTYPE_PCM'} + not_exported = {'WAVE_FORMAT_PCM', 'WAVE_FORMAT_IEEE_FLOAT', 'WAVE_FORMAT_EXTENSIBLE', 'KSDATAFORMAT_SUBTYPE_PCM'} support.check__all__(self, wave, not_exported=not_exported) diff --git a/Lib/wave.py b/Lib/wave.py index d5858e5d4b80da1..12ec3915b570b54 100644 --- a/Lib/wave.py +++ b/Lib/wave.py @@ -15,6 +15,8 @@ getsampwidth() -- returns sample width in bytes getframerate() -- returns sampling frequency getnframes() -- returns number of audio frames + getencoding() -- returns frame encoding (WAVE_FORMAT_PCM, WAVE_FORMAT_IEEE_FLOAT + or WAVE_FORMAT_EXTENSIBLE) getcomptype() -- returns compression type ('NONE' for linear samples) getcompname() -- returns human-readable version of compression type ('not compressed' linear samples) @@ -46,6 +48,9 @@ setsampwidth(n) -- set the sample width setframerate(n) -- set the frame rate setnframes(n) -- set the number of frames + setencoding(encoding) + -- set the frame encoding. Only WAVE_FORMAT_PCM, + WAVE_FORMAT_IEEE_FLOAT are supported. setcomptype(type, name) -- set the compression type and the human-readable compression type @@ -83,6 +88,7 @@ class Error(Exception): pass WAVE_FORMAT_PCM = 0x0001 +WAVE_FORMAT_IEEE_FLOAT = 0x0003 WAVE_FORMAT_EXTENSIBLE = 0xFFFE # Derived from uuid.UUID("00000001-0000-0010-8000-00aa00389b71").bytes_le KSDATAFORMAT_SUBTYPE_PCM = b'\x01\x00\x00\x00\x00\x00\x10\x00\x80\x00\x00\xaa\x008\x9bq' @@ -230,6 +236,10 @@ class Wave_read: available through the getsampwidth() method _framerate -- the sampling frequency available through the getframerate() method + _encoding -- frame encoding + One of WAVE_FORMAT_PCM, WAVE_FORMAT_IEEE_FLOAT + or WAVE_FORMAT_EXTENSIBLE available through + getencoding() method _comptype -- the AIFF-C compression type ('NONE' if AIFF) available through the getcomptype() method _compname -- the human-readable AIFF-C compression type @@ -348,6 +358,9 @@ def getmarkers(self): def getmark(self, id): raise Error('no marks') + def getencoding(self): + return self._encoding + def setpos(self, pos): if pos < 0 or pos > self._nframes: raise Error('position not in range') @@ -377,16 +390,16 @@ def readframes(self, nframes): def _read_fmt_chunk(self, chunk): try: - wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack_from('