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
+
-