Initial commit

This commit is contained in:
2018-11-11 13:26:55 +08:00
commit d68e4e97a6
42 changed files with 7060 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
node_modules
.idea
tmp
admin/i18n/*/flat.txt
admin/i18n/flat.txt
iob_npm.done
package-lock.json
/.project

12
.npmignore Normal file
View File

@@ -0,0 +1,12 @@
Gruntfile.js
gulpfile.js
test
tmp
docs
.idea
.travis.yml
.git
appveyor.yml
admin/i18n
iob_npm.done
package-lock.json

23
.travis.yml Normal file
View File

@@ -0,0 +1,23 @@
os:
- linux
- osx
language: node_js
node_js:
- '4'
- '6'
- '8'
- '10'
before_script:
- export NPMVERSION=$(echo "$($(which npm) -v)"|cut -c1)
- 'if [[ $NPMVERSION == 5 ]]; then npm install -g npm@5; fi'
- npm -v
- npm install winston@2.3.1
- 'npm install https://git.spacen.net/yunkong2/yunkong2.js-controller/tarball/master --production'
env:
- CXX=g++-4.8
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8

22
LICENSE Normal file
View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015-2017 bluefox <dogafox@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

408
README.md Normal file
View File

@@ -0,0 +1,408 @@
![Logo](admin/sayit.png)
yunkong2 sayit adapter
=================
[![NPM version](http://img.shields.io/npm/v/yunkong2.sayit.svg)](https://www.npmjs.com/package/yunkong2.sayit)
[![Downloads](https://img.shields.io/npm/dm/yunkong2.sayit.svg)](https://www.npmjs.com/package/yunkong2.sayit)
[![NPM](https://nodei.co/npm/yunkong2.sayit.png?downloads=true)](https://nodei.co/npm/yunkong2.sayit/)
SayIt Adapter can convert text to speech and play it on some device.
## Configuration
Actual following outputs are supported:
- *Browser* - the text will be played by browser with opened yunkong2.vis page. It is supported almost by every desktop browser and by few mobily Browsers.
- *[Home24- MediaPlayer](http://www.home-24.net/index.php?app=media)* - the text will be sent and played to the Android device with Home24 - MediaPlayer installed. For this will be used build in Android text to speech engine. The port cannot be changed and set to 50000.
- *Home24 - MediaPlayer and [FTP Server](https://play.google.com/store/apps/details?id=lutey.FTPServer)* - the text will be sent and played on the Android device with Home24 - MediaPlayer installed. For this will be used the Google text to speech engine. Generated mp3 file will be copied over FTP to android device and played with Home24 - MediaPlayer.
Both apps have to have same home directories. (E.g. root directory of \"sd card\").
- *System* - the text will be played by OS, where the yunkong2 adapter runs. Following OS are supported: Windows, linux, Mac OSx.
- *Windows engine* - the text will be played by windows, where the sayIt adapter runs. For this will be used windows text to speech engine, that should be preconfigured by user. You can check [here](http://windows.microsoft.com/en-us/windows/setting-speech-options#1TC=windows-7) how to setup it.
- *Sonos* - play text on sonos device. Be sure the Web Adapter is enabled. It is required to enable SONOS to read the generated mp3 files.
- *Chromecast* - play text on Chromecast device.
- *MPD* - play text on Music Player Daemon. Use only **http** for web adapter, don't use https.
To enable the text to speech on RaspberryPI or linux system call one time following command ```sudo apt-get -y install mpg321``` to install mpg321.
The mp3/wav files can be played to by writing its name into the object. (e.g. "/vis.0/main/img/door-bell.mp3")
The file must be first loaded.
### TTS engines
online:
- Google: English, German, Russian, Italian, Spanish, French
- Yandex: Russian
To use Yandex voices you must request the API key here: [https://tech.yandex.ru/speechkit/cloud/doc/dg/concepts/About-docpage/](https://tech.yandex.ru/speechkit/cloud/doc/dg/concepts/About-docpage/).
- Ivona: English, German, Russian, Italian, Spanish, French, Dansk, Welsh, Icelandic, Dutch, Polish, Portuguese, Romanian, Swedish, Turkish
To use Amazon(Ivona) voices you need to get access key and secret key [here](http://www.ivona.com/us/for-business/speech-cloud/).
- Cloud:
To use Cloud voices you need configured cloud adapter. (It can be disabled, but must be configured). This service use AWS Polly and it can be used directly.
- Amazon Web Services Polly:
To use AWS Polly voices you need to create access key and secret key [here](https://console.aws.amazon.com/iam/home). The Amazon documentation can you find [here](http://docs.aws.amazon.com/general/latest/gr/managing-aws-access-keys.html).
offline:
- PicoTTS (linux only): English, German, Italian, Spanish, French
For PicoTTS it is necessary to install the following packages: libttspico-utils and lame.
Installation command: 'sudo apt-get install libttspico-utils lame'
### Cloud and Amazon Web Services Polly text formatting
You can format your text with [Speech Synthesis Markup Language](http://docs.aws.amazon.com/polly/latest/dg/ssml.html).
Most useful features:
- ```<break time="3s"/>```- make a pause for x seconds (max 10 seconds).
- ```<emphasis> big </emphasis>``` - make an emphasis on some word.
- ```<prosody volume="+6dB" rate="90%">I am speaking this</prosody>``` - control speed and volume parameters.
- ```<say-as interpret-as="digits">12345</say-as>``` - say every digit separately.
More [info](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/speech-synthesis-markup-language-ssml-reference).
### System command
If you have some program, that can play audio files locally or somewhere else, you can write this command here. E.g.
```myCustomPlayer --option```
If **System** output is selected, the sayit adapter will execute following command on local system:
```myCustomPlayer --option /opt/yunkong2/node_modules/yunkong2.sayit/say.mp3```
If file name must stay somewhere in the middle you can use *%s* to specify where the file name must be placed:
```myCustomPlayer --option "%s" > /dev/null```
sayIt will make ```myCustomPlayer --option "/opt/yunkong2/node_modules/yunkong2.sayit/say.mp3" > /dev/null``` from it.
## Usage
SayIt adapter cannot be used alone. It must be controlled from javascript adapter or from "vis" with specific widget.
After creation of adapter instance will can find following objects:
- sayit.N.tts.text: Phrase to be spoken.
- sayit.N.tts.volume: volume which will be used by playing of the phrase.
- sayit.N.tts.playing: true if text is now playing and false if not. Supported only for "windows" and "system" play mode.
- sayit.N.tts.cachetext: Phrase to be cached and then it can be used without internet.
E.g. you can enter here manually "No internet" and if ping to google.com is negative, write "No internet" to "tts.text" and it will pronounced. Of course cache must be enabled.
State **tts.text** supports extended syntax, so the langugage/engine and volume can be defined together with text. It is used to enable multi-language text2speech engines.
E.g. if adapter has engine "Google-english", it is possible with phrase ```de:Sag es``` to force to use Google-Deustch speech engine.
With ```ru;75;Погода хорошая``` we can force to use russian language and volume 75%.
You can specify the volume of announcement in percent from current or given volume (not from maximal). E.g. if command is ```de;75;Gutes Wetter```and "announce volume" is 50%, the announce will be played with volume 38% from 100% possible.
The system command to play the mp3 file can be specified too. If you leave it blank, the default settings will be used: windows - cmdmp3.exe, OSX - /usr/bin/afplay, linux - mpg321 or omxplayer (recommended).
To install omxplayer write ```sudo apt-get install omxplayer``` or write ```sudo apt-get install mpg321``` to install mpg321.
**Note:** The default announce selection will be possible only after start of the instance.
### Engines
Following values for engines are possible:
#### Google
- **en** - English
- **de** - Deutsch
- **ru** - Русский
- **it** - Italiano
- **es** - Espaniol
- **fr** - Français
#### Yandex
- **ru_YA:Yandex** - Русский
#### Amazon polly via cloud
- **ru-RU_CLOUD_Female** - Русский - Татьяна
- **ru-RU_CLOUD_Male** - Русский - Максим
- **de-DE_CLOUD_Female** - Deutsch - Marlene
- **de-DE_CLOUD_Male** - Deutsch - Hans
- **en-US_CLOUD_Female** - en-US - Female - Salli
- **en-US_CLOUD_Male** - en-US - Male - Joey
- **da-DK_CLOUD_Female** - da-DK - Female - Naja
- **da-DK_CLOUD_Male** - da-DK - Male - Mads
- **en-AU_CLOUD_Female** - en-AU - Female - Nicole
- **en-AU_CLOUD_Male** - en-AU - Male - Russell
- **en-GB_CLOUD_Female_Amy** - en-GB - Female - Amy
- **en-GB_CLOUD_Male** - en-GB - Male - Brian
- **en-GB_CLOUD_Female_Emma** - en-GB - Female - Emma
- **en-GB-WLS_CLOUD_Female** - en-GB-WLS - Female - Gwyneth
- **en-GB-WLS_CLOUD_Male** - en-GB-WLS - Male - Geraint
- **cy-GB_CLOUD_Female** - cy-GB - Female - Gwyneth
- **cy-GB_CLOUD_Male** - cy-GB - Male - Geraint
- **en-IN_CLOUD_Female** - en-IN - Female - Raveena
- **en-US_CLOUD_Male_Chipmunk** - en-US - Male - Chipmunk
- **en-US_CLOUD_Male_Eric** - en-US - Male - Eric
- **en-US_CLOUD_Female_Ivy** - en-US - Female - Ivy
- **en-US_CLOUD_Female_Jennifer** - en-US - Female - Jennifer
- **en-US_CLOUD_Male_Justin** - en-US - Male - Justin
- **en-US_CLOUD_Female_Kendra** - en-US - Female - Kendra
- **en-US_CLOUD_Female_Kimberly** - en-US - Female - Kimberly
- **es-ES_CLOUD_Female** - es-ES - Female - Conchita
- **es-ES_CLOUD_Male** - es-ES - Male - Enrique
- **es-US_CLOUD_Female** - es-US - Female - Penelope
- **es-US_CLOUD_Male** - es-US - Male - Miguel
- **fr-CA_CLOUD_Female** - fr-CA - Female - Chantal
- **fr-FR_CLOUD_Female** - fr-FR - Female - Celine
- **fr-FR_CLOUD_Male** - fr-FR - Male - Mathieu
- **is-IS_CLOUD_Female** - is-IS - Female - Dora
- **is-IS_CLOUD_Male** - is-IS - Male - Karl
- **it-IT_CLOUD_Female** - it-IT - Female - Carla
- **it-IT_CLOUD_Male** - it-IT - Male - Giorgio
- **nb-NO_CLOUD_Female** - nb-NO - Female - Liv
- **nl-NL_CLOUD_Female** - nl-NL - Female - Lotte
- **nl-NL_CLOUD_Male** - nl-NL - Male - Ruben
- **pl-PL_CLOUD_Female_Agnieszka** - pl-PL - Female - Agnieszka
- **pl-PL_CLOUD_Male_Jacek** - pl-PL - Male - Jacek
- **pl-PL_CLOUD_Female_Ewa** - pl-PL - Female - Ewa
- **pl-PL_CLOUD_Male_Jan** - pl-PL - Male - Jan
- **pl-PL_CLOUD_Female** - pl-PL - Female - Maja
- **pt-BR_CLOUD_Female** - pt-BR - Female - Vitoria
- **pt-BR_CLOUD_Male** - pt-BR - Male - Ricardo
- **pt-PT_CLOUD_Male** - pt-PT - Male - Cristiano
- **pt-PT_CLOUD_Female** - pt-PT - Female - Ines
- **ro-RO_CLOUD_Female** - ro-RO - Female - Carmen
- **sv-SE_CLOUD_Female** - sv-SE - Female - Astrid
- **tr-TR_CLOUD_Female** - tr-TR - Female - Filiz
#### Pico TTS
- **en-US** - Englisch US
- **en-GB** - Englisch GB
- **de-DE** - Deutsch
- **it-IT** - Italiano
- **es-ES** - Espaniol
- **fr-FR** - Français
#### Amazon polly direct
- **(ru-RU_AP_Female)** - Русский - Татьяна
- **(ru-RU_AP_Male)** - Русский - Максим
- **(de-DE_AP_Female)** - Deutsch - Marlene
- **(de-DE_AP_Male)** - Deutsch - Hans
- **(en-US_AP_Female)** - en-US - Female - Salli
- **(en-US_AP_Male)** - en-US - Male - Joey
- **(da-DK_AP_Female)** - da-DK - Female - Naja
- **(da-DK_AP_Male)** - da-DK - Male - Mads
- **(en-AU_AP_Female)** - en-AU - Female - Nicole
- **(en-AU_AP_Male)** - en-AU - Male - Russell
- **(en-GB_AP_Female_Amy)** - en-GB - Female - Amy
- **(en-GB_AP_Male)** - en-GB - Male - Brian
- **(en-GB_AP_Female_Emma)** - en-GB - Female - Emma
- **(en-GB-WLS_AP_Female)** - en-GB-WLS - Female - Gwyneth
- **(en-GB-WLS_AP_Male)** - en-GB-WLS - Male - Geraint
- **(cy-GB_AP_Female)** - cy-GB - Female - Gwyneth
- **(cy-GB_AP_Male)** - cy-GB - Male - Geraint
- **(en-IN_AP_Female)** - en-IN - Female - Raveena
- **(en-US_AP_Male_Chipmunk)** - en-US - Male - Chipmunk
- **(en-US_AP_Male_Eric)** - en-US - Male - Eric
- **(en-US_AP_Female_Ivy)** - en-US - Female - Ivy
- **(en-US_AP_Female_Jennifer)** - en-US - Female - Jennifer
- **(en-US_AP_Male_Justin)** - en-US - Male - Justin
- **(en-US_AP_Female_Kendra)** - en-US - Female - Kendra
- **(en-US_AP_Female_Kimberly)** - en-US - Female - Kimberly
- **(es-ES_AP_Female)** - es-ES - Female - Conchita
- **(es-ES_AP_Male)** - es-ES - Male - Enrique
- **(es-US_AP_Female)** - es-US - Female - Penelope
- **(es-US_AP_Male)** - es-US - Male - Miguel
- **(fr-CA_AP_Female)** - fr-CA - Female - Chantal
- **(fr-FR_AP_Female)** - fr-FR - Female - Celine
- **(fr-FR_AP_Male)** - fr-FR - Male - Mathieu
- **(is-IS_AP_Female)** - is-IS - Female - Dora
- **(is-IS_AP_Male)** - is-IS - Male - Karl
- **(it-IT_AP_Female)** - it-IT - Female - Carla
- **(it-IT_AP_Male)** - it-IT - Male - Giorgio
- **(nb-NO_AP_Female)** - nb-NO - Female - Liv
- **(nl-NL_AP_Female)** - nl-NL - Female - Lotte
- **(nl-NL_AP_Male)** - nl-NL - Male - Ruben
- **(pl-PL_AP_Female_Agnieszka)** - pl-PL - Female - Agnieszka
- **(pl-PL_AP_Male_Jacek)** - pl-PL - Male - Jacek
- **(pl-PL_AP_Female_Ewa)** - pl-PL - Female - Ewa
- **(pl-PL_AP_Male_Jan)** - pl-PL - Male - Jan
- **(pl-PL_AP_Female)** - pl-PL - Female - Maja
- **(pt-BR_AP_Female)** - pt-BR - Female - Vitoria
- **(pt-BR_AP_Male)** - pt-BR - Male - Ricardo
- **(pt-PT_AP_Male)** - pt-PT - Male - Cristiano
- **(pt-PT_AP_Female)** - pt-PT - Female - Ines
- **(ro-RO_AP_Female)** - ro-RO - Female - Carmen
- **(sv-SE_AP_Female)** - sv-SE - Female - Astrid
- **(tr-TR_AP_Female)** - tr-TR - Female - Filiz
## Changelog
### 1.7.1 (2018-09-19)
* (BuZZy1337) fixed error in Blockly-Block
### 1.7.0 (2018-06-08)
* (bluefox) Ivona removed
* (bluefox) Error was fixed by upload of file to FTP
* (bluefox) admin3
### 1.6.8 (2018-04-11)
* (BuZZy1337) Generate separate mp3 files for each instance.
* Fixes [Issue#34](https://git.spacen.net/yunkong2/yunkong2.sayit/issues/34)
* (BuZZy1337) Always upload mp3 files to the state sayit.X.tts.mp3
### 1.6.7 (2018-02-05)
* (Apollon77) Remove unneeded logging
* (bondrogeen) Admin3 Fixes
### 1.6.6 (2017-11-27)
* (angelnu) Wait for Google Home announcement to complete
### 1.6.5 (2017-11-04)
* (bluefox) Fix cloud .pro
### 1.6.4 (2017-10-18)
* (bluefox) Fix system commands
### 1.6.3 (2017-10-04)
* (bluefox) Code refactoring
* (bluefox) Add google home as output
* (bluefox) Remove ivona because not more supported
### 1.5.2 (2017-03-09)
* (bluefox) Catch error if some directory in mp3 folder
### 1.5.1 (2017-02-15)
* (bluefox) Fix blockly language
### 1.5.0 (2017-01-27)
* (DarkChaos) Add AWS Polly as source
* (bluefox) Add cloud as source
### 1.4.0 (2017-01-16)
* (bluefox) fix install problem
* (bluefox) add PicoTTS as source
### 1.3.3 (2017-01-13)
* (bluefox) show only installed instances in blockly
### 1.3.2 (2017-01-10)
* (angelnu) changes for new chromecast tts
### 1.3.1 (2016-12-27)
* (bluefox) small fix of config dialog
* (AirKing555) Fix Volume change
### 1.3.0 (2016-12-20)
* (instalator) add mpd
### 1.2.1 (2016-10-31)
* (bluefox) Fix cache
### 1.2.0 (2016-10-28)
* (bluefox) Finish sayit
### 1.1.3 (2016-10-24)
* (bluefox) Fix changing of engine
### 1.1.2 (2016-10-20)
* (bluefox) Add omxplayer option
### 1.0.1 (2016-10-12)
* (bluefox) support of blockly
### 1.0.0 (2016-05-14)
* (bluefox) Make the type of mp3 as file
### 0.3.16 (2015-12-26)
* (Vegetto) Support for Chromecast devices
### 0.3.16 (2015-12-26)
* (bluefox) enable play of mp3 files from disk
### 0.3.15 (2015-11-10)
* (bluefox) fill default settings by first start
### 0.3.14 (2015-11-01)
* (bluefox) fix error with sayItWindows
### 0.3.13 (2015-10-27)
* (bluefox) fix error with sayItSystem
### 0.3.12 (2015-10-06)
* (bluefox) fix error if received mp3 file is too short
* (bluefox) try to implement cache datapoint (you can use sayit.0.tts.cachetext to create cache for phrases and use sayit without internet)
### 0.3.11 (2015-08-03)
* (bluefox) change google requests from http to https
### 0.3.10 (2015-07-26)
* (bluefox) add new voice Russian-Maxim
* (bluefox) fix error with mp24ftp
### 0.3.9 (2015-07-09)
* (bluefox) fix error by mediaplayer24
### 0.3.8 (2015-06-09)
* (bluefox) make the volume for announce configurable
* (bluefox) make the command for "system" configurable
### 0.3.7 (2015-05-28)
* (bluefox) fix volume for announce
* (bluefox) support of play files from internal filesystem, like "/sayit.0/tts.userfiles/myGong.mp3"
### 0.3.6 (2015-03-24)
* (bluefox) fix error with volume by sonos
### 0.3.5 (2015-03-22)
* (bluefox) fix error in announcement
### 0.3.4 (2015-03-20)
* (bluefox) fix error in announcement
### 0.3.3 (2015-03-20)
* (bluefox) enable announcement
### 0.3.2 (2015-03-16)
* (bluefox) clear cache if engine changed
### 0.3.1 (2015-03-15)
* (bluefox) fix small error with log
### 0.3.0 (2015-03-08)
* (bluefox) add ivona/Amazon voices
### 0.2.2 (2015-03-08)
* (bluefox) fix error by buffering of non-generated texts.
### 0.2.1 (2015-03-07)
* (bluefox) fix error by buffering of non-generated texts.
### 0.2.0 (2015-03-02)
* (bluefox) add yandex-russian support
### 0.1.0 (2015-03-02)
* (bluefox) queue texts
### 0.0.1 (2015-02-06)
* (bluefox) initial commit
## License
The MIT License (MIT)
Copyright (c) 2014-2017, bluefox<dogafox@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

242
admin/blockly.js Normal file
View File

@@ -0,0 +1,242 @@
'use strict';
goog.provide('Blockly.JavaScript.Sendto');
goog.require('Blockly.JavaScript');
// --- SayIt --------------------------------------------------
Blockly.Words['sayit'] = {'en': 'say text', 'de': 'aussprechen', 'ru': 'произнести'};
Blockly.Words['sayit_message'] = {'en': 'message', 'de': 'Meldung', 'ru': 'сообщение'};
Blockly.Words['sayit_volume'] = {'en': 'volume (optional)', 'de': 'Lautstärke (optional)', 'ru': 'громкость (не обяз.)'};
Blockly.Words['sayit_tooltip'] = {'en': 'Text to speech', 'de': 'Text zu Sprache', 'ru': 'Произнести сообщение'};
Blockly.Words['sayit_help'] = {'en': 'https://git.spacen.net/yunkong2/yunkong2.sayit/blob/master/README.md', 'de': 'http://www.yunkong2.net/?page_id=178&lang=de', 'ru': 'http://www.yunkong2.net/?page_id=4262&lang=ru'};
Blockly.Words['sayit_configured'] = {'en': 'configured', 'de': 'standart', 'ru': 'настроенный'};
Blockly.Words['sayit_log'] = {'en': 'log level', 'de': 'Loglevel', 'ru': 'Протокол'};
Blockly.Words['sayit_log_none'] = {'en': 'none', 'de': 'keins', 'ru': 'нет'};
Blockly.Words['sayit_log_info'] = {'en': 'info', 'de': 'info', 'ru': 'инфо'};
Blockly.Words['sayit_log_debug'] = {'en': 'debug', 'de': 'debug', 'ru': 'debug'};
Blockly.Words['sayit_log_warn'] = {'en': 'warning', 'de': 'warning', 'ru': 'warning'};
Blockly.Words['sayit_log_error'] = {'en': 'error', 'de': 'error', 'ru': 'ошибка'};
// this is copy of engines.js
var sayitEngines = {
"en": {name: "Google - English", engine: "google", params: []},
"de": {name: "Google - Deutsch", engine: "google", params: []},
"ru": {name: "Google - Русский", engine: "google", params: []},
"it": {name: "Google - Italiano", engine: "google", params: []},
"es": {name: "Google - Espaniol", engine: "google", params: []},
"fr": {name: "Google - Français", engine: "google", params: []},
"ru_YA": {name: "Yandex - Русский", engine: "yandex", params: ['key', 'voice', 'emotion', 'ill', 'drunk', 'robot'], voice: ['jane', 'zahar'], emotion: ['none', 'good', 'neutral', 'evil', 'mixed']},
"en-US": {name: "PicoTTS - Englisch US", engine: "PicoTTS", params: []},
"en-GB": {name: "PicoTTS - Englisch GB", engine: "PicoTTS", params: []},
"de-DE": {name: "PicoTTS - Deutsch", engine: "PicoTTS", params: []},
"it-IT": {name: "PicoTTS - Italiano", engine: "PicoTTS", params: []},
"es-ES": {name: "PicoTTS - Espaniol", engine: "PicoTTS", params: []},
"fr-FR": {name: "PicoTTS - Français", engine: "PicoTTS", params: []},
"ru-RU_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "ru-RU", ename: "Tatyana", ssml: true, name: "Cloud - Русский - Татьяна"},
"ru-RU_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "ru-RU", ename: "Maxim", ssml: true, name: "Cloud - Русский - Максим"},
"de-DE_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "de-DE", ename: "Marlene", ssml: true, name: "Cloud - Deutsch - Marlene"},
"de-DE_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "de-DE", ename: "Hans", ssml: true, name: "Cloud - Deutsch - Hans"},
"en-US_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "en-US", ename: "Salli", ssml: true, name: "Cloud - en-US - Female - Salli"},
"en-US_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "en-US", ename: "Joey", ssml: true, name: "Cloud - en-US - Male - Joey"},
"da-DK_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "da-DK", ename: "Naja", ssml: true, name: "Cloud - da-DK - Female - Naja"},
"da-DK_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "da-DK", ename: "Mads", ssml: true, name: "Cloud - da-DK - Male - Mads"},
"en-AU_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "en-AU", ename: "Nicole", ssml: true, name: "Cloud - en-AU - Female - Nicole"},
"en-AU_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "en-AU", ename: "Russell", ssml: true, name: "Cloud - en-AU - Male - Russell"},
"en-GB_CLOUD_Female_Amy": {gender: "Female", engine: "cloud", params: ['cloud'], language: "en-GB", ename: "Amy", ssml: true, name: "Cloud - en-GB - Female - Amy"},
"en-GB_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "en-GB", ename: "Brian", ssml: true, name: "Cloud - en-GB - Male - Brian"},
"en-GB_CLOUD_Female_Emma": {gender: "Female", engine: "cloud", params: ['cloud'], language: "en-GB", ename: "Emma", ssml: true, name: "Cloud - en-GB - Female - Emma"},
"en-GB-WLS_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "en-GB-WLS", ename: "Gwyneth", ssml: true, name: "Cloud - en-GB-WLS - Female - Gwyneth"},
"en-GB-WLS_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "en-GB-WLS", ename: "Geraint", ssml: true, name: "Cloud - en-GB-WLS - Male - Geraint"},
"cy-GB_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "cy-GB", ename: "Gwyneth", ssml: true, name: "Cloud - cy-GB - Female - Gwyneth"},
"cy-GB_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "cy-GB", ename: "Geraint", ssml: true, name: "Cloud - cy-GB - Male - Geraint"},
"en-IN_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "en-IN", ename: "Raveena", ssml: true, name: "Cloud - en-IN - Female - Raveena"},
"en-US_CLOUD_Male_Chipmunk":{gender: "Male", engine: "cloud", params: ['cloud'], language: "en-US", ename: "Chipmunk", ssml: true, name: "Cloud - en-US - Male - Chipmunk"},
"en-US_CLOUD_Male_Eric": {gender: "Male", engine: "cloud", params: ['cloud'], language: "en-US", ename: "Eric", ssml: true, name: "Cloud - en-US - Male - Eric"},
"en-US_CLOUD_Female_Ivy": {gender: "Female", engine: "cloud", params: ['cloud'], language: "en-US", ename: "Ivy", ssml: true, name: "Cloud - en-US - Female - Ivy"},
"en-US_CLOUD_Female_Jennifer": {gender: "Female", engine: "cloud", params: ['cloud'], language: "en-US", ename: "Jennifer", ssml: true, name: "Cloud - en-US - Female - Jennifer"},
"en-US_CLOUD_Male_Justin": {gender: "Male", engine: "cloud", params: ['cloud'], language: "en-US", ename: "Justin", ssml: true, name: "Cloud - en-US - Male - Justin"},
"en-US_CLOUD_Female_Kendra": {gender: "Female", engine: "cloud", params: ['cloud'], language: "en-US", ename: "Kendra", ssml: true, name: "Cloud - en-US - Female - Kendra"},
"en-US_CLOUD_Female_Kimberly": {gender: "Female", engine: "cloud", params: ['cloud'], language: "en-US", ename: "Kimberly", ssml: true, name: "Cloud - en-US - Female - Kimberly"},
"es-ES_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "es-ES", ename: "Conchita", ssml: true, name: "Cloud - es-ES - Female - Conchita"},
"es-ES_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "es-ES", ename: "Enrique", ssml: true, name: "Cloud - es-ES - Male - Enrique"},
"es-US_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "es-US", ename: "Penelope", ssml: true, name: "Cloud - es-US - Female - Penelope"},
"es-US_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "es-US", ename: "Miguel", ssml: true, name: "Cloud - es-US - Male - Miguel"},
"fr-CA_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "fr-CA", ename: "Chantal", ssml: true, name: "Cloud - fr-CA - Female - Chantal"},
"fr-FR_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "fr-FR", ename: "Celine", ssml: true, name: "Cloud - fr-FR - Female - Celine"},
"fr-FR_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "fr-FR", ename: "Mathieu", ssml: true, name: "Cloud - fr-FR - Male - Mathieu"},
"is-IS_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "is-IS", ename: "Dora", ssml: true, name: "Cloud - is-IS - Female - Dora"},
"is-IS_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "is-IS", ename: "Karl", ssml: true, name: "Cloud - is-IS - Male - Karl"},
"it-IT_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "it-IT", ename: "Carla", ssml: true, name: "Cloud - it-IT - Female - Carla"},
"it-IT_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "it-IT", ename: "Giorgio", ssml: true, name: "Cloud - it-IT - Male - Giorgio"},
"nb-NO_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "nb-NO", ename: "Liv", ssml: true, name: "Cloud - nb-NO - Female - Liv"},
"nl-NL_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "nl-NL", ename: "Lotte", ssml: true, name: "Cloud - nl-NL - Female - Lotte"},
"nl-NL_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "nl-NL", ename: "Ruben", ssml: true, name: "Cloud - nl-NL - Male - Ruben"},
"pl-PL_CLOUD_Female_Agnieszka":{gender: "Female", engine: "cloud",params: ['cloud'], language: "pl-PL", ename: "Agnieszka", ssml: true, name: "Cloud - pl-PL - Female - Agnieszka"},
"pl-PL_CLOUD_Male_Jacek": {gender: "Male", engine: "cloud", params: ['cloud'], language: "pl-PL", ename: "Jacek", ssml: true, name: "Cloud - pl-PL - Male - Jacek"},
"pl-PL_CLOUD_Female_Ewa": {gender: "Female", engine: "cloud", params: ['cloud'], language: "pl-PL", ename: "Ewa", ssml: true, name: "Cloud - pl-PL - Female - Ewa"},
"pl-PL_CLOUD_Male_Jan": {gender: "Male", engine: "cloud", params: ['cloud'], language: "pl-PL", ename: "Jan", ssml: true, name: "Cloud - pl-PL - Male - Jan"},
"pl-PL_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "pl-PL", ename: "Maja", ssml: true, name: "Cloud - pl-PL - Female - Maja"},
"pt-BR_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "pt-BR", ename: "Vitoria", ssml: true, name: "Cloud - pt-BR - Female - Vitoria"},
"pt-BR_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "pt-BR", ename: "Ricardo", ssml: true, name: "Cloud - pt-BR - Male - Ricardo"},
"pt-PT_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "pt-PT", ename: "Cristiano", ssml: true, name: "Cloud - pt-PT - Male - Cristiano"},
"pt-PT_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "pt-PT", ename: "Ines", ssml: true, name: "Cloud - pt-PT - Female - Ines"},
"ro-RO_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "ro-RO", ename: "Carmen", ssml: true, name: "Cloud - ro-RO - Female - Carmen"},
"sv-SE_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "sv-SE", ename: "Astrid", ssml: true, name: "Cloud - sv-SE - Female - Astrid"},
"tr-TR_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "tr-TR", ename: "Filiz", ssml: true, name: "Cloud - tr-TR - Female - Filiz"},
"ru-RU_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "ru-RU", ename: "Tatyana", ssml: true, name: "AWS Polly - Русский - Татьяна"},
"ru-RU_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "ru-RU", ename: "Maxim", ssml: true, name: "AWS Polly - Русский - Максим"},
"de-DE_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "de-DE", ename: "Marlene", ssml: true, name: "AWS Polly - Deutsch - Marlene"},
"de-DE_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "de-DE", ename: "Hans", ssml: true, name: "AWS Polly - Deutsch - Hans"},
"en-US_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-US", ename: "Salli", ssml: true, name: "AWS Polly - en-US - Female - Salli"},
"en-US_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-US", ename: "Joey", ssml: true, name: "AWS Polly - en-US - Male - Joey"},
"da-DK_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "da-DK", ename: "Naja", ssml: true, name: "AWS Polly - da-DK - Female - Naja"},
"da-DK_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "da-DK", ename: "Mads", ssml: true, name: "AWS Polly - da-DK - Male - Mads"},
"en-AU_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-AU", ename: "Nicole", ssml: true, name: "AWS Polly - en-AU - Female - Nicole"},
"en-AU_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-AU", ename: "Russell", ssml: true, name: "AWS Polly - en-AU - Male - Russell"},
"en-GB_AP_Female_Amy": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-GB", ename: "Amy", ssml: true, name: "AWS Polly - en-GB - Female - Amy"},
"en-GB_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-GB", ename: "Brian", ssml: true, name: "AWS Polly - en-GB - Male - Brian"},
"en-GB_AP_Female_Emma": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-GB", ename: "Emma", ssml: true, name: "AWS Polly - en-GB - Female - Emma"},
"en-GB-WLS_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-GB-WLS", ename: "Gwyneth", ssml: true, name: "AWS Polly - en-GB-WLS - Female - Gwyneth"},
"en-GB-WLS_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-GB-WLS", ename: "Geraint", ssml: true, name: "AWS Polly - en-GB-WLS - Male - Geraint"},
"cy-GB_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "cy-GB", ename: "Gwyneth", ssml: true, name: "AWS Polly - cy-GB - Female - Gwyneth"},
"cy-GB_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "cy-GB", ename: "Geraint", ssml: true, name: "AWS Polly - cy-GB - Male - Geraint"},
"en-IN_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-IN", ename: "Raveena", ssml: true, name: "AWS Polly - en-IN - Female - Raveena"},
"en-US_AP_Male_Chipmunk": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-US", ename: "Chipmunk", ssml: true, name: "AWS Polly - en-US - Male - Chipmunk"},
"en-US_AP_Male_Eric": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-US", ename: "Eric", ssml: true, name: "AWS Polly - en-US - Male - Eric"},
"en-US_AP_Female_Ivy": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-US", ename: "Ivy", ssml: true, name: "AWS Polly - en-US - Female - Ivy"},
"en-US_AP_Female_Jennifer": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-US", ename: "Jennifer", ssml: true, name: "AWS Polly - en-US - Female - Jennifer"},
"en-US_AP_Male_Justin": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-US", ename: "Justin", ssml: true, name: "AWS Polly - en-US - Male - Justin"},
"en-US_AP_Female_Kendra": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-US", ename: "Kendra", ssml: true, name: "AWS Polly - en-US - Female - Kendra"},
"en-US_AP_Female_Kimberly": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-US", ename: "Kimberly", ssml: true, name: "AWS Polly - en-US - Female - Kimberly"},
"es-ES_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "es-ES", ename: "Conchita", ssml: true, name: "AWS Polly - es-ES - Female - Conchita"},
"es-ES_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "es-ES", ename: "Enrique", ssml: true, name: "AWS Polly - es-ES - Male - Enrique"},
"es-US_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "es-US", ename: "Penelope", ssml: true, name: "AWS Polly - es-US - Female - Penelope"},
"es-US_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "es-US", ename: "Miguel", ssml: true, name: "AWS Polly - es-US - Male - Miguel"},
"fr-CA_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "fr-CA", ename: "Chantal", ssml: true, name: "AWS Polly - fr-CA - Female - Chantal"},
"fr-FR_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "fr-FR", ename: "Celine", ssml: true, name: "AWS Polly - fr-FR - Female - Celine"},
"fr-FR_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "fr-FR", ename: "Mathieu", ssml: true, name: "AWS Polly - fr-FR - Male - Mathieu"},
"is-IS_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "is-IS", ename: "Dora", ssml: true, name: "AWS Polly - is-IS - Female - Dora"},
"is-IS_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "is-IS", ename: "Karl", ssml: true, name: "AWS Polly - is-IS - Male - Karl"},
"it-IT_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "it-IT", ename: "Carla", ssml: true, name: "AWS Polly - it-IT - Female - Carla"},
"it-IT_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "it-IT", ename: "Giorgio", ssml: true, name: "AWS Polly - it-IT - Male - Giorgio"},
"nb-NO_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "nb-NO", ename: "Liv", ssml: true, name: "AWS Polly - nb-NO - Female - Liv"},
"nl-NL_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "nl-NL", ename: "Lotte", ssml: true, name: "AWS Polly - nl-NL - Female - Lotte"},
"nl-NL_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "nl-NL", ename: "Ruben", ssml: true, name: "AWS Polly - nl-NL - Male - Ruben"},
"pl-PL_AP_Female_Agnieszka":{gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "pl-PL", ename: "Agnieszka", ssml: true, name: "AWS Polly - pl-PL - Female - Agnieszka"},
"pl-PL_AP_Male_Jacek": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "pl-PL", ename: "Jacek", ssml: true, name: "AWS Polly - pl-PL - Male - Jacek"},
"pl-PL_AP_Female_Ewa": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "pl-PL", ename: "Ewa", ssml: true, name: "AWS Polly - pl-PL - Female - Ewa"},
"pl-PL_AP_Male_Jan": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "pl-PL", ename: "Jan", ssml: true, name: "AWS Polly - pl-PL - Male - Jan"},
"pl-PL_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "pl-PL", ename: "Maja", ssml: true, name: "AWS Polly - pl-PL - Female - Maja"},
"pt-BR_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "pt-BR", ename: "Vitoria", ssml: true, name: "AWS Polly - pt-BR - Female - Vitoria"},
"pt-BR_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "pt-BR", ename: "Ricardo", ssml: true, name: "AWS Polly - pt-BR - Male - Ricardo"},
"pt-PT_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "pt-PT", ename: "Cristiano", ssml: true, name: "AWS Polly - pt-PT - Male - Cristiano"},
"pt-PT_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "pt-PT", ename: "Ines", ssml: true, name: "AWS Polly - pt-PT - Female - Ines"},
"ro-RO_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "ro-RO", ename: "Carmen", ssml: true, name: "AWS Polly - ro-RO - Female - Carmen"},
"sv-SE_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "sv-SE", ename: "Astrid", ssml: true, name: "AWS Polly - sv-SE - Female - Astrid"},
"tr-TR_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "tr-TR", ename: "Filiz", ssml: true, name: "AWS Polly - tr-TR - Female - Filiz"}
};
// Blockly.Sendto is global variable and defined in javascript/admin/google-blockly/own/blocks_sendto.js
Blockly.Sendto.blocks['sayit'] =
'<block type="sayit">'
+ ' <value name="INSTANCE">'
+ ' </value>'
+ ' <value name="LANGUAGE">'
+ ' </value>'
+ ' <value name="VOLUME">'
+ ' </value>'
+ ' <value name="MESSAGE">'
+ ' <shadow type="text">'
+ ' <field name="TEXT">text</field>'
+ ' </shadow>'
+ ' </value>'
+ ' <value name="LOG">'
+ ' </value>'
+ '</block>';
Blockly.Blocks['sayit'] = {
init: function() {
var options = [];
if (typeof main !== 'undefined' && main.instances) {
for (var i = 0; i < main.instances.length; i++) {
var m = main.instances[i].match(/^system.adapter.sayit.(\d+)$/);
if (m) {
var n = parseInt(m[1], 10);
options.push(['sayit.' + n, '.' + n]);
}
}
if (options.length === 0) {
for (var u = 0; u <= 4; u++) {
options.push(['sayit.' + u, '.' + u]);
}
}
} else {
for (var u = 0; u <= 4; u++) {
options.push(['sayit.' + u, '.' + u]);
}
}
this.appendDummyInput('INSTANCE')
.appendField(Blockly.Words['sayit'][systemLang])
.appendField(new Blockly.FieldDropdown(options), 'INSTANCE');
var languages = [[Blockly.Words['sayit_configured'][systemLang], '']];
for (var l in sayitEngines) {
if (sayitEngines.hasOwnProperty(l)) languages.push([sayitEngines[l].name, l]);
}
this.appendDummyInput('LANGUAGE')
.appendField(new Blockly.FieldDropdown(languages), 'LANGUAGE');
var input = this.appendValueInput('VOLUME')
.setCheck('Number')
.appendField(Blockly.Words['sayit_volume'][systemLang]);
if (input.connection) input.connection._optional = true;
this.appendValueInput('MESSAGE')
.appendField(Blockly.Words['sayit_message'][systemLang]);
this.appendDummyInput('LOG')
.appendField(Blockly.Words['sayit_log'][systemLang])
.appendField(new Blockly.FieldDropdown([
[Blockly.Words['sayit_log_none'][systemLang], ''],
[Blockly.Words['sayit_log_info'][systemLang], 'log'],
[Blockly.Words['sayit_log_debug'][systemLang], 'debug'],
[Blockly.Words['sayit_log_warn'][systemLang], 'warn'],
[Blockly.Words['sayit_log_error'][systemLang], 'error']
]), 'LOG');
this.setInputsInline(false);
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(Blockly.Sendto.HUE);
this.setTooltip(Blockly.Words['sayit_tooltip'][systemLang]);
this.setHelpUrl(Blockly.Words['sayit_help'][systemLang]);
}
};
Blockly.JavaScript['sayit'] = function(block) {
var dropdown_instance = block.getFieldValue('INSTANCE');
var dropdown_language = block.getFieldValue('LANGUAGE');
var value_message = Blockly.JavaScript.valueToCode(block, 'MESSAGE', Blockly.JavaScript.ORDER_ATOMIC);
var value_volume = Blockly.JavaScript.valueToCode(block, 'VOLUME', Blockly.JavaScript.ORDER_ATOMIC);
var logLevel = block.getFieldValue('LOG');
var logText;
if (logLevel) {
logText = 'console.' + logLevel + '("sayIt' + (dropdown_language ? '[' + dropdown_language + ']' : '') + (value_volume ? '[Volume - ' + value_volume + ']' : '') + ': " + ' + value_message + ');\n'
} else {
logText = '';
}
return 'setState("sayit' + dropdown_instance + '.tts.text", "' + (dropdown_language ? dropdown_language + ';' : '') + (value_volume !== null && value_volume !== '' ? value_volume + ';' : '') + '" + ' + value_message + ');\n' +
logText;
};

139
admin/engines.js Normal file
View File

@@ -0,0 +1,139 @@
// DONT Forget to update sayitEngines in blockly.js (If you change this table)
var sayitEngines = {
"en": {name: "Google - English", engine: "google", params: []},
"de": {name: "Google - Deutsch", engine: "google", params: []},
"ru": {name: "Google - Русский", engine: "google", params: []},
"it": {name: "Google - Italiano", engine: "google", params: []},
"es": {name: "Google - Espaniol", engine: "google", params: []},
"fr": {name: "Google - Français", engine: "google", params: []},
"ru_YA": {name: "Yandex - Русский", engine: "yandex", params: ['key', 'voice', 'emotion', 'ill', 'drunk', 'robot'], voice: ['jane', 'zahar'], emotion: ['none', 'good', 'neutral', 'evil', 'mixed']},
"ru-RU_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "ru-RU", ename: "Tatyana", ssml: true, name: "Cloud - Русский - Татьяна"},
"ru-RU_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "ru-RU", ename: "Maxim", ssml: true, name: "Cloud - Русский - Максим"},
"de-DE_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "de-DE", ename: "Marlene", ssml: true, name: "Cloud - Deutsch - Marlene"},
"de-DE_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "de-DE", ename: "Hans", ssml: true, name: "Cloud - Deutsch - Hans"},
"en-US_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "en-US", ename: "Salli", ssml: true, name: "Cloud - en-US - Female - Salli"},
"en-US_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "en-US", ename: "Joey", ssml: true, name: "Cloud - en-US - Male - Joey"},
"da-DK_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "da-DK", ename: "Naja", ssml: true, name: "Cloud - da-DK - Female - Naja"},
"da-DK_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "da-DK", ename: "Mads", ssml: true, name: "Cloud - da-DK - Male - Mads"},
"en-AU_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "en-AU", ename: "Nicole", ssml: true, name: "Cloud - en-AU - Female - Nicole"},
"en-AU_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "en-AU", ename: "Russell", ssml: true, name: "Cloud - en-AU - Male - Russell"},
"en-GB_CLOUD_Female_Amy": {gender: "Female", engine: "cloud", params: ['cloud'], language: "en-GB", ename: "Amy", ssml: true, name: "Cloud - en-GB - Female - Amy"},
"en-GB_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "en-GB", ename: "Brian", ssml: true, name: "Cloud - en-GB - Male - Brian"},
"en-GB_CLOUD_Female_Emma": {gender: "Female", engine: "cloud", params: ['cloud'], language: "en-GB", ename: "Emma", ssml: true, name: "Cloud - en-GB - Female - Emma"},
"en-GB-WLS_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "en-GB-WLS", ename: "Gwyneth", ssml: true, name: "Cloud - en-GB-WLS - Female - Gwyneth"},
"en-GB-WLS_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "en-GB-WLS", ename: "Geraint", ssml: true, name: "Cloud - en-GB-WLS - Male - Geraint"},
"cy-GB_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "cy-GB", ename: "Gwyneth", ssml: true, name: "Cloud - cy-GB - Female - Gwyneth"},
"cy-GB_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "cy-GB", ename: "Geraint", ssml: true, name: "Cloud - cy-GB - Male - Geraint"},
"en-IN_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "en-IN", ename: "Raveena", ssml: true, name: "Cloud - en-IN - Female - Raveena"},
"en-US_CLOUD_Male_Chipmunk":{gender: "Male", engine: "cloud", params: ['cloud'], language: "en-US", ename: "Chipmunk", ssml: true, name: "Cloud - en-US - Male - Chipmunk"},
"en-US_CLOUD_Male_Eric": {gender: "Male", engine: "cloud", params: ['cloud'], language: "en-US", ename: "Eric", ssml: true, name: "Cloud - en-US - Male - Eric"},
"en-US_CLOUD_Female_Ivy": {gender: "Female", engine: "cloud", params: ['cloud'], language: "en-US", ename: "Ivy", ssml: true, name: "Cloud - en-US - Female - Ivy"},
"en-US_CLOUD_Female_Jennifer": {gender: "Female", engine: "cloud", params: ['cloud'], language: "en-US", ename: "Jennifer", ssml: true, name: "Cloud - en-US - Female - Jennifer"},
"en-US_CLOUD_Male_Justin": {gender: "Male", engine: "cloud", params: ['cloud'], language: "en-US", ename: "Justin", ssml: true, name: "Cloud - en-US - Male - Justin"},
"en-US_CLOUD_Female_Kendra": {gender: "Female", engine: "cloud", params: ['cloud'], language: "en-US", ename: "Kendra", ssml: true, name: "Cloud - en-US - Female - Kendra"},
"en-US_CLOUD_Female_Kimberly": {gender: "Female", engine: "cloud", params: ['cloud'], language: "en-US", ename: "Kimberly", ssml: true, name: "Cloud - en-US - Female - Kimberly"},
"es-ES_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "es-ES", ename: "Conchita", ssml: true, name: "Cloud - es-ES - Female - Conchita"},
"es-ES_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "es-ES", ename: "Enrique", ssml: true, name: "Cloud - es-ES - Male - Enrique"},
"es-US_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "es-US", ename: "Penelope", ssml: true, name: "Cloud - es-US - Female - Penelope"},
"es-US_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "es-US", ename: "Miguel", ssml: true, name: "Cloud - es-US - Male - Miguel"},
"fr-CA_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "fr-CA", ename: "Chantal", ssml: true, name: "Cloud - fr-CA - Female - Chantal"},
"fr-FR_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "fr-FR", ename: "Celine", ssml: true, name: "Cloud - fr-FR - Female - Celine"},
"fr-FR_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "fr-FR", ename: "Mathieu", ssml: true, name: "Cloud - fr-FR - Male - Mathieu"},
"is-IS_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "is-IS", ename: "Dora", ssml: true, name: "Cloud - is-IS - Female - Dora"},
"is-IS_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "is-IS", ename: "Karl", ssml: true, name: "Cloud - is-IS - Male - Karl"},
"it-IT_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "it-IT", ename: "Carla", ssml: true, name: "Cloud - it-IT - Female - Carla"},
"it-IT_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "it-IT", ename: "Giorgio", ssml: true, name: "Cloud - it-IT - Male - Giorgio"},
"nb-NO_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "nb-NO", ename: "Liv", ssml: true, name: "Cloud - nb-NO - Female - Liv"},
"nl-NL_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "nl-NL", ename: "Lotte", ssml: true, name: "Cloud - nl-NL - Female - Lotte"},
"nl-NL_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "nl-NL", ename: "Ruben", ssml: true, name: "Cloud - nl-NL - Male - Ruben"},
"pl-PL_CLOUD_Female_Agnieszka":{gender: "Female", engine: "cloud",params: ['cloud'], language: "pl-PL", ename: "Agnieszka", ssml: true, name: "Cloud - pl-PL - Female - Agnieszka"},
"pl-PL_CLOUD_Male_Jacek": {gender: "Male", engine: "cloud", params: ['cloud'], language: "pl-PL", ename: "Jacek", ssml: true, name: "Cloud - pl-PL - Male - Jacek"},
"pl-PL_CLOUD_Female_Ewa": {gender: "Female", engine: "cloud", params: ['cloud'], language: "pl-PL", ename: "Ewa", ssml: true, name: "Cloud - pl-PL - Female - Ewa"},
"pl-PL_CLOUD_Male_Jan": {gender: "Male", engine: "cloud", params: ['cloud'], language: "pl-PL", ename: "Jan", ssml: true, name: "Cloud - pl-PL - Male - Jan"},
"pl-PL_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "pl-PL", ename: "Maja", ssml: true, name: "Cloud - pl-PL - Female - Maja"},
"pt-BR_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "pt-BR", ename: "Vitoria", ssml: true, name: "Cloud - pt-BR - Female - Vitoria"},
"pt-BR_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "pt-BR", ename: "Ricardo", ssml: true, name: "Cloud - pt-BR - Male - Ricardo"},
"pt-PT_CLOUD_Male": {gender: "Male", engine: "cloud", params: ['cloud'], language: "pt-PT", ename: "Cristiano", ssml: true, name: "Cloud - pt-PT - Male - Cristiano"},
"pt-PT_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "pt-PT", ename: "Ines", ssml: true, name: "Cloud - pt-PT - Female - Ines"},
"ro-RO_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "ro-RO", ename: "Carmen", ssml: true, name: "Cloud - ro-RO - Female - Carmen"},
"sv-SE_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "sv-SE", ename: "Astrid", ssml: true, name: "Cloud - sv-SE - Female - Astrid"},
"tr-TR_CLOUD_Female": {gender: "Female", engine: "cloud", params: ['cloud'], language: "tr-TR", ename: "Filiz", ssml: true, name: "Cloud - tr-TR - Female - Filiz"},
"en-US": {name: "PicoTTS - Englisch US", engine: "PicoTTS", params: []},
"en-GB": {name: "PicoTTS - Englisch GB", engine: "PicoTTS", params: []},
"de-DE": {name: "PicoTTS - Deutsch", engine: "PicoTTS", params: []},
"it-IT": {name: "PicoTTS - Italiano", engine: "PicoTTS", params: []},
"es-ES": {name: "PicoTTS - Espaniol", engine: "PicoTTS", params: []},
"fr-FR": {name: "PicoTTS - Français", engine: "PicoTTS", params: []},
"ru-RU_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "ru-RU", ename: "Tatyana", ssml: true, name: "AWS Polly - Русский - Татьяна"},
"ru-RU_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "ru-RU", ename: "Maxim", ssml: true, name: "AWS Polly - Русский - Максим"},
"de-DE_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "de-DE", ename: "Marlene", ssml: true, name: "AWS Polly - Deutsch - Marlene"},
"de-DE_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "de-DE", ename: "Hans", ssml: true, name: "AWS Polly - Deutsch - Hans"},
"en-US_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-US", ename: "Salli", ssml: true, name: "AWS Polly - en-US - Female - Salli"},
"en-US_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-US", ename: "Joey", ssml: true, name: "AWS Polly - en-US - Male - Joey"},
"da-DK_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "da-DK", ename: "Naja", ssml: true, name: "AWS Polly - da-DK - Female - Naja"},
"da-DK_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "da-DK", ename: "Mads", ssml: true, name: "AWS Polly - da-DK - Male - Mads"},
"en-AU_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-AU", ename: "Nicole", ssml: true, name: "AWS Polly - en-AU - Female - Nicole"},
"en-AU_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-AU", ename: "Russell", ssml: true, name: "AWS Polly - en-AU - Male - Russell"},
"en-GB_AP_Female_Amy": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-GB", ename: "Amy", ssml: true, name: "AWS Polly - en-GB - Female - Amy"},
"en-GB_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-GB", ename: "Brian", ssml: true, name: "AWS Polly - en-GB - Male - Brian"},
"en-GB_AP_Female_Emma": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-GB", ename: "Emma", ssml: true, name: "AWS Polly - en-GB - Female - Emma"},
"en-GB-WLS_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-GB-WLS", ename: "Gwyneth", ssml: true, name: "AWS Polly - en-GB-WLS - Female - Gwyneth"},
"en-GB-WLS_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-GB-WLS", ename: "Geraint", ssml: true, name: "AWS Polly - en-GB-WLS - Male - Geraint"},
"cy-GB_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "cy-GB", ename: "Gwyneth", ssml: true, name: "AWS Polly - cy-GB - Female - Gwyneth"},
"cy-GB_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "cy-GB", ename: "Geraint", ssml: true, name: "AWS Polly - cy-GB - Male - Geraint"},
"en-IN_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-IN", ename: "Raveena", ssml: true, name: "AWS Polly - en-IN - Female - Raveena"},
"en-US_AP_Male_Chipmunk": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-US", ename: "Chipmunk", ssml: true, name: "AWS Polly - en-US - Male - Chipmunk"},
"en-US_AP_Male_Eric": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-US", ename: "Eric", ssml: true, name: "AWS Polly - en-US - Male - Eric"},
"en-US_AP_Female_Ivy": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-US", ename: "Ivy", ssml: true, name: "AWS Polly - en-US - Female - Ivy"},
"en-US_AP_Female_Jennifer": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-US", ename: "Jennifer", ssml: true, name: "AWS Polly - en-US - Female - Jennifer"},
"en-US_AP_Male_Justin": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-US", ename: "Justin", ssml: true, name: "AWS Polly - en-US - Male - Justin"},
"en-US_AP_Female_Kendra": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-US", ename: "Kendra", ssml: true, name: "AWS Polly - en-US - Female - Kendra"},
"en-US_AP_Female_Kimberly": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "en-US", ename: "Kimberly", ssml: true, name: "AWS Polly - en-US - Female - Kimberly"},
"es-ES_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "es-ES", ename: "Conchita", ssml: true, name: "AWS Polly - es-ES - Female - Conchita"},
"es-ES_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "es-ES", ename: "Enrique", ssml: true, name: "AWS Polly - es-ES - Male - Enrique"},
"es-US_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "es-US", ename: "Penelope", ssml: true, name: "AWS Polly - es-US - Female - Penelope"},
"es-US_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "es-US", ename: "Miguel", ssml: true, name: "AWS Polly - es-US - Male - Miguel"},
"fr-CA_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "fr-CA", ename: "Chantal", ssml: true, name: "AWS Polly - fr-CA - Female - Chantal"},
"fr-FR_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "fr-FR", ename: "Celine", ssml: true, name: "AWS Polly - fr-FR - Female - Celine"},
"fr-FR_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "fr-FR", ename: "Mathieu", ssml: true, name: "AWS Polly - fr-FR - Male - Mathieu"},
"is-IS_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "is-IS", ename: "Dora", ssml: true, name: "AWS Polly - is-IS - Female - Dora"},
"is-IS_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "is-IS", ename: "Karl", ssml: true, name: "AWS Polly - is-IS - Male - Karl"},
"it-IT_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "it-IT", ename: "Carla", ssml: true, name: "AWS Polly - it-IT - Female - Carla"},
"it-IT_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "it-IT", ename: "Giorgio", ssml: true, name: "AWS Polly - it-IT - Male - Giorgio"},
"nb-NO_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "nb-NO", ename: "Liv", ssml: true, name: "AWS Polly - nb-NO - Female - Liv"},
"nl-NL_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "nl-NL", ename: "Lotte", ssml: true, name: "AWS Polly - nl-NL - Female - Lotte"},
"nl-NL_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "nl-NL", ename: "Ruben", ssml: true, name: "AWS Polly - nl-NL - Male - Ruben"},
"pl-PL_AP_Female_Agnieszka":{gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "pl-PL", ename: "Agnieszka", ssml: true, name: "AWS Polly - pl-PL - Female - Agnieszka"},
"pl-PL_AP_Male_Jacek": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "pl-PL", ename: "Jacek", ssml: true, name: "AWS Polly - pl-PL - Male - Jacek"},
"pl-PL_AP_Female_Ewa": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "pl-PL", ename: "Ewa", ssml: true, name: "AWS Polly - pl-PL - Female - Ewa"},
"pl-PL_AP_Male_Jan": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "pl-PL", ename: "Jan", ssml: true, name: "AWS Polly - pl-PL - Male - Jan"},
"pl-PL_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "pl-PL", ename: "Maja", ssml: true, name: "AWS Polly - pl-PL - Female - Maja"},
"pt-BR_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "pt-BR", ename: "Vitoria", ssml: true, name: "AWS Polly - pt-BR - Female - Vitoria"},
"pt-BR_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "pt-BR", ename: "Ricardo", ssml: true, name: "AWS Polly - pt-BR - Male - Ricardo"},
"pt-PT_AP_Male": {gender: "Male", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "pt-PT", ename: "Cristiano", ssml: true, name: "AWS Polly - pt-PT - Male - Cristiano"},
"pt-PT_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "pt-PT", ename: "Ines", ssml: true, name: "AWS Polly - pt-PT - Female - Ines"},
"ro-RO_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "ro-RO", ename: "Carmen", ssml: true, name: "AWS Polly - ro-RO - Female - Carmen"},
"sv-SE_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "sv-SE", ename: "Astrid", ssml: true, name: "AWS Polly - sv-SE - Female - Astrid"},
"tr-TR_AP_Female": {gender: "Female", engine: "polly", params: ['accessKey', 'secretKey', 'region'], language: "tr-TR", ename: "Filiz", ssml: true, name: "AWS Polly - tr-TR - Female - Filiz"}
};
var sayitOptions = {
"browser": {name: "Browser", params: ['engine', 'cache', 'instance'], mp3Required: true, checkLength: true, func: (typeof sayItBrowser !== 'undefined') ? sayItBrowser : null, server: true, libs: ['fs', 'crypto', 'http']},
"mp24ftp": {name: "MediaPlayer24+FTP", params: ['engine', 'cache', 'server', 'port', 'user', 'pass'], mp3Required: true, checkLength: true, func: (typeof sayItMP24ftp !== 'undefined') ? sayItMP24ftp : null, server: false, libs: ['fs', 'crypto', 'http', 'jsftp']},
"mp24": {name: "MediaPlayer24", params: ['server'], mp3Required: false, checkLength: true, func: (typeof sayItMP24 !== 'undefined') ? sayItMP24 : null, server: false, libs: ['fs', 'crypto', 'http']},
"system": {name: "System", params: ['engine', 'cache'], mp3Required: true, checkLength: false, func: (typeof sayItSystem !== 'undefined') ? sayItSystem : null, server: false, libs: ['fs', 'crypto', 'http', 'child_process', 'os']},
"windows": {name: "Windows default", params: [], mp3Required: false, checkLength: true, func: (typeof sayItWindows !== 'undefined') ? sayItWindows : null, server: false, libs: ['fs', 'child_process']},
"sonos": {name: "Sonos", params: ['engine', 'cache', 'device', 'web', 'webServer'], mp3Required: true, checkLength: true, func: (typeof sayItSonos !== 'undefined') ? sayItSonos : null, server: true, libs: ['fs', 'crypto', 'http']},
"chromecast": {name: "Chromecast", params: ['engine', 'cache', 'cDevice', 'web', 'webServer'], mp3Required: true, checkLength: true, func: (typeof sayItChromecast !== 'undefined') ? sayItChromecast : null, server: true, libs: ['fs', 'crypto', 'http']},
"mpd": {name: "MPD", params: ['engine', 'cache', 'mpd_device', 'web', 'webServer'], mp3Required: true, checkLength: true, func: (typeof sayItMpd !== 'undefined') ? sayItMpd : null, server: true, libs: ['fs', 'crypto', 'http']},
"googleHome": {name: "Google Home", params: ['engine', 'cache', 'server', 'web', 'webServer'], mp3Required: true, checkLength: true, func: (typeof sayItGoogleHome !== 'undefined') ? sayItGoogleHome : null, server: true, libs: ['fs', 'crypto', 'http', 'castv2-client']}
};
if (typeof module !== 'undefined' && module.parent) {
module.exports.sayitEngines = sayitEngines;
module.exports.sayitOptions = sayitOptions;
}

View File

@@ -0,0 +1,42 @@
{
"sayIt adapter settings": "sayIt Einstellungen",
"Language:": "Sprache",
"Type:": "Ausgabe durch",
"Cache:": "Cache benutzen",
"Server:": "Server",
"Port:": "Port",
"User:": "Anwender",
"Password:": "Kennwort",
"Browser instance:": "Browser-Instanz",
"Device:": "Gerät",
"Web server IP:": "Web server IP",
"All": "Alle",
"Web instance:": "Web instance",
"Voice:": "Stimme",
"API Key:": "API Key",
"Emotion:": "Emotion",
"Drunk:": "Betrunken",
"Ill:": "Krank",
"Robot:": "Roboter",
"Announce:": "Ankündigung",
"place here": "Dateien hier ablegen",
"Announce timeout (sec):": "Ankündigungstimeout (Sek)",
"Announce length (sec):": "Ankündigunglänge (Sek)",
"API Key is not set!": "API Key ist leer!",
"System command:": "Systemkommando",
"Announce volume (%):": "Ankündigungslautstärke(%)",
"Linux player:": "Linux-Player",
"Ignore for non linux OS": "Ignorieren für nicht linux OS",
"Access Key:": "Access Key",
"Secret Key:": "Secret Key",
"AWS Region:": "AWS Region",
"Cloud instance:": "Cloud-Intsanz",
"Cache-Expiry:": "Cache-Expiry",
"Browse": "Suchen",
"select": "auswählen",
"error": "Fehler",
"IP not accessible for server": "IP ist für dieses Gerät nicht erreichbar",
"Install first cloud adapter": "Erst muss cloud-Adapter installiert werden",
"tooltip_type": "Damit wird gesetzt, wo die Sprache ausgegeben wird",
"tooltip_command": "Dieses Kommando wird ausgeführt, um eigenen System-Player zu benutzen"
}

View File

@@ -0,0 +1,42 @@
{
"sayIt adapter settings": "sayIt adapter settings",
"Language:": "Language",
"Type:": "Output in",
"Cache:": "Cache received audio files",
"Server:": "Server",
"Port:": "Port",
"User:": "User",
"Password:": "Password",
"Browser instance:": "Browser instance",
"Device:": "Device",
"Web server IP:": "Web server IP",
"All": "All",
"Web instance:": "Web instance",
"Voice:": "Voice",
"API Key:": "API Key",
"Emotion:": "Emotion",
"Drunk:": "Drunk",
"Ill:": "Ill",
"Robot:": "Robot",
"Announce:": "Announce",
"place here": "Place files here",
"Announce timeout (sec):": "Announce timeout (sec)",
"Announce length (sec):": "Announce length (sec)",
"API Key is not set!": "API Key is not set!",
"System command:": "System command",
"Announce volume (%):": "Announce volume(%)",
"Linux player:": "Linux player",
"Ignore for non linux OS": "Ignore for non linux OS",
"Access Key:": "Access Key",
"Secret Key:": "Secret Key",
"AWS Region:": "AWS Region",
"Cloud instance:": "Cloud instance",
"Cache-Expiry:": "Cache-Expiry",
"Browse": "Browse",
"select": "select",
"error": "error",
"IP not accessible for server": "IP is not accessible for the device",
"Install first cloud adapter": "Install first cloud adapter",
"tooltip_type": "Specify where the voice must be played",
"tooltip_command": "This command will be executed, to use the custom system player"
}

View File

@@ -0,0 +1,42 @@
{
"sayIt adapter settings": "sayIt configuración del adaptador",
"Language:": "Idioma",
"Type:": "Salida en",
"Cache:": "Caché recibió archivos de audio",
"Server:": "Servidor",
"Port:": "Puerto",
"User:": "Usuario",
"Password:": "Contraseña",
"Browser instance:": "Instancia del navegador",
"Device:": "Dispositivo",
"Web server IP:": "Servidor web IP",
"All": "Todas",
"Web instance:": "Instancia web",
"Voice:": "Voz",
"API Key:": "Clave API",
"Emotion:": "Emoción",
"Drunk:": "Borracho",
"Ill:": "Enfermo",
"Robot:": "Robot",
"Announce:": "Anunciar",
"place here": "Coloque los archivos aquí",
"Announce timeout (sec):": "Anunciar tiempo de espera (sec)",
"Announce length (sec):": "Anunciar longitud (sec)",
"API Key is not set!": "¡La clave API no está configurada!",
"System command:": "Comando del sistema",
"Announce volume (%):": "Anunciar volumen (%)",
"Linux player:": "Reproductor de Linux",
"Ignore for non linux OS": "Ignorar para el sistema operativo no Linux",
"Access Key:": "Llave de acceso",
"Secret Key:": "Llave secreta",
"AWS Region:": "Región de AWS",
"Cloud instance:": "Instancia de la nube",
"Cache-Expiry:": "Caché-expiración",
"Browse": "Vistazo",
"select": "seleccionar",
"error": "error",
"IP not accessible for server": "IP no es accesible para el dispositivo",
"Install first cloud adapter": "Instale el primer adaptador de nube",
"tooltip_type": "Especifique dónde debe reproducirse la voz",
"tooltip_command": "Este comando se ejecutará para usar el reproductor de sistema personalizado"
}

View File

@@ -0,0 +1,42 @@
{
"sayIt adapter settings": "sayIt paramètres de l'adaptateur",
"Language:": "La langue",
"Type:": "Sortie dans",
"Cache:": "Cache les fichiers audio reçus",
"Server:": "Serveur",
"Port:": "Port",
"User:": "Utilisateur",
"Password:": "Mot de passe",
"Browser instance:": "Instance du navigateur",
"Device:": "Dispositif",
"Web server IP:": "Adresse IP du serveur Web",
"All": "Tout",
"Web instance:": "Instance Web",
"Voice:": "Voix",
"API Key:": "clé API",
"Emotion:": "Émotion",
"Drunk:": "Ivre",
"Ill:": "Mauvais",
"Robot:": "Robot",
"Announce:": "Annoncer",
"place here": "Placez les fichiers ici",
"Announce timeout (sec):": "Annoncer le délai (sec)",
"Announce length (sec):": "Annoncer la longueur (sec)",
"API Key is not set!": "La clé de l'API n'est pas définie!",
"System command:": "Commande système",
"Announce volume (%):": "Annoncer le volume (%)",
"Linux player:": "Lecteur Linux",
"Ignore for non linux OS": "Ignorer pour les systèmes d'exploitation autres que Linux",
"Access Key:": "Clef d'accès",
"Secret Key:": "Clef secrète",
"AWS Region:": "Région AWS",
"Cloud instance:": "Instance de cloud",
"Cache-Expiry:": "Cache-Expiration",
"Browse": "Feuilleter",
"select": "sélectionner",
"error": "Erreur",
"IP not accessible for server": "L'adresse IP n'est pas accessible pour l'appareil",
"Install first cloud adapter": "Installez le premier adaptateur de cloud",
"tooltip_type": "Précisez où la voix doit être jouée",
"tooltip_command": "Cette commande sera exécutée pour utiliser le lecteur système personnalisé"
}

View File

@@ -0,0 +1,42 @@
{
"sayIt adapter settings": "impostazioni dell'adattatore sayIt",
"Language:": "linguaggio",
"Type:": "Uscita in",
"Cache:": "La cache ha ricevuto file audio",
"Server:": "server",
"Port:": "Porta",
"User:": "Utente",
"Password:": "Parola d'ordine",
"Browser instance:": "Istanza del browser",
"Device:": "Dispositivo",
"Web server IP:": "IP del server Web",
"All": "Tutti",
"Web instance:": "Istanza Web",
"Voice:": "Voce",
"API Key:": "Chiave API",
"Emotion:": "Emozione",
"Drunk:": "Ubriaco",
"Ill:": "Malato",
"Robot:": "Robot",
"Announce:": "Annunciare",
"place here": "Inserisci i file qui",
"Announce timeout (sec):": "Annuncia timeout (sec)",
"Announce length (sec):": "Annunciare la lunghezza (sec)",
"API Key is not set!": "Chiave API non impostata!",
"System command:": "Comando di sistema",
"Announce volume (%):": "Annuncia volume (%)",
"Linux player:": "Lettore Linux",
"Ignore for non linux OS": "Ignora per sistema operativo non Linux",
"Access Key:": "Chiave di accesso",
"Secret Key:": "Chiave segreta",
"AWS Region:": "Regione AWS",
"Cloud instance:": "Istanza Cloud",
"Cache-Expiry:": "Cache-scadenza",
"Browse": "Navigare",
"select": "selezionare",
"error": "errore",
"IP not accessible for server": "L'IP non è accessibile per il dispositivo",
"Install first cloud adapter": "Installa il primo adattatore cloud",
"tooltip_type": "Specificare dove deve essere suonata la voce",
"tooltip_command": "Questo comando verrà eseguito, per utilizzare il player di sistema personalizzato"
}

View File

@@ -0,0 +1,42 @@
{
"sayIt adapter settings": "sayIt adapter-instellingen",
"Language:": "Taal",
"Type:": "Uitgang in",
"Cache:": "Cache ontvangen audiobestanden",
"Server:": "Server",
"Port:": "Haven",
"User:": "Gebruiker",
"Password:": "Wachtwoord",
"Browser instance:": "Browserinstantie",
"Device:": "Apparaat",
"Web server IP:": "Webserver IP",
"All": "Alle",
"Web instance:": "Web-instantie",
"Voice:": "Stem",
"API Key:": "API sleutel",
"Emotion:": "Emotie",
"Drunk:": "Dronken",
"Ill:": "Ziek",
"Robot:": "Robot",
"Announce:": "aankondigen",
"place here": "Plaats hier bestanden",
"Announce timeout (sec):": "Announce time-out (sec)",
"Announce length (sec):": "Kondig lengte (sec)",
"API Key is not set!": "API-sleutel is niet ingesteld!",
"System command:": "Systeemopdracht",
"Announce volume (%):": "Volume aankondigen (%)",
"Linux player:": "Linux-speler",
"Ignore for non linux OS": "Negeren voor niet-Linux OS",
"Access Key:": "Toegangssleutel",
"Secret Key:": "Geheime sleutel",
"AWS Region:": "AWS-regio",
"Cloud instance:": "Cloud instantie",
"Cache-Expiry:": "Cache-Vervaldatum",
"Browse": "Blader",
"select": "kiezen",
"error": "fout",
"IP not accessible for server": "IP is niet toegankelijk voor het apparaat",
"Install first cloud adapter": "Installeer de eerste cloudadapter",
"tooltip_type": "Geef op waar de stem moet worden afgespeeld",
"tooltip_command": "Deze opdracht wordt uitgevoerd om de aangepaste systeemspeler te gebruiken"
}

View File

@@ -0,0 +1,42 @@
{
"sayIt adapter settings": "sayTo ustawienia adaptera",
"Language:": "Język",
"Type:": "Wyjście w",
"Cache:": "Pamięć podręczna otrzymała pliki audio",
"Server:": "serwer",
"Port:": "Port",
"User:": "Użytkownik",
"Password:": "Hasło",
"Browser instance:": "Browser instance",
"Device:": "Urządzenie",
"Web server IP:": "Adres IP serwera WWW",
"All": "Wszystko",
"Web instance:": "Instancja internetowa",
"Voice:": "Głos",
"API Key:": "Klucz API",
"Emotion:": "Emocja",
"Drunk:": "Pijany",
"Ill:": "Chory",
"Robot:": "Robot",
"Announce:": "Ogłosić",
"place here": "Umieść pliki tutaj",
"Announce timeout (sec):": "Ogłaszaj limit czasu (sek)",
"Announce length (sec):": "Ogłaszaj długość (s)",
"API Key is not set!": "Klucz API nie jest ustawiony!",
"System command:": "Polecenie systemowe",
"Announce volume (%):": "Ogłaszaj objętość (%)",
"Linux player:": "Odtwarzacz Linux",
"Ignore for non linux OS": "Zignoruj dla systemu operacyjnego non linux",
"Access Key:": "Klucz dostępu",
"Secret Key:": "Sekretny klucz",
"AWS Region:": "Region AWS",
"Cloud instance:": "Instancja chmury",
"Cache-Expiry:": "Cache-Expiry",
"Browse": "Paść się",
"select": "Wybierz",
"error": "błąd",
"IP not accessible for server": "Adres IP jest niedostępny dla urządzenia",
"Install first cloud adapter": "Zainstaluj pierwszy adapter chmury",
"tooltip_type": "Określ, gdzie musi być odtwarzany głos",
"tooltip_command": "To polecenie zostanie wykonane, aby użyć niestandardowego odtwarzacza systemu"
}

View File

@@ -0,0 +1,42 @@
{
"sayIt adapter settings": "configurações do adaptador sayIt",
"Language:": "Língua",
"Type:": "Saída em",
"Cache:": "Cache recebido arquivos de áudio",
"Server:": "Servidor",
"Port:": "Porta",
"User:": "Do utilizador",
"Password:": "Senha",
"Browser instance:": "Instância do navegador",
"Device:": "Dispositivo",
"Web server IP:": "IP do servidor da Web",
"All": "Todos",
"Web instance:": "Instância da Web",
"Voice:": "Voz",
"API Key:": "Chave API",
"Emotion:": "Emoção",
"Drunk:": "Bêbado",
"Ill:": "Eu vou",
"Robot:": "Robô",
"Announce:": "Anunciar",
"place here": "Coloque arquivos aqui",
"Announce timeout (sec):": "Anular o tempo limite (seg)",
"Announce length (sec):": "Anunciar o comprimento (seg)",
"API Key is not set!": "A chave da API não está definida!",
"System command:": "Comando do sistema",
"Announce volume (%):": "Anunciar volume (%)",
"Linux player:": "Jogador Linux",
"Ignore for non linux OS": "Ignorar para SO não linux",
"Access Key:": "Chave de acesso",
"Secret Key:": "Chave secreta",
"AWS Region:": "Região da AWS",
"Cloud instance:": "Instância de nuvem",
"Cache-Expiry:": "Cache-Expiração",
"Browse": "Squeaky toy",
"select": "selecione",
"error": "erro",
"IP not accessible for server": "IP não está acessível para o dispositivo",
"Install first cloud adapter": "Instalar o primeiro adaptador de nuvem",
"tooltip_type": "Especifique onde a voz deve ser tocada",
"tooltip_command": "Este comando será executado para usar o player do sistema personalizado"
}

View File

@@ -0,0 +1,42 @@
{
"sayIt adapter settings": "sayIt Настройки",
"Language:": "Язык",
"Type:": "Вывод звука",
"Cache:": "Кэшировать полученные аудио-файлы",
"Server:": "Сервер",
"Port:": "Порт",
"User:": "Пользователь",
"Password:": "Пароль",
"Browser instance:": "ID броузера",
"Device:": "Устройство",
"Web server IP:": "IP Веб сервера",
"All": "Все",
"Web instance:": "Web instance",
"Voice:": "Голос",
"API Key:": "API Key",
"Emotion:": "Емоциональный окрас",
"Drunk:": "Пьяный голос",
"Ill:": "Больной голос",
"Robot:": "Голос робота",
"Announce:": "Извещение",
"place here": "Перетащите файлы сюда",
"Announce timeout (sec):": "Таймаут извещения (сек)",
"Announce length (sec):": "Длительность извещения (сек)",
"API Key is not set!": "API Key отсутствует!",
"System command:": "Системный вызов",
"Announce volume (%):": "Громкость извещения(%)",
"Linux player:": "Linux плейер",
"Ignore for non linux OS": "Игнорировать для не linux OS",
"Access Key:": "Access Key",
"Secret Key:": "Secret Key",
"AWS Region:": "AWS регион",
"Cloud instance:": "Cloud драйвер",
"Cache-Expiry:": "Срок хранения кэш",
"Browse": "Искать",
"select": "выбрать",
"error": "ошибка",
"IP not accessible for server": "Этот IP недоступен для устройства",
"Install first cloud adapter": "Сначала установите cloud драйвер",
"tooltip_type": "Укажите как и где будет выводится голос",
"tooltip_command": "Эта команда выполнится для проигрывания с собственным плеером"
}

650
admin/index.html Normal file
View File

@@ -0,0 +1,650 @@
<html>
<head>
<link rel="stylesheet" type="text/css" href="../../lib/css/themes/jquery-ui/redmond/jquery-ui.min.css"/>
<script type="text/javascript" src="../../lib/js/jquery-1.11.1.min.js"></script>
<script type="text/javascript" src="../../socket.io/socket.io.js"></script>
<script type="text/javascript" src="../../lib/js/jquery-ui-1.10.3.full.min.js"></script>
<link rel="stylesheet" type="text/css" href="../../css/adapter.css"/>
<script type="text/javascript" src="engines.js"></script>
<script type="text/javascript" src="../../js/translate.js"></script>
<script type="text/javascript" src="../../js/adapter-settings.js"></script>
<script type="text/javascript" src="words.js"></script>
<style>
#drop_zone {
border: 2px dashed #bbb;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
padding: 25px;
text-align: center;
font-size: 20pt;
font-weight: bold;
font-family: 'Arial';
color: #bbb;
width: 90%;
height: 60px;
}
.error {
border: 2px solid red;
}
</style>
<script type="text/javascript">
var webServers = null;
var gOnChange;
function showHideSettings() {
$('.variable').hide();
var type = $('#type').val();
for (var j = 0; j < sayitOptions[type].params.length; j++) {
$('#tr_' + sayitOptions[type].params[j]).show();
}
if (type === 'googleHome') {
$('.announceValue').hide();
$('.announce').hide();
$('.system').hide();
$('.googleHome').show();
getIsAdapterAlive(function (isAlive) {
if (isAlive || common.enabled) {
$('#search').button('enable');
fillGoogleHome();
}
});
} else {
$('.googleHome').hide();
$('.announceValue').show();
$('.announce').show();
}
$('.engine').hide();
var engine = $('#engine').val();
if (!sayitEngines[engine] || sayitOptions[type].params.indexOf('engine') === -1) return;
for (var i = 0; i < sayitEngines[engine].params.length; i++) {
$('#tr_' + sayitEngines[engine].params[i]).show();
if (sayitEngines[engine][sayitEngines[engine].params[i]]) {
var arr = sayitEngines[engine][sayitEngines[engine].params[i]];
var param = sayitEngines[engine].params[i];
var $val = $('#' + param);
if (arr.length) {
var val = $val.val();
var text = '';
for (var p = 0; p < arr.length; p++) {
text += '<option value="' + arr[p] + '">' + arr[p] + '</option>';
}
$val.html(text);
if (val) {
$val.val(val);
} else {
$val.val(arr[0]);
}
} else {
$val.html('');
}
}
}
if (type !== 'system') {
$('.system').hide();
} else {
$('.system').show();
}
if (!$('#announce').val()) {
$('.announce').hide();
$('#play').button('disable');
} else {
$('.announce').show();
$('#play').button('enable');
}
}
function fillSonosDevices(elem, current) {
socket.emit('getObjectView', 'system', 'channel', {startkey: 'sonos.', endkey: 'sonos.\u9999'}, function (err, res) {
if (!err && res) {
for (var i = 0; i < res.rows.length; i++) {
$(elem).append('<option value="' + res.rows[i].id + '">' + res.rows[i].id + '</option>');
}
}
$(elem).val(current);
});
}
function fillMpdDevice(elem, current) {
socket.emit('getObjectView', 'system', 'instance', {startkey: 'system.adapter.mpd.', endkey: 'system.adapter.mpd.\u9999'}, function (err, res) {
if (!err && res) {
for (var i = 0; i < res.rows.length; i++) {
var n = res.rows[i].id.replace('system.adapter.', '');
$(elem).append('<option value="' + n + '">' + n + '</option>');
}
}
$(elem).val(current);
});
}
function fillChromecastDevices(elem, current) {
socket.emit('getObjectView', 'system', 'device', {startkey: 'chromecast.', endkey: 'chromecast.\u9999'}, function (err, res) {
if (!err && res) {
for (var i = 0; i < res.rows.length; i++) {
$(elem).append('<option value="' + res.rows[i].id + '">' + res.rows[i].id + '</option>');
}
}
$(elem).val(current);
});
}
function fillGoogleHome() {
$('#search').button('disable');
sendTo(null, 'browseChromecast', null, function (list) {
var text = '<option value="">' + _('select') + '</option>';
if (list) {
for (var i = 0; i < list.length; i++) {
text += '<option value="' + list[i].ip + '">' + list[i].name + '[' + list[i].ip + ']</option>';
}
$('#googleHome').html(text).prop('disabled', false).off('change').on('change', function () {
$('#server').val($(this).val()).trigger('change');
});
} else {
$('#googleHome').html('<option value="">' + _('error') + '</option>').prop('disabled', true);
}
$('#search').button('enable');
});
}
function ip2hex(ip) {
var octets = ip.split('.');
if (octets.length !== 4) {
return 0;
}
var result = 0;
for (var i = 0; i < octets.length; ++i) {
var octet = parseInt(octets[i], 10);
if (Number.isNaN(octet) || octet < 0 || octet > 255) {
throw new Error("Each octet must be between 0 and 255");
}
result |= octet << ((octets.length - i) * 8);
}
return result;
}
function checkWeb(elem, current) {
var web = $('#web').val();
for (var i = 0; i < webServers.length; i++) {
if (webServers[i].id === 'system.adapter.' + web) {
if (webServers[i].value.native.auth) {
showMessage(_('Cannot use web server with authentication'), null, 'info');
}
if (webServers[i].value.native.bind === 'localhost' || webServers[i].value.native.bind === '127.0.0.1' || webServers[i].value.native.bind === '::1') {
showMessage(_('Cannot use web server only on localhost'), null, 'info');
}
if (webServers[i].value.native.bind === '0.0.0.0') {
$('#tr_webServer').show();
var $webServer = $('#webServer');
$webServer.html('');
// read all ipv4 addresses of host
socket.emit('getObject', 'system.host.' + webServers[i].value.common.host, function (err, obj) {
var text = '';
if (!err && obj && obj.native) {
for (var iface in obj.native.hardware.networkInterfaces) {
if (obj.native.hardware.networkInterfaces.hasOwnProperty(iface)) {
for (var i = 0; i < obj.native.hardware.networkInterfaces[iface].length; i++) {
if (obj.native.hardware.networkInterfaces[iface][i].family === 'IPv4' && !obj.native.hardware.networkInterfaces[iface][i].internal) {
text += '<option value="' + obj.native.hardware.networkInterfaces[iface][i].address + '" data-mask="' + obj.native.hardware.networkInterfaces[iface][i].netmask + '">[IPv4] ' + obj.native.hardware.networkInterfaces[iface][i].address + ' - ' + iface + '</option>';
}
}
}
}
}
$webServer.html(text);
if (current) {
$webServer.val(current);
}
$webServer.off('change').on('change', function () {
gOnChange();
var ip = $(this).val();
var netmask = $(this).find('option[value="' + ip + '"]').data('mask');
var server;
var type = $('#type').val();
if (type === 'googleHome') {
server = $('#server').val();
} else if (type === 'sonos') {
server = $('#device').val();
} else if (type === 'chromecast') {
server = $('#cDevice').val();
} else if (type === 'mpd') {
server = $('#mpd_device').val();
}
if (server && ip && netmask && server.indexOf(':') === -1 && ip.indexOf(':') === -1) {
ip = ip2hex(ip);
netmask = ip2hex(netmask);
server = ip2hex(server);
if ((ip & netmask) !== (server & netmask)) {
$webServer.addClass('error').attr('title', _('IP not accessible for server'));
} else {
$webServer.removeClass('error').attr('title', '');
}
} else {
$webServer.removeClass('error');
}
});
});
} else if (webServers[i].value.native.bind === '::') {
// read all ipv6 addresses of host
socket.emit('getObject', 'system.host.' + webServers[i].value.common.host, function (err, obj) {
if (!err && obj && obj.native) {
for (var iface in obj.native.hardware.networkInterfaces) {
if (obj.native.hardware.networkInterfaces.hasOwnProperty(iface)) {
for (var i = 0; i < obj.native.hardware.networkInterfaces[iface].length; i++) {
if (obj.native.hardware.networkInterfaces[iface][i].family === 'IPv6' && !obj.native.hardware.networkInterfaces[iface][i].internal) {
$('#webServer').append('<option value="' + obj.native.hardware.networkInterfaces[iface][i].address + '">[IPv6] ' + obj.native.hardware.networkInterfaces[iface][i].address + ' - ' + iface + '</option>');
}
}
}
}
}
if (current) {
$('#webServer').val(current);
}
});
} else {
$('#tr_webServer').hide();
}
}
}
}
function fillWebServices(elem, current, type, webServer) {
socket.emit('getObjectView', 'system', 'instance', {startkey: 'system.adapter.web.', endkey: 'system.adapter.web.\u9999'}, function (err, res) {
if (!err && res) {
webServers = res.rows;
for (var i = 0; i < res.rows.length; i++) {
var n = res.rows[i].id.replace('system.adapter.', '');
var auth = res.rows[i].value.native.auth ? 'data-auth="true"' : '';
$(elem).append('<option value="' + n + '" ' + auth + '>' + n + '</option>');
}
}
$(elem).val(current);
if ((type === 'sonos') ||
(type === 'chromecast') ||
(type === 'googleHome')) {
checkWeb('#web', webServer);
}
});
}
function uploadFile(file, callback) {
var reader = new FileReader();
// Closure to capture the file information.
reader.onload = function(e) {
socket.emit('writeFile', 'sayit.' + instance, 'tts.userfiles/' + file.name, e.target.result, function () {
if (callback) callback(file.name);
});
};
// Read in the image file as a data URL.
reader.readAsArrayBuffer(file);
}
function handleFileSelect(evt) {
var files = evt.target.files; // FileList object
$('#drop_indcator').hide();
// files is a FileList of File objects. List some properties.
var count = 0;
for (var i = 0, f; f = files[i]; i++) {
if (f.size > 1024 * 1024) {
showMessage(_('File %s is too big. Maximum 1MB', escape(f.name)));
$('#files').val('');
return;
}
if (f.name === 'say.mp3') {
showMessage(_('Name say.mp3 is reserved'));
$('#files').val('');
return;
}
count++;
uploadFile(f, function (name) {
count--;
if (!count) {
// Read names of files for gong
socket.emit('readDir', 'sayit.' + instance, 'tts.userfiles', function (err, dir) {
var text = '<option value="">' + _('none') + '</option>';
if (dir) {
for (var i = 0; i < dir.length; i++) {
if (dir[i].isDir) continue;
text += '<option value="' + dir[i].file + '">' + dir[i].file + '</option>';
}
}
$('#announce').html(text).val(name).trigger('change');
$('#files').val('');
});
}
});
}
}
function handleFileSelectDrop(evt) {
$('#drop_indcator').hide();
evt.stopPropagation();
evt.preventDefault();
var files = evt.dataTransfer.files; // FileList object.
// files is a FileList of File objects. List some properties.
var output = [];
for (var i = 0, f; f = files[i]; i++) {
if (f.size > 1024 * 1024) {
showMessage(_('File %s is too big. Maximum 1MB', escape(f.name)));
return;
}
console.log(escape(f.name));
}
}
function handleDragOver(evt) {
evt.stopPropagation();
evt.preventDefault();
evt.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
$('#drop_indcator').show();
}
// the function loadSettings has to exist ...
function load(settings, onChange) {
if (!settings) return;
if (!settings.player) {
settings.player = 'mpg321';
}
gOnChange = onChange;
var $play = $('#play');
var $announce = $('#announce');
$play.button({icons: {primary: 'ui-icon-play'}, text: false}).css({width: 22, height: 22}).click(function () {
socket.emit('readFile', 'sayit.' + instance, 'tts.userfiles/' + $('#announce').val(), function (err, data) {
if (typeof AudioContext !== 'undefined') {
context = new AudioContext();
context.decodeAudioData(data, function(buffer) {
//console.log(buffer);
var source = context.createBufferSource(); // creates a sound source
source.buffer = buffer; // tell the source which sound to play
source.connect(context.destination); // connect the source to the context's destination (the speakers)
source.start(0);
}, function(err) {
console.log(err);
});
}
});
});
var $type = $('#type');
for (name in sayitOptions) {
if (sayitOptions.hasOwnProperty(name)) {
$type.append('<option value="' + name + '">' + sayitOptions[name].name + '</option>');
}
}
$('#search').button({
label: _('Browse')
}).click(function () {
fillGoogleHome();
});
getIsAdapterAlive(function (isAlive) {
if (isAlive || common.enabled) {
$('#search').button('enable');
if (settings.type === 'googleHome') {
fillGoogleHome();
}
} else {
$('#search').button('disable').title(_('Adapter must be enabled'));
}
});
var $engine = $('#engine');
for (var name in sayitEngines) {
if (sayitEngines.hasOwnProperty(name)) {
$engine.append('<option value="' + name + '">' + sayitEngines[name].name + '</option>');
}
}
for (var key in settings) {
if (settings.hasOwnProperty(key)) {
var $val = $('#' + key + '.value');
if ($val.attr('type') === 'checkbox') {
$val.prop('checked', settings[key]);
} else {
$val.val(settings[key]);
}
}
}
$('.value').on('change', function () {
var key = $(this).attr('id');
if (key === 'auth') {
if ($('#auth').prop('checked')) {
$('#secure').prop('checked', true);
}
} else
if (key === 'type' || key === 'engine') {
showHideSettings();
var type = $('#type').val();
if ((type === 'sonos') ||
(type === 'chromecast') ||
(type === 'mpd') ||
(type === 'googleHome')) {
checkWeb('#web');
$('#announce').val('').trigger('change');
showHideSettings();
} else {
showHideSettings();
}
} else
if (key === 'announce') {
showHideSettings();
if ($(this).val()) {
socket.emit('readFile', 'sayit.' + instance, 'tts.userfiles/' + $('#announce').val(), function (err, data) {
if (typeof AudioContext !== 'undefined') {
context = new AudioContext();
context.decodeAudioData(data, function (buffer) {
$('#annoDuration').val(Math.ceil(buffer.duration));
}, function (err) {
console.log(err);
});
}
});
}
}
if (key === 'web') {
checkWeb('#web');
}
onChange();
}).keyup(function() {
$(this).trigger('change');
});
if (!settings.engine) {
settings.engine = systemLang;
$engine.val(systemLang).trigger('change');
}
if (!settings.instance) {
settings.instance = 'FFFFFFFF';
$('#instance').val(settings.instance).trigger('change');
}
fillSonosDevices('#device', settings.device);
fillChromecastDevices('#cDevice', settings.cDevice);
fillWebServices('#web', settings.web, settings.type, settings.webServer);
fillMpdDevice('#mpd_device', settings.mpd_device);
if ((settings.type === 'sonos') ||
(settings.type === 'chromecast') ||
(settings.type === 'mpd') ||
(settings.type === 'googleHome')){
$('.announce').hide();
if (settings.announce) {
$announce.val('').trigger('change');
}
}
// Read names of files for gong
socket.emit('readDir', 'sayit.' + instance, 'tts.userfiles', function (err, dir) {
var text = '<option value="">' + _('none') + '</option>';
if (dir) {
for (var i = 0; i < dir.length; i++) {
if (dir[i].isDir) continue;
text += '<option value="' + dir[i].file + '">' + dir[i].file + '</option>';
}
}
$('#announce').html(text).val(settings.announce);
showHideSettings();
});
getAdapterInstances('cloud', function (list) {
if (list) {
var text = '';
for (var i = 0; i < list.length; i++) {
var id = list[i]._id.substring('system.adapter.'.length);
text += '<option value="' + id + '" ' + (id === settings.cloud ? 'selected' : '') + '>' + id + '</option>';
}
if (text) $('#cloud').html(text);
}
});
$('#voice').val(settings.voice);
var w = $engine.width();
$announce.css({width: w});
$type.css({width: w});
document.getElementById('files').addEventListener('change', handleFileSelect, false);
showHideSettings();
if (!$announce.val()) {
$play.button('disable');
}
if (typeof AudioContext === 'undefined') {
$play.hide();
}
var dropZone = document.getElementById('drop_zone');
if (dropZone) {
dropZone.addEventListener('dragover', handleDragOver, false);
dropZone.addEventListener('drop', handleFileSelect, false);
/*dropZone.addEventListener('dragend', function () {
$(this).css({background: 'white'});
console.log('dragend');
return false;
}, false);
dropZone.addEventListener('dragstart', function () {
console.log('dragstart');
}, false);
dropZone.addEventListener('dragleave', function () {
$(this).css({background: 'white'});
}, false);
dropZone.addEventListener('dragenter', function () {
$(this).css({background: 'gray'});
}, false);*/
}
onChange(false);
}
function save(callback) {
var obj = {};
$('.value').each(function () {
var $this = $(this);
if ($this.attr('type') === 'checkbox') {
obj[$this.attr('id')] = $this.prop('checked');
} else {
obj[$this.attr('id')] = $this.val();
}
});
if (obj.engine && sayitEngines[obj.engine].engine === 'yandex') {
if (!obj.key) {
showMessage(_('API Key is not set!'));
return;
}
} else
if (obj.engine && sayitEngines[obj.engine].engine === 'polly') {
if (!obj.accessKey || !obj.secretKey) {
showMessage(_('API Key is not set!'));
return;
}
if (!obj.region) {
showMessage(_('AWS Region is not set!'));
return;
}
}
if (obj.type === 'googleHome') {
obj.cache = false;
}
callback(obj);
}
</script>
</head>
<body>
<!-- you have to put your config page in a div with id adapter-container -->
<div id="adapter-container" ondragover="return false" ondrop="return false">
<table><tr><td><img src="sayit.png"></td><td><h3 class="translate">sayIt adapter settings</h3></td></tr></table>
<div>
<table>
<tr><td><label class="translate" for="type">Type:</label></td><td class="admin-icon" data-link="configuration"></td><td><select class="value" id="type"></select></td></tr>
<tr id="tr_engine" class="variable"><td style="width: 150px"><label class="translate" for="engine">Language:</label></td><td class="admin-icon" data-link="tts-engines"></td><td><select class="value" id="engine"></select></td></tr>
<tr class="system"><td><label for="player" class="translate">Linux player:</label></td><td class="admin-icon"></td><td>
<select class="value" id="player" >
<option value="mpg321">mpg321</option>
<option value="omxplayer">omxplayer</option>
</select><span class="translate" style="padding-left: 10px; font-size: 10px">Ignore for non linux OS</span>
</td>
</tr>
<tr class="system"><td><label class="translate" for="command">System command:</label></td><td class="admin-icon" data-link="system-command"></td><td><input class="value" id="command"/></td></tr>
<tr class="announceValue"><td><label class="translate" for="announce">Announce:</label></td><td class="admin-icon"></td><td><select class="value" id="announce"></select>&nbsp;<button id="play"></button></td></tr>
<tr class="announceValue"><td></td><td></td><td><input type="file" accept=".mp3,audio/*" id="files" name="files[]" multiple /></td></tr>
<tr class="announce"><td><label class="translate" for="annoTimeout">Announce timeout (sec):</label></td><td class="admin-icon"></td><td><input class="value" id="annoTimeout" size="8" /></td></tr>
<tr class="announce"><td><label class="translate" for="annoDuration">Announce length (sec):</label></td><td class="admin-icon"></td><td><input class="value" id="annoDuration" size="2" /></td></tr>
<tr class="announce"><td><label class="translate" for="annoVolume">Announce volume (%):</label></td><td class="admin-icon"></td><td><input class="value" id="annoVolume" size="3" /></td></tr>
<tr><td>&nbsp;</td><td></td><td>&nbsp;</td></tr>
<tr id="tr_cache" class="variable"><td><label class="translate" for="cache">Cache:</label></td><td class="admin-icon"></td> <td><input class="value" id="cache" type="checkbox" /></td></tr>
<tr id="tr_cacheExpiryDays" class="variable"><td><label class="translate" for="tr_cacheExpiryDays">Cache-Expiry:</label></td><td class="admin-icon"></td> <td><input class="value" id="cacheExpiryDays" size="5" /></td></tr>
<tr id="tr_server" class="variable"><td><label class="translate" for="server">Server:</label></td><td class="admin-icon"></td> <td><input class="value" id="server"/><select class="value googleHome" id="googleHome"><option value="" class="translate">none</option></select><button style="font-size: 10px; margin-left: 10px;" id="search" class="translate googleHome"></button></td></tr>
<tr id="tr_port" class="variable"><td><label class="translate" for="port">Port:</label></td><td class="admin-icon"></td> <td><input class="value" id="port" size="5" maxlength="5"/></td></tr>
<tr id="tr_user" class="variable"><td><label class="translate" for="user">User:</label></td><td class="admin-icon"></td> <td><input class="value" id="user" /></td></tr>
<tr id="tr_pass" class="variable"><td><label class="translate" for="pass">Password:</label></td><td class="admin-icon"></td><td><input class="value" id="pass" /></td></tr>
<tr id="tr_instance" class="variable"><td><label class="translate" for="instance">Browser instance:</label></td><td class="admin-icon"></td><td><input class="value" id="instance" /></td></tr>
<tr id="tr_device" class="variable"><td><label class="translate" for="device">Device:</label></td><td class="admin-icon"></td><td><select class="value" id="device"><option value="" class="translate">All</option></select></td></tr>
<tr id="tr_cDevice" class="variable"><td><label class="translate" for="cDevice">Device:</label></td><td class="admin-icon"></td><td><select class="value" id="cDevice"><option value="" class="translate">All</option></select></td></tr>
<tr id="tr_mpd_device" class="variable"><td><label class="translate" for="mpd_device">Device:</label></td><td class="admin-icon"></td><td><select class="value" id="mpd_device"><option value="" class="translate">All</option></select></td></tr>
<tr id="tr_web" class="variable"><td><label class="translate" for="web">Web instance:</label></td><td class="admin-icon"></td><td><select class="value" id="web"></select></td></tr>
<tr id="tr_webServer" class="variable"><td><label class="translate" for="webServer">Web server IP:</label></td><td class="admin-icon"></td><td><select class="value" id="webServer"></select></td></tr>
<tr id="tr_voice" class="engine" ><td><label class="translate" for="voice">Voice:</label></td><td class="admin-icon"></td><td><select class="value" id="voice"></select></td></tr>
<tr id="tr_key" class="engine" ><td><label class="translate" for="key">API Key:</label></td><td class="admin-icon"></td><td><input class="value" id="key" type="text"/></td></tr>
<tr id="tr_emotion" class="engine" ><td><label class="translate" for="emotion">Emotion:</label></td><td class="admin-icon"></td><td><select class="value" id="emotion"></select></td></tr>
<tr id="tr_drunk" class="engine" ><td><label class="translate" for="drunk">Drunk:</label></td><td class="admin-icon"></td><td><input type="checkbox" class="value" id="drunk"/></td></tr>
<tr id="tr_ill" class="engine" ><td><label class="translate" for="ill">Ill:</label></td><td class="admin-icon"></td><td><input type="checkbox" class="value" id="ill"/></td></tr>
<tr id="tr_robot" class="engine" ><td><label class="translate" for="robot">Robot:</label></td><td class="admin-icon"></td><td><input type="checkbox" class="value" id="robot"/></td></tr>
<tr id="tr_accessKey" class="engine" ><td><label class="translate" for="accessKey">Access Key:</label></td><td class="admin-icon"></td><td><input class="value" id="accessKey" type="text"/></td></tr>
<tr id="tr_secretKey" class="engine" ><td><label class="translate" for="secretKey">Secret Key:</label></td><td class="admin-icon"></td><td><input class="value" id="secretKey" type="password"/></td></tr>
<tr id="tr_region" class="engine" ><td><label class="translate" for="region">AWS Region:</label></td><td class="admin-icon"></td><td><input class="value" id="region" type="text"/></td></tr>
<tr id="tr_cloud" class="engine" ><td><label class="translate" for="cloud">Cloud instance:</label></td><td class="admin-icon"></td><td><select class="value" id="cloud"><option value="" class="translate">Install first cloud adapter</option></select></td></tr>
</table>
</div>
<div id="drop_zone" style="display: none" class="translate">place here</div>
</div>
</body>
</html>

811
admin/index_m.html Normal file
View File

@@ -0,0 +1,811 @@
<html>
<head>
<!-- Materialze style -->
<link rel="stylesheet" type="text/css" href="../../css/adapter.css"/>
<link rel="stylesheet" type="text/css" href="../../lib/css/materialize.css">
<script type="text/javascript" src="../../lib/js/jquery-3.2.1.min.js"></script>
<script type="text/javascript" src="../../socket.io/socket.io.js"></script>
<script type="text/javascript" src="engines.js"></script>
<script type="text/javascript" src="../../js/translate.js"></script>
<script type="text/javascript" src="../../lib/js/materialize.js"></script>
<script type="text/javascript" src="../../js/adapter-settings.js"></script>
<script type="text/javascript" src="words.js"></script>
<style>
#drop_zone {
border: 2px dashed #bbb;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
padding: 25px;
text-align: center;
font-size: 20pt;
font-weight: bold;
font-family: 'Arial';
color: #bbb;
width: 90%;
height: 60px;
}
.error {
border: 2px solid red;
}
</style>
<script type="text/javascript">
var webServers = null;
var gOnChange;
function showHideSettings() {
$('.variable').hide();
var type = $('#type').val();
for (var j = 0; j < sayitOptions[type].params.length; j++) {
$('#tr_' + sayitOptions[type].params[j]).show();
}
if (type === 'googleHome') {
$('.announceValue').hide();
$('.announce').hide();
$('.system').hide();
$('.googleHome').show();
getIsAdapterAlive(function (isAlive) {
if (isAlive || common.enabled) {
$('#search').removeClass('disabled');
fillGoogleHome();
}
});
} else {
$('.googleHome').hide();
$('.announceValue').show();
$('.announce').show();
}
$('.engine').hide();
var engine = $('#engine').val();
if (!sayitEngines[engine] || sayitOptions[type].params.indexOf('engine') === -1) return;
for (var i = 0; i < sayitEngines[engine].params.length; i++) {
$('#tr_' + sayitEngines[engine].params[i]).show();
if (sayitEngines[engine][sayitEngines[engine].params[i]]) {
var arr = sayitEngines[engine][sayitEngines[engine].params[i]];
var param = sayitEngines[engine].params[i];
var $val = $('#' + param);
if (arr.length) {
var val = $val.val();
var text = '';
for (var p = 0; p < arr.length; p++) {
text += '<option value="' + arr[p] + '">' + arr[p] + '</option>';
}
$val.html(text);
if (val) {
$val.val(val);
} else {
$val.val(arr[0]);
}
} else {
$val.html('');
}
}
}
if (type !== 'system') {
$('.system').hide();
} else {
$('.system').show();
}
if (!$('#announce').val()) {
$('.announce').hide();
$('#play').addClass('disabled');
} else {
$('.announce').show();
$('#play').removeClass('disabled');
}
}
function fillSonosDevices(elem, current) {
socket.emit('getObjectView', 'system', 'channel', {startkey: 'sonos.', endkey: 'sonos.\u9999'}, function (err, res) {
if (!err && res) {
for (var i = 0; i < res.rows.length; i++) {
$(elem).append('<option value="' + res.rows[i].id + '">' + res.rows[i].id + '</option>');
}
$(elem).select();
}
$(elem).val(current);
});
}
function fillMpdDevice(elem, current) {
socket.emit('getObjectView', 'system', 'instance', {startkey: 'system.adapter.mpd.', endkey: 'system.adapter.mpd.\u9999'}, function (err, res) {
if (!err && res) {
for (var i = 0; i < res.rows.length; i++) {
var n = res.rows[i].id.replace('system.adapter.', '');
$(elem).append('<option value="' + n + '">' + n + '</option>');
}
$(elem).select();
}
$(elem).val(current);
});
}
function fillChromecastDevices(elem, current) {
socket.emit('getObjectView', 'system', 'device', {startkey: 'chromecast.', endkey: 'chromecast.\u9999'}, function (err, res) {
if (!err && res) {
for (var i = 0; i < res.rows.length; i++) {
$(elem).append('<option value="' + res.rows[i].id + '">' + res.rows[i].id + '</option>');
}
}
$(elem).select();
$(elem).val(current);
});
}
function fillGoogleHome() {
$('#search').addClass('disabled');
sendTo(null, 'browseChromecast', null, function (list) {
var text = '<option value="">' + _('select') + '</option>';
if (list) {
for (var i = 0; i < list.length; i++) {
text += '<option value="' + list[i].ip + '">' + list[i].name + '[' + list[i].ip + ']</option>';
}
$('#googleHome').html(text).prop('disabled', false).off('change').on('change', function () {
$('#server').val($(this).val()).trigger('change');
});
} else {
$('#googleHome').html('<option value="">' + _('error') + '</option>').prop('disabled', true);
}
$('#search').removeClass('disabled');
$('#googleHome').select();
});
}
function ip2hex(ip) {
var octets = ip.split('.');
if (octets.length !== 4) {
return 0;
}
var result = 0;
for (var i = 0; i < octets.length; ++i) {
var octet = parseInt(octets[i], 10);
if (Number.isNaN(octet) || octet < 0 || octet > 255) {
throw new Error("Each octet must be between 0 and 255");
}
result |= octet << ((octets.length - i) * 8);
}
return result;
}
function checkWeb(elem, current) {
var web = $('#web').val();
for (var i = 0; i < webServers.length; i++) {
if (webServers[i].id === 'system.adapter.' + web) {
if (webServers[i].value.native.auth) {
showMessage(_('Cannot use web server with authentication'), null, 'info');
}
if (webServers[i].value.native.bind === 'localhost' || webServers[i].value.native.bind === '127.0.0.1' || webServers[i].value.native.bind === '::1') {
showMessage(_('Cannot use web server only on localhost'), null, 'info');
}
if (webServers[i].value.native.bind === '0.0.0.0') {
$('#tr_webServer').show();
var $webServer = $('#webServer');
$webServer.html('');
// read all ipv4 addresses of host
socket.emit('getObject', 'system.host.' + webServers[i].value.common.host, function (err, obj) {
var text = '';
if (!err && obj && obj.native) {
for (var iface in obj.native.hardware.networkInterfaces) {
if (obj.native.hardware.networkInterfaces.hasOwnProperty(iface)) {
for (var i = 0; i < obj.native.hardware.networkInterfaces[iface].length; i++) {
if (obj.native.hardware.networkInterfaces[iface][i].family === 'IPv4' && !obj.native.hardware.networkInterfaces[iface][i].internal) {
text += '<option value="' + obj.native.hardware.networkInterfaces[iface][i].address + '" data-mask="' + obj.native.hardware.networkInterfaces[iface][i].netmask + '">[IPv4] ' + obj.native.hardware.networkInterfaces[iface][i].address + ' - ' + iface + '</option>';
}
}
}
}
}
$webServer.html(text);
if (current) {
$webServer.val(current);
}
$webServer.select();
$webServer.off('change').on('change', function () {
gOnChange();
var ip = $(this).val();
var netmask = $(this).find('option[value="' + ip + '"]').data('mask');
var server;
var type = $('#type').val();
if (type === 'googleHome') {
server = $('#server').val();
} else if (type === 'sonos') {
server = $('#device').val().split('.')[3].replace('_', '.').replace('_', '.').replace('_', '.');
} else if (type === 'chromecast') {
server = $('#cDevice').val();
} else if (type === 'mpd') {
server = $('#mpd_device').val();
}
if (server && ip && netmask && server.indexOf(':') === -1 && ip.indexOf(':') === -1) {
ip = ip2hex(ip);
netmask = ip2hex(netmask);
server = ip2hex(server);
if ((ip & netmask) !== (server & netmask)) {
$webServer.addClass('error').attr('title', _('IP not accessible for server'));
} else {
$webServer.removeClass('error').attr('title', '');
}
} else {
$webServer.removeClass('error');
}
});
});
} else if (webServers[i].value.native.bind === '::') {
// read all ipv6 addresses of host
socket.emit('getObject', 'system.host.' + webServers[i].value.common.host, function (err, obj) {
if (!err && obj && obj.native) {
for (var iface in obj.native.hardware.networkInterfaces) {
if (obj.native.hardware.networkInterfaces.hasOwnProperty(iface)) {
for (var i = 0; i < obj.native.hardware.networkInterfaces[iface].length; i++) {
if (obj.native.hardware.networkInterfaces[iface][i].family === 'IPv6' && !obj.native.hardware.networkInterfaces[iface][i].internal) {
$('#webServer').append('<option value="' + obj.native.hardware.networkInterfaces[iface][i].address + '">[IPv6] ' + obj.native.hardware.networkInterfaces[iface][i].address + ' - ' + iface + '</option>');
}
}
}
}
}
if (current) {
$('#webServer').val(current);
}
});
$('#webServer').select();
} else {
$('#tr_webServer').hide();
}
}
}
}
function fillWebServices(elem, current, type, webServer) {
socket.emit('getObjectView', 'system', 'instance', {startkey: 'system.adapter.web.', endkey: 'system.adapter.web.\u9999'}, function (err, res) {
if (!err && res) {
webServers = res.rows;
for (var i = 0; i < res.rows.length; i++) {
var n = res.rows[i].id.replace('system.adapter.', '');
var auth = res.rows[i].value.native.auth ? 'data-auth="true"' : '';
$(elem).append('<option value="' + n + '" ' + auth + '>' + n + '</option>');
}
}
$(elem).val(current);
if ((type === 'sonos') ||
(type === 'chromecast') ||
(type === 'googleHome')) {
checkWeb('#web', webServer);
}
$(elem).select();
});
}
function uploadFile(file, callback) {
var reader = new FileReader();
// Closure to capture the file information.
reader.onload = function(e) {
socket.emit('writeFile', 'sayit.' + instance, 'tts.userfiles/' + file.name, e.target.result, function () {
if (callback) callback(file.name);
});
};
// Read in the image file as a data URL.
reader.readAsArrayBuffer(file);
}
function handleFileSelect(evt) {
var files = evt.target.files; // FileList object
$('#drop_indcator').hide();
// files is a FileList of File objects. List some properties.
var count = 0;
for (var i = 0, f; f = files[i]; i++) {
if (f.size > 1024 * 1024) {
showMessage(_('File %s is too big. Maximum 1MB', escape(f.name)));
$('#files').val('');
return;
}
if (f.name === 'say.mp3') {
showMessage(_('Name say.mp3 is reserved'));
$('#files').val('');
return;
}
count++;
uploadFile(f, function (name) {
count--;
if (!count) {
// Read names of files for gong
socket.emit('readDir', 'sayit.' + instance, 'tts.userfiles', function (err, dir) {
var text = '<option value="">' + _('none') + '</option>';
if (dir) {
for (var i = 0; i < dir.length; i++) {
if (dir[i].isDir) continue;
text += '<option value="' + dir[i].file + '">' + dir[i].file + '</option>';
}
}
$('#announce').html(text).val(name).trigger('change');
$('#announce').select();
$('#files').val('');
});
}
});
}
}
function handleFileSelectDrop(evt) {
$('#drop_indcator').hide();
evt.stopPropagation();
evt.preventDefault();
var files = evt.dataTransfer.files; // FileList object.
// files is a FileList of File objects. List some properties.
var output = [];
for (var i = 0, f; f = files[i]; i++) {
if (f.size > 1024 * 1024) {
showMessage(_('File %s is too big. Maximum 1MB', escape(f.name)));
return;
}
console.log(escape(f.name));
}
}
function handleDragOver(evt) {
evt.stopPropagation();
evt.preventDefault();
evt.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
$('#drop_indcator').show();
}
// the function loadSettings has to exist ...
function load(settings, onChange) {
if (!settings) return;
if (!settings.player) {
settings.player = 'mpg321';
}
gOnChange = onChange;
$('#play').on('click', function () {
socket.emit('readFile', 'sayit.' + instance, 'tts.userfiles/' + $('#announce').val(), function (err, data) {
if (typeof AudioContext !== 'undefined') {
context = new AudioContext();
context.decodeAudioData(data, function(buffer) {
//console.log(buffer);
var source = context.createBufferSource(); // creates a sound source
source.buffer = buffer; // tell the source which sound to play
source.connect(context.destination); // connect the source to the context's destination (the speakers)
source.start(0);
}, function(err) {
console.log(err);
});
}
});
});
var $t = $('#type');
for (name in sayitOptions) {
if (sayitOptions.hasOwnProperty(name)) {
$t.append('<option value="' + name + '">' + sayitOptions[name].name + '</option>');
}
}
$('#search').on('click', function () {
fillGoogleHome();
});
getIsAdapterAlive(function (isAlive) {
if (isAlive || common.enabled) {
$('#search').removeClass('disabled');
if (settings.type === 'googleHome') {
fillGoogleHome();
}
} else {
$('#search').addClass('disabled').attr('title', _('Adapter must be enabled'));
}
});
var $e = $('#engine');
for (var name in sayitEngines) {
if (sayitEngines.hasOwnProperty(name)) {
$e.append('<option value="' + name + '">' + sayitEngines[name].name + '</option>');
}
}
for (var key in settings) {
if (settings.hasOwnProperty(key)) {
var $val = $('#' + key + '.value');
if ($val.attr('type') === 'checkbox') {
$val.prop('checked', settings[key]);
} else {
$val.val(settings[key]);
}
}
}
$('.value').on('change', function () {
var key = $(this).attr('id');
if (key === 'auth') {
if ($('#auth').prop('checked')) {
$('#secure').prop('checked', true);
}
} else
if (key === 'type' || key === 'engine') {
showHideSettings();
var type = $('#type').val();
if ((type === 'sonos') ||
(type === 'chromecast') ||
(type === 'mpd') ||
(type === 'googleHome')) {
checkWeb('#web');
$('#announce').val('').trigger('change');
showHideSettings();
} else {
showHideSettings();
}
} else
if (key === 'announce') {
showHideSettings();
if ($(this).val()) {
socket.emit('readFile', 'sayit.' + instance, 'tts.userfiles/' + $('#announce').val(), function (err, data) {
if (typeof AudioContext !== 'undefined') {
context = new AudioContext();
context.decodeAudioData(data, function (buffer) {
$('#annoDuration').val(Math.ceil(buffer.duration));
}, function (err) {
console.log(err);
});
}
});
}
}
if (key === 'web') {
checkWeb('#web');
}
onChange();
}).keyup(function() {
$(this).trigger('change');
});
if (!settings.engine) {
settings.engine = systemLang;
$('#engine').val(systemLang).trigger('change');
}
if (!settings.instance) {
settings.instance = 'FFFFFFFF';
$('#instance').val(settings.instance).trigger('change');
}
fillSonosDevices('#device', settings.device);
fillChromecastDevices('#cDevice', settings.cDevice);
fillWebServices('#web', settings.web, settings.type, settings.webServer);
fillMpdDevice('#mpd_device', settings.mpd_device);
if ((settings.type === 'sonos') ||
(settings.type === 'chromecast') ||
(settings.type === 'mpd') ||
(settings.type === 'googleHome')){
$('.announce').hide();
if (settings.announce) {
$('#announce').val('').trigger('change');
}
}
// Read names of files for gong
socket.emit('readDir', 'sayit.' + instance, 'tts.userfiles', function (err, dir) {
var text = '<option value="">' + _('none') + '</option>';
if (dir) {
for (var i = 0; i < dir.length; i++) {
if (dir[i].isDir) continue;
text += '<option value="' + dir[i].file + '">' + dir[i].file + '</option>';
}
}
$('#announce').html(text).val(settings.announce);
showHideSettings();
});
getAdapterInstances('cloud', function (list) {
if (list) {
var text = '';
for (var i = 0; i < list.length; i++) {
var id = list[i]._id.substring('system.adapter.'.length);
text += '<option value="' + id + '" ' + (id === settings.cloud ? 'selected' : '') + '>' + id + '</option>';
}
if (text) $('#cloud').html(text);
}
});
$('#voice').val(settings.voice);
var w = $('#engine').width();
$('#announce').css({width: w});
$('#type').css({width: w});
document.getElementById('files').addEventListener('change', handleFileSelect, false);
showHideSettings();
if (!$('#announce').val()) $('#play').addClass('disabled');
if (typeof AudioContext === 'undefined') $('#play').hide();
var dropZone = document.getElementById('drop_zone');
if (dropZone) {
dropZone.addEventListener('dragover', handleDragOver, false);
dropZone.addEventListener('drop', handleFileSelect, false);
/*dropZone.addEventListener('dragend', function () {
$(this).css({background: 'white'});
console.log('dragend');
return false;
}, false);
dropZone.addEventListener('dragstart', function () {
console.log('dragstart');
}, false);
dropZone.addEventListener('dragleave', function () {
$(this).css({background: 'white'});
}, false);
dropZone.addEventListener('dragenter', function () {
$(this).css({background: 'gray'});
}, false);*/
}
onChange(false);
}
function save(callback) {
var obj = {};
$('.value').each(function () {
var $this = $(this);
if ($this.attr('type') === 'checkbox') {
obj[$this.attr('id')] = $this.prop('checked');
} else {
obj[$this.attr('id')] = $this.val();
}
});
if (obj.engine && sayitEngines[obj.engine].engine === 'yandex') {
if (!obj.key) {
showMessage(_('API Key is not set!'));
return;
}
} else
if (obj.engine && sayitEngines[obj.engine].engine === 'polly') {
if (!obj.accessKey || !obj.secretKey) {
showMessage(_('API Key is not set!'));
return;
}
if (!obj.region) {
showMessage(_('AWS Region is not set!'));
return;
}
}
if (obj.type === 'googleHome') {
obj.cache = false;
}
callback(obj);
}
</script>
</head>
<body>
<div class="m adapter-container" ondragover="return false" ondrop="return false">
<div class="row">
<div class="col s12">
<div class="row">
<div class="col s6">
<img src="sayit.png" class="logo">
</div>
</div>
<div class="row">
<div class="input-field col s6 m4">
<select class="value" id="type"></select>
<label class="translate" for="type">Type:</label>
</div>
<div class="input-field col s6 m4" id="tr_engine" >
<select class="value" id="engine"></select>
<label class="translate" for="engine">Language:</label>
</div>
</div>
<div class="row system">
<div class="input-field col s6 m4">
<select class="value" id="player" >
<option value="mpg321">mpg321</option>
<option value="omxplayer">omxplayer</option>
</select>
<label for="player"><span class="translate">Linux player:</span><span class="translate" style="padding-left: 10px; font-size: 10px">Ignore for non linux OS</span></label>
</div>
<div class="input-field col s6 m4 l2">
<input class="value" id="command"/>
<label class="translate" for="command">System command:</label>
</div>
</div>
<div class="row announceValue">
<div class="input-field col s6 m4">
<select class="value" id="announce"></select>
<label class="translate" for="announce">Announce:</label>
&nbsp;<a class="waves-effect waves-light btn translate" id="play">play</a>
</div>
<div class="file-field input-field col s6 m4">
<div class="btn">
<span class="translate">File</span>
<input type="file" accept=".mp3,audio/*" id="files" name="files[]" multiple />
</div>
<div class="file-path-wrapper">
<input class="file-path validate" type="text">
</div>
</div>
</div>
<div class="row announce">
<div class="input-field col s4 m4 l2">
<input class="value" id="annoTimeout" type="number" min="0" max="1000" />
<label class="translate" for="annoTimeout">Announce timeout (sec):</label>
</div>
<div class="input-field col s4 m4 l2">
<input class="value" id="annoDuration" type="number" min="0" max="99"/>
<label class="translate" for="annoDuration">Announce length (sec):</label>
</div>
<div class="input-field col s4 m4 l2">
<input class="value" id="annoVolume" type="number" min="0" max="100"/>
<label class="translate" for="annoVolume">Announce volume (%):</label>
</div>
</div>
<div class="row">
</div>
<div class="row variable">
<div class="input-field col s6 m4" id="tr_cache">
<input class="value" id="cache" type="checkbox" />
<label class="translate" for="cache">Cache:</label>
</div>
<div class="input-field col s6 m4" id="tr_cacheExpiryDays" >
<input class="value" id="cacheExpiryDays" size="5" />
<label class="translate" for="tr_cacheExpiryDays">Cache-Expiry:</label>
</div>
</div>
<div id="tr_server" class="row variable">
<div class="input-field col s6 m4 l2">
<input class="value" id="server"/>
<label class="translate" for="server">Server:</label>
<select class="value googleHome" id="googleHome">
<option value="" class="translate">none</option>
</select>
<a class="waves-effect waves-light btn translate googleHome" style="font-size: 10px; margin-left: 10px;" id="search">Browse</a>
</div>
</div>
<div id="tr_port" class="row variable">
<div class="input-field col s6 m4 l2">
<input class="value" id="port" size="5" maxlength="5"/>
<label class="translate" for="port">Port:</label>
</div>
</div>
<div class="row variable">
<div id="tr_user" class="input-field col s6 m4 l2">
<input class="value" id="user" />
<label class="translate" for="user">User:</label>
</div>
<div id="tr_pass"class="input-field col s6 m4 l2">
<input class="value" id="pass" />
<label class="translate" for="pass">Password:</label>
</div>
</div>
<div id="tr_instance" class="row variable">
<div class="input-field col s6 m4 l2">
<label class="translate" for="instance">Browser instance:</label>
<input class="value" id="instance" />
</div>
</div>
<div id="tr_device" class="row variable">
<div class="input-field col s6 m4 l2">
<select class="value" id="device">
<option value="" class="translate">All</option>
</select>
<label class="translate" for="device">Device:</label>
</div>
</div>
<div id="tr_cDevice" class="row variable">
<div class="input-field col s6 m4 l2">
<select class="value" id="cDevice">
<option value="" class="translate">All</option>
</select>
<label class="translate" for="cDevice">Device:</label>
</div>
</div>
<div id="tr_mpd_device" class="row variable">
<div class="input-field col s6 m4 l2">
<select class="value" id="mpd_device">
<option value="" class="translate">All</option>
</select>
<label class="translate" for="mpd_device">Device:</label>
</div>
</div>
<div id="tr_web" class="row variable">
<div class="input-field col s6 m4 l2">
<select class="value" id="web"></select>
<label class="translate" for="web">Web instance:</label>
</div>
</div>
<div id="tr_webServer" class="row variable">
<div class="input-field col s6 m4 l2">
<select class="value" id="webServer"></select>
<label class="translate" for="webServer">Web server IP:</label>
</div>
</div>
<div id="tr_voice" class="row engine" >
<div class="input-field col s6 m4 l2">
<select class="value" id="voice"></select>
<label class="translate" for="voice">Voice:</label>
</div>
</div>
<div id="tr_key" class="row engine" >
<div class="input-field col s6 m4 l2">
<input class="value" id="key" type="text"/>
<label class="translate" for="key">API Key:</label>
</div>
</div>
<div id="tr_emotion" class="row engine" >
<div class="input-field col s6 m4 l2">
<select class="value" id="emotion"></select>
<label class="translate" for="emotion">Emotion:</label>
</div>
</div>
<div id="tr_drunk" class="row engine" >
<div class="input-field col s6 m4 l2">
<input type="checkbox" class="value" id="drunk"/>
<label class="translate" for="drunk">Drunk:</label>
</div>
</div>
<div id="tr_ill" class="row engine" >
<div class="input-field col s6 m4 l2">
<input type="checkbox" class="value" id="ill"/>
<label class="translate" for="ill">Ill:</label>
</div>
</div>
<div id="tr_robot" class="row engine" >
<div class="input-field col s6 m4 l2">
<input type="checkbox" class="value" id="robot"/>
<label class="translate" for="robot">Robot:</label>
</div>
</div>
<div id="tr_accessKey" class="row engine" >
<div class="input-field col s6 m4 l2">
<label class="translate" for="accessKey">Access Key:</label>
<input class="value" id="accessKey" type="text"/>
</div>
</div>
<div id="tr_secretKey" class="row engine" >
<div class="input-field col s6 m4 l2">
<label class="translate" for="secretKey">Secret Key:</label>
<input class="value" id="secretKey" type="password"/>
</div>
</div>
<div id="tr_region" class="row engine" >
<div class="input-field col s6 m4 l2">
<input class="value" id="region" type="text"/>
<label class="translate" for="region">AWS Region:</label>
</div>
</div>
<div id="tr_cloud" class="row engine" >
<div class="input-field col s6 m4 l2">
<select class="value" id="cloud">
<option value="" class="translate">Install first cloud adapter</option>
</select>
<label class="translate" for="cloud">Cloud instance:</label>
</div>
</div>
</div>
</div>
<div id="drop_zone" style="display: none" class="translate">place here</div>
</div>
</body>
</html>

BIN
admin/sayit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

50
admin/words.js Normal file
View File

@@ -0,0 +1,50 @@
// DO NOT EDIT THIS FILE!!! IT WILL BE AUTOMATICALLY GENERATED FROM src/i18n
/*global systemDictionary:true */
'use strict';
systemDictionary = {
"sayIt adapter settings": { "en": "sayIt adapter settings", "de": "sayIt Einstellungen", "ru": "sayIt Настройки", "pt": "configurações do adaptador sayIt", "nl": "sayIt adapter-instellingen", "fr": "sayIt paramètres de l'adaptateur", "it": "impostazioni dell'adattatore sayIt", "es": "sayIt configuración del adaptador", "pl": "sayTo ustawienia adaptera"},
"Language:": { "en": "Language", "de": "Sprache", "ru": "Язык", "pt": "Língua", "nl": "Taal", "fr": "La langue", "it": "linguaggio", "es": "Idioma", "pl": "Język"},
"Type:": { "en": "Output in", "de": "Ausgabe durch", "ru": "Вывод звука", "pt": "Saída em", "nl": "Uitgang in", "fr": "Sortie dans", "it": "Uscita in", "es": "Salida en", "pl": "Wyjście w"},
"Cache:": { "en": "Cache received audio files", "de": "Cache benutzen", "ru": "Кэшировать полученные аудио-файлы", "pt": "Cache recebido arquivos de áudio", "nl": "Cache ontvangen audiobestanden", "fr": "Cache les fichiers audio reçus", "it": "La cache ha ricevuto file audio", "es": "Caché recibió archivos de audio", "pl": "Pamięć podręczna otrzymała pliki audio"},
"Server:": { "en": "Server", "de": "Server", "ru": "Сервер", "pt": "Servidor", "nl": "Server", "fr": "Serveur", "it": "server", "es": "Servidor", "pl": "serwer"},
"Port:": { "en": "Port", "de": "Port", "ru": "Порт", "pt": "Porta", "nl": "Haven", "fr": "Port", "it": "Porta", "es": "Puerto", "pl": "Port"},
"User:": { "en": "User", "de": "Anwender", "ru": "Пользователь", "pt": "Do utilizador", "nl": "Gebruiker", "fr": "Utilisateur", "it": "Utente", "es": "Usuario", "pl": "Użytkownik"},
"Password:": { "en": "Password", "de": "Kennwort", "ru": "Пароль", "pt": "Senha", "nl": "Wachtwoord", "fr": "Mot de passe", "it": "Parola d'ordine", "es": "Contraseña", "pl": "Hasło"},
"Browser instance:": { "en": "Browser instance", "de": "Browser-Instanz", "ru": "ID броузера", "pt": "Instância do navegador", "nl": "Browserinstantie", "fr": "Instance du navigateur", "it": "Istanza del browser", "es": "Instancia del navegador", "pl": "Browser instance"},
"Device:": { "en": "Device", "de": "Gerät", "ru": "Устройство", "pt": "Dispositivo", "nl": "Apparaat", "fr": "Dispositif", "it": "Dispositivo", "es": "Dispositivo", "pl": "Urządzenie"},
"Web server IP:": { "en": "Web server IP", "de": "Web server IP", "ru": "IP Веб сервера", "pt": "IP do servidor da Web", "nl": "Webserver IP", "fr": "Adresse IP du serveur Web", "it": "IP del server Web", "es": "Servidor web IP", "pl": "Adres IP serwera WWW"},
"All": { "en": "All", "de": "Alle", "ru": "Все", "pt": "Todos", "nl": "Alle", "fr": "Tout", "it": "Tutti", "es": "Todas", "pl": "Wszystko"},
"Web instance:": { "en": "Web instance", "de": "Web instance", "ru": "Web instance", "pt": "Instância da Web", "nl": "Web-instantie", "fr": "Instance Web", "it": "Istanza Web", "es": "Instancia web", "pl": "Instancja internetowa"},
"Voice:": { "en": "Voice", "de": "Stimme", "ru": "Голос", "pt": "Voz", "nl": "Stem", "fr": "Voix", "it": "Voce", "es": "Voz", "pl": "Głos"},
"API Key:": { "en": "API Key", "de": "API Key", "ru": "API Key", "pt": "Chave API", "nl": "API sleutel", "fr": "clé API", "it": "Chiave API", "es": "Clave API", "pl": "Klucz API"},
"Emotion:": { "en": "Emotion", "de": "Emotion", "ru": "Емоциональный окрас", "pt": "Emoção", "nl": "Emotie", "fr": "Émotion", "it": "Emozione", "es": "Emoción", "pl": "Emocja"},
"Drunk:": { "en": "Drunk", "de": "Betrunken", "ru": "Пьяный голос", "pt": "Bêbado", "nl": "Dronken", "fr": "Ivre", "it": "Ubriaco", "es": "Borracho", "pl": "Pijany"},
"Ill:": { "en": "Ill", "de": "Krank", "ru": "Больной голос", "pt": "Eu vou", "nl": "Ziek", "fr": "Mauvais", "it": "Malato", "es": "Enfermo", "pl": "Chory"},
"Robot:": { "en": "Robot", "de": "Roboter", "ru": "Голос робота", "pt": "Robô", "nl": "Robot", "fr": "Robot", "it": "Robot", "es": "Robot", "pl": "Robot"},
"Announce:": { "en": "Announce", "de": "Ankündigung", "ru": "Извещение", "pt": "Anunciar", "nl": "aankondigen", "fr": "Annoncer", "it": "Annunciare", "es": "Anunciar", "pl": "Ogłosić"},
"place here": { "en": "Place files here", "de": "Dateien hier ablegen", "ru": "Перетащите файлы сюда", "pt": "Coloque arquivos aqui", "nl": "Plaats hier bestanden", "fr": "Placez les fichiers ici", "it": "Inserisci i file qui", "es": "Coloque los archivos aquí", "pl": "Umieść pliki tutaj"},
"Announce timeout (sec):": { "en": "Announce timeout (sec)", "de": "Ankündigungstimeout (Sek)", "ru": "Таймаут извещения (сек)", "pt": "Anular o tempo limite (seg)", "nl": "Announce time-out (sec)", "fr": "Annoncer le délai (sec)", "it": "Annuncia timeout (sec)", "es": "Anunciar tiempo de espera (sec)", "pl": "Ogłaszaj limit czasu (sek)"},
"Announce length (sec):": { "en": "Announce length (sec)", "de": "Ankündigunglänge (Sek)", "ru": "Длительность извещения (сек)", "pt": "Anunciar o comprimento (seg)", "nl": "Kondig lengte (sec)", "fr": "Annoncer la longueur (sec)", "it": "Annunciare la lunghezza (sec)", "es": "Anunciar longitud (sec)", "pl": "Ogłaszaj długość (s)"},
"API Key is not set!": { "en": "API Key is not set!", "de": "API Key ist leer!", "ru": "API Key отсутствует!", "pt": "A chave da API não está definida!", "nl": "API-sleutel is niet ingesteld!", "fr": "La clé de l'API n'est pas définie!", "it": "Chiave API non impostata!", "es": "¡La clave API no está configurada!", "pl": "Klucz API nie jest ustawiony!"},
"System command:": { "en": "System command", "de": "Systemkommando", "ru": "Системный вызов", "pt": "Comando do sistema", "nl": "Systeemopdracht", "fr": "Commande système", "it": "Comando di sistema", "es": "Comando del sistema", "pl": "Polecenie systemowe"},
"Announce volume (%):": { "en": "Announce volume(%)", "de": "Ankündigungslautstärke(%)", "ru": "Громкость извещения(%)", "pt": "Anunciar volume (%)", "nl": "Volume aankondigen (%)", "fr": "Annoncer le volume (%)", "it": "Annuncia volume (%)", "es": "Anunciar volumen (%)", "pl": "Ogłaszaj objętość (%)"},
"Linux player:": { "en": "Linux player", "de": "Linux-Player", "ru": "Linux плейер", "pt": "Jogador Linux", "nl": "Linux-speler", "fr": "Lecteur Linux", "it": "Lettore Linux", "es": "Reproductor de Linux", "pl": "Odtwarzacz Linux"},
"Ignore for non linux OS": { "en": "Ignore for non linux OS", "de": "Ignorieren für nicht linux OS", "ru": "Игнорировать для не linux OS", "pt": "Ignorar para SO não linux", "nl": "Negeren voor niet-Linux OS", "fr": "Ignorer pour les systèmes d'exploitation autres que Linux", "it": "Ignora per sistema operativo non Linux", "es": "Ignorar para el sistema operativo no Linux", "pl": "Zignoruj dla systemu operacyjnego non linux"},
"Access Key:": { "en": "Access Key", "de": "Access Key", "ru": "Access Key", "pt": "Chave de acesso", "nl": "Toegangssleutel", "fr": "Clef d'accès", "it": "Chiave di accesso", "es": "Llave de acceso", "pl": "Klucz dostępu"},
"Secret Key:": { "en": "Secret Key", "de": "Secret Key", "ru": "Secret Key", "pt": "Chave secreta", "nl": "Geheime sleutel", "fr": "Clef secrète", "it": "Chiave segreta", "es": "Llave secreta", "pl": "Sekretny klucz"},
"AWS Region:": { "en": "AWS Region", "de": "AWS Region", "ru": "AWS регион", "pt": "Região da AWS", "nl": "AWS-regio", "fr": "Région AWS", "it": "Regione AWS", "es": "Región de AWS", "pl": "Region AWS"},
"Cloud instance:": { "en": "Cloud instance", "de": "Cloud-Instanz", "ru": "Cloud драйвер", "pt": "Instância de nuvem", "nl": "Cloud instantie", "fr": "Instance de cloud", "it": "Istanza Cloud", "es": "Instancia de la nube", "pl": "Instancja chmury"},
"Cache-Expiry:": { "en": "Cache-Expiry", "de": "Cache-Ablauf", "ru": "Срок хранения кэш", "pt": "Cache-Expiração", "nl": "Cache-Vervaldatum", "fr": "Cache-Expiration", "it": "Cache-scadenza", "es": "Caché-expiración", "pl": "Cache-Expiry"},
"Browse": { "en": "Browse", "de": "Suchen", "ru": "Искать", "pt": "Squeaky toy", "nl": "Blader", "fr": "Feuilleter", "it": "Navigare", "es": "Vistazo", "pl": "Paść się"},
"select": { "en": "select", "de": "auswählen", "ru": "выбрать", "pt": "selecione", "nl": "kiezen", "fr": "sélectionner", "it": "selezionare", "es": "seleccionar", "pl": "Wybierz"},
"error": { "en": "error", "de": "Fehler", "ru": "ошибка", "pt": "erro", "nl": "fout", "fr": "Erreur", "it": "errore", "es": "error", "pl": "błąd"},
"IP not accessible for server": { "en": "IP is not accessible for the device", "de": "IP ist für dieses Gerät nicht erreichbar", "ru": "Этот IP недоступен для устройства", "pt": "IP não está acessível para o dispositivo", "nl": "IP is niet toegankelijk voor het apparaat", "fr": "L'adresse IP n'est pas accessible pour l'appareil", "it": "L'IP non è accessibile per il dispositivo", "es": "IP no es accesible para el dispositivo", "pl": "Adres IP jest niedostępny dla urządzenia"},
"Install first cloud adapter": { "en": "Install cloud adapter at first", "de": "Installieren Sie zuerst den Cloud-Adapter", "ru": "Сначала установить облачный адаптер", "pt": "Instale o adaptador de nuvem primeiro", "nl": "Installeer eerst de cloud-adapter", "fr": "Installer l'adaptateur cloud au début", "it": "Installare inizialmente l'adattatore cloud", "es": "Instalar el adaptador de nube al principio", "pl": "Najpierw zainstaluj adapter chmury"},
"tooltip_type": { "en": "Specify where the voice must be played", "de": "Damit wird gesetzt, wo die Sprache ausgegeben wird", "ru": "Укажите как и где будет выводится голос", "pt": "Especifique onde a voz deve ser tocada", "nl": "Geef op waar de stem moet worden afgespeeld", "fr": "Précisez où la voix doit être jouée", "it": "Specificare dove deve essere suonata la voce", "es": "Especifique dónde debe reproducirse la voz", "pl": "Określ, gdzie musi być odtwarzany głos"},
"tooltip_command": { "en": "This command will be executed, to use the custom system player", "de": "Dieses Kommando wird ausgeführt, um eigenen System-Player zu benutzen", "ru": "Эта команда выполнится для проигрывания с собственным плеером", "pt": "Este comando será executado para usar o player do sistema personalizado", "nl": "Deze opdracht wordt uitgevoerd om de aangepaste systeemspeler te gebruiken", "fr": "Cette commande sera exécutée pour utiliser le lecteur système personnalisé", "it": "Questo comando verrà eseguito, per utilizzare il player di sistema personalizzato", "es": "Este comando se ejecutará para usar el reproductor de sistema personalizado", "pl": "To polecenie zostanie wykonane, aby użyć niestandardowego odtwarzacza systemu"},
"File": { "en": "File", "de": "Datei", "ru": "файл", "pt": "Arquivo", "nl": "het dossier", "fr": "Fichier", "it": "File", "es": "Expediente", "pl": "Plik"},
"tree": { "en": "tree", "de": "Baum", "ru": "дерево", "pt": "árvore", "nl": "boom", "fr": "arbre", "it": "albero", "es": "árbol", "pl": "drzewo"},
"play": { "en": "play", "de": "abspielen", "ru": "играть", "pt": "Toque", "nl": "spelen", "fr": "jouer", "it": "giocare", "es": "jugar", "pl": "grać"},
"Adapter must be enabled": { "en": "Adapter must be enabled", "de": "Adapter muss aktiviert sein", "ru": "Адаптер должен быть включен", "pt": "O adaptador deve estar ativado", "nl": "Adapter moet zijn ingeschakeld", "fr": "L'adaptateur doit être activé", "it": "L'adattatore deve essere abilitato", "es": "El adaptador debe estar habilitado", "pl": "Adapter musi być włączony"}
};

25
appveyor.yml Normal file
View File

@@ -0,0 +1,25 @@
version: 'test-{build}'
environment:
matrix:
- nodejs_version: '4'
- nodejs_version: '6'
- nodejs_version: '8'
- nodejs_version: '10'
platform:
- x86
- x64
clone_folder: 'c:\projects\%APPVEYOR_PROJECT_NAME%'
install:
- ps: 'Install-Product node $env:nodejs_version $env:platform'
- ps: '$NpmVersion = (npm -v).Substring(0,1)'
- ps: 'if($NpmVersion -eq 5) { npm install -g npm@5 }'
- ps: npm --version
- npm install
- npm install winston@2.3.1
- 'npm install https://git.spacen.net/yunkong2/yunkong2.js-controller/tarball/master --production'
test_script:
- echo %cd%
- node --version
- npm --version
- npm test
build: 'off'

BIN
cmdmp3/cmdmp3.exe Normal file

Binary file not shown.

BIN
cmdmp3/cmdmp3win.exe Normal file

Binary file not shown.

2
cmdmp3/readme.txt Normal file
View File

@@ -0,0 +1,2 @@
For more information describing the files in this archive, please search my blog ( http://jimlawless.wordpress.com ) for the corresponding entry.

401
gulpfile.js Normal file
View File

@@ -0,0 +1,401 @@
'use strict';
var gulp = require('gulp');
var fs = require('fs');
var pkg = require('./package.json');
var iopackage = require('./io-package.json');
var version = (pkg && pkg.version) ? pkg.version : iopackage.common.version;
/*var appName = getAppName();
function getAppName() {
var parts = __dirname.replace(/\\/g, '/').split('/');
return parts[parts.length - 1].split('.')[0].toLowerCase();
}
*/
const fileName = 'words.js';
var languages = {
en: {},
de: {},
ru: {},
pt: {},
nl: {},
fr: {},
it: {},
es: {},
pl: {}
};
function lang2data(lang, isFlat) {
var str = isFlat ? '' : '{\n';
var count = 0;
for (var w in lang) {
if (lang.hasOwnProperty(w)) {
count++;
if (isFlat) {
str += (lang[w] === '' ? (isFlat[w] || w) : lang[w]) + '\n';
} else {
var key = ' "' + w.replace(/"/g, '\\"') + '": ';
str += key + '"' + lang[w].replace(/"/g, '\\"') + '",\n';
}
}
}
if (!count) return isFlat ? '' : '{\n}';
if (isFlat) {
return str;
} else {
return str.substring(0, str.length - 2) + '\n}';
}
}
function readWordJs(src) {
try {
var words;
if (fs.existsSync(src + 'js/' + fileName)) {
words = fs.readFileSync(src + 'js/' + fileName).toString();
} else {
words = fs.readFileSync(src + fileName).toString();
}
var lines = words.split(/\r\n|\r|\n/g);
var i = 0;
while (!lines[i].match(/^systemDictionary = {/)) {
i++;
}
lines.splice(0, i);
// remove last empty lines
i = lines.length - 1;
while (!lines[i]) {
i--;
}
if (i < lines.length - 1) {
lines.splice(i + 1);
}
lines[0] = lines[0].replace('systemDictionary = ', '');
lines[lines.length - 1] = lines[lines.length - 1].trim().replace(/};$/, '}');
words = lines.join('\n');
var resultFunc = new Function('return ' + words + ';');
return resultFunc();
} catch (e) {
return null;
}
}
function padRight(text, totalLength) {
return text + (text.length < totalLength ? new Array(totalLength - text.length).join(' ') : '');
}
function writeWordJs(data, src) {
var text = '// DO NOT EDIT THIS FILE!!! IT WILL BE AUTOMATICALLY GENERATED FROM src/i18n\n';
text += '/*global systemDictionary:true */\n';
text += '\'use strict\';\n\n';
text += 'systemDictionary = {\n';
for (var word in data) {
if (data.hasOwnProperty(word)) {
text += ' ' + padRight('"' + word.replace(/"/g, '\\"') + '": {', 50);
var line = '';
for (var lang in data[word]) {
if (data[word].hasOwnProperty(lang)) {
line += '"' + lang + '": "' + padRight(data[word][lang].replace(/"/g, '\\"') + '",', 50) + ' ';
}
}
if (line) {
line = line.trim();
line = line.substring(0, line.length - 1);
}
text += line + '},\n';
}
}
text = text.replace(/},\n$/, '}\n');
text += '};';
if (fs.existsSync(src + 'js/' + fileName)) {
fs.writeFileSync(src + 'js/' + fileName, text);
} else {
fs.writeFileSync(src + '' + fileName, text);
}
}
const EMPTY = '';
function words2languages(src) {
var langs = Object.assign({}, languages);
var data = readWordJs(src);
if (data) {
for (var word in data) {
if (data.hasOwnProperty(word)) {
for (var lang in data[word]) {
if (data[word].hasOwnProperty(lang)) {
langs[lang][word] = data[word][lang];
// pre-fill all other languages
for (var j in langs) {
if (langs.hasOwnProperty(j)) {
langs[j][word] = langs[j][word] || EMPTY;
}
}
}
}
}
}
if (!fs.existsSync(src + 'i18n/')) {
fs.mkdirSync(src + 'i18n/');
}
for (var l in langs) {
if (!langs.hasOwnProperty(l)) continue;
var keys = Object.keys(langs[l]);
//keys.sort();
var obj = {};
for (var k = 0; k < keys.length; k++) {
obj[keys[k]] = langs[l][keys[k]];
}
if (!fs.existsSync(src + 'i18n/' + l)) {
fs.mkdirSync(src + 'i18n/' + l);
}
fs.writeFileSync(src + 'i18n/' + l + '/translations.json', lang2data(obj));
}
} else {
console.error('Cannot read or parse ' + fileName);
}
}
function words2languagesFlat(src) {
var langs = Object.assign({}, languages);
var data = readWordJs(src);
if (data) {
for (var word in data) {
if (data.hasOwnProperty(word)) {
for (var lang in data[word]) {
if (data[word].hasOwnProperty(lang)) {
langs[lang][word] = data[word][lang];
// pre-fill all other languages
for (var j in langs) {
if (langs.hasOwnProperty(j)) {
langs[j][word] = langs[j][word] || EMPTY;
}
}
}
}
}
}
var keys = Object.keys(langs.en);
//keys.sort();
for (var l in langs) {
if (!langs.hasOwnProperty(l)) continue;
var obj = {};
for (var k = 0; k < keys.length; k++) {
obj[keys[k]] = langs[l][keys[k]];
}
langs[l] = obj;
}
if (!fs.existsSync(src + 'i18n/')) {
fs.mkdirSync(src + 'i18n/');
}
for (var ll in langs) {
if (!langs.hasOwnProperty(ll)) continue;
if (!fs.existsSync(src + 'i18n/' + ll)) {
fs.mkdirSync(src + 'i18n/' + ll);
}
fs.writeFileSync(src + 'i18n/' + ll + '/flat.txt', lang2data(langs[ll], langs.en));
}
fs.writeFileSync(src + 'i18n/flat.txt', keys.join('\n'));
} else {
console.error('Cannot read or parse ' + fileName);
}
}
function languagesFlat2words(src) {
var dirs = fs.readdirSync(src + 'i18n/');
var langs = {};
var bigOne = {};
var order = Object.keys(languages);
dirs.sort(function (a, b) {
var posA = order.indexOf(a);
var posB = order.indexOf(b);
if (posA === -1 && posB === -1) {
if (a > b) return 1;
if (a < b) return -1;
return 0;
} else if (posA === -1) {
return -1;
} else if (posB === -1) {
return 1;
} else {
if (posA > posB) return 1;
if (posA < posB) return -1;
return 0;
}
});
var keys = fs.readFileSync(src + 'i18n/flat.txt').toString().split('\n');
for (var l = 0; l < dirs.length; l++) {
if (dirs[l] === 'flat.txt') continue;
var lang = dirs[l];
var values = fs.readFileSync(src + 'i18n/' + lang + '/flat.txt').toString().split('\n');
langs[lang] = {};
keys.forEach(function (word, i) {
langs[lang][word] = values[i].replace(/<\/ i>/g, '</i>').replace(/<\/ b>/g, '</b>').replace(/<\/ span>/g, '</span>').replace(/% s/g, ' %s');
});
var words = langs[lang];
for (var word in words) {
if (words.hasOwnProperty(word)) {
bigOne[word] = bigOne[word] || {};
if (words[word] !== EMPTY) {
bigOne[word][lang] = words[word];
}
}
}
}
// read actual words.js
var aWords = readWordJs();
var temporaryIgnore = ['pt', 'fr', 'nl', 'flat.txt'];
if (aWords) {
// Merge words together
for (var w in aWords) {
if (aWords.hasOwnProperty(w)) {
if (!bigOne[w]) {
console.warn('Take from actual words.js: ' + w);
bigOne[w] = aWords[w]
}
dirs.forEach(function (lang) {
if (temporaryIgnore.indexOf(lang) !== -1) return;
if (!bigOne[w][lang]) {
console.warn('Missing "' + lang + '": ' + w);
}
});
}
}
}
writeWordJs(bigOne, src);
}
function languages2words(src) {
var dirs = fs.readdirSync(src + 'i18n/');
var langs = {};
var bigOne = {};
var order = Object.keys(languages);
dirs.sort(function (a, b) {
var posA = order.indexOf(a);
var posB = order.indexOf(b);
if (posA === -1 && posB === -1) {
if (a > b) return 1;
if (a < b) return -1;
return 0;
} else if (posA === -1) {
return -1;
} else if (posB === -1) {
return 1;
} else {
if (posA > posB) return 1;
if (posA < posB) return -1;
return 0;
}
});
for (var l = 0; l < dirs.length; l++) {
if (dirs[l] === 'flat.txt') continue;
var lang = dirs[l];
langs[lang] = fs.readFileSync(src + 'i18n/' + lang + '/translations.json').toString();
langs[lang] = JSON.parse(langs[lang]);
var words = langs[lang];
for (var word in words) {
if (words.hasOwnProperty(word)) {
bigOne[word] = bigOne[word] || {};
if (words[word] !== EMPTY) {
bigOne[word][lang] = words[word];
}
}
}
}
// read actual words.js
var aWords = readWordJs();
var temporaryIgnore = ['pt', 'fr', 'nl', 'it'];
if (aWords) {
// Merge words together
for (var w in aWords) {
if (aWords.hasOwnProperty(w)) {
if (!bigOne[w]) {
console.warn('Take from actual words.js: ' + w);
bigOne[w] = aWords[w]
}
dirs.forEach(function (lang) {
if (temporaryIgnore.indexOf(lang) !== -1) return;
if (!bigOne[w][lang]) {
console.warn('Missing "' + lang + '": ' + w);
}
});
}
}
}
writeWordJs(bigOne, src);
}
gulp.task('adminWords2languages', function (done) {
words2languages('./admin/');
done();
});
gulp.task('adminWords2languagesFlat', function (done) {
words2languagesFlat('./admin/');
done();
});
gulp.task('adminLanguagesFlat2words', function (done) {
languagesFlat2words('./admin/');
done();
});
gulp.task('adminLanguages2words', function (done) {
languages2words('./admin/');
done();
});
gulp.task('updatePackages', function (done) {
iopackage.common.version = pkg.version;
iopackage.common.news = iopackage.common.news || {};
if (!iopackage.common.news[pkg.version]) {
var news = iopackage.common.news;
var newNews = {};
newNews[pkg.version] = {
en: 'news',
de: 'neues',
ru: 'новое'
};
iopackage.common.news = Object.assign(newNews, news);
}
fs.writeFileSync('io-package.json', JSON.stringify(iopackage, null, 4));
done();
});
gulp.task('updateReadme', function (done) {
var readme = fs.readFileSync('README.md').toString();
var pos = readme.indexOf('## Changelog\n');
if (pos !== -1) {
var readmeStart = readme.substring(0, pos + '## Changelog\n'.length);
var readmeEnd = readme.substring(pos + '## Changelog\n'.length);
if (readme.indexOf(version) === -1) {
var timestamp = new Date();
var date = timestamp.getFullYear() + '-' +
('0' + (timestamp.getMonth() + 1).toString(10)).slice(-2) + '-' +
('0' + (timestamp.getDate()).toString(10)).slice(-2);
var news = '';
if (iopackage.common.news && iopackage.common.news[pkg.version]) {
news += '* ' + iopackage.common.news[pkg.version].en;
}
fs.writeFileSync('README.md', readmeStart + '### ' + version + ' (' + date + ')\n' + (news ? news + '\n\n' : '\n') + readmeEnd);
}
}
done();
});
gulp.task('default', ['updatePackages', 'updateReadme']);

160
io-package.json Normal file
View File

@@ -0,0 +1,160 @@
{
"common": {
"name": "sayit",
"version": "1.7.1",
"title": "Text to speech",
"titleLang": {
"en": "Text to speech",
"de": "Text zu Sprache",
"ru": "Текст в речь (Text to speech)",
"pt": "Texto para fala (Text to speech)",
"nl": "Tekst naar spraak (Text to speech)",
"fr": "Texte pour parler (Text to speech)",
"it": "Sintesi vocale (Text to speech)",
"es": "Texto a voz (Text to speech)",
"pl": "Tekst na mowę (Text to speech)"
},
"desc": {
"en": "Text to speech in a cloud or local. Runs on windows, linux and OSX. It can play mp3/wav too.",
"de": "Text to speech in Cloud oder lokal für windows, linux und OSX. Mp3 und Wav können auch abgespielt werden.",
"ru": "Синтезатор речи локально или в облаке работает под windows, linux и OSX. Mp3 и Wav можно тоже проигрывать.",
"pt": "Texto para fala em uma nuvem ou local. Funciona no windows, linux e OSX. Pode reproduzir mp3 / wav também.",
"nl": "Tekst naar spraak in een cloud of lokaal. Werkt op Windows, Linux en OSX. Het kan ook mp3 / wav spelen.",
"fr": "Text to speech dans un nuage ou local. Fonctionne sur Windows, Linux et OSX. Il peut aussi jouer au format mp3 / wav.",
"it": "Sintesi vocale in un cloud o locale. Funziona su Windows, Linux e OSX. Può riprodurre anche mp3 / wav.",
"es": "Texto a voz en una nube o local. Se ejecuta en Windows, Linux y OSX. Puede reproducir mp3 / wav también.",
"pl": "Tekst na mowę w chmurze lub lokalnie. Działa w systemie Windows, Linux i OSX. Może również odtwarzać mp3 / wav."
},
"authors": [
"bluefox <dogafox@gmail.com>"
],
"license": "MIT",
"platform": "Javascript/Node.js",
"mode": "daemon",
"readme": "https://git.spacen.net/yunkong2/yunkong2.sayit/blob/master/README.md",
"loglevel": "info",
"messagebox": true,
"supportStopInstance": true,
"stopBeforeUpdate": true,
"icon": "sayit.png",
"materialize": true,
"enabled": true,
"keywords": [
"TTS",
"voice",
"speech"
],
"extIcon": "https://git.spacen.net/yunkong2/yunkong2.sayit/master/admin/sayit.png",
"type": "multimedia",
"blockly": true
},
"native": {
"type": "system",
"command": "",
"player": "",
"cache": true,
"engine": "",
"sonos": "",
"server": "",
"port": 0,
"user": "",
"pass": "",
"instance": "",
"web": "",
"webServer": "",
"cacheDir": "../../cache/",
"key": "",
"announce": "",
"annoTimeout": 15,
"annoDuration": 0,
"annoVolume": "70",
"cloud": ""
},
"instanceObjects": [
{
"_id": "tts.userfiles",
"type": "meta",
"common": {
"name": "Gong files",
"role": "meta.user"
},
"native": {}
},
{
"_id": "tts",
"type": "channel",
"common": {
"name": "Google text to speech",
"role": "media.tts"
},
"native": {}
},
{
"_id": "tts.volume",
"type": "state",
"common": {
"role": "level.volume",
"name": "TTS Volume",
"min": 0,
"max": 100,
"def": 70,
"type": "number",
"read": true,
"write": true,
"desc": "System volume or remote device volume"
},
"native": {}
},
{
"_id": "tts.text",
"type": "state",
"common": {
"role": "media.tts.text",
"name": "Text to speech",
"type": "string",
"read": true,
"write": true,
"desc": "This text will be converted to speech"
},
"native": {}
},
{
"_id": "tts.cachetext",
"type": "state",
"common": {
"role": "media.tts.cachetext",
"name": "Text to cache",
"type": "string",
"read": true,
"write": true,
"desc": "This text will be cached on disk to play it later"
},
"native": {}
},
{
"_id": "tts.playing",
"type": "state",
"common": {
"role": "media.tts.text",
"name": "Is now speaking",
"type": "boolean",
"read": true,
"write": false,
"desc": "Is true while speaking"
},
"native": {}
},
{
"_id": "tts.mp3",
"type": "state",
"common": {
"name": "Binary mp3 file",
"type": "file",
"read": true,
"write": false,
"desc": "Can be accessed from web server under http://ip:8082/state/sayit.0.tts.mp3"
},
"native": {}
}
]
}

518
lib/speech2device.js Normal file
View File

@@ -0,0 +1,518 @@
'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;

484
lib/text2speech.js Normal file
View File

@@ -0,0 +1,484 @@
'use strict';
function Text2Speech(adapter, libs, options, sayIt) {
const that = this;
const engines = require(__dirname + '/../admin/engines.js');
const sayitEngines = engines.sayitEngines;
const MP3FILE = __dirname + '/../' + adapter.namespace + '.say.mp3';
let appkey = null;
let cloudUrl = null;
let polly = null;
function splitText(text, max) {
if (!max) max = 70;
if (text.length > max) {
const parts = text.split(/,|.|;|:/);
const result = [];
for (let p = 0; p < parts.length; p++) {
if (parts[p].length < max) {
result.push(parts[p]);
continue;
}
const _parts = parts[p].split(' ');
let i = 0;
for (let w = 0; w < _parts.length; w++) {
if (_parts[i] && ((result[i] || '') + ' ' + _parts[w]).length > max) {
i++;
}
if (!result[i]) {
result.push(_parts[w]);
} else {
result[i] += ' ' + _parts[w];
}
}
}
return result;
} else {
return [text];
}
}
function copyFile(text, language, volume, source, dest, callback) {
libs.fs = libs.fs || require('fs');
try {
const input = libs.fs.createReadStream(source); // Input stream
const output = libs.fs.createWriteStream(dest); // Output stream
input.on('data', d =>output.write(d));
// Copy in to out
input.on('error', err => {
throw err;
});
// Raise errors
input.on('end', () => { // When input ends
output.end(); // close output
adapter.log.info('Copied file "' + source + '" to "' + dest + '"');
if (callback) callback(null, text, language, volume); // And notify callback
});
} catch (e) {
if (callback) callback('Cannot copy file "' + source + '" to "' + dest + '": ' + e, '', '', volume); // And notify callback
}
}
function cacheFile(error, text, language, volume, seconds, callback) {
libs.fs = libs.fs || require('fs');
libs.path = libs.path || require('path');
if (!error) {
if (adapter.config.cache) {
const md5filename = libs.path.join(options.cacheDir, libs.crypto.createHash('md5').update(language + ';' + text).digest('hex') + '.mp3');
const stat = libs.fs.statSync(MP3FILE);
if (stat.size < 100) {
adapter.log.warn('Received file is too short: ' + libs.fs.readFileSync(MP3FILE).toString());
} else {
copyFile(text, language, volume, MP3FILE, md5filename, (error, text, language, volume) => {
if (error) adapter.log.error(error);
});
}
}
}
callback(error, text, language, volume, seconds);
}
function sayItGetSpeechGoogle(text, language, volume, callback) {
if (typeof volume === 'function') {
callback = volume;
volume = undefined;
}
if (text.length === 0) {
if (callback) callback(null, '', language, volume, 0);
return;
}
if (text.length > 70) {
const parts = splitText(text);
for (let t = 1; t < parts.length; t++) {
sayIt(parts[t], language, volume);
}
text = parts[0];
}
language = language || adapter.config.engine;
// https://translate.google.com/translate_tts?ie=UTF-8&client=tw-ob&q=%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D0%B8%D1%82%D1%8C%207&tl=ru&total=1&idx=0&textlen=10
const options = {
host: 'translate.google.com',
//port: 443,
path: '/translate_tts?ie=UTF-8&client=tw-ob&q=' + encodeURI(text) + '&tl=' + language + '&total=1&idx=0&textlen=' + text.length //
};
if (language === 'ru') {
options.headers = {
'Accept-Encoding': 'identity;q=1, *;q=0',
Range: 'bytes=0-',
Referer: 'https://www.google.de/',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.125 Safari/537.36'
//"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0",
//"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
//"Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3",
//"Accept-Encoding": "gzip, deflate"//
};
}
if (!libs.https) libs.https = require('https');
if (!libs.fs) libs.fs = require('fs');
let sounddata = '';
libs.https.get(options, res => {
res.setEncoding('binary');
res.on('data', chunk => sounddata += chunk);
res.on('end', () => {
if (sounddata.length < 100) {
if (callback) callback('Cannot get file: received file is too short', text, language, volume, 0);
return;
}
if (sounddata.toString().indexOf('302 Moved') !== -1) {
if (callback) callback('http://' + options.host + options.path + '\nCannot get file: ' + sounddata, text, language, volume, 0);
return;
}
libs.fs.writeFile(MP3FILE, sounddata, 'binary', err => {
if (err) {
if (callback) callback('File error: ' + err, text, language, volume, 0);
} else {
that.getLength(MP3FILE, (error, seconds) => {
if (callback) callback(error, text, language, volume, seconds);
});
}
});
});
}).on('error', err => {
sounddata = '';
if (callback) callback('Cannot get file:' + err, text, language, volume, 0);
});
}
function sayItGetSpeechAcapela(text, language, volume, callback) {
const options = {
host: 'vaassl3.acapela-group.com',
path: '/Services/Synthesizer?prot_vers=2&req_voice=' + language + '22k&cl_env=FLASH_AS_3.0&req_text=%5Cvct%3D100%5C+%5Cspd%3D180%5C+' +
encodeURI(text) + '&req_asw_type=STREAM&cl_vers=1-30&req_err_as_id3=yes&cl_login=ACAPELA_BOX&cl_app=PROD&cl_pwd=0g7znor2aa'
};
if (!libs.https) libs.https = require('https');
let sounddata = '';
libs.https.get(options, res => {
res.setEncoding('binary');
res.on('data', chunk => sounddata += chunk);
res.on('end', () => {
if (sounddata.length < 100) {
if (callback) callback('Cannot get file: received file is too short', text, language, volume);
return;
}
libs.fs.writeFile(MP3FILE, sounddata, 'binary', err => {
if (err) {
if (callback) callback('File error:' + err, text, language, volume);
} else {
console.log('File saved.');
if (callback) callback(null, text, language, volume);
}
});
});
}).on('error', err => {
sounddata = '';
if (callback) callback('Cannot get file:' + err, text, language, volume);
});
}
function sayItGetSpeechYandex(text, language, volume, callback) {
if (language === 'ru' || language === 'ru_YA') {
language = 'ru-RU';
}
if (!libs.https) libs.https = require('https');
/*emotion: good, neutral, evil, mixed
drunk: true, false
ill: true, false
robot: true, false
*/
const options = {
host: 'tts.voicetech.yandex.net',
path: '/generate?lang=' + language + '&format=mp3&speaker=' + adapter.config.voice + '&key=' + adapter.config.key +
'&text=' + encodeURI(text.trim())
};
if (adapter.config.emotion && adapter.config.emotion !== 'none') options.path += '&emotion=' + adapter.config.emotion;
if (adapter.config.drunk === 'true' || adapter.config.drunk === true) options.path += '&drunk=true';
if (adapter.config.ill === 'true' || adapter.config.ill === true) options.path += '&ill=true';
if (adapter.config.robot === 'true' || adapter.config.robot === true) options.path += '&robot=true';
let sounddata = '';
libs.https.get(options, res => {
res.setEncoding('binary');
res.on('data', chunk => sounddata += chunk);
res.on('end', () => {
if (sounddata.length < 100) {
if (callback) callback('Cannot get file: received file is too short', text, language, volume, 0);
return;
}
libs.fs.writeFile(MP3FILE, sounddata, 'binary', err => {
if (err) {
if (callback) callback('File error: ' + err, text, language, volume, 0);
} else {
if (callback) callback(null, text, language, volume);
}
});
});
}).on('error', err => {
sounddata = '';
if (callback) callback('Cannot get file: ' + err, text, language, volume);
});
}
function sayItGetSpeechPolly(text, language, volume, callback) {
if (!libs.aws) libs.aws = require('aws-sdk');
if (!libs.fs) libs.fs = require('fs');
try {
polly = polly || new libs.aws.Polly({
accessKeyId: adapter.config.accessKey,
secretAccessKey: adapter.config.secretKey,
region: adapter.config.region
});
let type = 'text';
if (text.match(/<[-+\w\s'"=]+>/)) {
if (!text.match(/^<speak>/)) text = '<speak>' + text + '</speak>';
type = 'ssml';
}
const params = {
Text: text,
OutputFormat: 'mp3',
TextType: type,
VoiceId: sayitEngines[language].ename
};
polly.synthesizeSpeech(params, (err, data) => {
if (err) {
if (callback) callback('Cannot get answer: ' + JSON.stringify(err), text, language, volume, 0);
} else if (data) {
if (data.AudioStream instanceof Buffer) {
libs.fs.writeFile(MP3FILE, data.AudioStream, 'binary', err => {
if (err) {
if (callback) callback('File error: ' + err, text, language, volume, 0);
} else {
that.getLength(MP3FILE, (error, seconds) => {
if (callback) callback(error, text, language, volume, seconds);
});
}
});
} else {
if (callback) callback('Answer in invalid format: ' + (data ? data.toString() : 'null'), text, language, volume, 0);
}
}
});
} catch (e) {
if (callback) callback(e.toString(), text, language, volume);
}
}
function sayItGetSpeechCloud(text, language, volume, callback) {
if (!libs.https) libs.https = require('https');
if (!libs.fs) libs.fs = require('fs');
if (!appkey) {
adapter.getForeignObject('system.adapter.' + adapter.config.cloud, (err, obj) => {
appkey = obj && obj.native && obj.native.apikey ? obj.native.apikey : 'error';
cloudUrl = (obj && obj.native && obj.native.cloudUrl) || '';
if (appkey && appkey.match(/^@pro_/)) {
if (cloudUrl.indexOf('https://yunkong2.pro:') === -1 &&
cloudUrl.indexOf('https://yunkong2.info:') === -1) {
cloudUrl = 'https://yunkong2.pro:10555';
}
}
cloudUrl = cloudUrl.replace(/https\:\/\//, '').replace(/:\d+/, '');
sayItGetSpeechCloud(text, language, volume, callback);
});
return;
}
if (appkey === 'error') {
if (callback) callback('No app key found in "' + adapter.config.cloud + '".', text, language, volume, 0);
return;
}
try {
let type = 'text';
if (text.match(/<[-+\w\s'"=]+>/)) {
if (!text.match(/^<speak>/)) text = '<speak>' + text + '</speak>';
type = 'ssml';
}
const postData = JSON.stringify({
Text: text,
appkey: appkey,
TextType: type,
VoiceId: sayitEngines[language].ename
});
const postOptions = {
host: cloudUrl,
port: 443,
path: '/polly/',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(postData)
}
};
// Set up the request
const postReq = libs.https.request(postOptions, (res) => {
adapter.log.debug('Status code: ' + res.statusCode);
if (res.statusCode === 200) {
res.setEncoding('binary');
} else {
res.setEncoding('utf8');
}
let data = [];
res.on('data', chunk => {
if (typeof chunk === 'string') chunk = new Buffer(chunk, 'binary');
data.push(chunk);
});
res.on('error', error => {
if (callback) {
callback('Cannot read: ' + error, text, language, volume, 0);
callback = null;
}
});
res.on('end', () => {
data = Buffer.concat(data);
if (data instanceof Buffer && data.length > 500) {
libs.fs.writeFile(MP3FILE, data, 'binary', err => {
if (err) {
if (callback) callback('File error: ' + err, text, language, volume, 0);
} else {
that.getLength(MP3FILE, (error, seconds) => {
if (callback) callback(error, text, language, volume, seconds);
});
}
});
} else {
if (callback) callback('Answer in invalid format: ' + (data ? data.toString().substring(0, 100) : 'null'), text, language, volume, 0);
}
});
});
// post the data
postReq.write(postData);
postReq.end();
} catch (e) {
if (callback) callback(e.toString(), text, language, volume);
}
}
function sayItGetSpeechPicoTTS(text, language, volume, callback) {
if (!libs.fs) libs.fs = require('fs');
if (!libs.exec) libs.exec = require('child_process').exec;
try {
const cmd = 'pico2wave -l ' + language + ' -w ' + __dirname + '/say.wav "' + text + '"';
libs.exec(cmd, (error, stdout, stderr) => {
if (error) {
if (callback) callback('Cannot create (pico2wave) "say.wav": ' + error, text, language, volume);
} else {
libs.exec('lame ' + __dirname + '/say.wav ' + MP3FILE, (error, stdout, stderr) => {
if (callback) callback(error ? 'Cannot create (lame) "say.mp3": ' + error : null, text, language, volume);
});
}
});
} catch (e) {
if (callback) callback(e.toString(), text, language, volume);
}
}
this.getLength = function (fileName, callback) {
libs.fs = libs.fs || require('fs');
// create a new parser from a node ReadStream
if (fileName === adapter.config.announce && adapter.config.annoDuration) {
if (callback) callback(null, adapter.config.annoDuration - 1);
return;
}
if (libs.fs.existsSync(fileName)) {
try {
const stat = libs.fs.statSync(fileName);
const size = stat.size;
if (callback) callback(null, Math.ceil(size / 4096));
} catch (e) {
adapter.log.warn('Cannot read length of file ' + fileName);
if (callback) callback(null, 0);
}
} else {
if (callback) callback(null, 0);
}
};
const ENGINES = {
'google': sayItGetSpeechGoogle,
'yandex': sayItGetSpeechYandex,
'acapela': sayItGetSpeechAcapela,
'polly': sayItGetSpeechPolly,
'cloud': sayItGetSpeechCloud,
'PicoTTS': sayItGetSpeechPicoTTS
};
this.sayItGetSpeech = function (text, language, volume, callback) {
if (adapter.config.cache) {
libs.path = libs.path || require('path');
const md5filename = libs.path.join(options.cacheDir, libs.crypto.createHash('md5').update(language + ';' + text).digest('hex') + '.mp3');
if (libs.fs.existsSync(md5filename)) {
let cacheFileValid = true;
if (adapter.config.cacheExpiryDays) {
const fileStat = libs.fs.statSync(md5filename);
if (fileStat.ctime && (new Date().getTime()-new Date(fileStat.ctime).getTime() > adapter.config.cacheExpiryDays*1000*60*60*24)) {
cacheFileValid = false;
adapter.log.info('Cached File expired, remove and re-generate');
libs.fs.unlinkSync(md5filename);
}
}
if (cacheFileValid) {
that.getLength(md5filename, (error, seconds) => {
if (callback) callback(error, md5filename, language, volume, seconds);
});
return;
}
}
}
if (sayitEngines[language] && sayitEngines[language].engine) {
if (!sayitEngines[language].ssml) text = text.replace(/<\/?[-+\w\s'"=]+>/g, ''); // remove SSML
if (!ENGINES[sayitEngines[language].engine]) {
callback && callback('Engine ' + sayitEngines[language].engine + ' not yet supported.', text, language, volume, 0);
} else {
ENGINES[sayitEngines[language].engine](text, language, volume,
(error, _text, _language, _volume, seconds) => cacheFile(error, _text, _language, _volume, seconds, callback));
}
} else {
sayItGetSpeechGoogle(text, language, volume,
(error, _text, _language, _volume, seconds) => cacheFile(error, _text, _language, _volume, seconds, callback));
}
};
return this;
}
module.exports = Text2Speech;

83
lib/utils.js Normal file
View File

@@ -0,0 +1,83 @@
'use strict';
const fs = require('fs');
const path = require('path');
let controllerDir;
let appName;
/**
* returns application name
*
* The name of the application can be different and this function finds it out.
*
* @returns {string}
*/
function getAppName() {
const parts = __dirname.replace(/\\/g, '/').split('/');
return parts[parts.length - 2].split('.')[0];
}
/**
* looks for js-controller home folder
*
* @param {boolean} isInstall
* @returns {string}
*/
function getControllerDir(isInstall) {
// Find the js-controller location
const possibilities = [
'yunkong2.js-controller',
'yunkong2.js-controller',
];
/** @type {string} */
let controllerPath;
for (const pkg of possibilities) {
try {
const possiblePath = require.resolve(pkg);
if (fs.existsSync(possiblePath)) {
controllerPath = possiblePath;
break;
}
} catch (e) { /* not found */ }
}
if (controllerPath == null) {
if (!isInstall) {
console.log('Cannot find js-controller');
process.exit(10);
} else {
process.exit();
}
}
// we found the controller
return path.dirname(controllerPath);
}
/**
* reads controller base settings
*
* @alias getConfig
* @returns {object}
*/
function getConfig() {
let configPath;
if (fs.existsSync(
configPath = path.join(controllerDir, 'conf', appName + '.json')
)) {
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
} else if (fs.existsSync(
configPath = path.join(controllerDir, 'conf', + appName.toLowerCase() + '.json')
)) {
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
} else {
throw new Error('Cannot find ' + controllerDir + '/conf/' + appName + '.json');
}
}
appName = getAppName();
controllerDir = getControllerDir(typeof process !== 'undefined' && process.argv && process.argv.indexOf('--install') !== -1);
const adapter = require(path.join(controllerDir, 'lib/adapter.js'));
exports.controllerDir = controllerDir;
exports.getConfig = getConfig;
exports.Adapter = adapter;
exports.appName = appName;

636
main.js Normal file
View File

@@ -0,0 +1,636 @@
/* jshint -W097 */
/* jshint strict: false */
/* jslint node: true */
'use strict';
const utils = require(__dirname + '/lib/utils'); // Get common adapter utils
const engines = require(__dirname + '/admin/engines.js');
const Text2Speech = require(__dirname + '/lib/text2speech');
const Speech2Device = require(__dirname + '/lib/speech2device');
const sayitOptions = engines.sayitOptions;
const libs = {};
const adapter = new utils.Adapter({
name: 'sayit',
unload: stop
});
process.on('SIGINT', stop);
adapter.on('stateChange', (id, state) => {
if (state && !state.ack) {
if (id === adapter.namespace + '.tts.volume') {
if (adapter.config.type === 'system') {
speech2device.sayItSystemVolume(state.val);
} else {
options.sayLastVolume = state.val;
}
} else if (id === adapter.namespace + '.tts.text') {
if (typeof state.val !== 'string') {
if (state.val === null || state.val === undefined || state.val === '') {
adapter.log.warn('Cannot cache empty text');
return;
}
state.val = state.val.toString();
}
sayIt(state.val);
} else if (id === adapter.namespace + '.tts.cachetext') {
if (typeof state.val !== 'string') {
if (state.val === null || state.val === undefined || state.val === '') {
adapter.log.warn('Cannot cache empty text');
return;
}
state.val = state.val.toString();
}
cacheIt(state.val);
}
}
});
adapter.on('ready', main);
adapter.on('message', obj => {
if (obj) processMessage(obj);
processMessages();
});
function processMessage(obj) {
if (obj) {
if (obj.command === 'stopInstance') {
stop(() => {
if (obj.callback) {
adapter.sendTo(obj.from, obj.command, null, obj.callback);
}
});
} else if (obj.command === 'browseChromecast') {
try {
const mdns = require('mdns');
let browser = mdns.createBrowser(mdns.tcp('googlecast'));
const result = [];
browser.on('serviceUp', service => result.push({name: service.name, ip: service.addresses[0]}));
setTimeout(() => {
browser.stop();
browser = null;
if (obj.callback) {
adapter.sendTo(obj.from, obj.command, result, obj.callback);
}
}, 2000);
browser.start();
} catch (e) {
adapter.log.error(e);
if (obj.callback) adapter.sendTo(obj.from, obj.command, null, obj.callback);
}
}
}
}
function processMessages() {
adapter.getMessage((err, obj) => {
if (obj) setTimeout(processMessages, 0);
});
}
function stop(callback) {
try {
if (adapter && adapter.log && adapter.log.info) {
adapter.log.info('stopping...');
}
setTimeout(() => process.exit(), 1000);
if (typeof callback === 'function') callback();
} catch (e) {
process.exit();
}
}
const options = {
sayLastVolume: null,
webLink: '',
cacheDir: ''
};
let sayLastGeneratedText = '';
let list = [];
let lastSay = null;
const text2speech = new Text2Speech(adapter, libs, options, sayIt);
const speech2device = new Speech2Device(adapter, libs, options);
const MP3FILE = __dirname + '/' + adapter.namespace + '.say.mp3';
function mkpathSync(rootpath, dirpath) {
libs.fs = libs.fs || require('fs');
// Remove filename
dirpath = dirpath.split('/');
dirpath.pop();
if (!dirpath.length) return;
for (let i = 0; i < dirpath.length; i++) {
rootpath += dirpath[i] + '/';
if (!libs.fs.existsSync(rootpath)) {
if (dirpath[i] !== '..') {
libs.fs.mkdirSync(rootpath);
} else {
throw 'Cannot create ' + rootpath + dirpath.join('/');
}
}
}
}
function sayFinished(error, duration) {
if (error) {
adapter.log.error(error);
}
duration = duration || 0;
if (list.length) {
adapter.log.debug('Duration "' + list[0].text + '": ' + duration);
}
setTimeout(() => {
// Remember when last text finished
lastSay = Date.now();
if (list.length) list.shift();
if (list.length) {
sayIt(list[0].text, list[0].language, list[0].volume, true);
}
}, duration * 1000);
}
let cacheRunning = false;
let cacheFiles = [];
function cacheIt(text, language) {
// process queue
if (text === true) {
if (!cacheFiles.length) {
cacheRunning = false;
return;
}
// get next queued text
const toCache = cacheFiles.shift();
text = toCache.text;
language = toCache.language;
} else {
// new text to cache
if (!adapter.config.cache) {
adapter.log.warn('Cache is not enabled. Unable to cache: ' + text);
return;
}
// Extract language from "en;volume;Text to say"
if (text.indexOf(';') !== -1) {
const arr = text.split(';', 3);
// If language;text or volume;text
if (arr.length === 2) {
// If number
if (parseInt(arr[0]).toString() !== arr[0]) {
language = arr[0];
}
text = arr[1];
} else if (arr.length === 3) {
// If language;volume;text or volume;language;text
// If number
if (parseInt(arr[0]).toString() === arr[0]) {
language = arr[1];
} else {
language = arr[0];
}
text = arr[2];
}
}
// if no text => do not process
if (!text.length) {
return;
}
// Check: may be it is file from DB filesystem, like /vis.0/main/img/door-bell.mp3
if (text[0] === '/') {
adapter.log.warn('mp3 file must not be cached: ' + text);
return;
}
let isGenerate = false;
if (!language) language = adapter.config.engine;
// find out if say.mp3 must be generated
if (!speech2device.sayItIsPlayFile(text)) isGenerate = sayitOptions[adapter.config.type].mp3Required;
if (!isGenerate) {
if (speech2device.sayItIsPlayFile(text)) {
adapter.log.warn('mp3 file must not be cached: ' + text);
} else {
adapter.log.warn('Cache does not required for this engine: ' + adapter.config.engine);
}
return;
}
const md5filename = libs.path.join(options.cacheDir, libs.crypto.createHash('md5').update(language + ';' + text).digest('hex') + '.mp3');
libs.fs = libs.fs || require('fs');
if (libs.fs.existsSync(md5filename)) {
adapter.log.debug('Text is yet cached: ' + text);
return;
}
if (cacheRunning) {
cacheFiles.push({text: text, language: language});
return;
}
}
cacheRunning = true;
text2speech.sayItGetSpeech(text, language, false, (error, md5filename, _language, volume, seconds) => {
if (error) {
adapter.log.error('Cannot cache text: "' + error);
} else {
adapter.log.debug('Text is cached: "' + text + '" under ' + md5filename);
}
setTimeout(function () {
cacheIt(true);
}, 2000);
});
}
function sayIt(text, language, volume, process) {
let md5filename;
// Extract language from "en;volume;Text to say"
if (text.indexOf(';') !== -1) {
const arr = text.split(';', 3);
// If language;text or volume;text
if (arr.length === 2) {
// If number
if (parseInt(arr[0]).toString() === arr[0].toString()) {
volume = arr[0];
} else {
language = arr[0];
}
text = arr[1];
} else if (arr.length === 3) {
// If language;volume;text or volume;language;text
// If number
if (parseInt(arr[0]).toString() === arr[0].toString()) {
volume = arr[0];
language = arr[1];
} else {
volume = arr[1];
language = arr[0];
}
text = arr[2];
}
}
// if no text => do not process
if (!text.length) {
sayFinished(0);
return;
}
// Check: may be it is file from DB filesystem, like /vis.0/main/img/door-bell.mp3
if (text[0] === '/') {
let cached = false;
if (adapter.config.cache) {
md5filename = libs.path.join(options.cacheDir, libs.crypto.createHash('md5').update(text).digest('hex') + '.mp3');
if (libs.fs.existsSync(md5filename)) {
cached = true;
text = md5filename;
}
}
if (!cached) {
const parts = text.split('/');
const adap = parts[0];
parts.splice(0, 1);
const _path = parts.join('/');
adapter.readFile(adap, _path, (err, data) => {
if (data) {
try {
// Cache the file
if (md5filename) libs.fs.writeFileSync(md5filename, data);
libs.fs.writeFileSync(MP3FILE, data);
sayIt(MP3FILE, language, volume, process);
} catch (e) {
adapter.log.error('Cannot write file "' + MP3FILE + '": ' + e.toString());
sayFinished(0);
}
} else {
// may be file from real FS
if (libs.fs.existsSync(text)) {
try {
data = libs.fs.readFileSync(text);
} catch (e) {
adapter.log.error('Cannot read file "' + text + '": ' + e.toString());
sayFinished(0);
}
// Cache the file
if (md5filename) libs.fs.writeFileSync(md5filename, data);
libs.fs.writeFileSync(MP3FILE, data);
sayIt(MP3FILE, language, volume, process);
} else {
adapter.log.warn('File "' + text + '" not found');
sayFinished(0);
}
}
});
return;
}
}
if (!process) {
const time = Date.now();
// Workaround for double text
if (list.length > 1 && (list[list.length - 1].text === text) && (time - list[list.length - 1].time < 500)) {
adapter.log.warn('Same text in less than half a second.. Strange. Ignore it.');
return;
}
// If more time than 15 seconds
if (adapter.config.announce && !list.length && (!lastSay || (time - lastSay > adapter.config.annoTimeout * 1000))) {
// place as first the announce mp3
list.push({text: adapter.config.announce, language: language, volume: (volume || adapter.config.volume) / 2, time: time});
// and then text
list.push({text: text, language: language, volume: (volume || adapter.config.volume), time: time});
text = adapter.config.announce;
volume = Math.round((volume || adapter.config.volume) / 100 * adapter.config.annoVolume);
} else {
list.push({text: text, language: language, volume: (volume || adapter.config.volume), time: time});
if (list.length > 1) return;
}
}
adapter.log.info('saying: ' + text);
let isGenerate = false;
if (!language) {
language = adapter.config.engine;
}
if (!volume && adapter.config.volume) volume = adapter.config.volume;
// find out if say.mp3 must be generated
if (!speech2device.sayItIsPlayFile(text)) {
isGenerate = sayitOptions[adapter.config.type].mp3Required;
}
const speechFunction = speech2device.getFunction(adapter.config.type);
// If text first must be generated
if (isGenerate && sayLastGeneratedText !== '[' + language + ']' + text) {
sayLastGeneratedText = '[' + language + ']' + text;
text2speech.sayItGetSpeech(text, language, volume, (error, text, language, volume, duration) => {
speechFunction(error, text, language, volume, duration, sayFinished);
});
} else {
if (speech2device.sayItIsPlayFile(text)) {
text2speech.getLength(text, (error, duration) => {
speechFunction(error, text, language, volume, duration, sayFinished);
});
} else {
if (!isGenerate) {
speechFunction(null, text, language, volume, 0, sayFinished);
} else if (adapter.config.cache) {
md5filename = libs.path.join(options.cacheDir, libs.crypto.createHash('md5').update(language + ';' + text).digest('hex') + '.mp3');
if (libs.fs.existsSync(md5filename)) {
text2speech.getLength(md5filename, (error, duration) => {
speechFunction(error, md5filename, language, volume, duration, sayFinished);
});
} else {
sayLastGeneratedText = '[' + language + ']' + text;
text2speech.sayItGetSpeech(text, language, volume, (error, text, language, volume, duration) => {
speechFunction(error, text, language, volume, duration, sayFinished);
});
}
} else {
text2speech.getLength(MP3FILE, (error, duration) => {
speechFunction(error, text, language, volume, duration, sayFinished);
});
}
}
}
}
function uploadFile(file, callback) {
try {
const stat = libs.fs.statSync(libs.path.join(__dirname + '/mp3/', file));
if (!stat.isFile()) {
// ignore not a file
if (callback) callback();
return;
}
} catch (e) {
// ignore not a file
if (callback) callback();
return;
}
adapter.readFile(adapter.namespace, 'tts.userfiles/' + file, (err, data) => {
if (err || !data) {
try {
adapter.writeFile(adapter.namespace, 'tts.userfiles/' + file, libs.fs.readFileSync(libs.path.join(__dirname + '/mp3/', file)), () => {
if (callback) callback();
});
} catch (e) {
adapter.log.error('Cannot read file "' + __dirname + '/mp3/' + file + '": ' + e.toString());
if (callback) callback();
}
} else {
if (callback) callback();
}
});
}
function _uploadFiles(files, callback) {
if (!files || !files.length) {
adapter.log.info('All files uploaded');
if (callback) callback();
return;
}
uploadFile(files.pop(), () => setTimeout(_uploadFiles, 0, files, callback));
}
function uploadFiles(callback) {
if (libs.fs.existsSync(__dirname + '/mp3')) {
adapter.log.info('Upload announce mp3 files');
_uploadFiles(libs.fs.readdirSync(__dirname + '/mp3'), callback);
} else if (callback) {
callback();
}
}
function start() {
if (adapter.config.announce) {
adapter.config.annoDuration = parseInt(adapter.config.annoDuration) || 0;
adapter.config.annoTimeout = parseInt(adapter.config.annoTimeout) || 15;
adapter.config.annoVolume = parseInt(adapter.config.annoVolume) || 70; // percent from actual volume
if (!libs.fs.existsSync(libs.path.join(__dirname, adapter.config.announce))) {
adapter.readFile(adapter.namespace, 'tts.userfiles/' + adapter.config.announce, (err, data) => {
if (data) {
try {
libs.fs.writeFileSync(libs.path.join(__dirname, adapter.config.announce), data);
adapter.config.announce = libs.path.join(__dirname, adapter.config.announce);
} catch (e) {
adapter.log.error('Cannot write file: ' + e.toString());
adapter.config.announce = '';
}
}
});
} else {
adapter.config.announce = __dirname + '/' + adapter.config.announce;
}
}
// If cache enabled
if (adapter.config.cache) {
if (adapter.config.cacheDir && (adapter.config.cacheDir[0] === '/' || adapter.config.cacheDir[0] === '\\')) {
adapter.config.cacheDir = adapter.config.cacheDir.substring(1);
}
options.cacheDir = libs.path.join(__dirname, adapter.config.cacheDir);
if (options.cacheDir) {
options.cacheDir = options.cacheDir.replace(/\\/g, '/');
if (options.cacheDir[options.cacheDir.length - 1] === '/') {
options.cacheDir = options.cacheDir.substring(0, options.cacheDir.length - 1);
}
} else {
options.cacheDir = '';
}
const parts = options.cacheDir.split('/');
let i = 0;
while (i < parts.length) {
if (parts[i] === '..') {
parts.splice(i - 1, 2);
i--;
} else {
i++;
}
}
options.cacheDir = parts.join('/');
// Create cache dir if does not exist
if (!libs.fs.existsSync(options.cacheDir)) {
try {
mkpathSync(__dirname + '/', adapter.config.cacheDir);
} catch (e) {
adapter.log.error('Cannot create "' + options.cacheDir + '": ' + e.message);
}
} else {
let engine = '';
// Read the old engine
if (libs.fs.existsSync(libs.path.join(options.cacheDir, 'engine.txt'))) {
try {
engine = libs.fs.readFileSync(libs.path.join(options.cacheDir, 'engine.txt')).toString();
} catch (e) {
adapter.log.error('Cannot read file "' + libs.path.join(options.cacheDir, 'engine.txt') + ': ' + e.toString());
}
}
// If engine changed
if (engine !== adapter.config.engine) {
// Delete all files in this directory
const files = libs.fs.readdirSync(options.cacheDir);
for (let f = 0; f < files.length; f++) {
if (files[f] === 'engine.txt') continue;
if (libs.fs.existsSync(libs.path.join(options.cacheDir, files[f])) && libs.fs.lstatSync(libs.path.join(options.cacheDir, files[f])).isDirectory()) {
libs.fs.unlinkSync(libs.path.join(options.cacheDir, files[f]));
}
}
try {
libs.fs.writeFileSync(libs.path.join(options.cacheDir, 'engine.txt'), adapter.config.engine);
} catch (e) {
adapter.log.error('Cannot write file "' + libs.path.join(options.cacheDir, 'engine.txt') + ': ' + e.toString());
}
}
}
}
// Load libs
for (let j = 0; j < sayitOptions[adapter.config.type].libs.length; j++) {
libs[sayitOptions[adapter.config.type].libs[j]] = require(sayitOptions[adapter.config.type].libs[j]);
}
adapter.getState('tts.text', (err, state) => {
if (err || !state) {
adapter.setState('tts.text', '', true);
}
});
adapter.getState('tts.volume', (err, state) => {
if (err || !state) {
adapter.setState('tts.volume', 70, true);
if (adapter.config.type !== 'system') options.sayLastVolume = 70;
} else {
if (adapter.config.type !== 'system') options.sayLastVolume = state.val;
}
});
adapter.getState('tts.playing', (err, state) => {
if (err || !state) {
adapter.setState('tts.playing', false, true);
}
});
if (adapter.config.type === 'system') {
// Read volume
adapter.getState('tts.volume', (err, state) => {
if (!err && state) {
speech2device.sayItSystemVolume(state.val);
} else {
speech2device.sayItSystemVolume(70);
}
});
}
// calculate weblink for devices that require it
if ((adapter.config.type === 'sonos') ||
(adapter.config.type === 'chromecast') ||
(adapter.config.type === 'mpd') ||
(adapter.config.type === 'googleHome')) {
adapter.getForeignObject('system.adapter.' + adapter.config.web, (err, obj) => {
if (!err && obj && obj.native) {
options.webLink = 'http';
if (obj.native.auth) {
adapter.log.error('Cannot use server "' + adapter.config.web + '" with authentication for sonos/chromecast. Select other or create another one.');
} else {
if (obj.native.secure) options.webLink += 's';
options.webLink += '://';
if (obj.native.bind === 'localhost' || obj.native.bind === '127.0.0.1') {
adapter.log.error('Selected web server "' + adapter.config.web + '" is only on local device available. Select other or create another one.');
} else {
if (obj.native.bind === '0.0.0.0') {
options.webLink += adapter.config.webServer;
} else {
options.webLink += obj.native.bind;
}
}
options.webLink += ':' + obj.native.port;
}
} else {
adapter.log.error('Cannot read information about "' + adapter.config.web + '". No web server is active');
}
});
}
adapter.subscribeStates('*');
}
function main() {
libs.fs = require('fs');
libs.path = require('path');
if ((process.argv && process.argv.indexOf('--install') !== -1) ||
((!process.argv || process.argv.indexOf('--force') === -1) && (!adapter.common || !adapter.common.enabled))) {
adapter.log.info('Install process. Upload files and stop.');
// Check if files exists in datastorage
uploadFiles(() => adapter.stop ? adapter.stop() : process.exit());
} else {
// Check if files exists in datastorage
uploadFiles(start);
}
}

BIN
mp3/gong.mp3 Normal file

Binary file not shown.

BIN
mp3/scifi.mp3 Normal file

Binary file not shown.

View File

@@ -0,0 +1,998 @@
<!-- saved from url=(0040)http://www.nirsoft.net/utils/nircmd.html -->
<html><head><meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
<title>NirCmd - Windows command line tool</title>
<meta name="keywords" content="command-line,freeware,dial,monitor off,create shortcut,open cd-rom door,turn off,registry,change volume,mute">
<meta name="description" content="NirCmd is a small utility that allows you to do many useful tasks from command-line, without displaying any user interface: change your display settings, turn off your monitor, open the door of your CD-ROM drive, and more...">
<link rel="stylesheet" href="http://www.nirsoft.net/main.css">
<body leftmargin="0" topmargin="0" marginheight="0" marginwidth="0">
<table border="0" cellpadding="0" cellspacing="0" width="100%" height="100%">
<tbody><tr>
<td colspan="3" class="siteheader" height="50">
<table border="0" cellpadding="0" cellspacing="0" width="100%" height="100%">
<tbody><tr style="height:2">
<td>
</td><td>
</td></tr><tr>
<td>
<a href="http://www.nirsoft.net/">
</a>
</td><td>
</td></tr><tr style="height:4">
<td>
</td><td>
</td></tr></tbody></table>
</td>
</tr>
<tr>
<td valign="top" class="siteheader" style="width:140">
<table border="0" cellpadding="3" width="100%" style="width:140">
<tbody><tr class="menu1"><td nowrap=""><a href="http://www.nirsoft.net/">Main Page</a><br>
</td></tr>
<tr class="menu1"><td nowrap=""><a href="http://blog.nirsoft.net/">Blog</a><br>
</td></tr>
<tr class="menu1"><td nowrap=""><a href="http://www.nirsoft.net/search_freeware.html">Search</a><br>
</td></tr>
<tr class="menu1"><td nowrap=""><a href="http://www.nirsoft.net/faq.html">FAQ</a><br>
</td></tr>
<tr class="menu1"><td nowrap=""><a href="http://www.nirsoft.net/top_utilities_downloads.html">TOP 10</a><br>
</td></tr>
<tr class="menu1"><td nowrap=""><a href="http://www.nirsoft.net/freeware_links.html">Links</a><br>
</td></tr>
<tr class="menu1"><td nowrap=""><a href="http://www.nirsoft.net/awards.html">Awards</a><br>
</td></tr>
<tr class="menu1"><td nowrap=""><a href="http://www.nirsoft.net/pad">Pad Files</a><br>
</td></tr>
<tr class="menu1"><td nowrap=""><a href="http://www.nirsoft.net/contact-new.html">Contact</a><br>
</td></tr>
<tr class="menu1"><td nowrap=""><a href="http://www.nirsoft.net/about_nirsoft_freeware.html">About...</a><br>
</td></tr>
<tr class="menu1"><td nowrap=""><a href="http://www.nirsoft.net/donate.html" style="color:#000080">Donate</a><br>
</td></tr>
</tbody></table>
<br>
<table border="0" cellpadding="3" width="100%">
<tbody><tr class="menu2"><td nowrap=""><a href="http://www.nirsoft.net/utils/index.html">All Utilities</a><br>
</td></tr>
<tr class="menu2"><td nowrap=""><a href="http://www.nirsoft.net/password_recovery_tools.html">Password Tools</a><br>
</td></tr>
<tr class="menu2"><td nowrap=""><a href="http://www.nirsoft.net/system_tools.html">System Tools</a><br>
</td></tr>
<tr class="menu2"><td nowrap=""><a href="http://www.nirsoft.net/web_browser_tools.html">Browser Tools</a><br>
</td></tr>
<tr class="menu2"><td nowrap=""><a href="http://www.nirsoft.net/programmer_tools.html">Programmer Tools</a><br>
</td></tr>
<tr class="menu2"><td nowrap=""><a href="http://www.nirsoft.net/network_tools.html">Network Tools</a><br>
</td></tr>
<tr class="menu2"><td nowrap=""><a href="http://www.nirsoft.net/outlook_office_software.html">Outlook/Office</a><br>
</td></tr>
<tr class="menu2"><td nowrap=""><a href="http://www.nirsoft.net/x64_download_package.html">64-bit Download</a><br>
</td></tr>
<tr class="menu2"><td nowrap=""><a href="http://www.nirsoft.net/panel">Panel</a><br>
</td></tr>
<tr class="menu2"><td nowrap=""><a href="http://www.nirsoft.net/computer_forensic_software.html">Forensics</a><br>
</td></tr>
<tr class="menu2"><td nowrap=""><a href="http://www.nirsoft.net/code_samples.html">Code Samples</a><br>
</td></tr>
<tr class="menu2"><td nowrap=""><a href="http://www.nirsoft.net/articles">Articles</a><br>
</td></tr>
</tbody></table>
<p>
</p><center>
</center>
</td>
<td class="menutomain">&nbsp;
</td>
<td valign="top">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tbody><tr>
<td class="toptomain" colspan="3">
</td>
</tr><tr>
<td style="width:7">&nbsp;</td>
<td valign="top">
<table border="0" class="utilcaption">
<tbody><tr>
<td>NirCmd v2.75
<br>
Copyright (c) 2003 - 2013 Nir Sofer
</td></tr></tbody></table>
<p>
</p><h4 class="utilsubject">See Also</h4>
<ul>
<li><a href="http://www.nirsoft.net/utils/outlook_nk2_edit.html">NK2Edit</a> - Edit, merge and fix the AutoComplete files (.NK2) of Microsoft Outlook.
</li></ul>
<p>
<a name="description"></a></p><h4 class="utilsubject">Description</h4>
NirCmd is a small command-line utility that allows you to do some useful tasks without
displaying any user interface. By running NirCmd with simple command-line option, you can
write and delete values and keys in the Registry, write values into INI file, dial to your
internet account or connect to a VPN network, restart windows or shut down the computer,
create shortcut to a file, change the created/modified date of a file, change your display
settings, turn off your monitor, open the door of your CD-ROM drive, and more...
<p>
<a name="examples"></a></p><h4 class="utilsubject">Examples of what you can do with NirCmd</h4>
<table border="1" cellpadding="5">
<tbody><tr>
<td width="50%">Open the door of J: CD-ROM drive
</td><td nowrap="">
<span class="commandline">
nircmd.exe cdrom open j:
</span>
</td></tr><tr>
<td>Close the door of Y: CD-ROM drive
</td><td class="commandline" nowrap="">
nircmd.exe cdrom close y:
</td></tr><tr>
<td>Speaks the text currently in the clipboard (For Windows XP/Vista/7/8).
</td><td class="commandline" nowrap="">
speak text ~$clipboard$
</td></tr><tr>
<td>Speaks the text stored inside speak.txt into speak.wav filename (For Windows XP/Vista/7/8).
</td><td class="commandline" nowrap="">
speak file "c:\temp\speak.txt" 0 100 "c:\temp\speak.wav" 48kHz16BitStereo
</td></tr><tr>
<td>Increase the system volume by 2000 units (out of 65535)
</td><td class="commandline" nowrap="">
nircmd.exe changesysvolume 2000
</td></tr><tr>
<td>Decrease the system volume by 5000 units (out of 65535)
</td><td class="commandline" nowrap="">
nircmd.exe changesysvolume -5000
</td></tr><tr>
<td>Set the volume to the highest value
</td><td class="commandline" nowrap="">
nircmd.exe setsysvolume 65535
</td></tr><tr>
<td>Mute the system volume
</td><td class="commandline" nowrap="">
nircmd.exe mutesysvolume 1
</td></tr><tr>
<td>Unmute the system volume
</td><td class="commandline" nowrap="">
nircmd.exe mutesysvolume 0
</td></tr><tr>
<td>Switch the system volume between the mute and normal state.
</td><td class="commandline" nowrap="">
nircmd.exe mutesysvolume 2
</td></tr><tr>
<td>Create a shortcut on your desktop that switch the system volume between the mute and normal state.
</td><td class="commandline">
nircmd.exe cmdshortcut "~$folder.desktop$" "Switch Volume" mutesysvolume 2
</td></tr><tr>
<td>Turn off the monitor
</td><td class="commandline" nowrap="">
nircmd.exe monitor off
</td></tr><tr>
<td>Start the default screen saver
</td><td class="commandline" nowrap="">
nircmd.exe screensaver
</td></tr><tr>
<td>Put your computer in 'standby' mode
</td><td class="commandline" nowrap="">
nircmd.exe standby
</td></tr><tr>
<td>
log off the current user
</td><td class="commandline" nowrap="">
nircmd.exe exitwin logoff
</td></tr><tr>
<td>
Ask if you want to reboot, and if you answer 'Yes', reboot the computer.
</td><td class="commandline" nowrap="">
nircmd.exe qboxcom "Do you want to reboot ?" "question" exitwin reboot
</td></tr><tr>
<td>
Turn off your computer
</td><td class="commandline" nowrap="">
nircmd.exe exitwin poweroff
</td></tr><tr>
<td>
Turn off all computers specified in computers.txt !
</td><td class="commandline" nowrap="">
multiremote copy "c:\temp\computers.txt" exitwin poweroff force
</td></tr><tr>
<td>Dial to "My Internet" connection
</td><td class="commandline" nowrap="">
nircmd.exe rasdial "My Internet"
</td></tr><tr>
<td>Disconnect the "My Internet" connection
</td><td class="commandline" nowrap="">
nircmd.exe rashangup "My Internet"
</td></tr><tr>
<td>Make your Internet Explorer windows 75% transparent ! (192 / 256)
</td><td class="commandline" nowrap="">
nircmd.exe win trans ititle "internet explorer" 192
</td></tr><tr>
<td>Minimize all your Internet Explorer windows
</td><td class="commandline" nowrap="">
nircmd.exe win min class "IEFrame"
</td></tr><tr>
<td>Close all your Internet Explorer windows
</td><td class="commandline" nowrap="">
nircmd.exe win close class "IEFrame"
</td></tr><tr>
<td>Close all your Explorer windows (My Computer, folders, and so on)
</td><td class="commandline" nowrap="">
nircmd.exe win close class "CabinetWClass"
</td></tr><tr>
<td>Hide all your Internet Explorer windows
</td><td class="commandline" nowrap="">
nircmd.exe win hide class "IEFrame"
</td></tr><tr>
<td>Show all your Internet Explorer windows (after you made them hidden with previous example)
</td><td class="commandline" nowrap="">
nircmd.exe win show class "IEFrame"
</td></tr><tr>
<td>Center all top-level windows
</td><td class="commandline" nowrap="">
nircmd.exe win center alltop
</td></tr><tr>
<td>Remove the title bar of My Computer window.
</td><td class="commandline" nowrap="">
nircmd.exe win -style title "my computer" 0x00C00000
</td></tr><tr>
<td>Return the title bar of My Computer window that we removed in the previous example.
</td><td class="commandline" nowrap="">
nircmd.exe win +style title "my computer" 0x00C00000
</td></tr><tr>
<td>Set the My Computer window to right-to-left order (For hebrew and arabic languages)
</td><td class="commandline" nowrap="">
nircmd win +exstyle title "my computer" 0x00400000
</td></tr><tr>
<td>Set all child windows of My Computer window to right-to-left order (For hebrew and arabic languages)
</td><td class="commandline" nowrap="">
nircmd win child title "my computer" +exstyle all 0x00400000
</td></tr><tr>
<td>Create a shortcut on your desktop that closes all your Internet Explorer windows
</td><td class="commandline">
nircmd.exe cmdshortcut " "~$folder.desktop$ "Close All IE" win close class "IEFrame"
</td></tr><tr>
<td>Create a shortcut on your desktop that hides all your Internet Explorer windows
</td><td class="commandline">
nircmd.exe cmdshortcut " "~$folder.desktop$ "Hide All IE" win hide class "IEFrame"
</td></tr><tr>
<td>Create a shortcut on your desktop that shows back all your Internet Explorer windows
</td><td class="commandline">
nircmd.exe cmdshortcut " "~$folder.desktop$ "Show All IE" win show class "IEFrame"
</td></tr><tr>
<td>Set the Windows Calculator as top-most window (above all other windows)
</td><td class="commandline" nowrap="">
nircmd.exe win settopmost title "Calculator" 1
</td></tr><tr>
<td>Set the Windows Calculator back to regular window (non top-most window)
</td><td class="commandline" nowrap="">
nircmd.exe win settopmost title "Calculator" 0
</td></tr><tr>
<td>Create a shortcut to Windows calculator under Start Menu-&gt;Programs-&gt;Calculators
</td><td class="commandline">
nircmd.exe shortcut "f:\winnt\system32\calc.exe" "~$folder.programs$\Calculators" "Windows Calculator"
</td></tr><tr>
<td>Hide the desktop window
</td><td class="commandline" nowrap="">
nircmd.exe win hide class progman
</td></tr><tr>
<td>Show the desktop window (After hiding it in previous example)
</td><td class="commandline" nowrap="">
nircmd.exe win show class progman
</td></tr><tr>
<td>Hide the start button on the system tray
</td><td class="commandline" nowrap="">
nircmd.exe win child class "Shell_TrayWnd" hide class "button"
</td></tr><tr>
<td>Show the start button on the system tray
</td><td class="commandline" nowrap="">
nircmd.exe win child class "Shell_TrayWnd" show class "button"
</td></tr><tr>
<td>Hide the clock on the system tray
</td><td class="commandline" nowrap="">
nircmd.exe win child class "Shell_TrayWnd" hide class "TrayClockWClass"
</td></tr><tr>
<td>Show the clock on the system tray
</td><td class="commandline" nowrap="">
nircmd.exe win child class "Shell_TrayWnd" show class "TrayClockWClass"
</td></tr><tr>
<td>Kill (terminate) all instance of Internet Explorer processes
</td><td class="commandline" nowrap="">
nircmd.exe killprocess iexplore.exe
</td></tr><tr>
<td>Create a shortcut on your desktop that opens the door of K: CDROM drive when you run it.
</td><td class="commandline">
nircmd.exe cmdshortcut "~$folder.desktop$" "Open CDROM" cdrom open k:
</td></tr><tr>
<td>Create a shortcut to NirSoft Web site on your desktop
</td><td class="commandline" nowrap="">
nircmd.exe urlshortcut "http://www.nirsoft.net" "~$folder.desktop$" "NirSoft"
</td></tr><tr>
<td>Add NirSoft Web site to your Favorities under Links folder.
</td><td class="commandline">
nircmd.exe urlshortcut "http://www.nirsoft.net" "~$folder.favorites$\Links" "NirSoft"
</td></tr><tr>
<td>Create a shortcut to NirSoft Web site on the desktop of all computers listed in computers.txt
</td><td class="commandline">
nircmd.exe multiremote copy "c:\temp\computers.txt" urlshortcut "http://www.nirsoft.net" "~$folder.common_desktop$" "NirSoft"
</td></tr><tr>
<td>Set the display mode to 800x600x24bit colors
</td><td class="commandline" nowrap="">
nircmd.exe setdisplay 800 600 24
</td></tr><tr>
<td>Create a shortcut on the desktop that set the display mode to 800x600x24bit colors
</td><td class="commandline">
nircmd.exe cmdshortcut "~$folder.desktop$" "800x600x24" setdisplay 800 600 24
</td></tr><tr>
<td>Copy all shortcuts on your desktop to another folder (f:\temp\desktop).
</td><td class="commandline">
nircmd.exe execmd copy "~$folder.desktop$\*.lnk" f:\temp\desktop
</td></tr><tr>
<td>Restart your Apache server (under Windows NT/2000/XP/2003)
</td><td class="commandline" nowrap="">
nircmd.exe service restart apache
</td></tr><tr>
<td>Create a shortcut on your desktop that restarts the Apache server
</td><td class="commandline">
nircmd.exe cmdshortcut "~$folder.desktop$" "Restart Apache" service restart apache
</td></tr><tr>
<td>Restart your IIS
</td><td class="commandline" nowrap="">
nircmd.exe service restart w3svc
</td></tr><tr>
<td>Restart MySql
</td><td class="commandline" nowrap="">
nircmd.exe service restart MySql
</td></tr><tr>
<td>Open the desired Registry key/value in RegEdit
</td><td class="commandline">
nircmd.exe regedit "HKLM\Software\Microsoft\Windows\CurrentVersion" "CommonFilesDir"
</td></tr><tr>
<td>Open the Registry key that you copied to the clipboard in RegEdit.
</td><td class="commandline">
nircmd regedit "~$clipboard$"
</td></tr><tr>
<td>Disable the screen saver
</td><td class="commandline" nowrap="">
nircmd.exe regsetval sz "HKCU\control panel\desktop" "ScreenSaveActive" 0
</td></tr><tr>
<td>Enable the screen saver
</td><td class="commandline" nowrap="">
nircmd.exe regsetval sz "HKCU\control panel\desktop" "ScreenSaveActive" 1
</td></tr><tr>
<td>Change the date/time of the specified filename (creation time and modified time)
</td><td class="commandline">
nircmd.exe setfiletime "c:\temp\myfile.txt" "24-06-2003 17:57:11" "22-11-2005 10:21:56"
</td></tr><tr>
<td>Copy your desktop folder path to the clipboard
</td><td class="commandline">
nircmd.exe clipboard set ~$folder.desktop$
</td></tr><tr>
<td>Copy your start menu folder path to the clipboard
</td><td class="commandline">
nircmd.exe clipboard set ~$folder.start_menu$
</td></tr><tr>
<td>Copy the content of info1.txt (simple text file) to the clipboard
</td><td class="commandline">
nircmd.exe clipboard readfile "c:\My Files\info1.txt"
</td></tr><tr>
<td>Add the text content of clipboard to info1.txt
</td><td class="commandline">
nircmd.exe clipboard addfile "c:\My Files\info1.txt"
</td></tr><tr>
<td>Clear the clipboard
</td><td class="commandline">
nircmd.exe clipboard clear
</td></tr><tr>
<td>Create all folders specified in "c:\temp\folders.txt". The folder path names are
separated by CRLF characters.
</td><td class="commandline">
nircmd.exe paramsfile "c:\temp\folders.txt" "" "" execmd md ~$fparam.1$
</td></tr><tr>
<td>Install the specified .NET assembly in the global assembly cache (like gacutil)
</td><td class="commandline">
nircmd.exe gac install "C:\temp\MyAssembly\bin\MyAssembly.dll"
</td></tr><tr>
<td>Empty the recycle bin in all drives.
</td><td class="commandline" nowrap="">
nircmd.exe emptybin
</td></tr><tr>
<td>Answer 'Yes' to a standard Windows message-box.
</td><td class="commandline" nowrap="">
nircmd.exe dlg "" "" click yes
</td></tr><tr>
<td>Wait 2 seconds, and then save the current screen to shot.png
</td><td class="commandline" nowrap="">
nircmd.exe cmdwait 2000 savescreenshot "f:\temp\shot.png"
</td></tr><tr>
<td>Save 10 screenshots in a loop, and wait 60 seconds between the screenshot save calls.
The filenames of the screenshot will contain the time and date of the saved screenshot.
</td><td class="commandline">
nircmd.exe loop 10 60000 savescreenshot c:\temp\scr~$currdate.MM_dd_yyyy$-~$currtime.HH_mm_ss$.png
</td></tr><tr>
<td>
Wait until Firefox is closed, and then say "Firefox was closed"
</td><td class="commandline" nowrap="">
nircmd.exe waitprocess firefox.exe speak text "Firefox was closed"
</td></tr><tr>
<td>
Run RegEdit with SYSTEM user (On Windows 7/8/Vista/2008), which allows you to view all hidden system Registry keys
</td><td class="commandline" nowrap="">
nircmd.exe elevatecmd runassystem c:\windows\regedit.exe
</td></tr></tbody></table>
<p>
<a name="sysreq"></a></p><h4 class="utilsubject">System Requirements</h4>
This utility can work in all versions of Windows operating system: Windows 9x/ME,
Windows NT, Windows 2000, Windows XP, Windows Server 2003, Windows Vista, Windows Server 2008, Windows 7, and Windows 8.
<a name="verhistory"></a><h4 class="utilsubject">Versions History</h4>
<table border="1" cellpadding="5">
<tbody><tr class="utiltableheader"><th>Date </th><th> Version </th><th> Description
</th></tr><tr><td>10/08/2013</td><td>2.75</td><td>
<ul>
<li>Added new commands: moverecyclebin, suspendprocess , resumeprocess.
</li><li>Added etitle option to the find parameter of win command.
</li><li>'clipboard readfile' command now reads Unicode files (UTF-8 and UTF-16).
</li></ul>
</td></tr><tr><td>29/10/2012</td><td>2.71</td><td>
<ul>
<li>Added runassystem command, which allows you to run a process with SYSTEM user (Works on Windows 7/8/Vista/2008)
</li></ul>
</td></tr><tr><td>12/09/2012</td><td>2.70</td><td>
<ul>
<li>Added new option to speak command: You can now save the entire speach into a .wav file that can be played with any
media player (Instead of playing it directly to your sound card).
</li><li>Added restartexplorer command, which simply restarts Windows Explorer gracefully.
</li><li>Added screensavertimeout command, which allows you to set the screensaver timeout, in seconds.
</li><li>Added togglemax, togglemin, togglehide, toggledisable actions to win command.
</li><li>Added SystemSounds support for setappvolume, changeappvolume, and muteappvolume commands.
</li><li>Fixed issue on Windows 7: 'win close alltopnodesktop' command caused a shutdown dialog-box to appear.
</li><li>Fixed the setprimarydisplay to work properly.
</li></ul>
</td></tr><tr><td>28/06/2011</td><td>2.65</td><td>
<ul>
<li>Added setdefaultsounddevice command (for Windows 7/Vista/2008 only), which
allows you to set the default sound device.
</li><li>Added setsubunitvolumedb command (for Windows 7/Vista/2008 only), which
allows you to set the volume of sound device subunits (Line In, Microphone, and so on)
</li><li>Added mutesubunitvolume command (for Windows 7/Vista/2008 only), which
allows you to mute/unmute the volume of sound device subunits (Line In, Microphone, and so on)
</li></ul>
</td></tr><tr><td>21/06/2011</td><td>2.60</td><td>
<ul>
<li>Added mediaplay command for playing audio file (.mp3, .wav, and so on) for the
specified number of milliseconds.
</li><li>Added changebrightness and setbrightness commands for setting the
screen brightness on laptops and netbook computers.
</li></ul>
</td></tr><tr><td>08/06/2011</td><td>2.55</td><td>
<ul>
<li>On savescreenshot, savescreenshotfull, and savescreenshotwin commands,
you can now specify *clipboard* as a filename, in order to save the screenshow into the clipboard
</li><li>On savescreenshot command, you can now specify a rectangle to capture as x, y, width, and height instead of
capturing the entire screen.
</li><li>Fixed savescreenshotfull command to work properly on multiple screen system with negative coordinates.
</li></ul>
</td></tr><tr><td>27/03/2011</td><td>2.52</td><td>
<ul>
<li>Added 'runas' command, which allows you to run a new process with the specified user name/password.
</li><li>Added 'Flash' action to the win command, with allows you to flash the specified window.
</li></ul>
</td></tr><tr><td>13/03/2011</td><td>2.51</td><td>
<ul>
<li>Added 'focused' option for setappvolume, changeappvolume, and muteappvolume commands, which allow you to easily
change the volume of the application that is currently in focus, on Windows 7/Vista/2008.
</li><li>Added 'default_record' option for
setsysvolume, changesysvolume, setsysvolume2, changesysvolume2, and mutesysvolume commands, which allows you set easily
change the volume of default recording device, on Windows 7/Vista/2008.
</li></ul>
</td></tr><tr><td>24/02/2011</td><td>2.50</td><td>
<ul>
<li>Added 3 new sound volume commands to change or mute application volume on Windows 7/Vista/2008:
setappvolume, changeappvolume, muteappvolume
</li><li>Added new information command that shows you a message box with all sound devices on your Windows 7/Vista/2008 system:
showsounddevices.
</li></ul>
</td></tr><tr><td>22/02/2011</td><td>2.48</td><td>
<ul>
<li>Fixed the problems with the timeout of the tray balloon in trayballoon command.
</li></ul>
</td></tr><tr><td>20/01/2011</td><td>2.47</td><td>
<ul>
<li>Fixed bug: The sendkeypress command didn't work properly with Fxx keys (F1, F2, and so on).
</li></ul>
</td></tr><tr><td>28/11/2010</td><td>2.46</td><td>
<ul>
<li>Added option to locate window by process in win command.
</li><li>Added max and min options (Minimized and maximized windows) into exec and exec2 commands.
</li><li>Added x64 builds of nircmd.exe and nircmdc.exe
</li></ul>
</td></tr><tr><td>18/10/2010</td><td>2.45</td><td>
<ul>
<li>Added trayballoon command - displays a tray balloon with your desired text and icon.
</li><li>Added sendkeypress command - Alternative to the sendkey command. Allows you to send key press
combination in much easier way, for example: sendkeypress ctrl+shift+esc
</li><li>Added clonefiletime command to clone the date/time of existing file into one or more files.
</li><li>Added setconsolemode command to set the console display mode - window or full screen.
</li><li>Added setconsolecolor command to set the foreground and background colors of the console text.
</li><li>Added consolewrite command to write text into the console window.
</li><li>Added debugwrite for writing text into the debug output.
</li><li>Added ~$folder.mydocuments$ variable.
</li></ul>
</td></tr><tr><td>21/02/2010</td><td>2.41</td><td>
<ul>
<li>Fixed a bug in elevate command: when running a program without parameters, NirCmd sent the elevate command string as a parameter of the program.
</li></ul>
</td></tr><tr><td>16/02/2010</td><td>2.40</td><td>
<ul>
<li>Added elevate and elevatecmd commands to run and execute commands with administrator rights under Windows 7/Vista/2008.
</li><li>Added monitor parameter to setdisplay command, for using it with multiple monitors.
</li><li>Added setprimarydisplay command.
</li><li>Added folder.programfiles and folder.common_programfiles variables.
</li><li>Added async_off, async_on, async_low for monitor command. You can try them if NirCmd.exe remains in memory
when you use monitor on/off command.
</li></ul>
</td></tr><tr><td>26/05/2009</td><td>2.37</td><td>
<ul>
<li>Improved the help .chm file - Now it contains a link to every command.
</li><li>Added help command - Automatically opens the reference of the desired command in <a href="http://nircmd.nirsoft.net/" target="nircmd">http://nircmd.nirsoft.net</a>.
For example, if you type 'nircmd.exe help speak', the reference page of speak command - <a href="http://nircmd.nirsoft.net/speak.html" target="speak">http://nircmd.nirsoft.net/speak.html</a> will be opened in your default browser.
</li></ul>
</td></tr><tr><td>19/05/2009</td><td>2.36</td><td>
<ul>
<li>Fixed bug in script: Sometimes the last line in the script was not executed properly.
</li></ul>
</td></tr><tr><td>20/04/2009</td><td>2.35</td><td>
<ul>
<li>Added loop command - execute a command multiple times.
</li><li>Added currtime and currdate variables.
</li><li>waitprocess - Added option to execute a NirCmd command after the process was closed.
</li><li>Added rem (or ;) command, just for adding remarks in a script.
</li></ul>
</td></tr><tr><td>01/04/2009</td><td>2.30</td><td>
<ul>
<li>Added speak command - Speaks the contents of the text or file that you specify, by using the Speech library (SAPI) that comes with Windows XP and Windows Vista.
</li><li>Added savescreenshotfull command.
</li></ul>
</td></tr><tr><td>02/02/2009</td><td>2.25</td><td>
<ul>
<li>New actions for clipboard command - loadclp and saveclp (load/save in Windows .clp format)
</li><li>New command: setprocessaffinity
</li></ul>
</td></tr><tr><td>01/01/2009</td><td>2.20</td><td>
<ul>
<li>New commands: waitprocess, setprocesspriority, qboxtop, qboxcomtop.
</li><li>New action in clipboard command: copyimage (Copy image file to the clipboard)
</li></ul>
</td></tr><tr><td>25/06/2008</td><td>2.15</td><td>
<ul>
<li>New commands: setcursorwin, savescreenshotwin, filldelete
</li></ul>
</td></tr><tr><td>12/04/2008</td><td>2.10</td><td>
<ul>
<li>New commands: shellcopy, savescreenshot
</li></ul>
</td></tr><tr><td>09/02/2008</td><td>2.05</td><td>
<ul>
<li>New commands: shellrefresh, convertimage, convertimages
</li><li>New option in clipboard command - saveimage
</li></ul>
</td></tr><tr><td>16/06/2007</td><td>2.00</td><td>
<ul>
<li>All sound volume commands and monitor command now works under Vista.
</li><li>New commands: dlg, dlgany, sendkey, sendmouse, returnval
</li><li>New actions in win command - dlgclick, dlgsettext, dlgsetfocus
</li></ul>
</td></tr><tr><td>24/07/2006</td><td>1.85</td><td>
<ul>
<li>New command: cmdshortcutkey
</li></ul>
</td></tr><tr><td>15/07/2006</td><td>1.84</td><td>
<ul>
<li>Starting from this version, if you don't specify a sound device index in setsysvolume, changesysvolume, setsysvolume2, changesysvolume2 and mutesysvolume commands,
the default sound device in control panel ("preferred device") is used.
</li><li>New find options in win command: active, foreground, desktop
</li></ul>
</td></tr><tr><td>20/05/2006</td><td>1.83</td><td>
<ul>
<li>Fixed bug with parameter variables: parameter dialog-box appear more than once.
</li><li>New find option in Win command: alltopnodesktop
</li></ul>
</td></tr><tr><td>18/02/2006</td><td>1.82</td><td>
<ul>
<li>New win actions: postmsg and sendmsg.
</li><li>New command: closeprocess
</li><li>NirCmd now returns a non-zero value on error.
</li></ul>
</td></tr><tr><td>19/11/2005</td><td>1.81</td><td>
<ul>
<li>New command: emptybin - Empty the Recycle Bin.
</li><li>New optional parameter in setdisplay command: refresh rate.
</li><li>rashangup command without parameter - disconnect all active connections.
</li></ul>
</td></tr><tr><td>24/09/2005</td><td>1.80</td><td>
<ul>
<li>New commands: exec2, cmdwait.
</li><li>New options in clipboard command.
</li><li>Volume commands (setsysvolume, changesysvolume, setsysvolume2, changesysvolume2, mutesysvolume) now allows you to change the volume of non-default sound card.
</li></ul>
</td></tr><tr><td>07/07/2005</td><td>1.70</td><td>
<ul>
<li>New win actions: activate, hideshow, +style, -style, +exstyle, -exstyle
</li><li>New commands: movecursor, setcursor.
</li><li>New command: setfilefoldertime - allows you to change the date/time of folders, under Windows 2000/XP only.
</li></ul>
</td></tr><tr><td>10/06/2005</td><td>1.62</td><td>
<ul>
<li>New actions in win command: focus, enable, disable
</li></ul>
</td></tr><tr><td>26/05/2005</td><td>1.61</td><td>
<ul>
<li>new command: sysrefresh - make a general system refresh after modifying your system settings in the Registry.
</li></ul>
</td></tr><tr><td>07/05/2005</td><td>1.60</td><td>
<ul>
<li>New paramsfile command: Allows you to execute a command multiple times by loading the parameters from a text file.
</li><li>New actions in win command: move, center, settext.
</li><li>New 'child' action in win command: Allows you to move/hide child windows (For example: the start button in the system tray)
</li><li>New clipboard command: Allows you to put a string in the clipboard, and clear the clipboard.
</li><li>New gac command: Allows you to install assemblies in Global Assembly Cache.
</li></ul>
</td></tr><tr><td>28/12/2004</td><td>1.56</td><td>
<ul>
<li>New variable: ~$clipboard$ - paste the text that you copied to the clipboard into the command-line.
</li><li>Parameter variables: ~$param.ParamName$ - Display an input window for typing the text that you want insert into the command-line.
</li></ul>
</td></tr><tr><td>07/12/2004</td><td>1.55</td><td>
Added RegEdit command.
</td></tr><tr><td>18/11/2004</td><td>1.54</td><td>
<ul>
<li>On reg commands (regsetval, regdelval, regdelkey), you can now also use the following
abbreviated key name: HKCU for HKEY_CURRENT_USER, HKLM for HKEY_LOCAL_MACHINE, HKCR for HKEY_CLASSES_ROOT, HKU for HKEY_USERS,
HKCC for HKEY_CURRENT_CONFIG.
</li><li>redraw action for win command.
</li></ul>
</td></tr><tr><td>03/11/2004</td><td>1.53</td><td>
Fixed 'monitor off' problem in XP SP2.
</td></tr><tr><td>09/10/2004</td><td>1.52</td><td>
rasapi32.dll is now loaded only when using the dial commands (rasdial, rasdialdlg, rashangup).
In previous versions, NirCmd was statically linked to rasapi32.dll, and that caused problems in old NT systems.
</td></tr><tr><td>29/09/2004</td><td>1.51</td><td>
Variable names are now enclosed with '$' char instead of '%' char.
<br>
I made this change because the '%' char causes problems when running NirCmd from cmd/bat file.
using the '%' char for variable names is still supported for backward compatibility.
</td></tr><tr><td>22/09/2004</td><td>1.50</td><td>
<ul>
<li>New commands: cmdshortcut, regsvr, mutesysvolume, changesysvolume, changesysvolume2, setsysvolume2.
</li><li>New actions in win command: trans, setsize, settopmost, ititle.
</li><li>New variables: folder.nircmd, nir.exefile
</li><li>nircmdc.exe - console version of NirCmd.
</li><li>Changed name - from NirComLime to NirCmd.
</li></ul>
</td></tr><tr><td>05/05/2004</td><td>1.40</td><td>
<ul>
<li>New commands: wait, setvolume, setsysvolume, execmd, exec.
</li><li>New commands for executing NirCmd on remote machines: remote and multiremote.
</li><li>Folder and system variables.
</li><li>New option in setfiletime command - set the file time to the current time (now parameter)
</li><li>New parameter in shortcut command - hot key.
</li></ul>
</td></tr><tr><td>01/03/2004</td><td>1.30</td><td>
<ul>
<li>New commands: qboxcom, beep, stdbeep, cdrom, urlshortcut, monitor, screensaver, standby, and hibernate.
</li><li>Fixed the bug with ~x string sequences.
</li></ul>
</td></tr><tr><td>08/01/2004</td><td>1.20</td><td>
<ul>
<li>New commands: setdialuplogon, script
</li><li>Special string sequences (~q, ~t, ~n and so on) to allow you to embed quotes and new line characters into the parameters of any command.
</li></ul>
</td></tr><tr><td>19/11/2003</td><td>1.11</td><td>
New options in shortcut command
</td></tr><tr><td>10/10/2003</td><td>1.10</td><td>
New commands: killprocess, service, memdump, win, lockws.
</td></tr><tr><td>09/09/2003</td><td>1.00</td><td>
First release.
</td></tr></tbody></table>
<p>
</p><h4 class="utilsubject">License</h4>
This utility is released as freeware.
You are allowed to freely distribute this utility via floppy disk, CD-ROM,
Internet, or in any other way, as long as you don't charge anything for this.
If you distribute this utility, you must include all files in
the distribution package, without any modification !
<p>
<table border="0" cellpadding="10" width="100%">
<tbody><tr><td align="center">
<a class="downloadline" href="http://www.nirsoft.net/utils/nircmd2.html#using">View NirCmd Full Help File</a>
</td></tr><tr><td align="center">
<a class="downloadline" href="http://www.nirsoft.net/utils/nircmd.zip">Download NirCmd</a>
</td></tr><tr><td align="center">
<a class="downloadline" href="http://www.nirsoft.net/utils/nircmd-x64.zip">Download NirCmd 64-bit</a>
</td></tr></tbody></table>
</p><p>
</p><p>
<table>
<tbody><tr>
<td>
&nbsp;
</td></tr></tbody></table>
</p><p>
<table>
<tbody><tr>
<td>
&nbsp;
</td></tr></tbody></table>
</p><p>
</p><center>
<table cellspacing="5">
<tbody><tr>
<td>
</td></tr></tbody></table>
</center>
<p>
<table>
<tbody><tr>
<td>
&nbsp;
</td></tr></tbody></table>
</p><p>
</p><p>
</p></td>
<td width="10">&nbsp;&nbsp;</td>
</tr>
</tbody></table>
<p>
</p></td>
</tr>
</tbody></table>
</body></html>

BIN
nircmd/NirCmd.chm Normal file

Binary file not shown.

6
nircmd/license.txt Normal file
View File

@@ -0,0 +1,6 @@
NirCmd v2.75
Copyright (c) 2003 - 2013 Nir Sofer
License
This utility is released as freeware. You are allowed to freely distribute this utility via floppy disk, CD-ROM, Internet, or in any other way, as long as you don't charge anything for this. If you distribute this utility, you must include all files in the distribution package, without any modification !

BIN
nircmd/nircmd.exe Normal file

Binary file not shown.

BIN
nircmd/nircmdc.exe Normal file

Binary file not shown.

43
package.json Normal file
View File

@@ -0,0 +1,43 @@
{
"name": "yunkong2.sayit",
"version": "1.7.1",
"description": "Text to speech interface for yunkong2.",
"author": {
"name": "bluefox",
"email": "dogafox@gmail.com"
},
"homepage": "https://git.spacen.net/yunkong2/yunkong2.sayit",
"keywords": [
"yunkong2",
"text to speech",
"TTS",
"Voice synthesis"
],
"repository": {
"type": "git",
"url": "https://git.spacen.net/yunkong2/yunkong2.sayit"
},
"optionalDependencies": {
"castv2-client": "^1.2.0",
"mdns": "^2.4.0"
},
"dependencies": {
"jsftp": "^2.1.3",
"aws-sdk": "~2.253.1"
},
"devDependencies": {
"gulp": "^3.9.1",
"mocha": "^4.1.0",
"chai": "^4.1.2"
},
"bugs": {
"url": "https://git.spacen.net/yunkong2/yunkong2.sayit/issues"
},
"main": "main.js",
"scripts": {
"test": "node node_modules/mocha/bin/mocha --exit",
"install": "node main.js --install"
},
"license": "MIT",
"readmeFilename": "README.md"
}

BIN
say/SayStatic.exe Normal file

Binary file not shown.

728
test/lib/setup.js Normal file
View File

@@ -0,0 +1,728 @@
/* jshint -W097 */// jshint strict:false
/*jslint node: true */
// check if tmp directory exists
var fs = require('fs');
var path = require('path');
var child_process = require('child_process');
var rootDir = path.normalize(__dirname + '/../../');
var pkg = require(rootDir + 'package.json');
var debug = typeof v8debug === 'object';
pkg.main = pkg.main || 'main.js';
var adapterName = path.normalize(rootDir).replace(/\\/g, '/').split('/');
adapterName = adapterName[adapterName.length - 2];
var adapterStarted = false;
function getAppName() {
var parts = __dirname.replace(/\\/g, '/').split('/');
return parts[parts.length - 3].split('.')[0];
}
var appName = getAppName().toLowerCase();
var objects;
var states;
var pid = null;
function copyFileSync(source, target) {
var targetFile = target;
//if target is a directory a new file with the same name will be created
if (fs.existsSync(target)) {
if ( fs.lstatSync( target ).isDirectory() ) {
targetFile = path.join(target, path.basename(source));
}
}
try {
fs.writeFileSync(targetFile, fs.readFileSync(source));
}
catch (err) {
console.log("file copy error: " +source +" -> " + targetFile + " (error ignored)");
}
}
function copyFolderRecursiveSync(source, target, ignore) {
var files = [];
var base = path.basename(source);
if (base === adapterName) {
base = pkg.name;
}
//check if folder needs to be created or integrated
var targetFolder = path.join(target, base);
if (!fs.existsSync(targetFolder)) {
fs.mkdirSync(targetFolder);
}
//copy
if (fs.lstatSync(source).isDirectory()) {
files = fs.readdirSync(source);
files.forEach(function (file) {
if (ignore && ignore.indexOf(file) !== -1) {
return;
}
var curSource = path.join(source, file);
var curTarget = path.join(targetFolder, file);
if (fs.lstatSync(curSource).isDirectory()) {
// ignore grunt files
if (file.indexOf('grunt') !== -1) return;
if (file === 'chai') return;
if (file === 'mocha') return;
copyFolderRecursiveSync(curSource, targetFolder, ignore);
} else {
copyFileSync(curSource, curTarget);
}
});
}
}
if (!fs.existsSync(rootDir + 'tmp')) {
fs.mkdirSync(rootDir + 'tmp');
}
function storeOriginalFiles() {
console.log('Store original files...');
var dataDir = rootDir + 'tmp/' + appName + '-data/';
var f = fs.readFileSync(dataDir + 'objects.json');
var objects = JSON.parse(f.toString());
if (objects['system.adapter.admin.0'] && objects['system.adapter.admin.0'].common) {
objects['system.adapter.admin.0'].common.enabled = false;
}
if (objects['system.adapter.admin.1'] && objects['system.adapter.admin.1'].common) {
objects['system.adapter.admin.1'].common.enabled = false;
}
fs.writeFileSync(dataDir + 'objects.json.original', JSON.stringify(objects));
try {
f = fs.readFileSync(dataDir + 'states.json');
fs.writeFileSync(dataDir + 'states.json.original', f);
}
catch (err) {
console.log('no states.json found - ignore');
}
}
function restoreOriginalFiles() {
console.log('restoreOriginalFiles...');
var dataDir = rootDir + 'tmp/' + appName + '-data/';
var f = fs.readFileSync(dataDir + 'objects.json.original');
fs.writeFileSync(dataDir + 'objects.json', f);
try {
f = fs.readFileSync(dataDir + 'states.json.original');
fs.writeFileSync(dataDir + 'states.json', f);
}
catch (err) {
console.log('no states.json.original found - ignore');
}
}
function checkIsAdapterInstalled(cb, counter, customName) {
customName = customName || pkg.name.split('.').pop();
counter = counter || 0;
var dataDir = rootDir + 'tmp/' + appName + '-data/';
console.log('checkIsAdapterInstalled...');
try {
var f = fs.readFileSync(dataDir + 'objects.json');
var objects = JSON.parse(f.toString());
if (objects['system.adapter.' + customName + '.0']) {
console.log('checkIsAdapterInstalled: ready!');
setTimeout(function () {
if (cb) cb();
}, 100);
return;
} else {
console.warn('checkIsAdapterInstalled: still not ready');
}
} catch (err) {
}
if (counter > 20) {
console.error('checkIsAdapterInstalled: Cannot install!');
if (cb) cb('Cannot install');
} else {
console.log('checkIsAdapterInstalled: wait...');
setTimeout(function() {
checkIsAdapterInstalled(cb, counter + 1);
}, 1000);
}
}
function checkIsControllerInstalled(cb, counter) {
counter = counter || 0;
var dataDir = rootDir + 'tmp/' + appName + '-data/';
console.log('checkIsControllerInstalled...');
try {
var f = fs.readFileSync(dataDir + 'objects.json');
var objects = JSON.parse(f.toString());
if (objects['system.adapter.admin.0']) {
console.log('checkIsControllerInstalled: installed!');
setTimeout(function () {
if (cb) cb();
}, 100);
return;
}
} catch (err) {
}
if (counter > 20) {
console.log('checkIsControllerInstalled: Cannot install!');
if (cb) cb('Cannot install');
} else {
console.log('checkIsControllerInstalled: wait...');
setTimeout(function() {
checkIsControllerInstalled(cb, counter + 1);
}, 1000);
}
}
function installAdapter(customName, cb) {
if (typeof customName === 'function') {
cb = customName;
customName = null;
}
customName = customName || pkg.name.split('.').pop();
console.log('Install adapter...');
var startFile = 'node_modules/' + appName + '.js-controller/' + appName + '.js';
// make first install
if (debug) {
child_process.execSync('node ' + startFile + ' add ' + customName + ' --enabled false', {
cwd: rootDir + 'tmp',
stdio: [0, 1, 2]
});
checkIsAdapterInstalled(function (error) {
if (error) console.error(error);
console.log('Adapter installed.');
if (cb) cb();
});
} else {
// add controller
var _pid = child_process.fork(startFile, ['add', customName, '--enabled', 'false'], {
cwd: rootDir + 'tmp',
stdio: [0, 1, 2, 'ipc']
});
waitForEnd(_pid, function () {
checkIsAdapterInstalled(function (error) {
if (error) console.error(error);
console.log('Adapter installed.');
if (cb) cb();
});
});
}
}
function waitForEnd(_pid, cb) {
if (!_pid) {
cb(-1, -1);
return;
}
_pid.on('exit', function (code, signal) {
if (_pid) {
_pid = null;
cb(code, signal);
}
});
_pid.on('close', function (code, signal) {
if (_pid) {
_pid = null;
cb(code, signal);
}
});
}
function installJsController(cb) {
console.log('installJsController...');
if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller') ||
!fs.existsSync(rootDir + 'tmp/' + appName + '-data')) {
// try to detect appName.js-controller in node_modules/appName.js-controller
// travis CI installs js-controller into node_modules
if (fs.existsSync(rootDir + 'node_modules/' + appName + '.js-controller')) {
console.log('installJsController: no js-controller => copy it from "' + rootDir + 'node_modules/' + appName + '.js-controller"');
// copy all
// stop controller
console.log('Stop controller if running...');
var _pid;
if (debug) {
// start controller
_pid = child_process.exec('node ' + appName + '.js stop', {
cwd: rootDir + 'node_modules/' + appName + '.js-controller',
stdio: [0, 1, 2]
});
} else {
_pid = child_process.fork(appName + '.js', ['stop'], {
cwd: rootDir + 'node_modules/' + appName + '.js-controller',
stdio: [0, 1, 2, 'ipc']
});
}
waitForEnd(_pid, function () {
// copy all files into
if (!fs.existsSync(rootDir + 'tmp')) fs.mkdirSync(rootDir + 'tmp');
if (!fs.existsSync(rootDir + 'tmp/node_modules')) fs.mkdirSync(rootDir + 'tmp/node_modules');
if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller')){
console.log('Copy js-controller...');
copyFolderRecursiveSync(rootDir + 'node_modules/' + appName + '.js-controller', rootDir + 'tmp/node_modules/');
}
console.log('Setup js-controller...');
var __pid;
if (debug) {
// start controller
_pid = child_process.exec('node ' + appName + '.js setup first --console', {
cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller',
stdio: [0, 1, 2]
});
} else {
__pid = child_process.fork(appName + '.js', ['setup', 'first', '--console'], {
cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller',
stdio: [0, 1, 2, 'ipc']
});
}
waitForEnd(__pid, function () {
checkIsControllerInstalled(function () {
// change ports for object and state DBs
var config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json');
config.objects.port = 19001;
config.states.port = 19000;
fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2));
console.log('Setup finished.');
copyAdapterToController();
installAdapter(function () {
storeOriginalFiles();
if (cb) cb(true);
});
});
});
});
} else {
// check if port 9000 is free, else admin adapter will be added to running instance
var client = new require('net').Socket();
client.connect(9000, '127.0.0.1', function() {
console.error('Cannot initiate fisrt run of test, because one instance of application is running on this PC. Stop it and repeat.');
process.exit(0);
});
setTimeout(function () {
client.destroy();
if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller')) {
console.log('installJsController: no js-controller => install from git');
child_process.execSync('npm install https://git.spacen.net/' + appName + '/' + appName + '.js-controller/tarball/master --prefix ./ --production', {
cwd: rootDir + 'tmp/',
stdio: [0, 1, 2]
});
} else {
console.log('Setup js-controller...');
var __pid;
if (debug) {
// start controller
child_process.exec('node ' + appName + '.js setup first', {
cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller',
stdio: [0, 1, 2]
});
} else {
child_process.fork(appName + '.js', ['setup', 'first'], {
cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller',
stdio: [0, 1, 2, 'ipc']
});
}
}
// let npm install admin and run setup
checkIsControllerInstalled(function () {
var _pid;
if (fs.existsSync(rootDir + 'node_modules/' + appName + '.js-controller/' + appName + '.js')) {
_pid = child_process.fork(appName + '.js', ['stop'], {
cwd: rootDir + 'node_modules/' + appName + '.js-controller',
stdio: [0, 1, 2, 'ipc']
});
}
waitForEnd(_pid, function () {
// change ports for object and state DBs
var config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json');
config.objects.port = 19001;
config.states.port = 19000;
fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2));
copyAdapterToController();
installAdapter(function () {
storeOriginalFiles();
if (cb) cb(true);
});
});
});
}, 1000);
}
} else {
setTimeout(function () {
console.log('installJsController: js-controller installed');
if (cb) cb(false);
}, 0);
}
}
function copyAdapterToController() {
console.log('Copy adapter...');
// Copy adapter to tmp/node_modules/appName.adapter
copyFolderRecursiveSync(rootDir, rootDir + 'tmp/node_modules/', ['.idea', 'test', 'tmp', '.git', appName + '.js-controller']);
console.log('Adapter copied.');
}
function clearControllerLog() {
var dirPath = rootDir + 'tmp/log';
var files;
try {
if (fs.existsSync(dirPath)) {
console.log('Clear controller log...');
files = fs.readdirSync(dirPath);
} else {
console.log('Create controller log directory...');
files = [];
fs.mkdirSync(dirPath);
}
} catch(e) {
console.error('Cannot read "' + dirPath + '"');
return;
}
if (files.length > 0) {
try {
for (var i = 0; i < files.length; i++) {
var filePath = dirPath + '/' + files[i];
fs.unlinkSync(filePath);
}
console.log('Controller log cleared');
} catch (err) {
console.error('cannot clear log: ' + err);
}
}
}
function clearDB() {
var dirPath = rootDir + 'tmp/yunkong2-data/sqlite';
var files;
try {
if (fs.existsSync(dirPath)) {
console.log('Clear sqlite DB...');
files = fs.readdirSync(dirPath);
} else {
console.log('Create controller log directory...');
files = [];
fs.mkdirSync(dirPath);
}
} catch(e) {
console.error('Cannot read "' + dirPath + '"');
return;
}
if (files.length > 0) {
try {
for (var i = 0; i < files.length; i++) {
var filePath = dirPath + '/' + files[i];
fs.unlinkSync(filePath);
}
console.log('Clear sqlite DB');
} catch (err) {
console.error('cannot clear DB: ' + err);
}
}
}
function setupController(cb) {
installJsController(function (isInited) {
clearControllerLog();
clearDB();
if (!isInited) {
restoreOriginalFiles();
copyAdapterToController();
}
// read system.config object
var dataDir = rootDir + 'tmp/' + appName + '-data/';
var objs;
try {
objs = fs.readFileSync(dataDir + 'objects.json');
objs = JSON.parse(objs);
}
catch (e) {
console.log('ERROR reading/parsing system configuration. Ignore');
objs = {'system.config': {}};
}
if (!objs || !objs['system.config']) {
objs = {'system.config': {}};
}
if (cb) cb(objs['system.config']);
});
}
function startAdapter(objects, states, callback) {
if (adapterStarted) {
console.log('Adapter already started ...');
if (callback) callback(objects, states);
return;
}
adapterStarted = true;
console.log('startAdapter...');
if (fs.existsSync(rootDir + 'tmp/node_modules/' + pkg.name + '/' + pkg.main)) {
try {
if (debug) {
// start controller
pid = child_process.exec('node node_modules/' + pkg.name + '/' + pkg.main + ' --console silly', {
cwd: rootDir + 'tmp',
stdio: [0, 1, 2]
});
} else {
// start controller
pid = child_process.fork('node_modules/' + pkg.name + '/' + pkg.main, ['--console', 'silly'], {
cwd: rootDir + 'tmp',
stdio: [0, 1, 2, 'ipc']
});
}
} catch (error) {
console.error(JSON.stringify(error));
}
} else {
console.error('Cannot find: ' + rootDir + 'tmp/node_modules/' + pkg.name + '/' + pkg.main);
}
if (callback) callback(objects, states);
}
function startController(isStartAdapter, onObjectChange, onStateChange, callback) {
if (typeof isStartAdapter === 'function') {
callback = onStateChange;
onStateChange = onObjectChange;
onObjectChange = isStartAdapter;
isStartAdapter = true;
}
if (onStateChange === undefined) {
callback = onObjectChange;
onObjectChange = undefined;
}
if (pid) {
console.error('Controller is already started!');
} else {
console.log('startController...');
adapterStarted = false;
var isObjectConnected;
var isStatesConnected;
var Objects = require(rootDir + 'tmp/node_modules/' + appName + '.js-controller/lib/objects/objectsInMemServer');
objects = new Objects({
connection: {
"type" : "file",
"host" : "127.0.0.1",
"port" : 19001,
"user" : "",
"pass" : "",
"noFileCache": false,
"connectTimeout": 2000
},
logger: {
silly: function (msg) {
console.log(msg);
},
debug: function (msg) {
console.log(msg);
},
info: function (msg) {
console.log(msg);
},
warn: function (msg) {
console.warn(msg);
},
error: function (msg) {
console.error(msg);
}
},
connected: function () {
isObjectConnected = true;
if (isStatesConnected) {
console.log('startController: started!');
if (isStartAdapter) {
startAdapter(objects, states, callback);
} else {
if (callback) {
callback(objects, states);
callback = null;
}
}
}
},
change: onObjectChange
});
// Just open in memory DB itself
var States = require(rootDir + 'tmp/node_modules/' + appName + '.js-controller/lib/states/statesInMemServer');
states = new States({
connection: {
type: 'file',
host: '127.0.0.1',
port: 19000,
options: {
auth_pass: null,
retry_max_delay: 15000
}
},
logger: {
silly: function (msg) {
console.log(msg);
},
debug: function (msg) {
console.log(msg);
},
info: function (msg) {
console.log(msg);
},
warn: function (msg) {
console.log(msg);
},
error: function (msg) {
console.log(msg);
}
},
connected: function () {
isStatesConnected = true;
if (isObjectConnected) {
console.log('startController: started!!');
if (isStartAdapter) {
startAdapter(objects, states, callback);
} else {
if (callback) {
callback(objects, states);
callback = null;
}
}
}
},
change: onStateChange
});
}
}
function stopAdapter(cb) {
if (!pid) {
console.error('Controller is not running!');
if (cb) {
setTimeout(function () {
cb(false);
}, 0);
}
} else {
adapterStarted = false;
pid.on('exit', function (code, signal) {
if (pid) {
console.log('child process terminated due to receipt of signal ' + signal);
if (cb) cb();
pid = null;
}
});
pid.on('close', function (code, signal) {
if (pid) {
if (cb) cb();
pid = null;
}
});
pid.kill('SIGTERM');
}
}
function _stopController() {
if (objects) {
objects.destroy();
objects = null;
}
if (states) {
states.destroy();
states = null;
}
}
function stopController(cb) {
var timeout;
if (objects) {
console.log('Set system.adapter.' + pkg.name + '.0');
objects.setObject('system.adapter.' + pkg.name + '.0', {
common:{
enabled: false
}
});
}
stopAdapter(function () {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
_stopController();
if (cb) {
cb(true);
cb = null;
}
});
timeout = setTimeout(function () {
timeout = null;
console.log('child process NOT terminated');
_stopController();
if (cb) {
cb(false);
cb = null;
}
pid = null;
}, 5000);
}
// Setup the adapter
function setAdapterConfig(common, native, instance) {
var objects = JSON.parse(fs.readFileSync(rootDir + 'tmp/' + appName + '-data/objects.json').toString());
var id = 'system.adapter.' + adapterName.split('.').pop() + '.' + (instance || 0);
if (common) objects[id].common = common;
if (native) objects[id].native = native;
fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/objects.json', JSON.stringify(objects));
}
// Read config of the adapter
function getAdapterConfig(instance) {
var objects = JSON.parse(fs.readFileSync(rootDir + 'tmp/' + appName + '-data/objects.json').toString());
var id = 'system.adapter.' + adapterName.split('.').pop() + '.' + (instance || 0);
return objects[id];
}
if (typeof module !== undefined && module.parent) {
module.exports.getAdapterConfig = getAdapterConfig;
module.exports.setAdapterConfig = setAdapterConfig;
module.exports.startController = startController;
module.exports.stopController = stopController;
module.exports.setupController = setupController;
module.exports.stopAdapter = stopAdapter;
module.exports.startAdapter = startAdapter;
module.exports.installAdapter = installAdapter;
module.exports.appName = appName;
module.exports.adapterName = adapterName;
module.exports.adapterStarted = adapterStarted;
}

142
test/testAdapter.js Normal file
View File

@@ -0,0 +1,142 @@
/* jshint -W097 */// jshint strict:false
/*jslint node: true */
var expect = require('chai').expect;
var setup = require(__dirname + '/lib/setup');
var objects = null;
var states = null;
var onStateChanged = null;
var onObjectChanged = null;
var sendToID = 1;
var adapterShortName = setup.adapterName.substring(setup.adapterName.indexOf('.') + 1);
var runningMode = require(__dirname + '/../io-package.json').common.mode;
function checkConnectionOfAdapter(cb, counter) {
counter = counter || 0;
console.log('Try check #' + counter);
if (counter > 30) {
if (cb) cb('Cannot check connection');
return;
}
states.getState('system.adapter.' + adapterShortName + '.0.alive', function (err, state) {
if (err) console.error(err);
if (state && state.val) {
if (cb) cb();
} else {
setTimeout(function () {
checkConnectionOfAdapter(cb, counter + 1);
}, 1000);
}
});
}
function checkValueOfState(id, value, cb, counter) {
counter = counter || 0;
if (counter > 20) {
if (cb) cb('Cannot check value Of State ' + id);
return;
}
states.getState(id, function (err, state) {
if (err) console.error(err);
if (value === null && !state) {
if (cb) cb();
} else
if (state && (value === undefined || state.val === value)) {
if (cb) cb();
} else {
setTimeout(function () {
checkValueOfState(id, value, cb, counter + 1);
}, 500);
}
});
}
function sendTo(target, command, message, callback) {
onStateChanged = function (id, state) {
if (id === 'messagebox.system.adapter.test.0') {
callback(state.message);
}
};
states.pushMessage('system.adapter.' + target, {
command: command,
message: message,
from: 'system.adapter.test.0',
callback: {
message: message,
id: sendToID++,
ack: false,
time: (new Date()).getTime()
}
});
}
describe('Test ' + adapterShortName + ' adapter', function() {
before('Test ' + adapterShortName + ' adapter: Start js-controller', function (_done) {
this.timeout(600000); // because of first install from npm
setup.setupController(function () {
var config = setup.getAdapterConfig();
// enable adapter
config.common.enabled = true;
config.common.loglevel = 'debug';
config.native.announce = 'gong.mp3';
setup.setAdapterConfig(config.common, config.native);
setup.startController(true, function(id, obj) {}, function (id, state) {
if (onStateChanged) onStateChanged(id, state);
},
function (_objects, _states) {
objects = _objects;
states = _states;
_done();
});
});
});
it('Test ' + adapterShortName + ' instance object: it must exists', function (done) {
objects.getObject('system.adapter.' + adapterShortName + '.0', function (err, obj) {
expect(err).to.be.null;
expect(obj).to.be.an('object');
expect(obj).not.to.be.null;
done();
});
});
it('Test ' + adapterShortName + ' adapter: Check if adapter started', function (done) {
this.timeout(60000);
checkConnectionOfAdapter(function (res) {
if (res) console.log(res);
if (runningMode === 'daemon') {
expect(res).not.to.be.equal('Cannot check connection');
} else {
//??
}
done();
});
});
/**/
/*
PUT YOUR OWN TESTS HERE USING
it('Testname', function ( done) {
...
});
You can also use "sendTo" method to send messages to the started adapter
*/
after('Test ' + adapterShortName + ' adapter: Stop js-controller', function (done) {
this.timeout(10000);
setup.stopController(function (normalTerminated) {
console.log('Adapter normal terminated: ' + normalTerminated);
done();
});
});
});

91
test/testPackageFiles.js Normal file
View File

@@ -0,0 +1,91 @@
/* jshint -W097 */
/* jshint strict:false */
/* jslint node: true */
/* jshint expr: true */
var expect = require('chai').expect;
var fs = require('fs');
describe('Test package.json and io-package.json', function() {
it('Test package files', function (done) {
console.log();
var fileContentIOPackage = fs.readFileSync(__dirname + '/../io-package.json', 'utf8');
var ioPackage = JSON.parse(fileContentIOPackage);
var fileContentNPMPackage = fs.readFileSync(__dirname + '/../package.json', 'utf8');
var npmPackage = JSON.parse(fileContentNPMPackage);
expect(ioPackage).to.be.an('object');
expect(npmPackage).to.be.an('object');
expect(ioPackage.common.version, 'ERROR: Version number in io-package.json needs to exist').to.exist;
expect(npmPackage.version, 'ERROR: Version number in package.json needs to exist').to.exist;
expect(ioPackage.common.version, 'ERROR: Version numbers in package.json and io-package.json needs to match').to.be.equal(npmPackage.version);
if (!ioPackage.common.news || !ioPackage.common.news[ioPackage.common.version]) {
console.log('WARNING: No news entry for current version exists in io-package.json, no rollback in Admin possible!');
console.log();
}
expect(npmPackage.author, 'ERROR: Author in package.json needs to exist').to.exist;
expect(ioPackage.common.authors, 'ERROR: Authors in io-package.json needs to exist').to.exist;
if (ioPackage.common.name.indexOf('template') !== 0) {
if (Array.isArray(ioPackage.common.authors)) {
expect(ioPackage.common.authors.length, 'ERROR: Author in io-package.json needs to be set').to.not.be.equal(0);
if (ioPackage.common.authors.length === 1) {
expect(ioPackage.common.authors[0], 'ERROR: Author in io-package.json needs to be a real name').to.not.be.equal('my Name <my@email.com>');
}
}
else {
expect(ioPackage.common.authors, 'ERROR: Author in io-package.json needs to be a real name').to.not.be.equal('my Name <my@email.com>');
}
}
else {
console.log('WARNING: Testing for set authors field in io-package skipped because template adapter');
console.log();
}
expect(fs.existsSync(__dirname + '/../README.md'), 'ERROR: README.md needs to exist! Please create one with description, detail information and changelog. English is mandatory.').to.be.true;
if (!ioPackage.common.titleLang || typeof ioPackage.common.titleLang !== 'object') {
console.log('WARNING: titleLang is not existing in io-package.json. Please add');
console.log();
}
if (
ioPackage.common.title.indexOf('yunkong2') !== -1 ||
ioPackage.common.title.indexOf('yunkong2') !== -1 ||
ioPackage.common.title.indexOf('adapter') !== -1 ||
ioPackage.common.title.indexOf('Adapter') !== -1
) {
console.log('WARNING: title contains Adapter or yunkong2. It is clear anyway, that it is adapter for yunkong2.');
console.log();
}
if (ioPackage.common.name.indexOf('vis-') !== 0) {
if (!ioPackage.common.materialize || !fs.existsSync(__dirname + '/../admin/index_m.html') || !fs.existsSync(__dirname + '/../gulpfile.js')) {
console.log('WARNING: Admin3 support is missing! Please add it');
console.log();
}
if (ioPackage.common.materialize) {
expect(fs.existsSync(__dirname + '/../admin/index_m.html'), 'Admin3 support is enabled in io-package.json, but index_m.html is missing!').to.be.true;
}
}
var licenseFileExists = fs.existsSync(__dirname + '/../LICENSE');
var fileContentReadme = fs.readFileSync(__dirname + '/../README.md', 'utf8');
if (fileContentReadme.indexOf('## Changelog') === -1) {
console.log('Warning: The README.md should have a section ## Changelog');
console.log();
}
expect((licenseFileExists || fileContentReadme.indexOf('## License') !== -1), 'A LICENSE must exist as LICENSE file or as part of the README.md').to.be.true;
if (!licenseFileExists) {
console.log('Warning: The License should also exist as LICENSE file');
console.log();
}
if (fileContentReadme.indexOf('## License') === -1) {
console.log('Warning: The README.md should also have a section ## License to be shown in Admin3');
console.log();
}
done();
});
});