Add Composer, Lyricist and Arranger tags

MusicBrainz provids composer, lyricist and arranger infomations related
to individual recordings. This commit adds query parameters to fetch them, and
write down to media files.

Tagging mapping is implemented according MusicBrainz Picard's data:
https://picard.musicbrainz.org/docs/mappings/

Signed-off-by: Shen-Ta Hsieh <ibmibmibm.tw@gmail.com>
This commit is contained in:
Shen-Ta Hsieh 2016-12-20 20:15:46 +08:00
parent cba30163cd
commit 7060b512b8
8 changed files with 111 additions and 3 deletions

View file

@ -44,6 +44,14 @@ def apply_item_metadata(item, track_info):
item.mb_artistid = track_info.artist_id
if track_info.data_source:
item.data_source = track_info.data_source
if track_info.lyricist is not None:
item.lyricist = track_info.lyricist
if track_info.composer is not None:
item.composer = track_info.composer
if track_info.arranger is not None:
item.arranger = track_info.arranger
# At the moment, the other metadata is left intact (including album
# and track number). Perhaps these should be emptied?
@ -142,3 +150,10 @@ def apply_metadata(album_info, mapping):
if track_info.media is not None:
item.media = track_info.media
if track_info.lyricist is not None:
item.lyricist = track_info.lyricist
if track_info.composer is not None:
item.composer = track_info.composer
if track_info.arranger is not None:
item.arranger = track_info.arranger

View file

@ -142,6 +142,9 @@ class TrackInfo(object):
- ``artist_credit``: Recording-specific artist name
- ``data_source``: The original data source (MusicBrainz, Discogs, etc.)
- ``data_url``: The data source release URL.
- ``lyricist``: individual track lyricist name
- ``composer``: individual track composer name
- ``arranger`: individual track arranger name
Only ``title`` and ``track_id`` are required. The rest of the fields
may be None. The indices ``index``, ``medium``, and ``medium_index``
@ -151,7 +154,7 @@ class TrackInfo(object):
length=None, index=None, medium=None, medium_index=None,
medium_total=None, artist_sort=None, disctitle=None,
artist_credit=None, data_source=None, data_url=None,
media=None):
media=None, lyricist=None, composer=None, arranger=None):
self.title = title
self.track_id = track_id
self.artist = artist
@ -167,6 +170,9 @@ class TrackInfo(object):
self.artist_credit = artist_credit
self.data_source = data_source
self.data_url = data_url
self.lyricist = lyricist
self.composer = composer
self.arranger = arranger
# As above, work around a bug in python-musicbrainz-ngs.
def decode(self, codec='utf-8'):

View file

@ -54,8 +54,10 @@ class MusicBrainzAPIError(util.HumanReadableException):
log = logging.getLogger('beets')
RELEASE_INCLUDES = ['artists', 'media', 'recordings', 'release-groups',
'labels', 'artist-credits', 'aliases']
TRACK_INCLUDES = ['artists', 'aliases']
'labels', 'artist-credits', 'aliases',
'recording-level-rels', 'work-rels',
'work-level-rels', 'artist-rels']
TRACK_INCLUDES = ['artists', 'aliases', 'work-level-rels', 'artist-rels']
def track_url(trackid):
@ -179,6 +181,33 @@ def track_info(recording, index=None, medium=None, medium_index=None,
if recording.get('length'):
info.length = int(recording['length']) / (1000.0)
lyricist = []
composer = []
for work_relation in recording.get('work-relation-list', ()):
if work_relation['type'] != 'performance':
continue
for artist_relation in work_relation['work'].get(
'artist-relation-list', ()):
if 'type' in artist_relation:
type = artist_relation['type']
if type == 'lyricist':
lyricist.append(artist_relation['artist']['name'])
elif type == 'composer':
composer.append(artist_relation['artist']['name'])
if lyricist:
info.lyricist = u', '.join(lyricist)
if composer:
info.composer = u', '.join(composer)
arranger = []
for artist_relation in recording.get('artist-relation-list', ()):
if 'type' in artist_relation:
type = artist_relation['type']
if type == 'arranger':
arranger.append(artist_relation['artist']['name'])
if arranger:
info.arranger = u', '.join(arranger)
info.decode()
return info

View file

@ -415,7 +415,9 @@ class Item(LibModel):
'albumartist_sort': types.STRING,
'albumartist_credit': types.STRING,
'genre': types.STRING,
'lyricist': types.STRING,
'composer': types.STRING,
'arranger': types.STRING,
'grouping': types.STRING,
'year': types.PaddedInt(4),
'month': types.PaddedInt(2),

View file

@ -752,6 +752,45 @@ class MP3StorageStyle(StorageStyle):
mutagen_file.tags.setall(self.key, [frame])
class MP3PeopleStorageStyle(MP3StorageStyle):
"""Store list of people in ID3 frames.
"""
def __init__(self, key, involvement='', **kwargs):
self.involvement = involvement
super(MP3PeopleStorageStyle, self).__init__(key, **kwargs)
def store(self, mutagen_file, value):
frames = mutagen_file.tags.getall(self.key)
print(frames)
# Try modifying in place.
found = False
for frame in frames:
if frame.encoding == mutagen.id3.Encoding.UTF8:
for pair in frame.people:
if pair[0].lower() == self.involvement.lower():
pair[1] = value
found = True
# Try creating a new frame.
if not found:
frame = mutagen.id3.Frames[self.key](
encoding=mutagen.id3.Encoding.UTF8,
people=[[self.involvement, value]]
)
print(frame)
mutagen_file.tags.add(frame)
def fetch(self, mutagen_file):
for frame in mutagen_file.tags.getall(self.key):
for pair in frame.people:
if pair[0].lower() == self.involvement.lower():
try:
return pair[1]
except IndexError:
return None
class MP3ListStorageStyle(ListStorageStyle, MP3StorageStyle):
"""Store lists of data in multiple ID3 frames.
"""
@ -1590,12 +1629,25 @@ class MediaFile(object):
)
genre = genres.single_field()
lyricist = MediaField(
MP3StorageStyle('TEXT'),
MP4StorageStyle('----:com.apple.iTunes:LYRICIST'),
StorageStyle('LYRICIST'),
ASFStorageStyle('WM/Writer'),
)
composer = MediaField(
MP3StorageStyle('TCOM'),
MP4StorageStyle('\xa9wrt'),
StorageStyle('COMPOSER'),
ASFStorageStyle('WM/Composer'),
)
arranger = MediaField(
MP3PeopleStorageStyle('TIPL', involvement='arranger'),
MP4StorageStyle('----:com.apple.iTunes:Arranger'),
StorageStyle('ARRANGER'),
ASFStorageStyle('beets/Arranger'),
)
grouping = MediaField(
MP3StorageStyle('TIT1'),
MP4StorageStyle('\xa9grp'),

View file

@ -65,7 +65,9 @@ def item(lib=None):
albumartist=u'the album artist',
album=u'the album',
genre=u'the genre',
lyricist=u'the lyricist',
composer=u'the composer',
arranger=u'the arranger',
grouping=u'the grouping',
year=1,
month=2,

Binary file not shown.

View file

@ -329,7 +329,9 @@ class ReadWriteTestBase(ArtTestMixin, GenreListTestMixin,
'artist',
'album',
'genre',
'lyricist',
'composer',
'arranger',
'grouping',
'year',
'month',