diff --git a/build/icon.ico b/build/icon.ico new file mode 100644 index 0000000..8676ba6 Binary files /dev/null and b/build/icon.ico differ diff --git a/main.js b/main.js index c446043..9f1d8b5 100644 --- a/main.js +++ b/main.js @@ -114,6 +114,15 @@ async function ensureMicAccess() { function blenderPath() { const cands = ['/Applications/Blender.app/Contents/MacOS/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; return null; } @@ -209,7 +218,8 @@ function ffmpegPath() { const cands = []; try { cands.push(require('ffmpeg-static')); } catch (e) { /* optional */ } 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) { if (!c) continue; if (c === 'ffmpeg' || fs.existsSync(c)) return c; diff --git a/package.json b/package.json index 394e8a4..23a3e17 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "license": "MIT", "scripts": { "start": "electron .", - "dist": "electron-builder --mac" + "dist": "electron-builder --mac", + "dist:win": "electron-builder --win" }, "build": { "appId": "com.djvisualizer.app", @@ -22,6 +23,21 @@ "icon": "build/icon.icns", "identity": null }, + "win": { + "target": [ + { + "target": "nsis", + "arch": [ + "x64" + ] + } + ], + "icon": "build/icon.ico" + }, + "nsis": { + "oneClick": false, + "allowToChangeInstallationDirectory": true + }, "files": [ "main.js", "preload.js", diff --git a/src/control.js b/src/control.js index cf018b3..7bee3b1 100644 --- a/src/control.js +++ b/src/control.js @@ -250,8 +250,8 @@ function glbLabel(name) { : 'Il modello ruota a tempo di musica nella famiglia “Modello 3D”.'; } if (glbPath) { - glbLabel(glbPath.split('/').pop()); - if (!glbModels.some(m => m.path === glbPath)) addGlbModel(glbPath.split('/').pop(), glbPath); + glbLabel(glbPath.split(/[\\/]/).pop()); + if (!glbModels.some(m => m.path === glbPath)) addGlbModel(glbPath.split(/[\\/]/).pop(), glbPath); } renderGlbModels(); // 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 (!glbAnimFiles.includes(res.path)) glbAnimFiles.push(res.path); send({ type: 'animAdd', path: res.path }); - glbLabel(glbPath ? glbPath.split('/').pop() : ''); + glbLabel(glbPath ? glbPath.split(/[\\/]/).pop() : ''); } else { $('#glb-name').textContent = '⚠️ ' + ((res && res.error) || 'conversione fallita'); } @@ -784,7 +784,7 @@ function refreshScenePreview(i) { advanceCues(playCur || 0); } -const baseName = (p) => p.split('/').pop(); +const baseName = (p) => p.split(/[\\/]/).pop(); function probePaths(paths) { const todo = paths.filter(p => p && !(p in durations)); diff --git a/src/output.js b/src/output.js index 2b8ae58..17d699b 100644 --- a/src/output.js +++ b/src/output.js @@ -17,9 +17,12 @@ try { const audio = new AudioEngine(); 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) { - 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 @@ -341,7 +344,7 @@ djv.onControl(async (m) => { const buf = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength); const ok = viz.setModelData(buf); 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(); hideHint(); } catch (e) { djv.report({ type: 'error', message: 'GLB: ' + e.message }); }