Fix cover metadata bugs: concurrency locks and FFmpeg video stream stripping

This commit is contained in:
joel
2026-07-20 17:26:26 +02:00
parent 293aa5a907
commit c2d050ed71
2 changed files with 48 additions and 26 deletions
+22 -2
View File
@@ -115,6 +115,9 @@ def _build_ffmpeg_cmd(
else: else:
cmd.append("-n") cmd.append("-n")
# Keine Video/Bilder-Streams übernehmen (verhindert Konflikte mit eigenen Covern)
cmd.append("-vn")
cmd.append(str(output_path)) cmd.append(str(output_path))
return cmd return cmd
@@ -190,6 +193,8 @@ def convert_file_entry(
audio_settings: dict | None = None, audio_settings: dict | None = None,
threads: int = 0, threads: int = 0,
progress_callback: Callable[[int, int, str], None] | None = None, progress_callback: Callable[[int, int, str], None] | None = None,
cover_data: bytes | None = None,
cover_mime: str = "image/jpeg",
) -> tuple[int, str, str | None]: ) -> tuple[int, str, str | None]:
"""Konvertiert eine Datei und schreibt die Metadaten. """Konvertiert eine Datei und schreibt die Metadaten.
@@ -227,7 +232,6 @@ def convert_file_entry(
# ID3-Metadaten nur für MP3 schreiben # ID3-Metadaten nur für MP3 schreiben
if output_format == "mp3": if output_format == "mp3":
cover_path = Path(meta["cover"]) if meta.get("cover") else None
apply_id3_tags( apply_id3_tags(
mp3_path=output_file, mp3_path=output_file,
title=title, title=title,
@@ -235,7 +239,8 @@ def convert_file_entry(
album=meta.get("album", ""), album=meta.get("album", ""),
track_number=index + 1, track_number=index + 1,
year=meta.get("year", ""), year=meta.get("year", ""),
cover_path=cover_path, cover_data=cover_data,
cover_mime=cover_mime,
) )
return (index, f"{title}.{output_format}", None) return (index, f"{title}.{output_format}", None)
@@ -266,6 +271,19 @@ def run_batch_conversion(
successful_files = [] successful_files = []
errors = [] errors = []
# Cover-Bild einmalig in den Arbeitsspeicher laden, um Dateisperren (Locking)
# bei parallelem Zugriff durch Windows Defender/Antivirus zu vermeiden
cover_data = None
cover_mime = "image/jpeg"
cover_path_str = meta.get("cover")
if cover_path_str:
cp = Path(cover_path_str)
if cp.exists():
if cp.suffix.lower() == ".png":
cover_mime = "image/png"
with open(cp, "rb") as f:
cover_data = f.read()
with ThreadPoolExecutor(max_workers=max_workers) as executor: with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = { futures = {
executor.submit( executor.submit(
@@ -280,6 +298,8 @@ def run_batch_conversion(
audio_settings=audio_settings, audio_settings=audio_settings,
threads=0, threads=0,
progress_callback=progress_callback, progress_callback=progress_callback,
cover_data=cover_data,
cover_mime=cover_mime,
): idx ): idx
for idx, entry in enumerate(entries) for idx, entry in enumerate(entries)
} }
+21 -19
View File
@@ -51,6 +51,8 @@ def extract_event_from_folder(file_path: Path) -> tuple[str, str]:
return "", "" return "", ""
from mutagen.id3 import ID3, APIC, TIT2, TPE1, TALB, TRCK, TDRC
def apply_id3_tags( def apply_id3_tags(
mp3_path: Path, mp3_path: Path,
title: str, title: str,
@@ -58,31 +60,31 @@ def apply_id3_tags(
album: str, album: str,
track_number: int, track_number: int,
year: str, year: str,
cover_path: Path | None = None, cover_data: bytes | None = None,
cover_mime: str = "image/jpeg",
) -> None: ) -> None:
"""Schreibt ID3-Metadaten und optional ein Cover in die MP3-Datei.""" """Schreibt ID3-Metadaten und optional ein Cover in die MP3-Datei."""
audiofile = EasyID3(str(mp3_path)) try:
audiofile["title"] = title tags = ID3(str(mp3_path))
audiofile["artist"] = artist or "Unbekannt" except Exception:
audiofile["album"] = album or "Unbekanntes Album" tags = ID3()
audiofile["tracknumber"] = str(track_number)
audiofile["date"] = year
audiofile.save()
if cover_path and cover_path.exists(): tags.add(TIT2(encoding=3, text=title))
audio_id3 = ID3(str(mp3_path)) tags.add(TPE1(encoding=3, text=artist or "Unbekannt"))
mime = "image/jpeg" tags.add(TALB(encoding=3, text=album or "Unbekanntes Album"))
suffix = cover_path.suffix.lower() tags.add(TRCK(encoding=3, text=str(track_number)))
if suffix == ".png": if year:
mime = "image/png" tags.add(TDRC(encoding=3, text=year))
with open(cover_path, "rb") as img:
audio_id3.add( if cover_data:
tags.add(
APIC( APIC(
encoding=3, encoding=3,
mime=mime, mime=cover_mime,
type=3, type=3,
desc="Cover", desc="Cover",
data=img.read(), data=cover_data,
) )
) )
audio_id3.save()
tags.save(str(mp3_path), v2_version=3)