mirror of
https://github.com/k4yt3x/video2x.git
synced 2024-12-28 23:19:11 +00:00
added pause function
This commit is contained in:
parent
fa485b6cdd
commit
8ba56e58c2
@ -19,7 +19,7 @@ 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: March 1, 2022
|
Last Modified: March 20, 2022
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
@ -30,6 +30,8 @@ import queue
|
|||||||
import signal
|
import signal
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
|
import time
|
||||||
|
from multiprocessing.sharedctypes import Synchronized
|
||||||
|
|
||||||
import ffmpeg
|
import ffmpeg
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@ -58,6 +60,7 @@ class VideoDecoder(threading.Thread):
|
|||||||
frame_rate: float,
|
frame_rate: float,
|
||||||
processing_queue: multiprocessing.Queue,
|
processing_queue: multiprocessing.Queue,
|
||||||
processing_settings: tuple,
|
processing_settings: tuple,
|
||||||
|
pause: Synchronized,
|
||||||
ignore_max_image_pixels=True,
|
ignore_max_image_pixels=True,
|
||||||
) -> None:
|
) -> None:
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
@ -67,6 +70,7 @@ class VideoDecoder(threading.Thread):
|
|||||||
self.input_height = input_height
|
self.input_height = input_height
|
||||||
self.processing_queue = processing_queue
|
self.processing_queue = processing_queue
|
||||||
self.processing_settings = processing_settings
|
self.processing_settings = processing_settings
|
||||||
|
self.pause = pause
|
||||||
|
|
||||||
# this disables the "possible DDoS" warning
|
# this disables the "possible DDoS" warning
|
||||||
if ignore_max_image_pixels:
|
if ignore_max_image_pixels:
|
||||||
@ -109,6 +113,12 @@ class VideoDecoder(threading.Thread):
|
|||||||
# continue running until an exception occurs
|
# continue running until an exception occurs
|
||||||
# or all frames have been decoded
|
# or all frames have been decoded
|
||||||
while self.running:
|
while self.running:
|
||||||
|
|
||||||
|
# pause if pause flag is set
|
||||||
|
if self.pause.value is True:
|
||||||
|
time.sleep(0.1)
|
||||||
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
buffer = self.decoder.stdout.read(
|
buffer = self.decoder.stdout.read(
|
||||||
3 * self.input_width * self.input_height
|
3 * self.input_width * self.input_height
|
||||||
|
@ -19,18 +19,17 @@ 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: March 1, 2022
|
Last Modified: March 20, 2022
|
||||||
"""
|
"""
|
||||||
|
|
||||||
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 threading
|
||||||
import time
|
import time
|
||||||
|
from multiprocessing.managers import ListProxy
|
||||||
|
from multiprocessing.sharedctypes import Synchronized
|
||||||
|
|
||||||
import ffmpeg
|
import ffmpeg
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@ -58,8 +57,9 @@ class VideoEncoder(threading.Thread):
|
|||||||
output_width: int,
|
output_width: int,
|
||||||
output_height: int,
|
output_height: int,
|
||||||
total_frames: int,
|
total_frames: int,
|
||||||
processed_frames: multiprocessing.managers.ListProxy,
|
processed_frames: ListProxy,
|
||||||
processed: multiprocessing.sharedctypes.Synchronized,
|
processed: Synchronized,
|
||||||
|
pause: Synchronized,
|
||||||
copy_audio: bool = True,
|
copy_audio: bool = True,
|
||||||
copy_subtitle: bool = True,
|
copy_subtitle: bool = True,
|
||||||
copy_data: bool = False,
|
copy_data: bool = False,
|
||||||
@ -72,6 +72,7 @@ class VideoEncoder(threading.Thread):
|
|||||||
self.total_frames = total_frames
|
self.total_frames = total_frames
|
||||||
self.processed_frames = processed_frames
|
self.processed_frames = processed_frames
|
||||||
self.processed = processed
|
self.processed = processed
|
||||||
|
self.pause = pause
|
||||||
|
|
||||||
# stores exceptions if the thread exits with errors
|
# stores exceptions if the thread exits with errors
|
||||||
self.exception = None
|
self.exception = None
|
||||||
@ -140,6 +141,12 @@ class VideoEncoder(threading.Thread):
|
|||||||
self.running = True
|
self.running = True
|
||||||
frame_index = 0
|
frame_index = 0
|
||||||
while self.running and frame_index < self.total_frames:
|
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:
|
try:
|
||||||
image = self.processed_frames[frame_index]
|
image = self.processed_frames[frame_index]
|
||||||
if image is None:
|
if image is None:
|
||||||
|
@ -19,15 +19,15 @@ 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 28, 2022
|
Last Modified: March 20, 2022
|
||||||
"""
|
"""
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from PIL import ImageChops, ImageStat
|
from PIL import ImageChops, ImageStat
|
||||||
@ -40,12 +40,14 @@ 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:
|
) -> None:
|
||||||
multiprocessing.Process.__init__(self)
|
multiprocessing.Process.__init__(self)
|
||||||
self.running = False
|
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
|
||||||
|
|
||||||
signal.signal(signal.SIGTERM, self._stop)
|
signal.signal(signal.SIGTERM, self._stop)
|
||||||
|
|
||||||
@ -57,6 +59,11 @@ class Interpolator(multiprocessing.Process):
|
|||||||
processor_objects = {}
|
processor_objects = {}
|
||||||
while self.running:
|
while self.running:
|
||||||
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
|
||||||
(
|
(
|
||||||
|
@ -19,16 +19,16 @@ 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: March 19, 2022
|
Last Modified: March 20, 2022
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import math
|
import math
|
||||||
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
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from PIL import Image, ImageChops, ImageStat
|
from PIL import Image, ImageChops, ImageStat
|
||||||
@ -58,12 +58,14 @@ class Upscaler(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:
|
) -> None:
|
||||||
multiprocessing.Process.__init__(self)
|
multiprocessing.Process.__init__(self)
|
||||||
self.running = False
|
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
|
||||||
|
|
||||||
signal.signal(signal.SIGTERM, self._stop)
|
signal.signal(signal.SIGTERM, self._stop)
|
||||||
|
|
||||||
@ -75,6 +77,11 @@ class Upscaler(multiprocessing.Process):
|
|||||||
processor_objects = {}
|
processor_objects = {}
|
||||||
while self.running:
|
while self.running:
|
||||||
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
|
||||||
(
|
(
|
||||||
|
@ -27,7 +27,7 @@ __ __ _ _ ___ __ __
|
|||||||
Name: Video2X
|
Name: Video2X
|
||||||
Creator: K4YT3X
|
Creator: K4YT3X
|
||||||
Date Created: February 24, 2018
|
Date Created: February 24, 2018
|
||||||
Last Modified: March 19, 2022
|
Last Modified: March 20, 2022
|
||||||
|
|
||||||
Editor: BrianPetkovsek
|
Editor: BrianPetkovsek
|
||||||
Last Modified: June 17, 2019
|
Last Modified: June 17, 2019
|
||||||
@ -40,15 +40,19 @@ Last Modified: March 23, 2020
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import ctypes
|
||||||
import math
|
import math
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
|
import signal
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
import cv2
|
import cv2
|
||||||
import ffmpeg
|
import ffmpeg
|
||||||
|
import pynput
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from rich import print
|
from rich import print
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
@ -150,6 +154,22 @@ class Video2X:
|
|||||||
|
|
||||||
return video_info["width"], video_info["height"], total_frames, frame_rate
|
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(
|
def _run(
|
||||||
self,
|
self,
|
||||||
input_path: pathlib.Path,
|
input_path: pathlib.Path,
|
||||||
@ -186,6 +206,7 @@ class Video2X:
|
|||||||
self.processing_queue = multiprocessing.Queue(maxsize=processes * 10)
|
self.processing_queue = multiprocessing.Queue(maxsize=processes * 10)
|
||||||
processed_frames = multiprocessing.Manager().list([None] * total_frames)
|
processed_frames = multiprocessing.Manager().list([None] * total_frames)
|
||||||
self.processed = multiprocessing.Value("I", 0)
|
self.processed = multiprocessing.Value("I", 0)
|
||||||
|
self.pause = multiprocessing.Value(ctypes.c_bool, False)
|
||||||
|
|
||||||
# set up and start decoder thread
|
# set up and start decoder thread
|
||||||
logger.info("Starting video decoder")
|
logger.info("Starting video decoder")
|
||||||
@ -196,6 +217,7 @@ class Video2X:
|
|||||||
frame_rate,
|
frame_rate,
|
||||||
self.processing_queue,
|
self.processing_queue,
|
||||||
processing_settings,
|
processing_settings,
|
||||||
|
self.pause,
|
||||||
)
|
)
|
||||||
self.decoder.start()
|
self.decoder.start()
|
||||||
|
|
||||||
@ -210,23 +232,20 @@ class Video2X:
|
|||||||
total_frames,
|
total_frames,
|
||||||
processed_frames,
|
processed_frames,
|
||||||
self.processed,
|
self.processed,
|
||||||
|
self.pause,
|
||||||
)
|
)
|
||||||
self.encoder.start()
|
self.encoder.start()
|
||||||
|
|
||||||
# create processor processes
|
# create processor processes
|
||||||
for process_name in range(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.name = str(process_name)
|
||||||
process.daemon = True
|
process.daemon = True
|
||||||
process.start()
|
process.start()
|
||||||
self.processor_processes.append(process)
|
self.processor_processes.append(process)
|
||||||
|
|
||||||
# a temporary variable that stores the exception
|
|
||||||
exception = []
|
|
||||||
|
|
||||||
try:
|
|
||||||
# create progress bar
|
# create progress bar
|
||||||
with Progress(
|
self.progress = Progress(
|
||||||
"[progress.description]{task.description}",
|
"[progress.description]{task.description}",
|
||||||
BarColumn(complete_style="blue", finished_style="green"),
|
BarColumn(complete_style="blue", finished_style="green"),
|
||||||
"[progress.percentage]{task.percentage:>3.0f}%",
|
"[progress.percentage]{task.percentage:>3.0f}%",
|
||||||
@ -237,14 +256,38 @@ class Video2X:
|
|||||||
TimeRemainingColumn(),
|
TimeRemainingColumn(),
|
||||||
console=console,
|
console=console,
|
||||||
disable=True,
|
disable=True,
|
||||||
) as progress:
|
|
||||||
task = progress.add_task(
|
|
||||||
f"[cyan]{MODE_LABELS.get(mode, 'Unknown')}", total=total_frames
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.description = f"[cyan]{MODE_LABELS.get(self.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)
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
|
||||||
# wait for jobs in queue to deplete
|
# wait for jobs in queue to deplete
|
||||||
while self.processed.value < total_frames - 1:
|
while self.processed.value < total_frames - 1:
|
||||||
time.sleep(0.5)
|
time.sleep(1)
|
||||||
|
|
||||||
# check processor health
|
# check processor health
|
||||||
for process in self.processor_processes:
|
for process in self.processor_processes:
|
||||||
@ -252,42 +295,47 @@ class Video2X:
|
|||||||
raise Exception("process died unexpectedly")
|
raise Exception("process died unexpectedly")
|
||||||
|
|
||||||
# check decoder health
|
# check decoder health
|
||||||
if (
|
if not self.decoder.is_alive() and self.decoder.exception is not None:
|
||||||
not self.decoder.is_alive()
|
|
||||||
and self.decoder.exception is not None
|
|
||||||
):
|
|
||||||
raise Exception("decoder died unexpectedly")
|
raise Exception("decoder died unexpectedly")
|
||||||
|
|
||||||
# check encoder health
|
# check encoder health
|
||||||
if (
|
if not self.encoder.is_alive() and self.encoder.exception is not None:
|
||||||
not self.encoder.is_alive()
|
|
||||||
and self.encoder.exception is not None
|
|
||||||
):
|
|
||||||
raise Exception("encoder died unexpectedly")
|
raise Exception("encoder died unexpectedly")
|
||||||
|
|
||||||
# show progress bar when upscale starts
|
# show progress bar when upscale starts
|
||||||
if progress.disable is True and self.processed.value > 0:
|
if self.progress.disable is True and self.processed.value > 0:
|
||||||
progress.disable = False
|
self.progress.disable = False
|
||||||
progress.start()
|
self.progress.start()
|
||||||
|
|
||||||
# update progress
|
# update progress
|
||||||
progress.update(task, completed=self.processed.value)
|
if self.pause.value is False:
|
||||||
|
self.progress.update(self.task, completed=self.processed.value)
|
||||||
|
|
||||||
progress.update(task, completed=total_frames)
|
self.progress.update(self.task, completed=total_frames)
|
||||||
|
self.progress.stop()
|
||||||
logger.info("Processing has completed")
|
logger.info("Processing has completed")
|
||||||
|
|
||||||
# if SIGTERM is received or ^C is pressed
|
# if SIGTERM is received or ^C is pressed
|
||||||
# TODO: pause and continue here
|
# TODO: pause and continue here
|
||||||
except (SystemExit, KeyboardInterrupt) as e:
|
except (SystemExit, KeyboardInterrupt) as e:
|
||||||
|
self.progress.stop()
|
||||||
logger.warning("Exit signal received, exiting gracefully")
|
logger.warning("Exit signal received, exiting gracefully")
|
||||||
logger.warning("Press ^C again to force terminate")
|
logger.warning("Press ^C again to force terminate")
|
||||||
exception.append(e)
|
exception.append(e)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
self.progress.stop()
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
exception.append(e)
|
exception.append(e)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
|
# stop keyboard listener
|
||||||
|
keyboard_listener.stop()
|
||||||
|
keyboard_listener.join()
|
||||||
|
|
||||||
|
# stop progress display
|
||||||
|
self.progress.stop()
|
||||||
|
|
||||||
# stop processor processes
|
# stop processor processes
|
||||||
logger.info("Stopping processor processes")
|
logger.info("Stopping processor processes")
|
||||||
for process in self.processor_processes:
|
for process in self.processor_processes:
|
||||||
|
Loading…
Reference in New Issue
Block a user