Skip to content

Commit

Permalink
Add support for ID3v2 involved people list frames [#22]
Browse files Browse the repository at this point in the history
  • Loading branch information
thebigmunch committed Feb 26, 2020
1 parent 3da786b commit 890ff56
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 4 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ This project adheres to [Semantic Versioning](https://semver.org).

[Commits](https://github.com/thebigmunch/audio-metadata/compare/0.7.0...master)

### Added

* Support for ID3v2 involved people list frames.
* ``ID3v2InvolvedPerson``.
* ``ID3v2InvolvedPeopleListFrame``.

### Changed

* Make all attrs classes require keyword arguments.
Expand Down
4 changes: 4 additions & 0 deletions src/audio_metadata/formats/id3v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class ID3v2Frames(Tags):
'originalartist': 'TOA',
'originalauthor': 'TOL',
'originaldate': 'TOR',
'people': 'IPL',
'pictures': 'PIC',
'playcount': 'CNT',
'remixer': 'TP4',
Expand Down Expand Up @@ -113,6 +114,7 @@ class ID3v2Frames(Tags):
'originalartist': 'TOPE',
'originalauthor': 'TOLY',
'originaldate': 'TORY',
'people': 'IPLS',
'pictures': 'APIC',
'playcount': 'PCNT',
'remixer': 'TPE4',
Expand Down Expand Up @@ -158,6 +160,7 @@ class ID3v2Frames(Tags):
'originalartist': 'TOPE',
'originalauthor': 'TOLY',
'originaldate': 'TDOR',
'people': 'TIPL',
'pictures': 'APIC',
'playcount': 'PCNT',
'remixer': 'TPE4',
Expand Down Expand Up @@ -257,6 +260,7 @@ def load(cls, data, id3_version):
elif isinstance(
frame,
(
ID3v2InvolvedPeopleListFrame,
ID3v2NumericTextFrame,
ID3v2TextFrame,
ID3v2TimestampFrame,
Expand Down
54 changes: 50 additions & 4 deletions src/audio_metadata/formats/id3v2_frames.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
'ID3v2Frame',
'ID3v2GEOBFrame',
'ID3v2GenreFrame',
'ID3v2InvolvedPeopleListFrame',
'ID3v2InvolvedPerson',
'ID3v2NumberFrame',
'ID3v2NumericTextFrame',
'ID3v2Picture',
Expand All @@ -26,6 +28,7 @@
import struct
from urllib.parse import unquote

import more_itertools
from attr import (
attrib,
attrs,
Expand All @@ -52,6 +55,15 @@
_genre_re = re.compile(r"((?:\((?P<id>\d+|RX|CR)\))*)(?P<name>.+)?")


@attrs(
repr=False,
kw_only=True,
)
class ID3v2InvolvedPerson(AttrMapping):
involvement = attrib()
name = attrib()


class ID3v2Picture(Picture):
@datareader
@classmethod
Expand Down Expand Up @@ -117,6 +129,14 @@ class ID3v2GEOBFrame(ID3v2BaseFrame):
value = attrib()


@attrs(
repr=False,
kw_only=True,
)
class ID3v2InvolvedPeopleListFrame(ID3v2BaseFrame):
value = attrib()


@attrs(
repr=False,
kw_only=True,
Expand Down Expand Up @@ -311,26 +331,29 @@ class ID3v2Frame(ID3v2BaseFrame):
value = attrib()

# TODO:ID3v2.2
# TODO: BUF, CNT, CRA, CRM, ETC, EQU, IPL, LNK, MCI, MLL, POP, REV,
# TODO: BUF, CNT, CRA, CRM, ETC, EQU, LNK, MCI, MLL, POP, REV,
# TODO: RVA, STC, UFI

# TODO: ID3v2.3
# TODO: AENC, COMR, ENCR, EQUA, ETCO, GRID, IPLS, LINK, MCDI, MLLT, OWNE
# TODO: AENC, COMR, ENCR, EQUA, ETCO, GRID, LINK, MCDI, MLLT, OWNE
# TODO: PCNT, POPM, POSS, RBUF, RVAD, RVRB, SYTC, UFID, USER

# TODO: ID3v2.4
# TODO: ASPI, EQU2, RVA2, SEEK, SIGN, TDEN, TDOR, TDRC, TDRL, TDTG, TIPL
# TODO: TMCL, TPRO,
# TODO: ASPI, EQU2, RVA2, SEEK, SIGN, TDEN, TDOR, TDRC, TDRL, TDTG
# TODO: TMCL, TPRO

_FRAME_TYPES = {
# Complex Text Frames
'COM': ID3v2CommentFrame,
'GEO': ID3v2GEOBFrame,
'IPL': ID3v2InvolvedPeopleListFrame,
'TXX': ID3v2UserTextFrame,

'COMM': ID3v2CommentFrame,
'GEOB': ID3v2GEOBFrame,
'IPLS': ID3v2InvolvedPeopleListFrame,
'PRIV': ID3v2PrivateFrame,
'TIPL': ID3v2InvolvedPeopleListFrame,
'TXXX': ID3v2UserTextFrame,

# Genre Frame
Expand Down Expand Up @@ -492,6 +515,29 @@ def load(cls, data, struct_pattern, size_len, per_byte):
kwargs['language'] = decode_bytestring(frame_data[1:4])
kwargs['description'] = decode_bytestring(values[0], encoding)
kwargs['value'] = decode_bytestring(values[1], encoding)
elif frame_type is ID3v2InvolvedPeopleListFrame:
encoding = determine_encoding(frame_data[0:1])

values = []
tail = frame_data[1:]

while tail:
head, tail = split_encoded(tail, encoding)
values.append(head)

people = [
ID3v2InvolvedPerson(
involvement=decode_bytestring(involvement, encoding),
name=decode_bytestring(name, encoding),
)
for involvement, name in more_itertools.chunked(values, 2)
]

# Ignore empty people list.
if len(values) < 1:
return None

kwargs['value'] = people
elif frame_type is ID3v2GenreFrame:
encoding = determine_encoding(frame_data[0:1])

Expand Down

0 comments on commit 890ff56

Please sign in to comment.