Thumbnail Generation Refactoring (#16)

* Fix file_list and convert save_first_frame to use FFMPEG directly

* All thumbnail generation now uses FFMPEG engine
This commit is contained in:
2023-06-15 13:06:46 -05:00
committed by GitHub
parent cae5dbb41c
commit 0a0a228731
4 changed files with 13 additions and 58 deletions

View File

@@ -1,58 +1,19 @@
import subprocess import subprocess
import ffmpeg # todo: remove all references to ffmpeg library and instead use direct subprocesses
from lib.engines.ffmpeg_engine import FFMPEG from lib.engines.ffmpeg_engine import FFMPEG
def file_info(path): def image_sequence_to_video(source_glob_pattern, output_path, framerate=24, encoder="libx264", pix_fmt="yuv420p"):
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"):
subprocess.run([FFMPEG.renderer_path(), "-framerate", str(framerate), "-i", f"{source_glob_pattern}", 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): def save_first_frame(source_path, dest_path, max_width=1280):
stream = ffmpeg.input(source_path) subprocess.run([FFMPEG.renderer_path(), '-i', source_path, '-vf', f'scale={max_width}:-1',
stream = ffmpeg.output(stream, dest_path, **{'vf': f'format=yuv420p,scale={max_width}:trunc(ow/a/2)*2', '-vframes', '1', dest_path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
'vframes': '1'})
return _run_output(stream, run_async)
def generate_fast_preview(source_path, dest_path, max_width=1280, run_async=False): def generate_thumbnail(source_path, dest_path, max_width=240, fps=12):
stream = ffmpeg.input(source_path) subprocess.run([FFMPEG.renderer_path(), '-i', source_path, '-vf',
stream = ffmpeg.output(stream, dest_path, **{'vf': 'format=yuv420p,scale={width}:-2'.format(width=max_width), f"scale={max_width}:trunc(ow/a/2)*2,format=yuv420p", '-r', str(fps), '-c:v', 'libx264', '-preset',
'preset': 'ultrafast'}) 'ultrafast', '-an', dest_path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
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)

View File

@@ -1,13 +1,9 @@
import json
import logging import logging
import os import os
import subprocess import subprocess
import threading import threading
import requests
from .ffmpeg_helper import generate_thumbnail, save_first_frame from .ffmpeg_helper import generate_thumbnail, save_first_frame
from lib.workers.base_worker import RenderStatus
logger = logging.getLogger() logger = logging.getLogger()
@@ -21,8 +17,8 @@ def generate_thumbnail_for_job(job, thumb_video_path, thumb_image_path, max_widt
try: try:
logger.debug(f"Generating video thumbnail for {source}") logger.debug(f"Generating video thumbnail for {source}")
generate_thumbnail(source_path=source, dest_path=thumb_video_path, max_width=max_width) generate_thumbnail(source_path=source, dest_path=thumb_video_path, max_width=max_width)
except Exception as e: except subprocess.CalledProcessError as e:
logger.error(f"Error generating thumbnail for {source}: {e}") logger.error(f"Error generating video thumbnail for {source}: {e}")
try: try:
os.remove(in_progress_path) os.remove(in_progress_path)

View File

@@ -5,7 +5,6 @@ import os
import subprocess import subprocess
import threading import threading
import json import json
import glob
from datetime import datetime from datetime import datetime
from enum import Enum from enum import Enum
@@ -287,7 +286,7 @@ class BaseRenderWorker(Base):
def file_list(self): def file_list(self):
job_dir = os.path.dirname(self.output_path) 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() file_list.sort()
return file_list return file_list

View File

@@ -4,7 +4,6 @@ psutil==5.9.5
PyYAML~=6.0 PyYAML~=6.0
Flask==2.3.2 Flask==2.3.2
rich==13.4.1 rich==13.4.1
ffmpeg-python
Werkzeug==2.3.5 Werkzeug==2.3.5
tkinterdnd2~=0.3.0 tkinterdnd2~=0.3.0
future==0.18.3 future==0.18.3