blob: db0924a2b7f18e31dae8cce632022cc18995c536 [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)
72 getparams() -- returns a tuple consisting of all of the
73 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.
124When all frames have been written, either call writeframes('') or
125close() 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
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000255
Guido van Rossumd3166071993-05-24 14:16:22 +0000256class Aifc_read:
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000257 # Variables used in this class:
258 #
259 # These variables are available to the user though appropriate
260 # methods of this class:
261 # _file -- the open file with methods read(), close(), and seek()
262 # set through the __init__() method
263 # _nchannels -- the number of audio channels
264 # available through the getnchannels() method
265 # _nframes -- the number of audio frames
266 # available through the getnframes() method
267 # _sampwidth -- the number of bytes per audio sample
268 # available through the getsampwidth() method
269 # _framerate -- the sampling frequency
270 # available through the getframerate() method
271 # _comptype -- the AIFF-C compression type ('NONE' if AIFF)
272 # available through the getcomptype() method
273 # _compname -- the human-readable AIFF-C compression type
274 # available through the getcomptype() method
275 # _markers -- the marks in the audio file
276 # available through the getmarkers() and getmark()
277 # methods
278 # _soundpos -- the position in the audio stream
279 # available through the tell() method, set through the
280 # setpos() method
281 #
282 # These variables are used internally only:
283 # _version -- the AIFF-C version number
284 # _decomp -- the decompressor from builtin module cl
285 # _comm_chunk_read -- 1 iff the COMM chunk has been read
286 # _aifc -- 1 iff reading an AIFF-C file
287 # _ssnd_seek_needed -- 1 iff positioned correctly in audio
288 # file for readframes()
289 # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk
290 # _framesize -- size of one frame in the file
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000291
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000292 def initfp(self, file):
293 self._version = 0
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000294 self._convert = None
295 self._markers = []
296 self._soundpos = 0
R. David Murray99352742009-05-07 18:24:38 +0000297 self._file = file
298 chunk = Chunk(file)
299 if chunk.getname() != b'FORM':
Collin Winterce36ad82007-08-30 01:19:48 +0000300 raise Error('file does not start with FORM id')
R. David Murray99352742009-05-07 18:24:38 +0000301 formdata = chunk.read(4)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000302 if formdata == b'AIFF':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000303 self._aifc = 0
Georg Brandl2095cfe2008-06-07 19:01:03 +0000304 elif formdata == b'AIFC':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000305 self._aifc = 1
306 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000307 raise Error('not an AIFF or AIFF-C file')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000308 self._comm_chunk_read = 0
309 while 1:
310 self._ssnd_seek_needed = 1
311 try:
312 chunk = Chunk(self._file)
313 except EOFError:
314 break
315 chunkname = chunk.getname()
Georg Brandl2095cfe2008-06-07 19:01:03 +0000316 if chunkname == b'COMM':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000317 self._read_comm_chunk(chunk)
318 self._comm_chunk_read = 1
Georg Brandl2095cfe2008-06-07 19:01:03 +0000319 elif chunkname == b'SSND':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000320 self._ssnd_chunk = chunk
321 dummy = chunk.read(8)
322 self._ssnd_seek_needed = 0
Georg Brandl2095cfe2008-06-07 19:01:03 +0000323 elif chunkname == b'FVER':
Guido van Rossum820819c2002-08-12 22:11:28 +0000324 self._version = _read_ulong(chunk)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000325 elif chunkname == b'MARK':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000326 self._readmark(chunk)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000327 chunk.skip()
328 if not self._comm_chunk_read or not self._ssnd_chunk:
Collin Winterce36ad82007-08-30 01:19:48 +0000329 raise Error('COMM chunk and/or SSND chunk missing')
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000330
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000331 def __init__(self, f):
Georg Brandl2095cfe2008-06-07 19:01:03 +0000332 if isinstance(f, str):
Georg Brandl1a3284e2007-12-02 09:40:06 +0000333 f = builtins.open(f, 'rb')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000334 # else, assume it is an open file object already
335 self.initfp(f)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000336
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000337 #
338 # User visible methods.
339 #
340 def getfp(self):
341 return self._file
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000342
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000343 def rewind(self):
344 self._ssnd_seek_needed = 1
345 self._soundpos = 0
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000346
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000347 def close(self):
Benjamin Peterson1d1285d2009-05-07 11:53:38 +0000348 self._file.close()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000349
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000350 def tell(self):
351 return self._soundpos
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000352
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000353 def getnchannels(self):
354 return self._nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000355
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000356 def getnframes(self):
357 return self._nframes
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000358
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000359 def getsampwidth(self):
360 return self._sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000361
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000362 def getframerate(self):
363 return self._framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000364
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000365 def getcomptype(self):
366 return self._comptype
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000367
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000368 def getcompname(self):
369 return self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000370
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000371## def getversion(self):
372## return self._version
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000373
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000374 def getparams(self):
375 return self.getnchannels(), self.getsampwidth(), \
376 self.getframerate(), self.getnframes(), \
377 self.getcomptype(), self.getcompname()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000378
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000379 def getmarkers(self):
380 if len(self._markers) == 0:
381 return None
382 return self._markers
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000383
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000384 def getmark(self, id):
385 for marker in self._markers:
386 if id == marker[0]:
387 return marker
Georg Brandl2095cfe2008-06-07 19:01:03 +0000388 raise Error('marker {0!r} does not exist'.format(id))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000389
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000390 def setpos(self, pos):
391 if pos < 0 or pos > self._nframes:
Collin Winterce36ad82007-08-30 01:19:48 +0000392 raise Error('position not in range')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000393 self._soundpos = pos
394 self._ssnd_seek_needed = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000395
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000396 def readframes(self, nframes):
397 if self._ssnd_seek_needed:
398 self._ssnd_chunk.seek(0)
399 dummy = self._ssnd_chunk.read(8)
400 pos = self._soundpos * self._framesize
401 if pos:
Guido van Rossum2663c132000-03-07 15:19:31 +0000402 self._ssnd_chunk.seek(pos + 8)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000403 self._ssnd_seek_needed = 0
404 if nframes == 0:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000405 return b''
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000406 data = self._ssnd_chunk.read(nframes * self._framesize)
407 if self._convert and data:
408 data = self._convert(data)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000409 self._soundpos = self._soundpos + len(data) // (self._nchannels
410 * self._sampwidth)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000411 return data
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000412
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000413 #
414 # Internal methods.
415 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000416
Georg Brandl2095cfe2008-06-07 19:01:03 +0000417 def _alaw2lin(self, data):
418 import audioop
419 return audioop.alaw2lin(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000420
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000421 def _ulaw2lin(self, data):
422 import audioop
423 return audioop.ulaw2lin(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000424
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000425 def _adpcm2lin(self, data):
426 import audioop
427 if not hasattr(self, '_adpcmstate'):
428 # first time
429 self._adpcmstate = None
Georg Brandl2095cfe2008-06-07 19:01:03 +0000430 data, self._adpcmstate = audioop.adpcm2lin(data, 2, self._adpcmstate)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000431 return data
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000432
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000433 def _read_comm_chunk(self, chunk):
434 self._nchannels = _read_short(chunk)
435 self._nframes = _read_long(chunk)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000436 self._sampwidth = (_read_short(chunk) + 7) // 8
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000437 self._framerate = int(_read_float(chunk))
438 self._framesize = self._nchannels * self._sampwidth
439 if self._aifc:
440 #DEBUG: SGI's soundeditor produces a bad size :-(
441 kludge = 0
442 if chunk.chunksize == 18:
443 kludge = 1
Ezio Melotti48d578c2012-03-12 23:57:18 +0200444 warnings.warn('Warning: bad COMM chunk size')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000445 chunk.chunksize = 23
446 #DEBUG end
447 self._comptype = chunk.read(4)
448 #DEBUG start
449 if kludge:
450 length = ord(chunk.file.read(1))
451 if length & 1 == 0:
452 length = length + 1
453 chunk.chunksize = chunk.chunksize + length
454 chunk.file.seek(-1, 1)
455 #DEBUG end
456 self._compname = _read_string(chunk)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000457 if self._comptype != b'NONE':
458 if self._comptype == b'G722':
459 self._convert = self._adpcm2lin
Georg Brandl2095cfe2008-06-07 19:01:03 +0000460 elif self._comptype in (b'ulaw', b'ULAW'):
461 self._convert = self._ulaw2lin
Georg Brandl2095cfe2008-06-07 19:01:03 +0000462 elif self._comptype in (b'alaw', b'ALAW'):
463 self._convert = self._alaw2lin
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000464 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000465 raise Error('unsupported compression type')
Serhiy Storchaka4b532592013-10-12 18:21:33 +0300466 self._sampwidth = 2
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000467 else:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000468 self._comptype = b'NONE'
469 self._compname = b'not compressed'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000470
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000471 def _readmark(self, chunk):
472 nmarkers = _read_short(chunk)
473 # Some files appear to contain invalid counts.
474 # Cope with this by testing for EOF.
475 try:
476 for i in range(nmarkers):
477 id = _read_short(chunk)
478 pos = _read_long(chunk)
479 name = _read_string(chunk)
480 if pos or name:
481 # some files appear to have
482 # dummy markers consisting of
483 # a position 0 and name ''
484 self._markers.append((id, pos, name))
485 except EOFError:
Ezio Melotti48d578c2012-03-12 23:57:18 +0200486 w = ('Warning: MARK chunk contains only %s marker%s instead of %s' %
487 (len(self._markers), '' if len(self._markers) == 1 else 's',
488 nmarkers))
489 warnings.warn(w)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000490
Guido van Rossumd3166071993-05-24 14:16:22 +0000491class Aifc_write:
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000492 # Variables used in this class:
493 #
494 # These variables are user settable through appropriate methods
495 # of this class:
496 # _file -- the open file with methods write(), close(), tell(), seek()
497 # set through the __init__() method
498 # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
499 # set through the setcomptype() or setparams() method
500 # _compname -- the human-readable AIFF-C compression type
501 # set through the setcomptype() or setparams() method
502 # _nchannels -- the number of audio channels
503 # set through the setnchannels() or setparams() method
504 # _sampwidth -- the number of bytes per audio sample
505 # set through the setsampwidth() or setparams() method
506 # _framerate -- the sampling frequency
507 # set through the setframerate() or setparams() method
508 # _nframes -- the number of audio frames written to the header
509 # set through the setnframes() or setparams() method
510 # _aifc -- whether we're writing an AIFF-C file or an AIFF file
511 # set through the aifc() method, reset through the
512 # aiff() method
513 #
514 # These variables are used internally only:
515 # _version -- the AIFF-C version number
516 # _comp -- the compressor from builtin module cl
517 # _nframeswritten -- the number of audio frames actually written
518 # _datalength -- the size of the audio samples written to the header
519 # _datawritten -- the size of the audio samples actually written
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000520
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000521 def __init__(self, f):
Georg Brandl2095cfe2008-06-07 19:01:03 +0000522 if isinstance(f, str):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000523 filename = f
Georg Brandl1a3284e2007-12-02 09:40:06 +0000524 f = builtins.open(f, 'wb')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000525 else:
526 # else, assume it is an open file object already
527 filename = '???'
528 self.initfp(f)
529 if filename[-5:] == '.aiff':
530 self._aifc = 0
531 else:
532 self._aifc = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000533
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000534 def initfp(self, file):
535 self._file = file
536 self._version = _AIFC_version
Georg Brandl2095cfe2008-06-07 19:01:03 +0000537 self._comptype = b'NONE'
538 self._compname = b'not compressed'
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000539 self._convert = None
540 self._nchannels = 0
541 self._sampwidth = 0
542 self._framerate = 0
543 self._nframes = 0
544 self._nframeswritten = 0
545 self._datawritten = 0
546 self._datalength = 0
547 self._markers = []
548 self._marklength = 0
549 self._aifc = 1 # AIFF-C is default
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000550
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000551 def __del__(self):
Sandro Tosi70efbef2012-01-01 22:53:08 +0100552 self.close()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000553
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000554 #
555 # User visible methods.
556 #
557 def aiff(self):
558 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000559 raise Error('cannot change parameters after starting to write')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000560 self._aifc = 0
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000561
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000562 def aifc(self):
563 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000564 raise Error('cannot change parameters after starting to write')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000565 self._aifc = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000566
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000567 def setnchannels(self, nchannels):
568 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000569 raise Error('cannot change parameters after starting to write')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000570 if nchannels < 1:
Collin Winterce36ad82007-08-30 01:19:48 +0000571 raise Error('bad # of channels')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000572 self._nchannels = nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000573
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000574 def getnchannels(self):
575 if not self._nchannels:
Collin Winterce36ad82007-08-30 01:19:48 +0000576 raise Error('number of channels not set')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000577 return self._nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000578
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000579 def setsampwidth(self, sampwidth):
580 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000581 raise Error('cannot change parameters after starting to write')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000582 if sampwidth < 1 or sampwidth > 4:
Collin Winterce36ad82007-08-30 01:19:48 +0000583 raise Error('bad sample width')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000584 self._sampwidth = sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000585
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000586 def getsampwidth(self):
587 if not self._sampwidth:
Collin Winterce36ad82007-08-30 01:19:48 +0000588 raise Error('sample width not set')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000589 return self._sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000590
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000591 def setframerate(self, framerate):
592 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000593 raise Error('cannot change parameters after starting to write')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000594 if framerate <= 0:
Collin Winterce36ad82007-08-30 01:19:48 +0000595 raise Error('bad frame rate')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000596 self._framerate = framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000597
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000598 def getframerate(self):
599 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000600 raise Error('frame rate not set')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000601 return self._framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000602
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000603 def setnframes(self, nframes):
604 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000605 raise Error('cannot change parameters after starting to write')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000606 self._nframes = nframes
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000607
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000608 def getnframes(self):
609 return self._nframeswritten
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000610
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000611 def setcomptype(self, comptype, compname):
612 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000613 raise Error('cannot change parameters after starting to write')
Georg Brandl2095cfe2008-06-07 19:01:03 +0000614 if comptype not in (b'NONE', b'ulaw', b'ULAW',
615 b'alaw', b'ALAW', b'G722'):
Collin Winterce36ad82007-08-30 01:19:48 +0000616 raise Error('unsupported compression type')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000617 self._comptype = comptype
618 self._compname = compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000619
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000620 def getcomptype(self):
621 return self._comptype
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000622
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000623 def getcompname(self):
624 return self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000625
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000626## def setversion(self, version):
627## if self._nframeswritten:
628## raise Error, 'cannot change parameters after starting to write'
629## self._version = version
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000630
Guido van Rossum1bc535d2007-05-15 18:46:22 +0000631 def setparams(self, params):
632 nchannels, sampwidth, framerate, nframes, comptype, compname = params
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000633 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000634 raise Error('cannot change parameters after starting to write')
Georg Brandl2095cfe2008-06-07 19:01:03 +0000635 if comptype not in (b'NONE', b'ulaw', b'ULAW',
636 b'alaw', b'ALAW', b'G722'):
Collin Winterce36ad82007-08-30 01:19:48 +0000637 raise Error('unsupported compression type')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000638 self.setnchannels(nchannels)
639 self.setsampwidth(sampwidth)
640 self.setframerate(framerate)
641 self.setnframes(nframes)
642 self.setcomptype(comptype, compname)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000643
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000644 def getparams(self):
645 if not self._nchannels or not self._sampwidth or not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000646 raise Error('not all parameters set')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000647 return self._nchannels, self._sampwidth, self._framerate, \
648 self._nframes, self._comptype, self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000649
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000650 def setmark(self, id, pos, name):
651 if id <= 0:
Collin Winterce36ad82007-08-30 01:19:48 +0000652 raise Error('marker ID must be > 0')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000653 if pos < 0:
Collin Winterce36ad82007-08-30 01:19:48 +0000654 raise Error('marker position must be >= 0')
Sandro Tosi70efbef2012-01-01 22:53:08 +0100655 if not isinstance(name, bytes):
656 raise Error('marker name must be bytes')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000657 for i in range(len(self._markers)):
658 if id == self._markers[i][0]:
659 self._markers[i] = id, pos, name
660 return
661 self._markers.append((id, pos, name))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000662
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000663 def getmark(self, id):
664 for marker in self._markers:
665 if id == marker[0]:
666 return marker
Georg Brandl2095cfe2008-06-07 19:01:03 +0000667 raise Error('marker {0!r} does not exist'.format(id))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000668
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000669 def getmarkers(self):
670 if len(self._markers) == 0:
671 return None
672 return self._markers
Tim Peters146965a2001-01-14 18:09:23 +0000673
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000674 def tell(self):
675 return self._nframeswritten
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000676
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000677 def writeframesraw(self, data):
678 self._ensure_header_written(len(data))
Georg Brandl2095cfe2008-06-07 19:01:03 +0000679 nframes = len(data) // (self._sampwidth * self._nchannels)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000680 if self._convert:
681 data = self._convert(data)
682 self._file.write(data)
683 self._nframeswritten = self._nframeswritten + nframes
684 self._datawritten = self._datawritten + len(data)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000685
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000686 def writeframes(self, data):
687 self.writeframesraw(data)
688 if self._nframeswritten != self._nframes or \
689 self._datalength != self._datawritten:
690 self._patchheader()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000691
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000692 def close(self):
Serhiy Storchaka051722d2012-12-29 22:30:56 +0200693 if self._file is None:
694 return
695 try:
Sandro Tosi70efbef2012-01-01 22:53:08 +0100696 self._ensure_header_written(0)
697 if self._datawritten & 1:
698 # quick pad to even size
699 self._file.write(b'\x00')
700 self._datawritten = self._datawritten + 1
701 self._writemarkers()
702 if self._nframeswritten != self._nframes or \
703 self._datalength != self._datawritten or \
704 self._marklength:
705 self._patchheader()
Serhiy Storchaka051722d2012-12-29 22:30:56 +0200706 finally:
Sandro Tosi70efbef2012-01-01 22:53:08 +0100707 # Prevent ref cycles
708 self._convert = None
Serhiy Storchaka051722d2012-12-29 22:30:56 +0200709 f = self._file
Sandro Tosi70efbef2012-01-01 22:53:08 +0100710 self._file = None
Serhiy Storchaka051722d2012-12-29 22:30:56 +0200711 f.close()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000712
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000713 #
714 # Internal methods.
715 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000716
Georg Brandl2095cfe2008-06-07 19:01:03 +0000717 def _lin2alaw(self, data):
718 import audioop
719 return audioop.lin2alaw(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000720
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000721 def _lin2ulaw(self, data):
722 import audioop
723 return audioop.lin2ulaw(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000724
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000725 def _lin2adpcm(self, data):
726 import audioop
727 if not hasattr(self, '_adpcmstate'):
728 self._adpcmstate = None
Georg Brandl2095cfe2008-06-07 19:01:03 +0000729 data, self._adpcmstate = audioop.lin2adpcm(data, 2, self._adpcmstate)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000730 return data
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000731
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000732 def _ensure_header_written(self, datasize):
733 if not self._nframeswritten:
Sandro Tosibdd53542012-01-01 18:04:37 +0100734 if self._comptype in (b'ULAW', b'ulaw', b'ALAW', b'alaw', b'G722'):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000735 if not self._sampwidth:
736 self._sampwidth = 2
737 if self._sampwidth != 2:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000738 raise Error('sample width must be 2 when compressing '
Sandro Tosibdd53542012-01-01 18:04:37 +0100739 'with ulaw/ULAW, alaw/ALAW or G7.22 (ADPCM)')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000740 if not self._nchannels:
Collin Winterce36ad82007-08-30 01:19:48 +0000741 raise Error('# channels not specified')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000742 if not self._sampwidth:
Collin Winterce36ad82007-08-30 01:19:48 +0000743 raise Error('sample width not specified')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000744 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000745 raise Error('sampling rate not specified')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000746 self._write_header(datasize)
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000747
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000748 def _init_compression(self):
Georg Brandl2095cfe2008-06-07 19:01:03 +0000749 if self._comptype == b'G722':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000750 self._convert = self._lin2adpcm
Georg Brandl2095cfe2008-06-07 19:01:03 +0000751 elif self._comptype in (b'ulaw', b'ULAW'):
752 self._convert = self._lin2ulaw
753 elif self._comptype in (b'alaw', b'ALAW'):
754 self._convert = self._lin2alaw
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000755
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000756 def _write_header(self, initlength):
Georg Brandl2095cfe2008-06-07 19:01:03 +0000757 if self._aifc and self._comptype != b'NONE':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000758 self._init_compression()
Georg Brandl2095cfe2008-06-07 19:01:03 +0000759 self._file.write(b'FORM')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000760 if not self._nframes:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000761 self._nframes = initlength // (self._nchannels * self._sampwidth)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000762 self._datalength = self._nframes * self._nchannels * self._sampwidth
763 if self._datalength & 1:
764 self._datalength = self._datalength + 1
765 if self._aifc:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000766 if self._comptype in (b'ulaw', b'ULAW', b'alaw', b'ALAW'):
767 self._datalength = self._datalength // 2
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000768 if self._datalength & 1:
769 self._datalength = self._datalength + 1
Georg Brandl2095cfe2008-06-07 19:01:03 +0000770 elif self._comptype == b'G722':
771 self._datalength = (self._datalength + 3) // 4
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000772 if self._datalength & 1:
773 self._datalength = self._datalength + 1
Serhiy Storchaka84d28b42013-12-14 20:35:04 +0200774 try:
775 self._form_length_pos = self._file.tell()
776 except (AttributeError, OSError):
777 self._form_length_pos = None
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000778 commlength = self._write_form_length(self._datalength)
779 if self._aifc:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000780 self._file.write(b'AIFC')
781 self._file.write(b'FVER')
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100782 _write_ulong(self._file, 4)
783 _write_ulong(self._file, self._version)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000784 else:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000785 self._file.write(b'AIFF')
786 self._file.write(b'COMM')
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100787 _write_ulong(self._file, commlength)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000788 _write_short(self._file, self._nchannels)
Serhiy Storchaka84d28b42013-12-14 20:35:04 +0200789 if self._form_length_pos is not None:
790 self._nframes_pos = self._file.tell()
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100791 _write_ulong(self._file, self._nframes)
Serhiy Storchaka4b532592013-10-12 18:21:33 +0300792 if self._comptype in (b'ULAW', b'ulaw', b'ALAW', b'alaw', b'G722'):
793 _write_short(self._file, 8)
794 else:
795 _write_short(self._file, self._sampwidth * 8)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000796 _write_float(self._file, self._framerate)
797 if self._aifc:
798 self._file.write(self._comptype)
799 _write_string(self._file, self._compname)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000800 self._file.write(b'SSND')
Serhiy Storchaka84d28b42013-12-14 20:35:04 +0200801 if self._form_length_pos is not None:
802 self._ssnd_length_pos = self._file.tell()
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100803 _write_ulong(self._file, self._datalength + 8)
804 _write_ulong(self._file, 0)
805 _write_ulong(self._file, 0)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000806
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000807 def _write_form_length(self, datalength):
808 if self._aifc:
809 commlength = 18 + 5 + len(self._compname)
810 if commlength & 1:
811 commlength = commlength + 1
812 verslength = 12
813 else:
814 commlength = 18
815 verslength = 0
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100816 _write_ulong(self._file, 4 + verslength + self._marklength + \
817 8 + commlength + 16 + datalength)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000818 return commlength
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000819
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000820 def _patchheader(self):
821 curpos = self._file.tell()
822 if self._datawritten & 1:
823 datalength = self._datawritten + 1
Georg Brandl2095cfe2008-06-07 19:01:03 +0000824 self._file.write(b'\x00')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000825 else:
826 datalength = self._datawritten
827 if datalength == self._datalength and \
828 self._nframes == self._nframeswritten and \
829 self._marklength == 0:
830 self._file.seek(curpos, 0)
831 return
832 self._file.seek(self._form_length_pos, 0)
833 dummy = self._write_form_length(datalength)
834 self._file.seek(self._nframes_pos, 0)
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100835 _write_ulong(self._file, self._nframeswritten)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000836 self._file.seek(self._ssnd_length_pos, 0)
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100837 _write_ulong(self._file, datalength + 8)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000838 self._file.seek(curpos, 0)
839 self._nframes = self._nframeswritten
840 self._datalength = datalength
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000841
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000842 def _writemarkers(self):
843 if len(self._markers) == 0:
844 return
Georg Brandl2095cfe2008-06-07 19:01:03 +0000845 self._file.write(b'MARK')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000846 length = 2
847 for marker in self._markers:
848 id, pos, name = marker
849 length = length + len(name) + 1 + 6
850 if len(name) & 1 == 0:
851 length = length + 1
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100852 _write_ulong(self._file, length)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000853 self._marklength = length + 8
854 _write_short(self._file, len(self._markers))
855 for marker in self._markers:
856 id, pos, name = marker
857 _write_short(self._file, id)
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100858 _write_ulong(self._file, pos)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000859 _write_string(self._file, name)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000860
Fred Drake43161351999-06-22 21:23:23 +0000861def open(f, mode=None):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000862 if mode is None:
863 if hasattr(f, 'mode'):
864 mode = f.mode
865 else:
866 mode = 'rb'
867 if mode in ('r', 'rb'):
868 return Aifc_read(f)
869 elif mode in ('w', 'wb'):
870 return Aifc_write(f)
871 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000872 raise Error("mode must be 'r', 'rb', 'w', or 'wb'")
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000873
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000874openfp = open # B/W compatibility
Guido van Rossum36bb1811996-12-31 05:57:34 +0000875
876if __name__ == '__main__':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000877 import sys
878 if not sys.argv[1:]:
879 sys.argv.append('/usr/demos/data/audio/bach.aiff')
880 fn = sys.argv[1]
881 f = open(fn, 'r')
Serhiy Storchakab33baf12013-08-25 19:12:56 +0300882 try:
883 print("Reading", fn)
884 print("nchannels =", f.getnchannels())
885 print("nframes =", f.getnframes())
886 print("sampwidth =", f.getsampwidth())
887 print("framerate =", f.getframerate())
888 print("comptype =", f.getcomptype())
889 print("compname =", f.getcompname())
890 if sys.argv[2:]:
891 gn = sys.argv[2]
892 print("Writing", gn)
893 g = open(gn, 'w')
894 try:
895 g.setparams(f.getparams())
896 while 1:
897 data = f.readframes(1024)
898 if not data:
899 break
900 g.writeframes(data)
901 finally:
902 g.close()
903 print("Done.")
904 finally:
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000905 f.close()