Add Deno setup and integration to application

Introduces Deno binary management, setup dialog, and download/verification logic. Updates localization files for Deno setup messages in all supported languages. Integrates Deno setup checks into main.py and GUI initialization, and adds Deno constants for platform-specific downloads.
This commit is contained in:
oop7
2025-11-14 17:58:45 +02:00
parent bb231ccd12
commit 38a8d9d29f
18 changed files with 812 additions and 0 deletions
+14
View File
@@ -489,5 +489,19 @@
"check_failed": "فشل التحقق من الإصدار. يرجى التحقق من اتصال الإنترنت.",
"description": "تحقق من إصدار FFmpeg الخاص بك وقارنه بأحدث إصدار متاح.",
"guide_info": " لتثبيت أو تحديث FFmpeg، <a href='https://github.com/oop7/ffmpeg-install-guide'>انقر هنا لعرض دليل التثبيت الشامل</a>."
},
"deno": {
"setup_required": "Deno Setup Required",
"setup_description": "YTSage requires Deno to run certain features.<br><br>Deno was not found in the app's local directory. YTSage needs to set up Deno for your {os_name} system.<br><br>Click 'Setup Deno' to download and install automatically.",
"setup_button": "Setup Deno",
"downloading": "Downloading Deno...",
"extracting": "Extracting Deno...",
"verifying": "Verifying Deno...",
"success": "Deno was successfully installed!",
"download_failed": "Download Failed",
"download_error": "Failed to download Deno: {error}",
"verification_failed": "SHA256 verification failed. The downloaded file may be corrupted or tampered with.",
"setup_failed": "Failed to set up Deno. Some features may not work correctly.",
"setup_error": "Setup Error"
}
}
+14
View File
@@ -489,5 +489,19 @@
"check_failed": "Versionprüfung fehlgeschlagen. Bitte überprüfen Sie Ihre Internetverbindung.",
"description": "Überprüfen Sie Ihre FFmpeg-Version und vergleichen Sie sie mit der neuesten verfügbaren Version.",
"guide_info": " Um FFmpeg zu installieren oder zu aktualisieren, <a href='https://github.com/oop7/ffmpeg-install-guide'>klicken Sie hier, um unseren umfassenden Installationsleitfaden anzuzeigen</a>."
},
"deno": {
"setup_required": "Deno Setup Required",
"setup_description": "YTSage requires Deno to run certain features.<br><br>Deno was not found in the app's local directory. YTSage needs to set up Deno for your {os_name} system.<br><br>Click 'Setup Deno' to download and install automatically.",
"setup_button": "Setup Deno",
"downloading": "Downloading Deno...",
"extracting": "Extracting Deno...",
"verifying": "Verifying Deno...",
"success": "Deno was successfully installed!",
"download_failed": "Download Failed",
"download_error": "Failed to download Deno: {error}",
"verification_failed": "SHA256 verification failed. The downloaded file may be corrupted or tampered with.",
"setup_failed": "Failed to set up Deno. Some features may not work correctly.",
"setup_error": "Setup Error"
}
}
+14
View File
@@ -489,5 +489,19 @@
"check_failed": "Failed to check version. Please check your internet connection.",
"description": "Check your FFmpeg version and compare it with the latest available version.",
"guide_info": " To install or update FFmpeg, <a href='https://github.com/oop7/ffmpeg-install-guide'>click here to view our comprehensive installation guide</a>."
},
"deno": {
"setup_required": "Deno Setup Required",
"setup_description": "YTSage requires Deno to run certain features.<br><br>Deno was not found in the app's local directory. YTSage needs to set up Deno for your {os_name} system.<br><br>Click 'Setup Deno' to download and install automatically.",
"setup_button": "Setup Deno",
"downloading": "Downloading Deno...",
"extracting": "Extracting Deno...",
"verifying": "Verifying Deno...",
"success": "Deno was successfully installed!",
"download_failed": "Download Failed",
"download_error": "Failed to download Deno: {error}",
"verification_failed": "SHA256 verification failed. The downloaded file may be corrupted or tampered with.",
"setup_failed": "Failed to set up Deno. Some features may not work correctly.",
"setup_error": "Setup Error"
}
}
+14
View File
@@ -472,5 +472,19 @@
"check_failed": "Error al verificar la versión. Por favor, verifique su conexión a internet.",
"description": "Compruebe su versión de FFmpeg y compárela con la última versión disponible.",
"guide_info": " Para instalar o actualizar FFmpeg, <a href='https://github.com/oop7/ffmpeg-install-guide'>haga clic aquí para ver nuestra guía completa de instalación</a>."
},
"deno": {
"setup_required": "Deno Setup Required",
"setup_description": "YTSage requires Deno to run certain features.<br><br>Deno was not found in the app's local directory. YTSage needs to set up Deno for your {os_name} system.<br><br>Click 'Setup Deno' to download and install automatically.",
"setup_button": "Setup Deno",
"downloading": "Downloading Deno...",
"extracting": "Extracting Deno...",
"verifying": "Verifying Deno...",
"success": "Deno was successfully installed!",
"download_failed": "Download Failed",
"download_error": "Failed to download Deno: {error}",
"verification_failed": "SHA256 verification failed. The downloaded file may be corrupted or tampered with.",
"setup_failed": "Failed to set up Deno. Some features may not work correctly.",
"setup_error": "Setup Error"
}
}
+14
View File
@@ -489,5 +489,19 @@
"check_failed": "Échec de la vérification de la version. Vérifiez votre connexion Internet.",
"description": "Vérifiez votre version de FFmpeg et comparez-la avec la dernière version disponible.",
"guide_info": " Pour installer ou mettre à jour FFmpeg, <a href='https://github.com/oop7/ffmpeg-install-guide'>cliquez ici pour consulter notre guide d'installation complet</a>."
},
"deno": {
"setup_required": "Deno Setup Required",
"setup_description": "YTSage requires Deno to run certain features.<br><br>Deno was not found in the app's local directory. YTSage needs to set up Deno for your {os_name} system.<br><br>Click 'Setup Deno' to download and install automatically.",
"setup_button": "Setup Deno",
"downloading": "Downloading Deno...",
"extracting": "Extracting Deno...",
"verifying": "Verifying Deno...",
"success": "Deno was successfully installed!",
"download_failed": "Download Failed",
"download_error": "Failed to download Deno: {error}",
"verification_failed": "SHA256 verification failed. The downloaded file may be corrupted or tampered with.",
"setup_failed": "Failed to set up Deno. Some features may not work correctly.",
"setup_error": "Setup Error"
}
}
+14
View File
@@ -489,5 +489,19 @@
"check_failed": "संस्करण जांच विफल रही। कृपया अपना इंटरनेट कनेक्शन जांचें।",
"description": "अपने FFmpeg संस्करण की जांच करें और इसे नवीनतम उपलब्ध संस्करण से तुलना करें।",
"guide_info": " FFmpeg को इंस्टॉल या अपडेट करने के लिए, <a href='https://github.com/oop7/ffmpeg-install-guide'>हमारी व्यापक इंस्टॉलेशन गाइड देखने के लिए यहां क्लिक करें</a>."
},
"deno": {
"setup_required": "Deno Setup Required",
"setup_description": "YTSage requires Deno to run certain features.<br><br>Deno was not found in the app's local directory. YTSage needs to set up Deno for your {os_name} system.<br><br>Click 'Setup Deno' to download and install automatically.",
"setup_button": "Setup Deno",
"downloading": "Downloading Deno...",
"extracting": "Extracting Deno...",
"verifying": "Verifying Deno...",
"success": "Deno was successfully installed!",
"download_failed": "Download Failed",
"download_error": "Failed to download Deno: {error}",
"verification_failed": "SHA256 verification failed. The downloaded file may be corrupted or tampered with.",
"setup_failed": "Failed to set up Deno. Some features may not work correctly.",
"setup_error": "Setup Error"
}
}
+14
View File
@@ -489,5 +489,19 @@
"check_failed": "Gagal memeriksa versi. Periksa koneksi internet Anda.",
"description": "Periksa versi FFmpeg Anda dan bandingkan dengan versi terbaru yang tersedia.",
"guide_info": " Untuk menginstal atau memperbarui FFmpeg, <a href='https://github.com/oop7/ffmpeg-install-guide'>klik di sini untuk melihat panduan instalasi lengkap kami</a>."
},
"deno": {
"setup_required": "Deno Setup Required",
"setup_description": "YTSage requires Deno to run certain features.<br><br>Deno was not found in the app's local directory. YTSage needs to set up Deno for your {os_name} system.<br><br>Click 'Setup Deno' to download and install automatically.",
"setup_button": "Setup Deno",
"downloading": "Downloading Deno...",
"extracting": "Extracting Deno...",
"verifying": "Verifying Deno...",
"success": "Deno was successfully installed!",
"download_failed": "Download Failed",
"download_error": "Failed to download Deno: {error}",
"verification_failed": "SHA256 verification failed. The downloaded file may be corrupted or tampered with.",
"setup_failed": "Failed to set up Deno. Some features may not work correctly.",
"setup_error": "Setup Error"
}
}
+14
View File
@@ -489,5 +489,19 @@
"check_failed": "Verifica versione non riuscita. Controlla la connessione Internet.",
"description": "Controlla la tua versione di FFmpeg e confrontala con l'ultima versione disponibile.",
"guide_info": " Per installare o aggiornare FFmpeg, <a href='https://github.com/oop7/ffmpeg-install-guide'>clicca qui per visualizzare la nostra guida completa all'installazione</a>."
},
"deno": {
"setup_required": "Deno Setup Required",
"setup_description": "YTSage requires Deno to run certain features.<br><br>Deno was not found in the app's local directory. YTSage needs to set up Deno for your {os_name} system.<br><br>Click 'Setup Deno' to download and install automatically.",
"setup_button": "Setup Deno",
"downloading": "Downloading Deno...",
"extracting": "Extracting Deno...",
"verifying": "Verifying Deno...",
"success": "Deno was successfully installed!",
"download_failed": "Download Failed",
"download_error": "Failed to download Deno: {error}",
"verification_failed": "SHA256 verification failed. The downloaded file may be corrupted or tampered with.",
"setup_failed": "Failed to set up Deno. Some features may not work correctly.",
"setup_error": "Setup Error"
}
}
+14
View File
@@ -489,5 +489,19 @@
"check_failed": "バージョンの確認に失敗しました。インターネット接続を確認してください。",
"description": "FFmpegのバージョンを確認し、利用可能な最新バージョンと比較します。",
"guide_info": " FFmpegをインストールまたは更新するには、<a href='https://github.com/oop7/ffmpeg-install-guide'>こちらをクリックして包括的なインストールガイドをご覧ください</a>。"
},
"deno": {
"setup_required": "Deno Setup Required",
"setup_description": "YTSage requires Deno to run certain features.<br><br>Deno was not found in the app's local directory. YTSage needs to set up Deno for your {os_name} system.<br><br>Click 'Setup Deno' to download and install automatically.",
"setup_button": "Setup Deno",
"downloading": "Downloading Deno...",
"extracting": "Extracting Deno...",
"verifying": "Verifying Deno...",
"success": "Deno was successfully installed!",
"download_failed": "Download Failed",
"download_error": "Failed to download Deno: {error}",
"verification_failed": "SHA256 verification failed. The downloaded file may be corrupted or tampered with.",
"setup_failed": "Failed to set up Deno. Some features may not work correctly.",
"setup_error": "Setup Error"
}
}
+14
View File
@@ -489,5 +489,19 @@
"check_failed": "Nie udało się sprawdzić wersji. Sprawdź połączenie internetowe.",
"description": "Sprawdź swoją wersję FFmpeg i porównaj ją z najnowszą dostępną wersją.",
"guide_info": " Aby zainstalować lub zaktualizować FFmpeg, <a href='https://github.com/oop7/ffmpeg-install-guide'>kliknij tutaj, aby wyświetlić nasz kompleksowy przewodnik instalacji</a>."
},
"deno": {
"setup_required": "Deno Setup Required",
"setup_description": "YTSage requires Deno to run certain features.<br><br>Deno was not found in the app's local directory. YTSage needs to set up Deno for your {os_name} system.<br><br>Click 'Setup Deno' to download and install automatically.",
"setup_button": "Setup Deno",
"downloading": "Downloading Deno...",
"extracting": "Extracting Deno...",
"verifying": "Verifying Deno...",
"success": "Deno was successfully installed!",
"download_failed": "Download Failed",
"download_error": "Failed to download Deno: {error}",
"verification_failed": "SHA256 verification failed. The downloaded file may be corrupted or tampered with.",
"setup_failed": "Failed to set up Deno. Some features may not work correctly.",
"setup_error": "Setup Error"
}
}
+14
View File
@@ -489,5 +489,19 @@
"check_failed": "Falha ao verificar versão. Verifique sua conexão com a Internet.",
"description": "Verifique sua versão do FFmpeg e compare com a última versão disponível.",
"guide_info": " Para instalar ou atualizar o FFmpeg, <a href='https://github.com/oop7/ffmpeg-install-guide'>clique aqui para ver nosso guia completo de instalação</a>."
},
"deno": {
"setup_required": "Deno Setup Required",
"setup_description": "YTSage requires Deno to run certain features.<br><br>Deno was not found in the app's local directory. YTSage needs to set up Deno for your {os_name} system.<br><br>Click 'Setup Deno' to download and install automatically.",
"setup_button": "Setup Deno",
"downloading": "Downloading Deno...",
"extracting": "Extracting Deno...",
"verifying": "Verifying Deno...",
"success": "Deno was successfully installed!",
"download_failed": "Download Failed",
"download_error": "Failed to download Deno: {error}",
"verification_failed": "SHA256 verification failed. The downloaded file may be corrupted or tampered with.",
"setup_failed": "Failed to set up Deno. Some features may not work correctly.",
"setup_error": "Setup Error"
}
}
+14
View File
@@ -489,5 +489,19 @@
"check_failed": "Не удалось проверить версию. Проверьте подключение к Интернету.",
"description": "Проверьте версию FFmpeg и сравните ее с последней доступной версией.",
"guide_info": " Для установки или обновления FFmpeg, <a href='https://github.com/oop7/ffmpeg-install-guide'>нажмите здесь, чтобы просмотреть наше подробное руководство по установке</a>."
},
"deno": {
"setup_required": "Deno Setup Required",
"setup_description": "YTSage requires Deno to run certain features.<br><br>Deno was not found in the app's local directory. YTSage needs to set up Deno for your {os_name} system.<br><br>Click 'Setup Deno' to download and install automatically.",
"setup_button": "Setup Deno",
"downloading": "Downloading Deno...",
"extracting": "Extracting Deno...",
"verifying": "Verifying Deno...",
"success": "Deno was successfully installed!",
"download_failed": "Download Failed",
"download_error": "Failed to download Deno: {error}",
"verification_failed": "SHA256 verification failed. The downloaded file may be corrupted or tampered with.",
"setup_failed": "Failed to set up Deno. Some features may not work correctly.",
"setup_error": "Setup Error"
}
}
+14
View File
@@ -489,5 +489,19 @@
"check_failed": "Sürüm kontrolu başarısız oldu. İnternet bağlantınızı kontrol edin.",
"description": "FFmpeg sürümünüzü kontrol edin ve mevcut en son sürümle karşılaştırın.",
"guide_info": " FFmpeg'i yüklemek veya güncellemek için, <a href='https://github.com/oop7/ffmpeg-install-guide'>kapsamlı kurulum kılavuzumuzu görüntülemek için buraya tıklayın</a>."
},
"deno": {
"setup_required": "Deno Setup Required",
"setup_description": "YTSage requires Deno to run certain features.<br><br>Deno was not found in the app's local directory. YTSage needs to set up Deno for your {os_name} system.<br><br>Click 'Setup Deno' to download and install automatically.",
"setup_button": "Setup Deno",
"downloading": "Downloading Deno...",
"extracting": "Extracting Deno...",
"verifying": "Verifying Deno...",
"success": "Deno was successfully installed!",
"download_failed": "Download Failed",
"download_error": "Failed to download Deno: {error}",
"verification_failed": "SHA256 verification failed. The downloaded file may be corrupted or tampered with.",
"setup_failed": "Failed to set up Deno. Some features may not work correctly.",
"setup_error": "Setup Error"
}
}
+14
View File
@@ -489,5 +489,19 @@
"check_failed": "检查版本失败。请检查您的网络连接。",
"description": "检查您的 FFmpeg 版本并与最新可用版本进行比较。",
"guide_info": " 要安装或更新 FFmpeg<a href='https://github.com/oop7/ffmpeg-install-guide'>请点击此处查看我们的综合安装指南</a>。"
},
"deno": {
"setup_required": "Deno Setup Required",
"setup_description": "YTSage requires Deno to run certain features.<br><br>Deno was not found in the app's local directory. YTSage needs to set up Deno for your {os_name} system.<br><br>Click 'Setup Deno' to download and install automatically.",
"setup_button": "Setup Deno",
"downloading": "Downloading Deno...",
"extracting": "Extracting Deno...",
"verifying": "Verifying Deno...",
"success": "Deno was successfully installed!",
"download_failed": "Download Failed",
"download_error": "Failed to download Deno: {error}",
"verification_failed": "SHA256 verification failed. The downloaded file may be corrupted or tampered with.",
"setup_failed": "Failed to set up Deno. Some features may not work correctly.",
"setup_error": "Setup Error"
}
}
+8
View File
@@ -4,6 +4,7 @@ from PySide6.QtWidgets import QApplication, QMessageBox
from src.utils.ytsage_logger import logger
from src.core.ytsage_yt_dlp import check_ytdlp_binary, setup_ytdlp # Import the new yt-dlp setup functions
from src.core.ytsage_deno import check_deno_binary, setup_deno # Import the new Deno setup functions
from src.gui.ytsage_gui_main import YTSageApp # Import the main application class from ytsage_gui_main
@@ -29,6 +30,13 @@ def main():
if yt_dlp_path == "yt-dlp": # If user canceled or something went wrong
logger.warning("yt-dlp not configured properly")
# Check for Deno binary
if not check_deno_binary():
logger.warning("No Deno binary found, starting setup process")
deno_path = setup_deno()
if deno_path == "deno": # If user canceled or something went wrong
logger.warning("Deno not configured properly")
window = YTSageApp() # Instantiate the main application class
window.show()
logger.info("Application window shown, entering main loop")
+553
View File
@@ -0,0 +1,553 @@
import os
import shutil
import subprocess
import tempfile
import zipfile
from pathlib import Path
from typing import Optional
import requests
from PySide6.QtCore import Qt, QThread, Signal
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import (
QDialog,
QHBoxLayout,
QLabel,
QMessageBox,
QProgressBar,
QPushButton,
QVBoxLayout,
)
from src.utils.ytsage_logger import logger
from src.utils.ytsage_localization import _
from src.utils.ytsage_constants import (
APP_BIN_DIR,
ICON_PATH,
OS_FULL_NAME,
OS_NAME,
SUBPROCESS_CREATIONFLAGS,
DENO_APP_BIN_PATH,
DENO_DOWNLOAD_URL,
DENO_SHA256_URL,
)
from src.core.ytsage_ffmpeg import get_file_sha256
def verify_deno_sha256(file_path: Path, sha256_url: str) -> bool:
"""
Verify Deno file SHA256 hash against official checksums.
Args:
file_path: Path to the downloaded Deno zip file
sha256_url: URL to download the SHA256 checksum file
Returns:
bool: True if verification successful, False otherwise
"""
try:
# Download the SHA256 checksum file
logger.info(f"Downloading SHA256 checksum from: {sha256_url}")
response = requests.get(sha256_url, timeout=10)
response.raise_for_status()
checksum_content = response.text
# Parse the checksum file - format is:
# Algorithm : SHA256
# Hash : <HASH>
# Path : <PATH>
expected_hash = None
for line in checksum_content.strip().split("\n"):
if line.startswith("Hash"):
# Extract hash from "Hash : <HASH>" format
parts = line.split(":", 1)
if len(parts) == 2:
expected_hash = parts[1].strip()
break
if not expected_hash:
logger.error("Could not find SHA256 hash in checksum file")
return False
# Calculate actual hash of downloaded file
logger.info("Calculating SHA256 hash of downloaded file...")
actual_hash = get_file_sha256(file_path)
# Compare hashes (case-insensitive)
if actual_hash.lower() == expected_hash.lower():
logger.info("✓ SHA256 verification successful!")
logger.info(f" Expected: {expected_hash}")
logger.info(f" Actual: {actual_hash}")
return True
else:
logger.error("✗ SHA256 verification failed!")
logger.error(f" Expected: {expected_hash}")
logger.error(f" Actual: {actual_hash}")
return False
except requests.RequestException as e:
logger.error(f"Failed to download SHA256 checksum: {e}")
return False
except Exception as e:
logger.exception(f"Error during SHA256 verification: {e}")
return False
class DownloadDenoThread(QThread):
progress_signal = Signal(int)
status_signal = Signal(str)
finished_signal = Signal(bool, str)
def __init__(self):
super().__init__()
def run(self) -> None:
temp_zip_path = None
try:
# Create temporary file for zip download
temp_zip_fd, temp_zip_path = tempfile.mkstemp(suffix=".zip")
os.close(temp_zip_fd) # Close the file descriptor
# Download with progress reporting
logger.info(f"Downloading Deno from: {DENO_DOWNLOAD_URL}")
self.status_signal.emit(_("deno.downloading"))
response = requests.get(DENO_DOWNLOAD_URL, stream=True)
response.raise_for_status()
total_size = int(response.headers.get("content-length", 0))
block_size = 8192 # 8KB blocks
if total_size == 0:
self.progress_signal.emit(100)
with open(temp_zip_path, "wb") as f:
downloaded = 0
for data in response.iter_content(block_size):
f.write(data)
downloaded += len(data)
if total_size > 0:
progress = int(downloaded / total_size * 100)
self.progress_signal.emit(progress)
logger.info("Download complete, verifying SHA256 hash...")
self.status_signal.emit(_("deno.verifying"))
# Verify SHA256 hash
if not verify_deno_sha256(Path(temp_zip_path), DENO_SHA256_URL):
# Hash verification failed - delete the downloaded file
logger.error("SHA256 verification failed! Removing downloaded file.")
if Path(temp_zip_path).exists():
Path(temp_zip_path).unlink()
self.finished_signal.emit(
False,
_("deno.verification_failed")
)
return
# Extract deno executable from zip
logger.info("Extracting Deno executable...")
self.status_signal.emit(_("deno.extracting"))
with zipfile.ZipFile(temp_zip_path, 'r') as zip_ref:
# Deno zip contains just the executable at root
executable_name = "deno.exe" if OS_NAME == "Windows" else "deno"
# Find the executable in the zip
if executable_name not in zip_ref.namelist():
logger.error(f"Executable '{executable_name}' not found in zip file")
self.finished_signal.emit(False, f"Executable '{executable_name}' not found in zip")
return
# Extract to app bin directory
zip_ref.extract(executable_name, APP_BIN_DIR)
# Verify the extracted file exists
exe_path = DENO_APP_BIN_PATH
if not exe_path.exists():
logger.error(f"Extraction failed: {exe_path} does not exist")
self.finished_signal.emit(False, "Extraction failed")
return
# Make executable on macOS and Linux
if OS_NAME != "Windows":
os.chmod(exe_path, 0o755)
logger.info("Set executable permissions on Unix system")
# Clean up the temporary zip file
if temp_zip_path and Path(temp_zip_path).exists():
Path(temp_zip_path).unlink()
logger.info("Cleaned up temporary zip file")
logger.info("Deno downloaded, verified, and extracted successfully!")
self.finished_signal.emit(True, str(exe_path))
except Exception as e:
logger.exception(f"Error downloading/extracting Deno: {e}")
# Clean up temporary zip file on error
if temp_zip_path and Path(temp_zip_path).exists():
try:
Path(temp_zip_path).unlink()
except Exception:
pass
self.finished_signal.emit(False, str(e))
class DenoSetupDialog(QDialog):
setup_complete = Signal(str) # Signal emitting the path to Deno
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle(_("deno.setup_required"))
self.setMinimumWidth(520)
self.setMinimumHeight(300)
self.resize(520, 320)
# Set the window icon to match the main app
if parent and parent.windowIcon():
self.setWindowIcon(parent.windowIcon())
else:
icon_path = ICON_PATH
if Path.exists(icon_path):
self.setWindowIcon(QIcon(str(icon_path)))
self.init_ui()
# Apply dark theme styling to match app
self.setStyleSheet(
"""
QDialog {
background-color: #15181b;
color: #ffffff;
}
QLabel {
color: #cccccc;
line-height: 1.4;
}
QPushButton {
padding: 10px 20px;
background-color: #c90000;
border: none;
border-radius: 6px;
color: white;
font-weight: bold;
font-size: 13px;
min-width: 120px;
min-height: 20px;
}
QPushButton:hover {
background-color: #a50000;
}
QPushButton:pressed {
background-color: #800000;
}
QPushButton:disabled {
background-color: #666666;
color: #999999;
}
QProgressBar {
border: 2px solid #1d1e22;
border-radius: 6px;
text-align: center;
color: white;
background-color: #1d1e22;
height: 25px;
font-weight: bold;
}
QProgressBar::chunk {
background: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0,
stop: 0 #e60000, stop: 0.5 #ff3333, stop: 1 #c90000);
border-radius: 4px;
margin: 1px;
}
"""
)
def init_ui(self) -> None:
layout = QVBoxLayout()
layout.setSpacing(15)
layout.setContentsMargins(25, 25, 25, 25)
# Header title
title_label = QLabel(_("deno.setup_required"))
title_label.setStyleSheet("font-size: 16px; font-weight: bold; color: #ffffff; padding: 5px 0;")
title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(title_label)
# Information label with improved styling
info_label = QLabel(
_("deno.setup_description", os_name=OS_FULL_NAME)
)
info_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
info_label.setWordWrap(True)
info_label.setStyleSheet("font-size: 13px; color: #cccccc; padding: 5px; line-height: 1.4;")
layout.addWidget(info_label)
# Progress bar with proper sizing
self.progress_bar = QProgressBar()
self.progress_bar.setVisible(False)
self.progress_bar.setFixedHeight(20)
self.progress_bar.setStyleSheet(
"""
QProgressBar {
border: 1px solid #3d3d3d;
border-radius: 8px;
background-color: #1d1e22;
text-align: center;
color: #ffffff;
font-size: 12px;
font-weight: bold;
height: 20px;
}
QProgressBar::chunk {
background-color: #c90000;
border-radius: 6px;
margin: 1px;
}
"""
)
layout.addWidget(self.progress_bar)
# Status label with better spacing
self.status_label = QLabel("")
self.status_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.status_label.setStyleSheet("font-size: 12px; color: #cccccc; padding: 8px 0;")
self.status_label.setWordWrap(True)
layout.addWidget(self.status_label)
# Add stretch to push buttons to bottom
layout.addStretch()
# Button layout with improved spacing
button_layout = QHBoxLayout()
button_layout.setSpacing(15)
button_layout.setContentsMargins(0, 10, 0, 0)
self.setup_button = QPushButton(_("deno.setup_button"))
self.setup_button.clicked.connect(self.download_deno)
self.cancel_button = QPushButton(_("buttons.cancel"))
self.cancel_button.clicked.connect(self.reject)
button_layout.addWidget(self.setup_button)
button_layout.addWidget(self.cancel_button)
layout.addLayout(button_layout)
self.setLayout(layout)
def download_deno(self) -> None:
self.progress_bar.setVisible(True)
self.progress_bar.setValue(0)
self.status_label.setText(_("deno.downloading"))
self.setup_button.setEnabled(False)
self.cancel_button.setEnabled(False)
self.download_thread = DownloadDenoThread()
self.download_thread.progress_signal.connect(self.update_progress)
self.download_thread.status_signal.connect(self.update_status)
self.download_thread.finished_signal.connect(self.download_finished)
self.download_thread.start()
def update_progress(self, value) -> None:
self.progress_bar.setValue(value)
def update_status(self, status: str) -> None:
self.status_label.setText(status)
def download_finished(self, success, result) -> None:
self.setup_button.setEnabled(True)
self.cancel_button.setEnabled(True)
if success:
self.status_label.setText(_("deno.success"))
self.setup_complete.emit(result)
self.accept()
else:
self.status_label.setText(f"{_('deno.download_error', error=result)}")
error_dialog = QMessageBox(self)
error_dialog.setIcon(QMessageBox.Icon.Critical)
error_dialog.setWindowTitle(_("deno.download_failed"))
error_dialog.setText(_("deno.download_error", error=result))
error_dialog.setWindowIcon(self.windowIcon())
error_dialog.setStyleSheet(
"""
QMessageBox {
background-color: #15181b;
color: #ffffff;
}
QLabel {
color: #ffffff;
}
QPushButton {
padding: 8px 15px;
background-color: #c90000;
border: none;
border-radius: 4px;
color: white;
font-weight: bold;
}
QPushButton:hover {
background-color: #a50000;
}
"""
)
error_dialog.exec()
def check_deno_binary() -> Optional[Path]:
"""
Check if Deno binary exists in the app's bin directory ONLY.
We only use our managed binary, not system PATH.
Returns:
Path or None: Path to Deno binary if found in app bin, None otherwise
"""
exe_path = DENO_APP_BIN_PATH
if exe_path.exists():
# Make sure it's executable on Unix systems
if OS_NAME != "Windows" and not os.access(exe_path, os.X_OK):
try:
os.chmod(exe_path, 0o755)
logger.info(f"Fixed permissions on Deno at {exe_path}")
except Exception as e:
logger.exception(f"Could not set executable permissions on {exe_path}: {e}")
logger.info(f"Found Deno in app bin directory: {exe_path}")
return exe_path
# Binary not found in app directory - return None to trigger setup
logger.warning(f"Deno binary not found in app bin directory: {exe_path}")
return None
def check_deno_installed() -> bool:
"""
Check if Deno is installed and accessible.
Returns:
bool: True if Deno is found and working, False otherwise
"""
try:
deno_path = check_deno_binary()
if deno_path:
# Try to run deno --version to verify it's working
try:
result = subprocess.run(
[str(deno_path), "--version"],
capture_output=True,
text=True,
timeout=5,
creationflags=SUBPROCESS_CREATIONFLAGS
)
return result.returncode == 0
except Exception:
return False
return False
except Exception:
return False
def get_deno_path() -> Path:
"""
Get the Deno path from the app's bin directory.
Returns:
Path or str: Path to Deno binary, or "deno" as fallback command
"""
deno_path = check_deno_binary()
if deno_path:
logger.info(f"Using Deno from: {deno_path}")
return deno_path
# If not found, fall back to the command name as a last resort
logger.info("Deno not found in app directory, falling back to command name")
return "deno" # type: ignore[return-value]
def setup_deno(parent_widget=None):
"""
Show the Deno setup dialog and handle the result.
Returns:
str: Path to Deno binary
"""
logger.debug("Starting Deno setup dialog")
dialog = DenoSetupDialog(parent_widget)
# Store the setup result from the signal
setup_result = {"path": None}
def on_setup_complete(path) -> None:
logger.debug(f"Received setup_complete signal with path: {path}")
setup_result["path"] = path
# Connect to the setup_complete signal
dialog.setup_complete.connect(on_setup_complete)
# Show the dialog
result = dialog.exec()
logger.debug(f"Dialog result: {result} (Accepted={QDialog.DialogCode.Accepted})")
if result == QDialog.DialogCode.Accepted:
# First check if we received a path from the signal
if setup_result["path"]:
path_obj = Path(setup_result["path"]) if isinstance(setup_result["path"], str) else setup_result["path"]
if path_obj.exists():
logger.debug(f"Using path from signal: {setup_result['path']}")
return str(setup_result["path"])
# Get the expected path for verification as fallback
expected_path = DENO_APP_BIN_PATH
logger.debug(f"Expected Deno path: {expected_path}")
# Verify the path exists after dialog is accepted
if expected_path.exists():
logger.debug(f"Deno successfully found at expected path: {expected_path}")
return str(expected_path)
else:
logger.debug(f"Expected path does not exist, trying alternate detection")
# Try to use the get_deno_path function to find Deno elsewhere
deno_path = get_deno_path()
logger.debug(f"Alternate detection result: {deno_path}")
if deno_path != "deno":
path_obj = Path(deno_path) if isinstance(deno_path, str) else deno_path
if path_obj.exists():
logger.debug(f"Deno found at alternate location: {deno_path}")
return str(deno_path)
# Something went wrong, show an error message
logger.debug(f"Setup failed, showing error dialog")
if parent_widget:
error_dialog = QMessageBox(parent_widget)
error_dialog.setIcon(QMessageBox.Icon.Warning)
error_dialog.setWindowTitle(_("deno.setup_error"))
error_dialog.setText(_("deno.setup_failed"))
error_dialog.setWindowIcon(parent_widget.windowIcon())
error_dialog.setStyleSheet(
"""
QMessageBox {
background-color: #15181b;
color: #ffffff;
}
QLabel {
color: #ffffff;
}
QPushButton {
padding: 8px 15px;
background-color: #c90000;
border: none;
border-radius: 4px;
color: white;
font-weight: bold;
}
QPushButton:hover {
background-color: #a50000;
}
"""
)
error_dialog.exec()
logger.warning(f"Deno setup failed, path does not exist: {expected_path}")
else:
logger.debug("User cancelled the setup dialog")
# User cancelled or setup failed, return the fallback command
logger.debug("Returning fallback command 'deno'")
return "deno"
+41
View File
@@ -32,6 +32,7 @@ from src import __version__ as APP_VERSION
from src.core.ytsage_downloader import DownloadThread, SignalManager # Import downloader related classes
from src.core.ytsage_utils import check_ffmpeg, load_saved_path, parse_yt_dlp_error, save_path, should_check_for_auto_update, validate_video_url
from src.core.ytsage_yt_dlp import get_yt_dlp_path, setup_ytdlp # Import the new yt-dlp functions
from src.core.ytsage_deno import get_deno_path, setup_deno # Import the new Deno functions
from src.gui.ytsage_gui_dialogs import ( # use of src\gui\ytsage_gui_dialogs\__init__.py
AboutDialog,
AutoUpdateThread,
@@ -71,6 +72,13 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from
else:
logger.info(f"Using yt-dlp from: {ytdlp_path}")
# Check for Deno in our app's bin directory
deno_path = get_deno_path()
if deno_path == "deno": # Not found in app dir
self.show_deno_setup_dialog()
else:
logger.info(f"Using Deno from: {deno_path}")
self.version = APP_VERSION
self.check_for_updates()
@@ -1766,6 +1774,39 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from
)
success_dialog.exec()
def show_deno_setup_dialog(self) -> None:
"""Show the Deno setup dialog to configure Deno"""
deno_path = setup_deno(self)
if deno_path != "deno":
success_dialog = QMessageBox(self)
success_dialog.setIcon(QMessageBox.Icon.Information)
success_dialog.setWindowTitle(_("deno.setup_required"))
success_dialog.setText(f"{_('deno.success')}\n{deno_path}")
success_dialog.setWindowIcon(self.windowIcon())
success_dialog.setStyleSheet(
"""
QMessageBox {
background-color: #15181b;
color: #ffffff;
}
QLabel {
color: #ffffff;
}
QPushButton {
padding: 8px 15px;
background-color: #c90000;
border: none;
border-radius: 4px;
color: white;
font-weight: bold;
}
QPushButton:hover {
background-color: #a50000;
}
"""
)
success_dialog.exec()
def _analyze_url_with_subprocess(self, url) -> None:
"""Analyze URL using yt-dlp executable when Python module is not available"""
+14
View File
@@ -148,6 +148,20 @@ YTDLP_DOCS_URL: str = "https://github.com/yt-dlp/yt-dlp?tab=readme-ov-file#usage
# yt-dlp SHA256 checksums URL
YTDLP_SHA256_URL: str = "https://github.com/yt-dlp/yt-dlp/releases/latest/download/SHA2-256SUMS"
# Deno download URLs and paths
if OS_NAME == "Windows":
DENO_DOWNLOAD_URL: str = "https://github.com/denoland/deno/releases/latest/download/deno-x86_64-pc-windows-msvc.zip"
DENO_SHA256_URL: str = "https://github.com/denoland/deno/releases/latest/download/deno-x86_64-pc-windows-msvc.zip.sha256sum"
DENO_APP_BIN_PATH: Path = APP_BIN_DIR / "deno.exe"
elif OS_NAME == "Darwin": # macOS
DENO_DOWNLOAD_URL: str = "https://github.com/denoland/deno/releases/latest/download/deno-x86_64-apple-darwin.zip"
DENO_SHA256_URL: str = "https://github.com/denoland/deno/releases/latest/download/deno-x86_64-apple-darwin.zip.sha256sum"
DENO_APP_BIN_PATH: Path = APP_BIN_DIR / "deno"
else: # Linux
DENO_DOWNLOAD_URL: str = "https://github.com/denoland/deno/releases/latest/download/deno-x86_64-unknown-linux-gnu.zip"
DENO_SHA256_URL: str = "https://github.com/denoland/deno/releases/latest/download/deno-x86_64-unknown-linux-gnu.zip.sha256sum"
DENO_APP_BIN_PATH: Path = APP_BIN_DIR / "deno"
# 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"