Add browser cookies support and improve error handling
Introduces support for extracting cookies from browsers in addition to cookie files, updating relevant logic and UI handling. Error handling for yt-dlp extraction is improved with user-friendly messages using a new error parser. Also updates version to 4.8.0b and refines cookie management throughout the app.
This commit is contained in:
+77
-17
@@ -30,7 +30,7 @@ from PySide6.QtWidgets import (
|
|||||||
from src.core.ytsage_downloader import DownloadThread, SignalManager # Import downloader related classes
|
from src.core.ytsage_downloader import DownloadThread, SignalManager # Import downloader related classes
|
||||||
from src.core.ytsage_logging import logger
|
from src.core.ytsage_logging import logger
|
||||||
from src.core.ytsage_utils import check_ffmpeg # Import utility functions
|
from src.core.ytsage_utils import check_ffmpeg # Import utility functions
|
||||||
from src.core.ytsage_utils import load_saved_path, save_path, should_check_for_auto_update
|
from src.core.ytsage_utils import load_saved_path, save_path, should_check_for_auto_update, parse_yt_dlp_error
|
||||||
from src.core.ytsage_yt_dlp import get_yt_dlp_path, setup_ytdlp # Import the new yt-dlp functions
|
from src.core.ytsage_yt_dlp import get_yt_dlp_path, setup_ytdlp # Import the new yt-dlp functions
|
||||||
from src.gui.ytsage_gui_dialogs import ( # use of src\gui\ytsage_gui_dialogs\__init__.py
|
from src.gui.ytsage_gui_dialogs import ( # use of src\gui\ytsage_gui_dialogs\__init__.py
|
||||||
AboutDialog,
|
AboutDialog,
|
||||||
@@ -48,6 +48,7 @@ from src.utils.ytsage_constants import ICON_PATH, SOUND_PATH, SUBPROCESS_CREATIO
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
import yt_dlp
|
import yt_dlp
|
||||||
|
from yt_dlp.utils import ExtractorError, DownloadError
|
||||||
|
|
||||||
YT_DLP_AVAILABLE = True
|
YT_DLP_AVAILABLE = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -85,7 +86,7 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from
|
|||||||
else:
|
else:
|
||||||
self.logger.info(f"Using yt-dlp from: {ytdlp_path}")
|
self.logger.info(f"Using yt-dlp from: {ytdlp_path}")
|
||||||
|
|
||||||
self.version = "4.7.0"
|
self.version = "4.8.0b"
|
||||||
self.check_for_updates()
|
self.check_for_updates()
|
||||||
|
|
||||||
# Check for auto-updates if enabled
|
# Check for auto-updates if enabled
|
||||||
@@ -118,7 +119,9 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from
|
|||||||
self.thumbnail_image = None
|
self.thumbnail_image = None
|
||||||
self.video_url = ""
|
self.video_url = ""
|
||||||
self.selected_subtitles = [] # Initialize selected subtitles list
|
self.selected_subtitles = [] # Initialize selected subtitles list
|
||||||
self.cookie_file_path = None # Initialize cookie file path
|
# Initialize cookie settings - ensure they start clean
|
||||||
|
self.cookie_file_path = None
|
||||||
|
self.browser_cookies_option = None
|
||||||
self.speed_limit_value = None # Store speed limit value
|
self.speed_limit_value = None # Store speed limit value
|
||||||
self.speed_limit_unit_index = 0 # Store speed limit unit index (0: KB/s, 1: MB/s)
|
self.speed_limit_unit_index = 0 # Store speed limit unit index (0: KB/s, 1: MB/s)
|
||||||
self.download_section = None
|
self.download_section = None
|
||||||
@@ -737,7 +740,7 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from
|
|||||||
"no_warnings": True, # <-- Suppress warnings for initial check
|
"no_warnings": True, # <-- Suppress warnings for initial check
|
||||||
"extract_flat": True,
|
"extract_flat": True,
|
||||||
"force_generic_extractor": False,
|
"force_generic_extractor": False,
|
||||||
"ignoreerrors": True,
|
"ignoreerrors": False, # Set to False to catch errors properly
|
||||||
"no_color": True,
|
"no_color": True,
|
||||||
"verbose": True,
|
"verbose": True,
|
||||||
"cookiefile": None,
|
"cookiefile": None,
|
||||||
@@ -745,16 +748,27 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from
|
|||||||
|
|
||||||
# Add cookies argument if cookie file path is set
|
# Add cookies argument if cookie file path is set
|
||||||
if self.cookie_file_path:
|
if self.cookie_file_path:
|
||||||
ydl_opts["cookiefile"] = self.cookie_file_path
|
ydl_opts["cookiefile"] = str(self.cookie_file_path)
|
||||||
|
elif self.browser_cookies_option:
|
||||||
|
ydl_opts["cookiesfrombrowser"] = (self.browser_cookies_option.split(':')[0],
|
||||||
|
self.browser_cookies_option.split(':')[1] if ':' in self.browser_cookies_option else None)
|
||||||
|
|
||||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||||
try:
|
try:
|
||||||
basic_info = ydl.extract_info(url, download=False)
|
basic_info = ydl.extract_info(url, download=False)
|
||||||
if not basic_info:
|
if not basic_info:
|
||||||
raise Exception("Could not extract basic video information")
|
# This case usually means the URL is invalid or not found
|
||||||
|
raise Exception("Invalid URL or video not found. Please check the link and try again.")
|
||||||
|
except (ExtractorError, DownloadError) as e:
|
||||||
|
# This is a yt-dlp specific error, use the original message
|
||||||
|
user_friendly_error = parse_yt_dlp_error(str(e))
|
||||||
|
raise Exception(user_friendly_error)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
# This is our own exception or other unexpected error
|
||||||
self.logger.error(f"First extraction failed: {str(e)}")
|
self.logger.error(f"First extraction failed: {str(e)}")
|
||||||
raise Exception("Could not extract video information, please check your link")
|
self.logger.error(f"Exception type: {type(e)}")
|
||||||
|
user_friendly_error = parse_yt_dlp_error(str(e))
|
||||||
|
raise Exception(user_friendly_error)
|
||||||
|
|
||||||
self.signals.update_status.emit("Analyzing (30%)... Extracting detailed info")
|
self.signals.update_status.emit("Analyzing (30%)... Extracting detailed info")
|
||||||
# Configure options for detailed extraction (keep other options)
|
# Configure options for detailed extraction (keep other options)
|
||||||
@@ -774,7 +788,10 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from
|
|||||||
|
|
||||||
# Add cookies argument if cookie file path is set
|
# Add cookies argument if cookie file path is set
|
||||||
if self.cookie_file_path:
|
if self.cookie_file_path:
|
||||||
ydl_opts_detail["cookiefile"] = self.cookie_file_path
|
ydl_opts_detail["cookiefile"] = str(self.cookie_file_path)
|
||||||
|
elif self.browser_cookies_option:
|
||||||
|
ydl_opts_detail["cookiesfrombrowser"] = (self.browser_cookies_option.split(':')[0],
|
||||||
|
self.browser_cookies_option.split(':')[1] if ':' in self.browser_cookies_option else None)
|
||||||
|
|
||||||
# Use a separate options dict for the detailed extraction
|
# Use a separate options dict for the detailed extraction
|
||||||
with yt_dlp.YoutubeDL(ydl_opts_detail) as ydl_detail:
|
with yt_dlp.YoutubeDL(ydl_opts_detail) as ydl_detail:
|
||||||
@@ -798,8 +815,14 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from
|
|||||||
try:
|
try:
|
||||||
# Use the ydl_detail instance with no_warnings
|
# Use the ydl_detail instance with no_warnings
|
||||||
self.video_info = ydl_detail.extract_info(first_video_url, download=False)
|
self.video_info = ydl_detail.extract_info(first_video_url, download=False)
|
||||||
|
except (ExtractorError, DownloadError) as first_video_error:
|
||||||
|
# Use error parser for yt-dlp specific playlist video errors
|
||||||
|
user_friendly_error = parse_yt_dlp_error(str(first_video_error))
|
||||||
|
raise Exception(f"Failed to extract info for the first playlist video: {user_friendly_error}")
|
||||||
except Exception as first_video_error:
|
except Exception as first_video_error:
|
||||||
raise Exception(f"Failed to extract info for the first playlist video: {first_video_error}")
|
# Use error parser for other playlist video errors too
|
||||||
|
user_friendly_error = parse_yt_dlp_error(str(first_video_error))
|
||||||
|
raise Exception(f"Failed to extract info for the first playlist video: {user_friendly_error}")
|
||||||
|
|
||||||
# Update playlist info label text (remains the same)
|
# Update playlist info label text (remains the same)
|
||||||
playlist_text = (
|
playlist_text = (
|
||||||
@@ -886,9 +909,16 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from
|
|||||||
|
|
||||||
self.signals.update_status.emit("Analysis complete!")
|
self.signals.update_status.emit("Analysis complete!")
|
||||||
|
|
||||||
|
except (ExtractorError, DownloadError) as e:
|
||||||
|
logger.error(f"yt-dlp detailed extraction failed: {e}", exc_info=True)
|
||||||
|
# Use the error parser for yt-dlp specific errors
|
||||||
|
user_friendly_error = parse_yt_dlp_error(str(e))
|
||||||
|
raise Exception(user_friendly_error)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Detailed extraction failed: {e}", exc_info=True)
|
logger.error(f"Detailed extraction failed: {e}", exc_info=True)
|
||||||
raise Exception(f"Failed to extract video details: {str(e)}")
|
# Use the error parser for other extraction errors too
|
||||||
|
user_friendly_error = parse_yt_dlp_error(str(e))
|
||||||
|
raise Exception(user_friendly_error)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Error in analysis: {e}", exc_info=True)
|
self.logger.error(f"Error in analysis: {e}", exc_info=True)
|
||||||
@@ -1023,6 +1053,7 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from
|
|||||||
save_description=self.save_description, # Pass the new flag here
|
save_description=self.save_description, # Pass the new flag here
|
||||||
embed_chapters=self.embed_chapters, # Pass the embed chapters flag
|
embed_chapters=self.embed_chapters, # Pass the embed chapters flag
|
||||||
cookie_file=self.cookie_file_path, # Pass the cookie file path
|
cookie_file=self.cookie_file_path, # Pass the cookie file path
|
||||||
|
browser_cookies=self.browser_cookies_option, # Pass the browser cookies option
|
||||||
rate_limit=rate_limit, # Pass the calculated rate limit
|
rate_limit=rate_limit, # Pass the calculated rate limit
|
||||||
download_section=self.download_section, # Pass the download section
|
download_section=self.download_section, # Pass the download section
|
||||||
force_keyframes=self.force_keyframes, # Pass the force keyframes setting
|
force_keyframes=self.force_keyframes, # Pass the force keyframes setting
|
||||||
@@ -1381,8 +1412,14 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from
|
|||||||
def show_custom_options(self) -> None:
|
def show_custom_options(self) -> None:
|
||||||
dialog = CustomOptionsDialog(self)
|
dialog = CustomOptionsDialog(self)
|
||||||
if dialog.exec():
|
if dialog.exec():
|
||||||
# Handle cookies if set
|
# Handle cookies
|
||||||
cookie_path = dialog.get_cookie_file_path()
|
cookie_path = dialog.get_cookie_file_path()
|
||||||
|
browser_cookies = dialog.get_browser_cookies_option()
|
||||||
|
|
||||||
|
# Clear both first to avoid conflicts
|
||||||
|
self.cookie_file_path = None
|
||||||
|
self.browser_cookies_option = None
|
||||||
|
|
||||||
if cookie_path:
|
if cookie_path:
|
||||||
self.cookie_file_path = cookie_path
|
self.cookie_file_path = cookie_path
|
||||||
self.logger.info(f"Selected cookie file: {self.cookie_file_path}")
|
self.logger.info(f"Selected cookie file: {self.cookie_file_path}")
|
||||||
@@ -1391,9 +1428,15 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from
|
|||||||
"Cookie File Selected",
|
"Cookie File Selected",
|
||||||
f"Cookie file selected: {self.cookie_file_path}",
|
f"Cookie file selected: {self.cookie_file_path}",
|
||||||
)
|
)
|
||||||
else:
|
elif browser_cookies:
|
||||||
# Don't clear the cookie path if nothing was selected
|
self.browser_cookies_option = browser_cookies
|
||||||
pass
|
self.logger.info(f"Selected browser cookies: {self.browser_cookies_option}")
|
||||||
|
QMessageBox.information(
|
||||||
|
self,
|
||||||
|
"Browser Cookies Selected",
|
||||||
|
f"Browser cookies will be extracted from: {browser_cookies}",
|
||||||
|
)
|
||||||
|
# If neither is selected, both remain None (cleared above)
|
||||||
|
|
||||||
def show_about_dialog(self) -> None: # ADDED METHOD HERE
|
def show_about_dialog(self) -> None: # ADDED METHOD HERE
|
||||||
dialog = AboutDialog(self)
|
dialog = AboutDialog(self)
|
||||||
@@ -1571,16 +1614,31 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from
|
|||||||
dialog = CustomOptionsDialog(self)
|
dialog = CustomOptionsDialog(self)
|
||||||
dialog.tab_widget.setCurrentIndex(0) # Select the Cookie Login tab
|
dialog.tab_widget.setCurrentIndex(0) # Select the Cookie Login tab
|
||||||
if dialog.exec():
|
if dialog.exec():
|
||||||
self.cookie_file_path = dialog.get_cookie_file_path()
|
# Handle cookies
|
||||||
if self.cookie_file_path:
|
cookie_path = dialog.get_cookie_file_path()
|
||||||
|
browser_cookies = dialog.get_browser_cookies_option()
|
||||||
|
|
||||||
|
if cookie_path:
|
||||||
|
self.cookie_file_path = cookie_path
|
||||||
|
self.browser_cookies_option = None # Clear browser cookies if file is used
|
||||||
self.logger.info(f"Selected cookie file: {self.cookie_file_path}")
|
self.logger.info(f"Selected cookie file: {self.cookie_file_path}")
|
||||||
QMessageBox.information(
|
QMessageBox.information(
|
||||||
self,
|
self,
|
||||||
"Cookie File Selected",
|
"Cookie File Selected",
|
||||||
f"Cookie file selected: {self.cookie_file_path}",
|
f"Cookie file selected: {self.cookie_file_path}",
|
||||||
)
|
)
|
||||||
|
elif browser_cookies:
|
||||||
|
self.browser_cookies_option = browser_cookies
|
||||||
|
self.cookie_file_path = None # Clear file cookies if browser is used
|
||||||
|
self.logger.info(f"Selected browser cookies: {self.browser_cookies_option}")
|
||||||
|
QMessageBox.information(
|
||||||
|
self,
|
||||||
|
"Browser Cookies Selected",
|
||||||
|
f"Browser cookies will be extracted from: {browser_cookies}",
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.cookie_file_path = None # Clear path if dialog accepted but no file selected
|
self.cookie_file_path = None # Clear path if dialog accepted but no file selected
|
||||||
|
self.browser_cookies_option = None # Clear browser cookies too
|
||||||
|
|
||||||
def cancel_download(self) -> None:
|
def cancel_download(self) -> None:
|
||||||
if self.current_download:
|
if self.current_download:
|
||||||
@@ -1678,7 +1736,9 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from
|
|||||||
|
|
||||||
# Add cookies if available
|
# Add cookies if available
|
||||||
if self.cookie_file_path:
|
if self.cookie_file_path:
|
||||||
cmd.extend(["--cookies", self.cookie_file_path])
|
cmd.extend(["--cookies", str(self.cookie_file_path)])
|
||||||
|
elif self.browser_cookies_option:
|
||||||
|
cmd.extend(["--cookies-from-browser", self.browser_cookies_option])
|
||||||
|
|
||||||
# Execute command with hidden console window on Windows
|
# Execute command with hidden console window on Windows
|
||||||
# Extra logic moved to src\utils\ytsage_constants.py
|
# Extra logic moved to src\utils\ytsage_constants.py
|
||||||
|
|||||||
Reference in New Issue
Block a user