From 5104df5c0b4cc1408f662e0264b199abf8afba84 Mon Sep 17 00:00:00 2001 From: Louis Mazin Date: Wed, 18 Mar 2026 19:28:41 +0100 Subject: [PATCH] nice --- app/core/main_manager.py | 13 ++- app/core/metadata_manager.py | 82 ++++++++++++++ ...download_manager.py => youtube_manager.py} | 102 ++---------------- app/ui/windows/add_music_window.py | 96 ++++++++--------- app/ui/windows/download_list_window.py | 35 +++--- data/lang/en.json | 1 - data/lang/fr.json | 1 - main.py | 3 +- 8 files changed, 162 insertions(+), 171 deletions(-) create mode 100644 app/core/metadata_manager.py rename app/core/{download_manager.py => youtube_manager.py} (85%) diff --git a/app/core/main_manager.py b/app/core/main_manager.py index 643a4d3..eb78748 100644 --- a/app/core/main_manager.py +++ b/app/core/main_manager.py @@ -5,7 +5,8 @@ from app.core.settings_manager import SettingsManager from app.core.alert_manager import AlertManager from app.core.update_manager import UpdateManager from app.core.license_manager import LicenseManager -from app.core.download_manager import DownloadManager +from app.core.youtube_manager import YoutubeManager +from app.core.metadata_manager import MetadataManager from typing import Optional @@ -24,7 +25,8 @@ class MainManager: self.alert_manager: AlertManager = AlertManager(self.language_manager, self.theme_manager) self.update_manager: UpdateManager = UpdateManager(self.settings_manager, self.language_manager, self.alert_manager) self.license_manager: LicenseManager = LicenseManager(self.settings_manager) - self.download_manager: DownloadManager = DownloadManager(self.settings_manager) + self.youtube_manager: YoutubeManager = YoutubeManager(self.settings_manager) + self.metadata_manager: MetadataManager = MetadataManager() @classmethod def get_instance(cls) -> 'MainManager': if cls._instance is None: @@ -52,5 +54,8 @@ class MainManager: def get_license_manager(self) -> LicenseManager: return self.license_manager - def get_download_manager(self) -> DownloadManager: - return self.download_manager \ No newline at end of file + def get_youtube_manager(self) -> YoutubeManager: + return self.youtube_manager + + def get_metadata_manager(self) -> MetadataManager: + return self.metadata_manager \ No newline at end of file diff --git a/app/core/metadata_manager.py b/app/core/metadata_manager.py new file mode 100644 index 0000000..3c55ad5 --- /dev/null +++ b/app/core/metadata_manager.py @@ -0,0 +1,82 @@ +from pathlib import Path +import re +from mutagen.id3 import ID3, TALB, TIT2, TPE1, delete as id3_delete + +class MetadataManager: + def apply_mp3_tags(self, file_path: Path, metadata: dict, fallback_title: str) -> None: + track_title = str(metadata.get("mp3_title") or metadata.get("title") or fallback_title).strip() + track_artist = str(metadata.get("artist", "")).strip() + track_album = str(metadata.get("album", "")).strip() + track_cover = str(metadata.get("cover_path", "")).strip() + + try: + id3_delete(str(file_path)) + except Exception: + pass + + tags = ID3() + tags.delall("TIT2") + tags.add(TIT2(encoding=3, text=track_title or fallback_title)) + tags.delall("TPE1") + if track_artist: + tags.add(TPE1(encoding=3, text=track_artist)) + tags.delall("TALB") + if track_album: + tags.add(TALB(encoding=3, text=track_album)) + tags.save(str(file_path), v2_version=4) + + # Use 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") + 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}") + + def sanitize_filename_part(self, value: str) -> str: + clean_value = re.sub(r"[<>:\"/\\|?*]", "_", value).strip() + clean_value = re.sub(r"\s+", " ", clean_value) + return clean_value.strip(". ") + + def rename_output_file(self, file_path: Path, title: str, artist: str) -> Path: + safe_title = self.sanitize_filename_part(title) + safe_artist = self.sanitize_filename_part(artist) + if safe_artist and safe_title: + base_name = f"{safe_artist} - {safe_title}" + else: + base_name = safe_title or self.sanitize_filename_part(file_path.stem) + target_path = file_path.with_name(f"{base_name}{file_path.suffix}") + if target_path == file_path: + return file_path + counter = 2 + while target_path.exists(): + target_path = file_path.with_name(f"{base_name} ({counter}){file_path.suffix}") + counter += 1 + try: + file_path.rename(target_path) + return target_path + except Exception: + return file_path diff --git a/app/core/download_manager.py b/app/core/youtube_manager.py similarity index 85% rename from app/core/download_manager.py rename to app/core/youtube_manager.py index 4c93e04..0915bb6 100644 --- a/app/core/download_manager.py +++ b/app/core/youtube_manager.py @@ -117,101 +117,13 @@ class AudioDownloadWorker(QThread): return None def _apply_mp3_tags(self, file_path: Path, fallback_title: str) -> None: - metadata = self.track_metadata - track_title = str(metadata.get("mp3_title") or metadata.get("title") or fallback_title).strip() - track_artist = str(metadata.get("artist", "")).strip() - track_album = str(metadata.get("album", "")).strip() - track_cover = str(metadata.get("cover_path", "")).strip() - - try: - id3_delete(str(file_path)) - except Exception: - pass - - tags = ID3() - - tags.delall("TIT2") - tags.add(TIT2(encoding=3, text=track_title or fallback_title)) - - tags.delall("TPE1") - if track_artist: - tags.add(TPE1(encoding=3, text=track_artist)) - - tags.delall("TALB") - if track_album: - tags.add(TALB(encoding=3, text=track_album)) - - - 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) + from app.core.metadata_manager import MetadataManager + metadata_manager = MetadataManager() + metadata_manager.apply_mp3_tags(file_path, self.track_metadata, fallback_title) + renamed_file = metadata_manager.rename_output_file(file_path, self.track_metadata.get("mp3_title") or fallback_title, self.track_metadata.get("artist", "")) if renamed_file is not None: self.progress_text.emit(renamed_file.stem) - def _sanitize_filename_part(self, value: str) -> str: - clean_value = re.sub(r"[<>:\"/\\|?*]", "_", value).strip() - clean_value = re.sub(r"\s+", " ", clean_value) - return clean_value.strip(". ") - - def _rename_output_file(self, file_path: Path, title: str, artist: str) -> Optional[Path]: - safe_title = self._sanitize_filename_part(title) - safe_artist = self._sanitize_filename_part(artist) - - if safe_artist and safe_title: - base_name = f"{safe_artist} - {safe_title}" - else: - base_name = safe_title or self._sanitize_filename_part(file_path.stem) - - if not base_name: - return None - - target_path = file_path.with_name(f"{base_name}{file_path.suffix}") - if target_path == file_path: - return file_path - - counter = 2 - while target_path.exists(): - target_path = file_path.with_name(f"{base_name} ({counter}){file_path.suffix}") - counter += 1 - - try: - file_path.rename(target_path) - return target_path - except Exception: - return None - class YoutubeSearchWorker(QThread): finished_search = pyqtSignal(list) @@ -257,10 +169,10 @@ class YoutubeSearchWorker(QThread): video_id = entry.get("id") if not video_id: return None - title = entry.get("title") or "Untitled" channel = entry.get("uploader") or entry.get("channel") or "" - duration = self._format_duration(entry.get("duration")) + raw_duration = int(entry.get("duration")) + duration = self._format_duration(raw_duration) thumbnail_url = f"https://i.ytimg.com/vi/{video_id}/hqdefault.jpg" return { @@ -341,7 +253,7 @@ class PreviewStreamWorker(QThread): self.failed_preview.emit("preview_unavailable") -class DownloadManager(QObject): +class YoutubeManager(QObject): def apply_album_cover(self, album_name: str, cover_path: str) -> None: """Applique la cover à tous les morceaux de l'album (business logic)""" for idx, track in enumerate(self.track_list): diff --git a/app/ui/windows/add_music_window.py b/app/ui/windows/add_music_window.py index 022080d..5ae1e07 100644 --- a/app/ui/windows/add_music_window.py +++ b/app/ui/windows/add_music_window.py @@ -29,8 +29,8 @@ class AddMusicWindow(QWidget): progress = pyqtSignal(int, int) finished = pyqtSignal(bool, int) failed = pyqtSignal(str) - def __init__(self, download_manager, url): - super().__init__() + def __init__(self, download_manager, url, parent=None): + super().__init__(parent) self.download_manager = download_manager self.url = url def run(self): @@ -58,18 +58,17 @@ class AddMusicWindow(QWidget): self.main_manager = MainManager.get_instance() self.language_manager = self.main_manager.get_language_manager() self.alert_manager = self.main_manager.get_alert_manager() - self.download_manager = self.main_manager.get_download_manager() + self.youtube_manager = self.main_manager.get_youtube_manager() + self.metadata_manager = self.main_manager.get_metadata_manager() self.observer_manager = self.main_manager.get_observer_manager() self.observer_manager.subscribe(NotificationType.LANGUAGE, self.update_language) - self.download_manager.list_changed.connect(self.on_list_changed) - self.download_manager.failed.connect(self.on_manager_error) - self.download_manager.search_started.connect(self.on_search_started) - self.download_manager.search_results.connect(self.on_search_results) - self.download_manager.preview_started.connect(self.on_preview_started) - self.download_manager.preview_ready.connect(self.on_preview_ready) - self.download_manager.preview_failed.connect(self.on_preview_failed) + self.youtube_manager.failed.connect(self.on_manager_error) + self.youtube_manager.search_started.connect(self.on_search_started) + self.youtube_manager.search_results.connect(self.on_search_results) + self.youtube_manager.preview_ready.connect(self.on_preview_ready) + self.youtube_manager.preview_failed.connect(self.on_preview_failed) self.thumbnail_manager = QNetworkAccessManager(self) self.thumbnail_replies: dict[QNetworkReply, QListWidgetItem] = {} @@ -80,9 +79,6 @@ class AddMusicWindow(QWidget): self.video_player.setAudioOutput(self.audio_output) self.player_widget = None - self.count_label = QLabel("", self) - self.status_label = QLabel("", self) - self.status_label.setWordWrap(True) self.setup_ui() # Always initialize search_results and related widgets self.content_stack = QStackedWidget(self) @@ -127,11 +123,8 @@ class AddMusicWindow(QWidget): self.content_stack.addWidget(self.player_view) self.content_stack.setCurrentIndex(self.RESULTS_VIEW_INDEX) - self.layout().addWidget(self.count_label) - self.layout().addWidget(self.status_label) if self.player_widget and isinstance(self.player_widget, QVideoWidget): self.video_player.setVideoOutput(self.player_widget) - self.on_list_changed(self.download_manager.get_tracks()) def setup_ui(self) -> None: layout = QVBoxLayout(self) @@ -160,20 +153,19 @@ class AddMusicWindow(QWidget): def add_link(self) -> None: url = self.link_input.text().strip() if not url: - self.status_label.setText(self.language_manager.get_text("invalid_link")) + self.alert_manager.show_error("invalid_link", self) return if "list=" in url: self._show_loading_bar(self.language_manager.get_text("downloading_audio")) - self.playlist_thread = self.PlaylistAddThread(self.download_manager, url) + # Always assign as self.playlist_thread and parent to self + self.playlist_thread = self.PlaylistAddThread(self.youtube_manager, url, parent=self) self.playlist_thread.progress.connect(self.on_playlist_progress) self.playlist_thread.finished.connect(self.on_playlist_finished) self.playlist_thread.failed.connect(self.on_playlist_failed) self.playlist_thread.start() else: - if self.download_manager.add_track(url): - self.status_label.setText(self.language_manager.get_text("link_added")) - else: - self.status_label.setText(self.language_manager.get_text("invalid_link")) + if not self.youtube_manager.add_track(url): + self.alert_manager.show_error("invalid_link", self) def _show_loading_bar(self, label: str): if hasattr(self, "loading_bar") and self.loading_bar is not None: @@ -194,7 +186,7 @@ class AddMusicWindow(QWidget): self.loading_bar.set_progress(100) self.loading_bar.deleteLater() self.loading_bar = None - self.status_label.setText(self.language_manager.get_text("playlist_added_count").replace("{count}", str(count))) + self.alert_manager.show_info(self.language_manager.get_text("playlist_added_count").replace("{count}", str(count)), self) def on_playlist_failed(self, error_key): if hasattr(self, "loading_bar") and self.loading_bar is not None: @@ -202,7 +194,7 @@ class AddMusicWindow(QWidget): self.loading_bar.set_progress(0) self.loading_bar.deleteLater() self.loading_bar = None - self.status_label.setText(self.language_manager.get_text(error_key)) + self.alert_manager.show_error(error_key, self) self.content_stack = QStackedWidget(self) self.layout().addWidget(self.content_stack, 1) @@ -246,16 +238,12 @@ class AddMusicWindow(QWidget): self.content_stack.addWidget(self.player_view) self.content_stack.setCurrentIndex(self.RESULTS_VIEW_INDEX) - self.layout().addWidget(self.count_label) - self.layout().addWidget(self.status_label) - def search_tracks(self) -> None: - self.download_manager.search_tracks(self.search_input.text()) + self.youtube_manager.search_tracks(self.search_input.text()) def on_search_started(self) -> None: self.search_button.setEnabled(False) self.search_button.setText(self.language_manager.get_text("searching")) - self.status_label.setText(self.language_manager.get_text("searching")) def on_search_results(self, tracks: list) -> None: self._clear_thumbnail_replies() @@ -266,30 +254,44 @@ class AddMusicWindow(QWidget): self.content_stack.setCurrentIndex(self.RESULTS_VIEW_INDEX) video_tracks = [track for track in tracks if track.get("kind", "video") == "video"] + playlist_tracks = [track for track in tracks if track.get("kind", "") == "playlist"] + for track in video_tracks: title = track.get("title", "Untitled") channel = track.get("channel", "") duration = track.get("duration", "") subtitle = f"{self.language_manager.get_text('result_video')} • {duration}" - if channel: subtitle = f"{subtitle} • {channel}" - item = QListWidgetItem(f"{title}\n{subtitle}") item.setData(Qt.ItemDataRole.UserRole, track) self.search_results.addItem(item) - thumbnail_url = track.get("thumbnail_url", "") if thumbnail_url: self._request_thumbnail(thumbnail_url, item) - if video_tracks: - self.status_label.setText(self.language_manager.get_text("search_results_found").replace("{count}", str(len(video_tracks)))) - else: - self.status_label.setText(self.language_manager.get_text("no_search_results")) + for track in playlist_tracks: + title = track.get("title", "Untitled playlist") + channel = track.get("channel", "") + item_count = track.get("item_count", "") + subtitle = f"{self.language_manager.get_text('result_playlist')}" + if item_count: + subtitle = f"{subtitle} • {item_count} tracks" + if channel: + subtitle = f"{subtitle} • {channel}" + item = QListWidgetItem(f"{title}\n{subtitle}") + item.setData(Qt.ItemDataRole.UserRole, track) + self.search_results.addItem(item) + thumbnail_url = track.get("thumbnail_url", "") + if thumbnail_url: + self._request_thumbnail(thumbnail_url, item) - self.search_button.setEnabled(True) - self.search_button.setText(self.language_manager.get_text("search_youtube")) + total_results = len(video_tracks) + len(playlist_tracks) + if not total_results: + self.alert_manager.show_error(self.language_manager.get_text("no_search_results"), self) + + self.search_button.setEnabled(True) + self.search_button.setText(self.language_manager.get_text("search_youtube")) def add_selected_result(self) -> None: row = self.search_results.currentRow() @@ -301,8 +303,8 @@ class AddMusicWindow(QWidget): if not isinstance(item_data, dict): return - if self.download_manager.add_search_result(item_data): - self.status_label.setText(self.language_manager.get_text("track_added")) + if self.youtube_manager.add_search_result(item_data): + self.alert_manager.show_success("track_added", self) def on_result_selected(self, current_item: Optional[QListWidgetItem]) -> None: if current_item is None: @@ -325,25 +327,21 @@ class AddMusicWindow(QWidget): self.content_stack.setCurrentIndex(self.RESULTS_VIEW_INDEX) def _load_video_in_player(self, track: dict) -> None: - self.download_manager.start_preview(track) + self.youtube_manager.start_preview(track) def _create_player_widget(self) -> QWidget: video_widget = QVideoWidget(self) video_widget.setMinimumHeight(320) return video_widget - def on_preview_started(self) -> None: - self.status_label.setText(self.language_manager.get_text("preview_loading")) - def on_preview_ready(self, stream_url: str, title: str) -> None: self.video_player.setSource(QUrl(stream_url)) self.video_player.play() self.content_stack.setCurrentIndex(self.PLAYER_VIEW_INDEX) - self.status_label.setText(self.language_manager.get_text("now_playing").replace("{title}", title)) def on_preview_failed(self, error_key: str) -> None: self.content_stack.setCurrentIndex(self.RESULTS_VIEW_INDEX) - self.status_label.setText(self.language_manager.get_text(error_key)) + self.alert_manager.show_error(error_key, self) def _request_thumbnail(self, url: str, item: QListWidgetItem) -> None: request = QNetworkRequest(QUrl(url)) @@ -373,9 +371,6 @@ class AddMusicWindow(QWidget): reply.deleteLater() self.thumbnail_replies.clear() - def on_list_changed(self, tracks: list) -> None: - text = self.language_manager.get_text("tracks_in_list").replace("{count}", str(len(tracks))) - self.count_label.setText(text) def on_manager_error(self, error_key: str) -> None: handled_errors = { @@ -391,16 +386,13 @@ class AddMusicWindow(QWidget): self.search_button.setEnabled(True) self.search_button.setText(self.language_manager.get_text("search_youtube")) - self.status_label.setText(self.language_manager.get_text(error_key)) self.alert_manager.show_error(error_key, self) def update_language(self) -> None: - self.title_label.setText(self.language_manager.get_text("add_music_title")) self.search_input.setPlaceholderText(self.language_manager.get_text("search_placeholder")) self.search_button.setText(self.language_manager.get_text("search_youtube")) self.back_to_results_button.setText(self.language_manager.get_text("back_to_results")) self.add_selected_button.setText(self.language_manager.get_text("add_selected_result")) - self.on_list_changed(self.download_manager.get_tracks()) def closeEvent(self, event) -> None: self.video_player.stop() diff --git a/app/ui/windows/download_list_window.py b/app/ui/windows/download_list_window.py index ea8c95a..663a768 100644 --- a/app/ui/windows/download_list_window.py +++ b/app/ui/windows/download_list_window.py @@ -201,26 +201,27 @@ class DownloadListWindow(QWidget): self.main_manager = MainManager.get_instance() self.language_manager = self.main_manager.get_language_manager() self.alert_manager = self.main_manager.get_alert_manager() - self.download_manager = self.main_manager.get_download_manager() + self.youtube_manager = self.main_manager.get_youtube_manager() + self.metadata_manager = self.main_manager.get_metadata_manager() self.observer_manager = self.main_manager.get_observer_manager() self.observer_manager.subscribe(NotificationType.LANGUAGE, self.update_language) - self.download_manager.list_changed.connect(self.refresh_list) - self.download_manager.started.connect(self.on_download_started) - self.download_manager.progress_text.connect(self.on_progress_text) - self.download_manager.progress_value.connect(self.on_progress_value) - self.download_manager.completed.connect(self.on_download_completed) - self.download_manager.failed.connect(self.on_download_failed) - self.download_manager.queue_finished.connect(self.on_queue_finished) - self.download_manager.downloading_state_changed.connect(self.set_downloading_state) + self.youtube_manager.list_changed.connect(self.refresh_list) + self.youtube_manager.started.connect(self.on_download_started) + self.youtube_manager.progress_text.connect(self.on_progress_text) + self.youtube_manager.progress_value.connect(self.on_progress_value) + self.youtube_manager.completed.connect(self.on_download_completed) + self.youtube_manager.failed.connect(self.on_download_failed) + self.youtube_manager.queue_finished.connect(self.on_queue_finished) + self.youtube_manager.downloading_state_changed.connect(self.set_downloading_state) self.track_data: list[dict] = [] self.album_profiles: dict[str, dict[str, str]] = {} self.current_album: str = "" self.setup_ui() - self.refresh_list(self.download_manager.get_tracks()) + self.refresh_list(self.youtube_manager.get_tracks()) def setup_ui(self) -> None: layout = QVBoxLayout(self) @@ -435,7 +436,7 @@ class DownloadListWindow(QWidget): "cover_path": cover_path, } # UI: demande au manager d'appliquer la cover à tous les morceaux de l'album - self.download_manager.apply_album_cover(album_name, cover_path) + self.metadata_manager.apply_album_cover(album_name, cover_path) self.current_album = "" self._render_grid() self.status_label.setText(self.language_manager.get_text("album_saved")) @@ -456,7 +457,7 @@ class DownloadListWindow(QWidget): final_artist = dialog.artist_input.text().strip() or profile.get("artist", "") final_cover = profile.get("cover_path", "") if album_name else str(track.get("cover_path", "")) - if self.download_manager.update_track_metadata( + if self.metadata_manager.update_track_metadata( track_index, dialog.title_input.text(), final_artist, @@ -470,7 +471,7 @@ class DownloadListWindow(QWidget): return profile = self.album_profiles.get(album_name, {"artist": "", "cover_path": ""}) - if self.download_manager.update_track_metadata( + if self.metadata_manager.update_track_metadata( track_index, str(self.track_data[track_index].get("mp3_title") or self.track_data[track_index].get("title", "")), profile.get("artist", ""), @@ -490,18 +491,18 @@ class DownloadListWindow(QWidget): def remove_selected(self) -> None: track_index = self._selected_track_index() - self.download_manager.remove_track(track_index) + self.youtube_manager.remove_track(track_index) def clear_list(self) -> None: - self.download_manager.clear_tracks() + self.youtube_manager.clear_tracks() self.status_label.setText(self.language_manager.get_text("list_cleared")) def download_selected(self) -> None: track_index = self._selected_track_index() - self.download_manager.download_track_at(track_index, self.folder_input.text().strip()) + self.youtube_manager.download_track_at(track_index, self.folder_input.text().strip()) def download_all(self) -> None: - self.download_manager.download_all_tracks(self.folder_input.text().strip()) + self.youtube_manager.download_all_tracks(self.folder_input.text().strip()) def on_download_started(self) -> None: self._show_loading_bar(self.language_manager.get_text("downloading_audio")) diff --git a/data/lang/en.json b/data/lang/en.json index a22454c..780e294 100644 --- a/data/lang/en.json +++ b/data/lang/en.json @@ -92,7 +92,6 @@ "preview_in_progress": "A preview is already loading.", "add_to_list": "Add to list", "track_added": "Track added to the list.", - "playlist_added_count": "Playlist added: {count} tracks.", "tracks_in_list": "Tracks in list: {count}", "albums_title": "Albums", "tracks_title": "Tracks", diff --git a/data/lang/fr.json b/data/lang/fr.json index cd5eb07..06beff3 100644 --- a/data/lang/fr.json +++ b/data/lang/fr.json @@ -93,7 +93,6 @@ "preview_in_progress": "Une préécoute est déjà en cours de chargement.", "add_to_list": "Ajouter à la liste", "track_added": "Musique ajoutée à la liste.", - "playlist_added_count": "Playlist ajoutée : {count} musiques.", "tracks_in_list": "Musiques dans la liste : {count}", "albums_title": "Albums", "tracks_title": "Musiques", diff --git a/main.py b/main.py index 04848d8..924884c 100644 --- a/main.py +++ b/main.py @@ -60,7 +60,8 @@ def main() -> int: theme_manager = main_manager.get_theme_manager() settings_manager = main_manager.get_settings_manager() - except Exception: + except Exception as e: + print(f"Error occurred while initializing managers: {e}") return 0 try: