generated from LouisMazin/PythonApplicationTemplate
83 lines
3.5 KiB
Python
83 lines
3.5 KiB
Python
from pathlib import Path
|
|
import re
|
|
from mutagen.id3 import ID3, TALB, TIT2, TPE1, delete as id3_delete
|
|
|
|
class MetadataManager:
|
|
def apply_mp3_tags(self, file_path: Path, metadata: dict, fallback_title: str) -> None:
|
|
track_title = str(metadata.get("mp3_title") or metadata.get("title") or fallback_title).strip()
|
|
track_artist = str(metadata.get("artist", "")).strip()
|
|
track_album = str(metadata.get("album", "")).strip()
|
|
track_cover = str(metadata.get("cover_path", "")).strip()
|
|
|
|
try:
|
|
id3_delete(str(file_path))
|
|
except Exception:
|
|
pass
|
|
|
|
tags = ID3()
|
|
tags.delall("TIT2")
|
|
tags.add(TIT2(encoding=3, text=track_title or fallback_title))
|
|
tags.delall("TPE1")
|
|
if track_artist:
|
|
tags.add(TPE1(encoding=3, text=track_artist))
|
|
tags.delall("TALB")
|
|
if track_album:
|
|
tags.add(TALB(encoding=3, text=track_album))
|
|
tags.save(str(file_path), v2_version=4)
|
|
|
|
# Use eyeD3 for cover art (Windows Explorer compatibility)
|
|
if track_cover:
|
|
try:
|
|
import eyed3
|
|
from PIL import Image
|
|
import io
|
|
audiofile = eyed3.load(str(file_path))
|
|
if audiofile is not None:
|
|
if audiofile.tag is None:
|
|
audiofile.initTag()
|
|
cover_file = Path(track_cover)
|
|
if cover_file.exists() and cover_file.is_file():
|
|
# Always convert to JPEG and resize to 999x999
|
|
try:
|
|
img = Image.open(cover_file)
|
|
img = img.convert("RGB")
|
|
img = img.resize((999, 999), Image.LANCZOS)
|
|
buf = io.BytesIO()
|
|
img.save(buf, format="JPEG")
|
|
image_data = buf.getvalue()
|
|
mime_type = "image/jpeg"
|
|
except Exception as e:
|
|
print(f"[eyeD3] Failed to process image: {e}")
|
|
image_data = cover_file.read_bytes()
|
|
mime_type = "image/jpeg"
|
|
audiofile.tag.images.set(3, image_data, mime_type, u"Cover")
|
|
audiofile.tag.version = (2, 3, 0)
|
|
audiofile.tag.save(version=(2, 3, 0))
|
|
except Exception as e:
|
|
print(f"[eyeD3] Failed to embed cover: {e}")
|
|
|
|
def sanitize_filename_part(self, value: str) -> str:
|
|
clean_value = re.sub(r"[<>:\"/\\|?*]", "_", value).strip()
|
|
clean_value = re.sub(r"\s+", " ", clean_value)
|
|
return clean_value.strip(". ")
|
|
|
|
def rename_output_file(self, file_path: Path, title: str, artist: str) -> Path:
|
|
safe_title = self.sanitize_filename_part(title)
|
|
safe_artist = self.sanitize_filename_part(artist)
|
|
if safe_artist and safe_title:
|
|
base_name = f"{safe_artist} - {safe_title}"
|
|
else:
|
|
base_name = safe_title or self.sanitize_filename_part(file_path.stem)
|
|
target_path = file_path.with_name(f"{base_name}{file_path.suffix}")
|
|
if target_path == file_path:
|
|
return file_path
|
|
counter = 2
|
|
while target_path.exists():
|
|
target_path = file_path.with_name(f"{base_name} ({counter}){file_path.suffix}")
|
|
counter += 1
|
|
try:
|
|
file_path.rename(target_path)
|
|
return target_path
|
|
except Exception:
|
|
return file_path
|