From 40b5ecfe69a2448f976e3a2dd13d8423b0a3d928 Mon Sep 17 00:00:00 2001 From: mmcwilliams Date: Fri, 8 Feb 2019 18:21:16 -0500 Subject: [PATCH] Digital projector feature added. Select a video and it will be displayed on the main monitor (TODO: address this) and the camera will advance in sync, as with a regular optical printer. --- app/index.html | 6 +- app/lib/display/index.js | 2 +- app/lib/ffmpeg/index.js | 4 +- app/lib/ffprobe/index.js | 33 ++++++--- app/lib/ui/devices.js | 124 +++++++++++++++++++++++---------- app/lib/ui/seq.js | 11 ++- app/main.js | 147 +++++++++++++++++++++++++++++++++++---- 7 files changed, 260 insertions(+), 67 deletions(-) diff --git a/app/index.html b/app/index.html index 87da465..b7e120c 100644 --- a/app/index.html +++ b/app/index.html @@ -259,11 +259,11 @@ - +
- - + +

Camera

diff --git a/app/lib/display/index.js b/app/lib/display/index.js index 1a7ef61..592bd07 100644 --- a/app/lib/display/index.js +++ b/app/lib/display/index.js @@ -88,7 +88,7 @@ async function end () { if (system.platform !== 'nix') { await wv.close(); } else { - cp.kill() + if (cp) cp.kill() } } diff --git a/app/lib/ffmpeg/index.js b/app/lib/ffmpeg/index.js index 33a5b49..dfa7d30 100644 --- a/app/lib/ffmpeg/index.js +++ b/app/lib/ffmpeg/index.js @@ -40,8 +40,8 @@ async function frame (video, frame, obj) { //-vf "select=gte(n\,${frame})" -compression_algo raw -pix_fmt rgb24 "export-${padded}.png" try { - output = await exec(cmd); console.log(cmd); + output = await exec(cmd); } catch (err) { console.error(err); } @@ -145,7 +145,7 @@ async function checkDir () { module.exports = (sys) => { system = sys; - TMPDIR = path.join(system.tmp, 'intval_go_node'); + TMPDIR = path.join(system.tmp, 'mcopy_digital'); checkDir(); diff --git a/app/lib/ffprobe/index.js b/app/lib/ffprobe/index.js index cea2514..21684ed 100644 --- a/app/lib/ffprobe/index.js +++ b/app/lib/ffprobe/index.js @@ -4,7 +4,7 @@ const fs = require('fs-extra'); const exec = require('exec'); //const spawn = require('spawn'); -const exit = require('exit'); +//const exit = require('exit'); let system = {}; @@ -21,17 +21,24 @@ async function info (video) { return exit(err, 5); } if (!exists) { - return exit(`File ${video} does not exist`, 6); + //return exit(`File ${video} does not exist`, 6); + console.error(err); + return false } + try { + console.log(cmd); raw = await exec(cmd); } catch (err) { - return exit(err, 7); + //return exit(err, 7); + console.error(err); + return false } + try { - json = JSON.parse(raw); + json = JSON.parse(raw.stdout); } catch (err) { - return raw; + return raw.stdout; } if (json && json.streams) { @@ -57,22 +64,28 @@ async function frames (video) { try { exists = await fs.exists(video); } catch (err) { - return exit(err, 5); + //return exit(err, 5); + console.error(err); + return false } if (!exists) { - return exit(`File ${video} does not exist`, 6); + //return exit(`File ${video} does not exist`, 6); + console.error(err); + return false; } - + try { + console.log(cmd); raw = await exec(cmd); } catch (err) { console.error(err); + return false; } try { - frames = parseInt(raw) + frames = parseInt(raw.stdout) } catch (err) { - return raw; + return raw.stdout; } return frames; diff --git a/app/lib/ui/devices.js b/app/lib/ui/devices.js index bc14195..4302e29 100644 --- a/app/lib/ui/devices.js +++ b/app/lib/ui/devices.js @@ -37,6 +37,7 @@ devices.listen = function () { 'use strict'; ipcRenderer.on('ready', devices.ready); ipcRenderer.on('intval', devices.intvalCb); + ipcRenderer.on('digital', devices.digitalCb); }; devices.ready = function (event, arg) { 'use strict'; @@ -104,44 +105,6 @@ devices.intval = function () { } }; -devices.digitalSelect = function () { - const elem = $('#digital'); - dialog.showOpenDialog({ - properties: [ - 'openFile', - 'openDirectory' - ], - filters: [ - { name: 'Movies', extensions: ['mkv', 'avi', 'mp4', 'mpeg', 'mov'] }, - { name: 'All Files', extensions: ['*'] } - ] - }); -} - -devices.digital = function () { - 'use strict'; - const elem = $('#digital'); - let proceed = false; - let obj = { - connect: true, - url : url - }; - if ( url !== '' && typeof url !== 'undefined') { - proceed = confirm(`Are you sure you want to`); - } else { - alert('Cannot connect') - } - - if (proceed) { - //gui.overlay(true); - //gui.spinner(true, `Connecting to`); - ipcRenderer.send('video', obj) - } else { - $('#camera_type_arduino').prop('checked', 'checked'); - $('#intval').removeClass('active'); - } -}; - devices.intvalCb = function (evt, args) { 'use strict'; let state; @@ -163,4 +126,89 @@ devices.intvalCb = function (evt, args) { } }; +devices.digitalSelect = function () { + 'use strict'; + const elem = $('#digital'); + const extensions = ['mpg', 'mpeg', 'mov', 'mkv', 'avi']; + dialog.showOpenDialog({ + title : `Select video or image sequence`, + properties : [`openFile`], // openDirectory, multiSelection, openFile + defaultPath: 'c:/', + filters : + [ + { + name: 'Videos', + extensions + }, + { + name: 'All Files', + extensions: ['*'] + }, + ] + }, (files) => { + let valid = false; + console.dir(files) + let path = files[0] + if (path && path !== '') { + for (let ext of extensions) { + if (path.toLowerCase().indexOf(`.${ext}`) !== -1) { + valid = true; + } + } + if (!valid) return false; + log.info(`Selected video ${path.split('/').pop()}`, 'DIGITAL', true); + elem.attr('data-file', path); + elem.val(path.split('/').pop()); + } + }) +} + +devices.digital = function () { + 'use strict'; + const elem = $('#digital'); + const path = elem.attr('data-file'); + const fileName = elem.val(); + let proceed = false; + let obj = { + path, + fileName + } + + if (path && path !== '') { + proceed = confirm(`Are you sure you want to use ${fileName}?`); + } + + if (proceed) { + gui.overlay(true); + gui.spinner(true, `Getting info about ${fileName}`); + ipcRenderer.send('digital', obj) + } else { + $('#projector_type_digital').prop('checked', 'checked'); + $('#digital').removeClass('active'); + } +}; + +devices.digitalCb = function (evt, args) { + 'use strict'; + let state; + gui.spinner(false); + gui.overlay(false); + if (args.valid && args.valid === true) { + //success state + state = JSON.parse(args.state); + $('#digital').addClass('active'); + $('#projector_type_digital').prop('checked', 'checked'); + gui.notify('DEVICES', `Using video ${state.fileName}`); + + mcopy.state.sequence.arr = ['PF', 'CF']; + gui.grid.state(0); + gui.grid.state(1); + + gui.updateState(); + } else { + $('#projector_type_digital').prop('checked', 'checked'); + $('#digital').removeClass('active'); + } +}; + module.exports = devices; \ No newline at end of file diff --git a/app/lib/ui/seq.js b/app/lib/ui/seq.js index 88b7734..d86be8e 100644 --- a/app/lib/ui/seq.js +++ b/app/lib/ui/seq.js @@ -26,6 +26,7 @@ seq.run = function () { } if (seq.i == 0) { $('#loop_current').text(gui.fmtZero(mcopy.loopCount + 1, 6)); + ipcRenderer.send('seq', { action : 'loop' }); } if (seq.stop()) { $('.row input').removeClass('h'); @@ -73,7 +74,7 @@ seq.run = function () { } else { log.info('Sequence completed in ' + humanizeDuration(timeEnd), 'SEQUENCE', true); } - + ipcRenderer.send('seq', { action : 'stop' }); //capture.report = ipcRenderer.sendSync('transfer', { action: 'end'}); //if (capture.active) { //alert(capture.report); @@ -90,6 +91,9 @@ seq.run = function () { seq.stop = function (state) { 'use strict'; if (typeof state === 'undefined') { + if (seq.stopState === true) { + ipcRenderer.send('seq', { action : 'stop' }); + } return seq.stopState; } else { seq.stopState = state; @@ -97,7 +101,10 @@ seq.stop = function (state) { if (state === false) { mcopy.loopCount = 0 $('#loop_current').text(''); + } else { + ipcRenderer.send('seq', { action : 'stop' }); } + return state }; seq.init = function (start) { 'use strict'; @@ -108,7 +115,9 @@ seq.init = function (start) { } seq.stop(false); seq.i = start; + //ipcRenderer.sendSync('transfer', { action: 'start'}); + ipcRenderer.send('seq', { action : 'start' }); seq.run(); }; seq.stats = function () { diff --git a/app/main.js b/app/main.js index 63935b7..14bb34f 100644 --- a/app/main.js +++ b/app/main.js @@ -405,7 +405,8 @@ light.end = async function (rgb, id, ms) { } proj.state = { - dir : true //default dir + dir : true, //default dir + digital : false } proj.init = function () { proj.listen() @@ -419,20 +420,32 @@ proj.set = async function (dir, id) { cmd = mcopy.cfg.arduino.cmd.proj_backward } proj.state.dir = dir - try { - ms = await arduino.send('projector', cmd) - } catch (err) { - console.error(err) + if (proj.digital) { + dig.set(dir) + } else { + try { + ms = await arduino.send('projector', cmd) + } catch (err) { + console.error(err) + } } return await proj.end(cmd, id, ms) } proj.move = async function (frame, id) { const cmd = mcopy.cfg.arduino.cmd.projector let ms - try { - ms = await arduino.send('projector', cmd) - } catch (err) { - console.error(err) + if (proj.digital) { + try { + ms = await dig.move() + } catch (err) { + console.error(err) + } + } else { + try { + ms = await arduino.send('projector', cmd) + } catch (err) { + console.error(err) + } } return await proj.end(mcopy.cfg.arduino.cmd.projector, id, ms) } @@ -453,6 +466,7 @@ proj.listen = function () { } event.returnValue = true }) + ipcMain.on('digital', proj.connectDigital) } proj.end = async function (cmd, id, ms) { let message = '' @@ -473,6 +487,94 @@ proj.end = async function (cmd, id, ms) { return await mainWindow.webContents.send('proj', {cmd: cmd, id : id, ms: ms}) } +/** + * Use a file as the "digital" source on "projector" + * + **/ +proj.connectDigital = async function (evt, arg) { + let info; + let frames = 0; + + try { + info = await ffprobe.info(arg.path); + } catch (err) { + log.error(err, 'DIGITAL', true, true); + proj.digital = false; + await mainWindow.webContents.send('digital', { valid : false }); + return false; + } + try { + frames = await ffprobe.frames(arg.path); + } catch (err) { + log.error(err, 'DIGITAL', true, true); + proj.digital = false; + await mainWindow.webContents.send('digital', { valid : false }); + return false; + } + + dig.state.frame = 0; + dig.state.path = arg.path; + dig.state.fileName = arg.fileName; + dig.state.frames = frames; + dig.state.info = info; + + console.dir(dig.state); + + log.info(`Opened ${dig.state.fileName}`, 'DIGITAL', true, true); + log.info(`Frames : ${frames}`, 'DIGITAL', true, true); + proj.digital = true; + return await mainWindow.webContents.send('digital', { valid : true, state : JSON.stringify(dig.state) }); +} + +const dig = {}; +dig.state = { + frame : 0, + frames : 0, + path : null, + fileName : null, + info : {}, + dir : true +}; + +dig.set = function (dir) { + dig.state.dir = dir; +} + +dig.move = async function () { + let start = +new Date() + let last = dig.state.dir + 0; + if (dig.state.dir) { + dig.state.frame++ + } else { + dig.state.frame-- + } + if (dig.state.frame < 1) { + dig.state.frame = 1 + } + + if (last > 0) { + display.end() + //wipe last frame + try { + await ffmpeg.clear(last) + } catch (err) { + console.error(err) + } + } + + try { + await ffmpeg.frame(dig.state.path, dig.state.frame) + } catch (err) { + console.error(err) + } + + display.start(dig.state.frame) + + await delay(100) + + return (+new Date()) - start +} + cam.intval = null cam.state = { dir : true //default dir @@ -510,10 +612,17 @@ cam.move = async function (frame, id) { const cmd = mcopy.cfg.arduino.cmd.camera let ms if (cam.intval) { - - ms = await cam.intval.move() + try { + ms = await cam.intval.move() + } catch (err) { + console.error(err); + } } else { - ms = await arduino.send('camera', cmd) + try { + ms = await arduino.send('camera', cmd) + } catch (err) { + console.error(err) + } } log.info('Camera move time', { ms }) return cam.end(cmd, id, ms) @@ -591,6 +700,19 @@ cam.end = async function (cmd, id, ms) { mainWindow.webContents.send('cam', {cmd: cmd, id : id, ms: ms}) }; +const seq = {}; +seq.init = function () { + seq.listen(); +} + +seq.listen = function () { + ipcMain.on('seq', async (evt, arg) => { + if (arg.action === 'stop' && proj.digital) { + display.end() + } + }) +} + log.file = function () { let logPath = path.join(os.homedir(), `/.config/mcopy/`) if (process.platform === 'darwin') { @@ -675,6 +797,7 @@ var init = async function () { proj.init() cam.init() dev.init() + seq.init() //capture = require('capture')(SYSTEM); //redundant