mirror of
https://github.com/blw1138/Zordon.git
synced 2025-12-17 16:58:12 +00:00
Add ability to get file list and download files from project output
This commit is contained in:
@@ -21,7 +21,7 @@ class RenderJob:
|
|||||||
self.date_created = datetime.now()
|
self.date_created = datetime.now()
|
||||||
self.scheduled_start = None
|
self.scheduled_start = None
|
||||||
self.renderer = render.renderer
|
self.renderer = render.renderer
|
||||||
self.name = os.path.basename(render.input) + '_' + self.date_created.isoformat()
|
self.name = os.path.basename(render.input_path) + '_' + self.date_created.isoformat()
|
||||||
self.archived = False
|
self.archived = False
|
||||||
|
|
||||||
def render_status(self):
|
def render_status(self):
|
||||||
|
|||||||
@@ -92,7 +92,9 @@ class RenderQueue:
|
|||||||
for job in saved_state.get('jobs', []):
|
for job in saved_state.get('jobs', []):
|
||||||
|
|
||||||
# Identify renderer type and recreate Renderer object
|
# Identify renderer type and recreate Renderer object
|
||||||
job_render_object = RenderWorkerFactory.create_worker(job['renderer'], input_path=job['render']['input'], output_path=job['render']['output'])
|
job_render_object = RenderWorkerFactory.create_worker(job['renderer'],
|
||||||
|
input_path=job['render']['input_path'],
|
||||||
|
output_path=job['render']['output_path'])
|
||||||
|
|
||||||
# Load Renderer values
|
# Load Renderer values
|
||||||
for key, val in job['render'].items():
|
for key, val in job['render'].items():
|
||||||
|
|||||||
@@ -6,3 +6,5 @@ Flask==2.2.2
|
|||||||
rich==12.6.0
|
rich==12.6.0
|
||||||
ffmpeg-python
|
ffmpeg-python
|
||||||
Werkzeug~=2.2.2
|
Werkzeug~=2.2.2
|
||||||
|
tkinterdnd2~=0.3.0
|
||||||
|
future~=0.18.2
|
||||||
55
server.py
55
server.py
@@ -3,15 +3,17 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import pathlib
|
||||||
import shutil
|
import shutil
|
||||||
import socket
|
import socket
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from zipfile import ZipFile
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import yaml
|
import yaml
|
||||||
from flask import Flask, request, render_template
|
from flask import Flask, request, render_template, send_file, after_this_request
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
from lib.render_job import RenderJob
|
from lib.render_job import RenderJob
|
||||||
@@ -34,17 +36,53 @@ def filtered_jobs_json(status_val):
|
|||||||
if jobs:
|
if jobs:
|
||||||
return jobs
|
return jobs
|
||||||
else:
|
else:
|
||||||
return {'error', f'Cannot find jobs with status {status_val}'}, 400
|
return f'Cannot find jobs with status {status_val}', 400
|
||||||
|
|
||||||
|
|
||||||
@app.get('/job_status/<job_id>')
|
@app.get('/job_status/<job_id>')
|
||||||
def get_job_status(job_id):
|
def get_job_status(job_id):
|
||||||
found_job = RenderQueue.job_with_id(job_id)
|
found_job = RenderQueue.job_with_id(job_id)
|
||||||
if found_job:
|
if found_job:
|
||||||
logger.info("Founbd jobs")
|
|
||||||
return found_job.json()
|
return found_job.json()
|
||||||
else:
|
else:
|
||||||
return {'error': f'Cannot find job with ID {job_id}'}, 400
|
return f'Cannot find job with ID {job_id}', 400
|
||||||
|
|
||||||
|
|
||||||
|
@app.get('/file_list/<job_id>')
|
||||||
|
def get_file_list(job_id):
|
||||||
|
found_job = RenderQueue.job_with_id(job_id)
|
||||||
|
if found_job:
|
||||||
|
job_dir = os.path.dirname(found_job.render.output_path)
|
||||||
|
return os.listdir(job_dir)
|
||||||
|
else:
|
||||||
|
return f'Cannot find job with ID {job_id}', 400
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/download_all/<job_id>')
|
||||||
|
def download_all(job_id):
|
||||||
|
|
||||||
|
zip_filename = None
|
||||||
|
|
||||||
|
@after_this_request
|
||||||
|
def clear_zip(response):
|
||||||
|
if zip_filename and os.path.exists(zip_filename):
|
||||||
|
os.remove(zip_filename)
|
||||||
|
return response
|
||||||
|
|
||||||
|
found_job = RenderQueue.job_with_id(job_id)
|
||||||
|
if found_job:
|
||||||
|
output_dir = os.path.dirname(found_job.render.output_path)
|
||||||
|
if os.path.exists(output_dir):
|
||||||
|
zip_filename = pathlib.Path(found_job.render.input_path).stem + '.zip'
|
||||||
|
with ZipFile(zip_filename, 'w') as zipObj:
|
||||||
|
for f in os.listdir(output_dir):
|
||||||
|
zipObj.write(filename=os.path.join(output_dir, f),
|
||||||
|
arcname=os.path.basename(f))
|
||||||
|
return send_file(zip_filename, mimetype="zip", as_attachment=True, )
|
||||||
|
else:
|
||||||
|
return f'Cannot find project files for job {job_id}', 500
|
||||||
|
else:
|
||||||
|
return f'Cannot find job with ID {job_id}', 400
|
||||||
|
|
||||||
|
|
||||||
@app.post('/register_client')
|
@app.post('/register_client')
|
||||||
@@ -103,7 +141,6 @@ def snapshot():
|
|||||||
|
|
||||||
@app.post('/add_job')
|
@app.post('/add_job')
|
||||||
def add_job():
|
def add_job():
|
||||||
|
|
||||||
def remove_job_dir():
|
def remove_job_dir():
|
||||||
if job_dir and os.path.exists(job_dir):
|
if job_dir and os.path.exists(job_dir):
|
||||||
logger.debug(f"Removing job dir: {job_dir}")
|
logger.debug(f"Removing job dir: {job_dir}")
|
||||||
@@ -146,14 +183,17 @@ def add_job():
|
|||||||
local_path = os.path.join(job_dir, secure_filename(uploaded_file.filename))
|
local_path = os.path.join(job_dir, secure_filename(uploaded_file.filename))
|
||||||
uploaded_file.save(local_path)
|
uploaded_file.save(local_path)
|
||||||
input_path = local_path
|
input_path = local_path
|
||||||
output_path = os.path.join(job_dir, os.path.basename(output_path))
|
output_dir = os.path.join(job_dir, 'output')
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
output_path = os.path.join(output_dir, os.path.basename(output_path))
|
||||||
|
|
||||||
# local renders
|
# local renders
|
||||||
if client == RenderQueue.host_name:
|
if client == RenderQueue.host_name:
|
||||||
logger.info(f"Creating job locally - {input_path}")
|
logger.info(f"Creating job locally - {input_path}")
|
||||||
try:
|
try:
|
||||||
render_job = RenderWorkerFactory.create_worker(renderer, input_path, output_path, args)
|
render_job = RenderWorkerFactory.create_worker(renderer, input_path, output_path, args)
|
||||||
except ValueError as e:
|
render_job.log_path = os.path.join(os.path.dirname(input_path), os.path.basename(input_path) + '.log')
|
||||||
|
except Exception as e:
|
||||||
err_msg = f"Error creating job: {str(e)}"
|
err_msg = f"Error creating job: {str(e)}"
|
||||||
logger.exception(err_msg)
|
logger.exception(err_msg)
|
||||||
remove_job_dir()
|
remove_job_dir()
|
||||||
@@ -268,7 +308,6 @@ def post_job_to_server(input_path, job_json, client, server_port=8080):
|
|||||||
|
|
||||||
|
|
||||||
def start_server(background_thread=False):
|
def start_server(background_thread=False):
|
||||||
|
|
||||||
def eval_loop(delay_sec=1):
|
def eval_loop(delay_sec=1):
|
||||||
while True:
|
while True:
|
||||||
RenderQueue.evaluate_queue()
|
RenderQueue.evaluate_queue()
|
||||||
|
|||||||
@@ -75,15 +75,15 @@ class AERenderWorker(BaseRenderWorker):
|
|||||||
# }
|
# }
|
||||||
job = {'template':
|
job = {'template':
|
||||||
{
|
{
|
||||||
'src': 'file://' + self.input, 'composition': self.comp.replace('"', ''),
|
'src': 'file://' + self.input_path, 'composition': self.comp.replace('"', ''),
|
||||||
'settingsTemplate': self.render_settings.replace('"', ''),
|
'settingsTemplate': self.render_settings.replace('"', ''),
|
||||||
'outputModule': self.omsettings.replace('"', ''), 'outputExt': 'mov'}
|
'outputModule': self.omsettings.replace('"', ''), 'outputExt': 'mov'}
|
||||||
}
|
}
|
||||||
x = ['./nexrender-cli-macos', "'{}'".format(json.dumps(job))]
|
x = ['./nexrender-cli-macos', "'{}'".format(json.dumps(job))]
|
||||||
else:
|
else:
|
||||||
logging.info('nexrender not found')
|
logging.info('nexrender not found')
|
||||||
x = [aerender_path(), '-project', self.input, '-comp', self.comp, '-RStemplate', self.render_settings,
|
x = [aerender_path(), '-project', self.input_path, '-comp', self.comp, '-RStemplate', self.render_settings,
|
||||||
'-OMtemplate', self.omsettings, '-output', self.output]
|
'-OMtemplate', self.omsettings, '-output', self.output_path]
|
||||||
return x
|
return x
|
||||||
|
|
||||||
def _parse_stdout(self, line):
|
def _parse_stdout(self, line):
|
||||||
|
|||||||
@@ -44,12 +44,12 @@ class BlenderRenderWorker(BaseRenderWorker):
|
|||||||
|
|
||||||
def _generate_subprocess(self):
|
def _generate_subprocess(self):
|
||||||
|
|
||||||
cmd = [self.renderer_path(), '-b', self.input]
|
cmd = [self.renderer_path(), '-b', self.input_path]
|
||||||
|
|
||||||
if self.camera:
|
if self.camera:
|
||||||
cmd.extend(['--python-expr', f"import bpy;bpy.context.scene.camera = bpy.data.objects['{self.camera}'];"])
|
cmd.extend(['--python-expr', f"import bpy;bpy.context.scene.camera = bpy.data.objects['{self.camera}'];"])
|
||||||
|
|
||||||
cmd.extend(['-E', self.engine, '-o', self.output, '-F', self.export_format])
|
cmd.extend(['-E', self.engine, '-o', self.output_path, '-F', self.export_format])
|
||||||
|
|
||||||
# all frames or single
|
# all frames or single
|
||||||
cmd.extend(['-a'] if self.render_all_frames else ['-f', str(self.frame)])
|
cmd.extend(['-a'] if self.render_all_frames else ['-f', str(self.frame)])
|
||||||
|
|||||||
@@ -41,10 +41,10 @@ class FFMPEGRenderWorker(BaseRenderWorker):
|
|||||||
|
|
||||||
def _generate_subprocess(self):
|
def _generate_subprocess(self):
|
||||||
|
|
||||||
cmd = [self.renderer_path(), '-y', '-stats', '-i', self.input]
|
cmd = [self.renderer_path(), '-y', '-stats', '-i', self.input_path]
|
||||||
if self.args:
|
if self.args:
|
||||||
cmd.extend(self.args)
|
cmd.extend(self.args)
|
||||||
cmd.append(self.output)
|
cmd.append(self.output_path)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ class BaseRenderWorker(object):
|
|||||||
raise ValueError(err_meg)
|
raise ValueError(err_meg)
|
||||||
|
|
||||||
# Essential Info
|
# Essential Info
|
||||||
self.input = input_path
|
self.input_path = input_path
|
||||||
self.output = output_path
|
self.output_path = output_path
|
||||||
self.args = args or {}
|
self.args = args or {}
|
||||||
self.date_created = datetime.now()
|
self.date_created = datetime.now()
|
||||||
self.renderer_version = self.version()
|
self.renderer_version = self.version()
|
||||||
@@ -96,9 +96,9 @@ class BaseRenderWorker(object):
|
|||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
|
|
||||||
if not os.path.exists(self.input):
|
if not os.path.exists(self.input_path):
|
||||||
self.status = RenderStatus.ERROR
|
self.status = RenderStatus.ERROR
|
||||||
msg = 'Cannot find input path: {}'.format(self.input)
|
msg = 'Cannot find input path: {}'.format(self.input_path)
|
||||||
logger.error(msg)
|
logger.error(msg)
|
||||||
self.errors.append(msg)
|
self.errors.append(msg)
|
||||||
return
|
return
|
||||||
@@ -111,7 +111,7 @@ class BaseRenderWorker(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
self.status = RenderStatus.RUNNING
|
self.status = RenderStatus.RUNNING
|
||||||
logger.info('Starting {0} {1} Render for {2}'.format(self.renderer, self.version(), self.input))
|
logger.info('Starting {0} {1} Render for {2}'.format(self.renderer, self.version(), self.input_path))
|
||||||
self.thread.start()
|
self.thread.start()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
@@ -119,10 +119,10 @@ class BaseRenderWorker(object):
|
|||||||
# Setup logging
|
# Setup logging
|
||||||
try:
|
try:
|
||||||
if not self.log_path:
|
if not self.log_path:
|
||||||
log_dir = os.path.join(os.path.dirname(self.input), 'logs')
|
log_dir = os.path.join(os.path.dirname(self.input_path), 'logs')
|
||||||
if not os.path.exists(log_dir):
|
if not os.path.exists(log_dir):
|
||||||
os.makedirs(log_dir)
|
os.makedirs(log_dir)
|
||||||
self.log_path = os.path.join(log_dir, os.path.basename(self.input)) + '.log'
|
self.log_path = os.path.join(log_dir, os.path.basename(self.input_path)) + '.log'
|
||||||
logger.info('Logs saved in {}'.format(self.log_path))
|
logger.info('Logs saved in {}'.format(self.log_path))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Error setting up logging: {}".format(e))
|
logger.error("Error setting up logging: {}".format(e))
|
||||||
@@ -141,7 +141,7 @@ class BaseRenderWorker(object):
|
|||||||
|
|
||||||
with open(self.log_path, "a") as f:
|
with open(self.log_path, "a") as f:
|
||||||
|
|
||||||
f.write("{3} - Starting {0} {1} Render for {2}\n".format(self.renderer, self.version(), self.input,
|
f.write("{3} - Starting {0} {1} Render for {2}\n".format(self.renderer, self.version(), self.input_path,
|
||||||
self.start_time.isoformat()))
|
self.start_time.isoformat()))
|
||||||
f.write(f"Running command: {' '.join(subprocess_cmds)}\n")
|
f.write(f"Running command: {' '.join(subprocess_cmds)}\n")
|
||||||
for c in io.TextIOWrapper(self.process.stdout, encoding="utf-8"): # or another encoding
|
for c in io.TextIOWrapper(self.process.stdout, encoding="utf-8"): # or another encoding
|
||||||
@@ -173,7 +173,7 @@ class BaseRenderWorker(object):
|
|||||||
f.write(message)
|
f.write(message)
|
||||||
|
|
||||||
if self.failed_attempts >= self.maximum_attempts and self.status is not RenderStatus.CANCELLED:
|
if self.failed_attempts >= self.maximum_attempts and self.status is not RenderStatus.CANCELLED:
|
||||||
logger.error('{} Render of {} failed after {} attempts'.format(self.renderer, self.input, self.failed_attempts))
|
logger.error('{} Render of {} failed after {} attempts'.format(self.renderer, self.input_path, self.failed_attempts))
|
||||||
self.status = RenderStatus.ERROR
|
self.status = RenderStatus.ERROR
|
||||||
if not self.errors:
|
if not self.errors:
|
||||||
self.errors = [self.last_output]
|
self.errors = [self.last_output]
|
||||||
|
|||||||
Reference in New Issue
Block a user