              "opts": ("-j", "--job"),
              "action": Radio,
              "type": str,
              "choices": ("draw", "extract", "manual", "merge", "missing-alignments",
                          "missing-frames", "legacy", "leftover-faces", "multi-faces", "no-faces",
                          "reformat", "remove-faces", "remove-frames", "rename", "sort-x", "sort-y",
                          "spatial", "update-hashes"),
              "required": True,
              "help": "R|Choose which action you want to perform. "
                      "NB: All actions require an alignments file (-a) to be passed in."
 
</s> remove         argument_list.append({"opts": ("-fmt", "--alignment_format"),
                              "type": str,
                              "choices": ("json", "pickle", "yaml"),
                              "group": "data",
                              "help": "The file format to save the alignment "
                                      "data in. Defaults to same as source."})
 </s> add  </s> remove     # < Rotation > #
    # The old rotation method would rotate the image to find a face, then
    # store the rotated landmarks along with a rotation value to tell the
    # convert process that it had to rotate the frame to find the landmarks.
    # This is problematic for numerous reasons. The process now rotates the
    # landmarks to correctly correspond with the original frame. The below are
    # functions to convert legacy alignments to the currently supported
    # infrastructure.
    # This can eventually be removed

    def get_legacy_rotation(self):
        """ Return a list of frames with legacy rotations
            Looks for an 'r' value in the alignments file that
            is not zero """
        logger.debug("Getting alignments containing legacy rotations")
        keys = list()
        for key, val in self.data.items():
            if any(alignment.get("r", None) for alignment in val):
                keys.append(key)
        logger.debug("Got alignments containing legacy rotations: %s", len(keys))
        return keys

    def rotate_existing_landmarks(self, frame_name, frame):
        """ Backwards compatability fix. Rotates the landmarks to
            their correct position and deletes r

            NB: The original frame must be passed in otherwise
            the transformation cannot be performed """
        logger.trace("Rotating existing landmarks for frame: '%s'", frame_name)
        dims = frame.shape[:2]
        for face in self.get_faces_in_frame(frame_name):
            angle = face.get("r", 0)
            if not angle:
                logger.trace("Landmarks do not require rotation: '%s'", frame_name)
                return
            logger.trace("Rotating landmarks: (frame: '%s', angle: %s)", frame_name, angle)
            r_mat = self.get_original_rotation_matrix(dims, angle)
            rotate_landmarks(face, r_mat)
            del face["r"]
        logger.trace("Rotatated existing landmarks for frame: '%s'", frame_name)
 </s> add  </s> remove     @staticmethod
    def get_serializer(filename):
        """ Set the serializer to be used for loading and
            saving alignments

            If a filename with a valid extension is passed in
            this will be used as the serializer, otherwise the
            compressed pickle will be used """
        logger.debug("Getting serializer: (filename: '%s')", filename)
        extension = os.path.splitext(filename)[1]
        if extension in (".json", ".p", ".yaml", ".yml"):
            logger.debug("Serializer set from filename extension: '%s'", extension)
            retval = get_serializer_from_filename(filename)
        else:
            logger.debug("Returning default Pickle serializer")
            retval = get_serializer("compressed")
        logger.verbose("Using '%s' serializer for alignments", retval.file_extension)
        return retval

 </s> add  </s> remove                     "\nL|'legacy': This updates legacy alignments to the latest format by "
                    "rotating the landmarks and bounding boxes and adding face_hashes." +
                    frames_and_faces_dir +
 </s> add  </s> remove     def check_legacy(self):
        """ Legacy rotated alignments will not have the correct x, y
            positions. Faces without hashes won't process.
            Check for these and generate a warning and exit """
        rotated = self.alignments.get_legacy_rotation()
        hashes = self.alignments.get_legacy_no_hashes()
        if rotated or hashes:
            logger.error("Legacy alignments found. Sort cannot continue. You should run legacy "
                         "tool to update the file prior to running sort: 'python tools.py "
                         "alignments -j legacy -a <alignments_file> -fr <frames_folder> -fc "
                         "<faces_folder>'")
            exit(0)

 </s> add 
                        "missing-frames", "leftover-faces", "multi-faces", "no-faces",
              "opts": ("-j", "--job"),
              "action": Radio,
              "type": str,
              "choices": ("draw", "extract", "manual", "merge", "missing-alignments",
                        "missing-frames", "leftover-faces", "multi-faces", "no-faces",
                          "reformat", "remove-faces", "remove-frames", "rename", "sort-x", "sort-y",
                          "spatial", "update-hashes"),
              "required": True,
              "help": "R|Choose which action you want to perform. "
                      "NB: All actions require an alignments file (-a) to be passed in."
keep keep keep keep replace keep keep keep keep keep
 Legacy alignments update

- Remove legacy update hashes
- Remove legacy job from alignment-tools
- Remove legacy landmark rotation
- Add rotate face method to plugins/extract/detect
- Update travis test for new alignments extension
- Alignments format to .fsa
- Remove serializer option from alignments-tool
- Auto update legacy format alignment files to new format 
tools/cli.py