Multi-Hoster-Upload/app.py

245 lines
8.9 KiB
Python

from __future__ import annotations
import json
import queue
import threading
from dataclasses import dataclass
from pathlib import Path
from tkinter import END, LEFT, RIGHT, W, filedialog, messagebox
import tkinter as tk
from tkinter import ttk
from hosters import UploadError, UploadResult, build_uploaders
APP_DIR = Path(__file__).resolve().parent
CONFIG_PATH = APP_DIR / "config.json"
@dataclass
class HostRow:
host: str
enabled: tk.BooleanVar
credential: tk.StringVar
class UploaderApp:
def __init__(self, root: tk.Tk) -> None:
self.root = root
self.root.title("Multi Hoster Uploader")
self.root.geometry("980x680")
self.uploaders = build_uploaders()
self.files: list[Path] = []
self.log_queue: queue.Queue[str] = queue.Queue()
self.link_lines: list[str] = []
self.host_rows: dict[str, HostRow] = {}
self._build_ui()
self._load_config()
self._poll_logs()
def _build_ui(self) -> None:
wrapper = ttk.Frame(self.root, padding=12)
wrapper.pack(fill=tk.BOTH, expand=True)
title = ttk.Label(wrapper, text="Uploader fuer doodstream / voe / vidmoly / byse", font=("Segoe UI", 13, "bold"))
title.pack(anchor=W)
subtitle = ttk.Label(
wrapper,
text="Nutze deine eigenen Accounts/API Keys. Das Tool nutzt nur offizielle oder kompatible Upload-Endpunkte.",
)
subtitle.pack(anchor=W, pady=(2, 10))
hosts_frame = ttk.LabelFrame(wrapper, text="Hoster Login / API Key", padding=10)
hosts_frame.pack(fill=tk.X)
for idx, host in enumerate(self.uploaders.keys()):
enabled = tk.BooleanVar(value=True if host in ("doodstream.com", "voe.sx") else False)
credential = tk.StringVar()
chk = ttk.Checkbutton(hosts_frame, text=host, variable=enabled)
chk.grid(row=idx, column=0, sticky=W, padx=(0, 10), pady=4)
entry = ttk.Entry(hosts_frame, width=70, textvariable=credential, show="*")
entry.grid(row=idx, column=1, sticky="we", pady=4)
self.host_rows[host] = HostRow(host=host, enabled=enabled, credential=credential)
hosts_frame.columnconfigure(1, weight=1)
files_frame = ttk.LabelFrame(wrapper, text="Dateien", padding=10)
files_frame.pack(fill=tk.BOTH, expand=False, pady=(10, 0))
btn_row = ttk.Frame(files_frame)
btn_row.pack(fill=tk.X)
ttk.Button(btn_row, text="Dateien waehlen", command=self._pick_files).pack(side=LEFT)
ttk.Button(btn_row, text="Auswahl loeschen", command=self._clear_files).pack(side=LEFT, padx=8)
self.upload_btn = ttk.Button(btn_row, text="Upload starten", command=self._start_upload)
self.upload_btn.pack(side=RIGHT)
self.file_list = tk.Listbox(files_frame, height=8)
self.file_list.pack(fill=tk.X, pady=(8, 0))
output_pane = ttk.PanedWindow(wrapper, orient=tk.HORIZONTAL)
output_pane.pack(fill=tk.BOTH, expand=True, pady=(10, 0))
log_frame = ttk.LabelFrame(output_pane, text="Log", padding=8)
links_frame = ttk.LabelFrame(output_pane, text="Output Links", padding=8)
output_pane.add(log_frame, weight=1)
output_pane.add(links_frame, weight=1)
self.log_text = tk.Text(log_frame, height=14, wrap=tk.WORD)
self.log_text.pack(fill=tk.BOTH, expand=True)
self.links_text = tk.Text(links_frame, height=14, wrap=tk.WORD)
self.links_text.pack(fill=tk.BOTH, expand=True)
action_row = ttk.Frame(wrapper)
action_row.pack(fill=tk.X, pady=(8, 0))
ttk.Button(action_row, text="Links kopieren", command=self._copy_links).pack(side=LEFT)
ttk.Button(action_row, text="Config speichern", command=self._save_config).pack(side=LEFT, padx=8)
def _pick_files(self) -> None:
selected = filedialog.askopenfilenames(title="Dateien zum Upload auswaehlen")
if not selected:
return
for item in selected:
path = Path(item)
if path not in self.files:
self.files.append(path)
self.file_list.insert(END, str(path))
def _clear_files(self) -> None:
self.files = []
self.file_list.delete(0, END)
def _copy_links(self) -> None:
text = self.links_text.get("1.0", END).strip()
if not text:
messagebox.showinfo("Info", "Noch keine Links vorhanden.")
return
self.root.clipboard_clear()
self.root.clipboard_append(text)
self.root.update_idletasks()
messagebox.showinfo("Fertig", "Links in Zwischenablage kopiert.")
def _append_log(self, line: str) -> None:
self.log_text.insert(END, line + "\n")
self.log_text.see(END)
def _append_result(self, result: UploadResult) -> None:
lines = [
f"[{result.host}] {result.file_path.name}",
f" file_code: {result.file_code or '-'}",
f" download: {result.download_url or '-'}",
f" embed: {result.embed_url or '-'}",
"",
]
for line in lines:
self.link_lines.append(line)
self.links_text.delete("1.0", END)
self.links_text.insert("1.0", "\n".join(self.link_lines).strip() + "\n")
def _save_config(self) -> None:
payload = {
"hosts": {
host: {
"enabled": row.enabled.get(),
"credential": row.credential.get(),
}
for host, row in self.host_rows.items()
}
}
CONFIG_PATH.write_text(json.dumps(payload, indent=2), encoding="utf-8")
messagebox.showinfo("Gespeichert", f"Config gespeichert: {CONFIG_PATH}")
def _load_config(self) -> None:
if not CONFIG_PATH.exists():
return
try:
data = json.loads(CONFIG_PATH.read_text(encoding="utf-8"))
except Exception:
return
hosts = data.get("hosts", {})
if not isinstance(hosts, dict):
return
for host, row in self.host_rows.items():
host_cfg = hosts.get(host, {})
if not isinstance(host_cfg, dict):
continue
if "enabled" in host_cfg:
row.enabled.set(bool(host_cfg["enabled"]))
if "credential" in host_cfg and isinstance(host_cfg["credential"], str):
row.credential.set(host_cfg["credential"])
def _start_upload(self) -> None:
enabled_hosts = [row for row in self.host_rows.values() if row.enabled.get()]
if not enabled_hosts:
messagebox.showwarning("Fehlt", "Bitte mindestens einen Hoster aktivieren.")
return
if not self.files:
messagebox.showwarning("Fehlt", "Bitte mindestens eine Datei auswaehlen.")
return
self.upload_btn.config(state=tk.DISABLED)
worker = threading.Thread(target=self._run_upload, args=(enabled_hosts, list(self.files)), daemon=True)
worker.start()
def _run_upload(self, enabled_hosts: list[HostRow], files: list[Path]) -> None:
self.log_queue.put("Upload gestartet...")
ok_count = 0
fail_count = 0
for host_row in enabled_hosts:
uploader = self.uploaders[host_row.host]
credential = host_row.credential.get().strip()
if not credential:
self.log_queue.put(f"[{host_row.host}] uebersprungen: Kein Login/API Key hinterlegt")
fail_count += len(files)
continue
for file_path in files:
self.log_queue.put(f"[{host_row.host}] Upload: {file_path.name}")
try:
result = uploader.upload_file(file_path, credential)
self.root.after(0, self._append_result, result)
self.log_queue.put(f"[{host_row.host}] OK: {file_path.name}")
ok_count += 1
except (UploadError, OSError, ValueError) as exc:
self.log_queue.put(f"[{host_row.host}] FEHLER: {file_path.name} -> {exc}")
fail_count += 1
except Exception as exc:
self.log_queue.put(f"[{host_row.host}] FEHLER (unerwartet): {file_path.name} -> {exc}")
fail_count += 1
self.log_queue.put(f"Fertig. Erfolgreich: {ok_count}, Fehler: {fail_count}")
self.root.after(0, lambda: self.upload_btn.config(state=tk.NORMAL))
def _poll_logs(self) -> None:
while True:
try:
line = self.log_queue.get_nowait()
self._append_log(line)
except queue.Empty:
break
self.root.after(120, self._poll_logs)
def main() -> None:
root = tk.Tk()
style = ttk.Style(root)
if "vista" in style.theme_names():
style.theme_use("vista")
app = UploaderApp(root)
root.mainloop()
if __name__ == "__main__":
main()