blob: 7ebdbeb68c1093e387046a8c616b6f41d3bce07e [file] [log] [blame]
Guido van Rossum4acc25b2000-02-02 15:10:15 +00001"""Stuff to parse AIFF-C and AIFF files.
2
3Unless explicitly stated otherwise, the description below is true
4both for AIFF-C files and AIFF files.
5
6An AIFF-C file has the following structure.
7
8 +-----------------+
9 | FORM |
10 +-----------------+
11 | <size> |
12 +----+------------+
13 | | AIFC |
14 | +------------+
15 | | <chunks> |
16 | | . |
17 | | . |
18 | | . |
19 +----+------------+
20
21An AIFF file has the string "AIFF" instead of "AIFC".
22
23A chunk consists of an identifier (4 bytes) followed by a size (4 bytes,
24big endian order), followed by the data. The size field does not include
25the size of the 8 byte header.
26
27The following chunk types are recognized.
28
29 FVER
30 <version number of AIFF-C defining document> (AIFF-C only).
31 MARK
32 <# of markers> (2 bytes)
33 list of markers:
34 <marker ID> (2 bytes, must be > 0)
35 <position> (4 bytes)
36 <marker name> ("pstring")
37 COMM
38 <# of channels> (2 bytes)
39 <# of sound frames> (4 bytes)
40 <size of the samples> (2 bytes)
41 <sampling frequency> (10 bytes, IEEE 80-bit extended
42 floating point)
43 in AIFF-C files only:
44 <compression type> (4 bytes)
45 <human-readable version of compression type> ("pstring")
46 SSND
47 <offset> (4 bytes, not used by this program)
48 <blocksize> (4 bytes, not used by this program)
49 <sound data>
50
51A pstring consists of 1 byte length, a string of characters, and 0 or 1
52byte pad to make the total length even.
53
54Usage.
55
56Reading AIFF files:
57 f = aifc.open(file, 'r')
58where file is either the name of a file or an open file pointer.
59The open file pointer must have methods read(), seek(), and close().
60In some types of audio files, if the setpos() method is not used,
61the seek() method is not necessary.
62
63This returns an instance of a class with the following public methods:
64 getnchannels() -- returns number of audio channels (1 for
65 mono, 2 for stereo)
66 getsampwidth() -- returns sample width in bytes
67 getframerate() -- returns sampling frequency
68 getnframes() -- returns number of audio frames
69 getcomptype() -- returns compression type ('NONE' for AIFF files)
70 getcompname() -- returns human-readable version of
71 compression type ('not compressed' for AIFF files)
R David Murray4d35e752013-07-25 16:12:01 -040072 getparams() -- returns a namedtuple consisting of all of the
Guido van Rossum4acc25b2000-02-02 15:10:15 +000073 above in the above order
74 getmarkers() -- get the list of marks in the audio file or None
75 if there are no marks
76 getmark(id) -- get mark with the specified id (raises an error
77 if the mark does not exist)
78 readframes(n) -- returns at most n frames of audio
79 rewind() -- rewind to the beginning of the audio stream
80 setpos(pos) -- seek to the specified position
81 tell() -- return the current position
82 close() -- close the instance (make it unusable)
83The position returned by tell(), the position given to setpos() and
84the position of marks are all compatible and have nothing to do with
Thomas Wouters7e474022000-07-16 12:04:32 +000085the actual position in the file.
Guido van Rossum4acc25b2000-02-02 15:10:15 +000086The close() method is called automatically when the class instance
87is destroyed.
88
89Writing AIFF files:
90 f = aifc.open(file, 'w')
91where file is either the name of a file or an open file pointer.
92The open file pointer must have methods write(), tell(), seek(), and
93close().
94
95This returns an instance of a class with the following public methods:
96 aiff() -- create an AIFF file (AIFF-C default)
97 aifc() -- create an AIFF-C file
98 setnchannels(n) -- set the number of channels
99 setsampwidth(n) -- set the sample width
100 setframerate(n) -- set the frame rate
101 setnframes(n) -- set the number of frames
102 setcomptype(type, name)
103 -- set the compression type and the
104 human-readable compression type
105 setparams(tuple)
106 -- set all parameters at once
107 setmark(id, pos, name)
108 -- add specified mark to the list of marks
109 tell() -- return current position in output file (useful
110 in combination with setmark())
111 writeframesraw(data)
112 -- write audio frames without pathing up the
113 file header
114 writeframes(data)
115 -- write audio frames and patch up the file header
116 close() -- patch up the file header and close the
117 output file
118You should set the parameters before the first writeframesraw or
119writeframes. The total number of frames does not need to be set,
120but when it is set to the correct value, the header does not have to
121be patched up.
122It is best to first set all parameters, perhaps possibly the
123compression type, and then write audio frames using writeframesraw.
Serhiy Storchakae0fd7ef2015-07-10 22:13:40 +0300124When all frames have been written, either call writeframes(b'') or
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000125close() to patch up the sizes in the header.
Ezio Melotti30b9d5d2013-08-17 15:50:46 +0300126Marks can be added anytime. If there are any marks, you must call
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000127close() after all frames have been written.
128The close() method is called automatically when the class instance
129is destroyed.
130
131When a file is opened with the extension '.aiff', an AIFF file is
132written, otherwise an AIFF-C file is written. This default can be
133changed by calling aiff() or aifc() before the first writeframes or
134writeframesraw.
135"""
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000136
Guido van Rossum36bb1811996-12-31 05:57:34 +0000137import struct
Georg Brandl1a3284e2007-12-02 09:40:06 +0000138import builtins
Ezio Melotti48d578c2012-03-12 23:57:18 +0200139import warnings
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000140
Georg Brandl2095cfe2008-06-07 19:01:03 +0000141__all__ = ["Error", "open", "openfp"]
Skip Montanaroe99d5ea2001-01-20 19:54:20 +0000142
Fred Drake227b1202000-08-17 05:06:49 +0000143class Error(Exception):
144 pass
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000145
Guido van Rossume2a383d2007-01-15 16:59:06 +0000146_AIFC_version = 0xA2805140 # Version 1 of AIFF-C
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000147
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000148def _read_long(file):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000149 try:
150 return struct.unpack('>l', file.read(4))[0]
151 except struct.error:
152 raise EOFError
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000153
154def _read_ulong(file):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000155 try:
156 return struct.unpack('>L', file.read(4))[0]
157 except struct.error:
158 raise EOFError
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000159
160def _read_short(file):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000161 try:
162 return struct.unpack('>h', file.read(2))[0]
163 except struct.error:
164 raise EOFError
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000165
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100166def _read_ushort(file):
167 try:
168 return struct.unpack('>H', file.read(2))[0]
169 except struct.error:
170 raise EOFError
171
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000172def _read_string(file):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000173 length = ord(file.read(1))
174 if length == 0:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000175 data = b''
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000176 else:
177 data = file.read(length)
178 if length & 1 == 0:
179 dummy = file.read(1)
180 return data
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000181
182_HUGE_VAL = 1.79769313486231e+308 # See <limits.h>
183
184def _read_float(f): # 10 bytes
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000185 expon = _read_short(f) # 2 bytes
186 sign = 1
187 if expon < 0:
188 sign = -1
189 expon = expon + 0x8000
190 himant = _read_ulong(f) # 4 bytes
191 lomant = _read_ulong(f) # 4 bytes
192 if expon == himant == lomant == 0:
193 f = 0.0
194 elif expon == 0x7FFF:
195 f = _HUGE_VAL
196 else:
197 expon = expon - 16383
Guido van Rossume2a383d2007-01-15 16:59:06 +0000198 f = (himant * 0x100000000 + lomant) * pow(2.0, expon - 63)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000199 return sign * f
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000200
201def _write_short(f, x):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000202 f.write(struct.pack('>h', x))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000203
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100204def _write_ushort(f, x):
205 f.write(struct.pack('>H', x))
206
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000207def _write_long(f, x):
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100208 f.write(struct.pack('>l', x))
209
210def _write_ulong(f, x):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000211 f.write(struct.pack('>L', x))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000212
213def _write_string(f, s):
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000214 if len(s) > 255:
215 raise ValueError("string exceeds maximum pstring length")
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100216 f.write(struct.pack('B', len(s)))
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000217 f.write(s)
218 if len(s) & 1 == 0:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000219 f.write(b'\x00')
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000220
221def _write_float(f, x):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000222 import math
223 if x < 0:
224 sign = 0x8000
225 x = x * -1
226 else:
227 sign = 0
228 if x == 0:
229 expon = 0
230 himant = 0
231 lomant = 0
232 else:
233 fmant, expon = math.frexp(x)
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100234 if expon > 16384 or fmant >= 1 or fmant != fmant: # Infinity or NaN
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000235 expon = sign|0x7FFF
236 himant = 0
237 lomant = 0
238 else: # Finite
239 expon = expon + 16382
240 if expon < 0: # denormalized
241 fmant = math.ldexp(fmant, expon)
242 expon = 0
243 expon = expon | sign
244 fmant = math.ldexp(fmant, 32)
245 fsmant = math.floor(fmant)
Guido van Rossume2a383d2007-01-15 16:59:06 +0000246 himant = int(fsmant)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000247 fmant = math.ldexp(fmant - fsmant, 32)
248 fsmant = math.floor(fmant)
Guido van Rossume2a383d2007-01-15 16:59:06 +0000249 lomant = int(fsmant)
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100250 _write_ushort(f, expon)
251 _write_ulong(f, himant)
252 _write_ulong(f, lomant)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000253
Guido van Rossum8ea7bb81999-06-09 13:32:28 +0000254from chunk import Chunk
R David Murray4d35e752013-07-25 16:12:01 -0400255from collections import namedtuple
256
257_aifc_params = namedtuple('_aifc_params',
258 'nchannels sampwidth framerate nframes comptype compname')
259
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000260
Guido van Rossumd3166071993-05-24 14:16:22 +0000261class Aifc_read:
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000262 # Variables used in this class:
263 #
264 # These variables are available to the user though appropriate
265 # methods of this class:
266 # _file -- the open file with methods read(), close(), and seek()
267 # set through the __init__() method
268 # _nchannels -- the number of audio channels
269 # available through the getnchannels() method
270 # _nframes -- the number of audio frames
271 # available through the getnframes() method
272 # _sampwidth -- the number of bytes per audio sample
273 # available through the getsampwidth() method
274 # _framerate -- the sampling frequency
275 # available through the getframerate() method
276 # _comptype -- the AIFF-C compression type ('NONE' if AIFF)
277 # available through the getcomptype() method
278 # _compname -- the human-readable AIFF-C compression type
279 # available through the getcomptype() method
280 # _markers -- the marks in the audio file
281 # available through the getmarkers() and getmark()
282 # methods
283 # _soundpos -- the position in the audio stream
284 # available through the tell() method, set through the
285 # setpos() method
286 #
287 # These variables are used internally only:
288 # _version -- the AIFF-C version number
289 # _decomp -- the decompressor from builtin module cl
290 # _comm_chunk_read -- 1 iff the COMM chunk has been read
291 # _aifc -- 1 iff reading an AIFF-C file
292 # _ssnd_seek_needed -- 1 iff positioned correctly in audio
293 # file for readframes()
294 # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk
295 # _framesize -- size of one frame in the file
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000296
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000297 def initfp(self, file):
298 self._version = 0
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000299 self._convert = None
300 self._markers = []
301 self._soundpos = 0
R. David Murray99352742009-05-07 18:24:38 +0000302 self._file = file
303 chunk = Chunk(file)
304 if chunk.getname() != b'FORM':
Collin Winterce36ad82007-08-30 01:19:48 +0000305 raise Error('file does not start with FORM id')
R. David Murray99352742009-05-07 18:24:38 +0000306 formdata = chunk.read(4)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000307 if formdata == b'AIFF':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000308 self._aifc = 0
Georg Brandl2095cfe2008-06-07 19:01:03 +0000309 elif formdata == b'AIFC':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000310 self._aifc = 1
311 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000312 raise Error('not an AIFF or AIFF-C file')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000313 self._comm_chunk_read = 0
314 while 1:
315 self._ssnd_seek_needed = 1
316 try:
317 chunk = Chunk(self._file)
318 except EOFError:
319 break
320 chunkname = chunk.getname()
Georg Brandl2095cfe2008-06-07 19:01:03 +0000321 if chunkname == b'COMM':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000322 self._read_comm_chunk(chunk)
323 self._comm_chunk_read = 1
Georg Brandl2095cfe2008-06-07 19:01:03 +0000324 elif chunkname == b'SSND':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000325 self._ssnd_chunk = chunk
326 dummy = chunk.read(8)
327 self._ssnd_seek_needed = 0
Georg Brandl2095cfe2008-06-07 19:01:03 +0000328 elif chunkname == b'FVER':
Guido van Rossum820819c2002-08-12 22:11:28 +0000329 self._version = _read_ulong(chunk)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000330 elif chunkname == b'MARK':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000331 self._readmark(chunk)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000332 chunk.skip()
333 if not self._comm_chunk_read or not self._ssnd_chunk:
Collin Winterce36ad82007-08-30 01:19:48 +0000334 raise Error('COMM chunk and/or SSND chunk missing')
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000335
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000336 def __init__(self, f):
Georg Brandl2095cfe2008-06-07 19:01:03 +0000337 if isinstance(f, str):
Georg Brandl1a3284e2007-12-02 09:40:06 +0000338 f = builtins.open(f, 'rb')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000339 # else, assume it is an open file object already
340 self.initfp(f)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000341
Serhiy Storchaka44c66c72012-12-29 22:54:49 +0200342 def __enter__(self):
343 return self
344
345 def __exit__(self, *args):
346 self.close()
347
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000348 #
349 # User visible methods.
350 #
351 def getfp(self):
352 return self._file
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000353
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000354 def rewind(self):
355 self._ssnd_seek_needed = 1
356 self._soundpos = 0
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000357
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000358 def close(self):
Serhiy Storchaka7e7a3db2015-04-10 13:24:41 +0300359 file = self._file
360 if file is not None:
361 self._file = None
362 file.close()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000363
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000364 def tell(self):
365 return self._soundpos
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000366
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000367 def getnchannels(self):
368 return self._nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000369
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000370 def getnframes(self):
371 return self._nframes
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000372
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000373 def getsampwidth(self):
374 return self._sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000375
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000376 def getframerate(self):
377 return self._framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000378
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000379 def getcomptype(self):
380 return self._comptype
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000381
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000382 def getcompname(self):
383 return self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000384
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000385## def getversion(self):
386## return self._version
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000387
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000388 def getparams(self):
R David Murray4d35e752013-07-25 16:12:01 -0400389 return _aifc_params(self.getnchannels(), self.getsampwidth(),
390 self.getframerate(), self.getnframes(),
391 self.getcomptype(), self.getcompname())
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000392
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000393 def getmarkers(self):
394 if len(self._markers) == 0:
395 return None
396 return self._markers
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000397
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000398 def getmark(self, id):
399 for marker in self._markers:
400 if id == marker[0]:
401 return marker
Georg Brandl2095cfe2008-06-07 19:01:03 +0000402 raise Error('marker {0!r} does not exist'.format(id))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000403
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000404 def setpos(self, pos):
405 if pos < 0 or pos > self._nframes:
Collin Winterce36ad82007-08-30 01:19:48 +0000406 raise Error('position not in range')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000407 self._soundpos = pos
408 self._ssnd_seek_needed = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000409
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000410 def readframes(self, nframes):
411 if self._ssnd_seek_needed:
412 self._ssnd_chunk.seek(0)
413 dummy = self._ssnd_chunk.read(8)
414 pos = self._soundpos * self._framesize
415 if pos:
Guido van Rossum2663c132000-03-07 15:19:31 +0000416 self._ssnd_chunk.seek(pos + 8)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000417 self._ssnd_seek_needed = 0
418 if nframes == 0:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000419 return b''
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000420 data = self._ssnd_chunk.read(nframes * self._framesize)
421 if self._convert and data:
422 data = self._convert(data)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000423 self._soundpos = self._soundpos + len(data) // (self._nchannels
424 * self._sampwidth)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000425 return data
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000426
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000427 #
428 # Internal methods.
429 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000430
Georg Brandl2095cfe2008-06-07 19:01:03 +0000431 def _alaw2lin(self, data):
432 import audioop
433 return audioop.alaw2lin(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000434
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000435 def _ulaw2lin(self, data):
436 import audioop
437 return audioop.ulaw2lin(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000438
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000439 def _adpcm2lin(self, data):
440 import audioop
441 if not hasattr(self, '_adpcmstate'):
442 # first time
443 self._adpcmstate = None
Georg Brandl2095cfe2008-06-07 19:01:03 +0000444 data, self._adpcmstate = audioop.adpcm2lin(data, 2, self._adpcmstate)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000445 return data
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000446
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000447 def _read_comm_chunk(self, chunk):
448 self._nchannels = _read_short(chunk)
449 self._nframes = _read_long(chunk)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000450 self._sampwidth = (_read_short(chunk) + 7) // 8
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000451 self._framerate = int(_read_float(chunk))
452 self._framesize = self._nchannels * self._sampwidth
453 if self._aifc:
454 #DEBUG: SGI's soundeditor produces a bad size :-(
455 kludge = 0
456 if chunk.chunksize == 18:
457 kludge = 1
Ezio Melotti48d578c2012-03-12 23:57:18 +0200458 warnings.warn('Warning: bad COMM chunk size')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000459 chunk.chunksize = 23
460 #DEBUG end
461 self._comptype = chunk.read(4)
462 #DEBUG start
463 if kludge:
464 length = ord(chunk.file.read(1))
465 if length & 1 == 0:
466 length = length + 1
467 chunk.chunksize = chunk.chunksize + length
468 chunk.file.seek(-1, 1)
469 #DEBUG end
470 self._compname = _read_string(chunk)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000471 if self._comptype != b'NONE':
472 if self._comptype == b'G722':
473 self._convert = self._adpcm2lin
Georg Brandl2095cfe2008-06-07 19:01:03 +0000474 elif self._comptype in (b'ulaw', b'ULAW'):
475 self._convert = self._ulaw2lin
Georg Brandl2095cfe2008-06-07 19:01:03 +0000476 elif self._comptype in (b'alaw', b'ALAW'):
477 self._convert = self._alaw2lin
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000478 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000479 raise Error('unsupported compression type')
Serhiy Storchaka4b532592013-10-12 18:21:33 +0300480 self._sampwidth = 2
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000481 else:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000482 self._comptype = b'NONE'
483 self._compname = b'not compressed'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000484
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000485 def _readmark(self, chunk):
486 nmarkers = _read_short(chunk)
487 # Some files appear to contain invalid counts.
488 # Cope with this by testing for EOF.
489 try:
490 for i in range(nmarkers):
491 id = _read_short(chunk)
492 pos = _read_long(chunk)
493 name = _read_string(chunk)
494 if pos or name:
495 # some files appear to have
496 # dummy markers consisting of
497 # a position 0 and name ''
498 self._markers.append((id, pos, name))
499 except EOFError:
Ezio Melotti48d578c2012-03-12 23:57:18 +0200500 w = ('Warning: MARK chunk contains only %s marker%s instead of %s' %
501 (len(self._markers), '' if len(self._markers) == 1 else 's',
502 nmarkers))
503 warnings.warn(w)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000504
Guido van Rossumd3166071993-05-24 14:16:22 +0000505class Aifc_write:
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000506 # Variables used in this class:
507 #
508 # These variables are user settable through appropriate methods
509 # of this class:
510 # _file -- the open file with methods write(), close(), tell(), seek()
511 # set through the __init__() method
512 # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
513 # set through the setcomptype() or setparams() method
514 # _compname -- the human-readable AIFF-C compression type
515 # set through the setcomptype() or setparams() method
516 # _nchannels -- the number of audio channels
517 # set through the setnchannels() or setparams() method
518 # _sampwidth -- the number of bytes per audio sample
519 # set through the setsampwidth() or setparams() method
520 # _framerate -- the sampling frequency
521 # set through the setframerate() or setparams() method
522 # _nframes -- the number of audio frames written to the header
523 # set through the setnframes() or setparams() method
524 # _aifc -- whether we're writing an AIFF-C file or an AIFF file
525 # set through the aifc() method, reset through the
526 # aiff() method
527 #
528 # These variables are used internally only:
529 # _version -- the AIFF-C version number
530 # _comp -- the compressor from builtin module cl
531 # _nframeswritten -- the number of audio frames actually written
532 # _datalength -- the size of the audio samples written to the header
533 # _datawritten -- the size of the audio samples actually written
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000534
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000535 def __init__(self, f):
Georg Brandl2095cfe2008-06-07 19:01:03 +0000536 if isinstance(f, str):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000537 filename = f
Georg Brandl1a3284e2007-12-02 09:40:06 +0000538 f = builtins.open(f, 'wb')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000539 else:
540 # else, assume it is an open file object already
541 filename = '???'
542 self.initfp(f)
543 if filename[-5:] == '.aiff':
544 self._aifc = 0
545 else:
546 self._aifc = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000547
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000548 def initfp(self, file):
549 self._file = file
550 self._version = _AIFC_version
Georg Brandl2095cfe2008-06-07 19:01:03 +0000551 self._comptype = b'NONE'
552 self._compname = b'not compressed'
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000553 self._convert = None
554 self._nchannels = 0
555 self._sampwidth = 0
556 self._framerate = 0
557 self._nframes = 0
558 self._nframeswritten = 0
559 self._datawritten = 0
560 self._datalength = 0
561 self._markers = []
562 self._marklength = 0
563 self._aifc = 1 # AIFF-C is default
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000564
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000565 def __del__(self):
Sandro Tosi70efbef2012-01-01 22:53:08 +0100566 self.close()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000567
Serhiy Storchaka44c66c72012-12-29 22:54:49 +0200568 def __enter__(self):
569 return self
570
571 def __exit__(self, *args):
572 self.close()
573
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000574 #
575 # User visible methods.
576 #
577 def aiff(self):
578 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000579 raise Error('cannot change parameters after starting to write')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000580 self._aifc = 0
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000581
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000582 def aifc(self):
583 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000584 raise Error('cannot change parameters after starting to write')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000585 self._aifc = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000586
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000587 def setnchannels(self, nchannels):
588 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000589 raise Error('cannot change parameters after starting to write')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000590 if nchannels < 1:
Collin Winterce36ad82007-08-30 01:19:48 +0000591 raise Error('bad # of channels')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000592 self._nchannels = nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000593
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000594 def getnchannels(self):
595 if not self._nchannels:
Collin Winterce36ad82007-08-30 01:19:48 +0000596 raise Error('number of channels not set')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000597 return self._nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000598
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000599 def setsampwidth(self, sampwidth):
600 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000601 raise Error('cannot change parameters after starting to write')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000602 if sampwidth < 1 or sampwidth > 4:
Collin Winterce36ad82007-08-30 01:19:48 +0000603 raise Error('bad sample width')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000604 self._sampwidth = sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000605
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000606 def getsampwidth(self):
607 if not self._sampwidth:
Collin Winterce36ad82007-08-30 01:19:48 +0000608 raise Error('sample width not set')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000609 return self._sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000610
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000611 def setframerate(self, framerate):
612 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000613 raise Error('cannot change parameters after starting to write')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000614 if framerate <= 0:
Collin Winterce36ad82007-08-30 01:19:48 +0000615 raise Error('bad frame rate')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000616 self._framerate = framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000617
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000618 def getframerate(self):
619 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000620 raise Error('frame rate not set')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000621 return self._framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000622
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000623 def setnframes(self, nframes):
624 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000625 raise Error('cannot change parameters after starting to write')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000626 self._nframes = nframes
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000627
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000628 def getnframes(self):
629 return self._nframeswritten
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000630
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000631 def setcomptype(self, comptype, compname):
632 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000633 raise Error('cannot change parameters after starting to write')
Georg Brandl2095cfe2008-06-07 19:01:03 +0000634 if comptype not in (b'NONE', b'ulaw', b'ULAW',
635 b'alaw', b'ALAW', b'G722'):
Collin Winterce36ad82007-08-30 01:19:48 +0000636 raise Error('unsupported compression type')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000637 self._comptype = comptype
638 self._compname = compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000639
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000640 def getcomptype(self):
641 return self._comptype
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000642
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000643 def getcompname(self):
644 return self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000645
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000646## def setversion(self, version):
647## if self._nframeswritten:
648## raise Error, 'cannot change parameters after starting to write'
649## self._version = version
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000650
Guido van Rossum1bc535d2007-05-15 18:46:22 +0000651 def setparams(self, params):
652 nchannels, sampwidth, framerate, nframes, comptype, compname = params
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000653 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000654 raise Error('cannot change parameters after starting to write')
Georg Brandl2095cfe2008-06-07 19:01:03 +0000655 if comptype not in (b'NONE', b'ulaw', b'ULAW',
656 b'alaw', b'ALAW', b'G722'):
Collin Winterce36ad82007-08-30 01:19:48 +0000657 raise Error('unsupported compression type')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000658 self.setnchannels(nchannels)
659 self.setsampwidth(sampwidth)
660 self.setframerate(framerate)
661 self.setnframes(nframes)
662 self.setcomptype(comptype, compname)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000663
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000664 def getparams(self):
665 if not self._nchannels or not self._sampwidth or not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000666 raise Error('not all parameters set')
R David Murray4d35e752013-07-25 16:12:01 -0400667 return _aifc_params(self._nchannels, self._sampwidth, self._framerate,
668 self._nframes, self._comptype, self._compname)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000669
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000670 def setmark(self, id, pos, name):
671 if id <= 0:
Collin Winterce36ad82007-08-30 01:19:48 +0000672 raise Error('marker ID must be > 0')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000673 if pos < 0:
Collin Winterce36ad82007-08-30 01:19:48 +0000674 raise Error('marker position must be >= 0')
Sandro Tosi70efbef2012-01-01 22:53:08 +0100675 if not isinstance(name, bytes):
676 raise Error('marker name must be bytes')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000677 for i in range(len(self._markers)):
678 if id == self._markers[i][0]:
679 self._markers[i] = id, pos, name
680 return
681 self._markers.append((id, pos, name))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000682
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000683 def getmark(self, id):
684 for marker in self._markers:
685 if id == marker[0]:
686 return marker
Georg Brandl2095cfe2008-06-07 19:01:03 +0000687 raise Error('marker {0!r} does not exist'.format(id))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000688
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000689 def getmarkers(self):
690 if len(self._markers) == 0:
691 return None
692 return self._markers
Tim Peters146965a2001-01-14 18:09:23 +0000693
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000694 def tell(self):
695 return self._nframeswritten
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000696
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000697 def writeframesraw(self, data):
Serhiy Storchaka452bab42013-11-16 14:01:31 +0200698 if not isinstance(data, (bytes, bytearray)):
699 data = memoryview(data).cast('B')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000700 self._ensure_header_written(len(data))
Georg Brandl2095cfe2008-06-07 19:01:03 +0000701 nframes = len(data) // (self._sampwidth * self._nchannels)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000702 if self._convert:
703 data = self._convert(data)
704 self._file.write(data)
705 self._nframeswritten = self._nframeswritten + nframes
706 self._datawritten = self._datawritten + len(data)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000707
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000708 def writeframes(self, data):
709 self.writeframesraw(data)
710 if self._nframeswritten != self._nframes or \
711 self._datalength != self._datawritten:
712 self._patchheader()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000713
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000714 def close(self):
Serhiy Storchaka051722d2012-12-29 22:30:56 +0200715 if self._file is None:
716 return
717 try:
Sandro Tosi70efbef2012-01-01 22:53:08 +0100718 self._ensure_header_written(0)
719 if self._datawritten & 1:
720 # quick pad to even size
721 self._file.write(b'\x00')
722 self._datawritten = self._datawritten + 1
723 self._writemarkers()
724 if self._nframeswritten != self._nframes or \
725 self._datalength != self._datawritten or \
726 self._marklength:
727 self._patchheader()
Serhiy Storchaka051722d2012-12-29 22:30:56 +0200728 finally:
Sandro Tosi70efbef2012-01-01 22:53:08 +0100729 # Prevent ref cycles
730 self._convert = None
Serhiy Storchaka051722d2012-12-29 22:30:56 +0200731 f = self._file
Sandro Tosi70efbef2012-01-01 22:53:08 +0100732 self._file = None
Serhiy Storchaka051722d2012-12-29 22:30:56 +0200733 f.close()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000734
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000735 #
736 # Internal methods.
737 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000738
Georg Brandl2095cfe2008-06-07 19:01:03 +0000739 def _lin2alaw(self, data):
740 import audioop
741 return audioop.lin2alaw(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000742
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000743 def _lin2ulaw(self, data):
744 import audioop
745 return audioop.lin2ulaw(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000746
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000747 def _lin2adpcm(self, data):
748 import audioop
749 if not hasattr(self, '_adpcmstate'):
750 self._adpcmstate = None
Georg Brandl2095cfe2008-06-07 19:01:03 +0000751 data, self._adpcmstate = audioop.lin2adpcm(data, 2, self._adpcmstate)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000752 return data
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000753
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000754 def _ensure_header_written(self, datasize):
755 if not self._nframeswritten:
Sandro Tosibdd53542012-01-01 18:04:37 +0100756 if self._comptype in (b'ULAW', b'ulaw', b'ALAW', b'alaw', b'G722'):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000757 if not self._sampwidth:
758 self._sampwidth = 2
759 if self._sampwidth != 2:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000760 raise Error('sample width must be 2 when compressing '
Sandro Tosibdd53542012-01-01 18:04:37 +0100761 'with ulaw/ULAW, alaw/ALAW or G7.22 (ADPCM)')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000762 if not self._nchannels:
Collin Winterce36ad82007-08-30 01:19:48 +0000763 raise Error('# channels not specified')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000764 if not self._sampwidth:
Collin Winterce36ad82007-08-30 01:19:48 +0000765 raise Error('sample width not specified')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000766 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000767 raise Error('sampling rate not specified')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000768 self._write_header(datasize)
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000769
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000770 def _init_compression(self):
Georg Brandl2095cfe2008-06-07 19:01:03 +0000771 if self._comptype == b'G722':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000772 self._convert = self._lin2adpcm
Georg Brandl2095cfe2008-06-07 19:01:03 +0000773 elif self._comptype in (b'ulaw', b'ULAW'):
774 self._convert = self._lin2ulaw
775 elif self._comptype in (b'alaw', b'ALAW'):
776 self._convert = self._lin2alaw
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000777
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000778 def _write_header(self, initlength):
Georg Brandl2095cfe2008-06-07 19:01:03 +0000779 if self._aifc and self._comptype != b'NONE':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000780 self._init_compression()
Georg Brandl2095cfe2008-06-07 19:01:03 +0000781 self._file.write(b'FORM')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000782 if not self._nframes:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000783 self._nframes = initlength // (self._nchannels * self._sampwidth)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000784 self._datalength = self._nframes * self._nchannels * self._sampwidth
785 if self._datalength & 1:
786 self._datalength = self._datalength + 1
787 if self._aifc:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000788 if self._comptype in (b'ulaw', b'ULAW', b'alaw', b'ALAW'):
789 self._datalength = self._datalength // 2
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000790 if self._datalength & 1:
791 self._datalength = self._datalength + 1
Georg Brandl2095cfe2008-06-07 19:01:03 +0000792 elif self._comptype == b'G722':
793 self._datalength = (self._datalength + 3) // 4
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000794 if self._datalength & 1:
795 self._datalength = self._datalength + 1
Serhiy Storchaka84d28b42013-12-14 20:35:04 +0200796 try:
797 self._form_length_pos = self._file.tell()
798 except (AttributeError, OSError):
799 self._form_length_pos = None
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000800 commlength = self._write_form_length(self._datalength)
801 if self._aifc:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000802 self._file.write(b'AIFC')
803 self._file.write(b'FVER')
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100804 _write_ulong(self._file, 4)
805 _write_ulong(self._file, self._version)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000806 else:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000807 self._file.write(b'AIFF')
808 self._file.write(b'COMM')
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100809 _write_ulong(self._file, commlength)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000810 _write_short(self._file, self._nchannels)
Serhiy Storchaka84d28b42013-12-14 20:35:04 +0200811 if self._form_length_pos is not None:
812 self._nframes_pos = self._file.tell()
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100813 _write_ulong(self._file, self._nframes)
Serhiy Storchaka4b532592013-10-12 18:21:33 +0300814 if self._comptype in (b'ULAW', b'ulaw', b'ALAW', b'alaw', b'G722'):
815 _write_short(self._file, 8)
816 else:
817 _write_short(self._file, self._sampwidth * 8)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000818 _write_float(self._file, self._framerate)
819 if self._aifc:
820 self._file.write(self._comptype)
821 _write_string(self._file, self._compname)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000822 self._file.write(b'SSND')
Serhiy Storchaka84d28b42013-12-14 20:35:04 +0200823 if self._form_length_pos is not None:
824 self._ssnd_length_pos = self._file.tell()
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100825 _write_ulong(self._file, self._datalength + 8)
826 _write_ulong(self._file, 0)
827 _write_ulong(self._file, 0)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000828
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000829 def _write_form_length(self, datalength):
830 if self._aifc:
831 commlength = 18 + 5 + len(self._compname)
832 if commlength & 1:
833 commlength = commlength + 1
834 verslength = 12
835 else:
836 commlength = 18
837 verslength = 0
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100838 _write_ulong(self._file, 4 + verslength + self._marklength + \
839 8 + commlength + 16 + datalength)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000840 return commlength
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000841
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000842 def _patchheader(self):
843 curpos = self._file.tell()
844 if self._datawritten & 1:
845 datalength = self._datawritten + 1
Georg Brandl2095cfe2008-06-07 19:01:03 +0000846 self._file.write(b'\x00')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000847 else:
848 datalength = self._datawritten
849 if datalength == self._datalength and \
850 self._nframes == self._nframeswritten and \
851 self._marklength == 0:
852 self._file.seek(curpos, 0)
853 return
854 self._file.seek(self._form_length_pos, 0)
855 dummy = self._write_form_length(datalength)
856 self._file.seek(self._nframes_pos, 0)
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100857 _write_ulong(self._file, self._nframeswritten)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000858 self._file.seek(self._ssnd_length_pos, 0)
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100859 _write_ulong(self._file, datalength + 8)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000860 self._file.seek(curpos, 0)
861 self._nframes = self._nframeswritten
862 self._datalength = datalength
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000863
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000864 def _writemarkers(self):
865 if len(self._markers) == 0:
866 return
Georg Brandl2095cfe2008-06-07 19:01:03 +0000867 self._file.write(b'MARK')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000868 length = 2
869 for marker in self._markers:
870 id, pos, name = marker
871 length = length + len(name) + 1 + 6
872 if len(name) & 1 == 0:
873 length = length + 1
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100874 _write_ulong(self._file, length)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000875 self._marklength = length + 8
876 _write_short(self._file, len(self._markers))
877 for marker in self._markers:
878 id, pos, name = marker
879 _write_short(self._file, id)
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100880 _write_ulong(self._file, pos)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000881 _write_string(self._file, name)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000882
Fred Drake43161351999-06-22 21:23:23 +0000883def open(f, mode=None):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000884 if mode is None:
885 if hasattr(f, 'mode'):
886 mode = f.mode
887 else:
888 mode = 'rb'
889 if mode in ('r', 'rb'):
890 return Aifc_read(f)
891 elif mode in ('w', 'wb'):
892 return Aifc_write(f)
893 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000894 raise Error("mode must be 'r', 'rb', 'w', or 'wb'")
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000895
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000896openfp = open # B/W compatibility
Guido van Rossum36bb1811996-12-31 05:57:34 +0000897
898if __name__ == '__main__':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000899 import sys
900 if not sys.argv[1:]:
901 sys.argv.append('/usr/demos/data/audio/bach.aiff')
902 fn = sys.argv[1]
Serhiy Storchaka58b3ebf2013-08-25 19:16:01 +0300903 with open(fn, 'r') as f:
Serhiy Storchakab33baf12013-08-25 19:12:56 +0300904 print("Reading", fn)
905 print("nchannels =", f.getnchannels())
906 print("nframes =", f.getnframes())
907 print("sampwidth =", f.getsampwidth())
908 print("framerate =", f.getframerate())
909 print("comptype =", f.getcomptype())
910 print("compname =", f.getcompname())
911 if sys.argv[2:]:
912 gn = sys.argv[2]
913 print("Writing", gn)
Serhiy Storchaka58b3ebf2013-08-25 19:16:01 +0300914 with open(gn, 'w') as g:
Serhiy Storchakab33baf12013-08-25 19:12:56 +0300915 g.setparams(f.getparams())
916 while 1:
917 data = f.readframes(1024)
918 if not data:
919 break
920 g.writeframes(data)
Serhiy Storchakab33baf12013-08-25 19:12:56 +0300921 print("Done.")