          return pre_face_count - post_face_count
  
  
  class Rename():
      """ Rename faces to match their source frame and position index """
      def __init__(self, alignments, arguments, faces=None):
          logger.debug("Initializing %s: (arguments: %s, faces: %s)",
                       self.__class__.__name__, arguments, faces)
          self.alignments = alignments
          self.faces = faces if faces else Faces(arguments.faces_dir)
 
</s> remove         self.seen_multihash = set()
 </s> add  </s> remove     def check_multi_hashes(self, faces, frame, idx):
        """ Check filenames for where multiple faces have the
            same hash (e.g. for freeze frames) """
        logger.debug("Multiple hashes: (frame: faces: %s, frame: '%s', idx: %s", faces, frame, idx)
        frame_idx = "{}_{}".format(frame, idx)
        retval = None
        for face_name, extension in faces:
            if (face_name, extension) in self.seen_multihash:
                # Don't return a filename that has already been processed
                logger.debug("Already seen: %s", (face_name, extension))
 </s> add     @staticmethod
    def _validate_hash_match(disk_faces, align_faces):
        """ Validate that the hash has returned corresponding faces from disk and alignments file.

        Parameters
        ----------
        disk_faces: list
            List of tuples of (`file name`, `file extension`) for all faces that exist for the
            current hash
        align_faces: dict
            `frame filename`: `index` for all faces that exist in the alignments file for the
            current hash

        Returns
        -------
        list
            List of disk_faces that do not correspond to a matching entry in the alignments file.
            Returns `None` if there is a valid match
        """
        if align_faces is None:
            logger.debug("No matching hash found for faces: %s", disk_faces)
            return [face[0] + face[1] for face in disk_faces]
        if len(disk_faces) != len(align_faces):
            logger.debug("Number of faces mismatch for hash: (disk_faces: %s, align_faces: %s)",
                         disk_faces, align_faces)
            return [face[0] + face[1] for face in disk_faces[: len(align_faces)]]
        return None

    @staticmethod
    def _get_filename_mapping(disk_faces, align_faces):
        """ Map the source filenames for this hash to the destination filenames.

        Parameters
        ----------
        disk_faces: list
            List of tuples of (`file name`, `file extension`) for all faces that exist for the
            current hash
        align_faces: dict
            `frame filename`: `index` for all faces that exist in the alignments file for the
            current hash

        Returns
        -------
        source_filenames: list
            List of source filenames to be renamed for this hash
        dest_filenames: list
            List of destination filenames that faces for this hash are to be renamed to
            List of disk_faces that do not correspond to a matching entry in the alignments file.
            Returns `None` if there is a valid match
        """
        source_filenames = []
        dest_filenames = []
        # Force deterministic order on alignments dict for multi hash faces
        sorted_aligned = sorted([(frame, idx) for frame, idx in align_faces.items()])
        for disk_face, align_face in zip(disk_faces, sorted_aligned):
            extension = disk_face[1]
            src_fname = disk_face[0] + extension

            dst_frame = os.path.splitext(align_face[0])[0]
            dst_fname = "{}_{}{}".format(dst_frame, align_face[1], extension)
            logger.debug("Mapping rename from '%s' to '%s'", src_fname, dst_fname)
            source_filenames.append(src_fname)
            dest_filenames.append(dst_fname)
        return source_filenames, dest_filenames

    @staticmethod
    def _sort_mappings(sources, destinations):
        """ Sort the mapping lists by destinations to avoid filename clash.

        Parameters
        ----------
        sources: list
            List of source filenames in the same order as :attr:`destinations`
        destinations: dict
            List of destination filenames in the same order as :attr:`sources`

        Returns
        -------
        list
            List of tuples of (`source filename`, `destination filename`) ordered by destination
            filename
        """
        sorted_indices = [idx for idx, _ in sorted(enumerate(destinations), key=lambda x: x[1])]
        mappings = [(sources[idx], destinations[idx]) for idx in sorted_indices]
        logger.trace("filename mappings: %s", mappings)
        return mappings

    def _rename_faces(self, filename_mappings):
        """ Rename faces back to their original name as exists in the alignments file.

        If the source and destination filename are the same then skip that file.

        Parameters
        ----------
        filename_mappings: list
            List of tuples of (`source filename`, `destination filename`) ordered by destination
            filename

        Returns
        -------
        int
            The number of faces that have been renamed
        """
        rename_count = 0
        for src, dst in tqdm(filename_mappings, desc="Renaming Faces"):
            if src == dst:
                logger.debug("Skipping rename of '%s' as destination name is same as souce", src) </s> remove             if face_name == frame_idx:
                # If a matching filename already exists return that
                retval = (face_name, extension)
                logger.debug("Matching filename found: %s", retval)
                self.seen_multihash.add(retval)
                break
            if face_name.startswith(frame):
                # If a matching framename already exists return that
                retval = (face_name, extension)
                logger.debug("Matching freamename found: %s", retval)
                self.seen_multihash.add(retval)
                break
        if not retval:
            # If no matches, just pop the first filename
            retval = [face for face in faces if face not in self.seen_multihash][0]
            logger.debug("No matches found. Choosing: %s", retval)
            self.seen_multihash.add(retval)
        logger.debug("Returning: %s", retval)
        return retval
 </s> add             old = os.path.join(self.faces.folder, src)
            new = os.path.join(self.faces.folder, dst)
            if os.path.exists(new):
                # This should never happen, but is a safety measure to prevent deletion of faces
                # when multiple files have the same hash.
                logger.debug("Skipping renaming to an existing file: (src: '%s', dst: '%s'",
                             src, dst)
                continue
            logger.verbose("Renaming '%s' to '%s'", old, new)
            os.rename(old, new)
            rename_count += 1
        return rename_count </s> remove     def rename_faces(self, frame, frame_fullname, details):
        """ Rename faces
            Done in 2 iterations as two files cannot share the same name """
        logger.trace("Renaming faces for frame: '%s'", frame_fullname)
        temp_ext = ".temp_move"
        frame_faces = [(x["hash"], idx) for idx, x in enumerate(details)]
        rename_count = 0
        rename_files = list()
        for f_hash, idx in frame_faces:
            faces = self.faces.items[f_hash]
            if len(faces) == 1:
                face_name, face_ext = faces[0]
            else:
                face_name, face_ext = self.check_multi_hashes(faces, frame, idx)
            old = face_name + face_ext
            new = "{}_{}{}".format(frame, idx, face_ext)
            if old == new:
                logger.trace("Face does not require renaming: '%s'", old)
 </s> add     def _build_rename_list(self):
        """ Build a list of source and destination filenames for renaming.

        Validates that all files in the faces folder have a corresponding match in the alignments
        file. Orders the rename list by destination filename to avoid potential for filename clash.

        Returns
        -------
        list
            List of tuples of (`source filename`, `destination filename`) ordered by destination
            filename
        """
        source_filenames = []
        dest_filenames = []
        errors = []
        pbar = tqdm(desc="Building Rename Lists", total=self.faces.count)
        for disk_hash, disk_faces in self.faces.items.items():
            align_faces = self.alignments.hashes_to_frame.get(disk_hash, None)
            face_error = self._validate_hash_match(disk_faces, align_faces)
            if face_error is not None:
                errors.extend(face_error)
                pbar.update(len(disk_faces)) </s> remove             rename_files.append((old, new))
        for action in ("temp", "final"):
            for files in rename_files:
                old, new = files
                old_file = old if action == "temp" else old + temp_ext
                new_file = old + temp_ext if action == "temp" else new
                src = os.path.join(self.faces.folder, old_file)
                dst = os.path.join(self.faces.folder, new_file)
                logger.trace("Renaming: '%s' to '%s'", old_file, new_file)
                os.rename(src, dst)
                if action == "final":
                    rename_count += 1
                    logger.verbose("Renamed '%s' to '%s'", old, new)
        return rename_count
 </s> add             src_faces, dst_faces = self._get_filename_mapping(disk_faces, align_faces)
            source_filenames.extend(src_faces)
            dest_filenames.extend(dst_faces)
            pbar.update(len(src_faces))
        pbar.close()
        if errors:
            logger.error("There are faces in the given folder that do not correspond to entries "
                         "in the alignments file. Please check your data, and if neccesarry run "
                         "the `remove-faces` job. To get a list of faces missing alignments "
                         "entries, run with VERBOSE logging")
            logger.verbose("Files in faces folder not in alignments file: %s", errors)
            exit(1)
        return self._sort_mappings(source_filenames, dest_filenames)
    """ Rename faces in a folder to match their filename as stored in an alignments file.

    Parameters
    ----------
    alignments: :class:`tools.lib_alignments.media.AlignmentData`
        The alignments data loaded from an alignments file for this rename job
    arguments: :class:`argparse.Namespace`
        The :mod:`argparse` arguments as passed in from :mod:`tools.py`
    faces: :class:`tools.lib_alignments.media.Faces`, Optional
        An optional faces object, if the rename task is being called by another job.
        Default: ``None``
    """
          return pre_face_count - post_face_count
  
  
  class Rename():
    """ Rename faces in a folder to match their filename as stored in an alignments file.

    Parameters
    ----------
    alignments: :class:`tools.lib_alignments.media.AlignmentData`
        The alignments data loaded from an alignments file for this rename job
    arguments: :class:`argparse.Namespace`
        The :mod:`argparse` arguments as passed in from :mod:`tools.py`
    faces: :class:`tools.lib_alignments.media.Faces`, Optional
        An optional faces object, if the rename task is being called by another job.
        Default: ``None``
    """
      def __init__(self, alignments, arguments, faces=None):
          logger.debug("Initializing %s: (arguments: %s, faces: %s)",
                       self.__class__.__name__, arguments, faces)
          self.alignments = alignments
          self.faces = faces if faces else Faces(arguments.faces_dir)
keep keep keep keep replace keep keep keep keep keep
 tools.alignments - Rewrite rename job to be more robust 
tools/lib_alignments/jobs.py