diff --git a/app/core/download_manager.py b/app/core/download_manager.py index 5d556d0..4c93e04 100644 --- a/app/core/download_manager.py +++ b/app/core/download_manager.py @@ -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 diff --git a/config.json b/config.json index 39bb785..4676091 100644 --- a/config.json +++ b/config.json @@ -1,5 +1,5 @@ { - "app_name": "Application", + "app_name": "Youtube MP3 Downloader", "app_os": "Windows", "app_version": "1.0.0", "architecture": "x64", diff --git a/data/assets/icon.ico b/data/assets/icon.ico deleted file mode 100644 index 2e25305..0000000 Binary files a/data/assets/icon.ico and /dev/null differ diff --git a/main.py b/main.py index 82d60b0..04848d8 100644 --- a/main.py +++ b/main.py @@ -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 - QApplication.setAttribute(Qt.ApplicationAttribute.AA_ShareOpenGLContexts, True) - - main_manager: MainManager = MainManager.get_instance() - theme_manager = main_manager.get_theme_manager() - settings_manager = main_manager.get_settings_manager() + 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 - app: QApplication = QApplication(sys.argv) - app.setStyleSheet(theme_manager.get_sheet()) - app.setApplicationName(settings_manager.get_config("app_name")) - app.setWindowIcon(QIcon(paths.get_asset_path("icon"))) + 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: - splash = SplashScreen(preload_function=lambda callback: preload_application(callback, splash)) - splash.show_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 - - if preloaded_window: - preloaded_window.show() - else: - window = MainWindow() - window.show() + 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 - - window = preloaded_window if preloaded_window else MainWindow() - window.show() - + 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) \ No newline at end of file + sys.exit(main()) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 039ee52..60c1aed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ pyinstaller python-dotenv requests yt-dlp -mutagen \ No newline at end of file +mutagen +pillow \ No newline at end of file