]> git.lizzy.rs Git - plan9front.git/blob - sys/lib/python/chunk.py
dist/mkfile: run binds in subshell
[plan9front.git] / sys / lib / python / chunk.py
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