192 lines
4.9 KiB
Python
192 lines
4.9 KiB
Python
#!/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() |