node-red-contrib-tts-ultimate 3.1.1 → 3.2.1

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,23 @@
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
+ <b>Version 3.2.0</b> June 2026<br/>
6
+
7
+ - NEW: **Google Cast** player (Chromecast / Google Nest). Devices are discovered via mDNS (press "Discover" in the node), with multi-room playback on the main device + additional players.<br/>
8
+ - NEW: **DLNA / UPnP renderer** player (smart TVs, AV receivers, etc.). Devices are discovered via SSDP. A native UPnP client locates the AVTransport/RenderingControl services anywhere in the device tree, so it also works with renderers that nest a MediaRenderer sub-device (e.g. Sonos).<br/>
9
+ - NEW: the "Additional players" list now works for Google Cast and DLNA too (not only Sonos), to play the announcement on several devices at once.<br/>
10
+ - FIX: the built-in HTTP file server now serves the TTS audio with proper streaming headers (Content-Type, Content-Length, byte-range support and DLNA headers), so strict DLNA renderers (some TVs) start playing correctly.<br/>
11
+ - CHORE: removed the "upnp-mediarenderer-client" dependency (replaced by a native UPnP client); added "castv2-client" and "multicast-dns".<br/>
12
+ </p>
13
+
14
+ <p>
15
+ <b>Version 3.1.2</b> June 2026<br/>
16
+
17
+ - CHORE: removed the external "google-translate-tts" dependency, replaced by a native built-in implementation (same voices and behaviour).<br/>
18
+ - CHORE: removed the redundant "path" dependency (Node.js built-in module is used instead).<br/>
19
+ </p>
20
+
21
+ <p>
5
22
  <b>Version 3.1.1</b> June 2026<br/>
6
23
 
7
24
  - NEW: voice option fields (ElevenLabs Stability/Similarity/Style/Speed and Google Rate/Pitch) are now sliders showing the current value live.<br/>
package/README.md CHANGED
@@ -1,16 +1,35 @@
1
- ![Sample Node](img/logo.png)
1
+ <p align="center">
2
+ <img src="img/logo-v2.svg" alt="TTS Ultimate" width="700">
3
+ </p>
4
+
5
+ <p align="center">
6
+ <a href="https://npmjs.org/package/node-red-contrib-tts-ultimate"><img src="https://img.shields.io/npm/v/node-red-contrib-tts-ultimate.svg" alt="NPM version"></a>
7
+ <a href="https://npmjs.org/package/node-red-contrib-tts-ultimate"><img src="https://img.shields.io/npm/dm/node-red-contrib-tts-ultimate.svg" alt="NPM downloads per month"></a>
8
+ <a href="https://npmjs.org/package/node-red-contrib-tts-ultimate"><img src="https://img.shields.io/npm/dt/node-red-contrib-tts-ultimate.svg" alt="NPM downloads total"></a>
9
+ <a href="https://github.com/Supergiovane/node-red-contrib-tts-ultimate/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License"></a>
10
+ <a href="https://standardjs.com"><img src="https://img.shields.io/badge/code_style-standard-brightgreen.svg" alt="JavaScript Standard Style"></a>
11
+ <a href="https://www.paypal.me/techtoday"><img src="https://img.shields.io/badge/Donate-PayPal-blue.svg?style=flat-square" alt="Donate via PayPal"></a>
12
+ <a href="https://www.facebook.com/supergiovaneDev"><img src="https://img.shields.io/badge/Visit%20me-Facebook-blue" alt="Visit me on Facebook"></a>
13
+ </p>
14
+
15
+ <p align="center">
16
+ <strong>Text-to-speech for Node-RED with Sonos, Google Cast, DLNA/UPnP and file-only output.</strong>
17
+ </p>
2
18
 
3
- [![NPM version][npm-version-image]][npm-url]
4
- [![NPM downloads per month][npm-downloads-month-image]][npm-url]
5
- [![NPM downloads total][npm-downloads-total-image]][npm-url]
6
- [![MIT License][license-image]][license-url]
7
- [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
8
- [![Donate via PayPal](https://img.shields.io/badge/Donate-PayPal-blue.svg?style=flat-square)](https://www.paypal.me/techtoday)
9
- [![Facebook][facebook-image]][facebook-url]
19
+ <p align="center">
20
+ <a href="#description">Description</a> ·
21
+ <a href="#supported-tts-engines">TTS engines</a> ·
22
+ <a href="#supported-players">Players</a> ·
23
+ <a href="#features">Features</a> ·
24
+ <a href="#tts-service-node">Configuration</a> ·
25
+ <a href="#ownfile-node-configuration">OwnFile</a>
26
+ </p>
10
27
 
11
- <img src='https://raw.githubusercontent.com/Supergiovane/node-red-contrib-tts-ultimate/master/img/main.png' width="80%">
28
+ <p align="center">
29
+ <img src="https://raw.githubusercontent.com/Supergiovane/node-red-contrib-tts-ultimate/master/img/main.png" width="82%" alt="TTS Ultimate Node-RED flow">
30
+ </p>
12
31
 
13
- <details><summary> VIEW SAMPLE CODE </summary>
32
+ <details><summary><strong>VIEW SAMPLE FLOW CODE</strong></summary>
14
33
 
15
34
  > Adjust the nodes according to your setup
16
35
 
@@ -161,30 +180,95 @@
161
180
 
162
181
  ## DESCRIPTION
163
182
 
164
- This node transforms a text into a speech audio that you can hear natively via <b>SONOS</b> speakers, but you can also simply create an audio file, without using SONOS at all.<br/>
165
- You can also generate an audio file for bluetooth speakers, web pages, etc.<br/>
166
- You can also 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/>
167
- The node can also create a **_TTS file (without the use of any Sonos device)_**, to be read by third parties nodes.<br/>
168
- This is a major **_upgrade from the previously popular node SonosPollyTTS_** (SonosPollyTTS is not developed anymore).<br/>
183
+ TTS Ultimate transforms text into speech audio from Node-RED. It can play announcements directly on **Sonos** speakers, **Google Cast** devices (Chromecast / Google Nest) and generic **DLNA/UPnP** renderers such as smart TVs and AV receivers.
169
184
 
170
- ## SUPPORTED TTS ENGINES
185
+ It can also work without any player: generate an audio file, cache it, and pass it to other nodes, web pages, Bluetooth workflows or third-party services. With the OwnFile node, you can upload and play your own audio messages and keep selected flows working completely offline.
171
186
 
172
- <p>
173
- <a href="https://www.npmjs.com/package/google-translate-tts" title="Google Translate TTS (free)">
174
- <img src="https://cdn.simpleicons.org/googletranslate/4285F4" height="126" alt="Google Translate logo"/>
175
- </a>&nbsp;&nbsp;
176
- <a href="https://cloud.google.com/text-to-speech" title="Google Cloud Text-to-Speech">
177
- <img src="https://cdn.simpleicons.org/googlecloud/4285F4" height="126" alt="Google Cloud logo"/>
178
- </a>&nbsp;&nbsp;
179
- <a href="https://elevenlabs.io" title="ElevenLabs">
180
- <img src="https://cdn.simpleicons.org/elevenlabs/000000" height="126" alt="ElevenLabs logo"/>
181
- </a>&nbsp;&nbsp;
182
- <a href="https://voice.ai/docs/api-reference/text-to-speech/generate-speech" title="Voice.ai">
183
- <img src="https://voice.ai/favicon.ico" height="126" alt="Voice.ai logo"/>
184
- </a>
185
- </p>
187
+ This is a major upgrade from the previously popular **SonosPollyTTS** node, which is no longer developed.
188
+
189
+ ## SUPPORTED TTS ENGINES
186
190
 
187
- [![Donate via PayPal](https://img.shields.io/badge/Donate-PayPal-blue.svg?style=flat-square)](https://www.paypal.me/techtoday)
191
+ <table>
192
+ <tr>
193
+ <td align="center" width="25%">
194
+ <a href="https://www.npmjs.com/package/google-translate-tts" title="Google Translate TTS (free)">
195
+ <img src="https://cdn.simpleicons.org/googletranslate/4285F4" height="86" alt="Google Translate logo"/>
196
+ </a>
197
+ <br/>
198
+ <strong>Google free TTS</strong>
199
+ <br/>
200
+ <sub>No credentials required</sub>
201
+ </td>
202
+ <td align="center" width="25%">
203
+ <a href="https://cloud.google.com/text-to-speech" title="Google Cloud Text-to-Speech">
204
+ <img src="https://cdn.simpleicons.org/googlecloud/4285F4" height="86" alt="Google Cloud logo"/>
205
+ </a>
206
+ <br/>
207
+ <strong>Google Cloud TTS</strong>
208
+ <br/>
209
+ <sub>Advanced voices and tuning</sub>
210
+ </td>
211
+ <td align="center" width="25%">
212
+ <a href="https://elevenlabs.io" title="ElevenLabs">
213
+ <img src="https://cdn.simpleicons.org/elevenlabs/000000" height="86" alt="ElevenLabs logo"/>
214
+ </a>
215
+ <br/>
216
+ <strong>ElevenLabs</strong>
217
+ <br/>
218
+ <sub>V1 and V2 multilingual voices</sub>
219
+ </td>
220
+ <td align="center" width="25%">
221
+ <a href="https://voice.ai/docs/api-reference/text-to-speech/generate-speech" title="Voice.ai">
222
+ <img src="https://voice.ai/favicon.ico" height="86" alt="Voice.ai logo"/>
223
+ </a>
224
+ <br/>
225
+ <strong>Voice.ai</strong>
226
+ <br/>
227
+ <sub>API key based voices</sub>
228
+ </td>
229
+ </tr>
230
+ </table>
231
+
232
+ ## SUPPORTED PLAYERS
233
+
234
+ <table>
235
+ <tr>
236
+ <td align="center" width="25%">
237
+ <a href="https://www.sonos.com" title="Sonos speakers">
238
+ <img src="https://cdn.simpleicons.org/sonos/000000" height="86" alt="Sonos logo"/>
239
+ </a>
240
+ <br/>
241
+ <strong>Sonos</strong>
242
+ <br/>
243
+ <sub>Native speakers and groups</sub>
244
+ </td>
245
+ <td align="center" width="25%">
246
+ <a href="https://developers.google.com/cast" title="Google Cast devices">
247
+ <img src="https://cdn.simpleicons.org/googlecast/4285F4" height="86" alt="Google Cast logo"/>
248
+ </a>
249
+ <br/>
250
+ <strong>Google Cast</strong>
251
+ <br/>
252
+ <sub>Chromecast and Google Nest</sub>
253
+ </td>
254
+ <td align="center" width="25%">
255
+ <a href="https://www.dlna.org" title="DLNA and UPnP renderers">
256
+ <img src="https://cdn.simpleicons.org/dlna/0066CC" height="86" alt="DLNA logo"/>
257
+ </a>
258
+ <br/>
259
+ <strong>DLNA / UPnP</strong>
260
+ <br/>
261
+ <sub>Smart TVs, AV receivers and renderers</sub>
262
+ </td>
263
+ <td align="center" width="25%">
264
+ <img src="img/audio-file.svg" height="86" alt="Audio file icon"/>
265
+ <br/>
266
+ <strong>No player</strong>
267
+ <br/>
268
+ <sub>Create an audio file only</sub>
269
+ </td>
270
+ </tr>
271
+ </table>
188
272
 
189
273
  ## CHANGELOG
190
274
 
@@ -192,22 +276,22 @@ This is a major **_upgrade from the previously popular node SonosPollyTTS_** (So
192
276
 
193
277
  ## FEATURES
194
278
 
195
- - **Native Sonos support**: hear the TTS audio directly via Sonos. You can also group speakers, set an hailing sound, choose the volume of each speaker etc.
196
- - **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.
197
- - **Google Translate Voices, Google TTS Voices, ElevenLabs voices and Voice.ai voices** are supported.
198
- - **Automatic grouping** is supported. You can group all players you want to play your announcements.
199
- - **Automatic discovery** of your players.
200
- - **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. **Be aware that this could not work with all music queues**.
201
- - **TTS caching**. ElevenLabs and Google paid service charge you for a high rate of text-to-speech requests. TTS-Ultimate caches the TTS files: it downloads each generated audio only once, and then reads it from cache. The cache is resilient (survives reboots and updates).
202
- - **Can work offline**. You can use your own audio files (with OwnFile node) to make the node works offline.
203
- - **UPLOAD your own audio files**. You can also upload your own audio files with OwnFile node.
204
-
205
- ## BREAKING CHANGE !
206
-
207
- <p>
208
- <b>Version 3.0.0</b> April 2025<br/>
209
- - 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/>
210
- </p>
279
+ - **Native Sonos support**: play TTS audio directly via Sonos, group speakers, set a hailing sound and choose the volume for each speaker.
280
+ - **Google Cast support**: play announcements on Chromecast / Google Nest devices, including multi-room playback.
281
+ - **DLNA / UPnP renderer support**: play TTS on smart TVs, AV receivers and other UPnP renderers. Renderers with nested MediaRenderer devices are supported too.
282
+ - **File-only output**: create the TTS file without using any player, then pass it to other nodes or services.
283
+ - **Multiple TTS engines**: Google free TTS, Google Cloud TTS, ElevenLabs and Voice.ai voices are supported.
284
+ - **Automatic grouping / multi-room**: add additional players and play one announcement on several devices at once.
285
+ - **Automatic discovery**: find Sonos and DLNA/UPnP renderers via SSDP, and Google Cast devices via mDNS.
286
+ - **Music resume**: resume the previous queue after an announcement where supported. Some radio-station queues may not resume reliably because of Sonos API limitations.
287
+ - **TTS caching**: generated audio is downloaded once and then served from cache. The cache survives reboots and updates.
288
+ - **Offline-ready OwnFile playback**: upload custom audio messages and use them without an internet connection.
289
+
290
+ ## BREAKING CHANGE
291
+
292
+ > **Version 3.0.0 - April 2025**
293
+ >
294
+ > Amazon Polly and Microsoft Azure TTS have been removed because the old APIs required a larger update. Contributions are welcome via fork and PR. If you still need those TTS engines, stay on or revert to `2.0.10`.
211
295
 
212
296
  ---
213
297
 
@@ -215,26 +299,26 @@ This is a major **_upgrade from the previously popular node SonosPollyTTS_** (So
215
299
 
216
300
  > **_NOTE IF YOU CANNOT UPLOAD YOUR OWN FILES_**
217
301
  >
218
- > If you're running node-red as "plugin" for homeassistant, redmatic etc...<br/>
219
- > You may expect not to be able to upload your own files. Please check that the user running node-red, has premission **to write to the filesystem**.<br/>
302
+ > If you're running Node-RED as a plugin for Home Assistant, RedMatic, etc...<br/>
303
+ > You may not be able to upload your own files. Please check that the user running Node-RED has permission **to write to the filesystem**.<br/>
220
304
 
221
305
  <br/><br/>
222
306
 
223
- # TTS Service node
307
+ ## TTS Service Node
224
308
 
225
309
  Here you can set all parameters you need. All nodes will refer to this config node, so you need to set it only once.<br/>
226
310
  IF YOU RUN NODE-RED BEHIND DOCKER OR SOMETHING ELSE, BE AWARE: <br/>
227
- PORT USED BY THE NODE ARE 1980 (DEFAULT) AND 1400 (FOR SONOS DISCOVER). <br/>
311
+ PORTS USED BY THE NODE ARE 1980 (DEFAULT, HTTP FILE SERVER) AND 1400 (FOR SONOS DISCOVERY). <br/>
228
312
  PLEASE ALLOW MDNS AND UDP AS WELL
229
313
 
230
314
  **TTS Service**<br/>
231
- You can choose between Voice.ai, Elevenlabs.io, Google (without credentials), Google TTS (require credentials and registration to google).<br/>
315
+ You can choose between Voice.ai, ElevenLabs.io, Google (without credentials), and Google TTS (requires credentials and registration with Google).<br/>
232
316
  For Google TTS Engine, you can choose pitch and speed rate of the voice.
233
317
  <br/>
234
318
  <br/>
235
319
 
236
320
  - **TTS Service using Google (without credentials)**<br/>
237
- 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.
321
+ 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.
238
322
  Note: long texts are automatically split into 200-character chunks (Google Translate TTS limit) and merged into a single audio output.
239
323
  Manual verify: `npm run verify:googletranslate-split -- --voice it-IT --text "..." --out ./out.mp3`
240
324
 
@@ -251,21 +335,21 @@ For Google TTS Engine, you can choose pitch and speed rate of the voice.
251
335
  <br/>
252
336
 
253
337
  - **TTS Service using ElevenLabs**<br/>
254
- Please use the V2 engine, as the V1 is deprecated and will not longer be supported. The V2 has multilingual voices and is more powerful.
255
- You have two choiches: To register to eventlabs, or not to register. If you don't register to elevenlabs.io, you will either have access on a limited amount of voices, or no access at all.
256
- After registration at elevenlabs.io, you can add any language to your personal list. The personal list will be then show in the node voice's list.<br/>
338
+ Please use the V2 engine, as the V1 is deprecated and will no longer be supported. The V2 has multilingual voices and is more powerful.<br/>
339
+ You have two choices: register to ElevenLabs, or do not register. If you don't register to ElevenLabs.io, you will either have access to a limited amount of voices, or no access at all.<br/>
340
+ After registration at ElevenLabs.io, you can add any language to your personal list. The personal list will then be shown in the node voice list.<br/>
257
341
  <br/>
258
342
 
259
343
  - **TTS Service using Voice.ai**<br/>
260
- Add your Voice.ai API key in the config node, deploy and restart Node-RED. The node will load your available voices and show them in the Voice dropdown.
344
+ Add your Voice.ai API key in the config node, deploy and restart Node-RED. The node will load your available voices and show them in the Voice dropdown.<br/>
261
345
  Note: SSML is not supported by this engine.
262
346
  <br/>
263
347
 
264
- **Node-Red IP**<br/>
265
- set IP of your node-red machine. Write **AUTODISCOVER** to allow the node to auto discover your IP.
348
+ **Node-RED IP**<br/>
349
+ Set IP of your Node-RED machine. Write **AUTODISCOVER** to allow the node to auto discover your IP.
266
350
 
267
351
  **Host Port**<br/>
268
- Sonos will connect to this port in order to play TTS. Default 1980. Choose a free port. Do not use 1880 or any other port already in use on your computer.
352
+ The players (Sonos, Google Cast, DLNA/UPnP renderers) will connect to this port to fetch the TTS audio. Default 1980. Choose a free port. Do not use 1880 or any other port already in use on your computer. The port must be reachable from the players on your network.
269
353
 
270
354
  Note: if you use multiple `ttsultimate-config` nodes, each one now keeps its own TTS cache folder; the “purge on restart/deploy” option only affects that config node’s cache.
271
355
 
@@ -286,9 +370,9 @@ Leave this field blank for the default.<br/>
286
370
  <br/>
287
371
  <br/>
288
372
 
289
- # TTS-ULTIMATE NODE
373
+ ## TTS-Ultimate Node
290
374
 
291
- ## INPUT MESSAGES TO THE NODE <br/>
375
+ ### INPUT MESSAGES TO THE NODE <br/>
292
376
 
293
377
  _Examples_
294
378
 
@@ -340,7 +424,7 @@ msg.stop = true;
340
424
  return msg;
341
425
  ```
342
426
 
343
- ## CHANGE CONFIGURATION VIA MSG PROPERTY
427
+ ### CHANGE CONFIGURATION VIA MSG PROPERTY
344
428
 
345
429
  You can change the configuration of tts-ultimate, _via msg.setConfig_ property.<br/>
346
430
  The property is a JSON object.
@@ -502,7 +586,7 @@ return msg;
502
586
  <br/>
503
587
  <br/>
504
588
 
505
- # OWNFILE NODE CONFIGURATION
589
+ ## OwnFile Node Configuration
506
590
 
507
591
  <img src='https://github.com/Supergiovane/node-red-contrib-tts-ultimate/raw/master/img/of.png' width="80%">
508
592
 
@@ -620,7 +704,7 @@ return msg;
620
704
 
621
705
  </details>
622
706
 
623
- This node allow you to upload your custom message and play it via ttsultimate without the need of an internet connection. You can use it, for example, with your alarm panel, to annuce a zone breach, a doorbell or so.
707
+ This node allows you to upload your custom message and play it via TTS Ultimate without the need for an internet connection. You can use it, for example, with your alarm panel to announce a zone breach, a doorbell or similar events.
624
708
 
625
709
  **Name**<br/>
626
710
  Node name
@@ -632,13 +716,13 @@ Select a file to be played. You can upload one or multiple files at the same tim
632
716
  If set to <b>true</b>, the OwnFile message will cancel the current TTS queue, will stop the current phrase being spoken and the TTS-Ultimate node will play this priority message.<br/>
633
717
  Please refer to _msg.priority_ msg input property of TTS-Ultimate for info on how this message will be handled<br/>
634
718
 
635
- ## INPUT MESSAGE
719
+ ### INPUT MESSAGE
636
720
 
637
721
  **msg.payload = true**<br/>
638
722
  Begin play of the message <br/>
639
723
 
640
724
  **msg.selectedFile = "Garage door open"** <br/>
641
- Overrides the selected message and plays the filename you passed in. Please double check the spelling of the filename (must be the same as you can see in the dropdown list of your own files, in the node config window) and do not include the <b>.mp3</b> extenson.<br/>
725
+ Overrides the selected message and plays the filename you passed in. Please double check the spelling of the filename (must be the same as you can see in the dropdown list of your own files, in the node config window) and do not include the <b>.mp3</b> extension.<br/>
642
726
 
643
727
  **msg.priority**<br/>
644
728
  If set to <b>true</b>, the OwnFile message will cancel the current TTS queue, will stop the current phrase being spoken and the TTS-Ultimate node will play this priority message.<br/>
@@ -647,7 +731,7 @@ Please refer to _msg.priority_ msg input property of TTS-Ultimate for info on ho
647
731
  ![Logo](https://raw.githubusercontent.com/Supergiovane/node-red-contrib-tts-ultimate/master/img/madeinitaly.png)
648
732
 
649
733
  [license-image]: https://img.shields.io/badge/license-MIT-blue.svg
650
- [license-url]: https://github.com/Supergiovane/node-red-contrib-tts-ultimate/master/LICENSE
734
+ [license-url]: https://github.com/Supergiovane/node-red-contrib-tts-ultimate/blob/master/LICENSE
651
735
  [npm-url]: https://npmjs.org/package/node-red-contrib-tts-ultimate
652
736
  [npm-version-image]: https://img.shields.io/npm/v/node-red-contrib-tts-ultimate.svg
653
737
  [npm-downloads-month-image]: https://img.shields.io/npm/dm/node-red-contrib-tts-ultimate.svg
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-labelledby="title desc">
2
+ <title id="title">Audio file</title>
3
+ <desc id="desc">A document with an audio waveform, representing output without a player.</desc>
4
+ <rect width="128" height="128" rx="18" fill="#f3f6fb"/>
5
+ <path d="M36 18h38l22 22v70H36z" fill="#ffffff" stroke="#1f2937" stroke-width="6" stroke-linejoin="round"/>
6
+ <path d="M74 18v24h22" fill="none" stroke="#1f2937" stroke-width="6" stroke-linejoin="round"/>
7
+ <path d="M48 72v-8m12 22V50m12 42V58m12 26V66" fill="none" stroke="#2563eb" stroke-width="8" stroke-linecap="round"/>
8
+ </svg>
@@ -0,0 +1,39 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="900" height="210" viewBox="0 0 900 210" role="img" aria-labelledby="title desc">
2
+ <title id="title">TTS Ultimate</title>
3
+ <desc id="desc">TTS Ultimate logo with a speech waveform mark and wordmark.</desc>
4
+ <defs>
5
+ <linearGradient id="markGradient" x1="16" y1="18" x2="172" y2="172" gradientUnits="userSpaceOnUse">
6
+ <stop offset="0" stop-color="#ffb3f0"/>
7
+ <stop offset="0.5" stop-color="#f47adf"/>
8
+ <stop offset="1" stop-color="#d84fc2"/>
9
+ </linearGradient>
10
+ <linearGradient id="textGradient" x1="218" y1="70" x2="620" y2="70" gradientUnits="userSpaceOnUse">
11
+ <stop offset="0" stop-color="#f56bdd"/>
12
+ <stop offset="0.55" stop-color="#e24ac8"/>
13
+ <stop offset="1" stop-color="#b932a8"/>
14
+ </linearGradient>
15
+ <filter id="softShadow" x="-20%" y="-20%" width="140%" height="140%">
16
+ <feDropShadow dx="0" dy="8" stdDeviation="9" flood-color="#172033" flood-opacity="0.16"/>
17
+ </filter>
18
+ </defs>
19
+
20
+ <rect x="10" y="10" width="880" height="190" rx="34" fill="#ffffff"/>
21
+ <rect x="10" y="10" width="880" height="190" rx="34" fill="none" stroke="#d9e1ef" stroke-width="2"/>
22
+
23
+ <g filter="url(#softShadow)">
24
+ <rect x="48" y="34" width="142" height="142" rx="36" fill="url(#markGradient)"/>
25
+ <path d="M78 106c15-24 36-36 61-36 18 0 33 6 46 18" fill="none" stroke="#ffffff" stroke-width="10" stroke-linecap="round"/>
26
+ <path d="M78 106c15 24 36 36 61 36 18 0 33-6 46-18" fill="none" stroke="#ffffff" stroke-width="10" stroke-linecap="round" opacity="0.92"/>
27
+ <path d="M86 108h12m15 18V90m17 54V72m17 62V82m17 38v-28" fill="none" stroke="#ffffff" stroke-width="8" stroke-linecap="round"/>
28
+ <circle cx="75" cy="106" r="8" fill="#ffffff"/>
29
+ <circle cx="186" cy="106" r="5" fill="#ffffff" opacity="0.8"/>
30
+ </g>
31
+
32
+ <g font-family="Inter, Avenir Next, Segoe UI, Arial, sans-serif">
33
+ <text x="220" y="100" fill="url(#textGradient)" font-size="62" font-weight="900" letter-spacing="3">TTS</text>
34
+ <text x="355" y="100" fill="#2a1830" font-size="62" font-weight="850" letter-spacing="2">ULTIMATE</text>
35
+ <text x="224" y="137" fill="#6f5574" font-size="24" font-weight="600" letter-spacing="1">NODE-RED TEXT TO SPEECH</text>
36
+ <path d="M224 153h312" stroke="url(#textGradient)" stroke-width="6" stroke-linecap="round"/>
37
+ <path d="M552 153h34" stroke="#ffb3f0" stroke-width="6" stroke-linecap="round"/>
38
+ </g>
39
+ </svg>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "node-red-contrib-tts-ultimate",
3
- "version": "3.1.1",
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 Google (without credentials as well), Google TTS, ElevenLabs.io TTS, Voice.ai 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.",
3
+ "version": "3.2.1",
4
+ "description": "Transforms the text in speech and hear it using Sonos, Google Cast (Chromecast / Nest) or DLNA/UPnP players, or generate an audio file to be used with third parties nodes. Works with voices from Google (without credentials as well), Google TTS, ElevenLabs.io TTS, Voice.ai 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": {
7
7
  "test": "test",
@@ -39,13 +39,13 @@
39
39
  },
40
40
  "homepage": "https://github.com/Supergiovane/node-red-contrib-tts-ultimate",
41
41
  "dependencies": {
42
- "sonos": "1.14.1",
43
- "formidable": "1.2.2",
44
- "path": ">=0.12.7",
45
42
  "@google-cloud/text-to-speech": "4.2.2",
46
- "google-translate-tts": ">=0.3.0",
43
+ "castv2-client": "^1.2.0",
44
+ "elevenlabs": "0.18.0",
47
45
  "elevenlabs-node": "1.1.3",
48
- "elevenlabs": "0.18.0"
46
+ "formidable": "1.2.2",
47
+ "multicast-dns": "^7.2.5",
48
+ "sonos": "1.14.1"
49
49
  },
50
50
  "devDependencies": {
51
51
  "eslint": ">=4.18.2",
@@ -0,0 +1,44 @@
1
+ // Discover DLNA / UPnP MediaRenderer devices on the local network.
2
+ // Prints the device description XML URL (LOCATION) to paste into the
3
+ // tts-ultimate "DLNA / UPnP renderer" player configuration.
4
+ //
5
+ // Usage:
6
+ // node scripts/discover-dlna.js (search ~5s)
7
+ // node scripts/discover-dlna.js --timeout 8000
8
+ const { discoverRenderers } = require("../ttsultimate/lib/dlna-discovery");
9
+
10
+ const argValue = (name, fallback) => {
11
+ const idx = process.argv.indexOf(name);
12
+ if (idx === -1) return fallback;
13
+ const value = process.argv[idx + 1];
14
+ return value && !value.startsWith("--") ? value : fallback;
15
+ };
16
+
17
+ const timeoutMs = Number(argValue("--timeout", 5000)) || 5000;
18
+
19
+ (async () => {
20
+ // eslint-disable-next-line no-console
21
+ console.log(`Searching for DLNA/UPnP MediaRenderers for ${timeoutMs} ms...\n`);
22
+ const devices = await discoverRenderers({ timeoutMs });
23
+ if (devices.length === 0) {
24
+ // eslint-disable-next-line no-console
25
+ console.log("No MediaRenderer devices found. Make sure a renderer is on and on the same subnet.");
26
+ process.exit(0);
27
+ }
28
+ // eslint-disable-next-line no-console
29
+ console.log(`Found ${devices.length} renderer(s):\n`);
30
+ for (const dev of devices) {
31
+ // eslint-disable-next-line no-console
32
+ console.log(" " + (dev.name || "(unknown name)"));
33
+ // eslint-disable-next-line no-console
34
+ console.log(" Description URL (paste this in the node): " + dev.location);
35
+ if (dev.server) console.log(" Server: " + dev.server);
36
+ // eslint-disable-next-line no-console
37
+ console.log("");
38
+ }
39
+ process.exit(0);
40
+ })().catch((err) => {
41
+ // eslint-disable-next-line no-console
42
+ console.error(err);
43
+ process.exit(1);
44
+ });
@@ -1,6 +1,6 @@
1
1
  const fs = require("fs");
2
2
  const path = require("path");
3
- const GoogleTranslate = require("google-translate-tts");
3
+ const GoogleTranslate = require("../ttsultimate/lib/googletranslate");
4
4
 
5
5
  const GOOGLE_TRANSLATE_MAX_CHARS = 200;
6
6
 
@@ -0,0 +1,98 @@
1
+ // Discover DLNA / UPnP MediaRenderer devices on the local network via SSDP.
2
+ // No external dependencies: built-in dgram (UDP multicast) + http only.
3
+ const dgram = require("dgram");
4
+ const http = require("http");
5
+ const { URL } = require("url");
6
+
7
+ const SSDP_ADDR = "239.255.255.250";
8
+ const SSDP_PORT = 1900;
9
+ const SEARCH_TARGET = "urn:schemas-upnp-org:device:MediaRenderer:1";
10
+
11
+ // Reads the <friendlyName> from a device description XML URL (best-effort).
12
+ function fetchFriendlyName(location) {
13
+ return new Promise((resolve) => {
14
+ try {
15
+ const u = new URL(location);
16
+ const req = http.get(
17
+ { hostname: u.hostname, port: u.port || 80, path: u.pathname + u.search, timeout: 3000 },
18
+ (res) => {
19
+ let body = "";
20
+ res.on("data", (c) => (body += c));
21
+ res.on("end", () => {
22
+ const m = body.match(/<friendlyName>([^<]+)<\/friendlyName>/i);
23
+ resolve(m ? m[1].trim() : "");
24
+ });
25
+ }
26
+ );
27
+ req.on("error", () => resolve(""));
28
+ req.on("timeout", () => { req.destroy(); resolve(""); });
29
+ } catch (e) {
30
+ resolve("");
31
+ }
32
+ });
33
+ }
34
+
35
+ // discoverRenderers({ timeoutMs }) -> Promise<[{ name, location, server }]>
36
+ function discoverRenderers(options) {
37
+ const opts = options || {};
38
+ const timeoutMs = Number(opts.timeoutMs) || 5000;
39
+ const withNames = opts.withNames !== false; // resolve friendlyName by default
40
+
41
+ return new Promise((resolve, reject) => {
42
+ const found = new Map(); // location -> { location, server }
43
+ const mSearch = Buffer.from(
44
+ "M-SEARCH * HTTP/1.1\r\n" +
45
+ `HOST: ${SSDP_ADDR}:${SSDP_PORT}\r\n` +
46
+ 'MAN: "ssdp:discover"\r\n' +
47
+ "MX: 2\r\n" +
48
+ `ST: ${SEARCH_TARGET}\r\n` +
49
+ "\r\n"
50
+ );
51
+
52
+ let socket;
53
+ try {
54
+ socket = dgram.createSocket({ type: "udp4", reuseAddr: true });
55
+ } catch (error) {
56
+ reject(error);
57
+ return;
58
+ }
59
+
60
+ socket.on("message", (msg) => {
61
+ const text = msg.toString();
62
+ // Keep only genuine MediaRenderer responses (some non-compliant devices,
63
+ // e.g. Hue bridges, answer any M-SEARCH; their ST/USN won't mention it).
64
+ if (!/MediaRenderer/i.test(text)) return;
65
+ const locationMatch = text.match(/LOCATION:\s*(.+)\r/i);
66
+ if (!locationMatch) return;
67
+ const location = locationMatch[1].trim();
68
+ if (found.has(location)) return;
69
+ const serverMatch = text.match(/SERVER:\s*(.+)\r/i);
70
+ found.set(location, { location, server: serverMatch ? serverMatch[1].trim() : "" });
71
+ });
72
+
73
+ socket.on("error", (err) => {
74
+ try { socket.close(); } catch (e) { }
75
+ reject(err);
76
+ });
77
+
78
+ socket.bind(() => {
79
+ try { socket.setBroadcast(true); } catch (e) { }
80
+ const send = () => { try { socket.send(mSearch, 0, mSearch.length, SSDP_PORT, SSDP_ADDR); } catch (e) { } };
81
+ send();
82
+ setTimeout(send, 500);
83
+ setTimeout(send, 1500);
84
+
85
+ setTimeout(async () => {
86
+ try { socket.close(); } catch (e) { }
87
+ const devices = Array.from(found.values());
88
+ if (!withNames) { resolve(devices); return; }
89
+ for (const dev of devices) {
90
+ dev.name = (await fetchFriendlyName(dev.location)) || "";
91
+ }
92
+ resolve(devices);
93
+ }, timeoutMs);
94
+ });
95
+ });
96
+ }
97
+
98
+ module.exports = { discoverRenderers, fetchFriendlyName };