node-red-contrib-tts-ultimate 3.0.0 → 3.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,11 +2,16 @@
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
- ## BREAKING CHANGE ! BREAKING CHANGE ! BREAKING CHANGE ! BREAKING CHANGE !
6
5
 
7
6
  <p>
8
- <b>Version 3.0.0</b> April 2025<br/>
7
+ <b>Version 3.0.1</b> October 2025<br/>
8
+ - Elevenlabs Engine: added more option to personalize the voice.<br/>
9
+ </p>
10
+
11
+ <p>
12
+ <b>Version 3.0.0</b> June 2025<br/>
9
13
  - BREAKING CHANGE: Amazon Polly and Microsoft Azure TTS have been removed due to lack of time to update the old and complex API's. Anyone can add these again by forking the project and do a PR. Thank you!. If you still need those TTS, please stay or revert to 2.0.10.<br/>
14
+ - NEW: Added option to avoid resuming music if it was playing before TTS messages.<br/>
10
15
  </p>
11
16
 
12
17
  -----------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-tts-ultimate",
3
- "version": "3.0.0",
3
+ "version": "3.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": {
@@ -49,6 +49,47 @@
49
49
  <label></label>
50
50
  <input type="checkbox" id="node-input-elevenlabsUse_speaker_boost" style="margin-left: 0px; vertical-align: top; width: auto !important;"> <label style="width:auto !important;"> Speaker boost</label>
51
51
  </div>
52
+ <div class="form-row">
53
+ <label for="node-input-elevenlabsModel"><i class="fa fa-cubes"></i> Model</label>
54
+ <select id="node-input-elevenlabsModel" style="width:60%">
55
+ <option value="">Automatic</option>
56
+ <option value="eleven_monolingual_v1">Eleven Monolingual v1</option>
57
+ <option value="eleven_multilingual_v1">Eleven Multilingual v1</option>
58
+ <option value="eleven_multilingual_v2">Eleven Multilingual v2</option>
59
+ <option value="eleven_turbo_v2">Eleven Turbo v2</option>
60
+ <option value="eleven_turbo_v2_5">Eleven Turbo v2.5</option>
61
+ </select>
62
+ </div>
63
+ <div class="form-row">
64
+ <label for="node-input-elevenlabsOptimizeLatency"><i class="fa fa-tachometer"></i> Latency preset</label>
65
+ <select id="node-input-elevenlabsOptimizeLatency" style="width:60%">
66
+ <option value="">Default</option>
67
+ <option value="0">0 - Best quality</option>
68
+ <option value="1">1</option>
69
+ <option value="2">2</option>
70
+ <option value="3">3</option>
71
+ <option value="4">4 - Lowest latency</option>
72
+ </select>
73
+ </div>
74
+ <div class="form-row">
75
+ <label for="node-input-elevenlabsOutputFormat"><i class="fa fa-file-audio-o"></i> Output format</label>
76
+ <select id="node-input-elevenlabsOutputFormat" style="width:60%">
77
+ <option value="">Default (mp3_44100_128)</option>
78
+ <option value="mp3_44100_192">mp3_44100_192</option>
79
+ <option value="mp3_44100_128">mp3_44100_128</option>
80
+ <option value="mp3_44100_64">mp3_44100_64</option>
81
+ <option value="mp3_44100_32">mp3_44100_32</option>
82
+ <option value="mp3_22050_128">mp3_22050_128</option>
83
+ <option value="mp3_22050_64">mp3_22050_64</option>
84
+ <option value="pcm_16000">pcm_16000</option>
85
+ <option value="pcm_22050">pcm_22050</option>
86
+ <option value="ulaw_8000">ulaw_8000</option>
87
+ </select>
88
+ </div>
89
+ <div class="form-row">
90
+ <label for="node-input-elevenlabsSeed"><i class="fa fa-random"></i> Seed</label>
91
+ <input type="text" id="node-input-elevenlabsSeed" style="width:120px" placeholder="Leave blank for random">
92
+ </div>
52
93
  </div>
53
94
  <div class="form-row" id="divSSML">
54
95
  <label></label>
@@ -79,7 +120,10 @@
79
120
  <label for="node-input-unmuteIfMuted"><i class="fa fa-bell-slash-o"></i> Unmute</label>
80
121
  <input type="checkbox" id="node-input-unmuteIfMuted" style="margin-left: 0px; vertical-align: top; width: auto !important;"> <label style="width:auto !important;"> Unmute, then restore previous state after play.</label>
81
122
  </div>
82
-
123
+ <div class="form-row">
124
+ <label for="node-input-doNotResumeMusic"><i class="fa fa-bell-slash-o"></i> Resume</label>
125
+ <input type="checkbox" id="node-input-doNotResumeMusic" style="margin-left: 0px; vertical-align: top; width: auto !important;"> <label style="width:auto !important;"> Do not resume previous music.</label>
126
+ </div>
83
127
  <div class="form-row">
84
128
  <label for="node-input-sonosipaddress"><i class="fa fa-globe"></i> Main Sonos Player</label>
85
129
  <label style="width:200px;" id="node-input-sonosipaddress">Discovering.... wait...</label>
@@ -136,6 +180,12 @@
136
180
  value: "Hailing_Hailing.mp3",
137
181
  required: false,
138
182
  },
183
+ doNotResumeMusic:
184
+ {
185
+ value: true,
186
+ required: false,
187
+ },
188
+
139
189
  config:
140
190
  {
141
191
  type: "ttsultimate-config",
@@ -152,6 +202,10 @@
152
202
  elevenlabsSimilarity_boost: { value: "0.5", required: false },
153
203
  elevenlabsStyle: { value: "0.0", required: false },
154
204
  elevenlabsUse_speaker_boost: { value: true, required: false },
205
+ elevenlabsModel: { value: "", required: false },
206
+ elevenlabsOptimizeLatency: { value: "", required: false },
207
+ elevenlabsOutputFormat: { value: "", required: false },
208
+ elevenlabsSeed: { value: "", required: false },
155
209
  },
156
210
  inputs: 1,
157
211
  outputs: 2,
@@ -175,6 +229,10 @@
175
229
  oneditprepare: function () {
176
230
  var node = this;
177
231
  var oNodeServer = RED.nodes.node($("#node-input-config").val()); // Store the config-node
232
+ if (node.elevenlabsModel !== undefined) $("#node-input-elevenlabsModel").val(node.elevenlabsModel);
233
+ if (node.elevenlabsOptimizeLatency !== undefined) $("#node-input-elevenlabsOptimizeLatency").val(node.elevenlabsOptimizeLatency);
234
+ if (node.elevenlabsOutputFormat !== undefined) $("#node-input-elevenlabsOutputFormat").val(node.elevenlabsOutputFormat);
235
+ if (node.elevenlabsSeed !== undefined) $("#node-input-elevenlabsSeed").val(node.elevenlabsSeed);
178
236
 
179
237
  // 19/02/2020 Used to alert the user if the CSV file has not been loaded and to get the server sooner als deploy
180
238
  // ###########################
@@ -549,6 +607,7 @@
549
607
  | Player | Select the player. If you select not to use a player, the node will output a msg with an array of files, ready to be played by third party nodes. In case you select No player, only output file name, 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. Please see below the OUTPUT MESSAGES FROM THE NODE section. |
550
608
  | Volume | Set the preferred TTS volume, from "0" to "100" (can be overridden by passing msg.volume = "40"; to the node). |
551
609
  | Unmute | Unmute the main and the addotional players, then restore the previous mute state once finished. (Can be overridden by passing msg.unmute = true; to the node). |
610
+ | Resume | If music was playing prior to TTS messages, the node will try to resume it, but can fail in some cases. Enabla this option to avoid resuming music after TTS message. |
552
611
  | Main Sonos Player | Select your Sonos primary player. (It's strongly suggested to set a fixed IP for this player; you can reserve an IP using the DHCP Reservation function of your router/firewall's DHCP Server). It's possibile to group players, so your announcement can be played on all selected players. For this to happen, you need to select your primary coordinator player. All other players will be then controlled by this coordinator. |
553
612
  | Additional Players | Here you can add all additional players that will be grouped toghether to the Main Sonos Player coordinator group. You can add a player using the "ADD" button, below the list. For each additional player, you can adjust their volume, based on the Main Sonos Player volume -+100. |
554
613
 
@@ -602,4 +661,4 @@ The node has two output pins. The first pin is to signal play status, the second
602
661
  [Find it useful?](https://www.paypal.me/techtoday)
603
662
 
604
663
  <br/>
605
- </script>
664
+ </script>
@@ -80,7 +80,7 @@ module.exports = function (RED) {
80
80
  node.sonosCoordinatorIsPreviouslyMuted = false;
81
81
  node.passThroughMessage = {};
82
82
  node.bTimeOutPlay = false;
83
-
83
+ node.doNotResumeMusic = config.doNotResumeMusic === undefined ? false : config.doNotResumeMusic; // 06/2025 Do not resume previous music after TTS, if playing.
84
84
  if (typeof node.server !== "undefined" && node.server !== null) {
85
85
  node.sNoderedURL = node.server.sNoderedURL || "";
86
86
  }
@@ -660,15 +660,18 @@ module.exports = function (RED) {
660
660
  }
661
661
  } else if (node.server.ttsservice === "elevenlabs") {
662
662
  // VoiceId is: code
663
+ const stability = config.elevenlabsStability !== undefined && config.elevenlabsStability !== "" ? Number(config.elevenlabsStability) : undefined;
664
+ const similarity = config.elevenlabsSimilarity_boost !== undefined && config.elevenlabsSimilarity_boost !== "" ? Number(config.elevenlabsSimilarity_boost) : undefined;
665
+ const resolvedModel = config.elevenlabsModel && config.elevenlabsModel !== "" ? config.elevenlabsModel : "eleven_monolingual_v1";
663
666
  const params = {
664
667
  text: msg,
665
668
  voice: node.voiceId,
666
- model_id: "eleven_monolingual_v1",
667
- voice_settings: {
668
- stability: config.elevenlabsStability,
669
- similarity_boost: config.elevenlabsSimilarity_boost
670
- }
669
+ model_id: resolvedModel,
670
+ voice_settings: {}
671
671
  };
672
+ if (stability !== undefined && !Number.isNaN(stability)) params.voice_settings.stability = stability;
673
+ if (similarity !== undefined && !Number.isNaN(similarity)) params.voice_settings.similarity_boost = similarity;
674
+ if (Object.keys(params.voice_settings).length === 0) delete params.voice_settings;
672
675
  // Download or read from cache
673
676
  node.sFileToBePlayed = getFilename(msg, params);
674
677
  node.sFileToBePlayed = path.join(node.userDir, "ttsfiles", node.sFileToBePlayed);
@@ -680,18 +683,29 @@ module.exports = function (RED) {
680
683
  }
681
684
  } else if (node.server.ttsservice === "elevenlabsv2") {
682
685
  // VoiceId is: code
686
+ const stability = config.elevenlabsStability !== undefined && config.elevenlabsStability !== "" ? Number(config.elevenlabsStability) : undefined;
687
+ const similarity = config.elevenlabsSimilarity_boost !== undefined && config.elevenlabsSimilarity_boost !== "" ? Number(config.elevenlabsSimilarity_boost) : undefined;
688
+ const style = config.elevenlabsStyle !== undefined && config.elevenlabsStyle !== "" ? Number(config.elevenlabsStyle) : undefined;
689
+ const resolvedModel = config.elevenlabsModel && config.elevenlabsModel !== "" ? config.elevenlabsModel : "eleven_multilingual_v2";
690
+ const latencyPreset = config.elevenlabsOptimizeLatency && config.elevenlabsOptimizeLatency !== "" ? config.elevenlabsOptimizeLatency : undefined;
691
+ const outputFormat = config.elevenlabsOutputFormat && config.elevenlabsOutputFormat !== "" ? config.elevenlabsOutputFormat : undefined;
692
+ const seed = config.elevenlabsSeed && config.elevenlabsSeed !== "" ? Number(config.elevenlabsSeed) : undefined;
693
+ const useSpeakerBoost = config.elevenlabsUse_speaker_boost === undefined ? true : config.elevenlabsUse_speaker_boost;
683
694
  const params = {
684
695
  stream: false,
685
696
  text: msg,
686
697
  voice: node.voiceId,
687
- model_id: "eleven_multilingual_v2",
688
- voice_settings: {
689
- stability: config.elevenlabsStability,
690
- similarity_boost: config.elevenlabsSimilarity_boost,
691
- style: config.elevenlabsStyle || 0,
692
- use_speaker_boost: config.elevenlabsUse_speaker_boost === undefined ? true : config.elevenlabsUse_speaker_boost
693
- }
698
+ model_id: resolvedModel,
699
+ voice_settings: {}
694
700
  };
701
+ if (stability !== undefined && !Number.isNaN(stability)) params.voice_settings.stability = stability;
702
+ if (similarity !== undefined && !Number.isNaN(similarity)) params.voice_settings.similarity_boost = similarity;
703
+ if (style !== undefined && !Number.isNaN(style)) params.voice_settings.style = style;
704
+ params.voice_settings.use_speaker_boost = useSpeakerBoost;
705
+ if (Object.keys(params.voice_settings).length === 0) delete params.voice_settings;
706
+ if (latencyPreset !== undefined) params.optimize_streaming_latency = latencyPreset;
707
+ if (outputFormat !== undefined) params.output_format = outputFormat;
708
+ if (seed !== undefined && !Number.isNaN(seed)) params.seed = seed;
695
709
  // Download or read from cache
696
710
  node.sFileToBePlayed = getFilename(msg, params);
697
711
  node.sFileToBePlayed = path.join(node.userDir, "ttsfiles", node.sFileToBePlayed);
@@ -857,37 +871,39 @@ module.exports = function (RED) {
857
871
  await delay(2000);
858
872
 
859
873
  // Resume music
860
- try {
861
- if (oCurTrack !== null && (!oCurTrack.hasOwnProperty("title") || oCurTrack.title.indexOf(".mp3") === -1)) {
862
- node.setNodeStatus({ fill: 'grey', shape: 'ring', text: "Resuming original queue..." });
863
- await resumeMusicQueue(oCurTrack);
864
- node.setNodeStatus({ fill: 'green', shape: 'ring', text: "Done resuming queue." });
865
- } else {
866
- // 28/08/2021 There was no queue playing. Delete the TTS from the queue
867
- node.setNodeStatus({ fill: 'green', shape: 'ring', text: "No queue to resume." });
874
+ if (!node.doNotResumeMusic) {
875
+ try {
876
+ if (oCurTrack !== null && (!oCurTrack.hasOwnProperty("title") || oCurTrack.title.indexOf(".mp3") === -1)) {
877
+ node.setNodeStatus({ fill: 'grey', shape: 'ring', text: "Resuming original queue..." });
878
+ await resumeMusicQueue(oCurTrack);
879
+ node.setNodeStatus({ fill: 'green', shape: 'ring', text: "Done resuming queue." });
880
+ } else {
881
+ // 28/08/2021 There was no queue playing. Delete the TTS from the queue
882
+ node.setNodeStatus({ fill: 'green', shape: 'ring', text: "No queue to resume." });
883
+ }
884
+ } catch (error) {
885
+ node.setNodeStatus({ fill: 'red', shape: 'ring', text: "Error resuming queue: " + error.message });
868
886
  }
869
- } catch (error) {
870
- node.setNodeStatus({ fill: 'red', shape: 'ring', text: "Error resuming queue: " + error.message });
871
887
  }
872
888
 
873
-
874
889
  // 19/04/2022 Resume music queue of additional players
875
- for (let index = 0; index < node.oAdditionalSonosPlayers.length; index++) {
876
- let addPlayer = node.oAdditionalSonosPlayers[index].oPlayer;
877
- let trackAddPlayer = addPlayer.additionalPlayerCurrentTrack;
878
- if (trackAddPlayer !== null) {
879
- try {
880
- await resumeMusicQueue(trackAddPlayer, addPlayer);
881
- node.setNodeStatus({ fill: 'green', shape: 'ring', text: "Done resuming queue additional player " + addPlayer.host || "" });
882
- } catch (error) {
883
- // Dont care
884
- RED.log.warn("ttsultimate: Error resuming music queue of additional player " + error.message + " " + addPlayer.host || "");
890
+ if (!node.doNotResumeMusic) {
891
+ for (let index = 0; index < node.oAdditionalSonosPlayers.length; index++) {
892
+ let addPlayer = node.oAdditionalSonosPlayers[index].oPlayer;
893
+ let trackAddPlayer = addPlayer.additionalPlayerCurrentTrack;
894
+ if (trackAddPlayer !== null) {
895
+ try {
896
+ await resumeMusicQueue(trackAddPlayer, addPlayer);
897
+ node.setNodeStatus({ fill: 'green', shape: 'ring', text: "Done resuming queue additional player " + addPlayer.host || "" });
898
+ } catch (error) {
899
+ // Dont care
900
+ RED.log.warn("ttsultimate: Error resuming music queue of additional player " + error.message + " " + addPlayer.host || "");
901
+ }
902
+ } else {
903
+ node.setNodeStatus({ fill: 'green', shape: 'ring', text: "No queue to resume for " + addPlayer.host || "" });
885
904
  }
886
- } else {
887
- node.setNodeStatus({ fill: 'green', shape: 'ring', text: "No queue to resume for " + addPlayer.host || "" });
888
905
  }
889
906
  }
890
-
891
907
  // Signal end playing
892
908
  let t = setTimeout(() => {
893
909
  node.msg.completed = true;
@@ -1192,7 +1208,10 @@ module.exports = function (RED) {
1192
1208
  }
1193
1209
  return new Promise((resolve, reject) => {
1194
1210
  // "model_id": "eleven_multilingual_v1",
1195
- ttsService.textToSpeechStream(node.server.credentials.elevenlabsKey, params.voice, params.text, null, null, "eleven_multilingual_v1").then((res) => {
1211
+ const stability = params.voice_settings && params.voice_settings.stability !== undefined ? params.voice_settings.stability : null;
1212
+ const similarity = params.voice_settings && params.voice_settings.similarity_boost !== undefined ? params.voice_settings.similarity_boost : null;
1213
+ const model = params.model_id !== undefined && params.model_id !== "" ? params.model_id : "eleven_monolingual_v1";
1214
+ ttsService.textToSpeechStream(node.server.credentials.elevenlabsKey, params.voice, params.text, stability, similarity, model).then((res) => {
1196
1215
  try {
1197
1216
  if (res !== undefined) {
1198
1217
  resolve(stream2buffer(res));