├── .gitignore ├── CHANGELOG ├── CONTRIBUTING.md ├── LICENSE.md ├── Procfile ├── README.md ├── app.js ├── bin └── www ├── config ├── default.js ├── development.js ├── production.js └── test.js ├── endpoints └── recognition.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 27 | node_modules 28 | 29 | # Debug log from npm 30 | npm-debug.log 31 | 32 | # Generic ignore 33 | .DS_Store 34 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | v1.0.0: 2 | date: 2015-12-7 3 | changes: 4 | - Initial release. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Speechy 2 | 3 | Please take a moment to review this document in order to make the contribution 4 | process easy and effective for everyone involved. 5 | 6 | Following these guidelines helps to communicate that you respect the time of 7 | the developers managing and developing this open source project. In return, 8 | they should reciprocate that respect in addressing your issue or assessing 9 | patches and features. 10 | 11 | 12 | ## Using the issue tracker 13 | 14 | The issue tracker is the preferred channel for [bug reports](#bug-reports), 15 | [features requests](#feature-requests) and [submitting pull 16 | requests](#pull-requests), but please respect the following restrictions: 17 | 18 | * Please **do not** use the issue tracker for personal support requests (use 19 | [Stack Overflow](http://stackoverflow.com) or IRC). 20 | 21 | * Please **do not** derail or troll issues. Keep the discussion on topic and 22 | respect the opinions of others. 23 | 24 | 25 | ## Bug reports 26 | 27 | A bug is a _demonstrable problem_ that is caused by the code in the repository. 28 | Good bug reports are extremely helpful - thank you! 29 | 30 | Guidelines for bug reports: 31 | 32 | 1. **Use the GitHub issue search** — check if the issue has already been 33 | reported. 34 | 35 | 2. **Check if the issue has been fixed** — try to reproduce it using the 36 | latest `master` or development branch in the repository. 37 | 38 | 3. **Isolate the problem** — create a [reduced test 39 | case](http://css-tricks.com/6263-reduced-test-cases/) and a live example. 40 | 41 | A good bug report shouldn't leave others needing to chase you up for more 42 | information. Please try to be as detailed as possible in your report. What is 43 | your environment? What steps will reproduce the issue? What browser(s) and OS 44 | experience the problem? What would you expect to be the outcome? All these 45 | details will help people to fix any potential bugs. 46 | 47 | Example: 48 | 49 | > Short and descriptive example bug report title 50 | > 51 | > A summary of the issue and the browser/OS environment in which it occurs. If 52 | > suitable, include the steps required to reproduce the bug. 53 | > 54 | > 1. This is the first step 55 | > 2. This is the second step 56 | > 3. Further steps, etc. 57 | > 58 | > `` - a link to the reduced test case 59 | > 60 | > Any other information you want to share that is relevant to the issue being 61 | > reported. This might include the lines of code that you have identified as 62 | > causing the bug, and potential solutions (and your opinions on their 63 | > merits). 64 | 65 | 66 | ## Feature requests 67 | 68 | Feature requests are welcome. But take a moment to find out whether your idea 69 | fits with the scope and aims of the project. It's up to *you* to make a strong 70 | case to convince the project's developers of the merits of this feature. Please 71 | provide as much detail and context as possible. 72 | 73 | 74 | ## Pull requests 75 | 76 | Good pull requests - patches, improvements, new features - are a fantastic 77 | help. They should remain focused in scope and avoid containing unrelated 78 | commits. 79 | 80 | **Please ask first** before embarking on any significant pull request (e.g. 81 | implementing features, refactoring code, porting to a different language), 82 | otherwise you risk spending a lot of time working on something that the 83 | project's developers might not want to merge into the project. 84 | 85 | Please adhere to the coding conventions used throughout a project (indentation, 86 | accurate comments, etc.) and any other requirements (such as test coverage). 87 | 88 | Follow this process if you'd like your work considered for inclusion in the 89 | project: 90 | 91 | 1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork, 92 | and configure the remotes: 93 | 94 | ```bash 95 | # Clone your fork of the repo into the current directory 96 | git clone https://github.com//speechy 97 | # Navigate to the newly cloned directory 98 | cd speechy 99 | # Assign the original repo to a remote called "upstream" 100 | git remote add upstream https://github.com/chrisenytc/speechy 101 | ``` 102 | 103 | 2. If you cloned a while ago, get the latest changes from upstream: 104 | 105 | ```bash 106 | git checkout 107 | git pull upstream 108 | ``` 109 | 110 | 3. Create a new topic branch (off the main project development branch) to 111 | contain your feature, change, or fix: 112 | 113 | ```bash 114 | git checkout -b 115 | ``` 116 | 117 | 4. Commit your changes in logical chunks. Please adhere to these [git commit 118 | message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) 119 | or your code is unlikely be merged into the main project. Use Git's 120 | [interactive rebase](https://help.github.com/articles/interactive-rebase) 121 | feature to tidy up your commits before making them public. 122 | 123 | 5. Locally merge (or rebase) the upstream development branch into your topic branch: 124 | 125 | ```bash 126 | git pull [--rebase] upstream 127 | ``` 128 | 129 | 6. Push your topic branch up to your fork: 130 | 131 | ```bash 132 | git push origin 133 | ``` 134 | 135 | 7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) 136 | with a clear title and description. 137 | 138 | ## Conventions of commit messages 139 | 140 | Addding files on repo 141 | 142 | ```bash 143 | git commit -m "Add filename" 144 | ``` 145 | 146 | Updating files on repo 147 | 148 | ```bash 149 | git commit -m "Update filename, filename2, filename3" 150 | ``` 151 | 152 | Removing files on repo 153 | 154 | ```bash 155 | git commit -m "Remove filename" 156 | ``` 157 | 158 | Renaming files on repo 159 | 160 | ```bash 161 | git commit -m "Rename filename" 162 | ``` 163 | 164 | Fixing errors and issues on repo 165 | 166 | ```bash 167 | git commit -m "Fixed #issuenumber Message about this fix" 168 | ``` 169 | 170 | Adding features on repo 171 | 172 | ```bash 173 | git commit -m "Add Feature: nameoffeature Message about this feature" 174 | ``` 175 | 176 | Updating features on repo 177 | 178 | ```bash 179 | git commit -m "Update Feature: nameoffeature Message about this update" 180 | ``` 181 | 182 | Removing features on repo 183 | 184 | ```bash 185 | git commit -m "Remove Feature: nameoffeature Message about this" 186 | ``` 187 | 188 | Ignoring Travis CI build on repo 189 | 190 | ```bash 191 | git commit -m "Commit message here [ci-skip]" 192 | ``` 193 | 194 | **IMPORTANT**: By submitting a patch, you agree to allow the project owner to 195 | license your work under the same license as that used by the project. 196 | 197 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2015, Christopher EnyTC 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | 26 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node ./bin/www 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Speechy 2 | 3 | > A speech recognition API service to decode audio to text 4 | 5 | ## Getting Started 6 | 7 | 1º Clone speechy repo 8 | 9 | ```bash 10 | git clone git@github.com:chrisenytc/speechy.git 11 | ``` 12 | 13 | 2º Enter in speechy directory 14 | ```bash 15 | cd speechy 16 | ``` 17 | 18 | 3º Install dependencies 19 | 20 | ```bash 21 | npm install 22 | ``` 23 | 24 | 4º Configure the settings in `config` 25 | 26 | 5º Run speechy 27 | 28 | ```bash 29 | npm start 30 | ``` 31 | 32 | Test your speechy app 33 | 34 | ```bash 35 | npm test 36 | ``` 37 | 38 | ## How to get a Speech API key 39 | 40 | See how to get a speech api key for use with speechy in [this link](https://www.chromium.org/developers/how-tos/api-keys) 41 | 42 | ## Contributing 43 | 44 | See the [CONTRIBUTING Guidelines](https://github.com/chrisenytc/speechy/blob/master/CONTRIBUTING.md) 45 | 46 | ## Support 47 | If you have any problem or suggestion please open an issue [here](https://github.com/chrisenytc/speechy/issues). 48 | 49 | ## License 50 | 51 | The MIT License 52 | 53 | Copyright (c) 2015, Christopher EnyTC 54 | 55 | Permission is hereby granted, free of charge, to any person 56 | obtaining a copy of this software and associated documentation 57 | files (the "Software"), to deal in the Software without 58 | restriction, including without limitation the rights to use, 59 | copy, modify, merge, publish, distribute, sublicense, and/or sell 60 | copies of the Software, and to permit persons to whom the 61 | Software is furnished to do so, subject to the following 62 | conditions: 63 | 64 | The above copyright notice and this permission notice shall be 65 | included in all copies or substantial portions of the Software. 66 | 67 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 68 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 69 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 70 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 71 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 72 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 73 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 74 | OTHER DEALINGS IN THE SOFTWARE. 75 | 76 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var path = require('path'); 3 | var logger = require('morgan'); 4 | var express = require('express'); 5 | var bodyParser = require('body-parser'); 6 | var responseTime = require('response-time'); 7 | var requestId = require('request-id/express'); 8 | var methodOverride = require('method-override'); 9 | 10 | var env = process.env.NODE_ENV || 'development'; 11 | var routes = require('./endpoints/recognition'); 12 | 13 | // Get config file 14 | var defaultConfig = require(path.join(process.cwd(), 'config', 'default')); 15 | 16 | // Merge default config file with environment config file 17 | _.merge(defaultConfig, require(path.join(process.cwd(), 'config', env))); 18 | 19 | var app = express(); 20 | 21 | app.use(logger('combined')); 22 | app.use(responseTime()); 23 | app.use(requestId()); 24 | app.use(bodyParser.json()); 25 | app.use(bodyParser.urlencoded({ extended: true })); 26 | app.use(methodOverride()); 27 | 28 | //Config middleware 29 | app.use(function(req, res, next) { 30 | req.configs = defaultConfig; 31 | return next(); 32 | }); 33 | 34 | // Parse header 35 | app.use(function(req, res, next) { 36 | if(req.headers['x-api-key']) { 37 | req.query.api_key = req.headers['x-api-key']; 38 | } 39 | return next(); 40 | }); 41 | 42 | // Authentication 43 | app.use(function(req, res, next) { 44 | if(req.query.api_key && req.query.api_key == req.configs.auth.token) { 45 | return next(); 46 | } 47 | return res.status(403).json({ error: 'Unauthorized!' }); 48 | }); 49 | 50 | app.use('/', routes); 51 | 52 | // catch 404 and forward to error handler 53 | app.use(function(req, res, next) { 54 | var err = new Error(req.configs.errors.notFound.message); 55 | res.status = 404; 56 | return res.json({ 57 | error: err.message 58 | }); 59 | }); 60 | 61 | // error handlers 62 | 63 | // development error handler 64 | // will print stacktrace 65 | if (app.get('env') === 'development') { 66 | app.use(function(err, req, res, next) { 67 | res.status(err.status || 500); 68 | return res.json({ 69 | message: err.message, 70 | error: err 71 | }); 72 | }); 73 | } 74 | 75 | // production error handler 76 | // no stacktraces leaked to user 77 | app.use(function(err, req, res, next) { 78 | res.status(err.status || 500); 79 | return res.json({ 80 | message: req.configs.errors.serverError.message, 81 | error: {} 82 | }); 83 | }); 84 | 85 | module.exports = app; 86 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('speechy:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /config/default.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | temp_dir: process.env.TEMP_DIR || '/tmp/rescuer/', 4 | speech: { 5 | key: process.env.GOOGLE_SPEECH_KEY || '', 6 | lang: process.env.GOOGLE_SPEECH_LANGUAGE || 'pt-BR', 7 | max_results: process.env.GOOGLE_SPEECH_MAX_RESULTS || '1' 8 | }, 9 | auth: { 10 | token: process.env.AUTH_TOKEN || '' 11 | }, 12 | messages: { 13 | welcome: { 14 | message: 'Welcome to the Speechy Recognition API.' 15 | }, 16 | documentation_url: { 17 | message: 'https://github.com/chrisenytc/speechy/#documentation' 18 | } 19 | }, 20 | errors: { 21 | serverError: { 22 | message: 'An error has occurred!' 23 | }, 24 | notFound: { 25 | message: 'Page not found.' 26 | }, 27 | required: { 28 | message: 'The parameter \'url\' is required!' 29 | }, 30 | invalid: { 31 | message: 'Invalid url.' 32 | }, 33 | fail: { 34 | message: 'An error has occurred when try to download the audio file!' 35 | }, 36 | unknow: { 37 | message: 'Unable to decode the audio!' 38 | } 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /config/development.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | speech: { 4 | key: process.env.GOOGLE_SPEECH_KEY || 'ENTER_YOUR_KEY' 5 | }, 6 | auth: { 7 | token: process.env.AUTH_CODE || 'cii04yvgm0000q48tyfve0agm' 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /config/production.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | }; 4 | 5 | -------------------------------------------------------------------------------- /config/test.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | }; 4 | 5 | -------------------------------------------------------------------------------- /endpoints/recognition.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var _ = require('lodash'); 3 | var path = require('path'); 4 | var cuid = require('cuid'); 5 | var express = require('express'); 6 | var request = require('request'); 7 | var speech = require('google-speech-api'); 8 | var router = express.Router(); 9 | 10 | /* GET Welcome resource */ 11 | router.get('/', function(req, res) { 12 | return res.json({ 13 | message: req.configs.messages.welcome.message, 14 | documentation_url: req.configs.messages.documentation_url.message 15 | }); 16 | }); 17 | 18 | /* POST recognition endpoint */ 19 | router.post('/recognition', function(req, res, next) { 20 | 21 | if(!req.body.url) { 22 | return res.status(400).json({ 23 | error: req.configs.errors.required.message 24 | }); 25 | } 26 | 27 | var regex = new RegExp('^https://api.twilio.com/2010-04-01/'); 28 | 29 | if(!regex.test(req.body.url)) { 30 | return res.status(400).json({ 31 | error: req.configs.errors.invalid.message 32 | }); 33 | } 34 | 35 | var output = path.join(req.configs.temp_dir, cuid() + '.wav'); 36 | var stream = fs.createWriteStream(output); 37 | 38 | request 39 | .get(req.body.url) 40 | .on('error', function errorHandler(err) { 41 | console.log('Error: ', err); 42 | return res.status(500).json({ message: req.configs.errors.fail.message }); 43 | }) 44 | .on('end', function handler(apiRes) { 45 | speech({ 46 | file: output, 47 | key: req.configs.speech.key, 48 | lang: req.body.locale || req.configs.speech.lang, 49 | maxResults: req.configs.speech.max_results 50 | }, function (err, results) { 51 | if(err || !results) { 52 | console.log('Speech Error: ', err); 53 | return res.status(500).json({ message: req.configs.errors.unknow.message }); 54 | } 55 | 56 | fs.unlinkSync(output); 57 | 58 | console.log('Speech API output =>'); 59 | console.log(JSON.stringify(results)); 60 | 61 | return res.status(200).json({ 62 | transcripts: _.get(results, '[0].result.[0].alternative') || [] 63 | }); 64 | }); 65 | }) 66 | .pipe(stream); 67 | }); 68 | 69 | module.exports = router; 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "speechy", 3 | "description": "A speech recognition API service to decode audio to text", 4 | "version": "1.0.0", 5 | "homepage": "https://github.com/chrisenytc/speechy", 6 | "private": true, 7 | "author": { 8 | "name": "Christopher EnyTC", 9 | "email": "chris.enytc@gmail.com" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git@github.com:chrisenytc/speechy.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/chrisenytc/speechy/issues" 17 | }, 18 | "main": "app.js", 19 | "engines": { 20 | "node": "4.2.2", 21 | "npm": "3.3.9" 22 | }, 23 | "scripts": { 24 | "start": "forever start -a --uid 'speechyy' --minUptime 1000ms --spinSleepTime 1000ms ./bin/www", 25 | "stop": "forever stop stone" 26 | }, 27 | "dependencies": { 28 | "body-parser": "~1.13.2", 29 | "cuid": "^1.3.8", 30 | "debug": "~2.2.0", 31 | "express": "~4.13.1", 32 | "forever": "~0.15.1", 33 | "google-speech-api": "^1.1.3", 34 | "lodash": "^3.10.1", 35 | "method-override": "^2.3.5", 36 | "morgan": "~1.6.1", 37 | "request": "^2.67.0", 38 | "request-id": "^0.10.0", 39 | "response-time": "^2.3.1" 40 | }, 41 | "keywords": [ 42 | "speech", 43 | "recognition", 44 | "api", 45 | "service", 46 | "google", 47 | "solution", 48 | "rescuer" 49 | ] 50 | } 51 | --------------------------------------------------------------------------------