mirror of
https://github.com/blw1138/Zordon.git
synced 2026-02-05 21:56:10 +00:00
Dynamic engine options in UI for blender / ffmpeg (#66)
* Make sure progress UI updates occur on main thread * Cleanup unnecessary code in FFMPEG * Cleanup extension matching * Make sure supported_extensions is now called as a method everywhere * Fix add_job crashing * Update the renderer to reflect the current file type * Sort engine versions from newest to oldest * Consolidate Project Group and Server Group * Split UI options into its own file for easier updating * Add ffmpeg ui stem
This commit is contained in:
@@ -10,7 +10,6 @@ logger = logging.getLogger()
|
||||
class Blender(BaseRenderEngine):
|
||||
|
||||
install_paths = ['/Applications/Blender.app/Contents/MacOS/Blender']
|
||||
supported_extensions = ['.blend']
|
||||
binary_names = {'linux': 'blender', 'windows': 'blender.exe', 'macos': 'Blender'}
|
||||
|
||||
@staticmethod
|
||||
@@ -23,6 +22,14 @@ class Blender(BaseRenderEngine):
|
||||
from src.engines.blender.blender_worker import BlenderRenderWorker
|
||||
return BlenderRenderWorker
|
||||
|
||||
def ui_options(self):
|
||||
from src.engines.blender.blender_ui import BlenderUI
|
||||
return BlenderUI.get_options(self)
|
||||
|
||||
@staticmethod
|
||||
def supported_extensions():
|
||||
return ['blend']
|
||||
|
||||
def version(self):
|
||||
version = None
|
||||
try:
|
||||
@@ -150,13 +157,6 @@ class Blender(BaseRenderEngine):
|
||||
render_engines = [x.strip() for x in engine_output.split('Blender Engine Listing:')[-1].strip().splitlines()]
|
||||
return render_engines
|
||||
|
||||
# UI and setup
|
||||
def get_options(self):
|
||||
options = [
|
||||
{'name': 'engine', 'options': self.supported_render_engines()},
|
||||
]
|
||||
return options
|
||||
|
||||
def perform_presubmission_tasks(self, project_path):
|
||||
packed_path = self.pack_project_file(project_path, timeout=30)
|
||||
return packed_path
|
||||
|
||||
8
src/engines/blender/blender_ui.py
Normal file
8
src/engines/blender/blender_ui.py
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
class BlenderUI:
|
||||
@staticmethod
|
||||
def get_options(instance):
|
||||
options = [
|
||||
{'name': 'engine', 'options': instance.supported_render_engines()},
|
||||
]
|
||||
return options
|
||||
@@ -47,7 +47,10 @@ class BaseRenderEngine(object):
|
||||
def worker_class(): # override when subclassing to link worker class
|
||||
raise NotImplementedError("Worker class not implemented")
|
||||
|
||||
def get_help(self):
|
||||
def ui_options(self): # override to return options for ui
|
||||
return {}
|
||||
|
||||
def get_help(self): # override if renderer uses different help flag
|
||||
path = self.renderer_path()
|
||||
if not path:
|
||||
raise FileNotFoundError("renderer path not found")
|
||||
@@ -56,7 +59,7 @@ class BaseRenderEngine(object):
|
||||
return help_doc
|
||||
|
||||
def get_project_info(self, project_path, timeout=10):
|
||||
raise NotImplementedError(f"get_project_info not implemented for {cls.__name__}")
|
||||
raise NotImplementedError(f"get_project_info not implemented for {self.__name__}")
|
||||
|
||||
@classmethod
|
||||
def get_output_formats(cls):
|
||||
|
||||
@@ -47,7 +47,7 @@ class BaseRenderWorker(Base):
|
||||
name=None):
|
||||
|
||||
if not ignore_extensions:
|
||||
if not any(ext in input_path for ext in self.engine.supported_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'
|
||||
logger.error(err_meg)
|
||||
raise ValueError(err_meg)
|
||||
|
||||
@@ -76,7 +76,9 @@ class EngineManager:
|
||||
|
||||
@classmethod
|
||||
def all_versions_for_engine(cls, engine):
|
||||
return [x for x in cls.all_engines() if x['engine'] == engine]
|
||||
versions = [x for x in cls.all_engines() if x['engine'] == engine]
|
||||
sorted_versions = sorted(versions, key=lambda x: x['version'], reverse=True)
|
||||
return sorted_versions
|
||||
|
||||
@classmethod
|
||||
def newest_engine_version(cls, engine, system_os=None, cpu=None):
|
||||
@@ -84,9 +86,8 @@ class EngineManager:
|
||||
cpu = cpu or current_system_cpu()
|
||||
|
||||
try:
|
||||
filtered = [x for x in cls.all_engines() if x['engine'] == engine and x['system_os'] == system_os and x['cpu'] == cpu]
|
||||
versions = sorted(filtered, key=lambda x: x['version'], reverse=True)
|
||||
return versions[0]
|
||||
filtered = [x for x in cls.all_versions_for_engine(engine) if x['system_os'] == system_os and x['cpu'] == cpu]
|
||||
return filtered[0]
|
||||
except IndexError:
|
||||
logger.error(f"Cannot find newest engine version for {engine}-{system_os}-{cpu}")
|
||||
return None
|
||||
@@ -234,10 +235,11 @@ class EngineManager:
|
||||
@classmethod
|
||||
def engine_for_project_path(cls, path):
|
||||
name, extension = os.path.splitext(path)
|
||||
extension = extension.lower().strip('.')
|
||||
for engine in cls.supported_engines():
|
||||
if extension in engine.supported_extensions:
|
||||
if extension in engine.supported_extensions():
|
||||
return engine
|
||||
undefined_renderer_support = [x for x in cls.supported_engines() if not x.supported_extensions]
|
||||
undefined_renderer_support = [x for x in cls.supported_engines() if not x.supported_extensions()]
|
||||
return undefined_renderer_support[0]
|
||||
|
||||
|
||||
|
||||
@@ -18,6 +18,20 @@ class FFMPEG(BaseRenderEngine):
|
||||
from src.engines.ffmpeg.ffmpeg_worker import FFMPEGRenderWorker
|
||||
return FFMPEGRenderWorker
|
||||
|
||||
def ui_options(self):
|
||||
from src.engines.ffmpeg.ffmpeg_ui import FFMPEGUI
|
||||
return FFMPEGUI.get_options(self)
|
||||
|
||||
@classmethod
|
||||
def supported_extensions(cls):
|
||||
help_text = (subprocess.check_output([cls().renderer_path(), '-h', 'full'], stderr=subprocess.STDOUT)
|
||||
.decode('utf-8'))
|
||||
found = re.findall('extensions that .* is allowed to access \(default "(.*)"', help_text)
|
||||
found_extensions = set()
|
||||
for match in found:
|
||||
found_extensions.update(match.split(','))
|
||||
return list(found_extensions)
|
||||
|
||||
def version(self):
|
||||
version = None
|
||||
try:
|
||||
@@ -31,15 +45,11 @@ class FFMPEG(BaseRenderEngine):
|
||||
return version
|
||||
|
||||
def get_project_info(self, project_path, timeout=10):
|
||||
return self.get_video_info_ffprobe(project_path)
|
||||
|
||||
@staticmethod
|
||||
def get_video_info_ffprobe(video_path):
|
||||
try:
|
||||
# Run ffprobe and parse the output as JSON
|
||||
cmd = [
|
||||
'ffprobe', '-v', 'quiet', '-print_format', 'json',
|
||||
'-show_streams', '-select_streams', 'v', video_path
|
||||
'-show_streams', '-select_streams', 'v', project_path
|
||||
]
|
||||
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
video_info = json.loads(result.stdout)
|
||||
@@ -85,7 +95,7 @@ class FFMPEG(BaseRenderEngine):
|
||||
try:
|
||||
formats_raw = subprocess.check_output([self.renderer_path(), '-formats'], stderr=subprocess.DEVNULL,
|
||||
timeout=SUBPROCESS_TIMEOUT).decode('utf-8')
|
||||
pattern = '(?P<type>[DE]{1,2})\s+(?P<id>\S{2,})\s+(?P<name>.*)\r'
|
||||
pattern = '(?P<type>[DE]{1,2})\s+(?P<id>\S{2,})\s+(?P<name>.*)'
|
||||
all_formats = [m.groupdict() for m in re.finditer(pattern, formats_raw)]
|
||||
return all_formats
|
||||
except Exception as e:
|
||||
@@ -105,7 +115,7 @@ class FFMPEG(BaseRenderEngine):
|
||||
return found_extensions
|
||||
|
||||
def get_output_formats(self):
|
||||
return [x for x in self.get_all_formats() if 'E' in x['type'].upper()]
|
||||
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',
|
||||
@@ -117,7 +127,8 @@ class FFMPEG(BaseRenderEngine):
|
||||
return frame_number
|
||||
|
||||
def get_arguments(self):
|
||||
help_text = subprocess.check_output([self.renderer_path(), '-h', 'long'], stderr=subprocess.STDOUT).decode('utf-8')
|
||||
help_text = (subprocess.check_output([self.renderer_path(), '-h', 'long'], stderr=subprocess.STDOUT)
|
||||
.decode('utf-8'))
|
||||
lines = help_text.splitlines()
|
||||
|
||||
options = {}
|
||||
|
||||
5
src/engines/ffmpeg/ffmpeg_ui.py
Normal file
5
src/engines/ffmpeg/ffmpeg_ui.py
Normal file
@@ -0,0 +1,5 @@
|
||||
class FFMPEGUI:
|
||||
@staticmethod
|
||||
def get_options(instance):
|
||||
options = []
|
||||
return options
|
||||
@@ -10,15 +10,9 @@ class FFMPEGRenderWorker(BaseRenderWorker):
|
||||
|
||||
engine = FFMPEG
|
||||
|
||||
def __init__(self, input_path, output_path, args=None, parent=None, name=None):
|
||||
super(FFMPEGRenderWorker, self).__init__(input_path=input_path, output_path=output_path, args=args,
|
||||
parent=parent, name=name)
|
||||
|
||||
stream_info = subprocess.check_output([self.renderer_path, "-i", # https://stackoverflow.com/a/61604105
|
||||
input_path, "-map", "0:v:0", "-c", "copy", "-f", "null", "-y",
|
||||
"/dev/null"], stderr=subprocess.STDOUT).decode('utf-8')
|
||||
found_frames = re.findall('frame=\s*(\d+)', stream_info)
|
||||
self.project_length = found_frames[-1] if found_frames else '-1'
|
||||
def __init__(self, input_path, output_path, engine_path, args=None, parent=None, name=None):
|
||||
super(FFMPEGRenderWorker, self).__init__(input_path=input_path, output_path=output_path,
|
||||
engine_path=engine_path, args=args, parent=parent, name=name)
|
||||
self.current_frame = -1
|
||||
|
||||
def generate_worker_subprocess(self):
|
||||
|
||||
Reference in New Issue
Block a user