Archives
Trending
Support
Login
clear text
XML
Django
JavaScript
MATLAB
C
C++
C#
Python
SQL
Shell
Bash
Markdown
YAML
JSON
HTML
CSS
PHP
Java
Ruby
Go
Rust
Swift
Kotlin
Arduino
TypeScript
Perl
Autohotkey
Lua
SQF
R
Scala
Haskell
Groovy
Dart
Clojure
VB.NET
Objective-C
PowerShell
Bash
CoffeeScript
Verilog
import tkinter as tk from tkinter import messagebox, filedialog from tkinter import ttk import requests import re import os import subprocess import json from threading import Thread # Hilfsfunktion zur Extraktion der Video-ID aus Twitch-URL def extract_video_id(url): match = re.search(r"videos/(\d+)", url) return match.group(1) if match else None # Video-Metadaten (Titel) via Twitch GraphQL API holen def fetch_video_title(video_id): client_id = 'kimne78kx3ncx6brgo4mv6wki5h1ko' query = ''' query VideoByID($id: ID!) { video(id: $id) { title } } ''' payload = [{ 'operationName': 'VideoByID', 'variables': {'id': video_id}, 'query': query }] headers = {'Client-ID': client_id, 'Content-Type': 'application/json'} try: res = requests.post('https://gql.twitch.tv/gql', headers=headers, data=json.dumps(payload)) res.raise_for_status() data = res.json() return data[0]['data']['video']['title'] except Exception: return video_id # M3U8-Playlist abrufen über Twitch GraphQL API def get_all_m3u8_urls(video_id): payload = [{ "operationName": "PlaybackAccessToken_Template", "variables": {"vodID": video_id, "playerType": "embed"}, "query": """ query PlaybackAccessToken_Template($vodID: ID!, $playerType: String!) { videoPlaybackAccessToken( id: $vodID, params: { platform: \"web\", playerBackend: \"mediaplayer\", playerType: $playerType } ) { signature value } } """ }] headers = {"Client-ID": "kimne78kx3ncx6brgo4mv6wki5h1ko", "Content-Type": "application/json"} res = requests.post("https://gql.twitch.tv/gql", headers=headers, data=json.dumps(payload)) res.raise_for_status() data = res.json()[0]['data']['videoPlaybackAccessToken'] sig, token = data['signature'], data['value'] m3u8_url = f"https://usher.ttvnw.net/vod/{video_id}.m3u8" params = {"player": "twitchweb", "token": token, "sig": sig, "allow_source": "true", "allow_audio_only": "true", "playlist_include_framerate": "true"} playlist_resp = requests.get(m3u8_url, params=params) playlist_resp.raise_for_status() playlist = playlist_resp.text streams = re.findall(r"#EXT-X-STREAM-INF:.*RESOLUTION=(\d+x\d+).*\n(.*)", playlist, re.IGNORECASE) return {res: url for res, url in streams} # Download-Funktion, Aufrufe GUI-aktualisierend via safe callbacks def download_video(m3u8_url, output_path, safe_log, safe_progress): try: cmd = ["ffmpeg", "-i", m3u8_url, "-c", "copy", "-bsf:a", "aac_adtstoasc", output_path] process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding='utf-8', errors='ignore') for line in process.stdout: safe_log(line.strip()) process.wait() safe_log(f"Video gespeichert: {output_path}") safe_progress(100) except Exception as e: safe_log(f"Fehler beim ffmpeg-Lauf: {e}") safe_progress(0) # Hauptklasse für GUI def main(): root = tk.Tk() root.title("Twitch Downloader") # Widgets tk.Label(root, text="Twitch VOD URL:").pack(anchor='w', padx=10, pady=(10,0)) url_entry = tk.Entry(root, width=60) url_entry.pack(padx=10) tk.Label(root, text="Video-Titel:").pack(anchor='w', padx=10, pady=(10,0)) title_var = tk.StringVar() title_entry = tk.Entry(root, textvariable=title_var, width=60) title_entry.pack(padx=10) tk.Label(root, text="Qualität auswählen:").pack(anchor='w', padx=10, pady=(10,0)) quality_var = tk.StringVar() quality_menu = tk.OptionMenu(root, quality_var, "Bitte URL eingeben") quality_menu.pack(padx=10) progress = tk.DoubleVar() progressbar = ttk.Progressbar(root, variable=progress, maximum=100) progressbar.pack(fill='x', padx=10, pady=5) log_text = tk.Text(root, height=8, width=70, state='disabled') log_text.pack(padx=10, pady=(0,10)) output_path = [None] # mutable container # Thread-safe GUI-Update-Funktionen def safe_log(msg): root.after(0, lambda: log(msg)) def safe_progress(val): root.after(0, lambda: progress.set(val)) def log(msg): log_text.configure(state='normal') log_text.insert(tk.END, msg+"\n") log_text.see(tk.END) log_text.configure(state='disabled') def on_url_change(event=None): vid = extract_video_id(url_entry.get().strip()) if not vid: return title = fetch_video_title(vid) title_var.set(title) log(f"Titel geladen: {title}") streams = get_all_m3u8_urls(vid) menu = quality_menu['menu'] menu.delete(0,'end') for r,u in streams.items(): menu.add_command(label=r, command=lambda r=r: quality_var.set(r)) if streams: q = next(iter(streams)) quality_var.set(q) root.streams = streams log(f"Qualitäten: {', '.join(streams.keys())}") def save_as(): fname = filedialog.asksaveasfilename( defaultextension='.mp4', filetypes=[('MP4','*.mp4'),('All','*.*')], initialfile=f"{title_var.get() or 'video'}.mp4" ) if fname: output_path[0] = fname log(f"Speichern unter: {fname}") def start_dl(): if not output_path[0]: messagebox.showerror('Fehler','Bitte über "Speichern unter..." einen Zielpfad wählen.') return vid = extract_video_id(url_entry.get().strip()) q = quality_var.get() u = getattr(root,'streams',{}).get(q) if not all([vid,q,u]): messagebox.showerror('Fehler','Bitte URL eingeben und Qualität auswählen.') return log('Starte Download...') Thread(target=lambda: download_video(u, output_path[0], safe_log, safe_progress)).start() url_entry.bind('
', on_url_change) tk.Button(root, text='Speichern unter...', command=save_as).pack(pady=5) tk.Button(root, text='Download starten', command=start_dl).pack(pady=5) root.mainloop() if __name__=='__main__': main()
Mark as private (unlisted)
for 30 minutes
for 6 hours
for 1 day
for 1 week
for 1 month
for 1 year
everlasting (like CCCP)