import glob import hashlib import json import logging import os import threading import uuid from datetime import datetime from .render_workers.render_worker import RenderStatus, RenderWorkerFactory logger = logging.getLogger() class RenderJob: # Get file hash on bg thread def __get_file_hash(self): if os.path.exists(self.worker.input_path): self.file_hash = hashlib.md5(open(self.worker.input_path, 'rb').read()).hexdigest() def __init__(self, renderer, input_path, output_path, args, priority=2, owner=None, client=None, notify=None, custom_id=None, name=None): self.id = custom_id or self.generate_id() self.owner = owner self.priority = priority self.client = client self.notify = notify self.date_created = datetime.now() self.scheduled_start = None self.renderer = renderer self.name = name or os.path.basename(input_path) + '_' + self.date_created.strftime("%Y.%m.%d_%H.%M.%S") 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 def render_status(self): if self.scheduled_start and self.worker.status == RenderStatus.NOT_STARTED: return RenderStatus.SCHEDULED else: return self.worker.status def file_hash(self): if os.path.exists(self.worker.input_path): return hashlib.md5(open(self.worker.input_path, 'rb').read()).hexdigest() return None def json(self): """Converts RenderJob into JSON-friendly dict""" job_dict = None try: job_dict = self.__dict__.copy() job_dict['status'] = self.render_status().value job_dict['time_elapsed'] = self.time_elapsed() if type(self.time_elapsed) != str else self.time_elapsed job_dict['file_hash'] = self.file_hash job_dict['percent_complete'] = self.percent_complete() job_dict['file_list'] = self.file_list() job_dict['worker'] = self.worker.__dict__.copy() job_dict['worker']['status'] = job_dict['status'] # remove unwanted keys from dict keys_to_remove = ['thread', 'process'] for key in job_dict['worker'].keys(): if key.startswith('_'): keys_to_remove.append(key) for key in keys_to_remove: job_dict['worker'].pop(key, None) # convert to json and back to auto-convert dates to iso format def date_serializer(o): if isinstance(o, datetime): return o.isoformat() json_convert = json.dumps(job_dict, default=date_serializer) job_dict = json.loads(json_convert) except Exception as e: logger.exception(e) logger.error("Error converting to JSON: {}".format(e)) return job_dict def start(self): self.worker.start() def stop(self): self.worker.stop() def time_elapsed(self): from string import Template class DeltaTemplate(Template): delimiter = "%" def strfdelta(tdelta, fmt='%H:%M:%S'): d = {"D": tdelta.days} hours, rem = divmod(tdelta.seconds, 3600) minutes, seconds = divmod(rem, 60) d["H"] = '{:02d}'.format(hours) d["M"] = '{:02d}'.format(minutes) d["S"] = '{:02d}'.format(seconds) t = DeltaTemplate(fmt) return t.substitute(**d) # calculate elapsed time elapsed_time = None start_time = self.worker.start_time end_time = self.worker.end_time if start_time: if end_time: elapsed_time = end_time - start_time elif self.render_status() == RenderStatus.RUNNING: elapsed_time = datetime.now() - start_time elapsed_time_string = strfdelta(elapsed_time) if elapsed_time else "Unknown" return elapsed_time_string def frame_count(self): return self.worker.total_frames def work_path(self): return os.path.dirname(self.worker.output_path) def file_list(self): job_dir = os.path.dirname(self.worker.output_path) return glob.glob(os.path.join(job_dir, '*')) def log_path(self): return self.worker.log_path def percent_complete(self): return self.worker.percent_complete() @classmethod def generate_id(cls): return str(uuid.uuid4()).split('-')[0]