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 CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  [![Donate via PayPal](https://img.shields.io/badge/Donate-PayPal-blue.svg?style=flat-square)](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 and Google TTS voices (even without credentials nor registration), 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/>
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.10.0.0 or newer is needed**.
29
+ **Node v.12.0.0 or newer is needed**.
29
30
 
30
31
  [![Donate via PayPal](https://img.shields.io/badge/Donate-PayPal-blue.svg?style=flat-square)](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
- * **Amazon Voices, Gooogle Translate Voices and Google TTS Voices** are all supported, with all avaiables languages and genders.
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 Google TTS engines.
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/><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.29",
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.3",
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>&nbsp&nbsp<b><span style="color:red"><i class="fa fa-question-circle"></i>&nbsp<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 || "polly";
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
- node.polly = new AWS.Polly(params);
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 the environment variable
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
- node.googleTTS = new GoogleTTS.TextToSpeechClient();
98
- node.googleTranslateTTS = GoogleTranslate;
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: 'green', shape: 'ring', text: 'Initialized.' });
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 || _oTrack.uri.startsWith("x-sonosprog-http")) {
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
- try {
465
- await setAVTransportURISync(_oTrack.uri);
466
- } catch (error) {
467
- return error;
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;