Repository: audeering/audiofile

Issue:
Allow outfile in convert_to_wav() to be None
For reference see https://github.com/audeering/audiofile/pull/101#issuecomment-1406171509.

When converting a file to WAV we might want to use the same path as the original file, but just store a WAV file, e.g.

```python
audiofile.convert_to_wav('file.mp4', 'file.wav')
```

In this case we might think about just providing `None` as second argument:

```python
audiofile.convert_to_wav('file.mp4', None)
```

or add `None` as default value to the `outfile` argument of `audiofile.convert_to_wav()`, then we could just do:

```python
audiofile.convert_to_wav('file.mp4')
```

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/audeering/audiofile/pull/106?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=audeering) Report
> Merging [#106](https://codecov.io/gh/audeering/audiofile/pull/106?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=audeering) (2636f0d) into [main](https://codecov.io/gh/audeering/audiofile/commit/c3c4c54438db983c111a926422b84d10a3cc583a?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=audeering) (c3c4c54) will **not change** coverage.
> The diff coverage is `100.0%`.

| [Impacted Files](https://codecov.io/gh/audeering/audiofile/pull/106?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=audeering) | Coverage Δ | |
|---|---|---|
| [audiofile/core/io.py](https://codecov.io/gh/audeering/audiofile/pull/106?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=audeering#diff-YXVkaW9maWxlL2NvcmUvaW8ucHk=) | `100.0% <100.0%> (ø)` | |


Patch:
diff --git a/audiofile/core/io.py b/audiofile/core/io.py
index 41e8998..a38ab55 100644
--- a/audiofile/core/io.py
+++ b/audiofile/core/io.py
@@ -17,11 +17,12 @@ from audiofile.core.utils import (
 
 def convert_to_wav(
         infile: str,
-        outfile: str,
+        outfile: str = None,
         offset: float = 0,
         duration: float = None,
         bit_depth: int = 16,
         normalize: bool = False,
+        overwrite: bool = False,
         **kwargs,
 ) -> str:
     """Convert any audio/video file to WAV.
@@ -37,12 +38,16 @@ def convert_to_wav(
 
     Args:
         infile: audio/video file name
-        outfile: WAV file name
+        outfile: WAV file name.
+            If ``None`` same path as ``infile``
+            but file extension is replaced by ``'wav'``
         duration: return only a specified duration in seconds
         offset: start reading at offset in seconds
         bit_depth: bit depth of written file in bit,
             can be 8, 16, 24
         normalize: normalize audio data before writing
+        overwrite: force overwriting
+            if ``outfile`` is identical to ``outfile``
         kwargs: pass on further arguments to :func:`soundfile.write`
 
     Returns:
@@ -53,15 +58,29 @@ def convert_to_wav(
             but cannot be found
         RuntimeError: if ``file`` is missing,
             broken or format is not supported
+        RuntimeError: if ``infile`` would need to be overwritten
+            and ``overwrite`` is ``False``
 
     Examples:
-        >>> path = convert_to_wav('stereo.flac', 'stereo.wav')
+        >>> path = convert_to_wav('stereo.flac')
         >>> os.path.basename(path)
         'stereo.wav'
 
     """
     infile = audeer.safe_path(infile)
-    outfile = audeer.safe_path(outfile)
+    if outfile is None:
+        outfile = audeer.replace_file_extension(infile, 'wav')
+    else:
+        outfile = audeer.safe_path(outfile)
+    if (
+            infile == outfile
+            and not overwrite
+    ):
+        raise RuntimeError(
+            f"'{infile}' would be overwritten. "
+            "Select 'overwrite=True', "
+            "or provide an 'outfile' argument."
+        )
     signal, sampling_rate = read(
         infile,
         offset=offset,
diff --git a/docs/requirements.txt b/docs/requirements.txt
index f957556..d978d76 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,5 +1,5 @@
 jupyter-sphinx
 sphinx
-sphinx-audeering-theme >=1.1.2
+sphinx-audeering-theme >=1.2.1
 sphinx_autodoc_typehints
 sphinx-copybutton


Test patch:
diff --git a/tests/test_audiofile.py b/tests/test_audiofile.py
index 1fc1f08..1d65082 100644
--- a/tests/test_audiofile.py
+++ b/tests/test_audiofile.py
@@ -1,5 +1,6 @@
 from __future__ import division
 import os
+import re
 import subprocess
 
 import pytest
@@ -305,8 +306,24 @@ def test_convert_to_wav(tmpdir, bit_depth, file_extension):
         convert_to_mp3(tmpfile, infile, sampling_rate, channels)
     else:
         af.write(infile, signal, sampling_rate, bit_depth=bit_depth)
-    outfile = str(tmpdir.join('signal_converted.wav'))
-    af.convert_to_wav(infile, outfile, bit_depth=bit_depth)
+    if file_extension == 'wav':
+        error_msg = (
+            f"'{infile}' would be overwritten. "
+            "Select 'overwrite=True', "
+            "or provide an 'outfile' argument."
+        )
+        with pytest.raises(RuntimeError, match=re.escape(error_msg)):
+            outfile = af.convert_to_wav(infile, bit_depth=bit_depth)
+        outfile = af.convert_to_wav(
+            infile,
+            bit_depth=bit_depth,
+            overwrite=True,
+        )
+    elif file_extension == 'mp3':
+        outfile = af.convert_to_wav(infile, bit_depth=bit_depth)
+    else:
+        outfile = str(tmpdir.join('signal_converted.wav'))
+        af.convert_to_wav(infile, outfile, bit_depth=bit_depth)
     converted_signal, converted_sampling_rate = af.read(outfile)
     assert converted_sampling_rate == sampling_rate
     if file_extension == 'mp3':