├── src ├── assets │ ├── .gitkeep │ └── img │ │ └── loading.svg ├── favicon.ico ├── tsconfig.app.json ├── tsconfig.spec.json ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── app │ ├── components │ │ ├── index.ts │ │ └── dialog-alert │ │ │ ├── dialog-alert.component.html │ │ │ └── dialog-alert.component.ts │ ├── models │ │ ├── IEvent.ts │ │ ├── index.ts │ │ ├── vision.ts │ │ └── nlp.ts │ ├── app.component.html │ ├── app.component.ts │ ├── pages │ │ ├── index.ts │ │ └── widgets │ │ │ ├── widgets.component.scss │ │ │ ├── widgets.component.spec.ts │ │ │ ├── widgets.component.html │ │ │ └── widgets.component.ts │ ├── services │ │ ├── index.ts │ │ ├── web-synth │ │ │ ├── web-synth.service.spec.ts │ │ │ └── web-synth.service.ts │ │ ├── web-speech │ │ │ ├── web-speech.service.spec.ts │ │ │ └── web-speech.service.ts │ │ ├── gcloud-speech │ │ │ ├── gcloud-speech.service.spec.ts │ │ │ └── gcloud-speech.service.ts │ │ ├── gcloud-nlp │ │ │ ├── gcloud-nlp.service.ts │ │ │ └── gcloud-nlp.service.spec.ts │ │ └── gcloud-vision │ │ │ ├── gcloud-vision.service.ts │ │ │ └── gcloud-vision.service.spec.ts │ ├── app.component.scss │ ├── app-routing.module.ts │ ├── app.component.spec.ts │ └── app.module.ts ├── typings.d.ts ├── styles.scss ├── main.ts ├── index.html ├── test.ts └── polyfills.ts ├── e2e ├── tsconfig.e2e.json ├── app.e2e-spec.ts └── widgets.po.ts ├── tsconfig.json ├── speech-server ├── tsconfig.json ├── src │ ├── environment.ts │ └── server.ts └── package.json ├── NOTICE ├── .gitignore ├── karma.conf.js ├── protractor.conf.js ├── .angular-cli.json ├── package.json ├── tslint.json └── README.md /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gridcellcoder/cloud-speech-and-vision-demos/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es6", 7 | "types": [ 8 | "jasmine", 9 | "node" 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "es2015", 6 | "baseUrl": "", 7 | "types": [] 8 | }, 9 | "exclude": [ 10 | "test.ts", 11 | "**/*.spec.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "baseUrl": "", 8 | "types": [ 9 | "jasmine", 10 | "node" 11 | ] 12 | }, 13 | "files": [ 14 | "test.ts" 15 | ], 16 | "include": [ 17 | "**/*.spec.ts", 18 | "**/*.d.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | speechServerUrl: 'ws://localhost:8000', 4 | 5 | gCloudProjectId: 'ENTER YOUR GLCOUD PROJECT ID HERE', 6 | gCloudProjectApiKey: 'ENTER YOUR OWN API KEY HERE', 7 | 8 | gCloudNLPApiUrl: 'https://language.googleapis.com/v1/documents:annotateText', 9 | gCloudVisionApiUrl: 'https://vision.googleapis.com/v1/images:annotate', 10 | }; 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "outDir": "./dist/out-tsc", 5 | "baseUrl": "src", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "lib": [ 16 | "es2016", 17 | "dom" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /speech-server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "baseUrl": "src", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "lib": [ 16 | "es2016", 17 | "dom" 18 | ] 19 | }, 20 | "include": [ 21 | "src/**/*.ts" 22 | ], 23 | "exclude": [ 24 | "node_modules" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017. GridCell Ltd 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | **/dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | **/node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | testem.log 34 | /typings 35 | 36 | # e2e 37 | /e2e/*.js 38 | /e2e/*.map 39 | 40 | # System Files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /src/app/components/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. GridCell Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export * from './dialog-alert/dialog-alert.component'; 18 | -------------------------------------------------------------------------------- /speech-server/src/environment.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. GridCell Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | export const environment = { 17 | gCloudProjectId: '' 18 | }; 19 | -------------------------------------------------------------------------------- /src/app/models/IEvent.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. GridCell Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export interface IEvent { 18 | type: string; 19 | value: string; 20 | } 21 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 16 |
17 | 18 |
19 | -------------------------------------------------------------------------------- /src/app/models/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. GridCell Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export * from './IEvent'; 18 | export * from './nlp'; 19 | export * from './vision'; 20 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false, 8 | speechServerUrl: 'ws://localhost:8000', 9 | 10 | gCloudProjectId: 'ENTER YOUR GLCOUD PROJECT ID HERE', 11 | gCloudProjectApiKey: 'ENTER YOUR OWN API KEY HERE', 12 | 13 | gCloudNLPApiUrl: 'https://language.googleapis.com/v1/documents:annotateText', 14 | gCloudVisionApiUrl: 'https://vision.googleapis.com/v1/images:annotate', 15 | }; 16 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. GridCell Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* SystemJS module definition */ 18 | declare var module: NodeModule; 19 | interface NodeModule { 20 | id: string; 21 | } 22 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. GridCell Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Component } from '@angular/core'; 18 | 19 | @Component({ 20 | selector: 'app-root', 21 | templateUrl: './app.component.html' 22 | }) 23 | export class AppComponent { } 24 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2017. GridCell Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | @import '~@angular/material/prebuilt-themes/deeppurple-amber.css'; 17 | 18 | $gray: #C6C6C5; 19 | 20 | @import './app/app.component.scss'; 21 | @import './app/pages/widgets/widgets.component.scss'; 22 | -------------------------------------------------------------------------------- /src/app/pages/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2017 GridCell Ltd 3 | 4 | Licensed to the Apache Software Foundation (ASF) under one 5 | or more contributor license agreements. See the NOTICE file 6 | distributed with this work for additional information 7 | regarding copyright ownership. The ASF licenses this file 8 | to you under the Apache License, Version 2.0 (the 9 | "License"); you may not use this file except in compliance 10 | with the License. You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, 15 | software distributed under the License is distributed on an 16 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | KIND, either express or implied. See the License for the 18 | specific language governing permissions and limitations 19 | under the License. 20 | */ 21 | 22 | export * from './widgets/widgets.component'; 23 | -------------------------------------------------------------------------------- /src/app/services/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. GridCell Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export * from './web-speech/web-speech.service'; 18 | export * from './web-synth/web-synth.service'; 19 | export * from './gcloud-speech/gcloud-speech.service'; 20 | export * from './gcloud-nlp/gcloud-nlp.service'; 21 | export * from './gcloud-vision/gcloud-vision.service'; 22 | -------------------------------------------------------------------------------- /speech-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "google-speech-demo-server", 3 | "version": "0.0.1", 4 | "description": "Speech Server", 5 | "license": "Apache License, Version 2.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/gridcellcoder/google-speech-demos/speech-server" 9 | }, 10 | "author": "GridCell", 11 | "contributors": [ 12 | "Nuno Freire 16 | 17 | 18 | 19 | 20 | Gridcell Google Speech Demo 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/app/components/dialog-alert/dialog-alert.component.html: -------------------------------------------------------------------------------- 1 | 16 |

{{title}}

17 |
{{text}}
18 |
19 | 20 | 21 | 22 |
23 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/0.13/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular/cli'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular/cli/plugins/karma') 14 | ], 15 | client:{ 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | reports: [ 'html', 'lcovonly' ], 20 | fixWebpackSourcePaths: true 21 | }, 22 | angularCli: { 23 | environment: 'dev' 24 | }, 25 | reporters: ['progress', 'kjhtml'], 26 | port: 9876, 27 | colors: true, 28 | logLevel: config.LOG_INFO, 29 | autoWatch: true, 30 | browsers: ['Chrome'], 31 | singleRun: false 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2017. GridCell Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | body { 17 | .loading-animation { 18 | width: 100%; 19 | line-height: 192px; 20 | text-align: center !important; 21 | vertical-align: middle; 22 | } 23 | 24 | .fill-space { 25 | flex: 1 1 auto; 26 | } 27 | 28 | app-root { 29 | .page { 30 | height: 100vh; 31 | padding: 15px; 32 | background-color: $gray; 33 | overflow: auto; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. GridCell Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { NgModule } from '@angular/core'; 18 | import { RouterModule, Routes } from '@angular/router'; 19 | import { PageWidgetsComponent } from './pages/index'; 20 | 21 | const routes: Routes = [ 22 | { path: '**', component: PageWidgetsComponent }, 23 | ]; 24 | 25 | @NgModule({ 26 | imports: [ RouterModule.forRoot(routes, { useHash: true }) ], 27 | exports: [ RouterModule ] 28 | }) 29 | export class AppRoutingModule { } 30 | -------------------------------------------------------------------------------- /e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2017 GridCell Ltd 3 | 4 | Licensed to the Apache Software Foundation (ASF) under one 5 | or more contributor license agreements. See the NOTICE file 6 | distributed with this work for additional information 7 | regarding copyright ownership. The ASF licenses this file 8 | to you under the Apache License, Version 2.0 (the 9 | "License"); you may not use this file except in compliance 10 | with the License. You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, 15 | software distributed under the License is distributed on an 16 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | KIND, either express or implied. See the License for the 18 | specific language governing permissions and limitations 19 | under the License. 20 | */ 21 | 22 | import { PageWidgetsComponent } from './widgets.po'; 23 | 24 | describe('gridcell-gcloud App', () => { 25 | let page: PageWidgetsComponent; 26 | 27 | beforeEach(() => { 28 | page = new PageWidgetsComponent(); 29 | page.navigateTo(); 30 | }); 31 | 32 | it('should have all the right widgets', () => { 33 | page.shouldHaveAllWidgets(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/app/components/dialog-alert/dialog-alert.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. GridCell Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Component, OnInit, Inject } from '@angular/core'; 18 | import { MdDialog, MdDialogRef, MdDialogConfig, MD_DIALOG_DATA } from '@angular/material'; 19 | 20 | @Component({ 21 | selector: 'dialog-alert', 22 | templateUrl: 'dialog-alert.component.html', 23 | }) 24 | export class DialogAlertComponent { 25 | title = 'Alert' 26 | text = 'Oops! Something wrong happened...'; 27 | 28 | constructor(public dialogRef: MdDialogRef, 29 | @Inject(MD_DIALOG_DATA) private data: any) { 30 | if (data) { 31 | this.title = data.title; 32 | this.text = data.text; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. GridCell Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { TestBed, async } from '@angular/core/testing'; 18 | import { RouterTestingModule } from '@angular/router/testing'; 19 | 20 | import { AppComponent } from './app.component'; 21 | 22 | describe('AppComponent', () => { 23 | beforeEach(async(() => { 24 | TestBed.configureTestingModule({ 25 | declarations: [ 26 | AppComponent 27 | ], 28 | imports: [ 29 | RouterTestingModule, 30 | ] 31 | }).compileComponents(); 32 | })); 33 | 34 | it('should create the app', async(() => { 35 | const fixture = TestBed.createComponent(AppComponent); 36 | const app = fixture.debugElement.componentInstance; 37 | expect(app).toBeTruthy(); 38 | })); 39 | }); 40 | -------------------------------------------------------------------------------- /src/app/models/vision.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. GridCell Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export enum VisionFeatureType { 18 | // TYPE_UNSPECIFIED = 0, 19 | FACE_DETECTION = 1, 20 | LANDMARK_DETECTION = 2, 21 | LOGO_DETECTION = 3, 22 | LABEL_DETECTION = 4, 23 | TEXT_DETECTION = 5, 24 | DOCUMENT_TEXT_DETECTION = 6, 25 | SAFE_SEARCH_DETECTION = 7, 26 | IMAGE_PROPERTIES = 8, 27 | CROP_HINTS = 9, 28 | WEB_DETECTION = 10 29 | } 30 | 31 | export class VisionAnnotateRequest { 32 | requests: VisionRequest[]; 33 | } 34 | 35 | export class VisionRequest { 36 | image: VisionImageContent; 37 | features: VisionFeature[]; 38 | } 39 | 40 | export class VisionImageContent { 41 | content: string; 42 | // source: any; 43 | } 44 | 45 | export class VisionFeature { 46 | type: VisionFeatureType; 47 | maxResults: number; 48 | } 49 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. GridCell Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Protractor configuration file, see link for more information 18 | // https://github.com/angular/protractor/blob/master/lib/config.ts 19 | 20 | const { SpecReporter } = require('jasmine-spec-reporter'); 21 | 22 | exports.config = { 23 | allScriptsTimeout: 11000, 24 | specs: [ 25 | './e2e/**/*.e2e-spec.ts' 26 | ], 27 | capabilities: { 28 | 'browserName': 'chrome' 29 | }, 30 | directConnect: true, 31 | baseUrl: 'http://localhost:4200/', 32 | framework: 'jasmine', 33 | jasmineNodeOpts: { 34 | showColors: true, 35 | defaultTimeoutInterval: 30000, 36 | print: function() {} 37 | }, 38 | onPrepare() { 39 | require('ts-node').register({ 40 | project: 'e2e/tsconfig.e2e.json' 41 | }); 42 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /.angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "project": { 4 | "name": "cloud-speech-and-vision-demos" 5 | }, 6 | "apps": [ 7 | { 8 | "root": "src", 9 | "outDir": "dist", 10 | "assets": [ 11 | "assets", 12 | "favicon.ico" 13 | ], 14 | "index": "index.html", 15 | "main": "main.ts", 16 | "polyfills": "polyfills.ts", 17 | "test": "test.ts", 18 | "tsconfig": "tsconfig.app.json", 19 | "testTsconfig": "tsconfig.spec.json", 20 | "prefix": "app", 21 | "styles": [ 22 | "../node_modules/bootstrap/dist/css/bootstrap.css", 23 | "../node_modules/font-awesome/scss/font-awesome.scss", 24 | "../node_modules/angular2-busy/build/style/busy.css", 25 | "styles.scss" 26 | ], 27 | "scripts": [], 28 | "environmentSource": "environments/environment.ts", 29 | "environments": { 30 | "dev": "environments/environment.ts", 31 | "prod": "environments/environment.prod.ts" 32 | } 33 | } 34 | ], 35 | "e2e": { 36 | "protractor": { 37 | "config": "./protractor.conf.js" 38 | } 39 | }, 40 | "lint": [ 41 | { 42 | "project": "src/tsconfig.app.json" 43 | }, 44 | { 45 | "project": "src/tsconfig.spec.json" 46 | }, 47 | { 48 | "project": "e2e/tsconfig.e2e.json" 49 | } 50 | ], 51 | "test": { 52 | "karma": { 53 | "config": "./karma.conf.js" 54 | } 55 | }, 56 | "defaults": { 57 | "styleExt": "scss", 58 | "component": {} 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/app/services/web-synth/web-synth.service.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. GridCell Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { inject, fakeAsync, tick, TestBed } from '@angular/core/testing'; 18 | import { Observable } from 'rxjs/Observable'; 19 | import { WebSynthService } from '../index'; 20 | 21 | describe('WebSynthService', () => { 22 | let fakeTestObject: any = { dummy: 1 }; 23 | 24 | beforeEach(() => { 25 | TestBed.configureTestingModule({ 26 | providers: [ 27 | WebSynthService, 28 | ] 29 | }); 30 | }); 31 | 32 | describe('start', () => { 33 | it('should start recognizing and return observable', 34 | inject([WebSynthService], fakeAsync((service: WebSynthService) => { 35 | let spy = spyOn((service).engine, 'start'); 36 | let observable = service.start(); 37 | tick(); 38 | 39 | expect(spy).toHaveBeenCalled(); 40 | expect(observable).toEqual(jasmine.any(Observable)); 41 | })) 42 | ); 43 | }); 44 | 45 | }); 46 | -------------------------------------------------------------------------------- /src/app/services/web-speech/web-speech.service.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. GridCell Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { inject, fakeAsync, tick, TestBed } from '@angular/core/testing'; 18 | import { Observable } from 'rxjs/Observable'; 19 | import { WebSpeechService } from '../index'; 20 | 21 | describe('WebSpeechService', () => { 22 | let fakeTestObject: any = { dummy: 1 }; 23 | 24 | beforeEach(() => { 25 | TestBed.configureTestingModule({ 26 | providers: [ 27 | WebSpeechService, 28 | ] 29 | }); 30 | }); 31 | 32 | describe('start', () => { 33 | it('should start recognizing and return observable', 34 | inject([WebSpeechService], fakeAsync((service: WebSpeechService) => { 35 | let spy = spyOn((service).engine, 'start'); 36 | let observable = service.start(); 37 | tick(); 38 | 39 | expect(spy).toHaveBeenCalled(); 40 | expect(observable).toEqual(jasmine.any(Observable)); 41 | })) 42 | ); 43 | }); 44 | 45 | }); 46 | -------------------------------------------------------------------------------- /src/app/pages/widgets/widgets.component.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2017. GridCell Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | page-widgets { 17 | a { 18 | color: initial; 19 | text-decoration: underline; 20 | } 21 | 22 | .card { 23 | min-height: 128px; 24 | margin-bottom: 15px; 25 | } 26 | 27 | .title { 28 | margin-right: 15px; 29 | } 30 | 31 | .inline { 32 | display: inline-block; 33 | } 34 | 35 | .spacer { 36 | height: 8px; 37 | } 38 | 39 | .ng-busy-backdrop { 40 | left: 15px; 41 | right: 15px; 42 | bottom: 15px; 43 | } 44 | 45 | .mat-input-container { 46 | width: 100%; 47 | } 48 | 49 | .mat-chip:not(.mat-basic-chip) { 50 | font-size: 12px !important; 51 | line-height: 14px !important; 52 | margin-bottom: 4px; 53 | } 54 | 55 | agm-map { 56 | height: 300px; 57 | } 58 | 59 | .select-features { 60 | min-width: 128px; 61 | margin-right: 16px; 62 | } 63 | 64 | .bg-warning { 65 | font-size: 12px; 66 | padding: 4px; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /e2e/widgets.po.ts: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2017 GridCell Ltd 3 | 4 | Licensed to the Apache Software Foundation (ASF) under one 5 | or more contributor license agreements. See the NOTICE file 6 | distributed with this work for additional information 7 | regarding copyright ownership. The ASF licenses this file 8 | to you under the Apache License, Version 2.0 (the 9 | "License"); you may not use this file except in compliance 10 | with the License. You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, 15 | software distributed under the License is distributed on an 16 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | KIND, either express or implied. See the License for the 18 | specific language governing permissions and limitations 19 | under the License. 20 | */ 21 | 22 | import { browser, by, element } from 'protractor'; 23 | 24 | export class PageWidgetsComponent { 25 | readonly titles: string[] = [ 26 | 'WEB SPEECH API', 'GCLOUD SPEECH API', 'GCLOUD VISION API' 27 | ]; 28 | 29 | navigateTo() { 30 | return browser.get('/'); 31 | } 32 | 33 | getAllWidgetsTitles() { 34 | return element(by.css('page-widgets')).all(by.css('.title')); 35 | } 36 | 37 | shouldHaveAllWidgets() { 38 | let widgets = this.getAllWidgetsTitles(); 39 | widgets.count().then((count) => { 40 | expect(count).toEqual(this.titles.length); 41 | }); 42 | 43 | widgets.each((title, index) => { 44 | title.getText().then((text: string) => { 45 | expect(this.titles).toContain(text); 46 | }); 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/app/services/gcloud-speech/gcloud-speech.service.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. GridCell Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { inject, fakeAsync, tick, TestBed } from '@angular/core/testing'; 18 | import { Observable } from 'rxjs/Observable'; 19 | import { GCloudSpeechService } from '../index'; 20 | 21 | describe('GCloudNLPService', () => { 22 | let fakeTestObject: any = { dummy: 1 }; 23 | 24 | beforeEach(() => { 25 | TestBed.configureTestingModule({ 26 | providers: [ 27 | GCloudSpeechService, 28 | ] 29 | }); 30 | }); 31 | 32 | describe('start', () => { 33 | it('should start recognizing and return observable', 34 | inject([GCloudSpeechService], fakeAsync((service: GCloudSpeechService) => { 35 | let spy = spyOn(service, 'getUserMedia'); 36 | let observable = service.start(); 37 | tick(); 38 | 39 | expect(spy).toHaveBeenCalled(); 40 | expect(observable).toEqual(jasmine.any(Observable)); 41 | expect(service.isRecognizing()).toBeTruthy(); 42 | })) 43 | ); 44 | }); 45 | 46 | }); 47 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. GridCell Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 18 | 19 | import 'zone.js/dist/long-stack-trace-zone'; 20 | import 'zone.js/dist/proxy.js'; 21 | import 'zone.js/dist/sync-test'; 22 | import 'zone.js/dist/jasmine-patch'; 23 | import 'zone.js/dist/async-test'; 24 | import 'zone.js/dist/fake-async-test'; 25 | import { getTestBed } from '@angular/core/testing'; 26 | import { 27 | BrowserDynamicTestingModule, 28 | platformBrowserDynamicTesting 29 | } from '@angular/platform-browser-dynamic/testing'; 30 | 31 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 32 | declare const __karma__: any; 33 | declare const require: any; 34 | 35 | // Prevent Karma from running prematurely. 36 | __karma__.loaded = function () {}; 37 | 38 | // First, initialize the Angular testing environment. 39 | getTestBed().initTestEnvironment( 40 | BrowserDynamicTestingModule, 41 | platformBrowserDynamicTesting() 42 | ); 43 | // Then we find all the tests. 44 | const context = require.context('./', true, /\.spec\.ts$/); 45 | // And load the modules. 46 | context.keys().map(context); 47 | // Finally, start Karma to run the tests. 48 | __karma__.start(); 49 | -------------------------------------------------------------------------------- /src/app/models/nlp.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. GridCell Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export enum NLPDocumentType { 18 | TYPE_UNSPECIFIED, 19 | PLAIN_TEXT, 20 | HTML 21 | } 22 | 23 | export enum NLPEncodingType { 24 | NONE, 25 | UTF8, 26 | UTF16, 27 | UTF32 28 | } 29 | 30 | export class NLPDocument { 31 | type: NLPDocumentType; 32 | language: string; 33 | content: string; 34 | } 35 | 36 | export class NLPFeatures { 37 | extractSyntax: boolean; 38 | extractEntities: boolean; 39 | extractDocumentSentiment: boolean; 40 | } 41 | 42 | export class NLPSpeechAnalysis { 43 | json: string; 44 | tags: NLPSpeechAnalysisTag[]; 45 | sentiment: NLPSpeechAnalysisSentiment; 46 | 47 | constructor(data: any) { 48 | this.json = JSON.stringify(data, null, 2); 49 | this.sentiment = { 50 | score: data.documentSentiment.score, 51 | magnitude: data.documentSentiment.magnitude 52 | } 53 | this.tags = []; 54 | 55 | for (let entity of data.entities) { 56 | if (entity.name !== 'name') { 57 | this.tags.push({ 58 | name: entity.name, 59 | type: entity.type, 60 | wikipedia_url: entity.metadata.wikipedia_url 61 | }); 62 | } 63 | } 64 | } 65 | } 66 | 67 | export class NLPSpeechAnalysisTag { 68 | name: string; 69 | type: string; 70 | wikipedia_url: string; 71 | } 72 | 73 | export class NLPSpeechAnalysisSentiment { 74 | score: number; 75 | magnitude: number; 76 | } 77 | -------------------------------------------------------------------------------- /src/app/services/gcloud-nlp/gcloud-nlp.service.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. GridCell Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Injectable } from '@angular/core'; 18 | import { Http, Response, Headers, RequestOptions, ResponseOptions } from '@angular/http'; 19 | import { Observable } from 'rxjs/Observable'; 20 | import { NLPDocument, NLPFeatures, NLPEncodingType } from '../../models/index'; 21 | import { environment } from '../../../environments/environment'; 22 | 23 | @Injectable() 24 | export class GCloudNLPService { 25 | 26 | constructor(public http: Http) { } 27 | 28 | /** 29 | * Ask google to do a Natural Language Processing / Analysis of the document passed 30 | * @param document Contains the text to be processed / analysed. 31 | * @param features What exactly do we want to analyse the document for (specific features? or all?) 32 | * @param encodingType The text encoding type 33 | * @returns { Observable } Response from Google Cloud 34 | */ 35 | annotateText(document: NLPDocument, features: NLPFeatures, encodingType: NLPEncodingType): Observable { 36 | return this.http.post(environment.gCloudNLPApiUrl + '?key=' + environment.gCloudProjectApiKey, 37 | { document: document, features: features, encodingType: encodingType}) 38 | .map((res: Response) => { 39 | if (res.status === 401) { 40 | throw Error('Not Authorized.'); 41 | } else { 42 | return res.json(); 43 | } 44 | }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "google-speech-demo", 3 | "version": "0.0.1", 4 | "description": "Demo App for Google Cloud Speech, Natural Language and Vision APIs", 5 | "license": "Apache License, Version 2.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/gridcellcoder/google-speech-demos" 9 | }, 10 | "author": "GridCell", 11 | "contributors": [ 12 | "Nuno Freire =4.1.2", 39 | "core-js": "^2.4.1", 40 | "font-awesome": "^4.7.0", 41 | "hammerjs": "^2.0.8", 42 | "rxjs": "^5.4.1", 43 | "zone.js": "^0.8.12" 44 | }, 45 | "devDependencies": { 46 | "@angular/cli": "1.1.2", 47 | "@angular/compiler-cli": "^4.2.3", 48 | "@angular/language-service": "^4.2.3", 49 | "@types/hammerjs": "2.0.34", 50 | "@types/jasmine": "^2.5.52", 51 | "@types/node": "~6.0.60", 52 | "codelyzer": "~3.0.1", 53 | "jasmine-core": "~2.6.4", 54 | "jasmine-spec-reporter": "~4.1.0", 55 | "karma": "~1.7.0", 56 | "karma-chrome-launcher": "~2.1.1", 57 | "karma-cli": "~1.0.1", 58 | "karma-jasmine": "~1.1.0", 59 | "karma-jasmine-html-reporter": "^0.2.2", 60 | "karma-coverage-istanbul-reporter": "^1.3.0", 61 | "protractor": "~5.1.2", 62 | "ts-node": "~3.0.6", 63 | "tslint": "~5.4.3", 64 | "typescript": "~2.3.4" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/app/services/gcloud-vision/gcloud-vision.service.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. GridCell Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Injectable } from '@angular/core'; 18 | import { Http, Response, Headers, RequestOptions, ResponseOptions } from '@angular/http'; 19 | import { Observable } from 'rxjs/Observable'; 20 | import { VisionAnnotateRequest, VisionFeature } from '../../models/index'; 21 | import { environment } from '../../../environments/environment'; 22 | 23 | 24 | @Injectable() 25 | export class GCloudVisionService { 26 | 27 | constructor(public http: Http) { } 28 | 29 | /** 30 | * Ask google process the image passed. 31 | * @param imageBase64 Contains the image to be processed / analysed in base64 format. 32 | * @param features What exactly do we want to analyse the document for (specific features? or all?) 33 | * @returns { Observable } Response from Google Cloud 34 | */ 35 | annotateImage(imageBase64: string, features: VisionFeature[]): Observable { 36 | let options = new RequestOptions({ 37 | headers: new Headers({ 38 | 'Content-Type': 'application/json' 39 | }) 40 | }); 41 | 42 | let request: VisionAnnotateRequest = { 43 | requests: [ 44 | { 45 | image: { 46 | content: imageBase64 47 | }, 48 | features: features 49 | } 50 | ] 51 | }; 52 | 53 | return this.http.post(environment.gCloudVisionApiUrl + '?key=' + environment.gCloudProjectApiKey, 54 | request) 55 | .map((res: Response) => { 56 | if (res.status === 401) { 57 | throw Error('Not Authorized.'); 58 | } else { 59 | return res.json(); 60 | } 61 | }); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. GridCell Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { BrowserModule } from '@angular/platform-browser'; 18 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 19 | import { HttpModule } from '@angular/http'; 20 | import { NgModule } from '@angular/core'; 21 | import { FormsModule } from '@angular/forms'; 22 | 23 | import { MaterialModule } from '@angular/material'; 24 | import { AgmCoreModule } from '@agm/core'; 25 | import { BusyModule, BusyConfig } from 'angular2-busy'; 26 | 27 | import { AppComponent } from './app.component'; 28 | import { AppRoutingModule } from './app-routing.module'; 29 | import { DialogAlertComponent } from './components/index'; 30 | import { PageWidgetsComponent } from './pages/index'; 31 | import { WebSpeechService, 32 | WebSynthService, 33 | GCloudSpeechService, 34 | GCloudNLPService, 35 | GCloudVisionService } from './services/index'; 36 | 37 | import { environment } from '../environments/environment'; 38 | 39 | 40 | @NgModule({ 41 | declarations: [ 42 | AppComponent, 43 | 44 | DialogAlertComponent, 45 | 46 | PageWidgetsComponent, 47 | ], 48 | entryComponents: [ DialogAlertComponent ], 49 | imports: [ 50 | AppRoutingModule, 51 | 52 | BrowserModule, 53 | BrowserAnimationsModule, 54 | FormsModule, 55 | HttpModule, 56 | MaterialModule, 57 | AgmCoreModule.forRoot({ 58 | apiKey: environment.gCloudProjectApiKey 59 | }), 60 | BusyModule.forRoot({ 61 | message: '', 62 | backdrop: true, 63 | template: '
busy
', 64 | delay: 200, 65 | minDuration: 250, 66 | wrapperClass: 'ng-busy' 67 | }) 68 | ], 69 | providers: [ 70 | WebSpeechService, 71 | WebSynthService, 72 | GCloudNLPService, 73 | GCloudSpeechService, 74 | GCloudVisionService 75 | ], 76 | bootstrap: [ AppComponent ] 77 | }) 78 | export class AppModule { } 79 | -------------------------------------------------------------------------------- /src/app/pages/widgets/widgets.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. GridCell Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 18 | import { NoopAnimationsModule } from '@angular/platform-browser/animations'; 19 | import { FormsModule } from '@angular/forms'; 20 | import { MaterialModule } from '@angular/material'; 21 | import { AgmCoreModule } from '@agm/core'; 22 | import { BusyModule, BusyConfig } from 'angular2-busy'; 23 | import { PageWidgetsComponent } from './widgets.component'; 24 | import { WebSpeechService, 25 | GCloudSpeechService, 26 | GCloudNLPService, 27 | GCloudVisionService } from '../../services/index'; 28 | 29 | class MockWebSpeechService { 30 | start() { } 31 | stop() { } 32 | isRecognizing() { }; 33 | } 34 | class MockGCloudSpeechService { 35 | start() { } 36 | stop() { } 37 | isRecognizing() { }; 38 | } 39 | class MockGCloudNLPService { 40 | annotateText() { } 41 | } 42 | class MockGCloudVisionService { 43 | annotateImage() { } 44 | } 45 | 46 | describe('PageWidgetsComponent', () => { 47 | let component: PageWidgetsComponent; 48 | let fixture: ComponentFixture; 49 | 50 | beforeEach(async(() => { 51 | TestBed.configureTestingModule({ 52 | declarations: [ 53 | PageWidgetsComponent, 54 | ], 55 | imports: [ 56 | NoopAnimationsModule, 57 | FormsModule, 58 | MaterialModule, 59 | AgmCoreModule.forRoot(), 60 | BusyModule 61 | ], 62 | providers: [ 63 | {provide: WebSpeechService, useClass: MockWebSpeechService}, 64 | {provide: GCloudSpeechService, useClass: MockGCloudSpeechService}, 65 | {provide: GCloudNLPService, useClass: MockGCloudNLPService}, 66 | {provide: GCloudVisionService, useClass: MockGCloudVisionService}, 67 | ] 68 | }).compileComponents(); 69 | })); 70 | 71 | beforeEach(() => { 72 | fixture = TestBed.createComponent(PageWidgetsComponent); 73 | component = fixture.componentInstance; 74 | fixture.detectChanges(); 75 | }); 76 | 77 | it('should create', () => { 78 | expect(component).toBeTruthy(); 79 | }); 80 | 81 | it('should toggle web speech', () => { 82 | let result = true; 83 | let spy1 = spyOn((component).webSpeechService, 'isRecognizing').and.callFake(() => { return result; }); 84 | let spy2 = spyOn(component, 'stopWebSpeech'); 85 | let spy3 = spyOn(component, 'startWebSpeech'); 86 | 87 | component.toggleWebSpeech(); 88 | expect(spy1).toHaveBeenCalled(); 89 | expect(spy2).toHaveBeenCalled(); 90 | expect(spy3).not.toHaveBeenCalled(); 91 | 92 | result = false; 93 | component.toggleWebSpeech(); 94 | expect(spy1).toHaveBeenCalled(); 95 | expect(spy3).toHaveBeenCalled(); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /src/assets/img/loading.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. GridCell Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * This file includes polyfills needed by Angular and is loaded before the app. 19 | * You can add your own extra polyfills to this file. 20 | * 21 | * This file is divided into 2 sections: 22 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 23 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 24 | * file. 25 | * 26 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 27 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 28 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 29 | * 30 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 31 | */ 32 | 33 | /*************************************************************************************************** 34 | * BROWSER POLYFILLS 35 | */ 36 | 37 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 38 | // import 'core-js/es6/symbol'; 39 | // import 'core-js/es6/object'; 40 | // import 'core-js/es6/function'; 41 | // import 'core-js/es6/parse-int'; 42 | // import 'core-js/es6/parse-float'; 43 | // import 'core-js/es6/number'; 44 | // import 'core-js/es6/math'; 45 | // import 'core-js/es6/string'; 46 | // import 'core-js/es6/date'; 47 | // import 'core-js/es6/array'; 48 | // import 'core-js/es6/regexp'; 49 | // import 'core-js/es6/map'; 50 | // import 'core-js/es6/weak-map'; 51 | // import 'core-js/es6/set'; 52 | 53 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 54 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 55 | 56 | /** IE10 and IE11 requires the following to support `@angular/animation`. */ 57 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 58 | 59 | 60 | /** Evergreen browsers require these. **/ 61 | import 'core-js/es6/reflect'; 62 | import 'core-js/es7/reflect'; 63 | 64 | 65 | /** ALL Firefox browsers require the following to support `@angular/animation`. **/ 66 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 67 | 68 | 69 | 70 | /*************************************************************************************************** 71 | * Zone JS is required by Angular itself. 72 | */ 73 | import 'zone.js/dist/zone'; // Included with Angular CLI. 74 | 75 | 76 | 77 | /*************************************************************************************************** 78 | * APPLICATION IMPORTS 79 | */ 80 | 81 | /** 82 | * Date, currency, decimal and percent pipes. 83 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 84 | */ 85 | // import 'intl'; // Run `npm install --save intl`. 86 | /** 87 | * Need to import at least one locale-data with intl. 88 | */ 89 | // import 'intl/locale-data/jsonp/en'; 90 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": false, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "eofline": true, 15 | "forin": true, 16 | "import-blacklist": [ 17 | true, 18 | "rxjs" 19 | ], 20 | "import-spacing": true, 21 | "indent": [ 22 | true, 23 | "spaces" 24 | ], 25 | "interface-over-type-literal": true, 26 | "label-position": true, 27 | "max-line-length": [ 28 | true, 29 | 140 30 | ], 31 | "member-access": false, 32 | "member-ordering": [ 33 | true, 34 | "static-before-instance", 35 | "variables-before-functions" 36 | ], 37 | "no-arg": true, 38 | "no-bitwise": true, 39 | "no-console": [ 40 | true, 41 | "debug", 42 | "info", 43 | "time", 44 | "timeEnd", 45 | "trace" 46 | ], 47 | "no-construct": true, 48 | "no-debugger": true, 49 | "no-duplicate-super": true, 50 | "no-empty": false, 51 | "no-empty-interface": true, 52 | "no-eval": true, 53 | "no-inferrable-types": [ 54 | true, 55 | "ignore-params" 56 | ], 57 | "no-misused-new": true, 58 | "no-non-null-assertion": true, 59 | "no-shadowed-variable": true, 60 | "no-string-literal": false, 61 | "no-string-throw": true, 62 | "no-switch-case-fall-through": true, 63 | "no-trailing-whitespace": true, 64 | "no-unnecessary-initializer": true, 65 | "no-unused-expression": true, 66 | "no-use-before-declare": true, 67 | "no-var-keyword": true, 68 | "object-literal-sort-keys": false, 69 | "one-line": [ 70 | true, 71 | "check-open-brace", 72 | "check-catch", 73 | "check-else", 74 | "check-whitespace" 75 | ], 76 | "prefer-const": false, 77 | "quotemark": [ 78 | true, 79 | "single" 80 | ], 81 | "radix": true, 82 | "semicolon": [ 83 | "always" 84 | ], 85 | "triple-equals": [ 86 | true, 87 | "allow-null-check" 88 | ], 89 | "typedef-whitespace": [ 90 | true, 91 | { 92 | "call-signature": "nospace", 93 | "index-signature": "nospace", 94 | "parameter": "nospace", 95 | "property-declaration": "nospace", 96 | "variable-declaration": "nospace" 97 | } 98 | ], 99 | "typeof-compare": true, 100 | "unified-signatures": true, 101 | "variable-name": false, 102 | "whitespace": [ 103 | true, 104 | "check-branch", 105 | "check-decl", 106 | "check-operator", 107 | "check-separator", 108 | "check-type" 109 | ], 110 | "directive-selector": [ 111 | true, 112 | "attribute", 113 | "app", 114 | "camelCase" 115 | ], 116 | "component-selector": [ 117 | false, 118 | "element", 119 | "app", 120 | "kebab-case" 121 | ], 122 | "use-input-property-decorator": true, 123 | "use-output-property-decorator": true, 124 | "use-host-property-decorator": true, 125 | "no-input-rename": true, 126 | "no-output-rename": true, 127 | "use-life-cycle-interface": true, 128 | "use-pipe-transform-interface": true, 129 | "component-class-suffix": true, 130 | "directive-class-suffix": true, 131 | "no-access-missing-member": true, 132 | "templates-use-public": true, 133 | "invoke-injectable": true 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Speech, NL & Vision API Demos (by GridCell) 2 | 3 | This project showcases some of the APIs made available by the Google Cloud platform, namely: 4 | - [Google Cloud Speech](https://cloud.google.com/speech/) 5 | - [Google Cloud Natural Language](https://cloud.google.com/natural-language/) 6 | - [Google Cloud Vision](https://cloud.google.com/vision/) 7 | 8 | It also showcases the Web Speech API made available by all modern browsers, as a mean of comparision against Google Cloud Speech. 9 | 10 | ## Getting started 11 | Either: 12 | - Clone the project & follow the instructions below 13 | - See it live at: (https://gcpdemo.gridcell.io/). The local server process is still required for GCP Speech Demo. 14 | 15 | The project was generated with [Angular CLI](https://github.com/angular/angular-cli) and its structure remains pretty much unchanged; Only new pages / components / services were added along with a complementary Node.js server process. [Material Design](https://material.angular.io/) and [Bootstrap](http://getbootstrap.com/) were used just to make the web app look nicer. 16 | 17 | ## Why is a server process needed? 18 | 19 | While Google Cloud provides a REST and a RPC API, usually it is the client libraries that are the more complete way of accessing its features. That said, we were able to use the REST API for both Natural Language and Vision processing, but to be able to "stream" the speech for recognizing, we had to use the client library which is only available for server side languages. 20 | 21 | Hence the reason for the "speech-server". The following flow can be observed: 22 | - the web app captures and streams the audio through a websocket (binaryJS) to a Node.js process; 23 | - in turn, the server process streams it to GCS through its client library implementation; 24 | - once a response is returned from GCS, it is piped back to the web app. 25 | 26 | ## Project structure 27 | 28 | The meaningful bits and pieces of the project can be found in the following folders: 29 | - src/app/services/ 30 | - src/app/pages/widgets/ 31 | - speech-server/src/ 32 | 33 | The `src/app/services/` holds the services responsible for audio capture and integrating with the Google Cloud REST API or with our own Speech-Server. The services are broken down into separate files according to their responsibilities: Speech (WebSpeech & GoogleCloud), Natural Language Processing (NLP) and Vision APIs. 34 | 35 | The `src/app/pages/widgets/` contains the main web app page. It basically consists of 3 cards showcasing the aforementioned features. They could / should have been split into separate components but are all bundled together so you can easily see and compare the relevant code. 36 | 37 | The `speech-server/src` holds the Node.js typescript implementation of the server process responsible for piping the audio stream from the wep app to the Google Cloud Speech and back. 38 | 39 | ## Configuration 40 | 41 | Before being able to run both web app and speech server, we need to configure the google cloud project ID and API Key. This can be done in the following files: 42 | - src/environments/environment.ts (and / or environment.prod.ts) 43 | - speech-server/src/environment.ts 44 | 45 | ## Web app (local) server 46 | 47 | Run `npm install` and then `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 48 | 49 | ## Speech-(local)-server 50 | 51 | Since the speech-server relies on google cloud's client library, to run it on your local workstation you must first install the [Google Cloud SDK](https://cloud.google.com/sdk/docs/) and authenticate by running the following command: `gcloud auth application-default login` 52 | 53 | Then `cd speech-server` and run `npm start` on a separate terminal window. The server will be accessable on `ws://localhost:8000`. 54 | 55 | ## Build 56 | 57 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 58 | 59 | ## Running unit tests 60 | 61 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 62 | The coverage is not exhaustive in any way, just wanting to provide a few examples. 63 | 64 | ## Running end-to-end tests 65 | 66 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 67 | Before running the tests make sure you are serving the app via `ng serve`. 68 | The coverage is not exaustive in any way, just wanting to provide a few examples. 69 | 70 | ## Further help 71 | 72 | Tweet us @gridcell_io 73 | 74 | All trademarks acknowledged, this is not a Google product nor affiliated with Google, Google Cloud Services. 75 | -------------------------------------------------------------------------------- /src/app/services/web-speech/web-speech.service.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. GridCell Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Injectable, NgZone, EventEmitter } from '@angular/core'; 18 | import { Observable } from 'rxjs/Observable'; 19 | import { Observer } from 'rxjs/Observer'; 20 | import { IEvent } from '../../models/index'; 21 | 22 | // Extend the Window class with the Web Speech API -> SpeechRecognition engines 23 | // for the different browser types 24 | interface IWindow extends Window { 25 | webkitSpeechRecognition: any; 26 | mozSpeechRecognition: any; 27 | msSpeechRecognition: any; 28 | SpeechRecognition: any; 29 | } 30 | 31 | @Injectable() 32 | export class WebSpeechService { 33 | 34 | private engine: any = null; 35 | 36 | private recognizing = false; 37 | private observer: Observer; 38 | 39 | constructor(private zone: NgZone) { 40 | this.create(); 41 | } 42 | 43 | /** 44 | * Starts the audio capture and speech recognition engine. 45 | * @returns {Observable} Observable that emits any event related to the speech recognition, 46 | * including the resulting transcript and any error that might occur... 47 | */ 48 | start(): Observable { 49 | if (!this.recognizing) { 50 | this.engine.start(); 51 | } 52 | return new Observable((observer: Observer) => { this.observer = this.observer || observer; }); 53 | } 54 | 55 | /** 56 | * Stops the audio capture and speech recognition engine. 57 | */ 58 | stop() { 59 | this.engine.stop(); 60 | 61 | if (this.observer) { 62 | // Give it some time to any additional event to propragate to subscribers... 63 | setTimeout(() => { this.observer = null; }, 500); 64 | } 65 | } 66 | 67 | /** 68 | * Returns true if audio capture is in progress; false, otherwise. 69 | * @returns {boolean} 70 | */ 71 | isRecognizing(): boolean { 72 | return this.recognizing; 73 | } 74 | 75 | /** 76 | * Helper function to create SpeechRecognition engine and bind relevant events. 77 | */ 78 | private create() { 79 | this.engine = this.createEngine(); 80 | this.engine.continuous = true; 81 | this.engine.lang = 'en-US'; 82 | // this.engine.interimResults = true; 83 | // this.engine.maxAlternatives = 1; 84 | 85 | this.engine.onerror = this.onerror.bind(this); 86 | this.engine.onresult = this.onresult.bind(this); 87 | this.engine.onaudiostart = this.onaudiostart.bind(this); 88 | this.engine.onaudioend = this.onaudioend.bind(this); 89 | this.engine.onnomatch = this.onnomatch.bind(this); 90 | } 91 | 92 | /** 93 | * Helper function to create SpeechRecognition object supporting multiple browsers' engines. 94 | */ 95 | private createEngine(): any { 96 | const win: IWindow = window; 97 | return new (win.webkitSpeechRecognition || 98 | win.mozSpeechRecognition || 99 | win.msSpeechRecognition || 100 | win.SpeechRecognition)(); 101 | }; 102 | 103 | private onaudiostart() { 104 | this.recognizing = true; 105 | 106 | this.zone.run(() => { 107 | this.observer.next({ 108 | type: 'hint', 109 | value: 'Capturing audio...' 110 | }); 111 | }); 112 | } 113 | 114 | private onaudioend() { 115 | this.recognizing = false; 116 | 117 | this.zone.run(() => { 118 | this.observer.next({ 119 | type: 'hint', 120 | value: 'Stopped capturing audio.' 121 | }); 122 | }); 123 | } 124 | 125 | private onnomatch() { 126 | this.zone.run(() => { 127 | this.observer.next({ 128 | type: 'hint', 129 | value: 'No match!' 130 | }); 131 | }); 132 | } 133 | 134 | private onerror(event: any) { 135 | this.recognizing = false; 136 | 137 | this.zone.run(() => { 138 | this.observer.error({ 139 | type: 'error', 140 | value: event.error 141 | }); 142 | }); 143 | 144 | this.stop(); 145 | } 146 | 147 | private onresult(event: any) { 148 | this.zone.run(() => { 149 | this.transcriptText(event); 150 | }); 151 | } 152 | 153 | /** 154 | * Basic parsing of the speech recognition result object, emitting 'tag' event for subscribers. 155 | * @param event The onresult event returned by the SpeechRecognition engine 156 | */ 157 | private transcriptText(event: any) { 158 | for (let i = event.resultIndex; i < event.results.length; ++i) { 159 | if (event.results[i].isFinal) { 160 | this.observer.next({ 161 | type: 'tag', 162 | value: event.results[i][0].transcript 163 | }); 164 | } 165 | } 166 | } 167 | 168 | } 169 | -------------------------------------------------------------------------------- /src/app/services/gcloud-nlp/gcloud-nlp.service.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. GridCell Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | import { inject, fakeAsync, tick, TestBed } from '@angular/core/testing'; 17 | import { MockBackend, MockConnection } from '@angular/http/testing'; 18 | import { 19 | Http, 20 | BaseRequestOptions, 21 | Response, 22 | ResponseOptions, RequestMethod, Headers 23 | } from '@angular/http'; 24 | 25 | import { GCloudNLPService } from '../index'; 26 | import { 27 | NLPDocument, 28 | NLPDocumentType, 29 | NLPEncodingType, 30 | NLPFeatures, 31 | NLPSpeechAnalysis 32 | } from '../../models/index'; 33 | import { environment } from '../../../environments/environment'; 34 | 35 | describe('GCloudNLPService', () => { 36 | let document: NLPDocument = { 37 | type: NLPDocumentType.PLAIN_TEXT, 38 | language: 'en-US', 39 | content: 'text' 40 | }; 41 | let features: NLPFeatures = { 42 | extractSyntax: true, 43 | extractEntities: true, 44 | extractDocumentSentiment: true 45 | } 46 | let fakeTestObject: any = { dummy: 1 }; 47 | 48 | beforeEach(() => { 49 | TestBed.configureTestingModule({ 50 | providers: [ 51 | GCloudNLPService, 52 | BaseRequestOptions, 53 | MockBackend, 54 | { 55 | provide: Http, 56 | useFactory: (backend: MockBackend, defaultOptions: BaseRequestOptions) => { 57 | return new Http(backend, defaultOptions); 58 | }, deps: [MockBackend, BaseRequestOptions] 59 | }, 60 | ] 61 | }); 62 | }); 63 | 64 | function prepareMockBackendToReturnResponseAndExpectHTTPStuff(backend: MockBackend, method: RequestMethod, url: string, body?: any) { 65 | backend.connections.subscribe((connection: MockConnection) => { 66 | expect(connection.request.method).toBe(method); 67 | expect(connection.request.url).toBe(url + '?key=' + environment.gCloudProjectApiKey); 68 | if (body) { 69 | expect(connection.request.getBody()).toBe(JSON.stringify(body, null, 2)); 70 | } 71 | 72 | let response = new ResponseOptions({ body: JSON.stringify(fakeTestObject) }); 73 | connection.mockRespond(new Response(response)); 74 | }); 75 | } 76 | 77 | function prepareMockBackendToFailAuthentication(backend: MockBackend) { 78 | backend.connections.subscribe((connection: MockConnection) => { 79 | let response = new ResponseOptions({ status: 401, statusText: 'Not Authorized' }); 80 | connection.mockRespond(new Response(response)); 81 | }); 82 | } 83 | 84 | function prepareMockBackendToReturnError(backend: MockBackend) { 85 | backend.connections.subscribe((connection: MockConnection) => { 86 | let response = new ResponseOptions({ body: JSON.stringify(fakeTestObject) }); 87 | connection.mockError(new Error('Error.')); 88 | }); 89 | } 90 | 91 | 92 | describe('annotateText', () => { 93 | it('should query the REST endpoint successfully', 94 | inject([GCloudNLPService, MockBackend], fakeAsync((service: GCloudNLPService, backend: MockBackend) => { 95 | prepareMockBackendToReturnResponseAndExpectHTTPStuff(backend, 96 | RequestMethod.Post, 97 | environment.gCloudNLPApiUrl, 98 | {document: document, features: features, encodingType: NLPEncodingType.UTF16}); 99 | 100 | service.annotateText(document, features, NLPEncodingType.UTF16).subscribe((res) => { 101 | expect(res).toBeDefined(); 102 | }); 103 | tick(); 104 | })) 105 | ); 106 | 107 | it('should throw exception if invalid API KEY is passed or no longer valid', 108 | inject([GCloudNLPService, MockBackend], fakeAsync((service: GCloudNLPService, backend: MockBackend) => { 109 | prepareMockBackendToFailAuthentication(backend); 110 | 111 | service.annotateText(document, features, NLPEncodingType.UTF16).subscribe((res) => { 112 | expect(res.status).toBe(401); 113 | }, (error) => { 114 | expect(error.message).toBe('Not Authorized.'); 115 | }); 116 | tick(); 117 | })) 118 | ); 119 | 120 | it('should catch server error after enough time for retries', 121 | inject([GCloudNLPService, MockBackend], fakeAsync((service: GCloudNLPService, backend: MockBackend) => { 122 | prepareMockBackendToReturnError(backend); 123 | 124 | let errorCaught = false; 125 | service.annotateText(document, features, NLPEncodingType.UTF16).subscribe((res) => { 126 | expect(1).toBe(2); // this should never happen 127 | }, (error) => { 128 | expect(error.message).toBe('Error.'); 129 | errorCaught = true; 130 | }); 131 | 132 | expect(errorCaught).toBeTruthy(); 133 | })) 134 | ); 135 | }); 136 | 137 | }); 138 | -------------------------------------------------------------------------------- /src/app/services/gcloud-vision/gcloud-vision.service.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. GridCell Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { inject, fakeAsync, tick, TestBed } from '@angular/core/testing'; 18 | import { MockBackend, MockConnection } from '@angular/http/testing'; 19 | import { 20 | Http, 21 | BaseRequestOptions, 22 | Response, 23 | ResponseOptions, RequestMethod, Headers 24 | } from '@angular/http'; 25 | 26 | import { GCloudVisionService } from '../index'; 27 | import { 28 | VisionAnnotateRequest, 29 | VisionFeatureType, 30 | VisionFeature 31 | } from '../../models/index'; 32 | import { environment } from '../../../environments/environment'; 33 | 34 | describe('GCloudVisionService', () => { 35 | let features: VisionFeature[] = [ 36 | { type: VisionFeatureType.WEB_DETECTION, maxResults: 3 } 37 | ]; 38 | let fakeTestObject: any = { dummy: 1 }; 39 | 40 | beforeEach(() => { 41 | TestBed.configureTestingModule({ 42 | providers: [ 43 | GCloudVisionService, 44 | BaseRequestOptions, 45 | MockBackend, 46 | { 47 | provide: Http, 48 | useFactory: (backend: MockBackend, defaultOptions: BaseRequestOptions) => { 49 | return new Http(backend, defaultOptions); 50 | }, deps: [MockBackend, BaseRequestOptions] 51 | }, 52 | ] 53 | }); 54 | }); 55 | 56 | function prepareMockBackendToReturnResponseAndExpectHTTPStuff(backend: MockBackend, method: RequestMethod, url: string, body?: any) { 57 | backend.connections.subscribe((connection: MockConnection) => { 58 | expect(connection.request.method).toBe(method); 59 | expect(connection.request.url).toBe(url + '?key=' + environment.gCloudProjectApiKey); 60 | if (body) { 61 | expect(connection.request.getBody()).toBe(JSON.stringify(body, null, 2)); 62 | } 63 | 64 | let response = new ResponseOptions({ body: JSON.stringify(fakeTestObject) }); 65 | connection.mockRespond(new Response(response)); 66 | }); 67 | } 68 | 69 | function prepareMockBackendToFailAuthentication(backend: MockBackend) { 70 | backend.connections.subscribe((connection: MockConnection) => { 71 | let response = new ResponseOptions({ status: 401, statusText: 'Not Authorized' }); 72 | connection.mockRespond(new Response(response)); 73 | }); 74 | } 75 | 76 | function prepareMockBackendToReturnError(backend: MockBackend) { 77 | backend.connections.subscribe((connection: MockConnection) => { 78 | let response = new ResponseOptions({ body: JSON.stringify(fakeTestObject) }); 79 | connection.mockError(new Error('Error.')); 80 | }); 81 | } 82 | 83 | 84 | describe('annotateText', () => { 85 | it('should query the REST endpoint successfully', 86 | inject([GCloudVisionService, MockBackend], fakeAsync((service: GCloudVisionService, backend: MockBackend) => { 87 | let request: VisionAnnotateRequest = { 88 | requests: [ 89 | { 90 | image: { 91 | content: 'imageBase64' 92 | }, 93 | features: features 94 | } 95 | ] 96 | }; 97 | 98 | prepareMockBackendToReturnResponseAndExpectHTTPStuff(backend, 99 | RequestMethod.Post, 100 | environment.gCloudVisionApiUrl, 101 | request); 102 | 103 | service.annotateImage('imageBase64', features).subscribe((res) => { 104 | expect(res).toBeDefined(); 105 | }); 106 | tick(); 107 | })) 108 | ); 109 | 110 | it('should throw exception if invalid API KEY is passed or no longer valid', 111 | inject([GCloudVisionService, MockBackend], fakeAsync((service: GCloudVisionService, backend: MockBackend) => { 112 | prepareMockBackendToFailAuthentication(backend); 113 | 114 | service.annotateImage('imageBase64', features).subscribe((res) => { 115 | expect(res.status).toBe(401); 116 | }, (error) => { 117 | expect(error.message).toBe('Not Authorized.'); 118 | }); 119 | tick(); 120 | })) 121 | ); 122 | 123 | it('should catch server error after enough time for retries', 124 | inject([GCloudVisionService, MockBackend], fakeAsync((service: GCloudVisionService, backend: MockBackend) => { 125 | prepareMockBackendToReturnError(backend); 126 | 127 | let errorCaught = false; 128 | service.annotateImage('imageBase64', features).subscribe((res) => { 129 | expect(1).toBe(2); // this should never happen 130 | }, (error) => { 131 | expect(error.message).toBe('Error.'); 132 | errorCaught = true; 133 | }); 134 | 135 | expect(errorCaught).toBeTruthy(); 136 | })) 137 | ); 138 | }); 139 | 140 | }); 141 | -------------------------------------------------------------------------------- /src/app/services/web-synth/web-synth.service.ts: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2017 GridCell Ltd 3 | 4 | Licensed to the Apache Software Foundation (ASF) under one 5 | or more contributor license agreements. See the NOTICE file 6 | distributed with this work for additional information 7 | regarding copyright ownership. The ASF licenses this file 8 | to you under the Apache License, Version 2.0 (the 9 | "License"); you may not use this file except in compliance 10 | with the License. You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, 15 | software distributed under the License is distributed on an 16 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | KIND, either express or implied. See the License for the 18 | specific language governing permissions and limitations 19 | under the License. 20 | */ 21 | 22 | import { Injectable, NgZone, EventEmitter } from '@angular/core'; 23 | import { Observable } from 'rxjs/Observable'; 24 | import { Observer } from 'rxjs/Observer'; 25 | import { BinaryClient } from 'binaryjs-client'; 26 | import { IEvent } from '../../models/index'; 27 | import { environment } from '../../../environments/environment'; 28 | 29 | // Extend the Window class with the Web Audio API -> SpeechSynthesis engine 30 | // for the different browser types 31 | interface IWindow extends Window { 32 | webkitSpeechSynthesis: any; 33 | mozSpeechSynthesis: any; 34 | msSpeechSynthesis: any; 35 | speechSynthesis: any; 36 | } 37 | 38 | @Injectable() 39 | export class WebSynthService { 40 | 41 | private engine: any = null; 42 | private utterThis: SpeechSynthesisUtterance; 43 | 44 | private speechServerClient: any = null; 45 | private speechServerStream: any = null; 46 | 47 | private busy = false; 48 | private observer: Observer; 49 | 50 | constructor(private zone: NgZone) { 51 | this.create(); 52 | } 53 | 54 | /** 55 | * Starts the speech synthetiser engine. First asks server for random sentence and speaks it out loud. 56 | * @returns {Observable} Observable that emits any event related to the speech synth, 57 | * including the utterance to be spoken and any error that might occur... 58 | */ 59 | start(): Observable { 60 | if (!this.isSpeaking()) { 61 | this.getUtteranceAndSpeak(); 62 | } 63 | return new Observable((observer: Observer) => { this.observer = this.observer || observer; }); 64 | } 65 | 66 | /** 67 | * Stops the speech synthetiser engine removing any utterances from the queue. 68 | */ 69 | stop() { 70 | this.engine.cancel(); 71 | 72 | if (this.speechServerClient) { 73 | this.speechServerClient.close(); 74 | this.speechServerClient = null; 75 | this.speechServerStream = null; 76 | } 77 | 78 | if (this.observer) { 79 | // Give it some time to any additional event to propragate to subscribers... 80 | setTimeout(() => { this.observer = null; }, 100); 81 | } 82 | } 83 | 84 | /** 85 | * Returns true if a utterance is being spoken; false, otherwise. 86 | * @returns {boolean} 87 | */ 88 | isSpeaking(): boolean { 89 | return this.busy || this.engine.speaking; 90 | } 91 | 92 | /** 93 | * Helper function to create SpeechSynthetiser engine and bind relevant events. 94 | */ 95 | private create() { 96 | this.engine = this.createSynth(); 97 | this.utterThis = new SpeechSynthesisUtterance(); 98 | 99 | this.utterThis.onstart = this.onstart.bind(this); 100 | this.utterThis.onend = this.onend.bind(this); 101 | this.utterThis.onerror = this.onerror.bind(this); 102 | } 103 | 104 | private getUtteranceAndSpeak() { 105 | // connect to the speech server and get stream ready for send / receive data... 106 | this.speechServerClient = new BinaryClient(environment.speechServerUrl) 107 | .on('error', this.onerror.bind(this)) 108 | .on('open', () => { 109 | // special initial message to request random utterance. 110 | this.speechServerStream = this.speechServerClient.createStream({ type: 'random_utterance' }); 111 | }) 112 | .on('stream', (serverStream) => { 113 | serverStream 114 | .on('data', this.onutterance.bind(this)) 115 | .on('error', this.onerror.bind(this)) 116 | .on('close', this.onerror.bind(this)) 117 | }); 118 | } 119 | 120 | /** 121 | * Helper function to create SpeechSynthetiser object supporting multiple browsers' engines. 122 | */ 123 | private createSynth(): SpeechSynthesis { 124 | const win: IWindow = window; 125 | return (win.webkitSpeechSynthesis || 126 | win.mozSpeechSynthesis || 127 | win.msSpeechSynthesis || 128 | win.speechSynthesis); 129 | }; 130 | 131 | private onstart() { 132 | this.busy = false; 133 | 134 | this.zone.run(() => { 135 | this.observer.next({ 136 | type: 'start', 137 | value: 'Speaking utterance...' 138 | }); 139 | }); 140 | } 141 | 142 | private onend() { 143 | this.zone.run(() => { 144 | this.observer.next({ 145 | type: 'end', 146 | value: 'Finished speaking.' 147 | }); 148 | }); 149 | } 150 | 151 | private onerror(event: any) { 152 | this.zone.run(() => { 153 | this.observer.error({ 154 | type: 'error', 155 | value: 'Failed to get utterance from speech server.' 156 | }); 157 | }); 158 | 159 | this.stop(); 160 | } 161 | 162 | private onutterance(utterance: any) { 163 | Object.assign(this.utterThis, utterance); 164 | let voice = (this.engine).getVoices().find((v: SpeechSynthesisVoice) => { 165 | return v.name === utterance.voiceName; 166 | }); 167 | this.utterThis.voice = voice; 168 | 169 | this.engine.speak(this.utterThis); 170 | 171 | this.zone.run(() => { 172 | this.observer.next({ 173 | type: 'tag', 174 | value: utterance.text 175 | }); 176 | }); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /speech-server/src/server.ts: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2017 GridCell Ltd 3 | 4 | Licensed to the Apache Software Foundation (ASF) under one 5 | or more contributor license agreements. See the NOTICE file 6 | distributed with this work for additional information 7 | regarding copyright ownership. The ASF licenses this file 8 | to you under the Apache License, Version 2.0 (the 9 | "License"); you may not use this file except in compliance 10 | with the License. You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, 15 | software distributed under the License is distributed on an 16 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | KIND, either express or implied. See the License for the 18 | specific language governing permissions and limitations 19 | under the License. 20 | */ 21 | 22 | import { environment } from './environment'; 23 | import * as Speech from '@google-cloud/speech'; 24 | import * as binaryjs from 'binaryjs'; 25 | 26 | // Instantiates the speech client and binary websocket server 27 | const speechClient = Speech({ 28 | projectId: environment.gCloudProjectId 29 | }); 30 | const port = Number(process.env.PORT || 8000); 31 | const server = new binaryjs.BinaryServer({ 32 | port: port 33 | }); 34 | 35 | // The audio file's encoding, sample rate in hertz, and BCP-47 language code 36 | let options = { 37 | config: { 38 | encoding: 'LINEAR16', 39 | languageCode: 'en-US', 40 | sampleRateHertz: 16000 41 | }, 42 | singleUtterance: false, 43 | interimResults: false, 44 | verbose: true 45 | }; 46 | 47 | // handle client connections 48 | server 49 | .on('error', (error) => { console.log('Server error:' + error); }) 50 | .on('close', () => { console.log('Server closed'); }) 51 | .on('connection', (client) => { 52 | client 53 | .on('error', (error) => { console.log('Client error: ' + error); }) 54 | .on('close', () => { console.log('Client closed.'); }) 55 | .on('stream', (clientStream, meta) => { 56 | console.log('New Client: ' + JSON.stringify(meta)); 57 | 58 | if (meta.type === 'speech') { 59 | handleSpeechRequest(client, clientStream, meta); 60 | } else { 61 | handleRandomUtteranceRequest(client); 62 | } 63 | }); 64 | }); 65 | 66 | function handleSpeechRequest(client, clientStream, meta) { 67 | options.config.sampleRateHertz = meta.sampleRate; 68 | 69 | let speechStream = speechClient.createRecognizeStream(options) 70 | .on('error', (data) => { handleGCSMessage(data, client, speechStream); }) 71 | .on('data', (data) => { handleGCSMessage(data, client, speechStream); }) 72 | .on('close', () => { client.close(); }); 73 | 74 | clientStream.pipe(speechStream); 75 | } 76 | 77 | function handleRandomUtteranceRequest(client) { 78 | let data = getRandomSentence(); 79 | console.log(data); 80 | 81 | try { 82 | client.send(data); 83 | } catch (ex) { 84 | console.log('Failed to send message back to client...Closed?'); 85 | } 86 | } 87 | 88 | function handleGCSMessage(data, client, speechStream) { 89 | if (client && client.streams[0] && 90 | client.streams[0].writable && !client.streams[0].destroyed) { 91 | try { 92 | console.log(data); 93 | 94 | client.send(data); 95 | } catch (ex) { 96 | console.log('Failed to send message back to client...Closed?'); 97 | } 98 | if (data.error || data.Error) { 99 | try { 100 | speechStream.end(); 101 | speechStream = null; 102 | client.close(); 103 | client = null; 104 | } catch (ex) { 105 | console.log('ERROR closing the streams after error!'); 106 | } 107 | } 108 | } 109 | } 110 | 111 | function getRandomSentence(): any { 112 | let random = Math.round(Math.random() * 10); 113 | switch (random) { 114 | case 0: 115 | return { 116 | text: 'The old apple revels in its authority.', 117 | lang: 'en-US', pitch: 1, rate: 1, volume: 1, voiceName: 'Google UK English Male' 118 | }; 119 | case 1: 120 | return { 121 | text: 'Don\'t step on the broken glass.', 122 | lang: 'en-US', pitch: 1, rate: 1, volume: 1, voiceName: 'Google UK English Male' 123 | }; 124 | case 2: 125 | return { 126 | text: 'I will never be this young again. Ever. Oh damn… I just got older.', 127 | lang: 'en-US', pitch: 1, rate: 1, volume: 1, voiceName: 'Google UK English Male' 128 | }; 129 | case 3: 130 | return { 131 | text: 'There was no ice cream in the freezer, nor did they have money to go to the store.', 132 | lang: 'en-US', pitch: 1, rate: 1, volume: 1, voiceName: 'Google UK English Male' 133 | }; 134 | case 4: 135 | return { 136 | text: 'I think I will buy the red car, or I will lease the blue one.', 137 | lang: 'en-US', pitch: 1, rate: 1, volume: 1, voiceName: 'Google UK English Male' 138 | }; 139 | case 5: 140 | return { 141 | text: 'He didn’t want to go to the dentist, yet he went anyway.', 142 | lang: 'en-US', pitch: 1, rate: 1, volume: 1, voiceName: 'Google UK English Male' 143 | }; 144 | case 6: 145 | return { 146 | text: 'We have never been to Asia, nor have we visited Africa.', 147 | lang: 'en-US', pitch: 1, rate: 1, volume: 1, voiceName: 'Google UK English Female' 148 | }; 149 | case 7: 150 | return { 151 | text: 'How was the math test?', 152 | lang: 'en-US', pitch: 1, rate: 1, volume: 1, voiceName: 'Google UK English Female' 153 | }; 154 | case 8: 155 | return { 156 | text: 'Tom got a small piece of pie.', 157 | lang: 'en-US', pitch: 1, rate: 1, volume: 1, voiceName: 'Google UK English Female' 158 | }; 159 | case 9: 160 | return { 161 | text: 'The book is in front of the table.', 162 | lang: 'en-US', pitch: 1, rate: 1, volume: 1, voiceName: 'Google UK English Female' 163 | }; 164 | case 10: 165 | return { 166 | text: 'Check back tomorrow; I will see if the book has arrived.', 167 | lang: 'en-US', pitch: 1, rate: 1, volume: 1, voiceName: 'Google UK English Female' 168 | }; 169 | } 170 | } 171 | 172 | -------------------------------------------------------------------------------- /src/app/services/gcloud-speech/gcloud-speech.service.ts: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2017 GridCell Ltd 3 | 4 | Licensed to the Apache Software Foundation (ASF) under one 5 | or more contributor license agreements. See the NOTICE file 6 | distributed with this work for additional information 7 | regarding copyright ownership. The ASF licenses this file 8 | to you under the Apache License, Version 2.0 (the 9 | "License"); you may not use this file except in compliance 10 | with the License. You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, 15 | software distributed under the License is distributed on an 16 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | KIND, either express or implied. See the License for the 18 | specific language governing permissions and limitations 19 | under the License. 20 | */ 21 | 22 | import { Injectable, NgZone } from '@angular/core'; 23 | import { Observable } from 'rxjs/Observable'; 24 | import { Observer } from 'rxjs/Observer'; 25 | import { BinaryClient } from 'binaryjs-client'; 26 | import { IEvent } from '../../models/index'; 27 | import { environment } from '../../../environments/environment'; 28 | 29 | // Extend the Window class with the Web Audio API -> AudioContext 30 | // for the different browser types 31 | interface IWindow extends Window { 32 | webkitAudioContext: any; 33 | mozAudioContext: any; 34 | AudioContext: any; 35 | } 36 | 37 | // Extend the Navigator class with the MediaStream API -> GetUserMedia 38 | // for the different browser types 39 | interface INavigator extends Navigator { 40 | webkitGetUserMedia: any; 41 | mozGetUserMedia: any; 42 | GetUserMedia: any; 43 | } 44 | 45 | @Injectable() 46 | export class GCloudSpeechService { 47 | 48 | private audioContext: AudioContext = null; 49 | private scriptProcessor: ScriptProcessorNode = null; 50 | private audioInput: MediaStreamAudioSourceNode = null; 51 | 52 | private userMediaStream: MediaStream = null; 53 | 54 | private speechServerClient: any = null; 55 | private speechServerStream: any = null; 56 | 57 | private recognizing = false; 58 | private observer: Observer; 59 | 60 | constructor(private zone: NgZone) { 61 | this.audioContext = this.createAudioContext(); 62 | this.scriptProcessor = this.audioContext.createScriptProcessor(2048, 1, 1); 63 | } 64 | 65 | /** 66 | * Starts the audio capture and sets up a streaming connection to the speech-server, sending the audio 67 | * for speech recognition on Google Cloud platform. 68 | * @returns {Observable} Observable that emits any event related to the speech recognition, 69 | * including the resulting transcript and any error that might occur... 70 | */ 71 | start(): Observable { 72 | if (!this.recognizing) { 73 | this.getUserMedia(this.onaudiostart.bind(this), this.onerror.bind(this)); 74 | this.recognizing = true; 75 | } 76 | 77 | return new Observable((observer: Observer) => { this.observer = this.observer || observer; }); 78 | } 79 | 80 | /** 81 | * Stops the audio capture and speech recognition engine. 82 | */ 83 | stop() { 84 | // dispose of audio inputs / streams and speech server client 85 | if (this.audioInput) { 86 | this.audioInput.disconnect(); 87 | } 88 | 89 | if (this.userMediaStream) { 90 | this.userMediaStream.getTracks().map((track: any) => { 91 | track.stop(); 92 | }); 93 | } 94 | 95 | if (this.speechServerClient) { 96 | this.speechServerClient.close(); 97 | this.speechServerClient = null; 98 | this.speechServerStream = null; 99 | } 100 | 101 | if (this.observer) { 102 | // Give it some time to any additional event to propragate to subscribers... 103 | setTimeout(() => { this.observer = null; }, 500); 104 | } 105 | 106 | this.recognizing = false; 107 | } 108 | 109 | /** 110 | * Returns true if audio capture is in progress; false, otherwise. 111 | * @returns {boolean} 112 | */ 113 | isRecognizing(): boolean { 114 | return this.recognizing; 115 | } 116 | 117 | /** 118 | * Helper function to create AudioContext object supporting multiple browsers' engines. 119 | */ 120 | private createAudioContext(): any { 121 | let win: IWindow = window; 122 | return new (win.AudioContext || 123 | win.mozAudioContext || 124 | win.webkitAudioContext)(); 125 | }; 126 | 127 | /** 128 | * Helper function to create GetUserMedia object supporting multiple browsers' engines. 129 | */ 130 | private getUserMedia(successFn: Function, errorFn: Function): any { 131 | let nav: INavigator = navigator; 132 | let getUserMedia = nav.GetUserMedia || nav.mozGetUserMedia || nav.webkitGetUserMedia; 133 | return getUserMedia.call(navigator, 134 | { audio: true }, 135 | successFn, 136 | errorFn 137 | ); 138 | }; 139 | 140 | /** 141 | * Event triggered when the audio capture is under way. 142 | * This is where the connection to the speech server is established and the 143 | * input audio stream is piped to the server 144 | */ 145 | private onaudiostart(stream: any) { 146 | this.userMediaStream = stream; 147 | 148 | // get all the audio capture, processing and streaming ready to go... 149 | this.userMediaStream.getTracks().forEach((track: any) => { 150 | track.onended = this.onaudioend.bind(this); 151 | } 152 | ); 153 | 154 | this.audioInput = this.audioContext.createMediaStreamSource(this.userMediaStream); 155 | this.audioInput.connect(this.scriptProcessor); 156 | this.scriptProcessor.connect(this.audioContext.destination) 157 | this.scriptProcessor.onaudioprocess = (event: any) => { 158 | // we're only using one audio channel here... 159 | let leftChannel = event.inputBuffer.getChannelData(0); 160 | 161 | if (this.speechServerStream) { 162 | this.speechServerStream.write(this.convertFloat32ToInt16(leftChannel)); 163 | } 164 | } 165 | 166 | // connect to the speech server and get stream ready for send / receive data... 167 | this.speechServerClient = new BinaryClient(environment.speechServerUrl) 168 | .on('error', this.onerror.bind(this)) 169 | .on('open', () => { 170 | // pass the sampleRate as a parameter to the server and get a reference to the communication stream. 171 | this.speechServerStream = this.speechServerClient.createStream({ 172 | type: 'speech', 173 | sampleRate: this.audioContext.sampleRate 174 | }); 175 | }) 176 | .on('stream', (serverStream) => { 177 | serverStream 178 | .on('data', this.onresult.bind(this)) 179 | .on('error', this.onerror.bind(this)) 180 | .on('close', this.onerror.bind(this)) 181 | }); 182 | 183 | // let the subscribers know we're ready! 184 | this.zone.run(() => { 185 | this.observer.next({ 186 | type: 'hint', 187 | value: 'Capturing audio...' 188 | }); 189 | }); 190 | } 191 | 192 | private onaudioend() { 193 | this.zone.run(() => { 194 | this.observer.next({ 195 | type: 'hint', 196 | value: 'Stopped capturing audio.' 197 | }); 198 | }); 199 | } 200 | 201 | private onerror(error: any) { 202 | this.zone.run(() => { 203 | this.observer.error({ 204 | type: 'error', 205 | value: typeof(error) === 'string' ? error : 'Couldn\'t connect to speech server.' 206 | }); 207 | }); 208 | 209 | this.stop(); 210 | } 211 | 212 | private onresult(event: any) { 213 | if (event.error && event.error.message) { 214 | this.onerror(event.error.message); 215 | } else { 216 | this.zone.run(() => { 217 | this.transcriptText(event); 218 | }); 219 | } 220 | } 221 | 222 | /** 223 | * Basic parsing of the speech recognition result object, emitting 'tag' event for subscribers. 224 | * @param event The onresult event returned by the SpeechRecognition engine 225 | */ 226 | private transcriptText(event: any) { 227 | for (let i = 0; i < event.results.length; ++i) { 228 | if (event.results[i].isFinal) { 229 | this.observer.next({ 230 | type: 'tag', 231 | value: event.results[i].transcript 232 | }); 233 | } 234 | } 235 | } 236 | 237 | private convertFloat32ToInt16 (buffer) { 238 | let l = buffer.length; 239 | let buf = new Int16Array(l); 240 | while (l >= 0) { 241 | buf[l] = Math.min(1, buffer[l]) * 0x7FFF; 242 | l = l - 1; 243 | } 244 | return buf.buffer; 245 | } 246 | 247 | } 248 | -------------------------------------------------------------------------------- /src/app/pages/widgets/widgets.component.html: -------------------------------------------------------------------------------- 1 | 16 |
17 |
18 | 19 | 20 |
Cloud Speech Demos by GridCell
21 |

22 | This project showcases some of the APIs made available by the Google Cloud platform, namely: 23 |

24 | 29 |

It also showcases the Web Speech API made available by all modern browsers, as a mean of comparision against Google Cloud Speech.

30 |

The source code and instructions for this project can be found on: 31 | github 32 |

33 |
34 |
35 |
36 |
37 | 38 | 39 |
Web Speech API
40 |

Press the start button and say something..

41 | 45 |
46 | 47 | 50 | 51 |
52 |
53 |
54 | Sentiment 55 | 57 | Score: {{webSpeechAnalysis.sentiment.score}}, Magnitude: {{webSpeechAnalysis.sentiment.magnitude}} 58 |   59 | 60 |
61 | 62 | 63 | {{tag.type}} - "{{tag.name}}" 64 | {{tag.type}} - "{{tag.name}}" 65 | 66 | 67 |
68 |
69 |
70 | 71 |
72 | 73 |
Speech Syntethiser
74 |

To get this to work you need to have a local instance of the speech-server up and running (See README) for full details. Then press the start button and a random sentence will be spoken..

75 | 76 |
77 | 78 | 80 | 81 |
82 |
83 |
84 | 85 |
86 |
87 | 88 |
GCloud Speech API
89 |

To get this to work you need to have a 90 | Google Compute account, install the gcloud sdk, 91 | create a google compute project. Then clone 92 | and change environment.ts with your project-id. 93 | and start the speech-server (See README) for full details. Then press the start button and say something..

94 | 98 |
99 | 100 | 103 | 104 |
105 |
106 |
107 | Sentiment 108 | 110 | Score: {{gCloudSpeechAnalysis.sentiment.score}}, Magnitude: {{gCloudSpeechAnalysis.sentiment.magnitude}} 111 |   112 | 113 |
114 | 115 | 116 | {{tag.type}} - "{{tag.name}}" 117 | {{tag.type}} - "{{tag.name}}" 118 | 119 | 120 |
121 |
122 |
123 | 124 |
125 | 126 |
GCloud Vision API
127 |

First select some features. Then upload an image from your computer to analyze its content

128 |
129 |
130 | 133 | 134 | {{feature}} 135 | 136 | 137 | 139 |
140 | 143 | 145 | 146 |
147 |
{{gCloudVisionAnalysis}}
148 |
149 |
150 |
151 | 152 |
153 | -------------------------------------------------------------------------------- /src/app/pages/widgets/widgets.component.ts: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2017 GridCell Ltd 3 | 4 | Licensed to the Apache Software Foundation (ASF) under one 5 | or more contributor license agreements. See the NOTICE file 6 | distributed with this work for additional information 7 | regarding copyright ownership. The ASF licenses this file 8 | to you under the Apache License, Version 2.0 (the 9 | "License"); you may not use this file except in compliance 10 | with the License. You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, 15 | software distributed under the License is distributed on an 16 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | KIND, either express or implied. See the License for the 18 | specific language governing permissions and limitations 19 | under the License. 20 | */ 21 | 22 | import { Component, OnInit, OnDestroy, AfterViewInit } from '@angular/core'; 23 | import { DialogAlertComponent } from '../../components/index'; 24 | import { MdDialog, MdDialogRef, MdDialogConfig } from '@angular/material'; 25 | import { Subscription } from 'rxjs/Subscription'; 26 | import { Observable } from 'rxjs/Observable'; 27 | import { Observer } from 'rxjs/Observer'; 28 | import { 29 | WebSpeechService, 30 | WebSynthService, 31 | GCloudSpeechService, 32 | GCloudNLPService, 33 | GCloudVisionService 34 | } from '../../services/index'; 35 | import { 36 | NLPDocument, 37 | NLPDocumentType, 38 | NLPEncodingType, 39 | NLPFeatures, 40 | NLPSpeechAnalysis, 41 | VisionFeatureType, 42 | VisionFeature 43 | } from '../../models/index'; 44 | 45 | 46 | @Component({ 47 | selector: 'page-widgets', 48 | templateUrl: './widgets.component.html' 49 | }) 50 | export class PageWidgetsComponent implements OnInit, OnDestroy { 51 | 52 | webSpeechSubscription: Subscription; 53 | webSpeechTranscript: string; 54 | webSpeechAnalysis: NLPSpeechAnalysis; 55 | 56 | webSynthSubscription: Subscription; 57 | webSynthUtterance: string; 58 | 59 | gCloudSpeechSubscription: Subscription; 60 | gCloudSpeechTranscript: string; 61 | gCloudSpeechAnalysis: NLPSpeechAnalysis; 62 | 63 | gCloudVisionSubscription: Subscription; 64 | gCloudVisionAnalysis: string; 65 | gCloudVisionLocation: any; 66 | 67 | selectedVisionFeatures: string[]; 68 | 69 | constructor( 70 | private webSpeechService: WebSpeechService, 71 | private webSynthService: WebSynthService, 72 | private gCloudSpeechService: GCloudSpeechService, 73 | private gCloudNLPService: GCloudNLPService, 74 | private gCloudVisionService: GCloudVisionService, 75 | private dialog: MdDialog) { } 76 | 77 | ngOnInit() { 78 | this.selectedVisionFeatures = []; 79 | } 80 | 81 | ngOnDestroy() { 82 | this.stopWebSpeech(); 83 | this.stopGCloudSpeech(); 84 | } 85 | 86 | toggleWebSpeech() { 87 | if (this.webSpeechService.isRecognizing()) { 88 | this.stopWebSpeech(); 89 | } else { 90 | this.startWebSpeech(); 91 | } 92 | } 93 | 94 | toggleGCloudSpeech() { 95 | if (this.gCloudSpeechService.isRecognizing()) { 96 | this.stopGCloudSpeech(); 97 | } else { 98 | this.startGCloudSpeech(); 99 | } 100 | } 101 | 102 | startWebSpeech() { 103 | this.webSpeechTranscript = null; 104 | this.webSpeechAnalysis = null; 105 | this.webSpeechSubscription = this.webSpeechService.start().subscribe((data: any) => { 106 | console.log('WebSpeechAPI: ' + JSON.stringify(data)); 107 | if (data.type === 'tag') { 108 | this.webSpeechTranscript = data.value; 109 | this.stopWebSpeech(); // we want to get the first result and stop listening... 110 | 111 | this.webSpeechAnalyseTranscript(); 112 | } 113 | }, (error: any) => { 114 | console.log('WebSpeechAPI: ' + JSON.stringify(error)); 115 | this.stopWebSpeech(); 116 | this.showAlert('Oops! Something wrong happened:', error.value, this.startWebSpeech.bind(this)); 117 | }); 118 | } 119 | 120 | startWebSynth() { 121 | this.webSynthUtterance = null; 122 | this.webSynthSubscription = this.webSynthService.start().subscribe((data: any) => { 123 | console.log('WebSynthAPI: ' + JSON.stringify(data)); 124 | if (data.type === 'tag') { 125 | this.webSynthUtterance = data.value; 126 | } else if (data.type === 'end') { 127 | this.stopWebSynth(); // we want to get the first result and stop... 128 | } 129 | }, (error: any) => { 130 | console.log('WebSynthAPI: ' + JSON.stringify(error)); 131 | this.stopWebSynth(); 132 | this.showAlert('Oops! Something wrong happened:', error.value, this.startWebSynth.bind(this)); 133 | }); 134 | } 135 | 136 | startGCloudSpeech() { 137 | this.gCloudSpeechTranscript = null; 138 | this.gCloudSpeechAnalysis = null; 139 | this.gCloudSpeechSubscription = this.gCloudSpeechService.start().subscribe((data: any) => { 140 | console.log('GCloudSpeechAPI: ' + JSON.stringify(data)); 141 | if (data.type === 'tag') { 142 | this.gCloudSpeechTranscript = data.value; 143 | this.stopGCloudSpeech(); // we want to get the first result and stop listening... 144 | 145 | this.gCloudSpeechAnalyseTranscript(); 146 | } 147 | }, (error: any) => { 148 | console.log('GCloudSpeechAPI: ' + JSON.stringify(error)); 149 | this.stopGCloudSpeech(); 150 | this.showAlert('Oops! Something wrong happened:', error.value, this.startGCloudSpeech.bind(this)); 151 | }); 152 | } 153 | 154 | stopWebSpeech() { 155 | this.webSpeechService.stop(); 156 | if (this.webSpeechSubscription) { 157 | this.webSpeechSubscription.unsubscribe(); 158 | this.webSpeechSubscription = null; 159 | } 160 | } 161 | 162 | stopWebSynth() { 163 | this.webSynthService.stop(); 164 | if (this.webSynthSubscription) { 165 | this.webSynthSubscription.unsubscribe(); 166 | this.webSynthSubscription = null; 167 | } 168 | } 169 | 170 | stopGCloudSpeech() { 171 | this.gCloudSpeechService.stop(); 172 | if (this.gCloudSpeechSubscription) { 173 | this.gCloudSpeechSubscription.unsubscribe(); 174 | this.gCloudSpeechSubscription = null; 175 | } 176 | } 177 | 178 | webSpeechAnalyseTranscript() { 179 | if (this.webSpeechTranscript) { 180 | this.webSpeechSubscription = this.analyseSpeech(this.webSpeechTranscript).first().subscribe((speechAnalysis: any) => { 181 | this.webSpeechAnalysis = new NLPSpeechAnalysis(speechAnalysis); 182 | console.log('WebSpeechAPI: ' + this.webSpeechAnalysis.json); 183 | 184 | this.webSpeechSubscription.unsubscribe(); 185 | this.webSpeechSubscription = null; 186 | }, (error: any) => { 187 | console.error('WebSpeechAPI: ' + error); 188 | }); 189 | } else { 190 | this.webSpeechAnalysis = null; 191 | } 192 | } 193 | 194 | gCloudSpeechAnalyseTranscript() { 195 | if (this.gCloudSpeechTranscript) { 196 | this.gCloudSpeechSubscription = this.analyseSpeech(this.gCloudSpeechTranscript).first().subscribe((speechAnalysis: any) => { 197 | this.gCloudSpeechAnalysis = new NLPSpeechAnalysis(speechAnalysis); 198 | console.log('GCloudSpeechAPI: ' + this.gCloudSpeechAnalysis.json); 199 | 200 | this.gCloudSpeechSubscription.unsubscribe(); 201 | this.gCloudSpeechSubscription = null; 202 | }, (error: any) => { 203 | console.error('GCloudSpeechAPI: ' + error); 204 | }); 205 | } else { 206 | this.gCloudSpeechAnalysis = null; 207 | } 208 | } 209 | 210 | speechAnalysisSave(speechAnalysis: NLPSpeechAnalysis) { 211 | alert('Not implemented.'); 212 | } 213 | 214 | fileChange(event: any) { 215 | this.gCloudVisionAnalysis = null; 216 | this.gCloudVisionLocation = null; 217 | 218 | this.readFileInputAsBase64(event.target).first().subscribe( 219 | (base64: string) => { 220 | let features: VisionFeature[] = this.selectedVisionFeatures.map((feature: string) => { 221 | return { 222 | type: VisionFeatureType[feature], 223 | maxResults: 3 224 | }; 225 | }); 226 | this.gCloudVisionSubscription = this.gCloudVisionService.annotateImage(base64, features).first(). 227 | subscribe((response: any) => { 228 | this.gCloudVisionAnalysis = JSON.stringify(response, null, 2); 229 | // Check if a landmark location was return...if so, display it on a map 230 | if (response.responses[0].landmarkAnnotations && response.responses[0].landmarkAnnotations[0].locations) { 231 | this.gCloudVisionLocation = response.responses[0].landmarkAnnotations[0].locations[0].latLng; 232 | } 233 | 234 | this.gCloudVisionSubscription.unsubscribe(); 235 | this.gCloudVisionSubscription = null; 236 | }); 237 | }, 238 | (error: any) => { 239 | console.log('VISION: ' + error); 240 | } 241 | ); 242 | } 243 | 244 | /** 245 | * Convert VisionFeatureType enum into an array to be used by the template (select options) 246 | */ 247 | getVisionFeatureKeys(): string[] { 248 | let keys = Object.keys(VisionFeatureType); 249 | // a enum has both numeric and string keys and we only want the latter 250 | return keys.slice(keys.length / 2); 251 | } 252 | 253 | private analyseSpeech(text: string): Observable { 254 | let document: NLPDocument = { 255 | type: NLPDocumentType.PLAIN_TEXT, 256 | language: 'en-US', 257 | content: text 258 | }; 259 | let features: NLPFeatures = { 260 | extractSyntax: true, 261 | extractEntities: true, 262 | extractDocumentSentiment: true 263 | } 264 | 265 | return this.gCloudNLPService.annotateText(document, features, NLPEncodingType.UTF16); 266 | } 267 | 268 | private showAlert(title: string, text: string, retryCallback: Function) { 269 | let dialogRef = this.dialog.open(DialogAlertComponent, { 270 | data: { 271 | title: title, 272 | text: text 273 | } 274 | }); 275 | dialogRef.afterClosed().subscribe((retry) => { 276 | if (retry) { 277 | retryCallback(); 278 | } 279 | }); 280 | } 281 | 282 | private readFileInputAsBase64(input: any): Observable { 283 | return new Observable((observer: Observer) => { 284 | let file: File = input.files[0]; 285 | if (file) { 286 | let myReader: FileReader = new FileReader(); 287 | 288 | myReader.onloadend = (e) => { 289 | let base64 = myReader.result.substring(myReader.result.indexOf(',') + 1); 290 | observer.next(base64); 291 | observer.complete(); 292 | } 293 | myReader.readAsDataURL(file); 294 | } else { 295 | observer.error('No file selected.'); 296 | } 297 | }); 298 | } 299 | } 300 | --------------------------------------------------------------------------------