Coverage for src / audioio / audiowriter.py: 98%
360 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-17 21:34 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-17 21:34 +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
28from pathlib import Path
29from .audiomodules import *
30from .riffmetadata import write_wave as audioio_write_wave
31from .riffmetadata import append_riff
34def format_from_extension(filepath):
35 """Deduce audio file format from file extension.
37 Parameters
38 ----------
39 filepath: str or Path or None
40 Name of the audio file.
42 Returns
43 -------
44 format: str
45 Audio format deduced from file extension.
46 """
47 if filepath is None:
48 return None
49 filepath = Path(filepath)
50 ext = filepath.suffix
51 if not ext:
52 return None
53 if ext[0] == '.':
54 ext = ext[1:]
55 if not ext:
56 return None
57 ext = ext.upper()
58 if ext == 'WAVE':
59 return 'WAV'
60 ext = ext.replace('MPEG' , 'MP')
61 return ext
64def formats_wave():
65 """Audio file formats supported by the wave module.
67 Returns
68 -------
69 formats: list of str
70 List of supported file formats as strings.
71 """
72 if not audio_modules['wave']:
73 return []
74 else:
75 return ['WAV']
78def encodings_wave(format):
79 """Encodings of an audio file format supported by the wave module.
81 Parameters
82 ----------
83 format: str
84 The file format.
86 Returns
87 -------
88 encodings: list of str
89 List of supported encodings as strings.
90 """
91 if not audio_modules['wave']:
92 return []
93 elif format.upper() != 'WAV':
94 return []
95 else:
96 return ['PCM_32', 'PCM_16', 'PCM_U8']
99def write_wave(filepath, data, rate, metadata=None, locs=None,
100 labels=None, format=None, encoding=None,
101 marker_hint='cue'):
102 """Write audio data using the wave module from pythons standard libray.
104 Documentation
105 -------------
106 https://docs.python.org/3.8/library/wave.html
108 Parameters
109 ----------
110 filepath: str or Path
111 Full path and name of the file to write.
112 data: 1-D or 2-D array of floats
113 Array with the data (first index time, second index channel,
114 values within -1.0 and 1.0).
115 rate: float
116 Sampling rate of the data in Hertz.
117 metadata: None or nested dict
118 Metadata as key-value pairs. Values can be strings, integers,
119 or dictionaries.
120 locs: None or 1-D or 2-D array of ints
121 Marker positions (first column) and spans (optional second column)
122 for each marker (rows).
123 labels: None or 2-D array of string objects
124 Labels (first column) and texts (optional second column)
125 for each marker (rows).
126 format: str or None
127 File format, only 'WAV' is supported.
128 encoding: str or None
129 Encoding of the data: 'PCM_32', 'PCM_16', or 'PCM_U8'.
130 If None or empty string use 'PCM_16'.
131 marker_hint: str
132 - 'cue': store markers in cue and and adtl chunks.
133 - 'lbl': store markers in avisoft lbl chunk.
135 Raises
136 ------
137 ImportError
138 The wave module is not installed.
139 *
140 Writing of the data failed.
141 ValueError
142 File format or encoding not supported.
143 """
144 if not audio_modules['wave']:
145 raise ImportError
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(os.fspath(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 str
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 str
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: str
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: str or None
245 File format, only 'WAV' and 'WAVEX' are supported.
246 encoding: str 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
265 if not format:
266 format = format_from_extension(filepath)
267 if format and format.upper() != 'WAV' and format.upper() != 'WAVEX':
268 raise ValueError('file format %s not supported by ewave module' % format)
270 ewave_encodings = {'PCM_64': 'l',
271 'PCM_32': 'i',
272 'PCM_16': 'h',
273 'FLOAT': 'f',
274 'DOUBLE': 'd' }
275 if not encoding:
276 encoding = 'PCM_16'
277 encoding = encoding.upper()
278 if encoding not in ewave_encodings:
279 raise ValueError('file encoding %s not supported by ewave module' % encoding)
281 channels = 1
282 if len(data.shape) > 1:
283 channels = data.shape[1]
285 with ewave.open(filepath, 'w', sampling_rate=int(rate),
286 dtype=ewave_encodings[encoding], nchannels=channels) as wf:
287 wf.write(data, scale=True)
288 append_riff(filepath, metadata, locs, labels, rate, marker_hint)
291def formats_wavfile():
292 """Audio file formats supported by the scipy.io.wavfile module.
294 Returns
295 -------
296 formats: list of str
297 List of supported file formats as strings.
298 """
299 if not audio_modules['scipy.io.wavfile']:
300 return []
301 else:
302 return ['WAV']
305def encodings_wavfile(format):
306 """Encodings of an audio file format supported by the scipy.io.wavfile module.
308 Parameters
309 ----------
310 format: str
311 The file format.
313 Returns
314 -------
315 encodings: list of str
316 List of supported encodings as strings.
317 """
318 if not audio_modules['scipy.io.wavfile']:
319 return []
320 elif format.upper() != 'WAV':
321 return []
322 else:
323 return ['PCM_U8', 'PCM_16', 'PCM_32', 'PCM_64', 'FLOAT', 'DOUBLE']
326def write_wavfile(filepath, data, rate, metadata=None, locs=None,
327 labels=None, format=None, encoding=None,
328 marker_hint='cue'):
329 """Write audio data using the scipy.io.wavfile module.
331 Documentation
332 -------------
333 http://docs.scipy.org/doc/scipy/reference/io.html
335 Parameters
336 ----------
337 filepath: str or Path
338 Full path and name of the file to write.
339 data: 1-D or 2-D array of floats
340 Array with the data (first index time, second index channel,
341 values within -1.0 and 1.0).
342 rate: float
343 Sampling rate of the data in Hertz.
344 metadata: None or nested dict
345 Metadata as key-value pairs. Values can be strings, integers,
346 or dictionaries.
347 locs: None or 1-D or 2-D array of ints
348 Marker positions (first column) and spans (optional second column)
349 for each marker (rows).
350 labels: None or 2-D array of string objects
351 Labels (first column) and texts (optional second column)
352 for each marker (rows).
353 format: str or None
354 File format, only 'WAV' is supported.
355 encoding: str or None
356 Encoding of the data: 'PCM_64', 'PCM_32', PCM_16', 'PCM_U8', 'FLOAT', 'DOUBLE'
357 If None or empty string use 'PCM_16'.
358 marker_hint: str
359 - 'cue': store markers in cue and and adtl chunks.
360 - 'lbl': store markers in avisoft lbl chunk.
362 Raises
363 ------
364 ImportError
365 The wavfile module is not installed.
366 ValueError
367 File format or encoding not supported.
368 *
369 Writing of the data failed.
370 """
371 if not audio_modules['scipy.io.wavfile']:
372 raise ImportError
374 if not format:
375 format = format_from_extension(filepath)
376 if format and format.upper() != 'WAV':
377 raise ValueError('file format %s not supported by scipy.io.wavfile module' % format)
379 wave_encodings = {'PCM_U8': [1, 'u1'],
380 'PCM_16': [2, 'i2'],
381 'PCM_32': [4, 'i4'],
382 'PCM_64': [8, 'i8'],
383 'FLOAT': [4, 'f'],
384 'DOUBLE': [8, 'd']}
385 if not encoding:
386 encoding = 'PCM_16'
387 encoding = encoding.upper()
388 if encoding not in wave_encodings:
389 raise ValueError('file encoding %s not supported by scipy.io.wavfile module' % encoding)
390 sampwidth = wave_encodings[encoding][0]
391 dtype = wave_encodings[encoding][1]
392 if sampwidth == 1:
393 factor = 2**(sampwidth*8-1)
394 buffer = np.floor((data+1.0) * factor).astype(dtype)
395 buffer[data >= 1.0] = 2*factor - 1
396 elif dtype[0] == 'i':
397 factor = 2**(sampwidth*8-1)
398 buffer = np.floor(data * factor).astype(dtype)
399 buffer[data >= 1.0] = factor - 1
400 else:
401 buffer = data.astype(dtype, copy=False)
402 wavfile.write(filepath, int(rate), buffer)
403 append_riff(filepath, metadata, locs, labels, rate, marker_hint)
406def formats_soundfile():
407 """Audio file formats supported by the SoundFile module.
409 Returns
410 -------
411 formats: list of str
412 List of supported file formats as strings.
413 """
414 if not audio_modules['soundfile']:
415 return []
416 else:
417 return sorted(list(soundfile.available_formats()))
420def encodings_soundfile(format):
421 """Encodings of an audio file format supported by the SoundFile module.
423 Parameters
424 ----------
425 format: str
426 The file format.
428 Returns
429 -------
430 encodings: list of str
431 List of supported encodings as strings.
432 """
433 if not audio_modules['soundfile']:
434 return []
435 else:
436 return sorted(list(soundfile.available_subtypes(format)))
439def write_soundfile(filepath, data, rate, metadata=None, locs=None,
440 labels=None, format=None, encoding=None,
441 marker_hint='cue'):
442 """Write audio data using the SoundFile module (based on libsndfile).
444 Documentation
445 -------------
446 http://pysoundfile.readthedocs.org
448 Parameters
449 ----------
450 filepath: str or Path
451 Full path and name of the file to write.
452 data: 1-D or 2-D array of floats
453 Array with the data (first index time, second index channel,
454 values within -1.0 and 1.0).
455 rate: float
456 Sampling rate of the data in Hertz.
457 metadata: None or nested dict
458 Metadata as key-value pairs. Values can be strings, integers,
459 or dictionaries.
460 locs: None or 1-D or 2-D array of ints
461 Marker positions (first column) and spans (optional second column)
462 for each marker (rows).
463 labels: None or 2-D array of string objects
464 Labels (first column) and texts (optional second column)
465 for each marker (rows).
466 format: str or None
467 File format.
468 encoding: str or None
469 Encoding of the data.
470 If None or empty string use 'PCM_16'.
471 marker_hint: str
472 - 'cue': store markers in cue and and adtl chunks.
473 - 'lbl': store markers in avisoft lbl chunk.
475 Raises
476 ------
477 ImportError
478 The SoundFile module is not installed.
479 *
480 Writing of the data failed.
481 """
482 if not audio_modules['soundfile']:
483 raise ImportError
485 if not format:
486 format = format_from_extension(filepath)
487 if format:
488 format = format.upper()
490 if not encoding:
491 encoding = 'PCM_16'
492 encoding = encoding.upper()
494 soundfile.write(filepath, data, int(rate), format=format,
495 subtype=encoding)
496 try:
497 append_riff(filepath, metadata, locs, labels, rate, marker_hint)
498 except ValueError:
499 pass
502def formats_wavefile():
503 """Audio file formats supported by the wavefile module.
505 Returns
506 -------
507 formats: list of str
508 List of supported file formats as strings.
509 """
510 if not audio_modules['wavefile']:
511 return []
512 formats = []
513 for attr in dir(wavefile.Format):
514 v = getattr(wavefile.Format, attr)
515 if ( isinstance(v, int)
516 and v & wavefile.Format.TYPEMASK > 0
517 and v != wavefile.Format.TYPEMASK ):
518 formats.append(attr)
519 return sorted(formats)
522def encodings_wavefile(format):
523 """Encodings supported by the wavefile module.
525 Parameters
526 ----------
527 format: str
528 The file format (ignored).
530 Returns
531 -------
532 encodings: list of str
533 List of supported encodings as strings.
534 """
535 if not audio_modules['wavefile']:
536 return []
537 if not format.upper() in formats_wavefile():
538 return []
539 encodings = []
540 for attr in dir(wavefile.Format):
541 v = getattr(wavefile.Format, attr)
542 if ( isinstance(v, int)
543 and v & wavefile.Format.SUBMASK > 0
544 and v != wavefile.Format.SUBMASK ):
545 encodings.append(attr)
546 return sorted(encodings)
549def write_wavefile(filepath, data, rate, metadata=None, locs=None,
550 labels=None, format=None, encoding=None,
551 marker_hint='cue'):
552 """Write audio data using the wavefile module (based on libsndfile).
554 Documentation
555 -------------
556 https://github.com/vokimon/python-wavefile
558 Parameters
559 ----------
560 filepath: str or Path
561 Full path and name of the file to write.
562 data: 1-D or 2-D array of floats
563 Array with the data (first index time, second index channel,
564 values within -1.0 and 1.0).
565 rate: float
566 Sampling rate of the data in Hertz.
567 metadata: None or nested dict
568 Metadata as key-value pairs. Values can be strings, integers,
569 or dictionaries.
570 locs: None or 1-D or 2-D array of ints
571 Marker positions (first column) and spans (optional second column)
572 for each marker (rows).
573 labels: None or 2-D array of string objects
574 Labels (first column) and texts (optional second column)
575 for each marker (rows).
576 format: str or None
577 File format as in wavefile.Format.
578 encoding: str or None
579 Encoding of the data as in wavefile.Format.
580 If None or empty string use 'PCM_16'.
581 marker_hint: str
582 - 'cue': store markers in cue and and adtl chunks.
583 - 'lbl': store markers in avisoft lbl chunk.
585 Raises
586 ------
587 ImportError
588 The wavefile module is not installed.
589 ValueError
590 File format or encoding not supported.
591 *
592 Writing of the data failed.
593 """
594 if not audio_modules['wavefile']:
595 raise ImportError
597 if not format:
598 format = format_from_extension(filepath)
599 format = format.upper()
600 try:
601 format_value = getattr(wavefile.Format, format)
602 except AttributeError:
603 raise ValueError('file format %s not supported by wavefile module' % format)
605 if not encoding:
606 encodings = encodings_wavefile(format)
607 encoding = encodings[0]
608 if 'PCM_16' in encodings:
609 encoding = 'PCM_16'
610 encoding = encoding.upper()
611 try:
612 encoding_value = getattr(wavefile.Format, encoding)
613 except AttributeError:
614 raise ValueError('file encoding %s not supported by wavefile module' % encoding)
616 channels = 1
617 if len(data.shape) > 1:
618 channels = data.shape[1]
619 else:
620 data = data.reshape((-1, 1))
621 with wavefile.WaveWriter(os.fspath(filepath), channels=channels,
622 samplerate=int(rate),
623 format=format_value|encoding_value) as w:
624 w.write(data.T)
625 try:
626 append_riff(filepath, metadata, locs, labels, rate, marker_hint)
627 except ValueError:
628 pass
631def formats_pydub():
632 """Audio file formats supported by the Pydub module.
634 Returns
635 -------
636 formats: list of str
637 List of supported file formats as strings.
638 """
639 if not audio_modules['pydub']:
640 return []
641 formats = []
642 command = [pydub.AudioSegment.converter, '-formats']
643 with open(os.devnull, 'rb') as devnull:
644 p = subprocess.Popen(command, stdin=devnull, stdout=subprocess.PIPE,
645 stderr=subprocess.PIPE, universal_newlines=True)
646 skip = True
647 for line in p.communicate()[0].split('\n'):
648 if '--' in line[:3]:
649 skip = False
650 continue
651 if skip:
652 continue
653 cols = line.split()
654 if len(cols) > 2 and 'E' in cols[0]:
655 formats.append(cols[1].upper())
656 return formats
658def encodings_pydub(format):
659 """Encodings of an audio file format supported by the Pydub module.
661 Parameters
662 ----------
663 format: str
664 The file format.
666 Returns
667 -------
668 encodings: list of str
669 List of supported encodings as strings.
670 """
671 pydub_encodings = {'pcm_s16le': 'PCM_16',
672 'pcm_s24le': 'PCM_24',
673 'pcm_s32le': 'PCM_32',
674 'pcm_f32le': 'FLOAT',
675 'pcm_f64le': 'DOUBLE',
676 }
677 if not audio_modules['pydub']:
678 return []
679 if format.upper() not in formats_pydub():
680 return []
681 encodings = []
682 command = [pydub.AudioSegment.converter, '-encoders']
683 with open(os.devnull, 'rb') as devnull:
684 p = subprocess.Popen(command, stdin=devnull, stdout=subprocess.PIPE,
685 stderr=subprocess.PIPE, universal_newlines=True)
686 skip = True
687 for line in p.communicate()[0].split('\n'):
688 if '--' in line[:3]:
689 skip = False
690 continue
691 if skip:
692 continue
693 cols = line.split()
694 if len(cols) > 2 and cols[0][0] == 'A':
695 encoding = cols[1]
696 if encoding in pydub_encodings:
697 encoding = pydub_encodings[encoding]
698 encodings.append(encoding.upper())
699 return encodings
701def write_pydub(filepath, data, rate, metadata=None, locs=None,
702 labels=None, format=None, encoding=None,
703 marker_hint='cue'):
704 """Write audio data using the Pydub module.
706 Documentation
707 -------------
708 https://github.com/jiaaro/pydub
710 Parameters
711 ----------
712 filepath: str or Path
713 Full path and name of the file to write.
714 data: 1-D or 2-D array of floats
715 Array with the data (first index time, second index channel,
716 values within -1.0 and 1.0).
717 rate: float
718 Sampling rate of the data in Hertz.
719 metadata: None or nested dict
720 Metadata as key-value pairs. Values can be strings, integers,
721 or dictionaries.
722 locs: None or 1-D or 2-D array of ints
723 Marker positions (first column) and spans (optional second column)
724 for each marker (rows).
725 labels: None or 2-D array of string objects
726 Labels (first column) and texts (optional second column)
727 for each marker (rows).
728 format: str or None
729 File format, everything ffmpeg or avtools are supporting.
730 encoding: str or None
731 Encoding of the data.
732 If None or empty string use 'PCM_16'.
733 marker_hint: str
734 - 'cue': store markers in cue and and adtl chunks.
735 - 'lbl': store markers in avisoft lbl chunk.
737 Raises
738 ------
739 ImportError
740 The Pydub module is not installed.
741 *
742 Writing of the data failed.
743 ValueError
744 File format or encoding not supported.
745 """
746 if not audio_modules['pydub']:
747 raise ImportError
749 if not format:
750 format = format_from_extension(filepath)
751 if format and format.upper() not in formats_pydub():
752 raise ValueError('file format %s not supported by Pydub module' % format)
754 pydub_encodings = {'PCM_16': 'pcm_s16le',
755 'PCM_24': 'pcm_s24le',
756 'PCM_32': 'pcm_s32le',
757 'DOUBLE': 'pcm_f32le',
758 'FLOAT': 'pcm_f64le',
759 }
760 if encoding:
761 encoding = encoding.upper()
762 if encoding in pydub_encodings:
763 encoding = pydub_encodings[encoding]
764 if encoding not in encodings_pydub(format):
765 raise ValueError('file encoding %s not supported by Pydub module' % encoding)
766 encoding = encoding.lower()
767 else:
768 encoding = None
770 channels = 1
771 if len(data.shape) > 1:
772 channels = data.shape[1]
773 int_data = (data*(2**31-1)).astype(np.int32)
774 sound = pydub.AudioSegment(int_data.ravel(), sample_width=4,
775 frame_rate=rate, channels=channels)
776 sound.export(filepath, format=format.lower(), codec=encoding)
777 try:
778 append_riff(filepath, metadata, locs, labels, rate, marker_hint)
779 except ValueError:
780 pass
783audio_formats_funcs = (
784 ('soundfile', formats_soundfile),
785 ('wavefile', formats_wavefile),
786 ('wave', formats_wave),
787 ('ewave', formats_ewave),
788 ('scipy.io.wavfile', formats_wavfile),
789 ('pydub', formats_pydub)
790 )
791""" List of implemented formats() functions.
793Each element of the list is a tuple with the module's name and the formats() function.
794"""
797def available_formats():
798 """Audio file formats supported by any of the installed audio modules.
800 Returns
801 -------
802 formats: list of str
803 List of supported file formats as strings.
805 Examples
806 --------
807 ```
808 >>> from audioio import available_formats
809 >>> f = available_formats()
810 >>> printf(f)
811 ['3G2', '3GP', 'A64', 'AC3', 'ADTS', 'ADX', 'AIFF', ..., 'WAV', 'WAVEX', 'WEBM', 'WEBM_CHUNK', 'WEBM_DASH_MANIFEST', 'WEBP', 'WEBVTT', 'WTV', 'WV', 'WVE', 'XI', 'XV', 'YUV4MPEGPIPE']
812 ```
813 """
814 formats = set()
815 for module, formats_func in audio_formats_funcs:
816 formats |= set(formats_func())
817 return sorted(list(formats))
820audio_encodings_funcs = (
821 ('soundfile', encodings_soundfile),
822 ('wavefile', encodings_wavefile),
823 ('wave', encodings_wave),
824 ('ewave', encodings_ewave),
825 ('scipy.io.wavfile', encodings_wavfile),
826 ('pydub', encodings_pydub)
827 )
828""" List of implemented encodings() functions.
830Each element of the list is a tuple with the module's name and the encodings() function.
831"""
834def available_encodings(format):
835 """Encodings of an audio file format supported by any of the installed audio modules.
837 Parameters
838 ----------
839 format: str
840 The file format.
842 Returns
843 -------
844 encodings: list of str
845 List of supported encodings as strings.
847 Example
848 -------
849 ```
850 >>> from audioio import available_encodings
851 >>> e = available_encodings('WAV')
852 >>> printf(e)
853 ['ALAW', 'DOUBLE', 'FLOAT', 'G721_32', 'GSM610', 'IMA_ADPCM', 'MS_ADPCM', 'PCM_16', 'PCM_24', 'PCM_32', 'PCM_U8', 'ULAW']
854 ```
855 """
856 for module, encodings_func in audio_encodings_funcs:
857 encs = encodings_func(format)
858 if len(encs) > 0:
859 return encs
860 return []
863audio_writer_funcs = (
864 ('soundfile', write_soundfile),
865 ('wavefile', write_wavefile),
866 ('wave', write_wave),
867 ('ewave', write_ewave),
868 ('scipy.io.wavfile', write_wavfile),
869 ('pydub', write_pydub)
870 )
871""" List of implemented write() functions.
873Each element of the list is a tuple with the module's name and the write() function.
874"""
877def write_audio(filepath, data, rate, metadata=None, locs=None,
878 labels=None, format=None, encoding=None,
879 marker_hint='cue', verbose=0):
880 """Write audio data, metadata, and marker to file.
882 Parameters
883 ----------
884 filepath: str or Path
885 Full path and name of the file to write.
886 data: 1-D or 2-D array of floats
887 Array with the data (first index time, second index channel,
888 values within -1.0 and 1.0).
889 rate: float
890 Sampling rate of the data in Hertz.
891 metadata: None or nested dict
892 Metadata as key-value pairs. Values can be strings, integers,
893 or dictionaries.
894 locs: None or 1-D or 2-D array of ints
895 Marker positions (first column) and spans (optional second column)
896 for each marker (rows).
897 labels: None or 2-D array of string objects
898 Labels (first column) and texts (optional second column)
899 for each marker (rows).
900 format: str or None
901 File format. If None deduce file format from filepath.
902 See `available_formats()` for possible values.
903 encoding: str or None
904 Encoding of the data. See `available_encodings()` for possible values.
905 If None or empty string use 'PCM_16'.
906 marker_hint: str
907 - 'cue': store markers in cue and and adtl chunks.
908 - 'lbl': store markers in avisoft lbl chunk.
909 verbose: int
910 If >0 show detailed error/warning messages.
912 Raises
913 ------
914 ValueError
915 `filepath` is empty string.
916 IOError
917 Writing of the data failed.
919 Examples
920 --------
921 ```
922 import numpy as np
923 from audioio import write_audio
925 rate = 28000.0
926 freq = 800.0
927 time = np.arange(0.0, 1.0, 1/rate) # one second
928 data = np.sin(2.0*np.p*freq*time) # 800Hz sine wave
929 md = dict(Artist='underscore_') # metadata
931 write_audio('audio/file.wav', data, rate, md)
932 ```
933 """
934 # write audio with metadata and markers:
935 if not format:
936 format = format_from_extension(filepath)
937 if format == 'WAV' and (metadata is not None or locs is not None):
938 try:
939 audioio_write_wave(filepath, data, rate, metadata,
940 locs, labels, encoding, marker_hint)
941 return
942 except ValueError:
943 pass
944 # write audio file by trying available modules:
945 errors = [f'failed to write data to file "{filepath}":']
946 for lib, write_file in audio_writer_funcs:
947 if not audio_modules[lib]:
948 continue
949 try:
950 write_file(filepath, data, rate, metadata, locs,
951 labels, format, encoding, marker_hint)
952 success = True
953 if verbose > 0:
954 print('wrote data to file "%s" using %s module' %
955 (filepath, lib))
956 if verbose > 1:
957 print(' sampling rate: %g Hz' % rate)
958 print(' channels : %d' % (data.shape[1] if len(data.shape) > 1 else 1))
959 print(' frames : %d' % len(data))
960 return
961 except Exception as e:
962 errors.append(f' {lib} failed: {str(e)}')
963 raise IOError('\n'.join(errors))
966def demo(file_path, channels=2, encoding=''):
967 """Demo of the audiowriter functions.
969 Parameters
970 ----------
971 file_path: str
972 File path of an audio file.
973 encoding: str
974 Encoding to be used.
975 """
976 print('generate data ...')
977 rate = 44100.0
978 t = np.arange(0.0, 1.0, 1.0/rate)
979 data = np.zeros((len(t), channels))
980 for c in range(channels):
981 data[:,c] = np.sin(2.0*np.pi*(440.0+c*8.0)*t)
983 print("write_audio(%s) ..." % file_path)
984 write_audio(file_path, data, rate, encoding=encoding, verbose=2)
986 print('done.')
989def main(*args):
990 """Call demo with command line arguments.
992 Parameters
993 ----------
994 args: list of str
995 Command line arguments as provided by sys.argv[1:]
996 """
997 help = False
998 file_path = None
999 encoding = ''
1000 mod = False
1001 nchan = False
1002 channels = 2
1003 for arg in args:
1004 if mod:
1005 select_module(arg)
1006 mod = False
1007 elif nchan:
1008 channels = int(arg)
1009 nchan = False
1010 elif arg == '-h':
1011 help = True
1012 break
1013 elif arg == '-m':
1014 mod = True
1015 elif arg == '-n':
1016 nchan = True
1017 elif file_path is None:
1018 file_path = arg
1019 else:
1020 encoding = arg
1021 break
1022 if file_path is None:
1023 file_path = 'test.wav'
1025 if help:
1026 print('')
1027 print('Usage:')
1028 print(' python -m src.audioio.audiowriter [-m module] [-n channels] [<filename>] [<encoding>]')
1029 return
1031 demo(file_path, channels=channels, encoding=encoding)
1034if __name__ == "__main__":
1035 main(*sys.argv[1:])