YouTube-Downloader lädt automatisch komplette Playlists als MP3 herunter und erstellt M3U-Playlists

22. März 2025 (zuletzt aktualisiert am 23. März 2025)

Komplette YouTube-Playlists auf einen Schlag als MP3 runterladen und direkt eine M3U-Playlist erstellen – das ist die Idee hinter meinem neuesten Python-Projekt. Mit einer einfachen grafischen Oberfläche und der Hilfe von Grok 3, entwickelt von xAI, habe ich ein Skript geschrieben, das Playlists von YouTube automatisch herunterlädt, in MP3 umwandelt und übersichtlich organisiert. Hier teile ich den Quellcode und erzähle, wie das Programm entstanden ist.

Bildschirmfoto zu 2025 03 22 18 25 10
Für den den Download kompletter Youtube-Playlists wurden auf einfachste Bedienung geachtet. Bisher wurde das Programm auf Ubuntu getestet.

Download des Python-Skripts mit Zip gepackt: youtube-playlist-downloader.zip

Falls du dieses Python-Skript unter Windows laufen lassen möchtest, befindet sich unter https://grok.com/share/bGVnYWN5_ab3d92b7-4d4d-46ee-9ae3-80faca4f1e7f eine Anleitung dafür.

Download als ausführbares Programm für Debian / Ubuntu mit Zip gepackt:
http://www.janson-soft.de/ubuntu/yt-playlist-downloader.zip

Damit das ausführbare Programm auf deinem Linux-Rechner läuft, muss ffmpeg installiert sein. Das machst du wie folgt:

sudo apt update && sudo apt install ffmpeg

ffmpeg wird benötigt, um die mp4-Dateien nach mp3 umwandeln zu können.

Urheber- und Nutzungsrechte: Ein wichtiger Hinweis: Beim Herunterladen von YouTube-Videos musst du die Urheber- und Nutzungsrechtsrechte beachten, die von Land zu Land unterschiedlich sind. In Deutschland ist das Herunterladen für den privaten Gebrauch oft erlaubt, solange du die Dateien nicht weiterverbreitest – aber das gilt nicht überall. In den USA kann schon das Umgehen von Kopierschutzmechanismen rechtlich heikel sein. Prüfe also die Gesetze in deinem Land, bevor du das Skript nutzt, und lade nur Inhalte herunter, bei denen du sicher bist, dass es erlaubt ist (z. B. Creative-Commons-Videos oder eigene Inhalte). Ich übernehme keine Haftung für Missbrauch!

Bedienung und Nutzerfreundlichkeit: Das Programm ist bewusst einfach gehalten, damit jeder es nutzen kann. Nach dem Start öffnet sich ein Fenster mit einem blaugrauen Design, in dem man die YouTube-URL einer Playlist oder eines Videos eingibt. Darunter wählt man den Speicherort und entscheidet, ob MP3 oder MP4 heruntergeladen werden soll – meistens nehme ich MP3, das ist praktischer für Playlists. Ein Klick auf „Download starten“, und los geht’s. Was das Skript besonders nutzerfreundlich macht? Es legt automatisch einen Unterordner wie „downloads01“ an (oder „downloads02“, falls der erste schon existiert), in dem alle Dateien – MP4, MP3, temporäre Dateien und die M3U-Playlist – landen. So bleibt der Speicherort aufgeräumt, und man kann den Ordner später umbenennen, z. B. in „Sommerhits 2025“. MP4-Dateien werden direkt in MP3 umgewandelt, und wenn ein Video in der Playlist fehlt oder nicht verfügbar ist, überspringt das Programm es einfach, ohne abzubrechen. Am Ende erstellt es automatisch eine M3U-Playlist mit allen MP3s, die man sofort in einem Player wie VLC abspielen kann. Ein Fortschrittsbalken und ein Log-Fenster zeigen dabei live, was passiert – vom Herunterladen bis zur Konvertierung.

So findest du die URL für die Youtube-Playlist: Die nachfolgenden Screenschots geben dir eine Schritt für Schritt eine Anleitung. Suche in Youtube nach „Jazz Playlists“ oder so ähnlich. Die nachfolgenden Screenshots erklären, wie du zu der URL einer Youtube-Playlist kommst und sie in die Zwischenablage kopierst:

View full playlist
1. Klicke auf „View full playlist“
Bildschirmfoto zu 2025 03 22 17 52 22
2. Klicke auf das Pfeil-Symbol, das hier eingekreist ist.
Bildschirmfoto zu 2025 03 22 17 54 10
3. Klicke auf „Copy“. Danach befindet sich die URL der Playlist in der Zwischenablage.

Das Beispiel zeigt nur, wie du die URL teilen kannst. Das bedeutet aber nicht, dass du sie für einen Download benutzen darfst! Abgesehen davon würde ich nicht gleich beim ersten Versuch Playlisten mit mehreren Hundert Stücken herunterladen.

Bitte beachte beim Download die Urheber- und Nutzungsrechte bevor du auf „Download starten“ klickst. Es wird dann ein neue Unterordner angelegt in dem die MP4-Videos landen, die dann in MP3-Audiodaten umgewandelt werden. Danach werden die MP4-Videos und temporären Dateien gelöscht. Schließlich erzeugt das Programm automatisch eine M3U-Playlist, die man zum Beispiel bequem mit VLC von Videolan oder einem anderen Player abspielen kann.

Entwicklung mit Grok 3: Dieses Programm entstand innerhalb weniger Stunden – und das verdanke ich Grok 3. Ich hatte die Idee, Playlists von YouTube schnell herunterzuladen, und habe Grok einfach gefragt, wie man das mit Python umsetzen könnte. Schritt für Schritt haben wir es zusammen entwickelt: Erst ein Grundgerüst mit tkinter für die GUI und yt-dlp für den Download, dann die MP3-Konvertierung mit ffmpeg und schließlich die automatische Playlist-Erstellung. Zwischendurch lief nicht alles glatt – mal lud es nur ein Video, mal stimmte der Fortschritt nicht. Aber Grok hat geduldig jede Fehlermeldung analysiert und Verbesserungen vorgeschlagen. Während ich wartete, habe ich die Küche aufgeräumt, unseren Saugroboter repariert und mit meiner Katze gespielt – die perfekte Mischung aus Produktivität und Entspannung! Am Ende hatten wir in unter vier Stunden ein voll funktionsfähiges Tool. Das zeigt, wie mächtig KI sein kann: Ich musste keine komplexen Tutorials wälzen, sondern konnte einfach meine Ideen beschreiben und bekam sofort brauchbare Lösungen.

Funktion: Technisch kombiniert das Skript mehrere Werkzeuge. Die Oberfläche basiert auf tkinter, angepasst mit einem kräftigen blaugrauen Farbschema für einen modernen Look. Für den Download kommt yt-dlp zum Einsatz, eine Weiterentwicklung von youtube-dl, die Playlists zuverlässig abarbeitet. Die MP4-Dateien werden mit ffmpeg in MP3 umgewandelt, wobei temporäre Dateien danach automatisch gelöscht werden. Ein „progress hook“ sorgt dafür, dass der Fortschrittsbalken den aktuellen Stand zeigt, und ein benutzerdefinierter Logger leitet die Ausgaben von yt-dlp ins Log-Fenster – so sieht man genau, was hinter den Kulissen passiert. Der Unterordner-Mechanismus prüft vor jedem Download, ob „downloads01“ existiert, und zählt bei Bedarf hoch („downloads02“, „downloads03“, usw.). Das alles läuft in einem separaten Thread, damit die GUI reaktiv bleibt. Die M3U-Playlist wird am Schluss aus allen MP3s im Ordner generiert – simpel, aber effektiv.

Notwendige Abhängigkeiten: Damit das Skript läuft, braucht man ein paar Dinge. Python 3 und pip sollten auf den meisten Systemen vorhanden sein. Die GUI nutzt tkinter, das normalerweise mit Python kommt. Für den Download installiert man yt-dlp mit „pip install yt-dlp“, und für die MP3-Konvertierung braucht man ffmpeg, z. B. unter Linux mit „sudo apt install ffmpeg“ oder unter Windows/macOS über die offizielle Seite. Ein Kommando für Linux, um alles auf einmal zu holen, wäre:

sudo apt-get update && sudo apt-get install -y python3-tk ffmpeg && pip install yt-dlp

Da meistens unter Linux (Debian, Ubuntu) Python 3 schon installiert ist, reichen folgende Befehle:

sudo apt-get update
sudo apt-get install ffmpeg
sudo pip install yt-dlp

Ohne ffmpeg funktioniert die Konvertierung nicht, und ohne Internet geht gar nichts, da yt-dlp die Videos online abruft.

Das Python-Skript:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import yt_dlp
import os
import threading
import time
import subprocess
import re

class YouTubeDownloader:
    def __init__(self, root):
        self.root = root
        self.root.title("YouTube Downloader")
        self.root.geometry("500x500")
        self.root.configure(bg="#8A9EB2")

        self.total_videos = 0
        self.current_video = 0
        self.skipped_videos = 0
        self.download_dir = None  # Wird später gesetzt
        self.create_widgets()

    def create_widgets(self):
        style = ttk.Style()
        style.configure("TProgressbar", background="#55768A")
        style.configure("TButton", background="#7A8FA3", foreground="#1F2D3A")
        style.configure("TCombobox", fieldbackground="#A3B7C9", foreground="#1F2D3A")

        url_label = tk.Label(self.root, text="YouTube URL (Video oder Playlist):", bg="#8A9EB2", fg="#1F2D3A")
        url_label.pack(pady=10, anchor="center")
        self.url_entry = tk.Entry(self.root, width=50, bg="#A3B7C9", fg="#1F2D3A", insertbackground="#1F2D3A")
        self.url_entry.pack(pady=5, anchor="center")

        save_label = tk.Label(self.root, text="Speicherort:", bg="#8A9EB2", fg="#1F2D3A")
        save_label.pack(pady=10, anchor="center")
        save_frame = tk.Frame(self.root, bg="#8A9EB2")
        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="#A3B7C9", fg="#1F2D3A", insertbackground="#1F2D3A")
        self.save_path_entry.pack(side=tk.LEFT, pady=5)
        browse_button = tk.Button(save_frame, text="Durchsuchen", command=self.browse_save_path, bg="#7A8FA3", fg="#1F2D3A")
        browse_button.pack(side=tk.LEFT, padx=5)

        format_label = tk.Label(self.root, text="Format wählen:", bg="#8A9EB2", fg="#1F2D3A")
        format_label.pack(pady=10, anchor="center")
        self.format_var = tk.StringVar(value="MP3")
        format_combo = ttk.Combobox(self.root, textvariable=self.format_var, values=["MP3", "MP4"], state="readonly", style="TCombobox")
        format_combo.pack(pady=5, anchor="center")

        self.download_button = tk.Button(self.root, text="Download starten", command=self.start_download, bg="#7A8FA3", fg="#1F2D3A")
        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="#8A9EB2", fg="#1F2D3A")
        self.status_label.pack(pady=5, anchor="center")

        self.log_text = tk.Text(self.root, height=10, width=60, bg="#A3B7C9", fg="#1F2D3A", wrap=tk.WORD)
        self.log_text.pack(pady=10, anchor="center")
        self.log_text.insert(tk.END, "Logausgaben erscheinen hier...\n")

    def browse_save_path(self):
        folder = filedialog.askdirectory()
        if folder:
            self.save_path_var.set(folder)

    def create_download_dir(self, base_path):
        """Erstellt einen Unterordner downloadsXX und gibt den Pfad zurück."""
        i = 1
        while True:
            download_dir = os.path.join(base_path, f"downloads{i:02d}")
            if not os.path.exists(download_dir):
                os.makedirs(download_dir)
                self.log(f"Unterordner erstellt: {download_dir}")
                return download_dir
            i += 1

    def sanitize_filename(self, filename):
        return re.sub(r'[<>:"/\\|?*]', '', filename)

    def log(self, message):
        self.log_text.insert(tk.END, f"{message}\n")
        self.log_text.see(tk.END)
        self.root.update_idletasks()

    class YTDLPLogger:
        def __init__(self, log_func):
            self.log_func = log_func

        def debug(self, msg):
            self.log_func(msg)

        def info(self, msg):
            self.log_func(msg)

        def warning(self, msg):
            self.log_func(f"WARNING: {msg}")

        def error(self, msg):
            self.log_func(f"ERROR: {msg}")

    def download_content(self, url, save_path):
        format_choice = self.format_var.get()
        self.download_dir = self.create_download_dir(save_path)
        output_template = os.path.join(self.download_dir, "%(title)s.%(ext)s")

        if "&si=" in url:
            url = url.split("&si=")[0]

        ydl_opts = {
            "format": "bestvideo+bestaudio/best",
            "outtmpl": output_template,
            "merge_output_format": "mp4",
            "quiet": False,
            "progress_hooks": [self.progress_hook],
            "nopart": False,
            "ignoreerrors": True,
            "logger": self.YTDLPLogger(self.log),
        }

        try:
            with yt_dlp.YoutubeDL(ydl_opts) as ydl:
                self.status_label.config(text="Prüfe URL...")
                self.log("Prüfe URL...")
                info = ydl.extract_info(url, download=False)
                if "entries" in info:
                    self.total_videos = len(info["entries"])
                    self.status_label.config(text=f"Playlist erkannt: {self.total_videos} Videos")
                    self.log(f"{self.total_videos} Videos in der Playlist erkannt")
                else:
                    self.total_videos = 1
                    self.status_label.config(text="Einzelvideo erkannt")
                    self.log("Einzelvideo erkannt")
                self.current_video = 0
                self.skipped_videos = 0
                self.download_button.config(state="disabled")
                ydl.download([url])

            if format_choice == "MP3":
                self.convert_to_mp3(self.download_dir)
                self.create_m3u_playlist(self.download_dir)

            self.status_label.config(text=f"Download abgeschlossen! ({self.skipped_videos} Videos übersprungen)")
            self.log(f"Download abgeschlossen! {self.skipped_videos} Videos übersprungen in {self.download_dir}")
            self.progress["value"] = 100
            messagebox.showinfo("Erfolg", f"Download abgeschlossen in {self.download_dir}! {self.skipped_videos} Videos wurden übersprungen.")
        except Exception as e:
            messagebox.showerror("Allgemeiner Fehler", f"Ein Fehler ist aufgetreten: {str(e)}")
            self.status_label.config(text=f"Fehler: {str(e)}")
            self.log(f"Allgemeiner Fehler: {str(e)}")
        finally:
            time.sleep(2)
            self.cleanup_temp_files(self.download_dir)
            self.download_button.config(state="normal")

    def convert_to_mp3(self, download_dir):
        self.status_label.config(text="Konvertiere zu MP3...")
        self.log("Konvertiere zu MP3...")
        for file in os.listdir(download_dir):
            if file.endswith(".mp4"):
                mp4_file = os.path.join(download_dir, file)
                mp3_filename = self.sanitize_filename(file.replace(".mp4", ".mp3"))
                mp3_file = os.path.join(download_dir, mp3_filename)
                
                if not os.path.exists(mp4_file):
                    self.status_label.config(text=f"Fehler: {mp4_file} existiert nicht")
                    self.log(f"MP4-Datei {mp4_file} existiert nicht")
                    continue
                
                ffmpeg_cmd = [
                    "ffmpeg", "-i", mp4_file, "-vn", "-acodec", "mp3", "-ab", "192k", "-y", mp3_file
                ]
                try:
                    result = subprocess.run(ffmpeg_cmd, capture_output=True, text=True)
                    if result.returncode == 0 and os.path.exists(mp3_file):
                        os.remove(mp4_file)
                        self.status_label.config(text=f"Konvertiert: {mp3_filename}")
                        self.log(f"Konvertiert: {mp3_filename}")
                    else:
                        raise Exception(f"FFmpeg Fehler: {result.stderr}")
                except Exception as e:
                    self.status_label.config(text=f"Konvertierung fehlgeschlagen für {file}: {str(e)}")
                    self.log(f"Konvertierung fehlgeschlagen für {file}: {str(e)}")

    def create_m3u_playlist(self, download_dir):
        self.status_label.config(text="Erstelle M3U-Playlist...")
        self.log("Erstelle M3U-Playlist...")
        m3u_file = os.path.join(download_dir, "playlist.m3u")
        mp3_files = sorted([f for f in os.listdir(download_dir) if f.lower().endswith('.mp3')])

        if not mp3_files:
            self.status_label.config(text="Keine MP3-Dateien für Playlist gefunden")
            self.log("Keine MP3-Dateien für Playlist gefunden")
            return

        with open(m3u_file, 'w', encoding='utf-8') as playlist:
            playlist.write("#EXTM3U\n")
            for mp3 in mp3_files:
                playlist.write(f"#EXTINF:-1,{mp3}\n{mp3}\n")
                self.log(f"{mp3} zur Playlist hinzugefügt")

        self.status_label.config(text=f"M3U-Playlist erstellt: {m3u_file}")
        self.log(f"M3U-Playlist erstellt: {m3u_file}")

    def progress_hook(self, d):
        if d["status"] == "downloading":
            self.current_video = d.get("_default_playlist_index", self.current_video + 1) or 1
            total = d.get("total_bytes") or d.get("total_bytes_estimate", 0)
            downloaded = d.get("downloaded_bytes", 0)
            if total > 0:
                video_progress = (downloaded / total) * 100
                overall_progress = ((self.current_video - 1) * 100 + video_progress) / self.total_videos
                self.progress["value"] = overall_progress
                self.status_label.config(text=f"Video {self.current_video}/{self.total_videos} ({int(overall_progress)}%)")
                self.log(f"Video {self.current_video}/{self.total_videos} wird heruntergeladen ({int(video_progress)}%)")
                self.root.update_idletasks()
        elif d["status"] == "finished":
            self.current_video = d.get("_default_playlist_index", self.current_video + 1) or 1
            self.status_label.config(text=f"Video {self.current_video}/{self.total_videos} abgeschlossen")
            self.log(f"Video {self.current_video} abgeschlossen")
        elif d["status"] == "error":
            self.skipped_videos += 1
            self.current_video = d.get("_default_playlist_index", self.current_video + 1) or 1
            self.status_label.config(text=f"Video {self.current_video} übersprungen (Fehler)")
            self.log(f"Video {self.current_video} übersprungen")

    def cleanup_temp_files(self, download_dir):
        self.status_label.config(text="Bereinige temporäre Dateien...")
        self.log("Bereinige temporäre Dateien...")
        temp_extensions = [".part", ".f[0-9]*", ".ytdl"]
        for file in os.listdir(download_dir):
            if any(file.endswith(ext) or "Frag" in file for ext in temp_extensions):
                file_path = os.path.join(download_dir, file)
                if os.path.exists(file_path):
                    for _ in range(5):
                        try:
                            os.remove(file_path)
                            self.status_label.config(text=f"Gelöscht: {file}")
                            self.log(f"Temporäre Datei {file} gelöscht")
                            break
                        except PermissionError:
                            time.sleep(1)
                        except Exception as e:
                            self.status_label.config(text=f"Konnte {file} nicht löschen: {str(e)}")
                            self.log(f"Fehler beim Löschen von {file}: {str(e)}")
                            break
        self.status_label.config(text="Bereinigung abgeschlossen")
        self.log("Bereinigung abgeschlossen")

    def start_download(self):
        url = self.url_entry.get().strip()
        save_path = self.save_path_var.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

        self.progress["value"] = 0
        threading.Thread(target=self.download_content, args=(url, save_path), 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()

Fazit: Dieses kleine Tool ist ein praktischer Helfer für alle, die YouTube-Playlists offline hören wollen – sei es für Musik, Podcasts oder Tutorials. Der Quellcode ist quelloffen und kann frei angepasst werden – lade ihn hier herunter (rechte Maustaste, „Ziel speichern unter“). Dank Grok 3 war die Entwicklung ein Kinderspiel, und ich bin begeistert, wie KI Hobbyprogrammierern wie mir hilft, Ideen in Stunden statt Tagen umzusetzen. Die Welt verändert sich rasant, und ich hoffe, dass dieser Artikel andere inspiriert, selbst mit KI zu experimentieren!

Auch interessant:

Bildschirmfoto zu 2025 03 20 16 58 50
Youtube-Downloader, quelloffen, auf Python 3 geschrieben, einfache Bedienung.
YouTube-Downloader: Simpel, kostenlos, ohne Limits, Open Source – 20. März 2025: Ein zuverlässiger YouTube-Downloader ist heutzutage schwer zu finden – viele kostenlose Tools funktionieren nicht mehr, sind eingeschränkt oder gar nicht kostenlos. Deshalb habe ich ein eigenes Python-Skript entwickelt, das YouTube-Videos als MP4 oder MP3 herunterlädt, mit einer einfachen GUI und ohne Einschränkungen. Hier erzähle ich, wie es entstanden ist, wie es funktioniert und was du brauchst, um es selbst zu nutzen. – weiter

Bildschirmfoto zu 2025 03 17 13 45 41
Mit kostenlosen Tools und Programmen baust du dir deine persönliche und private Musiksammlung auf.
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. – weiter

Bildschirmfoto zu 2025 03 15 15 59 47
Die Oberfläche ist bewusst einfach gehalten. Einfache Bedienung und Funktionalität des Pythonskripts stand bei der Entwicklung im Vordergrund.
Webradio mit Python: HTTP-Streaming für MP3 und M3U leicht gemacht – 16. März 2025: Ich habe schon länger mit dem Gedanken gespielt, einen eigenen Webradio-Stream für mein lokales Netzwerk einzurichten – idealerweise mit einer einfachen grafischen Oberfläche, um die Songs und Playlists zu verwalten. Nach einigen Experimenten und Fehlschlägen habe ich endlich ein funktionierendes Python-Skript erstellt, das genau das macht: Es streamt MP3-Dateien oder M3U-Playlists über HTTP, sodass man sie mit einem Client wie VLC abspielen kann. – weiter