Combine RenderWorker creation into RenderJob creation. Misc cleanup.

This commit is contained in:
Brett Williams
2022-10-28 09:58:32 -07:00
parent 39d6e95e9a
commit 37f91c6f8a
10 changed files with 108 additions and 396 deletions

View File

@@ -17,20 +17,26 @@ class BlenderRenderWorker(BaseRenderWorker):
super(BlenderRenderWorker, self).__init__(input_path=input_path, output_path=output_path,
ignore_extensions=False, args=args)
# Args
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.frame = 0
self.render_all_frames = self.args.get('render_all_frames', False)
self.frame = 0 #todo: remove this attribute
# Stats
self.current_frame = -1
self.memory_use = None
self.time_elapsed = None
self.time_remaining = None
self.frame_percent_complete = 0.0
# Scene Info
self.scene_info = get_scene_info(input_path)
self.total_frames = int(self.scene_info.get('frame_end', 0))
self.current_frame = int(self.scene_info.get('frame_start', 0))
self.resolution = {'x': int(self.scene_info.get('resolution_x', 0)),
'y': int(self.scene_info.get('resolution_y', 0))}
@classmethod
def version(cls):
version = None
@@ -111,8 +117,10 @@ class BlenderRenderWorker(BaseRenderWorker):
if self.total_frames <= 1:
return self.frame_percent_complete
else:
return (self.current_frame / self.total_frames) + \
(self.frame_percent_complete * (self.current_frame / self.total_frames))
whole_frame_percent = (self.current_frame - 1) / self.total_frames
adjusted_frame_percent = self.frame_percent_complete / self.total_frames
total_percent = whole_frame_percent + adjusted_frame_percent
return max(total_percent, 0)
def run_python_expression_in_blend(path, python_expression):
@@ -141,7 +149,10 @@ def run_python_script_in_blend(path, python_path):
def pack_blender_files(path):
# Credit to L0Lock for pack script - https://blender.stackexchange.com/a/243935
pack_script = "import bpy\nbpy.ops.file.pack_all()\nmyPath = bpy.data.filepath\nmyPath = str(myPath)\n" \
pack_script = "import bpy\n" \
"bpy.ops.file.pack_all()\n" \
"myPath = bpy.data.filepath\n" \
"myPath = str(myPath)\n" \
"bpy.ops.wm.save_as_mainfile(filepath=myPath[:-6]+'_packed'+myPath[-6:])"
try:
@@ -171,7 +182,7 @@ def get_scene_info(path):
scene_info = None
try:
results = run_python_script_in_blend(path, os.path.join(os.path.dirname(os.path.realpath(__file__)),
'get_blender_info.py'))
'scripts', 'get_blender_info.py'))
result_text = results.stdout.decode()
for line in result_text.splitlines():
if line.startswith('SCENE_DATA:'):

View File

@@ -1,200 +0,0 @@
import xml.etree.ElementTree as ET
import argparse
import os
import glob
from urllib2 import unquote
import time
library = None
class FCPXLibrary:
def __init__(self, xml_path):
parser = ET.parse(xml_path)
self.root = parser.getroot()
self.xml_version = self.root.attrib.get('version')
self.location = self.library_location()
# self.projects = self.root.findall('./library/event/project')
self.formats = self.root.findall('./resources/format')
self.clips = [Clip(x, self) for x in self.root.findall(".//asset-clip")]
self.projects = [Project(x, self) for x in self.root.findall('./library/event/project')]
def formats(self):
return self.root.findall('./resources/format')
def element_with_tag_value(self, element, tag, value):
return self.root.findall(".//{e}[@{t}='{v}']".format(e=element, t=tag, v=value))
def clips_with_videorole(self, role):
return [clip for clip in self.clips if getattr(clip, 'videoRole', None) == role]
def format_with_id(self, id):
# return self.root.findall("./resources/format[id='{}']".format(id))
return self.element_with_tag_value('format', 'id', id)
def library_location(self):
# urllib2.unquote(asset_ref.get('src'))[7:]
path = self.root.findall('./library')[0].attrib['location']
return unquote(path)[7:]
class Project:
def __init__(self, project_element, library):
for attrib in project_element.attrib:
setattr(self, attrib, project_element.get(attrib))
print(project_element.attrib)
print(project_element.parent)
ref_clips = project_element.findall(".//ref-clip")
print('start')
for clip in library.clips:
print(clip.name)
if clip.name == ref_clips[0]:
print(clip)
break
print('end')
# for child in ref_clips:
# print(child.tag, child.attrib)
class Clip:
def __init__(self, clip_element, library):
# self.library = library
# Get attribs from XML
for attrib in clip_element.attrib:
setattr(self, attrib, clip_element.get(attrib))
self.type = 'audio' if hasattr(self, 'audioRole') else 'video'
# Get clip reference
asset_ref = next(iter(library.element_with_tag_value('asset', 'id', self.ref)))
for attrib in asset_ref.attrib:
if not hasattr(self, attrib):
setattr(self, attrib, asset_ref.get(attrib))
self.source = unquote(asset_ref.get('src'))[7:]
if self.type == 'video':
format_id = getattr(self, 'format', asset_ref.get('format', None))
video_format = next(iter(library.format_with_id(format_id)))
if not hasattr(self, 'format'):
print('no format!')
try:
frame_duration = fcp_time_to_float(video_format.get('frameDuration'))
self.in_frame = int(round(fcp_time_to_float(self.start) / frame_duration))
duration = int(round(fcp_time_to_float(self.duration) / frame_duration))
self.out_frame = self.in_frame + duration
except Exception as e:
print('in/out fail: ' + str(e))
print(dir(self))
pass
def optimized_source(self):
path = None
mov = os.path.splitext(os.path.basename(self.source))[0] + '.mov'
found = glob.glob(os.path.join(library.location, '*', 'Transcoded Media', 'High Quality Media', mov))
if found:
path = found[0]
print(path)
return path
def proxy_source(self):
path = None
mov = os.path.splitext(os.path.basename(self.source))[0] + '.mov'
found = glob.glob(os.path.join(library.location, '*', 'Transcoded Media', 'Proxy Media', mov))
if found:
path = found[0]
print(path)
return path
def __repr__(self):
if self.type == 'video':
return "<Clip name:'%s' type: %s role: '%s' duration:%s frames>" % (getattr(self, 'name', None), self.type,
getattr(self, 'videoRole', None), self.out_frame - self.in_frame)
else:
return "<Clip name:'%s' type: %s role: '%s'>" % (getattr(self, 'name', None), self.type, getattr(self, 'audioRole', None))
def fcp_time_to_float(timestr):
try:
rates = timestr.strip('s').split('/')
return float(rates[0]) / float(rates[-1])
except (ZeroDivisionError, AttributeError) as e:
return 0.0
import sys
from types import ModuleType, FunctionType
from gc import get_referents
# Custom objects know their class.
# Function objects seem to know way too much, including modules.
# Exclude modules as well.
BLACKLIST = type, ModuleType, FunctionType
def getsize(obj):
"""sum size of object & members."""
if isinstance(obj, BLACKLIST):
raise TypeError('getsize() does not take argument of type: '+ str(type(obj)))
seen_ids = set()
size = 0
objects = [obj]
while objects:
need_referents = []
for obj in objects:
if not isinstance(obj, BLACKLIST) and id(obj) not in seen_ids:
seen_ids.add(id(obj))
size += sys.getsizeof(obj)
need_referents.append(obj)
objects = get_referents(*need_referents)
return size
# if __name__ == "__main__":
#
# parser = argparse.ArgumentParser()
# parser.add_argument('-i', '--input', help='Input FCPX Library XML', required=True)
# parser.add_argument('-s', '--save-file', help='Description', required=False)
#
# args = parser.parse_args()
#
# library = FCPXLibrary(args.input)
#
# print getsize(library)
# while True:
# time.sleep(4)
#
# print library.library_location()
#
# print dir(library.clips[0])
# print library.clips[0]
# print library.clips[0].proxy_source()
#
# print(args.input)
# print(args.save_file)
if __name__ == '__main__':
library = FCPXLibrary('new.fcpxml')
# print library.clips[0].source
# print library.library_location()
#
# print dir(library.clips[0])
# print library.clips[0]
# print library.clips[0].proxy_source()
# for clip in library.clips:
# print clip
print(dir(library.projects[0]))
print(library.formats)

View File

@@ -1,107 +0,0 @@
import json
import requests
from requests.auth import HTTPBasicAuth
from datetime import datetime, timezone
import time
import logging
class OpenProject:
def __init__(self):
# self.server_url = "http://localhost:8080"
self.server_url = "http://17.114.221.240:8080"
# self.api_key = "bb5897eb1daf9bdc4b400675de8e1e52bd64e1e8bce95b341a61a036431c850e"
self.api_key = "b902d975fcf6a29558e611e665145282acffa1e7109bfb462ef25266f7f9ed6e"
def create_shot(self, scene, shot, project, sequence=None):
url = self.server_url + "/api/v3/work_packages"
project_url = 1
attributes = {
"subject": "SC{}_{}".format(scene, shot),
"customField2": scene,
"customField1": shot,
"_links": {
"project": {"href": "/api/v3/projects/{}".format(project_url)},
"type": {"href": "/api/v3/types/1"}
}
}
return self._send_command(url, attributes)
def add_comment(self, work_project_id, comment, notify=False):
url = self.server_url + "/api/v3/work_packages/{}/activities?notify={}".format(str(work_project_id), str(notify))
attributes = {"comment": {"raw": comment}}
return self._send_command(url, attributes)
def get_work_package(self, identifier=None, attribute=None):
url = self.server_url + "/api/v3/work_packages/"
if identifier:
url = url + str(identifier)
return self._send_command(url, attribute)
def get_projects(self, identifier=None):
url = self.server_url + "/api/v3/projects/"
if identifier:
url = url + str(identifier)
return self._send_command(url, None)
def _send_command(self, url, body):
if body:
response = requests.post(url, json=body,
auth=HTTPBasicAuth('apikey', self.api_key))
else:
response = requests.get(url, auth=HTTPBasicAuth('apikey', self.api_key))
if not response.ok:
logging.error('Response error: {}'.format(response.reason))
return response.json()
class OpenProjectWatcher:
def __init__(self, op_instance, interval=30):
self.op = OpenProject()
self.interval = interval
self.last_check = None
def _check_projects(self):
projects = self.op.get_projects()
for project in projects['_embedded']['elements']:
# last_update = datetime.datetime.fromisoformat(project['updatedAt'])
last_update = datetime.strptime(project['updatedAt'], "%Y-%m-%dT%H:%M:%S%z")
if not self.last_check or last_update > self.last_check:
logging.info("Update found for project: {}".format(project['name']))
# todo: do something with updated info
def _check_work_projects(self):
packages = self.op.get_work_package()
for pkg in packages['_embedded']['elements']:
# print(pkg.keys())
last_update = datetime.strptime(pkg['updatedAt'], "%Y-%m-%dT%H:%M:%S%z")
if not self.last_check or last_update > self.last_check:
logging.info("Update found for shot: {}".format(pkg['subject']))
# todo: do something with updated info
def watch(self):
while True:
now = datetime.now(timezone.utc)
self._check_projects()
self._check_work_projects()
self.last_check = now
time.sleep(self.interval)
if __name__ == '__main__':
logging.basicConfig(level=logging.INFO)
op = OpenProject()
op.add_comment(42, "After Effects Render completed successfully. Log available here.", True)
# print(op.get_projects())
watcher = OpenProjectWatcher(OpenProject())
watcher.watch()

View File

@@ -51,7 +51,7 @@ class BaseRenderWorker(object):
self.date_created = datetime.now()
self.renderer_version = self.version()
# Ranges
# Frame Ranges
self.total_frames = 0
self.current_frame = 0

View File

@@ -16,4 +16,4 @@ data = {'cameras': cameras,
'resolution_y': bpy.data.scenes[0].render.resolution_y}
data_string = json.dumps(data)
print("SCENE_DATA:" + data_string)
print("SCENE_DATA:" + data_string)