From 0a0a228731372a35fe6d6ec0f1ff6a7e09af8a13 Mon Sep 17 00:00:00 2001 From: Brett Williams Date: Thu, 15 Jun 2023 13:06:46 -0500 Subject: [PATCH] Thumbnail Generation Refactoring (#16) * Fix file_list and convert save_first_frame to use FFMPEG directly * All thumbnail generation now uses FFMPEG engine --- lib/utilities/ffmpeg_helper.py | 59 ++++++---------------------------- lib/utilities/server_helper.py | 8 ++--- lib/workers/base_worker.py | 3 +- requirements.txt | 1 - 4 files changed, 13 insertions(+), 58 deletions(-) diff --git a/lib/utilities/ffmpeg_helper.py b/lib/utilities/ffmpeg_helper.py index 62d2f26..c79e2cf 100644 --- a/lib/utilities/ffmpeg_helper.py +++ b/lib/utilities/ffmpeg_helper.py @@ -1,58 +1,19 @@ import subprocess -import ffmpeg # todo: remove all references to ffmpeg library and instead use direct subprocesses from lib.engines.ffmpeg_engine import FFMPEG -def file_info(path): - try: - return ffmpeg.probe(path) - except Exception as e: - print('Error getting ffmpeg info: ' + str(e)) - return None - - -def image_sequence_to_video(source_glob_pattern, output_path, framerate="24", encoder="libx264", pix_fmt="yuv420p"): +def image_sequence_to_video(source_glob_pattern, output_path, framerate=24, encoder="libx264", pix_fmt="yuv420p"): subprocess.run([FFMPEG.renderer_path(), "-framerate", str(framerate), "-i", f"{source_glob_pattern}", - "-c:v", encoder, "-pix_fmt", pix_fmt, output_path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + "-c:v", encoder, "-pix_fmt", pix_fmt, output_path], stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, check=True) -def save_first_frame(source_path, dest_path, max_width=1280, run_async=False): - stream = ffmpeg.input(source_path) - stream = ffmpeg.output(stream, dest_path, **{'vf': f'format=yuv420p,scale={max_width}:trunc(ow/a/2)*2', - 'vframes': '1'}) - return _run_output(stream, run_async) +def save_first_frame(source_path, dest_path, max_width=1280): + subprocess.run([FFMPEG.renderer_path(), '-i', source_path, '-vf', f'scale={max_width}:-1', + '-vframes', '1', dest_path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True) -def generate_fast_preview(source_path, dest_path, max_width=1280, run_async=False): - stream = ffmpeg.input(source_path) - stream = ffmpeg.output(stream, dest_path, **{'vf': 'format=yuv420p,scale={width}:-2'.format(width=max_width), - 'preset': 'ultrafast'}) - return _run_output(stream, run_async) - - -def generate_thumbnail(source_path, dest_path, max_width=240, run_async=False): - stream = ffmpeg.input(source_path).video - stream = ffmpeg.output(stream, dest_path, **{'vf': f'scale={max_width}:trunc(ow/a/2)*2,format=yuv420p', - 'preset': 'veryfast', - 'r': '15', - 'c:v': 'libx265', - 'tag:v': 'hvc1'}) - return _run_output(stream, run_async) - - -def generate_prores_trim(source_path, dest_path, start_frame, end_frame, handles=10, run_async=False): - stream = ffmpeg.input(source_path) - stream = stream.trim(**{'start_frame': max(start_frame-handles, 0), 'end_frame': end_frame + handles}) - stream = stream.setpts('PTS-STARTPTS') # reset timecode - stream = ffmpeg.output(stream, dest_path, strict='-2', **{'c:v': 'prores_ks', 'profile:v': 4}) - return _run_output(stream, run_async) - - -def _run_output(stream, run_async): - return ffmpeg.run_async(stream, quiet=True, overwrite_output=True) if run_async else \ - ffmpeg.run(stream, quiet=True, overwrite_output=True) - - -if __name__ == '__main__': - x = generate_thumbnail("/Users/brett/Desktop/pexels.mp4", "/Users/brett/Desktop/test-output.mp4", max_width=320) - print(x) +def generate_thumbnail(source_path, dest_path, max_width=240, fps=12): + subprocess.run([FFMPEG.renderer_path(), '-i', source_path, '-vf', + f"scale={max_width}:trunc(ow/a/2)*2,format=yuv420p", '-r', str(fps), '-c:v', 'libx264', '-preset', + 'ultrafast', '-an', dest_path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True) diff --git a/lib/utilities/server_helper.py b/lib/utilities/server_helper.py index 0b55881..0246931 100644 --- a/lib/utilities/server_helper.py +++ b/lib/utilities/server_helper.py @@ -1,13 +1,9 @@ -import json import logging import os import subprocess import threading -import requests - from .ffmpeg_helper import generate_thumbnail, save_first_frame -from lib.workers.base_worker import RenderStatus logger = logging.getLogger() @@ -21,8 +17,8 @@ def generate_thumbnail_for_job(job, thumb_video_path, thumb_image_path, max_widt try: logger.debug(f"Generating video thumbnail for {source}") generate_thumbnail(source_path=source, dest_path=thumb_video_path, max_width=max_width) - except Exception as e: - logger.error(f"Error generating thumbnail for {source}: {e}") + except subprocess.CalledProcessError as e: + logger.error(f"Error generating video thumbnail for {source}: {e}") try: os.remove(in_progress_path) diff --git a/lib/workers/base_worker.py b/lib/workers/base_worker.py index 35306b5..ccbb64c 100644 --- a/lib/workers/base_worker.py +++ b/lib/workers/base_worker.py @@ -5,7 +5,6 @@ import os import subprocess import threading import json -import glob from datetime import datetime from enum import Enum @@ -287,7 +286,7 @@ class BaseRenderWorker(Base): def file_list(self): job_dir = os.path.dirname(self.output_path) - file_list = glob.glob(os.path.join(job_dir, '*')) + file_list = [os.path.join(job_dir, file) for file in os.listdir(job_dir)] file_list.sort() return file_list diff --git a/requirements.txt b/requirements.txt index 0952de9..63d56a7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,6 @@ psutil==5.9.5 PyYAML~=6.0 Flask==2.3.2 rich==13.4.1 -ffmpeg-python Werkzeug==2.3.5 tkinterdnd2~=0.3.0 future==0.18.3