/[soapbox]/prolatio/music_tagger/tagging.py
ViewVC logotype

Contents of /prolatio/music_tagger/tagging.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3031 - (show annotations) (download) (as text)
Sun Sep 5 19:17:29 2010 UTC (11 years, 8 months ago) by tadas
File MIME type: text/x-python
File size: 10764 byte(s)
sutaisytas pilnai veikiantis taggingas
1 from UserDict import DictMixin
2 import tagpy
3
4
5 class TagExtBase(object):
6 def __init__(self, metafile, tag, map):
7 # bypass __setattr__
8 self.__dict__['metafile'] = metafile
9 self.__dict__['tag'] = tag
10 self.__dict__['map'] = map
11
12 # a shortcut for getting values from field map
13 def map_get(self, key, default=None):
14 try:
15 el = self.map[key]
16 except:
17 return default
18 type_name = type(el).__name__
19 if type_name == 'StringList':
20 return el[0]
21 elif type_name == 'id3v2_FrameList':
22 return el[0].toString()
23 return el
24
25 # from string of format %d/%d get first number
26 def afromb_a(self, value):
27 if not value:
28 return 0
29 slash_pos = value.find('/')
30 if not slash_pos:
31 return 0
32 return int(value[:slash_pos])
33
34 # from string of format %d/%d get second number
35 def afromb_b(self, value):
36 if not value:
37 return 0
38 b_pos = value.find('/')
39 if not b_pos:
40 return 0
41 b_pos += 1
42 return int(value[b_pos:])
43
44
45 class TagExtXiph(TagExtBase):
46 # used XiphComment fields are: COMPOSER, CONDUCTOR, COPYRIGHT, DISCNUMBER, DISCTOTAL, TRACKTOTAL
47
48 def __init__(self, metafile, tag):
49 super(TagExtXiph, self).__init__(metafile, tag, tag.fieldListMap())
50
51 # map internal field names to XiphComment fields
52 def field_map(self, field):
53 if field == 'composer':
54 return 'COMPOSER'
55 if field == 'conductor':
56 return 'CONDUCTOR'
57 if field == 'copyright':
58 return 'COPYRIGHT'
59 if field == 'disc':
60 return 'DISCNUMBER'
61 if field == 'disc_count':
62 return 'DISCTOTAL'
63 if field == 'track_count':
64 return 'TRACKTOTAL'
65 return None
66
67 def __getattr__(self, name):
68 field = self.field_map(name)
69 if not field:
70 return None
71 value = self.map_get(field)
72 conversion = self.metafile.map[name][1]
73 if callable(conversion) and value != None:
74 value = conversion.__call__(value)
75 return value
76
77 def __setattr__(self, name, value):
78 field = self.field_map(name)[0]
79 if not field:
80 return
81 self.tag.addField(field, unicode(value), True)
82
83
84 class TagExtID3v2(TagExtBase):
85 def __init__(self, metafile, tag):
86 super(TagExtID3v2, self).__init__(metafile, tag, tag.frameListMap())
87
88 # map internal field names to ID3v2 frame types
89 def field_map(self, field):
90 if field == 'composer':
91 return 'TCOM'
92 if field == 'conductor':
93 return 'TPE3'
94 if field == 'copyright':
95 return 'TCOP'
96 if field == 'disc' or field == 'disc_count':
97 return 'TPOS'
98 if field == 'track_count':
99 return 'TRCK'
100 return None
101
102 def __getattr__(self, name):
103 frame_type = self.field_map(name)
104 if not frame_type:
105 return None
106 value = self.map_get(frame_type)
107 # convert to single integers for disc, disc_count and track_count
108 if name == 'disc':
109 value = self.afromb_a(value)
110 elif name == 'disc_count' or name == 'track_count':
111 value = self.afromb_b(value)
112
113 return value
114
115 def __setattr__(self, name, value):
116 from tagpy.id3v2 import TextIdentificationFrame
117
118 frame_type = self.field_map(name)
119 if not frame_type:
120 return
121
122 # some special processing
123 if name == 'disc':
124 total = self.metafile.get('disc_count') or 0
125 value = '%s/%s' % (value, total)
126 elif name == 'disc_count':
127 disc = self.metafile.get('disc') or 0
128 value = '%s/%s' % (disc, value)
129 elif name == 'track_count':
130 track = self.metafile.get('track') or 0
131 value = '%s/%s' % (track, value)
132 print "name: %s, value: %s" % (name, value)
133
134 frame_list = self.map[frame_type]
135 if frame_list and frame_list.size:
136 # replace frame value if it exists
137 frame = frame_list[0]
138 frame.setText(value)
139 else:
140 # add new frame otherwise
141 frame = TextIdentificationFrame(frame_type)
142 frame.setText(value)
143 self.tag.addFrame(frame)
144
145
146 class TagExtAPE(TagExtBase):
147 def __init__(self, metafile, tag):
148 super(TagExtAPE, self).__init__(metafile, tag, tag.itemListMap())
149
150 # map internal field names to APE fields
151 def field_map(self, field):
152 if field == 'composer':
153 return 'Composer'
154 if field == 'conductor':
155 return 'Conductor'
156 if field == 'copyright':
157 return 'Copyright'
158 if field == 'disc' or field == 'disc_count':
159 return 'Disc'
160 if field == 'track_count':
161 return 'Track'
162 return None
163
164 def __getattr__(self, name):
165 field = self.field_map(name)
166 if not field:
167 return None
168 value = self.map_get(field)
169 # convert to single integers for disc, disc_count and track_count
170 if name == 'disc':
171 value = self.afromb_a(value)
172 elif name == 'disc_count' or name == 'track_count':
173 value = self.afromb_b(value)
174
175 return value
176
177 def __setattr__(self, name, value):
178 field = self.field_map(name)
179 if not field:
180 return
181
182 # some special processing
183 if name == 'disc':
184 total = self.metafile.get('disc_count') or 0
185 value = '%s/%s' % (value, total)
186 elif name == 'disc_count':
187 disc = self.metafile.get('disc') or 0
188 value = '%s/%s' % (disc, value)
189 elif name == 'track_count':
190 track = self.metafile.get('track') or 0
191 value = '%s/%s' % (track, value)
192
193 self.tag.addValue(field, value, True)
194
195
196 # a wrapper around TagPy to seamlessly handle non-standard tags
197 # specific tag handling implementation is based on the excellent taglib-sharp
198 class MetaFile(DictMixin):
199 def __init__(self, file, readAudioProperties=True, audioPropertiesStyle=tagpy.ReadStyle.Average):
200 fileref = tagpy.FileRef(file, readAudioProperties, audioPropertiesStyle)
201 # set main attrs (bypassing __setattr__)
202 self.f = fileref.file()
203 self.tag = self.f.tag()
204 self._tags = dict()
205 self._changes = dict()
206
207 def get_ext_tag(self):
208 def by_tag_class(tag):
209 tag_type = tag.__class__.__name__
210 if tag_type == 'ogg_XiphComment':
211 return TagExtXiph(self, tag)
212 if tag_type == 'id3v2_Tag':
213 return TagExtID3v2(self, tag)
214 if tag_type == 'ape_Tag':
215 return TagExtAPE(self, tag)
216
217 file = self.f
218 # choose most preferrable tag type where we have choice
219 # don't consider ID3v1 since it's not possible to add any non-standard tags to it
220 file_type = file.__class__.__name__
221 if file_type == 'flac_File':
222 tag_xiph = file.xiphComment()
223 tag_id3v2 = file.ID3v2Tag()
224 return by_tag_class(tag_xiph or tag_id3v2 or file.xiphComment(create=True))
225 if file_type == 'mpc_File':
226 tag_ape = file.APETag()
227 tag_id3v1 = file.ID3v2Tag()
228 return by_tag_class(tag_ape or tag_id3v1 or file.APETag(create=True))
229 if file_type == 'mpeg_File':
230 tag_ape = file.APETag()
231 tag_id3v2 = file.ID3v2Tag()
232 return by_tag_class(tag_ape or tag_id3v2 or file.APETag(create=True))
233 # TrueAudio and Wavpack don't seem to be available in tagpy
234 # if file_type == 'trueaudio_File':
235 # tag_id3v2 = file.ID3v2Tag(create=True)
236 # return by_tag_class(tag_id3v2)
237 # if file_type == 'wavpack_File':
238 # tag_ape = file.APETag(create=True)
239 # return by_tag_class(tag_ape)
240 # get default tag type
241 return by_tag_class(file.tag())
242
243 def __getattr__(self, name):
244 if name == 'audioprops':
245 self.audioprops = self.f.audioProperties()
246 elif name == 'tag_ext':
247 self.tag_ext = self.get_ext_tag()
248 elif name == 'map':
249 self.map = dict({
250 'album' :('tag', unicode),
251 'artist' :('tag', unicode),
252 'comment' :('tag', unicode),
253 'genre' :('tag', unicode),
254 'title' :('tag', unicode),
255 'track' :('tag', int),
256 'year' :('tag', int),
257 'composer' :('tag_ext', unicode),
258 'conductor' :('tag_ext', unicode),
259 'copyright' :('tag_ext', unicode),
260 'disc' :('tag_ext', int),
261 'disc_count' :('tag_ext', int),
262 'track_count':('tag_ext', int),
263 'bitrate' :('audioprops', None),
264 'channels' :('audioprops', None),
265 'length' :('audioprops', None),
266 'sampleRate' :('audioprops', None)})
267
268 return self.__dict__[name]
269
270 def __getitem__(self, name):
271 if self._tags.has_key(name):
272 return self._tags[name]
273 if not self.map.has_key(name):
274 return None
275 store = getattr(self, self.map[name][0])
276 self._tags[name] = getattr(store, name)
277 return self._tags[name]
278
279 def __setitem__(self, name, value):
280 if self._tags.get(name) == value:
281 return
282 if not self.map.has_key(name):
283 return
284 # get conversion from map
285 conversion = self.map[name][1]
286 if callable(conversion) and value != None:
287 value = conversion.__call__(value)
288 self._tags[name] = value
289 self._changes[name] = True
290
291 def __delitem__(self, name):
292 pass
293
294 def keys(self):
295 return self._tags.keys()
296
297 def save(self):
298 if len(self._changes):
299 for name in self._changes.iterkeys():
300 store = getattr(self, self.map[name][0])
301 value = self._tags[name]
302 setattr(store, name, value)
303 self.f.save()

tadas_AT_dailyda_DOT_com
ViewVC Help
Powered by ViewVC 1.1.8