ยปCore Development>Code coverage>Lib/aifc.py

Python code coverage for Lib/aifc.py

#countcontent
1n/a"""Stuff to parse AIFF-C and AIFF files.
2n/a
3n/aUnless explicitly stated otherwise, the description below is true
4n/aboth for AIFF-C files and AIFF files.
5n/a
6n/aAn AIFF-C file has the following structure.
7n/a
8n/a +-----------------+
9n/a | FORM |
10n/a +-----------------+
11n/a | <size> |
12n/a +----+------------+
13n/a | | AIFC |
14n/a | +------------+
15n/a | | <chunks> |
16n/a | | . |
17n/a | | . |
18n/a | | . |
19n/a +----+------------+
20n/a
21n/aAn AIFF file has the string "AIFF" instead of "AIFC".
22n/a
23n/aA chunk consists of an identifier (4 bytes) followed by a size (4 bytes,
24n/abig endian order), followed by the data. The size field does not include
25n/athe size of the 8 byte header.
26n/a
27n/aThe following chunk types are recognized.
28n/a
29n/a FVER
30n/a <version number of AIFF-C defining document> (AIFF-C only).
31n/a MARK
32n/a <# of markers> (2 bytes)
33n/a list of markers:
34n/a <marker ID> (2 bytes, must be > 0)
35n/a <position> (4 bytes)
36n/a <marker name> ("pstring")
37n/a COMM
38n/a <# of channels> (2 bytes)
39n/a <# of sound frames> (4 bytes)
40n/a <size of the samples> (2 bytes)
41n/a <sampling frequency> (10 bytes, IEEE 80-bit extended
42n/a floating point)
43n/a in AIFF-C files only:
44n/a <compression type> (4 bytes)
45n/a <human-readable version of compression type> ("pstring")
46n/a SSND
47n/a <offset> (4 bytes, not used by this program)
48n/a <blocksize> (4 bytes, not used by this program)
49n/a <sound data>
50n/a
51n/aA pstring consists of 1 byte length, a string of characters, and 0 or 1
52n/abyte pad to make the total length even.
53n/a
54n/aUsage.
55n/a
56n/aReading AIFF files:
57n/a f = aifc.open(file, 'r')
58n/awhere file is either the name of a file or an open file pointer.
59n/aThe open file pointer must have methods read(), seek(), and close().
60n/aIn some types of audio files, if the setpos() method is not used,
61n/athe seek() method is not necessary.
62n/a
63n/aThis returns an instance of a class with the following public methods:
64n/a getnchannels() -- returns number of audio channels (1 for
65n/a mono, 2 for stereo)
66n/a getsampwidth() -- returns sample width in bytes
67n/a getframerate() -- returns sampling frequency
68n/a getnframes() -- returns number of audio frames
69n/a getcomptype() -- returns compression type ('NONE' for AIFF files)
70n/a getcompname() -- returns human-readable version of
71n/a compression type ('not compressed' for AIFF files)
72n/a getparams() -- returns a namedtuple consisting of all of the
73n/a above in the above order
74n/a getmarkers() -- get the list of marks in the audio file or None
75n/a if there are no marks
76n/a getmark(id) -- get mark with the specified id (raises an error
77n/a if the mark does not exist)
78n/a readframes(n) -- returns at most n frames of audio
79n/a rewind() -- rewind to the beginning of the audio stream
80n/a setpos(pos) -- seek to the specified position
81n/a tell() -- return the current position
82n/a close() -- close the instance (make it unusable)
83n/aThe position returned by tell(), the position given to setpos() and
84n/athe position of marks are all compatible and have nothing to do with
85n/athe actual position in the file.
86n/aThe close() method is called automatically when the class instance
87n/ais destroyed.
88n/a
89n/aWriting AIFF files:
90n/a f = aifc.open(file, 'w')
91n/awhere file is either the name of a file or an open file pointer.
92n/aThe open file pointer must have methods write(), tell(), seek(), and
93n/aclose().
94n/a
95n/aThis returns an instance of a class with the following public methods:
96n/a aiff() -- create an AIFF file (AIFF-C default)
97n/a aifc() -- create an AIFF-C file
98n/a setnchannels(n) -- set the number of channels
99n/a setsampwidth(n) -- set the sample width
100n/a setframerate(n) -- set the frame rate
101n/a setnframes(n) -- set the number of frames
102n/a setcomptype(type, name)
103n/a -- set the compression type and the
104n/a human-readable compression type
105n/a setparams(tuple)
106n/a -- set all parameters at once
107n/a setmark(id, pos, name)
108n/a -- add specified mark to the list of marks
109n/a tell() -- return current position in output file (useful
110n/a in combination with setmark())
111n/a writeframesraw(data)
112n/a -- write audio frames without pathing up the
113n/a file header
114n/a writeframes(data)
115n/a -- write audio frames and patch up the file header
116n/a close() -- patch up the file header and close the
117n/a output file
118n/aYou should set the parameters before the first writeframesraw or
119n/awriteframes. The total number of frames does not need to be set,
120n/abut when it is set to the correct value, the header does not have to
121n/abe patched up.
122n/aIt is best to first set all parameters, perhaps possibly the
123n/acompression type, and then write audio frames using writeframesraw.
124n/aWhen all frames have been written, either call writeframes(b'') or
125n/aclose() to patch up the sizes in the header.
126n/aMarks can be added anytime. If there are any marks, you must call
127n/aclose() after all frames have been written.
128n/aThe close() method is called automatically when the class instance
129n/ais destroyed.
130n/a
131n/aWhen a file is opened with the extension '.aiff', an AIFF file is
132n/awritten, otherwise an AIFF-C file is written. This default can be
133n/achanged by calling aiff() or aifc() before the first writeframes or
134n/awriteframesraw.
135n/a"""
136n/a
137n/aimport struct
138n/aimport builtins
139n/aimport warnings
140n/a
141n/a__all__ = ["Error", "open", "openfp"]
142n/a
143n/aclass Error(Exception):
144n/a pass
145n/a
146n/a_AIFC_version = 0xA2805140 # Version 1 of AIFF-C
147n/a
148n/adef _read_long(file):
149n/a try:
150n/a return struct.unpack('>l', file.read(4))[0]
151n/a except struct.error:
152n/a raise EOFError
153n/a
154n/adef _read_ulong(file):
155n/a try:
156n/a return struct.unpack('>L', file.read(4))[0]
157n/a except struct.error:
158n/a raise EOFError
159n/a
160n/adef _read_short(file):
161n/a try:
162n/a return struct.unpack('>h', file.read(2))[0]
163n/a except struct.error:
164n/a raise EOFError
165n/a
166n/adef _read_ushort(file):
167n/a try:
168n/a return struct.unpack('>H', file.read(2))[0]
169n/a except struct.error:
170n/a raise EOFError
171n/a
172n/adef _read_string(file):
173n/a length = ord(file.read(1))
174n/a if length == 0:
175n/a data = b''
176n/a else:
177n/a data = file.read(length)
178n/a if length & 1 == 0:
179n/a dummy = file.read(1)
180n/a return data
181n/a
182n/a_HUGE_VAL = 1.79769313486231e+308 # See <limits.h>
183n/a
184n/adef _read_float(f): # 10 bytes
185n/a expon = _read_short(f) # 2 bytes
186n/a sign = 1
187n/a if expon < 0:
188n/a sign = -1
189n/a expon = expon + 0x8000
190n/a himant = _read_ulong(f) # 4 bytes
191n/a lomant = _read_ulong(f) # 4 bytes
192n/a if expon == himant == lomant == 0:
193n/a f = 0.0
194n/a elif expon == 0x7FFF:
195n/a f = _HUGE_VAL
196n/a else:
197n/a expon = expon - 16383
198n/a f = (himant * 0x100000000 + lomant) * pow(2.0, expon - 63)
199n/a return sign * f
200n/a
201n/adef _write_short(f, x):
202n/a f.write(struct.pack('>h', x))
203n/a
204n/adef _write_ushort(f, x):
205n/a f.write(struct.pack('>H', x))
206n/a
207n/adef _write_long(f, x):
208n/a f.write(struct.pack('>l', x))
209n/a
210n/adef _write_ulong(f, x):
211n/a f.write(struct.pack('>L', x))
212n/a
213n/adef _write_string(f, s):
214n/a if len(s) > 255:
215n/a raise ValueError("string exceeds maximum pstring length")
216n/a f.write(struct.pack('B', len(s)))
217n/a f.write(s)
218n/a if len(s) & 1 == 0:
219n/a f.write(b'\x00')
220n/a
221n/adef _write_float(f, x):
222n/a import math
223n/a if x < 0:
224n/a sign = 0x8000
225n/a x = x * -1
226n/a else:
227n/a sign = 0
228n/a if x == 0:
229n/a expon = 0
230n/a himant = 0
231n/a lomant = 0
232n/a else:
233n/a fmant, expon = math.frexp(x)
234n/a if expon > 16384 or fmant >= 1 or fmant != fmant: # Infinity or NaN
235n/a expon = sign|0x7FFF
236n/a himant = 0
237n/a lomant = 0
238n/a else: # Finite
239n/a expon = expon + 16382
240n/a if expon < 0: # denormalized
241n/a fmant = math.ldexp(fmant, expon)
242n/a expon = 0
243n/a expon = expon | sign
244n/a fmant = math.ldexp(fmant, 32)
245n/a fsmant = math.floor(fmant)
246n/a himant = int(fsmant)
247n/a fmant = math.ldexp(fmant - fsmant, 32)
248n/a fsmant = math.floor(fmant)
249n/a lomant = int(fsmant)
250n/a _write_ushort(f, expon)
251n/a _write_ulong(f, himant)
252n/a _write_ulong(f, lomant)
253n/a
254n/afrom chunk import Chunk
255n/afrom collections import namedtuple
256n/a
257n/a_aifc_params = namedtuple('_aifc_params',
258n/a 'nchannels sampwidth framerate nframes comptype compname')
259n/a
260n/a_aifc_params.nchannels.__doc__ = 'Number of audio channels (1 for mono, 2 for stereo)'
261n/a_aifc_params.sampwidth.__doc__ = 'Sample width in bytes'
262n/a_aifc_params.framerate.__doc__ = 'Sampling frequency'
263n/a_aifc_params.nframes.__doc__ = 'Number of audio frames'
264n/a_aifc_params.comptype.__doc__ = 'Compression type ("NONE" for AIFF files)'
265n/a_aifc_params.compname.__doc__ = ("""\
266n/aA human-readable version of the compression type
267n/a('not compressed' for AIFF files)""")
268n/a
269n/a
270n/aclass Aifc_read:
271n/a # Variables used in this class:
272n/a #
273n/a # These variables are available to the user though appropriate
274n/a # methods of this class:
275n/a # _file -- the open file with methods read(), close(), and seek()
276n/a # set through the __init__() method
277n/a # _nchannels -- the number of audio channels
278n/a # available through the getnchannels() method
279n/a # _nframes -- the number of audio frames
280n/a # available through the getnframes() method
281n/a # _sampwidth -- the number of bytes per audio sample
282n/a # available through the getsampwidth() method
283n/a # _framerate -- the sampling frequency
284n/a # available through the getframerate() method
285n/a # _comptype -- the AIFF-C compression type ('NONE' if AIFF)
286n/a # available through the getcomptype() method
287n/a # _compname -- the human-readable AIFF-C compression type
288n/a # available through the getcomptype() method
289n/a # _markers -- the marks in the audio file
290n/a # available through the getmarkers() and getmark()
291n/a # methods
292n/a # _soundpos -- the position in the audio stream
293n/a # available through the tell() method, set through the
294n/a # setpos() method
295n/a #
296n/a # These variables are used internally only:
297n/a # _version -- the AIFF-C version number
298n/a # _decomp -- the decompressor from builtin module cl
299n/a # _comm_chunk_read -- 1 iff the COMM chunk has been read
300n/a # _aifc -- 1 iff reading an AIFF-C file
301n/a # _ssnd_seek_needed -- 1 iff positioned correctly in audio
302n/a # file for readframes()
303n/a # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk
304n/a # _framesize -- size of one frame in the file
305n/a
306n/a def initfp(self, file):
307n/a self._version = 0
308n/a self._convert = None
309n/a self._markers = []
310n/a self._soundpos = 0
311n/a self._file = file
312n/a chunk = Chunk(file)
313n/a if chunk.getname() != b'FORM':
314n/a raise Error('file does not start with FORM id')
315n/a formdata = chunk.read(4)
316n/a if formdata == b'AIFF':
317n/a self._aifc = 0
318n/a elif formdata == b'AIFC':
319n/a self._aifc = 1
320n/a else:
321n/a raise Error('not an AIFF or AIFF-C file')
322n/a self._comm_chunk_read = 0
323n/a while 1:
324n/a self._ssnd_seek_needed = 1
325n/a try:
326n/a chunk = Chunk(self._file)
327n/a except EOFError:
328n/a break
329n/a chunkname = chunk.getname()
330n/a if chunkname == b'COMM':
331n/a self._read_comm_chunk(chunk)
332n/a self._comm_chunk_read = 1
333n/a elif chunkname == b'SSND':
334n/a self._ssnd_chunk = chunk
335n/a dummy = chunk.read(8)
336n/a self._ssnd_seek_needed = 0
337n/a elif chunkname == b'FVER':
338n/a self._version = _read_ulong(chunk)
339n/a elif chunkname == b'MARK':
340n/a self._readmark(chunk)
341n/a chunk.skip()
342n/a if not self._comm_chunk_read or not self._ssnd_chunk:
343n/a raise Error('COMM chunk and/or SSND chunk missing')
344n/a
345n/a def __init__(self, f):
346n/a if isinstance(f, str):
347n/a f = builtins.open(f, 'rb')
348n/a # else, assume it is an open file object already
349n/a self.initfp(f)
350n/a
351n/a def __enter__(self):
352n/a return self
353n/a
354n/a def __exit__(self, *args):
355n/a self.close()
356n/a
357n/a #
358n/a # User visible methods.
359n/a #
360n/a def getfp(self):
361n/a return self._file
362n/a
363n/a def rewind(self):
364n/a self._ssnd_seek_needed = 1
365n/a self._soundpos = 0
366n/a
367n/a def close(self):
368n/a file = self._file
369n/a if file is not None:
370n/a self._file = None
371n/a file.close()
372n/a
373n/a def tell(self):
374n/a return self._soundpos
375n/a
376n/a def getnchannels(self):
377n/a return self._nchannels
378n/a
379n/a def getnframes(self):
380n/a return self._nframes
381n/a
382n/a def getsampwidth(self):
383n/a return self._sampwidth
384n/a
385n/a def getframerate(self):
386n/a return self._framerate
387n/a
388n/a def getcomptype(self):
389n/a return self._comptype
390n/a
391n/a def getcompname(self):
392n/a return self._compname
393n/a
394n/a## def getversion(self):
395n/a## return self._version
396n/a
397n/a def getparams(self):
398n/a return _aifc_params(self.getnchannels(), self.getsampwidth(),
399n/a self.getframerate(), self.getnframes(),
400n/a self.getcomptype(), self.getcompname())
401n/a
402n/a def getmarkers(self):
403n/a if len(self._markers) == 0:
404n/a return None
405n/a return self._markers
406n/a
407n/a def getmark(self, id):
408n/a for marker in self._markers:
409n/a if id == marker[0]:
410n/a return marker
411n/a raise Error('marker {0!r} does not exist'.format(id))
412n/a
413n/a def setpos(self, pos):
414n/a if pos < 0 or pos > self._nframes:
415n/a raise Error('position not in range')
416n/a self._soundpos = pos
417n/a self._ssnd_seek_needed = 1
418n/a
419n/a def readframes(self, nframes):
420n/a if self._ssnd_seek_needed:
421n/a self._ssnd_chunk.seek(0)
422n/a dummy = self._ssnd_chunk.read(8)
423n/a pos = self._soundpos * self._framesize
424n/a if pos:
425n/a self._ssnd_chunk.seek(pos + 8)
426n/a self._ssnd_seek_needed = 0
427n/a if nframes == 0:
428n/a return b''
429n/a data = self._ssnd_chunk.read(nframes * self._framesize)
430n/a if self._convert and data:
431n/a data = self._convert(data)
432n/a self._soundpos = self._soundpos + len(data) // (self._nchannels
433n/a * self._sampwidth)
434n/a return data
435n/a
436n/a #
437n/a # Internal methods.
438n/a #
439n/a
440n/a def _alaw2lin(self, data):
441n/a import audioop
442n/a return audioop.alaw2lin(data, 2)
443n/a
444n/a def _ulaw2lin(self, data):
445n/a import audioop
446n/a return audioop.ulaw2lin(data, 2)
447n/a
448n/a def _adpcm2lin(self, data):
449n/a import audioop
450n/a if not hasattr(self, '_adpcmstate'):
451n/a # first time
452n/a self._adpcmstate = None
453n/a data, self._adpcmstate = audioop.adpcm2lin(data, 2, self._adpcmstate)
454n/a return data
455n/a
456n/a def _read_comm_chunk(self, chunk):
457n/a self._nchannels = _read_short(chunk)
458n/a self._nframes = _read_long(chunk)
459n/a self._sampwidth = (_read_short(chunk) + 7) // 8
460n/a self._framerate = int(_read_float(chunk))
461n/a self._framesize = self._nchannels * self._sampwidth
462n/a if self._aifc:
463n/a #DEBUG: SGI's soundeditor produces a bad size :-(
464n/a kludge = 0
465n/a if chunk.chunksize == 18:
466n/a kludge = 1
467n/a warnings.warn('Warning: bad COMM chunk size')
468n/a chunk.chunksize = 23
469n/a #DEBUG end
470n/a self._comptype = chunk.read(4)
471n/a #DEBUG start
472n/a if kludge:
473n/a length = ord(chunk.file.read(1))
474n/a if length & 1 == 0:
475n/a length = length + 1
476n/a chunk.chunksize = chunk.chunksize + length
477n/a chunk.file.seek(-1, 1)
478n/a #DEBUG end
479n/a self._compname = _read_string(chunk)
480n/a if self._comptype != b'NONE':
481n/a if self._comptype == b'G722':
482n/a self._convert = self._adpcm2lin
483n/a elif self._comptype in (b'ulaw', b'ULAW'):
484n/a self._convert = self._ulaw2lin
485n/a elif self._comptype in (b'alaw', b'ALAW'):
486n/a self._convert = self._alaw2lin
487n/a else:
488n/a raise Error('unsupported compression type')
489n/a self._sampwidth = 2
490n/a else:
491n/a self._comptype = b'NONE'
492n/a self._compname = b'not compressed'
493n/a
494n/a def _readmark(self, chunk):
495n/a nmarkers = _read_short(chunk)
496n/a # Some files appear to contain invalid counts.
497n/a # Cope with this by testing for EOF.
498n/a try:
499n/a for i in range(nmarkers):
500n/a id = _read_short(chunk)
501n/a pos = _read_long(chunk)
502n/a name = _read_string(chunk)
503n/a if pos or name:
504n/a # some files appear to have
505n/a # dummy markers consisting of
506n/a # a position 0 and name ''
507n/a self._markers.append((id, pos, name))
508n/a except EOFError:
509n/a w = ('Warning: MARK chunk contains only %s marker%s instead of %s' %
510n/a (len(self._markers), '' if len(self._markers) == 1 else 's',
511n/a nmarkers))
512n/a warnings.warn(w)
513n/a
514n/aclass Aifc_write:
515n/a # Variables used in this class:
516n/a #
517n/a # These variables are user settable through appropriate methods
518n/a # of this class:
519n/a # _file -- the open file with methods write(), close(), tell(), seek()
520n/a # set through the __init__() method
521n/a # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
522n/a # set through the setcomptype() or setparams() method
523n/a # _compname -- the human-readable AIFF-C compression type
524n/a # set through the setcomptype() or setparams() method
525n/a # _nchannels -- the number of audio channels
526n/a # set through the setnchannels() or setparams() method
527n/a # _sampwidth -- the number of bytes per audio sample
528n/a # set through the setsampwidth() or setparams() method
529n/a # _framerate -- the sampling frequency
530n/a # set through the setframerate() or setparams() method
531n/a # _nframes -- the number of audio frames written to the header
532n/a # set through the setnframes() or setparams() method
533n/a # _aifc -- whether we're writing an AIFF-C file or an AIFF file
534n/a # set through the aifc() method, reset through the
535n/a # aiff() method
536n/a #
537n/a # These variables are used internally only:
538n/a # _version -- the AIFF-C version number
539n/a # _comp -- the compressor from builtin module cl
540n/a # _nframeswritten -- the number of audio frames actually written
541n/a # _datalength -- the size of the audio samples written to the header
542n/a # _datawritten -- the size of the audio samples actually written
543n/a
544n/a def __init__(self, f):
545n/a if isinstance(f, str):
546n/a filename = f
547n/a f = builtins.open(f, 'wb')
548n/a else:
549n/a # else, assume it is an open file object already
550n/a filename = '???'
551n/a self.initfp(f)
552n/a if filename[-5:] == '.aiff':
553n/a self._aifc = 0
554n/a else:
555n/a self._aifc = 1
556n/a
557n/a def initfp(self, file):
558n/a self._file = file
559n/a self._version = _AIFC_version
560n/a self._comptype = b'NONE'
561n/a self._compname = b'not compressed'
562n/a self._convert = None
563n/a self._nchannels = 0
564n/a self._sampwidth = 0
565n/a self._framerate = 0
566n/a self._nframes = 0
567n/a self._nframeswritten = 0
568n/a self._datawritten = 0
569n/a self._datalength = 0
570n/a self._markers = []
571n/a self._marklength = 0
572n/a self._aifc = 1 # AIFF-C is default
573n/a
574n/a def __del__(self):
575n/a self.close()
576n/a
577n/a def __enter__(self):
578n/a return self
579n/a
580n/a def __exit__(self, *args):
581n/a self.close()
582n/a
583n/a #
584n/a # User visible methods.
585n/a #
586n/a def aiff(self):
587n/a if self._nframeswritten:
588n/a raise Error('cannot change parameters after starting to write')
589n/a self._aifc = 0
590n/a
591n/a def aifc(self):
592n/a if self._nframeswritten:
593n/a raise Error('cannot change parameters after starting to write')
594n/a self._aifc = 1
595n/a
596n/a def setnchannels(self, nchannels):
597n/a if self._nframeswritten:
598n/a raise Error('cannot change parameters after starting to write')
599n/a if nchannels < 1:
600n/a raise Error('bad # of channels')
601n/a self._nchannels = nchannels
602n/a
603n/a def getnchannels(self):
604n/a if not self._nchannels:
605n/a raise Error('number of channels not set')
606n/a return self._nchannels
607n/a
608n/a def setsampwidth(self, sampwidth):
609n/a if self._nframeswritten:
610n/a raise Error('cannot change parameters after starting to write')
611n/a if sampwidth < 1 or sampwidth > 4:
612n/a raise Error('bad sample width')
613n/a self._sampwidth = sampwidth
614n/a
615n/a def getsampwidth(self):
616n/a if not self._sampwidth:
617n/a raise Error('sample width not set')
618n/a return self._sampwidth
619n/a
620n/a def setframerate(self, framerate):
621n/a if self._nframeswritten:
622n/a raise Error('cannot change parameters after starting to write')
623n/a if framerate <= 0:
624n/a raise Error('bad frame rate')
625n/a self._framerate = framerate
626n/a
627n/a def getframerate(self):
628n/a if not self._framerate:
629n/a raise Error('frame rate not set')
630n/a return self._framerate
631n/a
632n/a def setnframes(self, nframes):
633n/a if self._nframeswritten:
634n/a raise Error('cannot change parameters after starting to write')
635n/a self._nframes = nframes
636n/a
637n/a def getnframes(self):
638n/a return self._nframeswritten
639n/a
640n/a def setcomptype(self, comptype, compname):
641n/a if self._nframeswritten:
642n/a raise Error('cannot change parameters after starting to write')
643n/a if comptype not in (b'NONE', b'ulaw', b'ULAW',
644n/a b'alaw', b'ALAW', b'G722'):
645n/a raise Error('unsupported compression type')
646n/a self._comptype = comptype
647n/a self._compname = compname
648n/a
649n/a def getcomptype(self):
650n/a return self._comptype
651n/a
652n/a def getcompname(self):
653n/a return self._compname
654n/a
655n/a## def setversion(self, version):
656n/a## if self._nframeswritten:
657n/a## raise Error, 'cannot change parameters after starting to write'
658n/a## self._version = version
659n/a
660n/a def setparams(self, params):
661n/a nchannels, sampwidth, framerate, nframes, comptype, compname = params
662n/a if self._nframeswritten:
663n/a raise Error('cannot change parameters after starting to write')
664n/a if comptype not in (b'NONE', b'ulaw', b'ULAW',
665n/a b'alaw', b'ALAW', b'G722'):
666n/a raise Error('unsupported compression type')
667n/a self.setnchannels(nchannels)
668n/a self.setsampwidth(sampwidth)
669n/a self.setframerate(framerate)
670n/a self.setnframes(nframes)
671n/a self.setcomptype(comptype, compname)
672n/a
673n/a def getparams(self):
674n/a if not self._nchannels or not self._sampwidth or not self._framerate:
675n/a raise Error('not all parameters set')
676n/a return _aifc_params(self._nchannels, self._sampwidth, self._framerate,
677n/a self._nframes, self._comptype, self._compname)
678n/a
679n/a def setmark(self, id, pos, name):
680n/a if id <= 0:
681n/a raise Error('marker ID must be > 0')
682n/a if pos < 0:
683n/a raise Error('marker position must be >= 0')
684n/a if not isinstance(name, bytes):
685n/a raise Error('marker name must be bytes')
686n/a for i in range(len(self._markers)):
687n/a if id == self._markers[i][0]:
688n/a self._markers[i] = id, pos, name
689n/a return
690n/a self._markers.append((id, pos, name))
691n/a
692n/a def getmark(self, id):
693n/a for marker in self._markers:
694n/a if id == marker[0]:
695n/a return marker
696n/a raise Error('marker {0!r} does not exist'.format(id))
697n/a
698n/a def getmarkers(self):
699n/a if len(self._markers) == 0:
700n/a return None
701n/a return self._markers
702n/a
703n/a def tell(self):
704n/a return self._nframeswritten
705n/a
706n/a def writeframesraw(self, data):
707n/a if not isinstance(data, (bytes, bytearray)):
708n/a data = memoryview(data).cast('B')
709n/a self._ensure_header_written(len(data))
710n/a nframes = len(data) // (self._sampwidth * self._nchannels)
711n/a if self._convert:
712n/a data = self._convert(data)
713n/a self._file.write(data)
714n/a self._nframeswritten = self._nframeswritten + nframes
715n/a self._datawritten = self._datawritten + len(data)
716n/a
717n/a def writeframes(self, data):
718n/a self.writeframesraw(data)
719n/a if self._nframeswritten != self._nframes or \
720n/a self._datalength != self._datawritten:
721n/a self._patchheader()
722n/a
723n/a def close(self):
724n/a if self._file is None:
725n/a return
726n/a try:
727n/a self._ensure_header_written(0)
728n/a if self._datawritten & 1:
729n/a # quick pad to even size
730n/a self._file.write(b'\x00')
731n/a self._datawritten = self._datawritten + 1
732n/a self._writemarkers()
733n/a if self._nframeswritten != self._nframes or \
734n/a self._datalength != self._datawritten or \
735n/a self._marklength:
736n/a self._patchheader()
737n/a finally:
738n/a # Prevent ref cycles
739n/a self._convert = None
740n/a f = self._file
741n/a self._file = None
742n/a f.close()
743n/a
744n/a #
745n/a # Internal methods.
746n/a #
747n/a
748n/a def _lin2alaw(self, data):
749n/a import audioop
750n/a return audioop.lin2alaw(data, 2)
751n/a
752n/a def _lin2ulaw(self, data):
753n/a import audioop
754n/a return audioop.lin2ulaw(data, 2)
755n/a
756n/a def _lin2adpcm(self, data):
757n/a import audioop
758n/a if not hasattr(self, '_adpcmstate'):
759n/a self._adpcmstate = None
760n/a data, self._adpcmstate = audioop.lin2adpcm(data, 2, self._adpcmstate)
761n/a return data
762n/a
763n/a def _ensure_header_written(self, datasize):
764n/a if not self._nframeswritten:
765n/a if self._comptype in (b'ULAW', b'ulaw', b'ALAW', b'alaw', b'G722'):
766n/a if not self._sampwidth:
767n/a self._sampwidth = 2
768n/a if self._sampwidth != 2:
769n/a raise Error('sample width must be 2 when compressing '
770n/a 'with ulaw/ULAW, alaw/ALAW or G7.22 (ADPCM)')
771n/a if not self._nchannels:
772n/a raise Error('# channels not specified')
773n/a if not self._sampwidth:
774n/a raise Error('sample width not specified')
775n/a if not self._framerate:
776n/a raise Error('sampling rate not specified')
777n/a self._write_header(datasize)
778n/a
779n/a def _init_compression(self):
780n/a if self._comptype == b'G722':
781n/a self._convert = self._lin2adpcm
782n/a elif self._comptype in (b'ulaw', b'ULAW'):
783n/a self._convert = self._lin2ulaw
784n/a elif self._comptype in (b'alaw', b'ALAW'):
785n/a self._convert = self._lin2alaw
786n/a
787n/a def _write_header(self, initlength):
788n/a if self._aifc and self._comptype != b'NONE':
789n/a self._init_compression()
790n/a self._file.write(b'FORM')
791n/a if not self._nframes:
792n/a self._nframes = initlength // (self._nchannels * self._sampwidth)
793n/a self._datalength = self._nframes * self._nchannels * self._sampwidth
794n/a if self._datalength & 1:
795n/a self._datalength = self._datalength + 1
796n/a if self._aifc:
797n/a if self._comptype in (b'ulaw', b'ULAW', b'alaw', b'ALAW'):
798n/a self._datalength = self._datalength // 2
799n/a if self._datalength & 1:
800n/a self._datalength = self._datalength + 1
801n/a elif self._comptype == b'G722':
802n/a self._datalength = (self._datalength + 3) // 4
803n/a if self._datalength & 1:
804n/a self._datalength = self._datalength + 1
805n/a try:
806n/a self._form_length_pos = self._file.tell()
807n/a except (AttributeError, OSError):
808n/a self._form_length_pos = None
809n/a commlength = self._write_form_length(self._datalength)
810n/a if self._aifc:
811n/a self._file.write(b'AIFC')
812n/a self._file.write(b'FVER')
813n/a _write_ulong(self._file, 4)
814n/a _write_ulong(self._file, self._version)
815n/a else:
816n/a self._file.write(b'AIFF')
817n/a self._file.write(b'COMM')
818n/a _write_ulong(self._file, commlength)
819n/a _write_short(self._file, self._nchannels)
820n/a if self._form_length_pos is not None:
821n/a self._nframes_pos = self._file.tell()
822n/a _write_ulong(self._file, self._nframes)
823n/a if self._comptype in (b'ULAW', b'ulaw', b'ALAW', b'alaw', b'G722'):
824n/a _write_short(self._file, 8)
825n/a else:
826n/a _write_short(self._file, self._sampwidth * 8)
827n/a _write_float(self._file, self._framerate)
828n/a if self._aifc:
829n/a self._file.write(self._comptype)
830n/a _write_string(self._file, self._compname)
831n/a self._file.write(b'SSND')
832n/a if self._form_length_pos is not None:
833n/a self._ssnd_length_pos = self._file.tell()
834n/a _write_ulong(self._file, self._datalength + 8)
835n/a _write_ulong(self._file, 0)
836n/a _write_ulong(self._file, 0)
837n/a
838n/a def _write_form_length(self, datalength):
839n/a if self._aifc:
840n/a commlength = 18 + 5 + len(self._compname)
841n/a if commlength & 1:
842n/a commlength = commlength + 1
843n/a verslength = 12
844n/a else:
845n/a commlength = 18
846n/a verslength = 0
847n/a _write_ulong(self._file, 4 + verslength + self._marklength + \
848n/a 8 + commlength + 16 + datalength)
849n/a return commlength
850n/a
851n/a def _patchheader(self):
852n/a curpos = self._file.tell()
853n/a if self._datawritten & 1:
854n/a datalength = self._datawritten + 1
855n/a self._file.write(b'\x00')
856n/a else:
857n/a datalength = self._datawritten
858n/a if datalength == self._datalength and \
859n/a self._nframes == self._nframeswritten and \
860n/a self._marklength == 0:
861n/a self._file.seek(curpos, 0)
862n/a return
863n/a self._file.seek(self._form_length_pos, 0)
864n/a dummy = self._write_form_length(datalength)
865n/a self._file.seek(self._nframes_pos, 0)
866n/a _write_ulong(self._file, self._nframeswritten)
867n/a self._file.seek(self._ssnd_length_pos, 0)
868n/a _write_ulong(self._file, datalength + 8)
869n/a self._file.seek(curpos, 0)
870n/a self._nframes = self._nframeswritten
871n/a self._datalength = datalength
872n/a
873n/a def _writemarkers(self):
874n/a if len(self._markers) == 0:
875n/a return
876n/a self._file.write(b'MARK')
877n/a length = 2
878n/a for marker in self._markers:
879n/a id, pos, name = marker
880n/a length = length + len(name) + 1 + 6
881n/a if len(name) & 1 == 0:
882n/a length = length + 1
883n/a _write_ulong(self._file, length)
884n/a self._marklength = length + 8
885n/a _write_short(self._file, len(self._markers))
886n/a for marker in self._markers:
887n/a id, pos, name = marker
888n/a _write_short(self._file, id)
889n/a _write_ulong(self._file, pos)
890n/a _write_string(self._file, name)
891n/a
892n/adef open(f, mode=None):
893n/a if mode is None:
894n/a if hasattr(f, 'mode'):
895n/a mode = f.mode
896n/a else:
897n/a mode = 'rb'
898n/a if mode in ('r', 'rb'):
899n/a return Aifc_read(f)
900n/a elif mode in ('w', 'wb'):
901n/a return Aifc_write(f)
902n/a else:
903n/a raise Error("mode must be 'r', 'rb', 'w', or 'wb'")
904n/a
905n/aopenfp = open # B/W compatibility
906n/a
907n/aif __name__ == '__main__':
908n/a import sys
909n/a if not sys.argv[1:]:
910n/a sys.argv.append('/usr/demos/data/audio/bach.aiff')
911n/a fn = sys.argv[1]
912n/a with open(fn, 'r') as f:
913n/a print("Reading", fn)
914n/a print("nchannels =", f.getnchannels())
915n/a print("nframes =", f.getnframes())
916n/a print("sampwidth =", f.getsampwidth())
917n/a print("framerate =", f.getframerate())
918n/a print("comptype =", f.getcomptype())
919n/a print("compname =", f.getcompname())
920n/a if sys.argv[2:]:
921n/a gn = sys.argv[2]
922n/a print("Writing", gn)
923n/a with open(gn, 'w') as g:
924n/a g.setparams(f.getparams())
925n/a while 1:
926n/a data = f.readframes(1024)
927n/a if not data:
928n/a break
929n/a g.writeframes(data)
930n/a print("Done.")