diff --git a/CHANGELOG.md b/CHANGELOG.md index c739d65..c75afc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,10 @@ This project adheres to [Semantic Versioning](https://semver.org). * Support for ID3v2 unique file identifier frames. * ``ID3v2UniqueFileIdentifier``. * ``ID3v2UniqueFileIdentifierFrame``. +* Support for ID3v2 synchronized tempo codes frames. + * ``ID3v2TempoTimestampFormat``, + * ``ID3v2SynchronizedTempoCodes``. + * ``ID3v2SynchronizedTempoCodesFrame``. ### Changed diff --git a/src/audio_metadata/formats/id3v2_frames.py b/src/audio_metadata/formats/id3v2_frames.py index af82496..caf9a2a 100644 --- a/src/audio_metadata/formats/id3v2_frames.py +++ b/src/audio_metadata/formats/id3v2_frames.py @@ -24,6 +24,8 @@ 'ID3v2PrivateInfo', 'ID3v2SynchronizedLyrics', 'ID3v2SynchronizedLyricsFrame', + 'ID3v2SynchronizedTempoCodes', + 'ID3v2SynchronizedTempoCodesFrame', 'ID3v2TimeFrame', 'ID3v2TMCLFrame', 'ID3v2TextFrame', @@ -62,6 +64,7 @@ ID3v1Genres, ID3v2LyricsContentType, ID3v2LyricsTimestampFormat, + ID3v2TempoTimestampFormat, ) from ..exceptions import ( FormatError, @@ -185,6 +188,15 @@ class ID3v2PrivateInfo(AttrMapping): data = attrib() +@attrs( + repr=False, + kw_only=True, +) +class ID3v2SynchronizedTempoCodes(AttrMapping): + timestamp_format = attrib(converter=ID3v2TempoTimestampFormat) + data = attrib() + + @attrs( repr=False, kw_only=True, @@ -504,6 +516,25 @@ def _parse_frame_data(cls, data, frame_size): ) +@attrs( + repr=False, + kw_only=True, +) +class ID3v2SynchronizedTempoCodesFrame(ID3v2Frame): + @datareader + @classmethod + def _parse_frame_data(cls, data, frame_size): + frame_data = data.read(frame_size) + + return ( + ID3v2SynchronizedTempoCodes( + timestamp_format=ID3v2TempoTimestampFormat(frame_data[0]), + data=frame_data[1:] + ), + None, + ) + + @attrs( repr=False, kw_only=True, @@ -834,11 +865,11 @@ def _parse_frame_data(cls, data, frame_size): # TODO:ID3v2.2 # TODO: BUF, CNT, CRA, CRM, ETC, EQU, LNK, MCI, MLL, PCS, -# TODO: POP, REV, RVA, STC +# TODO: POP, REV, RVA # TODO: ID3v2.3 # TODO: AENC, COMR, ENCR, EQUA, ETCO, GRID, LINK, MLLT, OWNE -# TODO: PCNT, PCST, POPM, POSS, RBUF, RGAD, RVAD, RVRB, SYTC, +# TODO: PCNT, PCST, POPM, POSS, RBUF, RGAD, RVAD, RVRB, # TODO: USER, XRVA # TODO: ID3v2.4 @@ -849,21 +880,26 @@ def _parse_frame_data(cls, data, frame_size): 'MCDI': ID3v2BinaryDataFrame, 'NCON': ID3v2BinaryDataFrame, + # Complex binary data frames + 'GEO': ID3v2GeneralEncapsulatedObjectFrame, + 'STC': ID3v2SynchronizedTempoCodesFrame, + 'UFI': ID3v2UniqueFileIdentifierFrame, + + 'GEOB': ID3v2GeneralEncapsulatedObjectFrame, + 'PRIV': ID3v2PrivateFrame, + 'SYTC': ID3v2SynchronizedTempoCodesFrame, + 'UFID': ID3v2UniqueFileIdentifierFrame, + # Complex Text Frames 'COM': ID3v2CommentFrame, - 'GEO': ID3v2GeneralEncapsulatedObjectFrame, 'IPL': ID3v2InvolvedPeopleListFrame, 'TXX': ID3v2UserTextFrame, - 'UFI': ID3v2UniqueFileIdentifierFrame, 'COMM': ID3v2CommentFrame, - 'GEOB': ID3v2GeneralEncapsulatedObjectFrame, 'IPLS': ID3v2InvolvedPeopleListFrame, - 'PRIV': ID3v2PrivateFrame, 'TIPL': ID3v2InvolvedPeopleListFrame, 'TMCL': ID3v2TMCLFrame, 'TXXX': ID3v2UserTextFrame, - 'UFID': ID3v2UniqueFileIdentifierFrame, # Genre Frame 'TCO': ID3v2GenreFrame, diff --git a/src/audio_metadata/formats/tables.py b/src/audio_metadata/formats/tables.py index 6171fd1..e619cab 100644 --- a/src/audio_metadata/formats/tables.py +++ b/src/audio_metadata/formats/tables.py @@ -7,6 +7,7 @@ 'ID3v2FrameIDs', 'ID3v2LyricsContentType', 'ID3v2LyricsTimestampFormat', + 'ID3v2TempoTimestampFormat', 'ID3v2UnofficialFrameIDs', 'LAMEBitrateMode', 'LAMEChannelMode', @@ -94,6 +95,11 @@ class ID3v2LyricsTimestampFormat(_BaseIntEnum): MILLISECONDS = 1 +class ID3v2TempoTimestampFormat(_BaseIntEnum): + MPEG_FRAMES = 0 + MILLISECONDS = 1 + + class ID3Version(_BaseEnum): v10 = (1, 0) v11 = (1, 1) @@ -146,6 +152,7 @@ class ID3Version(_BaseEnum): 'rating': 'POP', 'remixer': 'TP4', 'subtitle': 'TT3', + 'tempo': 'STC', 'title': 'TT2', 'titlesort': 'TST', 'tracknumber': 'TRK', @@ -198,6 +205,7 @@ class ID3Version(_BaseEnum): 'rating': 'POPM', 'remixer': 'TPE4', 'subtitle': 'TIT3', + 'tempo': 'SYTC', 'title': 'TIT2', 'titlesort': 'TSOT', 'tracknumber': 'TRCK', @@ -254,6 +262,7 @@ class ID3Version(_BaseEnum): 'rating': 'POPM', 'remixer': 'TPE4', 'subtitle': 'TIT3', + 'tempo': 'SYTC', 'title': 'TIT2', 'titlesort': 'TSOT', 'tracknumber': 'TRCK',