node-red-contrib-tts-ultimate 1.0.34 → 1.0.35
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 +4 -0
- package/README.md +7 -0
- package/package.json +1 -1
- package/ttsultimate/ttsultimate-config.js +41 -42
- package/ttsultimate/ttsultimate.html +8 -1
- package/ttsultimate/ttsultimate.js +66 -17
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.paypal.me/techtoday)
|
|
4
4
|
|
|
5
|
+
<p>
|
|
6
|
+
<b>Version 1.0.35</b> October 2021<br/>
|
|
7
|
+
- NEW: You can force unmuting all players, then restore their previous state once finished playing.<br/>
|
|
8
|
+
<p>
|
|
5
9
|
<p>
|
|
6
10
|
<b>Version 1.0.34</b> October 2021<br/>
|
|
7
11
|
- FIX: fixed an issue in retrieving voices if you have more than one TTS engine enabled at the same time.<br/>
|
package/README.md
CHANGED
|
@@ -151,6 +151,9 @@ In case you select ***No player, only output file name***, you'll get a message
|
|
|
151
151
|
**Volume** <br/>
|
|
152
152
|
Set the preferred TTS volume, from "0" to "100" (can be overridden by passing <code>msg.volume = "40";</code> to the node)
|
|
153
153
|
|
|
154
|
+
**Unmute** <br/>
|
|
155
|
+
Unmute the main and the addotional players, then restore the previous mute state once finished. (Can be overridden by passing <code>msg.unmute = true;</code> to the node)
|
|
156
|
+
|
|
154
157
|
**Main Sonos Player** <br/>
|
|
155
158
|
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).<br/>
|
|
156
159
|
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.
|
|
@@ -164,6 +167,10 @@ Here you can add all additional players that will be grouped toghether to the *M
|
|
|
164
167
|
**msg.volume**<br/>
|
|
165
168
|
Set the volume (values between "0" and "100" with quotes)</br>
|
|
166
169
|
|
|
170
|
+
**msg.unmute**<br/>
|
|
171
|
+
*true*: Unmute all players then mutes it again once finished playing.</br>
|
|
172
|
+
*false*: Leave the player as they are.</br>
|
|
173
|
+
|
|
167
174
|
**msg.nohailing**<br/>
|
|
168
175
|
Temporarely doesn't play the Hailing sound prior to the message (values "true" or "1" with quotes)</br>
|
|
169
176
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-tts-ultimate",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.35",
|
|
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, 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": {
|
|
@@ -145,59 +145,58 @@ module.exports = function (RED) {
|
|
|
145
145
|
return node.microsoftAzureTTS;
|
|
146
146
|
}
|
|
147
147
|
try {
|
|
148
|
-
|
|
148
|
+
|
|
149
149
|
let speechConfig = microsoftAzureTTS.SpeechConfig.fromSubscription(node.credentials.mssubscriptionKey, node.credentials.mslocation);
|
|
150
150
|
//speechConfig.speechSynthesisLanguage = "it-IT";
|
|
151
151
|
//speechConfig.speechSynthesisVoiceName = "it-IT-IsabellaNeural";
|
|
152
152
|
speechConfig.speechSynthesisOutputFormat = microsoftAzureTTS.SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3;
|
|
153
153
|
node.microsoftAzureTTS = new microsoftAzureTTS.SpeechSynthesizer(speechConfig);
|
|
154
154
|
node.microsoftAzureTTSVoiceList = [];
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
'Ocp-Apim-Subscription-Key': node.credentials.mssubscriptionKey
|
|
166
|
-
}
|
|
155
|
+
// Get the voices
|
|
156
|
+
async function listVoicesAzure() {
|
|
157
|
+
const httpsAzure = require('https')
|
|
158
|
+
let options = {
|
|
159
|
+
hostname: node.credentials.mslocation + '.tts.speech.microsoft.com',
|
|
160
|
+
port: 443,
|
|
161
|
+
path: '/cognitiveservices/voices/list',
|
|
162
|
+
method: 'GET',
|
|
163
|
+
headers: {
|
|
164
|
+
'Ocp-Apim-Subscription-Key': node.credentials.mssubscriptionKey
|
|
167
165
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
resVoices.on('end', () => {
|
|
174
|
-
try {
|
|
175
|
-
let oVoices = JSON.parse(sChunkResponse);
|
|
176
|
-
RED.log.info('ttsultimate-config ' + node.id + ': Microsoft Azure voices count: ' + oVoices.length);
|
|
177
|
-
for (let index = 0; index < oVoices.length; index++) {
|
|
178
|
-
const element = oVoices[index];
|
|
179
|
-
node.microsoftAzureTTSVoiceList.push({ name: element.ShortName + " (" + element.Gender + ")", id: element.ShortName })
|
|
180
|
-
}
|
|
181
|
-
} catch (error) {
|
|
182
|
-
RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error parsing Microsoft Azure TTS voices: ' + error.message);
|
|
183
|
-
node.microsoftAzureTTSVoiceList.push({ name: "Error parsing Microsoft Azure voices: " + error.message, id: "Ivy" });
|
|
184
|
-
}
|
|
185
|
-
})
|
|
166
|
+
}
|
|
167
|
+
const reqAzure = httpsAzure.request(options, resVoices => {
|
|
168
|
+
var sChunkResponse = "";
|
|
169
|
+
resVoices.on('data', d => {
|
|
170
|
+
sChunkResponse += d.toString();
|
|
186
171
|
})
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
172
|
+
resVoices.on('end', () => {
|
|
173
|
+
try {
|
|
174
|
+
let oVoices = JSON.parse(sChunkResponse);
|
|
175
|
+
RED.log.info('ttsultimate-config ' + node.id + ': Microsoft Azure voices count: ' + oVoices.length);
|
|
176
|
+
for (let index = 0; index < oVoices.length; index++) {
|
|
177
|
+
const element = oVoices[index];
|
|
178
|
+
node.microsoftAzureTTSVoiceList.push({ name: element.ShortName + " (" + element.Gender + ")", id: element.ShortName })
|
|
179
|
+
}
|
|
180
|
+
} catch (error) {
|
|
181
|
+
RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error parsing Microsoft Azure TTS voices: ' + error.message);
|
|
182
|
+
node.microsoftAzureTTSVoiceList.push({ name: "Error parsing Microsoft Azure voices: " + error.message, id: "Ivy" });
|
|
183
|
+
}
|
|
191
184
|
})
|
|
185
|
+
})
|
|
186
|
+
reqAzure.on('error', error => {
|
|
187
|
+
RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error contacting Azure for getting the voices list: ' + error.message);
|
|
188
|
+
node.microsoftAzureTTSVoiceList.push({ name: "Error getting Microsoft Azure voices: " + error.message, id: "Ivy" })
|
|
192
189
|
reqAzure.end();
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
190
|
+
})
|
|
191
|
+
reqAzure.end();
|
|
192
|
+
};
|
|
193
|
+
RED.log.info("ttsultimate-config " + node.id + ": Microsoft AzureTTS service enabled.")
|
|
194
|
+
try {
|
|
195
|
+
listVoicesAzure();
|
|
196
|
+
} catch (error) {
|
|
197
|
+
RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error getting Microsoft Azure voices: ' + error.message);
|
|
200
198
|
}
|
|
199
|
+
|
|
201
200
|
} catch (error) {
|
|
202
201
|
RED.log.warn("ttsultimate-config " + node.id + ": Microsoft AzureTTS service disabled. " + error.message)
|
|
203
202
|
}
|
|
@@ -58,6 +58,11 @@
|
|
|
58
58
|
<label for="node-input-sonosvolume"><i class="fa fa-volume-up"></i> Volume</label>
|
|
59
59
|
<input type="text" id="node-input-sonosvolume" style="width:150px">
|
|
60
60
|
</div>
|
|
61
|
+
<div class="form-row">
|
|
62
|
+
<label for="node-input-unmuteIfMuted"><i class="fa fa-bell-slash-o"></i> Unmute</label>
|
|
63
|
+
<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>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
61
66
|
<div class="form-row">
|
|
62
67
|
<label><i class="fa fa-upload"></i> Upload hail</label>
|
|
63
68
|
<input id="ownFileUpload" type="file" multiple="multiple">
|
|
@@ -128,7 +133,9 @@
|
|
|
128
133
|
rules: { value: [] },
|
|
129
134
|
playertype: { value: "sonos", required: false },
|
|
130
135
|
speakingrate: { value: "1", required: false },
|
|
131
|
-
speakingpitch: { value: "0", required: false }
|
|
136
|
+
speakingpitch: { value: "0", required: false },
|
|
137
|
+
unmuteIfMuted: { value: true }
|
|
138
|
+
|
|
132
139
|
},
|
|
133
140
|
inputs: 1,
|
|
134
141
|
outputs: 2,
|
|
@@ -76,6 +76,8 @@ module.exports = function (RED) {
|
|
|
76
76
|
node.playertype = config.playertype === undefined ? "sonos" : config.playertype; // 20/09/2021 Player type
|
|
77
77
|
node.speakingpitch = config.speakingpitch === undefined ? "0" : config.speakingpitch; // 21/09/2021 AudioConfig speakingpitch
|
|
78
78
|
node.speakingrate = config.speakingrate === undefined ? "1" : config.speakingrate; // 21/09/2021 AudioConfig speakingrate
|
|
79
|
+
node.unmuteIfMuted = config.unmuteIfMuted === undefined ? false : config.unmuteIfMuted; // 21/10/2021 Unmute if previiously muted.
|
|
80
|
+
node.sonosCoordinatorIsPreviouslyMuted = false;
|
|
79
81
|
|
|
80
82
|
if (typeof node.server !== "undefined" && node.server !== null) {
|
|
81
83
|
node.sNoderedURL = node.server.sNoderedURL || "";
|
|
@@ -243,18 +245,29 @@ module.exports = function (RED) {
|
|
|
243
245
|
});
|
|
244
246
|
}
|
|
245
247
|
|
|
246
|
-
//
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
248
|
+
// 21/10/2021 Sync wrapper
|
|
249
|
+
function GETMutedSync() {
|
|
250
|
+
return new Promise((resolve, reject) => {
|
|
251
|
+
node.SonosClient.getMuted().then(state => {
|
|
252
|
+
resolve(state);
|
|
253
|
+
}).catch(err => {
|
|
254
|
+
RED.log.error("ttsultimate: Error GETMutedSync: " + err.message);
|
|
255
|
+
reject(err);
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// 21/10/2021 Sync wrapper
|
|
261
|
+
function SETMutedSync(_muted) {
|
|
262
|
+
return new Promise((resolve, reject) => {
|
|
263
|
+
node.SonosClient.setMuted(_muted).then(state => {
|
|
264
|
+
resolve(state);
|
|
265
|
+
}).catch(err => {
|
|
266
|
+
RED.log.error("ttsultimate: Error SETMutedSync: " + err.message);
|
|
267
|
+
reject(err);
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
}
|
|
258
271
|
|
|
259
272
|
// 20/03/2020 Join Coordinator queue
|
|
260
273
|
// ######################################################
|
|
@@ -262,11 +275,12 @@ module.exports = function (RED) {
|
|
|
262
275
|
// 05/07/2021 Get the main coordinator player previous volume set by app
|
|
263
276
|
try {
|
|
264
277
|
node.sonosCoordinatorPreviousVolumeSetByApp = await GETVOLUMESync();
|
|
278
|
+
node.sonosCoordinatorIsPreviouslyMuted = await GETMutedSync();
|
|
265
279
|
} catch (error) {
|
|
266
280
|
node.sonosCoordinatorPreviousVolumeSetByApp = node.sSonosVolume;
|
|
281
|
+
node.sonosCoordinatorIsPreviouslyMuted = false;
|
|
267
282
|
}
|
|
268
283
|
// 30/03/2020 in the middle of coronavirus emergency. Group Speakers
|
|
269
|
-
// You don't have to worry about who is the coordinator.
|
|
270
284
|
for (let index = 0; index < node.oAdditionalSonosPlayers.length; index++) {
|
|
271
285
|
const element = node.oAdditionalSonosPlayers[index];
|
|
272
286
|
try {
|
|
@@ -280,6 +294,12 @@ module.exports = function (RED) {
|
|
|
280
294
|
} catch (error) {
|
|
281
295
|
RED.log.warn("ttsultimate: Error setting volume of joined device " + error.message);
|
|
282
296
|
}
|
|
297
|
+
// 21/10/2021 set the previous mute/unmute state
|
|
298
|
+
try {
|
|
299
|
+
element.isPreviouslyMuted = await element.getMuted();
|
|
300
|
+
} catch (error) {
|
|
301
|
+
RED.log.warn("ttsultimate: Error getMuted of joined device " + error.message);
|
|
302
|
+
}
|
|
283
303
|
};
|
|
284
304
|
}
|
|
285
305
|
|
|
@@ -291,6 +311,14 @@ module.exports = function (RED) {
|
|
|
291
311
|
} catch (error) {
|
|
292
312
|
RED.log.warn("ttsultimate: Error set preious volume on main coordinator in ungroupSpeakers " + error.message);
|
|
293
313
|
}
|
|
314
|
+
// 21/10/2021 Unmute?
|
|
315
|
+
if (node.unmuteIfMuted && node.sonosCoordinatorIsPreviouslyMuted) {
|
|
316
|
+
try {
|
|
317
|
+
await SETMutedSync(true);
|
|
318
|
+
} catch (error) {
|
|
319
|
+
RED.log.warn("ttsultimate: Error set preivous mute state on main coordinator in ungroupSpeakers " + error.message);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
294
322
|
|
|
295
323
|
for (let index = 0; index < node.oAdditionalSonosPlayers.length; index++) {
|
|
296
324
|
const element = node.oAdditionalSonosPlayers[index];
|
|
@@ -307,6 +335,14 @@ module.exports = function (RED) {
|
|
|
307
335
|
RED.log.warn("ttsultimate: Error set previous volume on group device " + error.message);
|
|
308
336
|
}
|
|
309
337
|
}
|
|
338
|
+
// 21/10/2021 Unmute?
|
|
339
|
+
if (node.unmuteIfMuted && element.isPreviouslyMuted) {
|
|
340
|
+
try {
|
|
341
|
+
await element.setMuted(true);
|
|
342
|
+
} catch (error) {
|
|
343
|
+
RED.log.warn("ttsultimate: Error set previous mute state on group device " + error.message);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
310
346
|
}
|
|
311
347
|
}
|
|
312
348
|
// ######################################################
|
|
@@ -617,12 +653,15 @@ module.exports = function (RED) {
|
|
|
617
653
|
volTemp = node.sSonosVolume;
|
|
618
654
|
}
|
|
619
655
|
await SETVOLUMESync(volTemp);
|
|
656
|
+
if (node.unmuteIfMuted) await SETMutedSync(false); // 21/10/2021 Unmute
|
|
657
|
+
|
|
620
658
|
if (node.oAdditionalSonosPlayers.length > 0) {
|
|
621
659
|
// 05/07/2021 set the volume of additional coordinatores
|
|
622
660
|
for (let index = 0; index < node.oAdditionalSonosPlayers.length; index++) {
|
|
623
661
|
const element = node.oAdditionalSonosPlayers[index];
|
|
624
662
|
try {
|
|
625
663
|
await element.setVolume(volTemp);
|
|
664
|
+
if (node.unmuteIfMuted) await element.setMuted(false); // 21/10/2021 Unmute
|
|
626
665
|
} catch (error) {
|
|
627
666
|
RED.log.error("ttsultimate: Handlequeue: Unable to set the volume on additional player " + error.message);
|
|
628
667
|
}
|
|
@@ -767,6 +806,10 @@ module.exports = function (RED) {
|
|
|
767
806
|
}
|
|
768
807
|
|
|
769
808
|
node.on('input', function (msg) {
|
|
809
|
+
// if (msg.hasOwnProperty("banana")) {
|
|
810
|
+
// node.SonosClient.setMuted(msg.banana);
|
|
811
|
+
// return;
|
|
812
|
+
// }
|
|
770
813
|
|
|
771
814
|
// 09/01/2021 Set the main player and groups IP on request
|
|
772
815
|
// *********************************
|
|
@@ -819,6 +862,12 @@ module.exports = function (RED) {
|
|
|
819
862
|
return;
|
|
820
863
|
}
|
|
821
864
|
|
|
865
|
+
|
|
866
|
+
// 21/10/2021 force unmute
|
|
867
|
+
if (msg.hasOwnProperty("unmute")) {
|
|
868
|
+
node.unmuteIfMuted = msg.unmute;
|
|
869
|
+
}
|
|
870
|
+
|
|
822
871
|
// 05/12/2020 handling Hailing
|
|
823
872
|
var hailingMSG = null;
|
|
824
873
|
if (msg.hasOwnProperty("nohailing") && (msg.nohailing == "1" || msg.nohailing.toLowerCase() == "true")) {
|
|
@@ -998,7 +1047,7 @@ module.exports = function (RED) {
|
|
|
998
1047
|
|
|
999
1048
|
// 12/10/2021 Microsoft Azure TTS Service
|
|
1000
1049
|
async function synthesizeSpeechMicrosoftAzureTTS(ttsService, params) {
|
|
1001
|
-
|
|
1050
|
+
|
|
1002
1051
|
return new Promise(function (resolve, reject) {
|
|
1003
1052
|
try {
|
|
1004
1053
|
|
|
@@ -1009,14 +1058,14 @@ module.exports = function (RED) {
|
|
|
1009
1058
|
params.text,
|
|
1010
1059
|
result => {
|
|
1011
1060
|
ttsService.close();
|
|
1012
|
-
resolve
|
|
1061
|
+
resolve(Buffer.from(result.audioData));
|
|
1013
1062
|
},
|
|
1014
1063
|
error => {
|
|
1015
1064
|
ttsService.close();
|
|
1016
|
-
reject
|
|
1065
|
+
reject(error);
|
|
1017
1066
|
});
|
|
1018
1067
|
} catch (error) {
|
|
1019
|
-
reject
|
|
1068
|
+
reject(error);
|
|
1020
1069
|
}
|
|
1021
1070
|
});
|
|
1022
1071
|
};
|