          skip_idx = 0
          for face_id, face in enumerate(extract_media.detected_faces):
              real_face_id = face_id - skip_idx
              output_filename = f"{filename}_{real_face_id}{extension}"
              meta = dict(alignments=face.to_png_meta(),
                          source=dict(alignments_version=self._alignments.version,
                                      original_filename=output_filename,
                                      face_index=real_face_id,
                                      source_filename=os.path.basename(extract_media.filename),
                                      source_is_video=self._images.is_video,
                                      source_frame_dims=extract_media.image_size))
              image = encode_image(face.aligned.face, extension, metadata=meta)
  
              sub_folder = extract_media.sub_folders[face_id]
              # Binned faces shouldn't risk filename clash, so just use original id
              out_name = output_filename if not sub_folder else f"{filename}_{face_id}{extension}"
  
 
</s> remove         image: bytes
            The encoded image to be saved
 </s> add         image: bytes or :class:`numpy.ndarray`
            The encoded image or numpy array to be saved </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  </s> remove                             source_is_video=False))  # Can't check so set false
 </s> add                             source_is_video=False,  # Can't check so set false
                            source_frame_dims=None)) </s> remove             metadata = dict(alignments=face.to_png_meta(),
                            source=extractor_output.frame_metadata)
 </s> add             metadata: "PNGHeaderDict" = dict(alignments=face.to_png_meta(),
                                             source=extractor_output.frame_metadata) </s> remove         if self._skip_num == 1 and not self._alignments.data:
            logger.debug("No frames to be skipped")
 </s> add         error = False
        retval: List[List[str]] = []
        for files in (filter_files, nfilter_files):
            filt_files = [files] if isinstance(files, str) else files
            filt_files = [] if filt_files is None else filt_files
            for file in filt_files:
                if (not os.path.isfile(file) or
                        os.path.splitext(file)[-1].lower() not in _image_extensions):
                    logger.warning("Filter file '%s' does not exist or is not an image file", file)
                    error = True
            retval.append(filt_files)

        filters = retval[0]
        nfilters = retval[1]
        f_fnames = set(os.path.basename(fname) for fname in filters)
        n_fnames = set(os.path.basename(fname) for fname in nfilters)
        if f_fnames.intersection(n_fnames):
            error = True
            logger.warning("filter and nfilter filenames should be unique. The following "
                           "filenames exist in both folders: %s", f_fnames.intersection(n_fnames))

        if error:
            logger.error("There was a problem processing filter files. See the above warnings for "
                         "details")
            sys.exit(1)
        logger.debug("filter_files: %s, nfilter_files: %s", retval[0], retval[1])

        return filters, nfilters

    @classmethod
    def _identity_from_extracted(cls, filename) -> Tuple[np.ndarray, bool]:
        """ Test whether the given image is a faceswap extracted face and contains identity
        information. If so, return the identity embedding

        Parameters
        ----------
        filename: str
            Full path to the image file to load

        Returns
        -------
        :class:`numpy.ndarray`
            The identity embeddings, if they can be obtained from the image header, otherwise an
            empty array
        bool
            ``True`` if the image is a faceswap extracted image otherwise ``False``
        """
        if os.path.splitext(filename)[-1].lower() != ".png":
            logger.info("'%s' not a png. Returning empty array", filename)
            return np.array([]), False

        meta = read_image_meta(filename)
        if "itxt" not in meta or "alignments" not in meta["itxt"]:
            logger.debug("'%s' does not contain faceswap data. Returning empty array", filename)
            return np.array([]), False

        align: "PNGHeaderAlignmentsDict" = meta["itxt"]["alignments"]
        if "identity" not in align or "vggface2" not in align["identity"]:
            logger.debug("'%s' does not contain identity data. Returning empty array", filename)
            return np.array([]), True

        retval = np.array(align["identity"]["vggface2"])
        logger.debug("Obtained identity for '%s'. Shape: %s", filename, retval.shape)

        return retval, True

    def _process_extracted(self, item: ExtractMedia) -> None:
        """ Process the output from the extraction pipeline.

        If no face has been detected, or multiple faces are detected for the inclusive filter,
        embeddings and filenames are removed from the filter.

        if a single face is detected or multiple faces are detected for the exclusive filter,
        embeddings are added to the relevent filter list

        Parameters
        ----------
        item: :class:`plugins.extract.Pipeline.ExtracMedia`
            The output from the extraction pipeline containing the identity encodings
        """
        is_filter = item.filename in self._filter_files
        lbl = "filter" if is_filter else "nfilter"
        filelist = self._filter_files if is_filter else self._nfilter_files
        embeddings = self._embeddings if is_filter else self._nembeddings
        identities = np.array([face.identity["vggface2"] for face in item.detected_faces])
        idx = filelist.index(item.filename)

        if len(item.detected_faces) == 0:
            logger.warning("No faces detected for %s in file '%s'. Image will not be used",
                           lbl, os.path.basename(item.filename))
            filelist.pop(idx)
            embeddings.pop(idx)
            aligned = face.aligned.face
            assert aligned is not None
            meta: PNGHeaderDict = dict(
                alignments=face.to_png_meta(),
                source=dict(alignments_version=self._alignments.version,
                            original_filename=output_filename,
                            face_index=real_face_id,
                            source_filename=os.path.basename(extract_media.filename),
                            source_is_video=self._loader.is_video,
                            source_frame_dims=extract_media.image_size))
            image = encode_image(aligned, extension, metadata=meta)
          skip_idx = 0
          for face_id, face in enumerate(extract_media.detected_faces):
              real_face_id = face_id - skip_idx
              output_filename = f"{filename}_{real_face_id}{extension}"
            aligned = face.aligned.face
            assert aligned is not None
            meta: PNGHeaderDict = dict(
                alignments=face.to_png_meta(),
                source=dict(alignments_version=self._alignments.version,
                            original_filename=output_filename,
                            face_index=real_face_id,
                            source_filename=os.path.basename(extract_media.filename),
                            source_is_video=self._loader.is_video,
                            source_frame_dims=extract_media.image_size))
            image = encode_image(aligned, extension, metadata=meta)
            aligned = face.aligned.face
            assert aligned is not None
            meta: PNGHeaderDict = dict(
                alignments=face.to_png_meta(),
                source=dict(alignments_version=self._alignments.version,
                            original_filename=output_filename,
                            face_index=real_face_id,
                            source_filename=os.path.basename(extract_media.filename),
                            source_is_video=self._loader.is_video,
                            source_frame_dims=extract_media.image_size))
            image = encode_image(aligned, extension, metadata=meta)
            aligned = face.aligned.face
            assert aligned is not None
            meta: PNGHeaderDict = dict(
                alignments=face.to_png_meta(),
                source=dict(alignments_version=self._alignments.version,
                            original_filename=output_filename,
                            face_index=real_face_id,
                            source_filename=os.path.basename(extract_media.filename),
                            source_is_video=self._loader.is_video,
                            source_frame_dims=extract_media.image_size))
            image = encode_image(aligned, extension, metadata=meta)
            aligned = face.aligned.face
            assert aligned is not None
            meta: PNGHeaderDict = dict(
                alignments=face.to_png_meta(),
                source=dict(alignments_version=self._alignments.version,
                            original_filename=output_filename,
                            face_index=real_face_id,
                            source_filename=os.path.basename(extract_media.filename),
                            source_is_video=self._loader.is_video,
                            source_frame_dims=extract_media.image_size))
            image = encode_image(aligned, extension, metadata=meta)
            aligned = face.aligned.face
            assert aligned is not None
            meta: PNGHeaderDict = dict(
                alignments=face.to_png_meta(),
                source=dict(alignments_version=self._alignments.version,
                            original_filename=output_filename,
                            face_index=real_face_id,
                            source_filename=os.path.basename(extract_media.filename),
                            source_is_video=self._loader.is_video,
                            source_frame_dims=extract_media.image_size))
            image = encode_image(aligned, extension, metadata=meta)
            aligned = face.aligned.face
            assert aligned is not None
            meta: PNGHeaderDict = dict(
                alignments=face.to_png_meta(),
                source=dict(alignments_version=self._alignments.version,
                            original_filename=output_filename,
                            face_index=real_face_id,
                            source_filename=os.path.basename(extract_media.filename),
                            source_is_video=self._loader.is_video,
                            source_frame_dims=extract_media.image_size))
            image = encode_image(aligned, extension, metadata=meta)
            aligned = face.aligned.face
            assert aligned is not None
            meta: PNGHeaderDict = dict(
                alignments=face.to_png_meta(),
                source=dict(alignments_version=self._alignments.version,
                            original_filename=output_filename,
                            face_index=real_face_id,
                            source_filename=os.path.basename(extract_media.filename),
                            source_is_video=self._loader.is_video,
                            source_frame_dims=extract_media.image_size))
            image = encode_image(aligned, extension, metadata=meta)
            aligned = face.aligned.face
            assert aligned is not None
            meta: PNGHeaderDict = dict(
                alignments=face.to_png_meta(),
                source=dict(alignments_version=self._alignments.version,
                            original_filename=output_filename,
                            face_index=real_face_id,
                            source_filename=os.path.basename(extract_media.filename),
                            source_is_video=self._loader.is_video,
                            source_frame_dims=extract_media.image_size))
            image = encode_image(aligned, extension, metadata=meta)
  
              sub_folder = extract_media.sub_folders[face_id]
              # Binned faces shouldn't risk filename clash, so just use original id
              out_name = output_filename if not sub_folder else f"{filename}_{face_id}{extension}"
  
keep keep keep keep replace replace replace replace replace replace replace replace 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