419 lines
14 KiB
JavaScript
419 lines
14 KiB
JavaScript
const { app, BrowserWindow, ipcMain, dialog, shell, clipboard, nativeImage } = require("electron");
|
|
const path = require("path");
|
|
const fs = require("fs");
|
|
const CameraParser = require("./src/main/CameraParser.js");
|
|
const QueueManager = require("./src/main/QueueManager.js");
|
|
const ConfigManager = require("./src/main/ConfigManager.js");
|
|
const UpdateManager = require("./src/main/UpdateManager.js");
|
|
const PathResolver = require("./src/main/PathResolver.js");
|
|
const VideoGenerator = require("./src/main/VideoGenerator.js");
|
|
const EmailNotifier = require("./src/main/EmailNotifier.js");
|
|
|
|
let obj_main_window = null;
|
|
let obj_queue_manager = null;
|
|
let obj_notification_config = { is_notify_each_image: false, is_notify_all_done: true };
|
|
let obj_email_config = { is_enabled: false, str_to: "" };
|
|
|
|
const STR_NOTIFICATION_CONFIG_FILE = "notification_config.json";
|
|
const STR_EMAIL_CONFIG_FILE = "email_config.json";
|
|
|
|
const _load_notification_config = () => {
|
|
let str_config_path = path.join(app.getPath("userData"), STR_NOTIFICATION_CONFIG_FILE);
|
|
try {
|
|
if (fs.existsSync(str_config_path)) {
|
|
let str_content = fs.readFileSync(str_config_path, "utf8");
|
|
obj_notification_config = JSON.parse(str_content);
|
|
}
|
|
} catch (obj_err) {
|
|
console.error("Erreur lecture config notifications :", obj_err.message);
|
|
}
|
|
};
|
|
|
|
const _save_notification_config = () => {
|
|
let str_config_path = path.join(app.getPath("userData"), STR_NOTIFICATION_CONFIG_FILE);
|
|
try {
|
|
fs.writeFileSync(str_config_path, JSON.stringify(obj_notification_config, null, 4), "utf8");
|
|
} catch (obj_err) {
|
|
console.error("Erreur sauvegarde config notifications :", obj_err.message);
|
|
}
|
|
};
|
|
|
|
const _load_email_config = () => {
|
|
let str_config_path = path.join(app.getPath("userData"), STR_EMAIL_CONFIG_FILE);
|
|
try {
|
|
if (fs.existsSync(str_config_path)) {
|
|
let str_content = fs.readFileSync(str_config_path, "utf8");
|
|
obj_email_config = JSON.parse(str_content);
|
|
EmailNotifier.set_config(obj_email_config);
|
|
}
|
|
} catch (obj_err) {
|
|
console.error("Erreur lecture config email :", obj_err.message);
|
|
}
|
|
};
|
|
|
|
const _save_email_config = () => {
|
|
let str_config_path = path.join(app.getPath("userData"), STR_EMAIL_CONFIG_FILE);
|
|
try {
|
|
fs.writeFileSync(str_config_path, JSON.stringify(obj_email_config, null, 4), "utf8");
|
|
} catch (obj_err) {
|
|
console.error("Erreur sauvegarde config email :", obj_err.message);
|
|
}
|
|
};
|
|
|
|
const create_window = () => {
|
|
obj_main_window = new BrowserWindow({
|
|
width: 1400,
|
|
height: 900,
|
|
minWidth: 1000,
|
|
minHeight: 700,
|
|
webPreferences: {
|
|
preload: path.join(__dirname, "preload.js"),
|
|
contextIsolation: true,
|
|
nodeIntegration: false,
|
|
},
|
|
title: "Multi Render Blender",
|
|
});
|
|
|
|
obj_main_window.loadFile(path.join(__dirname, "src", "renderer", "index.html"));
|
|
|
|
obj_queue_manager = new QueueManager(obj_main_window);
|
|
|
|
PathResolver.load_saved_path();
|
|
PathResolver.load_saved_ffmpeg_path();
|
|
_load_notification_config();
|
|
_load_email_config();
|
|
obj_queue_manager.set_notification_config(obj_notification_config);
|
|
obj_queue_manager.set_email_notifier(EmailNotifier);
|
|
|
|
UpdateManager.init(obj_main_window);
|
|
obj_main_window.webContents.on("did-finish-load", () => {
|
|
UpdateManager.check_for_updates();
|
|
obj_main_window.webContents.send("blender-path-status", PathResolver.get_status());
|
|
obj_main_window.webContents.send("ffmpeg-path-status", PathResolver.get_ffmpeg_status());
|
|
});
|
|
};
|
|
|
|
// ── App lifecycle ──────────────────────────────────────────────
|
|
|
|
app.whenReady().then(() => {
|
|
create_window();
|
|
|
|
app.on("activate", () => {
|
|
if (BrowserWindow.getAllWindows().length === 0) {
|
|
create_window();
|
|
}
|
|
});
|
|
});
|
|
|
|
app.on("window-all-closed", () => {
|
|
if (process.platform !== "darwin") {
|
|
app.quit();
|
|
}
|
|
});
|
|
|
|
// ── IPC Handlers ───────────────────────────────────────────────
|
|
|
|
ipcMain.handle("select-blend-file", () => {
|
|
return dialog.showOpenDialog(obj_main_window, {
|
|
title: "Selectionner un fichier Blender",
|
|
filters: [
|
|
{ name: "Blender Files", extensions: ["blend"] },
|
|
],
|
|
properties: ["openFile"],
|
|
})
|
|
.then((obj_result) => {
|
|
if (obj_result.canceled || obj_result.filePaths.length === 0) {
|
|
return null;
|
|
}
|
|
return obj_result.filePaths[0];
|
|
});
|
|
});
|
|
|
|
ipcMain.handle("get-cameras", (event, str_blend_path) => {
|
|
return CameraParser.list_cameras(str_blend_path);
|
|
});
|
|
|
|
ipcMain.handle("start-render", (event, obj_config) => {
|
|
return obj_queue_manager.start(obj_config);
|
|
});
|
|
|
|
ipcMain.handle("check-queue", (event, obj_config) => {
|
|
return obj_queue_manager.check_queue(obj_config);
|
|
});
|
|
|
|
ipcMain.handle("pause-render", () => {
|
|
return obj_queue_manager.pause();
|
|
});
|
|
|
|
ipcMain.handle("stop-render", () => {
|
|
return obj_queue_manager.stop();
|
|
});
|
|
|
|
ipcMain.handle("save-config", (event, obj_config) => {
|
|
return ConfigManager.save(obj_main_window, obj_config);
|
|
});
|
|
|
|
ipcMain.handle("load-config", () => {
|
|
return ConfigManager.load(obj_main_window);
|
|
});
|
|
|
|
ipcMain.handle("read-image", (event, str_image_path) => {
|
|
let NB_MAX_RETRIES = 10;
|
|
let NB_RETRY_DELAY = 500;
|
|
|
|
let fn_try_read = (nb_attempt) => {
|
|
return new Promise((resolve, reject) => {
|
|
if (!fs.existsSync(str_image_path)) {
|
|
if (nb_attempt < NB_MAX_RETRIES) {
|
|
setTimeout(() => {
|
|
fn_try_read(nb_attempt + 1).then(resolve).catch(reject);
|
|
}, NB_RETRY_DELAY);
|
|
return;
|
|
}
|
|
reject(new Error("Fichier introuvable apres " + NB_MAX_RETRIES + " tentatives : " + str_image_path));
|
|
return;
|
|
}
|
|
|
|
// Wait for file size to stabilize (file fully written)
|
|
let nb_size_prev = -1;
|
|
let fn_check_stable = () => {
|
|
let obj_stats = fs.statSync(str_image_path);
|
|
if (obj_stats.size === 0 || obj_stats.size !== nb_size_prev) {
|
|
nb_size_prev = obj_stats.size;
|
|
if (nb_attempt < NB_MAX_RETRIES) {
|
|
setTimeout(() => {
|
|
nb_attempt++;
|
|
fn_check_stable();
|
|
}, NB_RETRY_DELAY);
|
|
return;
|
|
}
|
|
if (obj_stats.size === 0) {
|
|
reject(new Error("Le fichier est vide apres " + NB_MAX_RETRIES + " tentatives : " + str_image_path));
|
|
return;
|
|
}
|
|
}
|
|
|
|
fs.readFile(str_image_path, (obj_err, obj_buffer) => {
|
|
if (obj_err) {
|
|
reject(new Error("Impossible de lire l'image : " + obj_err.message));
|
|
return;
|
|
}
|
|
|
|
let str_ext = path.extname(str_image_path).toLowerCase().replace(".", "");
|
|
let str_mime = "image/png";
|
|
if (str_ext === "jpg" || str_ext === "jpeg") {
|
|
str_mime = "image/jpeg";
|
|
} else if (str_ext === "bmp") {
|
|
str_mime = "image/bmp";
|
|
} else if (str_ext === "tiff" || str_ext === "tif") {
|
|
str_mime = "image/tiff";
|
|
} else if (str_ext === "exr") {
|
|
str_mime = "image/x-exr";
|
|
}
|
|
|
|
let str_base64 = obj_buffer.toString("base64");
|
|
resolve("data:" + str_mime + ";base64," + str_base64);
|
|
});
|
|
};
|
|
|
|
fn_check_stable();
|
|
});
|
|
};
|
|
|
|
return fn_try_read(0);
|
|
});
|
|
|
|
ipcMain.handle("get-blender-path", () => {
|
|
return PathResolver.get_status();
|
|
});
|
|
|
|
ipcMain.handle("set-blender-path", (event, str_path) => {
|
|
let obj_result = PathResolver.set_blender_path(str_path);
|
|
if (obj_result.is_success) {
|
|
obj_main_window.webContents.send("blender-path-status", PathResolver.get_status());
|
|
}
|
|
return obj_result;
|
|
});
|
|
|
|
ipcMain.handle("get-ffmpeg-path", () => {
|
|
return PathResolver.get_ffmpeg_status();
|
|
});
|
|
|
|
ipcMain.handle("set-ffmpeg-path", (event, str_path) => {
|
|
let obj_result = PathResolver.set_ffmpeg_path(str_path);
|
|
if (obj_result.is_success) {
|
|
obj_main_window.webContents.send("ffmpeg-path-status", PathResolver.get_ffmpeg_status());
|
|
}
|
|
return obj_result;
|
|
});
|
|
|
|
ipcMain.handle("select-ffmpeg-exe", () => {
|
|
let str_exe_name = process.platform === "win32" ? "ffmpeg.exe" : "ffmpeg";
|
|
let list_filters = process.platform === "win32"
|
|
? [{ name: "FFmpeg", extensions: ["exe"] }]
|
|
: [{ name: "FFmpeg", extensions: ["*"] }];
|
|
|
|
return dialog.showOpenDialog(obj_main_window, {
|
|
title: "Selectionner l'executable FFmpeg",
|
|
filters: list_filters,
|
|
properties: ["openFile"],
|
|
})
|
|
.then((obj_result) => {
|
|
if (obj_result.canceled || obj_result.filePaths.length === 0) {
|
|
return null;
|
|
}
|
|
return obj_result.filePaths[0];
|
|
});
|
|
});
|
|
|
|
ipcMain.handle("select-blender-exe", () => {
|
|
let str_exe_name = process.platform === "win32" ? "blender.exe" : "blender";
|
|
let list_filters = process.platform === "win32"
|
|
? [{ name: "Blender", extensions: ["exe"] }]
|
|
: [{ name: "Blender", extensions: ["*"] }];
|
|
|
|
return dialog.showOpenDialog(obj_main_window, {
|
|
title: "Selectionner l'executable Blender",
|
|
filters: list_filters,
|
|
properties: ["openFile"],
|
|
})
|
|
.then((obj_result) => {
|
|
if (obj_result.canceled || obj_result.filePaths.length === 0) {
|
|
return null;
|
|
}
|
|
return obj_result.filePaths[0];
|
|
});
|
|
});
|
|
|
|
ipcMain.handle("check-for-updates", () => {
|
|
return UpdateManager.check_for_updates();
|
|
});
|
|
|
|
ipcMain.handle("apply-update", (event, str_tag_name) => {
|
|
return UpdateManager.download_and_apply(str_tag_name);
|
|
});
|
|
|
|
ipcMain.handle("generate-preview-video", (event, obj_params) => {
|
|
let fn_on_log = (str_msg) => {
|
|
if (obj_main_window && !obj_main_window.isDestroyed()) {
|
|
obj_main_window.webContents.send("log", "[ffmpeg] " + str_msg);
|
|
}
|
|
};
|
|
return VideoGenerator.generate(obj_params, fn_on_log);
|
|
});
|
|
|
|
ipcMain.handle("select-output-folder", () => {
|
|
return dialog.showOpenDialog(obj_main_window, {
|
|
title: "Selectionner le dossier de sortie",
|
|
properties: ["openDirectory"],
|
|
})
|
|
.then((obj_result) => {
|
|
if (obj_result.canceled || obj_result.filePaths.length === 0) {
|
|
return null;
|
|
}
|
|
return obj_result.filePaths[0];
|
|
});
|
|
});
|
|
|
|
// ── File actions (context menu) ───────────────────────────────
|
|
|
|
ipcMain.handle("show-item-in-folder", (event, str_path) => {
|
|
shell.showItemInFolder(str_path);
|
|
return { is_success: true };
|
|
});
|
|
|
|
ipcMain.handle("open-file-default", (event, str_path) => {
|
|
return shell.openPath(str_path)
|
|
.then((str_error) => {
|
|
if (str_error) {
|
|
return { is_success: false, str_error: str_error };
|
|
}
|
|
return { is_success: true };
|
|
});
|
|
});
|
|
|
|
ipcMain.handle("delete-rendered-file", (event, str_path) => {
|
|
try {
|
|
if (fs.existsSync(str_path)) {
|
|
fs.unlinkSync(str_path);
|
|
return { is_success: true };
|
|
}
|
|
return { is_success: false, str_error: "Fichier introuvable" };
|
|
} catch (obj_err) {
|
|
return { is_success: false, str_error: obj_err.message };
|
|
}
|
|
});
|
|
|
|
ipcMain.handle("delete-rendered-files", (event, list_paths) => {
|
|
let nb_deleted = 0;
|
|
let list_errors = [];
|
|
for (let str_p of list_paths) {
|
|
try {
|
|
if (fs.existsSync(str_p)) {
|
|
fs.unlinkSync(str_p);
|
|
nb_deleted++;
|
|
}
|
|
} catch (obj_err) {
|
|
list_errors.push(str_p + ": " + obj_err.message);
|
|
}
|
|
}
|
|
return { is_success: true, nb_deleted: nb_deleted, list_errors: list_errors };
|
|
});
|
|
|
|
ipcMain.handle("get-file-info", (event, str_path) => {
|
|
try {
|
|
let obj_stats = fs.statSync(str_path);
|
|
return {
|
|
is_success: true,
|
|
nb_size: obj_stats.size,
|
|
str_modified: obj_stats.mtime.toISOString(),
|
|
str_created: obj_stats.birthtime.toISOString(),
|
|
};
|
|
} catch (obj_err) {
|
|
return { is_success: false, str_error: obj_err.message };
|
|
}
|
|
});
|
|
|
|
ipcMain.handle("copy-image-to-clipboard", (event, str_path) => {
|
|
try {
|
|
let obj_image = nativeImage.createFromPath(str_path);
|
|
if (obj_image.isEmpty()) {
|
|
return { is_success: false, str_error: "Image vide ou format non supporte" };
|
|
}
|
|
clipboard.writeImage(obj_image);
|
|
return { is_success: true };
|
|
} catch (obj_err) {
|
|
return { is_success: false, str_error: obj_err.message };
|
|
}
|
|
});
|
|
|
|
// ── Notification Config ───────────────────────────────────────
|
|
|
|
ipcMain.handle("get-notification-config", () => {
|
|
return obj_notification_config;
|
|
});
|
|
|
|
ipcMain.handle("set-notification-config", (event, obj_config) => {
|
|
obj_notification_config = obj_config;
|
|
_save_notification_config();
|
|
obj_queue_manager.set_notification_config(obj_notification_config);
|
|
return { is_success: true };
|
|
});
|
|
|
|
// ── Email Config ──────────────────────────────────────────────
|
|
|
|
ipcMain.handle("get-email-config", () => {
|
|
return obj_email_config;
|
|
});
|
|
|
|
ipcMain.handle("set-email-config", (event, obj_config) => {
|
|
obj_email_config = obj_config;
|
|
_save_email_config();
|
|
EmailNotifier.set_config(obj_email_config);
|
|
return { is_success: true };
|
|
});
|
|
|
|
ipcMain.handle("test-email", (event, obj_config) => {
|
|
return EmailNotifier.test(obj_config);
|
|
});
|