Rename renderers to engines (#123)

* Bulk rename
* Fix build issues
This commit is contained in:
2025-12-27 18:53:09 -06:00
committed by GitHub
parent 574c6f0755
commit daf445ee9e
23 changed files with 367 additions and 157 deletions

View File

@@ -8,7 +8,7 @@ class AERender(BaseRenderEngine):
def version(self):
version = None
try:
render_path = self.renderer_path()
render_path = self.engine_path()
if render_path:
ver_out = subprocess.check_output([render_path, '-version'], timeout=SUBPROCESS_TIMEOUT)
version = ver_out.decode('utf-8').split(" ")[-1].strip()

View File

@@ -35,7 +35,7 @@ class Blender(BaseRenderEngine):
def version(self):
version = None
try:
render_path = self.renderer_path()
render_path = self.engine_path()
if render_path:
ver_out = subprocess.check_output([render_path, '-v'], timeout=SUBPROCESS_TIMEOUT,
creationflags=_creationflags)
@@ -52,7 +52,7 @@ class Blender(BaseRenderEngine):
def run_python_expression(self, project_path, python_expression, timeout=None):
if os.path.exists(project_path):
try:
return subprocess.run([self.renderer_path(), '-b', project_path, '--python-expr', python_expression],
return subprocess.run([self.engine_path(), '-b', project_path, '--python-expr', python_expression],
capture_output=True, timeout=timeout, creationflags=_creationflags)
except Exception as e:
err_msg = f"Error running python expression in blender: {e}"
@@ -69,7 +69,7 @@ class Blender(BaseRenderEngine):
raise FileNotFoundError(f'Python script not found: {script_path}')
try:
command = [self.renderer_path(), '-b', '--python', script_path]
command = [self.engine_path(), '-b', '--python', script_path]
if project_path:
command.insert(2, project_path)
result = subprocess.run(command, capture_output=True, timeout=timeout, creationflags=_creationflags)
@@ -132,7 +132,7 @@ class Blender(BaseRenderEngine):
return None
def get_arguments(self):
help_text = subprocess.check_output([self.renderer_path(), '-h'], creationflags=_creationflags).decode('utf-8')
help_text = subprocess.check_output([self.engine_path(), '-h'], creationflags=_creationflags).decode('utf-8')
lines = help_text.splitlines()
options = {}
@@ -179,7 +179,7 @@ class Blender(BaseRenderEngine):
logger.error("GPU data not found in the output.")
def supported_render_engines(self):
engine_output = subprocess.run([self.renderer_path(), '-E', 'help'], timeout=SUBPROCESS_TIMEOUT,
engine_output = subprocess.run([self.engine_path(), '-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

View File

@@ -26,7 +26,7 @@ class BlenderRenderWorker(BaseRenderWorker):
def generate_worker_subprocess(self):
cmd = [self.renderer_path]
cmd = [self.engine_path]
if self.args.get('background', True): # optionally run render not in background
cmd.append('-b')
cmd.append(self.input_path)

View File

@@ -9,7 +9,7 @@ SUBPROCESS_TIMEOUT = 5
class BaseRenderEngine(object):
"""Base class for render engines. This class provides common functionality and structure for various rendering
engines. Create subclasses and override the methods marked below to add additional renderers
engines. Create subclasses and override the methods marked below to add additional engines
Attributes:
install_paths (list): A list of default installation paths where the render engine
@@ -24,13 +24,13 @@ class BaseRenderEngine(object):
# --------------------------------------------
def __init__(self, custom_path=None):
self.custom_renderer_path = custom_path
if not self.renderer_path() or not os.path.exists(self.renderer_path()):
raise FileNotFoundError(f"Cannot find path to renderer for {self.name()} instance: {self.renderer_path()}")
self.custom_engine_path = custom_path
if not self.engine_path() or not os.path.exists(self.engine_path()):
raise FileNotFoundError(f"Cannot find path to engine for {self.name()} instance: {self.engine_path()}")
if not os.access(self.renderer_path(), os.X_OK):
logger.warning(f"Path is not executable. Setting permissions to 755 for {self.renderer_path()}")
os.chmod(self.renderer_path(), 0o755)
if not os.access(self.engine_path(), os.X_OK):
logger.warning(f"Path is not executable. Setting permissions to 755 for {self.engine_path()}")
os.chmod(self.engine_path(), 0o755)
def version(self):
"""Return the version number as a string.
@@ -60,7 +60,7 @@ class BaseRenderEngine(object):
@classmethod
def get_output_formats(cls):
"""Returns a list of available output formats supported by the renderer.
"""Returns a list of available output formats supported by the engine.
Returns:
list[str]: A list of strings representing the available output formats.
@@ -83,20 +83,20 @@ class BaseRenderEngine(object):
return []
def get_help(self):
"""Retrieves the help documentation for the renderer.
"""Retrieves the help documentation for the engine.
This method runs the renderer's help command (default: '-h') and captures the output.
Override this method if the renderer uses a different help flag.
This method runs the engine's help command (default: '-h') and captures the output.
Override this method if the engine uses a different help flag.
Returns:
str: The help documentation as a string.
Raises:
FileNotFoundError: If the renderer path is not found.
FileNotFoundError: If the engine path is not found.
"""
path = self.renderer_path()
path = self.engine_path()
if not path:
raise FileNotFoundError("renderer path not found")
raise FileNotFoundError(f"Engine path not found: {path}")
creationflags = subprocess.CREATE_NO_WINDOW if platform.system() == 'Windows' else 0
help_doc = subprocess.check_output([path, '-h'], stderr=subprocess.STDOUT,
timeout=SUBPROCESS_TIMEOUT, creationflags=creationflags).decode('utf-8')
@@ -141,15 +141,15 @@ class BaseRenderEngine(object):
# Do Not Override These Methods:
# --------------------------------------------
def renderer_path(self):
return self.custom_renderer_path or self.default_renderer_path()
def engine_path(self):
return self.custom_engine_path or self.default_engine_path()
@classmethod
def name(cls):
return str(cls.__name__).lower()
@classmethod
def default_renderer_path(cls):
def default_engine_path(cls):
path = None
try: # Linux and macOS
path = subprocess.check_output(['which', cls.name()], timeout=SUBPROCESS_TIMEOUT).decode('utf-8').strip()

View File

@@ -32,9 +32,9 @@ class BaseRenderWorker(Base):
date_created = Column(DateTime)
start_time = Column(DateTime, nullable=True)
end_time = Column(DateTime, nullable=True)
renderer = Column(String)
renderer_version = Column(String)
renderer_path = Column(String)
engine_name = Column(String)
engine_version = Column(String)
engine_path = Column(String)
priority = Column(Integer)
project_length = Column(Integer)
start_frame = Column(Integer)
@@ -46,8 +46,6 @@ class BaseRenderWorker(Base):
file_hash = Column(String)
_status = Column(String)
engine = None
# --------------------------------------------
# Required Overrides for Subclasses:
# --------------------------------------------
@@ -57,7 +55,7 @@ class BaseRenderWorker(Base):
if not ignore_extensions:
if not any(ext in input_path for ext in self.engine.supported_extensions()):
err_meg = f'Cannot find valid project with supported file extension for {self.engine.name()} renderer'
err_meg = f"Cannot find valid project with supported file extension for '{self.engine.name()}'"
logger.error(err_meg)
raise ValueError(err_meg)
if not self.engine:
@@ -74,10 +72,10 @@ class BaseRenderWorker(Base):
self.output_path = output_path
self.args = args or {}
self.date_created = datetime.now()
self.renderer = self.engine.name()
self.renderer_path = engine_path
self.renderer_version = self.engine(engine_path).version()
self.custom_renderer_path = None
self.engine_name = self.engine.name()
self.engine_path = engine_path
self.engine_version = self.engine(engine_path).version()
self.custom_engine_path = None
self.priority = priority
self.parent = parent
self.children = {}
@@ -116,17 +114,17 @@ class BaseRenderWorker(Base):
raise NotImplementedError("generate_worker_subprocess not implemented")
def _parse_stdout(self, line):
"""Parses a line of standard output from the renderer.
"""Parses a line of standard output from the engine.
This method should be overridden in a subclass to implement the logic for processing
and interpreting a single line of output from the renderer's standard output stream.
and interpreting a single line of output from the engine's standard output stream.
On frame completion, the subclass should:
1. Update value of self.current_frame
2. Call self._send_frame_complete_notification()
Args:
line (str): A line of text from the renderer's standard output.
line (str): A line of text from the engine's standard output.
Raises:
NotImplementedError: If the method is not overridden in a subclass.
@@ -152,7 +150,7 @@ class BaseRenderWorker(Base):
# --------------------------------------------
def __repr__(self):
return f"<Job id:{self.id} p{self.priority} {self.renderer}-{self.renderer_version} '{self.name}' status:{self.status.value}>"
return f"<Job id:{self.id} p{self.priority} {self.engine_name}-{self.engine_version} '{self.name}' status:{self.status.value}>"
@property
def total_frames(self):
@@ -215,7 +213,7 @@ class BaseRenderWorker(Base):
self.errors.append(msg)
return
if not os.path.exists(self.renderer_path):
if not os.path.exists(self.engine_path):
self.status = RenderStatus.ERROR
msg = f'Cannot find render engine path for {self.engine.name()}'
logger.error(msg)
@@ -269,14 +267,14 @@ class BaseRenderWorker(Base):
logger.error(err_msg)
self.errors.append(err_msg)
# handle instances where renderer exits ok but doesnt generate files
# handle instances where engine exits ok but doesnt generate files
if not return_code and not file_count_has_increased:
err_msg = (f"{self.engine.name()} render exited ok, but file count has not increased. "
f"Count is still {len(self.file_list())}")
log_file.write(f'Error: {err_msg}\n\n')
self.errors.append(err_msg)
# only count the attempt as failed if renderer creates no output - reset counter on successful output
# only count the attempt as failed if engine creates no output - reset counter on successful output
failed_attempts = 0 if file_count_has_increased else failed_attempts + 1
def __run__wait_for_subjobs(self, logfile):
@@ -302,7 +300,7 @@ class BaseRenderWorker(Base):
with open(self.log_path(), "a") as log_file:
self.log_and_print(f"{self.start_time.isoformat()} - Starting "
f"{self.engine.name()} {self.renderer_version} render job for {self.name} "
f"{self.engine.name()} {self.engine_version} render job for {self.name} "
f"({self.input_path})", log_file)
log_file.write(f"\n")
if not self.children:
@@ -493,8 +491,8 @@ class BaseRenderWorker(Base):
'file_hash': self.file_hash,
'percent_complete': self.percent_complete(),
'file_count': len(self.file_list()),
'renderer': self.renderer,
'renderer_version': self.renderer_version,
'engine': self.engine_name,
'engine_version': self.engine_version,
'errors': getattr(self, 'errors', None),
'start_frame': self.start_frame,
'end_frame': self.end_frame,

View File

@@ -12,7 +12,7 @@ logger = logging.getLogger()
class EngineManager:
"""Class that manages different versions of installed renderers and handles fetching and downloading new versions,
"""Class that manages different versions of installed render engines and handles fetching and downloading new versions,
if possible.
"""
@@ -88,7 +88,7 @@ class EngineManager:
'version': version or 'error',
'system_os': current_system_os(),
'cpu': current_system_cpu(),
'path': eng.default_renderer_path(),
'path': eng.default_engine_path(),
'type': 'system'
}
@@ -96,7 +96,7 @@ class EngineManager:
futures = {
executor.submit(fetch_engine_details, eng, include_corrupt): eng.name()
for eng in cls.supported_engines()
if eng.default_renderer_path() and (not filter_name or filter_name == eng.name())
if eng.default_engine_path() and (not filter_name or filter_name == eng.name())
}
for future in concurrent.futures.as_completed(futures):
@@ -240,14 +240,14 @@ class EngineManager:
thread.start()
@classmethod
def create_worker(cls, renderer, input_path, output_path, engine_version=None, args=None, parent=None, name=None):
def create_worker(cls, engine_name, input_path, output_path, engine_version=None, args=None, parent=None, name=None):
worker_class = cls.engine_with_name(renderer).worker_class()
worker_class = cls.engine_with_name(engine_name).worker_class()
# check to make sure we have versions installed
all_versions = cls.all_versions_for_engine(renderer)
all_versions = cls.all_versions_for_engine(engine_name)
if not all_versions:
raise FileNotFoundError(f"Cannot find any installed {renderer} engines")
raise FileNotFoundError(f"Cannot find any installed '{engine_name}' engines")
# Find the path to the requested engine version or use default
engine_path = None
@@ -259,9 +259,9 @@ class EngineManager:
# Download the required engine if not found locally
if not engine_path:
download_result = cls.download_engine(renderer, engine_version)
download_result = cls.download_engine(engine_name, engine_version)
if not download_result:
raise FileNotFoundError(f"Cannot download requested version: {renderer} {engine_version}")
raise FileNotFoundError(f"Cannot download requested version: {engine_name} {engine_version}")
engine_path = download_result['path']
logger.info("Engine downloaded. Creating worker.")
else:

View File

@@ -24,7 +24,7 @@ class FFMPEG(BaseRenderEngine):
return FFMPEGUI.get_options(self)
def supported_extensions(self):
help_text = (subprocess.check_output([self.renderer_path(), '-h', 'full'], stderr=subprocess.STDOUT,
help_text = (subprocess.check_output([self.engine_path(), '-h', 'full'], stderr=subprocess.STDOUT,
creationflags=_creationflags).decode('utf-8'))
found = re.findall(r'extensions that .* is allowed to access \(default "(.*)"', help_text)
found_extensions = set()
@@ -35,7 +35,7 @@ class FFMPEG(BaseRenderEngine):
def version(self):
version = None
try:
ver_out = subprocess.check_output([self.renderer_path(), '-version'], timeout=SUBPROCESS_TIMEOUT,
ver_out = subprocess.check_output([self.engine_path(), '-version'], timeout=SUBPROCESS_TIMEOUT,
creationflags=_creationflags).decode('utf-8')
match = re.match(r".*version\s*([\w.*]+)\W*", ver_out)
if match:
@@ -82,7 +82,7 @@ class FFMPEG(BaseRenderEngine):
return None
def get_encoders(self):
raw_stdout = subprocess.check_output([self.renderer_path(), '-encoders'], stderr=subprocess.DEVNULL,
raw_stdout = subprocess.check_output([self.engine_path(), '-encoders'], stderr=subprocess.DEVNULL,
timeout=SUBPROCESS_TIMEOUT, creationflags=_creationflags).decode('utf-8')
pattern = r'(?P<type>[VASFXBD.]{6})\s+(?P<name>\S{2,})\s+(?P<description>.*)'
encoders = [m.groupdict() for m in re.finditer(pattern, raw_stdout)]
@@ -94,7 +94,7 @@ class FFMPEG(BaseRenderEngine):
def get_all_formats(self):
try:
formats_raw = subprocess.check_output([self.renderer_path(), '-formats'], stderr=subprocess.DEVNULL,
formats_raw = subprocess.check_output([self.engine_path(), '-formats'], stderr=subprocess.DEVNULL,
timeout=SUBPROCESS_TIMEOUT,
creationflags=_creationflags).decode('utf-8')
pattern = r'(?P<type>[DE]{1,2})\s+(?P<id>\S{2,})\s+(?P<name>.*)'
@@ -108,7 +108,7 @@ class FFMPEG(BaseRenderEngine):
# Extract the common extension using regex
muxer_flag = 'muxer' if 'E' in ffmpeg_format['type'] else 'demuxer'
format_detail_raw = subprocess.check_output(
[self.renderer_path(), '-hide_banner', '-h', f"{muxer_flag}={ffmpeg_format['id']}"],
[self.engine_path(), '-hide_banner', '-h', f"{muxer_flag}={ffmpeg_format['id']}"],
creationflags=_creationflags).decode('utf-8')
pattern = r"Common extensions: (\w+)"
common_extensions = re.findall(pattern, format_detail_raw)
@@ -121,7 +121,7 @@ class FFMPEG(BaseRenderEngine):
return [x['id'] for x in self.get_all_formats() if 'E' in x['type'].upper()]
def get_frame_count(self, path_to_file):
raw_stdout = subprocess.check_output([self.renderer_path(), '-i', path_to_file, '-map', '0:v:0', '-c', 'copy',
raw_stdout = subprocess.check_output([self.engine_path(), '-i', path_to_file, '-map', '0:v:0', '-c', 'copy',
'-f', 'null', '-'], stderr=subprocess.STDOUT,
timeout=SUBPROCESS_TIMEOUT, creationflags=_creationflags).decode('utf-8')
match = re.findall(r'frame=\s*(\d+)', raw_stdout)
@@ -131,7 +131,7 @@ class FFMPEG(BaseRenderEngine):
return -1
def get_arguments(self):
help_text = (subprocess.check_output([self.renderer_path(), '-h', 'long'], stderr=subprocess.STDOUT,
help_text = (subprocess.check_output([self.engine_path(), '-h', 'long'], stderr=subprocess.STDOUT,
creationflags=_creationflags).decode('utf-8'))
lines = help_text.splitlines()

View File

@@ -16,7 +16,7 @@ class FFMPEGRenderWorker(BaseRenderWorker):
def generate_worker_subprocess(self):
cmd = [self.renderer_path, '-y', '-stats', '-i', self.input_path]
cmd = [self.engine_path, '-y', '-stats', '-i', self.input_path]
# Resize frame
if self.args.get('x_resolution', None) and self.args.get('y_resolution', None):