Programm: YTDL

Von Zeit zu Zeit entwickle ich massgeschneiderte Softwarelösungen, um den Alltag meiner Kunden zu erleichtern. Ein Kunde stand vor der Herausforderung, MP3-Dateien von YouTube herunterzuladen. Bisher hat er dies über verschiedene Drittanbieter-Websites getan. Da sich diese Seiten jedoch ständig ändern (oftmals, um mehr Werbung zu schalten), war dieser Prozess nicht mehr reibungslos.

Um dieses Problem zu lösen, habe ich ein einfaches Skript entwickelt, das den Download-Prozess vereinfacht und meinen Kunden von der Notwendigkeit befreit, sich auf Drittanbieter-Websites zu verlassen.

In der ersten Version des Programms versuchte ich, mit möglichst wenigen Bibliotheken auszukommen, um das Programm so schlank wie möglich zu halten. Der Benutzer musste den Browser öffnen, ein Video auf YouTube auswählen, die Adresse des Videos in mein Programm kopieren, auf Herunterladen klicken und den Speicherort der MP3-Datei auswählen. Nach dem Download war das Programm bereit für den nächsten Download.

Mir wurde schnell klar, dass dies nicht zielführend ist. Der Prozess war zu kompliziert. Benutzer, die in der Lage waren, diese Schritte auszuführen, konnten wahrscheinlich auch ohne mein Programm MP3-Dateien von YouTube herunterladen.

Daher habe ich das Skript weiterentwickelt, um den Prozess zu vereinfachen und benutzerfreundlicher zu gestalten. In der nächsten Iteration meines Programms konnte man es öffnen, ein Klick weiter öffnete direkt den Browser mit der youtube.com Domain und überwachte ab dann die Zwischenablage. Sobald erkannt wurde, dass es sich um eine korrekte Video-URL von Youtube handelt wurde diese weiter überprüft um zu sehen, ob sich eine mp3-Datei herunterladen lässt. Falls dies auch klappte wurde die Datei direkt heruntergeladen und im selben Verzeichnis, wo sich mein Programm befand, gespeichert. Firefox wurde automatisch geschlossen, nach erfolgreichem Download auch mein Programm um danach direkt ein Fenster zu öffnen, in welchem sich die mp3-Datei befand.


Grösste Unterschiede?

Erste Version (11-Schritte):

  • Programm öffnen
  • Firefox öffnen
  • Youtube öffnen
  • Video suchen
  • URL kopieren
  • Firefox schliessen
  • URL einfügen
  • herunterladen klicken
  • Speicherort auswählen
  • OK drücken
  • Programm schliessen
  • (suchen wo die mp3-Datei gelanden sein könnte…)

Zweite Version (4-Schritte):

  • Programm öffnen
  • Youtube klicken
  • Video suchen
  • URL kopieren

Kunde zufrieden, ich zufrieden und viel dazu gelernt, alles in allem wunderbar. Seither verstehe ich viel besser, warum Entwickler meistens am User vorbeiprogrammieren. Vieles, was aus meiner Sicht ja ganz klar und einfach ist sowie Möglichkeiten für Weiteres bieten würde, ist einfach nicht zielführend.
Auf einem Windows-PC bei solch einem kleinen Programm auf Effizienz achten zu wollen ist zum Beispiel überhaupt nicht notwendig, da die Rechenpower der allermeisten Computer heutzutage gross genug ist auch „Murksprogramme“ wie meines auszuführen.
Auch macht es keinen Sinn, weitere mp3’s herunterladen zu können, wenn er doch nur ein File will…


Script

import yt_dlp
import os
import tkinter as tk
import tkinter.filedialog as fd
import webbrowser
import pyperclip
import re
import subprocess
import psutil
import time
import sys

# globale Variablen
video_title = ""
filename = ""

# Funktion Youtube-öffnen
def open_website():
    webbrowser.open_new("https://www.youtube.com/")
    yt_open_status.config(text="\u2713", fg="light grey")
    yt_linkcheck_status.config(text="\u2713", fg="orange")
    yt_open.config(text="")
    check_clipboard(counter_label)
    open_website_button.config(state="disabled")

# Funktion zur Überwachung der Zwischenablage
def check_clipboard(counter_label):
    current_clipboard = pyperclip.paste()
    # Überprüfen, ob die Zwischenablage eine YouTube-URL enthält
    if "youtube.com" in current_clipboard:
        # Zwischenablage löschen
        pyperclip.copy("")
        # Video-Info abrufen (ist video? ist kürzer als 20min?)
        youtube_url_regex = r"https?://(www\.)?youtube\.com/watch\?v=[\w-]+(&\S*)?"
        if re.match(youtube_url_regex, current_clipboard):
            with yt_dlp.YoutubeDL() as ydl:
                try:
                    video_info = ydl.extract_info(current_clipboard, download=False)
                except yt_dlp.utils.DownloadError:
                    # URL ist ungültig oder das Video ist privat/gelöscht
                    return
                # Überprüfen, ob das Video kürzer als 20 Minuten ist
                if "duration" in video_info and video_info["duration"] <= 1200: # = 20min x 60s
                    close_button.config(state="disabled")
                    global video_title
                    with yt_dlp.YoutubeDL() as ydl:
                        video_info = ydl.extract_info(current_clipboard, download=False)
                        try:
                            video_info = ydl.extract_info(current_clipboard, download=False)
                            video_title = video_info["title"]
                        except yt_dlp.utils.DownloadError:
                            return
                    # URL in das Benutzereingabefeld einfügen
                    url_entry2.delete(0, tk.END)
                    url_entry2.insert(0, current_clipboard)
                    counter_label.config(text="Bitte warten!", fg="green")
                    yt_linkcheck_status.config(text="\u2713", fg="green")
                    url_entry2.config(state="disabled")
                    close_firefox()
                    download_audio()
                    return
    # Vorherige Zwischenablage speichern
    previous_clipboard = current_clipboard
    # Zähler aktualisieren
    counter_label.config(text="Überwache Zwischenablage seit {} Sekunden.".format(counter_label.counter))
    counter_label.counter += 1
    # Zwischenablage jede Sekunde prüfen.
    root.after(1000, check_clipboard, counter_label)



# Funktion zum Herunterladen der Audio-Datei
def download_audio():
    global video_title
    global filename
    filename = fd.asksaveasfilename(filetypes=[("MP3 Dateien", "*.mp3")], initialfile=video_title)
    url = url_entry2.get()
    ydl_opts = {
        'format': 'bestaudio/best',
        'postprocessors': [{
            'key': 'FFmpegExtractAudio',
            'preferredcodec': 'mp3',
            'preferredquality': '320'
        }],
        'outtmpl': filename,
    }
    with yt_dlp.YoutubeDL(ydl_opts) as ydl:
        ydl.download([url])
    # Löschen der .webm-Datei
    for file in os.listdir():
        if file.endswith(".webm"):
            os.remove(file)
    close_button.config(text="Fertig")

    if not filename.endswith(".mp3"):
        filename += ".mp3"
    exit_program()

# Funktion: firefox.exe beenden
def close_firefox():
    while True:
        firefox_procs = [proc.info['pid'] for proc in psutil.process_iter(['pid', 'name']) if proc.info['name'] == 'firefox.exe']
        if not firefox_procs:
            break
        for pid in firefox_procs:
            subprocess.run(['taskkill', '/T', '/F', '/PID', str(pid)])
        time.sleep(1)

# Funktion eigenes Programm beenden
def exit_program():
    global filename
    if filename == "":
        root.after(1000, root.quit())
    else: 
        subprocess.Popen(r'explorer /select,"{0}"'.format(filename.replace("/", "\\")))
        root.quit()



# __ ___________ ___________ ___________ ___________ ___________ ___________ ___________ ___________ __________

# GUI-Hauptfenster-Fenster erstellen
root = tk.Tk()
root.title("YouTubeDL")
# root.geometry("400x350+1300+10")
root.resizable(False, False)


# Reihe 1, Positionsnummer
label_1 = tk.Label(root, text="1.")
label_1.grid(row=1, column=0)

# Haken
yt_open_status = tk.Label(root, text="\u2713", fg="orange", font=("Arial", 16))
yt_open_status.grid(row=1, column=1)

# Button: Youtube öffnen
open_website_button = tk.Button(root, text="Youtube öffnen", command=open_website)
open_website_button.grid(row=1, column=2, pady=20)

# Beschreibung
yt_open = tk.Label(root, text="Lied suchen & Adresse kopieren")
yt_open.grid(row=1, column=3)

# Reihe 2, Positionsnummer
url_label = tk.Label(root, text="2:")
url_label.grid(row=2, column=0)

# Haken
yt_linkcheck_status = tk.Label(root, text="\u2717", font=("Arial", 16))
yt_linkcheck_status.grid(row=2, column=1)


# Eingabefeld Youtube-URL
url_entry2 = tk.Entry(root)
url_entry2.grid(row=2, column=2, columnspan=2, sticky="ew")

# Label für die Anzeige der Zwischenablage-Überwachung
counter_label = tk.Label(root, text="")
counter_label.grid(row=3, column=0, columnspan=4, sticky="ew")
counter_label.counter = 0

# Schließen-Button hinzufügen
close_button = tk.Button(root, text="Schließen", command=exit_program)
close_button.grid(row=6, column=0, columnspan=4, sticky="ew", pady=30)



# GUI ausführen
root.mainloop()

verwendete Bibliotheken

yt_dlp

Dies ist eine Bibliothek, die zum Herunterladen von Videos und Audiodateien von YouTube und anderen Video-Hosting-Websites verwendet wird. Sie bietet eine Vielzahl von Funktionen, einschliesslich der Möglichkeit, Metadaten zu extrahieren und Videos in verschiedene Formate zu konvertieren.

os

Diese Bibliothek bietet Funktionen für die Interaktion mit dem Betriebssystem. Sie wird in einer Vielzahl von Fällen verwendet, einschliesslich der Arbeit mit Dateien und Verzeichnissen.

tkinter

Dies ist eine Standardbibliothek für die Erstellung von grafischen Benutzeroberflächen (GUIs) in Python. Sie bietet eine Vielzahl von Widgets, einschliesslich Schaltflächen, Menüs und Textfelder.

tkinter.filedialog

Dies ist ein Modul innerhalb von tkinter, das Dialoge zur Dateiauswahl bereitstellt. Es ermöglicht Benutzern, Dateien und Verzeichnisse über ein GUI auszuwählen.

webbrowser

Diese Bibliothek bietet eine Schnittstelle zur Anzeige von Webdokumenten. Sie kann verwendet werden, um einen Webbrowser zu öffnen und eine bestimmte URL anzuzeigen.

pyperclip

Pyperclip ist eine plattformübergreifende Python-Bibliothek zum Kopieren und Einfügen von Text in die Zwischenablage.

re

Die re-Bibliothek in Python wird verwendet, um reguläre Ausdrücke zu verarbeiten. Mit regulären Ausdrücken können Sie Text auf komplexe Weise durchsuchen und manipulieren.

subprocess

Diese Bibliothek ermöglicht es Ihnen, neue Prozesse zu starten, mit ihren Eingabe-/Ausgabefehlern zu interagieren und ihre Rückgabecodes zu erhalten.

psutil

Diese Bibliothek wird verwendet, um Zugriff auf Systeminformationen wie CPU-Auslastung, Speicher, Festplatten, Netzwerk, etc. zu erhalten.

time

Die time-Bibliothek bietet Funktionen zur Arbeit mit Zeit, einschliesslich Funktionen zur Manipulation von Zeit und zur Umwandlung zwischen verschiedenen Zeitformaten.

sys

Diese Bibliothek bietet Zugriff auf einige Variablen, die vom Python-Interpreter verwendet werden, und auf Funktionen, die stark mit diesem interagieren. Sie wird oft für die Interaktion mit dem Python-Laufzeitsystem verwendet.


Das ganze habe ich dann per pyinstaller kompiliert und mit der ffmpeg.exe beim Kunden eingerichtet. Damit das Progrämmli nicht lange gesucht werden musste noch eine Verknüpfung auf dem Desktop und Tadaa!

so sahs aus.

Weiterentwicklung?

Leider nein. Ich denke für den Anfang habe ich hier genug rumgebastelt. Das Script funktioniert mittlerweile auch nicht mehr so wie gewünscht, ich nehme an dass es mit der yt_dlp-Bibliothek zusammenhängt. Ich mein, es hat noch nicht mal einen richtigen Namen ;)

RIP Werner
Danke für deine jahrelange Freundschaft.