diff --git a/languages/ar.json b/languages/ar.json index d581efe..7a465ec 100644 --- a/languages/ar.json +++ b/languages/ar.json @@ -389,7 +389,10 @@ "video_file_exists": "⚠️ ملف الفيديو موجود بالفعل", "audio_file_exists": "⚠️ ملف الصوت موجود بالفعل", "subtitle_file_exists": "⚠️ ملف الترجمات موجود بالفعل", - "cancelling": "جاري إلغاء التنزيل..." + "cancelling": "جاري إلغاء التنزيل...", + "thumbnail_saved": "✅ تم حفظ الصورة المصغرة: {filename}", + "thumbnail_error": "❌ خطأ في الصورة المصغرة: {error}", + "thumbnail_no_image": "لا توجد صورة مصغرة متاحة للحفظ" }, "errors": { "playlist_no_videos": "خطأ: قائمة التشغيل لا تحتوي على مقاطع فيديو صالحة.", diff --git a/languages/de.json b/languages/de.json index 4697419..846744f 100644 --- a/languages/de.json +++ b/languages/de.json @@ -389,7 +389,10 @@ "video_file_exists": "⚠️ Video-Datei existiert bereits", "audio_file_exists": "⚠️ Audio-Datei existiert bereits", "subtitle_file_exists": "⚠️ Untertitel-Datei existiert bereits", - "cancelling": "Download wird abgebrochen..." + "cancelling": "Download wird abgebrochen...", + "thumbnail_saved": "✅ Miniaturbild gespeichert: {filename}", + "thumbnail_error": "❌ Miniaturbild-Fehler: {error}", + "thumbnail_no_image": "Kein Miniaturbild zum Speichern verfügbar" }, "errors": { "playlist_no_videos": "Fehler: Playlist enthält keine gültigen Videos.", diff --git a/languages/en.json b/languages/en.json index 293c177..54e7229 100644 --- a/languages/en.json +++ b/languages/en.json @@ -389,7 +389,10 @@ "video_file_exists": "⚠️ Video file already exists", "audio_file_exists": "⚠️ Audio file already exists", "subtitle_file_exists": "⚠️ Subtitle file already exists", - "cancelling": "Cancelling download..." + "cancelling": "Cancelling download...", + "thumbnail_saved": "✅ Thumbnail saved: {filename}", + "thumbnail_error": "❌ Thumbnail error: {error}", + "thumbnail_no_image": "No thumbnail available to save" }, "errors": { "playlist_no_videos": "Error: Playlist contains no valid videos.", diff --git a/languages/es.json b/languages/es.json index 9d9620b..6cae9b4 100644 --- a/languages/es.json +++ b/languages/es.json @@ -372,7 +372,10 @@ "video_file_exists": "⚠️ El archivo de video ya existe", "audio_file_exists": "⚠️ El archivo de audio ya existe", "subtitle_file_exists": "⚠️ El archivo de subtítulos ya existe", - "cancelling": "Cancelando descarga..." + "cancelling": "Cancelando descarga...", + "thumbnail_saved": "✅ Miniatura guardada: {filename}", + "thumbnail_error": "❌ Error de miniatura: {error}", + "thumbnail_no_image": "No hay miniatura disponible para guardar" }, "errors": { "playlist_no_videos": "Error: La lista de reproducción no contiene videos válidos.", diff --git a/languages/fr.json b/languages/fr.json index 1fb8746..509dafe 100644 --- a/languages/fr.json +++ b/languages/fr.json @@ -389,7 +389,10 @@ "video_file_exists": "⚠️ Le fichier vidéo existe déjà", "audio_file_exists": "⚠️ Le fichier audio existe déjà", "subtitle_file_exists": "⚠️ Le fichier de sous-titres existe déjà", - "cancelling": "Annulation du téléchargement..." + "cancelling": "Annulation du téléchargement...", + "thumbnail_saved": "✅ Miniature enregistrée : {filename}", + "thumbnail_error": "❌ Erreur de miniature : {error}", + "thumbnail_no_image": "Aucune miniature disponible à enregistrer" }, "errors": { "playlist_no_videos": "Erreur : La playlist ne contient aucune vidéo valide.", diff --git a/languages/hi.json b/languages/hi.json index c6edf37..6ded3d4 100644 --- a/languages/hi.json +++ b/languages/hi.json @@ -389,7 +389,10 @@ "video_file_exists": "⚠️ वीडियो फ़ाइल पहले से मौजूद है", "audio_file_exists": "⚠️ ऑडियो फ़ाइल पहले से मौजूद है", "subtitle_file_exists": "⚠️ उपशीर्षक फ़ाइल पहले से मौजूद है", - "cancelling": "डाउनलोड रद्द हो रहा है..." + "cancelling": "डाउनलोड रद्द हो रहा है...", + "thumbnail_saved": "✅ थंबनेल सहेजा गया: {filename}", + "thumbnail_error": "❌ थंबनेल त्रुटि: {error}", + "thumbnail_no_image": "सहेजने के लिए कोई थंबनेल उपलब्ध नहीं" }, "errors": { "playlist_no_videos": "त्रुटि: प्लेलिस्ट में कोई वैध वीडियो नहीं है।", diff --git a/languages/id.json b/languages/id.json index 000150e..a695c6c 100644 --- a/languages/id.json +++ b/languages/id.json @@ -389,7 +389,10 @@ "video_file_exists": "⚠️ File video sudah ada", "audio_file_exists": "⚠️ File audio sudah ada", "subtitle_file_exists": "⚠️ File subtitle sudah ada", - "cancelling": "Membatalkan unduhan..." + "cancelling": "Membatalkan unduhan...", + "thumbnail_saved": "✅ Thumbnail disimpan: {filename}", + "thumbnail_error": "❌ Kesalahan thumbnail: {error}", + "thumbnail_no_image": "Tidak ada thumbnail yang tersedia untuk disimpan" }, "errors": { "playlist_no_videos": "Kesalahan: Playlist tidak berisi video yang valid.", diff --git a/languages/it.json b/languages/it.json index ecea0de..a8ab159 100644 --- a/languages/it.json +++ b/languages/it.json @@ -389,7 +389,10 @@ "video_file_exists": "⚠️ File video già esistente", "audio_file_exists": "⚠️ File audio già esistente", "subtitle_file_exists": "⚠️ File sottotitoli già esistente", - "cancelling": "Annullamento download..." + "cancelling": "Annullamento download...", + "thumbnail_saved": "✅ Miniatura salvata: {filename}", + "thumbnail_error": "❌ Errore miniatura: {error}", + "thumbnail_no_image": "Nessuna miniatura disponibile da salvare" }, "errors": { "playlist_no_videos": "Errore: La playlist non contiene video validi.", diff --git a/languages/ja.json b/languages/ja.json index f2ff69b..1780b67 100644 --- a/languages/ja.json +++ b/languages/ja.json @@ -389,7 +389,10 @@ "video_file_exists": "⚠️ 動画ファイルがすでに存在します", "audio_file_exists": "⚠️ 音声ファイルがすでに存在します", "subtitle_file_exists": "⚠️ 字幕ファイルがすでに存在します", - "cancelling": "ダウンロードをキャンセル中..." + "cancelling": "ダウンロードをキャンセル中...", + "thumbnail_saved": "✅ サムネイルを保存しました: {filename}", + "thumbnail_error": "❌ サムネイルエラー: {error}", + "thumbnail_no_image": "保存できるサムネイルがありません" }, "errors": { "playlist_no_videos": "エラー: 再生リストに有効な動画が含まれていません。", diff --git a/languages/pl.json b/languages/pl.json index 6924ef1..8f080b9 100644 --- a/languages/pl.json +++ b/languages/pl.json @@ -389,7 +389,10 @@ "video_file_exists": "⚠️ Plik wideo już istnieje", "audio_file_exists": "⚠️ Plik audio już istnieje", "subtitle_file_exists": "⚠️ Plik napisów już istnieje", - "cancelling": "Anulowanie pobierania..." + "cancelling": "Anulowanie pobierania...", + "thumbnail_saved": "✅ Miniatura zapisana: {filename}", + "thumbnail_error": "❌ Błąd miniatury: {error}", + "thumbnail_no_image": "Brak miniatury do zapisania" }, "errors": { "playlist_no_videos": "Błąd: Playlista nie zawiera prawidłowych wideo.", diff --git a/languages/pt.json b/languages/pt.json index efb92bc..8742058 100644 --- a/languages/pt.json +++ b/languages/pt.json @@ -389,7 +389,10 @@ "video_file_exists": "⚠️ Arquivo de vídeo já existe", "audio_file_exists": "⚠️ Arquivo de áudio já existe", "subtitle_file_exists": "⚠️ Arquivo de legendas já existe", - "cancelling": "Cancelando download..." + "cancelling": "Cancelando download...", + "thumbnail_saved": "✅ Miniatura salva: {filename}", + "thumbnail_error": "❌ Erro na miniatura: {error}", + "thumbnail_no_image": "Nenhuma miniatura disponível para salvar" }, "errors": { "playlist_no_videos": "Erro: A playlist não contém vídeos válidos.", diff --git a/languages/ru.json b/languages/ru.json index ae9a3fa..926cfa7 100644 --- a/languages/ru.json +++ b/languages/ru.json @@ -389,7 +389,10 @@ "video_file_exists": "⚠️ Видеофайл уже существует", "audio_file_exists": "⚠️ Аудиофайл уже существует", "subtitle_file_exists": "⚠️ Файл субтитров уже существует", - "cancelling": "Отмена загрузки..." + "cancelling": "Отмена загрузки...", + "thumbnail_saved": "✅ Миниатюра сохранена: {filename}", + "thumbnail_error": "❌ Ошибка миниатюры: {error}", + "thumbnail_no_image": "Нет доступной миниатюры для сохранения" }, "errors": { "playlist_no_videos": "Ошибка: Плейлист не содержит действительных видео.", diff --git a/languages/tr.json b/languages/tr.json index 8a43755..fba6dd9 100644 --- a/languages/tr.json +++ b/languages/tr.json @@ -389,7 +389,10 @@ "video_file_exists": "⚠️ Video dosyası zaten mevcut", "audio_file_exists": "⚠️ Ses dosyası zaten mevcut", "subtitle_file_exists": "⚠️ Altyazı dosyası zaten mevcut", - "cancelling": "İndirme iptal ediliyor..." + "cancelling": "İndirme iptal ediliyor...", + "thumbnail_saved": "✅ Küçük resim kaydedildi: {filename}", + "thumbnail_error": "❌ Küçük resim hatası: {error}", + "thumbnail_no_image": "Kaydedilecek küçük resim yok" }, "errors": { "playlist_no_videos": "Hata: Oynatma listesi geçerli video içermiyor.", diff --git a/languages/zh.json b/languages/zh.json index a05d380..746482c 100644 --- a/languages/zh.json +++ b/languages/zh.json @@ -389,7 +389,10 @@ "video_file_exists": "⚠️ 视频文件已存在", "audio_file_exists": "⚠️ 音频文件已存在", "subtitle_file_exists": "⚠️ 字幕文件已存在", - "cancelling": "正在取消下载..." + "cancelling": "正在取消下载...", + "thumbnail_saved": "✅ 缩略图已保存: {filename}", + "thumbnail_error": "❌ 缩略图错误: {error}", + "thumbnail_no_image": "没有可保存的缩略图" }, "errors": { "playlist_no_videos": "错误:播放列表不包含有效视频。", diff --git a/src/gui/ytsage_gui_video_info.py b/src/gui/ytsage_gui_video_info.py index fceaf02..d9f4bba 100644 --- a/src/gui/ytsage_gui_video_info.py +++ b/src/gui/ytsage_gui_video_info.py @@ -389,74 +389,47 @@ class VideoInfoMixin: def download_thumbnail_file(self, video_url, path) -> bool: self = cast("YTSageApp", self) # for autocompletion and type inference. - - def download_thumbnail_file(self, video_url, path) -> bool: if not self.save_thumbnail: return False try: - # Note: yt_dlp Python package removed - this feature now uses subprocess - # Extract thumbnail info using yt-dlp CLI instead - from src.core.ytsage_yt_dlp import get_yt_dlp_path - from src.utils.ytsage_constants import SUBPROCESS_CREATIONFLAGS - import subprocess - import json - - logger.debug(f"Attempting to save thumbnail for URL: {video_url}") - - ytdlp_path = get_yt_dlp_path() - - # Use yt-dlp CLI to extract thumbnail info - result = subprocess.run( - [ytdlp_path, "--dump-json", "--skip-download", video_url], - capture_output=True, - text=True, - timeout=30, - creationflags=SUBPROCESS_CREATIONFLAGS, - ) - - if result.returncode != 0: - logger.error(f"Failed to extract video info: {result.stderr}") - return False - - info = json.loads(result.stdout) - thumbnails = info.get("thumbnails", []) - - if not thumbnails: - logger.info("No thumbnails available") + # Use cached thumbnail image from analysis if available + if self.thumbnail_image is None: + logger.info("No thumbnail image cached from analysis") + self.signals.update_status.emit(_("status.thumbnail_no_image")) return False - thumbnail_url = max( - thumbnails, - key=lambda t: (t.get("height", 0) or 0) * (t.get("width", 0) or 0), - ).get("url") + # Get video title from cached video_info + video_title = "thumbnail" + if self.video_info and "title" in self.video_info: + video_title = self.video_info["title"] + elif self.playlist_info and "title" in self.playlist_info: + video_title = self.playlist_info["title"] - if not thumbnail_url: - logger.info("Failed to extract thumbnail URL") - return False - - # Download using requests - response = requests.get(thumbnail_url) - response.raise_for_status() + logger.debug(f"Saving cached thumbnail for: {video_title}") # Save the thumbnail thumb_dir = Path(path).joinpath("Thumbnails") thumb_dir.mkdir(exist_ok=True) - filename = f"{self.sanitize_filename(info['title'])}.jpg" + filename = f"{self.sanitize_filename(video_title)}.jpg" thumbnail_path = thumb_dir.joinpath(filename) - with open(thumbnail_path, "wb") as f: - f.write(response.content) + # Save the cached PIL Image directly + # Convert to RGB if necessary (in case of RGBA or other modes) + if self.thumbnail_image.mode in ("RGBA", "P"): + rgb_image = self.thumbnail_image.convert("RGB") + rgb_image.save(thumbnail_path, "JPEG", quality=95) + else: + self.thumbnail_image.save(thumbnail_path, "JPEG", quality=95) logger.info(f"Thumbnail saved to: {thumbnail_path}") - self.signals.update_status.emit(f"✅ Thumbnail saved: {filename}") + self.signals.update_status.emit(_("status.thumbnail_saved", filename=filename)) return True except Exception as e: - error_msg = f"❌ Thumbnail error: {e}" logger.exception(f"Thumbnail Save Error: {e}") - self.signals.update_status.emit(error_msg) + self.signals.update_status.emit(_("status.thumbnail_error", error=str(e))) return False def sanitize_filename(self, name) -> str: