From ac98777adc7279b7bd216d22077d8e84d2b35f54 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Sat, 10 Jul 2010 14:05:01 -0700 Subject: [PATCH] MediaFile support for MusicBrainz track, album, and artist IDs --- beets/mediafile.py | 73 ++++++++++++++++++++++++++++++----- test/rsrc/full.ape | Bin 13730 -> 13955 bytes test/rsrc/full.flac | Bin 21890 -> 21890 bytes test/rsrc/full.m4a | Bin 5862 -> 5862 bytes test/rsrc/full.mp3 | Bin 12820 -> 12820 bytes test/rsrc/full.ogg | Bin 8849 -> 9031 bytes test/test_mediafile_basic.py | 19 +++++++-- 7 files changed, 78 insertions(+), 14 deletions(-) diff --git a/beets/mediafile.py b/beets/mediafile.py index 51d806944..74dc00b11 100644 --- a/beets/mediafile.py +++ b/beets/mediafile.py @@ -68,8 +68,8 @@ class StorageStyle(object): - key: The Mutagen key used to access the field's data. - list_elem: Store item as a single object or as first element of a list. - - as_type: Which type the value is stored as (unicode, int, or - bool). + - as_type: Which type the value is stored as (unicode, int, + bool, or str). - packing: If this value is packed in a multiple-value storage unit, which type of packing (in the packing enum). Otherwise, None. (Makes as_type irrelevant). @@ -78,8 +78,8 @@ class StorageStyle(object): - ID3 storage only: match against this 'desc' field as well as the key. """ - def __init__(self, key, list_elem = True, as_type = unicode, packing = None, - pack_pos = 0, id3_desc = None): + def __init__(self, key, list_elem = True, as_type = unicode, + packing = None, pack_pos = 0, id3_desc = None): self.key = key self.list_elem = list_elem self.as_type = as_type @@ -214,10 +214,19 @@ class MediaField(object): if entry is None: # no desc match return None else: + # Get the metadata frame object. try: - entry = obj.mgfile[style.key].text + frame = obj.mgfile[style.key] except KeyError: return None + + # For most frame types, the data is in the 'text' field. + # For UFID (used for the MusicBrainz track ID), it's in + # the 'data' field. + if isinstance(frame, mutagen.id3.UFID): + entry = frame.data + else: + entry = frame.text else: # Not MP3. try: @@ -244,7 +253,8 @@ class MediaField(object): else: out = val if obj.type == 'mp3': - if style.id3_desc is not None: # match on desc field + # Try to match on "desc" field. + if style.id3_desc is not None: frames = obj.mgfile.tags.getall(style.key) # try modifying in place @@ -258,10 +268,29 @@ class MediaField(object): # need to make a new frame? if not found: frame = mutagen.id3.Frames[style.key]( - encoding=3, desc=style.id3_desc, text=val) + encoding=3, + desc=style.id3_desc, + text=val + ) obj.mgfile.tags.add(frame) - else: # no match on desc; just replace based on key + # Try to match on "owner" field. + elif style.key.startswith('UFID:'): + owner = style.key.split(':', 1)[1] + frames = obj.mgfile.tags.getall(style.key) + + for frame in frames: + # Replace existing frame data. + if frame.owner == owner: + frame.data = out + else: + # New frame. + frame = mutagen.id3.UFID(owner=owner, data=val) + obj.mgfile.tags.setall('UFID', [frame]) + + # Just replace based on key. + else: + frame = mutagen.id3.Frames[style.key](encoding=3, text=val) obj.mgfile.tags.setall(style.key, [frame]) @@ -366,8 +395,8 @@ class MediaField(object): out = 0 else: out = int(out) - elif style.as_type == bool: - out = bool(out) + elif style.as_type in (bool, str): + out = style.as_type(out) # store the data self._storedata(obj, out, style) @@ -592,6 +621,30 @@ class MediaFile(object): as_type = bool), etc = StorageStyle('compilation') ) + + # MusicBrainz IDs. + mb_trackid = MediaField( + mp3 = StorageStyle('UFID:http://musicbrainz.org', + list_elem = False), + mp4 = StorageStyle( + '----:com.apple.iTunes:MusicBrainz Track Id', + as_type=str), + etc = StorageStyle('musicbrainz_trackid') + ) + mb_albumid = MediaField( + mp3 = StorageStyle('TXXX', id3_desc='MusicBrainz Album Id'), + mp4 = StorageStyle( + '----:com.apple.iTunes:MusicBrainz Album Id', + as_type=str), + etc = StorageStyle('musicbrainz_albumid') + ) + mb_artistid = MediaField( + mp3 = StorageStyle('TXXX', id3_desc='MusicBrainz Artist Id'), + mp4 = StorageStyle( + '----:com.apple.iTunes:MusicBrainz Artist Id', + as_type=str), + etc = StorageStyle('musicbrainz_artistid') + ) @property def length(self): diff --git a/test/rsrc/full.ape b/test/rsrc/full.ape index 5b47f23eb680d7890165b9ea3040139e5d897123..5c9efc049b5258bf2eff89f9a4ff90bccbc46f2f 100644 GIT binary patch delta 340 zcmZ{g!AiqG5QY~of>-gT;GqYR4y4IWcJ>%b(1OyE+-E1(lv67?*y(styc>(bW zd=$?)J-$QvC&EhP&itm07g-{1V9E-j_H`-mu-^-i(WHL@g z`{>RWFLh~~qkch%D9I*i)(^OD%ckvyI5-(kZ)ejqPWrxGR?o}E)!j9oy$yC;wQ5Rh zYA&B%A9!I_RhbLzNCku%h*^SE+5@8r0a;_<$Q7Ya`}nqIJ74o;h;M1& delta 117 zcmZq9U6j2+$7HgP$u%v7f<#v zRS|U$@(&I0^m7M`rx)dy7G&n7PtGuvRd5V&4RLgLjkv(hz);4>z#srI#sO*^0{}|;7#kbW6DS_TN<2|+qF^C- zfXBN$^yQ(ivrcDtn~(J185a6PN~z6Z9|~7X*dCWnEyXI7Q*uNpT7-lI!5BkPoTHQvkUEIev8sbBX delta 48 zcmV-00MGw|s{w+m0gyrh0dA2*Bmon#X-NSD1^@sclaT@-1c3k^ma~xp-yM^n0=Khp GAQClJ01k2h diff --git a/test/rsrc/full.m4a b/test/rsrc/full.m4a index e2a952f12c0afbf27dc817ff92a59b519ceff34e..32744686ae7d5553aa321d5a2af6d137f768480d 100755 GIT binary patch delta 349 zcmZwBze>YU6b5k9{28Q^t4rD-f^Z;Ba+7l%gbEH0r3mVNZgL}0TQE&Q9bBB85BLC% zB2vjJK8r4W0w2J{IcGU=zKS5FURK6s;aT_3B~UG|48rDngr9DOwwJgtl5HDk(~=)SF9qK~=u3F^ zU3?yAqdBH#~!tb@vK0xGCRhMZVXE}%m z?-nCF2(xK{Xy0|v+^>>mK{3b5;p1P9-J~G84<5Rj*ffk52yb|gc3DG^d7_e3!PDcN zz-SGjl1bTe_L|0F2HtQmz~~g<<6^oJ-oQt13xQO0rNm&8 z65FOGk*;(}7z9kVsMQ*CB}SvsFU!_I*LTYLdb{k@kAN%YT8whSq_m0ca)Sh%Q9>oB zmSSq`m<&Y6*Rr)xr_xgcs}!Wg`ZsMK3;?{FzSAK>%bBE$Vj1HGf7iCLiEI0Of5xS2 zwkivkv0P*-g}+fRvC+R#%WKzhRyQ0QO7e&>C*Z`6WJChV6)`PJrDHSBmHay1Jl*-a I+WS8H0dX#ikN^Mx delta 320 zcmYL^%}T>S6oros4T8V)39@nFrcKgX7lN3^AY!#at_zp89g{#ZDU(ESCG-u%K8Cxz zi_hcj#Nce^@Nw?B=Wdpp<;CswumgU_BY$?!AB2n&s$_WMg|Gn%9)KfkktnRU^ZvN+ zLR;g{@7UC8{S|c9A5T;In@1T7D}7hVq5y>su&>FBDwCpQ;sU*fr1L7XTJH1yL(La? zw#o~kK;9JC`}cx~;~G<>@@kdH2=sUdbmwpfLk{;?V>CDeO+vuH8062|kVr@+n)E!nor=j4b|vPQiUi+6SJ3ZY<{!sTvv^lJy}J_ YKq4Bn