예제 #1
0
    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
예제 #2
0
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()
예제 #3
0
 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
예제 #4
0
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
예제 #5
0
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))
예제 #6
0
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
예제 #7
0
 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)