Audio.encode_example() throws an error when writing example from array
State: closed | Author: polinaeterna (CONTRIBUTOR)
Labels: name: bug

## Describe the bug
When trying to do `Audio().encode_example()` with preexisting array (see [this line](https://github.com/huggingface/datasets/blob/master/src/datasets/features/audio.py#L73)), `sf.write()` throws you an error:
`TypeError: No format specified and unable to get format from file extension: <_io.BytesIO object at 0x7f4218c0db30>`

## Steps to reproduce the bug
### Sample code to reproduce the bug
```python
# download sample file
!wget https://huggingface.co/datasets/polinaeterna/test_encode_example/resolve/main/common_voice_vi_21824030.mp3

arr, sr = librosa.load("common_voice_vi_21824030.mp3")

Audio().encode_example({
    "path": "common_voice_vi_21824030.mp3",
    "array": arr,
    "sampling_rate":sr
})
```

## Expected results
An encoded example (`{"bytes": b'....', "path": 'path'}`)

## Actual results
```python
TypeError                                 Traceback (most recent call last)
Input In [3], in <module>
      1 arr, sr = librosa.load("common_voice_vi_21824030.mp3")
----> 3 Audio().encode_example({
      4     "path": "common_voice_vi_21824030.mp3",
      5     "array": arr,
      6     "sampling_rate":sr
      7 })

File ~/workspace/datasets/src/datasets/features/audio.py:75, in Audio.encode_example(self, value)
     73 elif isinstance(value, dict) and "array" in value:
     74     buffer = BytesIO()
---> 75     sf.write(buffer, value["array"], value["sampling_rate"])
     76     return {"bytes": buffer.getvalue(), "path": value.get("path")}
     77 elif value.get("bytes") is not None or value.get("path") is not None:

File ~/miniconda3/envs/datasets/lib/python3.8/site-packages/soundfile.py:314, in write(file, data, samplerate, subtype, endian, format, closefd)
    312 else:
    313     channels = data.shape[1]
--> 314 with SoundFile(file, 'w', samplerate, channels,
    315                subtype, endian, format, closefd) as f:
    316     f.write(data)

File ~/miniconda3/envs/datasets/lib/python3.8/site-packages/soundfile.py:627, in SoundFile.__init__(self, file, mode, samplerate, channels, subtype, endian, format, closefd)
    625 mode_int = _check_mode(mode)
    626 self._mode = mode
--> 627 self._info = _create_info_struct(file, mode, samplerate, channels,
    628                                  format, subtype, endian)
    629 self._file = self._open(file, mode_int, closefd)
    630 if set(mode).issuperset('r+') and self.seekable():
    631     # Move write position to 0 (like in Python file objects)

File ~/miniconda3/envs/datasets/lib/python3.8/site-packages/soundfile.py:1416, in _create_info_struct(file, mode, samplerate, channels, format, subtype, endian)
   1414 original_format = format
   1415 if format is None:
-> 1416     format = _get_format_from_filename(file, mode)
   1417     assert isinstance(format, (_unicode, str))
   1418 else:

File ~/miniconda3/envs/datasets/lib/python3.8/site-packages/soundfile.py:1457, in _get_format_from_filename(file, mode)
   1455     pass
   1456 if format.upper() not in _formats and 'r' not in mode:
-> 1457     raise TypeError("No format specified and unable to get format from "
   1458                     "file extension: {0!r}".format(file))
   1459 return format

TypeError: No format specified and unable to get format from file extension: <_io.BytesIO object at 0x7fd8daf88180>
```

## Environment info
<!-- You can run the command `datasets-cli env` and copy-and-paste its output below. -->
- `datasets` version: datasets master
- Platform: Ubuntu 20.04
- Python version: python 3.8.12
- PyArrow version: 6.0.1

## Solution

I guess we just need to add `format` arg in [this line](https://github.com/huggingface/datasets/blob/master/src/datasets/features/audio.py#L75) like this:
```python
sf.write(buffer, value["array"], value["sampling_rate"], format="wav")
```


BTW discovered this when trying to decode audio in mp3 format without torchaudio (would be useful for TensorFlow users), like this:
```python
from datasets import load_dataset, Features, Audio

ds = load_dataset("common_voice", "vi", split="test")
ds = ds.remove_columns("audio")
ds.select(range(3))  # 3 samples just for testing

def load_mp3_with_librosa(example):
    arr, sr = librosa.load(example["path"])
    example["audio"] = {
        "path": example["path"],
        "array": arr,
        "sampling_rate": sr
    }
    return example

updated_dataset = ds.map(lambda example: load_mp3_with_librosa(example),
                         features=Features(
                             {"audio": Audio(decode=False)}
                         ))
```

@lhoestq @mariosasko @albertvillanova am I right in my logic? do we agree that we can set wav as the format? 🤗

Comments: Good catch ! Yes I think passing `format="wav"` is the right thing to do

Thanks @polinaeterna for reporting this issue.

In relation to the decoding of MP3 audio files without torchaudio, I remember Patrick made some tests and these had quite bad performance. That is why he proposed to support MP3 files only with torchaudio. But yes, nice to give an alternative to non-torchaudio users (with a big warning on performance).

>  I remember Patrick made some tests and these had quite bad performance. That is why he proposed to support MP3 files only with torchaudio.

Yeah, I know, but as far as I understand, some users just categorically don't want to have torchaudio in their environment. Anyway, it's just a more or less random example, they can use any library they like following the same logic (I'm just not a big expert in decoding utils so if you can give me some presentation / resources about that I would really appreciate it 🤗)