Пример #1
0
def rename_files(release: Release, parent_path: str, dry_run=False) -> None:

    renames = []

    # rename files
    for filename in release.tracks:
        correct_filename = release.tracks[filename].get_filename(release.is_va())

        if not correct_filename:
            return

        dest_path = os.path.join(parent_path, correct_filename)

        # if the track passes validation, and the filename differs from the calculated correct filename
        if filename != correct_filename and (
                # and the destination doesn't exist, or it's a case-insensitive match of the source
                not os.path.exists(dest_path) or os.path.normcase(filename) == os.path.normcase(correct_filename)):
            renames.append([filename, correct_filename])

    for rename in renames:
        if not dry_run:
            dest_path = os.path.join(parent_path, rename[1])

            if os.name == "nt" and len(dest_path) > 260:
                fn, ext = os.path.splitext(dest_path)
                dest_path = dest_path[0:260 - len(ext)] + ext

            if os.path.exists(dest_path) and os.path.normcase(rename[0]) != os.path.normcase(rename[1]):
                logging.getLogger(__name__).error("File already exists, could not rename: {0}".format(dest_path))
                continue

            while True:
                try:
                    os.rename(os.path.join(parent_path, rename[0]), dest_path)
                    break
                except PermissionError:
                    logging.getLogger(__name__).error("PermissionError: could not rename '{0}' -> '{1}', retrying..."
                                                      .format(os.path.join(parent_path, rename[0]), dest_path))
                    time.sleep(1)

            # clean up empty directories
            curr_parent_path = os.path.join(parent_path, os.path.split(rename[0])[0])
            while not os.listdir(curr_parent_path):
                os.rmdir(curr_parent_path)
                curr_parent_path = os.path.split(curr_parent_path)[0]

        release.tracks[rename[1]] = release.tracks.pop(rename[0])
Пример #2
0
def move_rename_folder(release: Release, unique_releases: Set[Tuple],
                       curr_dir: str, dest_folder: str, duplicate_folder: str,
                       args: argparse.Namespace) -> str:
    """Rename a release folder, and move to a destination folder"""

    # if a dry run,or the folder name cannot be validated, do nothing
    if args.dry_run or not release.can_validate_folder_name():
        return curr_dir

    moved_dir = curr_dir

    # rename the release folder
    codec_short = not args.full_codec_names
    fixed_dir = os.path.join(
        os.path.split(curr_dir)[0],
        release.get_folder_name(codec_short=codec_short,
                                group_by_category=args.group_by_category))
    if curr_dir != fixed_dir:
        if not os.path.exists(fixed_dir) or os.path.normcase(
                curr_dir) == os.path.normcase(fixed_dir):
            while True:
                try:
                    os.rename(curr_dir, fixed_dir)
                    break
                except PermissionError:
                    logging.getLogger(__name__).error(
                        "PermissionError: could not rename directory to {0}".
                        format(fixed_dir))
                    time.sleep(1)

            moved_dir = fixed_dir
        else:
            logging.getLogger(__name__).error(
                "Release folder already exists: {0}".format(fixed_dir))

    # move the release folder to a destination
    moved_duplicate = False
    if dest_folder and release.num_violations == 0:
        artist_folder = flatten_artists(release.validate_release_artists()) \
            if args.group_by_artist and not release.is_va() else ""

        category_folder = str(
            release.category.value) if args.group_by_category else ""
        curr_dest_parent_folder = os.path.join(dest_folder, category_folder,
                                               artist_folder)
        curr_dest_folder = os.path.join(
            curr_dest_parent_folder,
            release.get_folder_name(codec_short=codec_short,
                                    group_by_category=args.group_by_category))

        if os.path.normcase(moved_dir) != os.path.normcase(curr_dest_folder):
            if not os.path.exists(curr_dest_parent_folder):
                os.makedirs(curr_dest_parent_folder, exist_ok=True)
            if not os.path.exists(curr_dest_folder):
                os.rename(moved_dir, curr_dest_folder)
                moved_dir = curr_dest_folder

                # clean up empty directories
                curr_src_parent_folder = os.path.split(fixed_dir)[0]
                while not os.listdir(curr_src_parent_folder):
                    os.rmdir(curr_src_parent_folder)
                    curr_src_parent_folder = os.path.split(
                        curr_src_parent_folder)[0]
            else:
                if duplicate_folder:
                    release_folder_name = release.get_folder_name(
                        codec_short=codec_short,
                        group_by_category=args.group_by_category)
                    moved_dir = move_duplicate(duplicate_folder, moved_dir,
                                               release_folder_name)
                    moved_duplicate = True

                else:
                    logging.getLogger(__name__).error(
                        "Destination folder already exists: {0}".format(
                            fixed_dir))

    # deduplicate versions of the same release
    unique_release = UniqueRelease(
        release.validate_release_artists(),
        release.validate_release_date().split("-")[0],
        release.validate_release_title(), release.validate_codec(),
        release.get_codec_rank(), moved_dir)

    if duplicate_folder and release.num_violations == 0 and not moved_duplicate:
        if unique_release in unique_releases:
            existing = [x for x in unique_releases if x == unique_release][0]
            if unique_release > existing:
                # move the existing one
                release_folder_name = os.path.split(existing.path)[1]
                moved_dir = move_duplicate(duplicate_folder, existing.path,
                                           release_folder_name)
                unique_releases.remove(unique_release)
                unique_releases.add(unique_release)
            else:
                # move the current one
                release_folder_name = release.get_folder_name(
                    codec_short=codec_short,
                    group_by_category=args.group_by_category)
                moved_dir = move_duplicate(duplicate_folder, moved_dir,
                                           release_folder_name)

        else:
            unique_releases.add(unique_release)

    return moved_dir
Пример #3
0
    def validate(self, release: Release) -> List[Violation]:
        violations = OrderedSet()

        # leading/trailing whitespace
        for filename, track in release.tracks.items():
            if track.artists != track.strip_whitespace_artists():
                violations.add(
                    Violation(
                        ViolationType.ARTIST_WHITESPACE,
                        "File '{0}' has leading/trailing whitespace in its Artist(s)"
                        .format(filename)))

        for filename, track in release.tracks.items():
            if track.release_artists != track.strip_whitespace_release_artists(
            ):
                violations.add(
                    Violation(
                        ViolationType.RELEASE_ARTIST_WHITESPACE,
                        "File '{0}' has leading/trailing whitespace in its Album/Release Artist(s)"
                        .format(filename)))

        for filename, track in release.tracks.items():
            if track.date != track.strip_whitespace_date():
                violations.add(
                    Violation(
                        ViolationType.DATE_WHITESPACE,
                        "File '{0}' has leading/trailing whitespace in its Year/Date"
                        .format(filename)))

        for filename, track in release.tracks.items():
            if track.release_title != track.strip_whitespace_release_title():
                violations.add(
                    Violation(
                        ViolationType.RELEASE_TITLE_WHITESPACE,
                        "File '{0}' has leading/trailing whitespace in its Album/Release Title"
                        .format(filename)))

        for filename, track in release.tracks.items():
            if track.track_title != track.strip_whitespace_track_title():
                violations.add(
                    Violation(
                        ViolationType.TRACK_TITLE_WHITESPACE,
                        "File '{0}' has leading/trailing whitespace in its Track Title"
                        .format(filename)))

        for filename, track in release.tracks.items():
            if track.genres != track.strip_whitespace_genres():
                violations.add(
                    Violation(
                        ViolationType.GENRE_WHITESPACE,
                        "File '{0}' has leading/trailing whitespace in its Genre(s)"
                        .format(filename)))

        # release date
        if not release.validate_release_date():
            violations.add(
                Violation(
                    ViolationType.DATE_INCONSISTENT,
                    "Release contains blank or inconsistent 'Date' tags"))

        # artists
        if release.blank_artists():
            violations.add(
                Violation(
                    ViolationType.ARTIST_BLANK,
                    "Release contains {0} tracks with missing 'Artist' tags".
                    format(release.blank_artists())))

        # track titles
        if release.blank_track_titles():
            violations.add(
                Violation(
                    ViolationType.TRACK_TITLE_BLANK,
                    "Release contains {0} tracks with missing 'Track Title' tags"
                    .format(release.blank_track_titles())))

        # release artist
        release_artists = release.validate_release_artists()
        if not release_artists:
            violations.add(
                Violation(
                    ViolationType.RELEASE_ARTIST_INCONSISTENT,
                    "Release contains blank or inconsistent 'Album/Release Artist' tags"
                ))

        # if the lastfmcache is present, validate the release artist
        validated_release_artists = release_artists
        if self.lastfm and len(release_artists) == 1:
            validated_release_artists = []
            for artist in release_artists:
                try:
                    validated_release_artist = self.lastfm.get_artist(
                        artist.strip()).artist_name

                    if validated_release_artist != artist:
                        violations.add(
                            Violation(
                                ViolationType.RELEASE_ARTIST_SPELLING,
                                "Incorrectly spelled Album/Release Artist '{0}' (should be '{1}')"
                                .format(artist, validated_release_artist)))

                    validated_release_artists.append(validated_release_artist)
                except LastfmCache.ArtistNotFoundError:
                    violations.add(
                        Violation(
                            ViolationType.ARTIST_LOOKUP,
                            "Lookup failed of release artist '{release_artist}'"
                            .format(release_artist=artist.strip())))

        # release title
        release_title = release.validate_release_title()
        if not release_title:
            violations.add(
                Violation(
                    ViolationType.RELEASE_TITLE_INCONSISTENT,
                    "Release contains blank or inconsistent 'Album/Release Title' tags"
                ))

        bracket_pairs = [["[", "]"], ["(", ")"], ["{", "}"]]

        if release_title:
            # check if "[Source]" is contained in the release title
            for source in ReleaseSource:
                for brackets in bracket_pairs:
                    curr_source = "{0}{1}{2}".format(brackets[0], source.value,
                                                     brackets[1])
                    if curr_source.lower() in release_title.lower():
                        violations.add(
                            Violation(
                                ViolationType.RELEASE_TITLE_SOURCE,
                                "Release title contains source {0}".format(
                                    curr_source)))

            # check if the release title ends with a space and a source name, without brackets
            for source in [x for x in ReleaseSource]:
                if release_title.lower().endswith(" {0}".format(
                        source.value.lower())):
                    violations.add(
                        Violation(
                            ViolationType.RELEASE_TITLE_SOURCE,
                            "Release title ends with source {0}".format(
                                source.value)))

            # check if "[Category]" is contained in the release title
            for category in ReleaseCategory:
                for brackets in bracket_pairs:
                    curr_category = "{0}{1}{2}".format(brackets[0],
                                                       category.value,
                                                       brackets[1])
                    if curr_category.lower() in release_title.lower():
                        violations.add(
                            Violation(
                                ViolationType.RELEASE_TITLE_CATEGORY,
                                "Release title contains category {0}".format(
                                    curr_category)))

            # check if the release title ends with a space and a category name, without brackets (except Album)
            for category in [
                    x for x in ReleaseCategory
                    if x is not ReleaseCategory.ALBUM
            ]:
                if release_title.lower().endswith(" {0}".format(
                        category.value.lower())):
                    violations.add(
                        Violation(
                            ViolationType.RELEASE_TITLE_CATEGORY,
                            "Release title ends with category {0}".format(
                                category.value)))

        # lastfm artist validations
        if self.lastfm and release_title and len(validated_release_artists):
            # extract (edition info) from release titles
            release_title, _ = split_release_title(
                normalize_release_title(release_title))

            flattened_artist = flatten_artists(validated_release_artists)
            lastfm_release = None

            try:
                lastfm_release = self.lastfm.get_release(
                    flattened_artist, release_title)
            except LastfmCache.ReleaseNotFoundError as e:
                logging.getLogger(__name__).error(e)

            if lastfm_release:
                # release title
                if lastfm_release.release_name != release_title and \
                        ReleaseValidator.__lastfm_can_fix_release_title(release_title, lastfm_release.release_name):
                    violations.add(
                        Violation(
                            ViolationType.RELEASE_TITLE_SPELLING,
                            "Incorrectly spelled Album/Release name '{0}' (should be '{1}')"
                            .format(release_title,
                                    lastfm_release.release_name)))

                # dates
                if lastfm_release.release_date:
                    date = next(iter(release.tracks.values())).date
                    if lastfm_release.release_date != date and \
                            (not date or len(lastfm_release.release_date) >= len(date)):
                        violations.add(
                            Violation(
                                ViolationType.DATE_INCORRECT,
                                "Incorrect Release Date '{0}' (should be '{1}')"
                                .format(date, lastfm_release.release_date)))

                # tags/genres (only fail if 0-1 genres - i.e. lastfm tags have never been applied)
                release_genres = release.validate_genres()
                lastfm_tags = self.__get_lastfm_tags(
                    release_title, validated_release_artists)
                if len(release_genres) < 2 <= len(lastfm_tags):
                    violations.add(
                        Violation(
                            ViolationType.BAD_GENRES,
                            "Bad release genres: [{0}] (should be [{1}])".
                            format(", ".join(release_genres),
                                   ", ".join(lastfm_tags))))

                # match and validate track titles (intersection only)
                if self.lastfm_track_title_validation:
                    for track in release.tracks.values():
                        if track.track_number in lastfm_release.tracks:
                            lastfm_title = normalize_track_title(
                                lastfm_release.tracks[
                                    track.track_number].track_name)
                            if not track.track_title or track.track_title.lower(
                            ) != lastfm_title.lower():
                                violations.add(
                                    Violation(
                                        ViolationType.INCORRECT_TRACK_TITLE,
                                        "Incorrect track title '{0}' should be: '{1}'"
                                        .format(track.track_title,
                                                lastfm_title)))

            # track artists
            for track in release.tracks.values():
                for artist in track.artists:
                    while True:
                        try:
                            validated_artist = self.lastfm.get_artist(
                                normalize_artist_name(artist)).artist_name
                            if validated_artist != artist:
                                violations.add(
                                    Violation(
                                        ViolationType.TRACK_ARTIST_SPELLING,
                                        "Incorrectly spelled Track Artist '{0}' (should be '{1}')"
                                        .format(artist, validated_artist)))
                            break
                        except LastfmCache.ArtistNotFoundError:  # as e:
                            # violations.add(str(e))
                            break
                        except LastfmCache.LastfmCacheError:
                            time.sleep(1)

            # release artists
            for track in release.tracks.values():
                for artist in track.release_artists:
                    while True:
                        try:
                            validated_artist = self.lastfm.get_artist(
                                normalize_artist_name(artist)).artist_name
                            if validated_artist != artist:
                                violations.add(
                                    Violation(
                                        ViolationType.RELEASE_ARTIST_SPELLING,
                                        "Incorrectly spelled Release Artist '{0}' (should be '{1}')"
                                        .format(artist, validated_artist)))
                            break
                        except LastfmCache.ArtistNotFoundError:  # as e:
                            # violations.add(str(e))
                            break
                        except LastfmCache.LastfmCacheError:
                            time.sleep(1)

        validated_track_numbers = release.validate_track_numbers()
        if validated_track_numbers:
            flattened_track_nums = []
            for disc in validated_track_numbers:
                flattened_track_nums.append(
                    "\nDisc " + str(disc) + ": " +
                    ",".join(str(i) for i in validated_track_numbers[disc]))
            violations.add(
                Violation(
                    ViolationType.MISSING_TRACKS,
                    "Release does not have a full set of tracks:{0}".format(
                        "".join(flattened_track_nums))))

        validated_total_tracks = release.validate_total_tracks()
        for disc in validated_total_tracks:
            violations.add(
                Violation(
                    ViolationType.TOTAL_TRACKS_INCONSISTENT,
                    "Release disc {0} has blank, inconsistent or incorrect 'Total Tracks' tags"
                    .format(disc)))

        # disc number
        validated_disc_numbers = release.validate_disc_numbers()
        if validated_disc_numbers:
            violations.add(
                Violation(
                    ViolationType.MISSING_DISCS,
                    "Release does not have a full set of discs: {0}".format(
                        ", ".join(str(i) for i in validated_disc_numbers))))

        # total discs
        if not release.validate_total_discs():
            violations.add(
                Violation(ViolationType.TOTAL_DISCS_INCONSISTENT,
                          "Release has incorrect 'Total Discs' tags"))

        # file type
        if len(release.get_tag_types()) != 1:
            violations.add(
                Violation(
                    ViolationType.TAG_TYPES_INCONSISTENT,
                    "Release has inconsistent tag types: {0}".format(", ".join(
                        [str(x) for x in release.get_tag_types()]))))

        # bitrate - CBR/VBR/Vx/APS/APE
        if len(release.get_codecs()) != 1:
            violations.add(
                Violation(
                    ViolationType.CODECS_INCONSISTENT,
                    "Release has inconsistent codecs: [{0}]".format(", ".join(
                        release.get_codecs()))))

        if len(unique([int(x / 1000)
                       for x in release.get_cbr_bitrates()])) > 1:
            violations.add(
                Violation(
                    ViolationType.CBR_INCONSISTENT,
                    "Release has inconsistent CBR bitrates: {0}".format(
                        ", ".join([str(x)
                                   for x in release.get_cbr_bitrates()]))))

        # track titles
        for filename in release.tracks:
            correct_filename = release.tracks[filename].get_filename(
                release.is_va())
            if correct_filename and filename != correct_filename:
                violations.add(
                    Violation(
                        ViolationType.FILENAME,
                        "Invalid filename: {0} - should be '{1}'".format(
                            filename, correct_filename)))

        # forbidden comment substrings
        for track in release.tracks.values():
            if not track.comment:
                continue
            for substr in self.forbidden_comment_substrings:
                if substr in track.comment.lower():
                    violations.add(
                        Violation(
                            ViolationType.COMMENT_SUBSTRING,
                            "Invalid comment: contains forbidden substring '{0}'"
                            .format(substr)))

        release.num_violations = len(violations)

        return list(violations)