├── .gitignore ├── .npmignore ├── .travis.yml ├── Gruntfile.coffee ├── LICENSE ├── README.md ├── lib └── voicetext.js ├── package.json ├── sample └── index.coffee └── src ├── lib └── voicetext.coffee └── test └── voicetext_test.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | npm-debug.log 3 | out/ 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | 3 | .travis* 4 | History.md 5 | Gruntfile..coffee 6 | Gruntfile.js 7 | 8 | src/ 9 | /out/bin/ 10 | /out/docs/ 11 | /out/test/ 12 | 13 | .git/ 14 | .gitignore 15 | .jshintrc 16 | .idea/ 17 | 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | -------------------------------------------------------------------------------- /Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = (grunt)-> 4 | 5 | # load all grunt tasks 6 | (require 'matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks) 7 | 8 | _ = grunt.util._ 9 | path = require 'path' 10 | 11 | # Project configuration. 12 | grunt.initConfig 13 | pkg: grunt.file.readJSON('package.json') 14 | coffeelint: 15 | gruntfile: 16 | src: '<%= watch.gruntfile.files %>' 17 | lib: 18 | src: '<%= watch.lib.files %>' 19 | test: 20 | src: '<%= watch.test.files %>' 21 | options: 22 | no_trailing_whitespace: 23 | level: 'error' 24 | max_line_length: 25 | level: 'warn' 26 | coffee: 27 | lib: 28 | expand: true 29 | cwd: 'src/lib/' 30 | src: ['**/*.coffee'] 31 | dest: 'lib/' 32 | ext: '.js' 33 | test: 34 | expand: true 35 | cwd: 'src/test/' 36 | src: ['**/*.coffee'] 37 | dest: 'out/test/' 38 | ext: '.js' 39 | simplemocha: 40 | all: 41 | src: [ 42 | 'out/test/**/*.js' 43 | ] 44 | options: 45 | timeout: 3000 46 | ignoreLeaks: false 47 | ui: 'bdd' 48 | reporter: 'spec' 49 | watch: 50 | options: 51 | spawn: false 52 | gruntfile: 53 | files: 'Gruntfile.coffee' 54 | tasks: ['coffeelint:gruntfile'] 55 | lib: 56 | files: ['src/lib/**/*.coffee'] 57 | tasks: ['coffeelint:lib', 'coffee:lib', 'simplemocha'] 58 | test: 59 | files: ['src/test/**/*.coffee'] 60 | tasks: ['coffeelint:test', 'coffee:test', 'simplemocha'] 61 | clean: ['out/'] 62 | 63 | grunt.event.on 'watch', (action, files, target)-> 64 | grunt.log.writeln "#{target}: #{files} has #{action}" 65 | 66 | # coffeelint 67 | grunt.config ['coffeelint', target], src: files 68 | 69 | # coffee 70 | coffeeData = grunt.config ['coffee', target] 71 | files = [files] if _.isString files 72 | files = files.map (file)-> path.relative coffeeData.cwd, file 73 | coffeeData.src = files 74 | 75 | grunt.config ['coffee', target], coffeeData 76 | 77 | # tasks. 78 | grunt.registerTask 'compile', [ 79 | 'coffeelint' 80 | 'coffee' 81 | ] 82 | 83 | grunt.registerTask 'test', [ 84 | 'simplemocha' 85 | ] 86 | 87 | grunt.registerTask 'default', [ 88 | 'compile' 89 | 'test' 90 | ] 91 | 92 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 pchw 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # voicetext [![Build Status](https://travis-ci.org/pchw/node-voicetext.svg?branch=master)](https://travis-ci.org/pchw/node-voicetext) 2 | 3 | [VoiceText Web API beta](https://cloud.voicetext.jp/) client for node.js 4 | 5 | ## Getting Started 6 | Install the module with: `npm install voicetext` 7 | 8 | ## Documentation 9 | See [official api doc](https://cloud.voicetext.jp/webapi/docs/api) 10 | 11 | ``` 12 | voice 13 | .speaker(voice.SPEAKER.HIKARI) 14 | .emotion(voice.EMOTION.HAPPINESS) 15 | .emotion_level(voice.EMOTION_LEVEL.HIGH) 16 | .pitch(200) 17 | .speed(400) 18 | .volume(200) 19 | .speak 'きょうもいちにちがんばるぞい', (e, buf)-> 20 | ``` 21 | 22 | ## Examples 23 | ```coffee-script 24 | VoiceText = require 'voicetext' 25 | 26 | voice = new VoiceText('') 27 | voice 28 | .speaker(voice.SPEAKER.HIKARI) 29 | .speak 'おはようございます', (e, buf)-> 30 | console.error e if e 31 | fs.writeFile './test.wav', buf, 'binary', (e)-> 32 | console.error e if e 33 | # ./test.wav file generated 34 | ``` 35 | 36 | ## Release History 37 | _(Nothing yet)_ 38 | 39 | ## License 40 | Copyright (c) 2014 pchw. Licensed under the MIT license. -------------------------------------------------------------------------------- /lib/voicetext.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | voicetext 5 | https://github.com/pchw/node-voicetext 6 | 7 | Copyright (c) 2014 pchw 8 | Licensed under the MIT license. 9 | */ 10 | 11 | (function() { 12 | 'use strict'; 13 | var VoiceText, debug, request; 14 | 15 | request = require('superagent'); 16 | 17 | debug = require('debug')('voicetext'); 18 | 19 | module.exports = VoiceText = (function() { 20 | VoiceText.prototype.API_URL = 'https://api.voicetext.jp/v1/tts'; 21 | 22 | VoiceText.prototype.EMOTION = { 23 | NONE: void 0, 24 | HAPPINESS: 'happiness', 25 | ANGER: 'anger', 26 | SADNESS: 'sadness' 27 | }; 28 | 29 | VoiceText.prototype.EMOTION_LEVEL = { 30 | NONE: void 0, 31 | EXTREME: '4', 32 | SUPER: '3', 33 | HIGH: '2', 34 | NORMAL: '1' 35 | }; 36 | 37 | VoiceText.prototype.SPEAKER = { 38 | SHOW: 'show', 39 | HARUKA: 'haruka', 40 | HIKARI: 'hikari', 41 | TAKERU: 'takeru', 42 | SANTA: 'santa', 43 | BEAR: 'bear' 44 | }; 45 | 46 | VoiceText.prototype.FORMAT = { 47 | OGG: 'ogg', 48 | WAV: 'wav', 49 | AAC: 'aac' 50 | }; 51 | 52 | function VoiceText(api_key) { 53 | this.api_key = api_key; 54 | this._pitch = 100; 55 | this._speed = 100; 56 | this._volume = 100; 57 | this._speaker = this.SPEAKER.SHOW; 58 | this._emotion = void 0; 59 | this._emotion_level = void 0; 60 | this._format = this.FORMAT.WAV; 61 | this; 62 | } 63 | 64 | VoiceText.prototype.speaker = function(speaker) { 65 | var k, v, _ref; 66 | _ref = this.SPEAKER; 67 | for (k in _ref) { 68 | v = _ref[k]; 69 | if (speaker === v) { 70 | this._speaker = v; 71 | } 72 | } 73 | return this; 74 | }; 75 | 76 | VoiceText.prototype.emotion = function(cat) { 77 | var k, v, _ref; 78 | if (this._speaker === this.SPEAKER.SHOW) { 79 | return this; 80 | } 81 | _ref = this.EMOTION; 82 | for (k in _ref) { 83 | v = _ref[k]; 84 | if (cat === v) { 85 | this._emotion = v; 86 | } 87 | } 88 | return this; 89 | }; 90 | 91 | VoiceText.prototype.emotion_level = function(lvl) { 92 | var k, v, _ref; 93 | if (this._speaker === this.SPEAKER.SHOW) { 94 | return this; 95 | } 96 | _ref = this.EMOTION_LEVEL; 97 | for (k in _ref) { 98 | v = _ref[k]; 99 | if (lvl === v) { 100 | this._emotion_level = v; 101 | } 102 | } 103 | return this; 104 | }; 105 | 106 | VoiceText.prototype.pitch = function(lvl) { 107 | if ((50 <= lvl && lvl <= 200)) { 108 | this._pitch = lvl; 109 | } 110 | return this; 111 | }; 112 | 113 | VoiceText.prototype.speed = function(lvl) { 114 | if ((50 <= lvl && lvl <= 400)) { 115 | this._speed = lvl; 116 | } 117 | return this; 118 | }; 119 | 120 | VoiceText.prototype.volume = function(lvl) { 121 | if ((50 <= lvl && lvl <= 200)) { 122 | this._volume = lvl; 123 | } 124 | return this; 125 | }; 126 | 127 | VoiceText.prototype.format = function(format) { 128 | var k, v, _ref; 129 | _ref = this.FORMAT; 130 | for (k in _ref) { 131 | v = _ref[k]; 132 | if (format === v) { 133 | this._format = v; 134 | } 135 | } 136 | return this; 137 | }; 138 | 139 | VoiceText.prototype.build_params = function(text) { 140 | var params; 141 | return params = { 142 | volume: this._volume, 143 | speed: this._speed, 144 | pitch: this._pitch, 145 | emotion_level: this._emotion_level, 146 | emotion: this._emotion, 147 | speaker: this._speaker, 148 | format: this._format, 149 | text: text 150 | }; 151 | }; 152 | 153 | VoiceText.prototype.speak = function(text, callback) { 154 | if (!text) { 155 | return callback(new Error('invalid argument. text: null')); 156 | } 157 | text = text.slice(0, 200); 158 | debug("params: " + (JSON.stringify(this.build_params(text)))); 159 | debug("access to " + this.API_URL); 160 | debug("api_key is " + this.api_key); 161 | return request.post(this.API_URL).type('form').auth(this.api_key, '').buffer(true).send(this.build_params(text)).parse(function(res, done) { 162 | res.setEncoding('binary'); 163 | res.data = ''; 164 | res.on('data', function(chunk) { 165 | return res.data += chunk; 166 | }); 167 | return res.on('end', function() { 168 | return done(null, new Buffer(res.data, 'binary')); 169 | }); 170 | }).end(function(e, res) { 171 | var _ref; 172 | debug("response status: " + res.status); 173 | debug("response statusType: " + res.statusType); 174 | if (res.status === 200) { 175 | return callback(null, res.body); 176 | } else if ((_ref = res.statusType) === 4 || _ref === 5) { 177 | return callback(new Error(JSON.stringify(res.body))); 178 | } 179 | }); 180 | }; 181 | 182 | return VoiceText; 183 | 184 | })(); 185 | 186 | }).call(this); 187 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "voicetext", 3 | "version": "0.0.7", 4 | "main": "lib/voicetext.js", 5 | "description": "voicetext client", 6 | "homepage": "https://github.com/pchw/node-voicetext", 7 | "bugs": "https://github.com/pchw/voicetext/issues", 8 | "author": { 9 | "name": "pchw", 10 | "email": "bump.luv@gmail.com", 11 | "url": "http://pchw.github.io" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/pchw/node-voicetext" 16 | }, 17 | "licenses": [ 18 | { 19 | "type": "MIT" 20 | } 21 | ], 22 | "main":"lib/voicetext.js", 23 | "keywords":["voicetext"], 24 | "devDependencies": { 25 | "grunt-contrib-watch": "~0.5.3", 26 | "grunt-contrib-coffee": "~0.9.0", 27 | "grunt-contrib-clean": "~0.5.0", 28 | "grunt-coffeelint": "~0.0.8", 29 | "grunt-simple-mocha": "~0.4.0", 30 | "should": "~3.1.2", 31 | "matchdep": "~0.3.0", 32 | "mocha-cakes": "~0.9.0", 33 | "grunt": "~0.4.2", 34 | "coffee-script": "^1.7.1" 35 | }, 36 | "dependencies": { 37 | "superagent": "^0.18.1", 38 | "debug": "^1.0.2" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /sample/index.coffee: -------------------------------------------------------------------------------- 1 | require('coffee-script/register') 2 | fs = require 'fs' 3 | VoiceText = require '../src/lib/voicetext' 4 | 5 | voice = new VoiceText('') 6 | 7 | voice 8 | .speaker(voice.SPEAKER.HIKARI) 9 | .speak 'おはようございます', (e, buf)-> 10 | console.error e if e 11 | fs.writeFile './test.wav', buf, 'binary', (e)-> 12 | console.error e if e 13 | # ./test.wav file generated -------------------------------------------------------------------------------- /src/lib/voicetext.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | 3 | voicetext 4 | https://github.com/pchw/node-voicetext 5 | 6 | Copyright (c) 2014 pchw 7 | Licensed under the MIT license. 8 | 9 | ### 10 | 11 | 'use strict' 12 | 13 | request = require 'superagent' 14 | debug = require('debug')('voicetext') 15 | 16 | module.exports = class VoiceText 17 | API_URL: 'https://api.voicetext.jp/v1/tts' 18 | EMOTION: 19 | NONE: undefined 20 | HAPPINESS: 'happiness' 21 | ANGER: 'anger' 22 | SADNESS :'sadness' 23 | EMOTION_LEVEL: 24 | NONE: undefined 25 | EXTREME: '4' 26 | SUPER: '3' 27 | HIGH: '2' 28 | NORMAL: '1' 29 | SPEAKER: 30 | SHOW: 'show' 31 | HARUKA: 'haruka' 32 | HIKARI: 'hikari' 33 | TAKERU: 'takeru' 34 | SANTA: 'santa' 35 | BEAR: 'bear' 36 | FORMAT: 37 | OGG: 'ogg' 38 | WAV: 'wav' 39 | AAC: 'aac' 40 | 41 | constructor: (@api_key)-> 42 | @_pitch = 100 43 | @_speed = 100 44 | @_volume = 100 45 | @_speaker = @SPEAKER.SHOW 46 | @_emotion = undefined 47 | @_emotion_level = undefined 48 | @_format = @FORMAT.WAV 49 | @ 50 | 51 | speaker: (speaker)-> 52 | for k,v of @SPEAKER 53 | if speaker is v 54 | @_speaker = v 55 | @ 56 | 57 | emotion: (cat)-> 58 | # can not change emotion when speaker is SHOW 59 | if @_speaker is @SPEAKER.SHOW 60 | return @ 61 | 62 | for k,v of @EMOTION 63 | if cat is v 64 | @_emotion = v 65 | @ 66 | 67 | emotion_level: (lvl)-> 68 | return @ if @_speaker is @SPEAKER.SHOW 69 | for k,v of @EMOTION_LEVEL 70 | if lvl is v 71 | @_emotion_level = v 72 | @ 73 | 74 | pitch: (lvl)-> 75 | if 50 <= lvl <= 200 76 | @_pitch = lvl 77 | @ 78 | 79 | speed: (lvl)-> 80 | if 50 <= lvl <= 400 81 | @_speed = lvl 82 | @ 83 | 84 | volume: (lvl)-> 85 | if 50 <= lvl <= 200 86 | @_volume = lvl 87 | @ 88 | 89 | format: (format)-> 90 | for k,v of @FORMAT 91 | if format is v 92 | @_format = v 93 | @ 94 | 95 | build_params: (text)-> 96 | params = 97 | volume: @_volume 98 | speed: @_speed 99 | pitch: @_pitch 100 | emotion_level: @_emotion_level 101 | emotion: @_emotion 102 | speaker: @_speaker 103 | format: @_format 104 | text: text 105 | 106 | speak: (text, callback)-> 107 | return callback new Error 'invalid argument. text: null' unless text 108 | 109 | # maximum text size is 200 110 | text = text.slice(0,200) 111 | 112 | debug "params: #{JSON.stringify(@build_params text)}" 113 | debug "access to #{@API_URL}" 114 | debug "api_key is #{@api_key}" 115 | 116 | request 117 | .post(@API_URL) 118 | .type('form') 119 | .auth(@api_key, '') 120 | .buffer(true) 121 | .send(@build_params text) 122 | .parse( 123 | (res,done)-> 124 | res.setEncoding 'binary' 125 | res.data = '' 126 | res.on 'data', (chunk)-> 127 | res.data += chunk 128 | res.on 'end', -> 129 | done null, new Buffer(res.data, 'binary') 130 | ) 131 | .end (e, res)-> 132 | debug "response status: #{res.status}" 133 | debug "response statusType: #{res.statusType}" 134 | if res.status is 200 135 | # res.body is binary 136 | callback null, res.body 137 | else if res.statusType in [4, 5] 138 | callback new Error(JSON.stringify res.body) 139 | 140 | -------------------------------------------------------------------------------- /src/test/voicetext_test.coffee: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | should = require 'should' 4 | voicetext = require '../../lib/voicetext.js' 5 | 6 | ### 7 | ======== A Handy Little Mocha Reference ======== 8 | https://github.com/visionmedia/should.js 9 | https://github.com/visionmedia/mocha 10 | 11 | Mocha hooks: 12 | before ()-> # before describe 13 | after ()-> # after describe 14 | beforeEach ()-> # before each it 15 | afterEach ()-> # after each it 16 | 17 | Should assertions: 18 | should.exist('hello') 19 | should.fail('expected an error!') 20 | true.should.be.ok 21 | true.should.be.true 22 | false.should.be.false 23 | 24 | (()-> arguments)(1,2,3).should.be.arguments 25 | [1,2,3].should.eql([1,2,3]) 26 | should.strictEqual(undefined, value) 27 | user.age.should.be.within(5, 50) 28 | username.should.match(/^\w+$/) 29 | 30 | user.should.be.a('object') 31 | [].should.be.an.instanceOf(Array) 32 | 33 | user.should.have.property('age', 15) 34 | 35 | user.age.should.be.above(5) 36 | user.age.should.be.below(100) 37 | user.pets.should.have.length(5) 38 | 39 | res.should.have.status(200) #res.statusCode should be 200 40 | res.should.be.json 41 | res.should.be.html 42 | res.should.have.header('Content-Length', '123') 43 | 44 | [].should.be.empty 45 | [1,2,3].should.include(3) 46 | 'foo bar baz'.should.include('foo') 47 | { name: 'TJ', pet: tobi }.user.should.include({ pet: tobi, name: 'TJ' }) 48 | { foo: 'bar', baz: 'raz' }.should.have.keys('foo', 'bar') 49 | 50 | (()-> throw new Error('failed to baz')).should.throwError(/^fail.+/) 51 | 52 | user.should.have.property('pets').with.lengthOf(4) 53 | user.should.be.a('object').and.have.property('name', 'tj') 54 | ### 55 | 56 | describe 'VoiceText', -> 57 | describe '#of()', -> 58 | it 'new', -> 59 | v = new voicetext() 60 | should.exists v 61 | 62 | --------------------------------------------------------------------------------