import logging
import time
import uuid
from concurrent.futures import ThreadPoolExecutor

import cv2

from app import redis_service

from libs.face_recognition import ALG, FaceRecognitionStatusEnum

import numpy as np

from settings import NUM_MAX_THREADS


class FaceRecognition:
    """Service for using opencv face recognition."""
    def __init__(self, file_id, video_path, threshold=80):
        """
        Sets model’s parameters.
        Args:
            file_id (str): file id
            video_path (str): path to video
            threshold (int): model’s threshold
        """
        self.face_cascade_path = cv2.data.haarcascades + ALG
        self.face_cascade = cv2.CascadeClassifier(self.face_cascade_path)
        self.faces_list = []
        self.names_list = []
        self.threshold = threshold
        self.video_path = video_path
        self.video = cv2.VideoCapture(self.video_path)
        self.frame_count = int(self.video.get(cv2.CAP_PROP_FRAME_COUNT))
        self.frame_num = int(self.video.get(cv2.CAP_PROP_POS_FRAMES))
        self.file_id = file_id
        self.status = FaceRecognitionStatusEnum.PROCESS
        self.persons = 0

    def process(self):
        """
        Process of recognition faces in video by frames.
        Writes id as uuid4.
        Returns:
            tuple: with list of faces and list of names
        """
        pool = ThreadPoolExecutor(max_workers=NUM_MAX_THREADS)

        # redis_data = redis_service.get(self.file_id)
        # self.status = redis_data.get("status")

        while True:
            if self.status == FaceRecognitionStatusEnum.PAUSE:
                logging.info(
                    f"File id: {self.file_id} | Paused by user"
                    f"Frame: {self.frame_num}")
                break

            if self.status == FaceRecognitionStatusEnum.RESUME:
                resume_data = redis_service.get(self.file_id)
                self.faces_list = resume_data.get("faces_list")
                self.names_list = resume_data.get("names_list")
                self.frame_num = resume_data.get("frame")
                self.persons = resume_data.get("persons")

                # @@@
                logging.info(f"RESUME"
                             f"faces {self.faces_list} / {type(self.faces_list)}"
                             f"names {self.names_list} / {type(self.names_list)}"
                             f"frame {self.frame_num} / {type(self.frame_num)}"
                             f"persons {self.persons} / {type(self.persons)}")

                logging.info(
                    f"\nFile id: {self.file_id} | Resume\n"
                    f"Frame: {self.frame_num}")
                for i in range(self.frame_num + 1):
                    self.video.read()
                self.status = FaceRecognitionStatusEnum.PROCESS

            ret, frame = self.video.read()

            if not ret:
                break

            pool.submit(self._process_frame, frame)

        pool.shutdown()
        self._close()

        if self.frame_count == self.frame_num:
            self.status = FaceRecognitionStatusEnum.READY

        logging.info(
            f"Video with id {self.file_id} closed\n"
            f"Status: {self.status}")
        return self.faces_list, self.names_list

    def _process_frame(self, frame):
        """
        Frame processing.
        Args:
            frame (cv2.typing.MatLike): cv2 frame

        Returns:
            None:
        """
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        faces = self.face_cascade.detectMultiScale(
            gray, scaleFactor=1.1, minNeighbors=5, minSize=(100, 100))

        for (x, y, w, h) in faces:
            cur_face = gray[y:y + h, x:x + w]
            rec = cv2.face.LBPHFaceRecognizer.create()
            rec.train([cur_face], np.array(0))
            f = True
            for face in self.faces_list:
                _, confidence = rec.predict(face)
                if confidence < self.threshold:
                    f = False

            if f:
                label = str(uuid.uuid4())
                self.faces_list.append(cur_face.tolist())
                self.names_list.append(label)
                self.persons += 1

        redis_service.update(
            self.file_id,
            {
                "file_id": self.file_id,
                "faces_list": self.faces_list,
                "names_list": self.names_list,
                "status": str(self.status),
                "frame": str(self.frame_num),
                "persons": str(self.persons),
                "filepath": self.video_path
            }
        )
        logging.info(
            f"\n-----\n"
            f"File id: {self.file_id}\n"
            f"Frame: {self.frame_num}/{self.frame_count} passed\n"
            f"Persons: {self.persons}\n"
            f"Status: {self.status}\n"
            f"-----\n")

    def _close(self):
        """
        Closes video and destroys all windows.
        Returns:
            None:
        """
        self.video.release()
        cv2.destroyAllWindows()


Я хочу обрабатывать каждый кадр видео в разном потоке, не должна ли функция self.video.read() вызываться в _process_frame? Может еще что-то поменять? Перепиши, как надо