Add ability to get file list and download files from project output

This commit is contained in:
Brett Williams
2022-10-26 18:27:13 -07:00
parent 2da06ab166
commit eb9e719f47
8 changed files with 71 additions and 28 deletions

View File

@@ -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):

View File

@@ -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():

View File

@@ -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

View File

@@ -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()

View File

@@ -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):

View File

@@ -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)])

View File

@@ -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

View File

@@ -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]