├── app
├── utils
│ ├── .gitkeep
│ ├── mainConstants.js
│ ├── queue.js
│ ├── fileObject.js
│ ├── db.js
│ ├── openCVProperties.js
│ ├── saveThumb.js
│ ├── utilsForMain.js
│ ├── faceDetection.js
│ └── utilsForIndexedDB.js
├── app.icns
├── img
│ ├── Thumb_IN.png
│ ├── Thumb_ADD.png
│ ├── Thumb_BACK.png
│ ├── Thumb_EMPTY.png
│ ├── Thumb_HIDE.png
│ ├── Thumb_OUT.png
│ ├── Thumb_REDO.png
│ ├── Thumb_SAVE.png
│ ├── Thumb_SCRUB.png
│ ├── Thumb_SHOW.png
│ ├── Thumb_UNDO.png
│ ├── Thumb_CHOOSE.png
│ ├── Thumb_FORWARD.png
│ ├── Thumb_HANDLE.png
│ ├── Thumb_HIDDEN.png
│ ├── Thumb_VISIBLE.png
│ ├── Thumb_MOVIEPRINT.png
│ ├── Thumb_HANDLE_wide.png
│ ├── Thumb_TRANSPARENT.png
│ ├── MoviePrint_Corrupt_00000.jpg
│ ├── MoviePrint_Logo_v002_128.jpg
│ ├── icon-1x1.svg
│ ├── icon-no-image.svg
│ ├── icon-thumb-view.svg
│ ├── icon-caret-down.svg
│ ├── icon-filter.svg
│ ├── icon-filter-enabled.svg
│ ├── icon-2x2.svg
│ ├── icon-filter-enabled copy.svg
│ ├── icon-cut-view.svg
│ ├── icon-image.svg
│ ├── icon-copy.svg
│ ├── icon-arrow-up.svg
│ ├── icon-resize-vertical.svg
│ ├── icon-resize-horizontal.svg
│ ├── icon-barcode.svg
│ ├── icon-expand.svg
│ ├── icon-3x3.svg
│ ├── icon-header.svg
│ ├── icon-zoom-out.svg
│ ├── icon-header-enabled.svg
│ ├── icon-add-scene.svg
│ ├── icon-zoom-in.svg
│ ├── icon-add-interval.svg
│ ├── icon-4x4.svg
│ ├── icon-grid.svg
│ ├── icon-unhide.svg
│ ├── icon-sort.svg
│ ├── icon-hide.svg
│ ├── icon-frame-info.svg
│ ├── icon-5x5.svg
│ ├── icon-frame-info-enabled.svg
│ ├── icon-show-face-rect.svg
│ ├── icon-show-face-rect-enabled.svg
│ ├── icon-add-face.svg
│ ├── icon-6x6.svg
│ ├── listOfNames.json
│ └── MoviePrint-titleimage.svg
├── components
│ ├── Conditional.js
│ ├── ErrorBoundary.css
│ ├── Popup.css
│ ├── Timeline.css
│ ├── Menu.css
│ ├── ErrorBoundary.js
│ ├── Footer.js
│ ├── FloatingMenu.css
│ ├── Scrub.css
│ ├── HeaderComponent.js
│ ├── VideoPlayer.css
│ └── SceneGrid.css
├── webViewPreload.js
├── worker_printPDF.html
├── containers
│ ├── WorkerRoot.js
│ ├── Root.js
│ ├── Settings.css
│ ├── FileList.js
│ └── VisibleSceneGrid.js
├── store
│ ├── configureStore.js
│ ├── configureStore.prod.js
│ └── configureStore.dev.js
├── worker.js
├── reducers
│ ├── index.js
│ ├── visibilitySettings.js
│ └── files.js
├── package.json
├── index.js
├── app.html
├── worker.html
├── worker_opencv.html
├── app.global.css
└── worker_database.html
├── internals
├── mocks
│ └── fileMock.js
├── flow
│ ├── WebpackAsset.js.flow
│ └── CSSModule.js.flow
├── img
│ ├── js.png
│ ├── flow.png
│ ├── jest.png
│ ├── npm.png
│ ├── react.png
│ ├── redux.png
│ ├── yarn.png
│ ├── eslint.png
│ ├── webpack.png
│ ├── js-padded.png
│ ├── flow-padded.png
│ ├── jest-padded.png
│ ├── react-padded.png
│ ├── react-router.png
│ ├── redux-padded.png
│ ├── yarn-padded.png
│ ├── eslint-padded.png
│ ├── flow-padded-90.png
│ ├── jest-padded-90.png
│ ├── react-padded-90.png
│ ├── redux-padded-90.png
│ ├── webpack-padded.png
│ ├── yarn-padded-90.png
│ ├── eslint-padded-90.png
│ ├── webpack-padded-90.png
│ ├── react-router-padded.png
│ └── react-router-padded-90.png
└── scripts
│ ├── BabelRegister.js
│ ├── RunTests.js
│ ├── CheckNodeEnv.js
│ ├── CheckPortInUse.js
│ ├── ElectronRebuild.js
│ ├── CheckBuildsExist.js
│ └── CheckNativeDep.js
├── .stylelintrc
├── resources
├── icon.ico
├── icon.png
├── icon.icns
├── icons
│ ├── 16x16.png
│ ├── 24x24.png
│ ├── 32x32.png
│ ├── 48x48.png
│ ├── 64x64.png
│ ├── 96x96.png
│ ├── 128x128.png
│ ├── 256x256.png
│ ├── 512x512.png
│ └── 1024x1024.png
├── font
│ └── Franchise-Bold.woff
├── test_files
│ └── test_movie_1.mp4
└── weights
│ ├── age_gender_model-shard1
│ ├── face_expression_model-shard1
│ ├── face_landmark_68_model-shard1
│ ├── face_recognition_model-shard1
│ ├── face_recognition_model-shard2
│ ├── ssd_mobilenetv1_model-shard1
│ ├── ssd_mobilenetv1_model-shard2
│ └── face_expression_model-weights_manifest.json
├── flow-typed
└── module_vx.x.x.js
├── .gitattributes
├── .eslintrc.js
├── test
├── example.js
├── .eslintrc
├── actions
│ ├── __snapshots__
│ │ └── counter.spec.js.snap
│ └── counter.spec.js
├── reducers
│ ├── __snapshots__
│ │ └── counter.spec.js.snap
│ └── counter.spec.js
├── components
│ ├── __snapshots__
│ │ └── Counter.spec.js.snap
│ └── Counter.spec.js
├── containers
│ └── CounterPage.spec.js
└── e2e
│ └── e2e.spec.js
├── configs
├── webpack.config.eslint.js
├── webpack.config.renderer.dev.dll.babel.js
├── webpack.config.main.prod.babel.js
└── webpack.config.base.js
├── .editorconfig
├── .prettierrc.json
├── .github
└── stale.yml
├── appveyor.yml
├── .vscode
└── settings.json
├── scripts
├── includeInDist.js
└── opencv.js
├── .flowconfig
├── .dockerignore
├── .gitignore
├── .eslintignore
├── LICENSE
├── babel.config.js
└── .travis.yml
/app/utils/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/internals/mocks/fileMock.js:
--------------------------------------------------------------------------------
1 | export default 'test-file-stub';
2 |
--------------------------------------------------------------------------------
/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "stylelint-config-standard"
3 | }
4 |
--------------------------------------------------------------------------------
/app/app.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/app/app.icns
--------------------------------------------------------------------------------
/internals/flow/WebpackAsset.js.flow:
--------------------------------------------------------------------------------
1 | // @flow
2 | declare export default string
3 |
--------------------------------------------------------------------------------
/resources/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/resources/icon.ico
--------------------------------------------------------------------------------
/resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/resources/icon.png
--------------------------------------------------------------------------------
/app/img/Thumb_IN.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/app/img/Thumb_IN.png
--------------------------------------------------------------------------------
/flow-typed/module_vx.x.x.js:
--------------------------------------------------------------------------------
1 | declare module 'module' {
2 | declare module.exports: any;
3 | }
4 |
--------------------------------------------------------------------------------
/internals/flow/CSSModule.js.flow:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | declare export default { [key: string]: string }
--------------------------------------------------------------------------------
/internals/img/js.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/internals/img/js.png
--------------------------------------------------------------------------------
/resources/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/resources/icon.icns
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.png binary
2 | *.jpg binary
3 | *.jpeg binary
4 | *.ico binary
5 | *.icns binary
6 |
--------------------------------------------------------------------------------
/app/img/Thumb_ADD.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/app/img/Thumb_ADD.png
--------------------------------------------------------------------------------
/app/img/Thumb_BACK.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/app/img/Thumb_BACK.png
--------------------------------------------------------------------------------
/app/img/Thumb_EMPTY.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/app/img/Thumb_EMPTY.png
--------------------------------------------------------------------------------
/app/img/Thumb_HIDE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/app/img/Thumb_HIDE.png
--------------------------------------------------------------------------------
/app/img/Thumb_OUT.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/app/img/Thumb_OUT.png
--------------------------------------------------------------------------------
/app/img/Thumb_REDO.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/app/img/Thumb_REDO.png
--------------------------------------------------------------------------------
/app/img/Thumb_SAVE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/app/img/Thumb_SAVE.png
--------------------------------------------------------------------------------
/app/img/Thumb_SCRUB.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/app/img/Thumb_SCRUB.png
--------------------------------------------------------------------------------
/app/img/Thumb_SHOW.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/app/img/Thumb_SHOW.png
--------------------------------------------------------------------------------
/app/img/Thumb_UNDO.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/app/img/Thumb_UNDO.png
--------------------------------------------------------------------------------
/internals/img/flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/internals/img/flow.png
--------------------------------------------------------------------------------
/internals/img/jest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/internals/img/jest.png
--------------------------------------------------------------------------------
/internals/img/npm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/internals/img/npm.png
--------------------------------------------------------------------------------
/internals/img/react.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/internals/img/react.png
--------------------------------------------------------------------------------
/internals/img/redux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/internals/img/redux.png
--------------------------------------------------------------------------------
/internals/img/yarn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/internals/img/yarn.png
--------------------------------------------------------------------------------
/app/img/Thumb_CHOOSE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/app/img/Thumb_CHOOSE.png
--------------------------------------------------------------------------------
/app/img/Thumb_FORWARD.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/app/img/Thumb_FORWARD.png
--------------------------------------------------------------------------------
/app/img/Thumb_HANDLE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/app/img/Thumb_HANDLE.png
--------------------------------------------------------------------------------
/app/img/Thumb_HIDDEN.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/app/img/Thumb_HIDDEN.png
--------------------------------------------------------------------------------
/app/img/Thumb_VISIBLE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/app/img/Thumb_VISIBLE.png
--------------------------------------------------------------------------------
/internals/img/eslint.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/internals/img/eslint.png
--------------------------------------------------------------------------------
/internals/img/webpack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/internals/img/webpack.png
--------------------------------------------------------------------------------
/resources/icons/16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/resources/icons/16x16.png
--------------------------------------------------------------------------------
/resources/icons/24x24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/resources/icons/24x24.png
--------------------------------------------------------------------------------
/resources/icons/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/resources/icons/32x32.png
--------------------------------------------------------------------------------
/resources/icons/48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/resources/icons/48x48.png
--------------------------------------------------------------------------------
/resources/icons/64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/resources/icons/64x64.png
--------------------------------------------------------------------------------
/resources/icons/96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/resources/icons/96x96.png
--------------------------------------------------------------------------------
/app/img/Thumb_MOVIEPRINT.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/app/img/Thumb_MOVIEPRINT.png
--------------------------------------------------------------------------------
/internals/img/js-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/internals/img/js-padded.png
--------------------------------------------------------------------------------
/resources/icons/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/resources/icons/128x128.png
--------------------------------------------------------------------------------
/resources/icons/256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/resources/icons/256x256.png
--------------------------------------------------------------------------------
/resources/icons/512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/resources/icons/512x512.png
--------------------------------------------------------------------------------
/app/img/Thumb_HANDLE_wide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/app/img/Thumb_HANDLE_wide.png
--------------------------------------------------------------------------------
/app/img/Thumb_TRANSPARENT.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/app/img/Thumb_TRANSPARENT.png
--------------------------------------------------------------------------------
/internals/img/flow-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/internals/img/flow-padded.png
--------------------------------------------------------------------------------
/internals/img/jest-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/internals/img/jest-padded.png
--------------------------------------------------------------------------------
/internals/img/react-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/internals/img/react-padded.png
--------------------------------------------------------------------------------
/internals/img/react-router.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/internals/img/react-router.png
--------------------------------------------------------------------------------
/internals/img/redux-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/internals/img/redux-padded.png
--------------------------------------------------------------------------------
/internals/img/yarn-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/internals/img/yarn-padded.png
--------------------------------------------------------------------------------
/resources/icons/1024x1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/resources/icons/1024x1024.png
--------------------------------------------------------------------------------
/internals/img/eslint-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/internals/img/eslint-padded.png
--------------------------------------------------------------------------------
/internals/img/flow-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/internals/img/flow-padded-90.png
--------------------------------------------------------------------------------
/internals/img/jest-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/internals/img/jest-padded-90.png
--------------------------------------------------------------------------------
/internals/img/react-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/internals/img/react-padded-90.png
--------------------------------------------------------------------------------
/internals/img/redux-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/internals/img/redux-padded-90.png
--------------------------------------------------------------------------------
/internals/img/webpack-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/internals/img/webpack-padded.png
--------------------------------------------------------------------------------
/internals/img/yarn-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/internals/img/yarn-padded-90.png
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: 'erb',
3 | settings: {
4 | 'import/resolver': 'webpack'
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/internals/img/eslint-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/internals/img/eslint-padded-90.png
--------------------------------------------------------------------------------
/internals/img/webpack-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/internals/img/webpack-padded-90.png
--------------------------------------------------------------------------------
/resources/font/Franchise-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/resources/font/Franchise-Bold.woff
--------------------------------------------------------------------------------
/app/img/MoviePrint_Corrupt_00000.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/app/img/MoviePrint_Corrupt_00000.jpg
--------------------------------------------------------------------------------
/app/img/MoviePrint_Logo_v002_128.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/app/img/MoviePrint_Logo_v002_128.jpg
--------------------------------------------------------------------------------
/internals/img/react-router-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/internals/img/react-router-padded.png
--------------------------------------------------------------------------------
/resources/test_files/test_movie_1.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/resources/test_files/test_movie_1.mp4
--------------------------------------------------------------------------------
/internals/img/react-router-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/internals/img/react-router-padded-90.png
--------------------------------------------------------------------------------
/resources/weights/age_gender_model-shard1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/resources/weights/age_gender_model-shard1
--------------------------------------------------------------------------------
/test/example.js:
--------------------------------------------------------------------------------
1 | describe('description', () => {
2 | it('should have description', () => {
3 | expect(1 + 2).toBe(3);
4 | });
5 | });
6 |
--------------------------------------------------------------------------------
/resources/weights/face_expression_model-shard1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/resources/weights/face_expression_model-shard1
--------------------------------------------------------------------------------
/resources/weights/face_landmark_68_model-shard1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/resources/weights/face_landmark_68_model-shard1
--------------------------------------------------------------------------------
/resources/weights/face_recognition_model-shard1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/resources/weights/face_recognition_model-shard1
--------------------------------------------------------------------------------
/resources/weights/face_recognition_model-shard2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/resources/weights/face_recognition_model-shard2
--------------------------------------------------------------------------------
/resources/weights/ssd_mobilenetv1_model-shard1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/resources/weights/ssd_mobilenetv1_model-shard1
--------------------------------------------------------------------------------
/resources/weights/ssd_mobilenetv1_model-shard2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakob/MoviePrint_v004/HEAD/resources/weights/ssd_mobilenetv1_model-shard2
--------------------------------------------------------------------------------
/internals/scripts/BabelRegister.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | require('@babel/register')({
4 | cwd: path.join(__dirname, '..', '..')
5 | });
6 |
--------------------------------------------------------------------------------
/app/utils/mainConstants.js:
--------------------------------------------------------------------------------
1 | // const { app } = require('electron').remote;
2 | // throws error as it would be packed into main.js where this is can not be required
3 |
--------------------------------------------------------------------------------
/app/img/icon-1x1.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/app/components/Conditional.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | const Conditional = (props) => {
4 | return(
5 | !!props.if && props.children
6 | );
7 | }
8 |
9 | export default Conditional;
10 |
--------------------------------------------------------------------------------
/configs/webpack.config.eslint.js:
--------------------------------------------------------------------------------
1 | /* eslint import/no-unresolved: off, import/no-self-import: off */
2 | require('@babel/register');
3 |
4 | module.exports = require('./webpack.config.renderer.dev.babel').default;
5 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/app/img/icon-no-image.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/app/webViewPreload.js:
--------------------------------------------------------------------------------
1 | const { ipcRenderer } = require('electron');
2 |
3 | document.addEventListener( 'wpcf7mailsent', ( event ) => {
4 | // document.addEventListener( 'wpcf7submit', ( event ) => {
5 | ipcRenderer.sendToHost('wpcf7mailsent', event.detail.inputs);
6 | }, false );
7 |
--------------------------------------------------------------------------------
/test/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "jest/globals": true
4 | },
5 | "plugins": [
6 | "jest"
7 | ],
8 | "rules": {
9 | "jest/no-disabled-tests": "warn",
10 | "jest/no-focused-tests": "error",
11 | "jest/no-identical-title": "error"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "overrides": [
3 | {
4 | "files": [".prettierrc", ".babelrc", ".eslintrc", ".stylelintrc"],
5 | "options": {
6 | "parser": "json"
7 | }
8 | }
9 | ],
10 | "singleQuote": true,
11 | "trailingComma": "all",
12 | "printWidth": 120
13 | }
14 |
--------------------------------------------------------------------------------
/app/img/icon-thumb-view.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/app/img/icon-caret-down.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/app/worker_printPDF.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
14 |
--------------------------------------------------------------------------------
/app/img/icon-filter.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/test/actions/__snapshots__/counter.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`actions should decrement should create decrement action 1`] = `
4 | Object {
5 | "type": "DECREMENT_COUNTER",
6 | }
7 | `;
8 |
9 | exports[`actions should increment should create increment action 1`] = `
10 | Object {
11 | "type": "INCREMENT_COUNTER",
12 | }
13 | `;
14 |
--------------------------------------------------------------------------------
/app/img/icon-filter-enabled.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/test/reducers/__snapshots__/counter.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`reducers counter should handle DECREMENT_COUNTER 1`] = `0`;
4 |
5 | exports[`reducers counter should handle INCREMENT_COUNTER 1`] = `2`;
6 |
7 | exports[`reducers counter should handle initial state 1`] = `0`;
8 |
9 | exports[`reducers counter should handle unknown action type 1`] = `1`;
10 |
--------------------------------------------------------------------------------
/app/img/icon-2x2.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/app/containers/WorkerRoot.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import { Provider } from 'react-redux';
4 | import WorkerApp from './WorkerApp';
5 |
6 | type RootType = {
7 | store: {},
8 | history: {}
9 | };
10 |
11 | export default function WorkerRoot({ store, history }: RootType) {
12 | return (
13 |
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/app/store/configureStore.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import configureStoreDev from './configureStore.dev';
3 | import configureStoreProd from './configureStore.prod';
4 |
5 | const selectedConfigureStore =
6 |
7 | process.env.NODE_ENV === 'production'
8 | ? configureStoreProd
9 | : configureStoreDev;
10 |
11 | export const { configureStore } = selectedConfigureStore;
12 |
13 | export const { history } = selectedConfigureStore;
14 |
--------------------------------------------------------------------------------
/internals/scripts/RunTests.js:
--------------------------------------------------------------------------------
1 | import spawn from 'cross-spawn';
2 | import path from 'path';
3 |
4 | const pattern =
5 | process.argv[2] === 'e2e'
6 | ? 'test/e2e/.+\\.spec\\.js'
7 | : 'test/(?!e2e/)[^/]+/.+\\.spec\\.js$';
8 |
9 | const result = spawn.sync(
10 | path.normalize('./node_modules/.bin/jest'),
11 | [pattern, ...process.argv.slice(2)],
12 | { stdio: 'inherit' }
13 | );
14 |
15 | process.exit(result.status);
16 |
--------------------------------------------------------------------------------
/app/img/icon-filter-enabled copy.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/app/img/icon-cut-view.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/internals/scripts/CheckNodeEnv.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import chalk from 'chalk';
3 |
4 | export default function CheckNodeEnv(expectedEnv: string) {
5 | if (!expectedEnv) {
6 | throw new Error('"expectedEnv" not set');
7 | }
8 |
9 | if (process.env.NODE_ENV !== expectedEnv) {
10 | console.log(
11 | chalk.whiteBright.bgRed.bold(
12 | `"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config`
13 | )
14 | );
15 | process.exit(2);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/img/icon-image.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/app/containers/Root.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import { Provider } from 'react-redux';
4 | import { hot } from 'react-hot-loader/root';
5 | import App from './App';
6 | import ErrorBoundary from '../components/ErrorBoundary';
7 |
8 | type Props = {
9 | store: Store,
10 | history: {}
11 | };
12 |
13 | const Root = ({ store, history }: Props) => (
14 |
15 |
16 |
17 |
18 |
19 | );
20 |
21 | export default hot(Root);
22 |
--------------------------------------------------------------------------------
/app/img/icon-copy.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/internals/scripts/CheckPortInUse.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import chalk from 'chalk';
3 | import detectPort from 'detect-port';
4 |
5 | (function CheckPortInUse() {
6 | const port: string = process.env.PORT || '1212';
7 |
8 | detectPort(port, (err: ?Error, availablePort: number) => {
9 | if (port !== String(availablePort)) {
10 | throw new Error(
11 | chalk.whiteBright.bgRed.bold(
12 | `Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 yarn dev`
13 | )
14 | );
15 | } else {
16 | process.exit(0);
17 | }
18 | });
19 | })();
20 |
--------------------------------------------------------------------------------
/app/img/icon-arrow-up.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/app/components/ErrorBoundary.css:
--------------------------------------------------------------------------------
1 | .ErrorContainer{
2 | position: relative;/* need this to position inner content */
3 | background-color: #1e1e1e;
4 | width: 100%; /* Full width */
5 | height: 100vh; /* Full height */
6 | }
7 |
8 | .ErrorContent{
9 | position: absolute;
10 | top: 50%;
11 | left: 50%;
12 | right: auto;
13 | bottom: auto;
14 | margin-right: -50%;
15 | transform: translate(-50%, -50%);
16 | outline: 0;
17 | font-size: 100px;
18 | font-weight: 600;
19 | line-height: 100px;
20 | text-align: center;
21 | color: #fff;
22 | letter-spacing: 1px;
23 | margin: auto;
24 | font-family: 'Franchise', 'Roboto Condensed';
25 | }
26 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 60
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: 7
5 | # Issues with these labels will never be considered stale
6 | exemptLabels:
7 | - pr
8 | - discussion
9 | - e2e
10 | - enhancement
11 | # Comment to post when marking an issue as stale. Set to `false` to disable
12 | markComment: >
13 | This issue has been automatically marked as stale because it has not had
14 | recent activity. It will be closed if no further activity occurs. Thank you
15 | for your contributions.
16 | # Comment to post when closing a stale issue. Set to `false` to disable
17 | closeComment: false
18 |
--------------------------------------------------------------------------------
/app/img/icon-resize-vertical.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/app/img/icon-resize-horizontal.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | image: Visual Studio 2017
2 |
3 | platform:
4 | - x64
5 |
6 | environment:
7 | matrix:
8 | - nodejs_version: 10
9 | - nodejs_version: 9
10 |
11 | cache:
12 | - '%LOCALAPPDATA%/Yarn'
13 | - node_modules -> package.json
14 | - app/node_modules -> app/package.json
15 | - flow-typed
16 | - '%USERPROFILE%\.electron'
17 |
18 | matrix:
19 | fast_finish: true
20 |
21 | build: off
22 |
23 | version: '{build}'
24 |
25 | shallow_clone: true
26 |
27 | clone_depth: 1
28 |
29 | install:
30 | - ps: Install-Product node $env:nodejs_version x64
31 | - set CI=true
32 | - yarn
33 |
34 | test_script:
35 | - node --version
36 | - yarn lint
37 | - yarn package
38 | - yarn test
39 | - yarn test-e2e
40 |
--------------------------------------------------------------------------------
/app/img/icon-barcode.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.associations": {
3 | ".eslintrc": "jsonc",
4 | ".babelrc": "jsonc",
5 | ".prettierrc": "jsonc"
6 | },
7 |
8 | "javascript.validate.enable": false,
9 | "javascript.format.enable": false,
10 | "typescript.validate.enable": false,
11 | "typescript.format.enable": false,
12 |
13 | "flow.useNPMPackagedFlow": true,
14 | "search.exclude": {
15 | ".git": true,
16 | ".eslintcache": true,
17 | "app/dist": true,
18 | "app/main.prod.js": true,
19 | "app/main.prod.js.map": true,
20 | "bower_components": true,
21 | "dll": true,
22 | "flow-typed": true,
23 | "release": true,
24 | "node_modules": true,
25 | "npm-debug.log.*": true,
26 | "test/**/__snapshots__": true,
27 | "yarn.lock": true
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/img/icon-expand.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/app/img/icon-3x3.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/test/reducers/counter.spec.js:
--------------------------------------------------------------------------------
1 | import counter from '../../app/reducers/counter';
2 | import {
3 | INCREMENT_COUNTER,
4 | DECREMENT_COUNTER
5 | } from '../../app/actions/counter';
6 |
7 | describe('reducers', () => {
8 | describe('counter', () => {
9 | it('should handle initial state', () => {
10 | expect(counter(undefined, {})).toMatchSnapshot();
11 | });
12 |
13 | it('should handle INCREMENT_COUNTER', () => {
14 | expect(counter(1, { type: INCREMENT_COUNTER })).toMatchSnapshot();
15 | });
16 |
17 | it('should handle DECREMENT_COUNTER', () => {
18 | expect(counter(1, { type: DECREMENT_COUNTER })).toMatchSnapshot();
19 | });
20 |
21 | it('should handle unknown action type', () => {
22 | expect(counter(1, { type: 'unknown' })).toMatchSnapshot();
23 | });
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/app/img/icon-header.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/scripts/includeInDist.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import log from 'electron-log';
3 |
4 | const shell = require('shelljs');
5 |
6 | // as the dist folder is not synced on github, we copy files into it before packaging
7 | log.info('running includeInDist script to copy some files into the dist folder for later packaging');
8 |
9 | // set variables
10 | const moviePrintDir = shell.pwd().stdout;
11 | const distDir = path.resolve(moviePrintDir, 'app/dist/');
12 | const resourcesDir = path.resolve(moviePrintDir, 'resources/');
13 | // log.debug(moviePrintDir);
14 | // log.debug(distDir);
15 | // log.debug(resourcesDir);
16 |
17 | // copy files
18 | shell.set('-v'); // verbose
19 | shell.mkdir('-p', distDir); // create folder if it does not exist yet
20 | shell.cp('-nf', path.resolve(resourcesDir, 'font/Franchise-Bold.woff'), distDir);
21 | shell.cp('-nfr', path.resolve(resourcesDir, 'weights/'), distDir);
22 |
--------------------------------------------------------------------------------
/app/img/icon-zoom-out.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/internals/scripts/ElectronRebuild.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import path from 'path';
3 | import { execSync } from 'child_process';
4 | import fs from 'fs';
5 | import { dependencies } from '../../app/package.json';
6 |
7 | (() => {
8 | const nodeModulesPath = path.join(
9 | __dirname,
10 | '..',
11 | '..',
12 | 'app',
13 | 'node_modules'
14 | );
15 |
16 | if (
17 | Object.keys(dependencies || {}).length > 0 &&
18 | fs.existsSync(nodeModulesPath)
19 | ) {
20 | const electronRebuildCmd =
21 | '../node_modules/.bin/electron-rebuild --parallel --force --types prod,dev,optional --module-dir .';
22 | const cmd =
23 | process.platform === 'win32'
24 | ? electronRebuildCmd.replace(/\//g, '\\')
25 | : electronRebuildCmd;
26 | execSync(cmd, {
27 | cwd: path.join(__dirname, '..', '..', 'app'),
28 | stdio: 'inherit'
29 | });
30 | }
31 | })();
32 |
--------------------------------------------------------------------------------
/app/img/icon-header-enabled.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/app/worker.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import { AppContainer } from 'react-hot-loader';
4 | import { ActionCreators as UndoActionCreators } from 'redux-undo';
5 | import WorkerRoot from './containers/WorkerRoot';
6 | import { configureStore, history } from './store/configureStore';
7 |
8 | const store = configureStore();
9 |
10 | render(
11 |
12 |
13 | ,
14 | document.getElementById('worker')
15 | );
16 |
17 | if (module.hot) {
18 | module.hot.accept('./containers/WorkerRoot', () => {
19 | const NextWorkerRoot = require('./containers/WorkerRoot').default; // eslint-disable-line global-require
20 | render(
21 |
22 |
23 | ,
24 | document.getElementById('worker')
25 | );
26 | });
27 | }
28 |
--------------------------------------------------------------------------------
/internals/scripts/CheckBuildsExist.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | // Check if the renderer and main bundles are built
3 | import path from 'path';
4 | import chalk from 'chalk';
5 | import fs from 'fs';
6 |
7 | function CheckBuildsExist() {
8 | const mainPath = path.join(__dirname, '..', '..', 'app', 'main.prod.js');
9 | const rendererPath = path.join(
10 | __dirname,
11 | '..',
12 | '..',
13 | 'app',
14 | 'dist',
15 | 'renderer.prod.js'
16 | );
17 |
18 | if (!fs.existsSync(mainPath)) {
19 | throw new Error(
20 | chalk.whiteBright.bgRed.bold(
21 | 'The main process is not built yet. Build it by running "yarn build-main"'
22 | )
23 | );
24 | }
25 |
26 | if (!fs.existsSync(rendererPath)) {
27 | throw new Error(
28 | chalk.whiteBright.bgRed.bold(
29 | 'The renderer process is not built yet. Build it by running "yarn build-renderer"'
30 | )
31 | );
32 | }
33 | }
34 |
35 | CheckBuildsExist();
36 |
--------------------------------------------------------------------------------
/app/img/icon-add-scene.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 | /app/main.prod.js
3 | /app/main.prod.js.map
4 | /app/dist/.*
5 | /resources/.*
6 | /node_modules/webpack-cli
7 | /release/.*
8 | /dll/.*
9 | /release/.*
10 | /git/.*
11 |
12 | [include]
13 |
14 | [libs]
15 |
16 | [options]
17 | esproposal.class_static_fields=enable
18 | esproposal.class_instance_fields=enable
19 | esproposal.export_star_as=enable
20 | module.name_mapper.extension='css' -> '/internals/flow/CSSModule.js.flow'
21 | module.name_mapper.extension='styl' -> '/internals/flow/CSSModule.js.flow'
22 | module.name_mapper.extension='scss' -> '/internals/flow/CSSModule.js.flow'
23 | module.name_mapper.extension='png' -> '/internals/flow/WebpackAsset.js.flow'
24 | module.name_mapper.extension='jpg' -> '/internals/flow/WebpackAsset.js.flow'
25 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe
26 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue
27 |
--------------------------------------------------------------------------------
/app/img/icon-zoom-in.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 | .eslintcache
25 |
26 | # Dependency directory
27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
28 | node_modules
29 | app/node_modules
30 |
31 | # OSX
32 | .DS_Store
33 |
34 | # flow-typed
35 | flow-typed/npm/*
36 | !flow-typed/npm/module_vx.x.x.js
37 |
38 | # App packaged
39 | release
40 | app/main.prod.js
41 | app/main.prod.js.map
42 | app/renderer.prod.js
43 | app/renderer.prod.js.map
44 | app/style.css
45 | app/style.css.map
46 | dist
47 | dll
48 | main.js
49 | main.js.map
50 |
51 | .idea
52 | npm-debug.log.*
53 | .*.dockerfile
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 | .eslintcache
25 |
26 | # Dependency directory
27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
28 | node_modules
29 | app/node_modules
30 |
31 | # OSX
32 | .DS_Store
33 |
34 | # flow-typed
35 | flow-typed/npm/*
36 | !flow-typed/npm/module_vx.x.x.js
37 |
38 | # App packaged
39 | release
40 | app/main.prod.js
41 | app/main.prod.js.map
42 | app/renderer.prod.js
43 | app/renderer.prod.js.map
44 | app/style.css
45 | app/style.css.map
46 | dist
47 | dll
48 | main.js
49 | main.js.map
50 |
51 | .idea
52 | npm-debug.log.*
53 | .env
54 | electron-builder.env
55 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 | .eslintcache
25 |
26 | # Dependency directory
27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
28 | node_modules
29 | app/node_modules
30 |
31 | # OSX
32 | .DS_Store
33 |
34 | # flow-typed
35 | flow-typed/npm/*
36 | !flow-typed/npm/module_vx.x.x.js
37 |
38 | # App packaged
39 | release
40 | app/main.prod.js
41 | app/main.prod.js.map
42 | app/renderer.prod.js
43 | app/renderer.prod.js.map
44 | app/style.css
45 | app/style.css.map
46 | dist
47 | dll
48 | main.js
49 | main.js.map
50 |
51 | .idea
52 | npm-debug.log.*
53 | __snapshots__
54 |
55 | # Package.json
56 | package.json
57 | .travis.yml
58 |
--------------------------------------------------------------------------------
/app/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import undoable, { excludeAction, groupByActionTypes } from 'redux-undo';
3 | import sheetsByFileId from './sheetsByFileId';
4 | import files from './files';
5 | import settings from './settings';
6 | import visibilitySettings from './visibilitySettings';
7 | import { UNDO_STEPS_LIMIT } from '../utils/constants';
8 |
9 | const rootReducer = combineReducers({
10 | visibilitySettings,
11 | undoGroup: undoable(
12 | combineReducers({
13 | settings,
14 | sheetsByFileId,
15 | files,
16 | }),
17 | {
18 | filter: excludeAction([
19 | // 'UPDATE_MOVIE_LIST_ITEM_USERATIO'
20 | ]),
21 | groupBy: groupByActionTypes([
22 | 'UPDATE_MOVIE_LIST_ITEM_USERATIO',
23 | 'UPDATE_FRAMENUMBER_AND_COLORARRAY_OF_THUMB',
24 | 'UPDATE_OBJECTURL_FROM_THUMBLIST',
25 | 'UPDATE_SHEET_COLUMNCOUNT',
26 | 'SET_DEFAULT_MARGIN',
27 | 'SET_DEFAULT_SHOW_HEADER',
28 | 'SET_DEFAULT_ROUNDED_CORNERS',
29 | ]),
30 | limit: UNDO_STEPS_LIMIT,
31 | },
32 | ),
33 | });
34 |
35 | export default rootReducer;
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018-present Jakob Schindegger
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/app/store/configureStore.prod.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { createStore, applyMiddleware } from 'redux';
3 | import thunk from 'redux-thunk';
4 | import { createHashHistory } from 'history';
5 | import { routerMiddleware } from 'connected-react-router';
6 | import throttle from 'lodash/throttle';
7 | import rootReducer from '../reducers';
8 | import { loadState, saveState } from './localStorage';
9 |
10 | const history = createHashHistory();
11 | const router = routerMiddleware(history);
12 | const enhancer = applyMiddleware(thunk, router);
13 |
14 | function configureStore(initialState) {
15 | let persistedState;
16 | if (initialState === undefined) {
17 | persistedState = loadState();
18 | } else {
19 | persistedState = initialState;
20 | }
21 |
22 | // Create Store
23 | const store = createStore(rootReducer, persistedState, enhancer); // eslint-disable-line
24 |
25 | store.subscribe(throttle(() => {
26 | saveState(store.getState());
27 | // // only store thumbs in localStorage
28 | // saveState({
29 | // thumbs: store.getState().thumbs
30 | // });
31 | }, 1000));
32 |
33 | return store;
34 | }
35 |
36 | export default { configureStore, history };
37 |
--------------------------------------------------------------------------------
/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "MoviePrint_v004",
3 | "productName": "MoviePrint_v004",
4 | "version": "0.2.23",
5 | "description": "A tool which lets you create screenshots of entire movies in an instant.",
6 | "main": "./main.prod.js",
7 | "author": {
8 | "name": "Jakob Schindegger",
9 | "email": "jakob@movieprint.org",
10 | "url": "https://movieprint.org"
11 | },
12 | "license": "MIT",
13 | "bugs": {
14 | "url": "https://github.com/fakob/MoviePrint_v004/issues"
15 | },
16 | "keywords": [
17 | "opencv",
18 | "screenshot",
19 | "movie"
20 | ],
21 | "homepage": "https://github.com/fakob/MoviePrint_v004#readme",
22 | "scripts": {
23 | "electron-rebuild": "node -r ../internals/scripts/BabelRegister.js ../internals/scripts/ElectronRebuild.js",
24 | "postinstall": "yarn electron-rebuild -w opencv4nodejs,better-sqlite3,@tensorflow/tfjs-node"
25 | },
26 | "dependencies": {
27 | "@tensorflow/tfjs-node": "^1.5.2",
28 | "better-sqlite3": "^5.4.0",
29 | "face-api.js": "^0.22.1",
30 | "opencv4nodejs": "^4.9.1",
31 | "typeface-open-sans": "^0.0.54",
32 | "typeface-roboto-condensed": "^0.0.54",
33 | "typeface-ubuntu": "^0.0.65"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/img/icon-add-interval.svg:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/app/index.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import { render } from 'react-dom';
3 | import { AppContainer as ReactHotAppContainer } from 'react-hot-loader';
4 | import { ActionCreators as UndoActionCreators } from 'redux-undo';
5 | import Root from './containers/Root';
6 | import { configureStore, history } from './store/configureStore';
7 |
8 | const { ipcRenderer } = require('electron');
9 |
10 | ipcRenderer.on('undo', () => {
11 | store.dispatch(UndoActionCreators.undo());
12 | });
13 |
14 | ipcRenderer.on('redo', () => {
15 | store.dispatch(UndoActionCreators.redo());
16 | });
17 |
18 | const store = configureStore();
19 |
20 | const AppContainer = process.env.PLAIN_HMR ? Fragment : ReactHotAppContainer;
21 |
22 | render(
23 |
24 |
25 | ,
26 | document.getElementById('root')
27 | );
28 |
29 | // if (module.hot) {
30 | // module.hot.accept('./containers/Root', () => {
31 | // // eslint-disable-line global-require
32 | //
33 | // const NextRoot = require('./containers/Root').default;
34 | // render(
35 | //
36 | //
37 | // ,
38 | // document.getElementById('root')
39 | // );
40 | // });
41 | // }
42 |
--------------------------------------------------------------------------------
/app/img/icon-4x4.svg:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/app/utils/queue.js:
--------------------------------------------------------------------------------
1 | /* eslint func-names: ["error", "never"] */
2 |
3 | // import log from 'electron-log';
4 |
5 | // the queue adds all new ones in front
6 | // const q = new Queue():
7 | // q.add(1);
8 | // q.add(2);
9 | // q.add(3);
10 | // console.log(q);
11 | // // [3,2,1]
12 |
13 | function Queue() {
14 | this.data = [];
15 | }
16 |
17 | Queue.prototype.add = function(record) {
18 | this.data.unshift(record);
19 | }
20 |
21 | Queue.prototype.addArray = function(arrayOfRecords) {
22 | const combinedArray = [...this.data, ...arrayOfRecords];
23 | this.data = combinedArray;
24 | }
25 |
26 | Queue.prototype.removeFirst = function() {
27 | return this.data.shift();
28 | }
29 |
30 | Queue.prototype.removeFirstMany = function(amount) {
31 | return this.data.splice(0, amount);
32 | }
33 |
34 | Queue.prototype.removeLastMany = function(amount) {
35 | return this.data.splice(amount * -1, amount);
36 | }
37 |
38 | Queue.prototype.removeLast = function() {
39 | return this.data.pop();
40 | }
41 |
42 | Queue.prototype.clear = function() {
43 | this.data = [];
44 | }
45 |
46 | Queue.prototype.first = function() {
47 | return this.data[0];
48 | }
49 |
50 | Queue.prototype.last = function() {
51 | return this.data[this.data.length - 1];
52 | }
53 |
54 | Queue.prototype.size = function() {
55 | return this.data.length;
56 | }
57 |
58 | export default Queue;
59 |
--------------------------------------------------------------------------------
/app/reducers/visibilitySettings.js:
--------------------------------------------------------------------------------
1 | const visibilitySettings = (state = {}, action) => {
2 | switch (action.type) {
3 | case 'SET_VISIBILITY_FILTER':
4 | return { ...state, visibilityFilter: action.filter };
5 | case 'TOGGLE_MOVIELIST':
6 | return { ...state, showMovielist: !state.showMovielist };
7 | case 'SHOW_MOVIELIST':
8 | return { ...state, showMovielist: true };
9 | case 'HIDE_MOVIELIST':
10 | return { ...state, showMovielist: false };
11 | case 'TOGGLE_SETTINGS':
12 | return { ...state, showSettings: !state.showSettings };
13 | case 'SHOW_SETTINGS':
14 | return { ...state, showSettings: true };
15 | case 'HIDE_SETTINGS':
16 | return { ...state, showSettings: false };
17 | case 'SET_VIEW':
18 | return { ...state, defaultView: action.defaultView };
19 | case 'SET_SHEETVIEW':
20 | return { ...state, defaultSheetView: action.defaultSheetView };
21 | case 'SET_ZOOMLEVEL':
22 | return { ...state, defaultZoomLevel: action.defaultZoomLevel };
23 | // case 'TOGGLE_ZOOM_OUT':
24 | // return { ...state, zoomOut: !state.zoomOut };
25 | // case 'ZOOM_OUT':
26 | // return { ...state, zoomOut: true };
27 | // case 'ZOOM_IN':
28 | // return { ...state, zoomOut: false };
29 | default:
30 | return state;
31 | }
32 | };
33 |
34 | export default visibilitySettings;
35 |
--------------------------------------------------------------------------------
/app/components/Popup.css:
--------------------------------------------------------------------------------
1 | .popup {
2 | margin-bottom: 0 !important;
3 | font-family: Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif !important;
4 | font-weight: 100 !important;
5 | font-size: 12px !important;
6 | }
7 |
8 | .popupSmall {
9 | /* background-color: rgba(30, 30, 30, 0.9) !important; */
10 | /* color: #ffffff !important; */
11 | /* border: none !important; */
12 | margin: 0 !important;
13 | padding: 4px 8px !important;
14 | font-family: Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif !important;
15 | font-weight: 100 !important;
16 | font-size: 12px !important;
17 | }
18 |
19 | .popupSmall::before {
20 | display: none !important;
21 | }
22 |
23 | .toast {
24 | padding: 20px;
25 | border-radius: 4px;
26 | /* overflow-wrap: break-word; */
27 | overflow-wrap: anywhere;
28 | hyphens: auto;
29 | }
30 |
31 | .toastInfo {
32 | /* background-color: rgba(60, 60, 60, 1.0); */
33 | background-color: rgba(60, 60, 60, 0.97);
34 | box-shadow: 0px 0px 64px 0px rgba(255,255,255,0.1);
35 | color: white;
36 | }
37 |
38 | .toastSuccess {
39 | background-color: rgba(56, 143, 2, 0.97);
40 | /* background-color: #388F02; */
41 | color: white;
42 | }
43 |
44 | .toastError {
45 | background-color: rgba(194, 0, 0, 0.97);
46 | /* background-color: #c20000; */
47 | color: white;
48 | }
49 |
50 | .toastProgress {
51 | background: #f2711c;
52 | }
53 |
--------------------------------------------------------------------------------
/app/utils/fileObject.js:
--------------------------------------------------------------------------------
1 | // import Dexie from 'dexie';
2 | import log from 'electron-log';
3 | import imageDB from './db';
4 |
5 | const FileObject = imageDB.frameList.defineClass({
6 | frameId: String,
7 | lastModified: Number,
8 | lastModifiedDate: String,
9 | name: String,
10 | path: String,
11 | size: Number,
12 | frameNumber: Number,
13 | type: String,
14 | webkitRelativePath: String,
15 | base64: String,
16 | data: Blob
17 | });
18 |
19 | FileObject.prototype.objectUrl = '';
20 |
21 | FileObject.prototype.getObjectUrl2 = () => {
22 | console.log(this);
23 | const objectUrl = window.URL.createObjectURL(this.data);
24 | return objectUrl;
25 | };
26 |
27 | FileObject.prototype.getObjectUrl = () => {
28 | if (this.objectUrl !== '' && !this.disposed) {
29 | return this.objectUrl;
30 | }
31 | if (!this.disposed) {
32 | this.objectUrl = window.URL.createObjectURL(this.data);
33 | return this.objectUrl;
34 | }
35 | log.warn('File disposed!');
36 | throw 'File disposed!';
37 | };
38 |
39 | FileObject.prototype.revokeObjectURL = () => {
40 | URL.revokeObjectURL(this.objectUrl);
41 | this.objectUrl = '';
42 | };
43 |
44 | FileObject.prototype.disposed = false;
45 |
46 | FileObject.prototype.disposeData = () => {
47 | URL.revokeObjectURL(this.objectUrl);
48 | this.objectUrl = '';
49 | this.data = null;
50 | this.disposed = true;
51 | };
52 |
53 | export default FileObject;
54 |
--------------------------------------------------------------------------------
/app/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MoviePrint
6 |
17 |
18 |
19 |
20 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/app/worker.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MoviePrint_worker
6 |
17 |
18 |
19 |
20 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/app/components/Timeline.css:
--------------------------------------------------------------------------------
1 |
2 | .container {
3 |
4 | }
5 |
6 | .timelineWrapperSelection {
7 | width: 100%;
8 | position: relative;
9 | /* background-color: #421400; */
10 | background-color: rgba(142, 44, 2, 1.0);
11 | height: 28px;
12 | margin-bottom: 4px;
13 | cursor: col-resize;
14 | }
15 |
16 | .timelineWrapper {
17 | width: 100%;
18 | position: relative;
19 | /* background-color: #421400; */
20 | height: 16px;
21 | /* cursor: col-resize; */
22 | }
23 |
24 | .timelinePlayheadSelection {
25 | position: absolute;
26 | /* bottom: 0; */
27 | /* top: 0; */
28 | width: 2px;
29 | background-color: rgba(255, 80, 6, 1);
30 | height: 24px;
31 | margin-top: 2px;
32 | }
33 |
34 | .timelinePlayhead {
35 | position: absolute;
36 | /* bottom: 0; */
37 | /* top: 0; */
38 | width: 2px;
39 | background-color: rgba(255, 80, 6, 1);
40 | height: 16px;
41 | margin-top: 0px;
42 | }
43 |
44 | .timelineCutSelection {
45 | position: absolute;
46 | background-color: rgba(255, 80, 6, 0.4);
47 | height: 16px;
48 | cursor: col-resize;
49 | }
50 |
51 | .timelineCut {
52 | position: absolute;
53 | background-color: #421400;
54 | height: 16px;
55 | cursor: col-resize;
56 | }
57 |
58 | .timelineWrapper .currentTime {
59 | background-color: #FF5006;
60 | z-index: 2;
61 | }
62 | .timelineWrapper .cutStartTime {
63 | background-color: rgba(0, 0, 0, 0.3);
64 | border-left: 1px solid black;
65 | border-right: 1px solid black;
66 | z-index: 1;
67 | }
68 |
--------------------------------------------------------------------------------
/app/worker_opencv.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MoviePrint_opencvWorker
6 |
17 |
18 |
19 |
20 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/app/app.global.css:
--------------------------------------------------------------------------------
1 | /*
2 | * @NOTE: Prepend a `~` to css file paths that are in your node_modules
3 | * See https://github.com/webpack-contrib/sass-loader#imports
4 | */
5 |
6 | @import '~semantic-ui-css/semantic.min.css';
7 | @import '~react-toastify/dist/ReactToastify.min.css';
8 | @import '~typeface-open-sans';
9 | @import '~typeface-roboto-condensed';
10 | @import '~typeface-ubuntu';
11 | @import '~rc-slider/assets/index.css';
12 | @font-face {
13 | font-family: Franchise;
14 | src: url("./dist/Franchise-Bold.woff");
15 | }
16 |
17 | .rc-slider-tooltip {
18 | z-index: 1000;
19 | }
20 |
21 | .rc-slider-disabled {
22 | opacity: 0.3;
23 | background-color: transparent;
24 | }
25 |
26 | body {
27 | color: white;
28 | }
29 |
30 | label {
31 | color: white;
32 | }
33 |
34 | mark {
35 | margin-left: 4px;
36 | margin-right: 4px;
37 | background-color: rgba(255, 80, 6, 0.9);
38 | color: white;
39 | white-space: nowrap;
40 | }
41 |
42 | mark::before {
43 | content: '\00a0\00a0';
44 | }
45 |
46 | mark::after {
47 | content: '\00a0\00a0';
48 | }
49 |
50 | /* width */
51 | ::-webkit-scrollbar {
52 | width: 6px !important;
53 | }
54 |
55 | /* Track */
56 | ::-webkit-scrollbar-track {
57 | background: #1e1e1e !important;
58 | }
59 |
60 |
61 | /* Handle */
62 | ::-webkit-scrollbar-thumb {
63 | background: #1e1e1e !important;
64 | }
65 |
66 | /* Handle on hover */
67 | ::-webkit-scrollbar-thumb:hover {
68 | background: #2c2c2c !important;
69 | }
70 |
71 | h6 {
72 | font-size: 0.7rem;
73 | }
74 |
--------------------------------------------------------------------------------
/app/worker_database.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MoviePrint_databaseWorker
6 |
17 |
18 |
19 |
20 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/test/components/__snapshots__/Counter.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Counter component should match exact snapshot 1`] = `
4 |
5 |
6 |
19 |
23 | 1
24 |
25 |
28 |
38 |
48 |
56 |
64 |
65 |
66 |
67 | `;
68 |
--------------------------------------------------------------------------------
/test/actions/counter.spec.js:
--------------------------------------------------------------------------------
1 | import { spy } from 'sinon';
2 | import * as actions from '../../app/actions/counter';
3 |
4 | describe('actions', () => {
5 | it('should increment should create increment action', () => {
6 | expect(actions.increment()).toMatchSnapshot();
7 | });
8 |
9 | it('should decrement should create decrement action', () => {
10 | expect(actions.decrement()).toMatchSnapshot();
11 | });
12 |
13 | it('should incrementIfOdd should create increment action', () => {
14 | const fn = actions.incrementIfOdd();
15 | expect(fn).toBeInstanceOf(Function);
16 | const dispatch = spy();
17 | const getState = () => ({ counter: 1 });
18 | fn(dispatch, getState);
19 | expect(dispatch.calledWith({ type: actions.INCREMENT_COUNTER })).toBe(true);
20 | });
21 |
22 | it('should incrementIfOdd shouldnt create increment action if counter is even', () => {
23 | const fn = actions.incrementIfOdd();
24 | const dispatch = spy();
25 | const getState = () => ({ counter: 2 });
26 | fn(dispatch, getState);
27 | expect(dispatch.called).toBe(false);
28 | });
29 |
30 | // There's no nice way to test this at the moment...
31 | it('should incrementAsync', done => {
32 | const fn = actions.incrementAsync(1);
33 | expect(fn).toBeInstanceOf(Function);
34 | const dispatch = spy();
35 | fn(dispatch);
36 | setTimeout(() => {
37 | expect(dispatch.calledWith({ type: actions.INCREMENT_COUNTER })).toBe(
38 | true
39 | );
40 | done();
41 | }, 5);
42 | });
43 | });
44 |
--------------------------------------------------------------------------------
/app/img/icon-grid.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/app/utils/db.js:
--------------------------------------------------------------------------------
1 | import Dexie from 'dexie';
2 | import log from 'electron-log';
3 | // import FileObject from './fileObject';
4 |
5 | // Force debug mode to get async stacks from exceptions.
6 | if (process.env.NODE_ENV === 'production') {
7 | Dexie.debug = false;
8 | } else {
9 | Dexie.debug = true; // In production, set to false to increase performance a little.
10 | }
11 | const imageDB = new Dexie('ImageDatabase');
12 | imageDB.version(1).stores({
13 | frameList: '&frameId, fileId, frameNumber, [fileId+frameNumber]',
14 | // fileScanList: '&fileId',
15 | });
16 |
17 | const FileObject = imageDB.frameList.defineClass({
18 | frameId: String,
19 | fileId: String,
20 | frameNumber: Number,
21 | data: Blob
22 | });
23 |
24 | FileObject.prototype.objectUrl = '';
25 |
26 | FileObject.prototype.getObjectUrl2 = () => {
27 | console.log(this);
28 | const objectUrl = window.URL.createObjectURL(this.data);
29 | return objectUrl;
30 | };
31 |
32 | FileObject.prototype.getObjectUrl = () => {
33 | if (this.objectUrl !== '' && !this.disposed) {
34 | return this.objectUrl;
35 | }
36 | if (!this.disposed) {
37 | this.objectUrl = window.URL.createObjectURL(this.data);
38 | return this.objectUrl;
39 | }
40 | log.warn('File disposed!');
41 | throw 'File disposed!';
42 | };
43 |
44 | FileObject.prototype.revokeObjectURL = () => {
45 | URL.revokeObjectURL(this.objectUrl);
46 | this.objectUrl = '';
47 | };
48 |
49 | FileObject.prototype.disposed = false;
50 |
51 | FileObject.prototype.disposeData = () => {
52 | URL.revokeObjectURL(this.objectUrl);
53 | this.objectUrl = '';
54 | this.data = null;
55 | this.disposed = true;
56 | };
57 |
58 | export default imageDB;
59 |
--------------------------------------------------------------------------------
/app/img/icon-unhide.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/app/containers/Settings.css:
--------------------------------------------------------------------------------
1 |
2 | .slider {
3 | width: 90%;
4 | margin: 10px;
5 | background-color: #3e3e3e;
6 | }
7 |
8 | .label {
9 | color: white !important;
10 | }
11 |
12 | .subCheckbox {
13 | margin-left: 30px;
14 | }
15 |
16 | .input {
17 | height: 34px;
18 | width: 80px;
19 | }
20 |
21 | .edit {
22 | padding-left: 4px;
23 | }
24 |
25 | .colorPickerColor {
26 | position: relative;
27 | width: 190px;
28 | height: 32px;
29 | border-radius: 2px;
30 | }
31 |
32 | .colorPickerSwatch {
33 | padding: 2px;
34 | background: #fff;
35 | border-radius: 1px;
36 | box-shadow: 0 0 0 1px rgba(0,0,0,.1);
37 | display: inline-block;
38 | cursor: pointer;
39 | }
40 |
41 | .colorPickerPopover {
42 | position: absolute;
43 | z-index: 2;
44 | }
45 |
46 | .colorPickerCover {
47 | position: fixed;
48 | top: 0px;
49 | right: 0px;
50 | bottom: 0px;
51 | left: 0px;
52 | }
53 |
54 | .colorPickerText {
55 | padding: 6px;
56 | font-size: 20px;
57 | font-family: 'Open sans';
58 | text-align: center;
59 | }
60 |
61 | .smallText {
62 | margin-top: 8px;
63 | font-size: 0.9rem;
64 | }
65 |
66 | .previewCustomName {
67 | background-color: rgba(0,0,0,0.8) !important;
68 | color: rgba(255,255,255,0.5) !important;
69 | font-style: italic !important;
70 | font-weight: 200 !important;
71 | letter-spacing: 0.3px;
72 | line-height: 1.2rem !important;
73 | }
74 |
75 | .smallDivider {
76 | margin: 0.4rem 0 !important;
77 | }
78 |
79 | .attributeButton {
80 | margin: 1px !important;
81 | }
82 |
83 | .accordion > :global(.active) {
84 | background-color: rgba(255,255,255,0.05) !important;
85 | }
86 |
87 | .accordion :global(.title) {
88 | border-width: 1px 0 0 0 !important;
89 | border-style: solid !important;
90 | border-color: rgba(255,255,255,0.05) !important;
91 | }
92 |
--------------------------------------------------------------------------------
/app/img/icon-sort.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/app/components/Menu.css:
--------------------------------------------------------------------------------
1 | .container {
2 | z-index: 200;
3 | background-color: rgba(30,30,30,1);
4 | }
5 |
6 | .menu {
7 | z-index: 200;
8 | /* background-color: #1e1e1e; */
9 | background-color: rgba(30,30,30,1) !important;
10 | }
11 |
12 | .saveButton {
13 | background-color: rgba(255, 80, 6, 1.0) !important;
14 | }
15 |
16 | .saveButton:hover {
17 | background-color: rgba(255, 80, 6, 0.5) !important;
18 | }
19 |
20 | .saveButton :global(.ui.loader) {
21 | margin-right: 4.5px !important;
22 | }
23 |
24 | .saveButton :global(.ui.mini.loader) {
25 | height: 0.8rem !important;
26 | }
27 |
28 | .saveButton :global(.ui.loader:after) {
29 | border-color: #ffffff transparent transparent !important;
30 | }
31 |
32 | @keyframes rotating {
33 | from {
34 | -ms-transform: rotate(0deg);
35 | -moz-transform: rotate(0deg);
36 | -webkit-transform: rotate(0deg);
37 | -o-transform: rotate(0deg);
38 | transform: rotate(0deg);
39 | }
40 | to {
41 | -ms-transform: rotate(360deg);
42 | -moz-transform: rotate(360deg);
43 | -webkit-transform: rotate(360deg);
44 | -o-transform: rotate(360deg);
45 | transform: rotate(360deg);
46 | }
47 | }
48 |
49 | @keyframes loader{
50 | from {
51 | -webkit-transform:rotate(0);
52 | transform:rotate(0)
53 | } to {
54 | -webkit-transform:rotate(360deg);
55 | transform:rotate(360deg)
56 | }}
57 |
58 | .spinner {
59 | animation:loader .6s linear;
60 | animation-iteration-count:infinite;
61 | border-radius:500rem;
62 | border-color:#767676 transparent transparent;
63 | border-style:solid;
64 | width: 4px;
65 | border-width:.2em;
66 | box-shadow:0 0 0 1px transparent
67 | }
68 |
69 | /* .headerItem {
70 | float: left;
71 | opacity: 0.5;
72 | }
73 |
74 | .headerItem:hover {
75 | cursor: pointer;
76 | opacity: 1;
77 | }
78 |
79 | .subHeader {
80 | float: left;
81 | opacity: 0.5;
82 | font-family: 'Open sans';
83 | font-size: 12px;
84 | color: #eee;
85 | padding: 12px;
86 | } */
87 |
--------------------------------------------------------------------------------
/app/components/ErrorBoundary.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Button, Divider } from 'semantic-ui-react';
3 | import log from 'electron-log';
4 | import styles from './ErrorBoundary.css';
5 |
6 | const { ipcRenderer } = require('electron');
7 |
8 | class ErrorBoundary extends Component {
9 | constructor(props) {
10 | super(props);
11 | this.state = { hasError: false };
12 |
13 | this.onReloadClick = this.onReloadClick.bind(this);
14 | this.onResetClick = this.onResetClick.bind(this);
15 | }
16 |
17 | componentDidCatch(error, info) {
18 | // Display fallback UI
19 | this.setState({ hasError: true });
20 | // You can also log the error to an error reporting service
21 | log.error(error);
22 | log.warn(info);
23 | }
24 |
25 | onReloadClick() {
26 | log.info('reloadclick');
27 | ipcRenderer.send('reload-application');
28 | }
29 |
30 | onResetClick() {
31 | log.info('resetclick');
32 | ipcRenderer.send('reset-application');
33 | }
34 |
35 | render() {
36 | if (this.state.hasError) {
37 | // You can render any custom fallback UI
38 | return (
39 |
40 |
41 | SOMETHING WENT WRONG
42 | {/*
*/}
43 |
50 |
54 |
55 |
60 |
61 |
62 |
63 | );
64 | }
65 | return this.props.children;
66 | }
67 | }
68 |
69 | export default ErrorBoundary;
70 |
--------------------------------------------------------------------------------
/app/img/icon-hide.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/configs/webpack.config.renderer.dev.dll.babel.js:
--------------------------------------------------------------------------------
1 | /* eslint global-require: off, import/no-dynamic-require: off */
2 |
3 | /**
4 | * Builds the DLL for development electron renderer process
5 | */
6 |
7 | import webpack from 'webpack';
8 | import path from 'path';
9 | import merge from 'webpack-merge';
10 | import baseConfig from './webpack.config.base';
11 | import { dependencies } from '../package.json';
12 | import CheckNodeEnv from '../internals/scripts/CheckNodeEnv';
13 |
14 | CheckNodeEnv('development');
15 |
16 | const dist = path.join(__dirname, '..', 'dll');
17 |
18 | export default merge.smart(baseConfig, {
19 | context: path.join(__dirname, '..'),
20 |
21 | devtool: 'eval',
22 |
23 | mode: 'development',
24 |
25 | target: 'electron-renderer',
26 |
27 | externals: ['fsevents', 'crypto-browserify'],
28 |
29 | /**
30 | * Use `module` from `webpack.config.renderer.dev.babel.js`
31 | */
32 | module: require('./webpack.config.renderer.dev.babel').default.module,
33 |
34 | entry: {
35 | renderer: Object.keys(dependencies || {})
36 | },
37 |
38 | output: {
39 | library: 'renderer',
40 | path: dist,
41 | filename: '[name].dev.dll.js',
42 | libraryTarget: 'var'
43 | },
44 |
45 | plugins: [
46 | new webpack.DllPlugin({
47 | path: path.join(dist, '[name].json'),
48 | name: '[name]'
49 | }),
50 |
51 | /**
52 | * Create global constants which can be configured at compile time.
53 | *
54 | * Useful for allowing different behaviour between development builds and
55 | * release builds
56 | *
57 | * NODE_ENV should be production so that modules do not perform certain
58 | * development checks
59 | */
60 | new webpack.EnvironmentPlugin({
61 | NODE_ENV: 'development'
62 | }),
63 |
64 | new webpack.LoaderOptionsPlugin({
65 | debug: true,
66 | options: {
67 | context: path.join(__dirname, '..', 'app'),
68 | output: {
69 | path: path.join(__dirname, '..', 'dll')
70 | }
71 | }
72 | })
73 | ]
74 | });
75 |
--------------------------------------------------------------------------------
/app/img/icon-frame-info.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/configs/webpack.config.main.prod.babel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Webpack config for production electron main process
3 | */
4 |
5 | import path from 'path';
6 | import webpack from 'webpack';
7 | import merge from 'webpack-merge';
8 | import TerserPlugin from 'terser-webpack-plugin';
9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
10 | import baseConfig from './webpack.config.base';
11 | import CheckNodeEnv from '../internals/scripts/CheckNodeEnv';
12 |
13 | CheckNodeEnv('production');
14 |
15 | export default merge.smart(baseConfig, {
16 | devtool: 'source-map',
17 |
18 | mode: 'production',
19 |
20 | target: 'electron-main',
21 |
22 | entry: './app/main.dev',
23 |
24 | output: {
25 | path: path.join(__dirname, '..'),
26 | filename: './app/main.prod.js'
27 | },
28 |
29 | optimization: {
30 | minimizer: process.env.E2E_BUILD
31 | ? []
32 | : [
33 | new TerserPlugin({
34 | parallel: true,
35 | sourceMap: true,
36 | cache: true
37 | })
38 | ]
39 | },
40 |
41 | plugins: [
42 | new BundleAnalyzerPlugin({
43 | analyzerMode:
44 | process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled',
45 | openAnalyzer: process.env.OPEN_ANALYZER === 'true'
46 | }),
47 |
48 | /**
49 | * Create global constants which can be configured at compile time.
50 | *
51 | * Useful for allowing different behaviour between development builds and
52 | * release builds
53 | *
54 | * NODE_ENV should be production so that modules do not perform certain
55 | * development checks
56 | */
57 | new webpack.EnvironmentPlugin({
58 | NODE_ENV: 'production',
59 | DEBUG_PROD: false,
60 | START_MINIMIZED: false
61 | })
62 | ],
63 |
64 | /**
65 | * Disables webpack processing of __dirname and __filename.
66 | * If you run the bundle in node.js it falls back to these values of node.js.
67 | * https://github.com/webpack/webpack/issues/2010
68 | */
69 | node: {
70 | __dirname: false,
71 | __filename: false
72 | }
73 | });
74 |
--------------------------------------------------------------------------------
/test/containers/CounterPage.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Enzyme, { mount } from 'enzyme';
3 | import Adapter from 'enzyme-adapter-react-16';
4 | import { Provider } from 'react-redux';
5 | import { createBrowserHistory } from 'history';
6 | import { ConnectedRouter } from 'react-router-redux';
7 | import CounterPage from '../../app/containers/CounterPage';
8 | import { configureStore } from '../../app/store/configureStore';
9 |
10 | Enzyme.configure({ adapter: new Adapter() });
11 |
12 | function setup(initialState) {
13 | const store = configureStore(initialState);
14 | const history = createBrowserHistory();
15 | const provider = (
16 |
17 |
18 |
19 |
20 |
21 | );
22 | const app = mount(provider);
23 | return {
24 | app,
25 | buttons: app.find('button'),
26 | p: app.find('.counter')
27 | };
28 | }
29 |
30 | describe('containers', () => {
31 | describe('App', () => {
32 | it('should display initial count', () => {
33 | const { p } = setup();
34 | expect(p.text()).toMatch(/^0$/);
35 | });
36 |
37 | it('should display updated count after increment button click', () => {
38 | const { buttons, p } = setup();
39 | buttons.at(0).simulate('click');
40 | expect(p.text()).toMatch(/^1$/);
41 | });
42 |
43 | it('should display updated count after decrement button click', () => {
44 | const { buttons, p } = setup();
45 | buttons.at(1).simulate('click');
46 | expect(p.text()).toMatch(/^-1$/);
47 | });
48 |
49 | it('shouldnt change if even and if odd button clicked', () => {
50 | const { buttons, p } = setup();
51 | buttons.at(2).simulate('click');
52 | expect(p.text()).toMatch(/^0$/);
53 | });
54 |
55 | it('should change if odd and if odd button clicked', () => {
56 | const { buttons, p } = setup({ counter: 1 });
57 | buttons.at(2).simulate('click');
58 | expect(p.text()).toMatch(/^2$/);
59 | });
60 | });
61 | });
62 |
--------------------------------------------------------------------------------
/app/img/icon-5x5.svg:
--------------------------------------------------------------------------------
1 |
28 |
--------------------------------------------------------------------------------
/app/img/icon-frame-info-enabled.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/app/utils/openCVProperties.js:
--------------------------------------------------------------------------------
1 | export const VideoCaptureProperties = Object.freeze({
2 | CAP_PROP_POS_MSEC: 0,
3 | CAP_PROP_POS_FRAMES: 1,
4 | CAP_PROP_POS_AVI_RATIO: 2,
5 | CAP_PROP_FRAME_WIDTH: 3,
6 | CAP_PROP_FRAME_HEIGHT: 4,
7 | CAP_PROP_FPS: 5,
8 | CAP_PROP_FOURCC: 6,
9 | CAP_PROP_FRAME_COUNT: 7,
10 | CAP_PROP_FORMAT: 8,
11 | CAP_PROP_MODE: 9,
12 | CAP_PROP_BRIGHTNESS: 10,
13 | CAP_PROP_CONTRAST: 11,
14 | CAP_PROP_SATURATION: 12,
15 | CAP_PROP_HUE: 13,
16 | CAP_PROP_GAIN: 14,
17 | CAP_PROP_EXPOSURE: 15,
18 | CAP_PROP_CONVERT_RGB: 16,
19 | CAP_PROP_WHITE_BALANCE_BLUE_U: 17,
20 | CAP_PROP_RECTIFICATION: 18,
21 | CAP_PROP_MONOCHROME: 19,
22 | CAP_PROP_SHARPNESS: 20,
23 | CAP_PROP_AUTO_EXPOSURE: 21,
24 | CAP_PROP_GAMMA: 22,
25 | CAP_PROP_TEMPERATURE: 23,
26 | CAP_PROP_TRIGGER: 24,
27 | CAP_PROP_TRIGGER_DELAY: 25,
28 | CAP_PROP_WHITE_BALANCE_RED_V: 26,
29 | CAP_PROP_ZOOM: 27,
30 | CAP_PROP_FOCUS: 28,
31 | CAP_PROP_GUID: 29,
32 | CAP_PROP_ISO_SPEED: 30,
33 | CAP_PROP_BACKLIGHT: 32,
34 | CAP_PROP_PAN: 33,
35 | CAP_PROP_TILT: 34,
36 | CAP_PROP_ROLL: 35,
37 | CAP_PROP_IRIS: 36,
38 | CAP_PROP_SETTINGS: 37,
39 | CAP_PROP_BUFFERSIZE: 38,
40 | CAP_PROP_AUTOFOCUS: 39,
41 | });
42 |
43 | export const ImwriteFlags = Object.freeze({
44 | IMWRITE_JPEG_QUALITY: 1,
45 | IMWRITE_JPEG_PROGRESSIVE: 2,
46 | IMWRITE_JPEG_OPTIMIZE: 3,
47 | IMWRITE_JPEG_RST_INTERVAL: 4,
48 | IMWRITE_JPEG_LUMA_QUALITY: 5,
49 | IMWRITE_JPEG_CHROMA_QUALITY: 6,
50 | IMWRITE_PNG_COMPRESSION: 16,
51 | IMWRITE_PNG_STRATEGY: 17,
52 | IMWRITE_PNG_BILEVEL: 18,
53 | IMWRITE_PXM_BINARY: 32,
54 | // IMWRITE_EXR_TYPE: (3 << 4) + 0,
55 | IMWRITE_WEBP_QUALITY: 64,
56 | IMWRITE_PAM_TUPLETYPE: 128,
57 | IMWRITE_TIFF_RESUNIT: 256,
58 | IMWRITE_TIFF_XDPI: 257,
59 | IMWRITE_TIFF_YDPI: 258,
60 | IMWRITE_TIFF_COMPRESSION: 259,
61 | });
62 |
63 | export const RotateFlags = Object.freeze({
64 | ROTATE_90_CLOCKWISE: 0, // Rotate 90 degrees clockwise
65 | ROTATE_180: 1, // Rotate 180 degrees clockwise
66 | ROTATE_90_COUNTERCLOCKWISE: 2, // Rotate 270 degrees clockwise
67 | NO_ROTATION: 3, // No rotation - Artifical no rotation flag for opencv!
68 | });
69 |
--------------------------------------------------------------------------------
/test/components/Counter.spec.js:
--------------------------------------------------------------------------------
1 | import { spy } from 'sinon';
2 | import React from 'react';
3 | import Enzyme, { shallow } from 'enzyme';
4 | import Adapter from 'enzyme-adapter-react-16';
5 | import { BrowserRouter as Router } from 'react-router-dom';
6 | import renderer from 'react-test-renderer';
7 | import Counter from '../../app/components/Counter';
8 |
9 | Enzyme.configure({ adapter: new Adapter() });
10 |
11 | function setup() {
12 | const actions = {
13 | increment: spy(),
14 | incrementIfOdd: spy(),
15 | incrementAsync: spy(),
16 | decrement: spy()
17 | };
18 | const component = shallow();
19 | return {
20 | component,
21 | actions,
22 | buttons: component.find('button'),
23 | p: component.find('.counter')
24 | };
25 | }
26 |
27 | describe('Counter component', () => {
28 | it('should should display count', () => {
29 | const { p } = setup();
30 | expect(p.text()).toMatch(/^1$/);
31 | });
32 |
33 | it('should first button should call increment', () => {
34 | const { buttons, actions } = setup();
35 | buttons.at(0).simulate('click');
36 | expect(actions.increment.called).toBe(true);
37 | });
38 |
39 | it('should match exact snapshot', () => {
40 | const { actions } = setup();
41 | const counter = (
42 |
43 |
44 |
45 |
46 |
47 | );
48 | const tree = renderer.create(counter).toJSON();
49 |
50 | expect(tree).toMatchSnapshot();
51 | });
52 |
53 | it('should second button should call decrement', () => {
54 | const { buttons, actions } = setup();
55 | buttons.at(1).simulate('click');
56 | expect(actions.decrement.called).toBe(true);
57 | });
58 |
59 | it('should third button should call incrementIfOdd', () => {
60 | const { buttons, actions } = setup();
61 | buttons.at(2).simulate('click');
62 | expect(actions.incrementIfOdd.called).toBe(true);
63 | });
64 |
65 | it('should fourth button should call incrementAsync', () => {
66 | const { buttons, actions } = setup();
67 | buttons.at(3).simulate('click');
68 | expect(actions.incrementAsync.called).toBe(true);
69 | });
70 | });
71 |
--------------------------------------------------------------------------------
/app/img/icon-show-face-rect.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/app/img/icon-show-face-rect-enabled.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/app/utils/saveThumb.js:
--------------------------------------------------------------------------------
1 | import pathR from 'path';
2 | import log from 'electron-log';
3 | import { DEFAULT_THUMB_JPG_QUALITY, DEFAULT_THUMB_FORMAT } from './constants';
4 | import { ensureDirectoryExistence, getFilePathObject } from './utils';
5 | import { getBase64Object } from './utilsForOpencv';
6 |
7 | const { ipcRenderer } = require('electron');
8 | const { app } = require('electron').remote;
9 |
10 | const saveThumb = (
11 | filePath,
12 | fileUseRatio,
13 | movieFileName,
14 | sheetName,
15 | frameNumber,
16 | fileNameTemplate,
17 | frameId,
18 | transformObject,
19 | saveToFolder = '',
20 | overwrite = false,
21 | defaultThumbFormat = DEFAULT_THUMB_FORMAT,
22 | defaultThumbJpgQuality = DEFAULT_THUMB_JPG_QUALITY,
23 | fps = 25,
24 | ) => {
25 | // save thumbs in folder with the same name as moviePrint
26 | let newFolderName = app.getPath('desktop');
27 | if (saveToFolder) {
28 | newFolderName = saveToFolder;
29 | ensureDirectoryExistence(newFolderName);
30 | }
31 |
32 | const frameSize = 0; // save frame in original size
33 |
34 | const newFilePathObject = getFilePathObject(
35 | movieFileName,
36 | sheetName,
37 | frameNumber,
38 | fileNameTemplate,
39 | defaultThumbFormat,
40 | newFolderName,
41 | overwrite,
42 | fps,
43 | );
44 | const newFilePathAndName = pathR.join(newFilePathObject.dir, newFilePathObject.base);
45 |
46 | const thumbFormatObject = {
47 | defaultThumbFormat,
48 | defaultThumbJpgQuality,
49 | };
50 |
51 | const base64Object = getBase64Object(
52 | filePath,
53 | fileUseRatio,
54 | [
55 | {
56 | frameId,
57 | frameNumber,
58 | },
59 | ],
60 | frameSize,
61 | transformObject,
62 | thumbFormatObject,
63 | true,
64 | );
65 | const base64 = base64Object[frameId];
66 |
67 | if (base64 === '') {
68 | ipcRenderer.send('message-from-workerWindow-to-mainWindow', 'received-saved-file-error', 'Frame is empty');
69 | } else {
70 | const buf = Buffer.from(base64, 'base64');
71 |
72 | ipcRenderer.send('send-save-file', frameId, newFilePathAndName, buf);
73 | log.debug(`Saving ${JSON.stringify({ newFilePathAndName, size: buf.length })}`);
74 | }
75 | };
76 |
77 | export default saveThumb;
78 |
--------------------------------------------------------------------------------
/configs/webpack.config.base.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Base webpack config used across other specific configs
3 | */
4 |
5 | import path from 'path';
6 | import webpack from 'webpack';
7 | // import fs from 'fs';
8 | import { dependencies as externals } from '../app/package.json';
9 | // import { dependencies as possibleExternals } from '../package.json';
10 |
11 | // // Find all the dependencies without a `main` property and add them as webpack externals
12 | // function filterDepWithoutEntryPoints(dep: string): boolean {
13 | // // Return true if we want to add a dependency to externals
14 | // try {
15 | // // If the root of the dependency has an index.js, return true
16 | // if (fs.existsSync(path.join(__dirname, '..', `node_modules/${dep}/index.js`))) {
17 | // return false;
18 | // }
19 | // const pgkString = fs
20 | // .readFileSync(path.join(__dirname, '..', `node_modules/${dep}/package.json`))
21 | // .toString();
22 | // const pkg = JSON.parse(pgkString);
23 | // const fields = ['main', 'module', 'jsnext:main', 'browser'];
24 | // return !fields.some(field => field in pkg);
25 | // } catch (e) {
26 | // console.log(e);
27 | // return true;
28 | // }
29 | // }
30 |
31 | export default {
32 | externals: [
33 | ...Object.keys(externals || {})
34 | // ...Object.keys(possibleExternals || {}).filter(filterDepWithoutEntryPoints)
35 | ],
36 |
37 | module: {
38 | rules: [
39 | {
40 | test: /\.jsx?$/,
41 | exclude: /node_modules/,
42 | use: {
43 | loader: 'babel-loader',
44 | options: {
45 | cacheDirectory: true
46 | }
47 | }
48 | }
49 | ]
50 | },
51 |
52 | output: {
53 | path: path.join(__dirname, '..', 'app'),
54 | // https://github.com/webpack/webpack/issues/1114
55 | libraryTarget: 'commonjs2'
56 | },
57 |
58 | /**
59 | * Determine the array of extensions that should be used to resolve modules.
60 | */
61 | resolve: {
62 | extensions: ['.js', '.jsx', '.json'],
63 | modules: [path.join(__dirname, '..', 'app'), 'node_modules']
64 | },
65 |
66 | plugins: [
67 | new webpack.EnvironmentPlugin({
68 | NODE_ENV: 'production'
69 | }),
70 |
71 | new webpack.NamedModulesPlugin()
72 | ]
73 | };
74 |
--------------------------------------------------------------------------------
/internals/scripts/CheckNativeDep.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import fs from 'fs';
3 | import chalk from 'chalk';
4 | import { execSync } from 'child_process';
5 | import { dependencies } from '../../package.json';
6 |
7 | (() => {
8 | if (!dependencies) return;
9 |
10 | const dependenciesKeys = Object.keys(dependencies);
11 | const nativeDeps = fs
12 | .readdirSync('node_modules')
13 | .filter(folder => fs.existsSync(`node_modules/${folder}/binding.gyp`));
14 |
15 | try {
16 | // Find the reason for why the dependency is installed. If it is installed
17 | // because of a devDependency then that is okay. Warn when it is installed
18 | // because of a dependency
19 | const dependenciesObject = JSON.parse(
20 | execSync(`npm ls ${nativeDeps.join(' ')} --json`).toString()
21 | );
22 | const rootDependencies = Object.keys(dependenciesObject.dependencies);
23 | const filteredRootDependencies = rootDependencies.filter(rootDependency =>
24 | dependenciesKeys.includes(rootDependency)
25 | );
26 |
27 | if (filteredRootDependencies.length > 0) {
28 | const plural = filteredRootDependencies.length > 1;
29 | console.log(`
30 |
31 | ${chalk.whiteBright.bgYellow.bold(
32 | 'Webpack does not work with native dependencies.'
33 | )}
34 | ${chalk.bold(filteredRootDependencies.join(', '))} ${
35 | plural ? 'are native dependencies' : 'is a native dependency'
36 | } and should be installed inside of the "./app" folder.
37 |
38 |
39 | First uninstall the packages from "./package.json":
40 | ${chalk.whiteBright.bgGreen.bold('npm uninstall your-package')}
41 |
42 | ${chalk.bold(
43 | 'Then, instead of installing the package to the root "./package.json":'
44 | )}
45 | ${chalk.whiteBright.bgRed.bold('npm install your-package --save')}
46 |
47 | ${chalk.bold('Install the package to "./app/package.json"')}
48 | ${chalk.whiteBright.bgGreen.bold('cd ./app && npm install your-package --save')}
49 |
50 |
51 | Read more about native dependencies at:
52 | ${chalk.bold(
53 | 'https://github.com/chentsulin/electron-react-boilerplate/wiki/Module-Structure----Two-package.json-Structure'
54 | )}
55 |
56 |
57 | `);
58 |
59 | process.exit(1);
60 | }
61 | } catch (e) {
62 | console.log('Native dependencies could not be checked');
63 | }
64 | })();
65 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | /* eslint global-require: off */
2 |
3 | const developmentEnvironments = ['development', 'test'];
4 |
5 | const developmentPlugins = [require('react-hot-loader/babel')];
6 |
7 | const productionPlugins = [
8 | require('babel-plugin-dev-expression'),
9 |
10 | // babel-preset-react-optimize
11 | require('@babel/plugin-transform-react-constant-elements'),
12 | require('@babel/plugin-transform-react-inline-elements'),
13 | require('babel-plugin-transform-react-remove-prop-types')
14 | ];
15 |
16 | module.exports = api => {
17 | // see docs about api at https://babeljs.io/docs/en/config-files#apicache
18 |
19 | const development = api.env(developmentEnvironments);
20 |
21 | return {
22 | presets: [
23 | [
24 | require('@babel/preset-env'),
25 | {
26 | targets: { electron: require('electron/package.json').version }
27 | }
28 | ],
29 | require('@babel/preset-flow'),
30 | [require('@babel/preset-react'), { development }]
31 | ],
32 | plugins: [
33 | // Stage 0
34 | require('@babel/plugin-proposal-function-bind'),
35 |
36 | // Stage 1
37 | require('@babel/plugin-proposal-export-default-from'),
38 | require('@babel/plugin-proposal-logical-assignment-operators'),
39 | [require('@babel/plugin-proposal-optional-chaining'), { loose: false }],
40 | [
41 | require('@babel/plugin-proposal-pipeline-operator'),
42 | { proposal: 'minimal' }
43 | ],
44 | [
45 | require('@babel/plugin-proposal-nullish-coalescing-operator'),
46 | { loose: false }
47 | ],
48 | require('@babel/plugin-proposal-do-expressions'),
49 |
50 | // Stage 2
51 | [require('@babel/plugin-proposal-decorators'), { legacy: true }],
52 | require('@babel/plugin-proposal-function-sent'),
53 | require('@babel/plugin-proposal-export-namespace-from'),
54 | require('@babel/plugin-proposal-numeric-separator'),
55 | require('@babel/plugin-proposal-throw-expressions'),
56 |
57 | // Stage 3
58 | require('@babel/plugin-syntax-dynamic-import'),
59 | require('@babel/plugin-syntax-import-meta'),
60 | [require('@babel/plugin-proposal-class-properties'), { loose: true }],
61 | require('@babel/plugin-proposal-json-strings'),
62 |
63 | ...(development ? developmentPlugins : productionPlugins)
64 | ]
65 | };
66 | };
67 |
--------------------------------------------------------------------------------
/app/img/icon-add-face.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/app/utils/utilsForMain.js:
--------------------------------------------------------------------------------
1 | import log from 'electron-log';
2 | import path from 'path';
3 |
4 | export const clearCache = win => {
5 | log.debug('clearCache');
6 | win.webContents.session
7 | .getCacheSize()
8 | .then(cacheSizeBefore => {
9 | log.debug(`cacheSize before: ${cacheSizeBefore}`);
10 | return win.webContents.session.clearCache();
11 | })
12 | .then(() => {
13 | return win.webContents.session.clearStorageData();
14 | })
15 | .then(() => {
16 | return win.webContents.session.getCacheSize();
17 | })
18 | .then(cacheSizeAfter => {
19 | log.debug(`cacheSize after: ${cacheSizeAfter}`);
20 | // and reload to use initialStateJSON
21 | win.webContents.reload();
22 | return undefined;
23 | })
24 | .catch(error => {
25 | log.error(`There has been a problem with your clearCache operation: ${error.message}`);
26 | });
27 | };
28 |
29 | export const resetApplication = (mainWindow, workerWindow, opencvWorkerWindow, databaseWorkerWindow) => {
30 | mainWindow.webContents.send('delete-all-tables');
31 | setTimeout(() => {
32 | clearCache(mainWindow);
33 | workerWindow.webContents.reload();
34 | opencvWorkerWindow.webContents.reload();
35 | databaseWorkerWindow.webContents.reload(); // needs reload to open indexedDB connection
36 | }, 1000);
37 | };
38 |
39 | // soft reset only deletes the indexedDB table, and does not clear cache nor all storage data
40 | // and also does not reload the windows
41 | export const softResetApplication = mainWindow => {
42 | mainWindow.webContents.send('delete-all-tables');
43 | };
44 |
45 | export const reloadApplication = (mainWindow, workerWindow, opencvWorkerWindow, databaseWorkerWindow) => {
46 | mainWindow.webContents.reload();
47 | workerWindow.webContents.reload();
48 | opencvWorkerWindow.webContents.reload();
49 | databaseWorkerWindow.webContents.reload();
50 | };
51 |
52 | export const getPathOfLogFileAndFolder = (processPlatform, appName) => {
53 | let pathOfLogFolder;
54 | switch (processPlatform) {
55 | case 'darwin':
56 | pathOfLogFolder = path.resolve(process.env.HOME || process.env.USERPROFILE, 'Library/Logs/', `${appName}/`);
57 | break;
58 | default:
59 | pathOfLogFolder = path.resolve(
60 | process.env.HOME || process.env.USERPROFILE,
61 | 'AppData\\Roaming\\',
62 | `${appName}`,
63 | 'logs/',
64 | );
65 | }
66 | const pathOfLogFile = path.resolve(pathOfLogFolder, 'main.log');
67 | return { pathOfLogFile, pathOfLogFolder };
68 | };
69 |
--------------------------------------------------------------------------------
/app/store/configureStore.dev.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import { createHashHistory } from 'history';
4 | import { routerMiddleware, push } from 'connected-react-router';
5 | import { createLogger } from 'redux-logger';
6 | import throttle from 'lodash/throttle';
7 | import rootReducer from '../reducers';
8 | import * as thumbActions from '../actions/index';
9 | import { loadState, saveState } from './localStorage';
10 |
11 | const history = createHashHistory();
12 |
13 | const configureStore = (initialState) => {
14 | // store State in localStorage
15 | let persistedState;
16 | if (initialState === undefined) {
17 | persistedState = loadState();
18 | } else {
19 | persistedState = initialState;
20 | }
21 |
22 | // Redux Configuration
23 | const middleware = [];
24 | const enhancers = [];
25 |
26 | // Thunk Middleware
27 | middleware.push(thunk);
28 |
29 | // Logging Middleware
30 | const logger = createLogger({
31 | level: 'info',
32 | collapsed: true
33 | });
34 |
35 | // Skip redux logs in console during the tests
36 | if (process.env.NODE_ENV !== 'test') {
37 | middleware.push(logger);
38 | }
39 |
40 | // Router Middleware
41 | const router = routerMiddleware(history);
42 | middleware.push(router);
43 |
44 | // Redux DevTools Configuration
45 | const actionCreators = {
46 | ...thumbActions,
47 | push,
48 | };
49 | // If Redux DevTools Extension is installed use it, otherwise use Redux compose
50 | /* eslint-disable no-underscore-dangle */
51 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
52 | ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
53 | // Options: http://extension.remotedev.io/docs/API/Arguments.html
54 | actionCreators
55 | })
56 | : compose;
57 | /* eslint-enable no-underscore-dangle */
58 |
59 | // Apply Middleware & Compose Enhancers
60 | enhancers.push(applyMiddleware(...middleware));
61 | const enhancer = composeEnhancers(...enhancers);
62 |
63 | // Create Store
64 | const store = createStore(rootReducer, persistedState, enhancer);
65 |
66 | store.subscribe(throttle(() => {
67 | saveState(store.getState());
68 | // // only store thumbs in localStorage
69 | // saveState({
70 | // thumbs: store.getState().thumbs
71 | // });
72 | }, 1000));
73 |
74 | if (module.hot) {
75 | module.hot.accept(
76 | '../reducers',
77 | // eslint-disable-line global-require
78 |
79 | () => store.replaceReducer(require('../reducers').default)
80 | );
81 | }
82 |
83 | return store;
84 | };
85 |
86 | export default { configureStore, history };
87 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # Config adapted from:
2 | # https://www.electron.build/multi-platform-build#sample-travisyml-to-build-electron-app-for-macos-linux-and-windows
3 | sudo: true
4 |
5 | matrix:
6 | include:
7 | - os: osx
8 | osx_image: xcode9.4
9 | language: node_js
10 | node_js:
11 | - node
12 | - 9
13 | env:
14 | - ELECTRON_CACHE=$HOME/.cache/electron
15 | - ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
16 |
17 | - os: linux
18 | services: docker
19 | language: node_js
20 | node_js:
21 | - node
22 | - 9
23 | addons:
24 | apt:
25 | sources:
26 | - ubuntu-toolchain-r-test
27 | packages:
28 | - g++-4.8
29 | - icnsutils
30 | - graphicsmagick
31 | - xz-utils
32 | - xorriso
33 |
34 | before_cache:
35 | - rm -rf $HOME/.cache/electron-builder/wine
36 |
37 | cache:
38 | yarn: true
39 | directories:
40 | - node_modules
41 | - app/node_modules
42 | - $(npm config get prefix)/lib/node_modules
43 | - flow-typed
44 | - $HOME/.cache/electron
45 | - $HOME/.cache/electron-builder
46 | - $HOME/docker
47 |
48 | install:
49 | - export CXX="g++-4.8"
50 | - yarn
51 | # On Linux, initialize "virtual display". See before_script
52 | - |
53 | if [ "$TRAVIS_OS_NAME" == "linux" ]; then
54 | /sbin/start-stop-daemon \
55 | --start \
56 | --quiet \
57 | --pidfile /tmp/custom_xvfb_99.pid \
58 | --make-pidfile \
59 | --background \
60 | --exec /usr/bin/Xvfb \
61 | -- :99 -ac -screen 0 1280x1024x16
62 | else
63 | :
64 | fi
65 |
66 | before_script:
67 | # On Linux, create a "virtual display". This allows browsers to work properly
68 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export DISPLAY=:99.0; fi
69 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sh -e /etc/init.d/xvfb start; fi
70 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sleep 3; fi
71 |
72 | script:
73 | - yarn package
74 | - yarn lint
75 | - yarn flow
76 | - yarn test
77 | - yarn test-e2e
78 | # On Linux, check if packaging to windows works
79 | - |
80 | if [ "$TRAVIS_OS_NAME" == "linux" ]; then
81 | docker run --rm \
82 | --env-file <(env | grep -iE 'DEBUG|NODE_|ELECTRON_|YARN_|NPM_|CI|CIRCLE|TRAVIS|APPVEYOR_|CSC_|_TOKEN|_KEY|AWS_|STRIP|BUILD_') \
83 | -v ${PWD}:/project \
84 | -v ~/.cache/electron:/root/.cache/electron \
85 | -v ~/.cache/electron-builder:/root/.cache/electron-builder \
86 | electronuserland/builder:wine \
87 | /bin/bash -c "yarn --link-duplicates --pure-lockfile && yarn package --linux --win"
88 | else
89 | :
90 | fi
91 |
--------------------------------------------------------------------------------
/app/img/icon-6x6.svg:
--------------------------------------------------------------------------------
1 |
39 |
--------------------------------------------------------------------------------
/app/containers/FileList.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React, { Component } from 'react';
3 | import PropTypes from 'prop-types';
4 | import FileListElement from '../components/FileListElement';
5 | import styles from '../components/FileList.css';
6 | import {
7 | getObjectProperty,
8 | } from '../utils/utils';
9 | import { MENU_HEADER_HEIGHT, MENU_FOOTER_HEIGHT } from '../utils/constants';
10 |
11 | class SortedFileList extends Component {
12 |
13 | render() {
14 | const { files, settings, posterobjectUrlObjects, visibilitySettings } = this.props;
15 |
16 | return (
17 |
68 | );
69 | }
70 | }
71 |
72 | export default SortedFileList;
73 |
--------------------------------------------------------------------------------
/app/components/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Menu, Popup, Icon, Dropdown } from 'semantic-ui-react';
4 | import { MENU_FOOTER_HEIGHT, SHEET_VIEW, VIEW } from '../utils/constants';
5 | import styles from './Menu.css';
6 | import stylesPop from './Popup.css';
7 |
8 | const Footer = ({
9 | defaultView,
10 | file,
11 | onSaveAllMoviePrints,
12 | onOpenFileExplorer,
13 | onSaveMoviePrint,
14 | savingMoviePrint,
15 | sheetView,
16 | }) => {
17 | return (
18 |
24 |
90 |
91 | );
92 | };
93 |
94 | Footer.defaultProps = {};
95 |
96 | Footer.propTypes = {
97 | file: PropTypes.object,
98 | };
99 |
100 | export default Footer;
101 |
--------------------------------------------------------------------------------
/app/components/FloatingMenu.css:
--------------------------------------------------------------------------------
1 |
2 | .floatingMenu {
3 | transition: transform 0.5s ease;
4 | position: fixed;
5 | transform: translate(-50%);
6 | top: 8px;
7 | left: calc(50% + 10px);
8 | z-index: 990;
9 | width: 760px;
10 | /* background-color: rgba(255, 0, 0, 0.5); */
11 | }
12 |
13 | .imageButton {
14 | color: white !important;
15 | /* background-color: rgba(30, 15, 0, 1.0) !important; */
16 | background: rgba(0,0,0,0.8) !important;
17 | /* box-shadow: none !important; */
18 | padding-left: 11px !important;
19 | padding-right: 11px !important;
20 | /* padding-top: 10px !important; */
21 | /* padding-bottom: 9px !important; */
22 | }
23 |
24 | .imageButton:hover {
25 | /* background-color: rgba(60, 30, 0, 1.0) !important; */
26 | background-color: rgba(255, 80, 6, 1.0) !important;
27 | }
28 |
29 | .dropDownButton {
30 | color: white !important;
31 | /* background-color: rgba(30, 15, 0, 1.0) !important; */
32 | background: rgba(0,0,0,0.8) !important;
33 | /* box-shadow: none !important; */
34 | padding-left: 15px !important;
35 | padding-right: 11px !important;
36 | /* padding-top: 11px !important; */
37 | /* padding-bottom: 10px !important; */
38 | font-size: 16px !important;
39 | }
40 |
41 | .dropDownButton:hover {
42 | /* background-color: rgba(60, 30, 0, 1.0) !important; */
43 | background-color: rgba(255, 80, 6, 1.0) !important;
44 | }
45 |
46 | .dropDownButton i{
47 | opacity: 0.9 !important;
48 | }
49 |
50 | .dropDownMenu {
51 | /* background-color: rgba(60, 30, 0, 1.0) !important; */
52 | }
53 |
54 | .dropDownMenuFilter {
55 | width: 260px;
56 | }
57 |
58 | .dropDownItem {
59 | height: 40px !important;
60 | }
61 |
62 | .dropDownItem img{
63 | margin-top: -2px !important;
64 | }
65 |
66 | .dropDownItemIconInvert img{
67 | filter: invert(100%);
68 | }
69 |
70 | .dropDownItemCheckbox{
71 | height: 40px !important;
72 | }
73 |
74 | .dropDownItemCheckboxAndSlider{
75 | height: 72px !important;
76 | }
77 |
78 | .dropDownItemRadioGroup{
79 | height: 40px !important;
80 | margin: 16px;
81 | }
82 |
83 | .dropDownItemHidden {
84 | visibility: hidden;
85 | /* height: 32px !important; */
86 | }
87 |
88 | .dropDownItemRadioGroup > div{
89 | margin-right: 16px;
90 | }
91 |
92 | .slider{
93 | margin-top: 8px !important;
94 | }
95 |
96 | .normalButton {
97 | color: white !important;
98 | /* background-color: rgba(30, 15, 0, 1.0) !important; */
99 | background: rgba(0,0,0,0.8) !important;
100 | font-family: Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif !important;
101 | box-shadow: none !important;
102 | }
103 |
104 | .normalButton:hover {
105 | /* background-color: rgba(60, 30, 0, 1.0) !important; */
106 | background-color: rgba(255, 80, 6, 1.0) !important;
107 | }
108 |
109 | .textButton {
110 | color: white !important;
111 | background-color: rgba(0, 0, 0, 0.2) !important;
112 | font-family: Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif !important;
113 | font-weight: 600 !important;
114 | font-size: 14px !important;
115 | height: 34px !important;
116 | border-radius: 0 !important;
117 | padding-top: 11px !important;
118 | }
119 |
120 | .textButton:hover {
121 | background-color: rgba(60, 30, 0, 1.0) !important;
122 | }
123 |
124 | .selected {
125 | background-color: rgba(255, 80, 6, 0.5) !important;
126 | color: white !important;
127 | }
128 |
129 | .selected:hover {
130 | background-color: rgba(255, 80, 6, 1.0) !important;
131 | /* background-color: rgba(60, 30, 0, 1.0) !important; */
132 | color: white !important;
133 | }
134 |
135 | .hidden {
136 | visibility: hidden;
137 | }
138 |
139 | .noBackground {
140 | background-color: none !important;
141 | box-shadow: none !important;
142 | padding: 0px 16px 16px 16px !important;
143 | opacity: 0.5 !important;
144 | }
145 |
--------------------------------------------------------------------------------
/scripts/opencv.js:
--------------------------------------------------------------------------------
1 | /* eslint no-inner-declarations: 0 */
2 |
3 | import path from 'path';
4 | import log from 'electron-log';
5 |
6 | const shell = require('shelljs');
7 |
8 | // cross platform variables
9 | const projectRoot = shell.pwd().stdout;
10 |
11 | if (process.platform === 'darwin') {
12 |
13 | // Include all opencv dependencies including ffmpeg
14 | log.info(
15 | 'running opencv script to copy all its dependencies including ffmpeg library files into the opencv folder for later packaging and relink them if necessary'
16 | );
17 |
18 | function fixDeps(dirPath, dependencyDirName) {
19 | log.info(`checking all dylib files in ${dirPath}, copy dependencies into ${dependencyDirName} and change the dylib linking`);
20 | const dylibs = shell.ls(dirPath)
21 | const allDeps = []
22 | dylibs
23 | .filter(file => file.indexOf('dylib') !== -1)
24 | .forEach(dylibFilename => {
25 | const dylib = path.join(dirPath, dylibFilename);
26 | console.log(`checking outer dependencies of file ${dylib}`);
27 | const outerDeps = shell
28 | .exec(`otool -l ${dylib} | grep 'name /usr/local'`)
29 | .stdout.split('\n')
30 | .filter(dep => dep !== '');
31 |
32 | if (outerDeps.length > 0) {
33 | shell.exec(`install_name_tool -add_rpath @loader_path/${dependencyDirName}`);
34 | }
35 | outerDeps.forEach(depLine => {
36 | const regex = /name (.+?(?=\ \(offset))/g; // transform "name /usr/lib/libc++.1.dylib (offset 24)" -> "/usr/lib/libc++.1.dylib"
37 | const depfilePath = regex.exec(depLine)[1]; // "/usr/lib/libc++.1.dylib"
38 | const depfileName = depfilePath.replace(/^.*[\\\/]/, '');
39 |
40 | if (!allDeps.find(e => e === depfilePath)) {
41 | // if not added already -> add it
42 | allDeps.push(depfilePath);
43 | console.log(` copying outer dependency ${depfilePath} to ${outerDependencyDir}`);
44 | shell.cp('-n', depfilePath, outerDependencyDir);
45 | }
46 | shell.chmod('-v', '666', dylib);
47 | const fixCommand = `install_name_tool -change ${depfilePath} @loader_path/${dependencyDirName}/${depfileName} ${dylib}`;
48 | console.log(` fix with command: ${fixCommand}`);
49 | shell.exec(fixCommand);
50 | })
51 |
52 | console.log(`\n\n`);
53 | })
54 | console.info('All outer dependencies:');
55 | console.log(allDeps);
56 | return allDeps;
57 | }
58 |
59 | // osx path variables
60 | const opencvLibDir = path.resolve(
61 | projectRoot,
62 | 'app/node_modules/opencv-build/opencv/build/lib/'
63 | );
64 | const dependencyDir = 'dependencies'
65 | const outerDependencyDir = path.join(opencvLibDir, dependencyDir);
66 |
67 | log.debug(`projectRoot: ${projectRoot}`);
68 | log.debug(`outerDependencyDir: ${outerDependencyDir}`);
69 | log.debug(`opencvLibDir: ${opencvLibDir}`);
70 |
71 | // create dependencies folder
72 | shell.mkdir('-p', outerDependencyDir);
73 |
74 | fixDeps(opencvLibDir, dependencyDir);
75 | fixDeps(outerDependencyDir, '');
76 | fixDeps(outerDependencyDir, '');
77 | fixDeps(outerDependencyDir, '');
78 |
79 | } else if (process.platform === 'win32') {
80 |
81 | // it seems that on windows opencv is already bundled with ffmpeg
82 | // but the redistributable files need to be copied into the the apps root folder
83 | // I did not manage to configure electron-builder to copy the dll's directly
84 | // therefore this is done in a 2 step process
85 | // 1. this script copies the dll's into the dist folder
86 | // 2. electron-builder copies the dll's into the root folder when packaging
87 |
88 | const distDir = path.resolve(projectRoot, 'app/dist/redistributable/');
89 | shell.mkdir('-p', distDir); // create folder if it does not exist yet
90 |
91 | // copy necessary redistributable files
92 | shell.cp('-n', '/Windows/system32/CONCRT140.dll',distDir );
93 | shell.cp('-n', '/Windows/system32/MSVCP140.dll',distDir );
94 | shell.cp('-n', '/Windows/system32/VCRUNTIME140.dll',distDir );
95 | }
96 |
--------------------------------------------------------------------------------
/app/components/Scrub.css:
--------------------------------------------------------------------------------
1 | .scrubContainerBackground {
2 | /* outline-style: solid;
3 | outline-color: #0c3158;
4 | outline-width: 15px;
5 | outline-offset: -15px; */
6 | position: fixed; /* Stay in place */
7 | left: 0;
8 | top: 0;
9 | width: 100%; /* Full width */
10 | height: 100%; /* Full height */
11 | background-color: rgba(0,0,0,0.8); /* Black w/ opacity */
12 | z-index: 999;
13 | cursor: col-resize;
14 | }
15 |
16 | .scrubInfo {
17 | position: absolute;
18 | top: 30px;
19 | left: 50%;
20 | transform: translateX(-50%);
21 | background-color: rgba(0,0,0,1); /* Black w/ opacity */
22 | font-family: 'Franchise', 'Roboto Condensed';
23 | color: #ffffff;
24 | font-size: 30px;
25 | opacity: 0.5;
26 | -webkit-user-select:none;
27 | }
28 |
29 | .scrubContainer {
30 | position: absolute;
31 | top: 50%;
32 | left: 50%;
33 | transform: translate(-50%,-50%);
34 | background-color: rgba(0,0,0,1); /* Black w/ opacity */
35 | }
36 |
37 | .scrubInnerContainer {
38 | position: absolute;
39 | top: 50%;
40 | left: 50%;
41 | transform: translate(-50%,-50%);
42 | }
43 |
44 | .scrubThumb {
45 | position: relative;
46 | }
47 |
48 | .scrubThumbLeft {
49 | /* position: absolute; */
50 | left: 0;
51 | display: inline-block;
52 | background-position: right center;
53 | background-size: cover;
54 | /* margin-right: 2px; */
55 | /* height: 24px; */
56 | /* width: 24px; */
57 | object-fit: cover;
58 | transform: translateY(-50%);
59 | }
60 |
61 | .scrubThumbRight {
62 | /* position: absolute; */
63 | right: 0;
64 | display: inline-block;
65 | background-position: left center;
66 | background-size: cover;
67 | /* margin-left: 2px; */
68 | /* height: 24px; */
69 | /* width: 24px; */
70 | object-fit: cover;
71 | transform: translateY(-50%);
72 | }
73 |
74 | .scrubCancelBar {
75 | position: absolute;
76 | bottom: 0;
77 | width: 100%;
78 | background-color: rgba(100, 0, 0, 1);
79 | z-index: 1000;
80 | text-align: center;
81 | padding-top: 6px;
82 | cursor: pointer;
83 | }
84 |
85 | .scrubDescription {
86 | position: absolute;
87 | top: 0;
88 | width: 100%;
89 | /* background-color: #333333; */
90 | z-index: 1000;
91 | text-align: center;
92 | padding-top: 6px;
93 | }
94 |
95 | .scrubLine {
96 | position: absolute;
97 | top: 0;
98 | height: 100%;
99 | width: 1px;
100 | background-color: #FF5006;
101 | z-index: 2000;
102 | }
103 |
104 | .frameNumberOrTimeCode {
105 | position: absolute;
106 | /* top: 0; */
107 | left: 0;
108 | top: 0;
109 | transform: translate(-50%, -20px);
110 | background: #eee;
111 | border-radius:2px;
112 | font-family: 'Open sans';
113 | font-size: 14px;
114 | line-height: 14px;
115 | padding: 1px;
116 | color: #000000;
117 | -webkit-user-select:none;
118 | z-index: 2001;
119 | }
120 |
121 | .scrubThumbLine {
122 | position: absolute;
123 | top: 0;
124 | height: 100%;
125 | width: 1px;
126 | background-color: rgba(255, 255, 255, 0.2);
127 | z-index: 2000;
128 | }
129 |
130 | .scrubThumbframeNumberOrTimeCode {
131 | position: absolute;
132 | /* top: 0; */
133 | left: 0;
134 | top: 0;
135 | transform: translate(-50%, -20px);
136 | background-color: rgba(100, 100, 100, 1);
137 | border-radius:2px;
138 | font-family: 'Open sans';
139 | font-size: 14px;
140 | line-height: 14px;
141 | padding: 1px;
142 | color: #000000;
143 | -webkit-user-select:none;
144 | z-index: 1999;
145 | /* opacity: 0.5; */
146 | }
147 |
148 | .timelineWrapper {
149 | bottom: -24px;
150 | width: 100%;
151 | position: absolute;
152 | background-color: #421400;
153 | height: 24px;
154 | }
155 |
156 | .timelineCut {
157 | position: absolute;
158 | background-color: rgba(255, 80, 6, 0.4);
159 | height: 24px;
160 | }
161 |
162 | .timelinePlayhead {
163 | position: absolute;
164 | /* bottom: 0; */
165 | /* top: 0; */
166 | width: 2px;
167 | background-color: rgba(255, 80, 6, 1);
168 | height: 20px;
169 | margin-top: 2px;
170 | }
171 |
172 | .timelineScrubThumb {
173 | position: absolute;
174 | /* bottom: 0; */
175 | /* top: 0; */
176 | width: 1px;
177 | background-color: rgba(255, 255, 255, 0.2);
178 | height: 20px;
179 | margin-top: 2px;
180 | }
181 |
182 | /* .timelineWrapper .currentTime {
183 | background-color: #FF5006;
184 | z-index: 2;
185 | } */
186 |
187 | /* .timelineWrapper .cutStartTime {
188 | background-color: rgba(0, 0, 0, 0.3);
189 | border-left: 1px solid black;
190 | border-right: 1px solid black;
191 | z-index: 1;
192 | } */
193 |
--------------------------------------------------------------------------------
/app/components/HeaderComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Menu, Dropdown, Icon, Popup } from 'semantic-ui-react';
4 | import { MENU_HEADER_HEIGHT } from '../utils/constants';
5 | import styles from './Menu.css';
6 | import stylesPop from './Popup.css';
7 |
8 | const Header = ({
9 | file,
10 | visibilitySettings,
11 | openMoviesDialog,
12 | onOpenFeedbackForm,
13 | onImportMoviePrint,
14 | fileCount,
15 | onClearMovieList,
16 | checkForUpdates,
17 | isCheckingForUpdates,
18 | }) => {
19 | return (
20 |
26 |
124 |
125 | );
126 | };
127 |
128 | Header.defaultProps = {
129 | file: undefined,
130 | };
131 |
132 | Header.propTypes = {
133 | file: PropTypes.object,
134 | };
135 |
136 | export default Header;
137 |
--------------------------------------------------------------------------------
/app/img/listOfNames.json:
--------------------------------------------------------------------------------
1 | [
2 | {"firstName": "Adriana", "fullName": "Adriana Herlers"},
3 | {"firstName": "Agnieszka", "fullName": "Agnieszka Holland"},
4 | {"firstName": "Agnès", "fullName": "Agnès Godard"},
5 | {"firstName": "Agnès", "fullName": "Agnès Varda"},
6 | {"firstName": "Akie", "fullName": "Akie Namiki"},
7 | {"firstName": "Alice", "fullName": "Alice Guy-Blaché"},
8 | {"firstName": "Andrea", "fullName": "Andrea Arnold"},
9 | {"firstName": "Anna", "fullName": "Anna Muylaert"},
10 | {"firstName": "Anne", "fullName": "Anne Bancroft"},
11 | {"firstName": "Archana", "fullName": "Archana Phadke"},
12 | {"firstName": "Autumn", "fullName": "Autumn Durald"},
13 | {"firstName": "Barbara", "fullName": "Barbara Hammer"},
14 | {"firstName": "Barbra", "fullName": "Barbra Streisand"},
15 | {"firstName": "Candida", "fullName": "Candida Beltrán Rendón"},
16 | {"firstName": "Carmen", "fullName": "Carmen Santos"},
17 | {"firstName": "Caroline", "fullName": "Caroline Champetier"},
18 | {"firstName": "Caroline", "fullName": "Caroline Link"},
19 | {"firstName": "Chantal", "fullName": "Chantal Akerman"},
20 | {"firstName": "Claire", "fullName": "Claire Denis"},
21 | {"firstName": "Deepa", "fullName": "Deepa Mehta"},
22 | {"firstName": "Dolores", "fullName": "Dolores Herlers"},
23 | {"firstName": "Dominique", "fullName": "Dominique Gonzales-Foerster"},
24 | {"firstName": "Doris", "fullName": "Doris Dörrie"},
25 | {"firstName": "Dorota", "fullName": "Dorota Kedzierzawska"},
26 | {"firstName": "Dorothy", "fullName": "Dorothy Arzner"},
27 | {"firstName": "Edith", "fullName": "Edith Carlmar"},
28 | {"firstName": "Elaine", "fullName": "Elaine May"},
29 | {"firstName": "Eleanor", "fullName": "Eleanor Coppola"},
30 | {"firstName": "Ellen", "fullName": "Ellen Kuras"},
31 | {"firstName": "Elvira", "fullName": "Elvira Notari"},
32 | {"firstName": "Eriko", "fullName": "Eriko Sonoda"},
33 | {"firstName": "G.B.", "fullName": "G. B. Jones"},
34 | {"firstName": "Gabriela", "fullName": "Gabriela von Bussenius Vega"},
35 | {"firstName": "Germaine", "fullName": "Germaine Dulac"},
36 | {"firstName": "Gilda", "fullName": "Gilda de Abreu"},
37 | {"firstName": "Gunvor", "fullName": "Gunvor Nelson"},
38 | {"firstName": "Helen", "fullName": "Helen Levitt"},
39 | {"firstName": "Hyeong", "fullName": "Hyeong-ju Kim"},
40 | {"firstName": "Icíar", "fullName": "Icíar Bollaín"},
41 | {"firstName": "Ida", "fullName": "Ida Lupino"},
42 | {"firstName": "Ida", "fullName": "Ida May Park"},
43 | {"firstName": "Isabel", "fullName": "Isabel Coixet"},
44 | {"firstName": "Jane", "fullName": "Jane Campion"},
45 | {"firstName": "Jasmila", "fullName": "Jasmila Zbanic"},
46 | {"firstName": "Jennifer", "fullName": "Jennifer Fox"},
47 | {"firstName": "Jessica", "fullName": "Jessica Dimmock"},
48 | {"firstName": "Joanna", "fullName": "Joanna Priestley"},
49 | {"firstName": "Jocelyn", "fullName": "Jocelyn Moorhouse"},
50 | {"firstName": "Julie", "fullName": "Julie Taymor"},
51 | {"firstName": "Kate", "fullName": "Kate Brooks"},
52 | {"firstName": "Kathryn", "fullName": "Kathryn Bigelow"},
53 | {"firstName": "Katia", "fullName": "Katia Lund"},
54 | {"firstName": "Kirsten", "fullName": "Kirsten Johnson"},
55 | {"firstName": "Larisa", "fullName": "Larisa Shepitko"},
56 | {"firstName": "Laurel", "fullName": "Laurel Nakadate"},
57 | {"firstName": "Leni", "fullName": "Leni Riefenstahl"},
58 | {"firstName": "Lina", "fullName": "Lina Wertmüller"},
59 | {"firstName": "Liz", "fullName": "Liz Canner"},
60 | {"firstName": "Lois", "fullName": "Lois Weber"},
61 | {"firstName": "Lone", "fullName": "Lone Scherfig"},
62 | {"firstName": "Louise", "fullName": "Louise Kolm-Fleck"},
63 | {"firstName": "Lucrecia", "fullName": "Lucrecia Martel"},
64 | {"firstName": "Mabel", "fullName": "Mabel Normand"},
65 | {"firstName": "Malgorzata", "fullName": "Malgorzata Szumowska"},
66 | {"firstName": "Margarethe", "fullName": "Margarethe von Trotta"},
67 | {"firstName": "Marie", "fullName": "Marie Louise Droop"},
68 | {"firstName": "Marjane", "fullName": "Marjane Satrapi"},
69 | {"firstName": "Mary", "fullName": "Mary McIlwain"},
70 | {"firstName": "Maryse", "fullName": "Maryse Alberti"},
71 | {"firstName": "Maya", "fullName": "Maya Deren"},
72 | {"firstName": "Mimi", "fullName": "Mimi Derba"},
73 | {"firstName": "Mira", "fullName": "Mira Nair"},
74 | {"firstName": "Muriel", "fullName": "Muriel Box"},
75 | {"firstName": "Nadine", "fullName": "Nadine Labaki"},
76 | {"firstName": "Natasha", "fullName": "Natasha Braier"},
77 | {"firstName": "Niki", "fullName": "Niki Caro"},
78 | {"firstName": "Olga", "fullName": "Olga Preobrazhenskaia"},
79 | {"firstName": "Rachel", "fullName": "Rachel Morrison"},
80 | {"firstName": "Randa", "fullName": "Randa Haines"},
81 | {"firstName": "Reed", "fullName": "Reed Morano"},
82 | {"firstName": "Sofia", "fullName": "Sofia Coppola"},
83 | {"firstName": "Susanne", "fullName": "Susanne Bier"},
84 | {"firstName": "Toni", "fullName": "Toni English"},
85 | {"firstName": "Vera", "fullName": "Vera Chytilová"}
86 | ]
87 |
--------------------------------------------------------------------------------
/app/utils/faceDetection.js:
--------------------------------------------------------------------------------
1 | /* eslint import/prefer-default-export: "off" */
2 |
3 | import path from 'path';
4 | import * as faceapi from 'face-api.js';
5 | import log from 'electron-log';
6 | import uuidV4 from 'uuid/v4';
7 |
8 | import { roundNumber } from './utils';
9 | import { FACE_CONFIDENCE_THRESHOLD, FACE_SIZE_THRESHOLD } from './constants';
10 |
11 | const { ipcRenderer } = require('electron');
12 | const { app } = require('electron').remote;
13 |
14 | const appPath = app.getAppPath();
15 | const weightsPath = path.join(appPath, './dist/weights');
16 |
17 | const loadNet = async () => {
18 | const toastId = 'loadNet';
19 | ipcRenderer.send(
20 | 'message-from-opencvWorkerWindow-to-mainWindow',
21 | 'progressMessage',
22 | 'info',
23 | 'Initialising face detection',
24 | false,
25 | toastId,
26 | );
27 | log.debug(weightsPath);
28 | log.debug(faceapi.nets);
29 | const detectionNet = faceapi.nets.ssdMobilenetv1;
30 | log.debug(detectionNet);
31 | await detectionNet.loadFromDisk(weightsPath);
32 | await faceapi.nets.faceLandmark68Net.loadFromDisk(weightsPath);
33 | await faceapi.nets.ageGenderNet.loadFromDisk(weightsPath);
34 | await faceapi.nets.faceRecognitionNet.loadFromDisk(weightsPath);
35 | ipcRenderer.send(
36 | 'message-from-opencvWorkerWindow-to-mainWindow',
37 | 'progressMessage',
38 | 'success',
39 | 'Face detection successfully initialised',
40 | 3000,
41 | toastId,
42 | true,
43 | );
44 | return detectionNet;
45 | };
46 |
47 | loadNet()
48 | .then(detectionNet => {
49 | log.debug(detectionNet);
50 | return undefined;
51 | })
52 | .catch(error => {
53 | log.error(`There has been a problem with your loadNet operation: ${error.message}`);
54 | ipcRenderer.send(
55 | 'message-from-opencvWorkerWindow-to-mainWindow',
56 | 'progressMessage',
57 | 'error',
58 | `There has been a problem with your loadNet operation: ${error.message}`,
59 | false,
60 | 'loadNet',
61 | true,
62 | );
63 | });
64 |
65 | export const detectAllFaces = async (
66 | image,
67 | frameNumber,
68 | detectionArray,
69 | defaultFaceConfidenceThreshold = FACE_SIZE_THRESHOLD,
70 | defaultFaceSizeThreshold = FACE_CONFIDENCE_THRESHOLD,
71 | ) => {
72 | // detect expression
73 | const detections = await faceapi
74 | .detectAllFaces(image)
75 | .withFaceLandmarks()
76 | .withAgeAndGender()
77 | .withFaceDescriptors();
78 | console.log(frameNumber);
79 |
80 | const faceCount = detections.length;
81 |
82 | if (faceCount === 0) {
83 | console.log('no face detected!');
84 | detectionArray.push({
85 | frameNumber,
86 | faceCount,
87 | });
88 | return detections;
89 | }
90 | console.log(`Face count: ${faceCount}`);
91 |
92 | console.log(detections);
93 |
94 | const facesArray = [];
95 | detections.forEach(face => {
96 | const { age, gender, descriptor, detection } = face;
97 | const { relativeBox, score } = detection;
98 | const size = Math.round(relativeBox.height * 100);
99 | const scoreInPercent = Math.round(score * 100);
100 |
101 | // create full copy of array to be pushed later
102 | const copyOfDescriptor = descriptor.slice();
103 |
104 | // console.log(detection);
105 | // console.log(uniqueFaceArray);
106 | // console.log(copyOfDescriptor);
107 |
108 | // console.log(face);
109 | if (size < defaultFaceSizeThreshold || scoreInPercent < defaultFaceConfidenceThreshold) {
110 | console.log('detected face below size or confidence threshold!');
111 | return undefined;
112 | }
113 |
114 | const simpleBox = {
115 | x: roundNumber(relativeBox.x, 4),
116 | y: roundNumber(relativeBox.y, 4),
117 | width: roundNumber(relativeBox.width, 4),
118 | height: roundNumber(relativeBox.height, 4),
119 | };
120 |
121 | facesArray.push({
122 | score: scoreInPercent,
123 | size,
124 | box: simpleBox,
125 | gender,
126 | age: Math.round(age),
127 | faceId: uuidV4(),
128 | faceDescriptor: copyOfDescriptor,
129 | });
130 |
131 | // const drawBox = new faceapi.draw.DrawBox(box, {
132 | // // label: Math.round(age),
133 | // label: faceId,
134 | // lineWidth: 1,
135 | // boxColor: 'red'
136 | // })
137 | // drawBox.draw(image);
138 | });
139 |
140 | if (facesArray.length === 0) {
141 | // no faces stored as they are below thresholds
142 | detectionArray.push({
143 | frameNumber,
144 | faceCount,
145 | });
146 | return detections;
147 | }
148 |
149 | // sort faces by size with the largest one first
150 | facesArray.sort((face1, face2) => face2.size - face1.size);
151 | console.log(facesArray);
152 |
153 | detectionArray.push({
154 | frameNumber,
155 | faceCount,
156 | largestSize: facesArray[0].size,
157 | facesArray,
158 | });
159 | return detections;
160 | };
161 |
--------------------------------------------------------------------------------
/app/img/MoviePrint-titleimage.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/app/utils/utilsForIndexedDB.js:
--------------------------------------------------------------------------------
1 | import log from 'electron-log';
2 | import imageDB from './db';
3 |
4 | const { ipcRenderer } = require('electron');
5 |
6 | export const openDBConnection = () => {
7 | // dexie documentation:
8 | // Even though open() is asynchronous,
9 | // you can already now start interact with the database.
10 | // The operations will be pending until open() completes.
11 | // If open() succeeds, the operations below will resume.
12 | // If open() fails, the below operations below will fail and
13 | if (!imageDB.isOpen()) {
14 | imageDB.open().catch(err => {
15 | log.error(`Failed to open imageDB: ${err.stack || err}`);
16 | });
17 | }
18 | };
19 |
20 | export const deleteTableFramelist = () =>
21 | imageDB.frameList.clear().catch(err => {
22 | log.error(`Failed to delete all objects in frameList: ${err.stack || err}`);
23 | });
24 |
25 | export const addFrameToIndexedDB = (frameId, fileId, frameNumber, outBase64, objectUrlQueue) => {
26 | const url = `data:image/jpg;base64,${outBase64}`;
27 | return fetch(url)
28 | .then(res => res.blob())
29 | .then(blob => {
30 | try {
31 | return imageDB
32 | .transaction('rw', imageDB.frameList, async () => {
33 | await imageDB.frameList.put({
34 | frameId,
35 | fileId,
36 | frameNumber,
37 | data: blob,
38 | });
39 | const key = await imageDB.frameList.get(frameId);
40 | // console.log(key);
41 | return key;
42 | })
43 | .catch(e => {
44 | log.error('error inside addFrameToIndexedDB - transaction');
45 | log.error(e.stack || e);
46 | });
47 | } catch (e) {
48 | log.error(e.stack || e);
49 | return undefined;
50 | }
51 | })
52 | .then(frame => {
53 | // console.log(frame);
54 | const objectUrl = window.URL.createObjectURL(frame.data);
55 | objectUrlQueue.add({
56 | frameId,
57 | objectUrl,
58 | });
59 | return objectUrl;
60 | })
61 | .catch(e => {
62 | log.error(e.stack || e);
63 | });
64 | };
65 |
66 | export const updateFrameInIndexedDB = (frameId, outBase64, objectUrlQueue, fastTrack) => {
67 | if (outBase64 === '') {
68 | return undefined;
69 | }
70 | const url = `data:image/jpg;base64,${outBase64}`;
71 | fetch(url)
72 | .then(res => res.blob())
73 | .then(blob => {
74 | return imageDB
75 | .transaction('rw', imageDB.frameList, async () => {
76 | await imageDB.frameList
77 | .where('frameId')
78 | .equals(frameId)
79 | .modify({
80 | data: blob,
81 | });
82 | const key = await imageDB.frameList.get(frameId);
83 | console.log(key);
84 | return key;
85 | })
86 | .then(key => {
87 | console.log('Transaction committed');
88 | return key;
89 | })
90 | .catch(e => {
91 | log.error('error inside updateFrameInIndexedDB - transaction');
92 | log.error(e.stack || e);
93 | });
94 | })
95 | .then(frame => {
96 | console.log(frame);
97 | if (frame !== undefined) {
98 | const objectUrl = window.URL.createObjectURL(frame.data);
99 | if (fastTrack) {
100 | ipcRenderer.send('message-from-databaseWorkerWindow-to-mainWindow', 'update-objectUrl', frameId, objectUrl);
101 | } else {
102 | objectUrlQueue.add({
103 | frameId,
104 | objectUrl,
105 | });
106 | }
107 | return objectUrl;
108 | }
109 | return undefined;
110 | })
111 | .catch(e => {
112 | log.error(e.stack || e);
113 | });
114 | };
115 |
116 | export const getObjectUrlsFromFramelist = objectUrlQueue => {
117 | console.log('inside getObjectUrlsFromFramelist');
118 | try {
119 | log.warn(imageDB.isOpen());
120 | imageDB
121 | .transaction('r', imageDB.frameList, async () => {
122 | try {
123 | console.log(imageDB.isOpen());
124 | const array = await imageDB.frameList.toArray().catch(e => {
125 | log.error('error inside promise catch');
126 | log.error(e.stack || e);
127 | });
128 | if (array.length === 0) {
129 | return [];
130 | }
131 | const arrayOfObjectUrls = [];
132 | array.map(frame => {
133 | const objectUrl = window.URL.createObjectURL(frame.data);
134 | if (objectUrl !== undefined) {
135 | arrayOfObjectUrls.push({
136 | frameId: frame.frameId,
137 | objectUrl: window.URL.createObjectURL(frame.data),
138 | });
139 | }
140 | return undefined;
141 | });
142 | objectUrlQueue.addArray(arrayOfObjectUrls);
143 | return undefined;
144 | } catch (e) {
145 | log.error('error inside the inner try catch');
146 | log.error(e.stack || e);
147 | }
148 | })
149 | .catch(e => {
150 | log.error('error inside promise catch');
151 | log.error(e.stack || e);
152 | });
153 | } catch (e) {
154 | log.error('error inside try catch');
155 | log.error(e.stack || e);
156 | }
157 | };
158 |
--------------------------------------------------------------------------------
/app/reducers/files.js:
--------------------------------------------------------------------------------
1 | /* eslint no-case-declarations: "off" */
2 |
3 | import log from 'electron-log';
4 | import { RotateFlags } from '../utils/openCVProperties';
5 |
6 | const file = (state = {}, type, payload, index) => {
7 | switch (type) {
8 | case 'UPDATE_SHEETCOUNTER':
9 | if (state.id !== payload.fileId) {
10 | return state;
11 | }
12 | return { ...state, sheetCounter: (state.sheetCounter || 0) + payload.incrementValue };
13 | case 'UPDATE_FILESCAN_STATUS':
14 | if (state.id !== payload.fileId) {
15 | return state;
16 | }
17 | return { ...state, fileScanStatus: payload.fileScanStatus };
18 | case 'UPDATE_FILE_MISSING_STATUS':
19 | if (state.id !== payload.fileId) {
20 | return state;
21 | }
22 | return { ...state, fileMissingStatus: payload.fileMissingStatus };
23 | case 'UPDATE_MOVIE_LIST_ITEM_USERATIO':
24 | if (state.id !== payload.fileId) {
25 | return state;
26 | }
27 | return { ...state, useRatio: payload.useRatio };
28 | case 'REPLACE_MOVIE_LIST_ITEM':
29 | if (state.id !== payload.fileId) {
30 | return state;
31 | }
32 | return {
33 | ...state,
34 | path: payload.path,
35 | name: payload.name,
36 | size: payload.size,
37 | lastModified: payload.lastModified,
38 | };
39 | case 'UPDATE_MOVIE_LIST_ITEM': {
40 | if (state.id !== payload.fileId) {
41 | return state;
42 | }
43 | const { fps, fourCC, frameCount } = payload;
44 | let { height, width } = payload;
45 | const originalWidth = width;
46 | const originalHeight = height;
47 |
48 | // if transformObject already exists then calculate width and height from it
49 | const { transformObject } = state;
50 | console.log(state);
51 | if (transformObject !== undefined) {
52 | width = originalWidth - transformObject.cropLeft - transformObject.cropRight;
53 | height = originalHeight - transformObject.cropTop - transformObject.cropBottom;
54 | }
55 | return {
56 | ...state,
57 | frameCount,
58 | originalWidth,
59 | width,
60 | originalHeight,
61 | height,
62 | fps,
63 | fourCC,
64 | };
65 | }
66 | case 'SET_TRANSFORM':
67 | if (state.id !== payload.fileId) {
68 | return state;
69 | }
70 | return { ...state, transformObject: payload.transformObject };
71 | case 'ROTATE_WIDTH_AND_HEIGHT': {
72 | if (state.id !== payload.fileId) {
73 | return state;
74 | }
75 | const { shouldRotate } = payload;
76 | if (shouldRotate) {
77 | return { ...state, width: state.originalHeight, height: state.originalWidth };
78 | }
79 | return { ...state, width: state.originalWidth, height: state.originalHeight };
80 | }
81 | case 'UPDATE_CROPPING': {
82 | if (state.id !== payload.fileId) {
83 | return state;
84 | }
85 | const { transformObject } = payload;
86 | const { cropTop, cropLeft, cropBottom, cropRight, rotationFlag } = transformObject;
87 |
88 | let origWidth = state.originalWidth;
89 | let origHeight = state.originalHeight;
90 | if (rotationFlag === RotateFlags.ROTATE_90_CLOCKWISE || rotationFlag === RotateFlags.ROTATE_90_COUNTERCLOCKWISE) {
91 | [origWidth, origHeight] = [origHeight, origWidth]; // swapping of width and height
92 | }
93 | const newWidth = origWidth - cropLeft - cropRight;
94 | const newHeight = origHeight - cropTop - cropBottom;
95 | return { ...state, width: newWidth, height: newHeight, transformObject };
96 | }
97 | case 'UPDATE_ASPECT_RATIO':
98 | if (state.id !== payload.fileId) {
99 | return state;
100 | }
101 | return {
102 | ...state, transformObject: {
103 | ...state.transformObject,
104 | aspectRatioInv: payload.aspectRatioInv
105 | }
106 | };
107 | case 'UPDATE_IN_OUT_POINT':
108 | if (state.id !== payload.fileId) {
109 | return state;
110 | }
111 | return { ...state, fadeInPoint: payload.fadeInPoint, fadeOutPoint: payload.fadeOutPoint };
112 | default:
113 | return state;
114 | }
115 | };
116 |
117 | const files = (state = [], { type, payload }) => {
118 | switch (type) {
119 | case 'CLEAR_MOVIE_LIST':
120 | return [];
121 | case 'ADD_MOVIE_LIST_ITEMS': {
122 | log.debug(payload);
123 | log.debug(state);
124 | // combine state array and new files array
125 | const combinedArray = state.concat(payload);
126 | log.debug(combinedArray);
127 | return combinedArray;
128 | }
129 | case 'REMOVE_MOVIE_LIST_ITEM': {
130 | const newArray = state.slice();
131 | const indexOfItemToRemove = newArray.findIndex(singleFile => singleFile.id === payload.fileId);
132 | newArray.splice(indexOfItemToRemove, 1);
133 | return newArray;
134 | }
135 | case 'REPLACE_MOVIE_LIST_ITEM':
136 | case 'UPDATE_MOVIE_LIST_ITEM_USERATIO':
137 | case 'UPDATE_MOVIE_LIST_ITEM':
138 | case 'SET_TRANSFORM':
139 | case 'ROTATE_WIDTH_AND_HEIGHT':
140 | case 'UPDATE_CROPPING':
141 | case 'UPDATE_ASPECT_RATIO':
142 | case 'UPDATE_IN_OUT_POINT':
143 | case 'UPDATE_FILE_MISSING_STATUS':
144 | case 'UPDATE_FILESCAN_STATUS':
145 | case 'UPDATE_SHEETCOUNTER':
146 | return state.map((t, index) => file(t, type, payload, index));
147 | default:
148 | return state;
149 | }
150 | };
151 |
152 | export default files;
153 |
--------------------------------------------------------------------------------
/app/containers/VisibleSceneGrid.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import PropTypes from 'prop-types';
4 | import { arrayMove } from 'react-sortable-hoc';
5 | import scrollIntoView from 'scroll-into-view';
6 | import {
7 | toggleThumb, updateOrder,
8 | changeThumb, toggleScene, toggleSceneArray
9 | } from '../actions';
10 | import styles from '../components/ThumbGrid.css';
11 | import SortableSceneGrid from '../components/SceneGrid';
12 | import { getLowestFrame, getHighestFrame } from '../utils/utils';
13 | import { CHANGE_THUMB_STEP } from '../utils/constants';
14 |
15 | class SortedVisibleSceneGrid extends Component {
16 | constructor(props) {
17 | super(props);
18 | this.state = {
19 | };
20 |
21 | this.scrollIntoViewElement = React.createRef();
22 |
23 | this.scrollThumbIntoView = this.scrollThumbIntoView.bind(this);
24 | this.onSelectClick = this.onSelectClick.bind(this);
25 | this.onDeselectClick = this.onDeselectClick.bind(this);
26 | }
27 |
28 | componentDidMount() {
29 | setTimeout(() => {
30 | this.scrollThumbIntoView();
31 | }, 500);
32 | }
33 |
34 | componentDidUpdate(prevProps) {
35 | if (prevProps.selectedThumbsArray.length !== 0 &&
36 | this.props.selectedThumbsArray.length !== 0 &&
37 | (prevProps.selectedThumbsArray[0].thumbId !== this.props.selectedThumbsArray[0].thumbId)) {
38 | this.scrollThumbIntoView();
39 | }
40 | // delay when switching to gridView so it waits for the sheetView to be ready
41 | if ((prevProps.view !== this.props.view) &&
42 | prevProps.view) {
43 | setTimeout(() => {
44 | this.scrollThumbIntoView();
45 | }, 500);
46 | }
47 | }
48 |
49 | onSelectClick = (sceneId, frameNumber) => {
50 | this.props.onSelectThumbMethod(sceneId, frameNumber);
51 | }
52 |
53 | onDeselectClick = () => {
54 | console.log('deselect')
55 | this.props.onDeselectThumbMethod();
56 | }
57 |
58 | scrollThumbIntoView = () => {
59 | if (this.scrollIntoViewElement && this.scrollIntoViewElement.current !== null) {
60 | scrollIntoView(this.scrollIntoViewElement.current, {
61 | time: 300,
62 | align: {
63 | left: 0.5,
64 | }
65 | });
66 | }
67 | };
68 |
69 | render() {
70 | return (
71 |
112 | );
113 | }
114 | }
115 |
116 | const mapStateToProps = state => ({});
117 |
118 | const mapDispatchToProps = (dispatch, ownProps) => {
119 | return {
120 | onSortEnd: ({ oldIndex, newIndex }) => {
121 | const { settings, sheetsByFileId } = ownProps;
122 | const newOrderedThumbs = arrayMove(sheetsByFileId[settings.currentFileId][settings.currentSheetId].thumbsArray,
123 | oldIndex,
124 | newIndex);
125 | dispatch(updateOrder(
126 | settings.currentFileId,
127 | settings.currentSheetId,
128 | newOrderedThumbs));
129 | },
130 | onToggleClick: (fileId, sceneId) => {
131 | dispatch(toggleScene(fileId, ownProps.settings.currentSheetId, sceneId));
132 | },
133 | onHideBeforeAfterClick: (fileId, sheetId, thumbIdArray) => {
134 | dispatch(toggleSceneArray(
135 | fileId,
136 | sheetId,
137 | thumbIdArray
138 | ));
139 | },
140 | };
141 | };
142 |
143 | SortedVisibleSceneGrid.contextTypes = {
144 | };
145 |
146 | SortedVisibleSceneGrid.defaultProps = {
147 | selectedThumbsArray: [],
148 | };
149 |
150 | SortedVisibleSceneGrid.propTypes = {
151 | selectedThumbsArray: PropTypes.array,
152 | };
153 |
154 | export default connect(mapStateToProps, mapDispatchToProps)(SortedVisibleSceneGrid);
155 |
--------------------------------------------------------------------------------
/app/components/VideoPlayer.css:
--------------------------------------------------------------------------------
1 | .player {
2 | /* position: absolute;
3 | top: 0;
4 | left: 0;
5 | right: 0;
6 | bottom: 6rem; */
7 | background: #1e1e1e;
8 | }
9 |
10 | .video {
11 | /* width: 100%;
12 | height: 100%; */
13 | object-fit: contain;
14 | }
15 |
16 | .videoOverlay {
17 | position: absolute;
18 | transform-origin: center bottom;
19 | transform: translate(-50%, 0%);
20 | top: 0;
21 | left: 50%;
22 | width: 640px;
23 | object-fit: contain;
24 | background-color: rgba(0,0,0,0.8); /* Black w/ opacity */
25 | z-index: 2;
26 | }
27 |
28 | .frameNumberOrTimeCode {
29 | position: absolute;
30 | top: 0;
31 | left: 0;
32 | background: #eee;
33 | border-radius:2px;
34 | font-family: 'Open sans';
35 | font-size: 14px;
36 | line-height: 14px;
37 | padding: 1px;
38 | color: #000000;
39 | -webkit-user-select:none;
40 | z-index: 3;
41 | }
42 |
43 | .moveToMiddle {
44 | left: 50%;
45 | }
46 |
47 | .controlsWrapper button, .rightMenu button, .leftMenu button {
48 | /* background: black; */
49 | border-radius: .3em;
50 | /* color: rgba(0, 0, 0, 0.7); */
51 | fontSize: 60%;
52 | vertical-align: middle;
53 | /* padding: .2em .4em; */
54 | /* margin: 0 .5em; */
55 | border: none;
56 | }
57 |
58 | .controlsWrapper {
59 | left: 0;
60 | right: 0;
61 | bottom: 0;
62 | /* height: 5.75rem; */
63 | /* background: #6b6b6b; */
64 | text-align: center;
65 | margin-top: 4px;
66 | }
67 |
68 | #currentTimeDisplay {
69 | text-align: center;
70 | color: rgba(255, 255, 255, 0.3);
71 | /* padding: .5em; */
72 | }
73 |
74 | .buttonWrapper {
75 | width: 100%;
76 | position: relative;
77 | /* background-color: #444; */
78 | height: 32px;
79 | margin-top: 4px;
80 | padding: 4px;
81 | }
82 |
83 | .overVideoButtonWrapper {
84 | width: 100%;
85 | position: absolute;
86 | transform: translate(0%, -100%);
87 | height: 36px;
88 | /* background-color: rgba(0, 0, 0, 0.05); */
89 | }
90 |
91 | .hoverButton {
92 | border: 0;
93 | cursor: pointer;
94 | background-color: transparent;
95 | outline:none;
96 | -webkit-user-select:none;
97 | }
98 |
99 | .inPoint {
100 | position: absolute;
101 | bottom: 0;
102 | left: 0;
103 | opacity: 0.5;
104 | }
105 |
106 | .outPoint {
107 | position: absolute;
108 | bottom: 0;
109 | right: 0;
110 | opacity: 0.5;
111 | }
112 |
113 | .choose {
114 | position: absolute;
115 | bottom: 0;
116 | /* left: calc(50% - 24px); */
117 | /* top: 50%; */
118 | left: 50%;
119 | transform: translate(-50%, 0%);
120 | opacity: 0.5;
121 | /* cursor: col-resize; */
122 | }
123 |
124 | .previousScene {
125 | position: absolute;
126 | bottom: 0;
127 | left: 30%;
128 | transform-origin: center bottom;
129 | transform: translate(-100%, 0%);
130 | opacity: 0.5;
131 | }
132 |
133 | .nextScene {
134 | position: absolute;
135 | bottom: 0;
136 | right: 28%;
137 | transform-origin: center bottom;
138 | transform: translate(50%, 0%);
139 | opacity: 0.5;
140 | }
141 |
142 | .hundredFramesBack {
143 | position: absolute;
144 | bottom: 0;
145 | left: 35%;
146 | transform-origin: center bottom;
147 | transform: translate(-50%, 0%);
148 | opacity: 0.5;
149 | }
150 |
151 | .tenFramesBack {
152 | position: absolute;
153 | bottom: 0;
154 | left: 40%;
155 | transform-origin: center bottom;
156 | transform: translate(-50%, 0%);
157 | opacity: 0.5;
158 | }
159 |
160 | .oneFrameBack {
161 | position: absolute;
162 | bottom: 0;
163 | left: 44%;
164 | transform-origin: center bottom;
165 | transform: translate(-50%, 0%);
166 | opacity: 0.5;
167 | }
168 |
169 | .hundredFramesForward {
170 | position: absolute;
171 | bottom: 0;
172 | right: 35%;
173 | transform-origin: center bottom;
174 | transform: translate(-50%, 0%);
175 | opacity: 0.5;
176 | }
177 |
178 | .tenFramesForward {
179 | position: absolute;
180 | bottom: 0;
181 | right: 40%;
182 | transform-origin: center bottom;
183 | transform: translate(-50%, 0%);
184 | opacity: 0.5;
185 | }
186 |
187 | .oneFrameForward {
188 | position: absolute;
189 | bottom: 0;
190 | right: 44%;
191 | transform-origin: center bottom;
192 | transform: translate(-50%, 0%);
193 | opacity: 0.5;
194 | }
195 |
196 | .html5Button {
197 | position: absolute;
198 | top: 0;
199 | left: 0;
200 | margin-top: 8px;
201 | margin-left: 8px;
202 | z-Index: 1;
203 | }
204 |
205 | .saveFrameButton {
206 | position: absolute;
207 | bottom: 92px;
208 | left: 50%;
209 | margin-top: 8px;
210 | margin-right: 8px;
211 | z-Index: 1;
212 | }
213 |
214 | .centerTheButton {
215 | transform: translate(-50%, 0%);
216 | }
217 |
218 | .backButton {
219 | position: absolute;
220 | top: 0;
221 | right: 0;
222 | margin-top: 8px;
223 | margin-right: 8px;
224 | z-Index: 1;
225 | }
226 |
227 | .cutMergeButton {
228 | position: absolute;
229 | bottom: 0;
230 | left: 50%;
231 | transform-origin: center bottom;
232 | transform: translateX(-60%);
233 | /* display: block; */
234 | }
235 |
236 | .changeModeButton {
237 | position: absolute;
238 | bottom: 0;
239 | left: 0;
240 | margin-left: 8px;
241 | }
242 |
243 | .textButton {
244 | font-family: 'Franchise', 'Roboto Condensed';
245 | color: #ffffff;
246 | font-size: 30px;
247 | opacity: 0.5;
248 | -webkit-user-select:none;
249 | /* text-shadow: 1px 1px 80px rgba(0,0,0,0.6); */
250 | }
251 |
252 | .noVideoText {
253 | position: absolute;
254 | top: 30%;
255 | left: 50%;
256 | transform-origin: center center;
257 | transform: translateX(-50%);
258 | z-Index: 1;
259 | }
260 |
--------------------------------------------------------------------------------
/app/components/SceneGrid.css:
--------------------------------------------------------------------------------
1 | .grid {
2 | display: block;
3 | height: 100%;
4 | white-space: nowrap;
5 | border: 0;
6 | overflow: auto;
7 | margin: auto;
8 | outline: none;
9 | position: relative;
10 | }
11 |
12 | .gridItem {
13 | float: left;
14 | /*width: 168px;*/
15 | /*padding: 8px;*/
16 | /*background: #3b3b3b;*/
17 | /* border: 0; */
18 | margin: 0.5px;
19 | position: relative;
20 | outline: none;
21 | -webkit-user-select: none;
22 | background-repeat: no-repeat;
23 | /* background-size: cover; */
24 | /* background-size: auto 100%; */
25 | /* background-size: auto calc(100% + 20px); */
26 | background-position: center;
27 | /* background-blend-mode: overlay; */
28 | /* background-repeat: space; */
29 | /* background-clip: padding-box; */
30 | /* border: 4px solid rgba(0,0,0,0.1); */
31 | border-style: solid;
32 | border-color: rgba(0,0,0,0.1);
33 | }
34 |
35 | .gridHeader {
36 | background: #3b3b3b;
37 | color: #eee;
38 | font-family: 'Open sans';
39 | padding-left: 4px;
40 | overflow: hidden;
41 | position: relative;
42 | line-height: 0;
43 | }
44 |
45 | .gridHeaderImageAndText {
46 | float: left;
47 | transform-origin: 'left bottom';
48 | vertical-align: baseline;
49 | }
50 |
51 | .gridHeaderImage {
52 | /* display: inline-block; */
53 | position: absolute;
54 | }
55 |
56 | .gridHeaderText {
57 | display: inline-block;
58 | white-space: normal;
59 | color: #ff925e;
60 | }
61 |
62 | .gridHeaderTextName {
63 | font-size: 12px;
64 | font-weight: bold;
65 | color: #ffd3bf;
66 | letter-spacing: 0.3px;
67 | text-align: right;
68 | }
69 |
70 | .timelineWrapper {
71 | position: relative;
72 | background: rgba(255, 219, 204, 0.2);
73 | clear: both;
74 | }
75 |
76 | .timelineCut {
77 | position: absolute;
78 | background: #e85d22;
79 | height: 100%;
80 | }
81 |
82 | .timelineThumbIndicator {
83 | position: absolute;
84 | min-width: 1px;
85 | background: #ffdbcc;
86 | }
87 |
88 | .gridItemSelected {
89 | outline-style: solid;
90 | outline-color: #ff5006;
91 | background-color: #ff5006;
92 | /* box-shadow: 5px 0px 0px 0px #FF5006 */
93 | }
94 |
95 | .sceneExpanded {
96 | outline-style: solid;
97 | outline-color: #ffd3bf;
98 | /* outline-color: #ff5006; */
99 | outline-offset: -1px;
100 | /* background-color: #ff5006; */
101 | /* box-shadow: 5px 0px 0px 0px #FF5006 */
102 | }
103 |
104 | /* .gridItemSelected:after {
105 | content: "";
106 | background-color: #FF5006;
107 | position: absolute;
108 | width: 5px;
109 | height: 100%;
110 | top: 0;
111 | right: 0;
112 | display: block;
113 | transform: translateX(10px);
114 | } */
115 |
116 | .gridItem:hover {
117 | /* background: #eee; */
118 | cursor: pointer;
119 | }
120 |
121 | .image {
122 | float: left;
123 | /*width: 168px;*/
124 | /*padding: 8px;*/
125 | /*background: #3b3b3b;*/
126 | /*border: 0;*/
127 | /*margin: 4px;*/
128 | /* border-radius:8px; */
129 | /*position: relative;*/
130 | }
131 |
132 | .gridForPrinting {
133 | margin-left: -50px;
134 | }
135 |
136 | .frameNumber {
137 | position: absolute;
138 | top: 0;
139 | left: 0;
140 | background: #eee;
141 | border-radius: 2px;
142 | font-family: 'Open sans';
143 | font-size: 10px;
144 | line-height: 10px;
145 | padding: 1px;
146 | color: #000000;
147 | transform-origin: left top;
148 | /*opacity: 0.5;*/
149 | }
150 |
151 | .dragHandleButton {
152 | position: absolute;
153 | /* top: calc(50% - 28px);
154 | left: calc(50% - 72px); */
155 | top: 50%;
156 | left: 50%;
157 | transform: translate(-50%, -50%) scale(0.95);
158 | /* width: 144px; */
159 | /* height: 80%; */
160 | opacity: 1;
161 | cursor: move;
162 | background-color: transparent;
163 | border: 0;
164 | outline: none;
165 | }
166 |
167 | .dragHandleIcon {
168 | position: absolute;
169 | /* top: calc(50% - 28px);
170 | left: calc(50% - 72px); */
171 | top: 50%;
172 | left: 50%;
173 | transform: translate(-50%, -50%);
174 | width: 144px;
175 | opacity: 1;
176 | /* cursor: move; */
177 | }
178 |
179 | .whileDragging {
180 | box-shadow: 0px 0px 80px 10px rgba(0, 0, 0, 0.5);
181 | z-index: 1000; /* has to be higher than dropzoneshow*/
182 | }
183 |
184 | .hide {
185 | position: absolute;
186 | outline: none;
187 | top: 0;
188 | left: 50%;
189 | opacity: 0.5;
190 | background-color: transparent;
191 | }
192 |
193 | .inPoint {
194 | position: absolute;
195 | outline: none;
196 | bottom: 0;
197 | left: 0;
198 | opacity: 0.5;
199 | }
200 |
201 | /* .back {
202 | position: absolute;
203 | outline:none;
204 | bottom: 0;
205 | left: calc(50% - 24px - 48px);
206 | opacity: 0.5;
207 | } */
208 |
209 | .save {
210 | position: absolute;
211 | outline: none;
212 | top: 0;
213 | right: 0;
214 | opacity: 0.5;
215 | }
216 |
217 | .textButton {
218 | font-family: 'Franchise';
219 | color: #ffffff;
220 | font-size: 30px;
221 | opacity: 0.5;
222 | }
223 |
224 | /* .forward {
225 | position: absolute;
226 | outline:none;
227 | bottom: 0;
228 | left: calc(50% + 24px);
229 | opacity: 0.5;
230 | } */
231 |
232 | .outPoint {
233 | position: absolute;
234 | outline: none;
235 | bottom: 0;
236 | right: 0;
237 | opacity: 0.5;
238 | }
239 |
240 | .dim {
241 | transition: opacity 0.3s;
242 | opacity: 0.2 !important;
243 | transition-delay: 0.5s;
244 | }
245 |
246 | .hoverButton {
247 | border: 0;
248 | cursor: pointer;
249 | background-color: transparent;
250 | outline: none;
251 | -webkit-user-select: none;
252 | }
253 |
254 | .opaque {
255 | opacity: 1 !important;
256 | }
257 |
258 | .lineBreak {
259 | clear: both;
260 | }
261 |
262 | .sheetTypeSwitchButton {
263 | position: fixed;
264 | transform-origin: center center;
265 | transform: translateY(-50%);
266 | top: 80%;
267 | left: 0;
268 | margin-top: 8px;
269 | padding-left: 8px;
270 | z-Index: 1;
271 | }
272 |
--------------------------------------------------------------------------------
/test/e2e/e2e.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint no-restricted-syntax: ["error", "WithStatement", "BinaryExpression[operator='in']"] */
2 |
3 | import { Application } from 'spectron';
4 | import electronPath from 'electron';
5 | import fakeDialog from 'spectron-fake-dialog';
6 | import path from 'path';
7 | import fs from 'fs';
8 | import '../../internals/scripts/CheckBuildsExist';
9 |
10 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000;
11 |
12 | const delay = time => new Promise(resolve => setTimeout(resolve, time));
13 |
14 | describe('main window', function spec() {
15 | beforeAll(async () => {
16 | this.app = new Application({
17 | path: electronPath,
18 | args: [path.join(__dirname, '..', '..', 'app'), '--softreset']
19 | // args: [path.join(__dirname, '..', '..', 'app'), '--softreset', '--debug']
20 | });
21 | fakeDialog.apply(this.app);
22 | console.log(await this.app.getSettings());
23 | await delay(3000);
24 | return this.app.start();
25 | });
26 |
27 | afterAll(() => {
28 | if (this.app && this.app.isRunning()) {
29 | return this.app.stop();
30 | }
31 | });
32 |
33 | // prepare to store window title and handle for later use
34 | const windowObject = {};
35 |
36 | it('should open the 4 windows', async () => {
37 | const { client } = this.app;
38 | const windowHandles = await client.windowHandles();
39 | const windowNames = [];
40 | for (const windowHandleValue of windowHandles.value) {
41 | await client.window(windowHandleValue);
42 | const title = await client.getTitle();
43 | // only add to array if it has a title
44 | // this will exclude debug windows
45 | if (title !== '') {
46 | windowNames.push(title)
47 | // store window title and handle for later use
48 | windowObject[title] = windowHandleValue
49 | }
50 | }
51 | console.log(windowNames.sort());
52 |
53 | expect(windowNames).toEqual([
54 | 'MoviePrint',
55 | 'MoviePrint_databaseWorker',
56 | 'MoviePrint_opencvWorker',
57 | 'MoviePrint_worker'
58 | ]);
59 | });
60 |
61 | it("shouldn't have any logs in console of all windows", async () => {
62 | const { client } = this.app;
63 | Object.values(windowObject).map(async windowHandleValue => {
64 | await client.window(windowHandleValue);
65 | const logs = await client.getRenderProcessLogs();
66 | // Print renderer process logs for MoviePrint renderer
67 | logs.forEach(log => {
68 | if (log.level === 'SEVERE' &&
69 | log.message !== 'data:image/jpeg;base64, undefined - Failed to load resource: net::ERR_INVALID_URL') {
70 | expect(log.level).not.toEqual('SEVERE');
71 | }
72 | });
73 | })
74 | });
75 |
76 | it('should load a movie and get all 16 thumbs', async () => {
77 | const { client } = this.app;
78 | await client.window(windowObject['MoviePrint']); // focus main window
79 | const dragndropInput = '[type="file"]'; // selecting the input div via type
80 | const pathOfMovie = path.join(__dirname, '..', '..', 'resources', 'test_files', 'test_movie_1.mp4');
81 | await client.chooseFile(dragndropInput, pathOfMovie);
82 | const val = await client.getValue(dragndropInput)
83 | console.log(val);
84 | client.waitForExist('[data-tid="thumbGridDiv"]', 5000);
85 | expect(await client.isExisting('[data-tid="thumbGridDiv"]')).toBe(true);
86 | expect(await client.isExisting('#thumb15')).toBe(true);
87 | // await client.browserWindow.capturePage().then((imageBuffer) => {
88 | // fs.writeFile('end of should load a movie and get all 16 thumbs.png', imageBuffer);
89 | // return undefined;
90 | // }).catch((err) => {
91 | // console.error(err);
92 | // });
93 | });
94 |
95 | it('should increase thumb count to 20', async () => {
96 | const { client } = this.app;
97 | // show settings menu
98 | await client.waitForExist('[data-tid="moreSettingsBtn"]', 3000);
99 | await client.element('[data-tid="moreSettingsBtn"]').click();
100 |
101 | // move down to switch sliders to inputs
102 | await client.moveToObject('[data-tid="changeCachedFramesSizeDropdown"]');
103 | await client.waitForVisible('[data-tid="showSlidersCheckbox"]', 3000);
104 | await client.element('[data-tid="showSlidersCheckbox"]').click();
105 |
106 | // move up and change column count
107 | await client.moveToObject('[data-tid="columnCountInput"]');
108 | await client.waitForVisible('[data-tid="columnCountInput"]', 3000);
109 | await client.setValue('[data-tid="columnCountInput"] input', 5);
110 | await client.keys('Enter');
111 | await client.element('[data-tid="applyNewGridBtn"]').click();
112 |
113 | await client.waitForExist('#thumb19', 3000);
114 | expect(await client.isExisting('#thumb19')).toBe(true);
115 | });
116 |
117 | // it('should open a dialog', async () => {
118 | // const { client } = this.app;
119 | // fakeDialog.mock([ { method: 'showOpenDialogSync', value: ['faked.txt'] } ])
120 | //
121 | // await client.click('[data-tid=openMoviesBtn]');
122 | // const pathOfMovie = await client.getText('#return-value');
123 | // console.log(pathOfMovie);
124 | // expect(await findCounter().getText()).toBe('0');
125 | // });
126 |
127 | // const findThumbGridDiv = () => this.app.client.element('[data-tid="thumbGridDiv"]');
128 | // const thumbs = $$(".//*[contains(@class,'ThumbGrid__gridItem')]")
129 | // const findThumbs = () => this.app.client.elements('.//*[contains(@class,"ThumbGrid__gridItem")]');
130 | // const thumbs = await findThumbs();
131 | // console.log(thumbs);
132 | // console.log(thumbs.value.length);
133 | // await delay(1500);
134 |
135 | // const findCounter = () => this.app.client.element('[data-tid="counter"]');
136 | //
137 | // const findButtons = async () => {
138 | // const { value } = await this.app.client.elements('[data-tclass="btn"]');
139 | // return value.map(btn => btn.ELEMENT);
140 | // };
141 |
142 | });
143 |
--------------------------------------------------------------------------------
/resources/weights/face_expression_model-weights_manifest.json:
--------------------------------------------------------------------------------
1 | [{"weights":[{"name":"dense0/conv0/filters","shape":[3,3,3,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0057930146946626555,"min":-0.7125408074435067}},{"name":"dense0/conv0/bias","shape":[32],"dtype":"float32"},{"name":"dense0/conv1/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006473719839956246,"min":-0.6408982641556684}},{"name":"dense0/conv1/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010509579321917366,"min":-1.408283629136927}},{"name":"dense0/conv1/bias","shape":[32],"dtype":"float32"},{"name":"dense0/conv2/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005666389652326995,"min":-0.7252978754978554}},{"name":"dense0/conv2/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010316079270605948,"min":-1.1760330368490781}},{"name":"dense0/conv2/bias","shape":[32],"dtype":"float32"},{"name":"dense0/conv3/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0063220320963392074,"min":-0.853474333005793}},{"name":"dense0/conv3/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010322785377502442,"min":-1.4658355236053466}},{"name":"dense0/conv3/bias","shape":[32],"dtype":"float32"},{"name":"dense1/conv0/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0042531527724920535,"min":-0.5741756242864272}},{"name":"dense1/conv0/pointwise_filter","shape":[1,1,32,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010653339647779278,"min":-1.1825207009035}},{"name":"dense1/conv0/bias","shape":[64],"dtype":"float32"},{"name":"dense1/conv1/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005166931012097527,"min":-0.6355325144879957}},{"name":"dense1/conv1/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.011478300188101974,"min":-1.3888743227603388}},{"name":"dense1/conv1/bias","shape":[64],"dtype":"float32"},{"name":"dense1/conv2/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006144821410085641,"min":-0.8479853545918185}},{"name":"dense1/conv2/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010541967317169788,"min":-1.3809977185492421}},{"name":"dense1/conv2/bias","shape":[64],"dtype":"float32"},{"name":"dense1/conv3/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005769844849904378,"min":-0.686611537138621}},{"name":"dense1/conv3/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010939095534530341,"min":-1.2689350820055196}},{"name":"dense1/conv3/bias","shape":[64],"dtype":"float32"},{"name":"dense2/conv0/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0037769308277204924,"min":-0.40790852939381317}},{"name":"dense2/conv0/pointwise_filter","shape":[1,1,64,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01188667194516051,"min":-1.4382873053644218}},{"name":"dense2/conv0/bias","shape":[128],"dtype":"float32"},{"name":"dense2/conv1/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006497045825509464,"min":-0.8381189114907208}},{"name":"dense2/conv1/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.011632198913424622,"min":-1.3377028750438316}},{"name":"dense2/conv1/bias","shape":[128],"dtype":"float32"},{"name":"dense2/conv2/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005947182225246056,"min":-0.7969224181829715}},{"name":"dense2/conv2/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.011436844339557722,"min":-1.4524792311238306}},{"name":"dense2/conv2/bias","shape":[128],"dtype":"float32"},{"name":"dense2/conv3/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006665432686899222,"min":-0.8998334127313949}},{"name":"dense2/conv3/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01283421422920975,"min":-1.642779421338848}},{"name":"dense2/conv3/bias","shape":[128],"dtype":"float32"},{"name":"dense3/conv0/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004711699953266218,"min":-0.6737730933170692}},{"name":"dense3/conv0/pointwise_filter","shape":[1,1,128,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010955964817720302,"min":-1.3914075318504784}},{"name":"dense3/conv0/bias","shape":[256],"dtype":"float32"},{"name":"dense3/conv1/depthwise_filter","shape":[3,3,256,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00554193468654857,"min":-0.7149095745647656}},{"name":"dense3/conv1/pointwise_filter","shape":[1,1,256,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.016790372250126858,"min":-2.484975093018775}},{"name":"dense3/conv1/bias","shape":[256],"dtype":"float32"},{"name":"dense3/conv2/depthwise_filter","shape":[3,3,256,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006361540626077091,"min":-0.8142772001378676}},{"name":"dense3/conv2/pointwise_filter","shape":[1,1,256,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01777329678628959,"min":-1.7062364914838006}},{"name":"dense3/conv2/bias","shape":[256],"dtype":"float32"},{"name":"dense3/conv3/depthwise_filter","shape":[3,3,256,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006900275922289082,"min":-0.8625344902861353}},{"name":"dense3/conv3/pointwise_filter","shape":[1,1,256,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.015449936717164282,"min":-1.9003422162112067}},{"name":"dense3/conv3/bias","shape":[256],"dtype":"float32"},{"name":"fc/weights","shape":[256,7],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004834276554631252,"min":-0.7203072066400565}},{"name":"fc/bias","shape":[7],"dtype":"float32"}],"paths":["face_expression_model-shard1"]}]
--------------------------------------------------------------------------------