19 Commits
4.1.0 ... 4.3.0

Author SHA1 Message Date
k4yt3x
4680647f1f removed the insignificant master version number badge 2020-05-23 09:05:31 -04:00
k4yt3x
4015db5bcf CLI 4.0.1: require input and output to be specified if help is not specified 2020-05-23 08:59:57 -04:00
k4yt3x
082c6d44fa GUI 2.4.0: added H264/265 tune option 2020-05-23 08:59:39 -04:00
k4yt3x
de841a4636 removed bold exception string to avoid formatting bugs 2020-05-23 07:41:01 -04:00
k4yt3x
40711a2711 updated translations for upscaler 4.1.1 2020-05-22 17:56:31 -04:00
k4yt3x
0c63768165 Upscaler 4.1.1: fixed python-magic detection issues, added more debug output 2020-05-22 17:56:22 -04:00
k4yt3x
ac2d447391 GUI 2.3.1: added shortcut keys 2020-05-22 17:55:55 -04:00
k4yt3x
b03747dbde updated translations after fixing typo 2020-05-22 16:30:02 -04:00
k4yt3x
89740f01dc using mimetypes as a backup mime detection method, fixed typo 2020-05-22 16:29:51 -04:00
k4yt3x
676e70f088 updated drivers tooltips 2020-05-17 16:15:38 -04:00
k4yt3x
f57b5e9d04 bumped GUI version number 2020-05-17 16:15:26 -04:00
k4yt3x
f48e23a890 ignore FileNotFoundError while clearing cache 2020-05-17 15:50:05 -04:00
k4yt3x
826b4e9829 renamed stream copy checkbox 2020-05-17 15:48:21 -04:00
k4yt3x
c56be51e21 updated output path generation logic, organized lines 2020-05-17 15:48:06 -04:00
k4yt3x
d2b3175ccd changed default output codec to yuv420p for wider compatibility, added frame interpolation comment 2020-05-17 15:47:33 -04:00
k4yt3x
a98d1c7277 added stopping confirmation 2020-05-17 10:51:17 -04:00
k4yt3x
e107ddc96e updated translations 2020-05-17 10:13:43 -04:00
k4yt3x
289f5441eb added FFmpeg frame interpolation option 2020-05-17 09:57:07 -04:00
k4yt3x
179bd6afc8 updated GUI 2.1.0 screenshot 2020-05-16 08:01:11 -04:00
11 changed files with 619 additions and 304 deletions

View File

@@ -2,7 +2,6 @@
<img src="https://user-images.githubusercontent.com/21986859/81626588-ae5ab800-93eb-11ea-918f-ebe98c2de40a.png"/>
</p>
![Master Branch Version](https://img.shields.io/badge/master-v4.0.0-9cf?style=flat-square)
![GitHub release (latest by date)](https://img.shields.io/github/v/release/k4yt3x/video2x?style=flat-square)
![GitHub Workflow Status](https://img.shields.io/github/workflow/status/k4yt3x/video2x/Video2X%20Nightly%20Build?label=Nightly&style=flat-square)
![GitHub All Releases](https://img.shields.io/github/downloads/k4yt3x/video2x/total?style=flat-square)
@@ -86,7 +85,7 @@ The list is sorted from new to old.
### Video2X GUI
![GUI Preview](https://user-images.githubusercontent.com/21986859/81662226-cacb1480-942c-11ea-831f-2f0827f9cd11.png)\
![GUI Preview](https://user-images.githubusercontent.com/21986859/82119295-bc526500-976c-11ea-9ea8-53264689023e.png)\
*Video2X GUI Screenshot*
### Video2X CLI

View File

@@ -5,8 +5,8 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2020-05-12 04:27-0400\n"
"PO-Revision-Date: 2020-05-12 04:29-0400\n"
"POT-Creation-Date: 2020-05-22 17:51-0400\n"
"PO-Revision-Date: 2020-05-22 17:54-0400\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: zh_CN\n"
@@ -21,276 +21,304 @@ msgstr ""
msgid "Upscaling Progress"
msgstr "放大进度"
#: upscaler.py:106
#: upscaler.py:110
msgid "Specified or default cache directory is a file/link"
msgstr "指定或默认的缓存目录是文件/链接"
#: upscaler.py:112
#: upscaler.py:116
msgid "Creating cache directory {}"
msgstr "创建缓存目录 {}"
#: upscaler.py:115
#: upscaler.py:119
msgid "Unable to create {}"
msgstr "无法创建 {}"
#: upscaler.py:120
#: upscaler.py:124
msgid "Extracted frames are being saved to: {}"
msgstr "提取的帧将被保存到:{}"
#: upscaler.py:122
#: upscaler.py:126
msgid "Upscaled frames are being saved to: {}"
msgstr "已放大的帧将被保存到:{}"
#: upscaler.py:132
#: upscaler.py:136
msgid "Cleaning up cache directory: {}"
msgstr "清理缓存目录:{}"
#: upscaler.py:135
#: upscaler.py:141
msgid "Unable to delete: {}"
msgstr "无法删除:{}"
#: upscaler.py:141 upscaler.py:156 upscaler.py:167
#: upscaler.py:147 upscaler.py:162 upscaler.py:173
msgid "Input and output path type mismatch"
msgstr "输入和输出路径类型不匹配"
#: upscaler.py:142
#: upscaler.py:148
msgid "Input is multiple files but output is not directory"
msgstr "输入是多个文件,但输出不是目录"
#: upscaler.py:146
#: upscaler.py:152
msgid "Input path {} is neither a file nor a directory"
msgstr "输入路径 {} 既不是文件也不是目录"
#: upscaler.py:150 upscaler.py:172
#: upscaler.py:156 upscaler.py:178
msgid "Input directory and output directory cannot be the same"
msgstr "输入目录和输出目录不能相同"
#: upscaler.py:157
#: upscaler.py:163
msgid "Input is single file but output is directory"
msgstr "所选的输入路径是单个文件,但输出路径是目录"
#: upscaler.py:160
#: upscaler.py:166
msgid "No suffix found in output file path"
msgstr "在输出文件路径中未找到后缀"
#: upscaler.py:161
#: upscaler.py:167
msgid "Suffix must be specified"
msgstr "必须指定文件后缀"
#: upscaler.py:168
#: upscaler.py:174
msgid "Input is directory but output is existing single file"
msgstr "输入是目录,但输出是现有的单个文件"
#: upscaler.py:177
#: upscaler.py:183
msgid "Input path is neither a file nor a directory"
msgstr "输入路径既不是文件也不是目录"
#: upscaler.py:186
#: upscaler.py:192
msgid "FFmpeg or FFprobe cannot be found under the specified path"
msgstr "在指定的路径下找不到 FFmpeg 或 FFprobe"
#: upscaler.py:187 upscaler.py:197
#: upscaler.py:193 upscaler.py:203
msgid "Please check the configuration file settings"
msgstr "请检查配置文件设置"
#: upscaler.py:196
#: upscaler.py:202
msgid "Specified driver executable directory doesn't exist"
msgstr "指定驱动的可执行文件不存在"
#: upscaler.py:223
#: upscaler.py:229
msgid "Failed to parse driver argument: {}"
msgstr "解析驱动程序参数失败:{}"
#: upscaler.py:248
msgid "Anime4KCPP doesn't yet support GIF processing"
msgstr "Anime4KCPP 尚不支持GIF处理"
#: upscaler.py:263
#: upscaler.py:261
msgid "Unrecognized driver: {}"
msgstr "无法识别的驱动名称:{}"
#: upscaler.py:303
#: upscaler.py:301
msgid "Starting progress monitor"
msgstr "启动进度监视器"
#: upscaler.py:308
#: upscaler.py:306
msgid "Starting upscaled image cleaner"
msgstr "启动已放大图像清理程序"
#: upscaler.py:317 upscaler.py:334
#: upscaler.py:315 upscaler.py:332
msgid "Killing progress monitor"
msgstr "终结进度监视器"
#: upscaler.py:320 upscaler.py:337
#: upscaler.py:318 upscaler.py:335
msgid "Killing upscaled image cleaner"
msgstr "终结已放大图像清理程序"
#: upscaler.py:341
#: upscaler.py:339
msgid "Terminating all processes"
msgstr "正在终止所有进程"
#: upscaler.py:348
#: upscaler.py:346
msgid "Main process waiting for subprocesses to exit"
msgstr "主进程开始等待子进程结束"
#: upscaler.py:367 upscaler.py:371
#: upscaler.py:365 upscaler.py:369
msgid "Subprocess {} exited with code {}"
msgstr "子进程 {} 结束,返回码 {}"
#: upscaler.py:377
#: upscaler.py:375
msgid "Stop signal received"
msgstr "收到停止信号"
#: upscaler.py:382
#: upscaler.py:380
msgid "Subprocess execution ran into an error"
msgstr "子进程执行遇到错误"
#: upscaler.py:430
msgid "Upscaling single file: {}"
msgstr "放大单个文件:{}"
#: upscaler.py:410
msgid "Loading files into processing queue"
msgstr "正在将文件添加到处理队列中"
#: upscaler.py:466
#: upscaler.py:415
msgid "Loading files from multiple paths"
msgstr "正在从多个路径中导入文件"
#: upscaler.py:416 upscaler.py:435 upscaler.py:442
msgid "Input path(s): {}"
msgstr "输入路径:{}"
#: upscaler.py:434
msgid "Loading single file"
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:469 upscaler.py:487 upscaler.py:545
#: upscaler.py:488 upscaler.py:550
msgid "Upscaling completed"
msgstr "放大完成"
#: upscaler.py:482
msgid "Starting to upscale video with Anime4KCPP"
msgstr "开始用 Anime4KCPP 放大视频"
#: upscaler.py:496
#: upscaler.py:502
msgid "Reading video information"
msgstr "读取视频信息"
#: upscaler.py:510
#: upscaler.py:516
msgid "Aborting: No video stream found"
msgstr "程序中止:文件中未找到视频流"
#: upscaler.py:531
msgid "Unsupported pixel format: {}"
msgstr "不支持的像素格式:{}"
#: upscaler.py:534
#: upscaler.py:521
msgid "Framerate: {}"
msgstr "帧率:{}"
#: upscaler.py:543
#: upscaler.py:538
msgid "Unsupported pixel format: {}"
msgstr "不支持的像素格式:{}"
#: upscaler.py:548
msgid "Starting to upscale extracted frames"
msgstr "开始对提取的帧进行放大"
#: upscaler.py:550
msgid "File {} ({}) neither an image of a video"
#: upscaler.py:555
msgid "File {} ({}) neither an image nor a video"
msgstr "文件 {} {} 既不是图片也不是视频"
#: upscaler.py:551
#: upscaler.py:556
msgid "Skipping this file"
msgstr "将跳过此文件"
#: upscaler.py:561
#: upscaler.py:566
msgid "Converting extracted frames into GIF image"
msgstr "正在将提取的帧转换为 GIF"
#: upscaler.py:565 upscaler.py:574
#: upscaler.py:570 upscaler.py:579
msgid "Conversion completed"
msgstr "转换已完成"
#: upscaler.py:570
#: upscaler.py:575
msgid "Converting extracted frames into video"
msgstr "正在将提取的帧转换为视频"
#: upscaler.py:578
#: upscaler.py:583
msgid "Migrating audio, subtitles and other streams to upscaled video"
msgstr "正在将音频、字幕和其他流迁移到放大后的视频"
#: upscaler.py:587
#: upscaler.py:593
msgid "Failed to migrate streams"
msgstr "迁移流失败"
#: upscaler.py:588
#: upscaler.py:594
msgid "Trying to output video without additional streams"
msgstr "正在尝试输出不含其他流的视频"
#: upscaler.py:599
msgid "Output video file exists, aborting"
msgstr "输出目标文件已存在,取消输出"
#: upscaler.py:610
msgid "Output video file exists"
msgstr "输出目标文件已存在"
#: upscaler.py:603
#: upscaler.py:614
msgid "Created temporary directory to contain file"
msgstr "为文件创建了临时目录"
#: upscaler.py:617
msgid "Writing intermediate file to: {}"
msgstr "正在将中间视频文件写入至:{}"
#: video2x.py:84
#: video2x.py:85
msgid ""
"Video2X Version: {}\n"
"Video2X CLI Version: {}\n"
"Upscaler Version: {}\n"
"Author: K4YT3X\n"
"License: GNU GPL v3\n"
"Github Page: https://github.com/k4yt3x/video2x\n"
"Contact: k4yt3x@k4yt3x.com"
msgstr ""
"Video2X 版本: {}\n"
"放大组件版本:{}\n"
"作者: K4YT3X\n"
"开源许可: GNU GPL v3\n"
"GitHub 主页https://github.com/k4yt3x/video2x\n"
"联系方式k4yt3x@k4yt3x.com"
#: video2x.py:106
#: video2x.py:108
msgid "Video2X Options"
msgstr "Video2X 选项"
#: video2x.py:107
#: video2x.py:109
msgid "show this help message and exit"
msgstr "显示此帮助消息并退出"
#: video2x.py:108
#: video2x.py:110
msgid "source video file/directory"
msgstr "源视频文件/目录"
#: video2x.py:109
#: video2x.py:111
msgid "output video file/directory"
msgstr "输出视频文件/目录"
#: video2x.py:110
#: video2x.py:112
msgid "video2x config file path"
msgstr "video2x 配置文件路径"
#: video2x.py:112
#: video2x.py:114
msgid "display version, lawful information and exit"
msgstr "显示版本和法律信息并退出"
#: video2x.py:115
#: video2x.py:117
msgid "Upscaling Options"
msgstr "视频放大选项"
#: video2x.py:116
#: video2x.py:118
msgid "upscaling driver"
msgstr "视频放大驱动"
#: video2x.py:117
#: video2x.py:119
msgid "scaling ratio"
msgstr "缩放比"
#: video2x.py:118
#: video2x.py:120
msgid "number of processes to use for upscaling"
msgstr "并发进程数"
#: video2x.py:119
#: video2x.py:121
msgid "preserve extracted and upscaled frames"
msgstr "保留提取的和放大的帧"
#: video2x.py:159
#: video2x.py:161
msgid "This file cannot be imported"
msgstr "此文件无法被当作模块导入"
#: video2x.py:234
#: video2x.py:236
msgid "Program completed, taking {} seconds"
msgstr "程序执行完毕,总计花费 {} 秒"
#: video2x.py:237
#: video2x.py:239
msgid "An exception has occurred"
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 "输出视频宽度"
@@ -317,6 +345,3 @@ msgstr "发生了异常"
#~ msgid "You must specify both width and height"
#~ msgstr "您必须同时指定宽度和高度"
#~ msgid "Upscaling videos in directory: {}"
#~ msgstr "放大该文件夹中的所有视频:{}"

View File

@@ -4,7 +4,7 @@
Name: Video2X Upscaler
Author: K4YT3X
Date Created: December 10, 2018
Last Modified: May 16, 2020
Last Modified: May 22, 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 mimetypes
import pathlib
import queue
import re
@@ -49,7 +50,7 @@ language.install()
_ = language.gettext
# version information
UPSCALER_VERSION = '4.1.0'
UPSCALER_VERSION = '4.1.1'
# these names are consistent for
# - driver selection in command line
@@ -134,7 +135,9 @@ class Upscaler:
# therefore, plain print is used
print(_('Cleaning up cache directory: {}').format(directory))
shutil.rmtree(directory)
except (OSError, FileNotFoundError):
except FileNotFoundError:
pass
except OSError:
print(_('Unable to delete: {}').format(directory))
traceback.print_exc()
@@ -404,8 +407,14 @@ class Upscaler:
# define processing queue
self.processing_queue = queue.Queue()
Avalon.info(_('Loading files into processing queue'))
# if input is a list of files
if isinstance(self.input, list):
Avalon.info(_('Loading files from multiple paths'))
Avalon.debug_info(_('Input path(s): {}').format(self.input))
# make output directory if it doesn't exist
self.output.mkdir(parents=True, exist_ok=True)
@@ -422,12 +431,15 @@ class Upscaler:
# if input specified is single file
elif self.input.is_file():
Avalon.info(_('Upscaling single file: {}').format(self.input))
Avalon.info(_('Loading single file'))
Avalon.debug_info(_('Input path(s): {}').format(self.input))
self.processing_queue.put((self.input.absolute(), self.output.absolute()))
# if input specified is a directory
elif self.input.is_dir():
Avalon.info(_('Loading files from directory'))
Avalon.debug_info(_('Input path(s): {}').format(self.input))
# make output directory if it doesn't exist
self.output.mkdir(parents=True, exist_ok=True)
for input_path in [f for f in self.input.iterdir() if f.is_file()]:
@@ -440,6 +452,11 @@ class Upscaler:
# record file count for external calls
self.total_files = self.processing_queue.qsize()
Avalon.info(_('Loaded files into processing queue'))
# print all files in queue for debugging
for job in self.processing_queue.queue:
Avalon.debug_info(_('Input file: {}').format(job[0].absolute()))
try:
while not self.processing_queue.empty():
@@ -455,6 +472,13 @@ class Upscaler:
input_file_type = input_file_mime_type.split('/')[0]
input_file_subtype = input_file_mime_type.split('/')[1]
# in case python-magic fails to detect file type
# try guessing file mime type with mimetypes
if input_file_type not in ['image', 'video']:
input_file_mime_type = mimetypes.guess_type(self.current_input_file.name)[0]
input_file_type = input_file_mime_type.split('/')[0]
input_file_subtype = input_file_mime_type.split('/')[1]
# start handling input
# if input file is a static image
if input_file_type == 'image' and input_file_subtype != 'gif':
@@ -528,7 +552,7 @@ class Upscaler:
# if file is none of: image, image/gif, video
# skip to the next task
else:
Avalon.error(_('File {} ({}) neither an image of a video').format(self.current_input_file, input_file_mime_type))
Avalon.error(_('File {} ({}) neither an image nor a video').format(self.current_input_file, input_file_mime_type))
Avalon.warning(_('Skipping this file'))
self.processing_queue.task_done()
self.total_processed += 1

View File

@@ -5,7 +5,7 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2020-05-12 04:27-0400\n"
"POT-Creation-Date: 2020-05-22 17:51-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"
@@ -19,268 +19,289 @@ msgstr ""
msgid "Upscaling Progress"
msgstr ""
#: upscaler.py:106
#: upscaler.py:110
msgid "Specified or default cache directory is a file/link"
msgstr ""
#: upscaler.py:112
#: upscaler.py:116
msgid "Creating cache directory {}"
msgstr ""
#: upscaler.py:115
#: upscaler.py:119
msgid "Unable to create {}"
msgstr ""
#: upscaler.py:120
#: upscaler.py:124
msgid "Extracted frames are being saved to: {}"
msgstr ""
#: upscaler.py:122
#: upscaler.py:126
msgid "Upscaled frames are being saved to: {}"
msgstr ""
#: upscaler.py:132
#: upscaler.py:136
msgid "Cleaning up cache directory: {}"
msgstr ""
#: upscaler.py:135
#: upscaler.py:141
msgid "Unable to delete: {}"
msgstr ""
#: upscaler.py:141 upscaler.py:156 upscaler.py:167
#: upscaler.py:147 upscaler.py:162 upscaler.py:173
msgid "Input and output path type mismatch"
msgstr ""
#: upscaler.py:142
#: upscaler.py:148
msgid "Input is multiple files but output is not directory"
msgstr ""
#: upscaler.py:146
#: upscaler.py:152
msgid "Input path {} is neither a file nor a directory"
msgstr ""
#: upscaler.py:150 upscaler.py:172
#: upscaler.py:156 upscaler.py:178
msgid "Input directory and output directory cannot be the same"
msgstr ""
#: upscaler.py:157
#: upscaler.py:163
msgid "Input is single file but output is directory"
msgstr ""
#: upscaler.py:160
#: upscaler.py:166
msgid "No suffix found in output file path"
msgstr ""
#: upscaler.py:161
#: upscaler.py:167
msgid "Suffix must be specified"
msgstr ""
#: upscaler.py:168
#: upscaler.py:174
msgid "Input is directory but output is existing single file"
msgstr ""
#: upscaler.py:177
#: upscaler.py:183
msgid "Input path is neither a file nor a directory"
msgstr ""
#: upscaler.py:186
#: upscaler.py:192
msgid "FFmpeg or FFprobe cannot be found under the specified path"
msgstr ""
#: upscaler.py:187 upscaler.py:197
#: upscaler.py:193 upscaler.py:203
msgid "Please check the configuration file settings"
msgstr ""
#: upscaler.py:196
#: upscaler.py:202
msgid "Specified driver executable directory doesn't exist"
msgstr ""
#: upscaler.py:223
#: upscaler.py:229
msgid "Failed to parse driver argument: {}"
msgstr ""
#: upscaler.py:248
msgid "Anime4KCPP doesn't yet support GIF processing"
msgstr ""
#: upscaler.py:263
#: upscaler.py:261
msgid "Unrecognized driver: {}"
msgstr ""
#: upscaler.py:303
#: upscaler.py:301
msgid "Starting progress monitor"
msgstr ""
#: upscaler.py:308
#: upscaler.py:306
msgid "Starting upscaled image cleaner"
msgstr ""
#: upscaler.py:317 upscaler.py:334
#: upscaler.py:315 upscaler.py:332
msgid "Killing progress monitor"
msgstr ""
#: upscaler.py:320 upscaler.py:337
#: upscaler.py:318 upscaler.py:335
msgid "Killing upscaled image cleaner"
msgstr ""
#: upscaler.py:341
#: upscaler.py:339
msgid "Terminating all processes"
msgstr ""
#: upscaler.py:348
#: upscaler.py:346
msgid "Main process waiting for subprocesses to exit"
msgstr ""
#: upscaler.py:367 upscaler.py:371
#: upscaler.py:365 upscaler.py:369
msgid "Subprocess {} exited with code {}"
msgstr ""
#: upscaler.py:377
#: upscaler.py:375
msgid "Stop signal received"
msgstr ""
#: upscaler.py:382
#: upscaler.py:380
msgid "Subprocess execution ran into an error"
msgstr ""
#: upscaler.py:430
msgid "Upscaling single file: {}"
#: upscaler.py:410
msgid "Loading files into processing queue"
msgstr ""
#: upscaler.py:466
#: upscaler.py:415
msgid "Loading files from multiple paths"
msgstr ""
#: upscaler.py:416 upscaler.py:435 upscaler.py:442
msgid "Input path(s): {}"
msgstr ""
#: upscaler.py:434
msgid "Loading single file"
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:469 upscaler.py:487 upscaler.py:545
#: upscaler.py:488 upscaler.py:550
msgid "Upscaling completed"
msgstr ""
#: upscaler.py:482
msgid "Starting to upscale video with Anime4KCPP"
msgstr ""
#: upscaler.py:496
#: upscaler.py:502
msgid "Reading video information"
msgstr ""
#: upscaler.py:510
#: upscaler.py:516
msgid "Aborting: No video stream found"
msgstr ""
#: upscaler.py:531
msgid "Unsupported pixel format: {}"
msgstr ""
#: upscaler.py:534
#: upscaler.py:521
msgid "Framerate: {}"
msgstr ""
#: upscaler.py:543
#: upscaler.py:538
msgid "Unsupported pixel format: {}"
msgstr ""
#: upscaler.py:548
msgid "Starting to upscale extracted frames"
msgstr ""
#: upscaler.py:550
msgid "File {} ({}) neither an image of a video"
#: upscaler.py:555
msgid "File {} ({}) neither an image nor a video"
msgstr ""
#: upscaler.py:551
#: upscaler.py:556
msgid "Skipping this file"
msgstr ""
#: upscaler.py:561
#: upscaler.py:566
msgid "Converting extracted frames into GIF image"
msgstr ""
#: upscaler.py:565 upscaler.py:574
#: upscaler.py:570 upscaler.py:579
msgid "Conversion completed"
msgstr ""
#: upscaler.py:570
#: upscaler.py:575
msgid "Converting extracted frames into video"
msgstr ""
#: upscaler.py:578
#: upscaler.py:583
msgid "Migrating audio, subtitles and other streams to upscaled video"
msgstr ""
#: upscaler.py:587
#: upscaler.py:593
msgid "Failed to migrate streams"
msgstr ""
#: upscaler.py:588
#: upscaler.py:594
msgid "Trying to output video without additional streams"
msgstr ""
#: upscaler.py:599
msgid "Output video file exists, aborting"
#: upscaler.py:610
msgid "Output video file exists"
msgstr ""
#: upscaler.py:603
#: upscaler.py:614
msgid "Created temporary directory to contain file"
msgstr ""
#: upscaler.py:617
msgid "Writing intermediate file to: {}"
msgstr ""
#: video2x.py:84
#: video2x.py:85
msgid ""
"Video2X Version: {}\n"
"Video2X CLI Version: {}\n"
"Upscaler Version: {}\n"
"Author: K4YT3X\n"
"License: GNU GPL v3\n"
"Github Page: https://github.com/k4yt3x/video2x\n"
"Contact: k4yt3x@k4yt3x.com"
msgstr ""
#: video2x.py:106
#: video2x.py:108
msgid "Video2X Options"
msgstr ""
#: video2x.py:107
#: video2x.py:109
msgid "show this help message and exit"
msgstr ""
#: video2x.py:108
#: video2x.py:110
msgid "source video file/directory"
msgstr ""
#: video2x.py:109
#: video2x.py:111
msgid "output video file/directory"
msgstr ""
#: video2x.py:110
#: video2x.py:112
msgid "video2x config file path"
msgstr ""
#: video2x.py:112
#: video2x.py:114
msgid "display version, lawful information and exit"
msgstr ""
#: video2x.py:115
#: video2x.py:117
msgid "Upscaling Options"
msgstr ""
#: video2x.py:116
#: video2x.py:118
msgid "upscaling driver"
msgstr ""
#: video2x.py:117
#: video2x.py:119
msgid "scaling ratio"
msgstr ""
#: video2x.py:118
#: video2x.py:120
msgid "number of processes to use for upscaling"
msgstr ""
#: video2x.py:119
#: video2x.py:121
msgid "preserve extracted and upscaled frames"
msgstr ""
#: video2x.py:159
#: video2x.py:161
msgid "This file cannot be imported"
msgstr ""
#: video2x.py:234
#: video2x.py:236
msgid "Program completed, taking {} seconds"
msgstr ""
#: video2x.py:237
#: video2x.py:239
msgid "An exception has occurred"
msgstr ""

View File

@@ -13,7 +13,7 @@ __ __ _ _ ___ __ __
Name: Video2X Controller
Creator: K4YT3X
Date Created: Feb 24, 2018
Last Modified: May 15, 2020
Last Modified: May 23, 2020
Editor: BrianPetkovsek
Last Modified: June 17, 2019
@@ -80,7 +80,7 @@ language.install()
_ = language.gettext
CLI_VERSION = '4.0.0'
CLI_VERSION = '4.0.1'
LEGAL_INFO = _('''Video2X CLI Version: {}
Upscaler Version: {}
@@ -107,8 +107,15 @@ 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('-i', '--input', type=pathlib.Path, help=_('source video file/directory'))
video2x_options.add_argument('-o', '--output', type=pathlib.Path, help=_('output video file/directory'))
# if help is in arguments list
# do not require input and output path to be specified
require_input_output = True
if '-h' in sys.argv or '--help' in sys.argv:
require_input_output = False
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',
default=pathlib.Path(__file__).parent.absolute() / 'video2x.yaml')
video2x_options.add_argument('-v', '--version', help=_('display version, lawful information and exit'), action='store_true')

View File

@@ -1,7 +1,7 @@
# Name: Video2X Configuration File
# Creator: K4YT3X
# Date Created: October 23, 2018
# Last Modified: May 14, 2020
# Last Modified: May 23, 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
@@ -119,10 +119,11 @@ ffmpeg:
'-f': image2 # force image2 format
output_options:
'-vcodec': libx264 # video codec
'-pix_fmt': 'yuv444p10le' # overwrite default pixel format
'-pix_fmt': 'yuv420p' # overwrite default pixel format
'-crf': 17 # H.264 Constant Rate Factor
'-b:v': null # target average bitrate
'-vf': 'pad=ceil(iw/2)*2:ceil(ih/2)*2' # ensure output is divisible by 2, recommended for libx264
'-tune': 'animation' # encoding tuning film/animation/grain/stillimage/fastdecode/zerolatency/psnr/ssim
# Step 3: Streams Migration
# migrate audio and subtitle streams from original
# video into the upscaled video
@@ -138,6 +139,7 @@ ffmpeg:
- '1:d?' # copy data streams
- '1:t?' # copy fonts
'-c': copy # copy codec for all streams
# '-vf': 'minterpolate=''fps=60''' # minterpolate frame interpolation
'-map_metadata': 0 # copy known metadata tags
# '-movflags': 'use_metadata_tags' # copy custom/arbitrary metadata tags
'-pix_fmt': null

View File

@@ -4,7 +4,7 @@
Creator: Video2X GUI
Author: K4YT3X
Date Created: May 5, 2020
Last Modified: May 15, 2020
Last Modified: May 23, 2020
"""
# local imports
@@ -15,6 +15,7 @@ from wrappers.ffmpeg import Ffmpeg
# built-in imports
import contextlib
import json
import mimetypes
import os
import pathlib
import sys
@@ -25,13 +26,13 @@ import urllib
import yaml
# third-party imports
from PyQt5 import QtGui, uic
from PyQt5 import uic
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import magic
# QObject, pyqtSlot, pyqtSignal, QRunnable, QThreadPool, QAbstractTableModel, Qt
GUI_VERSION = '2.1.0'
GUI_VERSION = '2.4.0'
LEGAL_INFO = f'''Video2X GUI Version: {GUI_VERSION}\\
Upscaler Version: {UPSCALER_VERSION}\\
@@ -132,6 +133,14 @@ class InputTableModel(QAbstractTableModel):
input_file_mime_type = magic.from_file(str(file_path.absolute()), mime=True)
input_file_type = input_file_mime_type.split('/')[0]
input_file_subtype = input_file_mime_type.split('/')[1]
# in case python-magic fails to detect file type
# try guessing file mime type with mimetypes
if input_file_type not in ['image', 'video']:
input_file_mime_type = mimetypes.guess_type(file_path.name)[0]
input_file_type = input_file_mime_type.split('/')[0]
input_file_subtype = input_file_mime_type.split('/')[1]
if input_file_type == 'image':
if input_file_subtype == 'gif':
return 'GIF'
@@ -183,11 +192,22 @@ class Video2XMainWindow(QMainWindow):
# set window title and icon
self.video2x_icon_path = str(resource_path('images/video2x.png'))
self.setWindowTitle(f'Video2X GUI {GUI_VERSION}')
self.setWindowIcon(QtGui.QIcon(self.video2x_icon_path))
self.setWindowIcon(QIcon(self.video2x_icon_path))
# register shortcut keys
QShortcut(QKeySequence(Qt.CTRL + Qt.Key_W), self, self.close)
QShortcut(QKeySequence(Qt.CTRL + Qt.Key_Q), self, self.close)
QShortcut(QKeySequence(Qt.CTRL + Qt.Key_I), self, self.select_input_file)
QShortcut(QKeySequence(Qt.CTRL + Qt.Key_O), self, self.select_output_file)
QShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_I), self, self.select_input_folder)
QShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_O), self, self.select_output_folder)
# menu bar
self.action_exit = self.findChild(QAction, 'actionExit')
self.action_exit.triggered.connect(sys.exit)
self.action_exit.triggered.connect(self.close)
self.action_shortcuts = self.findChild(QAction, 'actionShortcuts')
self.action_shortcuts.triggered.connect(self.show_shortcuts)
self.action_about = self.findChild(QAction, 'actionAbout')
self.action_about.triggered.connect(self.show_about)
@@ -363,6 +383,7 @@ class Video2XMainWindow(QMainWindow):
self.ffmpeg_assemble_video_output_options_video_codec_line_edit = self.findChild(QLineEdit, 'ffmpegAssembleVideoOutputOptionsVideoCodecLineEdit')
self.ffmpeg_assemble_video_output_options_pixel_format_line_edit = self.findChild(QLineEdit, 'ffmpegAssembleVideoOutputOptionsPixelFormatLineEdit')
self.ffmpeg_assemble_video_output_options_crf_spin_box = self.findChild(QSpinBox, 'ffmpegAssembleVideoOutputOptionsCrfSpinBox')
self.ffmpeg_assemble_video_output_options_tune_combo_box = self.findChild(QComboBox, 'ffmpegAssembleVideoOutputOptionsTuneComboBox')
self.ffmpeg_assemble_video_output_options_bitrate_line_edit = self.findChild(QLineEdit, 'ffmpegAssembleVideoOutputOptionsBitrateLineEdit')
self.ffmpeg_assemble_video_output_options_ensure_divisible_check_box = self.findChild(QCheckBox, 'ffmpegAssembleVideoOutputOptionsEnsureDivisibleCheckBox')
self.ffmpeg_assemble_video_hardware_acceleration_check_box = self.findChild(QCheckBox, 'ffmpegAssembleVideoHardwareAccelerationCheckBox')
@@ -374,9 +395,12 @@ class Video2XMainWindow(QMainWindow):
self.ffmpeg_migrate_streams_output_options_mapping_data_check_box_check_box = self.findChild(QCheckBox, 'ffmpegMigrateStreamsOutputOptionsMappingDataCheckBox')
self.ffmpeg_migrate_streams_output_options_mapping_font_check_box_check_box = self.findChild(QCheckBox, 'ffmpegMigrateStreamsOutputOptionsMappingFontCheckBox')
self.ffmpeg_migrate_streams_output_options_pixel_format_line_edit = self.findChild(QLineEdit, 'ffmpegMigrateStreamsOutputOptionsPixelFormatLineEdit')
self.ffmpeg_migrate_streams_output_options_copy_codec_check_box = self.findChild(QCheckBox, 'ffmpegMigrateStreamsOutputOptionsCopyCodecCheckBox')
self.ffmpeg_migrate_streams_output_options_copy_known_metadata_tags_check_box = self.findChild(QCheckBox, 'ffmpegMigrateStreamsOutputOptionsOtherCopyKnownMetadataTagsCheckBox')
self.ffmpeg_migrate_streams_output_options_copy_arbitrary_metadata_tags_check_box = self.findChild(QCheckBox, 'ffmpegMigrateStreamsOutputOptionsOtherCopyArbitraryMetadataTagsCheckBox')
self.ffmpeg_migrate_streams_output_options_frame_interpolation_spin_box = self.findChild(QSpinBox, 'ffmpegMigrateStreamsOutputOptionsFrameInterpolationSpinBox')
self.ffmpeg_migrate_streams_output_options_frame_interpolation_spin_box.valueChanged.connect(self.mutually_exclude_frame_interpolation_stream_copy)
self.ffmpeg_migrate_streams_output_options_frame_interpolation_spin_box.textChanged.connect(self.mutually_exclude_frame_interpolation_stream_copy)
self.ffmpeg_migrate_streams_output_options_copy_streams_check_box = self.findChild(QCheckBox, 'ffmpegMigrateStreamsOutputOptionsCopyStreamsCheckBox')
self.ffmpeg_migrate_streams_output_options_copy_known_metadata_tags_check_box = self.findChild(QCheckBox, 'ffmpegMigrateStreamsOutputOptionsCopyKnownMetadataTagsCheckBox')
self.ffmpeg_migrate_streams_output_options_copy_arbitrary_metadata_tags_check_box = self.findChild(QCheckBox, 'ffmpegMigrateStreamsOutputOptionsCopyArbitraryMetadataTagsCheckBox')
self.ffmpeg_migrate_streams_hardware_acceleration_check_box = self.findChild(QCheckBox, 'ffmpegMigrateStreamsHardwareAccelerationCheckBox')
# Gifski settings
@@ -396,48 +420,6 @@ class Video2XMainWindow(QMainWindow):
# load configurations after GUI initialization
self.load_configurations()
def dragEnterEvent(self, event):
if event.mimeData().hasUrls():
event.accept()
else:
event.ignore()
def dropEvent(self, event):
input_paths = [pathlib.Path(u.toLocalFile()) for u in event.mimeData().urls()]
for path in input_paths:
if (path.is_file() or path.is_dir()) and not self.input_table_path_exists(path):
self.input_table_data.append(path)
self.update_input_table()
self.update_output_path()
def enable_line_edit_file_drop(self, line_edit: QLineEdit):
line_edit.dragEnterEvent = self.dragEnterEvent
line_edit.dropEvent = lambda event: line_edit.setText(str(pathlib.Path(event.mimeData().urls()[0].toLocalFile()).absolute()))
def show_ffprobe_output(self, event):
input_paths = [pathlib.Path(u.toLocalFile()) for u in event.mimeData().urls()]
if not input_paths[0].is_file():
return
ffmpeg_object = Ffmpeg(self.ffmpeg_settings)
file_info_json = ffmpeg_object.probe_file_info(input_paths[0])
self.ffprobe_plain_text_edit.setPlainText(json.dumps(file_info_json, indent=2))
@staticmethod
def read_config(config_file: pathlib.Path) -> dict:
""" read video2x configurations from config file
Arguments:
config_file {pathlib.Path} -- video2x configuration file pathlib.Path
Returns:
dict -- dictionary of video2x configuration
"""
with open(config_file, 'r') as config:
return yaml.load(config, Loader=yaml.FullLoader)
def load_configurations(self):
# get config file path from line edit
@@ -549,6 +531,7 @@ class Video2XMainWindow(QMainWindow):
self.ffmpeg_assemble_video_output_options_video_codec_line_edit.setText(settings['output_options']['-vcodec'])
self.ffmpeg_assemble_video_output_options_pixel_format_line_edit.setText(settings['output_options']['-pix_fmt'])
self.ffmpeg_assemble_video_output_options_crf_spin_box.setValue(settings['output_options']['-crf'])
self.ffmpeg_assemble_video_output_options_tune_combo_box.setCurrentText(settings['output_options']['-tune'])
self.ffmpeg_assemble_video_output_options_bitrate_line_edit.setText(settings['output_options']['-b:v'])
# migrate streams
@@ -648,6 +631,7 @@ class Video2XMainWindow(QMainWindow):
self.config['ffmpeg']['assemble_video']['output_options']['-vcodec'] = self.ffmpeg_assemble_video_output_options_video_codec_line_edit.text()
self.config['ffmpeg']['assemble_video']['output_options']['-pix_fmt'] = self.ffmpeg_assemble_video_output_options_pixel_format_line_edit.text()
self.config['ffmpeg']['assemble_video']['output_options']['-crf'] = self.ffmpeg_assemble_video_output_options_crf_spin_box.value()
self.config['ffmpeg']['assemble_video']['output_options']['-tune'] = self.ffmpeg_assemble_video_output_options_tune_combo_box.currentText()
if self.ffmpeg_assemble_video_output_options_bitrate_line_edit.text() != '':
self.config['ffmpeg']['assemble_video']['output_options']['-b:v'] = self.ffmpeg_assemble_video_output_options_bitrate_line_edit.text()
else:
@@ -691,8 +675,18 @@ 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:
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']):
self.config['ffmpeg']['migrate_streams']['output_options']['-vf'] += f',minterpolate=\'fps={fps}\''
else:
self.config['ffmpeg']['migrate_streams']['output_options']['-vf'] = f'minterpolate=\'fps={fps}\''
else:
self.config['ffmpeg']['migrate_streams']['output_options'].pop('-vf', None)
# copy source codec
if self.ffmpeg_migrate_streams_output_options_copy_codec_check_box.isChecked():
if self.ffmpeg_migrate_streams_output_options_copy_streams_check_box.isChecked():
self.config['ffmpeg']['migrate_streams']['output_options']['-c'] = 'copy'
else:
self.config['ffmpeg']['migrate_streams']['output_options'].pop('-c', None)
@@ -728,6 +722,56 @@ class Video2XMainWindow(QMainWindow):
self.config['gifski']['once'] = self.gifski_once_check_box.isChecked()
self.config['gifski']['quiet'] = self.gifski_quiet_check_box.isChecked()
def dragEnterEvent(self, event):
if event.mimeData().hasUrls():
event.accept()
else:
event.ignore()
def dropEvent(self, event):
input_paths = [pathlib.Path(u.toLocalFile()) for u in event.mimeData().urls()]
for path in input_paths:
if (path.is_file() or path.is_dir()) and not self.input_table_path_exists(path):
self.input_table_data.append(path)
self.update_output_path()
self.update_input_table()
def enable_line_edit_file_drop(self, line_edit: QLineEdit):
line_edit.dragEnterEvent = self.dragEnterEvent
line_edit.dropEvent = lambda event: line_edit.setText(str(pathlib.Path(event.mimeData().urls()[0].toLocalFile()).absolute()))
def show_ffprobe_output(self, event):
input_paths = [pathlib.Path(u.toLocalFile()) for u in event.mimeData().urls()]
if not input_paths[0].is_file():
return
ffmpeg_object = Ffmpeg(self.ffmpeg_settings)
file_info_json = ffmpeg_object.probe_file_info(input_paths[0])
self.ffprobe_plain_text_edit.setPlainText(json.dumps(file_info_json, indent=2))
@staticmethod
def read_config(config_file: pathlib.Path) -> dict:
""" read video2x configurations from config file
Arguments:
config_file {pathlib.Path} -- video2x configuration file pathlib.Path
Returns:
dict -- dictionary of video2x configuration
"""
with open(config_file, 'r') as config:
return yaml.load(config, Loader=yaml.FullLoader)
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)
self.ffmpeg_migrate_streams_output_options_copy_streams_check_box.setDisabled(True)
else:
self.ffmpeg_migrate_streams_output_options_copy_streams_check_box.setChecked(True)
self.ffmpeg_migrate_streams_output_options_copy_streams_check_box.setDisabled(False)
def update_gui_for_driver(self):
current_driver = AVAILABLE_DRIVERS[self.driver_combo_box.currentText()]
@@ -764,13 +808,16 @@ class Video2XMainWindow(QMainWindow):
for item in items_to_delete:
self.input_table_data.remove(item)
self.update_output_path()
self.update_input_table()
def input_table_clear_all(self):
self.input_table_data = []
self.update_output_path()
self.update_input_table()
def input_table_path_exists(self, input_path):
def input_table_path_exists(self, input_path: pathlib.Path) -> bool:
for path in self.input_table_data:
# not using Path.samefile since file may not exist
if str(path.absolute()) == str(input_path.absolute()):
@@ -790,58 +837,74 @@ class Video2XMainWindow(QMainWindow):
return pathlib.Path(folder_selected)
def update_output_path(self):
# if there is more than one input
if len(self.input_table_data) != 1:
return
# if input list is empty
# clear output path
if len(self.input_table_data) == 0:
self.output_line_edit.setText('')
input_path = self.input_table_data[0]
# give up if input path doesn't exist or isn't a file or a directory
if not input_path.exists() or not (input_path.is_file() or input_path.is_dir()):
return
# 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()))
if input_path.is_file():
# if there's only one input file
# generate output file/directory name automatically
elif len(self.input_table_data) == 1:
input_path = self.input_table_data[0]
# give up if input path doesn't exist or isn't a file or a directory
if not input_path.exists() or not (input_path.is_file() or input_path.is_dir()):
return
# generate suffix automatically
input_file_mime_type = magic.from_file(str(input_path.absolute()), mime=True)
input_file_type = input_file_mime_type.split('/')[0]
input_file_subtype = input_file_mime_type.split('/')[1]
# if input file is an image
if input_file_type == 'image':
# if file is a gif, use .gif
if input_file_subtype == 'gif':
suffix = '.gif'
# otherwise, use .png by default for all images
else:
suffix = '.png'
# if input is video, use .mp4 as output by default
elif input_file_type == 'video':
suffix = '.mp4'
# if failed to detect file type
# use input file's suffix
else:
suffix = input_path.suffix
output_path = input_path.parent / f'{input_path.stem}_output{suffix}'
elif input_path.is_dir():
output_path = input_path.parent / f'{input_path.stem}_output'
# try up to 1000 times
output_path_id = 0
while output_path.exists() and output_path_id <= 1000:
if input_path.is_file():
output_path = input_path.parent / pathlib.Path(f'{input_path.stem}_output_{output_path_id}{suffix}')
elif input_path.is_dir():
output_path = input_path.parent / pathlib.Path(f'{input_path.stem}_output_{output_path_id}')
output_path_id += 1
if not output_path.exists():
self.output_line_edit.setText(str(output_path.absolute()))
# generate suffix automatically
input_file_mime_type = magic.from_file(str(input_path.absolute()), mime=True)
input_file_type = input_file_mime_type.split('/')[0]
input_file_subtype = input_file_mime_type.split('/')[1]
# in case python-magic fails to detect file type
# try guessing file mime type with mimetypes
if input_file_type not in ['image', 'video']:
input_file_mime_type = mimetypes.guess_type(input_path.name)[0]
input_file_type = input_file_mime_type.split('/')[0]
input_file_subtype = input_file_mime_type.split('/')[1]
# if input file is an image
if input_file_type == 'image':
# if file is a gif, use .gif
if input_file_subtype == 'gif':
suffix = '.gif'
# otherwise, use .png by default for all images
else:
suffix = '.png'
# if input is video, use .mp4 as output by default
elif input_file_type == 'video':
suffix = '.mp4'
# if failed to detect file type
# use input file's suffix
else:
suffix = input_path.suffix
output_path = input_path.parent / f'{input_path.stem}_output{suffix}'
elif input_path.is_dir():
output_path = input_path.parent / f'{input_path.stem}_output'
# try up to 1000 times
output_path_id = 0
while output_path.exists() and output_path_id <= 1000:
if input_path.is_file():
output_path = input_path.parent / pathlib.Path(f'{input_path.stem}_output_{output_path_id}{suffix}')
elif input_path.is_dir():
output_path = input_path.parent / pathlib.Path(f'{input_path.stem}_output_{output_path_id}')
output_path_id += 1
if not output_path.exists():
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
@@ -880,15 +943,28 @@ class Video2XMainWindow(QMainWindow):
self.config_line_edit.setText(str(config_file.absolute()))
self.load_configurations()
def select_driver_binary_path(self, driver_line_edit):
def select_driver_binary_path(self, driver_line_edit: QLineEdit):
if (driver_binary_path := self.select_file('Select Driver Binary File')) is None:
return
driver_line_edit.setText(str(driver_binary_path.absolute()))
def show_about(self, message: str):
def show_shortcuts(self):
message_box = QMessageBox(self)
message_box.setWindowTitle('Video2X Shortcuts')
message_box.setTextFormat(Qt.MarkdownText)
shortcut_information = '''**Ctrl+W**:\tExit application\\
**Ctrl+Q**:\tExit application\\
**Ctrl+I**:\tOpen select input file dialog\\
**Ctrl+O**:\tOpen select output file dialog\\
**Ctrl+Shift+I**:\tOpen select input folder dialog\\
**Ctrl+Shift+O**:\tOpen select output folder dialog'''
message_box.setText(shortcut_information)
message_box.exec_()
def show_about(self):
message_box = QMessageBox(self)
message_box.setWindowTitle('About Video2X')
message_box.setIconPixmap(QtGui.QPixmap(self.video2x_icon_path).scaled(64, 64))
message_box.setIconPixmap(QPixmap(self.video2x_icon_path).scaled(64, 64))
message_box.setTextFormat(Qt.MarkdownText)
message_box.setText(LEGAL_INFO)
message_box.exec_()
@@ -915,14 +991,14 @@ class Video2XMainWindow(QMainWindow):
message_box.setTextFormat(Qt.MarkdownText)
error_message = '''Upscaler ran into an error:\\
**{}**\\
{}\\
Check the console output for details.\\
When reporting an error, please include console output.\\
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.'''
message_box.setText(error_message.format(exception, urllib.parse.quote(str(exception))))
message_box.exec_()
def progress_monitor(self, progress_callback):
def progress_monitor(self, progress_callback: pyqtSignal):
# initialize progress bar values
upscale_begin_time = time.time()
@@ -976,7 +1052,7 @@ You can [submit an issue on GitHub](https://github.com/k4yt3x/video2x/issues/new
# if show frame is checked, show preview image
if self.frame_preview_show_preview_check_box.isChecked() and last_frame_upscaled.is_file():
last_frame_pixmap = QtGui.QPixmap(str(last_frame_upscaled.absolute()))
last_frame_pixmap = QPixmap(str(last_frame_upscaled.absolute()))
# the -2 here behind geometry subtracts frame size from width and height
self.frame_preview_label.setPixmap(last_frame_pixmap.scaled(self.frame_preview_label.width() - 2,
self.frame_preview_label.height() - 2,
@@ -1093,13 +1169,39 @@ You can [submit an issue on GitHub](https://github.com/k4yt3x/video2x/issues/new
self.reset_progress_display()
def stop(self):
with contextlib.suppress(AttributeError):
self.upscaler.running = False
def closeEvent(self, event):
try:
# if upscaler is running, ask the user for confirmation
if self.upscaler.running is True:
confirmation = QMessageBox.question(self,
'Stopping Confirmation',
'Are you sure you want to want to stop the upscaling process?',
QMessageBox.Yes,
QMessageBox.No)
# if the user indeed wants to stop processing
if confirmation == QMessageBox.Yes:
with contextlib.suppress(AttributeError):
self.upscaler.running = False
return True
# if the user doesn't want ot stop processing
else:
return False
# if the upscaler is not running
else:
return True
# if an AttributeError happens
# that means the upscaler object haven't been created yet
except AttributeError:
return True
def closeEvent(self, event: QCloseEvent):
# try cleaning up temp directories
self.stop()
event.accept()
if self.stop():
event.accept()
else:
event.ignore()
# this file shouldn't be imported

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 4.12.0, 2020-05-14T22:33:50. -->
<!-- Written by QtCreator 4.12.0, 2020-05-23T08:51:47. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>

View File

@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>671</width>
<width>673</width>
<height>802</height>
</rect>
</property>
@@ -270,7 +270,57 @@
<item>
<widget class="QComboBox" name="driverComboBox">
<property name="toolTip">
<string>Driver to use for upscaling. Waifu2x Caffe is only for Nvidia GPUs.</string>
<string>&lt;p style=&quot;-qt-block-indent: 0; text-indent: 0px; margin: 0px;&quot;&gt;&lt;span style=&quot;font-size: 8pt;&quot;&gt;Driver to use for
upscaling. &lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li style=&quot;-qt-block-indent: 0; text-indent: 0px; margin: 0px;&quot;&gt;&lt;span style=&quot;font-size: 8pt;&quot;&gt;Waifu2X Caffe&lt;/span&gt;
&lt;ul&gt;
&lt;li style=&quot;-qt-block-indent: 0; text-indent: 0px; margin: 0px;&quot;&gt;&lt;span
style=&quot;font-size: 8pt; font-weight: 600;&quot;&gt;requires Nvidia GPU and CUDA/cuDNN&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;-qt-block-indent: 0; text-indent: 0px; margin: 0px;&quot;&gt;&lt;span style=&quot;font-size: 8pt;&quot;&gt;Caffe
implementation of waifu2x which is classic and stable&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;-qt-block-indent: 0; text-indent: 0px; margin: 0px;&quot;&gt;&lt;span style=&quot;font-size: 8pt;&quot;&gt;Lots of models
available&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;-qt-block-indent: 0; text-indent: 0px; margin: 0px;&quot;&gt;&lt;span style=&quot;font-size: 8pt;&quot;&gt;Waifu2X Converter
CPP&lt;/span&gt;
&lt;ul&gt;
&lt;li style=&quot;-qt-block-indent: 0; text-indent: 0px; margin: 0px;&quot;&gt;&lt;span style=&quot;font-size: 8pt;&quot;&gt;CPP
implementation of waifu2x&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;-qt-block-indent: 0; text-indent: 0px; margin: 0px;&quot;&gt;&lt;span style=&quot;font-size: 8pt;&quot;&gt;Uses OpenCL
and OpenCV for graphical processing&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;-qt-block-indent: 0; text-indent: 0px; margin: 0px;&quot;&gt;&lt;span style=&quot;font-size: 8pt;&quot;&gt;Waifu2X NCNN
Vulkan&lt;/span&gt;
&lt;ul&gt;
&lt;li style=&quot;-qt-block-indent: 0; text-indent: 0px; margin: 0px;&quot;&gt;&lt;span style=&quot;font-size: 8pt;&quot;&gt;NCNN
implementation of waifu2x&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;-qt-block-indent: 0; text-indent: 0px; margin: 0px;&quot;&gt;&lt;span style=&quot;font-size: 8pt;&quot;&gt;Uses Vulkan
API for graphical processing&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;-qt-block-indent: 0; text-indent: 0px; margin: 0px;&quot;&gt;&lt;span style=&quot;font-size: 8pt;&quot;&gt;SRMD NCNN
Vulkan&lt;/span&gt;
&lt;ul&gt;
&lt;li style=&quot;-qt-block-indent: 0; text-indent: 0px; margin: 0px;&quot;&gt;&lt;span style=&quot;font-size: 8pt;&quot;&gt;NCNN
implementation of SRMD&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;-qt-block-indent: 0; text-indent: 0px; margin: 0px;&quot;&gt;&lt;span style=&quot;font-size: 8pt;&quot;&gt;Uses Vulkan
API for graphical processing&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;-qt-block-indent: 0; text-indent: 0px; margin: 0px;&quot;&gt;&lt;span style=&quot;font-size: 8pt;&quot;&gt;Anime4KCPP&lt;/span&gt;
&lt;ul&gt;
&lt;li style=&quot;-qt-block-indent: 0; text-indent: 0px; margin: 0px;&quot;&gt;&lt;span style=&quot;font-size: 8pt;&quot;&gt;CPP
implementation of Anime4K&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;-qt-block-indent: 0; text-indent: 0px; margin: 0px;&quot;&gt;&lt;span style=&quot;font-size: 8pt;&quot;&gt;Very fast but
low quality&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;-qt-block-indent: 0; text-indent: 0px; margin: 0px;&quot;&gt;&lt;span style=&quot;font-size: 8pt;&quot;&gt;Multithreading
is preferred&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</string>
</property>
<item>
<property name="text">
@@ -1657,7 +1707,7 @@
<item>
<widget class="QTabWidget" name="ffmpegSettingsTabWidget">
<property name="currentIndex">
<number>0</number>
<number>2</number>
</property>
<widget class="QWidget" name="ffmpegGlobalOptionsTab">
<attribute name="title">
@@ -1879,7 +1929,7 @@
</sizepolicy>
</property>
<property name="text">
<string>yuv444p10le</string>
<string>yuv420p</string>
</property>
</widget>
</item>
@@ -1906,6 +1956,64 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="ffmpegAssembleVideoOutputOptionsTuneHorizontalLayout">
<item>
<widget class="QLabel" name="ffmpegAssembleVideoOutputOptionsTuneLabel">
<property name="text">
<string>H.264/H.265 Tune (-tune)</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="ffmpegAssembleVideoOutputOptionsTuneComboBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;- film (x264 only): use for high quality movie content; lowers deblocking&lt;/p&gt;&lt;p&gt;- animation (x264 only): good for cartoons; uses higher deblocking and more reference frames&lt;/p&gt;&lt;p&gt;- grain: preserves the grain structure in old, grainy film material&lt;/p&gt;&lt;p&gt;- stillimage: (x264 only) good for slideshow-like content&lt;/p&gt;&lt;p&gt;- fastdecode: allows faster decoding by disabling certain filters&lt;/p&gt;&lt;p&gt;- zerolatency: good for fast encoding and low-latency streaming&lt;/p&gt;&lt;p&gt;- psnr: ignore this as it is only used for codec development&lt;/p&gt;&lt;p&gt;- ssim: ignore this as it is only used for codec development &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<item>
<property name="text">
<string>animation</string>
</property>
</item>
<item>
<property name="text">
<string>film</string>
</property>
</item>
<item>
<property name="text">
<string>grain</string>
</property>
</item>
<item>
<property name="text">
<string>stillimage</string>
</property>
</item>
<item>
<property name="text">
<string>fastdecode</string>
</property>
</item>
<item>
<property name="text">
<string>zerolatency</string>
</property>
</item>
<item>
<property name="text">
<string>psnr</string>
</property>
</item>
<item>
<property name="text">
<string>ssim</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="ffmpegAssembleVideoOutputOptionsBitrateHorizontalLayout">
<item>
@@ -1933,7 +2041,7 @@
<item>
<widget class="QCheckBox" name="ffmpegAssembleVideoOutputOptionsEnsureDivisibleCheckBox">
<property name="text">
<string>Ensure output width and height are divisible by 2</string>
<string>Ensure output width and height are divisible by 2 (-vf: &quot;pad=ceil(iw/2)*2:ceil(ih/2)*2&quot;)</string>
</property>
<property name="checked">
<bool>true</bool>
@@ -2070,9 +2178,9 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_32">
<item>
<layout class="QHBoxLayout" name="ffmpegMigrateStreamsOutputOptionsOtherPixelFormatHorizontalLayout">
<layout class="QHBoxLayout" name="ffmpegMigrateStreamsOutputOptionsPixelFormatHorizontalLayout">
<item>
<widget class="QLabel" name="ffmpegMigrateStreamsOutputOptionsOtherPixelFormatLabel">
<widget class="QLabel" name="ffmpegMigrateStreamsOutputOptionsPixelFormatLabel">
<property name="text">
<string>Pixel Format (-pix_fmt)</string>
</property>
@@ -2084,9 +2192,30 @@
</layout>
</item>
<item>
<widget class="QCheckBox" name="ffmpegMigrateStreamsOutputOptionsCopyCodecCheckBox">
<layout class="QHBoxLayout" name="ffmpegMigrateStreamsOutputOptionsFrameInterpolationHorizontalLayout">
<item>
<widget class="QLabel" name="ffmpegMigrateStreamsOutputOptionsFrameInterpolationLabel">
<property name="text">
<string>Frame Interpolation (-filter &quot;minterpolate='fps=n'&quot;)</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="ffmpegMigrateStreamsOutputOptionsFrameInterpolationSpinBox">
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="ffmpegMigrateStreamsOutputOptionsCopyStreamsCheckBox">
<property name="toolTip">
<string>Copy streams without re-encoding</string>
</property>
<property name="text">
<string>Copy codec</string>
<string>Copy streams (-c copy)</string>
</property>
<property name="checked">
<bool>true</bool>
@@ -2094,9 +2223,9 @@
</widget>
</item>
<item>
<widget class="QCheckBox" name="ffmpegMigrateStreamsOutputOptionsOtherCopyKnownMetadataTagsCheckBox">
<widget class="QCheckBox" name="ffmpegMigrateStreamsOutputOptionsCopyKnownMetadataTagsCheckBox">
<property name="text">
<string>Copy known metadata tags</string>
<string>Copy known metadata tags (-map_metadata 0)</string>
</property>
<property name="checked">
<bool>true</bool>
@@ -2104,9 +2233,9 @@
</widget>
</item>
<item>
<widget class="QCheckBox" name="ffmpegMigrateStreamsOutputOptionsOtherCopyArbitraryMetadataTagsCheckBox">
<widget class="QCheckBox" name="ffmpegMigrateStreamsOutputOptionsCopyArbitraryMetadataTagsCheckBox">
<property name="text">
<string>Copy arbitrary metadata tags</string>
<string>Copy arbitrary metadata tags (-movflags use_metadata_tags)</string>
</property>
<property name="checked">
<bool>true</bool>
@@ -2114,7 +2243,7 @@
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<spacer name="ffmpegMigrateStreamsOutputOptionsOtherVerticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
@@ -2511,7 +2640,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>671</width>
<width>673</width>
<height>21</height>
</rect>
</property>
@@ -2525,6 +2654,7 @@
<property name="title">
<string>Help</string>
</property>
<addaction name="actionShortcuts"/>
<addaction name="actionAbout"/>
</widget>
<addaction name="menuFile"/>
@@ -2541,6 +2671,11 @@
<string>About</string>
</property>
</action>
<action name="actionShortcuts">
<property name="text">
<string>Shortcuts</string>
</property>
</action>
</widget>
<resources/>
<connections/>