mirror of
https://github.com/k4yt3x/video2x.git
synced 2024-12-29 16:09:10 +00:00
commit
734cf7bb0e
14
README.md
14
README.md
@ -18,10 +18,16 @@ Component names that are **bolded** can be automatically downloaded and configur
|
|||||||
2. AMD GPU / Nvidia GPU
|
2. AMD GPU / Nvidia GPU
|
||||||
3. AMD GPU driver / Nvidia GPU driver / Nvidia CUDNN
|
3. AMD GPU driver / Nvidia GPU driver / Nvidia CUDNN
|
||||||
4. [**FFmpeg**](https://ffmpeg.zeranoe.com/builds/)
|
4. [**FFmpeg**](https://ffmpeg.zeranoe.com/builds/)
|
||||||
5. [**waifu2x-caffe**](https://github.com/lltcggie/waifu2x-caffe/releases) / [**waifu2x-converter-cpp**](https://github.com/DeadSix27/waifu2x-converter-cpp/releases)
|
5. [**waifu2x-caffe**](https://github.com/lltcggie/waifu2x-caffe/releases) / [**waifu2x-converter-cpp**](https://github.com/DeadSix27/waifu2x-converter-cpp/releases) / [**waifu2x-ncnn-vulkan**](https://github.com/nihui/waifu2x-ncnn-vulkan)
|
||||||
|
|
||||||
## Recent Changes
|
## Recent Changes
|
||||||
|
|
||||||
|
### 2.9.0 (July 27, 2019)
|
||||||
|
|
||||||
|
- Changed file handling method from `os` to `pathlib`
|
||||||
|
- Removed f_string dependency and support for legacy versions of Python
|
||||||
|
- Organized file import statements
|
||||||
|
|
||||||
### 2.8.1 (July 9, 2019)
|
### 2.8.1 (July 9, 2019)
|
||||||
|
|
||||||
- Added automatic pixel format detection
|
- Added automatic pixel format detection
|
||||||
@ -35,12 +41,6 @@ Component names that are **bolded** can be automatically downloaded and configur
|
|||||||
|
|
||||||
- Fixed video2x custom temp folder bug found by @cr08 .
|
- Fixed video2x custom temp folder bug found by @cr08 .
|
||||||
|
|
||||||
### 2.7.0 (March 30, 2019)
|
|
||||||
|
|
||||||
- Added support for different extracted image formats.
|
|
||||||
- Redesigned FFmpeg wrapper, FFmpeg settings are now customizable in the `video2x.json` config file.
|
|
||||||
- Other minor enhancements and adjustments (e.g. argument -> method variable)
|
|
||||||
|
|
||||||
### Setup Script 1.3.0 (June 25, 2019)
|
### Setup Script 1.3.0 (June 25, 2019)
|
||||||
|
|
||||||
- Added automatic installation support for `waifu2x-ncnn-vulkan`
|
- Added automatic installation support for `waifu2x-ncnn-vulkan`
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: future_fstrings -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Name: Video2X Exceptions
|
Name: Video2X Exceptions
|
||||||
Dev: K4YT3X
|
Dev: K4YT3X
|
||||||
Date Created: December 13, 2018
|
Date Created: December 13, 2018
|
||||||
Last Modified: March 19, 2019
|
Last Modified: July 27, 2019
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,25 +1,28 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: future_fstrings -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Name: FFMPEG Class
|
Name: Video2X FFmpeg Controller
|
||||||
Author: K4YT3X
|
Author: K4YT3X
|
||||||
Date Created: Feb 24, 2018
|
Date Created: Feb 24, 2018
|
||||||
Last Modified: July 26, 2019
|
Last Modified: July 27, 2019
|
||||||
|
|
||||||
Description: This class handles all FFmpeg related operations.
|
Description: This class handles all FFmpeg related operations.
|
||||||
"""
|
"""
|
||||||
from avalon_framework import Avalon
|
|
||||||
|
# built-in imports
|
||||||
import json
|
import json
|
||||||
import subprocess
|
|
||||||
import os
|
import os
|
||||||
|
import pathlib
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
# third-party imports
|
||||||
|
from avalon_framework import Avalon
|
||||||
|
|
||||||
|
|
||||||
class Ffmpeg:
|
class Ffmpeg:
|
||||||
"""This class communicates with FFmpeg
|
"""This class communicates with FFmpeg
|
||||||
|
|
||||||
This class deals with FFmpeg. It handles extracitng
|
This class deals with FFmpeg. It handles extracting
|
||||||
frames, stripping audio, converting images into videos
|
frames, stripping audio, converting images into videos
|
||||||
and inserting audio tracks to videos.
|
and inserting audio tracks to videos.
|
||||||
"""
|
"""
|
||||||
@ -27,18 +30,17 @@ class Ffmpeg:
|
|||||||
def __init__(self, ffmpeg_settings, image_format):
|
def __init__(self, ffmpeg_settings, image_format):
|
||||||
self.ffmpeg_settings = ffmpeg_settings
|
self.ffmpeg_settings = ffmpeg_settings
|
||||||
|
|
||||||
self.ffmpeg_path = self.ffmpeg_settings['ffmpeg_path']
|
self.ffmpeg_path = pathlib.Path(self.ffmpeg_settings['ffmpeg_path'])
|
||||||
|
self.ffmpeg_binary = self.ffmpeg_path / 'ffmpeg.exe'
|
||||||
self.ffmpeg_binary = os.path.join(self.ffmpeg_path, 'ffmpeg.exe')
|
self.ffmpeg_probe_binary = self.ffmpeg_path / 'ffprobe.exe'
|
||||||
self.ffmpeg_probe_binary = os.path.join(self.ffmpeg_path, 'ffprobe.exe')
|
|
||||||
self.image_format = image_format
|
self.image_format = image_format
|
||||||
self.pixel_format = None
|
self.pixel_format = None
|
||||||
|
|
||||||
def get_pixel_formats(self):
|
def get_pixel_formats(self):
|
||||||
""" Get a dictionary of supported pixel formats
|
""" Get a dictionary of supported pixel formats
|
||||||
|
|
||||||
List all supported pixel formats and their corresponding
|
List all supported pixel formats and their
|
||||||
bit depth.
|
corresponding bit depth.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dictionary -- JSON dict of all pixel formats to bit depth
|
dictionary -- JSON dict of all pixel formats to bit depth
|
||||||
@ -50,6 +52,9 @@ class Ffmpeg:
|
|||||||
'-pix_fmts'
|
'-pix_fmts'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# turn elements into str
|
||||||
|
execute = [str(e) for e in execute]
|
||||||
|
|
||||||
Avalon.debug_info(f'Executing: {" ".join(execute)}')
|
Avalon.debug_info(f'Executing: {" ".join(execute)}')
|
||||||
|
|
||||||
# initialize dictionary to store pixel formats
|
# initialize dictionary to store pixel formats
|
||||||
@ -71,7 +76,7 @@ class Ffmpeg:
|
|||||||
""" Gets input video information
|
""" Gets input video information
|
||||||
|
|
||||||
This method reads input video information
|
This method reads input video information
|
||||||
using ffprobe in dictionary.
|
using ffprobe in dictionary
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
input_video {string} -- input video file path
|
input_video {string} -- input video file path
|
||||||
@ -94,6 +99,9 @@ class Ffmpeg:
|
|||||||
input_video
|
input_video
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# turn elements into str
|
||||||
|
execute = [str(e) for e in execute]
|
||||||
|
|
||||||
Avalon.debug_info(f'Executing: {" ".join(execute)}')
|
Avalon.debug_info(f'Executing: {" ".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'))
|
||||||
@ -101,8 +109,7 @@ class Ffmpeg:
|
|||||||
def extract_frames(self, input_video, extracted_frames):
|
def extract_frames(self, input_video, extracted_frames):
|
||||||
"""Extract every frame from original videos
|
"""Extract every frame from original videos
|
||||||
|
|
||||||
This method extracts every frame from videoin
|
This method extracts every frame from input video using FFmpeg
|
||||||
using FFmpeg
|
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
input_video {string} -- input video path
|
input_video {string} -- input video path
|
||||||
@ -120,7 +127,7 @@ class Ffmpeg:
|
|||||||
execute.extend(self._read_configuration(phase='video_to_frames', section='output_options'))
|
execute.extend(self._read_configuration(phase='video_to_frames', section='output_options'))
|
||||||
|
|
||||||
execute.extend([
|
execute.extend([
|
||||||
os.path.join(extracted_frames, f'extracted_%0d.{self.image_format}')
|
extracted_frames / f'extracted_%0d.{self.image_format}'
|
||||||
])
|
])
|
||||||
|
|
||||||
execute.extend(self._read_configuration(phase='video_to_frames'))
|
execute.extend(self._read_configuration(phase='video_to_frames'))
|
||||||
@ -130,8 +137,7 @@ class Ffmpeg:
|
|||||||
def convert_video(self, framerate, resolution, upscaled_frames):
|
def convert_video(self, framerate, resolution, upscaled_frames):
|
||||||
"""Converts images into videos
|
"""Converts images into videos
|
||||||
|
|
||||||
This method converts a set of images into a
|
This method converts a set of images into a video
|
||||||
video.
|
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
framerate {float} -- target video framerate
|
framerate {float} -- target video framerate
|
||||||
@ -150,17 +156,19 @@ class Ffmpeg:
|
|||||||
execute.extend(self._read_configuration(phase='frames_to_video', section='input_options'))
|
execute.extend(self._read_configuration(phase='frames_to_video', section='input_options'))
|
||||||
|
|
||||||
# WORKAROUND FOR WAIFU2X-NCNN-VULKAN
|
# WORKAROUND FOR WAIFU2X-NCNN-VULKAN
|
||||||
|
# Dev: SAT3LL
|
||||||
|
# rename all .png.png suffixes to .png
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
regex = re.compile(r'\.png\.png$')
|
regex = re.compile(r'\.png\.png$')
|
||||||
for raw_frame in os.listdir(upscaled_frames):
|
for frame_name in upscaled_frames.iterdir():
|
||||||
shutil.move(os.path.join(upscaled_frames, raw_frame), os.path.join(upscaled_frames, regex.sub('.png', raw_frame)))
|
(upscaled_frames / frame_name).rename(upscaled_frames / regex.sub('.png', str(frame_name)))
|
||||||
# END WORKAROUND
|
# END WORKAROUND
|
||||||
|
|
||||||
# append input frames path into command
|
# append input frames path into command
|
||||||
execute.extend([
|
execute.extend([
|
||||||
'-i',
|
'-i',
|
||||||
os.path.join(upscaled_frames, f'extracted_%d.{self.image_format}')
|
upscaled_frames / f'extracted_%d.{self.image_format}'
|
||||||
])
|
])
|
||||||
|
|
||||||
# read FFmpeg output options
|
# read FFmpeg output options
|
||||||
@ -171,7 +179,7 @@ class Ffmpeg:
|
|||||||
|
|
||||||
# specify output file location
|
# specify output file location
|
||||||
execute.extend([
|
execute.extend([
|
||||||
os.path.join(upscaled_frames, 'no_audio.mp4')
|
upscaled_frames / 'no_audio.mp4'
|
||||||
])
|
])
|
||||||
|
|
||||||
self._execute(execute)
|
self._execute(execute)
|
||||||
@ -187,7 +195,7 @@ class Ffmpeg:
|
|||||||
execute = [
|
execute = [
|
||||||
self.ffmpeg_binary,
|
self.ffmpeg_binary,
|
||||||
'-i',
|
'-i',
|
||||||
os.path.join(upscaled_frames, 'no_audio.mp4'),
|
upscaled_frames / 'no_audio.mp4',
|
||||||
'-i',
|
'-i',
|
||||||
input_video
|
input_video
|
||||||
]
|
]
|
||||||
@ -249,7 +257,7 @@ class Ffmpeg:
|
|||||||
if value is not True:
|
if value is not True:
|
||||||
configuration.append(str(subvalue))
|
configuration.append(str(subvalue))
|
||||||
|
|
||||||
# otherwise the value is typical
|
# otherwise the value is typical
|
||||||
else:
|
else:
|
||||||
configuration.append(key)
|
configuration.append(key)
|
||||||
|
|
||||||
@ -271,4 +279,8 @@ class Ffmpeg:
|
|||||||
int -- execution return code
|
int -- execution return code
|
||||||
"""
|
"""
|
||||||
Avalon.debug_info(f'Executing: {execute}')
|
Avalon.debug_info(f'Executing: {execute}')
|
||||||
|
|
||||||
|
# turn all list elements into string to avoid errors
|
||||||
|
execute = [str(e) for e in execute]
|
||||||
|
|
||||||
return subprocess.run(execute, shell=True, check=True).returncode
|
return subprocess.run(execute, shell=True, check=True).returncode
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: future_fstrings -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Name: Video2X Image Cleaner
|
Name: Video2X Image Cleaner
|
||||||
Author: BrianPetkovsek
|
Author: BrianPetkovsek
|
||||||
Author: K4YT3X
|
Author: K4YT3X
|
||||||
Date Created: March 24, 2019
|
Date Created: March 24, 2019
|
||||||
Last Modified: April 28, 2019
|
Last Modified: July 27, 2019
|
||||||
|
|
||||||
Description: This class is to remove the extracted frames
|
Description: This class is to remove the extracted frames
|
||||||
that have already been upscaled.
|
that have already been upscaled.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# built-in imports
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
@ -60,19 +59,19 @@ class ImageCleaner(threading.Thread):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# list all images in the extracted frames
|
# list all images in the extracted frames
|
||||||
output_frames = [f for f in os.listdir(self.output_directory) if os.path.isfile(os.path.join(self.output_directory, f))]
|
output_frames = [f for f in self.output_directory.iterdir() if f.is_file()]
|
||||||
|
|
||||||
# compare and remove frames downscaled images that finished being upscaled
|
# compare and remove frames downscaled images that finished being upscaled
|
||||||
# within each thread's extracted frames directory
|
# within each thread's extracted frames directory
|
||||||
for i in range(self.threads):
|
for thread_id in range(self.threads):
|
||||||
dir_path = os.path.join(self.input_directory, str(i))
|
dir_path = self.input_directory / str(thread_id)
|
||||||
|
|
||||||
# for each file within all the directories
|
# for each file within all the directories
|
||||||
for f in os.listdir(dir_path):
|
for file in dir_path.iterdir():
|
||||||
file_path = os.path.join(dir_path, f)
|
file_path = dir_path / file
|
||||||
|
|
||||||
# if file also exists in the output directory, then the file
|
# if file also exists in the output directory, then the file
|
||||||
# has already been processed, thus not needed anymore
|
# has already been processed, thus not needed anymore
|
||||||
if os.path.isfile(file_path) and f in output_frames:
|
if file_path.is_file() and file in output_frames:
|
||||||
os.remove(file_path)
|
file_path.unlink(file)
|
||||||
output_frames.remove(f)
|
output_frames.remove(file)
|
||||||
|
@ -4,4 +4,3 @@ GPUtil
|
|||||||
psutil
|
psutil
|
||||||
requests
|
requests
|
||||||
tqdm
|
tqdm
|
||||||
future-fstrings>=1.1.0
|
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: future_fstrings -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Name: Video2X Upscaler
|
Name: Video2X Upscaler
|
||||||
Author: K4YT3X
|
Author: K4YT3X
|
||||||
Date Created: December 10, 2018
|
Date Created: December 10, 2018
|
||||||
Last Modified: July 9, 2019
|
Last Modified: July 27, 2019
|
||||||
|
|
||||||
Dev: SAT3LL
|
Dev: SAT3LL
|
||||||
|
|
||||||
@ -16,22 +14,28 @@ Licensed under the GNU General Public License Version 3 (GNU GPL v3),
|
|||||||
(C) 2018-2019 K4YT3X
|
(C) 2018-2019 K4YT3X
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from avalon_framework import Avalon
|
# local imports
|
||||||
from exceptions import *
|
from exceptions import *
|
||||||
from ffmpeg import Ffmpeg
|
from ffmpeg import Ffmpeg
|
||||||
from fractions import Fraction
|
|
||||||
from image_cleaner import ImageCleaner
|
from image_cleaner import ImageCleaner
|
||||||
from tqdm import tqdm
|
|
||||||
from waifu2x_caffe import Waifu2xCaffe
|
from waifu2x_caffe import Waifu2xCaffe
|
||||||
from waifu2x_converter import Waifu2xConverter
|
from waifu2x_converter import Waifu2xConverter
|
||||||
from waifu2x_ncnn_vulkan import Waifu2xNcnnVulkan
|
from waifu2x_ncnn_vulkan import Waifu2xNcnnVulkan
|
||||||
|
|
||||||
|
# built-in imports
|
||||||
|
from fractions import Fraction
|
||||||
import copy
|
import copy
|
||||||
import os
|
import pathlib
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
# third-party imports
|
||||||
|
from avalon_framework import Avalon
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
|
||||||
class Upscaler:
|
class Upscaler:
|
||||||
@ -58,16 +62,16 @@ class Upscaler:
|
|||||||
self.scale_ratio = None
|
self.scale_ratio = None
|
||||||
self.model_dir = None
|
self.model_dir = None
|
||||||
self.threads = 5
|
self.threads = 5
|
||||||
self.video2x_cache_directory = os.path.join(tempfile.gettempdir(), 'video2x')
|
self.video2x_cache_directory = pathlib.Path(tempfile.gettempdir()) / 'video2x'
|
||||||
self.image_format = 'png'
|
self.image_format = 'png'
|
||||||
self.preserve_frames = False
|
self.preserve_frames = False
|
||||||
|
|
||||||
def create_temp_directories(self):
|
def create_temp_directories(self):
|
||||||
"""create temporary directory
|
"""create temporary directory
|
||||||
"""
|
"""
|
||||||
self.extracted_frames = tempfile.mkdtemp(dir=self.video2x_cache_directory)
|
self.extracted_frames = pathlib.Path(tempfile.mkdtemp(dir=self.video2x_cache_directory))
|
||||||
Avalon.debug_info(f'Extracted frames are being saved to: {self.extracted_frames}')
|
Avalon.debug_info(f'Extracted frames are being saved to: {self.extracted_frames}')
|
||||||
self.upscaled_frames = tempfile.mkdtemp(dir=self.video2x_cache_directory)
|
self.upscaled_frames = pathlib.Path(tempfile.mkdtemp(dir=self.video2x_cache_directory))
|
||||||
Avalon.debug_info(f'Upscaled frames are being saved to: {self.upscaled_frames}')
|
Avalon.debug_info(f'Upscaled frames are being saved to: {self.upscaled_frames}')
|
||||||
|
|
||||||
def cleanup_temp_directories(self):
|
def cleanup_temp_directories(self):
|
||||||
@ -81,7 +85,8 @@ class Upscaler:
|
|||||||
print(f'Cleaning up cache directory: {directory}')
|
print(f'Cleaning up cache directory: {directory}')
|
||||||
shutil.rmtree(directory)
|
shutil.rmtree(directory)
|
||||||
except (OSError, FileNotFoundError):
|
except (OSError, FileNotFoundError):
|
||||||
pass
|
print(f'Unable to delete: {directory}')
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
def _check_arguments(self):
|
def _check_arguments(self):
|
||||||
# check if arguments are valid / all necessary argument
|
# check if arguments are valid / all necessary argument
|
||||||
@ -106,7 +111,7 @@ class Upscaler:
|
|||||||
# get number of extracted frames
|
# get number of extracted frames
|
||||||
total_frames = 0
|
total_frames = 0
|
||||||
for directory in extracted_frames_directories:
|
for directory in extracted_frames_directories:
|
||||||
total_frames += len([f for f in os.listdir(directory) if f[-4:] == f'.{self.image_format}'])
|
total_frames += len([f for f in directory.iterdir() if str(f)[-4:] == f'.{self.image_format}'])
|
||||||
|
|
||||||
with tqdm(total=total_frames, ascii=True, desc='Upscaling Progress') as progress_bar:
|
with tqdm(total=total_frames, ascii=True, desc='Upscaling Progress') as progress_bar:
|
||||||
|
|
||||||
@ -117,7 +122,7 @@ class Upscaler:
|
|||||||
while not self.progress_bar_exit_signal:
|
while not self.progress_bar_exit_signal:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
total_frames_upscaled = len([f for f in os.listdir(self.upscaled_frames) if f[-4:] == f'.{self.image_format}'])
|
total_frames_upscaled = len([f for f in self.upscaled_frames.iterdir() if str(f)[-4:] == f'.{self.image_format}'])
|
||||||
delta = total_frames_upscaled - previous_cycle_frames
|
delta = total_frames_upscaled - previous_cycle_frames
|
||||||
previous_cycle_frames = total_frames_upscaled
|
previous_cycle_frames = total_frames_upscaled
|
||||||
|
|
||||||
@ -163,9 +168,9 @@ class Upscaler:
|
|||||||
progress_bar.start()
|
progress_bar.start()
|
||||||
|
|
||||||
w2.upscale(self.extracted_frames, self.upscaled_frames, self.scale_ratio, self.threads, self.image_format, self.upscaler_exceptions)
|
w2.upscale(self.extracted_frames, self.upscaled_frames, self.scale_ratio, self.threads, self.image_format, self.upscaler_exceptions)
|
||||||
for image in [f for f in os.listdir(self.upscaled_frames) if os.path.isfile(os.path.join(self.upscaled_frames, f))]:
|
for image in [f for f in self.upscaled_frames.iterdir() if f.is_file()]:
|
||||||
renamed = re.sub(f'_\[.*-.*\]\[x(\d+(\.\d+)?)\]\.{self.image_format}', f'.{self.image_format}', image)
|
renamed = re.sub(f'_\[.*-.*\]\[x(\d+(\.\d+)?)\]\.{self.image_format}', f'.{self.image_format}', image)
|
||||||
shutil.move(os.path.join(self.upscaled_frames, image), os.path.join(self.upscaled_frames, renamed))
|
(self.upscaled_frames / image).rename(self.upscaled_frames / renamed)
|
||||||
|
|
||||||
self.progress_bar_exit_signal = True
|
self.progress_bar_exit_signal = True
|
||||||
progress_bar.join()
|
progress_bar.join()
|
||||||
@ -175,7 +180,7 @@ class Upscaler:
|
|||||||
upscaler_threads = []
|
upscaler_threads = []
|
||||||
|
|
||||||
# list all images in the extracted frames
|
# list all images in the extracted frames
|
||||||
frames = [os.path.join(self.extracted_frames, f) for f in os.listdir(self.extracted_frames) if os.path.isfile(os.path.join(self.extracted_frames, f))]
|
frames = [(self.extracted_frames / f) for f in self.extracted_frames.iterdir() if f.is_file]
|
||||||
|
|
||||||
# if we have less images than threads,
|
# if we have less images than threads,
|
||||||
# create only the threads necessary
|
# create only the threads necessary
|
||||||
@ -188,13 +193,13 @@ class Upscaler:
|
|||||||
thread_pool = []
|
thread_pool = []
|
||||||
thread_directories = []
|
thread_directories = []
|
||||||
for thread_id in range(self.threads):
|
for thread_id in range(self.threads):
|
||||||
thread_directory = os.path.join(self.extracted_frames, str(thread_id))
|
thread_directory = self.extracted_frames / str(thread_id)
|
||||||
thread_directories.append(thread_directory)
|
thread_directories.append(thread_directory)
|
||||||
|
|
||||||
# delete old directories and create new directories
|
# delete old directories and create new directories
|
||||||
if os.path.isdir(thread_directory):
|
if thread_directory.is_dir():
|
||||||
shutil.rmtree(thread_directory)
|
shutil.rmtree(thread_directory)
|
||||||
os.mkdir(thread_directory)
|
thread_directory.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
# append directory path into list
|
# append directory path into list
|
||||||
thread_pool.append((thread_directory, thread_id))
|
thread_pool.append((thread_directory, thread_id))
|
||||||
@ -203,7 +208,7 @@ class Upscaler:
|
|||||||
# until there is none left in the directory
|
# until there is none left in the directory
|
||||||
for image in frames:
|
for image in frames:
|
||||||
# move image
|
# move image
|
||||||
shutil.move(image, thread_pool[0][0])
|
image.rename(thread_pool[0][0] / image.name)
|
||||||
# rotate list
|
# rotate list
|
||||||
thread_pool = thread_pool[-1:] + thread_pool[:-1]
|
thread_pool = thread_pool[-1:] + thread_pool[:-1]
|
||||||
|
|
||||||
@ -216,23 +221,23 @@ class Upscaler:
|
|||||||
if self.scale_ratio:
|
if self.scale_ratio:
|
||||||
thread = threading.Thread(target=w2.upscale,
|
thread = threading.Thread(target=w2.upscale,
|
||||||
args=(thread_info[0],
|
args=(thread_info[0],
|
||||||
self.upscaled_frames,
|
self.upscaled_frames,
|
||||||
self.scale_ratio,
|
self.scale_ratio,
|
||||||
False,
|
False,
|
||||||
False,
|
False,
|
||||||
self.image_format,
|
self.image_format,
|
||||||
self.upscaler_exceptions))
|
self.upscaler_exceptions))
|
||||||
else:
|
else:
|
||||||
thread = threading.Thread(target=w2.upscale,
|
thread = threading.Thread(target=w2.upscale,
|
||||||
args=(thread_info[0],
|
args=(thread_info[0],
|
||||||
self.upscaled_frames,
|
self.upscaled_frames,
|
||||||
False,
|
False,
|
||||||
self.scale_width,
|
self.scale_width,
|
||||||
self.scale_height,
|
self.scale_height,
|
||||||
self.image_format,
|
self.image_format,
|
||||||
self.upscaler_exceptions))
|
self.upscaler_exceptions))
|
||||||
|
|
||||||
# if the driver being used is waifu2x_ncnn_vulkan
|
# if the driver being used is waifu2x_ncnn_vulkan
|
||||||
elif self.waifu2x_driver == 'waifu2x_ncnn_vulkan':
|
elif self.waifu2x_driver == 'waifu2x_ncnn_vulkan':
|
||||||
w2 = Waifu2xNcnnVulkan(copy.deepcopy(self.waifu2x_settings))
|
w2 = Waifu2xNcnnVulkan(copy.deepcopy(self.waifu2x_settings))
|
||||||
thread = threading.Thread(target=w2.upscale,
|
thread = threading.Thread(target=w2.upscale,
|
||||||
@ -273,7 +278,6 @@ class Upscaler:
|
|||||||
if len(self.upscaler_exceptions) != 0:
|
if len(self.upscaler_exceptions) != 0:
|
||||||
raise(self.upscaler_exceptions[0])
|
raise(self.upscaler_exceptions[0])
|
||||||
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""Main controller for Video2X
|
"""Main controller for Video2X
|
||||||
|
|
||||||
@ -286,8 +290,8 @@ class Upscaler:
|
|||||||
self._check_arguments()
|
self._check_arguments()
|
||||||
|
|
||||||
# convert paths to absolute paths
|
# convert paths to absolute paths
|
||||||
self.input_video = os.path.abspath(self.input_video)
|
self.input_video = self.input_video.absolute()
|
||||||
self.output_video = os.path.abspath(self.output_video)
|
self.output_video = self.output_video.absolute()
|
||||||
|
|
||||||
# initialize objects for ffmpeg and waifu2x-caffe
|
# initialize objects for ffmpeg and waifu2x-caffe
|
||||||
fm = Ffmpeg(self.ffmpeg_settings, self.image_format)
|
fm = Ffmpeg(self.ffmpeg_settings, self.image_format)
|
||||||
|
126
bin/video2x.py
126
bin/video2x.py
@ -1,8 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: future_fstrings -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
r"""
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
__ __ _ _ ___ __ __
|
__ __ _ _ ___ __ __
|
||||||
\ \ / / (_) | | |__ \ \ \ / /
|
\ \ / / (_) | | |__ \ \ \ / /
|
||||||
@ -15,7 +13,7 @@ __ __ _ _ ___ __ __
|
|||||||
Name: Video2X Controller
|
Name: Video2X Controller
|
||||||
Author: K4YT3X
|
Author: K4YT3X
|
||||||
Date Created: Feb 24, 2018
|
Date Created: Feb 24, 2018
|
||||||
Last Modified: July 9, 2019
|
Last Modified: July 27, 2019
|
||||||
|
|
||||||
Dev: BrianPetkovsek
|
Dev: BrianPetkovsek
|
||||||
Dev: SAT3LL
|
Dev: SAT3LL
|
||||||
@ -43,13 +41,16 @@ enlarging engine. It extracts frames from a video, enlarge it by a
|
|||||||
number of times without losing any details or quality, keeping lines
|
number of times without losing any details or quality, keeping lines
|
||||||
smooth and edges sharp.
|
smooth and edges sharp.
|
||||||
"""
|
"""
|
||||||
from avalon_framework import Avalon
|
|
||||||
|
# local imports
|
||||||
from upscaler import Upscaler
|
from upscaler import Upscaler
|
||||||
|
|
||||||
|
# built-in imports
|
||||||
import argparse
|
import argparse
|
||||||
import GPUtil
|
import glob
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import psutil
|
import pathlib
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
@ -57,7 +58,12 @@ import tempfile
|
|||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
VERSION = '2.8.1'
|
# third-party imports
|
||||||
|
from avalon_framework import Avalon
|
||||||
|
import GPUtil
|
||||||
|
import psutil
|
||||||
|
|
||||||
|
VERSION = '2.9.0'
|
||||||
|
|
||||||
# each thread might take up to 2.5 GB during initialization.
|
# each thread might take up to 2.5 GB during initialization.
|
||||||
# (system memory, not to be confused with GPU memory)
|
# (system memory, not to be confused with GPU memory)
|
||||||
@ -76,16 +82,16 @@ def process_arguments():
|
|||||||
|
|
||||||
# video options
|
# video options
|
||||||
file_options = parser.add_argument_group('File Options')
|
file_options = parser.add_argument_group('File Options')
|
||||||
file_options.add_argument('-i', '--input', help='source video file/directory', action='store')
|
file_options.add_argument('-i', '--input', type=pathlib.Path, help='source video file/directory', action='store')
|
||||||
file_options.add_argument('-o', '--output', help='output video file/directory', action='store')
|
file_options.add_argument('-o', '--output', type=pathlib.Path, help='output video file/directory', action='store')
|
||||||
|
|
||||||
# upscaler options
|
# upscaler options
|
||||||
upscaler_options = parser.add_argument_group('Upscaler Options')
|
upscaler_options = parser.add_argument_group('Upscaler Options')
|
||||||
upscaler_options.add_argument('-m', '--method', help='upscaling method', action='store', default='gpu', choices=['cpu', 'gpu', 'cudnn'])
|
upscaler_options.add_argument('-m', '--method', help='upscaling method', action='store', default='gpu', choices=['cpu', 'gpu', 'cudnn'])
|
||||||
upscaler_options.add_argument('-d', '--driver', help='waifu2x driver', action='store', default='waifu2x_caffe', choices=['waifu2x_caffe', 'waifu2x_converter', 'waifu2x_ncnn_vulkan'])
|
upscaler_options.add_argument('-d', '--driver', help='waifu2x driver', action='store', default='waifu2x_caffe', choices=['waifu2x_caffe', 'waifu2x_converter', 'waifu2x_ncnn_vulkan'])
|
||||||
upscaler_options.add_argument('-y', '--model_dir', help='directory containing model JSON files', action='store')
|
upscaler_options.add_argument('-y', '--model_dir', type=pathlib.Path, help='directory containing model JSON files', action='store')
|
||||||
upscaler_options.add_argument('-t', '--threads', help='number of threads to use for upscaling', action='store', type=int, default=1)
|
upscaler_options.add_argument('-t', '--threads', help='number of threads to use for upscaling', action='store', type=int, default=1)
|
||||||
upscaler_options.add_argument('-c', '--config', help='video2x config file location', action='store', default=os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), 'video2x.json'))
|
upscaler_options.add_argument('-c', '--config', type=pathlib.Path, help='video2x config file location', action='store', default=pathlib.Path(sys.argv[0]).parent.absolute() / 'video2x.json')
|
||||||
upscaler_options.add_argument('-b', '--batch', help='enable batch mode (select all default values to questions)', action='store_true')
|
upscaler_options.add_argument('-b', '--batch', help='enable batch mode (select all default values to questions)', action='store_true')
|
||||||
|
|
||||||
# scaling options
|
# scaling options
|
||||||
@ -103,12 +109,16 @@ def process_arguments():
|
|||||||
|
|
||||||
|
|
||||||
def print_logo():
|
def print_logo():
|
||||||
print('__ __ _ _ ___ __ __')
|
"""print video2x logo"""
|
||||||
print('\\ \\ / / (_) | | |__ \\ \\ \\ / /')
|
logo = r'''
|
||||||
print(' \\ \\ / / _ __| | ___ ___ ) | \\ V /')
|
__ __ _ _ ___ __ __
|
||||||
print(' \\ \\/ / | | / _` | / _ \\ / _ \\ / / > <')
|
\ \ / / (_) | | |__ \ \ \ / /
|
||||||
print(' \\ / | | | (_| | | __/ | (_) | / /_ / . \\')
|
\ \ / / _ __| | ___ ___ ) | \ V /
|
||||||
print(' \\/ |_| \\__,_| \\___| \\___/ |____| /_/ \\_\\')
|
\ \/ / | | / _` | / _ \ / _ \ / / > <
|
||||||
|
\ / | | | (_| | | __/ | (_) | / /_ / . \
|
||||||
|
\/ |_| \__,_| \___| \___/ |____| /_/ \_\
|
||||||
|
'''
|
||||||
|
print(logo)
|
||||||
print('\n Video2X Video Enlarger')
|
print('\n Video2X Video Enlarger')
|
||||||
spaces = ((44 - len(f'Version {VERSION}')) // 2) * ' '
|
spaces = ((44 - len(f'Version {VERSION}')) // 2) * ' '
|
||||||
print(f'{Avalon.FM.BD}\n{spaces} Version {VERSION}\n{Avalon.FM.RST}')
|
print(f'{Avalon.FM.BD}\n{spaces} Version {VERSION}\n{Avalon.FM.RST}')
|
||||||
@ -129,7 +139,7 @@ def check_memory():
|
|||||||
# GPUtil requires nvidia-smi.exe to interact with GPU
|
# GPUtil requires nvidia-smi.exe to interact with GPU
|
||||||
if args.method == 'gpu' or args.method == 'cudnn':
|
if args.method == 'gpu' or args.method == 'cudnn':
|
||||||
if not (shutil.which('nvidia-smi') or
|
if not (shutil.which('nvidia-smi') or
|
||||||
os.path.isfile('C:\\Program Files\\NVIDIA Corporation\\NVSMI\\nvidia-smi.exe')):
|
pathlib.Path(r'C:\Program Files\NVIDIA Corporation\NVSMI\nvidia-smi.exe').is_file()):
|
||||||
# Nvidia System Management Interface not available
|
# Nvidia System Management Interface not available
|
||||||
Avalon.warning('Nvidia-smi not available, skipping available memory check')
|
Avalon.warning('Nvidia-smi not available, skipping available memory check')
|
||||||
Avalon.warning('If you experience error \"cudaSuccess out of memory\", try reducing number of threads you\'re using')
|
Avalon.warning('If you experience error \"cudaSuccess out of memory\", try reducing number of threads you\'re using')
|
||||||
@ -193,28 +203,28 @@ def absolutify_paths(config):
|
|||||||
Returns:
|
Returns:
|
||||||
dict -- configuration file dictionary
|
dict -- configuration file dictionary
|
||||||
"""
|
"""
|
||||||
current_directory = os.path.dirname(os.path.abspath(sys.argv[0]))
|
current_directory = pathlib.Path(sys.argv[0]).parent.absolute()
|
||||||
|
|
||||||
# check waifu2x-caffe path
|
# check waifu2x-caffe path
|
||||||
if not re.match('^[a-z]:', config['waifu2x_caffe']['waifu2x_caffe_path'], re.IGNORECASE):
|
if not re.match('^[a-z]:', config['waifu2x_caffe']['waifu2x_caffe_path'], re.IGNORECASE):
|
||||||
config['waifu2x_caffe']['waifu2x_caffe_path'] = os.path.join(current_directory, config['waifu2x_caffe']['waifu2x_caffe_path'])
|
config['waifu2x_caffe']['waifu2x_caffe_path'] = current_directory / config['waifu2x_caffe']['waifu2x_caffe_path']
|
||||||
|
|
||||||
# check waifu2x-converter-cpp path
|
# check waifu2x-converter-cpp path
|
||||||
if not re.match('^[a-z]:', config['waifu2x_converter']['waifu2x_converter_path'], re.IGNORECASE):
|
if not re.match('^[a-z]:', config['waifu2x_converter']['waifu2x_converter_path'], re.IGNORECASE):
|
||||||
config['waifu2x_converter']['waifu2x_converter_path'] = os.path.join(current_directory, config['waifu2x_converter']['waifu2x_converter_path'])
|
config['waifu2x_converter']['waifu2x_converter_path'] = current_directory / config['waifu2x_converter']['waifu2x_converter_path']
|
||||||
|
|
||||||
# check waifu2x_ncnn_vulkan path
|
# check waifu2x_ncnn_vulkan path
|
||||||
if not re.match('^[a-z]:', config['waifu2x_ncnn_vulkan']['waifu2x_ncnn_vulkan_path'], re.IGNORECASE):
|
if not re.match('^[a-z]:', config['waifu2x_ncnn_vulkan']['waifu2x_ncnn_vulkan_path'], re.IGNORECASE):
|
||||||
config['waifu2x_ncnn_vulkan']['waifu2x_ncnn_vulkan_path'] = os.path.join(current_directory, config['waifu2x_ncnn_vulkan']['waifu2x_ncnn_vulkan_path'])
|
config['waifu2x_ncnn_vulkan']['waifu2x_ncnn_vulkan_path'] = current_directory / config['waifu2x_ncnn_vulkan']['waifu2x_ncnn_vulkan_path']
|
||||||
|
|
||||||
# check ffmpeg path
|
# check ffmpeg path
|
||||||
if not re.match('^[a-z]:', config['ffmpeg']['ffmpeg_path'], re.IGNORECASE):
|
if not re.match('^[a-z]:', config['ffmpeg']['ffmpeg_path'], re.IGNORECASE):
|
||||||
config['ffmpeg']['ffmpeg_path'] = os.path.join(current_directory, config['ffmpeg']['ffmpeg_path'])
|
config['ffmpeg']['ffmpeg_path'] = current_directory / config['ffmpeg']['ffmpeg_path']
|
||||||
|
|
||||||
# check video2x cache path
|
# check video2x cache path
|
||||||
if config['video2x']['video2x_cache_directory']:
|
if config['video2x']['video2x_cache_directory']:
|
||||||
if not re.match('^[a-z]:', config['video2x']['video2x_cache_directory'], re.IGNORECASE):
|
if not re.match('^[a-z]:', config['video2x']['video2x_cache_directory'], re.IGNORECASE):
|
||||||
config['video2x']['video2x_cache_directory'] = os.path.join(current_directory, config['video2x']['video2x_cache_directory'])
|
config['video2x']['video2x_cache_directory'] = current_directory / config['video2x']['video2x_cache_directory']
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
@ -271,19 +281,19 @@ config = absolutify_paths(config)
|
|||||||
# load waifu2x configuration
|
# load waifu2x configuration
|
||||||
if args.driver == 'waifu2x_caffe':
|
if args.driver == 'waifu2x_caffe':
|
||||||
waifu2x_settings = config['waifu2x_caffe']
|
waifu2x_settings = config['waifu2x_caffe']
|
||||||
if not os.path.isfile(waifu2x_settings['waifu2x_caffe_path']):
|
if not pathlib.Path(waifu2x_settings['waifu2x_caffe_path']).is_file():
|
||||||
Avalon.error('Specified waifu2x-caffe directory doesn\'t exist')
|
Avalon.error('Specified waifu2x-caffe directory doesn\'t exist')
|
||||||
Avalon.error('Please check the configuration file settings')
|
Avalon.error('Please check the configuration file settings')
|
||||||
raise FileNotFoundError(waifu2x_settings['waifu2x_caffe_path'])
|
raise FileNotFoundError(waifu2x_settings['waifu2x_caffe_path'])
|
||||||
elif args.driver == 'waifu2x_converter':
|
elif args.driver == 'waifu2x_converter':
|
||||||
waifu2x_settings = config['waifu2x_converter']
|
waifu2x_settings = config['waifu2x_converter']
|
||||||
if not os.path.isdir(waifu2x_settings['waifu2x_converter_path']):
|
if not pathlib.Path(waifu2x_settings['waifu2x_converter_path']).is_dir():
|
||||||
Avalon.error('Specified waifu2x-converter-cpp directory doesn\'t exist')
|
Avalon.error('Specified waifu2x-converter-cpp directory doesn\'t exist')
|
||||||
Avalon.error('Please check the configuration file settings')
|
Avalon.error('Please check the configuration file settings')
|
||||||
raise FileNotFoundError(waifu2x_settings['waifu2x_converter_path'])
|
raise FileNotFoundError(waifu2x_settings['waifu2x_converter_path'])
|
||||||
elif args.driver == 'waifu2x_ncnn_vulkan':
|
elif args.driver == 'waifu2x_ncnn_vulkan':
|
||||||
waifu2x_settings = config['waifu2x_ncnn_vulkan']
|
waifu2x_settings = config['waifu2x_ncnn_vulkan']
|
||||||
if not os.path.isfile(waifu2x_settings['waifu2x_ncnn_vulkan_path']):
|
if not pathlib.Path(waifu2x_settings['waifu2x_ncnn_vulkan_path']).is_file():
|
||||||
Avalon.error('Specified waifu2x_ncnn_vulkan directory doesn\'t exist')
|
Avalon.error('Specified waifu2x_ncnn_vulkan directory doesn\'t exist')
|
||||||
Avalon.error('Please check the configuration file settings')
|
Avalon.error('Please check the configuration file settings')
|
||||||
raise FileNotFoundError(waifu2x_settings['waifu2x_ncnn_vulkan_path'])
|
raise FileNotFoundError(waifu2x_settings['waifu2x_ncnn_vulkan_path'])
|
||||||
@ -292,28 +302,38 @@ elif args.driver == 'waifu2x_ncnn_vulkan':
|
|||||||
ffmpeg_settings = config['ffmpeg']
|
ffmpeg_settings = config['ffmpeg']
|
||||||
|
|
||||||
# load video2x settings
|
# load video2x settings
|
||||||
video2x_cache_directory = config['video2x']['video2x_cache_directory']
|
|
||||||
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']
|
||||||
|
|
||||||
# create temp directories if they don't exist
|
# load cache directory
|
||||||
if not video2x_cache_directory:
|
if isinstance(config['video2x']['video2x_cache_directory'], str):
|
||||||
video2x_cache_directory = os.path.join(tempfile.gettempdir(), 'video2x')
|
video2x_cache_directory = pathlib.Path(config['video2x']['video2x_cache_directory'])
|
||||||
|
else:
|
||||||
|
video2x_cache_directory = pathlib.Path(tempfile.gettempdir()) / 'video2x'
|
||||||
|
|
||||||
if video2x_cache_directory and not os.path.isdir(video2x_cache_directory):
|
if video2x_cache_directory.exists() and not video2x_cache_directory.is_dir():
|
||||||
if not os.path.isfile(video2x_cache_directory) and not os.path.islink(video2x_cache_directory):
|
Avalon.error('Specified cache directory is a file/link')
|
||||||
Avalon.warning(f'Specified cache directory {video2x_cache_directory} does not exist')
|
raise FileExistsError('Specified cache directory is a file/link')
|
||||||
if Avalon.ask('Create directory?', default=True, batch=args.batch):
|
|
||||||
if os.mkdir(video2x_cache_directory) is None:
|
elif not video2x_cache_directory.exists():
|
||||||
Avalon.info(f'{video2x_cache_directory} created')
|
# if destination file is a file or a symbolic link
|
||||||
else:
|
Avalon.warning(f'Specified cache directory {video2x_cache_directory} does not exist')
|
||||||
Avalon.error(f'Unable to create {video2x_cache_directory}')
|
|
||||||
Avalon.error('Aborting...')
|
# try creating the cache directory
|
||||||
exit(1)
|
if Avalon.ask('Create directory?', default=True, batch=args.batch):
|
||||||
|
try:
|
||||||
|
video2x_cache_directory.mkdir(parents=True, exist_ok=True)
|
||||||
|
Avalon.info(f'{video2x_cache_directory} created')
|
||||||
|
|
||||||
|
# there can be a number of exceptions here
|
||||||
|
# PermissionError, FileExistsError, etc.
|
||||||
|
# therefore, we put a catch-them-all here
|
||||||
|
except Exception as e:
|
||||||
|
Avalon.error(f'Unable to create {video2x_cache_directory}')
|
||||||
|
Avalon.error('Aborting...')
|
||||||
|
raise e
|
||||||
else:
|
else:
|
||||||
Avalon.error('Specified cache directory is a file/link')
|
raise FileNotFoundError('Could not create cache directory')
|
||||||
Avalon.error('Unable to continue, exiting...')
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
# start execution
|
# start execution
|
||||||
@ -322,17 +342,17 @@ try:
|
|||||||
begin_time = time.time()
|
begin_time = time.time()
|
||||||
|
|
||||||
# if input specified is a single file
|
# if input specified is a single file
|
||||||
if os.path.isfile(args.input):
|
if args.input.is_file():
|
||||||
|
|
||||||
# upscale single video file
|
# upscale single video file
|
||||||
Avalon.info(f'Upscaling single video file: {args.input}')
|
Avalon.info(f'Upscaling single video file: {args.input}')
|
||||||
|
|
||||||
# check for input output format mismatch
|
# check for input output format mismatch
|
||||||
if os.path.isdir(args.output):
|
if args.output.is_dir():
|
||||||
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 Exception('input output path type mismatch')
|
raise Exception('input output path type mismatch')
|
||||||
if not re.search('.*\..*$', args.output):
|
if not re.search('.*\..*$', str(args.output)):
|
||||||
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 for FFmpeg')
|
||||||
raise Exception('No suffix specified')
|
raise Exception('No suffix specified')
|
||||||
@ -356,12 +376,12 @@ try:
|
|||||||
upscaler.cleanup_temp_directories()
|
upscaler.cleanup_temp_directories()
|
||||||
|
|
||||||
# if input specified is a directory
|
# if input specified is a directory
|
||||||
elif os.path.isdir(args.input):
|
elif args.input.is_dir():
|
||||||
# upscale videos in a directory
|
# upscale videos in a directory
|
||||||
Avalon.info(f'Upscaling videos in directory: {args.input}')
|
Avalon.info(f'Upscaling videos in directory: {args.input}')
|
||||||
for input_video in [f for f in os.listdir(args.input) if os.path.isfile(os.path.join(args.input, f))]:
|
for input_video in [f for f in args.input.iterdir() if f.is_file()]:
|
||||||
output_video = os.path.join(args.output, input_video)
|
output_video = args.output / input_video
|
||||||
upscaler = Upscaler(input_video=os.path.join(args.input, input_video), output_video=output_video, method=args.method, waifu2x_settings=waifu2x_settings, ffmpeg_settings=ffmpeg_settings)
|
upscaler = Upscaler(input_video=args.input / input_video, output_video=output_video, method=args.method, waifu2x_settings=waifu2x_settings, ffmpeg_settings=ffmpeg_settings)
|
||||||
|
|
||||||
# set optional options
|
# set optional options
|
||||||
upscaler.waifu2x_driver = args.driver
|
upscaler.waifu2x_driver = args.driver
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Name: Video2X Setup Script
|
Name: Video2X Setup Script
|
||||||
Author: K4YT3X
|
Author: K4YT3X
|
||||||
Author: BrianPetkovsek
|
Author: BrianPetkovsek
|
||||||
Date Created: November 28, 2018
|
Date Created: November 28, 2018
|
||||||
Last Modified: June 26, 2019
|
Last Modified: July 27, 2019
|
||||||
|
|
||||||
Dev: SAT3LL
|
Dev: SAT3LL
|
||||||
|
|
||||||
@ -26,14 +24,19 @@ Installation Details:
|
|||||||
- waifu2x_ncnn_vulkan: %LOCALAPPDATA%\\video2x\\waifu2x-ncnn-vulkan
|
- waifu2x_ncnn_vulkan: %LOCALAPPDATA%\\video2x\\waifu2x-ncnn-vulkan
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# built-in imports
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import pathlib
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import traceback
|
import traceback
|
||||||
|
import urllib
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
# Requests doesn't come with windows, therefore
|
# Requests doesn't come with windows, therefore
|
||||||
@ -43,6 +46,9 @@ import zipfile
|
|||||||
|
|
||||||
VERSION = '1.3.0'
|
VERSION = '1.3.0'
|
||||||
|
|
||||||
|
# global static variables
|
||||||
|
LOCALAPPDATA = pathlib.Path(os.getenv('localappdata'))
|
||||||
|
|
||||||
|
|
||||||
def process_arguments():
|
def process_arguments():
|
||||||
"""Processes CLI arguments
|
"""Processes CLI arguments
|
||||||
@ -104,14 +110,15 @@ class Video2xSetup:
|
|||||||
"""
|
"""
|
||||||
for file in self.trash:
|
for file in self.trash:
|
||||||
try:
|
try:
|
||||||
if os.path.isfile(file):
|
if file.is_dir():
|
||||||
print('Deleting: {}'.format(file))
|
print(f'Deleting directory: {file}')
|
||||||
os.remove(file)
|
|
||||||
else:
|
|
||||||
print('Deleting: {}'.format(file))
|
|
||||||
shutil.rmtree(file)
|
shutil.rmtree(file)
|
||||||
except FileNotFoundError:
|
else:
|
||||||
pass
|
print(f'Deleting file: {file}')
|
||||||
|
file.unlink()
|
||||||
|
except Exception:
|
||||||
|
print(f'Error deleting: {file}')
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
def _install_ffmpeg(self):
|
def _install_ffmpeg(self):
|
||||||
""" Install FFMPEG
|
""" Install FFMPEG
|
||||||
@ -122,7 +129,7 @@ class Video2xSetup:
|
|||||||
self.trash.append(ffmpeg_zip)
|
self.trash.append(ffmpeg_zip)
|
||||||
|
|
||||||
with zipfile.ZipFile(ffmpeg_zip) as zipf:
|
with zipfile.ZipFile(ffmpeg_zip) as zipf:
|
||||||
zipf.extractall(os.path.join(os.getenv('localappdata'), 'video2x'))
|
zipf.extractall(LOCALAPPDATA / 'video2x')
|
||||||
|
|
||||||
def _install_waifu2x_caffe(self):
|
def _install_waifu2x_caffe(self):
|
||||||
""" Install waifu2x_caffe
|
""" Install waifu2x_caffe
|
||||||
@ -139,7 +146,7 @@ class Video2xSetup:
|
|||||||
self.trash.append(waifu2x_caffe_zip)
|
self.trash.append(waifu2x_caffe_zip)
|
||||||
|
|
||||||
with zipfile.ZipFile(waifu2x_caffe_zip) as zipf:
|
with zipfile.ZipFile(waifu2x_caffe_zip) as zipf:
|
||||||
zipf.extractall(os.path.join(os.getenv('localappdata'), 'video2x'))
|
zipf.extractall(LOCALAPPDATA / 'video2x')
|
||||||
|
|
||||||
def _install_waifu2x_converter_cpp(self):
|
def _install_waifu2x_converter_cpp(self):
|
||||||
""" Install waifu2x_caffe
|
""" Install waifu2x_caffe
|
||||||
@ -157,7 +164,7 @@ class Video2xSetup:
|
|||||||
self.trash.append(waifu2x_converter_cpp_zip)
|
self.trash.append(waifu2x_converter_cpp_zip)
|
||||||
|
|
||||||
with zipfile.ZipFile(waifu2x_converter_cpp_zip) as zipf:
|
with zipfile.ZipFile(waifu2x_converter_cpp_zip) as zipf:
|
||||||
zipf.extractall(os.path.join(os.getenv('localappdata'), 'video2x', 'waifu2x-converter-cpp'))
|
zipf.extractall(LOCALAPPDATA / 'video2x' / 'waifu2x-converter-cpp')
|
||||||
|
|
||||||
def _install_waifu2x_ncnn_vulkan(self):
|
def _install_waifu2x_ncnn_vulkan(self):
|
||||||
""" Install waifu2x-ncnn-vulkan
|
""" Install waifu2x-ncnn-vulkan
|
||||||
@ -176,12 +183,11 @@ class Video2xSetup:
|
|||||||
|
|
||||||
# extract then move (to remove the top level directory)
|
# extract then move (to remove the top level directory)
|
||||||
with zipfile.ZipFile(waifu2x_ncnn_vulkan_zip) as zipf:
|
with zipfile.ZipFile(waifu2x_ncnn_vulkan_zip) as zipf:
|
||||||
extraction_path = os.path.join(tempfile.gettempdir(), 'waifu2x-ncnn-vulkan-ext')
|
extraction_path = pathlib.Path(tempfile.gettempdir()) / 'waifu2x-ncnn-vulkan-ext'
|
||||||
zipf.extractall(extraction_path)
|
zipf.extractall(extraction_path)
|
||||||
shutil.move(os.path.join(extraction_path, os.listdir(extraction_path)[0]), os.path.join(os.getenv('localappdata'), 'video2x', 'waifu2x-ncnn-vulkan'))
|
shutil.move(extraction_path / os.listdir(extraction_path)[0], LOCALAPPDATA / 'video2x' / 'waifu2x-ncnn-vulkan')
|
||||||
self.trash.append(extraction_path)
|
self.trash.append(extraction_path)
|
||||||
|
|
||||||
|
|
||||||
def _generate_config(self):
|
def _generate_config(self):
|
||||||
""" Generate video2x config
|
""" Generate video2x config
|
||||||
"""
|
"""
|
||||||
@ -190,21 +196,19 @@ class Video2xSetup:
|
|||||||
template_dict = json.load(template)
|
template_dict = json.load(template)
|
||||||
template.close()
|
template.close()
|
||||||
|
|
||||||
local_app_data = os.getenv('localappdata')
|
|
||||||
|
|
||||||
# configure only the specified drivers
|
# configure only the specified drivers
|
||||||
if self.driver == 'all':
|
if self.driver == 'all':
|
||||||
template_dict['waifu2x_caffe']['waifu2x_caffe_path'] = os.path.join(local_app_data, 'video2x', 'waifu2x-caffe', 'waifu2x-caffe-cui.exe')
|
template_dict['waifu2x_caffe']['waifu2x_caffe_path'] = LOCALAPPDATA / 'video2x' / 'waifu2x-caffe' / 'waifu2x-caffe-cui.exe'
|
||||||
template_dict['waifu2x_converter']['waifu2x_converter_path'] = os.path.join(local_app_data, 'video2x', 'waifu2x-converter-cpp')
|
template_dict['waifu2x_converter']['waifu2x_converter_path'] = LOCALAPPDATA / 'video2x' / 'waifu2x-converter-cpp'
|
||||||
template_dict['waifu2x_ncnn_vulkan']['waifu2x_ncnn_vulkan_path'] = os.path.join(local_app_data, 'video2x', 'waifu2x-ncnn-vulkan', 'waifu2x-ncnn-vulkan.exe')
|
template_dict['waifu2x_ncnn_vulkan']['waifu2x_ncnn_vulkan_path'] = LOCALAPPDATA / 'video2x' / 'waifu2x-ncnn-vulkan' / 'waifu2x-ncnn-vulkan.exe'
|
||||||
elif self.driver == 'waifu2x_caffe':
|
elif self.driver == 'waifu2x_caffe':
|
||||||
template_dict['waifu2x_caffe']['waifu2x_caffe_path'] = os.path.join(local_app_data, 'video2x', 'waifu2x-caffe', 'waifu2x-caffe-cui.exe')
|
template_dict['waifu2x_caffe']['waifu2x_caffe_path'] = LOCALAPPDATA / 'video2x' / 'waifu2x-caffe' / 'waifu2x-caffe-cui.exe'
|
||||||
elif self.driver == 'waifu2x_converter':
|
elif self.driver == 'waifu2x_converter':
|
||||||
template_dict['waifu2x_converter']['waifu2x_converter_path'] = os.path.join(local_app_data, 'video2x', 'waifu2x-converter-cpp')
|
template_dict['waifu2x_converter']['waifu2x_converter_path'] = LOCALAPPDATA / 'video2x' / 'waifu2x-converter-cpp'
|
||||||
elif self.driver == 'waifu2x_ncnn_vulkan':
|
elif self.driver == 'waifu2x_ncnn_vulkan':
|
||||||
template_dict['waifu2x_ncnn_vulkan']['waifu2x_ncnn_vulkan_path'] = os.path.join(local_app_data, 'video2x', 'waifu2x-ncnn-vulkan', 'waifu2x-ncnn-vulkan.exe')
|
template_dict['waifu2x_ncnn_vulkan']['waifu2x_ncnn_vulkan_path'] = LOCALAPPDATA / 'video2x' / 'waifu2x-ncnn-vulkan' / 'waifu2x-ncnn-vulkan.exe'
|
||||||
|
|
||||||
template_dict['ffmpeg']['ffmpeg_path'] = os.path.join(local_app_data, 'video2x', 'ffmpeg-latest-win64-static', 'bin')
|
template_dict['ffmpeg']['ffmpeg_path'] = LOCALAPPDATA / 'video2x' / 'ffmpeg-latest-win64-static' / 'bin'
|
||||||
template_dict['video2x']['video2x_cache_directory'] = None
|
template_dict['video2x']['video2x_cache_directory'] = None
|
||||||
template_dict['video2x']['preserve_frames'] = False
|
template_dict['video2x']['preserve_frames'] = False
|
||||||
|
|
||||||
@ -220,13 +224,42 @@ def download(url, save_path, chunk_size=4096):
|
|||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
output_file = os.path.join(save_path, url.split('/')[-1])
|
save_path = pathlib.Path(save_path)
|
||||||
print('Downloading: {}'.format(url))
|
|
||||||
print('Chunk size: {}'.format(chunk_size))
|
|
||||||
print('Saving to: {}'.format(output_file))
|
|
||||||
|
|
||||||
stream = requests.get(url, stream=True)
|
# create target folder if it doesn't exist
|
||||||
total_size = int(stream.headers['content-length'])
|
save_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# create requests stream for steaming file
|
||||||
|
stream = requests.get(url, stream=True, allow_redirects=True)
|
||||||
|
|
||||||
|
# get file name
|
||||||
|
file_name = None
|
||||||
|
if 'content-disposition' in stream.headers:
|
||||||
|
disposition = stream.headers['content-disposition']
|
||||||
|
try:
|
||||||
|
file_name = re.findall("filename=(.+)", disposition)[0].strip('"')
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if file_name is None:
|
||||||
|
# output_file = f'{save_path}\\{stream.url.split("/")[-1]}'
|
||||||
|
output_file = save_path / stream.url.split('/')[-1]
|
||||||
|
else:
|
||||||
|
output_file = save_path / file_name
|
||||||
|
|
||||||
|
# decode url encoding
|
||||||
|
output_file = pathlib.Path(urllib.parse.unquote(str(output_file)))
|
||||||
|
|
||||||
|
# get total size for progress bar if provided in headers
|
||||||
|
total_size = 0
|
||||||
|
if 'content-length' in stream.headers:
|
||||||
|
total_size = int(stream.headers['content-length'])
|
||||||
|
|
||||||
|
# print download information summary
|
||||||
|
print(f'Downloading: {url}')
|
||||||
|
print(f'Total size: {total_size}')
|
||||||
|
print(f'Chunk size: {chunk_size}')
|
||||||
|
print(f'Saving to: {output_file}')
|
||||||
|
|
||||||
# Write content into file
|
# Write content into file
|
||||||
with open(output_file, 'wb') as output:
|
with open(output_file, 'wb') as output:
|
||||||
@ -236,6 +269,7 @@ def download(url, save_path, chunk_size=4096):
|
|||||||
output.write(chunk)
|
output.write(chunk)
|
||||||
progress_bar.update(len(chunk))
|
progress_bar.update(len(chunk))
|
||||||
|
|
||||||
|
# return the full path of saved file
|
||||||
return output_file
|
return output_file
|
||||||
|
|
||||||
|
|
||||||
@ -252,7 +286,7 @@ if __name__ == '__main__':
|
|||||||
try:
|
try:
|
||||||
args = process_arguments()
|
args = process_arguments()
|
||||||
print('Video2X Setup Script')
|
print('Video2X Setup Script')
|
||||||
print('Version: {}'.format(VERSION))
|
print(f'Version: {VERSION}')
|
||||||
|
|
||||||
# do not install pip modules if script
|
# do not install pip modules if script
|
||||||
# is packaged in exe format
|
# is packaged in exe format
|
||||||
|
@ -1,20 +1,22 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: future_fstrings -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Name: Waifu2x Caffe Driver
|
Name: Waifu2x Caffe Driver
|
||||||
Author: K4YT3X
|
Author: K4YT3X
|
||||||
Date Created: Feb 24, 2018
|
Date Created: Feb 24, 2018
|
||||||
Last Modified: July 9, 2019
|
Last Modified: July 27, 2019
|
||||||
|
|
||||||
Description: This class is a high-level wrapper
|
Description: This class is a high-level wrapper
|
||||||
for waifu2x-caffe.
|
for waifu2x-caffe.
|
||||||
"""
|
"""
|
||||||
from avalon_framework import Avalon
|
|
||||||
|
# built-in imports
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
# third-party imports
|
||||||
|
from avalon_framework import Avalon
|
||||||
|
|
||||||
|
|
||||||
class Waifu2xCaffe:
|
class Waifu2xCaffe:
|
||||||
"""This class communicates with waifu2x cui engine
|
"""This class communicates with waifu2x cui engine
|
||||||
|
@ -1,21 +1,23 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: future_fstrings -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Name: Waifu2x Converter CPP Driver
|
Name: Waifu2x Converter CPP Driver
|
||||||
Author: K4YT3X
|
Author: K4YT3X
|
||||||
Date Created: February 8, 2019
|
Date Created: February 8, 2019
|
||||||
Last Modified: June 15, 2019
|
Last Modified: July 27, 2019
|
||||||
|
|
||||||
Description: This class is a high-level wrapper
|
Description: This class is a high-level wrapper
|
||||||
for waifu2x-converter-cpp.
|
for waifu2x-converter-cpp.
|
||||||
"""
|
"""
|
||||||
from avalon_framework import Avalon
|
|
||||||
import os
|
# built-in imports
|
||||||
|
import pathlib
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
# third-party imports
|
||||||
|
from avalon_framework import Avalon
|
||||||
|
|
||||||
|
|
||||||
class Waifu2xConverter:
|
class Waifu2xConverter:
|
||||||
"""This class communicates with waifu2x cui engine
|
"""This class communicates with waifu2x cui engine
|
||||||
@ -62,8 +64,7 @@ class Waifu2xConverter:
|
|||||||
# models_rgb must be specified manually for waifu2x-converter-cpp
|
# models_rgb must be specified manually for waifu2x-converter-cpp
|
||||||
# if it's not specified in the arguments, create automatically
|
# if it's not specified in the arguments, create automatically
|
||||||
if self.waifu2x_settings['model-dir'] is None:
|
if self.waifu2x_settings['model-dir'] is None:
|
||||||
self.waifu2x_settings['model-dir'] = os.path.join(self.waifu2x_settings['waifu2x_converter_path'],
|
self.waifu2x_settings['model-dir'] = pathlib.Path(self.waifu2x_settings['waifu2x_converter_path']) / 'models_rgb'
|
||||||
'models_rgb')
|
|
||||||
|
|
||||||
# print thread start message
|
# print thread start message
|
||||||
self.print_lock.acquire()
|
self.print_lock.acquire()
|
||||||
@ -79,7 +80,7 @@ class Waifu2xConverter:
|
|||||||
|
|
||||||
# the key doesn't need to be passed in this case
|
# the key doesn't need to be passed in this case
|
||||||
if key == 'waifu2x_converter_path':
|
if key == 'waifu2x_converter_path':
|
||||||
execute.append(os.path.join(str(value), 'waifu2x-converter-cpp.exe'))
|
execute.append(pathlib.Path(str(value)) / 'waifu2x-converter-cpp.exe')
|
||||||
|
|
||||||
# null or None means that leave this option out (keep default)
|
# null or None means that leave this option out (keep default)
|
||||||
elif value is None or value is False:
|
elif value is None or value is False:
|
||||||
|
@ -1,23 +1,25 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: future_fstrings -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Name: Waifu2x NCNN Vulkan Driver
|
Name: Waifu2x NCNN Vulkan Driver
|
||||||
Author: SAT3LL
|
Author: SAT3LL
|
||||||
Date Created: June 26, 2019
|
Date Created: June 26, 2019
|
||||||
Last Modified: June 26, 2019
|
Last Modified: July 27, 2019
|
||||||
|
|
||||||
Dev: K4YT3X
|
Dev: K4YT3X
|
||||||
|
|
||||||
Description: This class is a high-level wrapper
|
Description: This class is a high-level wrapper
|
||||||
for waifu2x_ncnn_vulkan.
|
for waifu2x_ncnn_vulkan.
|
||||||
"""
|
"""
|
||||||
from avalon_framework import Avalon
|
|
||||||
|
# built-in imports
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
# third-party imports
|
||||||
|
from avalon_framework import Avalon
|
||||||
|
|
||||||
|
|
||||||
class Waifu2xNcnnVulkan:
|
class Waifu2xNcnnVulkan:
|
||||||
"""This class communicates with waifu2x ncnn vulkan engine
|
"""This class communicates with waifu2x ncnn vulkan engine
|
||||||
|
Loading…
Reference in New Issue
Block a user