diff --git a/src/upscaler.py b/src/upscaler.py
index a2b463f..0bb07c6 100755
--- a/src/upscaler.py
+++ b/src/upscaler.py
@@ -4,7 +4,7 @@
 Name: Video2X Upscaler
 Author: K4YT3X
 Date Created: December 10, 2018
-Last Modified: June 5, 2020
+Last Modified: June 7, 2020
 
 Description: This file contains the Upscaler class. Each
 instance of the Upscaler class is an upscaler on an image or
@@ -86,7 +86,9 @@ class Upscaler:
         self.scale_ratio = None
         self.processes = 1
         self.video2x_cache_directory = pathlib.Path(tempfile.gettempdir()) / 'video2x'
-        self.image_format = 'png'
+        self.extracted_frame_format = 'png'
+        self.image_output_extension = '.png'
+        self.video_output_extension = '.mp4'
         self.preserve_frames = False
 
         # other internal members and signals
@@ -234,7 +236,7 @@ class Upscaler:
         if self.driver == 'waifu2x_caffe':
             if (driver_settings['scale_width'] != 0 and driver_settings['scale_height'] == 0 or
                     driver_settings['scale_width'] == 0 and driver_settings['scale_height'] != 0):
-                Avalon.error('Only one of scale_width and scale_height is specified for waifu2x-caffe')
+                Avalon.error(_('Only one of scale_width and scale_height is specified for waifu2x-caffe'))
                 raise AttributeError('only one of scale_width and scale_height is specified for waifu2x-caffe')
 
             # if scale_width and scale_height are specified, ensure scale_ratio is None
@@ -324,8 +326,8 @@ class Upscaler:
         # images need to be renamed to be recognizable for FFmpeg
         if self.driver == 'waifu2x_converter_cpp':
             for image in [f for f in self.upscaled_frames.iterdir() if f.is_file()]:
-                renamed = re.sub(f'_\\[.*\\]\\[x(\\d+(\\.\\d+)?)\\]\\.{self.image_format}',
-                                 f'.{self.image_format}',
+                renamed = re.sub(f'_\\[.*\\]\\[x(\\d+(\\.\\d+)?)\\]\\.{self.extracted_frame_format}',
+                                 f'.{self.extracted_frame_format}',
                                  str(image.name))
                 (self.upscaled_frames / image).rename(self.upscaled_frames / renamed)
 
@@ -403,49 +405,93 @@ class Upscaler:
         self.driver_object.load_configurations(self)
 
         # initialize FFmpeg object
-        self.ffmpeg_object = Ffmpeg(self.ffmpeg_settings, image_format=self.image_format)
+        self.ffmpeg_object = Ffmpeg(self.ffmpeg_settings, extracted_frame_format=self.extracted_frame_format)
 
         # define processing queue
         self.processing_queue = queue.Queue()
 
         Avalon.info(_('Loading files into processing queue'))
+        Avalon.debug_info(_('Input path(s): {}').format(self.input))
 
-        # if input is a list of files
-        if isinstance(self.input, list):
-
-            Avalon.info(_('Loading files from multiple paths'))
-            Avalon.debug_info(_('Input path(s): {}').format(self.input))
-
-            # make output directory if it doesn't exist
+        # make output directory if the input is a list or a directory
+        if isinstance(self.input, list) or self.input.is_dir():
             self.output.mkdir(parents=True, exist_ok=True)
 
-            for input_path in self.input:
+        input_files = []
 
-                if input_path.is_file():
-                    output_path = self.output / input_path.name
-                    self.processing_queue.put((input_path.absolute(), output_path.absolute()))
+        # if input is single directory
+        # put it in a list for compability with the following code
+        if not isinstance(self.input, list):
+            input_paths = [self.input]
+        else:
+            input_paths = self.input
 
-                elif input_path.is_dir():
-                    for input_path in [f for f in input_path.iterdir() if f.is_file()]:
-                        output_path = self.output / input_path.name
-                        self.processing_queue.put((input_path.absolute(), output_path.absolute()))
+        # flatten directories into file paths
+        for input_path in input_paths:
 
-        # if input specified is single file
-        elif self.input.is_file():
-            Avalon.info(_('Loading single file'))
-            Avalon.debug_info(_('Input path(s): {}').format(self.input))
-            self.processing_queue.put((self.input.absolute(), self.output.absolute()))
+            # if the input path is a single file
+            # add the file's path object to input_files
+            if input_path.is_file():
+                input_files.append(input_path)
 
-        # if input specified is a directory
-        elif self.input.is_dir():
+            # if the input path is a directory
+            # add all files under the directory into the input_files (non-recursive)
+            elif input_path.is_dir():
+                input_files.extend([f for f in input_path.iterdir() if f.is_file()])
 
-            Avalon.info(_('Loading files from directory'))
-            Avalon.debug_info(_('Input path(s): {}').format(self.input))
-            # make output directory if it doesn't exist
-            self.output.mkdir(parents=True, exist_ok=True)
-            for input_path in [f for f in self.input.iterdir() if f.is_file()]:
-                output_path = self.output / input_path.name
-                self.processing_queue.put((input_path.absolute(), output_path.absolute()))
+        output_paths = []
+
+        for input_path in input_files:
+
+            # get file type
+            # try python-magic if it's available
+            try:
+                input_file_mime_type = magic.from_file(str(input_path.absolute()), mime=True)
+                input_file_type = input_file_mime_type.split('/')[0]
+                input_file_subtype = input_file_mime_type.split('/')[1]
+            except Exception:
+                input_file_type = input_file_subtype = None
+
+            # in case python-magic fails to detect file type
+            # try guessing file mime type with mimetypes
+            if input_file_type not in ['image', 'video']:
+                input_file_mime_type = mimetypes.guess_type(input_path.name)[0]
+                input_file_type = input_file_mime_type.split('/')[0]
+                input_file_subtype = input_file_mime_type.split('/')[1]
+
+            # set default output file suffixes
+            # if image type is GIF, default output suffix is also .gif
+            if input_file_mime_type == 'image/gif':
+                output_path = self.output / (input_path.stem + '.gif')
+
+            elif input_file_type == 'image':
+                output_path = self.output / (input_path.stem + self.image_output_extension)
+
+            elif input_file_type == 'video':
+                output_path = self.output / (input_path.stem + self.video_output_extension)
+
+            # if file is none of: image, image/gif, video
+            # skip to the next task
+            else:
+                Avalon.error(_('File {} ({}) neither an image nor a video').format(input_path, input_file_mime_type))
+                Avalon.warning(_('Skipping this file'))
+                continue
+
+            # if there is only one input file
+            # do not modify output file suffix
+            if isinstance(self.input, pathlib.Path) and self.input.is_file():
+                output_path = self.output
+
+            output_path_id = 0
+            while str(output_path) in output_paths:
+                output_path = output_path.parent / pathlib.Path(f'{output_path.stem}_{output_path_id}{output_path.suffix}')
+                output_path_id += 1
+
+            # record output path
+            output_paths.append(str(output_path))
+
+            # push file information into processing queue
+            self.processing_queue.put((input_path.absolute(), output_path.absolute(), input_file_mime_type, input_file_type, input_file_subtype))
 
         # check argument sanity before running
         self._check_arguments()
@@ -461,27 +507,8 @@ class Upscaler:
         try:
             while not self.processing_queue.empty():
 
-                # reset current processing progress for new job
-                self.total_frames_upscaled = 0
-                self.total_frames = 0
-
                 # get new job from queue
-                self.current_input_file, output_path = self.processing_queue.get()
-
-                # get file type
-                try:
-                    input_file_mime_type = magic.from_file(str(self.current_input_file.absolute()), mime=True)
-                    input_file_type = input_file_mime_type.split('/')[0]
-                    input_file_subtype = input_file_mime_type.split('/')[1]
-                except Exception:
-                    input_file_type = input_file_subtype = None
-
-                # in case python-magic fails to detect file type
-                # try guessing file mime type with mimetypes
-                if input_file_type not in ['image', 'video']:
-                    input_file_mime_type = mimetypes.guess_type(self.current_input_file.name)[0]
-                    input_file_type = input_file_mime_type.split('/')[0]
-                    input_file_subtype = input_file_mime_type.split('/')[1]
+                self.current_input_file, output_path, input_file_mime_type, input_file_type, input_file_subtype = self.processing_queue.get()
 
                 # start handling input
                 # if input file is a static image
@@ -553,15 +580,6 @@ class Upscaler:
                     self._upscale_frames()
                     Avalon.info(_('Upscaling completed'))
 
-                # if file is none of: image, image/gif, video
-                # skip to the next task
-                else:
-                    Avalon.error(_('File {} ({}) neither an image nor a video').format(self.current_input_file, input_file_mime_type))
-                    Avalon.warning(_('Skipping this file'))
-                    self.processing_queue.task_done()
-                    self.total_processed += 1
-                    continue
-
                 # start handling output
                 # output can be either GIF or video
 
@@ -569,7 +587,7 @@ class Upscaler:
                 if output_path.suffix.lower() == '.gif':
                     Avalon.info(_('Converting extracted frames into GIF image'))
                     gifski_object = Gifski(self.gifski_settings)
-                    self.process_pool.append(gifski_object.make_gif(self.upscaled_frames, output_path, framerate, self.image_format))
+                    self.process_pool.append(gifski_object.make_gif(self.upscaled_frames, output_path, framerate, self.extracted_frame_format))
                     self._wait()
                     Avalon.info(_('Conversion completed'))
 
diff --git a/src/video2x.py b/src/video2x.py
index 7b14a8c..1c30a22 100755
--- a/src/video2x.py
+++ b/src/video2x.py
@@ -13,7 +13,7 @@ __      __  _       _                  ___   __   __
 Name: Video2X Controller
 Creator: K4YT3X
 Date Created: Feb 24, 2018
-Last Modified: June 4, 2020
+Last Modified: June 7, 2020
 
 Editor: BrianPetkovsek
 Last Modified: June 17, 2019
@@ -206,7 +206,9 @@ gifski_settings = config['gifski']
 gifski_settings['gifski_path'] = os.path.expandvars(gifski_settings['gifski_path'])
 
 # load video2x settings
-image_format = config['video2x']['image_format'].lower()
+extracted_frame_format = config['video2x']['extracted_frame_format'].lower()
+image_output_extension = config['video2x']['image_output_extension']
+video_output_extension = config['video2x']['video_output_extension']
 preserve_frames = config['video2x']['preserve_frames']
 
 # if preserve frames specified in command line
@@ -245,7 +247,9 @@ try:
     upscaler.scale_ratio = video2x_args.ratio
     upscaler.processes = video2x_args.processes
     upscaler.video2x_cache_directory = video2x_cache_directory
-    upscaler.image_format = image_format
+    upscaler.extracted_frame_format = extracted_frame_format
+    upscaler.image_output_extension = image_output_extension
+    upscaler.video_output_extension = video_output_extension
     upscaler.preserve_frames = preserve_frames
 
     # run upscaler
diff --git a/src/video2x.yaml b/src/video2x.yaml
index a2eebbe..527762d 100644
--- a/src/video2x.yaml
+++ b/src/video2x.yaml
@@ -169,5 +169,7 @@ gifski:
   quiet: false # Do not show a progress bar
 video2x:
   video2x_cache_directory: null # default: %TEMP%\video2x, directory where cache files are stored, will be deleted if preserve_frames is not set to true
-  image_format: png # png/jpg intermediate file format used for extracted frames during video processing
+  extracted_frame_format: png # png/jpg intermediate file format used for extracted frames during video processing
+  image_output_extension: .png # image output extension during batch processing
+  video_output_extension: .mp4 # video output extension during batch processing
   preserve_frames: false # if set to true, the cache directory won't be cleaned upon task completion
diff --git a/src/video2x_gui.py b/src/video2x_gui.py
index 63d4c6a..6de7ce3 100755
--- a/src/video2x_gui.py
+++ b/src/video2x_gui.py
@@ -4,7 +4,7 @@
 Creator: Video2X GUI
 Author: K4YT3X
 Date Created: May 5, 2020
-Last Modified: June 5, 2020
+Last Modified: June 7, 2020
 """
 
 # local imports
@@ -17,6 +17,7 @@ from wrappers.ffmpeg import Ffmpeg
 import contextlib
 import datetime
 import json
+import math
 import mimetypes
 import os
 import pathlib
@@ -277,6 +278,8 @@ class Video2XMainWindow(QMainWindow):
         self.driver_combo_box.currentTextChanged.connect(self.update_gui_for_driver)
         self.processes_spin_box = self.findChild(QSpinBox, 'processesSpinBox')
         self.scale_ratio_double_spin_box = self.findChild(QDoubleSpinBox, 'scaleRatioDoubleSpinBox')
+        self.image_output_extension_line_edit = self.findChild(QLineEdit, 'imageOutputExtensionLineEdit')
+        self.video_output_extension_line_edit = self.findChild(QLineEdit, 'videoOutputExtensionLineEdit')
         self.preserve_frames_check_box = self.findChild(QCheckBox, 'preserveFramesCheckBox')
 
         # frame preview
@@ -472,6 +475,9 @@ class Video2XMainWindow(QMainWindow):
             self.config['video2x']['video2x_cache_directory'] = str((pathlib.Path(tempfile.gettempdir()) / 'video2x').absolute())
         self.cache_line_edit.setText(self.config['video2x']['video2x_cache_directory'])
 
+        self.image_output_extension_line_edit.setText(self.config['video2x']['image_output_extension'])
+        self.video_output_extension_line_edit.setText(self.config['video2x']['video_output_extension'])
+
         # load preserve frames settings
         self.preserve_frames_check_box.setChecked(self.config['video2x']['preserve_frames'])
         self.start_button.setEnabled(True)
@@ -926,11 +932,11 @@ class Video2XMainWindow(QMainWindow):
 
                     # otherwise, use .png by default for all images
                     else:
-                        suffix = '.png'
+                        suffix = self.image_output_extension_line_edit.text()
 
                 # if input is video, use .mp4 as output by default
                 elif input_file_type == 'video':
-                    suffix = '.mp4'
+                    suffix = self.video_output_extension_line_edit.text()
 
                 # if failed to detect file type
                 # use input file's suffix
@@ -942,9 +948,9 @@ class Video2XMainWindow(QMainWindow):
             elif input_path.is_dir():
                 output_path = input_path.parent / f'{input_path.stem}_output'
 
-            # try up to 1000 times
+            # try a new name with a different file ID
             output_path_id = 0
-            while output_path.exists() and output_path_id <= 1000:
+            while output_path.exists():
                 if input_path.is_file():
                     output_path = input_path.parent / pathlib.Path(f'{input_path.stem}_output_{output_path_id}{suffix}')
                 elif input_path.is_dir():
@@ -1067,7 +1073,7 @@ It\'s also highly recommended for you to attach the [log file]({}) under the pro
 
         # upscale process will stop at 99%
         # so it's set to 100 manually when all is done
-        progress_callback.emit((upscale_begin_time, 0, 0, 0, 0, pathlib.Path(), pathlib.Path()))
+        # progress_callback.emit((upscale_begin_time, 0, 0, 0, 0, pathlib.Path(), pathlib.Path()))
 
     def set_progress(self, progress_information: tuple):
         upscale_begin_time = progress_information[0]
@@ -1177,7 +1183,9 @@ It\'s also highly recommended for you to attach the [log file]({}) under the pro
             self.upscaler.scale_ratio = self.scale_ratio_double_spin_box.value()
             self.upscaler.processes = self.processes_spin_box.value()
             self.upscaler.video2x_cache_directory = pathlib.Path(os.path.expandvars(self.cache_line_edit.text()))
-            self.upscaler.image_format = self.config['video2x']['image_format'].lower()
+            self.upscaler.extracted_frame_format = self.config['video2x']['extracted_frame_format'].lower()
+            self.upscaler.image_output_extension = self.image_output_extension_line_edit.text()
+            self.upscaler.video_output_extension = self.video_output_extension_line_edit.text()
             self.upscaler.preserve_frames = bool(self.preserve_frames_check_box.isChecked())
 
             # run upscaler
@@ -1200,6 +1208,10 @@ It\'s also highly recommended for you to attach the [log file]({}) under the pro
             self.upscale_errored(e)
 
     def upscale_errored(self, exception: Exception):
+        # send stop signal in case it's not triggered
+        with contextlib.suppress(AttributeError):
+            self.upscaler.running = False
+
         self.show_error(exception)
         self.threadpool.waitForDone(5)
         self.start_button.setEnabled(True)
diff --git a/src/video2x_gui.ui b/src/video2x_gui.ui
index ec6c551..5d54add 100644
--- a/src/video2x_gui.ui
+++ b/src/video2x_gui.ui
@@ -7,7 +7,7 @@
     0
     0
     673
-    844
+    876
    
   
   
@@ -407,6 +407,54 @@
                
               
              
+             - 
+              
+               - 
+                
+                 
+                  Image Output Extension
+                 
+                
+               +
- 
+                
+                 
+                  
+                   0
+                   0
+                  
+                 
+                 
+                  .png
+                 
+                
+               +              
+
+- 
+              
+               - 
+                
+                 
+                  Video Output Extension
+                 
+                
+               +
- 
+                
+                 
+                  
+                   0
+                   0
+                  
+                 
+                 
+                  .mp4
+                 
+                
+               +              
+
- 
               
                
@@ -448,6 +496,9 @@
                    
                     Keep Aspect Ratio
                    
+                   
+                    true
+                   
                   
                  
-