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

Python code coverage for Lib/sunau.py

#countcontent
1n/a"""Stuff to parse Sun and NeXT audio files.
2n/a
3n/aAn audio file consists of a header followed by the data. The structure
4n/aof the header is as follows.
5n/a
6n/a +---------------+
7n/a | magic word |
8n/a +---------------+
9n/a | header size |
10n/a +---------------+
11n/a | data size |
12n/a +---------------+
13n/a | encoding |
14n/a +---------------+
15n/a | sample rate |
16n/a +---------------+
17n/a | # of channels |
18n/a +---------------+
19n/a | info |
20n/a | |
21n/a +---------------+
22n/a
23n/aThe magic word consists of the 4 characters '.snd'. Apart from the
24n/ainfo field, all header fields are 4 bytes in size. They are all
25n/a32-bit unsigned integers encoded in big-endian byte order.
26n/a
27n/aThe header size really gives the start of the data.
28n/aThe data size is the physical size of the data. From the other
29n/aparameters the number of frames can be calculated.
30n/aThe encoding gives the way in which audio samples are encoded.
31n/aPossible values are listed below.
32n/aThe info field currently consists of an ASCII string giving a
33n/ahuman-readable description of the audio file. The info field is
34n/apadded with NUL bytes to the header size.
35n/a
36n/aUsage.
37n/a
38n/aReading audio files:
39n/a f = sunau.open(file, 'r')
40n/awhere file is either the name of a file or an open file pointer.
41n/aThe open file pointer must have methods read(), seek(), and close().
42n/aWhen the setpos() and rewind() methods are not used, the seek()
43n/amethod is not necessary.
44n/a
45n/aThis returns an instance of a class with the following public methods:
46n/a getnchannels() -- returns number of audio channels (1 for
47n/a mono, 2 for stereo)
48n/a getsampwidth() -- returns sample width in bytes
49n/a getframerate() -- returns sampling frequency
50n/a getnframes() -- returns number of audio frames
51n/a getcomptype() -- returns compression type ('NONE' or 'ULAW')
52n/a getcompname() -- returns human-readable version of
53n/a compression type ('not compressed' matches 'NONE')
54n/a getparams() -- returns a namedtuple consisting of all of the
55n/a above in the above order
56n/a getmarkers() -- returns None (for compatibility with the
57n/a aifc module)
58n/a getmark(id) -- raises an error since the mark does not
59n/a exist (for compatibility with the aifc module)
60n/a readframes(n) -- returns at most n frames of audio
61n/a rewind() -- rewind to the beginning of the audio stream
62n/a setpos(pos) -- seek to the specified position
63n/a tell() -- return the current position
64n/a close() -- close the instance (make it unusable)
65n/aThe position returned by tell() and the position given to setpos()
66n/aare compatible and have nothing to do with the actual position in the
67n/afile.
68n/aThe close() method is called automatically when the class instance
69n/ais destroyed.
70n/a
71n/aWriting audio files:
72n/a f = sunau.open(file, 'w')
73n/awhere file is either the name of a file or an open file pointer.
74n/aThe open file pointer must have methods write(), tell(), seek(), and
75n/aclose().
76n/a
77n/aThis returns an instance of a class with the following public methods:
78n/a setnchannels(n) -- set the number of channels
79n/a setsampwidth(n) -- set the sample width
80n/a setframerate(n) -- set the frame rate
81n/a setnframes(n) -- set the number of frames
82n/a setcomptype(type, name)
83n/a -- set the compression type and the
84n/a human-readable compression type
85n/a setparams(tuple)-- set all parameters at once
86n/a tell() -- return current position in output file
87n/a writeframesraw(data)
88n/a -- write audio frames without pathing up the
89n/a file header
90n/a writeframes(data)
91n/a -- write audio frames and patch up the file header
92n/a close() -- patch up the file header and close the
93n/a output file
94n/aYou should set the parameters before the first writeframesraw or
95n/awriteframes. The total number of frames does not need to be set,
96n/abut when it is set to the correct value, the header does not have to
97n/abe patched up.
98n/aIt is best to first set all parameters, perhaps possibly the
99n/acompression type, and then write audio frames using writeframesraw.
100n/aWhen all frames have been written, either call writeframes(b'') or
101n/aclose() to patch up the sizes in the header.
102n/aThe close() method is called automatically when the class instance
103n/ais destroyed.
104n/a"""
105n/a
106n/afrom collections import namedtuple
107n/a
108n/a_sunau_params = namedtuple('_sunau_params',
109n/a 'nchannels sampwidth framerate nframes comptype compname')
110n/a
111n/a# from <multimedia/audio_filehdr.h>
112n/aAUDIO_FILE_MAGIC = 0x2e736e64
113n/aAUDIO_FILE_ENCODING_MULAW_8 = 1
114n/aAUDIO_FILE_ENCODING_LINEAR_8 = 2
115n/aAUDIO_FILE_ENCODING_LINEAR_16 = 3
116n/aAUDIO_FILE_ENCODING_LINEAR_24 = 4
117n/aAUDIO_FILE_ENCODING_LINEAR_32 = 5
118n/aAUDIO_FILE_ENCODING_FLOAT = 6
119n/aAUDIO_FILE_ENCODING_DOUBLE = 7
120n/aAUDIO_FILE_ENCODING_ADPCM_G721 = 23
121n/aAUDIO_FILE_ENCODING_ADPCM_G722 = 24
122n/aAUDIO_FILE_ENCODING_ADPCM_G723_3 = 25
123n/aAUDIO_FILE_ENCODING_ADPCM_G723_5 = 26
124n/aAUDIO_FILE_ENCODING_ALAW_8 = 27
125n/a
126n/a# from <multimedia/audio_hdr.h>
127n/aAUDIO_UNKNOWN_SIZE = 0xFFFFFFFF # ((unsigned)(~0))
128n/a
129n/a_simple_encodings = [AUDIO_FILE_ENCODING_MULAW_8,
130n/a AUDIO_FILE_ENCODING_LINEAR_8,
131n/a AUDIO_FILE_ENCODING_LINEAR_16,
132n/a AUDIO_FILE_ENCODING_LINEAR_24,
133n/a AUDIO_FILE_ENCODING_LINEAR_32,
134n/a AUDIO_FILE_ENCODING_ALAW_8]
135n/a
136n/aclass Error(Exception):
137n/a pass
138n/a
139n/adef _read_u32(file):
140n/a x = 0
141n/a for i in range(4):
142n/a byte = file.read(1)
143n/a if not byte:
144n/a raise EOFError
145n/a x = x*256 + ord(byte)
146n/a return x
147n/a
148n/adef _write_u32(file, x):
149n/a data = []
150n/a for i in range(4):
151n/a d, m = divmod(x, 256)
152n/a data.insert(0, int(m))
153n/a x = d
154n/a file.write(bytes(data))
155n/a
156n/aclass Au_read:
157n/a
158n/a def __init__(self, f):
159n/a if type(f) == type(''):
160n/a import builtins
161n/a f = builtins.open(f, 'rb')
162n/a self._opened = True
163n/a else:
164n/a self._opened = False
165n/a self.initfp(f)
166n/a
167n/a def __del__(self):
168n/a if self._file:
169n/a self.close()
170n/a
171n/a def __enter__(self):
172n/a return self
173n/a
174n/a def __exit__(self, *args):
175n/a self.close()
176n/a
177n/a def initfp(self, file):
178n/a self._file = file
179n/a self._soundpos = 0
180n/a magic = int(_read_u32(file))
181n/a if magic != AUDIO_FILE_MAGIC:
182n/a raise Error('bad magic number')
183n/a self._hdr_size = int(_read_u32(file))
184n/a if self._hdr_size < 24:
185n/a raise Error('header size too small')
186n/a if self._hdr_size > 100:
187n/a raise Error('header size ridiculously large')
188n/a self._data_size = _read_u32(file)
189n/a if self._data_size != AUDIO_UNKNOWN_SIZE:
190n/a self._data_size = int(self._data_size)
191n/a self._encoding = int(_read_u32(file))
192n/a if self._encoding not in _simple_encodings:
193n/a raise Error('encoding not (yet) supported')
194n/a if self._encoding in (AUDIO_FILE_ENCODING_MULAW_8,
195n/a AUDIO_FILE_ENCODING_ALAW_8):
196n/a self._sampwidth = 2
197n/a self._framesize = 1
198n/a elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_8:
199n/a self._framesize = self._sampwidth = 1
200n/a elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_16:
201n/a self._framesize = self._sampwidth = 2
202n/a elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_24:
203n/a self._framesize = self._sampwidth = 3
204n/a elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_32:
205n/a self._framesize = self._sampwidth = 4
206n/a else:
207n/a raise Error('unknown encoding')
208n/a self._framerate = int(_read_u32(file))
209n/a self._nchannels = int(_read_u32(file))
210n/a self._framesize = self._framesize * self._nchannels
211n/a if self._hdr_size > 24:
212n/a self._info = file.read(self._hdr_size - 24)
213n/a self._info, _, _ = self._info.partition(b'\0')
214n/a else:
215n/a self._info = b''
216n/a try:
217n/a self._data_pos = file.tell()
218n/a except (AttributeError, OSError):
219n/a self._data_pos = None
220n/a
221n/a def getfp(self):
222n/a return self._file
223n/a
224n/a def getnchannels(self):
225n/a return self._nchannels
226n/a
227n/a def getsampwidth(self):
228n/a return self._sampwidth
229n/a
230n/a def getframerate(self):
231n/a return self._framerate
232n/a
233n/a def getnframes(self):
234n/a if self._data_size == AUDIO_UNKNOWN_SIZE:
235n/a return AUDIO_UNKNOWN_SIZE
236n/a if self._encoding in _simple_encodings:
237n/a return self._data_size // self._framesize
238n/a return 0 # XXX--must do some arithmetic here
239n/a
240n/a def getcomptype(self):
241n/a if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
242n/a return 'ULAW'
243n/a elif self._encoding == AUDIO_FILE_ENCODING_ALAW_8:
244n/a return 'ALAW'
245n/a else:
246n/a return 'NONE'
247n/a
248n/a def getcompname(self):
249n/a if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
250n/a return 'CCITT G.711 u-law'
251n/a elif self._encoding == AUDIO_FILE_ENCODING_ALAW_8:
252n/a return 'CCITT G.711 A-law'
253n/a else:
254n/a return 'not compressed'
255n/a
256n/a def getparams(self):
257n/a return _sunau_params(self.getnchannels(), self.getsampwidth(),
258n/a self.getframerate(), self.getnframes(),
259n/a self.getcomptype(), self.getcompname())
260n/a
261n/a def getmarkers(self):
262n/a return None
263n/a
264n/a def getmark(self, id):
265n/a raise Error('no marks')
266n/a
267n/a def readframes(self, nframes):
268n/a if self._encoding in _simple_encodings:
269n/a if nframes == AUDIO_UNKNOWN_SIZE:
270n/a data = self._file.read()
271n/a else:
272n/a data = self._file.read(nframes * self._framesize)
273n/a self._soundpos += len(data) // self._framesize
274n/a if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
275n/a import audioop
276n/a data = audioop.ulaw2lin(data, self._sampwidth)
277n/a return data
278n/a return None # XXX--not implemented yet
279n/a
280n/a def rewind(self):
281n/a if self._data_pos is None:
282n/a raise OSError('cannot seek')
283n/a self._file.seek(self._data_pos)
284n/a self._soundpos = 0
285n/a
286n/a def tell(self):
287n/a return self._soundpos
288n/a
289n/a def setpos(self, pos):
290n/a if pos < 0 or pos > self.getnframes():
291n/a raise Error('position not in range')
292n/a if self._data_pos is None:
293n/a raise OSError('cannot seek')
294n/a self._file.seek(self._data_pos + pos * self._framesize)
295n/a self._soundpos = pos
296n/a
297n/a def close(self):
298n/a file = self._file
299n/a if file:
300n/a self._file = None
301n/a if self._opened:
302n/a file.close()
303n/a
304n/aclass Au_write:
305n/a
306n/a def __init__(self, f):
307n/a if type(f) == type(''):
308n/a import builtins
309n/a f = builtins.open(f, 'wb')
310n/a self._opened = True
311n/a else:
312n/a self._opened = False
313n/a self.initfp(f)
314n/a
315n/a def __del__(self):
316n/a if self._file:
317n/a self.close()
318n/a self._file = None
319n/a
320n/a def __enter__(self):
321n/a return self
322n/a
323n/a def __exit__(self, *args):
324n/a self.close()
325n/a
326n/a def initfp(self, file):
327n/a self._file = file
328n/a self._framerate = 0
329n/a self._nchannels = 0
330n/a self._sampwidth = 0
331n/a self._framesize = 0
332n/a self._nframes = AUDIO_UNKNOWN_SIZE
333n/a self._nframeswritten = 0
334n/a self._datawritten = 0
335n/a self._datalength = 0
336n/a self._info = b''
337n/a self._comptype = 'ULAW' # default is U-law
338n/a
339n/a def setnchannels(self, nchannels):
340n/a if self._nframeswritten:
341n/a raise Error('cannot change parameters after starting to write')
342n/a if nchannels not in (1, 2, 4):
343n/a raise Error('only 1, 2, or 4 channels supported')
344n/a self._nchannels = nchannels
345n/a
346n/a def getnchannels(self):
347n/a if not self._nchannels:
348n/a raise Error('number of channels not set')
349n/a return self._nchannels
350n/a
351n/a def setsampwidth(self, sampwidth):
352n/a if self._nframeswritten:
353n/a raise Error('cannot change parameters after starting to write')
354n/a if sampwidth not in (1, 2, 3, 4):
355n/a raise Error('bad sample width')
356n/a self._sampwidth = sampwidth
357n/a
358n/a def getsampwidth(self):
359n/a if not self._framerate:
360n/a raise Error('sample width not specified')
361n/a return self._sampwidth
362n/a
363n/a def setframerate(self, framerate):
364n/a if self._nframeswritten:
365n/a raise Error('cannot change parameters after starting to write')
366n/a self._framerate = framerate
367n/a
368n/a def getframerate(self):
369n/a if not self._framerate:
370n/a raise Error('frame rate not set')
371n/a return self._framerate
372n/a
373n/a def setnframes(self, nframes):
374n/a if self._nframeswritten:
375n/a raise Error('cannot change parameters after starting to write')
376n/a if nframes < 0:
377n/a raise Error('# of frames cannot be negative')
378n/a self._nframes = nframes
379n/a
380n/a def getnframes(self):
381n/a return self._nframeswritten
382n/a
383n/a def setcomptype(self, type, name):
384n/a if type in ('NONE', 'ULAW'):
385n/a self._comptype = type
386n/a else:
387n/a raise Error('unknown compression type')
388n/a
389n/a def getcomptype(self):
390n/a return self._comptype
391n/a
392n/a def getcompname(self):
393n/a if self._comptype == 'ULAW':
394n/a return 'CCITT G.711 u-law'
395n/a elif self._comptype == 'ALAW':
396n/a return 'CCITT G.711 A-law'
397n/a else:
398n/a return 'not compressed'
399n/a
400n/a def setparams(self, params):
401n/a nchannels, sampwidth, framerate, nframes, comptype, compname = params
402n/a self.setnchannels(nchannels)
403n/a self.setsampwidth(sampwidth)
404n/a self.setframerate(framerate)
405n/a self.setnframes(nframes)
406n/a self.setcomptype(comptype, compname)
407n/a
408n/a def getparams(self):
409n/a return _sunau_params(self.getnchannels(), self.getsampwidth(),
410n/a self.getframerate(), self.getnframes(),
411n/a self.getcomptype(), self.getcompname())
412n/a
413n/a def tell(self):
414n/a return self._nframeswritten
415n/a
416n/a def writeframesraw(self, data):
417n/a if not isinstance(data, (bytes, bytearray)):
418n/a data = memoryview(data).cast('B')
419n/a self._ensure_header_written()
420n/a if self._comptype == 'ULAW':
421n/a import audioop
422n/a data = audioop.lin2ulaw(data, self._sampwidth)
423n/a nframes = len(data) // self._framesize
424n/a self._file.write(data)
425n/a self._nframeswritten = self._nframeswritten + nframes
426n/a self._datawritten = self._datawritten + len(data)
427n/a
428n/a def writeframes(self, data):
429n/a self.writeframesraw(data)
430n/a if self._nframeswritten != self._nframes or \
431n/a self._datalength != self._datawritten:
432n/a self._patchheader()
433n/a
434n/a def close(self):
435n/a if self._file:
436n/a try:
437n/a self._ensure_header_written()
438n/a if self._nframeswritten != self._nframes or \
439n/a self._datalength != self._datawritten:
440n/a self._patchheader()
441n/a self._file.flush()
442n/a finally:
443n/a file = self._file
444n/a self._file = None
445n/a if self._opened:
446n/a file.close()
447n/a
448n/a #
449n/a # private methods
450n/a #
451n/a
452n/a def _ensure_header_written(self):
453n/a if not self._nframeswritten:
454n/a if not self._nchannels:
455n/a raise Error('# of channels not specified')
456n/a if not self._sampwidth:
457n/a raise Error('sample width not specified')
458n/a if not self._framerate:
459n/a raise Error('frame rate not specified')
460n/a self._write_header()
461n/a
462n/a def _write_header(self):
463n/a if self._comptype == 'NONE':
464n/a if self._sampwidth == 1:
465n/a encoding = AUDIO_FILE_ENCODING_LINEAR_8
466n/a self._framesize = 1
467n/a elif self._sampwidth == 2:
468n/a encoding = AUDIO_FILE_ENCODING_LINEAR_16
469n/a self._framesize = 2
470n/a elif self._sampwidth == 3:
471n/a encoding = AUDIO_FILE_ENCODING_LINEAR_24
472n/a self._framesize = 3
473n/a elif self._sampwidth == 4:
474n/a encoding = AUDIO_FILE_ENCODING_LINEAR_32
475n/a self._framesize = 4
476n/a else:
477n/a raise Error('internal error')
478n/a elif self._comptype == 'ULAW':
479n/a encoding = AUDIO_FILE_ENCODING_MULAW_8
480n/a self._framesize = 1
481n/a else:
482n/a raise Error('internal error')
483n/a self._framesize = self._framesize * self._nchannels
484n/a _write_u32(self._file, AUDIO_FILE_MAGIC)
485n/a header_size = 25 + len(self._info)
486n/a header_size = (header_size + 7) & ~7
487n/a _write_u32(self._file, header_size)
488n/a if self._nframes == AUDIO_UNKNOWN_SIZE:
489n/a length = AUDIO_UNKNOWN_SIZE
490n/a else:
491n/a length = self._nframes * self._framesize
492n/a try:
493n/a self._form_length_pos = self._file.tell()
494n/a except (AttributeError, OSError):
495n/a self._form_length_pos = None
496n/a _write_u32(self._file, length)
497n/a self._datalength = length
498n/a _write_u32(self._file, encoding)
499n/a _write_u32(self._file, self._framerate)
500n/a _write_u32(self._file, self._nchannels)
501n/a self._file.write(self._info)
502n/a self._file.write(b'\0'*(header_size - len(self._info) - 24))
503n/a
504n/a def _patchheader(self):
505n/a if self._form_length_pos is None:
506n/a raise OSError('cannot seek')
507n/a self._file.seek(self._form_length_pos)
508n/a _write_u32(self._file, self._datawritten)
509n/a self._datalength = self._datawritten
510n/a self._file.seek(0, 2)
511n/a
512n/adef open(f, mode=None):
513n/a if mode is None:
514n/a if hasattr(f, 'mode'):
515n/a mode = f.mode
516n/a else:
517n/a mode = 'rb'
518n/a if mode in ('r', 'rb'):
519n/a return Au_read(f)
520n/a elif mode in ('w', 'wb'):
521n/a return Au_write(f)
522n/a else:
523n/a raise Error("mode must be 'r', 'rb', 'w', or 'wb'")
524n/a
525n/aopenfp = open