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 +5 -0
- package/package.json +1 -1
- package/ttsultimate/ttsultimate.html +21 -3
- package/ttsultimate/ttsultimate.js +105 -69
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
[](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.
|
|
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
|
|
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
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
node.
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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
|
|
609
|
-
node.setNodeStatus({ fill: 'green', shape: 'ring', text: '
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
}
|
|
617
|
-
|
|
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
|
|
620
|
-
node.setNodeStatus({ fill: 'green', shape: 'ring', text: '
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
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
|
|
630
|
-
node.setNodeStatus({ fill: 'green', shape: 'ring', text: '
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
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
|
|
638
|
-
node.setNodeStatus({ fill: 'green', shape: 'ring', text: '
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
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
|
-
|
|
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
|
-
|
|
663
|
-
|
|
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,
|
|
1182
|
-
let sTextToBeHashed = _text.concat(
|
|
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') + "."
|
|
1221
|
+
return hashSum.digest('hex') + ".mp3";
|
|
1186
1222
|
}
|
|
1187
1223
|
|
|
1188
1224
|
function notifyError(msg, err) {
|