This commit is contained in:
Dan Steinman 2020-07-06 22:41:26 -04:00 коммит произвёл Alexandre Lissy
Родитель 336f260958
Коммит d765400548
14 изменённых файлов: 16885 добавлений и 0 удалений

Просмотреть файл

@ -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#:
-----------

96
electron/.gitignore поставляемый Normal file
Просмотреть файл

@ -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/

68
electron/Readme.md Normal file
Просмотреть файл

@ -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`

16313
electron/package-lock.json сгенерированный Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

97
electron/package.json Normal file
Просмотреть файл

@ -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
};

60
electron/src/App.js Normal file
Просмотреть файл

@ -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;

5
electron/src/index.js Normal file
Просмотреть файл

@ -0,0 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App/>, document.getElementById('root'));