mirror of
https://github.com/k4yt3x/video2x.git
synced 2025-01-01 10:29:09 +00:00
added image and GIF upscale support
This commit is contained in:
parent
5cf3271aad
commit
e305d0188e
@ -3,6 +3,8 @@ colorama
|
|||||||
patool
|
patool
|
||||||
psutil
|
psutil
|
||||||
pyqt5
|
pyqt5
|
||||||
|
python-magic
|
||||||
|
python-magic-bin
|
||||||
pyunpack
|
pyunpack
|
||||||
pyyaml
|
pyyaml
|
||||||
requests
|
requests
|
||||||
|
188
src/upscaler.py
188
src/upscaler.py
@ -4,7 +4,7 @@
|
|||||||
Name: Video2X Upscaler
|
Name: Video2X Upscaler
|
||||||
Author: K4YT3X
|
Author: K4YT3X
|
||||||
Date Created: December 10, 2018
|
Date Created: December 10, 2018
|
||||||
Last Modified: May 10, 2020
|
Last Modified: May 11, 2020
|
||||||
|
|
||||||
Description: This file contains the Upscaler class. Each
|
Description: This file contains the Upscaler class. Each
|
||||||
instance of the Upscaler class is an upscaler on an image or
|
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 image_cleaner import ImageCleaner
|
||||||
from progress_monitor import ProgressMonitor
|
from progress_monitor import ProgressMonitor
|
||||||
from wrappers.ffmpeg import Ffmpeg
|
from wrappers.ffmpeg import Ffmpeg
|
||||||
|
from wrappers.gifski import Gifski
|
||||||
|
|
||||||
# built-in imports
|
# built-in imports
|
||||||
from fractions import Fraction
|
from fractions import Fraction
|
||||||
@ -24,7 +25,6 @@ import copy
|
|||||||
import gettext
|
import gettext
|
||||||
import importlib
|
import importlib
|
||||||
import locale
|
import locale
|
||||||
import os
|
|
||||||
import pathlib
|
import pathlib
|
||||||
import queue
|
import queue
|
||||||
import re
|
import re
|
||||||
@ -36,6 +36,7 @@ import traceback
|
|||||||
|
|
||||||
# third-party imports
|
# third-party imports
|
||||||
from avalon_framework import Avalon
|
from avalon_framework import Avalon
|
||||||
|
import magic
|
||||||
|
|
||||||
# internationalization constants
|
# internationalization constants
|
||||||
DOMAIN = 'video2x'
|
DOMAIN = 'video2x'
|
||||||
@ -67,12 +68,13 @@ class Upscaler:
|
|||||||
ArgumentError -- if argument is not valid
|
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
|
# mandatory arguments
|
||||||
self.input = input_path
|
self.input = input_path
|
||||||
self.output = output_path
|
self.output = output_path
|
||||||
self.driver_settings = driver_settings
|
self.driver_settings = driver_settings
|
||||||
self.ffmpeg_settings = ffmpeg_settings
|
self.ffmpeg_settings = ffmpeg_settings
|
||||||
|
self.gifski_settings = gifski_settings
|
||||||
|
|
||||||
# optional arguments
|
# optional arguments
|
||||||
self.driver = 'waifu2x_caffe'
|
self.driver = 'waifu2x_caffe'
|
||||||
@ -86,9 +88,9 @@ class Upscaler:
|
|||||||
self.running = False
|
self.running = False
|
||||||
self.total_frames_upscaled = 0
|
self.total_frames_upscaled = 0
|
||||||
self.total_frames = 0
|
self.total_frames = 0
|
||||||
self.total_videos = 0
|
self.total_files = 0
|
||||||
self.total_processed = 0
|
self.total_processed = 0
|
||||||
self.current_input_video = pathlib.Path()
|
self.current_input_file = pathlib.Path()
|
||||||
self.last_frame_upscaled = pathlib.Path()
|
self.last_frame_upscaled = pathlib.Path()
|
||||||
|
|
||||||
def create_temp_directories(self):
|
def create_temp_directories(self):
|
||||||
@ -154,10 +156,10 @@ class Upscaler:
|
|||||||
Avalon.error(_('Input and output path type mismatch'))
|
Avalon.error(_('Input and output path type mismatch'))
|
||||||
Avalon.error(_('Input is single file but output is directory'))
|
Avalon.error(_('Input is single file but output is directory'))
|
||||||
raise ArgumentError('input output path type mismatch')
|
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(_('No suffix found in output file path'))
|
||||||
Avalon.error(_('Suffix must be specified for FFmpeg'))
|
Avalon.error(_('Suffix must be specified'))
|
||||||
raise ArgumentError('no output video suffix specified')
|
raise ArgumentError('no output file suffix specified')
|
||||||
|
|
||||||
# if input is a directory
|
# if input is a directory
|
||||||
elif self.input.is_dir():
|
elif self.input.is_dir():
|
||||||
@ -238,6 +240,14 @@ class Upscaler:
|
|||||||
self.driver_settings['scale_width'] = None
|
self.driver_settings['scale_width'] = None
|
||||||
self.driver_settings['scale_height'] = 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):
|
def _upscale_frames(self):
|
||||||
""" Upscale video frames with waifu2x-caffe
|
""" Upscale video frames with waifu2x-caffe
|
||||||
|
|
||||||
@ -393,9 +403,8 @@ class Upscaler:
|
|||||||
# load options from upscaler class into driver settings
|
# load options from upscaler class into driver settings
|
||||||
self.driver_object.load_configurations(self)
|
self.driver_object.load_configurations(self)
|
||||||
|
|
||||||
# parse arguments for waifu2x
|
# initialize FFmpeg object
|
||||||
# check argument sanity
|
self.ffmpeg_object = Ffmpeg(self.ffmpeg_settings, self.image_format)
|
||||||
self._check_arguments()
|
|
||||||
|
|
||||||
# define processing queue
|
# define processing queue
|
||||||
self.processing_queue = queue.Queue()
|
self.processing_queue = queue.Queue()
|
||||||
@ -408,17 +417,17 @@ class Upscaler:
|
|||||||
for input_path in self.input:
|
for input_path in self.input:
|
||||||
|
|
||||||
if input_path.is_file():
|
if input_path.is_file():
|
||||||
output_video = self.output / input_path.name
|
output_path = self.output / input_path.name
|
||||||
self.processing_queue.put((input_path.absolute(), output_video.absolute()))
|
self.processing_queue.put((input_path.absolute(), output_path.absolute()))
|
||||||
|
|
||||||
elif input_path.is_dir():
|
elif input_path.is_dir():
|
||||||
for input_video in [f for f in input_path.iterdir() if f.is_file()]:
|
for input_path in [f for f in input_path.iterdir() if f.is_file()]:
|
||||||
output_video = self.output / input_video.name
|
output_path = self.output / input_path.name
|
||||||
self.processing_queue.put((input_video.absolute(), output_video.absolute()))
|
self.processing_queue.put((input_path.absolute(), output_path.absolute()))
|
||||||
|
|
||||||
# if input specified is single file
|
# if input specified is single file
|
||||||
elif self.input.is_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()))
|
self.processing_queue.put((self.input.absolute(), self.output.absolute()))
|
||||||
|
|
||||||
# if input specified is a directory
|
# if input specified is a directory
|
||||||
@ -426,36 +435,63 @@ class Upscaler:
|
|||||||
|
|
||||||
# make output directory if it doesn't exist
|
# make output directory if it doesn't exist
|
||||||
self.output.mkdir(parents=True, exist_ok=True)
|
self.output.mkdir(parents=True, exist_ok=True)
|
||||||
for input_video in [f for f in self.input.iterdir() if f.is_file()]:
|
for input_path in [f for f in self.input.iterdir() if f.is_file()]:
|
||||||
output_video = self.output / input_video.name
|
output_path = self.output / input_path.name
|
||||||
self.processing_queue.put((input_video.absolute(), output_video.absolute()))
|
self.processing_queue.put((input_path.absolute(), output_path.absolute()))
|
||||||
|
|
||||||
# record video count for external calls
|
# check argument sanity before running
|
||||||
self.total_videos = self.processing_queue.qsize()
|
self._check_arguments()
|
||||||
|
|
||||||
|
# record file count for external calls
|
||||||
|
self.total_files = self.processing_queue.qsize()
|
||||||
|
|
||||||
|
try:
|
||||||
while not self.processing_queue.empty():
|
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'))
|
|
||||||
|
|
||||||
# run Anime4KCPP
|
# reset current processing progress for new job
|
||||||
self.process_pool.append(self.driver_object.upscale(self.current_input_video, output_video))
|
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
|
||||||
|
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]
|
||||||
|
|
||||||
|
# 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()
|
||||||
|
Avalon.info(_('Upscaling completed'))
|
||||||
|
|
||||||
|
# static images don't require GIF or video encoding
|
||||||
|
# go to the next task
|
||||||
|
self.processing_queue.task_done()
|
||||||
|
self.total_processed += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 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()
|
self._wait()
|
||||||
Avalon.info(_('Upscaling completed'))
|
Avalon.info(_('Upscaling completed'))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
try:
|
|
||||||
self.create_temp_directories()
|
self.create_temp_directories()
|
||||||
|
|
||||||
# initialize objects for ffmpeg and waifu2x-caffe
|
# get video information JSON using FFprobe
|
||||||
fm = Ffmpeg(self.ffmpeg_settings, self.image_format)
|
|
||||||
|
|
||||||
Avalon.info(_('Reading video information'))
|
Avalon.info(_('Reading video information'))
|
||||||
video_info = fm.get_video_info(self.current_input_video)
|
video_info = self.ffmpeg_object.probe_file_info(self.current_input_file)
|
||||||
# analyze original video with FFprobe and retrieve framerate
|
# analyze original video with FFprobe and retrieve framerate
|
||||||
# width, height = info['streams'][0]['width'], info['streams'][0]['height']
|
# width, height = info['streams'][0]['width'], info['streams'][0]['height']
|
||||||
|
|
||||||
@ -471,53 +507,74 @@ class Upscaler:
|
|||||||
Avalon.error(_('Aborting: No video stream found'))
|
Avalon.error(_('Aborting: No video stream found'))
|
||||||
raise StreamNotFoundError('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)))
|
|
||||||
self._wait()
|
|
||||||
|
|
||||||
# get average frame rate of video stream
|
# get average frame rate of video stream
|
||||||
framerate = float(Fraction(video_info['streams'][video_stream_index]['r_frame_rate']))
|
framerate = float(Fraction(video_info['streams'][video_stream_index]['r_frame_rate']))
|
||||||
fm.pixel_format = video_info['streams'][video_stream_index]['pix_fmt']
|
# 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':
|
if self.driver == 'waifu2x_caffe':
|
||||||
# get a dict of all pixel formats and corresponding bit depth
|
# get a dict of all pixel formats and corresponding bit depth
|
||||||
pixel_formats = fm.get_pixel_formats()
|
pixel_formats = self.ffmpeg_object.get_pixel_formats()
|
||||||
|
|
||||||
# try getting pixel format's corresponding bti depth
|
# try getting pixel format's corresponding bti depth
|
||||||
try:
|
try:
|
||||||
self.driver_settings['output_depth'] = pixel_formats[fm.pixel_format]
|
self.driver_settings['output_depth'] = pixel_formats[self.ffmpeg_object.pixel_format]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
Avalon.error(_('Unsupported pixel format: {}').format(fm.pixel_format))
|
Avalon.error(_('Unsupported pixel format: {}').format(self.ffmpeg_object.pixel_format))
|
||||||
raise UnsupportedPixelError(f'unsupported pixel format {fm.pixel_format}')
|
raise UnsupportedPixelError(f'unsupported pixel format {self.ffmpeg_object.pixel_format}')
|
||||||
|
|
||||||
Avalon.info(_('Framerate: {}').format(framerate))
|
Avalon.info(_('Framerate: {}').format(framerate))
|
||||||
|
|
||||||
# width/height will be coded width/height x upscale factor
|
# width/height will be coded width/height x upscale factor
|
||||||
original_width = video_info['streams'][video_stream_index]['width']
|
# original_width = video_info['streams'][video_stream_index]['width']
|
||||||
original_height = video_info['streams'][video_stream_index]['height']
|
# original_height = video_info['streams'][video_stream_index]['height']
|
||||||
scale_width = int(self.scale_ratio * original_width)
|
# scale_width = int(self.scale_ratio * original_width)
|
||||||
scale_height = int(self.scale_ratio * original_height)
|
# scale_height = int(self.scale_ratio * original_height)
|
||||||
|
|
||||||
# upscale images one by one using waifu2x
|
# upscale images one by one using waifu2x
|
||||||
Avalon.info(_('Starting to upscale extracted images'))
|
Avalon.info(_('Starting to upscale extracted frames'))
|
||||||
self._upscale_frames()
|
self._upscale_frames()
|
||||||
Avalon.info(_('Upscaling completed'))
|
Avalon.info(_('Upscaling completed'))
|
||||||
|
|
||||||
# frames to Video
|
# if file is none of: image, image/gif, video
|
||||||
Avalon.info(_('Converting extracted frames into 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
|
||||||
|
|
||||||
# use user defined output size
|
# start handling output
|
||||||
self.process_pool.append(fm.assemble_video(framerate,
|
# output can be either GIF or video
|
||||||
f'{scale_width}x{scale_height}',
|
|
||||||
self.upscaled_frames))
|
# 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()
|
self._wait()
|
||||||
Avalon.info(_('Conversion completed'))
|
Avalon.info(_('Conversion completed'))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# migrate audio tracks and subtitles
|
# migrate audio tracks and subtitles
|
||||||
Avalon.info(_('Migrating audio, subtitles and other streams to upscaled video'))
|
Avalon.info(_('Migrating audio, subtitles and other streams to upscaled video'))
|
||||||
self.process_pool.append(fm.migrate_streams(self.current_input_video,
|
self.process_pool.append(self.ffmpeg_object.migrate_streams(self.current_input_file,
|
||||||
output_video,
|
output_path,
|
||||||
self.upscaled_frames))
|
self.upscaled_frames))
|
||||||
self._wait()
|
self._wait()
|
||||||
|
|
||||||
@ -527,8 +584,12 @@ class Upscaler:
|
|||||||
Avalon.error(_('Failed to migrate streams'))
|
Avalon.error(_('Failed to migrate streams'))
|
||||||
Avalon.warning(_('Trying to output video without additional streams'))
|
Avalon.warning(_('Trying to output video without additional streams'))
|
||||||
|
|
||||||
|
if input_file_mime_type == 'image/gif':
|
||||||
|
(self.upscaled_frames / self.ffmpeg_object.intermediate_file_name).replace(output_path)
|
||||||
|
|
||||||
|
else:
|
||||||
# construct output file path
|
# construct output file path
|
||||||
output_video_path = output_video.parent / f'{output_video.stem}{fm.intermediate_file_name.suffix}'
|
output_video_path = output_path.parent / f'{output_path.stem}{self.ffmpeg_object.intermediate_file_name.suffix}'
|
||||||
|
|
||||||
# if output file already exists, cancel
|
# if output file already exists, cancel
|
||||||
if output_video_path.exists():
|
if output_video_path.exists():
|
||||||
@ -537,10 +598,12 @@ class Upscaler:
|
|||||||
# otherwise, rename intermediate file to the output file
|
# otherwise, rename intermediate file to the output file
|
||||||
else:
|
else:
|
||||||
Avalon.info(_('Writing intermediate file to: {}').format(output_video_path.absolute()))
|
Avalon.info(_('Writing intermediate file to: {}').format(output_video_path.absolute()))
|
||||||
(self.upscaled_frames / fm.intermediate_file_name).rename(output_video_path)
|
(self.upscaled_frames / self.ffmpeg_object.intermediate_file_name).rename(output_video_path)
|
||||||
|
|
||||||
# destroy temp directories
|
# increment total number of files processed
|
||||||
self.cleanup_temp_directories()
|
self.cleanup_temp_directories()
|
||||||
|
self.processing_queue.task_done()
|
||||||
|
self.total_processed += 1
|
||||||
|
|
||||||
except (Exception, KeyboardInterrupt, SystemExit) as e:
|
except (Exception, KeyboardInterrupt, SystemExit) as e:
|
||||||
with contextlib.suppress(ValueError):
|
with contextlib.suppress(ValueError):
|
||||||
@ -548,8 +611,5 @@ class Upscaler:
|
|||||||
self.running = False
|
self.running = False
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
# increment total number of videos processed
|
|
||||||
self.total_processed += 1
|
|
||||||
|
|
||||||
# signal upscaling completion
|
# signal upscaling completion
|
||||||
self.running = False
|
self.running = False
|
||||||
|
@ -13,7 +13,7 @@ __ __ _ _ ___ __ __
|
|||||||
Name: Video2X Controller
|
Name: Video2X Controller
|
||||||
Creator: K4YT3X
|
Creator: K4YT3X
|
||||||
Date Created: Feb 24, 2018
|
Date Created: Feb 24, 2018
|
||||||
Last Modified: May 10, 2020
|
Last Modified: May 11, 2020
|
||||||
|
|
||||||
Editor: BrianPetkovsek
|
Editor: BrianPetkovsek
|
||||||
Last Modified: June 17, 2019
|
Last Modified: June 17, 2019
|
||||||
@ -181,6 +181,10 @@ driver_settings['path'] = os.path.expandvars(driver_settings['path'])
|
|||||||
ffmpeg_settings = config['ffmpeg']
|
ffmpeg_settings = config['ffmpeg']
|
||||||
ffmpeg_settings['ffmpeg_path'] = os.path.expandvars(ffmpeg_settings['ffmpeg_path'])
|
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
|
# load video2x settings
|
||||||
image_format = config['video2x']['image_format'].lower()
|
image_format = config['video2x']['image_format'].lower()
|
||||||
preserve_frames = config['video2x']['preserve_frames']
|
preserve_frames = config['video2x']['preserve_frames']
|
||||||
@ -213,7 +217,8 @@ try:
|
|||||||
upscaler = Upscaler(input_path=video2x_args.input,
|
upscaler = Upscaler(input_path=video2x_args.input,
|
||||||
output_path=video2x_args.output,
|
output_path=video2x_args.output,
|
||||||
driver_settings=driver_settings,
|
driver_settings=driver_settings,
|
||||||
ffmpeg_settings=ffmpeg_settings)
|
ffmpeg_settings=ffmpeg_settings,
|
||||||
|
gifski_settings=gifski_settings)
|
||||||
|
|
||||||
# set upscaler optional options
|
# set upscaler optional options
|
||||||
upscaler.driver = video2x_args.driver
|
upscaler.driver = video2x_args.driver
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# Name: Video2X Configuration File
|
# Name: Video2X Configuration File
|
||||||
# Creator: K4YT3X
|
# Creator: K4YT3X
|
||||||
# Date Created: October 23, 2018
|
# 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
|
# Values here are the default values. Change the value here to
|
||||||
# save the default value permanently.
|
# save the default value permanently.
|
||||||
# Items commented out are parameters irrelevant to this context
|
# Items commented out are parameters irrelevant to this context
|
||||||
@ -87,7 +87,7 @@ anime4kcpp:
|
|||||||
zoomFactor: 2.0 # zoom factor for resizing (double [=2])
|
zoomFactor: 2.0 # zoom factor for resizing (double [=2])
|
||||||
threads: 16 # Threads count for video processing (unsigned int [=16])
|
threads: 16 # Threads count for video processing (unsigned int [=16])
|
||||||
fastMode: false # Faster but maybe low quality
|
fastMode: false # Faster but maybe low quality
|
||||||
videoMode: true # Video process
|
videoMode: false # Video process
|
||||||
preview: null # Preview image
|
preview: null # Preview image
|
||||||
preprocessing: False # Enable pre processing
|
preprocessing: False # Enable pre processing
|
||||||
postprocessing: False # Enable post processing
|
postprocessing: False # Enable post processing
|
||||||
@ -101,9 +101,9 @@ anime4kcpp:
|
|||||||
ffmpeg:
|
ffmpeg:
|
||||||
ffmpeg_path: '%LOCALAPPDATA%\video2x\ffmpeg-latest-win64-static\bin'
|
ffmpeg_path: '%LOCALAPPDATA%\video2x\ffmpeg-latest-win64-static\bin'
|
||||||
intermediate_file_name: 'intermediate.mkv'
|
intermediate_file_name: 'intermediate.mkv'
|
||||||
# step 1: extract all frames from original video
|
# step 1: extract all frames from original input
|
||||||
# into temporary directory
|
# into temporary directory
|
||||||
video_to_frames:
|
input_to_frames:
|
||||||
output_options:
|
output_options:
|
||||||
'-qscale:v': null
|
'-qscale:v': null
|
||||||
'-pix_fmt': rgba64be
|
'-pix_fmt': rgba64be
|
||||||
@ -138,6 +138,17 @@ ffmpeg:
|
|||||||
'-metadata': 'comment=Upscaled by Video2X'
|
'-metadata': 'comment=Upscaled by Video2X'
|
||||||
'-hwaccel': auto
|
'-hwaccel': auto
|
||||||
'-y': true
|
'-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:
|
||||||
video2x_cache_directory: null # default: %TEMP%\video2x
|
video2x_cache_directory: null # default: %TEMP%\video2x
|
||||||
image_format: png
|
image_format: png
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
Creator: Video2X GUI
|
Creator: Video2X GUI
|
||||||
Author: K4YT3X
|
Author: K4YT3X
|
||||||
Date Created: May 5, 2020
|
Date Created: May 5, 2020
|
||||||
Last Modified: May 10, 2020
|
Last Modified: May 11, 2020
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
@ -25,6 +25,7 @@ import yaml
|
|||||||
from PyQt5 import QtGui, uic
|
from PyQt5 import QtGui, uic
|
||||||
from PyQt5.QtCore import *
|
from PyQt5.QtCore import *
|
||||||
from PyQt5.QtWidgets import *
|
from PyQt5.QtWidgets import *
|
||||||
|
import magic
|
||||||
# QObject, pyqtSlot, pyqtSignal, QRunnable, QThreadPool, QAbstractTableModel, Qt
|
# QObject, pyqtSlot, pyqtSignal, QRunnable, QThreadPool, QAbstractTableModel, Qt
|
||||||
|
|
||||||
VERSION = '2.0.0'
|
VERSION = '2.0.0'
|
||||||
@ -110,13 +111,34 @@ class InputTableModel(QAbstractTableModel):
|
|||||||
def data(self, index, role):
|
def data(self, index, role):
|
||||||
if role == Qt.DisplayRole:
|
if role == Qt.DisplayRole:
|
||||||
|
|
||||||
|
file_path = self._data[index.row()]
|
||||||
|
|
||||||
if index.column() == 0:
|
if index.column() == 0:
|
||||||
return str(self._data[index.row()].absolute())
|
return str(file_path.absolute())
|
||||||
else:
|
else:
|
||||||
if self._data[index.row()].is_file():
|
|
||||||
return 'File'
|
# determine file type
|
||||||
elif self._data[index.row()].is_dir():
|
# if path is a folder
|
||||||
|
if file_path.is_dir():
|
||||||
return 'Folder'
|
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:
|
else:
|
||||||
return 'Unknown'
|
return 'Unknown'
|
||||||
|
|
||||||
@ -373,6 +395,10 @@ class Video2XMainWindow(QMainWindow):
|
|||||||
self.ffmpeg_settings = self.config['ffmpeg']
|
self.ffmpeg_settings = self.config['ffmpeg']
|
||||||
self.ffmpeg_settings['ffmpeg_path'] = str(pathlib.Path(os.path.expandvars(self.ffmpeg_settings['ffmpeg_path'])).absolute())
|
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
|
# set cache directory path
|
||||||
if self.config['video2x']['video2x_cache_directory'] is None:
|
if self.config['video2x']['video2x_cache_directory'] is None:
|
||||||
self.config['video2x']['video2x_cache_directory'] = str((pathlib.Path(tempfile.gettempdir()) / 'video2x').absolute())
|
self.config['video2x']['video2x_cache_directory'] = str((pathlib.Path(tempfile.gettempdir()) / 'video2x').absolute())
|
||||||
@ -585,7 +611,34 @@ class Video2XMainWindow(QMainWindow):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if input_path.is_file():
|
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():
|
elif input_path.is_dir():
|
||||||
output_path = input_path.parent / f'{input_path.stem}_output'
|
output_path = input_path.parent / f'{input_path.stem}_output'
|
||||||
|
|
||||||
@ -593,7 +646,7 @@ class Video2XMainWindow(QMainWindow):
|
|||||||
output_path_id = 0
|
output_path_id = 0
|
||||||
while output_path.exists() and output_path_id <= 1000:
|
while output_path.exists() and output_path_id <= 1000:
|
||||||
if input_path.is_file():
|
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():
|
elif input_path.is_dir():
|
||||||
output_path = input_path.parent / pathlib.Path(f'{input_path.stem}_output_{output_path_id}')
|
output_path = input_path.parent / pathlib.Path(f'{input_path.stem}_output_{output_path_id}')
|
||||||
output_path_id += 1
|
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_upscaled,
|
||||||
self.upscaler.total_frames,
|
self.upscaler.total_frames,
|
||||||
self.upscaler.total_processed,
|
self.upscaler.total_processed,
|
||||||
self.upscaler.total_videos,
|
self.upscaler.total_files,
|
||||||
self.upscaler.current_input_video,
|
self.upscaler.current_input_file,
|
||||||
self.upscaler.last_frame_upscaled))
|
self.upscaler.last_frame_upscaled))
|
||||||
time.sleep(1)
|
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_upscaled = progress_information[1]
|
||||||
total_frames = progress_information[2]
|
total_frames = progress_information[2]
|
||||||
total_processed = progress_information[3]
|
total_processed = progress_information[3]
|
||||||
total_videos = progress_information[4]
|
total_files = progress_information[4]
|
||||||
current_input_video = progress_information[5]
|
current_input_file = progress_information[5]
|
||||||
last_frame_upscaled = progress_information[6]
|
last_frame_upscaled = progress_information[6]
|
||||||
|
|
||||||
# calculate fields based on frames and time elapsed
|
# 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_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.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.rate_label.setText('Rate (FPS): {}'.format(round(rate, 2)))
|
||||||
self.overall_progress_label.setText('Overall Progress: {}/{}'.format(total_processed, total_videos))
|
self.overall_progress_label.setText('Overall Progress: {}/{}'.format(total_processed, total_files))
|
||||||
self.overall_progress_bar.setMaximum(total_videos)
|
self.overall_progress_bar.setMaximum(total_files)
|
||||||
self.overall_progress_bar.setValue(total_processed)
|
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 show frame is checked, show preview image
|
||||||
if self.frame_preview_show_preview_check_box.isChecked() and last_frame_upscaled.is_file():
|
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,
|
self.upscaler = Upscaler(input_path=input_directory,
|
||||||
output_path=output_directory,
|
output_path=output_directory,
|
||||||
driver_settings=self.driver_settings,
|
driver_settings=self.driver_settings,
|
||||||
ffmpeg_settings=self.ffmpeg_settings)
|
ffmpeg_settings=self.ffmpeg_settings,
|
||||||
|
gifski_settings=self.gifski_settings)
|
||||||
|
|
||||||
# set optional options
|
# set optional options
|
||||||
self.upscaler.driver = AVAILABLE_DRIVERS[self.driver_combo_box.currentText()]
|
self.upscaler.driver = AVAILABLE_DRIVERS[self.driver_combo_box.currentText()]
|
||||||
|
@ -31,6 +31,7 @@ import re
|
|||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import tarfile
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
@ -42,12 +43,19 @@ import zipfile
|
|||||||
# later in the script.
|
# later in the script.
|
||||||
# import requests
|
# import requests
|
||||||
|
|
||||||
VERSION = '1.8.0'
|
VERSION = '2.0.0'
|
||||||
|
|
||||||
# global static variables
|
# global static variables
|
||||||
LOCALAPPDATA = pathlib.Path(os.getenv('localappdata'))
|
LOCALAPPDATA = pathlib.Path(os.getenv('localappdata'))
|
||||||
VIDEO2X_CONFIG = pathlib.Path(__file__).parent.absolute() / 'video2x.yaml'
|
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():
|
def parse_arguments():
|
||||||
@ -76,32 +84,21 @@ class Video2xSetup:
|
|||||||
self.trash = []
|
self.trash = []
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
# regardless of which driver to install
|
||||||
|
# always ensure Python modules are installed and up-to-date
|
||||||
if self.download_python_modules:
|
if self.download_python_modules:
|
||||||
print('\nInstalling Python libraries')
|
print('\nInstalling Python libraries')
|
||||||
self._install_python_requirements()
|
self._install_python_requirements()
|
||||||
|
|
||||||
|
# if all drivers are to be installed
|
||||||
if self.driver == 'all':
|
if self.driver == 'all':
|
||||||
self._install_ffmpeg()
|
DRIVER_OPTIONS.remove('all')
|
||||||
self._install_waifu2x_caffe()
|
for driver in DRIVER_OPTIONS:
|
||||||
self._install_waifu2x_converter_cpp()
|
getattr(self, f'_install_{driver}')()
|
||||||
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()
|
|
||||||
|
|
||||||
print('\nGenerating Video2X configuration file')
|
# install only the selected driver
|
||||||
self._generate_config()
|
else:
|
||||||
|
getattr(self, f'_install_{self.driver}')()
|
||||||
|
|
||||||
print('\nCleaning up temporary files')
|
print('\nCleaning up temporary files')
|
||||||
self._cleanup()
|
self._cleanup()
|
||||||
@ -139,6 +136,22 @@ class Video2xSetup:
|
|||||||
with zipfile.ZipFile(ffmpeg_zip) as zipf:
|
with zipfile.ZipFile(ffmpeg_zip) as zipf:
|
||||||
zipf.extractall(LOCALAPPDATA / 'video2x')
|
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):
|
def _install_waifu2x_caffe(self):
|
||||||
""" Install waifu2x_caffe
|
""" Install waifu2x_caffe
|
||||||
"""
|
"""
|
||||||
@ -248,42 +261,6 @@ class Video2xSetup:
|
|||||||
# rename the newly extracted directory
|
# rename the newly extracted directory
|
||||||
(LOCALAPPDATA / 'video2x' / zipf.namelist()[0]).rename(srmd_ncnn_vulkan_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):
|
def download(url, save_path, chunk_size=4096):
|
||||||
""" Download file to local with requests library
|
""" Download file to local with requests library
|
||||||
|
@ -69,6 +69,10 @@ class WrapperMain:
|
|||||||
self.driver_settings['zoomFactor'] = upscaler.scale_ratio
|
self.driver_settings['zoomFactor'] = upscaler.scale_ratio
|
||||||
self.driver_settings['threads'] = upscaler.processes
|
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):
|
def upscale(self, input_file, output_file):
|
||||||
"""This is the core function for WAIFU2X class
|
"""This is the core function for WAIFU2X class
|
||||||
|
|
||||||
@ -90,14 +94,14 @@ class WrapperMain:
|
|||||||
|
|
||||||
# list to be executed
|
# list to be executed
|
||||||
# initialize the list with waifu2x binary path as the first element
|
# 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():
|
for key in self.driver_settings.keys():
|
||||||
|
|
||||||
value = self.driver_settings[key]
|
value = self.driver_settings[key]
|
||||||
|
|
||||||
# null or None means that leave this option out (keep default)
|
# 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
|
continue
|
||||||
else:
|
else:
|
||||||
if len(key) == 1:
|
if len(key) == 1:
|
||||||
|
@ -12,6 +12,7 @@ Description: This class handles all FFmpeg related operations.
|
|||||||
# built-in imports
|
# built-in imports
|
||||||
import json
|
import json
|
||||||
import pathlib
|
import pathlib
|
||||||
|
import shlex
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
# third-party imports
|
# third-party imports
|
||||||
@ -36,7 +37,7 @@ class Ffmpeg:
|
|||||||
# video metadata
|
# video metadata
|
||||||
self.image_format = image_format
|
self.image_format = image_format
|
||||||
self.intermediate_file_name = pathlib.Path(self.ffmpeg_settings['intermediate_file_name'])
|
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):
|
def get_pixel_formats(self):
|
||||||
""" Get a dictionary of supported pixel formats
|
""" Get a dictionary of supported pixel formats
|
||||||
@ -49,8 +50,8 @@ class Ffmpeg:
|
|||||||
"""
|
"""
|
||||||
execute = [
|
execute = [
|
||||||
self.ffmpeg_probe_binary,
|
self.ffmpeg_probe_binary,
|
||||||
'-v',
|
# '-v',
|
||||||
'quiet',
|
# 'quiet',
|
||||||
'-pix_fmts'
|
'-pix_fmts'
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -74,7 +75,7 @@ class Ffmpeg:
|
|||||||
|
|
||||||
return pixel_formats
|
return pixel_formats
|
||||||
|
|
||||||
def get_video_info(self, input_video):
|
def probe_file_info(self, input_video):
|
||||||
""" Gets input video information
|
""" Gets input video information
|
||||||
|
|
||||||
This method reads input video information
|
This method reads input video information
|
||||||
@ -104,31 +105,25 @@ class Ffmpeg:
|
|||||||
# turn elements into str
|
# turn elements into str
|
||||||
execute = [str(e) for e in execute]
|
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
|
json_str = subprocess.run(execute, check=True, stdout=subprocess.PIPE).stdout
|
||||||
return json.loads(json_str.decode('utf-8'))
|
return json.loads(json_str.decode('utf-8'))
|
||||||
|
|
||||||
def extract_frames(self, input_video, extracted_frames):
|
def extract_frames(self, input_file, extracted_frames):
|
||||||
"""Extract every frame from original videos
|
""" extract frames from video or GIF file
|
||||||
|
|
||||||
This method extracts every frame from input video using FFmpeg
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
input_video {string} -- input video path
|
|
||||||
extracted_frames {string} -- video output directory
|
|
||||||
"""
|
"""
|
||||||
execute = [
|
execute = [
|
||||||
self.ffmpeg_binary
|
self.ffmpeg_binary
|
||||||
]
|
]
|
||||||
|
|
||||||
execute.extend(self._read_configuration(phase='video_to_frames'))
|
execute.extend(self._read_configuration(phase='input_to_frames'))
|
||||||
|
|
||||||
execute.extend([
|
execute.extend([
|
||||||
'-i',
|
'-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([
|
execute.extend([
|
||||||
extracted_frames / f'extracted_%0d.{self.image_format}'
|
extracted_frames / f'extracted_%0d.{self.image_format}'
|
||||||
@ -136,7 +131,7 @@ class Ffmpeg:
|
|||||||
|
|
||||||
return(self._execute(execute))
|
return(self._execute(execute))
|
||||||
|
|
||||||
def assemble_video(self, framerate, resolution, upscaled_frames):
|
def assemble_video(self, framerate, upscaled_frames):
|
||||||
"""Converts images into videos
|
"""Converts images into videos
|
||||||
|
|
||||||
This method converts a set of images into a video
|
This method converts a set of images into a video
|
||||||
@ -149,9 +144,9 @@ class Ffmpeg:
|
|||||||
execute = [
|
execute = [
|
||||||
self.ffmpeg_binary,
|
self.ffmpeg_binary,
|
||||||
'-r',
|
'-r',
|
||||||
str(framerate),
|
str(framerate)
|
||||||
'-s',
|
# '-s',
|
||||||
resolution
|
# resolution
|
||||||
]
|
]
|
||||||
|
|
||||||
# read other options
|
# read other options
|
||||||
@ -274,17 +269,9 @@ class Ffmpeg:
|
|||||||
return configuration
|
return configuration
|
||||||
|
|
||||||
def _execute(self, execute):
|
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
|
# turn all list elements into string to avoid errors
|
||||||
execute = [str(e) for e in execute]
|
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)
|
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
|
# list to be executed
|
||||||
# initialize the list with the binary path as the first element
|
# 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():
|
for key in self.driver_settings.keys():
|
||||||
|
|
||||||
value = self.driver_settings[key]
|
value = self.driver_settings[key]
|
||||||
|
|
||||||
# null or None means that leave this option out (keep default)
|
# 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
|
continue
|
||||||
else:
|
else:
|
||||||
if len(key) == 1:
|
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('-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('-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('-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('-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_file', type=str, help=argparse.SUPPRESS) # help='(required) path to input image file')
|
parser.add_argument('-i', '--input_path', type=str, help=argparse.SUPPRESS) # help='(required) path to input image file')
|
||||||
return parser.parse_args(arguments)
|
return parser.parse_args(arguments)
|
||||||
|
|
||||||
def load_configurations(self, upscaler):
|
def load_configurations(self, upscaler):
|
||||||
@ -79,14 +79,14 @@ class WrapperMain:
|
|||||||
|
|
||||||
# list to be executed
|
# list to be executed
|
||||||
# initialize the list with waifu2x binary path as the first element
|
# 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():
|
for key in self.driver_settings.keys():
|
||||||
|
|
||||||
value = self.driver_settings[key]
|
value = self.driver_settings[key]
|
||||||
|
|
||||||
# null or None means that leave this option out (keep default)
|
# 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
|
continue
|
||||||
else:
|
else:
|
||||||
if len(key) == 1:
|
if len(key) == 1:
|
||||||
|
@ -93,14 +93,14 @@ class WrapperMain:
|
|||||||
|
|
||||||
# list to be executed
|
# list to be executed
|
||||||
# initialize the list with waifu2x binary path as the first element
|
# 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():
|
for key in self.driver_settings.keys():
|
||||||
|
|
||||||
value = self.driver_settings[key]
|
value = self.driver_settings[key]
|
||||||
|
|
||||||
# null or None means that leave this option out (keep default)
|
# 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
|
continue
|
||||||
else:
|
else:
|
||||||
if len(key) == 1:
|
if len(key) == 1:
|
||||||
|
@ -80,14 +80,14 @@ class WrapperMain:
|
|||||||
|
|
||||||
# list to be executed
|
# list to be executed
|
||||||
# initialize the list with waifu2x binary path as the first element
|
# 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():
|
for key in self.driver_settings.keys():
|
||||||
|
|
||||||
value = self.driver_settings[key]
|
value = self.driver_settings[key]
|
||||||
|
|
||||||
# null or None means that leave this option out (keep default)
|
# 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
|
continue
|
||||||
else:
|
else:
|
||||||
if len(key) == 1:
|
if len(key) == 1:
|
||||||
|
Loading…
Reference in New Issue
Block a user