1068 lines
46 KiB
Python
1068 lines
46 KiB
Python
#!/usr/bin/env -S uv run --script
|
|
# /// script
|
|
# requires-python = ">=3.11"
|
|
# dependencies = [
|
|
# "PyQt5==5.15.9",
|
|
# "PyInstaller==5.13.2",
|
|
# "requests==2.31.0",
|
|
# ]
|
|
# ///
|
|
import sys
|
|
import os
|
|
import json
|
|
import subprocess
|
|
import re
|
|
import urllib.request
|
|
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
|
QLabel, QLineEdit, QPushButton, QComboBox, QTextEdit,
|
|
QFileDialog, QMessageBox, QListWidget, QDialog, QFormLayout,
|
|
QDialogButtonBox, QInputDialog, QGroupBox, QCheckBox, QTabWidget)
|
|
from PyQt5.QtCore import QThread, pyqtSignal, Qt
|
|
|
|
# Hilfsfunktionen für den Ressourcenpfad
|
|
def get_base_path():
|
|
"""Gibt den Basispfad für Ressourcen zurück, funktioniert sowohl für PyInstaller als auch für reguläre Ausführung."""
|
|
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
|
|
# PyInstaller-Bundled-Modus
|
|
return sys._MEIPASS
|
|
else:
|
|
# Regulärer Modus
|
|
return os.path.dirname(os.path.abspath(__file__))
|
|
|
|
def get_user_data_dir():
|
|
if getattr(sys, 'frozen', False):
|
|
return os.path.dirname(sys.executable)
|
|
else:
|
|
return os.path.dirname(os.path.abspath(__file__))
|
|
|
|
def get_user_presets_dir():
|
|
# Presets-Ordner neben der EXE (bzw. Script)
|
|
base = get_user_data_dir()
|
|
path = os.path.join(base, "presets")
|
|
if not os.path.exists(path):
|
|
os.makedirs(path, exist_ok=True)
|
|
return path
|
|
|
|
CONFIG_FILE = os.path.join(get_user_data_dir(), "config.json")
|
|
PRESETS_DIR = os.path.join(get_base_path(), "presets") # Nur für Lesezugriff auf mitgelieferte Presets
|
|
DEFAULT_CONFIG = {
|
|
"output_dir": "",
|
|
"use_local_ytdlp": True,
|
|
"last_preset": "",
|
|
"presets": [],
|
|
"ytdlp_flags": {
|
|
"ignore_config": False,
|
|
"remux_mkv": False,
|
|
"embed_metadata": False
|
|
},
|
|
"hide_default_presets": False
|
|
}
|
|
|
|
# Template-Variablen für Serien
|
|
SERIES_TEMPLATE = "{series} S{season}E{episode}{extension}"
|
|
|
|
class DownloadThread(QThread):
|
|
update_signal = pyqtSignal(str)
|
|
finished_signal = pyqtSignal(bool, str)
|
|
|
|
def __init__(self, url, output_dir, cmd_args, use_local_ytdlp, output_filename=None):
|
|
super().__init__()
|
|
self.url = url
|
|
self.output_dir = output_dir
|
|
self.cmd_args = cmd_args
|
|
self.use_local_ytdlp = use_local_ytdlp
|
|
self.output_filename = output_filename
|
|
|
|
def run(self):
|
|
try:
|
|
# Bestimme den Pfad zu yt-dlp
|
|
if self.use_local_ytdlp:
|
|
ytdlp_path = os.path.join(get_base_path(), "bin", "yt-dlp.exe")
|
|
if not os.path.exists(ytdlp_path):
|
|
ytdlp_path = "yt-dlp" # Fallback auf PATH
|
|
else:
|
|
ytdlp_path = "yt-dlp"
|
|
|
|
cmd = [ytdlp_path]
|
|
|
|
# Debug-Ausgabe
|
|
self.update_signal.emit(f"Debug - self.cmd_args: {self.cmd_args}")
|
|
self.update_signal.emit(f"Debug - self.output_dir: {self.output_dir}")
|
|
self.update_signal.emit(f"Debug - self.output_filename: {self.output_filename}")
|
|
|
|
if self.cmd_args:
|
|
# Argumente per Split aufteilen, dabei aber Anführungszeichen berücksichtigen
|
|
args = []
|
|
in_quotes = False
|
|
current_arg = ""
|
|
|
|
for char in self.cmd_args:
|
|
if char == '"' or char == "'":
|
|
in_quotes = not in_quotes
|
|
current_arg += char
|
|
elif char.isspace() and not in_quotes:
|
|
if current_arg:
|
|
args.append(current_arg)
|
|
current_arg = ""
|
|
else:
|
|
current_arg += char
|
|
|
|
if current_arg:
|
|
args.append(current_arg)
|
|
|
|
# Debug-Ausgabe
|
|
self.update_signal.emit(f"Debug - Parsed args: {args}")
|
|
cmd.extend(args)
|
|
|
|
if self.output_dir:
|
|
if self.output_filename:
|
|
output_path = os.path.join(self.output_dir, self.output_filename)
|
|
self.update_signal.emit(f"Debug - Vollständiger Ausgabepfad: {output_path}")
|
|
cmd.extend(["-o", output_path])
|
|
else:
|
|
default_path = os.path.join(self.output_dir, "%(title)s.%(ext)s")
|
|
self.update_signal.emit(f"Debug - Standard-Ausgabepfad: {default_path}")
|
|
cmd.extend(["-o", default_path])
|
|
elif self.output_filename:
|
|
self.update_signal.emit(f"Debug - Nur Ausgabedateiname: {self.output_filename}")
|
|
cmd.extend(["-o", self.output_filename])
|
|
|
|
cmd.append(self.url)
|
|
|
|
# Debug-Ausgabe vor der Formatierung
|
|
self.update_signal.emit(f"Debug - Befehlszeile vor Formatierung: {cmd}")
|
|
|
|
# Formatiere die Befehlszeile mit Anführungszeichen für Elemente mit Leerzeichen
|
|
formatted_cmd = []
|
|
for arg in cmd:
|
|
if " " in arg and not (arg.startswith('"') and arg.endswith('"')) and not (arg.startswith("'") and arg.endswith("'")):
|
|
formatted_cmd.append(f'"{arg}"')
|
|
else:
|
|
formatted_cmd.append(arg)
|
|
|
|
self.update_signal.emit(f"Ausführen: {' '.join(formatted_cmd)}")
|
|
|
|
# Unterdrücke das CMD-Fenster unter Windows
|
|
creationflags = subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0
|
|
process = subprocess.Popen(
|
|
cmd,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
text=True,
|
|
bufsize=1,
|
|
universal_newlines=True,
|
|
creationflags=creationflags
|
|
)
|
|
|
|
for line in process.stdout:
|
|
self.update_signal.emit(line.strip())
|
|
|
|
process.wait()
|
|
|
|
if process.returncode == 0:
|
|
self.finished_signal.emit(True, "Download erfolgreich abgeschlossen!")
|
|
else:
|
|
self.finished_signal.emit(False, f"Download fehlgeschlagen mit Exitcode {process.returncode}")
|
|
|
|
except Exception as e:
|
|
self.update_signal.emit(f"Fehler: {str(e)}")
|
|
self.finished_signal.emit(False, f"Fehler: {str(e)}")
|
|
|
|
|
|
class PresetDialog(QDialog):
|
|
def __init__(self, parent=None, preset_data=None):
|
|
super().__init__(parent)
|
|
self.setWindowTitle("Preset erstellen/bearbeiten")
|
|
self.resize(500, 450)
|
|
|
|
self.preset_data = preset_data or {
|
|
"name": "",
|
|
"description": "",
|
|
"args": "",
|
|
"has_series_template": False,
|
|
"series_template": SERIES_TEMPLATE,
|
|
"series": "",
|
|
"season": "1",
|
|
"episode": "1",
|
|
"extension": ".mkv",
|
|
"custom_path": "",
|
|
"is_audio": False
|
|
}
|
|
|
|
self.setup_ui()
|
|
|
|
def setup_ui(self):
|
|
layout = QVBoxLayout()
|
|
form_layout = QFormLayout()
|
|
|
|
self.name_edit = QLineEdit(self.preset_data["name"])
|
|
self.description_edit = QLineEdit(self.preset_data["description"])
|
|
self.args_edit = QTextEdit(self.preset_data["args"])
|
|
|
|
form_layout.addRow("Name:", self.name_edit)
|
|
form_layout.addRow("Beschreibung:", self.description_edit)
|
|
form_layout.addRow("yt-dlp Argumente:", self.args_edit)
|
|
|
|
# Audio-Preset Checkbox
|
|
self.is_audio_cb = QCheckBox("Ist Audio-Preset (kein Remux nach MKV)")
|
|
self.is_audio_cb.setChecked(self.preset_data.get("is_audio", False))
|
|
form_layout.addRow(self.is_audio_cb)
|
|
|
|
# Referer
|
|
self.referer_edit = QLineEdit(self.preset_data.get("referer", ""))
|
|
self.referer_edit.setPlaceholderText("Optional: Referer-Link für --referer=")
|
|
form_layout.addRow("Referer:", self.referer_edit)
|
|
|
|
# HLS-ffmpeg Checkbox
|
|
self.hls_ffmpeg_cb = QCheckBox("HLS-Streams mit ffmpeg herunterladen (--downloader ffmpeg --hls-use-mpegts)")
|
|
self.hls_ffmpeg_cb.setChecked(self.preset_data.get("hls_ffmpeg", False))
|
|
form_layout.addRow(self.hls_ffmpeg_cb)
|
|
|
|
# Untertitel-Optionen
|
|
self.sublang_edit = QLineEdit(self.preset_data.get("sublang", ""))
|
|
self.sublang_edit.setPlaceholderText("z.B. de, en, de,en ...")
|
|
form_layout.addRow("Untertitelsprache:", self.sublang_edit)
|
|
self.embed_subs_cb = QCheckBox("Untertitel einbetten (--embed-subs)")
|
|
self.embed_subs_cb.setChecked(self.preset_data.get("embed_subs", False))
|
|
form_layout.addRow(self.embed_subs_cb)
|
|
|
|
# Untertitel-Format Dropdown
|
|
self.subformat_combo = QComboBox()
|
|
self.subformat_combo.addItem("(keine Konvertierung)", "")
|
|
self.subformat_combo.addItem("srt", "srt")
|
|
self.subformat_combo.addItem("ass", "ass")
|
|
self.subformat_combo.addItem("tx3g", "tx3g")
|
|
# Vorbelegen
|
|
subformat = self.preset_data.get("subformat", "")
|
|
idx = self.subformat_combo.findData(subformat)
|
|
if idx >= 0:
|
|
self.subformat_combo.setCurrentIndex(idx)
|
|
form_layout.addRow("Untertitel-Format:", self.subformat_combo)
|
|
|
|
# Eigener Pfad (immer sichtbar, mit Durchsuchen)
|
|
self.custom_path_edit = QLineEdit(self.preset_data.get("custom_path", ""))
|
|
self.custom_path_edit.setPlaceholderText("Optional: Eigener Zielordner für Download")
|
|
custom_path_hbox = QHBoxLayout()
|
|
custom_path_hbox.addWidget(self.custom_path_edit)
|
|
self.custom_path_browse_btn = QPushButton("Durchsuchen...")
|
|
self.custom_path_browse_btn.clicked.connect(self.browse_custom_path)
|
|
custom_path_hbox.addWidget(self.custom_path_browse_btn)
|
|
form_layout.addRow("Eigener Pfad:", custom_path_hbox)
|
|
|
|
# Login-Felder
|
|
self.username_edit = QLineEdit(self.preset_data.get("username", ""))
|
|
self.username_edit.setPlaceholderText("Optional: Benutzername für Login (-u)")
|
|
form_layout.addRow("Benutzername:", self.username_edit)
|
|
pw_hbox = QHBoxLayout()
|
|
self.password_edit = QLineEdit(self.preset_data.get("password", ""))
|
|
self.password_edit.setEchoMode(QLineEdit.Password)
|
|
self.password_edit.setPlaceholderText("Optional: Passwort für Login (-p)")
|
|
self.show_pw_cb = QCheckBox("Passwort anzeigen")
|
|
self.show_pw_cb.toggled.connect(self.toggle_password_visible)
|
|
pw_hbox.addWidget(self.password_edit)
|
|
pw_hbox.addWidget(self.show_pw_cb)
|
|
form_layout.addRow("Passwort:", pw_hbox)
|
|
pw_hint = QLabel("Hinweis: Passwörter werden im Klartext lokal gespeichert!")
|
|
pw_hint.setStyleSheet("color: red;")
|
|
form_layout.addRow(pw_hint)
|
|
|
|
# Serien-Template
|
|
self.series_box = QGroupBox("Serien-Template aktivieren")
|
|
self.series_box.setCheckable(True)
|
|
self.series_box.setChecked(self.preset_data.get("has_series_template", False))
|
|
|
|
series_layout = QFormLayout()
|
|
|
|
self.template_edit = QLineEdit(self.preset_data.get("series_template", SERIES_TEMPLATE))
|
|
self.series_edit = QLineEdit(self.preset_data.get("series", ""))
|
|
self.season_edit = QLineEdit(self.preset_data.get("season", "1"))
|
|
self.episode_edit = QLineEdit(self.preset_data.get("episode", "1"))
|
|
|
|
series_layout.addRow("Template:", self.template_edit)
|
|
series_layout.addRow("Serie:", self.series_edit)
|
|
series_layout.addRow("Staffel:", self.season_edit)
|
|
series_layout.addRow("Folge:", self.episode_edit)
|
|
|
|
help_text = QLabel("Verwende {series}, {season}, {episode}, %(ext)s und {path} im Template.")
|
|
series_layout.addRow(help_text)
|
|
|
|
self.series_box.setLayout(series_layout)
|
|
|
|
layout.addLayout(form_layout)
|
|
layout.addWidget(self.series_box)
|
|
|
|
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
|
buttons.accepted.connect(self.accept)
|
|
buttons.rejected.connect(self.reject)
|
|
|
|
layout.addWidget(buttons)
|
|
self.setLayout(layout)
|
|
|
|
def toggle_password_visible(self, checked):
|
|
self.password_edit.setEchoMode(QLineEdit.Normal if checked else QLineEdit.Password)
|
|
|
|
def get_preset_data(self):
|
|
return {
|
|
"name": self.name_edit.text(),
|
|
"description": self.description_edit.text(),
|
|
"args": self.args_edit.toPlainText(),
|
|
"has_series_template": self.series_box.isChecked(),
|
|
"series_template": self.template_edit.text(),
|
|
"series": self.series_edit.text(),
|
|
"season": self.season_edit.text(),
|
|
"episode": self.episode_edit.text(),
|
|
"custom_path": self.custom_path_edit.text(),
|
|
"is_audio": self.is_audio_cb.isChecked(),
|
|
"username": self.username_edit.text(),
|
|
"password": self.password_edit.text(),
|
|
"referer": self.referer_edit.text(),
|
|
"hls_ffmpeg": self.hls_ffmpeg_cb.isChecked(),
|
|
"sublang": self.sublang_edit.text(),
|
|
"embed_subs": self.embed_subs_cb.isChecked(),
|
|
"subformat": self.subformat_combo.currentData()
|
|
}
|
|
|
|
def browse_custom_path(self):
|
|
directory = QFileDialog.getExistingDirectory(self, "Eigenen Zielordner auswählen", self.custom_path_edit.text() or "")
|
|
if directory:
|
|
self.custom_path_edit.setText(directory)
|
|
|
|
|
|
class YtDlpDownloadThread(QThread):
|
|
finished_signal = pyqtSignal(bool, str)
|
|
def run(self):
|
|
try:
|
|
url = "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe"
|
|
bin_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "bin")
|
|
os.makedirs(bin_dir, exist_ok=True)
|
|
dest_path = os.path.join(bin_dir, "yt-dlp.exe")
|
|
urllib.request.urlretrieve(url, dest_path)
|
|
self.finished_signal.emit(True, f"yt-dlp.exe wurde erfolgreich nach {dest_path} heruntergeladen.")
|
|
except Exception as e:
|
|
self.finished_signal.emit(False, f"Fehler beim Herunterladen von yt-dlp.exe: {str(e)}")
|
|
|
|
|
|
class OptionenDialog(QDialog):
|
|
def __init__(self, output_dir, use_local_ytdlp, parent=None, ytdlp_flags=None):
|
|
super().__init__(parent)
|
|
self.setWindowTitle("Optionen")
|
|
self.resize(420, 250)
|
|
self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
|
|
self.selected_output_dir = output_dir
|
|
self.selected_use_local_ytdlp = use_local_ytdlp
|
|
self.selected_flags = ytdlp_flags or {
|
|
"ignore_config": False,
|
|
"remux_mkv": False,
|
|
"embed_metadata": False
|
|
}
|
|
self.setup_ui()
|
|
|
|
def setup_ui(self):
|
|
main_layout = QVBoxLayout()
|
|
tabs = QTabWidget()
|
|
|
|
# Tab 1: Allgemein
|
|
tab_allgemein = QWidget()
|
|
layout = QFormLayout()
|
|
self.output_dir_input = QLineEdit(self.selected_output_dir)
|
|
browse_btn = QPushButton("Durchsuchen...")
|
|
browse_btn.clicked.connect(self.browse_output_dir)
|
|
hbox = QHBoxLayout()
|
|
hbox.addWidget(self.output_dir_input)
|
|
hbox.addWidget(browse_btn)
|
|
layout.addRow("Standardpfad:", hbox)
|
|
self.ytdlp_source_combo = QComboBox()
|
|
self.ytdlp_source_combo.addItems(["Lokal (bin/yt-dlp.exe)", "System (PATH)"])
|
|
self.ytdlp_source_combo.setCurrentIndex(0 if self.selected_use_local_ytdlp else 1)
|
|
layout.addRow("yt-dlp-Quelle:", self.ytdlp_source_combo)
|
|
# Default-Presets ausblenden
|
|
self.hide_defaults_cb = QCheckBox("Default-Presets ausblenden")
|
|
self.hide_defaults_cb.setChecked(self.parent().config.get("hide_default_presets", False) if self.parent() else False)
|
|
layout.addRow(self.hide_defaults_cb)
|
|
bin_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "bin")
|
|
ytdlp_path = os.path.join(bin_dir, "yt-dlp.exe")
|
|
# Button 1: Herunterladen
|
|
self.download_btn = QPushButton("yt-dlp.exe herunterladen")
|
|
self.download_btn.clicked.connect(self.download_ytdlp)
|
|
self.download_btn.setEnabled(not os.path.exists(ytdlp_path))
|
|
layout.addRow(self.download_btn)
|
|
# Button 2: Updaten
|
|
self.update_btn = QPushButton("yt-dlp.exe updaten")
|
|
self.update_btn.clicked.connect(self.update_ytdlp)
|
|
layout.addRow(self.update_btn)
|
|
tab_allgemein.setLayout(layout)
|
|
tabs.addTab(tab_allgemein, "Allgemein")
|
|
|
|
# Tab 2: yt-dlp-Flags
|
|
tab_flags = QWidget()
|
|
flags_layout = QVBoxLayout()
|
|
self.cb_ignore_config = QCheckBox("--ignore-config")
|
|
self.cb_ignore_config.setChecked(self.selected_flags.get("ignore_config", False))
|
|
flags_layout.addWidget(self.cb_ignore_config)
|
|
flags_layout.addWidget(QLabel("Ignoriert die systemweite yt-dlp-Konfiguration."))
|
|
self.cb_remux_mkv = QCheckBox("--remux-video mkv")
|
|
self.cb_remux_mkv.setChecked(self.selected_flags.get("remux_mkv", False))
|
|
flags_layout.addWidget(self.cb_remux_mkv)
|
|
flags_layout.addWidget(QLabel("Remuxt das Video ins MKV-Format."))
|
|
self.cb_embed_metadata = QCheckBox("--embed-metadata")
|
|
self.cb_embed_metadata.setChecked(self.selected_flags.get("embed_metadata", False))
|
|
flags_layout.addWidget(self.cb_embed_metadata)
|
|
flags_layout.addWidget(QLabel("Betten Metadaten in die Ausgabedatei ein."))
|
|
tab_flags.setLayout(flags_layout)
|
|
tabs.addTab(tab_flags, "yt-dlp-Flags")
|
|
|
|
# Tab 3: Info
|
|
tab_info = QWidget()
|
|
info_layout = QVBoxLayout()
|
|
info_text = (
|
|
"<b>Version:</b> 1.0<br>"
|
|
"<b>© 2025 Akamaru</b><br>"
|
|
"<b>Sourcecode:</b> <a href='https://git.ponywave.de/Akamaru/video-download-helper'>https://git.ponywave.de/Akamaru/video-download-helper</a><br>"
|
|
"<b>Erstellt mit Hilfe von Claude, GPT & Gemini</b>"
|
|
)
|
|
info_label = QLabel(info_text)
|
|
info_label.setOpenExternalLinks(True)
|
|
info_label.setTextFormat(Qt.RichText)
|
|
info_layout.addWidget(info_label)
|
|
info_layout.addStretch(1)
|
|
tab_info.setLayout(info_layout)
|
|
tabs.addTab(tab_info, "Info")
|
|
|
|
main_layout.addWidget(tabs)
|
|
# Stelle sicher, dass QDialogButtonBox nur einmal erstellt und verbunden wird
|
|
if hasattr(self, 'button_box'):
|
|
try:
|
|
main_layout.removeWidget(self.button_box)
|
|
self.button_box.deleteLater()
|
|
except Exception:
|
|
pass
|
|
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
|
self.button_box.accepted.connect(self.accept)
|
|
self.button_box.rejected.connect(self.reject)
|
|
main_layout.addWidget(self.button_box)
|
|
self.setLayout(main_layout)
|
|
|
|
def browse_output_dir(self):
|
|
directory = QFileDialog.getExistingDirectory(self, "Standardpfad auswählen", self.output_dir_input.text() or os.path.expanduser("~"))
|
|
if directory:
|
|
self.output_dir_input.setText(directory)
|
|
|
|
def get_values(self):
|
|
return (
|
|
self.output_dir_input.text(),
|
|
self.ytdlp_source_combo.currentIndex() == 0,
|
|
{
|
|
"ignore_config": self.cb_ignore_config.isChecked(),
|
|
"remux_mkv": self.cb_remux_mkv.isChecked(),
|
|
"embed_metadata": self.cb_embed_metadata.isChecked()
|
|
},
|
|
self.hide_defaults_cb.isChecked()
|
|
)
|
|
|
|
def download_ytdlp(self):
|
|
self.download_btn.setEnabled(False)
|
|
self.download_btn.setText("Lade herunter...")
|
|
self.thread = YtDlpDownloadThread()
|
|
self.thread.finished_signal.connect(self.download_finished)
|
|
self.thread.start()
|
|
|
|
def download_finished(self, success, message):
|
|
bin_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "bin")
|
|
ytdlp_path = os.path.join(bin_dir, "yt-dlp.exe")
|
|
self.download_btn.setEnabled(not os.path.exists(ytdlp_path))
|
|
self.download_btn.setText("yt-dlp.exe herunterladen")
|
|
if success:
|
|
QMessageBox.information(self, "Erfolg", message)
|
|
else:
|
|
QMessageBox.critical(self, "Fehler", message)
|
|
|
|
def update_ytdlp(self):
|
|
self.update_btn.setEnabled(False)
|
|
self.update_btn.setText("Aktualisiere...")
|
|
# Bestimme, ob lokal oder systemweit
|
|
use_local = self.ytdlp_source_combo.currentIndex() == 0
|
|
if use_local:
|
|
bin_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "bin")
|
|
ytdlp_path = os.path.join(bin_dir, "yt-dlp.exe")
|
|
cmd = [ytdlp_path, "-U"]
|
|
else:
|
|
cmd = ["yt-dlp", "-U"]
|
|
try:
|
|
creationflags = subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0
|
|
result = subprocess.run(cmd, capture_output=True, text=True, creationflags=creationflags)
|
|
if result.returncode == 0:
|
|
QMessageBox.information(self, "Erfolg", f"yt-dlp wurde aktualisiert.\n\n{result.stdout}")
|
|
else:
|
|
QMessageBox.warning(self, "Fehler", f"Fehler beim Updaten von yt-dlp:\n{result.stderr or result.stdout}")
|
|
except Exception as e:
|
|
QMessageBox.critical(self, "Fehler", f"Fehler beim Ausführen von yt-dlp -U: {str(e)}")
|
|
self.update_btn.setEnabled(True)
|
|
self.update_btn.setText("yt-dlp.exe updaten")
|
|
|
|
|
|
class MainWindow(QMainWindow):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.setWindowTitle("Video Download Helper")
|
|
self.resize(800, 600)
|
|
|
|
# Stelle sicher, dass Verzeichnisse existieren
|
|
self.ensure_directories()
|
|
|
|
# Default-Presets automatisch anlegen, falls keine vorhanden
|
|
if not any(f.endswith('.json') for f in os.listdir(PRESETS_DIR)):
|
|
self.create_default_presets()
|
|
|
|
self.config = self.load_config()
|
|
self.presets = self.load_presets()
|
|
|
|
self.download_thread = None
|
|
|
|
self.setup_ui()
|
|
|
|
def ensure_directories(self):
|
|
"""Stellt sicher, dass alle benötigten Verzeichnisse existieren."""
|
|
# Stelle sicher, dass der Presets-Ordner existiert
|
|
os.makedirs(PRESETS_DIR, exist_ok=True)
|
|
|
|
# Stelle sicher, dass der bin-Ordner existiert
|
|
bin_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "bin")
|
|
os.makedirs(bin_dir, exist_ok=True)
|
|
|
|
def setup_ui(self):
|
|
central_widget = QWidget()
|
|
main_layout = QVBoxLayout()
|
|
|
|
# URL Input
|
|
url_layout = QHBoxLayout()
|
|
url_layout.addWidget(QLabel("Video-URL:"))
|
|
self.url_input = QLineEdit()
|
|
url_layout.addWidget(self.url_input)
|
|
main_layout.addLayout(url_layout)
|
|
|
|
# Preset Selection
|
|
preset_layout = QHBoxLayout()
|
|
preset_layout.addWidget(QLabel("Preset:"))
|
|
self.preset_combo = QComboBox()
|
|
self.update_preset_combo()
|
|
preset_layout.addWidget(self.preset_combo)
|
|
|
|
# Preset Buttons
|
|
self.add_preset_btn = QPushButton("Neu")
|
|
self.add_preset_btn.clicked.connect(self.add_preset)
|
|
preset_layout.addWidget(self.add_preset_btn)
|
|
|
|
self.edit_preset_btn = QPushButton("Bearbeiten")
|
|
self.edit_preset_btn.clicked.connect(self.edit_preset)
|
|
preset_layout.addWidget(self.edit_preset_btn)
|
|
|
|
self.delete_preset_btn = QPushButton("Löschen")
|
|
self.delete_preset_btn.clicked.connect(self.delete_preset)
|
|
preset_layout.addWidget(self.delete_preset_btn)
|
|
|
|
main_layout.addLayout(preset_layout)
|
|
|
|
# Serien-Einstellungen
|
|
self.series_group = QGroupBox("Serien-Einstellungen")
|
|
self.series_group.setVisible(False)
|
|
|
|
series_form = QFormLayout()
|
|
|
|
self.series_input = QLineEdit()
|
|
self.season_input = QLineEdit()
|
|
self.episode_input = QLineEdit()
|
|
|
|
series_form.addRow("Serie:", self.series_input)
|
|
series_form.addRow("Staffel:", self.season_input)
|
|
series_form.addRow("Folge:", self.episode_input)
|
|
|
|
self.custom_path_input = QLineEdit()
|
|
|
|
series_form.addRow("Eigener Pfad:", self.custom_path_input)
|
|
|
|
self.series_group.setLayout(series_form)
|
|
main_layout.addWidget(self.series_group)
|
|
|
|
# Options-Button statt Felder für Standardpfad und yt-dlp-Quelle
|
|
optionen_layout = QHBoxLayout()
|
|
self.optionen_btn = QPushButton("Optionen...")
|
|
self.optionen_btn.clicked.connect(self.open_optionen_dialog)
|
|
optionen_layout.addWidget(self.optionen_btn)
|
|
main_layout.addLayout(optionen_layout)
|
|
|
|
# Command Preview
|
|
main_layout.addWidget(QLabel("Befehlsvorschau:"))
|
|
self.cmd_preview = QTextEdit()
|
|
self.cmd_preview.setReadOnly(True)
|
|
self.cmd_preview.setMaximumHeight(60)
|
|
main_layout.addWidget(self.cmd_preview)
|
|
|
|
# Download Button
|
|
self.download_btn = QPushButton("Download starten")
|
|
self.download_btn.clicked.connect(self.start_download)
|
|
main_layout.addWidget(self.download_btn)
|
|
|
|
# Log Output
|
|
main_layout.addWidget(QLabel("Ausgabe:"))
|
|
self.log_output = QTextEdit()
|
|
self.log_output.setReadOnly(True)
|
|
main_layout.addWidget(self.log_output)
|
|
|
|
# Connect signals
|
|
self.url_input.textChanged.connect(self.update_cmd_preview)
|
|
self.preset_combo.currentIndexChanged.connect(self.preset_changed)
|
|
# self.optionen_btn.clicked.connect(self.open_optionen_dialog) # Entfernt, um doppeltes Öffnen zu verhindern
|
|
|
|
# Serie, Staffel, Folge Signals
|
|
self.series_input.textChanged.connect(self.update_cmd_preview)
|
|
self.season_input.textChanged.connect(self.update_cmd_preview)
|
|
self.episode_input.textChanged.connect(self.update_cmd_preview)
|
|
self.custom_path_input.textChanged.connect(self.update_cmd_preview)
|
|
|
|
central_widget.setLayout(main_layout)
|
|
self.setCentralWidget(central_widget)
|
|
|
|
# Initial update
|
|
self.preset_changed()
|
|
self.update_cmd_preview()
|
|
|
|
def load_config(self):
|
|
if os.path.exists(CONFIG_FILE):
|
|
try:
|
|
with open(CONFIG_FILE, 'r') as f:
|
|
return json.load(f)
|
|
except Exception:
|
|
return DEFAULT_CONFIG.copy()
|
|
return DEFAULT_CONFIG.copy()
|
|
|
|
def save_config(self):
|
|
self.config["output_dir"] = self.output_dir_input.text() if hasattr(self, 'output_dir_input') else self.config.get("output_dir", "")
|
|
self.config["use_local_ytdlp"] = self.ytdlp_source_combo.currentIndex() == 0 if hasattr(self, 'ytdlp_source_combo') else self.config.get("use_local_ytdlp", True)
|
|
if self.preset_combo.currentIndex() >= 0:
|
|
self.config["last_preset"] = self.preset_combo.currentText()
|
|
try:
|
|
with open(CONFIG_FILE, 'w') as f:
|
|
json.dump(self.config, f, indent=4)
|
|
except Exception as e:
|
|
self.log_output.append(f"Fehler beim Speichern der Konfiguration: {str(e)}")
|
|
|
|
def load_presets(self):
|
|
# Default-Presets immer aus dem Code laden
|
|
defaults = [
|
|
{
|
|
"name": "Audio (MP3)",
|
|
"description": "Nur Audio als MP3",
|
|
"args": "-x --audio-format mp3",
|
|
"has_series_template": False,
|
|
"is_audio": True,
|
|
"source": "default"
|
|
},
|
|
{
|
|
"name": "Video (Best)",
|
|
"description": "Beste Videoqualität",
|
|
"args": "-f bestvideo+bestaudio",
|
|
"has_series_template": False,
|
|
"is_audio": False,
|
|
"source": "default"
|
|
}
|
|
]
|
|
presets = defaults.copy()
|
|
# User-Presets (können mitgelieferte überschreiben)
|
|
user_presets_dir = get_user_presets_dir()
|
|
for filename in os.listdir(user_presets_dir):
|
|
if filename.endswith('.json'):
|
|
try:
|
|
with open(os.path.join(user_presets_dir, filename), 'r', encoding='utf-8') as f:
|
|
preset = json.load(f)
|
|
preset["source"] = "user"
|
|
# Überschreibe ggf. gleichnamiges Preset
|
|
presets = [p for p in presets if p["name"] != preset["name"]]
|
|
presets.append(preset)
|
|
except Exception as e:
|
|
self.log_output.append(f"Fehler beim Laden von User-Preset {filename}: {str(e)}")
|
|
return presets
|
|
|
|
def create_default_presets(self):
|
|
defaults = [
|
|
{
|
|
"name": "Audio (MP3)",
|
|
"description": "Nur Audio als MP3",
|
|
"args": "-x --audio-format mp3",
|
|
"has_series_template": False,
|
|
"is_audio": True,
|
|
"source": "default"
|
|
},
|
|
{
|
|
"name": "Video (Best)",
|
|
"description": "Beste Videoqualität",
|
|
"args": "-f bestvideo+bestaudio",
|
|
"has_series_template": False,
|
|
"is_audio": False,
|
|
"source": "default"
|
|
}
|
|
]
|
|
|
|
for preset in defaults:
|
|
filename = f"{preset['name'].lower().replace(' ', '_')}.json"
|
|
try:
|
|
with open(os.path.join(PRESETS_DIR, filename), 'w') as f:
|
|
json.dump(preset, f, indent=4)
|
|
except Exception as e:
|
|
self.log_output.append(f"Fehler beim Erstellen von Standard-Preset {preset['name']}: {str(e)}")
|
|
|
|
def update_preset_combo(self):
|
|
self.preset_combo.clear()
|
|
hide_defaults = self.config.get("hide_default_presets", False)
|
|
for preset in self.presets:
|
|
if hide_defaults and preset.get("source") == "default":
|
|
continue
|
|
self.preset_combo.addItem(preset["name"])
|
|
# Letztes verwendetes Preset auswählen
|
|
if self.config["last_preset"]:
|
|
index = self.preset_combo.findText(self.config["last_preset"])
|
|
if index >= 0:
|
|
self.preset_combo.setCurrentIndex(index)
|
|
|
|
def get_current_preset(self):
|
|
if self.preset_combo.currentIndex() >= 0:
|
|
preset_name = self.preset_combo.currentText()
|
|
for preset in self.presets:
|
|
if preset["name"] == preset_name:
|
|
return preset
|
|
return None
|
|
|
|
def add_preset(self):
|
|
dialog = PresetDialog(self)
|
|
if dialog.exec_() == QDialog.Accepted:
|
|
preset_data = dialog.get_preset_data()
|
|
if not preset_data["name"]:
|
|
QMessageBox.warning(self, "Fehler", "Der Preset-Name darf nicht leer sein.")
|
|
return
|
|
for preset in self.presets:
|
|
if preset["name"] == preset_data["name"]:
|
|
QMessageBox.warning(self, "Fehler", f"Ein Preset mit dem Namen '{preset_data['name']}' existiert bereits.")
|
|
return
|
|
preset_data["source"] = "user"
|
|
self.presets.append(preset_data)
|
|
# Speichern des Presets im User-Presets-Ordner
|
|
user_presets_dir = get_user_presets_dir()
|
|
filename = f"{preset_data['name'].lower().replace(' ', '_')}.json"
|
|
try:
|
|
with open(os.path.join(user_presets_dir, filename), 'w', encoding='utf-8') as f:
|
|
json.dump(preset_data, f, indent=4, ensure_ascii=False)
|
|
self.update_preset_combo()
|
|
self.preset_combo.setCurrentText(preset_data["name"])
|
|
self.update_cmd_preview()
|
|
except Exception as e:
|
|
self.log_output.append(f"Fehler beim Speichern des Presets: {str(e)}")
|
|
|
|
def edit_preset(self):
|
|
current_preset = self.get_current_preset()
|
|
if not current_preset:
|
|
QMessageBox.warning(self, "Warnung", "Kein Preset ausgewählt.")
|
|
return
|
|
dialog = PresetDialog(self, current_preset)
|
|
if dialog.exec_() == QDialog.Accepted:
|
|
new_preset_data = dialog.get_preset_data()
|
|
if not new_preset_data["name"]:
|
|
QMessageBox.warning(self, "Fehler", "Der Preset-Name darf nicht leer sein.")
|
|
return
|
|
if new_preset_data["name"] != current_preset["name"]:
|
|
for preset in self.presets:
|
|
if preset["name"] == new_preset_data["name"] and preset != current_preset:
|
|
QMessageBox.warning(self, "Fehler", f"Ein Preset mit dem Namen '{new_preset_data['name']}' existiert bereits.")
|
|
return
|
|
if new_preset_data["name"] != current_preset["name"]:
|
|
old_filename = f"{current_preset['name'].lower().replace(' ', '_')}.json"
|
|
try:
|
|
os.remove(os.path.join(get_user_presets_dir(), old_filename))
|
|
except Exception as e:
|
|
self.log_output.append(f"Warnung: Konnte alte Preset-Datei nicht löschen: {str(e)}")
|
|
for i, preset in enumerate(self.presets):
|
|
if preset["name"] == current_preset["name"]:
|
|
new_preset_data["source"] = "user"
|
|
self.presets[i] = new_preset_data
|
|
break
|
|
filename = f"{new_preset_data['name'].lower().replace(' ', '_')}.json"
|
|
try:
|
|
with open(os.path.join(get_user_presets_dir(), filename), 'w', encoding='utf-8') as f:
|
|
json.dump(new_preset_data, f, indent=4, ensure_ascii=False)
|
|
old_current_index = self.preset_combo.currentIndex()
|
|
self.update_preset_combo()
|
|
new_index = self.preset_combo.findText(new_preset_data["name"])
|
|
if new_index >= 0:
|
|
self.preset_combo.setCurrentIndex(new_index)
|
|
else:
|
|
self.preset_combo.setCurrentIndex(old_current_index)
|
|
self.update_cmd_preview()
|
|
except Exception as e:
|
|
self.log_output.append(f"Fehler beim Speichern des Presets: {str(e)}")
|
|
|
|
def delete_preset(self):
|
|
current_preset = self.get_current_preset()
|
|
if not current_preset:
|
|
QMessageBox.warning(self, "Warnung", "Kein Preset ausgewählt.")
|
|
return
|
|
reply = QMessageBox.question(
|
|
self,
|
|
"Preset löschen",
|
|
f"Möchten Sie das Preset '{current_preset['name']}' wirklich löschen?",
|
|
QMessageBox.Yes | QMessageBox.No,
|
|
QMessageBox.No
|
|
)
|
|
if reply == QMessageBox.Yes:
|
|
self.presets = [p for p in self.presets if p["name"] != current_preset["name"]]
|
|
filename = f"{current_preset['name'].lower().replace(' ', '_')}.json"
|
|
try:
|
|
os.remove(os.path.join(get_user_presets_dir(), filename))
|
|
self.update_preset_combo()
|
|
self.update_cmd_preview()
|
|
except Exception as e:
|
|
self.log_output.append(f"Fehler beim Löschen des Presets: {str(e)}")
|
|
|
|
def open_optionen_dialog(self):
|
|
dialog = OptionenDialog(
|
|
self.config["output_dir"],
|
|
self.config["use_local_ytdlp"],
|
|
self,
|
|
self.config.get("ytdlp_flags", DEFAULT_CONFIG["ytdlp_flags"])
|
|
)
|
|
if dialog.exec_() == QDialog.Accepted:
|
|
output_dir, use_local_ytdlp, ytdlp_flags, hide_defaults = dialog.get_values()
|
|
self.config["output_dir"] = output_dir
|
|
self.config["use_local_ytdlp"] = use_local_ytdlp
|
|
self.config["ytdlp_flags"] = ytdlp_flags
|
|
self.config["hide_default_presets"] = hide_defaults
|
|
self.update_preset_combo()
|
|
self.update_cmd_preview()
|
|
|
|
def preset_changed(self):
|
|
preset = self.get_current_preset()
|
|
if not preset:
|
|
self.series_group.setVisible(False)
|
|
return
|
|
|
|
# Aktualisiere Serien-UI basierend auf dem Preset
|
|
has_series = preset.get("has_series_template", False)
|
|
self.series_group.setVisible(has_series)
|
|
|
|
if has_series:
|
|
self.series_input.setText(preset.get("series", ""))
|
|
self.season_input.setText(preset.get("season", "1"))
|
|
self.episode_input.setText(preset.get("episode", "1"))
|
|
self.custom_path_input.setText(preset.get("custom_path", ""))
|
|
|
|
self.update_cmd_preview()
|
|
|
|
def get_output_filename(self, preset):
|
|
if preset.get("has_series_template", False):
|
|
template = preset.get("series_template", SERIES_TEMPLATE)
|
|
series = self.series_input.text() or preset.get("series", "")
|
|
season = self.season_input.text() or preset.get("season", "1")
|
|
episode = self.episode_input.text() or preset.get("episode", "1")
|
|
custom_path = self.custom_path_input.text() or preset.get("custom_path", "")
|
|
# Stelle sicher, dass der Pfad mit einem Trennzeichen endet, wenn er nicht leer ist
|
|
if custom_path and not custom_path.endswith("/") and not custom_path.endswith("\\"):
|
|
custom_path += "/" # Verwende / als universellen Pfadtrenner
|
|
output_name = template
|
|
output_name = output_name.replace("{series}", series)
|
|
output_name = output_name.replace("{season}", season)
|
|
output_name = output_name.replace("{episode}", episode)
|
|
output_name = output_name.replace("{path}", custom_path)
|
|
# {extension} durch .%(ext)s ersetzen (auch rückwärtskompatibel)
|
|
output_name = output_name.replace("{extension}", ".%(ext)s")
|
|
# Falls jemand %(ext)s nicht im Template hat, ergänzen wir es am Ende
|
|
if "%(ext)s" not in output_name:
|
|
output_name += ".%(ext)s"
|
|
# Entferne doppelte Schrägstriche
|
|
while "//" in output_name:
|
|
output_name = output_name.replace("//", "/")
|
|
while "\\\\" in output_name:
|
|
output_name = output_name.replace("\\\\", "\\")
|
|
self.log_output.append("Ausgabedatei-Komponenten:")
|
|
self.log_output.append(f" Template: {template}")
|
|
self.log_output.append(f" Serie: {series}")
|
|
self.log_output.append(f" Staffel: {season}")
|
|
self.log_output.append(f" Folge: {episode}")
|
|
self.log_output.append(f" Eigener Pfad: {custom_path}")
|
|
self.log_output.append(f"Generierter Dateiname: {output_name}")
|
|
return output_name
|
|
# Debug-Ausgabe auch im Nicht-Serienfall
|
|
output_name = "%(title)s.%(ext)s"
|
|
custom_path = self.custom_path_input.text() or preset.get("custom_path", "")
|
|
if custom_path and not custom_path.endswith("/") and not custom_path.endswith("\\"):
|
|
custom_path += "/"
|
|
if custom_path:
|
|
output_name = custom_path + output_name
|
|
self.log_output.append("Ausgabedatei-Komponenten:")
|
|
self.log_output.append(f" Template: {output_name}")
|
|
self.log_output.append(f" Eigener Pfad: {custom_path}")
|
|
self.log_output.append(f"Generierter Dateiname: {output_name}")
|
|
return output_name
|
|
|
|
def update_cmd_preview(self):
|
|
preset = self.get_current_preset()
|
|
if not preset:
|
|
self.cmd_preview.setText("Kein Preset ausgewählt.")
|
|
return
|
|
if self.config["use_local_ytdlp"]:
|
|
ytdlp_path = os.path.join(get_base_path(), "bin", "yt-dlp.exe")
|
|
if not os.path.exists(ytdlp_path):
|
|
ytdlp_path = "yt-dlp" # Fallback auf PATH
|
|
else:
|
|
ytdlp_path = "yt-dlp"
|
|
cmd = [ytdlp_path]
|
|
flags = self.config.get("ytdlp_flags", {})
|
|
is_audio = preset.get("is_audio", False)
|
|
# Login-Optionen
|
|
if preset.get("username"):
|
|
cmd.extend(["-u", preset["username"]])
|
|
if preset.get("password"):
|
|
cmd.extend(["-p", preset["password"]])
|
|
# Referer
|
|
if preset.get("referer"):
|
|
cmd.append(f"--referer={preset['referer']}")
|
|
# HLS-ffmpeg
|
|
if preset.get("hls_ffmpeg"):
|
|
cmd.extend(["--downloader", "ffmpeg", "--hls-use-mpegts"])
|
|
# Untertitel
|
|
if preset.get("sublang"):
|
|
cmd.extend(["--sub-lang", preset["sublang"]])
|
|
if preset.get("embed_subs"):
|
|
cmd.append("--embed-subs")
|
|
if preset.get("subformat"):
|
|
cmd.extend(["--convert-subs", preset["subformat"]])
|
|
if flags.get("ignore_config"):
|
|
cmd.append("--ignore-config")
|
|
if flags.get("remux_mkv") and not is_audio:
|
|
cmd.extend(["--remux-video", "mkv"])
|
|
if flags.get("embed_metadata"):
|
|
cmd.append("--embed-metadata")
|
|
if preset["args"]:
|
|
args = []
|
|
in_quotes = False
|
|
current_arg = ""
|
|
for char in preset["args"]:
|
|
if char == '"' or char == "'":
|
|
in_quotes = not in_quotes
|
|
current_arg += char
|
|
elif char.isspace() and not in_quotes:
|
|
if current_arg:
|
|
args.append(current_arg)
|
|
current_arg = ""
|
|
else:
|
|
current_arg += char
|
|
if current_arg:
|
|
args.append(current_arg)
|
|
cmd.extend(args)
|
|
output_dir = self.config["output_dir"]
|
|
output_filename = self.get_output_filename(preset)
|
|
if output_dir:
|
|
output_path = os.path.join(output_dir, output_filename)
|
|
cmd.extend(["-o", output_path])
|
|
else:
|
|
cmd.extend(["-o", output_filename])
|
|
url = self.url_input.text() or "[URL]"
|
|
cmd.append(url)
|
|
formatted_cmd = []
|
|
for arg in cmd:
|
|
if " " in arg and not (arg.startswith('"') and arg.endswith('"')) and not (arg.startswith("'") and arg.endswith("'")):
|
|
formatted_cmd.append(f'"{arg}"')
|
|
else:
|
|
formatted_cmd.append(arg)
|
|
self.cmd_preview.setText(" ".join(formatted_cmd))
|
|
|
|
def start_download(self):
|
|
url = self.url_input.text()
|
|
if not url:
|
|
QMessageBox.warning(self, "Fehler", "Bitte geben Sie eine URL ein.")
|
|
return
|
|
preset = self.get_current_preset()
|
|
if not preset:
|
|
QMessageBox.warning(self, "Fehler", "Bitte wählen Sie ein Preset aus.")
|
|
return
|
|
output_dir = self.config["output_dir"]
|
|
use_local_ytdlp = self.config["use_local_ytdlp"]
|
|
flags = self.config.get("ytdlp_flags", {})
|
|
is_audio = preset.get("is_audio", False)
|
|
extra_args = []
|
|
if preset.get("username"):
|
|
extra_args.extend(["-u", preset["username"]])
|
|
if preset.get("password"):
|
|
extra_args.extend(["-p", preset["password"]])
|
|
if preset.get("referer"):
|
|
extra_args.append(f"--referer={preset['referer']}")
|
|
if preset.get("hls_ffmpeg"):
|
|
extra_args.extend(["--downloader", "ffmpeg", "--hls-use-mpegts"])
|
|
if preset.get("sublang"):
|
|
extra_args.extend(["--sub-lang", preset["sublang"]])
|
|
if preset.get("embed_subs"):
|
|
extra_args.append("--embed-subs")
|
|
if preset.get("subformat"):
|
|
extra_args.extend(["--convert-subs", preset["subformat"]])
|
|
if flags.get("ignore_config"):
|
|
extra_args.append("--ignore-config")
|
|
if flags.get("remux_mkv") and not is_audio:
|
|
extra_args.extend(["--remux-video", "mkv"])
|
|
if flags.get("embed_metadata"):
|
|
extra_args.append("--embed-metadata")
|
|
full_args = (" ".join(extra_args) + " " + preset["args"]).strip() if extra_args else preset["args"]
|
|
self.save_config()
|
|
self.log_output.clear()
|
|
self.log_output.append(f"Starte Download von: {url}")
|
|
output_filename = None
|
|
if preset.get("has_series_template", False):
|
|
output_filename = self.get_output_filename(preset)
|
|
self.log_output.append(f"Verwende Ausgabedateiname: {output_filename}")
|
|
self.log_output.append("Download-Parameter:")
|
|
self.log_output.append(f"URL: {url}")
|
|
self.log_output.append(f"Ausgabeverzeichnis: {output_dir}")
|
|
self.log_output.append(f"Argumente: {preset['args']}")
|
|
self.log_output.append(f"Lokales yt-dlp: {use_local_ytdlp}")
|
|
self.log_output.append(f"yt-dlp-Flags: {flags}")
|
|
self.log_output.append(f"Audio-Preset: {is_audio}")
|
|
self.log_output.append(f"Benutzername: {preset.get('username','')}")
|
|
self.log_output.append(f"Referer: {preset.get('referer','')}")
|
|
self.log_output.append(f"HLS-ffmpeg: {preset.get('hls_ffmpeg',False)}")
|
|
self.log_output.append(f"Untertitelsprache: {preset.get('sublang','')}")
|
|
self.log_output.append(f"Embed-Subs: {preset.get('embed_subs',False)}")
|
|
self.log_output.append(f"Untertitel-Format: {preset.get('subformat','')}")
|
|
self.log_output.append(f"Ausgabedateiname: {output_filename}")
|
|
self.download_thread = DownloadThread(
|
|
url=url,
|
|
output_dir=output_dir,
|
|
cmd_args=full_args,
|
|
use_local_ytdlp=use_local_ytdlp,
|
|
output_filename=output_filename
|
|
)
|
|
self.download_thread.update_signal.connect(self.update_log)
|
|
self.download_thread.finished_signal.connect(self.download_finished)
|
|
self.download_btn.setEnabled(False)
|
|
self.download_thread.start()
|
|
|
|
def update_log(self, text):
|
|
self.log_output.append(text)
|
|
# Scroll zum Ende
|
|
scrollbar = self.log_output.verticalScrollBar()
|
|
scrollbar.setValue(scrollbar.maximum())
|
|
|
|
def download_finished(self, success, message):
|
|
self.download_btn.setEnabled(True)
|
|
|
|
if success:
|
|
self.log_output.append(message)
|
|
QMessageBox.information(self, "Erfolg", message)
|
|
else:
|
|
self.log_output.append(f"Fehler: {message}")
|
|
QMessageBox.warning(self, "Fehler", message)
|
|
|
|
def closeEvent(self, event):
|
|
self.save_config()
|
|
event.accept()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
app = QApplication(sys.argv)
|
|
window = MainWindow()
|
|
window.show()
|
|
sys.exit(app.exec_()) |