blob: 692d0bfd272bf0edc51e442f9118b542719fc1f3 [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
Raymond Hettinger5b798ab2015-08-17 22:04:45 -0700260_aifc_params.nchannels.__doc__ = 'Number of audio channels (1 for mono, 2 for stereo)'
Raymond Hettinger4e707722015-08-23 11:28:01 -0700261_aifc_params.sampwidth.__doc__ = 'Sample width in bytes'
Raymond Hettinger5b798ab2015-08-17 22:04:45 -0700262_aifc_params.framerate.__doc__ = 'Sampling frequency'
263_aifc_params.nframes.__doc__ = 'Number of audio frames'
264_aifc_params.comptype.__doc__ = 'Compression type ("NONE" for AIFF files)'
Raymond Hettinger4e707722015-08-23 11:28:01 -0700265_aifc_params.compname.__doc__ = ("""\
266A human-readable version of the compression type
267('not compressed' for AIFF files)""")
Raymond Hettinger5b798ab2015-08-17 22:04:45 -0700268
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000269
Guido van Rossumd3166071993-05-24 14:16:22 +0000270class Aifc_read:
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000271 # Variables used in this class:
272 #
273 # These variables are available to the user though appropriate
274 # methods of this class:
275 # _file -- the open file with methods read(), close(), and seek()
276 # set through the __init__() method
277 # _nchannels -- the number of audio channels
278 # available through the getnchannels() method
279 # _nframes -- the number of audio frames
280 # available through the getnframes() method
281 # _sampwidth -- the number of bytes per audio sample
282 # available through the getsampwidth() method
283 # _framerate -- the sampling frequency
284 # available through the getframerate() method
285 # _comptype -- the AIFF-C compression type ('NONE' if AIFF)
286 # available through the getcomptype() method
287 # _compname -- the human-readable AIFF-C compression type
288 # available through the getcomptype() method
289 # _markers -- the marks in the audio file
290 # available through the getmarkers() and getmark()
291 # methods
292 # _soundpos -- the position in the audio stream
293 # available through the tell() method, set through the
294 # setpos() method
295 #
296 # These variables are used internally only:
297 # _version -- the AIFF-C version number
298 # _decomp -- the decompressor from builtin module cl
299 # _comm_chunk_read -- 1 iff the COMM chunk has been read
300 # _aifc -- 1 iff reading an AIFF-C file
301 # _ssnd_seek_needed -- 1 iff positioned correctly in audio
302 # file for readframes()
303 # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk
304 # _framesize -- size of one frame in the file
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000305
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000306 def initfp(self, file):
307 self._version = 0
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000308 self._convert = None
309 self._markers = []
310 self._soundpos = 0
R. David Murray99352742009-05-07 18:24:38 +0000311 self._file = file
312 chunk = Chunk(file)
313 if chunk.getname() != b'FORM':
Collin Winterce36ad82007-08-30 01:19:48 +0000314 raise Error('file does not start with FORM id')
R. David Murray99352742009-05-07 18:24:38 +0000315 formdata = chunk.read(4)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000316 if formdata == b'AIFF':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000317 self._aifc = 0
Georg Brandl2095cfe2008-06-07 19:01:03 +0000318 elif formdata == b'AIFC':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000319 self._aifc = 1
320 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000321 raise Error('not an AIFF or AIFF-C file')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000322 self._comm_chunk_read = 0
323 while 1:
324 self._ssnd_seek_needed = 1
325 try:
326 chunk = Chunk(self._file)
327 except EOFError:
328 break
329 chunkname = chunk.getname()
Georg Brandl2095cfe2008-06-07 19:01:03 +0000330 if chunkname == b'COMM':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000331 self._read_comm_chunk(chunk)
332 self._comm_chunk_read = 1
Georg Brandl2095cfe2008-06-07 19:01:03 +0000333 elif chunkname == b'SSND':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000334 self._ssnd_chunk = chunk
335 dummy = chunk.read(8)
336 self._ssnd_seek_needed = 0
Georg Brandl2095cfe2008-06-07 19:01:03 +0000337 elif chunkname == b'FVER':
Guido van Rossum820819c2002-08-12 22:11:28 +0000338 self._version = _read_ulong(chunk)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000339 elif chunkname == b'MARK':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000340 self._readmark(chunk)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000341 chunk.skip()
342 if not self._comm_chunk_read or not self._ssnd_chunk:
Collin Winterce36ad82007-08-30 01:19:48 +0000343 raise Error('COMM chunk and/or SSND chunk missing')
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000344
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000345 def __init__(self, f):
Georg Brandl2095cfe2008-06-07 19:01:03 +0000346 if isinstance(f, str):
Georg Brandl1a3284e2007-12-02 09:40:06 +0000347 f = builtins.open(f, 'rb')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000348 # else, assume it is an open file object already
349 self.initfp(f)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000350
Serhiy Storchaka44c66c72012-12-29 22:54:49 +0200351 def __enter__(self):
352 return self
353
354 def __exit__(self, *args):
355 self.close()
356
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000357 #
358 # User visible methods.
359 #
360 def getfp(self):
361 return self._file
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000362
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000363 def rewind(self):
364 self._ssnd_seek_needed = 1
365 self._soundpos = 0
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000366
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000367 def close(self):
Serhiy Storchaka7e7a3db2015-04-10 13:24:41 +0300368 file = self._file
369 if file is not None:
370 self._file = None
371 file.close()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000372
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000373 def tell(self):
374 return self._soundpos
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000375
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000376 def getnchannels(self):
377 return self._nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000378
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000379 def getnframes(self):
380 return self._nframes
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000381
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000382 def getsampwidth(self):
383 return self._sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000384
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000385 def getframerate(self):
386 return self._framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000387
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000388 def getcomptype(self):
389 return self._comptype
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000390
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000391 def getcompname(self):
392 return self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000393
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000394## def getversion(self):
395## return self._version
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000396
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000397 def getparams(self):
R David Murray4d35e752013-07-25 16:12:01 -0400398 return _aifc_params(self.getnchannels(), self.getsampwidth(),
399 self.getframerate(), self.getnframes(),
400 self.getcomptype(), self.getcompname())
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000401
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000402 def getmarkers(self):
403 if len(self._markers) == 0:
404 return None
405 return self._markers
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000406
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000407 def getmark(self, id):
408 for marker in self._markers:
409 if id == marker[0]:
410 return marker
Georg Brandl2095cfe2008-06-07 19:01:03 +0000411 raise Error('marker {0!r} does not exist'.format(id))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000412
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000413 def setpos(self, pos):
414 if pos < 0 or pos > self._nframes:
Collin Winterce36ad82007-08-30 01:19:48 +0000415 raise Error('position not in range')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000416 self._soundpos = pos
417 self._ssnd_seek_needed = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000418
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000419 def readframes(self, nframes):
420 if self._ssnd_seek_needed:
421 self._ssnd_chunk.seek(0)
422 dummy = self._ssnd_chunk.read(8)
423 pos = self._soundpos * self._framesize
424 if pos:
Guido van Rossum2663c132000-03-07 15:19:31 +0000425 self._ssnd_chunk.seek(pos + 8)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000426 self._ssnd_seek_needed = 0
427 if nframes == 0:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000428 return b''
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000429 data = self._ssnd_chunk.read(nframes * self._framesize)
430 if self._convert and data:
431 data = self._convert(data)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000432 self._soundpos = self._soundpos + len(data) // (self._nchannels
433 * self._sampwidth)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000434 return data
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000435
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000436 #
437 # Internal methods.
438 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000439
Georg Brandl2095cfe2008-06-07 19:01:03 +0000440 def _alaw2lin(self, data):
441 import audioop
442 return audioop.alaw2lin(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000443
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000444 def _ulaw2lin(self, data):
445 import audioop
446 return audioop.ulaw2lin(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000447
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000448 def _adpcm2lin(self, data):
449 import audioop
450 if not hasattr(self, '_adpcmstate'):
451 # first time
452 self._adpcmstate = None
Georg Brandl2095cfe2008-06-07 19:01:03 +0000453 data, self._adpcmstate = audioop.adpcm2lin(data, 2, self._adpcmstate)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000454 return data
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000455
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000456 def _read_comm_chunk(self, chunk):
457 self._nchannels = _read_short(chunk)
458 self._nframes = _read_long(chunk)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000459 self._sampwidth = (_read_short(chunk) + 7) // 8
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000460 self._framerate = int(_read_float(chunk))
461 self._framesize = self._nchannels * self._sampwidth
462 if self._aifc:
463 #DEBUG: SGI's soundeditor produces a bad size :-(
464 kludge = 0
465 if chunk.chunksize == 18:
466 kludge = 1
Ezio Melotti48d578c2012-03-12 23:57:18 +0200467 warnings.warn('Warning: bad COMM chunk size')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000468 chunk.chunksize = 23
469 #DEBUG end
470 self._comptype = chunk.read(4)
471 #DEBUG start
472 if kludge:
473 length = ord(chunk.file.read(1))
474 if length & 1 == 0:
475 length = length + 1
476 chunk.chunksize = chunk.chunksize + length
477 chunk.file.seek(-1, 1)
478 #DEBUG end
479 self._compname = _read_string(chunk)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000480 if self._comptype != b'NONE':
481 if self._comptype == b'G722':
482 self._convert = self._adpcm2lin
Georg Brandl2095cfe2008-06-07 19:01:03 +0000483 elif self._comptype in (b'ulaw', b'ULAW'):
484 self._convert = self._ulaw2lin
Georg Brandl2095cfe2008-06-07 19:01:03 +0000485 elif self._comptype in (b'alaw', b'ALAW'):
486 self._convert = self._alaw2lin
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000487 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000488 raise Error('unsupported compression type')
Serhiy Storchaka4b532592013-10-12 18:21:33 +0300489 self._sampwidth = 2
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000490 else:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000491 self._comptype = b'NONE'
492 self._compname = b'not compressed'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000493
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000494 def _readmark(self, chunk):
495 nmarkers = _read_short(chunk)
496 # Some files appear to contain invalid counts.
497 # Cope with this by testing for EOF.
498 try:
499 for i in range(nmarkers):
500 id = _read_short(chunk)
501 pos = _read_long(chunk)
502 name = _read_string(chunk)
503 if pos or name:
504 # some files appear to have
505 # dummy markers consisting of
506 # a position 0 and name ''
507 self._markers.append((id, pos, name))
508 except EOFError:
Ezio Melotti48d578c2012-03-12 23:57:18 +0200509 w = ('Warning: MARK chunk contains only %s marker%s instead of %s' %
510 (len(self._markers), '' if len(self._markers) == 1 else 's',
511 nmarkers))
512 warnings.warn(w)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000513
Guido van Rossumd3166071993-05-24 14:16:22 +0000514class Aifc_write:
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000515 # Variables used in this class:
516 #
517 # These variables are user settable through appropriate methods
518 # of this class:
519 # _file -- the open file with methods write(), close(), tell(), seek()
520 # set through the __init__() method
521 # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
522 # set through the setcomptype() or setparams() method
523 # _compname -- the human-readable AIFF-C compression type
524 # set through the setcomptype() or setparams() method
525 # _nchannels -- the number of audio channels
526 # set through the setnchannels() or setparams() method
527 # _sampwidth -- the number of bytes per audio sample
528 # set through the setsampwidth() or setparams() method
529 # _framerate -- the sampling frequency
530 # set through the setframerate() or setparams() method
531 # _nframes -- the number of audio frames written to the header
532 # set through the setnframes() or setparams() method
533 # _aifc -- whether we're writing an AIFF-C file or an AIFF file
534 # set through the aifc() method, reset through the
535 # aiff() method
536 #
537 # These variables are used internally only:
538 # _version -- the AIFF-C version number
539 # _comp -- the compressor from builtin module cl
540 # _nframeswritten -- the number of audio frames actually written
541 # _datalength -- the size of the audio samples written to the header
542 # _datawritten -- the size of the audio samples actually written
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000543
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000544 def __init__(self, f):
Georg Brandl2095cfe2008-06-07 19:01:03 +0000545 if isinstance(f, str):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000546 filename = f
Georg Brandl1a3284e2007-12-02 09:40:06 +0000547 f = builtins.open(f, 'wb')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000548 else:
549 # else, assume it is an open file object already
550 filename = '???'
551 self.initfp(f)
552 if filename[-5:] == '.aiff':
553 self._aifc = 0
554 else:
555 self._aifc = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000556
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000557 def initfp(self, file):
558 self._file = file
559 self._version = _AIFC_version
Georg Brandl2095cfe2008-06-07 19:01:03 +0000560 self._comptype = b'NONE'
561 self._compname = b'not compressed'
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000562 self._convert = None
563 self._nchannels = 0
564 self._sampwidth = 0
565 self._framerate = 0
566 self._nframes = 0
567 self._nframeswritten = 0
568 self._datawritten = 0
569 self._datalength = 0
570 self._markers = []
571 self._marklength = 0
572 self._aifc = 1 # AIFF-C is default
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000573
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000574 def __del__(self):
Sandro Tosi70efbef2012-01-01 22:53:08 +0100575 self.close()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000576
Serhiy Storchaka44c66c72012-12-29 22:54:49 +0200577 def __enter__(self):
578 return self
579
580 def __exit__(self, *args):
581 self.close()
582
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000583 #
584 # User visible methods.
585 #
586 def aiff(self):
587 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000588 raise Error('cannot change parameters after starting to write')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000589 self._aifc = 0
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000590
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000591 def aifc(self):
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 self._aifc = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000595
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000596 def setnchannels(self, nchannels):
597 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000598 raise Error('cannot change parameters after starting to write')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000599 if nchannels < 1:
Collin Winterce36ad82007-08-30 01:19:48 +0000600 raise Error('bad # of channels')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000601 self._nchannels = nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000602
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000603 def getnchannels(self):
604 if not self._nchannels:
Collin Winterce36ad82007-08-30 01:19:48 +0000605 raise Error('number of channels not set')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000606 return self._nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000607
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000608 def setsampwidth(self, sampwidth):
609 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000610 raise Error('cannot change parameters after starting to write')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000611 if sampwidth < 1 or sampwidth > 4:
Collin Winterce36ad82007-08-30 01:19:48 +0000612 raise Error('bad sample width')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000613 self._sampwidth = sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000614
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000615 def getsampwidth(self):
616 if not self._sampwidth:
Collin Winterce36ad82007-08-30 01:19:48 +0000617 raise Error('sample width not set')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000618 return self._sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000619
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000620 def setframerate(self, framerate):
621 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000622 raise Error('cannot change parameters after starting to write')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000623 if framerate <= 0:
Collin Winterce36ad82007-08-30 01:19:48 +0000624 raise Error('bad frame rate')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000625 self._framerate = framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000626
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000627 def getframerate(self):
628 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000629 raise Error('frame rate not set')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000630 return self._framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000631
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000632 def setnframes(self, nframes):
633 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000634 raise Error('cannot change parameters after starting to write')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000635 self._nframes = nframes
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000636
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000637 def getnframes(self):
638 return self._nframeswritten
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000639
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000640 def setcomptype(self, comptype, compname):
641 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000642 raise Error('cannot change parameters after starting to write')
Georg Brandl2095cfe2008-06-07 19:01:03 +0000643 if comptype not in (b'NONE', b'ulaw', b'ULAW',
644 b'alaw', b'ALAW', b'G722'):
Collin Winterce36ad82007-08-30 01:19:48 +0000645 raise Error('unsupported compression type')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000646 self._comptype = comptype
647 self._compname = compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000648
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000649 def getcomptype(self):
650 return self._comptype
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000651
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000652 def getcompname(self):
653 return self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000654
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000655## def setversion(self, version):
656## if self._nframeswritten:
657## raise Error, 'cannot change parameters after starting to write'
658## self._version = version
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000659
Guido van Rossum1bc535d2007-05-15 18:46:22 +0000660 def setparams(self, params):
661 nchannels, sampwidth, framerate, nframes, comptype, compname = params
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000662 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000663 raise Error('cannot change parameters after starting to write')
Georg Brandl2095cfe2008-06-07 19:01:03 +0000664 if comptype not in (b'NONE', b'ulaw', b'ULAW',
665 b'alaw', b'ALAW', b'G722'):
Collin Winterce36ad82007-08-30 01:19:48 +0000666 raise Error('unsupported compression type')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000667 self.setnchannels(nchannels)
668 self.setsampwidth(sampwidth)
669 self.setframerate(framerate)
670 self.setnframes(nframes)
671 self.setcomptype(comptype, compname)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000672
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000673 def getparams(self):
674 if not self._nchannels or not self._sampwidth or not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000675 raise Error('not all parameters set')
R David Murray4d35e752013-07-25 16:12:01 -0400676 return _aifc_params(self._nchannels, self._sampwidth, self._framerate,
677 self._nframes, self._comptype, self._compname)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000678
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000679 def setmark(self, id, pos, name):
680 if id <= 0:
Collin Winterce36ad82007-08-30 01:19:48 +0000681 raise Error('marker ID must be > 0')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000682 if pos < 0:
Collin Winterce36ad82007-08-30 01:19:48 +0000683 raise Error('marker position must be >= 0')
Sandro Tosi70efbef2012-01-01 22:53:08 +0100684 if not isinstance(name, bytes):
685 raise Error('marker name must be bytes')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000686 for i in range(len(self._markers)):
687 if id == self._markers[i][0]:
688 self._markers[i] = id, pos, name
689 return
690 self._markers.append((id, pos, name))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000691
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000692 def getmark(self, id):
693 for marker in self._markers:
694 if id == marker[0]:
695 return marker
Georg Brandl2095cfe2008-06-07 19:01:03 +0000696 raise Error('marker {0!r} does not exist'.format(id))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000697
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000698 def getmarkers(self):
699 if len(self._markers) == 0:
700 return None
701 return self._markers
Tim Peters146965a2001-01-14 18:09:23 +0000702
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000703 def tell(self):
704 return self._nframeswritten
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000705
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000706 def writeframesraw(self, data):
Serhiy Storchaka452bab42013-11-16 14:01:31 +0200707 if not isinstance(data, (bytes, bytearray)):
708 data = memoryview(data).cast('B')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000709 self._ensure_header_written(len(data))
Georg Brandl2095cfe2008-06-07 19:01:03 +0000710 nframes = len(data) // (self._sampwidth * self._nchannels)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000711 if self._convert:
712 data = self._convert(data)
713 self._file.write(data)
714 self._nframeswritten = self._nframeswritten + nframes
715 self._datawritten = self._datawritten + len(data)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000716
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000717 def writeframes(self, data):
718 self.writeframesraw(data)
719 if self._nframeswritten != self._nframes or \
720 self._datalength != self._datawritten:
721 self._patchheader()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000722
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000723 def close(self):
Serhiy Storchaka051722d2012-12-29 22:30:56 +0200724 if self._file is None:
725 return
726 try:
Sandro Tosi70efbef2012-01-01 22:53:08 +0100727 self._ensure_header_written(0)
728 if self._datawritten & 1:
729 # quick pad to even size
730 self._file.write(b'\x00')
731 self._datawritten = self._datawritten + 1
732 self._writemarkers()
733 if self._nframeswritten != self._nframes or \
734 self._datalength != self._datawritten or \
735 self._marklength:
736 self._patchheader()
Serhiy Storchaka051722d2012-12-29 22:30:56 +0200737 finally:
Sandro Tosi70efbef2012-01-01 22:53:08 +0100738 # Prevent ref cycles
739 self._convert = None
Serhiy Storchaka051722d2012-12-29 22:30:56 +0200740 f = self._file
Sandro Tosi70efbef2012-01-01 22:53:08 +0100741 self._file = None
Serhiy Storchaka051722d2012-12-29 22:30:56 +0200742 f.close()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000743
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000744 #
745 # Internal methods.
746 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000747
Georg Brandl2095cfe2008-06-07 19:01:03 +0000748 def _lin2alaw(self, data):
749 import audioop
750 return audioop.lin2alaw(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000751
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000752 def _lin2ulaw(self, data):
753 import audioop
754 return audioop.lin2ulaw(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000755
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000756 def _lin2adpcm(self, data):
757 import audioop
758 if not hasattr(self, '_adpcmstate'):
759 self._adpcmstate = None
Georg Brandl2095cfe2008-06-07 19:01:03 +0000760 data, self._adpcmstate = audioop.lin2adpcm(data, 2, self._adpcmstate)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000761 return data
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000762
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000763 def _ensure_header_written(self, datasize):
764 if not self._nframeswritten:
Sandro Tosibdd53542012-01-01 18:04:37 +0100765 if self._comptype in (b'ULAW', b'ulaw', b'ALAW', b'alaw', b'G722'):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000766 if not self._sampwidth:
767 self._sampwidth = 2
768 if self._sampwidth != 2:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000769 raise Error('sample width must be 2 when compressing '
Sandro Tosibdd53542012-01-01 18:04:37 +0100770 'with ulaw/ULAW, alaw/ALAW or G7.22 (ADPCM)')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000771 if not self._nchannels:
Collin Winterce36ad82007-08-30 01:19:48 +0000772 raise Error('# channels not specified')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000773 if not self._sampwidth:
Collin Winterce36ad82007-08-30 01:19:48 +0000774 raise Error('sample width not specified')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000775 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000776 raise Error('sampling rate not specified')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000777 self._write_header(datasize)
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000778
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000779 def _init_compression(self):
Georg Brandl2095cfe2008-06-07 19:01:03 +0000780 if self._comptype == b'G722':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000781 self._convert = self._lin2adpcm
Georg Brandl2095cfe2008-06-07 19:01:03 +0000782 elif self._comptype in (b'ulaw', b'ULAW'):
783 self._convert = self._lin2ulaw
784 elif self._comptype in (b'alaw', b'ALAW'):
785 self._convert = self._lin2alaw
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000786
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000787 def _write_header(self, initlength):
Georg Brandl2095cfe2008-06-07 19:01:03 +0000788 if self._aifc and self._comptype != b'NONE':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000789 self._init_compression()
Georg Brandl2095cfe2008-06-07 19:01:03 +0000790 self._file.write(b'FORM')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000791 if not self._nframes:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000792 self._nframes = initlength // (self._nchannels * self._sampwidth)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000793 self._datalength = self._nframes * self._nchannels * self._sampwidth
794 if self._datalength & 1:
795 self._datalength = self._datalength + 1
796 if self._aifc:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000797 if self._comptype in (b'ulaw', b'ULAW', b'alaw', b'ALAW'):
798 self._datalength = self._datalength // 2
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000799 if self._datalength & 1:
800 self._datalength = self._datalength + 1
Georg Brandl2095cfe2008-06-07 19:01:03 +0000801 elif self._comptype == b'G722':
802 self._datalength = (self._datalength + 3) // 4
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000803 if self._datalength & 1:
804 self._datalength = self._datalength + 1
Serhiy Storchaka84d28b42013-12-14 20:35:04 +0200805 try:
806 self._form_length_pos = self._file.tell()
807 except (AttributeError, OSError):
808 self._form_length_pos = None
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000809 commlength = self._write_form_length(self._datalength)
810 if self._aifc:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000811 self._file.write(b'AIFC')
812 self._file.write(b'FVER')
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100813 _write_ulong(self._file, 4)
814 _write_ulong(self._file, self._version)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000815 else:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000816 self._file.write(b'AIFF')
817 self._file.write(b'COMM')
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100818 _write_ulong(self._file, commlength)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000819 _write_short(self._file, self._nchannels)
Serhiy Storchaka84d28b42013-12-14 20:35:04 +0200820 if self._form_length_pos is not None:
821 self._nframes_pos = self._file.tell()
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100822 _write_ulong(self._file, self._nframes)
Serhiy Storchaka4b532592013-10-12 18:21:33 +0300823 if self._comptype in (b'ULAW', b'ulaw', b'ALAW', b'alaw', b'G722'):
824 _write_short(self._file, 8)
825 else:
826 _write_short(self._file, self._sampwidth * 8)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000827 _write_float(self._file, self._framerate)
828 if self._aifc:
829 self._file.write(self._comptype)
830 _write_string(self._file, self._compname)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000831 self._file.write(b'SSND')
Serhiy Storchaka84d28b42013-12-14 20:35:04 +0200832 if self._form_length_pos is not None:
833 self._ssnd_length_pos = self._file.tell()
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100834 _write_ulong(self._file, self._datalength + 8)
835 _write_ulong(self._file, 0)
836 _write_ulong(self._file, 0)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000837
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000838 def _write_form_length(self, datalength):
839 if self._aifc:
840 commlength = 18 + 5 + len(self._compname)
841 if commlength & 1:
842 commlength = commlength + 1
843 verslength = 12
844 else:
845 commlength = 18
846 verslength = 0
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100847 _write_ulong(self._file, 4 + verslength + self._marklength + \
848 8 + commlength + 16 + datalength)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000849 return commlength
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000850
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000851 def _patchheader(self):
852 curpos = self._file.tell()
853 if self._datawritten & 1:
854 datalength = self._datawritten + 1
Georg Brandl2095cfe2008-06-07 19:01:03 +0000855 self._file.write(b'\x00')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000856 else:
857 datalength = self._datawritten
858 if datalength == self._datalength and \
859 self._nframes == self._nframeswritten and \
860 self._marklength == 0:
861 self._file.seek(curpos, 0)
862 return
863 self._file.seek(self._form_length_pos, 0)
864 dummy = self._write_form_length(datalength)
865 self._file.seek(self._nframes_pos, 0)
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100866 _write_ulong(self._file, self._nframeswritten)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000867 self._file.seek(self._ssnd_length_pos, 0)
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100868 _write_ulong(self._file, datalength + 8)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000869 self._file.seek(curpos, 0)
870 self._nframes = self._nframeswritten
871 self._datalength = datalength
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000872
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000873 def _writemarkers(self):
874 if len(self._markers) == 0:
875 return
Georg Brandl2095cfe2008-06-07 19:01:03 +0000876 self._file.write(b'MARK')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000877 length = 2
878 for marker in self._markers:
879 id, pos, name = marker
880 length = length + len(name) + 1 + 6
881 if len(name) & 1 == 0:
882 length = length + 1
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100883 _write_ulong(self._file, length)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000884 self._marklength = length + 8
885 _write_short(self._file, len(self._markers))
886 for marker in self._markers:
887 id, pos, name = marker
888 _write_short(self._file, id)
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100889 _write_ulong(self._file, pos)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000890 _write_string(self._file, name)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000891
Fred Drake43161351999-06-22 21:23:23 +0000892def open(f, mode=None):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000893 if mode is None:
894 if hasattr(f, 'mode'):
895 mode = f.mode
896 else:
897 mode = 'rb'
898 if mode in ('r', 'rb'):
899 return Aifc_read(f)
900 elif mode in ('w', 'wb'):
901 return Aifc_write(f)
902 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000903 raise Error("mode must be 'r', 'rb', 'w', or 'wb'")
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000904
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000905openfp = open # B/W compatibility
Guido van Rossum36bb1811996-12-31 05:57:34 +0000906
907if __name__ == '__main__':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000908 import sys
909 if not sys.argv[1:]:
910 sys.argv.append('/usr/demos/data/audio/bach.aiff')
911 fn = sys.argv[1]
Serhiy Storchaka58b3ebf2013-08-25 19:16:01 +0300912 with open(fn, 'r') as f:
Serhiy Storchakab33baf12013-08-25 19:12:56 +0300913 print("Reading", fn)
914 print("nchannels =", f.getnchannels())
915 print("nframes =", f.getnframes())
916 print("sampwidth =", f.getsampwidth())
917 print("framerate =", f.getframerate())
918 print("comptype =", f.getcomptype())
919 print("compname =", f.getcompname())
920 if sys.argv[2:]:
921 gn = sys.argv[2]
922 print("Writing", gn)
Serhiy Storchaka58b3ebf2013-08-25 19:16:01 +0300923 with open(gn, 'w') as g:
Serhiy Storchakab33baf12013-08-25 19:12:56 +0300924 g.setparams(f.getparams())
925 while 1:
926 data = f.readframes(1024)
927 if not data:
928 break
929 g.writeframes(data)
Serhiy Storchakab33baf12013-08-25 19:12:56 +0300930 print("Done.")