blob: 231a59caff0423d9325385e66818f711b578d1ff [file] [log] [blame]
Guido van Rossum8ea7bb81999-06-09 13:32:28 +00001"""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
Fred Drake624a1911999-06-25 14:58:44 +000020Usually an IFF-type file consists of one or more chunks. The proposed
Guido van Rossum8ea7bb81999-06-09 13:32:28 +000021usage 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 a EOFError
25exception.
26
27Usage:
28while 1:
29 try:
30 chunk = Chunk(file)
31 except EOFError:
32 break
33 chunktype = chunk.getname()
34 while 1:
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 = 1):
53 import struct
54 self.closed = 0
55 self.align = align # whether to align to word (2-byte) boundaries
56 self.file = file
57 self.chunkname = file.read(4)
58 if len(self.chunkname) < 4:
59 raise EOFError
60 try:
61 self.chunksize = struct.unpack('>l', file.read(4))[0]
62 except struct.error:
63 raise EOFError
Guido van Rossum56b20591999-06-09 13:41:18 +000064 self.chunksize = self.chunksize - 8 # subtract header
Guido van Rossum8ea7bb81999-06-09 13:32:28 +000065 self.size_read = 0
Guido van Rossum7bb11d61999-06-16 12:25:34 +000066 try:
67 self.offset = self.file.tell()
68 except:
69 self.seekable = 0
70 else:
71 self.seekable = 1
Guido van Rossum8ea7bb81999-06-09 13:32:28 +000072
73 def getname(self):
74 """Return the name (ID) of the current chunk."""
75 return self.chunkname
76
77 def close(self):
78 if not self.closed:
79 self.skip()
80 self.closed = 1
81
82 def isatty(self):
83 if self.closed:
84 raise ValueError, "I/O operation on closed file"
85 return 0
86
Fred Drake624a1911999-06-25 14:58:44 +000087 def seek(self, pos, whence = 0):
Guido van Rossum8ea7bb81999-06-09 13:32:28 +000088 """Seek to specified position into the chunk.
89 Default position is 0 (start of chunk).
90 If the file is not seekable, this will result in an error.
91 """
92
93 if self.closed:
94 raise ValueError, "I/O operation on closed file"
Guido van Rossum7bb11d61999-06-16 12:25:34 +000095 if not self.seekable:
96 raise IOError, "cannot seek"
Fred Drake624a1911999-06-25 14:58:44 +000097 if whence == 1:
Guido van Rossum8ea7bb81999-06-09 13:32:28 +000098 pos = pos + self.size_read
Fred Drake624a1911999-06-25 14:58:44 +000099 elif whence == 2:
Guido van Rossum8ea7bb81999-06-09 13:32:28 +0000100 pos = pos + self.chunk_size
101 if pos < 0 or pos > self.chunksize:
102 raise RuntimeError
103 self.file.seek(self.offset + pos, 0)
104 self.size_read = pos
105
106 def tell(self):
107 if self.closed:
108 raise ValueError, "I/O operation on closed file"
109 return self.size_read
110
Fred Drake624a1911999-06-25 14:58:44 +0000111 def read(self, size = -1):
112 """Read at most size bytes from the chunk.
113 If size is omitted or negative, read until the end
Guido van Rossum8ea7bb81999-06-09 13:32:28 +0000114 of the chunk.
115 """
116
117 if self.closed:
118 raise ValueError, "I/O operation on closed file"
119 if self.size_read >= self.chunksize:
120 return ''
Fred Drake624a1911999-06-25 14:58:44 +0000121 if size < 0:
122 size = self.chunksize - self.size_read
123 if size > self.chunksize - self.size_read:
124 size = self.chunksize - self.size_read
125 data = self.file.read(size)
Guido van Rossum8ea7bb81999-06-09 13:32:28 +0000126 self.size_read = self.size_read + len(data)
127 if self.size_read == self.chunksize and \
128 self.align and \
129 (self.chunksize & 1):
130 dummy = self.file.read(1)
131 self.size_read = self.size_read + len(dummy)
132 return data
133
134 def skip(self):
135 """Skip the rest of the chunk.
136 If you are not interested in the contents of the chunk,
137 this method should be called so that the file points to
138 the start of the next chunk.
139 """
140
141 if self.closed:
142 raise ValueError, "I/O operation on closed file"
Guido van Rossum7bb11d61999-06-16 12:25:34 +0000143 if self.seekable:
144 try:
145 n = self.chunksize - self.size_read
146 # maybe fix alignment
147 if self.align and (self.chunksize & 1):
148 n = n + 1
149 self.file.seek(n, 1)
150 self.size_read = self.size_read + n
151 return
152 except:
153 pass
154 while self.size_read < self.chunksize:
155 n = min(8192, self.chunksize - self.size_read)
156 dummy = self.read(n)
157 if not dummy:
158 raise EOFError
Guido van Rossum8ea7bb81999-06-09 13:32:28 +0000159