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

1"""Write numpy arrays of floats to audio files. 

2 

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. 

7 

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. 

10 

11For support of more audio formats, you might need to install 

12additional packages. 

13See [installation](https://bendalab.github.io/audioio/installation) 

14for further instructions. 

15 

16For a demo, run the script as: 

17``` 

18python -m src.audioio.audiowriter 

19``` 

20 

21""" 

22 

23import os 

24import sys 

25import subprocess 

26import numpy as np 

27 

28from pathlib import Path 

29from .audiomodules import * 

30from .riffmetadata import write_wave as audioio_write_wave 

31from .riffmetadata import append_riff 

32 

33 

34def format_from_extension(filepath): 

35 """Deduce audio file format from file extension. 

36 

37 Parameters 

38 ---------- 

39 filepath: str or Path or None 

40 Name of the audio file. 

41 

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 

62 

63 

64def formats_wave(): 

65 """Audio file formats supported by the wave module. 

66 

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'] 

76 

77 

78def encodings_wave(format): 

79 """Encodings of an audio file format supported by the wave module. 

80 

81 Parameters 

82 ---------- 

83 format: str 

84 The file format. 

85 

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'] 

97 

98 

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. 

103  

104 Documentation 

105 ------------- 

106 https://docs.python.org/3.8/library/wave.html 

107 

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. 

134 

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) 

150 

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] 

161 

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) 

180 

181 

182def formats_ewave(): 

183 """Audio file formats supported by the ewave module. 

184 

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'] 

194 

195 

196def encodings_ewave(format): 

197 """Encodings of an audio file format supported by the ewave module. 

198 

199 Parameters 

200 ---------- 

201 format: str 

202 The file format. 

203 

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'] 

215 

216 

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. 

221 

222 Documentation 

223 ------------- 

224 https://github.com/melizalab/py-ewave 

225 

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. 

252 

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 

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) 

269 

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) 

280 

281 channels = 1 

282 if len(data.shape) > 1: 

283 channels = data.shape[1] 

284 

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) 

289 

290 

291def formats_wavfile(): 

292 """Audio file formats supported by the scipy.io.wavfile module. 

293 

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'] 

303 

304 

305def encodings_wavfile(format): 

306 """Encodings of an audio file format supported by the scipy.io.wavfile module. 

307 

308 Parameters 

309 ---------- 

310 format: str 

311 The file format. 

312 

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'] 

324 

325 

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. 

330  

331 Documentation 

332 ------------- 

333 http://docs.scipy.org/doc/scipy/reference/io.html 

334 

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. 

361 

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 

373 

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) 

378 

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) 

404 

405 

406def formats_soundfile(): 

407 """Audio file formats supported by the SoundFile module. 

408 

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())) 

418 

419 

420def encodings_soundfile(format): 

421 """Encodings of an audio file format supported by the SoundFile module. 

422 

423 Parameters 

424 ---------- 

425 format: str 

426 The file format. 

427 

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))) 

437 

438 

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). 

443  

444 Documentation 

445 ------------- 

446 http://pysoundfile.readthedocs.org 

447 

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. 

474 

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 

484 

485 if not format: 

486 format = format_from_extension(filepath) 

487 if format: 

488 format = format.upper() 

489 

490 if not encoding: 

491 encoding = 'PCM_16' 

492 encoding = encoding.upper() 

493 

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 

500 

501 

502def formats_wavefile(): 

503 """Audio file formats supported by the wavefile module. 

504 

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) 

520 

521 

522def encodings_wavefile(format): 

523 """Encodings supported by the wavefile module. 

524 

525 Parameters 

526 ---------- 

527 format: str 

528 The file format (ignored). 

529 

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) 

547 

548 

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). 

553  

554 Documentation 

555 ------------- 

556 https://github.com/vokimon/python-wavefile 

557 

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. 

584 

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 

596 

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) 

604 

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) 

615 

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 

629 

630 

631def formats_pydub(): 

632 """Audio file formats supported by the Pydub module. 

633 

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 

657 

658def encodings_pydub(format): 

659 """Encodings of an audio file format supported by the Pydub module. 

660 

661 Parameters 

662 ---------- 

663 format: str 

664 The file format. 

665 

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 

700 

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. 

705  

706 Documentation 

707 ------------- 

708 https://github.com/jiaaro/pydub 

709 

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. 

736 

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 

748 

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) 

753 

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 

769 

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 

781 

782 

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. 

792 

793Each element of the list is a tuple with the module's name and the formats() function. 

794""" 

795 

796 

797def available_formats(): 

798 """Audio file formats supported by any of the installed audio modules. 

799 

800 Returns 

801 ------- 

802 formats: list of str 

803 List of supported file formats as strings. 

804 

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)) 

818 

819 

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. 

829 

830Each element of the list is a tuple with the module's name and the encodings() function. 

831""" 

832 

833 

834def available_encodings(format): 

835 """Encodings of an audio file format supported by any of the installed audio modules. 

836 

837 Parameters 

838 ---------- 

839 format: str 

840 The file format. 

841 

842 Returns 

843 ------- 

844 encodings: list of str 

845 List of supported encodings as strings. 

846 

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 [] 

861 

862 

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. 

872 

873Each element of the list is a tuple with the module's name and the write() function. 

874""" 

875 

876 

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. 

881 

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. 

911 

912 Raises 

913 ------ 

914 ValueError 

915 `filepath` is empty string. 

916 IOError 

917 Writing of the data failed. 

918 

919 Examples 

920 -------- 

921 ``` 

922 import numpy as np 

923 from audioio import write_audio 

924  

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 

930 

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)) 

964 

965 

966def demo(file_path, channels=2, encoding=''): 

967 """Demo of the audiowriter functions. 

968 

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) 

982 

983 print("write_audio(%s) ..." % file_path) 

984 write_audio(file_path, data, rate, encoding=encoding, verbose=2) 

985 

986 print('done.') 

987 

988 

989def main(*args): 

990 """Call demo with command line arguments. 

991 

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' 

1024 

1025 if help: 

1026 print('') 

1027 print('Usage:') 

1028 print(' python -m src.audioio.audiowriter [-m module] [-n channels] [<filename>] [<encoding>]') 

1029 return 

1030 

1031 demo(file_path, channels=channels, encoding=encoding) 

1032 

1033 

1034if __name__ == "__main__": 

1035 main(*sys.argv[1:])