Coverage for src/audioio/audiowriter.py: 98%
372 statements
« prev ^ index » next coverage.py v7.6.3, created at 2024-10-15 07:29 +0000
« prev ^ index » next coverage.py v7.6.3, created at 2024-10-15 07:29 +0000
1"""Write numpy arrays of floats to audio files.
3- `write_audio()`: write audio data to file.
4- `available_formats()`: audio file formats supported by any of the installed audio modules.
5- `available_encodings()`: encodings of an audio file format supported by any of the installed audio modules.
6- `format_from_extension()`: deduce audio file format from file extension.
8The data to be written are 1-D or 2-D numpy arrays of floats ranging
9between -1 and 1 with first axis time and (optional) second axis channel.
11For support of more audio formats, you might need to install
12additional packages.
13See [installation](https://bendalab.github.io/audioio/installation)
14for further instructions.
16For a demo, run the script as:
17```
18python -m src.audioio.audiowriter
19```
21"""
23import os
24import sys
25import subprocess
26import numpy as np
27from .audiomodules import *
28from .riffmetadata import write_wave as audioio_write_wave
29from .riffmetadata import append_riff
32def format_from_extension(filepath):
33 """Deduce audio file format from file extension.
35 Parameters
36 ----------
37 filepath: string
38 Name of the audio file.
40 Returns
41 -------
42 format: string
43 Audio format deduced from file extension.
44 """
45 if not filepath:
46 return None
47 ext = os.path.splitext(filepath)[1]
48 if not ext:
49 return None
50 if ext[0] == '.':
51 ext = ext[1:]
52 if not ext:
53 return None
54 ext = ext.upper()
55 if ext == 'WAVE':
56 return 'WAV'
57 ext = ext.replace('MPEG' , 'MP')
58 return ext
61def formats_wave():
62 """Audio file formats supported by the wave module.
64 Returns
65 -------
66 formats: list of strings
67 List of supported file formats as strings.
68 """
69 if not audio_modules['wave']:
70 return []
71 else:
72 return ['WAV']
75def encodings_wave(format):
76 """Encodings of an audio file format supported by the wave module.
78 Parameters
79 ----------
80 format: str
81 The file format.
83 Returns
84 -------
85 encodings: list of strings
86 List of supported encodings as strings.
87 """
88 if not audio_modules['wave']:
89 return []
90 elif format.upper() != 'WAV':
91 return []
92 else:
93 return ['PCM_32', 'PCM_16', 'PCM_U8']
96def write_wave(filepath, data, rate, metadata=None, locs=None,
97 labels=None, format=None, encoding=None,
98 marker_hint='cue'):
99 """Write audio data using the wave module from pythons standard libray.
101 Documentation
102 -------------
103 https://docs.python.org/3.8/library/wave.html
105 Parameters
106 ----------
107 filepath: string
108 Full path and name of the file to write.
109 data: 1-D or 2-D array of floats
110 Array with the data (first index time, second index channel,
111 values within -1.0 and 1.0).
112 rate: float
113 Sampling rate of the data in Hertz.
114 metadata: None or nested dict
115 Metadata as key-value pairs. Values can be strings, integers,
116 or dictionaries.
117 locs: None or 1-D or 2-D array of ints
118 Marker positions (first column) and spans (optional second column)
119 for each marker (rows).
120 labels: None or 2-D array of string objects
121 Labels (first column) and texts (optional second column)
122 for each marker (rows).
123 format: string or None
124 File format, only 'WAV' is supported.
125 encoding: string or None
126 Encoding of the data: 'PCM_32', 'PCM_16', or 'PCM_U8'.
127 If None or empty string use 'PCM_16'.
128 marker_hint: str
129 - 'cue': store markers in cue and and adtl chunks.
130 - 'lbl': store markers in avisoft lbl chunk.
132 Raises
133 ------
134 ImportError
135 The wave module is not installed.
136 *
137 Writing of the data failed.
138 ValueError
139 File format or encoding not supported.
140 """
141 if not audio_modules['wave']:
142 raise ImportError
143 if not filepath:
144 raise ValueError('no file specified!')
146 if not format:
147 format = format_from_extension(filepath)
148 if format and format.upper() != 'WAV':
149 raise ValueError('file format %s not supported by wave module' % format)
151 wave_encodings = {'PCM_32': [4, 'i4'],
152 'PCM_16': [2, 'i2'],
153 'PCM_U8': [1, 'u1'] }
154 if not encoding:
155 encoding = 'PCM_16'
156 encoding = encoding.upper()
157 if encoding not in wave_encodings:
158 raise ValueError('file encoding %s not supported by wave module' % encoding)
159 sampwidth = wave_encodings[encoding][0]
160 dtype = wave_encodings[encoding][1]
162 wf = wave.open(filepath, 'w') # 'with' is not supported by wave
163 channels = 1
164 if len(data.shape) > 1:
165 channels = data.shape[1]
166 wf.setnchannels(channels)
167 wf.setnframes(len(data))
168 wf.setframerate(int(rate))
169 wf.setsampwidth(sampwidth)
170 factor = 2**(sampwidth*8-1)
171 if sampwidth == 1:
172 buffer = np.floor((data+1.0) * factor).astype(dtype)
173 buffer[data >= 1.0] = 2*factor - 1
174 else:
175 buffer = np.floor(data * factor).astype(dtype)
176 buffer[data >= 1.0] = factor - 1
177 wf.writeframes(buffer.tobytes())
178 wf.close()
179 append_riff(filepath, metadata, locs, labels, rate, marker_hint)
182def formats_ewave():
183 """Audio file formats supported by the ewave module.
185 Returns
186 -------
187 formats: list of strings
188 List of supported file formats as strings.
189 """
190 if not audio_modules['ewave']:
191 return []
192 else:
193 return ['WAV', 'WAVEX']
196def encodings_ewave(format):
197 """Encodings of an audio file format supported by the ewave module.
199 Parameters
200 ----------
201 format: str
202 The file format.
204 Returns
205 -------
206 encodings: list of strings
207 List of supported encodings as strings.
208 """
209 if not audio_modules['ewave']:
210 return []
211 elif format.upper() != 'WAV' and format.upper() != 'WAVEX':
212 return []
213 else:
214 return ['PCM_64', 'PCM_32', 'PCM_16', 'FLOAT', 'DOUBLE']
217def write_ewave(filepath, data, rate, metadata=None, locs=None,
218 labels=None, format=None, encoding=None,
219 marker_hint='cue'):
220 """Write audio data using the ewave module from pythons standard libray.
222 Documentation
223 -------------
224 https://github.com/melizalab/py-ewave
226 Parameters
227 ----------
228 filepath: string
229 Full path and name of the file to write.
230 data: 1-D or 2-D array of floats
231 Array with the data (first index time, second index channel,
232 values within -1.0 and 1.0).
233 rate: float
234 Sampling rate of the data in Hertz.
235 metadata: None or nested dict
236 Metadata as key-value pairs. Values can be strings, integers,
237 or dictionaries.
238 locs: None or 1-D or 2-D array of ints
239 Marker positions (first column) and spans (optional second column)
240 for each marker (rows).
241 labels: None or 2-D array of string objects
242 Labels (first column) and texts (optional second column)
243 for each marker (rows).
244 format: string or None
245 File format, only 'WAV' and 'WAVEX' are supported.
246 encoding: string or None
247 Encoding of the data: 'PCM_64', 'PCM_32', PCM_16', 'FLOAT', 'DOUBLE'
248 If None or empty string use 'PCM_16'.
249 marker_hint: str
250 - 'cue': store markers in cue and and adtl chunks.
251 - 'lbl': store markers in avisoft lbl chunk.
253 Raises
254 ------
255 ImportError
256 The ewave module is not installed.
257 *
258 Writing of the data failed.
259 ValueError
260 File format or encoding not supported.
261 """
262 if not audio_modules['ewave']:
263 raise ImportError
264 if not filepath:
265 raise ValueError('no file specified!')
267 if not format:
268 format = format_from_extension(filepath)
269 if format and format.upper() != 'WAV' and format.upper() != 'WAVEX':
270 raise ValueError('file format %s not supported by ewave module' % format)
272 ewave_encodings = {'PCM_64': 'l',
273 'PCM_32': 'i',
274 'PCM_16': 'h',
275 'FLOAT': 'f',
276 'DOUBLE': 'd' }
277 if not encoding:
278 encoding = 'PCM_16'
279 encoding = encoding.upper()
280 if encoding not in ewave_encodings:
281 raise ValueError('file encoding %s not supported by ewave module' % encoding)
283 channels = 1
284 if len(data.shape) > 1:
285 channels = data.shape[1]
287 with ewave.open(filepath, 'w', sampling_rate=int(rate),
288 dtype=ewave_encodings[encoding], nchannels=channels) as wf:
289 wf.write(data, scale=True)
290 append_riff(filepath, metadata, locs, labels, rate, marker_hint)
293def formats_wavfile():
294 """Audio file formats supported by the scipy.io.wavfile module.
296 Returns
297 -------
298 formats: list of strings
299 List of supported file formats as strings.
300 """
301 if not audio_modules['scipy.io.wavfile']:
302 return []
303 else:
304 return ['WAV']
307def encodings_wavfile(format):
308 """Encodings of an audio file format supported by the scipy.io.wavfile module.
310 Parameters
311 ----------
312 format: str
313 The file format.
315 Returns
316 -------
317 encodings: list of strings
318 List of supported encodings as strings.
319 """
320 if not audio_modules['scipy.io.wavfile']:
321 return []
322 elif format.upper() != 'WAV':
323 return []
324 else:
325 return ['PCM_U8', 'PCM_16', 'PCM_32', 'PCM_64', 'FLOAT', 'DOUBLE']
328def write_wavfile(filepath, data, rate, metadata=None, locs=None,
329 labels=None, format=None, encoding=None,
330 marker_hint='cue'):
331 """Write audio data using the scipy.io.wavfile module.
333 Documentation
334 -------------
335 http://docs.scipy.org/doc/scipy/reference/io.html
337 Parameters
338 ----------
339 filepath: string
340 Full path and name of the file to write.
341 data: 1-D or 2-D array of floats
342 Array with the data (first index time, second index channel,
343 values within -1.0 and 1.0).
344 rate: float
345 Sampling rate of the data in Hertz.
346 metadata: None or nested dict
347 Metadata as key-value pairs. Values can be strings, integers,
348 or dictionaries.
349 locs: None or 1-D or 2-D array of ints
350 Marker positions (first column) and spans (optional second column)
351 for each marker (rows).
352 labels: None or 2-D array of string objects
353 Labels (first column) and texts (optional second column)
354 for each marker (rows).
355 format: string or None
356 File format, only 'WAV' is supported.
357 encoding: string or None
358 Encoding of the data: 'PCM_64', 'PCM_32', PCM_16', 'PCM_U8', 'FLOAT', 'DOUBLE'
359 If None or empty string use 'PCM_16'.
360 marker_hint: str
361 - 'cue': store markers in cue and and adtl chunks.
362 - 'lbl': store markers in avisoft lbl chunk.
364 Raises
365 ------
366 ImportError
367 The wavfile module is not installed.
368 ValueError
369 File format or encoding not supported.
370 *
371 Writing of the data failed.
372 """
373 if not audio_modules['scipy.io.wavfile']:
374 raise ImportError
375 if not filepath:
376 raise ValueError('no file specified!')
378 if not format:
379 format = format_from_extension(filepath)
380 if format and format.upper() != 'WAV':
381 raise ValueError('file format %s not supported by scipy.io.wavfile module' % format)
383 wave_encodings = {'PCM_U8': [1, 'u1'],
384 'PCM_16': [2, 'i2'],
385 'PCM_32': [4, 'i4'],
386 'PCM_64': [8, 'i8'],
387 'FLOAT': [4, 'f'],
388 'DOUBLE': [8, 'd']}
389 if not encoding:
390 encoding = 'PCM_16'
391 encoding = encoding.upper()
392 if encoding not in wave_encodings:
393 raise ValueError('file encoding %s not supported by scipy.io.wavfile module' % encoding)
394 sampwidth = wave_encodings[encoding][0]
395 dtype = wave_encodings[encoding][1]
396 if sampwidth == 1:
397 factor = 2**(sampwidth*8-1)
398 buffer = np.floor((data+1.0) * factor).astype(dtype)
399 buffer[data >= 1.0] = 2*factor - 1
400 elif dtype[0] == 'i':
401 factor = 2**(sampwidth*8-1)
402 buffer = np.floor(data * factor).astype(dtype)
403 buffer[data >= 1.0] = factor - 1
404 else:
405 buffer = data.astype(dtype, copy=False)
406 wavfile.write(filepath, int(rate), buffer)
407 append_riff(filepath, metadata, locs, labels, rate, marker_hint)
410def formats_soundfile():
411 """Audio file formats supported by the SoundFile module.
413 Returns
414 -------
415 formats: list of strings
416 List of supported file formats as strings.
417 """
418 if not audio_modules['soundfile']:
419 return []
420 else:
421 return sorted(list(soundfile.available_formats()))
424def encodings_soundfile(format):
425 """Encodings of an audio file format supported by the SoundFile module.
427 Parameters
428 ----------
429 format: str
430 The file format.
432 Returns
433 -------
434 encodings: list of strings
435 List of supported encodings as strings.
436 """
437 if not audio_modules['soundfile']:
438 return []
439 else:
440 return sorted(list(soundfile.available_subtypes(format)))
443def write_soundfile(filepath, data, rate, metadata=None, locs=None,
444 labels=None, format=None, encoding=None,
445 marker_hint='cue'):
446 """Write audio data using the SoundFile module (based on libsndfile).
448 Documentation
449 -------------
450 http://pysoundfile.readthedocs.org
452 Parameters
453 ----------
454 filepath: string
455 Full path and name of the file to write.
456 data: 1-D or 2-D array of floats
457 Array with the data (first index time, second index channel,
458 values within -1.0 and 1.0).
459 rate: float
460 Sampling rate of the data in Hertz.
461 metadata: None or nested dict
462 Metadata as key-value pairs. Values can be strings, integers,
463 or dictionaries.
464 locs: None or 1-D or 2-D array of ints
465 Marker positions (first column) and spans (optional second column)
466 for each marker (rows).
467 labels: None or 2-D array of string objects
468 Labels (first column) and texts (optional second column)
469 for each marker (rows).
470 format: string or None
471 File format.
472 encoding: string or None
473 Encoding of the data.
474 If None or empty string use 'PCM_16'.
475 marker_hint: str
476 - 'cue': store markers in cue and and adtl chunks.
477 - 'lbl': store markers in avisoft lbl chunk.
479 Raises
480 ------
481 ImportError
482 The SoundFile module is not installed.
483 *
484 Writing of the data failed.
485 """
486 if not audio_modules['soundfile']:
487 raise ImportError
488 if not filepath:
489 raise ValueError('no file specified!')
491 if not format:
492 format = format_from_extension(filepath)
493 if format:
494 format = format.upper()
496 if not encoding:
497 encoding = 'PCM_16'
498 encoding = encoding.upper()
500 soundfile.write(filepath, data, int(rate), format=format,
501 subtype=encoding)
502 try:
503 append_riff(filepath, metadata, locs, labels, rate, marker_hint)
504 except ValueError:
505 pass
508def formats_wavefile():
509 """Audio file formats supported by the wavefile module.
511 Returns
512 -------
513 formats: list of strings
514 List of supported file formats as strings.
515 """
516 if not audio_modules['wavefile']:
517 return []
518 formats = []
519 for attr in dir(wavefile.Format):
520 v = getattr(wavefile.Format, attr)
521 if ( isinstance(v, int)
522 and v & wavefile.Format.TYPEMASK > 0
523 and v != wavefile.Format.TYPEMASK ):
524 formats.append(attr)
525 return sorted(formats)
528def encodings_wavefile(format):
529 """Encodings supported by the wavefile module.
531 Parameters
532 ----------
533 format: str
534 The file format (ignored).
536 Returns
537 -------
538 encodings: list of strings
539 List of supported encodings as strings.
540 """
541 if not audio_modules['wavefile']:
542 return []
543 if not format.upper() in formats_wavefile():
544 return []
545 encodings = []
546 for attr in dir(wavefile.Format):
547 v = getattr(wavefile.Format, attr)
548 if ( isinstance(v, int)
549 and v & wavefile.Format.SUBMASK > 0
550 and v != wavefile.Format.SUBMASK ):
551 encodings.append(attr)
552 return sorted(encodings)
555def write_wavefile(filepath, data, rate, metadata=None, locs=None,
556 labels=None, format=None, encoding=None,
557 marker_hint='cue'):
558 """Write audio data using the wavefile module (based on libsndfile).
560 Documentation
561 -------------
562 https://github.com/vokimon/python-wavefile
564 Parameters
565 ----------
566 filepath: string
567 Full path and name of the file to write.
568 data: 1-D or 2-D array of floats
569 Array with the data (first index time, second index channel,
570 values within -1.0 and 1.0).
571 rate: float
572 Sampling rate of the data in Hertz.
573 metadata: None or nested dict
574 Metadata as key-value pairs. Values can be strings, integers,
575 or dictionaries.
576 locs: None or 1-D or 2-D array of ints
577 Marker positions (first column) and spans (optional second column)
578 for each marker (rows).
579 labels: None or 2-D array of string objects
580 Labels (first column) and texts (optional second column)
581 for each marker (rows).
582 format: string or None
583 File format as in wavefile.Format.
584 encoding: string or None
585 Encoding of the data as in wavefile.Format.
586 If None or empty string use 'PCM_16'.
587 marker_hint: str
588 - 'cue': store markers in cue and and adtl chunks.
589 - 'lbl': store markers in avisoft lbl chunk.
591 Raises
592 ------
593 ImportError
594 The wavefile module is not installed.
595 ValueError
596 File format or encoding not supported.
597 *
598 Writing of the data failed.
599 """
600 if not audio_modules['wavefile']:
601 raise ImportError
602 if not filepath:
603 raise ValueError('no file specified!')
605 if not format:
606 format = format_from_extension(filepath)
607 format = format.upper()
608 try:
609 format_value = getattr(wavefile.Format, format)
610 except AttributeError:
611 raise ValueError('file format %s not supported by wavefile module' % format)
613 if not encoding:
614 encodings = encodings_wavefile(format)
615 encoding = encodings[0]
616 if 'PCM_16' in encodings:
617 encoding = 'PCM_16'
618 encoding = encoding.upper()
619 try:
620 encoding_value = getattr(wavefile.Format, encoding)
621 except AttributeError:
622 raise ValueError('file encoding %s not supported by wavefile module' % encoding)
624 channels = 1
625 if len(data.shape) > 1:
626 channels = data.shape[1]
627 else:
628 data = data.reshape((-1, 1))
629 with wavefile.WaveWriter(filepath, channels=channels,
630 samplerate=int(rate),
631 format=format_value|encoding_value) as w:
632 w.write(data.T)
633 try:
634 append_riff(filepath, metadata, locs, labels, rate, marker_hint)
635 except ValueError:
636 pass
639def formats_pydub():
640 """Audio file formats supported by the Pydub module.
642 Returns
643 -------
644 formats: list of strings
645 List of supported file formats as strings.
646 """
647 if not audio_modules['pydub']:
648 return []
649 formats = []
650 command = [pydub.AudioSegment.converter, '-formats']
651 with open(os.devnull, 'rb') as devnull:
652 p = subprocess.Popen(command, stdin=devnull, stdout=subprocess.PIPE,
653 stderr=subprocess.PIPE, universal_newlines=True)
654 skip = True
655 for line in p.communicate()[0].split('\n'):
656 if '--' in line[:3]:
657 skip = False
658 continue
659 if skip:
660 continue
661 cols = line.split()
662 if len(cols) > 2 and 'E' in cols[0]:
663 formats.append(cols[1].upper())
664 return formats
666def encodings_pydub(format):
667 """Encodings of an audio file format supported by the Pydub module.
669 Parameters
670 ----------
671 format: str
672 The file format.
674 Returns
675 -------
676 encodings: list of strings
677 List of supported encodings as strings.
678 """
679 pydub_encodings = {'pcm_s16le': 'PCM_16',
680 'pcm_s24le': 'PCM_24',
681 'pcm_s32le': 'PCM_32',
682 'pcm_f32le': 'FLOAT',
683 'pcm_f64le': 'DOUBLE',
684 }
685 if not audio_modules['pydub']:
686 return []
687 if format.upper() not in formats_pydub():
688 return []
689 encodings = []
690 command = [pydub.AudioSegment.converter, '-encoders']
691 with open(os.devnull, 'rb') as devnull:
692 p = subprocess.Popen(command, stdin=devnull, stdout=subprocess.PIPE,
693 stderr=subprocess.PIPE, universal_newlines=True)
694 skip = True
695 for line in p.communicate()[0].split('\n'):
696 if '--' in line[:3]:
697 skip = False
698 continue
699 if skip:
700 continue
701 cols = line.split()
702 if len(cols) > 2 and cols[0][0] == 'A':
703 encoding = cols[1]
704 if encoding in pydub_encodings:
705 encoding = pydub_encodings[encoding]
706 encodings.append(encoding.upper())
707 return encodings
709def write_pydub(filepath, data, rate, metadata=None, locs=None,
710 labels=None, format=None, encoding=None,
711 marker_hint='cue'):
712 """Write audio data using the Pydub module.
714 Documentation
715 -------------
716 https://github.com/jiaaro/pydub
718 Parameters
719 ----------
720 filepath: string
721 Full path and name of the file to write.
722 data: 1-D or 2-D array of floats
723 Array with the data (first index time, second index channel,
724 values within -1.0 and 1.0).
725 rate: float
726 Sampling rate of the data in Hertz.
727 metadata: None or nested dict
728 Metadata as key-value pairs. Values can be strings, integers,
729 or dictionaries.
730 locs: None or 1-D or 2-D array of ints
731 Marker positions (first column) and spans (optional second column)
732 for each marker (rows).
733 labels: None or 2-D array of string objects
734 Labels (first column) and texts (optional second column)
735 for each marker (rows).
736 format: string or None
737 File format, everything ffmpeg or avtools are supporting.
738 encoding: string or None
739 Encoding of the data.
740 If None or empty string use 'PCM_16'.
741 marker_hint: str
742 - 'cue': store markers in cue and and adtl chunks.
743 - 'lbl': store markers in avisoft lbl chunk.
745 Raises
746 ------
747 ImportError
748 The Pydub module is not installed.
749 *
750 Writing of the data failed.
751 ValueError
752 File format or encoding not supported.
753 """
754 if not audio_modules['pydub']:
755 raise ImportError
756 if not filepath:
757 raise ValueError('no file specified!')
759 if not format:
760 format = format_from_extension(filepath)
761 if format and format.upper() not in formats_pydub():
762 raise ValueError('file format %s not supported by Pydub module' % format)
764 pydub_encodings = {'PCM_16': 'pcm_s16le',
765 'PCM_24': 'pcm_s24le',
766 'PCM_32': 'pcm_s32le',
767 'DOUBLE': 'pcm_f32le',
768 'FLOAT': 'pcm_f64le',
769 }
770 if encoding:
771 encoding = encoding.upper()
772 if encoding in pydub_encodings:
773 encoding = pydub_encodings[encoding]
774 if encoding not in encodings_pydub(format):
775 raise ValueError('file encoding %s not supported by Pydub module' % encoding)
776 encoding = encoding.lower()
777 else:
778 encoding = None
780 channels = 1
781 if len(data.shape) > 1:
782 channels = data.shape[1]
783 int_data = (data*(2**31-1)).astype(np.int32)
784 sound = pydub.AudioSegment(int_data.ravel(), sample_width=4,
785 frame_rate=rate, channels=channels)
786 sound.export(filepath, format=format.lower(), codec=encoding)
787 try:
788 append_riff(filepath, metadata, locs, labels, rate, marker_hint)
789 except ValueError:
790 pass
793audio_formats_funcs = (
794 ('soundfile', formats_soundfile),
795 ('wavefile', formats_wavefile),
796 ('wave', formats_wave),
797 ('ewave', formats_ewave),
798 ('scipy.io.wavfile', formats_wavfile),
799 ('pydub', formats_pydub)
800 )
801""" List of implemented formats() functions.
803Each element of the list is a tuple with the module's name and the formats() function.
804"""
807def available_formats():
808 """Audio file formats supported by any of the installed audio modules.
810 Returns
811 -------
812 formats: list of strings
813 List of supported file formats as strings.
815 Examples
816 --------
817 ```
818 >>> from audioio import available_formats
819 >>> f = available_formats()
820 >>> printf(f)
821 ['3G2', '3GP', 'A64', 'AC3', 'ADTS', 'ADX', 'AIFF', ..., 'WAV', 'WAVEX', 'WEBM', 'WEBM_CHUNK', 'WEBM_DASH_MANIFEST', 'WEBP', 'WEBVTT', 'WTV', 'WV', 'WVE', 'XI', 'XV', 'YUV4MPEGPIPE']
822 ```
823 """
824 formats = set()
825 for module, formats_func in audio_formats_funcs:
826 formats |= set(formats_func())
827 return sorted(list(formats))
830audio_encodings_funcs = (
831 ('soundfile', encodings_soundfile),
832 ('wavefile', encodings_wavefile),
833 ('wave', encodings_wave),
834 ('ewave', encodings_ewave),
835 ('scipy.io.wavfile', encodings_wavfile),
836 ('pydub', encodings_pydub)
837 )
838""" List of implemented encodings() functions.
840Each element of the list is a tuple with the module's name and the encodings() function.
841"""
844def available_encodings(format):
845 """Encodings of an audio file format supported by any of the installed audio modules.
847 Parameters
848 ----------
849 format: str
850 The file format.
852 Returns
853 -------
854 encodings: list of strings
855 List of supported encodings as strings.
857 Example
858 -------
859 ```
860 >>> from audioio import available_encodings
861 >>> e = available_encodings('WAV')
862 >>> printf(e)
863 ['ALAW', 'DOUBLE', 'FLOAT', 'G721_32', 'GSM610', 'IMA_ADPCM', 'MS_ADPCM', 'PCM_16', 'PCM_24', 'PCM_32', 'PCM_U8', 'ULAW']
864 ```
865 """
866 for module, encodings_func in audio_encodings_funcs:
867 encs = encodings_func(format)
868 if len(encs) > 0:
869 return encs
870 return []
873audio_writer_funcs = (
874 ('soundfile', write_soundfile),
875 ('wavefile', write_wavefile),
876 ('wave', write_wave),
877 ('ewave', write_ewave),
878 ('scipy.io.wavfile', write_wavfile),
879 ('pydub', write_pydub)
880 )
881""" List of implemented write() functions.
883Each element of the list is a tuple with the module's name and the write() function.
884"""
887def write_audio(filepath, data, rate, metadata=None, locs=None,
888 labels=None, format=None, encoding=None,
889 marker_hint='cue', verbose=0):
890 """Write audio data, metadata, and marker to file.
892 Parameters
893 ----------
894 filepath: string
895 Full path and name of the file to write.
896 data: 1-D or 2-D array of floats
897 Array with the data (first index time, second index channel,
898 values within -1.0 and 1.0).
899 rate: float
900 Sampling rate of the data in Hertz.
901 metadata: None or nested dict
902 Metadata as key-value pairs. Values can be strings, integers,
903 or dictionaries.
904 locs: None or 1-D or 2-D array of ints
905 Marker positions (first column) and spans (optional second column)
906 for each marker (rows).
907 labels: None or 2-D array of string objects
908 Labels (first column) and texts (optional second column)
909 for each marker (rows).
910 format: string or None
911 File format. If None deduce file format from filepath.
912 See `available_formats()` for possible values.
913 encoding: string or None
914 Encoding of the data. See `available_encodings()` for possible values.
915 If None or empty string use 'PCM_16'.
916 marker_hint: str
917 - 'cue': store markers in cue and and adtl chunks.
918 - 'lbl': store markers in avisoft lbl chunk.
919 verbose: int
920 If >0 show detailed error/warning messages.
922 Raises
923 ------
924 ValueError
925 `filepath` is empty string.
926 IOError
927 Writing of the data failed.
929 Examples
930 --------
931 ```
932 import numpy as np
933 from audioio import write_audio
935 rate = 28000.0
936 freq = 800.0
937 time = np.arange(0.0, 1.0, 1/rate) # one second
938 data = np.sin(2.0*np.p*freq*time) # 800Hz sine wave
939 md = dict(Artist='underscore_') # metadata
941 write_audio('audio/file.wav', data, rate, md)
942 ```
943 """
944 if not filepath:
945 raise ValueError('no file specified!')
947 # write audio with metadata and markers:
948 if not format:
949 format = format_from_extension(filepath)
950 if format == 'WAV' and (metadata is not None or locs is not None):
951 try:
952 audioio_write_wave(filepath, data, rate, metadata,
953 locs, labels, encoding, marker_hint)
954 return
955 except ValueError:
956 pass
957 # write audio file by trying available modules:
958 errors = [f'failed to write data to file "{filepath}":']
959 for lib, write_file in audio_writer_funcs:
960 if not audio_modules[lib]:
961 continue
962 try:
963 write_file(filepath, data, rate, metadata, locs,
964 labels, format, encoding, marker_hint)
965 success = True
966 if verbose > 0:
967 print('wrote data to file "%s" using %s module' %
968 (filepath, lib))
969 if verbose > 1:
970 print(' sampling rate: %g Hz' % rate)
971 print(' channels : %d' % (data.shape[1] if len(data.shape) > 1 else 1))
972 print(' frames : %d' % len(data))
973 return
974 except Exception as e:
975 errors.append(f' {lib} failed: {str(e)}')
976 raise IOError('\n'.join(errors))
979def demo(file_path, channels=2, encoding=''):
980 """Demo of the audiowriter functions.
982 Parameters
983 ----------
984 file_path: string
985 File path of an audio file.
986 encoding: string
987 Encoding to be used.
988 """
989 print('generate data ...')
990 rate = 44100.0
991 t = np.arange(0.0, 1.0, 1.0/rate)
992 data = np.zeros((len(t), channels))
993 for c in range(channels):
994 data[:,c] = np.sin(2.0*np.pi*(440.0+c*8.0)*t)
996 print("write_audio(%s) ..." % file_path)
997 write_audio(file_path, data, rate, encoding=encoding, verbose=2)
999 print('done.')
1002def main(*args):
1003 """Call demo with command line arguments.
1005 Parameters
1006 ----------
1007 args: list of strings
1008 Command line arguments as provided by sys.argv[1:]
1009 """
1010 help = False
1011 file_path = None
1012 encoding = ''
1013 mod = False
1014 nchan = False
1015 channels = 2
1016 for arg in args:
1017 if mod:
1018 select_module(arg)
1019 mod = False
1020 elif nchan:
1021 channels = int(arg)
1022 nchan = False
1023 elif arg == '-h':
1024 help = True
1025 break
1026 elif arg == '-m':
1027 mod = True
1028 elif arg == '-n':
1029 nchan = True
1030 elif file_path is None:
1031 file_path = arg
1032 else:
1033 encoding = arg
1034 break
1035 if file_path is None:
1036 file_path = 'test.wav'
1038 if help:
1039 print('')
1040 print('Usage:')
1041 print(' python -m src.audioio.audiowriter [-m module] [-n channels] [<filename>] [<encoding>]')
1042 return
1044 demo(file_path, channels=channels, encoding=encoding)
1047if __name__ == "__main__":
1048 main(*sys.argv[1:])