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

Contents of /prolatio/music_tagger/tagging.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3040 - (show annotations) (download) (as text)
Tue Sep 14 15:42:58 2010 UTC (11 years, 9 months ago) by tadas
File MIME type: text/x-python
File size: 29466 byte(s)
more tagging fixes (id3v2 works just fine now, others not so much)
1 import tagpy
2
3
4 def cached_get(func):
5 def decor(self):
6 name = func.__name__[:-4]
7 if name in self.cache:
8 return self.cache[name]
9 self.cache[name] = func(self)
10 return self.cache[name]
11 return decor
12
13 def cached_set(func):
14 def decor(self, value):
15 name = func.__name__[:-4]
16 if name in self.cache and value == self.cache[name]:
17 return
18 func(self, value)
19 self.cache[name] = value
20 return decor
21
22 def cached_property(func_get, func_set=None):
23 if func_set:
24 func_set = cached_set(func_set)
25 return property(cached_get(func_get), func_set)
26
27
28 class TagExtBase(object):
29 class Def:
30 @staticmethod
31 def default_property(name):
32 def default_get(self):
33 return getattr(self.tag, name)
34 def default_set(self, value):
35 return setattr(self.tag, name, value)
36 default_get.__name__ = name+'_get'
37 default_set.__name__ = name+'_set'
38 return cached_property(default_get, default_set)
39
40 def __init__(self, tag, map):
41 self.tag = tag
42 self.map = map
43 self.cache = dict()
44
45 album = Def.default_property('album') # FIXME: problema del utf !!!
46
47 comment = Def.default_property('comment') # FIXME: problema del utf !!!
48
49 title = Def.default_property('title') # FIXME: problema del utf !!!
50
51 track = Def.default_property('track')
52
53 year = Def.default_property('year')
54
55 # a shortcut for getting values from field map
56 def map_get(self, key, default=None):
57 if not key:
58 return default
59 try:
60 return self.map[key]
61 except:
62 return default
63
64 def try_float(self, str):
65 if not str:
66 return 0.0
67 try:
68 return float(str)
69 except:
70 return 0.0
71
72 def try_int(self, str):
73 if not str:
74 return 0
75 try:
76 return int(str)
77 except:
78 return 0
79
80 def get_avail_properties(self):
81 def get_properties(class_dict):
82 for name, attr in class_dict.items():
83 if type(attr).__name__ == 'property':
84 yield name
85
86 prop_list = list()
87 prop_list.extend(get_properties(self.__class__.__dict__))
88 for base in self.__class__.__bases__:
89 prop_list.extend(get_properties(base.__dict__))
90 return prop_list
91
92 def copy_to(self, other_tag):
93 prop_list = self.get_avail_properties()
94 for prop in prop_list:
95 if hasattr(other_tag, prop):
96 setattr(other_tag, prop, getattr(self, prop))
97
98
99 class TagExtXiph(TagExtBase):
100 class Def:
101 @staticmethod
102 def default_property(prop, field_name=None, first=False):
103 field = field_name or prop.upper()
104
105 def default_get(self):
106 if first:
107 return self.get_first_field(field)
108 return self.get_field(field)
109 def default_set(self, value):
110 self.set_field(field, value)
111
112 default_get.__name__ = prop+'_get'
113 default_set.__name__ = prop+'_set'
114 return cached_property(default_get, default_set)
115
116 def __init__(self, tag):
117 super(TagExtXiph, self).__init__(tag, tag.fieldListMap())
118
119 def get_field(self, key):
120 value_list = self.map_get(key)
121 res = list()
122 if value_list:
123 for value in value_list:
124 res.append(value)
125 return res
126
127 def get_first_field(self, key):
128 values = self.get_field(key)
129 if len(values):
130 return values[0]
131 return None
132
133 def set_field(self, key, value_list):
134 #from _tagpy import StringList # very hacky stuff, but no other way to do it :D
135
136 if not key:
137 raise TypeError('key not given')
138 if isinstance(value_list, str) or isinstance(value_list, unicode):
139 value_list = [value_list]
140 if not getattr(value_list, '__iter__', False):
141 raise TypeError('value_list must be iterable')
142 if not len(value_list):
143 self.tag.removeField(key)
144 return
145 to_add = list()
146 for value in value_list:
147 if isinstance(value, str) or isinstance(value, unicode) and len(value.strip()):
148 to_add.append(value)
149 #if not len(to_add):
150 self.tag.removeField(key)
151 for value in to_add:
152 self.tag.addField(key, value, False)
153 #else:
154 #str_list = StringList()
155 #for value in to_add:
156 # str_list.append(value)
157 #self.map[key] = str_list
158
159 def set_field_int(self, key, number):
160 if not key:
161 raise TypeError('key not given')
162 if not isinstance(number, int):
163 raise TypeError("number must be of type 'int'")
164 if number == 0:
165 self.tag.removeField(key)
166 else:
167 self.set_field(key, str(number))
168
169 def album_artists_get(self):
170 value = self.get_field('ALBUMARTIST')
171 if not value:
172 value = self.get_field('ALBUM ARTIST')
173 if value:
174 return value
175 return self.get_field('ENSEMBLE')
176 def album_artists_set(self, value):
177 if 'ALBUM ARTIST' in self.map:
178 self.tag.removeField('ALBUM ARTIST')
179 if 'ENSEMBLE' in self.map:
180 self.tag.removeField('ENSEMBLE')
181 self.set_field('ALBUMARTIST', value)
182 album_artists = cached_property(album_artists_get, album_artists_set)
183
184 album_artists_sort = Def.default_property('album_artists_sort', 'ALBUMARTISTSORT')
185
186 album_sort = Def.default_property('album_sort', 'ALBUMSORT', True)
187
188 amazon_id = Def.default_property('amazon_id', 'ASIN', True)
189
190 def beats_per_minute_get(self):
191 value = self.get_first_field('TEMPO')
192 value = self.try_float(value)
193 if value:
194 return int(round(value))
195 return 0
196 def beats_per_minute_set(self, value):
197 self.set_field_int('TEMPO', value)
198 beats_per_minute = cached_property(beats_per_minute_get, beats_per_minute_set)
199
200 composers = Def.default_property('composers', 'COMPOSER')
201
202 composers_sort = Def.default_property('composers_sort', 'COMPOSERSORT')
203
204 conductor = Def.default_property('conductor', first=True)
205
206 copyright = Def.default_property('copyright', first=True)
207
208 def disc_get(self):
209 str = self.get_first_field('DISCNUMBER')
210 if not str:
211 return 0
212 num = self.try_int(str)
213 if num:
214 return num
215 split = str.partition('/')
216 if not split[2]:
217 return 0
218 return self.try_int(split[0])
219 def disc_set(self, value):
220 self.set_field_int('DISCTOTAL', self.disc_count)
221 self.set_field_int('DISCNUMBER', value)
222 disc = cached_property(disc_get, disc_set)
223
224 def disc_count_get(self):
225 str = self.get_first_field('DISCTOTAL')
226 num = self.try_int(str)
227 if num:
228 return num
229 str = self.get_first_field('DISCNUMBER')
230 if not str:
231 return 0
232 split = str.partition('/')
233 if not split[2]:
234 return 0
235 return self.try_int(split[2])
236 def disc_count_set(self, value):
237 self.set_field_int('DISCTOTAL', value)
238 disc_count = cached_property(disc_count_get, disc_count_set)
239
240 genres = Def.default_property('genres', 'GENRE')
241
242 def is_compilation_get(self):
243 str = self.get_first_field('COMPILATION')
244 num = self.try_int(str)
245 if num:
246 return True
247 return False
248 def is_compilation_set(self, value):
249 if value:
250 self.set_field_int('COMPILATION', 1)
251 else:
252 self.tag.removeField('COMPILATION')
253 is_compilation = cached_property(is_compilation_get, is_compilation_set)
254
255 lyrics = Def.default_property('lyrics', first=True)
256
257 grouping = Def.default_property('grouping', first=True)
258
259 musicbrainz_artist_id = Def.default_property('musicbrainz_artist_id', 'MUSICBRAINZ_ARTISTID', True)
260
261 musicbrainz_disc_id = Def.default_property('musicbrainz_disc_id', 'MUSICBRAINZ_DISCID', True)
262
263 musicbrainz_release_artist_id = Def.default_property('musicbrainz_release_artist_id', 'MUSICBRAINZ_ALBUMARTISTID', True)
264
265 musicbrainz_release_id = Def.default_property('musicbrainz_release_id', 'MUSICBRAINZ_ALBUMID', True)
266
267 musicbrainz_release_status = Def.default_property('musicbrainz_release_status', 'MUSICBRAINZ_ALBUMSTATUS', True)
268
269 musicbrainz_release_country = Def.default_property('musicbrainz_release_country', 'RELEASECOUNTRY', True)
270
271 musicbrainz_release_type = Def.default_property('musicbrainz_release_type', 'MUSICBRAINZ_ALBUMTYPE', True)
272
273 musicbrainz_track_id = Def.default_property('musicbrainz_track_id', 'MUSICBRAINZ_TRACKID', True)
274
275 musicip_id = Def.default_property('musicip_id', 'MUSICIP_PUID', True)
276
277 performers = Def.default_property('performers', 'ARTIST')
278
279 performers_sort = Def.default_property('performers_sort', 'ARTISTSORT')
280
281 title_sort = Def.default_property('title_sort', 'TITLESORT', True)
282
283 def track_count_get(self):
284 value = self.try_int(self.get_first_field('TRACKTOTAL'))
285 if value:
286 return value
287 value = self.get_first_field('TRACKNUMBER')
288 if not isinstance(value, unicode):
289 return 0
290 return self.try_int(value.partition('/')[2])
291 def track_count_set(self, value):
292 self.set_field_int('TRACKTOTAL', value)
293 track_count = cached_property(track_count_get, track_count_set)
294
295
296 class TagExtID3v2(TagExtBase):
297 class Def:
298 # default property handler for TextIdentification frames
299 @staticmethod
300 def default_property(prop, frame_type, as_string=False):
301 def default_get(self):
302 if as_string:
303 return self.get_text_as_string(frame_type)
304 return self.get_text_as_array(frame_type)
305 def default_set(self, value):
306 self.set_text(frame_type, value)
307
308 default_get.__name__ = prop+'_get'
309 default_set.__name__ = prop+'_set'
310 return cached_property(default_get, default_set)
311
312 # default property handler for UserTextIdentification frames
313 @staticmethod
314 def default_user_property(prop, description):
315 def default_get(self):
316 return self.get_text_user(description)
317 def default_set(self, value):
318 self.set_text_user(description, value)
319
320 default_get.__name__ = prop+'_get'
321 default_set.__name__ = prop+'_set'
322 return cached_property(default_get, default_set)
323
324
325 def __init__(self, tag):
326 super(TagExtID3v2, self).__init__(tag, tag.frameListMap())
327
328 # returns array of values from TextIdentification frame
329 # works with frame types which have string data
330 def get_text_as_array(self, frame_type):
331 frame_list = self.map_get(frame_type)
332 res = list()
333 if frame_list and len(frame_list):
334 frame = frame_list[0]
335 # we always use the first frame in the list
336 for field in frame.fieldList():
337 res.append(field)
338 return res
339
340 # returns value of TextIdentification frame as string
341 # works with frame types which have string data
342 def get_text_as_string(self, frame_type):
343 frame_list = self.map_get(frame_type)
344 if frame_list and len(frame_list):
345 return frame_list[0].toString()
346 return None
347
348 # returns integer at specified index from '/' separated list of numbers in TextIdentification frame
349 # works with frame types which have string data
350 def get_text_as_int(self, frame_type, index):
351 str = self.get_text_as_string(frame_type)
352 if not str:
353 return 0
354 values = str.split('/', index + 2)
355 if len(values) < index + 1:
356 return 0
357 try:
358 return int(values[index])
359 except:
360 return 0
361
362 # returns value of UserTextIdentification frame with specified description
363 def get_text_user(self, description):
364 if not description:
365 raise TypeError('description not given')
366 frame_list = self.map_get('TXXX')
367 if not frame_list:
368 return None
369 for frame in frame_list:
370 if frame.description() == description:
371 return frame.fieldList()[1]
372 return None
373
374 # return identifier from UniqueFileIdentifierFrame with specified owner
375 def get_text_ufid(self, owner):
376 if not owner:
377 raise TypeError('owner not given')
378 frame_list = self.map_get('UFID')
379 if not frame_list:
380 return None
381 for frame in frame_list:
382 if frame.owner() == owner:
383 return frame.identifier()
384
385 def _set_text(self, frame_type, value_list, frame_class='TextIdentificationFrame'):
386 from tagpy.id3v2 import TextIdentificationFrame, UserTextIdentificationFrame, UniqueFileIdentifierFrame
387 from _tagpy import StringList
388
389 if not frame_type:
390 raise TypeError('frame type not given')
391 if isinstance(value_list, str) or isinstance(value_list, unicode):
392 value_list = [value_list]
393 if not getattr(value_list, '__iter__', False):
394 raise TypeError('value_list must be iterable')
395 to_add = list()
396 for value in value_list:
397 if isinstance(value, str) or isinstance(value, unicode) and len(value.strip()):
398 to_add.append(value)
399 if not len(to_add):
400 return
401
402 if frame_class == 'TextIdentificationFrame':
403 self.tag.removeFrames(frame_type) # trash old values
404 frame = TextIdentificationFrame(frame_type)
405 str_list = StringList()
406 for value in to_add:
407 str_list.append(value)
408 frame.setTextEncoding(tagpy.StringType.UTF8)
409 frame.setText(str_list)
410 elif frame_class == 'UserTextIdentificationFrame':
411 frame = UserTextIdentificationFrame()
412 frame_type = frame_type.encode('ascii', 'ignore')
413 frame.setDescription(frame_type)
414 frame.setTextEncoding(tagpy.StringType.UTF8)
415 frame.setText(to_add[0])
416 elif frame_class == 'UniqueFileIdentifierFrame':
417 # UFID frame doesn't like UTF, so make sure we give only ascii as it's value
418 ufid_value = to_add[0].encode('ascii', 'ignore')
419 frame = UniqueFileIdentifierFrame(frame_type, ufid_value)
420 else:
421 return
422
423 self.tag.addFrame(frame)
424
425 # set TextIdentification frame (value can be either string or list of strings)
426 def set_text(self, frame_type, value_list):
427 return self._set_text(frame_type, value_list, 'TextIdentificationFrame')
428
429 # set UserTextIdentification frame
430 def set_text_user(self, description, value):
431 if not description:
432 raise TypeError('description not given')
433 return self._set_text(description, value, 'UserTextIdentificationFrame')
434
435 # set UniqueFileIdentifierFrame
436 def set_text_ufid(self, owner, identifier):
437 if not owner:
438 raise TypeError('owner not given')
439 if not identifier:
440 raise TypeError('identifier not given')
441 return self._set_text(owner, identifier, 'UniqueFileIdentifierFrame')
442
443 # set TextIdentification frame as string of format %d/%d (number of count) or %d (number) when count=0
444 def set_text_as_ints(self, frame_type, number, count=0):
445 number = int(number)
446 count = int(count)
447 if not number and not count:
448 self.tag.removeFrames(frame_type)
449 elif count != 0:
450 self.set_text(frame_type, '%d/%d' % (number, count))
451 else:
452 self.set_text(frame_type, str(number))
453
454 album_artists = Def.default_property('album_artists', 'TPE2')
455
456 album_artists_sort = Def.default_property('album_artists_sort', 'TSO2')
457
458 album_sort = Def.default_property('album_sort', 'TSOA', True)
459
460 amazon_id = Def.default_user_property('amazon_id', 'ASIN')
461
462 def beats_per_minute_get(self):
463 value = self.get_text_as_string('TBPM')
464 value = self.try_float(value)
465 if value:
466 return int(round(value))
467 return 0
468 def beats_per_minute_set(self, value):
469 self.set_text_as_ints('TBPM', value)
470 beats_per_minute = cached_property(beats_per_minute_get, beats_per_minute_set)
471
472 composers = Def.default_property('composers', 'TCOM')
473
474 composers_sort = Def.default_property('composers_sort', 'TSOC')
475
476 conductor = Def.default_property('conductor', 'TPE3', True)
477
478 copyright = Def.default_property('copyright', 'TCOP', True)
479
480 def disc_get(self):
481 return self.get_text_as_int('TPOS', 0)
482 def disc_set(self, value):
483 self.set_text_as_ints('TPOS', value, self.disc_count)
484 disc = cached_property(disc_get, disc_set)
485
486 def disc_count_get(self):
487 return self.get_text_as_int('TPOS', 1)
488 def disc_count_set(self, value):
489 self.set_text_as_ints('TPOS', self.disc, value)
490 disc_count = cached_property(disc_count_get, disc_count_set)
491
492 def genres_get(self):
493 from tagpy import id3v1
494
495 genre_list = self.get_text_as_array('TCON')
496 if not len(genre_list):
497 return genre_list
498
499 res = list()
500 for genre in genre_list:
501 if not genre:
502 continue
503 # check if we have id3v1 genre index instead of genre as string
504 try:
505 genre_index = int(genre)
506 genre = id3v1.genreList()[genre_index]
507 except:
508 pass
509 res.append(genre)
510 return res
511 def genres_set(self, value):
512 self.set_text('TCON', value)
513 genres = cached_property(genres_get, genres_set)
514
515 grouping = Def.default_property('grouping', 'TIT1', True)
516
517 # not implemented yet :)
518 #lyrics = cached_property(lyrics_get, lyrics_set)
519
520 musicbrainz_artist_id = Def.default_user_property('musicbrainz_artist_id', 'MusicBrainz Artist Id')
521
522 musicbrainz_disc_id = Def.default_user_property('musicbrainz_disc_id', 'MusicBrainz Disc Id')
523
524 musicbrainz_release_artist_id = Def.default_user_property('musicbrainz_release_artist_id', 'MusicBrainz Album Artist IdD')
525
526 musicbrainz_release_id = Def.default_user_property('musicbrainz_release_id', '"MusicBrainz Album Id')
527
528 musicbrainz_release_status = Def.default_user_property('musicbrainz_release_status', 'MusicBrainz Album Status')
529
530 musicbrainz_release_country = Def.default_user_property('musicbrainz_release_country', 'MusicBrainz Album Release Country')
531
532 musicbrainz_release_type = Def.default_user_property('musicbrainz_release_type', 'MusicBrainz Album Type')
533
534 def musicbrainz_track_id_get(self):
535 return self.get_text_ufid('http://musicbrainz.org')
536 def musicbrainz_track_id_set(self, value):
537 self.set_text_ufid('http://musicbrainz.org', value)
538 musicbrainz_track_id = cached_property(musicbrainz_track_id_get, musicbrainz_track_id_set)
539
540 musicip_id = Def.default_user_property('musicip_id', 'MusicIP PUID')
541
542 performers = Def.default_property('performers', 'TPE1')
543
544 performers_sort = Def.default_property('performers_sort', 'TSOP')
545
546 title_sort = Def.default_property('title_sort', 'TSOT', True)
547
548 def track_count_get(self):
549 return self.get_text_as_int('TRCK', 1)
550 def track_count_set(self, value):
551 self.set_text_as_ints('TRCK', self.track, value)
552 track_count = cached_property(track_count_get, track_count_set)
553
554
555 class TagExtAPE(TagExtBase):
556 class Def:
557 @staticmethod
558 def default_property(prop, key, first=False):
559 def default_get(self):
560 if first:
561 return self.get_first_item(key)
562 return self.get_item(key)
563 def default_set(self, value):
564 self.set_item(key, value)
565
566 default_get.__name__ = prop+'_get'
567 default_set.__name__ = prop+'_set'
568 return cached_property(default_get, default_set)
569
570 def __init__(self, tag):
571 super(TagExtAPE, self).__init__(tag, tag.itemListMap())
572
573 def get_item(self, key):
574 item_list = self.map_get(key)
575 res = list()
576 if item_list:
577 value_list = item_list.toStringList()
578 for value in value_list:
579 res.append(value)
580 return res
581
582 def get_first_item(self, key):
583 values = self.get_item(key)
584 if len(values):
585 return values[0]
586 return None
587
588 # returns integer at specified index from '/' separated list of numbers
589 def get_item_as_int(self, key, index):
590 str = self.get_first_item(key)
591 if not str:
592 return 0
593 values = str.split('/', index + 2)
594 if len(values) < index + 1:
595 return 0
596 try:
597 return int(values[index])
598 except:
599 return 0
600
601 def set_item(self, key, value_list):
602 from _tagpy import ape_Item, StringList # very hacky stuff, but no other way to do it :D
603
604 if not key:
605 raise TypeError('key not given')
606 if isinstance(value_list, str) or isinstance(value_list, unicode):
607 value_list = [value_list]
608 if not getattr(value_list, '__iter__', False):
609 raise TypeError('value_list must be iterable')
610 if not len(value_list):
611 self.tag.removeItem(key)
612 return
613 to_add = list()
614 for value in value_list:
615 if isinstance(value, str) or isinstance(value, unicode) and len(value.strip()):
616 to_add.append(value)
617 self.tag.removeItem(key) # trash old values
618 if len(to_add):
619 str_list = StringList()
620 for value in to_add:
621 str_list.append(value)
622 item = ape_Item(key, str_list)
623 self.tag.setItem(key, item)
624
625 # set item as string of format %d/%d (number of count) or %d (number) when count=0
626 def set_item_as_ints(self, key, number, count=0):
627 number = int(number)
628 count = int(count)
629 if not number and not count:
630 self.tag.removeItem(key)
631 elif count != 0:
632 self.set_item(key, '%d/%d' % (number, count))
633 else:
634 self.set_item(key, str(number))
635
636 def album_artists_get(self):
637 list = self.get_item('Album Artist')
638 if not len(list):
639 list = self.get_item('AlbumArtist')
640 return list
641 def album_artists_set(self, value):
642 self.set_item('Album Artist', value)
643 # compatibility
644 # has to be in upper case, cause all APE item keys are uppercased in TagLib!!
645 if 'ALBUMARTIST' in self.map:
646 self.set_item('AlbumArtist', value)
647 album_artists = cached_property(album_artists_get, album_artists_set)
648
649 album_artists_sort = Def.default_property('album_artists_sort', 'AlbumArtistSort')
650
651 album_sort = Def.default_property('album_sort', 'AlbumSort', True)
652
653 amazon_id = Def.default_property('amazon_id', 'ASIN', True)
654
655 def beats_per_minute_get(self):
656 value = self.get_item('BPM')
657 value = self.try_float(value)
658 if value:
659 return int(round(value))
660 return 0
661 def beats_per_minute_set(self, value):
662 self.set_item_as_ints('BPM', value)
663 beats_per_minute = cached_property(beats_per_minute_get, beats_per_minute_set)
664
665 composers = Def.default_property('composers', 'Composer')
666
667 composers_sort = Def.default_property('composers_sort', 'ComposerSort')
668
669 conductor = Def.default_property('conductor', 'Conductor', True)
670
671 copyright = Def.default_property('copyright', 'Copyright', True)
672
673 def disc_get(self):
674 return self.get_item_as_int('Disc', 0)
675 def disc_set(self, value):
676 self.set_item_as_ints('Disc', value, self.disc_count)
677 disc = cached_property(disc_get, disc_set)
678
679 def disc_count_get(self):
680 return self.get_item_as_int('Disc', 1)
681 def disc_count_set(self, value):
682 self.set_item_as_ints('Disc', self.disc, value)
683 disc_count = cached_property(disc_count_get, disc_count_set)
684
685 genres = Def.default_property('genres', 'Genre')
686
687 grouping = Def.default_property('grouping', 'Grouping', True)
688
689 lyrics = Def.default_property('lyrics', 'Lyrics', True)
690
691 musicbrainz_artist_id = Def.default_property('musicbrainz_artist_id', 'MUSICBRAINZ_ARTISTID', True)
692
693 musicbrainz_disc_id = Def.default_property('musicbrainz_disc_id', 'MUSICBRAINZ_DISCID', True)
694
695 musicbrainz_release_artist_id = Def.default_property('musicbrainz_release_artist_id', 'MUSICBRAINZ_ALBUMARTISTID', True)
696
697 musicbrainz_release_id = Def.default_property('musicbrainz_release_id', 'MUSICBRAINZ_ALBUMID', True)
698
699 musicbrainz_release_status = Def.default_property('musicbrainz_release_status', 'MUSICBRAINZ_ALBUMSTATUS', True)
700
701 musicbrainz_release_country = Def.default_property('musicbrainz_release_country', 'RELEASECOUNTRY', True)
702
703 musicbrainz_release_type = Def.default_property('musicbrainz_release_type', 'MUSICBRAINZ_ALBUMTYPE', True)
704
705 musicbrainz_track_id = Def.default_property('musicbrainz_track_id', 'MUSICBRAINZ_TRACKID', True)
706
707 musicip_id = Def.default_property('musicip_id', 'MUSICIP_PUID', True)
708
709 performers = Def.default_property('performers', 'Artist')
710
711 performers_sort = Def.default_property('performers_sort', 'ArtistSort')
712
713 title_sort = Def.default_property('title_sort', 'TitleSort', True)
714
715 def track_count_get(self):
716 return self.get_item_as_int('Track', 1)
717 def track_count_set(self, value):
718 self.set_item_as_ints('Track', self.track, value)
719 track_count = cached_property(track_count_get, track_count_set)
720
721
722 # a wrapper around TagPy to seamlessly handle non-standard tags
723 # specific tag handling implementation is based on the excellent taglib-sharp
724 class MetaFile(object):
725 def __init__(self, file, readAudioProperties=True, audioPropertiesStyle=tagpy.ReadStyle.Average):
726 fileref = tagpy.FileRef(file, readAudioProperties, audioPropertiesStyle)
727 self.f = fileref.file()
728 self.audioprops = self.f.audioProperties()
729 self._tag = self.f.tag()
730 self.tag = self.get_ext_tag()
731
732 def get_ext_tag(self):
733 def by_tag_class(tag):
734 tag_type = tag.__class__.__name__
735 if tag_type == 'ogg_XiphComment':
736 return TagExtXiph(tag)
737 if tag_type == 'id3v2_Tag':
738 return TagExtID3v2(tag)
739 if tag_type == 'ape_Tag':
740 return TagExtAPE(tag)
741 return None
742
743 file = self.f
744 # choose most preferrable tag type where we have choice
745 # don't consider ID3v1 since it's not possible to add any non-standard tags to it
746 file_type = file.__class__.__name__
747 if file_type == 'flac_File':
748 tag_xiph = file.xiphComment()
749 tag_id3v2 = file.ID3v2Tag()
750 return by_tag_class(tag_xiph or tag_id3v2 or file.xiphComment(True))
751 if file_type == 'mpc_File':
752 return by_tag_class(file.APETag(True))
753 if file_type == 'mpeg_File':
754 tag_ape = file.APETag()
755 tag_id3v2 = file.ID3v2Tag()
756 return by_tag_class(tag_ape or tag_id3v2 or file.APETag(True))
757 # TrueAudio and Wavpack don't seem to be available in tagpy
758 # if file_type == 'trueaudio_File':
759 # tag_id3v2 = file.ID3v2Tag(create=True)
760 # return by_tag_class(tag_id3v2)
761 # if file_type == 'wavpack_File':
762 # tag_ape = file.APETag(create=True)
763 # return by_tag_class(tag_ape)
764 # get default tag type
765 return by_tag_class(file.tag()) or file.tag()
766
767 def save(self):
768 self.f.save()

tadas_AT_dailyda_DOT_com
ViewVC Help
Powered by ViewVC 1.1.8