Windows File Path Fixes (#39)

* Added system_safe_path to convert paths to Windows

* Fix issue where engines would not be reported unless a system engine was installed

* Platform independent searching for binaries in engine directory

* Add missing package to requirements.txt

* Better error handling for ffmpeg.get_all_formats()

* Add system_safe_path to more locations in api_server.py

* Fix naming issue with Blender on macos

* Fix path lookups and add engine_path to workers

* Report installed renderers in status

* Remove files included by accident
This commit is contained in:
2023-10-21 20:12:09 -07:00
committed by GitHub
parent 0b6b971fbc
commit 7a52cce40a
16 changed files with 129 additions and 61 deletions

View File

@@ -13,6 +13,8 @@ class BaseRenderEngine(object):
def __init__(self, custom_path=None):
self.custom_renderer_path = custom_path
if not self.renderer_path():
raise FileNotFoundError(f"Cannot find path to renderer for {self.name()} instance")
def renderer_path(self):
return self.custom_renderer_path or self.default_renderer_path()
@@ -24,9 +26,9 @@ class BaseRenderEngine(object):
@classmethod
def default_renderer_path(cls):
path = None
try:
try: # Linux and macOS
path = subprocess.check_output(['which', cls.name()], timeout=SUBPROCESS_TIMEOUT).decode('utf-8').strip()
except subprocess.CalledProcessError:
except (subprocess.CalledProcessError, FileNotFoundError):
for p in cls.install_paths:
if os.path.exists(p):
path = p

View File

@@ -13,6 +13,7 @@ class Blender(BaseRenderEngine):
install_paths = ['/Applications/Blender.app/Contents/MacOS/Blender']
supported_extensions = ['.blend']
binary_names = {'linux': 'blender', 'windows': 'blender.exe', 'macos': 'Blender'}
def version(self):
version = None
@@ -36,18 +37,17 @@ class Blender(BaseRenderEngine):
return subprocess.run([self.renderer_path(), '-b', project_path, '--python-expr', python_expression],
capture_output=True, timeout=timeout)
except Exception as e:
logger.warning(f"Error running python expression in blender: {e}")
pass
logger.error(f"Error running python expression in blender: {e}")
else:
raise FileNotFoundError(f'Project file not found: {project_path}')
def run_python_script(self, project_path, script_path, timeout=None):
if os.path.exists(project_path) and os.path.exists(script_path):
try:
return subprocess.run([self.default_renderer_path(), '-b', project_path, '--python', script_path],
return subprocess.run([self.renderer_path(), '-b', project_path, '--python', script_path],
capture_output=True, timeout=timeout)
except Exception as e:
logger.warning(f"Error running python expression in blender: {e}")
logger.warning(f"Error running python script in blender: {e}")
pass
elif not os.path.exists(project_path):
raise FileNotFoundError(f'Project file not found: {project_path}')
@@ -58,8 +58,9 @@ class Blender(BaseRenderEngine):
def get_scene_info(self, project_path, timeout=10):
scene_info = {}
try:
results = self.run_python_script(project_path, os.path.join(os.path.dirname(os.path.realpath(__file__)),
'scripts', 'blender', 'get_file_info.py'), timeout=timeout)
script_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
'scripts', 'blender', 'get_file_info.py')
results = self.run_python_script(project_path, system_safe_path(script_path), timeout=timeout)
result_text = results.stdout.decode()
for line in result_text.splitlines():
if line.startswith('SCENE_DATA:'):
@@ -76,8 +77,9 @@ class Blender(BaseRenderEngine):
# Credit to L0Lock for pack script - https://blender.stackexchange.com/a/243935
try:
logger.info(f"Starting to pack Blender file: {project_path}")
results = self.run_python_script(project_path, os.path.join(os.path.dirname(os.path.realpath(__file__)),
'scripts', 'blender', 'pack_project.py'), timeout=timeout)
script_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
'scripts', 'blender', 'pack_project.py')
results = self.run_python_script(project_path, system_safe_path(script_path), timeout=timeout)
result_text = results.stdout.decode()
dir_name = os.path.dirname(project_path)

View File

@@ -53,7 +53,7 @@ class BlenderDownloader:
versions_data = [x for x in versions_data if x['cpu'] == cpu]
for v in versions_data:
v['url'] = os.path.join(base_url, v['file'])
v['url'] = base_url + '/' + v['file']
versions_data = sorted(versions_data, key=lambda x: x['version'], reverse=True)
return versions_data

View File

@@ -87,7 +87,7 @@ class FFMPEGDownloader:
release_dir = 'releases' if version == cls.get_linux_versions()[0] else 'old-releases'
remote_url = os.path.join(cls.linux_url, release_dir, f'ffmpeg-{version}-{cpu}-static.tar.xz')
elif system_os == 'windows':
remote_url = os.path.join(cls.windows_download_url, version, f'ffmpeg-{version}-full_build.zip')
remote_url = f"{cls.windows_download_url.strip('/')}/{version}/ffmpeg-{version}-full_build.zip"
# Download and extract
try:

View File

@@ -4,6 +4,7 @@ import platform
import shutil
from .downloaders.blender_downloader import BlenderDownloader
from .downloaders.ffmpeg_downloader import FFMPEGDownloader
from ..utilities.misc_helper import system_safe_path
try:
from .blender_engine import Blender
@@ -37,14 +38,25 @@ class EngineManager:
# Split the input string by dashes to get segments
segments = directory.split('-')
# Define the keys for each word
keys = ["engine", "version", "system_os", "cpu"]
# Create a dictionary with named keys
executable_names = {'linux': 'blender', 'windows': 'blender.exe', 'macos': 'Blender.app'}
keys = ["engine", "version", "system_os", "cpu"]
result_dict = {keys[i]: segments[i] for i in range(min(len(keys), len(segments)))}
result_dict['path'] = os.path.join(cls.engines_path, directory, executable_names.get(result_dict['system_os'], 'unknown'))
result_dict['type'] = 'managed'
# Figure out the binary name for the path
binary_name = result_dict['engine'].lower()
for eng in cls.supported_engines():
if eng.name().lower() == result_dict['engine']:
binary_name = eng.binary_names.get(result_dict['system_os'], binary_name)
# Find path to binary
path = None
for root, _, files in os.walk(system_safe_path(os.path.join(cls.engines_path, directory))):
if binary_name in files:
path = os.path.join(root, binary_name)
break
result_dict['path'] = path
results.append(result_dict)
except FileNotFoundError:
logger.warning("Cannot find local engines download directory")
@@ -113,11 +125,15 @@ class EngineManager:
return
# Get the appropriate downloader class based on the engine type
downloader = downloader_classes[engine]
if downloader.download_engine(version, download_location=cls.engines_path, system_os=system_os, cpu=cpu):
return cls.has_engine_version(engine, version, system_os, cpu)
else:
downloader_classes[engine].download_engine(version, download_location=cls.engines_path,
system_os=system_os, cpu=cpu)
# Check that engine was properly downloaded
found_engine = cls.has_engine_version(engine, version, system_os, cpu)
if not found_engine:
logger.error(f"Error downloading {engine}")
return found_engine
@classmethod
def delete_engine_download(cls, engine, version, system_os=None, cpu=None):

View File

@@ -7,6 +7,8 @@ import re
class FFMPEG(BaseRenderEngine):
binary_names = {'linux': 'ffmpeg', 'windows': 'ffmpeg.exe', 'macos': 'ffmpeg'}
def version(self):
version = None
try:
@@ -31,11 +33,15 @@ class FFMPEG(BaseRenderEngine):
return encoders
def get_all_formats(self):
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>.*)'
all_formats = [m.groupdict() for m in re.finditer(pattern, formats_raw)]
return all_formats
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'
all_formats = [m.groupdict() for m in re.finditer(pattern, formats_raw)]
return all_formats
except Exception as e:
logger.error(f"Error getting all formats: {e}")
return []
def extension_for_format(self, ffmpeg_format):
# Extract the common extension using regex
@@ -53,7 +59,7 @@ class FFMPEG(BaseRenderEngine):
return [x 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.default_renderer_path(), '-i', path_to_file, '-map', '0:v:0', '-c', 'copy',
raw_stdout = subprocess.check_output([self.renderer_path(), '-i', path_to_file, '-map', '0:v:0', '-c', 'copy',
'-f', 'null', '-'], stderr=subprocess.STDOUT,
timeout=SUBPROCESS_TIMEOUT).decode('utf-8')
match = re.findall(r'frame=\s*(\d+)', raw_stdout)