import tkinter as tk
from tkinter import filedialog
from tkinter import messagebox
from tkinter.ttk import Progressbar, Combobox
import subprocess
import os
import configparser
import webbrowser
import datetime
import threading
import json  # JSON Modul für die Verarbeitung von Analyse-Daten im JSON-Format

# --- START Globale Variablen und Konstanten ---
VERSION = "2.0.1"            # Aktuelle Version des Programms, wird im Info-Fenster und Fenstertitel angezeigt.
CONFIG_DATEI = "options.ini"  # Name der Konfigurationsdatei, in der Programmeinstellungen gespeichert werden.
LOG_DATEI = "ffmpeg_log.txt"  # Name der Log-Datei für die Aufzeichnung von FFmpeg-Normalisierungsprozessen.
ANALYSE_LOG_DATEI = "ffmpeg_analyze.txt" # Name der Log-Datei für die Aufzeichnung von FFmpeg-Analyseprozessen.
STANDARD_LOG_DATEI_GROESSE_KB = 1024 # Standardgröße für Log-Dateien in Kilobyte (KB), hier 1MB.
log_datei_groesse_kb = STANDARD_LOG_DATEI_GROESSE_KB # Globale Variable, die die aktuelle maximale Log-Datei-Größe in KB speichert. Initialisiert mit dem Standardwert.
single_log_eintrag_aktiviert = True # Globale Variable, die steuert, ob nur ein Log-Eintrag pro Prozess gespeichert wird (True) oder Log-Rolling verwendet wird (False). Standardmäßig aktiviert (True).
BUILD_DATUM = "2024-02-28" # Build-Datum des Programms, manuell gesetzt, wird im Info-Fenster angezeigt.

ffmpeg_pfad = "ffmpeg.exe"  # Standardpfad zu ffmpeg.exe. Nimmt an, dass ffmpeg.exe im selben Verzeichnis wie das Skript liegt.
                               # Kann in den Optionen geändert werden.

# --- LUFS Presets Definition ---
LUFS_PRESETS = {
    "Standard (-10 LUFS)": "-10", # Standard LUFS-Zielwert.
    "YouTube (-14 LUFS)": "-14",   # LUFS-Zielwert für YouTube-konforme Normalisierung.
    "Spotify (-14 LUFS)": "-14",   # LUFS-Zielwert für Spotify-konforme Normalisierung.
    "Broadcast EBU R128 (-23 LUFS)": "-23", # LUFS-Zielwert für Broadcast-Standards nach EBU R128.
    "Benutzerdefiniert": "custom"    # Schlüsselwort, um anzuzeigen, dass ein benutzerdefinierter LUFS-Wert verwendet wird.
}
# Dictionary, das vordefinierte LUFS-Zielwerte speichert.
# Schlüssel sind menschenlesbare Namen für die Presets, die im GUI angezeigt werden.
# Werte sind die zugehörigen LUFS-Werte als Strings, die an FFmpeg übergeben werden.
LUFS_PRESET_NAMES = list(LUFS_PRESETS.keys())
# Liste der Preset-Namen, extrahiert aus den Schlüsseln von LUFS_PRESETS.
# Wird verwendet, um die Preset-Optionen in der "LUFS Preset" Combobox im GUI zu befüllen.

# --- True Peak Presets Definition ---
TRUE_PEAK_PRESETS = {
    "Standard (-1 dBTP)": "-1",     # Standard True Peak-Zielwert.
    "Broadcast (-2 dBTP)": "-2",    # True Peak-Zielwert für Broadcast-Anwendungen.
    "Kein Limit (0 dBTP)": "0",      # True Peak-Zielwert 0 dBTP (kein Limit). Hinweis: Kann zu Clipping führen und sollte vorsichtig verwendet werden.
    "Benutzerdefiniert": "custom"     # Schlüsselwort für benutzerdefinierten True Peak-Wert.
}
# Dictionary für vordefinierte True Peak-Zielwerte (dB True Peak).
# Ähnlich wie LUFS_PRESETS, aber für True Peak-Werte.
TRUE_PEAK_PRESET_NAMES = list(TRUE_PEAK_PRESETS.keys()) # **KORREKTUR: Nutze TRUE_PEAK_PRESETS (Mehrzahl)**
# Liste der Preset-Namen für True Peak, für die "True Peak Preset" Combobox.
# --- ENDE Globale Variablen und Konstanten ---

# --- START Funktionen für Optionen und Info-Box ---
def load_options():
    """Lädt die Programmeinstellungen aus der Konfigurationsdatei 'options.ini'."""
    global ffmpeg_pfad, log_datei_groesse_kb, single_log_eintrag_aktiviert # Globale Variablen, die in dieser Funktion verändert werden sollen.

    config = configparser.ConfigParser() # Erstellt ein ConfigParser-Objekt zum Lesen der INI-Datei.
    if os.path.exists(CONFIG_DATEI): # Prüft, ob die Konfigurationsdatei existiert.
        config.read(CONFIG_DATEI) # Liest die Konfigurationsdatei.
        if "Einstellungen" in config: # Prüft, ob die Sektion "Einstellungen" in der INI-Datei vorhanden ist.
            if "ffmpeg_pfad" in config["Einstellungen"]: # Liest den FFmpeg-Pfad aus der Konfiguration, falls vorhanden.
                ffmpeg_pfad = config["Einstellungen"]["ffmpeg_pfad"]
            # --- NEU: Log-Datei Größe laden ---
            if "log_datei_groesse_kb" in config["Einstellungen"]: # Liest die Log-Datei-Größe aus der Konfiguration.
                try:
                    log_datei_groesse_kb = int(config["Einstellungen"]["log_datei_groesse_kb"]) # Versucht, den Wert als Integer zu lesen.
                except ValueError: # Falls der Wert keine gültige Zahl ist.
                    log_datei_groesse_kb = STANDARD_LOG_DATEI_GROESSE_KB # Setzt den Standardwert.
            else: # Falls die Option nicht in der INI-Datei existiert.
                log_datei_groesse_kb = STANDARD_LOG_DATEI_GROESSE_KB # Setzt den Standardwert.
            # --- NEU: Log-Datei Größe laden ENDE ---
            # --- NEU: Einzel-Log-Eintrag Option laden ---
            if "einzel_log_eintrag_aktiviert" in config["Einstellungen"]: # Liest den Zustand der Einzel-Log-Eintrag Option.
                single_log_eintrag_aktiviert = config["Einstellungen"]["einzel_log_eintrag_aktiviert"].lower() == "true" # Liest als String und wandelt in Boolean um (case-insensitive).
            else: # Falls die Option nicht in der INI-Datei existiert.
                single_log_eintrag_aktiviert = True # Setzt den Standardwert (AN).
            # --- NEU: Einzel-Log-Eintrag Option laden ENDE ---

    else: # Falls die Konfigurationsdatei nicht existiert.
        save_options() # Erstellt eine neue Konfigurationsdatei mit Standardeinstellungen.

    # --- Sicherstellen, dass log_datei_groesse_kb immer einen gültigen Wert hat ---
    if not isinstance(log_datei_groesse_kb, int) or log_datei_groesse_kb <= 0: # Validierung: Größe muss ein Integer und größer als 0 sein.
        log_datei_groesse_kb = STANDARD_LOG_DATEI_GROESSE_KB # Setzt auf Standardwert, falls ungültig.
    # --- Validierung Ende ---

    print(f"Log-Datei Größe beim Laden: {log_datei_groesse_kb} KB") # DEBUG Ausgabe in der Konsole.
    print(f"Einzel-Log-Eintrag Option beim Laden: {single_log_eintrag_aktiviert}") # DEBUG Ausgabe in der Konsole.

def save_options():
    """Speichert die Programmeinstellungen in der Konfigurationsdatei 'options.ini'."""
    config = configparser.ConfigParser() # Erstellt ein ConfigParser-Objekt zum Schreiben der INI-Datei.
    config["Einstellungen"] = {"ffmpeg_pfad": ffmpeg_pfad, # Speichert den FFmpeg-Pfad.
                              # --- NEU: Log-Datei Größe speichern ---
                              "log_datei_groesse_kb": log_datei_groesse_kb, # Speichert die Log-Datei-Größe.
                              # --- NEU: Log-Datei Größe speichern ENDE ---
                              # --- NEU: Einzel-Log-Eintrag Option speichern ---
                              "einzel_log_eintrag_aktiviert": str(single_log_eintrag_aktiviert) # Speichert den Zustand der Einzel-Log-Eintrag Option als String ("True" oder "False").
                              # --- NEU: Einzel-Log-Eintrag Option speichern ENDE ---
                              }
    with open(CONFIG_DATEI, "w") as configfile: # Öffnet die Konfigurationsdatei im Schreibmodus ('w').
        config.write(configfile) # Schreibt die Konfiguration in die Datei.

def show_options_dialog():
    """Zeigt ein Dialogfenster für die Programmeinstellungen (Optionen) an."""
    options_window = tk.Toplevel(window) # Erstellt ein neues Fenster als Toplevel-Fenster, über dem Hauptfenster.
    options_window.title("Optionen") # Setzt den Titel des Optionsfensters.
    options_window.transient(window) # Setzt das Optionsfenster als abhängig vom Hauptfenster (erscheint immer darüber).
    options_window.grab_set() # Fokussiert das Optionsfenster und blockiert Interaktion mit dem Hauptfenster, bis es geschlossen wird (modaler Dialog).

    # --- Zentrierung des Dialogfensters ---
    fenster_breite = window.winfo_width() # Breite des Hauptfensters.
    fenster_hoehe = window.winfo_height() # Höhe des Hauptfensters.
    fenster_x = window.winfo_rootx() # X-Position des Hauptfensters auf dem Bildschirm.
    fenster_y = window.winfo_rooty() # Y-Position des Hauptfensters auf dem Bildschirm.
    dialog_breite = 450 # Breite des Optionsdialogs.
    dialog_hoehe = 220 # Höhe des Optionsdialogs (leicht erhöht für LabelFrames).
    x_position = fenster_x + (fenster_breite - dialog_breite) // 2 # Berechnet die X-Position für die Zentrierung.
    y_position = fenster_y + (fenster_hoehe - dialog_hoehe) // 2 # Berechnet die Y-Position für die Zentrierung.
    options_window.geometry(f"+{x_position}+{y_position}") # Setzt die Position des Optionsfensters.
    # --- Zentrierung Ende ---

    # --- Optionen Frames (LabelFrames) ---
    ffmpeg_frame = tk.LabelFrame(options_window, text="FFmpeg Pfad", padx=10, pady=5) # LabelFrame für FFmpeg Pfad Optionen mit Rahmen und Text.
    ffmpeg_frame.grid(row=0, column=0, columnspan=3, padx=10, pady=10, sticky="ew") # Plazierung im Grid, spannt über 3 Spalten, horizontale Ausdehnung, Padding.

    log_frame = tk.LabelFrame(options_window, text="Log-Datei Einstellungen", padx=10, pady=5) # LabelFrame für Log-Optionen.
    log_frame.grid(row=1, column=0, columnspan=3, padx=10, pady=10, sticky="ew") # Plazierung im Grid.
    # --- Optionen Frames Ende ---

    ffmpeg_pfad_label = tk.Label(ffmpeg_frame, text="Ordner mit ffmpeg.exe:") # Label für das FFmpeg-Pfad Eingabefeld.
    ffmpeg_pfad_label.grid(row=0, column=0, padx=10, pady=5, sticky="w") # Plazierung im Grid, linksbündig, Padding.
    ffmpeg_pfad_eingabe = tk.Entry(ffmpeg_frame, width=50) # Eingabefeld für den FFmpeg-Pfad.
    ffmpeg_pfad_eingabe.grid(row=0, column=1, padx=10, pady=5, sticky="ew") # Plazierung im Grid, horizontale Ausdehnung, Padding.

    # --- NEU: Pfad im Eingabefeld setzen (unterschiedliches Verhalten beim ersten Start) ---
    if ffmpeg_pfad == "ffmpeg.exe": # Prüft, ob der Pfad noch der Standardwert ist (-> erster Start).
        programm_pfad = os.path.dirname(os.path.abspath(__file__)) # Ermittelt den Pfad des aktuellen Python-Skripts.
        ffmpeg_pfad_eingabe.insert(0, programm_pfad) # Setzt den Programmpfad als Standardwert in das Eingabefeld.
    else: # Pfad ist bereits in options.ini gespeichert -> lade gespeicherten Pfad.
        ffmpeg_pfad_eingabe.insert(0, ffmpeg_pfad) # Setzt den gespeicherten FFmpeg-Pfad in das Eingabefeld.
    # --- NEU: Pfad im Eingabefeld setzen ENDE ---

    # --- NEU: Checkbox für Einzel-Log-Eintrag ---
    einzel_log_check_var = tk.BooleanVar(value=single_log_eintrag_aktiviert) # BooleanVar, um den Zustand der Checkbox zu speichern, initialisiert mit dem aktuellen Wert der globalen Variable.
    einzel_log_check = tk.Checkbutton(log_frame, text="Nur einen Log-Eintrag pro Prozess speichern", variable=einzel_log_check_var, command=lambda: update_log_groesse_state(einzel_log_check_var, log_groesse_eingabe)) # Checkbox für die Einzel-Log-Option. `command` ruft `update_log_groesse_state` Funktion beim Klicken auf.
    einzel_log_check.grid(row=1, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="w") # Plazierung im Grid, spannt über 2 Spalten, Padding.
    # --- NEU: Checkbox Ende ---

    log_groesse_label = tk.Label(log_frame, text="Maximale Log-Datei Größe (KB):") # Label für das Log-Datei-Größe Eingabefeld.
    log_groesse_label.grid(row=0, column=0, padx=10, pady=5, sticky="w") # Plazierung im Grid, linksbündig, Padding.
    log_groesse_eingabe = tk.Entry(log_frame, width=10) # Eingabefeld für die maximale Log-Datei-Größe.
    log_groesse_eingabe.grid(row=0, column=1, padx=10, pady=5, sticky="w") # Plazierung im Grid, linksbündig, Padding.
    log_groesse_eingabe.insert(0, str(log_datei_groesse_kb)) # Setzt den aktuellen Wert der Log-Datei-Größe in das Eingabefeld.

    # --- NEU: Funktion zum Aktivieren/Deaktivieren des Log-Größen-Felds ---
    def update_log_groesse_state(check_var, groesse_eingabe_feld):
        """Aktiviert oder deaktiviert das Log-Größen-Eingabefeld basierend auf dem Zustand der Checkbox."""
        if check_var.get(): # Wenn die Checkbox aktiviert ist (Einzel-Log-Eintrag aktiv).
            groesse_eingabe_feld.config(state=tk.DISABLED) # Deaktiviert das Eingabefeld für die Log-Datei-Größe, da es in diesem Modus nicht relevant ist.
        else: # Wenn die Checkbox deaktiviert ist (Log-Rolling aktiv).
            groesse_eingabe_feld.config(state=tk.NORMAL) # Aktiviert das Eingabefeld, um die maximale Log-Datei-Größe einzustellen.
    # --- NEU: Funktion Ende ---

    # --- Initialen Zustand des Log-Größen-Feldes setzen ---
    update_log_groesse_state(einzel_log_check_var, log_groesse_eingabe) # Ruft die Funktion beim Start auf, um den initialen Zustand des Eingabefelds basierend auf der Checkbox zu setzen.
    # --- Initialen Zustand Ende ---

    def browse_ffmpeg_pfad():
        """Öffnet einen Dateiauswahldialog zur Auswahl des FFmpeg-Pfads."""
        pfad = filedialog.askdirectory(title="FFmpeg Pfad auswählen") # Öffnet einen Ordnerauswahldialog.
        if pfad: # Wenn ein Pfad ausgewählt wurde.
            ffmpeg_pfad_eingabe.delete(0, tk.END) # Leert das Eingabefeld.
            ffmpeg_pfad_eingabe.insert(0, pfad) # Setzt den ausgewählten Pfad in das Eingabefeld.

    durchsuchen_button = tk.Button(ffmpeg_frame, text="Durchsuchen", command=browse_ffmpeg_pfad) # Button zum Öffnen des FFmpeg-Pfad-Auswahldialogs.
    durchsuchen_button.grid(row=0, column=2, padx=10, pady=5) # Plazierung im Grid, Padding.

    def save_and_close_options():
        """Speichert die Optionen, validiert den FFmpeg-Pfad und schließt das Optionsfenster."""
        global ffmpeg_pfad, log_datei_groesse_kb, single_log_eintrag_aktiviert # Globale Variablen, die in dieser Funktion verändert werden sollen.
        ffmpeg_pfad_eingabe_wert = ffmpeg_pfad_eingabe.get() # Liest den Wert aus dem FFmpeg-Pfad Eingabefeld.
        ffmpeg_exe_pfad = os.path.join(ffmpeg_pfad_eingabe_wert, "ffmpeg.exe") # Erstellt den vollständigen Pfad zu ffmpeg.exe.

        if not os.path.exists(ffmpeg_exe_pfad): # Prüft, ob ffmpeg.exe unter dem angegebenen Pfad existiert.
            messagebox.showerror("Fehler", "Ungültiger FFmpeg Pfad!\n'ffmpeg.exe' wurde im angegebenen Ordner nicht gefunden.", parent=options_window) # Zeigt eine Fehlermeldung, wenn ffmpeg.exe nicht gefunden wurde.
            return # Beendet die Funktion, ohne die Optionen zu speichern.

        # --- Überprüfung, ob FFmpeg ausführbar ist ---
        try:
            subprocess.run([ffmpeg_exe_pfad, "-version"], capture_output=True, check=True) # Versucht, FFmpeg auszuführen und die Version abzurufen. `check=True` löst eine Exception aus, wenn FFmpeg mit einem Fehlercode beendet wird.
        except (FileNotFoundError, subprocess.CalledProcessError): # Fängt Fehler ab, wenn FFmpeg nicht gefunden oder nicht ausführbar ist.
            messagebox.showerror("Fehler", "FFmpeg ist nicht ausführbar oder beschädigt.\nBitte überprüfen Sie den Pfad.", parent=options_window) # Zeigt eine Fehlermeldung.
            return # Beendet die Funktion.
        # --- Überprüfung Ende ---

        ffmpeg_pfad = ffmpeg_pfad_eingabe_wert # Aktualisiert die globale Variable `ffmpeg_pfad` mit dem neuen Pfad.

        # --- NEU: Log-Datei Größe speichern (nur wenn nicht Einzel-Log aktiv) ---
        if not einzel_log_check_var.get(): # Nur wenn die Einzel-Log-Option NICHT aktiv ist.
            try:
                log_datei_groesse_kb_eingabe = int(log_groesse_eingabe.get()) # Liest den Wert aus dem Log-Größe Eingabefeld und wandelt ihn in einen Integer um.
                if log_datei_groesse_kb_eingabe <= 0: # Validierung: Größe muss positiv sein.
                    messagebox.showerror("Fehler", "Ungültige Log-Datei Größe!\nBitte geben Sie eine positive Zahl größer als 0 ein.", parent=options_window) # Zeigt eine Fehlermeldung, wenn die Größe ungültig ist.
                    return # Beendet die Funktion.
                log_datei_groesse_kb = log_datei_groesse_kb_eingabe # Aktualisiert die globale Variable `log_datei_groesse_kb`.
            except ValueError: # Fehler, wenn die Eingabe keine Zahl ist.
                messagebox.showerror("Fehler", "Ungültige Log-Datei Größe!\nBitte geben Sie eine ganze Zahl (in KB) ein.", parent=options_window) # Zeigt eine Fehlermeldung, wenn die Eingabe keine Zahl ist.
                return # Beendet die Funktion.
        # --- NEU: Log-Datei Größe speichern ENDE ---

        # --- NEU: Einzel-Log-Eintrag Option speichern ---
        single_log_eintrag_aktiviert = einzel_log_check_var.get() # Speichert den Zustand der Checkbox in der globalen Variable `single_log_eintrag_aktiviert`.
        # --- NEU: Einzel-Log-Eintrag Option speichern ENDE ---

        save_options() # Speichert die Optionen in die Konfigurationsdatei.
        options_window.destroy() # Schließt das Optionsfenster.

    speichern_button = tk.Button(options_window, text="Speichern", command=save_and_close_options) # Button zum Speichern der Optionen und Schließen des Dialogs.
    speichern_button.grid(row=2, column=0, columnspan=3, pady=10) # Plazierung im Grid, unterhalb der LabelFrames.

    options_window.columnconfigure(1, weight=1) # Konfiguriert Spalte 1 des Optionsfensters, um sich horizontal auszudehnen (für responsives Layout).
    options_window.wait_window(options_window) # Wartet, bis das Optionsfenster geschlossen wird, bevor das Hauptprogramm fortfährt (macht den Dialog modal).

def show_info_box():
    """Zeigt ein Info-Fenster mit Programmversion und Kontaktinformationen an."""
    info_window = tk.Toplevel(window) # Erstellt ein neues Toplevel-Fenster für die Info-Box.
    info_window.title("Über") # Setzt den Titel des Info-Fensters.
    info_window.transient(window) # Setzt das Info-Fenster als abhängig vom Hauptfenster.
    info_window.grab_set() # Fokussiert das Info-Fenster und blockiert Interaktion mit dem Hauptfenster.

    info_frame = tk.LabelFrame(info_window, text="Über melcom's FFmpeg Audio Normalizer", padx=10, pady=10) # LabelFrame für den Inhalt der Info-Box.
    info_frame.pack(padx=10, pady=10, fill=tk.BOTH, expand=True) # Plaziert im Fenster, füllt in beide Richtungen, dehnt sich aus, wenn das Fenster größer wird.

    info_text = (
        f"melcom's FFmpeg Audio Normalizer v{VERSION}\n\n" # Zeigt den Programmnamen und die Version.
        f"Build Datum: {BUILD_DATUM}\n\n" # Zeigt das Build-Datum.
        "Dieses Tool normalisiert Audio-Dateien unter Verwendung von FFmpeg.\n" # Kurze Beschreibung des Programms.
        "Es unterstützt verschiedene Audioformate und LUFS/True-Peak-Zielwerte.\n\n" # Weitere Beschreibung der Funktionalität.
        "Author: melcom (Andreas Thomas Urban)\n" # Author des Programms.
        "E-Mail: melcom [@] vodafonemail.de\n\n" # Kontakt E-Mail.
        "Webseiten:\n" # Überschrift für Webseiten-Links.
    )
    info_label = tk.Label(info_frame, text=info_text, justify=tk.LEFT) # Label für den Haupt-Info-Text, linksbündig.
    info_label.pack(padx=10, pady=5, anchor="nw") # Plazierung im Frame, Padding, links oben ausgerichtet.

    website_label_1 = tk.Label(info_frame, text="melcom-music.de", fg="blue", cursor="hand2") # Label für die erste Webseite, blau, Hand-Cursor beim Überfahren.
    website_label_1.pack(anchor="w", padx=20, pady=2) # Plazierung, linksbündig, eingerückt, Padding.
    website_label_1.bind("<Button-1>", lambda e: webbrowser.open("https://www.melcom-music.de")) # Bindet Klick-Event an das Label, um die Webseite im Browser zu öffnen.

    website_label_2 = tk.Label(info_frame, text="scenes.at/melcom", fg="blue", cursor="hand2") # Label für die zweite Webseite.
    website_label_2.pack(anchor="w", padx=20, pady=2) # Plazierung.
    website_label_2.bind("<Button-1>", lambda e: webbrowser.open("https://scenes.at/melcom")) # Bindet Klick-Event.

    opensource_label = tk.Label(info_frame, text=f"\nOpen Source Software (kostenlos nutzbar)\nCopyright © {datetime.datetime.now().year} Andreas Thomas Urban") # Label für Open Source Hinweis und Copyright, aktuelles Jahr.
    opensource_label.pack(pady=10, anchor="center") # Plazierung, zentriert, Padding.

    ok_button = tk.Button(info_window, text="OK", command=info_window.destroy) # OK-Button zum Schließen des Info-Fensters.
    ok_button.pack(pady=10, anchor="center") # Plazierung, zentriert, Padding.

    info_window.wait_window(info_window) # Wartet, bis das Info-Fenster geschlossen wird.
# --- ENDE Funktionen für Optionen und Info-Box ---

# --- START Funktionen für Dateiauswahl, Audioanalyse und GUI-Update nach Analyse ---
def browse_datei():
    """Öffnet einen Dateiauswahldialog zur Auswahl einer Audio-Datei."""
    datei_pfad = filedialog.askopenfilename( # Öffnet einen "Datei öffnen" Dialog.
        defaultextension=".wav", # Setzt die Standarddateiendung auf .wav.
        filetypes=[ # Definiert Filter für Dateitypen im Dialog.
            ("Audio-Dateien", "*.wav *.mp3 *.flac *.aac *.ogg *.m4a"), # Filter für alle unterstützten Audioformate.
            ("WAV-Dateien", "*.wav"), # Filter nur für WAV-Dateien.
            ("MP3-Dateien", "*.mp3"), # Filter nur für MP3-Dateien.
            ("FLAC-Dateien", "*.flac"), # Filter nur für FLAC-Dateien.
            ("AAC-Dateien", "*.aac *.m4a"), # Filter für AAC und M4A Dateien.
            ("OGG-Dateien", "*.ogg"), # Filter nur für OGG-Dateien.
            ("Alle Dateien", "*.*") # Filter für alle Dateitypen.
        ],
        title="Audio-Datei auswählen" # Setzt den Titel des Dialogfensters.
    )
    if datei_pfad: # Wenn ein Dateipfad ausgewählt wurde.
        datei_eingabe.delete(0, tk.END) # Leert das Dateipfad-Eingabefeld.
        datei_eingabe.insert(0, datei_pfad) # Setzt den ausgewählten Dateipfad in das Eingabefeld.

def audio_analysieren():
    """Startet die Audioanalyse der ausgewählten Datei in einem separaten Thread."""
    datei = datei_eingabe.get() # Liest den Dateipfad aus dem Eingabefeld.

    if not datei: # Prüft, ob eine Datei ausgewählt wurde.
        messagebox.showerror("Fehler", "Bitte wählen Sie zuerst eine Audio-Datei aus, bevor Sie sie analysieren.", parent=window) # Fehlermeldung, wenn keine Datei ausgewählt ist.
        return # Beendet die Funktion.

    # --- GUI-Elemente für Analyse-Prozesszustand ---
    analysieren_button.config(state=tk.DISABLED) # Deaktiviert den "Audio-Datei analysieren" Button während der Analyse.
    starten_button.config(state=tk.DISABLED) # Deaktiviert den "Normalisierung starten" Button während der Analyse.
    window.config(cursor="wait") # Ändert den Mauszeiger zu "Warten" (Sanduhr/Kreis).
    progressbar.grid(row=4, column=0, columnspan=3, sticky="ew", padx=20, pady=(0,10)) # Zeigt den Fortschrittsbalken im Grid an.
    progressbar.start() # Startet die Animation des Fortschrittsbalkens (unbestimmter Fortschritt).
    # --- GUI-Elemente Ende ---

    prozess_info_feld.config(state=tk.NORMAL) # Aktiviert das Prozessinfo-Textfeld, um Text hineinschreiben zu können.
    prozess_info_feld.delete("1.0", tk.END) # Leert das Prozessinfo-Textfeld.
    prozess_info_feld.insert(tk.END, f"Audioanalyse gestartet...\nDatei: {os.path.basename(datei)}\nBitte warten, Analyse läuft...") # Fügt eine Startmeldung in das Prozessinfo-Textfeld ein.
    prozess_info_feld.config(state=tk.DISABLED) # Deaktiviert das Prozessinfo-Textfeld wieder, um manuelle Änderungen zu verhindern.

    log_eintrag_start = f"======================== {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')} ========================\n" # Start-Separator für den Log-Eintrag mit Zeitstempel.
    log_eintrag_start += f"Audio-Datei Analyse gestartet für Datei: {datei}\n" # Log-Meldung: Analyse gestartet.
    log_eintrag_start += "FFmpeg Analyse Befehl wird generiert...\n" # Log-Meldung: Befehl wird generiert.
    append_analyse_log(log_eintrag_start) # Schreibt die Startmeldung in die Analyse-Log-Datei.

    thread_analyse = threading.Thread(target=audio_analyse_thread_function, args=(datei,)) # Erstellt einen neuen Thread für die Audioanalyse. `target` ist die Funktion, die im Thread ausgeführt wird, `args` sind die Argumente für die Funktion.
    thread_analyse.start() # Startet den Analyse-Thread.

def audio_analyse_thread_function(datei):
    """Führt die Audioanalyse mit FFmpeg in einem separaten Thread aus."""
    ffmpeg_analyse_befehl = [
        os.path.join(ffmpeg_pfad, "ffmpeg.exe"), # Pfad zu ffmpeg.exe zusammensetzen.
        "-i", datei, # Eingabedatei angeben.
        "-af", "loudnorm=print_format=json", # Audiofilter 'loudnorm' für die Analyse aktivieren, Ausgabeformat auf JSON setzen.
        "-f", "null", "-" # Ausgabeformat auf 'null' setzen (keine Ausgabedatei erzeugen), Ausgabeziel auf '-' (Standardausgabe/Standardfehler) setzen.
    ]
    log_eintrag_befehl = "FFmpeg Analyse Befehl:\n" + " ".join(ffmpeg_analyse_befehl) + "\n\n" # Formatiert den FFmpeg-Befehl für den Log-Eintrag.
    append_analyse_log(log_eintrag_befehl) # Schreibt den generierten FFmpeg-Befehl in die Analyse-Log-Datei.

    try:
        ergebnis_analyse = subprocess.run(ffmpeg_analyse_befehl, check=True, capture_output=True, text=True) # Führt den FFmpeg-Analysebefehl aus. `check=True` löst eine Exception aus, wenn FFmpeg mit einem Fehlercode beendet wird. `capture_output=True` fängt die Standardausgabe und Standardfehler ab. `text=True` dekodiert die Ausgabe als Text.
        log_ausgabe = ergebnis_analyse.stderr # FFmpeg schreibt die Analyse-Daten nach Standardfehler (stderr).
        # --- DEBUGGING: ROHE FFmpeg AUSGABE AUSGEBEN (für Entwickler) ---
        print("--- ROHE FFmpeg AUSGABE (stderr) ---") # Markierung in der Konsole für Debug-Ausgabe.
        print(log_ausgabe) # Gibt die rohe FFmpeg-Ausgabe in der Konsole aus (nützlich für Fehlersuche).
        print("--- ROHE FFmpeg AUSGABE ENDE ---") # Markierung in der Konsole für Debug-Ausgabe Ende.
        # --- DEBUGGING ENDE ---
        append_analyse_log("FFmpeg Analyse Ausgabe:\n" + log_ausgabe + "\n") # Schreibt die FFmpeg-Ausgabe in die Analyse-Log-Datei.

        # --- JSON Ausgabe parsen und Werte extrahieren ---
        try:
            start_index = log_ausgabe.find("{") # Sucht den Beginn des JSON-Blocks in der FFmpeg-Ausgabe.
            end_index = log_ausgabe.rfind("}") + 1 # Sucht das Ende des JSON-Blocks.
            json_string = log_ausgabe[start_index:end_index] # Extrahiert den JSON-String aus der FFmpeg-Ausgabe.
            json_ausgabe = json.loads(json_string) # Parst den JSON-String in ein Python-Dictionary.

            input_i = json_ausgabe.get("input_i") # Extrahiert den Wert für "input_i" (Integrierte Lautheit) aus dem JSON.
            input_tp = json_ausgabe.get("input_tp") # Extrahiert den Wert für "input_tp" (True Peak).
            lra = json_ausgabe.get("input_lra") # Extrahiert den Wert für "input_lra" (Loudness Range).
            analyse_ergebnisse = { # Speichert die extrahierten Analyseergebnisse in einem Dictionary.
                "input_i": input_i,
                "input_tp": input_tp,
                "lra": lra
            }
            window.after(0, update_gui_on_analyse_completion, "Erfolg", datei, analyse_ergebnisse) # Ruft die GUI-Update-Funktion im Hauptthread auf, um die Ergebnisse anzuzeigen. `window.after(0, ...)` sorgt dafür, dass die Funktion im Tkinter-Hauptthread ausgeführt wird (wichtig für GUI-Operationen aus einem Thread).
                                                                                                    # Übergibt den Status "Erfolg", den Dateinamen und die Analyseergebnisse.

        except json.JSONDecodeError: # Fehler beim Parsen des JSON.
            fehlermeldung_json = "Fehler beim Auswerten der FFmpeg Analyse-Daten (JSON-Format).\nBitte überprüfen Sie die FFmpeg-Ausgabe im Analyse-Logfile." # Fehlermeldung für JSON-Parsing-Fehler.
            append_analyse_log("FEHLER: " + fehlermeldung_json + "\n") # Schreibt die Fehlermeldung in die Analyse-Log-Datei.
            window.after(0, update_gui_on_analyse_completion, "Fehler", datei, fehlermeldung_json) # Ruft die GUI-Update-Funktion mit dem Status "Fehler" und der Fehlermeldung auf.

        # ... (Fehlerbehandlung innerhalb von audio_analyse_thread_function) ...
    except subprocess.CalledProcessError as e: # Fehler beim Ausführen von FFmpeg.
        fehlermeldung_ffmpeg = f"FFmpeg Fehler bei der Analyse:\nReturn Code: {e.returncode}\n{e.stderr}" # Formatiert eine Fehlermeldung mit dem Return-Code und der Fehlerausgabe von FFmpeg.
        append_analyse_log("FEHLER: " + fehlermeldung_ffmpeg + "\n") # Schreibt die FFmpeg-Fehlermeldung in die Analyse-Log-Datei.
        window.after(0, update_gui_on_analyse_completion, "Fehler", datei, fehlermeldung_ffmpeg) # Ruft die GUI-Update-Funktion mit dem Status "Fehler" und der FFmpeg-Fehlermeldung auf.
    except FileNotFoundError: # ffmpeg.exe nicht gefunden.
        fehlermeldung_ffmpeg_pfad = f"ffmpeg.exe nicht gefunden!\nBitte überprüfen Sie den FFmpeg-Pfad in den Optionen (Datei -> Optionen)." # Fehlermeldung für den Fall, dass ffmpeg.exe nicht gefunden wurde.
        append_analyse_log("FEHLER: " + fehlermeldung_ffmpeg_pfad + "\n") # Schreibt die 'Datei nicht gefunden' Fehlermeldung in die Analyse-Log-Datei.
        window.after(0, update_gui_on_analyse_completion, "DateiNichtGefunden", datei, fehlermeldung_ffmpeg_pfad) # Ruft die GUI-Update-Funktion mit dem Status "DateiNichtGefunden" und der Fehlermeldung auf.
    except Exception as e: # Unerwarteter Fehler.
        fehlermeldung_unbekannt = f"Unerwarteter Fehler während der Audio-Analyse: {e}" # Fehlermeldung für unerwartete Fehler.
        append_analyse_log("FEHLER: " + fehlermeldung_unbekannt + "\n") # Schreibt die unbekannte Fehlermeldung in die Analyse-Log-Datei.
        window.after(0, update_gui_on_analyse_completion, "UnbekannterFehler", datei, fehlermeldung_unbekannt) # Ruft die GUI-Update-Funktion mit dem Status "UnbekannterFehler" und der Fehlermeldung auf.
    finally: # Wird immer ausgeführt, unabhängig davon, ob ein Fehler aufgetreten ist oder nicht.
        window.after(0, update_gui_on_analyse_completion) # Ruft die GUI-Update-Funktion ohne Status und Ergebnisse auf, um GUI-Elemente zurückzusetzen (z.B. Buttons wieder aktivieren, Fortschrittsbalken ausblenden).

def update_gui_on_analyse_completion(status=None, datei=None, ergebnis=None):
    """Aktualisiert die GUI nach Abschluss der Audioanalyse (wird im Hauptthread ausgeführt)."""
    analysieren_button.config(state=tk.NORMAL) # Reaktiviert den "Audio-Datei analysieren" Button.
    starten_button.config(state=tk.NORMAL) # Reaktiviert den "Normalisierung starten" Button.
    window.config(cursor="") # Setzt den Mauszeiger zurück zum Standard.
    progressbar.stop() # Stoppt die Animation des Fortschrittsbalkens.
    progressbar.grid_forget() # Blendet den Fortschrittsbalken aus dem Grid aus.
    fortschritt_label.grid_forget() # Blendet das Fortschritts-Label aus (ist aktuell nicht im Code sichtbar, könnte entfernt werden, falls nicht verwendet).

    prozess_info_feld.config(state=tk.NORMAL) # Aktiviert das Prozessinfo-Textfeld.
    prozess_info_feld.delete("1.0", tk.END) # Leert das Prozessinfo-Textfeld.
    if status == "Erfolg": # Analyse erfolgreich abgeschlossen.
        analyse_ergebnisse = ergebnis # Holt die Analyseergebnisse aus dem `ergebnis` Parameter.
        prozess_info_feld.insert(tk.END, f"Analyseergebnisse für: {os.path.basename(datei)}\n\n") # Fügt eine Überschrift mit dem Dateinamen in das Prozessinfo-Textfeld ein.
        prozess_info_feld.insert(tk.END, f"Integrierte Lautheit (LUFS):  {analyse_ergebnisse['input_i']} LUFS\n") # Zeigt die Integrierte Lautheit (LUFS) im Textfeld an.
        prozess_info_feld.insert(tk.END, f"True Peak:                  {analyse_ergebnisse['input_tp']} dBTP\n") # Zeigt den True Peak-Wert im Textfeld an.
        prozess_info_feld.insert(tk.END, f"Loudness Range (Input LRA): {analyse_ergebnisse['lra']} LU\n") # Zeigt die Loudness Range (LRA) im Textfeld an.

        # --- NEU: Hinweis-Text für Analyse-Logdatei ---
        hinweis_text_analyse = "Hinweis:\nDen Prozess der Audio-Analyse finden Sie in der Datei: ffmpeg_analyze.txt" # Hinweistext, der auf die Analyse-Logdatei verweist.
        # --- NEU: Hinweis-Text Ende ---

        analyse_meldung = ( # Formatiert eine Nachricht für die MessageBox mit den Analyseergebnissen.
            f"Analyse abgeschlossen für:\n{os.path.basename(datei)}\n\n" # Überschrift mit Dateiname.
            f"Integrierte Lautheit (LUFS):  {analyse_ergebnisse['input_i']} LUFS\n" # LUFS-Wert.
            f"True Peak:                  {analyse_ergebnisse['input_tp']} dBTP\n" # True Peak-Wert.
            f"Loudness Range (Input LRA): {analyse_ergebnisse['lra']} LU\n\n" # LRA-Wert.
            f"{hinweis_text_analyse}" # Fügt den Hinweis-Text zur Analyse-Logdatei hinzu.
        )
        messagebox.showinfo("Analyse abgeschlossen", analyse_meldung, parent=window) # Zeigt eine MessageBox mit den Analyseergebnissen.

    elif status == "Fehler": # FFmpeg Fehler während der Analyse.
        prozess_info_feld.insert(tk.END, ergebnis) # Zeigt die Fehlermeldung im Prozessinfo-Textfeld an.
        messagebox.showerror("Fehler", ergebnis, parent=window) # Zeigt eine MessageBox mit der Fehlermeldung.
    elif status == "DateiNichtGefunden": # ffmpeg.exe nicht gefunden.
        prozess_info_feld.insert(tk.END, fehlermeldung) # Zeigt die Fehlermeldung im Prozessinfo-Textfeld an.
        messagebox.showerror("Fehler", fehlermeldung, parent=window) # Zeigt eine MessageBox mit der Fehlermeldung.
    elif status == "UnbekannterFehler": # Unerwarteter Fehler während der Analyse.
        prozess_info_feld.insert(tk.END, ergebnis) # Zeigt die Fehlermeldung im Prozessinfo-Textfeld an.
        messagebox.showerror("Fehler", "Unerwarteter Fehler bei der Analyse!", parent=window) # Zeigt eine MessageBox mit einer generischen Fehlermeldung.
    prozess_info_feld.config(state=tk.DISABLED) # Deaktiviert das Prozessinfo-Textfeld wieder.

    window.after(100, lambda: window.focus_force()) # Setzt den Fokus zurück auf das Hauptfenster (nach 100ms Verzögerung).
    window.after(100, lambda: window.update()) # Aktualisiert die GUI (nach 100ms Verzögerung). `window.update()` erzwingt das Neuzeichnen der GUI, ist hier aber wahrscheinlich nicht unbedingt notwendig.
# --- ENDE Funktionen für Dateiauswahl, Audioanalyse und GUI-Update nach Analyse ---

# --- START Funktionen für Normalisierung, GUI-Update nach Normalisierung und Beenden ---
def starten():
    """Startet den Normalisierungsprozess der ausgewählten Audio-Datei in einem separaten Thread."""
    datei = datei_eingabe.get() # Liest den Dateipfad aus dem Eingabefeld.
    ausgabe_format = output_format_var.get() # Liest das ausgewählte Ausgabeformat aus der Combobox.
    lufs_preset_name = lufs_preset_var.get() # Liest den Namen des ausgewählten LUFS-Presets aus der Combobox.
    ziel_lufs = "" # Initialisiert die Variable für den Ziel-LUFS-Wert.
    true_peak_preset_name = true_peak_preset_var.get() # Liest den Namen des ausgewählten True Peak-Presets aus der Combobox.
    true_peak_wert_str = true_peak_eingabe.get() # Liest den eingegebenen True Peak-Wert als String aus dem Eingabefeld.
    ziel_true_peak = "" # Initialisiert die Variable für den Ziel-True Peak-Wert.

    if not datei: # Prüft, ob eine Datei ausgewählt wurde.
        messagebox.showerror("Fehler", "Bitte wählen Sie zuerst eine Audio-Datei aus.") # Fehlermeldung, wenn keine Datei ausgewählt ist.
        return # Beendet die Funktion.

    # --- Ziel-LUFS-Wert ermitteln ---
    if lufs_preset_name == "Benutzerdefiniert": # Benutzerdefiniertes Preset ausgewählt.
        ziel_lufs = lufs_eingabe.get() # Verwendet den Wert aus dem LUFS-Eingabefeld.
        if not ziel_lufs: # Wenn das Feld leer ist.
            ziel_lufs = "-10" # Setzt den Standardwert -10 LUFS, falls das Eingabefeld leer ist.
    else: # Vordefiniertes Preset ausgewählt.
        ziel_lufs = LUFS_PRESETS[lufs_preset_name] # Holt den LUFS-Wert aus dem LUFS_PRESETS Dictionary basierend auf dem ausgewählten Preset-Namen.
    # --- Ziel-LUFS-Wert Ende ---

    # --- Ziel-True Peak-Wert ermitteln und validieren ---
    if true_peak_preset_name == "Benutzerdefiniert": # Benutzerdefiniertes Preset ausgewählt.
        ziel_true_peak = true_peak_eingabe.get() # Verwendet den Wert aus dem True Peak-Eingabefeld.
    else: # Vordefiniertes Preset ausgewählt.
        ziel_true_peak = TRUE_PEAK_PRESETS[true_peak_preset_name] # Holt den True Peak-Wert aus dem TRUE_PEAK_PRESETS Dictionary.

    if true_peak_preset_name == "Benutzerdefiniert": # Validierung nur bei benutzerdefiniertem Wert notwendig.
        try:
            ziel_true_peak = float(ziel_true_peak) # Versucht, den eingegebenen Wert in eine Fließkommazahl umzuwandeln.
        except ValueError: # Fehler, wenn der eingegebene Wert keine gültige Zahl ist.
            messagebox.showerror("Fehler", "Ungültiger True Peak Wert!\nBitte geben Sie eine Zahl ein (z.B. -1 oder -2.5).", parent=window) # Fehlermeldung, wenn der True Peak-Wert ungültig ist.
            return # Beendet die Funktion.
    # --- Ziel-True Peak-Wert Ende ---

    # --- Ausgabeformat-Optionen ---
    format_optionen = { # Dictionary, das Formatoptionen für verschiedene Ausgabeformate speichert.
        "WAV": {"extension": ".wav", "codec": "pcm_f32le"}, # WAV Formatoptionen: Dateiendung und Codec.
        "MP3": {"extension": ".mp3", "codec": "libmp3lame"}, # MP3 Formatoptionen.
        "FLAC": {"extension": ".flac", "codec": "flac"},   # FLAC Formatoptionen.
        "AAC": {"extension": ".m4a", "codec": "aac"},     # AAC Formatoptionen.
        "OGG": {"extension": ".ogg", "codec": "libvorbis"}  # OGG Formatoptionen.
    }
    gewähltes_format = format_optionen[ausgabe_format] # Holt die Formatoptionen für das ausgewählte Ausgabeformat aus dem Dictionary.
    ausgabe_datei_name_ohne_endung = os.path.splitext(datei)[0] + "-Normalized" # Erzeugt den Ausgabedateinamen, indem die Dateiendung der Eingabedatei entfernt und "-Normalized" angehängt wird.
    ausgabe_datei = ausgabe_datei_name_ohne_endung + gewähltes_format["extension"] # Fügt die Dateiendung des ausgewählten Formats zum Ausgabedateinamen hinzu.
    # --- Ausgabeformat-Optionen Ende ---

    # --- Datei überschreiben-Dialog ---
    if os.path.exists(ausgabe_datei): # Prüft, ob die Ausgabedatei bereits existiert.
        antwort = messagebox.askyesnocancel( # Zeigt einen Dialog zur Bestätigung des Überschreibens (Ja/Nein/Abbrechen).
            "Datei existiert", # Titel des Dialogs.
            f"Die Datei '{os.path.basename(ausgabe_datei)}' existiert bereits.\nMöchten Sie sie überschreiben?", # Nachricht im Dialog.
            default=messagebox.NO, # Setzt "Nein" als Standardauswahl.
            icon=messagebox.WARNING, # Zeigt ein Warnsymbol im Dialog.
            parent=window # Setzt das Hauptfenster als Elternfenster für den Dialog.
        )
        if antwort is True: # Benutzer hat "Ja" gewählt (überschreiben).
            pass # Fortfahren mit dem Überschreiben.
        elif antwort is False: # Benutzer hat "Nein" gewählt (nicht überschreiben).
            return # Normalisierungsprozess abbrechen.
        elif antwort is None: # Benutzer hat "Abbrechen" gewählt.
            return # Normalisierungsprozess abbrechen.
    # --- Datei überschreiben-Dialog Ende ---

    # --- FFmpeg Befehl für Normalisierung ---
    ffmpeg_befehl = [
        os.path.join(ffmpeg_pfad, "ffmpeg.exe"), # Pfad zu ffmpeg.exe zusammensetzen.
        "-y", # Option "-y": Überschreibt Ausgabedateien ohne Rückfrage.
        "-i", datei, # Eingabedatei angeben.
        "-af", f"loudnorm=I={ziel_lufs}:TP={ziel_true_peak}", # Audiofilter 'loudnorm' aktivieren und Ziel-LUFS und True Peak-Werte setzen.
        "-ar", "48000", # Sample Rate auf 48kHz setzen.
        "-ac", "2", # Audiokanäle auf 2 (Stereo) setzen.
        "-c:a", gewähltes_format["codec"], # Audio-Codec für das Ausgabeformat setzen, aus den Formatoptionen geholt.
        ausgabe_datei # Ausgabedatei angeben.
    ]
    # --- FFmpeg Befehl Ende ---

    # --- GUI-Elemente für Normalisierungs-Prozesszustand ---
    starten_button.config(state=tk.DISABLED) # Deaktiviert den "Normalisierung starten" Button während der Normalisierung.
    analysieren_button.config(state=tk.DISABLED) # Deaktiviert den "Audio-Datei analysieren" Button während der Normalisierung.
    window.config(cursor="wait") # Ändert den Mauszeiger zu "Warten".
    progressbar.grid(row=4, column=0, columnspan=3, sticky="ew", padx=20, pady=(0,10)) # Zeigt den Fortschrittsbalken im Grid an.
    progressbar.start() # Startet die Animation des Fortschrittsbalkens.
    # --- GUI-Elemente Ende ---

    prozess_info_feld.config(state=tk.NORMAL) # Aktiviert das Prozessinfo-Textfeld.
    prozess_info_feld.delete("1.0", tk.END) # Leert das Prozessinfo-Textfeld.
    prozess_info_feld.insert(tk.END, f"Normalisierung gestartet...\nDatei: {os.path.basename(datei_eingabe.get())}\nZiel LUFS: {ziel_lufs} LUFS\nTrue Peak: {ziel_true_peak} dBTP\nAusgabeformat: {ausgabe_format}\nLUFS Preset: {lufs_preset_name}\nTP Preset: {true_peak_preset_name}\nBitte Geduld, der Prozess läuft...") # Setzt eine Startmeldung mit Prozessinformationen in das Prozessinfo-Textfeld.
    prozess_info_feld.config(state=tk.DISABLED) # Deaktiviert das Prozessinfo-Textfeld wieder.
    prozess_info_feld.grid() # Stellt sicher, dass das Textfeld im Grid-Layout angezeigt wird (redundant, da es bereits im Grid platziert wurde, aber schadet nicht).

    log_eintrag_start = f"======================== {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')} ========================\n" # Start-Separator für den Log-Eintrag mit Zeitstempel.
    log_eintrag_start += f"Normalisierung gestartet für Datei: {datei}\n" # Log-Meldung: Normalisierung gestartet.
    log_eintrag_start += f"Ziel LUFS: {ziel_lufs} LUFS\n" # Log-Meldung: Ziel LUFS-Wert.
    log_eintrag_start += f"True Peak: {ziel_true_peak} dBTP\n" # Log-Meldung: Ziel True Peak-Wert.
    log_eintrag_start += f"Ausgabeformat: {ausgabe_format}\n" # Log-Meldung: Ausgewähltes Ausgabeformat.
    log_eintrag_start += f"LUFS Preset: {lufs_preset_name}\n" # Log-Meldung: Ausgewähltes LUFS Preset.
    log_eintrag_start += f"TP Preset: {true_peak_preset_name}\n" # Log-Meldung: Ausgewähltes True Peak Preset.
    log_eintrag_start += "FFmpeg Befehl:\n" + " ".join(ffmpeg_befehl) + "\n\n" # Log-Meldung: FFmpeg-Befehl.
    append_log(log_eintrag_start) # Schreibt die Startmeldung und den FFmpeg-Befehl in die Normalisierungs-Log-Datei.

    thread = threading.Thread(target=normalize_audio_thread_function, args=(ffmpeg_befehl, ausgabe_datei)) # Erstellt einen neuen Thread für die Normalisierungsfunktion.
    thread.start() # Startet den Normalisierungs-Thread.

def normalize_audio_thread_function(ffmpeg_befehl, ausgabe_datei):
    """Führt die Audio-Normalisierung mit FFmpeg in einem separaten Thread aus."""
    try:
        ergebnis = subprocess.run(ffmpeg_befehl, check=True, capture_output=True, text=True) # Führt den FFmpeg-Normalisierungsbefehl aus.
        log_ausgabe = ergebnis.stdout + "\n" + ergebnis.stderr # Kombiniert Standardausgabe und Standardfehler von FFmpeg für die Log-Datei.
        append_log(log_ausgabe) # Schreibt die FFmpeg-Ausgabe in die Normalisierungs-Log-Datei.
        window.after(0, update_gui_on_completion, "Erfolg", ausgabe_datei) # Ruft die GUI-Update-Funktion im Hauptthread auf, um den Erfolg zu melden.
    except subprocess.CalledProcessError as e: # Fehler beim Ausführen von FFmpeg.
        fehlermeldung = f"FFmpeg Fehler:\nReturn Code: {e.returncode}\n{e.stderr}" # Formatiert eine Fehlermeldung mit dem Return-Code und der Fehlerausgabe von FFmpeg.
        append_log("FEHLER: " + fehlermeldung + "\n") # Schreibt die FFmpeg-Fehlermeldung in die Normalisierungs-Log-Datei.
        window.after(0, update_gui_on_completion, "Fehler", ausgabe_datei, fehlermeldung) # Ruft die GUI-Update-Funktion mit dem Status "Fehler" und der Fehlermeldung auf.
    except FileNotFoundError: # ffmpeg.exe nicht gefunden.
        fehlermeldung = f"ffmpeg.exe nicht gefunden!\nBitte überprüfen Sie den FFmpeg-Pfad in den Optionen (Datei -> Optionen)." # Fehlermeldung für den Fall, dass ffmpeg.exe nicht gefunden wurde.
        append_log("FEHLER: " + fehlermeldung + "\n") # Schreibt die 'Datei nicht gefunden' Fehlermeldung in die Normalisierungs-Log-Datei.
        window.after(0, update_gui_on_completion, "DateiNichtGefunden", ausgabe_datei, fehlermeldung) # Ruft die GUI-Update-Funktion mit dem Status "DateiNichtGefunden" und der Fehlermeldung auf.
    except Exception as e: # Unerwarteter Fehler.
        fehlermeldung = f"Unerwarteter Fehler während der Normalisierung: {e}" # Fehlermeldung für unerwartete Fehler.
        append_log("FEHLER: " + fehlermeldung + "\n") # Schreibt die unbekannte Fehlermeldung in die Normalisierungs-Log-Datei.
        window.after(0, update_gui_on_completion, "UnbekannterFehler", ausgabe_datei, fehlermeldung) # Ruft die GUI-Update-Funktion mit dem Status "UnbekannterFehler" und der Fehlermeldung auf.
    finally: # Wird immer ausgeführt, unabhängig davon, ob ein Fehler aufgetreten ist oder nicht.
        window.after(0, update_gui_on_completion) # Ruft die GUI-Update-Funktion ohne Status auf, um GUI-Elemente zurückzusetzen.

def update_gui_on_completion(status=None, ausgabe_datei=None, fehlermeldung=None):
    """Aktualisiert die GUI nach Abschluss der Normalisierung (wird im Hauptthread ausgeführt)."""
    starten_button.config(state=tk.NORMAL) # Reaktiviert den "Normalisierung starten" Button.
    analysieren_button.config(state=tk.NORMAL) # Reaktiviert den "Audio-Datei analysieren" Button.
    window.config(cursor="") # Setzt den Mauszeiger zurück zum Standard.
    progressbar.stop() # Stoppt die Animation des Fortschrittsbalkens.
    progressbar.grid_forget() # Blendet den Fortschrittsbalken aus dem Grid aus.
    fortschritt_label.grid_forget() # Blendet das Fortschritts-Label aus.

    prozess_info_feld.config(state=tk.NORMAL) # Aktiviert das Prozessinfo-Textfeld.
    prozess_info_feld.delete("1.0", tk.END) # Leert das Prozessinfo-Textfeld.
    if status == "Erfolg": # Normalisierung erfolgreich abgeschlossen.
        prozess_info_feld.insert(tk.END, f"Normalisierung abgeschlossen!\nDatei: {os.path.basename(datei_eingabe.get())}\nAusgabedatei erstellt:\n{ausgabe_datei}") # Erfolgsmeldung im Prozessinfo-Textfeld.
        ausgabe_datei_name_nur_datei = os.path.basename(ausgabe_datei)  # Extrahiert nur den Dateinamen der Ausgabedatei.
        hinweis_text = "Hinweis:\nDen Prozess der Normalisierung finden Sie in der Datei: ffmpeg_log.txt" # Hinweistext zur Log-Datei.
        messagebox.showinfo( # Zeigt eine Erfolgs-MessageBox.
            "Erfolg", # Titel der MessageBox.
            f"Normalisierung abgeschlossen!\n\nAusgabedatei:\n{ausgabe_datei_name_nur_datei}\n\n{hinweis_text}", # Nachricht in der MessageBox.
            parent=window # Setzt das Hauptfenster als Elternfenster für die MessageBox.
        )
    elif status == "Fehler": # FFmpeg Fehler während der Normalisierung.
        prozess_info_feld.insert(tk.END, fehlermeldung) # Zeigt die Fehlermeldung im Prozessinfo-Textfeld an.
        messagebox.showerror("Fehler", fehlermeldung, parent=window) # Zeigt eine MessageBox mit der Fehlermeldung.
    elif status == "DateiNichtGefunden": # ffmpeg.exe nicht gefunden.
        prozess_info_feld.insert(tk.END, fehlermeldung) # Zeigt die Fehlermeldung im Prozessinfo-Textfeld an.
        messagebox.showerror("Fehler", fehlermeldung, parent=window) # Zeigt eine MessageBox mit der Fehlermeldung.
    elif status == "UnbekannterFehler": # Unerwarteter Fehler während der Normalisierung.
        prozess_info_feld.insert(tk.END, fehlermeldung) # Zeigt die Fehlermeldung im Prozessinfo-Textfeld an.
        messagebox.showerror("Fehler", "Unerwarteter Fehler!", parent=window) # Zeigt eine MessageBox mit einer generischen Fehlermeldung.
    prozess_info_feld.config(state=tk.DISABLED) # Deaktiviert das Prozessinfo-Textfeld wieder.

    window.after(100, lambda: window.focus_force()) # Setzt den Fokus zurück auf das Hauptfenster.
    window.after(100, lambda: window.update()) # Aktualisiert die GUI.

def beenden():
    """Beendet das Programm."""
    window.destroy() # Zerstört das Hauptfenster und beendet die Tkinter-mainloop.
# --- ENDE Funktionen für Normalisierung, GUI-Update nach Normalisierung und Beenden ---

# --- START Funktionen für Log-Datei-Handling ---
def append_log(text):
    """Schreibt Text in die Log-Datei 'ffmpeg_log.txt' (mit Größenbegrenzung und Rolling)."""
    _append_log_with_rolling(LOG_DATEI, text) # Ruft die Hilfsfunktion für das Schreiben in die Log-Datei auf, übergibt den Dateinamen und den zu schreibenden Text.

def append_analyse_log(text):
    """Schreibt Text in die Log-Datei für die Analyse 'ffmpeg_analyze.txt' (mit Größenbegrenzung und Rolling)."""
    _append_log_with_rolling(ANALYSE_LOG_DATEI, text) # Ruft die Hilfsfunktion für die Analyse-Log-Datei auf.

def _append_log_with_rolling(log_datei_name, text):
    """Hilfsfunktion zum Schreiben in eine Log-Datei mit Größenbegrenzung und Rolling."""
    try:
        log_datei_pfad = log_datei_name # Setzt den Log-Datei-Pfad auf den übergebenen Dateinamen.
        log_groessen_limit_bytes = log_datei_groesse_kb * 1024 # Berechnet das Größenlimit für die Log-Datei in Bytes (KB * 1024).

        # --- NEU: Prüfe Einzel-Log-Eintrag Option ---
        if single_log_eintrag_aktiviert: # Wenn die Einzel-Log-Eintrag Option aktiv ist.
            if os.path.exists(log_datei_pfad): # Prüft, ob die Log-Datei bereits existiert.
                with open(log_datei_pfad, "w") as logfile_truncate: # Öffnet die Log-Datei im Schreibmodus "w" (Truncate-Modus), wodurch der Dateiinhalt gelöscht wird.
                    pass # Die Datei wird durch das Öffnen im "w"-Modus geleert.
        else: # Wenn die Einzel-Log-Eintrag Option NICHT aktiv ist -> Normale Log-Rolling Logik.
            # --- Prüfe Log-Datei Größe vor dem Schreiben ---
            if os.path.exists(log_datei_pfad) and os.path.getsize(log_datei_pfad) >= log_groessen_limit_bytes: # Prüft, ob die Log-Datei existiert und ihre Größe das Limit überschreitet.
                with open(log_datei_pfad, "r") as logfile_r: # Öffnet die Log-Datei im Lesemodus "r".
                    zeilen = logfile_r.readlines() # Liest alle Zeilen der Log-Datei in eine Liste.

                zu_schreibende_zeilen = zeilen[len(zeilen)//2:] # Nimmt die zweite Hälfte der Zeilen (ab der Mitte) für die neue Log-Datei. `len(zeilen)//2` berechnet den Mittelindex.

                with open(log_datei_pfad, "w") as logfile_w: # Öffnet die Log-Datei im Schreibmodus "w", um sie zu überschreiben.
                    logfile_w.writelines(zu_schreibende_zeilen) # Schreibt die zweite Hälfte der alten Zeilen in die neue (gekürzte) Log-Datei.
            # --- Rolling Logik Ende ---
        # --- NEU: Einzel-Log-Eintrag Option Ende ---

        with open(log_datei_pfad, "a") as logfile: # Öffnet die Log-Datei im Anhängen-Modus "a".
            logfile.write(text) # Schreibt den übergebenen Text am Ende der Log-Datei.

    except Exception as e: # Fängt alle möglichen Fehler beim Schreiben in die Log-Datei ab.
        print(f"Fehler beim Schreiben in Log-Datei {log_datei_name}: {e}") # Gibt eine Fehlermeldung in der Konsole aus, falls ein Fehler beim Schreiben auftritt.
# --- ENDE Funktionen für Log-Datei-Handling ---

# --- START GUI-Code ---
# --- Hauptfenster ---
window = tk.Tk() # Erstellt das Hauptfenster der Tkinter-Anwendung.
window.title(f"melcom's FFmpeg Audio Normalizer v{VERSION}") # Setzt den Fenstertitel mit dem Programmnamen und der Version.
window.geometry("700x610") # Setzt die initiale Fenstergröße (Breite x Höhe in Pixeln).

# --- Menüleiste ---
menubar = tk.Menu(window) # Erstellt die Menüleiste.
filemenu = tk.Menu(menubar, tearoff=0) # Erstellt das "Datei"-Menü, `tearoff=0` verhindert, dass es abgetrennt werden kann.
filemenu.add_command(label="Optionen", command=show_options_dialog) # Fügt den "Optionen"-Menüeintrag hinzu und verknüpft ihn mit der Funktion `show_options_dialog`.
filemenu.add_separator() # Fügt eine horizontale Trennlinie im Menü hinzu.
filemenu.add_command(label="Beenden", command=beenden) # Fügt den "Beenden"-Menüeintrag hinzu und verknüpft ihn mit der Funktion `beenden`.
menubar.add_cascade(label="Datei", menu=filemenu) # Fügt das "Datei"-Menü zur Menüleiste hinzu.

infomenu = tk.Menu(menubar, tearoff=0) # Erstellt das "Info"-Menü.
infomenu.add_command(label="Über", command=show_info_box) # Fügt den "Über"-Menüeintrag hinzu und verknüpft ihn mit der Funktion `show_info_box`.
menubar.add_cascade(label="Info", menu=infomenu) # Fügt das "Info"-Menü zur Menüleiste hinzu.
window.config(menu=menubar) # Konfiguriert das Hauptfenster, die Menüleiste anzuzeigen.
# --- Menüleiste Ende ---

# --- Haupt-GUI-Elemente (Frame für den Inhalt) ---
content_frame = tk.Frame(window, padx=20, pady=20) # Erstellt einen Frame für den Hauptinhalt, mit Innenabstand (Padding).
content_frame.grid(row=0, column=0, sticky="nsew") # Platziert den Content-Frame im Grid-Layout im Hauptfenster, `sticky="nsew"` sorgt dafür, dass er sich in alle Richtungen ausdehnt, wenn das Fenster größer wird.
# --- Haupt-GUI-Elemente Ende ---
# --- ENDE GUI-Code ---

# --- START GUI-Code - Inhalt des Content-Frames ---
# --- Datei Auswahl Bereich ---
datei_frame = tk.Frame(content_frame) # Frame für den Bereich zur Dateiauswahl.
datei_frame.grid(row=0, column=0, columnspan=3, sticky="ew", pady=(0, 10)) # Platziert im Grid, spannt über 3 Spalten, horizontale Ausdehnung, Abstand nach unten.

datei_label = tk.Label(datei_frame, text="Audio-Datei auswählen:", anchor="w") # Label für Dateiauswahl, linksbündig (`anchor="w"`).
datei_label.grid(row=0, column=0, sticky="w", padx=(0, 5)) # Platziert im Grid, linksbündig, Abstand rechts.
datei_eingabe = tk.Entry(datei_frame, width=70) # Eingabefeld für den Dateipfad, Breite 70 Zeichen.
datei_eingabe.grid(row=0, column=1, sticky="ew") # Platziert im Grid, horizontale Ausdehnung.
durchsuchen_button = tk.Button(datei_frame, text="Durchsuchen", command=browse_datei) # Button zum Öffnen des Dateiauswahldialogs, verknüpft mit der Funktion `browse_datei`.
durchsuchen_button.grid(row=0, column=2, padx=(5, 0)) # Platziert im Grid, Abstand links.
# --- Datei Auswahl Bereich Ende ---

# --- LUFS Optionen Bereich (LabelFrame) ---
lufs_options_frame = tk.LabelFrame(content_frame, text="LUFS Optionen", padx=10, pady=10) # LabelFrame für LUFS-Optionen, mit Rahmen und Beschriftung "LUFS Optionen", Innenabstand.
lufs_options_frame.grid(row=1, column=0, columnspan=3, sticky="ew", pady=(10, 10)) # Platziert im Grid, spannt über 3 Spalten, horizontale Ausdehnung, Abstand oben und unten.

lufs_preset_label = tk.Label(lufs_options_frame, text="LUFS Preset:", anchor="w") # Label für LUFS-Preset-Auswahl.
lufs_preset_label.grid(row=0, column=0, sticky="w", padx=(0, 5), pady=(0, 5)) # Platziert im Grid, linksbündig, Abstand rechts und unten.
lufs_preset_var = tk.StringVar() # StringVar zur Speicherung des ausgewählten LUFS-Presets (für die Combobox).
lufs_preset_combobox = Combobox( # Combobox für die Auswahl der LUFS-Presets.
    lufs_options_frame, # Eltern-Widget.
    textvariable=lufs_preset_var, # Verknüpft mit der StringVar.
    values=LUFS_PRESET_NAMES, # Setzt die angezeigten Werte auf die Liste der Preset-Namen (aus LUFS_PRESET_NAMES Liste).
    state="readonly", # Verhindert manuelle Eingabe in die Combobox, nur Auswahl aus der Liste möglich.
    width=30 # Setzt die Breite der Combobox.
)
lufs_preset_combobox.set(LUFS_PRESET_NAMES[0]) # Setzt das standardmäßig ausgewählte Preset auf das erste in der Liste.
lufs_preset_combobox.grid(row=0, column=1, sticky="ew", padx=(0, 20), pady=(0, 5)) # Platziert im Grid, horizontale Ausdehnung, Abstand rechts und unten.

lufs_label = tk.Label(lufs_options_frame, text="Ziel-LUFS-Wert (Benutzerdefiniert):", anchor="w") # Label für das benutzerdefinierte LUFS-Eingabefeld.
lufs_label.grid(row=1, column=0, sticky="w", padx=(0, 5), pady=(5, 10)) # Platziert im Grid, linksbündig, Abstand rechts, oben und unten.
lufs_eingabe = tk.Entry(lufs_options_frame, width=10) # Eingabefeld für den benutzerdefinierten LUFS-Wert.
lufs_eingabe.insert(0, "-10") # Setzt den Standardwert im Eingabefeld auf -10.
lufs_eingabe.grid(row=1, column=1, sticky="w", padx=(0, 20), pady=(5, 10)) # Platziert im Grid, linksbündig, Abstand rechts, oben und unten.

true_peak_preset_label = tk.Label(lufs_options_frame, text="True Peak Preset:", anchor="w") # Label für True Peak-Preset-Auswahl.
true_peak_preset_label.grid(row=2, column=0, sticky="w", padx=(0, 5), pady=(0, 5)) # Platziert im Grid, linksbündig, Abstand rechts und unten.
true_peak_preset_var = tk.StringVar() # StringVar für True Peak-Preset Combobox.
true_peak_preset_combobox = Combobox( # Combobox für True Peak-Preset-Auswahl.
    lufs_options_frame, # Eltern-Widget.
    textvariable=true_peak_preset_var, # Verknüpft mit StringVar.
    values=TRUE_PEAK_PRESET_NAMES, # Werte aus TRUE_PEAK_PRESET_NAMES Liste.
    state="readonly", # Readonly Combobox.
    width=20 # Breite der Combobox.
)
true_peak_preset_combobox.set(TRUE_PEAK_PRESET_NAMES[0]) # Standardauswahl.
true_peak_preset_combobox.grid(row=2, column=1, sticky="ew", padx=(0, 20), pady=(0, 5)) # Platzierung im Grid.

true_peak_label = tk.Label(lufs_options_frame, text="True Peak (dBTP):", anchor="w") # Label für True Peak-Eingabefeld.
true_peak_label.grid(row=3, column=0, sticky="w", padx=(0, 5), pady=(5, 0)) # Platzierung im Grid.
true_peak_eingabe = tk.Entry(lufs_options_frame, width=10) # Eingabefeld für True Peak-Wert.
true_peak_eingabe.insert(0, "-1") # Standardwert -1.
true_peak_eingabe.grid(row=3, column=1, sticky="w", padx=(0, 20), pady=(5, 0)) # Platzierung im Grid.

def update_lufs_eingabe_state(*args):
    """Aktiviert oder deaktiviert das LUFS-Eingabefeld basierend auf der Preset-Auswahl."""
    if lufs_preset_var.get() == "Benutzerdefiniert": # Wenn "Benutzerdefiniert" in der LUFS-Preset Combobox ausgewählt ist.
        lufs_eingabe.config(state=tk.NORMAL) # Aktiviere das LUFS-Eingabefeld.
    else: # Wenn ein vordefiniertes Preset ausgewählt ist.
        lufs_eingabe.config(state=tk.DISABLED) # Deaktiviere das LUFS-Eingabefeld.
        lufs_eingabe.delete(0, tk.END) # Leere das LUFS-Eingabefeld.
        lufs_eingabe.insert(0, LUFS_PRESETS[lufs_preset_var.get()]) # Setze den Wert des Eingabefelds auf den Wert des ausgewählten Presets aus LUFS_PRESETS.

lufs_preset_var.trace("w", update_lufs_eingabe_state) # Überwacht Änderungen der LUFS-Preset-Combobox. Bei jeder Änderung wird `update_lufs_eingabe_state` aufgerufen.
update_lufs_eingabe_state() # Initialer Aufruf, um den Zustand des LUFS-Eingabefelds beim Programmstart zu setzen.

def update_true_peak_eingabe_state(*args):
    """Aktiviert oder deaktiviert das True Peak-Eingabefeld basierend auf der Preset-Auswahl."""
    if true_peak_preset_var.get() == "Benutzerdefiniert": # Wenn "Benutzerdefiniert" im True Peak-Preset ausgewählt.
        true_peak_eingabe.config(state=tk.NORMAL) # Aktiviere True Peak-Eingabefeld.
    else: # Vordefiniertes Preset.
        true_peak_eingabe.config(state=tk.DISABLED) # Deaktiviere True Peak-Eingabefeld.
        true_peak_eingabe.delete(0, tk.END) # Leere True Peak-Eingabefeld.
        true_peak_eingabe.insert(0, TRUE_PEAK_PRESETS[true_peak_preset_var.get()]) # Setze Wert auf Preset-Wert.

true_peak_preset_var.trace("w", update_true_peak_eingabe_state) # Überwacht Änderungen der True Peak-Preset-Combobox und ruft `update_true_peak_eingabe_state` auf.
update_true_peak_eingabe_state() # Initialer Aufruf.
# --- LUFS Optionen Bereich Ende ---

# --- Ausgabeformat Optionen Bereich (LabelFrame) ---
output_format_frame = tk.LabelFrame(content_frame, text="Ausgabeformat", padx=10, pady=10) # LabelFrame für Ausgabeformat-Optionen.
output_format_frame.grid(row=2, column=0, columnspan=3, sticky="ew", pady=(0, 10)) # Platzierung im Grid.

output_format_label = tk.Label(output_format_frame, text="Format:", anchor="w") # Label für Ausgabeformat-Auswahl.
output_format_label.grid(row=0, column=0, sticky="w", padx=(0, 5), pady=(0, 5)) # Platzierung im Grid.
output_format_var = tk.StringVar() # StringVar für Ausgabeformat-Combobox.
output_format_combobox = Combobox( # Combobox für Ausgabeformat-Auswahl.
    output_format_frame, # Eltern-Widget.
    textvariable=output_format_var, # Verknüpft mit StringVar.
    values=["WAV", "MP3", "FLAC", "AAC", "OGG"], # Liste der verfügbaren Ausgabeformate.
    state="readonly" # Readonly Combobox.
)
output_format_combobox.set("WAV") # Standardauswahl "WAV".
output_format_combobox.grid(row=0, column=1, sticky="w", padx=(0, 20), pady=(0, 5)) # Platzierung im Grid.
# --- Ausgabeformat Optionen Bereich Ende ---

# --- Buttons: Normalisieren & Analysieren (nebeneinander in einem Frame) ---
buttons_frame = tk.Frame(content_frame) # Frame für die Buttons "Normalisierung starten" und "Audio-Datei analysieren".
buttons_frame.grid(row=3, column=0, columnspan=3, pady=(20, 10), sticky="ew") # Platzierung im Grid, Abstand oben und unten, horizontale Ausdehnung.

starten_button = tk.Button(buttons_frame, text="Normalisierung starten", command=starten, padx=20, pady=10) # "Normalisierung starten" Button, verknüpft mit der Funktion `starten`.
starten_button.grid(row=0, column=0, sticky="ew") # Platzierung im Grid, horizontale Ausdehnung.
analysieren_button = tk.Button(buttons_frame, text="Audio-Datei analysieren", command=audio_analysieren, padx=20, pady=10) # "Audio-Datei analysieren" Button, verknüpft mit `audio_analysieren`.
analysieren_button.grid(row=0, column=1, sticky="ew", padx=(10, 0)) # Platzierung im Grid, horizontale Ausdehnung, Abstand links.

buttons_frame.columnconfigure(0, weight=1) # Konfiguriert Spalte 0 des Buttons-Frames, um sich horizontal auszudehnen und den verfügbaren Platz gleichmäßig zu verteilen.
buttons_frame.columnconfigure(1, weight=1) # Konfiguriert Spalte 1 des Buttons-Frames ebenfalls zur horizontalen Ausdehnung.
# --- Buttons Frame Ende ---
# --- ENDE GUI-Code - Inhalt des Content-Frames ---

# --- START GUI-Code - Fortschrittsbalken, Prozessinformationen und Mainloop ---
# --- Fortschrittsbalken ---
progressbar = Progressbar(content_frame, mode='indeterminate') # Fortschrittsbalken im 'indeterminate'-Modus (keine Fortschrittsanzeige, nur Animation), Eltern-Widget ist `content_frame`.

# --- Prozessinformationen Bereich ---
prozess_info_frame = tk.Frame(content_frame) # Frame für den Bereich zur Anzeige von Prozessinformationen.
prozess_info_frame.grid(row=5, column=0, columnspan=3, sticky="nsew") # Platzierung im Grid, spannt über 3 Spalten, dehnt sich in alle Richtungen aus.

fortschritt_label = tk.Label(prozess_info_frame, text="Prozess-Informationen:", anchor="w") # Label für den Prozessinformationsbereich.
fortschritt_label.grid(row=0, column=0, sticky="ew", padx=5, pady=(0, 5)) # Platzierung im Grid, horizontale Ausdehnung, Abstand horizontal und unten.

prozess_info_feld = tk.Text(prozess_info_frame, height=6, width=60, wrap=tk.WORD, state=tk.DISABLED, bg="lightgrey") # Textfeld zur Anzeige von Prozessinformationen.
                                                                                                      # `height=6`: Höhe in Textzeilen.
                                                                                                      # `width=60`: Breite in Zeichen.
                                                                                                      # `wrap=tk.WORD`: Zeilenumbruch bei Wortgrenzen.
                                                                                                      # `state=tk.DISABLED`: Textfeld ist initial deaktiviert (nicht editierbar).
                                                                                                      # `bg="lightgrey"`: Hintergrundfarbe hellgrau.
prozess_info_feld.grid(row=1, column=0, sticky="nsew", padx=5, pady=(0, 5)) # Platzierung im Grid, dehnt sich in alle Richtungen aus, Abstand horizontal und unten.

prozess_info_frame.columnconfigure(0, weight=1) # Konfiguriert Spalte 0 des Prozessinfo-Frames, um sich horizontal auszudehnen.
prozess_info_frame.rowconfigure(1, weight=1) # Konfiguriert Zeile 1 des Prozessinfo-Frames (das Textfeld), um sich vertikal auszudehnen.
# --- Prozessinformationen Bereich Ende ---

# --- Konfiguration des Grids im Hauptfenster und Content-Frame ---
content_frame.columnconfigure(0, weight=1) # Konfiguriert Spalte 0 des Content-Frames, um sich horizontal auszudehnen.
content_frame.rowconfigure(5, weight=1) # Konfiguriert Zeile 5 des Content-Frames (Prozessinfo-Bereich), um sich vertikal auszudehnen.
window.columnconfigure(0, weight=1) # Konfiguriert Spalte 0 des Hauptfensters, um sich horizontal auszudehnen.
window.rowconfigure(0, weight=1) # Konfiguriert Zeile 0 des Hauptfensters (Content-Frame), um sich vertikal auszudehnen.

load_options() # Lädt die Programmeinstellungen beim Programmstart (ruft die Funktion `load_options` auf).

window.mainloop() # Startet die Tkinter-Ereignisschleife. Das Programm bleibt in dieser Schleife und wartet auf GUI-Ereignisse (z.B. Button-Klicks, Menüauswahl) bis das Fenster geschlossen wird.
# --- ENDE GUI-Code - Fortschrittsbalken, Prozessinformationen und Mainloop ---
