Add generic-mode URL validation and UI
Introduce a "generic_mode" option to allow validating/downloading from non-YouTube sites and wire it through the UI, config, and validation logic. Key changes: - Add generic_mode default to ConfigManager and persist setting from DownloadSettingsDialog (checkbox + help text). - Extend validate_video_url to accept a generic_mode flag and allow any http/https URL with a domain when enabled; pass this flag from Analysis and Download flows. - Update YTSageApp to load/save generic_mode, update URL placeholder and settings tooltip behavior, and refresh tooltip when settings change. - Improve robustness in FormatTableMixin: handle None/incorrect types for format_note, abr, resolution, ext, and codec values to avoid type errors and ensure consistent display. - Add localization entries for generic mode, placeholder, and related help text across multiple language files and update the in-app default localization strings. These changes enable broader site support via yt-dlp while hardening UI format handling and keeping user settings persistent.
This commit is contained in:
@@ -287,7 +287,7 @@ class AnalysisMixin:
|
||||
return
|
||||
|
||||
# Validate URL before processing
|
||||
is_valid, error_message = validate_video_url(url)
|
||||
is_valid, error_message = validate_video_url(url, generic_mode=self.generic_mode_enabled)
|
||||
if not is_valid:
|
||||
QMessageBox.warning(self, _("main_ui.error_title"), error_message)
|
||||
if hasattr(self, "animate_widget_shake"):
|
||||
|
||||
@@ -195,6 +195,24 @@ class DownloadSettingsDialog(QDialog):
|
||||
speed_group_box.setLayout(speed_layout)
|
||||
layout.addWidget(speed_group_box)
|
||||
|
||||
# --- Generic Mode Section ---
|
||||
generic_mode_group_box = QGroupBox(_("settings.generic_mode"))
|
||||
generic_mode_layout = QVBoxLayout()
|
||||
|
||||
self.generic_mode_enabled = ConfigManager.get("generic_mode") or False
|
||||
|
||||
self.generic_mode_checkbox = QCheckBox(_("settings.enable_generic_mode"))
|
||||
self.generic_mode_checkbox.setChecked(self.generic_mode_enabled)
|
||||
generic_mode_layout.addWidget(self.generic_mode_checkbox)
|
||||
|
||||
generic_mode_help_label = QLabel(_("settings.generic_mode_help"))
|
||||
generic_mode_help_label.setWordWrap(True)
|
||||
generic_mode_help_label.setStyleSheet("color: #cccccc; margin: 5px; font-size: 11px;")
|
||||
generic_mode_layout.addWidget(generic_mode_help_label)
|
||||
|
||||
generic_mode_group_box.setLayout(generic_mode_layout)
|
||||
layout.addWidget(generic_mode_group_box)
|
||||
|
||||
# --- Output Format Settings Section ---
|
||||
output_format_group_box = QGroupBox(_("settings.output_format_settings"))
|
||||
output_format_layout = QVBoxLayout()
|
||||
@@ -349,6 +367,10 @@ class DownloadSettingsDialog(QDialog):
|
||||
"""Returns whether force output format is enabled."""
|
||||
return self.force_format_checkbox.isChecked()
|
||||
|
||||
def get_generic_mode_enabled(self) -> bool:
|
||||
"""Returns whether generic mode is enabled."""
|
||||
return self.generic_mode_checkbox.isChecked()
|
||||
|
||||
def get_preferred_format(self) -> str:
|
||||
"""Returns the selected preferred format (lowercase)."""
|
||||
format_map = {0: "mp4", 1: "webm", 2: "mkv"}
|
||||
@@ -405,6 +427,8 @@ class DownloadSettingsDialog(QDialog):
|
||||
def accept(self) -> None:
|
||||
"""Override accept to save format settings."""
|
||||
try:
|
||||
ConfigManager.set("generic_mode", self.get_generic_mode_enabled())
|
||||
|
||||
# Save output format settings
|
||||
force_format = self.get_force_format_enabled()
|
||||
preferred_format = self.get_preferred_format()
|
||||
|
||||
@@ -207,7 +207,7 @@ class FormatTableMixin:
|
||||
audio_formats = [
|
||||
f
|
||||
for f in self.all_formats
|
||||
if (f.get("vcodec") == "none" or "audio only" in f.get("format_note", "").lower())
|
||||
if (f.get("vcodec") == "none" or "audio only" in str(f.get("format_note") or "").lower())
|
||||
and f.get("acodec") != "none"
|
||||
and f.get("filesize") is not None
|
||||
]
|
||||
@@ -224,7 +224,8 @@ class FormatTableMixin:
|
||||
except (ValueError, IndexError):
|
||||
return 0
|
||||
else:
|
||||
return f.get("abr", 0)
|
||||
abr = f.get("abr") or 0
|
||||
return abr if isinstance(abr, (int, float)) else 0
|
||||
|
||||
video_formats.sort(key=get_quality, reverse=True)
|
||||
audio_formats.sort(key=get_quality, reverse=True)
|
||||
@@ -320,7 +321,9 @@ class FormatTableMixin:
|
||||
self.format_table.setItem(row, 1, quality_item)
|
||||
|
||||
# Resolution
|
||||
resolution = f.get("resolution", "N/A")
|
||||
resolution = f.get("resolution") or "N/A"
|
||||
if not isinstance(resolution, str):
|
||||
resolution = str(resolution)
|
||||
|
||||
if is_playlist_mode:
|
||||
# Column 2 for playlist mode: Resolution
|
||||
@@ -361,7 +364,8 @@ class FormatTableMixin:
|
||||
self.format_table.setItem(row, 4, hdr_item)
|
||||
else:
|
||||
# Extension for normal mode (column 2)
|
||||
self.format_table.setItem(row, 2, QTableWidgetItem(f.get("ext", "").upper()))
|
||||
extension = str(f.get("ext") or "")
|
||||
self.format_table.setItem(row, 2, QTableWidgetItem(extension.upper()))
|
||||
|
||||
# Audio Status column
|
||||
needs_audio = f.get("acodec") == "none" and f.get("vcodec") != "none"
|
||||
@@ -387,11 +391,11 @@ class FormatTableMixin:
|
||||
|
||||
# Column 5: Codec
|
||||
if f.get("vcodec") == "none":
|
||||
codec = f.get("acodec", "N/A")
|
||||
codec = str(f.get("acodec") or "N/A")
|
||||
else:
|
||||
codec = f"{f.get('vcodec', 'N/A')}"
|
||||
codec = str(f.get("vcodec") or "N/A")
|
||||
if f.get("acodec") != "none":
|
||||
codec += f" / {f.get('acodec', 'N/A')}"
|
||||
codec += f" / {str(f.get('acodec') or 'N/A')}"
|
||||
self.format_table.setItem(row, 5, QTableWidgetItem(codec))
|
||||
|
||||
# Column 7: FPS (Frame Rate)
|
||||
@@ -469,7 +473,9 @@ class FormatTableMixin:
|
||||
|
||||
if format_info.get("vcodec") == "none":
|
||||
# Audio quality
|
||||
abr = format_info.get("abr", 0)
|
||||
abr = format_info.get("abr") or 0
|
||||
if not isinstance(abr, (int, float)):
|
||||
abr = 0
|
||||
if abr >= 256:
|
||||
return _("formats.best_audio")
|
||||
elif abr >= 192:
|
||||
|
||||
@@ -257,6 +257,7 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin, AnalysisMixin):
|
||||
self.preferred_output_format = ConfigManager.get("preferred_output_format") or "mp4"
|
||||
self.force_audio_format = ConfigManager.get("force_audio_format") or False
|
||||
self.preferred_audio_format = ConfigManager.get("preferred_audio_format") or "best"
|
||||
self.generic_mode_enabled = ConfigManager.get("generic_mode") or False
|
||||
# Track if video analysis is completed
|
||||
self.analysis_completed = False
|
||||
|
||||
@@ -351,7 +352,7 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin, AnalysisMixin):
|
||||
url_layout.setSpacing(10)
|
||||
|
||||
self.url_input = QLineEdit()
|
||||
self.url_input.setPlaceholderText(_("main_ui.url_placeholder"))
|
||||
self._update_url_placeholder()
|
||||
self.url_input.returnPressed.connect(self.analyze_url) # Analyze on Enter key
|
||||
self.url_input.textChanged.connect(self._on_url_text_changed) # Enable/disable analyze button
|
||||
self.url_input.setMinimumHeight(42)
|
||||
@@ -489,13 +490,7 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin, AnalysisMixin):
|
||||
# --- Rename Path Button to Settings Button ---
|
||||
self.settings_button = QPushButton(_("buttons.download_settings")) # Renamed button
|
||||
self.settings_button.clicked.connect(self.show_download_settings_dialog) # Renamed method
|
||||
self.settings_button.setToolTip(
|
||||
_(
|
||||
"main_ui.settings_tooltip",
|
||||
path=self.last_path,
|
||||
speed_limit=_("main_ui.speed_limit_none"),
|
||||
)
|
||||
) # Update initial tooltip
|
||||
self._update_settings_tooltip()
|
||||
# --- End Settings Button ---
|
||||
|
||||
self.download_btn = QPushButton(_("buttons.download"))
|
||||
@@ -581,6 +576,27 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin, AnalysisMixin):
|
||||
"""Enable or disable the Analyze button based on URL input content."""
|
||||
self.analyze_button.setEnabled(bool(text.strip()))
|
||||
|
||||
def _get_speed_limit_tooltip_text(self) -> str:
|
||||
"""Return the current speed limit string for the settings tooltip."""
|
||||
if self.speed_limit_value:
|
||||
return f"{self.speed_limit_value} {['KB/s', 'MB/s'][self.speed_limit_unit_index]}"
|
||||
return _("main_ui.speed_limit_none")
|
||||
|
||||
def _update_settings_tooltip(self) -> None:
|
||||
"""Refresh the download settings tooltip text."""
|
||||
self.settings_button.setToolTip(
|
||||
_(
|
||||
"main_ui.settings_tooltip",
|
||||
path=self.last_path,
|
||||
speed_limit=self._get_speed_limit_tooltip_text(),
|
||||
)
|
||||
)
|
||||
|
||||
def _update_url_placeholder(self) -> None:
|
||||
"""Update the URL placeholder based on the selected validation mode."""
|
||||
placeholder_key = "main_ui.url_placeholder_generic" if self.generic_mode_enabled else "main_ui.url_placeholder"
|
||||
self.url_input.setPlaceholderText(_(placeholder_key))
|
||||
|
||||
|
||||
|
||||
def paste_url(self) -> None:
|
||||
@@ -631,18 +647,18 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin, AnalysisMixin):
|
||||
audio_format_changed = True
|
||||
logger.info(f"Audio format settings updated - Force: {self.force_audio_format}, Preferred: {self.preferred_audio_format}")
|
||||
|
||||
# Update Generic Mode Setting
|
||||
new_generic_mode = dialog.get_generic_mode_enabled()
|
||||
generic_mode_changed = False
|
||||
if new_generic_mode != self.generic_mode_enabled:
|
||||
self.generic_mode_enabled = new_generic_mode
|
||||
generic_mode_changed = True
|
||||
self._update_url_placeholder()
|
||||
logger.info(f"Generic mode updated - Enabled: {self.generic_mode_enabled}")
|
||||
|
||||
# Update Tooltip if anything changed
|
||||
if path_changed or limit_changed or format_changed or audio_format_changed:
|
||||
limit_text = _("main_ui.speed_limit_none")
|
||||
if self.speed_limit_value:
|
||||
limit_text = f"{self.speed_limit_value} {['KB/s', 'MB/s'][self.speed_limit_unit_index]}"
|
||||
self.settings_button.setToolTip(
|
||||
_(
|
||||
"main_ui.settings_tooltip",
|
||||
path=self.last_path,
|
||||
speed_limit=limit_text,
|
||||
)
|
||||
)
|
||||
if path_changed or limit_changed or format_changed or audio_format_changed or generic_mode_changed:
|
||||
self._update_settings_tooltip()
|
||||
|
||||
def start_download(self) -> None:
|
||||
if self.is_updating_ytdlp:
|
||||
@@ -669,7 +685,7 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin, AnalysisMixin):
|
||||
# --- End Path Change ---
|
||||
|
||||
# Validate URL before starting download
|
||||
is_valid, error_message = validate_video_url(url)
|
||||
is_valid, error_message = validate_video_url(url, generic_mode=self.generic_mode_enabled)
|
||||
if not is_valid:
|
||||
QMessageBox.warning(self, _("main_ui.error_title"), error_message)
|
||||
self.animate_widget_shake(self.url_input)
|
||||
|
||||
Reference in New Issue
Block a user