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: if track_album:
tags.add(TALB(encoding=3, text=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) 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) renamed_file = self._rename_output_file(file_path, track_title or fallback_title, track_artist)
if renamed_file is not None: if renamed_file is not None:
self.progress_text.emit(renamed_file.stem) self.progress_text.emit(renamed_file.stem)
@ -441,84 +449,10 @@ class DownloadManager(QObject):
return False return False
track = self.track_list[index] track = self.track_list[index]
clean_mp3_title = mp3_title.strip() or track.get("title", "") track["mp3_title"] = mp3_title.strip()
clean_artist = artist.strip() track["artist"] = artist.strip()
clean_album = album.strip() track["album"] = album.strip()
clean_cover = cover_path.strip() track["cover_path"] = 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
self.list_changed.emit(self.get_tracks()) self.list_changed.emit(self.get_tracks())
return True return True

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

85
main.py
View File

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

View File

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