node-red-contrib-tts-ultimate 2.0.10 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +33 -29
- package/package.json +2 -4
- package/ttsultimate/ttsultimate-config copy.js +765 -0
- package/ttsultimate/ttsultimate-config.html +11 -15
- package/ttsultimate/ttsultimate-config.js +1 -128
- package/ttsultimate/ttsultimate.html +26 -8
- package/ttsultimate/ttsultimate.js +27 -25
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.paypal.me/techtoday)
|
|
4
4
|
|
|
5
|
+
## BREAKING CHANGE ! BREAKING CHANGE ! BREAKING CHANGE ! BREAKING CHANGE !
|
|
6
|
+
|
|
7
|
+
<p>
|
|
8
|
+
<b>Version 3.0.0</b> June 2025<br/>
|
|
9
|
+
- BREAKING CHANGE: Amazon Polly and Microsoft Azure TTS have been removed due to lack of time to update the old and complex API's. Anyone can add these again by forking the project and do a PR. Thank you!. If you still need those TTS, please stay or revert to 2.0.10.<br/>
|
|
10
|
+
- NEW: Added option to avoid resuming music if it was playing before TTS messages.<br/>
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
-----------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
5
17
|
<p>
|
|
6
18
|
<b>Version 2.0.10</b> November 2024<br/>
|
|
7
19
|
- ElevenLabs V2: now you can set the additional parameters for the voice.<br/>
|
package/README.md
CHANGED
|
@@ -20,13 +20,11 @@
|
|
|
20
20
|
```
|
|
21
21
|
</details>
|
|
22
22
|
|
|
23
|
-
## WARNING
|
|
24
|
-
Due to Microsoft Azure SDK limitation, the node can only be installed on systems with **NodeJS** versions: (^12.22.0, ^14.17.0, or >=16.0.0) built with SSL support. (If you are using an official Node.js distribution, SSL is always built in.).
|
|
25
23
|
|
|
26
24
|
## DESCRIPTION
|
|
27
|
-
This node transforms a text into a speech audio that you can hear natively via <b>SONOS</b> speakers.<br/>
|
|
25
|
+
This node transforms a text into a speech audio that you can hear natively via <b>SONOS</b> speakers, but you can also simply create an audio file, without using SONOS at all.<br/>
|
|
28
26
|
You can also generate an audio file for bluetooth speakers, web pages, etc.<br/>
|
|
29
|
-
|
|
27
|
+
You can also use it with **your own audio file** as well and it can be used **totally offline** even without the use of TTS, without internet connection.<br/>
|
|
30
28
|
The node can also create a ***TTS file (without the use of any Sonos device)***, to be read by third parties nodes.<br/>
|
|
31
29
|
This is a major ***upgrade from the previously popular node SonosPollyTTS*** (SonosPollyTTS is not developed anymore).<br/>
|
|
32
30
|
|
|
@@ -39,22 +37,23 @@ This is a major ***upgrade from the previously popular node SonosPollyTTS*** (So
|
|
|
39
37
|
## FEATURES
|
|
40
38
|
* **Native Sonos support**: hear the TTS audio directly via Sonos. You can also group speakers, set an hailing sound, choose the volume of each speaker etc.
|
|
41
39
|
* **Output audio file**: the node can just create the TTS file to be used by other nodes. In this case, you doesn't need to use Sonos as player.
|
|
42
|
-
* **
|
|
40
|
+
* **Gooogle Translate Voices, Google TTS Voices and Elevenlabs.io voices** are all supported, with all avaiables languages and genders.
|
|
43
41
|
* **Automatic grouping** is supported. You can group all players you want to play your announcements.
|
|
44
42
|
* **Automatic discovery** of your players.
|
|
45
43
|
* **Automatic resume of music** queue (including radio stations, but here, some users reports problem resuming ***radio stations*** and, because of lack of Sonos API documentation, the issue cannot currently be fixed), at exact track, at exact time. **Be aware that this could not work with all music queues**.
|
|
46
|
-
* **TTS caching**. Elevenlabs
|
|
44
|
+
* **TTS caching**. Elevenlabs and Google paid service, charges you if you use they tts service for a high rate of text to speech requests. TTS-Ultimate caches the TTS files. It downloads the TTS audio from Amazon or Google only once. The second time, the node will read it from the cache. The caches is resilient, that means it survives reboots and updates.
|
|
47
45
|
* **Can work offline**. You can use your own audio files (with OwnFile node) to make the node works offline.
|
|
48
46
|
* **UPLOAD your own audio files**. You can also upload your own audio files with OwnFile node.
|
|
49
47
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
>
|
|
54
|
-
>
|
|
55
|
-
|
|
56
|
-
>
|
|
57
|
-
|
|
48
|
+
|
|
49
|
+
## BREAKING CHANGE ! BREAKING CHANGE ! BREAKING CHANGE ! BREAKING CHANGE !
|
|
50
|
+
|
|
51
|
+
<p>
|
|
52
|
+
<b>Version 3.0.0</b> April 2025<br/>
|
|
53
|
+
- BREAKING CHANGE: Amazon Polly and Microsoft Azure TTS have been removed due to lack of time to update the old and complex API's. Anyone can add these again by forking the project and do a PR. Thank you!. If you still need those TTS, please stay or revert to 2.0.10.<br/>
|
|
54
|
+
</p>
|
|
55
|
+
|
|
56
|
+
-----------------------------------------------------------------------
|
|
58
57
|
|
|
59
58
|
<br/>
|
|
60
59
|
|
|
@@ -74,19 +73,20 @@ PORT USED BY THE NODE ARE 1980 (DEFAULT) AND 1400 (FOR SONOS DISCOVER). <br/>
|
|
|
74
73
|
PLEASE ALLOW MDNS AND UDP AS WELL
|
|
75
74
|
|
|
76
75
|
**TTS Service**<br/>
|
|
77
|
-
You can choose between Elevenlabs.io, Google (without credentials),
|
|
76
|
+
You can choose between Elevenlabs.io, Google (without credentials), Google TTS (require credentials and registration to google).<br/>
|
|
78
77
|
For Google TTS Engine, you can choose pitch and speed rate of the voice.
|
|
79
78
|
<br/>
|
|
80
79
|
<br/>
|
|
81
80
|
|
|
82
|
-
* **TTS Service using Amazon AWS (Polly)**<br/>
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
81
|
+
* **TTS Service using Amazon AWS (Polly)**<br/>
|
|
82
|
+
|
|
83
|
+
(REMOVED IN v3.0.0 AND NOT USED ANYMORE)
|
|
84
|
+
|
|
85
|
+
!IF YOU NEED THIS SERVICE, INSTALL ANY VERSION < 3.0.0 (ANY 2.x.x IS FINE)!
|
|
86
|
+
> ``` npm install node-red-contrib-tts-ultimate@2.0.10 ```
|
|
87
|
+
|
|
88
|
+
[Navigate here go here to view the old version](https://www.npmjs.com/package/node-red-contrib-tts-ultimate/v/2.0.10)
|
|
89
|
+
|
|
90
90
|
<br/>
|
|
91
91
|
|
|
92
92
|
* **TTS Service using Google (without credentials)**<br/>
|
|
@@ -105,12 +105,16 @@ For Google TTS Engine, you can choose pitch and speed rate of the voice.
|
|
|
105
105
|
|
|
106
106
|
<br/>
|
|
107
107
|
|
|
108
|
-
* **TTS Service using Microsot Azure TTS
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
108
|
+
* **TTS Service using Microsot Azure TTS**
|
|
109
|
+
|
|
110
|
+
(REMOVED IN v3.0.0 AND NOT USED ANYMORE)
|
|
111
|
+
|
|
112
|
+
!IF YOU NEED THIS SERVICE, INSTALL ANY VERSION < 3.0.0 (ANY 2.x.x IS FINE)!
|
|
113
|
+
> ``` npm install node-red-contrib-tts-ultimate@2.0.10 ```
|
|
114
|
+
|
|
115
|
+
[Navigate here go here to view the old version](https://www.npmjs.com/package/node-red-contrib-tts-ultimate/v/2.0.10)
|
|
116
|
+
|
|
117
|
+
|
|
114
118
|
<br/>
|
|
115
119
|
|
|
116
120
|
* **TTS Service using ElevenLabs**<br/>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-tts-ultimate",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Transforms the text in speech and hear it using Sonos player or generate an audio file to be used with third parties nodes. Works with voices from Amazon, Google (without credentials as well), Microsoft TTS Azure, ElevenLabs.io TTS or your own voice. You can also only create a TTS file to be read by third party nodes. Update of the popular SonosPollyTTS node.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -40,13 +40,11 @@
|
|
|
40
40
|
},
|
|
41
41
|
"homepage": "https://github.com/Supergiovane/node-red-contrib-tts-ultimate",
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"aws-sdk": "2.1444.0",
|
|
44
43
|
"sonos": "1.14.1",
|
|
45
44
|
"formidable": "1.2.2",
|
|
46
45
|
"path": ">=0.12.7",
|
|
47
46
|
"@google-cloud/text-to-speech": "4.2.2",
|
|
48
47
|
"google-translate-tts": ">=0.3.0",
|
|
49
|
-
"microsoft-cognitiveservices-speech-sdk": "1.29.0",
|
|
50
48
|
"elevenlabs-node": "1.1.3",
|
|
51
49
|
"elevenlabs": "0.18.0"
|
|
52
50
|
},
|
|
@@ -55,6 +53,6 @@
|
|
|
55
53
|
"eslint-config-google": "^0.7.1"
|
|
56
54
|
},
|
|
57
55
|
"engines": {
|
|
58
|
-
"node": ">=
|
|
56
|
+
"node": ">=22.0.0"
|
|
59
57
|
}
|
|
60
58
|
}
|
|
@@ -0,0 +1,765 @@
|
|
|
1
|
+
const { Redshift } = require('aws-sdk');
|
|
2
|
+
|
|
3
|
+
module.exports = function (RED) {
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
// 31/05/2022 checking nodejs version due to Microsoft Azure SDK bad nodejs compatibility.
|
|
7
|
+
let nodejsVersion = process.version.match(/^v(\d+\.\d+)/)[1];
|
|
8
|
+
if (nodejsVersion.startsWith("18")) {
|
|
9
|
+
//RED.log.error('ttsultimate-config: YOUR NODEJS VERSION IS CURRENTLY INCOMPATIBLE WITH Microsoft Azure SDK. Your NodeJS version: ' + nodejsVersion + ", please install one of these: (^12.22.0, ^14.17.0, or >=16.0.0), with SSL support.");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
// Setting up the engines
|
|
14
|
+
const AWS = require('aws-sdk');
|
|
15
|
+
const GoogleTTS = require('@google-cloud/text-to-speech');
|
|
16
|
+
const GoogleTranslate = require('google-translate-tts'); // TTS without credentials, limited to 200 chars per row.
|
|
17
|
+
const microsoftAzureTTS = require("microsoft-cognitiveservices-speech-sdk"); // 12/10/2021
|
|
18
|
+
const elevenlabsTTS = require("elevenlabs-node"); // 03/08/2023
|
|
19
|
+
const ElevenLabsClient = require("elevenlabs").ElevenLabsClient;
|
|
20
|
+
|
|
21
|
+
var fs = require('fs');
|
|
22
|
+
var path = require("path");
|
|
23
|
+
var formidable = require('formidable');
|
|
24
|
+
const oOS = require('os');
|
|
25
|
+
const sonos = require('sonos');
|
|
26
|
+
|
|
27
|
+
AWS.config.update({
|
|
28
|
+
region: 'us-east-1'
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Configuration Node Register
|
|
32
|
+
function TTSConfigNode(config) {
|
|
33
|
+
RED.nodes.createNode(this, config);
|
|
34
|
+
var node = this;
|
|
35
|
+
node.noderedipaddress = config.noderedipaddress;
|
|
36
|
+
if (node.noderedipaddress === undefined || node.noderedipaddress === "AUTODISCOVER") {
|
|
37
|
+
node.noderedipaddress = GetEthAddress();
|
|
38
|
+
RED.log.info('ttsultimate-config ' + node.id + ': Autodiscover current IP ' + node.noderedipaddress);
|
|
39
|
+
}
|
|
40
|
+
node.whoIsUsingTheServer = ""; // Client node.id using the server, because only a ttsultimate node can use the serve at once.
|
|
41
|
+
node.ttsservice = config.ttsservice || "googletranslate";
|
|
42
|
+
node.TTSRootFolderPath = (config.TTSRootFolderPath === undefined || config.TTSRootFolderPath === "") ? path.join(RED.settings.userDir, "sonospollyttsstorage") : path.join(config.TTSRootFolderPath, "sonospollyttsstorage");
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
// 03/06/2019 you can select the temp dir
|
|
46
|
+
//#region "SETUP PATHS"
|
|
47
|
+
// 26/10/2020 Check for path and create it if doens't exists
|
|
48
|
+
function setupDirectory(_aPath) {
|
|
49
|
+
|
|
50
|
+
if (!fs.existsSync(_aPath)) {
|
|
51
|
+
// Create the path
|
|
52
|
+
try {
|
|
53
|
+
fs.mkdirSync(_aPath);
|
|
54
|
+
return true;
|
|
55
|
+
} catch (error) { return false; }
|
|
56
|
+
} else {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (!setupDirectory(node.TTSRootFolderPath)) {
|
|
61
|
+
RED.log.error('ttsultimate-config ' + node.id + ': Unable to set up MAIN directory: ' + node.TTSRootFolderPath);
|
|
62
|
+
}
|
|
63
|
+
if (!setupDirectory(path.join(node.TTSRootFolderPath, "ttsfiles"))) {
|
|
64
|
+
RED.log.error('ttsultimate-config ' + node.id + ': Unable to set up cache directory: ' + path.join(node.TTSRootFolderPath, "ttsfiles"));
|
|
65
|
+
} else {
|
|
66
|
+
RED.log.info('ttsultimate-config ' + node.id + ': TTS cache set to ' + path.join(node.TTSRootFolderPath, "ttsfiles"));
|
|
67
|
+
}
|
|
68
|
+
if (!setupDirectory(path.join(node.TTSRootFolderPath, "ttsultimategooglecredentials"))) {
|
|
69
|
+
RED.log.error('ttsultimate-config ' + node.id + ': Unable to set google creds directory: ' + path.join(node.TTSRootFolderPath, "ttsultimategooglecredentials"));
|
|
70
|
+
} else {
|
|
71
|
+
RED.log.info('ttsultimate-config ' + node.id + ': google credentials path set to ' + path.join(node.TTSRootFolderPath, "ttsultimategooglecredentials"));
|
|
72
|
+
}
|
|
73
|
+
if (!setupDirectory(path.join(node.TTSRootFolderPath, "hailingpermanentfiles"))) {
|
|
74
|
+
RED.log.error('ttsultimate-config ' + node.id + ': Unable to set up hailing directory: ' + path.join(node.TTSRootFolderPath, "hailingpermanentfiles"));
|
|
75
|
+
} else {
|
|
76
|
+
RED.log.info('ttsultimate-config ' + node.id + ': hailing path set to ' + path.join(node.TTSRootFolderPath, "hailingpermanentfiles"));
|
|
77
|
+
// 09/03/2020 Copy defaults to the userDir
|
|
78
|
+
fs.readdirSync(path.join(__dirname, "hailingpermanentfiles")).forEach(file => {
|
|
79
|
+
try {
|
|
80
|
+
fs.copyFileSync(path.join(__dirname, "hailingpermanentfiles", file), path.join(node.TTSRootFolderPath, "hailingpermanentfiles", file));
|
|
81
|
+
} catch (error) { }
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
if (!setupDirectory(path.join(node.TTSRootFolderPath, "ttspermanentfiles"))) {
|
|
85
|
+
RED.log.error('ttsultimate-config ' + node.id + ': Unable to set up permanent files directory: ' + path.join(node.TTSRootFolderPath, "ttspermanentfiles"));
|
|
86
|
+
} else {
|
|
87
|
+
RED.log.info('ttsultimate-config ' + node.id + ': permanent files path set to ' + path.join(node.TTSRootFolderPath, "ttspermanentfiles"));
|
|
88
|
+
// 09/03/2020 // Copy the samples of permanent files into the userDir
|
|
89
|
+
fs.readdirSync(path.join(__dirname, "ttspermanentfiles")).forEach(file => {
|
|
90
|
+
try {
|
|
91
|
+
fs.copyFileSync(path.join(__dirname, "ttspermanentfiles", file), path.join(node.TTSRootFolderPath, "ttspermanentfiles", file));
|
|
92
|
+
} catch (error) { }
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
//#region "INSTANTIATE SERVICE ENGINES"
|
|
99
|
+
// POLLY
|
|
100
|
+
if (node.ttsservice === "polly") {
|
|
101
|
+
var params = {
|
|
102
|
+
accessKeyId: node.credentials.accessKey,
|
|
103
|
+
secretAccessKey: node.credentials.secretKey,
|
|
104
|
+
apiVersion: '2016-06-10'
|
|
105
|
+
};
|
|
106
|
+
try {
|
|
107
|
+
node.polly = new AWS.Polly(params);
|
|
108
|
+
RED.log.info("ttsultimate-config " + node.id + ": Polly service enabled.")
|
|
109
|
+
} catch (error) {
|
|
110
|
+
RED.log.warn("ttsultimate-config " + node.id + ": Polly service disabled. " + error.message)
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
//RED.log.info("ttsultimate-config " + node.id + ": Polly service not used.");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
// Google TTS with authentication
|
|
118
|
+
if (node.ttsservice === "googletts") {
|
|
119
|
+
try {
|
|
120
|
+
// 23/12/2020 Set environment path of googleTTS
|
|
121
|
+
RED.log.info("ttsultimate-config " + node.id + ": Google credentials are stored in the file " + path.join(node.TTSRootFolderPath, "ttsultimategooglecredentials", "googlecredentials.json"));
|
|
122
|
+
process.env.GOOGLE_APPLICATION_CREDENTIALS = path.join(node.TTSRootFolderPath, "ttsultimategooglecredentials", "googlecredentials.json");
|
|
123
|
+
try {
|
|
124
|
+
node.googleTTS = new GoogleTTS.TextToSpeechClient();
|
|
125
|
+
RED.log.info("ttsultimate-config " + node.id + ": Google TTS service enabled. ")
|
|
126
|
+
} catch (error) {
|
|
127
|
+
RED.log.warn("ttsultimate-config " + node.id + ": Google TTS service disabled. " + error.message)
|
|
128
|
+
}
|
|
129
|
+
} catch (error) {
|
|
130
|
+
RED.log.warn("ttsultimate-config " + node.id + ": Google TTS service error: " + error.message)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
} else {
|
|
134
|
+
// RED.log.info("ttsultimate-config " + node.id + ": Google TTS service not used.");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
// Google Translate without authentication
|
|
140
|
+
if (node.ttsservice === "googletranslate") {
|
|
141
|
+
try {
|
|
142
|
+
node.googleTranslateTTS = GoogleTranslate;
|
|
143
|
+
} catch (error) {
|
|
144
|
+
//RED.log.info("ttsultimate-config " + node.id + ": Google Translate free service not used.");
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
//RED.log.info("ttsultimate-config " + node.id + ": Google Translate free service not used.");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 12/10/2021 Microsoft Azure TTS SpeechConfig.fromSubscription(subscriptionKey, serviceRegion)
|
|
151
|
+
if (node.ttsservice === "microsoftazuretts") {
|
|
152
|
+
// #########################################
|
|
153
|
+
node.setMicrosoftAzureVoice = function (_voiceName) {
|
|
154
|
+
let speechConfig = microsoftAzureTTS.SpeechConfig.fromSubscription(node.credentials.mssubscriptionKey, node.credentials.mslocation);
|
|
155
|
+
speechConfig.speechSynthesisVoiceName = _voiceName;
|
|
156
|
+
speechConfig.speechSynthesisOutputFormat = microsoftAzureTTS.SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3;
|
|
157
|
+
node.microsoftAzureTTS = new microsoftAzureTTS.SpeechSynthesizer(speechConfig);
|
|
158
|
+
return node.microsoftAzureTTS;
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
|
|
162
|
+
let speechConfig = microsoftAzureTTS.SpeechConfig.fromSubscription(node.credentials.mssubscriptionKey, node.credentials.mslocation);
|
|
163
|
+
//speechConfig.speechSynthesisLanguage = "it-IT";
|
|
164
|
+
//speechConfig.speechSynthesisVoiceName = "it-IT-IsabellaNeural";
|
|
165
|
+
speechConfig.speechSynthesisOutputFormat = microsoftAzureTTS.SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3;
|
|
166
|
+
node.microsoftAzureTTS = new microsoftAzureTTS.SpeechSynthesizer(speechConfig);
|
|
167
|
+
node.microsoftAzureTTSVoiceList = [];
|
|
168
|
+
// Get the voices
|
|
169
|
+
async function listVoicesAzure() {
|
|
170
|
+
const httpsAzure = require('https')
|
|
171
|
+
let options = {
|
|
172
|
+
hostname: node.credentials.mslocation + '.tts.speech.microsoft.com',
|
|
173
|
+
port: 443,
|
|
174
|
+
path: '/cognitiveservices/voices/list',
|
|
175
|
+
method: 'GET',
|
|
176
|
+
headers: {
|
|
177
|
+
'Ocp-Apim-Subscription-Key': node.credentials.mssubscriptionKey
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const reqAzure = httpsAzure.request(options, resVoices => {
|
|
181
|
+
var sChunkResponse = "";
|
|
182
|
+
resVoices.on('data', d => {
|
|
183
|
+
sChunkResponse += d.toString();
|
|
184
|
+
})
|
|
185
|
+
resVoices.on('end', () => {
|
|
186
|
+
try {
|
|
187
|
+
let oVoices = JSON.parse(sChunkResponse);
|
|
188
|
+
RED.log.info('ttsultimate-config ' + node.id + ': Microsoft Azure voices count: ' + oVoices.length);
|
|
189
|
+
for (let index = 0; index < oVoices.length; index++) {
|
|
190
|
+
const element = oVoices[index];
|
|
191
|
+
node.microsoftAzureTTSVoiceList.push({ name: element.ShortName + " (" + element.Gender + ")", id: element.ShortName })
|
|
192
|
+
}
|
|
193
|
+
} catch (error) {
|
|
194
|
+
RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error parsing Microsoft Azure TTS voices: ' + error.message);
|
|
195
|
+
node.microsoftAzureTTSVoiceList.push({ name: "Error parsing Microsoft Azure voices: " + error.message + " Check cretentials, deploy and restart node-red.", id: "Ivy" });
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
reqAzure.on('error', error => {
|
|
200
|
+
RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error contacting Azure for getting the voices list: ' + error.message);
|
|
201
|
+
node.microsoftAzureTTSVoiceList.push({ name: "Error getting Microsoft Azure voices: " + error.message + " Check cretentials, deploy and restart node-red.", id: "Ivy" })
|
|
202
|
+
reqAzure.end();
|
|
203
|
+
})
|
|
204
|
+
reqAzure.end();
|
|
205
|
+
};
|
|
206
|
+
RED.log.info("ttsultimate-config " + node.id + ": Microsoft AzureTTS service enabled.")
|
|
207
|
+
try {
|
|
208
|
+
listVoicesAzure();
|
|
209
|
+
} catch (error) {
|
|
210
|
+
RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error getting Microsoft Azure voices: ' + error.message);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
} catch (error) {
|
|
214
|
+
RED.log.warn("ttsultimate-config " + node.id + ": Microsoft AzureTTS service disabled. " + error.message)
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
//RED.log.info("ttsultimate-config " + node.id + ": Microsoft AzureTTS service not used. ");
|
|
218
|
+
}
|
|
219
|
+
// #########################################
|
|
220
|
+
|
|
221
|
+
// elevenlabsTTS v1 deprecated
|
|
222
|
+
if (node.ttsservice === "elevenlabs") {
|
|
223
|
+
node.elevenlabsTTSVoiceList = []
|
|
224
|
+
try {
|
|
225
|
+
node.elevenlabsTTS = elevenlabsTTS;
|
|
226
|
+
try {
|
|
227
|
+
node.elevenlabsTTS.getVoices(node.credentials.elevenlabsKey).then((res) => {
|
|
228
|
+
try {
|
|
229
|
+
res.voices.forEach(element => {
|
|
230
|
+
node.elevenlabsTTSVoiceList.push({ name: element.labels.accent + " - " + element.name + " (" + element.labels.gender + ")", id: element.voice_id })
|
|
231
|
+
});
|
|
232
|
+
node.elevenlabsTTSVoiceList.sort(function (a, b) {
|
|
233
|
+
let x = a.name.toLowerCase();
|
|
234
|
+
let y = b.name.toLowerCase();
|
|
235
|
+
if (x < y) { return -1; }
|
|
236
|
+
if (x > y) { return 1; }
|
|
237
|
+
return 0;
|
|
238
|
+
});
|
|
239
|
+
} catch (error) {
|
|
240
|
+
RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error getting elevenlabs 2 voices: ' + error.message);
|
|
241
|
+
node.elevenlabsTTSVoiceList.push({ name: "Error getting elevenlabs 2 voices: " + error.message + " Check cretentials, deploy and restart node-red.", id: "Ivy" })
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
});
|
|
245
|
+
} catch (error) {
|
|
246
|
+
RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error getting elevenlabs voices: ' + error.message);
|
|
247
|
+
node.elevenlabsTTSVoiceList.push({ name: "Error getting elevenlabs voices: " + error.message + " Check cretentials, deploy and restart node-red.", id: "Ivy" })
|
|
248
|
+
}
|
|
249
|
+
RED.log.info("ttsultimate-config " + node.id + ": elevenlabsTTS service enabled.")
|
|
250
|
+
} catch (error) {
|
|
251
|
+
RED.log.warn("ttsultimate-config " + node.id + ": elevenlabsTTS service disabled. " + error.message)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
} else {
|
|
255
|
+
//RED.log.info("ttsultimate-config " + node.id + ": Google Translate free service not used.");
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// elevenlabsTTS v2
|
|
259
|
+
if (node.ttsservice === "elevenlabsv2") {
|
|
260
|
+
const elevenlabsv2 = new ElevenLabsClient({
|
|
261
|
+
apiKey: node.credentials.elevenlabsKey
|
|
262
|
+
});
|
|
263
|
+
node.elevenlabsTTSVoiceList = []
|
|
264
|
+
try {
|
|
265
|
+
node.elevenlabsTTS = elevenlabsv2;
|
|
266
|
+
try {
|
|
267
|
+
node.elevenlabsTTS.voices.getAll().then((res) => {
|
|
268
|
+
try {
|
|
269
|
+
res.voices.forEach(element => {
|
|
270
|
+
node.elevenlabsTTSVoiceList.push({ name: element.labels.accent + (element.labels.language !== undefined ? " (" + element.labels.language + ")" : "") + " - " + element.name + " (" + element.labels.gender + ")", id: element.voice_id })
|
|
271
|
+
});
|
|
272
|
+
node.elevenlabsTTSVoiceList.sort(function (a, b) {
|
|
273
|
+
let x = a.name.toLowerCase();
|
|
274
|
+
let y = b.name.toLowerCase();
|
|
275
|
+
if (x < y) { return -1; }
|
|
276
|
+
if (x > y) { return 1; }
|
|
277
|
+
return 0;
|
|
278
|
+
});
|
|
279
|
+
} catch (error) {
|
|
280
|
+
RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error getting elevenlabsv2 voices: ' + error.message);
|
|
281
|
+
node.elevenlabsTTSVoiceList.push({ name: "Error getting elevenlabsv2 voices: " + error.message + " Check cretentials, deploy and restart node-red.", id: "Ivy" })
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
});
|
|
285
|
+
} catch (error) {
|
|
286
|
+
RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error getting elevenlabsv2 voices: ' + error.message);
|
|
287
|
+
node.elevenlabsTTSVoiceList.push({ name: "Error getting elevenlabsv2 voices: " + error.message + " Check cretentials, deploy and restart node-red.", id: "Ivy" })
|
|
288
|
+
}
|
|
289
|
+
RED.log.info("ttsultimate-config " + node.id + ": elevenlabsTTS servicev2 enabled.")
|
|
290
|
+
} catch (error) {
|
|
291
|
+
RED.log.warn("ttsultimate-config " + node.id + ": elevenlabsTTS servicev2 disabled. " + error.message)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
} else {
|
|
295
|
+
//RED.log.info("ttsultimate-config " + node.id + ": Google Translate free service not used.");
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
//#endregion
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
//#region HTTP ENDPOINTS
|
|
303
|
+
// ######################################################
|
|
304
|
+
// 21/03/2019 Endpoint for retrieving the default IP
|
|
305
|
+
RED.httpAdmin.get("/ttsultimateGetEthAddress", RED.auth.needsPermission('TTSConfigNode.read'), function (req, res) {
|
|
306
|
+
res.json(GetEthAddress());
|
|
307
|
+
});
|
|
308
|
+
function GetEthAddress() {
|
|
309
|
+
var oiFaces = oOS.networkInterfaces();
|
|
310
|
+
var jListInterfaces = [];
|
|
311
|
+
try {
|
|
312
|
+
Object.keys(oiFaces).forEach(ifname => {
|
|
313
|
+
// Interface with single IP
|
|
314
|
+
if (Object.keys(oiFaces[ifname]).length === 1) {
|
|
315
|
+
if (Object.keys(oiFaces[ifname])[0].internal == false) jListInterfaces.push({ name: ifname, address: Object.keys(oiFaces[ifname])[0].address });
|
|
316
|
+
} else {
|
|
317
|
+
var sAddresses = "";
|
|
318
|
+
oiFaces[ifname].forEach(function (iface) {
|
|
319
|
+
if (iface.internal == false && iface.family !== undefined && iface.family.toString().includes("4")) sAddresses = iface.address;
|
|
320
|
+
});
|
|
321
|
+
if (sAddresses !== "") jListInterfaces.push({ name: ifname, address: sAddresses });
|
|
322
|
+
}
|
|
323
|
+
})
|
|
324
|
+
} catch (error) { }
|
|
325
|
+
if (jListInterfaces.length > 0) {
|
|
326
|
+
return (jListInterfaces[0].address); // Retunr the first usable IP
|
|
327
|
+
} else {
|
|
328
|
+
return ("NO ETH INTERFACE FOUND");
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// 20/03/2020 in the middle of coronavirus, get the sonos groups
|
|
333
|
+
RED.httpAdmin.get("/sonosgetAllGroups", RED.auth.needsPermission('TTSConfigNode.read'), function (req, res) {
|
|
334
|
+
var jListGroups = [];
|
|
335
|
+
try {
|
|
336
|
+
const discovery = new sonos.AsyncDeviceDiscovery()
|
|
337
|
+
discovery.discover().then((device, model) => {
|
|
338
|
+
return device.getAllGroups().then((groups) => {
|
|
339
|
+
//RED.log.warn('Groups ' + JSON.stringify(groups, null, 2))
|
|
340
|
+
for (let index = 0; index < groups.length; index++) {
|
|
341
|
+
const element = groups[index];
|
|
342
|
+
if (jListGroups.find(x => x.host === element.host) === undefined) jListGroups.push({ name: element.Name, host: element.host })
|
|
343
|
+
// 02/03/2023 If there are speakers grouped, read the single speaker
|
|
344
|
+
if (element.hasOwnProperty("ZoneGroupMember") && element.ZoneGroupMember.length > 0) {
|
|
345
|
+
try {
|
|
346
|
+
for (let i = 0; i < element.ZoneGroupMember.length; i++) {
|
|
347
|
+
const single = element.ZoneGroupMember[i];
|
|
348
|
+
let sHost = single.Location.split("//")[1].split(":")[0]
|
|
349
|
+
if (jListGroups.find(x => x.host === sHost) === undefined) jListGroups.push({ name: single.ZoneName, host: sHost }) // Get Name and IP from Location field
|
|
350
|
+
}
|
|
351
|
+
} catch (error) {
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
res.json(jListGroups)
|
|
357
|
+
//return groups[0].CoordinatorDevice().togglePlayback()
|
|
358
|
+
})
|
|
359
|
+
}).catch(e => {
|
|
360
|
+
RED.log.warn('ttsultimate-config ' + node.id + ': Error in discovery ' + e);
|
|
361
|
+
res.json("ERRORDISCOVERY");
|
|
362
|
+
})
|
|
363
|
+
} catch (error) { }
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
// 09/03/2020 Get list of filenames in hailing folder
|
|
368
|
+
RED.httpAdmin.get("/getHailingFilesList", RED.auth.needsPermission('TTSConfigNode.read'), function (req, res) {
|
|
369
|
+
var jListOwnFiles = [];
|
|
370
|
+
var sName = "";
|
|
371
|
+
try {
|
|
372
|
+
fs.readdirSync(path.join(node.TTSRootFolderPath, "hailingpermanentfiles")).forEach(file => {
|
|
373
|
+
if (file.indexOf("Hailing_") > -1) {
|
|
374
|
+
sName = file.replace("Hailing_", "").replace(".mp3", "");
|
|
375
|
+
jListOwnFiles.push({ name: sName, filename: file });
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
} catch (error) { }
|
|
380
|
+
res.json(jListOwnFiles)
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
// 09/03/2020 Delete Hailing
|
|
384
|
+
RED.httpAdmin.get("/deleteHailingFile", RED.auth.needsPermission('TTSConfigNode.read'), function (req, res) {
|
|
385
|
+
// Delete the file
|
|
386
|
+
try {
|
|
387
|
+
var newPath = path.join(node.TTSRootFolderPath, "hailingpermanentfiles", req.query.FileName);
|
|
388
|
+
fs.unlinkSync(newPath)
|
|
389
|
+
} catch (error) { }
|
|
390
|
+
res.json({ status: 220 });
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// 09/03/2020 Receive new hailing files from html
|
|
394
|
+
RED.httpAdmin.post("/ttsultimateHailing", function (req, res) {
|
|
395
|
+
var form = new formidable.IncomingForm();
|
|
396
|
+
form.parse(req, function (err, fields, files) {
|
|
397
|
+
try {
|
|
398
|
+
if (files.customHailing.name.indexOf(".mp3") !== -1) {
|
|
399
|
+
var newPath = path.join(node.TTSRootFolderPath, "hailingpermanentfiles", "Hailing_" + files.customHailing.name);
|
|
400
|
+
// 30/12/2020 To avoid XDEV issue: oldpath and newpath are not on the same mounted filesystem. (Linux permits a filesystem to be mounted at multiple points,
|
|
401
|
+
// but rename() does not work across different mount points, even if the same filesystem is mounted on both.)
|
|
402
|
+
// Instead of renaming it, i must copy the file and then delete the old one.
|
|
403
|
+
try {
|
|
404
|
+
fs.unlinkSync(newPath)
|
|
405
|
+
} catch (error) {
|
|
406
|
+
}
|
|
407
|
+
fs.copyFileSync(files.customHailing.path, newPath)
|
|
408
|
+
try {
|
|
409
|
+
fs.unlinkSync(files.customHailing.path);
|
|
410
|
+
} catch (error) {
|
|
411
|
+
// Don't care
|
|
412
|
+
}
|
|
413
|
+
res.json({ status: 220 });
|
|
414
|
+
res.end;
|
|
415
|
+
}
|
|
416
|
+
} catch (error) {
|
|
417
|
+
RED.log.error("Upload Hailing " + error.message);
|
|
418
|
+
res.json({ status: 404, message: "Unable to write to the filesystem. PLEASE CHECK THAT THE USER RUNNING NODE-RED, HAS PERMISSIONS TO WRITE TO THE FILESYSTEM." });
|
|
419
|
+
res.end;
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
// 22/12/2020 Receive the google credential and on the fly set the environment path
|
|
426
|
+
RED.httpAdmin.post("/ttsultimatesavegooglecredentialsfile", function (req, res) {
|
|
427
|
+
var form = new formidable.IncomingForm();
|
|
428
|
+
form.parse(req, function (err, fields, files) {
|
|
429
|
+
if (err) { };
|
|
430
|
+
// Allow only json
|
|
431
|
+
if (files.googleCreds.name.indexOf(".json") !== -1) {
|
|
432
|
+
var newPath = path.join(node.TTSRootFolderPath, "ttsultimategooglecredentials", "googlecredentials.json");
|
|
433
|
+
// Set the environment variable
|
|
434
|
+
process.env.GOOGLE_APPLICATION_CREDENTIALS = newPath;
|
|
435
|
+
// 30/12/2020 To avoid XDEV issue: oldpath and newpath are not on the same mounted filesystem. (Linux permits a filesystem to be mounted at multiple points,
|
|
436
|
+
// but rename() does not work across different mount points, even if the same filesystem is mounted on both.)
|
|
437
|
+
// Instead of renaming it, i must copy the file and then delete the old one.
|
|
438
|
+
try {
|
|
439
|
+
fs.unlinkSync(newPath)
|
|
440
|
+
} catch (error) {
|
|
441
|
+
}
|
|
442
|
+
fs.copyFileSync(files.googleCreds.path, newPath)
|
|
443
|
+
try {
|
|
444
|
+
fs.unlinkSync(files.googleCreds.path);
|
|
445
|
+
} catch (error) {
|
|
446
|
+
// Don't care
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
res.json({ status: 220 });
|
|
451
|
+
res.end;
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
// 26/10/2020 Supergiovane, get the updated voice list.
|
|
455
|
+
RED.httpAdmin.get("/ttsgetvoices" + encodeURIComponent(node.id), RED.auth.needsPermission('TTSConfigNode.read'), function (req, res) {
|
|
456
|
+
var ttsservice = req.query.ttsservice;// Retrieve the ttsservice engine
|
|
457
|
+
var jListVoices = [];
|
|
458
|
+
|
|
459
|
+
// 23/12/2020 Based on tts service engine, return appropriate voice list
|
|
460
|
+
if (ttsservice === "polly") {
|
|
461
|
+
try {
|
|
462
|
+
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Polly.html#describeVoices-property
|
|
463
|
+
var jfiltroVoci = {
|
|
464
|
+
//Engine: standard | neural,
|
|
465
|
+
//IncludeAdditionalLanguageCodes: true
|
|
466
|
+
//LanguageCode: arb | cmn-CN | cy-GB | da-DK | de-DE | en-AU | en-GB | en-GB-WLS | en-IN | en-US | es-ES | es-MX | es-US | fr-CA | fr-FR | is-IS | it-IT | ja-JP | hi-IN | ko-KR | nb-NO | nl-NL | pl-PL | pt-BR | pt-PT | ro-RO | ru-RU | sv-SE | tr-TR,
|
|
467
|
+
//NextToken: "STRING_VALUE"
|
|
468
|
+
};
|
|
469
|
+
node.polly.describeVoices(jfiltroVoci, function (err, data) {
|
|
470
|
+
if (err) {
|
|
471
|
+
RED.log.warn('ttsultimate-config ' + node.id + ': Error getting polly voices ' + err);
|
|
472
|
+
jListVoices.push({ name: "Error retrieving voices. " + err + " Check cretentials, deploy and restart node-red.", id: "Ivy" })
|
|
473
|
+
res.json(jListVoices)
|
|
474
|
+
} else {
|
|
475
|
+
for (let index = 0; index < data.Voices.length; index++) {
|
|
476
|
+
const oVoice = data.Voices[index];
|
|
477
|
+
if (oVoice.hasOwnProperty("SupportedEngines")) {
|
|
478
|
+
oVoice.SupportedEngines.forEach(voicetype => {
|
|
479
|
+
jListVoices.push({ name: oVoice.LanguageName + " (" + oVoice.LanguageCode + ") " + oVoice.Name + " - " + oVoice.Gender + " - " + voicetype, id: oVoice.Id + "#engineType:" + voicetype })
|
|
480
|
+
});
|
|
481
|
+
} else {
|
|
482
|
+
jListVoices.push({ name: oVoice.LanguageName + " (" + oVoice.LanguageCode + ") " + oVoice.Name + " - " + oVoice.Gender, id: oVoice.Id })
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
res.json(jListVoices)
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
} catch (error) {
|
|
490
|
+
jListVoices.push({ name: "Error " + error, id: "Ivy" })
|
|
491
|
+
res.json(jListVoices)
|
|
492
|
+
}
|
|
493
|
+
} else if (ttsservice === "googletts") {
|
|
494
|
+
async function listVoices() {
|
|
495
|
+
try {
|
|
496
|
+
const [result] = await node.googleTTS.listVoices({});
|
|
497
|
+
const voices = result.voices;
|
|
498
|
+
voices.forEach(oVoice => {
|
|
499
|
+
oVoice.languageCodes.forEach(languageCode => {
|
|
500
|
+
jListVoices.push({ name: languageCode + " " + oVoice.name + " - " + oVoice.ssmlGender, id: oVoice.name + "#" + languageCode + "#" + oVoice.ssmlGender })
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
res.json(jListVoices)
|
|
504
|
+
} catch (error) {
|
|
505
|
+
RED.log.error('ttsultimate-config ' + node.id + ': Error getting google TTS voices ' + error.message + " Please deploy and restart node-red.");
|
|
506
|
+
jListVoices.push({ name: "Error getting Google TTS voices. " + error.message + " Check credentials, deploy and restart node-red.", id: "Ivy" })
|
|
507
|
+
res.json(jListVoices)
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
try {
|
|
511
|
+
listVoices();
|
|
512
|
+
} catch (error) {
|
|
513
|
+
RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error getting google TTS voices ' + error.message + " Please deploy and restart node-red.");
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
} else if (ttsservice === "googletranslate") {
|
|
517
|
+
async function listVoices() {
|
|
518
|
+
try {
|
|
519
|
+
const voices = node.googleTranslateTTS.voices;
|
|
520
|
+
voices.forEach(oVoice => {
|
|
521
|
+
jListVoices.push({ name: oVoice.name + " - " + oVoice.code, id: oVoice.code })
|
|
522
|
+
});
|
|
523
|
+
res.json(jListVoices)
|
|
524
|
+
} catch (error) {
|
|
525
|
+
RED.log.error('ttsultimate-config ' + node.id + ': Error getting google Translate voices ' + error.message);
|
|
526
|
+
jListVoices.push({ name: "Error getting Google Translate voices. " + error.message + " Deploy and restart node-red.", id: "Ivy" })
|
|
527
|
+
res.json(jListVoices)
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
try {
|
|
531
|
+
listVoices();
|
|
532
|
+
} catch (error) {
|
|
533
|
+
RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error getting google Translate voices ' + error.message + " Please deploy and restart node-red.");
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
} else if (ttsservice === "microsoftazuretts") {
|
|
537
|
+
res.json(node.microsoftAzureTTSVoiceList);
|
|
538
|
+
} else if (ttsservice.includes("elevenlabs")) {
|
|
539
|
+
res.json(node.elevenlabsTTSVoiceList);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
// ########################################################
|
|
545
|
+
//#endregion
|
|
546
|
+
|
|
547
|
+
//#region OWNFILE NODE HTTP ENDPOINTS
|
|
548
|
+
// ######################################################
|
|
549
|
+
|
|
550
|
+
// Receive new files from html
|
|
551
|
+
RED.httpAdmin.post("/ttsultimateOwnFile", function (req, res) {
|
|
552
|
+
var form = new formidable.IncomingForm();
|
|
553
|
+
form.parse(req, function (err, fields, files) {
|
|
554
|
+
if (err) { };
|
|
555
|
+
// Allow only mp3
|
|
556
|
+
try {
|
|
557
|
+
if (files.customTTS.name.indexOf(".mp3") !== -1) {
|
|
558
|
+
var newPath = path.join(node.TTSRootFolderPath, "ttspermanentfiles", "OwnFile_" + files.customTTS.name);
|
|
559
|
+
// 30/12/2020 To avoid XDEV issue: oldpath and newpath are not on the same mounted filesystem. (Linux permits a filesystem to be mounted at multiple points,
|
|
560
|
+
// but rename() does not work across different mount points, even if the same filesystem is mounted on both.)
|
|
561
|
+
// Instead of renaming it, i must copy the file and then delete the old one.
|
|
562
|
+
try {
|
|
563
|
+
fs.unlinkSync(newPath)
|
|
564
|
+
} catch (error) {
|
|
565
|
+
}
|
|
566
|
+
fs.copyFileSync(files.customTTS.path, newPath)
|
|
567
|
+
try {
|
|
568
|
+
fs.unlinkSync(files.customTTS.path);
|
|
569
|
+
} catch (error) {
|
|
570
|
+
// Don't care
|
|
571
|
+
}
|
|
572
|
+
res.json({ status: 220 });
|
|
573
|
+
res.end;
|
|
574
|
+
}
|
|
575
|
+
} catch (error) {
|
|
576
|
+
RED.log.error("OwnFile " + error.message);
|
|
577
|
+
res.json({ status: 404, message: "Unable to write to the filesystem. PLEASE CHECK THAT THE USER RUNNING NODE-RED, HAS PERMISSIONS TO WRITE TO THE FILESYSTEM." });
|
|
578
|
+
res.end;
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
// 27/02/2020 Get list of filenames starting with OwnFile_
|
|
584
|
+
RED.httpAdmin.get("/getOwnFilesList", RED.auth.needsPermission('TTSConfigNode.read'), function (req, res) {
|
|
585
|
+
var jListOwnFiles = [];
|
|
586
|
+
var sName = "";
|
|
587
|
+
try {
|
|
588
|
+
fs.readdirSync(path.join(node.TTSRootFolderPath, "ttspermanentfiles")).forEach(file => {
|
|
589
|
+
if (file.indexOf("OwnFile_") > -1) {
|
|
590
|
+
sName = file.replace("OwnFile_", '').replace(".mp3", '');
|
|
591
|
+
jListOwnFiles.push({ name: sName, filename: file });
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
} catch (error) { }
|
|
596
|
+
res.json(jListOwnFiles)
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
// 27/02/2020 Delete OwnFile_
|
|
600
|
+
RED.httpAdmin.get("/deleteOwnFile", RED.auth.needsPermission('TTSConfigNode.read'), function (req, res) {
|
|
601
|
+
try {
|
|
602
|
+
if (req.query.FileName == "DELETEallFiles") {
|
|
603
|
+
// Delete all OwnFiles_
|
|
604
|
+
try {
|
|
605
|
+
fs.readdir(path.join(node.TTSRootFolderPath, "ttspermanentfiles"), (err, files) => {
|
|
606
|
+
files.forEach(function (file) {
|
|
607
|
+
if (file.indexOf("OwnFile_") !== -1) {
|
|
608
|
+
RED.log.warn("ttsultimate-config " + node.id + ": Deleted file " + path.join(node.TTSRootFolderPath, "ttspermanentfiles", file));
|
|
609
|
+
try {
|
|
610
|
+
fs.unlinkSync(path.join(node.TTSRootFolderPath, "ttspermanentfiles", file));
|
|
611
|
+
} catch (error) { }
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
} catch (error) { }
|
|
617
|
+
} else {
|
|
618
|
+
// Delete only one file
|
|
619
|
+
try {
|
|
620
|
+
var newPath = path.join(node.TTSRootFolderPath, "ttspermanentfiles", req.query.FileName);
|
|
621
|
+
try {
|
|
622
|
+
fs.unlinkSync(newPath)
|
|
623
|
+
} catch (error) { }
|
|
624
|
+
|
|
625
|
+
} catch (error) { }
|
|
626
|
+
}
|
|
627
|
+
} catch (err) {
|
|
628
|
+
}
|
|
629
|
+
res.json({ status: 220 });
|
|
630
|
+
});
|
|
631
|
+
// ########################################################
|
|
632
|
+
//#endregion
|
|
633
|
+
|
|
634
|
+
node.oWebserver; // 11/11/2019 Stores the Webserver
|
|
635
|
+
node.purgediratrestart = config.purgediratrestart || "leave"; // 26/02/2020
|
|
636
|
+
node.noderedport = typeof config.noderedport === "undefined" ? "1980" : config.noderedport;
|
|
637
|
+
// 11/11/2019 NEW in V 1.1.0, changed webserver behaviour. Redirect pre V. 1.1.0 1880 ports to the nde default 1980
|
|
638
|
+
if (node.noderedport.trim() == "1880") {
|
|
639
|
+
RED.log.warn("ttsultimate-config " + node.id + ": The webserver port ist 1880. Please change it to another port, not to conflict with default node-red 1880 port. I've changed this temporarly for you to 1980");
|
|
640
|
+
node.noderedport = "1980";
|
|
641
|
+
}
|
|
642
|
+
node.sNoderedURL = "http://" + node.noderedipaddress.trim() + ":" + node.noderedport.trim(); // 11/11/2019 New Endpoint to overcome https problem.
|
|
643
|
+
RED.log.info('ttsultimate-config ' + node.id + ': Node-Red node.js Endpoint will be created here: ' + node.sNoderedURL + "/tts");
|
|
644
|
+
|
|
645
|
+
// 26/02/2020
|
|
646
|
+
if (node.purgediratrestart === "purge") {
|
|
647
|
+
// Delete all files, that are'nt OwnFiles_
|
|
648
|
+
try {
|
|
649
|
+
let files = fs.readdirSync(path.join(node.TTSRootFolderPath, "ttsfiles"));
|
|
650
|
+
try {
|
|
651
|
+
if (files.length > 0) {
|
|
652
|
+
files.forEach(function (file) {
|
|
653
|
+
RED.log.info("ttsultimate-config " + node.id + ": Deleted TTS file " + path.join(node.TTSRootFolderPath, "ttsfiles", file));
|
|
654
|
+
try {
|
|
655
|
+
fs.unlinkSync(path.join(node.TTSRootFolderPath, "ttsfiles", file));
|
|
656
|
+
} catch (error) {
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
};
|
|
660
|
+
} catch (error) { }
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
} catch (error) { }
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
// 11/11/2019 CREATE THE ENDPOINT
|
|
670
|
+
// #################################
|
|
671
|
+
const http = require('http')
|
|
672
|
+
const sWebport = node.noderedport.trim();
|
|
673
|
+
const requestHandler = (req, res) => {
|
|
674
|
+
try {
|
|
675
|
+
|
|
676
|
+
var url = require('url');
|
|
677
|
+
var url_parts = url.parse(req.url, true);
|
|
678
|
+
var query = url_parts.query;
|
|
679
|
+
|
|
680
|
+
res.setHeader('Content-Disposition', 'attachment; filename=tts.mp3')
|
|
681
|
+
if (fs.existsSync(query.f.toString())) {
|
|
682
|
+
// 26/01/2021 security check
|
|
683
|
+
// File should be something like mydocs/.node-red/sonospollyttsstorage/ttsfiles/Hello_de-DE.mp3
|
|
684
|
+
if (path.extname(query.f.toString()) === ".mp3" && path.dirname(path.dirname(query.f.toString())).endsWith("sonospollyttsstorage")) {
|
|
685
|
+
var readStream = fs.createReadStream(query.f.toString());
|
|
686
|
+
readStream.on("error", function (error) {
|
|
687
|
+
RED.log.error("ttsultimate-config " + node.id + ": Playsonos error opening stream : " + query.f.toString() + ' : ' + error);
|
|
688
|
+
res.end();
|
|
689
|
+
return;
|
|
690
|
+
});
|
|
691
|
+
readStream.pipe(res);
|
|
692
|
+
} else {
|
|
693
|
+
res.write("NOT ALLOWED");
|
|
694
|
+
res.end();
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// http://localhost:1980/tts?f=/etc/passwd
|
|
698
|
+
|
|
699
|
+
} else {
|
|
700
|
+
RED.log.error("ttsultimate-config " + node.id + ": Playsonos RED.httpAdmin file not found: " + query.f);
|
|
701
|
+
res.write("File not found");
|
|
702
|
+
res.end();
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
} catch (error) {
|
|
706
|
+
RED.log.error("ttsultimate-config " + node.id + ": Playsonos RED.httpAdmin error: " + error + " on: " + query.f);
|
|
707
|
+
res.end();
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
try {
|
|
714
|
+
node.oWebserver = http.createServer(requestHandler);
|
|
715
|
+
node.oWebserver.on('error', function (e) {
|
|
716
|
+
RED.log.error("ttsultimate-config " + node.id + ": " + node.ID + " error starting webserver on port " + sWebport + " " + e);
|
|
717
|
+
});
|
|
718
|
+
} catch (error) {
|
|
719
|
+
// Already open. Close it and redo.
|
|
720
|
+
RED.log.error("ttsultimate-config " + node.id + ": Webserver creation error: " + error);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
try {
|
|
724
|
+
node.oWebserver.listen(sWebport, (err) => {
|
|
725
|
+
if (err) {
|
|
726
|
+
RED.log.error("ttsultimate-config " + node.id + ": error listening webserver on port " + sWebport + " " + err);
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
} catch (error) {
|
|
731
|
+
// In case oWebserver is null
|
|
732
|
+
RED.log.error("ttsultimate-config " + node.id + ": error listening webserver on port " + sWebport + " " + error);
|
|
733
|
+
}
|
|
734
|
+
// #################################
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
node.on('close', function (done) {
|
|
740
|
+
// 11/11/2019 Close the Webserver
|
|
741
|
+
try {
|
|
742
|
+
node.oWebserver.close(function () { RED.log.info("ttsultimate-config " + node.id + ": Webserver UP. Closing down."); });
|
|
743
|
+
} catch (error) {
|
|
744
|
+
|
|
745
|
+
}
|
|
746
|
+
let t = setTimeout(function () {
|
|
747
|
+
// Wait some time to allow time to do promises.
|
|
748
|
+
done();
|
|
749
|
+
}, 500);
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
|
|
753
|
+
}
|
|
754
|
+
RED.nodes.registerType("ttsultimate-config", TTSConfigNode, {
|
|
755
|
+
credentials: {
|
|
756
|
+
accessKey: { type: "text" },
|
|
757
|
+
secretKey: { type: "password" },
|
|
758
|
+
mssubscriptionKey: { type: "text" },
|
|
759
|
+
mslocation: { type: "text" },
|
|
760
|
+
elevenlabsKey: { type: "text" }
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
}
|
|
765
|
+
|
|
@@ -180,10 +180,10 @@
|
|
|
180
180
|
<div class="form-row">
|
|
181
181
|
<label for="node-config-input-ttsservice"><i class="fa fa-comments"></i> TTS Service</label>
|
|
182
182
|
<select id="node-config-input-ttsservice">
|
|
183
|
-
<option value="polly">Amazon Polly</option>
|
|
183
|
+
<option value="polly">(REMOVED. SEE THE GITHUB PROJECT PAGE)Amazon Polly</option>
|
|
184
184
|
<option value="googletts">Google TTS</option>
|
|
185
185
|
<option value="googletranslate">Google free TTS</option>
|
|
186
|
-
<option value="microsoftazuretts">Microsoft Azure TTS</option>
|
|
186
|
+
<option value="microsoftazuretts">(REMOVED. SEE THE GITHUB PROJECT PAGE)Microsoft Azure TTS</option>
|
|
187
187
|
<option value="elevenlabs">ElevenLabs TTS V1 (deprecated)</option>
|
|
188
188
|
<option value="elevenlabsv2">ElevenLabs TTS V2 Multilingual</option>
|
|
189
189
|
</select>  <b><span style="color:red"><i class="fa fa-question-circle"></i> <a target="_blank" href="https://github.com/Supergiovane/node-red-contrib-tts-ultimate"><u>Help configure</u></a></span>
|
|
@@ -253,15 +253,13 @@ For Google TTS Engine, you can choose pitch and speed rate of the voice.
|
|
|
253
253
|
<br/>
|
|
254
254
|
<br/>
|
|
255
255
|
|
|
256
|
-
* **TTS Service using Amazon AWS (Polly)**
|
|
256
|
+
* **(REMOVED AND NOT USED ANYMORE) TTS Service using Amazon AWS (Polly)**
|
|
257
|
+
|
|
258
|
+
!IF YOU NEED THIS SERVICE, INSTALL ANY VERSION < 3.0.0 (ANY 2.x.x IS FINE)!
|
|
259
|
+
> ``` npm install node-red-contrib-tts-ultimate@2.0.10 ```
|
|
257
260
|
|
|
258
261
|
> HOW-TO in Deutsch: for german users, there is a very helpful how-to, where you can learn how to use the node and how to register to Amazon AWS Polly as well: here: https://technikkram.net/blog/2020/09/26/sonos-sprachausgabe-mit-raspberry-pi-node-red-und-amazon-polly-fuer-homematic-oder-knx-systeme
|
|
259
|
-
|
|
260
|
-
**AWS Access key**<br/>
|
|
261
|
-
AWS access key credential
|
|
262
|
-
<br/><br/>
|
|
263
|
-
**AWS Secret key**<br/>
|
|
264
|
-
AWS access Secret key.
|
|
262
|
+
|
|
265
263
|
<br/>
|
|
266
264
|
|
|
267
265
|
* **TTS Service using Google (without credentials)**
|
|
@@ -281,13 +279,11 @@ For Google TTS Engine, you can choose pitch and speed rate of the voice.
|
|
|
281
279
|
|
|
282
280
|
<br/>
|
|
283
281
|
|
|
284
|
-
* **TTS Service using Microsot Azure TTS**
|
|
282
|
+
* **(REMOVED AND NOT USED ANYMORE) TTS Service using Microsot Azure TTS**
|
|
285
283
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
Then paste both into the TTS-Ultimate engine configuration window and restart node-red.<br/>
|
|
290
|
-
|
|
284
|
+
!IF YOU NEED THIS SERVICE, INSTALL ANY VERSION < 3.0.0 (ANY 2.x.x IS FINE)!
|
|
285
|
+
> ``` npm install node-red-contrib-tts-ultimate@2.0.10 ```
|
|
286
|
+
|
|
291
287
|
<br/>
|
|
292
288
|
|
|
293
289
|
* **TTS Service using ElevenLabs**
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const { Redshift } = require('aws-sdk');
|
|
2
1
|
|
|
3
2
|
module.exports = function (RED) {
|
|
4
3
|
'use strict';
|
|
@@ -11,10 +10,8 @@ module.exports = function (RED) {
|
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
// Setting up the engines
|
|
14
|
-
const AWS = require('aws-sdk');
|
|
15
13
|
const GoogleTTS = require('@google-cloud/text-to-speech');
|
|
16
14
|
const GoogleTranslate = require('google-translate-tts'); // TTS without credentials, limited to 200 chars per row.
|
|
17
|
-
const microsoftAzureTTS = require("microsoft-cognitiveservices-speech-sdk"); // 12/10/2021
|
|
18
15
|
const elevenlabsTTS = require("elevenlabs-node"); // 03/08/2023
|
|
19
16
|
const ElevenLabsClient = require("elevenlabs").ElevenLabsClient;
|
|
20
17
|
|
|
@@ -24,10 +21,6 @@ module.exports = function (RED) {
|
|
|
24
21
|
const oOS = require('os');
|
|
25
22
|
const sonos = require('sonos');
|
|
26
23
|
|
|
27
|
-
AWS.config.update({
|
|
28
|
-
region: 'us-east-1'
|
|
29
|
-
});
|
|
30
|
-
|
|
31
24
|
// Configuration Node Register
|
|
32
25
|
function TTSConfigNode(config) {
|
|
33
26
|
RED.nodes.createNode(this, config);
|
|
@@ -96,23 +89,6 @@ module.exports = function (RED) {
|
|
|
96
89
|
|
|
97
90
|
|
|
98
91
|
//#region "INSTANTIATE SERVICE ENGINES"
|
|
99
|
-
// POLLY
|
|
100
|
-
if (node.ttsservice === "polly") {
|
|
101
|
-
var params = {
|
|
102
|
-
accessKeyId: node.credentials.accessKey,
|
|
103
|
-
secretAccessKey: node.credentials.secretKey,
|
|
104
|
-
apiVersion: '2016-06-10'
|
|
105
|
-
};
|
|
106
|
-
try {
|
|
107
|
-
node.polly = new AWS.Polly(params);
|
|
108
|
-
RED.log.info("ttsultimate-config " + node.id + ": Polly service enabled.")
|
|
109
|
-
} catch (error) {
|
|
110
|
-
RED.log.warn("ttsultimate-config " + node.id + ": Polly service disabled. " + error.message)
|
|
111
|
-
}
|
|
112
|
-
} else {
|
|
113
|
-
//RED.log.info("ttsultimate-config " + node.id + ": Polly service not used.");
|
|
114
|
-
}
|
|
115
|
-
|
|
116
92
|
|
|
117
93
|
// Google TTS with authentication
|
|
118
94
|
if (node.ttsservice === "googletts") {
|
|
@@ -147,75 +123,7 @@ module.exports = function (RED) {
|
|
|
147
123
|
//RED.log.info("ttsultimate-config " + node.id + ": Google Translate free service not used.");
|
|
148
124
|
}
|
|
149
125
|
|
|
150
|
-
// 12/10/2021 Microsoft Azure TTS SpeechConfig.fromSubscription(subscriptionKey, serviceRegion)
|
|
151
|
-
if (node.ttsservice === "microsoftazuretts") {
|
|
152
|
-
// #########################################
|
|
153
|
-
node.setMicrosoftAzureVoice = function (_voiceName) {
|
|
154
|
-
let speechConfig = microsoftAzureTTS.SpeechConfig.fromSubscription(node.credentials.mssubscriptionKey, node.credentials.mslocation);
|
|
155
|
-
speechConfig.speechSynthesisVoiceName = _voiceName;
|
|
156
|
-
speechConfig.speechSynthesisOutputFormat = microsoftAzureTTS.SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3;
|
|
157
|
-
node.microsoftAzureTTS = new microsoftAzureTTS.SpeechSynthesizer(speechConfig);
|
|
158
|
-
return node.microsoftAzureTTS;
|
|
159
|
-
}
|
|
160
|
-
try {
|
|
161
|
-
|
|
162
|
-
let speechConfig = microsoftAzureTTS.SpeechConfig.fromSubscription(node.credentials.mssubscriptionKey, node.credentials.mslocation);
|
|
163
|
-
//speechConfig.speechSynthesisLanguage = "it-IT";
|
|
164
|
-
//speechConfig.speechSynthesisVoiceName = "it-IT-IsabellaNeural";
|
|
165
|
-
speechConfig.speechSynthesisOutputFormat = microsoftAzureTTS.SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3;
|
|
166
|
-
node.microsoftAzureTTS = new microsoftAzureTTS.SpeechSynthesizer(speechConfig);
|
|
167
|
-
node.microsoftAzureTTSVoiceList = [];
|
|
168
|
-
// Get the voices
|
|
169
|
-
async function listVoicesAzure() {
|
|
170
|
-
const httpsAzure = require('https')
|
|
171
|
-
let options = {
|
|
172
|
-
hostname: node.credentials.mslocation + '.tts.speech.microsoft.com',
|
|
173
|
-
port: 443,
|
|
174
|
-
path: '/cognitiveservices/voices/list',
|
|
175
|
-
method: 'GET',
|
|
176
|
-
headers: {
|
|
177
|
-
'Ocp-Apim-Subscription-Key': node.credentials.mssubscriptionKey
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
const reqAzure = httpsAzure.request(options, resVoices => {
|
|
181
|
-
var sChunkResponse = "";
|
|
182
|
-
resVoices.on('data', d => {
|
|
183
|
-
sChunkResponse += d.toString();
|
|
184
|
-
})
|
|
185
|
-
resVoices.on('end', () => {
|
|
186
|
-
try {
|
|
187
|
-
let oVoices = JSON.parse(sChunkResponse);
|
|
188
|
-
RED.log.info('ttsultimate-config ' + node.id + ': Microsoft Azure voices count: ' + oVoices.length);
|
|
189
|
-
for (let index = 0; index < oVoices.length; index++) {
|
|
190
|
-
const element = oVoices[index];
|
|
191
|
-
node.microsoftAzureTTSVoiceList.push({ name: element.ShortName + " (" + element.Gender + ")", id: element.ShortName })
|
|
192
|
-
}
|
|
193
|
-
} catch (error) {
|
|
194
|
-
RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error parsing Microsoft Azure TTS voices: ' + error.message);
|
|
195
|
-
node.microsoftAzureTTSVoiceList.push({ name: "Error parsing Microsoft Azure voices: " + error.message + " Check cretentials, deploy and restart node-red.", id: "Ivy" });
|
|
196
|
-
}
|
|
197
|
-
})
|
|
198
|
-
})
|
|
199
|
-
reqAzure.on('error', error => {
|
|
200
|
-
RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error contacting Azure for getting the voices list: ' + error.message);
|
|
201
|
-
node.microsoftAzureTTSVoiceList.push({ name: "Error getting Microsoft Azure voices: " + error.message + " Check cretentials, deploy and restart node-red.", id: "Ivy" })
|
|
202
|
-
reqAzure.end();
|
|
203
|
-
})
|
|
204
|
-
reqAzure.end();
|
|
205
|
-
};
|
|
206
|
-
RED.log.info("ttsultimate-config " + node.id + ": Microsoft AzureTTS service enabled.")
|
|
207
|
-
try {
|
|
208
|
-
listVoicesAzure();
|
|
209
|
-
} catch (error) {
|
|
210
|
-
RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error getting Microsoft Azure voices: ' + error.message);
|
|
211
|
-
}
|
|
212
126
|
|
|
213
|
-
} catch (error) {
|
|
214
|
-
RED.log.warn("ttsultimate-config " + node.id + ": Microsoft AzureTTS service disabled. " + error.message)
|
|
215
|
-
}
|
|
216
|
-
} else {
|
|
217
|
-
//RED.log.info("ttsultimate-config " + node.id + ": Microsoft AzureTTS service not used. ");
|
|
218
|
-
}
|
|
219
127
|
// #########################################
|
|
220
128
|
|
|
221
129
|
// elevenlabsTTS v1 deprecated
|
|
@@ -457,40 +365,7 @@ module.exports = function (RED) {
|
|
|
457
365
|
var jListVoices = [];
|
|
458
366
|
|
|
459
367
|
// 23/12/2020 Based on tts service engine, return appropriate voice list
|
|
460
|
-
if (ttsservice === "
|
|
461
|
-
try {
|
|
462
|
-
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Polly.html#describeVoices-property
|
|
463
|
-
var jfiltroVoci = {
|
|
464
|
-
//Engine: standard | neural,
|
|
465
|
-
//IncludeAdditionalLanguageCodes: true
|
|
466
|
-
//LanguageCode: arb | cmn-CN | cy-GB | da-DK | de-DE | en-AU | en-GB | en-GB-WLS | en-IN | en-US | es-ES | es-MX | es-US | fr-CA | fr-FR | is-IS | it-IT | ja-JP | hi-IN | ko-KR | nb-NO | nl-NL | pl-PL | pt-BR | pt-PT | ro-RO | ru-RU | sv-SE | tr-TR,
|
|
467
|
-
//NextToken: "STRING_VALUE"
|
|
468
|
-
};
|
|
469
|
-
node.polly.describeVoices(jfiltroVoci, function (err, data) {
|
|
470
|
-
if (err) {
|
|
471
|
-
RED.log.warn('ttsultimate-config ' + node.id + ': Error getting polly voices ' + err);
|
|
472
|
-
jListVoices.push({ name: "Error retrieving voices. " + err + " Check cretentials, deploy and restart node-red.", id: "Ivy" })
|
|
473
|
-
res.json(jListVoices)
|
|
474
|
-
} else {
|
|
475
|
-
for (let index = 0; index < data.Voices.length; index++) {
|
|
476
|
-
const oVoice = data.Voices[index];
|
|
477
|
-
if (oVoice.hasOwnProperty("SupportedEngines")) {
|
|
478
|
-
oVoice.SupportedEngines.forEach(voicetype => {
|
|
479
|
-
jListVoices.push({ name: oVoice.LanguageName + " (" + oVoice.LanguageCode + ") " + oVoice.Name + " - " + oVoice.Gender + " - " + voicetype, id: oVoice.Id + "#engineType:" + voicetype })
|
|
480
|
-
});
|
|
481
|
-
} else {
|
|
482
|
-
jListVoices.push({ name: oVoice.LanguageName + " (" + oVoice.LanguageCode + ") " + oVoice.Name + " - " + oVoice.Gender, id: oVoice.Id })
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
res.json(jListVoices)
|
|
486
|
-
}
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
} catch (error) {
|
|
490
|
-
jListVoices.push({ name: "Error " + error, id: "Ivy" })
|
|
491
|
-
res.json(jListVoices)
|
|
492
|
-
}
|
|
493
|
-
} else if (ttsservice === "googletts") {
|
|
368
|
+
if (ttsservice === "googletts") {
|
|
494
369
|
async function listVoices() {
|
|
495
370
|
try {
|
|
496
371
|
const [result] = await node.googleTTS.listVoices({});
|
|
@@ -533,8 +408,6 @@ module.exports = function (RED) {
|
|
|
533
408
|
RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error getting google Translate voices ' + error.message + " Please deploy and restart node-red.");
|
|
534
409
|
}
|
|
535
410
|
|
|
536
|
-
} else if (ttsservice === "microsoftazuretts") {
|
|
537
|
-
res.json(node.microsoftAzureTTSVoiceList);
|
|
538
411
|
} else if (ttsservice.includes("elevenlabs")) {
|
|
539
412
|
res.json(node.elevenlabsTTSVoiceList);
|
|
540
413
|
}
|
|
@@ -79,7 +79,10 @@
|
|
|
79
79
|
<label for="node-input-unmuteIfMuted"><i class="fa fa-bell-slash-o"></i> Unmute</label>
|
|
80
80
|
<input type="checkbox" id="node-input-unmuteIfMuted" style="margin-left: 0px; vertical-align: top; width: auto !important;"> <label style="width:auto !important;"> Unmute, then restore previous state after play.</label>
|
|
81
81
|
</div>
|
|
82
|
-
|
|
82
|
+
<div class="form-row">
|
|
83
|
+
<label for="node-input-doNotResumeMusic"><i class="fa fa-bell-slash-o"></i> Resume</label>
|
|
84
|
+
<input type="checkbox" id="node-input-doNotResumeMusic" style="margin-left: 0px; vertical-align: top; width: auto !important;"> <label style="width:auto !important;"> Do not resume previous music.</label>
|
|
85
|
+
</div>
|
|
83
86
|
<div class="form-row">
|
|
84
87
|
<label for="node-input-sonosipaddress"><i class="fa fa-globe"></i> Main Sonos Player</label>
|
|
85
88
|
<label style="width:200px;" id="node-input-sonosipaddress">Discovering.... wait...</label>
|
|
@@ -136,6 +139,12 @@
|
|
|
136
139
|
value: "Hailing_Hailing.mp3",
|
|
137
140
|
required: false,
|
|
138
141
|
},
|
|
142
|
+
doNotResumeMusic:
|
|
143
|
+
{
|
|
144
|
+
value: true,
|
|
145
|
+
required: false,
|
|
146
|
+
},
|
|
147
|
+
|
|
139
148
|
config:
|
|
140
149
|
{
|
|
141
150
|
type: "ttsultimate-config",
|
|
@@ -544,21 +553,30 @@
|
|
|
544
553
|
|--|--|
|
|
545
554
|
| TTS Service | Select the TTS SERVICE ENGINE NODE. |
|
|
546
555
|
| Voice | Select your preferred voice. If you use Amazon, Polly voices will be displayed (standard and neural). If you use Google, google voices will be displayed. Google service without authentication, has a limited set of voices. |
|
|
547
|
-
| Enable SSML | Enable the SSML XML notation. Please be aware, not all the TTS engines supports that.|
|
|
548
|
-
| Rate | Only avaiable if you choose Google TTS Engine (with credentials). Specifies the speech speed (Between 0.25 and 4.0, default 1). |
|
|
549
|
-
| Pitch | Only avaiable if you choose Google TTS Engine (with credentials). Specifies the speech pitch (Between -20.0 and 20.0, default 0). |
|
|
550
|
-
| Stability | Only avaiable with Elevenlabs. Please refer to Elevenlabs.io description. Values from 0 to 1 (for example 0.2, 0.7, etc.) |
|
|
551
|
-
| Similarity | Only avaiable with Elevenlabs. Please refer to Elevenlabs.io description. Values from 0 to 1 (for example 0.2, 0.7, etc.) |
|
|
552
|
-
| Style Exaggeration | Only avaiable with Elevenlabs. Please refer to Elevenlabs.io description. Values from 0 to 1 (for example 0.2, 0.7, etc.)|
|
|
553
|
-
| Speaker boost | Only avaiable with Elevenlabs. Please refer to Elevenlabs.io description. Values from 0 to 1 (for example 0.2, 0.7, etc.) |
|
|
554
556
|
| Hailing | Before the first TTS message of the message queues, the node will play an "hailing" sound. You can select the hailing, upload your own, or totally disable it. |
|
|
555
557
|
| Upload hail | It allows you to upload your own hailing file. |
|
|
556
558
|
| Player | Select the player. If you select not to use a player, the node will output a msg with an array of files, ready to be played by third party nodes. In case you select No player, only output file name, you'll get a message with an additional property filesArray, containing an array of all mp3 files ready to be played with third party nodes. Please see below the OUTPUT MESSAGES FROM THE NODE section. |
|
|
557
559
|
| Volume | Set the preferred TTS volume, from "0" to "100" (can be overridden by passing msg.volume = "40"; to the node). |
|
|
558
560
|
| Unmute | Unmute the main and the addotional players, then restore the previous mute state once finished. (Can be overridden by passing msg.unmute = true; to the node). |
|
|
561
|
+
| Resume | If music was playing prior to TTS messages, the node will try to resume it, but can fail in some cases. Enabla this option to avoid resuming music after TTS message. |
|
|
559
562
|
| Main Sonos Player | Select your Sonos primary player. (It's strongly suggested to set a fixed IP for this player; you can reserve an IP using the DHCP Reservation function of your router/firewall's DHCP Server). It's possibile to group players, so your announcement can be played on all selected players. For this to happen, you need to select your primary coordinator player. All other players will be then controlled by this coordinator. |
|
|
560
563
|
| Additional Players | Here you can add all additional players that will be grouped toghether to the Main Sonos Player coordinator group. You can add a player using the "ADD" button, below the list. For each additional player, you can adjust their volume, based on the Main Sonos Player volume -+100. |
|
|
561
564
|
|
|
565
|
+
**Google TTS Engines specific options**
|
|
566
|
+
|Property|Description|
|
|
567
|
+
|--|--|
|
|
568
|
+
| Enable SSML | Enable the SSML XML notation. Please be aware, not all the TTS engines supports that.|
|
|
569
|
+
| Rate | Only avaiable if you choose Google TTS Engine (with credentials). Specifies the speech speed (Between 0.25 and 4.0, default 1). |
|
|
570
|
+
| Pitch | Only avaiable if you choose Google TTS Engine (with credentials). Specifies the speech pitch (Between -20.0 and 20.0, default 0). |
|
|
571
|
+
|
|
572
|
+
**Elevenlabs TTS Engines specific options**
|
|
573
|
+
|Property|Description|
|
|
574
|
+
|--|--|
|
|
575
|
+
| Stability | Only avaiable with Elevenlabs. Please refer to Elevenlabs.io description. Values from 0 to 1 (for example 0.2, 0.7, etc.) |
|
|
576
|
+
| Similarity | Only avaiable with Elevenlabs. Please refer to Elevenlabs.io description. Values from 0 to 1 (for example 0.2, 0.7, etc.) |
|
|
577
|
+
| Style Exaggeration | Only avaiable with Elevenlabs. Please refer to Elevenlabs.io description. Values from 0 to 1 (for example 0.2, 0.7, etc.)|
|
|
578
|
+
| Speaker boost | Only avaiable with Elevenlabs. Please refer to Elevenlabs.io description. Values from 0 to 1 (for example 0.2, 0.7, etc.) |
|
|
579
|
+
|
|
562
580
|
<br/>
|
|
563
581
|
|
|
564
582
|
### Inputs
|
|
@@ -80,7 +80,7 @@ module.exports = function (RED) {
|
|
|
80
80
|
node.sonosCoordinatorIsPreviouslyMuted = false;
|
|
81
81
|
node.passThroughMessage = {};
|
|
82
82
|
node.bTimeOutPlay = false;
|
|
83
|
-
|
|
83
|
+
node.doNotResumeMusic = config.doNotResumeMusic === undefined ? false : config.doNotResumeMusic; // 06/2025 Do not resume previous music after TTS, if playing.
|
|
84
84
|
if (typeof node.server !== "undefined" && node.server !== null) {
|
|
85
85
|
node.sNoderedURL = node.server.sNoderedURL || "";
|
|
86
86
|
}
|
|
@@ -857,37 +857,39 @@ module.exports = function (RED) {
|
|
|
857
857
|
await delay(2000);
|
|
858
858
|
|
|
859
859
|
// Resume music
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
860
|
+
if (!node.doNotResumeMusic) {
|
|
861
|
+
try {
|
|
862
|
+
if (oCurTrack !== null && (!oCurTrack.hasOwnProperty("title") || oCurTrack.title.indexOf(".mp3") === -1)) {
|
|
863
|
+
node.setNodeStatus({ fill: 'grey', shape: 'ring', text: "Resuming original queue..." });
|
|
864
|
+
await resumeMusicQueue(oCurTrack);
|
|
865
|
+
node.setNodeStatus({ fill: 'green', shape: 'ring', text: "Done resuming queue." });
|
|
866
|
+
} else {
|
|
867
|
+
// 28/08/2021 There was no queue playing. Delete the TTS from the queue
|
|
868
|
+
node.setNodeStatus({ fill: 'green', shape: 'ring', text: "No queue to resume." });
|
|
869
|
+
}
|
|
870
|
+
} catch (error) {
|
|
871
|
+
node.setNodeStatus({ fill: 'red', shape: 'ring', text: "Error resuming queue: " + error.message });
|
|
868
872
|
}
|
|
869
|
-
} catch (error) {
|
|
870
|
-
node.setNodeStatus({ fill: 'red', shape: 'ring', text: "Error resuming queue: " + error.message });
|
|
871
873
|
}
|
|
872
874
|
|
|
873
|
-
|
|
874
875
|
// 19/04/2022 Resume music queue of additional players
|
|
875
|
-
|
|
876
|
-
let
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
876
|
+
if (!node.doNotResumeMusic) {
|
|
877
|
+
for (let index = 0; index < node.oAdditionalSonosPlayers.length; index++) {
|
|
878
|
+
let addPlayer = node.oAdditionalSonosPlayers[index].oPlayer;
|
|
879
|
+
let trackAddPlayer = addPlayer.additionalPlayerCurrentTrack;
|
|
880
|
+
if (trackAddPlayer !== null) {
|
|
881
|
+
try {
|
|
882
|
+
await resumeMusicQueue(trackAddPlayer, addPlayer);
|
|
883
|
+
node.setNodeStatus({ fill: 'green', shape: 'ring', text: "Done resuming queue additional player " + addPlayer.host || "" });
|
|
884
|
+
} catch (error) {
|
|
885
|
+
// Dont care
|
|
886
|
+
RED.log.warn("ttsultimate: Error resuming music queue of additional player " + error.message + " " + addPlayer.host || "");
|
|
887
|
+
}
|
|
888
|
+
} else {
|
|
889
|
+
node.setNodeStatus({ fill: 'green', shape: 'ring', text: "No queue to resume for " + addPlayer.host || "" });
|
|
885
890
|
}
|
|
886
|
-
} else {
|
|
887
|
-
node.setNodeStatus({ fill: 'green', shape: 'ring', text: "No queue to resume for " + addPlayer.host || "" });
|
|
888
891
|
}
|
|
889
892
|
}
|
|
890
|
-
|
|
891
893
|
// Signal end playing
|
|
892
894
|
let t = setTimeout(() => {
|
|
893
895
|
node.msg.completed = true;
|