mirror of
https://github.com/k4yt3x/video2x.git
synced 2026-02-15 09:44:46 +08:00
Compare commits
84 Commits
5.0.0-beta
...
5.0.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84b730497b | ||
|
|
db0b87597d | ||
|
|
102340e2be | ||
|
|
85437a8481 | ||
|
|
176ae90bbb | ||
|
|
44238aed35 | ||
|
|
045e643867 | ||
|
|
c92805e7bc | ||
|
|
899fe3ae2d | ||
|
|
a75c2a50ca | ||
|
|
508d6ea4d0 | ||
|
|
8976dd8199 | ||
|
|
29a55e633c | ||
|
|
f7d6dc41b3 | ||
|
|
d236131134 | ||
|
|
d669654142 | ||
|
|
4b0ab5382c | ||
|
|
737646a248 | ||
|
|
9fc0aa787e | ||
|
|
a041a60d87 | ||
|
|
020fb2dc80 | ||
|
|
9a27960bf7 | ||
|
|
862b811517 | ||
|
|
e01d24c164 | ||
|
|
0a052a3a72 | ||
|
|
f3eaa47ec6 | ||
|
|
3f457907b6 | ||
|
|
a1d750e7ca | ||
|
|
22f656b800 | ||
|
|
8eeba71ece | ||
|
|
afca10a17b | ||
|
|
f976bdc1c9 | ||
|
|
51c0c38b34 | ||
|
|
f2b2e11c41 | ||
|
|
865e3bd193 | ||
|
|
e0dc8237f5 | ||
|
|
bbc1b57445 | ||
|
|
ebbe4570d5 | ||
|
|
bcb2e97f89 | ||
|
|
ba29349e65 | ||
|
|
1b67f373d1 | ||
|
|
7215ee1921 | ||
|
|
65c0d32a51 | ||
|
|
268460fd17 | ||
|
|
d72ecb332a | ||
|
|
8ba56e58c2 | ||
|
|
fa485b6cdd | ||
|
|
625b340f3d | ||
|
|
fa3bd38217 | ||
|
|
0fca5c8e9a | ||
|
|
237f6336ec | ||
|
|
083af60c6f | ||
|
|
f7756886e7 | ||
|
|
6e72df66fc | ||
|
|
2bfcb13976 | ||
|
|
49e0375eee | ||
|
|
4459f4d3be | ||
|
|
c0fe81bd2e | ||
|
|
8cb64d3b70 | ||
|
|
0b154a715c | ||
|
|
05031b2b5a | ||
|
|
2510c8fa28 | ||
|
|
f17d75539c | ||
|
|
82512ef10c | ||
|
|
ad479e53b8 | ||
|
|
a356bfeaff | ||
|
|
dee8e23485 | ||
|
|
1fa0821057 | ||
|
|
b5ecffba81 | ||
|
|
672c9b8652 | ||
|
|
9f73e75f17 | ||
|
|
ef1a8f3e41 | ||
|
|
04f409ef80 | ||
|
|
51c8693dce | ||
|
|
bb572e2468 | ||
|
|
c07fafc0e9 | ||
|
|
595b179d3c | ||
|
|
c865d494a1 | ||
|
|
f4acb2188d | ||
|
|
01d4006c75 | ||
|
|
a7f0f34751 | ||
|
|
b6b1bf9f0e | ||
|
|
5d7a53a2fc | ||
|
|
b32e0ec132 |
78
.github/workflows/release.yml
vendored
Normal file
78
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
name: Release
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
setup:
|
||||||
|
if: github.event.base_ref == 'refs/heads/master'
|
||||||
|
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
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- uses: mr-smithers-excellent/docker-build-push@v5
|
||||||
|
name: Build & push the Docker image
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ secrets.GHCR_USER }}
|
||||||
|
password: ${{ secrets.GHCR_TOKEN }}
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: video2x
|
||||||
|
tags: latest, ${{ needs.setup.outputs.tag }}
|
||||||
|
|
||||||
|
container-variants:
|
||||||
|
name: Build and upload variants of the container
|
||||||
|
needs:
|
||||||
|
- setup
|
||||||
|
- create-release
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
version:
|
||||||
|
- slim-alpine
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- uses: mr-smithers-excellent/docker-build-push@v5
|
||||||
|
name: Build & push the Docker image
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ secrets.GHCR_USER }}
|
||||||
|
password: ${{ secrets.GHCR_TOKEN }}
|
||||||
|
dockerfile: Dockerfile.${{ matrix.version }}
|
||||||
|
image: video2x
|
||||||
|
tags: ${{ needs.setup.outputs.tag }}-${{ matrix.version }}
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,3 +1,7 @@
|
|||||||
|
# PDM
|
||||||
|
.pdm.toml
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
# test videos
|
# test videos
|
||||||
*.mp4
|
*.mp4
|
||||||
*.mkv
|
*.mkv
|
||||||
|
|||||||
31
Dockerfile
31
Dockerfile
@@ -1,43 +1,44 @@
|
|||||||
# Name: Video2X Dockerfile (CUDA)
|
# Name: Video2X Dockerfile
|
||||||
# Creator: K4YT3X
|
# Creator: K4YT3X
|
||||||
# Date Created: February 3, 2022
|
# Date Created: February 3, 2022
|
||||||
# Last Modified: February 12, 2022
|
# Last Modified: August 28, 2022
|
||||||
|
|
||||||
# stage 1: build the python components into wheels
|
# stage 1: build the python components into wheels
|
||||||
FROM docker.io/nvidia/cuda:11.6.0-runtime-ubuntu20.04 AS builder
|
FROM docker.io/nvidia/vulkan:1.3-470 AS builder
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
COPY . /video2x
|
COPY . /video2x
|
||||||
WORKDIR /video2x
|
WORKDIR /video2x
|
||||||
|
RUN gpg --keyserver=keyserver.ubuntu.com --receive-keys A4B469963BF863CC \
|
||||||
|
&& gpg --export A4B469963BF863CC > /etc/apt/trusted.gpg.d/cuda.gpg
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y --no-install-recommends \
|
&& apt-get install -y --no-install-recommends \
|
||||||
python3-pip python3-opencv python3-pil \
|
python3.8 python3-pip python3-opencv python3-pil \
|
||||||
python3-dev libvulkan-dev glslang-dev glslang-tools \
|
python3.8-dev libvulkan-dev glslang-dev glslang-tools \
|
||||||
build-essential swig git \
|
build-essential swig \
|
||||||
&& git config --global http.postBuffer 1048576000 \
|
&& pip wheel -w /wheels wheel pdm-pep517 .
|
||||||
&& git config --global https.postBuffer 1048576000 \
|
|
||||||
&& pip wheel -w /wheels \
|
|
||||||
wheel setuptools setuptools_scm \
|
|
||||||
rife-ncnn-vulkan-python@git+https://github.com/media2x/rife-ncnn-vulkan-python.git .
|
|
||||||
|
|
||||||
# stage 2: install wheels into the final image
|
# stage 2: install wheels into the final image
|
||||||
FROM docker.io/nvidia/cuda:11.6.0-runtime-ubuntu20.04
|
FROM docker.io/nvidia/vulkan:1.3-470
|
||||||
LABEL maintainer="K4YT3X <i@k4yt3x.com>" \
|
LABEL maintainer="K4YT3X <i@k4yt3x.com>" \
|
||||||
org.opencontainers.image.source="https://github.com/k4yt3x/video2x" \
|
org.opencontainers.image.source="https://github.com/k4yt3x/video2x" \
|
||||||
org.opencontainers.image.description="A lossless video/GIF/image upscaler"
|
org.opencontainers.image.description="A lossless video/GIF/image upscaler"
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
ENV VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/nvidia_icd.json\
|
||||||
|
:/usr/share/vulkan/icd.d/radeon_icd.x86_64.json\
|
||||||
|
:/usr/share/vulkan/icd.d/intel_icd.x86_64.json
|
||||||
|
|
||||||
COPY --from=builder /var/lib/apt/lists* /var/lib/apt/lists/
|
COPY --from=builder /var/lib/apt/lists* /var/lib/apt/lists/
|
||||||
COPY --from=builder /wheels /wheels
|
COPY --from=builder /wheels /wheels
|
||||||
COPY . /video2x
|
COPY . /video2x
|
||||||
WORKDIR /video2x
|
WORKDIR /video2x
|
||||||
RUN apt-get install -y --no-install-recommends \
|
RUN apt-get install -y --no-install-recommends \
|
||||||
python3-pip python3-dev \
|
python3-pip python3.8-dev \
|
||||||
python3-opencv python3-pil \
|
python3-opencv python3-pil \
|
||||||
mesa-vulkan-drivers ffmpeg \
|
mesa-vulkan-drivers cuda-drivers ffmpeg \
|
||||||
&& pip install --no-cache-dir --no-index -f /wheels . \
|
&& pip install --no-cache-dir --no-index -f /wheels . \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /wheels /video2x /var/lib/apt/lists/*
|
&& rm -rf /wheels /video2x /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /host
|
WORKDIR /host
|
||||||
ENTRYPOINT ["/usr/bin/python3", "-m", "video2x"]
|
ENTRYPOINT ["/usr/bin/python3.8", "-m", "video2x"]
|
||||||
|
|||||||
@@ -1,24 +1,22 @@
|
|||||||
# Name: Video2X Dockerfile (Slim Alpine)
|
# Name: Video2X Dockerfile (Slim Alpine)
|
||||||
# Creator: K4YT3X
|
# Creator: K4YT3X
|
||||||
# Date Created: February 1, 2022
|
# Date Created: February 1, 2022
|
||||||
# Last Modified: February 4, 2022
|
# Last Modified: March 18, 2022
|
||||||
|
|
||||||
# stage: build python components into heels
|
# stage: build python components into heels
|
||||||
FROM docker.io/library/python:3.10.2-alpine3.15 AS builder
|
FROM docker.io/library/python:3.10.4-alpine3.15 AS builder
|
||||||
COPY . /video2x
|
COPY . /video2x
|
||||||
WORKDIR /video2x
|
WORKDIR /video2x
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
cmake g++ gcc git ninja swig \
|
cmake g++ gcc git ninja swig linux-headers \
|
||||||
ffmpeg-dev glslang-dev jpeg-dev vulkan-loader-dev zlib-dev \
|
ffmpeg-dev glslang-dev jpeg-dev vulkan-loader-dev zlib-dev \
|
||||||
linux-headers \
|
|
||||||
&& pip install -U pip \
|
&& pip install -U pip \
|
||||||
&& CMAKE_ARGS="-DWITH_FFMPEG=YES" pip wheel -w /wheels opencv-python \
|
&& CMAKE_ARGS="-DWITH_FFMPEG=YES" pip wheel -w /wheels opencv-python \
|
||||||
&& pip wheel -w /wheels wheel setuptools setuptools_scm \
|
&& pip wheel -w /wheels wheel pdm-pep517 .
|
||||||
rife-ncnn-vulkan-python@git+https://github.com/media2x/rife-ncnn-vulkan-python.git .
|
|
||||||
|
|
||||||
# stage 2: install wheels into final image
|
# stage 2: install wheels into final image
|
||||||
FROM docker.io/library/python:3.10.2-alpine3.15
|
FROM docker.io/library/python:3.10.4-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.source="https://github.com/k4yt3x/video2x" \
|
||||||
org.opencontainers.image.description="A lossless video/GIF/image upscaler"
|
org.opencontainers.image.description="A lossless video/GIF/image upscaler"
|
||||||
|
|
||||||
6
NOTICE
6
NOTICE
@@ -3,7 +3,7 @@ Copyright (c) 2018-2022 K4YT3X and contributors.
|
|||||||
|
|
||||||
This product depends on FFmpeg, which is available under
|
This product depends on FFmpeg, which is available under
|
||||||
the GNU Lesser General Public License 2.1. The source code can be found at
|
the GNU Lesser General Public License 2.1. The source code can be found at
|
||||||
https://www.gnu.org/licenses/agpl-3.0.txt.
|
https://github.com/FFmpeg/FFmpeg.
|
||||||
|
|
||||||
This product depends on waifu2x-ncnn-vulkan, which is available under
|
This product depends on waifu2x-ncnn-vulkan, which is available under
|
||||||
the MIT License. The source code can be found at
|
the MIT License. The source code can be found at
|
||||||
@@ -41,3 +41,7 @@ https://github.com/python-pillow/Pillow.
|
|||||||
This product depends on Rich, which is available under
|
This product depends on Rich, which is available under
|
||||||
the MIT License. The source code can be found at
|
the MIT License. The source code can be found at
|
||||||
https://github.com/Textualize/rich.
|
https://github.com/Textualize/rich.
|
||||||
|
|
||||||
|
This product depends on pynput, which is available under
|
||||||
|
the GNU Lesser General Public License 3.0. The source code can be found at
|
||||||
|
https://github.com/moses-palmer/pynput.
|
||||||
|
|||||||
33
README.md
33
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"/>
|
<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>
|
</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.
|
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.
|
||||||
|
|
||||||
@@ -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)
|
## [📦 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)
|
## [📖 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
|
## Introduction
|
||||||
|
|
||||||
@@ -82,18 +82,20 @@ Copyright (c) 2018-2022 K4YT3X and contributors.
|
|||||||
|
|
||||||
This project includes or depends on these following projects:
|
This project includes or depends on these following projects:
|
||||||
|
|
||||||
| Project | License |
|
| Project | License |
|
||||||
| ------------------------------------------------------------------- | --------------- |
|
| ----------------------------------------------------------------------- | --------------- |
|
||||||
| [FFmpeg](https://www.ffmpeg.org/) | LGPLv2.1, GPLv2 |
|
| [FFmpeg](https://www.ffmpeg.org/) | LGPLv2.1, GPLv2 |
|
||||||
| [waifu2x-ncnn-vulkan](https://github.com/nihui/waifu2x-ncnn-vulkan) | MIT License |
|
| [waifu2x-ncnn-vulkan](https://github.com/nihui/waifu2x-ncnn-vulkan) | MIT License |
|
||||||
| [srmd-ncnn-vulkan](https://github.com/nihui/srmd-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 |
|
| [realsr-ncnn-vulkan](https://github.com/nihui/realsr-ncnn-vulkan) | MIT License |
|
||||||
| [rife-ncnn-vulkan](https://github.com/nihui/rife-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 |
|
| [realcugan-ncnn-vulkan](https://github.com/nihui/realcugan-ncnn-vulkan) | MIT License |
|
||||||
| [Loguru](https://github.com/Delgan/loguru) | MIT License |
|
| [ffmpeg-python](https://github.com/kkroening/ffmpeg-python) | Apache-2.0 |
|
||||||
| [opencv-python](https://github.com/opencv/opencv-python) | MIT License |
|
| [Loguru](https://github.com/Delgan/loguru) | MIT License |
|
||||||
| [Pillow](https://github.com/python-pillow/Pillow) | HPND License |
|
| [opencv-python](https://github.com/opencv/opencv-python) | MIT License |
|
||||||
| [Rich](https://github.com/Textualize/rich) | MIT License |
|
| [Pillow](https://github.com/python-pillow/Pillow) | HPND License |
|
||||||
|
| [Rich](https://github.com/Textualize/rich) | MIT License |
|
||||||
|
| [pynput](https://github.com/moses-palmer/pynput) | LGPLv3.0 |
|
||||||
|
|
||||||
Legacy versions of this project includes or depends on these following projects:
|
Legacy versions of this project includes or depends on these following projects:
|
||||||
|
|
||||||
@@ -117,6 +119,7 @@ Appreciations given to the following personnel who have contributed significantl
|
|||||||
- [@ddouglas87](https://github.com/ddouglas87)
|
- [@ddouglas87](https://github.com/ddouglas87)
|
||||||
- [@lhanjian](https://github.com/lhanjian)
|
- [@lhanjian](https://github.com/lhanjian)
|
||||||
- [@ArchieMeng](https://github.com/archiemeng)
|
- [@ArchieMeng](https://github.com/archiemeng)
|
||||||
|
- [@nihui](https://github.com/nihui)
|
||||||
|
|
||||||
## Similar Projects
|
## Similar Projects
|
||||||
|
|
||||||
|
|||||||
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-beta4-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-beta4-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
|
||||||
|
)
|
||||||
21
licenses/LICENSE-realcugan-ncnn-vulkan
Normal file
21
licenses/LICENSE-realcugan-ncnn-vulkan
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2019 nihui
|
||||||
|
|
||||||
|
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.
|
||||||
165
licenses/lgpl-3.0.txt
Normal file
165
licenses/lgpl-3.0.txt
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
||||||
|
This version of the GNU Lesser General Public License incorporates
|
||||||
|
the terms and conditions of version 3 of the GNU General Public
|
||||||
|
License, supplemented by the additional permissions listed below.
|
||||||
|
|
||||||
|
0. Additional Definitions.
|
||||||
|
|
||||||
|
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||||
|
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||||
|
General Public License.
|
||||||
|
|
||||||
|
"The Library" refers to a covered work governed by this License,
|
||||||
|
other than an Application or a Combined Work as defined below.
|
||||||
|
|
||||||
|
An "Application" is any work that makes use of an interface provided
|
||||||
|
by the Library, but which is not otherwise based on the Library.
|
||||||
|
Defining a subclass of a class defined by the Library is deemed a mode
|
||||||
|
of using an interface provided by the Library.
|
||||||
|
|
||||||
|
A "Combined Work" is a work produced by combining or linking an
|
||||||
|
Application with the Library. The particular version of the Library
|
||||||
|
with which the Combined Work was made is also called the "Linked
|
||||||
|
Version".
|
||||||
|
|
||||||
|
The "Minimal Corresponding Source" for a Combined Work means the
|
||||||
|
Corresponding Source for the Combined Work, excluding any source code
|
||||||
|
for portions of the Combined Work that, considered in isolation, are
|
||||||
|
based on the Application, and not on the Linked Version.
|
||||||
|
|
||||||
|
The "Corresponding Application Code" for a Combined Work means the
|
||||||
|
object code and/or source code for the Application, including any data
|
||||||
|
and utility programs needed for reproducing the Combined Work from the
|
||||||
|
Application, but excluding the System Libraries of the Combined Work.
|
||||||
|
|
||||||
|
1. Exception to Section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
You may convey a covered work under sections 3 and 4 of this License
|
||||||
|
without being bound by section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
2. Conveying Modified Versions.
|
||||||
|
|
||||||
|
If you modify a copy of the Library, and, in your modifications, a
|
||||||
|
facility refers to a function or data to be supplied by an Application
|
||||||
|
that uses the facility (other than as an argument passed when the
|
||||||
|
facility is invoked), then you may convey a copy of the modified
|
||||||
|
version:
|
||||||
|
|
||||||
|
a) under this License, provided that you make a good faith effort to
|
||||||
|
ensure that, in the event an Application does not supply the
|
||||||
|
function or data, the facility still operates, and performs
|
||||||
|
whatever part of its purpose remains meaningful, or
|
||||||
|
|
||||||
|
b) under the GNU GPL, with none of the additional permissions of
|
||||||
|
this License applicable to that copy.
|
||||||
|
|
||||||
|
3. Object Code Incorporating Material from Library Header Files.
|
||||||
|
|
||||||
|
The object code form of an Application may incorporate material from
|
||||||
|
a header file that is part of the Library. You may convey such object
|
||||||
|
code under terms of your choice, provided that, if the incorporated
|
||||||
|
material is not limited to numerical parameters, data structure
|
||||||
|
layouts and accessors, or small macros, inline functions and templates
|
||||||
|
(ten or fewer lines in length), you do both of the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the object code that the
|
||||||
|
Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
4. Combined Works.
|
||||||
|
|
||||||
|
You may convey a Combined Work under terms of your choice that,
|
||||||
|
taken together, effectively do not restrict modification of the
|
||||||
|
portions of the Library contained in the Combined Work and reverse
|
||||||
|
engineering for debugging such modifications, if you also do each of
|
||||||
|
the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the Combined Work that
|
||||||
|
the Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
c) For a Combined Work that displays copyright notices during
|
||||||
|
execution, include the copyright notice for the Library among
|
||||||
|
these notices, as well as a reference directing the user to the
|
||||||
|
copies of the GNU GPL and this license document.
|
||||||
|
|
||||||
|
d) Do one of the following:
|
||||||
|
|
||||||
|
0) Convey the Minimal Corresponding Source under the terms of this
|
||||||
|
License, and the Corresponding Application Code in a form
|
||||||
|
suitable for, and under terms that permit, the user to
|
||||||
|
recombine or relink the Application with a modified version of
|
||||||
|
the Linked Version to produce a modified Combined Work, in the
|
||||||
|
manner specified by section 6 of the GNU GPL for conveying
|
||||||
|
Corresponding Source.
|
||||||
|
|
||||||
|
1) Use a suitable shared library mechanism for linking with the
|
||||||
|
Library. A suitable mechanism is one that (a) uses at run time
|
||||||
|
a copy of the Library already present on the user's computer
|
||||||
|
system, and (b) will operate properly with a modified version
|
||||||
|
of the Library that is interface-compatible with the Linked
|
||||||
|
Version.
|
||||||
|
|
||||||
|
e) Provide Installation Information, but only if you would otherwise
|
||||||
|
be required to provide such information under section 6 of the
|
||||||
|
GNU GPL, and only to the extent that such information is
|
||||||
|
necessary to install and execute a modified version of the
|
||||||
|
Combined Work produced by recombining or relinking the
|
||||||
|
Application with a modified version of the Linked Version. (If
|
||||||
|
you use option 4d0, the Installation Information must accompany
|
||||||
|
the Minimal Corresponding Source and Corresponding Application
|
||||||
|
Code. If you use option 4d1, you must provide the Installation
|
||||||
|
Information in the manner specified by section 6 of the GNU GPL
|
||||||
|
for conveying Corresponding Source.)
|
||||||
|
|
||||||
|
5. Combined Libraries.
|
||||||
|
|
||||||
|
You may place library facilities that are a work based on the
|
||||||
|
Library side by side in a single library together with other library
|
||||||
|
facilities that are not Applications and are not covered by this
|
||||||
|
License, and convey such a combined library under terms of your
|
||||||
|
choice, if you do both of the following:
|
||||||
|
|
||||||
|
a) Accompany the combined library with a copy of the same work based
|
||||||
|
on the Library, uncombined with any other library facilities,
|
||||||
|
conveyed under the terms of this License.
|
||||||
|
|
||||||
|
b) Give prominent notice with the combined library that part of it
|
||||||
|
is a work based on the Library, and explaining where to find the
|
||||||
|
accompanying uncombined form of the same work.
|
||||||
|
|
||||||
|
6. Revised Versions of the GNU Lesser General Public License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the GNU Lesser General Public License from time to time. Such new
|
||||||
|
versions will be similar in spirit to the present version, but may
|
||||||
|
differ in detail to address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Library as you received it specifies that a certain numbered version
|
||||||
|
of the GNU Lesser General Public License "or any later version"
|
||||||
|
applies to it, you have the option of following the terms and
|
||||||
|
conditions either of that published version or of any later version
|
||||||
|
published by the Free Software Foundation. If the Library as you
|
||||||
|
received it does not specify a version number of the GNU Lesser
|
||||||
|
General Public License, you may choose any version of the GNU Lesser
|
||||||
|
General Public License ever published by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Library as you received it specifies that a proxy can decide
|
||||||
|
whether future versions of the GNU Lesser General Public License shall
|
||||||
|
apply, that proxy's public statement of acceptance of any version is
|
||||||
|
permanent authorization for you to choose that version for the
|
||||||
|
Library.
|
||||||
533
pdm.lock
generated
Normal file
533
pdm.lock
generated
Normal file
@@ -0,0 +1,533 @@
|
|||||||
|
[[package]]
|
||||||
|
name = "cmake"
|
||||||
|
version = "3.22.3"
|
||||||
|
summary = "CMake is an open-source, cross-platform family of tools designed to build, test and package software"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cmake-build-extension"
|
||||||
|
version = "0.5.0"
|
||||||
|
requires_python = ">=3.6"
|
||||||
|
summary = "Setuptools extension to build and package CMake projects."
|
||||||
|
dependencies = [
|
||||||
|
"GitPython",
|
||||||
|
"cmake",
|
||||||
|
"ninja",
|
||||||
|
"setuptools-scm",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorama"
|
||||||
|
version = "0.4.4"
|
||||||
|
requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
summary = "Cross-platform colored terminal text."
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "commonmark"
|
||||||
|
version = "0.9.1"
|
||||||
|
summary = "Python parser for the CommonMark Markdown spec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "evdev"
|
||||||
|
version = "1.4.0"
|
||||||
|
summary = "Bindings to the Linux input handling subsystem"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ffmpeg-python"
|
||||||
|
version = "0.2.0"
|
||||||
|
summary = "Python bindings for FFmpeg - with complex filtering support"
|
||||||
|
dependencies = [
|
||||||
|
"future",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "future"
|
||||||
|
version = "0.18.2"
|
||||||
|
requires_python = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||||
|
summary = "Clean single-source support for Python 3 and 2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gitdb"
|
||||||
|
version = "4.0.9"
|
||||||
|
requires_python = ">=3.6"
|
||||||
|
summary = "Git Object Database"
|
||||||
|
dependencies = [
|
||||||
|
"smmap<6,>=3.0.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gitpython"
|
||||||
|
version = "3.1.27"
|
||||||
|
requires_python = ">=3.7"
|
||||||
|
summary = "GitPython is a python library used to interact with Git repositories"
|
||||||
|
dependencies = [
|
||||||
|
"gitdb<5,>=4.0.1",
|
||||||
|
"typing-extensions>=3.7.4.3; python_version < \"3.8\"",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "loguru"
|
||||||
|
version = "0.6.0"
|
||||||
|
requires_python = ">=3.5"
|
||||||
|
summary = "Python logging made (stupidly) simple"
|
||||||
|
dependencies = [
|
||||||
|
"colorama>=0.3.4; sys_platform == \"win32\"",
|
||||||
|
"win32-setctime>=1.0.0; sys_platform == \"win32\"",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ninja"
|
||||||
|
version = "1.10.2.3"
|
||||||
|
summary = "Ninja is a small build system with a focus on speed"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "numpy"
|
||||||
|
version = "1.22.3"
|
||||||
|
requires_python = ">=3.8"
|
||||||
|
summary = "NumPy is the fundamental package for array computing with Python."
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opencv-python"
|
||||||
|
version = "4.5.5.64"
|
||||||
|
requires_python = ">=3.6"
|
||||||
|
summary = "Wrapper package for OpenCV python bindings."
|
||||||
|
dependencies = [
|
||||||
|
"numpy>=1.14.5; python_version >= \"3.7\"",
|
||||||
|
"numpy>=1.17.3; python_version >= \"3.8\"",
|
||||||
|
"numpy>=1.19.3; python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\"",
|
||||||
|
"numpy>=1.19.3; python_version >= \"3.9\"",
|
||||||
|
"numpy>=1.21.2; python_version >= \"3.10\"",
|
||||||
|
"numpy>=1.21.2; python_version >= \"3.6\" and platform_system == \"Darwin\" and platform_machine == \"arm64\"",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "packaging"
|
||||||
|
version = "21.3"
|
||||||
|
requires_python = ">=3.6"
|
||||||
|
summary = "Core utilities for Python packages"
|
||||||
|
dependencies = [
|
||||||
|
"pyparsing!=3.0.5,>=2.0.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pillow"
|
||||||
|
version = "9.0.1"
|
||||||
|
requires_python = ">=3.7"
|
||||||
|
summary = "Python Imaging Library (Fork)"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pygments"
|
||||||
|
version = "2.11.2"
|
||||||
|
requires_python = ">=3.5"
|
||||||
|
summary = "Pygments is a syntax highlighting package written in Python."
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pynput"
|
||||||
|
version = "1.7.6"
|
||||||
|
summary = "Monitor and control user input devices"
|
||||||
|
dependencies = [
|
||||||
|
"evdev>=1.3; \"linux\" in sys_platform",
|
||||||
|
"pyobjc-framework-ApplicationServices>=8.0; sys_platform == \"darwin\"",
|
||||||
|
"pyobjc-framework-Quartz>=8.0; sys_platform == \"darwin\"",
|
||||||
|
"python-xlib>=0.17; \"linux\" in sys_platform",
|
||||||
|
"six",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyobjc-core"
|
||||||
|
version = "8.4.1"
|
||||||
|
requires_python = ">=3.6"
|
||||||
|
summary = "Python<->ObjC Interoperability Module"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyobjc-framework-applicationservices"
|
||||||
|
version = "8.4.1"
|
||||||
|
requires_python = ">=3.6"
|
||||||
|
summary = "Wrappers for the framework ApplicationServices on macOS"
|
||||||
|
dependencies = [
|
||||||
|
"pyobjc-core>=8.4.1",
|
||||||
|
"pyobjc-framework-Cocoa>=8.4.1",
|
||||||
|
"pyobjc-framework-Quartz>=8.4.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyobjc-framework-cocoa"
|
||||||
|
version = "8.4.1"
|
||||||
|
requires_python = ">=3.6"
|
||||||
|
summary = "Wrappers for the Cocoa frameworks on macOS"
|
||||||
|
dependencies = [
|
||||||
|
"pyobjc-core>=8.4.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyobjc-framework-quartz"
|
||||||
|
version = "8.4.1"
|
||||||
|
requires_python = ">=3.6"
|
||||||
|
summary = "Wrappers for the Quartz frameworks on macOS"
|
||||||
|
dependencies = [
|
||||||
|
"pyobjc-core>=8.4.1",
|
||||||
|
"pyobjc-framework-Cocoa>=8.4.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyparsing"
|
||||||
|
version = "3.0.7"
|
||||||
|
requires_python = ">=3.6"
|
||||||
|
summary = "Python parsing module"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-xlib"
|
||||||
|
version = "0.31"
|
||||||
|
summary = "Python X Library"
|
||||||
|
dependencies = [
|
||||||
|
"six>=1.10.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "realcugan-ncnn-vulkan-python"
|
||||||
|
version = "1.0.0"
|
||||||
|
requires_python = ">=3.6"
|
||||||
|
summary = "A Python FFI of nihui/realcugan-ncnn-vulkan achieved with SWIG"
|
||||||
|
dependencies = [
|
||||||
|
"pillow",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "realsr-ncnn-vulkan-python"
|
||||||
|
version = "1.0.4"
|
||||||
|
requires_python = ">=3.6"
|
||||||
|
summary = "A Python FFI of nihui/realsr-ncnn-vulkan achieved with SWIG"
|
||||||
|
dependencies = [
|
||||||
|
"pillow",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rich"
|
||||||
|
version = "12.0.0"
|
||||||
|
requires_python = ">=3.6.2,<4.0.0"
|
||||||
|
summary = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
||||||
|
dependencies = [
|
||||||
|
"commonmark<0.10.0,>=0.9.0",
|
||||||
|
"pygments<3.0.0,>=2.6.0",
|
||||||
|
"typing-extensions<5.0,>=3.7.4; python_version < \"3.8\"",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rife-ncnn-vulkan-python"
|
||||||
|
version = "1.1.2.post3"
|
||||||
|
requires_python = ">=3.6"
|
||||||
|
summary = "A Python FFI of nihui/rife-ncnn-vulkan achieved with SWIG"
|
||||||
|
dependencies = [
|
||||||
|
"pillow",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "setuptools"
|
||||||
|
version = "60.10.0"
|
||||||
|
requires_python = ">=3.7"
|
||||||
|
summary = "Easily download, build, install, upgrade, and uninstall Python packages"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "setuptools-scm"
|
||||||
|
version = "6.4.2"
|
||||||
|
requires_python = ">=3.6"
|
||||||
|
summary = "the blessed package to manage your versions by scm tags"
|
||||||
|
dependencies = [
|
||||||
|
"packaging>=20.0",
|
||||||
|
"setuptools",
|
||||||
|
"tomli>=1.0.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "six"
|
||||||
|
version = "1.16.0"
|
||||||
|
requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||||
|
summary = "Python 2 and 3 compatibility utilities"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smmap"
|
||||||
|
version = "5.0.0"
|
||||||
|
requires_python = ">=3.6"
|
||||||
|
summary = "A pure Python implementation of a sliding window memory map manager"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "srmd-ncnn-vulkan-python"
|
||||||
|
version = "1.0.2"
|
||||||
|
requires_python = ">=3.6"
|
||||||
|
summary = "A Python FFI of nihui/srmd-ncnn-vulkan achieved with SWIG"
|
||||||
|
dependencies = [
|
||||||
|
"pillow",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tomli"
|
||||||
|
version = "2.0.1"
|
||||||
|
requires_python = ">=3.7"
|
||||||
|
summary = "A lil' TOML parser"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-extensions"
|
||||||
|
version = "4.1.1"
|
||||||
|
requires_python = ">=3.6"
|
||||||
|
summary = "Backported and Experimental Type Hints for Python 3.6+"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "waifu2x-ncnn-vulkan-python"
|
||||||
|
version = "1.0.1"
|
||||||
|
requires_python = ">=3.6"
|
||||||
|
summary = "A Python FFI of nihui/waifu2x-ncnn-vulkan achieved with SWIG"
|
||||||
|
dependencies = [
|
||||||
|
"cmake",
|
||||||
|
"cmake-build-extension",
|
||||||
|
"pillow",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "win32-setctime"
|
||||||
|
version = "1.1.0"
|
||||||
|
requires_python = ">=3.5"
|
||||||
|
summary = "A small Python utility to set file creation time on Windows"
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
lock_version = "3.1"
|
||||||
|
content_hash = "sha256:f72fb9417f87c737f6a94710af1039611dc2d723d056e2479172fc6b695cab7c"
|
||||||
|
|
||||||
|
[metadata.files]
|
||||||
|
"cmake 3.22.3" = [
|
||||||
|
{file = "cmake-3.22.3-py2.py3-none-macosx_10_10_universal2.macosx_10_10_x86_64.macosx_11_0_arm64.macosx_11_0_universal2.whl", hash = "sha256:d387e4062541094d302c80b3beed08cfa4f2ef98454d04f34fd1071c595152d8"},
|
||||||
|
{file = "cmake-3.22.3-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ff1a89c264a6645d5d222f73efe83ea47abc96d77ae96e3e4eb4580f6f396fc"},
|
||||||
|
{file = "cmake-3.22.3-py2.py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3adc8532f5029931e82c9ae6a07b5d866b216b422cedc72074155c80db6e27d7"},
|
||||||
|
{file = "cmake-3.22.3-py2.py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c46f0573b0455a234c397fff2f3679e18925eb3da32069cc41f1aab7cd968260"},
|
||||||
|
{file = "cmake-3.22.3-py2.py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86c68b283223375ff942bce5b9f2b286fa5ac94b02f848e52fcec2369a407b22"},
|
||||||
|
{file = "cmake-3.22.3-py2.py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:452e53346d52241fbebdb9487e88786221c92d4abfa59fb002dc928ada69399c"},
|
||||||
|
{file = "cmake-3.22.3-py2.py3-none-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fb5fbcb8b45c9f9823a1bffffe8eab673e463b8d36b258856035725e04baf446"},
|
||||||
|
{file = "cmake-3.22.3-py2.py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e615d29c2c26faf7e3876c47bafbab0eefe29683e01bec3a14736e9694bdff73"},
|
||||||
|
{file = "cmake-3.22.3-py2.py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:1d6096da730d2d0c8352926b647d0d340fc2ea3a6497dd13cff1c999d68d9d7b"},
|
||||||
|
{file = "cmake-3.22.3-py2.py3-none-musllinux_1_1_i686.whl", hash = "sha256:d3be0f5256bfe76c81d2db30216c937a7f7cefd251fb63c1cb30c5e0a896bbad"},
|
||||||
|
{file = "cmake-3.22.3-py2.py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:d73961a801ea9e2fdda9afa3b9fc84539e368ecec30de1ef54045abfabce4ece"},
|
||||||
|
{file = "cmake-3.22.3-py2.py3-none-musllinux_1_1_s390x.whl", hash = "sha256:45e89f6ff37283ecff912c7fe65dc4bc6dfe2fb78b7747b95272dd7423a3847f"},
|
||||||
|
{file = "cmake-3.22.3-py2.py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:c776ad422f7ce3866497f132af4ba7240cb81d554ee5eb801fd5e2d8c7cd67d6"},
|
||||||
|
{file = "cmake-3.22.3-py2.py3-none-win32.whl", hash = "sha256:8d32168fb3153313408befc7b8b5593130bad7d5fe0b8df764bb723b8c710bfc"},
|
||||||
|
{file = "cmake-3.22.3-py2.py3-none-win_amd64.whl", hash = "sha256:d8db320fb36560fcf2ca66481009bc1d1f8ed45fd756ce91e4dee97026a0e3a4"},
|
||||||
|
{file = "cmake-3.22.3.tar.gz", hash = "sha256:6e1d1991775915dac047af851f2feec7eb35ab4b4610d3e7fd94d93bce8faf58"},
|
||||||
|
]
|
||||||
|
"cmake-build-extension 0.5.0" = [
|
||||||
|
{file = "cmake_build_extension-0.5.0-py3-none-any.whl", hash = "sha256:b11b8c140ef0a7595576c8a8f807dbae30924e78f92cdecd5be8060087a84e88"},
|
||||||
|
{file = "cmake-build-extension-0.5.0.tar.gz", hash = "sha256:75a8e866544976e213aad31799f6a9b68a5135cf37cc41af29c1706446d56155"},
|
||||||
|
]
|
||||||
|
"colorama 0.4.4" = [
|
||||||
|
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
|
||||||
|
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
|
||||||
|
]
|
||||||
|
"commonmark 0.9.1" = [
|
||||||
|
{file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"},
|
||||||
|
{file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"},
|
||||||
|
]
|
||||||
|
"evdev 1.4.0" = [
|
||||||
|
{file = "evdev-1.4.0.tar.gz", hash = "sha256:8782740eb1a86b187334c07feb5127d3faa0b236e113206dfe3ae8f77fb1aaf1"},
|
||||||
|
]
|
||||||
|
"ffmpeg-python 0.2.0" = [
|
||||||
|
{file = "ffmpeg_python-0.2.0-py3-none-any.whl", hash = "sha256:ac441a0404e053f8b6a1113a77c0f452f1cfc62f6344a769475ffdc0f56c23c5"},
|
||||||
|
{file = "ffmpeg-python-0.2.0.tar.gz", hash = "sha256:65225db34627c578ef0e11c8b1eb528bb35e024752f6f10b78c011f6f64c4127"},
|
||||||
|
]
|
||||||
|
"future 0.18.2" = [
|
||||||
|
{file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"},
|
||||||
|
]
|
||||||
|
"gitdb 4.0.9" = [
|
||||||
|
{file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"},
|
||||||
|
{file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"},
|
||||||
|
]
|
||||||
|
"gitpython 3.1.27" = [
|
||||||
|
{file = "GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"},
|
||||||
|
{file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"},
|
||||||
|
]
|
||||||
|
"loguru 0.6.0" = [
|
||||||
|
{file = "loguru-0.6.0-py3-none-any.whl", hash = "sha256:4e2414d534a2ab57573365b3e6d0234dfb1d84b68b7f3b948e6fb743860a77c3"},
|
||||||
|
{file = "loguru-0.6.0.tar.gz", hash = "sha256:066bd06758d0a513e9836fd9c6b5a75bfb3fd36841f4b996bc60b547a309d41c"},
|
||||||
|
]
|
||||||
|
"ninja 1.10.2.3" = [
|
||||||
|
{file = "ninja-1.10.2.3-py2.py3-none-macosx_10_9_universal2.macosx_10_9_x86_64.macosx_11_0_arm64.macosx_11_0_universal2.whl", hash = "sha256:d5e0275d28997a750a4f445c00bdd357b35cc334c13cdff13edf30e544704fbd"},
|
||||||
|
{file = "ninja-1.10.2.3-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ea785bf6a15727040835256577239fa3cf5da0d60e618c307aa5efc31a1f0ce"},
|
||||||
|
{file = "ninja-1.10.2.3-py2.py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29570a18d697fc84d361e7e6330f0021f34603ae0fcb0ef67ae781e9814aae8d"},
|
||||||
|
{file = "ninja-1.10.2.3-py2.py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:21a1d84d4c7df5881bfd86c25cce4cf7af44ba2b8b255c57bc1c434ec30a2dfc"},
|
||||||
|
{file = "ninja-1.10.2.3-py2.py3-none-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9ca8dbece144366d5f575ffc657af03eb11c58251268405bc8519d11cf42f113"},
|
||||||
|
{file = "ninja-1.10.2.3-py2.py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:279836285975e3519392c93c26e75755e8a8a7fafec9f4ecbb0293119ee0f9c6"},
|
||||||
|
{file = "ninja-1.10.2.3-py2.py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:cc8b31b5509a2129e4d12a35fc21238c157038022560aaf22e49ef0a77039086"},
|
||||||
|
{file = "ninja-1.10.2.3-py2.py3-none-musllinux_1_1_i686.whl", hash = "sha256:688167841b088b6802e006f911d911ffa925e078c73e8ef2f88286107d3204f8"},
|
||||||
|
{file = "ninja-1.10.2.3-py2.py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:840a0b042d43a8552c4004966e18271ec726e5996578f28345d9ce78e225b67e"},
|
||||||
|
{file = "ninja-1.10.2.3-py2.py3-none-musllinux_1_1_s390x.whl", hash = "sha256:84be6f9ec49f635dc40d4b871319a49fa49b8d55f1d9eae7cd50d8e57ddf7a85"},
|
||||||
|
{file = "ninja-1.10.2.3-py2.py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:6bd76a025f26b9ae507cf8b2b01bb25bb0031df54ed685d85fc559c411c86cf4"},
|
||||||
|
{file = "ninja-1.10.2.3-py2.py3-none-win32.whl", hash = "sha256:740d61fefb4ca13573704ee8fe89b973d40b8dc2a51aaa4e9e68367233743bb6"},
|
||||||
|
{file = "ninja-1.10.2.3-py2.py3-none-win_amd64.whl", hash = "sha256:0560eea57199e41e86ac2c1af0108b63ae77c3ca4d05a9425a750e908135935a"},
|
||||||
|
{file = "ninja-1.10.2.3.tar.gz", hash = "sha256:e1b86ad50d4e681a7dbdff05fc23bb52cb773edb90bc428efba33fa027738408"},
|
||||||
|
]
|
||||||
|
"numpy 1.22.3" = [
|
||||||
|
{file = "numpy-1.22.3-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:92bfa69cfbdf7dfc3040978ad09a48091143cffb778ec3b03fa170c494118d75"},
|
||||||
|
{file = "numpy-1.22.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8251ed96f38b47b4295b1ae51631de7ffa8260b5b087808ef09a39a9d66c97ab"},
|
||||||
|
{file = "numpy-1.22.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48a3aecd3b997bf452a2dedb11f4e79bc5bfd21a1d4cc760e703c31d57c84b3e"},
|
||||||
|
{file = "numpy-1.22.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3bae1a2ed00e90b3ba5f7bd0a7c7999b55d609e0c54ceb2b076a25e345fa9f4"},
|
||||||
|
{file = "numpy-1.22.3-cp310-cp310-win32.whl", hash = "sha256:f950f8845b480cffe522913d35567e29dd381b0dc7e4ce6a4a9f9156417d2430"},
|
||||||
|
{file = "numpy-1.22.3-cp310-cp310-win_amd64.whl", hash = "sha256:08d9b008d0156c70dc392bb3ab3abb6e7a711383c3247b410b39962263576cd4"},
|
||||||
|
{file = "numpy-1.22.3-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:201b4d0552831f7250a08d3b38de0d989d6f6e4658b709a02a73c524ccc6ffce"},
|
||||||
|
{file = "numpy-1.22.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8c1f39caad2c896bc0018f699882b345b2a63708008be29b1f355ebf6f933fe"},
|
||||||
|
{file = "numpy-1.22.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:568dfd16224abddafb1cbcce2ff14f522abe037268514dd7e42c6776a1c3f8e5"},
|
||||||
|
{file = "numpy-1.22.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ca688e1b9b95d80250bca34b11a05e389b1420d00e87a0d12dc45f131f704a1"},
|
||||||
|
{file = "numpy-1.22.3-cp38-cp38-win32.whl", hash = "sha256:e7927a589df200c5e23c57970bafbd0cd322459aa7b1ff73b7c2e84d6e3eae62"},
|
||||||
|
{file = "numpy-1.22.3-cp38-cp38-win_amd64.whl", hash = "sha256:07a8c89a04997625236c5ecb7afe35a02af3896c8aa01890a849913a2309c676"},
|
||||||
|
{file = "numpy-1.22.3-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:2c10a93606e0b4b95c9b04b77dc349b398fdfbda382d2a39ba5a822f669a0123"},
|
||||||
|
{file = "numpy-1.22.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fade0d4f4d292b6f39951b6836d7a3c7ef5b2347f3c420cd9820a1d90d794802"},
|
||||||
|
{file = "numpy-1.22.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bfb1bb598e8229c2d5d48db1860bcf4311337864ea3efdbe1171fb0c5da515d"},
|
||||||
|
{file = "numpy-1.22.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97098b95aa4e418529099c26558eeb8486e66bd1e53a6b606d684d0c3616b168"},
|
||||||
|
{file = "numpy-1.22.3-cp39-cp39-win32.whl", hash = "sha256:fdf3c08bce27132395d3c3ba1503cac12e17282358cb4bddc25cc46b0aca07aa"},
|
||||||
|
{file = "numpy-1.22.3-cp39-cp39-win_amd64.whl", hash = "sha256:639b54cdf6aa4f82fe37ebf70401bbb74b8508fddcf4797f9fe59615b8c5813a"},
|
||||||
|
{file = "numpy-1.22.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c34ea7e9d13a70bf2ab64a2532fe149a9aced424cd05a2c4ba662fd989e3e45f"},
|
||||||
|
{file = "numpy-1.22.3.zip", hash = "sha256:dbc7601a3b7472d559dc7b933b18b4b66f9aa7452c120e87dfb33d02008c8a18"},
|
||||||
|
]
|
||||||
|
"opencv-python 4.5.5.64" = [
|
||||||
|
{file = "opencv_python-4.5.5.64-cp36-abi3-macosx_10_15_x86_64.whl", hash = "sha256:a512a0c59b6fec0fac3844b2f47d6ecb1a9d18d235e6c5491ce8dbbe0663eae8"},
|
||||||
|
{file = "opencv_python-4.5.5.64-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca6138b6903910e384067d001763d40f97656875487381aed32993b076f44375"},
|
||||||
|
{file = "opencv_python-4.5.5.64-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b293ced62f4360d9f11cf72ae7e9df95320ff7bf5b834d87546f844e838c0c35"},
|
||||||
|
{file = "opencv_python-4.5.5.64-cp36-abi3-win32.whl", hash = "sha256:6247e584813c00c3b9ed69a795da40d2c153dc923d0182e957e1c2f00a554ac2"},
|
||||||
|
{file = "opencv_python-4.5.5.64-cp36-abi3-win_amd64.whl", hash = "sha256:408d5332550287aa797fd06bef47b2dfed163c6787668cc82ef9123a9484b56a"},
|
||||||
|
{file = "opencv_python-4.5.5.64-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:7787bb017ae93d5f9bb1b817ac8e13e45dd193743cb648498fcab21d00cf20a3"},
|
||||||
|
{file = "opencv-python-4.5.5.64.tar.gz", hash = "sha256:f65de0446a330c3b773cd04ba10345d8ce1b15dcac3f49770204e37602d0b3f7"},
|
||||||
|
]
|
||||||
|
"packaging 21.3" = [
|
||||||
|
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
|
||||||
|
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
|
||||||
|
]
|
||||||
|
"pillow 9.0.1" = [
|
||||||
|
{file = "Pillow-9.0.1-1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a5d24e1d674dd9d72c66ad3ea9131322819ff86250b30dc5821cbafcfa0b96b4"},
|
||||||
|
{file = "Pillow-9.0.1-1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2632d0f846b7c7600edf53c48f8f9f1e13e62f66a6dbc15191029d950bfed976"},
|
||||||
|
{file = "Pillow-9.0.1-1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9618823bd237c0d2575283f2939655f54d51b4527ec3972907a927acbcc5bfc"},
|
||||||
|
{file = "Pillow-9.0.1-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:9bfdb82cdfeccec50aad441afc332faf8606dfa5e8efd18a6692b5d6e79f00fd"},
|
||||||
|
{file = "Pillow-9.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5100b45a4638e3c00e4d2320d3193bdabb2d75e79793af7c3eb139e4f569f16f"},
|
||||||
|
{file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:528a2a692c65dd5cafc130de286030af251d2ee0483a5bf50c9348aefe834e8a"},
|
||||||
|
{file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f29d831e2151e0b7b39981756d201f7108d3d215896212ffe2e992d06bfe049"},
|
||||||
|
{file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:855c583f268edde09474b081e3ddcd5cf3b20c12f26e0d434e1386cc5d318e7a"},
|
||||||
|
{file = "Pillow-9.0.1-cp310-cp310-win32.whl", hash = "sha256:d9d7942b624b04b895cb95af03a23407f17646815495ce4547f0e60e0b06f58e"},
|
||||||
|
{file = "Pillow-9.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81c4b81611e3a3cb30e59b0cf05b888c675f97e3adb2c8672c3154047980726b"},
|
||||||
|
{file = "Pillow-9.0.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:413ce0bbf9fc6278b2d63309dfeefe452835e1c78398efb431bab0672fe9274e"},
|
||||||
|
{file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80fe64a6deb6fcfdf7b8386f2cf216d329be6f2781f7d90304351811fb591360"},
|
||||||
|
{file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cef9c85ccbe9bee00909758936ea841ef12035296c748aaceee535969e27d31b"},
|
||||||
|
{file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d19397351f73a88904ad1aee421e800fe4bbcd1aeee6435fb62d0a05ccd1030"},
|
||||||
|
{file = "Pillow-9.0.1-cp37-cp37m-win32.whl", hash = "sha256:d21237d0cd37acded35154e29aec853e945950321dd2ffd1a7d86fe686814669"},
|
||||||
|
{file = "Pillow-9.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ede5af4a2702444a832a800b8eb7f0a7a1c0eed55b644642e049c98d589e5092"},
|
||||||
|
{file = "Pillow-9.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:b5b3f092fe345c03bca1e0b687dfbb39364b21ebb8ba90e3fa707374b7915204"},
|
||||||
|
{file = "Pillow-9.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:335ace1a22325395c4ea88e00ba3dc89ca029bd66bd5a3c382d53e44f0ccd77e"},
|
||||||
|
{file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db6d9fac65bd08cea7f3540b899977c6dee9edad959fa4eaf305940d9cbd861c"},
|
||||||
|
{file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f154d173286a5d1863637a7dcd8c3437bb557520b01bddb0be0258dcb72696b5"},
|
||||||
|
{file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d4b1341ac07ae07eb2cc682f459bec932a380c3b122f5540432d8977e64eae"},
|
||||||
|
{file = "Pillow-9.0.1-cp38-cp38-win32.whl", hash = "sha256:effb7749713d5317478bb3acb3f81d9d7c7f86726d41c1facca068a04cf5bb4c"},
|
||||||
|
{file = "Pillow-9.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:7f7609a718b177bf171ac93cea9fd2ddc0e03e84d8fa4e887bdfc39671d46b00"},
|
||||||
|
{file = "Pillow-9.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:80ca33961ced9c63358056bd08403ff866512038883e74f3a4bf88ad3eb66838"},
|
||||||
|
{file = "Pillow-9.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c3c33ac69cf059bbb9d1a71eeaba76781b450bc307e2291f8a4764d779a6b28"},
|
||||||
|
{file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12875d118f21cf35604176872447cdb57b07126750a33748bac15e77f90f1f9c"},
|
||||||
|
{file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:514ceac913076feefbeaf89771fd6febde78b0c4c1b23aaeab082c41c694e81b"},
|
||||||
|
{file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3c5c79ab7dfce6d88f1ba639b77e77a17ea33a01b07b99840d6ed08031cb2a7"},
|
||||||
|
{file = "Pillow-9.0.1-cp39-cp39-win32.whl", hash = "sha256:718856856ba31f14f13ba885ff13874be7fefc53984d2832458f12c38205f7f7"},
|
||||||
|
{file = "Pillow-9.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:f25ed6e28ddf50de7e7ea99d7a976d6a9c415f03adcaac9c41ff6ff41b6d86ac"},
|
||||||
|
{file = "Pillow-9.0.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:011233e0c42a4a7836498e98c1acf5e744c96a67dd5032a6f666cc1fb97eab97"},
|
||||||
|
{file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:253e8a302a96df6927310a9d44e6103055e8fb96a6822f8b7f514bb7ef77de56"},
|
||||||
|
{file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6295f6763749b89c994fcb6d8a7f7ce03c3992e695f89f00b741b4580b199b7e"},
|
||||||
|
{file = "Pillow-9.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a9f44cd7e162ac6191491d7249cceb02b8116b0f7e847ee33f739d7cb1ea1f70"},
|
||||||
|
{file = "Pillow-9.0.1.tar.gz", hash = "sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa"},
|
||||||
|
]
|
||||||
|
"pygments 2.11.2" = [
|
||||||
|
{file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"},
|
||||||
|
{file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"},
|
||||||
|
]
|
||||||
|
"pynput 1.7.6" = [
|
||||||
|
{file = "pynput-1.7.6-py2.py3-none-any.whl", hash = "sha256:19861b2a0c430d646489852f89500e0c9332e295f2c020e7c2775e7046aa2e2f"},
|
||||||
|
{file = "pynput-1.7.6.tar.gz", hash = "sha256:3a5726546da54116b687785d38b1db56997ce1d28e53e8d22fc656d8b92e533c"},
|
||||||
|
]
|
||||||
|
"pyobjc-core 8.4.1" = [
|
||||||
|
{file = "pyobjc_core-8.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9a89cac910fbd64728fe7ec0c7a3a7cf20959bc1d7e2f41db4d7800556e47745"},
|
||||||
|
{file = "pyobjc_core-8.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2cf1d4348cb99fcba04fa38199a46e35263b2fe7bb66e6dfbd4f19bc2602998d"},
|
||||||
|
{file = "pyobjc_core-8.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a130324b25c0f5f4cfe30b6a28b8e70865d6e1eee158caababb603906ef431d2"},
|
||||||
|
{file = "pyobjc_core-8.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c31195d1a8f00da99abf79643f902d09c709dbbe9c9b6feb6b585303c57d720c"},
|
||||||
|
{file = "pyobjc_core-8.4.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:e7fd2aefb53a96a8f688c8bb36c6ebd5446250a7251bfa6b688a045e05afc60b"},
|
||||||
|
{file = "pyobjc_core-8.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b3191173ce268e23c84d84f036fc94c3a8749a6726fc7fe73baf27dbac14f7d0"},
|
||||||
|
{file = "pyobjc-core-8.4.1.tar.gz", hash = "sha256:df98669e957adb33566d9ef46773a5ac876a81afe8849c282d6a80448e35dd74"},
|
||||||
|
]
|
||||||
|
"pyobjc-framework-applicationservices 8.4.1" = [
|
||||||
|
{file = "pyobjc_framework_ApplicationServices-8.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:362d178c624a617fc60c3a35397550193d82da9a59272b215cf1dc6fb68c011c"},
|
||||||
|
{file = "pyobjc_framework_ApplicationServices-8.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:41f0f6be6d343f91de92cab053be4a983e3936b364ca267a94c6e06bc34ff7fe"},
|
||||||
|
{file = "pyobjc_framework_ApplicationServices-8.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:73287b758a70a037b6e74a60036f4adb1677407dfeaddee94102074d92539e6e"},
|
||||||
|
{file = "pyobjc_framework_ApplicationServices-8.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:287b541b66c5c931a11dde90d7be69ddd2e5c3624c2e980e679f5242ec3ee0da"},
|
||||||
|
{file = "pyobjc_framework_ApplicationServices-8.4.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:780fbe2e3b02237f79e2f5780094dd95b7308ecdb265c062b78a507b9564eb4d"},
|
||||||
|
{file = "pyobjc_framework_ApplicationServices-8.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:092c4835e73e5dab1f3c498511b28b2e96503671fc7c1bc25f732c5e687b7214"},
|
||||||
|
{file = "pyobjc-framework-ApplicationServices-8.4.1.tar.gz", hash = "sha256:b058466266412d2bf24b0303785c91538961367f26db616bf2f9f45c498a83a3"},
|
||||||
|
]
|
||||||
|
"pyobjc-framework-cocoa 8.4.1" = [
|
||||||
|
{file = "pyobjc_framework_Cocoa-8.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cfbe038a0108ae1b45f8f7067af70af5811b8352d2dc3d86a7bcb4484ff5d56e"},
|
||||||
|
{file = "pyobjc_framework_Cocoa-8.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:118225562064d991bafb41d0913899d6b3d723984d1888cb7181e4dba63b22c2"},
|
||||||
|
{file = "pyobjc_framework_Cocoa-8.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d117a1eb24fd317e9f63792ac6a8703ed899de5d42e8a861c7bf885625668c31"},
|
||||||
|
{file = "pyobjc_framework_Cocoa-8.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3af8577acbd6b980d3b9270ec99bc0164f66ef8397351a72fcdee527f23c1a34"},
|
||||||
|
{file = "pyobjc_framework_Cocoa-8.4.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:91fdc964acb4dee4d37ae81fb603d48397739dbbfcc1eadbe0cdafaa8144b6e6"},
|
||||||
|
{file = "pyobjc_framework_Cocoa-8.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:197dd28668e786b55d7d4139afd85c285f780564ebbccc166e84a24be31de34f"},
|
||||||
|
{file = "pyobjc-framework-Cocoa-8.4.1.tar.gz", hash = "sha256:dc596bac0f5d424f67944e95b2d0d7c94a07c4166359d7b4a4d4ae4f8e112822"},
|
||||||
|
]
|
||||||
|
"pyobjc-framework-quartz 8.4.1" = [
|
||||||
|
{file = "pyobjc_framework_Quartz-8.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a82d9eab3b2a6fca46602465f731815026d06e4fd0ba65b5b5211f1a0472b861"},
|
||||||
|
{file = "pyobjc_framework_Quartz-8.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1bd0a506385141b81330464b6e2537a7056cdca56deb98222b6926a04f72a3e6"},
|
||||||
|
{file = "pyobjc_framework_Quartz-8.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:64cc616386b7a387dce53d3adb8c12a8461c2720861ab36aeeb53768cd0ba7e4"},
|
||||||
|
{file = "pyobjc_framework_Quartz-8.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a2dff998c4d1286998390f58a3131b99bdc9dbf5c3d881562b33f168098bbe2e"},
|
||||||
|
{file = "pyobjc_framework_Quartz-8.4.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:a98c58e1b265d5b413f5ecc6ec186b9e305fb6e37909d8b4b97fd681176f5f1c"},
|
||||||
|
{file = "pyobjc_framework_Quartz-8.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:39f55426ef883cbe12a53969f90886f71e2a94513c98cf3730efdf404cdc5c83"},
|
||||||
|
{file = "pyobjc-framework-Quartz-8.4.1.tar.gz", hash = "sha256:f8acebf0b526f0687e79ea6946e8f2528ced4ef02d0ed3fbf7116124b2749e52"},
|
||||||
|
]
|
||||||
|
"pyparsing 3.0.7" = [
|
||||||
|
{file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"},
|
||||||
|
{file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"},
|
||||||
|
]
|
||||||
|
"python-xlib 0.31" = [
|
||||||
|
{file = "python_xlib-0.31-py2.py3-none-any.whl", hash = "sha256:1ec6ce0de73d9e6592ead666779a5732b384e5b8fb1f1886bd0a81cafa477759"},
|
||||||
|
{file = "python-xlib-0.31.tar.gz", hash = "sha256:74d83a081f532bc07f6d7afcd6416ec38403d68f68b9b9dc9e1f28fbf2d799e9"},
|
||||||
|
]
|
||||||
|
"realcugan-ncnn-vulkan-python 1.0.0" = [
|
||||||
|
{file = "realcugan-ncnn-vulkan-python-1.0.0.tar.gz", hash = "sha256:f72764ffe0a0c53b13cdbe1ae328ff9b0eb5582b9d5d15a0ca0c0ac254d17410"},
|
||||||
|
]
|
||||||
|
"realsr-ncnn-vulkan-python 1.0.4" = [
|
||||||
|
{file = "realsr-ncnn-vulkan-python-1.0.4.tar.gz", hash = "sha256:bbfd9fc067cfea6ab28e8074f1d6aadcc7f927f166d90fab9dcc46380b7ef02d"},
|
||||||
|
]
|
||||||
|
"rich 12.0.0" = [
|
||||||
|
{file = "rich-12.0.0-py3-none-any.whl", hash = "sha256:fdcd2f8d416e152bcf35c659987038d1ae5a7bd336e821ca7551858a4c7e38a9"},
|
||||||
|
{file = "rich-12.0.0.tar.gz", hash = "sha256:14bfd0507edc633e021b02c45cbf7ca22e33b513817627b8de3412f047a3e798"},
|
||||||
|
]
|
||||||
|
"rife-ncnn-vulkan-python 1.1.2.post3" = [
|
||||||
|
{file = "rife-ncnn-vulkan-python-1.1.2.post3.tar.gz", hash = "sha256:df66d1f83bc6beacac33b0e384ee352b45fa39931b9e1ff8247b396d4d7a486d"},
|
||||||
|
]
|
||||||
|
"setuptools 60.10.0" = [
|
||||||
|
{file = "setuptools-60.10.0-py3-none-any.whl", hash = "sha256:782ef48d58982ddb49920c11a0c5c9c0b02e7d7d1c2ad0aa44e1a1e133051c96"},
|
||||||
|
{file = "setuptools-60.10.0.tar.gz", hash = "sha256:6599055eeb23bfef457d5605d33a4d68804266e6cb430b0fb12417c5efeae36c"},
|
||||||
|
]
|
||||||
|
"setuptools-scm 6.4.2" = [
|
||||||
|
{file = "setuptools_scm-6.4.2-py3-none-any.whl", hash = "sha256:acea13255093849de7ccb11af9e1fb8bde7067783450cee9ef7a93139bddf6d4"},
|
||||||
|
{file = "setuptools_scm-6.4.2.tar.gz", hash = "sha256:6833ac65c6ed9711a4d5d2266f8024cfa07c533a0e55f4c12f6eff280a5a9e30"},
|
||||||
|
]
|
||||||
|
"six 1.16.0" = [
|
||||||
|
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||||
|
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||||
|
]
|
||||||
|
"smmap 5.0.0" = [
|
||||||
|
{file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"},
|
||||||
|
{file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"},
|
||||||
|
]
|
||||||
|
"srmd-ncnn-vulkan-python 1.0.2" = [
|
||||||
|
{file = "srmd-ncnn-vulkan-python-1.0.2.tar.gz", hash = "sha256:3c7f71cbba2b6310c4b14c2b11dae7737582ac004893f8c9cc3f7c305c7bbe49"},
|
||||||
|
]
|
||||||
|
"tomli 2.0.1" = [
|
||||||
|
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
|
||||||
|
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
|
||||||
|
]
|
||||||
|
"typing-extensions 4.1.1" = [
|
||||||
|
{file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"},
|
||||||
|
{file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"},
|
||||||
|
]
|
||||||
|
"waifu2x-ncnn-vulkan-python 1.0.1" = [
|
||||||
|
{file = "waifu2x-ncnn-vulkan-python-1.0.1.tar.gz", hash = "sha256:18dbdd00141fc427a5ef8996f0a0197f1d6f3086a7c60bb88934e1d2a3004801"},
|
||||||
|
]
|
||||||
|
"win32-setctime 1.1.0" = [
|
||||||
|
{file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"},
|
||||||
|
{file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"},
|
||||||
|
]
|
||||||
@@ -1,3 +1,59 @@
|
|||||||
|
[project]
|
||||||
|
name = "video2x"
|
||||||
|
description = "A video/image upscaling and frame interpolation framework"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.7"
|
||||||
|
license-expression = "AGPL-3.0-or-later"
|
||||||
|
keywords = [
|
||||||
|
"super-resolution",
|
||||||
|
"upscaling",
|
||||||
|
"neural-network",
|
||||||
|
"machine-learning",
|
||||||
|
]
|
||||||
|
authors = [{ name = "K4YT3X", email = "i@k4yt3x.com" }]
|
||||||
|
classifiers = [
|
||||||
|
"Environment :: Console",
|
||||||
|
"Operating System :: OS Independent",
|
||||||
|
"Programming Language :: Python",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.6",
|
||||||
|
"Programming Language :: Python :: 3.7",
|
||||||
|
"Programming Language :: Python :: 3.8",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Topic :: Multimedia :: Video",
|
||||||
|
"Topic :: Scientific/Engineering :: Image Processing",
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
"ffmpeg-python>=0.2.0",
|
||||||
|
"loguru>=0.6.0",
|
||||||
|
"opencv-python==4.5.5.64",
|
||||||
|
"pillow>=9.1.0",
|
||||||
|
"pynput>=1.7.6",
|
||||||
|
"rich>=12.0.0",
|
||||||
|
"waifu2x-ncnn-vulkan-python>=1.0.2rc3",
|
||||||
|
"srmd-ncnn-vulkan-python>=1.0.2",
|
||||||
|
"realsr-ncnn-vulkan-python>=1.0.4",
|
||||||
|
"rife-ncnn-vulkan-python>=1.1.2.post3",
|
||||||
|
"realcugan-ncnn-vulkan-python>=1.0.2",
|
||||||
|
]
|
||||||
|
dynamic = ["version"]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
homepage = "https://github.com/k4yt3x/video2x/"
|
||||||
|
documentation = "https://github.com/k4yt3x/video2x/wiki"
|
||||||
|
repository = "https://github.com/k4yt3x/video2x.git"
|
||||||
|
changelog = "https://github.com/k4yt3x/video2x/releases"
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
video2x = "video2x:main"
|
||||||
|
|
||||||
|
[tool.isort]
|
||||||
|
profile = "black"
|
||||||
|
|
||||||
|
[tool.pdm]
|
||||||
|
version = { from = "video2x/__init__.py" }
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools>=46.4", "wheel", "setuptools_scm[toml]>=3.4.3"]
|
requires = ["pdm-pep517>=0.12.0"]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "pdm.pep517.api"
|
||||||
|
|||||||
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
|
||||||
49
setup.cfg
49
setup.cfg
@@ -1,49 +0,0 @@
|
|||||||
# Name: Video2X PyPI setup file
|
|
||||||
# Creator: K4YT3X
|
|
||||||
# Date Created: June 17, 2021
|
|
||||||
# Last Modified: February 12, 2022
|
|
||||||
|
|
||||||
# build & publish commands
|
|
||||||
# pip install --user -U setuptools wheel twine build
|
|
||||||
# python -m build .
|
|
||||||
# twine upload --repository pypi dist/*
|
|
||||||
|
|
||||||
[metadata]
|
|
||||||
name = video2x
|
|
||||||
version = attr: video2x.__version__
|
|
||||||
author = K4YT3X
|
|
||||||
author_email = i@k4yt3x.com
|
|
||||||
license = GNU Affero General Public License v3.0
|
|
||||||
description = A video/GIF/image upscaler and frame interpolator
|
|
||||||
url = https://github.com/k4yt3x/video2x
|
|
||||||
long_description = file: README.md
|
|
||||||
long_description_content_type = text/markdown
|
|
||||||
classifiers =
|
|
||||||
Environment :: Console
|
|
||||||
Operating System :: OS Independent
|
|
||||||
Programming Language :: Python
|
|
||||||
Programming Language :: Python :: 3
|
|
||||||
Programming Language :: Python :: 3.6
|
|
||||||
Programming Language :: Python :: 3.7
|
|
||||||
Programming Language :: Python :: 3.8
|
|
||||||
Programming Language :: Python :: 3.9
|
|
||||||
Programming Language :: Python :: 3.10
|
|
||||||
Topic :: Multimedia :: Video
|
|
||||||
Topic :: Scientific/Engineering :: Image Processing
|
|
||||||
|
|
||||||
[options]
|
|
||||||
packages = find:
|
|
||||||
install_requires =
|
|
||||||
ffmpeg-python
|
|
||||||
loguru
|
|
||||||
opencv-python
|
|
||||||
pillow
|
|
||||||
rich
|
|
||||||
realsr-ncnn-vulkan-python
|
|
||||||
rife-ncnn-vulkan-python
|
|
||||||
srmd-ncnn-vulkan-python
|
|
||||||
waifu2x-ncnn-vulkan-python
|
|
||||||
python_requires = >=3.6
|
|
||||||
|
|
||||||
[options.entry_points]
|
|
||||||
console_scripts = video2x = video2x:main
|
|
||||||
BIN
tests/data/test_image.png
Normal file
BIN
tests/data/test_image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 92 KiB |
BIN
tests/data/test_image_ref.png
Normal file
BIN
tests/data/test_image_ref.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 465 KiB |
55
tests/test_upscaler.py
Normal file
55
tests/test_upscaler.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import ctypes
|
||||||
|
import multiprocessing
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import utils
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
from video2x import Upscaler, Video2X
|
||||||
|
|
||||||
|
|
||||||
|
def test_upscaling():
|
||||||
|
video2x = Video2X()
|
||||||
|
output_path = Path("data/test_video_output.mp4")
|
||||||
|
video2x.upscale(
|
||||||
|
Path("data/test_video.mp4"),
|
||||||
|
output_path,
|
||||||
|
None,
|
||||||
|
720,
|
||||||
|
3,
|
||||||
|
5,
|
||||||
|
0,
|
||||||
|
"waifu2x",
|
||||||
|
)
|
||||||
|
output_path.unlink()
|
||||||
|
|
||||||
|
|
||||||
|
def test_upscale_image():
|
||||||
|
|
||||||
|
# initialize upscaler instance
|
||||||
|
processing_queue = multiprocessing.Queue(maxsize=30)
|
||||||
|
processed_frames = multiprocessing.Manager().list([None])
|
||||||
|
pause = multiprocessing.Value(ctypes.c_bool, False)
|
||||||
|
upscaler = Upscaler(processing_queue, processed_frames, pause)
|
||||||
|
|
||||||
|
image = Image.open("data/test_image.png")
|
||||||
|
upscaled_image = upscaler.upscale_image(image, 1680, 960, "waifu2x", 3)
|
||||||
|
|
||||||
|
reference_image = Image.open("data/test_image_ref.png")
|
||||||
|
assert utils.get_image_diff(upscaled_image, reference_image) < 0.5
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_scaling_tasks():
|
||||||
|
dimensions = [320, 240, 3840, 2160]
|
||||||
|
|
||||||
|
for algorithm, correct_answer in [
|
||||||
|
("waifu2x", [2, 2, 2, 2]),
|
||||||
|
["srmd", [3, 4]],
|
||||||
|
("realsr", [4, 4]),
|
||||||
|
("realcugan", [3, 4]),
|
||||||
|
]:
|
||||||
|
assert Upscaler._get_scaling_tasks(*dimensions, algorithm) == correct_answer
|
||||||
18
tests/utils.py
Normal file
18
tests/utils.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from PIL import Image, ImageChops, ImageStat
|
||||||
|
|
||||||
|
|
||||||
|
def get_image_diff(image0: Image.Image, image1: Image.Image) -> float:
|
||||||
|
"""
|
||||||
|
calculate the percentage of differences between two images
|
||||||
|
|
||||||
|
:param image0 Image.Image: the first frame
|
||||||
|
:param image1 Image.Image: the second frame
|
||||||
|
:rtype float: the percent difference between the two images
|
||||||
|
"""
|
||||||
|
difference = ImageChops.difference(image0, image1)
|
||||||
|
difference_stat = ImageStat.Stat(difference)
|
||||||
|
percent_diff = sum(difference_stat.mean) / (len(difference_stat.mean) * 255) * 100
|
||||||
|
return percent_diff
|
||||||
@@ -19,14 +19,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
Name: Package Init
|
Name: Package Init
|
||||||
Author: K4YT3X
|
Author: K4YT3X
|
||||||
Date Created: July 3, 2021
|
Date Created: July 3, 2021
|
||||||
Last Modified: February 11, 2022
|
Last Modified: August 28, 2022
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# version assignment has to precede imports to
|
# version assignment has to precede imports to
|
||||||
# prevent setup.cfg from producing import errors
|
# prevent setup.cfg from producing import errors
|
||||||
__version__ = "5.0.0-beta2"
|
__version__ = "5.0.0-beta6"
|
||||||
|
|
||||||
# local imports
|
# flake8: noqa
|
||||||
from .video2x import Video2X
|
# let flake8 ignore this file to avoid F401 warnings
|
||||||
from .upscaler import Upscaler
|
# generated by the following lines
|
||||||
from .interpolator import Interpolator
|
from .interpolator import Interpolator
|
||||||
|
from .upscaler import Upscaler
|
||||||
|
from .video2x import Video2X
|
||||||
|
|||||||
@@ -19,12 +19,221 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
Name: Package Main
|
Name: Package Main
|
||||||
Author: K4YT3X
|
Author: K4YT3X
|
||||||
Date Created: July 3, 2021
|
Date Created: July 3, 2021
|
||||||
Last Modified: February 11, 2022
|
Last Modified: February 26, 2022
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# local imports
|
import argparse
|
||||||
from .video2x import main
|
import os
|
||||||
|
import pathlib
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
from rich import print as rich_print
|
||||||
|
|
||||||
|
from . import __version__
|
||||||
|
from .video2x import LOGURU_FORMAT, Video2X
|
||||||
|
|
||||||
|
LEGAL_INFO = f"""Video2X\t\t{__version__}
|
||||||
|
Author:\t\tK4YT3X
|
||||||
|
License:\tGNU AGPL v3
|
||||||
|
Github Page:\thttps://github.com/k4yt3x/video2x
|
||||||
|
Contact:\ti@k4yt3x.com"""
|
||||||
|
|
||||||
|
# algorithms available for upscaling tasks
|
||||||
|
UPSCALING_ALGORITHMS = [
|
||||||
|
"waifu2x",
|
||||||
|
"srmd",
|
||||||
|
"realsr",
|
||||||
|
"realcugan",
|
||||||
|
]
|
||||||
|
|
||||||
|
# algorithms available for frame interpolation tasks
|
||||||
|
INTERPOLATION_ALGORITHMS = ["rife"]
|
||||||
|
|
||||||
|
|
||||||
|
def parse_arguments() -> argparse.Namespace:
|
||||||
|
"""
|
||||||
|
parse command line arguments
|
||||||
|
|
||||||
|
:rtype argparse.Namespace: command parsing results
|
||||||
|
"""
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog="video2x",
|
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--version", help="show version information and exit", action="store_true"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-i",
|
||||||
|
"--input",
|
||||||
|
type=pathlib.Path,
|
||||||
|
help="input file/directory path",
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-o",
|
||||||
|
"--output",
|
||||||
|
type=pathlib.Path,
|
||||||
|
help="output file/directory path",
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-p", "--processes", type=int, help="number of processes to launch", default=1
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-l",
|
||||||
|
"--loglevel",
|
||||||
|
choices=["trace", "debug", "info", "success", "warning", "error", "critical"],
|
||||||
|
default="info",
|
||||||
|
)
|
||||||
|
|
||||||
|
# upscaler arguments
|
||||||
|
action = parser.add_subparsers(
|
||||||
|
help="action to perform", dest="action", required=True
|
||||||
|
)
|
||||||
|
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
upscale.add_argument("-w", "--width", type=int, help="output width")
|
||||||
|
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(
|
||||||
|
"-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 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",
|
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||||
|
add_help=False,
|
||||||
|
)
|
||||||
|
interpolate.add_argument(
|
||||||
|
"--help", action="help", help="show this help message and exit"
|
||||||
|
)
|
||||||
|
interpolate.add_argument(
|
||||||
|
"-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 percent difference between two adjacent frames exceeds this"
|
||||||
|
" value; set to 100 to interpolate all frames"
|
||||||
|
),
|
||||||
|
default=10,
|
||||||
|
)
|
||||||
|
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
"""
|
||||||
|
command line entrypoint for direct CLI invocation
|
||||||
|
|
||||||
|
:rtype int: 0 if completed successfully, else other int
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
# display version and lawful informaition
|
||||||
|
if "--version" in sys.argv:
|
||||||
|
rich_print(LEGAL_INFO)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# parse command line arguments
|
||||||
|
args = parse_arguments()
|
||||||
|
|
||||||
|
# check input/output file paths
|
||||||
|
if not args.input.exists():
|
||||||
|
logger.critical(f"Cannot find input file: {args.input}")
|
||||||
|
return 1
|
||||||
|
if not args.input.is_file():
|
||||||
|
logger.critical("Input path is not a file")
|
||||||
|
return 1
|
||||||
|
if not args.output.parent.exists():
|
||||||
|
logger.critical(f"Output directory does not exist: {args.output.parent}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# set logger level
|
||||||
|
if os.environ.get("LOGURU_LEVEL") is None:
|
||||||
|
os.environ["LOGURU_LEVEL"] = args.loglevel.upper()
|
||||||
|
|
||||||
|
# remove default handler
|
||||||
|
logger.remove()
|
||||||
|
|
||||||
|
# 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 video2x object
|
||||||
|
video2x = Video2X()
|
||||||
|
|
||||||
|
if args.action == "upscale":
|
||||||
|
video2x.upscale(
|
||||||
|
args.input,
|
||||||
|
args.output,
|
||||||
|
args.width,
|
||||||
|
args.height,
|
||||||
|
args.noise,
|
||||||
|
args.processes,
|
||||||
|
args.threshold,
|
||||||
|
args.algorithm,
|
||||||
|
)
|
||||||
|
|
||||||
|
elif args.action == "interpolate":
|
||||||
|
video2x.interpolate(
|
||||||
|
args.input,
|
||||||
|
args.output,
|
||||||
|
args.processes,
|
||||||
|
args.threshold,
|
||||||
|
args.algorithm,
|
||||||
|
)
|
||||||
|
|
||||||
|
# don't print the traceback for manual terminations
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
return 2
|
||||||
|
|
||||||
|
except Exception as error:
|
||||||
|
logger.exception(error)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# if no exceptions were produced
|
||||||
|
else:
|
||||||
|
logger.success("Processing completed successfully")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
sys.exit(main())
|
||||||
|
|||||||
@@ -19,23 +19,22 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
Name: Video Decoder
|
Name: Video Decoder
|
||||||
Author: K4YT3X
|
Author: K4YT3X
|
||||||
Date Created: June 17, 2021
|
Date Created: June 17, 2021
|
||||||
Last Modified: February 12, 2022
|
Last Modified: April 9, 2022
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# built-in imports
|
|
||||||
import contextlib
|
import contextlib
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import queue
|
|
||||||
import signal
|
import signal
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
from multiprocessing import Queue
|
||||||
|
from queue import Full
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
# third-party imports
|
|
||||||
from loguru import logger
|
|
||||||
from PIL import Image
|
|
||||||
import ffmpeg
|
import ffmpeg
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
from .pipe_printer import PipePrinter
|
||||||
|
|
||||||
# map Loguru log levels to FFmpeg log levels
|
# map Loguru log levels to FFmpeg log levels
|
||||||
LOGURU_FFMPEG_LOGLEVELS = {
|
LOGURU_FFMPEG_LOGLEVELS = {
|
||||||
@@ -49,36 +48,41 @@ LOGURU_FFMPEG_LOGLEVELS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class VideoDecoder(threading.Thread):
|
class VideoDecoder:
|
||||||
|
"""
|
||||||
|
A video decoder that generates frames read from FFmpeg.
|
||||||
|
|
||||||
|
:param input_path pathlib.Path: the input file's path
|
||||||
|
:param input_width int: the input file's width
|
||||||
|
:param input_height int: the input file's height
|
||||||
|
:param frame_rate float: the input file's frame rate
|
||||||
|
:param pil_ignore_max_image_pixels bool: setting this to True
|
||||||
|
disables PIL's "possible DDoS" warning
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
input_path: pathlib.Path,
|
input_path: pathlib.Path,
|
||||||
input_width: int,
|
input_width: int,
|
||||||
input_height: int,
|
input_height: int,
|
||||||
frame_rate: float,
|
frame_rate: float,
|
||||||
processing_queue: queue.Queue,
|
pil_ignore_max_image_pixels: bool = True,
|
||||||
processing_settings: tuple,
|
) -> None:
|
||||||
ignore_max_image_pixels=True,
|
|
||||||
):
|
|
||||||
threading.Thread.__init__(self)
|
|
||||||
self.running = False
|
|
||||||
self.input_path = input_path
|
self.input_path = input_path
|
||||||
self.input_width = input_width
|
self.input_width = input_width
|
||||||
self.input_height = input_height
|
self.input_height = input_height
|
||||||
self.processing_queue = processing_queue
|
|
||||||
self.processing_settings = processing_settings
|
|
||||||
|
|
||||||
# this disables the "possible DDoS" warning
|
# this disables the "possible DDoS" warning
|
||||||
if ignore_max_image_pixels:
|
if pil_ignore_max_image_pixels is True:
|
||||||
Image.MAX_IMAGE_PIXELS = None
|
Image.MAX_IMAGE_PIXELS = None
|
||||||
|
|
||||||
self.exception = None
|
|
||||||
self.decoder = subprocess.Popen(
|
self.decoder = subprocess.Popen(
|
||||||
ffmpeg.compile(
|
ffmpeg.compile(
|
||||||
ffmpeg.input(input_path, r=frame_rate)["v"]
|
ffmpeg.input(input_path, r=frame_rate)["v"]
|
||||||
.output("pipe:1", format="rawvideo", pix_fmt="rgb24", vsync="1")
|
.output("pipe:1", format="rawvideo", pix_fmt="rgb24")
|
||||||
.global_args("-hide_banner")
|
.global_args("-hide_banner")
|
||||||
.global_args("-nostats")
|
.global_args("-nostats")
|
||||||
|
.global_args("-nostdin")
|
||||||
.global_args(
|
.global_args(
|
||||||
"-loglevel",
|
"-loglevel",
|
||||||
LOGURU_FFMPEG_LOGLEVELS.get(
|
LOGURU_FFMPEG_LOGLEVELS.get(
|
||||||
@@ -87,82 +91,87 @@ class VideoDecoder(threading.Thread):
|
|||||||
),
|
),
|
||||||
overwrite_output=True,
|
overwrite_output=True,
|
||||||
),
|
),
|
||||||
|
env=dict(AV_LOG_FORCE_COLOR="TRUE", **os.environ),
|
||||||
|
stdin=subprocess.DEVNULL,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
# stderr=subprocess.DEVNULL,
|
stderr=subprocess.PIPE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# start the PIPE printer to start printing FFmpeg logs
|
||||||
|
self.pipe_printer = PipePrinter(self.decoder.stderr)
|
||||||
|
self.pipe_printer.start()
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
|
||||||
|
# continue yielding while FFmpeg continues to produce output
|
||||||
|
# it is possible to use := for this block to be more concise
|
||||||
|
# but it is purposefully avoided to remain compatible with Python 3.7
|
||||||
|
buffer = self.decoder.stdout.read(3 * self.input_width * self.input_height)
|
||||||
|
|
||||||
|
while len(buffer) > 0:
|
||||||
|
|
||||||
|
# convert raw bytes into image object
|
||||||
|
frame = Image.frombytes(
|
||||||
|
"RGB", (self.input_width, self.input_height), buffer
|
||||||
|
)
|
||||||
|
|
||||||
|
# return this frame
|
||||||
|
yield frame
|
||||||
|
|
||||||
|
# read the next frame
|
||||||
|
buffer = self.decoder.stdout.read(3 * self.input_width * self.input_height)
|
||||||
|
|
||||||
|
# automatically self-join and clean up after iterations are done
|
||||||
|
self.join()
|
||||||
|
|
||||||
|
def kill(self):
|
||||||
|
self.decoder.send_signal(signal.SIGKILL)
|
||||||
|
|
||||||
|
def join(self):
|
||||||
|
|
||||||
|
# close PIPEs to prevent process from getting stuck
|
||||||
|
self.decoder.stdout.close()
|
||||||
|
self.decoder.stderr.close()
|
||||||
|
|
||||||
|
# wait for process to exit
|
||||||
|
self.decoder.wait()
|
||||||
|
|
||||||
|
# wait for PIPE printer to exit
|
||||||
|
self.pipe_printer.stop()
|
||||||
|
self.pipe_printer.join()
|
||||||
|
|
||||||
|
|
||||||
|
class VideoDecoderThread(Thread):
|
||||||
|
def __init__(
|
||||||
|
self, tasks_queue: Queue, decoder: VideoDecoder, processing_settings: tuple
|
||||||
|
):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.tasks_queue = tasks_queue
|
||||||
|
self.decoder = decoder
|
||||||
|
self.processing_settings = processing_settings
|
||||||
|
self.running = False
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.running = True
|
self.running = True
|
||||||
|
previous_frame = None
|
||||||
|
for frame_index, frame in enumerate(self.decoder):
|
||||||
|
|
||||||
# the index of the frame
|
while True:
|
||||||
frame_index = 0
|
|
||||||
|
|
||||||
# create placeholder for previous frame
|
# check for the stop signal
|
||||||
# used in interpolate mode
|
if self.running is False:
|
||||||
previous_image = None
|
self.decoder.join()
|
||||||
|
return
|
||||||
|
|
||||||
# continue running until an exception occurs
|
with contextlib.suppress(Full):
|
||||||
# or all frames have been decoded
|
self.tasks_queue.put(
|
||||||
while self.running:
|
(frame_index, previous_frame, frame, self.processing_settings),
|
||||||
try:
|
timeout=0.1,
|
||||||
buffer = self.decoder.stdout.read(
|
)
|
||||||
3 * self.input_width * self.input_height
|
|
||||||
)
|
|
||||||
|
|
||||||
# source depleted (decoding finished)
|
|
||||||
# after the last frame has been decoded
|
|
||||||
# read will return nothing
|
|
||||||
if len(buffer) == 0:
|
|
||||||
logger.debug("Decoding queue depleted")
|
|
||||||
break
|
break
|
||||||
|
|
||||||
# convert raw bytes into image object
|
previous_frame = frame
|
||||||
image = Image.frombytes(
|
|
||||||
"RGB", (self.input_width, self.input_height), buffer
|
|
||||||
)
|
|
||||||
|
|
||||||
# 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:
|
|
||||||
|
|
||||||
# ignore queue closed
|
|
||||||
if not "is closed" in str(e):
|
|
||||||
logger.exception(e)
|
|
||||||
break
|
|
||||||
|
|
||||||
# send exceptions into the client connection pipe
|
|
||||||
except Exception as e:
|
|
||||||
self.exception = e
|
|
||||||
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.info("Decoder thread exiting")
|
|
||||||
|
|
||||||
self.running = False
|
|
||||||
return super().run()
|
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self.running = False
|
self.running = False
|
||||||
|
|||||||
@@ -19,24 +19,18 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
Name: Video Encoder
|
Name: Video Encoder
|
||||||
Author: K4YT3X
|
Author: K4YT3X
|
||||||
Date Created: June 17, 2021
|
Date Created: June 17, 2021
|
||||||
Last Modified: June 30, 2021
|
Last Modified: August 28, 2022
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# built-in imports
|
|
||||||
import multiprocessing
|
|
||||||
import multiprocessing.managers
|
|
||||||
import multiprocessing.sharedctypes
|
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import signal
|
import signal
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
|
|
||||||
# third-party imports
|
|
||||||
from loguru import logger
|
|
||||||
import ffmpeg
|
import ffmpeg
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
from .pipe_printer import PipePrinter
|
||||||
|
|
||||||
# map Loguru log levels to FFmpeg log levels
|
# map Loguru log levels to FFmpeg log levels
|
||||||
LOGURU_FFMPEG_LOGLEVELS = {
|
LOGURU_FFMPEG_LOGLEVELS = {
|
||||||
@@ -50,7 +44,7 @@ LOGURU_FFMPEG_LOGLEVELS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class VideoEncoder(threading.Thread):
|
class VideoEncoder:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
input_path: pathlib.Path,
|
input_path: pathlib.Path,
|
||||||
@@ -58,55 +52,51 @@ class VideoEncoder(threading.Thread):
|
|||||||
output_path: pathlib.Path,
|
output_path: pathlib.Path,
|
||||||
output_width: int,
|
output_width: int,
|
||||||
output_height: int,
|
output_height: int,
|
||||||
total_frames: int,
|
copy_audio: bool = True,
|
||||||
processed_frames: multiprocessing.managers.ListProxy,
|
copy_subtitle: bool = True,
|
||||||
processed: multiprocessing.sharedctypes.Synchronized,
|
copy_data: bool = False,
|
||||||
):
|
copy_attachments: bool = False,
|
||||||
threading.Thread.__init__(self)
|
) -> None:
|
||||||
self.running = False
|
|
||||||
self.input_path = input_path
|
|
||||||
self.output_path = output_path
|
|
||||||
self.total_frames = total_frames
|
|
||||||
self.processed_frames = processed_frames
|
|
||||||
self.processed = processed
|
|
||||||
|
|
||||||
self.original = ffmpeg.input(input_path)
|
# create FFmpeg input for the original input video
|
||||||
|
original = ffmpeg.input(input_path)
|
||||||
|
|
||||||
# define frames as input
|
# define frames as input
|
||||||
frames = ffmpeg.input(
|
frames = ffmpeg.input(
|
||||||
"pipe:0",
|
"pipe:0",
|
||||||
format="rawvideo",
|
format="rawvideo",
|
||||||
pix_fmt="rgb24",
|
pix_fmt="rgb24",
|
||||||
vsync="1",
|
|
||||||
s=f"{output_width}x{output_height}",
|
s=f"{output_width}x{output_height}",
|
||||||
r=frame_rate,
|
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 = [
|
additional_streams = [
|
||||||
# self.original["v?"],
|
# original["1:v?"],
|
||||||
self.original["a?"],
|
original["a?"] if copy_audio is True else None,
|
||||||
self.original["s?"],
|
original["s?"] if copy_subtitle is True else None,
|
||||||
self.original["d?"],
|
original["d?"] if copy_data is True else None,
|
||||||
self.original["t?"],
|
original["t?"] if copy_attachments is True else None,
|
||||||
]
|
]
|
||||||
"""
|
|
||||||
|
|
||||||
# run FFmpeg and produce final output
|
# run FFmpeg and produce final output
|
||||||
self.encoder = subprocess.Popen(
|
self.encoder = subprocess.Popen(
|
||||||
ffmpeg.compile(
|
ffmpeg.compile(
|
||||||
ffmpeg.output(
|
ffmpeg.output(
|
||||||
frames,
|
frames,
|
||||||
str(self.output_path),
|
*[s for s in additional_streams if s is not None],
|
||||||
pix_fmt="yuv420p",
|
str(output_path),
|
||||||
vcodec="libx264",
|
vcodec="libx264",
|
||||||
acodec="copy",
|
scodec="copy",
|
||||||
r=frame_rate,
|
pix_fmt="yuv420p",
|
||||||
crf=17,
|
crf=17,
|
||||||
vsync="1",
|
preset="veryslow",
|
||||||
# map_metadata=1,
|
# acodec="libfdk_aac",
|
||||||
# metadata="comment=Upscaled with Video2X",
|
# cutoff=20000,
|
||||||
|
r=frame_rate,
|
||||||
|
map_metadata=1,
|
||||||
|
metadata="comment=Processed with Video2X",
|
||||||
)
|
)
|
||||||
.global_args("-hide_banner")
|
.global_args("-hide_banner")
|
||||||
.global_args("-nostats")
|
.global_args("-nostats")
|
||||||
@@ -118,52 +108,42 @@ class VideoEncoder(threading.Thread):
|
|||||||
),
|
),
|
||||||
overwrite_output=True,
|
overwrite_output=True,
|
||||||
),
|
),
|
||||||
|
env=dict(AV_LOG_FORCE_COLOR="TRUE", **os.environ),
|
||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
# stdout=subprocess.DEVNULL,
|
stderr=subprocess.PIPE,
|
||||||
# stderr=subprocess.DEVNULL,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(self):
|
# start the PIPE printer to start printing FFmpeg logs
|
||||||
self.running = True
|
self.pipe_printer = PipePrinter(self.encoder.stderr)
|
||||||
frame_index = 0
|
self.pipe_printer.start()
|
||||||
while self.running and frame_index < self.total_frames:
|
|
||||||
try:
|
|
||||||
image = self.processed_frames[frame_index]
|
|
||||||
if image is None:
|
|
||||||
time.sleep(0.1)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# send the image to FFmpeg for encoding
|
def kill(self):
|
||||||
self.encoder.stdin.write(image.tobytes())
|
self.encoder.send_signal(signal.SIGKILL)
|
||||||
|
|
||||||
# remove the image from memory
|
def write(self, frame: Image.Image) -> None:
|
||||||
self.processed_frames[frame_index] = None
|
"""
|
||||||
|
write a frame into FFmpeg encoder's STDIN
|
||||||
|
|
||||||
with self.processed.get_lock():
|
:param frame Image.Image: the Image object to use for writing
|
||||||
self.processed.value += 1
|
"""
|
||||||
|
self.encoder.stdin.write(frame.tobytes())
|
||||||
|
|
||||||
frame_index += 1
|
def join(self) -> None:
|
||||||
|
"""
|
||||||
# send exceptions into the client connection pipe
|
signal the encoder that all frames have been sent and the FFmpeg
|
||||||
except Exception as e:
|
should be instructed to wrap-up the processing
|
||||||
logger.exception(e)
|
"""
|
||||||
break
|
# flush the remaining data in STDIN and STDERR
|
||||||
|
|
||||||
# flush the remaining data in STDIN and close PIPE
|
|
||||||
logger.debug("Encoding queue depleted")
|
|
||||||
self.encoder.stdin.flush()
|
self.encoder.stdin.flush()
|
||||||
|
self.encoder.stderr.flush()
|
||||||
|
|
||||||
|
# close PIPEs to prevent process from getting stuck
|
||||||
self.encoder.stdin.close()
|
self.encoder.stdin.close()
|
||||||
|
self.encoder.stderr.close()
|
||||||
|
|
||||||
# send SIGINT (2) to FFmpeg
|
# wait for process to exit
|
||||||
# this instructs it to finalize and exit
|
|
||||||
self.encoder.send_signal(signal.SIGINT)
|
|
||||||
|
|
||||||
# wait for process to terminate
|
|
||||||
self.encoder.wait()
|
self.encoder.wait()
|
||||||
logger.info("Encoder thread exiting")
|
|
||||||
|
|
||||||
self.running = False
|
# wait for PIPE printer to exit
|
||||||
return super().run()
|
self.pipe_printer.stop()
|
||||||
|
self.pipe_printer.join()
|
||||||
def stop(self):
|
|
||||||
self.running = False
|
|
||||||
|
|||||||
@@ -19,53 +19,58 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
Name: Interpolator
|
Name: Interpolator
|
||||||
Author: K4YT3X
|
Author: K4YT3X
|
||||||
Date Created: May 27, 2021
|
Date Created: May 27, 2021
|
||||||
Last Modified: February 2, 2022
|
Last Modified: March 20, 2022
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# local imports
|
|
||||||
from rife_ncnn_vulkan_python.rife_ncnn_vulkan import Rife
|
|
||||||
|
|
||||||
# built-in imports
|
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import multiprocessing.managers
|
|
||||||
import multiprocessing.sharedctypes
|
|
||||||
import queue
|
import queue
|
||||||
import signal
|
import signal
|
||||||
import time
|
import time
|
||||||
|
from multiprocessing.managers import ListProxy
|
||||||
|
from multiprocessing.sharedctypes import Synchronized
|
||||||
|
|
||||||
# third-party imports
|
|
||||||
from PIL import ImageChops, ImageStat
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
from PIL import ImageChops, ImageStat
|
||||||
|
from rife_ncnn_vulkan_python.rife_ncnn_vulkan import Rife
|
||||||
|
|
||||||
|
ALGORITHM_CLASSES = {"rife": Rife}
|
||||||
DRIVER_CLASSES = {"rife": Rife}
|
|
||||||
|
|
||||||
|
|
||||||
class Interpolator(multiprocessing.Process):
|
class Interpolator(multiprocessing.Process):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
processing_queue: multiprocessing.Queue,
|
processing_queue: multiprocessing.Queue,
|
||||||
processed_frames: multiprocessing.managers.ListProxy,
|
processed_frames: ListProxy,
|
||||||
):
|
pause: Synchronized,
|
||||||
|
) -> None:
|
||||||
multiprocessing.Process.__init__(self)
|
multiprocessing.Process.__init__(self)
|
||||||
self.running = False
|
|
||||||
self.processing_queue = processing_queue
|
self.processing_queue = processing_queue
|
||||||
self.processed_frames = processed_frames
|
self.processed_frames = processed_frames
|
||||||
|
self.pause = pause
|
||||||
|
|
||||||
|
self.running = False
|
||||||
|
self.processor_objects = {}
|
||||||
|
|
||||||
signal.signal(signal.SIGTERM, self._stop)
|
signal.signal(signal.SIGTERM, self._stop)
|
||||||
|
|
||||||
def run(self):
|
def run(self) -> None:
|
||||||
self.running = True
|
self.running = True
|
||||||
logger.info(f"Interpolator process {self.name} initiating")
|
logger.opt(colors=True).info(
|
||||||
driver_objects = {}
|
f"Interpolator process <blue>{self.name}</blue> initiating"
|
||||||
while self.running:
|
)
|
||||||
|
while self.running is True:
|
||||||
try:
|
try:
|
||||||
|
# pause if pause flag is set
|
||||||
|
if self.pause.value is True:
|
||||||
|
time.sleep(0.1)
|
||||||
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# get new job from queue
|
# get new job from queue
|
||||||
(
|
(
|
||||||
frame_index,
|
frame_index,
|
||||||
(image0, image1),
|
(image0, image1),
|
||||||
(difference_threshold, driver),
|
(difference_threshold, algorithm),
|
||||||
) = self.processing_queue.get(False)
|
) = self.processing_queue.get(False)
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
@@ -76,6 +81,7 @@ class Interpolator(multiprocessing.Process):
|
|||||||
if image0 is None:
|
if image0 is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# calculate the %diff between the current frame and the previous frame
|
||||||
difference = ImageChops.difference(image0, image1)
|
difference = ImageChops.difference(image0, image1)
|
||||||
difference_stat = ImageStat.Stat(difference)
|
difference_stat = ImageStat.Stat(difference)
|
||||||
difference_ratio = (
|
difference_ratio = (
|
||||||
@@ -86,13 +92,13 @@ class Interpolator(multiprocessing.Process):
|
|||||||
# process the interpolation
|
# process the interpolation
|
||||||
if difference_ratio < difference_threshold:
|
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
|
# create a new object if none are available
|
||||||
driver_object = driver_objects.get(driver)
|
processor_object = self.processor_objects.get(algorithm)
|
||||||
if driver_object is None:
|
if processor_object is None:
|
||||||
driver_object = DRIVER_CLASSES[driver](0)
|
processor_object = ALGORITHM_CLASSES[algorithm](0)
|
||||||
driver_objects[driver] = driver_object
|
self.processor_objects[algorithm] = processor_object
|
||||||
interpolated_image = driver_object.process(image0, image1)
|
interpolated_image = processor_object.process(image0, image1)
|
||||||
|
|
||||||
# if the difference is greater than threshold
|
# if the difference is greater than threshold
|
||||||
# there's a change in camera angle, ignore
|
# there's a change in camera angle, ignore
|
||||||
@@ -108,13 +114,14 @@ class Interpolator(multiprocessing.Process):
|
|||||||
except (SystemExit, KeyboardInterrupt):
|
except (SystemExit, KeyboardInterrupt):
|
||||||
break
|
break
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as error:
|
||||||
logger.exception(e)
|
logger.exception(error)
|
||||||
break
|
break
|
||||||
|
|
||||||
logger.info(f"Interpolator process {self.name} terminating")
|
logger.opt(colors=True).info(
|
||||||
self.running = False
|
f"Interpolator process <blue>{self.name}</blue> terminating"
|
||||||
|
)
|
||||||
return super().run()
|
return super().run()
|
||||||
|
|
||||||
def _stop(self, _signal_number, _frame):
|
def _stop(self, _signal_number, _frame) -> None:
|
||||||
self.running = False
|
self.running = False
|
||||||
|
|||||||
63
video2x/pipe_printer.py
Executable file
63
video2x/pipe_printer.py
Executable file
@@ -0,0 +1,63 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Copyright (C) 2018-2022 K4YT3X and contributors.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
published by the Free Software Foundation, either version 3 of the
|
||||||
|
License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Name: PIPE Printer
|
||||||
|
Author: K4YT3X
|
||||||
|
Date Created: February 27, 2022
|
||||||
|
Last Modified: February 28, 2022
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
from typing import IO
|
||||||
|
|
||||||
|
|
||||||
|
class PipePrinter(threading.Thread):
|
||||||
|
def __init__(self, stderr: IO[bytes]) -> None:
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
self.stderr = stderr
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
# set read mode to non-blocking
|
||||||
|
os.set_blocking(self.stderr.fileno(), False)
|
||||||
|
|
||||||
|
def _print_output(self) -> None:
|
||||||
|
output = self.stderr.read()
|
||||||
|
if output is not None and len(output) != 0:
|
||||||
|
print(output.decode(), file=sys.stderr)
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
self.running = True
|
||||||
|
|
||||||
|
# keep printing contents in the PIPE
|
||||||
|
while self.running is True:
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._print_output()
|
||||||
|
|
||||||
|
# pipe closed
|
||||||
|
except ValueError:
|
||||||
|
break
|
||||||
|
|
||||||
|
return super().run()
|
||||||
|
|
||||||
|
def stop(self) -> None:
|
||||||
|
self.running = False
|
||||||
69
video2x/processor.py
Executable file
69
video2x/processor.py
Executable file
@@ -0,0 +1,69 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Copyright (C) 2018-2022 K4YT3X and contributors.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
published by the Free Software Foundation, either version 3 of the
|
||||||
|
License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Name: Processor Abstract Class
|
||||||
|
Author: K4YT3X
|
||||||
|
Date Created: April 9, 2022
|
||||||
|
Last Modified: April 9, 2022
|
||||||
|
"""
|
||||||
|
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from multiprocessing import Queue
|
||||||
|
from multiprocessing.managers import DictProxy
|
||||||
|
from multiprocessing.sharedctypes import Synchronized
|
||||||
|
|
||||||
|
from PIL import Image, ImageChops, ImageStat
|
||||||
|
|
||||||
|
|
||||||
|
class Processor(ABC):
|
||||||
|
def __init__(
|
||||||
|
self, tasks_queue: Queue, processed_frames: DictProxy, pause_flag: Synchronized
|
||||||
|
) -> None:
|
||||||
|
self.tasks_queue = tasks_queue
|
||||||
|
self.processed_frames = processed_frames
|
||||||
|
self.pause_flag = pause_flag
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def process(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_image_diff(image0: Image.Image, image1: Image.Image) -> float:
|
||||||
|
"""
|
||||||
|
get the percentage difference between two images
|
||||||
|
|
||||||
|
:param image0 Image.Image: the image to compare
|
||||||
|
:param image1 Image.Image: the image to compare against
|
||||||
|
:rtype float: precentage difference between two frames
|
||||||
|
"""
|
||||||
|
difference_stat = ImageStat.Stat(ImageChops.difference(image0, image1))
|
||||||
|
return sum(difference_stat.mean) / (len(difference_stat.mean) * 255) * 100
|
||||||
|
|
||||||
|
"""
|
||||||
|
def run(
|
||||||
|
self,
|
||||||
|
) -> None:
|
||||||
|
self.running = True
|
||||||
|
while self.running is True:
|
||||||
|
self.process()
|
||||||
|
self.running = False
|
||||||
|
return super().run()
|
||||||
|
|
||||||
|
def stop(self, _signal_number, _frame) -> None:
|
||||||
|
self.running = False
|
||||||
|
"""
|
||||||
@@ -19,179 +19,184 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
Name: Upscaler
|
Name: Upscaler
|
||||||
Author: K4YT3X
|
Author: K4YT3X
|
||||||
Date Created: May 27, 2021
|
Date Created: May 27, 2021
|
||||||
Last Modified: August 17, 2021
|
Last Modified: April 10, 2022
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# local imports
|
|
||||||
from realsr_ncnn_vulkan_python.realsr_ncnn_vulkan import Realsr
|
|
||||||
from srmd_ncnn_vulkan_python.srmd_ncnn_vulkan import Srmd
|
|
||||||
from waifu2x_ncnn_vulkan_python.waifu2x_ncnn_vulkan import Waifu2x
|
|
||||||
|
|
||||||
# built-in imports
|
|
||||||
import math
|
import math
|
||||||
import multiprocessing
|
|
||||||
import multiprocessing.managers
|
|
||||||
import multiprocessing.sharedctypes
|
|
||||||
import queue
|
|
||||||
import signal
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
# third-party imports
|
from PIL import Image
|
||||||
from PIL import Image, ImageChops, ImageStat
|
from realcugan_ncnn_vulkan_python import Realcugan
|
||||||
from loguru import logger
|
from realsr_ncnn_vulkan_python import Realsr
|
||||||
|
from srmd_ncnn_vulkan_python import Srmd
|
||||||
|
from waifu2x_ncnn_vulkan_python import Waifu2x
|
||||||
|
|
||||||
# fixed scaling ratios supported by the drivers
|
from .processor import Processor
|
||||||
# that only support certain fixed scale ratios
|
|
||||||
DRIVER_FIXED_SCALING_RATIOS = {
|
|
||||||
"waifu2x": [1, 2],
|
|
||||||
"srmd": [2, 3, 4],
|
|
||||||
"realsr": [4],
|
|
||||||
}
|
|
||||||
|
|
||||||
DRIVER_CLASSES = {"waifu2x": Waifu2x, "srmd": Srmd, "realsr": Realsr}
|
|
||||||
|
|
||||||
|
|
||||||
class Upscaler(multiprocessing.Process):
|
class Upscaler:
|
||||||
def __init__(
|
# fixed scaling ratios supported by the algorithms
|
||||||
self,
|
# that only support certain fixed scale ratios
|
||||||
processing_queue: multiprocessing.Queue,
|
ALGORITHM_FIXED_SCALING_RATIOS = {
|
||||||
processed_frames: multiprocessing.managers.ListProxy,
|
"waifu2x": [1, 2],
|
||||||
):
|
"srmd": [2, 3, 4],
|
||||||
multiprocessing.Process.__init__(self)
|
"realsr": [4],
|
||||||
self.running = False
|
"realcugan": [1, 2, 3, 4],
|
||||||
self.processing_queue = processing_queue
|
}
|
||||||
self.processed_frames = processed_frames
|
|
||||||
|
|
||||||
signal.signal(signal.SIGTERM, self._stop)
|
ALGORITHM_CLASSES = {
|
||||||
|
"waifu2x": Waifu2x,
|
||||||
|
"srmd": Srmd,
|
||||||
|
"realsr": Realsr,
|
||||||
|
"realcugan": Realcugan,
|
||||||
|
}
|
||||||
|
|
||||||
def run(self):
|
processor_objects = {}
|
||||||
self.running = True
|
|
||||||
logger.opt(colors=True).info(
|
@staticmethod
|
||||||
f"Upscaler process <blue>{self.name}</blue> initiating"
|
def _get_scaling_tasks(
|
||||||
|
input_width: int,
|
||||||
|
input_height: int,
|
||||||
|
output_width: int,
|
||||||
|
output_height: int,
|
||||||
|
algorithm: str,
|
||||||
|
) -> list:
|
||||||
|
"""
|
||||||
|
Get the required tasks for upscaling the image until it is larger than
|
||||||
|
or equal to the desired output dimensions. For example, SRMD only supports
|
||||||
|
2x, 3x, and 4x, so upsclaing an image from 320x240 to 3840x2160 will
|
||||||
|
require the SRMD to run 3x then 4x. In this case, this function will
|
||||||
|
return [3, 4].
|
||||||
|
|
||||||
|
:param input_width int: input image width
|
||||||
|
:param input_height int: input image height
|
||||||
|
:param output_width int: desired output image width
|
||||||
|
:param output_height int: desired output image size
|
||||||
|
:param algorithm str: upsclaing algorithm
|
||||||
|
:rtype list: the list of upsclaing tasks required
|
||||||
|
"""
|
||||||
|
# calculate required minimum scale ratio
|
||||||
|
output_scale = max(output_width / input_width, output_height / input_height)
|
||||||
|
|
||||||
|
# select the optimal algorithm scaling ratio to use
|
||||||
|
supported_scaling_ratios = sorted(
|
||||||
|
Upscaler.ALGORITHM_FIXED_SCALING_RATIOS[algorithm]
|
||||||
)
|
)
|
||||||
driver_objects = {}
|
|
||||||
while self.running:
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
# get new job from queue
|
|
||||||
(
|
|
||||||
frame_index,
|
|
||||||
(image0, image1),
|
|
||||||
(
|
|
||||||
output_width,
|
|
||||||
output_height,
|
|
||||||
noise,
|
|
||||||
difference_threshold,
|
|
||||||
driver,
|
|
||||||
),
|
|
||||||
) = self.processing_queue.get(False)
|
|
||||||
|
|
||||||
# destructure settings
|
remaining_scaling_ratio = math.ceil(output_scale)
|
||||||
except queue.Empty:
|
|
||||||
|
# if the scaling ratio is 1.0
|
||||||
|
# apply the smallest scaling ratio available
|
||||||
|
if remaining_scaling_ratio == 1:
|
||||||
|
return [supported_scaling_ratios[0]]
|
||||||
|
|
||||||
|
scaling_jobs = []
|
||||||
|
while remaining_scaling_ratio > 1:
|
||||||
|
for ratio in supported_scaling_ratios:
|
||||||
|
if ratio >= remaining_scaling_ratio:
|
||||||
|
scaling_jobs.append(ratio)
|
||||||
|
remaining_scaling_ratio /= ratio
|
||||||
|
break
|
||||||
|
|
||||||
|
else:
|
||||||
|
found = False
|
||||||
|
for i in supported_scaling_ratios:
|
||||||
|
for j in supported_scaling_ratios:
|
||||||
|
if i * j >= remaining_scaling_ratio:
|
||||||
|
scaling_jobs.extend([i, j])
|
||||||
|
remaining_scaling_ratio /= i * j
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
if found is True:
|
||||||
|
break
|
||||||
|
|
||||||
|
if found is False:
|
||||||
|
scaling_jobs.append(supported_scaling_ratios[-1])
|
||||||
|
remaining_scaling_ratio /= supported_scaling_ratios[-1]
|
||||||
|
return scaling_jobs
|
||||||
|
|
||||||
|
def upscale_image(
|
||||||
|
self,
|
||||||
|
image: Image.Image,
|
||||||
|
output_width: int,
|
||||||
|
output_height: int,
|
||||||
|
algorithm: str,
|
||||||
|
noise: int,
|
||||||
|
) -> Image.Image:
|
||||||
|
"""
|
||||||
|
upscale an image
|
||||||
|
|
||||||
|
:param image Image.Image: the image to upscale
|
||||||
|
:param output_width int: the desired output width
|
||||||
|
:param output_height int: the desired output height
|
||||||
|
:param algorithm str: the algorithm to use
|
||||||
|
:param noise int: the noise level (available only for some algorithms)
|
||||||
|
:rtype Image.Image: the upscaled image
|
||||||
|
"""
|
||||||
|
width, height = image.size
|
||||||
|
|
||||||
|
for task in self._get_scaling_tasks(
|
||||||
|
width, height, output_width, output_height, algorithm
|
||||||
|
):
|
||||||
|
|
||||||
|
# select a processor object with the required settings
|
||||||
|
# create a new object if none are available
|
||||||
|
processor_object = self.processor_objects.get((algorithm, task))
|
||||||
|
if processor_object is None:
|
||||||
|
processor_object = self.ALGORITHM_CLASSES[algorithm](
|
||||||
|
noise=noise, scale=task
|
||||||
|
)
|
||||||
|
self.processor_objects[(algorithm, task)] = processor_object
|
||||||
|
|
||||||
|
# process the image with the selected algorithm
|
||||||
|
image = processor_object.process(image)
|
||||||
|
|
||||||
|
# downscale the image to the desired output size and
|
||||||
|
# save the image to disk
|
||||||
|
return image.resize((output_width, output_height), Image.Resampling.LANCZOS)
|
||||||
|
|
||||||
|
|
||||||
|
class UpscalerProcessor(Processor, Upscaler):
|
||||||
|
def process(self) -> None:
|
||||||
|
|
||||||
|
task = self.tasks_queue.get()
|
||||||
|
while task is not None:
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
if self.pause_flag.value is True:
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# unpack the task's values
|
||||||
|
(
|
||||||
|
frame_index,
|
||||||
|
previous_frame,
|
||||||
|
current_frame,
|
||||||
|
(output_width, output_height, algorithm, noise, threshold),
|
||||||
|
) = task
|
||||||
|
|
||||||
|
# calculate the %diff between the current frame and the previous frame
|
||||||
difference_ratio = 0
|
difference_ratio = 0
|
||||||
if image0 is not None:
|
if previous_frame is not None:
|
||||||
difference = ImageChops.difference(image0, image1)
|
difference_ratio = self.get_image_diff(
|
||||||
difference_stat = ImageStat.Stat(difference)
|
previous_frame, current_frame
|
||||||
difference_ratio = (
|
|
||||||
sum(difference_stat.mean)
|
|
||||||
/ (len(difference_stat.mean) * 255)
|
|
||||||
* 100
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# if the difference is lower than threshold
|
# if the difference is lower than threshold, skip this frame
|
||||||
# skip this frame
|
if difference_ratio < threshold:
|
||||||
if difference_ratio < difference_threshold:
|
|
||||||
|
|
||||||
# make sure the previous frame has been processed
|
|
||||||
if frame_index > 0:
|
|
||||||
while self.processed_frames[frame_index - 1] is None:
|
|
||||||
time.sleep(0.1)
|
|
||||||
|
|
||||||
# make the current image the same as the previous result
|
# make the current image the same as the previous result
|
||||||
self.processed_frames[frame_index] = self.processed_frames[
|
self.processed_frames[frame_index] = True
|
||||||
frame_index - 1
|
|
||||||
]
|
|
||||||
|
|
||||||
# if the difference is greater than threshold
|
# if the difference is greater than threshold
|
||||||
# process this frame
|
# process this frame
|
||||||
else:
|
else:
|
||||||
width, height = image1.size
|
self.processed_frames[frame_index] = self.upscale_image(
|
||||||
|
current_frame, output_width, output_height, algorithm, noise
|
||||||
# calculate required minimum scale ratio
|
|
||||||
output_scale = max(output_width / width, output_height / height)
|
|
||||||
|
|
||||||
# select the optimal driver scaling ratio to use
|
|
||||||
supported_scaling_ratios = sorted(
|
|
||||||
DRIVER_FIXED_SCALING_RATIOS[driver]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
remaining_scaling_ratio = math.ceil(output_scale)
|
task = self.tasks_queue.get()
|
||||||
scaling_jobs = []
|
|
||||||
|
|
||||||
# if the scaling ratio is 1.0
|
except KeyboardInterrupt:
|
||||||
# apply the smallest scaling ratio available
|
|
||||||
if remaining_scaling_ratio == 1:
|
|
||||||
scaling_jobs.append(supported_scaling_ratios[0])
|
|
||||||
else:
|
|
||||||
while remaining_scaling_ratio > 1:
|
|
||||||
for ratio in supported_scaling_ratios:
|
|
||||||
if ratio >= remaining_scaling_ratio:
|
|
||||||
scaling_jobs.append(ratio)
|
|
||||||
remaining_scaling_ratio /= ratio
|
|
||||||
break
|
|
||||||
|
|
||||||
else:
|
|
||||||
found = False
|
|
||||||
for i in supported_scaling_ratios:
|
|
||||||
for j in supported_scaling_ratios:
|
|
||||||
if i * j >= remaining_scaling_ratio:
|
|
||||||
scaling_jobs.extend([i, j])
|
|
||||||
remaining_scaling_ratio /= i * j
|
|
||||||
found = True
|
|
||||||
break
|
|
||||||
if found is True:
|
|
||||||
break
|
|
||||||
|
|
||||||
if found is False:
|
|
||||||
scaling_jobs.append(supported_scaling_ratios[-1])
|
|
||||||
remaining_scaling_ratio /= supported_scaling_ratios[
|
|
||||||
-1
|
|
||||||
]
|
|
||||||
|
|
||||||
for job in scaling_jobs:
|
|
||||||
|
|
||||||
# select a driver 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](
|
|
||||||
scale=job, noise=noise
|
|
||||||
)
|
|
||||||
driver_objects[(driver, job)] = driver_object
|
|
||||||
|
|
||||||
# process the image with the selected driver
|
|
||||||
image1 = driver_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)
|
|
||||||
self.processed_frames[frame_index] = image1
|
|
||||||
|
|
||||||
# send exceptions into the client connection pipe
|
|
||||||
except (SystemExit, KeyboardInterrupt):
|
|
||||||
break
|
break
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception(e)
|
|
||||||
break
|
|
||||||
|
|
||||||
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):
|
|
||||||
self.running = False
|
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ __ __ _ _ ___ __ __
|
|||||||
|
|
||||||
Name: Video2X
|
Name: Video2X
|
||||||
Creator: K4YT3X
|
Creator: K4YT3X
|
||||||
Date Created: Feb 24, 2018
|
Date Created: February 24, 2018
|
||||||
Last Modified: February 12, 2022
|
Last Modified: August 28, 2022
|
||||||
|
|
||||||
Editor: BrianPetkovsek
|
Editor: BrianPetkovsek
|
||||||
Last Modified: June 17, 2019
|
Last Modified: June 17, 2019
|
||||||
@@ -39,25 +39,18 @@ Editor: 28598519a
|
|||||||
Last Modified: March 23, 2020
|
Last Modified: March 23, 2020
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# local imports
|
import ctypes
|
||||||
from . import __version__
|
|
||||||
from .decoder import VideoDecoder
|
|
||||||
from .encoder import VideoEncoder
|
|
||||||
from .interpolator import Interpolator
|
|
||||||
from .upscaler import Upscaler
|
|
||||||
|
|
||||||
# built-in imports
|
|
||||||
import argparse
|
|
||||||
import math
|
import math
|
||||||
import multiprocessing
|
import signal
|
||||||
import os
|
|
||||||
import pathlib
|
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
from enum import Enum
|
||||||
|
from multiprocessing import Manager, Pool, Queue, Value
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
# third-party imports
|
import ffmpeg
|
||||||
|
from cv2 import cv2
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from rich import print
|
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.file_proxy import FileProxy
|
from rich.file_proxy import FileProxy
|
||||||
from rich.progress import (
|
from rich.progress import (
|
||||||
@@ -69,36 +62,23 @@ from rich.progress import (
|
|||||||
TimeRemainingColumn,
|
TimeRemainingColumn,
|
||||||
)
|
)
|
||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
import cv2
|
|
||||||
import ffmpeg
|
|
||||||
|
|
||||||
|
from video2x.processor import Processor
|
||||||
|
|
||||||
LEGAL_INFO = """Video2X {}
|
from . import __version__
|
||||||
Author: K4YT3X
|
from .decoder import VideoDecoder, VideoDecoderThread
|
||||||
License: GNU GPL v3
|
from .encoder import VideoEncoder
|
||||||
Github Page: https://github.com/k4yt3x/video2x
|
from .interpolator import Interpolator
|
||||||
Contact: k4yt3x@k4yt3x.com""".format(
|
from .upscaler import UpscalerProcessor
|
||||||
__version__
|
|
||||||
)
|
|
||||||
|
|
||||||
UPSCALING_DRIVERS = [
|
# for desktop environments only
|
||||||
"waifu2x",
|
# if pynput can be loaded, enable global pause hotkey support
|
||||||
"srmd",
|
try:
|
||||||
"realsr",
|
from pynput.keyboard import HotKey, Listener
|
||||||
]
|
except ImportError:
|
||||||
|
ENABLE_HOTKEY = False
|
||||||
INTERPOLATION_DRIVERS = ["rife"]
|
else:
|
||||||
|
ENABLE_HOTKEY = True
|
||||||
# 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
|
# format string for Loguru loggers
|
||||||
LOGURU_FORMAT = (
|
LOGURU_FORMAT = (
|
||||||
@@ -119,6 +99,11 @@ class ProcessingSpeedColumn(ProgressColumn):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessingMode(Enum):
|
||||||
|
UPSCALE = {"label": "Upscaling", "processor": UpscalerProcessor}
|
||||||
|
INTERPOLATE = {"label": "Interpolating", "processor": Interpolator}
|
||||||
|
|
||||||
|
|
||||||
class Video2X:
|
class Video2X:
|
||||||
"""
|
"""
|
||||||
Video2X class
|
Video2X class
|
||||||
@@ -128,14 +113,15 @@ class Video2X:
|
|||||||
- interpolate: perform motion interpolation on a file
|
- interpolate: perform motion interpolation on a file
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
self.version = "5.0.0"
|
self.version = __version__
|
||||||
|
|
||||||
def _get_video_info(self, path: pathlib.Path):
|
@staticmethod
|
||||||
|
def _get_video_info(path: Path) -> tuple:
|
||||||
"""
|
"""
|
||||||
get video file information with FFmpeg
|
get video file information with FFmpeg
|
||||||
|
|
||||||
:param path pathlib.Path: video file path
|
:param path Path: video file path
|
||||||
:raises RuntimeError: raised when video stream isn't found
|
:raises RuntimeError: raised when video stream isn't found
|
||||||
"""
|
"""
|
||||||
# probe video file info
|
# probe video file info
|
||||||
@@ -161,19 +147,18 @@ class Video2X:
|
|||||||
|
|
||||||
def _run(
|
def _run(
|
||||||
self,
|
self,
|
||||||
input_path: pathlib.Path,
|
input_path: Path,
|
||||||
width: int,
|
width: int,
|
||||||
height: int,
|
height: int,
|
||||||
total_frames: int,
|
total_frames: int,
|
||||||
frame_rate: float,
|
frame_rate: float,
|
||||||
output_path: pathlib.Path,
|
output_path: Path,
|
||||||
output_width: int,
|
output_width: int,
|
||||||
output_height: int,
|
output_height: int,
|
||||||
Processor: object,
|
mode: ProcessingMode,
|
||||||
mode: str,
|
|
||||||
processes: int,
|
processes: int,
|
||||||
processing_settings: tuple,
|
processing_settings: tuple,
|
||||||
):
|
) -> None:
|
||||||
|
|
||||||
# record original STDOUT and STDERR for restoration
|
# record original STDOUT and STDERR for restoration
|
||||||
original_stdout = sys.stdout
|
original_stdout = sys.stdout
|
||||||
@@ -190,117 +175,176 @@ class Video2X:
|
|||||||
logger.remove()
|
logger.remove()
|
||||||
logger.add(sys.stderr, colorize=True, format=LOGURU_FORMAT)
|
logger.add(sys.stderr, colorize=True, format=LOGURU_FORMAT)
|
||||||
|
|
||||||
# initialize values
|
# TODO: add docs
|
||||||
self.processor_processes = []
|
tasks_queue = Queue(maxsize=processes * 10)
|
||||||
self.processing_queue = multiprocessing.Queue(maxsize=processes * 10)
|
processed_frames = Manager().dict()
|
||||||
processed_frames = multiprocessing.Manager().list([None] * total_frames)
|
pause_flag = Value(ctypes.c_bool, False)
|
||||||
self.processed = multiprocessing.Value("I", 0)
|
|
||||||
|
|
||||||
# set up and start decoder thread
|
# set up and start decoder thread
|
||||||
logger.info("Starting video decoder")
|
logger.info("Starting video decoder")
|
||||||
self.decoder = VideoDecoder(
|
decoder = VideoDecoder(
|
||||||
input_path,
|
input_path,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
frame_rate,
|
frame_rate,
|
||||||
self.processing_queue,
|
|
||||||
processing_settings,
|
|
||||||
)
|
)
|
||||||
self.decoder.start()
|
decoder_thread = VideoDecoderThread(tasks_queue, decoder, processing_settings)
|
||||||
|
decoder_thread.start()
|
||||||
|
|
||||||
# set up and start encoder thread
|
# set up and start encoder thread
|
||||||
logger.info("Starting video encoder")
|
logger.info("Starting video encoder")
|
||||||
self.encoder = VideoEncoder(
|
encoder = VideoEncoder(
|
||||||
input_path,
|
input_path,
|
||||||
frame_rate * 2 if mode == "interpolate" else frame_rate,
|
frame_rate * 2 if mode == "interpolate" else frame_rate,
|
||||||
output_path,
|
output_path,
|
||||||
output_width,
|
output_width,
|
||||||
output_height,
|
output_height,
|
||||||
total_frames,
|
|
||||||
processed_frames,
|
|
||||||
self.processed,
|
|
||||||
)
|
)
|
||||||
self.encoder.start()
|
|
||||||
|
|
||||||
# create processor processes
|
# create a pool of processor processes to process the queue
|
||||||
for process_name in range(processes):
|
processor: Processor = mode.value["processor"](
|
||||||
process = Processor(self.processing_queue, processed_frames)
|
tasks_queue, processed_frames, pause_flag
|
||||||
process.name = str(process_name)
|
)
|
||||||
process.daemon = True
|
processor_pool = Pool(processes, processor.process)
|
||||||
process.start()
|
|
||||||
self.processor_processes.append(process)
|
# create progress bar
|
||||||
|
progress = Progress(
|
||||||
|
"[progress.description]{task.description}",
|
||||||
|
BarColumn(complete_style="blue", finished_style="green"),
|
||||||
|
"[progress.percentage]{task.percentage:>3.0f}%",
|
||||||
|
"[color(240)]({task.completed}/{task.total})",
|
||||||
|
ProcessingSpeedColumn(),
|
||||||
|
TimeElapsedColumn(),
|
||||||
|
"<",
|
||||||
|
TimeRemainingColumn(),
|
||||||
|
console=console,
|
||||||
|
speed_estimate_period=300.0,
|
||||||
|
disable=True,
|
||||||
|
)
|
||||||
|
task = progress.add_task(f"[cyan]{mode.value['label']}", total=total_frames)
|
||||||
|
|
||||||
|
def _toggle_pause(_signal_number: int = -1, _frame=None):
|
||||||
|
|
||||||
|
# allow the closure to modify external immutable flag
|
||||||
|
nonlocal pause_flag
|
||||||
|
|
||||||
|
# print console messages and update the progress bar's status
|
||||||
|
if pause_flag.value is False:
|
||||||
|
progress.update(
|
||||||
|
task, description=f"[cyan]{mode.value['label']} (paused)"
|
||||||
|
)
|
||||||
|
progress.stop_task(task)
|
||||||
|
logger.warning("Processing paused, press Ctrl+Alt+V again to resume")
|
||||||
|
|
||||||
|
# the lock is already acquired
|
||||||
|
elif pause_flag.value is True:
|
||||||
|
progress.update(task, description=f"[cyan]{mode.value['label']}")
|
||||||
|
logger.warning("Resuming processing")
|
||||||
|
progress.start_task(task)
|
||||||
|
|
||||||
|
# invert the flag
|
||||||
|
with pause_flag.get_lock():
|
||||||
|
pause_flag.value = not pause_flag.value
|
||||||
|
|
||||||
|
# allow sending SIGUSR1 to pause/resume processing
|
||||||
|
signal.signal(signal.SIGUSR1, _toggle_pause)
|
||||||
|
|
||||||
|
# enable global pause hotkey if it's supported
|
||||||
|
if ENABLE_HOTKEY is True:
|
||||||
|
|
||||||
|
# create global pause hotkey
|
||||||
|
pause_hotkey = HotKey(HotKey.parse("<ctrl>+<alt>+v"), _toggle_pause)
|
||||||
|
|
||||||
|
# create global keyboard input listener
|
||||||
|
keyboard_listener = Listener(
|
||||||
|
on_press=(
|
||||||
|
lambda key: pause_hotkey.press(keyboard_listener.canonical(key))
|
||||||
|
),
|
||||||
|
on_release=(
|
||||||
|
lambda key: pause_hotkey.release(keyboard_listener.canonical(key))
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# start monitoring global key presses
|
||||||
|
keyboard_listener.start()
|
||||||
|
|
||||||
# a temporary variable that stores the exception
|
# a temporary variable that stores the exception
|
||||||
exception = []
|
exceptions = []
|
||||||
|
|
||||||
try:
|
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
|
# let the context manager automatically stop the progress bar
|
||||||
while self.processed.value < total_frames - 1:
|
with progress:
|
||||||
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
|
frame_index = 0
|
||||||
if progress.disable is True and self.processed.value > 0:
|
while frame_index < total_frames:
|
||||||
|
|
||||||
|
current_frame = processed_frames.get(frame_index)
|
||||||
|
|
||||||
|
if pause_flag.value is True or current_frame is None:
|
||||||
|
time.sleep(0.1)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# show the progress bar after the processing starts
|
||||||
|
# reduces speed estimation inaccuracies and print overlaps
|
||||||
|
if frame_index == 0:
|
||||||
progress.disable = False
|
progress.disable = False
|
||||||
progress.start()
|
progress.start()
|
||||||
|
|
||||||
# update progress
|
if current_frame is True:
|
||||||
progress.update(task, completed=self.processed.value)
|
encoder.write(processed_frames.get(frame_index - 1))
|
||||||
|
|
||||||
progress.update(task, completed=total_frames)
|
else:
|
||||||
logger.info("Processing has completed")
|
encoder.write(current_frame)
|
||||||
|
|
||||||
|
if frame_index > 0:
|
||||||
|
del processed_frames[frame_index - 1]
|
||||||
|
|
||||||
|
progress.update(task, completed=frame_index + 1)
|
||||||
|
frame_index += 1
|
||||||
|
|
||||||
# if SIGTERM is received or ^C is pressed
|
# if SIGTERM is received or ^C is pressed
|
||||||
# TODO: pause and continue here
|
except (SystemExit, KeyboardInterrupt) as error:
|
||||||
except (SystemExit, KeyboardInterrupt) as e:
|
logger.warning("Exit signal received, exiting gracefully")
|
||||||
logger.warning("Exit signal received, terminating")
|
logger.warning("Press ^C again to force terminate")
|
||||||
exception.append(e)
|
exceptions.append(error)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as error:
|
||||||
logger.exception(e)
|
logger.exception(error)
|
||||||
exception.append(e)
|
exceptions.append(error)
|
||||||
|
|
||||||
|
else:
|
||||||
|
logger.info("Processing has completed")
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
# mark processing queue as closed
|
|
||||||
self.processing_queue.close()
|
|
||||||
|
|
||||||
# stop upscaler processes
|
# stop keyboard listener
|
||||||
logger.info("Stopping upscaler processes")
|
if ENABLE_HOTKEY is True:
|
||||||
for process in self.processor_processes:
|
keyboard_listener.stop()
|
||||||
process.terminate()
|
keyboard_listener.join()
|
||||||
|
|
||||||
# wait for processes to finish
|
# if errors have occurred, kill the FFmpeg processes
|
||||||
for process in self.processor_processes:
|
if len(exceptions) > 0:
|
||||||
process.join()
|
decoder.kill()
|
||||||
|
encoder.kill()
|
||||||
|
|
||||||
# ensure both the decoder and the encoder have exited
|
# stop the decoder
|
||||||
self.decoder.stop()
|
decoder_thread.stop()
|
||||||
self.encoder.stop()
|
decoder_thread.join()
|
||||||
self.decoder.join()
|
|
||||||
self.encoder.join()
|
|
||||||
|
|
||||||
# raise the error if there is any
|
# clear queue and signal processors to exit
|
||||||
if len(exception) > 0:
|
# multiprocessing.Queue has no Queue.queue.clear
|
||||||
raise exception[0]
|
while tasks_queue.empty() is not True:
|
||||||
|
tasks_queue.get()
|
||||||
|
for _ in range(processes):
|
||||||
|
tasks_queue.put(None)
|
||||||
|
|
||||||
|
# close and join the process pool
|
||||||
|
processor_pool.close()
|
||||||
|
processor_pool.join()
|
||||||
|
|
||||||
|
# stop the encoder
|
||||||
|
encoder.join()
|
||||||
|
|
||||||
# restore original STDOUT and STDERR
|
# restore original STDOUT and STDERR
|
||||||
sys.stdout = original_stdout
|
sys.stdout = original_stdout
|
||||||
@@ -310,16 +354,20 @@ class Video2X:
|
|||||||
logger.remove()
|
logger.remove()
|
||||||
logger.add(sys.stderr, colorize=True, format=LOGURU_FORMAT)
|
logger.add(sys.stderr, colorize=True, format=LOGURU_FORMAT)
|
||||||
|
|
||||||
|
# raise the first collected error
|
||||||
|
if len(exceptions) > 0:
|
||||||
|
raise exceptions[0]
|
||||||
|
|
||||||
def upscale(
|
def upscale(
|
||||||
self,
|
self,
|
||||||
input_path: pathlib.Path,
|
input_path: Path,
|
||||||
output_path: pathlib.Path,
|
output_path: Path,
|
||||||
output_width: int,
|
output_width: int,
|
||||||
output_height: int,
|
output_height: int,
|
||||||
noise: int,
|
noise: int,
|
||||||
processes: int,
|
processes: int,
|
||||||
threshold: float,
|
threshold: float,
|
||||||
driver: str,
|
algorithm: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
# get basic video information
|
# get basic video information
|
||||||
@@ -346,25 +394,24 @@ class Video2X:
|
|||||||
output_path,
|
output_path,
|
||||||
output_width,
|
output_width,
|
||||||
output_height,
|
output_height,
|
||||||
Upscaler,
|
ProcessingMode.UPSCALE,
|
||||||
"upscale",
|
|
||||||
processes,
|
processes,
|
||||||
(
|
(
|
||||||
output_width,
|
output_width,
|
||||||
output_height,
|
output_height,
|
||||||
|
algorithm,
|
||||||
noise,
|
noise,
|
||||||
threshold,
|
threshold,
|
||||||
driver,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def interpolate(
|
def interpolate(
|
||||||
self,
|
self,
|
||||||
input_path: pathlib.Path,
|
input_path: Path,
|
||||||
output_path: pathlib.Path,
|
output_path: Path,
|
||||||
processes: int,
|
processes: int,
|
||||||
threshold: float,
|
threshold: float,
|
||||||
driver: str,
|
algorithm: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
# get video basic information
|
# get video basic information
|
||||||
@@ -383,164 +430,7 @@ class Video2X:
|
|||||||
output_path,
|
output_path,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
Interpolator,
|
ProcessingMode.INTERPOLATE,
|
||||||
"interpolate",
|
|
||||||
processes,
|
processes,
|
||||||
(threshold, driver),
|
(threshold, algorithm),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def parse_arguments() -> argparse.Namespace:
|
|
||||||
"""
|
|
||||||
parse command line arguments
|
|
||||||
|
|
||||||
:rtype argparse.Namespace: command parsing results
|
|
||||||
"""
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
prog="video2x",
|
|
||||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-v", "--version", help="show version information and exit", action="store_true"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-i",
|
|
||||||
"--input",
|
|
||||||
type=pathlib.Path,
|
|
||||||
help="input file/directory path",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-o",
|
|
||||||
"--output",
|
|
||||||
type=pathlib.Path,
|
|
||||||
help="output file/directory path",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-p", "--processes", type=int, help="number of processes to launch", default=1
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-l",
|
|
||||||
"--loglevel",
|
|
||||||
choices=["trace", "debug", "info", "success", "warning", "error", "critical"],
|
|
||||||
default="info",
|
|
||||||
)
|
|
||||||
|
|
||||||
# upscaler arguments
|
|
||||||
action = parser.add_subparsers(
|
|
||||||
help="action to perform", dest="action", required=True
|
|
||||||
)
|
|
||||||
|
|
||||||
upscale = action.add_parser("upscale", help="upscale a file", add_help=False)
|
|
||||||
upscale.add_argument(
|
|
||||||
"--help", action="help", help="show this help message and exit"
|
|
||||||
)
|
|
||||||
upscale.add_argument("-w", "--width", type=int, help="output width")
|
|
||||||
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],
|
|
||||||
)
|
|
||||||
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",
|
|
||||||
default=0,
|
|
||||||
)
|
|
||||||
|
|
||||||
# interpolator arguments
|
|
||||||
interpolate = action.add_parser(
|
|
||||||
"interpolate", help="interpolate frames for file", 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],
|
|
||||||
)
|
|
||||||
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",
|
|
||||||
default=10,
|
|
||||||
)
|
|
||||||
|
|
||||||
return parser.parse_args()
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""
|
|
||||||
command line direct invocation
|
|
||||||
program entry point
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
|
|
||||||
# parse command line arguments
|
|
||||||
args = parse_arguments()
|
|
||||||
|
|
||||||
# set logger level
|
|
||||||
if os.environ.get("LOGURU_LEVEL") is None:
|
|
||||||
os.environ["LOGURU_LEVEL"] = args.loglevel.upper()
|
|
||||||
|
|
||||||
# remove default handler
|
|
||||||
logger.remove()
|
|
||||||
|
|
||||||
# add new sink with custom handler
|
|
||||||
logger.add(sys.stderr, colorize=True, format=LOGURU_FORMAT)
|
|
||||||
|
|
||||||
# display version and lawful informaition
|
|
||||||
if args.version:
|
|
||||||
print(LEGAL_INFO)
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
# 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()
|
|
||||||
|
|
||||||
if args.action == "upscale":
|
|
||||||
video2x.upscale(
|
|
||||||
args.input,
|
|
||||||
args.output,
|
|
||||||
args.width,
|
|
||||||
args.height,
|
|
||||||
args.noise,
|
|
||||||
args.processes,
|
|
||||||
args.threshold,
|
|
||||||
args.driver,
|
|
||||||
)
|
|
||||||
|
|
||||||
elif args.action == "interpolate":
|
|
||||||
video2x.interpolate(
|
|
||||||
args.input,
|
|
||||||
args.output,
|
|
||||||
args.processes,
|
|
||||||
args.threshold,
|
|
||||||
args.driver,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.success("Processing completed successfully")
|
|
||||||
|
|
||||||
# don't print the traceback for manual terminations
|
|
||||||
except (SystemExit, KeyboardInterrupt) as e:
|
|
||||||
raise SystemExit(e)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception(e)
|
|
||||||
raise SystemExit(e)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user