blob: 13ad7dc5ca3d62d20dd6500053af77298d52eccf [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
INADA Naokic9131b62017-02-27 04:09:32 +0900306 _file = None # Set here since __del__ checks it
307
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000308 def initfp(self, file):
309 self._version = 0
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000310 self._convert = None
311 self._markers = []
312 self._soundpos = 0
R. David Murray99352742009-05-07 18:24:38 +0000313 self._file = file
314 chunk = Chunk(file)
315 if chunk.getname() != b'FORM':
Collin Winterce36ad82007-08-30 01:19:48 +0000316 raise Error('file does not start with FORM id')
R. David Murray99352742009-05-07 18:24:38 +0000317 formdata = chunk.read(4)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000318 if formdata == b'AIFF':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000319 self._aifc = 0
Georg Brandl2095cfe2008-06-07 19:01:03 +0000320 elif formdata == b'AIFC':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000321 self._aifc = 1
322 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000323 raise Error('not an AIFF or AIFF-C file')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000324 self._comm_chunk_read = 0
325 while 1:
326 self._ssnd_seek_needed = 1
327 try:
328 chunk = Chunk(self._file)
329 except EOFError:
330 break
331 chunkname = chunk.getname()
Georg Brandl2095cfe2008-06-07 19:01:03 +0000332 if chunkname == b'COMM':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000333 self._read_comm_chunk(chunk)
334 self._comm_chunk_read = 1
Georg Brandl2095cfe2008-06-07 19:01:03 +0000335 elif chunkname == b'SSND':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000336 self._ssnd_chunk = chunk
337 dummy = chunk.read(8)
338 self._ssnd_seek_needed = 0
Georg Brandl2095cfe2008-06-07 19:01:03 +0000339 elif chunkname == b'FVER':
Guido van Rossum820819c2002-08-12 22:11:28 +0000340 self._version = _read_ulong(chunk)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000341 elif chunkname == b'MARK':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000342 self._readmark(chunk)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000343 chunk.skip()
344 if not self._comm_chunk_read or not self._ssnd_chunk:
Collin Winterce36ad82007-08-30 01:19:48 +0000345 raise Error('COMM chunk and/or SSND chunk missing')
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000346
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000347 def __init__(self, f):
Georg Brandl2095cfe2008-06-07 19:01:03 +0000348 if isinstance(f, str):
INADA Naokic9131b62017-02-27 04:09:32 +0900349 file_object = builtins.open(f, 'rb')
350 try:
351 self.initfp(file_object)
352 except:
353 file_object.close()
354 raise
355 else:
356 # assume it is an open file object already
357 self.initfp(f)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000358
Serhiy Storchaka44c66c72012-12-29 22:54:49 +0200359 def __enter__(self):
360 return self
361
362 def __exit__(self, *args):
363 self.close()
364
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000365 #
366 # User visible methods.
367 #
368 def getfp(self):
369 return self._file
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000370
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000371 def rewind(self):
372 self._ssnd_seek_needed = 1
373 self._soundpos = 0
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000374
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000375 def close(self):
Serhiy Storchaka7e7a3db2015-04-10 13:24:41 +0300376 file = self._file
377 if file is not None:
378 self._file = None
379 file.close()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000380
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000381 def tell(self):
382 return self._soundpos
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000383
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000384 def getnchannels(self):
385 return self._nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000386
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000387 def getnframes(self):
388 return self._nframes
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000389
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000390 def getsampwidth(self):
391 return self._sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000392
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000393 def getframerate(self):
394 return self._framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000395
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000396 def getcomptype(self):
397 return self._comptype
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000398
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000399 def getcompname(self):
400 return self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000401
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000402## def getversion(self):
403## return self._version
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000404
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000405 def getparams(self):
R David Murray4d35e752013-07-25 16:12:01 -0400406 return _aifc_params(self.getnchannels(), self.getsampwidth(),
407 self.getframerate(), self.getnframes(),
408 self.getcomptype(), self.getcompname())
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000409
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000410 def getmarkers(self):
411 if len(self._markers) == 0:
412 return None
413 return self._markers
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000414
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000415 def getmark(self, id):
416 for marker in self._markers:
417 if id == marker[0]:
418 return marker
Georg Brandl2095cfe2008-06-07 19:01:03 +0000419 raise Error('marker {0!r} does not exist'.format(id))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000420
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000421 def setpos(self, pos):
422 if pos < 0 or pos > self._nframes:
Collin Winterce36ad82007-08-30 01:19:48 +0000423 raise Error('position not in range')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000424 self._soundpos = pos
425 self._ssnd_seek_needed = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000426
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000427 def readframes(self, nframes):
428 if self._ssnd_seek_needed:
429 self._ssnd_chunk.seek(0)
430 dummy = self._ssnd_chunk.read(8)
431 pos = self._soundpos * self._framesize
432 if pos:
Guido van Rossum2663c132000-03-07 15:19:31 +0000433 self._ssnd_chunk.seek(pos + 8)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000434 self._ssnd_seek_needed = 0
435 if nframes == 0:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000436 return b''
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000437 data = self._ssnd_chunk.read(nframes * self._framesize)
438 if self._convert and data:
439 data = self._convert(data)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000440 self._soundpos = self._soundpos + len(data) // (self._nchannels
441 * self._sampwidth)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000442 return data
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000443
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000444 #
445 # Internal methods.
446 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000447
Georg Brandl2095cfe2008-06-07 19:01:03 +0000448 def _alaw2lin(self, data):
449 import audioop
450 return audioop.alaw2lin(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000451
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000452 def _ulaw2lin(self, data):
453 import audioop
454 return audioop.ulaw2lin(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000455
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000456 def _adpcm2lin(self, data):
457 import audioop
458 if not hasattr(self, '_adpcmstate'):
459 # first time
460 self._adpcmstate = None
Georg Brandl2095cfe2008-06-07 19:01:03 +0000461 data, self._adpcmstate = audioop.adpcm2lin(data, 2, self._adpcmstate)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000462 return data
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000463
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000464 def _read_comm_chunk(self, chunk):
465 self._nchannels = _read_short(chunk)
466 self._nframes = _read_long(chunk)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000467 self._sampwidth = (_read_short(chunk) + 7) // 8
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000468 self._framerate = int(_read_float(chunk))
469 self._framesize = self._nchannels * self._sampwidth
470 if self._aifc:
471 #DEBUG: SGI's soundeditor produces a bad size :-(
472 kludge = 0
473 if chunk.chunksize == 18:
474 kludge = 1
Ezio Melotti48d578c2012-03-12 23:57:18 +0200475 warnings.warn('Warning: bad COMM chunk size')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000476 chunk.chunksize = 23
477 #DEBUG end
478 self._comptype = chunk.read(4)
479 #DEBUG start
480 if kludge:
481 length = ord(chunk.file.read(1))
482 if length & 1 == 0:
483 length = length + 1
484 chunk.chunksize = chunk.chunksize + length
485 chunk.file.seek(-1, 1)
486 #DEBUG end
487 self._compname = _read_string(chunk)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000488 if self._comptype != b'NONE':
489 if self._comptype == b'G722':
490 self._convert = self._adpcm2lin
Georg Brandl2095cfe2008-06-07 19:01:03 +0000491 elif self._comptype in (b'ulaw', b'ULAW'):
492 self._convert = self._ulaw2lin
Georg Brandl2095cfe2008-06-07 19:01:03 +0000493 elif self._comptype in (b'alaw', b'ALAW'):
494 self._convert = self._alaw2lin
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000495 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000496 raise Error('unsupported compression type')
Serhiy Storchaka4b532592013-10-12 18:21:33 +0300497 self._sampwidth = 2
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000498 else:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000499 self._comptype = b'NONE'
500 self._compname = b'not compressed'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000501
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000502 def _readmark(self, chunk):
503 nmarkers = _read_short(chunk)
504 # Some files appear to contain invalid counts.
505 # Cope with this by testing for EOF.
506 try:
507 for i in range(nmarkers):
508 id = _read_short(chunk)
509 pos = _read_long(chunk)
510 name = _read_string(chunk)
511 if pos or name:
512 # some files appear to have
513 # dummy markers consisting of
514 # a position 0 and name ''
515 self._markers.append((id, pos, name))
516 except EOFError:
Ezio Melotti48d578c2012-03-12 23:57:18 +0200517 w = ('Warning: MARK chunk contains only %s marker%s instead of %s' %
518 (len(self._markers), '' if len(self._markers) == 1 else 's',
519 nmarkers))
520 warnings.warn(w)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000521
Guido van Rossumd3166071993-05-24 14:16:22 +0000522class Aifc_write:
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000523 # Variables used in this class:
524 #
525 # These variables are user settable through appropriate methods
526 # of this class:
527 # _file -- the open file with methods write(), close(), tell(), seek()
528 # set through the __init__() method
529 # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
530 # set through the setcomptype() or setparams() method
531 # _compname -- the human-readable AIFF-C compression type
532 # set through the setcomptype() or setparams() method
533 # _nchannels -- the number of audio channels
534 # set through the setnchannels() or setparams() method
535 # _sampwidth -- the number of bytes per audio sample
536 # set through the setsampwidth() or setparams() method
537 # _framerate -- the sampling frequency
538 # set through the setframerate() or setparams() method
539 # _nframes -- the number of audio frames written to the header
540 # set through the setnframes() or setparams() method
541 # _aifc -- whether we're writing an AIFF-C file or an AIFF file
542 # set through the aifc() method, reset through the
543 # aiff() method
544 #
545 # These variables are used internally only:
546 # _version -- the AIFF-C version number
547 # _comp -- the compressor from builtin module cl
548 # _nframeswritten -- the number of audio frames actually written
549 # _datalength -- the size of the audio samples written to the header
550 # _datawritten -- the size of the audio samples actually written
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000551
INADA Naokic9131b62017-02-27 04:09:32 +0900552 _file = None # Set here since __del__ checks it
553
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000554 def __init__(self, f):
Georg Brandl2095cfe2008-06-07 19:01:03 +0000555 if isinstance(f, str):
INADA Naokic9131b62017-02-27 04:09:32 +0900556 file_object = builtins.open(f, 'wb')
557 try:
558 self.initfp(file_object)
559 except:
560 file_object.close()
561 raise
562
563 # treat .aiff file extensions as non-compressed audio
564 if f.endswith('.aiff'):
565 self._aifc = 0
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000566 else:
INADA Naokic9131b62017-02-27 04:09:32 +0900567 # assume it is an open file object already
568 self.initfp(f)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000569
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000570 def initfp(self, file):
571 self._file = file
572 self._version = _AIFC_version
Georg Brandl2095cfe2008-06-07 19:01:03 +0000573 self._comptype = b'NONE'
574 self._compname = b'not compressed'
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000575 self._convert = None
576 self._nchannels = 0
577 self._sampwidth = 0
578 self._framerate = 0
579 self._nframes = 0
580 self._nframeswritten = 0
581 self._datawritten = 0
582 self._datalength = 0
583 self._markers = []
584 self._marklength = 0
585 self._aifc = 1 # AIFF-C is default
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000586
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000587 def __del__(self):
Sandro Tosi70efbef2012-01-01 22:53:08 +0100588 self.close()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000589
Serhiy Storchaka44c66c72012-12-29 22:54:49 +0200590 def __enter__(self):
591 return self
592
593 def __exit__(self, *args):
594 self.close()
595
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000596 #
597 # User visible methods.
598 #
599 def aiff(self):
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 self._aifc = 0
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000603
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000604 def aifc(self):
605 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000606 raise Error('cannot change parameters after starting to write')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000607 self._aifc = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000608
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000609 def setnchannels(self, nchannels):
610 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000611 raise Error('cannot change parameters after starting to write')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000612 if nchannels < 1:
Collin Winterce36ad82007-08-30 01:19:48 +0000613 raise Error('bad # of channels')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000614 self._nchannels = nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000615
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000616 def getnchannels(self):
617 if not self._nchannels:
Collin Winterce36ad82007-08-30 01:19:48 +0000618 raise Error('number of channels not set')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000619 return self._nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000620
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000621 def setsampwidth(self, sampwidth):
622 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000623 raise Error('cannot change parameters after starting to write')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000624 if sampwidth < 1 or sampwidth > 4:
Collin Winterce36ad82007-08-30 01:19:48 +0000625 raise Error('bad sample width')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000626 self._sampwidth = sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000627
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000628 def getsampwidth(self):
629 if not self._sampwidth:
Collin Winterce36ad82007-08-30 01:19:48 +0000630 raise Error('sample width not set')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000631 return self._sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000632
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000633 def setframerate(self, framerate):
634 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000635 raise Error('cannot change parameters after starting to write')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000636 if framerate <= 0:
Collin Winterce36ad82007-08-30 01:19:48 +0000637 raise Error('bad frame rate')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000638 self._framerate = framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000639
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000640 def getframerate(self):
641 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000642 raise Error('frame rate not set')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000643 return self._framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000644
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000645 def setnframes(self, nframes):
646 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000647 raise Error('cannot change parameters after starting to write')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000648 self._nframes = nframes
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000649
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000650 def getnframes(self):
651 return self._nframeswritten
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000652
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000653 def setcomptype(self, comptype, compname):
654 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000655 raise Error('cannot change parameters after starting to write')
Georg Brandl2095cfe2008-06-07 19:01:03 +0000656 if comptype not in (b'NONE', b'ulaw', b'ULAW',
657 b'alaw', b'ALAW', b'G722'):
Collin Winterce36ad82007-08-30 01:19:48 +0000658 raise Error('unsupported compression type')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000659 self._comptype = comptype
660 self._compname = compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000661
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000662 def getcomptype(self):
663 return self._comptype
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000664
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000665 def getcompname(self):
666 return self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000667
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000668## def setversion(self, version):
669## if self._nframeswritten:
670## raise Error, 'cannot change parameters after starting to write'
671## self._version = version
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000672
Guido van Rossum1bc535d2007-05-15 18:46:22 +0000673 def setparams(self, params):
674 nchannels, sampwidth, framerate, nframes, comptype, compname = params
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000675 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000676 raise Error('cannot change parameters after starting to write')
Georg Brandl2095cfe2008-06-07 19:01:03 +0000677 if comptype not in (b'NONE', b'ulaw', b'ULAW',
678 b'alaw', b'ALAW', b'G722'):
Collin Winterce36ad82007-08-30 01:19:48 +0000679 raise Error('unsupported compression type')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000680 self.setnchannels(nchannels)
681 self.setsampwidth(sampwidth)
682 self.setframerate(framerate)
683 self.setnframes(nframes)
684 self.setcomptype(comptype, compname)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000685
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000686 def getparams(self):
687 if not self._nchannels or not self._sampwidth or not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000688 raise Error('not all parameters set')
R David Murray4d35e752013-07-25 16:12:01 -0400689 return _aifc_params(self._nchannels, self._sampwidth, self._framerate,
690 self._nframes, self._comptype, self._compname)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000691
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000692 def setmark(self, id, pos, name):
693 if id <= 0:
Collin Winterce36ad82007-08-30 01:19:48 +0000694 raise Error('marker ID must be > 0')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000695 if pos < 0:
Collin Winterce36ad82007-08-30 01:19:48 +0000696 raise Error('marker position must be >= 0')
Sandro Tosi70efbef2012-01-01 22:53:08 +0100697 if not isinstance(name, bytes):
698 raise Error('marker name must be bytes')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000699 for i in range(len(self._markers)):
700 if id == self._markers[i][0]:
701 self._markers[i] = id, pos, name
702 return
703 self._markers.append((id, pos, name))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000704
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000705 def getmark(self, id):
706 for marker in self._markers:
707 if id == marker[0]:
708 return marker
Georg Brandl2095cfe2008-06-07 19:01:03 +0000709 raise Error('marker {0!r} does not exist'.format(id))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000710
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000711 def getmarkers(self):
712 if len(self._markers) == 0:
713 return None
714 return self._markers
Tim Peters146965a2001-01-14 18:09:23 +0000715
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000716 def tell(self):
717 return self._nframeswritten
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000718
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000719 def writeframesraw(self, data):
Serhiy Storchaka452bab42013-11-16 14:01:31 +0200720 if not isinstance(data, (bytes, bytearray)):
721 data = memoryview(data).cast('B')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000722 self._ensure_header_written(len(data))
Georg Brandl2095cfe2008-06-07 19:01:03 +0000723 nframes = len(data) // (self._sampwidth * self._nchannels)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000724 if self._convert:
725 data = self._convert(data)
726 self._file.write(data)
727 self._nframeswritten = self._nframeswritten + nframes
728 self._datawritten = self._datawritten + len(data)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000729
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000730 def writeframes(self, data):
731 self.writeframesraw(data)
732 if self._nframeswritten != self._nframes or \
733 self._datalength != self._datawritten:
734 self._patchheader()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000735
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000736 def close(self):
Serhiy Storchaka051722d2012-12-29 22:30:56 +0200737 if self._file is None:
738 return
739 try:
Sandro Tosi70efbef2012-01-01 22:53:08 +0100740 self._ensure_header_written(0)
741 if self._datawritten & 1:
742 # quick pad to even size
743 self._file.write(b'\x00')
744 self._datawritten = self._datawritten + 1
745 self._writemarkers()
746 if self._nframeswritten != self._nframes or \
747 self._datalength != self._datawritten or \
748 self._marklength:
749 self._patchheader()
Serhiy Storchaka051722d2012-12-29 22:30:56 +0200750 finally:
Sandro Tosi70efbef2012-01-01 22:53:08 +0100751 # Prevent ref cycles
752 self._convert = None
Serhiy Storchaka051722d2012-12-29 22:30:56 +0200753 f = self._file
Sandro Tosi70efbef2012-01-01 22:53:08 +0100754 self._file = None
Serhiy Storchaka051722d2012-12-29 22:30:56 +0200755 f.close()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000756
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000757 #
758 # Internal methods.
759 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000760
Georg Brandl2095cfe2008-06-07 19:01:03 +0000761 def _lin2alaw(self, data):
762 import audioop
763 return audioop.lin2alaw(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000764
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000765 def _lin2ulaw(self, data):
766 import audioop
767 return audioop.lin2ulaw(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000768
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000769 def _lin2adpcm(self, data):
770 import audioop
771 if not hasattr(self, '_adpcmstate'):
772 self._adpcmstate = None
Georg Brandl2095cfe2008-06-07 19:01:03 +0000773 data, self._adpcmstate = audioop.lin2adpcm(data, 2, self._adpcmstate)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000774 return data
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000775
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000776 def _ensure_header_written(self, datasize):
777 if not self._nframeswritten:
Sandro Tosibdd53542012-01-01 18:04:37 +0100778 if self._comptype in (b'ULAW', b'ulaw', b'ALAW', b'alaw', b'G722'):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000779 if not self._sampwidth:
780 self._sampwidth = 2
781 if self._sampwidth != 2:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000782 raise Error('sample width must be 2 when compressing '
Sandro Tosibdd53542012-01-01 18:04:37 +0100783 'with ulaw/ULAW, alaw/ALAW or G7.22 (ADPCM)')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000784 if not self._nchannels:
Collin Winterce36ad82007-08-30 01:19:48 +0000785 raise Error('# channels not specified')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000786 if not self._sampwidth:
Collin Winterce36ad82007-08-30 01:19:48 +0000787 raise Error('sample width not specified')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000788 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000789 raise Error('sampling rate not specified')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000790 self._write_header(datasize)
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000791
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000792 def _init_compression(self):
Georg Brandl2095cfe2008-06-07 19:01:03 +0000793 if self._comptype == b'G722':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000794 self._convert = self._lin2adpcm
Georg Brandl2095cfe2008-06-07 19:01:03 +0000795 elif self._comptype in (b'ulaw', b'ULAW'):
796 self._convert = self._lin2ulaw
797 elif self._comptype in (b'alaw', b'ALAW'):
798 self._convert = self._lin2alaw
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000799
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000800 def _write_header(self, initlength):
Georg Brandl2095cfe2008-06-07 19:01:03 +0000801 if self._aifc and self._comptype != b'NONE':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000802 self._init_compression()
Georg Brandl2095cfe2008-06-07 19:01:03 +0000803 self._file.write(b'FORM')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000804 if not self._nframes:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000805 self._nframes = initlength // (self._nchannels * self._sampwidth)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000806 self._datalength = self._nframes * self._nchannels * self._sampwidth
807 if self._datalength & 1:
808 self._datalength = self._datalength + 1
809 if self._aifc:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000810 if self._comptype in (b'ulaw', b'ULAW', b'alaw', b'ALAW'):
811 self._datalength = self._datalength // 2
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000812 if self._datalength & 1:
813 self._datalength = self._datalength + 1
Georg Brandl2095cfe2008-06-07 19:01:03 +0000814 elif self._comptype == b'G722':
815 self._datalength = (self._datalength + 3) // 4
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000816 if self._datalength & 1:
817 self._datalength = self._datalength + 1
Serhiy Storchaka84d28b42013-12-14 20:35:04 +0200818 try:
819 self._form_length_pos = self._file.tell()
820 except (AttributeError, OSError):
821 self._form_length_pos = None
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000822 commlength = self._write_form_length(self._datalength)
823 if self._aifc:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000824 self._file.write(b'AIFC')
825 self._file.write(b'FVER')
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100826 _write_ulong(self._file, 4)
827 _write_ulong(self._file, self._version)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000828 else:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000829 self._file.write(b'AIFF')
830 self._file.write(b'COMM')
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100831 _write_ulong(self._file, commlength)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000832 _write_short(self._file, self._nchannels)
Serhiy Storchaka84d28b42013-12-14 20:35:04 +0200833 if self._form_length_pos is not None:
834 self._nframes_pos = self._file.tell()
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100835 _write_ulong(self._file, self._nframes)
Serhiy Storchaka4b532592013-10-12 18:21:33 +0300836 if self._comptype in (b'ULAW', b'ulaw', b'ALAW', b'alaw', b'G722'):
837 _write_short(self._file, 8)
838 else:
839 _write_short(self._file, self._sampwidth * 8)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000840 _write_float(self._file, self._framerate)
841 if self._aifc:
842 self._file.write(self._comptype)
843 _write_string(self._file, self._compname)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000844 self._file.write(b'SSND')
Serhiy Storchaka84d28b42013-12-14 20:35:04 +0200845 if self._form_length_pos is not None:
846 self._ssnd_length_pos = self._file.tell()
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100847 _write_ulong(self._file, self._datalength + 8)
848 _write_ulong(self._file, 0)
849 _write_ulong(self._file, 0)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000850
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000851 def _write_form_length(self, datalength):
852 if self._aifc:
853 commlength = 18 + 5 + len(self._compname)
854 if commlength & 1:
855 commlength = commlength + 1
856 verslength = 12
857 else:
858 commlength = 18
859 verslength = 0
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100860 _write_ulong(self._file, 4 + verslength + self._marklength + \
861 8 + commlength + 16 + datalength)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000862 return commlength
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000863
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000864 def _patchheader(self):
865 curpos = self._file.tell()
866 if self._datawritten & 1:
867 datalength = self._datawritten + 1
Georg Brandl2095cfe2008-06-07 19:01:03 +0000868 self._file.write(b'\x00')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000869 else:
870 datalength = self._datawritten
871 if datalength == self._datalength and \
872 self._nframes == self._nframeswritten and \
873 self._marklength == 0:
874 self._file.seek(curpos, 0)
875 return
876 self._file.seek(self._form_length_pos, 0)
877 dummy = self._write_form_length(datalength)
878 self._file.seek(self._nframes_pos, 0)
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100879 _write_ulong(self._file, self._nframeswritten)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000880 self._file.seek(self._ssnd_length_pos, 0)
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100881 _write_ulong(self._file, datalength + 8)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000882 self._file.seek(curpos, 0)
883 self._nframes = self._nframeswritten
884 self._datalength = datalength
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000885
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000886 def _writemarkers(self):
887 if len(self._markers) == 0:
888 return
Georg Brandl2095cfe2008-06-07 19:01:03 +0000889 self._file.write(b'MARK')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000890 length = 2
891 for marker in self._markers:
892 id, pos, name = marker
893 length = length + len(name) + 1 + 6
894 if len(name) & 1 == 0:
895 length = length + 1
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100896 _write_ulong(self._file, length)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000897 self._marklength = length + 8
898 _write_short(self._file, len(self._markers))
899 for marker in self._markers:
900 id, pos, name = marker
901 _write_short(self._file, id)
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100902 _write_ulong(self._file, pos)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000903 _write_string(self._file, name)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000904
Fred Drake43161351999-06-22 21:23:23 +0000905def open(f, mode=None):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000906 if mode is None:
907 if hasattr(f, 'mode'):
908 mode = f.mode
909 else:
910 mode = 'rb'
911 if mode in ('r', 'rb'):
912 return Aifc_read(f)
913 elif mode in ('w', 'wb'):
914 return Aifc_write(f)
915 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000916 raise Error("mode must be 'r', 'rb', 'w', or 'wb'")
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000917
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000918openfp = open # B/W compatibility
Guido van Rossum36bb1811996-12-31 05:57:34 +0000919
920if __name__ == '__main__':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000921 import sys
922 if not sys.argv[1:]:
923 sys.argv.append('/usr/demos/data/audio/bach.aiff')
924 fn = sys.argv[1]
Serhiy Storchaka58b3ebf2013-08-25 19:16:01 +0300925 with open(fn, 'r') as f:
Serhiy Storchakab33baf12013-08-25 19:12:56 +0300926 print("Reading", fn)
927 print("nchannels =", f.getnchannels())
928 print("nframes =", f.getnframes())
929 print("sampwidth =", f.getsampwidth())
930 print("framerate =", f.getframerate())
931 print("comptype =", f.getcomptype())
932 print("compname =", f.getcompname())
933 if sys.argv[2:]:
934 gn = sys.argv[2]
935 print("Writing", gn)
Serhiy Storchaka58b3ebf2013-08-25 19:16:01 +0300936 with open(gn, 'w') as g:
Serhiy Storchakab33baf12013-08-25 19:12:56 +0300937 g.setparams(f.getparams())
938 while 1:
939 data = f.readframes(1024)
940 if not data:
941 break
942 g.writeframes(data)
Serhiy Storchakab33baf12013-08-25 19:12:56 +0300943 print("Done.")