      def _skip_num(self) -> int:
          """ int: Number of frames to skip if extract_every_n has been provided """
          return self._args.extract_every_n if hasattr(self._args, "extract_every_n") else 1
  
      def _set_skip_list(self) -> None:
          """ Add the skip list to the image loader
  
          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> remove     def _skip_num(self) -> int:
        """ int: Number of frames to skip if extract_every_n has been provided """
        return self._args.extract_every_n if hasattr(self._args, "extract_every_n") else 1
 </s> add     def embeddings(self) -> np.ndarray:
        """ :class:`numpy.ndarray`: The filter embeddings"""
        if self._embeddings and all(np.any(e) for e in self._embeddings):
            retval = np.concatenate(self._embeddings, axis=0)
        else:
            retval = np.array([])
        return retval </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     def _save_interval(self) -> Optional[int]:
        """ int: The number of frames to be processed between each saving of the alignments file if
        it has been provided, otherwise ``None`` """
        if hasattr(self._args, "save_interval"):
            return self._args.save_interval
        return None
 </s> add     def active(self):
        """ bool: ``True`` if filter files have been passed in command line arguments. ``False`` if
        no filter files have been provided """
        return bool(self._filter_files) or bool(self._nfilter_files) </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) </s> remove     def frame_metadata(self) -> dict:
 </s> add     def is_aligned(self) -> bool:
        """ bool. ``True`` if :attr:`image` is an aligned faceswap image otherwise ``False`` """
        return self._is_aligned

    @property
    def frame_metadata(self) -> "PNGHeaderSourceDict":
    @property
    def n_embeddings(self) -> np.ndarray:
        """ :class:`numpy.ndarray`: The n-filter embeddings"""
        if self._nembeddings and all(np.any(e) for e in self._nembeddings):
            retval = np.concatenate(self._nembeddings, axis=0)
        else:
            retval = np.array([])
        return retval
      def _skip_num(self) -> int:
          """ int: Number of frames to skip if extract_every_n has been provided """
          return self._args.extract_every_n if hasattr(self._args, "extract_every_n") else 1
  
    @property
    def n_embeddings(self) -> np.ndarray:
        """ :class:`numpy.ndarray`: The n-filter embeddings"""
        if self._nembeddings and all(np.any(e) for e in self._nembeddings):
            retval = np.concatenate(self._nembeddings, axis=0)
        else:
            retval = np.array([])
        return retval
    @property
    def n_embeddings(self) -> np.ndarray:
        """ :class:`numpy.ndarray`: The n-filter embeddings"""
        if self._nembeddings and all(np.any(e) for e in self._nembeddings):
            retval = np.concatenate(self._nembeddings, axis=0)
        else:
            retval = np.array([])
        return retval
  
          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`.
          """
keep keep keep keep 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