diff --git a/mutagen/wave.py b/mutagen/wave.py index 29826034..f37d6b01 100644 --- a/mutagen/wave.py +++ b/mutagen/wave.py @@ -13,7 +13,7 @@ from mutagen import StreamInfo, FileType -from mutagen.id3 import ID3 +from mutagen.id3 import Encoding, ID3, TIT2, TPE1, TORY, TCON, TALB, TRCK from mutagen._riff import RiffFile, InvalidChunk from mutagen._iff import error as IffError from mutagen.id3._util import ID3NoHeaderError, error as ID3Error @@ -26,6 +26,15 @@ __all__ = ["WAVE", "Open", "delete"] +_INFO_TO_ID3_MAP = { + "INAM": TIT2, # Title + "IART": TPE1, # Artist + "ICRD": TORY, # Original release year + "IGNR": TCON, # Genre + "IPRD": TALB, # Album name + "IPRT": TRCK, # Track +} + class error(IffError): """WAVE stream parsing errors.""" @@ -151,10 +160,38 @@ def delete(self, filething): def delete(filething): """Completely removes the ID3 chunk from the RIFF/WAVE file""" - try: - _WaveFile(filething.fileobj).delete_chunk(u'id3') - except KeyError: - pass + for chunk in [u'id3', u'LIST']: + try: + _WaveFile(filething.fileobj).delete_chunk(chunk) + except KeyError: + pass + + +def _read_tags_from_riff_list(fileobj): + """Read metadata tags from RIFF LIST tag and convert to ID3 format.""" + wave_file = _WaveFile(fileobj) + + if u"LIST" not in wave_file: + return None + + list_chunk = wave_file[u"LIST"] + if list_chunk.name != u"INFO": + return None + + wave_id3 = _WaveID3() + for chunk in list_chunk.subchunks(): + try: + # Value is null-terminated and supposed to be ASCII, but not everyone + # respects that and puts unicode in there anyway + value = chunk.read()[:-1].decode("utf-8") + except UnicodeDecodeError: + continue + else: + id3_tag = _INFO_TO_ID3_MAP.get(chunk.id) + if id3_tag: + wave_id3.add(id3_tag(encoding=Encoding.UTF8, text=value)) + + return wave_id3 class WAVE(FileType): @@ -199,11 +236,11 @@ def load(self, filething, **kwargs): try: self.tags = _WaveID3(fileobj, **kwargs) except ID3NoHeaderError: - self.tags = None + self.tags = _read_tags_from_riff_list(fileobj) except ID3Error as e: raise error(e) - else: - self.tags.filename = self.filename + if self.tags: + self.tags.filename = self.filename Open = WAVE diff --git a/tests/data/silence-2s-PCM-16000-08-INFO.wav b/tests/data/silence-2s-PCM-16000-08-INFO.wav new file mode 100644 index 00000000..3dc057b0 Binary files /dev/null and b/tests/data/silence-2s-PCM-16000-08-INFO.wav differ diff --git a/tests/test_wave.py b/tests/test_wave.py index 921cdf14..482e7ed8 100644 --- a/tests/test_wave.py +++ b/tests/test_wave.py @@ -17,6 +17,11 @@ def setUp(self): self.tmp_wav_pcm_2s_16000_08_ID3v23 = \ WAVE(self.tmp_fn_pcm_2s_16000_08_ID3v23) + fn_wav_pcm_2s_16000_08_info = \ + os.path.join(DATA_DIR, "silence-2s-PCM-16000-08-INFO.wav") + self.wav_pcm_2s_16000_08_INFO = \ + WAVE(fn_wav_pcm_2s_16000_08_info) + self.fn_wav_pcm_2s_16000_08_notags = \ os.path.join(DATA_DIR, "silence-2s-PCM-16000-08-notags.wav") self.wav_pcm_2s_16000_08_notags = \ @@ -90,6 +95,15 @@ def test_id3_tags_uppercase_chunk(self): self.assertEquals(id3["TIT2"], "Silence") self.assertEquals(id3["TPE1"], ["piman / jzig"]) + def test_riff_info_tags(self): + id3 = self.wav_pcm_2s_16000_08_INFO.tags + self.assertEquals(id3["TALB"], "Quod Libet Test Data") + self.assertEquals(id3["TCON"], "Silence") + self.assertEquals(id3["TIT2"], "Silence") + self.assertEquals(id3["TPE1"], ["piman, jzig"]) + self.assertEquals(id3["TORY"], "2004") + self.assertEquals(id3["TRCK"], ["02"]) + def test_delete(self): self.tmp_wav_pcm_2s_16000_08_ID3v23.delete()