def _remove_deleted_tags(self, metadata, tags): """Remove the tags from the file that were deleted in the UI""" config = get_config() for name in metadata.deleted_tags: real_name = self._get_tag_name(name) try: if name.startswith('performer:'): role = name.split(':', 1)[1] _remove_people_with_role(tags, ['TMCL', 'TIPL', 'IPLS'], role) elif name.startswith('comment:') or name == 'comment': (lang, desc) = parse_comment_tag(name) for key, frame in list(tags.items()): if (frame.FrameID == 'COMM' and frame.desc == desc and frame.lang == lang): del tags[key] elif name.startswith('lyrics:') or name == 'lyrics': if ':' in name: desc = name.split(':', 1)[1] else: desc = '' for key, frame in list(tags.items()): if frame.FrameID == 'USLT' and frame.desc == desc: del tags[key] elif name in self._rtipl_roles: role = self._rtipl_roles[name] _remove_people_with_role(tags, ['TIPL', 'IPLS'], role) elif name == 'musicbrainz_recordingid': for key, frame in list(tags.items()): if frame.FrameID == 'UFID' and frame.owner == 'http://musicbrainz.org': del tags[key] elif real_name == 'POPM': user_email = config.setting['rating_user_email'] for key, frame in list(tags.items()): if frame.FrameID == 'POPM' and frame.email == user_email: del tags[key] elif real_name in self.__translate: del tags[real_name] elif name.lower() in self.__rtranslate_freetext_ci: delall_ci( tags, 'TXXX:' + self.__rtranslate_freetext_ci[name.lower()]) elif real_name in self.__translate_freetext: tags.delall('TXXX:' + real_name) if real_name in self.__rrename_freetext: tags.delall('TXXX:' + self.__rrename_freetext[real_name]) elif not name.startswith( "~id3:") and name not in self.__other_supported_tags: tags.delall('TXXX:' + name) elif name.startswith("~id3:"): frameid = name[5:] tags.delall(frameid) elif name in self.__other_supported_tags: del tags[real_name] except KeyError: pass
def test_delall_ci(self): tags = { 'TAGNAME:ABC': 'a', 'tagname:abc': 'a', 'TagName:Abc': 'a', 'OtherTag': 'a' } mutagenext.delall_ci(tags, 'tagname:Abc') self.assertEqual({'OtherTag': 'a'}, tags)
def _save(self, filename, metadata): log.debug("Saving file %r", filename) config = get_config() file = ASF(encode_filename(filename)) tags = file.tags if config.setting['clear_existing_tags']: cover = tags['WM/Picture'] if config.setting[ 'preserve_images'] else None tags.clear() if cover: tags['WM/Picture'] = cover cover = [] for image in metadata.images.to_be_saved_to_tags(): tag_data = pack_image(image.mimetype, image.data, image_type_as_id3_num(image.maintype), image.comment) cover.append(ASFByteArrayAttribute(tag_data)) if cover: tags['WM/Picture'] = cover for name, values in metadata.rawitems(): if name.startswith('lyrics:'): name = 'lyrics' elif name == '~rating': values = [ int(values[0]) * 99 // (config.setting['rating_steps'] - 1) ] elif name == 'discnumber' and 'totaldiscs' in metadata: values = [ '%s/%s' % (metadata['discnumber'], metadata['totaldiscs']) ] if name in self.__TRANS: name = self.__TRANS[name] elif name in self.__TRANS_CI: if name in self.__casemap: name = self.__casemap[name] else: name = self.__TRANS_CI[name] delall_ci(tags, name) else: continue tags[name] = values self._remove_deleted_tags(metadata, tags) file.save()
def _save(self, filename, metadata): log.debug("Saving file %r", filename) config = get_config() file = MP4(encode_filename(self.filename)) if file.tags is None: file.add_tags() tags = file.tags if config.setting["clear_existing_tags"]: tags.clear() for name, values in metadata.rawitems(): if name.startswith('lyrics:'): name = 'lyrics' if name == 'comment:': name = 'comment' if name in self.__r_text_tags: tags[self.__r_text_tags[name]] = values elif name in self.__r_bool_tags: tags[self.__r_bool_tags[name]] = (values[0] == '1') elif name in self.__r_int_tags: try: tags[self.__r_int_tags[name]] = [ int(value) for value in values ] except ValueError: pass elif name in self.__r_freeform_tags: values = [v.encode("utf-8") for v in values] tags[self.__r_freeform_tags[name]] = values elif name in self.__r_freeform_tags_ci: values = [v.encode("utf-8") for v in values] delall_ci(tags, self.__r_freeform_tags_ci[name]) if name in self.__casemap: name = self.__casemap[name] else: name = self.__r_freeform_tags_ci[name] tags[name] = values elif name == "musicip_fingerprint": tags["----:com.apple.iTunes:fingerprint"] = [ b"MusicMagic Fingerprint%s" % v.encode('ascii') for v in values ] elif self.supports_tag( name) and name not in self.__other_supported_tags: values = [v.encode("utf-8") for v in values] name = self.__casemap.get(name, name) tags['----:com.apple.iTunes:' + name] = values if "tracknumber" in metadata: try: tracknumber = int(metadata["tracknumber"]) except ValueError: pass else: totaltracks = 0 if "totaltracks" in metadata: try: totaltracks = int(metadata["totaltracks"]) except ValueError: pass tags["trkn"] = [(tracknumber, totaltracks)] if "discnumber" in metadata: try: discnumber = int(metadata["discnumber"]) except ValueError: pass else: totaldiscs = 0 if "totaldiscs" in metadata: try: totaldiscs = int(metadata["totaldiscs"]) except ValueError: pass tags["disk"] = [(discnumber, totaldiscs)] covr = [] for image in metadata.images.to_be_saved_to_tags(): if image.mimetype == "image/jpeg": covr.append(MP4Cover(image.data, MP4Cover.FORMAT_JPEG)) elif image.mimetype == "image/png": covr.append(MP4Cover(image.data, MP4Cover.FORMAT_PNG)) if covr: tags["covr"] = covr self._remove_deleted_tags(metadata, tags) file.save()
def _save(self, filename, metadata): """Save metadata to the file.""" log.debug("Saving file %r", filename) tags = self._get_tags(filename) if config.setting['clear_existing_tags']: tags.clear() images_to_save = list(metadata.images.to_be_saved_to_tags()) if images_to_save: tags.delall('APIC') encoding = { 'utf-8': 3, 'utf-16': 1 }.get(config.setting['id3v2_encoding'], 0) if 'tracknumber' in metadata: if 'totaltracks' in metadata: text = '%s/%s' % (metadata['tracknumber'], metadata['totaltracks']) else: text = metadata['tracknumber'] tags.add(id3.TRCK(encoding=0, text=id3text(text, 0))) if 'discnumber' in metadata: if 'totaldiscs' in metadata: text = '%s/%s' % (metadata['discnumber'], metadata['totaldiscs']) else: text = metadata['discnumber'] tags.add(id3.TPOS(encoding=0, text=id3text(text, 0))) if 'movementnumber' in metadata: if 'movementtotal' in metadata: text = '%s/%s' % (metadata['movementnumber'], metadata['movementtotal']) else: text = metadata['movementnumber'] tags.add(id3.MVIN(encoding=0, text=id3text(text, 0))) # This is necessary because mutagens HashKey for APIC frames only # includes the FrameID (APIC) and description - it's basically # impossible to save two images, even of different types, without # any description. counters = defaultdict(lambda: 0) for image in images_to_save: desc = desctag = image.comment if counters[desc] > 0: if desc: desctag = "%s (%i)" % (desc, counters[desc]) else: desctag = "(%i)" % counters[desc] counters[desc] += 1 tags.add( id3.APIC(encoding=0, mime=image.mimetype, type=image_type_as_id3_num(image.maintype), desc=id3text(desctag, 0), data=image.data)) tmcl = mutagen.id3.TMCL(encoding=encoding, people=[]) tipl = mutagen.id3.TIPL(encoding=encoding, people=[]) for name, values in metadata.rawitems(): values = [id3text(v, encoding) for v in values] name = id3text(name, encoding) name_lower = name.lower() if not self.supports_tag(name): continue elif name.startswith('performer:'): role = name.split(':', 1)[1] for value in values: if config.setting['write_id3v23']: # TIPL will be upgraded to IPLS tipl.people.append([role, value]) else: tmcl.people.append([role, value]) elif name == 'comment' or name.startswith('comment:'): (lang, desc) = parse_comment_tag(name) if desc.lower()[:4] == 'itun': tags.delall('COMM:' + desc) tags.add( id3.COMM(encoding=0, desc=desc, lang='eng', text=[v + '\x00' for v in values])) else: tags.add( id3.COMM(encoding=encoding, desc=desc, lang=lang, text=values)) elif name.startswith('lyrics:') or name == 'lyrics': if ':' in name: desc = name.split(':', 1)[1] else: desc = '' for value in values: tags.add(id3.USLT(encoding=encoding, desc=desc, text=value)) elif name in self._rtipl_roles: for value in values: tipl.people.append([self._rtipl_roles[name], value]) elif name == 'musicbrainz_recordingid': tags.add( id3.UFID(owner='http://musicbrainz.org', data=bytes(values[0], 'ascii'))) elif name == '~rating': # Search for an existing POPM frame to get the current playcount for frame in tags.values(): if frame.FrameID == 'POPM' and frame.email == config.setting[ 'rating_user_email']: count = getattr(frame, 'count', 0) break else: count = 0 # Convert rating to range between 0 and 255 rating = int( round( float(values[0]) * 255 / (config.setting['rating_steps'] - 1))) tags.add( id3.POPM(email=config.setting['rating_user_email'], rating=rating, count=count)) elif name == 'grouping': if config.setting['itunes_compatible_grouping']: tags.add(id3.GRP1(encoding=encoding, text=values)) else: tags.add(id3.TIT1(encoding=encoding, text=values)) elif name == 'work' and config.setting[ 'itunes_compatible_grouping']: tags.add(id3.TIT1(encoding=encoding, text=values)) tags.delall('TXXX:Work') tags.delall('TXXX:WORK') elif name in self.__rtranslate: frameid = self.__rtranslate[name] if frameid.startswith('W'): valid_urls = all([all(urlparse(v)[:2]) for v in values]) if frameid == 'WCOP': # Only add WCOP if there is only one license URL, otherwise use TXXX:LICENSE if len(values) > 1 or not valid_urls: tags.add( self.build_TXXX( encoding, self.__rtranslate_freetext[name], values)) else: tags.add(id3.WCOP(url=values[0])) elif frameid == 'WOAR' and valid_urls: for url in values: tags.add(id3.WOAR(url=url)) elif frameid.startswith('T') or frameid == 'MVNM': if config.setting['write_id3v23']: if frameid == 'TMOO': tags.add(self.build_TXXX(encoding, 'mood', values)) # No need to care about the TMOO tag being added again as it is # automatically deleted by Mutagen if id2v23 is selected tags.add( getattr(id3, frameid)(encoding=encoding, text=values)) if frameid == 'TSOA': tags.delall('XSOA') elif frameid == 'TSOP': tags.delall('XSOP') elif frameid == 'TSO2': tags.delall('TXXX:ALBUMARTISTSORT') elif name_lower in self.__rtranslate_freetext_ci: if name_lower in self.__casemap: description = self.__casemap[name_lower] else: description = self.__rtranslate_freetext_ci[name_lower] delall_ci(tags, 'TXXX:' + description) tags.add(self.build_TXXX(encoding, description, values)) elif name in self.__rtranslate_freetext: description = self.__rtranslate_freetext[name] if description in self.__rrename_freetext: tags.delall('TXXX:' + self.__rrename_freetext[description]) tags.add(self.build_TXXX(encoding, description, values)) elif name.startswith('~id3:'): name = name[5:] if name.startswith('TXXX:'): tags.add(self.build_TXXX(encoding, name[5:], values)) else: frameclass = getattr(id3, name[:4], None) if frameclass: tags.add(frameclass(encoding=encoding, text=values)) # don't save private / already stored tags elif not name.startswith( "~") and name not in self.__other_supported_tags: tags.add(self.build_TXXX(encoding, name, values)) if tmcl.people: tags.add(tmcl) if tipl.people: tags.add(tipl) self._remove_deleted_tags(metadata, tags) self._save_tags(tags, encode_filename(filename)) if self._IsMP3 and config.setting["remove_ape_from_mp3"]: try: mutagen.apev2.delete(encode_filename(filename)) except BaseException: pass