generated from LouisMazin/PythonApplicationTemplate
nice
This commit is contained in:
parent
7b29ea57b7
commit
5104df5c0b
@ -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
|
||||
def get_youtube_manager(self) -> YoutubeManager:
|
||||
return self.youtube_manager
|
||||
|
||||
def get_metadata_manager(self) -> MetadataManager:
|
||||
return self.metadata_manager
|
||||
82
app/core/metadata_manager.py
Normal file
82
app/core/metadata_manager.py
Normal 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
|
||||
@ -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):
|
||||
@ -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()
|
||||
|
||||
@ -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"))
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user