Patch/new code by Sjoerd Mullender:

Separate the Chunk class out of the aifc module into a new "chunk" module.
This commit is contained in:
Guido van Rossum 1999-06-09 13:32:28 +00:00
parent 1f2e09bc45
commit 8ea7bb8e78
2 changed files with 155 additions and 74 deletions

View File

@ -236,45 +236,7 @@ def _write_float(f, x):
_write_long(f, himant) _write_long(f, himant)
_write_long(f, lomant) _write_long(f, lomant)
class Chunk: from chunk import Chunk
def __init__(self, file):
self.file = file
self.chunkname = self.file.read(4)
if len(self.chunkname) < 4:
raise EOFError
self.chunksize = _read_long(self.file)
self.size_read = 0
self.offset = self.file.tell()
def rewind(self):
self.file.seek(self.offset, 0)
self.size_read = 0
def setpos(self, pos):
if pos < 0 or pos > self.chunksize:
raise RuntimeError
self.file.seek(self.offset + pos, 0)
self.size_read = pos
def read(self, length):
if self.size_read >= self.chunksize:
return ''
if length > self.chunksize - self.size_read:
length = self.chunksize - self.size_read
data = self.file.read(length)
self.size_read = self.size_read + len(data)
return data
def skip(self):
try:
self.file.seek(self.chunksize - self.size_read, 1)
except RuntimeError:
while self.size_read < self.chunksize:
dummy = self.read(8192)
if not dummy:
raise EOFError
if self.chunksize & 1:
dummy = self.read(1)
class Aifc_read: class Aifc_read:
# Variables used in this class: # Variables used in this class:
@ -312,26 +274,16 @@ class Aifc_read:
# _ssnd_chunk -- instantiation of a chunk class for the SSND chunk # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk
# _framesize -- size of one frame in the file # _framesize -- size of one frame in the file
## if 0: access _file, _nchannels, _nframes, _sampwidth, _framerate, \
## _comptype, _compname, _markers, _soundpos, _version, \
## _decomp, _comm_chunk_read, __aifc, _ssnd_seek_needed, \
## _ssnd_chunk, _framesize: private
def initfp(self, file): def initfp(self, file):
self._file = file
self._version = 0 self._version = 0
self._decomp = None self._decomp = None
self._convert = None self._convert = None
self._markers = [] self._markers = []
self._soundpos = 0 self._soundpos = 0
form = self._file.read(4) self._file = Chunk(file)
if form != 'FORM': if self._file.getname() != 'FORM':
raise Error, 'file does not start with FORM id' raise Error, 'file does not start with FORM id'
formlength = _read_long(self._file)
if formlength <= 0:
raise Error, 'invalid FORM chunk data size'
formdata = self._file.read(4) formdata = self._file.read(4)
formlength = formlength - 4
if formdata == 'AIFF': if formdata == 'AIFF':
self._aifc = 0 self._aifc = 0
elif formdata == 'AIFC': elif formdata == 'AIFC':
@ -339,38 +291,31 @@ class Aifc_read:
else: else:
raise Error, 'not an AIFF or AIFF-C file' raise Error, 'not an AIFF or AIFF-C file'
self._comm_chunk_read = 0 self._comm_chunk_read = 0
while formlength > 0: while 1:
self._ssnd_seek_needed = 1 self._ssnd_seek_needed = 1
#DEBUG: SGI's soundfiler has a bug. There should #DEBUG: SGI's soundfiler has a bug. There should
# be no need to check for EOF here. # be no need to check for EOF here.
try: try:
chunk = Chunk(self._file) chunk = Chunk(self._file)
except EOFError: except EOFError:
if formlength == 8: break
print 'Warning: FORM chunk size too large' chunkname = chunk.getname()
formlength = 0 if chunkname == 'COMM':
break
raise EOFError # different error, raise exception
if chunk.chunkname == 'COMM':
self._read_comm_chunk(chunk) self._read_comm_chunk(chunk)
self._comm_chunk_read = 1 self._comm_chunk_read = 1
elif chunk.chunkname == 'SSND': elif chunkname == 'SSND':
self._ssnd_chunk = chunk self._ssnd_chunk = chunk
dummy = chunk.read(8) dummy = chunk.read(8)
self._ssnd_seek_needed = 0 self._ssnd_seek_needed = 0
elif chunk.chunkname == 'FVER': elif chunkname == 'FVER':
self._version = _read_long(chunk) self._version = _read_long(chunk)
elif chunk.chunkname == 'MARK': elif chunkname == 'MARK':
self._readmark(chunk) self._readmark(chunk)
elif chunk.chunkname in _skiplist: elif chunkname in _skiplist:
pass pass
else: else:
raise Error, 'unrecognized chunk type '+chunk.chunkname raise Error, 'unrecognized chunk type '+chunk.chunkname
formlength = formlength - 8 - chunk.chunksize chunk.skip()
if chunk.chunksize & 1:
formlength = formlength - 1
if formlength > 0:
chunk.skip()
if not self._comm_chunk_read or not self._ssnd_chunk: if not self._comm_chunk_read or not self._ssnd_chunk:
raise Error, 'COMM chunk and/or SSND chunk missing' raise Error, 'COMM chunk and/or SSND chunk missing'
if self._aifc and self._decomp: if self._aifc and self._decomp:
@ -460,7 +405,7 @@ class Aifc_read:
def readframes(self, nframes): def readframes(self, nframes):
if self._ssnd_seek_needed: if self._ssnd_seek_needed:
self._ssnd_chunk.rewind() self._ssnd_chunk.seek(0)
dummy = self._ssnd_chunk.read(8) dummy = self._ssnd_chunk.read(8)
pos = self._soundpos * self._framesize pos = self._soundpos * self._framesize
if pos: if pos:
@ -477,7 +422,6 @@ class Aifc_read:
# #
# Internal methods. # Internal methods.
# #
## if 0: access *: private
def _decomp_data(self, data): def _decomp_data(self, data):
import cl import cl
@ -611,10 +555,6 @@ class Aifc_write:
# _datalength -- the size of the audio samples written to the header # _datalength -- the size of the audio samples written to the header
# _datawritten -- the size of the audio samples actually written # _datawritten -- the size of the audio samples actually written
## if 0: access _file, _comptype, _compname, _nchannels, _sampwidth, \
## _framerate, _nframes, _aifc, _version, _comp, \
## _nframeswritten, _datalength, _datawritten: private
def __init__(self, f): def __init__(self, f):
if type(f) == type(''): if type(f) == type(''):
filename = f filename = f
@ -805,7 +745,6 @@ class Aifc_write:
# #
# Internal methods. # Internal methods.
# #
## if 0: access *: private
def _comp_data(self, data): def _comp_data(self, data):
import cl import cl

142
Lib/chunk.py Normal file
View File

@ -0,0 +1,142 @@
"""Simple class to read IFF chunks.
An IFF chunk (used in formats such as AIFF, TIFF, RMFF (RealMedia File
Format)) has the following structure:
+----------------+
| ID (4 bytes) |
+----------------+
| size (4 bytes) |
+----------------+
| data |
| ... |
+----------------+
The ID is a 4-byte string which identifies the type of chunk.
The size field (a 32-bit value, encoded using big-endian byte order)
gives the size of the whole chunk, including the 8-byte header.
Usually a IFF-type file consists of one or more chunks. The proposed
usage of the Chunk class defined here is to instantiate an instance at
the start of each chunk and read from the instance until it reaches
the end, after which a new instance can be instantiated. At the end
of the file, creating a new instance will fail with a EOFError
exception.
Usage:
while 1:
try:
chunk = Chunk(file)
except EOFError:
break
chunktype = chunk.getname()
while 1:
data = chunk.read(nbytes)
if not data:
pass
# do something with data
The interface is file-like. The implemented methods are:
read, close, seek, tell, isatty.
Extra methods are: skip() (called by close, skips to the end of the chunk),
getname() (returns the name (ID) of the chunk)
The __init__ method has one required argument, a file-like object
(including a chunk instance), and one optional argument, a flag which
specifies whether or not chunks are aligned on 2-byte boundaries. The
default is 1, i.e. aligned.
"""
class Chunk:
def __init__(self, file, align = 1):
import struct
self.closed = 0
self.align = align # whether to align to word (2-byte) boundaries
self.file = file
self.chunkname = file.read(4)
if len(self.chunkname) < 4:
raise EOFError
try:
self.chunksize = struct.unpack('>l', file.read(4))[0]
except struct.error:
raise EOFError
self.size_read = 0
self.offset = self.file.tell()
def getname(self):
"""Return the name (ID) of the current chunk."""
return self.chunkname
def close(self):
if not self.closed:
self.skip()
self.closed = 1
def isatty(self):
if self.closed:
raise ValueError, "I/O operation on closed file"
return 0
def seek(self, pos, mode = 0):
"""Seek to specified position into the chunk.
Default position is 0 (start of chunk).
If the file is not seekable, this will result in an error.
"""
if self.closed:
raise ValueError, "I/O operation on closed file"
if mode == 1:
pos = pos + self.size_read
elif mode == 2:
pos = pos + self.chunk_size
if pos < 0 or pos > self.chunksize:
raise RuntimeError
self.file.seek(self.offset + pos, 0)
self.size_read = pos
def tell(self):
if self.closed:
raise ValueError, "I/O operation on closed file"
return self.size_read
def read(self, n = -1):
"""Read at most n bytes from the chunk.
If n is omitted or negative, read until the end
of the chunk.
"""
if self.closed:
raise ValueError, "I/O operation on closed file"
if self.size_read >= self.chunksize:
return ''
if n < 0:
n = self.chunksize - self.size_read
if n > self.chunksize - self.size_read:
n = self.chunksize - self.size_read
data = self.file.read(n)
self.size_read = self.size_read + len(data)
if self.size_read == self.chunksize and \
self.align and \
(self.chunksize & 1):
dummy = self.file.read(1)
self.size_read = self.size_read + len(dummy)
return data
def skip(self):
"""Skip the rest of the chunk.
If you are not interested in the contents of the chunk,
this method should be called so that the file points to
the start of the next chunk.
"""
if self.closed:
raise ValueError, "I/O operation on closed file"
try:
self.file.seek(self.chunksize - self.size_read, 1)
except RuntimeError:
while self.size_read < self.chunksize:
dummy = self.read(8192)
if not dummy:
raise EOFError