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

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 

27from .audiomodules import * 

28from .riffmetadata import write_wave as audioio_write_wave 

29from .riffmetadata import append_riff 

30 

31 

32def format_from_extension(filepath): 

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

34 

35 Parameters 

36 ---------- 

37 filepath: string 

38 Name of the audio file. 

39 

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 

59 

60 

61def formats_wave(): 

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

63 

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

73 

74 

75def encodings_wave(format): 

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

77 

78 Parameters 

79 ---------- 

80 format: str 

81 The file format. 

82 

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

94 

95 

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. 

100  

101 Documentation 

102 ------------- 

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

104 

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. 

131 

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

145 

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

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

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

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 if not filepath: 

265 raise ValueError('no file specified!') 

266 

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) 

271 

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) 

282 

283 channels = 1 

284 if len(data.shape) > 1: 

285 channels = data.shape[1] 

286 

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) 

291 

292 

293def formats_wavfile(): 

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

295 

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

305 

306 

307def encodings_wavfile(format): 

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

309 

310 Parameters 

311 ---------- 

312 format: str 

313 The file format. 

314 

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

326 

327 

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. 

332  

333 Documentation 

334 ------------- 

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

336 

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. 

363 

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

377 

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) 

382 

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) 

408 

409 

410def formats_soundfile(): 

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

412 

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

422 

423 

424def encodings_soundfile(format): 

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

426 

427 Parameters 

428 ---------- 

429 format: str 

430 The file format. 

431 

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

441 

442 

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

447  

448 Documentation 

449 ------------- 

450 http://pysoundfile.readthedocs.org 

451 

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. 

478 

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

490 

491 if not format: 

492 format = format_from_extension(filepath) 

493 if format: 

494 format = format.upper() 

495 

496 if not encoding: 

497 encoding = 'PCM_16' 

498 encoding = encoding.upper() 

499 

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 

506 

507 

508def formats_wavefile(): 

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

510 

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) 

526 

527 

528def encodings_wavefile(format): 

529 """Encodings supported by the wavefile module. 

530 

531 Parameters 

532 ---------- 

533 format: str 

534 The file format (ignored). 

535 

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) 

553 

554 

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

559  

560 Documentation 

561 ------------- 

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

563 

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. 

590 

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

604 

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) 

612 

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) 

623 

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 

637 

638 

639def formats_pydub(): 

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

641 

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 

665 

666def encodings_pydub(format): 

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

668 

669 Parameters 

670 ---------- 

671 format: str 

672 The file format. 

673 

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 

708 

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. 

713  

714 Documentation 

715 ------------- 

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

717 

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. 

744 

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

758 

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) 

763 

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 

779 

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 

791 

792 

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. 

802 

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

804""" 

805 

806 

807def available_formats(): 

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

809 

810 Returns 

811 ------- 

812 formats: list of strings 

813 List of supported file formats as strings. 

814 

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

828 

829 

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. 

839 

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

841""" 

842 

843 

844def available_encodings(format): 

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

846 

847 Parameters 

848 ---------- 

849 format: str 

850 The file format. 

851 

852 Returns 

853 ------- 

854 encodings: list of strings 

855 List of supported encodings as strings. 

856 

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

871 

872 

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. 

882 

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

884""" 

885 

886 

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. 

891 

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. 

921 

922 Raises 

923 ------ 

924 ValueError 

925 `filepath` is empty string. 

926 IOError 

927 Writing of the data failed. 

928 

929 Examples 

930 -------- 

931 ``` 

932 import numpy as np 

933 from audioio import write_audio 

934  

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 

940 

941 write_audio('audio/file.wav', data, rate, md) 

942 ``` 

943 """ 

944 if not filepath: 

945 raise ValueError('no file specified!') 

946 

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

977 

978 

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

980 """Demo of the audiowriter functions. 

981 

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) 

995 

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

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

998 

999 print('done.') 

1000 

1001 

1002def main(*args): 

1003 """Call demo with command line arguments. 

1004 

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' 

1037 

1038 if help: 

1039 print('') 

1040 print('Usage:') 

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

1042 return 

1043 

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

1045 

1046 

1047if __name__ == "__main__": 

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