1 /*
2  * mod_ssml for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
3  * Copyright (C) 2013-2014,2016 Grasshopper
4  *
5  * Version: MPL 1.1
6  *
7  * The contents of this file are subject to the Mozilla Public License Version
8  * 1.1 (the "License"); you may not use this file except in compliance with
9  * the License. You may obtain a copy of the License at
10  * http://www.mozilla.org/MPL/
11  *
12  * Software distributed under the License is distributed on an "AS IS" basis,
13  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14  * for the specific language governing rights and limitations under the
15  * License.
16  *
17  * The Original Code is mod_ssml for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
18  *
19  * The Initial Developer of the Original Code is Grasshopper
20  * Portions created by the Initial Developer are Copyright (C)
21  * the Initial Developer. All Rights Reserved.
22  *
23  * Contributor(s):
24  * Chris Rienzo <chris.rienzo@grasshopper.com>
25  *
26  * mod_ssml.c -- SSML audio rendering format
27  *
28  */
29 #include <switch.h>
30 #include <iksemel.h>
31 
32 SWITCH_MODULE_LOAD_FUNCTION(mod_ssml_load);
33 SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_ssml_shutdown);
34 SWITCH_MODULE_DEFINITION(mod_ssml, mod_ssml_load, mod_ssml_shutdown, NULL);
35 
36 #define MAX_VOICE_FILES 256
37 #define MAX_VOICE_PRIORITY 999
38 #define VOICE_NAME_PRIORITY 1000
39 #define VOICE_GENDER_PRIORITY 1000
40 #define VOICE_LANG_PRIORITY 1000000
41 
42 struct ssml_parser;
43 
44 /** function to handle tag attributes */
45 typedef int (* tag_attribs_fn)(struct ssml_parser *, char **);
46 /** function to handle tag CDATA */
47 typedef int (* tag_cdata_fn)(struct ssml_parser *, char *, size_t);
48 
49 /**
50  * Tag definition
51  */
52 struct tag_def {
53 	tag_attribs_fn attribs_fn;
54 	tag_cdata_fn cdata_fn;
55 	switch_bool_t is_root;
56 	switch_hash_t *children_tags;
57 };
58 
59 /**
60  * Module configuration
61  */
62 static struct {
63 	/** Mapping of mod-name-language-gender to voice */
64 	switch_hash_t *voice_cache;
65 	/** Mapping of voice names */
66 	switch_hash_t *say_voice_map;
67 	/** Synchronizes access to say_voice_map */
68 	switch_mutex_t *say_voice_map_mutex;
69 	/** Mapping of voice names */
70 	switch_hash_t *tts_voice_map;
71 	/** Synchronizes access to tts_voice_map */
72 	switch_mutex_t *tts_voice_map_mutex;
73 	/** Mapping of interpret-as value to macro */
74 	switch_hash_t *interpret_as_map;
75 	/** Mapping of ISO language code to say-module */
76 	switch_hash_t *language_map;
77 	/** Mapping of tag name to definition */
78 	switch_hash_t *tag_defs;
79 	/** module memory pool */
80 	switch_memory_pool_t *pool;
81 } globals;
82 
83 /**
84  * A say language
85  */
86 struct language {
87 	/** The ISO language code */
88 	char *iso;
89 	/** The FreeSWITCH language code */
90 	char *language;
91 	/** The say module name */
92 	char *say_module;
93 };
94 
95 /**
96  * A say macro
97  */
98 struct macro {
99 	/** interpret-as name (cardinal...) */
100 	char *name;
101 	/** language (en-US, en-UK, ...) */
102 	char *language;
103 	/** type (number, items, persons, messages...) */
104 	char *type;
105 	/** method (pronounced, counted, iterated...) */
106 	char *method;
107 };
108 
109 /**
110  * A TTS voice
111  */
112 struct voice {
113 	/** higher priority = more likely to pick */
114 	int priority;
115 	/** voice gender */
116 	char *gender;
117 	/** voice name / macro */
118 	char *name;
119 	/** voice language */
120 	char *language;
121 	/** internal file prefix */
122 	char *prefix;
123 };
124 
125 #define TAG_LEN 32
126 #define NAME_LEN 128
127 #define LANGUAGE_LEN 6
128 #define GENDER_LEN 8
129 
130 /**
131  * SSML voice state
132  */
133 struct ssml_node {
134 	/** tag name */
135 	char tag_name[TAG_LEN];
136 	/** requested name */
137 	char name[NAME_LEN];
138 	/** requested language */
139 	char language[LANGUAGE_LEN];
140 	/** requested gender */
141 	char gender[GENDER_LEN];
142 	/** voice to use */
143 	struct voice *tts_voice;
144 	/** say macro to use */
145 	struct macro *say_macro;
146 	/** tag handling data */
147 	struct tag_def *tag_def;
148 	/** previous node */
149 	struct ssml_node *parent_node;
150 };
151 
152 /**
153  * A file to play
154  */
155 struct ssml_file {
156 	/** prefix to add to file handle */
157 	char *prefix;
158 	/** the file to play */
159 	const char *name;
160 };
161 
162 /**
163  * SSML parser state
164  */
165 struct ssml_parser {
166 	/** current attribs */
167 	struct ssml_node *cur_node;
168 	/** files to play */
169 	struct ssml_file *files;
170 	/** number of files */
171 	int num_files;
172 	/** max files to play */
173 	int max_files;
174 	/** memory pool to use */
175 	switch_memory_pool_t *pool;
176 	/** desired sample rate */
177 	int sample_rate;
178 };
179 
180 /**
181  * SSML playback state
182  */
183 struct ssml_context {
184 	/** handle to current file */
185 	switch_file_handle_t fh;
186 	/** files to play */
187 	struct ssml_file *files;
188 	/** number of files */
189 	int num_files;
190 	/** current file being played */
191 	int index;
192 };
193 
194 /**
195  * Add a definition for a tag
196  * @param tag the name
197  * @param attribs_fn the function to handle the tag attributes
198  * @param cdata_fn the function to handler the tag CDATA
199  * @param children_tags comma-separated list of valid child tag names
200  * @return the definition
201  */
add_tag_def(const char * tag,tag_attribs_fn attribs_fn,tag_cdata_fn cdata_fn,const char * children_tags)202 static struct tag_def *add_tag_def(const char *tag, tag_attribs_fn attribs_fn, tag_cdata_fn cdata_fn, const char *children_tags)
203 {
204 	struct tag_def *def = switch_core_alloc(globals.pool, sizeof(*def));
205 	switch_core_hash_init(&def->children_tags);
206 	if (!zstr(children_tags)) {
207 		char *children_tags_dup = switch_core_strdup(globals.pool, children_tags);
208 		char *tags[32] = { 0 };
209 		int tag_count = switch_separate_string(children_tags_dup, ',', tags, sizeof(tags) / sizeof(tags[0]));
210 		if (tag_count) {
211 			int i;
212 			for (i = 0; i < tag_count; i++) {
213 				switch_core_hash_insert(def->children_tags, tags[i], tags[i]);
214 			}
215 		}
216 	}
217 	def->attribs_fn = attribs_fn;
218 	def->cdata_fn = cdata_fn;
219 	def->is_root = SWITCH_FALSE;
220 	switch_core_hash_insert(globals.tag_defs, tag, def);
221 	return def;
222 }
223 
224 /**
225  * Add a definition for a root tag
226  * @param tag the name
227  * @param attribs_fn the function to handle the tag attributes
228  * @param cdata_fn the function to handler the tag CDATA
229  * @param children_tags comma-separated list of valid child tag names
230  * @return the definition
231  */
add_root_tag_def(const char * tag,tag_attribs_fn attribs_fn,tag_cdata_fn cdata_fn,const char * children_tags)232 static struct tag_def *add_root_tag_def(const char *tag, tag_attribs_fn attribs_fn, tag_cdata_fn cdata_fn, const char *children_tags)
233 {
234 	struct tag_def *def = add_tag_def(tag, attribs_fn, cdata_fn, children_tags);
235 	def->is_root = SWITCH_TRUE;
236 	return def;
237 }
238 
239 /**
240  * Handle tag attributes
241  * @param parser the parser
242  * @param name the tag name
243  * @param atts the attributes
244  * @return IKS_OK if OK IKS_BADXML on parse failure
245  */
process_tag(struct ssml_parser * parser,const char * name,char ** atts)246 static int process_tag(struct ssml_parser *parser, const char *name, char **atts)
247 {
248 	struct tag_def *def = switch_core_hash_find(globals.tag_defs, name);
249 	if (def) {
250 		parser->cur_node->tag_def = def;
251 		if (def->is_root && parser->cur_node->parent_node == NULL) {
252 			/* no parent for ROOT tags */
253 			return def->attribs_fn(parser, atts);
254 		} else if (!def->is_root && parser->cur_node->parent_node) {
255 			/* check if this child is allowed by parent node */
256 			struct tag_def *parent_def = parser->cur_node->parent_node->tag_def;
257 			if (switch_core_hash_find(parent_def->children_tags, "ANY") ||
258 				switch_core_hash_find(parent_def->children_tags, name)) {
259 				return def->attribs_fn(parser, atts);
260 			}
261 		}
262 	}
263 	return IKS_BADXML;
264 }
265 
266 /**
267  * Handle CDATA that is ignored
268  * @param parser the parser
269  * @param data the CDATA
270  * @param len the CDATA length
271  * @return IKS_OK
272  */
process_cdata_ignore(struct ssml_parser * parser,char * data,size_t len)273 static int process_cdata_ignore(struct ssml_parser *parser, char *data, size_t len)
274 {
275 	return IKS_OK;
276 }
277 
278 /**
279  * Handle CDATA that is not allowed
280  * @param parser the parser
281  * @param data the CDATA
282  * @param len the CDATA length
283  * @return IKS_BADXML
284  */
process_cdata_bad(struct ssml_parser * parser,char * data,size_t len)285 static int process_cdata_bad(struct ssml_parser *parser, char *data, size_t len)
286 {
287 	int i;
288 	for (i = 0; i < len; i++) {
289 		if (isgraph(data[i])) {
290 			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Unexpected CDATA for <%s>\n", parser->cur_node->tag_name);
291 			return IKS_BADXML;
292 		}
293 	}
294 	return IKS_OK;
295 }
296 
297 /**
298  * Score the voice on how close it is to desired language, name, and gender
299  * @param voice the voice to score
300  * @param cur_node the desired voice attributes
301  * @param lang_required if true, language must match
302  * @return the score
303  */
score_voice(struct voice * voice,struct ssml_node * cur_node,int lang_required)304 static int score_voice(struct voice *voice, struct ssml_node *cur_node, int lang_required)
305 {
306 	/* language > gender,name > priority */
307 	int score = voice->priority;
308 	if (!zstr_buf(cur_node->gender) && !strcmp(cur_node->gender, voice->gender)) {
309 		score += VOICE_GENDER_PRIORITY;
310 	}
311 	if (!zstr_buf(cur_node->name) && !strcmp(cur_node->name, voice->name)) {
312 		score += VOICE_NAME_PRIORITY;
313 	}
314 	if (!zstr_buf(cur_node->language) && !strcmp(cur_node->language, voice->language)) {
315 		score += VOICE_LANG_PRIORITY;
316 	} else if (lang_required) {
317 		score = 0;
318 	}
319 	return score;
320 }
321 
322 /**
323  * Search for best voice based on attributes
324  * @param cur_node the desired voice attributes
325  * @param map the map to search
326  * @param type "say" or "tts"
327  * @param lang_required if true, language must match
328  * @return the voice or NULL
329  */
find_voice(struct ssml_node * cur_node,switch_hash_t * map,char * type,int lang_required)330 static struct voice *find_voice(struct ssml_node *cur_node, switch_hash_t *map, char *type, int lang_required)
331 {
332 	switch_hash_index_t *hi = NULL;
333 	struct voice *voice = NULL;
334 	char *lang_name_gender = NULL;
335 	int best_score = 0;
336 
337 	/* check cache */
338 	lang_name_gender = switch_mprintf("%s-%s-%s-%s", type, cur_node->language, cur_node->name, cur_node->gender);
339 	voice = (struct voice *)switch_core_hash_find(globals.voice_cache, lang_name_gender);
340 	if (voice) {
341 		/* that was easy! */
342 		goto done;
343 	}
344 
345 	/* find best language, name, gender match */
346 	for (hi = switch_core_hash_first(map); hi; hi = switch_core_hash_next(&hi)) {
347 		const void *key;
348 		void *val;
349 		struct voice *candidate;
350 		int candidate_score = 0;
351 		switch_core_hash_this(hi, &key, NULL, &val);
352 		candidate = (struct voice *)val;
353 		candidate_score = score_voice(candidate, cur_node, lang_required);
354 		if (candidate_score > 0 && candidate_score > best_score) {
355 			voice = candidate;
356 			best_score = candidate_score;
357 		}
358 	}
359 
360 	/* remember for next time */
361 	if (voice) {
362 		switch_core_hash_insert(globals.voice_cache, lang_name_gender, voice);
363 	}
364 
365 done:
366 	switch_safe_free(lang_name_gender);
367 
368 	return voice;
369 }
370 
371 /**
372  * Search for best voice based on attributes
373  * @param cur_node the desired voice attributes
374  * @return the voice or NULL
375  */
find_tts_voice(struct ssml_node * cur_node)376 static struct voice *find_tts_voice(struct ssml_node *cur_node)
377 {
378 	struct voice *v;
379 	switch_mutex_lock(globals.tts_voice_map_mutex);
380 	v = find_voice(cur_node, globals.tts_voice_map, "tts", 0);
381 	switch_mutex_unlock(globals.tts_voice_map_mutex);
382 	return v;
383 }
384 
385 /**
386  * Search for best voice based on attributes
387  * @param cur_node the desired voice attributes
388  * @return the voice or NULL
389  */
find_say_voice(struct ssml_node * cur_node)390 static struct voice *find_say_voice(struct ssml_node *cur_node)
391 {
392 	struct voice *v;
393 	switch_mutex_lock(globals.say_voice_map_mutex);
394 	v = find_voice(cur_node, globals.say_voice_map, "say", 1);
395 	switch_mutex_unlock(globals.say_voice_map_mutex);
396 	return v;
397 }
398 
399 /**
400  * Handle tag attributes that are ignored
401  * @param parser the parser
402  * @param atts the attributes
403  * @return IKS_OK
404  */
process_attribs_ignore(struct ssml_parser * parsed_data,char ** atts)405 static int process_attribs_ignore(struct ssml_parser *parsed_data, char **atts)
406 {
407 	struct ssml_node *cur_node = parsed_data->cur_node;
408 	cur_node->tts_voice = find_tts_voice(cur_node);
409 	return IKS_OK;
410 }
411 
412 /**
413  * open next file for reading
414  * @param handle the file handle
415  */
next_file(switch_file_handle_t * handle)416 static switch_status_t next_file(switch_file_handle_t *handle)
417 {
418 	struct ssml_context *context = handle->private_info;
419 	const char *file;
420 
421   top:
422 
423 	context->index++;
424 
425 	if (switch_test_flag((&context->fh), SWITCH_FILE_OPEN)) {
426 		switch_core_file_close(&context->fh);
427 	}
428 
429 	if (context->index >= context->num_files) {
430 		return SWITCH_STATUS_FALSE;
431 	}
432 
433 
434 	file = context->files[context->index].name;
435 	context->fh.prefix = context->files[context->index].prefix;
436 
437 	if (switch_test_flag(handle, SWITCH_FILE_FLAG_WRITE)) {
438 		/* unsupported */
439 		return SWITCH_STATUS_FALSE;
440 	}
441 
442 	if (switch_core_file_open(&context->fh, file, handle->channels, handle->samplerate, handle->flags, NULL) != SWITCH_STATUS_SUCCESS) {
443 		goto top;
444 	}
445 
446 	handle->samples = context->fh.samples;
447 	handle->format = context->fh.format;
448 	handle->sections = context->fh.sections;
449 	handle->seekable = context->fh.seekable;
450 	handle->speed = context->fh.speed;
451 	handle->interval = context->fh.interval;
452 
453 	if (switch_test_flag((&context->fh), SWITCH_FILE_NATIVE)) {
454 		switch_set_flag_locked(handle, SWITCH_FILE_NATIVE);
455 	} else {
456 		switch_clear_flag_locked(handle, SWITCH_FILE_NATIVE);
457 	}
458 
459 	return SWITCH_STATUS_SUCCESS;
460 }
461 
462 /**
463  * Process xml:lang attribute
464  */
process_xml_lang(struct ssml_parser * parsed_data,char ** atts)465 static int process_xml_lang(struct ssml_parser *parsed_data, char **atts)
466 {
467 	struct ssml_node *cur_node = parsed_data->cur_node;
468 
469 	/* only allow language change in <speak>, <p>, and <s> */
470 	if (atts) {
471 		int i = 0;
472 		while (atts[i]) {
473 			if (!strcmp("xml:lang", atts[i])) {
474 				if (!zstr(atts[i + 1])) {
475 					strncpy(cur_node->language, atts[i + 1], LANGUAGE_LEN);
476 					cur_node->language[LANGUAGE_LEN - 1] = '\0';
477 				}
478 			}
479 			i += 2;
480 		}
481 	}
482 	cur_node->tts_voice = find_tts_voice(cur_node);
483 	return IKS_OK;
484 }
485 
486 /**
487  * Process <voice>
488  */
process_voice(struct ssml_parser * parsed_data,char ** atts)489 static int process_voice(struct ssml_parser *parsed_data, char **atts)
490 {
491 	struct ssml_node *cur_node = parsed_data->cur_node;
492 	if (atts) {
493 		int i = 0;
494 		while (atts[i]) {
495 			if (!strcmp("xml:lang", atts[i])) {
496 				if (!zstr(atts[i + 1])) {
497 					strncpy(cur_node->language, atts[i + 1], LANGUAGE_LEN);
498 					cur_node->language[LANGUAGE_LEN - 1] = '\0';
499 				}
500 			} else if (!strcmp("name", atts[i])) {
501 				if (!zstr(atts[i + 1])) {
502 					strncpy(cur_node->name, atts[i + 1], NAME_LEN);
503 					cur_node->name[NAME_LEN - 1] = '\0';
504 				}
505 			} else if (!strcmp("gender", atts[i])) {
506 				if (!zstr(atts[i + 1])) {
507 					strncpy(cur_node->gender, atts[i + 1], GENDER_LEN);
508 					cur_node->gender[GENDER_LEN - 1] = '\0';
509 				}
510 			}
511 			i += 2;
512 		}
513 	}
514 	cur_node->tts_voice = find_tts_voice(cur_node);
515 	return IKS_OK;
516 }
517 
518 /**
519  * Process <say-as>
520  */
process_say_as(struct ssml_parser * parsed_data,char ** atts)521 static int process_say_as(struct ssml_parser *parsed_data, char **atts)
522 {
523 	struct ssml_node *cur_node = parsed_data->cur_node;
524 	if (atts) {
525 		int i = 0;
526 		while (atts[i]) {
527 			if (!strcmp("interpret-as", atts[i])) {
528 				char *interpret_as = atts[i + 1];
529 				if (!zstr(interpret_as)) {
530 					switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "interpret-as: %s\n", atts[i + 1]);
531 					cur_node->say_macro = (struct macro *)switch_core_hash_find(globals.interpret_as_map, interpret_as);
532 				}
533 				break;
534 			}
535 			i += 2;
536 		}
537 	}
538 	cur_node->tts_voice = find_tts_voice(cur_node);
539 	return IKS_OK;
540 }
541 
542 /**
543  * Process <break>- this is a period of silence
544  */
process_break(struct ssml_parser * parsed_data,char ** atts)545 static int process_break(struct ssml_parser *parsed_data, char **atts)
546 {
547 	if (atts) {
548 		int i = 0;
549 		while (atts[i]) {
550 			if (!strcmp("time", atts[i])) {
551 				char *t = atts[i + 1];
552 				if (!zstr(t) && parsed_data->num_files < parsed_data->max_files) {
553 					int timeout_ms = 0;
554 					char *unit;
555 					if ((unit = strstr(t, "ms"))) {
556 						*unit = '\0';
557 						if (switch_is_number(t)) {
558 							timeout_ms = atoi(t);
559 						}
560 					} else if ((unit = strstr(t, "s"))) {
561 						*unit = '\0';
562 						if (switch_is_number(t)) {
563 							timeout_ms = atoi(t) * 1000;
564 						}
565 					}
566 					if (timeout_ms > 0) {
567 						switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Adding <break>: \"%s\"\n", t);
568 						parsed_data->files[parsed_data->num_files].name = switch_core_sprintf(parsed_data->pool, "silence_stream://%i", timeout_ms);
569 						parsed_data->files[parsed_data->num_files++].prefix = NULL;
570 					}
571 				}
572 				return IKS_OK;
573 			}
574 			i += 2;
575 		}
576 	}
577 	return IKS_OK;
578 }
579 
580 /**
581  * Process <audio>- this is a URL to play
582  */
process_audio(struct ssml_parser * parsed_data,char ** atts)583 static int process_audio(struct ssml_parser *parsed_data, char **atts)
584 {
585 	if (atts) {
586 		int i = 0;
587 		while (atts[i]) {
588 			if (!strcmp("src", atts[i])) {
589 				char *src = atts[i + 1];
590 				ikstack *stack = NULL;
591 				if (!zstr(src) && parsed_data->num_files < parsed_data->max_files) {
592 					/* get the URI */
593 					if (strchr(src, '&')) {
594 						stack = iks_stack_new(256, 0);
595 						src = iks_unescape(stack, src, strlen(src));
596 					}
597 					switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Adding <audio>: \"%s\"\n", src);
598 					parsed_data->files[parsed_data->num_files].name = switch_core_strdup(parsed_data->pool, src);
599 					parsed_data->files[parsed_data->num_files++].prefix = NULL;
600 					if (stack) {
601 						iks_stack_delete(&stack);
602 					}
603 				}
604 				return IKS_OK;
605 			}
606 			i += 2;
607 		}
608 	}
609 	return IKS_OK;
610 }
611 
612 /**
613  * Process a tag
614  */
tag_hook(void * user_data,char * name,char ** atts,int type)615 static int tag_hook(void *user_data, char *name, char **atts, int type)
616 {
617 	int result = IKS_OK;
618 	struct ssml_parser *parsed_data = (struct ssml_parser *)user_data;
619 	struct ssml_node *parent_node = parsed_data->cur_node;
620 
621 	if (type == IKS_OPEN || type == IKS_SINGLE) {
622 		struct ssml_node *new_node = malloc(sizeof *new_node);
623 		switch_assert(new_node);
624 		if (parent_node) {
625 			/* inherit parent attribs */
626 			*new_node = *parent_node;
627 			new_node->parent_node = parent_node;
628 		} else {
629 			new_node->name[0] = '\0';
630 			new_node->language[0] = '\0';
631 			new_node->gender[0] = '\0';
632 			new_node->parent_node = NULL;
633 		}
634 		new_node->tts_voice = NULL;
635 		new_node->say_macro = NULL;
636 		strncpy(new_node->tag_name, name, TAG_LEN);
637 		new_node->tag_name[TAG_LEN - 1] = '\0';
638 		parsed_data->cur_node = new_node;
639 		result = process_tag(parsed_data, name, atts);
640 	}
641 
642 	if (type == IKS_CLOSE || type == IKS_SINGLE) {
643 		if (parsed_data->cur_node) {
644 			struct ssml_node *parent_node = parsed_data->cur_node->parent_node;
645 			free(parsed_data->cur_node);
646 			parsed_data->cur_node = parent_node;
647 		}
648 	}
649 
650 	return result;
651 }
652 
653 /**
654  * Try to get file(s) from say module
655  * @param parsed_data
656  * @param to_say
657  * @return 1 if successful
658  */
get_file_from_macro(struct ssml_parser * parsed_data,char * to_say)659 static int get_file_from_macro(struct ssml_parser *parsed_data, char *to_say)
660 {
661 	struct ssml_node *cur_node = parsed_data->cur_node;
662 	struct macro *say_macro = cur_node->say_macro;
663 	struct voice *say_voice = find_say_voice(cur_node);
664 	struct language *language;
665 	char *file_string = NULL;
666 	char *gender = NULL;
667 	switch_say_interface_t *si;
668 
669 	/* voice is required */
670 	if (!say_voice) {
671 		return 0;
672 	}
673 
674 	language = switch_core_hash_find(globals.language_map, say_voice->language);
675 	/* language is required */
676 	if (!language) {
677 		return 0;
678 	}
679 
680 	/* TODO need to_say gender, not voice gender */
681 	gender = "neuter";
682 
683 	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Trying macro: %s, %s, %s, %s, %s\n", language->language, to_say, say_macro->type, say_macro->method, gender);
684 
685 	if ((si = switch_loadable_module_get_say_interface(language->say_module)) && si->say_string_function) {
686 		switch_say_args_t say_args = {0};
687 		say_args.type = switch_ivr_get_say_type_by_name(say_macro->type);
688 		say_args.method = switch_ivr_get_say_method_by_name(say_macro->method);
689 		say_args.gender = switch_ivr_get_say_gender_by_name(gender);
690 		say_args.ext = "wav";
691 		si->say_string_function(NULL, to_say, &say_args, &file_string);
692 	}
693 	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Adding macro: \"%s\", prefix=\"%s\"\n", file_string, say_voice->prefix);
694 	if (!zstr(file_string)) {
695 		parsed_data->files[parsed_data->num_files].name = switch_core_strdup(parsed_data->pool, file_string);
696 		parsed_data->files[parsed_data->num_files++].prefix = switch_core_strdup(parsed_data->pool, say_voice->prefix);
697 		return 1;
698 	}
699 	switch_safe_free(file_string);
700 
701 	return 0;
702 }
703 
704 /**
705  * Get TTS file for voice
706  */
get_file_from_voice(struct ssml_parser * parsed_data,char * to_say)707 static int get_file_from_voice(struct ssml_parser *parsed_data, char *to_say)
708 {
709 	struct ssml_node *cur_node = parsed_data->cur_node;
710 	if (cur_node->tts_voice) {
711 		char *file = switch_core_sprintf(parsed_data->pool, "%s%s", cur_node->tts_voice->prefix, to_say);
712 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Adding <%s>: \"%s\"\n", cur_node->tag_name, file);
713 		parsed_data->files[parsed_data->num_files].name = file;
714 		parsed_data->files[parsed_data->num_files++].prefix = NULL;
715 		return 1;
716 	}
717 	return 0;
718 }
719 
720 /**
721  * Get TTS from CDATA
722  */
process_cdata_tts(struct ssml_parser * parsed_data,char * data,size_t len)723 static int process_cdata_tts(struct ssml_parser *parsed_data, char *data, size_t len)
724 {
725 	struct ssml_node *cur_node = parsed_data->cur_node;
726 	if (!len) {
727 		return IKS_OK;
728 	}
729 	if (cur_node && parsed_data->num_files < parsed_data->max_files) {
730 		int i = 0;
731 		int empty = 1;
732 		char *to_say;
733 
734 		/* is CDATA empty? */
735 		for (i = 0; i < len && empty; i++) {
736 			empty &= !isgraph(data[i]);
737 		}
738 		if (empty) {
739 			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Skipping empty tts\n");
740 			return IKS_OK;
741 		}
742 
743 		/* try macro */
744 		to_say = malloc(len + 1);
745 		switch_assert(to_say);
746 		strncpy(to_say, data, len);
747 		to_say[len] = '\0';
748 		if (!cur_node->say_macro || !get_file_from_macro(parsed_data, to_say)) {
749 			/* use voice instead */
750 			if (!get_file_from_voice(parsed_data, to_say)) {
751 				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "No TTS voices available to render text!\n");
752 			}
753 		}
754 		free(to_say);
755 		return IKS_OK;
756 	}
757 	return IKS_BADXML;
758 }
759 
760 /**
761  * Process <sub>- this is an alias for text to speak
762  */
process_sub(struct ssml_parser * parsed_data,char ** atts)763 static int process_sub(struct ssml_parser *parsed_data, char **atts)
764 {
765 	if (atts) {
766 		int i = 0;
767 		while (atts[i]) {
768 			if (!strcmp("alias", atts[i])) {
769 				char *alias = atts[i + 1];
770 				if (!zstr(alias)) {
771 					return process_cdata_tts(parsed_data, alias, strlen(alias));
772 				}
773 				return IKS_BADXML;
774 			}
775 			i += 2;
776 		}
777 	}
778 	return IKS_OK;
779 }
780 
781 /**
782  * Process cdata
783  */
cdata_hook(void * user_data,char * data,size_t len)784 static int cdata_hook(void *user_data, char *data, size_t len)
785 {
786 	struct ssml_parser *parsed_data = (struct ssml_parser *)user_data;
787 	if (!parsed_data) {
788 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Missing parser\n");
789 		return IKS_BADXML;
790 	}
791 	if (parsed_data->cur_node) {
792 		struct tag_def *handler = switch_core_hash_find(globals.tag_defs, parsed_data->cur_node->tag_name);
793 		if (handler) {
794 			return handler->cdata_fn(parsed_data, data, len);
795 		}
796 		return IKS_BADXML;
797 	}
798 	return IKS_OK;
799 }
800 
801 /**
802  * Transforms SSML into file_string format and opens file_string.
803  * @param handle
804  * @param path the inline SSML
805  * @return SWITCH_STATUS_SUCCESS if opened
806  */
ssml_file_open(switch_file_handle_t * handle,const char * path)807 static switch_status_t ssml_file_open(switch_file_handle_t *handle, const char *path)
808 {
809 	switch_status_t status = SWITCH_STATUS_FALSE;
810 	struct ssml_context *context = switch_core_alloc(handle->memory_pool, sizeof(*context));
811 	struct ssml_parser *parsed_data = switch_core_alloc(handle->memory_pool, sizeof(*parsed_data));
812 	iksparser *parser = iks_sax_new(parsed_data, tag_hook, cdata_hook);
813 
814 	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Open: %s\n", path);
815 
816 	parsed_data->cur_node = NULL;
817 	parsed_data->files = switch_core_alloc(handle->memory_pool, sizeof(struct ssml_file) * MAX_VOICE_FILES);
818 	parsed_data->max_files = MAX_VOICE_FILES;
819 	parsed_data->num_files = 0;
820 	parsed_data->pool = handle->memory_pool;
821 	parsed_data->sample_rate = handle->samplerate;
822 
823 	if (iks_parse(parser, path, 0, 1) == IKS_OK) {
824 		if (parsed_data->num_files) {
825 			context->files = parsed_data->files;
826 			context->num_files = parsed_data->num_files;
827 			context->index = -1;
828 			handle->private_info = context;
829 			status = next_file(handle);
830 		} else {
831 			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "No files to play: %s\n", path);
832 		}
833 	} else {
834 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Parse error: %s, num_files = %i\n", path, parsed_data->num_files);
835 	}
836 
837 	iks_parser_delete(parser);
838 
839 	return status;
840 }
841 
842 /**
843  * Close SSML document.
844  * @param handle
845  * @return SWITCH_STATUS_SUCCESS
846  */
ssml_file_close(switch_file_handle_t * handle)847 static switch_status_t ssml_file_close(switch_file_handle_t *handle)
848 {
849 	struct ssml_context *context = (struct ssml_context *)handle->private_info;
850 	if (switch_test_flag((&context->fh), SWITCH_FILE_OPEN)) {
851 		return switch_core_file_close(&context->fh);
852 	}
853 
854 	return SWITCH_STATUS_SUCCESS;
855 }
856 
857 /**
858  * Read from SSML document
859  * @param handle
860  * @param data
861  * @param len
862  * @return
863  */
ssml_file_read(switch_file_handle_t * handle,void * data,size_t * len)864 static switch_status_t ssml_file_read(switch_file_handle_t *handle, void *data, size_t *len)
865 {
866 	switch_status_t status;
867 	struct ssml_context *context = (struct ssml_context *)handle->private_info;
868 	size_t llen = *len;
869 
870 	status = switch_core_file_read(&context->fh, data, len);
871 	if (status != SWITCH_STATUS_SUCCESS) {
872 		if ((status = next_file(handle)) != SWITCH_STATUS_SUCCESS) {
873 			return status;
874 		}
875 		*len = llen;
876 		status = switch_core_file_read(&context->fh, data, len);
877 	}
878 	return status;
879 }
880 
881 /**
882  * Seek file
883  */
ssml_file_seek(switch_file_handle_t * handle,unsigned int * cur_sample,int64_t samples,int whence)884 static switch_status_t ssml_file_seek(switch_file_handle_t *handle, unsigned int *cur_sample, int64_t samples, int whence)
885 {
886 	struct ssml_context *context = handle->private_info;
887 
888 	if (samples == 0 && whence == SWITCH_SEEK_SET) {
889 		/* restart from beginning */
890 		context->index = -1;
891 		return next_file(handle);
892 	}
893 
894 	if (!handle->seekable) {
895 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "File is not seekable\n");
896 		return SWITCH_STATUS_NOTIMPL;
897 	}
898 
899 	return switch_core_file_seek(&context->fh, cur_sample, samples, whence);
900 }
901 
902 /**
903  * TTS playback state
904  */
905 struct tts_context {
906 	/** handle to TTS engine */
907 	switch_speech_handle_t sh;
908 	/** TTS flags */
909 	switch_speech_flag_t flags;
910 	/** maximum number of samples to read at a time */
911 	int max_frame_size;
912 	/** done flag */
913 	int done;
914 };
915 
916 /**
917  * Do TTS as file format
918  * @param handle
919  * @param path the inline SSML
920  * @return SWITCH_STATUS_SUCCESS if opened
921  */
tts_file_open(switch_file_handle_t * handle,const char * path)922 static switch_status_t tts_file_open(switch_file_handle_t *handle, const char *path)
923 {
924 	switch_status_t status = SWITCH_STATUS_SUCCESS;
925 	struct tts_context *context = switch_core_alloc(handle->memory_pool, sizeof(*context));
926 	char *arg_string = switch_core_strdup(handle->memory_pool, path);
927 	char *args[3] = { 0 };
928 	int argc = switch_separate_string(arg_string, '|', args, (sizeof(args) / sizeof(args[0])));
929 	char *module;
930 	char *voice;
931 	char *document;
932 
933 	/* path is module:(optional)profile|voice|{param1=val1,param2=val2}TTS document */
934 	if (argc != 3) {
935 		return SWITCH_STATUS_FALSE;
936 	}
937 	module = args[0];
938 	voice = args[1];
939 	document = args[2];
940 
941 	memset(context, 0, sizeof(*context));
942 	context->flags = SWITCH_SPEECH_FLAG_NONE;
943 	if ((status = switch_core_speech_open(&context->sh, module, voice, handle->samplerate, handle->interval, handle->channels, &context->flags, NULL)) == SWITCH_STATUS_SUCCESS) {
944 		if ((status = switch_core_speech_feed_tts(&context->sh, document, &context->flags)) == SWITCH_STATUS_SUCCESS) {
945 			handle->channels = 1;
946 			handle->samples = 0;
947 			handle->format = 0;
948 			handle->sections = 0;
949 			handle->seekable = 0;
950 			handle->speed = 0;
951 			context->max_frame_size = handle->samplerate / 1000 * SWITCH_MAX_INTERVAL;
952 		} else {
953 			switch_core_speech_close(&context->sh, &context->flags);
954 		}
955 	}
956 	handle->private_info = context;
957 	return status;
958 }
959 
960 /**
961  * Read audio from TTS engine
962  * @param handle
963  * @param data
964  * @param len
965  * @return
966  */
tts_file_read(switch_file_handle_t * handle,void * data,size_t * len)967 static switch_status_t tts_file_read(switch_file_handle_t *handle, void *data, size_t *len)
968 {
969 	switch_status_t status = SWITCH_STATUS_SUCCESS;
970 	struct tts_context *context = (struct tts_context *)handle->private_info;
971 	switch_size_t rlen;
972 
973 	if (*len > context->max_frame_size) {
974 		*len = context->max_frame_size;
975 	}
976 	rlen = *len * 2; /* rlen (bytes) = len (samples) * 2 */
977 
978 	if (!context->done) {
979 		context->flags = SWITCH_SPEECH_FLAG_BLOCKING;
980 		if ((status = switch_core_speech_read_tts(&context->sh, data, &rlen, &context->flags))) {
981 			context->done = 1;
982 		}
983 	} else {
984 		switch_core_speech_flush_tts(&context->sh);
985 		memset(data, 0, rlen);
986 		status = SWITCH_STATUS_FALSE;
987 	}
988 	*len = rlen / 2; /* len (samples) = rlen (bytes) / 2 */
989 	return status;
990 }
991 
992 /**
993  * Close TTS engine
994  * @param handle
995  * @return SWITCH_STATUS_SUCCESS
996  */
tts_file_close(switch_file_handle_t * handle)997 static switch_status_t tts_file_close(switch_file_handle_t *handle)
998 {
999 	struct tts_context *context = (struct tts_context *)handle->private_info;
1000 	switch_core_speech_close(&context->sh, &context->flags);
1001 	return SWITCH_STATUS_SUCCESS;
1002 }
1003 
1004 /**
1005  * Configure voices
1006  * @param pool memory pool to use
1007  * @param map voice map to load
1008  * @param type type of voices (for logging)
1009  */
do_config_voices(switch_memory_pool_t * pool,switch_xml_t voices,switch_hash_t * map,const char * type)1010 static void do_config_voices(switch_memory_pool_t *pool, switch_xml_t voices, switch_hash_t *map, const char *type)
1011 {
1012 	if (voices) {
1013 		int priority = MAX_VOICE_PRIORITY;
1014 		switch_xml_t voice;
1015 		for (voice = switch_xml_child(voices, "voice"); voice; voice = voice->next) {
1016 			const char *name = switch_xml_attr_soft(voice, "name");
1017 			const char *language = switch_xml_attr_soft(voice, "language");
1018 			const char *gender = switch_xml_attr_soft(voice, "gender");
1019 			const char *prefix = switch_xml_attr_soft(voice, "prefix");
1020 			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s map (%s, %s, %s) = %s\n", type, name, language, gender, prefix);
1021 			if (!zstr(name) && !zstr(prefix)) {
1022 				struct voice *v = (struct voice *)switch_core_alloc(pool, sizeof(*v));
1023 				v->name = switch_core_strdup(pool, name);
1024 				v->language = switch_core_strdup(pool, language);
1025 				v->gender = switch_core_strdup(pool, gender);
1026 				v->prefix = switch_core_strdup(pool, prefix);
1027 				v->priority = priority--;
1028 				switch_core_hash_insert(map, name, v);
1029 			}
1030 		}
1031 	}
1032 }
1033 
1034 /**
1035  * Set default configuration when no XML configuration is present.
1036  * @param pool memory pool to use
1037  * @return SWITCH_STATUS_SUCCESS if module is configured
1038  */
do_default_config(switch_memory_pool_t * pool)1039 static switch_status_t do_default_config(switch_memory_pool_t *pool)
1040 {
1041 	struct voice *v = NULL;
1042 	struct language *l = NULL;
1043 	struct macro *m = NULL;
1044 	const char *sounds_dir = switch_core_get_variable("sounds_dir");
1045 
1046 	/* add TTS voice */
1047 	v = switch_core_alloc(pool, sizeof(*v));
1048 	v->name = "slt";
1049 	v->language = "en-US";
1050 	v->gender = "female";
1051 	v->prefix = "tts://flite|slt|";
1052 	v->priority = MAX_VOICE_PRIORITY;
1053 	switch_core_hash_insert(globals.tts_voice_map, "slt", v);
1054 
1055 	/* add Say voice */
1056 	v = switch_core_alloc(pool, sizeof(*v));
1057 	v->name = "callie";
1058 	v->language = "en-US";
1059 	v->gender = "female";
1060 	v->prefix = switch_core_sprintf(pool, "%s/en/us/callie/", sounds_dir ? sounds_dir : "");
1061 	switch_core_hash_insert(globals.say_voice_map, "callie", v);
1062 
1063 	/* Add ISO language to Say language mapping */
1064 	l = switch_core_alloc(pool, sizeof(*l));
1065 	l->iso = "en-US";
1066 	l->say_module = "en";
1067 	l->language = "en";
1068 	switch_core_hash_insert(globals.language_map, "en-US", l);
1069 
1070 	/* Map interpret-as to Say */
1071 	m = switch_core_alloc(pool, sizeof(*m));
1072 	m->name = "ordinal";
1073 	m->method = "counted";
1074 	m->type = "number";
1075 	switch_core_hash_insert(globals.interpret_as_map, "ordinal", m);
1076 
1077 	m = switch_core_alloc(pool, sizeof(*m));
1078 	m->name = "cardinal";
1079 	m->method = "pronounced";
1080 	m->type = "number";
1081 	switch_core_hash_insert(globals.interpret_as_map, "cardinal", m);
1082 
1083 	m = switch_core_alloc(pool, sizeof(*m));
1084 	m->name = "characters";
1085 	m->method = "pronounced";
1086 	m->type = "name_spelled";
1087 	switch_core_hash_insert(globals.interpret_as_map, "characters", m);
1088 
1089 	m = switch_core_alloc(pool, sizeof(*m));
1090 	m->name = "telephone";
1091 	m->method = "pronounced";
1092 	m->type = "telephone_number";
1093 	switch_core_hash_insert(globals.interpret_as_map, "telephone", m);
1094 
1095 	return SWITCH_STATUS_SUCCESS;
1096 }
1097 
1098 
1099 /**
1100  * Configure module
1101  * @param pool memory pool to use
1102  * @return SWITCH_STATUS_SUCCESS if module is configured
1103  */
do_config(switch_memory_pool_t * pool)1104 static switch_status_t do_config(switch_memory_pool_t *pool)
1105 {
1106 	char *cf = "ssml.conf";
1107 	switch_xml_t cfg, xml;
1108 
1109 	if (!(xml = switch_xml_open_cfg(cf, &cfg, NULL))) {
1110 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "open of %s failed, using default configuration\n", cf);
1111 		return do_default_config(pool);
1112 	}
1113 
1114 	/* get voices */
1115 	do_config_voices(pool, switch_xml_child(cfg, "tts-voices"), globals.tts_voice_map, "tts");
1116 	do_config_voices(pool, switch_xml_child(cfg, "say-voices"), globals.say_voice_map, "say");
1117 
1118 	/* get languages */
1119 	{
1120 		switch_xml_t languages = switch_xml_child(cfg, "language-map");
1121 		if (languages) {
1122 			switch_xml_t language;
1123 			for (language = switch_xml_child(languages, "language"); language; language = language->next) {
1124 				const char *iso = switch_xml_attr_soft(language, "iso");
1125 				const char *say_module = switch_xml_attr_soft(language, "say-module");
1126 				const char *lang = switch_xml_attr_soft(language, "language");
1127 				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "language map: %s = (%s, %s) \n", iso, say_module, lang);
1128 				if (!zstr(iso) && !zstr(say_module) && !zstr(lang)) {
1129 					struct language *l = (struct language *)switch_core_alloc(pool, sizeof(*l));
1130 					l->iso = switch_core_strdup(pool, iso);
1131 					l->say_module = switch_core_strdup(pool, say_module);
1132 					l->language = switch_core_strdup(pool, lang);
1133 					switch_core_hash_insert(globals.language_map, iso, l);
1134 				}
1135 			}
1136 		}
1137 	}
1138 
1139 	/* get macros */
1140 	{
1141 		switch_xml_t macros = switch_xml_child(cfg, "macros");
1142 		if (macros) {
1143 			switch_xml_t macro;
1144 			for (macro = switch_xml_child(macros, "macro"); macro; macro = macro->next) {
1145 				const char *name = switch_xml_attr_soft(macro, "name");
1146 				const char *method = switch_xml_attr_soft(macro, "method");
1147 				const char *type = switch_xml_attr_soft(macro, "type");
1148 				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "macro: %s = (%s, %s) \n", name, method, type);
1149 				if (!zstr(name) && !zstr(type)) {
1150 					struct macro *m = (struct macro *)switch_core_alloc(pool, sizeof(*m));
1151 					m->name = switch_core_strdup(pool, name);
1152 					m->method = switch_core_strdup(pool, method);
1153 					m->type = switch_core_strdup(pool, type);
1154 					switch_core_hash_insert(globals.interpret_as_map, name, m);
1155 				}
1156 			}
1157 		}
1158 	}
1159 
1160 	switch_xml_free(xml);
1161 
1162 	return SWITCH_STATUS_SUCCESS;
1163 }
1164 
1165 static char *ssml_supported_formats[] = { "ssml", NULL };
1166 static char *tts_supported_formats[] = { "tts", NULL };
1167 
SWITCH_MODULE_LOAD_FUNCTION(mod_ssml_load)1168 SWITCH_MODULE_LOAD_FUNCTION(mod_ssml_load)
1169 {
1170 	switch_file_interface_t *file_interface;
1171 
1172 	*module_interface = switch_loadable_module_create_module_interface(pool, modname);
1173 	file_interface = switch_loadable_module_create_interface(*module_interface, SWITCH_FILE_INTERFACE);
1174 	file_interface->interface_name = modname;
1175 	file_interface->extens = ssml_supported_formats;
1176 	file_interface->file_open = ssml_file_open;
1177 	file_interface->file_close = ssml_file_close;
1178 	file_interface->file_read = ssml_file_read;
1179 	file_interface->file_seek = ssml_file_seek;
1180 
1181 	file_interface = switch_loadable_module_create_interface(*module_interface, SWITCH_FILE_INTERFACE);
1182 	file_interface->interface_name = modname;
1183 	file_interface->extens = tts_supported_formats;
1184 	file_interface->file_open = tts_file_open;
1185 	file_interface->file_close = tts_file_close;
1186 	file_interface->file_read = tts_file_read;
1187 	/* TODO allow skip ahead if TTS supports it
1188 	 * file_interface->file_seek = tts_file_seek;
1189 	 */
1190 
1191 	globals.pool = pool;
1192 	switch_core_hash_init(&globals.voice_cache);
1193 	switch_core_hash_init(&globals.tts_voice_map);
1194 	switch_mutex_init(&globals.tts_voice_map_mutex, SWITCH_MUTEX_NESTED, pool);
1195 	switch_core_hash_init(&globals.say_voice_map);
1196 	switch_mutex_init(&globals.say_voice_map_mutex, SWITCH_MUTEX_NESTED, pool);
1197 	switch_core_hash_init(&globals.interpret_as_map);
1198 	switch_core_hash_init(&globals.language_map);
1199 	switch_core_hash_init(&globals.tag_defs);
1200 
1201 	add_root_tag_def("speak", process_xml_lang, process_cdata_tts, "audio,break,emphasis,mark,phoneme,prosody,say-as,voice,sub,p,s,lexicon,metadata,meta");
1202 	add_tag_def("p", process_xml_lang, process_cdata_tts, "audio,break,emphasis,mark,phoneme,prosody,say-as,voice,sub,s");
1203 	add_tag_def("s", process_xml_lang, process_cdata_tts, "audio,break,emphasis,mark,phoneme,prosody,say-as,voice,sub");
1204 	add_tag_def("voice", process_voice, process_cdata_tts, "audio,break,emphasis,mark,phoneme,prosody,say-as,voice,sub,p,s");
1205 	add_tag_def("prosody", process_attribs_ignore, process_cdata_tts, "audio,break,emphasis,mark,phoneme,prosody,say-as,voice,sub,p,s");
1206 	add_tag_def("audio", process_audio, process_cdata_tts, "audio,break,emphasis,mark,phoneme,prosody,say-as,voice,sub,p,s,desc");
1207 	add_tag_def("desc", process_attribs_ignore, process_cdata_ignore, "");
1208 	add_tag_def("emphasis", process_attribs_ignore, process_cdata_tts, "audio,break,emphasis,mark,phoneme,prosody,say-as,voice,sub");
1209 	add_tag_def("say-as", process_say_as, process_cdata_tts, "");
1210 	add_tag_def("sub", process_sub, process_cdata_ignore, "");
1211 	add_tag_def("phoneme", process_attribs_ignore, process_cdata_tts, "");
1212 	add_tag_def("break", process_break, process_cdata_bad, "");
1213 	add_tag_def("mark", process_attribs_ignore, process_cdata_bad, "");
1214 	add_tag_def("lexicon", process_attribs_ignore, process_cdata_bad, "");
1215 	add_tag_def("metadata", process_attribs_ignore, process_cdata_ignore, "ANY");
1216 	add_tag_def("meta", process_attribs_ignore, process_cdata_bad, "");
1217 
1218 	return do_config(pool);
1219 }
1220 
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_ssml_shutdown)1221 SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_ssml_shutdown)
1222 {
1223 	switch_core_hash_destroy(&globals.voice_cache);
1224 	switch_core_hash_destroy(&globals.tts_voice_map);
1225 	switch_core_hash_destroy(&globals.say_voice_map);
1226 	switch_core_hash_destroy(&globals.interpret_as_map);
1227 	switch_core_hash_destroy(&globals.language_map);
1228 	{
1229 		switch_hash_index_t *hi = NULL;
1230 		for (hi = switch_core_hash_first(globals.tag_defs); hi; hi = switch_core_hash_next(&hi)) {
1231 			const void *key;
1232 			struct tag_def *def;
1233 			switch_core_hash_this(hi, &key, NULL, (void *)&def);
1234 			switch_core_hash_destroy(&def->children_tags);
1235 		}
1236 	}
1237 	switch_core_hash_destroy(&globals.tag_defs);
1238 
1239 	return SWITCH_STATUS_SUCCESS;
1240 }
1241 
1242 /* For Emacs:
1243  * Local Variables:
1244  * mode:c
1245  * indent-tabs-mode:t
1246  * tab-width:4
1247  * c-basic-offset:4
1248  * End:
1249  * For VIM:
1250  * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet:
1251  */
1252