blob: 0a9abd255a63e4a4ba44d17747ea76b13a6e60c5 [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
20Usually a 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 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
64 self.size_read = 0
65 self.offset = self.file.tell()
66
67 def getname(self):
68 """Return the name (ID) of the current chunk."""
69 return self.chunkname
70
71 def close(self):
72 if not self.closed:
73 self.skip()
74 self.closed = 1
75
76 def isatty(self):
77 if self.closed:
78 raise ValueError, "I/O operation on closed file"
79 return 0
80
81 def seek(self, pos, mode = 0):
82 """Seek to specified position into the chunk.
83 Default position is 0 (start of chunk).
84 If the file is not seekable, this will result in an error.
85 """
86
87 if self.closed:
88 raise ValueError, "I/O operation on closed file"
89 if mode == 1:
90 pos = pos + self.size_read
91 elif mode == 2:
92 pos = pos + self.chunk_size
93 if pos < 0 or pos > self.chunksize:
94 raise RuntimeError
95 self.file.seek(self.offset + pos, 0)
96 self.size_read = pos
97
98 def tell(self):
99 if self.closed:
100 raise ValueError, "I/O operation on closed file"
101 return self.size_read
102
103 def read(self, n = -1):
104 """Read at most n bytes from the chunk.
105 If n is omitted or negative, read until the end
106 of the chunk.
107 """
108
109 if self.closed:
110 raise ValueError, "I/O operation on closed file"
111 if self.size_read >= self.chunksize:
112 return ''
113 if n < 0:
114 n = self.chunksize - self.size_read
115 if n > self.chunksize - self.size_read:
116 n = self.chunksize - self.size_read
117 data = self.file.read(n)
118 self.size_read = self.size_read + len(data)
119 if self.size_read == self.chunksize and \
120 self.align and \
121 (self.chunksize & 1):
122 dummy = self.file.read(1)
123 self.size_read = self.size_read + len(dummy)
124 return data
125
126 def skip(self):
127 """Skip the rest of the chunk.
128 If you are not interested in the contents of the chunk,
129 this method should be called so that the file points to
130 the start of the next chunk.
131 """
132
133 if self.closed:
134 raise ValueError, "I/O operation on closed file"
135 try:
136 self.file.seek(self.chunksize - self.size_read, 1)
137 except RuntimeError:
138 while self.size_read < self.chunksize:
139 dummy = self.read(8192)
140 if not dummy:
141 raise EOFError
142