Add th ability to select videos also along side the ability for folders and have the videos process like they are in separate folders but they are not to be able to do the processing that the folder way does  here is code import ttkbootstrap as tb
from ttkbootstrap.constants import *
from ttkbootstrap.dialogs import Messagebox
from tkinter import filedialog
import os
import subprocess
import threading
import concurrent.futures
import queue
import psutil
import sys

# Constants for the application
APP_NAME = "DeepAscension Video2Image+"
DEFAULT_MAX_CONCURRENT_TASKS = 4
DISK_CHECK_INTERVAL = 5000  # in milliseconds

def select_input_folders():
    while True:
        folder = filedialog.askdirectory(title="Select Input Folder (Cancel to finish)")
        if folder:
            if folder not in input_folders:
                input_folders.append(folder)
                tree.insert('', 'end', iid=folder, values=(folder, "Pending"))
            else:
                Messagebox.show_warning("Duplicate Folder", f"The folder '{folder}' is already selected.")
        else:
            break

def remove_selected_folders():
    selected_items = tree.selection()
    for item in selected_items:
        input_folders.remove(item)
        tree.delete(item)
    update_remove_button()

def select_output_folder():
    folder = filedialog.askdirectory(title="Select Output Folder")
    if folder:
        output_folder_var.set(folder)
        output_entry.config(state='normal')
        output_entry.delete(0, tb.END)
        output_entry.insert(0, folder)
        output_entry.config(state='readonly')

def sanitize_folder_name(name):
    """Sanitize the folder 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():
    """Run the video processing in a separate thread."""
    threading.Thread(target=process_videos, daemon=True).start()

def process_videos():
    global is_paused
    output_folder = output_folder_var.get()

    if not input_folders:
        Messagebox.show_error("Error", "Please select at least one input folder.")
        return
    if not output_folder:
        Messagebox.show_error("Error", "Please select an output folder.")
        return

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

    # Disable the process button and folder selection buttons to prevent multiple clicks
    set_buttons_state(DISABLED)

    # Initialize overall progress bar
    progress_bar['maximum'] = len(input_folders)
    progress_bar['value'] = 0
    status_label.config(text="Starting processing...")

    # Queue to receive progress updates
    progress_queue = queue.Queue()

    # List to collect errors
    errors = []

    # Create a copy of input_folders to iterate over
    tasks_queue = queue.Queue()
    for folder in input_folders:
        tasks_queue.put(folder)

    # Determine optimal number of workers based on CPU cores
    max_workers = min(os.cpu_count() * 2 or DEFAULT_MAX_CONCURRENT_TASKS, 32)

    def worker(folder):
        if is_paused:
            # Wait until resumed
            while is_paused:
                if stop_all_event.is_set():
                    return
                threading.Event().wait(0.1)
        # Update Treeview status to "Processing"
        progress_queue.put(("status", folder, "Processing"))
        base_name = os.path.basename(os.path.normpath(folder))
        sanitized_base = sanitize_folder_name(base_name)
        unique_output_dir = os.path.join(output_folder, f"{sanitized_base}_{base_name}")
        os.makedirs(unique_output_dir, exist_ok=True)
        try:
            # Retrieve the selected output format
            selected_format = output_format_var.get()

            # Run the video2image command with the unique output directory and selected format
            subprocess.run(
                ['video2image', '-i', folder, '-o', unique_output_dir, '-f', selected_format],
                check=True,
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL
            )
            progress_queue.put(("success", folder))
        except subprocess.CalledProcessError as e:
            error_msg = f"Failed to process folder: {folder}\nError Code: {e.returncode}"
            progress_queue.put(("error", folder, error_msg))
        except Exception as ex:
            error_msg = f"An unexpected error occurred while processing folder: {folder}\nError: {ex}"
            progress_queue.put(("error", folder, error_msg))

    # Initialize ThreadPoolExecutor
    with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
        futures = {executor.submit(worker, tasks_queue.get()): tasks_queue.get() for _ in range(min(max_workers, tasks_queue.qsize()))}
        while futures:
            done, _ = concurrent.futures.wait(futures, return_when=concurrent.futures.FIRST_COMPLETED)
            for future in done:
                folder = futures.pop(future)
                try:
                    future.result()
                except Exception as e:
                    error_msg = f"Error processing folder: {folder}\nError: {e}"
                    progress_queue.put(("error", folder, error_msg))
                if not tasks_queue.empty() and not is_paused:
                    next_folder = tasks_queue.get()
                    futures[executor.submit(worker, next_folder)] = next_folder

    # Finalize processing
    finalize_processing(errors)

    # Start disk space monitoring
    monitor_disk_space()

def set_buttons_state(state):
    process_button.config(state=state)
    select_input_button.config(state=state)
    select_output_button.config(state=state)
    remove_button.config(state=NORMAL if input_folders and state == NORMAL else DISABLED)
    pause_button.config(state=state if state == NORMAL else DISABLED)
    resume_button.config(state=DISABLED if state == NORMAL else NORMAL)
    output_format_combobox.config(state=DISABLED if state == DISABLED else NORMAL)

def on_pause():
    global is_paused
    is_paused = True
    status_label.config(text="Processing paused.")
    pause_button.config(state=DISABLED)
    resume_button.config(state=NORMAL)

def on_resume():
    global is_paused
    is_paused = False
    status_label.config(text="Resuming processing...")
    pause_button.config(state=NORMAL)
    resume_button.config(state=DISABLED)
    # Resume task submission by re-invoking process_videos_thread
    process_videos_thread()

def monitor_disk_space():
    output_path = output_folder_var.get() or '/'
    try:
        total, used, free = psutil.disk_usage(output_path)
        if free < disk_space_threshold_mb * 1024 * 1024:
            if not is_paused:
                on_pause()
                Messagebox.show_warning("Auto-Pause", "Disk space below threshold. Processing has been paused.")
    except Exception as e:
        Messagebox.show_error("Disk Space Error", f"Unable to determine disk space for '{output_path}'.\nError: {e}")
    root.after(DISK_CHECK_INTERVAL, monitor_disk_space)

def set_disk_threshold():
    try:
        value = int(disk_threshold_entry.get())
        if value <= 0:
            raise ValueError
        global disk_space_threshold_mb
        disk_space_threshold_mb = value
        Messagebox.show_info("Threshold Set", f"Auto-pause threshold set to {value} MB.")
    except ValueError:
        Messagebox.show_error("Invalid Input", "Please enter a valid positive integer for the disk space threshold.")

def update_remove_button():
    """Enable or disable the remove button based on folder selection."""
    if input_folders:
        remove_button.config(state=NORMAL)
    else:
        remove_button.config(state=DISABLED)

def finalize_processing(errors):
    set_buttons_state(NORMAL)
    if errors:
        error_message = "\n\n".join(errors)
        Messagebox.show_error("Processing Completed with Errors", error_message)
    else:
        Messagebox.show_success("Success", "All videos have been successfully processed.")
    progress_bar['value'] = 0
    status_label.config(text="Processing completed.")

def update_progress():
    try:
        while True:
            msg = progress_queue.get_nowait()
            if msg[0] == "status":
                _, folder, status = msg
                tree.set(folder, "Status", status)
            elif msg[0] == "success":
                _, folder = msg
                tree.set(folder, "Status", "Completed")
                progress_bar['value'] += 1
            elif msg[0] == "error":
                _, folder, error_msg = msg
                tree.set(folder, "Status", "Error")
                errors.append(error_msg)
                progress_bar['value'] += 1

            # Update overall progress
            overall = progress_bar['value']
            status_label.config(text=f"Processed {overall} of {len(input_folders)} folders.")

            if overall >= len(input_folders):
                finalize_processing(errors)
    except queue.Empty:
        pass
    if progress_bar['value'] < len(input_folders):
        root.after(100, update_progress)

# Initialize the main window with ttkbootstrap
root = tb.Window(themename="darkly")
root.title(APP_NAME)
root.geometry("1100x800")
root.resizable(True, True)  # Allow resizing for better usability

# Initialize the list to store input folders
input_folders = []

# Variable to store output folder path
output_folder_var = tb.StringVar()

# Variable for disk space threshold
disk_space_threshold_mb = 500  # Default to 500 MB

# Variable for output format
output_format_var = tb.StringVar(value="jpg")  # Default to 'jpg'

# Pause state
is_paused = False

# Event to signal stopping of all tasks
stop_all_event = threading.Event()

# Configure styles using ttkbootstrap
style = tb.Style()

# Frame for Input Folders
input_frame = tb.LabelFrame(root, text="Input Folders")
input_frame.pack(fill="both", expand=True, padx=20, pady=10)

# Treeview to display selected input folders and their statuses
columns = ("Folder", "Status")
tree = tb.Treeview(input_frame, columns=columns, show='headings', selectmode='extended', height=15)
tree.heading("Folder", text="Folder")
tree.heading("Status", text="Status")
tree.column("Folder", anchor='w', width=800)
tree.column("Status", anchor='center', width=200)
tree.pack(side=tb.LEFT, fill=tb.BOTH, expand=True, padx=(0,5), pady=5)

# Scrollbar for the Treeview
tree_scroll = tb.Scrollbar(input_frame, orient=tb.VERTICAL, command=tree.yview)
tree_scroll.pack(side=tb.RIGHT, fill=tb.Y)
tree.config(yscrollcommand=tree_scroll.set)

# Frame for Input Buttons
input_button_frame = tb.Frame(root)
input_button_frame.pack(fill='x', padx=20, pady=(0,10))

# Button to select input folders
select_input_button = tb.Button(input_button_frame, text="Select Input Folders", bootstyle=PRIMARY, command=select_input_folders)
select_input_button.pack(side=tb.LEFT, padx=5)

# Button to remove selected input folders
remove_button = tb.Button(input_button_frame, text="Remove Selected", bootstyle=DANGER, command=remove_selected_folders, state=DISABLED)
remove_button.pack(side=tb.LEFT, padx=5)

# Frame for Output Folder
output_frame = tb.LabelFrame(root, text="Output Folder")
output_frame.pack(fill="both", expand=True, padx=20, pady=10)

# Entry widget to display selected output folder
output_entry = tb.Entry(output_frame, textvariable=output_folder_var, width=80, state='readonly')
output_entry.pack(side=tb.LEFT, padx=(0, 5), expand=True, fill=tb.X)

# Button to select output folder
select_output_button = tb.Button(output_frame, text="Select Output Folder", bootstyle=PRIMARY, command=select_output_folder)
select_output_button.pack(side=tb.LEFT, padx=5)

# Frame for Output Format Selection
format_frame = tb.LabelFrame(root, text="Output Format")
format_frame.pack(fill="x", expand=True, padx=20, pady=10)

format_label = tb.Label(format_frame, text="Select Output Format:")
format_label.pack(side=tb.LEFT, padx=(0,5))

output_format_combobox = tb.Combobox(format_frame, textvariable=output_format_var, values=["jpg", "png"], state="readonly")
output_format_combobox.pack(side=tb.LEFT, padx=5)
output_format_combobox.current(0)  # Set default to 'jpg'

# Frame for Disk Space Threshold
disk_frame = tb.LabelFrame(root, text="Auto-Pause Disk Space Threshold (MB)")
disk_frame.pack(fill="x", expand=True, padx=20, pady=10)

disk_threshold_entry = tb.Entry(disk_frame, width=10, justify='center')
disk_threshold_entry.insert(0, str(disk_space_threshold_mb))
disk_threshold_entry.pack(side=tb.LEFT, padx=(0,5))

set_threshold_button = tb.Button(disk_frame, text="Set Threshold", bootstyle=SUCCESS, command=set_disk_threshold)
set_threshold_button.pack(side=tb.LEFT, padx=5)

# Progress Frame with Overall Progress Bar
progress_frame = tb.Frame(root)
progress_frame.pack(fill='x', padx=20, pady=10)

progress_label = tb.Label(progress_frame, text="Overall Progress:")
progress_label.pack(anchor='w')

progress_bar = tb.Progressbar(progress_frame, orient='horizontal', length=1000, mode='determinate', bootstyle="info")
progress_bar.pack(pady=5, fill=tb.X)

# Status Label
status_label = tb.Label(root, text="Ready.", relief=RAISED, anchor='w', bootstyle="secondary")
status_label.pack(fill='x', padx=20, pady=(0,10))

# Frame for Control Buttons
control_button_frame = tb.Frame(root)
control_button_frame.pack(fill='x', padx=20, pady=10)

# Process Button
process_button = tb.Button(control_button_frame, text="Process Videos", bootstyle=SUCCESS, command=process_videos_thread)
process_button.pack(side=tb.LEFT, padx=5, ipadx=10, ipady=5)

# Pause Button
pause_button = tb.Button(control_button_frame, text="Pause", bootstyle=WARNING, command=on_pause, state=DISABLED)
pause_button.pack(side=tb.LEFT, padx=5, ipadx=10, ipady=5)

# Resume Button
resume_button = tb.Button(control_button_frame, text="Resume", bootstyle=INFO, command=on_resume, state=DISABLED)
resume_button.pack(side=tb.LEFT, padx=5, ipadx=10, ipady=5)

# Queue for inter-thread communication
progress_queue = queue.Queue()

# List to collect errors
errors = []

# Monitor changes in the input_folders list to update the remove button state
def monitor_input_folders():
    """Periodically check if input_folders is empty to enable/disable the remove button."""
    update_remove_button()
    root.after(500, monitor_input_folders)

monitor_input_folders()

# Periodically update the progress in the GUI
def periodic_update():
    update_progress()
    root.after(100, periodic_update)

periodic_update()

# Handle window closing to ensure all threads are properly terminated
def on_closing():
    stop_all_event.set()
    root.destroy()

root.protocol("WM_DELETE_WINDOW", on_closing)

# Run the GUI loop
root.mainloop()