simplified the encoder

This commit is contained in:
k4yt3x 2022-04-27 22:28:14 +00:00
parent f3eaa47ec6
commit 0a052a3a72

View File

@ -19,20 +19,16 @@ 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 20, 2022 Last Modified: April 9, 2022
""" """
import os import os
import pathlib import pathlib
import signal import signal
import subprocess import subprocess
import threading
import time
from multiprocessing.managers import ListProxy
from multiprocessing.sharedctypes import Synchronized
import ffmpeg import ffmpeg
from loguru import logger from PIL import Image
from .pipe_printer import PipePrinter from .pipe_printer import PipePrinter
@ -48,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,
@ -56,29 +52,14 @@ 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,
processed_frames: ListProxy,
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,
copy_attachments: bool = False, copy_attachments: bool = False,
) -> None: ) -> None:
threading.Thread.__init__(self)
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.pause = pause
# stores exceptions if the thread exits with errors
self.exception = None
# create FFmpeg input for the original input video # create FFmpeg input for the original input video
self.original = ffmpeg.input(input_path) original = ffmpeg.input(input_path)
# define frames as input # define frames as input
frames = ffmpeg.input( frames = ffmpeg.input(
@ -93,11 +74,11 @@ class VideoEncoder(threading.Thread):
# copy additional streams from original file # copy additional streams from original file
# https://ffmpeg.org/ffmpeg.html#Stream-specifiers-1 # https://ffmpeg.org/ffmpeg.html#Stream-specifiers-1
additional_streams = [ additional_streams = [
# self.original["1:v?"], # original["1:v?"],
self.original["a?"] if copy_audio is True else None, original["a?"] if copy_audio is True else None,
self.original["s?"] if copy_subtitle is True else None, original["s?"] if copy_subtitle is True else None,
self.original["d?"] if copy_data is True else None, original["d?"] if copy_data is True else None,
self.original["t?"] if copy_attachments is True else None, original["t?"] if copy_attachments is True else None,
] ]
# run FFmpeg and produce final output # run FFmpeg and produce final output
@ -106,7 +87,7 @@ class VideoEncoder(threading.Thread):
ffmpeg.output( ffmpeg.output(
frames, frames,
*[s for s in additional_streams if s is not None], *[s for s in additional_streams if s is not None],
str(self.output_path), str(output_path),
vcodec="libx264", vcodec="libx264",
scodec="copy", scodec="copy",
vsync="cfr", vsync="cfr",
@ -138,41 +119,19 @@ class VideoEncoder(threading.Thread):
self.pipe_printer = PipePrinter(self.encoder.stderr) self.pipe_printer = PipePrinter(self.encoder.stderr)
self.pipe_printer.start() self.pipe_printer.start()
def run(self) -> None: def write(self, frame: Image.Image) -> None:
self.running = True """
frame_index = 0 write a frame into FFmpeg encoder's STDIN
while self.running and frame_index < self.total_frames:
# pause if pause flag is set :param frame Image.Image: the Image object to use for writing
if self.pause.value is True: """
time.sleep(0.1) self.encoder.stdin.write(frame.tobytes())
continue
try:
image = self.processed_frames[frame_index]
if image is None:
time.sleep(0.1)
continue
# send the image to FFmpeg for encoding
self.encoder.stdin.write(image.tobytes())
# remove the image from memory
self.processed_frames[frame_index] = None
with self.processed.get_lock():
self.processed.value += 1
frame_index += 1
# send exceptions into the client connection pipe
except Exception as error:
self.exception = error
logger.exception(error)
break
else:
logger.debug("Encoding queue depleted")
def join(self) -> None:
"""
signal the encoder that all frames have been sent and the FFmpeg
should be instructed to wrap-up the processing
"""
# flush the remaining data in STDIN and STDERR # flush the remaining data in STDIN and STDERR
self.encoder.stdin.flush() self.encoder.stdin.flush()
self.encoder.stderr.flush() self.encoder.stderr.flush()
@ -191,9 +150,3 @@ class VideoEncoder(threading.Thread):
# wait for PIPE printer to exit # wait for PIPE printer to exit
self.pipe_printer.stop() self.pipe_printer.stop()
self.pipe_printer.join() self.pipe_printer.join()
logger.info("Encoder thread exiting")
return super().run()
def stop(self) -> None:
self.running = False