Windows support: cross-built NSIS installer
- toFileURL and path-basename handling made Windows-safe (backslashes, drive letters) so media/model loading works there - Blender discovery scans Program Files\Blender Foundation; ffmpeg candidates include common Windows paths - build config: win/nsis target (x64, custom install dir), build/icon.ico generated from the 1024 png, dist:win script Cross-compiled from macOS: dist/DJ Visualizer Setup 1.6.0.exe (95MB) with scripts unpacked and models/MediaPipe bundled. Untested on real Windows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a869d5b969
commit
7414dbe2ec
5 files changed
+38
-9
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 279 KiB |
@@ -114,6 +114,15 @@ async function ensureMicAccess() {
|
|||||||
function blenderPath() {
|
function blenderPath() {
|
||||||
const cands = ['/Applications/Blender.app/Contents/MacOS/Blender',
|
const cands = ['/Applications/Blender.app/Contents/MacOS/Blender',
|
||||||
'/opt/homebrew/bin/blender', '/usr/local/bin/blender'];
|
'/opt/homebrew/bin/blender', '/usr/local/bin/blender'];
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
// Blender installs versioned folders under Program Files
|
||||||
|
for (const root of ['C:\\Program Files\\Blender Foundation', 'C:\\Program Files (x86)\\Blender Foundation']) {
|
||||||
|
try {
|
||||||
|
for (const d of fs.readdirSync(root))
|
||||||
|
cands.push(path.join(root, d, 'blender.exe'));
|
||||||
|
} catch (e) { /* root missing */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
for (const c of cands) if (fs.existsSync(c)) return c;
|
for (const c of cands) if (fs.existsSync(c)) return c;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -209,7 +218,8 @@ function ffmpegPath() {
|
|||||||
const cands = [];
|
const cands = [];
|
||||||
try { cands.push(require('ffmpeg-static')); } catch (e) { /* optional */ }
|
try { cands.push(require('ffmpeg-static')); } catch (e) { /* optional */ }
|
||||||
cands.push(process.env.FFMPEG, 'ffmpeg',
|
cands.push(process.env.FFMPEG, 'ffmpeg',
|
||||||
'/opt/homebrew/bin/ffmpeg', '/usr/local/bin/ffmpeg', '/usr/bin/ffmpeg');
|
'/opt/homebrew/bin/ffmpeg', '/usr/local/bin/ffmpeg', '/usr/bin/ffmpeg',
|
||||||
|
'C:\\ffmpeg\\bin\\ffmpeg.exe', 'C:\\Program Files\\ffmpeg\\bin\\ffmpeg.exe');
|
||||||
for (const c of cands) {
|
for (const c of cands) {
|
||||||
if (!c) continue;
|
if (!c) continue;
|
||||||
if (c === 'ffmpeg' || fs.existsSync(c)) return c;
|
if (c === 'ffmpeg' || fs.existsSync(c)) return c;
|
||||||
|
|||||||
+17
-1
@@ -10,7 +10,8 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "electron .",
|
"start": "electron .",
|
||||||
"dist": "electron-builder --mac"
|
"dist": "electron-builder --mac",
|
||||||
|
"dist:win": "electron-builder --win"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "com.djvisualizer.app",
|
"appId": "com.djvisualizer.app",
|
||||||
@@ -22,6 +23,21 @@
|
|||||||
"icon": "build/icon.icns",
|
"icon": "build/icon.icns",
|
||||||
"identity": null
|
"identity": null
|
||||||
},
|
},
|
||||||
|
"win": {
|
||||||
|
"target": [
|
||||||
|
{
|
||||||
|
"target": "nsis",
|
||||||
|
"arch": [
|
||||||
|
"x64"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"icon": "build/icon.ico"
|
||||||
|
},
|
||||||
|
"nsis": {
|
||||||
|
"oneClick": false,
|
||||||
|
"allowToChangeInstallationDirectory": true
|
||||||
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"main.js",
|
"main.js",
|
||||||
"preload.js",
|
"preload.js",
|
||||||
|
|||||||
+4
-4
@@ -250,8 +250,8 @@ function glbLabel(name) {
|
|||||||
: 'Il modello ruota a tempo di musica nella famiglia “Modello 3D”.';
|
: 'Il modello ruota a tempo di musica nella famiglia “Modello 3D”.';
|
||||||
}
|
}
|
||||||
if (glbPath) {
|
if (glbPath) {
|
||||||
glbLabel(glbPath.split('/').pop());
|
glbLabel(glbPath.split(/[\\/]/).pop());
|
||||||
if (!glbModels.some(m => m.path === glbPath)) addGlbModel(glbPath.split('/').pop(), glbPath);
|
if (!glbModels.some(m => m.path === glbPath)) addGlbModel(glbPath.split(/[\\/]/).pop(), glbPath);
|
||||||
}
|
}
|
||||||
renderGlbModels();
|
renderGlbModels();
|
||||||
// every shader family can be a backdrop too (fluid/interactive/3D excluded:
|
// every shader family can be a backdrop too (fluid/interactive/3D excluded:
|
||||||
@@ -292,7 +292,7 @@ $('#anim-input').addEventListener('change', async (e) => {
|
|||||||
if (res && res.ok) {
|
if (res && res.ok) {
|
||||||
if (!glbAnimFiles.includes(res.path)) glbAnimFiles.push(res.path);
|
if (!glbAnimFiles.includes(res.path)) glbAnimFiles.push(res.path);
|
||||||
send({ type: 'animAdd', path: res.path });
|
send({ type: 'animAdd', path: res.path });
|
||||||
glbLabel(glbPath ? glbPath.split('/').pop() : '');
|
glbLabel(glbPath ? glbPath.split(/[\\/]/).pop() : '');
|
||||||
} else {
|
} else {
|
||||||
$('#glb-name').textContent = '⚠️ ' + ((res && res.error) || 'conversione fallita');
|
$('#glb-name').textContent = '⚠️ ' + ((res && res.error) || 'conversione fallita');
|
||||||
}
|
}
|
||||||
@@ -784,7 +784,7 @@ function refreshScenePreview(i) {
|
|||||||
advanceCues(playCur || 0);
|
advanceCues(playCur || 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseName = (p) => p.split('/').pop();
|
const baseName = (p) => p.split(/[\\/]/).pop();
|
||||||
|
|
||||||
function probePaths(paths) {
|
function probePaths(paths) {
|
||||||
const todo = paths.filter(p => p && !(p in durations));
|
const todo = paths.filter(p => p && !(p in durations));
|
||||||
|
|||||||
+6
-3
@@ -17,9 +17,12 @@ try {
|
|||||||
const audio = new AudioEngine();
|
const audio = new AudioEngine();
|
||||||
let speed = 1.0;
|
let speed = 1.0;
|
||||||
|
|
||||||
// Convert an absolute filesystem path into a file:// URL the renderer can load.
|
// Convert an absolute filesystem path into a file:// URL the renderer can
|
||||||
|
// load (Windows: backslashes -> slashes, drive letter needs a leading /).
|
||||||
function toFileURL(p) {
|
function toFileURL(p) {
|
||||||
return encodeURI('file://' + p).replace(/#/g, '%23').replace(/\?/g, '%3F');
|
let n = String(p).replace(/\\/g, '/');
|
||||||
|
if (n[0] !== '/') n = '/' + n;
|
||||||
|
return encodeURI('file://' + n).replace(/#/g, '%23').replace(/\?/g, '%3F');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------- overlays
|
// ---------------------------------------------------------------- overlays
|
||||||
@@ -341,7 +344,7 @@ djv.onControl(async (m) => {
|
|||||||
const buf = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
const buf = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
||||||
const ok = viz.setModelData(buf);
|
const ok = viz.setModelData(buf);
|
||||||
if (ok === false) throw new Error((viz.model3d && viz.model3d.loadError) || 'GLB non valido');
|
if (ok === false) throw new Error((viz.model3d && viz.model3d.loadError) || 'GLB non valido');
|
||||||
djv.report({ type: 'glbLoaded', name: m.path.split('/').pop() });
|
djv.report({ type: 'glbLoaded', name: m.path.split(/[\\/]/).pop() });
|
||||||
reportClips();
|
reportClips();
|
||||||
hideHint();
|
hideHint();
|
||||||
} catch (e) { djv.report({ type: 'error', message: 'GLB: ' + e.message }); }
|
} catch (e) { djv.report({ type: 'error', message: 'GLB: ' + e.message }); }
|
||||||
|
|||||||
Reference in new issue
Block a user