sane-tag-music: support directory/tree operations

This commit is contained in:
2023-11-29 12:52:35 +00:00
parent b2806bd649
commit 6edc6841bf

View File

@@ -1,5 +1,8 @@
#!/usr/bin/env nix-shell #!/usr/bin/env nix-shell
#!nix-shell -i python3 -p "python3.withPackages (ps: [ ps.mutagen ])" #!nix-shell -i python3 -p "python3.withPackages (ps: [ ps.mutagen ])"
#
# mutagen docs:
# - <https://mutagen.readthedocs.io/en/latest/>
from dataclasses import dataclass from dataclasses import dataclass
@@ -41,16 +44,28 @@ class Tags:
return f"artist:{self.artist}/{self.albumartist}, album:{self.album}, title:{self.title}, trackno:{self.tracknumber}" return f"artist:{self.artist}/{self.albumartist}, album:{self.album}, title:{self.title}, trackno:{self.tracknumber}"
def union(self, fallback: 'Tags') -> 'Tags': def union(self, fallback: 'Tags') -> 'Tags':
def merge_field(first: list[str], second: list[str]) -> list[str]: def merge_field(primary: list[str], secondary: list[str]) -> list[str]:
first_lower = [i.lower() for i in first] # primary_lower = [i.lower() for i in primary]
return first + [i for i in second if i.lower() not in first_lower] # return primary + [i for i in secondary if i.lower() not in primary_lower]
return primary or secondary
artist=merge_field(self.artist, fallback.artist)
album=merge_field(self.album, fallback.album)
title=merge_field(self.title, fallback.title)
albumartist=merge_field(self.albumartist, fallback.albumartist)
tracknumber=merge_field(self.tracknumber, fallback.tracknumber)
if artist == albumartist:
# if extraneous, then keep the album artist whatever it originally was
albumartist = self.albumartist
return Tags( return Tags(
artist=merge_field(self.artist, fallback.artist), artist=artist,
album=merge_field(self.album, fallback.album), album=album,
title=merge_field(self.title, fallback.title), title=title,
albumartist=merge_field(self.albumartist, fallback.albumartist), albumartist=albumartist,
tracknumber=merge_field(self.tracknumber, fallback.tracknumber), tracknumber=tracknumber,
) )
def promote_albumartist(self) -> None: def promote_albumartist(self) -> None:
@@ -75,7 +90,7 @@ class Tags:
comps = p.split('/') comps = p.split('/')
tags = Tags() tags = Tags()
if len(comps) == 3: if len(comps) == 3:
tags.albumartist = comps[0] tags.albumartist = [comps[0]]
album_part = comps[1].split('-') album_part = comps[1].split('-')
if len(album_part) == 2: if len(album_part) == 2:
# artist/artist-album/track # artist/artist-album/track
@@ -85,7 +100,7 @@ class Tags:
tags.album = [comps[1]] tags.album = [comps[1]]
track_part = comps[2].split('-') track_part = comps[2].split('-')
if len(track_part) == 1: if len(track_part) == 1:
tags.title = [comps[2]] tags.title = [os.path.splitext(comps[2])[0]]
# TODO: handle the else case # TODO: handle the else case
return tags return tags
@@ -104,6 +119,12 @@ class AudioFile:
else: else:
self.muta = None self.muta = None
@staticmethod
def new(path_: str) -> 'AudioFile':
f = AudioFile(path_)
if f.muta is not None:
return f
def tags_on_disk(self) -> Tags: def tags_on_disk(self) -> Tags:
return Tags( return Tags(
artist=self.muta.get('artist', []) if self.muta else [], artist=self.muta.get('artist', []) if self.muta else [],
@@ -131,7 +152,11 @@ class Tagger:
self.dry_run = dry_run self.dry_run = dry_run
def tag_file(self, path_: str) -> None: def tag_file(self, path_: str) -> None:
file_ = AudioFile(path_) file_ = AudioFile.new(path_)
if not file_:
logger.debug(f"skipping unsupported file: {path_}")
return
old_tags = file_.tags_on_disk() old_tags = file_.tags_on_disk()
path_tags = Tags.from_path(path_) path_tags = Tags.from_path(path_)
@@ -147,6 +172,17 @@ class Tagger:
if self.guard_dry_run("writing tags"): if self.guard_dry_run("writing tags"):
file_.write_tags(new_tags) file_.write_tags(new_tags)
def tag_file_tree(self, root: str) -> None:
for dir_, subdirs, files_ in os.walk(root):
for f in files_:
self.tag_file(os.path.join(dir_, f))
def tag_file_or_tree(self, path_: str) -> None:
if os.path.isdir(path_):
self.tag_file_tree(path_)
else:
self.tag_file(path_)
def show_tagdif(self, path_: str, old_tags: Tags, new_tags: Tags): def show_tagdif(self, path_: str, old_tags: Tags, new_tags: Tags):
logger.info(f"updating tags for {path_}") logger.info(f"updating tags for {path_}")
logger.info(f" {old_tags}") logger.info(f" {old_tags}")
@@ -183,7 +219,7 @@ def main():
logging.getLogger().setLevel(logging.DEBUG) logging.getLogger().setLevel(logging.DEBUG)
tagger = Tagger(dry_run=args.dry_run) tagger = Tagger(dry_run=args.dry_run)
tagger.tag_file(args.path) tagger.tag_file_or_tree(args.path)
if __name__ == '__main__': if __name__ == '__main__':
main() main()