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

Python code coverage for Lib/wave.py

#countcontent
1n/a"""Stuff to parse WAVE files.
2n/a
3n/aUsage.
4n/a
5n/aReading WAVE files:
6n/a f = wave.open(file, 'r')
7n/awhere file is either the name of a file or an open file pointer.
8n/aThe open file pointer must have methods read(), seek(), and close().
9n/aWhen the setpos() and rewind() methods are not used, the seek()
10n/amethod is not necessary.
11n/a
12n/aThis returns an instance of a class with the following public methods:
13n/a getnchannels() -- returns number of audio channels (1 for
14n/a mono, 2 for stereo)
15n/a getsampwidth() -- returns sample width in bytes
16n/a getframerate() -- returns sampling frequency
17n/a getnframes() -- returns number of audio frames
18n/a getcomptype() -- returns compression type ('NONE' for linear samples)
19n/a getcompname() -- returns human-readable version of
20n/a compression type ('not compressed' linear samples)
21n/a getparams() -- returns a namedtuple consisting of all of the
22n/a above in the above order
23n/a getmarkers() -- returns None (for compatibility with the
24n/a aifc module)
25n/a getmark(id) -- raises an error since the mark does not
26n/a exist (for compatibility with the aifc module)
27n/a readframes(n) -- returns at most n frames of audio
28n/a rewind() -- rewind to the beginning of the audio stream
29n/a setpos(pos) -- seek to the specified position
30n/a tell() -- return the current position
31n/a close() -- close the instance (make it unusable)
32n/aThe position returned by tell() and the position given to setpos()
33n/aare compatible and have nothing to do with the actual position in the
34n/afile.
35n/aThe close() method is called automatically when the class instance
36n/ais destroyed.
37n/a
38n/aWriting WAVE files:
39n/a f = wave.open(file, 'w')
40n/awhere file is either the name of a file or an open file pointer.
41n/aThe open file pointer must have methods write(), tell(), seek(), and
42n/aclose().
43n/a
44n/aThis returns an instance of a class with the following public methods:
45n/a setnchannels(n) -- set the number of channels
46n/a setsampwidth(n) -- set the sample width
47n/a setframerate(n) -- set the frame rate
48n/a setnframes(n) -- set the number of frames
49n/a setcomptype(type, name)
50n/a -- set the compression type and the
51n/a human-readable compression type
52n/a setparams(tuple)
53n/a -- set all parameters at once
54n/a tell() -- return current position in output file
55n/a writeframesraw(data)
56n/a -- write audio frames without pathing up the
57n/a file header
58n/a writeframes(data)
59n/a -- write audio frames and patch up the file header
60n/a close() -- patch up the file header and close the
61n/a output file
62n/aYou should set the parameters before the first writeframesraw or
63n/awriteframes. The total number of frames does not need to be set,
64n/abut when it is set to the correct value, the header does not have to
65n/abe patched up.
66n/aIt is best to first set all parameters, perhaps possibly the
67n/acompression type, and then write audio frames using writeframesraw.
68n/aWhen all frames have been written, either call writeframes(b'') or
69n/aclose() to patch up the sizes in the header.
70n/aThe close() method is called automatically when the class instance
71n/ais destroyed.
72n/a"""
73n/a
74n/aimport builtins
75n/a
76n/a__all__ = ["open", "openfp", "Error", "Wave_read", "Wave_write"]
77n/a
78n/aclass Error(Exception):
79n/a pass
80n/a
81n/aWAVE_FORMAT_PCM = 0x0001
82n/a
83n/a_array_fmts = None, 'b', 'h', None, 'i'
84n/a
85n/aimport audioop
86n/aimport struct
87n/aimport sys
88n/afrom chunk import Chunk
89n/afrom collections import namedtuple
90n/a
91n/a_wave_params = namedtuple('_wave_params',
92n/a 'nchannels sampwidth framerate nframes comptype compname')
93n/a
94n/aclass Wave_read:
95n/a """Variables used in this class:
96n/a
97n/a These variables are available to the user though appropriate
98n/a methods of this class:
99n/a _file -- the open file with methods read(), close(), and seek()
100n/a set through the __init__() method
101n/a _nchannels -- the number of audio channels
102n/a available through the getnchannels() method
103n/a _nframes -- the number of audio frames
104n/a available through the getnframes() method
105n/a _sampwidth -- the number of bytes per audio sample
106n/a available through the getsampwidth() method
107n/a _framerate -- the sampling frequency
108n/a available through the getframerate() method
109n/a _comptype -- the AIFF-C compression type ('NONE' if AIFF)
110n/a available through the getcomptype() method
111n/a _compname -- the human-readable AIFF-C compression type
112n/a available through the getcomptype() method
113n/a _soundpos -- the position in the audio stream
114n/a available through the tell() method, set through the
115n/a setpos() method
116n/a
117n/a These variables are used internally only:
118n/a _fmt_chunk_read -- 1 iff the FMT chunk has been read
119n/a _data_seek_needed -- 1 iff positioned correctly in audio
120n/a file for readframes()
121n/a _data_chunk -- instantiation of a chunk class for the DATA chunk
122n/a _framesize -- size of one frame in the file
123n/a """
124n/a
125n/a def initfp(self, file):
126n/a self._convert = None
127n/a self._soundpos = 0
128n/a self._file = Chunk(file, bigendian = 0)
129n/a if self._file.getname() != b'RIFF':
130n/a raise Error('file does not start with RIFF id')
131n/a if self._file.read(4) != b'WAVE':
132n/a raise Error('not a WAVE file')
133n/a self._fmt_chunk_read = 0
134n/a self._data_chunk = None
135n/a while 1:
136n/a self._data_seek_needed = 1
137n/a try:
138n/a chunk = Chunk(self._file, bigendian = 0)
139n/a except EOFError:
140n/a break
141n/a chunkname = chunk.getname()
142n/a if chunkname == b'fmt ':
143n/a self._read_fmt_chunk(chunk)
144n/a self._fmt_chunk_read = 1
145n/a elif chunkname == b'data':
146n/a if not self._fmt_chunk_read:
147n/a raise Error('data chunk before fmt chunk')
148n/a self._data_chunk = chunk
149n/a self._nframes = chunk.chunksize // self._framesize
150n/a self._data_seek_needed = 0
151n/a break
152n/a chunk.skip()
153n/a if not self._fmt_chunk_read or not self._data_chunk:
154n/a raise Error('fmt chunk and/or data chunk missing')
155n/a
156n/a def __init__(self, f):
157n/a self._i_opened_the_file = None
158n/a if isinstance(f, str):
159n/a f = builtins.open(f, 'rb')
160n/a self._i_opened_the_file = f
161n/a # else, assume it is an open file object already
162n/a try:
163n/a self.initfp(f)
164n/a except:
165n/a if self._i_opened_the_file:
166n/a f.close()
167n/a raise
168n/a
169n/a def __del__(self):
170n/a self.close()
171n/a
172n/a def __enter__(self):
173n/a return self
174n/a
175n/a def __exit__(self, *args):
176n/a self.close()
177n/a
178n/a #
179n/a # User visible methods.
180n/a #
181n/a def getfp(self):
182n/a return self._file
183n/a
184n/a def rewind(self):
185n/a self._data_seek_needed = 1
186n/a self._soundpos = 0
187n/a
188n/a def close(self):
189n/a self._file = None
190n/a file = self._i_opened_the_file
191n/a if file:
192n/a self._i_opened_the_file = None
193n/a file.close()
194n/a
195n/a def tell(self):
196n/a return self._soundpos
197n/a
198n/a def getnchannels(self):
199n/a return self._nchannels
200n/a
201n/a def getnframes(self):
202n/a return self._nframes
203n/a
204n/a def getsampwidth(self):
205n/a return self._sampwidth
206n/a
207n/a def getframerate(self):
208n/a return self._framerate
209n/a
210n/a def getcomptype(self):
211n/a return self._comptype
212n/a
213n/a def getcompname(self):
214n/a return self._compname
215n/a
216n/a def getparams(self):
217n/a return _wave_params(self.getnchannels(), self.getsampwidth(),
218n/a self.getframerate(), self.getnframes(),
219n/a self.getcomptype(), self.getcompname())
220n/a
221n/a def getmarkers(self):
222n/a return None
223n/a
224n/a def getmark(self, id):
225n/a raise Error('no marks')
226n/a
227n/a def setpos(self, pos):
228n/a if pos < 0 or pos > self._nframes:
229n/a raise Error('position not in range')
230n/a self._soundpos = pos
231n/a self._data_seek_needed = 1
232n/a
233n/a def readframes(self, nframes):
234n/a if self._data_seek_needed:
235n/a self._data_chunk.seek(0, 0)
236n/a pos = self._soundpos * self._framesize
237n/a if pos:
238n/a self._data_chunk.seek(pos, 0)
239n/a self._data_seek_needed = 0
240n/a if nframes == 0:
241n/a return b''
242n/a data = self._data_chunk.read(nframes * self._framesize)
243n/a if self._sampwidth != 1 and sys.byteorder == 'big':
244n/a data = audioop.byteswap(data, self._sampwidth)
245n/a if self._convert and data:
246n/a data = self._convert(data)
247n/a self._soundpos = self._soundpos + len(data) // (self._nchannels * self._sampwidth)
248n/a return data
249n/a
250n/a #
251n/a # Internal methods.
252n/a #
253n/a
254n/a def _read_fmt_chunk(self, chunk):
255n/a wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack_from('<HHLLH', chunk.read(14))
256n/a if wFormatTag == WAVE_FORMAT_PCM:
257n/a sampwidth = struct.unpack_from('<H', chunk.read(2))[0]
258n/a self._sampwidth = (sampwidth + 7) // 8
259n/a else:
260n/a raise Error('unknown format: %r' % (wFormatTag,))
261n/a self._framesize = self._nchannels * self._sampwidth
262n/a self._comptype = 'NONE'
263n/a self._compname = 'not compressed'
264n/a
265n/aclass Wave_write:
266n/a """Variables used in this class:
267n/a
268n/a These variables are user settable through appropriate methods
269n/a of this class:
270n/a _file -- the open file with methods write(), close(), tell(), seek()
271n/a set through the __init__() method
272n/a _comptype -- the AIFF-C compression type ('NONE' in AIFF)
273n/a set through the setcomptype() or setparams() method
274n/a _compname -- the human-readable AIFF-C compression type
275n/a set through the setcomptype() or setparams() method
276n/a _nchannels -- the number of audio channels
277n/a set through the setnchannels() or setparams() method
278n/a _sampwidth -- the number of bytes per audio sample
279n/a set through the setsampwidth() or setparams() method
280n/a _framerate -- the sampling frequency
281n/a set through the setframerate() or setparams() method
282n/a _nframes -- the number of audio frames written to the header
283n/a set through the setnframes() or setparams() method
284n/a
285n/a These variables are used internally only:
286n/a _datalength -- the size of the audio samples written to the header
287n/a _nframeswritten -- the number of frames actually written
288n/a _datawritten -- the size of the audio samples actually written
289n/a """
290n/a
291n/a def __init__(self, f):
292n/a self._i_opened_the_file = None
293n/a if isinstance(f, str):
294n/a f = builtins.open(f, 'wb')
295n/a self._i_opened_the_file = f
296n/a try:
297n/a self.initfp(f)
298n/a except:
299n/a if self._i_opened_the_file:
300n/a f.close()
301n/a raise
302n/a
303n/a def initfp(self, file):
304n/a self._file = file
305n/a self._convert = None
306n/a self._nchannels = 0
307n/a self._sampwidth = 0
308n/a self._framerate = 0
309n/a self._nframes = 0
310n/a self._nframeswritten = 0
311n/a self._datawritten = 0
312n/a self._datalength = 0
313n/a self._headerwritten = False
314n/a
315n/a def __del__(self):
316n/a self.close()
317n/a
318n/a def __enter__(self):
319n/a return self
320n/a
321n/a def __exit__(self, *args):
322n/a self.close()
323n/a
324n/a #
325n/a # User visible methods.
326n/a #
327n/a def setnchannels(self, nchannels):
328n/a if self._datawritten:
329n/a raise Error('cannot change parameters after starting to write')
330n/a if nchannels < 1:
331n/a raise Error('bad # of channels')
332n/a self._nchannels = nchannels
333n/a
334n/a def getnchannels(self):
335n/a if not self._nchannels:
336n/a raise Error('number of channels not set')
337n/a return self._nchannels
338n/a
339n/a def setsampwidth(self, sampwidth):
340n/a if self._datawritten:
341n/a raise Error('cannot change parameters after starting to write')
342n/a if sampwidth < 1 or sampwidth > 4:
343n/a raise Error('bad sample width')
344n/a self._sampwidth = sampwidth
345n/a
346n/a def getsampwidth(self):
347n/a if not self._sampwidth:
348n/a raise Error('sample width not set')
349n/a return self._sampwidth
350n/a
351n/a def setframerate(self, framerate):
352n/a if self._datawritten:
353n/a raise Error('cannot change parameters after starting to write')
354n/a if framerate <= 0:
355n/a raise Error('bad frame rate')
356n/a self._framerate = int(round(framerate))
357n/a
358n/a def getframerate(self):
359n/a if not self._framerate:
360n/a raise Error('frame rate not set')
361n/a return self._framerate
362n/a
363n/a def setnframes(self, nframes):
364n/a if self._datawritten:
365n/a raise Error('cannot change parameters after starting to write')
366n/a self._nframes = nframes
367n/a
368n/a def getnframes(self):
369n/a return self._nframeswritten
370n/a
371n/a def setcomptype(self, comptype, compname):
372n/a if self._datawritten:
373n/a raise Error('cannot change parameters after starting to write')
374n/a if comptype not in ('NONE',):
375n/a raise Error('unsupported compression type')
376n/a self._comptype = comptype
377n/a self._compname = compname
378n/a
379n/a def getcomptype(self):
380n/a return self._comptype
381n/a
382n/a def getcompname(self):
383n/a return self._compname
384n/a
385n/a def setparams(self, params):
386n/a nchannels, sampwidth, framerate, nframes, comptype, compname = params
387n/a if self._datawritten:
388n/a raise Error('cannot change parameters after starting to write')
389n/a self.setnchannels(nchannels)
390n/a self.setsampwidth(sampwidth)
391n/a self.setframerate(framerate)
392n/a self.setnframes(nframes)
393n/a self.setcomptype(comptype, compname)
394n/a
395n/a def getparams(self):
396n/a if not self._nchannels or not self._sampwidth or not self._framerate:
397n/a raise Error('not all parameters set')
398n/a return _wave_params(self._nchannels, self._sampwidth, self._framerate,
399n/a self._nframes, self._comptype, self._compname)
400n/a
401n/a def setmark(self, id, pos, name):
402n/a raise Error('setmark() not supported')
403n/a
404n/a def getmark(self, id):
405n/a raise Error('no marks')
406n/a
407n/a def getmarkers(self):
408n/a return None
409n/a
410n/a def tell(self):
411n/a return self._nframeswritten
412n/a
413n/a def writeframesraw(self, data):
414n/a if not isinstance(data, (bytes, bytearray)):
415n/a data = memoryview(data).cast('B')
416n/a self._ensure_header_written(len(data))
417n/a nframes = len(data) // (self._sampwidth * self._nchannels)
418n/a if self._convert:
419n/a data = self._convert(data)
420n/a if self._sampwidth != 1 and sys.byteorder == 'big':
421n/a data = audioop.byteswap(data, self._sampwidth)
422n/a self._file.write(data)
423n/a self._datawritten += len(data)
424n/a self._nframeswritten = self._nframeswritten + nframes
425n/a
426n/a def writeframes(self, data):
427n/a self.writeframesraw(data)
428n/a if self._datalength != self._datawritten:
429n/a self._patchheader()
430n/a
431n/a def close(self):
432n/a try:
433n/a if self._file:
434n/a self._ensure_header_written(0)
435n/a if self._datalength != self._datawritten:
436n/a self._patchheader()
437n/a self._file.flush()
438n/a finally:
439n/a self._file = None
440n/a file = self._i_opened_the_file
441n/a if file:
442n/a self._i_opened_the_file = None
443n/a file.close()
444n/a
445n/a #
446n/a # Internal methods.
447n/a #
448n/a
449n/a def _ensure_header_written(self, datasize):
450n/a if not self._headerwritten:
451n/a if not self._nchannels:
452n/a raise Error('# channels not specified')
453n/a if not self._sampwidth:
454n/a raise Error('sample width not specified')
455n/a if not self._framerate:
456n/a raise Error('sampling rate not specified')
457n/a self._write_header(datasize)
458n/a
459n/a def _write_header(self, initlength):
460n/a assert not self._headerwritten
461n/a self._file.write(b'RIFF')
462n/a if not self._nframes:
463n/a self._nframes = initlength // (self._nchannels * self._sampwidth)
464n/a self._datalength = self._nframes * self._nchannels * self._sampwidth
465n/a try:
466n/a self._form_length_pos = self._file.tell()
467n/a except (AttributeError, OSError):
468n/a self._form_length_pos = None
469n/a self._file.write(struct.pack('<L4s4sLHHLLHH4s',
470n/a 36 + self._datalength, b'WAVE', b'fmt ', 16,
471n/a WAVE_FORMAT_PCM, self._nchannels, self._framerate,
472n/a self._nchannels * self._framerate * self._sampwidth,
473n/a self._nchannels * self._sampwidth,
474n/a self._sampwidth * 8, b'data'))
475n/a if self._form_length_pos is not None:
476n/a self._data_length_pos = self._file.tell()
477n/a self._file.write(struct.pack('<L', self._datalength))
478n/a self._headerwritten = True
479n/a
480n/a def _patchheader(self):
481n/a assert self._headerwritten
482n/a if self._datawritten == self._datalength:
483n/a return
484n/a curpos = self._file.tell()
485n/a self._file.seek(self._form_length_pos, 0)
486n/a self._file.write(struct.pack('<L', 36 + self._datawritten))
487n/a self._file.seek(self._data_length_pos, 0)
488n/a self._file.write(struct.pack('<L', self._datawritten))
489n/a self._file.seek(curpos, 0)
490n/a self._datalength = self._datawritten
491n/a
492n/adef open(f, mode=None):
493n/a if mode is None:
494n/a if hasattr(f, 'mode'):
495n/a mode = f.mode
496n/a else:
497n/a mode = 'rb'
498n/a if mode in ('r', 'rb'):
499n/a return Wave_read(f)
500n/a elif mode in ('w', 'wb'):
501n/a return Wave_write(f)
502n/a else:
503n/a raise Error("mode must be 'r', 'rb', 'w', or 'wb'")
504n/a
505n/aopenfp = open # B/W compatibility