#!/usr/bin/env -S uv run --script """ Dialog-Klassen für den Video Download Helper """ import os import subprocess from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QFormLayout, QLineEdit, QTextEdit, QPushButton, QComboBox, QCheckBox, QTabWidget, QWidget, QHBoxLayout, QGroupBox, QLabel, QDialogButtonBox, QFileDialog, QMessageBox) from PyQt5.QtCore import Qt from PyQt5.QtGui import QIcon from config import get_base_path, SERIES_TEMPLATE from utils import set_window_icon from download_threads import YtDlpDownloadThread class PresetDialog(QDialog): def __init__(self, parent=None, preset_data=None): super().__init__(parent) self.setWindowTitle("Preset erstellen/bearbeiten") self.resize(500, 450) # Entferne den Hilfe-Button (Fragezeichen) in der Titelleiste self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) # Icon für den Dialog setzen set_window_icon(self) 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, "custom_output_template": False, "output_template": "%(title)s.%(ext)s" } self.setup_ui() def setup_ui(self): layout = QVBoxLayout() # Tabs hinzufügen tabs = QTabWidget() # Tab 1: Grundeinstellungen basic_tab = QWidget() 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) # Eigener Pfad (immer sichtbar, mit Durchsuchen) - hierher verschoben 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) # 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) basic_tab.setLayout(form_layout) tabs.addTab(basic_tab, "Grundeinstellungen") # Tab 2: Untertitel subtitle_tab = QWidget() subtitle_layout = QFormLayout() # Untertitel-Optionen self.sublang_edit = QLineEdit(self.preset_data.get("sublang", "")) self.sublang_edit.setPlaceholderText("z.B. de, en, de,en ...") subtitle_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)) subtitle_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) subtitle_layout.addRow("Untertitel-Format:", self.subformat_combo) subtitle_tab.setLayout(subtitle_layout) tabs.addTab(subtitle_tab, "Untertitel") # Tab 3: Output Template output_tab = QWidget() output_layout = QFormLayout() # Custom Output Template Checkbox self.custom_output_template_cb = QCheckBox("Eigenen Namen verwenden") self.custom_output_template_cb.setChecked(self.preset_data.get("custom_output_template", False)) output_layout.addRow(self.custom_output_template_cb) # Output Template Field self.output_template_edit = QLineEdit(self.preset_data.get("output_template", "%(title)s.%(ext)s")) self.output_template_edit.setPlaceholderText("z.B. %(title)s.%(ext)s, %(uploader)s/%(title)s.%(ext)s") output_layout.addRow("Name:", self.output_template_edit) # Add examples and documentation link examples_label = QLabel("Beispiele:
" + "%(title)s-%(id)s.%(ext)s
" + "%(uploader)s/%(title)s.%(ext)s
" + "%(playlist)s/%(playlist_index)s-%(title)s.%(ext)s

" + "Mehr Beispiele in der yt-dlp Dokumentation") examples_label.setOpenExternalLinks(True) examples_label.setTextFormat(Qt.RichText) output_layout.addRow(examples_label) output_tab.setLayout(output_layout) tabs.addTab(output_tab, "Name") # Tab 4: Authentifizierung auth_tab = QWidget() auth_layout = QFormLayout() # Login-Felder self.username_edit = QLineEdit(self.preset_data.get("username", "")) self.username_edit.setPlaceholderText("Optional: Benutzername für Login (-u)") auth_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) auth_layout.addRow("Passwort:", pw_hbox) pw_hint = QLabel("Hinweis: Passwörter werden im Klartext lokal gespeichert!") pw_hint.setStyleSheet("color: red;") auth_layout.addRow(pw_hint) auth_tab.setLayout(auth_layout) tabs.addTab(auth_tab, "Authentifizierung") # Tab 4: Pfade & Serien path_tab = QWidget() path_layout = QFormLayout() # 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) path_layout.addWidget(self.series_box) path_tab.setLayout(path_layout) tabs.addTab(path_tab, "Serien") # Tab 5: ADN (vorher Experte) adn_tab = QWidget() adn_layout = QVBoxLayout() # F-Option und Format-ID Checkbox self.use_format_selection_cb = QCheckBox("Format-Auswahl aktivieren") self.use_format_selection_cb.setChecked(self.preset_data.get("use_format_selection", False)) self.use_format_selection_cb.toggled.connect(self.toggle_format_selection) adn_layout.addWidget(self.use_format_selection_cb) # Beschreibung der Format-Auswahl format_desc = QLabel( "Diese Option führt beim Start des Downloads zuerst 'yt-dlp -F' aus,\n" "um verfügbare Formate anzuzeigen. Anschließend wird nach einer Format-ID gefragt." ) adn_layout.addWidget(format_desc) # Dual-Audio (Jap+Ger) und Untertitel Muxing aktivieren self.use_dual_audio_cb = QCheckBox("Dual-Audio Muxing aktivieren") self.use_dual_audio_cb.setChecked(self.preset_data.get("use_dual_audio", False)) self.use_dual_audio_cb.toggled.connect(self.toggle_dual_audio) adn_layout.addWidget(self.use_dual_audio_cb) # Dual-Audio Gruppe self.dual_audio_group = QGroupBox("Dual-Audio Einstellungen") self.dual_audio_group.setEnabled(self.preset_data.get("use_dual_audio", False)) dual_form = QFormLayout() # Format ID Präfixe self.jap_prefix_edit = QLineEdit(self.preset_data.get("jap_prefix", "vostde")) dual_form.addRow("Prefix für japanische Audio:", self.jap_prefix_edit) self.ger_prefix_edit = QLineEdit(self.preset_data.get("ger_prefix", "vde")) dual_form.addRow("Prefix für deutsche Audio:", self.ger_prefix_edit) # Suffix (Standard: -1) self.format_suffix_edit = QLineEdit(self.preset_data.get("format_suffix", "-1")) dual_form.addRow("Format-Suffix:", self.format_suffix_edit) # Dateinamen für temporäre Dateien self.temp_jp_filename_edit = QLineEdit(self.preset_data.get("temp_jp_filename", "video_jp.mp4")) dual_form.addRow("Temp. Dateiname JP:", self.temp_jp_filename_edit) self.temp_de_filename_edit = QLineEdit(self.preset_data.get("temp_de_filename", "video_de.mp4")) dual_form.addRow("Temp. Dateiname DE:", self.temp_de_filename_edit) # Untertitel-Dateien self.de_sub_filename_edit = QLineEdit(self.preset_data.get("de_sub_filename", "subs.de.ass")) dual_form.addRow("DE Untertitel-Datei:", self.de_sub_filename_edit) self.de_forced_sub_filename_edit = QLineEdit(self.preset_data.get("de_forced_sub_filename", "subs.de.forced.ass")) dual_form.addRow("DE forced Untertitel:", self.de_forced_sub_filename_edit) # Cleanup-Option self.cleanup_temp_cb = QCheckBox("Temporäre Dateien nach dem Muxing löschen") self.cleanup_temp_cb.setChecked(self.preset_data.get("cleanup_temp", True)) dual_form.addRow(self.cleanup_temp_cb) self.dual_audio_group.setLayout(dual_form) adn_layout.addWidget(self.dual_audio_group) adn_tab.setLayout(adn_layout) # ADN Tab nur hinzufügen, wenn aktiviert self.adn_tab_index = None if self.parent() and self.parent().config.get("enable_adn_tab", False): self.adn_tab_index = tabs.addTab(adn_tab, "ADN") layout.addWidget(tabs) 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 toggle_format_selection(self, checked): # Diese Methode setzt Flags, wenn die Format-Auswahl aktiviert wird pass def toggle_dual_audio(self, checked): # Aktiviere/Deaktiviere die Dual-Audio-Einstellungen self.dual_audio_group.setEnabled(checked) 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(), "use_format_selection": self.use_format_selection_cb.isChecked(), "use_dual_audio": self.use_dual_audio_cb.isChecked(), "jap_prefix": self.jap_prefix_edit.text(), "ger_prefix": self.ger_prefix_edit.text(), "format_suffix": self.format_suffix_edit.text(), "temp_jp_filename": self.temp_jp_filename_edit.text(), "temp_de_filename": self.temp_de_filename_edit.text(), "de_sub_filename": self.de_sub_filename_edit.text(), "de_forced_sub_filename": self.de_forced_sub_filename_edit.text(), "cleanup_temp": self.cleanup_temp_cb.isChecked(), "custom_output_template": self.custom_output_template_cb.isChecked(), "output_template": self.output_template_edit.text() } def browse_custom_path(self): dialog = QFileDialog(self) dialog.setFileMode(QFileDialog.Directory) dialog.setWindowTitle("Eigenen Zielordner auswählen") if self.custom_path_edit.text(): dialog.setDirectory(self.custom_path_edit.text()) # Icon setzen set_window_icon(dialog) if dialog.exec_(): directory = dialog.selectedFiles()[0] self.custom_path_edit.setText(directory) 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, 300) self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) # Icon für den Dialog setzen set_window_icon(self) 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, "use_ffmpeg_location": 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) # MKVMerge-Pfad hinzufügen self.mkvmerge_path_input = QLineEdit(self.parent().config.get("mkvmerge_path", "C:\\Program Files\\MKVToolNix\\mkvmerge.exe") if self.parent() else "C:\\Program Files\\MKVToolNix\\mkvmerge.exe") mkvmerge_browse_btn = QPushButton("Durchsuchen...") mkvmerge_browse_btn.clicked.connect(self.browse_mkvmerge_path) mkvmerge_hbox = QHBoxLayout() mkvmerge_hbox.addWidget(self.mkvmerge_path_input) mkvmerge_hbox.addWidget(mkvmerge_browse_btn) layout.addRow("MKVMerge-Pfad:", mkvmerge_hbox) # FFmpeg-Pfad hinzufügen self.ffmpeg_path_input = QLineEdit(self.parent().config.get("ffmpeg_path", "C:\\ffmpeg\\bin\\ffmpeg.exe") if self.parent() else "C:\\ffmpeg\\bin\\ffmpeg.exe") ffmpeg_browse_btn = QPushButton("Durchsuchen...") ffmpeg_browse_btn.clicked.connect(self.browse_ffmpeg_path) ffmpeg_hbox = QHBoxLayout() ffmpeg_hbox.addWidget(self.ffmpeg_path_input) ffmpeg_hbox.addWidget(ffmpeg_browse_btn) layout.addRow("FFmpeg-Pfad:", ffmpeg_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) # ADN Tab aktivieren self.enable_adn_tab_cb = QCheckBox("ADN Tab aktivieren") self.enable_adn_tab_cb.setChecked(self.parent().config.get("enable_adn_tab", False) if self.parent() else False) layout.addRow(self.enable_adn_tab_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.")) self.cb_use_ffmpeg_location = QCheckBox("--ffmpeg-location") self.cb_use_ffmpeg_location.setChecked(self.selected_flags.get("use_ffmpeg_location", False)) flags_layout.addWidget(self.cb_use_ffmpeg_location) flags_layout.addWidget(QLabel("Nutzt den konfigurierten FFmpeg-Pfad für yt-dlp.")) tab_flags.setLayout(flags_layout) tabs.addTab(tab_flags, "yt-dlp-Flags") # Tab 3: Info tab_info = QWidget() info_layout = QVBoxLayout() info_text = ( "Version: 1.4
" "© 2025 Akamaru
" "Sourcecode: https://git.ponywave.de/Akamaru/video-download-helper
" "Erstellt mit Hilfe von Claude, GPT & Gemini" ) 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): dialog = QFileDialog(self) dialog.setFileMode(QFileDialog.Directory) dialog.setWindowTitle("Standardpfad auswählen") dialog.setDirectory(self.output_dir_input.text() or os.path.expanduser("~")) # Icon setzen set_window_icon(dialog) if dialog.exec_(): directory = dialog.selectedFiles()[0] self.output_dir_input.setText(directory) def browse_mkvmerge_path(self): dialog = QFileDialog(self) dialog.setFileMode(QFileDialog.ExistingFile) dialog.setWindowTitle("MKVMerge-Executable auswählen") dialog.setDirectory(self.mkvmerge_path_input.text() or "C:\\Program Files\\MKVToolNix") dialog.setNameFilter("Executable (*.exe)") # Icon setzen set_window_icon(dialog) if dialog.exec_(): file_path = dialog.selectedFiles()[0] self.mkvmerge_path_input.setText(file_path) def browse_ffmpeg_path(self): dialog = QFileDialog(self) dialog.setFileMode(QFileDialog.ExistingFile) dialog.setWindowTitle("FFmpeg-Executable auswählen") dialog.setDirectory(self.ffmpeg_path_input.text() or "C:\\ffmpeg\\bin") dialog.setNameFilter("Executable (*.exe)") # Icon setzen set_window_icon(dialog) if dialog.exec_(): file_path = dialog.selectedFiles()[0] self.ffmpeg_path_input.setText(file_path) 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(), "use_ffmpeg_location": self.cb_use_ffmpeg_location.isChecked() }, self.hide_defaults_cb.isChecked(), self.mkvmerge_path_input.text(), self.enable_adn_tab_cb.isChecked(), self.ffmpeg_path_input.text() ) 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")