mirror of
				https://github.com/k4yt3x/video2x.git
				synced 2025-10-31 12:50:59 +01:00 
			
		
		
		
	made output extensions in batch processing customizable
This commit is contained in:
		
							parent
							
								
									0b15fb7bd2
								
							
						
					
					
						commit
						7e87dac15e
					
				
							
								
								
									
										148
									
								
								src/upscaler.py
									
									
									
									
									
								
							
							
						
						
									
										148
									
								
								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')) | ||||
| 
 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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) | ||||
|  | ||||
| @ -7,7 +7,7 @@ | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>673</width> | ||||
|     <height>844</height> | ||||
|     <height>876</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|   <property name="acceptDrops"> | ||||
| @ -407,6 +407,54 @@ | ||||
|                </item> | ||||
|               </layout> | ||||
|              </item> | ||||
|              <item> | ||||
|               <layout class="QHBoxLayout" name="imageOutputExtensionHorizontalLayout"> | ||||
|                <item> | ||||
|                 <widget class="QLabel" name="imageOutputExtensionLabel"> | ||||
|                  <property name="text"> | ||||
|                   <string>Image Output Extension</string> | ||||
|                  </property> | ||||
|                 </widget> | ||||
|                </item> | ||||
|                <item> | ||||
|                 <widget class="QLineEdit" name="imageOutputExtensionLineEdit"> | ||||
|                  <property name="sizePolicy"> | ||||
|                   <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> | ||||
|                    <horstretch>0</horstretch> | ||||
|                    <verstretch>0</verstretch> | ||||
|                   </sizepolicy> | ||||
|                  </property> | ||||
|                  <property name="text"> | ||||
|                   <string>.png</string> | ||||
|                  </property> | ||||
|                 </widget> | ||||
|                </item> | ||||
|               </layout> | ||||
|              </item> | ||||
|              <item> | ||||
|               <layout class="QHBoxLayout" name="videoOutputExtensionHorizontalLayout"> | ||||
|                <item> | ||||
|                 <widget class="QLabel" name="videoOutputExtensionLabel"> | ||||
|                  <property name="text"> | ||||
|                   <string>Video Output Extension</string> | ||||
|                  </property> | ||||
|                 </widget> | ||||
|                </item> | ||||
|                <item> | ||||
|                 <widget class="QLineEdit" name="videoOutputExtensionLineEdit"> | ||||
|                  <property name="sizePolicy"> | ||||
|                   <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> | ||||
|                    <horstretch>0</horstretch> | ||||
|                    <verstretch>0</verstretch> | ||||
|                   </sizepolicy> | ||||
|                  </property> | ||||
|                  <property name="text"> | ||||
|                   <string>.mp4</string> | ||||
|                  </property> | ||||
|                 </widget> | ||||
|                </item> | ||||
|               </layout> | ||||
|              </item> | ||||
|              <item> | ||||
|               <widget class="QCheckBox" name="preserveFramesCheckBox"> | ||||
|                <property name="toolTip"> | ||||
| @ -448,6 +496,9 @@ | ||||
|                    <property name="text"> | ||||
|                     <string>Keep Aspect Ratio</string> | ||||
|                    </property> | ||||
|                    <property name="checked"> | ||||
|                     <bool>true</bool> | ||||
|                    </property> | ||||
|                   </widget> | ||||
|                  </item> | ||||
|                  <item> | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user