Home | History | Annotate | Download | only in python2.7
      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 a 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             self.skip()
     89             self.closed = True
     90 
     91     def isatty(self):
     92         if self.closed:
     93             raise ValueError, "I/O operation on closed file"
     94         return False
     95 
     96     def seek(self, pos, whence=0):
     97         """Seek to specified position into the chunk.
     98         Default position is 0 (start of chunk).
     99         If the file is not seekable, this will result in an error.
    100         """
    101 
    102         if self.closed:
    103             raise ValueError, "I/O operation on closed file"
    104         if not self.seekable:
    105             raise IOError, "cannot seek"
    106         if whence == 1:
    107             pos = pos + self.size_read
    108         elif whence == 2:
    109             pos = pos + self.chunksize
    110         if pos < 0 or pos > self.chunksize:
    111             raise RuntimeError
    112         self.file.seek(self.offset + pos, 0)
    113         self.size_read = pos
    114 
    115     def tell(self):
    116         if self.closed:
    117             raise ValueError, "I/O operation on closed file"
    118         return self.size_read
    119 
    120     def read(self, size=-1):
    121         """Read at most size bytes from the chunk.
    122         If size is omitted or negative, read until the end
    123         of the chunk.
    124         """
    125 
    126         if self.closed:
    127             raise ValueError, "I/O operation on closed file"
    128         if self.size_read >= self.chunksize:
    129             return ''
    130         if size < 0:
    131             size = self.chunksize - self.size_read
    132         if size > self.chunksize - self.size_read:
    133             size = self.chunksize - self.size_read
    134         data = self.file.read(size)
    135         self.size_read = self.size_read + len(data)
    136         if self.size_read == self.chunksize and \
    137            self.align and \
    138            (self.chunksize & 1):
    139             dummy = self.file.read(1)
    140             self.size_read = self.size_read + len(dummy)
    141         return data
    142 
    143     def skip(self):
    144         """Skip the rest of the chunk.
    145         If you are not interested in the contents of the chunk,
    146         this method should be called so that the file points to
    147         the start of the next chunk.
    148         """
    149 
    150         if self.closed:
    151             raise ValueError, "I/O operation on closed file"
    152         if self.seekable:
    153             try:
    154                 n = self.chunksize - self.size_read
    155                 # maybe fix alignment
    156                 if self.align and (self.chunksize & 1):
    157                     n = n + 1
    158                 self.file.seek(n, 1)
    159                 self.size_read = self.size_read + n
    160                 return
    161             except IOError:
    162                 pass
    163         while self.size_read < self.chunksize:
    164             n = min(8192, self.chunksize - self.size_read)
    165             dummy = self.read(n)
    166             if not dummy:
    167                 raise EOFError
    168