Add proxy configuration support to downloader and GUI

Introduces proxy and geo-verification proxy options to the downloader and GUI dialogs, allowing users to specify network proxies for downloads and geo-restricted content. Updates the DownloadThread and main app logic to pass and apply these proxy settings, and adds validation and status indicators in the custom options dialog.
This commit is contained in:
Your Name
2025-09-22 14:49:58 +03:00
parent e9de913b47
commit d6fb926644
3 changed files with 240 additions and 0 deletions
+18
View File
@@ -59,6 +59,8 @@ class DownloadThread(QThread):
rate_limit=None,
download_section=None,
force_keyframes=False,
proxy_url=None,
geo_proxy_url=None,
) -> None:
super().__init__()
self.url = url
@@ -78,6 +80,8 @@ class DownloadThread(QThread):
self.rate_limit = rate_limit
self.download_section = download_section
self.force_keyframes = force_keyframes
self.proxy_url = proxy_url
self.geo_proxy_url = geo_proxy_url
self.paused = False
self.cancelled = False
self.process = None
@@ -156,6 +160,13 @@ class DownloadThread(QThread):
self.browser_cookies.split(":")[1] if ":" in self.browser_cookies else None,
)
# Add proxy settings if specified
if self.proxy_url:
ydl_opts_check["proxy"] = self.proxy_url
if self.geo_proxy_url:
ydl_opts_check["geo_verification_proxy"] = self.geo_proxy_url
if YT_DLP_AVAILABLE:
with yt_dlp.YoutubeDL(ydl_opts_check) as ydl:
info = ydl.extract_info(self.url, download=False)
@@ -324,6 +335,13 @@ class DownloadThread(QThread):
elif self.browser_cookies:
cmd.extend(["--cookies-from-browser", self.browser_cookies])
# Add proxy settings if specified
if self.proxy_url:
cmd.extend(["--proxy", self.proxy_url])
if self.geo_proxy_url:
cmd.extend(["--geo-verification-proxy", self.geo_proxy_url])
# Add rate limit if specified
if self.rate_limit:
cmd.extend(["-r", self.rate_limit])
@@ -326,9 +326,121 @@ class CustomOptionsDialog(QDialog):
)
command_layout.addWidget(self.log_output)
# === Proxy Tab ===
proxy_tab = QWidget()
proxy_layout = QVBoxLayout(proxy_tab)
# Help text
proxy_help_text = QLabel(
"Configure proxy settings for network connections and geo-verification.\n"
"Proxy can help bypass regional restrictions and improve download performance."
)
proxy_help_text.setWordWrap(True)
proxy_help_text.setStyleSheet("color: #999999; padding: 10px;")
proxy_layout.addWidget(proxy_help_text)
# Main Proxy section
main_proxy_group = QGroupBox("Main Proxy")
main_proxy_layout = QVBoxLayout(main_proxy_group)
main_proxy_help = QLabel("Use the specified HTTP/HTTPS/SOCKS proxy for all connections.")
main_proxy_help.setWordWrap(True)
main_proxy_help.setStyleSheet("color: #999999; font-size: 11px;")
main_proxy_layout.addWidget(main_proxy_help)
# Main proxy input
main_proxy_input_layout = QHBoxLayout()
main_proxy_input_layout.addWidget(QLabel("Proxy URL:"))
self.proxy_url_input = QLineEdit()
self.proxy_url_input.setPlaceholderText("e.g., http://proxy.example.com:8080 or socks5://user:pass@127.0.0.1:1080")
self.proxy_url_input.textChanged.connect(self.validate_proxy_inputs)
main_proxy_input_layout.addWidget(self.proxy_url_input)
main_proxy_layout.addLayout(main_proxy_input_layout)
# Example text
example_label = QLabel("Examples: http://proxy.com:8080, https://proxy.com:8080, socks5://127.0.0.1:1080")
example_label.setStyleSheet("color: #888888; font-size: 10px; font-style: italic;")
main_proxy_layout.addWidget(example_label)
proxy_layout.addWidget(main_proxy_group)
# Geo-verification Proxy section
geo_proxy_group = QGroupBox("Geo-verification Proxy")
geo_proxy_layout = QVBoxLayout(geo_proxy_group)
geo_proxy_help = QLabel(
"Use this proxy to verify IP address for geo-restricted sites. "
"The main proxy (if set) is used for actual downloading."
)
geo_proxy_help.setWordWrap(True)
geo_proxy_help.setStyleSheet("color: #999999; font-size: 11px;")
geo_proxy_layout.addWidget(geo_proxy_help)
# Geo proxy input
geo_proxy_input_layout = QHBoxLayout()
geo_proxy_input_layout.addWidget(QLabel("Geo Proxy URL:"))
self.geo_proxy_url_input = QLineEdit()
self.geo_proxy_url_input.setPlaceholderText("e.g., http://us-proxy.example.com:8080")
self.geo_proxy_url_input.textChanged.connect(self.validate_proxy_inputs)
geo_proxy_input_layout.addWidget(self.geo_proxy_url_input)
geo_proxy_layout.addLayout(geo_proxy_input_layout)
proxy_layout.addWidget(geo_proxy_group)
# Proxy status indicator
self.proxy_status = QLabel("")
self.proxy_status.setStyleSheet("color: #999999; font-style: italic;")
proxy_layout.addWidget(self.proxy_status)
# Clear buttons
clear_layout = QHBoxLayout()
clear_main_proxy_btn = QPushButton("Clear Main Proxy")
clear_main_proxy_btn.clicked.connect(lambda: self.proxy_url_input.clear())
clear_main_proxy_btn.setStyleSheet(
"""
QPushButton {
padding: 6px 12px;
background-color: #444444;
border: none;
border-radius: 4px;
color: white;
font-weight: bold;
}
QPushButton:hover {
background-color: #555555;
}
"""
)
clear_layout.addWidget(clear_main_proxy_btn)
clear_geo_proxy_btn = QPushButton("Clear Geo Proxy")
clear_geo_proxy_btn.clicked.connect(lambda: self.geo_proxy_url_input.clear())
clear_geo_proxy_btn.setStyleSheet(
"""
QPushButton {
padding: 6px 12px;
background-color: #444444;
border: none;
border-radius: 4px;
color: white;
font-weight: bold;
}
QPushButton:hover {
background-color: #555555;
}
"""
)
clear_layout.addWidget(clear_geo_proxy_btn)
clear_layout.addStretch()
proxy_layout.addLayout(clear_layout)
proxy_layout.addStretch()
# Add tabs to the tab widget
self.tab_widget.addTab(cookies_tab, "Login with Cookies")
self.tab_widget.addTab(command_tab, "Custom Command")
self.tab_widget.addTab(proxy_tab, "Proxy")
# Dialog buttons
button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
@@ -523,6 +635,78 @@ class CustomOptionsDialog(QDialog):
"""Returns True if browser cookies mode is selected"""
return self.cookie_browser_radio.isChecked()
def get_proxy_url(self) -> str | None:
"""Returns the main proxy URL if specified"""
proxy_url = self.proxy_url_input.text().strip()
return proxy_url if proxy_url else None
def get_geo_proxy_url(self) -> str | None:
"""Returns the geo-verification proxy URL if specified"""
geo_proxy_url = self.geo_proxy_url_input.text().strip()
return geo_proxy_url if geo_proxy_url else None
def validate_proxy_url(self, url: str) -> bool:
"""Basic validation for proxy URL format"""
if not url:
return True # Empty is OK
# Check if it starts with a valid scheme
valid_schemes = ['http://', 'https://', 'socks5://', 'socks4://']
if not any(url.lower().startswith(scheme) for scheme in valid_schemes):
return False
# Basic URL format check (contains at least host:port)
try:
# Remove the scheme to check host:port part
for scheme in valid_schemes:
if url.lower().startswith(scheme):
host_port = url[len(scheme):]
break
# Skip user:pass@ part if present
if '@' in host_port:
host_port = host_port.split('@')[1]
# Should have at least host:port
if ':' in host_port:
host, port = host_port.split(':', 1)
if host and port.isdigit():
return True
return False
except:
return False
def validate_proxy_inputs(self) -> None:
"""Validate proxy inputs and update status"""
main_proxy = self.proxy_url_input.text().strip()
geo_proxy = self.geo_proxy_url_input.text().strip()
if not main_proxy and not geo_proxy:
self.proxy_status.setText("")
return
issues = []
if main_proxy and not self.validate_proxy_url(main_proxy):
issues.append("Invalid main proxy URL format")
if geo_proxy and not self.validate_proxy_url(geo_proxy):
issues.append("Invalid geo proxy URL format")
if issues:
self.proxy_status.setText(" | ".join(issues))
self.proxy_status.setStyleSheet("color: #ff6666; font-style: italic;")
else:
status_parts = []
if main_proxy:
status_parts.append("Main proxy configured")
if geo_proxy:
status_parts.append("Geo proxy configured")
self.proxy_status.setText(" | ".join(status_parts))
self.proxy_status.setStyleSheet("color: #00cc00; font-style: italic;")
def run_custom_command(self) -> None:
url = self._parent.url_input.text().strip()
if not url:
+38
View File
@@ -110,6 +110,9 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from
# Initialize cookie settings - ensure they start clean
self.cookie_file_path = None
self.browser_cookies_option = None
# Initialize proxy settings
self.proxy_url = None
self.geo_proxy_url = None
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.download_section = None
@@ -1023,6 +1026,8 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from
rate_limit=rate_limit, # Pass the calculated rate limit
download_section=self.download_section, # Pass the download section
force_keyframes=self.force_keyframes, # Pass the force keyframes setting
proxy_url=self.proxy_url, # Pass the proxy URL
geo_proxy_url=self.geo_proxy_url, # Pass the geo-verification proxy URL
)
# Connect signals
@@ -1402,6 +1407,32 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from
)
# If neither is selected, both remain None (cleared above)
# Handle proxy options
proxy_url = dialog.get_proxy_url()
geo_proxy_url = dialog.get_geo_proxy_url()
# Clear existing proxy settings
self.proxy_url = None
self.geo_proxy_url = None
if proxy_url:
self.proxy_url = proxy_url
logger.info(f"Main proxy set: {self.proxy_url}")
QMessageBox.information(
self,
"Proxy Set",
f"Main proxy set: {proxy_url}",
)
if geo_proxy_url:
self.geo_proxy_url = geo_proxy_url
logger.info(f"Geo-verification proxy set: {self.geo_proxy_url}")
QMessageBox.information(
self,
"Geo Proxy Set",
f"Geo-verification proxy set: {geo_proxy_url}",
)
def show_about_dialog(self) -> None: # ADDED METHOD HERE
dialog = AboutDialog(self)
dialog.exec()
@@ -1708,6 +1739,13 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from
elif self.browser_cookies_option:
cmd.extend(["--cookies-from-browser", self.browser_cookies_option])
# Add proxy settings if available
if self.proxy_url:
cmd.extend(["--proxy", self.proxy_url])
if self.geo_proxy_url:
cmd.extend(["--geo-verification-proxy", self.geo_proxy_url])
# Execute command with hidden console window on Windows
# Extra logic moved to src\utils\ytsage_constants.py
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60, creationflags=SUBPROCESS_CREATIONFLAGS)