diff --git a/CHANGELOG.md b/CHANGELOG.md index d460dd9..9e383fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ This project adheres to [Semantic Versioning](https://semver.org). * ``ID3v2GeneralEncapsulatedObject``. * ``ID3v2PrivateInfo``. * ``ID3v2UserURLLink``. +* ``ID3v2UserText``. ### Changed @@ -71,6 +72,12 @@ This project adheres to [Semantic Versioning](https://semver.org). that contains a single user URL link object. * Change ``ID3v2Frames`` to present a list of user URL link objects for ``WXXX`` key. +* Rework ID3v2 user text frame abstractions. + * Add ``ID3v2UserText`` class. + * Change ``ID3v2UserTextFrame`` to have only value attribute. + that contains a single user text object. + * Change ``ID3v2Frames`` to present a list of user text + objects for ``TXXX`` key. ### Removed diff --git a/src/audio_metadata/formats/id3v2.py b/src/audio_metadata/formats/id3v2.py index 7d1e1d4..c7f0d4c 100644 --- a/src/audio_metadata/formats/id3v2.py +++ b/src/audio_metadata/formats/id3v2.py @@ -250,7 +250,7 @@ def parse(cls, data, id3_version): f"Ignoring '{frame.id}' frame with value '{frame.value}'.\n" f"'{frame.id}' is not supported in the ID3v2.{id3_version.value[1]} specification.\n" ), - AudioMetadataWarning + AudioMetadataWarning, ) continue @@ -264,11 +264,6 @@ def parse(cls, data, id3_version): ), ): frames[f'{frame.id}:{frame.description}:{frame.language}'].append(frame.value) - elif isinstance( - frame, - ID3v2UserTextFrame, - ): - frames[f'{frame.id}:{frame.description}'].append(frame.value) elif isinstance( frame, ( diff --git a/src/audio_metadata/formats/id3v2_frames.py b/src/audio_metadata/formats/id3v2_frames.py index 7fcb1a7..98d395c 100644 --- a/src/audio_metadata/formats/id3v2_frames.py +++ b/src/audio_metadata/formats/id3v2_frames.py @@ -23,6 +23,7 @@ 'ID3v2TimestampFrame', 'ID3v2UnsynchronizedLyricsFrame', 'ID3v2URLLinkFrame', + 'ID3v2UserText', 'ID3v2UserTextFrame', 'ID3v2UserURLLink', 'ID3v2UserURLLinkFrame', @@ -111,6 +112,15 @@ class ID3v2PrivateInfo(AttrMapping): data = attrib() +@attrs( + repr=False, + kw_only=True, +) +class ID3v2UserText(AttrMapping): + description = attrib() + text = attrib() + + @attrs( repr=False, kw_only=True, @@ -319,7 +329,6 @@ class ID3v2UserURLLinkFrame(ID3v2BaseFrame): kw_only=True, ) class ID3v2UserTextFrame(ID3v2BaseFrame): - description = attrib() value = attrib() @@ -711,6 +720,14 @@ def parse(cls, data, struct_pattern, size_len, per_byte): kwargs['value'] = decode_bytestring(description, encoding) elif frame_type is ID3v2URLLinkFrame: kwargs['value'] = unquote(decode_bytestring(frame_data)) + elif frame_type is ID3v2UserTextFrame: + encoding = determine_encoding(frame_data) + + description, text = split_encoded(frame_data[1:], encoding) + kwargs['value'] = ID3v2UserText( + description=decode_bytestring(description, encoding), + text=decode_bytestring(text, encoding), + ) elif frame_type is ID3v2UserURLLinkFrame: encoding = determine_encoding(frame_data) @@ -719,13 +736,7 @@ def parse(cls, data, struct_pattern, size_len, per_byte): description=decode_bytestring(description, encoding), url=unquote(decode_bytestring(url)), ) - elif issubclass( - frame_type, - ( - ID3v2NumberFrame, - ID3v2UserTextFrame, - ), - ): + elif issubclass(frame_type, ID3v2NumberFrame): encoding = determine_encoding(frame_data[0:1]) kwargs['value'] = decode_bytestring(frame_data[1:], encoding) elif issubclass(