          ----------
          filename: str
              The filename of the image to be saved. NB: Any folders passed in with the filename
              will be stripped and replaced with :attr:`location`.
          image: bytes
              The encoded image to be saved
          subfolder: str or ``None``
              If the file should be saved in a subfolder in the output location, the subfolder should
              be provided here. ``None`` for no subfolder.
          """
          location = os.path.join(self.location, sub_folder) if sub_folder else self._location
 
</s> remove         The original frame
 </s> add         The original frame or a faceswap aligned face image </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         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         """ Set global arguments and load masker plugin """
 </s> add         """ Set global arguments and load masker plugin

        Parameters
        ----------
        masker: str or ``none``
            The name of the masker plugin to use or ``None`` if no masker
        configfile: str
            Full path to custom config.ini file or ``None`` to use default

        Returns
        -------
        :class:`~plugins.extract.mask._base.Masker` or ``None``
            The masker plugin to use or ``None`` if no masker selected
        """ </s> remove #: lib/cli/args.py:587
 </s> add #: lib/cli/args.py:583
        image: bytes or :class:`numpy.ndarray`
            The encoded image or numpy array to be saved
          ----------
          filename: str
              The filename of the image to be saved. NB: Any folders passed in with the filename
              will be stripped and replaced with :attr:`location`.
        image: bytes or :class:`numpy.ndarray`
            The encoded image or numpy array to be saved
        image: bytes or :class:`numpy.ndarray`
            The encoded image or numpy array to be saved
          subfolder: str or ``None``
              If the file should be saved in a subfolder in the output location, the subfolder should
              be provided here. ``None`` for no subfolder.
          """
          location = os.path.join(self.location, sub_folder) if sub_folder else self._location
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 
lib/image.py