Remove yt-dlp Python package usage; use binary only
Refactored all modules to remove dependency on the yt-dlp Python package, switching to subprocess calls to the yt-dlp binary for all extraction and update operations. Update logic now exclusively manages the app's own yt-dlp binary and no longer supports pip-based updates. Simplified format/audio detection and thumbnail extraction to use CLI heuristics and subprocess output. This improves reliability and avoids Python package conflicts.
This commit is contained in:
@@ -1,13 +1,12 @@
|
||||
"""
|
||||
Update-related dialogs and threads for YTSage application.
|
||||
Contains dialogs and background threads for checking and performing yt-dlp updates.
|
||||
Contains dialogs and background threads for checking and performing yt-dlp binary updates.
|
||||
Note: This module only handles binary updates. Python package updates have been removed.
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from importlib.metadata import PackageNotFoundError
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
@@ -25,30 +24,6 @@ _ = LocalizationManager.get_text
|
||||
from src.utils.ytsage_localization import _
|
||||
from src.utils.ytsage_logger import logger
|
||||
|
||||
try:
|
||||
from importlib.metadata import PackageNotFoundError as ImportlibPackageNotFoundError
|
||||
from importlib.metadata import version as importlib_version
|
||||
|
||||
def get_version(package_name: str) -> str:
|
||||
return importlib_version(package_name)
|
||||
|
||||
PackageNotFoundError = ImportlibPackageNotFoundError
|
||||
except ImportError:
|
||||
# Fallback for older Python versions
|
||||
import pkg_resources
|
||||
|
||||
def get_version(package_name: str) -> str:
|
||||
return pkg_resources.get_distribution(package_name).version
|
||||
|
||||
PackageNotFoundError = pkg_resources.DistributionNotFound
|
||||
|
||||
try:
|
||||
import yt_dlp
|
||||
|
||||
YT_DLP_AVAILABLE = True
|
||||
except ImportError:
|
||||
YT_DLP_AVAILABLE = False
|
||||
|
||||
|
||||
class VersionCheckThread(QThread):
|
||||
finished = Signal(str, str, str) # current_version, latest_version, error_message
|
||||
@@ -73,31 +48,20 @@ class VersionCheckThread(QThread):
|
||||
)
|
||||
if result.returncode == 0:
|
||||
current_version = result.stdout.strip()
|
||||
else: # Try fallback if command failed
|
||||
if YT_DLP_AVAILABLE:
|
||||
current_version = yt_dlp.version.__version__ # type: ignore[reportAttributeAccessIssue]
|
||||
else:
|
||||
error_message = "yt-dlp not available."
|
||||
self.finished.emit(current_version, latest_version, error_message)
|
||||
else:
|
||||
error_message = "yt-dlp binary not accessible."
|
||||
self.finished.emit(current_version, latest_version, error_message)
|
||||
return
|
||||
except subprocess.TimeoutExpired:
|
||||
# Try fallback if timeout
|
||||
if YT_DLP_AVAILABLE:
|
||||
current_version = yt_dlp.version.__version__ # type: ignore[reportAttributeAccessIssue]
|
||||
else:
|
||||
error_message = "yt-dlp version check timed out and package not found."
|
||||
self.finished.emit(current_version, latest_version, error_message)
|
||||
return
|
||||
except Exception:
|
||||
# Fallback to importing yt_dlp package directly if subprocess fails
|
||||
if YT_DLP_AVAILABLE:
|
||||
current_version = yt_dlp.version.__version__ # type: ignore[reportAttributeAccessIssue]
|
||||
else:
|
||||
error_message = "yt-dlp not found or accessible."
|
||||
self.finished.emit(current_version, latest_version, error_message)
|
||||
return
|
||||
error_message = "yt-dlp version check timed out."
|
||||
self.finished.emit(current_version, latest_version, error_message)
|
||||
return
|
||||
except Exception as e:
|
||||
error_message = f"yt-dlp not found or accessible: {e}"
|
||||
self.finished.emit(current_version, latest_version, error_message)
|
||||
return
|
||||
|
||||
# Get latest version from PyPI
|
||||
# Get latest version from PyPI (yt-dlp releases are also published to PyPI)
|
||||
response = requests.get("https://pypi.org/pypi/yt-dlp/json", timeout=10)
|
||||
response.raise_for_status()
|
||||
latest_version = response.json()["info"]["version"]
|
||||
@@ -135,33 +99,11 @@ class UpdateThread(QThread):
|
||||
self.update_finished.emit(False, _('update.error_getting_path', error=e))
|
||||
return
|
||||
|
||||
# Extra logic moved to src\utils\ytsage_constants.py
|
||||
|
||||
self.update_progress.emit(20)
|
||||
|
||||
# Extra logic moved to src\utils\ytsage_constants.py
|
||||
# Check if this is an app-managed binary by comparing paths safely
|
||||
is_app_managed = False
|
||||
try:
|
||||
# Only compare if both files exist
|
||||
if yt_dlp_path.exists() and YTDLP_APP_BIN_PATH.exists():
|
||||
is_app_managed = yt_dlp_path.samefile(YTDLP_APP_BIN_PATH)
|
||||
elif str(yt_dlp_path) == str(YTDLP_APP_BIN_PATH):
|
||||
# If paths are identical as strings, consider it app-managed
|
||||
is_app_managed = True
|
||||
else:
|
||||
# If app binary doesn't exist, this is definitely not app-managed
|
||||
is_app_managed = False
|
||||
except (OSError, IOError) as e:
|
||||
logger.debug(f"Error comparing paths: {e}")
|
||||
is_app_managed = False
|
||||
|
||||
if is_app_managed:
|
||||
self.update_status.emit(_('update.updating_binary'))
|
||||
success = self._update_binary(yt_dlp_path)
|
||||
else:
|
||||
self.update_status.emit(_('update.updating_pip'))
|
||||
success = self._update_via_pip()
|
||||
# Update the binary (no more pip-based updates)
|
||||
self.update_status.emit(_('update.updating_binary'))
|
||||
success = self._update_binary(yt_dlp_path)
|
||||
|
||||
if success:
|
||||
self.update_progress.emit(100)
|
||||
@@ -219,77 +161,6 @@ class UpdateThread(QThread):
|
||||
self.update_status.emit(_('update.unexpected_error', error=e))
|
||||
return False
|
||||
|
||||
def _update_via_pip(self) -> bool:
|
||||
"""Update yt-dlp via pip."""
|
||||
try:
|
||||
self.update_status.emit(_('update.checking_pip'))
|
||||
self.update_progress.emit(30)
|
||||
|
||||
# Get current version
|
||||
try:
|
||||
current_version = get_version("yt-dlp")
|
||||
self.update_status.emit(_('update.current_version', version=current_version))
|
||||
except PackageNotFoundError:
|
||||
self.update_status.emit(_('update.not_found_pip'))
|
||||
current_version = "0.0.0"
|
||||
|
||||
self.update_progress.emit(40)
|
||||
|
||||
# Get the latest version from PyPI
|
||||
self.update_status.emit(_('update.checking_latest'))
|
||||
response = requests.get("https://pypi.org/pypi/yt-dlp/json", timeout=10)
|
||||
|
||||
if response.status_code != 200:
|
||||
self.update_status.emit(_('update.failed_check_updates'))
|
||||
return False
|
||||
|
||||
data = response.json()
|
||||
latest_version = data["info"]["version"]
|
||||
self.update_status.emit(_('update.latest_version', version=latest_version))
|
||||
self.update_progress.emit(50)
|
||||
|
||||
# Compare versions
|
||||
if version.parse(latest_version) > version.parse(current_version):
|
||||
self.update_status.emit(_('update.updating_from_to', current=current_version, latest=latest_version))
|
||||
self.update_progress.emit(60)
|
||||
|
||||
try:
|
||||
# Run pip update with timeout
|
||||
self.update_status.emit(_('update.running_pip_install'))
|
||||
update_result = subprocess.run(
|
||||
[sys.executable, "-m", "pip", "install", "--upgrade", "yt-dlp"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
timeout=300, # 5 minute timeout for pip install
|
||||
creationflags=SUBPROCESS_CREATIONFLAGS,
|
||||
)
|
||||
|
||||
self.update_progress.emit(85)
|
||||
|
||||
if update_result.returncode == 0:
|
||||
self.update_status.emit(_('update.pip_completed'))
|
||||
self.update_progress.emit(95)
|
||||
return True
|
||||
else:
|
||||
self.update_status.emit(_('update.pip_failed', error=update_result.stderr))
|
||||
return False
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
self.update_status.emit(_("update.pip_timeout"))
|
||||
return False
|
||||
except Exception as e:
|
||||
self.update_status.emit(_('update.error_pip_update', error=e))
|
||||
return False
|
||||
else:
|
||||
self.update_status.emit(_("update.already_up_to_date"))
|
||||
self.update_progress.emit(95)
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.update_status.emit(_('update.pip_update_failed', error=e))
|
||||
return False
|
||||
|
||||
|
||||
class YTDLPUpdateDialog(QDialog):
|
||||
def __init__(self, parent=None) -> None:
|
||||
@@ -569,35 +440,14 @@ class AutoUpdateThread(QThread):
|
||||
self.update_finished.emit(False, f"Critical error: {e}")
|
||||
|
||||
def _perform_update(self) -> bool:
|
||||
"""Perform the actual update using similar logic to UpdateThread but without UI feedback."""
|
||||
"""Perform the actual binary update."""
|
||||
try:
|
||||
# Get the yt-dlp path
|
||||
yt_dlp_path = get_yt_dlp_path()
|
||||
|
||||
# Extra logic moved to src\utils\ytsage_constants.py
|
||||
|
||||
# Check if this is an app-managed binary by comparing paths safely
|
||||
is_app_managed = False
|
||||
try:
|
||||
# Only compare if both files exist
|
||||
if yt_dlp_path.exists() and YTDLP_APP_BIN_PATH.exists():
|
||||
is_app_managed = yt_dlp_path.samefile(YTDLP_APP_BIN_PATH)
|
||||
elif str(yt_dlp_path) == str(YTDLP_APP_BIN_PATH):
|
||||
# If paths are identical as strings, consider it app-managed
|
||||
is_app_managed = True
|
||||
else:
|
||||
# If app binary doesn't exist, this is definitely not app-managed
|
||||
is_app_managed = False
|
||||
except (OSError, IOError) as e:
|
||||
logger.debug(f"AutoUpdateThread: Error comparing paths: {e}")
|
||||
is_app_managed = False
|
||||
|
||||
if is_app_managed:
|
||||
logger.info("AutoUpdateThread: Updating app-managed yt-dlp binary...")
|
||||
return self._update_binary(yt_dlp_path)
|
||||
else:
|
||||
logger.info("AutoUpdateThread: Updating system yt-dlp via pip...")
|
||||
return self._update_via_pip()
|
||||
# Always update the binary (no more pip-based updates)
|
||||
logger.info("AutoUpdateThread: Updating yt-dlp binary...")
|
||||
return self._update_binary(yt_dlp_path)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"AutoUpdateThread: Error in _perform_update: {e}")
|
||||
@@ -636,58 +486,3 @@ class AutoUpdateThread(QThread):
|
||||
except Exception as e:
|
||||
logger.exception(f"AutoUpdateThread: Unexpected error during update: {e}")
|
||||
return False
|
||||
|
||||
def _update_via_pip(self) -> bool:
|
||||
"""Update yt-dlp via pip (silent version)."""
|
||||
try:
|
||||
logger.info("AutoUpdateThread: Checking current pip installation...")
|
||||
|
||||
# Get current version
|
||||
try:
|
||||
current_version = get_version("yt-dlp")
|
||||
logger.info(f"AutoUpdateThread: Current version: {current_version}")
|
||||
except PackageNotFoundError:
|
||||
logger.warning("AutoUpdateThread: yt-dlp not found via pip, attempting installation...")
|
||||
current_version = "0.0.0"
|
||||
|
||||
# Get the latest version from PyPI
|
||||
logger.info("AutoUpdateThread: Checking for latest version...")
|
||||
response = requests.get("https://pypi.org/pypi/yt-dlp/json", timeout=10)
|
||||
|
||||
if response.status_code != 200:
|
||||
logger.error("AutoUpdateThread: Failed to check for updates")
|
||||
return False
|
||||
|
||||
data = response.json()
|
||||
latest_version = data["info"]["version"]
|
||||
logger.info(f"AutoUpdateThread: Latest version: {latest_version}")
|
||||
|
||||
# Compare versions
|
||||
if version.parse(latest_version) > version.parse(current_version):
|
||||
logger.info(f"AutoUpdateThread: Updating from {current_version} to {latest_version}...")
|
||||
|
||||
# Extra logic moved to src\utils\ytsage_constants.py
|
||||
|
||||
# Run pip update
|
||||
logger.info("AutoUpdateThread: Running pip install --upgrade...")
|
||||
update_result = subprocess.run(
|
||||
[sys.executable, "-m", "pip", "install", "--upgrade", "yt-dlp"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
creationflags=SUBPROCESS_CREATIONFLAGS,
|
||||
)
|
||||
|
||||
if update_result.returncode == 0:
|
||||
logger.info("AutoUpdateThread: Pip update completed successfully!")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"AutoUpdateThread: Pip update failed: {update_result.stderr}")
|
||||
return False
|
||||
else:
|
||||
logger.info("AutoUpdateThread: yt-dlp is already up to date!")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"AutoUpdateThread: Pip update failed: {e}")
|
||||
return False
|
||||
|
||||
Reference in New Issue
Block a user