• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Simple class to read IFF chunks.
2
3An IFF chunk (used in formats such as AIFF, TIFF, RMFF (RealMedia File
4Format)) has the following structure:
5
6+----------------+
7| ID (4 bytes)   |
8+----------------+
9| size (4 bytes) |
10+----------------+
11| data           |
12| ...            |
13+----------------+
14
15The ID is a 4-byte string which identifies the type of chunk.
16
17The size field (a 32-bit value, encoded using big-endian byte order)
18gives the size of the whole chunk, including the 8-byte header.
19
20Usually an IFF-type file consists of one or more chunks.  The proposed
21usage of the Chunk class defined here is to instantiate an instance at
22the start of each chunk and read from the instance until it reaches
23the end, after which a new instance can be instantiated.  At the end
24of the file, creating a new instance will fail with an EOFError
25exception.
26
27Usage:
28while 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
40The interface is file-like.  The implemented methods are:
41read, close, seek, tell, isatty.
42Extra methods are: skip() (called by close, skips to the end of the chunk),
43getname() (returns the name (ID) of the chunk)
44
45The __init__ method has one required argument, a file-like object
46(including a chunk instance), and one optional argument, a flag which
47specifies whether or not chunks are aligned on 2-byte boundaries.  The
48default is 1, i.e. aligned.
49"""
50
51class 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