def delete(filename, delete_v1=True, delete_v2=True): """Remove tags from a file. Keyword arguments: * delete_v1 -- delete any ID3v1 tag * delete_v2 -- delete any ID3v2 tag """ with mutagen.FileOpener(filename, 'rb+') as f: if delete_v1: tag, offset = _find_id3v1(f) if tag is not None: f.seek(offset, 2) f.truncate() # technically an insize=0 tag is invalid, but we delete it anyway # (primarily because we used to write it) if delete_v2: f.seek(0, 0) idata = f.read(10) try: id3, vmaj, vrev, flags, insize = unpack('>3sBBB4s', idata) except struct.error: id3, insize = b'', -1 insize = BitPaddedInt(insize) if id3 == b'ID3' and insize >= 0: delete_bytes(f, insize + 10, 0)
def load(self, filename, ID3=None, **kwargs): """Load stream and tag information from a file. A custom tag reader may be used in instead of the default mutagen.id3.ID3 object, e.g. an EasyID3 reader. """ if ID3 is None: ID3 = self.ID3 else: # If this was initialized with EasyID3, remember that for # when tags are auto-instantiated in add_tags. self.ID3 = ID3 self.filename = filename try: self.tags = ID3(filename, **kwargs) except ID3NoHeaderError: self.tags = None if self.tags is not None: try: offset = self.tags.size except AttributeError: offset = None else: offset = None with mutagen.FileOpener(filename, "rb") as fileobj: self.info = self._Info(fileobj, offset)
def load(self, filename): self.filename = filename self.info = self._Info(mutagen.FileOpener(filename, "rb")) try: self.tags = APEv2(filename) except APENoHeaderError: self.tags = None
def save(self, filename): """Save the metadata to the given filename.""" values = [] items = self.items() items.sort(self.__key_sort) for key, value in items: render = self.__atoms.get(key[:4], (None, M4ATags.__render_text))[1] values.append(render(self, key, value)) data = Atom.render("ilst", "".join(values)) # Find the old atoms. fileobj = mutagen.FileOpener(filename, "rb+") try: atoms = Atoms(fileobj) moov = atoms["moov"] if moov != atoms.atoms[-1]: # "Free" the old moov block. Something in the mdat # block is not happy when its offset changes and it # won't play back. So, rather than try to figure that # out, just move the moov atom to the end of the file. offset = self.__move_moov(fileobj, moov) else: offset = 0 try: path = atoms.path("moov", "udta", "meta", "ilst") except KeyError: self.__save_new(fileobj, atoms, data, offset) else: self.__save_existing(fileobj, atoms, path, data, offset) finally: fileobj.close()
def delete(filename): """Completely removes the ID3 chunk from the AIFF file""" with mutagen.FileOpener(filename, "rb+") as file_: try: del IFFFile(file_)[u'ID3'] except KeyError: pass
def delete(self, filename=None): """Remove tags from a file.""" filename = filename or self.filename fileobj = mutagen.FileOpener(filename, "r+b") try: data = _APEv2Data(fileobj) if data.start is not None and data.size is not None: delete_bytes(fileobj, data.end - data.start, data.start) finally: fileobj.close() self.clear()
def load(self, filename): self.filename = filename with mutagen.FileOpener(filename, "rb") as fileobj: self.size = 0 self.size1 = 0 self.size2 = 0 self.offset1 = 0 self.offset2 = 0 self.num_objects = 0 self.info = ASFInfo() self.tags = ASFTags() self.__read_file(fileobj)
def load(self, filename): """Load tags from a filename.""" self.filename = filename fileobj = mutagen.FileOpener(filename, "rb") try: data = _APEv2Data(fileobj) finally: fileobj.close() if data.tag: self.clear() self.__parse_tag(data.tag, data.items) else: raise APENoHeaderError("No APE tag found")
def load(self, filename): """Load file information from a filename.""" self.filename = filename fileobj = mutagen.FileOpener(filename, "rb") try: try: self.info = self._Info(fileobj) self.tags = self._Tags(fileobj, self.info) self.info._post_tags(fileobj) except error as e: reraise(self._Error, e, sys.exc_info()[2]) except EOFError: raise self._Error("no appropriate stream found") finally: fileobj.close()
def load(self, filename, **kwargs): """Load stream and tag information from a file.""" self.filename = filename try: self.tags = _IFFID3(filename, **kwargs) except ID3NoHeaderError: self.tags = None except ID3Error as e: raise error(e) try: fileobj = mutagen.FileOpener(filename, "rb") self.info = AIFFInfo(fileobj) finally: fileobj.close()
def load(self, filename): self.filename = filename fileobj = mutagen.FileOpener(filename, "rb") try: atoms = Atoms(fileobj) try: self.info = M4AInfo(atoms, fileobj) except StandardError as err: reraise(M4AStreamInfoError, err, sys.exc_info()[2]) try: self.tags = M4ATags(atoms, fileobj) except M4AMetadataError: self.tags = None except StandardError as err: reraise(M4AMetadataError, err, sys.exc_info()[2]) finally: fileobj.close()
def save(self, filename=None): """Save a tag to a file. If no filename is given, the one most recently loaded is used. """ if filename is None: filename = self.filename fileobj = mutagen.FileOpener(filename, "rb+") try: try: self.tags._inject(fileobj) except error as e: reraise(self._Error, e, sys.exc_info()[2]) except EOFError: raise self._Error("no appropriate stream found") finally: fileobj.close()
def save(self, filename): """Save the metadata to the given filename.""" values = [] items = sorted(self.items(), key=self._key_sort) for key, value in items: atom_name = _key2name(key)[:4] if atom_name in self.__atoms: render_func = self.__atoms[atom_name][1] else: render_func = type(self).__render_text try: values.append(render_func(self, key, value)) except (TypeError, ValueError) as s: reraise(MP4MetadataValueError, s, sys.exc_info()[2]) for key, failed in iteritems(self._failed_atoms): # don't write atoms back if we have added a new one with # the same name, this excludes freeform which can have # multiple atoms with the same key (most parsers seem to be able # to handle that) if key in self: assert _key2name(key) != b"----" continue for data in failed: values.append(Atom.render(_key2name(key), data)) data = Atom.render(b"ilst", b"".join(values)) # Find the old atoms. with mutagen.FileOpener(filename, "rb+") as fileobj: try: atoms = Atoms(fileobj) except AtomError as err: reraise(error, err, sys.exc_info()[2]) try: path = atoms.path(b"moov", b"udta", b"meta", b"ilst") except KeyError: self.__save_new(fileobj, atoms, data) else: self.__save_existing(fileobj, atoms, path, data)
def load(self, filename): """Load file information from a filename.""" self.metadata_blocks = [] self.tags = None self.cuesheet = None self.seektable = None self.filename = filename fileobj = StrictFileObject(mutagen.FileOpener(filename, "rb")) try: self.__check_header(fileobj) while self.__read_metadata_block(fileobj): pass finally: fileobj.close() try: self.metadata_blocks[0].length except (AttributeError, IndexError): raise FLACNoHeaderError("Stream info block not found")
def save(self, filename=None, v2_version=4, v23_sep='/'): """Save ID3v2 data to the AIFF file""" framedata = self._prepare_framedata(v2_version, v23_sep) framesize = len(framedata) if filename is None: filename = self.filename # Unlike the parent ID3.save method, we won't save to a blank file # since we would have to construct a empty AIFF file fileobj = mutagen.FileOpener(filename, 'rb+') iff_file = IFFFile(fileobj) try: if u'ID3' not in iff_file: iff_file.insert_chunk(u'ID3') chunk = iff_file[u'ID3'] fileobj.seek(chunk.data_offset) header = fileobj.read(10) header = self._prepare_id3_header(header, framesize, v2_version) header, new_size, _ = header data = header + framedata + (b'\x00' * (new_size - framesize)) # Include ID3 header size in 'new_size' calculation new_size += 10 # Expand the chunk if necessary, including pad byte if new_size > chunk.size: insert_at = chunk.offset + chunk.size insert_size = new_size - chunk.size + new_size % 2 insert_bytes(fileobj, insert_size, insert_at) chunk.resize(new_size) fileobj.seek(chunk.data_offset) fileobj.write(data) finally: fileobj.close()
def load(self, filename): self.filename = filename with mutagen.FileOpener(filename, "rb") as fileobj: try: atoms = Atoms(fileobj) except AtomError as err: reraise(error, err, sys.exc_info()[2]) try: self.info = MP4Info(atoms, fileobj) except error: raise except Exception as err: reraise(MP4StreamInfoError, err, sys.exc_info()[2]) if not MP4Tags._can_load(atoms): self.tags = None else: try: self.tags = self.MP4Tags(atoms, fileobj) except error: raise except Exception as err: reraise(MP4MetadataError, err, sys.exc_info()[2])
def load(self, filename): self.filename = filename with mutagen.FileOpener(filename, "rb") as h: self.info = AACInfo(h)
def save(self): # Move attributes to the right objects self.to_content_description = {} self.to_extended_content_description = {} self.to_metadata = {} self.to_metadata_library = [] for name, value in self.tags: library_only = (value.data_size() > 0xFFFF or value.TYPE == GUID) can_cont_desc = value.TYPE == UNICODE if library_only or value.language is not None: self.to_metadata_library.append((name, value)) elif value.stream is not None: if name not in self.to_metadata: self.to_metadata[name] = value else: self.to_metadata_library.append((name, value)) elif name in ContentDescriptionObject.NAMES: if name not in self.to_content_description and can_cont_desc: self.to_content_description[name] = value else: self.to_metadata_library.append((name, value)) else: if name not in self.to_extended_content_description: self.to_extended_content_description[name] = value else: self.to_metadata_library.append((name, value)) # Add missing objects if not self.content_description_obj: self.content_description_obj = \ ContentDescriptionObject() self.objects.append(self.content_description_obj) if not self.extended_content_description_obj: self.extended_content_description_obj = \ ExtendedContentDescriptionObject() self.objects.append(self.extended_content_description_obj) if not self.header_extension_obj: self.header_extension_obj = \ HeaderExtensionObject() self.objects.append(self.header_extension_obj) if not self.metadata_obj: self.metadata_obj = \ MetadataObject() self.header_extension_obj.objects.append(self.metadata_obj) if not self.metadata_library_obj: self.metadata_library_obj = \ MetadataLibraryObject() self.header_extension_obj.objects.append(self.metadata_library_obj) # Render the header data = b"".join([obj.render(self) for obj in self.objects]) data = (HeaderObject.GUID + struct.pack("<QL", len(data) + 30, len(self.objects)) + b"\x01\x02" + data) with mutagen.FileOpener(self.filename, "rb+") as fileobj: size = len(data) if size > self.size: insert_bytes(fileobj, size - self.size, self.size) if size < self.size: delete_bytes(fileobj, self.size - size, 0) fileobj.seek(0) fileobj.write(data) self.size = size self.num_objects = len(self.objects)
def save(self, filename=None): """Save changes to a file. If no filename is given, the one most recently loaded is used. Tags are always written at the end of the file, and include a header and a footer. """ filename = filename or self.filename try: fileobj = mutagen.FileOpener(filename, "r+b") except IOError: fileobj = mutagen.FileOpener(filename, "w+b") data = _APEv2Data(fileobj) if data.is_at_start: delete_bytes(fileobj, data.end - data.start, data.start) elif data.start is not None: fileobj.seek(data.start) # Delete an ID3v1 tag if present, too. fileobj.truncate() fileobj.seek(0, 2) tags = [] for key, value in self.items(): # Packed format for an item: # 4B: Value length # 4B: Value type # Key name # 1B: Null # Key value value_data = value._write() if not isinstance(key, bytes): key = key.encode("utf-8") tag_data = bytearray() tag_data += struct.pack("<2I", len(value_data), value.kind << 1) tag_data += key + b"\0" + value_data tags.append(bytes(tag_data)) # "APE tags items should be sorted ascending by size... This is # not a MUST, but STRONGLY recommended. Actually the items should # be sorted by importance/byte, but this is not feasible." tags.sort(key=len) num_tags = len(tags) tags = b"".join(tags) header = bytearray(b"APETAGEX") # version, tag size, item count, flags header += struct.pack("<4I", 2000, len(tags) + 32, num_tags, HAS_HEADER | IS_HEADER) header += b"\0" * 8 fileobj.write(header) fileobj.write(tags) footer = bytearray(b"APETAGEX") footer += struct.pack("<4I", 2000, len(tags) + 32, num_tags, HAS_HEADER) footer += b"\0" * 8 fileobj.write(footer) fileobj.close()
def save(self, filename=None, deleteid3=False): """Save metadata blocks to a file. If no filename is given, the one most recently loaded is used. """ if filename is None: filename = self.filename f = mutagen.FileOpener(filename, 'rb+') try: # Ensure we've got padding at the end, and only at the end. # If adding makes it too large, we'll scale it down later. self.metadata_blocks.append(Padding(b'\x00' * 1020)) MetadataBlock.group_padding(self.metadata_blocks) header = self.__check_header(f) # "fLaC" and maybe ID3 available = self.__find_audio_offset(f) - header data = MetadataBlock.writeblocks(self.metadata_blocks) # Delete ID3v2 if deleteid3 and header > 4: available += header - 4 header = 4 if len(data) > available: # If we have too much data, see if we can reduce padding. padding = self.metadata_blocks[-1] newlength = padding.length - (len(data) - available) if newlength > 0: padding.length = newlength data = MetadataBlock.writeblocks(self.metadata_blocks) assert len(data) == available elif len(data) < available: # If we have too little data, increase padding. self.metadata_blocks[-1].length += (available - len(data)) data = MetadataBlock.writeblocks(self.metadata_blocks) assert len(data) == available if len(data) != available: # We couldn't reduce the padding enough. diff = (len(data) - available) insert_bytes(f, diff, header) f.seek(header - 4) f.write(b"fLaC" + data) # Delete ID3v1 if deleteid3: try: f.seek(-128, 2) except IOError: pass else: if f.read(3) == b"TAG": f.seek(-128, 2) f.truncate() finally: f.close()
def load(self, filename, known_frames=None, translate=True, v2_version=4): """Load tags from a filename. Keyword arguments: * filename -- filename to load tag data from * known_frames -- dict mapping frame IDs to Frame objects * translate -- Update all tags to ID3v2.3/4 internally. If you intend to save, this must be true or you have to call update_to_v23() / update_to_v24() manually. * v2_version -- if update_to_v23 or update_to_v24 get called (3 or 4) Example of loading a custom frame:: my_frames = dict(mutagen.id3.Frames) class XMYF(Frame): ... my_frames["XMYF"] = XMYF mutagen.id3.ID3(filename, known_frames=my_frames) """ if v2_version not in (3, 4): raise ValueError("Only 3 and 4 possible for v2_version") self.filename = filename self.unknown_frames = [] self.__known_frames = known_frames self._header = None with mutagen.FileOpener(filename, 'rb') as fileobj: self._pre_load_header(fileobj) try: self._header = ID3Header(fileobj) except (ID3NoHeaderError, ID3UnsupportedVersionError): frames, offset = _find_id3v1(fileobj) if frames is None: raise self.version = ID3Header._V11 for v in frames.values(): self.add(v) else: frames = self.__known_frames if frames is None: if self.version >= ID3Header._V23: frames = Frames elif self.version >= ID3Header._V22: frames = Frames_2_2 try: data = _fullread(fileobj, self.size - 10) except (ValueError, EOFError, IOError) as e: raise error(e) for frame in self.__read_frames(data, frames=frames): if isinstance(frame, Frame): self.add(frame) else: self.unknown_frames.append(frame) self.__unknown_version = self.version[:2] if translate: if v2_version == 3: self.update_to_v23() else: self.update_to_v24()
def save(self, filename=None, v1=1, v2_version=4, v23_sep='/'): """Save changes to a file. Args: filename: Filename to save the tag to. If no filename is given, the one most recently loaded is used. v1 (ID3v1SaveOptions): if 0, ID3v1 tags will be removed. if 1, ID3v1 tags will be updated but not added. if 2, ID3v1 tags will be created and/or updated v2 (int): version of ID3v2 tags (3 or 4). v23_sep (str): the separator used to join multiple text values if v2_version == 3. Defaults to '/' but if it's None will be the ID3v2v2.4 null separator. By default Mutagen saves ID3v2.4 tags. If you want to save ID3v2.3 tags, you must call method update_to_v23 before saving the file. The lack of a way to update only an ID3v1 tag is intentional. """ framedata = self._prepare_framedata(v2_version, v23_sep) framesize = len(framedata) if not framedata: try: self.delete(filename) except EnvironmentError as err: from errno import ENOENT if err.errno != ENOENT: raise return if filename is None: filename = self.filename try: f = mutagen.FileOpener(filename, 'rb+') except IOError as err: from errno import ENOENT if err.errno != ENOENT: raise f = mutagen.FileOpener(filename, 'ab') # create, then reopen f = mutagen.FileOpener(filename, 'rb+') try: idata = f.read(10) header = self._prepare_id3_header(idata, framesize, v2_version) header, outsize, insize = header data = header + framedata + (b'\x00' * (outsize - framesize)) if (insize < outsize): insert_bytes(f, outsize - insize, insize + 10) f.seek(0) f.write(data) self.__save_v1(f, v1) finally: f.close()