@D3us3x: Anmerkungen zum Quelltext:
Sternchen-Importe sind Böse™. Gerade bei `tkinter` holt man sich fast 200 Namen ins Modul von denen nur ein kleiner Bruchteil tatsächlich benutzt wird. Auch Namen die gar nicht im `tkinter`-Modul definiert werden, sondern die dort ihrerseits aus anderen Modulen importiert werden.
Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase).
Die Fenstergrösse gibt man nicht vor, die ergibt sich aus dem Inhalt des Fensters. Das mag ja bei Dir funktionieren, bei mir sehen die beiden Fenster wie folgt aus, und Du erlaubst ja nicht mal mehr, dass der Anwender selbst das Fenster so gross ziehen kann, dass alles angezeigt wird:

Im zweiten Fenster sieht man was passiert wenn man den Button dort drückt. Das sieht nicht gut/richtig aus.
Wenn man das mit der Grössenbegrenzung raus nimmt, sieht das Hauptfenster bei mir so aus:

Oder mit anderen Worten: drei Buttons in die selbe Grid-Zelle zu setzen und versuchen mit `sticky` und `pad*` und absoluten Pixelwerten die so zu verschieben, dass sie alle sichtbar sind, ist letztlich `place()` mit anderen Mitteln – und funktioniert nicht wie man sieht.
`Entry`-Objekte sollten nicht `link` oder `path` heissen, denn das wären Namen für das was der Benutzer dort eingibt, nicht für das Anzeigeobjekt in dem das eingegeben wird.
Man verschachtelt keine Funktionen in Funktionen. Das macht Sinn wenn man eine Funktion schreibt, die ein Closure an den Aufrufer zurück gibt. Aber hier macht es das Programm einfach nur unübersichtlich und verhindert das man die Funktionen leicht testen kann.
Unübersichtlich ist das unter anderem auch deswegen, weil die Funktionen nicht für sich stehen, man muss immer auch den Inhalt der Funktion kennen in der sie definiert sind, weil sie auf Werte aus diesem umschliessenden Namensraum einfach so zugreifen. Alles was eine Funktion oder Methode ausser Konstanten benötigt, sollte sie aber als Argument(e) übergeben bekommen. Was dann zumindest auch der erste Schritt zur Beantwortung Deiner Frage ist: Die Umwandlungsfunkionen müssen den Pfad als Argument übergeben bekommen.
Fenster werden nicht aufgerufen sondern angezeigt.
Es darf in einem Programm nur *ein* `Tk`-Objekt zur gleichen Zeit geben. Das ist *das* Hauptfenster. Da hängt der Tcl/Tk-Interpreter dran und wenn es davon mehr als einen gibt, können komische Sachen, bis hin zu harten Programmabstürzen passieren. Zusätzliche Fenster erstellt man mit `tkinter.Toplevel`. In `selectFolder()` macht es noch nicht einmal Sinn noch ein `Tk`-Objekt zu erstellen das dann sowieso gleich wieder von der Anzeige zurückgezogen wird.
Das die Funktionen die Du an `command`\s von Buttons bindest Rückgabewerte haben, macht keinen Sinn. Die werden von der GUI-Hauptschleife aufgerufen und die kann mit irgendwelchen Rückgabewerten nichts anfangen und ignoriert die einfach. Hier ist dann auch die Stelle wo man nicht wirklich um objektorientierte Programmierung herum kommt. Bei sehr trivialen Programmen kann man sich noch mit den von `tkinter.Variable` abgeleiteten Typen und `functools.partial()` behelfen, aber das wird relativ schnell unübersichtlich.
Das sie Zeichenkette "/Downloads/%(title)s.%(ext)s" zwei mal im Code steht ist fehleranfällig. So etwas definiert man einmal und als Konstante. Das Dateinamensmuster gehört da auch nicht mit rein.
Warum fügst Du in ein leeres Eingabefeld etwas an Index 10 ein? Man kann da jeden Wert nehmen, die verhalten sich dann alle wie 0, also warum gerde 10 und nicht 23, 42, oder eben die logischste Variante 0‽ Und warum wird bei `clearPath()` der Inhalt von 0 bis Index 22 entfernt? Ich hätte da ja erwartet das *alles* entfernt wird‽ Siehe oben das Bildschirmfoto das den Murks zeigt der durch diese komische Kombination entstehen kann. Entry komplett löschen ist 0 bis `tk.END` und nicht irgendwelche willkürlichen Zahlen von denen man hofft das sie gross genug sind.
Dein Code berücksichtigt nicht, dass der Benutzer den Verzeichnisdialog auch abbrechen kann. Und jedes mal wenn man den Konfigurationsdialog aufruft, fängt der mit dem gleichen Verzeichnis an.
Man sollte keine kryptischen Abkürzungen in Namen verwenden. `convtom4a` und `convtomp4` sind absolut unlesbare Namen.
Der Inhalt der beiden Funktionen ist so ähnlich, dass das eigentlich nur *eine* sein sollte, die halt noch das Zielformat als Argument bekommt. Ausserdem ist `convert_to…()` ein etwas irreführender Name für eine Funktion die Hauptsächlich etwas aus dem Internet herunterlädt und bei der nur als zusätzliche Angabe eine Konvertierung passiert/passieren kann. Zudem sollte diese Funktion komplett unabhängig von der GUI sein.
`os.system()` sollte schon seit Ewigkeiten nicht mehr verwendet werden. Um externe Programme auszuführen, gibt es das `subprocess`-Modul. Da spart man sich viele Probleme, unter anderem auch das man Zeichenketten in den Argumenten verwenden kann, die eine besondere Bedeutung für die Shell in der die PowerShell gestartet wird haben. Wobei an der jetzigen Kombination das in einer PowerShell auszuführen wahrscheinlich immer noch Probleme mit Zeichen entstehen können die für dir PowerShell eine besondere Bedeutung haben. Das so zu machen ist keine gute, robuste Lösung. Warum musst Du da eine PowerShell dazwischen basteln?
Ungetestet:
Code: Alles auswählen
#!/usr/bin/env python3
import subprocess
import tkinter as tk
from functools import partial
from pathlib import Path
from tkinter import filedialog, messagebox
DOWNLOAD_PATH = Path("/Downloads")
FILENAME_TEMPLATE = "%(title)s.%(ext)s" # For ``youtube-dl`` output.
AUDIO_FORMAT = "m4a"
VIDEO_FORMAT = "mp4"
def download(url, target_path, output_format):
subprocess.run(
[
"powershell",
"-command",
"youtube-dl",
"--format",
output_format,
"--output",
str(target_path / FILENAME_TEMPLATE),
url,
],
check=True,
)
def on_convert(url_var, path_var, output_format):
download(url_var.get(), Path(path_var.get()), output_format)
def select_folder(path_var):
path = filedialog.askdirectory()
if path:
path_var.set(path)
def show_configuration_window(master, path_var):
window = tk.Toplevel(master)
window.title("Configuration")
tk.Label(window, text="Download Path:", font="Arial 16").grid(
row=0, column=0, sticky=tk.W, pady=5, padx=5
)
tk.Entry(window, width=60, textvariable=path_var).grid(
row=1, column=0, sticky=tk.W, pady=5, padx=2
)
tk.Button(
window, text="Browse", command=partial(select_folder, path_var)
).grid(row=1, column=1, sticky=tk.W, pady=5, padx=5)
tk.Button(
window,
text="Reset Settings",
command=partial(path_var.set, DOWNLOAD_PATH),
).grid(row=2, column=0, sticky=tk.W, pady=5, padx=5)
def on_closing(widget):
if messagebox.askokcancel("Quit", "Do you want to quit?"):
widget.quit()
def main():
main_window = tk.Tk()
main_window.title("YouTube Converter")
path_var = tk.StringVar(value=DOWNLOAD_PATH)
grid_options = {"padx": 5, "pady": 5}
tk.Label(main_window, text="Link:", font="Arial 22").grid(
row=0, column=0, sticky=tk.W, **grid_options
)
link_entry = tk.Entry(main_window, width=80)
link_entry.grid(row=1, column=0, **grid_options)
button_frame = tk.Frame(main_window)
tk.Button(
button_frame,
text="Configure",
command=partial(show_configuration_window, main_window, path_var),
).grid(row=0, column=0, sticky=tk.W, **grid_options)
tk.Button(
button_frame,
text="Convert to Video",
command=partial(on_convert, link_entry, path_var, VIDEO_FORMAT),
).grid(row=0, column=1, **grid_options)
tk.Button(
button_frame,
text="Convert to Audio",
command=partial(on_convert, link_entry, path_var, AUDIO_FORMAT),
).grid(row=0, column=2, **grid_options)
button_frame.columnconfigure(0, weight=1)
button_frame.grid(row=2, column=0, sticky=tk.EW, **grid_options)
main_window.protocol("WM_DELETE_WINDOW", partial(on_closing, main_window))
main_window.mainloop()
if __name__ == "__main__":
main()