def __init__(self, comment_strings, vendor_string): """comment_strings is a list of unicode strings vendor_string is a unicode string""" MetaData.__setattr__(self, "comment_strings", comment_strings) MetaData.__setattr__(self, "vendor_string", vendor_string)
def __setitem__(self, key, values): new_values = values[:] new_comment_strings = [] matching_keys = self.ALIASES.get(key.upper(), frozenset([key.upper()])) for comment in self.comment_strings: if (u"=" in comment): (c_key, c_value) = comment.split(u"=", 1) if (c_key.upper() in matching_keys): try: # replace current value with newly set value new_comment_strings.append( u"%s=%s" % (c_key, new_values.pop(0))) except IndexError: # no more newly set values, so remove current value continue else: # passthrough unmatching values new_comment_strings.append(comment) else: # passthrough values with no "=" sign new_comment_strings.append(comment) # append any leftover values for new_value in new_values: new_comment_strings.append(u"%s=%s" % (key.upper(), new_value)) MetaData.__setattr__(self, "comment_strings", new_comment_strings)
def __setitem__(self, key, values): from audiotools import PY3 assert(isinstance(key, str if PY3 else unicode)) for v in values: assert(isinstance(v, str if PY3 else unicode)) new_values = values[:] new_comment_strings = [] matching_keys = self.ALIASES.get(key.upper(), frozenset([key.upper()])) for comment in self.comment_strings: if u"=" in comment: (c_key, c_value) = comment.split(u"=", 1) if c_key.upper() in matching_keys: try: # replace current value with newly set value new_comment_strings.append( u"{}={}".format(c_key, new_values.pop(0))) except IndexError: # no more newly set values, so remove current value continue else: # passthrough unmatching values new_comment_strings.append(comment) else: # passthrough values with no "=" sign new_comment_strings.append(comment) # append any leftover values for new_value in new_values: new_comment_strings.append(u"{}={}".format(key.upper(), new_value)) MetaData.__setattr__(self, "comment_strings", new_comment_strings)
def __setitem__(self, key, values): from audiotools import PY3 assert (isinstance(key, str if PY3 else unicode)) for v in values: assert (isinstance(v, str if PY3 else unicode)) new_values = values[:] new_comment_strings = [] matching_keys = self.ALIASES.get(key.upper(), frozenset([key.upper()])) for comment in self.comment_strings: if u"=" in comment: (c_key, c_value) = comment.split(u"=", 1) if c_key.upper() in matching_keys: try: # replace current value with newly set value new_comment_strings.append(u"{}={}".format( c_key, new_values.pop(0))) except IndexError: # no more newly set values, so remove current value continue else: # passthrough unmatching values new_comment_strings.append(comment) else: # passthrough values with no "=" sign new_comment_strings.append(comment) # append any leftover values for new_value in new_values: new_comment_strings.append(u"{}={}".format(key.upper(), new_value)) MetaData.__setattr__(self, "comment_strings", new_comment_strings)
def __setattr__(self, attr, value): if (attr in self.ATTRIBUTE_MAP): if (value is not None): import re if (attr == 'track_number'): try: self[b'Track'].data = (re.sub( r'\d+', u"%d" % (value), self[b'Track'].__unicode__(), 1).encode("utf-8")) except KeyError: self[b'Track'] = self.ITEM.string( b'Track', __number_pair__(value, self.track_total)) elif (attr == 'track_total'): try: if (re.search(r'/\D*\d+', self[b'Track'].__unicode__()) is not None): self[b'Track'].data = (re.sub( r'(/\D*)(\d+)', u"\\g<1>" + u"%d" % (value), self[b'Track'].__unicode__(), 1).encode("utf-8")) else: self[b'Track'].data = ( u"%s/%d" % (self[b'Track'].__unicode__(), value)).encode("utf-8") except KeyError: self[b'Track'] = self.ITEM.string( b'Track', __number_pair__(self.track_number, value)) elif (attr == 'album_number'): try: self[b'Media'].data = (re.sub( r'\d+', u"%d" % (value), self[b'Media'].__unicode__(), 1).encode("utf-8")) except KeyError: self[b'Media'] = self.ITEM.string( b'Media', __number_pair__(value, self.album_total)) elif (attr == 'album_total'): try: if (re.search(r'/\D*\d+', self[b'Media'].__unicode__()) is not None): self[b'Media'].data = (re.sub( r'(/\D*)(\d+)', u"\\g<1>" + u"%d" % (value), self[b'Media'].__unicode__(), 1).encode("utf-8")) else: self[b'Media'].data = ( u"%s/%d" % (self[b'Media'].__unicode__(), value)).encode("utf-8") except KeyError: self[b'Media'] = self.ITEM.string( b'Media', __number_pair__(self.album_number, value)) else: self[self.ATTRIBUTE_MAP[attr]] = self.ITEM.string( self.ATTRIBUTE_MAP[attr], value) else: delattr(self, attr) else: MetaData.__setattr__(self, attr, value)
def metadata(self): def get_track_metadata(track_node, album_metadata, track_number): try: #FIXME - not sure if name or sort-name should take precendence artist_name = get_xml_text_node( get_xml_nodes(track_node, u'artist')[0], u'name') except IndexError: artist_name = album_metadata.artist_name track_metadata = MetaData(track_name=get_xml_text_node( track_node, u'title'), artist_name=artist_name, track_number=track_number) track_metadata.merge(album_metadata) return track_metadata try: release = self.dom.getElementsByTagName(u'release')[0] except IndexError: return AlbumMetaData([]) album_name = get_xml_text_node(release, u'title') try: #FIXME - not sure if name or sort-name should take precendence artist_name = get_xml_text_node( get_xml_nodes(release, u'artist')[0], u'name') except IndexError: artist_name = u'' try: tracks = get_xml_nodes( get_xml_nodes(release, u'track-list')[0], u'track') except IndexError: tracks = [] album_metadata = MetaData(album_name=album_name, artist_name=artist_name, track_total=len(tracks)) try: release_events = get_xml_nodes(release, u'release-event-list')[0] event = get_xml_nodes(release_events, u'event')[-1] album_metadata.year = event.getAttribute('date')[0:4] album_metadata.catalog = event.getAttribute('catalog-number') except IndexError: pass return AlbumMetaData([ get_track_metadata(track_node=node, album_metadata=album_metadata, track_number=i + 1) for (i, node) in enumerate(tracks) ])
def __init__(self, track_name, artist_name, album_name, year, comment, track_number, genre): #pre-emptively cut down overlong fields MetaData.__init__(self, track_name=track_name[0:30], artist_name=artist_name[0:30], album_name=album_name[0:30], year=year[0:4], comment=comment[0:28], track_number=track_number) self.__dict__['genre'] = genre
def __setattr__(self, attr, value): if (attr in self.ATTRIBUTE_MAP): if (value is not None): import re if (attr == 'track_number'): try: self['Track'].data = re.sub(r'\d+', str(int(value)), self['Track'].data, 1) except KeyError: self['Track'] = self.ITEM.string( 'Track', __number_pair__(value, self.track_total)) elif (attr == 'track_total'): try: if (re.search(r'/\D*\d+', self['Track'].data) is not None): self['Track'].data = re.sub( r'(/\D*)(\d+)', "\\g<1>" + str(int(value)), self['Track'].data, 1) else: self['Track'].data = "%s/%d" % (self['Track'].data, value) except KeyError: self['Track'] = self.ITEM.string( 'Track', __number_pair__(self.track_number, value)) elif (attr == 'album_number'): try: self['Media'].data = re.sub(r'\d+', str(int(value)), self['Media'].data, 1) except KeyError: self['Media'] = self.ITEM.string( 'Media', __number_pair__(value, self.album_total)) elif (attr == 'album_total'): try: if (re.search(r'/\D*\d+', self['Media'].data) is not None): self['Media'].data = re.sub( r'(/\D*)(\d+)', "\\g<1>" + str(int(value)), self['Media'].data, 1) else: self['Media'].data = "%s/%d" % (self['Media'].data, value) except KeyError: self['Media'] = self.ITEM.string( 'Media', __number_pair__(self.album_number, value)) else: self[self.ATTRIBUTE_MAP[attr]] = self.ITEM.string( self.ATTRIBUTE_MAP[attr], value) else: delattr(self, attr) else: MetaData.__setattr__(self, attr, value)
def __init__(self, comment_strings, vendor_string): """comment_strings is a list of unicode strings vendor_string is a unicode string""" from audiotools import PY3 # some debug type checking for s in comment_strings: assert(isinstance(s, str if PY3 else unicode)) assert(isinstance(vendor_string, str if PY3 else unicode)) MetaData.__setattr__(self, "comment_strings", comment_strings) MetaData.__setattr__(self, "vendor_string", vendor_string)
def __init__(self, track_name, artist_name, album_name, year, comment, track_number, genre): """all fields except track_number are binary strings""" # pre-emptively cut down overlong fields MetaData.__init__( self, track_name=track_name[0:30], artist_name=artist_name[0:30], album_name=album_name[0:30], year=year[0:4], comment=comment[0:28], track_number=track_number, ) self.__dict__["genre"] = genre
def __init__(self, comment_strings, vendor_string): """comment_strings is a list of unicode strings vendor_string is a unicode string""" from audiotools import PY3 # some debug type checking for s in comment_strings: assert (isinstance(s, str if PY3 else unicode)) assert (isinstance(vendor_string, str if PY3 else unicode)) MetaData.__setattr__(self, "comment_strings", comment_strings) MetaData.__setattr__(self, "vendor_string", vendor_string)
def get_track_metadata(track_node, album_metadata, track_number): try: #FIXME - not sure if name or sort-name should take precendence artist_name = get_xml_text_node( get_xml_nodes(track_node, u'artist')[0], u'name') except IndexError: artist_name = album_metadata.artist_name track_metadata = MetaData(track_name=get_xml_text_node( track_node, u'title'), artist_name=artist_name, track_number=track_number) track_metadata.merge(album_metadata) return track_metadata
def __delitem__(self, key): new_comment_strings = [] matching_keys = self.ALIASES.get(key.upper(), frozenset([key.upper()])) for comment in self.comment_strings: if (u"=" in comment): (c_key, c_value) = comment.split(u"=", 1) if (c_key.upper() not in matching_keys): # passthrough unmatching values new_comment_strings.append(comment) else: # passthrough values with no "=" sign new_comment_strings.append(comment) MetaData.__setattr__(self, "comment_strings", new_comment_strings)
def intersection(self, metadata): """given a MetaData-compatible object, returns a new MetaData object which contains all the matching fields and images of this object and 'metadata' """ def comment_present(comment): if u"=" in comment: key, value = comment.split(u"=", 1) try: for other_value in metadata[key]: if value == other_value: return True else: return False except KeyError: return False else: for other_comment in metadata.comment_strings: if comment == other_comment: return True else: return False if isinstance(metadata, VorbisComment): return self.__class__([ comment for comment in self.comment_strings if comment_present(comment) ], self.vendor_string) else: return MetaData.intersection(self, metadata)
def intersection(self, metadata): """given a MetaData-compatible object, returns a new MetaData object which contains all the matching fields and images of this object and 'metadata' """ def comment_present(comment): if u"=" in comment: key, value = comment.split(u"=", 1) try: for other_value in metadata[key]: if value == other_value: return True else: return False except KeyError: return False else: for other_comment in metadata.comment_strings: if comment == other_comment: return True else: return False if isinstance(metadata, VorbisComment): return self.__class__([comment for comment in self.comment_strings if comment_present(comment)], self.vendor_string) else: return MetaData.intersection(self, metadata)
def __delattr__(self, attr): import re def zero_number(unicode_value): return re.sub(r'\d+', u"0", unicode_value, 1) if attr in self.ATTRIBUTE_MAP: key = self.ATTRIBUTE_MAP[attr] if attr in {'track_number', 'album_number'}: try: tag = self[key] if tag.total() is None: # if no slashed _total field, delete entire tag del(self[key]) else: # otherwise replace initial portion with 0 self[key] = self.ITEM.string( key, zero_number(tag.__unicode__())) except KeyError: # no tag to delete pass elif attr in {'track_total', 'album_total'}: try: tag = self[key] if tag.total() is not None: if tag.number() is not None: self[key] = self.ITEM.string( key, tag.__unicode__().split(u"/", 1)[0].rstrip()) else: del(self[key]) else: # no total portion, so nothing to do pass except KeyError: # no tag to delete portion of pass else: try: del(self[key]) except KeyError: pass elif attr in MetaData.FIELDS: pass else: MetaData.__delattr__(self, attr)
def __getattr__(self, attr): # returns the first matching key for the given attribute # in our list of comment strings if ((attr == "track_number") or (attr == "album_number")): try: # get the TRACKNUMBER/DISCNUMBER values # return the first value that contains an integer for value in self[self.ATTRIBUTE_MAP[attr]]: integer = re.search(r'\d+', value) if (integer is not None): return int(integer.group(0)) else: # otherwise, return None return None except KeyError: # if no TRACKNUMBER/DISCNUMBER, return None return None elif ((attr == "track_total") or (attr == "album_total")): try: # get the TRACKTOTAL/DISCTOTAL values # return the first value that contains an integer for value in self[self.ATTRIBUTE_MAP[attr]]: integer = re.search(r'\d+', value) if (integer is not None): return int(integer.group(0)) except KeyError: pass # if no TRACKTOTAL/DISCTOTAL, # or none of them contain an integer, # look for slashed TRACKNUMBER/DISCNUMBER values try: for value in self[{ "track_total": u"TRACKNUMBER", "album_total": u"DISCNUMBER" }[attr]]: integer = re.search(r'/\D*(\d+)', value) if (integer is not None): return int(integer.group(1)) except KeyError: # no slashed TRACKNUMBER/DISCNUMBER values either # so return None return None elif (attr in self.ATTRIBUTE_MAP): # attribute is supported by VorbisComment try: # if present, return the first value return self[self.ATTRIBUTE_MAP[attr]][0] except KeyError: # if not present, return None return None elif (attr in self.FIELDS): # attribute is supported by MetaData # but not supported by VorbisComment return None else: # attribute is not MetaData-specific return MetaData.__getattribute__(self, attr)
def to_disc_metadata(self): from audiotools import MetaData return MetaData(album_name=self.get(u"TITLE", None), performer_name=self.get(u"PERFORMER", None), artist_name=self.get(u"SONGWRITER", None), composer_name=self.get(u"COMPOSER", None), comment=self.get(u"MESSAGE", None))
def __delitem__(self, key): from audiotools import PY3 assert (isinstance(key, str if PY3 else unicode)) new_comment_strings = [] matching_keys = self.ALIASES.get(key.upper(), frozenset([key.upper()])) for comment in self.comment_strings: if u"=" in comment: (c_key, c_value) = comment.split(u"=", 1) if c_key.upper() not in matching_keys: # passthrough unmatching values new_comment_strings.append(comment) else: # passthrough values with no "=" sign new_comment_strings.append(comment) MetaData.__setattr__(self, "comment_strings", new_comment_strings)
def metadata_audiotools(self, path, media): from audiotools import MetaData import audiotools meta = MetaData() # release-level metadata if media.release and media.release.main_image: if meta.supports_images() and os.path.exists(media.release.main_image.path): opt = dict(size=(200, 200), crop=True, bw=False, quality=80) image = get_thumbnailer(media.release.main_image).get_thumbnail(opt) meta.add_image(get_raw_image(image.path, 0)) audiotools.open(path).update_metadata(meta) return
def delete_metadata(self): """deletes the track's MetaData this removes or unsets tags as necessary in order to remove all data raises IOError if unable to write the file""" from audiotools import MetaData self.set_metadata(MetaData())
def __delitem__(self, key): from audiotools import PY3 assert(isinstance(key, str if PY3 else unicode)) new_comment_strings = [] matching_keys = self.ALIASES.get(key.upper(), frozenset([key.upper()])) for comment in self.comment_strings: if u"=" in comment: (c_key, c_value) = comment.split(u"=", 1) if c_key.upper() not in matching_keys: # passthrough unmatching values new_comment_strings.append(comment) else: # passthrough values with no "=" sign new_comment_strings.append(comment) MetaData.__setattr__(self, "comment_strings", new_comment_strings)
def to_track_metadata(self): from audiotools import MetaData return MetaData( track_name=decode_string(self.get("TITLE", None)), performer_name=decode_string(self.get("PERFORMER", None)), artist_name=decode_string(self.get("SONGWRITER", None)), composer_name=decode_string(self.get("COMPOSER", None)), comment=decode_string(self.get("MESSAGE", None)), ISRC=decode_string(self.get("ISRC", None)))
def delete_metadata(self): """deletes the track's MetaData raises IOError if unable to write the file""" if ((self.get_replay_gain() is not None) or (self.get_cuesheet() is not None)): # non-textual metadata is present and needs preserving self.set_metadata(MetaData()) else: # no non-textual metadata, so wipe out the entire block from os import access, R_OK, W_OK from audiotools.bitstream import BitstreamReader from audiotools import transfer_data if not access(self.filename, R_OK | W_OK): raise IOError(self.filename) with open(self.filename, "rb") as f: f.seek(-32, 2) (preamble, version, tag_size, item_count, read_only, item_encoding, is_header, no_footer, has_header) = BitstreamReader(f, True).parse( ApeTag.HEADER_FORMAT) if (preamble == b'APETAGEX') and (version == 2000): from audiotools import TemporaryFile from os.path import getsize # there's existing metadata to delete # so rewrite file without trailing metadata tag if has_header: old_tag_size = 32 + tag_size else: old_tag_size = tag_size # copy everything but the last "old_tag_size" bytes # from existing file to rewritten file new_apev2 = TemporaryFile(self.filename) old_apev2 = open(self.filename, "rb") limited_transfer_data( old_apev2.read, new_apev2.write, getsize(self.filename) - old_tag_size) old_apev2.close() new_apev2.close()
def delete_metadata(self): """deletes the track's MetaData this removes or unsets tags as necessary in order to remove all data raises IOError if unable to write the file""" from audiotools import MetaData # the vorbis comment packet is required, # so simply zero out its contents self.set_metadata(MetaData())
def from_cuesheet(cls, cuesheet, total_frames, sample_rate, metadata=None): if (metadata is None): metadata = MetaData() return cls.from_files([ DummyAudioFile(length=(pcm_frames * 75) / sample_rate, metadata=metadata, track_number=i + 1) for (i, pcm_frames) in enumerate(cuesheet.pcm_lengths(total_frames)) ])
def __setattr__(self, attr, value): def swap_number(unicode_value, new_number): import re return re.sub(r'\d+', u"%d" % (new_number), unicode_value, 1) def swap_slashed_number(unicode_value, new_number): if u"/" in unicode_value: (first, second) = unicode_value.split(u"/", 1) return u"/".join([first, swap_number(second, new_number)]) else: return u"/".join([unicode_value, u"%d" % (new_number)]) if attr in self.ATTRIBUTE_MAP: key = self.ATTRIBUTE_MAP[attr] if value is not None: if attr in {'track_number', 'album_number'}: try: current_value = self[key].__unicode__() self[key] = self.ITEM.string( key, swap_number(current_value, value)) except KeyError: self[key] = self.ITEM.string( key, __number_pair__(value, None)) elif attr in {'track_total', 'album_total'}: try: current_value = self[key].__unicode__() self[key] = self.ITEM.string( key, swap_slashed_number(current_value, value)) except KeyError: self[key] = self.ITEM.string( key, __number_pair__(None, value)) elif attr == 'compilation': self[key] = self.ITEM.string(key, u"%d" % (1 if value else 0)) else: self[key] = self.ITEM.string(key, value) else: delattr(self, attr) else: MetaData.__setattr__(self, attr, value)
def __setattr__(self, attr, value): def swap_number(unicode_value, new_number): import re return re.sub(r'\d+', u"{:d}".format(new_number), unicode_value, 1) def swap_slashed_number(unicode_value, new_number): if u"/" in unicode_value: (first, second) = unicode_value.split(u"/", 1) return u"/".join([first, swap_number(second, new_number)]) else: return u"/".join([unicode_value, u"{:d}".format(new_number)]) if attr in self.ATTRIBUTE_MAP: key = self.ATTRIBUTE_MAP[attr] if value is not None: if attr in {'track_number', 'album_number'}: try: current_value = self[key].__unicode__() self[key] = self.ITEM.string( key, swap_number(current_value, value)) except KeyError: self[key] = self.ITEM.string( key, __number_pair__(value, None)) elif attr in {'track_total', 'album_total'}: try: current_value = self[key].__unicode__() self[key] = self.ITEM.string( key, swap_slashed_number(current_value, value)) except KeyError: self[key] = self.ITEM.string( key, __number_pair__(None, value)) elif attr == 'compilation': self[key] = self.ITEM.string( key, u"{:d}".format(1 if value else 0)) else: self[key] = self.ITEM.string(key, value) else: delattr(self, attr) else: MetaData.__setattr__(self, attr, value)
def __init__(self, tags, contains_header=True, contains_footer=True): """constructs an ApeTag from a list of ApeTagItem objects""" for tag in tags: assert(isinstance(tag, ApeTagItem)) MetaData.__setattr__(self, "tags", list(tags)) MetaData.__setattr__(self, "contains_header", contains_header) MetaData.__setattr__(self, "contains_footer", contains_footer)
def __init__(self, tags, contains_header=True, contains_footer=True): """constructs an ApeTag from a list of ApeTagItem objects""" for tag in tags: if (not isinstance(tag, ApeTagItem)): raise ValueError("%s is not ApeTag" % (repr(tag))) MetaData.__setattr__(self, "tags", list(tags)) MetaData.__setattr__(self, "contains_header", contains_header) MetaData.__setattr__(self, "contains_footer", contains_footer)
def __getattr__(self, attr): if attr in self.ATTRIBUTE_MAP: try: if attr in {'track_number', 'album_number'}: return self[self.ATTRIBUTE_MAP[attr]].number() elif attr in {'track_total', 'album_total'}: return self[self.ATTRIBUTE_MAP[attr]].total() else: return self[self.ATTRIBUTE_MAP[attr]].__unicode__() except KeyError: return None elif attr in MetaData.FIELDS: return None else: return MetaData.__getattribute__(self, attr)
def intersection(self, metadata): """given a MetaData-compatible object, returns a new MetaData object which contains all the matching fields and images of this object and 'metadata' """ if type(metadata) is ID3v1Comment: return ID3v1Comment( genre=(self.__genre__ if self.__genre__ == metadata.__genre__ else 0), **{arg: getattr(self, field) for arg, field in ID3v1Comment.ID3v1_FIELDS.items() if getattr(self, field) == getattr(metadata, field)}) else: return MetaData.intersection(self, metadata)
def __delattr__(self, attr): if attr == "track_number": MetaData.__setattr__(self, "__track_number__", 0) elif attr in self.FIELD_LENGTHS: MetaData.__setattr__(self, self.ID3v1_FIELDS[attr], u"") elif attr in self.FIELDS: # field not supported by ID3v1Comment, so ignore it pass else: MetaData.__delattr__(self, attr)
def __delattr__(self, attr): if (attr == "track_number"): MetaData.__setattr__(self, "__track_number__", chr(0)) elif (attr in self.FIELD_LENGTHS): MetaData.__setattr__(self, self.ID3v1_FIELDS[attr], chr(0) * self.FIELD_LENGTHS[attr]) elif (attr in self.FIELDS): # field not supported by ID3v1Comment, so ignore it pass else: MetaData.__delattr__(self, attr)
def __getattr__(self, attr): if attr == "track_number": number = self.__track_number__ if number > 0: return number else: return None elif attr in self.ID3v1_FIELDS: value = getattr(self, self.ID3v1_FIELDS[attr]) if len(value) > 0: return value else: return None elif attr in self.FIELDS: return None else: return MetaData.__getattribute__(self, attr)
def intersection(self, metadata): """given a MetaData-compatible object, returns a new MetaData object which contains all the matching fields and images of this object and 'metadata' """ if type(metadata) is ID3v1Comment: return ID3v1Comment( genre=(self.__genre__ if self.__genre__ == metadata.__genre__ else 0), **{ arg: getattr(self, field) for arg, field in ID3v1Comment.ID3v1_FIELDS.items() if getattr(self, field) == getattr(metadata, field) }) else: return MetaData.intersection(self, metadata)
def __unicode__(self): if ('Cuesheet' not in self.keys()): return ApeTag.__unicode__(self) else: import cue try: return u"%s%sCuesheet:\n%s" % \ (MetaData.__unicode__(self), os.linesep * 2, sheet_to_unicode( cue.parse( cue.tokens(unicode(self['Cuesheet']).encode( 'ascii', 'replace'))), self.frame_count)) except cue.CueException: return ApeTag.__unicode__(self)
def __eq__(self, metadata): if (isinstance(metadata, ApeTag)): if (set(self.keys()) != set(metadata.keys())): return False for tag in self.tags: try: if (tag.data != metadata[tag.key].data): return False except KeyError: return False else: return True elif (isinstance(metadata, MetaData)): return MetaData.__eq__(self, metadata) else: return False
def get_metadata(self): """returns MetaData of Sheet, or None this metadata often contains information such as catalog number or CD-TEXT values""" from audiotools import MetaData if (self.__catalog__ is not None) and (self.__cd_text__ is not None): metadata = self.__cd_text__.to_disc_metadata() metadata.catalog = self.__catalog__ return metadata elif self.__catalog__ is not None: return MetaData(catalog=self.__catalog__) elif self.__cd_text__ is not None: return self.__cd_text__.to_disc_metadata() else: return None
def get_metadata(self): """returns SheetTrack's MetaData, or None""" from audiotools import MetaData isrc = self.first_flag(TOCFlag_ISRC) cd_text = self.first_flag(CDText) if (isrc is not None) and (cd_text is not None): metadata = cd_text.to_track_metadata() metadata.ISRC = decode_string(isrc.isrc()) return metadata elif cd_text is not None: return cd_text.to_track_metadata() elif isrc is not None: return MetaData(ISRC=decode_string(isrc.isrc())) else: return None
def intersection(self, metadata): """given a MetaData-compatible object, returns a new MetaData object which contains all the matching fields and images of this object and 'metadata' """ if type(metadata) is ApeTag: matching_keys = {key for key in set(self.keys()) & set(metadata.keys()) if self[key] == metadata[key]} return ApeTag( [tag.copy() for tag in self.tags if tag.key in matching_keys], contains_header=self.contains_header or metadata.contains_header, contains_footer=self.contains_footer or metadata.contains_footer) else: return MetaData.intersection(self, metadata)
def __getattr__(self, attr): if (attr == "track_number"): number = ord(self.__track_number__) if (number > 0): return number else: return None elif (attr in self.ID3v1_FIELDS): value = getattr( self, self.ID3v1_FIELDS[attr]).rstrip(chr(0)).decode('ascii', 'replace') if (len(value) > 0): return value else: return None elif (attr in self.FIELDS): return None else: return MetaData.__getattribute__(self, attr)
def __setattr__(self, attr, value): if attr == "track_number": MetaData.__setattr__( self, "__track_number__", min(0 if (value is None) else int(value), 0xFF)) elif attr in self.FIELD_LENGTHS: if value is None: delattr(self, attr) else: # all are text fields MetaData.__setattr__( self, self.ID3v1_FIELDS[attr], value[0:self.FIELD_LENGTHS[attr]]) elif attr in self.FIELDS: # field not supported by ID3v1Comment, so ignore it pass else: MetaData.__setattr__(self, attr, value)
def __setattr__(self, attr, value): # updates the first matching field for the given attribute # in our list of comment strings def has_number(unicode_string): import re return re.search(r'\d+', unicode_string) is not None def swap_number(unicode_value, new_number): import re return re.sub(r'\d+', u"{:d}".format(new_number), unicode_value, 1) if (attr in self.FIELDS) and (value is None): # setting any value to None is equivilent to deleting it # in this high-level implementation delattr(self, attr) elif attr in self.ATTRIBUTE_MAP: key = self.ATTRIBUTE_MAP[attr] if attr in {'track_number', 'album_number'}: try: current_values = self[key] for i in range(len(current_values)): current_value = current_values[i] if u"/" not in current_value: if has_number(current_value): current_values[i] = swap_number(current_value, value) self[key] = current_values break else: (first, second) = current_value.split(u"/", 1) if has_number(first): current_values[i] = u"/".join( [swap_number(first, value), second]) self[key] = current_values break else: # no integer field matching key, so add new one self[key] = current_values + [u"{:d}".format(value)] except KeyError: # no current field with key, so add new one self[key] = [u"{:d}".format(value)] elif attr in {'track_total', 'album_total'}: # look for standalone TRACKTOTAL/DISCTOTAL field try: current_values = self[key] for i in range(len(current_values)): current_value = current_values[i] if has_number(current_value): current_values[i] = swap_number(current_value, value) self[key] = current_values return except KeyError: current_values = [] # no TRACKTOTAL/DISCTOTAL field # or none of them contain an integer, # so look for slashed TRACKNUMBER/DISCNUMBER values try: new_key = {"track_total": u"TRACKNUMBER", "album_total": u"DISCNUMBER"}[attr] slashed_values = self[new_key] for i in range(len(slashed_values)): current_value = slashed_values[i] if u"/" in current_value: (first, second) = current_value.split(u"/", 1) if has_number(second): slashed_values[i] = u"/".join( [first, swap_number(second, value)]) self[new_key] = slashed_values return except KeyError: # no TRACKNUMBER/DISCNUMBER field found pass # no slashed TRACKNUMBER/DISCNUMBER values either # so append a TRACKTOTAL/DISCTOTAL field self[key] = current_values + [u"{:d}".format(value)] elif attr == "compilation": self[key] = [u"1" if value else u"0"] else: # leave subsequent fields with the same key as-is try: current_values = self[key] self[key] = [value] + current_values[1:] except KeyError: # no current field with key, so add new one self[key] = [value] elif attr in self.FIELDS: # attribute is supported by MetaData # but not supported by VorbisComment # so ignore it pass else: # attribute is not MetaData-specific, so set as-is MetaData.__setattr__(self, attr, value)
def __delattr__(self, attr): import re if (attr == 'track_number'): try: # if "Track" field contains a slashed total if (re.search(r'\d+.*?/.*?\d+', self['Track'].data) is not None): # replace unslashed portion with 0 self['Track'].data = re.sub(r'\d+', str(int(0)), self['Track'].data, 1) else: # otherwise, remove "Track" field del(self['Track']) except KeyError: pass elif (attr == 'track_total'): try: track_number = re.search(r'\d+', self["Track"].data.split("/")[0]) # if track number is nonzero if (((track_number is not None) and (int(track_number.group(0)) != 0))): # if "Track" field contains a slashed total # remove slashed total from "Track" field self['Track'].data = re.sub(r'\s*/.*', "", self['Track'].data) else: # if "Track" field contains a slashed total if (re.search(r'/\D*?\d+', self['Track'].data) is not None): # remove "Track" field entirely del(self['Track']) except KeyError: pass elif (attr == 'album_number'): try: # if "Media" field contains a slashed total if (re.search(r'\d+.*?/.*?\d+', self['Media'].data) is not None): # replace unslashed portion with 0 self['Media'].data = re.sub(r'\d+', str(int(0)), self['Media'].data, 1) else: # otherwise, remove "Media" field del(self['Media']) except KeyError: pass elif (attr == 'album_total'): try: album_number = re.search(r'\d+', self["Media"].data.split("/")[0]) # if album number is nonzero if (((album_number is not None) and (int(album_number.group(0)) != 0))): # if "Media" field contains a slashed total # remove slashed total from "Media" field self['Media'].data = re.sub(r'\s*/.*', "", self['Media'].data) else: # if "Media" field contains a slashed total if (re.search(r'/\D*?\d+', self['Media'].data) is not None): # remove "Media" field entirely del(self['Media']) except KeyError: pass elif (attr in self.ATTRIBUTE_MAP): try: del(self[self.ATTRIBUTE_MAP[attr]]) except KeyError: pass elif (attr in MetaData.FIELDS): pass else: MetaData.__delattr__(self, attr)
def __init__(self, track_name=u"", artist_name=u"", album_name=u"", year=u"", comment=u"", track_number=0, genre=0): """fields are as follows: | field | length | |--------------+--------| | track_name | 30 | | artist_name | 30 | | album_name | 30 | | year | 4 | | comment | 28 | | track_number | 1 | | genre | 1 | |--------------+--------| track_name, artist_name, album_name, year and comment are unicode strings track_number and genre are integers """ if len(track_name) > 30: raise ValueError("track_name cannot be longer than 30 characters") if len(artist_name) > 30: raise ValueError("artist_name cannot be longer than 30 characters") if len(album_name) > 30: raise ValueError("album_name cannot be longer than 30 characters") if len(year) > 4: raise ValueError("year cannot be longer than 4 characters") if len(comment) > 28: raise ValueError("comment cannot be longer than 28 characters") MetaData.__setattr__(self, "__track_name__", track_name) MetaData.__setattr__(self, "__artist_name__", artist_name) MetaData.__setattr__(self, "__album_name__", album_name) MetaData.__setattr__(self, "__year__", year) MetaData.__setattr__(self, "__comment__", comment) MetaData.__setattr__(self, "__track_number__", track_number) MetaData.__setattr__(self, "__genre__", genre)
def __eq__(self, metadata): if isinstance(metadata, self.__class__): return self.comment_strings == metadata.comment_strings else: return MetaData.__eq__(self, metadata)
def __setattr__(self, attr, value): if (attr in self.ATTRIBUTE_MAP): if (value is not None): import re if (attr == 'track_number'): try: self['Track'].data = re.sub(r'\d+', str(int(value)), self['Track'].data, 1) except KeyError: self['Track'] = self.ITEM.string( 'Track', __number_pair__(value, self.track_total)) elif (attr == 'track_total'): try: if (re.search(r'/\D*\d+', self['Track'].data) is not None): self['Track'].data = re.sub( r'(/\D*)(\d+)', "\\g<1>" + str(int(value)), self['Track'].data, 1) else: self['Track'].data = "%s/%d" % ( self['Track'].data, value) except KeyError: self['Track'] = self.ITEM.string( 'Track', __number_pair__(self.track_number, value)) elif (attr == 'album_number'): try: self['Media'].data = re.sub(r'\d+', str(int(value)), self['Media'].data, 1) except KeyError: self['Media'] = self.ITEM.string( 'Media', __number_pair__(value, self.album_total)) elif (attr == 'album_total'): try: if (re.search(r'/\D*\d+', self['Media'].data) is not None): self['Media'].data = re.sub( r'(/\D*)(\d+)', "\\g<1>" + str(int(value)), self['Media'].data, 1) else: self['Media'].data = "%s/%d" % ( self['Media'].data, value) except KeyError: self['Media'] = self.ITEM.string( 'Media', __number_pair__(self.album_number, value)) else: self[self.ATTRIBUTE_MAP[attr]] = self.ITEM.string( self.ATTRIBUTE_MAP[attr], value) else: delattr(self, attr) else: MetaData.__setattr__(self, attr, value)
def inject_metadata(self, format, version): """ audiotools.MetaData """ meta = MetaData() """ prepare metadata object """ # track-level metadata meta.track_name = self.name meta.track_number = self.tracknumber meta.media = "DIGITAL" meta.isrc = self.isrc """ Needs fixing... for extra_artist in self.extra_artists.all(): print extra_artist meta.performer_name = meta.composer_name = meta.conductor_name = """ # release-level metadata if self.release: meta.album_name = self.release.name meta.catalog = self.release.catalognumber meta.track_total = len(self.release.media_release.all()) if self.release.releasedate: try: meta.year = str(self.release.releasedate.year) meta.date = str(self.release.releasedate) except Exception, e: print e try: cover_image = self.release.cover_image if self.release.cover_image else self.release.main_image if meta.supports_images() and cover_image: for i in meta.images(): meta.delete_image(i) opt = dict(size=(200, 200), crop=True, bw=False, quality=80) image = get_thumbnailer(cover_image).get_thumbnail(opt) meta.add_image(get_raw_image(image.path, 0)) except Exception, e: print e
def __getattr__(self, attr): # returns the first matching key for the given attribute # in our list of comment strings if attr in self.ATTRIBUTE_MAP: key = self.ATTRIBUTE_MAP[attr] if attr in {'track_number', 'album_number'}: try: # get the TRACKNUMBER/DISCNUMBER values # return the first value that contains an integer for value in self[key]: integer = re.search(r'\d+', value) if integer is not None: return int(integer.group(0)) else: # otherwise, return None return None except KeyError: # if no TRACKNUMBER/DISCNUMBER, return None return None elif attr in {'track_total', 'album_total'}: try: # get the TRACKTOTAL/DISCTOTAL values # return the first value that contains an integer for value in self[key]: integer = re.search(r'\d+', value) if integer is not None: return int(integer.group(0)) except KeyError: pass # if no TRACKTOTAL/DISCTOTAL, # or none of them contain an integer, # look for slashed TRACKNUMBER/DISCNUMBER values try: for value in self[{"track_total": u"TRACKNUMBER", "album_total": u"DISCNUMBER"}[attr]]: if u"/" in value: integer = re.search(r'\d+', value.split(u"/", 1)[1]) if integer is not None: return int(integer.group(0)) else: return None except KeyError: # no slashed TRACKNUMBER/DISCNUMBER values either # so return None return None elif attr == "compilation": try: # if present, return True if the first value is "1" return self[key][0] == u"1" except KeyError: # if not present, return None return None else: # attribute is supported by VorbisComment try: # if present, return the first value return self[key][0] except KeyError: # if not present, return None return None elif attr in self.FIELDS: # attribute is supported by MetaData # but not supported by VorbisComment return None else: # attribute is not MetaData-specific return MetaData.__getattribute__(self, attr)
def __delattr__(self, attr): #FIXME # deletes all matching keys for the given attribute # in our list of comment strings import re if attr in self.ATTRIBUTE_MAP: key = self.ATTRIBUTE_MAP[attr] if attr in {'track_number', 'album_number'}: try: current_values = self[key] # save the _total side of any slashed fields for later slashed_totals = [int(match.group(0)) for match in [re.search(r'\d+', value.split(u"/", 1)[1]) for value in current_values if u"/" in value] if match is not None] # remove the TRACKNUMBER/DISCNUMBER field itself self[key] = [] # if there are any slashed totals # and there isn't a TRACKTOTAL/DISCTOTAL field already, # add a new one total_key = {'track_number': u"TRACKTOTAL", 'album_number': u"DISCTOTAL"}[attr] if (len(slashed_totals) > 0) and (total_key not in self): self[total_key] = [u"{:d}".format(slashed_totals[0])] except KeyError: # no TRACKNUMBER/DISCNUMBER field to remove pass elif attr in {'track_total', 'album_total'}: def slash_filter(unicode_string): if u"/" not in unicode_string: return unicode_string else: return unicode_string.split(u"/", 1)[0].rstrip() slashed_key = {"track_total": u"TRACKNUMBER", "album_total": u"DISCNUMBER"}[attr] # remove TRACKTOTAL/DISCTOTAL fields self[key] = [] # preserve the non-slashed side of # TRACKNUMBER/DISCNUMBER fields try: self[slashed_key] = [slash_filter(s) for s in self[slashed_key]] except KeyError: # no TRACKNUMBER/DISCNUMBER fields pass else: # unlike __setattr_, which tries to preserve multiple instances # of fields, __delattr__ wipes them all # so that orphaned fields don't show up after deletion self[key] = [] elif attr in self.FIELDS: # attribute is part of MetaData # but not supported by VorbisComment pass else: MetaData.__delattr__(self, attr)
def __getattr__(self, attr): import re if (attr == 'track_number'): try: track_text = unicode(self["Track"]) track = re.search(r'\d+', track_text) if (track is not None): track_number = int(track.group(0)) if ((track_number == 0) and (re.search(r'/.*?(\d+)', track_text) is not None)): # if track_total is nonzero and track_number is 0 # track_number is a placeholder # so treat track_number as None return None else: return track_number else: # "Track" isn't an integer return None except KeyError: # no "Track" in list of items return None elif (attr == 'track_total'): try: track = re.search(r'/.*?(\d+)', unicode(self["Track"])) if (track is not None): return int(track.group(1)) else: # no slashed integer field in "Track" return None except KeyError: # no "Track" in list of items return None elif (attr == 'album_number'): try: media_text = unicode(self["Media"]) media = re.search(r'\d+', media_text) if (media is not None): album_number = int(media.group(0)) if ((album_number == 0) and (re.search(r'/.*?(\d+)', media_text) is not None)): # if album_total is nonzero and album_number is 0 # album_number is a placeholder # so treat album_number as None return None else: return album_number else: # "Media" isn't an integer return None except KeyError: # no "Media" in list of items return None elif (attr == 'album_total'): try: media = re.search(r'/.*?(\d+)', unicode(self["Media"])) if (media is not None): return int(media.group(1)) else: # no slashed integer field in "Media" return None except KeyError: # no "Media" in list of items return None elif (attr in self.ATTRIBUTE_MAP): try: return unicode(self[self.ATTRIBUTE_MAP[attr]]) except KeyError: return None elif (attr in MetaData.FIELDS): return None else: return MetaData.__getattribute__(self, attr)