node-red-contrib-tts-ultimate 2.0.1 → 2.0.2

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,11 @@
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 2.0.2</b> August 2023<br/>
7
+ - NEW: added options for changing Elevenlabs voice settings.<br/>
8
+ - Fixed filename of the cached files, by including all settings. Previously, some settings were not taken in consideration.<br/>
9
+ </p>
5
10
  <p>
6
11
  <b>Version 2.0.1</b> August 2023<br/>
7
12
  - NEW: added Elevenlabs TTS engine https://elevenlabs.io.<br/>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-tts-ultimate",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
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, ElevenLabs.io TTS 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": {
@@ -32,7 +32,17 @@
32
32
  <input type="text" id="node-input-speakingpitch" style="width:60px"> (Between -20.0 and 20.0, default 0)
33
33
  </div>
34
34
  </div>
35
- <div class="form-row">
35
+ <div id="divElevenLabsOptions" hidden>
36
+ <div class="form-row">
37
+ <label for="node-input-elevenlabsStability"><i class="fa fa-volume-up"></i> Stability</label>
38
+ <input type="text" id="node-input-elevenlabsStability" style="width:60px"> (default 0.5)
39
+ </div>
40
+ <div class="form-row">
41
+ <label for="node-input-elevenlabsSimilarity_boost"><i class="fa fa-volume-up"></i> Similarity boost</label>
42
+ <input type="text" id="node-input-elevenlabsSimilarity_boost" style="width:60px"> (Default 0.5)
43
+ </div>
44
+ </div>
45
+ <div class="form-row" id="divSSML">
36
46
  <label></label>
37
47
  <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>
38
48
  </div>
@@ -129,8 +139,9 @@
129
139
  playertype: { value: "sonos", required: false },
130
140
  speakingrate: { value: "1", required: false },
131
141
  speakingpitch: { value: "0", required: false },
132
- unmuteIfMuted: { value: true }
133
-
142
+ unmuteIfMuted: { value: true },
143
+ elevenlabsStability: { value: "0.5", required: false },
144
+ elevenlabsSimilarity_boost: { value: "0.5", required: false },
134
145
  },
135
146
  inputs: 1,
136
147
  outputs: 2,
@@ -163,8 +174,15 @@
163
174
  getVoices();
164
175
  if (oNodeServer.ttsservice === "googletts") {
165
176
  $("#divGoogleTTSAudioConfig").show();
177
+ $("#divElevenLabsOptions").hide();
178
+ } else if (oNodeServer.ttsservice === "elevenlabs") {
179
+ $("#divGoogleTTSAudioConfig").hide();
180
+ $("#divElevenLabsOptions").show();
181
+ $("#divSSML").hide();
166
182
  } else {
167
183
  $("#divGoogleTTSAudioConfig").hide();
184
+ $("#divElevenLabsOptions").hide();
185
+ $("#divSSML").show();
168
186
  }
169
187
  } catch (error) {
170
188
  }
@@ -581,70 +581,109 @@ module.exports = function (RED) {
581
581
  RED.log.info('ttsultimate: Hailing .MP3, skip tts, filename: ' + msg);
582
582
  sFileToBePlayed = path.join(node.userDir, "hailingpermanentfiles", msg);
583
583
  } else {
584
- sFileToBePlayed = getFilename(msg, node.voiceId, node.ssml, "mp3", node.speakingpitch, node.speakingrate);
585
- sFileToBePlayed = path.join(node.userDir, "ttsfiles", sFileToBePlayed);
586
- // Check if cached
587
- if (!fs.existsSync(sFileToBePlayed)) {
588
- try {
589
- // No file in cache. Download from tts service
590
- var data;
591
- if (node.server.ttsservice === "polly") {
592
- node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Downloading from Amazon...' });
593
- var params = {
594
- OutputFormat: "mp3",
595
- SampleRate: '22050',
596
- Text: msg,
597
- TextType: node.ssml ? 'ssml' : 'text'
598
- };
599
- // 02/03/2022 check wether standard or neural engine is POLLY is selected
600
- if (node.voiceId.includes("#engineType:")) {
601
- params.VoiceId = node.voiceId.split("#engineType:")[0];
602
- params.Engine = node.voiceId.split("#engineType:")[1];
603
- } else {
604
- params.VoiceId = node.voiceId;
605
- }
606
-
584
+ try {
585
+ // No file in cache. Download from tts service
586
+ var data;
587
+ if (node.server.ttsservice === "polly") {
588
+ var params = {
589
+ OutputFormat: "mp3",
590
+ SampleRate: '22050',
591
+ Text: msg,
592
+ TextType: node.ssml ? 'ssml' : 'text'
593
+ };
594
+ // 02/03/2022 check wether standard or neural engine is POLLY is selected
595
+ if (node.voiceId.includes("#engineType:")) {
596
+ params.VoiceId = node.voiceId.split("#engineType:")[0];
597
+ params.Engine = node.voiceId.split("#engineType:")[1];
598
+ } else {
599
+ params.VoiceId = node.voiceId;
600
+ }
601
+ // Download or read from cache
602
+ sFileToBePlayed = getFilename(msg, params);
603
+ sFileToBePlayed = path.join(node.userDir, "ttsfiles", sFileToBePlayed);
604
+ if (!fs.existsSync(sFileToBePlayed)) {
605
+ node.setNodeStatus({ fill: 'blue', shape: 'ring', text: 'Download using' + node.server.ttsservice });
607
606
  data = await synthesizeSpeechPolly([node.server.polly, params]);
608
- } else if (node.server.ttsservice === "googletts") {
609
- node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Downloading from Google TTS...' });
610
- // VoiceId is: name + "#" + languageCode + "#" + ssmlGender
611
- // speakingRate tra 0.25 e 4.0
612
- // pitch tra -20.0 e 20.0
613
- const params = {
614
- voice: { name: node.voiceId.split("#")[0], languageCode: node.voiceId.split("#")[1], ssmlGender: node.voiceId.split("#")[2] },
615
- audioConfig: { audioEncoding: "MP3", speakingRate: parseFloat(node.speakingrate), pitch: parseFloat(node.speakingpitch), },
616
- };
617
- params.input = node.ssml === false ? { text: msg } : { ssml: msg };
607
+ } else {
608
+ node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Reading offline from cache' });
609
+ }
610
+ } else if (node.server.ttsservice === "googletts") {
611
+ // VoiceId is: name + "#" + languageCode + "#" + ssmlGender
612
+ // speakingRate tra 0.25 e 4.0
613
+ // pitch tra -20.0 e 20.0
614
+ const params = {
615
+ voice: { name: node.voiceId.split("#")[0], languageCode: node.voiceId.split("#")[1], ssmlGender: node.voiceId.split("#")[2] },
616
+ audioConfig: { audioEncoding: "MP3", speakingRate: parseFloat(node.speakingrate), pitch: parseFloat(node.speakingpitch), },
617
+ };
618
+ params.input = node.ssml === false ? { text: msg } : { ssml: msg };
619
+
620
+ // Download or read from cache
621
+ sFileToBePlayed = getFilename(msg, params);
622
+ sFileToBePlayed = path.join(node.userDir, "ttsfiles", sFileToBePlayed);
623
+ if (!fs.existsSync(sFileToBePlayed)) {
624
+ node.setNodeStatus({ fill: 'blue', shape: 'ring', text: 'Download using' + node.server.ttsservice });
618
625
  data = await synthesizeSpeechGoogleTTS([node.server.googleTTS, params]);
619
- } else if (node.server.ttsservice === "googletranslate") {
620
- node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Downloading from Google Translate...' });
621
- // VoiceId is: code. SSML is not supported by google translate
622
- if (node.voiceId === "cmn-Hant-TW") node.voiceId = "zh-CN"; // 06/08/2022 fix for a wrong voiceid sent by google translate as voice code
623
- const params = {
624
- text: msg,
625
- voice: node.voiceId,
626
- slow: false // optional
627
- };
626
+ } else {
627
+ node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Reading offline from cache' });
628
+ }
629
+ } else if (node.server.ttsservice === "googletranslate") {
630
+ // VoiceId is: code. SSML is not supported by google translate
631
+ if (node.voiceId === "cmn-Hant-TW") node.voiceId = "zh-CN"; // 06/08/2022 fix for a wrong voiceid sent by google translate as voice code
632
+ const params = {
633
+ text: msg,
634
+ voice: node.voiceId,
635
+ slow: false // optional
636
+ };
637
+
638
+ // Download or read from cache
639
+ sFileToBePlayed = getFilename(msg, params);
640
+ sFileToBePlayed = path.join(node.userDir, "ttsfiles", sFileToBePlayed);
641
+ if (!fs.existsSync(sFileToBePlayed)) {
642
+ node.setNodeStatus({ fill: 'blue', shape: 'ring', text: 'Download using' + node.server.ttsservice });
628
643
  data = await synthesizeSpeechGoogleTranslate(node.server.googleTranslateTTS, params);
629
- } else if (node.server.ttsservice === "microsoftazuretts") {
630
- node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Downloading from Microsoft Azure TTS...' });
631
- // VoiceId is: code
632
- const params = {
633
- text: msg,
634
- voice: node.voiceId
635
- };
644
+ } else {
645
+ node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Reading offline from cache' });
646
+ }
647
+ } else if (node.server.ttsservice === "microsoftazuretts") {
648
+ // VoiceId is: code
649
+ const params = {
650
+ text: msg,
651
+ voice: node.voiceId
652
+ };
653
+
654
+ // Download or read from cache
655
+ sFileToBePlayed = getFilename(msg, params);
656
+ sFileToBePlayed = path.join(node.userDir, "ttsfiles", sFileToBePlayed);
657
+ if (!fs.existsSync(sFileToBePlayed)) {
658
+ node.setNodeStatus({ fill: 'blue', shape: 'ring', text: 'Download using' + node.server.ttsservice });
636
659
  data = await synthesizeSpeechMicrosoftAzureTTS(node.server.microsoftAzureTTS, params);
637
- } else if (node.server.ttsservice === "elevenlabs") {
638
- node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Downloading from elevenLabs TTS...' });
639
- // VoiceId is: code
640
- const params = {
641
- text: msg,
642
- voice: node.voiceId
643
- };
660
+ } else {
661
+ node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Reading offline from cache' });
662
+ }
663
+ } else if (node.server.ttsservice === "elevenlabs") {
664
+ // VoiceId is: code
665
+ const params = {
666
+ text: msg,
667
+ voice: node.voiceId,
668
+ model_id: "eleven_monolingual_v1",
669
+ voice_settings: {
670
+ stability: config.elevenlabsStability,
671
+ similarity_boost: config.elevenlabsSimilarity_boost
672
+ }
673
+ };
674
+ // Download or read from cache
675
+ sFileToBePlayed = getFilename(msg, params);
676
+ sFileToBePlayed = path.join(node.userDir, "ttsfiles", sFileToBePlayed);
677
+ if (!fs.existsSync(sFileToBePlayed)) {
678
+ node.setNodeStatus({ fill: 'blue', shape: 'ring', text: 'Download using' + node.server.ttsservice });
644
679
  data = await synthesizeSpeechElevenLabs(node.server.elevenlabsTTS, params);
680
+ } else {
681
+ node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Reading offline from cache' });
645
682
  }
683
+ }
646
684
 
647
- // Save the downloaded file into the cache
685
+ // Save the downloaded file into the cache
686
+ if (data !== undefined) {
648
687
  try {
649
688
  fs.writeFileSync(sFileToBePlayed, data);
650
689
  } catch (error) {
@@ -652,15 +691,12 @@ module.exports = function (RED) {
652
691
  node.setNodeStatus({ fill: "red", shape: "ring", text: "Unable to save the file " + sFileToBePlayed + " " + error.message });
653
692
  throw (error);
654
693
  }
655
-
656
- } catch (error) {
657
- RED.log.error("ttsultimate: node id: " + node.id + " Error Downloading TTS: " + error.message + ". THE TTS SERVICE MAY BE DOWN.");
658
- node.setNodeStatus({ fill: 'red', shape: 'ring', text: 'Error Downloading TTS:' + error.message });
659
- sFileToBePlayed = "";
660
694
  }
661
- }
662
- else {
663
- node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Reading offline from cache' });
695
+
696
+ } catch (error) {
697
+ RED.log.error("ttsultimate: node id: " + node.id + " Error Downloading TTS: " + error.message + ". THE TTS SERVICE MAY BE DOWN.");
698
+ node.setNodeStatus({ fill: 'red', shape: 'ring', text: 'Error Downloading TTS:' + error.message });
699
+ sFileToBePlayed = "";
664
700
  }
665
701
  }
666
702
 
@@ -1163,7 +1199,7 @@ module.exports = function (RED) {
1163
1199
  }
1164
1200
  return new Promise((resolve, reject) => {
1165
1201
  // "model_id": "eleven_multilingual_v1",
1166
- ttsService.textToSpeechStream(node.server.credentials.elevenlabsKey, params.voice, params.text, null,null,"eleven_multilingual_v1").then((res) => {
1202
+ ttsService.textToSpeechStream(node.server.credentials.elevenlabsKey, params.voice, params.text, null, null, "eleven_multilingual_v1").then((res) => {
1167
1203
  try {
1168
1204
  if (res !== undefined) {
1169
1205
  resolve(stream2buffer(res));
@@ -1178,11 +1214,11 @@ module.exports = function (RED) {
1178
1214
  }
1179
1215
 
1180
1216
  // 04/01/2021 hashing filename to avoid issues with long filenames.
1181
- function getFilename(_text, _sVoice, _isSSML, _extension, _speakingpitch, _speakingrate) {
1182
- let sTextToBeHashed = _text.concat(_sVoice, _isSSML, _speakingpitch, _speakingrate);
1217
+ function getFilename(_text, _params) {
1218
+ let sTextToBeHashed = _text.concat(JSON.stringify(_params));
1183
1219
  const hashSum = crypto.createHash('md5');
1184
1220
  hashSum.update(sTextToBeHashed);
1185
- return hashSum.digest('hex') + "." + _extension;
1221
+ return hashSum.digest('hex') + ".mp3";
1186
1222
  }
1187
1223
 
1188
1224
  function notifyError(msg, err) {