Fix cover metadata bugs: concurrency locks and FFmpeg video stream stripping
This commit is contained in:
@@ -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
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user