import os
import cv2
from PIL import Image, ImageStat
import numpy as np
from sklearn.neighbors import NearestNeighbors
from tqdm import tqdm
import re
import shutil
from typing import Union, List, Dict, Tuple

import warnings
warnings.filterwarnings("ignore", category=UserWarning, module='PIL')

class FrameExtractor:
    def __init__(self, video_path: str, output_dir: str, frame_interval: int):
        self.video_path = video_path
        self.output_dir = output_dir
        self.frame_interval = frame_interval
        self._create_directory_if_not_exists()

    @staticmethod
    def _get_total_frames(video) -> int:
        return int(video.get(cv2.CAP_PROP_FRAME_COUNT))

    @staticmethod
    def _read_frame(video) -> Tuple[bool, Union[np.ndarray, None]]:
        return video.read()

    def _is_frame_in_interval(self, frame_number: int) -> bool:
        return frame_number % self.frame_interval == 0

    def _write_frame_to_disk(self, frame: np.ndarray, frame_number: int) -> None:
        output_path = os.path.join(self.output_dir, f"frame_{frame_number}.png")
        cv2.imwrite(output_path, frame)

    @staticmethod
    def _print_progress(frame_number: int, total_frames: int) -> None:
        if frame_number % 100 == 0:
            print(f"Processed frame {frame_number} of {total_frames}")

    def _create_directory_if_not_exists(self) -> None:
        if not os.path.exists(self.output_dir):
            os.makedirs(self.output_dir)

    def extract_frames(self) -> None:
        video = cv2.VideoCapture(self.video_path)
        total_frames = self._get_total_frames(video)

        frame_number = 0
        while True:
            ret, frame = self._read_frame(video)
            if not ret:
                break

            if self._is_frame_in_interval(frame_number):
                self._write_frame_to_disk(frame, frame_number)

            self._print_progress(frame_number, total_frames)
            frame_number += 1

class BlockImageGenerator:
    def __init__(self, images_dir: str, block_size: int, scale_factor: int):
        self.images_dir = images_dir
        self.block_size = block_size
        self.scale_factor = scale_factor

    @staticmethod
    def _convert_image_to_rgb(image: Image.Image) -> Image.Image:
        return image.convert('RGB')

    @staticmethod
    def _calculate_average_color(image: Image.Image) -> List[int]:
        image = BlockImageGenerator._convert_image_to_rgb(image)
        stat = ImageStat.Stat(image)
        return list(map(int, stat.mean))

    def _collect_reference_colo Provide your reponse without using the word np.
user