Skip to content

Commit

Permalink
feature: #1) implemented TTS with language swithcing capabilities.
Browse files Browse the repository at this point in the history
#2) implemented defining DTMF length (new Media property:
ANSWER_INPUT_LENGTH)
  • Loading branch information
shravanjc committed Nov 26, 2013
1 parent deb8a24 commit 0360ca0
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 72 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.almende.dialog.adapter;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
Expand Down Expand Up @@ -42,6 +45,7 @@
import com.almende.dialog.util.ServerUtils;
import com.almende.util.ParallelInit;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.api.server.spi.config.DefaultValue;
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
Expand Down Expand Up @@ -139,8 +143,7 @@ public static HashMap<String, String> dial( Map<String, String> addressNameMap,
loadAddress = addressNameMap.keySet().iterator().next();

//fetch the question
Question question = Question.fromURL(url, config.getConfigId(), loadAddress);
String questionJson = question.toJSON();
String questionJson = Question.getJSONFromURL( url, config.getConfigId(), loadAddress, "" );

for ( String address : addressNameMap.keySet() )
{
Expand Down Expand Up @@ -206,8 +209,10 @@ public static boolean killActiveCalls(AdapterConfig config) {
@Path("dtmf2hash")
@GET
@Produces("application/srgs+xml")
public Response getDTMF2Hash() {

public Response getDTMF2Hash(@QueryParam("minlength") String minLength, @QueryParam("maxlength") String maxLength) {
minLength = (minLength != null && !minLength.isEmpty()) ? minLength : "0";
maxLength = (maxLength != null && !maxLength.isEmpty()) ? maxLength : "";
String repeat = minLength.equals( maxLength ) ? minLength : minLength + "-" + maxLength;
String result = "<?xml version=\"1.0\"?> "+
"<grammar mode=\"dtmf\" version=\"1.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.w3.org/2001/06/grammar http://www.w3.org/TR/speech-grammar/grammar.xsd\" xmlns=\"http://www.w3.org/2001/06/grammar\" root=\"untilHash\" > "+
"<rule id=\"digit\"> "+
Expand All @@ -227,12 +232,11 @@ public Response getDTMF2Hash() {
"</rule> "+
"<rule id=\"untilHash\" scope=\"public\"> "+
"<one-of> "+
"<item repeat=\"0-\"><ruleref uri=\"#digit\"/></item> "+
"<item repeat=\"" + repeat + "\"><ruleref uri=\"#digit\"/></item> "+
"<item> # </item> "+
"</one-of> "+
"</rule> "+
"</grammar> ";

return Response.ok(result).build();
}

Expand Down Expand Up @@ -790,34 +794,18 @@ else if ( personality.getTextContent().equals( "Terminator" ) )

return Response.ok(reply).build();
}

/**
* redirects to the TSS functionality to play the audio
* @param text to be converted to Voice
* @return
*/

@GET
@Path( "tts/{text}" )
public HttpServletResponse redirectToTTS( @PathParam( "text" ) String text,
@Context HttpServletRequest httpServletRequest, @Context HttpServletResponse httpServletResponse )
throws Exception
@Path( "tts/{textForSpeech}" )
public Response redirectToSpeechEngine( @PathParam( "textForSpeech" ) String textForSpeech,
@QueryParam( "hl" ) @DefaultValue( "nl-nl" ) String language,
@QueryParam( "c" ) @DefaultValue( "wav" ) String contentType, @Context HttpServletRequest req,
@Context HttpServletResponse resp ) throws IOException, URISyntaxException
{
String ttsURL = "https://voicerss-text-to-speech.p.mashape.com/?key=afafc70fde4b4b32a730842e6fcf0c62&src="
+ text.replace( ".wav", "" ) + "&hl=en-us&r=0&c=wav&f=8khz_8bit_mono";
httpServletResponse.addHeader( "X-Mashape-Authorization", "Fy6ynDFxV5Yi6K0pj8NYxPneKgfztwp4" );
httpServletResponse.sendRedirect( ttsURL );

// Client client = ParallelInit.getClient();
// httpServletRequest.getSession().setAttribute( "X-Mashape-Authorization", "Fy6ynDFxV5Yi6K0pj8NYxPneKgfztwp4" );
// WebResource webResource = client.resource( ttsURL );
// ClientResponse clientResponse = webResource.getRequestBuilder()
// .header( "X-Mashape-Authorization", "Fy6ynDFxV5Yi6K0pj8NYxPneKgfztwp4" ).get( ClientResponse.class );
// return Response.ok( clientResponse.getEntityInputStream() ).build();
// return Response.seeOther( webResource.getURI() )
// .header( "X-Mashape-Authorization", "Fy6ynDFxV5Yi6K0pj8NYxPneKgfztwp4" ).build();
return httpServletResponse;
String ttsURL = getTTSURL( textForSpeech, language, contentType );
return Response.seeOther( new URI( ttsURL ) ).build();
}

public class Return {
ArrayList<String> prompts;
Question question;
Expand Down Expand Up @@ -1046,14 +1034,14 @@ protected String renderOpenQuestion(Question question,ArrayList<String> prompts,
// Check if media property type equals audio
// if so record audio message, if not record dtmf input
String typeProperty = question.getMediaPropertyValue( MediumType.BROADSOFT, MediaPropertyKey.TYPE );
String voiceMessageLengthProperty = question.getMediaPropertyValue( MediumType.BROADSOFT, MediaPropertyKey.VOICE_MESSAGE_LENGTH );
if(typeProperty!=null && typeProperty.equalsIgnoreCase("audio")) {
//assign a default voice mail length if one is not specified
String voiceMessageLength = voiceMessageLengthProperty != null ? voiceMessageLengthProperty : "15s";
if(!voiceMessageLength.endsWith("s"))
String voiceMessageLengthProperty = question.getMediaPropertyValue( MediumType.BROADSOFT, MediaPropertyKey.VOICE_MESSAGE_LENGTH );
voiceMessageLengthProperty = voiceMessageLengthProperty != null ? voiceMessageLengthProperty : "15s";
if(!voiceMessageLengthProperty.endsWith("s"))
{
log.warning("Redirect timeout must be end with 's'. E.g. 40s. Found: "+ voiceMessageLength);
voiceMessageLength += "s";
log.warning("Voicemail length must be end with 's'. E.g. 40s. Found: "+ voiceMessageLengthProperty);
voiceMessageLengthProperty += "s";
}

// Fetch the upload url
Expand All @@ -1072,7 +1060,7 @@ protected String renderOpenQuestion(Question question,ArrayList<String> prompts,
outputter.startTag("record");
outputter.attribute("name", "file");
outputter.attribute("beep", "true");
outputter.attribute("maxtime", voiceMessageLength);
outputter.attribute("maxtime", voiceMessageLengthProperty);
outputter.attribute("dtmfterm", "true");
//outputter.attribute("finalsilence", "3s");
for (String prompt : prompts){
Expand Down Expand Up @@ -1124,6 +1112,12 @@ protected String renderOpenQuestion(Question question,ArrayList<String> prompts,
outputter.endTag();
} else {

//see if a dtmf length is defined in the question
String dtmfMinLength = question.getMediaPropertyValue( MediumType.BROADSOFT, MediaPropertyKey.ANSWER_INPUT_MIN_LENGTH );
dtmfMinLength = dtmfMinLength != null ? dtmfMinLength : "";
String dtmfMaxLength = question.getMediaPropertyValue( MediumType.BROADSOFT, MediaPropertyKey.ANSWER_INPUT_MAX_LENGTH );
dtmfMaxLength = dtmfMaxLength != null ? dtmfMaxLength : "";

outputter.startTag("var");
outputter.attribute("name","answer_input");
outputter.endTag();
Expand All @@ -1140,7 +1134,8 @@ protected String renderOpenQuestion(Question question,ArrayList<String> prompts,
outputter.attribute("name", "answer");
outputter.startTag("grammar");
outputter.attribute("mode", "dtmf");
outputter.attribute("src", DTMFGRAMMAR);
outputter.attribute( "src", DTMFGRAMMAR + "?minlength=" + dtmfMinLength
+ "&maxlength=" + dtmfMaxLength );
outputter.attribute("type", "application/srgs+xml");
outputter.endTag();
for (String prompt: prompts){
Expand Down Expand Up @@ -1186,7 +1181,7 @@ private Response handleQuestion(Question question, String adapterID,String remot
if(question !=null && !question.getType().equalsIgnoreCase("comment"))
question = res.question;

log.info( "question formed at handleQuestion is: "+ question );
log.info( "question formed at handleQuestion is: "+ ServerUtils.serializeWithoutException( question ));
log.info( "prompts formed at handleQuestion is: "+ res.prompts );

if ( question != null )
Expand All @@ -1199,7 +1194,29 @@ private Response handleQuestion(Question question, String adapterID,String remot
Session session = Session.getSession( sessionKey );
StringStore.storeString( "question_" + session.getRemoteAddress() + "_" + session.getLocalAddress(),
questionJSON );


//convert all text prompts to speech
if(res.prompts != null)
{
String language = question.getPreferred_language().equals( "nl" ) ? "nl-nl" : "en-us";
ArrayList<String> promptsCopy = new ArrayList<String>();
for ( String prompt : res.prompts )
{
if ( !prompt.startsWith( "dtmfKey://" ) )
{
if ( !prompt.endsWith( ".wav" ) )
{
promptsCopy.add( getTTSURL( prompt, language, "wav" ) );
}
else
{
promptsCopy.add( prompt );
}
}
}
res.prompts = promptsCopy;
}

if ( question.getType().equalsIgnoreCase( "closed" ) )
{
result = renderClosedQuestion( question, res.prompts, sessionKey );
Expand Down Expand Up @@ -1296,4 +1313,29 @@ private HashMap<String, Object> getTimeMap( String startTime, String answerTime,
timeMap.put( "releaseTime", releaseTime );
return timeMap;
}

/**
* returns the TTS URL from voiceRSS.
*
* @param textForSpeech
* @param language
* @param contentType
* @return
*/
private String getTTSURL( String textForSpeech, String language, String contentType )
{
try
{
textForSpeech = URLEncoder.encode( textForSpeech.replace( "text://", "" ), "UTF-8").replace( "+", "%20" );
}
catch ( UnsupportedEncodingException e )
{
e.printStackTrace();
log.severe( e.getLocalizedMessage() );
}
return "http://api.voicerss.org/?key=afafc70fde4b4b32a730842e6fcf0c62&src=" + textForSpeech + "&hl=" + language
+ "&c=" + contentType + "&type=.wav";
// return "http://www.voicerss.org/controls/speech.ashx?hl=" + language + "&src="
// + textForSpeech.replace( ".wav", "" ) + "&c" + contentType + "&rnd=0.4776021621655673";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,17 @@ public class MediaProperty
{
public enum MediaPropertyKey
{
TIMEOUT, ANSWER_INPUT, LENGTH, TYPE;

TIMEOUT, //defines the timeout associated with the call
ANSWER_INPUT, //defines if the answer is given via dtmf, text etc
ANSWER_INPUT_MIN_LENGTH, //defines the length of th answer input. Typically dtmf
ANSWER_INPUT_MAX_LENGTH,
// defines a subtype for the question type.
//E.g. open question with type: audio refers to voicemail
TYPE,
VOICE_MESSAGE_LENGTH, //defines the length of the voicemail to be recorded
//defines the number of times the question should repeat in case of a wrong answer input.
//works only for phonecalls so as to end a call with repeated input errors.
RETRY_LIMIT;
@JsonCreator
public static MediaPropertyKey fromJson(String name) {
return valueOf(name.toUpperCase());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
import com.almende.dialog.model.MediaProperty.MediumType;
import com.almende.dialog.model.impl.Q_fields;
import com.almende.dialog.model.intf.QuestionIntf;
import com.almende.dialog.util.QuestionTextTransformer;
import com.almende.dialog.util.ServerUtils;
import com.almende.util.ParallelInit;
import com.eaio.uuid.UUID;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientHandlerException;
Expand All @@ -27,7 +27,6 @@
import com.thetransactioncompany.cors.HTTPMethod;

import flexjson.JSON;
import flexjson.JSONSerializer;

public class Question implements QuestionIntf {
private static final long serialVersionUID = -9069211642074173182L;
Expand All @@ -54,11 +53,30 @@ public Question() {
public static Question fromURL(String url,String adapterID) {
return fromURL(url, adapterID,"");
}

@JSON(include = false)
public static Question fromURL(String url,String adapterID,String remoteID) {
return fromURL(url, adapterID, remoteID, "");
}

public static String getJSONFromURL( String url, String adapterID, String remoteID, String fromID )
{
Client client = ParallelInit.getClient();
WebResource webResource;
try
{
webResource = client.resource( url ).queryParam( "responder", URLEncoder.encode( remoteID, "UTF-8" ) )
.queryParam( "requester", fromID );
return webResource.type( "text/plain" ).get( String.class );
}
catch ( UnsupportedEncodingException e )
{
log.severe( e.toString() );
dialogLog.severe( adapterID, "ERROR loading question: " + e.toString() );
}
return null;
}

@JSON( include = false )
public static Question fromURL( String url, String adapterID, String remoteID, String fromID )
{
Expand Down Expand Up @@ -143,25 +161,15 @@ public String toJSON() {
@JsonIgnore
public String toJSON( boolean expanded_texts )
{
if ( ServerUtils.isInUnitTestingEnvironment() )
try
{
try
{
return ServerUtils.serialize( this );
}
catch ( Exception e )
{
e.printStackTrace();
return null;
}
return ServerUtils.serialize( this );
}
else
catch ( Exception e )
{
return new JSONSerializer()
.exclude( "*.class" )
.transform( new QuestionTextTransformer( expanded_texts ), "question_text", "question_expandedtext",
"answer_text", "answer_expandedtext", "answers.answer_text", "answers.answer_expandedtext" )
.include( "answers", "event_callbacks" ).serialize( this );
e.printStackTrace();
log.severe( "Question serialization failed. Message: " + e.getLocalizedMessage() );
return null;
}
}

Expand Down Expand Up @@ -193,7 +201,6 @@ public void generateIds() {
@JSON( include = false )
public Question answer( String responder, String adapterID, String answer_id, String answer_input, String sessionKey )
{
Client client = ParallelInit.getClient();
boolean answered = false;
Answer answer = null;
if ( this.getType().equals( "open" ) )
Expand Down Expand Up @@ -291,7 +298,7 @@ else if(answer_input == null)
if ( newQ != null )
return newQ;

String retryLoadLimit = getMediaPropertyValue( MediumType.BROADSOFT, MediaPropertyKey.LENGTH );
String retryLoadLimit = getMediaPropertyValue( MediumType.BROADSOFT, MediaPropertyKey.RETRY_LIMIT );
Integer retryCount = getRetryCount( sessionKey );
if ( retryLoadLimit != null && retryCount != null && retryCount < Integer.parseInt( retryLoadLimit ) )
{
Expand Down Expand Up @@ -341,6 +348,7 @@ else if( sessionKey != null )
flushRetryCount( sessionKey );
}
// Send answer to answer.callback.
Client client = ParallelInit.getClient();
WebResource webResource = client.resource( answer.getCallback() );
AnswerPost ans = new AnswerPost( this.getQuestion_id(), answer.getAnswer_id(), answer_input, responder );
// Check if answer.callback gives new question for this dialog
Expand Down Expand Up @@ -523,14 +531,10 @@ public void setEvent_callbacks(ArrayList<EventCallback> event_callbacks) {
question.setEvent_callbacks(event_callbacks);
}

@JSON(include = false)
@JsonIgnore
public String getPreferred_language() {
return preferred_language;
}

@JSON(include = false)
@JsonIgnore
public void setPreferred_language(String preferred_language) {
this.preferred_language = preferred_language;
}
Expand All @@ -556,10 +560,17 @@ public void setTrackingToken(String token) {
this.question.setTrackingToken(token);
}

@JsonProperty("media_properties")
public Collection<MediaProperty> getMedia_properties()
{
return media_properties;
}

@JsonProperty("media_properties")
public void setMedia_Properties( Collection<MediaProperty> media_properties )
{
this.media_properties = media_properties;
}

@JSON(include = false)
public Map<MediaPropertyKey, String> getMediaPropertyByType( MediumType type ) {
Expand Down Expand Up @@ -587,11 +598,6 @@ public String getMediaPropertyValue( MediumType type, MediaPropertyKey key) {
return null;
}

public void setMedia_Properties( Collection<MediaProperty> media_properties )
{
this.media_properties = media_properties;
}

public void addMedia_Properties( MediaProperty mediaProperty )
{
media_properties = media_properties == null ? new ArrayList<MediaProperty>() : media_properties;
Expand Down
Loading

0 comments on commit 0360ca0

Please sign in to comment.