mirror of
				https://github.com/k4yt3x/video2x.git
				synced 2025-10-31 04:40:59 +01:00 
			
		
		
		
	added image and GIF upscale support
This commit is contained in:
		
							parent
							
								
									5cf3271aad
								
							
						
					
					
						commit
						e305d0188e
					
				| @ -3,6 +3,8 @@ colorama | ||||
| patool | ||||
| psutil | ||||
| pyqt5 | ||||
| python-magic | ||||
| python-magic-bin | ||||
| pyunpack | ||||
| pyyaml | ||||
| requests | ||||
|  | ||||
							
								
								
									
										282
									
								
								src/upscaler.py
									
									
									
									
									
								
							
							
						
						
									
										282
									
								
								src/upscaler.py
									
									
									
									
									
								
							| @ -4,7 +4,7 @@ | ||||
| Name: Video2X Upscaler | ||||
| Author: K4YT3X | ||||
| Date Created: December 10, 2018 | ||||
| Last Modified: May 10, 2020 | ||||
| Last Modified: May 11, 2020 | ||||
| 
 | ||||
| Description: This file contains the Upscaler class. Each | ||||
| instance of the Upscaler class is an upscaler on an image or | ||||
| @ -16,6 +16,7 @@ from exceptions import * | ||||
| from image_cleaner import ImageCleaner | ||||
| from progress_monitor import ProgressMonitor | ||||
| from wrappers.ffmpeg import Ffmpeg | ||||
| from wrappers.gifski import Gifski | ||||
| 
 | ||||
| # built-in imports | ||||
| from fractions import Fraction | ||||
| @ -24,7 +25,6 @@ import copy | ||||
| import gettext | ||||
| import importlib | ||||
| import locale | ||||
| import os | ||||
| import pathlib | ||||
| import queue | ||||
| import re | ||||
| @ -36,6 +36,7 @@ import traceback | ||||
| 
 | ||||
| # third-party imports | ||||
| from avalon_framework import Avalon | ||||
| import magic | ||||
| 
 | ||||
| # internationalization constants | ||||
| DOMAIN = 'video2x' | ||||
| @ -67,12 +68,13 @@ class Upscaler: | ||||
|         ArgumentError -- if argument is not valid | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, input_path, output_path, driver_settings, ffmpeg_settings): | ||||
|     def __init__(self, input_path, output_path, driver_settings, ffmpeg_settings, gifski_settings): | ||||
|         # mandatory arguments | ||||
|         self.input = input_path | ||||
|         self.output = output_path | ||||
|         self.driver_settings = driver_settings | ||||
|         self.ffmpeg_settings = ffmpeg_settings | ||||
|         self.gifski_settings = gifski_settings | ||||
| 
 | ||||
|         # optional arguments | ||||
|         self.driver = 'waifu2x_caffe' | ||||
| @ -86,9 +88,9 @@ class Upscaler: | ||||
|         self.running = False | ||||
|         self.total_frames_upscaled = 0 | ||||
|         self.total_frames = 0 | ||||
|         self.total_videos = 0 | ||||
|         self.total_files = 0 | ||||
|         self.total_processed = 0 | ||||
|         self.current_input_video = pathlib.Path() | ||||
|         self.current_input_file = pathlib.Path() | ||||
|         self.last_frame_upscaled = pathlib.Path() | ||||
| 
 | ||||
|     def create_temp_directories(self): | ||||
| @ -154,10 +156,10 @@ class Upscaler: | ||||
|                 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)): | ||||
|             if self.output.suffix == '': | ||||
|                 Avalon.error(_('No suffix found in output file path')) | ||||
|                 Avalon.error(_('Suffix must be specified for FFmpeg')) | ||||
|                 raise ArgumentError('no output video suffix specified') | ||||
|                 Avalon.error(_('Suffix must be specified')) | ||||
|                 raise ArgumentError('no output file suffix specified') | ||||
| 
 | ||||
|         # if input is a directory | ||||
|         elif self.input.is_dir(): | ||||
| @ -238,6 +240,14 @@ class Upscaler: | ||||
|                 self.driver_settings['scale_width'] = None | ||||
|                 self.driver_settings['scale_height'] = None | ||||
| 
 | ||||
|         # temporary file type check for Anime4KCPP | ||||
|         # it doesn't support GIF processing yet | ||||
|         if self.driver == 'anime4kcpp': | ||||
|             for task in self.processing_queue.queue: | ||||
|                 if task[0].suffix.lower() == '.gif': | ||||
|                     Avalon.error(_('Anime4KCPP doesn\'t yet support GIF processing')) | ||||
|                     raise AttributeError('Anime4KCPP doesn\'t yet support GIF file processing') | ||||
| 
 | ||||
|     def _upscale_frames(self): | ||||
|         """ Upscale video frames with waifu2x-caffe | ||||
| 
 | ||||
| @ -393,9 +403,8 @@ class Upscaler: | ||||
|         # load options from upscaler class into driver settings | ||||
|         self.driver_object.load_configurations(self) | ||||
| 
 | ||||
|         # parse arguments for waifu2x | ||||
|         # check argument sanity | ||||
|         self._check_arguments() | ||||
|         # initialize FFmpeg object | ||||
|         self.ffmpeg_object = Ffmpeg(self.ffmpeg_settings, self.image_format) | ||||
| 
 | ||||
|         # define processing queue | ||||
|         self.processing_queue = queue.Queue() | ||||
| @ -408,17 +417,17 @@ class Upscaler: | ||||
|             for input_path in self.input: | ||||
| 
 | ||||
|                 if input_path.is_file(): | ||||
|                     output_video = self.output / input_path.name | ||||
|                     self.processing_queue.put((input_path.absolute(), output_video.absolute())) | ||||
|                     output_path = self.output / input_path.name | ||||
|                     self.processing_queue.put((input_path.absolute(), output_path.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 | ||||
|                         self.processing_queue.put((input_video.absolute(), output_video.absolute())) | ||||
|                     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())) | ||||
| 
 | ||||
|         # if input specified is single file | ||||
|         elif self.input.is_file(): | ||||
|             Avalon.info(_('Upscaling single video file: {}').format(self.input)) | ||||
|             Avalon.info(_('Upscaling single file: {}').format(self.input)) | ||||
|             self.processing_queue.put((self.input.absolute(), self.output.absolute())) | ||||
| 
 | ||||
|         # if input specified is a directory | ||||
| @ -426,99 +435,147 @@ class Upscaler: | ||||
| 
 | ||||
|             # make output directory if it doesn't exist | ||||
|             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 | ||||
|                 self.processing_queue.put((input_video.absolute(), output_video.absolute())) | ||||
|             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())) | ||||
| 
 | ||||
|         # record video count for external calls | ||||
|         self.total_videos = self.processing_queue.qsize() | ||||
|         # check argument sanity before running | ||||
|         self._check_arguments() | ||||
| 
 | ||||
|         while not self.processing_queue.empty(): | ||||
|             self.current_input_video, output_video = self.processing_queue.get() | ||||
|             # drivers that have native support for video processing | ||||
|             if self.driver == 'anime4kcpp': | ||||
|                 # append FFmpeg path to the end of PATH | ||||
|                 # Anime4KCPP will then use FFmpeg to migrate audio tracks | ||||
|                 os.environ['PATH'] += f';{self.ffmpeg_settings["ffmpeg_path"]}' | ||||
|                 Avalon.info(_('Starting to upscale extracted images')) | ||||
|         # record file count for external calls | ||||
|         self.total_files = self.processing_queue.qsize() | ||||
| 
 | ||||
|                 # run Anime4KCPP | ||||
|                 self.process_pool.append(self.driver_object.upscale(self.current_input_video, output_video)) | ||||
|                 self._wait() | ||||
|                 Avalon.info(_('Upscaling completed')) | ||||
|         try: | ||||
|             while not self.processing_queue.empty(): | ||||
| 
 | ||||
|             else: | ||||
|                 try: | ||||
|                     self.create_temp_directories() | ||||
|                 # reset current processing progress for new job | ||||
|                 self.total_frames_upscaled = 0 | ||||
|                 self.total_frames = 0 | ||||
| 
 | ||||
|                     # initialize objects for ffmpeg and waifu2x-caffe | ||||
|                     fm = Ffmpeg(self.ffmpeg_settings, self.image_format) | ||||
|                 # get new job from queue | ||||
|                 self.current_input_file, output_path = self.processing_queue.get() | ||||
| 
 | ||||
|                     Avalon.info(_('Reading video information')) | ||||
|                     video_info = fm.get_video_info(self.current_input_video) | ||||
|                     # analyze original video with FFprobe and retrieve framerate | ||||
|                     # width, height = info['streams'][0]['width'], info['streams'][0]['height'] | ||||
|                 # get file type | ||||
|                 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] | ||||
| 
 | ||||
|                     # find index of video stream | ||||
|                     video_stream_index = None | ||||
|                     for stream in video_info['streams']: | ||||
|                         if stream['codec_type'] == 'video': | ||||
|                             video_stream_index = stream['index'] | ||||
|                             break | ||||
| 
 | ||||
|                     # exit if no video stream found | ||||
|                     if video_stream_index is None: | ||||
|                         Avalon.error(_('Aborting: No video stream found')) | ||||
|                         raise StreamNotFoundError('no video stream found') | ||||
| 
 | ||||
|                     # extract frames from video | ||||
|                     self.process_pool.append((fm.extract_frames(self.current_input_video, self.extracted_frames))) | ||||
|                 # start handling input | ||||
|                 # if input file is a static image | ||||
|                 if input_file_type == 'image' and input_file_subtype != 'gif': | ||||
|                     Avalon.info(_('Starting to upscale image')) | ||||
|                     self.process_pool.append(self.driver_object.upscale(self.current_input_file, output_path)) | ||||
|                     self._wait() | ||||
| 
 | ||||
|                     # get average frame rate of video stream | ||||
|                     framerate = float(Fraction(video_info['streams'][video_stream_index]['r_frame_rate'])) | ||||
|                     fm.pixel_format = video_info['streams'][video_stream_index]['pix_fmt'] | ||||
| 
 | ||||
|                     if self.driver == 'waifu2x_caffe': | ||||
|                         # get a dict of all pixel formats and corresponding bit depth | ||||
|                         pixel_formats = fm.get_pixel_formats() | ||||
| 
 | ||||
|                         # try getting pixel format's corresponding bti depth | ||||
|                         try: | ||||
|                             self.driver_settings['output_depth'] = pixel_formats[fm.pixel_format] | ||||
|                         except KeyError: | ||||
|                             Avalon.error(_('Unsupported pixel format: {}').format(fm.pixel_format)) | ||||
|                             raise UnsupportedPixelError(f'unsupported pixel format {fm.pixel_format}') | ||||
| 
 | ||||
|                     Avalon.info(_('Framerate: {}').format(framerate)) | ||||
| 
 | ||||
|                     # width/height will be coded width/height x upscale factor | ||||
|                     original_width = video_info['streams'][video_stream_index]['width'] | ||||
|                     original_height = video_info['streams'][video_stream_index]['height'] | ||||
|                     scale_width = int(self.scale_ratio * original_width) | ||||
|                     scale_height = int(self.scale_ratio * original_height) | ||||
| 
 | ||||
|                     # upscale images one by one using waifu2x | ||||
|                     Avalon.info(_('Starting to upscale extracted images')) | ||||
|                     self._upscale_frames() | ||||
|                     Avalon.info(_('Upscaling completed')) | ||||
| 
 | ||||
|                     # frames to Video | ||||
|                     Avalon.info(_('Converting extracted frames into video')) | ||||
|                     # static images don't require GIF or video encoding | ||||
|                     # go to the next task | ||||
|                     self.processing_queue.task_done() | ||||
|                     self.total_processed += 1 | ||||
|                     continue | ||||
| 
 | ||||
|                     # use user defined output size | ||||
|                     self.process_pool.append(fm.assemble_video(framerate, | ||||
|                                                                f'{scale_width}x{scale_height}', | ||||
|                                                                self.upscaled_frames)) | ||||
|                 # if input file is a image/gif file or a video | ||||
|                 elif input_file_mime_type == 'image/gif' or input_file_type == 'video': | ||||
| 
 | ||||
|                     # drivers that have native support for video processing | ||||
|                     if input_file_type == 'video' and self.driver == 'anime4kcpp': | ||||
|                         Avalon.info(_('Starting to upscale video with Anime4KCPP')) | ||||
|                         # enable video processing mode for Anime4KCPP | ||||
|                         self.driver_settings['videoMode'] = True | ||||
|                         self.process_pool.append(self.driver_object.upscale(self.current_input_file, output_path)) | ||||
|                         self._wait() | ||||
|                         Avalon.info(_('Upscaling completed')) | ||||
| 
 | ||||
|                     else: | ||||
|                         self.create_temp_directories() | ||||
| 
 | ||||
|                         # get video information JSON using FFprobe | ||||
|                         Avalon.info(_('Reading video information')) | ||||
|                         video_info = self.ffmpeg_object.probe_file_info(self.current_input_file) | ||||
|                         # analyze original video with FFprobe and retrieve framerate | ||||
|                         # width, height = info['streams'][0]['width'], info['streams'][0]['height'] | ||||
| 
 | ||||
|                         # find index of video stream | ||||
|                         video_stream_index = None | ||||
|                         for stream in video_info['streams']: | ||||
|                             if stream['codec_type'] == 'video': | ||||
|                                 video_stream_index = stream['index'] | ||||
|                                 break | ||||
| 
 | ||||
|                         # exit if no video stream found | ||||
|                         if video_stream_index is None: | ||||
|                             Avalon.error(_('Aborting: No video stream found')) | ||||
|                             raise StreamNotFoundError('no video stream found') | ||||
| 
 | ||||
|                         # get average frame rate of video stream | ||||
|                         framerate = float(Fraction(video_info['streams'][video_stream_index]['r_frame_rate'])) | ||||
|                         # self.ffmpeg_object.pixel_format = video_info['streams'][video_stream_index]['pix_fmt'] | ||||
| 
 | ||||
|                         # extract frames from video | ||||
|                         self.process_pool.append((self.ffmpeg_object.extract_frames(self.current_input_file, self.extracted_frames))) | ||||
|                         self._wait() | ||||
| 
 | ||||
|                         # if driver is waifu2x-caffe | ||||
|                         # pass pixel format output depth information | ||||
|                         if self.driver == 'waifu2x_caffe': | ||||
|                             # get a dict of all pixel formats and corresponding bit depth | ||||
|                             pixel_formats = self.ffmpeg_object.get_pixel_formats() | ||||
| 
 | ||||
|                             # try getting pixel format's corresponding bti depth | ||||
|                             try: | ||||
|                                 self.driver_settings['output_depth'] = pixel_formats[self.ffmpeg_object.pixel_format] | ||||
|                             except KeyError: | ||||
|                                 Avalon.error(_('Unsupported pixel format: {}').format(self.ffmpeg_object.pixel_format)) | ||||
|                                 raise UnsupportedPixelError(f'unsupported pixel format {self.ffmpeg_object.pixel_format}') | ||||
| 
 | ||||
|                         Avalon.info(_('Framerate: {}').format(framerate)) | ||||
| 
 | ||||
|                         # width/height will be coded width/height x upscale factor | ||||
|                         # original_width = video_info['streams'][video_stream_index]['width'] | ||||
|                         # original_height = video_info['streams'][video_stream_index]['height'] | ||||
|                         # scale_width = int(self.scale_ratio * original_width) | ||||
|                         # scale_height = int(self.scale_ratio * original_height) | ||||
| 
 | ||||
|                         # upscale images one by one using waifu2x | ||||
|                         Avalon.info(_('Starting to upscale extracted frames')) | ||||
|                         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 of 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 | ||||
| 
 | ||||
|                 # if the desired output is gif file | ||||
|                 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._wait() | ||||
|                     Avalon.info(_('Conversion completed')) | ||||
| 
 | ||||
|                 # if the desired output is video | ||||
|                 else: | ||||
|                     # frames to video | ||||
|                     Avalon.info(_('Converting extracted frames into video')) | ||||
|                     self.process_pool.append(self.ffmpeg_object.assemble_video(framerate, self.upscaled_frames)) | ||||
|                     # f'{scale_width}x{scale_height}', | ||||
|                     self._wait() | ||||
|                     Avalon.info(_('Conversion completed')) | ||||
| 
 | ||||
|                     try: | ||||
|                         # migrate audio tracks and subtitles | ||||
|                         Avalon.info(_('Migrating audio, subtitles and other streams to upscaled video')) | ||||
|                         self.process_pool.append(fm.migrate_streams(self.current_input_video, | ||||
|                                                                     output_video, | ||||
|                                                                     self.upscaled_frames)) | ||||
|                         self.process_pool.append(self.ffmpeg_object.migrate_streams(self.current_input_file, | ||||
|                                                                                     output_path, | ||||
|                                                                                     self.upscaled_frames)) | ||||
|                         self._wait() | ||||
| 
 | ||||
|                     # if failed to copy streams | ||||
| @ -527,29 +584,32 @@ class Upscaler: | ||||
|                         Avalon.error(_('Failed to migrate streams')) | ||||
|                         Avalon.warning(_('Trying to output video without additional streams')) | ||||
| 
 | ||||
|                         # construct output file path | ||||
|                         output_video_path = output_video.parent / f'{output_video.stem}{fm.intermediate_file_name.suffix}' | ||||
|                         if input_file_mime_type == 'image/gif': | ||||
|                             (self.upscaled_frames / self.ffmpeg_object.intermediate_file_name).replace(output_path) | ||||
| 
 | ||||
|                         # if output file already exists, cancel | ||||
|                         if output_video_path.exists(): | ||||
|                             Avalon.error(_('Output video file exists, aborting')) | ||||
| 
 | ||||
|                         # otherwise, rename intermediate file to the output file | ||||
|                         else: | ||||
|                             Avalon.info(_('Writing intermediate file to: {}').format(output_video_path.absolute())) | ||||
|                             (self.upscaled_frames / fm.intermediate_file_name).rename(output_video_path) | ||||
|                             # construct output file path | ||||
|                             output_video_path = output_path.parent / f'{output_path.stem}{self.ffmpeg_object.intermediate_file_name.suffix}' | ||||
| 
 | ||||
|                     # destroy temp directories | ||||
|                     self.cleanup_temp_directories() | ||||
|                             # if output file already exists, cancel | ||||
|                             if output_video_path.exists(): | ||||
|                                 Avalon.error(_('Output video file exists, aborting')) | ||||
| 
 | ||||
|                 except (Exception, KeyboardInterrupt, SystemExit) as e: | ||||
|                     with contextlib.suppress(ValueError): | ||||
|                         self.cleanup_temp_directories() | ||||
|                         self.running = False | ||||
|                     raise e | ||||
|                             # otherwise, rename intermediate file to the output file | ||||
|                             else: | ||||
|                                 Avalon.info(_('Writing intermediate file to: {}').format(output_video_path.absolute())) | ||||
|                                 (self.upscaled_frames / self.ffmpeg_object.intermediate_file_name).rename(output_video_path) | ||||
| 
 | ||||
|             # increment total number of videos processed | ||||
|             self.total_processed += 1 | ||||
|                 # increment total number of files processed | ||||
|                 self.cleanup_temp_directories() | ||||
|                 self.processing_queue.task_done() | ||||
|                 self.total_processed += 1 | ||||
| 
 | ||||
|         except (Exception, KeyboardInterrupt, SystemExit) as e: | ||||
|             with contextlib.suppress(ValueError): | ||||
|                 self.cleanup_temp_directories() | ||||
|                 self.running = False | ||||
|             raise e | ||||
| 
 | ||||
|         # signal upscaling completion | ||||
|         self.running = False | ||||
|  | ||||
| @ -13,7 +13,7 @@ __      __  _       _                  ___   __   __ | ||||
| Name: Video2X Controller | ||||
| Creator: K4YT3X | ||||
| Date Created: Feb 24, 2018 | ||||
| Last Modified: May 10, 2020 | ||||
| Last Modified: May 11, 2020 | ||||
| 
 | ||||
| Editor: BrianPetkovsek | ||||
| Last Modified: June 17, 2019 | ||||
| @ -181,6 +181,10 @@ driver_settings['path'] = os.path.expandvars(driver_settings['path']) | ||||
| ffmpeg_settings = config['ffmpeg'] | ||||
| ffmpeg_settings['ffmpeg_path'] = os.path.expandvars(ffmpeg_settings['ffmpeg_path']) | ||||
| 
 | ||||
| # read Gifski configuration | ||||
| 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() | ||||
| preserve_frames = config['video2x']['preserve_frames'] | ||||
| @ -213,7 +217,8 @@ try: | ||||
|     upscaler = Upscaler(input_path=video2x_args.input, | ||||
|                         output_path=video2x_args.output, | ||||
|                         driver_settings=driver_settings, | ||||
|                         ffmpeg_settings=ffmpeg_settings) | ||||
|                         ffmpeg_settings=ffmpeg_settings, | ||||
|                         gifski_settings=gifski_settings) | ||||
| 
 | ||||
|     # set upscaler optional options | ||||
|     upscaler.driver = video2x_args.driver | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| # Name: Video2X Configuration File | ||||
| # Creator: K4YT3X | ||||
| # Date Created: October 23, 2018 | ||||
| # Last Modified: May 9, 2020 | ||||
| # Last Modified: May 11, 2020 | ||||
| # Values here are the default values. Change the value here to | ||||
| #   save the default value permanently. | ||||
| # Items commented out are parameters irrelevant to this context | ||||
| @ -87,7 +87,7 @@ anime4kcpp: | ||||
|   zoomFactor: 2.0 # zoom factor for resizing (double [=2]) | ||||
|   threads: 16 # Threads count for video processing (unsigned int [=16]) | ||||
|   fastMode: false # Faster but maybe low quality | ||||
|   videoMode: true # Video process | ||||
|   videoMode: false # Video process | ||||
|   preview: null # Preview image | ||||
|   preprocessing: False # Enable pre processing | ||||
|   postprocessing: False # Enable post processing | ||||
| @ -101,9 +101,9 @@ anime4kcpp: | ||||
| ffmpeg: | ||||
|   ffmpeg_path: '%LOCALAPPDATA%\video2x\ffmpeg-latest-win64-static\bin' | ||||
|   intermediate_file_name: 'intermediate.mkv' | ||||
|   # step 1: extract all frames from original video | ||||
|   # step 1: extract all frames from original input | ||||
|   # into temporary directory | ||||
|   video_to_frames: | ||||
|   input_to_frames: | ||||
|     output_options: | ||||
|       '-qscale:v': null | ||||
|       '-pix_fmt': rgba64be | ||||
| @ -138,6 +138,17 @@ ffmpeg: | ||||
|       '-metadata': 'comment=Upscaled by Video2X' | ||||
|     '-hwaccel': auto | ||||
|     '-y': true | ||||
| gifski: | ||||
|   gifski_path: '%LOCALAPPDATA%\video2x\gifski\win\gifski' | ||||
|   # output: null # Destination file to write to | ||||
|   # fps: 20 # Animation frames per second (for PNG frames only) [default: 20] | ||||
|   fast: false # 3 times faster encoding, but 10% lower quality and bigger file | ||||
|   quality: 100 # Lower quality may give smaller file | ||||
|   width: null # Maximum width | ||||
|   height: null # Maximum height (if width is also set) | ||||
|   once: false # Do not loop the GIF | ||||
|   nosort: false # Use files exactly in the order given, rather than sorted | ||||
|   quiet: false # Do not show a progress bar | ||||
| video2x: | ||||
|   video2x_cache_directory: null # default: %TEMP%\video2x | ||||
|   image_format: png | ||||
|  | ||||
| @ -4,7 +4,7 @@ | ||||
| Creator: Video2X GUI | ||||
| Author: K4YT3X | ||||
| Date Created: May 5, 2020 | ||||
| Last Modified: May 10, 2020 | ||||
| Last Modified: May 11, 2020 | ||||
| """ | ||||
| 
 | ||||
| # local imports | ||||
| @ -25,6 +25,7 @@ import yaml | ||||
| from PyQt5 import QtGui, uic | ||||
| from PyQt5.QtCore import * | ||||
| from PyQt5.QtWidgets import * | ||||
| import magic | ||||
| # QObject, pyqtSlot, pyqtSignal, QRunnable, QThreadPool, QAbstractTableModel, Qt | ||||
| 
 | ||||
| VERSION = '2.0.0' | ||||
| @ -110,13 +111,34 @@ class InputTableModel(QAbstractTableModel): | ||||
|     def data(self, index, role): | ||||
|         if role == Qt.DisplayRole: | ||||
| 
 | ||||
|             file_path = self._data[index.row()] | ||||
| 
 | ||||
|             if index.column() == 0: | ||||
|                 return str(self._data[index.row()].absolute()) | ||||
|                 return str(file_path.absolute()) | ||||
|             else: | ||||
|                 if self._data[index.row()].is_file(): | ||||
|                     return 'File' | ||||
|                 elif self._data[index.row()].is_dir(): | ||||
| 
 | ||||
|                 # determine file type | ||||
|                 # if path is a folder | ||||
|                 if file_path.is_dir(): | ||||
|                     return 'Folder' | ||||
| 
 | ||||
|                 # if path is single file | ||||
|                 # determine file type | ||||
|                 elif file_path.is_file(): | ||||
|                     input_file_mime_type = magic.from_file(str(file_path.absolute()), mime=True) | ||||
|                     input_file_type = input_file_mime_type.split('/')[0] | ||||
|                     input_file_subtype = input_file_mime_type.split('/')[1] | ||||
|                     if input_file_type == 'image': | ||||
|                         if input_file_subtype == 'gif': | ||||
|                             return 'GIF' | ||||
|                         return 'Image' | ||||
| 
 | ||||
|                     elif input_file_type == 'video': | ||||
|                         return 'Video' | ||||
| 
 | ||||
|                     else: | ||||
|                         return 'Unknown' | ||||
| 
 | ||||
|                 else: | ||||
|                     return 'Unknown' | ||||
| 
 | ||||
| @ -373,6 +395,10 @@ class Video2XMainWindow(QMainWindow): | ||||
|         self.ffmpeg_settings = self.config['ffmpeg'] | ||||
|         self.ffmpeg_settings['ffmpeg_path'] = str(pathlib.Path(os.path.expandvars(self.ffmpeg_settings['ffmpeg_path'])).absolute()) | ||||
| 
 | ||||
|         # read Gifski configuration | ||||
|         self.gifski_settings = self.config['gifski'] | ||||
|         self.gifski_settings['gifski_path'] = str(pathlib.Path(os.path.expandvars(self.gifski_settings['gifski_path'])).absolute()) | ||||
| 
 | ||||
|         # set cache directory path | ||||
|         if self.config['video2x']['video2x_cache_directory'] is None: | ||||
|             self.config['video2x']['video2x_cache_directory'] = str((pathlib.Path(tempfile.gettempdir()) / 'video2x').absolute()) | ||||
| @ -585,7 +611,34 @@ class Video2XMainWindow(QMainWindow): | ||||
|             return | ||||
| 
 | ||||
|         if input_path.is_file(): | ||||
|             output_path = input_path.parent / f'{input_path.stem}_output.mp4' | ||||
| 
 | ||||
|             # generate suffix automatically | ||||
|             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] | ||||
| 
 | ||||
|             # if input file is an image | ||||
|             if input_file_type == 'image': | ||||
| 
 | ||||
|                 # if file is a gif, use .gif | ||||
|                 if input_file_subtype == 'gif': | ||||
|                     suffix = '.gif' | ||||
| 
 | ||||
|                 # otherwise, use .png by default for all images | ||||
|                 else: | ||||
|                     suffix = '.png' | ||||
| 
 | ||||
|             # if input is video, use .mp4 as output by default | ||||
|             elif input_file_type == 'video': | ||||
|                 suffix = '.mp4' | ||||
| 
 | ||||
|             # if failed to detect file type | ||||
|             # use input file's suffix | ||||
|             else: | ||||
|                 suffix = input_path.suffix | ||||
| 
 | ||||
|             output_path = input_path.parent / f'{input_path.stem}_output{suffix}' | ||||
| 
 | ||||
|         elif input_path.is_dir(): | ||||
|             output_path = input_path.parent / f'{input_path.stem}_output' | ||||
| 
 | ||||
| @ -593,7 +646,7 @@ class Video2XMainWindow(QMainWindow): | ||||
|         output_path_id = 0 | ||||
|         while output_path.exists() and output_path_id <= 1000: | ||||
|             if input_path.is_file(): | ||||
|                 output_path = input_path.parent / pathlib.Path(f'{input_path.stem}_output_{output_path_id}.mp4') | ||||
|                 output_path = input_path.parent / pathlib.Path(f'{input_path.stem}_output_{output_path_id}{suffix}') | ||||
|             elif input_path.is_dir(): | ||||
|                 output_path = input_path.parent / pathlib.Path(f'{input_path.stem}_output_{output_path_id}') | ||||
|             output_path_id += 1 | ||||
| @ -693,8 +746,8 @@ You can [submit an issue on GitHub](https://github.com/k4yt3x/video2x/issues/new | ||||
|                                     self.upscaler.total_frames_upscaled, | ||||
|                                     self.upscaler.total_frames, | ||||
|                                     self.upscaler.total_processed, | ||||
|                                     self.upscaler.total_videos, | ||||
|                                     self.upscaler.current_input_video, | ||||
|                                     self.upscaler.total_files, | ||||
|                                     self.upscaler.current_input_file, | ||||
|                                     self.upscaler.last_frame_upscaled)) | ||||
|             time.sleep(1) | ||||
| 
 | ||||
| @ -707,8 +760,8 @@ You can [submit an issue on GitHub](https://github.com/k4yt3x/video2x/issues/new | ||||
|         total_frames_upscaled = progress_information[1] | ||||
|         total_frames = progress_information[2] | ||||
|         total_processed = progress_information[3] | ||||
|         total_videos = progress_information[4] | ||||
|         current_input_video = progress_information[5] | ||||
|         total_files = progress_information[4] | ||||
|         current_input_file = progress_information[5] | ||||
|         last_frame_upscaled = progress_information[6] | ||||
| 
 | ||||
|         # calculate fields based on frames and time elapsed | ||||
| @ -727,10 +780,10 @@ You can [submit an issue on GitHub](https://github.com/k4yt3x/video2x/issues/new | ||||
|         self.time_elapsed_label.setText('Time Elapsed: {}'.format(time.strftime("%H:%M:%S", time.gmtime(time_elapsed)))) | ||||
|         self.time_remaining_label.setText('Time Remaining: {}'.format(time.strftime("%H:%M:%S", time.gmtime(time_remaining)))) | ||||
|         self.rate_label.setText('Rate (FPS): {}'.format(round(rate, 2))) | ||||
|         self.overall_progress_label.setText('Overall Progress: {}/{}'.format(total_processed, total_videos)) | ||||
|         self.overall_progress_bar.setMaximum(total_videos) | ||||
|         self.overall_progress_label.setText('Overall Progress: {}/{}'.format(total_processed, total_files)) | ||||
|         self.overall_progress_bar.setMaximum(total_files) | ||||
|         self.overall_progress_bar.setValue(total_processed) | ||||
|         self.currently_processing_label.setText('Currently Processing: {}'.format(str(current_input_video.name))) | ||||
|         self.currently_processing_label.setText('Currently Processing: {}'.format(str(current_input_file.name))) | ||||
| 
 | ||||
|         # if show frame is checked, show preview image | ||||
|         if self.frame_preview_show_preview_check_box.isChecked() and last_frame_upscaled.is_file(): | ||||
| @ -798,7 +851,8 @@ You can [submit an issue on GitHub](https://github.com/k4yt3x/video2x/issues/new | ||||
|             self.upscaler = Upscaler(input_path=input_directory, | ||||
|                                      output_path=output_directory, | ||||
|                                      driver_settings=self.driver_settings, | ||||
|                                      ffmpeg_settings=self.ffmpeg_settings) | ||||
|                                      ffmpeg_settings=self.ffmpeg_settings, | ||||
|                                      gifski_settings=self.gifski_settings) | ||||
| 
 | ||||
|             # set optional options | ||||
|             self.upscaler.driver = AVAILABLE_DRIVERS[self.driver_combo_box.currentText()] | ||||
|  | ||||
| @ -31,6 +31,7 @@ import re | ||||
| import shutil | ||||
| import subprocess | ||||
| import sys | ||||
| import tarfile | ||||
| import tempfile | ||||
| import time | ||||
| import traceback | ||||
| @ -42,12 +43,19 @@ import zipfile | ||||
| # later in the script. | ||||
| # import requests | ||||
| 
 | ||||
| VERSION = '1.8.0' | ||||
| VERSION = '2.0.0' | ||||
| 
 | ||||
| # global static variables | ||||
| LOCALAPPDATA = pathlib.Path(os.getenv('localappdata')) | ||||
| VIDEO2X_CONFIG = pathlib.Path(__file__).parent.absolute() / 'video2x.yaml' | ||||
| DRIVER_OPTIONS = ['all', 'ffmpeg', 'waifu2x_caffe', 'waifu2x_converter_cpp', 'waifu2x_ncnn_vulkan', 'anime4kcpp', 'srmd_ncnn_vulkan'] | ||||
| DRIVER_OPTIONS = ['all', | ||||
|                   'ffmpeg', | ||||
|                   'gifski', | ||||
|                   'waifu2x_caffe', | ||||
|                   'waifu2x_converter_cpp', | ||||
|                   'waifu2x_ncnn_vulkan', | ||||
|                   'anime4kcpp', | ||||
|                   'srmd_ncnn_vulkan'] | ||||
| 
 | ||||
| 
 | ||||
| def parse_arguments(): | ||||
| @ -76,32 +84,21 @@ class Video2xSetup: | ||||
|         self.trash = [] | ||||
| 
 | ||||
|     def run(self): | ||||
|         # regardless of which driver to install | ||||
|         # always ensure Python modules are installed and up-to-date | ||||
|         if self.download_python_modules: | ||||
|             print('\nInstalling Python libraries') | ||||
|             self._install_python_requirements() | ||||
| 
 | ||||
|         # if all drivers are to be installed | ||||
|         if self.driver == 'all': | ||||
|             self._install_ffmpeg() | ||||
|             self._install_waifu2x_caffe() | ||||
|             self._install_waifu2x_converter_cpp() | ||||
|             self._install_waifu2x_ncnn_vulkan() | ||||
|             self._install_anime4kcpp() | ||||
|             self._install_srmd_ncnn_vulkan() | ||||
|         elif self.driver == 'ffmpeg': | ||||
|             self._install_ffmpeg() | ||||
|         elif self.driver == 'waifu2x_caffe': | ||||
|             self._install_waifu2x_caffe() | ||||
|         elif self.driver == 'waifu2x_converter_cpp': | ||||
|             self._install_waifu2x_converter_cpp() | ||||
|         elif self.driver == 'waifu2x_ncnn_vulkan': | ||||
|             self._install_waifu2x_ncnn_vulkan() | ||||
|         elif self.driver == 'anime4kcpp': | ||||
|             self._install_anime4kcpp() | ||||
|         elif self.driver == 'srmd_ncnn_vulkan': | ||||
|             self._install_srmd_ncnn_vulkan() | ||||
|             DRIVER_OPTIONS.remove('all') | ||||
|             for driver in DRIVER_OPTIONS: | ||||
|                 getattr(self, f'_install_{driver}')() | ||||
| 
 | ||||
|         print('\nGenerating Video2X configuration file') | ||||
|         self._generate_config() | ||||
|         # install only the selected driver | ||||
|         else: | ||||
|             getattr(self, f'_install_{self.driver}')() | ||||
| 
 | ||||
|         print('\nCleaning up temporary files') | ||||
|         self._cleanup() | ||||
| @ -139,6 +136,22 @@ class Video2xSetup: | ||||
|         with zipfile.ZipFile(ffmpeg_zip) as zipf: | ||||
|             zipf.extractall(LOCALAPPDATA / 'video2x') | ||||
| 
 | ||||
|     def _install_gifski(self): | ||||
|         print('\nInstalling Gifski') | ||||
|         import requests | ||||
| 
 | ||||
|         # Get latest release of waifu2x-ncnn-vulkan via Github API | ||||
|         latest_release = requests.get('https://api.github.com/repos/ImageOptim/gifski/releases/latest').json() | ||||
| 
 | ||||
|         for a in latest_release['assets']: | ||||
|             if re.search(r'gifski-.*\.tar\.xz', a['browser_download_url']): | ||||
|                 gifski_tar_gz = download(a['browser_download_url'], tempfile.gettempdir()) | ||||
|                 self.trash.append(gifski_tar_gz) | ||||
| 
 | ||||
|         # extract and rename | ||||
|         with tarfile.open(gifski_tar_gz) as archive: | ||||
|             archive.extractall(LOCALAPPDATA / 'video2x' / 'gifski') | ||||
| 
 | ||||
|     def _install_waifu2x_caffe(self): | ||||
|         """ Install waifu2x_caffe | ||||
|         """ | ||||
| @ -248,42 +261,6 @@ class Video2xSetup: | ||||
|             # rename the newly extracted directory | ||||
|             (LOCALAPPDATA / 'video2x' / zipf.namelist()[0]).rename(srmd_ncnn_vulkan_directory) | ||||
| 
 | ||||
|     def _generate_config(self): | ||||
|         """ Generate video2x config | ||||
|         """ | ||||
|         import yaml | ||||
| 
 | ||||
|         # open current video2x configuration file as template | ||||
|         with open(VIDEO2X_CONFIG, 'r') as template: | ||||
|             template_dict = yaml.load(template, Loader=yaml.FullLoader) | ||||
|             template.close() | ||||
| 
 | ||||
|         # configure only the specified drivers | ||||
|         if self.driver == 'all': | ||||
|             template_dict['waifu2x_caffe']['path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-caffe' / 'waifu2x-caffe-cui') | ||||
|             template_dict['waifu2x_converter_cpp']['path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-converter-cpp' / 'waifu2x-converter-cpp') | ||||
|             template_dict['waifu2x_ncnn_vulkan']['path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-ncnn-vulkan' / 'waifu2x-ncnn-vulkan') | ||||
|             template_dict['srmd_ncnn_vulkan']['path'] = str(LOCALAPPDATA / 'video2x' / 'srmd-ncnn-vulkan' / 'srmd-ncnn-vulkan') | ||||
|             template_dict['anime4kcpp']['path'] = str(LOCALAPPDATA / 'video2x' / 'anime4kcpp' / 'CLI' / 'Anime4KCPP_CLI' / 'Anime4KCPP_CLI') | ||||
|         elif self.driver == 'waifu2x_caffe': | ||||
|             template_dict['waifu2x_caffe']['path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-caffe' / 'waifu2x-caffe-cui') | ||||
|         elif self.driver == 'waifu2x_converter_cpp': | ||||
|             template_dict['waifu2x_converter_cpp']['path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-converter-cpp' / 'waifu2x-converter-cpp') | ||||
|         elif self.driver == 'waifu2x_ncnn_vulkan': | ||||
|             template_dict['waifu2x_ncnn_vulkan']['path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-ncnn-vulkan' / 'waifu2x-ncnn-vulkan') | ||||
|         elif self.driver == 'srmd_ncnn_vulkan': | ||||
|             template_dict['srmd_ncnn_vulkan']['path'] = str(LOCALAPPDATA / 'video2x' / 'srmd-ncnn-vulkan' / 'srmd-ncnn-vulkan') | ||||
|         elif self.driver == 'anime4kcpp': | ||||
|             template_dict['anime4kcpp']['path'] = str(LOCALAPPDATA / 'video2x' / 'anime4kcpp' / 'CLI' / 'Anime4KCPP_CLI' / 'Anime4KCPP_CLI') | ||||
| 
 | ||||
|         template_dict['ffmpeg']['ffmpeg_path'] = str(LOCALAPPDATA / 'video2x' / 'ffmpeg-latest-win64-static' / 'bin') | ||||
|         template_dict['video2x']['video2x_cache_directory'] = None | ||||
|         template_dict['video2x']['preserve_frames'] = False | ||||
| 
 | ||||
|         # write configuration into file | ||||
|         with open(VIDEO2X_CONFIG, 'w') as config: | ||||
|             yaml.dump(template_dict, config) | ||||
| 
 | ||||
| 
 | ||||
| def download(url, save_path, chunk_size=4096): | ||||
|     """ Download file to local with requests library | ||||
|  | ||||
| @ -69,6 +69,10 @@ class WrapperMain: | ||||
|         self.driver_settings['zoomFactor'] = upscaler.scale_ratio | ||||
|         self.driver_settings['threads'] = upscaler.processes | ||||
| 
 | ||||
|         # append FFmpeg path to the end of PATH | ||||
|         # Anime4KCPP will then use FFmpeg to migrate audio tracks | ||||
|         os.environ['PATH'] += f';{upscaler.ffmpeg_settings["ffmpeg_path"]}' | ||||
| 
 | ||||
|     def upscale(self, input_file, output_file): | ||||
|         """This is the core function for WAIFU2X class | ||||
| 
 | ||||
| @ -90,14 +94,14 @@ class WrapperMain: | ||||
| 
 | ||||
|         # list to be executed | ||||
|         # initialize the list with waifu2x binary path as the first element | ||||
|         execute = [self.driver_settings.pop('path')] | ||||
|         execute = [self.driver_settings['path']] | ||||
| 
 | ||||
|         for key in self.driver_settings.keys(): | ||||
| 
 | ||||
|             value = self.driver_settings[key] | ||||
| 
 | ||||
|             # null or None means that leave this option out (keep default) | ||||
|             if value is None or value is False: | ||||
|             if key == 'path' or value is None or value is False: | ||||
|                 continue | ||||
|             else: | ||||
|                 if len(key) == 1: | ||||
|  | ||||
| @ -12,6 +12,7 @@ Description: This class handles all FFmpeg related operations. | ||||
| # built-in imports | ||||
| import json | ||||
| import pathlib | ||||
| import shlex | ||||
| import subprocess | ||||
| 
 | ||||
| # third-party imports | ||||
| @ -36,7 +37,7 @@ class Ffmpeg: | ||||
|         # video metadata | ||||
|         self.image_format = image_format | ||||
|         self.intermediate_file_name = pathlib.Path(self.ffmpeg_settings['intermediate_file_name']) | ||||
|         self.pixel_format = None | ||||
|         self.pixel_format = self.ffmpeg_settings['input_to_frames']['output_options']['-pix_fmt'] | ||||
| 
 | ||||
|     def get_pixel_formats(self): | ||||
|         """ Get a dictionary of supported pixel formats | ||||
| @ -49,8 +50,8 @@ class Ffmpeg: | ||||
|         """ | ||||
|         execute = [ | ||||
|             self.ffmpeg_probe_binary, | ||||
|             '-v', | ||||
|             'quiet', | ||||
|             # '-v', | ||||
|             # 'quiet', | ||||
|             '-pix_fmts' | ||||
|         ] | ||||
| 
 | ||||
| @ -74,7 +75,7 @@ class Ffmpeg: | ||||
| 
 | ||||
|         return pixel_formats | ||||
| 
 | ||||
|     def get_video_info(self, input_video): | ||||
|     def probe_file_info(self, input_video): | ||||
|         """ Gets input video information | ||||
| 
 | ||||
|         This method reads input video information | ||||
| @ -104,31 +105,25 @@ class Ffmpeg: | ||||
|         # turn elements into str | ||||
|         execute = [str(e) for e in execute] | ||||
| 
 | ||||
|         Avalon.debug_info(f'Executing: {" ".join(execute)}') | ||||
|         Avalon.debug_info(f'Executing: {shlex.join(execute)}') | ||||
|         json_str = subprocess.run(execute, check=True, stdout=subprocess.PIPE).stdout | ||||
|         return json.loads(json_str.decode('utf-8')) | ||||
| 
 | ||||
|     def extract_frames(self, input_video, extracted_frames): | ||||
|         """Extract every frame from original videos | ||||
| 
 | ||||
|         This method extracts every frame from input video using FFmpeg | ||||
| 
 | ||||
|         Arguments: | ||||
|             input_video {string} -- input video path | ||||
|             extracted_frames {string} -- video output directory | ||||
|     def extract_frames(self, input_file, extracted_frames): | ||||
|         """ extract frames from video or GIF file | ||||
|         """ | ||||
|         execute = [ | ||||
|             self.ffmpeg_binary | ||||
|         ] | ||||
| 
 | ||||
|         execute.extend(self._read_configuration(phase='video_to_frames')) | ||||
|         execute.extend(self._read_configuration(phase='input_to_frames')) | ||||
| 
 | ||||
|         execute.extend([ | ||||
|             '-i', | ||||
|             input_video | ||||
|             input_file | ||||
|         ]) | ||||
| 
 | ||||
|         execute.extend(self._read_configuration(phase='video_to_frames', section='output_options')) | ||||
|         execute.extend(self._read_configuration(phase='input_to_frames', section='output_options')) | ||||
| 
 | ||||
|         execute.extend([ | ||||
|             extracted_frames / f'extracted_%0d.{self.image_format}' | ||||
| @ -136,7 +131,7 @@ class Ffmpeg: | ||||
| 
 | ||||
|         return(self._execute(execute)) | ||||
| 
 | ||||
|     def assemble_video(self, framerate, resolution, upscaled_frames): | ||||
|     def assemble_video(self, framerate, upscaled_frames): | ||||
|         """Converts images into videos | ||||
| 
 | ||||
|         This method converts a set of images into a video | ||||
| @ -149,9 +144,9 @@ class Ffmpeg: | ||||
|         execute = [ | ||||
|             self.ffmpeg_binary, | ||||
|             '-r', | ||||
|             str(framerate), | ||||
|             '-s', | ||||
|             resolution | ||||
|             str(framerate) | ||||
|             # '-s', | ||||
|             # resolution | ||||
|         ] | ||||
| 
 | ||||
|         # read other options | ||||
| @ -274,17 +269,9 @@ class Ffmpeg: | ||||
|         return configuration | ||||
| 
 | ||||
|     def _execute(self, execute): | ||||
|         """ execute command | ||||
| 
 | ||||
|         Arguments: | ||||
|             execute {list} -- list of arguments to be executed | ||||
| 
 | ||||
|         Returns: | ||||
|             int -- execution return code | ||||
|         """ | ||||
|         # turn all list elements into string to avoid errors | ||||
|         execute = [str(e) for e in execute] | ||||
| 
 | ||||
|         Avalon.debug_info(f'Executing: {execute}') | ||||
|         Avalon.debug_info(f'Executing: {shlex.join(execute)}') | ||||
| 
 | ||||
|         return subprocess.Popen(execute) | ||||
|  | ||||
							
								
								
									
										70
									
								
								src/wrappers/gifski.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/wrappers/gifski.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,70 @@ | ||||
| #!/usr/bin/env python3 | ||||
| # -*- coding: utf-8 -*- | ||||
| """ | ||||
| Name: Gifski Wrapper | ||||
| Creator: K4YT3X | ||||
| Date Created: May 11, 2020 | ||||
| Last Modified: May 11, 2020 | ||||
| 
 | ||||
| Description: High-level wrapper for Gifski. | ||||
| """ | ||||
| 
 | ||||
| # built-in imports | ||||
| import pathlib | ||||
| import subprocess | ||||
| 
 | ||||
| # third-party imports | ||||
| from avalon_framework import Avalon | ||||
| 
 | ||||
| 
 | ||||
| class Gifski: | ||||
| 
 | ||||
|     def __init__(self, gifski_settings): | ||||
|         self.gifski_settings = gifski_settings | ||||
| 
 | ||||
|     def make_gif(self, upscaled_frames: pathlib.Path, output_path: pathlib.Path, framerate: float, image_format: str) -> subprocess.Popen: | ||||
|         execute = [ | ||||
|             self.gifski_settings['gifski_path'], | ||||
|             '-o', | ||||
|             output_path, | ||||
|             '--fps', | ||||
|             int(round(framerate, 0)) | ||||
|         ] | ||||
| 
 | ||||
|         # load configurations from config file | ||||
|         execute.extend(self._load_configuration()) | ||||
| 
 | ||||
|         # append frames location | ||||
|         execute.extend([upscaled_frames / f'extracted_*.{image_format}']) | ||||
| 
 | ||||
|         return(self._execute(execute)) | ||||
| 
 | ||||
|     def _load_configuration(self): | ||||
| 
 | ||||
|         configuration = [] | ||||
| 
 | ||||
|         for key in self.gifski_settings.keys(): | ||||
| 
 | ||||
|             value = self.gifski_settings[key] | ||||
| 
 | ||||
|             # null or None means that leave this option out (keep default) | ||||
|             if key == 'gifski_path' or value is None or value is False: | ||||
|                 continue | ||||
|             else: | ||||
|                 if len(key) == 1: | ||||
|                     configuration.append(f'-{key}') | ||||
|                 else: | ||||
|                     configuration.append(f'--{key}') | ||||
| 
 | ||||
|                 # true means key is an option | ||||
|                 if value is not True: | ||||
|                     configuration.append(str(value)) | ||||
|         return configuration | ||||
| 
 | ||||
|     def _execute(self, execute: list) -> subprocess.Popen: | ||||
|         # turn all list elements into string to avoid errors | ||||
|         execute = [str(e) for e in execute] | ||||
| 
 | ||||
|         Avalon.debug_info(f'Executing: {execute}') | ||||
| 
 | ||||
|         return subprocess.Popen(execute) | ||||
| @ -77,14 +77,14 @@ class WrapperMain: | ||||
| 
 | ||||
|         # list to be executed | ||||
|         # initialize the list with the binary path as the first element | ||||
|         execute = [self.driver_settings.pop('path')] | ||||
|         execute = [self.driver_settings['path']] | ||||
| 
 | ||||
|         for key in self.driver_settings.keys(): | ||||
| 
 | ||||
|             value = self.driver_settings[key] | ||||
| 
 | ||||
|             # null or None means that leave this option out (keep default) | ||||
|             if value is None or value is False: | ||||
|             if key == 'path' or value is None or value is False: | ||||
|                 continue | ||||
|             else: | ||||
|                 if len(key) == 1: | ||||
|  | ||||
| @ -56,8 +56,8 @@ class WrapperMain: | ||||
|         parser.add_argument('-m', '--mode', choices=['noise', 'scale', 'noise_scale', 'auto_scale'], help='image processing mode') | ||||
|         parser.add_argument('-e', '--output_extention', type=str, help='extention to output image file when output_path is (auto) or input_path is folder') | ||||
|         parser.add_argument('-l', '--input_extention_list', type=str, help='extention to input image file when input_path is folder') | ||||
|         parser.add_argument('-o', '--output', type=str, help=argparse.SUPPRESS)  # help='path to output image file (when input_path is folder, output_path must be folder)') | ||||
|         parser.add_argument('-i', '--input_file', type=str, help=argparse.SUPPRESS)  # help='(required) path to input image file') | ||||
|         parser.add_argument('-o', '--output_path', type=str, help=argparse.SUPPRESS)  # help='path to output image file (when input_path is folder, output_path must be folder)') | ||||
|         parser.add_argument('-i', '--input_path', type=str, help=argparse.SUPPRESS)  # help='(required) path to input image file') | ||||
|         return parser.parse_args(arguments) | ||||
| 
 | ||||
|     def load_configurations(self, upscaler): | ||||
| @ -79,14 +79,14 @@ class WrapperMain: | ||||
| 
 | ||||
|         # list to be executed | ||||
|         # initialize the list with waifu2x binary path as the first element | ||||
|         execute = [self.driver_settings.pop('path')] | ||||
|         execute = [self.driver_settings['path']] | ||||
| 
 | ||||
|         for key in self.driver_settings.keys(): | ||||
| 
 | ||||
|             value = self.driver_settings[key] | ||||
| 
 | ||||
|             # null or None means that leave this option out (keep default) | ||||
|             if value is None or value is False: | ||||
|             if key == 'path' or value is None or value is False: | ||||
|                 continue | ||||
|             else: | ||||
|                 if len(key) == 1: | ||||
|  | ||||
| @ -93,14 +93,14 @@ class WrapperMain: | ||||
| 
 | ||||
|         # list to be executed | ||||
|         # initialize the list with waifu2x binary path as the first element | ||||
|         execute = [self.driver_settings.pop('path')] | ||||
|         execute = [self.driver_settings['path']] | ||||
| 
 | ||||
|         for key in self.driver_settings.keys(): | ||||
| 
 | ||||
|             value = self.driver_settings[key] | ||||
| 
 | ||||
|             # null or None means that leave this option out (keep default) | ||||
|             if value is None or value is False: | ||||
|             if key == 'path' or value is None or value is False: | ||||
|                 continue | ||||
|             else: | ||||
|                 if len(key) == 1: | ||||
|  | ||||
| @ -80,14 +80,14 @@ class WrapperMain: | ||||
| 
 | ||||
|         # list to be executed | ||||
|         # initialize the list with waifu2x binary path as the first element | ||||
|         execute = [self.driver_settings.pop('path')] | ||||
|         execute = [self.driver_settings['path']] | ||||
| 
 | ||||
|         for key in self.driver_settings.keys(): | ||||
| 
 | ||||
|             value = self.driver_settings[key] | ||||
| 
 | ||||
|             # null or None means that leave this option out (keep default) | ||||
|             if value is None or value is False: | ||||
|             if key == 'path' or value is None or value is False: | ||||
|                 continue | ||||
|             else: | ||||
|                 if len(key) == 1: | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user