behebe alle fehler in diesem skript und verbesser das gui und optimiere es auch. Gestalte ein besseres, übersichtliches und nutzerfreundliches layout und füge überall wo es möglich ist tooltips und dragndrop funktionen hinzu. sorge dafür das alle funktionen stabil und effektiv funktionieren, insbesondere die input funktionen. nenne mir dann den gesamten code der anwendung in dem vollständigen skript.

import sys
import os
import cv2
import numpy as np
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtWidgets import (
    QLabel, QTabWidget, QTextEdit, QLineEdit, QPushButton,
    QListWidget, QVBoxLayout, QFileDialog, QHBoxLayout, QGroupBox,
    QFormLayout, QSlider, QCheckBox, QProgressBar, QMainWindow, QApplication
)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QMutex, QMutexLocker
from skimage.metrics import structural_similarity as ssi
from PIL import Image
from functools import partial

# Dark mode stylesheet
DARK_STYLE = """
QWidget {
    background-color: #2b2b2b;
    color: #f0f0f0;
    font-family: 'Segoe UI', sans-serif;
    font-size: 10pt;
}

QPushButton {
    background-color: #3c3c3c;
    border: 1px solid #5a5a5a;
    padding: 5px;
    border-radius: 5px;
}

QPushButton:hover {
    background-color: #505050;
}

QPushButton:pressed {
    background-color: #383838;
}

QLineEdit, QTextEdit, QListWidget, QLabel, QSlider, QGroupBox {
    background-color: #3c3c3c;
    border: 1px solid #5a5a5a;
    padding: 5px;
    border-radius: 5px;
    color: #f0f0f0;
}

QSlider::groove:horizontal {
    border: 1px solid #5a5a5a;
    height: 8px;
    background: #4a4a4a;
    border-radius: 4px;
}

QSlider::handle:horizontal {
    background: #8f8f91;
    border: 1px solid #5a5a5a;
    width: 18px;
    margin: -5px 0;
    border-radius: 9px;
}

QProgressBar {
    background-color: #3c3c3c;
    border: 1px solid #5a5a5a;
    border-radius: 5px;
    text-align: center;
}

QProgressBar::chunk {
    background-color: #5a5a5a;
    width: 20px;
    margin: 0.5px;
}

QTabWidget::pane { /* The tab widget frame */
    border-top: 2px solid #444;
}

QTabBar::tab {
    background: #3c3c3c;
    border: 1px solid #444;
    padding: 5px;
    border-bottom: none;
    border-top-left-radius: 4px;
    border-top-right-radius: 4px;
    margin-right: 2px;
}

QTabBar::tab:selected, QTabBar::tab:hover {
    background: #5a5a5a;
}
"""

class FrameExtractor(QtCore.QObject):
    progress = pyqtSignal(int)
    log = pyqtSignal(str)
    finished = pyqtSignal(list)

    def __init__(self, video_path, output_dir, sharpness_threshold, overlap_threshold,
                 brightness_adjustment, shadow_removal_enabled, contrast_adjustment,
                 saturation_adjustment):
        super().__init__()
        self.video_path = video_path
        self.output_dir = output_dir
        self.sharpness_threshold = sharpness_threshold
        self.overlap_threshold = overlap_threshold
        self.brightness_adjustment = brightness_adjustment
        self.shadow_removal_enabled = shadow_removal_enabled
        self.contrast_adjustment = contrast_adjustment
        self.saturation_adjustment = saturation_adjustment

    def log_message(self, message):
        self.log.emit(message)

    def measure_sharpness(self, frame):
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        lap = cv2.Laplacian(gray, cv2.CV_64F)
        return lap.var()

    def frames_overlap(self, frame1, frame2):
        # Faster Histogram comparison
        hist1 = cv2.calcHist([frame1], [0, 1, 2], None, [8,8,8], [0,256,0,256,0,256])
        hist2 = cv2.calcHist([frame2], [0, 1, 2], None, [8,8,8], [0,256,0,256,0,256])
        cv2.normalize(hist1, hist1)
        cv2.normalize(hist2, hist2)
        similarity = cv2.compareHist(hist1, hist2, cv2.HISTCMP_CORREL)
        return similarity  # Value between -1 and 1

    def adjust_brightness(self, frame):
        hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
        h, s, v = cv2.split(hsv)
        v = np.clip(v + self.brightness_adjustment, 0, 255).astype(np.uint8)
        final_hsv = cv2.merge((h, s, v))
        return cv2.cvtColor(final_hsv, cv2.COLOR_HSV2BGR)

    def adjust_contrast(self, frame):
        # Contrast adjustment scaling factor
        alpha = 1 + self.contrast_adjustment / 100.0  # scale factor
        return cv2.convertScaleAbs(frame, alpha=alpha, beta=0)

    def adjust_saturation(self, frame):
        hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
        h, s, v = cv2.split(hsv)
        s = np.clip(s + self.saturation_adjustment, 0, 255).astype(np.uint8)
        final_hsv = cv2.merge((h, s, v))
        return cv2.cvtColor(final_hsv, cv2.COLOR_HSV2BGR)

    def shadow_removal(self, frame):
        if not self.shadow_removal_enabled:
            return frame

        lab = cv2.cvtColor(frame, cv2.COLOR_BGR2LAB)
        l_channel, a_channel, b_channel = cv2.split(lab)
        clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
        cl = clahe.apply(l_channel)
        limg = cv2.merge((cl, a_channel, b_channel))
        return cv2.cvtColor(limg, cv2.COLOR_LAB2BGR)

    def sharpen_image(self, frame):
        kernel = np.array([[0, -1, 0],
                           [-1, 5, -1],
                           [0, -1, 0]])
        return cv2.filter2D(frame, -1, kernel)

    def run(self):
        try:
            cap = cv2.VideoCapture(self.video_path)
            if not cap.isOpened():
                self.log_message("Failed to open video file.")
                self.finished.emit([])
                return

            total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
            selected_frames = []
            successful_frame_count = 0

            basename = os.path.splitext(os.path.basename(self.video_path))[0]

            previous_frame = None

            for i in range(total_frames):
                ret, frame = cap.read()
                if not ret:
                    break

                sharpness = self.measure_sharpness(frame)
                if sharpness < self.sharpness_threshold:
                    #self.log_message(f"Frame {i}: Sharpness {sharpness:.2f} below threshold.")
                    pass
                else:
                    if previous_frame is not None:
                        overlap = self.frames_overlap(previous_frame, frame)
                        if overlap < self.overlap_threshold:
                            #self.log_message(f"Frame {i}: Overlap {overlap:.2f} below threshold.")
                            pass
                        else:
                            # Apply image processing
                            processed_frame = self.adjust_brightness(frame)
                            processed_frame = self.adjust_contrast(processed_frame)
                            processed_frame = self.adjust_saturation(processed_frame)
                            processed_frame = self.shadow_removal(processed_frame)
                            processed_frame = self.sharpen_image(processed_frame)

                            frame_name = f"{basename}_frame_{successful_frame_count:05d}.png"
                            frame_path = os.path.join(self.output_dir, frame_name)
                            cv2.imwrite(frame_path, processed_frame)
                            selected_frames.append(frame_path)
                            successful_frame_count += 1
                            previous_frame = processed_frame.copy()

                            if successful_frame_count % 10 == 0 or successful_frame_count == 1:
                                self.log_message(f"Frame {i}: Successfully extracted {successful_frame_count} frames.")

                    else:
                        # Apply image processing
                        processed_frame = self.adjust_brightness(frame)
                        processed_frame = self.adjust_contrast(processed_frame)
                        processed_frame = self.adjust_saturation(processed_frame)
                        processed_frame = self.shadow_removal(processed_frame)
                        processed_frame = self.sharpen_image(processed_frame)

                        frame_name = f"{basename}_frame_{successful_frame_count:05d}.png"
                        frame_path = os.path.join(self.output_dir, frame_name)
                        cv2.imwrite(frame_path, processed_frame)
                        selected_frames.append(frame_path)
                        successful_frame_count += 1
                        previous_frame = processed_frame.copy()

                        if successful_frame_count % 10 == 0 or successful_frame_count == 1:
                            self.log_message(f"Frame {i}: Successfully extracted {successful_frame_count} frames.")

                progress_percent = int((i + 1) / total_frames * 100)
                if i % 10 == 0 or i == total_frames -1:
                    self.progress.emit(progress_percent)

            cap.release()
            self.finished.emit(selected_frames)
        except Exception as e:
            self.log_message(f"Error during extraction: {str(e)}")
            self.finished.emit([])


class PreviewLabel(QLabel):
    def __init__(self):
        super().__init__()
        self.original_pixmap = None
        self.setFixedSize(300, 200)
        self.setAlignment(Qt.AlignCenter)
        self.setStyleSheet("background-color: #5a5a5a; border: 1px solid #444; border-radius: 5px;")

    def setPixmap(self, pixmap):
        if pixmap != self.original_pixmap:
            self.original_pixmap = pixmap
        scaled_pixmap = pixmap.scaled(
            self.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation
        )
        super().setPixmap(scaled_pixmap)

    def enterEvent(self, event):
        if self.original_pixmap:
            zoomed_pixmap = self.original_pixmap.scaled(
                int(1.2 * self.width()),
                int(1.2 * self.height()),
                Qt.KeepAspectRatio,
                Qt.SmoothTransformation
            )
            super().setPixmap(zoomed_pixmap)

    def leaveEvent(self, event):
        if self.original_pixmap:
            super().setPixmap(
                self.original_pixmap.scaled(
                    self.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation
                )
            )


class FrameExtractorThread(QThread):
    def __init__(self, extractor):
        super().__init__()
        self.extractor = extractor

    def run(self):
        self.extractor.run()


class FrameExtractorUI(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Video Frame Extractor")
        self.setup_ui()

    def setup_ui(self):
        layout = QVBoxLayout(self)

        # Video Selection
        video_layout = QHBoxLayout()
        self.video_path_edit = QLineEdit()
        browse_button = QPushButton("Browse")
        browse_button.setCursor(Qt.PointingHandCursor)
        browse_button.clicked.connect(self.browse_video)
        video_layout.addWidget(QLabel("Video File:"))
        video_layout.addWidget(self.video_path_edit)
        video_layout.addWidget(browse_button)
        layout.addLayout(video_layout)

        # Output Directory Selection
        output_layout = QHBoxLayout()
        self.output_path_edit = QLineEdit()
        browse_output_button = QPushButton("Browse")
        browse_output_button.setCursor(Qt.PointingHandCursor)
        browse_output_button.clicked.connect(self.browse_output)
        output_layout.addWidget(QLabel("Output Folder:"))
        output_layout.addWidget(self.output_path_edit)
        output_layout.addWidget(browse_output_button)
        layout.addLayout(output_layout)

        # Settings Group
        settings_group = QGroupBox("Settings")
        settings_layout = QFormLayout()

        # Sharpness Threshold
        self.sharpness_slider = QSlider(Qt.Horizontal)
        self.sharpness_slider.setMinimum(100)
        self.sharpness_slider.setMaximum(1000)
        self.sharpness_slider.setValue(300)
        self.sharpness_value = QLabel("300")
        self.sharpness_slider.valueChanged.connect(
            lambda val: self.sharpness_value.setText(str(val))
        )

        # Overlap Threshold (using correlation, so set between 0 and 1 with scaling)
        self.overlap_slider = QSlider(Qt.Horizontal)
        self.overlap_slider.setMinimum(0)
        self.overlap_slider.setMaximum(100)
        self.overlap_slider.setValue(50)
        self.overlap_value = QLabel("0.50")
        self.overlap_slider.valueChanged.connect(
            lambda val: self.overlap_value.setText(f"{val / 100:.2f}")
        )

        # Brightness Adjustment
        self.brightness_slider = QSlider(Qt.Horizontal)
        self.brightness_slider.setMinimum(-100)
        self.brightness_slider.setMaximum(100)
        self.brightness_slider.setValue(0)
        self.brightness_value = QLabel("0")
        self.brightness_slider.valueChanged.connect(
            lambda val: self.brightness_value.setText(str(val))
        )

        # Contrast Adjustment
        self.contrast_slider = QSlider(Qt.Horizontal)
        self.contrast_slider.setMinimum(-100)
        self.contrast_slider.setMaximum(100)
        self.contrast_slider.setValue(0)
        self.contrast_value = QLabel("0")
        self.contrast_slider.valueChanged.connect(
            lambda val: self.contrast_value.setText(str(val))
        )

        # Saturation Adjustment
        self.saturation_slider = QSlider(Qt.Horizontal)
        self.saturation_slider.setMinimum(-100)
        self.saturation_slider.setMaximum(100)
        self.saturation_slider.setValue(0)
        self.saturation_value = QLabel("0")
        self.saturation_slider.valueChanged.connect(
            lambda val: self.saturation_value.setText(str(val))
        )

        # Shadow Removal
        self.shadow_removal_checkbox = QCheckBox("Enable Shadow Removal")
        self.shadow_removal_checkbox.setChecked(True)

        # Add widgets to settings_layout
        settings_layout.addRow("Sharpness Threshold:", self.sharpness_slider)
        settings_layout.addRow("", self.sharpness_value)
        settings_layout.addRow("Overlap Threshold:", self.overlap_slider)
        settings_layout.addRow("", self.overlap_value)
        settings_layout.addRow("Brightness Adjustment:", self.brightness_slider)
        settings_layout.addRow("", self.brightness_value)
        settings_layout.addRow("Contrast Adjustment:", self.contrast_slider)
        settings_layout.addRow("", self.contrast_value)
        settings_layout.addRow("Saturation Adjustment:", self.saturation_slider)
        settings_layout.addRow("", self.saturation_value)
        settings_layout.addRow(self.shadow_removal_checkbox)

        settings_group.setLayout(settings_layout)
        layout.addWidget(settings_group)

        # Start Button
        self.start_button = QPushButton("Start Extraction")
        self.start_button.setCursor(Qt.PointingHandCursor)
        self.start_button.clicked.connect(self.start_extraction)
        layout.addWidget(self.start_button)

        # Progress Bar and Label
        self.progress_bar = QProgressBar()
        self.progress_label = QLabel("Processing: 0%")
        layout.addWidget(self.progress_label)
        layout.addWidget(self.progress_bar)

        # Log Text
        self.log_text = QTextEdit()
        self.log_text.setReadOnly(True)
        layout.addWidget(QLabel("Log:"))
        layout.addWidget(self.log_text)

        # Selected Frames List
        self.selected_frames_list = QListWidget()
        self.selected_frames_list.itemClicked.connect(self.preview_frame)
        layout.addWidget(QLabel("Selected Frames:"))
        layout.addWidget(self.selected_frames_list)

        # Remove Frame Button
        remove_button = QPushButton("Remove Selected Frame")
        remove_button.setCursor(Qt.PointingHandCursor)
        remove_button.clicked.connect(self.remove_selected_frame)
        layout.addWidget(remove_button)

        # Preview Section
        self.preview_label = QLabel("Preview:")
        layout.addWidget(self.preview_label)
        self.preview_image = PreviewLabel()
        layout.addWidget(self.preview_image)

    def browse_video(self):
        file_dialog = QFileDialog()
        path, _ = file_dialog.getOpenFileName(
            self, "Select Video File", "", "Videos (*.mp4 *.avi *.mov *.mkv)"
        )
        if path:
            self.video_path_edit.setText(path)

    def browse_output(self):
        dir_dialog = QFileDialog()
        path = dir_dialog.getExistingDirectory(self, "Select Output Folder")
        if path:
            self.output_path_edit.setText(path)

    def start_extraction(self):
        video_path = self.video_path_edit.text()
        output_dir = self.output_path_edit.text()
        sharpness_threshold = self.sharpness_slider.value()
        overlap_threshold = self.overlap_slider.value() / 100.0  # Scale to 0-1
        brightness_adjustment = self.brightness_slider.value()
        contrast_adjustment = self.contrast_slider.value()
        saturation_adjustment = self.saturation_slider.value()
        shadow_removal_enabled = self.shadow_removal_checkbox.isChecked()

        if not os.path.isfile(video_path):
            QtWidgets.QMessageBox.critical(self, "Error", "Bitte wählen Sie eine gültige Videodatei aus.")
            return
        if not os.path.isdir(output_dir):
            QtWidgets.QMessageBox.critical(self, "Error", "Bitte wählen Sie einen gültigen Ausgabefolder aus.")
            return

        self.start_button.setEnabled(False)
        self.log_text.clear()
        self.progress_bar.setValue(0)
        self.progress_label.setText("Processing: 0%")
        self.selected_frames_list.clear()
        self.preview_image.clear()

        self.extractor = FrameExtractor(
            video_path, output_dir, sharpness_threshold, overlap_threshold,
            brightness_adjustment, shadow_removal_enabled, contrast_adjustment,
            saturation_adjustment
        )

        self.thread = FrameExtractorThread(self.extractor)
        self.extractor.moveToThread(self.thread)

        self.thread.started.connect(self.extractor.run)
        self.extractor.progress.connect(self.update_progress)
        self.extractor.log.connect(self.update_log)
        self.extractor.finished.connect(self.extraction_finished)
        self.extractor.finished.connect(self.thread.quit)
        self.extractor.finished.connect(self.extractor.deleteLater)
        self.thread.finished.connect(self.thread.deleteLater)

        self.thread.start()

    def update_progress(self, value):
        self.progress_bar.setValue(value)
        self.progress_label.setText(f"Processing: {value}%")

    def update_log(self, message):
        self.log_text.append(message)

    def extraction_finished(self, frames):
        total_extracted = len(frames)
        self.log_text.append(f"Extraction abgeschlossen. {total_extracted} Frames extrahiert.")
        self.start_button.setEnabled(True)
        self.selected_frames_list.addItems(frames)

    def remove_selected_frame(self):
        selected_items = self.selected_frames_list.selectedItems()
        if not selected_items:
            return
        for item in selected_items:
            self.selected_frames_list.takeItem(self.selected_frames_list.row(item))
        self.preview_image.clear()

    def preview_frame(self, item):
        frame_path = item.text()
        if not os.path.isfile(frame_path):
            self.log_text.append(f"Vorschau nicht verfügbar: {frame_path} existiert nicht.")
            return
        image = QImage(frame_path)
        if image.isNull():
            self.log_text.append(f"Kann Bild nicht laden: {frame_path}")
            return
        pixmap = QPixmap.fromImage(image)
        self.preview_image.setPixmap(pixmap)


class ImageQualityChecker(QtCore.QObject):
    log = pyqtSignal(str)
    progress = pyqtSignal(int)
    finished = pyqtSignal(list)

    def __init__(self):
        super().__init__()
        self.image_files = []  # List to hold image file paths
        self.result_files = []  # List for selected files based on quality
        self.min_quality = 0  # Minimum quality threshold
        self.mutex = QMutex()

    def load_images(self, files):
        with QMutexLocker(self.mutex):
            self.image_files = list(files)
        return self.image_files

    def compute_quality(self, image_path, reference_gray):
        try:
            image = Image.open(image_path).convert('RGB')
            # Calculate brightness
            brightness = self.compute_brightness(image)
            # Calculate sharpness using OpenCV
            cv_image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
            gray = cv2.cvtColor(cv_image, cv2.COLOR_BGR2GRAY)
            lap_var = cv2.Laplacian(gray, cv2.CV_64F).var()
            # Normalize sharpness to 0-100
            sharpness = min(100, int(lap_var / 100.0))
            # Structural Similarity Index
            ssim_score = 100
            if reference_gray is not None:
                try:
                    ssim_index = ssim(reference_gray, gray)
                    ssim_score = int(ssim_index * 100)
                except:
                    ssim_score = 0
            # Combine metrics
            quality = min(100, (brightness + sharpness + ssim_score) // 3)
            return quality
        except Exception as e:
            self.log.emit(f"Error processing {os.path.basename(image_path)}: {str(e)}")
            return 0

    def compute_brightness(self, image: Image.Image) -> int:
        grayscale_image = image.convert('L')
        histogram = grayscale_image.histogram()
        total_pixels = sum(histogram)
        brightness = sum(i * hist for i, hist in enumerate(histogram)) / total_pixels
        return int((brightness / 255) * 100)

    def evaluate_quality(self, min_quality):
        self.result_files.clear()
        with QMutexLocker(self.mutex):
            images = list(self.image_files)

        if not images:
            self.log.emit("Keine Bilder zum Bewerten geladen.")
            self.finished.emit([])
            return

        reference_gray = None
        if images:
            try:
                reference = cv2.imread(images[0], cv2.IMREAD_GRAYSCALE)
                if reference is not None:
                    reference_gray = reference
            except:
                reference_gray = None

        total = len(images)
        for idx, file in enumerate(images):
            quality = self.compute_quality(file, reference_gray)
            if quality >= min_quality:
                self.result_files.append(file)
                self.log.emit(f"{os.path.basename(file)} - Qualität: {quality}")
            progress_percent = int((idx + 1) / total * 100)
            if idx % 10 == 0 or idx == total -1:
                self.progress.emit(progress_percent)

        self.finished.emit(self.result_files)

    def get_results(self):
        return self.result_files


class ImageQualityCheckerThread(QThread):
    def __init__(self, checker, min_quality):
        super().__init__()
        self.checker = checker
        self.min_quality = min_quality

    def run(self):
        self.checker.evaluate_quality(self.min_quality)


class ImageQualityCheckerUI(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Image Quality Checker")
        self.setup_ui()
        self.image_quality_checker = ImageQualityChecker()
        self.setup_signals()

    def setup_ui(self):
        layout = QVBoxLayout(self)

        # Load Images Button
        self.load_button = QPushButton("Load Images")
        self.load_button.setCursor(Qt.PointingHandCursor)
        self.load_button.clicked.connect(self.load_images)
        layout.addWidget(self.load_button)

        # Min Quality Input
        min_quality_layout = QHBoxLayout()
        self.min_quality_label = QLabel("Min Quality (0-100):")
        self.min_quality_entry = QLineEdit()
        self.min_quality_entry.setPlaceholderText("Enter minimum quality")
        min_quality_layout.addWidget(self.min_quality_label)
        min_quality_layout.addWidget(self.min_quality_entry)
        layout.addLayout(min_quality_layout)

        # Evaluate Button
        self.evaluate_button = QPushButton("Evaluate Quality")
        self.evaluate_button.setCursor(Qt.PointingHandCursor)
        self.evaluate_button.clicked.connect(self.evaluate_quality)
        layout.addWidget(self.evaluate_button)

        # Progress Bar and Label
        self.progress_bar = QProgressBar()
        self.progress_label = QLabel("Progress: 0%")
        layout.addWidget(self.progress_label)
        layout.addWidget(self.progress_bar)

        # Log Text
        self.result_text = QTextEdit()
        self.result_text.setReadOnly(True)
        layout.addWidget(QLabel("Results:"))
        layout.addWidget(self.result_text)

        # Selected Results List
        self.selected_results_list = QListWidget()
        layout.addWidget(QLabel("Selected High-Quality Images:"))
        layout.addWidget(self.selected_results_list)

        # Remove Frame Button
        remove_button = QPushButton("Remove Selected Image")
        remove_button.setCursor(Qt.PointingHandCursor)
        remove_button.clicked.connect(self.remove_selected_image)
        layout.addWidget(remove_button)

        # Preview Section
        self.preview_label = QLabel("Preview:")
        layout.addWidget(self.preview_label)
        self.preview_image = PreviewLabel()
        layout.addWidget(self.preview_image)

    def setup_signals(self):
        self.image_quality_checker.log.connect(self.update_log)
        self.image_quality_checker.progress.connect(self.update_progress)
        self.image_quality_checker.finished.connect(self.evaluation_finished)

    def load_images(self):
        directory = QFileDialog.getExistingDirectory(self, "Bildverzeichnis auswählen")
        if directory:
            self.load_button.setEnabled(False)
            self.result_text.append("Lade Bilder...")
            self.progress_bar.setValue(0)
            self.progress_label.setText("Lade Bilder: 0%")
            self.loader_thread = ImageLoaderThread(directory)
            self.loader_thread.finished.connect(self.on_images_loaded)
            self.loader_thread.progress.connect(self.update_progress)
            self.loader_thread.start()

    def on_images_loaded(self, image_files):
        self.image_quality_checker.load_images(image_files)
        self.update_listbox()
        self.load_button.setEnabled(True)
        self.result_text.append(f"{len(image_files)} Bilder geladen.")


    def update_listbox(self):
        self.selected_results_list.clear()
        self.selected_results_list.addItems([os.path.basename(f) for f in self.image_quality_checker.image_files])
        self.preview_image.clear()

    def evaluate_quality(self):
        min_quality_text = self.min_quality_entry.text()
        try:
            min_quality = int(min_quality_text)
            if not (0 <= min_quality <= 100):
                raise ValueError
            self.image_quality_checker.min_quality = min_quality
        except ValueError:
            QtWidgets.QMessageBox.critical(
                self, "Invalid Input", "Bitte geben Sie eine gültige Zahl zwischen 0 und 100 für die minimale Qualität ein."
            )
            return

        if not self.image_quality_checker.image_files:
            QtWidgets.QMessageBox.information(
                self, "No Images", "Bitte laden Sie Bilder, bevor Sie die Qualität bewerten."
            )
            return

        self.result_text.clear()
        self.evaluate_button.setEnabled(False)
        self.load_button.setEnabled(False)
        self.selected_results_list.clear()
        self.preview_image.clear()
        self.result_text.append("Starting quality evaluation...\n")

        self.thread = ImageQualityCheckerThread(self.image_quality_checker, self.image_quality_checker.min_quality)
        self.thread.started.connect(lambda: self.result_text.append("Quality evaluation started...\n"))
        self.image_quality_checker.finished.connect(self.thread.quit)
        self.thread.finished.connect(self.thread.deleteLater)
        self.image_quality_checker.finished.connect(self.evaluation_finished)
        self.thread.start()

    def update_log(self, message):
        self.result_text.append(message)

    def update_progress(self, value):
        self.progress_bar.setValue(value)
        self.progress_label.setText(f"Progress: {value}%")

    def evaluation_finished(self, results):
        self.evaluate_button.setEnabled(True)
        self.load_button.setEnabled(True)
        if results:
            self.result_text.append("\nEvaluation completed.")
            self.result_text.append(f"Total images meeting quality criteria: {len(results)}")
            self.selected_results_list.addItems([os.path.basename(f) for f in results])
        else:
            self.result_text.append("\nNo images meet the minimum quality requirement.")
        self.progress_bar.setValue(100)
        self.progress_label.setText("Progress: 100%")

    def remove_selected_image(self):
        selected_items = self.selected_results_list.selectedItems()
        if not selected_items:
            return
        for item in selected_items:
            index = self.selected_results_list.row(item)
            self.selected_results_list.takeItem(index)
        self.preview_image.clear()

    def preview_frame(self, item):
        frame_path = item.text()
        if not os.path.isfile(frame_path):
            self.result_text.append(f"Vorschau nicht verfügbar: {frame_path} existiert nicht.")
            return
        image = QImage(frame_path)
        if image.isNull():
            self.result_text.append(f"Kann Bild nicht laden: {frame_path}")
            return
        pixmap = QPixmap.fromImage(image)
        self.preview_image.setPixmap(pixmap)


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Image Processing Application")
        self.setGeometry(100, 100, 900, 700)
        self.setup_ui()

    def setup_ui(self):
        self.tabs = QTabWidget()
        self.setCentralWidget(self.tabs)

        self.frame_extractor_widget = FrameExtractorUI()
        self.image_quality_checker_widget = ImageQualityCheckerUI()

        self.tabs.addTab(self.frame_extractor_widget, "Video Frame Extractor")
        self.tabs.addTab(self.image_quality_checker_widget, "Image Quality Checker")


def main():
    app = QApplication(sys.argv)
    app.setStyle("Fusion")
    app.setStyleSheet(DARK_STYLE)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

class ImageLoaderThread(QThread):
    progress = pyqtSignal(int)
    finished = pyqtSignal(list)

    def run(self):
        image_files = []
        total_files = sum([len(files) for r, d, files in os.walk(self.directory)])
        processed_files = 0
        for root, dirs, files in os.walk(self.directory):
            for file in files:
                if file.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.bmp', '.tiff', '.webp')):
                    image_files.append(os.path.join(root, file))
                processed_files += 1
                self.progress.emit(int(processed_files / total_files * 100))
        self.finished.emit(image_files)