Skip to content

Commit

Permalink
resolves #1091
Browse files Browse the repository at this point in the history
exposes audio.write_metadata and adds test

this allows easily updating metadata without writing entire file
  • Loading branch information
sammlapp committed Jan 26, 2025
1 parent d209057 commit 20db988
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 2 deletions.
19 changes: 17 additions & 2 deletions opensoundscape/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -875,12 +875,18 @@ def save(

if metadata_format is not None and self.metadata is not None:
if fmt not in [".WAV", ".AIFF"]:
# detailed metadata will not be written to file!
if not suppress_warnings:
warnings.warn(
"Saving metadata is only supported for WAV and AIFF formats"
)
else: # we can write metadata for WAV and AIFF
_write_metadata(self.metadata, metadata_format, path)
write_metadata(
self.metadata,
metadata_format,
path,
suppress_warnings=suppress_warnings,
)

def split(self, clip_duration, **kwargs):
"""Split Audio into even-lengthed clips
Expand Down Expand Up @@ -1449,9 +1455,11 @@ def parse_metadata(path):
return metadata


def _write_metadata(metadata, metadata_format, path):
def write_metadata(metadata, metadata_format, path, suppress_warnings=False):
"""write metadata using one of the supported formats
Currently, only supports writing to .WAV and .AIFF.
metadata fields containing empty strings `''` will be replaced by a string
containing a single space `' '` as a workaround to
https://github.com/bastibe/python-soundfile/issues/386.
Expand All @@ -1461,7 +1469,14 @@ def _write_metadata(metadata, metadata_format, path):
metadata_format: one of 'opso','opso_metadata_v0.1','soundfile'
(see Audio.wave documentation)
path: file path to save metadata in with soundfile
suppress_warnings: if True, does not warn user when writing to unsupported format
"""
suffix = Path(path).suffix.upper()
assert suffix in [
".WAV",
".AIFF",
], f"Only supports writing to .WAV and .AIFF. Suffix was {suffix}"

metadata = metadata.copy() # avoid changing the existing object
if metadata_format == "soundfile":
pass # just write the metadata as is
Expand Down
20 changes: 20 additions & 0 deletions tests/test_audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,26 @@ def test_update_metadata(metadata_wav_str, new_metadata_wav_str):
assert Audio.from_file(new_metadata_wav_str).metadata["artist"] == "newartist"


def test_read_write_metadata(metadata_wav_str, new_metadata_wav_str):
"""update metadata header without reading/writing entire file"""
# first make both files exist with same metadata
a = Audio.from_file(metadata_wav_str)
a.save(new_metadata_wav_str)

# using opso metadata format
meta = audio.parse_metadata(metadata_wav_str)
meta["artist"] = "newartist"
audio.write_metadata(meta, "opso", new_metadata_wav_str)
meta2 = audio.parse_metadata(new_metadata_wav_str)
meta2["artist"] == "newartist"
# repeat with soundfinder metadata format
meta = audio.parse_metadata(metadata_wav_str)
meta["artist"] = "newartist"
audio.write_metadata(meta, "soundfile", new_metadata_wav_str)
meta2 = audio.parse_metadata(new_metadata_wav_str)
meta2["artist"] == "newartist"


def test_load_empty_wav(empty_wav_str):
with pytest.raises(AudioOutOfBoundsError):
s = Audio.from_file(empty_wav_str, out_of_bounds_mode="raise")
Expand Down

0 comments on commit 20db988

Please sign in to comment.