This commit is contained in:
Louis Mazin 2026-03-18 19:28:41 +01:00
parent 7b29ea57b7
commit 5104df5c0b
8 changed files with 162 additions and 171 deletions

View File

@ -5,7 +5,8 @@ from app.core.settings_manager import SettingsManager
from app.core.alert_manager import AlertManager from app.core.alert_manager import AlertManager
from app.core.update_manager import UpdateManager from app.core.update_manager import UpdateManager
from app.core.license_manager import LicenseManager 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 from typing import Optional
@ -24,7 +25,8 @@ class MainManager:
self.alert_manager: AlertManager = AlertManager(self.language_manager, self.theme_manager) 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.update_manager: UpdateManager = UpdateManager(self.settings_manager, self.language_manager, self.alert_manager)
self.license_manager: LicenseManager = LicenseManager(self.settings_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 @classmethod
def get_instance(cls) -> 'MainManager': def get_instance(cls) -> 'MainManager':
if cls._instance is None: if cls._instance is None:
@ -52,5 +54,8 @@ class MainManager:
def get_license_manager(self) -> LicenseManager: def get_license_manager(self) -> LicenseManager:
return self.license_manager return self.license_manager
def get_download_manager(self) -> DownloadManager: def get_youtube_manager(self) -> YoutubeManager:
return self.download_manager return self.youtube_manager
def get_metadata_manager(self) -> MetadataManager:
return self.metadata_manager

View File

@ -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

View File

@ -117,101 +117,13 @@ class AudioDownloadWorker(QThread):
return None return None
def _apply_mp3_tags(self, file_path: Path, fallback_title: str) -> None: def _apply_mp3_tags(self, file_path: Path, fallback_title: str) -> None:
metadata = self.track_metadata from app.core.metadata_manager import MetadataManager
track_title = str(metadata.get("mp3_title") or metadata.get("title") or fallback_title).strip() metadata_manager = MetadataManager()
track_artist = str(metadata.get("artist", "")).strip() metadata_manager.apply_mp3_tags(file_path, self.track_metadata, fallback_title)
track_album = str(metadata.get("album", "")).strip() renamed_file = metadata_manager.rename_output_file(file_path, self.track_metadata.get("mp3_title") or fallback_title, self.track_metadata.get("artist", ""))
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)
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)
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): class YoutubeSearchWorker(QThread):
finished_search = pyqtSignal(list) finished_search = pyqtSignal(list)
@ -257,10 +169,10 @@ class YoutubeSearchWorker(QThread):
video_id = entry.get("id") video_id = entry.get("id")
if not video_id: if not video_id:
return None return None
title = entry.get("title") or "Untitled" title = entry.get("title") or "Untitled"
channel = entry.get("uploader") or entry.get("channel") or "" 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" thumbnail_url = f"https://i.ytimg.com/vi/{video_id}/hqdefault.jpg"
return { return {
@ -341,7 +253,7 @@ class PreviewStreamWorker(QThread):
self.failed_preview.emit("preview_unavailable") self.failed_preview.emit("preview_unavailable")
class DownloadManager(QObject): class YoutubeManager(QObject):
def apply_album_cover(self, album_name: str, cover_path: str) -> None: def apply_album_cover(self, album_name: str, cover_path: str) -> None:
"""Applique la cover à tous les morceaux de l'album (business logic)""" """Applique la cover à tous les morceaux de l'album (business logic)"""
for idx, track in enumerate(self.track_list): for idx, track in enumerate(self.track_list):

View File

@ -29,8 +29,8 @@ class AddMusicWindow(QWidget):
progress = pyqtSignal(int, int) progress = pyqtSignal(int, int)
finished = pyqtSignal(bool, int) finished = pyqtSignal(bool, int)
failed = pyqtSignal(str) failed = pyqtSignal(str)
def __init__(self, download_manager, url): def __init__(self, download_manager, url, parent=None):
super().__init__() super().__init__(parent)
self.download_manager = download_manager self.download_manager = download_manager
self.url = url self.url = url
def run(self): def run(self):
@ -58,18 +58,17 @@ class AddMusicWindow(QWidget):
self.main_manager = MainManager.get_instance() self.main_manager = MainManager.get_instance()
self.language_manager = self.main_manager.get_language_manager() self.language_manager = self.main_manager.get_language_manager()
self.alert_manager = self.main_manager.get_alert_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 = self.main_manager.get_observer_manager()
self.observer_manager.subscribe(NotificationType.LANGUAGE, self.update_language) self.observer_manager.subscribe(NotificationType.LANGUAGE, self.update_language)
self.download_manager.list_changed.connect(self.on_list_changed) self.youtube_manager.failed.connect(self.on_manager_error)
self.download_manager.failed.connect(self.on_manager_error) self.youtube_manager.search_started.connect(self.on_search_started)
self.download_manager.search_started.connect(self.on_search_started) self.youtube_manager.search_results.connect(self.on_search_results)
self.download_manager.search_results.connect(self.on_search_results) self.youtube_manager.preview_ready.connect(self.on_preview_ready)
self.download_manager.preview_started.connect(self.on_preview_started) self.youtube_manager.preview_failed.connect(self.on_preview_failed)
self.download_manager.preview_ready.connect(self.on_preview_ready)
self.download_manager.preview_failed.connect(self.on_preview_failed)
self.thumbnail_manager = QNetworkAccessManager(self) self.thumbnail_manager = QNetworkAccessManager(self)
self.thumbnail_replies: dict[QNetworkReply, QListWidgetItem] = {} self.thumbnail_replies: dict[QNetworkReply, QListWidgetItem] = {}
@ -80,9 +79,6 @@ class AddMusicWindow(QWidget):
self.video_player.setAudioOutput(self.audio_output) self.video_player.setAudioOutput(self.audio_output)
self.player_widget = None self.player_widget = None
self.count_label = QLabel("", self)
self.status_label = QLabel("", self)
self.status_label.setWordWrap(True)
self.setup_ui() self.setup_ui()
# Always initialize search_results and related widgets # Always initialize search_results and related widgets
self.content_stack = QStackedWidget(self) self.content_stack = QStackedWidget(self)
@ -127,11 +123,8 @@ class AddMusicWindow(QWidget):
self.content_stack.addWidget(self.player_view) self.content_stack.addWidget(self.player_view)
self.content_stack.setCurrentIndex(self.RESULTS_VIEW_INDEX) 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): if self.player_widget and isinstance(self.player_widget, QVideoWidget):
self.video_player.setVideoOutput(self.player_widget) self.video_player.setVideoOutput(self.player_widget)
self.on_list_changed(self.download_manager.get_tracks())
def setup_ui(self) -> None: def setup_ui(self) -> None:
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
@ -160,20 +153,19 @@ class AddMusicWindow(QWidget):
def add_link(self) -> None: def add_link(self) -> None:
url = self.link_input.text().strip() url = self.link_input.text().strip()
if not url: if not url:
self.status_label.setText(self.language_manager.get_text("invalid_link")) self.alert_manager.show_error("invalid_link", self)
return return
if "list=" in url: if "list=" in url:
self._show_loading_bar(self.language_manager.get_text("downloading_audio")) 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.progress.connect(self.on_playlist_progress)
self.playlist_thread.finished.connect(self.on_playlist_finished) self.playlist_thread.finished.connect(self.on_playlist_finished)
self.playlist_thread.failed.connect(self.on_playlist_failed) self.playlist_thread.failed.connect(self.on_playlist_failed)
self.playlist_thread.start() self.playlist_thread.start()
else: else:
if self.download_manager.add_track(url): if not self.youtube_manager.add_track(url):
self.status_label.setText(self.language_manager.get_text("link_added")) self.alert_manager.show_error("invalid_link", self)
else:
self.status_label.setText(self.language_manager.get_text("invalid_link"))
def _show_loading_bar(self, label: str): def _show_loading_bar(self, label: str):
if hasattr(self, "loading_bar") and self.loading_bar is not None: 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.set_progress(100)
self.loading_bar.deleteLater() self.loading_bar.deleteLater()
self.loading_bar = None 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): def on_playlist_failed(self, error_key):
if hasattr(self, "loading_bar") and self.loading_bar is not None: 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.set_progress(0)
self.loading_bar.deleteLater() self.loading_bar.deleteLater()
self.loading_bar = None 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.content_stack = QStackedWidget(self)
self.layout().addWidget(self.content_stack, 1) self.layout().addWidget(self.content_stack, 1)
@ -246,16 +238,12 @@ class AddMusicWindow(QWidget):
self.content_stack.addWidget(self.player_view) self.content_stack.addWidget(self.player_view)
self.content_stack.setCurrentIndex(self.RESULTS_VIEW_INDEX) 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: 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: def on_search_started(self) -> None:
self.search_button.setEnabled(False) self.search_button.setEnabled(False)
self.search_button.setText(self.language_manager.get_text("searching")) 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: def on_search_results(self, tracks: list) -> None:
self._clear_thumbnail_replies() self._clear_thumbnail_replies()
@ -266,30 +254,44 @@ class AddMusicWindow(QWidget):
self.content_stack.setCurrentIndex(self.RESULTS_VIEW_INDEX) self.content_stack.setCurrentIndex(self.RESULTS_VIEW_INDEX)
video_tracks = [track for track in tracks if track.get("kind", "video") == "video"] 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: for track in video_tracks:
title = track.get("title", "Untitled") title = track.get("title", "Untitled")
channel = track.get("channel", "") channel = track.get("channel", "")
duration = track.get("duration", "") duration = track.get("duration", "")
subtitle = f"{self.language_manager.get_text('result_video')}{duration}" subtitle = f"{self.language_manager.get_text('result_video')}{duration}"
if channel: if channel:
subtitle = f"{subtitle}{channel}" subtitle = f"{subtitle}{channel}"
item = QListWidgetItem(f"{title}\n{subtitle}") item = QListWidgetItem(f"{title}\n{subtitle}")
item.setData(Qt.ItemDataRole.UserRole, track) item.setData(Qt.ItemDataRole.UserRole, track)
self.search_results.addItem(item) self.search_results.addItem(item)
thumbnail_url = track.get("thumbnail_url", "") thumbnail_url = track.get("thumbnail_url", "")
if thumbnail_url: if thumbnail_url:
self._request_thumbnail(thumbnail_url, item) self._request_thumbnail(thumbnail_url, item)
if video_tracks: for track in playlist_tracks:
self.status_label.setText(self.language_manager.get_text("search_results_found").replace("{count}", str(len(video_tracks)))) title = track.get("title", "Untitled playlist")
else: channel = track.get("channel", "")
self.status_label.setText(self.language_manager.get_text("no_search_results")) 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) total_results = len(video_tracks) + len(playlist_tracks)
self.search_button.setText(self.language_manager.get_text("search_youtube")) 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: def add_selected_result(self) -> None:
row = self.search_results.currentRow() row = self.search_results.currentRow()
@ -301,8 +303,8 @@ class AddMusicWindow(QWidget):
if not isinstance(item_data, dict): if not isinstance(item_data, dict):
return return
if self.download_manager.add_search_result(item_data): if self.youtube_manager.add_search_result(item_data):
self.status_label.setText(self.language_manager.get_text("track_added")) self.alert_manager.show_success("track_added", self)
def on_result_selected(self, current_item: Optional[QListWidgetItem]) -> None: def on_result_selected(self, current_item: Optional[QListWidgetItem]) -> None:
if current_item is None: if current_item is None:
@ -325,25 +327,21 @@ class AddMusicWindow(QWidget):
self.content_stack.setCurrentIndex(self.RESULTS_VIEW_INDEX) self.content_stack.setCurrentIndex(self.RESULTS_VIEW_INDEX)
def _load_video_in_player(self, track: dict) -> None: 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: def _create_player_widget(self) -> QWidget:
video_widget = QVideoWidget(self) video_widget = QVideoWidget(self)
video_widget.setMinimumHeight(320) video_widget.setMinimumHeight(320)
return video_widget 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: def on_preview_ready(self, stream_url: str, title: str) -> None:
self.video_player.setSource(QUrl(stream_url)) self.video_player.setSource(QUrl(stream_url))
self.video_player.play() self.video_player.play()
self.content_stack.setCurrentIndex(self.PLAYER_VIEW_INDEX) 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: def on_preview_failed(self, error_key: str) -> None:
self.content_stack.setCurrentIndex(self.RESULTS_VIEW_INDEX) 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: def _request_thumbnail(self, url: str, item: QListWidgetItem) -> None:
request = QNetworkRequest(QUrl(url)) request = QNetworkRequest(QUrl(url))
@ -373,9 +371,6 @@ class AddMusicWindow(QWidget):
reply.deleteLater() reply.deleteLater()
self.thumbnail_replies.clear() 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: def on_manager_error(self, error_key: str) -> None:
handled_errors = { handled_errors = {
@ -391,16 +386,13 @@ class AddMusicWindow(QWidget):
self.search_button.setEnabled(True) self.search_button.setEnabled(True)
self.search_button.setText(self.language_manager.get_text("search_youtube")) 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) self.alert_manager.show_error(error_key, self)
def update_language(self) -> None: 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_input.setPlaceholderText(self.language_manager.get_text("search_placeholder"))
self.search_button.setText(self.language_manager.get_text("search_youtube")) 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.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.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: def closeEvent(self, event) -> None:
self.video_player.stop() self.video_player.stop()

View File

@ -201,26 +201,27 @@ class DownloadListWindow(QWidget):
self.main_manager = MainManager.get_instance() self.main_manager = MainManager.get_instance()
self.language_manager = self.main_manager.get_language_manager() self.language_manager = self.main_manager.get_language_manager()
self.alert_manager = self.main_manager.get_alert_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 = self.main_manager.get_observer_manager()
self.observer_manager.subscribe(NotificationType.LANGUAGE, self.update_language) self.observer_manager.subscribe(NotificationType.LANGUAGE, self.update_language)
self.download_manager.list_changed.connect(self.refresh_list) self.youtube_manager.list_changed.connect(self.refresh_list)
self.download_manager.started.connect(self.on_download_started) self.youtube_manager.started.connect(self.on_download_started)
self.download_manager.progress_text.connect(self.on_progress_text) self.youtube_manager.progress_text.connect(self.on_progress_text)
self.download_manager.progress_value.connect(self.on_progress_value) self.youtube_manager.progress_value.connect(self.on_progress_value)
self.download_manager.completed.connect(self.on_download_completed) self.youtube_manager.completed.connect(self.on_download_completed)
self.download_manager.failed.connect(self.on_download_failed) self.youtube_manager.failed.connect(self.on_download_failed)
self.download_manager.queue_finished.connect(self.on_queue_finished) self.youtube_manager.queue_finished.connect(self.on_queue_finished)
self.download_manager.downloading_state_changed.connect(self.set_downloading_state) self.youtube_manager.downloading_state_changed.connect(self.set_downloading_state)
self.track_data: list[dict] = [] self.track_data: list[dict] = []
self.album_profiles: dict[str, dict[str, str]] = {} self.album_profiles: dict[str, dict[str, str]] = {}
self.current_album: str = "" self.current_album: str = ""
self.setup_ui() self.setup_ui()
self.refresh_list(self.download_manager.get_tracks()) self.refresh_list(self.youtube_manager.get_tracks())
def setup_ui(self) -> None: def setup_ui(self) -> None:
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
@ -435,7 +436,7 @@ class DownloadListWindow(QWidget):
"cover_path": cover_path, "cover_path": cover_path,
} }
# UI: demande au manager d'appliquer la cover à tous les morceaux de l'album # 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.current_album = ""
self._render_grid() self._render_grid()
self.status_label.setText(self.language_manager.get_text("album_saved")) 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_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", "")) 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, track_index,
dialog.title_input.text(), dialog.title_input.text(),
final_artist, final_artist,
@ -470,7 +471,7 @@ class DownloadListWindow(QWidget):
return return
profile = self.album_profiles.get(album_name, {"artist": "", "cover_path": ""}) 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, track_index,
str(self.track_data[track_index].get("mp3_title") or self.track_data[track_index].get("title", "")), str(self.track_data[track_index].get("mp3_title") or self.track_data[track_index].get("title", "")),
profile.get("artist", ""), profile.get("artist", ""),
@ -490,18 +491,18 @@ class DownloadListWindow(QWidget):
def remove_selected(self) -> None: def remove_selected(self) -> None:
track_index = self._selected_track_index() 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: 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")) self.status_label.setText(self.language_manager.get_text("list_cleared"))
def download_selected(self) -> None: def download_selected(self) -> None:
track_index = self._selected_track_index() 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: 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: def on_download_started(self) -> None:
self._show_loading_bar(self.language_manager.get_text("downloading_audio")) self._show_loading_bar(self.language_manager.get_text("downloading_audio"))

View File

@ -92,7 +92,6 @@
"preview_in_progress": "A preview is already loading.", "preview_in_progress": "A preview is already loading.",
"add_to_list": "Add to list", "add_to_list": "Add to list",
"track_added": "Track added to the list.", "track_added": "Track added to the list.",
"playlist_added_count": "Playlist added: {count} tracks.",
"tracks_in_list": "Tracks in list: {count}", "tracks_in_list": "Tracks in list: {count}",
"albums_title": "Albums", "albums_title": "Albums",
"tracks_title": "Tracks", "tracks_title": "Tracks",

View File

@ -93,7 +93,6 @@
"preview_in_progress": "Une préécoute est déjà en cours de chargement.", "preview_in_progress": "Une préécoute est déjà en cours de chargement.",
"add_to_list": "Ajouter à la liste", "add_to_list": "Ajouter à la liste",
"track_added": "Musique ajoutée à 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}", "tracks_in_list": "Musiques dans la liste : {count}",
"albums_title": "Albums", "albums_title": "Albums",
"tracks_title": "Musiques", "tracks_title": "Musiques",

View File

@ -60,7 +60,8 @@ def main() -> int:
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: except Exception as e:
print(f"Error occurred while initializing managers: {e}")
return 0 return 0
try: try: