1 |
import tagpy
|
2 |
|
3 |
class TagExtBase(object):
|
4 |
def __init__(self, tag, map):
|
5 |
# bypass __setattr__
|
6 |
self.__dict__['tag'] = tag
|
7 |
self.__dict__['map'] = map
|
8 |
|
9 |
# a shortcut for getting values from field map
|
10 |
def map_get(self, key, default=None):
|
11 |
try:
|
12 |
el = self.map[key]
|
13 |
except:
|
14 |
return default
|
15 |
if type(el).__name__ == 'StringList':
|
16 |
return el[0]
|
17 |
return el
|
18 |
|
19 |
class TagExtXiph(TagExtBase):
|
20 |
# used XiphComment fields are: COMPOSER, CONDUCTOR, COPYRIGHT, DISCNUMBER, DISCTOTAL, TRACKTOTAL
|
21 |
|
22 |
def __init__(self, tag):
|
23 |
super(TagExtXiph, self).__init__(tag, tag.fieldListMap())
|
24 |
|
25 |
# map internal field names to XiphComment fields and return conversion functions (if needed)
|
26 |
def field_map(self, field):
|
27 |
if field == 'disc':
|
28 |
return ('DISCNUMBER', int)
|
29 |
if field == 'disc_count':
|
30 |
return ('DISCTOTAL', int)
|
31 |
if field == 'track_count':
|
32 |
return ('TRACKTOTAL', int)
|
33 |
# just uppercase composer, conductor and copyright
|
34 |
return (str(field).upper(), None)
|
35 |
|
36 |
def __getattr__(self, name):
|
37 |
(field, conversion) = self.field_map(name)
|
38 |
value = self.map_get(field)
|
39 |
if callable(conversion) and value != None:
|
40 |
value = conversion.__call__(value)
|
41 |
return value
|
42 |
|
43 |
def __setattr__(self, name, value):
|
44 |
field = self.field_map(name)[0]
|
45 |
self.tag.addField(field, unicode(value), True)
|
46 |
|
47 |
class TagExtID3v2(TagExtBase):
|
48 |
def __init__(self, tag):
|
49 |
super(TagExtID3v2, self).__init__(tag, tag.frameListMap())
|
50 |
|
51 |
def __getattr__(self, name):
|
52 |
pass
|
53 |
|
54 |
def __setattr__(self, name, value):
|
55 |
pass
|
56 |
|
57 |
class TagExtAPE(TagExtBase):
|
58 |
def __init__(self, tag):
|
59 |
super(TagExtAPE, self).__init__(tag, tag.itemListMap())
|
60 |
|
61 |
def __getattr__(self, name):
|
62 |
pass
|
63 |
|
64 |
def __setattr__(self, name, value):
|
65 |
pass
|
66 |
|
67 |
# a wrapper around TagPy to seamlessly handle non-standard tags
|
68 |
# specific tag handling implementation is based on the excellent taglib-sharp
|
69 |
class MetaFile(object):
|
70 |
TAG_COMMON_ATTRS = ('album', 'artist', 'comment', 'genre', 'title', 'track', 'year')
|
71 |
TAG_EXT_ATTRS = ('composer', 'conductor', 'copyright', 'disc', 'disc_count', 'track_count')
|
72 |
AUDIOPROP_COMMON_ATTRS = ('bitrate', 'channels', 'length', 'sampleRate')
|
73 |
|
74 |
def __init__(self, file, readAudioProperties=True, audioPropertiesStyle=tagpy.ReadStyle.Average):
|
75 |
fileref = tagpy.FileRef(file, readAudioProperties, audioPropertiesStyle)
|
76 |
# set main attrs (bypassing __setattr__)
|
77 |
dict = self.__dict__
|
78 |
dict['f'] = fileref.file()
|
79 |
dict['tag'] = self.f.tag()
|
80 |
dict['tag_ext'] = MetaFile.get_ext_tag(self.f)
|
81 |
dict['audioprops'] = dict['f'].audioProperties()
|
82 |
|
83 |
@staticmethod
|
84 |
def get_ext_tag(file):
|
85 |
def by_tag_class(tag):
|
86 |
tag_type = tag.__class__.__name__
|
87 |
if tag_type == 'ogg_XiphComment':
|
88 |
return TagExtXiph(tag)
|
89 |
if tag_type == 'id3v2_Tag':
|
90 |
return TagExtID3v2(tag)
|
91 |
if tag_type == 'ape_Tag':
|
92 |
return TagExtAPE(tag)
|
93 |
|
94 |
# choose most preferrable tag type where we have choice
|
95 |
# don't consider ID3v1 since it's not possible to add any non-standard tags to it
|
96 |
file_type = file.__class__.__name__
|
97 |
if file_type == 'flac_File':
|
98 |
tag_xiph = file.xiphComment()
|
99 |
tag_id3v2 = file.ID3v2Tag()
|
100 |
return by_tag_class(tag_xiph or tag_id3v2 or file.xiphComment(create=True))
|
101 |
if file_type == 'mpc_File':
|
102 |
tag_ape = file.APETag()
|
103 |
tag_id3v1 = file.ID3v2Tag()
|
104 |
return by_tag_class(tag_ape or tag_id3v1 or file.APETag(create=True))
|
105 |
if file_type == 'mpeg_File':
|
106 |
tag_ape = file.APETag()
|
107 |
tag_id3v2 = file.ID3v2Tag()
|
108 |
return by_tag_class(tag_ape or tag_id3v2 or file.APETag(create=True))
|
109 |
# if file_type == 'trueaudio_File':
|
110 |
# tag_id3v2 = file.ID3v2Tag(create=True)
|
111 |
# return by_tag_class(tag_id3v2)
|
112 |
# if file_type == 'wavpack_File':
|
113 |
# tag_ape = file.APETag(create=True)
|
114 |
# return by_tag_class(tag_ape)
|
115 |
# get default tag type
|
116 |
return by_tag_class(file.tag())
|
117 |
|
118 |
def __getattr__(self, name):
|
119 |
if name in MetaFile.TAG_COMMON_ATTRS:
|
120 |
return getattr(self.tag, name)
|
121 |
if name in MetaFile.TAG_EXT_ATTRS:
|
122 |
return getattr(self.tag_ext, name)
|
123 |
if name in MetaFile.AUDIOPROP_COMMON_ATTRS:
|
124 |
return getattr(self.audioprops, name)
|
125 |
|
126 |
def __setattr__(self, name, value):
|
127 |
if name in MetaFile.TAG_COMMON_ATTRS:
|
128 |
setattr(self.tag, name, value)
|
129 |
elif name in MetaFile.TAG_EXT_ATTRS:
|
130 |
setattr(self.tag_ext, name, value)
|
131 |
|
132 |
def save(self):
|
133 |
self.f.save()
|