Issue #5202: Added support for unseekable files in the wave module.

This commit is contained in:
Serhiy Storchaka 2013-11-16 13:04:00 +02:00
parent a7a34a83f3
commit 7714ebbe0e
5 changed files with 90 additions and 34 deletions

View File

@ -19,7 +19,7 @@ The :mod:`wave` module defines the following function and exception:
.. function:: open(file, mode=None) .. function:: open(file, mode=None)
If *file* is a string, open the file by that name, otherwise treat it as a If *file* is a string, open the file by that name, otherwise treat it as a
seekable file-like object. *mode* can be: file-like object. *mode* can be:
``'rb'`` ``'rb'``
Read only mode. Read only mode.
@ -43,6 +43,8 @@ The :mod:`wave` module defines the following function and exception:
<wave.Wave_read.close>` or :meth:`Wave_write.close() <wave.Wave_read.close>` or :meth:`Wave_write.close()
<wave.Wave_write.close()>` method is called. <wave.Wave_write.close()>` method is called.
.. versionchanged:: 3.4
Added support for unseekable files.
.. function:: openfp(file, mode) .. function:: openfp(file, mode)
@ -154,7 +156,8 @@ Wave_write objects, as returned by :func:`.open`, have the following methods:
.. method:: Wave_write.close() .. method:: Wave_write.close()
Make sure *nframes* is correct, and close the file if it was opened by Make sure *nframes* is correct, and close the file if it was opened by
:mod:`wave`. This method is called upon object collection. :mod:`wave`. This method is called upon object collection. Can raise an
exception if *nframes* is not correct and a file is not seekable.
.. method:: Wave_write.setnchannels(n) .. method:: Wave_write.setnchannels(n)
@ -208,7 +211,8 @@ Wave_write objects, as returned by :func:`.open`, have the following methods:
.. method:: Wave_write.writeframes(data) .. method:: Wave_write.writeframes(data)
Write audio frames and make sure *nframes* is correct. Write audio frames and make sure *nframes* is correct. Can raise an
exception if a file is not seekable.
Note that it is invalid to set any parameters after calling :meth:`writeframes` Note that it is invalid to set any parameters after calling :meth:`writeframes`

View File

@ -21,6 +21,13 @@ def byteswap4(data):
a.byteswap() a.byteswap()
return a.tobytes() return a.tobytes()
class UnseekableIO(io.FileIO):
def tell(self):
raise io.UnsupportedOperation
def seek(self, *args, **kwargs):
raise io.UnsupportedOperation
class AudioTests: class AudioTests:
close_fd = False close_fd = False
@ -177,6 +184,59 @@ class AudioWriteTests(AudioTests):
self.assertEqual(testfile.read(13), b'ababagalamaga') self.assertEqual(testfile.read(13), b'ababagalamaga')
self.check_file(testfile, self.nframes, self.frames) self.check_file(testfile, self.nframes, self.frames)
def test_unseekable_read(self):
with self.create_file(TESTFN) as f:
f.setnframes(self.nframes)
f.writeframes(self.frames)
with UnseekableIO(TESTFN, 'rb') as testfile:
self.check_file(testfile, self.nframes, self.frames)
def test_unseekable_write(self):
with UnseekableIO(TESTFN, 'wb') as testfile:
with self.create_file(testfile) as f:
f.setnframes(self.nframes)
f.writeframes(self.frames)
self.check_file(TESTFN, self.nframes, self.frames)
def test_unseekable_incompleted_write(self):
with UnseekableIO(TESTFN, 'wb') as testfile:
testfile.write(b'ababagalamaga')
f = self.create_file(testfile)
f.setnframes(self.nframes + 1)
try:
f.writeframes(self.frames)
except OSError:
pass
try:
f.close()
except OSError:
pass
with open(TESTFN, 'rb') as testfile:
self.assertEqual(testfile.read(13), b'ababagalamaga')
self.check_file(testfile, self.nframes + 1, self.frames)
def test_unseekable_overflowed_write(self):
with UnseekableIO(TESTFN, 'wb') as testfile:
testfile.write(b'ababagalamaga')
f = self.create_file(testfile)
f.setnframes(self.nframes - 1)
try:
f.writeframes(self.frames)
except OSError:
pass
try:
f.close()
except OSError:
pass
with open(TESTFN, 'rb') as testfile:
self.assertEqual(testfile.read(13), b'ababagalamaga')
framesize = self.nchannels * self.sampwidth
self.check_file(testfile, self.nframes - 1, self.frames[:-framesize])
class AudioTestsWithSourceFile(AudioTests): class AudioTestsWithSourceFile(AudioTests):

View File

@ -8,10 +8,17 @@ import struct
import aifc import aifc
class AifcPCM8Test(audiotests.AudioWriteTests, class AifcTest(audiotests.AudioWriteTests,
audiotests.AudioTestsWithSourceFile, audiotests.AudioTestsWithSourceFile):
unittest.TestCase):
module = aifc module = aifc
close_fd = True
test_unseekable_read = None
test_unseekable_write = None
test_unseekable_incompleted_write = None
test_unseekable_overflowed_write = None
class AifcPCM8Test(AifcTest, unittest.TestCase):
sndfilename = 'pluck-pcm8.aiff' sndfilename = 'pluck-pcm8.aiff'
sndfilenframes = 3307 sndfilenframes = 3307
nchannels = 2 nchannels = 2
@ -26,13 +33,9 @@ class AifcPCM8Test(audiotests.AudioWriteTests,
11FA 3EFB BCFC 66FF CF04 4309 C10E 5112 EE17 8216 7F14 8012 \ 11FA 3EFB BCFC 66FF CF04 4309 C10E 5112 EE17 8216 7F14 8012 \
490E 520D EF0F CE0F E40C 630A 080A 2B0B 510E 8B11 B60E 440A \ 490E 520D EF0F CE0F E40C 630A 080A 2B0B 510E 8B11 B60E 440A \
""") """)
close_fd = True
class AifcPCM16Test(audiotests.AudioWriteTests, class AifcPCM16Test(AifcTest, unittest.TestCase):
audiotests.AudioTestsWithSourceFile,
unittest.TestCase):
module = aifc
sndfilename = 'pluck-pcm16.aiff' sndfilename = 'pluck-pcm16.aiff'
sndfilenframes = 3307 sndfilenframes = 3307
nchannels = 2 nchannels = 2
@ -49,13 +52,9 @@ class AifcPCM16Test(audiotests.AudioWriteTests,
EEE21753 82071665 7FFF1443 8004128F 49A20EAF 52BB0DBA EFB40F60 CE3C0FBF \ EEE21753 82071665 7FFF1443 8004128F 49A20EAF 52BB0DBA EFB40F60 CE3C0FBF \
E4B30CEC 63430A5C 08C80A20 2BBB0B08 514A0E43 8BCF1139 B6F60EEB 44120A5E \ E4B30CEC 63430A5C 08C80A20 2BBB0B08 514A0E43 8BCF1139 B6F60EEB 44120A5E \
""") """)
close_fd = True
class AifcPCM24Test(audiotests.AudioWriteTests, class AifcPCM24Test(AifcTest, unittest.TestCase):
audiotests.AudioTestsWithSourceFile,
unittest.TestCase):
module = aifc
sndfilename = 'pluck-pcm24.aiff' sndfilename = 'pluck-pcm24.aiff'
sndfilenframes = 3307 sndfilenframes = 3307
nchannels = 2 nchannels = 2
@ -78,13 +77,9 @@ class AifcPCM24Test(audiotests.AudioWriteTests,
E4B49C0CEA2D 6344A80A5A7C 08C8FE0A1FFE 2BB9860B0A0E \ E4B49C0CEA2D 6344A80A5A7C 08C8FE0A1FFE 2BB9860B0A0E \
51486F0E44E1 8BCC64113B05 B6F4EC0EEB36 4413170A5B48 \ 51486F0E44E1 8BCC64113B05 B6F4EC0EEB36 4413170A5B48 \
""") """)
close_fd = True
class AifcPCM32Test(audiotests.AudioWriteTests, class AifcPCM32Test(AifcTest, unittest.TestCase):
audiotests.AudioTestsWithSourceFile,
unittest.TestCase):
module = aifc
sndfilename = 'pluck-pcm32.aiff' sndfilename = 'pluck-pcm32.aiff'
sndfilenframes = 3307 sndfilenframes = 3307
nchannels = 2 nchannels = 2
@ -107,13 +102,9 @@ class AifcPCM32Test(audiotests.AudioWriteTests,
E4B49CC00CEA2D90 6344A8800A5A7CA0 08C8FE800A1FFEE0 2BB986C00B0A0E00 \ E4B49CC00CEA2D90 6344A8800A5A7CA0 08C8FE800A1FFEE0 2BB986C00B0A0E00 \
51486F800E44E190 8BCC6480113B0580 B6F4EC000EEB3630 441317800A5B48A0 \ 51486F800E44E190 8BCC6480113B0580 B6F4EC000EEB3630 441317800A5B48A0 \
""") """)
close_fd = True
class AifcULAWTest(audiotests.AudioWriteTests, class AifcULAWTest(AifcTest, unittest.TestCase):
audiotests.AudioTestsWithSourceFile,
unittest.TestCase):
module = aifc
sndfilename = 'pluck-ulaw.aifc' sndfilename = 'pluck-ulaw.aifc'
sndfilenframes = 3307 sndfilenframes = 3307
nchannels = 2 nchannels = 2
@ -132,13 +123,9 @@ class AifcULAWTest(audiotests.AudioWriteTests,
""") """)
if sys.byteorder != 'big': if sys.byteorder != 'big':
frames = audiotests.byteswap2(frames) frames = audiotests.byteswap2(frames)
close_fd = True
class AifcALAWTest(audiotests.AudioWriteTests, class AifcALAWTest(AifcTest, unittest.TestCase):
audiotests.AudioTestsWithSourceFile,
unittest.TestCase):
module = aifc
sndfilename = 'pluck-alaw.aifc' sndfilename = 'pluck-alaw.aifc'
sndfilenframes = 3307 sndfilenframes = 3307
nchannels = 2 nchannels = 2
@ -157,7 +144,6 @@ class AifcALAWTest(audiotests.AudioWriteTests,
""") """)
if sys.byteorder != 'big': if sys.byteorder != 'big':
frames = audiotests.byteswap2(frames) frames = audiotests.byteswap2(frames)
close_fd = True
class AifcMiscTest(audiotests.AudioTests, unittest.TestCase): class AifcMiscTest(audiotests.AudioTests, unittest.TestCase):

View File

@ -491,14 +491,18 @@ class Wave_write:
if not self._nframes: if not self._nframes:
self._nframes = initlength // (self._nchannels * self._sampwidth) self._nframes = initlength // (self._nchannels * self._sampwidth)
self._datalength = self._nframes * self._nchannels * self._sampwidth self._datalength = self._nframes * self._nchannels * self._sampwidth
self._form_length_pos = self._file.tell() try:
self._form_length_pos = self._file.tell()
except (AttributeError, OSError):
self._form_length_pos = None
self._file.write(struct.pack('<L4s4sLHHLLHH4s', self._file.write(struct.pack('<L4s4sLHHLLHH4s',
36 + self._datalength, b'WAVE', b'fmt ', 16, 36 + self._datalength, b'WAVE', b'fmt ', 16,
WAVE_FORMAT_PCM, self._nchannels, self._framerate, WAVE_FORMAT_PCM, self._nchannels, self._framerate,
self._nchannels * self._framerate * self._sampwidth, self._nchannels * self._framerate * self._sampwidth,
self._nchannels * self._sampwidth, self._nchannels * self._sampwidth,
self._sampwidth * 8, b'data')) self._sampwidth * 8, b'data'))
self._data_length_pos = self._file.tell() if self._form_length_pos is not None:
self._data_length_pos = self._file.tell()
self._file.write(struct.pack('<L', self._datalength)) self._file.write(struct.pack('<L', self._datalength))
self._headerwritten = True self._headerwritten = True

View File

@ -47,6 +47,8 @@ Core and Builtins
Library Library
------- -------
- Issue #5202: Added support for unseekable files in the wave module.
- Issue #19544 and Issue #1180: Restore global option to ignore - Issue #19544 and Issue #1180: Restore global option to ignore
~/.pydistutils.cfg in Distutils, accidentally removed in backout of ~/.pydistutils.cfg in Distutils, accidentally removed in backout of
distutils2 changes. distutils2 changes.