v1.0.0 — release avec auto-update Gitea

Ajout du systeme de mise a jour automatique :
- UpdateManager (main) : verifie les tags Gitea, telecharge et applique les MAJ
- UpdateBanner (renderer) : banniere UI avec progression et retry
- IPC channels : check-for-updates, apply-update, update-available, update-progress, update-error
- Desactivation asar pour permettre le remplacement des sources
- version.json comme source de verite pour la version locale

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
sorlinv
2026-02-25 15:58:02 +01:00
parent b556cce88c
commit f31f5aa605
16 changed files with 766 additions and 54 deletions

14
main.js
View File

@@ -4,6 +4,7 @@ const fs = require("fs");
const CameraParser = require("./src/main/CameraParser.js"); const CameraParser = require("./src/main/CameraParser.js");
const QueueManager = require("./src/main/QueueManager.js"); const QueueManager = require("./src/main/QueueManager.js");
const ConfigManager = require("./src/main/ConfigManager.js"); const ConfigManager = require("./src/main/ConfigManager.js");
const UpdateManager = require("./src/main/UpdateManager.js");
let obj_main_window = null; let obj_main_window = null;
let obj_queue_manager = null; let obj_queue_manager = null;
@@ -25,6 +26,11 @@ const create_window = () => {
obj_main_window.loadFile(path.join(__dirname, "src", "renderer", "index.html")); obj_main_window.loadFile(path.join(__dirname, "src", "renderer", "index.html"));
obj_queue_manager = new QueueManager(obj_main_window); obj_queue_manager = new QueueManager(obj_main_window);
UpdateManager.init(obj_main_window);
obj_main_window.webContents.on("did-finish-load", () => {
UpdateManager.check_for_updates();
});
}; };
// ── App lifecycle ────────────────────────────────────────────── // ── App lifecycle ──────────────────────────────────────────────
@@ -153,6 +159,14 @@ ipcMain.handle("read-image", (event, str_image_path) => {
return fn_try_read(0); return fn_try_read(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("select-output-folder", () => { ipcMain.handle("select-output-folder", () => {
return dialog.showOpenDialog(obj_main_window, { return dialog.showOpenDialog(obj_main_window, {
title: "Selectionner le dossier de sortie", title: "Selectionner le dossier de sortie",

56
package-lock.json generated
View File

@@ -851,7 +851,6 @@
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0", "fast-json-stable-stringify": "^2.0.0",
@@ -1018,6 +1017,7 @@
"integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"archiver-utils": "^2.1.0", "archiver-utils": "^2.1.0",
"async": "^3.2.4", "async": "^3.2.4",
@@ -1037,6 +1037,7 @@
"integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"glob": "^7.1.4", "glob": "^7.1.4",
"graceful-fs": "^4.2.0", "graceful-fs": "^4.2.0",
@@ -1059,6 +1060,7 @@
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"core-util-is": "~1.0.0", "core-util-is": "~1.0.0",
"inherits": "~2.0.3", "inherits": "~2.0.3",
@@ -1074,7 +1076,8 @@
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/archiver-utils/node_modules/string_decoder": { "node_modules/archiver-utils/node_modules/string_decoder": {
"version": "1.1.1", "version": "1.1.1",
@@ -1082,6 +1085,7 @@
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"safe-buffer": "~5.1.0" "safe-buffer": "~5.1.0"
} }
@@ -1710,6 +1714,7 @@
"integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"buffer-crc32": "^0.2.13", "buffer-crc32": "^0.2.13",
"crc32-stream": "^4.0.2", "crc32-stream": "^4.0.2",
@@ -1834,6 +1839,7 @@
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"bin": { "bin": {
"crc32": "bin/crc32.njs" "crc32": "bin/crc32.njs"
}, },
@@ -1847,6 +1853,7 @@
"integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"crc-32": "^1.2.0", "crc-32": "^1.2.0",
"readable-stream": "^3.4.0" "readable-stream": "^3.4.0"
@@ -2061,7 +2068,6 @@
"integrity": "sha512-NoXo6Liy2heSklTI5OIZbCgXC1RzrDQsZkeEwXhdOro3FT1VBOvbubvscdPnjVuQ4AMwwv61oaH96AbiYg9EnQ==", "integrity": "sha512-NoXo6Liy2heSklTI5OIZbCgXC1RzrDQsZkeEwXhdOro3FT1VBOvbubvscdPnjVuQ4AMwwv61oaH96AbiYg9EnQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"app-builder-lib": "25.1.8", "app-builder-lib": "25.1.8",
"builder-util": "25.1.7", "builder-util": "25.1.7",
@@ -2257,6 +2263,7 @@
"integrity": "sha512-2ntkJ+9+0GFP6nAISiMabKt6eqBB0kX1QqHNWFWAXgi0VULKGisM46luRFpIBiU3u/TDmhZMM8tzvo2Abn3ayg==", "integrity": "sha512-2ntkJ+9+0GFP6nAISiMabKt6eqBB0kX1QqHNWFWAXgi0VULKGisM46luRFpIBiU3u/TDmhZMM8tzvo2Abn3ayg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"app-builder-lib": "25.1.8", "app-builder-lib": "25.1.8",
"archiver": "^5.3.1", "archiver": "^5.3.1",
@@ -2270,6 +2277,7 @@
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"graceful-fs": "^4.2.0", "graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1", "jsonfile": "^6.0.1",
@@ -2285,6 +2293,7 @@
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"universalify": "^2.0.0" "universalify": "^2.0.0"
}, },
@@ -2298,6 +2307,7 @@
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">= 10.0.0" "node": ">= 10.0.0"
} }
@@ -2675,7 +2685,8 @@
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/fs-extra": { "node_modules/fs-extra": {
"version": "8.1.0", "version": "8.1.0",
@@ -3269,7 +3280,8 @@
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/isbinaryfile": { "node_modules/isbinaryfile": {
"version": "5.0.7", "version": "5.0.7",
@@ -3406,6 +3418,7 @@
"integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"readable-stream": "^2.0.5" "readable-stream": "^2.0.5"
}, },
@@ -3419,6 +3432,7 @@
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"core-util-is": "~1.0.0", "core-util-is": "~1.0.0",
"inherits": "~2.0.3", "inherits": "~2.0.3",
@@ -3434,7 +3448,8 @@
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/lazystream/node_modules/string_decoder": { "node_modules/lazystream/node_modules/string_decoder": {
"version": "1.1.1", "version": "1.1.1",
@@ -3442,6 +3457,7 @@
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"safe-buffer": "~5.1.0" "safe-buffer": "~5.1.0"
} }
@@ -3458,35 +3474,40 @@
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/lodash.difference": { "node_modules/lodash.difference": {
"version": "4.5.0", "version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz",
"integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/lodash.flatten": { "node_modules/lodash.flatten": {
"version": "4.4.0", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
"integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/lodash.isplainobject": { "node_modules/lodash.isplainobject": {
"version": "4.0.6", "version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/lodash.union": { "node_modules/lodash.union": {
"version": "4.6.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
"integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/log-symbols": { "node_modules/log-symbols": {
"version": "4.1.0", "version": "4.1.0",
@@ -3959,6 +3980,7 @@
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -4206,7 +4228,8 @@
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/progress": { "node_modules/progress": {
"version": "2.0.3", "version": "2.0.3",
@@ -4307,6 +4330,7 @@
"integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"dependencies": { "dependencies": {
"minimatch": "^5.1.0" "minimatch": "^5.1.0"
} }
@@ -4316,7 +4340,8 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/readdir-glob/node_modules/brace-expansion": { "node_modules/readdir-glob/node_modules/brace-expansion": {
"version": "2.0.2", "version": "2.0.2",
@@ -4324,6 +4349,7 @@
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0" "balanced-match": "^1.0.0"
} }
@@ -4334,6 +4360,7 @@
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"peer": true,
"dependencies": { "dependencies": {
"brace-expansion": "^2.0.1" "brace-expansion": "^2.0.1"
}, },
@@ -4836,6 +4863,7 @@
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"bl": "^4.0.3", "bl": "^4.0.3",
"end-of-stream": "^1.4.1", "end-of-stream": "^1.4.1",
@@ -5213,6 +5241,7 @@
"integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"archiver-utils": "^3.0.4", "archiver-utils": "^3.0.4",
"compress-commons": "^4.1.2", "compress-commons": "^4.1.2",
@@ -5228,6 +5257,7 @@
"integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"glob": "^7.2.3", "glob": "^7.2.3",
"graceful-fs": "^4.2.0", "graceful-fs": "^4.2.0",

View File

@@ -16,10 +16,12 @@
"build": { "build": {
"appId": "com.multirender.blender", "appId": "com.multirender.blender",
"productName": "Multi Render Blender", "productName": "Multi Render Blender",
"asar": false,
"files": [ "files": [
"main.js", "main.js",
"preload.js", "preload.js",
"src/**/*" "src/**/*",
"version.json"
], ],
"extraResources": [ "extraResources": [
{ {

View File

@@ -66,4 +66,30 @@ contextBridge.exposeInMainWorld("api", {
fn_callback(str_message); fn_callback(str_message);
}); });
}, },
check_for_updates: () => {
return ipcRenderer.invoke("check-for-updates");
},
apply_update: (str_tag_name) => {
return ipcRenderer.invoke("apply-update", str_tag_name);
},
on_update_available: (fn_callback) => {
ipcRenderer.on("update-available", (event, obj_data) => {
fn_callback(obj_data);
});
},
on_update_progress: (fn_callback) => {
ipcRenderer.on("update-progress", (event, obj_data) => {
fn_callback(obj_data);
});
},
on_update_error: (fn_callback) => {
ipcRenderer.on("update-error", (event, obj_data) => {
fn_callback(obj_data);
});
},
}); });

View File

@@ -1,12 +1,14 @@
const { spawn } = require("child_process"); const { spawn } = require("child_process");
const PathResolver = require("./PathResolver.js"); const PathResolver = require("./PathResolver.js");
const STR_CAMERA_MARKER = "CAMERAS_JSON:"; const STR_SCENE_MARKER = "SCENE_JSON:";
const STR_PYTHON_EXPR = [ const STR_PYTHON_EXPR = [
"import bpy, json, sys;", "import bpy, json, sys;",
"s=bpy.context.scene;",
"cams=[o.name for o in bpy.data.objects if o.type=='CAMERA'];", "cams=[o.name for o in bpy.data.objects if o.type=='CAMERA'];",
"sys.stdout.write('CAMERAS_JSON:' + json.dumps(cams) + '\\n');", "info={'list_cameras':cams,'obj_scene':{'nb_resolution_x':s.render.resolution_x,'nb_resolution_y':s.render.resolution_y,'nb_frame_start':s.frame_start,'nb_frame_end':s.frame_end,'nb_frame_step':s.frame_step}};",
"sys.stdout.write('SCENE_JSON:' + json.dumps(info) + '\\n');",
"sys.stdout.flush()", "sys.stdout.flush()",
].join(""); ].join("");
@@ -35,14 +37,14 @@ const CameraParser = {
return; return;
} }
let list_cameras = CameraParser._parse_camera_output(str_stdout); let obj_result = CameraParser._parse_scene_output(str_stdout);
if (list_cameras === null) { if (obj_result === null) {
console.error("[CameraParser] Stdout Blender:\n" + str_stdout.substring(str_stdout.length - 2000)); console.error("[CameraParser] Stdout Blender:\n" + str_stdout.substring(str_stdout.length - 2000));
reject(new Error("Impossible de parser les cameras. Verifiez que le fichier .blend contient des cameras.")); reject(new Error("Impossible de parser les cameras. Verifiez que le fichier .blend contient des cameras."));
return; return;
} }
resolve(list_cameras); resolve(obj_result);
}); });
obj_process.on("error", (obj_err) => { obj_process.on("error", (obj_err) => {
@@ -51,12 +53,12 @@ const CameraParser = {
}); });
}, },
_parse_camera_output: (str_stdout) => { _parse_scene_output: (str_stdout) => {
let list_lines = str_stdout.split("\n"); let list_lines = str_stdout.split("\n");
for (let str_line of list_lines) { for (let str_line of list_lines) {
let nb_index = str_line.indexOf(STR_CAMERA_MARKER); let nb_index = str_line.indexOf(STR_SCENE_MARKER);
if (nb_index !== -1) { if (nb_index !== -1) {
let str_json = str_line.substring(nb_index + STR_CAMERA_MARKER.length).trim(); let str_json = str_line.substring(nb_index + STR_SCENE_MARKER.length).trim();
try { try {
return JSON.parse(str_json); return JSON.parse(str_json);
} catch (obj_err) { } catch (obj_err) {

View File

@@ -66,6 +66,8 @@ class QueueManager {
let str_mode = obj_config.str_render_mode; let str_mode = obj_config.str_render_mode;
let str_base_output = obj_config.str_output_path; let str_base_output = obj_config.str_output_path;
let str_output_mode = obj_config.str_output_mode || "subfolder"; let str_output_mode = obj_config.str_output_mode || "subfolder";
let str_frame_prefix = obj_config.str_frame_prefix !== undefined ? obj_config.str_frame_prefix : "f_";
let nb_frame_padding = obj_config.nb_frame_padding || 5;
let list_cameras = obj_config.list_cameras; let list_cameras = obj_config.list_cameras;
let list_enabled = []; let list_enabled = [];
@@ -77,8 +79,9 @@ class QueueManager {
if (str_mode === "camera_by_camera") { if (str_mode === "camera_by_camera") {
for (let obj_cam of list_enabled) { for (let obj_cam of list_enabled) {
for (let nb_frame = obj_cam.nb_frame_start; nb_frame <= obj_cam.nb_frame_end; nb_frame++) { let nb_step = obj_cam.nb_frame_step || 1;
list_queue.push(this._create_queue_item(str_blend_path, str_base_output, str_output_mode, obj_cam, nb_frame)); for (let nb_frame = obj_cam.nb_frame_start; nb_frame <= obj_cam.nb_frame_end; nb_frame += nb_step) {
list_queue.push(this._create_queue_item(str_blend_path, str_base_output, str_output_mode, str_frame_prefix, nb_frame_padding, obj_cam, nb_frame));
} }
} }
} else { } else {
@@ -95,8 +98,9 @@ class QueueManager {
for (let nb_frame = nb_min_frame; nb_frame <= nb_max_frame; nb_frame++) { for (let nb_frame = nb_min_frame; nb_frame <= nb_max_frame; nb_frame++) {
for (let obj_cam of list_enabled) { for (let obj_cam of list_enabled) {
if (nb_frame >= obj_cam.nb_frame_start && nb_frame <= obj_cam.nb_frame_end) { let nb_cam_step = obj_cam.nb_frame_step || 1;
list_queue.push(this._create_queue_item(str_blend_path, str_base_output, str_output_mode, obj_cam, nb_frame)); if (nb_frame >= obj_cam.nb_frame_start && nb_frame <= obj_cam.nb_frame_end && (nb_frame - obj_cam.nb_frame_start) % nb_cam_step === 0) {
list_queue.push(this._create_queue_item(str_blend_path, str_base_output, str_output_mode, str_frame_prefix, nb_frame_padding, obj_cam, nb_frame));
} }
} }
} }
@@ -105,8 +109,9 @@ class QueueManager {
return list_queue; return list_queue;
} }
_create_queue_item(str_blend_path, str_base_output, str_output_mode, obj_cam, nb_frame) { _create_queue_item(str_blend_path, str_base_output, str_output_mode, str_frame_prefix, nb_frame_padding, obj_cam, nb_frame) {
let str_padded_frame = String(nb_frame).padStart(5, "0"); let str_padded_frame = String(nb_frame).padStart(nb_frame_padding, "0");
let str_hash_pattern = "#".repeat(nb_frame_padding);
let str_ext = obj_cam.str_format.toLowerCase(); let str_ext = obj_cam.str_format.toLowerCase();
if (str_ext === "open_exr") { if (str_ext === "open_exr") {
str_ext = "exr"; str_ext = "exr";
@@ -120,14 +125,16 @@ class QueueManager {
let str_expected_file = ""; let str_expected_file = "";
if (str_output_mode === "prefix") { if (str_output_mode === "prefix") {
// Flat: /sortie/Camera.001_frame_##### str_output_path = path.join(str_base_output, obj_cam.str_name + "_" + str_frame_prefix + str_hash_pattern);
str_output_path = path.join(str_base_output, obj_cam.str_name + "_frame_#####"); str_expected_file = path.join(str_base_output, obj_cam.str_name + "_" + str_frame_prefix + str_padded_frame + "." + str_ext);
str_expected_file = path.join(str_base_output, obj_cam.str_name + "_frame_" + str_padded_frame + "." + str_ext); } else if (str_output_mode === "both") {
} else {
// Subfolder: /sortie/Camera.001/frame_#####
let str_output_dir = path.join(str_base_output, obj_cam.str_name); let str_output_dir = path.join(str_base_output, obj_cam.str_name);
str_output_path = path.join(str_output_dir, "frame_#####"); str_output_path = path.join(str_output_dir, obj_cam.str_name + "_" + str_frame_prefix + str_hash_pattern);
str_expected_file = path.join(str_output_dir, "frame_" + str_padded_frame + "." + str_ext); str_expected_file = path.join(str_output_dir, obj_cam.str_name + "_" + str_frame_prefix + str_padded_frame + "." + str_ext);
} else {
let str_output_dir = path.join(str_base_output, obj_cam.str_name);
str_output_path = path.join(str_output_dir, str_frame_prefix + str_hash_pattern);
str_expected_file = path.join(str_output_dir, str_frame_prefix + str_padded_frame + "." + str_ext);
} }
return { return {

410
src/main/UpdateManager.js Normal file
View File

@@ -0,0 +1,410 @@
const https = require("https");
const http = require("http");
const fs = require("fs");
const path = require("path");
const { app } = require("electron");
const { execFile } = require("child_process");
const GITEA_HOST = "git.sorlinv.fr";
const REPO_PATH = "/api/v1/repos/sorlinv/multi_render_blender/tags";
const ARCHIVE_URL_BASE = "https://git.sorlinv.fr/sorlinv/multi_render_blender/archive/";
const NB_TIMEOUT = 10000;
const UpdateManager = {
obj_window: null,
str_local_version: null,
init: (obj_window) => {
UpdateManager.obj_window = obj_window;
UpdateManager.str_local_version = UpdateManager._read_local_version();
},
check_for_updates: () => {
if (!UpdateManager.str_local_version) {
return Promise.resolve(null);
}
return UpdateManager._fetch_tags()
.then((list_tags) => {
if (!list_tags || list_tags.length === 0) {
return null;
}
let str_latest = null;
let str_latest_tag = null;
for (let obj_tag of list_tags) {
let str_name = obj_tag.name || "";
let str_ver = str_name.replace(/^v/, "");
if (!UpdateManager._is_valid_semver(str_ver)) {
continue;
}
if (!str_latest || UpdateManager._compare_versions(str_ver, str_latest) > 0) {
str_latest = str_ver;
str_latest_tag = str_name;
}
}
if (!str_latest) {
return null;
}
if (UpdateManager._compare_versions(str_latest, UpdateManager.str_local_version) > 0) {
UpdateManager._send_event("update-available", {
str_version: str_latest,
str_tag_name: str_latest_tag,
});
return { str_version: str_latest, str_tag_name: str_latest_tag };
}
return null;
})
.catch(() => {
return null;
});
},
download_and_apply: (str_tag_name) => {
let str_temp_dir = path.join(app.getPath("temp"), "mrb_update_" + Date.now());
let str_zip_path = path.join(str_temp_dir, "update.zip");
UpdateManager._send_event("update-progress", {
str_step: "downloading",
nb_percent: 0,
});
return UpdateManager._ensure_dir(str_temp_dir)
.then(() => {
return UpdateManager._download_zip(str_tag_name, str_zip_path);
})
.then(() => {
UpdateManager._send_event("update-progress", {
str_step: "extracting",
nb_percent: 0,
});
return UpdateManager._extract_zip(str_zip_path, str_temp_dir);
})
.then(() => {
UpdateManager._send_event("update-progress", {
str_step: "installing",
nb_percent: 0,
});
return UpdateManager._find_extracted_root(str_temp_dir);
})
.then((str_extracted_root) => {
return UpdateManager._replace_app_files(str_extracted_root);
})
.then(() => {
UpdateManager._send_event("update-progress", {
str_step: "restarting",
nb_percent: 100,
});
UpdateManager._cleanup(str_temp_dir);
app.relaunch();
app.quit();
})
.catch((obj_err) => {
UpdateManager._cleanup(str_temp_dir);
UpdateManager._send_event("update-error", {
str_message: obj_err.message || "Erreur inconnue",
str_tag_name: str_tag_name,
});
throw obj_err;
});
},
// ── Private helpers ──────────────────────────────────────
_read_local_version: () => {
let str_version_path = path.join(__dirname, "..", "..", "version.json");
try {
let str_content = fs.readFileSync(str_version_path, "utf8");
let obj_data = JSON.parse(str_content);
return obj_data.str_version || null;
} catch (obj_err) {
console.error("UpdateManager: impossible de lire version.json :", obj_err.message);
return null;
}
},
_is_valid_semver: (str_ver) => {
return /^\d+\.\d+\.\d+$/.test(str_ver);
},
_compare_versions: (str_a, str_b) => {
let list_a = str_a.split(".").map(Number);
let list_b = str_b.split(".").map(Number);
for (let i = 0; i < 3; i++) {
if (list_a[i] > list_b[i]) {
return 1;
}
if (list_a[i] < list_b[i]) {
return -1;
}
}
return 0;
},
_fetch_tags: () => {
return new Promise((resolve, reject) => {
let obj_options = {
hostname: GITEA_HOST,
path: REPO_PATH,
method: "GET",
timeout: NB_TIMEOUT,
headers: {
"Accept": "application/json",
"User-Agent": "MultiRenderBlender",
},
};
let obj_req = https.request(obj_options, (obj_res) => {
let str_data = "";
obj_res.on("data", (chunk) => {
str_data += chunk;
});
obj_res.on("end", () => {
if (obj_res.statusCode !== 200) {
reject(new Error("Gitea API status " + obj_res.statusCode));
return;
}
try {
let list_tags = JSON.parse(str_data);
resolve(list_tags);
} catch (obj_err) {
reject(new Error("Reponse JSON invalide"));
}
});
});
obj_req.on("timeout", () => {
obj_req.destroy();
reject(new Error("Timeout"));
});
obj_req.on("error", (obj_err) => {
reject(obj_err);
});
obj_req.end();
});
},
_download_zip: (str_tag_name, str_zip_path) => {
let str_url = ARCHIVE_URL_BASE + str_tag_name + ".zip";
let fn_download = (str_download_url, nb_redirects) => {
if (nb_redirects > 5) {
return Promise.reject(new Error("Trop de redirections"));
}
return new Promise((resolve, reject) => {
let obj_parsed = new URL(str_download_url);
let obj_module = obj_parsed.protocol === "https:" ? https : http;
let obj_req = obj_module.get(str_download_url, {
timeout: NB_TIMEOUT,
headers: { "User-Agent": "MultiRenderBlender" },
}, (obj_res) => {
if (obj_res.statusCode === 301 || obj_res.statusCode === 302) {
let str_redirect = obj_res.headers.location;
if (!str_redirect) {
reject(new Error("Redirection sans header Location"));
return;
}
fn_download(str_redirect, nb_redirects + 1)
.then(resolve)
.catch(reject);
return;
}
if (obj_res.statusCode !== 200) {
reject(new Error("Telechargement echoue : HTTP " + obj_res.statusCode));
return;
}
let nb_total = parseInt(obj_res.headers["content-length"], 10) || 0;
let nb_downloaded = 0;
let obj_file = fs.createWriteStream(str_zip_path);
obj_res.on("data", (chunk) => {
nb_downloaded += chunk.length;
if (nb_total > 0) {
let nb_percent = Math.round((nb_downloaded / nb_total) * 100);
UpdateManager._send_event("update-progress", {
str_step: "downloading",
nb_percent: nb_percent,
});
}
});
obj_res.pipe(obj_file);
obj_file.on("finish", () => {
obj_file.close(() => {
resolve();
});
});
obj_file.on("error", (obj_err) => {
fs.unlink(str_zip_path, () => {});
reject(obj_err);
});
});
obj_req.on("timeout", () => {
obj_req.destroy();
reject(new Error("Timeout telechargement"));
});
obj_req.on("error", (obj_err) => {
reject(obj_err);
});
});
};
return fn_download(str_url, 0);
},
_extract_zip: (str_zip_path, str_dest_dir) => {
return new Promise((resolve, reject) => {
if (process.platform === "win32") {
let str_cmd = "Expand-Archive";
let list_args = [
"-Path", str_zip_path,
"-DestinationPath", str_dest_dir,
"-Force",
];
execFile("powershell.exe", ["-Command", str_cmd + " " + list_args.join(" ")], (obj_err) => {
if (obj_err) {
reject(new Error("Extraction echouee : " + obj_err.message));
return;
}
resolve();
});
} else {
execFile("unzip", ["-o", str_zip_path, "-d", str_dest_dir], (obj_err) => {
if (obj_err) {
reject(new Error("Extraction echouee : " + obj_err.message));
return;
}
resolve();
});
}
});
},
_find_extracted_root: (str_temp_dir) => {
return new Promise((resolve, reject) => {
let list_entries = fs.readdirSync(str_temp_dir);
let str_root = null;
for (let str_entry of list_entries) {
if (str_entry === "update.zip") {
continue;
}
let str_full = path.join(str_temp_dir, str_entry);
if (fs.statSync(str_full).isDirectory()) {
str_root = str_full;
break;
}
}
if (!str_root) {
reject(new Error("Dossier extrait introuvable"));
return;
}
resolve(str_root);
});
},
_replace_app_files: (str_source_dir) => {
let str_app_dir = path.join(__dirname, "..", "..");
let LIST_TARGETS = ["main.js", "preload.js", "src", "version.json", "package.json"];
return new Promise((resolve, reject) => {
try {
for (let str_target of LIST_TARGETS) {
let str_src = path.join(str_source_dir, str_target);
let str_dest = path.join(str_app_dir, str_target);
if (!fs.existsSync(str_src)) {
continue;
}
if (fs.existsSync(str_dest)) {
let obj_stat = fs.statSync(str_dest);
if (obj_stat.isDirectory()) {
UpdateManager._rm_recursive(str_dest);
} else {
fs.unlinkSync(str_dest);
}
}
UpdateManager._copy_recursive(str_src, str_dest);
}
resolve();
} catch (obj_err) {
reject(new Error("Remplacement fichiers echoue : " + obj_err.message));
}
});
},
_copy_recursive: (str_src, str_dest) => {
let obj_stat = fs.statSync(str_src);
if (obj_stat.isDirectory()) {
if (!fs.existsSync(str_dest)) {
fs.mkdirSync(str_dest, { recursive: true });
}
let list_entries = fs.readdirSync(str_src);
for (let str_entry of list_entries) {
UpdateManager._copy_recursive(
path.join(str_src, str_entry),
path.join(str_dest, str_entry)
);
}
} else {
fs.copyFileSync(str_src, str_dest);
}
},
_rm_recursive: (str_path) => {
fs.rmSync(str_path, { recursive: true, force: true });
},
_ensure_dir: (str_dir) => {
return new Promise((resolve, reject) => {
try {
fs.mkdirSync(str_dir, { recursive: true });
resolve();
} catch (obj_err) {
reject(obj_err);
}
});
},
_cleanup: (str_dir) => {
try {
if (fs.existsSync(str_dir)) {
UpdateManager._rm_recursive(str_dir);
}
} catch (obj_err) {
console.error("UpdateManager: nettoyage echoue :", obj_err.message);
}
},
_send_event: (str_channel, obj_data) => {
if (UpdateManager.obj_window && !UpdateManager.obj_window.isDestroyed()) {
UpdateManager.obj_window.webContents.send(str_channel, obj_data);
}
},
};
module.exports = UpdateManager;

View File

@@ -67,17 +67,33 @@
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="output_mode" id="radio_output_subfolder" value="subfolder" checked> <input class="form-check-input" type="radio" name="output_mode" id="radio_output_subfolder" value="subfolder" checked>
<label class="form-check-label" for="radio_output_subfolder"> <label class="form-check-label" for="radio_output_subfolder">
Sous-dossier par camera Sous-dossier
</label> </label>
</div> </div>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="output_mode" id="radio_output_prefix" value="prefix"> <input class="form-check-input" type="radio" name="output_mode" id="radio_output_prefix" value="prefix">
<label class="form-check-label" for="radio_output_prefix"> <label class="form-check-label" for="radio_output_prefix">
Nom camera dans le fichier Prefixe
</label> </label>
</div> </div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="output_mode" id="radio_output_both" value="both">
<label class="form-check-label" for="radio_output_both">
Les deux
</label>
</div>
<div class="mt-2 d-flex gap-2 align-items-end">
<div>
<label class="form-label form-label-sm mb-1">Prefixe frame</label>
<input type="text" class="form-control form-control-sm bg-dark text-light border-secondary" id="input_frame_prefix" value="f_" style="max-width: 160px;">
</div>
<div>
<label class="form-label form-label-sm mb-1">Padding</label>
<input type="number" class="form-control form-control-sm bg-dark text-light border-secondary" id="input_frame_padding" value="5" min="1" max="10" style="max-width: 80px;">
</div>
</div>
<div class="mt-1"> <div class="mt-1">
<small id="label_output_example" class="text-light-emphasis">Ex: /sortie/<strong>Camera.001</strong>/frame_00001.png</small> <small id="label_output_example" class="text-light-emphasis">Ex: /sortie/<strong>Camera.001</strong>/f_00001.png</small>
</div> </div>
</div> </div>
</div> </div>
@@ -244,6 +260,7 @@
<script src="scripts/RenderQueue.js"></script> <script src="scripts/RenderQueue.js"></script>
<script src="scripts/PreviewPanel.js"></script> <script src="scripts/PreviewPanel.js"></script>
<script src="scripts/ProgressBar.js"></script> <script src="scripts/ProgressBar.js"></script>
<script src="scripts/UpdateBanner.js"></script>
<script src="scripts/App.js"></script> <script src="scripts/App.js"></script>
</body> </body>
</html> </html>

View File

@@ -9,6 +9,7 @@ const App = {
RenderQueue.init(); RenderQueue.init();
PreviewPanel.init(); PreviewPanel.init();
ProgressBar.init(); ProgressBar.init();
UpdateBanner.init();
App._bind_events(); App._bind_events();
App._bind_render_events(); App._bind_render_events();
@@ -54,8 +55,14 @@ const App = {
let obj_radio_subfolder = document.getElementById("radio_output_subfolder"); let obj_radio_subfolder = document.getElementById("radio_output_subfolder");
let obj_radio_prefix = document.getElementById("radio_output_prefix"); let obj_radio_prefix = document.getElementById("radio_output_prefix");
let obj_radio_both = document.getElementById("radio_output_both");
let obj_input_frame_prefix = document.getElementById("input_frame_prefix");
let obj_input_frame_padding = document.getElementById("input_frame_padding");
obj_radio_subfolder.addEventListener("change", () => { App._update_output_example(); }); obj_radio_subfolder.addEventListener("change", () => { App._update_output_example(); });
obj_radio_prefix.addEventListener("change", () => { App._update_output_example(); }); obj_radio_prefix.addEventListener("change", () => { App._update_output_example(); });
obj_radio_both.addEventListener("change", () => { App._update_output_example(); });
obj_input_frame_prefix.addEventListener("input", () => { App._update_output_example(); });
obj_input_frame_padding.addEventListener("input", () => { App._update_output_example(); });
}, },
_bind_render_events: () => { _bind_render_events: () => {
@@ -73,11 +80,16 @@ const App = {
_update_output_example: () => { _update_output_example: () => {
let str_mode = document.querySelector('input[name="output_mode"]:checked').value; let str_mode = document.querySelector('input[name="output_mode"]:checked').value;
let str_prefix = document.getElementById("input_frame_prefix").value || "";
let nb_padding = parseInt(document.getElementById("input_frame_padding").value, 10) || 5;
let str_padded = String(1).padStart(nb_padding, "0");
let obj_label = document.getElementById("label_output_example"); let obj_label = document.getElementById("label_output_example");
if (str_mode === "subfolder") { if (str_mode === "subfolder") {
obj_label.innerHTML = 'Ex: /sortie/<strong>Camera.001</strong>/frame_00001.png'; obj_label.innerHTML = 'Ex: /sortie/<strong>Camera.001</strong>/' + str_prefix + str_padded + '.png';
} else if (str_mode === "prefix") {
obj_label.innerHTML = 'Ex: /sortie/<strong>Camera.001</strong>_' + str_prefix + str_padded + '.png';
} else { } else {
obj_label.innerHTML = 'Ex: /sortie/<strong>Camera.001</strong>_frame_00001.png'; obj_label.innerHTML = 'Ex: /sortie/<strong>Camera.001</strong>/<strong>Camera.001</strong>_' + str_prefix + str_padded + '.png';
} }
}, },
@@ -101,16 +113,16 @@ const App = {
return window.api.get_cameras(str_path); return window.api.get_cameras(str_path);
}) })
.then((list_cameras) => { .then((obj_result) => {
obj_btn_blend.disabled = false; obj_btn_blend.disabled = false;
if (!list_cameras) { if (!obj_result) {
return; return;
} }
CameraList.set_cameras(list_cameras); CameraList.set_cameras(obj_result.list_cameras, obj_result.obj_scene);
CameraConfig.clear(); CameraConfig.clear();
ConsoleLog.add(list_cameras.length + " camera(s) trouvee(s)."); ConsoleLog.add(obj_result.list_cameras.length + " camera(s) trouvee(s).");
App._update_start_button(); App._update_start_button();
}) })
.catch((obj_err) => { .catch((obj_err) => {
@@ -158,11 +170,15 @@ const App = {
let str_overwrite_mode = document.querySelector('input[name="overwrite_mode"]:checked').value; let str_overwrite_mode = document.querySelector('input[name="overwrite_mode"]:checked').value;
let list_cameras = CameraList.list_cameras; let list_cameras = CameraList.list_cameras;
let str_frame_prefix = document.getElementById("input_frame_prefix").value || "";
let nb_frame_padding = parseInt(document.getElementById("input_frame_padding").value, 10) || 5;
let obj_config = { let obj_config = {
str_blend_file: App.str_blend_path, str_blend_file: App.str_blend_path,
str_render_mode: str_mode, str_render_mode: str_mode,
str_output_mode: str_output_mode, str_output_mode: str_output_mode,
str_overwrite_mode: str_overwrite_mode, str_overwrite_mode: str_overwrite_mode,
str_frame_prefix: str_frame_prefix,
nb_frame_padding: nb_frame_padding,
str_output_path: App.str_output_path, str_output_path: App.str_output_path,
list_cameras: list_cameras, list_cameras: list_cameras,
}; };
@@ -205,11 +221,15 @@ const App = {
let str_mode = document.querySelector('input[name="render_mode"]:checked').value; let str_mode = document.querySelector('input[name="render_mode"]:checked').value;
let str_output_mode = document.querySelector('input[name="output_mode"]:checked').value; let str_output_mode = document.querySelector('input[name="output_mode"]:checked').value;
let str_overwrite_mode = document.querySelector('input[name="overwrite_mode"]:checked').value; let str_overwrite_mode = document.querySelector('input[name="overwrite_mode"]:checked').value;
let str_frame_prefix = document.getElementById("input_frame_prefix").value || "";
let nb_frame_padding = parseInt(document.getElementById("input_frame_padding").value, 10) || 5;
let obj_config = { let obj_config = {
str_blend_file: App.str_blend_path, str_blend_file: App.str_blend_path,
str_render_mode: str_mode, str_render_mode: str_mode,
str_output_mode: str_output_mode, str_output_mode: str_output_mode,
str_overwrite_mode: str_overwrite_mode, str_overwrite_mode: str_overwrite_mode,
str_frame_prefix: str_frame_prefix,
nb_frame_padding: nb_frame_padding,
str_output_path: App.str_output_path, str_output_path: App.str_output_path,
list_cameras: CameraList.list_cameras, list_cameras: CameraList.list_cameras,
}; };
@@ -245,6 +265,8 @@ const App = {
if (obj_config.str_output_mode === "prefix") { if (obj_config.str_output_mode === "prefix") {
document.getElementById("radio_output_prefix").checked = true; document.getElementById("radio_output_prefix").checked = true;
} else if (obj_config.str_output_mode === "both") {
document.getElementById("radio_output_both").checked = true;
} else { } else {
document.getElementById("radio_output_subfolder").checked = true; document.getElementById("radio_output_subfolder").checked = true;
} }
@@ -255,6 +277,13 @@ const App = {
document.getElementById("radio_overwrite").checked = true; document.getElementById("radio_overwrite").checked = true;
} }
if (obj_config.str_frame_prefix !== undefined) {
document.getElementById("input_frame_prefix").value = obj_config.str_frame_prefix;
}
if (obj_config.nb_frame_padding !== undefined) {
document.getElementById("input_frame_padding").value = obj_config.nb_frame_padding;
}
App._update_output_example(); App._update_output_example();
if (obj_config.list_cameras && obj_config.list_cameras.length > 0) { if (obj_config.list_cameras && obj_config.list_cameras.length > 0) {

View File

@@ -32,6 +32,10 @@ const CameraConfig = {
+ ' <label class="form-label form-label-sm">Frame end</label>' + ' <label class="form-label form-label-sm">Frame end</label>'
+ ' <input type="number" class="form-control form-control-sm bg-dark text-light border-secondary" id="input_frame_end" value="' + obj_camera.nb_frame_end + '" min="0">' + ' <input type="number" class="form-control form-control-sm bg-dark text-light border-secondary" id="input_frame_end" value="' + obj_camera.nb_frame_end + '" min="0">'
+ " </div>" + " </div>"
+ ' <div class="col-6">'
+ ' <label class="form-label form-label-sm">Frame step</label>'
+ ' <input type="number" class="form-control form-control-sm bg-dark text-light border-secondary" id="input_frame_step" value="' + obj_camera.nb_frame_step + '" min="1">'
+ " </div>"
+ ' <div class="col-12">' + ' <div class="col-12">'
+ ' <label class="form-label form-label-sm">Format</label>' + ' <label class="form-label form-label-sm">Format</label>'
+ ' <select class="form-select form-select-sm bg-dark text-light border-secondary" id="select_format">' + ' <select class="form-select form-select-sm bg-dark text-light border-secondary" id="select_format">'
@@ -69,8 +73,12 @@ const CameraConfig = {
obj_cam.nb_resolution_x = parseInt(document.getElementById("input_res_x").value, 10) || 1920; obj_cam.nb_resolution_x = parseInt(document.getElementById("input_res_x").value, 10) || 1920;
obj_cam.nb_resolution_y = parseInt(document.getElementById("input_res_y").value, 10) || 1080; obj_cam.nb_resolution_y = parseInt(document.getElementById("input_res_y").value, 10) || 1080;
obj_cam.nb_frame_start = parseInt(document.getElementById("input_frame_start").value, 10) || 1; let nb_parsed_start = parseInt(document.getElementById("input_frame_start").value, 10);
obj_cam.nb_frame_end = parseInt(document.getElementById("input_frame_end").value, 10) || 250; obj_cam.nb_frame_start = isNaN(nb_parsed_start) ? 1 : nb_parsed_start;
let nb_parsed_end = parseInt(document.getElementById("input_frame_end").value, 10);
obj_cam.nb_frame_end = isNaN(nb_parsed_end) ? 250 : nb_parsed_end;
let nb_parsed_step = parseInt(document.getElementById("input_frame_step").value, 10);
obj_cam.nb_frame_step = isNaN(nb_parsed_step) || nb_parsed_step < 1 ? 1 : nb_parsed_step;
obj_cam.str_format = document.getElementById("select_format").value; obj_cam.str_format = document.getElementById("select_format").value;
ConsoleLog.add("Config appliquee pour " + obj_cam.str_name); ConsoleLog.add("Config appliquee pour " + obj_cam.str_name);

View File

@@ -7,17 +7,24 @@ const CameraList = {
CameraList.fn_on_select = fn_on_select; CameraList.fn_on_select = fn_on_select;
}, },
set_cameras: (list_names) => { set_cameras: (list_names, obj_scene) => {
CameraList.list_cameras = []; CameraList.list_cameras = [];
let nb_res_x = (obj_scene && obj_scene.nb_resolution_x) || 1920;
let nb_res_y = (obj_scene && obj_scene.nb_resolution_y) || 1080;
let nb_start = obj_scene && obj_scene.nb_frame_start !== undefined ? obj_scene.nb_frame_start : 1;
let nb_end = (obj_scene && obj_scene.nb_frame_end) || 250;
let nb_step = (obj_scene && obj_scene.nb_frame_step) || 1;
for (let str_name of list_names) { for (let str_name of list_names) {
CameraList.list_cameras.push({ CameraList.list_cameras.push({
str_name: str_name, str_name: str_name,
is_enabled: true, is_enabled: true,
nb_resolution_x: 1920, nb_resolution_x: nb_res_x,
nb_resolution_y: 1080, nb_resolution_y: nb_res_y,
nb_frame_start: 1, nb_frame_start: nb_start,
nb_frame_end: 250, nb_frame_end: nb_end,
nb_frame_step: nb_step,
str_format: "PNG", str_format: "PNG",
}); });
} }

View File

@@ -38,7 +38,7 @@ const ProgressBar = {
obj_camera_label.textContent = str_camera; obj_camera_label.textContent = str_camera;
let obj_frame_label = document.getElementById("label_current_frame"); let obj_frame_label = document.getElementById("label_current_frame");
obj_frame_label.textContent = nb_frame > 0 ? String(nb_frame) : "-"; obj_frame_label.textContent = nb_frame !== null && nb_frame !== undefined ? String(nb_frame) : "-";
RenderQueue.update_progress(nb_current, obj_data.nb_last_render_ms || 0, obj_data.str_last_image_path || null, obj_data.list_skipped || []); RenderQueue.update_progress(nb_current, obj_data.nb_last_render_ms || 0, obj_data.str_last_image_path || null, obj_data.list_skipped || []);
}, },

View File

@@ -23,7 +23,8 @@ const RenderQueue = {
if (str_mode === "camera_by_camera") { if (str_mode === "camera_by_camera") {
for (let obj_cam of list_enabled) { for (let obj_cam of list_enabled) {
for (let nb_frame = obj_cam.nb_frame_start; nb_frame <= obj_cam.nb_frame_end; nb_frame++) { let nb_step = obj_cam.nb_frame_step || 1;
for (let nb_frame = obj_cam.nb_frame_start; nb_frame <= obj_cam.nb_frame_end; nb_frame += nb_step) {
RenderQueue.list_items.push({ RenderQueue.list_items.push({
str_camera: obj_cam.str_name, str_camera: obj_cam.str_name,
nb_frame: nb_frame, nb_frame: nb_frame,
@@ -50,7 +51,8 @@ const RenderQueue = {
for (let nb_frame = nb_min; nb_frame <= nb_max; nb_frame++) { for (let nb_frame = nb_min; nb_frame <= nb_max; nb_frame++) {
for (let obj_cam of list_enabled) { for (let obj_cam of list_enabled) {
if (nb_frame >= obj_cam.nb_frame_start && nb_frame <= obj_cam.nb_frame_end) { let nb_cam_step = obj_cam.nb_frame_step || 1;
if (nb_frame >= obj_cam.nb_frame_start && nb_frame <= obj_cam.nb_frame_end && (nb_frame - obj_cam.nb_frame_start) % nb_cam_step === 0) {
RenderQueue.list_items.push({ RenderQueue.list_items.push({
str_camera: obj_cam.str_name, str_camera: obj_cam.str_name,
nb_frame: nb_frame, nb_frame: nb_frame,

View File

@@ -0,0 +1,132 @@
const UpdateBanner = {
obj_banner: null,
str_pending_tag: null,
init: () => {
UpdateBanner._create_dom();
UpdateBanner._bind_events();
},
_create_dom: () => {
let obj_banner = document.createElement("div");
obj_banner.id = "update_banner";
obj_banner.className = "update-banner d-none";
obj_banner.innerHTML =
'<div class="d-flex align-items-center justify-content-between px-3 py-2">' +
'<span class="update-banner-text">' +
'<i class="mdi mdi-download-circle-outline me-1"></i>' +
'<span id="update_banner_message">Mise a jour disponible</span>' +
'</span>' +
'<div class="d-flex align-items-center gap-2">' +
'<button id="btn_update_install" class="btn btn-sm btn-light">' +
'<i class="mdi mdi-download me-1"></i>Installer' +
'</button>' +
'<button id="btn_update_close" class="btn btn-sm btn-outline-light border-0">' +
'<i class="mdi mdi-close"></i>' +
'</button>' +
'</div>' +
'</div>';
let obj_nav = document.querySelector("nav.navbar");
obj_nav.parentNode.insertBefore(obj_banner, obj_nav.nextSibling);
UpdateBanner.obj_banner = obj_banner;
},
_bind_events: () => {
let obj_btn_install = document.getElementById("btn_update_install");
obj_btn_install.addEventListener("click", () => {
UpdateBanner._on_install_click();
});
let obj_btn_close = document.getElementById("btn_update_close");
obj_btn_close.addEventListener("click", () => {
UpdateBanner._hide();
});
window.api.on_update_available((obj_data) => {
UpdateBanner.str_pending_tag = obj_data.str_tag_name;
UpdateBanner._show("Version " + obj_data.str_version + " disponible");
});
window.api.on_update_progress((obj_data) => {
UpdateBanner._show_progress(obj_data.str_step, obj_data.nb_percent);
});
window.api.on_update_error((obj_data) => {
UpdateBanner.str_pending_tag = obj_data.str_tag_name || UpdateBanner.str_pending_tag;
UpdateBanner._show_error(obj_data.str_message);
});
},
_show: (str_message) => {
let obj_message = document.getElementById("update_banner_message");
obj_message.textContent = str_message;
let obj_btn_install = document.getElementById("btn_update_install");
obj_btn_install.innerHTML = '<i class="mdi mdi-download me-1"></i>Installer';
obj_btn_install.disabled = false;
obj_btn_install.className = "btn btn-sm btn-light";
let obj_btn_close = document.getElementById("btn_update_close");
obj_btn_close.classList.remove("d-none");
UpdateBanner.obj_banner.classList.remove("d-none", "update-banner-error");
document.body.classList.add("has-update-banner");
},
_hide: () => {
UpdateBanner.obj_banner.classList.add("d-none");
document.body.classList.remove("has-update-banner");
},
_on_install_click: () => {
if (!UpdateBanner.str_pending_tag) {
return;
}
let obj_btn_install = document.getElementById("btn_update_install");
obj_btn_install.disabled = true;
obj_btn_install.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>0%';
let obj_btn_close = document.getElementById("btn_update_close");
obj_btn_close.classList.add("d-none");
window.api.apply_update(UpdateBanner.str_pending_tag)
.catch(() => {});
},
_show_progress: (str_step, nb_percent) => {
let obj_btn_install = document.getElementById("btn_update_install");
let obj_message = document.getElementById("update_banner_message");
let str_label = "Mise a jour";
if (str_step === "downloading") {
str_label = "Telechargement";
} else if (str_step === "extracting") {
str_label = "Extraction";
} else if (str_step === "installing") {
str_label = "Installation";
} else if (str_step === "restarting") {
str_label = "Redemarrage";
}
obj_message.textContent = str_label + "...";
obj_btn_install.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>' + nb_percent + "%";
obj_btn_install.disabled = true;
},
_show_error: (str_message) => {
let obj_message = document.getElementById("update_banner_message");
obj_message.textContent = "Erreur : " + str_message;
UpdateBanner.obj_banner.classList.add("update-banner-error");
let obj_btn_install = document.getElementById("btn_update_install");
obj_btn_install.innerHTML = '<i class="mdi mdi-refresh me-1"></i>Reessayer';
obj_btn_install.disabled = false;
obj_btn_install.className = "btn btn-sm btn-outline-light";
let obj_btn_close = document.getElementById("btn_update_close");
obj_btn_close.classList.remove("d-none");
},
};

View File

@@ -12,6 +12,29 @@ body {
overflow: hidden; overflow: hidden;
} }
body.has-update-banner .container-fluid {
height: calc(100vh - 56px - 42px);
}
/* ── Update Banner ─────────────────────────────────────────── */
.update-banner {
background-color: #1a4d2e;
border-bottom: 1px solid #2d6b42;
color: #d4edda;
font-size: 0.8rem;
}
.update-banner.update-banner-error {
background-color: #4d1a1a;
border-bottom-color: #6b2d2d;
color: #f5c6cb;
}
.update-banner-text {
font-weight: 500;
}
.row { .row {
height: 100%; height: 100%;
} }

3
version.json Normal file
View File

@@ -0,0 +1,3 @@
{
"str_version": "1.0.0"
}