Home | History | Annotate | Download | only in Lib
      1 """Simple class to read IFF chunks.
      2 
      3 An IFF chunk (used in formats such as AIFF, TIFF, RMFF (RealMedia File
      4 Format)) has the following structure:
      5 
      6 +----------------+
      7 | ID (4 bytes)   |
      8 +----------------+
      9 | size (4 bytes) |
     10 +----------------+
     11 | data           |
     12 | ...            |
     13 +----------------+
     14 
     15 The ID is a 4-byte string which identifies the type of chunk.
     16 
     17 The size field (a 32-bit value, encoded using big-endian byte order)
     18 gives the size of the whole chunk, including the 8-byte header.
     19 
     20 Usually an IFF-type file consists of one or more chunks.  The proposed
     21 usage of the Chunk class defined here is to instantiate an instance at
     22 the start of each chunk and read from the instance until it reaches
     23 the end, after which a new instance can be instantiated.  At the end
     24 of the file, creating a new instance will fail with an EOFError
     25 exception.
     26 
     27 Usage:
     28 while True:
     29     try:
     30         chunk = Chunk(file)
     31     except EOFError:
     32         break
     33     chunktype = chunk.getname()
     34     while True:
     35         data = chunk.read(nbytes)
     36         if not data:
     37             pass
     38         # do something with data
     39 
     40 The interface is file-like.  The implemented methods are:
     41 read, close, seek, tell, isatty.
     42 Extra methods are: skip() (called by close, skips to the end of the chunk),
     43 getname() (returns the name (ID) of the chunk)
     44 
     45 The __init__ method has one required argument, a file-like object
     46 (including a chunk instance), and one optional argument, a flag which
     47 specifies whether or not chunks are aligned on 2-byte boundaries.  The
     48 default is 1, i.e. aligned.
     49 """
     50 
     51 class Chunk:
     52     def __init__(self, file, align=True, bigendian=True, inclheader=False):
     53         import struct
     54         self.closed = False
     55         self.align = align      # whether to align to word (2-byte) boundaries
     56         if bigendian:
     57             strflag = '>'
     58         else:
     59             strflag = '<'
     60         self.file = file
     61         self.chunkname = file.read(4)
     62         if len(self.chunkname) < 4:
     63             raise EOFError
     64         try:
     65             self.chunksize = struct.unpack(strflag+'L', file.read(4))[0]
     66         except struct.error:
     67             raise EOFError
     68         if inclheader:
     69             self.chunksize = self.chunksize - 8 # subtract header
     70         self.size_read = 0
     71         try:
     72             self.offset = self.file.tell()
     73         except (AttributeError, IOError):
     74             self.seekable = False
     75         else:
     76             self.seekable = True
     77 
     78     def getname(self):
     79         """Return the name (ID) of the current chunk."""
     80         return self.chunkname
     81 
     82     def getsize(self):
     83         """Return the size of the current chunk."""
     84         return self.chunksize
     85 
     86     def close(self):
     87         if not self.closed:
     88             try:
     89                 self.skip()
     90             finally:
     91                 self.closed = True
     92 
     93     def isatty(self):
     94         if self.closed:
     95             raise ValueError, "I/O operation on closed file"
     96         return False
     97 
     98     def seek(self, pos, whence=0):
     99         """Seek to specified position into the chunk.
    100         Default position is 0 (start of chunk).
    101         If the file is not seekable, this will result in an error.
    102         """
    103 
    104         if self.closed:
    105             raise ValueError, "I/O operation on closed file"
    106         if not self.seekable:
    107             raise IOError, "cannot seek"
    108         if whence == 1:
    109             pos = pos + self.size_read
    110         elif whence == 2:
    111             pos = pos + self.chunksize
    112         if pos < 0 or pos > self.chunksize:
    113             raise RuntimeError
    114         self.file.seek(self.offset + pos, 0)
    115         self.size_read = pos
    116 
    117     def tell(self):
    118         if self.closed:
    119             raise ValueError, "I/O operation on closed file"
    120         return self.size_read
    121 
    122     def read(self, size=-1):
    123         """Read at most size bytes from the chunk.
    124         If size is omitted or negative, read until the end
    125         of the chunk.
    126         """
    127 
    128         if self.closed:
    129             raise ValueError, "I/O operation on closed file"
    130         if self.size_read >= self.chunksize:
    131             return ''
    132         if size < 0:
    133             size = self.chunksize - self.size_read
    134         if size > self.chunksize - self.size_read:
    135             size = self.chunksize - self.size_read
    136         data = self.file.read(size)
    137         self.size_read = self.size_read + len(data)
    138         if self.size_read == self.chunksize and \
    139            self.align and \
    140            (self.chunksize & 1):
    141             dummy = self.file.read(1)
    142             self.size_read = self.size_read + len(dummy)
    143         return data
    144 
    145     def skip(self):
    146         """Skip the rest of the chunk.
    147         If you are not interested in the contents of the chunk,
    148         this method should be called so that the file points to
    149         the start of the next chunk.
    150         """
    151 
    152         if self.closed:
    153             raise ValueError, "I/O operation on closed file"
    154         if self.seekable:
    155             try:
    156                 n = self.chunksize - self.size_read
    157                 # maybe fix alignment
    158                 if self.align and (self.chunksize & 1):
    159                     n = n + 1
    160                 self.file.seek(n, 1)
    161                 self.size_read = self.size_read + n
    162                 return
    163             except IOError:
    164                 pass
    165         while self.size_read < self.chunksize:
    166             n = min(8192, self.chunksize - self.size_read)
    167             dummy = self.read(n)
    168             if not dummy:
    169                 raise EOFError
    170