node-red-contrib-tts-ultimate 1.0.38 → 1.0.41

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.
@@ -0,0 +1,17 @@
1
+ {
2
+ // Usare IntelliSense per informazioni sui possibili attributi.
3
+ // Al passaggio del mouse vengono visualizzate le descrizioni degli attributi esistenti.
4
+ // Per altre informazioni, visitare: https://go.microsoft.com/fwlink/?linkid=830387
5
+ "version": "0.2.0",
6
+ "configurations": [
7
+ {
8
+ "type": "pwa-node",
9
+ "request": "launch",
10
+ "name": "Launch Program",
11
+ "skipFiles": [
12
+ "<node_internals>/**"
13
+ ],
14
+ "program": "${file}"
15
+ }
16
+ ]
17
+ }
package/CHANGELOG.md CHANGED
@@ -2,84 +2,103 @@
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.41</b> March 2022<br/>
7
+ - NEW: for Polly TTS, you can choose between neural and standard engine.<br/>
8
+ </p>
9
+ <p>
10
+ <b>Version 1.0.40</b> January 2022<br/>
11
+ - NEW: you can now select your own folder to save the TTS cached files.<br/>
12
+ - NEW: getting rid of file lenght issue by hashing the TTS cached files requested from TTS engines. Now the file names will be MD5 HEX hashed.<br/>
13
+ - NEW: now the input messages are passed through to the output pin.<br/>
14
+ - CAUTION: due to the new file management, the node will need to download again the TTS files from your TTS engine. Keep it in mind, because you can be charged by Amazon, Google or Microsoft.<br/>
15
+ </p>
16
+ <p>
17
+ <b>Version 1.0.39</b> January 2022<br/>
18
+ - SSML: fixed an issue prevent using it.<br/>
19
+ - SSML: if SSML is enabled, the text auto split function is disabled, to avoid splitting SSML XML text.<br/>
20
+ - Microsoft Azure: update TTS engine to 1.19.0<br/>
21
+ - Google paid TTS: update TTS engine to 3.4.0<br/>
22
+ </p>
5
23
  <p>
6
24
  <b>Version 1.0.38</b> December 2021<br/>
7
25
  - Removed some unwanted startup logs.<br/>
8
26
  - Fixed ownfile sample code. Thanks to plats98.<br/>
9
- <p>
27
+ </p>
10
28
  <p>
11
29
  <b>Version 1.0.36</b> November 2021<br/>
12
30
  - NEW: Autosplit function: you can now set the maximum lenght of the text-parts, in case your spoken text is too long for the allowed TTS Engine limits.<br/>
13
- <p>
31
+ </p>
14
32
  <p>
15
33
  <b>Version 1.0.35</b> October 2021<br/>
16
34
  - NEW: You can force unmuting all players, then restore their previous state once finished playing.<br/>
17
- <p>
35
+ </p>
18
36
  <p>
19
37
  <b>Version 1.0.34</b> October 2021<br/>
20
38
  - FIX: fixed an issue in retrieving voices if you have more than one TTS engine enabled at the same time.<br/>
21
- <p>
39
+ </p>
22
40
  <p>
23
41
  <b>Version 1.0.33</b> October 2021<br/>
24
42
  - NEW VOICE ENGINE: Microsoft Azure TTS.<br/>
25
- <p>
43
+ </p>
26
44
  <p>
27
45
  <b>Version 1.0.32</b> September 2021<br/>
28
46
  - 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/>
29
- <p>
47
+ </p>
30
48
  <p>
31
49
  <b>Version 1.0.31</b> September 2021<br/>
32
50
  - NEW: you can now choose voice PITCH and RATE. Avaiable only with Google TTS engine with credentials.<br/>
33
- <p>
51
+ </p>
34
52
  <p>
35
53
  <b>Version 1.0.29</b> September 2021<br/>
36
54
  - 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/>
37
- <p>
55
+ </p>
38
56
  <p>
39
57
  <b>Version 1.0.28</b> September 2021<br/>
40
58
  - Fixed queue resuming play even if was in stop (only occurs in some circumstances).<br/>
41
- <p>
59
+ </p>
42
60
  <p>
43
61
  <b>Version 1.0.27</b> September 2021<br/>
44
62
  - Hided some unwanted logs.<br/>
45
- <p>
63
+ </p>
46
64
  <p>
47
65
  <b>Version 1.0.26</b> August 2021<br/>
48
66
  - FIX: after playing tts, if you have no previous queue and you are on old Sonos V1, the last TTS played remains in the queue (it shouldn't).<br/>
49
- <p>
67
+ </p>
50
68
  <p>
51
69
  <b>Version 1.0.25</b> August 2021<br/>
52
70
  - Optimized setting volume speed.<br/>
53
- <p>
71
+ </p>
54
72
  <p>
55
73
  <b>Version 1.0.24</b> August 2021<br/>
56
74
  - Fixed a little issue with sonos beam, switching volumes with a 1-2 seconds delay.<br/>
57
- <p>
75
+ </p>
58
76
  <p>
59
77
  <b>Version 1.0.23</b> August 2021<br/>
60
78
  - Fixed a volume issue. The playing queue was jumping briefly at TTS volume before stopping. That was annoiyng.<br/>
61
79
  - Fixed issues with some async function not really async, so there was glitches in volume settings, seeking and so on, specially with playlist and queues.<br/>
62
80
  - There are known issues with resuming play of sonos streams, they work for a while, then stop.<br/>
63
- <p>
81
+ </p>
64
82
  <p>
65
83
  <b>Version 1.0.22</b> Juli 2021<br/>
66
84
  - The additional players don't obey to msg.volume input node message override (they instead get the volume set by the config window, that is OK, but they must also obey to the override msg). Fixed<br/>
67
- <p>
85
+ </p>
68
86
  <p>
69
87
  <b>Version 1.0.21</b> Juli 2021<br/>
70
88
  - The additional players in the group, now reverts to the previous volume after the speech.<br/>
71
- <p>
89
+ </p>
72
90
  <p>
73
91
  <b>Version 1.0.20</b> May 2021<br/>
74
92
  - Fixed an issue preventing TTS working on Windows machines. Thanks @McFozzy75<br/>
75
- <p>
93
+ </p>
76
94
  <p>
77
95
  <b>Version 1.0.19</b> February 2021<br/>
78
96
  - The previous limit of 200 chars (before the TTS text is automatically split) has been increased to 220.<br/>
79
- <p>
97
+ </p>
80
98
  <p>
81
99
  <b>Version 1.0.18</b> January 2021<br/>
82
100
  - Better handling of payloads long more than 200 chars.<br/>
101
+ </p>
83
102
  <p>
84
103
  <b>Version 1.0.16</b> January 2021<br/>
85
104
  - Currently, the FREE GOOGLE TRANSLATE TTS engine has changed some voice codes. I've been fixed that. You need to do nothing.<br/>
package/README.md CHANGED
@@ -23,7 +23,7 @@
23
23
 
24
24
  ## DESCRIPTION
25
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/>
26
+ Uses Amazon Polly (standard and neural engines), 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
27
  The node can also create a ***TTS file (without the use of any Sonos device)***, to be read by third parties nodes.<br/>
28
28
  This is a major ***upgrade from the previously popular node SonosPollyTTS*** (SonosPollyTTS is not developed anymore).<br/>
29
29
  **Node v.12.0.0 or newer is needed**.
@@ -126,10 +126,11 @@ On each deploy or node-red restart, delete all tts files in the cache. This is u
126
126
  Don't delete the files cached. Useful if you wish to keep the tts files, even in case of internet outages, node-red restart or reboots.
127
127
  <br/>
128
128
 
129
- **Autosplit max length
129
+ **Cache root folder**
130
130
  <br/>
131
- Due to some limitations in text lenght, applied by the online TTS Engines, TTS-Ultimate is capable to split your long text to be spoken, in shorter texts in an intelligent way.<br/>
132
- This field allow you to set the maximum lenght (**in chars**) of each splitted text part. The default is **220** and is OK for most cases. You can change this valut to whatever you want but keep in mind, that your TTS engine can either cut the text or return an error, if the text is too long. As the cached filename is equal to the text being spoken, you could also hit the maximum filename lenght of your filesystem. So keep the default value, unless you absolute need to change it.<br/>
131
+ Set your preferred output folder for the files downloaded by the TTS Engine.<br/>
132
+ This is useful if you wish to save the TTS cached files in a folder accessible, for example, by a third party web servers to serve an AirPlay2 speaker.<br/>
133
+ Leave this field blank for the default.<br/>
133
134
  <br/>
134
135
  <br/>
135
136
 
@@ -139,7 +140,10 @@ This field allow you to set the maximum lenght (**in chars**) of each splitted t
139
140
  Select the TTS SERVICE ENGINE NODE, as stated above.
140
141
 
141
142
  **Voice**<br/>
142
- 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.
143
+ Select your preferred voice. If you use Amazon, Polly voices will be displayed (standard and neural). If you use Google, google voices will be displayed. Google service without authentication, has a limited set of voices.
144
+
145
+ **Enable SSML**<br/>
146
+ Enable the SSML XML notation. Please be aware, not all the TTS engines supports that.
143
147
 
144
148
  **Rate**<br/>
145
149
  Only avaiable if you choose Google TTS Engine (with credentials). Specifies the speech speed (Between 0.25 and 4.0, default 1).
@@ -236,18 +240,21 @@ The node has two output pins. The first pin is to signal play status, the second
236
240
  **OUTPUT PIN 1**<br/>
237
241
  Payload is ***true*** when the node has finished playing, ***false*** if the node is playing<br/>
238
242
  In case you selected ***No player, only output file name.*** in the **Player** property, you'll get a message with an additional property *filesArray*, containing an array of all mp3 files ready to be played with third party nodes.<br/>
243
+ The property ***passThroughMessage*** contains the input msg passed through the output.<br/>
244
+
239
245
  ```js
240
246
  {
241
247
  "payload":true,
248
+ "passThroughMessage" : {original message object}
242
249
  "filesArray":[
243
250
  {
244
- "file":"/Users/supergiovane/.node-red/sonospollyttsstorage/hailingpermanentfiles/Hailing_ComputerCall.mp3"
251
+ "file":"/Users/supergiovane/.node-red/sonospollyttsstorage/hailingpermanentfiles/hail.mp3"
245
252
  },
246
253
  {
247
- "file":"/Users/supergiovane/.node-red/sonospollyttsstorage/ttsfiles/ille mi par esse deo videtur_pause_ ille si fas est superare divos_stop__it-IT.mp3"
254
+ "file":"/Users/supergiovane/.node-red/sonospollyttsstorage/ttsfiles/345938475938457.mp3"
248
255
  },
249
256
  {
250
- "file":"/Users/supergiovane/.node-red/sonospollyttsstorage/ttsfiles/Banana rama_it-IT.mp3"
257
+ "file":"/Users/supergiovane/.node-red/sonospollyttsstorage/ttsfiles/3666HJGH565656.mp3"
251
258
  }
252
259
  ],
253
260
  "_msgid":"8b6b22a45dfd5236"
@@ -352,7 +359,7 @@ This node allow you to upload your custom message and play it via ttsultimate wi
352
359
  **Name**<br/>
353
360
  Node name
354
361
 
355
- **File to be player** <br/>
362
+ **File to be played** <br/>
356
363
  Select a file to be played. You can upload one or multiple files at the same time via the "upload" button.
357
364
 
358
365
  **Priority**<br/>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-tts-ultimate",
3
- "version": "1.0.38",
3
+ "version": "1.0.41",
4
4
  "description": "Transforms the text in speech and hear it using Sonos player or generate an audio file to be used with third parties nodes. Works with voices from Amazon, Google (without credentials as well), Microsoft TTS Azure, or your own voice. You can also only create a TTS file to be read by third party nodes. Update of the popular SonosPollyTTS node.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -22,7 +22,8 @@
22
22
  "IOT",
23
23
  "speech",
24
24
  "ttsultimate",
25
- "sonospollytts"
25
+ "sonospollytts",
26
+ "neural"
26
27
  ],
27
28
  "node-red": {
28
29
  "nodes": {
@@ -39,16 +40,15 @@
39
40
  "homepage": "https://github.com/Supergiovane/node-red-contrib-tts-ultimate",
40
41
  "dependencies": {
41
42
  "aws-sdk": "2.816.0",
42
- "crypto-js": "^3.1.9-1",
43
43
  "fs": "0.0.1-security",
44
44
  "sonos": "1.14.1",
45
45
  "util": ">=0.10.1",
46
46
  "formidable": "1.2.2",
47
47
  "os": ">=0.1.1",
48
48
  "path": ">=0.12.7",
49
- "@google-cloud/text-to-speech": "3.3.1",
49
+ "@google-cloud/text-to-speech": "3.4.0",
50
50
  "google-translate-tts": ">=0.2.1",
51
- "microsoft-cognitiveservices-speech-sdk": ">=1.18.1"
51
+ "microsoft-cognitiveservices-speech-sdk": ">=1.19.0"
52
52
  },
53
53
  "devDependencies": {
54
54
  "eslint": ">=4.18.2",
@@ -18,7 +18,7 @@
18
18
  },
19
19
  purgediratrestart: { value: "leave", required: false },
20
20
  ttsservice: { value: "googletranslate", required: false },
21
- limitTTSFilenameLenght: { value: 220, required: false, validate: RED.validators.number() }
21
+ TTSRootFolderPath: { value: "", required: false }
22
22
 
23
23
  },
24
24
  credentials: {
@@ -33,10 +33,6 @@
33
33
  oneditprepare: function () {
34
34
  var node = this;
35
35
 
36
- // 01/11/2021
37
- //if (node.limitTTSFilenameLenght === undefined) {
38
- // $("#node-config-input-limitTTSFilenameLenght").val(220);
39
- //}
40
36
  // 21/03/2020 Check if the node is the absolute first in the flow. In this case, it has no http server instatiaced
41
37
  $.getJSON('ttsultimateGetEthAddress', (data) => {
42
38
  $("#pleaseDeploy").hide();
@@ -215,10 +211,10 @@
215
211
  </div>
216
212
 
217
213
  <div class="form-row">
218
- <label for="node-config-input-limitTTSFilenameLenght"><i class="fa fa-file"></i> Autosplit max length (chars)</label>
219
- <input type="text" id="node-config-input-limitTTSFilenameLenght">
214
+ <label for="node-config-input-TTSRootFolderPath"><i class="fa fa-folder-o"></i> Cache root folder</label>
215
+ <input type="text" id="node-config-input-TTSRootFolderPath" placeholder="The folder must exist. Leave blank for default">
220
216
  </div>
221
-
217
+
222
218
  </div>
223
219
 
224
220
  </script>
@@ -20,10 +20,9 @@ module.exports = function (RED) {
20
20
  RED.nodes.createNode(this, config);
21
21
  var node = this;
22
22
  node.noderedipaddress = typeof config.noderedipaddress === "undefined" ? "" : config.noderedipaddress;
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)
24
23
  node.whoIsUsingTheServer = ""; // Client node.id using the server, because only a ttsultimate node can use the serve at once.
25
24
  node.ttsservice = config.ttsservice || "googletranslate";
26
- node.limitTTSFilenameLenght = config.limitTTSFilenameLenght === undefined ? 220 : config.limitTTSFilenameLenght;
25
+ node.TTSRootFolderPath = (config.TTSRootFolderPath === undefined || config.TTSRootFolderPath === "") ? path.join(RED.settings.userDir, "sonospollyttsstorage") : path.join(config.TTSRootFolderPath, "sonospollyttsstorage");
27
26
  // node.polly = null;
28
27
  // node.googleTTS = null;
29
28
  // node.googleTranslateTTS = null;
@@ -44,38 +43,38 @@ module.exports = function (RED) {
44
43
  return true;
45
44
  }
46
45
  }
47
- if (!setupDirectory(node.userDir)) {
48
- RED.log.error('ttsultimate-config ' + node.id + ': Unable to set up MAIN directory: ' + node.userDir);
46
+ if (!setupDirectory(node.TTSRootFolderPath)) {
47
+ RED.log.error('ttsultimate-config ' + node.id + ': Unable to set up MAIN directory: ' + node.TTSRootFolderPath);
49
48
  }
50
- if (!setupDirectory(path.join(node.userDir, "ttsfiles"))) {
51
- RED.log.error('ttsultimate-config ' + node.id + ': Unable to set up cache directory: ' + path.join(node.userDir, "ttsfiles"));
49
+ if (!setupDirectory(path.join(node.TTSRootFolderPath, "ttsfiles"))) {
50
+ RED.log.error('ttsultimate-config ' + node.id + ': Unable to set up cache directory: ' + path.join(node.TTSRootFolderPath, "ttsfiles"));
52
51
  } else {
53
- RED.log.info('ttsultimate-config ' + node.id + ': TTS cache set to ' + path.join(node.userDir, "ttsfiles"));
52
+ RED.log.info('ttsultimate-config ' + node.id + ': TTS cache set to ' + path.join(node.TTSRootFolderPath, "ttsfiles"));
54
53
  }
55
- if (!setupDirectory(path.join(node.userDir, "ttsultimategooglecredentials"))) {
56
- RED.log.error('ttsultimate-config ' + node.id + ': Unable to set google creds directory: ' + path.join(node.userDir, "ttsultimategooglecredentials"));
54
+ if (!setupDirectory(path.join(node.TTSRootFolderPath, "ttsultimategooglecredentials"))) {
55
+ RED.log.error('ttsultimate-config ' + node.id + ': Unable to set google creds directory: ' + path.join(node.TTSRootFolderPath, "ttsultimategooglecredentials"));
57
56
  } else {
58
- RED.log.info('ttsultimate-config ' + node.id + ': 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.TTSRootFolderPath, "ttsultimategooglecredentials"));
59
58
  }
60
- if (!setupDirectory(path.join(node.userDir, "hailingpermanentfiles"))) {
61
- RED.log.error('ttsultimate-config ' + node.id + ': Unable to set up hailing directory: ' + path.join(node.userDir, "hailingpermanentfiles"));
59
+ if (!setupDirectory(path.join(node.TTSRootFolderPath, "hailingpermanentfiles"))) {
60
+ RED.log.error('ttsultimate-config ' + node.id + ': Unable to set up hailing directory: ' + path.join(node.TTSRootFolderPath, "hailingpermanentfiles"));
62
61
  } else {
63
- RED.log.info('ttsultimate-config ' + node.id + ': hailing path set to ' + path.join(node.userDir, "hailingpermanentfiles"));
62
+ RED.log.info('ttsultimate-config ' + node.id + ': hailing path set to ' + path.join(node.TTSRootFolderPath, "hailingpermanentfiles"));
64
63
  // 09/03/2020 Copy defaults to the userDir
65
64
  fs.readdirSync(path.join(__dirname, "hailingpermanentfiles")).forEach(file => {
66
65
  try {
67
- fs.copyFileSync(path.join(__dirname, "hailingpermanentfiles", file), path.join(node.userDir, "hailingpermanentfiles", file));
66
+ fs.copyFileSync(path.join(__dirname, "hailingpermanentfiles", file), path.join(node.TTSRootFolderPath, "hailingpermanentfiles", file));
68
67
  } catch (error) { }
69
68
  });
70
69
  }
71
- if (!setupDirectory(path.join(node.userDir, "ttspermanentfiles"))) {
72
- RED.log.error('ttsultimate-config ' + node.id + ': Unable to set up permanent files directory: ' + path.join(node.userDir, "ttspermanentfiles"));
70
+ if (!setupDirectory(path.join(node.TTSRootFolderPath, "ttspermanentfiles"))) {
71
+ RED.log.error('ttsultimate-config ' + node.id + ': Unable to set up permanent files directory: ' + path.join(node.TTSRootFolderPath, "ttspermanentfiles"));
73
72
  } else {
74
- RED.log.info('ttsultimate-config ' + node.id + ': 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.TTSRootFolderPath, "ttspermanentfiles"));
75
74
  // 09/03/2020 // Copy the samples of permanent files into the userDir
76
75
  fs.readdirSync(path.join(__dirname, "ttspermanentfiles")).forEach(file => {
77
76
  try {
78
- fs.copyFileSync(path.join(__dirname, "ttspermanentfiles", file), path.join(node.userDir, "ttspermanentfiles", file));
77
+ fs.copyFileSync(path.join(__dirname, "ttspermanentfiles", file), path.join(node.TTSRootFolderPath, "ttspermanentfiles", file));
79
78
  } catch (error) { }
80
79
  });
81
80
  }
@@ -105,8 +104,8 @@ module.exports = function (RED) {
105
104
  if (node.ttsservice === "googletts") {
106
105
  try {
107
106
  // 23/12/2020 Set environment path of googleTTS
108
- RED.log.info("ttsultimate-config " + node.id + ": Google credentials are stored in the file " + path.join(node.userDir, "ttsultimategooglecredentials", "googlecredentials.json"));
109
- process.env.GOOGLE_APPLICATION_CREDENTIALS = path.join(node.userDir, "ttsultimategooglecredentials", "googlecredentials.json");
107
+ RED.log.info("ttsultimate-config " + node.id + ": Google credentials are stored in the file " + path.join(node.TTSRootFolderPath, "ttsultimategooglecredentials", "googlecredentials.json"));
108
+ process.env.GOOGLE_APPLICATION_CREDENTIALS = path.join(node.TTSRootFolderPath, "ttsultimategooglecredentials", "googlecredentials.json");
110
109
  try {
111
110
  node.googleTTS = new GoogleTTS.TextToSpeechClient();
112
111
  RED.log.info("ttsultimate-config " + node.id + ": Google TTS service enabled. ")
@@ -180,13 +179,13 @@ module.exports = function (RED) {
180
179
  }
181
180
  } catch (error) {
182
181
  RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error parsing Microsoft Azure TTS voices: ' + error.message);
183
- node.microsoftAzureTTSVoiceList.push({ name: "Error parsing Microsoft Azure voices: " + error.message, id: "Ivy" });
182
+ node.microsoftAzureTTSVoiceList.push({ name: "Error parsing Microsoft Azure voices: " + error.message + " Check cretentials, deploy and restart node-red.", id: "Ivy" });
184
183
  }
185
184
  })
186
185
  })
187
186
  reqAzure.on('error', error => {
188
187
  RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error contacting Azure for getting the voices list: ' + error.message);
189
- node.microsoftAzureTTSVoiceList.push({ name: "Error getting Microsoft Azure voices: " + error.message, id: "Ivy" })
188
+ node.microsoftAzureTTSVoiceList.push({ name: "Error getting Microsoft Azure voices: " + error.message + " Check cretentials, deploy and restart node-red.", id: "Ivy" })
190
189
  reqAzure.end();
191
190
  })
192
191
  reqAzure.end();
@@ -265,7 +264,7 @@ module.exports = function (RED) {
265
264
  var jListOwnFiles = [];
266
265
  var sName = "";
267
266
  try {
268
- fs.readdirSync(path.join(node.userDir, "hailingpermanentfiles")).forEach(file => {
267
+ fs.readdirSync(path.join(node.TTSRootFolderPath, "hailingpermanentfiles")).forEach(file => {
269
268
  if (file.indexOf("Hailing_") > -1) {
270
269
  sName = file.replace("Hailing_", "").replace(".mp3", "");
271
270
  jListOwnFiles.push({ name: sName, filename: file });
@@ -280,7 +279,7 @@ module.exports = function (RED) {
280
279
  RED.httpAdmin.get("/deleteHailingFile", RED.auth.needsPermission('TTSConfigNode.read'), function (req, res) {
281
280
  // Delete the file
282
281
  try {
283
- var newPath = path.join(node.userDir, "hailingpermanentfiles", req.query.FileName);
282
+ var newPath = path.join(node.TTSRootFolderPath, "hailingpermanentfiles", req.query.FileName);
284
283
  fs.unlinkSync(newPath)
285
284
  } catch (error) { }
286
285
  res.json({ status: 220 });
@@ -292,7 +291,7 @@ module.exports = function (RED) {
292
291
  form.parse(req, function (err, fields, files) {
293
292
  try {
294
293
  if (files.customHailing.name.indexOf(".mp3") !== -1) {
295
- var newPath = path.join(node.userDir, "hailingpermanentfiles", "Hailing_" + files.customHailing.name);
294
+ var newPath = path.join(node.TTSRootFolderPath, "hailingpermanentfiles", "Hailing_" + files.customHailing.name);
296
295
  // 30/12/2020 To avoid XDEV issue: oldpath and newpath are not on the same mounted filesystem. (Linux permits a filesystem to be mounted at multiple points,
297
296
  // but rename() does not work across different mount points, even if the same filesystem is mounted on both.)
298
297
  // Instead of renaming it, i must copy the file and then delete the old one.
@@ -325,7 +324,7 @@ module.exports = function (RED) {
325
324
  if (err) { };
326
325
  // Allow only json
327
326
  if (files.googleCreds.name.indexOf(".json") !== -1) {
328
- var newPath = path.join(node.userDir, "ttsultimategooglecredentials", "googlecredentials.json");
327
+ var newPath = path.join(node.TTSRootFolderPath, "ttsultimategooglecredentials", "googlecredentials.json");
329
328
  // Set the environment variable
330
329
  process.env.GOOGLE_APPLICATION_CREDENTIALS = newPath;
331
330
  // 30/12/2020 To avoid XDEV issue: oldpath and newpath are not on the same mounted filesystem. (Linux permits a filesystem to be mounted at multiple points,
@@ -365,12 +364,18 @@ module.exports = function (RED) {
365
364
  node.polly.describeVoices(jfiltroVoci, function (err, data) {
366
365
  if (err) {
367
366
  RED.log.warn('ttsultimate-config ' + node.id + ': Error getting polly voices ' + err);
368
- jListVoices.push({ name: "Error retrieving voices. " + err, id: "Ivy" })
367
+ jListVoices.push({ name: "Error retrieving voices. " + err + " Check cretentials, deploy and restart node-red.", id: "Ivy" })
369
368
  res.json(jListVoices)
370
369
  } else {
371
370
  for (let index = 0; index < data.Voices.length; index++) {
372
371
  const oVoice = data.Voices[index];
373
- jListVoices.push({ name: oVoice.LanguageName + " (" + oVoice.LanguageCode + ") " + oVoice.Name + " - " + oVoice.Gender, id: oVoice.Id })
372
+ if (oVoice.hasOwnProperty("SupportedEngines")) {
373
+ oVoice.SupportedEngines.forEach(voicetype => {
374
+ jListVoices.push({ name: oVoice.LanguageName + " (" + oVoice.LanguageCode + ") " + oVoice.Name + " - " + oVoice.Gender + " - " + voicetype, id: oVoice.Id + "#engineType:" + voicetype })
375
+ });
376
+ } else {
377
+ jListVoices.push({ name: oVoice.LanguageName + " (" + oVoice.LanguageCode + ") " + oVoice.Name + " - " + oVoice.Gender, id: oVoice.Id })
378
+ }
374
379
  }
375
380
  res.json(jListVoices)
376
381
  }
@@ -392,15 +397,15 @@ module.exports = function (RED) {
392
397
  });
393
398
  res.json(jListVoices)
394
399
  } catch (error) {
395
- RED.log.error('ttsultimate-config ' + node.id + ': Error getting google TTS voices ' + error.message);
396
- jListVoices.push({ name: "Error getting Google TTS voices. " + error.message, id: "Ivy" })
400
+ RED.log.error('ttsultimate-config ' + node.id + ': Error getting google TTS voices ' + error.message + " Please deploy and restart node-red.");
401
+ jListVoices.push({ name: "Error getting Google TTS voices. " + error.message + " Check credentials, deploy and restart node-red.", id: "Ivy" })
397
402
  res.json(jListVoices)
398
403
  }
399
404
  };
400
405
  try {
401
406
  listVoices();
402
407
  } catch (error) {
403
- RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error getting google TTS voices ' + error.message);
408
+ RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error getting google TTS voices ' + error.message + " Please deploy and restart node-red.");
404
409
  }
405
410
 
406
411
  } else if (ttsservice === "googletranslate") {
@@ -413,14 +418,14 @@ module.exports = function (RED) {
413
418
  res.json(jListVoices)
414
419
  } catch (error) {
415
420
  RED.log.error('ttsultimate-config ' + node.id + ': Error getting google Translate voices ' + error.message);
416
- jListVoices.push({ name: "Error getting Google Translate voices. " + error.message, id: "Ivy" })
421
+ jListVoices.push({ name: "Error getting Google Translate voices. " + error.message + " Deploy and restart node-red.", id: "Ivy" })
417
422
  res.json(jListVoices)
418
423
  }
419
424
  };
420
425
  try {
421
426
  listVoices();
422
427
  } catch (error) {
423
- RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error getting google Translate voices ' + error.message);
428
+ RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error getting google Translate voices ' + error.message + " Please deploy and restart node-red.");
424
429
  }
425
430
 
426
431
  } else if (ttsservice === "microsoftazuretts") {
@@ -442,7 +447,7 @@ module.exports = function (RED) {
442
447
  // Allow only mp3
443
448
  try {
444
449
  if (files.customTTS.name.indexOf(".mp3") !== -1) {
445
- var newPath = path.join(node.userDir, "ttspermanentfiles", "OwnFile_" + files.customTTS.name);
450
+ var newPath = path.join(node.TTSRootFolderPath, "ttspermanentfiles", "OwnFile_" + files.customTTS.name);
446
451
  // 30/12/2020 To avoid XDEV issue: oldpath and newpath are not on the same mounted filesystem. (Linux permits a filesystem to be mounted at multiple points,
447
452
  // but rename() does not work across different mount points, even if the same filesystem is mounted on both.)
448
453
  // Instead of renaming it, i must copy the file and then delete the old one.
@@ -472,7 +477,7 @@ module.exports = function (RED) {
472
477
  var jListOwnFiles = [];
473
478
  var sName = "";
474
479
  try {
475
- fs.readdirSync(path.join(node.userDir, "ttspermanentfiles")).forEach(file => {
480
+ fs.readdirSync(path.join(node.TTSRootFolderPath, "ttspermanentfiles")).forEach(file => {
476
481
  if (file.indexOf("OwnFile_") > -1) {
477
482
  sName = file.replace("OwnFile_", '').replace(".mp3", '');
478
483
  jListOwnFiles.push({ name: sName, filename: file });
@@ -489,12 +494,12 @@ module.exports = function (RED) {
489
494
  if (req.query.FileName == "DELETEallFiles") {
490
495
  // Delete all OwnFiles_
491
496
  try {
492
- fs.readdir(path.join(node.userDir, "ttspermanentfiles"), (err, files) => {
497
+ fs.readdir(path.join(node.TTSRootFolderPath, "ttspermanentfiles"), (err, files) => {
493
498
  files.forEach(function (file) {
494
499
  if (file.indexOf("OwnFile_") !== -1) {
495
- RED.log.warn("ttsultimate-config " + node.id + ": Deleted file " + path.join(node.userDir, "ttspermanentfiles", file));
500
+ RED.log.warn("ttsultimate-config " + node.id + ": Deleted file " + path.join(node.TTSRootFolderPath, "ttspermanentfiles", file));
496
501
  try {
497
- fs.unlinkSync(path.join(node.userDir, "ttspermanentfiles", file));
502
+ fs.unlinkSync(path.join(node.TTSRootFolderPath, "ttspermanentfiles", file));
498
503
  } catch (error) { }
499
504
  }
500
505
  });
@@ -504,7 +509,7 @@ module.exports = function (RED) {
504
509
  } else {
505
510
  // Delete only one file
506
511
  try {
507
- var newPath = path.join(node.userDir, "ttspermanentfiles", req.query.FileName);
512
+ var newPath = path.join(node.TTSRootFolderPath, "ttspermanentfiles", req.query.FileName);
508
513
  try {
509
514
  fs.unlinkSync(newPath)
510
515
  } catch (error) { }
@@ -533,13 +538,13 @@ module.exports = function (RED) {
533
538
  if (node.purgediratrestart === "purge") {
534
539
  // Delete all files, that are'nt OwnFiles_
535
540
  try {
536
- fs.readdirSync(path.join(node.userDir, "ttsfiles"), (err, files) => {
541
+ fs.readdirSync(path.join(node.TTSRootFolderPath, "ttsfiles"), (err, files) => {
537
542
  try {
538
543
  if (files.length > 0) {
539
544
  files.forEach(function (file) {
540
- RED.log.info("ttsultimate-config " + node.id + ": Deleted TTS file " + path.join(node.userDir, "ttsfiles", file));
545
+ RED.log.info("ttsultimate-config " + node.id + ": Deleted TTS file " + path.join(node.TTSRootFolderPath, "ttsfiles", file));
541
546
  try {
542
- fs.unlinkSync(path.join(node.userDir, "ttsfiles", file));
547
+ fs.unlinkSync(path.join(node.TTSRootFolderPath, "ttsfiles", file));
543
548
  } catch (error) {
544
549
  }
545
550
  });
@@ -39,7 +39,7 @@
39
39
  </div>
40
40
  <div class="form-row">
41
41
  <label></label>
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>
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 (unsupported by Google without authentication)</label>
43
43
  </div>
44
44
  <div class="form-row">
45
45
  <label for="node-input-sonoshailing"><i class="fa fa-bell"></i> Hailing</label>
@@ -216,7 +216,7 @@
216
216
  };
217
217
 
218
218
 
219
- // 26/10/2020 Retrieve all avaiables polly voices
219
+ // 26/10/2020 Retrieve all avaiables voices
220
220
  // #####################################
221
221
  function getVoices() {
222
222
  $('#node-input-voice')
@@ -2,11 +2,10 @@ module.exports = function (RED) {
2
2
  'use strict';
3
3
 
4
4
  var fs = require('fs');
5
- var MD5 = require('crypto-js').MD5;
6
5
  var util = require('util');
7
6
  var path = require('path');
8
7
  const sonos = require('sonos');
9
-
8
+ const crypto = require("crypto");
10
9
 
11
10
  function slug(_text) {
12
11
  var sRet = _text;
@@ -64,7 +63,7 @@ module.exports = function (RED) {
64
63
  node.msg = {}; // 08/05/2019 Node message
65
64
  node.msg.completed = true;
66
65
  node.msg.connectionerror = true;
67
- 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)
66
+ node.userDir = node.server.TTSRootFolderPath === undefined ? path.join(RED.settings.userDir, "sonospollyttsstorage") : node.server.TTSRootFolderPath;
68
67
  node.oAdditionalSonosPlayers = []; // 20/03/2020 Contains other players to be grouped
69
68
  node.rules = config.rules || [{}];
70
69
  node.sNoderedURL = "";
@@ -78,7 +77,8 @@ module.exports = function (RED) {
78
77
  node.speakingrate = config.speakingrate === undefined ? "1" : config.speakingrate; // 21/09/2021 AudioConfig speakingrate
79
78
  node.unmuteIfMuted = config.unmuteIfMuted === undefined ? false : config.unmuteIfMuted; // 21/10/2021 Unmute if previiously muted.
80
79
  node.sonosCoordinatorIsPreviouslyMuted = false;
81
-
80
+ node.passThroughMessage = {};
81
+
82
82
  if (typeof node.server !== "undefined" && node.server !== null) {
83
83
  node.sNoderedURL = node.server.sNoderedURL || "";
84
84
  }
@@ -536,7 +536,7 @@ module.exports = function (RED) {
536
536
  RED.log.error("ttsultimate: Error grouping speakers: " + error.message);
537
537
  }
538
538
 
539
- node.send([{ payload: node.msg.completed }, null]);
539
+ node.send([{ passThroughMessage: node.passThroughMessage, payload: node.msg.completed }, null]);
540
540
 
541
541
  // 24/08/2021 If something was playing, stop the player https://github.com/Supergiovane/node-red-contrib-tts-ultimate/issues/32
542
542
  try {
@@ -578,9 +578,16 @@ module.exports = function (RED) {
578
578
  OutputFormat: "mp3",
579
579
  SampleRate: '22050',
580
580
  Text: msg,
581
- TextType: node.ssml ? 'ssml' : 'text',
582
- VoiceId: node.voiceId
581
+ TextType: node.ssml ? 'ssml' : 'text'
583
582
  };
583
+ // 02/03/2022 check wether standard or neural engine is POLLY is selected
584
+ if (node.voiceId.includes("#engineType:")) {
585
+ params.VoiceId = node.voiceId.split("#engineType:")[0];
586
+ params.Engine = node.voiceId.split("#engineType:")[1];
587
+ } else {
588
+ params.VoiceId = node.voiceId;
589
+ }
590
+
584
591
  data = await synthesizeSpeechPolly([node.server.polly, params]);
585
592
  } else if (node.server.ttsservice === "googletts") {
586
593
  node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Downloading from Google TTS...' });
@@ -591,11 +598,11 @@ module.exports = function (RED) {
591
598
  voice: { name: node.voiceId.split("#")[0], languageCode: node.voiceId.split("#")[1], ssmlGender: node.voiceId.split("#")[2] },
592
599
  audioConfig: { audioEncoding: "MP3", speakingRate: parseFloat(node.speakingrate), pitch: parseFloat(node.speakingpitch), },
593
600
  };
594
- params.input = node.ssml === "text" ? { text: msg } : { ssml: msg };
601
+ params.input = node.ssml === false ? { text: msg } : { ssml: msg };
595
602
  data = await synthesizeSpeechGoogleTTS([node.server.googleTTS, params]);
596
603
  } else if (node.server.ttsservice === "googletranslate") {
597
604
  node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Downloading from Google Translate...' });
598
- // VoiceId is: code
605
+ // VoiceId is: code. SSML is not supported by google translate
599
606
  const params = {
600
607
  text: msg,
601
608
  voice: node.voiceId,
@@ -777,7 +784,7 @@ module.exports = function (RED) {
777
784
  setTimeout(() => {
778
785
  node.msg.completed = true;
779
786
  node.currentMSGbeingSpoken = {};
780
- node.send([{ payload: node.msg.completed }, null]);
787
+ node.send([{ passThroughMessage: node.passThroughMessage, payload: node.msg.completed }, null]);
781
788
  node.bBusyPlayingQueue = false
782
789
  node.server.whoIsUsingTheServer = ""; // Signal to other ttsultimate node, that i'm not using the Sonos device anymore
783
790
  }, 1000)
@@ -790,7 +797,7 @@ module.exports = function (RED) {
790
797
  setTimeout(() => {
791
798
  node.msg.completed = true;
792
799
  node.currentMSGbeingSpoken = {};
793
- node.send([{ payload: node.msg.completed, filesArray: noPlayerFileArray }, null]);
800
+ node.send([{ passThroughMessage: node.passThroughMessage, payload: node.msg.completed, filesArray: noPlayerFileArray }, null]);
794
801
  node.bBusyPlayingQueue = false
795
802
  node.server.whoIsUsingTheServer = ""; // Signal to other ttsultimate node, that i'm not using the Sonos device anymore
796
803
  }, 1000)
@@ -811,6 +818,9 @@ module.exports = function (RED) {
811
818
  // return;
812
819
  // }
813
820
 
821
+ // 05/01/2022 Set the passtrough message o come cazzo si scrive
822
+ node.passThroughMessage = RED.util.cloneMessage(msg);
823
+
814
824
  // 09/01/2021 Set the main player and groups IP on request
815
825
  // *********************************
816
826
  if (msg.hasOwnProperty("setConfig")) {
@@ -941,39 +951,46 @@ module.exports = function (RED) {
941
951
  }
942
952
  // ########################
943
953
 
944
-
945
- // 30/01/2021 split the text if it's too long, otherwies i'll have issues with filename too long.
946
- if (msg.payload.length >= node.server.limitTTSFilenameLenght) {
947
- let sTemp = "";
948
- let aSeps = [".", ",", ":", ";", "!", "?"];
949
- let sPayload = msg.payload.replace(/[\r\n]+/gm, "");
950
- for (let index = 0; index < sPayload.length; index++) {
951
- const element = sPayload.substr(index, 1);
952
- sTemp += element;
953
- if (aSeps.indexOf(element) > -1 && sTemp.length > 20) {
954
- const oMsg = RED.util.cloneMessage(msg);
955
- oMsg.payload = sTemp;
956
- node.tempMSGStorage.push(oMsg);
957
- sTemp = "";
958
- }
959
- if (sTemp.length > node.server.limitTTSFilenameLenght && element === " ") {
960
- // Split using space
961
- const oMsg = RED.util.cloneMessage(msg);
962
- oMsg.payload = sTemp;
963
- node.tempMSGStorage.push(oMsg);
964
- sTemp = "";
965
- }
966
- }
967
- // Remaining
968
- const oMsg = RED.util.cloneMessage(msg);
969
- oMsg.payload = sTemp;
970
- node.tempMSGStorage.push(oMsg);
971
-
972
- } else {
973
- node.tempMSGStorage.push(msg);
974
- }
954
+ // // 03/01/2022 if ssml is enabled, disable the auto split function
955
+ // if (!node.ssml) {
956
+ // // SSML disabled
957
+ // // 30/01/2021 split the text if it's too long, otherwies i'll have issues with filename too long.
958
+ // if (msg.payload.length >= node.server.limitTTSFilenameLenght) {
959
+ // let sTemp = "";
960
+ // let aSeps = [".", ",", ":", ";", "!", "?"];
961
+ // let sPayload = msg.payload.replace(/[\r\n]+/gm, "");
962
+ // for (let index = 0; index < sPayload.length; index++) {
963
+ // const element = sPayload.substr(index, 1);
964
+ // sTemp += element;
965
+ // if (aSeps.indexOf(element) > -1 && sTemp.length > 20) {
966
+ // const oMsg = RED.util.cloneMessage(msg);
967
+ // oMsg.payload = sTemp;
968
+ // node.tempMSGStorage.push(oMsg);
969
+ // sTemp = "";
970
+ // }
971
+ // if (sTemp.length > node.server.limitTTSFilenameLenght && element === " ") {
972
+ // // Split using space
973
+ // const oMsg = RED.util.cloneMessage(msg);
974
+ // oMsg.payload = sTemp;
975
+ // node.tempMSGStorage.push(oMsg);
976
+ // sTemp = "";
977
+ // }
978
+ // }
979
+ // // Remaining
980
+ // const oMsg = RED.util.cloneMessage(msg);
981
+ // oMsg.payload = sTemp;
982
+ // node.tempMSGStorage.push(oMsg);
983
+
984
+ // } else {
985
+ // node.tempMSGStorage.push(msg);
986
+ // }
987
+ // } else {
988
+ // // SSML enabled
989
+ // node.tempMSGStorage.push(msg);
990
+ // }
975
991
 
976
992
  // Starts main queue watching
993
+ node.tempMSGStorage.push(msg);
977
994
  node.waitForQueue();
978
995
 
979
996
  });
@@ -1001,7 +1018,7 @@ module.exports = function (RED) {
1001
1018
  clearTimeout(node.oTimerSonosConnectionCheck);
1002
1019
  if (node.timerbTimeOutPlay !== null) clearTimeout(node.timerbTimeOutPlay);
1003
1020
  node.msg.completed = true;
1004
- node.send([{ payload: node.msg.completed }, null]);
1021
+ node.send([{ passThroughMessage: node.passThroughMessage, payload: node.msg.completed }, null]);
1005
1022
  node.setNodeStatus({ fill: "green", shape: "ring", text: "Shutdown" });
1006
1023
  node.flushQueue();
1007
1024
  done();
@@ -1069,28 +1086,12 @@ module.exports = function (RED) {
1069
1086
  });
1070
1087
  };
1071
1088
 
1072
- function getFilename(text, _sVoice, isSSML, extension, _speakingpitch, _speakingrate) {
1073
- // Slug the text.
1074
- var basename = slug(text);
1075
- _sVoice = slug(_sVoice);
1076
-
1077
- var ssml_text = isSSML ? '_ssml' : '';
1078
-
1079
- // Filename format: "text_voice.mp3"
1080
- var filename = util.format('%s_%s%s%s%s.%s', basename, _sVoice, _speakingpitch, _speakingrate, ssml_text, extension);
1081
-
1082
- // If filename is too long, cut it and add hash
1083
- if (filename.length > 250) {
1084
- var hash = MD5(basename);
1085
-
1086
- // Filename format: "text_hash_voice.mp3"
1087
- var ending = util.format('_%s_%s%s%s%s.%s', hash, _sVoice, _speakingpitch, _speakingrate, ssml_text, extension);
1088
- var beginning = basename.slice(0, 250 - ending.length);
1089
-
1090
- filename = beginning + ending;
1091
- }
1092
-
1093
- return filename;
1089
+ // 04/01/2021 hashing filename to avoid issues with long filenames.
1090
+ function getFilename(_text, _sVoice, _isSSML, _extension, _speakingpitch, _speakingrate) {
1091
+ let sTextToBeHashed = _text.concat(_sVoice, _isSSML, _speakingpitch, _speakingrate);
1092
+ const hashSum = crypto.createHash('md5');
1093
+ hashSum.update(sTextToBeHashed);
1094
+ return hashSum.digest('hex') + "." + _extension;
1094
1095
  }
1095
1096
 
1096
1097
  function notifyError(msg, err) {
@@ -1103,7 +1104,6 @@ module.exports = function (RED) {
1103
1104
  });
1104
1105
  // Set error in message
1105
1106
  msg.error = errorMessage;
1106
-
1107
1107
  }
1108
1108
 
1109
1109