blob: 3d2dc56de198cd7b0034d225b72f6bee128b7713 [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:
Serhiy Storchaka5affd232017-04-05 09:37:24 +0300152 raise EOFError from None
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:
Serhiy Storchaka5affd232017-04-05 09:37:24 +0300158 raise EOFError from None
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:
Serhiy Storchaka5affd232017-04-05 09:37:24 +0300164 raise EOFError from None
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:
Serhiy Storchaka5affd232017-04-05 09:37:24 +0300170 raise EOFError from None
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100171
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 Naoki5dc33ee2017-02-26 21:11:58 +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
Miss Islington (bot)1d927d42018-02-20 14:06:58 -0800325 self._ssnd_chunk = None
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000326 while 1:
327 self._ssnd_seek_needed = 1
328 try:
329 chunk = Chunk(self._file)
330 except EOFError:
331 break
332 chunkname = chunk.getname()
Georg Brandl2095cfe2008-06-07 19:01:03 +0000333 if chunkname == b'COMM':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000334 self._read_comm_chunk(chunk)
335 self._comm_chunk_read = 1
Georg Brandl2095cfe2008-06-07 19:01:03 +0000336 elif chunkname == b'SSND':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000337 self._ssnd_chunk = chunk
338 dummy = chunk.read(8)
339 self._ssnd_seek_needed = 0
Georg Brandl2095cfe2008-06-07 19:01:03 +0000340 elif chunkname == b'FVER':
Guido van Rossum820819c2002-08-12 22:11:28 +0000341 self._version = _read_ulong(chunk)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000342 elif chunkname == b'MARK':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000343 self._readmark(chunk)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000344 chunk.skip()
345 if not self._comm_chunk_read or not self._ssnd_chunk:
Collin Winterce36ad82007-08-30 01:19:48 +0000346 raise Error('COMM chunk and/or SSND chunk missing')
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000347
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000348 def __init__(self, f):
Georg Brandl2095cfe2008-06-07 19:01:03 +0000349 if isinstance(f, str):
Anthony Zhang03f68b62017-02-22 02:23:30 -0500350 file_object = builtins.open(f, 'rb')
351 try:
352 self.initfp(file_object)
353 except:
354 file_object.close()
355 raise
356 else:
357 # assume it is an open file object already
358 self.initfp(f)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000359
Serhiy Storchaka44c66c72012-12-29 22:54:49 +0200360 def __enter__(self):
361 return self
362
363 def __exit__(self, *args):
364 self.close()
365
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000366 #
367 # User visible methods.
368 #
369 def getfp(self):
370 return self._file
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000371
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000372 def rewind(self):
373 self._ssnd_seek_needed = 1
374 self._soundpos = 0
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000375
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000376 def close(self):
Serhiy Storchaka7e7a3db2015-04-10 13:24:41 +0300377 file = self._file
378 if file is not None:
379 self._file = None
380 file.close()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000381
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000382 def tell(self):
383 return self._soundpos
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000384
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000385 def getnchannels(self):
386 return self._nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000387
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000388 def getnframes(self):
389 return self._nframes
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000390
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000391 def getsampwidth(self):
392 return self._sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000393
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000394 def getframerate(self):
395 return self._framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000396
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000397 def getcomptype(self):
398 return self._comptype
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000399
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000400 def getcompname(self):
401 return self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000402
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000403## def getversion(self):
404## return self._version
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000405
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000406 def getparams(self):
R David Murray4d35e752013-07-25 16:12:01 -0400407 return _aifc_params(self.getnchannels(), self.getsampwidth(),
408 self.getframerate(), self.getnframes(),
409 self.getcomptype(), self.getcompname())
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000410
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000411 def getmarkers(self):
412 if len(self._markers) == 0:
413 return None
414 return self._markers
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000415
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000416 def getmark(self, id):
417 for marker in self._markers:
418 if id == marker[0]:
419 return marker
Georg Brandl2095cfe2008-06-07 19:01:03 +0000420 raise Error('marker {0!r} does not exist'.format(id))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000421
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000422 def setpos(self, pos):
423 if pos < 0 or pos > self._nframes:
Collin Winterce36ad82007-08-30 01:19:48 +0000424 raise Error('position not in range')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000425 self._soundpos = pos
426 self._ssnd_seek_needed = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000427
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000428 def readframes(self, nframes):
429 if self._ssnd_seek_needed:
430 self._ssnd_chunk.seek(0)
431 dummy = self._ssnd_chunk.read(8)
432 pos = self._soundpos * self._framesize
433 if pos:
Guido van Rossum2663c132000-03-07 15:19:31 +0000434 self._ssnd_chunk.seek(pos + 8)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000435 self._ssnd_seek_needed = 0
436 if nframes == 0:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000437 return b''
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000438 data = self._ssnd_chunk.read(nframes * self._framesize)
439 if self._convert and data:
440 data = self._convert(data)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000441 self._soundpos = self._soundpos + len(data) // (self._nchannels
442 * self._sampwidth)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000443 return data
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000444
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000445 #
446 # Internal methods.
447 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000448
Georg Brandl2095cfe2008-06-07 19:01:03 +0000449 def _alaw2lin(self, data):
450 import audioop
451 return audioop.alaw2lin(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000452
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000453 def _ulaw2lin(self, data):
454 import audioop
455 return audioop.ulaw2lin(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000456
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000457 def _adpcm2lin(self, data):
458 import audioop
459 if not hasattr(self, '_adpcmstate'):
460 # first time
461 self._adpcmstate = None
Georg Brandl2095cfe2008-06-07 19:01:03 +0000462 data, self._adpcmstate = audioop.adpcm2lin(data, 2, self._adpcmstate)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000463 return data
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000464
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000465 def _read_comm_chunk(self, chunk):
466 self._nchannels = _read_short(chunk)
467 self._nframes = _read_long(chunk)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000468 self._sampwidth = (_read_short(chunk) + 7) // 8
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000469 self._framerate = int(_read_float(chunk))
470 self._framesize = self._nchannels * self._sampwidth
471 if self._aifc:
472 #DEBUG: SGI's soundeditor produces a bad size :-(
473 kludge = 0
474 if chunk.chunksize == 18:
475 kludge = 1
Ezio Melotti48d578c2012-03-12 23:57:18 +0200476 warnings.warn('Warning: bad COMM chunk size')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000477 chunk.chunksize = 23
478 #DEBUG end
479 self._comptype = chunk.read(4)
480 #DEBUG start
481 if kludge:
482 length = ord(chunk.file.read(1))
483 if length & 1 == 0:
484 length = length + 1
485 chunk.chunksize = chunk.chunksize + length
486 chunk.file.seek(-1, 1)
487 #DEBUG end
488 self._compname = _read_string(chunk)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000489 if self._comptype != b'NONE':
490 if self._comptype == b'G722':
491 self._convert = self._adpcm2lin
Georg Brandl2095cfe2008-06-07 19:01:03 +0000492 elif self._comptype in (b'ulaw', b'ULAW'):
493 self._convert = self._ulaw2lin
Georg Brandl2095cfe2008-06-07 19:01:03 +0000494 elif self._comptype in (b'alaw', b'ALAW'):
495 self._convert = self._alaw2lin
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000496 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000497 raise Error('unsupported compression type')
Serhiy Storchaka4b532592013-10-12 18:21:33 +0300498 self._sampwidth = 2
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000499 else:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000500 self._comptype = b'NONE'
501 self._compname = b'not compressed'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000502
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000503 def _readmark(self, chunk):
504 nmarkers = _read_short(chunk)
505 # Some files appear to contain invalid counts.
506 # Cope with this by testing for EOF.
507 try:
508 for i in range(nmarkers):
509 id = _read_short(chunk)
510 pos = _read_long(chunk)
511 name = _read_string(chunk)
512 if pos or name:
513 # some files appear to have
514 # dummy markers consisting of
515 # a position 0 and name ''
516 self._markers.append((id, pos, name))
517 except EOFError:
Ezio Melotti48d578c2012-03-12 23:57:18 +0200518 w = ('Warning: MARK chunk contains only %s marker%s instead of %s' %
519 (len(self._markers), '' if len(self._markers) == 1 else 's',
520 nmarkers))
521 warnings.warn(w)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000522
Guido van Rossumd3166071993-05-24 14:16:22 +0000523class Aifc_write:
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000524 # Variables used in this class:
525 #
526 # These variables are user settable through appropriate methods
527 # of this class:
528 # _file -- the open file with methods write(), close(), tell(), seek()
529 # set through the __init__() method
530 # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
531 # set through the setcomptype() or setparams() method
532 # _compname -- the human-readable AIFF-C compression type
533 # set through the setcomptype() or setparams() method
534 # _nchannels -- the number of audio channels
535 # set through the setnchannels() or setparams() method
536 # _sampwidth -- the number of bytes per audio sample
537 # set through the setsampwidth() or setparams() method
538 # _framerate -- the sampling frequency
539 # set through the setframerate() or setparams() method
540 # _nframes -- the number of audio frames written to the header
541 # set through the setnframes() or setparams() method
542 # _aifc -- whether we're writing an AIFF-C file or an AIFF file
543 # set through the aifc() method, reset through the
544 # aiff() method
545 #
546 # These variables are used internally only:
547 # _version -- the AIFF-C version number
548 # _comp -- the compressor from builtin module cl
549 # _nframeswritten -- the number of audio frames actually written
550 # _datalength -- the size of the audio samples written to the header
551 # _datawritten -- the size of the audio samples actually written
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000552
INADA Naoki5dc33ee2017-02-26 21:11:58 +0900553 _file = None # Set here since __del__ checks it
554
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000555 def __init__(self, f):
Georg Brandl2095cfe2008-06-07 19:01:03 +0000556 if isinstance(f, str):
Anthony Zhang03f68b62017-02-22 02:23:30 -0500557 file_object = builtins.open(f, 'wb')
558 try:
559 self.initfp(file_object)
560 except:
561 file_object.close()
562 raise
563
564 # treat .aiff file extensions as non-compressed audio
565 if f.endswith('.aiff'):
566 self._aifc = 0
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000567 else:
Anthony Zhang03f68b62017-02-22 02:23:30 -0500568 # assume it is an open file object already
569 self.initfp(f)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000570
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000571 def initfp(self, file):
572 self._file = file
573 self._version = _AIFC_version
Georg Brandl2095cfe2008-06-07 19:01:03 +0000574 self._comptype = b'NONE'
575 self._compname = b'not compressed'
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000576 self._convert = None
577 self._nchannels = 0
578 self._sampwidth = 0
579 self._framerate = 0
580 self._nframes = 0
581 self._nframeswritten = 0
582 self._datawritten = 0
583 self._datalength = 0
584 self._markers = []
585 self._marklength = 0
586 self._aifc = 1 # AIFF-C is default
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000587
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000588 def __del__(self):
Sandro Tosi70efbef2012-01-01 22:53:08 +0100589 self.close()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000590
Serhiy Storchaka44c66c72012-12-29 22:54:49 +0200591 def __enter__(self):
592 return self
593
594 def __exit__(self, *args):
595 self.close()
596
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000597 #
598 # User visible methods.
599 #
600 def aiff(self):
601 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000602 raise Error('cannot change parameters after starting to write')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000603 self._aifc = 0
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000604
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000605 def aifc(self):
606 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000607 raise Error('cannot change parameters after starting to write')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000608 self._aifc = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000609
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000610 def setnchannels(self, nchannels):
611 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000612 raise Error('cannot change parameters after starting to write')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000613 if nchannels < 1:
Collin Winterce36ad82007-08-30 01:19:48 +0000614 raise Error('bad # of channels')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000615 self._nchannels = nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000616
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000617 def getnchannels(self):
618 if not self._nchannels:
Collin Winterce36ad82007-08-30 01:19:48 +0000619 raise Error('number of channels not set')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000620 return self._nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000621
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000622 def setsampwidth(self, sampwidth):
623 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000624 raise Error('cannot change parameters after starting to write')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000625 if sampwidth < 1 or sampwidth > 4:
Collin Winterce36ad82007-08-30 01:19:48 +0000626 raise Error('bad sample width')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000627 self._sampwidth = sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000628
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000629 def getsampwidth(self):
630 if not self._sampwidth:
Collin Winterce36ad82007-08-30 01:19:48 +0000631 raise Error('sample width not set')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000632 return self._sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000633
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000634 def setframerate(self, framerate):
635 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000636 raise Error('cannot change parameters after starting to write')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000637 if framerate <= 0:
Collin Winterce36ad82007-08-30 01:19:48 +0000638 raise Error('bad frame rate')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000639 self._framerate = framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000640
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000641 def getframerate(self):
642 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000643 raise Error('frame rate not set')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000644 return self._framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000645
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000646 def setnframes(self, nframes):
647 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000648 raise Error('cannot change parameters after starting to write')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000649 self._nframes = nframes
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000650
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000651 def getnframes(self):
652 return self._nframeswritten
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000653
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000654 def setcomptype(self, comptype, compname):
655 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000656 raise Error('cannot change parameters after starting to write')
Georg Brandl2095cfe2008-06-07 19:01:03 +0000657 if comptype not in (b'NONE', b'ulaw', b'ULAW',
658 b'alaw', b'ALAW', b'G722'):
Collin Winterce36ad82007-08-30 01:19:48 +0000659 raise Error('unsupported compression type')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000660 self._comptype = comptype
661 self._compname = compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000662
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000663 def getcomptype(self):
664 return self._comptype
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000665
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000666 def getcompname(self):
667 return self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000668
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000669## def setversion(self, version):
670## if self._nframeswritten:
671## raise Error, 'cannot change parameters after starting to write'
672## self._version = version
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000673
Guido van Rossum1bc535d2007-05-15 18:46:22 +0000674 def setparams(self, params):
675 nchannels, sampwidth, framerate, nframes, comptype, compname = params
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000676 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000677 raise Error('cannot change parameters after starting to write')
Georg Brandl2095cfe2008-06-07 19:01:03 +0000678 if comptype not in (b'NONE', b'ulaw', b'ULAW',
679 b'alaw', b'ALAW', b'G722'):
Collin Winterce36ad82007-08-30 01:19:48 +0000680 raise Error('unsupported compression type')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000681 self.setnchannels(nchannels)
682 self.setsampwidth(sampwidth)
683 self.setframerate(framerate)
684 self.setnframes(nframes)
685 self.setcomptype(comptype, compname)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000686
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000687 def getparams(self):
688 if not self._nchannels or not self._sampwidth or not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000689 raise Error('not all parameters set')
R David Murray4d35e752013-07-25 16:12:01 -0400690 return _aifc_params(self._nchannels, self._sampwidth, self._framerate,
691 self._nframes, self._comptype, self._compname)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000692
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000693 def setmark(self, id, pos, name):
694 if id <= 0:
Collin Winterce36ad82007-08-30 01:19:48 +0000695 raise Error('marker ID must be > 0')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000696 if pos < 0:
Collin Winterce36ad82007-08-30 01:19:48 +0000697 raise Error('marker position must be >= 0')
Sandro Tosi70efbef2012-01-01 22:53:08 +0100698 if not isinstance(name, bytes):
699 raise Error('marker name must be bytes')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000700 for i in range(len(self._markers)):
701 if id == self._markers[i][0]:
702 self._markers[i] = id, pos, name
703 return
704 self._markers.append((id, pos, name))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000705
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000706 def getmark(self, id):
707 for marker in self._markers:
708 if id == marker[0]:
709 return marker
Georg Brandl2095cfe2008-06-07 19:01:03 +0000710 raise Error('marker {0!r} does not exist'.format(id))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000711
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000712 def getmarkers(self):
713 if len(self._markers) == 0:
714 return None
715 return self._markers
Tim Peters146965a2001-01-14 18:09:23 +0000716
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000717 def tell(self):
718 return self._nframeswritten
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000719
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000720 def writeframesraw(self, data):
Serhiy Storchaka452bab42013-11-16 14:01:31 +0200721 if not isinstance(data, (bytes, bytearray)):
722 data = memoryview(data).cast('B')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000723 self._ensure_header_written(len(data))
Georg Brandl2095cfe2008-06-07 19:01:03 +0000724 nframes = len(data) // (self._sampwidth * self._nchannels)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000725 if self._convert:
726 data = self._convert(data)
727 self._file.write(data)
728 self._nframeswritten = self._nframeswritten + nframes
729 self._datawritten = self._datawritten + len(data)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000730
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000731 def writeframes(self, data):
732 self.writeframesraw(data)
733 if self._nframeswritten != self._nframes or \
734 self._datalength != self._datawritten:
735 self._patchheader()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000736
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000737 def close(self):
Serhiy Storchaka051722d2012-12-29 22:30:56 +0200738 if self._file is None:
739 return
740 try:
Sandro Tosi70efbef2012-01-01 22:53:08 +0100741 self._ensure_header_written(0)
742 if self._datawritten & 1:
743 # quick pad to even size
744 self._file.write(b'\x00')
745 self._datawritten = self._datawritten + 1
746 self._writemarkers()
747 if self._nframeswritten != self._nframes or \
748 self._datalength != self._datawritten or \
749 self._marklength:
750 self._patchheader()
Serhiy Storchaka051722d2012-12-29 22:30:56 +0200751 finally:
Sandro Tosi70efbef2012-01-01 22:53:08 +0100752 # Prevent ref cycles
753 self._convert = None
Serhiy Storchaka051722d2012-12-29 22:30:56 +0200754 f = self._file
Sandro Tosi70efbef2012-01-01 22:53:08 +0100755 self._file = None
Serhiy Storchaka051722d2012-12-29 22:30:56 +0200756 f.close()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000757
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000758 #
759 # Internal methods.
760 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000761
Georg Brandl2095cfe2008-06-07 19:01:03 +0000762 def _lin2alaw(self, data):
763 import audioop
764 return audioop.lin2alaw(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000765
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000766 def _lin2ulaw(self, data):
767 import audioop
768 return audioop.lin2ulaw(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000769
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000770 def _lin2adpcm(self, data):
771 import audioop
772 if not hasattr(self, '_adpcmstate'):
773 self._adpcmstate = None
Georg Brandl2095cfe2008-06-07 19:01:03 +0000774 data, self._adpcmstate = audioop.lin2adpcm(data, 2, self._adpcmstate)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000775 return data
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000776
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000777 def _ensure_header_written(self, datasize):
778 if not self._nframeswritten:
Sandro Tosibdd53542012-01-01 18:04:37 +0100779 if self._comptype in (b'ULAW', b'ulaw', b'ALAW', b'alaw', b'G722'):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000780 if not self._sampwidth:
781 self._sampwidth = 2
782 if self._sampwidth != 2:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000783 raise Error('sample width must be 2 when compressing '
Sandro Tosibdd53542012-01-01 18:04:37 +0100784 'with ulaw/ULAW, alaw/ALAW or G7.22 (ADPCM)')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000785 if not self._nchannels:
Collin Winterce36ad82007-08-30 01:19:48 +0000786 raise Error('# channels not specified')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000787 if not self._sampwidth:
Collin Winterce36ad82007-08-30 01:19:48 +0000788 raise Error('sample width not specified')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000789 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000790 raise Error('sampling rate not specified')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000791 self._write_header(datasize)
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000792
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000793 def _init_compression(self):
Georg Brandl2095cfe2008-06-07 19:01:03 +0000794 if self._comptype == b'G722':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000795 self._convert = self._lin2adpcm
Georg Brandl2095cfe2008-06-07 19:01:03 +0000796 elif self._comptype in (b'ulaw', b'ULAW'):
797 self._convert = self._lin2ulaw
798 elif self._comptype in (b'alaw', b'ALAW'):
799 self._convert = self._lin2alaw
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000800
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000801 def _write_header(self, initlength):
Georg Brandl2095cfe2008-06-07 19:01:03 +0000802 if self._aifc and self._comptype != b'NONE':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000803 self._init_compression()
Georg Brandl2095cfe2008-06-07 19:01:03 +0000804 self._file.write(b'FORM')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000805 if not self._nframes:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000806 self._nframes = initlength // (self._nchannels * self._sampwidth)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000807 self._datalength = self._nframes * self._nchannels * self._sampwidth
808 if self._datalength & 1:
809 self._datalength = self._datalength + 1
810 if self._aifc:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000811 if self._comptype in (b'ulaw', b'ULAW', b'alaw', b'ALAW'):
812 self._datalength = self._datalength // 2
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000813 if self._datalength & 1:
814 self._datalength = self._datalength + 1
Georg Brandl2095cfe2008-06-07 19:01:03 +0000815 elif self._comptype == b'G722':
816 self._datalength = (self._datalength + 3) // 4
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000817 if self._datalength & 1:
818 self._datalength = self._datalength + 1
Serhiy Storchaka84d28b42013-12-14 20:35:04 +0200819 try:
820 self._form_length_pos = self._file.tell()
821 except (AttributeError, OSError):
822 self._form_length_pos = None
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000823 commlength = self._write_form_length(self._datalength)
824 if self._aifc:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000825 self._file.write(b'AIFC')
826 self._file.write(b'FVER')
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100827 _write_ulong(self._file, 4)
828 _write_ulong(self._file, self._version)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000829 else:
Georg Brandl2095cfe2008-06-07 19:01:03 +0000830 self._file.write(b'AIFF')
831 self._file.write(b'COMM')
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100832 _write_ulong(self._file, commlength)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000833 _write_short(self._file, self._nchannels)
Serhiy Storchaka84d28b42013-12-14 20:35:04 +0200834 if self._form_length_pos is not None:
835 self._nframes_pos = self._file.tell()
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100836 _write_ulong(self._file, self._nframes)
Serhiy Storchaka4b532592013-10-12 18:21:33 +0300837 if self._comptype in (b'ULAW', b'ulaw', b'ALAW', b'alaw', b'G722'):
838 _write_short(self._file, 8)
839 else:
840 _write_short(self._file, self._sampwidth * 8)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000841 _write_float(self._file, self._framerate)
842 if self._aifc:
843 self._file.write(self._comptype)
844 _write_string(self._file, self._compname)
Georg Brandl2095cfe2008-06-07 19:01:03 +0000845 self._file.write(b'SSND')
Serhiy Storchaka84d28b42013-12-14 20:35:04 +0200846 if self._form_length_pos is not None:
847 self._ssnd_length_pos = self._file.tell()
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100848 _write_ulong(self._file, self._datalength + 8)
849 _write_ulong(self._file, 0)
850 _write_ulong(self._file, 0)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000851
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000852 def _write_form_length(self, datalength):
853 if self._aifc:
854 commlength = 18 + 5 + len(self._compname)
855 if commlength & 1:
856 commlength = commlength + 1
857 verslength = 12
858 else:
859 commlength = 18
860 verslength = 0
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100861 _write_ulong(self._file, 4 + verslength + self._marklength + \
862 8 + commlength + 16 + datalength)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000863 return commlength
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000864
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000865 def _patchheader(self):
866 curpos = self._file.tell()
867 if self._datawritten & 1:
868 datalength = self._datawritten + 1
Georg Brandl2095cfe2008-06-07 19:01:03 +0000869 self._file.write(b'\x00')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000870 else:
871 datalength = self._datawritten
872 if datalength == self._datalength and \
873 self._nframes == self._nframeswritten and \
874 self._marklength == 0:
875 self._file.seek(curpos, 0)
876 return
877 self._file.seek(self._form_length_pos, 0)
878 dummy = self._write_form_length(datalength)
879 self._file.seek(self._nframes_pos, 0)
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100880 _write_ulong(self._file, self._nframeswritten)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000881 self._file.seek(self._ssnd_length_pos, 0)
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100882 _write_ulong(self._file, datalength + 8)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000883 self._file.seek(curpos, 0)
884 self._nframes = self._nframeswritten
885 self._datalength = datalength
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000886
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000887 def _writemarkers(self):
888 if len(self._markers) == 0:
889 return
Georg Brandl2095cfe2008-06-07 19:01:03 +0000890 self._file.write(b'MARK')
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000891 length = 2
892 for marker in self._markers:
893 id, pos, name = marker
894 length = length + len(name) + 1 + 6
895 if len(name) & 1 == 0:
896 length = length + 1
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100897 _write_ulong(self._file, length)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000898 self._marklength = length + 8
899 _write_short(self._file, len(self._markers))
900 for marker in self._markers:
901 id, pos, name = marker
902 _write_short(self._file, id)
Antoine Pitrou03757ec2012-01-17 17:13:04 +0100903 _write_ulong(self._file, pos)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000904 _write_string(self._file, name)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000905
Fred Drake43161351999-06-22 21:23:23 +0000906def open(f, mode=None):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000907 if mode is None:
908 if hasattr(f, 'mode'):
909 mode = f.mode
910 else:
911 mode = 'rb'
912 if mode in ('r', 'rb'):
913 return Aifc_read(f)
914 elif mode in ('w', 'wb'):
915 return Aifc_write(f)
916 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000917 raise Error("mode must be 'r', 'rb', 'w', or 'wb'")
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000918
Brian Curtin9f914a02017-11-10 11:38:25 -0500919def openfp(f, mode=None):
920 warnings.warn("aifc.openfp is deprecated since Python 3.7. "
921 "Use aifc.open instead.", DeprecationWarning, stacklevel=2)
922 return open(f, mode=mode)
Guido van Rossum36bb1811996-12-31 05:57:34 +0000923
924if __name__ == '__main__':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000925 import sys
926 if not sys.argv[1:]:
927 sys.argv.append('/usr/demos/data/audio/bach.aiff')
928 fn = sys.argv[1]
Serhiy Storchaka58b3ebf2013-08-25 19:16:01 +0300929 with open(fn, 'r') as f:
Serhiy Storchakab33baf12013-08-25 19:12:56 +0300930 print("Reading", fn)
931 print("nchannels =", f.getnchannels())
932 print("nframes =", f.getnframes())
933 print("sampwidth =", f.getsampwidth())
934 print("framerate =", f.getframerate())
935 print("comptype =", f.getcomptype())
936 print("compname =", f.getcompname())
937 if sys.argv[2:]:
938 gn = sys.argv[2]
939 print("Writing", gn)
Serhiy Storchaka58b3ebf2013-08-25 19:16:01 +0300940 with open(gn, 'w') as g:
Serhiy Storchakab33baf12013-08-25 19:12:56 +0300941 g.setparams(f.getparams())
942 while 1:
943 data = f.readframes(1024)
944 if not data:
945 break
946 g.writeframes(data)
Serhiy Storchakab33baf12013-08-25 19:12:56 +0300947 print("Done.")