              "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."
                      "\nL|'draw': Draw landmarks on frames in the selected folder/video. A "
                      "subfolder will be created within the frames folder to hold the output." +
 
</s> remove             "choices": ("draw", "extract", "manual", "merge", "missing-alignments",
 </s> add             "choices": ("dfl", "draw", "extract", "manual", "merge", "missing-alignments", </s> remove         argument_list.append({"opts": ("-i", "--input-dir"),
                              "action": DirOrFileFullPaths,
                              "filetypes": "video",
                              "dest": "input_dir",
                              "required": True,
                              "group": "Data",
                              "help": "Input directory or video. Either a directory containing "
                                      "the image files you wish to process or path to a video "
                                      "file. NB: This should be the source video/frames NOT the "
                                      "source faces."})
        argument_list.append({"opts": ("-o", "--output-dir"),
                              "action": DirFullPaths,
                              "dest": "output_dir",
                              "required": True,
                              "group": "Data",
                              "help": "Output directory. This is where the converted files will "
                                      "be saved."})
        argument_list.append({"opts": ("-al", "--alignments"),
                              "action": FileFullPaths,
                              "filetypes": "alignments",
                              "type": str,
                              "dest": "alignments_path",
                              "group": "Data",
                              "help": "Optional path to an alignments file. Leave blank if the "
                                      "alignments file is at the default location."})
 </s> add         argument_list.append({
            "opts": ("-i", "--input-dir"),
            "action": DirOrFileFullPaths,
            "filetypes": "video",
            "dest": "input_dir",
            "required": True,
            "group": "Data",
            "help": "Input directory or video. Either a directory containing the image files you "
                    "wish to process or path to a video file. NB: This should be the source video/"
                    "frames NOT the source faces."})
        argument_list.append({
            "opts": ("-o", "--output-dir"),
            "action": DirFullPaths,
            "dest": "output_dir",
            "required": True,
            "group": "Data",
            "help": "Output directory. This is where the converted files will be saved."})
        argument_list.append({
            "opts": ("-al", "--alignments"),
            "action": FileFullPaths,
            "filetypes": "alignments",
            "type": str,
            "dest": "alignments_path",
            "group": "Data",
            "help": "Optional path to an alignments file. Leave blank if the alignments file is "
                    "at the default location."}) </s> remove         argument_list.append({"opts": ("-ref", "--reference-video"),
                              "action": FileFullPaths,
                              "dest": "reference_video",
                              "filetypes": "video",
                              "type": str,
                              "group": "data",
                              "help": "Only required if converting from images to video. Provide "
                                      "The original video that the source frames were extracted "
                                      "from (for extracting the fps and audio)."})
        argument_list.append({"opts": ("-m", "--model-dir"),
                              "action": DirFullPaths,
                              "dest": "model_dir",
                              "required": True,
                              "group": "data",
                              "help": "Model directory. The directory containing the trained "
                                      "model you wish to use for conversion."})
 </s> add         argument_list.append({
            "opts": ("-ref", "--reference-video"),
            "action": FileFullPaths,
            "dest": "reference_video",
            "filetypes": "video",
            "type": str,
            "group": "data",
            "help": "Only required if converting from images to video. Provide The original video "
                    "that the source frames were extracted from (for extracting the fps and "
                    "audio)."})
        argument_list.append({
            "opts": ("-m", "--model-dir"),
            "action": DirFullPaths,
            "dest": "model_dir",
            "required": True,
            "group": "data",
            "help": "Model directory. The directory containing the trained model you wish to use "
                    "for conversion."}) </s> remove         argument_list.append({"opts": ("-w", "--writer"),
                              "action": Radio,
                              "type": str,
                              "choices": PluginLoader.get_available_convert_plugins("writer",
                                                                                    False),
                              "group": "plugins",
                              "default": "opencv",
                              "help": "R|The plugin to use to output the converted images. The "
                                      "writers are configurable in '/config/convert.ini' or "
                                      "'Settings > Configure Convert Plugins:'"
                                      "\nL|ffmpeg: [video] Writes out the convert straight to "
                                      "video. When the input is a series of images then the "
                                      "'-ref' (--reference-video) parameter must be set."
                                      "\nL|gif: [animated image] Create an animated gif."
                                      "\nL|opencv: [images] The fastest image writer, but less "
                                      "options and formats than other plugins."
                                      "\nL|pillow: [images] Slower than opencv, but has more "
                                      "options and supports more formats."})
        argument_list.append({"opts": ("-osc", "--output-scale"),
                              "dest": "output_scale",
                              "action": Slider,
                              "type": int,
                              "default": 100,
                              "min_max": (25, 400),
                              "rounding": 1,
                              "group": "Frame Processing",
                              "help": "Scale the final output frames by this amount. 100%% will "
                                      "output the frames at source dimensions. 50%% at half size "
                                      "200%% at double size"})
        argument_list.append({"opts": ("-fr", "--frame-ranges"),
                              "nargs": "+",
                              "type": str,
                              "group": "Frame Processing",
                              "help": "Frame ranges to apply transfer to e.g. For frames 10 to 50 "
                                      "and 90 to 100 use --frame-ranges 10-50 90-100. Frames "
                                      "falling outside of the selected range will be discarded "
                                      "unless '-k' (--keep-unchanged) is selected. NB: If you are "
                                      "converting from images, then the filenames must end with "
                                      "the frame-number!"})
        argument_list.append({"opts": ("-a", "--input-aligned-dir"),
                              "action": DirFullPaths,
                              "dest": "input_aligned_dir",
                              "group": "Face Processing",
                              "default": None,
                              "help": "If you have not cleansed your alignments file, then you "
                                      "can filter out faces by defining a folder here that "
                                      "contains the faces extracted from your input files/video. "
                                      "If this folder is defined, then only faces that exist "
                                      "within your alignments file and also exist within the "
                                      "specified folder will be converted. Leaving this blank "
                                      "will convert all faces that exist within the alignments "
                                      "file."})
        argument_list.append({"opts": ("-n", "--nfilter"),
                              "action": FilesFullPaths,
                              "filetypes": "image",
                              "dest": "nfilter",
                              "nargs": "+",
                              "default": None,
                              "group": "Face Processing",
                              "help": "Optionally filter out people who you do not wish to "
                                      "process by passing in an image of that person. Should be a "
                                      "front portrait with a single person in the image. Multiple "
                                      "images can be added space separated. NB: Using face filter "
                                      "will significantly decrease extraction speed and its "
                                      "accuracy cannot be guaranteed."})
        argument_list.append({"opts": ("-f", "--filter"),
                              "action": FilesFullPaths,
                              "filetypes": "image",
                              "dest": "filter",
                              "nargs": "+",
                              "default": None,
                              "group": "Face Processing",
                              "help": "Optionally select people you wish to process by passing in "
                                      "an image of that person. Should be a front portrait with a "
                                      "single person in the image. Multiple images can be added "
                                      "space separated. NB: Using face filter will significantly "
                                      "decrease extraction speed and its accuracy cannot be "
                                      "guaranteed."})
        argument_list.append({"opts": ("-l", "--ref_threshold"),
                              "action": Slider,
                              "min_max": (0.01, 0.99),
                              "rounding": 2,
                              "type": float,
                              "dest": "ref_threshold",
                              "default": 0.4,
                              "group": "Face Processing",
                              "help": "For use with the optional nfilter/filter files. Threshold "
                                      "for positive face recognition. Lower values are stricter. "
                                      "NB: Using face filter will significantly decrease "
                                      "extraction speed and its accuracy cannot be "
                                      "guaranteed."})

        argument_list.append({"opts": ("-j", "--jobs"),
                              "dest": "jobs",
                              "action": Slider,
                              "group": "settings",
                              "type": int,
                              "default": 0,
                              "min_max": (0, 40),
                              "rounding": 1,
                              "help": "The maximum number of parallel processes for performing "
                                      "conversion. Converting images is system RAM heavy so it is "
                                      "possible to run out of memory if you have a lot of "
                                      "processes and not enough RAM to accommodate them all. "
                                      "Setting this to 0 will use the maximum available. No "
                                      "matter what you set this to, it will never attempt to use "
                                      "more processes than are available on your system. If "
                                      "singleprocess is enabled this setting will be ignored."})
        argument_list.append({"opts": ("-g", "--gpus"),
                              "type": int,
                              "backend": "nvidia",
                              "action": Slider,
                              "min_max": (1, 10),
                              "rounding": 1,
                              "group": "settings",
                              "default": 1,
                              "help": "Number of GPUs to use for conversion"})
        argument_list.append({"opts": ("-t", "--trainer"),
                              "type": str.lower,
                              "choices": PluginLoader.get_available_models(),
                              "group": "settings",
                              "help": "[LEGACY] This only needs to be selected if a legacy "
                                      "model is being loaded or if there are multiple models in "
                                      "the model folder"})
        argument_list.append({"opts": ("-k", "--keep-unchanged"),
                              "action": "store_true",
                              "dest": "keep_unchanged",
                              "group": "Frame Processing",
                              "default": False,
                              "help": "When used with --frame-ranges outputs the unchanged frames "
                                      "that are not processed instead of discarding them."})
        argument_list.append({"opts": ("-s", "--swap-model"),
                              "action": "store_true",
                              "dest": "swap_model",
                              "group": "settings",
                              "default": False,
                              "help": "Swap the model. Instead converting from of A -> B, "
                                      "converts B -> A"})
        argument_list.append({"opts": ("-sp", "--singleprocess"),
                              "action": "store_true",
                              "group": "settings",
                              "default": False,
                              "help": "Disable multiprocessing. Slower but less resource "
                                      "intensive."})
 </s> add         argument_list.append({
            "opts": ("-w", "--writer"),
            "action": Radio,
            "type": str,
            "choices": PluginLoader.get_available_convert_plugins("writer", False),
            "group": "plugins",
            "default": "opencv",
            "help": "R|The plugin to use to output the converted images. The writers are "
                    "configurable in '/config/convert.ini' or 'Settings > Configure Convert "
                    "Plugins:'"
                    "\nL|ffmpeg: [video] Writes out the convert straight to video. When the input "
                    "is a series of images then the '-ref' (--reference-video) parameter must be "
                    "set."
                    "\nL|gif: [animated image] Create an animated gif."
                    "\nL|opencv: [images] The fastest image writer, but less options and formats "
                    "than other plugins."
                    "\nL|pillow: [images] Slower than opencv, but has more options and supports "
                    "more formats."})
        argument_list.append({
            "opts": ("-osc", "--output-scale"),
            "dest": "output_scale",
            "action": Slider,
            "type": int,
            "default": 100,
            "min_max": (25, 400),
            "rounding": 1,
            "group": "Frame Processing",
            "help": "Scale the final output frames by this amount. 100%% will output the frames "
                    "at source dimensions. 50%% at half size 200%% at double size"})
        argument_list.append({
            "opts": ("-fr", "--frame-ranges"),
            "nargs": "+",
            "type": str,
            "group": "Frame Processing",
            "help": "Frame ranges to apply transfer to e.g. For frames 10 to 50 and 90 to 100 use "
                    "--frame-ranges 10-50 90-100. Frames falling outside of the selected range "
                    "will be discarded unless '-k' (--keep-unchanged) is selected. NB: If you are "
                    "converting from images, then the filenames must end with the frame-number!"})
        argument_list.append({
            "opts": ("-a", "--input-aligned-dir"),
            "action": DirFullPaths,
            "dest": "input_aligned_dir",
            "group": "Face Processing",
            "default": None,
            "help": "If you have not cleansed your alignments file, then you can filter out faces "
                    "by defining a folder here that contains the faces extracted from your input "
                    "files/video. If this folder is defined, then only faces that exist within "
                    "your alignments file and also exist within the specified folder will be "
                    "converted. Leaving this blank will convert all faces that exist within the "
                    "alignments file."})
        argument_list.append({
            "opts": ("-n", "--nfilter"),
            "action": FilesFullPaths,
            "filetypes": "image",
            "dest": "nfilter",
            "nargs": "+",
            "default": None,
            "group": "Face Processing",
            "help": "Optionally filter out people who you do not wish to process by passing in an "
                    "image of that person. Should be a front portrait with a single person in the "
                    "image. Multiple images can be added space separated. NB: Using face filter "
                    "will significantly decrease extraction speed and its accuracy cannot be "
                    "guaranteed."})
        argument_list.append({
            "opts": ("-f", "--filter"),
            "action": FilesFullPaths,
            "filetypes": "image",
            "dest": "filter",
            "nargs": "+",
            "default": None,
            "group": "Face Processing",
            "help": "Optionally select people you wish to process by passing in an image of that "
                    "person. Should be a front portrait with a single person in the image. "
                    "Multiple images can be added space separated. NB: Using face filter will "
                    "significantly decrease extraction speed and its accuracy cannot be "
                    "guaranteed."})
        argument_list.append({
            "opts": ("-l", "--ref_threshold"),
            "action": Slider,
            "min_max": (0.01, 0.99),
            "rounding": 2,
            "type": float,
            "dest": "ref_threshold",
            "default": 0.4,
            "group": "Face Processing",
            "help": "For use with the optional nfilter/filter files. Threshold for positive face "
                    "recognition. Lower values are stricter. NB: Using face filter will "
                    "significantly decrease extraction speed and its accuracy cannot be "
                    "guaranteed."})
        argument_list.append({
            "opts": ("-j", "--jobs"),
            "dest": "jobs",
            "action": Slider,
            "group": "settings",
            "type": int,
            "default": 0,
            "min_max": (0, 40),
            "rounding": 1,
            "help": "The maximum number of parallel processes for performing conversion. "
                    "Converting images is system RAM heavy so it is possible to run out of memory "
                    "if you have a lot of processes and not enough RAM to accommodate them all. "
                    "Setting this to 0 will use the maximum available. No matter what you set "
                    "this to, it will never attempt to use more processes than are available on "
                    "your system. If singleprocess is enabled this setting will be ignored."})
        argument_list.append({
            "opts": ("-g", "--gpus"),
            "type": int,
            "backend": "nvidia",
            "action": Slider,
            "min_max": (1, 10),
            "rounding": 1,
            "group": "settings",
            "default": 1,
            "help": "Number of GPUs to use for conversion"})
        argument_list.append({
            "opts": ("-t", "--trainer"),
            "type": str.lower,
            "choices": PluginLoader.get_available_models(),
            "group": "settings",
            "help": "[LEGACY] This only needs to be selected if a legacy model is being loaded or "
                    "if there are multiple models in the model folder"})
        argument_list.append({
            "opts": ("-k", "--keep-unchanged"),
            "action": "store_true",
            "dest": "keep_unchanged",
            "group": "Frame Processing",
            "default": False,
            "help": "When used with --frame-ranges outputs the unchanged frames that are not "
                    "processed instead of discarding them."})
        argument_list.append({
            "opts": ("-s", "--swap-model"),
            "action": "store_true",
            "dest": "swap_model",
            "group": "settings",
            "default": False,
            "help": "Swap the model. Instead converting from of A -> B, converts B -> A"})
        argument_list.append({
            "opts": ("-sp", "--singleprocess"),
            "action": "store_true",
            "group": "settings",
            "default": False,
            "help": "Disable multiprocessing. Slower but less resource intensive."})
                        "remove-faces", "remove-frames", "rename", "sort", "spatial",
                        "update-hashes"),
              "action": Radio,
              "type": str,
              "choices": ("draw", "extract", "manual", "merge", "missing-alignments",
                          "missing-frames", "leftover-faces", "multi-faces", "no-faces",
                        "remove-faces", "remove-frames", "rename", "sort", "spatial",
                        "update-hashes"),
                        "remove-faces", "remove-frames", "rename", "sort", "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."
                      "\nL|'draw': Draw landmarks on frames in the selected folder/video. A "
                      "subfolder will be created within the frames folder to hold the output." +
keep keep keep keep replace replace keep keep keep keep keep
 Smart Mask - Extract code review

- Lint simple_tests.py
- Only reformat alignments file if it exists otherwise change filename
- Update legacy alignments to new format at all stages
- faces_detect.Mask.from_dict - logging format fix
- convert.py fix otf for new pipeline
- cli.py - Add note that masks not used. Revert convert masks
- faces_detect.py - Revert non-extract code
- Add .p and .pickle extensions for serializer
- plugins/extract revert some changes
- scripts/fsmedia - Revert code changes
- Pipeline - cleanup
- Consistant alpha channel stripping (fixes single-process)
- Store landmarks as numpy array
- Code attribution
- Normalize feed face and reference face to 0.0 - 1.0 in convert
- Lock in mask VRAM sized
- Add documentation to plugin_loader
- Update alignments tool to work with new format 
tools/cli.py