-
衷心感谢所有通过提出改进建议或报告错误为该项目做出贡献的人。
+
特别鸣谢所有通过反馈、建议或代码合并来完善此工具的贡献者。
@@ -606,12 +567,10 @@ YTSage/
## ⚠️ 免责声明
-该工具仅供个人使用。请尊重 YouTube 的服务条款和内容创作者的权利。
+本工具仅供个人学习与研究使用。请尊重 YouTube 服务条款及创作者版权。
---
-
-由[oop7](https://github.com/oop7)用❤️制作
-
+由 [oop7](https://github.com/oop7) 倾力协作 ❤️
diff --git a/ytsage/__init__.py b/ytsage/__init__.py
index 578e99c..f8faf4e 100644
--- a/ytsage/__init__.py
+++ b/ytsage/__init__.py
@@ -4,5 +4,5 @@ YTSage - YouTube Video Downloader
A modern, user-friendly YouTube video downloader built with PySide6.
"""
-__version__ = "5.0.3"
+__version__ = "5.0.10b"
__author__ = "oop7"
diff --git a/ytsage/core/ytsage_downloader.py b/ytsage/core/ytsage_downloader.py
index a66d9a0..7ffa0e7 100644
--- a/ytsage/core/ytsage_downloader.py
+++ b/ytsage/core/ytsage_downloader.py
@@ -74,6 +74,7 @@ class DownloadThread(QThread):
preferred_audio_format="best",
audio_normalization=False,
filename_format=None,
+ concurrent_fragments=1,
) -> None:
super().__init__()
self.url = url
@@ -103,6 +104,7 @@ class DownloadThread(QThread):
self.preferred_audio_format = preferred_audio_format
self.audio_normalization = audio_normalization
self.filename_format = filename_format
+ self.concurrent_fragments = concurrent_fragments
self.paused: bool = False
self.cancelled: bool = False
self.process: Optional[subprocess.Popen] = None
@@ -229,13 +231,40 @@ class DownloadThread(QThread):
def _build_yt_dlp_command(self) -> List[str]:
"""Build the yt-dlp command line with all options for direct execution."""
- # Use the new yt-dlp path function from ytsage_yt_dlp module
yt_dlp_path: str = get_yt_dlp_path()
+ # Build the command line array
cmd: List[str] = [yt_dlp_path]
logger.debug(f"Using yt-dlp from: {yt_dlp_path}")
+ # Add concurrent fragments setting
+ if self.concurrent_fragments:
+ cmd.extend(["-N", str(self.concurrent_fragments)])
+ logger.debug(f"Using {self.concurrent_fragments} concurrent connections")
+
# Format selection strategy - use format ID if provided or fallback to resolution
- if self.format_id:
+ if self.is_playlist:
+ # For playlists, specific format_id from the first video often fails for subsequent videos.
+ # Instead, we rely on dynamic fallback/resolution limits.
+ if self.is_audio_only:
+ # For audio-only playlist, let yt-dlp pick best audio.
+ cmd.extend(["-f", "bestaudio/best"])
+ logger.debug(f"Playlist mode: using dynamic best audio fallback instead of format_id")
+ else:
+ # If a specific resolution is given, limit to it. Otherwise, select the overall best.
+ # The resolution might be e.g. "1920x1080" or "1080". We want the height.
+ try:
+ if self.resolution and self.resolution != "default":
+ res_str = str(self.resolution)
+ h = min(map(int, res_str.split('x'))) if 'x' in res_str else int(res_str)
+ cmd.extend(["-S", f"res:{h}"])
+ logger.debug(f"Playlist mode: using resolution limiter -S res:{h}")
+ else:
+ cmd.extend(["-f", "bestvideo+bestaudio/best"])
+ logger.debug("Playlist mode: using dynamic best quality overall")
+ except ValueError:
+ cmd.extend(["-f", "bestvideo+bestaudio/best"])
+ logger.debug("Playlist mode: invalid resolution string, using dynamic best quality overall")
+ elif self.format_id:
clean_format_id: str = self.format_id.split("-drc")[0] if "-drc" in self.format_id else self.format_id
# If the selected format is audio-only, pass it directly.
@@ -295,12 +324,15 @@ class DownloadThread(QThread):
base_path: str = self.path.as_posix()
# Determine the filename part of the template
- filename_part = self.filename_format if self.filename_format else "%(title)s_%(resolution)s.%(ext)s"
+ filename_part = self.filename_format if self.filename_format else "%(title)s_%(resolution)s_[%(id)s].%(ext)s"
if self.is_playlist:
# Create output template with playlist subfolder
output_template: str = f"{base_path}/%(playlist_title)s/{filename_part}"
else:
+ # For single files, automatically ignore/remove playlist-specific preamble (like "%(playlist_index)s - ")
+ import re
+ filename_part = re.sub(r'%\(playlist_index[^)]*\)[a-zA-Z0-9]*\s*(?:[-_]\s*)?', '', filename_part)
output_template: str = f"{base_path}/{filename_part}"
cmd.extend(["-o", str(output_template)])
@@ -320,17 +352,21 @@ class DownloadThread(QThread):
# Get language codes from subtitle selections
lang_codes: List[str] = []
+ has_auto_generated = False
for sub_selection in self.subtitle_langs:
try:
# Extract just the language code (e.g., 'en' from 'en - Manual')
lang_code = sub_selection.split(" - ")[0]
lang_codes.append(lang_code)
+ if "Auto-generated" in sub_selection:
+ has_auto_generated = True
except Exception as e:
logger.exception(f"Could not parse subtitle selection '{sub_selection}': {e}")
if lang_codes:
cmd.extend(["--sub-langs", ",".join(lang_codes)])
- cmd.append("--write-auto-subs") # Include auto-generated subtitles
+ if has_auto_generated:
+ cmd.append("--write-auto-subs") # Include auto-generated subtitles
# Only embed subtitles if merge is enabled
if self.merge_subs:
@@ -377,6 +413,9 @@ class DownloadThread(QThread):
logger.debug(f"Added download section: {self.download_section}, Force keyframes: {self.force_keyframes}")
# Add the URL as the final argument
+ if self.is_playlist:
+ cmd.append("--ignore-errors")
+ cmd.append("--no-abort-on-error")
cmd.append(self.url)
return cmd
@@ -447,6 +486,7 @@ class DownloadThread(QThread):
time.sleep(2)
self.cleanup_partial_files()
self.status_signal.emit(_("download.cancelled"))
+ self.finished_signal.emit()
return
# Wait if paused
@@ -467,7 +507,7 @@ class DownloadThread(QThread):
)
return
- if return_code == 0:
+ if return_code == 0 or (self.is_playlist and return_code != 0 and self.current_filename is not None):
self.progress_signal.emit(100)
# Robust file finding: Always search for the most recent file
@@ -516,7 +556,10 @@ class DownloadThread(QThread):
logger.error(f"Error finding final file: {e}", exc_info=True)
# Set completion status
- self.status_signal.emit(_("download.completed"))
+ if return_code != 0:
+ self.status_signal.emit(_("download.completed") + " (with some errors)")
+ else:
+ self.status_signal.emit(_("download.completed"))
# Clean up subtitle files if they were merged, with a small delay
# to ensure the embedding process has completed
@@ -532,6 +575,7 @@ class DownloadThread(QThread):
# Check if it was cancelled
if self.cancelled:
self.status_signal.emit(_("download.cancelled"))
+ self.finished_signal.emit()
else:
# Provide informative error message based on captured output
if self.error_lines:
diff --git a/ytsage/core/ytsage_utils.py b/ytsage/core/ytsage_utils.py
index bea2cd6..86cffea 100644
--- a/ytsage/core/ytsage_utils.py
+++ b/ytsage/core/ytsage_utils.py
@@ -94,8 +94,8 @@ def update_version_cache(tool_name: str, version_info: str, path: Optional[str],
current_mtime: float = get_file_mtime(path)
_version_cache[tool_name] = {
- "version": version_info,
- "path": path,
+ "version": str(version_info) if version_info else "",
+ "path": str(path) if path else None,
"last_check": current_time,
"path_mtime": current_mtime,
}
diff --git a/ytsage/gui/ytsage_gui_analysis.py b/ytsage/gui/ytsage_gui_analysis.py
index e05ee70..5bdd095 100644
--- a/ytsage/gui/ytsage_gui_analysis.py
+++ b/ytsage/gui/ytsage_gui_analysis.py
@@ -131,8 +131,13 @@ class AnalysisThread(QThread):
return
if result.returncode != 0:
- logger.error(f"yt-dlp failed: {result.stderr}")
- self.analysis_error.emit(_("errors.ytdlp_failed", error=result.stderr))
+ if "Private video" in result.stderr or "Sign in" in result.stderr:
+ logger.error(f"yt-dlp failed (private video): {result.stderr}")
+ self.analysis_error.emit(_("errors.private_video"))
+ else:
+ logger.error(f"yt-dlp failed: {result.stderr}")
+ self.analysis_error.emit(_("errors.ytdlp_failed", error=result.stderr))
+
self.playlist_info_visible.emit(False)
self.playlist_select_btn_visible.emit(False)
return
@@ -393,6 +398,29 @@ class AnalysisMixin:
self.available_automatic_subtitles = result_data["available_automatic_subtitles"]
self.selected_playlist_items = None
self.selected_subtitles = []
+
+ from ..utils.ytsage_config_manager import ConfigManager
+ default_sub = ConfigManager.get("default_subtitle_language")
+ if default_sub:
+ if isinstance(default_sub, str):
+ default_sub_list = [s.strip() for s in default_sub.split(",")]
+ else:
+ default_sub_list = []
+
+ for lang in default_sub_list:
+ if lang in self.available_subtitles:
+ self.selected_subtitles.append(f"{lang} - Manual")
+ elif lang in self.available_automatic_subtitles:
+ self.selected_subtitles.append(f"{lang} - Auto-generated")
+
+ count = len(self.selected_subtitles)
+ try:
+ self.signals.selected_subs_label_text.emit(_("subtitle_selection.count_selected", count=count))
+ self.subtitle_select_btn.setProperty("subtitlesSelected", count > 0)
+ self.subtitle_select_btn.style().unpolish(self.subtitle_select_btn)
+ self.subtitle_select_btn.style().polish(self.subtitle_select_btn)
+ except Exception:
+ pass
# Update UI components (safe - we're in main thread)
self.update_video_info(self.video_info)
@@ -407,7 +435,11 @@ class AnalysisMixin:
self.download_thumbnail_file(self.video_url, self.last_path)
# Update subtitle UI
- self.signals.selected_subs_label_text.emit(_("main_ui.zero_selected"))
+ count = len(self.selected_subtitles)
+ if count > 0:
+ self.signals.selected_subs_label_text.emit(_("subtitle_selection.count_selected", count=count))
+ else:
+ self.signals.selected_subs_label_text.emit(_("main_ui.zero_selected"))
# Update format table
self.video_button.setChecked(True)
diff --git a/ytsage/gui/ytsage_gui_dialogs/ytsage_dialogs_custom.py b/ytsage/gui/ytsage_gui_dialogs/ytsage_dialogs_custom.py
index 3c19863..8b1cf55 100644
--- a/ytsage/gui/ytsage_gui_dialogs/ytsage_dialogs_custom.py
+++ b/ytsage/gui/ytsage_gui_dialogs/ytsage_dialogs_custom.py
@@ -198,6 +198,37 @@ class CustomOptionsDialog(QDialog):
# Initially show browser group (recommended default) and hide file group
self.cookie_file_group.setVisible(False)
+
+ # Remember Cookie Settings Checkbox
+ self.remember_cb = QCheckBox(_("cookies.remember_settings"))
+ remember_val = ConfigManager.get("cookie_remember")
+ self.remember_cb.setChecked(True if remember_val is None else remember_val) # Default to True
+ self.remember_cb.setStyleSheet(
+ """
+ QCheckBox {
+ color: #ffffff;
+ padding: 10px 5px;
+ }
+ QCheckBox::indicator {
+ width: 18px;
+ height: 18px;
+ border-radius: 9px;
+ }
+ QCheckBox::indicator:unchecked {
+ border: 2px solid #666666;
+ background: #1d1e22;
+ border-radius: 9px;
+ }
+ QCheckBox::indicator:checked {
+ border: 2px solid #c90000;
+ background: #c90000;
+ border-radius: 9px;
+ }
+ """
+ )
+ # Connect state change directly to config save to make it work immediately without Apply button dependency if needed
+ self.remember_cb.toggled.connect(lambda checked: ConfigManager.set("cookie_remember", checked))
+ cookies_layout.addWidget(self.remember_cb)
self.cookie_browser_group.setVisible(True)
# Apply button and status indicator
@@ -725,6 +756,7 @@ class CustomOptionsDialog(QDialog):
self._parent.browser_cookies_option = None
# Save settings to ConfigManager for persistence
+ ConfigManager.set("cookie_remember", self.remember_cb.isChecked())
if self.cookie_file_radio.isChecked():
ConfigManager.set("cookie_source", "file")
ConfigManager.set("cookie_file_path", str(cookie_path) if cookie_path else None)
diff --git a/ytsage/gui/ytsage_gui_dialogs/ytsage_dialogs_history.py b/ytsage/gui/ytsage_gui_dialogs/ytsage_dialogs_history.py
index b3987f2..95556b3 100644
--- a/ytsage/gui/ytsage_gui_dialogs/ytsage_dialogs_history.py
+++ b/ytsage/gui/ytsage_gui_dialogs/ytsage_dialogs_history.py
@@ -279,7 +279,7 @@ class HistoryDelegate(QStyledItemDelegate):
painter.drawText(badge_rect, Qt.AlignmentFlag.AlignCenter, badge_text)
# File Size
- file_size = entry.get("file_size", 0)
+ file_size = entry.get("file_size") or 0
if file_size > 0:
size_str = self.format_file_size(file_size)
painter.setPen(QColor("#aaaaaa"))
diff --git a/ytsage/gui/ytsage_gui_dialogs/ytsage_dialogs_settings.py b/ytsage/gui/ytsage_gui_dialogs/ytsage_dialogs_settings.py
index 0c3f9aa..59d6e6b 100644
--- a/ytsage/gui/ytsage_gui_dialogs/ytsage_dialogs_settings.py
+++ b/ytsage/gui/ytsage_gui_dialogs/ytsage_dialogs_settings.py
@@ -229,6 +229,28 @@ class DownloadSettingsDialog(QDialog):
speed_group_box.setLayout(speed_layout)
general_layout.addWidget(speed_group_box)
+ # --- Connections Section ---
+ connections_group_box = QGroupBox(_("settings.concurrent_fragments", default="Concurrent Connections"))
+ connections_layout = QVBoxLayout()
+
+ self.connections_enabled = ConfigManager.get("concurrent_fragments") or 1
+
+ connections_spin_layout = QHBoxLayout()
+ self.connections_input = QComboBox()
+ self.connections_input.addItems([str(i) for i in range(1, 21)])
+ self.connections_input.setCurrentText(str(self.connections_enabled))
+ connections_spin_layout.addWidget(self.connections_input)
+ connections_spin_layout.addStretch()
+ connections_layout.addLayout(connections_spin_layout)
+
+ connections_help_label = QLabel(_("settings.concurrent_fragments_help", default="Number of connections per download. Higher values bypass throttling but may cause temporary blocks if set too high. Default: 1."))
+ connections_help_label.setWordWrap(True)
+ connections_help_label.setStyleSheet("color: #cccccc; margin: 5px; font-size: 11px;")
+ connections_layout.addWidget(connections_help_label)
+
+ connections_group_box.setLayout(connections_layout)
+ general_layout.addWidget(connections_group_box)
+
# --- Generic Mode Section ---
generic_mode_group_box = QGroupBox(_("settings.generic_mode"))
generic_mode_layout = QVBoxLayout()
@@ -354,6 +376,39 @@ class DownloadSettingsDialog(QDialog):
audio_format_group_box.setLayout(audio_format_layout)
format_layout.addWidget(audio_format_group_box)
+
+ # --- Default Quality and Subtitles Section ---
+ defaults_group_box = QGroupBox(_("settings.defaults_settings", default="Default Selection Settings"))
+ defaults_layout = QVBoxLayout()
+
+ # Default Video Quality
+ vid_qual_layout = QHBoxLayout()
+ vid_qual_label = QLabel(_("settings.default_video_quality", default="Default Video Resolution (Height):"))
+ vid_qual_label.setStyleSheet("color: #ffffff; margin-top: 5px;")
+ self.default_vid_qual_input = QLineEdit(str(ConfigManager.get("default_video_quality") or ""))
+ self.default_vid_qual_input.setPlaceholderText("e.g. 1080 or 720")
+ vid_qual_layout.addWidget(vid_qual_label)
+ vid_qual_layout.addWidget(self.default_vid_qual_input)
+ defaults_layout.addLayout(vid_qual_layout)
+
+ # Default Subtitles
+ sub_layout = QHBoxLayout()
+ sub_label = QLabel(_("settings.default_subtitle_language", default="Default Subtitle Language(s):"))
+ sub_label.setStyleSheet("color: #ffffff; margin-top: 5px;")
+ self.default_sub_input = QLineEdit(ConfigManager.get("default_subtitle_language") or "")
+ self.default_sub_input.setPlaceholderText("e.g. en, es")
+ sub_layout.addWidget(sub_label)
+ sub_layout.addWidget(self.default_sub_input)
+ defaults_layout.addLayout(sub_layout)
+
+ defaults_help = QLabel(_("settings.defaults_help", default="Set your preferred video height and subtitle languages (comma-separated). They will be auto-selected if available."))
+ defaults_help.setWordWrap(True)
+ defaults_help.setStyleSheet("color: #cccccc; margin: 5px; font-size: 11px;")
+ defaults_layout.addWidget(defaults_help)
+
+ defaults_group_box.setLayout(defaults_layout)
+ format_layout.addWidget(defaults_group_box)
+
format_layout.addStretch()
# === File Tab ===
@@ -365,18 +420,18 @@ class DownloadSettingsDialog(QDialog):
filename_layout = QVBoxLayout()
# Load current filename format from ConfigManager
- self.filename_format_value = ConfigManager.get("filename_format") or "%(title)s_%(resolution)s.%(ext)s"
+ self.filename_format_value = ConfigManager.get("filename_format") or "%(title)s_%(resolution)s_[%(id)s].%(ext)s"
# Input and Reset Button Layout
filename_input_layout = QHBoxLayout()
self.filename_format_input = QLineEdit(self.filename_format_value)
- self.filename_format_input.setPlaceholderText("%(title)s_%(resolution)s.%(ext)s")
+ self.filename_format_input.setPlaceholderText("%(title)s_%(resolution)s_[%(id)s].%(ext)s")
filename_input_layout.addWidget(self.filename_format_input)
self.reset_format_button = QPushButton(_("buttons.reset"))
self.reset_format_button.setFixedWidth(70)
- self.reset_format_button.clicked.connect(lambda: self.filename_format_input.setText("%(title)s_%(resolution)s.%(ext)s"))
+ self.reset_format_button.clicked.connect(lambda: self.filename_format_input.setText("%(title)s_%(resolution)s_[%(id)s].%(ext)s"))
filename_input_layout.addWidget(self.reset_format_button)
filename_layout.addLayout(filename_input_layout)
@@ -474,6 +529,13 @@ class DownloadSettingsDialog(QDialog):
"""Returns whether generic mode is enabled."""
return self.generic_mode_checkbox.isChecked()
+ def get_concurrent_fragments(self) -> int:
+ """Returns the number of concurrent fragments."""
+ try:
+ return int(self.connections_input.currentText())
+ except ValueError:
+ return 1
+
def get_preferred_format(self) -> str:
"""Returns the selected preferred format (lowercase)."""
format_map = {0: "mp4", 1: "webm", 2: "mkv"}
@@ -535,6 +597,7 @@ class DownloadSettingsDialog(QDialog):
"""Override accept to save format settings."""
try:
ConfigManager.set("generic_mode", self.get_generic_mode_enabled())
+ ConfigManager.set("concurrent_fragments", self.get_concurrent_fragments())
# Save output format settings
force_format = self.get_force_format_enabled()
@@ -550,6 +613,12 @@ class DownloadSettingsDialog(QDialog):
ConfigManager.set("preferred_audio_format", preferred_audio_format)
ConfigManager.set("audio_normalization", audio_normalization)
+ # Save defaults
+ default_vid = self.default_vid_qual_input.text().strip()
+ ConfigManager.set("default_video_quality", default_vid if default_vid else None)
+ default_sub = self.default_sub_input.text().strip()
+ ConfigManager.set("default_subtitle_language", default_sub if default_sub else None)
+
# Save filename format
filename_format = self.get_filename_format()
if filename_format:
diff --git a/ytsage/gui/ytsage_gui_format_table.py b/ytsage/gui/ytsage_gui_format_table.py
index 524a350..5b2dea0 100644
--- a/ytsage/gui/ytsage_gui_format_table.py
+++ b/ytsage/gui/ytsage_gui_format_table.py
@@ -238,6 +238,30 @@ class FormatTableMixin:
self._populate_format_table(all_filtered)
self._table_built = True
+ from ..utils.ytsage_config_manager import ConfigManager
+ default_video_quality = ConfigManager.get("default_video_quality")
+
+ # Auto-select the default or best format
+ if self.format_checkboxes:
+ found_default = False
+ if default_video_quality:
+ # Try to find a video format with the requested height (e.g. '1080')
+ target_height = str(default_video_quality)
+ # the formats are sorted by quality, so the first match is typically the best one for that height
+ for idx, (f, fmt_type) in enumerate(all_filtered):
+ if fmt_type == "video":
+ res = str(f.get("resolution", ""))
+ if res.endswith(f"x{target_height}") or target_height in res:
+ self.format_checkboxes[idx].setChecked(True)
+ self.handle_checkbox_click(self.format_checkboxes[idx])
+ found_default = True
+ break
+
+ if not found_default:
+ # Fallback to the first available format (which is the best quality since it's sorted)
+ self.format_checkboxes[0].setChecked(True)
+ self.handle_checkbox_click(self.format_checkboxes[0])
+
# Apply initial visibility based on current button states
self.filter_formats()
@@ -307,6 +331,9 @@ class FormatTableMixin:
# Quality label with color coding
quality_label = self.get_quality_label(f)
+ if is_playlist_mode and f.get("vcodec") != "none":
+ quality_label = f"≤ {quality_label}"
+
quality_item = QTableWidgetItem(quality_label)
# Set color based on quality (check multiple language terms)
quality_lower = quality_label.lower()
@@ -324,6 +351,9 @@ class FormatTableMixin:
resolution = f.get("resolution") or "N/A"
if not isinstance(resolution, str):
resolution = str(resolution)
+
+ if is_playlist_mode and f.get("vcodec") != "none" and resolution != "N/A":
+ resolution = f"≤ {resolution}"
if is_playlist_mode:
# Column 2 for playlist mode: Resolution
@@ -490,7 +520,10 @@ class FormatTableMixin:
resolution = format_info.get("resolution", "")
if resolution:
try:
- height = int(resolution.split("x")[1])
+ parts = resolution.split("x")
+ if len(parts) == 2:
+ # Use the smaller dimension to correctly classify vertical videos
+ height = min(int(parts[0]), int(parts[1]))
except:
pass
diff --git a/ytsage/gui/ytsage_gui_main.py b/ytsage/gui/ytsage_gui_main.py
index fd498f3..7743700 100644
--- a/ytsage/gui/ytsage_gui_main.py
+++ b/ytsage/gui/ytsage_gui_main.py
@@ -15,6 +15,7 @@ from PySide6.QtWidgets import (
QButtonGroup,
QCheckBox,
QDialog,
+ QFileDialog,
QHBoxLayout,
QLabel,
QLineEdit,
@@ -329,13 +330,31 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin, AnalysisMixin):
logger.exception(f"Error playing notification sound: {e}")
def _initialize_cookie_settings_from_config(self) -> None:
- """Initialize cookie settings - cookies are NOT auto-activated on startup.
- User must explicitly click Apply in the dialog each session."""
- # Cookies always start inactive on app launch
- # User must click Apply in Custom Options dialog to activate them
+ """Initialize cookie settings and restore from last session if active."""
self.cookie_file_path = None
self.browser_cookies_option = None
- logger.debug("Cookie settings initialized - no cookies active (user must apply manually)")
+
+ # Check if the user wants to remember cookies across sessions
+ remember_val = ConfigManager.get("cookie_remember")
+ should_remember = True if remember_val is None else remember_val
+
+ if ConfigManager.get("cookie_active") and should_remember:
+ source = ConfigManager.get("cookie_source")
+ if source == "file":
+ saved_path = ConfigManager.get("cookie_file_path")
+ if saved_path and Path(saved_path).exists():
+ self.cookie_file_path = Path(saved_path)
+ logger.info(f"Restored cookie file from previous session: {self.cookie_file_path}")
+ elif source == "browser":
+ browser = ConfigManager.get("cookie_browser")
+ profile = ConfigManager.get("cookie_browser_profile")
+ if browser:
+ self.browser_cookies_option = f"{browser}:{profile}" if profile else browser
+ logger.info(f"Restored browser cookies from previous session: {self.browser_cookies_option}")
+ else:
+ # Revert activation back if the user opted NOT to remember them
+ ConfigManager.set("cookie_active", False)
+ logger.debug("Cookie settings initialized - no cookies active")
def init_ui(self) -> None:
self.setWindowTitle(f"{_('app.title')} {_('app.version', version=self.version)}")
@@ -397,12 +416,24 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin, AnalysisMixin):
self.playlist_info_label = self.setup_playlist_info_section()
layout.addWidget(self.playlist_info_label)
+ # Playlist buttons layout
+ playlist_btns_layout = QHBoxLayout()
+
# Add playlist selection BUTTON (initially hidden) - REPLACED QLineEdit
self.playlist_select_btn = QPushButton(_("buttons.select_videos"))
self.playlist_select_btn.clicked.connect(self.open_playlist_selection_dialog)
self.playlist_select_btn.setVisible(False)
self.playlist_select_btn.setStyleSheet(StyleSheet.PLAYLIST_BUTTON)
- layout.addWidget(self.playlist_select_btn)
+ playlist_btns_layout.addWidget(self.playlist_select_btn)
+
+ # Save playlist as button
+ self.save_playlist_btn = QPushButton(_("buttons.save_playlist", default="Save Playlist As"))
+ self.save_playlist_btn.clicked.connect(self.save_playlist_to_file)
+ self.save_playlist_btn.setVisible(False)
+ self.save_playlist_btn.setStyleSheet(StyleSheet.PLAYLIST_BUTTON)
+ playlist_btns_layout.addWidget(self.save_playlist_btn)
+
+ layout.addLayout(playlist_btns_layout)
# --- End Playlist Info Section ---
# Format controls section with minimal spacing
@@ -568,6 +599,7 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin, AnalysisMixin):
self.signals.playlist_info_label_text.connect(self.playlist_info_label.setText)
self.signals.selected_subs_label_text.connect(self.selected_subs_label.setText)
self.signals.playlist_select_btn_visible.connect(lambda v: self.set_widget_visible_animated(self.playlist_select_btn, v))
+ self.signals.playlist_select_btn_visible.connect(lambda v: self.set_widget_visible_animated(self.save_playlist_btn, v))
self.signals.playlist_select_btn_text.connect(self.playlist_select_btn.setText)
# Disable analysis-dependent controls until video is analyzed
@@ -713,12 +745,19 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin, AnalysisMixin):
# Get resolution for filename
resolution = "default"
- for checkbox in self.format_checkboxes:
- if checkbox.isChecked():
- parts = checkbox.text().split("•")
- if len(parts) >= 1:
- resolution = parts[0].strip().lower()
- break
+ for row in range(self.format_table.rowCount()):
+ cell_widget = self.format_table.cellWidget(row, 0)
+ if cell_widget:
+ cb = cell_widget.layout().itemAt(0).widget()
+ if isinstance(cb, QCheckBox) and cb.isChecked():
+ if self.is_playlist:
+ res_item = self.format_table.item(row, 2)
+ else:
+ res_item = self.format_table.item(row, 3)
+
+ if res_item and res_item.text() != "N/A":
+ resolution = res_item.text().replace("≤ ", "").strip()
+ break
# Get subtitle selection if available - Now get the list
selected_subs = self.selected_subtitles if hasattr(self, "selected_subtitles") else []
@@ -755,6 +794,7 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin, AnalysisMixin):
# Get filename format from config
filename_format = ConfigManager.get("filename_format")
+ concurrent_fragments = ConfigManager.get("concurrent_fragments") or 1
# Create download thread with resolution in output template
self.download_thread = DownloadThread(
@@ -785,6 +825,7 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin, AnalysisMixin):
preferred_audio_format=self.preferred_audio_format, # Pass preferred audio format
audio_normalization=self.audio_normalization, # Pass audio normalization setting
filename_format=filename_format, # Pass the filename format
+ concurrent_fragments=concurrent_fragments, # Pass the concurrent fragments
)
# Connect signals
@@ -1312,6 +1353,79 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin, AnalysisMixin):
button_text = f"Select Videos... ({display_text})"
self.playlist_select_btn.setText(button_text) # Direct call is fine here
+ def save_playlist_to_file(self) -> None:
+ """Save current playlist URLs/info to a file."""
+ if not getattr(self, "playlist_entries", None):
+ QMessageBox.warning(self, _("playlist.save_error_title", default="Save Error"), _("playlist.no_videos_to_save", default="No playlist entries gathered!"))
+ return
+
+ default_dir = str(Path(self.last_path) / "playlist.txt")
+ file_path, selected_filter = QFileDialog.getSaveFileName(
+ self,
+ _("playlist.save_as", default="Save Playlist As"),
+ default_dir,
+ "Text files (*.txt);;M3U playlists (*.m3u);;CSV files (*.csv);;JSON files (*.json)"
+ )
+
+ if not file_path:
+ return
+
+ try:
+ with open(file_path, 'w', encoding='utf-8') as f:
+ if "Text files" in selected_filter:
+ for index, entry in enumerate(self.playlist_entries):
+ duration = entry.get("duration")
+ duration_str = ""
+ if duration:
+ try:
+ m, s = divmod(int(duration), 60)
+ h, m = divmod(m, 60)
+ if h > 0:
+ duration_str = f" [{h}:{m:02d}:{s:02d}]"
+ else:
+ duration_str = f" [{m:02d}:{s:02d}]"
+ except (ValueError, TypeError):
+ pass
+
+ title = entry.get('title', f'Video {index + 1}')
+ # Formatting output to include index and duration
+ f.write(f"{index + 1}. {title}{duration_str} - {entry.get('url', '')}\n")
+ elif "M3U" in selected_filter:
+ f.write("#EXTM3U\n")
+ for entry in self.playlist_entries:
+ duration = int(entry.get('duration', 0)) if entry.get('duration') else 0
+ title = entry.get('title', 'Unknown Title')
+ f.write(f"#EXTINF:{duration},{title}\n{entry.get('url', '')}\n")
+ elif "CSV" in selected_filter:
+ import csv
+ writer = csv.writer(f, lineterminator='\n')
+ # Adding Playlist Index to CSV and formatting Title with index and duration
+ writer.writerow(['Playlist Index', 'Title', 'URL', 'Duration', 'Uploader'])
+ for index, entry in enumerate(self.playlist_entries):
+ duration = entry.get("duration")
+ duration_str = ""
+ if duration:
+ try:
+ m, s = divmod(int(duration), 60)
+ h, m = divmod(m, 60)
+ if h > 0:
+ duration_str = f" [{h}:{m:02d}:{s:02d}]"
+ else:
+ duration_str = f" [{m:02d}:{s:02d}]"
+ except (ValueError, TypeError):
+ pass
+
+ title = entry.get('title', f'Video {index + 1}')
+ formatted_title = f"{index + 1}. {title}{duration_str}"
+ writer.writerow([index + 1, formatted_title, entry.get('url', ''), entry.get('duration', ''), entry.get('uploader', '')])
+ elif "JSON" in selected_filter:
+ import json
+ json.dump(self.playlist_entries, f, indent=4, ensure_ascii=False)
+ QMessageBox.information(self, _("playlist.save_success_title", default="Success"), _("playlist.saved_successfully", default="Playlist saved successfully."))
+ except Exception as e:
+ logger.exception(f"Error saving playlist: {e}")
+ QMessageBox.critical(self, _("playlist.save_error_title", default="Error"), _("playlist.save_error_msg", default="Failed to save playlist."))
+
# --- New Slot for Updating Playlist Button Text ---
# moved to SignalManager as Signal and added to init_ui() method.
diff --git a/ytsage/languages/ar.json b/ytsage/languages/ar.json
index 8b77b00..ca0d59b 100644
--- a/ytsage/languages/ar.json
+++ b/ytsage/languages/ar.json
@@ -83,7 +83,8 @@
"select_all": "تحديد الكل",
"deselect_all": "إلغاء تحديد الكل",
"open_folder": "فتح موقع المجلد",
- "history": "السجل"
+ "history": "السجل",
+ "save_playlist": "حفظ قائمة التشغيل باسم"
},
"dialogs": {
"custom_options": "خيارات مخصصة",
@@ -95,7 +96,8 @@
"filter_languages_placeholder": "تصفية اللغات (مثال: ar، en)...",
"no_subtitles_available": "لا توجد ترجمات متاحة",
"matching": "مطابقة",
- "ytdlp_log_title": "سجل yt-dlp"
+ "ytdlp_log_title": "سجل yt-dlp",
+ "filter_playlist_placeholder": "تصفية الفيديوهات..."
},
"tabs": {
"cookies": "تسجيل الدخول بالكوكيز",
@@ -129,7 +131,8 @@
"cleared_message": "تم مسح إعدادات الكوكيز",
"active_browser": "✓ نشط: كوكيز المتصفح ({browser})",
"active_file": "✓ نشط: ملف كوكيز ({file})",
- "none_active": "○ لا توجد كوكيز نشطة"
+ "none_active": "○ لا توجد كوكيز نشطة",
+ "remember_settings": "تذكر إعدادات ملفات تعريف الارتباط عند بدء التشغيل التالي"
},
"custom_command": {
"help_text": "أدخل أمر yt-dlp مخصص أدناه. سيتم إضافة الرابط الحالي تلقائياً.
للحصول على قائمة كاملة بالخيارات وأمثلة الاستخدام
انقر هنا لمشاهدة الوثائق الرسمية لـ yt-dlp.
ملاحظة: يتم التعامل مع مسار التنزيل ونموذج اسم الملف تلقائياً.",
@@ -342,7 +345,13 @@
"filename_format_help": "المتغيرات المتاحة: %(title)s, %(uploader)s, %(upload_date)s, %(resolution)s, %(id)s, %(ext)s. يتم دعم صيغة قالب إخراج yt-dlp القياسية.",
"tab_general": "عام",
"tab_format": "تنسيق",
- "tab_file": "ملف"
+ "tab_file": "ملف",
+ "concurrent_fragments": "اتصالات متزامنة",
+ "concurrent_fragments_help": "عدد الاتصالات لكل عملية تحميل. القيم الأعلى تتجاوز الاختناق ولكنها قد تسبب حظراً مؤقتاً إذا كانت عالية جداً. الافتراضي: 1.",
+ "defaults_settings": "إعدادات الاختيار الافتراضية",
+ "default_video_quality": "دقة الفيديو الافتراضية (الطول):",
+ "default_subtitle_language": "لغات الترجمة الافتراضية:",
+ "defaults_help": "اضبط ارتفاع الفيديو المفضل ولغات الترجمة (مفصولة بفاصلة). سيتم اختيارها تلقائيًا إذا كانت متوفرة."
},
"main_ui": {
"url_placeholder": "أدخل رابط فيديو يوتيوب أو قائمة تشغيل",
@@ -452,7 +461,8 @@
"generic_error": "خطأ: {error}",
"download_failed_return_code_conflict": "فشل التنزيل برمز الإرجاع {return_code}. قد يكون ذلك بسبب تعارض بين عدة عمليات تثبيت لـ yt-dlp. جرّب إزالة أي تثبيت لنظام التشغيل (مثل snap أو apt) ثم أعد تشغيل التطبيق.",
"download_failed_return_code": "فشل التنزيل برمز الإرجاع {return_code}",
- "direct_command_error": "خطأ في الأمر المباشر: {error}"
+ "direct_command_error": "خطأ في الأمر المباشر: {error}",
+ "private_video": "قد يكون هذا الفيديو خاصًا. يرجى استخدام ملفات تعريف الارتباط من الخيارات المخصصة."
},
"update_dialog": {
"title": "تحديث متاح",
@@ -467,7 +477,13 @@
"unknown": "قائمة تشغيل غير معروفة",
"total_videos": "إجمالي الفيديوهات: {count}",
"display_format": "قائمة التشغيل: {title} | {count} فيديوهات",
- "select_videos_title": "اختر مقاطع الفيديو من قائمة التشغيل"
+ "select_videos_title": "اختر مقاطع الفيديو من قائمة التشغيل",
+ "save_as": "حفظ قائمة التشغيل باسم",
+ "save_success_title": "تم بنجاح",
+ "saved_successfully": "تم حفظ قائمة التشغيل بنجاح.",
+ "save_error_title": "خطأ في الحفظ",
+ "no_videos_to_save": "لم يتم العثور على أي مقاطع فيديو لحفظها!",
+ "save_error_msg": "فشل في حفظ قائمة التشغيل."
},
"subtitle_selection": {
"count_selected": "{count} محدد"
@@ -624,4 +640,4 @@
"update_failed": "❌ فشل التحديث: {error}",
"check_failed": "فشل التحقق من التحديثات. يرجى التحقق من اتصالك بالإنترنت."
}
-}
+}
\ No newline at end of file
diff --git a/ytsage/languages/de.json b/ytsage/languages/de.json
index de2b30c..a643adb 100644
--- a/ytsage/languages/de.json
+++ b/ytsage/languages/de.json
@@ -83,7 +83,8 @@
"select_all": "Alle auswählen",
"deselect_all": "Alle abwählen",
"open_folder": "Ordnerspeicherort öffnen",
- "history": "Verlauf"
+ "history": "Verlauf",
+ "save_playlist": "Playlist speichern unter"
},
"dialogs": {
"custom_options": "Benutzerdefinierte Optionen",
@@ -95,7 +96,8 @@
"filter_languages_placeholder": "Sprachen filtern (z.B. en, de)...",
"no_subtitles_available": "Keine Untertitel verfügbar",
"matching": "passend",
- "ytdlp_log_title": "yt-dlp-Protokoll"
+ "ytdlp_log_title": "yt-dlp-Protokoll",
+ "filter_playlist_placeholder": "Videos filtern..."
},
"tabs": {
"cookies": "Mit Cookies anmelden",
@@ -129,7 +131,8 @@
"cleared_message": "Cookie-Einstellungen wurden gelöscht",
"active_browser": "✓ Aktiv: Browser-Cookies ({browser})",
"active_file": "✓ Aktiv: Cookie-Datei ({file})",
- "none_active": "○ Keine Cookies aktiv"
+ "none_active": "○ Keine Cookies aktiv",
+ "remember_settings": "Cookie-Einstellungen beim nächsten Start merken"
},
"custom_command": {
"help_text": "Geben Sie Ihren benutzerdefinierten yt-dlp-Befehl unten ein. Die aktuelle URL wird automatisch angehängt.
Für die vollständige Liste der Optionen und Verwendungsbeispiele
klicken Sie hier, um die offizielle yt-dlp-Dokumentation anzuzeigen.
Hinweis: Download-Pfad und Dateinamen-Vorlage werden automatisch behandelt.",
@@ -342,7 +345,13 @@
"filename_format_help": "Verfügbare Variablen: %(title)s, %(uploader)s, %(upload_date)s, %(resolution)s, %(id)s, %(ext)s. Die Standard-yt-dlp-Ausgabevorlagensyntax wird unterstützt.",
"tab_general": "Allgemein",
"tab_format": "Format",
- "tab_file": "Datei"
+ "tab_file": "Datei",
+ "concurrent_fragments": "Gleichzeitige Verbindungen",
+ "concurrent_fragments_help": "Anzahl der Verbindungen pro Download. Höhere Werte umgehen Drosselungen, können aber bei zu hohen Werten zu temporären Sperren führen. Standard: 1.",
+ "defaults_settings": "Standard-Auswahleinstellungen",
+ "default_video_quality": "Standard-Videoauflösung (Höhe):",
+ "default_subtitle_language": "Standard- Untertitelsprache(n):",
+ "defaults_help": "Legen Sie Ihre bevorzugte Videohöhe und Untertitelsprachen fest (kommagetrennt). Diese werden automatisch ausgewählt, falls verfügbar."
},
"main_ui": {
"url_placeholder": "YouTube-Video- oder Playlist-URL eingeben",
@@ -452,7 +461,8 @@
"generic_error": "Fehler: {error}",
"download_failed_return_code_conflict": "Download fehlgeschlagen mit Rückgabecode {return_code}. Dies kann an einem Konflikt mit mehreren yt-dlp-Installationen liegen. Deinstalliere ggf. eine systemweit installierte yt-dlp-Version (z. B. über snap oder apt) und starte die Anwendung neu.",
"download_failed_return_code": "Download fehlgeschlagen mit Rückgabecode {return_code}",
- "direct_command_error": "Fehler im direkten Befehl: {error}"
+ "direct_command_error": "Fehler im direkten Befehl: {error}",
+ "private_video": "Dieses Video ist möglicherweise privat. Bitte verwenden Sie Cookies aus den benutzerdefinierten Optionen."
},
"update_dialog": {
"title": "Update verfügbar",
@@ -467,7 +477,13 @@
"unknown": "Unbekannte Playlist",
"total_videos": "Gesamtanzahl Videos: {count}",
"display_format": "Playlist: {title} | {count} Videos",
- "select_videos_title": "Playlist-Videos auswählen"
+ "select_videos_title": "Playlist-Videos auswählen",
+ "save_as": "Playlist speichern unter",
+ "save_success_title": "Erfolg",
+ "saved_successfully": "Playlist erfolgreich gespeichert.",
+ "save_error_title": "Fehler beim Speichern",
+ "no_videos_to_save": "Keine Playlist-Einträge gesammelt!",
+ "save_error_msg": "Fehler beim Speichern der Playlist."
},
"subtitle_selection": {
"count_selected": "{count} ausgewählt"
@@ -624,4 +640,4 @@
"update_failed": "❌ Update fehlgeschlagen: {error}",
"check_failed": "Suche nach Updates fehlgeschlagen. Bitte überprüfen Sie Ihre Internetverbindung."
}
-}
+}
\ No newline at end of file
diff --git a/ytsage/languages/en.json b/ytsage/languages/en.json
index 61c4f0f..b17e700 100644
--- a/ytsage/languages/en.json
+++ b/ytsage/languages/en.json
@@ -83,6 +83,7 @@
"select_all": "Select All",
"deselect_all": "Deselect All",
"open_folder": "Open folder location",
+ "save_playlist": "Save Playlist As",
"reset": "Reset"
},
"dialogs": {
@@ -130,7 +131,8 @@
"cleared_message": "Cookie settings have been cleared",
"active_browser": "✓ Active: Browser cookies ({browser})",
"active_file": "✓ Active: Cookie file ({file})",
- "none_active": "○ No cookies active"
+ "none_active": "○ No cookies active",
+ "remember_settings": "Remember cookie settings on next startup"
},
"custom_command": {
"help_text": "Enter your custom yt-dlp command below. The current URL will be appended automatically.
For the full list of options and usage examples,
click here to view the official yt-dlp documentation.
Note: Download path and filename template will be handled automatically.",
@@ -330,7 +332,13 @@
"audio_normalization": "Audio Normalization (EBU R128)",
"audio_normalization_help": "When enabled, audio tracks will be normalized. Note: This requires re-encoding, so a specific audio format (like MP3 or M4A) must be forced.",
"filename_format": "Output Filename Format",
- "filename_format_help": "Available variables: %(title)s, %(uploader)s, %(upload_date)s, %(resolution)s, %(id)s, %(ext)s. Standard yt-dlp output template syntax is supported.",
+ "filename_format_help": "Available variables: %(title)s, %(uploader)s, %(upload_date)s, %(resolution)s, %(id)s, %(ext)s, %(playlist_index)s. Standard yt-dlp output template syntax is supported.",
+ "defaults_settings": "Default Selection Settings",
+ "default_video_quality": "Default Video Resolution (Height):",
+ "default_subtitle_language": "Default Subtitle Language(s):",
+ "defaults_help": "Set your preferred video height and subtitle languages (comma-separated). They will be auto-selected if available.",
+ "concurrent_fragments": "Concurrent Connections",
+ "concurrent_fragments_help": "Number of connections per download. Higher values bypass throttling but may cause temporary blocks if set too high. Default: 1.",
"preferred_audio_format": "Preferred audio format:",
"force_audio_format_help": "When enabled, audio-only downloads will be converted to your preferred format. This only applies when downloading audio formats.",
"audio_format_best": "Best (No conversion)",
@@ -448,6 +456,7 @@
"analysis_timeout": "Error: Analysis timed out. Please try again.",
"invalid_speed_limit": "❌ Error: Invalid speed limit value set in settings.",
"ytdlp_failed": "Error: yt-dlp failed: {error}",
+ "private_video": "This video might be private. Please use cookies from the custom options.",
"parse_failed": "Error: Failed to parse yt-dlp output: {error}",
"analysis_failed": "Error: Analysis failed: {error}",
"generic_error": "Error: {error}",
@@ -468,7 +477,13 @@
"unknown": "Unknown Playlist",
"total_videos": "Total Videos: {count}",
"display_format": "Playlist: {title} | {count} videos",
- "select_videos_title": "Select Playlist Videos"
+ "select_videos_title": "Select Playlist Videos",
+ "save_as": "Save Playlist As",
+ "save_success_title": "Success",
+ "saved_successfully": "Playlist saved successfully.",
+ "save_error_title": "Save Error",
+ "no_videos_to_save": "No playlist entries gathered!",
+ "save_error_msg": "Failed to save playlist."
},
"subtitle_selection": {
"count_selected": "{count} selected"
@@ -625,4 +640,4 @@
"update_failed": "❌ Update failed: {error}",
"check_failed": "Failed to check for updates. Please check your internet connection."
}
-}
+}
\ No newline at end of file
diff --git a/ytsage/languages/es.json b/ytsage/languages/es.json
index ea8b56f..c0b9a7b 100644
--- a/ytsage/languages/es.json
+++ b/ytsage/languages/es.json
@@ -17,7 +17,9 @@
"polish": "Polaco",
"italian": "Italiano",
"arabic": "Árabe",
- "japanese": "Japonés"
+ "japanese": "Japonés",
+ "help_text": "Seleccione su idioma preferido para la interfaz.",
+ "restart_notice": "El cambio de idioma tendrá efecto después de reiniciar la aplicación."
},
"app": {
"title": "YTSage",
@@ -50,7 +52,9 @@
"select_all": "Seleccionar Todos",
"deselect_all": "Deseleccionar Todos",
"open_folder": "Abrir ubicación de carpeta",
- "history": "Historial"
+ "history": "Historial",
+ "save_playlist": "Guardar playlist como",
+ "custom_command_help": "Ayuda"
},
"dialogs": {
"custom_options": "Opciones Personalizadas",
@@ -62,7 +66,8 @@
"filter_languages_placeholder": "Filtrar idiomas (ej., en, es)...",
"no_subtitles_available": "No hay subtítulos disponibles",
"matching": "que coincidan con",
- "ytdlp_log_title": "Registro de yt-dlp"
+ "ytdlp_log_title": "Registro de yt-dlp",
+ "filter_playlist_placeholder": "Filtrar videos..."
},
"tabs": {
"cookies": "Iniciar sesión con Cookies",
@@ -96,7 +101,8 @@
"cleared_message": "La configuración de cookies se ha borrado",
"active_browser": "✓ Activas: cookies del navegador ({browser})",
"active_file": "✓ Activo: archivo de cookies ({file})",
- "none_active": "○ No hay cookies activas"
+ "none_active": "○ No hay cookies activas",
+ "remember_settings": "Recordar configuración de cookies en el próximo inicio"
},
"custom_command": {
"help_text": "Ingresa tu comando yt-dlp personalizado a continuación. La URL actual se añadirá automáticamente.
Para ver la lista completa de opciones y ejemplos de uso,
haz clic aquí para ver la documentación oficial de yt-dlp.
Nota: La ruta de descarga y la plantilla de nombre de archivo se manejarán automáticamente.",
@@ -114,7 +120,9 @@
"url_label": "📍 URL: {url}",
"args_label": "⚙️ Argumentos: {command}",
"download_path_label": "📁 Ruta de descarga: {path}",
- "separator": "=================================================="
+ "separator": "==================================================",
+ "command_placeholder": "Enter custom yt-dlp command...",
+ "command_help": "Available placeholders:\n{url} - Video URL\n{output} - Output directory\n\nExample: --write-info-json --write-thumbnail"
},
"proxy": {
"help_text": "Configurar ajustes de proxy para conexiones de red y geo-verificación.\nEl proxy puede ayudar a evitar restricciones regionales y mejorar el rendimiento de descarga.",
@@ -140,7 +148,11 @@
"cleared_title": "Configuración de proxy borrada",
"cleared_message": "Se borró y guardó toda la configuración de proxy.",
"saved_main": "Proxy principal guardado: {proxy}",
- "saved_geo": "Proxy geo guardado: {proxy}"
+ "saved_geo": "Proxy geo guardado: {proxy}",
+ "proxy_url": "Proxy URL",
+ "proxy_placeholder": "http://proxy:port or socks5://proxy:port",
+ "geo_bypass": "Geo-bypass proxy (for geographic restrictions)",
+ "geo_bypass_placeholder": "http://proxy:port for bypassing geo-blocks"
},
"download": {
"preparing": "Preparando descarga...",
@@ -194,7 +206,11 @@
"high_audio": "Audio Alto",
"medium_audio": "Audio Medio",
"low_audio": "Audio Bajo",
- "audio_only_resolution": "Solo audio"
+ "audio_only_resolution": "Solo audio",
+ "video_format": "Video Format:",
+ "audio_format": "Audio Format:",
+ "no_formats": "No formats available",
+ "loading": "Loading formats..."
},
"about": {
"title": "Acerca de YTSage",
@@ -222,7 +238,11 @@
"end_time": "Tiempo de Fin:",
"start_time_placeholder": "00:00:00 (o dejar vacío para inicio)",
"end_time_placeholder": "00:10:00 (o dejar vacío para fin)",
- "force_keyframes": "Forzar fotogramas clave en cortes (mejor precisión, más lento)"
+ "force_keyframes": "Forzar fotogramas clave en cortes (mejor precisión, más lento)",
+ "start_placeholder": "00:00:00",
+ "end_placeholder": "00:00:00",
+ "invalid_format": "Invalid time format. Use HH:MM:SS format.",
+ "start_after_end": "Start time cannot be after end time."
},
"update": {
"title": "Actualizar yt-dlp",
@@ -325,7 +345,13 @@
"filename_format_help": "Variables disponibles: %(title)s, %(uploader)s, %(upload_date)s, %(resolution)s, %(id)s, %(ext)s. Se admite la sintaxis estándar de plantilla de salida de yt-dlp.",
"tab_general": "General",
"tab_format": "Formato",
- "tab_file": "Archivo"
+ "tab_file": "Archivo",
+ "concurrent_fragments": "Conexiones simultáneas",
+ "concurrent_fragments_help": "Número de conexiones por descarga. Los valores más altos evitan la limitación, pero pueden causar bloqueos temporales si se establecen demasiado altos. Predeterminado: 1.",
+ "defaults_settings": "Configuración de selección predeterminada",
+ "default_video_quality": "Resolución de video predeterminada (altura):",
+ "default_subtitle_language": "Idioma(s) de subtítulos predeterminado(s):",
+ "defaults_help": "Establece tu altura de video preferida y los idiomas de los subtítulos (separados por comas). Se seleccionarán automáticamente si están disponibles."
},
"main_ui": {
"url_placeholder": "Ingresa URL de video o lista de YouTube",
@@ -435,7 +461,8 @@
"generic_error": "Error: {error}",
"download_failed_return_code_conflict": "La descarga falló con el código de salida {return_code}. Esto puede deberse a un conflicto con varias instalaciones de yt-dlp. Intenta desinstalar cualquier yt-dlp instalado en el sistema (p. ej., mediante snap o apt) y reinicia la aplicación.",
"download_failed_return_code": "La descarga falló con el código de salida {return_code}",
- "direct_command_error": "Error en el comando directo: {error}"
+ "direct_command_error": "Error en el comando directo: {error}",
+ "private_video": "Este video podría ser privado. Por favor, utiliza cookies desde las opciones personalizadas."
},
"update_dialog": {
"title": "Actualización disponible",
@@ -450,7 +477,13 @@
"unknown": "Lista de reproducción desconocida",
"total_videos": "Total de Videos: {count}",
"display_format": "Lista de reproducción: {title} | {count} videos",
- "select_videos_title": "Seleccionar Videos de la Lista de Reproducción"
+ "select_videos_title": "Seleccionar Videos de la Lista de Reproducción",
+ "save_as": "Guardar playlist como",
+ "save_success_title": "Éxito",
+ "saved_successfully": "Playlist guardada correctamente.",
+ "save_error_title": "Error al guardar",
+ "no_videos_to_save": "¡No se han recopilado entradas de la lista de reproducción!",
+ "save_error_msg": "Error al guardar la lista de reproducción."
},
"subtitle_selection": {
"count_selected": "{count} seleccionados"
@@ -607,4 +640,4 @@
"update_failed": "❌ Error en la actualización: {error}",
"check_failed": "Error al buscar actualizaciones. Por favor, verifique su conexión a Internet."
}
-}
+}
\ No newline at end of file
diff --git a/ytsage/languages/fr.json b/ytsage/languages/fr.json
index db5976c..4db0f65 100644
--- a/ytsage/languages/fr.json
+++ b/ytsage/languages/fr.json
@@ -83,7 +83,8 @@
"select_all": "Tout sélectionner",
"deselect_all": "Tout désélectionner",
"open_folder": "Ouvrir l'emplacement du dossier",
- "history": "Historique"
+ "history": "Historique",
+ "save_playlist": "Enregistrer la playlist sous"
},
"dialogs": {
"custom_options": "Options personnalisées",
@@ -95,7 +96,8 @@
"filter_languages_placeholder": "Filtrer les langues (ex: en, fr)...",
"no_subtitles_available": "Aucun sous-titre disponible",
"matching": "correspondant",
- "ytdlp_log_title": "Journal yt-dlp"
+ "ytdlp_log_title": "Journal yt-dlp",
+ "filter_playlist_placeholder": "Filtrer les vidéos..."
},
"tabs": {
"cookies": "Se connecter avec des cookies",
@@ -129,7 +131,8 @@
"cleared_message": "Les paramètres des cookies ont été effacés",
"active_browser": "✓ Actifs : cookies du navigateur ({browser})",
"active_file": "✓ Actif : fichier cookies ({file})",
- "none_active": "○ Aucun cookie actif"
+ "none_active": "○ Aucun cookie actif",
+ "remember_settings": "Mémoriser les paramètres des cookies au prochain démarrage"
},
"custom_command": {
"help_text": "Entrez votre commande yt-dlp personnalisée ci-dessous. L'URL actuelle sera automatiquement ajoutée.
Pour la liste complète des options et exemples d'utilisation
cliquez ici pour voir la documentation officielle yt-dlp.
Note : Le chemin de téléchargement et le modèle de nom de fichier sont gérés automatiquement.",
@@ -342,7 +345,13 @@
"filename_format_help": "Variables disponibles : %(title)s, %(uploader)s, %(upload_date)s, %(resolution)s, %(id)s, %(ext)s. La syntaxe standard des modèles de sortie yt-dlp est prise en charge.",
"tab_general": "Général",
"tab_format": "Format",
- "tab_file": "Fichier"
+ "tab_file": "Fichier",
+ "concurrent_fragments": "Connexions simultanées",
+ "concurrent_fragments_help": "Nombre de connexions par téléchargement. Des valeurs plus élevées contournent le bridage mais peuvent entraîner des blocages temporaires si elles sont trop élevées. Par défaut : 1.",
+ "defaults_settings": "Paramètres de sélection par défaut",
+ "default_video_quality": "Résolution vidéo par défaut (hauteur) :",
+ "default_subtitle_language": "Langue(s) des sous-titres par défaut :",
+ "defaults_help": "Définissez votre hauteur de vidéo préférée et les langues des sous-titres (séparées par des virgules). Elles seront sélectionnées automatiquement si elles sont disponibles."
},
"main_ui": {
"url_placeholder": "Entrer l'URL de la vidéo ou de la playlist YouTube",
@@ -452,7 +461,8 @@
"generic_error": "Erreur : {error}",
"download_failed_return_code_conflict": "Téléchargement échoué avec le code de retour {return_code}. Cela peut être dû à un conflit entre plusieurs installations de yt-dlp. Essayez de désinstaller toute version installée au niveau du système (par ex. via snap ou apt) puis redémarrez l’application.",
"download_failed_return_code": "Téléchargement échoué avec le code de retour {return_code}",
- "direct_command_error": "Erreur dans la commande directe : {error}"
+ "direct_command_error": "Erreur dans la commande directe : {error}",
+ "private_video": "Cette vidéo pourrait être privée. Veuillez utiliser des cookies depuis les options personnalisées."
},
"update_dialog": {
"title": "Mise à jour disponible",
@@ -467,7 +477,13 @@
"unknown": "Playlist inconnue",
"total_videos": "Total des vidéos : {count}",
"display_format": "Playlist : {title} | {count} vidéos",
- "select_videos_title": "Sélectionner les Vidéos de la Playlist"
+ "select_videos_title": "Sélectionner les Vidéos de la Playlist",
+ "save_as": "Enregistrer la playlist sous",
+ "save_success_title": "Succès",
+ "saved_successfully": "Playlist enregistrée avec succès.",
+ "save_error_title": "Erreur d'enregistrement",
+ "no_videos_to_save": "Aucune vidéo de la playlist n'a été récupérée !",
+ "save_error_msg": "Échec de l'enregistrement de la playlist."
},
"subtitle_selection": {
"count_selected": "{count} sélectionné(s)"
@@ -624,4 +640,4 @@
"update_failed": "❌ Échec de la mise à jour : {error}",
"check_failed": "Échec de la vérification des mises à jour. Veuillez vérifier votre connexion Internet."
}
-}
+}
\ No newline at end of file
diff --git a/ytsage/languages/hi.json b/ytsage/languages/hi.json
index 048a1a8..e3759c1 100644
--- a/ytsage/languages/hi.json
+++ b/ytsage/languages/hi.json
@@ -83,7 +83,8 @@
"select_all": "सभी चुनें",
"deselect_all": "सभी को अचयनित करें",
"open_folder": "फ़ोल्डर स्थान खोलें",
- "history": "इतिहास"
+ "history": "इतिहास",
+ "save_playlist": "प्लेलिस्ट को इस रूप में सहेजें"
},
"dialogs": {
"custom_options": "कस्टम विकल्प",
@@ -91,11 +92,12 @@
"select_folder": "डाउनलोड फ़ोल्डर चुनें",
"sponsorblock_categories": "SponsorBlock श्रेणियां",
"sponsorblock_description": "डाउनलोड के दौरान अपने आप हटाए जाने वाले वीडियो सेगमेंट के प्रकार चुनें।\nSponsorBlock इन सेगमेंट की पहचान के लिए समुदाय द्वारा प्रस्तुत डेटा का उपयोग करता है।",
- "select_subtitles": "उपशीर्षक चुनें",
+ "select_subtitles": "उपशीर्षک चुनें",
"filter_languages_placeholder": "भाषाएं फ़िल्टर करें (जैसे: hi, en)...",
"no_subtitles_available": "कोई उपशीर्षक उपलब्ध नहीं",
"matching": "मेल खाता",
- "ytdlp_log_title": "yt-dlp लॉग"
+ "ytdlp_log_title": "yt-dlp लॉग",
+ "filter_playlist_placeholder": "वीडियो फ़िल्टर करें..."
},
"tabs": {
"cookies": "कुकीज़ के साथ लॉगिन",
@@ -129,7 +131,8 @@
"cleared_message": "कुकी सेटिंग्स साफ़ कर दी गई हैं",
"active_browser": "✓ सक्रिय: ब्राउज़र कुकीज़ ({browser})",
"active_file": "✓ सक्रिय: कुकी फ़ाइल ({file})",
- "none_active": "○ कोई कुकी सक्रिय नहीं"
+ "none_active": "○ कोई कुकी सक्रिय नहीं",
+ "remember_settings": "अगले स्टार्टअप पर कुकी सेटिंग्स याद रखें"
},
"custom_command": {
"help_text": "नीचे अपना कस्टम yt-dlp कमांड दर्ज करें। वर्तमान URL स्वचालित रूप से जोड़ा जाएगा।
विकल्पों की पूरी सूची और उपयोग के उदाहरणों के लिए
आधिकारिक yt-dlp दस्तावेज़ देखने के लिए यहाँ क्लिक करें।
नोट: डाउनलोड पथ और फ़ाइलनाम टेम्प्लेट स्वचालित रूप से संभाले जाते हैं।",
@@ -342,7 +345,13 @@
"filename_format_help": "उपलब्ध चर: %(title)s, %(uploader)s, %(upload_date)s, %(resolution)s, %(id)s, %(ext)s. मानक yt-dlp आउटपुट टेम्प्लेट सिंटैक्स समर्थित है.",
"tab_general": "सामान्य",
"tab_format": "प्रारूप",
- "tab_file": "फ़ाइल"
+ "tab_file": "फ़ाइल",
+ "concurrent_fragments": "एक साथ कनेक्शन",
+ "concurrent_fragments_help": "प्रति डाउनलोड कनेक्शन की संख्या। उच्च मान थ्रॉटलिंग को बायपास करते हैं लेकिन बहुत अधिक सेट होने पर अस्थायी ब्लॉक का कारण बन सकते हैं। डिफ़ॉल्ट: 1.",
+ "defaults_settings": "डिफ़ॉल्ट चयन सेटिंग्स",
+ "default_video_quality": "डिफ़ॉल्ट वीडियो रिज़ॉल्यूशन (ऊंचाई):",
+ "default_subtitle_language": "डिफ़ॉल्ट उपशीर्षक भाषा(ए):",
+ "defaults_help": "अपनी पसंदीदा वीडियो ऊंचाई और उपशीर्षक भाषाएं (कॉमा से अलग) सेट करें। उपलब्ध होने पर वे स्वतः चयनित हो जाएंगी।"
},
"main_ui": {
"url_placeholder": "YouTube वीडियो या प्लेलिस्ट URL दर्ज करें",
@@ -452,7 +461,8 @@
"generic_error": "त्रुटि: {error}",
"download_failed_return_code_conflict": "डाउनलोड {return_code} रिटर्न कोड के साथ विफल हुआ। यह कई yt-dlp इंस्टॉलेशन के टकराव के कारण हो सकता है। किसी भी सिस्टम-इंस्टॉल्ड yt-dlp (जैसे snap या apt) को हटाकर ऐप को रीस्टार्ट करें।",
"download_failed_return_code": "डाउनलोड {return_code} रिटर्न कोड के साथ विफल हुआ",
- "direct_command_error": "सीधे कमांड में त्रुटि: {error}"
+ "direct_command_error": "सीधे कमांड में त्रुटि: {error}",
+ "private_video": "यह वीडियो निजी हो सकता है। कृपया कस्टम विकल्पों से कुकीज़ का उपयोग करें।"
},
"update_dialog": {
"title": "अपडेट उपलब्ध",
@@ -467,7 +477,13 @@
"unknown": "अज्ञात प्लेलिस्ट",
"total_videos": "कुल वीडियो: {count}",
"display_format": "प्लेलिस्ट: {title} | {count} वीडियो",
- "select_videos_title": "प्लेलिस्ट वीडियो चुनें"
+ "select_videos_title": "प्लेलिस्ट वीडियो चुनें",
+ "save_as": "प्लेलिस्ट को इस रूप में सहेजें",
+ "save_success_title": "सफलता",
+ "saved_successfully": "प्लेलिस्ट सफलतापूर्वक सहेजी गई।",
+ "save_error_title": "सहेजने में त्रुटि",
+ "no_videos_to_save": "कोई प्लेलिस्ट प्रविष्टियाँ एकत्रित नहीं हुई!",
+ "save_error_msg": "प्लेलिस्ट सहेजने में विफल।"
},
"subtitle_selection": {
"count_selected": "{count} चुना गया"
@@ -624,4 +640,4 @@
"update_failed": "❌ अपडेट विफल: {error}",
"check_failed": "अपडेट की जांच विफल। कृपया अपना इंटरनेट कनेक्शन जांचें।"
}
-}
+}
\ No newline at end of file
diff --git a/ytsage/languages/id.json b/ytsage/languages/id.json
index 8d4927a..0081c3f 100644
--- a/ytsage/languages/id.json
+++ b/ytsage/languages/id.json
@@ -83,7 +83,8 @@
"select_all": "Pilih semua",
"deselect_all": "Batalkan pilihan semua",
"open_folder": "Buka lokasi folder",
- "history": "Riwayat"
+ "history": "Riwayat",
+ "save_playlist": "Simpan Playlist Sebagai"
},
"dialogs": {
"custom_options": "Opsi khusus",
@@ -95,7 +96,8 @@
"filter_languages_placeholder": "Filter bahasa (misalnya: id, en)...",
"no_subtitles_available": "Tidak ada subtitle yang tersedia",
"matching": "yang cocok",
- "ytdlp_log_title": "Log yt-dlp"
+ "ytdlp_log_title": "Log yt-dlp",
+ "filter_playlist_placeholder": "Filter videos..."
},
"tabs": {
"cookies": "Masuk dengan cookies",
@@ -129,7 +131,8 @@
"cleared_message": "Pengaturan cookie telah dihapus",
"active_browser": "✓ Aktif: cookie browser ({browser})",
"active_file": "✓ Aktif: file cookie ({file})",
- "none_active": "○ Tidak ada cookie aktif"
+ "none_active": "○ Tidak ada cookie aktif",
+ "remember_settings": "Ingat pengaturan cookie pada startup berikutnya"
},
"custom_command": {
"help_text": "Masukkan perintah yt-dlp khusus Anda di bawah. URL saat ini akan ditambahkan secara otomatis.
Untuk daftar lengkap opsi dan contoh penggunaan
klik di sini untuk melihat dokumentasi resmi yt-dlp.
Catatan: Jalur unduhan dan template nama file ditangani secara otomatis.",
@@ -342,7 +345,13 @@
"filename_format_help": "Variabel yang tersedia: %(title)s, %(uploader)s, %(upload_date)s, %(resolution)s, %(id)s, %(ext)s. Sintaks templat keluaran standar yt-dlp didukung.",
"tab_general": "Umum",
"tab_format": "Format",
- "tab_file": "Berkas"
+ "tab_file": "Berkas",
+ "concurrent_fragments": "Koneksi Simultan",
+ "concurrent_fragments_help": "Jumlah koneksi per unduhan. Nilai yang lebih tinggi melewati pembatasan tetapi dapat menyebabkan pemblokiran sementara jika diatur terlalu tinggi. Default: 1.",
+ "defaults_settings": "Pengaturan Pilihan Default",
+ "default_video_quality": "Resolusi Video Default (Tinggi):",
+ "default_subtitle_language": "Bahasa Subtitel Default:",
+ "defaults_help": "Atur tinggi video dan bahasa subtitel pilihan Anda (pisahkan dengan koma). Mereka akan dipilih otomatis jika tersedia."
},
"main_ui": {
"url_placeholder": "Masukkan URL video atau playlist YouTube",
@@ -452,7 +461,8 @@
"generic_error": "Kesalahan: {error}",
"download_failed_return_code_conflict": "Unduhan gagal dengan kode pengembalian {return_code}. Ini mungkin karena konflik dengan beberapa instalasi yt-dlp. Coba uninstall yt-dlp yang terpasang di sistem (mis. melalui snap atau apt) lalu mulai ulang aplikasi.",
"download_failed_return_code": "Unduhan gagal dengan kode pengembalian {return_code}",
- "direct_command_error": "Error pada perintah langsung: {error}"
+ "direct_command_error": "Error pada perintah langsung: {error}",
+ "private_video": "Video ini mungkin bersifat pribadi. Silakan gunakan cookie dari opsi kustom."
},
"update_dialog": {
"title": "Pembaruan Tersedia",
@@ -467,7 +477,13 @@
"unknown": "Playlist Tidak Dikenal",
"total_videos": "Total Video: {count}",
"display_format": "Playlist: {title} | {count} video",
- "select_videos_title": "Pilih Video Playlist"
+ "select_videos_title": "Pilih Video Playlist",
+ "save_as": "Simpan Playlist Sebagai",
+ "save_success_title": "Berhasil",
+ "saved_successfully": "Playlist berhasil disimpan.",
+ "save_error_title": "Gagal Menyimpan",
+ "no_videos_to_save": "Tidak ada entri playlist yang dikumpulkan!",
+ "save_error_msg": "Gagal menyimpan playlist."
},
"subtitle_selection": {
"count_selected": "{count} dipilih"
@@ -624,4 +640,4 @@
"update_failed": "❌ Pembaruan gagal: {error}",
"check_failed": "Gagal memeriksa pembaruan. Harap periksa koneksi internet Anda."
}
-}
+}
\ No newline at end of file
diff --git a/ytsage/languages/it.json b/ytsage/languages/it.json
index f55b1dc..46021d6 100644
--- a/ytsage/languages/it.json
+++ b/ytsage/languages/it.json
@@ -83,7 +83,8 @@
"select_all": "Seleziona tutto",
"deselect_all": "Deseleziona tutto",
"open_folder": "Apri posizione cartella",
- "history": "Cronologia"
+ "history": "Cronologia",
+ "save_playlist": "Salva playlist come"
},
"dialogs": {
"custom_options": "Opzioni personalizzate",
@@ -95,7 +96,8 @@
"filter_languages_placeholder": "Filtra lingue (es: it, en)...",
"no_subtitles_available": "Nessun sottotitolo disponibile",
"matching": "corrispondenti",
- "ytdlp_log_title": "Registro yt-dlp"
+ "ytdlp_log_title": "Registro yt-dlp",
+ "filter_playlist_placeholder": "Filter videos..."
},
"tabs": {
"cookies": "Accedi con i cookie",
@@ -129,7 +131,8 @@
"cleared_message": "Le impostazioni dei cookie sono state cancellate",
"active_browser": "✓ Attivi: cookie del browser ({browser})",
"active_file": "✓ Attivo: file cookie ({file})",
- "none_active": "○ Nessun cookie attivo"
+ "none_active": "○ Nessun cookie attivo",
+ "remember_settings": "Ricorda le impostazioni dei cookie al prossimo avvio"
},
"custom_command": {
"help_text": "Inserisci un comando yt-dlp personalizzato qui sotto. L'URL corrente verrà aggiunto automaticamente.
Per l'elenco completo delle opzioni ed esempi di utilizzo
clicca qui per vedere la documentazione ufficiale yt-dlp.
Nota: Il percorso di download e il modello del nome file sono gestiti automaticamente.",
@@ -342,7 +345,13 @@
"filename_format_help": "Variabili disponibili: %(title)s, %(uploader)s, %(upload_date)s, %(resolution)s, %(id)s, %(ext)s. È supportata la sintassi standard del modello di output di yt-dlp.",
"tab_general": "Generale",
"tab_format": "Formato",
- "tab_file": "File"
+ "tab_file": "File",
+ "concurrent_fragments": "Connessioni simultanee",
+ "concurrent_fragments_help": "Numero di connessioni per download. Valori più alti aggirano il throttling ma possono causare blocchi temporanei se impostati troppo alti. Predefinito: 1.",
+ "defaults_settings": "Impostazioni di selezione predefinite",
+ "default_video_quality": "Risoluzione video predefinita (altezza):",
+ "default_subtitle_language": "Lingua/e dei sottotitoli predefinita/e:",
+ "defaults_help": "Imposta l'altezza del video preferita e le lingue dei sottotitoli (separate da virgole). Verranno selezionate automaticamente se disponibili."
},
"main_ui": {
"url_placeholder": "Inserisci URL video YouTube o playlist",
@@ -452,7 +461,8 @@
"generic_error": "Errore: {error}",
"download_failed_return_code_conflict": "Download non riuscito con codice di uscita {return_code}. Potrebbe essere dovuto a un conflitto tra più installazioni di yt-dlp. Prova a disinstallare eventuali yt-dlp installati a livello di sistema (es. snap o apt) e riavvia l’app.",
"download_failed_return_code": "Download non riuscito con codice di uscita {return_code}",
- "direct_command_error": "Errore nel comando diretto: {error}"
+ "direct_command_error": "Errore nel comando diretto: {error}",
+ "private_video": "Questo video potrebbe essere privato. Si prega di utilizzare i cookie dalle opzioni personalizzate."
},
"update_dialog": {
"title": "Aggiornamento disponibile",
@@ -467,7 +477,13 @@
"unknown": "Playlist sconosciuta",
"total_videos": "Video totali: {count}",
"display_format": "Playlist: {title} | {count} video",
- "select_videos_title": "Seleziona Video Playlist"
+ "select_videos_title": "Seleziona Video Playlist",
+ "save_as": "Salva playlist come",
+ "save_success_title": "Successo",
+ "saved_successfully": "Playlist salvata con successo.",
+ "save_error_title": "Errore di salvataggio",
+ "no_videos_to_save": "Nessuna voce della playlist raccolta!",
+ "save_error_msg": "Impossibile salvare la playlist."
},
"subtitle_selection": {
"count_selected": "{count} selezionati"
@@ -624,4 +640,4 @@
"update_failed": "❌ Aggiornamento fallito: {error}",
"check_failed": "Impossibile controllare gli aggiornamenti. Controlla la tua connessione Internet."
}
-}
+}
\ No newline at end of file
diff --git a/ytsage/languages/ja.json b/ytsage/languages/ja.json
index e6936e4..cfc2939 100644
--- a/ytsage/languages/ja.json
+++ b/ytsage/languages/ja.json
@@ -83,7 +83,8 @@
"select_all": "すべて選択",
"deselect_all": "すべて解除",
"open_folder": "フォルダの場所を開く",
- "history": "履歴"
+ "history": "履歴",
+ "save_playlist": "プレイリストを別名で保存"
},
"dialogs": {
"custom_options": "カスタムオプション",
@@ -95,7 +96,8 @@
"filter_languages_placeholder": "言語でフィルタ (例: ja, en)...",
"no_subtitles_available": "利用可能な字幕がありません",
"matching": "一致",
- "ytdlp_log_title": "yt-dlpログ"
+ "ytdlp_log_title": "yt-dlpログ",
+ "filter_playlist_placeholder": "Filter videos..."
},
"tabs": {
"cookies": "Cookieでログイン",
@@ -129,7 +131,8 @@
"cleared_message": "Cookie設定がクリアされました",
"active_browser": "✓ 有効: ブラウザーのCookie ({browser})",
"active_file": "✓ 有効: Cookieファイル ({file})",
- "none_active": "○ 有効なCookieはありません"
+ "none_active": "○ 有効なCookieはありません",
+ "remember_settings": "次回の起動時にCookie設定を記憶する"
},
"custom_command": {
"help_text": "以下にカスタムyt-dlpコマンドを入力してください。現在のURLは自動的に追加されます。
オプションの完全なリストと使用例については、
こちらをクリックしてyt-dlp公式ドキュメントを参照してください。
注: ダウンロードパスとファイル名テンプレートは自動的に処理されます。",
@@ -342,7 +345,13 @@
"filename_format_help": "利用可能な変数: %(title)s, %(uploader)s, %(upload_date)s, %(resolution)s, %(id)s, %(ext)s。標準のyt-dlp出力テンプレート構文がサポートされています。",
"tab_general": "一般",
"tab_format": "フォーマット",
- "tab_file": "ファイル"
+ "tab_file": "ファイル",
+ "concurrent_fragments": "同時接続数",
+ "concurrent_fragments_help": "ダウンロードごとの接続数。値を大きくするとスロットリングを回避できますが、高すぎると一時的なブロックが発生する可能性があります。デフォルト: 1。",
+ "defaults_settings": "デフォルト選択設定",
+ "default_video_quality": "デフォルトのビデオ解像度 (高さ):",
+ "default_subtitle_language": "デフォルトの字幕言語:",
+ "defaults_help": "優先するビデオの高さと字幕言語(カンマ区切り)を設定します。利用可能な場合は自動的に選択されます。"
},
"main_ui": {
"url_placeholder": "YouTubeの動画URLまたはプレイリストURLを入力",
@@ -452,7 +461,8 @@
"generic_error": "エラー: {error}",
"download_failed_return_code_conflict": "ダウンロードは戻りコード {return_code} で失敗しました。複数の yt-dlp インストールの競合が原因の可能性があります。システムにインストールされた yt-dlp(例: snap や apt)をアンインストールしてアプリを再起動してください。",
"download_failed_return_code": "ダウンロードは戻りコード {return_code} で失敗しました",
- "direct_command_error": "直接コマンドのエラー: {error}"
+ "direct_command_error": "直接コマンドのエラー: {error}",
+ "private_video": "この動画は非公開かもしれません。カスタムオプションからCookieを使用してください。"
},
"update_dialog": {
"title": "アップデートが利用可能です",
@@ -467,7 +477,13 @@
"unknown": "不明な再生リスト",
"total_videos": "総動画数: {count}",
"display_format": "再生リスト: {title} | {count}個の動画",
- "select_videos_title": "プレイリスト動画を選択"
+ "select_videos_title": "プレイリスト動画を選択",
+ "save_as": "プレイリストを別名で保存",
+ "save_success_title": "成功",
+ "saved_successfully": "プレイリストを正常に保存しました。",
+ "save_error_title": "保存エラー",
+ "no_videos_to_save": "プレイリストのエントリが見つかりません!",
+ "save_error_msg": "プレイリストの保存に失敗しました。"
},
"subtitle_selection": {
"count_selected": "{count}個選択"
@@ -624,4 +640,4 @@
"update_failed": "❌ 更新が失敗しました:{error}",
"check_failed": "更新の確認に失敗しました。インターネット接続を確認してください。"
}
-}
+}
\ No newline at end of file
diff --git a/ytsage/languages/pl.json b/ytsage/languages/pl.json
index 3888292..118fb29 100644
--- a/ytsage/languages/pl.json
+++ b/ytsage/languages/pl.json
@@ -83,7 +83,8 @@
"select_all": "Zaznacz wszystko",
"deselect_all": "Odznacz wszystko",
"open_folder": "Otwórz lokalizację folderu",
- "history": "Historia"
+ "history": "Historia",
+ "save_playlist": "Zapisz playlistę jako"
},
"dialogs": {
"custom_options": "Opcje niestandardowe",
@@ -95,7 +96,8 @@
"filter_languages_placeholder": "Filtruj języki (np: pl, en)...",
"no_subtitles_available": "Brak dostępnych napisów",
"matching": "dopasowujące",
- "ytdlp_log_title": "Log yt-dlp"
+ "ytdlp_log_title": "Log yt-dlp",
+ "filter_playlist_placeholder": "Filter videos..."
},
"tabs": {
"cookies": "Zaloguj za pomocą ciasteczek",
@@ -129,7 +131,8 @@
"cleared_message": "Ustawienia ciasteczek zostały wyczyszczone",
"active_browser": "✓ Aktywne: ciasteczka przeglądarki ({browser})",
"active_file": "✓ Aktywny: plik ciasteczek ({file})",
- "none_active": "○ Brak aktywnych ciasteczek"
+ "none_active": "○ Brak aktywnych ciasteczek",
+ "remember_settings": "Zapamiętaj ustawienia plików cookie przy następnym uruchomieniu"
},
"custom_command": {
"help_text": "Wprowadź niestandardowe polecenie yt-dlp poniżej. Aktualny URL zostanie automatycznie dodany.
Aby uzyskać pełną listę opcji i przykładów użycia
kliknij tutaj, aby zobaczyć oficjalną dokumentację yt-dlp.
Uwaga: Ścieżka pobierania i szablon nazwy pliku są obsługiwane automatycznie.",
@@ -342,7 +345,13 @@
"filename_format_help": "Dostępne zmienne: %(title)s, %(uploader)s, %(upload_date)s, %(resolution)s, %(id)s, %(ext)s. Obsługiwana jest standardowa składnia szablonu wyjściowego yt-dlp.",
"tab_general": "Ogólne",
"tab_format": "Format",
- "tab_file": "Plik"
+ "tab_file": "Plik",
+ "concurrent_fragments": "Jednoczesne połączenia",
+ "concurrent_fragments_help": "Liczba połączeń na pobieranie. Wyższe wartości omijają dławienie, ale mogą powodować tymczasowe blokady, jeśli zostaną ustawione zbyt wysoko. Domyślnie: 1.",
+ "defaults_settings": "Domyślne ustawienia wyboru",
+ "default_video_quality": "Domyślna rozdzielczość wideo (wysokość):",
+ "default_subtitle_language": "Domyślny język(i) napisów:",
+ "defaults_help": "Ustaw preferowaną wysokość wideo i języki napisów (rozdzielone przecinkami). Zostaną one automatycznie wybrane, jeśli będą dostępne."
},
"main_ui": {
"url_placeholder": "Wprowadź URL wideo YouTube lub playlisty",
@@ -452,7 +461,8 @@
"generic_error": "Błąd: {error}",
"download_failed_return_code_conflict": "Pobieranie nie powiodło się z kodem {return_code}. Może to wynikać z konfliktu wielu instalacji yt-dlp. Spróbuj odinstalować systemowo zainstalowane yt-dlp (np. przez snap lub apt) i uruchom aplikację ponownie.",
"download_failed_return_code": "Pobieranie nie powiodło się z kodem {return_code}",
- "direct_command_error": "Błąd w bezpośrednim poleceniu: {error}"
+ "direct_command_error": "Błąd w bezpośrednim poleceniu: {error}",
+ "private_video": "Ten film może być prywatny. Proszę użyć plików cookie z opcji niestandardowych."
},
"update_dialog": {
"title": "Dostępna aktualizacja",
@@ -467,7 +477,13 @@
"unknown": "Nieznana playlista",
"total_videos": "Wszystkich wideo: {count}",
"display_format": "Playlista: {title} | {count} wideo",
- "select_videos_title": "Wybierz Filmy z Playlisty"
+ "select_videos_title": "Wybierz Filmy z Playlisty",
+ "save_as": "Zapisz playlistę jako",
+ "save_success_title": "Sukces",
+ "saved_successfully": "Playlista zapisana pomyślnie.",
+ "save_error_title": "Błąd zapisu",
+ "no_videos_to_save": "Nie zebrano żadnych wpisów z playlisty!",
+ "save_error_msg": "Nie udało się zapisać playlisty."
},
"subtitle_selection": {
"count_selected": "{count} wybrano"
@@ -624,4 +640,4 @@
"update_failed": "❌ Aktualizacja nie powiodła się: {error}",
"check_failed": "Nie udało się sprawdzić aktualizacji. Sprawdź połączenie internetowe."
}
-}
+}
\ No newline at end of file
diff --git a/ytsage/languages/pt.json b/ytsage/languages/pt.json
index 05ae754..da7609f 100644
--- a/ytsage/languages/pt.json
+++ b/ytsage/languages/pt.json
@@ -83,7 +83,8 @@
"select_all": "Selecionar Tudo",
"deselect_all": "Desmarcar Tudo",
"open_folder": "Abrir local da pasta",
- "history": "Histórico"
+ "history": "Histórico",
+ "save_playlist": "Salvar playlist como"
},
"dialogs": {
"custom_options": "Opções Personalizadas",
@@ -95,7 +96,8 @@
"filter_languages_placeholder": "Filtrar idiomas (ex., en, pt)...",
"no_subtitles_available": "Nenhuma legenda disponível",
"matching": "correspondendo",
- "ytdlp_log_title": "Log do yt-dlp"
+ "ytdlp_log_title": "Log do yt-dlp",
+ "filter_playlist_placeholder": "Filter videos..."
},
"tabs": {
"cookies": "Entrar com Cookies",
@@ -129,7 +131,8 @@
"cleared_message": "As configurações de cookies foram limpas",
"active_browser": "✓ Ativos: cookies do navegador ({browser})",
"active_file": "✓ Ativo: arquivo de cookies ({file})",
- "none_active": "○ Nenhum cookie ativo"
+ "none_active": "○ Nenhum cookie ativo",
+ "remember_settings": "Lembrar configurações de cookies na próxima inicialização"
},
"custom_command": {
"help_text": "Digite seu comando yt-dlp personalizado abaixo. A URL atual será anexada automaticamente.
Para a lista completa de opções e exemplos de uso,
clique aqui para ver a documentação oficial do yt-dlp.
Nota: O caminho de download e modelo de nome de arquivo serão tratados automaticamente.",
@@ -342,7 +345,13 @@
"filename_format_help": "Variáveis disponíveis: %(title)s, %(uploader)s, %(upload_date)s, %(resolution)s, %(id)s, %(ext)s. A sintaxe padrão de modelo de saída do yt-dlp é suportada.",
"tab_general": "Geral",
"tab_format": "Formato",
- "tab_file": "Arquivo"
+ "tab_file": "Arquivo",
+ "concurrent_fragments": "Conexões simultâneas",
+ "concurrent_fragments_help": "Número de conexões por download. Valores mais altos contornam o limite de velocidade, mas podem causar bloqueios temporários se forem muito altos. Padrão: 1.",
+ "defaults_settings": "Configurações de seleção padrão",
+ "default_video_quality": "Resolução de vídeo padrão (altura):",
+ "default_subtitle_language": "Idioma(s) de legenda padrão:",
+ "defaults_help": "Defina sua altura de vídeo preferida e os idiomas das legendas (separados por vírgula). Eles serão selecionados automaticamente se disponíveis."
},
"main_ui": {
"url_placeholder": "Digite a URL do vídeo ou playlist do YouTube",
@@ -452,7 +461,8 @@
"generic_error": "Erro: {error}",
"download_failed_return_code_conflict": "O download falhou com o código de retorno {return_code}. Isso pode ser devido a um conflito com várias instalações do yt-dlp. Tente desinstalar qualquer yt-dlp instalado no sistema (ex.: snap ou apt) e reinicie o aplicativo.",
"download_failed_return_code": "O download falhou com o código de retorno {return_code}",
- "direct_command_error": "Erro no comando direto: {error}"
+ "direct_command_error": "Erro no comando direto: {error}",
+ "private_video": "Este vídeo pode ser privado. Por favor, use cookies das opções personalizadas."
},
"update_dialog": {
"title": "Atualização Disponível",
@@ -467,7 +477,13 @@
"unknown": "Playlist Desconhecida",
"total_videos": "Total de Vídeos: {count}",
"display_format": "Playlist: {title} | {count} vídeos",
- "select_videos_title": "Selecionar Vídeos da Playlist"
+ "select_videos_title": "Selecionar Vídeos da Playlist",
+ "save_as": "Salvar playlist como",
+ "save_success_title": "Sucesso",
+ "saved_successfully": "Playlist salva com sucesso.",
+ "save_error_title": "Erro ao salvar",
+ "no_videos_to_save": "Nenhuma entrada de playlist coletada!",
+ "save_error_msg": "Falha ao salvar a playlist."
},
"subtitle_selection": {
"count_selected": "{count} selecionadas"
@@ -624,4 +640,4 @@
"update_failed": "❌ Falha na atualização: {error}",
"check_failed": "Falha ao verificar atualizações. Verifique sua conexão com a Internet."
}
-}
+}
\ No newline at end of file
diff --git a/ytsage/languages/ru.json b/ytsage/languages/ru.json
index fac7d94..2b5cef8 100644
--- a/ytsage/languages/ru.json
+++ b/ytsage/languages/ru.json
@@ -83,7 +83,8 @@
"select_all": "Выбрать всё",
"deselect_all": "Снять выделение",
"open_folder": "Открыть расположение папки",
- "history": "История"
+ "history": "История",
+ "save_playlist": "Сохранить плейлист как"
},
"dialogs": {
"custom_options": "Пользовательские опции",
@@ -95,7 +96,8 @@
"filter_languages_placeholder": "Фильтр языков (например, en, ru)...",
"no_subtitles_available": "Субтитры недоступны",
"matching": "соответствующие",
- "ytdlp_log_title": "Журнал yt-dlp"
+ "ytdlp_log_title": "Журнал yt-dlp",
+ "filter_playlist_placeholder": "Filter videos..."
},
"tabs": {
"cookies": "Войти через Cookie",
@@ -129,7 +131,8 @@
"cleared_message": "Настройки cookie были очищены",
"active_browser": "✓ Активны: cookie браузера ({browser})",
"active_file": "✓ Активен: файл cookie ({file})",
- "none_active": "○ Нет активных cookie"
+ "none_active": "○ Нет активных cookie",
+ "remember_settings": "Запомнить настройки cookie при следующем запуске"
},
"custom_command": {
"help_text": "Введите пользовательскую команду yt-dlp ниже. Текущий URL будет добавлен автоматически.
Для полного списка опций и примеров использования,
нажмите здесь для просмотра официальной документации yt-dlp.
Примечание: Путь загрузки и шаблон имени файла будут обработаны автоматически.",
@@ -342,7 +345,13 @@
"filename_format_help": "Доступные переменные: %(title)s, %(uploader)s, %(upload_date)s, %(resolution)s, %(id)s, %(ext)s. Поддерживается стандартный синтаксис шаблона вывода yt-dlp.",
"tab_general": "Общие",
"tab_format": "Формат",
- "tab_file": "Файл"
+ "tab_file": "Файл",
+ "concurrent_fragments": "Одновременные соединения",
+ "concurrent_fragments_help": "Количество соединений на одну загрузку. Более высокие значения позволяют обойти ограничение скорости, но могут вызвать временную блокировку при слишком высоких значениях. По умолчанию: 1.",
+ "defaults_settings": "Настройки выбора по умолчанию",
+ "default_video_quality": "Разрешение видео по умолчанию (высота):",
+ "default_subtitle_language": "Язык(и) субтитров по умолчанию:",
+ "defaults_help": "Установите предпочтительную высоту видео и языки субтитров (через запятую). Они будут выбраны автоматически, إذا كان متاحاً."
},
"main_ui": {
"url_placeholder": "Введите URL видео или плейлиста YouTube",
@@ -452,7 +461,8 @@
"generic_error": "Ошибка: {error}",
"download_failed_return_code_conflict": "Загрузка завершилась ошибкой с кодом {return_code}. Это может быть из-за конфликта нескольких установок yt-dlp. Попробуйте удалить системно установленный yt-dlp (например, через snap или apt) и перезапустите приложение.",
"download_failed_return_code": "Загрузка не удалась, код {return_code}",
- "direct_command_error": "Ошибка в прямой команде: {error}"
+ "direct_command_error": "Ошибка в прямой команде: {error}",
+ "private_video": "Это видео может быть приватным. Пожалуйста, используйте файлы cookie из пользовательских настроек."
},
"update_dialog": {
"title": "Доступно обновление",
@@ -467,7 +477,13 @@
"unknown": "Неизвестный плейлист",
"total_videos": "Всего видео: {count}",
"display_format": "Плейлист: {title} | {count} видео",
- "select_videos_title": "Выбрать Видео из Плейлиста"
+ "select_videos_title": "Выбрать Видео из Плейлиста",
+ "save_as": "Сохранить плейлист как",
+ "save_success_title": "Успех",
+ "saved_successfully": "Плейлист успешно сохранен.",
+ "save_error_title": "Ошибка сохранения",
+ "no_videos_to_save": "Записи плейлиста не собраны!",
+ "save_error_msg": "Не удалось сохранить плейлист."
},
"subtitle_selection": {
"count_selected": "выбрано: {count}"
@@ -624,4 +640,4 @@
"update_failed": "❌ Ошибка обновления: {error}",
"check_failed": "Не удалось проверить обновления. Проверьте подключение к Интернету."
}
-}
+}
\ No newline at end of file
diff --git a/ytsage/languages/tr.json b/ytsage/languages/tr.json
index 5c2c58f..e55e46b 100644
--- a/ytsage/languages/tr.json
+++ b/ytsage/languages/tr.json
@@ -83,7 +83,8 @@
"select_all": "Tümünü seç",
"deselect_all": "Tüm seçimi kaldır",
"open_folder": "Klasör konumunu aç",
- "history": "Geçmiş"
+ "history": "Geçmiş",
+ "save_playlist": "Oynatma Listesini Farklı Kaydet"
},
"dialogs": {
"custom_options": "Özel seçenekler",
@@ -95,7 +96,8 @@
"filter_languages_placeholder": "Dilleri filtrele (örn: tr, en)...",
"no_subtitles_available": "Altyazı mevcut değil",
"matching": "eşleşen",
- "ytdlp_log_title": "yt-dlp Günlüğü"
+ "ytdlp_log_title": "yt-dlp Günlüğü",
+ "filter_playlist_placeholder": "Filter videos..."
},
"tabs": {
"cookies": "Çerezlerle giriş yap",
@@ -129,7 +131,8 @@
"cleared_message": "Çerez ayarları temizlendi",
"active_browser": "✓ Etkin: Tarayıcı çerezleri ({browser})",
"active_file": "✓ Etkin: Çerez dosyası ({file})",
- "none_active": "○ Etkin çerez yok"
+ "none_active": "○ Etkin çerez yok",
+ "remember_settings": "Sonraki başlangıçta çerez ayarlarını hatırla"
},
"custom_command": {
"help_text": "Özel yt-dlp komutunuzu aşağıya girin. Mevcut URL otomatik olarak eklenecektir.
Seçeneklerin tam listesi ve kullanım örnekleri için
resmi yt-dlp belgelerini görmek için buraya tıklayın.
Not: İndirme yolu ve dosya adı şablonu otomatik olarak işlenir.",
@@ -177,7 +180,9 @@
"geo_set_title": "Coğrafi proxy ayarlandı",
"geo_set_message": "Coğrafi doğrulama proxy'si ayarlandı ve kaydedildi: {proxy}",
"cleared_title": "Proxy ayarları temizlendi",
- "cleared_message": "Tüm proxy ayarları temizlendi ve kaydedildi."
+ "cleared_message": "Tüm proxy ayarları temizlendi ve kaydedildi.",
+ "saved_main": "Saved main proxy: {proxy}",
+ "saved_geo": "Saved geo proxy: {proxy}"
},
"download": {
"preparing": "İndirme hazırlanıyor...",
@@ -340,7 +345,13 @@
"filename_format_help": "Kullanılabilir değişkenler: %(title)s, %(uploader)s, %(upload_date)s, %(resolution)s, %(id)s, %(ext)s. Standart yt-dlp çıktı şablonu sözdizimi desteklenir.",
"tab_general": "Genel",
"tab_format": "Biçim",
- "tab_file": "Dosya"
+ "tab_file": "Dosya",
+ "concurrent_fragments": "Eşzamanlı Bağlantılar",
+ "concurrent_fragments_help": "İndirme başına bağlantı sayısı. Daha yüksek değerler sınırlamayı atlar ancak çok yüksek ayarlanırsa geçici engellemelere neden olabilir. Varsayılan: 1.",
+ "defaults_settings": "Varsayılan Seçim Ayarları",
+ "default_video_quality": "Varsayılan Video Çözünürlüğü (Yükseklik):",
+ "default_subtitle_language": "Varsayılan Altyazı Dili/Dilleri:",
+ "defaults_help": "Tercih ettiğiniz video yüksekliğini ve altyazı dillerini (virgülle ayırarak) ayarlayın. Varsa otomatik olarak seçileceklerdir."
},
"main_ui": {
"url_placeholder": "YouTube video veya oynatma listesi URL'sini girin",
@@ -450,7 +461,8 @@
"generic_error": "Hata: {error}",
"download_failed_return_code_conflict": "İndirme {return_code} dönüş koduyla başarısız oldu. Bunun nedeni birden fazla yt-dlp kurulumunun çakışması olabilir. Sistem kurulumlu yt-dlp'yi (örn. snap veya apt) kaldırıp uygulamayı yeniden başlatmayı deneyin.",
"download_failed_return_code": "İndirme {return_code} dönüş koduyla başarısız oldu",
- "direct_command_error": "Doğrudan komutta hata: {error}"
+ "direct_command_error": "Doğrudan komutta hata: {error}",
+ "private_video": "Bu video gizli olabilir. Lütfen özel seçeneklerden çerezleri kullanın."
},
"update_dialog": {
"title": "Güncelleme Mevcut",
@@ -465,7 +477,13 @@
"unknown": "Bilinmeyen Oynatma Listesi",
"total_videos": "Toplam Video: {count}",
"display_format": "Oynatma Listesi: {title} | {count} video",
- "select_videos_title": "Oynatma Listesi Videolarını Seç"
+ "select_videos_title": "Oynatma Listesi Videolarını Seç",
+ "save_as": "Oynatma Listesini Farklı Kaydet",
+ "save_success_title": "Başarılı",
+ "saved_successfully": "Oynatma listesi başarıyla kaydedildi.",
+ "save_error_title": "Kaydetme Hatası",
+ "no_videos_to_save": "Hiçbir oynatma listesi girdisi toplanmadı!",
+ "save_error_msg": "Oynatma listesi kaydedilemedi."
},
"subtitle_selection": {
"count_selected": "{count} seçildi"
@@ -536,14 +554,21 @@
"redownload_confirm_message": "Tekrar indir?\n\n{title}",
"redownload_started": "İndirme başladı",
"no_url_error": "Geçmiş kaydında URL bulunamadı",
- "redownload_failed": "Yeniden indirme başlatılamadı: {error}"
+ "redownload_failed": "Yeniden indirme başlatılamadı: {error}",
+ "loading": "Loading history..."
},
"ffmpeg": {
"installation_title": "FFmpeg Kurulumu",
"installation_message": "YTSage, videoları işlemek için FFmpeg'e ihtiyaç duyar.\n\nAşağıdan bir kurulum seçeneği seçin:",
"install_button": "FFmpeg Kur",
"manual_guide": "Manuel Kılavuz",
- "installation_failed": "FFmpeg kurulumu bir sorunla karşılaştı."
+ "installation_failed": "FFmpeg kurulumu bir sorunla karşılaştı.",
+ "already_installed": "FFmpeg zaten yüklü!",
+ "installation_complete": "Kurulum tamamlandı. Bu iletişim kutusunu kapatabilir ve YTSage'i kullanmaya devam edebilirsiniz.",
+ "installing": "FFmpeg kuruluyor... Lütfen bekleyin",
+ "install_success": "FFmpeg başarıyla kuruldu!",
+ "installation_complete_close": "Kurulum tamamlandı. Artık bu iletişim kutusunu kapatabilir ve YTSage'i kullanmaya devam edebilirsiniz.",
+ "try_manual": "Lütfen bunun yerine manuel kurulum kılavuzunu kullanmayı deneyin."
},
"ytdlp_setup": {
"required_title": "yt-dlp Kurulumu Gerekli",
@@ -615,4 +640,4 @@
"update_failed": "❌ Güncelleme başarısız: {error}",
"check_failed": "Güncellemeler kontrol edilemedi. Lütfen internet bağlantınızı kontrol edin."
}
-}
+}
\ No newline at end of file
diff --git a/ytsage/languages/zh.json b/ytsage/languages/zh.json
index 820b648..5bc3c27 100644
--- a/ytsage/languages/zh.json
+++ b/ytsage/languages/zh.json
@@ -83,7 +83,8 @@
"select_all": "全选",
"deselect_all": "取消全选",
"open_folder": "打开文件夹位置",
- "history": "历史"
+ "history": "历史",
+ "save_playlist": "保存播放列表为"
},
"dialogs": {
"custom_options": "自定义选项",
@@ -95,7 +96,8 @@
"filter_languages_placeholder": "过滤语言(例如:en, zh)...",
"no_subtitles_available": "无可用字幕",
"matching": "匹配",
- "ytdlp_log_title": "yt-dlp 日志"
+ "ytdlp_log_title": "yt-dlp 日志",
+ "filter_playlist_placeholder": "Filter videos..."
},
"tabs": {
"cookies": "使用 Cookie 登录",
@@ -126,7 +128,11 @@
"browser_selected_title": "已应用浏览器 Cookie",
"browser_applied_message": "将从以下位置提取浏览器 cookie:{browser}",
"cleared_title": "已清除 Cookie",
- "cleared_message": "Cookie 设置已被清除"
+ "cleared_message": "Cookie 设置已被清除",
+ "active_browser": "✓ Active: Browser cookies ({browser})",
+ "active_file": "✓ Active: Cookie file ({file})",
+ "none_active": "○ No cookies active",
+ "remember_settings": "在下次启动时记住 cookie 设置"
},
"custom_command": {
"help_text": "在下面输入您的自定义 yt-dlp 命令。当前网址将自动附加。
有关选项和使用示例的完整列表,
点击此处查看官方 yt-dlp 文档。
注意:下载路径和文件名模板将自动处理。",
@@ -139,7 +145,14 @@
"full_command": "🔧 完整命令:{command}",
"command_success": "✅ 自定义命令执行成功!",
"command_failed": "❌ 命令失败,退出代码 {code}",
- "command_error": "❌ 执行自定义命令时出错:{error}"
+ "command_error": "❌ 执行自定义命令时出错:{error}",
+ "error_no_url": "❌ Error: No URL provided. Please enter a URL in the main window.",
+ "error_no_command": "❌ Error: No command provided. Please enter yt-dlp arguments.",
+ "executing": "🚀 Executing custom yt-dlp command",
+ "url_label": "📍 URL: {url}",
+ "args_label": "⚙️ Arguments: {command}",
+ "download_path_label": "📁 Download path: {path}",
+ "separator": "=================================================="
},
"proxy": {
"help_text": "配置下载的代理设置。留空使用直接连接。",
@@ -167,7 +180,9 @@
"geo_set_title": "已设置地理代理",
"geo_set_message": "地理验证代理已设置并保存: {proxy}",
"cleared_title": "代理设置已清除",
- "cleared_message": "所有代理设置已清除并保存。"
+ "cleared_message": "所有代理设置已清除并保存。",
+ "saved_main": "Saved main proxy: {proxy}",
+ "saved_geo": "Saved geo proxy: {proxy}"
},
"download": {
"preparing": "正在准备下载...",
@@ -330,7 +345,13 @@
"filename_format_help": "可用变量: %(title)s, %(uploader)s, %(upload_date)s, %(resolution)s, %(id)s, %(ext)s。支持标准的 yt-dlp 输出模板语法。",
"tab_general": "一般",
"tab_format": "格式",
- "tab_file": "文件"
+ "tab_file": "文件",
+ "concurrent_fragments": "并发连接数",
+ "concurrent_fragments_help": "每个下载的连接数。较高的值可以绕过限速,但如果设置得太高,可能会导致临时被封。默认值:1。",
+ "defaults_settings": "默认选择设置",
+ "default_video_quality": "默认视频分辨率 (高度):",
+ "default_subtitle_language": "默认字幕语言:",
+ "defaults_help": "设置您首选的视频高度和字幕语言(用逗号分隔)。如果可用,它们将被自动选择。"
},
"main_ui": {
"url_placeholder": "输入 YouTube 视频或播放列表网址",
@@ -440,7 +461,8 @@
"generic_error": "错误:{error}",
"download_failed_return_code_conflict": "下载失败,返回码 {return_code}。这可能是由于存在多个 yt-dlp 安装导致冲突。请卸载系统中安装的 yt-dlp(例如通过 snap 或 apt),然后重启应用。",
"download_failed_return_code": "下载失败,返回码 {return_code}",
- "direct_command_error": "直接命令出错:{error}"
+ "direct_command_error": "直接命令出错:{error}",
+ "private_video": "此视频可能是私享视频。请使用自定义选项中的 cookie。"
},
"update_dialog": {
"title": "有可用更新",
@@ -455,7 +477,13 @@
"unknown": "未知播放列表",
"total_videos": "总视频数:{count}",
"display_format": "播放列表:{title} | {count}个视频",
- "select_videos_title": "选择播放列表视频"
+ "select_videos_title": "选择播放列表视频",
+ "save_as": "保存播放列表为",
+ "save_success_title": "成功",
+ "saved_successfully": "播放列表已成功保存。",
+ "save_error_title": "保存错误",
+ "no_videos_to_save": "未收集到播放列表条目!",
+ "save_error_msg": "保存播放列表失败。"
},
"subtitle_selection": {
"count_selected": "已选择{count}个"
@@ -526,14 +554,21 @@
"redownload_confirm_message": "重新下载?\n\n{title}",
"redownload_started": "已开始下载",
"no_url_error": "历史记录中未找到 URL",
- "redownload_failed": "无法开始重新下载: {error}"
+ "redownload_failed": "无法开始重新下载: {error}",
+ "loading": "Loading history..."
},
"ffmpeg": {
"installation_title": "FFmpeg 安装",
"installation_message": "YTSage 需要 FFmpeg 来处理视频。\n\n请选择下面的安装选项:",
"install_button": "安装 FFmpeg",
"manual_guide": "手动指南",
- "installation_failed": "FFmpeg 安装遇到问题。"
+ "installation_failed": "FFmpeg 安装遇到问题。",
+ "already_installed": "FFmpeg 已安装!",
+ "installation_complete": "安装完成。您可以关闭此对话框并继续使用 YTSage。",
+ "installing": "正在安装 FFmpeg... 请稍候",
+ "install_success": "FFmpeg 已成功安装!",
+ "installation_complete_close": "安装完成。您现在可以关闭此对话框并继续使用 YTSage。",
+ "try_manual": "请尝试使用手动安装指南。"
},
"ytdlp_setup": {
"required_title": "需要设置 yt-dlp",
@@ -605,4 +640,4 @@
"update_failed": "❌ 更新失败:{error}",
"check_failed": "检查更新失败。请检查您的网络连接。"
}
-}
+}
\ No newline at end of file
diff --git a/ytsage/utils/ytsage_config_manager.py b/ytsage/utils/ytsage_config_manager.py
index d1f27da..fcbe92f 100644
--- a/ytsage/utils/ytsage_config_manager.py
+++ b/ytsage/utils/ytsage_config_manager.py
@@ -87,6 +87,7 @@ class ConfigManager:
"check_app_updates": True,
"check_beta_updates": False,
"last_update_check": 0,
+ "concurrent_fragments": 1,
"language": "en",
"ytdlp_channel": "stable",
"force_output_format": False,
@@ -94,7 +95,7 @@ class ConfigManager:
"force_audio_format": False,
"preferred_audio_format": "best",
"audio_normalization": False,
- "filename_format": "%(title)s_%(resolution)s.%(ext)s",
+ "filename_format": "%(title)s_%(resolution)s_[%(id)s].%(ext)s",
"cached_versions": {
"ytdlp": {"version": None, "path": None, "last_check": 0, "path_mtime": 0},
"ffmpeg": {"version": None, "path": None, "last_check": 0, "path_mtime": 0},