Erste Version
This commit is contained in:
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
build/
|
||||||
|
dist/
|
||||||
|
presets/
|
||||||
|
bin/yt-dlp.exe
|
||||||
|
config.json
|
||||||
|
main.spec
|
||||||
|
*.7z
|
63
README.md
Normal file
63
README.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# Vider Download Helper
|
||||||
|
|
||||||
|
Eine grafische Benutzeroberfläche für yt-dlp mit Preset-Funktionalität für Windows.
|
||||||
|
|
||||||
|
## Downloads
|
||||||
|
|
||||||
|
Die jeweils aktuelle Version (EXE, CLI) findest du hier:
|
||||||
|
|
||||||
|
https://git.ponywave.de/Akamaru/video-download-helper/releases/latest
|
||||||
|
|
||||||
|
## Funktionen
|
||||||
|
|
||||||
|
- Downloadvideos von YouTube und anderen unterstützten Plattformen
|
||||||
|
- Erstellen und Verwalten von Presets mit vordefinierten Einstellungen
|
||||||
|
- Auswahl zwischen installiertem yt-dlp im PATH oder im bin-Ordner der Anwendung
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Stelle sicher, dass Python 3.8+ installiert ist
|
||||||
|
2. Installiere die Abhängigkeiten: `pip install -r requirements.txt`
|
||||||
|
3. Führe die Anwendung aus: `python main.py`
|
||||||
|
|
||||||
|
## Kompilieren der EXE
|
||||||
|
|
||||||
|
```
|
||||||
|
pyinstaller --onefile --windowed main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Die erstellte EXE-Datei befindet sich im `dist`-Ordner.
|
||||||
|
|
||||||
|
## Verwendung
|
||||||
|
|
||||||
|
1. Starte die Anwendung
|
||||||
|
2. Füge die Video-URL ein
|
||||||
|
3. Wähle ein Preset oder passe die Einstellungen an
|
||||||
|
4. Klicke auf "Download"
|
||||||
|
|
||||||
|
## Starten der Anwendung
|
||||||
|
|
||||||
|
### Über die Batch-Datei (empfohlen für Python-Umgebungen)
|
||||||
|
|
||||||
|
Du kannst die Anwendung bequem über die mitgelieferte `startvdl.bat` starten. Diese sorgt dafür, dass das Fenster im Hintergrund läuft und keine Konsole angezeigt wird:
|
||||||
|
|
||||||
|
```bat
|
||||||
|
@echo off
|
||||||
|
start "" /b pythonw.exe main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Einfach einen Doppelklick auf `startvdl.bat` ausführen.
|
||||||
|
|
||||||
|
### Über die EXE-Datei (ohne Python)
|
||||||
|
|
||||||
|
Wenn du die Anwendung mit PyInstaller gebaut hast, findest du die ausführbare Datei im `dist`-Ordner (z.B. `main.exe`).
|
||||||
|
|
||||||
|
Starte die Anwendung einfach per Doppelklick auf die EXE:
|
||||||
|
|
||||||
|
```
|
||||||
|
dist\main.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hinweise
|
||||||
|
- Stelle sicher, dass sich `yt-dlp.exe` im `bin`-Ordner befindet, wenn du die lokale Variante nutzen möchtest.
|
||||||
|
- Alternativ kannst du auch die Systeminstallation von yt-dlp (im PATH) verwenden.
|
203
build.py
Normal file
203
build.py
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import argparse
|
||||||
|
import importlib.util
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
|
def check_requirements():
|
||||||
|
"""Überprüft, ob alle erforderlichen Pakete installiert sind."""
|
||||||
|
try:
|
||||||
|
import PyQt5
|
||||||
|
import PyInstaller
|
||||||
|
except ImportError as e:
|
||||||
|
print(f"Fehler: {e}")
|
||||||
|
print("Bitte führe 'pip install -r requirements.txt' aus.")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def find_yt_dlp():
|
||||||
|
"""Versucht, yt-dlp im PATH zu finden."""
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["where", "yt-dlp"] if os.name == "nt" else ["which", "yt-dlp"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
path = result.stdout.strip().split("\n")[0]
|
||||||
|
return path
|
||||||
|
return None
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def download_yt_dlp():
|
||||||
|
"""Lädt die aktuelle Version von yt-dlp herunter."""
|
||||||
|
bin_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "bin")
|
||||||
|
|
||||||
|
if not os.path.exists(bin_dir):
|
||||||
|
os.makedirs(bin_dir)
|
||||||
|
|
||||||
|
print("Lade yt-dlp herunter...")
|
||||||
|
|
||||||
|
ytdlp_url = "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe"
|
||||||
|
ytdlp_path = os.path.join(bin_dir, "yt-dlp.exe")
|
||||||
|
|
||||||
|
try:
|
||||||
|
import requests
|
||||||
|
response = requests.get(ytdlp_url)
|
||||||
|
with open(ytdlp_path, "wb") as f:
|
||||||
|
f.write(response.content)
|
||||||
|
print(f"yt-dlp wurde nach {ytdlp_path} heruntergeladen.")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Fehler beim Herunterladen von yt-dlp: {e}")
|
||||||
|
print("Bitte lade yt-dlp manuell herunter und platziere es im 'bin' Ordner.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def copy_yt_dlp_from_path():
|
||||||
|
"""Kopiert yt-dlp aus dem PATH in den bin-Ordner."""
|
||||||
|
ytdlp_path = find_yt_dlp()
|
||||||
|
|
||||||
|
if not ytdlp_path:
|
||||||
|
print("yt-dlp wurde nicht im PATH gefunden.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
bin_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "bin")
|
||||||
|
|
||||||
|
if not os.path.exists(bin_dir):
|
||||||
|
os.makedirs(bin_dir)
|
||||||
|
|
||||||
|
try:
|
||||||
|
dest_path = os.path.join(bin_dir, "yt-dlp.exe" if os.name == "nt" else "yt-dlp")
|
||||||
|
shutil.copy2(ytdlp_path, dest_path)
|
||||||
|
print(f"yt-dlp wurde nach {dest_path} kopiert.")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Fehler beim Kopieren von yt-dlp: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def build_exe(onedir=False, console=False):
|
||||||
|
"""Erstellt die ausführbare Datei mit PyInstaller."""
|
||||||
|
print("Erstelle EXE-Datei...")
|
||||||
|
# Nur Default-Presets übernehmen
|
||||||
|
main_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "main.py")
|
||||||
|
with open(main_path, "r", encoding="utf-8") as f:
|
||||||
|
main_code = f.read()
|
||||||
|
# Default-Presets extrahieren (vereinfachte Suche)
|
||||||
|
match = re.search(r'defaults\s*=\s*\[(.*?)\n\s*\]', main_code, re.DOTALL)
|
||||||
|
if match:
|
||||||
|
defaults_code = "[" + match.group(1) + "]"
|
||||||
|
try:
|
||||||
|
# eval mit eingeschränktem Namespace
|
||||||
|
defaults = eval(defaults_code, {"__builtins__": None}, {})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Fehler beim Parsen der Default-Presets: {e}")
|
||||||
|
defaults = []
|
||||||
|
else:
|
||||||
|
print("Konnte Default-Presets nicht finden!")
|
||||||
|
defaults = []
|
||||||
|
# Presets-Ordner bereinigen und nur Default-Presets schreiben
|
||||||
|
bin_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "bin")
|
||||||
|
presets_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "presets")
|
||||||
|
if os.path.exists(presets_dir):
|
||||||
|
for f in os.listdir(presets_dir):
|
||||||
|
if f.endswith('.json'):
|
||||||
|
try:
|
||||||
|
os.remove(os.path.join(presets_dir, f))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
os.makedirs(presets_dir)
|
||||||
|
for preset in defaults:
|
||||||
|
filename = f"{preset['name'].lower().replace(' ', '_')}.json"
|
||||||
|
try:
|
||||||
|
with open(os.path.join(presets_dir, filename), 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(preset, f, indent=4, ensure_ascii=False)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Fehler beim Schreiben von Default-Preset {preset['name']}: {e}")
|
||||||
|
# Rest wie gehabt
|
||||||
|
cmd = ["pyinstaller"]
|
||||||
|
if not onedir:
|
||||||
|
cmd.append("--onefile")
|
||||||
|
if not console:
|
||||||
|
cmd.append("--windowed")
|
||||||
|
if os.path.exists(bin_dir) and os.listdir(bin_dir):
|
||||||
|
cmd.extend(["--add-data", f"{bin_dir}{os.pathsep}bin"])
|
||||||
|
if os.path.exists(presets_dir):
|
||||||
|
cmd.extend(["--add-data", f"{presets_dir}{os.pathsep}presets"])
|
||||||
|
cmd.append("main.py")
|
||||||
|
print(f"Führe aus: {' '.join(cmd)}")
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
subprocess.run(cmd, check=True)
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("pyinstaller nicht im PATH gefunden, versuche 'python -m PyInstaller' ...")
|
||||||
|
cmd[0:1] = [sys.executable, "-m", "PyInstaller"]
|
||||||
|
subprocess.run(cmd, check=True)
|
||||||
|
if onedir:
|
||||||
|
print("Die ausführbare Datei wurde im 'dist/main' Ordner erstellt.")
|
||||||
|
else:
|
||||||
|
print("Die ausführbare Datei wurde als 'dist/main.exe' erstellt.")
|
||||||
|
return True
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"Fehler beim Erstellen der EXE-Datei: {e}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Unerwarteter Fehler: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def parse_arguments():
|
||||||
|
"""Parst die Kommandozeilenargumente."""
|
||||||
|
parser = argparse.ArgumentParser(description="Build-Skript für YT-DLP GUI")
|
||||||
|
parser.add_argument("--onedir", action="store_true", help="Erstellt einen Ordner statt einer einzelnen Datei")
|
||||||
|
parser.add_argument("--console", action="store_true", help="Zeigt die Konsole an")
|
||||||
|
parser.add_argument("--download-ytdlp", action="store_true", help="Lädt yt-dlp herunter")
|
||||||
|
parser.add_argument("--copy-ytdlp", action="store_true", help="Kopiert yt-dlp aus dem PATH")
|
||||||
|
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = parse_arguments()
|
||||||
|
|
||||||
|
if not check_requirements():
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Überprüfe, ob yt-dlp bereits im bin-Ordner existiert
|
||||||
|
bin_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "bin")
|
||||||
|
ytdlp_exists = os.path.exists(os.path.join(bin_dir, "yt-dlp.exe" if os.name == "nt" else "yt-dlp"))
|
||||||
|
|
||||||
|
if not ytdlp_exists:
|
||||||
|
if args.download_ytdlp:
|
||||||
|
if not download_yt_dlp():
|
||||||
|
return 1
|
||||||
|
elif args.copy_ytdlp:
|
||||||
|
if not copy_yt_dlp_from_path():
|
||||||
|
print("yt-dlp konnte nicht kopiert werden. Versuche, es herunterzuladen...")
|
||||||
|
if not download_yt_dlp():
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
# Frage den Benutzer
|
||||||
|
print("yt-dlp wurde nicht im bin-Ordner gefunden.")
|
||||||
|
choice = input("Möchten Sie (1) yt-dlp herunterladen, (2) aus dem PATH kopieren oder (3) ohne fortfahren? [1/2/3]: ")
|
||||||
|
|
||||||
|
if choice == "1":
|
||||||
|
if not download_yt_dlp():
|
||||||
|
return 1
|
||||||
|
elif choice == "2":
|
||||||
|
if not copy_yt_dlp_from_path():
|
||||||
|
print("yt-dlp konnte nicht kopiert werden. Versuche, es herunterzuladen...")
|
||||||
|
if not download_yt_dlp():
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Erstelle die ausführbare Datei
|
||||||
|
if not build_exe(onedir=args.onedir, console=args.console):
|
||||||
|
return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
PyQt5==5.15.9
|
||||||
|
PyInstaller==5.13.2
|
||||||
|
requests==2.31.0
|
2
startvdl.bat
Normal file
2
startvdl.bat
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
@echo off
|
||||||
|
start "" /b pythonw.exe main.py
|
Reference in New Issue
Block a user