26. März 2025
YouTube-Videos herunterladen war noch nie so einfach: Mit einem kleinen Python-Skript, das ich mit Hilfe von Grok 3 (xAI) entwickelt habe, kannst du Videos in wählbarer Auflösung oder als MP3 speichern – und das mit einer übersichtlichen grafischen Oberfläche. Open Source, für Linux (Debian/Ubuntu) kompiliert und kinderleicht zu bedienen, ist es perfekt für alle, die Inhalte schnell offline verfügbar machen wollen.
Beachte vor dem Download der Werke die Rechtslage hinsichtlich der Urheber- und Nutzungsrechte, die von Land zu Land verschieden sein können. Sei besonders vorsichtig, wenn du heruntergeladene Videos und Musik teilst. Dies kann unter Umständen strafbar sein und sehr teuer werden.

Download in einer Zip-Datei: http://www.janson-soft.de/ubuntu/yt-playlist-downloader.zip
Die etwa 30 MByte große Zip-Datei enthält das Python-Skript, eine Kurzanleitung und das für Debian / Ubuntu kompilierte Programm.
Bedienung: Nach dem Start öffnet sich ein Fenster mit einem sanften Beige-Braun-Design, in dem du die YouTube-URL eingibst, den Speicherort wählst und einen Dateinamen festlegst. Über Dropdown-Menüs entscheidest du, ob du ein MP4-Video (in 360p, 720p oder 1080p) oder eine MP3-Datei möchtest. Ein Klick auf „Download starten“ genügt, und das Skript lädt den Inhalt herunter. Ein Fortschrittsbalken zeigt den Status an, während eine Textzeile darunter meldet, ob der Download läuft, die MP3-Konvertierung erfolgt oder temporäre Dateien bereinigt werden. Fehler werden in Pop-ups angezeigt – simpel und nutzerfreundlich.
Funktion: Das Skript nutzt die Bibliothek yt-dlp, um YouTube-Videos effizient herunterzuladen. Für MP4 wird die gewählte Auflösung (z. B. „bestvideo[height<=720]+bestaudio“) an yt-dlp übergeben, das Video und Audio kombiniert und in einer MP4-Datei speichert. Bei MP3 wählt es die niedrigste Videoqualität, lädt diese mit dem besten Audio und konvertiert das Ergebnis per ffmpeg in eine MP3-Datei mit 192 kBit/s. Die GUI basiert auf tkinter, während threading dafür sorgt, dass der Download im Hintergrund läuft, ohne das Fenster einzufrieren. Temporäre Dateien (wie .part oder .ytdl) werden nach Abschluss automatisch gelöscht, was den Prozess sauber hält.
Das vollständige Python-Skript:
#!/usr/bin/env python3 # -*- coding: utf-8 -*- # 2025-03-24 21:30:31 SM5ZBS # getestet mit: # https://youtu.be/4HbyziUROS8?si=OkXN1qF1hJ7cgmod # On the Edge of Dream and Reality: Steampunk, Baroque, Sci-Fi - AI Short Film # https://youtu.be/DVI7rYdzH9g?si=ENfNS8mLC85wlsy_ # What If MARS COLONIZATION Was Possible? - Sci-Fi AI Short Film # Abhängigkeiten: # sudo apt update # sudo apt install ffmpeg # pip3 install yt-dlp # Ausführbare Datei für Ubuntu erzeugen # pyinstaller --onefile yt-mp4-mp3-downloader-video-res-gui.py import tkinter as tk from tkinter import ttk, messagebox, filedialog import yt_dlp import os import threading import time import subprocess import glob import fnmatch class YouTubeDownloader: def __init__(self, root): self.root = root self.root.title("YouTube Downloader mit wählbarer Video-Auflösung") self.root.geometry("500x530") self.root.configure(bg="#E6D7B2") # Sanftes Beige-Braun als Hintergrund # GUI-Elemente self.create_widgets() def create_widgets(self): style = ttk.Style() style.configure("TProgressbar", background="#A87C4D") # Mittelbraun für Fortschrittsbalken style.configure("TButton", background="#D3A875", foreground="#3C2F2F") # Warmer Braunton für Buttons style.configure("TCombobox", fieldbackground="#F5E8C7", foreground="#3C2F2F") # Helles Beige für Dropdowns url_label = tk.Label(self.root, text="YouTube URL:", bg="#E6D7B2", fg="#3C2F2F") url_label.pack(pady=10, anchor="center") self.url_entry = tk.Entry(self.root, width=50, bg="#F5E8C7", fg="#3C2F2F", insertbackground="#3C2F2F") self.url_entry.pack(pady=5, anchor="center") save_label = tk.Label(self.root, text="Speicherort:", bg="#E6D7B2", fg="#3C2F2F") save_label.pack(pady=10, anchor="center") save_frame = tk.Frame(self.root, bg="#E6D7B2") save_frame.pack(anchor="center") self.save_path_var = tk.StringVar(value=os.getcwd()) self.save_path_entry = tk.Entry(save_frame, textvariable=self.save_path_var, width=40, bg="#F5E8C7", fg="#3C2F2F", insertbackground="#3C2F2F") self.save_path_entry.pack(side=tk.LEFT, pady=5) browse_button = tk.Button(save_frame, text="Durchsuchen", command=self.browse_save_path, bg="#D3A875", fg="#3C2F2F") browse_button.pack(side=tk.LEFT, padx=5) filename_label = tk.Label(self.root, text="Dateiname (ohne Endung):", bg="#E6D7B2", fg="#3C2F2F") filename_label.pack(pady=10, anchor="center") self.filename_entry = tk.Entry(self.root, width=50, bg="#F5E8C7", fg="#3C2F2F", insertbackground="#3C2F2F") self.filename_entry.pack(pady=5, anchor="center") format_label = tk.Label(self.root, text="Format wählen:", bg="#E6D7B2", fg="#3C2F2F") format_label.pack(pady=10, anchor="center") self.format_var = tk.StringVar(value="MP4") format_combo = ttk.Combobox(self.root, textvariable=self.format_var, values=["MP4", "MP3"], state="readonly", style="TCombobox") format_combo.pack(pady=5, anchor="center") resolution_label = tk.Label(self.root, text="Auflösung (für MP4):", bg="#E6D7B2", fg="#3C2F2F") resolution_label.pack(pady=10, anchor="center") self.resolution_var = tk.StringVar(value="720p") resolution_combo = ttk.Combobox(self.root, textvariable=self.resolution_var, values=["360p", "720p", "1080p"], state="readonly", style="TCombobox") resolution_combo.pack(pady=5, anchor="center") self.download_button = tk.Button(self.root, text="Download starten", command=self.start_download, bg="#D3A875", fg="#3C2F2F") self.download_button.pack(pady=15, anchor="center") self.progress = ttk.Progressbar(self.root, length=400, mode="determinate", style="TProgressbar") self.progress.pack(pady=10, anchor="center") self.status_label = tk.Label(self.root, text="Bereit", bg="#E6D7B2", fg="#3C2F2F") self.status_label.pack(pady=10, anchor="center") def browse_save_path(self): folder = filedialog.askdirectory() if folder: self.save_path_var.set(folder) def download_video(self, url, save_path, filename): format_choice = self.format_var.get() resolution = self.resolution_var.get() mp4_file = os.path.join(save_path, f"{filename}.mp4") mp3_file = os.path.join(save_path, f"{filename}.mp3") output_template = os.path.join(save_path, f"{filename}.%(ext)s") # yt-dlp Optionen basierend auf Format und Auflösung if format_choice == "MP3": # Für MP3: Niedrigste Auflösung verwenden ydl_opts = { "format": "worstvideo+bestaudio/worst", # Niedrigste Videoqualität "outtmpl": output_template, "merge_output_format": "mp4", "quiet": True, "progress_hooks": [self.progress_hook], "nopart": False, } else: # Für MP4: Gewählte Auflösung verwenden resolution_map = { "360p": "bestvideo[height<=360]+bestaudio/best[height<=360]", "720p": "bestvideo[height<=720]+bestaudio/best[height<=720]", "1080p": "bestvideo[height<=1080]+bestaudio/best[height<=1080]" } ydl_opts = { "format": resolution_map.get(resolution, "bestvideo[height<=720]+bestaudio/best[height<=720]"), # Standard: 720p "outtmpl": output_template, "merge_output_format": "mp4", "quiet": True, "progress_hooks": [self.progress_hook], "nopart": False, } try: with yt_dlp.YoutubeDL(ydl_opts) as ydl: self.status_label.config(text="Download läuft...") self.download_button.config(state="disabled") ydl.download([url]) if not os.path.exists(mp4_file): raise Exception("MP4-Datei wurde nicht erstellt") if format_choice == "MP3": self.status_label.config(text="Konvertiere zu MP3...") ffmpeg_cmd = [ "ffmpeg", "-i", mp4_file, "-vn", "-acodec", "mp3", "-ab", "192k", "-y", mp3_file ] result = subprocess.run(ffmpeg_cmd, capture_output=True, text=True) if result.returncode != 0: raise Exception(f"Konvertierung fehlgeschlagen: {result.stderr}") os.remove(mp4_file) final_file = mp3_file file_extension = "mp3" else: final_file = mp4_file file_extension = "mp4" self.status_label.config(text=f"Download abgeschlossen: {filename}.{file_extension}") self.progress["value"] = 100 messagebox.showinfo("Erfolg", f"Datei wurde als {filename}.{file_extension} gespeichert!") except Exception as e: messagebox.showerror("Fehler", f"Download oder Konvertierung fehlgeschlagen: {str(e)}") self.status_label.config(text="Fehler beim Download/Konvertierung") finally: time.sleep(5) # Erhöhte Wartezeit auf 5 Sekunden self.cleanup_temp_files(save_path, filename) self.download_button.config(state="normal") def progress_hook(self, d): if d["status"] == "downloading": total = d.get("total_bytes") or d.get("total_bytes_estimate", 0) downloaded = d.get("downloaded_bytes", 0) if total > 0: percentage = (downloaded / total) * 100 self.progress["value"] = percentage self.root.update_idletasks() elif d["status"] == "finished": self.progress["value"] = 100 def cleanup_temp_files(self, save_path, filename): self.status_label.config(text="Bereinige temporäre Dateien...") # Erweiterte Liste temporärer Endungen und Muster temp_patterns = [ ".part", ".ytdl", ".temp", r"\.f\d+", r"\.part-Frag\d*", "*.webm", "*.part", "*.temp", "*.ytdl", "*.mhtml", "*.part-*" ] deleted_files = [] remaining_files = [] try: # Kombiniere alle möglichen temporären Dateimuster search_patterns = [ os.path.join(save_path, f"{filename}*"), os.path.join(save_path, f"*{filename}*") ] # Durchsuche alle Muster for pattern in search_patterns: for temp_file in glob.glob(pattern): # Überprüfe, ob die Datei ein temporäres Muster enthält if any(fnmatch.fnmatch(os.path.basename(temp_file), pat) for pat in temp_patterns): try: os.remove(temp_file) deleted_files.append(os.path.basename(temp_file)) except Exception as e: remaining_files.append(f"{os.path.basename(temp_file)} (Fehler: {str(e)})") # Zusätzliche Bereinigung für spezifische Dateitypen extra_patterns = [ os.path.join(save_path, f"{filename}*.webm"), os.path.join(save_path, f"{filename}*.part"), os.path.join(save_path, f"{filename}*.temp") ] for pattern in extra_patterns: for extra_file in glob.glob(pattern): try: os.remove(extra_file) if os.path.basename(extra_file) not in deleted_files: deleted_files.append(os.path.basename(extra_file)) except Exception as e: if os.path.basename(extra_file) not in remaining_files: remaining_files.append(f"{os.path.basename(extra_file)} (Fehler: {str(e)})") except Exception as global_error: self.status_label.config(text=f"Kritischer Bereinigungsfehler: {str(global_error)}") # Statusmeldung mit Details if deleted_files: self.status_label.config(text=f"Gelöscht: {', '.join(deleted_files)}") if remaining_files: messagebox.showwarning("Bereinigungswarnungen", f"Folgende Dateien konnten nicht gelöscht werden:\n{', '.join(remaining_files)}") else: self.status_label.config(text="Bereinigung abgeschlossen") def start_download(self): url = self.url_entry.get().strip() save_path = self.save_path_var.get().strip() filename = self.filename_entry.get().strip() if not url: messagebox.showwarning("Eingabe fehlt", "Bitte gib eine YouTube-URL ein!") return if not save_path: messagebox.showwarning("Eingabe fehlt", "Bitte wähle einen Speicherort!") return if not filename: messagebox.showwarning("Eingabe fehlt", "Bitte gib einen Dateinamen ein!") return self.progress["value"] = 0 threading.Thread(target=self.download_video, args=(url, save_path, filename), daemon=True).start() def on_closing(self): self.root.destroy() if __name__ == "__main__": root = tk.Tk() app = YouTubeDownloader(root) root.protocol("WM_DELETE_WINDOW", app.on_closing) root.mainloop()
Datenablauf: Alles beginnt mit der YouTube-URL, die an yt-dlp übergeben wird. Diese Bibliothek kontaktiert den YouTube-Server, analysiert die verfügbaren Formate und lädt die passenden Streams (Video und Audio) herunter. Die Daten werden zunächst als temporäre Dateien auf die Festplatte geschrieben. Bei MP4 werden Video- und Audiospur direkt zu einer finalen Datei gemerged; bei MP3 erfolgt nach dem Download eine Konvertierung mit ffmpeg, wobei die MP4-Zwischendatei gelöscht wird. Der Fortschrittsbalken wird über einen „progress_hook“ aktualisiert, der die heruntergeladenen Bytes mit der Gesamtgröße vergleicht. Am Ende liegt die fertige Datei im gewählten Ordner – bereit zur Nutzung.
Notwendige Abhängigkeiten: Damit das Skript läuft, brauchst du Python 3, yt-dlp („pip3 install yt-dlp“), ffmpeg („sudo apt install ffmpeg“) und tkinter, das meist vorinstalliert ist. Für die kompilierte Version unter Debian/Ubuntu habe ich PyInstaller verwendet („pyinstaller –onefile“), sodass du nur die ausführbare Datei brauchst. Der Quellcode ist Open Source und frei verfügbar – ideal zum Anpassen oder Erweitern.
Installiere folgende Abhängigkeiten:
pip3 install yt-dlp sudo apt update sudo apt install ffmpeg
Fazit: Dieses Tool ist ein praktischer Helfer für alle, die YouTube-Inhalte offline nutzen möchten, ohne sich mit komplizierten Kommandozeilen herumzuschlagen. Die Kombination aus einfacher Bedienung, flexibler Auflösungswahl und Open-Source-Charakter macht es zu einem Gewinn für Bastler und Technikfans. Ich nutze es selbst regelmäßig – und mit ein paar Zeilen Code kannst du es an deine Bedürfnisse anpassen.
Auch interessant:
|
Vom Video zur Playlist: YouTube-Downloads mit kostenlosen Tools meistern – 17. März 2025: YouTube ist eine Schatztruhe voller Videos und Musik, doch die Inhalte einfach herunterzuladen, ist oft eine Herausforderung. Browser-Plug-ins für Firefox, die früher funktionierten, sind meist veraltet oder inkompatibel. Zum Glück gibt es Alternativen! In diesem Artikel zeige ich dir, wie du mit dem 4K Video Downloader, Audacity, MusicBrainz Picard und ein paar Python-Skripten Videos und Musik von YouTube herunterladen, bearbeiten und organisieren kannst. |