diff --git a/src/mod/applications/mod_rss/mod_rss.c b/src/mod/applications/mod_rss/mod_rss.c new file mode 100644 index 0000000000..610e8cc3a5 --- /dev/null +++ b/src/mod/applications/mod_rss/mod_rss.c @@ -0,0 +1,605 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005/2006, Anthony Minessale II + * + * Version: MPL 1.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * + * The Initial Developer of the Original Code is + * Anthony Minessale II + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Anthony Minessale II + * + * + * mod_rss.c -- RSS Browser + * + */ +#include + +static const char modname[] = "mod_rss"; + +typedef enum { + SFLAG_INSTRUCT = (1 << 0), + SFLAG_INFO = (1 << 1), + SFLAG_MAIN = (1 << 2) +} SFLAGS; + +/* helper object */ +struct dtmf_buffer { + int32_t index; + uint32_t flags; + int32_t speed; + char voice[80]; + switch_speech_handle_t *sh; +}; + +#define TTS_MEAN_SPEED 170 +#define TTS_MAX_ENTRIES 99 +#define TTS_DEFAULT_ENGINE "cepstral" +#define TTS_DEFAULT_VOICE "david" + +struct shashdot_entry { + uint8_t inuse; + char *title_txt; + char *description_txt; + char *subject_txt; + char *dept_txt; +}; + +/* + dtmf handler function you can hook up to be executed when a digit is dialed during playback + if you return anything but SWITCH_STATUS_SUCCESS the playback will stop. +*/ +static switch_status_t on_dtmf(switch_core_session_t *session, char *dtmf, void *buf, unsigned int buflen) +{ + + struct dtmf_buffer *dtb; + dtb = (struct dtmf_buffer *) buf; + + switch(*dtmf) { + case '#': + switch_set_flag(dtb, SFLAG_MAIN); + return SWITCH_STATUS_BREAK; + case '6': + dtb->index++; + return SWITCH_STATUS_BREAK; + case '4': + dtb->index--; + return SWITCH_STATUS_BREAK; + case '*': + if (switch_test_flag(dtb->sh, SWITCH_SPEECH_FLAG_PAUSE)) { + switch_clear_flag(dtb->sh, SWITCH_SPEECH_FLAG_PAUSE); + } else { + switch_set_flag(dtb->sh, SWITCH_SPEECH_FLAG_PAUSE); + } + break; + case '5': + switch_core_speech_text_param_tts(dtb->sh, "voice", "next"); + switch_set_flag(dtb, SFLAG_INFO); + return SWITCH_STATUS_BREAK; + break; + case '9': + switch_core_speech_text_param_tts(dtb->sh, "voice", dtb->voice); + switch_set_flag(dtb, SFLAG_INFO); + return SWITCH_STATUS_BREAK; + break; + case '2': + if (dtb->speed < 220) { + dtb->speed += 20; + switch_core_speech_numeric_param_tts(dtb->sh, "speech/rate", dtb->speed); + switch_set_flag(dtb, SFLAG_INFO); + return SWITCH_STATUS_BREAK; + } + break; + case '7': + dtb->speed = TTS_MEAN_SPEED; + switch_core_speech_numeric_param_tts(dtb->sh, "speech/rate", dtb->speed); + switch_set_flag(dtb, SFLAG_INFO); + return SWITCH_STATUS_BREAK; + case '8': + if (dtb->speed > 80) { + dtb->speed -= 20; + switch_core_speech_numeric_param_tts(dtb->sh, "speech/rate", dtb->speed); + switch_set_flag(dtb, SFLAG_INFO); + return SWITCH_STATUS_BREAK; + } + break; + case '0': + switch_set_flag(dtb, SFLAG_INSTRUCT); + return SWITCH_STATUS_BREAK; + } + + return SWITCH_STATUS_SUCCESS; +} + +static void rss_function(switch_core_session_t *session, char *data) +{ + switch_channel_t *channel; + switch_status_t status; + switch_frame_t *read_frame; + const char *err = NULL; + struct dtmf_buffer dtb = {0}; + switch_xml_t xml = NULL, item, xchannel = NULL; + struct shashdot_entry entries[TTS_MAX_ENTRIES] = {}; + uint32_t i = 0; + char *title_txt = "", *description_txt = "", *rights_txt = ""; + switch_codec_t speech_codec, *codec = switch_core_session_get_read_codec(session); + char *engine = TTS_DEFAULT_ENGINE; + char *voice = TTS_DEFAULT_VOICE; + char *timer_name = NULL; + switch_speech_handle_t sh; + switch_speech_flag_t flags = SWITCH_SPEECH_FLAG_TTS; + switch_core_thread_session_t thread_session; + uint32_t rate, interval = 20, stream_id = 0; + switch_timer_t timer = {0}, *timerp = NULL; + uint32_t last; + char *mydata = NULL; + char *filename = NULL; + char *argv[3], *feed_list[TTS_MAX_ENTRIES] = {0} , *feed_names[TTS_MAX_ENTRIES] = {0}; + int argc, feed_index = 0; + char *cf = "rss.conf"; + switch_xml_t cfg, cxml, feeds, feed; + char buf[1024]; + uint32_t jumpto = -1; + + if (!(cxml = switch_xml_open_cfg(cf, &cfg, NULL))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "open of %s failed\n", cf); + return; + } + + if ((feeds = switch_xml_child(cfg, "feeds"))) { + for (feed = switch_xml_child(feeds, "feed"); feed; feed = feed->next) { + char *name = (char *) switch_xml_attr_soft(feed, "name"); + + if (!name) { + name = "Error No Name."; + } + + feed_names[feed_index] = switch_core_session_strdup(session, name); + feed_list[feed_index] = switch_core_session_strdup(session, feed->txt); + feed_index++; + } + } + + switch_xml_free(cxml); + + if (!switch_strlen_zero(data)) { + if ((mydata = switch_core_session_strdup(session, data))) { + argc = switch_separate_string(mydata, ' ', argv, sizeof(argv)/sizeof(argv[0])); + + if (argv[0]) { + engine = argv[0]; + if (argv[1]) { + voice = argv[1]; + if (argv[2]) { + jumpto = atoi(argv[2]); + } + } + } + } + } + + if (!feed_index) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "No Feeds Specified!\n"); + return; + } + + if (codec) { + rate = codec->implementation->samples_per_second; + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Codec Error!\n"); + return; + } + + channel = switch_core_session_get_channel(session); + assert(channel != NULL); + + + switch_channel_answer(channel); + + + memset(&sh, 0, sizeof(sh)); + if (switch_core_speech_open(&sh, + engine, + voice, + rate, + &flags, + switch_core_session_get_pool(session)) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid TTS module!\n"); + return; + } + + if (switch_core_codec_init(&speech_codec, + "L16", + (int)rate, + interval, + 1, + SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE, + NULL, + switch_core_session_get_pool(session)) == SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Raw Codec Activated\n"); + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Raw Codec Activation Failed L16@%uhz 1 channel %dms\n", rate, interval); + flags = 0; + switch_core_speech_close(&sh, &flags); + return; + } + + if (timer_name) { + if (switch_core_timer_init(&timer, timer_name, interval, (int)(rate / 50), switch_core_session_get_pool(session)) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "setup timer failed!\n"); + switch_core_codec_destroy(&speech_codec); + flags = 0; + switch_core_speech_close(&sh, &flags); + return; + } + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "setup timer success %u bytes per %d ms!\n", (rate / 50)*2, interval); + + /* start a thread to absorb incoming audio */ + for (stream_id = 0; stream_id < switch_core_session_get_stream_count(session); stream_id++) { + switch_core_service_session(session, &thread_session, stream_id); + } + timerp = &timer; + } + + + while (switch_channel_ready(channel)) { + int32_t len = 0, idx = 0; + char cmd[3]; + main_menu: + filename = NULL; + len = idx = 0; + *cmd = '\0'; + + if (jumpto > -1) { + snprintf(cmd, sizeof(cmd), "%d", jumpto); + } else { + switch_core_speech_flush_tts(&sh); + snprintf(buf + len, sizeof(buf) - len, + "Main Menu. Choose one of the following Feeds or press pound to exit. "); + len = strlen(buf); + + for (idx = 0; idx < feed_index; idx++) { + snprintf(buf + len, sizeof(buf) - len, "Feed Number %d %s. ", idx + 1, feed_names[idx]); + len = strlen(buf); + } + + + snprintf(buf + len, sizeof(buf) - len, ""); + len = strlen(buf); + + status = switch_ivr_speak_text_handle(session, + &sh, + &speech_codec, + timerp, + NULL, + buf, + cmd, + sizeof(cmd)); + if (status != SWITCH_STATUS_SUCCESS && status != SWITCH_STATUS_BREAK) { + goto finished; + } + } + if (!switch_strlen_zero(cmd)) { + int32_t i; + + if (*cmd == '#') { + break; + } + + i = atoi(cmd) - 1; + + if (i >= 0 && i <= feed_index) { + filename = feed_list[i]; + } + } + + if (!filename) { + continue; + } + + + if (!(xml = switch_xml_parse_file(filename))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "open of %s failed\n", filename); + goto finished; + } + + err = switch_xml_error(xml); + + if (!switch_strlen_zero(err)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Error [%s]\n", err); + goto finished; + } + + if ((xchannel = switch_xml_child(xml, "channel"))) { + switch_xml_t title, description, rights; + + if ((title = switch_xml_child(xchannel, "title"))) { + title_txt = title->txt; + } + + if ((description = switch_xml_child(xchannel, "description"))) { + description_txt = description->txt; + } + + if ((rights = switch_xml_child(xchannel, "dc:rights"))) { + rights_txt = rights->txt; + } + } + + + if (!(item = switch_xml_child(xml, "item"))) { + if (xchannel) { + item = switch_xml_child(xchannel, "item"); + } + } + + for (i = 0; item; item = item->next) { + switch_xml_t title, description, subject, dept; + char *p; + + entries[i].inuse = 1; + entries[i].title_txt = NULL; + entries[i].description_txt = NULL; + entries[i].subject_txt = NULL; + entries[i].dept_txt = NULL; + + + if ((title = switch_xml_child(item, "title"))) { + entries[i].title_txt = title->txt; + } + + if ((description = switch_xml_child(item, "description"))) { + char *t, *e; + entries[i].description_txt = description->txt; + for(;;) { + if (!(t = strchr(entries[i].description_txt, '<'))) { + break; + } + if (!(e = strchr(t, '>'))) { + break; + } + + memset(t, 32, ++e-t); + } + } + + if ((subject = switch_xml_child(item, "dc:subject"))) { + entries[i].subject_txt = subject->txt; + } + + if ((dept = switch_xml_child(item, "slash:department"))) { + entries[i].dept_txt = dept->txt; + } + + if (entries[i].description_txt && (p = strchr(entries[i].description_txt, '<'))) { + *p = '\0'; + } + +#ifdef _STRIP_SOME_CHARS_ + for(p = entries[i].description_txt; *p; p++) { + if (*p == '\'' || *p == '"' || *p == ':') { + *p = ' '; + } + } +#endif + + i++; + } + + for (dtb.index = 0; dtb.index < 10; dtb.index++) { + if ((status = switch_core_session_read_frame(session, &read_frame, -1, 0)) != SWITCH_STATUS_SUCCESS) { + switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); + goto done; + } + } + + if (switch_channel_ready(channel)) { + switch_time_exp_t tm; + char date[80] = ""; + switch_size_t retsize; + char cmd[5] = ""; + + switch_time_exp_lt(&tm, switch_time_now()); + switch_strftime(date, &retsize, sizeof(date), "%A, %B %d, %Y. %I:%M %p", &tm); + + + snprintf(buf, sizeof(buf), "%s. %s. %s. local time: %s, Press 0 for options or pound to return to the main menu. ", + title_txt, description_txt, rights_txt, date); + status = switch_ivr_speak_text_handle(session, + &sh, + &speech_codec, + timerp, + NULL, + buf, + cmd, + sizeof(cmd)); + if (status != SWITCH_STATUS_SUCCESS && status != SWITCH_STATUS_BREAK) { + goto finished; + } + if (!switch_strlen_zero(cmd)) { + switch (*cmd) { + case '0': + switch_set_flag(&dtb, SFLAG_INSTRUCT); + break; + } + } + } + + for(last = 0; last < TTS_MAX_ENTRIES; last++) { + if (!entries[last].inuse) { + last--; + break; + } + } + + dtb.index = 0; + dtb.sh = &sh; + dtb.speed = TTS_MEAN_SPEED; + switch_set_flag(&dtb, SFLAG_INFO); + switch_copy_string(dtb.voice, voice, sizeof(dtb.voice)); + while(entries[0].inuse && switch_channel_ready(channel)) { + while(switch_channel_ready(channel)) { + uint8_t cont = 0; + + if (dtb.index >= TTS_MAX_ENTRIES) { + dtb.index = 0; + } + if (dtb.index < 0) { + dtb.index = last; + } + + if (!entries[dtb.index].inuse) { + dtb.index = 0; + continue; + } + if (switch_channel_ready(channel)) { + char buf[1024] = ""; + uint32_t len = 0; + + if (switch_test_flag(&dtb, SFLAG_MAIN)) { + switch_clear_flag(&dtb, SFLAG_MAIN); + goto main_menu; + } + if (switch_test_flag(&dtb, SFLAG_INFO)) { + switch_clear_flag(&dtb, SFLAG_INFO); + snprintf(buf + len, sizeof(buf) - len, + "Hello my name is %s %s. I am speaking at %u words per minute. ", + sh.engine, + sh.voice, + dtb.speed + ); + len = strlen(buf); + } + + if (switch_test_flag(&dtb, SFLAG_INSTRUCT)) { + switch_clear_flag(&dtb, SFLAG_INSTRUCT); + cont = 1; + snprintf(buf + len, sizeof(buf) - len, + "Press star to pause or resume speech. " + "To go to the next item, press six. " + "To go back, press 4. " + "Press eight to go faster, two to slow down, or 7 to resume normal speed. " + "To change voices, press five. To restore the original voice press 9. " + "To hear these options again, press zero or press pound to return to the main menu. "); + } else { + snprintf(buf + len, sizeof(buf) - len, "Story %d. ", dtb.index + 1); + len = strlen(buf); + + if (entries[dtb.index].subject_txt) { + snprintf(buf + len, sizeof(buf) - len, "Subject %s. ", entries[dtb.index].subject_txt); + len = strlen(buf); + } + + if (entries[dtb.index].dept_txt) { + snprintf(buf + len, sizeof(buf) - len, "From the %s department. ", entries[dtb.index].dept_txt); + len = strlen(buf); + } + + if (entries[dtb.index].title_txt) { + snprintf(buf + len, sizeof(buf) - len, "%s", entries[dtb.index].title_txt); + len = strlen(buf); + } + } + switch_core_speech_flush_tts(&sh); + status = switch_ivr_speak_text_handle(session, + &sh, + &speech_codec, + timerp, + on_dtmf, + buf, + &dtb, + sizeof(dtb)); + if (status == SWITCH_STATUS_BREAK) { + continue; + } else if (status != SWITCH_STATUS_SUCCESS) { + goto finished; + } + + if (cont) { + cont = 0; + continue; + } + + status = switch_ivr_speak_text_handle(session, + &sh, + &speech_codec, + timerp, + on_dtmf, + entries[dtb.index].description_txt, + &dtb, + sizeof(dtb)); + if (status == SWITCH_STATUS_BREAK) { + continue; + } else if (status != SWITCH_STATUS_SUCCESS) { + goto finished; + } + } + + dtb.index++; + } + } + } + + finished: + switch_core_speech_close(&sh, &flags); + switch_core_codec_destroy(&speech_codec); + + if (timerp) { + /* End the audio absorbing thread */ + switch_core_thread_session_end(&thread_session); + switch_core_timer_destroy(&timer); + } + + done: + switch_xml_free(xml); + + switch_core_session_reset(session); +} + +static const switch_application_interface_t rss_application_interface = { + /*.interface_name */ "rss", + /*.application_function */ rss_function, + NULL, NULL, NULL, + /*.next*/ NULL +}; + + +static switch_loadable_module_interface_t rss_module_interface = { + /*.module_name */ modname, + /*.endpoint_interface */ NULL, + /*.timer_interface */ NULL, + /*.dialplan_interface */ NULL, + /*.codec_interface */ NULL, + /*.application_interface */ &rss_application_interface, + /*.api_interface */ NULL, + /*.file_interface */ NULL, + /*.speech_interface */ NULL, + /*.directory_interface */ NULL +}; + + +SWITCH_MOD_DECLARE(switch_status_t) switch_module_load(const switch_loadable_module_interface_t **module_interface, char *filename) +{ + /* connect my internal structure to the blank pointer passed to me */ + *module_interface = &rss_module_interface; + + /* indicate that the module should continue to be loaded */ + return SWITCH_STATUS_SUCCESS; +} +