Merge cookie/custom dialogs; add trim dialog

This commit is contained in:
Your Name
2025-06-04 18:46:39 +03:00
parent b4d1254c64
commit 6bd6ba8dd1
+499 -2
View File
@@ -5,7 +5,7 @@ from PySide6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
QHBoxLayout, QLineEdit, QPushButton, QTableWidget,
QTableWidgetItem, QProgressBar, QLabel, QFileDialog,
QHeaderView, QStyle, QStyleFactory, QComboBox, QTextEdit, QDialog, QPlainTextEdit, QCheckBox, QButtonGroup, QListWidget,
QListWidgetItem, QDialogButtonBox, QScrollArea, QGroupBox)
QListWidgetItem, QDialogButtonBox, QScrollArea, QGroupBox, QTabWidget)
from PySide6.QtCore import Qt, Signal, QObject, QThread, QProcess
from PySide6.QtGui import QIcon, QPalette, QColor, QPixmap
import requests
@@ -1296,4 +1296,501 @@ class DownloadSettingsDialog(QDialog): # Renamed class
def get_selected_unit_index(self):
"""Returns the index of the selected speed limit unit."""
return self.speed_limit_unit.currentIndex()
return self.speed_limit_unit.currentIndex()
# === New CustomOptions Dialog combining Cookies and Custom Commands ===
class CustomOptionsDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.parent = parent
self.setWindowTitle('Custom Options')
self.setMinimumSize(600, 500)
layout = QVBoxLayout(self)
# Create tab widget to organize content
self.tab_widget = QTabWidget()
layout.addWidget(self.tab_widget)
# === Cookies Tab ===
cookies_tab = QWidget()
cookies_layout = QVBoxLayout(cookies_tab)
# Help text
help_text = QLabel(
"Select the Netscape-format cookies file for logging in.\n"
"This allows downloading of private videos and premium quality audio."
)
help_text.setWordWrap(True)
help_text.setStyleSheet("color: #999999; padding: 10px;")
cookies_layout.addWidget(help_text)
# File path input and browse button
path_layout = QHBoxLayout()
self.cookie_path_input = QLineEdit()
self.cookie_path_input.setPlaceholderText("Path to cookies file (Netscape format)")
if hasattr(parent, 'cookie_file_path') and parent.cookie_file_path:
self.cookie_path_input.setText(parent.cookie_file_path)
path_layout.addWidget(self.cookie_path_input)
self.browse_button = QPushButton("Browse")
self.browse_button.clicked.connect(self.browse_cookie_file)
path_layout.addWidget(self.browse_button)
cookies_layout.addLayout(path_layout) # Add the horizontal layout to cookies layout
# Status indicator for cookies
self.cookie_status = QLabel("")
self.cookie_status.setStyleSheet("color: #999999; font-style: italic;")
cookies_layout.addWidget(self.cookie_status)
cookies_layout.addStretch()
# === Custom Command Tab ===
command_tab = QWidget()
command_layout = QVBoxLayout(command_tab)
# Help text
cmd_help_text = QLabel(
"Enter custom yt-dlp commands below. The URL will be automatically appended.\n"
"Example: --extract-audio --audio-format mp3 --audio-quality 0\n"
"Note: Download path and output template will be preserved."
)
cmd_help_text.setWordWrap(True)
cmd_help_text.setStyleSheet("color: #999999; padding: 10px;")
command_layout.addWidget(cmd_help_text)
# Add SponsorBlock checkbox
self.sponsorblock_checkbox = QCheckBox("Remove Sponsor Segments")
self.sponsorblock_checkbox.setStyleSheet("""
QCheckBox {
color: #ffffff;
padding: 5px;
margin-left: 0px;
}
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;
}
""")
command_layout.addWidget(self.sponsorblock_checkbox)
# Command input
self.command_input = QPlainTextEdit()
self.command_input.setPlaceholderText("Enter yt-dlp arguments...")
self.command_input.setStyleSheet("""
QPlainTextEdit {
background-color: #1d1e22;
color: #ffffff;
border: 2px solid #1d1e22;
border-radius: 4px;
padding: 8px;
font-family: Consolas, monospace;
}
""")
command_layout.addWidget(self.command_input)
# Run command button
self.run_btn = QPushButton("Run Command")
self.run_btn.clicked.connect(self.run_custom_command)
command_layout.addWidget(self.run_btn)
# Log output
self.log_output = QTextEdit()
self.log_output.setReadOnly(True)
self.log_output.setStyleSheet("""
QTextEdit {
background-color: #1d1e22;
color: #ffffff;
border: 2px solid #1d1e22;
border-radius: 4px;
padding: 8px;
font-family: Consolas, monospace;
font-size: 12px;
}
""")
command_layout.addWidget(self.log_output)
# Add tabs to the tab widget
self.tab_widget.addTab(cookies_tab, "Login with Cookies")
self.tab_widget.addTab(command_tab, "Custom Command")
# Dialog buttons
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
button_box.accepted.connect(self.accept)
button_box.rejected.connect(self.reject)
layout.addWidget(button_box)
# Apply global styles
self.setStyleSheet("""
QDialog {
background-color: #15181b;
}
QTabWidget::pane {
border: 1px solid #3d3d3d;
background-color: #15181b;
}
QTabBar::tab {
background-color: #1d1e22;
color: #ffffff;
padding: 8px 12px;
border: 1px solid #3d3d3d;
border-bottom: none;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
QTabBar::tab:selected {
background-color: #c90000;
}
QTabBar::tab:hover:!selected {
background-color: #2a2d36;
}
QLabel {
color: #ffffff;
}
QLineEdit {
padding: 8px;
border: 2px solid #1b2021;
border-radius: 4px;
background-color: #1b2021;
color: #ffffff;
}
QPushButton {
padding: 8px 15px;
background-color: #c90000;
border: none;
border-radius: 4px;
color: white;
font-weight: bold;
}
QPushButton:hover {
background-color: #a50000;
}
""")
def browse_cookie_file(self):
# Open file dialog to select cookie file
file_dialog = QFileDialog(self)
file_dialog.setFileMode(QFileDialog.ExistingFile)
file_dialog.setNameFilter("Cookies files (*.txt *.lwp)") # Assuming common cookie file extensions
if file_dialog.exec():
selected_files = file_dialog.selectedFiles()
if selected_files:
self.cookie_path_input.setText(selected_files[0])
self.cookie_status.setText("Cookie file selected - Click OK to apply")
self.cookie_status.setStyleSheet("color: #00cc00; font-style: italic;")
def get_cookie_file_path(self):
# Return the selected cookie file path if it's not empty
path = self.cookie_path_input.text().strip()
if path and os.path.exists(path):
return path
return None
def run_custom_command(self):
url = self.parent.url_input.text().strip()
if not url:
self.log_output.append("Error: No URL provided")
return
command = self.command_input.toPlainText().strip()
# Get download path from parent
path = self.parent.last_path
self.log_output.clear()
self.log_output.append(f"Running command with URL: {url}")
self.run_btn.setEnabled(False)
# Start command in thread
import threading
threading.Thread(target=self._run_command_thread,
args=(command, url, path),
daemon=True).start()
def _run_command_thread(self, command, url, path):
try:
class CommandLogger:
def debug(self, msg):
from PySide6.QtCore import QMetaObject, Qt, Q_ARG
QMetaObject.invokeMethod(
self.dialog.log_output, "append", Qt.ConnectionType.QueuedConnection,
Q_ARG(str, msg)
)
def warning(self, msg):
from PySide6.QtCore import QMetaObject, Qt, Q_ARG
QMetaObject.invokeMethod(
self.dialog.log_output, "append", Qt.ConnectionType.QueuedConnection,
Q_ARG(str, f"Warning: {msg}")
)
def error(self, msg):
from PySide6.QtCore import QMetaObject, Qt, Q_ARG
QMetaObject.invokeMethod(
self.dialog.log_output, "append", Qt.ConnectionType.QueuedConnection,
Q_ARG(str, f"Error: {msg}")
)
def __init__(self, dialog):
self.dialog = dialog
# Split command into arguments
args = command.split()
# Base options
ydl_opts = {
'logger': CommandLogger(self),
'paths': {'home': path},
'debug_printout': True,
'postprocessors': []
}
# Add SponsorBlock if selected
if self.sponsorblock_checkbox.isChecked():
ydl_opts['postprocessors'].append({
'key': 'SponsorBlock',
'categories': ['sponsor', 'intro', 'outro', 'selfpromo', 'preview', 'filler']
})
# Parse additional options
yt_dlp_path = get_yt_dlp_path()
base_cmd = [yt_dlp_path] + args + [url]
# Show the full command
from PySide6.QtCore import QMetaObject, Qt, Q_ARG
QMetaObject.invokeMethod(
self.log_output, "append", Qt.ConnectionType.QueuedConnection,
Q_ARG(str, f"Full command: {' '.join(base_cmd)}")
)
# Run the command
proc = subprocess.Popen(
base_cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
encoding='utf-8',
errors='replace'
)
# Stream output
for line in proc.stdout:
QMetaObject.invokeMethod(
self.log_output, "append", Qt.ConnectionType.QueuedConnection,
Q_ARG(str, line.rstrip())
)
ret = proc.wait()
if ret != 0:
QMetaObject.invokeMethod(
self.log_output, "append", Qt.ConnectionType.QueuedConnection,
Q_ARG(str, f"Command exited with code {ret}")
)
else:
QMetaObject.invokeMethod(
self.log_output, "append", Qt.ConnectionType.QueuedConnection,
Q_ARG(str, "Command completed successfully")
)
except Exception as e:
from PySide6.QtCore import QMetaObject, Qt, Q_ARG
QMetaObject.invokeMethod(
self.log_output, "append", Qt.ConnectionType.QueuedConnection,
Q_ARG(str, f"Error: {str(e)}")
)
finally:
# Re-enable the run button
QMetaObject.invokeMethod(
self.run_btn, "setEnabled", Qt.ConnectionType.QueuedConnection,
Q_ARG(bool, True)
)
# === End of CustomOptionsDialog ===
# === Time Range Selection Dialog ===
class TimeRangeDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.parent = parent
self.setWindowTitle('Download Video Section')
self.setMinimumWidth(400)
layout = QVBoxLayout(self)
# Help text explaining the feature
help_text = QLabel(
"Download only specific parts of a video by specifying time ranges.\n"
"Use HH:MM:SS format or seconds. Leave start or end empty to download from beginning or to end."
)
help_text.setWordWrap(True)
help_text.setStyleSheet("color: #999999; padding: 10px;")
layout.addWidget(help_text)
# Time range section
time_group = QGroupBox("Time Range")
time_layout = QVBoxLayout()
# Start time row
start_layout = QHBoxLayout()
start_layout.addWidget(QLabel("Start Time:"))
self.start_time_input = QLineEdit()
self.start_time_input.setPlaceholderText("00:00:00 (or leave empty for start)")
start_layout.addWidget(self.start_time_input)
time_layout.addLayout(start_layout)
# End time row
end_layout = QHBoxLayout()
end_layout.addWidget(QLabel("End Time:"))
self.end_time_input = QLineEdit()
self.end_time_input.setPlaceholderText("00:10:00 (or leave empty for end)")
end_layout.addWidget(self.end_time_input)
time_layout.addLayout(end_layout)
time_group.setLayout(time_layout)
layout.addWidget(time_group)
# Force keyframes option
self.force_keyframes = QCheckBox("Force keyframes at cuts (better accuracy, slower)")
self.force_keyframes.setChecked(True)
self.force_keyframes.setStyleSheet("""
QCheckBox {
color: #ffffff;
padding: 5px;
}
QCheckBox::indicator {
width: 18px;
height: 18px;
border-radius: 4px;
}
QCheckBox::indicator:unchecked {
border: 2px solid #666666;
background: #1d1e22;
border-radius: 4px;
}
QCheckBox::indicator:checked {
border: 2px solid #c90000;
background: #c90000;
border-radius: 4px;
}
""")
layout.addWidget(self.force_keyframes)
# Format preview
preview_group = QGroupBox("Command Preview")
preview_layout = QVBoxLayout()
self.preview_label = QLabel("--download-sections \"*-\"")
self.preview_label.setStyleSheet("""
QLabel {
background-color: #1d1e22;
color: #ffffff;
border: 1px solid #3d3d3d;
border-radius: 4px;
padding: 8px;
font-family: Consolas, monospace;
}
""")
preview_layout.addWidget(self.preview_label)
preview_group.setLayout(preview_layout)
layout.addWidget(preview_group)
# Connect signals for live preview updates
self.start_time_input.textChanged.connect(self.update_preview)
self.end_time_input.textChanged.connect(self.update_preview)
self.force_keyframes.stateChanged.connect(self.update_preview)
# Buttons
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
button_box.accepted.connect(self.accept)
button_box.rejected.connect(self.reject)
layout.addWidget(button_box)
# Apply styling
self.setStyleSheet("""
QDialog {
background-color: #15181b;
}
QGroupBox {
border: 1px solid #3d3d3d;
border-radius: 4px;
margin-top: 1.5ex;
color: #ffffff;
padding: 10px;
}
QGroupBox::title {
subcontrol-origin: margin;
subcontrol-position: top left;
padding: 0 5px;
}
QLabel {
color: #ffffff;
}
QLineEdit {
padding: 8px;
border: 2px solid #1b2021;
border-radius: 4px;
background-color: #1b2021;
color: #ffffff;
}
QPushButton {
padding: 8px 15px;
background-color: #c90000;
border: none;
border-radius: 4px;
color: white;
font-weight: bold;
}
QPushButton:hover {
background-color: #a50000;
}
""")
# Initialize preview
self.update_preview()
def update_preview(self):
start = self.start_time_input.text().strip()
end = self.end_time_input.text().strip()
if start and end:
time_range = f"*{start}-{end}"
elif start:
time_range = f"*{start}-"
elif end:
time_range = f"*-{end}"
else:
time_range = "*-" # Full video
preview = f"--download-sections \"{time_range}\""
if self.force_keyframes.isChecked():
preview += " --force-keyframes-at-cuts"
self.preview_label.setText(preview)
def get_download_sections(self):
"""Returns the download sections command arguments or None if no selection made"""
start = self.start_time_input.text().strip()
end = self.end_time_input.text().strip()
if not start and not end:
return None # No selection made
if start and end:
time_range = f"*{start}-{end}"
elif start:
time_range = f"*{start}-"
elif end:
time_range = f"*-{end}"
else:
return None # Shouldn't happen but just in case
return time_range
def get_force_keyframes(self):
"""Returns whether to force keyframes at cuts"""
return self.force_keyframes.isChecked()