node-red-contrib-tts-ultimate 1.0.29 → 1.0.33
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 +28 -11
- package/package.json +5 -4
- package/ttsultimate/ttsultimate-config.html +25 -4
- package/ttsultimate/ttsultimate-config.js +100 -7
- package/ttsultimate/ttsultimate.html +20 -2
- package/ttsultimate/ttsultimate.js +51 -12
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.paypal.me/techtoday)
|
|
4
4
|
|
|
5
|
+
<p>
|
|
6
|
+
<b>Version 1.0.33</b> October 2021<br/>
|
|
7
|
+
- NEW VOICE ENGINE: Microsoft Azure TTS.<br/>
|
|
8
|
+
<p>
|
|
9
|
+
<p>
|
|
10
|
+
<b>Version 1.0.32</b> September 2021<br/>
|
|
11
|
+
- 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/>
|
|
12
|
+
<p>
|
|
13
|
+
<p>
|
|
14
|
+
<b>Version 1.0.31</b> September 2021<br/>
|
|
15
|
+
- NEW: you can now choose voice PITCH and RATE. Avaiable only with Google TTS engine with credentials.<br/>
|
|
16
|
+
<p>
|
|
5
17
|
<p>
|
|
6
18
|
<b>Version 1.0.29</b> September 2021<br/>
|
|
7
19
|
- NEW: you can now choose not to use Sonos as player. In this case, the node will output an array of mp3, ready to be played by third parties nodes.<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,15 +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) or
|
|
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/>
|
|
75
|
+
For Google TTS Engine, you can choose pitch and speed rate of the voice.
|
|
73
76
|
<br/>
|
|
74
77
|
<br/>
|
|
75
78
|
|
|
76
|
-
* **TTS Service using Google (without credentials)**<br/>
|
|
77
|
-
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.
|
|
78
|
-
|
|
79
|
-
<br/>
|
|
80
|
-
|
|
81
79
|
* **TTS Service using Amazon AWS (Polly)**<br/>
|
|
82
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
|
|
83
81
|
|
|
@@ -88,8 +86,13 @@ You can choose between Google (without credentials), Amazon AWS (Polly) or Googl
|
|
|
88
86
|
AWS access Secret key.
|
|
89
87
|
<br/>
|
|
90
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/>
|
|
91
93
|
|
|
92
|
-
* **TTS Service using Google TTS**<br
|
|
94
|
+
* **TTS Service using Google TTS**<br/>
|
|
95
|
+
For Google TTS Engine, you can choose pitch and speed rate of the voice.<br/>
|
|
93
96
|
**Google credentials file path**<br/>
|
|
94
97
|
Here you must select your credential file, previously downloaded from Google, [with these steps](https://www.npmjs.com/package/@google-cloud/text-to-speech):
|
|
95
98
|
> [Select or create a Cloud Platform project](https://console.cloud.google.com/project)<br/>
|
|
@@ -99,6 +102,14 @@ You can choose between Google (without credentials), Amazon AWS (Polly) or Googl
|
|
|
99
102
|
|
|
100
103
|
<br/>
|
|
101
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
|
+
After your registration here https://portal.azure.com, you need to create a Voice Service, then click to Keys and Endpoint and copy/paste the KEY and your Location (for example westus).<br/>
|
|
108
|
+
Then paste both into the TTS-Ultimate engine configuration window and restart node-red.
|
|
109
|
+
|
|
110
|
+
<br/>
|
|
111
|
+
|
|
112
|
+
|
|
102
113
|
**Node-Red IP**<br/>
|
|
103
114
|
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**.
|
|
104
115
|
|
|
@@ -123,6 +134,12 @@ Select the TTS SERVICE ENGINE NODE, as stated above.
|
|
|
123
134
|
**Voice**<br/>
|
|
124
135
|
Select your preferred voice. If you use Amazon, Polly voices will be displayed. If you use Google, google voices will be displayed. Google service without authentication, has a limited set of voices.
|
|
125
136
|
|
|
137
|
+
**Rate**<br/>
|
|
138
|
+
Only avaiable if you choose Google TTS Engine (with credentials). Specifies the speech speed (Between 0.25 and 4.0, default 1).
|
|
139
|
+
|
|
140
|
+
**Pitch**<br/>
|
|
141
|
+
Only avaiable if you choose Google TTS Engine (with credentials). Specifies the speech pitch (Between -20.0 and 20.0, default 0).
|
|
142
|
+
|
|
126
143
|
**Hailing**<br/>
|
|
127
144
|
Before the first TTS message of the message queues, Sonos will play an "hailing" sound. You can select the hailing or totally disable it.
|
|
128
145
|
|
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.33",
|
|
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"
|
|
@@ -45,8 +45,9 @@
|
|
|
45
45
|
"formidable": "1.2.2",
|
|
46
46
|
"os": ">=0.1.1",
|
|
47
47
|
"path": ">=0.12.7",
|
|
48
|
-
"@google-cloud/text-to-speech": "3.1
|
|
49
|
-
"google-translate-tts": ">=0.2.1"
|
|
48
|
+
"@google-cloud/text-to-speech": "3.3.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:180px" for="node-config-input-mssubscriptionKey"><i class="fa fa-user"></i> Azure subscription key</label>
|
|
196
|
+
<input style="width:200px" type="text" id="node-config-input-mssubscriptionKey">
|
|
197
|
+
</div>
|
|
198
|
+
<div class="form-row">
|
|
199
|
+
<label style="width:180px" for="node-config-input-mslocation"><i class="fa fa-user"></i> Azure location (ex:westeurope)</label>
|
|
200
|
+
<input style="width:200px" 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,7 @@ 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";
|
|
25
26
|
|
|
26
27
|
// 03/06/2019 you can select the temp dir
|
|
27
28
|
//#region "SETUP PATHS"
|
|
@@ -76,26 +77,114 @@ module.exports = function (RED) {
|
|
|
76
77
|
//#endregion
|
|
77
78
|
|
|
78
79
|
|
|
79
|
-
// 23/12/2020 Set environment path of googleTTS
|
|
80
80
|
//#region "INSTANTIATE SERVICE ENGINES"
|
|
81
|
+
// POLLY
|
|
81
82
|
var params = {
|
|
82
83
|
accessKeyId: node.credentials.accessKey,
|
|
83
84
|
secretAccessKey: node.credentials.secretKey,
|
|
84
85
|
apiVersion: '2016-06-10'
|
|
85
86
|
};
|
|
86
|
-
|
|
87
|
+
try {
|
|
88
|
+
node.polly = new AWS.Polly(params);
|
|
89
|
+
RED.log.info("ttsultimate.config: Polly service enabled.")
|
|
90
|
+
} catch (error) {
|
|
91
|
+
RED.log.warn("ttsultimate.config: Polly service disabled. " + error.message)
|
|
92
|
+
}
|
|
87
93
|
|
|
94
|
+
// Google TTS with authentication
|
|
88
95
|
if (node.ttsservice === "googletts") {
|
|
89
96
|
try {
|
|
90
|
-
// Set
|
|
97
|
+
// 23/12/2020 Set environment path of googleTTS
|
|
91
98
|
RED.log.info("ttsultimate-config: Google credentials are stored in the file " + path.join(node.userDir, "ttsultimategooglecredentials", "googlecredentials.json"));
|
|
92
99
|
process.env.GOOGLE_APPLICATION_CREDENTIALS = path.join(node.userDir, "ttsultimategooglecredentials", "googlecredentials.json");
|
|
93
100
|
} catch (error) {
|
|
101
|
+
RED.log.warn("ttsultimate.config: Google Translate free service error: " + error.message)
|
|
94
102
|
}
|
|
95
103
|
|
|
96
104
|
}
|
|
97
|
-
|
|
98
|
-
|
|
105
|
+
try {
|
|
106
|
+
node.googleTTS = new GoogleTTS.TextToSpeechClient();
|
|
107
|
+
RED.log.info("ttsultimate.config: Google Translate free service enabled. ")
|
|
108
|
+
} catch (error) {
|
|
109
|
+
RED.log.warn("ttsultimate.config: Google Translate free service disabled. " + error.message)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
// Google Translate without authentication
|
|
114
|
+
try {
|
|
115
|
+
node.googleTranslateTTS = GoogleTranslate;
|
|
116
|
+
} catch (error) {
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
// 12/10/2021 Microsoft Azure TTS SpeechConfig.fromSubscription(subscriptionKey, serviceRegion)
|
|
121
|
+
// #########################################
|
|
122
|
+
node.setMicrosoftAzureVoice = function (_voiceName) {
|
|
123
|
+
console.log ("ILSIGNOREBUONO",_voiceName)
|
|
124
|
+
let speechConfig = microsoftAzureTTS.SpeechConfig.fromSubscription(node.credentials.mssubscriptionKey, node.credentials.mslocation);
|
|
125
|
+
speechConfig.speechSynthesisVoiceName = _voiceName;
|
|
126
|
+
speechConfig.speechSynthesisOutputFormat = microsoftAzureTTS.SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3;
|
|
127
|
+
node.microsoftAzureTTS = new microsoftAzureTTS.SpeechSynthesizer(speechConfig);
|
|
128
|
+
return node.microsoftAzureTTS;
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
let speechConfig = microsoftAzureTTS.SpeechConfig.fromSubscription(node.credentials.mssubscriptionKey, node.credentials.mslocation);
|
|
132
|
+
speechConfig.speechSynthesisLanguage = "it-IT";
|
|
133
|
+
speechConfig.speechSynthesisVoiceName = "it-IT-IsabellaNeural";
|
|
134
|
+
speechConfig.speechSynthesisOutputFormat = microsoftAzureTTS.SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3;
|
|
135
|
+
node.microsoftAzureTTS = new microsoftAzureTTS.SpeechSynthesizer(speechConfig);
|
|
136
|
+
node.microsoftAzureTTSVoiceList = [];
|
|
137
|
+
if (node.ttsservice === "microsoftazuretts") {
|
|
138
|
+
// Get the voices
|
|
139
|
+
async function listVoicesAzure() {
|
|
140
|
+
const httpsAzure = require('https')
|
|
141
|
+
let options = {
|
|
142
|
+
hostname: node.credentials.mslocation + '.tts.speech.microsoft.com',
|
|
143
|
+
port: 443,
|
|
144
|
+
path: '/cognitiveservices/voices/list',
|
|
145
|
+
method: 'GET',
|
|
146
|
+
headers: {
|
|
147
|
+
'Ocp-Apim-Subscription-Key': node.credentials.mssubscriptionKey
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const reqAzure = httpsAzure.request(options, resVoices => {
|
|
151
|
+
var sChunkResponse = "";
|
|
152
|
+
resVoices.on('data', d => {
|
|
153
|
+
sChunkResponse += d.toString();
|
|
154
|
+
})
|
|
155
|
+
resVoices.on('end', () => {
|
|
156
|
+
try {
|
|
157
|
+
let oVoices = JSON.parse(sChunkResponse);
|
|
158
|
+
RED.log.info('ttsultimate-config: Microsoft Azure voices count: ' + oVoices.length);
|
|
159
|
+
for (let index = 0; index < oVoices.length; index++) {
|
|
160
|
+
const element = oVoices[index];
|
|
161
|
+
node.microsoftAzureTTSVoiceList.push({ name: element.ShortName + " (" + element.Gender + ")", id: element.ShortName })
|
|
162
|
+
}
|
|
163
|
+
} catch (error) {
|
|
164
|
+
RED.log.error('ttsultimate-config: listVoices: Error parsing Microsoft Azure TTS voices: ' + error.message);
|
|
165
|
+
node.microsoftAzureTTSVoiceList.push({ name: "Error parsing Microsoft Azure voices: " + error.message, id: "Ivy" });
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
reqAzure.on('error', error => {
|
|
170
|
+
RED.log.error('ttsultimate-config: listVoices: Error contacting Azure for getting the voices list: ' + error.message);
|
|
171
|
+
node.microsoftAzureTTSVoiceList.push({ name: "Error getting Microsoft Azure voices: " + error.message, id: "Ivy" })
|
|
172
|
+
reqAzure.end();
|
|
173
|
+
})
|
|
174
|
+
reqAzure.end();
|
|
175
|
+
};
|
|
176
|
+
RED.log.info("ttsultimate.config: Microsoft AzureTTS service enabled.")
|
|
177
|
+
try {
|
|
178
|
+
listVoicesAzure();
|
|
179
|
+
} catch (error) {
|
|
180
|
+
RED.log.error('ttsultimate-config: listVoices: Error getting Microsoft Azure voices: ' + error.message);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
} catch (error) {
|
|
184
|
+
RED.log.warn("ttsultimate.config: Microsoft AzureTTS service disabled. " + error.message)
|
|
185
|
+
}
|
|
186
|
+
// #########################################
|
|
187
|
+
|
|
99
188
|
//#endregion
|
|
100
189
|
|
|
101
190
|
|
|
@@ -313,6 +402,8 @@ module.exports = function (RED) {
|
|
|
313
402
|
RED.log.error('ttsultimate-config: listVoices: Error getting google Translate voices ' + error.message);
|
|
314
403
|
}
|
|
315
404
|
|
|
405
|
+
} else if (ttsservice === "microsoftazuretts") {
|
|
406
|
+
res.json(node.microsoftAzureTTSVoiceList);
|
|
316
407
|
}
|
|
317
408
|
});
|
|
318
409
|
|
|
@@ -531,7 +622,9 @@ module.exports = function (RED) {
|
|
|
531
622
|
RED.nodes.registerType("ttsultimate-config", TTSConfigNode, {
|
|
532
623
|
credentials: {
|
|
533
624
|
accessKey: { type: "text" },
|
|
534
|
-
secretKey: { type: "password" }
|
|
625
|
+
secretKey: { type: "password" },
|
|
626
|
+
mssubscriptionKey: { type: "text" },
|
|
627
|
+
mslocation: { type: "text" }
|
|
535
628
|
}
|
|
536
629
|
});
|
|
537
630
|
|
|
@@ -26,6 +26,17 @@
|
|
|
26
26
|
<option value='Joanna'>Joanna (en-US)</option>
|
|
27
27
|
</select>
|
|
28
28
|
</div>
|
|
29
|
+
|
|
30
|
+
<div id = "divGoogleTTSAudioConfig">
|
|
31
|
+
<div class="form-row">
|
|
32
|
+
<label for="node-input-speakingrate"><i class="fa fa-volume-up"></i> Rate</label>
|
|
33
|
+
<input type="text" id="node-input-speakingrate" style="width:60px"> (Between 0.25 and 4.0, default 1)
|
|
34
|
+
</div>
|
|
35
|
+
<div class="form-row">
|
|
36
|
+
<label for="node-input-speakingpitch"><i class="fa fa-volume-up"></i> Pitch</label>
|
|
37
|
+
<input type="text" id="node-input-speakingpitch" style="width:60px"> (Between -20.0 and 20.0, default 0)
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
29
40
|
<div class="form-row">
|
|
30
41
|
<label></label>
|
|
31
42
|
<input type="checkbox" id="node-input-ssml" style="margin-left: 0px; vertical-align: top; width: auto !important;"> <label style="width:auto !important;"> Enable SSML</label>
|
|
@@ -115,7 +126,9 @@
|
|
|
115
126
|
property: { value: "payload", required: false, validate: RED.validators.typedInput("propertyType") },
|
|
116
127
|
propertyType: { value: "msg" },
|
|
117
128
|
rules: { value: [] },
|
|
118
|
-
playertype: { value: "sonos", required: false }
|
|
129
|
+
playertype: { value: "sonos", required: false },
|
|
130
|
+
speakingrate: { value: "1", required: false },
|
|
131
|
+
speakingpitch: { value: "0", required: false}
|
|
119
132
|
},
|
|
120
133
|
inputs: 1,
|
|
121
134
|
outputs: 2,
|
|
@@ -146,6 +159,11 @@
|
|
|
146
159
|
try {
|
|
147
160
|
oNodeServer = RED.nodes.node($(this).val());
|
|
148
161
|
getVoices();
|
|
162
|
+
if (oNodeServer.ttsservice === "googletts") {
|
|
163
|
+
$("#divGoogleTTSAudioConfig").show();
|
|
164
|
+
} else {
|
|
165
|
+
$("#divGoogleTTSAudioConfig").hide();
|
|
166
|
+
}
|
|
149
167
|
} catch (error) {
|
|
150
168
|
}
|
|
151
169
|
});
|
|
@@ -170,7 +188,7 @@
|
|
|
170
188
|
} else {
|
|
171
189
|
$("#divSonos").hide();
|
|
172
190
|
}
|
|
173
|
-
});
|
|
191
|
+
});
|
|
174
192
|
// ###########################
|
|
175
193
|
|
|
176
194
|
|
|
@@ -74,6 +74,8 @@ module.exports = function (RED) {
|
|
|
74
74
|
node.currentMSGbeingSpoken = {}; // Stores the current message being spoken
|
|
75
75
|
node.sonosCoordinatorPreviousVolumeSetByApp = 0; // 05/07/2021 stores the main payer volume set by the sonos app
|
|
76
76
|
node.playertype = config.playertype === undefined ? "sonos" : config.playertype; // 20/09/2021 Player type
|
|
77
|
+
node.speakingpitch = config.speakingpitch === undefined ? "0" : config.speakingpitch; // 21/09/2021 AudioConfig speakingpitch
|
|
78
|
+
node.speakingrate = config.speakingrate === undefined ? "1" : config.speakingrate; // 21/09/2021 AudioConfig speakingrate
|
|
77
79
|
|
|
78
80
|
if (typeof node.server !== "undefined" && node.server !== null) {
|
|
79
81
|
node.sNoderedURL = node.server.sNoderedURL || "";
|
|
@@ -374,7 +376,7 @@ module.exports = function (RED) {
|
|
|
374
376
|
node.msg.connectionerror = false;
|
|
375
377
|
}
|
|
376
378
|
|
|
377
|
-
node.setNodeStatus({ fill: '
|
|
379
|
+
node.setNodeStatus({ fill: 'grey', shape: 'ring', text: 'Initialized.' });
|
|
378
380
|
|
|
379
381
|
|
|
380
382
|
|
|
@@ -397,7 +399,8 @@ module.exports = function (RED) {
|
|
|
397
399
|
|
|
398
400
|
if (_oTrack !== null) {
|
|
399
401
|
// Do some checks on the track.
|
|
400
|
-
if (_oTrack.hasOwnProperty("duration") && _oTrack.duration === 0 ||
|
|
402
|
+
if (_oTrack.hasOwnProperty("duration") && _oTrack.duration === 0 ||
|
|
403
|
+
(_oTrack.uri.startsWith("x-sonosprog-http") || _oTrack.uri.startsWith("x-sonosapi-hls-static") || _oTrack.uri.startsWith("x-sonos-spotify"))) {
|
|
401
404
|
// Stream
|
|
402
405
|
_oTrack.trackType = "stream";
|
|
403
406
|
} else if (_oTrack.hasOwnProperty("duration") && isNaN(_oTrack.duration)) {
|
|
@@ -461,10 +464,12 @@ module.exports = function (RED) {
|
|
|
461
464
|
}
|
|
462
465
|
} else if (_oTrack.trackType === "lineinput") {
|
|
463
466
|
// Line in, TV in, etc...
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
467
|
+
if (_oTrack.state === "playing") {
|
|
468
|
+
try {
|
|
469
|
+
await setAVTransportURISync(_oTrack.uri);
|
|
470
|
+
} catch (error) {
|
|
471
|
+
return error;
|
|
472
|
+
}
|
|
468
473
|
}
|
|
469
474
|
}
|
|
470
475
|
}
|
|
@@ -524,7 +529,7 @@ module.exports = function (RED) {
|
|
|
524
529
|
RED.log.info('ttsultimate: Hailing .MP3, skip tts, filename: ' + msg);
|
|
525
530
|
sFileToBePlayed = path.join(node.userDir, "hailingpermanentfiles", msg);
|
|
526
531
|
} else {
|
|
527
|
-
sFileToBePlayed = getFilename(msg, node.voiceId, node.ssml, "mp3");
|
|
532
|
+
sFileToBePlayed = getFilename(msg, node.voiceId, node.ssml, "mp3", node.speakingpitch, node.speakingrate);
|
|
528
533
|
sFileToBePlayed = path.join(node.userDir, "ttsfiles", sFileToBePlayed);
|
|
529
534
|
// Check if cached
|
|
530
535
|
if (!fs.existsSync(sFileToBePlayed)) {
|
|
@@ -544,9 +549,11 @@ module.exports = function (RED) {
|
|
|
544
549
|
} else if (node.server.ttsservice === "googletts") {
|
|
545
550
|
node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Downloading from Google TTS...' });
|
|
546
551
|
// VoiceId is: name + "#" + languageCode + "#" + ssmlGender
|
|
552
|
+
// speakingRate tra 0.25 e 4.0
|
|
553
|
+
// pitch tra -20.0 e 20.0
|
|
547
554
|
const params = {
|
|
548
555
|
voice: { name: node.voiceId.split("#")[0], languageCode: node.voiceId.split("#")[1], ssmlGender: node.voiceId.split("#")[2] },
|
|
549
|
-
audioConfig: { audioEncoding: "MP3" },
|
|
556
|
+
audioConfig: { audioEncoding: "MP3", speakingRate: parseFloat(node.speakingrate), pitch: parseFloat(node.speakingpitch), },
|
|
550
557
|
};
|
|
551
558
|
params.input = node.ssml === "text" ? { text: msg } : { ssml: msg };
|
|
552
559
|
data = await synthesizeSpeechGoogleTTS([node.server.googleTTS, params]);
|
|
@@ -559,6 +566,14 @@ module.exports = function (RED) {
|
|
|
559
566
|
slow: false // optional
|
|
560
567
|
};
|
|
561
568
|
data = await synthesizeSpeechGoogleTranslate(node.server.googleTranslateTTS, params);
|
|
569
|
+
} else if (node.server.ttsservice === "microsoftazuretts") {
|
|
570
|
+
node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Downloading from Microsoft Azure TTS...' });
|
|
571
|
+
// VoiceId is: code
|
|
572
|
+
const params = {
|
|
573
|
+
text: msg,
|
|
574
|
+
voice: node.voiceId
|
|
575
|
+
};
|
|
576
|
+
data = await synthesizeSpeechMicrosoftAzureTTS(node.server.microsoftAzureTTS, params);
|
|
562
577
|
}
|
|
563
578
|
// Save the downloaded file into the cache
|
|
564
579
|
fs.writeFileSync(sFileToBePlayed, data, function (error, result) {
|
|
@@ -707,7 +722,7 @@ module.exports = function (RED) {
|
|
|
707
722
|
|
|
708
723
|
// Resume music
|
|
709
724
|
try {
|
|
710
|
-
if (oCurTrack !== null && oCurTrack.title.indexOf(".mp3") === -1) {
|
|
725
|
+
if (oCurTrack !== null && (!oCurTrack.hasOwnProperty("title") || oCurTrack.title.indexOf(".mp3") === -1)) {
|
|
711
726
|
node.setNodeStatus({ fill: 'grey', shape: 'ring', text: "Resuming original queue..." });
|
|
712
727
|
await resumeMusicQueue(oCurTrack);
|
|
713
728
|
node.setNodeStatus({ fill: 'green', shape: 'ring', text: "Done resuming queue." });
|
|
@@ -981,8 +996,32 @@ module.exports = function (RED) {
|
|
|
981
996
|
}
|
|
982
997
|
};
|
|
983
998
|
|
|
999
|
+
// 12/10/2021 Microsoft Azure TTS Service
|
|
1000
|
+
async function synthesizeSpeechMicrosoftAzureTTS(ttsService, params) {
|
|
1001
|
+
|
|
1002
|
+
return new Promise(function (resolve, reject) {
|
|
1003
|
+
try {
|
|
1004
|
+
|
|
1005
|
+
// Microsoft fa sempre tutto diverso dagli altri, per cui mi tocca reinstanziare l'oggetto
|
|
1006
|
+
ttsService = node.server.setMicrosoftAzureVoice(params.voice);
|
|
1007
|
+
|
|
1008
|
+
ttsService.speakTextAsync(
|
|
1009
|
+
params.text,
|
|
1010
|
+
result => {
|
|
1011
|
+
ttsService.close();
|
|
1012
|
+
resolve (Buffer.from(result.audioData));
|
|
1013
|
+
},
|
|
1014
|
+
error => {
|
|
1015
|
+
ttsService.close();
|
|
1016
|
+
reject (error);
|
|
1017
|
+
});
|
|
1018
|
+
} catch (error) {
|
|
1019
|
+
reject (error);
|
|
1020
|
+
}
|
|
1021
|
+
});
|
|
1022
|
+
};
|
|
984
1023
|
|
|
985
|
-
function getFilename(text, _sVoice, isSSML, extension) {
|
|
1024
|
+
function getFilename(text, _sVoice, isSSML, extension, _speakingpitch, _speakingrate) {
|
|
986
1025
|
// Slug the text.
|
|
987
1026
|
var basename = slug(text);
|
|
988
1027
|
_sVoice = slug(_sVoice);
|
|
@@ -990,14 +1029,14 @@ module.exports = function (RED) {
|
|
|
990
1029
|
var ssml_text = isSSML ? '_ssml' : '';
|
|
991
1030
|
|
|
992
1031
|
// Filename format: "text_voice.mp3"
|
|
993
|
-
var filename = util.format('%s_%s%s.%s', basename, _sVoice, ssml_text, extension);
|
|
1032
|
+
var filename = util.format('%s_%s%s%s%s.%s', basename, _sVoice, _speakingpitch, _speakingrate, ssml_text, extension);
|
|
994
1033
|
|
|
995
1034
|
// If filename is too long, cut it and add hash
|
|
996
1035
|
if (filename.length > 250) {
|
|
997
1036
|
var hash = MD5(basename);
|
|
998
1037
|
|
|
999
1038
|
// Filename format: "text_hash_voice.mp3"
|
|
1000
|
-
var ending = util.format('_%s_%s%s.%s', hash, _sVoice, ssml_text, extension);
|
|
1039
|
+
var ending = util.format('_%s_%s%s%s%s.%s', hash, _sVoice, _speakingpitch, _speakingrate, ssml_text, extension);
|
|
1001
1040
|
var beginning = basename.slice(0, 250 - ending.length);
|
|
1002
1041
|
|
|
1003
1042
|
filename = beginning + ending;
|