Melanie1204

Untitled

Oct 27th, 2025
594
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.39 KB | Software | 0 0
  1. import tkinter as tk
  2. from tkinter import messagebox, filedialog
  3. from tkinter import ttk
  4. import requests
  5. import re
  6. import os
  7. import subprocess
  8. import json
  9. from threading import Thread
  10.  
  11. # Hilfsfunktion zur Extraktion der Video-ID aus Twitch-URL
  12. def extract_video_id(url):
  13.     match = re.search(r"videos/(\d+)", url)
  14.     return match.group(1) if match else None
  15.  
  16. # Video-Metadaten (Titel) via Twitch GraphQL API holen
  17. def fetch_video_title(video_id):
  18.     client_id = 'kimne78kx3ncx6brgo4mv6wki5h1ko'
  19.     query = '''
  20.        query VideoByID($id: ID!) {
  21.            video(id: $id) {
  22.                title
  23.            }
  24.        }
  25.    '''
  26.     payload = [{
  27.         'operationName': 'VideoByID',
  28.         'variables': {'id': video_id},
  29.         'query': query
  30.     }]
  31.     headers = {'Client-ID': client_id, 'Content-Type': 'application/json'}
  32.     try:
  33.         res = requests.post('https://gql.twitch.tv/gql', headers=headers, data=json.dumps(payload))
  34.         res.raise_for_status()
  35.         data = res.json()
  36.         return data[0]['data']['video']['title']
  37.     except Exception:
  38.         return video_id
  39.  
  40. # M3U8-Playlist abrufen über Twitch GraphQL API
  41. def get_all_m3u8_urls(video_id):
  42.     payload = [{
  43.         "operationName": "PlaybackAccessToken_Template",
  44.         "variables": {"vodID": video_id, "playerType": "embed"},
  45.         "query": """
  46.            query PlaybackAccessToken_Template($vodID: ID!, $playerType: String!) {
  47.                videoPlaybackAccessToken(
  48.                    id: $vodID,
  49.                    params: { platform: \"web\", playerBackend: \"mediaplayer\", playerType: $playerType }
  50.                ) {
  51.                    signature
  52.                    value
  53.                }
  54.            }
  55.        """
  56.     }]
  57.     headers = {"Client-ID": "kimne78kx3ncx6brgo4mv6wki5h1ko", "Content-Type": "application/json"}
  58.     res = requests.post("https://gql.twitch.tv/gql", headers=headers, data=json.dumps(payload))
  59.     res.raise_for_status()
  60.     data = res.json()[0]['data']['videoPlaybackAccessToken']
  61.     sig, token = data['signature'], data['value']
  62.     m3u8_url = f"https://usher.ttvnw.net/vod/{video_id}.m3u8"
  63.     params = {"player": "twitchweb", "token": token, "sig": sig,
  64.               "allow_source": "true", "allow_audio_only": "true", "playlist_include_framerate": "true"}
  65.     playlist_resp = requests.get(m3u8_url, params=params)
  66.     playlist_resp.raise_for_status()
  67.     playlist = playlist_resp.text
  68.     streams = re.findall(r"#EXT-X-STREAM-INF:.*RESOLUTION=(\d+x\d+).*\n(.*)", playlist, re.IGNORECASE)
  69.     return {res: url for res, url in streams}
  70.  
  71. # Download-Funktion, Aufrufe GUI-aktualisierend via safe callbacks
  72. def download_video(m3u8_url, output_path, safe_log, safe_progress):
  73.     try:
  74.         cmd = ["ffmpeg", "-i", m3u8_url, "-c", "copy", "-bsf:a", "aac_adtstoasc", output_path]
  75.         process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding='utf-8', errors='ignore')
  76.         for line in process.stdout:
  77.             safe_log(line.strip())
  78.         process.wait()
  79.         safe_log(f"Video gespeichert: {output_path}")
  80.         safe_progress(100)
  81.     except Exception as e:
  82.         safe_log(f"Fehler beim ffmpeg-Lauf: {e}")
  83.         safe_progress(0)
  84.  
  85. # Hauptklasse für GUI
  86. def main():
  87.     root = tk.Tk()
  88.     root.title("Twitch Downloader")
  89.  
  90.     # Widgets
  91.     tk.Label(root, text="Twitch VOD URL:").pack(anchor='w', padx=10, pady=(10,0))
  92.     url_entry = tk.Entry(root, width=60)
  93.     url_entry.pack(padx=10)
  94.  
  95.     tk.Label(root, text="Video-Titel:").pack(anchor='w', padx=10, pady=(10,0))
  96.     title_var = tk.StringVar()
  97.     title_entry = tk.Entry(root, textvariable=title_var, width=60)
  98.     title_entry.pack(padx=10)
  99.  
  100.     tk.Label(root, text="Qualität auswählen:").pack(anchor='w', padx=10, pady=(10,0))
  101.     quality_var = tk.StringVar()
  102.     quality_menu = tk.OptionMenu(root, quality_var, "Bitte URL eingeben")
  103.     quality_menu.pack(padx=10)
  104.  
  105.     progress = tk.DoubleVar()
  106.     progressbar = ttk.Progressbar(root, variable=progress, maximum=100)
  107.     progressbar.pack(fill='x', padx=10, pady=5)
  108.  
  109.     log_text = tk.Text(root, height=8, width=70, state='disabled')
  110.     log_text.pack(padx=10, pady=(0,10))
  111.  
  112.     output_path = [None]  # mutable container
  113.  
  114.     # Thread-safe GUI-Update-Funktionen
  115.     def safe_log(msg):
  116.         root.after(0, lambda: log(msg))
  117.     def safe_progress(val):
  118.         root.after(0, lambda: progress.set(val))
  119.  
  120.     def log(msg):
  121.         log_text.configure(state='normal')
  122.         log_text.insert(tk.END, msg+"\n")
  123.         log_text.see(tk.END)
  124.         log_text.configure(state='disabled')
  125.  
  126.     def on_url_change(event=None):
  127.         vid = extract_video_id(url_entry.get().strip())
  128.         if not vid:
  129.             return
  130.         title = fetch_video_title(vid)
  131.         title_var.set(title)
  132.         log(f"Titel geladen: {title}")
  133.         streams = get_all_m3u8_urls(vid)
  134.         menu = quality_menu['menu']
  135.         menu.delete(0,'end')
  136.         for r,u in streams.items():
  137.             menu.add_command(label=r, command=lambda r=r: quality_var.set(r))
  138.         if streams:
  139.             q = next(iter(streams))
  140.             quality_var.set(q)
  141.             root.streams = streams
  142.             log(f"Qualitäten: {', '.join(streams.keys())}")
  143.  
  144.     def save_as():
  145.         fname = filedialog.asksaveasfilename(
  146.             defaultextension='.mp4',
  147.             filetypes=[('MP4','*.mp4'),('All','*.*')],
  148.             initialfile=f"{title_var.get() or 'video'}.mp4"
  149.         )
  150.         if fname:
  151.             output_path[0] = fname
  152.             log(f"Speichern unter: {fname}")
  153.  
  154.     def start_dl():
  155.         if not output_path[0]:
  156.             messagebox.showerror('Fehler','Bitte über "Speichern unter..." einen Zielpfad wählen.')
  157.             return
  158.         vid = extract_video_id(url_entry.get().strip())
  159.         q = quality_var.get()
  160.         u = getattr(root,'streams',{}).get(q)
  161.         if not all([vid,q,u]):
  162.             messagebox.showerror('Fehler','Bitte URL eingeben und Qualität auswählen.')
  163.             return
  164.         log('Starte Download...')
  165.         Thread(target=lambda: download_video(u, output_path[0], safe_log, safe_progress)).start()
  166.  
  167.     url_entry.bind('<FocusOut>', on_url_change)
  168.     tk.Button(root, text='Speichern unter...', command=save_as).pack(pady=5)
  169.     tk.Button(root, text='Download starten', command=start_dl).pack(pady=5)
  170.  
  171.     root.mainloop()
  172.  
  173. if __name__=='__main__':
  174.     main()
  175.  
Add Comment
Please, Sign In to add comment