Layout TLabelFrame was not found here is code import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import os
import subprocess
import threading
import concurrent.futures
import queue
import sys

class VideoImageConverterApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Video to Image Converter")
        self.root.geometry("1000x700")
        self.root.minsize(800, 600)
        self.root.configure(bg="#0b0c10")  # Dark background

        # Initialize data structures
        self.input_items = []  # List of tuples: (path, type)
        self.output_folder = tk.StringVar()
        self.selected_format = tk.StringVar(value="JPG")

        # Configure styles
        self.configure_styles()

        # Setup UI
        self.setup_ui()

    def configure_styles(self):
        style = ttk.Style(self.root)
        style.theme_use('clam')  # Modern theme

        # Define color palette
        self.BACKGROUND_COLOR = "#0b0c10"
        self.FRAME_COLOR = "#1f2833"
        self.BUTTON_COLOR = "#c5c6c7"
        self.TEXT_COLOR = "#66fcf1"
        self.PROGRESS_COLOR = "#45a29e"

        # Style configurations
        style.configure("TButton",
                        foreground=self.BACKGROUND_COLOR,
                        background=self.BUTTON_COLOR,
                        font=("Segoe UI", 10, "bold"),
                        borderwidth=1)
        style.map("TButton",
                  background=[('active', self.PROGRESS_COLOR)])

        style.configure("TLabel",
                        foreground=self.TEXT_COLOR,
                        background=self.BACKGROUND_COLOR,
                        font=("Segoe UI", 10))

        style.configure("TLabelFrame",
                        foreground=self.TEXT_COLOR,
                        background=self.FRAME_COLOR,
                        font=("Segoe UI", 12, "bold"),
                        labelbackground=self.FRAME_COLOR)

        style.configure("Horizontal.TProgressbar",
                        troughcolor=self.FRAME_COLOR,
                        background=self.PROGRESS_COLOR)

        style.configure("Readonly.TEntry",
                        fieldbackground=self.FRAME_COLOR,
                        foreground=self.TEXT_COLOR,
                        font=("Segoe UI", 10))

        # Style for Treeview
        style.configure("Custom.Treeview",
                        background=self.FRAME_COLOR,
                        foreground=self.TEXT_COLOR,
                        fieldbackground=self.FRAME_COLOR,
                        font=("Segoe UI", 10))
        style.map("Custom.Treeview",
                  background=[('selected', self.PROGRESS_COLOR)],
                  foreground=[('selected', self.BACKGROUND_COLOR)])

    def setup_ui(self):
        # Configure grid layout
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)

        main_frame = ttk.Frame(self.root, style="TFrame")
        main_frame.grid(row=0, column=0, sticky="NSEW", padx=10, pady=10)

        main_frame.columnconfigure(0, weight=1)
        main_frame.rowconfigure(1, weight=1)

        # Input Items Section
        input_frame = ttk.LabelFrame(main_frame, text="Input Items", style="TLabelFrame")
        input_frame.grid(row=0, column=0, sticky="EW", padx=5, pady=5)
        input_frame.columnconfigure(0, weight=1)

        # Treeview for input items
        columns = ("Type", "Path")
        self.tree = ttk.Treeview(input_frame, columns=columns, show='headings', style="Custom.Treeview")
        self.tree.heading("Type", text="Type")
        self.tree.heading("Path", text="Path")
        self.tree.column("Type", width=100, anchor='center')
        self.tree.column("Path", anchor='w')

        self.tree.grid(row=0, column=0, sticky="NSEW")

        # Scrollbar for Treeview
        tree_scroll = ttk.Scrollbar(input_frame, orient="vertical", command=self.tree.yview)
        self.tree.configure(yscrollcommand=tree_scroll.set)
        tree_scroll.grid(row=0, column=1, sticky="NS")

        # Buttons for adding/removing items
        button_frame = ttk.Frame(main_frame)
        button_frame.grid(row=1, column=0, sticky="EW", padx=5, pady=5)
        button_frame.columnconfigure(0, weight=1)
        button_frame.columnconfigure(1, weight=1)
        button_frame.columnconfigure(2, weight=1)
        button_frame.columnconfigure(3, weight=1)

        add_folder_btn = ttk.Button(button_frame, text="Add Folders", command=self.select_input_folders)
        add_folder_btn.grid(row=0, column=0, padx=5, pady=5, sticky="EW")

        add_video_btn = ttk.Button(button_frame, text="Add Videos", command=self.select_input_videos)
        add_video_btn.grid(row=0, column=1, padx=5, pady=5, sticky="EW")

        remove_selected_btn = ttk.Button(button_frame, text="Remove Selected", command=self.remove_selected_items)
        remove_selected_btn.grid(row=0, column=2, padx=5, pady=5, sticky="EW")

        clear_all_btn = ttk.Button(button_frame, text="Clear All", command=self.clear_all_items)
        clear_all_btn.grid(row=0, column=3, padx=5, pady=5, sticky="EW")

        # Output Folder Section
        output_frame = ttk.LabelFrame(main_frame, text="Output Folder", style="TLabelFrame")
        output_frame.grid(row=2, column=0, sticky="EW", padx=5, pady=5)
        output_frame.columnconfigure(0, weight=1)
        output_frame.columnconfigure(1, weight=0)

        output_entry = ttk.Entry(output_frame, textvariable=self.output_folder, state='readonly', style="Readonly.TEntry")
        output_entry.grid(row=0, column=0, sticky="EW", padx=(5, 5), pady=5)

        select_output_btn = ttk.Button(output_frame, text="Select Output Folder", command=self.select_output_folder)
        select_output_btn.grid(row=0, column=1, sticky="E", padx=(5, 5), pady=5)

        # Format Selection Section
        format_frame = ttk.LabelFrame(main_frame, text="Select Format", style="TLabelFrame")
        format_frame.grid(row=3, column=0, sticky="W", padx=5, pady=5)

        format_label = ttk.Label(format_frame, text="Image Format:")
        format_label.grid(row=0, column=0, padx=5, pady=5, sticky="W")

        format_dropdown = ttk.Combobox(format_frame, textvariable=self.selected_format, values=["JPG", "PNG"], state="readonly", width=10)
        format_dropdown.grid(row=0, column=1, padx=5, pady=5, sticky="W")
        format_dropdown.current(0)  # Default to JPG

        # Progress Bar Section
        progress_frame = ttk.Frame(main_frame)
        progress_frame.grid(row=4, column=0, sticky="EW", padx=5, pady=5)
        progress_frame.columnconfigure(0, weight=1)

        progress_label = ttk.Label(progress_frame, text="Processing Progress:", foreground=self.TEXT_COLOR, background=self.BACKGROUND_COLOR)
        progress_label.grid(row=0, column=0, sticky="W", padx=5, pady=(0,5))

        self.progress_bar = ttk.Progressbar(progress_frame, orient='horizontal', mode='determinate', style="Horizontal.TProgressbar")
        self.progress_bar.grid(row=1, column=0, sticky="EW", padx=5, pady=5)

        # Status Label
        self.status_label = ttk.Label(main_frame, text="Ready.", anchor='w', relief='sunken', foreground=self.TEXT_COLOR, background="#1f2833", font=("Segoe UI", 10))
        self.status_label.grid(row=5, column=0, sticky="EW", padx=5, pady=(0,5))

        # Process Button
        process_button = ttk.Button(main_frame, text="Process", command=self.process_videos_thread)
        process_button.grid(row=6, column=0, pady=10, sticky="E", padx=5, ipadx=10, ipady=5)

    def select_input_folders(self):
        folders = filedialog.askdirectory(title="Select Input Folders", mustexist=True, multi=True)
        if folders:
            for folder in folders:
                if not any(item[0] == folder for item in self.input_items):
                    self.input_items.append((folder, "Folder"))
                    self.tree.insert("", "end", values=("Folder", folder))
                else:
                    messagebox.showwarning("Duplicate Folder", f"The folder '{folder}' is already selected.")

    def select_input_videos(self):
        videos = filedialog.askopenfilenames(title="Select Video Files",
                                            filetypes=[("Video Files", "*.mp4 *.avi *.mov *.mkv *.flv *.wmv")])
        if videos:
            for video in videos:
                if not any(item[0] == video for item in self.input_items):
                    self.input_items.append((video, "Video"))
                    self.tree.insert("", "end", values=("Video", video))
                else:
                    messagebox.showwarning("Duplicate Video", f"The video '{video}' is already selected.")

    def remove_selected_items(self):
        selected = self.tree.selection()
        for sel in selected:
            item = self.tree.item(sel)
            path = item['values'][1]
            self.input_items = [i for i in self.input_items if i[0] != path]
            self.tree.delete(sel)
        self.update_status("Selected items removed.")

    def clear_all_items(self):
        if messagebox.askyesno("Confirm", "Are you sure you want to clear all input items?"):
            self.input_items.clear()
            for item in self.tree.get_children():
                self.tree.delete(item)
            self.update_status("All items cleared.")

    def select_output_folder(self):
        folder = filedialog.askdirectory(title="Select Output Folder")
        if folder:
            self.output_folder.set(folder)
            self.update_status(f"Output folder set to: {folder}")

    def sanitize_name(self, name):
        """Sanitize the folder or file name to remove or replace problematic characters."""
        return "".join(c if c.isalnum() or c in (' ', '_', '-') else "_" for c in name).replace(" ", "_")

    def process_videos_thread(self):
        """Run the video processing in a separate thread."""
        threading.Thread(target=self.process_videos, daemon=True).start()

    def process_videos(self):
        if not self.input_items:
            messagebox.showerror("Error", "Please select at least one input folder or video.")
            return
        if not self.output_folder.get():
            messagebox.showerror("Error", "Please select an output folder.")
            return

        selected_format = self.selected_format.get().lower()
        if selected_format not in ['jpg', 'png']:
            messagebox.showerror("Error", "Please select a valid format (JPG or PNG).")
            return

        # Ensure the output directory exists
        os.makedirs(self.output_folder.get(), exist_ok=True)

        # Disable buttons to prevent multiple clicks
        self.set_buttons_state(tk.DISABLED)

        # Initialize progress
        total_tasks = len(self.input_items)
        if total_tasks == 0:
            messagebox.showerror("Error", "No tasks to process.")
            self.set_buttons_state(tk.NORMAL)
            return

        self.progress_bar.config(maximum=total_tasks)
        self.progress_bar['value'] = 0
        self.update_status("Starting processing...")

        progress_queue = queue.Queue()
        errors = []

        def worker(item_path, item_type):
            base_name = os.path.basename(os.path.normpath(item_path))
            sanitized_base = self.sanitize_name(base_name)
            unique_output_dir = os.path.join(self.output_folder.get(), f"{sanitized_base}_{base_name}")
            os.makedirs(unique_output_dir, exist_ok=True)
            try:
                if item_type == "Folder":
                    cmd = ['video2image', '-i', item_path, '-o', unique_output_dir, '-f', selected_format]
                else:
                    cmd = ['video2image', '-i', item_path, '-o', unique_output_dir, '-f', selected_format]
                subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                progress_queue.put(("success", f"{item_type}: {item_path}"))
            except subprocess.CalledProcessError as e:
                error_msg = f"Failed to process {item_type.lower()}: {item_path}\nError: {e.stderr.decode().strip()}"
                progress_queue.put(("error", error_msg))
            except Exception as ex:
                error_msg = f"An unexpected error occurred while processing {item_type.lower()}: {item_path}\nError: {ex}"
                progress_queue.put(("error", error_msg))

        # Start thread pool
        executor = concurrent.futures.ThreadPoolExecutor(max_workers=4)
        for path, type_ in self.input_items:
            executor.submit(worker, path, type_)

        self.executor = executor  # Keep a reference

        # Start checking the queue
        self.process_queue(progress_queue, total_tasks, errors)

    def process_queue(self, progress_queue, total_tasks, errors, processed=0):
        try:
            while True:
                status, info = progress_queue.get_nowait()
                if status == "success":
                    self.progress_bar['value'] += 1
                    processed += 1
                    self.update_status(f"Processed: {info}")
                elif status == "error":
                    errors.append(info)
                    self.progress_bar['value'] += 1
                    processed += 1
                    self.update_status(f"Error: {info}")

                if processed >= total_tasks:
                    self.executor.shutdown(wait=False)
                    self.finalize_processing(errors)
        except queue.Empty:
            pass
        if processed < total_tasks:
            self.root.after(100, self.process_queue, progress_queue, total_tasks, errors, processed)

    def finalize_processing(self, errors):
        self.set_buttons_state(tk.NORMAL)
        if errors:
            error_message = "\n\n".join(errors)
            messagebox.showerror("Processing Completed with Errors", error_message)
        else:
            messagebox.showinfo("Success", "All videos and folders have been successfully processed.")
        self.progress_bar['value'] = 0
        self.update_status("Processing completed.")

    def set_buttons_state(self, state):
        # Disable/Enable all relevant buttons
        for child in self.root.winfo_children():
            self.recursive_set_state(child, state)

    def recursive_set_state(self, widget, state):
        for child in widget.winfo_children():
            if isinstance(child, ttk.Button):
                child.config(state=state)
            self.recursive_set_state(child, state)

    def update_status(self, message):
        self.status_label.config(text=message)

def main():
    root = tk.Tk()
    app = VideoImageConverterApp(root)
    root.mainloop()

if __name__ == "__main__":
    main()