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..73d62c8 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 @@ -61,9 +62,9 @@ class BlenderRenderWorker(BaseRenderWorker): 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) + raw_args = self.get_raw_args() if raw_args: - cmd.extend(raw_args.split(' ')) + cmd.extend(raw_args) return cmd diff --git a/lib/render_workers/ffmpeg_worker.py b/lib/render_workers/ffmpeg_worker.py index a3448d9..fee773f 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] @@ -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 8ab8ccf..4ee6cb6 100644 --- a/lib/render_workers/render_worker.py +++ b/lib/render_workers/render_worker.py @@ -92,8 +92,32 @@ 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 + 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 + raw_args = shlex.split(raw_args_string) + return raw_args + + def generate_worker_subprocess(self): + raise NotImplementedError("generate_worker_subprocess not implemented") def start(self): @@ -134,7 +158,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) diff --git a/scheduler_gui.py b/scheduler_gui.py index 26d7f09..dd90575 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,7 +137,9 @@ class ScheduleJob(Frame): self.blender_multiple_cameras = BooleanVar(value=False) self.blender_cameras_list = None - # 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): @@ -145,10 +147,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 +184,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): @@ -216,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): @@ -228,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() @@ -293,10 +309,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): @@ -307,7 +326,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 = [] @@ -332,8 +351,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()