Table of Contents

Mikos Batch Downloader

This tool provides a graphical interface for batch downloading videos or audio using yt-dlp. It supports playlists, format selection, cookie authentication, and live progress display.

Features

Requirements

Usage

  1. Start the GUI by running the script.
  2. Paste a video or playlist URL into the input field.
  3. Click Add Link to queue it.
  4. Choose an output folder and optionally a cookies.txt file.
  5. Select desired format from the dropdown.
  6. Click Start Downloads to begin.
  7. Use Cancel Downloads to stop the current process.

GUI Overview

Notes

License

This script is provided as-is. Feel free to modify and redistribute under an open-source license of your choice.

Code

import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import subprocess
import threading
import os
import re
 
download_queue = []
current_process = None
 
# ---------------- GUI ----------------
root = tk.Tk()
root.title("Mikos Batch Downloader")
 
cookies_file = tk.StringVar(master=root)  # für den Cookies-Dateipfad
output_dir = tk.StringVar(master=root)    # Ausgabeverzeichnis
 
def add_link():
    url = url_entry.get()
    if url:
        download_queue.append(url)
        url_listbox.insert(tk.END, url)
        url_entry.delete(0, tk.END)
 
def clear_links():
    download_queue.clear()
    url_listbox.delete(0, tk.END)
 
def choose_directory():
    folder = filedialog.askdirectory()
    if folder:
        output_dir.set(folder)
 
def choose_cookies():
    file = filedialog.askopenfilename(
        title="Choose cookies.txt",
        filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")]
    )
    if file:
        cookies_file.set(file)
        status_label.config(text=f"Cookies loaded: {os.path.basename(file)}")
 
def cancel_download():
    global current_process
    if current_process and current_process.poll() is None:
        confirm = messagebox.askyesno("Cancel Download", "Are you sure you want to cancel?")
        if confirm:
            current_process.terminate()
            cancel_button.config(state="disabled")
            status_label.config(text="Download cancelled.")
            progress_text.insert(tk.END, "\nDownload was manually cancelled.\n")
            progress_text.see(tk.END)
 
def start_downloads():
    format_choice = format_var.get()
    out_dir = output_dir.get()
    cookies_path = cookies_file.get()  # ✅ Zugriff im Hauptthread
 
    if not download_queue:
        messagebox.showwarning("No Links", "Please add at least one URL.")
        return
 
    threading.Thread(target=run_downloads, args=(format_choice, out_dir, cookies_path), daemon=True).start()
 
def run_downloads(format_choice, out_dir, cookies_path):
    global current_process
    idx = 0
    cancel_button.config(state="normal")
 
    while idx < len(download_queue):
        url = download_queue[idx]
        status_label.config(text=f"Downloading {idx+1}/{len(download_queue)}…")
        cmd = ["yt-dlp"]
 
        if cookies_path:
            cmd += ["--cookies", cookies_path]
 
        if format_choice == "mp3":
            cmd += ["--extract-audio", "--audio-format", "mp3"]
        else:
            cmd += ["-f", format_choice]
 
        if out_dir:
            cmd += ["-o", os.path.join(out_dir, "%(title)s.%(ext)s")]
 
        cmd.append(url)
 
        try:
            current_process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
        except FileNotFoundError:
            messagebox.showerror("Error", "yt-dlp not found. Please ensure it is installed and in PATH.")
            cancel_button.config(state="disabled")
            return
 
        progress_text.delete("1.0", tk.END)
 
        for line in current_process.stdout:
            progress_text.insert(tk.END, line)
            progress_text.see(tk.END)
 
            match = re.search(r"(\d{1,3}(?:\.\d+)?)%", line)
            if match:
                percent = float(match.group(1))
                progress_bar["value"] = percent
 
        current_process.wait()
        progress_bar["value"] = 0  # ✅ Reset nach Abschluss
 
        if current_process.returncode == 0:
            url_listbox.delete(idx)
            download_queue.pop(idx)
        else:
            idx += 1
 
    current_process = None
    cancel_button.config(state="disabled")
    status_label.config(text="All downloads completed.")
    messagebox.showinfo("Done", "All videos have been processed.")
 
# ---------------- GUI Layout ----------------
tk.Label(root, text="Video or Playlist URL:").pack()
url_entry = tk.Entry(root, width=80)
url_entry.pack()
 
button_frame = tk.Frame(root)
button_frame.pack(pady=5)
 
tk.Button(button_frame, text="Add Link", command=add_link).pack(side=tk.LEFT, padx=5)
tk.Button(button_frame, text="Clear List", command=clear_links).pack(side=tk.LEFT, padx=5)
tk.Button(button_frame, text="Choose Folder", command=choose_directory).pack(side=tk.LEFT, padx=5)
tk.Button(button_frame, text="Choose Cookies", command=choose_cookies).pack(side=tk.LEFT, padx=5)
 
url_listbox = tk.Listbox(root, width=80, height=5)
url_listbox.pack(pady=5)
 
tk.Label(root, text="Select Format:").pack()
format_var = tk.StringVar(value="best")
format_dropdown = ttk.Combobox(root, textvariable=format_var, values=["best", "mp4", "bestaudio", "mp3"], width=20)
format_dropdown.pack()
 
tk.Label(root, textvariable=output_dir).pack()
 
action_frame = tk.Frame(root)
action_frame.pack(pady=10)
 
tk.Button(action_frame, text="Start Downloads", command=start_downloads).pack(side=tk.LEFT, padx=10)
cancel_button = tk.Button(action_frame, text="Cancel Downloads", command=cancel_download, state="disabled")
cancel_button.pack(side=tk.LEFT, padx=10)
 
status_label = tk.Label(root, text="Ready")
status_label.pack()
 
progress_bar = ttk.Progressbar(root, length=400, mode="determinate")
progress_bar.pack(pady=5)
 
progress_text = tk.Text(root, height=10, width=80)
progress_text.pack()
 
root.mainloop()