519 lines
20 KiB
JavaScript
519 lines
20 KiB
JavaScript
'use strict';
|
|
const path = require('path');
|
|
|
|
function Speech2Device(adapter, libs, options) {
|
|
const sayitFuncs = {
|
|
browser: {func: sayItBrowser },
|
|
mp24ftp: {func: sayItMP24ftp },
|
|
mp24: {func: sayItMP24 },
|
|
system: {func: sayItSystem },
|
|
windows: {func: sayItWindows },
|
|
sonos: {func: sayItSonos },
|
|
chromecast: {func: sayItChromecast },
|
|
mpd: {func: sayItMpd },
|
|
googleHome: {func: sayItGoogleHome }
|
|
};
|
|
const MP3FILE = path.normalize(__dirname + '/../' + adapter.namespace + '.say.mp3');
|
|
const that = this;
|
|
|
|
// Function to fill the .tts.mp3 state
|
|
function uploadToStates(text, callback) {
|
|
let fileData;
|
|
if (isPlayFile(text)) {
|
|
text = path.normalize(text);
|
|
try {
|
|
fileData = libs.fs.readFileSync(text);
|
|
} catch (e) {
|
|
callback && callback('Cannot upload file "' + text + '" to state: ' + e.toString());
|
|
return;
|
|
}
|
|
} else {
|
|
try {
|
|
fileData = libs.fs.readFileSync(MP3FILE);
|
|
} catch (e) {
|
|
callback && callback('Cannot upload file "' + MP3FILE + '" to state: ' + e.toString());
|
|
return;
|
|
}
|
|
}
|
|
adapter.setBinaryState(adapter.namespace + '.tts.mp3', fileData, callback);
|
|
}
|
|
|
|
// Google home
|
|
let Client;
|
|
let DefaultMediaReceiver;
|
|
|
|
function isPlayFile(text) {
|
|
if (text.length > 4) {
|
|
const ext = text.substring(text.length - 4).toLowerCase();
|
|
if (ext === '.mp3' || ext === '.wav') {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// If text is gong.mp3 or bong.wav
|
|
function sayItGetFileName(text) {
|
|
if (isPlayFile(text)) {
|
|
if (libs.fs.existsSync(text)) {
|
|
return text;
|
|
} else {
|
|
return __dirname + '/' + text;
|
|
}
|
|
}
|
|
|
|
return MP3FILE;
|
|
}
|
|
|
|
function sayItBrowser(error, text, language, volume, duration, callback) {
|
|
if (error) {
|
|
callback && callback(error, 0);
|
|
return;
|
|
}
|
|
uploadToStates(text, err => {
|
|
if (err) {
|
|
return callback && callback(err);
|
|
}
|
|
adapter.setForeignState('vis.0.control.instance', adapter.config.instance);
|
|
adapter.setForeignState('vis.0.control.data', '/state/' + adapter.namespace + '.tts.mp3');
|
|
adapter.setForeignState('vis.0.control.command', 'playSound');
|
|
callback && callback(null, duration);
|
|
});
|
|
}
|
|
|
|
function sayItSonos(error, text, language, volume, duration, callback) {
|
|
if (error) {
|
|
callback && callback(error, 0);
|
|
return;
|
|
}
|
|
|
|
volume = volume || options.sayLastVolume;
|
|
uploadToStates(text, err => {
|
|
if (err) {
|
|
return callback && callback(err);
|
|
}
|
|
if (volume === 'null') volume = 0;
|
|
|
|
if (adapter.config.device && options.webLink) {
|
|
adapter.log.info('Set "' + adapter.config.device + '.tts: ' + (volume ? (volume + ';') : '') + options.webLink + '/state/' + adapter.namespace + '.tts.mp3');
|
|
adapter.setForeignState(adapter.config.device + '.tts', (volume ? (volume + ';') : '') + options.webLink + '/state/' + adapter.namespace + '.tts.mp3');
|
|
} else if (options.webLink) {
|
|
adapter.log.info('Send to sonos ' + (volume ? (volume + ';') : '') + options.webLink + '/state/' + adapter.namespace + '.tts.mp3');
|
|
adapter.sendTo('sonos', 'send', (volume ? (volume + ';') : '') + options.webLink + '/state/' + adapter.namespace + '.tts.mp3');
|
|
} else {
|
|
adapter.log.warn('Web server is unavailable!');
|
|
}
|
|
callback && callback(null, duration);
|
|
});
|
|
}
|
|
|
|
function sayItMpd(error, text, language, volume, duration, callback) {
|
|
if (error) {
|
|
callback && callback(error, 0);
|
|
return;
|
|
}
|
|
|
|
volume = volume || options.sayLastVolume;
|
|
uploadToStates(text, err => {
|
|
if (err) {
|
|
return callback && callback(err);
|
|
}
|
|
if (volume === 'null') volume = 0;
|
|
|
|
if (adapter.config.mpd_device && options.webLink) {
|
|
adapter.log.info('Set "' + adapter.config.mpd_device + '.say: ' + (volume ? (volume + ';') : '') + options.webLink + '/state/' + adapter.namespace + '.tts.mp3');
|
|
adapter.setForeignState(adapter.config.mpd_device + '.say', (volume ? (volume + ';') : '') + options.webLink + '/state/' + adapter.namespace + '.tts.mp3');
|
|
} else if (options.webLink) {
|
|
adapter.log.info('Send to MPD ' + (volume ? (volume + ';') : '') + options.webLink + '/state/' + adapter.namespace + '.tts.mp3');
|
|
adapter.sendTo('mpd', 'say', (volume ? (volume + ';') : '') + options.webLink + '/state/' + adapter.namespace + '.tts.mp3');
|
|
} else {
|
|
adapter.log.warn('Web server is unavailable!');
|
|
}
|
|
callback && callback(null, duration);
|
|
});
|
|
}
|
|
|
|
function sayItChromecast(error, text, language, volume, duration, callback) {
|
|
if (error) {
|
|
callback && callback(error, 0);
|
|
return;
|
|
}
|
|
|
|
volume = volume || options.sayLastVolume;
|
|
uploadToStates(text, err => {
|
|
if (err) {
|
|
return callback && callback(err);
|
|
}
|
|
if (volume === 'null') volume = 0;
|
|
|
|
//Create announcement JSON
|
|
const announcement = {
|
|
url: options.webLink + '/state/' + adapter.namespace + '.tts.mp3'
|
|
};
|
|
if (volume) {
|
|
announcement.volume = volume;
|
|
}
|
|
const announcementJSON = JSON.stringify(announcement);
|
|
|
|
if (adapter.config.cDevice && options.webLink) {
|
|
const chromecastAnouncementDev = adapter.config.cDevice + '.player.announcement';
|
|
adapter.log.info('Set "' + chromecastAnouncementDev + ' to ' + announcementJSON);
|
|
adapter.setForeignState(chromecastAnouncementDev, announcementJSON, () => {
|
|
//Check every 500 ms if the announcement has finished playing
|
|
let intervalHandler = setInterval(() => {
|
|
adapter.getForeignState(chromecastAnouncementDev, (err, state) => {
|
|
if (!err && state.ack) {
|
|
adapter.log.debug(chromecastAnouncementDev + ' finished playing announcement: ' + announcementJSON);
|
|
clearInterval(intervalHandler);
|
|
intervalHandler = null;
|
|
callback();
|
|
}
|
|
});
|
|
}, 500);
|
|
});
|
|
} else {
|
|
adapter.log.warn('Web server is unavailable!');
|
|
}
|
|
callback && callback(null, duration);
|
|
});
|
|
}
|
|
|
|
function launchGoogleHome(client, url, callback) {
|
|
client.launch(DefaultMediaReceiver, (err, player) => {
|
|
const media = {
|
|
contentId: url,
|
|
contentType: 'audio/mp3',
|
|
streamType: 'BUFFERED' // or LIVE
|
|
};
|
|
player.load(media, {autoplay: true}, (err /*, status */) => {
|
|
if (err) {
|
|
adapter.log.error(err);
|
|
}
|
|
client.close();
|
|
callback(err);
|
|
});
|
|
});
|
|
}
|
|
|
|
function sendToGoogleHome(host, url, volume, callback) {
|
|
const client = new Client();
|
|
client.connect(host, () => {
|
|
if (volume !== undefined) {
|
|
client.setVolume({ level: volume / 100 }, (err /* , newvol */) => {
|
|
if (err) adapter.log.error('there was an error setting the volume: ' + err);
|
|
launchGoogleHome(client, url, callback);
|
|
});
|
|
} else {
|
|
launchGoogleHome(client, url, callback);
|
|
}
|
|
});
|
|
|
|
client.on('error', err => {
|
|
client.close();
|
|
callback && callback(err);
|
|
});
|
|
}
|
|
|
|
function sayItGoogleHome(error, text, language, volume, duration, callback) {
|
|
if (error) {
|
|
callback && callback(error, 0);
|
|
return;
|
|
}
|
|
|
|
Client = Client || require('castv2-client').Client;
|
|
DefaultMediaReceiver = DefaultMediaReceiver || require('castv2-client').DefaultMediaReceiver;
|
|
|
|
volume = volume || options.sayLastVolume;
|
|
uploadToStates(text, err => {
|
|
if (err) {
|
|
return callback && callback(err);
|
|
}
|
|
if (volume === 'null' || volume === null) volume = 0;
|
|
|
|
if (adapter.config.server && options.webLink) {
|
|
const url = options.webLink + '/state/' + adapter.namespace + '.tts.mp3';
|
|
adapter.log.debug('Send to google home "' + adapter.config.server + '": ' + url);
|
|
|
|
sendToGoogleHome(adapter.config.server, url, volume, err => err && adapter.log.error(err));
|
|
} else {
|
|
error = 'Web server is unavailable!';
|
|
}
|
|
callback && callback(error, duration);
|
|
});
|
|
}
|
|
|
|
function sayItMP24(error, text, language, volume, duration, callback) {
|
|
if (error) {
|
|
callback && callback(error, 0);
|
|
return;
|
|
}
|
|
|
|
uploadToStates(text, err => {
|
|
if (err) {
|
|
return callback && callback(err);
|
|
}
|
|
if (adapter.config.server && !isPlayFile(text)) {
|
|
adapter.log.debug('Request MediaPlayer24 "http://' + adapter.config.server + ':50000/tts=' + encodeURI(text) + '"');
|
|
const opts = {
|
|
host: adapter.config.server,
|
|
port: 50000,
|
|
path: '/tts=' + encodeURI(text)
|
|
};
|
|
libs.http.get(opts, res => {
|
|
let body = '';
|
|
res.on('data', chunk => body += chunk);
|
|
// all data has been downloaded
|
|
res.on('end', () => adapter.log.debug('Response from MediaPlayer24 "' + adapter.config.server + '": ' + body));
|
|
res.on('error', e => adapter.log.error('Cannot say text on MediaPlayer24 "' + adapter.config.server + '": ' + e.message));
|
|
}).on('error', e => {
|
|
if (e.message === 'Parse Error') {
|
|
adapter.log.debug('Played successfully');
|
|
} else {
|
|
error = 'Cannot say text on MediaPlayer24 "' + adapter.config.server + '":' + e.message;
|
|
}
|
|
});
|
|
}
|
|
|
|
callback && callback(error, duration + 2);
|
|
});
|
|
}
|
|
|
|
function sayItMP24ftp(error, text, language, volume, duration, callback) {
|
|
if (error) {
|
|
callback && callback(error, 0);
|
|
return;
|
|
}
|
|
|
|
uploadToStates(text, err => {
|
|
if (err) {
|
|
return callback && callback(err);
|
|
}
|
|
// Copy mp3 file to android device to play it later with MediaPlayer
|
|
if (adapter.config.port && adapter.config.server) {
|
|
|
|
const file = sayItGetFileName(text);
|
|
libs.jsftp = libs.jsftp || require('jsftp');
|
|
|
|
const ftp = new libs.jsftp({
|
|
host: adapter.config.server,
|
|
port: parseInt(adapter.config.port, 10), // defaults to 21
|
|
user: adapter.config.user || 'anonymous', // defaults to 'anonymous'
|
|
pass: adapter.config.pass || 'anonymous' // defaults to 'anonymous'
|
|
});
|
|
|
|
try {
|
|
// Copy file to FTP server
|
|
ftp.put(file, adapter.namespace + '.say.mp3', hadError => {
|
|
if (!hadError) {
|
|
const opts = {
|
|
host: adapter.config.server,
|
|
port: 50000,
|
|
path: '/track=' + adapter.namespace + '.say.mp3'
|
|
};
|
|
|
|
libs.http.get(opts, res => {
|
|
let body = '';
|
|
res.on('data', chunk =>body += chunk);
|
|
// all data has been downloaded
|
|
res.on('end', () => adapter.log.debug('Response from MediaPlayer24 "' + adapter.config.server + '": ' + body));
|
|
res.on('error', e => adapter.log.error('Cannot say text on MediaPlayer24 "' + adapter.config.server + '":' + e.message));
|
|
}).on('error', e => {
|
|
if (e.message === 'Parse Error') {
|
|
adapter.log.debug('Played successfully');
|
|
} else {
|
|
adapter.log.error('Cannot say text on MediaPlayer24 "' + adapter.config.server + '":' + e.message);
|
|
}
|
|
});
|
|
} else {
|
|
adapter.log.error ('FTP error:' + hadError);
|
|
}
|
|
ftp.raw('quit', (err /* , data */) => {
|
|
if (err) adapter.log.error(err);
|
|
ftp.destroy();
|
|
});
|
|
});
|
|
} catch (e) {
|
|
error = 'Cannot upload file to ' + adapter.config.server + ':' + adapter.config.port;
|
|
}
|
|
}
|
|
callback && callback(error, duration + 2);
|
|
});
|
|
}
|
|
|
|
function sayItSystem(error, text, language, volume, duration, callback) {
|
|
if (error) {
|
|
callback && callback(error, 0);
|
|
return;
|
|
}
|
|
if (!libs.os) libs.os = require('os');
|
|
|
|
uploadToStates(text, err => {
|
|
if (err) {
|
|
return callback && callback(err);
|
|
}
|
|
|
|
const p = libs.os.platform();
|
|
let ls = null;
|
|
const file = sayItGetFileName(text);
|
|
let cmd;
|
|
|
|
that.sayItSystemVolume(volume);
|
|
|
|
if (adapter.config.command) {
|
|
//custom command
|
|
adapter.setState('tts.playing', true);
|
|
if (adapter.config.command.indexOf('%s') !== -1) {
|
|
cmd = adapter.config.command.replace('%s', file);
|
|
} else {
|
|
if (p.match(/^win/)) {
|
|
cmd = adapter.config.command + ' "' + file + '"';
|
|
} else {
|
|
cmd = adapter.config.command + ' ' + file;
|
|
}
|
|
}
|
|
ls = libs.child_process.exec(cmd, (error /* , stdout, stderr */) => {
|
|
if (error) adapter.log.error('Cannot play:' + error);
|
|
adapter.setState('tts.playing', false);
|
|
});
|
|
} else {
|
|
if (p === 'linux') {
|
|
//linux
|
|
adapter.setState('tts.playing', true);
|
|
|
|
if (adapter.config.player === 'omxplayer') {
|
|
cmd = 'omxplayer -o local ' + file;
|
|
} else {
|
|
cmd = 'mpg321 -g ' + options.sayLastVolume + ' ' + file;
|
|
}
|
|
ls = libs.child_process.exec(cmd, (error /* , stdout, stderr */) => {
|
|
if (error) adapter.log.error('Cannot play:' + error);
|
|
adapter.setState('tts.playing', false);
|
|
});
|
|
} else if (p.match(/^win/)) {
|
|
//windows
|
|
adapter.setState('tts.playing', true);
|
|
ls = libs.child_process.exec('cmdmp3.exe "' + file + '"', {cwd: __dirname + '/../cmdmp3/'}, (error /* , stdout, stderr */) => {
|
|
if (error) adapter.log.error('Cannot play:' + error);
|
|
adapter.setState('tts.playing', false);
|
|
});
|
|
} else if (p === 'darwin') {
|
|
//mac osx
|
|
adapter.setState('tts.playing', true);
|
|
ls = libs.child_process.exec('/usr/bin/afplay ' + file, (error /* , stdout, stderr */) => {
|
|
if (error) adapter.log.error('Cannot play:' + error);
|
|
adapter.setState('tts.playing', false);
|
|
});
|
|
}
|
|
}
|
|
|
|
if (ls) {
|
|
ls.on('error', e => {
|
|
throw new Error('sayIt.play: there was an error while playing the mp3 file:' + e);
|
|
});
|
|
}
|
|
if (text === adapter.config.announce) {
|
|
callback && callback(null, duration);
|
|
} else {
|
|
callback && callback(null, duration + 2);
|
|
}
|
|
});
|
|
}
|
|
|
|
function sayItWindows(error, text, language, volume, duration, callback) {
|
|
if (error) {
|
|
callback && callback(error, 0);
|
|
return;
|
|
}
|
|
|
|
// If mp3 file
|
|
if (isPlayFile(text)) {
|
|
sayItSystem(text, language, volume);
|
|
return;
|
|
}
|
|
|
|
if (!libs.os) libs.os = require('os');
|
|
|
|
// Call windows own text 2 speech
|
|
const p = libs.os.platform();
|
|
let ls = null;
|
|
// var file = sayItGetFileName(text);
|
|
|
|
if (volume || volume === 0) that.sayItSystemVolume(volume);
|
|
|
|
if (p.match(/^win/)) {
|
|
//windows
|
|
adapter.setState('tts.playing', true);
|
|
ls = libs.child_process.exec(__dirname + '/../say/SayStatic.exe ' + text, (error /* , stdout, stderr */) => {
|
|
if (error) adapter.log.error ('sayItWindows: ' + error);
|
|
adapter.setState('tts.playing', false);
|
|
});
|
|
} else {
|
|
adapter.log.error ('sayItWindows: only windows OS is supported for Windows default mode');
|
|
}
|
|
|
|
if (ls) {
|
|
ls.on('error', e => {
|
|
throw new Error('sayIt.play: there was an error while text2speech on window:' + e);
|
|
});
|
|
}
|
|
callback && callback(null, duration + 2);
|
|
}
|
|
|
|
this.sayItSystemVolume = function (level) {
|
|
if ((!level && level !== 0) || level === 'null') return;
|
|
|
|
level = parseInt(level);
|
|
if (level < 0) level = 0;
|
|
if (level > 100) level = 100;
|
|
|
|
if (level === options.sayLastVolume) return;
|
|
if (!libs.os) libs.os = require('os');
|
|
|
|
adapter.setState('tts.volume', level, true);
|
|
|
|
options.sayLastVolume = level;
|
|
|
|
const p = libs.os.platform();
|
|
let ls = null;
|
|
|
|
if (p === 'linux' && adapter.config.player !== 'mpg321') {
|
|
//linux
|
|
try {
|
|
ls = libs.child_process.spawn('amixer', ['cset', 'name="Master Playback Volume"', '--', level + '%']);
|
|
} catch (err) {
|
|
adapter.log.error('amixer is not available, so you may hear no audio. Install manually!');
|
|
ls = null;
|
|
}
|
|
} else if (p.match(/^win/)) {
|
|
//windows
|
|
// windows volume is from 0 to 65535
|
|
level = Math.round((65535 * level) / 100); // because this level is from 0 to 100
|
|
ls = libs.child_process.spawn(__dirname + '/../nircmd/nircmdc.exe', ['setsysvolume', level]);
|
|
} else if (p === 'darwin') {
|
|
//mac osx
|
|
ls = libs.child_process.spawn('sudo', ['osascript', '-e', '"set Volume ' + Math.round(level / 10) + '"']);
|
|
}
|
|
|
|
if (ls) {
|
|
ls.on('error', e => {
|
|
adapter.log.error('sayIt.play: there was an error while playing the mp3 file:' + e);
|
|
});
|
|
}
|
|
};
|
|
|
|
this.getFunction = function (type) {
|
|
if (sayitFuncs[type]) {
|
|
return sayitFuncs[type].func;
|
|
} else {
|
|
adapter.log.error('No processor for "' + type + '"');
|
|
return null;
|
|
}
|
|
};
|
|
|
|
this.sayItIsPlayFile = isPlayFile;
|
|
|
|
return this;
|
|
}
|
|
|
|
module.exports = Speech2Device;
|