22 Commits

Author SHA1 Message Date
k4yt3x
1b67f373d1 removed UESR nobody as it breaks permissions 2022-04-01 06:51:27 +00:00
k4yt3x
7215ee1921 fixed release pipeline ref quoting 2022-04-01 06:27:15 +00:00
k4yt3x
65c0d32a51 updated the Dockerfiles and the pipeline for 5.0.0-beta5 2022-04-01 06:23:46 +00:00
k4yt3x
268460fd17 added pynput as dependency; fixed hotkey in headless env; disabled FFmpeg stdin 2022-03-21 21:46:08 +00:00
k4yt3x
d72ecb332a fixed variable name error 2022-03-21 03:43:48 +00:00
k4yt3x
8ba56e58c2 added pause function 2022-03-21 03:28:19 +00:00
k4yt3x
fa485b6cdd restricted Vulkan ICD files in Dockerfile 2022-03-21 00:21:00 +00:00
k4yt3x
625b340f3d added RealCUGAN 2022-03-19 17:45:03 +00:00
k4yt3x
fa3bd38217 fixed minor flake8 formatting issues 2022-03-18 23:14:20 +00:00
k4yt3x
0fca5c8e9a added cuda drivers to cuda Dockerfile; updated slim Dockerfile for current version 2022-03-18 23:10:40 +00:00
k4yt3x
237f6336ec sorted encoder input and updated vsync to cft from 1 2022-03-05 21:30:01 +00:00
k4yt3x
083af60c6f sorted imports using isort 2022-03-01 21:11:50 +00:00
k4yt3x
f7756886e7 changed progress bar color 2022-02-28 17:33:16 +00:00
k4yt3x
6e72df66fc added colored threat id for interp 2022-02-28 06:56:16 +00:00
k4yt3x
2bfcb13976 fixed fail to exit upon ^C 2022-02-28 06:49:41 +00:00
k4yt3x
49e0375eee updated PDM dependencies 2022-02-28 06:45:56 +00:00
k4yt3x
4459f4d3be redirected subprocess outputs into Rich console 2022-02-28 01:54:52 +00:00
k4yt3x
c0fe81bd2e improved main function return handling 2022-02-27 06:21:46 +00:00
k4yt3x
8cb64d3b70 install rife directly from PyPI; changed setuptools to pdm 2022-02-27 02:39:48 +00:00
k4yt3x
0b154a715c changed builder from setuptools to pdm 2022-02-27 02:38:56 +00:00
k4yt3x
05031b2b5a fixed GHA release workflow hook 2022-02-19 04:59:45 +00:00
k4yt3x
2510c8fa28 bumped script versions to beta4 2022-02-19 03:21:47 +00:00
17 changed files with 995 additions and 220 deletions

View File

@@ -1,13 +1,12 @@
name: Release
on:
push:
branches:
- master
tags:
- "*"
jobs:
setup:
if: github.event.base_ref == 'refs/heads/master'
name: Setup
runs-on: ubuntu-latest
outputs:
@@ -41,21 +40,39 @@ jobs:
needs:
- setup
- create-release
strategy:
matrix:
version:
- slim-alpine
- cuda
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- uses: mr-smithers-excellent/docker-build-push@v5
name: Build & push Docker image
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: latest, ${{ needs.setup.outputs.tag }}-${{ matrix.version }}
tags: ${{ needs.setup.outputs.tag }}-${{ matrix.version }}

4
.gitignore vendored
View File

@@ -1,3 +1,7 @@
# PDM
.pdm.toml
__pypackages__/
# test videos
*.mp4
*.mkv

View File

@@ -1,43 +1,42 @@
# Name: Video2X Dockerfile (CUDA)
# Name: Video2X Dockerfile
# Creator: K4YT3X
# Date Created: February 3, 2022
# Last Modified: February 12, 2022
# Last Modified: March 28, 2022
# 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.2.133-450 AS builder
ENV DEBIAN_FRONTEND=noninteractive
COPY . /video2x
WORKDIR /video2x
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
python3-pip python3-opencv python3-pil \
python3-dev libvulkan-dev glslang-dev glslang-tools \
build-essential swig git \
&& git config --global http.postBuffer 1048576000 \
&& 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 .
python3.8 python3-pip python3-opencv python3-pil \
python3.8-dev libvulkan-dev glslang-dev glslang-tools \
build-essential swig \
&& pip wheel -w /wheels wheel pdm-pep517 .
# 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.2.133-450
LABEL maintainer="K4YT3X <i@k4yt3x.com>" \
org.opencontainers.image.source="https://github.com/k4yt3x/video2x" \
org.opencontainers.image.description="A lossless video/GIF/image upscaler"
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 /wheels /wheels
COPY . /video2x
WORKDIR /video2x
RUN apt-get install -y --no-install-recommends \
python3-pip python3-dev \
python3-pip python3.8-dev \
python3-opencv python3-pil \
mesa-vulkan-drivers ffmpeg \
mesa-vulkan-drivers cuda-drivers ffmpeg \
&& pip install --no-cache-dir --no-index -f /wheels . \
&& apt-get clean \
&& rm -rf /wheels /video2x /var/lib/apt/lists/*
WORKDIR /host
ENTRYPOINT ["/usr/bin/python3", "-m", "video2x"]
ENTRYPOINT ["/usr/bin/python3.8", "-m", "video2x"]

View File

@@ -1,23 +1,21 @@
# Name: Video2X Dockerfile (Slim Alpine)
# Creator: K4YT3X
# Date Created: February 1, 2022
# Last Modified: February 4, 2022
# Last Modified: March 18, 2022
# 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
WORKDIR /video2x
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 \
linux-headers \
&& pip install -U pip \
&& CMAKE_ARGS="-DWITH_FFMPEG=YES" pip wheel -w /wheels opencv-python \
&& pip wheel -w /wheels wheel setuptools setuptools_scm \
rife-ncnn-vulkan-python@git+https://github.com/media2x/rife-ncnn-vulkan-python.git .
&& pip wheel -w /wheels wheel pdm-pep517 .
# 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>" \
org.opencontainers.image.source="https://github.com/k4yt3x/video2x" \
org.opencontainers.image.description="A lossless video/GIF/image upscaler"

View File

@@ -5,6 +5,6 @@ set -euxo pipefail
sudo podman run \
-it --rm --gpus all -v /dev/dri:/dev/dri \
-v $PWD/data:/host \
ghcr.io/k4yt3x/video2x:5.0.0-beta3-cuda \
ghcr.io/k4yt3x/video2x:5.0.0-beta4-cuda \
-i input.mp4 -o output.mp4 \
interpolate

View File

@@ -5,7 +5,7 @@ set -euxo pipefail
sudo podman run \
-it --rm --gpus all -v /dev/dri:/dev/dri \
-v $PWD/data:/host \
ghcr.io/k4yt3x/video2x:5.0.0-beta3-cuda \
ghcr.io/k4yt3x/video2x:5.0.0-beta4-cuda \
-i input.mp4 -o output.mp4 \
-p5 upscale \
-h 720 -a waifu2x -n3

533
pdm.lock generated Normal file
View 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"},
]

View File

@@ -1,3 +1,56 @@
[project]
name = "video2x"
description = "A video/image upscaling and frame interpolation framework"
readme = "README.md"
requires-python = ">=3.7"
license = { text = "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.0.1",
"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.0",
"pynput>=1.7.6",
]
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.pdm]
version = { from = "video2x/__init__.py" }
[build-system]
requires = ["setuptools>=46.4", "wheel", "setuptools_scm[toml]>=3.4.3"]
build-backend = "setuptools.build_meta"
requires = ["pdm-pep517"]
build-backend = "pdm.pep517.api"

View File

@@ -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

View File

@@ -24,9 +24,8 @@ Last Modified: February 16, 2022
# version assignment has to precede imports to
# prevent setup.cfg from producing import errors
__version__ = "5.0.0-beta4"
__version__ = "5.0.0-beta5"
# local imports
from .video2x import Video2X
from .upscaler import Upscaler
from .interpolator import Interpolator
from .upscaler import Upscaler
from .video2x import Video2X

View File

@@ -19,12 +19,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
Name: Package Main
Author: K4YT3X
Date Created: July 3, 2021
Last Modified: February 11, 2022
Last Modified: February 26, 2022
"""
# local imports
import sys
from .video2x import main
if __name__ == "__main__":
main()
sys.exit(main())

View File

@@ -19,23 +19,25 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
Name: Video Decoder
Author: K4YT3X
Date Created: June 17, 2021
Last Modified: February 16, 2022
Last Modified: March 21, 2022
"""
# built-in imports
import contextlib
import multiprocessing
import os
import pathlib
import queue
import signal
import subprocess
import threading
import time
from multiprocessing.sharedctypes import Synchronized
# third-party imports
import ffmpeg
from loguru import logger
from PIL import Image
import ffmpeg
from .pipe_printer import PipePrinter
# map Loguru log levels to FFmpeg log levels
LOGURU_FFMPEG_LOGLEVELS = {
@@ -56,8 +58,9 @@ class VideoDecoder(threading.Thread):
input_width: int,
input_height: int,
frame_rate: float,
processing_queue: queue.Queue,
processing_queue: multiprocessing.Queue,
processing_settings: tuple,
pause: Synchronized,
ignore_max_image_pixels=True,
) -> None:
threading.Thread.__init__(self)
@@ -67,6 +70,7 @@ class VideoDecoder(threading.Thread):
self.input_height = input_height
self.processing_queue = processing_queue
self.processing_settings = processing_settings
self.pause = pause
# this disables the "possible DDoS" warning
if ignore_max_image_pixels:
@@ -76,9 +80,10 @@ class VideoDecoder(threading.Thread):
self.decoder = subprocess.Popen(
ffmpeg.compile(
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", vsync="cfr")
.global_args("-hide_banner")
.global_args("-nostats")
.global_args("-nostdin")
.global_args(
"-loglevel",
LOGURU_FFMPEG_LOGLEVELS.get(
@@ -87,10 +92,16 @@ class VideoDecoder(threading.Thread):
),
overwrite_output=True,
),
env={"AV_LOG_FORCE_COLOR": "TRUE"},
stdin=subprocess.DEVNULL,
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 run(self) -> None:
self.running = True
@@ -104,6 +115,12 @@ class VideoDecoder(threading.Thread):
# continue running until an exception occurs
# or all frames have been decoded
while self.running:
# pause if pause flag is set
if self.pause.value is True:
time.sleep(0.1)
continue
try:
buffer = self.decoder.stdout.read(
3 * self.input_width * self.input_height
@@ -113,8 +130,8 @@ class VideoDecoder(threading.Thread):
# after the last frame has been decoded
# read will return nothing
if len(buffer) == 0:
logger.debug("Decoding queue depleted")
break
self.stop()
continue
# convert raw bytes into image object
image = Image.frombytes(
@@ -140,9 +157,10 @@ class VideoDecoder(threading.Thread):
# most likely "not enough image data"
except ValueError as e:
self.exception = e
# ignore queue closed
if not "is closed" in str(e):
if "is closed" not in str(e):
logger.exception(e)
break
@@ -151,17 +169,29 @@ class VideoDecoder(threading.Thread):
self.exception = e
logger.exception(e)
break
else:
logger.debug("Decoding queue depleted")
# flush the remaining data in STDOUT and STDERR
self.decoder.stdout.flush()
self.decoder.stderr.flush()
# send SIGINT (2) to FFmpeg
# this instructs it to finalize and exit
if self.decoder.poll() is None:
self.decoder.send_signal(signal.SIGTERM)
self.decoder.send_signal(signal.SIGINT)
# ensure the decoder has exited
# close PIPEs to prevent process from getting stuck
self.decoder.stdout.close()
self.decoder.stderr.close()
# wait for process to exit
self.decoder.wait()
logger.info("Decoder thread exiting")
self.running = False
# wait for PIPE printer to exit
self.pipe_printer.stop()
self.pipe_printer.join()
logger.info("Decoder thread exiting")
return super().run()
def stop(self) -> None:

View File

@@ -19,24 +19,22 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
Name: Video Encoder
Author: K4YT3X
Date Created: June 17, 2021
Last Modified: February 16, 2022
Last Modified: March 20, 2022
"""
# built-in imports
import multiprocessing
import multiprocessing.managers
import multiprocessing.sharedctypes
import os
import pathlib
import signal
import subprocess
import threading
import time
from multiprocessing.managers import ListProxy
from multiprocessing.sharedctypes import Synchronized
# third-party imports
from loguru import logger
import ffmpeg
from loguru import logger
from .pipe_printer import PipePrinter
# map Loguru log levels to FFmpeg log levels
LOGURU_FFMPEG_LOGLEVELS = {
@@ -59,8 +57,9 @@ class VideoEncoder(threading.Thread):
output_width: int,
output_height: int,
total_frames: int,
processed_frames: multiprocessing.managers.ListProxy,
processed: multiprocessing.sharedctypes.Synchronized,
processed_frames: ListProxy,
processed: Synchronized,
pause: Synchronized,
copy_audio: bool = True,
copy_subtitle: bool = True,
copy_data: bool = False,
@@ -73,7 +72,12 @@ class VideoEncoder(threading.Thread):
self.total_frames = total_frames
self.processed_frames = processed_frames
self.processed = processed
self.pause = pause
# stores exceptions if the thread exits with errors
self.exception = None
# create FFmpeg input for the original input video
self.original = ffmpeg.input(input_path)
# define frames as input
@@ -81,7 +85,7 @@ class VideoEncoder(threading.Thread):
"pipe:0",
format="rawvideo",
pix_fmt="rgb24",
vsync="1",
vsync="cfr",
s=f"{output_width}x{output_height}",
r=frame_rate,
)
@@ -103,12 +107,14 @@ class VideoEncoder(threading.Thread):
frames,
*[s for s in additional_streams if s is not None],
str(self.output_path),
pix_fmt="yuv420p",
vcodec="libx264",
# acodec="copy",
r=frame_rate,
vsync="cfr",
pix_fmt="yuv420p",
crf=17,
vsync="1",
preset="veryslow",
# acodec="libfdk_aac",
# cutoff=20000,
r=frame_rate,
map_metadata=1,
metadata="comment=Processed with Video2X",
)
@@ -122,15 +128,25 @@ class VideoEncoder(threading.Thread):
),
overwrite_output=True,
),
env={"AV_LOG_FORCE_COLOR": "TRUE"},
stdin=subprocess.PIPE,
# stdout=subprocess.DEVNULL,
# stderr=subprocess.DEVNULL,
stderr=subprocess.PIPE,
)
# start the PIPE printer to start printing FFmpeg logs
self.pipe_printer = PipePrinter(self.encoder.stderr)
self.pipe_printer.start()
def run(self) -> None:
self.running = True
frame_index = 0
while self.running and frame_index < self.total_frames:
# pause if pause flag is set
if self.pause.value is True:
time.sleep(0.1)
continue
try:
image = self.processed_frames[frame_index]
if image is None:
@@ -150,23 +166,32 @@ class VideoEncoder(threading.Thread):
# send exceptions into the client connection pipe
except Exception as e:
self.exception = e
logger.exception(e)
break
else:
logger.debug("Encoding queue depleted")
# flush the remaining data in STDIN and close PIPE
logger.debug("Encoding queue depleted")
# flush the remaining data in STDIN and STDERR
self.encoder.stdin.flush()
self.encoder.stdin.close()
self.encoder.stderr.flush()
# send SIGINT (2) to FFmpeg
# this instructs it to finalize and exit
self.encoder.send_signal(signal.SIGINT)
# wait for process to terminate
self.encoder.wait()
logger.info("Encoder thread exiting")
# close PIPEs to prevent process from getting stuck
self.encoder.stdin.close()
self.encoder.stderr.close()
self.running = False
# wait for process to exit
self.encoder.wait()
# wait for PIPE printer to exit
self.pipe_printer.stop()
self.pipe_printer.join()
logger.info("Encoder thread exiting")
return super().run()
def stop(self) -> None:

View File

@@ -19,24 +19,19 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
Name: Interpolator
Author: K4YT3X
Date Created: May 27, 2021
Last Modified: February 16, 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.managers
import multiprocessing.sharedctypes
import queue
import signal
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 PIL import ImageChops, ImageStat
from rife_ncnn_vulkan_python.rife_ncnn_vulkan import Rife
ALGORITHM_CLASSES = {"rife": Rife}
@@ -45,21 +40,30 @@ class Interpolator(multiprocessing.Process):
def __init__(
self,
processing_queue: multiprocessing.Queue,
processed_frames: multiprocessing.managers.ListProxy,
processed_frames: ListProxy,
pause: Synchronized,
) -> None:
multiprocessing.Process.__init__(self)
self.running = False
self.processing_queue = processing_queue
self.processed_frames = processed_frames
self.pause = pause
signal.signal(signal.SIGTERM, self._stop)
def run(self) -> None:
self.running = True
logger.info(f"Interpolator process {self.name} initiating")
logger.opt(colors=True).info(
f"Interpolator process <blue>{self.name}</blue> initiating"
)
processor_objects = {}
while self.running:
try:
# pause if pause flag is set
if self.pause.value is True:
time.sleep(0.1)
continue
try:
# get new job from queue
(
@@ -112,8 +116,9 @@ class Interpolator(multiprocessing.Process):
logger.exception(e)
break
logger.info(f"Interpolator process {self.name} terminating")
self.running = False
logger.opt(colors=True).info(
f"Interpolator process <blue>{self.name}</blue> terminating"
)
return super().run()
def _stop(self, _signal_number, _frame) -> None:

63
video2x/pipe_printer.py Executable file
View 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:
time.sleep(0.5)
try:
self._print_output()
# pipe closed
except ValueError:
break
return super().run()
def stop(self) -> None:
self.running = False

View File

@@ -19,26 +19,23 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
Name: Upscaler
Author: K4YT3X
Date Created: May 27, 2021
Last Modified: February 16, 2022
Last Modified: March 20, 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 multiprocessing
import multiprocessing.managers
import multiprocessing.sharedctypes
import queue
import signal
import time
from multiprocessing.managers import ListProxy
from multiprocessing.sharedctypes import Synchronized
# third-party imports
from PIL import Image, ImageChops, ImageStat
from loguru import logger
from PIL import Image, ImageChops, ImageStat
from realcugan_ncnn_vulkan_python import Realcugan
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 algorithms
# that only support certain fixed scale ratios
@@ -46,21 +43,29 @@ ALGORITHM_FIXED_SCALING_RATIOS = {
"waifu2x": [1, 2],
"srmd": [2, 3, 4],
"realsr": [4],
"realcugan": [1, 2, 3, 4],
}
ALGORITHM_CLASSES = {"waifu2x": Waifu2x, "srmd": Srmd, "realsr": Realsr}
ALGORITHM_CLASSES = {
"waifu2x": Waifu2x,
"srmd": Srmd,
"realsr": Realsr,
"realcugan": Realcugan,
}
class Upscaler(multiprocessing.Process):
def __init__(
self,
processing_queue: multiprocessing.Queue,
processed_frames: multiprocessing.managers.ListProxy,
processed_frames: ListProxy,
pause: Synchronized,
) -> None:
multiprocessing.Process.__init__(self)
self.running = False
self.processing_queue = processing_queue
self.processed_frames = processed_frames
self.pause = pause
signal.signal(signal.SIGTERM, self._stop)
@@ -72,6 +77,11 @@ class Upscaler(multiprocessing.Process):
processor_objects = {}
while self.running:
try:
# pause if pause flag is set
if self.pause.value is True:
time.sleep(0.1)
continue
try:
# get new job from queue
(
@@ -168,14 +178,15 @@ class Upscaler(multiprocessing.Process):
processor_object = processor_objects.get((algorithm, job))
if processor_object is None:
processor_object = ALGORITHM_CLASSES[algorithm](
scale=job, noise=noise
noise=noise, scale=job
)
processor_objects[(algorithm, job)] = processor_object
# process the image with the selected algorithm
image1 = processor_object.process(image1)
# downscale the image to the desired output size and save the image to disk
# 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
@@ -190,7 +201,6 @@ class Upscaler(multiprocessing.Process):
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) -> None:

View File

@@ -27,7 +27,7 @@ __ __ _ _ ___ __ __
Name: Video2X
Creator: K4YT3X
Date Created: February 24, 2018
Last Modified: February 16, 2022
Last Modified: March 21, 2022
Editor: BrianPetkovsek
Last Modified: June 17, 2019
@@ -39,23 +39,18 @@ Editor: 28598519a
Last Modified: March 23, 2020
"""
# local imports
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 ctypes
import math
import multiprocessing
import os
import pathlib
import signal
import sys
import time
# third-party imports
import cv2
import ffmpeg
from loguru import logger
from rich import print
from rich.console import Console
@@ -69,9 +64,21 @@ from rich.progress import (
TimeRemainingColumn,
)
from rich.text import Text
import cv2
import ffmpeg
from . import __version__
from .decoder import VideoDecoder
from .encoder import VideoEncoder
from .interpolator import Interpolator
from .upscaler import Upscaler
# for desktop environments only
# if pynput can be loaded, enable global pause hotkey support
try:
import pynput
except ImportError:
ENABLE_HOTKEY = False
else:
ENABLE_HOTKEY = True
LEGAL_INFO = """Video2X\t\t{}
Author:\t\tK4YT3X
@@ -86,6 +93,7 @@ UPSCALING_ALGORITHMS = [
"waifu2x",
"srmd",
"realsr",
"realcugan",
]
# algorithms available for frame interpolation tasks
@@ -153,6 +161,22 @@ class Video2X:
return video_info["width"], video_info["height"], total_frames, frame_rate
def _toggle_pause(self, _signal_number: int = -1, _frame=None):
# print console messages and update the progress bar's status
if self.pause.value is False:
self.progress.update(self.task, description=self.description + " (paused)")
self.progress.stop_task(self.task)
logger.warning("Processing paused, press Ctrl+Alt+V again to resume")
elif self.pause.value is True:
self.progress.update(self.task, description=self.description)
logger.warning("Resuming processing")
self.progress.start_task(self.task)
# invert the value of the pause flag
with self.pause.get_lock():
self.pause.value = not self.pause.value
def _run(
self,
input_path: pathlib.Path,
@@ -189,6 +213,7 @@ class Video2X:
self.processing_queue = multiprocessing.Queue(maxsize=processes * 10)
processed_frames = multiprocessing.Manager().list([None] * total_frames)
self.processed = multiprocessing.Value("I", 0)
self.pause = multiprocessing.Value(ctypes.c_bool, False)
# set up and start decoder thread
logger.info("Starting video decoder")
@@ -199,6 +224,7 @@ class Video2X:
frame_rate,
self.processing_queue,
processing_settings,
self.pause,
)
self.decoder.start()
@@ -213,76 +239,120 @@ class Video2X:
total_frames,
processed_frames,
self.processed,
self.pause,
)
self.encoder.start()
# create processor processes
for process_name in range(processes):
process = Processor(self.processing_queue, processed_frames)
process = Processor(self.processing_queue, processed_frames, self.pause)
process.name = str(process_name)
process.daemon = True
process.start()
self.processor_processes.append(process)
# create progress bar
self.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,
)
self.description = f"[cyan]{MODE_LABELS.get(mode, 'Unknown')}"
self.task = self.progress.add_task(self.description, total=total_frames)
# allow sending SIGUSR1 to pause/resume processing
signal.signal(signal.SIGUSR1, self._toggle_pause)
# enable global pause hotkey if it's supported
if ENABLE_HOTKEY is True:
# create global pause hotkey
pause_hotkey = pynput.keyboard.HotKey(
pynput.keyboard.HotKey.parse("<ctrl>+<alt>+v"), self._toggle_pause
)
# create global keyboard input listener
keyboard_listener = pynput.keyboard.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
exception = []
try:
# create progress bar
with Progress(
"[progress.description]{task.description}",
BarColumn(finished_style="green"),
"[progress.percentage]{task.percentage:>3.0f}%",
"[color(240)]({task.completed}/{task.total})",
ProcessingSpeedColumn(),
TimeElapsedColumn(),
"<",
TimeRemainingColumn(),
console=console,
disable=True,
) as progress:
task = progress.add_task(
f"[cyan]{MODE_LABELS.get(mode, 'Unknown')}", total=total_frames
)
# wait for jobs in queue to deplete
while self.processed.value < total_frames - 1:
time.sleep(0.5)
for process in self.processor_processes:
if not process.is_alive():
raise Exception("process died unexpectedly")
# wait for jobs in queue to deplete
while self.processed.value < total_frames - 1:
time.sleep(1)
# show progress bar when upscale starts
if progress.disable is True and self.processed.value > 0:
progress.disable = False
progress.start()
# check processor health
for process in self.processor_processes:
if not process.is_alive():
raise Exception("process died unexpectedly")
# update progress
progress.update(task, completed=self.processed.value)
# check decoder health
if not self.decoder.is_alive() and self.decoder.exception is not None:
raise Exception("decoder died unexpectedly")
progress.update(task, completed=total_frames)
# check encoder health
if not self.encoder.is_alive() and self.encoder.exception is not None:
raise Exception("encoder died unexpectedly")
# show progress bar when upscale starts
if self.progress.disable is True and self.processed.value > 0:
self.progress.disable = False
self.progress.start()
# update progress
if self.pause.value is False:
self.progress.update(self.task, completed=self.processed.value)
self.progress.update(self.task, completed=total_frames)
self.progress.stop()
logger.info("Processing has completed")
# if SIGTERM is received or ^C is pressed
# TODO: pause and continue here
except (SystemExit, KeyboardInterrupt) as e:
logger.warning("Exit signal received, terminating")
self.progress.stop()
logger.warning("Exit signal received, exiting gracefully")
logger.warning("Press ^C again to force terminate")
exception.append(e)
except Exception as e:
self.progress.stop()
logger.exception(e)
exception.append(e)
# if no exceptions were produced
else:
logger.success("Processing completed successfully")
finally:
# mark processing queue as closed
self.processing_queue.close()
# stop upscaler processes
logger.info("Stopping upscaler processes")
# stop keyboard listener
if ENABLE_HOTKEY is True:
keyboard_listener.stop()
keyboard_listener.join()
# stop progress display
self.progress.stop()
# stop processor processes
logger.info("Stopping processor processes")
for process in self.processor_processes:
process.terminate()
@@ -290,12 +360,16 @@ class Video2X:
for process in self.processor_processes:
process.join()
# ensure both the decoder and the encoder have exited
# stop encoder and decoder
logger.info("Stopping decoder and encoder threads")
self.decoder.stop()
self.encoder.stop()
self.decoder.join()
self.encoder.join()
# mark processing queue as closed
self.processing_queue.close()
# raise the error if there is any
if len(exception) > 0:
raise exception[0]
@@ -491,21 +565,30 @@ def parse_arguments() -> argparse.Namespace:
return parser.parse_args()
def main() -> None:
def main() -> int:
"""
command line direct invocation
program entry point
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:
print(LEGAL_INFO)
sys.exit(0)
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
elif not args.input.is_file():
logger.critical("Input path is not a file")
return 1
# set logger level
if os.environ.get("LOGURU_LEVEL") is None:
os.environ["LOGURU_LEVEL"] = args.loglevel.upper()
@@ -522,7 +605,7 @@ def main() -> None:
"<magenta>Copyright (C) 2018-2022 K4YT3X and contributors.</magenta>"
)
# initialize upscaler object
# initialize video2x object
video2x = Video2X()
if args.action == "upscale":
@@ -547,9 +630,14 @@ def main() -> None:
)
# don't print the traceback for manual terminations
except KeyboardInterrupt as e:
raise SystemExit(e)
except KeyboardInterrupt:
return 2
except Exception as e:
logger.exception(e)
raise SystemExit(e)
return 1
# if no exceptions were produced
else:
logger.success("Processing completed successfully")
return 0