985ba9d864
This reverts commit 82de19c95a.
673 lines
24 KiB
Python
673 lines
24 KiB
Python
import sys
|
|
import os
|
|
import webbrowser
|
|
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)
|
|
from PySide6.QtCore import Qt, Signal, QObject, QThread, QProcess
|
|
from PySide6.QtGui import QIcon, QPalette, QColor, QPixmap
|
|
import requests
|
|
from io import BytesIO
|
|
from PIL import Image
|
|
from datetime import datetime
|
|
import json
|
|
from pathlib import Path
|
|
from packaging import version
|
|
import subprocess
|
|
import re
|
|
import yt_dlp
|
|
from ytsage_ffmpeg import auto_install_ffmpeg, check_ffmpeg_installed
|
|
|
|
from ytsage_utils import check_ffmpeg, get_yt_dlp_path, load_saved_path, save_path # Import utility functions
|
|
|
|
|
|
class LogWindow(QDialog):
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.setWindowTitle('yt-dlp Log')
|
|
self.setMinimumSize(700, 500)
|
|
|
|
layout = QVBoxLayout(self)
|
|
|
|
self.log_text = QTextEdit()
|
|
self.log_text.setReadOnly(True)
|
|
self.log_text.setStyleSheet("""
|
|
QTextEdit {
|
|
background-color: #2b2b2b;
|
|
color: #ffffff;
|
|
font-family: Consolas, monospace;
|
|
font-size: 12px;
|
|
border: 2px solid #3d3d3d;
|
|
border-radius: 4px;
|
|
}
|
|
""")
|
|
|
|
layout.addWidget(self.log_text)
|
|
|
|
def append_log(self, message):
|
|
self.log_text.append(message)
|
|
# Auto-scroll to bottom
|
|
scrollbar = self.log_text.verticalScrollBar()
|
|
scrollbar.setValue(scrollbar.maximum())
|
|
|
|
class CustomCommandDialog(QDialog):
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.parent = parent
|
|
self.setWindowTitle('Custom yt-dlp Command')
|
|
self.setMinimumSize(600, 400)
|
|
|
|
layout = QVBoxLayout(self)
|
|
|
|
# Help text
|
|
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."
|
|
)
|
|
help_text.setWordWrap(True)
|
|
help_text.setStyleSheet("color: #999999; padding: 10px;")
|
|
layout.addWidget(help_text)
|
|
|
|
# Command input
|
|
self.command_input = QPlainTextEdit()
|
|
self.command_input.setPlaceholderText("Enter yt-dlp arguments...")
|
|
self.command_input.setStyleSheet("""
|
|
QPlainTextEdit {
|
|
background-color: #363636;
|
|
color: #ffffff;
|
|
border: 2px solid #3d3d3d;
|
|
border-radius: 4px;
|
|
padding: 8px;
|
|
font-family: Consolas, monospace;
|
|
}
|
|
""")
|
|
layout.addWidget(self.command_input)
|
|
|
|
# Add SponsorBlock checkbox
|
|
self.sponsorblock_checkbox = QCheckBox("Remove Sponsor Segments")
|
|
self.sponsorblock_checkbox.setStyleSheet("""
|
|
QCheckBox {
|
|
color: #ffffff;
|
|
padding: 5px;
|
|
margin-left: 20px;
|
|
}
|
|
QCheckBox::indicator {
|
|
width: 18px;
|
|
height: 18px;
|
|
border-radius: 9px; /* Make indicator round */
|
|
}
|
|
QCheckBox::indicator:unchecked {
|
|
border: 2px solid #666666;
|
|
background: #2b2b2b;
|
|
border-radius: 9px; /* Make indicator round */
|
|
}
|
|
QCheckBox::indicator:checked {
|
|
border: 2px solid #ff0000;
|
|
background: #ff0000;
|
|
border-radius: 9px; /* Make indicator round */
|
|
}
|
|
""")
|
|
layout.insertWidget(layout.indexOf(self.command_input), self.sponsorblock_checkbox)
|
|
|
|
# Buttons
|
|
button_layout = QHBoxLayout()
|
|
|
|
self.run_btn = QPushButton("Run Command")
|
|
self.run_btn.clicked.connect(self.run_custom_command)
|
|
|
|
self.close_btn = QPushButton("Close")
|
|
self.close_btn.clicked.connect(self.close)
|
|
|
|
button_layout.addWidget(self.run_btn)
|
|
button_layout.addWidget(self.close_btn)
|
|
layout.addLayout(button_layout)
|
|
|
|
# Log output
|
|
self.log_output = QTextEdit()
|
|
self.log_output.setReadOnly(True)
|
|
self.log_output.setStyleSheet("""
|
|
QTextEdit {
|
|
background-color: #2b2b2b;
|
|
color: #ffffff;
|
|
border: 2px solid #3d3d3d;
|
|
border-radius: 4px;
|
|
padding: 8px;
|
|
font-family: Consolas, monospace;
|
|
font-size: 12px;
|
|
}
|
|
""")
|
|
layout.addWidget(self.log_output)
|
|
|
|
self.setStyleSheet("""
|
|
QDialog {
|
|
background-color: #2b2b2b;
|
|
}
|
|
QPushButton {
|
|
padding: 8px 15px;
|
|
background-color: #ff0000;
|
|
border: none;
|
|
border-radius: 4px;
|
|
color: white;
|
|
font-weight: bold;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #cc0000;
|
|
}
|
|
""")
|
|
|
|
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()
|
|
path = self.parent.path_input.text().strip()
|
|
|
|
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):
|
|
self.dialog.log_output.append(msg)
|
|
def warning(self, msg):
|
|
self.dialog.log_output.append(f"Warning: {msg}")
|
|
def error(self, msg):
|
|
self.dialog.log_output.append(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 options if enabled
|
|
if self.sponsorblock_checkbox.isChecked():
|
|
ydl_opts['postprocessors'].extend([{
|
|
'key': 'SponsorBlock',
|
|
'categories': ['sponsor', 'selfpromo', 'interaction'],
|
|
'api': 'https://sponsor.ajay.app'
|
|
}, {
|
|
'key': 'ModifyChapters',
|
|
'remove_sponsor_segments': ['sponsor', 'selfpromo', 'interaction'],
|
|
'sponsorblock_chapter_title': '[SponsorBlock]: %(category_names)l',
|
|
'force_keyframes': True
|
|
}])
|
|
|
|
# Add custom arguments
|
|
for i in range(0, len(args), 2):
|
|
if i + 1 < len(args):
|
|
key = args[i].lstrip('-').replace('-', '_')
|
|
value = args[i + 1]
|
|
try:
|
|
# Try to convert to appropriate type
|
|
if value.lower() in ('true', 'false'):
|
|
value = value.lower() == 'true'
|
|
elif value.isdigit():
|
|
value = int(value)
|
|
ydl_opts[key] = value
|
|
except:
|
|
ydl_opts[key] = value
|
|
|
|
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
|
ydl.download([url])
|
|
|
|
self.log_output.append("Command completed successfully")
|
|
|
|
except Exception as e:
|
|
self.log_output.append(f"Error: {str(e)}")
|
|
finally:
|
|
self.run_btn.setEnabled(True)
|
|
|
|
class FFmpegInstallThread(QThread):
|
|
finished = Signal(bool)
|
|
progress = Signal(str)
|
|
|
|
def run(self):
|
|
# Redirect stdout to capture progress messages
|
|
import sys
|
|
from io import StringIO
|
|
import contextlib
|
|
|
|
output = StringIO()
|
|
with contextlib.redirect_stdout(output):
|
|
success = auto_install_ffmpeg()
|
|
|
|
# Process captured output and emit progress signals
|
|
for line in output.getvalue().splitlines():
|
|
self.progress.emit(line)
|
|
|
|
self.finished.emit(success)
|
|
|
|
class FFmpegCheckDialog(QDialog):
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.setWindowTitle('Installing FFmpeg')
|
|
self.setMinimumWidth(450)
|
|
self.setMinimumHeight(250)
|
|
|
|
# Set the window icon to match the main app
|
|
self.setWindowIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_ArrowDown))
|
|
|
|
layout = QVBoxLayout(self)
|
|
layout.setSpacing(15)
|
|
|
|
# Header with icon
|
|
header_layout = QHBoxLayout()
|
|
icon_label = QLabel()
|
|
icon_label.setPixmap(self.style().standardIcon(QStyle.StandardPixmap.SP_ArrowDown).pixmap(32, 32))
|
|
header_layout.addWidget(icon_label)
|
|
|
|
header_text = QLabel("FFmpeg Installation")
|
|
header_text.setStyleSheet("font-size: 16px; font-weight: bold;")
|
|
header_layout.addWidget(header_text)
|
|
header_layout.addStretch()
|
|
layout.addLayout(header_layout)
|
|
|
|
# Message
|
|
self.message_label = QLabel(
|
|
"🎥 YTSage needs FFmpeg to process videos.\n"
|
|
"Let's set it up for you automatically!"
|
|
)
|
|
self.message_label.setWordWrap(True)
|
|
self.message_label.setStyleSheet("font-size: 13px;")
|
|
layout.addWidget(self.message_label)
|
|
|
|
# Progress label with cool emojis
|
|
self.progress_label = QLabel("")
|
|
self.progress_label.setWordWrap(True)
|
|
self.progress_label.setStyleSheet("""
|
|
QLabel {
|
|
background-color: #1e1e1e;
|
|
border-radius: 5px;
|
|
padding: 10px;
|
|
font-family: 'Consolas', monospace;
|
|
font-size: 12px;
|
|
}
|
|
""")
|
|
self.progress_label.hide()
|
|
layout.addWidget(self.progress_label)
|
|
|
|
# Buttons container
|
|
button_layout = QHBoxLayout()
|
|
|
|
# Install button
|
|
self.install_btn = QPushButton("Install FFmpeg")
|
|
self.install_btn.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_ArrowDown))
|
|
self.install_btn.clicked.connect(self.start_installation)
|
|
button_layout.addWidget(self.install_btn)
|
|
|
|
# Manual install button
|
|
self.manual_btn = QPushButton("Manual Guide")
|
|
self.manual_btn.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_DialogHelpButton))
|
|
self.manual_btn.clicked.connect(lambda: webbrowser.open('https://github.com/oop7/ffmpeg-install-guide'))
|
|
button_layout.addWidget(self.manual_btn)
|
|
|
|
# Close button
|
|
self.close_btn = QPushButton("Close")
|
|
self.close_btn.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_DialogCloseButton))
|
|
self.close_btn.clicked.connect(self.close)
|
|
button_layout.addWidget(self.close_btn)
|
|
|
|
layout.addLayout(button_layout)
|
|
|
|
# Style the dialog
|
|
self.setStyleSheet("""
|
|
QDialog {
|
|
background-color: #2b2b2b;
|
|
}
|
|
QLabel {
|
|
color: #ffffff;
|
|
}
|
|
QPushButton {
|
|
padding: 8px 15px;
|
|
background-color: #3d3d3d;
|
|
border: none;
|
|
border-radius: 4px;
|
|
color: white;
|
|
font-weight: bold;
|
|
margin: 5px;
|
|
min-width: 100px;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #4d4d4d;
|
|
}
|
|
QPushButton:disabled {
|
|
background-color: #2d2d2d;
|
|
color: #666666;
|
|
}
|
|
""")
|
|
|
|
# Initialize installation thread
|
|
self.install_thread = None
|
|
|
|
def start_installation(self):
|
|
self.install_btn.setEnabled(False)
|
|
self.manual_btn.setEnabled(False)
|
|
self.close_btn.setEnabled(False)
|
|
|
|
# Check if FFmpeg is already installed
|
|
if check_ffmpeg_installed():
|
|
self.message_label.setText("🎉 FFmpeg is already installed!")
|
|
self.progress_label.setText("✅ You can close this dialog and continue using YTSage.")
|
|
self.install_btn.hide()
|
|
self.manual_btn.hide()
|
|
self.close_btn.setEnabled(True)
|
|
return
|
|
|
|
self.message_label.setText("🚀 Installing FFmpeg... Hold tight!")
|
|
self.progress_label.show()
|
|
|
|
self.install_thread = FFmpegInstallThread()
|
|
self.install_thread.finished.connect(self.installation_finished)
|
|
self.install_thread.progress.connect(self.update_progress)
|
|
self.install_thread.start()
|
|
|
|
def update_progress(self, message):
|
|
self.progress_label.setText(message)
|
|
|
|
def installation_finished(self, success):
|
|
if success:
|
|
self.message_label.setText("🎉 FFmpeg has been installed successfully!")
|
|
self.progress_label.setText("✅ You're all set! You can now close this dialog and continue using YTSage.")
|
|
self.install_btn.hide()
|
|
self.manual_btn.hide()
|
|
else:
|
|
self.message_label.setText("❌ Oops! FFmpeg installation encountered an issue.")
|
|
self.progress_label.setText("💡 Try using the manual installation guide instead.")
|
|
self.install_btn.setEnabled(True)
|
|
self.manual_btn.setEnabled(True)
|
|
|
|
self.close_btn.setEnabled(True)
|
|
|
|
class YTDLPUpdateDialog(QDialog):
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.setWindowTitle("Update yt-dlp")
|
|
self.setMinimumWidth(400)
|
|
|
|
layout = QVBoxLayout(self)
|
|
|
|
# Status label
|
|
self.status_label = QLabel("Checking for updates...")
|
|
self.status_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
layout.addWidget(self.status_label)
|
|
|
|
# Progress bar
|
|
self.progress_bar = QProgressBar()
|
|
self.progress_bar.hide() # Hide initially
|
|
layout.addWidget(self.progress_bar)
|
|
|
|
# Buttons
|
|
button_layout = QHBoxLayout()
|
|
self.update_btn = QPushButton("Update")
|
|
self.update_btn.clicked.connect(self.perform_update)
|
|
self.update_btn.setEnabled(False)
|
|
|
|
self.close_btn = QPushButton("Close")
|
|
self.close_btn.clicked.connect(self.close)
|
|
|
|
button_layout.addWidget(self.update_btn)
|
|
button_layout.addWidget(self.close_btn)
|
|
layout.addLayout(button_layout)
|
|
|
|
# Style
|
|
self.setStyleSheet("""
|
|
QDialog {
|
|
background-color: #2b2b2b;
|
|
}
|
|
QLabel {
|
|
color: #ffffff;
|
|
font-size: 12px;
|
|
padding: 10px;
|
|
}
|
|
QPushButton {
|
|
padding: 8px 15px;
|
|
background-color: #ff0000;
|
|
border: none;
|
|
border-radius: 4px;
|
|
color: white;
|
|
font-weight: bold;
|
|
min-width: 100px;
|
|
}
|
|
QPushButton:disabled {
|
|
background-color: #666666;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #cc0000;
|
|
}
|
|
QProgressBar {
|
|
border: 2px solid #3d3d3d;
|
|
border-radius: 4px;
|
|
text-align: center;
|
|
color: white;
|
|
background-color: #363636;
|
|
height: 25px;
|
|
}
|
|
QProgressBar::chunk {
|
|
background-color: #ff0000;
|
|
border-radius: 2px;
|
|
}
|
|
""")
|
|
|
|
# Start version check
|
|
self.check_version()
|
|
|
|
def check_version(self):
|
|
try:
|
|
# Get current version using yt-dlp command
|
|
import subprocess
|
|
import sys
|
|
import os
|
|
|
|
# Get the yt-dlp executable path
|
|
if getattr(sys, 'frozen', False):
|
|
if sys.platform == 'win32':
|
|
yt_dlp_path = os.path.join(os.path.dirname(sys.executable), 'yt-dlp.exe')
|
|
else:
|
|
yt_dlp_path = os.path.join(os.path.dirname(sys.executable), 'yt-dlp')
|
|
else:
|
|
yt_dlp_path = 'yt-dlp'
|
|
|
|
# Get current version
|
|
try:
|
|
result = subprocess.run([yt_dlp_path, '--version'],
|
|
capture_output=True,
|
|
text=True)
|
|
current_version = result.stdout.strip()
|
|
except Exception:
|
|
import yt_dlp
|
|
current_version = yt_dlp.version.__version__
|
|
|
|
# Get latest version from PyPI
|
|
import requests
|
|
response = requests.get("https://pypi.org/pypi/yt-dlp/json")
|
|
latest_version = response.json()["info"]["version"]
|
|
|
|
# Clean up version strings
|
|
current_version = current_version.replace('_', '.')
|
|
latest_version = latest_version.replace('_', '.')
|
|
|
|
# Compare versions
|
|
from packaging import version
|
|
try:
|
|
current_ver = version.parse(current_version)
|
|
latest_ver = version.parse(latest_version)
|
|
|
|
if current_ver < latest_ver:
|
|
self.status_label.setText(f"Update available!\nCurrent version: {current_version}\nLatest version: {latest_version}")
|
|
self.update_btn.setEnabled(True)
|
|
else:
|
|
self.status_label.setText(f"yt-dlp is up to date (version {current_version})")
|
|
self.update_btn.setEnabled(False)
|
|
except version.InvalidVersion:
|
|
# If version parsing fails, do a simple string comparison
|
|
if current_version != latest_version:
|
|
self.status_label.setText(f"Update available!\nCurrent version: {current_version}\nLatest version: {latest_version}")
|
|
self.update_btn.setEnabled(True)
|
|
else:
|
|
self.status_label.setText(f"yt-dlp is up to date (version {current_version})")
|
|
self.update_btn.setEnabled(False)
|
|
|
|
except Exception as e:
|
|
self.status_label.setText(f"Error checking version: {str(e)}")
|
|
self.update_btn.setEnabled(False)
|
|
|
|
def perform_update(self):
|
|
try:
|
|
self.update_btn.setEnabled(False)
|
|
self.close_btn.setEnabled(False)
|
|
self.status_label.setText("Updating yt-dlp...")
|
|
self.progress_bar.setRange(0, 0)
|
|
self.progress_bar.show()
|
|
|
|
# Get the yt-dlp path
|
|
if getattr(sys, 'frozen', False):
|
|
if sys.platform == 'win32':
|
|
yt_dlp_path = os.path.join(os.path.dirname(sys.executable), 'yt-dlp.exe')
|
|
else:
|
|
yt_dlp_path = os.path.join(os.path.dirname(sys.executable), 'yt-dlp')
|
|
else:
|
|
yt_dlp_path = 'yt-dlp'
|
|
|
|
# Create a QProcess for updating
|
|
process = QProcess()
|
|
process.setWorkingDirectory(os.path.dirname(yt_dlp_path))
|
|
|
|
if sys.platform == 'win32':
|
|
# For Windows, use pip to update
|
|
python_path = os.path.join(os.path.dirname(sys.executable), 'python.exe')
|
|
if not os.path.exists(python_path):
|
|
python_path = sys.executable
|
|
|
|
process.start(python_path, ['-m', 'pip', 'install', '--upgrade', '--no-cache-dir', 'yt-dlp'])
|
|
else:
|
|
# For Unix systems
|
|
process.start('pip', ['install', '--upgrade', '--no-cache-dir', 'yt-dlp'])
|
|
|
|
process.waitForFinished()
|
|
|
|
if process.exitCode() == 0:
|
|
self.status_label.setText("Update completed successfully!\nPlease restart the application.")
|
|
self.check_version() # Recheck version after update
|
|
else:
|
|
error = process.readAllStandardError().data().decode()
|
|
self.status_label.setText(f"Update failed: {error}")
|
|
|
|
self.progress_bar.setRange(0, 100)
|
|
self.progress_bar.setValue(100)
|
|
self.close_btn.setEnabled(True)
|
|
|
|
except Exception as e:
|
|
self.status_label.setText(f"Update failed: {str(e)}")
|
|
self.close_btn.setEnabled(True)
|
|
self.progress_bar.hide()
|
|
|
|
class AboutDialog(QDialog):
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.setWindowTitle('About YTSage')
|
|
self.setMinimumWidth(500)
|
|
|
|
layout = QVBoxLayout(self)
|
|
|
|
# App title and version
|
|
title_label = QLabel("YTSage")
|
|
title_label.setStyleSheet("""
|
|
font-size: 24px;
|
|
font-weight: bold;
|
|
color: #ff0000;
|
|
padding: 10px;
|
|
""")
|
|
title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
layout.addWidget(title_label)
|
|
|
|
version_label = QLabel(f"Version {parent.version}")
|
|
version_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
layout.addWidget(version_label)
|
|
|
|
# Description
|
|
description = QLabel(
|
|
"A modern YouTube downloader with a clean interface.\n"
|
|
"Download videos in any quality, extract audio, fetch subtitles,\n"
|
|
"and view video metadata. Built with yt-dlp for reliable performance."
|
|
)
|
|
description.setWordWrap(True)
|
|
description.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
layout.addWidget(description)
|
|
|
|
# Features list
|
|
features_label = QLabel(
|
|
"Key Features:\n"
|
|
"• Smart video quality selection\n"
|
|
"• Audio extraction\n"
|
|
"• Subtitle support\n"
|
|
"• Playlist downloads\n"
|
|
"• Real-time progress tracking\n"
|
|
"• Custom command support\n"
|
|
"• SponsorBlock Integration\n"
|
|
"• Save Thumbnail\n"
|
|
"• One-click updates"
|
|
)
|
|
features_label.setStyleSheet("padding: 10px;")
|
|
layout.addWidget(features_label)
|
|
|
|
# Credits
|
|
credits_label = QLabel(
|
|
"Powered by:\n"
|
|
"• yt-dlp\n"
|
|
"• PySide6\n"
|
|
"• FFmpeg"
|
|
)
|
|
credits_label.setStyleSheet("padding: 10px;")
|
|
layout.addWidget(credits_label)
|
|
|
|
# GitHub link
|
|
github_btn = QPushButton("Visit GitHub Repository")
|
|
github_btn.clicked.connect(lambda: webbrowser.open('https://github.com/oop7/YTSage'))
|
|
layout.addWidget(github_btn)
|
|
|
|
# Close button
|
|
close_btn = QPushButton("Close")
|
|
close_btn.clicked.connect(self.close)
|
|
layout.addWidget(close_btn)
|
|
|
|
# Style the dialog
|
|
self.setStyleSheet("""
|
|
QDialog {
|
|
background-color: #2b2b2b;
|
|
}
|
|
QLabel {
|
|
color: #ffffff;
|
|
font-size: 12px;
|
|
}
|
|
QPushButton {
|
|
padding: 8px 15px;
|
|
background-color: #ff0000;
|
|
border: none;
|
|
border-radius: 4px;
|
|
color: white;
|
|
font-weight: bold;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #cc0000;
|
|
}
|
|
""") |