Commit | Line | Data |
---|---|---|
920dae64 AT |
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 |