def cleanup_tags(self) -> None: '''Delete any ReplayGain tags from track. This dicards any unsaved changes, then modifies and saves the track's tags on disk and then reloads the new tags from disk. ''' tags_to_clean = set(rg_tags) # type: Set[str] tags_to_clean.update('QuodLibet::' + tag for tag in rg_tags) tags_to_clean.update('TXXX:' + tag for tag in rg_tags) tags_to_clean.update(['RVA2:track', 'RVA2:album']) tags_to_clean = { tag.lower() for tag in tags_to_clean } # Need a non-easy interface for proper ID3 cleanup t = MusicFile(self.filename, easy=False) tags_to_delete = [] for k in t.keys(): if k.lower() in tags_to_clean: tags_to_delete.append(k) for k in tags_to_delete: logger.debug("Deleting tag: %s", repr(k)) del t[k] t.save() # Re-init to pick up tag changes new_track = type(self.track)(self.filename) self.track = new_track
def fixup_ID3(fname: Union[str, MusicFileType]) -> None: '''Convert RVA2 tags to TXXX:replaygain_* tags. Argument should be an MusicFile (instance of mutagen.FileType) or a string, which will be loaded by mutagen.MusicFile. If it is an instance of mutagen.id3.ID3FileType, the ReplayGain information in the RVA2 tags (if any) will be propagated to 'TXXX:replaygain_*' tags. Thus the resulting file will have the ReplayGain information encoded both ways for maximum compatibility. If the track is an instance of 'mutagen.mp3.EasyMP3', it will be re-opened as the non-easy equivalent, since EasyMP3 maps the replaygain tags to RVA2, preventing the editing of the TXXX tags. This function modifies the file on disk. ''' # Make sure we have the non-easy variant. if isinstance(fname, MusicFileType): fname = fname.filename # type: ignore track = MusicFile(fname, easy=False) # Only operate on ID3 if not isinstance(track, id3.ID3FileType): return # Get the RVA2 frames try: track_rva2 = track['RVA2:track'] if track_rva2.channel != 1: track_rva2 = None except KeyError: track_rva2 = None try: album_rva2 = track['RVA2:album'] if album_rva2.channel != 1: album_rva2 = None except KeyError: album_rva2 = None # Add the other tags based on RVA2 values if track_rva2: track['TXXX:replaygain_track_peak'] = \ id3.TXXX(encoding=id3.Encoding.UTF8, desc='replaygain_track_peak', text=format_peak(track_rva2.peak)) track['TXXX:replaygain_track_gain'] = \ id3.TXXX(encoding=id3.Encoding.UTF8, desc='replaygain_track_gain', text=format_gain(track_rva2.gain)) if album_rva2: track['TXXX:replaygain_album_peak'] = \ id3.TXXX(encoding=id3.Encoding.UTF8, desc='replaygain_album_peak', text=format_peak(album_rva2.peak)) track['TXXX:replaygain_album_gain'] = \ id3.TXXX(encoding=id3.Encoding.UTF8, desc='replaygain_album_gain', text=format_gain(album_rva2.gain)) track.save()
def __init__(self, filename: str, blacklist: List[Pattern[str]]=[], easy: bool=True) -> None: self.filename = filename self.data = MusicFile(self.filename, easy=easy) if self.data is None: raise ValueError("Unable to identify %s as a music file" % (repr(filename))) # Also exclude mutagen's internal tags self.blacklist = [ re.compile("^~") ] + blacklist
def get_all_music_files (paths: Iterable[str], ignore_hidden: bool = True) -> Iterable[MusicFileType]: '''Recursively search in one or more paths for music files. By default, hidden files and directories are ignored. ''' paths = map(fullpath, paths) for p in remove_redundant_paths(paths): if os.path.isdir(p): files = [] # type: Iterable[str] for root, dirs, files in os.walk(p, followlinks=True): logger.debug("Searching for music files in %s", repr(root)) if ignore_hidden: # Modify dirs in place to cut off os.walk dirs[:] = list(remove_hidden_paths(dirs)) files = remove_hidden_paths(files) files = filter(lambda f: is_music_file(os.path.join(root, f)), files) for f in files: yield MusicFile(os.path.join(root, f), easy=True) else: logger.debug("Checking for music files at %s", repr(p)) f = MusicFile(p, easy=True) if f is not None: yield f
def get_all_music_files(paths, ignore_hidden=True): '''Recursively search in one or more paths for music files. By default, hidden files and directories are ignored.''' music_files = [] if isinstance(paths, str): paths = (paths, ) for p in paths: if os.path.isdir(p): for root, dirs, files in os.walk(p, followlinks=True): if ignore_hidden: files = remove_hidden_paths(files) dirs = remove_hidden_paths(dirs) # Try to load every file as an audio file, and filter the # ones that aren't actually audio files more_files = [MusicFile(os.path.join(root, x)) for x in files] music_files.extend([f for f in more_files if f is not None]) else: f = MusicFile(p) if f is not None: music_files.append(f) # Filter duplicate files and return return sorted(unique(music_files, key_fun=lambda x: x.filename))
def is_music_file(file: str) -> bool: # Exists? if not os.path.exists(file): logger.debug("File %s does not exist", repr(file)) return False if not os.path.getsize(file) > 0: logger.debug("File %s has zero size", repr(file)) return False # Readable by Mutagen? try: if not MusicFile(file): logger.debug("File %s is not recognized by Mutagen", repr(file)) return False except Exception: logger.debug("File %s is not recognized", repr(file)) return False # OK! return True
def __init__(self, track: Union[MusicFileType, str]) -> None: if not isinstance(track, MusicFileType): track = MusicFile(track, easy=True) self.track = track # type: MusicFileType self.filename = self.track.filename self.directory = os.path.dirname(self.filename)