diff --git a/src/upscaler.py b/src/upscaler.py index af5157b..5e33b78 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: May 7, 2020 +Last Modified: May 8, 2020 Description: This file contains the Upscaler class. Each instance of the Upscaler class is an upscaler on an image or @@ -30,9 +30,7 @@ import queue import re import shutil import subprocess -import sys import tempfile -import threading import time import traceback @@ -71,8 +69,8 @@ class Upscaler: def __init__(self, input_path, output_path, driver_settings, ffmpeg_settings): # mandatory arguments - self.input_path = input_path - self.output_path = output_path + self.input = input_path + self.output = output_path self.driver_settings = driver_settings self.ffmpeg_settings = ffmpeg_settings @@ -134,20 +132,30 @@ class Upscaler: traceback.print_exc() def _check_arguments(self): + if isinstance(self.input, list): + if self.output.exists() and not self.output.is_dir(): + Avalon.error(_('Input and output path type mismatch')) + Avalon.error(_('Input is multiple files but output is not directory')) + raise ArgumentError('input output path type mismatch') + for input_path in self.input: + if not input_path.is_file() and not input_path.is_dir(): + Avalon.error(_('Input path {} is neither a file nor a directory'.format(input_path))) + raise FileNotFoundError(f'{input_path} is neither file nor directory') + # if input is a file - if self.input_path.is_file(): - if self.output_path.is_dir(): + elif self.input.is_file(): + if self.output.is_dir(): Avalon.error(_('Input and output path type mismatch')) Avalon.error(_('Input is single file but output is directory')) raise ArgumentError('input output path type mismatch') - if not re.search(r'.*\..*$', str(self.output_path)): + if not re.search(r'.*\..*$', str(self.output)): Avalon.error(_('No suffix found in output file path')) Avalon.error(_('Suffix must be specified for FFmpeg')) raise ArgumentError('no output video suffix specified') # if input is a directory - elif self.input_path.is_dir(): - if self.output_path.is_file(): + elif self.input.is_dir(): + if self.output.is_file(): Avalon.error(_('Input and output path type mismatch')) Avalon.error(_('Input is directory but output is existing single file')) raise ArgumentError('input output path type mismatch') @@ -155,7 +163,7 @@ class Upscaler: # if input is neither else: Avalon.error(_('Input path is neither a file nor a directory')) - raise FileNotFoundError(f'{self.input_path} is neither file nor directory') + raise FileNotFoundError(f'{self.input} is neither file nor directory') # check Fmpeg settings ffmpeg_path = pathlib.Path(self.ffmpeg_settings['ffmpeg_path']) @@ -259,32 +267,32 @@ class Upscaler: # if the driver being used is waifu2x-caffe if self.driver == 'waifu2x_caffe': self.process_pool.append(driver.upscale(process_directory, - self.upscaled_frames, - self.scale_ratio, - self.scale_width, - self.scale_height, - self.image_format, - self.bit_depth)) + self.upscaled_frames, + self.scale_ratio, + self.scale_width, + self.scale_height, + self.image_format, + self.bit_depth)) # if the driver being used is waifu2x-converter-cpp elif self.driver == 'waifu2x_converter_cpp': self.process_pool.append(driver.upscale(process_directory, - self.upscaled_frames, - self.scale_ratio, - self.processes, - self.image_format)) + self.upscaled_frames, + self.scale_ratio, + self.processes, + self.image_format)) # if the driver being used is waifu2x-ncnn-vulkan elif self.driver == 'waifu2x_ncnn_vulkan': self.process_pool.append(driver.upscale(process_directory, - self.upscaled_frames, - self.scale_ratio)) + self.upscaled_frames, + self.scale_ratio)) # if the driver being used is srmd_ncnn_vulkan elif self.driver == 'srmd_ncnn_vulkan': self.process_pool.append(driver.upscale(process_directory, - self.upscaled_frames, - self.scale_ratio)) + self.upscaled_frames, + self.scale_ratio)) # start progress bar in a different thread Avalon.debug_info(_('Starting progress monitor')) @@ -390,18 +398,34 @@ class Upscaler: # define processing queue processing_queue = queue.Queue() + # if input is a list of files + if isinstance(self.input, list): + # make output directory if it doesn't exist + self.output.mkdir(parents=True, exist_ok=True) + + for input_path in self.input: + + if input_path.is_file(): + output_video = self.output / input_path.name + processing_queue.put((input_path.absolute(), output_video.absolute())) + + elif input_path.is_dir(): + for input_video in [f for f in input_path.iterdir() if f.is_file()]: + output_video = self.output / input_video.name + processing_queue.put((input_video.absolute(), output_video.absolute())) + # if input specified is single file - if self.input_path.is_file(): - Avalon.info(_('Upscaling single video file: {}').format(self.input_path)) - processing_queue.put((self.input_path.absolute(), self.output_path.absolute())) + elif self.input.is_file(): + Avalon.info(_('Upscaling single video file: {}').format(self.input)) + processing_queue.put((self.input.absolute(), self.output.absolute())) # if input specified is a directory - elif self.input_path.is_dir(): + elif self.input.is_dir(): # make output directory if it doesn't exist - self.output_path.mkdir(parents=True, exist_ok=True) - for input_video in [f for f in self.input_path.iterdir() if f.is_file()]: - output_video = self.output_path / input_video.name + self.output.mkdir(parents=True, exist_ok=True) + for input_video in [f for f in self.input.iterdir() if f.is_file()]: + output_video = self.output / input_video.name processing_queue.put((input_video.absolute(), output_video.absolute())) while not processing_queue.empty(): diff --git a/src/video2x_gui.py b/src/video2x_gui.py index b5a3050..7592a53 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: May 7, 2020 +Last Modified: May 8, 2020 """ # local imports @@ -14,7 +14,6 @@ from upscaler import Upscaler import contextlib import os import pathlib -import re import sys import tempfile import time @@ -22,9 +21,10 @@ import traceback import yaml # third-party imports -from PyQt5 import QtWidgets, QtGui -from PyQt5 import uic -from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal, QRunnable, QThreadPool +from PyQt5 import QtGui, uic +from PyQt5.QtCore import * +from PyQt5.QtWidgets import * +# QObject, pyqtSlot, pyqtSignal, QRunnable, QThreadPool, QAbstractTableModel, Qt VERSION = '2.0.0' @@ -42,6 +42,7 @@ AVAILABLE_DRIVERS = { 'Anime4KCPP': 'anime4kcpp' } + def resource_path(relative_path: str) -> pathlib.Path: try: base_path = pathlib.Path(sys._MEIPASS) @@ -56,6 +57,7 @@ class WorkerSignals(QObject): interrupted = pyqtSignal() finished = pyqtSignal() + class ProgressBarWorker(QRunnable): def __init__(self, fn, *args, **kwargs): super(ProgressBarWorker, self).__init__() @@ -72,6 +74,7 @@ class ProgressBarWorker(QRunnable): except Exception: pass + class UpscalerWorker(QRunnable): def __init__(self, fn, *args, **kwargs): @@ -99,7 +102,50 @@ class UpscalerWorker(QRunnable): self.signals.finished.emit() -class Video2XMainWindow(QtWidgets.QMainWindow): +class InputTableModel(QAbstractTableModel): + def __init__(self, data): + super(InputTableModel, self).__init__() + self._data = data + + def data(self, index, role): + if role == Qt.DisplayRole: + + if index.column() == 0: + return str(self._data[index.row()].absolute()) + else: + if self._data[index.row()].is_file(): + return 'File' + elif self._data[index.row()].is_dir(): + return 'Folder' + else: + return 'Unknown' + + def rowCount(self, index): + return len(self._data) + + def columnCount(self, index): + return 2 + + def removeRow(self, index): + self._data.pop(index) + + def headerData(self, section, orientation, role): + # section is the index of the column/row. + if role != Qt.DisplayRole: + return None + + horizontal_headers = ['File Path', 'Type'] + + # return the correspondign header + if orientation == Qt.Horizontal: + return horizontal_headers[section] + + # simply return the line number + if orientation == Qt.Vertical: + return str(section) + + +class Video2XMainWindow(QMainWindow): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -114,131 +160,147 @@ class Video2XMainWindow(QtWidgets.QMainWindow): self.setWindowIcon(QtGui.QIcon(self.video2x_icon_path)) # menu bar - self.action_exit = self.findChild(QtWidgets.QAction, 'actionExit') + self.action_exit = self.findChild(QAction, 'actionExit') self.action_exit.triggered.connect(sys.exit) - self.action_about = self.findChild(QtWidgets.QAction, 'actionAbout') + self.action_about = self.findChild(QAction, 'actionAbout') self.action_about.triggered.connect(lambda: self.show_message(LEGAL_INFO, custom_icon=QtGui.QPixmap(self.video2x_icon_path))) # main tab # select input file/folder - self.input_line_edit = self.findChild(QtWidgets.QLineEdit, 'inputLineEdit') - self.enable_line_edit_file_drop(self.input_line_edit) - self.input_select_file_button = self.findChild(QtWidgets.QPushButton, 'inputSelectFileButton') - self.input_select_file_button.clicked.connect(self.select_input_file) - self.input_select_folder_button = self.findChild(QtWidgets.QPushButton, 'inputSelectFolderButton') - self.input_select_folder_button.clicked.connect(self.select_input_folder) + self.input_table_view = self.findChild(QTableView, 'inputTableView') + self.input_table_view.dragEnterEvent = self.dragEnterEvent + self.input_table_view.dropEvent = self.dropEvent + # initialize data in table + self.input_table_data = [] + self.input_table_model = InputTableModel(self.input_table_data) + self.input_table_view.setModel(self.input_table_model) + # stretch file path and fill columns horizontally + self.input_table_view.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch) + + # input table buttons + self.input_select_file_button = self.findChild(QPushButton, 'inputSelectFileButton') + self.input_select_file_button.clicked.connect(self.select_input_file) + self.input_select_folder_button = self.findChild(QPushButton, 'inputSelectFolderButton') + self.input_select_folder_button.clicked.connect(self.select_input_folder) + self.input_delete_selected_button = self.findChild(QPushButton, 'inputDeleteSelectedButton') + self.input_delete_selected_button.clicked.connect(self.input_table_delete_selected) + self.input_clear_all_button = self.findChild(QPushButton, 'inputClearAllButton') + self.input_clear_all_button.clicked.connect(self.input_table_clear_all) + + # other paths selection # select output file/folder - self.output_line_edit = self.findChild(QtWidgets.QLineEdit, 'outputLineEdit') + self.output_line_edit = self.findChild(QLineEdit, 'outputLineEdit') self.enable_line_edit_file_drop(self.output_line_edit) - self.output_select_file_button = self.findChild(QtWidgets.QPushButton, 'outputSelectFileButton') + self.output_line_edit.setText(str((pathlib.Path().cwd() / 'output').absolute())) + self.output_select_file_button = self.findChild(QPushButton, 'outputSelectFileButton') self.output_select_file_button.clicked.connect(self.select_output_file) - self.output_select_folder_button = self.findChild(QtWidgets.QPushButton, 'outputSelectFolderButton') + self.output_select_folder_button = self.findChild(QPushButton, 'outputSelectFolderButton') self.output_select_folder_button.clicked.connect(self.select_output_folder) # config file - self.config_line_edit = self.findChild(QtWidgets.QLineEdit, 'configLineEdit') + self.config_line_edit = self.findChild(QLineEdit, 'configLineEdit') self.enable_line_edit_file_drop(self.config_line_edit) self.config_line_edit.setText(str((pathlib.Path(__file__).parent / 'video2x.yaml').absolute())) - self.config_select_file_button = self.findChild(QtWidgets.QPushButton, 'configSelectButton') + self.config_select_file_button = self.findChild(QPushButton, 'configSelectButton') self.config_select_file_button.clicked.connect(self.select_config_file) # cache directory - self.cache_line_edit = self.findChild(QtWidgets.QLineEdit, 'cacheLineEdit') + self.cache_line_edit = self.findChild(QLineEdit, 'cacheLineEdit') self.enable_line_edit_file_drop(self.cache_line_edit) - self.cache_select_folder_button = self.findChild(QtWidgets.QPushButton, 'cacheSelectFolderButton') + self.cache_select_folder_button = self.findChild(QPushButton, 'cacheSelectFolderButton') self.cache_select_folder_button.clicked.connect(self.select_cache_folder) # express settings - self.driver_combo_box = self.findChild(QtWidgets.QComboBox, 'driverComboBox') + self.driver_combo_box = self.findChild(QComboBox, 'driverComboBox') self.driver_combo_box.currentTextChanged.connect(self.update_gui_for_driver) - self.processes_spin_box = self.findChild(QtWidgets.QSpinBox, 'processesSpinBox') - self.scale_ratio_double_spin_box = self.findChild(QtWidgets.QDoubleSpinBox, 'scaleRatioDoubleSpinBox') - self.preserve_frames_check_box = self.findChild(QtWidgets.QCheckBox, 'preserveFramesCheckBox') + self.processes_spin_box = self.findChild(QSpinBox, 'processesSpinBox') + self.scale_ratio_double_spin_box = self.findChild(QDoubleSpinBox, 'scaleRatioDoubleSpinBox') + self.preserve_frames_check_box = self.findChild(QCheckBox, 'preserveFramesCheckBox') # progress bar and start/stop controls - self.progress_bar = self.findChild(QtWidgets.QProgressBar, 'progressBar') - self.time_elapsed_label = self.findChild(QtWidgets.QLabel, 'timeElapsedLabel') - self.time_remaining_label = self.findChild(QtWidgets.QLabel, 'timeRemainingLabel') - self.rate_label = self.findChild(QtWidgets.QLabel, 'rateLabel') - self.start_button = self.findChild(QtWidgets.QPushButton, 'startButton') + self.progress_bar = self.findChild(QProgressBar, 'progressBar') + self.time_elapsed_label = self.findChild(QLabel, 'timeElapsedLabel') + self.time_remaining_label = self.findChild(QLabel, 'timeRemainingLabel') + self.rate_label = self.findChild(QLabel, 'rateLabel') + self.start_button = self.findChild(QPushButton, 'startButton') self.start_button.clicked.connect(self.start) - self.stop_button = self.findChild(QtWidgets.QPushButton, 'stopButton') + self.stop_button = self.findChild(QPushButton, 'stopButton') self.stop_button.clicked.connect(self.stop) # driver settings # waifu2x-caffe - self.waifu2x_caffe_path_line_edit = self.findChild(QtWidgets.QLineEdit, 'waifu2xCaffePathLineEdit') + self.waifu2x_caffe_path_line_edit = self.findChild(QLineEdit, 'waifu2xCaffePathLineEdit') self.enable_line_edit_file_drop(self.waifu2x_caffe_path_line_edit) - self.waifu2x_caffe_path_select_button = self.findChild(QtWidgets.QPushButton, 'waifu2xCaffePathSelectButton') + self.waifu2x_caffe_path_select_button = self.findChild(QPushButton, 'waifu2xCaffePathSelectButton') self.waifu2x_caffe_path_select_button.clicked.connect(lambda: self.select_driver_binary_path(self.waifu2x_caffe_path_line_edit)) - self.waifu2x_caffe_mode_combo_box = self.findChild(QtWidgets.QComboBox, 'waifu2xCaffeModeComboBox') - self.waifu2x_caffe_noise_level_spin_box = self.findChild(QtWidgets.QSpinBox, 'waifu2xCaffeNoiseLevelSpinBox') - self.waifu2x_caffe_process_combo_box = self.findChild(QtWidgets.QComboBox, 'waifu2xCaffeProcessComboBox') - self.waifu2x_caffe_model_combobox = self.findChild(QtWidgets.QComboBox, 'waifu2xCaffeModelComboBox') - self.waifu2x_caffe_crop_size_spin_box = self.findChild(QtWidgets.QSpinBox, 'waifu2xCaffeCropSizeSpinBox') - self.waifu2x_caffe_output_quality_spin_box = self.findChild(QtWidgets.QSpinBox, 'waifu2xCaffeOutputQualitySpinBox') - self.waifu2x_caffe_output_depth_spin_box = self.findChild(QtWidgets.QSpinBox, 'waifu2xCaffeOutputDepthSpinBox') - self.waifu2x_caffe_batch_size_spin_box = self.findChild(QtWidgets.QSpinBox, 'waifu2xCaffeBatchSizeSpinBox') - self.waifu2x_caffe_gpu_spin_box = self.findChild(QtWidgets.QSpinBox, 'waifu2xCaffeGpuSpinBox') - self.waifu2x_caffe_tta_check_box = self.findChild(QtWidgets.QCheckBox, 'waifu2xCaffeTtaCheckBox') + self.waifu2x_caffe_mode_combo_box = self.findChild(QComboBox, 'waifu2xCaffeModeComboBox') + self.waifu2x_caffe_noise_level_spin_box = self.findChild(QSpinBox, 'waifu2xCaffeNoiseLevelSpinBox') + self.waifu2x_caffe_process_combo_box = self.findChild(QComboBox, 'waifu2xCaffeProcessComboBox') + self.waifu2x_caffe_model_combobox = self.findChild(QComboBox, 'waifu2xCaffeModelComboBox') + self.waifu2x_caffe_crop_size_spin_box = self.findChild(QSpinBox, 'waifu2xCaffeCropSizeSpinBox') + self.waifu2x_caffe_output_quality_spin_box = self.findChild(QSpinBox, 'waifu2xCaffeOutputQualitySpinBox') + self.waifu2x_caffe_output_depth_spin_box = self.findChild(QSpinBox, 'waifu2xCaffeOutputDepthSpinBox') + self.waifu2x_caffe_batch_size_spin_box = self.findChild(QSpinBox, 'waifu2xCaffeBatchSizeSpinBox') + self.waifu2x_caffe_gpu_spin_box = self.findChild(QSpinBox, 'waifu2xCaffeGpuSpinBox') + self.waifu2x_caffe_tta_check_box = self.findChild(QCheckBox, 'waifu2xCaffeTtaCheckBox') # waifu2x-converter-cpp - self.waifu2x_converter_cpp_path_line_edit = self.findChild(QtWidgets.QLineEdit, 'waifu2xConverterCppPathLineEdit') + self.waifu2x_converter_cpp_path_line_edit = self.findChild(QLineEdit, 'waifu2xConverterCppPathLineEdit') self.enable_line_edit_file_drop(self.waifu2x_converter_cpp_path_line_edit) - self.waifu2x_converter_cpp_path_edit_button = self.findChild(QtWidgets.QPushButton, 'waifu2xConverterCppPathSelectButton') + self.waifu2x_converter_cpp_path_edit_button = self.findChild(QPushButton, 'waifu2xConverterCppPathSelectButton') self.waifu2x_converter_cpp_path_edit_button.clicked.connect(lambda: self.select_driver_binary_path(self.waifu2x_converter_cpp_path_line_edit)) - self.waifu2x_converter_cpp_png_compression_spin_box = self.findChild(QtWidgets.QSpinBox, 'waifu2xConverterCppPngCompressionSpinBox') - self.waifu2x_converter_cpp_processor_spin_box = self.findChild(QtWidgets.QSpinBox, 'waifu2xConverterCppProcessorSpinBox') - self.waifu2x_converter_cpp_model_combo_box = self.findChild(QtWidgets.QComboBox, 'waifu2xConverterCppModelComboBox') - self.waifu2x_converter_cpp_mode_combo_box = self.findChild(QtWidgets.QComboBox, 'waifu2xConverterCppModeComboBox') - self.waifu2x_converter_cpp_disable_gpu_check_box = self.findChild(QtWidgets.QCheckBox, 'disableGpuCheckBox') - self.waifu2x_converter_cpp_tta_check_box = self.findChild(QtWidgets.QCheckBox, 'ttaCheckBox') + self.waifu2x_converter_cpp_png_compression_spin_box = self.findChild(QSpinBox, 'waifu2xConverterCppPngCompressionSpinBox') + self.waifu2x_converter_cpp_processor_spin_box = self.findChild(QSpinBox, 'waifu2xConverterCppProcessorSpinBox') + self.waifu2x_converter_cpp_model_combo_box = self.findChild(QComboBox, 'waifu2xConverterCppModelComboBox') + self.waifu2x_converter_cpp_mode_combo_box = self.findChild(QComboBox, 'waifu2xConverterCppModeComboBox') + self.waifu2x_converter_cpp_disable_gpu_check_box = self.findChild(QCheckBox, 'disableGpuCheckBox') + self.waifu2x_converter_cpp_tta_check_box = self.findChild(QCheckBox, 'ttaCheckBox') # waifu2x-ncnn-vulkan - self.waifu2x_ncnn_vulkan_path_line_edit = self.findChild(QtWidgets.QLineEdit, 'waifu2xNcnnVulkanPathLineEdit') + self.waifu2x_ncnn_vulkan_path_line_edit = self.findChild(QLineEdit, 'waifu2xNcnnVulkanPathLineEdit') self.enable_line_edit_file_drop(self.waifu2x_ncnn_vulkan_path_line_edit) - self.waifu2x_ncnn_vulkan_path_select_button = self.findChild(QtWidgets.QPushButton, 'waifu2xNcnnVulkanPathSelectButton') + self.waifu2x_ncnn_vulkan_path_select_button = self.findChild(QPushButton, 'waifu2xNcnnVulkanPathSelectButton') self.waifu2x_ncnn_vulkan_path_select_button.clicked.connect(lambda: self.select_driver_binary_path(self.waifu2x_ncnn_vulkan_path_line_edit)) - self.waifu2x_ncnn_vulkan_noise_level_spin_box = self.findChild(QtWidgets.QSpinBox, 'waifu2xNcnnVulkanNoiseLevelSpinBox') - self.waifu2x_ncnn_vulkan_tile_size_spin_box = self.findChild(QtWidgets.QSpinBox, 'waifu2xNcnnVulkanTileSizeSpinBox') - self.waifu2x_ncnn_vulkan_model_combo_box = self.findChild(QtWidgets.QComboBox, 'waifu2xNcnnVulkanModelComboBox') - self.waifu2x_ncnn_vulkan_gpu_id_spin_box = self.findChild(QtWidgets.QSpinBox, 'waifu2xNcnnVulkanGpuIdSpinBox') - self.waifu2x_ncnn_vulkan_jobs_line_edit = self.findChild(QtWidgets.QLineEdit, 'waifu2xNcnnVulkanJobsLineEdit') - self.waifu2x_ncnn_vulkan_tta_check_box = self.findChild(QtWidgets.QCheckBox, 'waifu2xNcnnVulkanTtaCheckBox') + self.waifu2x_ncnn_vulkan_noise_level_spin_box = self.findChild(QSpinBox, 'waifu2xNcnnVulkanNoiseLevelSpinBox') + self.waifu2x_ncnn_vulkan_tile_size_spin_box = self.findChild(QSpinBox, 'waifu2xNcnnVulkanTileSizeSpinBox') + self.waifu2x_ncnn_vulkan_model_combo_box = self.findChild(QComboBox, 'waifu2xNcnnVulkanModelComboBox') + self.waifu2x_ncnn_vulkan_gpu_id_spin_box = self.findChild(QSpinBox, 'waifu2xNcnnVulkanGpuIdSpinBox') + self.waifu2x_ncnn_vulkan_jobs_line_edit = self.findChild(QLineEdit, 'waifu2xNcnnVulkanJobsLineEdit') + self.waifu2x_ncnn_vulkan_tta_check_box = self.findChild(QCheckBox, 'waifu2xNcnnVulkanTtaCheckBox') # srmd-ncnn-vulkan - self.srmd_ncnn_vulkan_path_line_edit = self.findChild(QtWidgets.QLineEdit, 'srmdNcnnVulkanPathLineEdit') + self.srmd_ncnn_vulkan_path_line_edit = self.findChild(QLineEdit, 'srmdNcnnVulkanPathLineEdit') self.enable_line_edit_file_drop(self.srmd_ncnn_vulkan_path_line_edit) - self.srmd_ncnn_vulkan_path_select_button = self.findChild(QtWidgets.QPushButton, 'srmdNcnnVulkanPathSelectButton') + self.srmd_ncnn_vulkan_path_select_button = self.findChild(QPushButton, 'srmdNcnnVulkanPathSelectButton') self.srmd_ncnn_vulkan_path_select_button.clicked.connect(lambda: self.select_driver_binary_path(self.srmd_ncnn_vulkan_path_line_edit)) - self.srmd_ncnn_vulkan_noise_level_spin_box = self.findChild(QtWidgets.QSpinBox, 'srmdNcnnVulkanNoiseLevelSpinBox') - self.srmd_ncnn_vulkan_tile_size_spin_box = self.findChild(QtWidgets.QSpinBox, 'srmdNcnnVulkanTileSizeSpinBox') - self.srmd_ncnn_vulkan_model_combo_box = self.findChild(QtWidgets.QComboBox, 'srmdNcnnVulkanModelComboBox') - self.srmd_ncnn_vulkan_gpu_id_spin_box = self.findChild(QtWidgets.QSpinBox, 'srmdNcnnVulkanGpuIdSpinBox') - self.srmd_ncnn_vulkan_jobs_line_edit = self.findChild(QtWidgets.QLineEdit, 'srmdNcnnVulkanJobsLineEdit') - self.srmd_ncnn_vulkan_tta_check_box = self.findChild(QtWidgets.QCheckBox, 'srmdNcnnVulkanTtaCheckBox') + self.srmd_ncnn_vulkan_noise_level_spin_box = self.findChild(QSpinBox, 'srmdNcnnVulkanNoiseLevelSpinBox') + self.srmd_ncnn_vulkan_tile_size_spin_box = self.findChild(QSpinBox, 'srmdNcnnVulkanTileSizeSpinBox') + self.srmd_ncnn_vulkan_model_combo_box = self.findChild(QComboBox, 'srmdNcnnVulkanModelComboBox') + self.srmd_ncnn_vulkan_gpu_id_spin_box = self.findChild(QSpinBox, 'srmdNcnnVulkanGpuIdSpinBox') + self.srmd_ncnn_vulkan_jobs_line_edit = self.findChild(QLineEdit, 'srmdNcnnVulkanJobsLineEdit') + self.srmd_ncnn_vulkan_tta_check_box = self.findChild(QCheckBox, 'srmdNcnnVulkanTtaCheckBox') # anime4k - self.anime4kcpp_path_line_edit = self.findChild(QtWidgets.QLineEdit, 'anime4kCppPathLineEdit') + self.anime4kcpp_path_line_edit = self.findChild(QLineEdit, 'anime4kCppPathLineEdit') self.enable_line_edit_file_drop(self.anime4kcpp_path_line_edit) - self.anime4kcpp_path_select_button = self.findChild(QtWidgets.QPushButton, 'anime4kCppPathSelectButton') + self.anime4kcpp_path_select_button = self.findChild(QPushButton, 'anime4kCppPathSelectButton') self.anime4kcpp_path_select_button.clicked.connect(lambda: self.select_driver_binary_path(self.anime4kcpp_path_line_edit)) - self.anime4kcpp_passes_spin_box = self.findChild(QtWidgets.QSpinBox, 'anime4kCppPassesSpinBox') - self.anime4kcpp_push_color_count_spin_box = self.findChild(QtWidgets.QSpinBox, 'anime4kCppPushColorCountSpinBox') - self.anime4kcpp_strength_color_spin_box = self.findChild(QtWidgets.QDoubleSpinBox, 'anime4kCppStrengthColorSpinBox') - self.anime4kcpp_strength_gradient_spin_box = self.findChild(QtWidgets.QDoubleSpinBox, 'anime4kCppStrengthGradientSpinBox') - self.anime4kcpp_threads_spin_box = self.findChild(QtWidgets.QSpinBox, 'anime4kCppThreadsSpinBox') - self.anime4kcpp_pre_filters_spin_box = self.findChild(QtWidgets.QSpinBox, 'anime4kCppPreFiltersSpinBox') - self.anime4kcpp_post_filters_spin_box = self.findChild(QtWidgets.QSpinBox, 'anime4kCppPostFiltersSpinBox') - self.anime4kcpp_platform_id_spin_box = self.findChild(QtWidgets.QSpinBox, 'anime4kCppPlatformIdSpinBox') - self.anime4kcpp_device_id_spin_box = self.findChild(QtWidgets.QSpinBox, 'anime4kCppDeviceIdSpinBox') - self.anime4kcpp_codec_combo_box = self.findChild(QtWidgets.QComboBox, 'anime4kCppCodecComboBox') - self.anime4kcpp_fast_mode_check_box = self.findChild(QtWidgets.QCheckBox, 'anime4kCppFastModeCheckBox') - self.anime4kcpp_pre_processing_check_box = self.findChild(QtWidgets.QCheckBox, 'anime4kCppPreProcessingCheckBox') - self.anime4kcpp_post_processing_check_box = self.findChild(QtWidgets.QCheckBox, 'anime4kCppPostProcessingCheckBox') - self.anime4kcpp_gpu_mode_check_box = self.findChild(QtWidgets.QCheckBox, 'anime4kCppGpuModeCheckBox') + self.anime4kcpp_passes_spin_box = self.findChild(QSpinBox, 'anime4kCppPassesSpinBox') + self.anime4kcpp_push_color_count_spin_box = self.findChild(QSpinBox, 'anime4kCppPushColorCountSpinBox') + self.anime4kcpp_strength_color_spin_box = self.findChild(QDoubleSpinBox, 'anime4kCppStrengthColorSpinBox') + self.anime4kcpp_strength_gradient_spin_box = self.findChild(QDoubleSpinBox, 'anime4kCppStrengthGradientSpinBox') + self.anime4kcpp_threads_spin_box = self.findChild(QSpinBox, 'anime4kCppThreadsSpinBox') + self.anime4kcpp_pre_filters_spin_box = self.findChild(QSpinBox, 'anime4kCppPreFiltersSpinBox') + self.anime4kcpp_post_filters_spin_box = self.findChild(QSpinBox, 'anime4kCppPostFiltersSpinBox') + self.anime4kcpp_platform_id_spin_box = self.findChild(QSpinBox, 'anime4kCppPlatformIdSpinBox') + self.anime4kcpp_device_id_spin_box = self.findChild(QSpinBox, 'anime4kCppDeviceIdSpinBox') + self.anime4kcpp_codec_combo_box = self.findChild(QComboBox, 'anime4kCppCodecComboBox') + self.anime4kcpp_fast_mode_check_box = self.findChild(QCheckBox, 'anime4kCppFastModeCheckBox') + self.anime4kcpp_pre_processing_check_box = self.findChild(QCheckBox, 'anime4kCppPreProcessingCheckBox') + self.anime4kcpp_post_processing_check_box = self.findChild(QCheckBox, 'anime4kCppPostProcessingCheckBox') + self.anime4kcpp_gpu_mode_check_box = self.findChild(QCheckBox, 'anime4kCppGpuModeCheckBox') # load configurations self.load_configurations() @@ -250,11 +312,15 @@ class Video2XMainWindow(QtWidgets.QMainWindow): event.ignore() def dropEvent(self, event): - input_path = pathlib.Path(event.mimeData().urls()[0].toLocalFile()) - self.input_line_edit.setText(str(input_path.absolute())) - self.generate_output_path(input_path) + input_paths = [pathlib.Path(u.toLocalFile()) for u in event.mimeData().urls()] + for path in input_paths: + if (path.is_file() or path.is_dir()) and not self.input_table_path_exists(path): + self.input_table_data.append(path) - def enable_line_edit_file_drop(self, line_edit: QtWidgets.QLineEdit): + self.update_input_table() + self.update_output_path() + + def enable_line_edit_file_drop(self, line_edit: QLineEdit): line_edit.dragEnterEvent = self.dragEnterEvent line_edit.dropEvent = lambda event: line_edit.setText(str(pathlib.Path(event.mimeData().urls()[0].toLocalFile()).absolute())) @@ -279,7 +345,7 @@ class Video2XMainWindow(QtWidgets.QMainWindow): # if file doesn't exist, return if not config_file_path.is_file(): - QtWidgets.QErrorMessage(self).showMessage('Video2X configuration file not found, please specify manually.') + QErrorMessage(self).showMessage('Video2X configuration file not found, please specify manually.') return # read configuration dict from config file @@ -438,19 +504,50 @@ class Video2XMainWindow(QtWidgets.QMainWindow): else: self.processes_spin_box.setValue(1) + def update_input_table(self): + # HACK: use insertRow, removeRow and signals + del self.input_table_model + self.input_table_model = InputTableModel(self.input_table_data) + self.input_table_view.setModel(self.input_table_model) + + def input_table_delete_selected(self): + items_to_delete = [] + for index in [i.row() for i in self.input_table_view.selectedIndexes()]: + items_to_delete.append(self.input_table_data[index]) + + for item in items_to_delete: + self.input_table_data.remove(item) + self.update_input_table() + + def input_table_clear_all(self): + self.input_table_data = [] + self.update_input_table() + + def input_table_path_exists(self, input_path): + for path in self.input_table_data: + # not using Path.samefile since file may not exist + if str(path.absolute()) == str(input_path.absolute()): + return True + return False + def select_file(self, *args, **kwargs) -> pathlib.Path: - file_selected = QtWidgets.QFileDialog.getOpenFileName(self, *args, **kwargs) + file_selected = QFileDialog.getOpenFileName(self, *args, **kwargs) if not isinstance(file_selected, tuple) or file_selected[0] == '': return None return pathlib.Path(file_selected[0]) def select_folder(self, *args, **kwargs) -> pathlib.Path: - folder_selected = QtWidgets.QFileDialog.getExistingDirectory(self, *args, **kwargs) + folder_selected = QFileDialog.getExistingDirectory(self, *args, **kwargs) if folder_selected == '': return None return pathlib.Path(folder_selected) - def generate_output_path(self, input_path: pathlib.Path): + def update_output_path(self): + # if there is more than one input + if len(self.input_table_data) != 1: + return + + input_path = self.input_table_data[0] # give up if input path doesn't exist or isn't a file or a directory if not input_path.exists() or not (input_path.is_file() or input_path.is_dir()): return @@ -466,23 +563,27 @@ class Video2XMainWindow(QtWidgets.QMainWindow): if input_path.is_file(): output_path = input_path.parent / pathlib.Path(f'{input_path.stem}_output_{output_path_id}.mp4') elif input_path.is_dir(): - output_path = input_path.parent / pathlib.Path(f'{input_path.stem}_output_{output_file_id}') + output_path = input_path.parent / pathlib.Path(f'{input_path.stem}_output_{output_path_id}') output_path_id += 1 if not output_path.exists(): self.output_line_edit.setText(str(output_path.absolute())) def select_input_file(self): - if (input_file := self.select_file('Select Input File')) is None: + if ((input_file := self.select_file('Select Input File')) is None or + self.input_table_path_exists(input_file)): return - self.generate_output_path(input_file) - self.input_line_edit.setText(str(input_file.absolute())) + self.input_table_data.append(input_file) + self.update_output_path() + self.update_input_table() def select_input_folder(self): - if (input_folder := self.select_folder('Select Input Folder')) is None: + if ((input_folder := self.select_folder('Select Input Folder')) is None or + self.input_table_path_exists(input_folder)): return - self.generate_output_path(input_folder) - self.input_line_edit.setText(str(input_folder.absolute())) + self.input_table_data.append(input_folder) + self.update_output_path() + self.update_input_table() def select_output_file(self): if (output_file := self.select_file('Select Output File')) is None: @@ -511,15 +612,15 @@ class Video2XMainWindow(QtWidgets.QMainWindow): driver_line_edit.setText(str(driver_binary_path.absolute())) def show_error(self, message: str): - QtWidgets.QErrorMessage(self).showMessage(message.replace('\n', '
')) + QErrorMessage(self).showMessage(message.replace('\n', '
')) def show_message(self, message: str, custom_icon=None): - message_box = QtWidgets.QMessageBox() + message_box = QMessageBox() message_box.setWindowTitle('Message') if custom_icon: message_box.setIconPixmap(custom_icon.scaled(64, 64)) else: - message_box.setIcon(QtWidgets.QMessageBox.Information) + message_box.setIcon(QMessageBox.Information) message_box.setText(message) message_box.exec_() @@ -542,9 +643,9 @@ class Video2XMainWindow(QtWidgets.QMainWindow): progress_percentage = 0 progress_callback.emit((progress_percentage, - self.upscaler.total_frames_upscaled, - self.upscaler.total_frames, - upscale_begin_time)) + self.upscaler.total_frames_upscaled, + self.upscaler.total_frames, + upscale_begin_time)) time.sleep(1) # upscale process will stop at 99% @@ -580,14 +681,17 @@ class Video2XMainWindow(QtWidgets.QMainWindow): self.begin_time = time.time() # resolve input and output directories from GUI - if self.input_line_edit.text().strip() == '': - self.show_error('Input path not specified') + if len(self.input_table_data) == 0: + self.show_error('Input path unspecified') return if self.output_line_edit.text().strip() == '': - self.show_error('Output path not specified') + self.show_error('Output path unspecified') return - input_directory = pathlib.Path(os.path.expandvars(self.input_line_edit.text())) + if len(self.input_table_data) == 1: + input_directory = self.input_table_data[0] + else: + input_directory = self.input_table_data output_directory = pathlib.Path(os.path.expandvars(self.output_line_edit.text())) # load driver settings from GUI @@ -658,7 +762,7 @@ class Video2XMainWindow(QtWidgets.QMainWindow): # this file shouldn't be imported if __name__ == '__main__': - app = QtWidgets.QApplication(sys.argv) + app = QApplication(sys.argv) window = Video2XMainWindow() window.show() app.exec_() diff --git a/src/video2x_gui.ui b/src/video2x_gui.ui index d61be73..b99a934 100644 --- a/src/video2x_gui.ui +++ b/src/video2x_gui.ui @@ -6,8 +6,8 @@ 0 0 - 691 - 503 + 718 + 668 @@ -16,7 +16,7 @@ Video2X GUI - + 5 @@ -79,154 +79,160 @@ - - - - - - - - 63 - 0 - - - - 1 - - - Input - - - false - - - 0 - - - - - - - - - - true - - - Select File - - - - - - - Select Folder - - - - - - - - - - - - 0 - 0 - - - - - 63 - 0 - - - - Output - - - - - - - - - - Select File - - - - - - - Select Folder - - - - - - - - - - - - 0 - 0 - - - - - 63 - 0 - - - - Config - - - - - - - - - - Select - - - - - - - - - - - - 63 - 0 - - - - Cache Folder - - - - - - - - - - Select Folder - - - - - - + + + Input Selection + + + + + + + + + + + true + + + Select File + + + + + + + Select Folder + + + + + + + Delete Selected + + + + + + + Clear All + + + + + + + + + + + + Other Paths Selection + + + + + + + + + 0 + 0 + + + + + 63 + 0 + + + + Output + + + + + + + + + + Select File + + + + + + + Select Folder + + + + + + + + + + + + 0 + 0 + + + + + 63 + 0 + + + + Config + + + + + + + + + + Select + + + + + + + + + + + + 63 + 0 + + + + Cache Folder + + + + + + + + + + Select Folder + + + + + + + @@ -1344,7 +1350,7 @@ 0 0 - 691 + 718 21