Add configurable output filename format
Introduce a new filename_format setting and UI to control yt-dlp output templates. ConfigManager now includes a default filename_format (%(title)s_%(resolution)s.%(ext)s). The DownloadSettingsDialog exposes a text input and help text for the format and saves the value to ConfigManager. DownloadThread now accepts a filename_format argument and uses it when building output templates for single videos and playlists. YTSageApp reads the config and passes the filename format into the download thread. Added corresponding English language strings.
This commit is contained in:
@@ -72,6 +72,7 @@ class DownloadThread(QThread):
|
|||||||
preferred_output_format="mp4",
|
preferred_output_format="mp4",
|
||||||
force_audio_format=False,
|
force_audio_format=False,
|
||||||
preferred_audio_format="best",
|
preferred_audio_format="best",
|
||||||
|
filename_format=None,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.url = url
|
self.url = url
|
||||||
@@ -99,6 +100,7 @@ class DownloadThread(QThread):
|
|||||||
self.preferred_output_format = preferred_output_format
|
self.preferred_output_format = preferred_output_format
|
||||||
self.force_audio_format = force_audio_format
|
self.force_audio_format = force_audio_format
|
||||||
self.preferred_audio_format = preferred_audio_format
|
self.preferred_audio_format = preferred_audio_format
|
||||||
|
self.filename_format = filename_format
|
||||||
self.paused: bool = False
|
self.paused: bool = False
|
||||||
self.cancelled: bool = False
|
self.cancelled: bool = False
|
||||||
self.process: Optional[subprocess.Popen] = None
|
self.process: Optional[subprocess.Popen] = None
|
||||||
@@ -274,11 +276,14 @@ class DownloadThread(QThread):
|
|||||||
# Use string concatenation instead of Path.joinpath to avoid Path object issues
|
# Use string concatenation instead of Path.joinpath to avoid Path object issues
|
||||||
base_path: str = self.path.as_posix()
|
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"
|
||||||
|
|
||||||
if self.is_playlist:
|
if self.is_playlist:
|
||||||
# Create output template with playlist subfolder
|
# Create output template with playlist subfolder
|
||||||
output_template: str = f"{base_path}/%(playlist_title)s/%(title)s_%(resolution)s.%(ext)s"
|
output_template: str = f"{base_path}/%(playlist_title)s/{filename_part}"
|
||||||
else:
|
else:
|
||||||
output_template: str = f"{base_path}/%(title)s_%(resolution)s.%(ext)s"
|
output_template: str = f"{base_path}/{filename_part}"
|
||||||
|
|
||||||
cmd.extend(["-o", str(output_template)])
|
cmd.extend(["-o", str(output_template)])
|
||||||
|
|
||||||
|
|||||||
@@ -280,6 +280,25 @@ class DownloadSettingsDialog(QDialog):
|
|||||||
audio_format_group_box.setLayout(audio_format_layout)
|
audio_format_group_box.setLayout(audio_format_layout)
|
||||||
layout.addWidget(audio_format_group_box)
|
layout.addWidget(audio_format_group_box)
|
||||||
|
|
||||||
|
# --- Filename Format Section ---
|
||||||
|
filename_format_group_box = QGroupBox(_("settings.filename_format"))
|
||||||
|
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_input = QLineEdit(self.filename_format_value)
|
||||||
|
self.filename_format_input.setPlaceholderText("%(title)s_%(resolution)s.%(ext)s")
|
||||||
|
filename_layout.addWidget(self.filename_format_input)
|
||||||
|
|
||||||
|
filename_help_label = QLabel(_("settings.filename_format_help"))
|
||||||
|
filename_help_label.setWordWrap(True)
|
||||||
|
filename_help_label.setStyleSheet("color: #cccccc; margin: 5px; font-size: 10px;")
|
||||||
|
filename_layout.addWidget(filename_help_label)
|
||||||
|
|
||||||
|
filename_format_group_box.setLayout(filename_layout)
|
||||||
|
layout.addWidget(filename_format_group_box)
|
||||||
|
|
||||||
# Dialog buttons (OK/Cancel)
|
# Dialog buttons (OK/Cancel)
|
||||||
button_box = QDialogButtonBox()
|
button_box = QDialogButtonBox()
|
||||||
ok_button = button_box.addButton(_("buttons.ok"), QDialogButtonBox.ButtonRole.AcceptRole)
|
ok_button = button_box.addButton(_("buttons.ok"), QDialogButtonBox.ButtonRole.AcceptRole)
|
||||||
@@ -332,6 +351,10 @@ class DownloadSettingsDialog(QDialog):
|
|||||||
audio_format_map = {0: "best", 1: "aac", 2: "mp3", 3: "flac", 4: "wav", 5: "opus", 6: "m4a", 7: "vorbis"}
|
audio_format_map = {0: "best", 1: "aac", 2: "mp3", 3: "flac", 4: "wav", 5: "opus", 6: "m4a", 7: "vorbis"}
|
||||||
return audio_format_map.get(self.audio_format_combo.currentIndex(), "best")
|
return audio_format_map.get(self.audio_format_combo.currentIndex(), "best")
|
||||||
|
|
||||||
|
def get_filename_format(self) -> str:
|
||||||
|
"""Returns the filename format string."""
|
||||||
|
return self.filename_format_input.text().strip()
|
||||||
|
|
||||||
def _create_styled_message_box(self, icon, title, text) -> QMessageBox:
|
def _create_styled_message_box(self, icon, title, text) -> QMessageBox:
|
||||||
"""Create a styled QMessageBox that matches the app theme."""
|
"""Create a styled QMessageBox that matches the app theme."""
|
||||||
msg_box = QMessageBox(self)
|
msg_box = QMessageBox(self)
|
||||||
@@ -382,6 +405,11 @@ class DownloadSettingsDialog(QDialog):
|
|||||||
ConfigManager.set("force_audio_format", force_audio_format)
|
ConfigManager.set("force_audio_format", force_audio_format)
|
||||||
ConfigManager.set("preferred_audio_format", preferred_audio_format)
|
ConfigManager.set("preferred_audio_format", preferred_audio_format)
|
||||||
|
|
||||||
|
# Save filename format
|
||||||
|
filename_format = self.get_filename_format()
|
||||||
|
if filename_format:
|
||||||
|
ConfigManager.set("filename_format", filename_format)
|
||||||
|
|
||||||
QMessageBox.information(
|
QMessageBox.information(
|
||||||
self,
|
self,
|
||||||
_("settings.settings_saved_title"),
|
_("settings.settings_saved_title"),
|
||||||
|
|||||||
@@ -678,6 +678,9 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin, AnalysisMixin):
|
|||||||
logger.warning(f"Thumbnail download failed: {e}", exc_info=True)
|
logger.warning(f"Thumbnail download failed: {e}", exc_info=True)
|
||||||
# Optionally inform the user, but don't stop the main download
|
# Optionally inform the user, but don't stop the main download
|
||||||
|
|
||||||
|
# Get filename format from config
|
||||||
|
filename_format = ConfigManager.get("filename_format")
|
||||||
|
|
||||||
# Create download thread with resolution in output template
|
# Create download thread with resolution in output template
|
||||||
self.download_thread = DownloadThread(
|
self.download_thread = DownloadThread(
|
||||||
url=url,
|
url=url,
|
||||||
@@ -705,6 +708,7 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin, AnalysisMixin):
|
|||||||
preferred_output_format=self.preferred_output_format, # Pass preferred format
|
preferred_output_format=self.preferred_output_format, # Pass preferred format
|
||||||
force_audio_format=self.force_audio_format, # Pass force audio format setting
|
force_audio_format=self.force_audio_format, # Pass force audio format setting
|
||||||
preferred_audio_format=self.preferred_audio_format, # Pass preferred audio format
|
preferred_audio_format=self.preferred_audio_format, # Pass preferred audio format
|
||||||
|
filename_format=filename_format, # Pass the filename format
|
||||||
)
|
)
|
||||||
|
|
||||||
# Connect signals
|
# Connect signals
|
||||||
|
|||||||
@@ -318,6 +318,8 @@
|
|||||||
"format_mkv": "MKV (Feature-rich)",
|
"format_mkv": "MKV (Feature-rich)",
|
||||||
"audio_format_settings": "Audio Format Settings",
|
"audio_format_settings": "Audio Format Settings",
|
||||||
"force_audio_format": "Force audio format for audio-only downloads",
|
"force_audio_format": "Force audio format for audio-only downloads",
|
||||||
|
"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.",
|
||||||
"preferred_audio_format": "Preferred audio format:",
|
"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.",
|
"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)",
|
"audio_format_best": "Best (No conversion)",
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ class ConfigManager:
|
|||||||
"preferred_output_format": "mp4",
|
"preferred_output_format": "mp4",
|
||||||
"force_audio_format": False,
|
"force_audio_format": False,
|
||||||
"preferred_audio_format": "best",
|
"preferred_audio_format": "best",
|
||||||
|
"filename_format": "%(title)s_%(resolution)s.%(ext)s",
|
||||||
"cached_versions": {
|
"cached_versions": {
|
||||||
"ytdlp": {"version": None, "path": None, "last_check": 0, "path_mtime": 0},
|
"ytdlp": {"version": None, "path": None, "last_check": 0, "path_mtime": 0},
|
||||||
"ffmpeg": {"version": None, "path": None, "last_check": 0, "path_mtime": 0},
|
"ffmpeg": {"version": None, "path": None, "last_check": 0, "path_mtime": 0},
|
||||||
|
|||||||
Reference in New Issue
Block a user