This commit is contained in:
Louis Mazin 2026-03-18 17:39:41 +01:00
parent 9cbe5d4de0
commit 7b29ea57b7
5 changed files with 88 additions and 140 deletions

View File

@ -141,33 +141,41 @@ class AudioDownloadWorker(QThread):
if track_album:
tags.add(TALB(encoding=3, text=track_album))
tags.delall("APIC")
if track_cover:
cover_file = Path(track_cover)
if cover_file.exists() and cover_file.is_file():
mime_type = "image/jpeg"
cover_data = None
if cover_file.suffix.lower() in (".png", ".webp"):
try:
from PIL import Image
import io
img = Image.open(cover_file)
buf = io.BytesIO()
img.convert("RGB").save(buf, format="JPEG")
cover_data = buf.getvalue()
mime_type = "image/jpeg"
except Exception:
with open(cover_file, "rb") as stream:
cover_data = stream.read()
mime_type = "image/png" if cover_file.suffix.lower() == ".png" else "image/webp"
else:
with open(cover_file, "rb") as stream:
cover_data = stream.read()
if cover_data:
tags.add(APIC(encoding=3, mime=mime_type, type=3, desc="Cover", data=cover_data))
tags.save(str(file_path), v2_version=4)
# Use only 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")
# Force ID3v2.3
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}")
renamed_file = self._rename_output_file(file_path, track_title or fallback_title, track_artist)
if renamed_file is not None:
self.progress_text.emit(renamed_file.stem)
@ -441,84 +449,10 @@ class DownloadManager(QObject):
return False
track = self.track_list[index]
clean_mp3_title = mp3_title.strip() or track.get("title", "")
clean_artist = artist.strip()
clean_album = album.strip()
clean_cover = cover_path.strip()
# Update in-memory metadata
track["mp3_title"] = clean_mp3_title
track["artist"] = clean_artist
track["album"] = clean_album
track["cover_path"] = clean_cover
# Try to find the file path (assume output dir is known, search for file)
# Use the current filename or try to reconstruct it
output_dir = self.settings_manager.get_config("output_dir")
if not output_dir:
output_dir = "."
output_dir = Path(output_dir)
# Try to find the file by matching artist/title or fallback to most recent
found_file = None
for f in output_dir.glob("*.mp3"):
# Try to match by old artist/title
if f.stem == f"{track.get('artist', '')} - {track.get('mp3_title', '')}":
found_file = f
break
if not found_file:
# fallback: most recent
mp3_files = sorted(output_dir.glob("*.mp3"), key=lambda file_path: file_path.stat().st_mtime, reverse=True)
if mp3_files:
found_file = mp3_files[0]
if found_file and found_file.exists():
# Update ID3 tags
try:
id3_delete(str(found_file))
except Exception:
pass
tags = ID3()
tags.delall("TIT2")
tags.add(TIT2(encoding=3, text=clean_mp3_title or track.get("title", "")))
tags.delall("TPE1")
if clean_artist:
tags.add(TPE1(encoding=3, text=clean_artist))
tags.delall("TALB")
if clean_album:
tags.add(TALB(encoding=3, text=clean_album))
tags.delall("APIC")
if clean_cover:
cover_file = Path(clean_cover)
if cover_file.exists() and cover_file.is_file():
mime_type = "image/jpeg"
if cover_file.suffix.lower() == ".png":
mime_type = "image/png"
elif cover_file.suffix.lower() == ".webp":
mime_type = "image/webp"
with open(cover_file, "rb") as stream:
cover_data = stream.read()
if cover_data:
tags.add(APIC(encoding=3, mime=mime_type, type=3, desc="Cover", data=cover_data))
tags.save(str(found_file), v2_version=4)
# Always rename file to 'artist - title.mp3'
safe_title = self._sanitize_filename_part(clean_mp3_title)
safe_artist = self._sanitize_filename_part(clean_artist)
if safe_artist and safe_title:
base_name = f"{safe_artist} - {safe_title}"
else:
base_name = safe_title or self._sanitize_filename_part(found_file.stem)
target_path = found_file.with_name(f"{base_name}{found_file.suffix}")
if target_path != found_file:
counter = 2
while target_path.exists():
target_path = found_file.with_name(f"{base_name} ({counter}){found_file.suffix}")
counter += 1
try:
found_file.rename(target_path)
except Exception:
pass
track["mp3_title"] = mp3_title.strip()
track["artist"] = artist.strip()
track["album"] = album.strip()
track["cover_path"] = cover_path.strip()
self.list_changed.emit(self.get_tracks())
return True

View File

@ -1,5 +1,5 @@
{
"app_name": "Application",
"app_name": "Youtube MP3 Downloader",
"app_os": "Windows",
"app_version": "1.0.0",
"architecture": "x64",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

41
main.py
View File

@ -24,82 +24,95 @@ def preload_application(progress_callback, splash=None):
try:
main_manager = MainManager.get_instance()
language_manager = main_manager.get_language_manager()
update_manager = main_manager.get_update_manager()
license_manager = main_manager.get_license_manager()
settings_manager = main_manager.get_settings_manager()
# Vérifier la licence uniquement si le système est activé
if settings_manager.get_config("enable_licensing"):
progress_callback(language_manager.get_text("verifying_license"))
license_manager.verify_license()
progress_callback(language_manager.get_text("checking_updates"))
if update_manager.check_for_update(splash, splash):
return False
progress_callback(language_manager.get_text("initializing"))
preloaded_window = MainWindow()
progress_callback(language_manager.get_text("loading_complete"))
return True
except Exception as e:
print(f"Error during preload: {e}")
except Exception:
return False
def main() -> int:
global preloaded_window
try:
QApplication.setAttribute(Qt.ApplicationAttribute.AA_ShareOpenGLContexts, True)
except Exception:
return 0
try:
main_manager: MainManager = MainManager.get_instance()
theme_manager = main_manager.get_theme_manager()
settings_manager = main_manager.get_settings_manager()
except Exception:
return 0
try:
app: QApplication = QApplication(sys.argv)
except Exception:
return 0
try:
app.setStyleSheet(theme_manager.get_sheet())
app.setApplicationName(settings_manager.get_config("app_name"))
app.setWindowIcon(QIcon(paths.get_asset_path("icon")))
except Exception:
return 0
splash_image_path = paths.get_asset_path(settings_manager.get_config("splash_image"))
use_splash = splash_image_path and paths.Path(splash_image_path).exists()
if use_splash:
try:
splash = SplashScreen(preload_function=lambda callback: preload_application(callback, splash))
splash.show_splash()
except Exception as e:
return 0
def on_splash_finished(success):
global preloaded_window
if not success:
app.quit()
return
try:
if preloaded_window:
preloaded_window.show()
else:
window = MainWindow()
window.show()
except Exception:
app.quit()
splash.finished.connect(on_splash_finished)
return app.exec()
else:
def dummy_progress(text):
pass
success = preload_application(dummy_progress)
if not success:
return 0
try:
window = preloaded_window if preloaded_window else MainWindow()
window.show()
except Exception:
return 0
return app.exec()
if __name__ == "__main__":
try:
sys.exit(main())
except KeyboardInterrupt:
sys.exit(0)

View File

@ -5,3 +5,4 @@ python-dotenv
requests
yt-dlp
mutagen
pillow