From c83037ee8a03ddc4e07517e6db55eca18a0cf571 Mon Sep 17 00:00:00 2001 From: Brett Date: Thu, 8 Jan 2026 13:42:00 -0600 Subject: [PATCH] Performance improvements / API refactoring --- src/api/api_server.py | 22 +++++++++++++++++++++- src/api/server_proxy.py | 4 ++++ src/engines/blender/blender_engine.py | 12 +++++++----- src/engines/blender/blender_ui.py | 9 --------- src/engines/core/base_engine.py | 3 +-- src/engines/ffmpeg/ffmpeg_engine.py | 6 ++---- src/engines/ffmpeg/ffmpeg_ui.py | 5 ----- src/ui/add_job_window.py | 10 +++++----- 8 files changed, 40 insertions(+), 31 deletions(-) delete mode 100644 src/engines/blender/blender_ui.py delete mode 100644 src/engines/ffmpeg/ffmpeg_ui.py diff --git a/src/api/api_server.py b/src/api/api_server.py index d7572de..d4d96aa 100755 --- a/src/api/api_server.py +++ b/src/api/api_server.py @@ -380,6 +380,16 @@ def delete_job(job_id): # Engine Info and Management: # -------------------------------------------- +@server.get('/api/engine_for_filename') +def get_engine_for_filename(): + filename = request.args.get("filename") + if not filename: + return "Error: filename is required", 400 + found_engine = EngineManager.engine_class_for_project_path(filename) + if not found_engine: + return f"Error: cannot find a suitable engine for '{filename}'", 400 + return found_engine.name() + @server.get('/api/installed_engines') def get_installed_engines(): result = {} @@ -464,7 +474,8 @@ def get_engine_info(engine_name): future_results = { 'supported_extensions': executor.submit(en.supported_extensions), 'supported_export_formats': executor.submit(en.get_output_formats), - 'system_info': executor.submit(en.system_info) + 'system_info': executor.submit(en.system_info), + 'options': executor.submit(en.ui_options) } for key, future in future_results.items(): @@ -620,6 +631,15 @@ def handle_detached_instance(_): return "Unavailable", 503 +@server.errorhandler(404) +def handle_404(error): + url = request.url + err_msg = f"404 Not Found: {url}" + if 'favicon' not in url: + logger.warning(err_msg) + return err_msg, 404 + + @server.errorhandler(Exception) def handle_general_error(general_error): err_msg = f"Server error: {general_error}" diff --git a/src/api/server_proxy.py b/src/api/server_proxy.py index 0822400..b3d7e0b 100644 --- a/src/api/server_proxy.py +++ b/src/api/server_proxy.py @@ -247,6 +247,10 @@ class RenderServerProxy: # Engines: # -------------------------------------------- + def get_engine_for_filename(self, filename, timeout=5): + response = self.request(f'engine_for_filename?filename={os.path.basename(filename)}', timeout) + return response.text + def get_installed_engines(self, timeout=5): return self.request_data(f'installed_engines', timeout) diff --git a/src/engines/blender/blender_engine.py b/src/engines/blender/blender_engine.py index 187fec7..303f6c7 100644 --- a/src/engines/blender/blender_engine.py +++ b/src/engines/blender/blender_engine.py @@ -24,10 +24,12 @@ class Blender(BaseRenderEngine): from src.engines.blender.blender_worker import BlenderRenderWorker return BlenderRenderWorker - @staticmethod - def ui_options(system_info): - from src.engines.blender.blender_ui import BlenderUI - return BlenderUI.get_options(system_info) + def ui_options(self): + options = [ + {'name': 'engine', 'options': self.supported_render_engines()}, + {'name': 'render_device', 'options': ['Any', 'GPU', 'CPU']}, + ] + return options def supported_extensions(self): return ['blend'] @@ -180,7 +182,7 @@ class Blender(BaseRenderEngine): logger.error("GPU data not found in the output.") def supported_render_engines(self): - engine_output = subprocess.run([self.engine_path(), '-E', 'help'], timeout=SUBPROCESS_TIMEOUT, + engine_output = subprocess.run([self.engine_path(), '-b', '-E', 'help'], timeout=SUBPROCESS_TIMEOUT, capture_output=True, creationflags=_creationflags).stdout.decode('utf-8').strip() render_engines = [x.strip() for x in engine_output.split('Blender Engine Listing:')[-1].strip().splitlines()] return render_engines diff --git a/src/engines/blender/blender_ui.py b/src/engines/blender/blender_ui.py deleted file mode 100644 index 100df2a..0000000 --- a/src/engines/blender/blender_ui.py +++ /dev/null @@ -1,9 +0,0 @@ - -class BlenderUI: - @staticmethod - def get_options(system_info): - options = [ - {'name': 'engine', 'options': system_info.get('engines', [])}, - {'name': 'render_device', 'options': ['Any', 'GPU', 'CPU']}, - ] - return options diff --git a/src/engines/core/base_engine.py b/src/engines/core/base_engine.py index 665eb8f..d50c6f7 100644 --- a/src/engines/core/base_engine.py +++ b/src/engines/core/base_engine.py @@ -133,8 +133,7 @@ class BaseRenderEngine(object): def downloader(): # override when subclassing if using a downloader class return None - @staticmethod - def ui_options(system_info): # override to return options for ui + def ui_options(self): # override to return options for ui return {} # -------------------------------------------- diff --git a/src/engines/ffmpeg/ffmpeg_engine.py b/src/engines/ffmpeg/ffmpeg_engine.py index a102e7a..2d89f36 100644 --- a/src/engines/ffmpeg/ffmpeg_engine.py +++ b/src/engines/ffmpeg/ffmpeg_engine.py @@ -20,10 +20,8 @@ class FFMPEG(BaseRenderEngine): from src.engines.ffmpeg.ffmpeg_worker import FFMPEGRenderWorker return FFMPEGRenderWorker - @staticmethod - def ui_options(system_info): - from src.engines.ffmpeg.ffmpeg_ui import FFMPEGUI - return FFMPEGUI.get_options(system_info) + def ui_options(self): + return [] def supported_extensions(self): help_text = (subprocess.check_output([self.engine_path(), '-h', 'full'], stderr=subprocess.STDOUT, diff --git a/src/engines/ffmpeg/ffmpeg_ui.py b/src/engines/ffmpeg/ffmpeg_ui.py deleted file mode 100644 index fdbe7fe..0000000 --- a/src/engines/ffmpeg/ffmpeg_ui.py +++ /dev/null @@ -1,5 +0,0 @@ -class FFMPEGUI: - @staticmethod - def get_options(system_info): - options = [] - return options diff --git a/src/ui/add_job_window.py b/src/ui/add_job_window.py index 5d203fc..a0ffc17 100644 --- a/src/ui/add_job_window.py +++ b/src/ui/add_job_window.py @@ -68,7 +68,6 @@ class NewRenderJobForm(QWidget): # Setup self.setWindowTitle("New Job") self.setup_ui() - self.update_engine_info() self.setup_project() # get renderer info in bg thread @@ -325,11 +324,11 @@ class NewRenderJobForm(QWidget): def update_engine_info(self): # get the engine info and add them all to the ui - engine = EngineManager.engine_class_for_project_path(self.project_path) installed_engines = self.server_proxy.get_installed_engines() self.engine_type.addItems(installed_engines.keys()) # select the best engine for the file type - self.engine_type.setCurrentText(engine.name().lower()) + engine_name = self.server_proxy.get_engine_for_filename(self.project_path) + self.engine_type.setCurrentText(engine_name) # refresh ui self.engine_changed() @@ -341,6 +340,7 @@ class NewRenderJobForm(QWidget): self.file_format_combo.clear() if current_engine: engine_info = self.server_proxy.get_engine_info(current_engine, 'full', timeout=10) + self.current_engine_options = engine_info.get('options', []) if not engine_info: raise FileNotFoundError(f"Cannot get information about engine '{current_engine}'") engine_vers = [v['version'] for v in engine_info['versions']] @@ -442,7 +442,6 @@ class NewRenderJobForm(QWidget): # Dynamic Engine Options clear_layout(self.engine_options_layout) # clear old options # dynamically populate option list - self.current_engine_options = {} #todo: fix this for option in self.current_engine_options: h_layout = QHBoxLayout() label = QLabel(option['name'].replace('_', ' ').capitalize() + ':') @@ -592,7 +591,7 @@ class SubmitWorker(QThread): children_jobs.append(child_job_data) job_json['child_jobs'] = children_jobs - # presubmission tasks + # presubmission tasks - use local installs engine_class = EngineManager.engine_class_with_name(self.window.engine_type.currentText().lower()) latest_engine = EngineManager.get_latest_engine_instance(engine_class) input_path = latest_engine.perform_presubmission_tasks(input_path) @@ -623,6 +622,7 @@ class GetProjectInfoWorker(QThread): def run(self): try: self.window.update_engine_info() + # this should be the only time we use a local engine instead of using the proxy besides submitting engine_class = EngineManager.engine_class_for_project_path(self.project_path) engine = EngineManager.get_latest_engine_instance(engine_class) self.window.project_info = engine.get_project_info(self.project_path)