forked from mmig/speech-to-flac
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathapp.js
311 lines (240 loc) · 11.6 KB
/
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
var recorderApp = angular.module('recorder', [ ]);
recorderApp.controller('RecorderController', [ '$scope' , function($scope) {
$scope.audio_context = null;
$scope.stream = null;
$scope.recording = false;
$scope.encoder = null;
$scope.ws = null;
$scope.input = null;
$scope.node = null;
$scope.samplerate = 44100;
$scope.autoSelectSamplerate = true;
$scope.samplerates = [ 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 ];
$scope.compression = 5;
$scope.compressions = [ 0, 1,2,3,4,5,6,7,8 ];
// $scope.bitrate = 16;
// $scope.bitrates = [ 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 192, 224, 256, 320 ];
$scope.recordButtonStyle = "red-btn";
$scope.flacdata = {};
$scope.flacdata.bps = 16;
$scope.flacdata.channels = 1;
$scope.flacdata.compression = 5;
$scope.wav_format = false;
$scope.outfilename_flac = "output.flac";
$scope.outfilename_wav = "output.wav";
//ASR-related settings (using Google Cloud Speech service)
$scope.languages = ['af-ZA', 'am-ET', 'hy-AM', 'az-AZ', 'id-ID', 'ms-MY', 'bn-BD', 'bn-IN', 'ca-ES', 'cs-CZ', 'da-DK', 'de-DE', 'en-AU', 'en-CA', 'en-GH', 'en-GB', 'en-IN', 'en-IE', 'en-KE', 'en-NZ', 'en-NG', 'en-PH', 'en-ZA', 'en-TZ', 'en-US', 'es-AR', 'es-BO', 'es-CL', 'es-CO', 'es-CR', 'es-EC', 'es-SV', 'es-ES', 'es-US', 'es-GT', 'es-HN', 'es-MX', 'es-NI', 'es-PA', 'es-PY', 'es-PE', 'es-PR', 'es-DO', 'es-UY', 'es-VE', 'eu-ES', 'fil-PH', 'fr-CA', 'fr-FR', 'gl-ES', 'ka-GE', 'gu-IN', 'hr-HR', 'zu-ZA', 'is-IS', 'it-IT', 'jv-ID', 'kn-IN', 'km-KH', 'lo-LA', 'lv-LV', 'lt-LT', 'hu-HU', 'ml-IN', 'mr-IN', 'nl-NL', 'ne-NP', 'nb-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'ro-RO', 'si-LK', 'sk-SK', 'sl-SI', 'su-ID', 'sw-TZ', 'sw-KE', 'fi-FI', 'sv-SE', 'ta-IN', 'ta-SG', 'ta-LK', 'ta-MY', 'te-IN', 'vi-VN', 'tr-TR', 'ur-PK', 'ur-IN', 'el-GR', 'bg-BG', 'ru-RU', 'sr-RS', 'uk-UA', 'he-IL', 'ar-IL', 'ar-JO', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-SA', 'ar-IQ', 'ar-KW', 'ar-MA', 'ar-TN', 'ar-OM', 'ar-PS', 'ar-QA', 'ar-LB', 'ar-EG', 'fa-IR', 'hi-IN', 'th-TH', 'ko-KR', 'cmn-Hant-TW', 'yue-Hant-HK', 'ja-JP', 'cmn-Hans-HK', 'cmn-Hans-CN'];
var __language = /\blanguage=([^&]*)/.exec(document.location.search);//<- for testing: set pre-selected language code via search-param in URL: ...?language=<language code>
$scope.language = __language? __language[1] : 'en-US';
$scope.result_mode = "file";//values: "asr" | "file" | TODO: "asr&file"
$scope.asr_result = {
text: ""
};
$scope._asr_alternatives = 20;
//your API key from Google Console for Google Cloud Speech service (secret!!!)
// for more details on how to obtain an API key see e.g.
// WARNING: for security reasons, it's recommended to use service API auth instead of an app key
// ... in any case: only use this for test, NEVER publish your secret key!
var __key = /\bkey=([^&]*)/.exec(document.location.search);//<- for testing: set app key via search-param in URL: ...?key=<API key>
$scope._google_api_key = __key? __key[1] : 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
var __authMethod = /\bauth=([^&]*)/.exec(document.location.search);//<- for testing: set auth-method via search-param in URL: ...?auth=<authentification method>
$scope.auth = __authMethod? __authMethod[1] : null;//values: "apiKey" | "serviceKey" (DEFAULT: "apiKey")
//do not changes these: this "detects" if a key for the Google Speech API is set or not
// (and updates page accordingly, i.e. enable/disable check-box for sending audio to ASR service):
var __def_key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
$scope.isNotASRCapable = $scope._google_api_key === __def_key;
$scope.recordaswave = function(isUseWavFormat) {
$scope.wav_format = isUseWavFormat;
};
$scope.setResultMode = function(isEnableASR){
$scope.result_mode = isEnableASR? 'asr' : 'file';
};
$scope.startRecording = function(e) {
if ($scope.recording)
return;
console.log('start recording');//DEBUG
$scope.encoder = new Worker('encoder.js');
if ($scope.wav_format == true){
$scope.encoder.postMessage({ cmd: 'save_as_wavfile'});
}
$scope.encoder.onmessage = function(e) {
if (e.data.cmd == 'end') {
var resultMode = $scope.result_mode;
if(resultMode === 'file'){
var fname = $scope.wav_format ? $scope.outfilename_wav : $scope.outfilename_flac;
$scope.forceDownload(e.data.buf, fname);
}
else if(resultMode === 'asr'){
if($scope.wav_format){
//can only use FLAC format (not WAVE)!
alert('Can only use FLAC format for speech recognition!');
}
else {
$scope.sendASRRequest(e.data.buf);
}
}
else {
console.error('Unknown mode for processing STOP RECORDING event: "'+resultMode+'"!');
}
$scope.encoder.terminate();
$scope.encoder = null;
} else if (e.data.cmd == 'debug') {
console.log(e.data);
} else {
console.error('Unknown event from encoder (WebWorker): "'+e.data.cmd+'"!');
}
};
if(navigator.webkitGetUserMedia)
navigator.webkitGetUserMedia({ video: false, audio: true }, $scope.gotUserMedia, $scope.userMediaFailed);
else if(navigator.mozGetUserMedia)
navigator.mozGetUserMedia({ video: false, audio: true }, $scope.gotUserMedia, $scope.userMediaFailed);
else
navigator.getUserMedia({ video: false, audio: true }, $scope.gotUserMedia, $scope.userMediaFailed);
};
$scope.userMediaFailed = function(code) {
console.log('grabbing microphone failed: ' + code);
};
$scope.gotUserMedia = function(localMediaStream) {
$scope.recording = true;
$scope.recordButtonStyle = '';
console.log('success grabbing microphone');
$scope.stream = localMediaStream;
var audio_context;
if(typeof webkitAudioContext !== 'undefined'){
audio_context = new webkitAudioContext;
}else if(typeof AudioContext !== 'undefined'){
audio_context = new AudioContext;
}
else {
console.error('JavaScript execution environment (Browser) does not support AudioContext interface.');
alert('Could not start recording audio:\n Web Audio is not supported by your browser!');
return;
}
$scope.audio_context = audio_context;
$scope.input = audio_context.createMediaStreamSource($scope.stream);
if($scope.input.context.createJavaScriptNode)
$scope.node = $scope.input.context.createJavaScriptNode(4096, 1, 1);
else if($scope.input.context.createScriptProcessor)
$scope.node = $scope.input.context.createScriptProcessor(4096, 1, 1);
else
console.error('Could not create audio node for JavaScript based Audio Processing.');
var sampleRate = $scope.audio_context.sampleRate;
console.log('audioContext.sampleRate: ' + sampleRate);//DEBUG
if($scope.autoSelectSamplerate){
$scope.samplerate = sampleRate;
}
console.log('initializing encoder with:');//DEBUG
console.log(' bits-per-sample = ' + $scope.flacdata.bps);//DEBUG
console.log(' channels = ' + $scope.flacdata.channels);//DEBUG
console.log(' sample rate = ' + $scope.samplerate);//DEBUG
console.log(' compression = ' + $scope.compression);//DEBUG
$scope.encoder.postMessage({ cmd: 'init', config: { samplerate: $scope.samplerate, bps: $scope.flacdata.bps, channels: $scope.flacdata.channels, compression:$scope.compression } });
$scope.node.onaudioprocess = function(e) {
if (!$scope.recording)
return;
// see also: http://typedarray.org/from-microphone-to-wav-with-getusermedia-and-web-audio/
var channelLeft = e.inputBuffer.getChannelData(0);
// var channelRight = e.inputBuffer.getChannelData(1);
$scope.encoder.postMessage({ cmd: 'encode', buf: channelLeft});
};
$scope.input.connect($scope.node);
$scope.node.connect(audio_context.destination);
$scope.$apply();
};
$scope.stopRecording = function() {
if (!$scope.recording) {
return;
}
$scope.recordButtonStyle = "red-btn";
console.log('stop recording');
var tracks = $scope.stream.getAudioTracks()
for(var i = tracks.length - 1; i >= 0; --i){
tracks[i].stop();
}
$scope.recording = false;
$scope.encoder.postMessage({ cmd: 'finish' });
$scope.input.disconnect();
$scope.node.disconnect();
$scope.input = $scope.node = null;
};
//create A-element for data BLOB and trigger download
$scope.forceDownload = function(blob, filename){
var url = (window.URL || window.webkitURL).createObjectURL(blob);
var link = window.document.createElement('a');
link.href = url;
link.download = filename || 'output.flac';
//NOTE: FireFox requires a MouseEvent (in Chrome a simple Event would do the trick)
var click = document.createEvent("MouseEvent");
click.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
link.dispatchEvent(click);
};
$scope.num = 0;
$scope.sendASRRequest = function(blob) {
function ajaxSuccess() {
var result = this.responseText;
console.log("AJAXSubmit - Success!"); //DEBUG
console.log(result);
try {
result = JSON.parse(result);
//format the result
result = JSON.stringify(result, null, 2);
} catch (exc) {
console.warn('Could not parse result into JSON object: "' + result + '"');
}
$scope.$apply(function() {
$scope.asr_result.text = result;
});
}
var data;
var sample_rate = $scope.samplerate;
var language = $scope.language;
var key = $scope._google_api_key;
var alternatives = $scope._asr_alternatives;
// use FileReader to convert Blob to base64 encoded data-URL
var reader = new window.FileReader();
reader.readAsDataURL(blob);
reader.onloadend = function() {
//only use base64-encoded data, i.e. remove meta-data from beginning:
var audioData = reader.result.replace(/^data:audio\/flac;base64,/,'');
data = {
config: {
encoding: "FLAC",
sampleRateHertz: sample_rate,
languageCode: language,
maxAlternatives: alternatives
},
audio: {
content: audioData
}
};
var oAjaxReq = new XMLHttpRequest();
oAjaxReq.onload = ajaxSuccess;
//NOTE: instead of API, it is recommended to use service API authentification,
// then create an access_token (on your server, retrieve it with the client)
// and set the token as access_token param
// ?access_token=<access_token>
//
// or set it in the header as
// Authorization: Bearer <access_token>
//
// (see example code below)
if(!$scope.auth){
$scope.auth = 'serviceKey';
} else if($scope.auth !== 'apiKey'){
console.error('unknown authentification method: ', $scope.auth);
}
var params = $scope.auth === 'apiKey'? '?key='+key : ($scope.auth === 'serviceKey'? '?access_token='+key : '');
//DISABLED: currently Google Cloud Speech does not support authorization (headers) in combination with CORS
// if($scope.auth === 'serviceKey'){
// oAjaxReq.setRequestHeader("Authorization", "Bearer "+key);
// oAjaxReq.withCredentials = true;
// }
oAjaxReq.open("post", "https://speech.googleapis.com/v1/speech:recognize"+params, true);
oAjaxReq.setRequestHeader("Content-Type", "application/json");
oAjaxReq.send(JSON.stringify(data));
};
$scope.$apply(function() {
$scope.asr_result.text = "Waiting for Recognition Result...";
});
};
}]);