From 815d5d78b21b89e9d42ad6fecd5d932f3296f53e Mon Sep 17 00:00:00 2001 From: Brett Williams Date: Mon, 22 May 2023 11:28:14 -0500 Subject: [PATCH 1/6] Scheduler defaults to localhost if available --- scheduler_gui.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/scheduler_gui.py b/scheduler_gui.py index 26d7f09..d5f452f 100755 --- a/scheduler_gui.py +++ b/scheduler_gui.py @@ -145,10 +145,13 @@ class ScheduleJob(Frame): hostname = file.read() server_data = request_data(hostname, 'status', timeout=server_setup_timeout) if server_data: - self.server_hostname = hostname - self.server_button.configure(text=hostname) + self.set_hostname(hostname) if not self.server_hostname: - self.request_new_hostname() + server_data = request_data('localhost', 'status', timeout=server_setup_timeout) + if server_data: + self.set_hostname(server_data['host_name']) + else: + self.request_new_hostname() self.fetch_server_data() def fetch_server_data(self): @@ -179,11 +182,12 @@ class ScheduleJob(Frame): messagebox.showerror("Cannot connect", f"Cannot connect to server \"{user_hostname_input}\"") else: hostname = user_hostname_input + self.set_hostname(hostname) + + def set_hostname(self, hostname): self.server_hostname = hostname self.server_button.configure(text=self.server_hostname) - - # save to prefs - with open(prefs_name, 'w') as file: + with open(prefs_name, 'w') as file: # save to prefs file.write(hostname) def choose_file_button(self): From 1e92cb3a06f4db7096ab48e46343c0011cce43e2 Mon Sep 17 00:00:00 2001 From: Brett Williams Date: Mon, 22 May 2023 13:43:39 -0500 Subject: [PATCH 2/6] Add custom args to GUI and add arg conflict validation to workers --- lib/render_job.py | 1 + lib/render_workers/aerender_worker.py | 2 +- lib/render_workers/blender_worker.py | 5 +++-- lib/render_workers/ffmpeg_worker.py | 2 +- lib/render_workers/render_worker.py | 24 ++++++++++++++++++++++-- scheduler_gui.py | 11 +++++++++-- 6 files changed, 37 insertions(+), 8 deletions(-) diff --git a/lib/render_job.py b/lib/render_job.py index 4ba22eb..3da21de 100644 --- a/lib/render_job.py +++ b/lib/render_job.py @@ -33,6 +33,7 @@ class RenderJob: self.worker = RenderWorkerFactory.create_worker(renderer, input_path, output_path, args) self.worker.log_path = os.path.join(os.path.dirname(input_path), self.name + '.log') + self.worker.validate() self.file_hash = None threading.Thread(target=self.__get_file_hash).start() # get file hash on bg thread diff --git a/lib/render_workers/aerender_worker.py b/lib/render_workers/aerender_worker.py index 79ae63a..57f446c 100644 --- a/lib/render_workers/aerender_worker.py +++ b/lib/render_workers/aerender_worker.py @@ -47,7 +47,7 @@ class AERenderWorker(BaseRenderWorker): logging.error(f'Failed to get {cls.renderer} version: {e}') return version - def _generate_subprocess(self): + def generate_worker_subprocess(self): if os.path.exists('nexrender-cli-macos'): logging.info('nexrender found') diff --git a/lib/render_workers/blender_worker.py b/lib/render_workers/blender_worker.py index 101349f..e78b8b1 100644 --- a/lib/render_workers/blender_worker.py +++ b/lib/render_workers/blender_worker.py @@ -21,7 +21,8 @@ class BlenderRenderWorker(BaseRenderWorker): self.engine = self.args.get('engine', 'BLENDER_EEVEE').upper() self.export_format = self.args.get('export_format', None) or 'JPEG' self.camera = self.args.get('camera', None) - self.render_all_frames = self.args.get('render_all_frames', False) + self.render_all_frames = self.args.get('render_all_frames', False) or \ + '-a' in (self.args.get('raw', None) or "").split(' ') self.frame_to_render = 0 # Stats @@ -45,7 +46,7 @@ class BlenderRenderWorker(BaseRenderWorker): logging.error(f'Failed to get {cls.renderer} version: {e}') return version - def _generate_subprocess(self): + def generate_worker_subprocess(self): cmd = [self.renderer_path()] if self.args.get('background', True): # optionally run render not in background diff --git a/lib/render_workers/ffmpeg_worker.py b/lib/render_workers/ffmpeg_worker.py index a3448d9..36aa102 100644 --- a/lib/render_workers/ffmpeg_worker.py +++ b/lib/render_workers/ffmpeg_worker.py @@ -35,7 +35,7 @@ class FFMPEGRenderWorker(BaseRenderWorker): logger.error("Failed to get FFMPEG version: {}".format(e)) return version - def _generate_subprocess(self): + def generate_worker_subprocess(self): cmd = [self.renderer_path(), '-y', '-stats', '-i', self.input_path] diff --git a/lib/render_workers/render_worker.py b/lib/render_workers/render_worker.py index 8ab8ccf..6c68ec7 100644 --- a/lib/render_workers/render_worker.py +++ b/lib/render_workers/render_worker.py @@ -92,8 +92,28 @@ class BaseRenderWorker(object): logging.exception(e) return path - def _generate_subprocess(self): - raise NotImplementedError("_generate_subprocess not implemented") + def validate(self): + if not os.path.exists(self.input_path): + raise FileNotFoundError(f"Cannot find input path: {self.input_path}") + self.generate_subprocess() + + def generate_subprocess(self): + # Convert raw args from string if available and catch conflicts + cmd = self.generate_worker_subprocess() + raw_args = self.args.get('raw', None) + if raw_args: + import shlex + args_split = shlex.split(raw_args) + flags = [x for x in args_split if x.startswith('-')] + conflict = any(element in flags for element in args_split) + if conflict: + raise ValueError("Custom args conflicts set parameters") + else: + cmd.extend(args_split) + return cmd + + def generate_worker_subprocess(self): + raise NotImplementedError("generate_worker_subprocess not implemented") def start(self): diff --git a/scheduler_gui.py b/scheduler_gui.py index d5f452f..87c585b 100755 --- a/scheduler_gui.py +++ b/scheduler_gui.py @@ -16,7 +16,7 @@ from lib.utilities.server_helper import post_job_to_server logger = logging.getLogger() prefs_name = 'config/.scheduler_prefs' -label_width = 7 +label_width = 9 header_padding = 6 server_setup_timeout = 5 @@ -137,6 +137,13 @@ class ScheduleJob(Frame): self.blender_multiple_cameras = BooleanVar(value=False) self.blender_cameras_list = None + # custom args frame + custom_args_frame = Frame(self) + custom_args_frame.pack(fill=X) + Label(custom_args_frame, text="Custom Args", width=label_width).pack(side=LEFT, padx=5, pady=5) + self.custom_args_entry = Entry(custom_args_frame) + self.custom_args_entry.pack(side=LEFT, padx=5, expand=True, fill=X) + # Submit Button self.submit_frame = None @@ -311,7 +318,7 @@ class ScheduleJob(Frame): 'renderer': renderer, 'client': client, 'output_path': os.path.join(os.path.dirname(self.chosen_file), self.output_entry.get()), - 'args': {}, + 'args': {'raw': self.custom_args_entry.get()}, 'name': None} job_list = [] From fb7eec37faf44d5984d3328765ff8754842f4bc8 Mon Sep 17 00:00:00 2001 From: Brett Williams Date: Mon, 22 May 2023 14:57:28 -0500 Subject: [PATCH 3/6] Fix issue with subprocess --- lib/render_workers/blender_worker.py | 5 ----- lib/render_workers/render_worker.py | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/render_workers/blender_worker.py b/lib/render_workers/blender_worker.py index e78b8b1..ba292e0 100644 --- a/lib/render_workers/blender_worker.py +++ b/lib/render_workers/blender_worker.py @@ -61,11 +61,6 @@ class BlenderRenderWorker(BaseRenderWorker): # all frames or single cmd.extend(['-a'] if self.render_all_frames else ['-f', str(self.frame_to_render)]) - # Convert raw args from string if available - raw_args = self.args.get('raw', None) - if raw_args: - cmd.extend(raw_args.split(' ')) - return cmd def _parse_stdout(self, line): diff --git a/lib/render_workers/render_worker.py b/lib/render_workers/render_worker.py index 6c68ec7..c78241b 100644 --- a/lib/render_workers/render_worker.py +++ b/lib/render_workers/render_worker.py @@ -154,7 +154,7 @@ class BaseRenderWorker(object): logger.info('Attempt #{} failed. Starting attempt #{}'.format(self.failed_attempts, self.failed_attempts + 1)) # Start process and get updates - subprocess_cmds = self._generate_subprocess() + subprocess_cmds = self.generate_subprocess() logger.debug("Renderer commands generated - {}".format(" ".join(subprocess_cmds))) self.__process = subprocess.Popen(subprocess_cmds, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=False) From 81d5787fb029406e656e423050ddb211dae1ae56 Mon Sep 17 00:00:00 2001 From: Brett Williams Date: Mon, 22 May 2023 15:10:25 -0500 Subject: [PATCH 4/6] Fix scheduler issues with multiple blender cameras --- scheduler_gui.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/scheduler_gui.py b/scheduler_gui.py index 87c585b..8416789 100755 --- a/scheduler_gui.py +++ b/scheduler_gui.py @@ -304,10 +304,13 @@ class ScheduleJob(Frame): if self.blender_cameras_frame: self.blender_cameras_frame.pack_forget() - show_cams_checkbutton = Checkbutton(pack_frame, text='Multiple Cameras', offvalue=False, onvalue=True, + # multiple cameras checkbox + camera_count = len(scene_data.get('cameras', [])) if scene_data else 0 + show_cams_checkbutton = Checkbutton(pack_frame, text=f'Multiple Cameras ({camera_count})', offvalue=False, + onvalue=True, variable=self.blender_multiple_cameras, command=draw_scene_cams) show_cams_checkbutton.pack(side=LEFT, padx=5) - show_cams_checkbutton['state'] = NORMAL if scene_data and scene_data.get('cameras', None) else DISABLED + show_cams_checkbutton['state'] = NORMAL if camera_count > 1 else DISABLED def submit_job(self): @@ -343,8 +346,8 @@ class ScheduleJob(Frame): job_json['args']['export_format'] = self.output_format.get() # multiple camera rendering - selected_cameras = self.blender_cameras_list.getCheckedItems() if self.blender_cameras_list else None - if selected_cameras: + if self.blender_cameras_list and self.blender_multiple_cameras.get(): + selected_cameras = self.blender_cameras_list.getCheckedItems() for cam in selected_cameras: job_copy = copy.deepcopy(job_json) job_copy['args']['camera'] = cam.split('-')[0].strip() From 50ca036a5d239676373bf0639e81636f2c759cb8 Mon Sep 17 00:00:00 2001 From: Brett Williams Date: Mon, 22 May 2023 15:54:56 -0500 Subject: [PATCH 5/6] Add raw args then validate for duplicate flags --- lib/render_workers/blender_worker.py | 5 +++++ lib/render_workers/ffmpeg_worker.py | 4 ++-- lib/render_workers/render_worker.py | 26 +++++++++++++++----------- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/lib/render_workers/blender_worker.py b/lib/render_workers/blender_worker.py index ba292e0..73d62c8 100644 --- a/lib/render_workers/blender_worker.py +++ b/lib/render_workers/blender_worker.py @@ -61,6 +61,11 @@ class BlenderRenderWorker(BaseRenderWorker): # all frames or single cmd.extend(['-a'] if self.render_all_frames else ['-f', str(self.frame_to_render)]) + # Convert raw args from string if available + raw_args = self.get_raw_args() + if raw_args: + cmd.extend(raw_args) + return cmd def _parse_stdout(self, line): diff --git a/lib/render_workers/ffmpeg_worker.py b/lib/render_workers/ffmpeg_worker.py index 36aa102..fee773f 100644 --- a/lib/render_workers/ffmpeg_worker.py +++ b/lib/render_workers/ffmpeg_worker.py @@ -44,9 +44,9 @@ class FFMPEGRenderWorker(BaseRenderWorker): cmd.extend(['-vf', f"scale={self.args['x_resolution']}:{self.args['y_resolution']}"]) # Convert raw args from string if available - raw_args = self.args.get('raw', None) + raw_args = self.get_raw_args() if raw_args: - cmd.extend(raw_args.split(' ')) + cmd.extend(raw_args) # Close with output path cmd.append(self.output_path) diff --git a/lib/render_workers/render_worker.py b/lib/render_workers/render_worker.py index c78241b..4ee6cb6 100644 --- a/lib/render_workers/render_worker.py +++ b/lib/render_workers/render_worker.py @@ -99,18 +99,22 @@ class BaseRenderWorker(object): def generate_subprocess(self): # Convert raw args from string if available and catch conflicts - cmd = self.generate_worker_subprocess() - raw_args = self.args.get('raw', None) - if raw_args: + generated_args = self.generate_worker_subprocess() + generated_args_flags = [x for x in generated_args if x.startswith('-')] + if len(generated_args_flags) != len(set(generated_args_flags)): + msg = "Cannot generate subprocess - Multiple arg conflicts detected" + logger.error(msg) + logger.debug(f"Generated args for subprocess: {generated_args}") + raise ValueError(msg) + return generated_args + + def get_raw_args(self): + raw_args_string = self.args.get('raw', None) + raw_args = None + if raw_args_string: import shlex - args_split = shlex.split(raw_args) - flags = [x for x in args_split if x.startswith('-')] - conflict = any(element in flags for element in args_split) - if conflict: - raise ValueError("Custom args conflicts set parameters") - else: - cmd.extend(args_split) - return cmd + raw_args = shlex.split(raw_args_string) + return raw_args def generate_worker_subprocess(self): raise NotImplementedError("generate_worker_subprocess not implemented") From ab13b858c717f5bbd6a8dd545ccd36a51d572418 Mon Sep 17 00:00:00 2001 From: Brett Williams Date: Mon, 22 May 2023 16:07:01 -0500 Subject: [PATCH 6/6] Fix custom args in gui to bottom of window --- scheduler_gui.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/scheduler_gui.py b/scheduler_gui.py index 8416789..dd90575 100755 --- a/scheduler_gui.py +++ b/scheduler_gui.py @@ -137,14 +137,9 @@ class ScheduleJob(Frame): self.blender_multiple_cameras = BooleanVar(value=False) self.blender_cameras_list = None - # custom args frame - custom_args_frame = Frame(self) - custom_args_frame.pack(fill=X) - Label(custom_args_frame, text="Custom Args", width=label_width).pack(side=LEFT, padx=5, pady=5) - self.custom_args_entry = Entry(custom_args_frame) - self.custom_args_entry.pack(side=LEFT, padx=5, expand=True, fill=X) - - # Submit Button + # Custom Args / Submit Button + self.custom_args_frame = None + self.custom_args_entry = None self.submit_frame = None if os.path.exists(prefs_name): @@ -227,6 +222,7 @@ class ScheduleJob(Frame): if renderer == 'blender': self.draw_blender_settings() + self.draw_custom_args() self.draw_submit_button() if self.renderer_info.get(renderer, {}).get('supported_export_formats', None): @@ -239,6 +235,15 @@ class ScheduleJob(Frame): self.output_format['values'] = [] self.output_format['state'] = DISABLED + def draw_custom_args(self): + if hasattr(self, 'custom_args_frame') and self.custom_args_frame: + self.custom_args_frame.forget() + self.custom_args_frame = Frame(self) + self.custom_args_frame.pack(side=TOP, fill=X, expand=False) + Label(self.custom_args_frame, text="Custom Args", width=label_width).pack(side=LEFT, padx=5, pady=5) + self.custom_args_entry = Entry(self.custom_args_frame) + self.custom_args_entry.pack(side=TOP, padx=5, expand=True, fill=X) + def draw_submit_button(self): if hasattr(self, 'submit_frame') and self.submit_frame: self.submit_frame.forget()