Skip to content

Commit

Permalink
Merge pull request #189 from chughts/ttsupdate
Browse files Browse the repository at this point in the history
TTS Dynamic Voice Selection
  • Loading branch information
chughts committed Sep 1, 2016
2 parents ea9a73d + f639ac2 commit 4b2833c
Show file tree
Hide file tree
Showing 3 changed files with 306 additions and 54 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Node-RED Watson Nodes for IBM Bluemix
<a href="https://cla-assistant.io/watson-developer-cloud/node-red-node-watson"><img src="https://cla-assistant.io/readme/badge/watson-developer-cloud/node-red-node-watson" alt="CLA assistant" /></a>

### New in version 0.4.14
- The dialog for the Test to Speech service now loads available voices dynamically. This allows
new voices and languages to be identified without requiring a further code change.

### New in version 0.4.13
- Emergency fix for Watson Language Translation node. Bluemix credentials read too late.
Expand Down
313 changes: 266 additions & 47 deletions services/text_to_speech/v1.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
<i class="fa fa-question-circle"></i><b> Please wait: </b> Checking for bound service credentials...
</div>
</div>

<div>
<label id="node-label-message"><i class="fa fa-exclamation-triangle"></i></label>
</div>

<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
Expand All @@ -35,28 +40,19 @@
<div class="form-row">
<label for="node-input-lang"><i class="fa fa-language"></i> Language</label>
<select type="text" id="node-input-lang" style="display: inline-block; width: 70%;" >
<option value="english">English</option>
<option value="german">German</option>
<option value="french">French</option>
<option value="spanish">Spanish</option>
<option value="italian">Italian</option>

</select>
</div>
<div class="form-row">
<input type="hidden" id="node-input-langhidden"/>
</div>
<div class="form-row">
<label for="node-input-voice"><i class="fa fa-comment"></i> Voice</label>
<select type="text" id="node-input-voice" style="display: inline-block; width: 70%;">
<option class="english" value="en-US_MichaelVoice">Michael (Male)</option>
<option class="english" value="en-US_LisaVoice">Lisa (Female)</option>
<option class="english" value="en-US_AllisonVoice">Allison (Female)</option>
<option class="spanish" value="es-ES_EnriqueVoice">Enrique (Male)</option>
<option class="spanish" value="es-ES_LauraVoice">Laura (Female)</option>
<option class="spanish" value="es-US_SofiaVoice">Sofia (Female)</option>
<option class="french" value="fr-FR_ReneeVoice">Renee(Female)</option>
<option class="italian" value="it-IT_FrancescaVoice">Francesca (Female)</option>
<option class="english" value="en-GB_KateVoice">Kate (Female)</option>
<option class="german" value="de-DE_DieterVoice">Dieter (Male)</option>
<option class="german" value="de-DE_BirgitVoice">Birgit (Female)</option>
</select>
</select>
</div>
<div class="form-row">
<input type="hidden" id="node-input-voicehidden"/>
</div>
<div class="form-row">
<label for="node-input-format"><i class="fa fa-file-audio-o"></i> Format</label>
Expand All @@ -71,14 +67,7 @@
<script type="text/x-red" data-help-name="watson-text-to-speech">
<p>The Text To Speech service understands text and natural language to generate synthesized audio output complete
with appropriate cadence and intonation.</p>
<p>You can choose from eleven different voices from three languages:</b>.</p>
<ul>
<li><b>English</b> Male and Female.</li>
<li><b>Spanish</b> Male and Female.</li>
<li><b>German</b> Male and Female.</li>
<li><b>French</b> Female.</li>
<li><b>Italian</b> Female.</li>
</ul>
<p>You can choose different voices for a range of languages:</b>.</p>
<p>The text to be converted should be passed in on <code>msg.payload</code>.</p>
<p><b>The source text must be in the language which matches the chosen voice, i.e. you cannot choose to a Spanish
voice with English text.</b>.</p>
Expand All @@ -87,18 +76,264 @@
</script>

<script type="text/javascript">

// Need to simulate a namespace, so that some of the variables don't leak across nodes
function TTS () {
}

// This is the namespace for tts.
var tts = new TTS();
//tts.language_selected = '';
//tts.voice_selected = '';
tts.LANGUAGES = { 'en-US': 'US English',
'en-GB': 'UK English',
'pt-BR': 'Portuguese Brazilian',
'fr-FR': 'French',
'it-IT': 'Italian',
'de-DE': 'German',
'zh-CN': 'Mandarin',
'es-ES': 'Spanish',
'es-US': 'US Spanish',
'ar-AR': 'Arablic',
'ja-JP': 'Japanese'
};

// Called to complete the languages selection table
function processLanguages() {
if (!tts.languages && tts.voices) {
tts.languages = tts.voices.map(function(m) {
return m.language;
});
}
if (tts.languages) {
$('select#node-input-lang').empty();
var unique_langs = tts.languages.filter(onlyUnique);

unique_langs.forEach(function(l) {
var selectedText = '';
if (tts.language_selected === l) {
selectedText = 'selected="selected"';
}
$('select#node-input-lang')
.append('<option value='
+ '"' + l + '"'
+ selectedText
+ '>'
+ (tts.LANGUAGES[l] ? tts.LANGUAGES[l] : l)
+ '</option>');
});

}
}

// Populate the Voices selection field
function populateVoices() {
if (!tts.voicenames && tts.voices) {
tts.voicenames = tts.voices.map(function(m) {
//return m.name.split('_')[1];
return m.name;
});
var unique_voices = tts.voicenames.filter(onlyUnique);
tts.voicenames = unique_voices;
}
if (!tts.voicedata && tts.voicenames){
tts.voicedata = [];
tts.voicenames.forEach(function(a){
var element = {};
var bits = a.split('_');
element.full = a;
element.language = bits[0];
element.person = bits[1].replace('Voice','');;
tts.voicedata.push(element);
});
}

if (tts.voicedata) {
$('select#node-input-voice').empty();

tts.voicedata.forEach(function(b) {
var selectedText = '';
if (tts.voice_selected === b.full) {
selectedText = 'selected="selected"';
}
if (tts.language_selected === b.language) {
$('select#node-input-voice')
.append('<option value='
+ '"' + b.full + '"'
+ selectedText
+ '>'
+ b.person
+ '</option>');
}
});
}
}


// Called to work through the voices, completing the dyanmic selection fields.
function processVoices() {
if (tts.voices) {
processLanguages();
populateVoices();
}
}

function visibilityCheck()
{
if (tts.voices) {
$('label#node-label-message').parent().hide();
$('select#node-input-lang').parent().show();
$('select#node-input-voice').parent().show();
} else {
$('label#node-label-message').parent().hide();
$('select#node-input-lang').parent().hide();
$('select#node-input-voice').parent().hide();
}

}

// Function called when either when the voices have been retrieved, or
// on dialog load, if the voices has already been retrieved
function postVoiceCheck(){
processVoices();
visibilityCheck();
}

// Retrieve the available voices from the server, if data is returned, then
// can enable the dynamic selection fields.
function getVoices(){
var u = $('#node-input-username').val();
var p = $('#node-input-password').val();

$.getJSON('watson-text-to-speech/voices/', {un: u, pwd: p}).done(function (data) {
if (data.error) {
$('label#node-label-message').parent().show();
$('label#node-label-message').text(data.error);
} else if (data.voices) {
tts.voices = data.voices;
postVoiceCheck();
}
}).fail(function (err) {
$('label#node-label-message').parent().show();
$('label#node-label-message').text('Error trying to determine available service voices');

}).always(function () {});
}


// The dynamic nature of the selection fields in this node has caused problems.
// Whenever there is a fetch for the models, on a page refresh or applicaiton
// restart, the settings for the dynamic fields are lost.
// So hidden (text) fields are being used to squirrel away the values, so that
// they can be restored.
function restoreFromHidden() {
tts.language_selected = $('#node-input-langhidden').val();
$('select#node-input-lang').val(tts.language_selected);

tts.voice_selected = $('#node-input-voicehidden').val();
$('select#node-input-voice').val(tts.voice_selected);
}

// Simple check that is only invoked if the service is not bound into bluemix. In this case the
// user has to provide credentials. Once there are credentials, then the tts.voices are retrieved.
function checkCredentials() {
var u = $('#node-input-username').val();
var p = $('#node-input-password').val();

if (u && u.length && p) {
if (!tts.voices) {
getVoices();
}
}
}

// Language Setting has changed, modofy voice options appropriately
function checkLanguage(){
//var lang = $('#node-input-lang').val();
//$('#node-input-voice option.' + lang).show();
//$('#node-input-voice option:not(.' + lang + ')').hide();
//var first = $('#node-input-voice option.' + lang + ':first').val();
//$('#node-input-voice').val(first);

tts.language_selected = $('#node-input-lang').val();
}

// Voice Setting has changed, modofy voice options appropriately
function checkVoice(){
tts.voice_selected = $('#node-input-voice').val();
}


// Register the onchange handlers
function registerHandlers() {
$('#node-input-username').change(function(val){
checkCredentials();
});
$('#node-input-password').change(function(val){
checkCredentials();
});
$('#node-input-lang').change(function () {
checkLanguage();
populateVoices();
});
$('#node-input-voice').change(function () {
checkVoice();
});

}

// Function to be used at the start, as don't want to expose any fields, unless the models are
// available. The models can only be fetched if the credentials are available.
function hideEverything() {
if (!stt.models) {
$('label#node-label-message').parent().hide();
$('select#node-input-lang').parent().hide();
$('select#node-input-voice').parent().hide();
}
}

// This is the on edit prepare function, which will be invoked everytime the dialog
// is shown.
function oneditprepare() {
hideEverything();
restoreFromHidden();
registerHandlers();

$.getJSON('watson-text-to-speech/vcap/')
.done(function (service) {
restoreFromHidden();
$('.credentials').toggle(!service);
if (!tts.voices) {getVoices();}
else {postVoiceCheck();}
})
.fail(function () {
$('.credentials').show();
}).always(function () {
$('#credentials-check').hide();
})
}

// Save the values in the dyanmic lists to the hidden fields.
function oneditsave(){
$('#node-input-langhidden').val(tts.language_selected);
$('#node-input-voicehidden').val(tts.voice_selected);
}

(function() {
RED.nodes.registerType('watson-text-to-speech', {
category: 'IBM Watson',
defaults: {
name: {value: ""},
lang: {value: "english"},
voice: {value: "en-US_MichaelVoice"},
format: {value: "audio/wav"}
lang: {value: ""},
langhidden: {value: ""},
voice: {value: ""},
voicehidden: {value: ""},
format: {value: "audio/wav"},
password: {value: ''}
},
credentials: {
username: {type:"text"},
password: {type:"password"}
// password: {type:"password"} - // Taken out because, was not being restored on dialog open.
},
color: "rgb(140, 198, 63)",
inputs: 1,
Expand All @@ -111,24 +346,8 @@
labelStyle: function() {
return this.name ? "node_label_italic" : "";
},
oneditprepare: function() {
$('#node-input-lang').change(function () {
var lang = $('#node-input-lang').val();
$('#node-input-voice option.' + lang).show();
$('#node-input-voice option:not(.' + lang + ')').hide();
var first = $('#node-input-voice option.' + lang + ':first').val();
$('#node-input-voice').val(first);
})
$.getJSON('watson-text-to-speech/vcap/')
.done(function (service) {
$('.credentials').toggle(!service);
})
.fail(function () {
$('.credentials').show();
}).always(function () {
$('#credentials-check').hide();
})
}
oneditsave: oneditsave,
oneditprepare: oneditprepare
});
})();
</script>
Loading

0 comments on commit 4b2833c

Please sign in to comment.