From f7ff789d81f3c35041df9f71a92299aaad99e85c Mon Sep 17 00:00:00 2001 From: Lucas Vivo Date: Thu, 8 Oct 2026 16:17:44 +0200 Subject: [PATCH] translate edit genre metadata to python ads asd --- .../metadata_genre_remove_from_file.py | 192 ++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 audiobook_metadata/metadata_genre_remove_from_file.py diff --git a/audiobook_metadata/metadata_genre_remove_from_file.py b/audiobook_metadata/metadata_genre_remove_from_file.py new file mode 100644 index 0000000..9ec87c6 --- /dev/null +++ b/audiobook_metadata/metadata_genre_remove_from_file.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 + +import argparse +import re +import subprocess +from pathlib import Path +from datetime import datetime + + +def get_genre(file_path): + try: + result = subprocess.run( + ["kid3-cli", "-c", "get genre", str(file_path)], + capture_output=True, + text=True, + check=False, + ) + return result.stdout.strip() + except Exception: + return "" + + +def set_genre(file_path, genre): + subprocess.run( + [ + "kid3-cli", + "-c", + "tag 2", + "-c", + f"set genre \"{genre}\"", + "-c", + "save", + str(file_path), + ], + check=False, + ) + + +def clean_genre(genre): + + genre = re.sub(r'(?i)^audio\s*books?$', '', genre) + genre = re.sub(r'(?i)^audiobooks?;?$', '', genre) + genre = re.sub(r'(?i)\b(hörb(uch|ücher)|hoerb(uch|uecher))\b', '', genre) + genre = re.sub(r'(?i)^(hörspiele?|hoerspiele?)$', '', genre) + genre = re.sub(r'(?i)^spoken\s*word$', '', genre) + genre = re.sub(r'(?i)gesprochene\s*inhalte$', '', genre) + genre = re.sub(r'(?i)^speech$', '', genre) + genre = re.sub(r'Fanasie', 'Fantasie', genre) + genre = re.sub(r'Mit Videospiel-?', 'Videospielbezug', genre) + genre = re.sub(r'(?i)nonfiction', 'NonFiction', genre) + + genre = re.sub(r'(?i)^speech$', '', genre) + genre = re.sub(r'(?i)^vocal$', '', genre) + genre = re.sub(r'(?i)^retail$', '', genre) + genre = re.sub(r'(?i)^other$', '', genre) + genre = re.sub(r'(?i)^misc$', '', genre) + + genre = re.sub(r'(?i)^(sci[- ]?fi|sf)$', 'Science Fiction', genre) + genre = re.sub(r'(?i)^\(?science fiction\)?$', 'Science Fiction', genre) + genre = re.sub(r'Thrillers', 'Thriller', genre) + genre = re.sub(r"(?i)\bchildren'?s audiobooks?\b", "", genre) + genre = re.sub(r'^-\s*|\s*-$', '', genre) + genre = re.sub(r'(?i)Engelske\s*\sLydbånd', '', genre) + genre = re.sub(r'^FIC028000.*$', '', genre) + genre = re.sub(r'^800.*$', '', genre) + genre = re.sub(r'^Fantastic Beasts.*$', '', genre) + genre = re.sub(r'^and thei.*$', '', genre) + genre = re.sub(r'^those you.*$', '', genre) + # Normalize separators + genre = re.sub( + r'\s*[,&:/]\s*', + ';', + genre + ) + + genre = re.sub( + r'\s*;\s*', + '; ', + genre + ) + + # Trim punctuation / whitespace + genre = re.sub( + r'^[\s,;:\-]+', + '', + genre + ) + + genre = re.sub( + r'[\s,;\-]+$', + '', + genre + ) + + genre = genre.strip() + genre = genre.lstrip() + + return genre + +def find_audio_files(library): + extensions = {".mp3", ".m4a", ".m4b"} + + return [ + p for p in Path(library).rglob("*") + if p.is_file() and p.suffix.lower() in extensions + ] + + +def main(): + parser = argparse.ArgumentParser( + description="Audiobook Genre Cleanup" + ) + + parser.add_argument( + "library", + help="Library path" + ) + + parser.add_argument( + "--delete", + action="store_true", + help="Update tags instead of reporting only" + ) + + parser.add_argument( + "--report", + default="/home/lucas/audiobook_genre_cleanup.txt", + help="Report file" + ) + + args = parser.parse_args() + + files = find_audio_files(args.library) + + total = len(files) + processed = 0 + matches = 0 + modified = 0 + + with open(args.report, "w", encoding="utf-8") as report: + report.write("Audiobook Genre Cleanup Report\n") + report.write(f"Generated: {datetime.now()}\n") + report.write(f"Library: {args.library}\n") + report.write(f"Total files: {total}\n\n") + + for file in files: + processed += 1 + + if processed % 100 == 0: + print(f"Progress: {processed}/{total} files processed") + + genre = get_genre(file) + + if not genre: + continue + + cleaned = clean_genre(genre) + + if genre != cleaned: + matches += 1 + status = "MATCH" + + if args.delete: + set_genre(file, cleaned) + modified += 1 + status = "UPDATED" + + line = ( + f'[{processed}/{total}] [{status}] ' + f'{file} | OLD="{genre}" | NEW="{cleaned}"' + ) + + print(line) + + with open(args.report, "a", encoding="utf-8") as report: + report.write(line + "\n") + + with open(args.report, "a", encoding="utf-8") as report: + report.write("\n========== SUMMARY ==========\n") + report.write(f"Files scanned : {total}\n") + report.write(f"Matches found : {matches}\n") + report.write(f"Files updated : {modified}\n") + + print("\n========== SUMMARY ==========") + print(f"Files scanned : {total}") + print(f"Matches found : {matches}") + print(f"Files updated : {modified}") + print(f"Report written to: {args.report}") + + +if __name__ == "__main__": + main() \ No newline at end of file