add electron example
This commit is contained in:
Родитель
336f260958
Коммит
d765400548
|
@ -29,6 +29,7 @@ JavaScript:
|
|||
* `Node.JS microphone VAD streaming <nodejs_mic_vad_streaming/Readme.md>`_
|
||||
* `Node.JS wav <nodejs_wav/Readme.md>`_
|
||||
* `Web Microphone Websocket streaming <web_microphone_websocket/Readme.md>`_
|
||||
* `Electron wav transcriber <electron/Readme.md>`_
|
||||
|
||||
Windows/C#:
|
||||
-----------
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
*.gz
|
||||
/build
|
||||
/dist
|
||||
/public/audio
|
||||
/public/*.pbmm
|
||||
/public/*.scorer
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
.DS_Store
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# nuxt.js build output
|
||||
.nuxt
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# Webpack
|
||||
.webpack/
|
||||
|
||||
# Electron-Forge
|
||||
out/
|
|
@ -0,0 +1,68 @@
|
|||
# DeepSpeech Electron example
|
||||
|
||||
This is an example of DeepSpeech running in an Electron app with a ReactJS front-end and processing .wav files.
|
||||
|
||||
## Install
|
||||
|
||||
Install NPM modules:
|
||||
|
||||
```
|
||||
npm install
|
||||
npm run rebuild
|
||||
```
|
||||
|
||||
Download and extract audio files to `/public` directory
|
||||
|
||||
```
|
||||
wget https://github.com/mozilla/DeepSpeech/releases/download/v0.7.0/audio-0.7.0.tar.gz
|
||||
tar xfvz audio-0.7.0.tar.gz -C ./public/
|
||||
```
|
||||
|
||||
(Optional) Download or softlink DeepSpeech 0.7.4 model files to the root of the project:
|
||||
|
||||
```
|
||||
wget https://github.com/mozilla/DeepSpeech/releases/download/v0.7.4/deepspeech-0.7.4-models.pbmm
|
||||
wget https://github.com/mozilla/DeepSpeech/releases/download/v0.7.4/deepspeech-0.7.4-models.scorer
|
||||
```
|
||||
|
||||
If the files do not exist, they will be downloaded.
|
||||
|
||||
## Run
|
||||
|
||||
Run development version (Mac/Linux):
|
||||
|
||||
```
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Run development version (Windows):
|
||||
|
||||
```
|
||||
export BROWSER=none
|
||||
npm run dev-win
|
||||
```
|
||||
|
||||
## Package
|
||||
|
||||
Build distributable package (Mac/Linux):
|
||||
|
||||
```
|
||||
npm run dist
|
||||
```
|
||||
|
||||
Build distributable package (Windows installer):
|
||||
|
||||
```
|
||||
export BROWSER=none
|
||||
npm run dist-win
|
||||
```
|
||||
|
||||
Test the (dmg/appimage/exe) package file that has been generated in `/dist`.
|
||||
|
||||
## Uninstall
|
||||
|
||||
The model files download to the following directories and must be deleted manually
|
||||
|
||||
- MacOSX: `~/Library/Application\ Support/deepspeech-electron`
|
||||
- Linux: `~/.config/deepspeech-electron`
|
||||
- Windows: `~/AppData/Roaming/deepspeech-electron`
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,97 @@
|
|||
{
|
||||
"name": "deepspeech-electron",
|
||||
"productName": "deepspeech-electron",
|
||||
"version": "1.0.0",
|
||||
"description": "My Electron application description",
|
||||
"main": "public/electron.js",
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
"dev": "concurrently \"BROWSER=none npm start\" \"wait-on http://localhost:3000 && electron --inspect=5858 .\"",
|
||||
"dev-win": "concurrently \"npm start\" \"wait-on http://localhost:3000 && electron --inspect=5858 .\"",
|
||||
"rebuild": "npm rebuild --runtime=electron --target=9.0.5 --disturl=https://atom.io/download/atom-shell --abi=75",
|
||||
"pack": "yarn run build && electron-builder --dir",
|
||||
"dist": "yarn run build && electron-builder",
|
||||
"dist-win": "yarn run build && electron-builder --x64"
|
||||
},
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"homepage": "./",
|
||||
"build": {
|
||||
"appId": "deepspeech-electron",
|
||||
"productName": "deepspeech-electron",
|
||||
"files": [
|
||||
"build/**/*",
|
||||
"node_modules/**/*",
|
||||
"package.json"
|
||||
],
|
||||
"buildDependenciesFromSource": true,
|
||||
"artifactName": "deepspeech-electron-${version}-${os}-${arch}.${ext}",
|
||||
"dmg": {
|
||||
"title": "${productName}"
|
||||
},
|
||||
"mac": {
|
||||
"category": "public.app-category.utilities",
|
||||
"target": [
|
||||
{
|
||||
"target": "dmg",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "zip",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"identity": null
|
||||
},
|
||||
"win": {
|
||||
"target": "nsis",
|
||||
"artifactName": "deepspeech-electron-${version}-${os}-${arch}.${ext}"
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
{
|
||||
"target": "AppImage"
|
||||
}
|
||||
],
|
||||
"category": "Utility"
|
||||
}
|
||||
},
|
||||
"keywords": [],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"deepspeech": "^0.7.4",
|
||||
"electron-is-dev": "^1.1.0",
|
||||
"lodash": "^4.17.15",
|
||||
"node-abi": "^2.18.0",
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-scripts": "^3.4.1",
|
||||
"request": "^2.88.2",
|
||||
"wav": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^5.0.0",
|
||||
"electron": "9.0.5",
|
||||
"electron-builder": "^22.7.0",
|
||||
"electron-rebuild": "^1.11.0",
|
||||
"wait-on": "^3.3.0"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
const electron = require('electron');
|
||||
const Path = require('path');
|
||||
const app = electron.app;
|
||||
const ipcMain = electron.ipcMain;
|
||||
const BrowserWindow = electron.BrowserWindow;
|
||||
const isDev = require('electron-is-dev');
|
||||
if (isDev) process.env.NODE_ENV = 'dev';
|
||||
const {recognizeWav} = require('./recognize-wav');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
let mainWindow;
|
||||
|
||||
function createWindow(model) {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 480,
|
||||
height: 480,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
nodeIntegrationInWorker: false,
|
||||
preload: __dirname + '/preload.js'
|
||||
}
|
||||
});
|
||||
|
||||
mainWindow.loadURL(isDev ? 'http://localhost:3000' : `file://${Path.join(__dirname, '../build/index.html')}`);
|
||||
|
||||
if (isDev) {
|
||||
// open Chrome Development Console
|
||||
// mainWindow.webContents.openDevTools();
|
||||
}
|
||||
|
||||
mainWindow.on('closed', () => mainWindow = null);
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
app.quit()
|
||||
});
|
||||
|
||||
// message from front-end App.js, request that this file be processed by DeepSpeech
|
||||
ipcMain.handle('recognize-wav', async function (event, file) {
|
||||
let filePath = path.resolve(__dirname, 'audio', file);
|
||||
return recognizeWav(filePath, model);
|
||||
});
|
||||
|
||||
// message from front-end App.js, retrieve list of .wav files in /public/audio
|
||||
ipcMain.handle('load-files', function (event) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
try {
|
||||
let audioPath = path.resolve(__dirname, 'audio');
|
||||
fs.readdir(audioPath, function (err, files) {
|
||||
files = files.filter(function (file) {
|
||||
return file.endsWith('.wav');
|
||||
});
|
||||
resolve(files);
|
||||
});
|
||||
} catch (e) {
|
||||
reject(e.toString())
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return mainWindow;
|
||||
}
|
||||
|
||||
module.exports = createWindow;
|
|
@ -0,0 +1,22 @@
|
|||
const request = require('request');
|
||||
const fs = require('fs');
|
||||
|
||||
// generic http download
|
||||
function download(url, dest, callback) {
|
||||
var file = fs.createWriteStream(dest);
|
||||
console.log('Downloading:', url);
|
||||
const sendReq = request.get(url);
|
||||
sendReq.on('response', (response) => {
|
||||
if (response.statusCode === 200) {
|
||||
console.log('PLEASE WAIT...');
|
||||
sendReq.pipe(file);
|
||||
}
|
||||
});
|
||||
file.on('finish', () => {
|
||||
file.close();
|
||||
console.log('Saved:', dest);
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = download;
|
|
@ -0,0 +1,24 @@
|
|||
const electron = require('electron');
|
||||
const app = electron.app;
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const createWindow = require('./create-window');
|
||||
const {getModel} = require('./recognize-wav');
|
||||
|
||||
let appDataPath;
|
||||
|
||||
if (fs.existsSync(path.resolve(__dirname, '../deepspeech-0.7.4-models.pbmm'))) {
|
||||
// if the deepspeech model was found at the root, use that directory
|
||||
appDataPath = path.resolve(__dirname, '..');
|
||||
}
|
||||
else {
|
||||
// otherwise use the electron "appData" path
|
||||
appDataPath = path.resolve(electron.app.getPath('appData'), 'deepspeech-electron');
|
||||
}
|
||||
|
||||
app.on('ready', function () {
|
||||
getModel(appDataPath, function (model) {
|
||||
console.log('model loaded')
|
||||
createWindow(model);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,44 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<link rel="stylesheet" href="fonts/stylesheet.css" type="text/css" charset="utf-8" />
|
||||
<title>DeepSpeech Electron Example</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
window.ipcRenderer = require('electron').ipcRenderer;
|
||||
console.log('ipcRenderer', window.ipcRenderer);
|
|
@ -0,0 +1,64 @@
|
|||
const DeepSpeech = require('deepspeech');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const wav = require('wav');
|
||||
const download = require('./download');
|
||||
|
||||
// return the deepspeech model or download it if it is not found
|
||||
function getModel(appDataPath, callback) {
|
||||
let modelPath = path.resolve(appDataPath, 'deepspeech-0.7.4-models.pbmm');
|
||||
let scorerPath = path.resolve(appDataPath, 'deepspeech-0.7.4-models.scorer');
|
||||
if (fs.existsSync(modelPath) && fs.existsSync(scorerPath)) {
|
||||
callback(createModel(modelPath, scorerPath));
|
||||
}
|
||||
else {
|
||||
// if the model files do not exist, download and save them to AppData path
|
||||
console.log('\nDOWNLOADING MODEL TO: '+appDataPath+'\n');
|
||||
const downloadPath = 'https://github.com/mozilla/DeepSpeech/releases/download/v0.7.4/deepspeech-0.7.4-models';
|
||||
download(downloadPath+'.pbmm', modelPath, function() {
|
||||
download(downloadPath+'.scorer', scorerPath, function() {
|
||||
callback(createModel(modelPath, scorerPath));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// create the deepspeech model
|
||||
function createModel(modelPath, scorerPath) {
|
||||
const model = new DeepSpeech.Model(modelPath);
|
||||
model.enableExternalScorer(scorerPath);
|
||||
return model;
|
||||
}
|
||||
|
||||
// create a deepspeech stream to process a .wav file
|
||||
function recognizeWav(path, model) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
try {
|
||||
let modelStream = model.createStream();
|
||||
const bufferSize = 512;
|
||||
const file = fs.createReadStream(path, {highWaterMark: bufferSize});
|
||||
const reader = new wav.Reader();
|
||||
reader.on('format', function (format) {
|
||||
if (format.sampleRate !== model.sampleRate()) {
|
||||
reject(new Error('invalid sample rate: '+format.sampleRate));
|
||||
}
|
||||
reader.on('end', function () {
|
||||
const results = modelStream.finishStream();
|
||||
resolve(results);
|
||||
});
|
||||
reader.on('data', function (data) {
|
||||
modelStream.feedAudioContent(data);
|
||||
});
|
||||
});
|
||||
file.pipe(reader);
|
||||
}
|
||||
catch(e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getModel,
|
||||
recognizeWav
|
||||
};
|
|
@ -0,0 +1,60 @@
|
|||
import React, {Component} from 'react';
|
||||
|
||||
class App extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
error: null,
|
||||
files: [],
|
||||
results: {}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// when the component mounts, get the list of .wav files
|
||||
window.ipcRenderer.invoke('load-files')
|
||||
.then(files => {
|
||||
console.log('files', files);
|
||||
this.setState({
|
||||
loading: false,
|
||||
files
|
||||
}, () => {
|
||||
files.forEach(file => {
|
||||
// request that each file be processed by deepspeech
|
||||
console.log('recognize', file);
|
||||
window.ipcRenderer.invoke('recognize-wav', file).then(result => {
|
||||
// add the recognition results to this.state.results
|
||||
console.log('result', result);
|
||||
const results = {...this.state.results};
|
||||
results[file] = result;
|
||||
this.setState({results});
|
||||
});
|
||||
})
|
||||
});
|
||||
}).catch(e => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
error: e
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.loading) return 'Loading...';
|
||||
if (this.state.error) return 'Error: ' + this.state.error;
|
||||
return (<div className="App">
|
||||
<ul>
|
||||
{
|
||||
this.state.files.map((file, index) => {
|
||||
return (<li key={index}>
|
||||
{file} = {this.state.results[file] || '...'}
|
||||
</li>)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
|
@ -0,0 +1,5 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.render(<App/>, document.getElementById('root'));
|
Загрузка…
Ссылка в новой задаче