Add FFmpeg updater module and GUI integration
Introduces a new FFmpeg updater module for checking and updating FFmpeg to the latest essentials build, with progress reporting and version comparison. Updates language files with new translation keys for the updater in multiple languages. Refactors Windows FFmpeg installation to use the new updater logic. Adds UpdaterTabWidget to the GUI dialogs for integration of the updater into the application's interface.
This commit is contained in:
+21
-1
@@ -99,7 +99,8 @@
|
||||
"cookies": "تسجيل الدخول بالكوكيز",
|
||||
"custom_command": "أمر مخصص",
|
||||
"proxy": "بروكسي",
|
||||
"language": "اللغة"
|
||||
"language": "اللغة",
|
||||
"updater": "التحديث"
|
||||
},
|
||||
"cookies": {
|
||||
"help_text": "اختر طريقة لتوفير ملفات تعريف الارتباط للمصادقة.\nيسمح هذا بتنزيل مقاطع الفيديو الخاصة وملفات الصوت عالية الجودة.",
|
||||
@@ -466,5 +467,24 @@
|
||||
"redownload_confirm_title": "إعادة التنزيل؟",
|
||||
"redownload_confirm_message": "إعادة التنزيل؟\n\n{title}",
|
||||
"redownload_started": "بدأ التنزيل"
|
||||
},
|
||||
"ffmpeg_updater": {
|
||||
"title": "محدث FFmpeg",
|
||||
"current_version": "الإصدار الحالي:",
|
||||
"latest_version": "أحدث إصدار:",
|
||||
"status_up_to_date": "✓ محدث",
|
||||
"status_update_available": "⚠ تحديث متاح",
|
||||
"status_not_installed": "✗ غير مثبت",
|
||||
"status_checking": "🔄 جارٍ التحقق من التحديثات...",
|
||||
"status_downloading": "⬇ جارٍ تنزيل FFmpeg...",
|
||||
"status_installing": "⚙ جارٍ تثبيت FFmpeg...",
|
||||
"status_verifying": "✓ جارٍ التحقق من السلامة...",
|
||||
"check_updates": "التحقق من التحديثات",
|
||||
"update_button": "تحديث FFmpeg",
|
||||
"update_success": "تم تحديث FFmpeg بنجاح إلى الإصدار {version}!",
|
||||
"update_failed": "فشل تحديث FFmpeg. يرجى المحاولة مرة أخرى.",
|
||||
"check_failed": "فشل التحقق من التحديثات. يرجى التحقق من اتصال الإنترنت.",
|
||||
"install_first": "FFmpeg غير مثبت. انقر فوق تحديث للتثبيت.",
|
||||
"description": "حافظ على تحديث FFmpeg للحصول على أفضل أداء وتوافق."
|
||||
}
|
||||
}
|
||||
+21
-1
@@ -99,7 +99,8 @@
|
||||
"cookies": "Mit Cookies anmelden",
|
||||
"custom_command": "Benutzerdefinierter Befehl",
|
||||
"proxy": "Proxy",
|
||||
"language": "Sprache"
|
||||
"language": "Sprache",
|
||||
"updater": "Updater"
|
||||
},
|
||||
"cookies": {
|
||||
"help_text": "Wählen Sie aus, wie Cookies für die Anmeldung bereitgestellt werden sollen.\nDies ermöglicht das Herunterladen privater Videos und hochwertiger Audio-Dateien.",
|
||||
@@ -466,5 +467,24 @@
|
||||
"redownload_confirm_title": "Erneut Laden?",
|
||||
"redownload_confirm_message": "Erneut herunterladen?\n\n{title}",
|
||||
"redownload_started": "Download gestartet"
|
||||
},
|
||||
"ffmpeg_updater": {
|
||||
"title": "FFmpeg-Updater",
|
||||
"current_version": "Aktuelle Version:",
|
||||
"latest_version": "Neueste Version:",
|
||||
"status_up_to_date": "✓ Auf dem neuesten Stand",
|
||||
"status_update_available": "⚠ Update verfügbar",
|
||||
"status_not_installed": "✗ Nicht installiert",
|
||||
"status_checking": "🔄 Suche nach Updates...",
|
||||
"status_downloading": "⬇ FFmpeg wird heruntergeladen...",
|
||||
"status_installing": "⚙ FFmpeg wird installiert...",
|
||||
"status_verifying": "✓ Integrität wird überprüft...",
|
||||
"check_updates": "Nach Updates suchen",
|
||||
"update_button": "FFmpeg aktualisieren",
|
||||
"update_success": "FFmpeg erfolgreich auf Version {version} aktualisiert!",
|
||||
"update_failed": "FFmpeg-Update fehlgeschlagen. Bitte versuchen Sie es erneut.",
|
||||
"check_failed": "Update-Prüfung fehlgeschlagen. Bitte überprüfen Sie Ihre Internetverbindung.",
|
||||
"install_first": "FFmpeg ist nicht installiert. Klicken Sie auf Aktualisieren, um zu installieren.",
|
||||
"description": "Halten Sie FFmpeg auf dem neuesten Stand für beste Leistung und Kompatibilität."
|
||||
}
|
||||
}
|
||||
+22
-1
@@ -99,7 +99,8 @@
|
||||
"cookies": "Login with Cookies",
|
||||
"custom_command": "Custom Command",
|
||||
"proxy": "Proxy",
|
||||
"language": "Language"
|
||||
"language": "Language",
|
||||
"updater": "Updater"
|
||||
},
|
||||
"cookies": {
|
||||
"help_text": "Choose how to provide cookies for logging in.\nThis allows downloading of private videos and premium quality audio.",
|
||||
@@ -466,5 +467,25 @@
|
||||
"redownload_confirm_title": "Redownload Video?",
|
||||
"redownload_confirm_message": "Download this video again using the same settings?\n\n{title}",
|
||||
"redownload_started": "Redownload started"
|
||||
},
|
||||
"ffmpeg_updater": {
|
||||
"title": "FFmpeg Updater",
|
||||
"current_version": "Current Version:",
|
||||
"latest_version": "Latest Version:",
|
||||
"status_up_to_date": "✓ Up to date",
|
||||
"status_update_available": "⚠ Update available",
|
||||
"status_not_installed": "✗ Not installed",
|
||||
"status_idle": "Click 'Check for Updates' to get started",
|
||||
"status_checking": "🔄 Checking for updates...",
|
||||
"status_downloading": "⬇ Downloading FFmpeg...",
|
||||
"status_installing": "⚙ Installing FFmpeg...",
|
||||
"status_verifying": "✓ Verifying integrity...",
|
||||
"check_updates": "Check for Updates",
|
||||
"update_button": "Update FFmpeg",
|
||||
"update_success": "FFmpeg successfully updated to version {version}!",
|
||||
"update_failed": "Failed to update FFmpeg. Please try again.",
|
||||
"check_failed": "Failed to check for updates. Please check your internet connection.",
|
||||
"install_first": "FFmpeg is not installed. Click Update to install.",
|
||||
"description": "Keep FFmpeg up to date for the best performance and compatibility."
|
||||
}
|
||||
}
|
||||
+21
-1
@@ -66,7 +66,8 @@
|
||||
"cookies": "Iniciar sesión con Cookies",
|
||||
"custom_command": "Comando Personalizado",
|
||||
"proxy": "Proxy",
|
||||
"language": "Idioma"
|
||||
"language": "Idioma",
|
||||
"updater": "Actualizador"
|
||||
},
|
||||
"cookies": {
|
||||
"help_text": "Elige cómo proporcionar cookies para iniciar sesión.\nEsto permite la descarga de videos privados y audio de calidad premium.",
|
||||
@@ -449,5 +450,24 @@
|
||||
"redownload_confirm_title": "¿Descargar de Nuevo?",
|
||||
"redownload_confirm_message": "¿Descargar nuevamente?\n\n{title}",
|
||||
"redownload_started": "Descarga iniciada"
|
||||
},
|
||||
"ffmpeg_updater": {
|
||||
"title": "Actualizador de FFmpeg",
|
||||
"current_version": "Versión Actual:",
|
||||
"latest_version": "Última Versión:",
|
||||
"status_up_to_date": "✓ Actualizado",
|
||||
"status_update_available": "⚠ Actualización disponible",
|
||||
"status_not_installed": "✗ No instalado",
|
||||
"status_checking": "🔄 Verificando actualizaciones...",
|
||||
"status_downloading": "⬇ Descargando FFmpeg...",
|
||||
"status_installing": "⚙ Instalando FFmpeg...",
|
||||
"status_verifying": "✓ Verificando integridad...",
|
||||
"check_updates": "Buscar Actualizaciones",
|
||||
"update_button": "Actualizar FFmpeg",
|
||||
"update_success": "¡FFmpeg actualizado exitosamente a la versión {version}!",
|
||||
"update_failed": "Error al actualizar FFmpeg. Por favor, inténtelo de nuevo.",
|
||||
"check_failed": "Error al verificar actualizaciones. Por favor, verifique su conexión a internet.",
|
||||
"install_first": "FFmpeg no está instalado. Haga clic en Actualizar para instalar.",
|
||||
"description": "Mantenga FFmpeg actualizado para el mejor rendimiento y compatibilidad."
|
||||
}
|
||||
}
|
||||
+21
-1
@@ -99,7 +99,8 @@
|
||||
"cookies": "Se connecter avec des cookies",
|
||||
"custom_command": "Commande personnalisée",
|
||||
"proxy": "Proxy",
|
||||
"language": "Langue"
|
||||
"language": "Langue",
|
||||
"updater": "Mise à jour"
|
||||
},
|
||||
"cookies": {
|
||||
"help_text": "Choisissez comment fournir les cookies pour l'authentification.\nCeci permet de télécharger des vidéos privées et des fichiers audio de haute qualité.",
|
||||
@@ -466,5 +467,24 @@
|
||||
"redownload_confirm_title": "Télécharger à Nouveau?",
|
||||
"redownload_confirm_message": "Télécharger à nouveau?\n\n{title}",
|
||||
"redownload_started": "Téléchargement démarré"
|
||||
},
|
||||
"ffmpeg_updater": {
|
||||
"title": "Mise à jour FFmpeg",
|
||||
"current_version": "Version Actuelle:",
|
||||
"latest_version": "Dernière Version:",
|
||||
"status_up_to_date": "✓ À jour",
|
||||
"status_update_available": "⚠ Mise à jour disponible",
|
||||
"status_not_installed": "✗ Non installé",
|
||||
"status_checking": "🔄 Vérification des mises à jour...",
|
||||
"status_downloading": "⬇ Téléchargement de FFmpeg...",
|
||||
"status_installing": "⚙ Installation de FFmpeg...",
|
||||
"status_verifying": "✓ Vérification de l'intégrité...",
|
||||
"check_updates": "Vérifier les mises à jour",
|
||||
"update_button": "Mettre à jour FFmpeg",
|
||||
"update_success": "FFmpeg mis à jour avec succès vers la version {version}!",
|
||||
"update_failed": "Échec de la mise à jour de FFmpeg. Veuillez réessayer.",
|
||||
"check_failed": "Échec de la vérification des mises à jour. Vérifiez votre connexion Internet.",
|
||||
"install_first": "FFmpeg n'est pas installé. Cliquez sur Mettre à jour pour installer.",
|
||||
"description": "Maintenez FFmpeg à jour pour les meilleures performances et compatibilité."
|
||||
}
|
||||
}
|
||||
+21
-1
@@ -99,7 +99,8 @@
|
||||
"cookies": "कुकीज़ के साथ लॉगिन",
|
||||
"custom_command": "कस्टम कमांड",
|
||||
"proxy": "प्रॉक्सी",
|
||||
"language": "भाषा"
|
||||
"language": "भाषा",
|
||||
"updater": "अपडेटर"
|
||||
},
|
||||
"cookies": {
|
||||
"help_text": "प्रमाणीकरण के लिए कुकीज़ प्रदान करने का तरीका चुनें।\nयह निजी वीडियो और उच्च गुणवत्ता वाली ऑडियो फ़ाइलें डाउनलोड करने की अनुमति देता है।",
|
||||
@@ -466,5 +467,24 @@
|
||||
"redownload_confirm_title": "फिर से डाउनलोड करें?",
|
||||
"redownload_confirm_message": "फिर से डाउनलोड करें?\n\n{title}",
|
||||
"redownload_started": "डाउनलोड शुरू हुआ"
|
||||
},
|
||||
"ffmpeg_updater": {
|
||||
"title": "FFmpeg अपडेटर",
|
||||
"current_version": "वर्तमान संस्करण:",
|
||||
"latest_version": "नवीनतम संस्करण:",
|
||||
"status_up_to_date": "✓ अप टू डेट",
|
||||
"status_update_available": "⚠ अपडेट उपलब्ध",
|
||||
"status_not_installed": "✗ इंस्टॉल नहीं है",
|
||||
"status_checking": "🔄 अपडेट जांच रहे हैं...",
|
||||
"status_downloading": "⬇ FFmpeg डाउनलोड हो रहा है...",
|
||||
"status_installing": "⚙ FFmpeg इंस्टॉल हो रहा है...",
|
||||
"status_verifying": "✓ अखंडता सत्यापित कर रहे हैं...",
|
||||
"check_updates": "अपडेट जांचें",
|
||||
"update_button": "FFmpeg अपडेट करें",
|
||||
"update_success": "FFmpeg सफलतापूर्वक संस्करण {version} में अपडेट किया गया!",
|
||||
"update_failed": "FFmpeg अपडेट विफल रहा। कृपया पुनः प्रयास करें।",
|
||||
"check_failed": "अपडेट जांच विफल रही। कृपया अपना इंटरनेट कनेक्शन जांचें।",
|
||||
"install_first": "FFmpeg इंस्टॉल नहीं है। इंस्टॉल करने के लिए अपडेट पर क्लिक करें।",
|
||||
"description": "सर्वोत्तम प्रदर्शन और संगतता के लिए FFmpeg को अप टू डेट रखें।"
|
||||
}
|
||||
}
|
||||
+21
-1
@@ -99,7 +99,8 @@
|
||||
"cookies": "Masuk dengan cookies",
|
||||
"custom_command": "Perintah khusus",
|
||||
"proxy": "Proxy",
|
||||
"language": "Bahasa"
|
||||
"language": "Bahasa",
|
||||
"updater": "Pembaruan"
|
||||
},
|
||||
"cookies": {
|
||||
"help_text": "Pilih cara memberikan cookies untuk autentikasi.\nIni memungkinkan pengunduhan video pribadi dan file audio berkualitas tinggi.",
|
||||
@@ -466,5 +467,24 @@
|
||||
"redownload_confirm_title": "Unduh Lagi?",
|
||||
"redownload_confirm_message": "Unduh lagi?\n\n{title}",
|
||||
"redownload_started": "Unduhan dimulai"
|
||||
},
|
||||
"ffmpeg_updater": {
|
||||
"title": "Pembaruan FFmpeg",
|
||||
"current_version": "Versi Saat Ini:",
|
||||
"latest_version": "Versi Terbaru:",
|
||||
"status_up_to_date": "✓ Terbaru",
|
||||
"status_update_available": "⚠ Pembaruan tersedia",
|
||||
"status_not_installed": "✗ Tidak terinstal",
|
||||
"status_checking": "🔄 Memeriksa pembaruan...",
|
||||
"status_downloading": "⬇ Mengunduh FFmpeg...",
|
||||
"status_installing": "⚙ Menginstal FFmpeg...",
|
||||
"status_verifying": "✓ Memverifikasi integritas...",
|
||||
"check_updates": "Periksa Pembaruan",
|
||||
"update_button": "Perbarui FFmpeg",
|
||||
"update_success": "FFmpeg berhasil diperbarui ke versi {version}!",
|
||||
"update_failed": "Gagal memperbarui FFmpeg. Silakan coba lagi.",
|
||||
"check_failed": "Gagal memeriksa pembaruan. Periksa koneksi internet Anda.",
|
||||
"install_first": "FFmpeg tidak terinstal. Klik Perbarui untuk menginstal.",
|
||||
"description": "Jaga FFmpeg tetap terbaru untuk performa dan kompatibilitas terbaik."
|
||||
}
|
||||
}
|
||||
+21
-1
@@ -99,7 +99,8 @@
|
||||
"cookies": "Accedi con i cookie",
|
||||
"custom_command": "Comando personalizzato",
|
||||
"proxy": "Proxy",
|
||||
"language": "Lingua"
|
||||
"language": "Lingua",
|
||||
"updater": "Aggiornamento"
|
||||
},
|
||||
"cookies": {
|
||||
"help_text": "Seleziona un metodo per fornire i cookie per l'autenticazione.\nCiò consente di scaricare video privati e file audio di alta qualità.",
|
||||
@@ -466,5 +467,24 @@
|
||||
"redownload_confirm_title": "Scaricare di Nuovo?",
|
||||
"redownload_confirm_message": "Scaricare di nuovo?\n\n{title}",
|
||||
"redownload_started": "Download avviato"
|
||||
},
|
||||
"ffmpeg_updater": {
|
||||
"title": "Aggiornamento FFmpeg",
|
||||
"current_version": "Versione corrente:",
|
||||
"latest_version": "Ultima versione:",
|
||||
"status_up_to_date": "✓ Aggiornato",
|
||||
"status_update_available": "⚠ Aggiornamento disponibile",
|
||||
"status_not_installed": "✗ Non installato",
|
||||
"status_checking": "🔄 Verifica aggiornamenti...",
|
||||
"status_downloading": "⬇ Download di FFmpeg...",
|
||||
"status_installing": "⚙ Installazione di FFmpeg...",
|
||||
"status_verifying": "✓ Verifica integrità...",
|
||||
"check_updates": "Verifica aggiornamenti",
|
||||
"update_button": "Aggiorna FFmpeg",
|
||||
"update_success": "FFmpeg aggiornato con successo alla versione {version}!",
|
||||
"update_failed": "Aggiornamento di FFmpeg non riuscito. Riprova.",
|
||||
"check_failed": "Verifica aggiornamenti non riuscita. Controlla la connessione Internet.",
|
||||
"install_first": "FFmpeg non è installato. Clicca Aggiorna per installare.",
|
||||
"description": "Mantieni FFmpeg aggiornato per le migliori prestazioni e compatibilità."
|
||||
}
|
||||
}
|
||||
+21
-1
@@ -99,7 +99,8 @@
|
||||
"cookies": "Cookieでログイン",
|
||||
"custom_command": "カスタムコマンド",
|
||||
"proxy": "プロキシ",
|
||||
"language": "言語"
|
||||
"language": "言語",
|
||||
"updater": "アップデーター"
|
||||
},
|
||||
"cookies": {
|
||||
"help_text": "認証用のCookieを提供する方法を選択してください。\nこれにより、プライベート動画や高品質の音声ファイルをダウンロードできます。",
|
||||
@@ -466,5 +467,24 @@
|
||||
"redownload_confirm_title": "再ダウンロード?",
|
||||
"redownload_confirm_message": "再ダウンロードしますか?\n\n{title}",
|
||||
"redownload_started": "ダウンロード開始"
|
||||
},
|
||||
"ffmpeg_updater": {
|
||||
"title": "FFmpegアップデーター",
|
||||
"current_version": "現在のバージョン:",
|
||||
"latest_version": "最新バージョン:",
|
||||
"status_up_to_date": "✓ 最新",
|
||||
"status_update_available": "⚠ 更新可能",
|
||||
"status_not_installed": "✗ 未インストール",
|
||||
"status_checking": "🔄 更新を確認中...",
|
||||
"status_downloading": "⬇ FFmpegをダウンロード中...",
|
||||
"status_installing": "⚙ FFmpegをインストール中...",
|
||||
"status_verifying": "✓ 整合性を検証中...",
|
||||
"check_updates": "更新を確認",
|
||||
"update_button": "FFmpegを更新",
|
||||
"update_success": "FFmpegがバージョン{version}に更新されました!",
|
||||
"update_failed": "FFmpegの更新に失敗しました。再試行してください。",
|
||||
"check_failed": "更新の確認に失敗しました。インターネット接続を確認してください。",
|
||||
"install_first": "FFmpegがインストールされていません。インストールするには「更新」をクリックしてください。",
|
||||
"description": "最高のパフォーマンスと互換性のためにFFmpegを最新の状態に保ってください。"
|
||||
}
|
||||
}
|
||||
+21
-1
@@ -99,7 +99,8 @@
|
||||
"cookies": "Zaloguj za pomocą ciasteczek",
|
||||
"custom_command": "Polecenie niestandardowe",
|
||||
"proxy": "Proxy",
|
||||
"language": "Język"
|
||||
"language": "Język",
|
||||
"updater": "Aktualizator"
|
||||
},
|
||||
"cookies": {
|
||||
"help_text": "Wybierz sposób dostarczania ciasteczek do uwierzytelniania.\nUmożliwia to pobieranie prywatnych filmów i wysokiej jakości plików audio.",
|
||||
@@ -466,5 +467,24 @@
|
||||
"redownload_confirm_title": "Pobrać Ponownie?",
|
||||
"redownload_confirm_message": "Pobrać ponownie?\n\n{title}",
|
||||
"redownload_started": "Rozpoczęto pobieranie"
|
||||
},
|
||||
"ffmpeg_updater": {
|
||||
"title": "Aktualizator FFmpeg",
|
||||
"current_version": "Obecna wersja:",
|
||||
"latest_version": "Najnowsza wersja:",
|
||||
"status_up_to_date": "✓ Aktualne",
|
||||
"status_update_available": "⚠ Dostępna aktualizacja",
|
||||
"status_not_installed": "✗ Nie zainstalowano",
|
||||
"status_checking": "🔄 Sprawdzanie aktualizacji...",
|
||||
"status_downloading": "⬇ Pobieranie FFmpeg...",
|
||||
"status_installing": "⚙ Instalowanie FFmpeg...",
|
||||
"status_verifying": "✓ Weryfikacja integralności...",
|
||||
"check_updates": "Sprawdź aktualizacje",
|
||||
"update_button": "Aktualizuj FFmpeg",
|
||||
"update_success": "FFmpeg zaktualizowano pomyślnie do wersji {version}!",
|
||||
"update_failed": "Nie udało się zaktualizować FFmpeg. Spróbuj ponownie.",
|
||||
"check_failed": "Nie udało się sprawdzić aktualizacji. Sprawdź połączenie internetowe.",
|
||||
"install_first": "FFmpeg nie jest zainstalowany. Kliknij Aktualizuj, aby zainstalować.",
|
||||
"description": "Utrzymuj FFmpeg w aktualnej wersji dla najlepszej wydajności i kompatybilności."
|
||||
}
|
||||
}
|
||||
+21
-1
@@ -99,7 +99,8 @@
|
||||
"cookies": "Entrar com Cookies",
|
||||
"custom_command": "Comando Personalizado",
|
||||
"proxy": "Proxy",
|
||||
"language": "Idioma"
|
||||
"language": "Idioma",
|
||||
"updater": "Atualizador"
|
||||
},
|
||||
"cookies": {
|
||||
"help_text": "Escolha como fornecer cookies para fazer login.\nIsso permite baixar vídeos privados e áudio de qualidade premium.",
|
||||
@@ -466,5 +467,24 @@
|
||||
"redownload_confirm_title": "Baixar Novamente?",
|
||||
"redownload_confirm_message": "Baixar novamente?\n\n{title}",
|
||||
"redownload_started": "Download iniciado"
|
||||
},
|
||||
"ffmpeg_updater": {
|
||||
"title": "Atualizador FFmpeg",
|
||||
"current_version": "Versão Atual:",
|
||||
"latest_version": "Última Versão:",
|
||||
"status_up_to_date": "✓ Atualizado",
|
||||
"status_update_available": "⚠ Atualização disponível",
|
||||
"status_not_installed": "✗ Não instalado",
|
||||
"status_checking": "🔄 Verificando atualizações...",
|
||||
"status_downloading": "⬇ Baixando FFmpeg...",
|
||||
"status_installing": "⚙ Instalando FFmpeg...",
|
||||
"status_verifying": "✓ Verificando integridade...",
|
||||
"check_updates": "Verificar Atualizações",
|
||||
"update_button": "Atualizar FFmpeg",
|
||||
"update_success": "FFmpeg atualizado com sucesso para a versão {version}!",
|
||||
"update_failed": "Falha ao atualizar FFmpeg. Tente novamente.",
|
||||
"check_failed": "Falha ao verificar atualizações. Verifique sua conexão com a Internet.",
|
||||
"install_first": "FFmpeg não está instalado. Clique em Atualizar para instalar.",
|
||||
"description": "Mantenha o FFmpeg atualizado para melhor desempenho e compatibilidade."
|
||||
}
|
||||
}
|
||||
+21
-1
@@ -99,7 +99,8 @@
|
||||
"cookies": "Войти через Cookie",
|
||||
"custom_command": "Пользовательская команда",
|
||||
"proxy": "Прокси",
|
||||
"language": "Язык"
|
||||
"language": "Язык",
|
||||
"updater": "Обновление"
|
||||
},
|
||||
"cookies": {
|
||||
"help_text": "Выберите способ предоставления cookie для входа в систему.\nЭто позволяет загружать приватные видео и аудио премиум качества.",
|
||||
@@ -466,5 +467,24 @@
|
||||
"redownload_confirm_title": "Загрузить Снова?",
|
||||
"redownload_confirm_message": "Загрузить снова?\n\n{title}",
|
||||
"redownload_started": "Загрузка начата"
|
||||
},
|
||||
"ffmpeg_updater": {
|
||||
"title": "Обновление FFmpeg",
|
||||
"current_version": "Текущая версия:",
|
||||
"latest_version": "Последняя версия:",
|
||||
"status_up_to_date": "✓ Обновлено",
|
||||
"status_update_available": "⚠ Доступно обновление",
|
||||
"status_not_installed": "✗ Не установлено",
|
||||
"status_checking": "🔄 Проверка обновлений...",
|
||||
"status_downloading": "⬇ Загрузка FFmpeg...",
|
||||
"status_installing": "⚙ Установка FFmpeg...",
|
||||
"status_verifying": "✓ Проверка целостности...",
|
||||
"check_updates": "Проверить обновления",
|
||||
"update_button": "Обновить FFmpeg",
|
||||
"update_success": "FFmpeg успешно обновлен до версии {version}!",
|
||||
"update_failed": "Не удалось обновить FFmpeg. Попробуйте еще раз.",
|
||||
"check_failed": "Не удалось проверить обновления. Проверьте подключение к Интернету.",
|
||||
"install_first": "FFmpeg не установлен. Нажмите Обновить для установки.",
|
||||
"description": "Поддерживайте FFmpeg в актуальном состоянии для лучшей производительности и совместимости."
|
||||
}
|
||||
}
|
||||
+21
-1
@@ -99,7 +99,8 @@
|
||||
"cookies": "Çerezlerle giriş yap",
|
||||
"custom_command": "Özel komut",
|
||||
"proxy": "Proxy",
|
||||
"language": "Dil"
|
||||
"language": "Dil",
|
||||
"updater": "Güncelleyici"
|
||||
},
|
||||
"cookies": {
|
||||
"help_text": "Kimlik doğrulama için çerezlerin nasıl sağlanacağını seçin.\nBu, özel videoları ve yüksek kaliteli ses dosyalarını indirmeyi sağlar.",
|
||||
@@ -466,5 +467,24 @@
|
||||
"redownload_confirm_title": "Tekrar İndir?",
|
||||
"redownload_confirm_message": "Tekrar indir?\n\n{title}",
|
||||
"redownload_started": "İndirme başladı"
|
||||
},
|
||||
"ffmpeg_updater": {
|
||||
"title": "FFmpeg Güncelleyici",
|
||||
"current_version": "Mevcut Sürüm:",
|
||||
"latest_version": "En Son Sürüm:",
|
||||
"status_up_to_date": "✓ Güncel",
|
||||
"status_update_available": "⚠ Güncelleme mevcut",
|
||||
"status_not_installed": "✗ Yüklü değil",
|
||||
"status_checking": "🔄 Güncellemeler kontrol ediliyor...",
|
||||
"status_downloading": "⬇ FFmpeg indiriliyor...",
|
||||
"status_installing": "⚙ FFmpeg yükleniyor...",
|
||||
"status_verifying": "✓ Bütünlük doğrulanıyor...",
|
||||
"check_updates": "Güncellemeleri Kontrol Et",
|
||||
"update_button": "FFmpeg'i Güncelle",
|
||||
"update_success": "FFmpeg başarıyla {version} sürümüne güncellendi!",
|
||||
"update_failed": "FFmpeg güncellemesi başarısız oldu. Lütfen tekrar deneyin.",
|
||||
"check_failed": "Güncelleme kontrolü başarısız oldu. İnternet bağlantınızı kontrol edin.",
|
||||
"install_first": "FFmpeg yüklü değil. Yüklemek için Güncelle'ye tıklayın.",
|
||||
"description": "En iyi performans ve uyumluluk için FFmpeg'i güncel tutun."
|
||||
}
|
||||
}
|
||||
+21
-1
@@ -99,7 +99,8 @@
|
||||
"cookies": "使用 Cookie 登录",
|
||||
"custom_command": "自定义命令",
|
||||
"proxy": "代理",
|
||||
"language": "语言"
|
||||
"language": "语言",
|
||||
"updater": "更新器"
|
||||
},
|
||||
"cookies": {
|
||||
"help_text": "选择如何提供 cookie 来登录。\n这允许下载私人视频和高级质量音频。",
|
||||
@@ -466,5 +467,24 @@
|
||||
"redownload_confirm_title": "重新下载?",
|
||||
"redownload_confirm_message": "重新下载?\n\n{title}",
|
||||
"redownload_started": "已开始下载"
|
||||
},
|
||||
"ffmpeg_updater": {
|
||||
"title": "FFmpeg 更新器",
|
||||
"current_version": "当前版本:",
|
||||
"latest_version": "最新版本:",
|
||||
"status_up_to_date": "✓ 已是最新",
|
||||
"status_update_available": "⚠ 有可用更新",
|
||||
"status_not_installed": "✗ 未安装",
|
||||
"status_checking": "🔄 正在检查更新...",
|
||||
"status_downloading": "⬇ 正在下载 FFmpeg...",
|
||||
"status_installing": "⚙ 正在安装 FFmpeg...",
|
||||
"status_verifying": "✓ 正在验证完整性...",
|
||||
"check_updates": "检查更新",
|
||||
"update_button": "更新 FFmpeg",
|
||||
"update_success": "FFmpeg 已成功更新到版本 {version}!",
|
||||
"update_failed": "更新 FFmpeg 失败。请重试。",
|
||||
"check_failed": "检查更新失败。请检查您的网络连接。",
|
||||
"install_first": "FFmpeg 未安装。点击更新以安装。",
|
||||
"description": "保持 FFmpeg 更新以获得最佳性能和兼容性。"
|
||||
}
|
||||
}
|
||||
+36
-93
@@ -85,9 +85,26 @@ def verify_sha256(file_path, expected_hash_url) -> bool:
|
||||
|
||||
|
||||
def get_ffmpeg_install_path() -> Path:
|
||||
"""Get the FFmpeg installation path."""
|
||||
"""
|
||||
Get the FFmpeg installation path.
|
||||
For Windows, tries to find the latest essentials build dynamically.
|
||||
"""
|
||||
if OS_NAME == "Windows":
|
||||
return Path(os.getenv("LOCALAPPDATA")) / "ffmpeg" / "ffmpeg-7.1.1-full_build" / "bin" # type: ignore
|
||||
ffmpeg_base = Path(os.getenv("LOCALAPPDATA")) / "ffmpeg" # type: ignore
|
||||
|
||||
# If the directory exists, look for any ffmpeg-*-essentials_build folder
|
||||
if ffmpeg_base.exists():
|
||||
# Find all directories matching the pattern
|
||||
essentials_dirs = list(ffmpeg_base.glob("ffmpeg-*-essentials_build"))
|
||||
if essentials_dirs:
|
||||
# Sort by name (which includes version) and take the latest
|
||||
latest_dir = sorted(essentials_dirs, reverse=True)[0]
|
||||
bin_dir = latest_dir / "bin"
|
||||
if bin_dir.exists():
|
||||
return bin_dir
|
||||
|
||||
# Fallback: return default path (even if it doesn't exist yet)
|
||||
return ffmpeg_base / "ffmpeg-essentials_build" / "bin"
|
||||
|
||||
elif OS_NAME == "Darwin":
|
||||
paths = ["/usr/local/bin", "/opt/homebrew/bin", "/usr/bin"]
|
||||
@@ -99,6 +116,7 @@ def get_ffmpeg_install_path() -> Path:
|
||||
return Path("/usr/bin") # Standard Linux path
|
||||
|
||||
|
||||
|
||||
def get_ffmpeg_path() -> str | Path:
|
||||
"""
|
||||
Get the FFmpeg executable path, either from PATH or installation directory.
|
||||
@@ -175,106 +193,29 @@ def check_ffmpeg_installed() -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def install_ffmpeg_windows() -> bool:
|
||||
def install_ffmpeg_windows(progress_callback=None) -> bool:
|
||||
"""Install FFmpeg on Windows using 7z method primarily, with zip as fallback."""
|
||||
ffmpeg_path = get_ffmpeg_install_path()
|
||||
|
||||
# Check if already installed
|
||||
if check_ffmpeg_installed():
|
||||
logger.info("FFmpeg is already installed!")
|
||||
if progress_callback:
|
||||
progress_callback("✅ FFmpeg is already installed!")
|
||||
return True
|
||||
|
||||
try:
|
||||
# Define variables - prioritize 7z version
|
||||
# ffmpeg variables moved to src\utils\ytsage_constants.py
|
||||
extract_dir = Path(os.getenv("LOCALAPPDATA")) / "ffmpeg" # type: ignore
|
||||
full_build_dir = extract_dir / "ffmpeg-7.1.1-full_build"
|
||||
bin_dir = full_build_dir / "bin"
|
||||
# Import the updater for installation
|
||||
from src.core.ytsage_ffmpeg_updater import update_ffmpeg_windows
|
||||
|
||||
# Create extraction directory if it doesn't exist
|
||||
extract_dir.mkdir(exist_ok=True)
|
||||
|
||||
# Try 7z method first (smaller size)
|
||||
use_7zip = check_7zip_installed()
|
||||
if use_7zip:
|
||||
logger.info("Using 7-Zip method (smaller download size)...")
|
||||
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".7z").name
|
||||
|
||||
# Download 7z file
|
||||
if not download_file(
|
||||
FFMPEG_7Z_DOWNLOAD_URL,
|
||||
temp_file,
|
||||
progress_callback=lambda msg: logger.debug(msg),
|
||||
):
|
||||
logger.error("Failed to download 7z file, trying zip fallback...")
|
||||
use_7zip = False
|
||||
else:
|
||||
# Verify SHA-256 hash for 7z file
|
||||
if verify_sha256(temp_file, FFMPEG_7Z_SHA256_URL):
|
||||
logger.info("Extracting FFmpeg components from 7z archive...")
|
||||
try:
|
||||
subprocess.run(
|
||||
["7z", "x", temp_file, f"-o{extract_dir}", "-y"],
|
||||
creationflags=SUBPROCESS_CREATIONFLAGS,
|
||||
timeout=300,
|
||||
) # 5-minute timeout
|
||||
except Exception as e:
|
||||
logger.exception(f"7z extraction failed: {e}, trying zip fallback...")
|
||||
use_7zip = False
|
||||
else:
|
||||
logger.error("SHA-256 verification failed for 7z file, trying zip fallback...")
|
||||
use_7zip = False
|
||||
|
||||
# Fallback to zip method if 7z failed or not available
|
||||
if not use_7zip:
|
||||
logger.info("Using ZIP method as fallback...")
|
||||
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".zip").name
|
||||
|
||||
# Download zip file
|
||||
if not download_file(
|
||||
FFMPEG_ZIP_DOWNLOAD_URL,
|
||||
temp_file,
|
||||
progress_callback=lambda msg: logger.debug(msg),
|
||||
):
|
||||
logger.exception("Failed to download FFmpeg (both 7z and zip methods failed)")
|
||||
return False
|
||||
|
||||
logger.info("Extracting FFmpeg components from zip archive...")
|
||||
try:
|
||||
import zipfile
|
||||
|
||||
with zipfile.ZipFile(temp_file, "r") as zip_ref:
|
||||
zip_ref.extractall(extract_dir)
|
||||
except Exception as e:
|
||||
logger.exception(f"Extraction failed: {e}")
|
||||
return False
|
||||
|
||||
logger.info("Configuring system paths...")
|
||||
# Add to System Path
|
||||
user_path = os.environ.get("PATH", "")
|
||||
if str(bin_dir) not in user_path.split(os.pathsep):
|
||||
subprocess.run(
|
||||
["setx", "PATH", f"{user_path};{bin_dir}"],
|
||||
creationflags=SUBPROCESS_CREATIONFLAGS,
|
||||
)
|
||||
os.environ["PATH"] = f"{user_path};{bin_dir}"
|
||||
|
||||
# Clean up
|
||||
try:
|
||||
Path(temp_file).unlink(missing_ok=True)
|
||||
except Exception:
|
||||
pass # Ignore cleanup errors
|
||||
|
||||
# Verify installation
|
||||
if not check_ffmpeg_installed():
|
||||
logger.error("FFmpeg installation verification failed")
|
||||
return False
|
||||
|
||||
logger.info("FFmpeg installation completed successfully!")
|
||||
return True
|
||||
# Use the updater to install the latest version
|
||||
logger.info("Installing FFmpeg using the updater module...")
|
||||
if progress_callback:
|
||||
progress_callback("📦 Installing FFmpeg...")
|
||||
return update_ffmpeg_windows(progress_callback=progress_callback)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"Error installing FFmpeg: {e}")
|
||||
if progress_callback:
|
||||
progress_callback(f"❌ Error installing FFmpeg: {e}")
|
||||
return False
|
||||
|
||||
|
||||
@@ -348,14 +289,16 @@ def install_ffmpeg_linux() -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def auto_install_ffmpeg() -> bool:
|
||||
def auto_install_ffmpeg(progress_callback=None) -> bool:
|
||||
"""Automatically install FFmpeg based on the operating system."""
|
||||
if OS_NAME == "Windows":
|
||||
return install_ffmpeg_windows()
|
||||
return install_ffmpeg_windows(progress_callback=progress_callback)
|
||||
elif OS_NAME == "Darwin":
|
||||
return install_ffmpeg_macos()
|
||||
elif OS_NAME == "Linux":
|
||||
return install_ffmpeg_linux()
|
||||
else:
|
||||
logger.info(f"Unsupported operating system: {OS_NAME}")
|
||||
if progress_callback:
|
||||
progress_callback(f"❌ Unsupported operating system: {OS_NAME}")
|
||||
return False
|
||||
|
||||
@@ -0,0 +1,413 @@
|
||||
"""
|
||||
FFmpeg updater module for YTSage application.
|
||||
Handles checking for updates and updating FFmpeg to the latest Essentials build.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Optional, Tuple, Callable
|
||||
|
||||
import requests
|
||||
|
||||
from src.utils.ytsage_logger import logger
|
||||
from src.utils.ytsage_constants import (
|
||||
FFMPEG_7Z_DOWNLOAD_URL,
|
||||
FFMPEG_7Z_SHA256_URL,
|
||||
FFMPEG_7Z_VERSION_URL,
|
||||
FFMPEG_ZIP_DOWNLOAD_URL,
|
||||
FFMPEG_ZIP_SHA256_URL,
|
||||
FFMPEG_ZIP_VERSION_URL,
|
||||
OS_NAME,
|
||||
SUBPROCESS_CREATIONFLAGS,
|
||||
)
|
||||
from src.core.ytsage_ffmpeg import (
|
||||
check_7zip_installed,
|
||||
download_file,
|
||||
get_file_sha256,
|
||||
verify_sha256,
|
||||
get_ffmpeg_install_path,
|
||||
)
|
||||
from src.core.ytsage_utils import get_ffmpeg_version_direct
|
||||
|
||||
|
||||
def get_latest_ffmpeg_version() -> Optional[str]:
|
||||
"""
|
||||
Fetch the latest FFmpeg version from the version URL.
|
||||
|
||||
Returns:
|
||||
str: Version string (e.g., "8.0") or None if fetch failed
|
||||
"""
|
||||
try:
|
||||
# Try 7z version URL first
|
||||
response = requests.get(FFMPEG_7Z_VERSION_URL, timeout=10)
|
||||
response.raise_for_status()
|
||||
version = response.text.strip()
|
||||
|
||||
# Validate version format (should be something like "8.0" or "7.1.1")
|
||||
if re.match(r'^\d+\.\d+(\.\d+)?$', version):
|
||||
logger.info(f"Latest FFmpeg version: {version}")
|
||||
return version
|
||||
else:
|
||||
logger.warning(f"Unexpected version format: {version}")
|
||||
return None
|
||||
|
||||
except requests.RequestException as e:
|
||||
logger.error(f"Failed to fetch latest FFmpeg version: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.exception(f"Unexpected error fetching FFmpeg version: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def parse_version(version_str: str) -> Tuple[int, ...]:
|
||||
"""
|
||||
Parse version string into tuple of integers for comparison.
|
||||
|
||||
Args:
|
||||
version_str: Version string like "8.0" or "7.1.1"
|
||||
|
||||
Returns:
|
||||
Tuple of integers (e.g., (8, 0) or (7, 1, 1))
|
||||
"""
|
||||
try:
|
||||
# Extract version numbers from string
|
||||
# Handles formats like "8.0", "7.1.1", "ffmpeg version 8.0", etc.
|
||||
match = re.search(r'(\d+\.\d+(?:\.\d+)?)', version_str)
|
||||
if match:
|
||||
version_str = match.group(1)
|
||||
|
||||
parts = version_str.split('.')
|
||||
return tuple(int(p) for p in parts)
|
||||
except (ValueError, AttributeError):
|
||||
logger.warning(f"Could not parse version: {version_str}")
|
||||
return (0,)
|
||||
|
||||
|
||||
def compare_versions(current: str, latest: str) -> bool:
|
||||
"""
|
||||
Compare two version strings.
|
||||
|
||||
Args:
|
||||
current: Current version string
|
||||
latest: Latest version string
|
||||
|
||||
Returns:
|
||||
True if update is needed (latest > current), False otherwise
|
||||
"""
|
||||
try:
|
||||
current_tuple = parse_version(current)
|
||||
latest_tuple = parse_version(latest)
|
||||
|
||||
logger.info(f"Comparing versions - Current: {current_tuple}, Latest: {latest_tuple}")
|
||||
|
||||
# Pad shorter version with zeros for comparison
|
||||
max_len = max(len(current_tuple), len(latest_tuple))
|
||||
current_padded = current_tuple + (0,) * (max_len - len(current_tuple))
|
||||
latest_padded = latest_tuple + (0,) * (max_len - len(latest_tuple))
|
||||
|
||||
return latest_padded > current_padded
|
||||
except Exception as e:
|
||||
logger.exception(f"Error comparing versions: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def check_ffmpeg_update_available() -> Tuple[bool, str, str]:
|
||||
"""
|
||||
Check if FFmpeg update is available.
|
||||
|
||||
Returns:
|
||||
Tuple of (update_available, current_version, latest_version)
|
||||
- update_available: True if update is needed
|
||||
- current_version: Currently installed version or "Not installed"
|
||||
- latest_version: Latest available version or "Unknown"
|
||||
"""
|
||||
try:
|
||||
# Get current version
|
||||
current_version = get_ffmpeg_version_direct()
|
||||
if current_version in ["Not found", "Error getting version", "Unknown version"]:
|
||||
current_version = "Not installed"
|
||||
|
||||
# Get latest version
|
||||
latest_version = get_latest_ffmpeg_version()
|
||||
if latest_version is None:
|
||||
latest_version = "Unknown"
|
||||
return False, current_version, latest_version
|
||||
|
||||
# If not installed, update is needed
|
||||
if current_version == "Not installed":
|
||||
return True, current_version, latest_version
|
||||
|
||||
# Compare versions
|
||||
update_needed = compare_versions(current_version, latest_version)
|
||||
|
||||
return update_needed, current_version, latest_version
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"Error checking for FFmpeg updates: {e}")
|
||||
return False, "Error", "Error"
|
||||
|
||||
|
||||
def get_ffmpeg_extract_dir() -> Path:
|
||||
"""Get the directory where FFmpeg should be extracted."""
|
||||
if OS_NAME == "Windows":
|
||||
return Path(os.getenv("LOCALAPPDATA")) / "ffmpeg" # type: ignore
|
||||
elif OS_NAME == "Darwin":
|
||||
return Path.home() / "Library" / "Application Support" / "ffmpeg"
|
||||
else:
|
||||
return Path.home() / ".local" / "share" / "ffmpeg"
|
||||
|
||||
|
||||
def find_ffmpeg_bin_dir(extract_dir: Path, version: str) -> Optional[Path]:
|
||||
"""
|
||||
Find the FFmpeg bin directory after extraction.
|
||||
|
||||
Args:
|
||||
extract_dir: Directory where FFmpeg was extracted
|
||||
version: Version string (e.g., "8.0")
|
||||
|
||||
Returns:
|
||||
Path to bin directory or None if not found
|
||||
"""
|
||||
# Expected pattern: ffmpeg-{version}-essentials_build
|
||||
pattern = f"ffmpeg-{version}-essentials_build"
|
||||
|
||||
# Look for the directory
|
||||
for item in extract_dir.iterdir():
|
||||
if item.is_dir() and item.name.startswith(f"ffmpeg-{version}"):
|
||||
bin_dir = item / "bin"
|
||||
if bin_dir.exists():
|
||||
logger.info(f"Found FFmpeg bin directory: {bin_dir}")
|
||||
return bin_dir
|
||||
|
||||
logger.warning(f"Could not find FFmpeg bin directory with pattern: {pattern}")
|
||||
return None
|
||||
|
||||
|
||||
def update_ffmpeg_windows(progress_callback: Optional[Callable[[str], None]] = None) -> bool:
|
||||
"""
|
||||
Update FFmpeg on Windows to the latest Essentials build.
|
||||
|
||||
Args:
|
||||
progress_callback: Optional callback function for progress updates
|
||||
|
||||
Returns:
|
||||
True if update was successful, False otherwise
|
||||
"""
|
||||
if OS_NAME != "Windows":
|
||||
logger.error("This update method is only for Windows")
|
||||
return False
|
||||
|
||||
try:
|
||||
# Track last progress percentage to avoid spam
|
||||
last_percent = -1
|
||||
|
||||
def report_progress(msg: str, force: bool = False):
|
||||
"""Helper to report progress (always logged, callback only if force=True or status message)."""
|
||||
logger.info(msg)
|
||||
|
||||
# Only send callback for non-percentage messages or when forced
|
||||
if progress_callback and (force or not msg.strip().endswith('%')):
|
||||
progress_callback(msg)
|
||||
|
||||
def download_progress_wrapper(msg: str):
|
||||
"""Wrapper for download progress that throttles percentage updates."""
|
||||
nonlocal last_percent
|
||||
|
||||
# Extract percentage if present
|
||||
percent_match = re.search(r'(\d+)%', msg)
|
||||
if percent_match:
|
||||
current_percent = int(percent_match.group(1))
|
||||
# Only report every 10% and to logger only
|
||||
if current_percent != last_percent and current_percent % 10 == 0:
|
||||
logger.info(msg)
|
||||
if progress_callback:
|
||||
progress_callback(msg)
|
||||
last_percent = current_percent
|
||||
else:
|
||||
# Non-percentage messages get reported normally
|
||||
report_progress(msg, force=True)
|
||||
|
||||
# Get latest version
|
||||
report_progress("🔍 Checking latest FFmpeg version...", force=True)
|
||||
latest_version = get_latest_ffmpeg_version()
|
||||
if not latest_version:
|
||||
report_progress("❌ Failed to determine latest version", force=True)
|
||||
return False
|
||||
|
||||
report_progress(f"📦 Latest version: {latest_version}", force=True)
|
||||
|
||||
extract_dir = get_ffmpeg_extract_dir()
|
||||
extract_dir.mkdir(exist_ok=True)
|
||||
|
||||
# Try 7z method first (smaller size)
|
||||
use_7zip = check_7zip_installed()
|
||||
success = False
|
||||
|
||||
if use_7zip:
|
||||
report_progress("⚡ Using 7-Zip method (smaller download)...", force=True)
|
||||
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".7z").name
|
||||
|
||||
# Download 7z file
|
||||
report_progress("⬇ Downloading FFmpeg (7z)...", force=True)
|
||||
last_percent = -1 # Reset for this download
|
||||
if download_file(
|
||||
FFMPEG_7Z_DOWNLOAD_URL,
|
||||
temp_file,
|
||||
progress_callback=download_progress_wrapper,
|
||||
):
|
||||
# Verify SHA-256 hash
|
||||
report_progress("🔐 Verifying download integrity...", force=True)
|
||||
if verify_sha256(temp_file, FFMPEG_7Z_SHA256_URL):
|
||||
report_progress("⚙ Extracting FFmpeg...", force=True)
|
||||
try:
|
||||
subprocess.run(
|
||||
["7z", "x", temp_file, f"-o{extract_dir}", "-y"],
|
||||
creationflags=SUBPROCESS_CREATIONFLAGS,
|
||||
timeout=300,
|
||||
check=True,
|
||||
)
|
||||
success = True
|
||||
except Exception as e:
|
||||
logger.exception(f"7z extraction failed: {e}")
|
||||
report_progress(f"❌ 7z extraction failed, trying zip fallback...", force=True)
|
||||
else:
|
||||
report_progress("❌ SHA-256 verification failed, trying zip fallback...", force=True)
|
||||
|
||||
# Clean up temp file
|
||||
try:
|
||||
Path(temp_file).unlink(missing_ok=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Fallback to zip method
|
||||
if not success:
|
||||
report_progress("📦 Using ZIP method...", force=True)
|
||||
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".zip").name
|
||||
|
||||
# Download zip file
|
||||
report_progress("⬇ Downloading FFmpeg (zip)...", force=True)
|
||||
last_percent = -1 # Reset for this download
|
||||
if not download_file(
|
||||
FFMPEG_ZIP_DOWNLOAD_URL,
|
||||
temp_file,
|
||||
progress_callback=download_progress_wrapper,
|
||||
):
|
||||
report_progress("❌ Failed to download FFmpeg", force=True)
|
||||
return False
|
||||
|
||||
# Verify SHA-256 hash for zip
|
||||
report_progress("🔐 Verifying download integrity...", force=True)
|
||||
if not verify_sha256(temp_file, FFMPEG_ZIP_SHA256_URL):
|
||||
report_progress("⚠️ SHA-256 verification failed, proceeding anyway...", force=True)
|
||||
|
||||
report_progress("⚙ Extracting FFmpeg...", force=True)
|
||||
try:
|
||||
import zipfile
|
||||
with zipfile.ZipFile(temp_file, "r") as zip_ref:
|
||||
zip_ref.extractall(extract_dir)
|
||||
success = True
|
||||
except Exception as e:
|
||||
logger.exception(f"Extraction failed: {e}")
|
||||
report_progress(f"❌ Extraction failed: {e}", force=True)
|
||||
return False
|
||||
finally:
|
||||
# Clean up temp file
|
||||
try:
|
||||
Path(temp_file).unlink(missing_ok=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not success:
|
||||
report_progress("❌ Both 7z and zip methods failed", force=True)
|
||||
return False
|
||||
|
||||
# Find the bin directory
|
||||
report_progress("🔍 Locating FFmpeg binaries...", force=True)
|
||||
bin_dir = find_ffmpeg_bin_dir(extract_dir, latest_version)
|
||||
if not bin_dir:
|
||||
report_progress("❌ Could not locate FFmpeg bin directory", force=True)
|
||||
return False
|
||||
|
||||
# Add to System Path
|
||||
report_progress("🔧 Configuring system paths...", force=True)
|
||||
user_path = os.environ.get("PATH", "")
|
||||
|
||||
# Remove old FFmpeg paths from PATH
|
||||
path_parts = user_path.split(os.pathsep)
|
||||
cleaned_paths = [p for p in path_parts if "ffmpeg" not in p.lower() or str(bin_dir) in p]
|
||||
|
||||
# Add new path if not already present
|
||||
if str(bin_dir) not in cleaned_paths:
|
||||
cleaned_paths.insert(0, str(bin_dir))
|
||||
|
||||
new_path = os.pathsep.join(cleaned_paths)
|
||||
|
||||
try:
|
||||
subprocess.run(
|
||||
["setx", "PATH", new_path],
|
||||
creationflags=SUBPROCESS_CREATIONFLAGS,
|
||||
timeout=30,
|
||||
check=True,
|
||||
)
|
||||
os.environ["PATH"] = new_path
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to update PATH permanently: {e}")
|
||||
# Still update for current session
|
||||
os.environ["PATH"] = new_path
|
||||
|
||||
# Verify installation
|
||||
report_progress("✅ Verifying installation...", force=True)
|
||||
new_version = get_ffmpeg_version_direct()
|
||||
if new_version not in ["Not found", "Error getting version", "Unknown version"]:
|
||||
report_progress(f"✅ FFmpeg successfully updated to version {new_version}!", force=True)
|
||||
return True
|
||||
else:
|
||||
report_progress("⚠️ Update completed but verification failed", force=True)
|
||||
return True # Still return True as files were extracted
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"Error updating FFmpeg: {e}")
|
||||
if progress_callback:
|
||||
progress_callback(f"❌ Error: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def update_ffmpeg(progress_callback: Optional[Callable[[str], None]] = None) -> bool:
|
||||
"""
|
||||
Update FFmpeg based on the operating system.
|
||||
|
||||
Args:
|
||||
progress_callback: Optional callback function for progress updates
|
||||
|
||||
Returns:
|
||||
True if update was successful, False otherwise
|
||||
"""
|
||||
if OS_NAME == "Windows":
|
||||
return update_ffmpeg_windows(progress_callback)
|
||||
elif OS_NAME == "Darwin":
|
||||
# macOS update via Homebrew
|
||||
if progress_callback:
|
||||
progress_callback("🍺 Updating FFmpeg via Homebrew...")
|
||||
try:
|
||||
subprocess.run(["brew", "upgrade", "ffmpeg"], check=True, timeout=300)
|
||||
if progress_callback:
|
||||
progress_callback("✅ FFmpeg updated successfully!")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.exception(f"Error updating FFmpeg on macOS: {e}")
|
||||
if progress_callback:
|
||||
progress_callback(f"❌ Error: {e}")
|
||||
return False
|
||||
elif OS_NAME == "Linux":
|
||||
# Linux update via package manager
|
||||
if progress_callback:
|
||||
progress_callback("📦 Please use your package manager to update FFmpeg")
|
||||
return False
|
||||
else:
|
||||
logger.error(f"Unsupported operating system: {OS_NAME}")
|
||||
return False
|
||||
@@ -23,6 +23,7 @@ from src.gui.ytsage_gui_dialogs.ytsage_dialogs_selection import (
|
||||
)
|
||||
from src.gui.ytsage_gui_dialogs.ytsage_dialogs_settings import AutoUpdateSettingsDialog, DownloadSettingsDialog
|
||||
from src.gui.ytsage_gui_dialogs.ytsage_dialogs_update import AutoUpdateThread, UpdateThread, VersionCheckThread, YTDLPUpdateDialog
|
||||
from src.gui.ytsage_gui_dialogs.ytsage_dialogs_updater import UpdaterTabWidget
|
||||
|
||||
__all__ = [
|
||||
# Base dialogs
|
||||
@@ -52,6 +53,9 @@ __all__ = [
|
||||
"CustomOptionsDialog",
|
||||
"TimeRangeDialog",
|
||||
|
||||
# Updater widget
|
||||
"UpdaterTabWidget",
|
||||
|
||||
# History dialog
|
||||
"HistoryDialog",
|
||||
]
|
||||
|
||||
@@ -34,6 +34,7 @@ from src.utils.ytsage_constants import YTDLP_DOCS_URL
|
||||
from src.utils.ytsage_config_manager import ConfigManager
|
||||
from src.utils.ytsage_localization import LocalizationManager, _
|
||||
from src.utils.ytsage_logger import logger
|
||||
from src.gui.ytsage_gui_dialogs.ytsage_dialogs_updater import UpdaterTabWidget
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from src.gui.ytsage_gui_main import YTSageApp # only for type hints (no runtime import)
|
||||
@@ -516,11 +517,15 @@ class CustomOptionsDialog(QDialog):
|
||||
|
||||
language_layout.addStretch()
|
||||
|
||||
# === Updater Tab ===
|
||||
updater_tab = UpdaterTabWidget(self)
|
||||
|
||||
# Add tabs to the tab widget
|
||||
self.tab_widget.addTab(cookies_tab, _("tabs.cookies"))
|
||||
self.tab_widget.addTab(command_tab, _("tabs.custom_command"))
|
||||
self.tab_widget.addTab(proxy_tab, _("tabs.proxy"))
|
||||
self.tab_widget.addTab(language_tab, _("tabs.language"))
|
||||
self.tab_widget.addTab(updater_tab, _("tabs.updater"))
|
||||
|
||||
# Dialog buttons
|
||||
button_box = QDialogButtonBox()
|
||||
|
||||
@@ -3,9 +3,7 @@ FFmpeg installation dialogs for YTSage application.
|
||||
Contains dialogs and threads for checking and installing FFmpeg.
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import webbrowser
|
||||
from io import StringIO
|
||||
|
||||
from PySide6.QtCore import Qt, QThread, Signal
|
||||
from PySide6.QtGui import QIcon
|
||||
@@ -20,15 +18,12 @@ class FFmpegInstallThread(QThread):
|
||||
progress = Signal(str)
|
||||
|
||||
def run(self) -> None:
|
||||
# Redirect stdout to capture progress messages
|
||||
output = StringIO()
|
||||
with contextlib.redirect_stdout(output):
|
||||
success = auto_install_ffmpeg()
|
||||
|
||||
# Process captured output and emit progress signals
|
||||
for line in output.getvalue().splitlines():
|
||||
self.progress.emit(line)
|
||||
# Use a callback to capture progress instead of stdout redirection
|
||||
def progress_callback(msg: str):
|
||||
self.progress.emit(msg)
|
||||
|
||||
# Install FFmpeg with progress callback
|
||||
success = auto_install_ffmpeg(progress_callback=progress_callback)
|
||||
self.finished.emit(success)
|
||||
|
||||
|
||||
@@ -36,9 +31,9 @@ class FFmpegCheckDialog(QDialog):
|
||||
def __init__(self, parent=None) -> None:
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("FFmpeg Installation")
|
||||
self.setMinimumWidth(450)
|
||||
self.setMinimumHeight(200)
|
||||
self.resize(450, 220)
|
||||
self.setMinimumWidth(500)
|
||||
self.setMinimumHeight(280)
|
||||
self.resize(500, 300)
|
||||
|
||||
# Set the window icon to match the main app
|
||||
if parent and parent.windowIcon():
|
||||
@@ -67,11 +62,12 @@ class FFmpegCheckDialog(QDialog):
|
||||
self.message_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
layout.addWidget(self.message_label)
|
||||
|
||||
# Progress label with improved styling and compact height
|
||||
# Progress label with improved styling
|
||||
self.progress_label = QLabel("")
|
||||
self.progress_label.setWordWrap(True)
|
||||
self.progress_label.setMinimumHeight(60) # Smaller but visible area
|
||||
self.progress_label.setMaximumHeight(80) # Limit maximum height
|
||||
self.progress_label.setMinimumHeight(80)
|
||||
self.progress_label.setMaximumHeight(120)
|
||||
self.progress_label.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
|
||||
self.progress_label.setStyleSheet(
|
||||
"""
|
||||
QLabel {
|
||||
@@ -79,22 +75,22 @@ class FFmpegCheckDialog(QDialog):
|
||||
color: #cccccc;
|
||||
border: 1px solid #3d3d3d;
|
||||
border-radius: 6px;
|
||||
padding: 8px;
|
||||
padding: 12px;
|
||||
font-family: 'Consolas', 'Courier New', monospace;
|
||||
font-size: 11px;
|
||||
line-height: 1.2;
|
||||
line-height: 1.4;
|
||||
}
|
||||
"""
|
||||
)
|
||||
self.progress_label.hide()
|
||||
layout.addWidget(self.progress_label)
|
||||
|
||||
# Add minimal stretch - just enough to push buttons down slightly
|
||||
layout.addSpacing(10)
|
||||
# Add stretch to push buttons to bottom
|
||||
layout.addStretch()
|
||||
|
||||
# Buttons container - simple approach that should work
|
||||
button_layout = QHBoxLayout()
|
||||
button_layout.setSpacing(15) # Simple spacing
|
||||
button_layout.setSpacing(12)
|
||||
|
||||
# Install button
|
||||
self.install_btn = QPushButton("Install FFmpeg")
|
||||
@@ -148,6 +144,7 @@ class FFmpegCheckDialog(QDialog):
|
||||
|
||||
# Initialize installation thread
|
||||
self.install_thread = None
|
||||
self.progress_messages = [] # Store progress messages
|
||||
|
||||
def start_installation(self) -> None:
|
||||
self.install_btn.setEnabled(False)
|
||||
@@ -165,6 +162,7 @@ class FFmpegCheckDialog(QDialog):
|
||||
return
|
||||
|
||||
self.message_label.setText("Installing FFmpeg... Please wait")
|
||||
self.progress_messages = [] # Clear previous messages
|
||||
self.progress_label.show()
|
||||
|
||||
self.install_thread = FFmpegInstallThread()
|
||||
@@ -173,7 +171,13 @@ class FFmpegCheckDialog(QDialog):
|
||||
self.install_thread.start()
|
||||
|
||||
def update_progress(self, message) -> None:
|
||||
self.progress_label.setText(message)
|
||||
# Keep only the last 5 messages to avoid overflow
|
||||
self.progress_messages.append(message)
|
||||
if len(self.progress_messages) > 5:
|
||||
self.progress_messages.pop(0)
|
||||
|
||||
# Display the messages
|
||||
self.progress_label.setText("\n".join(self.progress_messages))
|
||||
|
||||
def installation_finished(self, success) -> None:
|
||||
if success:
|
||||
|
||||
@@ -0,0 +1,362 @@
|
||||
"""
|
||||
Updater tab for Custom Options dialog.
|
||||
Handles checking for and installing FFmpeg updates.
|
||||
"""
|
||||
|
||||
import threading
|
||||
from typing import TYPE_CHECKING, cast
|
||||
|
||||
from PySide6.QtCore import QObject, Signal
|
||||
from PySide6.QtWidgets import (
|
||||
QGroupBox,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QProgressBar,
|
||||
QPushButton,
|
||||
QTextEdit,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from src.utils.ytsage_localization import _
|
||||
from src.utils.ytsage_logger import logger
|
||||
from src.core.ytsage_ffmpeg_updater import check_ffmpeg_update_available, update_ffmpeg
|
||||
from src.utils.ytsage_constants import OS_NAME
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from src.gui.ytsage_gui_dialogs.ytsage_dialogs_custom import CustomOptionsDialog
|
||||
|
||||
|
||||
class UpdateWorker(QObject):
|
||||
"""Worker class for running FFmpeg updates in a separate thread."""
|
||||
|
||||
progress = Signal(str) # Progress messages
|
||||
finished = Signal(bool) # Completion status (success/failure)
|
||||
|
||||
def run_update(self):
|
||||
"""Run the FFmpeg update process."""
|
||||
try:
|
||||
def progress_callback(msg: str):
|
||||
"""Callback for progress updates."""
|
||||
self.progress.emit(msg)
|
||||
|
||||
# Run the update
|
||||
success = update_ffmpeg(progress_callback=progress_callback)
|
||||
self.finished.emit(success)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"Error during FFmpeg update: {e}")
|
||||
self.progress.emit(f"❌ Error: {e}")
|
||||
self.finished.emit(False)
|
||||
|
||||
|
||||
class UpdaterTabWidget(QWidget):
|
||||
"""Widget for the Updater tab in Custom Options dialog."""
|
||||
|
||||
def __init__(self, parent=None) -> None:
|
||||
super().__init__(parent)
|
||||
self._parent: "CustomOptionsDialog" = cast("CustomOptionsDialog", self.parent())
|
||||
|
||||
self.update_thread = None
|
||||
self.update_worker = None
|
||||
|
||||
# State variables
|
||||
self.current_version = "Unknown"
|
||||
self.latest_version = "Unknown"
|
||||
self.update_available = False
|
||||
|
||||
self._init_ui()
|
||||
|
||||
def _init_ui(self) -> None:
|
||||
"""Initialize the UI components."""
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
# Help text
|
||||
help_text = QLabel(_('ffmpeg_updater.description'))
|
||||
help_text.setWordWrap(True)
|
||||
help_text.setStyleSheet("color: #999999; padding: 10px;")
|
||||
layout.addWidget(help_text)
|
||||
|
||||
# FFmpeg Update Section
|
||||
ffmpeg_group = QGroupBox(_('ffmpeg_updater.title'))
|
||||
ffmpeg_layout = QVBoxLayout(ffmpeg_group)
|
||||
|
||||
# Version information layout
|
||||
version_layout = QVBoxLayout()
|
||||
|
||||
# Current version
|
||||
current_layout = QHBoxLayout()
|
||||
current_label = QLabel(_('ffmpeg_updater.current_version'))
|
||||
current_label.setStyleSheet("font-weight: bold; color: #ffffff;")
|
||||
current_layout.addWidget(current_label)
|
||||
|
||||
self.current_version_label = QLabel("...")
|
||||
self.current_version_label.setStyleSheet("color: #cccccc;")
|
||||
current_layout.addWidget(self.current_version_label)
|
||||
current_layout.addStretch()
|
||||
version_layout.addLayout(current_layout)
|
||||
|
||||
# Latest version
|
||||
latest_layout = QHBoxLayout()
|
||||
latest_label = QLabel(_('ffmpeg_updater.latest_version'))
|
||||
latest_label.setStyleSheet("font-weight: bold; color: #ffffff;")
|
||||
latest_layout.addWidget(latest_label)
|
||||
|
||||
self.latest_version_label = QLabel("...")
|
||||
self.latest_version_label.setStyleSheet("color: #cccccc;")
|
||||
latest_layout.addWidget(self.latest_version_label)
|
||||
latest_layout.addStretch()
|
||||
version_layout.addLayout(latest_layout)
|
||||
|
||||
ffmpeg_layout.addLayout(version_layout)
|
||||
|
||||
# Status label
|
||||
self.status_label = QLabel(_('ffmpeg_updater.status_idle'))
|
||||
self.status_label.setWordWrap(True)
|
||||
self.status_label.setStyleSheet(
|
||||
"color: #888888; font-size: 12px; padding: 8px; "
|
||||
"background-color: #2a2d36; border-radius: 4px; margin: 5px 0;"
|
||||
)
|
||||
ffmpeg_layout.addWidget(self.status_label)
|
||||
|
||||
# Button layout
|
||||
button_layout = QHBoxLayout()
|
||||
|
||||
# Check for updates button
|
||||
self.check_button = QPushButton(_('ffmpeg_updater.check_updates'))
|
||||
self.check_button.clicked.connect(self.check_for_updates)
|
||||
self.check_button.setStyleSheet(
|
||||
"""
|
||||
QPushButton {
|
||||
padding: 8px 15px;
|
||||
background-color: #444444;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
min-width: 120px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #555555;
|
||||
}
|
||||
QPushButton:disabled {
|
||||
background-color: #2a2a2a;
|
||||
color: #666666;
|
||||
}
|
||||
"""
|
||||
)
|
||||
button_layout.addWidget(self.check_button)
|
||||
|
||||
button_layout.addStretch()
|
||||
|
||||
# Update button
|
||||
self.update_button = QPushButton(_('ffmpeg_updater.update_button'))
|
||||
self.update_button.clicked.connect(self.start_update)
|
||||
self.update_button.setEnabled(False)
|
||||
self.update_button.setStyleSheet(
|
||||
"""
|
||||
QPushButton {
|
||||
padding: 8px 15px;
|
||||
background-color: #c90000;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
min-width: 120px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #a50000;
|
||||
}
|
||||
QPushButton:disabled {
|
||||
background-color: #4a4a4a;
|
||||
color: #888888;
|
||||
}
|
||||
"""
|
||||
)
|
||||
button_layout.addWidget(self.update_button)
|
||||
|
||||
ffmpeg_layout.addLayout(button_layout)
|
||||
|
||||
# Progress bar
|
||||
self.progress_bar = QProgressBar()
|
||||
self.progress_bar.setRange(0, 0) # Indeterminate progress
|
||||
self.progress_bar.setVisible(False)
|
||||
self.progress_bar.setStyleSheet(
|
||||
"""
|
||||
QProgressBar {
|
||||
border: 2px solid #2a2d36;
|
||||
border-radius: 4px;
|
||||
background-color: #1d1e22;
|
||||
text-align: center;
|
||||
color: #ffffff;
|
||||
height: 20px;
|
||||
}
|
||||
QProgressBar::chunk {
|
||||
background-color: #c90000;
|
||||
border-radius: 2px;
|
||||
}
|
||||
"""
|
||||
)
|
||||
ffmpeg_layout.addWidget(self.progress_bar)
|
||||
|
||||
# Log output
|
||||
log_label = QLabel("Update Log:")
|
||||
log_label.setStyleSheet("font-size: 12px; font-weight: bold; color: #ffffff; margin-top: 10px;")
|
||||
ffmpeg_layout.addWidget(log_label)
|
||||
|
||||
self.log_output = QTextEdit()
|
||||
self.log_output.setReadOnly(True)
|
||||
self.log_output.setPlaceholderText("Update logs will appear here...")
|
||||
self.log_output.setMinimumHeight(150)
|
||||
self.log_output.setStyleSheet(
|
||||
"""
|
||||
QTextEdit {
|
||||
background-color: #1d1e22;
|
||||
color: #ffffff;
|
||||
border: 2px solid #2a2d36;
|
||||
border-radius: 6px;
|
||||
padding: 8px;
|
||||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||
font-size: 11px;
|
||||
}
|
||||
"""
|
||||
)
|
||||
ffmpeg_layout.addWidget(self.log_output)
|
||||
|
||||
layout.addWidget(ffmpeg_group)
|
||||
layout.addStretch()
|
||||
|
||||
def check_for_updates(self) -> None:
|
||||
"""Check if FFmpeg updates are available."""
|
||||
self.check_button.setEnabled(False)
|
||||
self.update_button.setEnabled(False)
|
||||
self.status_label.setText(_('ffmpeg_updater.status_checking'))
|
||||
self.status_label.setStyleSheet(
|
||||
"color: #ffaa00; font-size: 12px; padding: 8px; "
|
||||
"background-color: #2a2d36; border-radius: 4px; margin: 5px 0;"
|
||||
)
|
||||
self.log_output.append("🔍 Checking for FFmpeg updates...")
|
||||
|
||||
# Run check in background thread
|
||||
def check_thread():
|
||||
try:
|
||||
update_available, current_version, latest_version = check_ffmpeg_update_available()
|
||||
|
||||
# Update UI in main thread using signals
|
||||
self.check_button.setEnabled(True)
|
||||
self._update_check_results(update_available, current_version, latest_version)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"Error checking for updates: {e}")
|
||||
self.check_button.setEnabled(True)
|
||||
self._show_check_error(str(e))
|
||||
|
||||
thread = threading.Thread(target=check_thread, daemon=True)
|
||||
thread.start()
|
||||
|
||||
def _update_check_results(self, update_available: bool, current_version: str, latest_version: str) -> None:
|
||||
"""Handle completion of update check."""
|
||||
self.update_available = update_available
|
||||
self.current_version = current_version
|
||||
self.latest_version = latest_version
|
||||
|
||||
# Update version labels
|
||||
self.current_version_label.setText(current_version)
|
||||
self.latest_version_label.setText(latest_version)
|
||||
|
||||
# Update status
|
||||
if current_version == "Not installed":
|
||||
self.status_label.setText(_('ffmpeg_updater.install_first'))
|
||||
self.status_label.setStyleSheet(
|
||||
"color: #ff6666; font-size: 12px; padding: 8px; "
|
||||
"background-color: #2a2d36; border-radius: 4px; margin: 5px 0;"
|
||||
)
|
||||
self.update_button.setEnabled(True if OS_NAME == "Windows" else False)
|
||||
self.log_output.append(f"❌ FFmpeg is not installed. Latest version available: {latest_version}")
|
||||
elif update_available:
|
||||
self.status_label.setText(_('ffmpeg_updater.status_update_available'))
|
||||
self.status_label.setStyleSheet(
|
||||
"color: #ffaa00; font-size: 12px; padding: 8px; "
|
||||
"background-color: #2a2d36; border-radius: 4px; margin: 5px 0;"
|
||||
)
|
||||
self.update_button.setEnabled(True if OS_NAME == "Windows" else False)
|
||||
self.log_output.append(f"⚠ Update available: {current_version} → {latest_version}")
|
||||
else:
|
||||
self.status_label.setText(_('ffmpeg_updater.status_up_to_date'))
|
||||
self.status_label.setStyleSheet(
|
||||
"color: #00cc00; font-size: 12px; padding: 8px; "
|
||||
"background-color: #2a2d36; border-radius: 4px; margin: 5px 0;"
|
||||
)
|
||||
self.update_button.setEnabled(False)
|
||||
self.log_output.append(f"✅ FFmpeg is up to date (version {current_version})")
|
||||
|
||||
# Show message for non-Windows systems
|
||||
if OS_NAME != "Windows" and update_available:
|
||||
self.log_output.append("ℹ️ Automatic updates are only available on Windows.")
|
||||
self.log_output.append(" Please use your system's package manager to update FFmpeg.")
|
||||
|
||||
def _show_check_error(self, error: str) -> None:
|
||||
"""Handle error during update check."""
|
||||
self.status_label.setText(_('ffmpeg_updater.check_failed'))
|
||||
self.status_label.setStyleSheet(
|
||||
"color: #ff6666; font-size: 12px; padding: 8px; "
|
||||
"background-color: #2a2d36; border-radius: 4px; margin: 5px 0;"
|
||||
)
|
||||
self.log_output.append(f"❌ Error checking for updates: {error}")
|
||||
|
||||
def start_update(self) -> None:
|
||||
"""Start the FFmpeg update process."""
|
||||
if OS_NAME != "Windows":
|
||||
self.log_output.append("❌ Automatic updates are only supported on Windows.")
|
||||
return
|
||||
|
||||
# Disable buttons during update
|
||||
self.check_button.setEnabled(False)
|
||||
self.update_button.setEnabled(False)
|
||||
self.progress_bar.setVisible(True)
|
||||
|
||||
self.log_output.append("=" * 60)
|
||||
self.log_output.append("🚀 Starting FFmpeg update process...")
|
||||
self.log_output.append("=" * 60)
|
||||
|
||||
# Create worker and thread
|
||||
self.update_worker = UpdateWorker()
|
||||
self.update_thread = threading.Thread(target=self.update_worker.run_update, daemon=True)
|
||||
|
||||
# Connect signals
|
||||
self.update_worker.progress.connect(self._on_update_progress)
|
||||
self.update_worker.finished.connect(self._on_update_finished)
|
||||
|
||||
# Start update
|
||||
self.update_thread.start()
|
||||
|
||||
def _on_update_progress(self, message: str) -> None:
|
||||
"""Handle progress updates from the update worker."""
|
||||
self.log_output.append(message)
|
||||
# Auto-scroll to bottom
|
||||
cursor = self.log_output.textCursor()
|
||||
cursor.movePosition(cursor.MoveOperation.End)
|
||||
self.log_output.setTextCursor(cursor)
|
||||
|
||||
def _on_update_finished(self, success: bool) -> None:
|
||||
"""Handle completion of the update process."""
|
||||
self.progress_bar.setVisible(False)
|
||||
self.check_button.setEnabled(True)
|
||||
|
||||
self.log_output.append("=" * 60)
|
||||
|
||||
if success:
|
||||
self.log_output.append(_('ffmpeg_updater.update_success', version=self.latest_version))
|
||||
self.status_label.setText(_('ffmpeg_updater.status_up_to_date'))
|
||||
self.status_label.setStyleSheet(
|
||||
"color: #00cc00; font-size: 12px; padding: 8px; "
|
||||
"background-color: #2a2d36; border-radius: 4px; margin: 5px 0;"
|
||||
)
|
||||
# Re-check to update version info
|
||||
self.check_for_updates()
|
||||
else:
|
||||
self.log_output.append(_('ffmpeg_updater.update_failed'))
|
||||
self.status_label.setText(_('ffmpeg_updater.status_update_available'))
|
||||
self.update_button.setEnabled(True)
|
||||
|
||||
self.log_output.append("=" * 60)
|
||||
@@ -145,10 +145,13 @@ else: # Linux and other UNIX-like
|
||||
# Documentation URLs
|
||||
YTDLP_DOCS_URL: str = "https://github.com/yt-dlp/yt-dlp?tab=readme-ov-file#usage-and-options"
|
||||
|
||||
# ffmpeg download links
|
||||
FFMPEG_7Z_DOWNLOAD_URL = "https://github.com/GyanD/codexffmpeg/releases/download/7.1.1/ffmpeg-7.1.1-full_build.7z"
|
||||
FFMPEG_7Z_SHA256_URL = "https://www.gyan.dev/ffmpeg/builds/packages/ffmpeg-7.1.1-full_build.7z.sha256"
|
||||
FFMPEG_ZIP_DOWNLOAD_URL = "https://github.com/GyanD/codexffmpeg/releases/download/7.1.1/ffmpeg-7.1.1-full_build.zip"
|
||||
# FFmpeg download links (Essentials build - always latest version)
|
||||
FFMPEG_7Z_DOWNLOAD_URL = "https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.7z"
|
||||
FFMPEG_ZIP_DOWNLOAD_URL = "https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip"
|
||||
FFMPEG_7Z_SHA256_URL = "https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.7z.sha256"
|
||||
FFMPEG_ZIP_SHA256_URL = "https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip.sha256"
|
||||
FFMPEG_7Z_VERSION_URL = "https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.7z.ver"
|
||||
FFMPEG_ZIP_VERSION_URL = "https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip.ver"
|
||||
|
||||
if __name__ == "__main__":
|
||||
# If this file is run directly, print directory information; if imported, create the necessary directories for the application.
|
||||
|
||||
Reference in New Issue
Block a user