temps restant + video + download link + left click + show next frame done

This commit is contained in:
sorlinv
2026-03-05 15:43:28 +01:00
parent 9ab59373df
commit b169e69b24
20 changed files with 2128 additions and 30 deletions

View File

@@ -1,4 +1,5 @@
const BlenderProcess = require("./BlenderProcess.js");
const BlenderDaemon = require("./BlenderDaemon.js");
const path = require("path");
const fs = require("fs");
const os = require("os");
@@ -23,12 +24,17 @@ class QueueManager {
this.nb_total_render_ms = 0;
this.nb_completed_renders = 0;
this.map_remote_avgs = {};
this.obj_email_notifier = null;
}
set_notification_config(obj_config) {
this.obj_notification_config = obj_config || { is_notify_each_image: false, is_notify_all_done: true };
}
set_email_notifier(obj_notifier) {
this.obj_email_notifier = obj_notifier;
}
start(obj_config) {
if (this.str_status === STR_STATUS_PAUSED) {
this.str_status = STR_STATUS_RUNNING;
@@ -49,9 +55,24 @@ class QueueManager {
this.list_collections = obj_config.list_collections || [];
this.obj_render_settings = obj_config.obj_render_settings || null;
if (this.str_overwrite_mode === "skip") {
this._prescan_existing();
}
this._send_log("File de rendu construite : " + this.list_queue.length + " elements.");
this._send_progress();
this._process_next();
this._send_log("Demarrage du daemon Blender...");
BlenderDaemon.start(obj_config.str_blend_file)
.then(() => {
this._send_log("Daemon Blender pret.");
this._process_next();
})
.catch((obj_err) => {
this._send_log("ERREUR demarrage daemon : " + (obj_err.message || String(obj_err)));
this.str_status = STR_STATUS_IDLE;
this._send_progress();
});
return Promise.resolve({ is_success: true, nb_total: this.list_queue.length });
}
@@ -66,17 +87,33 @@ class QueueManager {
}
stop() {
let str_file_to_delete = null;
if (this.nb_current_index < this.list_queue.length) {
let obj_item = this.list_queue[this.nb_current_index];
if (obj_item.str_status === "rendering") {
obj_item.str_status = "stopped";
str_file_to_delete = obj_item.str_expected_file;
}
}
this.str_status = STR_STATUS_IDLE;
BlenderDaemon.stop();
if (this.obj_current_process) {
this.obj_current_process.kill("SIGTERM");
this.obj_current_process = null;
}
if (this.nb_current_index < this.list_queue.length) {
let obj_item = this.list_queue[this.nb_current_index];
if (obj_item.str_status === "rendering") {
obj_item.str_status = "stopped";
if (str_file_to_delete) {
try {
if (fs.existsSync(str_file_to_delete)) {
fs.unlinkSync(str_file_to_delete);
this._send_log("Fichier partiel supprime : " + path.basename(str_file_to_delete));
}
} catch (obj_del_err) {
this._send_log("Erreur suppression fichier partiel : " + obj_del_err.message);
}
}
@@ -196,6 +233,42 @@ class QueueManager {
};
}
_prescan_existing() {
let nb_skipped = 0;
let nb_remote = 0;
for (let obj_item of this.list_queue) {
try {
if (!fs.existsSync(obj_item.str_expected_file)) {
continue;
}
let nb_size = fs.statSync(obj_item.str_expected_file).size;
if (nb_size === 0) {
continue;
}
if (nb_size > NB_PLACEHOLDER_MAX_SIZE) {
obj_item.str_status = "skipped";
try {
obj_item.str_done_date = fs.statSync(obj_item.str_expected_file).mtime.toISOString();
} catch (obj_date_err) {
// ignore
}
nb_skipped++;
} else {
obj_item.str_status = "rendering_remote";
this._read_placeholder(obj_item);
nb_remote++;
}
} catch (obj_err) {
// Fichier inaccessible, on ignore
}
}
if (nb_skipped > 0 || nb_remote > 0) {
this._send_log("Pre-scan : " + nb_skipped + " fichier(s) existant(s), " + nb_remote + " en cours sur d'autres machines.");
}
}
_process_next() {
if (this.str_status !== STR_STATUS_RUNNING) {
return;
@@ -203,10 +276,19 @@ class QueueManager {
this._check_remote_completions();
// Batch skip : boucle iterative pour eviter un stack overflow recursif
// Batch skip : sauter les items deja marques par le prescan ou nouvellement detectes
let nb_skip_count = 0;
while (this.nb_current_index < this.list_queue.length && this.str_overwrite_mode === "skip") {
let obj_check = this.list_queue[this.nb_current_index];
// Item deja marque par le prescan
if (obj_check.str_status === "skipped" || obj_check.str_status === "rendering_remote") {
this.nb_current_index++;
nb_skip_count++;
continue;
}
// Verification fichier pour les items non pre-scannes (apparus entre-temps)
let is_exists = false;
let nb_size = 0;
try {
@@ -247,6 +329,7 @@ class QueueManager {
if (this.nb_current_index >= this.list_queue.length) {
this.str_status = STR_STATUS_IDLE;
BlenderDaemon.stop();
this._send_log("Tous les rendus sont termines !");
this._send_event("render-complete", { is_all_done: true });
if (this.obj_notification_config.is_notify_all_done) {
@@ -308,22 +391,18 @@ class QueueManager {
let nb_start = Date.now();
BlenderProcess.render_frame({
str_blend_path: obj_item.str_blend_path,
BlenderDaemon.render_frame({
str_camera_name: obj_item.str_camera_name,
nb_frame: obj_item.nb_frame,
nb_resolution_x: obj_item.nb_resolution_x,
nb_resolution_y: obj_item.nb_resolution_y,
str_format: obj_item.str_format,
str_output_path: obj_item.str_output_path,
str_output_path: obj_item.str_expected_file,
list_collections: this.list_collections,
obj_render_settings: this.obj_render_settings,
fn_on_stdout: (str_data) => {
this._send_log(str_data.trim());
},
fn_on_process: (obj_process) => {
this.obj_current_process = obj_process;
},
})
.then((obj_result) => {
this.obj_current_process = null;
@@ -383,7 +462,33 @@ class QueueManager {
this.nb_current_index++;
this._send_progress();
this._process_next();
let is_gpu_error = str_err_msg.indexOf("CUDA") !== -1
|| str_err_msg.indexOf("GPU memory") !== -1
|| str_err_msg.indexOf("out of memory") !== -1
|| str_err_msg.indexOf("OpenCL") !== -1
|| str_err_msg.indexOf("HIP") !== -1;
if (!BlenderDaemon.is_running() || is_gpu_error) {
if (is_gpu_error) {
this._send_log("Erreur GPU detectee, redemarrage du daemon pour contexte CUDA neuf...");
BlenderDaemon.stop();
} else {
this._send_log("Daemon crash detecte, redemarrage...");
}
BlenderDaemon.start(obj_item.str_blend_path)
.then(() => {
this._send_log("Daemon redemarre.");
this._process_next();
})
.catch((obj_restart_err) => {
this._send_log("ERREUR redemarrage daemon : " + (obj_restart_err.message || String(obj_restart_err)));
this.str_status = STR_STATUS_IDLE;
this._send_progress();
});
} else {
this._process_next();
}
});
}
@@ -440,9 +545,18 @@ class QueueManager {
}
}
let nb_remaining = 0;
for (let nb_r = this.nb_current_index; nb_r < this.list_queue.length; nb_r++) {
let obj_r = this.list_queue[nb_r];
if (obj_r.str_status === "pending" || obj_r.str_status === "rendering") {
nb_remaining++;
}
}
this._send_event("render-progress", {
nb_current: this.nb_current_index,
nb_total: this.list_queue.length,
nb_remaining: nb_remaining,
str_camera: obj_item.str_camera_name || "-",
nb_frame: obj_item.nb_frame || 0,
str_status: this.str_status,
@@ -517,6 +631,20 @@ class QueueManager {
let obj_notif = new Notification({ title: str_title, body: str_body });
obj_notif.show();
}
if (this.obj_email_notifier) {
this.obj_email_notifier.send("[Multi Render] " + str_title, str_body)
.then((obj_result) => {
if (obj_result.is_success) {
this._send_log("Email envoye : " + str_title);
} else if (obj_result.str_error && obj_result.str_error !== "Email non configure") {
this._send_log("Erreur email : " + obj_result.str_error);
}
})
.catch((obj_err) => {
this._send_log("Erreur email : " + obj_err.message);
});
}
}
}