From 7b33f0640512e17a1b0a9ca10f86ae90e296e476 Mon Sep 17 00:00:00 2001 From: Brett Williams Date: Sat, 8 Oct 2022 17:09:13 -0700 Subject: [PATCH] Change Renderer classes to RenderWorkers. Created RenderWorkerFactory. --- utilities/{aerender.py => aerender_worker.py} | 17 ++++--- utilities/{blender.py => blender_worker.py} | 20 ++++---- utilities/compressor.py | 8 +-- .../{ffmpeg_render.py => ffmpeg_worker.py} | 11 ++-- .../{generic_renderer.py => render_worker.py} | 34 +++++++++++-- zordon_server.py | 50 +++++++------------ 6 files changed, 79 insertions(+), 61 deletions(-) rename utilities/{aerender.py => aerender_worker.py} (89%) rename utilities/{blender.py => blender_worker.py} (85%) rename utilities/{ffmpeg_render.py => ffmpeg_worker.py} (82%) rename utilities/{generic_renderer.py => render_worker.py} (85%) diff --git a/utilities/aerender.py b/utilities/aerender_worker.py similarity index 89% rename from utilities/aerender.py rename to utilities/aerender_worker.py index 806b404..14a371a 100644 --- a/utilities/aerender.py +++ b/utilities/aerender_worker.py @@ -1,8 +1,9 @@ #! /usr/bin/python -from utilities.generic_renderer import * +from utilities.render_worker import * import glob import json import re +import time def aerender_path(): @@ -15,7 +16,7 @@ def aerender_path(): return paths[0] -class AERenderer(Renderer): +class AERenderWorker(RenderWorker): @staticmethod def version(): @@ -33,12 +34,12 @@ class AERenderer(Renderer): render_engine = 'aerender' supported_extensions = ['.aep'] - def __init__(self, project, comp, render_settings, omsettings, output): - super(AERenderer, self).__init__(input=project, output=output) + def __init__(self, input_path, output_path, args=None): + super(AERenderWorker, self).__init__(input_path=input_path, output_path=output_path, args=args) - self.comp = comp - self.render_settings = render_settings - self.omsettings = omsettings + self.comp = self.args.get('comp') + self.render_settings = self.args.get('render_settings') + self.omsettings = self.args.get('omsettings') self.progress = 0 self.progress_history = [] @@ -136,7 +137,7 @@ class AERenderer(Renderer): if __name__ == '__main__': logging.basicConfig(format='%(asctime)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S', level=logging.DEBUG) - r = AERenderer('/Users/brett/Desktop/Youtube_Vids/Film_Formats/Frame_Animations.aep', '"Film Pan"', + r = AERenderWorker('/Users/brett/Desktop/Youtube_Vids/Film_Formats/Frame_Animations.aep', '"Film Pan"', '"Draft Settings"', '"ProRes"', '/Users/brett/Desktop/test_render') r.start() while r.is_running(): diff --git a/utilities/blender.py b/utilities/blender_worker.py similarity index 85% rename from utilities/blender.py rename to utilities/blender_worker.py index c14871a..d2c03d6 100644 --- a/utilities/blender.py +++ b/utilities/blender_worker.py @@ -1,10 +1,12 @@ #! /usr/bin/python -from utilities.generic_renderer import * +from utilities.render_worker import * +import time -SUPPORTED_FORMATS = ['TGA', 'RAWTGA', 'JPEG', 'IRIS', 'IRIZ', 'AVIRAW', 'AVIJPEG', 'PNG', 'BMP', 'HDR', 'TIFF', 'OPEN_EXR', 'OPEN_EXR_MULTILAYER', 'MPEG', 'CINEON', 'DPX', 'DDS', 'JP2'] +SUPPORTED_FORMATS = ['TGA', 'RAWTGA', 'JPEG', 'IRIS', 'IRIZ', 'AVIRAW', 'AVIJPEG', 'PNG', 'BMP', 'HDR', 'TIFF', + 'OPEN_EXR', 'OPEN_EXR_MULTILAYER', 'MPEG', 'CINEON', 'DPX', 'DDS', 'JP2'] -class BlenderRenderer(Renderer): +class BlenderRenderWorker(RenderWorker): def version(self): version = None @@ -20,14 +22,14 @@ class BlenderRenderer(Renderer): supported_extensions = ['.blend'] install_paths = ['/Applications/Blender.app/Contents/MacOS/Blender'] - def __init__(self, input_path, output_path, render_all_frames=False, engine='BLENDER_EEVEE'): - super(BlenderRenderer, self).__init__(input_path=input_path, output_path=output_path) + def __init__(self, input_path, output_path, args=None): + super(BlenderRenderWorker, self).__init__(input_path=input_path, output_path=output_path, args=args) - self.engine = engine # or 'CYCLES' + self.engine = self.args.get('engine', 'BLENDER_EEVEE') self.format = 'JPEG' self.frame = 0 - self.render_all_frames = render_all_frames + self.render_all_frames = self.args.get('render_all_frames', False) # Stats self.current_frame = -1 @@ -65,7 +67,7 @@ class BlenderRenderer(Renderer): sample_string = line.split('|')[-1].strip() if "sample" in sample_string.lower(): - samples = re.sub(r'[^0-9/]', '', sample_string) + samples = re.sub(r'[^\d/]', '', sample_string) self.frame_percent_complete = int(samples.split('/')[0]) / int(samples.split('/')[-1]) @@ -110,7 +112,7 @@ class BlenderRenderer(Renderer): if __name__ == '__main__': logging.basicConfig(format='%(asctime)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S', level=logging.DEBUG) - r = BlenderRenderer('/Users/brett/Blender Files/Ian Hubert/CyberExtras.blend', '/Users/brett/testing1234', render_all_frames=False, engine="CYCLES") + r = BlenderRenderWorker('/Users/brett/Blender Files/Ian Hubert/CyberExtras.blend', '/Users/brett/testing1234', render_all_frames=False, engine="CYCLES") # r.engine = 'CYCLES' r.start() while r.is_running(): diff --git a/utilities/compressor.py b/utilities/compressor.py index a92ef1c..f2747df 100644 --- a/utilities/compressor.py +++ b/utilities/compressor.py @@ -1,5 +1,5 @@ #! /usr/bin/python -from generic_renderer import * +from render_worker import * import glob import logging import subprocess @@ -11,7 +11,7 @@ def compressor_path(): return '/Applications/Compressor.app/Contents/MacOS/Compressor' -class CompressorRenderer(Renderer): +class CompressorRenderWorker(RenderWorker): renderer = 'Compressor' @@ -94,7 +94,7 @@ class CompressorRenderer(Renderer): # -locationpath -- path to location file. Modified movie will be saved here. If unspecified, changes will be saved in place, overwriting the original file. def __init__(self, project, settings_path, output): - super(CompressorRenderer, self).__init__(project=project, output=output) + super(CompressorRenderWorker, self).__init__(project=project, output=output) self.settings_path = settings_path self.batch_name = os.path.basename(project) @@ -115,7 +115,7 @@ class CompressorRenderer(Renderer): if __name__ == '__main__': logging.basicConfig(format='%(asctime)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S', level=logging.DEBUG) - r = CompressorRenderer('/Users/brett/Desktop/drone_raw.mp4', '/Applications/Compressor.app/Contents/Resources/Settings/Website Sharing/HD720WebShareName.compressorsetting', '/Users/brett/Desktop/test_drone_output.mp4') + r = CompressorRenderWorker('/Users/brett/Desktop/drone_raw.mp4', '/Applications/Compressor.app/Contents/Resources/Settings/Website Sharing/HD720WebShareName.compressorsetting', '/Users/brett/Desktop/test_drone_output.mp4') r.start() while r.is_running(): time.sleep(1) \ No newline at end of file diff --git a/utilities/ffmpeg_render.py b/utilities/ffmpeg_worker.py similarity index 82% rename from utilities/ffmpeg_render.py rename to utilities/ffmpeg_worker.py index 8064114..fbc6dfe 100644 --- a/utilities/ffmpeg_render.py +++ b/utilities/ffmpeg_worker.py @@ -4,10 +4,10 @@ import time import ffmpeg -from utilities.generic_renderer import * +from utilities.render_worker import * -class FFMPEGRenderer(Renderer): +class FFMPEGRenderWorker(RenderWorker): def version(self): version = None @@ -23,7 +23,7 @@ class FFMPEGRenderer(Renderer): render_engine = 'ffmpeg' def __init__(self, input_path, output_path, args=None): - super(FFMPEGRenderer, self).__init__(input_path=input_path, output_path=output_path, ignore_extensions=True) + super(FFMPEGRenderWorker, self).__init__(input_path=input_path, output_path=output_path, ignore_extensions=True, args=args) self.total_frames = -1 if os.path.exists(input_path): @@ -34,7 +34,6 @@ class FFMPEGRenderer(Renderer): break self.frame = 0 - self.args = args # Stats self.current_frame = -1 @@ -58,6 +57,8 @@ class FFMPEGRenderer(Renderer): stats = found.groupdict() self.current_frame = stats['current_frame'] self.time_elapsed = stats['time_elapsed'] + elif "not found" in line: + self.errors.append(line) if __name__ == '__main__': @@ -65,7 +66,7 @@ if __name__ == '__main__': test_movie = '/Users/brettwilliams/Desktop/dark_knight_rises.mp4' - r = FFMPEGRenderer(test_movie, '/Users/brettwilliams/Desktop/test-ffmpeg.mp4', args=['-c:v','libx265','-vtag','hvc1']) + r = FFMPEGRenderWorker(test_movie, '/Users/brettwilliams/Desktop/test-ffmpeg.mp4', args=['-c:v', 'libx265', '-vtag', 'hvc1']) # r = FFMPEGRenderer(test_movie, '/Users/brettwilliams/Desktop/dark_knight_rises-output.mp4') r.start() while r.is_running(): diff --git a/utilities/generic_renderer.py b/utilities/render_worker.py similarity index 85% rename from utilities/generic_renderer.py rename to utilities/render_worker.py index e56daf7..6145dbb 100644 --- a/utilities/generic_renderer.py +++ b/utilities/render_worker.py @@ -28,7 +28,7 @@ def string_to_status(string): return RenderStatus.ERROR -class Renderer(object): +class RenderWorker(object): renderer = 'GenericRenderer' render_engine = None @@ -39,7 +39,7 @@ class Renderer(object): def version(): return 'Unknown' - def __init__(self, input_path, output_path, ignore_extensions=False): + def __init__(self, input_path, output_path, args=None, ignore_extensions=False): if not ignore_extensions: if not any(ext in input_path for ext in self.supported_extensions): @@ -50,6 +50,7 @@ class Renderer(object): # Essential Info self.input = input_path self.output = output_path + self.args = args or {} self.date_created = datetime.now() self.attributes = {} self.renderer_version = self.version() @@ -169,6 +170,8 @@ class Renderer(object): if self.failed_attempts >= self.maximum_attempts and self.status is not RenderStatus.CANCELLED: logger.error('{} Render of {} failed after {} attempts'.format(self.renderer, self.input, self.failed_attempts)) self.status = RenderStatus.ERROR + if not self.errors: + self.errors = [self.last_output] self.is_finished = True def is_running(self): @@ -204,8 +207,33 @@ class Renderer(object): return elapsed +class RenderWorkerFactory: + + @staticmethod + def create_worker(renderer, input_path, output_path, args=None): + + from utilities.blender_worker import BlenderRenderWorker + from utilities.aerender_worker import AERenderWorker + from utilities.ffmpeg_worker import FFMPEGRenderWorker + + if "blender" == renderer.lower(): + worker = BlenderRenderWorker(input_path, output_path, args=args) + elif "aerender" == renderer.lower() or "after effects" == renderer.lower(): + worker = AERenderWorker(input_path, output_path, args=args) + elif "ffmpeg" == renderer.lower(): + worker = FFMPEGRenderWorker(input_path, output_path, args=args) + else: + raise ValueError(f"Cannot find renderer for type '{renderer}'") + + return worker + + @staticmethod + def supported_renderers(): + return ['aerender', 'blender', 'ffmpeg'] + + def timecode_to_frames(timecode, frame_rate): e = [int(x) for x in timecode.split(':')] seconds = (((e[0] * 60) + e[1] * 60) + e[2]) frames = (seconds * frame_rate) + e[-1] + 1 - return frames \ No newline at end of file + return frames diff --git a/zordon_server.py b/zordon_server.py index 7bd869b..5388fd8 100755 --- a/zordon_server.py +++ b/zordon_server.py @@ -13,11 +13,7 @@ import json import os from flask import Flask, jsonify, request -from utilities.aerender import AERenderer -from utilities.blender import BlenderRenderer -from utilities.ffmpeg_render import FFMPEGRenderer -from utilities.generic_renderer import RenderStatus -from utilities.generic_renderer import string_to_status +from utilities.render_worker import RenderWorkerFactory, RenderStatus, string_to_status data = 'foo' app = Flask(__name__) @@ -86,11 +82,6 @@ class RenderJob: return json_string -def render_factory(input_path, output_path): - if '.blend' in input_path.lower(): - return BlenderRenderer(input_path, output_path) - - class RenderServer: render_queue = [] render_clients = [] @@ -154,14 +145,7 @@ class RenderServer: for job in job_list: # Identify renderer type and recreate Renderer object - # TODO: refactor to factory class - job_render_object = None - if job['renderer'] == 'Blender': - job_render_object = BlenderRenderer(job['render']['input'], job['render']['output']) - elif job['renderer'] == 'After Effects': - AERenderer() - elif job['renderer'] == 'ffmpeg': - job_render_object = FFMPEGRenderer(job['render']['input'], job['render']['output']) + job_render_object = RenderWorkerFactory.create_worker(job['renderer'], input_path=job['render']['input'], output_path=job['render']['output']) # Load Renderer values for key, val in job['render'].items(): @@ -335,27 +319,29 @@ def add_job(): renderer = request.json["renderer"] input_path = request.json["input"] output_path = request.json["output"] + priority = request.json.get('priority', 2) + args = request.json.get('args', None) + force_start = request.json.get('force_start', False) + + return add_job_handler(renderer, input_path, output_path, args=args, priority=priority, force_start=force_start) + + +def add_job_handler(renderer, input_path, output_path, args=None, priority=2, force_start=False): if not os.path.exists(input_path): err_msg = f"Cannot add job. Cannot find input file: {input_path}" logger.error(err_msg) return {"error": err_msg}, 400 - # todo: create factory class for creating renderers - if "blender" in renderer: - render_job = BlenderRenderer(input_path, output_path) - render_job.engine = request.json.get('engine', 'BLENDER_EEVEE') - elif "aerender" in renderer: - render_job = AERenderer(input_path, output_path) - elif "ffmpeg" in renderer: - render_job = FFMPEGRenderer(input_path, output_path, args=request.json.get('args', None)) - else: - err_msg = "Unknown renderer: {}".format(renderer) - logger.error(err_msg) - return {'error': err_msg}, 400 + try: + render_job = RenderWorkerFactory.create_worker(renderer, input_path, output_path, args) + except ValueError as e: + logger.exception(e) + return {'error': str(e)}, 400 + + new_job = RenderJob(render_job, priority=priority) + RenderServer.add_to_render_queue(new_job, force_start=force_start) - new_job = RenderJob(render_job, priority=request.json.get('priority', 2)) - RenderServer.add_to_render_queue(new_job, force_start=request.json.get('force_start', False)) return new_job.json()