29 Commits
4.6.1 ... 4.7.0

Author SHA1 Message Date
K4YT3X
c2260ca709 updated wordings and translations 2020-09-14 14:25:17 -04:00
K4YT3X
776475265e added generate POT file script 2020-09-13 17:10:03 -04:00
K4YT3X
21928f9eb4 commented controlled parameters for waifu2x-caffe and Gifski 2020-09-13 17:09:24 -04:00
K4YT3X
211f024e49 removed useless Gifski width/height elements in GUI 2020-09-13 17:08:34 -04:00
K4YT3X
bfdb051705 fixed global scaling variable errors and Gifski output resolution issues 2020-09-13 17:05:36 -04:00
K4YT3X
d824cd6516 added description for log file saving scheme 2020-09-13 16:43:44 -04:00
K4YT3X
b4f46ad31e enhanced logging and error reporting system 2020-09-13 16:38:44 -04:00
K4YT3X
a8d7f7ecf2 redesigned upscaler to use pillow's Lanczos filter for downscaling; bug fixes 2020-09-13 14:34:52 -04:00
K4YT3X
2b84e497b5 added pillow into requirements 2020-09-13 14:34:17 -04:00
K4YT3X
9b408a6e62 renamed labels 2020-09-13 14:33:46 -04:00
K4YT3X
ecf94490a7 fixed working directory and timing errors 2020-09-13 14:33:35 -04:00
K4YT3X
881183ed0a removed image resizing function which is no longer needed 2020-09-13 14:33:12 -04:00
K4YT3X
9389511d2d fixed bulk processing file output issues 2020-09-13 12:42:55 -04:00
K4YT3X
16389fc8a6 changed frame preview layout 2020-09-13 12:42:36 -04:00
K4YT3X
c7013b2576 redesigned upscaler class to make arbitrary scaling available for images 2020-09-13 11:07:39 -04:00
K4YT3X
a82fcc778e fixed waifu2x-caffe upscale by resolution errors and optimized upscaler code 2020-09-12 19:34:48 -04:00
K4YT3X
6dbdf93ca4 eliminated unnecessary variable upscale_begin_time 2020-09-12 17:40:17 -04:00
K4YT3X
64eb03ac08 calculate width/height automatically if the value is 0 2020-09-10 16:38:11 -04:00
K4YT3X
227cf54a47 removed all use of the walrus operator for better backwards compatibility 2020-09-10 16:19:07 -04:00
K4YT3X
107d31e5dc removed the use of shlex.join for better backwards compatibility 2020-09-10 16:15:05 -04:00
K4YT3X
04562dcaa1 allow only one of width an height to be specified 2020-09-10 13:14:36 -04:00
K4YT3X
7059852586 added arbitrary upscaling ratio/resolution support 2020-09-09 13:07:42 -04:00
K4YT3X
8b7e9f959b fixed Docker Anime4KCPP path 2020-09-04 22:04:04 -04:00
K4YT3X
19bd05149d updated Anime4KCPP build script for the latest Anime4KCPP 2020-09-04 21:48:13 -04:00
K4YT3X
a3706a1a17 fixed aria2c download directory 2020-09-04 16:56:00 -04:00
K4YT3X
5a7c464dea added Anime4KCPP into Ubuntu setup script 2020-09-04 12:26:34 -04:00
K4YT3X
6c1b49b5ed GUI 2.7.3: fixed waifu2x-caffe output quality option 2020-09-04 02:06:24 -04:00
K4YT3X
59860dcfc3 added missing source command in Docker script 2020-09-02 14:00:47 -04:00
K4YT3X
b38ce2c56d fixed gifski build issue by downloading the newest cargo directly 2020-09-02 13:54:58 -04:00
22 changed files with 997 additions and 650 deletions

View File

@@ -4,12 +4,11 @@
Creator: Video2X Bidirectional Logger
Author: K4YT3X
Date Created: June 4, 2020
Last Modified: July 17, 2020
Last Modified: September 13, 2020
"""
# built-in imports
import _io
import pathlib
class BiLogger(object):
@@ -19,15 +18,15 @@ class BiLogger(object):
Original code from: https://stackoverflow.com/a/14906787
"""
def __init__(self, terminal: _io.TextIOWrapper, logfile: pathlib.Path):
def __init__(self, terminal: _io.TextIOWrapper, log_file: _io.BufferedRandom):
""" initialize BiLogger
Args:
terminal (_io.TextIOWrapper): original terminal IO wrapper
logfile (pathlib.Path): target log file path object
logfile (_io.BufferedRandom): log file wrapper
"""
self.terminal = terminal
self.log = logfile.open(mode='a+', encoding='utf-8')
self.log_file = log_file
def write(self, message: str):
""" write message to original terminal output and log file
@@ -37,8 +36,8 @@ class BiLogger(object):
"""
self.terminal.write(message)
self.terminal.flush()
self.log.write(message)
self.log.flush()
self.log_file.write(message)
self.log_file.flush()
def flush(self):
""" flush logger (for compability only)

14
src/generate_pot.ps1 Normal file
View File

@@ -0,0 +1,14 @@
<#
Name: Video2X Generate POT Script
Creator: K4YT3X
Date Created: September 12, 2020
Last Modified: September 12, 2020
Description: A PowerShell script that uses Python's pygettext
script to generate the POT file for translations.
To start a PowerShell session with execution policy bypass
powershell -ExecutionPolicy Bypass
#>
python $env:LOCALAPPDATA\Programs\Python\Python38\Tools\i18n\pygettext.py -d video2x *.py wrappers\*.py

View File

@@ -5,8 +5,8 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2020-05-22 17:51-0400\n"
"PO-Revision-Date: 2020-05-22 17:54-0400\n"
"POT-Creation-Date: 2020-09-14 14:24-0400\n"
"PO-Revision-Date: 2020-09-14 14:24-0400\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: zh_CN\n"
@@ -14,230 +14,278 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
"X-Generator: Poedit 2.3.1\n"
"X-Generator: Poedit 2.4.1\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: progress_monitor.py:42
msgid "Upscaling Progress"
msgstr "放大进度"
#: progress_monitor.py:37
msgid "Processing: {} (pass {}/{})"
msgstr "正在处理:{}进度"
#: upscaler.py:110
#: upscaler.py:149
msgid "Specified or default cache directory is a file/link"
msgstr "指定或默认的缓存目录是文件/链接"
#: upscaler.py:116
#: upscaler.py:155
msgid "Creating cache directory {}"
msgstr "创建缓存目录 {}"
#: upscaler.py:119
#: upscaler.py:158
msgid "Unable to create {}"
msgstr "无法创建 {}"
#: upscaler.py:124
#: upscaler.py:163
msgid "Extracted frames are being saved to: {}"
msgstr "提取的帧将被保存到:{}"
#: upscaler.py:126
#: upscaler.py:165
msgid "Upscaled frames are being saved to: {}"
msgstr "已放大的帧将被保存到:{}"
#: upscaler.py:136
#: upscaler.py:175
msgid "Cleaning up cache directory: {}"
msgstr "清理缓存目录:{}"
#: upscaler.py:141
#: upscaler.py:180
msgid "Unable to delete: {}"
msgstr "无法删除:{}"
#: upscaler.py:147 upscaler.py:162 upscaler.py:173
#: upscaler.py:186 upscaler.py:201 upscaler.py:212
msgid "Input and output path type mismatch"
msgstr "输入和输出路径类型不匹配"
#: upscaler.py:148
#: upscaler.py:187
msgid "Input is multiple files but output is not directory"
msgstr "输入是多个文件,但输出不是目录"
#: upscaler.py:152
#: upscaler.py:191
msgid "Input path {} is neither a file nor a directory"
msgstr "输入路径 {} 既不是文件也不是目录"
#: upscaler.py:156 upscaler.py:178
#: upscaler.py:195 upscaler.py:217
msgid "Input directory and output directory cannot be the same"
msgstr "输入目录和输出目录不能相同"
#: upscaler.py:163
#: upscaler.py:202
msgid "Input is single file but output is directory"
msgstr "所选的输入路径是单个文件,但输出路径是目录"
#: upscaler.py:166
#: upscaler.py:205
msgid "No suffix found in output file path"
msgstr "在输出文件路径中未找到后缀"
#: upscaler.py:167
#: upscaler.py:206
msgid "Suffix must be specified"
msgstr "必须指定文件后缀"
#: upscaler.py:174
#: upscaler.py:213
msgid "Input is directory but output is existing single file"
msgstr "输入是目录,但输出是现有的单个文件"
#: upscaler.py:183
#: upscaler.py:222
msgid "Input path is neither a file nor a directory"
msgstr "输入路径既不是文件也不是目录"
#: upscaler.py:192
#: upscaler.py:231
msgid "FFmpeg or FFprobe cannot be found under the specified path"
msgstr "在指定的路径下找不到 FFmpeg 或 FFprobe"
#: upscaler.py:193 upscaler.py:203
#: upscaler.py:232 upscaler.py:242
msgid "Please check the configuration file settings"
msgstr "请检查配置文件设置"
#: upscaler.py:202
#: upscaler.py:241
msgid "Specified driver executable directory doesn't exist"
msgstr "指定驱动的可执行文件不存在"
#: upscaler.py:229
#: upscaler.py:268
msgid "Failed to parse driver argument: {}"
msgstr "解析驱动程序参数失败:{}"
#: upscaler.py:261
#: upscaler.py:288
msgid "Unrecognized driver: {}"
msgstr "无法识别的驱动名称:{}"
#: upscaler.py:301
#: upscaler.py:328
msgid "Starting progress monitor"
msgstr "启动进度监视器"
#: upscaler.py:306
#: upscaler.py:333
msgid "Starting upscaled image cleaner"
msgstr "启动已放大图像清理程序"
#: upscaler.py:315 upscaler.py:332
#: upscaler.py:342 upscaler.py:359
msgid "Killing progress monitor"
msgstr "终结进度监视器"
#: upscaler.py:318 upscaler.py:335
#: upscaler.py:345 upscaler.py:362
msgid "Killing upscaled image cleaner"
msgstr "终结已放大图像清理程序"
#: upscaler.py:339
#: upscaler.py:366
msgid "Terminating all processes"
msgstr "正在终止所有进程"
#: upscaler.py:346
#: upscaler.py:373
msgid "Main process waiting for subprocesses to exit"
msgstr "主进程开始等待子进程结束"
#: upscaler.py:365 upscaler.py:369
#: upscaler.py:392 upscaler.py:396
msgid "Subprocess {} exited with code {}"
msgstr "子进程 {} 结束,返回码 {}"
#: upscaler.py:375
#: upscaler.py:402
msgid "Stop signal received"
msgstr "收到停止信号"
#: upscaler.py:380
#: upscaler.py:407
msgid "Subprocess execution ran into an error"
msgstr "子进程执行遇到错误"
#: upscaler.py:410
#: upscaler.py:437
msgid "Loading files into processing queue"
msgstr "正在将文件添加到处理队列中"
#: upscaler.py:415
msgid "Loading files from multiple paths"
msgstr "正在从多个路径中导入文件"
#: upscaler.py:416 upscaler.py:435 upscaler.py:442
#: upscaler.py:438
msgid "Input path(s): {}"
msgstr "输入路径:{}"
#: upscaler.py:434
msgid "Loading single file"
msgstr "正在导入单个文件"
#: upscaler.py:484
msgid "File MIME type: {}"
msgstr "文件 MIME 类型:{}"
#: upscaler.py:441
msgid "Loading files from directory"
msgstr "正在从文件夹中导入文件"
#: upscaler.py:455
msgid "Loaded files into processing queue"
msgstr "文件已添加到处理队列"
#: upscaler.py:458
msgid "Input file: {}"
msgstr "输入文件:{}"
#: upscaler.py:485
msgid "Starting to upscale image"
msgstr "开始放大图像"
#: upscaler.py:488 upscaler.py:550
msgid "Upscaling completed"
msgstr "放大完成"
#: upscaler.py:502
msgid "Reading video information"
msgstr "读取视频信息"
#: upscaler.py:516
msgid "Aborting: No video stream found"
msgstr "程序中止:文件中未找到视频流"
#: upscaler.py:521
msgid "Framerate: {}"
msgstr "帧率:{}"
#: upscaler.py:538
msgid "Unsupported pixel format: {}"
msgstr "不支持的像素格式:{}"
#: upscaler.py:548
msgid "Starting to upscale extracted frames"
msgstr "开始对提取的帧进行放大"
#: upscaler.py:555
#: upscaler.py:500
msgid "File {} ({}) neither an image nor a video"
msgstr "文件 {} {} 既不是图片也不是视频"
#: upscaler.py:556
#: upscaler.py:501
msgid "Skipping this file"
msgstr "将跳过此文件"
#: upscaler.py:566
#: upscaler.py:526
msgid "Loaded files into processing queue"
msgstr "文件已添加到处理队列"
#: upscaler.py:529
msgid "Input file: {}"
msgstr "输入文件:{}"
#: upscaler.py:541
msgid "Reading file information"
msgstr "正在读取视频信息"
#: upscaler.py:550
msgid "Starting upscaling image"
msgstr "开始放大图像"
#: upscaler.py:561
msgid "Starting upscaling video/GIF"
msgstr "开始放大视频/GIF"
#: upscaler.py:572
msgid "Aborting: No video stream found"
msgstr "程序中止:文件中未找到视频流"
#: upscaler.py:581
msgid "Getting total number of frames in the file"
msgstr "正在获取文件中的总帧数"
#: upscaler.py:592
msgid "Calculating scaling parameters"
msgstr "正在计算缩放参数"
#: upscaler.py:656
msgid "Framerate: {}"
msgstr "帧率:{}"
#: upscaler.py:657
msgid "Width: {}"
msgstr "宽:{}"
#: upscaler.py:658
msgid "Height: {}"
msgstr "高:{}"
#: upscaler.py:659
msgid "Total number of frames: {}"
msgstr "总帧数:{}"
#: upscaler.py:660
msgid "Output width: {}"
msgstr "输出宽度:{}"
#: upscaler.py:661
msgid "Output height: {}"
msgstr "输出高度:{}"
#: upscaler.py:662
msgid "Required scale ratio: {}"
msgstr "需要的缩放比例:{}"
#: upscaler.py:663
msgid "Upscaling jobs queue: {}"
msgstr "放大工作队列:{}"
#: upscaler.py:680
msgid "Unsupported pixel format: {}"
msgstr "不支持的像素格式:{}"
#: upscaler.py:684
msgid "Starting to upscale extracted frames"
msgstr "开始对提取的帧进行放大"
#: upscaler.py:701
msgid "Upscaling completed"
msgstr "放大完成"
#: upscaler.py:702
msgid "Average processing speed: {} seconds per frame"
msgstr "平均处理速度:{} 秒每帧"
#: upscaler.py:705
msgid "Lanczos downscaling frames"
msgstr "正在使用 Lanczos 算法缩放图像"
#: upscaler.py:710
msgid "Downscaling"
msgstr "正在缩放图像"
#: upscaler.py:729
msgid "Exporting image"
msgstr "正在导出图像"
#: upscaler.py:739
msgid "Converting extracted frames into GIF image"
msgstr "正在将提取的帧转换为 GIF"
#: upscaler.py:570 upscaler.py:579
#: upscaler.py:743 upscaler.py:752
msgid "Conversion completed"
msgstr "转换已完成"
#: upscaler.py:575
#: upscaler.py:748
msgid "Converting extracted frames into video"
msgstr "正在将提取的帧转换为视频"
#: upscaler.py:583
#: upscaler.py:756
msgid "Migrating audio, subtitles and other streams to upscaled video"
msgstr "正在将音频、字幕和其他流迁移到放大后的视频"
#: upscaler.py:593
#: upscaler.py:766
msgid "Failed to migrate streams"
msgstr "迁移流失败"
#: upscaler.py:594
#: upscaler.py:767
msgid "Trying to output video without additional streams"
msgstr "正在尝试输出不含其他流的视频"
#: upscaler.py:610
#: upscaler.py:783
msgid "Output video file exists"
msgstr "输出目标文件已存在"
#: upscaler.py:614
#: upscaler.py:787
msgid "Created temporary directory to contain file"
msgstr "为文件创建了临时目录"
#: upscaler.py:617
#: upscaler.py:790
msgid "Writing intermediate file to: {}"
msgstr "正在将中间视频文件写入至:{}"
@@ -265,66 +313,101 @@ msgstr "Video2X 选项"
msgid "show this help message and exit"
msgstr "显示此帮助消息并退出"
#: video2x.py:110
#: video2x.py:116
msgid "source video file/directory"
msgstr "源视频文件/目录"
#: video2x.py:111
#: video2x.py:117
msgid "output video file/directory"
msgstr "输出视频文件/目录"
#: video2x.py:112
msgid "video2x config file path"
msgstr "video2x 配置文件路径"
#: video2x.py:119
msgid "Video2X config file path"
msgstr "Video2X 配置文件路径"
#: video2x.py:114
#: video2x.py:121
msgid "log file path"
msgstr "日志文件路径"
#: video2x.py:122
msgid "display version, lawful information and exit"
msgstr "显示版本和法律信息并退出"
#: video2x.py:117
#: video2x.py:125
msgid "Upscaling Options"
msgstr "视频放大选项"
#: video2x.py:118
msgid "upscaling driver"
msgstr "视频放大驱动"
#: video2x.py:119
#: video2x.py:126
msgid "scaling ratio"
msgstr "缩放比"
#: video2x.py:120
#: video2x.py:127
msgid "output width"
msgstr "输出宽度"
#: video2x.py:128
msgid "output height"
msgstr "输出高度"
#: video2x.py:129
msgid "upscaling driver"
msgstr "视频放大驱动"
#: video2x.py:130
msgid "number of processes to use for upscaling"
msgstr "并发进程数"
#: video2x.py:121
#: video2x.py:131
msgid "preserve extracted and upscaled frames"
msgstr "保留提取的和放大的帧"
#: video2x.py:161
#: video2x.py:171
msgid "This file cannot be imported"
msgstr "此文件无法被当作模块导入"
#: video2x.py:236
#: video2x.py:187
msgid "Specify either scaling ratio or scaling resolution, not both"
msgstr "您只能指定缩放比或输出分辨率两者之一"
#: video2x.py:273
msgid "Program completed, taking {} seconds"
msgstr "程序执行完毕,总计花费 {} 秒"
#: video2x.py:239
#: video2x.py:277
msgid "An exception has occurred"
msgstr "发生了异常"
#: video2x.py:292
msgid "The error log file can be found at: {}"
msgstr "错误日志已被保存到:{}"
#~ msgid "disable logging"
#~ msgstr "禁用日志"
#~ msgid "Only one of scaling width and scaling height is specified"
#~ msgstr "输出高度和宽度仅有其中一项被指定"
#~ msgid "Redirecting console logs to {}"
#~ msgstr "将控制台日志重定向到 {}"
#~ msgid "Upscaling Progress"
#~ msgstr "放大进度"
#~ msgid "Loading files from multiple paths"
#~ msgstr "正在从多个路径中导入文件"
#~ msgid "Loading single file"
#~ msgstr "正在导入单个文件"
#~ msgid "Loading files from directory"
#~ msgstr "正在从文件夹中导入文件"
#~ msgid "Anime4KCPP doesn't yet support GIF processing"
#~ msgstr "Anime4KCPP 尚不支持GIF处理"
#~ msgid "Starting to upscale video with Anime4KCPP"
#~ msgstr "开始用 Anime4KCPP 放大视频"
#~ msgid "output video width"
#~ msgstr "输出视频宽度"
#~ msgid "output video height"
#~ msgstr "输出视频高度"
#~ msgid "You must specify input video file/directory path"
#~ msgstr "您必须指定输入视频文件/目录路径"
@@ -340,8 +423,5 @@ msgstr "发生了异常"
#~ msgid "Scaling ratio must be one of 2, 3 or 4 for srmd_ncnn_vulkan"
#~ msgstr "srmd_ncnn_vulkan 的缩放比必须为 2、3 或 4"
#~ msgid "You can only specify either scaling ratio or output width and height"
#~ msgstr "您只能指定缩放比或输出宽度和高度两者之一"
#~ msgid "You must specify both width and height"
#~ msgstr "您必须同时指定宽度和高度"

View File

@@ -4,7 +4,7 @@
Name: Video2X Upscale Progress Monitor
Author: K4YT3X
Date Created: May 7, 2020
Last Modified: June 7, 2020
Last Modified: September 9, 2020
"""
# built-in imports
@@ -34,12 +34,7 @@ class ProgressMonitor(threading.Thread):
def run(self):
self.running = True
# get number of extracted frames
self.upscaler.total_frames = 0
for directory in self.extracted_frames_directories:
self.upscaler.total_frames += len([f for f in directory.iterdir() if str(f).lower().endswith(self.upscaler.extracted_frame_format.lower())])
with tqdm(total=self.upscaler.total_frames, ascii=True, desc=_('Upscaling Progress')) as progress_bar:
with tqdm(total=self.upscaler.total_frames, ascii=True, desc=_('Processing: {} (pass {}/{})').format(self.upscaler.current_input_file.name, self.upscaler.current_pass, len(self.upscaler.scaling_jobs))) as progress_bar:
# tqdm update method adds the value to the progress
# bar instead of setting the value. Therefore, a delta
# needs to be calculated.

View File

@@ -1,9 +1,10 @@
avalon_framework
colorama
patool
pillow
pyqt5
python-magic; platform_system != "Windows"
python-magic-bin; platform_system == "Windows"
python-magic; platform_system != "Windows"
pyyaml
requests
tqdm

View File

@@ -4,7 +4,7 @@
Name: Video2X Upscaler
Author: K4YT3X
Date Created: December 10, 2018
Last Modified: June 29, 2020
Last Modified: September 13, 2020
Description: This file contains the Upscaler class. Each
instance of the Upscaler class is an upscaler on an image or
@@ -25,6 +25,7 @@ import copy
import gettext
import importlib
import locale
import math
import mimetypes
import pathlib
import queue
@@ -36,7 +37,9 @@ import time
import traceback
# third-party imports
from PIL import Image
from avalon_framework import Avalon
from tqdm import tqdm
import magic
# internationalization constants
@@ -50,7 +53,7 @@ language.install()
_ = language.gettext
# version information
UPSCALER_VERSION = '4.2.2'
UPSCALER_VERSION = '4.4.0'
# these names are consistent for
# - driver selection in command line
@@ -63,6 +66,14 @@ AVAILABLE_DRIVERS = ['waifu2x_caffe',
'realsr_ncnn_vulkan',
'anime4kcpp']
# fixed scaling ratios supported by the drivers
# that only support certain fixed scale ratios
DRIVER_FIXED_SCALING_RATIOS = {
'waifu2x_ncnn_vulkan': [1, 2],
'srmd_ncnn_vulkan': [2, 3, 4],
'realsr_ncnn_vulkan': [4],
}
class Upscaler:
""" An instance of this class is a upscaler that will
@@ -82,6 +93,8 @@ class Upscaler:
gifski_settings: dict,
driver: str = 'waifu2x_caffe',
scale_ratio: float = None,
scale_width: int = None,
scale_height: int = None,
processes: int = 1,
video2x_cache_directory: pathlib.Path = pathlib.Path(tempfile.gettempdir()) / 'video2x',
extracted_frame_format: str = 'png',
@@ -101,6 +114,8 @@ class Upscaler:
# optional parameters
self.driver = driver
self.scale_ratio = scale_ratio
self.scale_width = scale_width
self.scale_height = scale_height
self.processes = processes
self.video2x_cache_directory = video2x_cache_directory
self.extracted_frame_format = extracted_frame_format
@@ -111,10 +126,13 @@ class Upscaler:
# other internal members and signals
self.running = False
self.current_processing_starting_time = time.time()
self.total_frames_upscaled = 0
self.total_frames = 0
self.total_files = 0
self.total_processed = 0
self.scaling_jobs = []
self.current_pass = 0
self.current_input_file = pathlib.Path()
self.last_frame_upscaled = pathlib.Path()
@@ -250,31 +268,19 @@ class Upscaler:
Avalon.error(_('Failed to parse driver argument: {}').format(e.args[0]))
raise e
# waifu2x-caffe scale_ratio, scale_width and scale_height check
if self.driver == 'waifu2x_caffe':
if (driver_settings['scale_width'] != 0 and driver_settings['scale_height'] == 0 or
driver_settings['scale_width'] == 0 and driver_settings['scale_height'] != 0):
Avalon.error(_('Only one of scale_width and scale_height is specified for waifu2x-caffe'))
raise AttributeError('only one of scale_width and scale_height is specified for waifu2x-caffe')
# if scale_width and scale_height are specified, ensure scale_ratio is None
elif self.driver_settings['scale_width'] != 0 and self.driver_settings['scale_height'] != 0:
self.driver_settings['scale_ratio'] = None
# if scale_width and scale_height not specified
# ensure they are None, not 0
else:
self.driver_settings['scale_width'] = None
self.driver_settings['scale_height'] = None
def _upscale_frames(self):
def _upscale_frames(self, input_directory: pathlib.Path, output_directory: pathlib.Path):
""" Upscale video frames with waifu2x-caffe
This function upscales all the frames extracted
by ffmpeg using the waifu2x-caffe binary.
Arguments:
w2 {Waifu2x Object} -- initialized waifu2x object
Args:
input_directory (pathlib.Path): directory containing frames to upscale
output_directory (pathlib.Path): directory which upscaled frames should be exported to
Raises:
UnrecognizedDriverError: raised when the given driver is not recognized
e: re-raised exception after an exception has been captured and finished processing in this scope
"""
# initialize waifu2x driver
@@ -282,7 +288,7 @@ class Upscaler:
raise UnrecognizedDriverError(_('Unrecognized driver: {}').format(self.driver))
# list all images in the extracted frames
frames = [(self.extracted_frames / f) for f in self.extracted_frames.iterdir() if f.is_file]
frames = [(input_directory / f) for f in input_directory.iterdir() if f.is_file]
# if we have less images than processes,
# create only the processes necessary
@@ -293,7 +299,7 @@ class Upscaler:
# name into a list
process_directories = []
for process_id in range(self.processes):
process_directory = self.extracted_frames / str(process_id)
process_directory = input_directory / str(process_id)
process_directories.append(process_directory)
# delete old directories and create new directories
@@ -303,7 +309,7 @@ class Upscaler:
# waifu2x-converter-cpp will perform multi-threading within its own process
if self.driver in ['waifu2x_converter_cpp', 'waifu2x_ncnn_vulkan', 'srmd_ncnn_vulkan', 'realsr_ncnn_vulkan', 'anime4kcpp']:
process_directories = [self.extracted_frames]
process_directories = [input_directory]
else:
# evenly distribute images into each directory
@@ -316,7 +322,7 @@ class Upscaler:
# create driver processes and start them
for process_directory in process_directories:
self.process_pool.append(self.driver_object.upscale(process_directory, self.upscaled_frames))
self.process_pool.append(self.driver_object.upscale(process_directory, output_directory))
# start progress bar in a different thread
Avalon.debug_info(_('Starting progress monitor'))
@@ -325,7 +331,7 @@ class Upscaler:
# create the clearer and start it
Avalon.debug_info(_('Starting upscaled image cleaner'))
self.image_cleaner = ImageCleaner(self.extracted_frames, self.upscaled_frames, len(self.process_pool))
self.image_cleaner = ImageCleaner(input_directory, output_directory, len(self.process_pool))
self.image_cleaner.start()
# wait for all process to exit
@@ -343,11 +349,11 @@ class Upscaler:
# if the driver is waifu2x-converter-cpp
# images need to be renamed to be recognizable for FFmpeg
if self.driver == 'waifu2x_converter_cpp':
for image in [f for f in self.upscaled_frames.iterdir() if f.is_file()]:
for image in [f for f in output_directory.iterdir() if f.is_file()]:
renamed = re.sub(f'_\\[.*\\]\\[x(\\d+(\\.\\d+)?)\\]\\.{self.extracted_frame_format}',
f'.{self.extracted_frame_format}',
str(image.name))
(self.upscaled_frames / image).rename(self.upscaled_frames / renamed)
(output_directory / image).rename(output_directory / renamed)
# upscaling done, kill helper threads
Avalon.debug_info(_('Killing progress monitor'))
@@ -468,15 +474,15 @@ class Upscaler:
input_file_type = input_file_mime_type.split('/')[0]
input_file_subtype = input_file_mime_type.split('/')[1]
except Exception:
input_file_type = input_file_subtype = None
# in case python-magic fails to detect file type
# try guessing file mime type with mimetypes
if input_file_type not in ['image', 'video']:
# in case python-magic fails to detect file type
# try guessing file mime type with mimetypes
input_file_mime_type = mimetypes.guess_type(input_path.name)[0]
input_file_type = input_file_mime_type.split('/')[0]
input_file_subtype = input_file_mime_type.split('/')[1]
Avalon.debug_info(_('File MIME type: {}').format(input_file_mime_type))
# set default output file suffixes
# if image type is GIF, default output suffix is also .gif
if input_file_mime_type == 'image/gif':
@@ -528,34 +534,35 @@ class Upscaler:
# get new job from queue
self.current_input_file, output_path, input_file_mime_type, input_file_type, input_file_subtype = self.processing_queue.get()
# get current job starting time for GUI calculations
self.current_processing_starting_time = time.time()
# get video information JSON using FFprobe
Avalon.info(_('Reading file information'))
file_info = self.ffmpeg_object.probe_file_info(self.current_input_file)
# create temporary directories for storing frames
self.create_temp_directories()
# 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'))
Avalon.info(_('Starting upscaling image'))
# static images don't require GIF or video encoding
# go to the next task
self.processing_queue.task_done()
self.total_processed += 1
continue
# copy original file into the pre-processing directory
shutil.copy(self.current_input_file, self.extracted_frames / self.current_input_file.name)
# if input file is a image/gif file or a video
elif input_file_mime_type == 'image/gif' or input_file_type == 'video':
width = int(file_info['streams'][0]['width'])
height = int(file_info['streams'][0]['height'])
framerate = self.total_frames = 1
self.create_temp_directories()
# get video information JSON using FFprobe
Avalon.info(_('Reading video information'))
video_info = self.ffmpeg_object.probe_file_info(self.current_input_file)
# analyze original video with FFprobe and retrieve framerate
# width, height = info['streams'][0]['width'], info['streams'][0]['height']
# elif input_file_mime_type == 'image/gif' or input_file_type == 'video':
else:
Avalon.info(_('Starting upscaling video/GIF'))
# find index of video stream
video_stream_index = None
for stream in video_info['streams']:
for stream in file_info['streams']:
if stream['codec_type'] == 'video':
video_stream_index = stream['index']
break
@@ -566,96 +573,222 @@ class Upscaler:
raise StreamNotFoundError('no video stream found')
# get average frame rate of video stream
framerate = float(Fraction(video_info['streams'][video_stream_index]['r_frame_rate']))
Avalon.info(_('Framerate: {}').format(framerate))
# self.ffmpeg_object.pixel_format = video_info['streams'][video_stream_index]['pix_fmt']
framerate = float(Fraction(file_info['streams'][video_stream_index]['r_frame_rate']))
width = int(file_info['streams'][video_stream_index]['width'])
height = int(file_info['streams'][video_stream_index]['height'])
# extract frames from video
# get total number of frames
Avalon.info(_('Getting total number of frames in the file'))
# if container stores total number of frames in nb_frames, fetch it directly
if 'nb_frames' in file_info['streams'][video_stream_index]:
self.total_frames = int(file_info['streams'][video_stream_index]['nb_frames'])
# otherwise call FFprobe to count the total number of frames
else:
self.total_frames = self.ffmpeg_object.get_number_of_frames(self.current_input_file, video_stream_index)
# calculate scale width/height/ratio and scaling jobs if required
Avalon.info(_('Calculating scaling parameters'))
# create a local copy of the global output settings
output_scale = self.scale_ratio
output_width = self.scale_width
output_height = self.scale_height
# calculate output width and height if scale ratio is specified
if output_scale is not None:
output_width = int(math.ceil(width * output_scale / 2.0) * 2)
output_height = int(math.ceil(height * output_scale / 2.0) * 2)
else:
# scale keeping aspect ratio is only one of width/height is given
if output_width == 0 or output_width is None:
output_width = output_height / height * width
elif output_height == 0 or output_height is None:
output_height = output_width / width * height
output_width = int(math.ceil(output_width / 2.0) * 2)
output_height = int(math.ceil(output_height / 2.0) * 2)
# calculate required minimum scale ratio
output_scale = max(output_width / width, output_height / height)
# if driver is one of the drivers that doesn't support arbitrary scaling ratio
# TODO: more documentations on this block
if self.driver in DRIVER_FIXED_SCALING_RATIOS:
# select the optimal driver scaling ratio to use
supported_scaling_ratios = sorted(DRIVER_FIXED_SCALING_RATIOS[self.driver])
remaining_scaling_ratio = math.ceil(output_scale)
self.scaling_jobs = []
while remaining_scaling_ratio > 1:
for ratio in supported_scaling_ratios:
if ratio >= remaining_scaling_ratio:
self.scaling_jobs.append(ratio)
remaining_scaling_ratio /= ratio
break
else:
found = False
for i in supported_scaling_ratios:
for j in supported_scaling_ratios:
if i * j >= remaining_scaling_ratio:
self.scaling_jobs.extend([i, j])
remaining_scaling_ratio /= i * j
found = True
break
if found is True:
break
if found is False:
self.scaling_jobs.append(supported_scaling_ratios[-1])
remaining_scaling_ratio /= supported_scaling_ratios[-1]
else:
self.scaling_jobs = [output_scale]
# print file information
Avalon.debug_info(_('Framerate: {}').format(framerate))
Avalon.debug_info(_('Width: {}').format(width))
Avalon.debug_info(_('Height: {}').format(height))
Avalon.debug_info(_('Total number of frames: {}').format(self.total_frames))
Avalon.debug_info(_('Output width: {}').format(output_width))
Avalon.debug_info(_('Output height: {}').format(output_height))
Avalon.debug_info(_('Required scale ratio: {}').format(output_scale))
Avalon.debug_info(_('Upscaling jobs queue: {}').format(self.scaling_jobs))
# extract frames from video
if input_file_mime_type == 'image/gif' or input_file_type == 'video':
self.process_pool.append((self.ffmpeg_object.extract_frames(self.current_input_file, self.extracted_frames)))
self._wait()
# if driver is waifu2x-caffe
# pass pixel format output depth information
if self.driver == 'waifu2x_caffe':
# get a dict of all pixel formats and corresponding bit depth
pixel_formats = self.ffmpeg_object.get_pixel_formats()
# if driver is waifu2x-caffe
# pass pixel format output depth information
if self.driver == 'waifu2x_caffe':
# get a dict of all pixel formats and corresponding bit depth
pixel_formats = self.ffmpeg_object.get_pixel_formats()
# try getting pixel format's corresponding bti depth
try:
self.driver_settings['output_depth'] = pixel_formats[self.ffmpeg_object.pixel_format]
except KeyError:
Avalon.error(_('Unsupported pixel format: {}').format(self.ffmpeg_object.pixel_format))
raise UnsupportedPixelError(f'unsupported pixel format {self.ffmpeg_object.pixel_format}')
# try getting pixel format's corresponding bti depth
try:
self.driver_settings['output_depth'] = pixel_formats[self.ffmpeg_object.pixel_format]
except KeyError:
Avalon.error(_('Unsupported pixel format: {}').format(self.ffmpeg_object.pixel_format))
raise UnsupportedPixelError(f'unsupported pixel format {self.ffmpeg_object.pixel_format}')
# width/height will be coded width/height x upscale factor
# original_width = video_info['streams'][video_stream_index]['width']
# original_height = video_info['streams'][video_stream_index]['height']
# scale_width = int(self.scale_ratio * original_width)
# scale_height = int(self.scale_ratio * original_height)
# upscale images one by one using waifu2x
Avalon.info(_('Starting to upscale extracted frames'))
upscale_begin_time = time.time()
# upscale images one by one using waifu2x
Avalon.info(_('Starting to upscale extracted frames'))
self._upscale_frames()
Avalon.info(_('Upscaling completed'))
self.current_pass = 1
if self.driver == 'waifu2x_caffe':
self.driver_object.set_scale_resolution(output_width, output_height)
else:
self.driver_object.set_scale_ratio(self.scaling_jobs[0])
self._upscale_frames(self.extracted_frames, self.upscaled_frames)
for job in self.scaling_jobs[1:]:
self.current_pass += 1
self.driver_object.set_scale_ratio(job)
shutil.rmtree(self.extracted_frames)
shutil.move(self.upscaled_frames, self.extracted_frames)
self.upscaled_frames.mkdir(parents=True, exist_ok=True)
self._upscale_frames(self.extracted_frames, self.upscaled_frames)
Avalon.info(_('Upscaling completed'))
Avalon.info(_('Average processing speed: {} seconds per frame').format(self.total_frames / (time.time() - upscale_begin_time)))
# downscale frames with Lanczos
Avalon.info(_('Lanczos downscaling frames'))
shutil.rmtree(self.extracted_frames)
shutil.move(self.upscaled_frames, self.extracted_frames)
self.upscaled_frames.mkdir(parents=True, exist_ok=True)
for image in tqdm([i for i in self.extracted_frames.iterdir() if i.is_file() and i.name.endswith(self.extracted_frame_format)], ascii=True, desc=_('Downscaling')):
image_object = Image.open(image)
# if the image dimensions are not equal to the output size
# resize the image using Lanczos
if (image_object.width, image_object.height) != (output_width, output_height):
image_object.resize((output_width, output_height), Image.LANCZOS).save(self.upscaled_frames / image.name)
image_object.close()
# if the image's dimensions are already equal to the output size
# move image to the finished directory
else:
image_object.close()
shutil.move(image, self.upscaled_frames / image.name)
# start handling output
# output can be either GIF or video
if input_file_type == 'image' and input_file_subtype != 'gif':
# 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.extracted_frame_format))
self._wait()
Avalon.info(_('Conversion completed'))
Avalon.info(_('Exporting image'))
# if the desired output is video
# there should be only one image in the directory
shutil.move([f for f in self.upscaled_frames.iterdir() if f.is_file()][0], output_path)
# elif input_file_mime_type == 'image/gif' or input_file_type == 'video':
else:
# frames to video
Avalon.info(_('Converting extracted frames into video'))
self.process_pool.append(self.ffmpeg_object.assemble_video(framerate, self.upscaled_frames))
# f'{scale_width}x{scale_height}'
self._wait()
Avalon.info(_('Conversion completed'))
try:
# migrate audio tracks and subtitles
Avalon.info(_('Migrating audio, subtitles and other streams to upscaled video'))
self.process_pool.append(self.ffmpeg_object.migrate_streams(self.current_input_file,
output_path,
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.extracted_frame_format, output_width, output_height))
self._wait()
Avalon.info(_('Conversion completed'))
# if failed to copy streams
# use file with only video stream
except subprocess.CalledProcessError:
traceback.print_exc()
Avalon.error(_('Failed to migrate streams'))
Avalon.warning(_('Trying to output video without additional streams'))
# if the desired output is video
else:
# frames to video
Avalon.info(_('Converting extracted frames into video'))
self.process_pool.append(self.ffmpeg_object.assemble_video(framerate, self.upscaled_frames))
# f'{scale_width}x{scale_height}'
self._wait()
Avalon.info(_('Conversion completed'))
if input_file_mime_type == 'image/gif':
# copy will overwrite destination content if exists
shutil.copy(self.upscaled_frames / self.ffmpeg_object.intermediate_file_name, output_path)
try:
# migrate audio tracks and subtitles
Avalon.info(_('Migrating audio, subtitles and other streams to upscaled video'))
self.process_pool.append(self.ffmpeg_object.migrate_streams(self.current_input_file,
output_path,
self.upscaled_frames))
self._wait()
else:
# construct output file path
output_file_name = f'{output_path.stem}{self.ffmpeg_object.intermediate_file_name.suffix}'
output_video_path = output_path.parent / output_file_name
# if failed to copy streams
# use file with only video stream
except subprocess.CalledProcessError:
traceback.print_exc()
Avalon.error(_('Failed to migrate streams'))
Avalon.warning(_('Trying to output video without additional streams'))
# if output file already exists
# create temporary directory in output folder
# temporary directories generated by tempfile are guaranteed to be unique
# and won't conflict with other files
if output_video_path.exists():
Avalon.error(_('Output video file exists'))
if input_file_mime_type == 'image/gif':
# copy will overwrite destination content if exists
shutil.copy(self.upscaled_frames / self.ffmpeg_object.intermediate_file_name, output_path)
temporary_directory = pathlib.Path(tempfile.mkdtemp(dir=output_path.parent))
output_video_path = temporary_directory / output_file_name
Avalon.info(_('Created temporary directory to contain file'))
else:
# construct output file path
output_file_name = f'{output_path.stem}{self.ffmpeg_object.intermediate_file_name.suffix}'
output_video_path = output_path.parent / output_file_name
# move file to new destination
Avalon.info(_('Writing intermediate file to: {}').format(output_video_path.absolute()))
shutil.move(self.upscaled_frames / self.ffmpeg_object.intermediate_file_name, output_video_path)
# if output file already exists
# create temporary directory in output folder
# temporary directories generated by tempfile are guaranteed to be unique
# and won't conflict with other files
if output_video_path.exists():
Avalon.error(_('Output video file exists'))
temporary_directory = pathlib.Path(tempfile.mkdtemp(dir=output_path.parent))
output_video_path = temporary_directory / output_file_name
Avalon.info(_('Created temporary directory to contain file'))
# move file to new destination
Avalon.info(_('Writing intermediate file to: {}').format(output_video_path.absolute()))
shutil.move(self.upscaled_frames / self.ffmpeg_object.intermediate_file_name, output_video_path)
# increment total number of files processed
self.cleanup_temp_directories()

View File

@@ -5,7 +5,7 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2020-05-22 17:51-0400\n"
"POT-Creation-Date: 2020-09-14 14:24-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -15,227 +15,275 @@ msgstr ""
"Generated-By: pygettext.py 1.5\n"
#: progress_monitor.py:42
msgid "Upscaling Progress"
#: progress_monitor.py:37
msgid "Processing: {} (pass {}/{})"
msgstr ""
#: upscaler.py:110
#: upscaler.py:149
msgid "Specified or default cache directory is a file/link"
msgstr ""
#: upscaler.py:116
#: upscaler.py:155
msgid "Creating cache directory {}"
msgstr ""
#: upscaler.py:119
#: upscaler.py:158
msgid "Unable to create {}"
msgstr ""
#: upscaler.py:124
#: upscaler.py:163
msgid "Extracted frames are being saved to: {}"
msgstr ""
#: upscaler.py:126
#: upscaler.py:165
msgid "Upscaled frames are being saved to: {}"
msgstr ""
#: upscaler.py:136
#: upscaler.py:175
msgid "Cleaning up cache directory: {}"
msgstr ""
#: upscaler.py:141
#: upscaler.py:180
msgid "Unable to delete: {}"
msgstr ""
#: upscaler.py:147 upscaler.py:162 upscaler.py:173
#: upscaler.py:186 upscaler.py:201 upscaler.py:212
msgid "Input and output path type mismatch"
msgstr ""
#: upscaler.py:148
#: upscaler.py:187
msgid "Input is multiple files but output is not directory"
msgstr ""
#: upscaler.py:152
#: upscaler.py:191
msgid "Input path {} is neither a file nor a directory"
msgstr ""
#: upscaler.py:156 upscaler.py:178
#: upscaler.py:195 upscaler.py:217
msgid "Input directory and output directory cannot be the same"
msgstr ""
#: upscaler.py:163
#: upscaler.py:202
msgid "Input is single file but output is directory"
msgstr ""
#: upscaler.py:166
#: upscaler.py:205
msgid "No suffix found in output file path"
msgstr ""
#: upscaler.py:167
#: upscaler.py:206
msgid "Suffix must be specified"
msgstr ""
#: upscaler.py:174
#: upscaler.py:213
msgid "Input is directory but output is existing single file"
msgstr ""
#: upscaler.py:183
#: upscaler.py:222
msgid "Input path is neither a file nor a directory"
msgstr ""
#: upscaler.py:192
#: upscaler.py:231
msgid "FFmpeg or FFprobe cannot be found under the specified path"
msgstr ""
#: upscaler.py:193 upscaler.py:203
#: upscaler.py:232 upscaler.py:242
msgid "Please check the configuration file settings"
msgstr ""
#: upscaler.py:202
#: upscaler.py:241
msgid "Specified driver executable directory doesn't exist"
msgstr ""
#: upscaler.py:229
#: upscaler.py:268
msgid "Failed to parse driver argument: {}"
msgstr ""
#: upscaler.py:261
#: upscaler.py:288
msgid "Unrecognized driver: {}"
msgstr ""
#: upscaler.py:301
#: upscaler.py:328
msgid "Starting progress monitor"
msgstr ""
#: upscaler.py:306
#: upscaler.py:333
msgid "Starting upscaled image cleaner"
msgstr ""
#: upscaler.py:315 upscaler.py:332
#: upscaler.py:342 upscaler.py:359
msgid "Killing progress monitor"
msgstr ""
#: upscaler.py:318 upscaler.py:335
#: upscaler.py:345 upscaler.py:362
msgid "Killing upscaled image cleaner"
msgstr ""
#: upscaler.py:339
#: upscaler.py:366
msgid "Terminating all processes"
msgstr ""
#: upscaler.py:346
#: upscaler.py:373
msgid "Main process waiting for subprocesses to exit"
msgstr ""
#: upscaler.py:365 upscaler.py:369
#: upscaler.py:392 upscaler.py:396
msgid "Subprocess {} exited with code {}"
msgstr ""
#: upscaler.py:375
#: upscaler.py:402
msgid "Stop signal received"
msgstr ""
#: upscaler.py:380
#: upscaler.py:407
msgid "Subprocess execution ran into an error"
msgstr ""
#: upscaler.py:410
#: upscaler.py:437
msgid "Loading files into processing queue"
msgstr ""
#: upscaler.py:415
msgid "Loading files from multiple paths"
msgstr ""
#: upscaler.py:416 upscaler.py:435 upscaler.py:442
#: upscaler.py:438
msgid "Input path(s): {}"
msgstr ""
#: upscaler.py:434
msgid "Loading single file"
#: upscaler.py:484
msgid "File MIME type: {}"
msgstr ""
#: upscaler.py:441
msgid "Loading files from directory"
msgstr ""
#: upscaler.py:455
msgid "Loaded files into processing queue"
msgstr ""
#: upscaler.py:458
msgid "Input file: {}"
msgstr ""
#: upscaler.py:485
msgid "Starting to upscale image"
msgstr ""
#: upscaler.py:488 upscaler.py:550
msgid "Upscaling completed"
msgstr ""
#: upscaler.py:502
msgid "Reading video information"
msgstr ""
#: upscaler.py:516
msgid "Aborting: No video stream found"
msgstr ""
#: upscaler.py:521
msgid "Framerate: {}"
msgstr ""
#: upscaler.py:538
msgid "Unsupported pixel format: {}"
msgstr ""
#: upscaler.py:548
msgid "Starting to upscale extracted frames"
msgstr ""
#: upscaler.py:555
#: upscaler.py:500
msgid "File {} ({}) neither an image nor a video"
msgstr ""
#: upscaler.py:556
#: upscaler.py:501
msgid "Skipping this file"
msgstr ""
#: upscaler.py:566
#: upscaler.py:526
msgid "Loaded files into processing queue"
msgstr ""
#: upscaler.py:529
msgid "Input file: {}"
msgstr ""
#: upscaler.py:541
msgid "Reading file information"
msgstr ""
#: upscaler.py:550
msgid "Starting upscaling image"
msgstr ""
#: upscaler.py:561
msgid "Starting upscaling video/GIF"
msgstr ""
#: upscaler.py:572
msgid "Aborting: No video stream found"
msgstr ""
#: upscaler.py:581
msgid "Getting total number of frames in the file"
msgstr ""
#: upscaler.py:592
msgid "Calculating scaling parameters"
msgstr ""
#: upscaler.py:656
msgid "Framerate: {}"
msgstr ""
#: upscaler.py:657
msgid "Width: {}"
msgstr ""
#: upscaler.py:658
msgid "Height: {}"
msgstr ""
#: upscaler.py:659
msgid "Total number of frames: {}"
msgstr ""
#: upscaler.py:660
msgid "Output width: {}"
msgstr ""
#: upscaler.py:661
msgid "Output height: {}"
msgstr ""
#: upscaler.py:662
msgid "Required scale ratio: {}"
msgstr ""
#: upscaler.py:663
msgid "Upscaling jobs queue: {}"
msgstr ""
#: upscaler.py:680
msgid "Unsupported pixel format: {}"
msgstr ""
#: upscaler.py:684
msgid "Starting to upscale extracted frames"
msgstr ""
#: upscaler.py:701
msgid "Upscaling completed"
msgstr ""
#: upscaler.py:702
msgid "Average processing speed: {} seconds per frame"
msgstr ""
#: upscaler.py:705
msgid "Lanczos downscaling frames"
msgstr ""
#: upscaler.py:710
msgid "Downscaling"
msgstr ""
#: upscaler.py:729
msgid "Exporting image"
msgstr ""
#: upscaler.py:739
msgid "Converting extracted frames into GIF image"
msgstr ""
#: upscaler.py:570 upscaler.py:579
#: upscaler.py:743 upscaler.py:752
msgid "Conversion completed"
msgstr ""
#: upscaler.py:575
#: upscaler.py:748
msgid "Converting extracted frames into video"
msgstr ""
#: upscaler.py:583
#: upscaler.py:756
msgid "Migrating audio, subtitles and other streams to upscaled video"
msgstr ""
#: upscaler.py:593
#: upscaler.py:766
msgid "Failed to migrate streams"
msgstr ""
#: upscaler.py:594
#: upscaler.py:767
msgid "Trying to output video without additional streams"
msgstr ""
#: upscaler.py:610
#: upscaler.py:783
msgid "Output video file exists"
msgstr ""
#: upscaler.py:614
#: upscaler.py:787
msgid "Created temporary directory to contain file"
msgstr ""
#: upscaler.py:617
#: upscaler.py:790
msgid "Writing intermediate file to: {}"
msgstr ""
@@ -257,51 +305,71 @@ msgstr ""
msgid "show this help message and exit"
msgstr ""
#: video2x.py:110
#: video2x.py:116
msgid "source video file/directory"
msgstr ""
#: video2x.py:111
#: video2x.py:117
msgid "output video file/directory"
msgstr ""
#: video2x.py:112
msgid "video2x config file path"
msgstr ""
#: video2x.py:114
msgid "display version, lawful information and exit"
msgstr ""
#: video2x.py:117
msgid "Upscaling Options"
msgstr ""
#: video2x.py:118
msgid "upscaling driver"
msgstr ""
#: video2x.py:119
msgid "scaling ratio"
msgstr ""
#: video2x.py:120
msgid "number of processes to use for upscaling"
msgid "Video2X config file path"
msgstr ""
#: video2x.py:121
msgid "log file path"
msgstr ""
#: video2x.py:122
msgid "display version, lawful information and exit"
msgstr ""
#: video2x.py:125
msgid "Upscaling Options"
msgstr ""
#: video2x.py:126
msgid "scaling ratio"
msgstr ""
#: video2x.py:127
msgid "output width"
msgstr ""
#: video2x.py:128
msgid "output height"
msgstr ""
#: video2x.py:129
msgid "upscaling driver"
msgstr ""
#: video2x.py:130
msgid "number of processes to use for upscaling"
msgstr ""
#: video2x.py:131
msgid "preserve extracted and upscaled frames"
msgstr ""
#: video2x.py:161
#: video2x.py:171
msgid "This file cannot be imported"
msgstr ""
#: video2x.py:236
#: video2x.py:187
msgid "Specify either scaling ratio or scaling resolution, not both"
msgstr ""
#: video2x.py:273
msgid "Program completed, taking {} seconds"
msgstr ""
#: video2x.py:239
#: video2x.py:277
msgid "An exception has occurred"
msgstr ""
#: video2x.py:292
msgid "The error log file can be found at: {}"
msgstr ""

View File

@@ -13,7 +13,7 @@ __ __ _ _ ___ __ __
Name: Video2X Controller
Creator: K4YT3X
Date Created: Feb 24, 2018
Last Modified: June 29, 2020
Last Modified: September 13, 2020
Editor: BrianPetkovsek
Last Modified: June 17, 2019
@@ -56,7 +56,6 @@ from upscaler import Upscaler
# built-in imports
import argparse
import datetime
import gettext
import importlib
import locale
@@ -81,7 +80,7 @@ language = gettext.translation(DOMAIN, LOCALE_DIRECTORY, [default_locale], fallb
language.install()
_ = language.gettext
CLI_VERSION = '4.2.0'
CLI_VERSION = '4.3.1'
LEGAL_INFO = _('''Video2X CLI Version: {}
Upscaler Version: {}
@@ -107,7 +106,7 @@ def parse_arguments():
# video options
video2x_options = parser.add_argument_group(_('Video2X Options'))
video2x_options.add_argument('-h', '--help', action='help', help=_('show this help message and exit'))
video2x_options.add_argument('--help', action='help', help=_('show this help message and exit'))
# if help is in arguments list
# do not require input and output path to be specified
@@ -117,17 +116,17 @@ def parse_arguments():
video2x_options.add_argument('-i', '--input', type=pathlib.Path, help=_('source video file/directory'), required=require_input_output)
video2x_options.add_argument('-o', '--output', type=pathlib.Path, help=_('output video file/directory'), required=require_input_output)
video2x_options.add_argument('-c', '--config', type=pathlib.Path, help=_('video2x config file path'), action='store',
video2x_options.add_argument('-c', '--config', type=pathlib.Path, help=_('Video2X config file path'), action='store',
default=pathlib.Path(__file__).parent.absolute() / 'video2x.yaml')
video2x_options.add_argument('--log', type=pathlib.Path, help=_('log file path'),
default=pathlib.Path(__file__).parent.absolute() / f'video2x_{datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")}.log')
video2x_options.add_argument('--disable_logging', help=_('disable logging'), action='store_true')
video2x_options.add_argument('--log', type=pathlib.Path, help=_('log file path'))
video2x_options.add_argument('-v', '--version', help=_('display version, lawful information and exit'), action='store_true')
# scaling options
upscaling_options = parser.add_argument_group(_('Upscaling Options'))
upscaling_options.add_argument('-d', '--driver', help=_('upscaling driver'), choices=AVAILABLE_DRIVERS, default='waifu2x_caffe')
upscaling_options.add_argument('-r', '--ratio', help=_('scaling ratio'), action='store', type=float, default=2.0)
upscaling_options.add_argument('-r', '--ratio', help=_('scaling ratio'), action='store', type=float)
upscaling_options.add_argument('-w', '--width', help=_('output width'), action='store', type=float)
upscaling_options.add_argument('-h', '--height', help=_('output height'), action='store', type=float)
upscaling_options.add_argument('-d', '--driver', help=_('upscaling driver'), choices=AVAILABLE_DRIVERS, default='waifu2x_ncnn_vulkan')
upscaling_options.add_argument('-p', '--processes', help=_('number of processes to use for upscaling'), action='store', type=int, default=1)
upscaling_options.add_argument('--preserve_frames', help=_('preserve extracted and upscaled frames'), action='store_true')
@@ -183,12 +182,21 @@ if video2x_args.version:
print(LEGAL_INFO)
sys.exit(0)
# additional checks on upscaling arguments
if video2x_args.ratio is not None and (video2x_args.width is not None or video2x_args.height is not None):
Avalon.error(_('Specify either scaling ratio or scaling resolution, not both'))
sys.exit(1)
# redirect output to both terminal and log file
if video2x_args.disable_logging is False:
LOGFILE = video2x_args.log
Avalon.debug_info(_('Redirecting console logs to {}').format(LOGFILE))
sys.stdout = BiLogger(sys.stdout, LOGFILE)
sys.stderr = BiLogger(sys.stderr, LOGFILE)
if video2x_args.log is not None:
log_file = video2x_args.log.open(mode='a+', encoding='utf-8')
else:
log_file = tempfile.TemporaryFile(mode='a+', suffix='.log', prefix='video2x_', encoding='utf-8')
original_stdout = sys.stdout
original_stderr = sys.stderr
sys.stdout = BiLogger(sys.stdout, log_file)
sys.stderr = BiLogger(sys.stderr, log_file)
# read configurations from configuration file
config = read_config(video2x_args.config)
@@ -248,6 +256,8 @@ try:
# optional parameters
driver=video2x_args.driver,
scale_ratio=video2x_args.ratio,
scale_width=video2x_args.width,
scale_height=video2x_args.height,
processes=video2x_args.processes,
video2x_cache_directory=video2x_cache_directory,
extracted_frame_format=extracted_frame_format,
@@ -263,5 +273,25 @@ try:
Avalon.info(_('Program completed, taking {} seconds').format(round((time.time() - begin_time), 5)))
except Exception:
Avalon.error(_('An exception has occurred'))
traceback.print_exc()
if video2x_args.log is not None:
log_file_path = video2x_args.log.absolute()
# if log file path is not specified, create temporary file as permanent log file
# tempfile.TempFile does not have a name attribute and is not guaranteed to have
# a visible name on the file system
else:
log_file_path = tempfile.mkstemp(suffix='.log', prefix='video2x_')[1]
with open(log_file_path, 'w', encoding='utf-8') as permanent_log_file:
log_file.seek(0)
permanent_log_file.write(log_file.read())
Avalon.error(_('The error log file can be found at: {}').format(log_file_path))
finally:
sys.stdout = original_stdout
sys.stderr = original_stderr
log_file.close()

View File

@@ -1,7 +1,7 @@
# Name: Video2X Configuration File
# Creator: K4YT3X
# Date Created: October 23, 2018
# Last Modified: August 18, 2020
# Last Modified: September 13, 2020
# Values here are the default values. Change the value here to
# save the default value permanently.
# Items commented out are parameters irrelevant to this context
@@ -18,8 +18,8 @@ waifu2x_caffe:
output_quality: -1 # output image quality
process: gpu # <cpu|gpu|cudnn> process mode
model_dir: null # path to custom model directory (don't append last / )
scale_height: 0 # custom scale height (specifying this will overwrite scale_ratio)
scale_width: 0 # custom scale width (specifying this will overwrite scale_ratio)
#scale_height: 0 # custom scale height (specifying this will overwrite scale_ratio)
#scale_width: 0 # custom scale width (specifying this will overwrite scale_ratio)
#scale_ratio: null # custom scale ratio
noise_level: 3 # <0|1|2|3> noise reduction level
mode: noise_scale # <noise|scale|noise_scale|auto_scale> image processing mode
@@ -168,8 +168,8 @@ gifski:
# 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)
#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

View File

@@ -4,7 +4,7 @@
Creator: Video2X GUI
Author: K4YT3X
Date Created: May 5, 2020
Last Modified: September 1, 2020
Last Modified: September 13, 2020
"""
# local imports
@@ -15,7 +15,6 @@ from wrappers.ffmpeg import Ffmpeg
# built-in imports
import contextlib
import datetime
import json
import mimetypes
import os
@@ -34,7 +33,7 @@ from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import magic
GUI_VERSION = '2.7.2'
GUI_VERSION = '2.8.0'
LEGAL_INFO = f'''Video2X GUI Version: {GUI_VERSION}\\
Upscaler Version: {UPSCALER_VERSION}\\
@@ -52,6 +51,9 @@ AVAILABLE_DRIVERS = {
'Anime4KCPP': 'anime4kcpp'
}
# get current working directory before it is changed by drivers
CWD = pathlib.Path.cwd()
def resource_path(relative_path: str) -> pathlib.Path:
try:
@@ -192,8 +194,10 @@ class Video2XMainWindow(QMainWindow):
super().__init__(*args, **kwargs)
uic.loadUi(str(resource_path('video2x_gui.ui')), self)
# generate log file name
self.logfile = pathlib.Path(__file__).parent.absolute() / f'video2x_{datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")}.log'
# redirect output to both terminal and log file
self.log_file = tempfile.TemporaryFile(mode='a+', suffix='.log', prefix='video2x_', encoding='utf-8')
sys.stdout = BiLogger(sys.stdout, self.log_file)
sys.stderr = BiLogger(sys.stderr, self.log_file)
# create thread pool for upscaler workers
self.threadpool = QThreadPool()
@@ -248,7 +252,7 @@ class Video2XMainWindow(QMainWindow):
# select output file/folder
self.output_line_edit = self.findChild(QLineEdit, 'outputLineEdit')
self.enable_line_edit_file_drop(self.output_line_edit)
self.output_line_edit.setText(str((pathlib.Path().cwd() / 'output').absolute()))
self.output_line_edit.setText(str((CWD / 'output').absolute()))
self.output_select_file_button = self.findChild(QPushButton, 'outputSelectFileButton')
self.output_select_file_button.clicked.connect(self.select_output_file)
self.output_select_folder_button = self.findChild(QPushButton, 'outputSelectFolderButton')
@@ -277,11 +281,14 @@ class Video2XMainWindow(QMainWindow):
self.driver_combo_box.currentTextChanged.connect(self.update_gui_for_driver)
self.processes_spin_box = self.findChild(QSpinBox, 'processesSpinBox')
self.scale_ratio_double_spin_box = self.findChild(QDoubleSpinBox, 'scaleRatioDoubleSpinBox')
self.output_width_spin_box = self.findChild(QSpinBox, 'outputWidthSpinBox')
self.output_width_spin_box.valueChanged.connect(self.mutually_exclude_scale_ratio_resolution)
self.output_height_spin_box = self.findChild(QSpinBox, 'outputHeightSpinBox')
self.output_height_spin_box.valueChanged.connect(self.mutually_exclude_scale_ratio_resolution)
self.output_file_name_format_string_line_edit = self.findChild(QLineEdit, 'outputFileNameFormatStringLineEdit')
self.image_output_extension_line_edit = self.findChild(QLineEdit, 'imageOutputExtensionLineEdit')
self.video_output_extension_line_edit = self.findChild(QLineEdit, 'videoOutputExtensionLineEdit')
self.preserve_frames_check_box = self.findChild(QCheckBox, 'preserveFramesCheckBox')
self.disable_logging_check_box = self.findChild(QCheckBox, 'disableLoggingCheckBox')
# frame preview
self.frame_preview_show_preview_check_box = self.findChild(QCheckBox, 'framePreviewShowPreviewCheckBox')
@@ -310,8 +317,6 @@ class Video2XMainWindow(QMainWindow):
self.enable_line_edit_file_drop(self.waifu2x_caffe_path_line_edit)
self.waifu2x_caffe_path_select_button = self.findChild(QPushButton, 'waifu2xCaffePathSelectButton')
self.waifu2x_caffe_path_select_button.clicked.connect(lambda: self.select_driver_binary_path(self.waifu2x_caffe_path_line_edit))
self.waifu2x_caffe_scale_width_spin_box = self.findChild(QSpinBox, 'waifu2xCaffeScaleWidthSpinBox')
self.waifu2x_caffe_scale_height_spin_box = self.findChild(QSpinBox, 'waifu2xCaffeScaleHeightSpinBox')
self.waifu2x_caffe_mode_combo_box = self.findChild(QComboBox, 'waifu2xCaffeModeComboBox')
self.waifu2x_caffe_noise_level_spin_box = self.findChild(QSpinBox, 'waifu2xCaffeNoiseLevelSpinBox')
self.waifu2x_caffe_process_combo_box = self.findChild(QComboBox, 'waifu2xCaffeProcessComboBox')
@@ -442,8 +447,6 @@ class Video2XMainWindow(QMainWindow):
self.gifski_path_line_edit = self.findChild(QLineEdit, 'gifskiPathLineEdit')
self.enable_line_edit_file_drop(self.gifski_path_line_edit)
self.gifski_quality_spin_box = self.findChild(QSpinBox, 'gifskiQualitySpinBox')
self.gifski_width_spin_box = self.findChild(QSpinBox, 'gifskiWidthSpinBox')
self.gifski_height_spin_box = self.findChild(QSpinBox, 'gifskiHeightSpinBox')
self.gifski_fast_check_box = self.findChild(QCheckBox, 'gifskiFastCheckBox')
self.gifski_once_check_box = self.findChild(QCheckBox, 'gifskiOnceCheckBox')
self.gifski_quiet_check_box = self.findChild(QCheckBox, 'gifskiQuietCheckBox')
@@ -491,8 +494,6 @@ class Video2XMainWindow(QMainWindow):
# waifu2x-caffe
settings = self.config['waifu2x_caffe']
self.waifu2x_caffe_scale_width_spin_box.setValue(settings['scale_width'])
self.waifu2x_caffe_scale_height_spin_box.setValue(settings['scale_height'])
self.waifu2x_caffe_path_line_edit.setText(str(pathlib.Path(os.path.expandvars(settings['path'])).absolute()))
self.waifu2x_caffe_mode_combo_box.setCurrentText(settings['mode'])
self.waifu2x_caffe_noise_level_spin_box.setValue(settings['noise_level'])
@@ -595,9 +596,6 @@ class Video2XMainWindow(QMainWindow):
settings = self.config['gifski']
self.gifski_path_line_edit.setText(str(pathlib.Path(os.path.expandvars(settings['gifski_path'])).absolute()))
self.gifski_quality_spin_box.setValue(settings['quality'])
if isinstance(settings['width'], int) and isinstance(settings['height'], int):
self.gifski_width_spin_box.setValue(settings['width'])
self.gifski_height_spin_box.setValue(settings['height'])
self.gifski_fast_check_box.setChecked(settings['fast'])
self.gifski_once_check_box.setChecked(settings['once'])
self.gifski_quiet_check_box.setChecked(settings['quiet'])
@@ -605,15 +603,13 @@ class Video2XMainWindow(QMainWindow):
def resolve_driver_settings(self):
# waifu2x-caffe
self.config['waifu2x_caffe']['scale_width'] = self.waifu2x_caffe_scale_width_spin_box.value()
self.config['waifu2x_caffe']['scale_height'] = self.waifu2x_caffe_scale_height_spin_box.value()
self.config['waifu2x_caffe']['path'] = os.path.expandvars(self.waifu2x_caffe_path_line_edit.text())
self.config['waifu2x_caffe']['mode'] = self.waifu2x_caffe_mode_combo_box.currentText()
self.config['waifu2x_caffe']['noise_level'] = self.waifu2x_caffe_noise_level_spin_box.value()
self.config['waifu2x_caffe']['process'] = self.waifu2x_caffe_process_combo_box.currentText()
self.config['waifu2x_caffe']['model_dir'] = str((pathlib.Path(self.config['waifu2x_caffe']['path']).parent / 'models' / self.waifu2x_caffe_model_combobox.currentText()).absolute())
self.config['waifu2x_caffe']['crop_size'] = self.waifu2x_caffe_crop_size_spin_box.value()
self.config['waifu2x_caffe']['output_quality'] = self.waifu2x_caffe_output_depth_spin_box.value()
self.config['waifu2x_caffe']['output_quality'] = self.waifu2x_caffe_output_quality_spin_box.value()
self.config['waifu2x_caffe']['output_depth'] = self.waifu2x_caffe_output_depth_spin_box.value()
self.config['waifu2x_caffe']['batch_size'] = self.waifu2x_caffe_batch_size_spin_box.value()
self.config['waifu2x_caffe']['gpu'] = self.waifu2x_caffe_gpu_spin_box.value()
@@ -742,7 +738,8 @@ class Video2XMainWindow(QMainWindow):
self.config['ffmpeg']['migrate_streams']['output_options']['-pix_fmt'] = self.ffmpeg_migrate_streams_output_options_pixel_format_line_edit.text()
if (fps := self.ffmpeg_migrate_streams_output_options_frame_interpolation_spin_box.value()) > 0:
fps = self.ffmpeg_migrate_streams_output_options_frame_interpolation_spin_box.value()
if fps > 0:
if ('-vf' in self.config['ffmpeg']['migrate_streams']['output_options'] and
len(self.config['ffmpeg']['migrate_streams']['output_options']['-vf']) > 0 and
'minterpolate=' not in self.config['ffmpeg']['migrate_streams']['output_options']['-vf']):
@@ -779,12 +776,6 @@ class Video2XMainWindow(QMainWindow):
# Gifski
self.config['gifski']['gifski_path'] = os.path.expandvars(self.gifski_path_line_edit.text())
self.config['gifski']['quality'] = self.gifski_quality_spin_box.value()
if self.gifski_width_spin_box.value() > 0 and self.gifski_height_spin_box.value() > 0:
self.config['gifski']['width'] = self.gifski_width_spin_box.value()
self.config['gifski']['height'] = self.gifski_height_spin_box.value()
else:
self.config['gifski']['width'] = None
self.config['gifski']['height'] = None
self.config['gifski']['fast'] = self.gifski_fast_check_box.isChecked()
self.config['gifski']['once'] = self.gifski_once_check_box.isChecked()
self.config['gifski']['quiet'] = self.gifski_quiet_check_box.isChecked()
@@ -831,6 +822,12 @@ class Video2XMainWindow(QMainWindow):
with open(config_file, 'r') as config:
return yaml.load(config, Loader=yaml.FullLoader)
def mutually_exclude_scale_ratio_resolution(self):
if self.output_width_spin_box.value() != 0 or self.output_height_spin_box.value() != 0:
self.scale_ratio_double_spin_box.setDisabled(True)
elif self.output_width_spin_box.value() == 0 and self.output_height_spin_box.value() == 0:
self.scale_ratio_double_spin_box.setDisabled(False)
def mutually_exclude_frame_interpolation_stream_copy(self):
if self.ffmpeg_migrate_streams_output_options_frame_interpolation_spin_box.value() > 0:
self.ffmpeg_migrate_streams_output_options_copy_streams_check_box.setChecked(False)
@@ -842,24 +839,6 @@ class Video2XMainWindow(QMainWindow):
def update_gui_for_driver(self):
current_driver = AVAILABLE_DRIVERS[self.driver_combo_box.currentText()]
# update scale ratio constraints
if current_driver in ['waifu2x_caffe', 'waifu2x_converter_cpp', 'anime4kcpp']:
self.scale_ratio_double_spin_box.setMinimum(0.0)
self.scale_ratio_double_spin_box.setMaximum(999.0)
self.scale_ratio_double_spin_box.setValue(2.0)
elif current_driver == 'waifu2x_ncnn_vulkan':
self.scale_ratio_double_spin_box.setMinimum(1.0)
self.scale_ratio_double_spin_box.setMaximum(2.0)
self.scale_ratio_double_spin_box.setValue(2.0)
elif current_driver == 'srmd_ncnn_vulkan':
self.scale_ratio_double_spin_box.setMinimum(2.0)
self.scale_ratio_double_spin_box.setMaximum(4.0)
self.scale_ratio_double_spin_box.setValue(2.0)
elif current_driver == 'realsr_ncnn_vulkan':
self.scale_ratio_double_spin_box.setMinimum(4.0)
self.scale_ratio_double_spin_box.setMaximum(4.0)
self.scale_ratio_double_spin_box.setValue(4.0)
# update preferred processes/threads count
if current_driver == 'anime4kcpp':
self.processes_spin_box.setValue(16)
@@ -904,6 +883,12 @@ class Video2XMainWindow(QMainWindow):
return None
return pathlib.Path(folder_selected)
def select_save_file(self, *args, **kwargs) -> pathlib.Path:
save_file_selected = QFileDialog.getSaveFileName(self, *args, **kwargs)
if not isinstance(save_file_selected, tuple) or save_file_selected[0] == '':
return None
return pathlib.Path(save_file_selected[0])
def update_output_path(self):
# if input list is empty
# clear output path
@@ -913,7 +898,7 @@ class Video2XMainWindow(QMainWindow):
# if there are multiple output files
# use cwd/output directory for output
elif len(self.input_table_data) > 1:
self.output_line_edit.setText(str((pathlib.Path.cwd() / 'output').absolute()))
self.output_line_edit.setText(str((CWD / 'output').absolute()))
# if there's only one input file
# generate output file/directory name automatically
@@ -978,44 +963,49 @@ class Video2XMainWindow(QMainWindow):
self.output_line_edit.setText(str(output_path.absolute()))
def select_input_file(self):
if ((input_file := self.select_file('Select Input File')) is None or
self.input_table_path_exists(input_file)):
input_file = self.select_file('Select Input File')
if (input_file is None or self.input_table_path_exists(input_file)):
return
self.input_table_data.append(input_file)
self.update_output_path()
self.update_input_table()
def select_input_folder(self):
if ((input_folder := self.select_folder('Select Input Folder')) is None or
self.input_table_path_exists(input_folder)):
input_folder = self.select_folder('Select Input Folder')
if (input_folder is None or self.input_table_path_exists(input_folder)):
return
self.input_table_data.append(input_folder)
self.update_output_path()
self.update_input_table()
def select_output_file(self):
if (output_file := self.select_file('Select Output File')) is None:
output_file = self.select_file('Select Output File')
if output_file is None:
return
self.output_line_edit.setText(str(output_file.absolute()))
def select_output_folder(self):
if (output_folder := self.select_folder('Select Output Folder')) is None:
output_folder = self.select_folder('Select Output Folder')
if output_folder is None:
return
self.output_line_edit.setText(str(output_folder.absolute()))
def select_cache_folder(self):
if (cache_folder := self.select_folder('Select Cache Folder')) is None:
cache_folder = self.select_folder('Select Cache Folder')
if cache_folder is None:
return
self.cache_line_edit.setText(str(cache_folder.absolute()))
def select_config_file(self):
if (config_file := self.select_file('Select Config File', filter='(YAML files (*.yaml))')) is None:
config_file = self.select_file('Select Config File', filter='(YAML files (*.yaml))')
if config_file is None:
return
self.config_line_edit.setText(str(config_file.absolute()))
self.load_configurations()
def select_driver_binary_path(self, driver_line_edit: QLineEdit):
if (driver_binary_path := self.select_file('Select Driver Binary File')) is None:
driver_binary_path = self.select_file('Select Driver Binary File')
if driver_binary_path is None:
return
driver_line_edit.setText(str(driver_binary_path.absolute()))
@@ -1055,6 +1045,16 @@ class Video2XMainWindow(QMainWindow):
message_box.exec_()
def show_error(self, exception: Exception):
def _process_button_press(button_pressed):
# if the user pressed the save button, save log file to destination
if button_pressed.text() == 'Save':
log_file_saving_path = self.select_save_file('Select Log File Saving Destination', 'video2x_error.log')
if log_file_saving_path is not None:
with open(log_file_saving_path, 'w', encoding='utf-8') as log_file:
self.log_file.seek(0)
log_file.write(self.log_file.read())
# QErrorMessage(self).showMessage(message.replace('\n', '<br>'))
message_box = QMessageBox(self)
message_box.setWindowTitle('Error')
@@ -1063,48 +1063,63 @@ class Video2XMainWindow(QMainWindow):
error_message = '''Upscaler ran into an error:\\
{}\\
Check the console output for details.\\
When reporting an error, please include console output.\\
Check the console output or the log file for details.\\
You can [submit an issue on GitHub](https://github.com/k4yt3x/video2x/issues/new?assignees=K4YT3X&labels=bug&template=bug-report.md&title={}) to report this error.\\
It\'s also highly recommended for you to attach the [log file]({}) under the programs\'s parent folder named {}.'''
message_box.setText(error_message.format(exception, urllib.parse.quote(str(exception)), self.logfile.as_uri(), self.logfile.name))
It\'s highly recommended to attach the log file.\\
You can click \"Save\" to save the log file.'''
message_box.setText(error_message.format(exception, urllib.parse.quote(str(exception))))
message_box.setStandardButtons(QMessageBox.Save | QMessageBox.Close)
message_box.setDefaultButton(QMessageBox.Save)
message_box.buttonClicked.connect(_process_button_press)
message_box.exec_()
def progress_monitor(self, progress_callback: pyqtSignal):
# initialize progress bar values
upscale_begin_time = time.time()
progress_callback.emit((upscale_begin_time, 0, 0, 0, 0, pathlib.Path(), pathlib.Path()))
progress_callback.emit((time.time(), 0, 0, 0, 0, 0, [], pathlib.Path(), pathlib.Path()))
# keep querying upscaling process and feed information to callback signal
while self.upscaler.running:
progress_callback.emit((upscale_begin_time,
progress_callback.emit((self.upscaler.current_processing_starting_time,
self.upscaler.total_frames_upscaled,
self.upscaler.total_frames,
self.upscaler.total_processed,
self.upscaler.total_files,
self.upscaler.current_pass,
self.upscaler.scaling_jobs,
self.upscaler.current_input_file,
self.upscaler.last_frame_upscaled))
time.sleep(1)
# upscale process will stop at 99%
# so it's set to 100 manually when all is done
# progress_callback.emit((upscale_begin_time, 0, 0, 0, 0, pathlib.Path(), pathlib.Path()))
progress_callback.emit((time.time(),
self.upscaler.total_frames,
self.upscaler.total_frames,
self.upscaler.total_files,
self.upscaler.total_files,
len(self.upscaler.scaling_jobs),
self.upscaler.scaling_jobs,
pathlib.Path(),
pathlib.Path()))
def set_progress(self, progress_information: tuple):
upscale_begin_time = progress_information[0]
current_processing_starting_time = progress_information[0]
total_frames_upscaled = progress_information[1]
total_frames = progress_information[2]
total_processed = progress_information[3]
total_files = progress_information[4]
current_input_file = progress_information[5]
last_frame_upscaled = progress_information[6]
current_pass = progress_information[5]
scaling_jobs = progress_information[6]
current_input_file = progress_information[7]
last_frame_upscaled = progress_information[8]
# calculate fields based on frames and time elapsed
time_elapsed = time.time() - upscale_begin_time
time_elapsed = time.time() - current_processing_starting_time
try:
rate = total_frames_upscaled / (time.time() - upscale_begin_time)
rate = total_frames_upscaled / time_elapsed
time_remaining = (total_frames - total_frames_upscaled) / rate
except Exception:
rate = 0.0
@@ -1120,7 +1135,7 @@ It\'s also highly recommended for you to attach the [log file]({}) under the pro
self.overall_progress_label.setText('Overall Progress: {}/{}'.format(total_processed, total_files))
self.overall_progress_bar.setMaximum(total_files)
self.overall_progress_bar.setValue(total_processed)
self.currently_processing_label.setText('Currently Processing: {}'.format(str(current_input_file.name)))
self.currently_processing_label.setText('Currently Processing: {} (pass {}/{})'.format(str(current_input_file.name), current_pass, len(scaling_jobs)))
# if show frame is checked, show preview image
if self.frame_preview_show_preview_check_box.isChecked() and last_frame_upscaled.is_file():
@@ -1171,11 +1186,6 @@ It\'s also highly recommended for you to attach the [log file]({}) under the pro
self.show_warning('Output path unspecified')
return
if self.disable_logging_check_box.isChecked() is False:
print(f'Redirecting console logs to {self.logfile}', file=sys.stderr)
sys.stdout = BiLogger(sys.stdout, self.logfile)
sys.stderr = BiLogger(sys.stderr, self.logfile)
if len(self.input_table_data) == 1:
input_directory = self.input_table_data[0]
else:
@@ -1190,6 +1200,16 @@ It\'s also highly recommended for you to attach the [log file]({}) under the pro
# load driver settings for the current driver
self.driver_settings = self.config[AVAILABLE_DRIVERS[self.driver_combo_box.currentText()]]
# get scale ratio or resolution
if self.scale_ratio_double_spin_box.isEnabled():
scale_ratio = self.scale_ratio_double_spin_box.value()
scale_width = scale_height = None
else:
scale_ratio = None
scale_width = self.output_width_spin_box.value()
scale_height = self.output_height_spin_box.value()
self.upscaler = Upscaler(
# required parameters
input_path=input_directory,
@@ -1200,7 +1220,9 @@ It\'s also highly recommended for you to attach the [log file]({}) under the pro
# optional parameters
driver=AVAILABLE_DRIVERS[self.driver_combo_box.currentText()],
scale_ratio=self.scale_ratio_double_spin_box.value(),
scale_ratio=scale_ratio,
scale_width=scale_width,
scale_height=scale_height,
processes=self.processes_spin_box.value(),
video2x_cache_directory=pathlib.Path(os.path.expandvars(self.cache_line_edit.text())),
extracted_frame_format=self.config['video2x']['extracted_frame_format'].lower(),

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>756</width>
<height>954</height>
<width>737</width>
<height>1020</height>
</rect>
</property>
<property name="acceptDrops">
@@ -40,8 +40,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>744</width>
<height>696</height>
<width>725</width>
<height>762</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_36">
@@ -376,6 +376,11 @@
&lt;/li&gt;
&lt;/ul&gt;</string>
</property>
<item>
<property name="text">
<string>Waifu2X NCNN Vulkan</string>
</property>
</item>
<item>
<property name="text">
<string>Waifu2X Caffe</string>
@@ -386,11 +391,6 @@
<string>Waifu2X Converter CPP</string>
</property>
</item>
<item>
<property name="text">
<string>Waifu2X NCNN Vulkan</string>
</property>
</item>
<item>
<property name="text">
<string>SRMD NCNN Vulkan</string>
@@ -461,6 +461,42 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="outputWidthHorizontalLayout">
<item>
<widget class="QLabel" name="outputWidthLabel">
<property name="text">
<string>Output Width</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="outputWidthSpinBox">
<property name="maximum">
<number>99999</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="outputHeightHorizontalLayout">
<item>
<widget class="QLabel" name="outputHeightLabel">
<property name="text">
<string>Output Height</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="outputHeightSpinBox">
<property name="maximum">
<number>99999</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="imageOutputExtensionHorizontalLayout">
<item>
@@ -522,13 +558,6 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="disableLoggingCheckBox">
<property name="text">
<string>Disable Logging</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@@ -542,6 +571,22 @@
<layout class="QHBoxLayout" name="framPreviewHorizontalLayout">
<item>
<layout class="QVBoxLayout" name="framePreviewOptionsVerticalLayout">
<item>
<widget class="QLabel" name="framePreviewLabel">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="framePreviewShowPreviewCheckBox">
<property name="text">
@@ -562,34 +607,8 @@
</property>
</widget>
</item>
<item>
<spacer name="framePreviewOptionsVerticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="framePreviewLabel">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
@@ -628,48 +647,6 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="waifu2xCaffeScaleWidthHorizontalLayout">
<item>
<widget class="QLabel" name="waifu2xCaffeScaleWidthLabel">
<property name="text">
<string>Scale Width</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="waifu2xCaffeScaleWidthSpinBox">
<property name="toolTip">
<string>custom scale width (specifying this will overwrite scale_ratio)</string>
</property>
<property name="maximum">
<number>999999</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="waifu2xCaffeScaleHeightHorizontalLayout">
<item>
<widget class="QLabel" name="waifu2xCaffeScaleHeightLabel">
<property name="text">
<string>Scale Height</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="waifu2xCaffeScaleHeightSpinBox">
<property name="toolTip">
<string>custom scale height (specifying this will overwrite scale_ratio)</string>
</property>
<property name="maximum">
<number>999999</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="waifu2xCaffeModeHorizontalLayout">
<item>
@@ -2644,42 +2621,6 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="gifskiWidthHorizontalLayout">
<item>
<widget class="QLabel" name="gifskiWidthLabel">
<property name="text">
<string>Width</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="gifskiWidthSpinBox">
<property name="maximum">
<number>99999</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="gifskiHeightHorizontalLayout">
<item>
<widget class="QLabel" name="gifskiHeightLabel">
<property name="text">
<string>Height</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="gifskiHeightSpinBox">
<property name="maximum">
<number>99999</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="gifskiSwitchesGroupBox">
<property name="title">
@@ -2760,9 +2701,9 @@
</widget>
</item>
<item>
<widget class="QGroupBox" name="currentVideoProcessingProgressGroupBox">
<widget class="QGroupBox" name="currentProcessingProgressGroupBox">
<property name="title">
<string>Current Video Processing Progress</string>
<string>Current Processing Progress</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_11">
<item>
@@ -2854,9 +2795,9 @@
</widget>
</item>
<item>
<widget class="QGroupBox" name="overallVideoProcessingProgressGroupBox">
<widget class="QGroupBox" name="overallProcessingProgressGroupBox">
<property name="title">
<string>Overall Video Processing Progress</string>
<string>Overall Processing Progress</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_13">
<item>
@@ -2946,7 +2887,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>756</width>
<width>737</width>
<height>21</height>
</rect>
</property>

View File

@@ -2,7 +2,7 @@
# Name: Video2X Setup Script (Ubuntu)
# Creator: K4YT3X
# Date Created: June 5, 2020
# Last Modified: July 25, 2020
# Last Modified: September 4, 2020
# help message if input is incorrect of if -h/--help is specified
if [ "$1" == "-h" ] || [ "$1" == "--help" ] || [ "$#" -gt 2 ]; then
@@ -34,7 +34,7 @@ apt-get install -y --no-install-recommends apt-utils software-properties-common
# add PPAs and sources
add-apt-repository -y ppa:apt-fast/stable
add-apt-repository -y ppa:graphics-drivers/ppa
apt-get install -y --no-install-recommends apt-fast
apt-get install -y --no-install-recommends apt-fast aria2
apt-fast update
# install runtime packages
@@ -55,11 +55,14 @@ python3.8 -m pip install -U -r $INSTALLATION_PATH/video2x/src/requirements.txt
mkdir -v -p $INSTALLATION_PATH/video2x/src/dependencies
# install gifski
apt-fast install -y --no-install-recommends cargo
# cargo from APT might be outdate and will result in gifski components not being built successfully
# apt-fast install -y --no-install-recommends cargo
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | bash -s -- -y
source $HOME/.cargo/env
cargo install gifski
# install waifu2x-caffe
apt-fast install -y --no-install-recommends autoconf build-essential cmake gcc-8 libatlas-base-dev libboost-atomic-dev libboost-chrono-dev libboost-date-time-dev libboost-filesystem-dev libboost-iostreams-dev libboost-python-dev libboost-system-dev libboost-thread-dev libcudnn7 libcudnn7-dev libgflags-dev libgoogle-glog-dev libhdf5-dev libleveldb-dev liblmdb-dev libopencv-dev libprotobuf-dev libsnappy-dev protobuf-compiler python-numpy texinfo yasm zlib1g-dev
apt-fast install -y --no-install-recommends autoconf build-essential cmake gcc-8 libatlas-base-dev libboost-atomic-dev libboost-chrono-dev libboost-date-time-dev libboost-filesystem-dev libboost-iostreams-dev libboost-python-dev libboost-system-dev libboost-thread-dev libcudnn7 libcudnn7-dev libgflags-dev libgoogle-glog-dev libhdf5-dev libleveldb-dev liblmdb-dev libopencv-dev libprotobuf-dev libsnappy-dev protobuf-compiler python-dev python-numpy texinfo yasm zlib1g-dev
git clone --recurse-submodules --depth=1 --progress --recurse-submodules https://github.com/nagadomi/waifu2x-caffe-ubuntu.git $TEMP/waifu2x-caffe-ubuntu
git clone --recurse-submodules --progress --depth=1 https://github.com/nagadomi/caffe.git $TEMP/waifu2x-caffe-ubuntu/caffe
@@ -113,7 +116,7 @@ fi
waifu2x_ncnn_vulkan_zip="$TEMP/waifu2x-ncnn-vulkan-linux.zip"
echo "Downloading $download_link to $waifu2x_ncnn_vulkan_zip"
wget "$download_link" -O "$waifu2x_ncnn_vulkan_zip"
aria2c "$download_link" --dir / -o "$waifu2x_ncnn_vulkan_zip"
unzip "$waifu2x_ncnn_vulkan_zip" -d $TEMP/waifu2x-ncnn-vulkan
mv -v $TEMP/waifu2x-ncnn-vulkan/waifu2x-ncnn-vulkan-*-linux $INSTALLATION_PATH/video2x/src/dependencies/waifu2x-ncnn-vulkan
@@ -145,7 +148,7 @@ fi
srmd_ncnn_vulkan_zip="$TEMP/srmd-ncnn-vulkan-linux.zip"
echo "Downloading $download_link to $srmd_ncnn_vulkan_zip"
wget "$download_link" -O "$srmd_ncnn_vulkan_zip"
aria2c "$download_link" --dir / -o "$srmd_ncnn_vulkan_zip"
unzip "$srmd_ncnn_vulkan_zip" -d $TEMP/srmd-ncnn-vulkan
mv -v $TEMP/srmd-ncnn-vulkan/srmd-ncnn-vulkan-*-linux $INSTALLATION_PATH/video2x/src/dependencies/srmd-ncnn-vulkan
@@ -177,19 +180,25 @@ fi
realsr_ncnn_vulkan_zip="$TEMP/realsr-ncnn-vulkan-linux.zip"
echo "Downloading $download_link to $realsr_ncnn_vulkan_zip"
wget "$download_link" -O "$realsr_ncnn_vulkan_zip"
aria2c "$download_link" --dir / -o "$realsr_ncnn_vulkan_zip"
unzip "$realsr_ncnn_vulkan_zip" -d $TEMP/realsr-ncnn-vulkan
mv -v $TEMP/realsr-ncnn-vulkan/realsr-ncnn-vulkan-*-linux $INSTALLATION_PATH/video2x/src/dependencies/realsr-ncnn-vulkan
# install anime4kcpp
#apt-fast install -y --no-install-recommends build-essential cmake libopencv-dev beignet-opencl-icd mesa-opencl-icd ocl-icd-opencl-dev opencl-headers
#git clone --recurse-submodules --depth=1 --progress https://github.com/TianZerL/Anime4KCPP.git $TEMP/anime4kcpp
#mkdir -v $TEMP/anime4kcpp/build
#cd $TEMP/anime4kcpp/CLI/build
#cmake -DBuild_GUI=OFF ..
#make -j$(nproc)
#mv -v $TEMP/anime4kcpp/build $INSTALLATION_PATH/video2x/src/dependencies/anime4kcpp
#mv -v $TEMP/anime4kcpp/models_rgb $INSTALLATION_PATH/video2x/src/dependencies/anime4kcpp/models_rgb
# install Anime4KCPP
# install the latest cmake for compiling Anime4KCPP
aria2c https://github.com/Kitware/CMake/releases/download/v3.18.2/cmake-3.18.2-Linux-x86_64.sh --dir / -o "$TEMP/cmake.sh"
mkdir /cmake
bash "$TEMP/cmake.sh" --prefix=/cmake --skip-license
# build and install Anime4KCPP
apt-fast install -y --no-install-recommends libopencv-dev opencl-dev
git clone --recurse-submodules --depth=1 --progress https://github.com/TianZerL/Anime4KCPP.git $TEMP/anime4kcpp
mkdir -v $TEMP/anime4kcpp/build
cd $TEMP/anime4kcpp/build
/cmake/bin/cmake -DBuild_GUI=OFF ..
make -j$(nproc)
mv -v $TEMP/anime4kcpp/build $INSTALLATION_PATH/video2x/src/dependencies/anime4kcpp
ln -s $INSTALLATION_PATH/video2x/src/dependencies/anime4kcpp/bin/libAnime4KCPPCore.so /usr/lib
# rewrite config file values
python3.8 - <<EOF
@@ -210,7 +219,7 @@ template_dict['waifu2x_converter_cpp']['path'] = '{}/video2x/src/dependencies/wa
template_dict['waifu2x_ncnn_vulkan']['path'] = '{}/video2x/src/dependencies/waifu2x-ncnn-vulkan/waifu2x-ncnn-vulkan'.format(INSTALLATION_PATH)
template_dict['srmd_ncnn_vulkan']['path'] = '{}/video2x/src/dependencies/srmd-ncnn-vulkan/srmd-ncnn-vulkan'.format(INSTALLATION_PATH)
template_dict['realsr_ncnn_vulkan']['path'] = '{}/video2x/src/dependencies/realsr-ncnn-vulkan/realsr-ncnn-vulkan'.format(INSTALLATION_PATH)
template_dict['anime4kcpp']['path'] = '{}/video2x/src/dependencies/anime4kcpp/anime4kcpp'.format(INSTALLATION_PATH)
template_dict['anime4kcpp']['path'] = '{}/video2x/src/dependencies/anime4kcpp/bin/Anime4KCPP_CLI'.format(INSTALLATION_PATH)
# write configuration into file
with open('{}/video2x/src/video2x.yaml'.format(INSTALLATION_PATH), 'w') as config:
@@ -234,4 +243,4 @@ EOF
# apt-get autoremove --purge -y
# remove temp directory
rm -vrf $TEMP
rm -rf $TEMP

View File

@@ -4,7 +4,7 @@
Name: Anime4KCPP Driver
Author: K4YT3X
Date Created: May 3, 2020
Last Modified: September 1, 2020
Last Modified: September 9, 2020
Description: This class is a high-level wrapper
for Anime4KCPP.
@@ -15,7 +15,6 @@ import argparse
import os
import pathlib
import platform
import shlex
import subprocess
import threading
@@ -73,13 +72,16 @@ class WrapperMain:
return parser.parse_args(arguments)
def load_configurations(self, upscaler):
self.driver_settings['zoomFactor'] = upscaler.scale_ratio
# self.driver_settings['zoomFactor'] = upscaler.scale_ratio
self.driver_settings['threads'] = upscaler.processes
# append FFmpeg path to the end of PATH
# Anime4KCPP will then use FFmpeg to migrate audio tracks
os.environ['PATH'] += f';{upscaler.ffmpeg_settings["ffmpeg_path"]}'
def set_scale_ratio(self, scale_ratio: float):
self.driver_settings['zoomFactor'] = scale_ratio
def upscale(self, input_file, output_file):
"""This is the core function for WAIFU2X class
@@ -126,6 +128,6 @@ class WrapperMain:
# return the Popen object of the new process created
self.print_lock.acquire()
Avalon.debug_info(f'[upscaler] Subprocess {os.getpid()} executing: {shlex.join(execute)}')
Avalon.debug_info(f'[upscaler] Subprocess {os.getpid()} executing: {" ".join(execute)}')
self.print_lock.release()
return subprocess.Popen(execute)

View File

@@ -4,7 +4,7 @@
Name: Video2X FFmpeg Controller
Author: K4YT3X
Date Created: Feb 24, 2018
Last Modified: June 7, 2020
Last Modified: September 13, 2020
Description: This class handles all FFmpeg related operations.
"""
@@ -12,7 +12,6 @@ Description: This class handles all FFmpeg related operations.
# built-in imports
import json
import pathlib
import shlex
import subprocess
# third-party imports
@@ -58,7 +57,7 @@ class Ffmpeg:
# turn elements into str
execute = [str(e) for e in execute]
Avalon.debug_info(f'Executing: {shlex.join(execute)}')
Avalon.debug_info(f'Executing: {" ".join(execute)}')
# initialize dictionary to store pixel formats
pixel_formats = {}
@@ -66,7 +65,7 @@ class Ffmpeg:
# record all pixel formats into dictionary
for line in subprocess.run(execute, check=True, stdout=subprocess.PIPE).stdout.decode().split('\n'):
try:
pixel_formats[' '.join(line.split()).split()[1]] = int(' '.join(line.split()).split()[3])
pixel_formats[" ".join(line.split()).split()[1]] = int(" ".join(line.split()).split()[3])
except (IndexError, ValueError):
pass
@@ -75,6 +74,37 @@ class Ffmpeg:
return pixel_formats
def get_number_of_frames(self, input_file: str, video_stream_index: int) -> int:
""" Count the number of frames in a video
Args:
input_file (str): input file path
video_stream_index (int): index number of the video stream
Returns:
int: number of frames in the video
"""
execute = [
self.ffmpeg_probe_binary,
'-v',
'quiet',
'-count_frames',
'-select_streams',
f'v:{video_stream_index}',
'-show_entries',
'stream=nb_read_frames',
'-of',
'default=nokey=1:noprint_wrappers=1',
input_file
]
# turn elements into str
execute = [str(e) for e in execute]
Avalon.debug_info(f'Executing: {" ".join(execute)}')
return int(subprocess.run(execute, check=True, stdout=subprocess.PIPE).stdout.decode().strip())
def probe_file_info(self, input_video):
""" Gets input video information
@@ -105,7 +135,7 @@ class Ffmpeg:
# turn elements into str
execute = [str(e) for e in execute]
Avalon.debug_info(f'Executing: {shlex.join(execute)}')
Avalon.debug_info(f'Executing: {" ".join(execute)}')
json_str = subprocess.run(execute, check=True, stdout=subprocess.PIPE).stdout
return json.loads(json_str.decode('utf-8'))
@@ -134,6 +164,7 @@ class Ffmpeg:
# specify output file
execute.extend([
extracted_frames / f'extracted_%0d.{self.extracted_frame_format}'
# extracted_frames / f'frame_%06d.{self.extracted_frame_format}'
])
return(self._execute(execute))
@@ -175,6 +206,7 @@ class Ffmpeg:
execute.extend([
'-i',
upscaled_frames / f'extracted_%d.{self.extracted_frame_format}'
# upscaled_frames / f'%06d.{self.extracted_frame_format}'
])
# read FFmpeg output options
@@ -283,5 +315,5 @@ class Ffmpeg:
def _execute(self, execute):
# turn all list elements into string to avoid errors
execute = [str(e) for e in execute]
Avalon.debug_info(f'Executing: {shlex.join(execute)}')
Avalon.debug_info(f'Executing: {" ".join(execute)}')
return subprocess.Popen(execute)

View File

@@ -4,7 +4,7 @@
Name: Gifski Wrapper
Creator: K4YT3X
Date Created: May 11, 2020
Last Modified: June 7, 2020
Last Modified: September 13, 2020
Description: High-level wrapper for Gifski.
"""
@@ -22,13 +22,17 @@ 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, extracted_frame_format: str) -> subprocess.Popen:
def make_gif(self, upscaled_frames: pathlib.Path, output_path: pathlib.Path, framerate: float, extracted_frame_format: str, output_width: int, output_height: int) -> subprocess.Popen:
execute = [
self.gifski_settings['gifski_path'],
'-o',
output_path,
'--fps',
int(round(framerate, 0))
int(round(framerate, 0)),
'--width',
output_width,
'--height',
output_height
]
# load configurations from config file

View File

@@ -4,7 +4,7 @@
Name: RealSR NCNN Vulkan Driver
Creator: K4YT3X
Date Created: May 26, 2020
Last Modified: May 26, 2020
Last Modified: September 9, 2020
Description: This class is a high-level wrapper
for realsr_ncnn_vulkan.
@@ -15,7 +15,6 @@ import argparse
import os
import pathlib
import platform
import shlex
import subprocess
import threading
@@ -44,7 +43,7 @@ class WrapperMain:
parser.add_argument('-v', action='store_true', help='verbose output')
parser.add_argument('-i', type=str, help=argparse.SUPPRESS) # help='input image path (jpg/png) or directory')
parser.add_argument('-o', type=str, help=argparse.SUPPRESS) # help='output image path (png) or directory')
parser.add_argument('-s', type=int, choices=[4], help='upscale ratio')
parser.add_argument('-s', type=int, help='upscale ratio')
parser.add_argument('-t', type=int, help='tile size (>=32/0=auto)')
parser.add_argument('-m', type=str, help='realsr model path')
parser.add_argument('-g', type=int, help='gpu device to use')
@@ -53,9 +52,12 @@ class WrapperMain:
return parser.parse_args(arguments)
def load_configurations(self, upscaler):
self.driver_settings['s'] = int(upscaler.scale_ratio)
# self.driver_settings['s'] = int(upscaler.scale_ratio)
self.driver_settings['j'] = '{}:{}:{}'.format(upscaler.processes, upscaler.processes, upscaler.processes)
def set_scale_ratio(self, scale_ratio: int):
self.driver_settings['s'] = int(scale_ratio)
def upscale(self, input_directory, output_directory):
"""This is the core function for RealSR NCNN Vulkan class
@@ -101,6 +103,6 @@ class WrapperMain:
# return the Popen object of the new process created
self.print_lock.acquire()
Avalon.debug_info(f'[upscaler] Subprocess {os.getpid()} executing: {shlex.join(execute)}')
Avalon.debug_info(f'[upscaler] Subprocess {os.getpid()} executing: {" ".join(execute)}')
self.print_lock.release()
return subprocess.Popen(execute)

View File

@@ -4,7 +4,7 @@
Name: SRMD NCNN Vulkan Driver
Creator: K4YT3X
Date Created: April 26, 2020
Last Modified: May 11, 2020
Last Modified: September 9, 2020
Description: This class is a high-level wrapper
for srmd_ncnn_vulkan.
@@ -15,7 +15,6 @@ import argparse
import os
import pathlib
import platform
import shlex
import subprocess
import threading
@@ -45,7 +44,7 @@ class WrapperMain:
parser.add_argument('-i', type=str, help=argparse.SUPPRESS) # help='input image path (jpg/png) or directory')
parser.add_argument('-o', type=str, help=argparse.SUPPRESS) # help='output image path (png) or directory')
parser.add_argument('-n', type=int, choices=range(-1, 11), help='denoise level')
parser.add_argument('-s', type=int, choices=range(2, 5), help='upscale ratio')
parser.add_argument('-s', type=int, help='upscale ratio')
parser.add_argument('-t', type=int, help='tile size (>=32)')
parser.add_argument('-m', type=str, help='srmd model path')
parser.add_argument('-g', type=int, help='gpu device to use')
@@ -54,9 +53,12 @@ class WrapperMain:
return parser.parse_args(arguments)
def load_configurations(self, upscaler):
self.driver_settings['s'] = int(upscaler.scale_ratio)
# self.driver_settings['s'] = int(upscaler.scale_ratio)
self.driver_settings['j'] = '{}:{}:{}'.format(upscaler.processes, upscaler.processes, upscaler.processes)
def set_scale_ratio(self, scale_ratio: int):
self.driver_settings['s'] = int(scale_ratio)
def upscale(self, input_directory, output_directory):
"""This is the core function for SRMD ncnn Vulkan class
@@ -102,6 +104,6 @@ class WrapperMain:
# return the Popen object of the new process created
self.print_lock.acquire()
Avalon.debug_info(f'[upscaler] Subprocess {os.getpid()} executing: {shlex.join(execute)}')
Avalon.debug_info(f'[upscaler] Subprocess {os.getpid()} executing: {" ".join(execute)}')
self.print_lock.release()
return subprocess.Popen(execute)

View File

@@ -4,7 +4,7 @@
Name: Waifu2x Caffe Driver
Author: K4YT3X
Date Created: Feb 24, 2018
Last Modified: June 7, 2020
Last Modified: September 12, 2020
Description: This class is a high-level wrapper
for waifu2x-caffe.
@@ -14,7 +14,6 @@ for waifu2x-caffe.
import argparse
import os
import pathlib
import shlex
import subprocess
import threading
@@ -63,13 +62,23 @@ class WrapperMain:
def load_configurations(self, upscaler):
# use scale width and scale height if specified
self.driver_settings['scale_ratio'] = upscaler.scale_ratio
# self.driver_settings['scale_ratio'] = upscaler.scale_ratio
self.driver_settings['output_extention'] = upscaler.extracted_frame_format
# bit_depth will be 12 at this point
# it will up updated later
self.driver_settings['output_depth'] = 12
def set_scale_resolution(self, width: int, height: int):
self.driver_settings['scale_width'] = width
self.driver_settings['scale_height'] = height
self.driver_settings['scale_ratio'] = None
def set_scale_ratio(self, scale_ratio: float):
self.driver_settings['scale_width'] = None
self.driver_settings['scale_height'] = None
self.driver_settings['scale_ratio'] = scale_ratio
def upscale(self, input_directory, output_directory):
""" start upscaling process
"""
@@ -105,6 +114,6 @@ class WrapperMain:
# return the Popen object of the new process created
self.print_lock.acquire()
Avalon.debug_info(f'[upscaler] Subprocess {os.getpid()} executing: {shlex.join(execute)}')
Avalon.debug_info(f'[upscaler] Subprocess {os.getpid()} executing: {" ".join(execute)}')
self.print_lock.release()
return subprocess.Popen(execute)

View File

@@ -4,7 +4,7 @@
Name: Waifu2x Converter CPP Driver
Author: K4YT3X
Date Created: February 8, 2019
Last Modified: June 13, 2020
Last Modified: September 9, 2020
Description: This class is a high-level wrapper
for waifu2x-converter-cpp.
@@ -14,7 +14,6 @@ for waifu2x-converter-cpp.
import argparse
import os
import pathlib
import shlex
import subprocess
import threading
@@ -67,10 +66,13 @@ class WrapperMain:
return parser.parse_args(arguments)
def load_configurations(self, upscaler):
self.driver_settings['scale-ratio'] = upscaler.scale_ratio
# self.driver_settings['scale-ratio'] = upscaler.scale_ratio
self.driver_settings['jobs'] = upscaler.processes
self.driver_settings['output-format'] = upscaler.extracted_frame_format.lower()
def set_scale_ratio(self, scale_ratio: float):
self.driver_settings['scale-ratio'] = scale_ratio
def upscale(self, input_directory, output_directory):
""" Waifu2x Converter Driver Upscaler
This method executes the upscaling of extracted frames.
@@ -118,6 +120,6 @@ class WrapperMain:
# return the Popen object of the new process created
self.print_lock.acquire()
Avalon.debug_info(f'[upscaler] Subprocess {os.getpid()} executing: {shlex.join(execute)}')
Avalon.debug_info(f'[upscaler] Subprocess {os.getpid()} executing: {" ".join(execute)}')
self.print_lock.release()
return subprocess.Popen(execute)

View File

@@ -7,7 +7,7 @@ Date Created: June 26, 2019
Last Modified: May 11, 2020
Editor: K4YT3X
Last Modified: February 22, 2020
Last Modified: September 9, 2020
Description: This class is a high-level wrapper
for waifu2x_ncnn_vulkan.
@@ -18,7 +18,6 @@ import argparse
import os
import pathlib
import platform
import shlex
import subprocess
import threading
@@ -48,7 +47,7 @@ class WrapperMain:
parser.add_argument('-i', type=str, help=argparse.SUPPRESS) # help='input image path (jpg/png) or directory')
parser.add_argument('-o', type=str, help=argparse.SUPPRESS) # help='output image path (png) or directory')
parser.add_argument('-n', type=int, choices=range(-1, 4), help='denoise level')
parser.add_argument('-s', type=int, choices=range(1, 3), help='upscale ratio')
parser.add_argument('-s', type=int, help='upscale ratio')
parser.add_argument('-t', type=int, help='tile size (>=32)')
parser.add_argument('-m', type=str, help='waifu2x model path')
parser.add_argument('-g', type=int, help='gpu device to use')
@@ -57,11 +56,14 @@ class WrapperMain:
return parser.parse_args(arguments)
def load_configurations(self, upscaler):
self.driver_settings['s'] = int(upscaler.scale_ratio)
# self.driver_settings['s'] = int(upscaler.scale_ratio)
self.driver_settings['j'] = '{}:{}:{}'.format(upscaler.processes, upscaler.processes, upscaler.processes)
def set_scale_ratio(self, scale_ratio: int):
self.driver_settings['s'] = int(scale_ratio)
def upscale(self, input_directory, output_directory):
"""This is the core function for WAIFU2X class
""" This is the core function for waifu2x class
Arguments:
input_directory {string} -- source directory path
@@ -105,6 +107,6 @@ class WrapperMain:
# return the Popen object of the new process created
self.print_lock.acquire()
Avalon.debug_info(f'[upscaler] Subprocess {os.getpid()} executing: {shlex.join(execute)}')
Avalon.debug_info(f'[upscaler] Subprocess {os.getpid()} executing: {" ".join(execute)}')
self.print_lock.release()
return subprocess.Popen(execute)