Skip to content

Instantly share code, notes, and snippets.

@zlargon
Last active June 29, 2016 11:29
Show Gist options
  • Save zlargon/17a7bb4d400b83cd22b54b47353d7fcb to your computer and use it in GitHub Desktop.
Save zlargon/17a7bb4d400b83cd22b54b47353d7fcb to your computer and use it in GitHub Desktop.
Node.js Google Text-To-Speech API Hacking
var googleTTS = require('./google-tts');
var opts = {
text: 'hello world',
lang: 'en',
speed: 1,
timeout: 10000
};
googleTTS(opts, function (err, url) {
if (err) {
console.error(err.stack);
return;
}
console.log(' Text => ' + opts.text);
console.log('Audio URL => ' + url)
});
var https = require('https');
var url = require('url');
var host = 'translate.google.com';
function XL (a, b) {
for (var c = 0; c < b.length - 2; c += 3) {
var d = b.charAt(c + 2);
d = d >= 'a' ? d.charCodeAt(0) - 87 : Number(d);
d = b.charAt(c + 1) == '+' ? a >>> d : a << d;
a = b.charAt(c) == '+' ? a + d & 4294967295 : a ^ d;
}
return a;
}
/**
* Generate API Token
*
* @param {String} text
* @param {String} key
* @return {String} token
*/
function getGoogleApiToken (text, key) {
var a = text, b = key, d = b.split('.');
b = Number(d[0]) || 0;
for (var e = [], f = 0, g = 0; g < a.length; g++) {
var m = a.charCodeAt(g);
128 > m ? e[f++] = m : (2048 > m ? e[f++] = m >> 6 | 192 : (55296 == (m & 64512) && g + 1 < a.length && 56320 == (a.charCodeAt(g + 1) & 64512) ? (m = 65536 + ((m & 1023) << 10) + (a.charCodeAt(++g) & 1023),
e[f++] = m >> 18 | 240,
e[f++] = m >> 12 & 63 | 128) : e[f++] = m >> 12 | 224,
e[f++] = m >> 6 & 63 | 128),
e[f++] = m & 63 | 128);
}
a = b;
for (f = 0; f < e.length; f++) {
a += e[f];
a = XL(a, '+-a^+6');
}
a = XL(a, '+-3^+b+-f');
a ^= Number(d[1]) || 0;
0 > a && (a = (a & 2147483647) + 2147483648);
a = a % 1E6;
return a.toString() + '.' + (a ^ b);
};
/**
* Get Key from https://translate.google.com
*
* @param {Function} callback(err: Error, key: String)
* @param {Number!} timeout default is 10000ms
*/
function getGoogleApiKey (callback, timeout) {
var cb = typeof callback === 'function' ? callback : function () {};
var timeout = timeout || 10 * 1000;
var options = {
hostname: host,
port: 443,
path: '/',
method: 'GET'
};
var req = https.request(options, function (res) {
if (res.statusCode !== 200) {
cb(new Error('request to ' + options.hostname + ' failed, status code = ' + res.statusCode + ' (' + res.statusMessage + ')'));
return;
}
var buffer = [];
res.on('data', function (chunk) {
buffer.push(chunk.toString());
});
res.on('end', function(){
var TKK = null;
try {
var html = buffer.join();
eval(html.match(/TKK=eval\(\'\(.*\)\'\);/g)[0]); // TKK = '405291.1334555331'
if (TKK === null) throw null;
} catch (e) {
cb(new Error('get key failed from google'), null);
return;
}
cb(null, TKK);
return;
});
res.on('error', function (err) {
cb(err, null);
return;
});
});
req.setTimeout(timeout, function () {
req.abort();
cb(new Error('request to ' + options.hostname + ' timeout'), null);
return;
});
req.on('error', function(err) {
cb(err, null);
return;
});
req.end();
}
/**
* Generate "Google TTS" audio link
*
* @param {String} text
* @param {String} key
* @param {String!} lang default is 'en'
* @param {Number!} speed show = 0.24, default is 1
* @return {String} url
*/
function getSpeechUrl (text, key, lang, speed) {
if (typeof text !== 'string' || text.length === 0) {
throw new TypeError('text should be a string');
}
if (typeof key !== 'string' || key.length === 0) {
throw new TypeError('key should be a string');
}
if (typeof lang !== 'undefined' && (typeof lang !== 'string' || lang.length === 0)) {
throw new TypeError('lang should be a string');
}
if (typeof speed !== 'undefined' && typeof speed !== 'number') {
throw new TypeError('speed should be a number');
}
return 'https://' + host + '/translate_tts' + url.format({
query: {
ie: 'UTF-8',
q: text,
tl: lang || 'en',
total: 1,
idx: 0,
textlen: text.length,
tk: getGoogleApiToken(text, key),
client: 't',
prev: 'input',
ttsspeed: speed || 1
}
});
};
/**
* get "Google TTS" audio URL
*
* @param {Object} options
* {String} options.text
* {String!} options.lang default is 'en'
* {Number!} options.speed show = 0.24, default is 1
* {Number!} options.timeout default is 10000ms
*
* @param {Function} callback(err: Error, url: String)
*/
module.exports = function (options, callback) {
var cb = typeof callback === 'function' ? callback : function () {};
if (typeof options !== 'object') {
cb(new TypeError('options should be an object'), null);
return;
}
getGoogleApiKey(function (err, apiKey) {
if (err) {
cb(err, null);
return;
}
// get API
try {
var url = getSpeechUrl(options.text, apiKey, options.lang, options.speed);
cb(null, url);
} catch (err) {
cb(err, null);
}
}, options.timeout);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment