mirror of
https://github.com/k4yt3x/video2x.git
synced 2026-02-10 14:54:46 +08:00
Compare commits
31 Commits
5.0.0-beta
...
5.0.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f17d75539c | ||
|
|
82512ef10c | ||
|
|
ad479e53b8 | ||
|
|
a356bfeaff | ||
|
|
dee8e23485 | ||
|
|
1fa0821057 | ||
|
|
b5ecffba81 | ||
|
|
672c9b8652 | ||
|
|
9f73e75f17 | ||
|
|
ef1a8f3e41 | ||
|
|
04f409ef80 | ||
|
|
51c8693dce | ||
|
|
bb572e2468 | ||
|
|
c07fafc0e9 | ||
|
|
595b179d3c | ||
|
|
c865d494a1 | ||
|
|
f4acb2188d | ||
|
|
01d4006c75 | ||
|
|
a7f0f34751 | ||
|
|
b6b1bf9f0e | ||
|
|
5d7a53a2fc | ||
|
|
b32e0ec132 | ||
|
|
6378a36d91 | ||
|
|
0d0fd70a24 | ||
|
|
7c0e9c45d8 | ||
|
|
7b60041529 | ||
|
|
6ffd6282e0 | ||
|
|
c37e7f0d72 | ||
|
|
ca1e593874 | ||
|
|
bfb0f339e2 | ||
|
|
3690337092 |
61
.github/workflows/release.yml
vendored
Normal file
61
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
name: Release
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- "*"
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
name: Setup
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
tag: ${{ steps.get_tag.outputs.tag }}
|
||||
steps:
|
||||
- name: Get tag
|
||||
id: get_tag
|
||||
run: echo ::set-output name=tag::${GITHUB_REF/refs\/tags\//}
|
||||
|
||||
create-release:
|
||||
name: Create release
|
||||
needs:
|
||||
- setup
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
steps:
|
||||
- name: Create release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ needs.setup.outputs.tag }}
|
||||
release_name: Video2X ${{ needs.setup.outputs.tag }}
|
||||
draft: true
|
||||
prerelease: false
|
||||
|
||||
container:
|
||||
name: Build and upload container
|
||||
needs:
|
||||
- setup
|
||||
- create-release
|
||||
strategy:
|
||||
matrix:
|
||||
version:
|
||||
- slim-alpine
|
||||
- cuda
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
- uses: mr-smithers-excellent/docker-build-push@v5
|
||||
name: Build & push Docker image
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ secrets.GHCR_USER }}
|
||||
password: ${{ secrets.GHCR_TOKEN }}
|
||||
dockerfile: Dockerfile.${{ matrix.version }}
|
||||
image: video2x
|
||||
tags: latest, ${{ needs.setup.outputs.tag }}-${{ matrix.version }}
|
||||
@@ -1,7 +1,7 @@
|
||||
# Name: Video2X Dockerfile (CUDA)
|
||||
# Creator: K4YT3X
|
||||
# Date Created: February 3, 2022
|
||||
# Last Modified: February 4, 2022
|
||||
# Last Modified: February 12, 2022
|
||||
|
||||
# stage 1: build the python components into wheels
|
||||
FROM docker.io/nvidia/cuda:11.6.0-runtime-ubuntu20.04 AS builder
|
||||
@@ -11,7 +11,7 @@ COPY . /video2x
|
||||
WORKDIR /video2x
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
python3-pip python3-opencv python3-pil python3-tqdm \
|
||||
python3-pip python3-opencv python3-pil \
|
||||
python3-dev libvulkan-dev glslang-dev glslang-tools \
|
||||
build-essential swig git \
|
||||
&& git config --global http.postBuffer 1048576000 \
|
||||
@@ -33,7 +33,7 @@ COPY . /video2x
|
||||
WORKDIR /video2x
|
||||
RUN apt-get install -y --no-install-recommends \
|
||||
python3-pip python3-dev \
|
||||
python3-opencv python3-pil python3-tqdm \
|
||||
python3-opencv python3-pil \
|
||||
mesa-vulkan-drivers ffmpeg \
|
||||
&& pip install --no-cache-dir --no-index -f /wheels . \
|
||||
&& apt-get clean \
|
||||
@@ -18,7 +18,7 @@ RUN apk add --no-cache \
|
||||
|
||||
# stage 2: install wheels into final image
|
||||
FROM docker.io/library/python:3.10.2-alpine3.15
|
||||
LABEL maintainer="K4YT3X <i@k4yt3x.com>"
|
||||
LABEL maintainer="K4YT3X <i@k4yt3x.com>" \
|
||||
org.opencontainers.image.source="https://github.com/k4yt3x/video2x" \
|
||||
org.opencontainers.image.description="A lossless video/GIF/image upscaler"
|
||||
|
||||
5
NOTICE
5
NOTICE
@@ -41,8 +41,3 @@ https://github.com/python-pillow/Pillow.
|
||||
This product depends on Rich, which is available under
|
||||
the MIT License. The source code can be found at
|
||||
https://github.com/Textualize/rich.
|
||||
|
||||
This product depends on tqdm, which is available under
|
||||
the Mozilla Public License Version 2.0 and the MIT License.
|
||||
The source code can be found at
|
||||
https://github.com/tqdm/tqdm.
|
||||
|
||||
48
README.md
48
README.md
@@ -8,7 +8,7 @@
|
||||
<img src="https://img.shields.io/badge/dynamic/json?color=%23e85b46&label=Patreon&query=data.attributes.patron_count&suffix=%20patrons&url=https%3A%2F%2Fwww.patreon.com%2Fapi%2Fcampaigns%2F4507807&style=flat-square"/>
|
||||
</p>
|
||||
|
||||
### Official [Telegram Discussion Group](https://t.me/video2x)
|
||||
## [💬 Telegram Discussion Group](https://t.me/video2x)
|
||||
|
||||
Join our Telegram discussion group to ask any questions you have about Video2X, chat directly with the developers, or discuss about upscaling technologies and the future of Video2X in general.
|
||||
|
||||
@@ -18,7 +18,7 @@ The latest Windows update is built based on version 4.8.1. GUI is not available
|
||||
|
||||
## [📔 Google Colab](https://colab.research.google.com/drive/1gWEwcA9y57EsxwOjmLNmNMXPsafw0kGo)
|
||||
|
||||
You can use Video2X on [Google Colab](https://colab.research.google.com/) **for free** if you don't have a powerful GPU of your own. You can borrow a powerful GPU (Tesla K80, T4, P4, or P100) on Google's server for free for a maximum of 12 hours per session. **Please use the free resource fairly** and do not create sessions back-to-back and run upscaling 24/7. This might result in you getting banned. You can get [Colab Pro/Pro+](https://colab.research.google.com/signup/pricing) if you'd like to use better GPUs and get longer runtimes. Usage instructions are embedded in the [Colab Notebook](https://github.com/k4yt3x/video2x/actions/workflows/ci.yml).
|
||||
You can use Video2X on [Google Colab](https://colab.research.google.com/) **for free** if you don't have a powerful GPU of your own. You can borrow a powerful GPU (Tesla K80, T4, P4, or P100) on Google's server for free for a maximum of 12 hours per session. **Please use the free resource fairly** and do not create sessions back-to-back and run upscaling 24/7. This might result in you getting banned. You can get [Colab Pro/Pro+](https://colab.research.google.com/signup/pricing) if you'd like to use better GPUs and get longer runtimes. Usage instructions are embedded in the [Colab Notebook](https://colab.research.google.com/drive/1gWEwcA9y57EsxwOjmLNmNMXPsafw0kGo).
|
||||
|
||||
## [🌙 Download Nightly Releases](https://github.com/k4yt3x/video2x/actions/workflows/ci.yml)
|
||||
|
||||
@@ -26,11 +26,11 @@ Nightly releases are automatically created by the GitHub Actions CI/CD pipelines
|
||||
|
||||
## [📦 Container Image](https://github.com/k4yt3x/video2x/pkgs/container/video2x)
|
||||
|
||||
Video2X container images are available on the GitHub Container Registry for easy deployment on Linux and macOS. If you already have Docker/Podman installed, only one command is needed to start upscaling a video. For more information on how to use Video2X's Docker image, please refer to the [documentations (outdated)](https://github.com/K4YT3X/video2x/wiki/Docker).
|
||||
Video2X container images are available on the GitHub Container Registry for easy deployment on Linux and macOS. If you already have Docker/Podman installed, only one command is needed to start upscaling a video. For more information on how to use Video2X's Docker image, please refer to the [documentations](https://github.com/K4YT3X/video2x/wiki/Container).
|
||||
|
||||
## [📖 Documentations](https://github.com/k4yt3x/video2x/wiki)
|
||||
|
||||
Video2X's documentations are hosted on this repository's [Wiki page](https://github.com/k4yt3x/video2x/wiki). It includes comprehensive explanations for how to use the [GUI](https://github.com/k4yt3x/video2x/wiki/GUI), the [CLI](https://github.com/k4yt3x/video2x/wiki/CLI), the [container image](https://github.com/K4YT3X/video2x/wiki/Container), the library, and more. The Wiki is open to edits by the community, so you, yes you, can also correct errors or add new contents to the documentations.
|
||||
Video2X's documentations are hosted on this repository's [Wiki page](https://github.com/k4yt3x/video2x/wiki). It includes comprehensive explanations for how to use the [GUI](https://github.com/k4yt3x/video2x/wiki/GUI), the [CLI](https://github.com/k4yt3x/video2x/wiki/CLI), the [container image](https://github.com/K4YT3X/video2x/wiki/Container), the [library](https://github.com/k4yt3x/video2x/wiki/Library), and more. The Wiki is open to edits by the community, so you, yes you, can also correct errors or add new contents to the documentations.
|
||||
|
||||
## Introduction
|
||||
|
||||
@@ -82,29 +82,29 @@ Copyright (c) 2018-2022 K4YT3X and contributors.
|
||||
|
||||
This project includes or depends on these following projects:
|
||||
|
||||
| Project | License |
|
||||
| ------------------------------------------------------------------- | -------------------- |
|
||||
| [FFmpeg](https://www.ffmpeg.org/) | LGPLv2.1, GPLv2 |
|
||||
| [waifu2x-ncnn-vulkan](https://github.com/nihui/waifu2x-ncnn-vulkan) | MIT License |
|
||||
| [srmd-ncnn-vulkan](https://github.com/nihui/srmd-ncnn-vulkan) | MIT License |
|
||||
| [realsr-ncnn-vulkan](https://github.com/nihui/realsr-ncnn-vulkan) | MIT License |
|
||||
| [rife-ncnn-vulkan](https://github.com/nihui/rife-ncnn-vulkan) | MIT License |
|
||||
| [ffmpeg-python](https://github.com/kkroening/ffmpeg-python) | Apache-2.0 |
|
||||
| [Loguru](https://github.com/Delgan/loguru) | MIT License |
|
||||
| [opencv-python](https://github.com/opencv/opencv-python) | MIT License |
|
||||
| [Pillow](https://github.com/python-pillow/Pillow) | HPND License |
|
||||
| [Rich](https://github.com/Textualize/rich) | MIT License |
|
||||
| [tqdm](https://github.com/tqdm/tqdm) | MPLv2.0, MIT License |
|
||||
| Project | License |
|
||||
| ------------------------------------------------------------------- | --------------- |
|
||||
| [FFmpeg](https://www.ffmpeg.org/) | LGPLv2.1, GPLv2 |
|
||||
| [waifu2x-ncnn-vulkan](https://github.com/nihui/waifu2x-ncnn-vulkan) | MIT License |
|
||||
| [srmd-ncnn-vulkan](https://github.com/nihui/srmd-ncnn-vulkan) | MIT License |
|
||||
| [realsr-ncnn-vulkan](https://github.com/nihui/realsr-ncnn-vulkan) | MIT License |
|
||||
| [rife-ncnn-vulkan](https://github.com/nihui/rife-ncnn-vulkan) | MIT License |
|
||||
| [ffmpeg-python](https://github.com/kkroening/ffmpeg-python) | Apache-2.0 |
|
||||
| [Loguru](https://github.com/Delgan/loguru) | MIT License |
|
||||
| [opencv-python](https://github.com/opencv/opencv-python) | MIT License |
|
||||
| [Pillow](https://github.com/python-pillow/Pillow) | HPND License |
|
||||
| [Rich](https://github.com/Textualize/rich) | MIT License |
|
||||
|
||||
Legacy versions of this project includes or depends on these following projects:
|
||||
|
||||
| Project | License |
|
||||
| --------------------------------------------------------------------------- | ----------- |
|
||||
| [waifu2x-caffe](https://github.com/lltcggie/waifu2x-caffe) | MIT License |
|
||||
| [waifu2x-converter-cpp](https://github.com/DeadSix27/waifu2x-converter-cpp) | MIT License |
|
||||
| [Anime4K](https://github.com/bloc97/Anime4K) | MIT License |
|
||||
| [Anime4KCPP](https://github.com/TianZerL/Anime4KCPP) | MIT License |
|
||||
| [Gifski](https://github.com/ImageOptim/gifski) | AGPLv3 |
|
||||
| Project | License |
|
||||
| --------------------------------------------------------------------------- | -------------------- |
|
||||
| [waifu2x-caffe](https://github.com/lltcggie/waifu2x-caffe) | MIT License |
|
||||
| [waifu2x-converter-cpp](https://github.com/DeadSix27/waifu2x-converter-cpp) | MIT License |
|
||||
| [Anime4K](https://github.com/bloc97/Anime4K) | MIT License |
|
||||
| [Anime4KCPP](https://github.com/TianZerL/Anime4KCPP) | MIT License |
|
||||
| [Gifski](https://github.com/ImageOptim/gifski) | AGPLv3 |
|
||||
| [tqdm](https://github.com/tqdm/tqdm) | MPLv2.0, MIT License |
|
||||
|
||||
More licensing information can be found in the [NOTICES](NOTICES) file.
|
||||
|
||||
|
||||
10
examples/container-run-interpolate-rife.sh
Executable file
10
examples/container-run-interpolate-rife.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
sudo podman run \
|
||||
-it --rm --gpus all -v /dev/dri:/dev/dri \
|
||||
-v $PWD/data:/host \
|
||||
ghcr.io/k4yt3x/video2x:5.0.0-beta3-cuda \
|
||||
-i input.mp4 -o output.mp4 \
|
||||
interpolate
|
||||
11
examples/container-run-upscale-waifu2x.sh
Executable file
11
examples/container-run-upscale-waifu2x.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
sudo podman run \
|
||||
-it --rm --gpus all -v /dev/dri:/dev/dri \
|
||||
-v $PWD/data:/host \
|
||||
ghcr.io/k4yt3x/video2x:5.0.0-beta3-cuda \
|
||||
-i input.mp4 -o output.mp4 \
|
||||
-p5 upscale \
|
||||
-h 720 -a waifu2x -n3
|
||||
21
examples/run_interpolate_rife.py
Executable file
21
examples/run_interpolate_rife.py
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# built-in imports
|
||||
import pathlib
|
||||
|
||||
# import video2x
|
||||
from video2x import Video2X
|
||||
|
||||
|
||||
# create video2x object
|
||||
video2x = Video2X()
|
||||
|
||||
# run upscale
|
||||
video2x.interpolate(
|
||||
pathlib.Path("input.mp4"), # input video path
|
||||
pathlib.Path("output.mp4"), # another
|
||||
3, # processes: number of parallel processors
|
||||
10, # threshold: adjacent frames with > n% diff won't be processed (100 == process all)
|
||||
"rife", # algorithm: the algorithm to use to process the video
|
||||
)
|
||||
24
examples/run_upscale_waifu2x.py
Executable file
24
examples/run_upscale_waifu2x.py
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# built-in imports
|
||||
import pathlib
|
||||
|
||||
# import video2x
|
||||
from video2x import Video2X
|
||||
|
||||
|
||||
# create video2x object
|
||||
video2x = Video2X()
|
||||
|
||||
# run upscale
|
||||
video2x.upscale(
|
||||
pathlib.Path("input.mp4"), # input video path
|
||||
pathlib.Path("output.mp4"), # another
|
||||
None, # width: width of output, None == auto
|
||||
720, # height: height of output, None == auto
|
||||
3, # noise: noise level, algorithm-dependent
|
||||
5, # processes: number of parallel processors
|
||||
0, # threshold: adjacent frames with < n% diff won't be processed (0 == process all)
|
||||
"waifu2x", # algorithm: the algorithm to use to process the video
|
||||
)
|
||||
@@ -1,49 +0,0 @@
|
||||
`tqdm` is a product of collaborative work.
|
||||
Unless otherwise stated, all authors (see commit logs) retain copyright
|
||||
for their respective work, and release the work under the MIT licence
|
||||
(text below).
|
||||
|
||||
Exceptions or notable authors are listed below
|
||||
in reverse chronological order:
|
||||
|
||||
* files: *
|
||||
MPLv2.0 2015-2021 (c) Casper da Costa-Luis
|
||||
[casperdcl](https://github.com/casperdcl).
|
||||
* files: tqdm/_tqdm.py
|
||||
MIT 2016 (c) [PR #96] on behalf of Google Inc.
|
||||
* files: tqdm/_tqdm.py setup.py README.rst MANIFEST.in .gitignore
|
||||
MIT 2013 (c) Noam Yorav-Raphael, original author.
|
||||
|
||||
[PR #96]: https://github.com/tqdm/tqdm/pull/96
|
||||
|
||||
|
||||
Mozilla Public Licence (MPL) v. 2.0 - Exhibit A
|
||||
-----------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the
|
||||
Mozilla Public License, v. 2.0.
|
||||
If a copy of the MPL was not distributed with this project,
|
||||
You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
|
||||
MIT License (MIT)
|
||||
-----------------
|
||||
|
||||
Copyright (c) 2013 noamraph
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,3 +1,3 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=44", "wheel", "setuptools_scm[toml]>=3.4.3"]
|
||||
requires = ["setuptools>=46.4", "wheel", "setuptools_scm[toml]>=3.4.3"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
19
scripts/run-interactive-container.sh
Executable file
19
scripts/run-interactive-container.sh
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/bin/sh
|
||||
# mount the current (video2x repo root) directory into a container
|
||||
# with drivers installed so the code can be debugged in the container
|
||||
# this one launches an interactive shell instead of Python
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
sudo podman run -it --rm \
|
||||
--gpus all -v /dev/dri:/dev/dri \
|
||||
-v $PWD:/host \
|
||||
-m 15g \
|
||||
--cpus 0.9 \
|
||||
-v $HOME/projects/media2x/video2x:/video2x \
|
||||
-e PYTHONPATH=/video2x \
|
||||
-e PYTHONDONTWRITEBYTECODE=1 \
|
||||
--entrypoint=/bin/bash \
|
||||
ghcr.io/k4yt3x/video2x:5.0.0-beta4-cuda
|
||||
|
||||
# alias upscale='python3 -m video2x -i /host/input-large.mp4 -o /host/output-large.mp4 -p3 upscale -h 1440 -d waifu2x -n3'
|
||||
19
scripts/run-source-in-container.sh
Executable file
19
scripts/run-source-in-container.sh
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/bin/sh
|
||||
# mount the current (video2x repo root) directory into a container
|
||||
# with drivers installed so the code can be debugged in the container
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
sudo podman run -it --rm \
|
||||
--gpus all -v /dev/dri:/dev/dri \
|
||||
-v $PWD:/host \
|
||||
-m 15g \
|
||||
--cpus 0.9 \
|
||||
-v $HOME/projects/media2x/video2x:/video2x \
|
||||
-e PYTHONPATH=/video2x \
|
||||
-e PYTHONDONTWRITEBYTECODE=1 \
|
||||
ghcr.io/k4yt3x/video2x:5.0.0-beta4-cuda \
|
||||
-i data/input.mp4 -o data/output.mp4 \
|
||||
-p3 \
|
||||
upscale \
|
||||
-h 1440 -a waifu2x -n3
|
||||
@@ -1,7 +1,7 @@
|
||||
# Name: Video2X PyPI setup file
|
||||
# Creator: K4YT3X
|
||||
# Date Created: June 17, 2021
|
||||
# Last Modified: February 11, 2022
|
||||
# Last Modified: February 12, 2022
|
||||
|
||||
# build & publish commands
|
||||
# pip install --user -U setuptools wheel twine build
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
[metadata]
|
||||
name = video2x
|
||||
version = 5.0.0-beta1
|
||||
version = attr: video2x.__version__
|
||||
author = K4YT3X
|
||||
author_email = i@k4yt3x.com
|
||||
license = GNU Affero General Public License v3.0
|
||||
@@ -39,7 +39,6 @@ install_requires =
|
||||
opencv-python
|
||||
pillow
|
||||
rich
|
||||
tqdm
|
||||
realsr-ncnn-vulkan-python
|
||||
rife-ncnn-vulkan-python
|
||||
srmd-ncnn-vulkan-python
|
||||
|
||||
@@ -19,9 +19,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
Name: Package Init
|
||||
Author: K4YT3X
|
||||
Date Created: July 3, 2021
|
||||
Last Modified: February 11, 2022
|
||||
Last Modified: February 16, 2022
|
||||
"""
|
||||
|
||||
# version assignment has to precede imports to
|
||||
# prevent setup.cfg from producing import errors
|
||||
__version__ = "5.0.0-beta4"
|
||||
|
||||
# local imports
|
||||
from .video2x import Video2X
|
||||
from .upscaler import Upscaler
|
||||
from .interpolator import Interpolator
|
||||
|
||||
@@ -22,6 +22,7 @@ Date Created: July 3, 2021
|
||||
Last Modified: February 11, 2022
|
||||
"""
|
||||
|
||||
# local imports
|
||||
from .video2x import main
|
||||
|
||||
|
||||
|
||||
@@ -19,13 +19,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
Name: Video Decoder
|
||||
Author: K4YT3X
|
||||
Date Created: June 17, 2021
|
||||
Last Modified: June 17, 2021
|
||||
Last Modified: February 16, 2022
|
||||
"""
|
||||
|
||||
# built-in imports
|
||||
import contextlib
|
||||
import os
|
||||
import pathlib
|
||||
import queue
|
||||
import signal
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
@@ -57,7 +59,7 @@ class VideoDecoder(threading.Thread):
|
||||
processing_queue: queue.Queue,
|
||||
processing_settings: tuple,
|
||||
ignore_max_image_pixels=True,
|
||||
):
|
||||
) -> None:
|
||||
threading.Thread.__init__(self)
|
||||
self.running = False
|
||||
self.input_path = input_path
|
||||
@@ -89,7 +91,7 @@ class VideoDecoder(threading.Thread):
|
||||
# stderr=subprocess.DEVNULL,
|
||||
)
|
||||
|
||||
def run(self):
|
||||
def run(self) -> None:
|
||||
self.running = True
|
||||
|
||||
# the index of the frame
|
||||
@@ -119,20 +121,29 @@ class VideoDecoder(threading.Thread):
|
||||
"RGB", (self.input_width, self.input_height), buffer
|
||||
)
|
||||
|
||||
self.processing_queue.put(
|
||||
(
|
||||
frame_index,
|
||||
(previous_image, image),
|
||||
self.processing_settings,
|
||||
)
|
||||
)
|
||||
previous_image = image
|
||||
# keep checking if the running flag is set to False
|
||||
# while waiting to put the next image into the queue
|
||||
while self.running:
|
||||
with contextlib.suppress(queue.Full):
|
||||
self.processing_queue.put(
|
||||
(
|
||||
frame_index,
|
||||
(previous_image, image),
|
||||
self.processing_settings,
|
||||
),
|
||||
timeout=0.1,
|
||||
)
|
||||
break
|
||||
|
||||
previous_image = image
|
||||
frame_index += 1
|
||||
|
||||
# most likely "not enough image data"
|
||||
except ValueError as e:
|
||||
logger.exception(e)
|
||||
|
||||
# ignore queue closed
|
||||
if not "is closed" in str(e):
|
||||
logger.exception(e)
|
||||
break
|
||||
|
||||
# send exceptions into the client connection pipe
|
||||
@@ -141,12 +152,17 @@ class VideoDecoder(threading.Thread):
|
||||
logger.exception(e)
|
||||
break
|
||||
|
||||
# send SIGINT (2) to FFmpeg
|
||||
# this instructs it to finalize and exit
|
||||
if self.decoder.poll() is None:
|
||||
self.decoder.send_signal(signal.SIGTERM)
|
||||
|
||||
# ensure the decoder has exited
|
||||
self.decoder.wait()
|
||||
logger.debug("Decoder thread exiting")
|
||||
logger.info("Decoder thread exiting")
|
||||
|
||||
self.running = False
|
||||
return super().run()
|
||||
|
||||
def stop(self):
|
||||
def stop(self) -> None:
|
||||
self.running = False
|
||||
|
||||
@@ -19,7 +19,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
Name: Video Encoder
|
||||
Author: K4YT3X
|
||||
Date Created: June 17, 2021
|
||||
Last Modified: June 30, 2021
|
||||
Last Modified: February 16, 2022
|
||||
"""
|
||||
|
||||
# built-in imports
|
||||
@@ -61,7 +61,11 @@ class VideoEncoder(threading.Thread):
|
||||
total_frames: int,
|
||||
processed_frames: multiprocessing.managers.ListProxy,
|
||||
processed: multiprocessing.sharedctypes.Synchronized,
|
||||
):
|
||||
copy_audio: bool = True,
|
||||
copy_subtitle: bool = True,
|
||||
copy_data: bool = False,
|
||||
copy_attachments: bool = False,
|
||||
) -> None:
|
||||
threading.Thread.__init__(self)
|
||||
self.running = False
|
||||
self.input_path = input_path
|
||||
@@ -82,31 +86,31 @@ class VideoEncoder(threading.Thread):
|
||||
r=frame_rate,
|
||||
)
|
||||
|
||||
# map additional streams from original file
|
||||
"""
|
||||
# copy additional streams from original file
|
||||
# https://ffmpeg.org/ffmpeg.html#Stream-specifiers-1
|
||||
additional_streams = [
|
||||
# self.original["v?"],
|
||||
self.original["a?"],
|
||||
self.original["s?"],
|
||||
self.original["d?"],
|
||||
self.original["t?"],
|
||||
# self.original["1:v?"],
|
||||
self.original["a?"] if copy_audio is True else None,
|
||||
self.original["s?"] if copy_subtitle is True else None,
|
||||
self.original["d?"] if copy_data is True else None,
|
||||
self.original["t?"] if copy_attachments is True else None,
|
||||
]
|
||||
"""
|
||||
|
||||
# run FFmpeg and produce final output
|
||||
self.encoder = subprocess.Popen(
|
||||
ffmpeg.compile(
|
||||
ffmpeg.output(
|
||||
frames,
|
||||
*[s for s in additional_streams if s is not None],
|
||||
str(self.output_path),
|
||||
pix_fmt="yuv420p",
|
||||
vcodec="libx264",
|
||||
acodec="copy",
|
||||
# acodec="copy",
|
||||
r=frame_rate,
|
||||
crf=17,
|
||||
vsync="1",
|
||||
# map_metadata=1,
|
||||
# metadata="comment=Upscaled with Video2X",
|
||||
map_metadata=1,
|
||||
metadata="comment=Processed with Video2X",
|
||||
)
|
||||
.global_args("-hide_banner")
|
||||
.global_args("-nostats")
|
||||
@@ -123,7 +127,7 @@ class VideoEncoder(threading.Thread):
|
||||
# stderr=subprocess.DEVNULL,
|
||||
)
|
||||
|
||||
def run(self):
|
||||
def run(self) -> None:
|
||||
self.running = True
|
||||
frame_index = 0
|
||||
while self.running and frame_index < self.total_frames:
|
||||
@@ -133,8 +137,12 @@ class VideoEncoder(threading.Thread):
|
||||
time.sleep(0.1)
|
||||
continue
|
||||
|
||||
# send the image to FFmpeg for encoding
|
||||
self.encoder.stdin.write(image.tobytes())
|
||||
|
||||
# remove the image from memory
|
||||
self.processed_frames[frame_index] = None
|
||||
|
||||
with self.processed.get_lock():
|
||||
self.processed.value += 1
|
||||
|
||||
@@ -156,10 +164,10 @@ class VideoEncoder(threading.Thread):
|
||||
|
||||
# wait for process to terminate
|
||||
self.encoder.wait()
|
||||
logger.debug("Encoder thread exiting")
|
||||
logger.info("Encoder thread exiting")
|
||||
|
||||
self.running = False
|
||||
return super().run()
|
||||
|
||||
def stop(self):
|
||||
def stop(self) -> None:
|
||||
self.running = False
|
||||
|
||||
@@ -19,7 +19,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
Name: Interpolator
|
||||
Author: K4YT3X
|
||||
Date Created: May 27, 2021
|
||||
Last Modified: February 2, 2022
|
||||
Last Modified: February 16, 2022
|
||||
"""
|
||||
|
||||
# local imports
|
||||
@@ -38,7 +38,7 @@ from PIL import ImageChops, ImageStat
|
||||
from loguru import logger
|
||||
|
||||
|
||||
DRIVER_CLASSES = {"rife": Rife}
|
||||
ALGORITHM_CLASSES = {"rife": Rife}
|
||||
|
||||
|
||||
class Interpolator(multiprocessing.Process):
|
||||
@@ -46,7 +46,7 @@ class Interpolator(multiprocessing.Process):
|
||||
self,
|
||||
processing_queue: multiprocessing.Queue,
|
||||
processed_frames: multiprocessing.managers.ListProxy,
|
||||
):
|
||||
) -> None:
|
||||
multiprocessing.Process.__init__(self)
|
||||
self.running = False
|
||||
self.processing_queue = processing_queue
|
||||
@@ -54,10 +54,10 @@ class Interpolator(multiprocessing.Process):
|
||||
|
||||
signal.signal(signal.SIGTERM, self._stop)
|
||||
|
||||
def run(self):
|
||||
def run(self) -> None:
|
||||
self.running = True
|
||||
logger.info(f"Interpolator process {self.name} initiating")
|
||||
driver_objects = {}
|
||||
processor_objects = {}
|
||||
while self.running:
|
||||
try:
|
||||
try:
|
||||
@@ -65,7 +65,7 @@ class Interpolator(multiprocessing.Process):
|
||||
(
|
||||
frame_index,
|
||||
(image0, image1),
|
||||
(difference_threshold, driver),
|
||||
(difference_threshold, algorithm),
|
||||
) = self.processing_queue.get(False)
|
||||
except queue.Empty:
|
||||
time.sleep(0.1)
|
||||
@@ -86,13 +86,13 @@ class Interpolator(multiprocessing.Process):
|
||||
# process the interpolation
|
||||
if difference_ratio < difference_threshold:
|
||||
|
||||
# select a driver object with the required settings
|
||||
# select a processor object with the required settings
|
||||
# create a new object if none are available
|
||||
driver_object = driver_objects.get(driver)
|
||||
if driver_object is None:
|
||||
driver_object = DRIVER_CLASSES[driver](0)
|
||||
driver_objects[driver] = driver_object
|
||||
interpolated_image = driver_object.process(image0, image1)
|
||||
processor_object = processor_objects.get(algorithm)
|
||||
if processor_object is None:
|
||||
processor_object = ALGORITHM_CLASSES[algorithm](0)
|
||||
processor_objects[algorithm] = processor_object
|
||||
interpolated_image = processor_object.process(image0, image1)
|
||||
|
||||
# if the difference is greater than threshold
|
||||
# there's a change in camera angle, ignore
|
||||
@@ -116,5 +116,5 @@ class Interpolator(multiprocessing.Process):
|
||||
self.running = False
|
||||
return super().run()
|
||||
|
||||
def _stop(self, _signal_number, _frame):
|
||||
def _stop(self, _signal_number, _frame) -> None:
|
||||
self.running = False
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
# *-ncnn-vulkan driver backends
|
||||
realsr-ncnn-vulkan-python
|
||||
rife-ncnn-vulkan-python
|
||||
srmd-ncnn-vulkan-python
|
||||
waifu2x-ncnn-vulkan-python
|
||||
|
||||
# regular Python packages from PyPI
|
||||
ffmpeg-python
|
||||
loguru
|
||||
opencv-python
|
||||
pillow
|
||||
rich
|
||||
tqdm
|
||||
@@ -19,7 +19,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
Name: Upscaler
|
||||
Author: K4YT3X
|
||||
Date Created: May 27, 2021
|
||||
Last Modified: August 17, 2021
|
||||
Last Modified: February 16, 2022
|
||||
"""
|
||||
|
||||
# local imports
|
||||
@@ -40,15 +40,15 @@ import time
|
||||
from PIL import Image, ImageChops, ImageStat
|
||||
from loguru import logger
|
||||
|
||||
# fixed scaling ratios supported by the drivers
|
||||
# fixed scaling ratios supported by the algorithms
|
||||
# that only support certain fixed scale ratios
|
||||
DRIVER_FIXED_SCALING_RATIOS = {
|
||||
ALGORITHM_FIXED_SCALING_RATIOS = {
|
||||
"waifu2x": [1, 2],
|
||||
"srmd": [2, 3, 4],
|
||||
"realsr": [4],
|
||||
}
|
||||
|
||||
DRIVER_CLASSES = {"waifu2x": Waifu2x, "srmd": Srmd, "realsr": Realsr}
|
||||
ALGORITHM_CLASSES = {"waifu2x": Waifu2x, "srmd": Srmd, "realsr": Realsr}
|
||||
|
||||
|
||||
class Upscaler(multiprocessing.Process):
|
||||
@@ -56,7 +56,7 @@ class Upscaler(multiprocessing.Process):
|
||||
self,
|
||||
processing_queue: multiprocessing.Queue,
|
||||
processed_frames: multiprocessing.managers.ListProxy,
|
||||
):
|
||||
) -> None:
|
||||
multiprocessing.Process.__init__(self)
|
||||
self.running = False
|
||||
self.processing_queue = processing_queue
|
||||
@@ -64,10 +64,12 @@ class Upscaler(multiprocessing.Process):
|
||||
|
||||
signal.signal(signal.SIGTERM, self._stop)
|
||||
|
||||
def run(self):
|
||||
def run(self) -> None:
|
||||
self.running = True
|
||||
logger.info(f"Upscaler process {self.name} initiating")
|
||||
driver_objects = {}
|
||||
logger.opt(colors=True).info(
|
||||
f"Upscaler process <blue>{self.name}</blue> initiating"
|
||||
)
|
||||
processor_objects = {}
|
||||
while self.running:
|
||||
try:
|
||||
try:
|
||||
@@ -80,7 +82,7 @@ class Upscaler(multiprocessing.Process):
|
||||
output_height,
|
||||
noise,
|
||||
difference_threshold,
|
||||
driver,
|
||||
algorithm,
|
||||
),
|
||||
) = self.processing_queue.get(False)
|
||||
|
||||
@@ -121,9 +123,9 @@ class Upscaler(multiprocessing.Process):
|
||||
# calculate required minimum scale ratio
|
||||
output_scale = max(output_width / width, output_height / height)
|
||||
|
||||
# select the optimal driver scaling ratio to use
|
||||
# select the optimal algorithm scaling ratio to use
|
||||
supported_scaling_ratios = sorted(
|
||||
DRIVER_FIXED_SCALING_RATIOS[driver]
|
||||
ALGORITHM_FIXED_SCALING_RATIOS[algorithm]
|
||||
)
|
||||
|
||||
remaining_scaling_ratio = math.ceil(output_scale)
|
||||
@@ -161,17 +163,17 @@ class Upscaler(multiprocessing.Process):
|
||||
|
||||
for job in scaling_jobs:
|
||||
|
||||
# select a driver object with the required settings
|
||||
# select a processor object with the required settings
|
||||
# create a new object if none are available
|
||||
driver_object = driver_objects.get((driver, job))
|
||||
if driver_object is None:
|
||||
driver_object = DRIVER_CLASSES[driver](
|
||||
processor_object = processor_objects.get((algorithm, job))
|
||||
if processor_object is None:
|
||||
processor_object = ALGORITHM_CLASSES[algorithm](
|
||||
scale=job, noise=noise
|
||||
)
|
||||
driver_objects[(driver, job)] = driver_object
|
||||
processor_objects[(algorithm, job)] = processor_object
|
||||
|
||||
# process the image with the selected driver
|
||||
image1 = driver_object.process(image1)
|
||||
# process the image with the selected algorithm
|
||||
image1 = processor_object.process(image1)
|
||||
|
||||
# downscale the image to the desired output size and save the image to disk
|
||||
image1 = image1.resize((output_width, output_height), Image.LANCZOS)
|
||||
@@ -185,9 +187,11 @@ class Upscaler(multiprocessing.Process):
|
||||
logger.exception(e)
|
||||
break
|
||||
|
||||
logger.info(f"Upscaler process {self.name} terminating")
|
||||
logger.opt(colors=True).info(
|
||||
f"Upscaler process <blue>{self.name}</blue> terminating"
|
||||
)
|
||||
self.running = False
|
||||
return super().run()
|
||||
|
||||
def _stop(self, _signal_number, _frame):
|
||||
def _stop(self, _signal_number, _frame) -> None:
|
||||
self.running = False
|
||||
|
||||
@@ -26,8 +26,8 @@ __ __ _ _ ___ __ __
|
||||
|
||||
Name: Video2X
|
||||
Creator: K4YT3X
|
||||
Date Created: Feb 24, 2018
|
||||
Last Modified: February 8, 2022
|
||||
Date Created: February 24, 2018
|
||||
Last Modified: February 16, 2022
|
||||
|
||||
Editor: BrianPetkovsek
|
||||
Last Modified: June 17, 2019
|
||||
@@ -40,6 +40,7 @@ Last Modified: March 23, 2020
|
||||
"""
|
||||
|
||||
# local imports
|
||||
from . import __version__
|
||||
from .decoder import VideoDecoder
|
||||
from .encoder import VideoEncoder
|
||||
from .interpolator import Interpolator
|
||||
@@ -57,35 +58,59 @@ import time
|
||||
# third-party imports
|
||||
from loguru import logger
|
||||
from rich import print
|
||||
from tqdm import tqdm
|
||||
from rich.console import Console
|
||||
from rich.file_proxy import FileProxy
|
||||
from rich.progress import (
|
||||
BarColumn,
|
||||
Progress,
|
||||
ProgressColumn,
|
||||
Task,
|
||||
TimeElapsedColumn,
|
||||
TimeRemainingColumn,
|
||||
)
|
||||
from rich.text import Text
|
||||
import cv2
|
||||
import ffmpeg
|
||||
|
||||
VERSION = "5.0.0"
|
||||
|
||||
LEGAL_INFO = """Video2X {}
|
||||
Author: K4YT3X
|
||||
License: GNU GPL v3
|
||||
Github Page: https://github.com/k4yt3x/video2x
|
||||
Contact: k4yt3x@k4yt3x.com""".format(
|
||||
VERSION
|
||||
LEGAL_INFO = """Video2X\t\t{}
|
||||
Author:\t\tK4YT3X
|
||||
License:\tGNU AGPL v3
|
||||
Github Page:\thttps://github.com/k4yt3x/video2x
|
||||
Contact:\ti@k4yt3x.com""".format(
|
||||
__version__
|
||||
)
|
||||
|
||||
UPSCALING_DRIVERS = [
|
||||
# algorithms available for upscaling tasks
|
||||
UPSCALING_ALGORITHMS = [
|
||||
"waifu2x",
|
||||
"srmd",
|
||||
"realsr",
|
||||
]
|
||||
|
||||
INTERPOLATION_DRIVERS = ["rife"]
|
||||
# algorithms available for frame interpolation tasks
|
||||
INTERPOLATION_ALGORITHMS = ["rife"]
|
||||
|
||||
# fixed scaling ratios supported by the drivers
|
||||
# that only support certain fixed scale ratios
|
||||
DRIVER_FIXED_SCALING_RATIOS = {
|
||||
"waifu2x": [1, 2],
|
||||
"srmd": [2, 3, 4],
|
||||
"realsr": [4],
|
||||
}
|
||||
# progress bar labels for different modes
|
||||
MODE_LABELS = {"upscale": "Upscaling", "interpolate": "Interpolating"}
|
||||
|
||||
# format string for Loguru loggers
|
||||
LOGURU_FORMAT = (
|
||||
"<green>{time:HH:mm:ss.SSSSSS!UTC}</green> | "
|
||||
"<level>{level: <8}</level> | "
|
||||
"<level>{message}</level>"
|
||||
)
|
||||
|
||||
|
||||
class ProcessingSpeedColumn(ProgressColumn):
|
||||
"""Custom progress bar column that displays the processing speed"""
|
||||
|
||||
def render(self, task: Task) -> Text:
|
||||
speed = task.finished_speed or task.speed
|
||||
return Text(
|
||||
f"{round(speed, 2) if isinstance(speed, float) else '?'} FPS",
|
||||
style="progress.data.speed",
|
||||
)
|
||||
|
||||
|
||||
class Video2X:
|
||||
@@ -97,10 +122,10 @@ class Video2X:
|
||||
- interpolate: perform motion interpolation on a file
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.version = "5.0.0"
|
||||
def __init__(self) -> None:
|
||||
self.version = __version__
|
||||
|
||||
def _get_video_info(self, path: pathlib.Path):
|
||||
def _get_video_info(self, path: pathlib.Path) -> tuple:
|
||||
"""
|
||||
get video file information with FFmpeg
|
||||
|
||||
@@ -142,10 +167,26 @@ class Video2X:
|
||||
mode: str,
|
||||
processes: int,
|
||||
processing_settings: tuple,
|
||||
):
|
||||
) -> None:
|
||||
|
||||
# record original STDOUT and STDERR for restoration
|
||||
original_stdout = sys.stdout
|
||||
original_stderr = sys.stderr
|
||||
|
||||
# create console for rich's Live display
|
||||
console = Console()
|
||||
|
||||
# redirect STDOUT and STDERR to console
|
||||
sys.stdout = FileProxy(console, sys.stdout)
|
||||
sys.stderr = FileProxy(console, sys.stderr)
|
||||
|
||||
# re-add Loguru to point to the new STDERR
|
||||
logger.remove()
|
||||
logger.add(sys.stderr, colorize=True, format=LOGURU_FORMAT)
|
||||
|
||||
# initialize values
|
||||
self.processor_processes = []
|
||||
self.processing_queue = multiprocessing.Queue()
|
||||
self.processing_queue = multiprocessing.Queue(maxsize=processes * 10)
|
||||
processed_frames = multiprocessing.Manager().list([None] * total_frames)
|
||||
self.processed = multiprocessing.Value("I", 0)
|
||||
|
||||
@@ -161,19 +202,11 @@ class Video2X:
|
||||
)
|
||||
self.decoder.start()
|
||||
|
||||
# in interpolate mode, the frame rate is doubled
|
||||
if mode == "upscale":
|
||||
progress = tqdm(total=total_frames, desc=f"UPSC {input_path.name}")
|
||||
# elif mode == "interpolate":
|
||||
else:
|
||||
frame_rate *= 2
|
||||
progress = tqdm(total=total_frames, desc=f"INTERP {input_path.name}")
|
||||
|
||||
# set up and start encoder thread
|
||||
logger.info("Starting video encoder")
|
||||
self.encoder = VideoEncoder(
|
||||
input_path,
|
||||
frame_rate,
|
||||
frame_rate * 2 if mode == "interpolate" else frame_rate,
|
||||
output_path,
|
||||
output_width,
|
||||
output_height,
|
||||
@@ -183,7 +216,7 @@ class Video2X:
|
||||
)
|
||||
self.encoder.start()
|
||||
|
||||
# create upscaler processes
|
||||
# create processor processes
|
||||
for process_name in range(processes):
|
||||
process = Processor(self.processing_queue, processed_frames)
|
||||
process.name = str(process_name)
|
||||
@@ -191,25 +224,58 @@ class Video2X:
|
||||
process.start()
|
||||
self.processor_processes.append(process)
|
||||
|
||||
# create progress bar
|
||||
try:
|
||||
# wait for jobs in queue to deplete
|
||||
while self.encoder.is_alive() is True:
|
||||
for process in self.processor_processes:
|
||||
if not process.is_alive():
|
||||
raise Exception("process died unexpectedly")
|
||||
progress.n = self.processed.value
|
||||
progress.refresh()
|
||||
time.sleep(0.1)
|
||||
# a temporary variable that stores the exception
|
||||
exception = []
|
||||
|
||||
logger.info("Encoding has completed")
|
||||
progress.n = self.processed.value
|
||||
progress.refresh()
|
||||
try:
|
||||
# create progress bar
|
||||
with Progress(
|
||||
"[progress.description]{task.description}",
|
||||
BarColumn(finished_style="green"),
|
||||
"[progress.percentage]{task.percentage:>3.0f}%",
|
||||
"[color(240)]({task.completed}/{task.total})",
|
||||
ProcessingSpeedColumn(),
|
||||
TimeElapsedColumn(),
|
||||
"<",
|
||||
TimeRemainingColumn(),
|
||||
console=console,
|
||||
disable=True,
|
||||
) as progress:
|
||||
task = progress.add_task(
|
||||
f"[cyan]{MODE_LABELS.get(mode, 'Unknown')}", total=total_frames
|
||||
)
|
||||
|
||||
# wait for jobs in queue to deplete
|
||||
while self.processed.value < total_frames - 1:
|
||||
time.sleep(0.5)
|
||||
for process in self.processor_processes:
|
||||
if not process.is_alive():
|
||||
raise Exception("process died unexpectedly")
|
||||
|
||||
# show progress bar when upscale starts
|
||||
if progress.disable is True and self.processed.value > 0:
|
||||
progress.disable = False
|
||||
progress.start()
|
||||
|
||||
# update progress
|
||||
progress.update(task, completed=self.processed.value)
|
||||
|
||||
progress.update(task, completed=total_frames)
|
||||
logger.info("Processing has completed")
|
||||
|
||||
# if SIGTERM is received or ^C is pressed
|
||||
# TODO: pause and continue here
|
||||
except (SystemExit, KeyboardInterrupt):
|
||||
except (SystemExit, KeyboardInterrupt) as e:
|
||||
logger.warning("Exit signal received, terminating")
|
||||
exception.append(e)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
exception.append(e)
|
||||
|
||||
# if no exceptions were produced
|
||||
else:
|
||||
logger.success("Processing completed successfully")
|
||||
|
||||
finally:
|
||||
# mark processing queue as closed
|
||||
@@ -230,6 +296,18 @@ class Video2X:
|
||||
self.decoder.join()
|
||||
self.encoder.join()
|
||||
|
||||
# raise the error if there is any
|
||||
if len(exception) > 0:
|
||||
raise exception[0]
|
||||
|
||||
# restore original STDOUT and STDERR
|
||||
sys.stdout = original_stdout
|
||||
sys.stderr = original_stderr
|
||||
|
||||
# re-add Loguru to point to the restored STDERR
|
||||
logger.remove()
|
||||
logger.add(sys.stderr, colorize=True, format=LOGURU_FORMAT)
|
||||
|
||||
def upscale(
|
||||
self,
|
||||
input_path: pathlib.Path,
|
||||
@@ -239,7 +317,7 @@ class Video2X:
|
||||
noise: int,
|
||||
processes: int,
|
||||
threshold: float,
|
||||
driver: str,
|
||||
algorithm: str,
|
||||
) -> None:
|
||||
|
||||
# get basic video information
|
||||
@@ -274,7 +352,7 @@ class Video2X:
|
||||
output_height,
|
||||
noise,
|
||||
threshold,
|
||||
driver,
|
||||
algorithm,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -284,7 +362,7 @@ class Video2X:
|
||||
output_path: pathlib.Path,
|
||||
processes: int,
|
||||
threshold: float,
|
||||
driver: str,
|
||||
algorithm: str,
|
||||
) -> None:
|
||||
|
||||
# get video basic information
|
||||
@@ -306,7 +384,7 @@ class Video2X:
|
||||
Interpolator,
|
||||
"interpolate",
|
||||
processes,
|
||||
(threshold, driver),
|
||||
(threshold, algorithm),
|
||||
)
|
||||
|
||||
|
||||
@@ -321,7 +399,7 @@ def parse_arguments() -> argparse.Namespace:
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v", "--version", help="show version information and exit", action="store_true"
|
||||
"--version", help="show version information and exit", action="store_true"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-i",
|
||||
@@ -352,7 +430,12 @@ def parse_arguments() -> argparse.Namespace:
|
||||
help="action to perform", dest="action", required=True
|
||||
)
|
||||
|
||||
upscale = action.add_parser("upscale", help="upscale a file", add_help=False)
|
||||
upscale = action.add_parser(
|
||||
"upscale",
|
||||
help="upscale a file",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
add_help=False,
|
||||
)
|
||||
upscale.add_argument(
|
||||
"--help", action="help", help="show this help message and exit"
|
||||
)
|
||||
@@ -360,52 +443,65 @@ def parse_arguments() -> argparse.Namespace:
|
||||
upscale.add_argument("-h", "--height", type=int, help="output height")
|
||||
upscale.add_argument("-n", "--noise", type=int, help="denoise level", default=3)
|
||||
upscale.add_argument(
|
||||
"-d",
|
||||
"--driver",
|
||||
choices=UPSCALING_DRIVERS,
|
||||
help="driver to use for upscaling",
|
||||
default=UPSCALING_DRIVERS[0],
|
||||
"-a",
|
||||
"--algorithm",
|
||||
choices=UPSCALING_ALGORITHMS,
|
||||
help="algorithm to use for upscaling",
|
||||
default=UPSCALING_ALGORITHMS[0],
|
||||
)
|
||||
upscale.add_argument(
|
||||
"-t",
|
||||
"--threshold",
|
||||
type=float,
|
||||
help="skip if the % difference between two adjacent frames is below this value; set to 0 to process all frames",
|
||||
help=(
|
||||
"skip if the percent difference between two adjacent frames is below this"
|
||||
" value; set to 0 to process all frames"
|
||||
),
|
||||
default=0,
|
||||
)
|
||||
|
||||
# interpolator arguments
|
||||
interpolate = action.add_parser(
|
||||
"interpolate", help="interpolate frames for file", add_help=False
|
||||
"interpolate",
|
||||
help="interpolate frames for file",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
add_help=False,
|
||||
)
|
||||
interpolate.add_argument(
|
||||
"--help", action="help", help="show this help message and exit"
|
||||
)
|
||||
interpolate.add_argument(
|
||||
"-d",
|
||||
"--driver",
|
||||
choices=UPSCALING_DRIVERS,
|
||||
help="driver to use for upscaling",
|
||||
default=INTERPOLATION_DRIVERS[0],
|
||||
"-a",
|
||||
"--algorithm",
|
||||
choices=UPSCALING_ALGORITHMS,
|
||||
help="algorithm to use for upscaling",
|
||||
default=INTERPOLATION_ALGORITHMS[0],
|
||||
)
|
||||
interpolate.add_argument(
|
||||
"-t",
|
||||
"--threshold",
|
||||
type=float,
|
||||
help="skip if the % difference between two adjacent frames exceeds this value; set to 100 to interpolate all frames",
|
||||
help=(
|
||||
"skip if the percent difference between two adjacent frames exceeds this"
|
||||
" value; set to 100 to interpolate all frames"
|
||||
),
|
||||
default=10,
|
||||
)
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
"""
|
||||
command line direct invocation
|
||||
program entry point
|
||||
"""
|
||||
|
||||
try:
|
||||
# display version and lawful informaition
|
||||
if "--version" in sys.argv:
|
||||
print(LEGAL_INFO)
|
||||
sys.exit(0)
|
||||
|
||||
# parse command line arguments
|
||||
args = parse_arguments()
|
||||
@@ -414,16 +510,17 @@ def main():
|
||||
if os.environ.get("LOGURU_LEVEL") is None:
|
||||
os.environ["LOGURU_LEVEL"] = args.loglevel.upper()
|
||||
|
||||
# set logger format
|
||||
if os.environ.get("LOGURU_FORMAT") is None:
|
||||
os.environ[
|
||||
"LOGURU_FORMAT"
|
||||
] = "<fg 240>{time:HH:mm:ss!UTC}</fg 240> | <level>{level: <8}</level> | <level>{message}</level>"
|
||||
# remove default handler
|
||||
logger.remove()
|
||||
|
||||
# display version and lawful informaition
|
||||
if args.version:
|
||||
print(LEGAL_INFO)
|
||||
sys.exit(0)
|
||||
# add new sink with custom handler
|
||||
logger.add(sys.stderr, colorize=True, format=LOGURU_FORMAT)
|
||||
|
||||
# print package version and copyright notice
|
||||
logger.opt(colors=True).info(f"<magenta>Video2X {__version__}</magenta>")
|
||||
logger.opt(colors=True).info(
|
||||
"<magenta>Copyright (C) 2018-2022 K4YT3X and contributors.</magenta>"
|
||||
)
|
||||
|
||||
# initialize upscaler object
|
||||
video2x = Video2X()
|
||||
@@ -437,7 +534,7 @@ def main():
|
||||
args.noise,
|
||||
args.processes,
|
||||
args.threshold,
|
||||
args.driver,
|
||||
args.algorithm,
|
||||
)
|
||||
|
||||
elif args.action == "interpolate":
|
||||
@@ -446,7 +543,13 @@ def main():
|
||||
args.output,
|
||||
args.processes,
|
||||
args.threshold,
|
||||
args.driver,
|
||||
args.algorithm,
|
||||
)
|
||||
|
||||
# don't print the traceback for manual terminations
|
||||
except KeyboardInterrupt as e:
|
||||
raise SystemExit(e)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
raise SystemExit(e)
|
||||
|
||||
Reference in New Issue
Block a user