From c1b20675c11d2b0745d41038896b79a01049ac7b Mon Sep 17 00:00:00 2001 From: Colin Date: Thu, 30 Nov 2023 12:52:55 +0000 Subject: [PATCH] sane-tag-music: handle more path schemas --- .../sane-scripts/src/sane-tag-music | 77 ++++++++++++++----- 1 file changed, 58 insertions(+), 19 deletions(-) diff --git a/pkgs/additional/sane-scripts/src/sane-tag-music b/pkgs/additional/sane-scripts/src/sane-tag-music index cdd10966..55b37800 100755 --- a/pkgs/additional/sane-scripts/src/sane-tag-music +++ b/pkgs/additional/sane-scripts/src/sane-tag-music @@ -70,9 +70,22 @@ class Tags: def promote_albumartist(self) -> None: """ - if there's only an album artist, and no track artist, turn the album artist into the track artist. - otherise: no-op + 1. if there's only an album artist, and no track artist, turn the album artist into the track artist. + 2. if the artist and album artist are nearly identical, try to merge them. """ + unomitable = 'abcdefghijklmnopqrstuvwxyz0123456789' + if len(self.artist) == len(self.albumartist) == 1: + filtered_artist = [i for i in self.artist[0] if i.lower() in unomitable] + filtered_albumartist = [i for i in self.albumartist[0] if i.lower() in unomitable] + if filtered_artist == filtered_albumartist: + # arist & album artist are nearly identical: + # probably guessed one of them from filename, which was lacking certain symbols of the actual artist. + # recover whichever of these fields had the fewer characters removed (i.e. is longest) + if len(self.artist[0]) > len(self.albumartist[0]): + self.artist = self.albumartist = self.artist + else: + self.artist = self.albumartist = self.albumartist + if self.artist == []: self.artist = self.albumartist self.albumartist = [] @@ -80,33 +93,58 @@ class Tags: def rewrite_singles(self) -> None: """ idiom is for singles to belong to self-titled album. else each artist's singles get merged into one massive album """ if self.album == ["Singles"]: - self.album = self.artist + if self.albumartist: + self.album = self.albumartist + else: + self.album = self.artist @staticmethod def from_path(p: str) -> 'Tags': """ path cases: - artist/album/track - - label/artist - album/track + - label/artist - album/track (in this case "label" is ignored) track naming: - could have many fields. the title will always be last. trackno could be embedded or not. - - artist - album - trackno title + - title (handled) + - artist - track (handled) + - trackno - track (handled) + - artist - album - trackno title (not handled) + additionally, clean the path before this logic: + - ./artist/album/track -> artist/album/track """ - comps = p.split('/') tags = Tags() + def parse_track(track: str) -> None: + track = os.path.splitext(track)[0] + track_parts = [p.strip() for p in track.split('-')] + if len(track_parts) == 1: + tags.title = [track] + elif len(track_parts) == 2: + if tags.albumartist and track_parts[0].lower() == tags.albumartist[0].lower(): + tags.title = [track_parts[1]] + elif all(l in '0123456789' for l in track_parts[0]): + tags.tracknumber = [track_parts[0].lstrip('0')] + tags.title = [track_parts[1]] + + def parse_album(album: str) -> None: + album_parts = [p.strip() for p in album.split('-')] + if len(album_parts) == 1: + # artist/album/track + tags.album = [album] + elif len(album_parts) == 2: + # artist/artist-album/track + tags.albumartist = [album_parts[0]] + tags.album = [album_parts[1]] + + comps = [c for c in p.split('/') if c != '.'] + if len(comps) == 3: tags.albumartist = [comps[0]] - album_part = comps[1].split('-') - if len(album_part) == 2: - # artist/artist-album/track - tags.albumartist, tags.album = [album_part[0].strip()], [album_part[1].strip()] - else: - # artist/album/track - tags.album = [comps[1]] - track_part = comps[2].split('-') - if len(track_part) == 1: - tags.title = [os.path.splitext(comps[2])[0]] - # TODO: handle the else case + parse_album(comps[1]) + parse_track(comps[2]) + elif len(comps) == 2: + tags.albumartist = [comps[0]] + parse_track(comps[1]) return tags @@ -218,7 +256,7 @@ def main(): logging.getLogger().setLevel(logging.INFO) parser = argparse.ArgumentParser(description="augment music tags based on library path") - parser.add_argument("path", help="relative path to a file to tag") + parser.add_argument("path", nargs="+", help="relative path to a file to tag") parser.add_argument('--dry-run', action='store_true') parser.add_argument('--verbose', action='store_true') @@ -228,7 +266,8 @@ def main(): logging.getLogger().setLevel(logging.DEBUG) tagger = Tagger(dry_run=args.dry_run) - tagger.tag_file_or_tree(args.path) + for p in args.path: + tagger.tag_file_or_tree(p) if __name__ == '__main__': main()