                                      min_size=self._args.min_size,
                                      normalize_method=normalization,
                                      re_feed=self._args.re_feed)
  
      def _get_input_locations(self) -> List[str]:
          """ Obtain the full path to input locations. Will be a list of locations if batch mode is
          selected, or a containing a single location if batch mode is not selected.
  
 
</s> remove         # from lib.queue_manager import queue_manager ; queue_manager.debug_monitor(3)
 </s> add         logger.info("Extracting faces to obtain identity from images")
        logger.debug("Files requiring full extraction: %s",
                     [fname for fname in file_list if fname not in aligned])
        logger.debug("Aligned files requiring identity info: %s", aligned)

        loader = PipelineLoader(file_list, self._extractor, aligned_filenames=aligned)
        loader.launch()

        for phase in range(self._extractor.passes):
            is_final = self._extractor.final_pass
            detected_faces: Dict[str, ExtractMedia] = {}
            self._extractor.launch()
            desc = "Obtaining reference face Identity"
            if self._extractor.passes > 1:
                desc = (f"{desc } pass {phase + 1} of {self._extractor.passes}: "
                        f"{self._extractor.phase_text}")
            for extract_media in tqdm(self._extractor.detected_faces(),
                                      total=len(file_list),
                                      file=sys.stdout,
                                      desc=desc):
                if is_final:
                    self._process_extracted(extract_media)
                else:
                    extract_media.remove_image()
                    # cache extract_media for next run
                    detected_faces[extract_media.filename] = extract_media

            if not is_final:
                logger.debug("Reloading images")
                loader.reload(detected_faces)

        self._extractor.reset_phase_index()

    def _get_embeddings(self) -> None:
        """ Obtain the embeddings for the given filter lists """
        needs_extraction: List[str] = []
        aligned: List[str] = []

        for files, embed in zip((self._filter_files, self._nfilter_files),
                                (self._embeddings, self._nembeddings)):
            for idx, file in enumerate(files):
                identity, is_aligned = self._identity_from_extracted(file)
                if np.any(identity):
                    logger.debug("Obtained identity from png header: '%s'", file)
                    embed[idx] = identity[None, ...]
                    continue

                needs_extraction.append(file)
                if is_aligned:
                    aligned.append(file)

        if needs_extraction:
            self._identity_from_extractor(needs_extraction, aligned)

        if not self._nfilter_files and not self._filter_files:
            logger.error("No faces were detected from your selected identity filter files")
            sys.exit(1)

        logger.debug("Filter: (filenames: %s, shape: %s), nFilter: (filenames: %s, shape: %s)",
                     [os.path.basename(f) for f in self._filter_files],
                     self.embeddings.shape,
                     [os.path.basename(f) for f in self._nfilter_files],
                     self.n_embeddings.shape)


class PipelineLoader():
    """ Handles loading and reloading images into the extraction pipeline.

    Parameters
    ----------
    path: str or list of str
        Full path to a folder of images or a video file or a list of image files
    extractor: :class:`~plugins.extract.pipeline.Extractor`
        The extractor pipeline for obtaining face identity from images
    aligned_filenames: list, optional
        Used for when the loader is used for getting face filter embeddings. List of full path to
        image files that exist in :attr:`path` that are aligned faceswap images
    """
    def __init__(self,
                 path: Union[str, List[str]],
                 extractor: Extractor,
                 aligned_filenames: Optional[List[str]] = None) -> None:
        logger.debug("Initializing %s: (path: %s, extractor: %s, aligned_filenames: %s)",
                     self.__class__.__name__, path, extractor, aligned_filenames)
        self._images = ImagesLoader(path, fast_count=True)
        self._extractor = extractor
        self._threads: List[MultiThread] = []
        self._aligned_filenames = [] if aligned_filenames is None else aligned_filenames
        logger.debug("Initialized %s", self.__class__.__name__)

    @property
    def is_video(self) -> bool:
        """ bool: ``True`` if the input location is a video file, ``False`` if it is a folder of
        images """
        return self._images.is_video

    @property
    def file_list(self) -> List[str]:
        """ list: A full list of files in the source location. If the input is a video
        then this is a list of dummy filenames as corresponding to an alignments file """
        return self._images.file_list

    @property
    def process_count(self) -> int:
        """ int: The number of images or video frames to be processed (IE the total count less
        items that are to be skipped from the :attr:`skip_list`)"""
        return self._images.process_count

    def add_skip_list(self, skip_list: List[int]) -> None:
        """ Add a skip list to the :class:`ImagesLoader`

        Parameters
        ----------
        skip_list: list
            A list of indices corresponding to the frame indices that should be skipped by the
            :func:`load` function.
        """
        self._images.add_skip_list(skip_list)

    def launch(self) -> None:
        """ Launch the image loading pipeline """ </s> remove         if os.path.isdir(self.location):
 </s> add         if not isinstance(self.location, str) or os.path.isdir(self.location): </s> remove         Should only be called from  :class:`lib.cli.launcher.ScriptExecutor`
 </s> add         if len(item.detected_faces) > 1 and is_filter:
            logger.warning("%s faces detected for filter in '%s'. These identies will not be used",
                           len(item.detected_faces), os.path.basename(item.filename))
            filelist.pop(idx)
            embeddings.pop(idx)
            return

        if len(item.detected_faces) > 1 and not is_filter:
            logger.warning("%s faces detected for nfilter in '%s'. All of these identies will be "
                           "used", len(item.detected_faces), os.path.basename(item.filename))
            embeddings[idx] = identities
            return

    def _identity_from_extractor(self, file_list: List[str], aligned: List[str]) -> None:
        """ Obtain the identity embeddings from the extraction pipeline

        Parameters
        ----------
        filesile_list: list
            List of full path to images to run through the extraction pipeline
        aligned: list
            List of full path to images that exist in attr:`filelist` that are faceswap aligned
            images </s> remove         Checks against `extract_every_n` and the existence of alignments data (can exist if
        `skip_existing` or `skip_existing_faces` has been provided) and compiles a list of frame
        indices that should not be processed, providing these to :class:`lib.image.ImagesLoader`.
 </s> add     @classmethod
    def _validate_inputs(cls,
                         filter_files: Optional[Union[str, List[str]]],
                         nfilter_files: Optional[Union[str, List[str]]]) -> Tuple[List[str],
                                                                                  List[str]]:
        """ Validates that the given filter/nfilter files exist, are image files and are unique

        Parameters
        ----------
        filter_files: str, list or ``None``
            The list of filter file(s) passed in as command line arguments
        nfilter_files: str, list or ``None``
            The list of nfilter file(s) passed in as command line arguments

        Returns
        -------
        filter_files: list
            List of full paths to filter files
        nfilter_files: list
            List of full paths to nfilter files </s> remove 

class FaceFilter(PostProcessAction):
    """ Filter in or out faces based on input image(s). Extract or Convert

    Parameters
    -----------
    args: tuple
        Unused
    kwargs: dict
        Keyword arguments for face filter:

        * **detector** (`str`) - The detector to use

        * **aligner** (`str`) - The aligner to use

        * **multiprocess** (`bool`) - Whether to run the extraction pipeline in single process \
        mode or not

        * **ref_threshold** (`float`) - The reference threshold for a positive match

        * **filter_lists** (`dict`) - The filter and nfilter image paths
    """

    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        logger.info("Extracting and aligning face for Face Filter...")
        self._filter = self._load_face_filter(**kwargs)
        logger.debug("Initialized %s", self.__class__.__name__)

    def _load_face_filter(self,
                          filter_lists: Dict[str, str],
                          ref_threshold: float,
                          aligner: str,
                          detector: str,
                          multiprocess: bool) -> Optional[FilterFunc]:
        """ Set up and load the :class:`~lib.face_filter.FaceFilter`.

        Parameters
        ----------
        filter_lists: dict
            The filter and nfilter image paths
        ref_threshold: float
            The reference threshold for a positive match
        aligner: str
            The aligner to use
        detector: str
            The detector to use
        multiprocess: bool
            Whether to run the extraction pipeline in single process mode or not

        Returns
        -------
        :class:`~lib.face_filter.FaceFilter`
            The face filter
        """
        if not any(val for val in filter_lists.values()):
            return None

        facefilter = None
        filter_files = [self._set_face_filter(f_type, filter_lists[f_type])
                        for f_type in get_args(Literal["filter", "nfilter"])]

        if any(filters for filters in filter_files):
            facefilter = FilterFunc(filter_files[0],
                                    filter_files[1],
                                    detector,
                                    aligner,
                                    multiprocess,
                                    ref_threshold)
            logger.debug("Face filter: %s", facefilter)
        else:
            self._valid = False
        return facefilter

    @classmethod
    def _set_face_filter(cls,
                         f_type: Literal["filter", "nfilter"],
                         f_args: Union[str, List[str]]) -> List[str]:
        """ Check filter files exist and add the filter file paths to a list.

        Parameters
        ----------
        f_type: {"filter", "nfilter"}
            The type of filter to create this list for
        f_args: str or list
            The filter image(s) to use

        Returns
        -------
        list
            The confirmed existing paths to filter files to use
        """
        if not f_args:
            return []

        logger.info("%s: %s", f_type.title(), f_args)
        filter_files = f_args if isinstance(f_args, list) else [f_args]
        filter_files = [fpath for fpath in filter_files if os.path.exists(fpath)]
        if not filter_files:
            logger.warning("Face %s files were requested, but no files could be found. This "
                           "filter will not be applied.", f_type)
        logger.debug("Face Filter files: %s", filter_files)
        return filter_files

    def process(self, extract_media: "ExtractMedia") -> None:
        """ Filters in or out any wanted or unwanted faces based on command line arguments.

        Parameters
        ----------
        extract_media: :class:`~plugins.extract.pipeline.ExtractMedia`
            The :class:`~plugins.extract.pipeline.ExtractMedia` object to perform the
            face filtering on.
        """
        if not self._filter:
            return
        ret_faces = []
        for idx, detect_face in enumerate(extract_media.detected_faces):
            check_item = detect_face["face"] if isinstance(detect_face, dict) else detect_face
            if not self._filter.check(extract_media.image, check_item):
                logger.verbose("Skipping not recognized face: (Frame: %s Face %s)",  # type: ignore
                               extract_media.filename, idx)
                continue
            logger.trace("Accepting recognised face. Frame: %s. Face: %s",  # type: ignore
                         extract_media.filename, idx)
            ret_faces.append(detect_face)
        extract_media.add_detected_faces(ret_faces)
 </s> add 
        self._filter = Filter(self._args.ref_threshold,
                              self._args.filter,
                              self._args.nfilter,
                              self._extractor)
                                      min_size=self._args.min_size,
                                      normalize_method=normalization,
                                      re_feed=self._args.re_feed)
        self._filter = Filter(self._args.ref_threshold,
                              self._args.filter,
                              self._args.nfilter,
                              self._extractor)
  
      def _get_input_locations(self) -> List[str]:
          """ Obtain the full path to input locations. Will be a list of locations if batch mode is
          selected, or a containing a single location if batch mode is not selected.
  
keep keep add keep keep keep keep keep
 Update Face Filter
  - Remove old face filter
  - plugins.extract.pipeline: Expose plugins directly
  - Change `is_aligned` from plugin level to ExtractMedia level
  - Allow extract pipeline to take faceswap aligned images
  - Add ability for recognition plugins to accept aligned faces as input
  - Add face filter to recognition plugin
  - Move extractor pipeline IO ops to own class 
scripts/extract.py