Skip to content

Commit

Permalink
Merge pull request #1 from zaru/feat
Browse files Browse the repository at this point in the history
Support windows, multiple video
  • Loading branch information
zaru authored May 20, 2020
2 parents 9f430d4 + 1b1c163 commit 8bbcb70
Show file tree
Hide file tree
Showing 23 changed files with 10,015 additions and 11,309 deletions.
File renamed without changes.
7 changes: 6 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,10 @@
"node": true,
"es6": true
},
"parserOptions": {
// Required for certain syntax usages
//"ecmaVersion": 6
"ecmaVersion": 2017
},
"extends": ["eslint:recommended", "google"]
}
}
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
dist-bodypix-app/
node_modules/
.cache/

releases/**/*.app
releases/**/*.dmg
releases/mewcam-darwin-x64/
releases/build/
Binary file added app/assets/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 36 additions & 0 deletions app/auto-update.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const {app} = require('electron');
const log = require('electron-log');
const {autoUpdater} = require('electron-updater');

autoUpdater.logger = log;
autoUpdater.logger.transports.file.level = 'info';
log.info('App starting...');

function sendStatusToWindow(text) {
log.info(text);
}
autoUpdater.on('checking-for-update', () => {
sendStatusToWindow('Checking for update...');
});
autoUpdater.on('update-available', (info) => {
sendStatusToWindow('Update available.');
});
autoUpdater.on('update-not-available', (info) => {
sendStatusToWindow('Update not available.');
});
autoUpdater.on('error', (err) => {
sendStatusToWindow('Error in auto-updater. ' + err);
});
autoUpdater.on('download-progress', (progressObj) => {
let message = 'Download speed: ' + progressObj.bytesPerSecond;
message = message + ' - Downloaded ' + progressObj.percent + '%';
message = message + ' (' + progressObj.transferred + '/' + progressObj.total + ')';
sendStatusToWindow(message);
});
autoUpdater.on('update-downloaded', (info) => {
sendStatusToWindow('Update downloaded');
});
app.on('ready', async () => {
console.log('load auto-update');
autoUpdater.checkForUpdatesAndNotify();
});
File renamed without changes.
106 changes: 86 additions & 20 deletions bodypix-app/index.js → app/index.js
Original file line number Diff line number Diff line change
@@ -1,52 +1,79 @@
const bodyPix = require('@tensorflow-models/body-pix');
const VideoManager = require('./video-manager');
const Settings = require('./settings');
const settings = new Settings;

const state = {
deviceId: null,
video: null,
videoWidth: 0,
videoHeight: 0,
changingVideo: false,
ratio() {
return this.videoHeight / this.videoWidth;
},
tray: null,
};

/**
* Main
* @param {string} deviceId
* @return {Promise<void>}
*/
async function workload() {
async function workload(deviceId) {
_setupResizeGuide();
await _loadVideo();
await _loadVideo(deviceId);

_resizeElement(window.innerWidth, window.innerHeight);

const net = await bodyPix.load({
architecture: 'MobileNetV1',
outputStride: 16,
multiplier: 0.5,
quantBytes: 2,
});
const net = await bodyPix.load(settings.getBodyPixModel());

const video = document.getElementById('video');
const canvas = document.getElementById('canvas');
const originalCanvas = document.getElementById('original-canvas');

async function segmentationFrame() {
const segmentation = await net.segmentPerson(state.video);

const originalCtx = originalCanvas.getContext('2d');
const scale = originalCanvas.width / video.videoWidth;
originalCtx.setTransform(scale, 0, 0, scale, 0, 0);
originalCtx.drawImage(state.video, 0, 0);
const imageData = originalCtx.getImageData(
0, 0, originalCanvas.width, originalCanvas.height,
);
_drawToCanvas(canvas, segmentation, imageData);
if (!state.changingVideo) {
const segmentation = await net.segmentPerson(state.video);

const originalCtx = originalCanvas.getContext('2d');
const scale = originalCanvas.width / video.videoWidth;
originalCtx.setTransform(scale, 0, 0, scale, 0, 0);
originalCtx.drawImage(state.video, 0, 0);
const imageData = originalCtx.getImageData(
0, 0, originalCanvas.width, originalCanvas.height,
);
_drawToCanvas(canvas, segmentation, imageData);
}

requestAnimationFrame(segmentationFrame);
}
segmentationFrame();
}

/**
* Switch video
* @param {string} deviceId
*/
function switchVideo(deviceId) {
state.changingVideo = true;
stopExistingVideoCapture();
_loadVideo(deviceId).then(() => {
state.changingVideo = false;
});

settings.setDeviceId(deviceId);
}

function stopExistingVideoCapture() {
if (state.video && state.video.srcObject) {
state.video.srcObject.getTracks().forEach((track) => {
track.stop();
});
state.video.srcObject = null;
}
}

/**
* Set up video stream
* @return {Promise<void>}
Expand All @@ -71,6 +98,7 @@ async function _setupStream() {
function _getStream() {
const config = {
video: {
deviceId: state.deviceId,
audio: false,
facingMode: 'user',
},
Expand All @@ -80,10 +108,12 @@ function _getStream() {

/**
* Load video stream
* @param {string} deviceId
* @return {Promise<void>}
* @private
*/
async function _loadVideo() {
async function _loadVideo(deviceId) {
state.deviceId = deviceId;
state.video = await _setupStream();
state.videoWidth = state.video.videoWidth;
state.videoHeight = state.video.videoHeight;
Expand Down Expand Up @@ -174,4 +204,40 @@ window.addEventListener('resize', () => {
}, 500);
});

workload();
/**
* Build tray menu
* @param {MediaDeviceInfo[]} videoList
*/
function buildTrayMenu(videoList) {
const remote = window.remote;
const {Tray, Menu} = remote;
const icon = window.os.platform() === 'darwin' ? 'TrayIconTemplate.png' : '[email protected]';
state.tray = new Tray(window.__dirname + `/assets/${icon}`);

const videoMenu = [];
videoList.forEach((device) => {
videoMenu.push({
label: device.label,
click() {
switchVideo(device.deviceId);
},
});
});

const menu = Menu.buildFromTemplate([
{
label: 'Select Video',
submenu: videoMenu,
},
]);

state.tray.setContextMenu(menu);
}

const videoManager = new VideoManager;
videoManager.getVideoList().then((list) => {
buildTrayMenu(list);

const deviceId = settings.getDeviceId() || list[0].deviceId;
workload(deviceId);
});
30 changes: 24 additions & 6 deletions app/main.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
const {app, BrowserWindow} = require('electron');
const isDev = require('electron-is-dev');
const os = require('os');
const path = require('path');
require('./auto-update');

if (isDev) {
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = true;
}

/**
* MainWindow
Expand All @@ -11,13 +19,23 @@ function createWindow() {
transparent: true,
frame: false,
resizable: true,
webPreferences: {
nodeIntegration: false,
preload: path.join(__dirname, '/preload.js'),
},
});

const osName = os.platform();
// MagickCode
app.dock.hide();
win.setAlwaysOnTop(true, 'floating');
win.setVisibleOnAllWorkspaces(true);
win.setFullScreenable(false);
app.dock.show();
if (osName === 'darwin') {
app.dock.hide();
win.setAlwaysOnTop(true, 'floating');
win.setVisibleOnAllWorkspaces(true);
win.setFullScreenable(false);
app.dock.show();
} else if (osName === 'win32' || osName === 'cygwin') {
win.setAlwaysOnTop(true);
}

win.addListener('resize', () => {
win.setOpacity(0.5);
Expand All @@ -26,7 +44,7 @@ function createWindow() {
}, 250);
});

win.loadFile('./dist-bodypix-app/index.html');
win.loadURL(isDev ? 'http://localhost:1234' : `file://${__dirname}/../dist-bodypix-app/index.html`);

// win.webContents.openDevTools();
}
Expand Down
3 changes: 3 additions & 0 deletions app/preload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
window.remote = require('electron').remote;
window.__dirname = __dirname;
window.os = require('os');
65 changes: 65 additions & 0 deletions app/settings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* Settings class
*/
module.exports = class Settings {
/**
* constructor
*/
constructor() {
}

/**
* Set deviceId
* @param {string} deviceId
*/
setDeviceId(deviceId) {
localStorage.setItem('deviceId', deviceId);
}

/**
* Get deviceId
* @return {string}
*/
getDeviceId() {
return localStorage.getItem('deviceId');
}

/**
* Set deviceId
* @param {string} key
*/
setBodyPixModel(key) {
localStorage.setItem('bodyPixModel', key);
}

/**
* Get deviceId
* @return {string}
*/
getBodyPixModel() {
const key = localStorage.getItem('bodyPixModel') || 'low';
return this._bodyPixModelList()[key];
}

/**
* BodyPix model list
* @return {{high: object, low: object}}
* @private
*/
_bodyPixModelList() {
return {
low: {
architecture: 'MobileNetV1',
outputStride: 16,
multiplier: 0.5,
quantBytes: 2,
},
high: {
architecture: 'ResNet50',
outputStride: 16,
multiplier: 1.0,
quantBytes: 4,
},
};
}
};
22 changes: 22 additions & 0 deletions app/video-manager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* VideoManager class
*/
module.exports = class VideoManager {
/**
* constructor
*/
constructor() {
}

/**
* Get video list
* @return {Promise<MediaDeviceInfo[]>}
*/
getVideoList() {
return navigator.mediaDevices.enumerateDevices().then((info) => {
return info.filter((device) => {
return device.kind === 'videoinput';
});
});
}
};
13 changes: 0 additions & 13 deletions bodypix-app/.eslintrc.json

This file was deleted.

3 changes: 0 additions & 3 deletions bodypix-app/.gitignore

This file was deleted.

Loading

0 comments on commit 8bbcb70

Please sign in to comment.