YoutubeDownloader/app/core/metadata_manager.py
2026-03-18 19:28:41 +01:00

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