      @staticmethod
      def get_argument_list():
          """ Put the arguments in a list so that they are accessible from both
          argparse and gui """
          alignments_filetypes = [["Serializers", ['json', 'p', 'yaml']],
                                  ["JSON", ["json"]],
                                  ["Pickle", ["p"]],
                                  ["YAML", ["yaml"]]]
          alignments_filetypes = FileFullPaths.prep_filetypes(alignments_filetypes)
          argument_list = list()
          argument_list.append({"opts": ("-i", "--input-dir"),
                                "action": DirFullPaths,
                                "dest": "input_dir",
                                "default": "input",
 
</s> remove     @staticmethod
    def get_argument_list():
        log_filetypes = [["Serializers", ['json', 'yaml']],
                         ["JSON", ["json"]],
                         ["YAML", ["yaml"]]]
        log_filetypes = FileFullPaths.prep_filetypes(log_filetypes)

        arguments_list = list()
        arguments_list.append({"opts": ('-i', '--input'),
                               "action": DirFullPaths,
                               "dest": "input_dir",
                               "default": "input_dir",
                               "help": "Input directory of aligned faces.",
                               "required": True})

        arguments_list.append({"opts": ('-o', '--output'),
                               "action": DirFullPaths,
                               "dest": "output_dir",
                               "default": "_output_dir",
                               "help": "Output directory for sorted aligned "
                                       "faces."})

        arguments_list.append({"opts": ('-fp', '--final-process'),
                               "type": str,
                               "choices": ("folders", "rename"),
                               "dest": 'final_process',
                               "default": "rename",
                               "help": "'folders': files are sorted using the "
                                       "-s/--sort-by method, then they are "
                                       "organized into folders using the "
                                       "-g/--group-by grouping method. "
                                       "'rename': files are sorted using the "
                                       "-s/--sort-by then they are renamed. "
                                       "Default: rename"})

        arguments_list.append({"opts": ('-k', '--keep'),
                               "action": 'store_true',
                               "dest": 'keep_original',
                               "default": False,
                               "help": "Keeps the original files in the input "
                                       "directory. Be careful when using this "
                                       "with rename grouping and no specified "
                                       "output directory as this would keep "
                                       "the original and renamed files in the "
                                       "same directory."})

        arguments_list.append({"opts": ('-s', '--sort-by'),
                               "type": str,
                               "choices": ("blur", "face", "face-cnn",
                                           "face-cnn-dissim", "face-dissim",
                                           "face-yaw", "hist",
                                           "hist-dissim"),
                               "dest": 'sort_method',
                               "default": "hist",
                               "help": "Sort by method. "
                                       "Choose how images are sorted. "
                                       "Default: hist"})

        arguments_list.append({"opts": ('-g', '--group-by'),
                               "type": str,
                               "choices": ("blur", "face", "face-cnn",
                                           "face-yaw", "hist"),
                               "dest": 'group_method',
                               "default": "hist",
                               "help": "Group by method. "
                                       "When -fp/--final-processing by "
                                       "folders choose the how the images are "
                                       "grouped after sorting. "
                                       "Default: hist"})

        arguments_list.append({"opts": ('-t', '--ref_threshold'),
                               "type": float,
                               "dest": 'min_threshold',
                               "default": -1.0,
                               "help": "Float value. "
                                       "Minimum threshold to use for grouping "
                                       "comparison with 'face' and 'hist' "
                                       "methods. The lower the value the more "
                                       "discriminating the grouping is. "
                                       "Leaving -1.0 will make the program "
                                       "set the default value automatically. "
                                       "For face 0.6 should be enough, with "
                                       "0.5 being very discriminating. "
                                       "For face-cnn 7.2 should be enough, "
                                       "with 4 being very discriminating. "
                                       "For hist 0.3 should be enough, with "
                                       "0.2 being very discriminating. "
                                       "Be careful setting a value that's too "
                                       "low in a directory with many images, "
                                       "as this could result in a lot of "
                                       "directories being created. "
                                       "Defaults: face 0.6, face-cnn 7.2, "
                                       "hist 0.3"})

        arguments_list.append({"opts": ('-b', '--bins'),
                               "type": int,
                               "dest": 'num_bins',
                               "default": 5,
                               "help": "Integer value. "
                                       "Number of folders that will be used "
                                       "to group by blur and face-yaw. "
                                       "For blur folder 0 will be the least "
                                       "blurry, while the last folder will be "
                                       "the blurriest. "
                                       "For face-yaw the number of bins is by "
                                       "how much 180 degrees is divided. So "
                                       "if you use 18, then each folder will "
                                       "be a 10 degree increment. Folder 0 "
                                       "will contain faces looking the most "
                                       "to the left whereas the last folder "
                                       "will contain the faces looking the "
                                       "most to the right. "
                                       "If the number of images doesn't "
                                       "divide evenly into the number of "
                                       "bins, the remaining images get put in "
                                       "the last bin."
                                       "Default value: 5"})

        arguments_list.append({"opts": ('-l', '--log-changes'),
                               "action": 'store_true',
                               "dest": 'log_changes',
                               "default": False,
                               "help": "Logs file renaming changes if "
                                       "grouping by renaming, or it logs the "
                                       "file copying/movement if grouping by "
                                       "folders. If no log file is specified "
                                       "with '--log-file', then a "
                                       "'sort_log.json' file will be created "
                                       "in the input directory."})

        arguments_list.append({"opts": ('-lf', '--log-file'),
                               "action": FileFullPaths,
                               "filetypes": log_filetypes,
                               "dest": 'log_file_path',
                               "default": 'sort_log.json',
                               "help": "Specify a log file to use for saving "
                                       "the renaming or grouping information. "
                                       "If specified extension isn't 'json' "
                                       "or 'yaml', then json will be used as "
                                       "the serializer, with the supplied "
                                       "filename. "
                                       "Default: sort_log.json"})

        return arguments_list

    @staticmethod
    def get_optional_arguments():
        """
        Put the arguments in a list so that they are accessible from both
        argparse and gui.
        """
        # Override this for custom arguments
        argument_list = []
        return argument_list

    def parse_arguments(self, description, subparser, command):
        parser = subparser.add_parser(
                command,
                help="This command lets you sort images using various "
                     "methods.",
                description=description,
                epilog="Questions and feedback: \
                        https://github.com/deepfakes/faceswap-playground"
        )

        for option in self.argument_list:
            args = option['opts']
            kwargs = {key: option[key] for key in option.keys() if key != 'opts'}
            parser.add_argument(*args, **kwargs)

        parser = self.add_optional_arguments(parser)
        parser.set_defaults(func=self.process_arguments)

    def add_optional_arguments(self, parser):
        for option in self.optional_arguments:
            args = option['opts']
            kwargs = {key: option[key] for key in option.keys() if key != 'opts'}
            parser.add_argument(*args, **kwargs)
        return parser

    def process_arguments(self, arguments):
        self.args = arguments
 </s> add     def process(self): </s> remove         self.parse_arguments(description, subparser, command)
 </s> add  </s> remove         self.parse_arguments(description, subparser, command)

    @staticmethod
    def get_argument_list():
        vid_files = FileFullPaths.prep_filetypes([["Video Files",
                                                  DataItem.vid_ext]])
        arguments_list = list()
        arguments_list.append({"opts": ('-a', '--action'),
                               "dest": "action",
                               "choices": ("extract", "gen-vid", "get-fps",
                                           "get-info", "mux-audio", "rescale",
                                           "rotate", "slice"),
                               "default": "extract",
                               "help": """Choose which action you want ffmpeg 
                                          ffmpeg to do.
                                          'slice' cuts a portion of the video
                                          into a separate video file.
                                          'get-fps' returns the chosen video's
                                          fps."""})

        arguments_list.append({"opts": ('-i', '--input'),
                               "action": ComboFullPaths,
                               "dest": "input",
                               "default": "input",
                               "help": "Input file.",
                               "required": True,
                               "actions_open_type": {
                                   "task_name": "effmpeg",
                                   "extract": "load",
                                   "gen-vid": "folder",
                                   "get-fps": "load",
                                   "get-info": "load",
                                   "mux-audio": "load",
                                   "rescale": "load",
                                   "rotate": "load",
                                   "slice": "load",
                               },
                               "filetypes": {
                                   "extract": vid_files,
                                   "gen-vid": None,
                                   "get-fps": vid_files,
                                   "get-info": vid_files,
                                   "mux-audio": vid_files,
                                   "rescale": vid_files,
                                   "rotate": vid_files,
                                   "slice": vid_files
                               }})

        arguments_list.append({"opts": ('-o', '--output'),
                               "action": ComboFullPaths,
                               "dest": "output",
                               "default": "",
                               "help": """Output file. If no output is 
                                          specified then: if the output is  
                                          meant to be a video then a video 
                                          called 'out.mkv' will be created in 
                                          the input directory; if the output is
                                          meant to be a directory then a 
                                          directory called 'out' will be 
                                          created inside the input 
                                          directory.
                                          Note: the chosen output file 
                                          extension will determine the file
                                          encoding.""",
                               "actions_open_type": {
                                   "task_name": "effmpeg",
                                   "extract": "save",
                                   "gen-vid": "save",
                                   "get-fps": "nothing",
                                   "get-info": "nothing",
                                   "mux-audio": "save",
                                   "rescale": "save",
                                   "rotate": "save",
                                   "slice": "save"
                               },
                               "filetypes": {
                                   "extract": None,
                                   "gen-vid": vid_files,
                                   "get-fps": None,
                                   "get-info": None,
                                   "mux-audio": vid_files,
                                   "rescale": vid_files,
                                   "rotate": vid_files,
                                   "slice": vid_files
                               }})

        arguments_list.append({"opts": ('-r', '--reference-video'),
                               "action": ComboFullPaths,
                               "dest": "ref_vid",
                               "default": "None",
                               "help": """Path to reference video if 'input' 
                                          was not a video.""",
                               "actions_open_type": {
                                   "task_name": "effmpeg",
                                   "extract": "nothing",
                                   "gen-vid": "load",
                                   "get-fps": "nothing",
                                   "get-info": "nothing",
                                   "mux-audio": "load",
                                   "rescale": "nothing",
                                   "rotate": "nothing",
                                   "slice": "nothing"
                               },
                               "filetypes": {
                                   "extract": None,
                                   "gen-vid": vid_files,
                                   "get-fps": None,
                                   "get-info": None,
                                   "mux-audio": vid_files,
                                   "rescale": None,
                                   "rotate": None,
                                   "slice": None
                               }})

        arguments_list.append({"opts": ('-fps', '--fps'),
                               "type": str,
                               "dest": "fps",
                               "default": "-1.0",
                               "help": """Provide video fps. Can be an integer,
                                          float or fraction. Negative values 
                                          will make the program try to get the 
                                          fps from the input or reference 
                                          videos."""})

        arguments_list.append({"opts": ("-ef", "--extract-filetype"),
                               "choices": DataItem.img_ext,
                               "dest": "extract_ext",
                               "default": ".png",
                               "help": """Image format that extracted images
                                          should be saved as. '.bmp' will offer
                                          the fastest extraction speed, but
                                          will take the most storage space.
                                          '.png' will be slower but will take
                                          less storage."""})

        arguments_list.append({"opts": ('-s', '--start'),
                               "type": str,
                               "dest": "start",
                               "default": "00:00:00",
                               "help": """Enter the start time from which an 
                                          action is to be applied.
                                          Default: 00:00:00, in HH:MM:SS 
                                          format. You can also enter the time
                                          with or without the colons, e.g. 
                                          00:0000 or 026010."""})

        arguments_list.append({"opts": ('-e', '--end'),
                               "type": str,
                               "dest": "end",
                               "default": "00:00:00",
                               "help": """Enter the end time to which an action
                                          is to be applied. If both an end time
                                          and duration are set, then the end 
                                          time will be used and the duration 
                                          will be ignored.
                                          Default: 00:00:00, in HH:MM:SS."""})

        arguments_list.append({"opts": ('-d', '--duration'),
                               "type": str,
                               "dest": "duration",
                               "default": "00:00:00",
                               "help": """Enter the duration of the chosen
                                          action, for example if you enter
                                          00:00:10 for slice, then the first 10 
                                          seconds after and including the start
                                          time will be cut out into a new
                                          video.
                                          Default: 00:00:00, in HH:MM:SS 
                                          format. You can also enter the time
                                          with or without the colons, e.g. 
                                          00:0000 or 026010."""})

        arguments_list.append({"opts": ('-m', '--mux-audio'),
                               "action": "store_true",
                               "dest": "mux_audio",
                               "default": False,
                               "help": """Mux the audio from the reference 
                                          video into the input video. This
                                          option is only used for the 'gen-vid'
                                          action. 'mux-audio' action has this
                                          turned on implicitly."""})

        arguments_list.append({"opts": ('-tr', '--transpose'),
                               "choices": ("(0, 90CounterClockwise&VerticalFlip)",
                                           "(1, 90Clockwise)",
                                           "(2, 90CounterClockwise)",
                                           "(3, 90Clockwise&VerticalFlip)",
                                           "None"),
                               "type": lambda v: Effmpeg.__parse_transpose(v),
                               "dest": "transpose",
                               "default": "None",
                               "help": """Transpose the video. If transpose is 
                                          set, then degrees will be ignored. For
                                          cli you can enter either the number
                                          or the long command name, 
                                          e.g. to use (1, 90Clockwise)
                                          -tr 1 or -tr 90Clockwise"""})

        arguments_list.append({"opts": ('-de', '--degrees'),
                               "type": str,
                               "dest": "degrees",
                               "default": "None",
                               "help": """Rotate the video clockwise by the 
                                          given number of degrees."""})

        arguments_list.append({"opts": ('-sc', '--scale'),
                               "type": str,
                               "dest": "scale",
                               "default": "1920x1080",
                               "help": """Set the new resolution scale if the
                                          chosen action is 'rescale'."""})

        arguments_list.append({"opts": ('-q', '--quiet'),
                               "action": "store_true",
                               "dest": "quiet",
                               "default": False,
                               "help": """Reduces output verbosity so that only
                                          serious errors are printed. If both
                                          quiet and verbose are set, verbose
                                          will override quiet."""})

        arguments_list.append({"opts": ('-v', '--verbose'),
                               "action": "store_true",
                               "dest": "verbose",
                               "default": False,
                               "help": """Increases output verbosity. If both
                                          quiet and verbose are set, verbose
                                          will override quiet."""})

        return arguments_list

    def parse_arguments(self, description, subparser, command):
        parser = subparser.add_parser(
                command,
                help="This command lets you easily invoke"
                     "common ffmpeg commands.",
                description=description,
                epilog="Questions and feedback: \
                        https://github.com/deepfakes/faceswap-playground"
        )

        for option in self.argument_list:
            args = option['opts']
            kwargs = {key: option[key] for key in option.keys() if key != 'opts'}
            parser.add_argument(*args, **kwargs)

        parser.set_defaults(func=self.process_arguments)

    def process_arguments(self, arguments):
        self.args = arguments
 </s> add  </s> remove     @staticmethod
    def prep_filetypes(filetypes):
        all_files = ("All Files", "*.*")
        filetypes_l = list()
        for i in range(len(filetypes)):
            filetypes_l.append(FileFullPaths._process_filetypes(filetypes[i]))
        filetypes_l.append(all_files)
        return tuple(filetypes_l)

    @staticmethod
    def _process_filetypes(filetypes):
        """        """
        if filetypes is None:
            return None

        filetypes_name = filetypes[0]
        filetypes_l = filetypes[1]
        if (type(filetypes_l) == list or type(filetypes_l) == tuple) \
                and all("*." in i for i in filetypes_l):
            return filetypes  # assume filetypes properly formatted

        if type(filetypes_l) != list and type(filetypes_l) != tuple:
            raise ValueError("The filetypes extensions list was "
                             "neither a list nor a tuple: "
                             "{}".format(filetypes_l))

        filetypes_list = list()
        for i in range(len(filetypes_l)):
            filetype = filetypes_l[i].strip("*.")
            filetype = filetype.strip(';')
            filetypes_list.append("*." + filetype)
        return filetypes_name, filetypes_list

 </s> add  </s> remove class ConsoleOut(object):
    """ The Console out tab of the Display section """

    def __init__(self, frame, utils):
        self.frame = frame
        utils.console = tk.Text(self.frame)
        self.console = utils.console
        self.debug = utils.debugconsole

    def build_console(self):
        """ Build and place the console """
        self.console.config(width=100, height=6, bg='gray90', fg='black')
        self.console.pack(side=tk.LEFT, anchor=tk.N, fill=tk.BOTH, expand=True)

        scrollbar = ttk.Scrollbar(self.frame, command=self.console.yview)
        scrollbar.pack(side=tk.LEFT, fill='y')
        self.console.configure(yscrollcommand=scrollbar.set)

        if self.debug:
            print('Console debug activated. Outputting to main terminal')
        else:
            sys.stdout = SysOutRouter(console=self.console, out_type="stdout")
            sys.stderr = SysOutRouter(console=self.console, out_type="stderr")


class SysOutRouter(object):
    """ Route stdout/stderr to the console window """

    def __init__(self, console=None, out_type=None):
        self.console = console
        self.out_type = out_type
        self.color = ("black" if out_type == "stdout" else "red")

    def write(self, string):
        """ Capture stdout/stderr """
        self.console.insert(tk.END, string, self.out_type)
        self.console.tag_config(self.out_type, foreground=self.color)
        self.console.see(tk.END)

    @staticmethod
    def flush():
        """ If flush is forced, send it to normal terminal """
        sys.__stdout__.flush()


class CommandTab(object):
    """ Tabs to hold the command options """

    def __init__(self, utils, notebook, command):
        self.utils = utils
        self.notebook = notebook
        self.page = ttk.Frame(self.notebook)
        self.command = command
        self.title = command.title()

    def build_tab(self):
        """ Build the tab """
        actionframe = ActionFrame(self.utils, self.page, self.command)
        actionframe.build_frame()

        self.add_frame_separator()
        opts_frame = OptionsFrame(self.utils, self.page, self.command)
        opts_frame.build_frame()

        self.notebook.add(self.page, text=self.title)

    def add_frame_separator(self):
        """ Add a separator between left and right frames """
        sep = ttk.Frame(self.page, height=2, relief=tk.RIDGE)
        sep.pack(fill=tk.X, pady=(5, 0), side=tk.BOTTOM)


class OptionsFrame(object):
    """ Options Frame - Holds the Options for each command """

    def __init__(self, utils, page, command):
        self.utils = utils
        self.page = page
        self.command = command

        self.canvas = tk.Canvas(self.page, bd=0, highlightthickness=0)
        self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        self.optsframe = tk.Frame(self.canvas)
        self.optscanvas = self.canvas.create_window((0, 0), window=self.optsframe, anchor=tk.NW)

    def build_frame(self):
        """ Build the options frame for this command """
        self.add_scrollbar()
        self.canvas.bind('<Configure>', self.resize_frame)

        for option in self.utils.opts[self.command]:
            optioncontrol = OptionControl(self.utils, option, self.optsframe)
            optioncontrol.build_full_control()

    def add_scrollbar(self):
        """ Add a scrollbar to the options frame """
        scrollbar = ttk.Scrollbar(self.page, command=self.canvas.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.canvas.config(yscrollcommand=scrollbar.set)
        self.optsframe.bind("<Configure>", self.update_scrollbar)

    def update_scrollbar(self, event):
        """ Update the options frame scrollbar """
        self.canvas.configure(scrollregion=self.canvas.bbox('all'))

    def resize_frame(self, event):
        """ Resize the options frame to fit the canvas """
        canvas_width = event.width
        self.canvas.itemconfig(self.optscanvas, width=canvas_width)


class OptionControl(object):
    """ Build the correct control for the option parsed and place it on the
    frame """

    def __init__(self, utils, option, option_frame):
        self.utils = utils
        self.option = option
        self.option_frame = option_frame

    def build_full_control(self):
        """ Build the correct control type for the option passed through """
        ctl = self.option['control']
        ctltitle = self.option['control_title']
        sysbrowser = self.option['filesystem_browser']
        ctlhelp = ' '.join(self.option.get('help', '').split())
        ctlhelp = '. '.join(i.capitalize() for i in ctlhelp.split('. '))
        ctlhelp = ctltitle + ' - ' + ctlhelp
        ctlframe = self.build_one_control_frame()
        dflt = self.option.get('default', '')
        dflt = self.option.get('default', False) if ctl == ttk.Checkbutton else dflt
        choices = self.option['choices'] if ctl == ttk.Combobox else None

        self.build_one_control_label(ctlframe, ctltitle)
        self.option['value'] = self.build_one_control(ctlframe,
                                                      ctl,
                                                      dflt,
                                                      ctlhelp,
                                                      choices,
                                                      sysbrowser)

    def build_one_control_frame(self):
        """ Build the frame to hold the control """
        frame = ttk.Frame(self.option_frame)
        frame.pack(fill=tk.X, expand=True)
        return frame

    @staticmethod
    def build_one_control_label(frame, control_title):
        """ Build and place the control label """
        lbl = ttk.Label(frame, text=control_title, width=18, anchor=tk.W)
        lbl.pack(padx=5, pady=5, side=tk.LEFT, anchor=tk.N)

    def build_one_control(self, frame, control, default, helptext, choices,
                          sysbrowser):
        """ Build and place the option controls """
        default = default if default is not None else ''

        var = tk.BooleanVar(
            frame) if control == ttk.Checkbutton else tk.StringVar(frame)
        var.set(default)

        if sysbrowser is not None:
            # if sysbrowser in "load file":
            self.add_browser_buttons(frame, sysbrowser, var)
            # elif sysbrowser == "combo":
            #    self.add_browser_combo_button(frame, sysbrowser, var)

        ctlkwargs = {'variable': var} if control == ttk.Checkbutton else {
            'textvariable': var}
        packkwargs = {'anchor': tk.W} if control == ttk.Checkbutton else {
            'fill': tk.X, 'expand': True}
        ctl = control(frame, **ctlkwargs)

        if control == ttk.Combobox:
            ctl['values'] = [choice for choice in choices]

        ctl.pack(padx=5, pady=5, **packkwargs)
        Tooltip(ctl, text=helptext, wraplength=200)
        return var

    def add_browser_buttons(self, frame, sysbrowser, filepath):
        """ Add correct file browser button for control """
        if sysbrowser == "combo":
            img = self.utils.icons['load']
        else:
            img = self.utils.icons[sysbrowser]
        action = getattr(self, 'ask_' + sysbrowser)
        filetypes = self.option['filetypes']
        fileopn = ttk.Button(frame, image=img,
                             command=lambda cmd=action: cmd(filepath,
                                                            filetypes))
        fileopn.pack(padx=(0, 5), side=tk.RIGHT)

    @staticmethod
    def ask_folder(filepath, filetypes=None):
        """
        Pop-up to get path to a directory
        :param filepath: tkinter StringVar object that will store the path to a
        directory.
        :param filetypes: Unused argument to allow filetypes to be given in
        ask_load().
        """
        dirname = filedialog.askdirectory()
        if dirname:
            filepath.set(dirname)

    @staticmethod
    def ask_load(filepath, filetypes=None):
        """ Pop-up to get path to a file """
        if filetypes is None:
            filename = filedialog.askopenfilename()
        else:
            # In case filetypes were not configured properly in the
            # arguments_list
            try:
                filename = filedialog.askopenfilename(filetypes=filetypes)
            except TclError as te1:
                filetypes = FileFullPaths.prep_filetypes(filetypes)
                filename = filedialog.askopenfilename(filetypes=filetypes)
            except TclError as te2:
                filename = filedialog.askopenfilename()
        if filename:
            filepath.set(filename)

    @staticmethod
    def ask_save(filepath, filetypes=None):
        """ Pop-up to get path to save a new file """
        if filetypes is None:
            filename = filedialog.asksaveasfilename()
        else:
            # In case filetypes were not configured properly in the
            # arguments_list
            try:
                filename = filedialog.asksaveasfilename(filetypes=filetypes)
            except TclError as te1:
                filetypes = FileFullPaths.prep_filetypes(filetypes)
                filename = filedialog.asksaveasfilename(filetypes=filetypes)
            except TclError as te2:
                filename = filedialog.asksaveasfilename()
        if filename:
            filepath.set(filename)

    @staticmethod
    def ask_nothing(filepath, filetypes=None):
        """ Method that does nothing, used for disabling open/save pop up """
        return

    def ask_combo(self, filepath, filetypes):
        actions_open_type = self.option['actions_open_type']
        task_name = actions_open_type['task_name']
        chosen_action = self.utils.get_chosen_action(task_name)
        action = getattr(self, "ask_" + actions_open_type[chosen_action])
        filetypes = filetypes[chosen_action]
        action(filepath, filetypes)


class ActionFrame(object):
    """Action Frame - Displays information and action controls """

    def __init__(self, utils, page, command):
        self.utils = utils
        self.page = page
        self.command = command
        self.title = command.title()

    def build_frame(self):
        """ Add help display and Action buttons to the left frame of each
        page """
        frame = ttk.Frame(self.page)
        frame.pack(fill=tk.BOTH, padx=(10, 5), side=tk.BOTTOM, anchor=tk.N)

        self.add_action_button(frame)
        self.add_util_buttons(frame)

    def add_action_button(self, frame):
        """ Add the action buttons for page """
        actframe = ttk.Frame(frame)
        actframe.pack(fill=tk.X, side=tk.LEFT, padx=5, pady=5)

        btnact = ttk.Button(actframe,
                            text=self.title,
                            width=12,
                            command=lambda: self.utils.action_command(
                                self.command))
        btnact.pack(side=tk.TOP)
        Tooltip(btnact, text='Run the {} script'.format(self.title), wraplength=200)
        self.utils.actionbtns[self.command] = btnact

    def add_util_buttons(self, frame):
        """ Add the section utility buttons """
        utlframe = ttk.Frame(frame)
        utlframe.pack(side=tk.RIGHT, padx=(5, 10), pady=5)

        for utl in ('load', 'save', 'clear', 'reset'):
            img = self.utils.icons[utl]
            action = getattr(self.utils, utl + '_config')
            btnutl = ttk.Button(utlframe,
                                image=img,
                                command=lambda cmd=action: cmd(self.command))
            btnutl.pack(padx=2, side=tk.LEFT)
            Tooltip(btnutl, text=utl.capitalize() + ' ' + self.title + ' config', wraplength=200)


class DisplayTab(object):
    """ The display tabs """

    def __init__(self, utils, notebook, display):
        self.utils = utils
        self.notebook = notebook
        self.page = ttk.Frame(self.notebook)
        self.display = display
        self.title = self.display.title()

    def build_tab(self):
        """ Build the tab """
        frame = ttk.Frame(self.page)
        frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5)

        if self.display == 'graph':
            graphframe = GraphDisplay(frame, self.utils)
            graphframe.create_graphs()
        elif self.display == 'preview':
            preview = PreviewDisplay(frame, self.utils.previewloc)
            preview.update_preview()
        else:  # Dummy in a placeholder
            lbl = ttk.Label(frame, text=self.display, width=15, anchor=tk.NW)
            lbl.pack(padx=5, pady=5, side=tk.LEFT, anchor=tk.N)

        self.notebook.add(self.page, text=self.title)


class GraphDisplay(object):
    """ The Graph Tab of the Display section """

    def __init__(self, frame, utils):
        self.frame = frame
        self.utils = utils
        self.losskeys = None

        self.graphpane = tk.PanedWindow(self.frame, sashrelief=tk.RAISED, orient=tk.VERTICAL)
        self.graphpane.pack(fill=tk.BOTH, expand=True)

        self.graphs = list()

    def create_graphs(self):
        """ create the graph frames when there are loss values to graph """
        if not self.utils.lossdict:
            self.frame.after(1000, self.create_graphs)
            return

        self.losskeys = sorted([key for key in self.utils.lossdict.keys()])

        framecount = int(len(self.utils.lossdict) / 2)
        for i in range(framecount):
            self.add_graph(i)

        self.monitor_state()

    def add_graph(self, index):
        """ Add a single graph to the graph window """
        graphframe = ttk.Frame(self.graphpane)
        self.graphpane.add(graphframe)

        selectedkeys = self.losskeys[index * 2:(index + 1) * 2]
        selectedloss = {key: self.utils.lossdict[key] for key in selectedkeys}

        graph = Graph(graphframe, selectedloss, selectedkeys)
        self.graphs.append(graph)
        graph.build_graph()

    def monitor_state(self):
        """ Check there is a task still running. If not, destroy graphs
            and reset graph display to waiting state """
        if self.utils.lossdict:
            self.frame.after(5000, self.monitor_state)
            return
        self.destroy_graphs()
        self.create_graphs()

    def destroy_graphs(self):
        """ Destroy graphs when the process has stopped """
        for graph in self.graphs:
            del graph
        self.graphs = list()
        for child in self.graphpane.panes():
            self.graphpane.remove(child)


class Graph(object):
    """ Each graph to be displayed. Until training is run it is not known
        how many graphs will be required, so they sit in their own class
        ready to be created when requested """

    def __init__(self, frame, loss, losskeys):
        self.frame = frame
        self.loss = loss
        self.losskeys = losskeys

        self.ylim = (100, 0)

        style.use('ggplot')

        self.fig = plt.figure(figsize=(4, 4), dpi=75)
        self.ax1 = self.fig.add_subplot(1, 1, 1)
        self.losslines = list()
        self.trndlines = list()

    def build_graph(self):
        """ Update the plot area with loss values and cycle through to
        animate """
        self.ax1.set_xlabel('Iterations')
        self.ax1.set_ylabel('Loss')
        self.ax1.set_ylim(0.00, 0.01)
        self.ax1.set_xlim(0, 1)

        losslbls = [lbl.replace('_', ' ').title() for lbl in self.losskeys]
        for idx, linecol in enumerate(['blue', 'red']):
            self.losslines.extend(self.ax1.plot(0, 0,
                                                color=linecol,
                                                linewidth=1,
                                                label=losslbls[idx]))
        for idx, linecol in enumerate(['navy', 'firebrick']):
            lbl = losslbls[idx]
            lbl = 'Trend{}'.format(lbl[lbl.rfind(' '):])
            self.trndlines.extend(self.ax1.plot(0, 0,
                                                color=linecol,
                                                linewidth=2,
                                                label=lbl))

        self.ax1.legend(loc='upper right')

        plt.subplots_adjust(left=0.075, bottom=0.075, right=0.95, top=0.95,
                            wspace=0.2, hspace=0.2)

        plotcanvas = FigureCanvasTkAgg(self.fig, self.frame)
        plotcanvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)
        ani = animation.FuncAnimation(self.fig, self.animate, interval=2000, blit=False)
        plotcanvas.draw()

    def animate(self, i):
        """ Read loss data and apply to graph """
        loss = [self.loss[key][:] for key in self.losskeys]

        xlim = self.recalculate_axes(loss)

        xrng = [x for x in range(xlim)]

        self.raw_plot(xrng, loss)

        if xlim > 10:
            self.trend_plot(xrng, loss)

    def recalculate_axes(self, loss):
        """ Recalculate the latest x and y axes limits from latest data """
        ymin = floor(min([min(lossvals) for lossvals in loss]) * 100) / 100
        ymax = ceil(max([max(lossvals) for lossvals in loss]) * 100) / 100

        if ymin < self.ylim[0] or ymax > self.ylim[1]:
            self.ylim = (ymin, ymax)
            self.ax1.set_ylim(self.ylim[0], self.ylim[1])

        xlim = len(loss[0])
        xlim = 2 if xlim == 1 else xlim
        self.ax1.set_xlim(0, xlim - 1)

        return xlim

    def raw_plot(self, x_range, loss):
        """ Raw value plotting """
        for idx, lossvals in enumerate(loss):
            self.losslines[idx].set_data(x_range, lossvals)

    def trend_plot(self, x_range, loss):
        """ Trend value plotting """
        for idx, lossvals in enumerate(loss):
            fit = numpy.polyfit(x_range, lossvals, 3)
            poly = numpy.poly1d(fit)
            self.trndlines[idx].set_data(x_range, poly(x_range))


class PreviewDisplay(object):
    """ The Preview tab of the Display section """

    def __init__(self, frame, previewloc):
        self.frame = frame
        self.previewimg = None
        self.errcount = 0
        self.previewloc = previewloc

        self.previewlbl = ttk.Label(self.frame, image=None, anchor=tk.NW)
        self.previewlbl.pack(side=tk.TOP, fill=tk.BOTH, expand=True)

    def update_preview(self):
        """ Display the image if it exists or a place holder if it doesn't """
        self.load_preview()
        if self.previewimg is None:
            self.previewlbl.config(image=None)
        else:
            self.previewlbl.config(image=self.previewimg)
        self.previewlbl.after(1000, self.update_preview)

    def load_preview(self):
        """ Load the preview image into tk PhotoImage """
        if os.path.exists(self.previewloc):
            try:
                self.previewimg = tk.PhotoImage(file=self.previewloc)
                self.errcount = 0
            except TclError:
                # This is probably an error reading the file whilst it's
                # being saved
                # so ignore it for now and only pick up if there have been
                # multiple
                # consecutive fails
                if self.errcount < 10:
                    self.errcount += 1
                    self.previewimg = None
                else:
                    print('Error reading the preview file')
        else:
            self.previewimg = None


class FaceswapControl(object):
    """ Control the underlying Faceswap tasks """
    __group_processes = ["effmpeg"]

    def __init__(self, utils, calling_file="faceswap.py"):
        self.pathexecscript = os.path.join(PATHSCRIPT, calling_file)
        self.utils = utils

        self.command = None
        self.args = None
        self.process = None
        self.lenloss = 0

    def prepare(self, options, command):
        """ Prepare for running the subprocess """
        self.command = command
        self.utils.runningtask = True
        self.utils.change_action_button()
        self.utils.guitext['status'].set('Executing - ' + self.command + '.py')
        print('Loading...')
        self.args = ['python', '-u', self.pathexecscript, self.command]
        self.build_args(options)

    def build_args(self, options):
        """ Build the faceswap command and arguments list """
        for item in options[self.command]:
            optval = str(item.get('value', '').get())
            opt = item['opts'][0]
            if optval == 'False' or optval == '':
                continue
            elif optval == 'True':
                if self.command == 'train' and opt == '-p':  # Embed the preview pane
                    self.args.append('-gui')
                else:
                    self.args.append(opt)
            else:
                if item.get('nargs', None):
                    optval = optval.split(' ')
                    opt = [opt] + optval
                else:
                    opt = (opt, optval)
                self.args.extend(opt)

    def execute_script(self):
        """ Execute the requested Faceswap Script """
        kwargs = {'stdout': PIPE,
                  'stderr': PIPE,
                  'bufsize': 1,
                  'universal_newlines': True}
        if self.command in self.__group_processes:
            kwargs['preexec_fn'] = os.setsid
        if os.name == 'nt':
            kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP
        self.process = Popen(self.args, **kwargs)
        self.thread_stdout()
        self.thread_stderr()

    def read_stdout(self):
        """ Read stdout from the subprocess. If training, pass the loss
        values to Queue """
        while True:
            output = self.process.stdout.readline()
            if output == '' and self.process.poll() is not None:
                break
            if output:
                if self.command == 'train' and str.startswith(output, '['):
                    self.capture_loss(output)
                print(output.strip())
        returncode = self.process.poll()
        self.utils.runningtask = False
        self.utils.change_action_button()
        self.set_final_status(returncode)
        print('Process exited.')

    def read_stderr(self):
        """ Read stdout from the subprocess. If training, pass the loss
        values to Queue """
        while True:
            output = self.process.stderr.readline()
            if output == '' and self.process.poll() is not None:
                break
            print(output.strip(), file=sys.stderr)

    def thread_stdout(self):
        """ Put the subprocess stdout so that it can be read without
        blocking """
        thread = Thread(target=self.read_stdout)
        thread.daemon = True
        thread.start()

    def thread_stderr(self):
        """ Put the subprocess stderr so that it can be read without
        blocking """
        thread = Thread(target=self.read_stderr)
        thread.daemon = True
        thread.start()

    def capture_loss(self, string):
        """ Capture loss values from stdout """
        # TODO: Remove this hideous hacky fix. When the subprocess is terminated and
        # the loss dictionary is reset, 1 set of loss values ALWAYS slips through
        # and appends to the lossdict AFTER the subprocess has closed meaning that
        # checks on whether the dictionary is empty fail.
        # Therefore if the size of current loss dictionary is smaller than the
        # previous loss dictionary, assume that the process has been terminated
        # and reset it.
        # I have tried and failed to empty the subprocess stdout with:
        #   sys.exit() on the stdout/err threads (no effect)
        #   sys.stdout/stderr.flush (no effect)
        #   thread.join (locks the whole process up, because the stdout thread
        #   stubbornly refuses to release its last line)

        currentlenloss = len(self.utils.lossdict)
        if self.lenloss > currentlenloss:
            self.utils.lossdict = dict()
            self.lenloss = 0
            return
        self.lenloss = currentlenloss

        loss = re.findall(r'([a-zA-Z_]+):.*?(\d+\.\d+)', string)

        if len(loss) < 2:
            return

        if not self.utils.lossdict:
            self.utils.lossdict.update((item[0], []) for item in loss)

        for item in loss:
            self.utils.lossdict[item[0]].append(float(item[1]))

    def terminate(self):
        """ Terminate the subprocess """
        if self.command == 'train':
            print('Sending Exit Signal', flush=True)
            try:
                now = time()
                if os.name == 'nt':
                    os.kill(self.process.pid, signal.CTRL_BREAK_EVENT)
                else:
                    self.process.send_signal(signal.SIGINT)
                while True:
                    timeelapsed = time() - now
                    if self.process.poll() is not None:
                        break
                    if timeelapsed > 30:
                        raise ValueError('Timeout reached sending Exit Signal')
                return
            except ValueError as err:
                print(err)
        elif self.command in self.__group_processes:
            print('Terminating Process Group...')
            pgid = os.getpgid(self.process.pid)
            try:
                os.killpg(pgid, signal.SIGINT)
                self.process.wait(timeout=10)
                print('Terminated')
            except TimeoutExpired:
                print('Termination timed out. Killing Process Group...')
                os.killpg(pgid, signal.SIGKILL)
                print('Killed')
        else:
            print('Terminating Process...')
            try:
                self.process.terminate()
                self.process.wait(timeout=10)
                print('Terminated')
            except TimeoutExpired:
                print('Termination timed out. Killing Process...')
                self.process.kill()
                print('Killed')

    def set_final_status(self, returncode):
        """ Set the status bar output based on subprocess return code """
        if returncode == 0 or returncode == 3221225786:
            status = 'Ready'
        elif returncode == -15:
            status = 'Terminated - {}.py'.format(self.command)
        elif returncode == -9:
            status = 'Killed - {}.py'.format(self.command)
        elif returncode == -6:
            status = 'Aborted - {}.py'.format(self.command)
        else:
            status = 'Failed - {}.py. Return Code: {}'.format(self.command, returncode)
        self.utils.guitext['status'].set(status)


 </s> add 
      @staticmethod
      def get_argument_list():
          """ Put the arguments in a list so that they are accessible from both
          argparse and gui """





          argument_list = list()
          argument_list.append({"opts": ("-i", "--input-dir"),
                                "action": DirFullPaths,
                                "dest": "input_dir",
                                "default": "input",
keep keep keep keep replace replace replace replace replace keep keep keep keep keep
 Gui v3.0b (#436)

* GUI version 3 (#411)

GUI version 3.0a

* Required for Shaonlu mode (#416)

Added two modes - Original and Shaonlu.
The later requires this file to function.

* model update (#417)

New, functional Original 128 model

* OriginalHighRes 128 model update (#418)

Required for OriginalHighRes Model to function

* Add OriginalHighRes 128 update to gui branch (#421)

* Required for Shaonlu mode (#416)

Added two modes - Original and Shaonlu.
The later requires this file to function.

* model update (#417)

New, functional Original 128 model

* OriginalHighRes 128 model update (#418)

Required for OriginalHighRes Model to function

* Dev gui (#420)

* reduce singletons

* Fix tooltips and screen boundaries on popup

* Remove dpi fix. Fix context filebrowsers

* fix tools.py execution and context filebrowser bugs

* Bugfixes (#422)

* Bump matplotlib requirement. Fix polyfit. Fix TQDM on sort

* Fixed memory usage at 6GB cards. (#423)

- Switched default encoder to ORIGINAL
- Fixed memory consumption. Tested with geforce gtx 9800 ti with 6Gb; batch_size 8 no OOM or memory warnings now.

* Staging (#426)

* altered trainer (#425)

altered trainer to accommodate with model change

* Update Model.py (#424)

- Added saving state (currently only saved epoch number, to be extended in future)
- Changed saving to ThreadPoolExecutor

* Add DPI Scaling (#428)

* Add dpi scaling

* Hotfix for effmpeg. (#429)

effmpeg fixed so it works both in cli and gui.
Initial work done to add previewing feature to effmpeg (currently does nothing).
Some small spacing changes in other files to improve PEP8 conformity.

* PEP8 Linting (#430)

* pep8 linting

* Requirements version bump (#432)

* altered trainer (#425)

altered trainer to accommodate with model change

* Update Model.py (#424)

- Added saving state (currently only saved epoch number, to be extended in future)
- Changed saving to ThreadPoolExecutor

* Requirements version bump (#431)

This bumps the versions of:

    scandir
    h5py
    Keras
    opencv-python

to their latest vesions.

Virtual Environment will need to be setup again to make use of these.

* High DPI Fixes (#433)

* dpi scaling

* DPI Fixes

* Fix and improve context manager. (#434)

effmpeg tool:
Context manager for GUI fixed.

Context manager in general:
Functionality extended to allow configuring the context with both:
  command -> action
  command -> variable (cli argument) -> action

* Change epoch option to iterations

* Change epochs to iterations 
lib/cli.py