node-red-contrib-tts-ultimate 1.0.31 → 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 +16 -0
- package/README.md +28 -10
- package/package.json +4 -3
- package/ttsultimate/ttsultimate-config.html +25 -4
- package/ttsultimate/ttsultimate-config.js +154 -41
- package/ttsultimate/ttsultimate.html +9 -2
- package/ttsultimate/ttsultimate.js +104 -20
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
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>
|
|
9
|
+
<p>
|
|
10
|
+
<b>Version 1.0.34</b> October 2021<br/>
|
|
11
|
+
- FIX: fixed an issue in retrieving voices if you have more than one TTS engine enabled at the same time.<br/>
|
|
12
|
+
<p>
|
|
13
|
+
<p>
|
|
14
|
+
<b>Version 1.0.33</b> October 2021<br/>
|
|
15
|
+
- NEW VOICE ENGINE: Microsoft Azure TTS.<br/>
|
|
16
|
+
<p>
|
|
17
|
+
<p>
|
|
18
|
+
<b>Version 1.0.32</b> September 2021<br/>
|
|
19
|
+
- Fix few restore issues. Line-in restore fix and only when it was playing. Amazon Music and Spotify considered as stream instead of music queue.<br/>
|
|
20
|
+
<p>
|
|
5
21
|
<p>
|
|
6
22
|
<b>Version 1.0.31</b> September 2021<br/>
|
|
7
23
|
- NEW: you can now choose voice PITCH and RATE. Avaiable only with Google TTS engine with credentials.<br/>
|
package/README.md
CHANGED
|
@@ -22,10 +22,11 @@
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
## DESCRIPTION
|
|
25
|
-
This node transforms a text into a speech audio. You can hear the voice through Sonos.<br/>
|
|
26
|
-
Uses Amazon Polly
|
|
25
|
+
This node transforms a text into a speech audio. You can generate an audio file, or hear the voice through Sonos, bluetooth speakers, web pages, etc.<br/>
|
|
26
|
+
Uses Amazon Polly, Google TTS voices (even without credentials nor registration) and Microsoft TTS Azure voices, and you can 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/>
|
|
27
|
+
The node can also create a ***TTS file (without the use of any Sonos device)***, to be read by third parties nodes.<br/>
|
|
27
28
|
This is a major ***upgrade from the previously popular node SonosPollyTTS*** (SonosPollyTTS is not developed anymore).<br/>
|
|
28
|
-
**Node v.
|
|
29
|
+
**Node v.12.0.0 or newer is needed**.
|
|
29
30
|
|
|
30
31
|
[](https://www.paypal.me/techtoday)
|
|
31
32
|
|
|
@@ -33,7 +34,8 @@ This is a major ***upgrade from the previously popular node SonosPollyTTS*** (So
|
|
|
33
34
|
* See <a href="https://github.com/Supergiovane/node-red-contrib-tts-ultimate/blob/master/CHANGELOG.md">here the changelog</a>
|
|
34
35
|
|
|
35
36
|
## FEATURES
|
|
36
|
-
* **
|
|
37
|
+
* **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.
|
|
38
|
+
* **Amazon Voices, Gooogle Translate Voices, Google TTS Voices and Microsoft TTS Azure voices** are all supported, with all avaiables languages and genders.
|
|
37
39
|
* **Automatic grouping** is supported. You can group all players you want to play your announcements.
|
|
38
40
|
* **Automatic discovery** of your players.
|
|
39
41
|
* **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.
|
|
@@ -69,16 +71,11 @@ PORT USED BY THE NODE ARE 1980 (DEFAULT) AND 1400 (FOR SONOS DISCOVER). <br/>
|
|
|
69
71
|
PLEASE ALLOW MDNS AND UDP AS WELL
|
|
70
72
|
|
|
71
73
|
**TTS Service**<br/>
|
|
72
|
-
You can choose between Google (without credentials), Amazon AWS (Polly)
|
|
74
|
+
You can choose between Google (without credentials), Amazon AWS (Polly), Google TTS (require credentials and registration to google) or Microsoft Azure TTS engines.<br/>
|
|
73
75
|
For Google TTS Engine, you can choose pitch and speed rate of the voice.
|
|
74
76
|
<br/>
|
|
75
77
|
<br/>
|
|
76
78
|
|
|
77
|
-
* **TTS Service using Google (without credentials)**<br/>
|
|
78
|
-
This is the simplest way. Just select the voice and you're done. You don't need any credential and you don't even need to be registered to any google service. The voice list is more limited than other services, but it works without hassles.
|
|
79
|
-
|
|
80
|
-
<br/>
|
|
81
|
-
|
|
82
79
|
* **TTS Service using Amazon AWS (Polly)**<br/>
|
|
83
80
|
> 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
|
|
84
81
|
|
|
@@ -89,6 +86,11 @@ For Google TTS Engine, you can choose pitch and speed rate of the voice.
|
|
|
89
86
|
AWS access Secret key.
|
|
90
87
|
<br/>
|
|
91
88
|
|
|
89
|
+
* **TTS Service using Google (without credentials)**<br/>
|
|
90
|
+
This is the simplest way. Just select the voice and you're done. You don't need any credential and you don't even need to be registered to any google service. The voice list is more limited than other services, but it works without hassles.
|
|
91
|
+
|
|
92
|
+
<br/>
|
|
93
|
+
|
|
92
94
|
* **TTS Service using Google TTS**<br/>
|
|
93
95
|
For Google TTS Engine, you can choose pitch and speed rate of the voice.<br/>
|
|
94
96
|
**Google credentials file path**<br/>
|
|
@@ -100,6 +102,15 @@ For Google TTS Engine, you can choose pitch and speed rate of the voice.
|
|
|
100
102
|
|
|
101
103
|
<br/>
|
|
102
104
|
|
|
105
|
+
* **TTS Service using Microsot Azure TTS**<br/>
|
|
106
|
+
For Microsoft Azure TTS Engine, you need to have a microsoft account and register to the Azure portal.<br/>
|
|
107
|
+
See my **YOUTUBE video**, here! https://youtu.be/asXajNpRWME<br/>
|
|
108
|
+
You need to register here https://portal.azure.com, then ceate a Voice Service (please click here https://portal.azure.com/#create/Microsoft.CognitiveServicesSpeechServices), then click on the left "Keys and Endpoint" menu and copy/paste the KEY and your Location (for example westus).<br/>
|
|
109
|
+
Then paste both into the TTS-Ultimate engine configuration window and restart node-red.<br/>
|
|
110
|
+
|
|
111
|
+
<br/>
|
|
112
|
+
|
|
113
|
+
|
|
103
114
|
**Node-Red IP**<br/>
|
|
104
115
|
set IP of your node-red machine. Sonos will connect to this address in order to play TTS. You can also write any value you want, for example 127.0.0.1 in this field (**don't leave this field blank in any case**), if you don't want to use Sonos as player. Please see below, the section **TTS-ULTIMATE NODE**, property **Player**.
|
|
105
116
|
|
|
@@ -140,6 +151,9 @@ In case you select ***No player, only output file name***, you'll get a message
|
|
|
140
151
|
**Volume** <br/>
|
|
141
152
|
Set the preferred TTS volume, from "0" to "100" (can be overridden by passing <code>msg.volume = "40";</code> to the node)
|
|
142
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
|
+
|
|
143
157
|
**Main Sonos Player** <br/>
|
|
144
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/>
|
|
145
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.
|
|
@@ -153,6 +167,10 @@ Here you can add all additional players that will be grouped toghether to the *M
|
|
|
153
167
|
**msg.volume**<br/>
|
|
154
168
|
Set the volume (values between "0" and "100" with quotes)</br>
|
|
155
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
|
+
|
|
156
174
|
**msg.nohailing**<br/>
|
|
157
175
|
Temporarely doesn't play the Hailing sound prior to the message (values "true" or "1" with quotes)</br>
|
|
158
176
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-tts-ultimate",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Transforms the text in speech and hear it using Sonos player. Works with voices from Amazon, Google (without credentials as well) or your own voice. Update of the popular SonosPollyTTS node.",
|
|
3
|
+
"version": "1.0.35",
|
|
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": {
|
|
7
7
|
"test": "test"
|
|
@@ -46,7 +46,8 @@
|
|
|
46
46
|
"os": ">=0.1.1",
|
|
47
47
|
"path": ">=0.12.7",
|
|
48
48
|
"@google-cloud/text-to-speech": "3.3.1",
|
|
49
|
-
"google-translate-tts": ">=0.2.1"
|
|
49
|
+
"google-translate-tts": ">=0.2.1",
|
|
50
|
+
"microsoft-cognitiveservices-speech-sdk": ">=1.18.1"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
52
53
|
"eslint": ">=4.18.2",
|
|
@@ -22,7 +22,9 @@
|
|
|
22
22
|
},
|
|
23
23
|
credentials: {
|
|
24
24
|
accessKey: { type: "text" },
|
|
25
|
-
secretKey: { type: "password" }
|
|
25
|
+
secretKey: { type: "password" },
|
|
26
|
+
mssubscriptionKey: { type: "text" },
|
|
27
|
+
mslocation: { type: "text" }
|
|
26
28
|
},
|
|
27
29
|
label: function () {
|
|
28
30
|
return this.name || "";
|
|
@@ -58,14 +60,21 @@
|
|
|
58
60
|
// ##########################################################
|
|
59
61
|
$("#node-config-input-ttsservice").change(function (e) {
|
|
60
62
|
if ($("#node-config-input-ttsservice").val() == "polly") {
|
|
61
|
-
$("#pollyForm").show();
|
|
62
63
|
$("#GoogleForm").hide();
|
|
64
|
+
$("#microsoftAzureForm").hide();
|
|
65
|
+
$("#pollyForm").show();
|
|
63
66
|
} else if ($("#node-config-input-ttsservice").val() == "googletts") {
|
|
67
|
+
$("#microsoftAzureForm").hide();
|
|
64
68
|
$("#pollyForm").hide();
|
|
65
69
|
$("#GoogleForm").show();
|
|
66
|
-
}else{
|
|
70
|
+
} else if ($("#node-config-input-ttsservice").val() == "googletranslate") {
|
|
67
71
|
$("#pollyForm").hide();
|
|
68
72
|
$("#GoogleForm").hide();
|
|
73
|
+
$("#microsoftAzureForm").hide();
|
|
74
|
+
} else if ($("#node-config-input-ttsservice").val() == "microsoftazuretts") {
|
|
75
|
+
$("#pollyForm").hide();
|
|
76
|
+
$("#GoogleForm").hide();
|
|
77
|
+
$("#microsoftAzureForm").show();
|
|
69
78
|
}
|
|
70
79
|
});
|
|
71
80
|
// ##########################################################
|
|
@@ -161,7 +170,8 @@
|
|
|
161
170
|
<select id="node-config-input-ttsservice">
|
|
162
171
|
<option value="polly">Amazon Polly</option>
|
|
163
172
|
<option value="googletts">Google TTS</option>
|
|
164
|
-
<option value="googletranslate">Google without authentication</option>
|
|
173
|
+
<option value="googletranslate">Google without authentication</option>
|
|
174
|
+
<option value="microsoftazuretts">Microsoft Azure TTS</option>
|
|
165
175
|
</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>
|
|
166
176
|
</div>
|
|
167
177
|
<div id="pollyForm">
|
|
@@ -180,6 +190,17 @@
|
|
|
180
190
|
<input style="width:180px" id="googleCredentialsPath" type="file">
|
|
181
191
|
</div>
|
|
182
192
|
</div>
|
|
193
|
+
<div id="microsoftAzureForm">
|
|
194
|
+
<div class="form-row">
|
|
195
|
+
<label style="width:35%" for="node-config-input-mssubscriptionKey"><i class="fa fa-user"></i> Azure subscription key</label>
|
|
196
|
+
<input style="width:58%" type="text" id="node-config-input-mssubscriptionKey">
|
|
197
|
+
</div>
|
|
198
|
+
<div class="form-row">
|
|
199
|
+
<label style="width:35%" for="node-config-input-mslocation"><i class="fa fa-user"></i> Azure location (ex:westeurope)</label>
|
|
200
|
+
<input style="width:58%" type="text" id="node-config-input-mslocation">
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
|
|
183
204
|
<div class="form-row">
|
|
184
205
|
<label for="node-config-input-purgediratrestart"><i class="fa fa-folder-o"></i> TTS Cache</label>
|
|
185
206
|
<select id="node-config-input-purgediratrestart">
|
|
@@ -4,6 +4,7 @@ module.exports = function (RED) {
|
|
|
4
4
|
const AWS = require('aws-sdk');
|
|
5
5
|
const GoogleTTS = require('@google-cloud/text-to-speech');
|
|
6
6
|
const GoogleTranslate = require('google-translate-tts'); // TTS without credentials, limited to 200 chars per row.
|
|
7
|
+
const microsoftAzureTTS = require("microsoft-cognitiveservices-speech-sdk"); // 12/10/2021
|
|
7
8
|
var fs = require('fs');
|
|
8
9
|
var path = require("path");
|
|
9
10
|
var formidable = require('formidable');
|
|
@@ -21,7 +22,11 @@ module.exports = function (RED) {
|
|
|
21
22
|
node.noderedipaddress = typeof config.noderedipaddress === "undefined" ? "" : config.noderedipaddress;
|
|
22
23
|
node.userDir = path.join(RED.settings.userDir, "sonospollyttsstorage"); // 09/03/2020 Storage of ttsultimate (otherwise, at each upgrade to a newer version, the node path is wiped out and recreated, loosing all custom files)
|
|
23
24
|
node.whoIsUsingTheServer = ""; // Client node.id using the server, because only a ttsultimate node can use the serve at once.
|
|
24
|
-
node.ttsservice = config.ttsservice || "
|
|
25
|
+
node.ttsservice = config.ttsservice || "googletranslate";
|
|
26
|
+
// node.polly = null;
|
|
27
|
+
// node.googleTTS = null;
|
|
28
|
+
// node.googleTranslateTTS = null;
|
|
29
|
+
// node.microsoftAzureTTS = null;
|
|
25
30
|
|
|
26
31
|
// 03/06/2019 you can select the temp dir
|
|
27
32
|
//#region "SETUP PATHS"
|
|
@@ -39,22 +44,22 @@ module.exports = function (RED) {
|
|
|
39
44
|
}
|
|
40
45
|
}
|
|
41
46
|
if (!setupDirectory(node.userDir)) {
|
|
42
|
-
RED.log.error('ttsultimate-config: Unable to set up MAIN directory: ' + node.userDir);
|
|
47
|
+
RED.log.error('ttsultimate-config ' + node.id + ': Unable to set up MAIN directory: ' + node.userDir);
|
|
43
48
|
}
|
|
44
49
|
if (!setupDirectory(path.join(node.userDir, "ttsfiles"))) {
|
|
45
|
-
RED.log.error('ttsultimate-config: Unable to set up cache directory: ' + path.join(node.userDir, "ttsfiles"));
|
|
50
|
+
RED.log.error('ttsultimate-config ' + node.id + ': Unable to set up cache directory: ' + path.join(node.userDir, "ttsfiles"));
|
|
46
51
|
} else {
|
|
47
|
-
RED.log.info('ttsultimate-config: TTS cache set to ' + path.join(node.userDir, "ttsfiles"));
|
|
52
|
+
RED.log.info('ttsultimate-config ' + node.id + ': TTS cache set to ' + path.join(node.userDir, "ttsfiles"));
|
|
48
53
|
}
|
|
49
54
|
if (!setupDirectory(path.join(node.userDir, "ttsultimategooglecredentials"))) {
|
|
50
|
-
RED.log.error('ttsultimate-config: Unable to set google creds directory: ' + path.join(node.userDir, "ttsultimategooglecredentials"));
|
|
55
|
+
RED.log.error('ttsultimate-config ' + node.id + ': Unable to set google creds directory: ' + path.join(node.userDir, "ttsultimategooglecredentials"));
|
|
51
56
|
} else {
|
|
52
|
-
RED.log.info('ttsultimate-config: google credentials path set to ' + path.join(node.userDir, "ttsultimategooglecredentials"));
|
|
57
|
+
RED.log.info('ttsultimate-config ' + node.id + ': google credentials path set to ' + path.join(node.userDir, "ttsultimategooglecredentials"));
|
|
53
58
|
}
|
|
54
59
|
if (!setupDirectory(path.join(node.userDir, "hailingpermanentfiles"))) {
|
|
55
|
-
RED.log.error('ttsultimate-config: Unable to set up hailing directory: ' + path.join(node.userDir, "hailingpermanentfiles"));
|
|
60
|
+
RED.log.error('ttsultimate-config ' + node.id + ': Unable to set up hailing directory: ' + path.join(node.userDir, "hailingpermanentfiles"));
|
|
56
61
|
} else {
|
|
57
|
-
RED.log.info('ttsultimate-config: hailing path set to ' + path.join(node.userDir, "hailingpermanentfiles"));
|
|
62
|
+
RED.log.info('ttsultimate-config ' + node.id + ': hailing path set to ' + path.join(node.userDir, "hailingpermanentfiles"));
|
|
58
63
|
// 09/03/2020 Copy defaults to the userDir
|
|
59
64
|
fs.readdirSync(path.join(__dirname, "hailingpermanentfiles")).forEach(file => {
|
|
60
65
|
try {
|
|
@@ -63,9 +68,9 @@ module.exports = function (RED) {
|
|
|
63
68
|
});
|
|
64
69
|
}
|
|
65
70
|
if (!setupDirectory(path.join(node.userDir, "ttspermanentfiles"))) {
|
|
66
|
-
RED.log.error('ttsultimate-config: Unable to set up permanent files directory: ' + path.join(node.userDir, "ttspermanentfiles"));
|
|
71
|
+
RED.log.error('ttsultimate-config ' + node.id + ': Unable to set up permanent files directory: ' + path.join(node.userDir, "ttspermanentfiles"));
|
|
67
72
|
} else {
|
|
68
|
-
RED.log.info('ttsultimate-config: permanent files path set to ' + path.join(node.userDir, "ttspermanentfiles"));
|
|
73
|
+
RED.log.info('ttsultimate-config ' + node.id + ': permanent files path set to ' + path.join(node.userDir, "ttspermanentfiles"));
|
|
69
74
|
// 09/03/2020 // Copy the samples of permanent files into the userDir
|
|
70
75
|
fs.readdirSync(path.join(__dirname, "ttspermanentfiles")).forEach(file => {
|
|
71
76
|
try {
|
|
@@ -76,26 +81,130 @@ module.exports = function (RED) {
|
|
|
76
81
|
//#endregion
|
|
77
82
|
|
|
78
83
|
|
|
79
|
-
// 23/12/2020 Set environment path of googleTTS
|
|
80
84
|
//#region "INSTANTIATE SERVICE ENGINES"
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
// POLLY
|
|
86
|
+
if (node.ttsservice === "polly") {
|
|
87
|
+
var params = {
|
|
88
|
+
accessKeyId: node.credentials.accessKey,
|
|
89
|
+
secretAccessKey: node.credentials.secretKey,
|
|
90
|
+
apiVersion: '2016-06-10'
|
|
91
|
+
};
|
|
92
|
+
try {
|
|
93
|
+
node.polly = new AWS.Polly(params);
|
|
94
|
+
RED.log.info("ttsultimate-config " + node.id + ": Polly service enabled.")
|
|
95
|
+
} catch (error) {
|
|
96
|
+
RED.log.warn("ttsultimate-config " + node.id + ": Polly service disabled. " + error.message)
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
RED.log.warn("ttsultimate-config " + node.id + ": Polly service not used.");
|
|
100
|
+
}
|
|
101
|
+
|
|
87
102
|
|
|
103
|
+
// Google TTS with authentication
|
|
88
104
|
if (node.ttsservice === "googletts") {
|
|
89
105
|
try {
|
|
90
|
-
// Set
|
|
91
|
-
RED.log.info("ttsultimate-config: Google credentials are stored in the file " + path.join(node.userDir, "ttsultimategooglecredentials", "googlecredentials.json"));
|
|
106
|
+
// 23/12/2020 Set environment path of googleTTS
|
|
107
|
+
RED.log.info("ttsultimate-config " + node.id + ": Google credentials are stored in the file " + path.join(node.userDir, "ttsultimategooglecredentials", "googlecredentials.json"));
|
|
92
108
|
process.env.GOOGLE_APPLICATION_CREDENTIALS = path.join(node.userDir, "ttsultimategooglecredentials", "googlecredentials.json");
|
|
109
|
+
try {
|
|
110
|
+
node.googleTTS = new GoogleTTS.TextToSpeechClient();
|
|
111
|
+
RED.log.info("ttsultimate-config " + node.id + ": Google TTS service enabled. ")
|
|
112
|
+
} catch (error) {
|
|
113
|
+
RED.log.warn("ttsultimate-config " + node.id + ": Google TTS service disabled. " + error.message)
|
|
114
|
+
}
|
|
93
115
|
} catch (error) {
|
|
116
|
+
RED.log.warn("ttsultimate-config " + node.id + ": Google TTS service error: " + error.message)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
} else {
|
|
120
|
+
RED.log.warn("ttsultimate-config " + node.id + ": Google TTS service not used.");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
// Google Translate without authentication
|
|
126
|
+
if (node.ttsservice === "googletranslate") {
|
|
127
|
+
try {
|
|
128
|
+
node.googleTranslateTTS = GoogleTranslate;
|
|
129
|
+
} catch (error) {
|
|
130
|
+
RED.log.warn("ttsultimate-config " + node.id + ": Google Translate free service not used.");
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
RED.log.warn("ttsultimate-config " + node.id + ": Google Translate free service not used.");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
// 12/10/2021 Microsoft Azure TTS SpeechConfig.fromSubscription(subscriptionKey, serviceRegion)
|
|
138
|
+
if (node.ttsservice === "microsoftazuretts") {
|
|
139
|
+
// #########################################
|
|
140
|
+
node.setMicrosoftAzureVoice = function (_voiceName) {
|
|
141
|
+
let speechConfig = microsoftAzureTTS.SpeechConfig.fromSubscription(node.credentials.mssubscriptionKey, node.credentials.mslocation);
|
|
142
|
+
speechConfig.speechSynthesisVoiceName = _voiceName;
|
|
143
|
+
speechConfig.speechSynthesisOutputFormat = microsoftAzureTTS.SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3;
|
|
144
|
+
node.microsoftAzureTTS = new microsoftAzureTTS.SpeechSynthesizer(speechConfig);
|
|
145
|
+
return node.microsoftAzureTTS;
|
|
94
146
|
}
|
|
147
|
+
try {
|
|
148
|
+
|
|
149
|
+
let speechConfig = microsoftAzureTTS.SpeechConfig.fromSubscription(node.credentials.mssubscriptionKey, node.credentials.mslocation);
|
|
150
|
+
//speechConfig.speechSynthesisLanguage = "it-IT";
|
|
151
|
+
//speechConfig.speechSynthesisVoiceName = "it-IT-IsabellaNeural";
|
|
152
|
+
speechConfig.speechSynthesisOutputFormat = microsoftAzureTTS.SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3;
|
|
153
|
+
node.microsoftAzureTTS = new microsoftAzureTTS.SpeechSynthesizer(speechConfig);
|
|
154
|
+
node.microsoftAzureTTSVoiceList = [];
|
|
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
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const reqAzure = httpsAzure.request(options, resVoices => {
|
|
168
|
+
var sChunkResponse = "";
|
|
169
|
+
resVoices.on('data', d => {
|
|
170
|
+
sChunkResponse += d.toString();
|
|
171
|
+
})
|
|
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
|
+
}
|
|
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" })
|
|
189
|
+
reqAzure.end();
|
|
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);
|
|
198
|
+
}
|
|
95
199
|
|
|
200
|
+
} catch (error) {
|
|
201
|
+
RED.log.warn("ttsultimate-config " + node.id + ": Microsoft AzureTTS service disabled. " + error.message)
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
RED.log.warn("ttsultimate-config " + node.id + ": Microsoft AzureTTS service not used. ");
|
|
96
205
|
}
|
|
97
|
-
|
|
98
|
-
|
|
206
|
+
// #########################################
|
|
207
|
+
|
|
99
208
|
//#endregion
|
|
100
209
|
|
|
101
210
|
|
|
@@ -143,7 +252,7 @@ module.exports = function (RED) {
|
|
|
143
252
|
//return groups[0].CoordinatorDevice().togglePlayback()
|
|
144
253
|
})
|
|
145
254
|
}).catch(e => {
|
|
146
|
-
RED.log.warn('ttsultimate-config: Error in discovery ' + e);
|
|
255
|
+
RED.log.warn('ttsultimate-config ' + node.id + ': Error in discovery ' + e);
|
|
147
256
|
res.json("ERRORDISCOVERY");
|
|
148
257
|
})
|
|
149
258
|
} catch (error) { }
|
|
@@ -238,7 +347,7 @@ module.exports = function (RED) {
|
|
|
238
347
|
});
|
|
239
348
|
|
|
240
349
|
// 26/10/2020 Supergiovane, get the updated voice list.
|
|
241
|
-
RED.httpAdmin.get("/ttsgetvoices", RED.auth.needsPermission('TTSConfigNode.read'), function (req, res) {
|
|
350
|
+
RED.httpAdmin.get("/ttsgetvoices" + encodeURIComponent(node.id), RED.auth.needsPermission('TTSConfigNode.read'), function (req, res) {
|
|
242
351
|
var ttsservice = req.query.ttsservice;// Retrieve the ttsservice engine
|
|
243
352
|
var jListVoices = [];
|
|
244
353
|
|
|
@@ -254,7 +363,7 @@ module.exports = function (RED) {
|
|
|
254
363
|
};
|
|
255
364
|
node.polly.describeVoices(jfiltroVoci, function (err, data) {
|
|
256
365
|
if (err) {
|
|
257
|
-
RED.log.warn('ttsultimate-config: Error getting polly voices ' + err);
|
|
366
|
+
RED.log.warn('ttsultimate-config ' + node.id + ': Error getting polly voices ' + err);
|
|
258
367
|
jListVoices.push({ name: "Error retrieving voices. " + err, id: "Ivy" })
|
|
259
368
|
res.json(jListVoices)
|
|
260
369
|
} else {
|
|
@@ -282,7 +391,7 @@ module.exports = function (RED) {
|
|
|
282
391
|
});
|
|
283
392
|
res.json(jListVoices)
|
|
284
393
|
} catch (error) {
|
|
285
|
-
RED.log.error('ttsultimate-config: Error getting google TTS voices ' + error.message);
|
|
394
|
+
RED.log.error('ttsultimate-config ' + node.id + ': Error getting google TTS voices ' + error.message);
|
|
286
395
|
jListVoices.push({ name: "Error getting Google TTS voices. " + error.message, id: "Ivy" })
|
|
287
396
|
res.json(jListVoices)
|
|
288
397
|
}
|
|
@@ -290,7 +399,7 @@ module.exports = function (RED) {
|
|
|
290
399
|
try {
|
|
291
400
|
listVoices();
|
|
292
401
|
} catch (error) {
|
|
293
|
-
RED.log.error('ttsultimate-config: listVoices: Error getting google TTS voices ' + error.message);
|
|
402
|
+
RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error getting google TTS voices ' + error.message);
|
|
294
403
|
}
|
|
295
404
|
|
|
296
405
|
} else if (ttsservice === "googletranslate") {
|
|
@@ -302,7 +411,7 @@ module.exports = function (RED) {
|
|
|
302
411
|
});
|
|
303
412
|
res.json(jListVoices)
|
|
304
413
|
} catch (error) {
|
|
305
|
-
RED.log.error('ttsultimate-config: Error getting google Translate voices ' + error.message);
|
|
414
|
+
RED.log.error('ttsultimate-config ' + node.id + ': Error getting google Translate voices ' + error.message);
|
|
306
415
|
jListVoices.push({ name: "Error getting Google Translate voices. " + error.message, id: "Ivy" })
|
|
307
416
|
res.json(jListVoices)
|
|
308
417
|
}
|
|
@@ -310,9 +419,11 @@ module.exports = function (RED) {
|
|
|
310
419
|
try {
|
|
311
420
|
listVoices();
|
|
312
421
|
} catch (error) {
|
|
313
|
-
RED.log.error('ttsultimate-config: listVoices: Error getting google Translate voices ' + error.message);
|
|
422
|
+
RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error getting google Translate voices ' + error.message);
|
|
314
423
|
}
|
|
315
424
|
|
|
425
|
+
} else if (ttsservice === "microsoftazuretts") {
|
|
426
|
+
res.json(node.microsoftAzureTTSVoiceList);
|
|
316
427
|
}
|
|
317
428
|
});
|
|
318
429
|
|
|
@@ -380,7 +491,7 @@ module.exports = function (RED) {
|
|
|
380
491
|
fs.readdir(path.join(node.userDir, "ttspermanentfiles"), (err, files) => {
|
|
381
492
|
files.forEach(function (file) {
|
|
382
493
|
if (file.indexOf("OwnFile_") !== -1) {
|
|
383
|
-
RED.log.warn("ttsultimate-config: Deleted file " + path.join(node.userDir, "ttspermanentfiles", file));
|
|
494
|
+
RED.log.warn("ttsultimate-config " + node.id + ": Deleted file " + path.join(node.userDir, "ttspermanentfiles", file));
|
|
384
495
|
try {
|
|
385
496
|
fs.unlinkSync(path.join(node.userDir, "ttspermanentfiles", file));
|
|
386
497
|
} catch (error) { }
|
|
@@ -411,11 +522,11 @@ module.exports = function (RED) {
|
|
|
411
522
|
node.noderedport = typeof config.noderedport === "undefined" ? "1980" : config.noderedport;
|
|
412
523
|
// 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
|
|
413
524
|
if (node.noderedport.trim() == "1880") {
|
|
414
|
-
RED.log.warn("ttsultimate-config: 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");
|
|
525
|
+
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");
|
|
415
526
|
node.noderedport = "1980";
|
|
416
527
|
}
|
|
417
528
|
node.sNoderedURL = "http://" + node.noderedipaddress.trim() + ":" + node.noderedport.trim(); // 11/11/2019 New Endpoint to overcome https problem.
|
|
418
|
-
RED.log.info('ttsultimate-config: Node-Red node.js Endpoint will be created here: ' + node.sNoderedURL + "/tts");
|
|
529
|
+
RED.log.info('ttsultimate-config ' + node.id + ': Node-Red node.js Endpoint will be created here: ' + node.sNoderedURL + "/tts");
|
|
419
530
|
|
|
420
531
|
// 26/02/2020
|
|
421
532
|
if (node.purgediratrestart === "purge") {
|
|
@@ -425,7 +536,7 @@ module.exports = function (RED) {
|
|
|
425
536
|
try {
|
|
426
537
|
if (files.length > 0) {
|
|
427
538
|
files.forEach(function (file) {
|
|
428
|
-
RED.log.info("ttsultimate-config: Deleted TTS file " + path.join(node.userDir, "ttsfiles", file));
|
|
539
|
+
RED.log.info("ttsultimate-config " + node.id + ": Deleted TTS file " + path.join(node.userDir, "ttsfiles", file));
|
|
429
540
|
try {
|
|
430
541
|
fs.unlinkSync(path.join(node.userDir, "ttsfiles", file));
|
|
431
542
|
} catch (error) {
|
|
@@ -461,7 +572,7 @@ module.exports = function (RED) {
|
|
|
461
572
|
if (path.extname(query.f) === ".mp3" && path.dirname(path.dirname(query.f)).endsWith("sonospollyttsstorage")) {
|
|
462
573
|
var readStream = fs.createReadStream(query.f);
|
|
463
574
|
readStream.on("error", function (error) {
|
|
464
|
-
RED.log.error("ttsultimate-config: Playsonos error opening stream : " + query.f + ' : ' + error);
|
|
575
|
+
RED.log.error("ttsultimate-config " + node.id + ": Playsonos error opening stream : " + query.f + ' : ' + error);
|
|
465
576
|
res.end();
|
|
466
577
|
return;
|
|
467
578
|
});
|
|
@@ -474,13 +585,13 @@ module.exports = function (RED) {
|
|
|
474
585
|
// http://localhost:1980/tts?f=/etc/passwd
|
|
475
586
|
|
|
476
587
|
} else {
|
|
477
|
-
RED.log.error("ttsultimate-config: Playsonos RED.httpAdmin file not found: " + query.f);
|
|
588
|
+
RED.log.error("ttsultimate-config " + node.id + ": Playsonos RED.httpAdmin file not found: " + query.f);
|
|
478
589
|
res.write("File not found");
|
|
479
590
|
res.end();
|
|
480
591
|
}
|
|
481
592
|
|
|
482
593
|
} catch (error) {
|
|
483
|
-
RED.log.error("ttsultimate-config: Playsonos RED.httpAdmin error: " + error + " on: " + query.f);
|
|
594
|
+
RED.log.error("ttsultimate-config " + node.id + ": Playsonos RED.httpAdmin error: " + error + " on: " + query.f);
|
|
484
595
|
res.end();
|
|
485
596
|
}
|
|
486
597
|
|
|
@@ -490,23 +601,23 @@ module.exports = function (RED) {
|
|
|
490
601
|
try {
|
|
491
602
|
node.oWebserver = http.createServer(requestHandler);
|
|
492
603
|
node.oWebserver.on('error', function (e) {
|
|
493
|
-
RED.log.error("ttsultimate-config: " + node.ID + " error starting webserver on port " + sWebport + " " + e);
|
|
604
|
+
RED.log.error("ttsultimate-config " + node.id + ": " + node.ID + " error starting webserver on port " + sWebport + " " + e);
|
|
494
605
|
});
|
|
495
606
|
} catch (error) {
|
|
496
607
|
// Already open. Close it and redo.
|
|
497
|
-
RED.log.error("ttsultimate-config: Webserver creation error: " + error);
|
|
608
|
+
RED.log.error("ttsultimate-config " + node.id + ": Webserver creation error: " + error);
|
|
498
609
|
}
|
|
499
610
|
|
|
500
611
|
try {
|
|
501
612
|
node.oWebserver.listen(sWebport, (err) => {
|
|
502
613
|
if (err) {
|
|
503
|
-
RED.log.error("ttsultimate-config: error listening webserver on port " + sWebport + " " + err);
|
|
614
|
+
RED.log.error("ttsultimate-config " + node.id + ": error listening webserver on port " + sWebport + " " + err);
|
|
504
615
|
}
|
|
505
616
|
});
|
|
506
617
|
|
|
507
618
|
} catch (error) {
|
|
508
619
|
// In case oWebserver is null
|
|
509
|
-
RED.log.error("ttsultimate-config: error listening webserver on port " + sWebport + " " + error);
|
|
620
|
+
RED.log.error("ttsultimate-config " + node.id + ": error listening webserver on port " + sWebport + " " + error);
|
|
510
621
|
}
|
|
511
622
|
// #################################
|
|
512
623
|
|
|
@@ -516,7 +627,7 @@ module.exports = function (RED) {
|
|
|
516
627
|
node.on('close', function (done) {
|
|
517
628
|
// 11/11/2019 Close the Webserver
|
|
518
629
|
try {
|
|
519
|
-
node.oWebserver.close(function () { RED.log.info("ttsultimate-config: Webserver UP. Closing down."); });
|
|
630
|
+
node.oWebserver.close(function () { RED.log.info("ttsultimate-config " + node.id + ": Webserver UP. Closing down."); });
|
|
520
631
|
} catch (error) {
|
|
521
632
|
|
|
522
633
|
}
|
|
@@ -531,7 +642,9 @@ module.exports = function (RED) {
|
|
|
531
642
|
RED.nodes.registerType("ttsultimate-config", TTSConfigNode, {
|
|
532
643
|
credentials: {
|
|
533
644
|
accessKey: { type: "text" },
|
|
534
|
-
secretKey: { type: "password" }
|
|
645
|
+
secretKey: { type: "password" },
|
|
646
|
+
mssubscriptionKey: { type: "text" },
|
|
647
|
+
mslocation: { type: "text" }
|
|
535
648
|
}
|
|
536
649
|
});
|
|
537
650
|
|
|
@@ -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,
|
|
@@ -216,7 +223,7 @@
|
|
|
216
223
|
.find('option')
|
|
217
224
|
.remove()
|
|
218
225
|
.end();
|
|
219
|
-
$.getJSON("ttsgetvoices?ttsservice=" + oNodeServer.ttsservice, new Date().getTime(), (data) => {
|
|
226
|
+
$.getJSON("ttsgetvoices" + encodeURIComponent(oNodeServer.id) + "?ttsservice=" + oNodeServer.ttsservice, new Date().getTime(), (data) => {
|
|
220
227
|
var aVoices = data.sort(compareVoices);
|
|
221
228
|
for (let index = 0; index < aVoices.length; index++) {
|
|
222
229
|
const oVoice = aVoices[index];
|
|
@@ -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
|
// ######################################################
|
|
@@ -376,7 +412,7 @@ module.exports = function (RED) {
|
|
|
376
412
|
node.msg.connectionerror = false;
|
|
377
413
|
}
|
|
378
414
|
|
|
379
|
-
node.setNodeStatus({ fill: '
|
|
415
|
+
node.setNodeStatus({ fill: 'grey', shape: 'ring', text: 'Initialized.' });
|
|
380
416
|
|
|
381
417
|
|
|
382
418
|
|
|
@@ -399,7 +435,8 @@ module.exports = function (RED) {
|
|
|
399
435
|
|
|
400
436
|
if (_oTrack !== null) {
|
|
401
437
|
// Do some checks on the track.
|
|
402
|
-
if (_oTrack.hasOwnProperty("duration") && _oTrack.duration === 0 ||
|
|
438
|
+
if (_oTrack.hasOwnProperty("duration") && _oTrack.duration === 0 ||
|
|
439
|
+
(_oTrack.uri.startsWith("x-sonosprog-http") || _oTrack.uri.startsWith("x-sonosapi-hls-static") || _oTrack.uri.startsWith("x-sonos-spotify"))) {
|
|
403
440
|
// Stream
|
|
404
441
|
_oTrack.trackType = "stream";
|
|
405
442
|
} else if (_oTrack.hasOwnProperty("duration") && isNaN(_oTrack.duration)) {
|
|
@@ -463,10 +500,12 @@ module.exports = function (RED) {
|
|
|
463
500
|
}
|
|
464
501
|
} else if (_oTrack.trackType === "lineinput") {
|
|
465
502
|
// Line in, TV in, etc...
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
503
|
+
if (_oTrack.state === "playing") {
|
|
504
|
+
try {
|
|
505
|
+
await setAVTransportURISync(_oTrack.uri);
|
|
506
|
+
} catch (error) {
|
|
507
|
+
return error;
|
|
508
|
+
}
|
|
470
509
|
}
|
|
471
510
|
}
|
|
472
511
|
}
|
|
@@ -563,6 +602,14 @@ module.exports = function (RED) {
|
|
|
563
602
|
slow: false // optional
|
|
564
603
|
};
|
|
565
604
|
data = await synthesizeSpeechGoogleTranslate(node.server.googleTranslateTTS, params);
|
|
605
|
+
} else if (node.server.ttsservice === "microsoftazuretts") {
|
|
606
|
+
node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Downloading from Microsoft Azure TTS...' });
|
|
607
|
+
// VoiceId is: code
|
|
608
|
+
const params = {
|
|
609
|
+
text: msg,
|
|
610
|
+
voice: node.voiceId
|
|
611
|
+
};
|
|
612
|
+
data = await synthesizeSpeechMicrosoftAzureTTS(node.server.microsoftAzureTTS, params);
|
|
566
613
|
}
|
|
567
614
|
// Save the downloaded file into the cache
|
|
568
615
|
fs.writeFileSync(sFileToBePlayed, data, function (error, result) {
|
|
@@ -606,12 +653,15 @@ module.exports = function (RED) {
|
|
|
606
653
|
volTemp = node.sSonosVolume;
|
|
607
654
|
}
|
|
608
655
|
await SETVOLUMESync(volTemp);
|
|
656
|
+
if (node.unmuteIfMuted) await SETMutedSync(false); // 21/10/2021 Unmute
|
|
657
|
+
|
|
609
658
|
if (node.oAdditionalSonosPlayers.length > 0) {
|
|
610
659
|
// 05/07/2021 set the volume of additional coordinatores
|
|
611
660
|
for (let index = 0; index < node.oAdditionalSonosPlayers.length; index++) {
|
|
612
661
|
const element = node.oAdditionalSonosPlayers[index];
|
|
613
662
|
try {
|
|
614
663
|
await element.setVolume(volTemp);
|
|
664
|
+
if (node.unmuteIfMuted) await element.setMuted(false); // 21/10/2021 Unmute
|
|
615
665
|
} catch (error) {
|
|
616
666
|
RED.log.error("ttsultimate: Handlequeue: Unable to set the volume on additional player " + error.message);
|
|
617
667
|
}
|
|
@@ -711,7 +761,7 @@ module.exports = function (RED) {
|
|
|
711
761
|
|
|
712
762
|
// Resume music
|
|
713
763
|
try {
|
|
714
|
-
if (oCurTrack !== null && oCurTrack.title.indexOf(".mp3") === -1) {
|
|
764
|
+
if (oCurTrack !== null && (!oCurTrack.hasOwnProperty("title") || oCurTrack.title.indexOf(".mp3") === -1)) {
|
|
715
765
|
node.setNodeStatus({ fill: 'grey', shape: 'ring', text: "Resuming original queue..." });
|
|
716
766
|
await resumeMusicQueue(oCurTrack);
|
|
717
767
|
node.setNodeStatus({ fill: 'green', shape: 'ring', text: "Done resuming queue." });
|
|
@@ -756,6 +806,10 @@ module.exports = function (RED) {
|
|
|
756
806
|
}
|
|
757
807
|
|
|
758
808
|
node.on('input', function (msg) {
|
|
809
|
+
// if (msg.hasOwnProperty("banana")) {
|
|
810
|
+
// node.SonosClient.setMuted(msg.banana);
|
|
811
|
+
// return;
|
|
812
|
+
// }
|
|
759
813
|
|
|
760
814
|
// 09/01/2021 Set the main player and groups IP on request
|
|
761
815
|
// *********************************
|
|
@@ -808,6 +862,12 @@ module.exports = function (RED) {
|
|
|
808
862
|
return;
|
|
809
863
|
}
|
|
810
864
|
|
|
865
|
+
|
|
866
|
+
// 21/10/2021 force unmute
|
|
867
|
+
if (msg.hasOwnProperty("unmute")) {
|
|
868
|
+
node.unmuteIfMuted = msg.unmute;
|
|
869
|
+
}
|
|
870
|
+
|
|
811
871
|
// 05/12/2020 handling Hailing
|
|
812
872
|
var hailingMSG = null;
|
|
813
873
|
if (msg.hasOwnProperty("nohailing") && (msg.nohailing == "1" || msg.nohailing.toLowerCase() == "true")) {
|
|
@@ -985,6 +1045,30 @@ module.exports = function (RED) {
|
|
|
985
1045
|
}
|
|
986
1046
|
};
|
|
987
1047
|
|
|
1048
|
+
// 12/10/2021 Microsoft Azure TTS Service
|
|
1049
|
+
async function synthesizeSpeechMicrosoftAzureTTS(ttsService, params) {
|
|
1050
|
+
|
|
1051
|
+
return new Promise(function (resolve, reject) {
|
|
1052
|
+
try {
|
|
1053
|
+
|
|
1054
|
+
// Microsoft fa sempre tutto diverso dagli altri, per cui mi tocca reinstanziare l'oggetto
|
|
1055
|
+
ttsService = node.server.setMicrosoftAzureVoice(params.voice);
|
|
1056
|
+
|
|
1057
|
+
ttsService.speakTextAsync(
|
|
1058
|
+
params.text,
|
|
1059
|
+
result => {
|
|
1060
|
+
ttsService.close();
|
|
1061
|
+
resolve(Buffer.from(result.audioData));
|
|
1062
|
+
},
|
|
1063
|
+
error => {
|
|
1064
|
+
ttsService.close();
|
|
1065
|
+
reject(error);
|
|
1066
|
+
});
|
|
1067
|
+
} catch (error) {
|
|
1068
|
+
reject(error);
|
|
1069
|
+
}
|
|
1070
|
+
});
|
|
1071
|
+
};
|
|
988
1072
|
|
|
989
1073
|
function getFilename(text, _sVoice, isSSML, extension, _speakingpitch, _speakingrate) {
|
|
990
1074
|
// Slug the text.
|