--- /dev/null
+Subject: say.c
+
+Adds SAYFILES function to retrieve the file names that would
+be played by corresponding Say applications, such as
+SayDigits, SayAlpha, etc.
+
+Additionally adds SayMoney and SayOrdinal applications.
--- /dev/null
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2021, Naveen Albert <asterisk@phreaknet.org>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Returns files played by Say applications
+ *
+ * \author Naveen Albert <asterisk@phreaknet.org>
+ * \ingroup functions
+ */
+
+/*** MODULEINFO
+ <support_level>extended</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include "asterisk/pbx.h"
+#include "asterisk/file.h"
+#include "asterisk/channel.h"
+#include "asterisk/say.h"
+#include "asterisk/lock.h"
+#include "asterisk/localtime.h"
+#include "asterisk/utils.h"
+#include "asterisk/app.h"
+#include "asterisk/test.h"
+#include "asterisk/module.h"
+#include "asterisk/conversions.h"
+
+/*** DOCUMENTATION
+ <function name="SAYFILES" language="en_US">
+ <synopsis>
+ Returns the ampersand-delimited file names that would be played by the Say applications (e.g. SayAlpha, SayDigits).
+ </synopsis>
+ <syntax>
+ <parameter name="value" required="true">
+ <para>The value to be translated to filenames.</para>
+ </parameter>
+ <parameter name="type">
+ <para>Say application type.</para>
+ <enumlist>
+ <enum name="alpha">
+ <para>Files played by SayAlpha(). Default if none is specified.</para>
+ </enum>
+ <enum name="digits">
+ <para>Files played by SayDigits().</para>
+ </enum>
+ <enum name="money">
+ <para>Files played by SayMoney(). Currently supported for English and US dollars only.</para>
+ </enum>
+ <enum name="number">
+ <para>Files played by SayNumber(). Currently supported for English only.</para>
+ </enum>
+ <enum name="ordinal">
+ <para>Files played by SayOrdinal(). Currently supported for English only.</para>
+ </enum>
+ <enum name="phonetic">
+ <para>Files played by SayPhonetic().</para>
+ </enum>
+ </enumlist>
+ </parameter>
+ </syntax>
+ <description>
+ <para>Returns the files that would be played by a Say application. These filenames could then be
+ passed directly into Playback, BackGround, Read, Queue, or any application which supports
+ playback of multiple ampersand-delimited files.</para>
+ <example title="Read using the number 123">
+ same => n,Read(response,${SAYFILES(123,number)})
+ </example>
+ </description>
+ <see-also>
+ <ref type="application">SayAlpha</ref>
+ <ref type="application">SayDigits</ref>
+ <ref type="application">SayMoney</ref>
+ <ref type="application">SayNumber</ref>
+ <ref type="application">SayOrdinal</ref>
+ <ref type="application">SayPhonetic</ref>
+ </see-also>
+ </function>
+ ***/
+static int sayfile_exec(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+ char *value, *type, *files;
+ const char *lang;
+ struct ast_str *filenames = NULL;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(value);
+ AST_APP_ARG(type);
+ );
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "SAYFILES requires an argument\n");
+ return 0;
+ }
+
+ AST_STANDARD_APP_ARGS(args, data);
+
+ value = args.value;
+ type = (ast_strlen_zero(args.type) ? "alpha" : args.type);
+ lang = (chan ? ast_channel_language(chan) : "en"); /* No chan for unit tests */
+
+ if (!strcmp(type, "alpha")) {
+ filenames = ast_get_character_str(value, lang, AST_SAY_CASE_NONE);
+ } else if (!strcmp(type, "phonetic")) {
+ filenames = ast_get_phonetic_str(value, lang);
+ } else if (!strcmp(type, "digits")) {
+ filenames = ast_get_digit_str(value, lang);
+ } else if (!strcmp(type, "number")) {
+ int num;
+ if (ast_str_to_int(value, &num)) {
+ ast_log(LOG_WARNING, "Invalid numeric argument: %s\n", value);
+ } else {
+ filenames = ast_get_number_str(num, lang);
+ }
+ } else if (!strcmp(type, "ordinal")) {
+ int num;
+ if (ast_str_to_int(value, &num)) {
+ ast_log(LOG_WARNING, "Invalid numeric argument: %s\n", value);
+ } else {
+ filenames = ast_get_ordinal_str(num, lang);
+ }
+ } else if (!strcmp(type, "money")) {
+ filenames = ast_get_money_str(value, lang);
+ } else {
+ ast_log(LOG_WARNING, "Invalid say type specified: %s\n", type);
+ }
+
+ if (!filenames) {
+ return -1;
+ }
+
+ files = ast_str_buffer(filenames);
+ snprintf(buf, len, "%s", files);
+ ast_free(filenames);
+
+ return 0;
+}
+
+static struct ast_custom_function sayfiles = {
+ .name = "SAYFILES",
+ .read = sayfile_exec,
+};
+
+#ifdef TEST_FRAMEWORK
+AST_TEST_DEFINE(test_SAYFILES_function)
+{
+ enum ast_test_result_state res = AST_TEST_PASS;
+ struct ast_str *expr, *result;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "test_SAYFILES_function";
+ info->category = "/funcs/func_sayfiles/";
+ info->summary = "Test SAYFILES function substitution";
+ info->description =
+ "Executes a series of variable substitutions using the SAYFILES function and ensures that the expected results are received.";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ ast_test_status_update(test, "Testing SAYFILES() substitution ...\n");
+
+ if (!(expr = ast_str_create(16))) {
+ return AST_TEST_FAIL;
+ }
+ if (!(result = ast_str_create(16))) {
+ ast_free(expr);
+ return AST_TEST_FAIL;
+ }
+
+ ast_str_set(&expr, 0, "${SAYFILES(hi Th3re,alpha)}");
+ ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+ if (strcmp(ast_str_buffer(result), "letters/h&letters/i&letters/space&letters/t&letters/h&digits/3&letters/r&letters/e") != 0) {
+ ast_test_status_update(test, "SAYFILES(hi Th3re,alpha) test failed ('%s')\n",
+ ast_str_buffer(result));
+ res = AST_TEST_FAIL;
+ }
+
+ ast_str_set(&expr, 0, "${SAYFILES(phreak,phonetic)}");
+ ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+ if (strcmp(ast_str_buffer(result), "phonetic/p_p&phonetic/h_p&phonetic/r_p&phonetic/e_p&phonetic/a_p&phonetic/k_p") != 0) {
+ ast_test_status_update(test, "SAYFILES(phreak,phonetic) test failed ('%s')\n",
+ ast_str_buffer(result));
+ res = AST_TEST_FAIL;
+ }
+
+ ast_str_set(&expr, 0, "${SAYFILES(35,digits)}");
+ ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+ if (strcmp(ast_str_buffer(result), "digits/3&digits/5") != 0) {
+ ast_test_status_update(test, "SAYFILES(35,digits) test failed ('%s')\n",
+ ast_str_buffer(result));
+ res = AST_TEST_FAIL;
+ }
+
+ ast_str_set(&expr, 0, "${SAYFILES(35,number)}");
+ ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+ if (strcmp(ast_str_buffer(result), "digits/30&digits/5") != 0) {
+ ast_test_status_update(test, "SAYFILES(35,number) test failed ('%s')\n",
+ ast_str_buffer(result));
+ res = AST_TEST_FAIL;
+ }
+
+ ast_str_set(&expr, 0, "${SAYFILES(747,number)}");
+ ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+ if (strcmp(ast_str_buffer(result), "digits/7&digits/hundred&digits/40&digits/7") != 0) {
+ ast_test_status_update(test, "SAYFILES(747,number) test failed ('%s')\n",
+ ast_str_buffer(result));
+ res = AST_TEST_FAIL;
+ }
+
+ ast_str_set(&expr, 0, "${SAYFILES(1042,number)}");
+ ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+ if (strcmp(ast_str_buffer(result), "digits/1&digits/thousand&digits/40&digits/2") != 0) {
+ ast_test_status_update(test, "SAYFILES(1042,number) test failed ('%s')\n",
+ ast_str_buffer(result));
+ res = AST_TEST_FAIL;
+ }
+
+ ast_str_set(&expr, 0, "${SAYFILES(0,number)}");
+ ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+ if (strcmp(ast_str_buffer(result), "digits/0") != 0) {
+ ast_test_status_update(test, "SAYFILES(0,digits) test failed ('%s')\n",
+ ast_str_buffer(result));
+ res = AST_TEST_FAIL;
+ }
+
+ ast_str_set(&expr, 0, "${SAYFILES(2001000001,number)}");
+ ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+ if (strcmp(ast_str_buffer(result), "digits/2&digits/billion&digits/1&digits/million&digits/1") != 0) {
+ ast_test_status_update(test, "SAYFILES(2001000001,number) test failed ('%s')\n",
+ ast_str_buffer(result));
+ res = AST_TEST_FAIL;
+ }
+
+ ast_str_set(&expr, 0, "${SAYFILES(7,ordinal)}");
+ ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+ if (strcmp(ast_str_buffer(result), "digits/h-7") != 0) {
+ ast_test_status_update(test, "SAYFILES(7,ordinal) test failed ('%s')\n",
+ ast_str_buffer(result));
+ res = AST_TEST_FAIL;
+ }
+
+ ast_str_set(&expr, 0, "${SAYFILES(35,ordinal)}");
+ ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+ if (strcmp(ast_str_buffer(result), "digits/30&digits/h-5") != 0) {
+ ast_test_status_update(test, "SAYFILES(35,ordinal) test failed ('%s')\n",
+ ast_str_buffer(result));
+ res = AST_TEST_FAIL;
+ }
+
+ ast_str_set(&expr, 0, "${SAYFILES(1042,ordinal)}");
+ ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+ if (strcmp(ast_str_buffer(result), "digits/1&digits/thousand&digits/40&digits/h-2") != 0) {
+ ast_test_status_update(test, "SAYFILES(1042,ordinal) test failed ('%s')\n",
+ ast_str_buffer(result));
+ res = AST_TEST_FAIL;
+ }
+
+ ast_str_set(&expr, 0, "${SAYFILES(11042,ordinal)}");
+ ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+ if (strcmp(ast_str_buffer(result), "digits/11&digits/thousand&digits/40&digits/h-2") != 0) {
+ ast_test_status_update(test, "SAYFILES(11042,ordinal) test failed ('%s')\n",
+ ast_str_buffer(result));
+ res = AST_TEST_FAIL;
+ }
+
+ ast_str_set(&expr, 0, "${SAYFILES(40000,ordinal)}");
+ ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+ if (strcmp(ast_str_buffer(result), "digits/40&digits/h-thousand") != 0) {
+ ast_test_status_update(test, "SAYFILES(40000,ordinal) test failed ('%s')\n",
+ ast_str_buffer(result));
+ res = AST_TEST_FAIL;
+ }
+
+ ast_str_set(&expr, 0, "${SAYFILES(43638,ordinal)}");
+ ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+ if (strcmp(ast_str_buffer(result), "digits/40&digits/3&digits/thousand&digits/6&digits/hundred&digits/30&digits/h-8") != 0) {
+ ast_test_status_update(test, "SAYFILES(43638,ordinal) test failed ('%s')\n",
+ ast_str_buffer(result));
+ res = AST_TEST_FAIL;
+ }
+
+ ast_str_set(&expr, 0, "${SAYFILES(1000000,ordinal)}");
+ ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+ if (strcmp(ast_str_buffer(result), "digits/1&digits/h-million") != 0) {
+ ast_test_status_update(test, "SAYFILES(1000000,ordinal) test failed ('%s')\n",
+ ast_str_buffer(result));
+ res = AST_TEST_FAIL;
+ }
+
+ ast_str_set(&expr, 0, "${SAYFILES(1000001,ordinal)}");
+ ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+ if (strcmp(ast_str_buffer(result), "digits/1&digits/million&digits/h-1") != 0) {
+ ast_test_status_update(test, "SAYFILES(1000001,ordinal) test failed ('%s')\n",
+ ast_str_buffer(result));
+ res = AST_TEST_FAIL;
+ }
+
+ ast_str_set(&expr, 0, "${SAYFILES(2001000001,ordinal)}");
+ ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+ if (strcmp(ast_str_buffer(result), "digits/2&digits/billion&digits/1&digits/million&digits/h-1") != 0) {
+ ast_test_status_update(test, "SAYFILES(2001000001,ordinal) test failed ('%s')\n",
+ ast_str_buffer(result));
+ res = AST_TEST_FAIL;
+ }
+
+ ast_str_set(&expr, 0, "${SAYFILES(0,money)}");
+ ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+ if (strcmp(ast_str_buffer(result), "digits/0¢s") != 0) {
+ ast_test_status_update(test, "SAYFILES(0,money) test failed ('%s')\n",
+ ast_str_buffer(result));
+ res = AST_TEST_FAIL;
+ }
+
+ ast_str_set(&expr, 0, "${SAYFILES(0.01,money)}");
+ ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+ if (strcmp(ast_str_buffer(result), "digits/1¢") != 0) {
+ ast_test_status_update(test, "SAYFILES(0.01,money) test failed ('%s')\n",
+ ast_str_buffer(result));
+ res = AST_TEST_FAIL;
+ }
+
+ ast_str_set(&expr, 0, "${SAYFILES(0.42,money)}");
+ ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+ if (strcmp(ast_str_buffer(result), "digits/40&digits/2¢s") != 0) {
+ ast_test_status_update(test, "SAYFILES(0.42,money) test failed ('%s')\n",
+ ast_str_buffer(result));
+ res = AST_TEST_FAIL;
+ }
+
+ ast_str_set(&expr, 0, "${SAYFILES(1.00,money)}");
+ ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+ if (strcmp(ast_str_buffer(result), "digits/1&letters/dollar") != 0) {
+ ast_test_status_update(test, "SAYFILES(1.00,money) test failed ('%s')\n",
+ ast_str_buffer(result));
+ res = AST_TEST_FAIL;
+ }
+
+ ast_str_set(&expr, 0, "${SAYFILES(1.42,money)}");
+ ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+ if (strcmp(ast_str_buffer(result), "digits/1&letters/dollar_&and&digits/40&digits/2¢s") != 0) {
+ ast_test_status_update(test, "SAYFILES(1.42,money) test failed ('%s')\n",
+ ast_str_buffer(result));
+ res = AST_TEST_FAIL;
+ }
+
+ ast_str_set(&expr, 0, "${SAYFILES(2.00,money)}");
+ ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+ if (strcmp(ast_str_buffer(result), "digits/2&dollars") != 0) {
+ ast_test_status_update(test, "SAYFILES(2.00,money) test failed ('%s')\n",
+ ast_str_buffer(result));
+ res = AST_TEST_FAIL;
+ }
+
+ ast_str_set(&expr, 0, "${SAYFILES(2.42,money)}");
+ ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+ if (strcmp(ast_str_buffer(result), "digits/2&dollars&and&digits/40&digits/2¢s") != 0) {
+ ast_test_status_update(test, "SAYFILES(2.42,money) test failed ('%s')\n",
+ ast_str_buffer(result));
+ res = AST_TEST_FAIL;
+ }
+
+ ast_free(expr);
+ ast_free(result);
+
+ return res;
+}
+#endif
+
+static int unload_module(void)
+{
+ AST_TEST_UNREGISTER(test_SAYFILES_function);
+ return ast_custom_function_unregister(&sayfiles);
+}
+
+static int load_module(void)
+{
+ AST_TEST_REGISTER(test_SAYFILES_function);
+ return ast_custom_function_register(&sayfiles);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Say application files");
/*! \brief Same as \ref ast_say_number() with audiofd for received audio and returns 1 on ctrlfd being readable */
SAY_EXTERN int (* ast_say_number_full)(struct ast_channel *chan, int num, const char *ints, const char *lang, const char *options, int audiofd, int ctrlfd) SAY_INIT(ast_say_number_full);
+/*!
+ * \brief says an ordinal number
+ * \param chan channel to say them number on
+ * \param num ordinal number to say on the channel
+ * \param ints which dtmf to interrupt on
+ * \param lang language to speak the number
+ * \param options set to 'f' for female, 'm' for male, 'c' for commune, 'n' for neuter
+ * \details
+ * Vocally says an ordinal number on a given channel
+ * \retval 0 on success
+ * \retval DTMF digit on interrupt
+ * \retval -1 on failure
+ */
+int ast_say_ordinal(struct ast_channel *chan, int num,
+ const char *ints, const char *lang, const char *options);
+
+/*! \brief Same as \ref ast_say_number() with audiofd for received audio and returns 1 on ctrlfd being readable */
+SAY_EXTERN int (* ast_say_ordinal_full)(struct ast_channel *chan, int num, const char *ints, const char *lang, const char *options, int audiofd, int ctrlfd) SAY_INIT(ast_say_ordinal_full);
+
/*!
* \brief says an enumeration
* \param chan channel to say them enumeration on
/*! \brief Same as \ref ast_say_digit_str() with audiofd for received audio and returns 1 on ctrlfd being readable */
SAY_EXTERN int (* ast_say_digit_str_full)(struct ast_channel *chan, const char *num, const char *ints, const char *lang, int audiofd, int ctrlfd) SAY_INIT(ast_say_digit_str_full);
+/*! \brief
+ * function to pronounce monetary amounts
+ */
+int ast_say_money_str(struct ast_channel *chan, const char *num,
+ const char *ints, const char *lang);
+
+SAY_EXTERN int (* ast_say_money_str_full)(struct ast_channel *chan, const char *num, const char *ints, const char *lang, int audiofd, int ctrlfd) SAY_INIT(ast_say_money_str_full);
+
/*! \brief
* the generic 'say' routine, with the first chars in the string
* defining the format to use
int ast_say_counted_adjective(struct ast_channel *chan, int num, const char *adjective, const char *gender);
+/*!
+ * \brief Returns an ast_str of files for SayAlpha playback.
+ *
+ * \param str Text to be translated to the corresponding audio files.
+ * \param lang Channel language
+ * \param sensitivity Case sensitivity
+ *
+ * Computes the list of files to be played by SayAlpha.
+ *
+ * \retval ampersand-separated string of Asterisk sound files that can be played back.
+ */
+struct ast_str* ast_get_character_str(const char *str, const char *lang, enum ast_say_case_sensitivity sensitivity);
+
+/*!
+ * \brief Returns an ast_str of files for SayPhonetic playback.
+ *
+ * \param str Text to be translated to the corresponding audio files.
+ * \param lang Channel language
+ *
+ * Computes the list of files to be played by SayPhonetic.
+ *
+ * \retval ampersand-separated string of Asterisk sound files that can be played back.
+ */
+struct ast_str* ast_get_phonetic_str(const char *str, const char *lang);
+
+/*!
+ * \brief Returns an ast_str of files for SayDigits playback.
+ *
+ * \param str Text to be translated to the corresponding audio files.
+ * \param lang Channel language
+ *
+ * Computes the list of files to be played by SayDigits.
+ *
+ * \retval ampersand-separated string of Asterisk sound files that can be played back.
+ */
+struct ast_str* ast_get_digit_str(const char *str, const char *lang);
+
+/*!
+ * \brief Returns an ast_str of files for SayMoney playback.
+ *
+ * \param str Text to be translated to the corresponding audio files.
+ * \param lang Channel language
+ *
+ * Computes the list of files to be played by SayMoney.
+ *
+ * \retval ampersand-separated string of Asterisk sound files that can be played back.
+ */
+struct ast_str* ast_get_money_str(const char *str, const char *lang);
+
+/*!
+ * \brief Returns an ast_str of files for SayNumber playback.
+ *
+ * \param num Integer to be translated to the corresponding audio files.
+ * \param lang Channel language
+ *
+ * Computes the list of files to be played by SayNumber.
+ *
+ * \retval ampersand-separated string of Asterisk sound files that can be played back.
+ */
+struct ast_str* ast_get_number_str(int num, const char *lang);
+
+/*!
+ * \brief Returns an ast_str of files for SayOrdinal playback.
+ *
+ * \param num Integer to be translated to the corresponding audio files.
+ * \param lang Channel language
+ *
+ * Computes the list of files to be played by SayOrdinal.
+ *
+ * \retval ampersand-separated string of Asterisk sound files that can be played back.
+ */
+struct ast_str* ast_get_ordinal_str(int num, const char *lang);
+
#if defined(__cplusplus) || defined(c_plusplus)
}
#endif
return ast_say_number_full(chan, num, ints, language, options, -1, -1);
}
+int ast_say_ordinal(struct ast_channel *chan, int num,
+ const char *ints, const char *language, const char *options)
+{
+ return ast_say_ordinal_full(chan, num, ints, language, options, -1, -1);
+}
+
int ast_say_enumeration(struct ast_channel *chan, int num,
const char *ints, const char *language, const char *options)
{
return ast_say_digit_str_full(chan, str, ints, lang, -1, -1);
}
+int ast_say_money_str(struct ast_channel *chan, const char *str,
+ const char *ints, const char *lang)
+{
+ return ast_say_money_str_full(chan, str, ints, lang, -1, -1);
+}
+
int ast_say_character_str(struct ast_channel *chan, const char *str,
const char *ints, const char *lang, enum ast_say_case_sensitivity sensitivity)
{
#include "asterisk/say.h"
#include "asterisk/app.h"
#include "asterisk/module.h"
+#include "asterisk/conversions.h"
#include "pbx_private.h"
/*** DOCUMENTATION
</description>
<see-also>
<ref type="application">SayDigits</ref>
+ <ref type="application">SayMoney</ref>
<ref type="application">SayNumber</ref>
+ <ref type="application">SayOrdinal</ref>
<ref type="application">SayPhonetic</ref>
<ref type="function">CHANNEL</ref>
+ <ref type="function">SAYFILES</ref>
</see-also>
</application>
<application name="SayAlphaCase" language="en_US">
</description>
<see-also>
<ref type="application">SayDigits</ref>
+ <ref type="application">SayMoney</ref>
<ref type="application">SayNumber</ref>
+ <ref type="application">SayOrdinal</ref>
<ref type="application">SayPhonetic</ref>
<ref type="application">SayAlpha</ref>
<ref type="function">CHANNEL</ref>
</description>
<see-also>
<ref type="application">SayAlpha</ref>
+ <ref type="application">SayMoney</ref>
<ref type="application">SayNumber</ref>
+ <ref type="application">SayOrdinal</ref>
<ref type="application">SayPhonetic</ref>
<ref type="function">CHANNEL</ref>
+ <ref type="function">SAYFILES</ref>
+ </see-also>
+ </application>
+ <application name="SayMoney" language="en_US">
+ <synopsis>
+ Say Money.
+ </synopsis>
+ <syntax>
+ <parameter name="dollars" required="true" />
+ </syntax>
+ <description>
+ <para>This application will play the currency sounds for the given floating point number
+ in the current language. Currently only English and US Dollars is supported.
+ If the channel variable <variable>SAY_DTMF_INTERRUPT</variable> is set to 'true'
+ (case insensitive), then this application will react to DTMF in the same way as
+ <literal>Background</literal>.</para>
+ </description>
+ <see-also>
+ <ref type="application">SayAlpha</ref>
+ <ref type="application">SayNumber</ref>
+ <ref type="application">SayOrdinal</ref>
+ <ref type="application">SayPhonetic</ref>
+ <ref type="function">CHANNEL</ref>
+ <ref type="function">SAYFILES</ref>
</see-also>
</application>
<application name="SayNumber" language="en_US">
<see-also>
<ref type="application">SayAlpha</ref>
<ref type="application">SayDigits</ref>
+ <ref type="application">SayMoney</ref>
+ <ref type="application">SayPhonetic</ref>
+ <ref type="function">CHANNEL</ref>
+ <ref type="function">SAYFILES</ref>
+ </see-also>
+ </application>
+ <application name="SayOrdinal" language="en_US">
+ <synopsis>
+ Say Ordinal Number.
+ </synopsis>
+ <syntax>
+ <parameter name="digits" required="true" />
+ <parameter name="gender" />
+ </syntax>
+ <description>
+ <para>This application will play the ordinal sounds that correspond to the given
+ <replaceable>digits</replaceable> (e.g. 1st, 42nd). Currently only English is supported.</para>
+ <para>Optionally, a <replaceable>gender</replaceable> may be
+ specified. This will use the language that is currently set for the channel. See the CHANNEL()
+ function for more information on setting the language for the channel. If the channel variable
+ <variable>SAY_DTMF_INTERRUPT</variable> is set to 'true' (case insensitive), then this
+ application will react to DTMF in the same way as <literal>Background</literal>.</para>
+ </description>
+ <see-also>
+ <ref type="application">SayAlpha</ref>
+ <ref type="application">SayDigits</ref>
+ <ref type="application">SayMoney</ref>
+ <ref type="application">SayNumber</ref>
<ref type="application">SayPhonetic</ref>
<ref type="function">CHANNEL</ref>
+ <ref type="function">SAYFILES</ref>
</see-also>
</application>
<application name="SayPhonetic" language="en_US">
<see-also>
<ref type="application">SayAlpha</ref>
<ref type="application">SayDigits</ref>
+ <ref type="application">SayMoney</ref>
<ref type="application">SayNumber</ref>
+ <ref type="application">SayOrdinal</ref>
+ <ref type="function">SAYFILES</ref>
</see-also>
</application>
<application name="SetAMAFlags" language="en_US">
ast_copy_string(tmp, data, sizeof(tmp));
strsep(&number, ",");
- if (sscanf(tmp, "%d", &number_val) != 1) {
+ if (ast_str_to_int(tmp, &number_val)) {
ast_log(LOG_WARNING, "argument '%s' to SayNumber could not be parsed as a number.\n", tmp);
return 0;
}
return interrupt ? res : 0;
}
+static int pbx_builtin_sayordinal(struct ast_channel *chan, const char *data)
+{
+ char tmp[256];
+ char *number = tmp;
+ int number_val;
+ char *options;
+ int res;
+ int interrupt = 0;
+ const char *interrupt_string;
+
+ ast_channel_lock(chan);
+ interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT");
+ if (ast_true(interrupt_string)) {
+ interrupt = 1;
+ }
+ ast_channel_unlock(chan);
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "SayOrdinal requires an argument (number)\n");
+ return -1;
+ }
+ ast_copy_string(tmp, data, sizeof(tmp));
+ strsep(&number, ",");
+
+ if (ast_str_to_int(tmp, &number_val)) {
+ ast_log(LOG_WARNING, "argument '%s' to SayOrdinal could not be parsed as a number.\n", tmp);
+ return 0;
+ }
+
+ options = strsep(&number, ",");
+ if (options) {
+ if ( strcasecmp(options, "f") && strcasecmp(options, "m") &&
+ strcasecmp(options, "c") && strcasecmp(options, "n") ) {
+ ast_log(LOG_WARNING, "SayOrdinal gender option is either 'f', 'm', 'c' or 'n'\n");
+ return -1;
+ }
+ }
+
+ res = ast_say_ordinal(chan, number_val, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan), options);
+
+ if (res < 0 && !ast_check_hangup_locked(chan)) {
+ ast_log(LOG_WARNING, "We were unable to say the number %s, is it too large?\n", tmp);
+ }
+
+ return interrupt ? res : 0;
+}
+
static int pbx_builtin_saydigits(struct ast_channel *chan, const char *data)
{
int res = 0;
return res;
}
+static int pbx_builtin_saymoney(struct ast_channel *chan, const char *data)
+{
+ int res = 0;
+ int interrupt = 0;
+ const char *interrupt_string;
+
+ ast_channel_lock(chan);
+ interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT");
+ if (ast_true(interrupt_string)) {
+ interrupt = 1;
+ }
+ ast_channel_unlock(chan);
+
+ if (data) {
+ res = ast_say_money_str(chan, data, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan));
+ }
+
+ return res;
+}
+
static int pbx_builtin_saycharacters_case(struct ast_channel *chan, const char *data)
{
int res = 0;
{ "SayAlpha", pbx_builtin_saycharacters },
{ "SayAlphaCase", pbx_builtin_saycharacters_case },
{ "SayDigits", pbx_builtin_saydigits },
+ { "SayMoney", pbx_builtin_saymoney },
{ "SayNumber", pbx_builtin_saynumber },
+ { "SayOrdinal", pbx_builtin_sayordinal },
{ "SayPhonetic", pbx_builtin_sayphonetic },
{ "SetAMAFlags", pbx_builtin_setamaflags },
{ "Wait", pbx_builtin_wait },
* Next Generation Networks (NGN).
* \note 2007-03-20 : Support for Thai added by Dome C. <dome@tel.co.th>,
* IP Crossing Co., Ltd.
+ * \note 2021-07-26 : Refactoring to separate string buildup and playback
+ * by Naveen Albert <asterisk@phreaknet.org>
*/
/*** MODULEINFO
/* Forward declaration */
static int wait_file(struct ast_channel *chan, const char *ints, const char *file, const char *lang);
-
-static int say_character_str_full(struct ast_channel *chan, const char *str, const char *ints, const char *lang, enum ast_say_case_sensitivity sensitivity, int audiofd, int ctrlfd)
-{
+struct ast_str* ast_get_character_str(const char *str, const char *lang, enum ast_say_case_sensitivity sensitivity) {
const char *fn;
char fnbuf[10], asciibuf[20] = "letters/ascii";
char ltr;
int upper = 0;
int lower = 0;
+ struct ast_str *filenames = ast_str_create(20);
+ if (!filenames) {
+ return NULL;
+ }
+ ast_str_reset(filenames);
+
while (str[num] && !res) {
fn = NULL;
switch (str[num]) {
}
if ((fn && ast_fileexists(fn, NULL, lang) > 0) ||
(snprintf(asciibuf + 13, sizeof(asciibuf) - 13, "%d", str[num]) > 0 && ast_fileexists(asciibuf, NULL, lang) > 0 && (fn = asciibuf))) {
- res = ast_streamfile(chan, fn, lang);
- if (!res) {
- if ((audiofd > -1) && (ctrlfd > -1))
- res = ast_waitstream_full(chan, ints, audiofd, ctrlfd);
- else
- res = ast_waitstream(chan, ints);
- }
- ast_stopstream(chan);
+ ast_str_append(&filenames, 0, (num == 0 ? "%s" : "&%s"), fn);
}
if (upper || lower) {
continue;
num++;
}
+ return filenames;
+}
+
+static int say_filenames(struct ast_channel *chan, const char *ints, const char *lang, int audiofd, int ctrlfd, struct ast_str *filenames)
+{
+ int res = 0;
+ char *files;
+ const char *fn;
+
+ if (!filenames) {
+ return -1;
+ }
+ files = ast_str_buffer(filenames);
+
+ while ((fn = strsep(&files, "&"))) {
+ res = ast_streamfile(chan, fn, lang);
+ if (!res) {
+ if ((audiofd > -1) && (ctrlfd > -1))
+ res = ast_waitstream_full(chan, ints, audiofd, ctrlfd);
+ else
+ res = ast_waitstream(chan, ints);
+ }
+ ast_stopstream(chan);
+ }
+
+ ast_free(filenames);
+
return res;
}
-static int say_phonetic_str_full(struct ast_channel *chan, const char *str, const char *ints, const char *lang, int audiofd, int ctrlfd)
+static int say_character_str_full(struct ast_channel *chan, const char *str, const char *ints, const char *lang, enum ast_say_case_sensitivity sensitivity, int audiofd, int ctrlfd)
+{
+ struct ast_str *filenames = ast_get_character_str(str, lang, sensitivity);
+ return say_filenames(chan, ints, lang, audiofd, ctrlfd, filenames);
+}
+
+struct ast_str* ast_get_phonetic_str(const char *str, const char *lang)
{
const char *fn;
char fnbuf[256];
char ltr;
int num = 0;
- int res = 0;
- while (str[num] && !res) {
+ struct ast_str *filenames = ast_str_create(20);
+ if (!filenames) {
+ return NULL;
+ }
+ ast_str_reset(filenames);
+
+ while (str[num]) {
fn = NULL;
switch (str[num]) {
case ('*'):
fn = fnbuf;
}
if (fn && ast_fileexists(fn, NULL, lang) > 0) {
- res = ast_streamfile(chan, fn, lang);
- if (!res) {
- if ((audiofd > -1) && (ctrlfd > -1))
- res = ast_waitstream_full(chan, ints, audiofd, ctrlfd);
- else
- res = ast_waitstream(chan, ints);
- }
- ast_stopstream(chan);
+ ast_str_append(&filenames, 0, (num == 0 ? "%s" : "&%s"), fn);
}
num++;
}
- return res;
+ return filenames;
}
-static int say_digit_str_full(struct ast_channel *chan, const char *str, const char *ints, const char *lang, int audiofd, int ctrlfd)
+static int say_phonetic_str_full(struct ast_channel *chan, const char *str, const char *ints, const char *lang, int audiofd, int ctrlfd)
+{
+ struct ast_str *filenames = ast_get_phonetic_str(str, lang);
+ return say_filenames(chan, ints, lang, audiofd, ctrlfd, filenames);
+}
+
+struct ast_str* ast_get_digit_str(const char *str, const char *lang)
{
const char *fn;
char fnbuf[256];
int num = 0;
- int res = 0;
- while (str[num] && !res) {
+ struct ast_str *filenames = ast_str_create(20);
+ if (!filenames) {
+ return NULL;
+ }
+ ast_str_reset(filenames);
+
+ while (str[num]) {
fn = NULL;
switch (str[num]) {
case ('*'):
break;
}
if (fn && ast_fileexists(fn, NULL, lang) > 0) {
- res = ast_streamfile(chan, fn, lang);
- if (!res) {
- if ((audiofd > -1) && (ctrlfd > -1))
- res = ast_waitstream_full(chan, ints, audiofd, ctrlfd);
- else
- res = ast_waitstream(chan, ints);
- }
- ast_stopstream(chan);
+ ast_str_append(&filenames, 0, (num == 0 ? "%s" : "&%s"), fn);
}
num++;
}
- return res;
+ return filenames;
+}
+
+static int say_digit_str_full(struct ast_channel *chan, const char *str, const char *ints, const char *lang, int audiofd, int ctrlfd)
+{
+ struct ast_str *filenames = ast_get_digit_str(str, lang);
+ return say_filenames(chan, ints, lang, audiofd, ctrlfd, filenames);
+}
+
+static struct ast_str* ast_get_money_en_dollars_str(const char *str, const char *lang)
+{
+ const char *fnr;
+
+ double dollars = 0;
+ int amt, cents;
+ struct ast_str *fnrecurse = NULL;
+
+ struct ast_str *filenames = ast_str_create(20);
+ if (!filenames) {
+ return NULL;
+ }
+ ast_str_reset(filenames);
+
+ if (sscanf(str, "%30lf", &dollars) != 1) {
+ amt = 0;
+ } else { /* convert everything to cents */
+ amt = dollars * 100;
+ }
+
+ /* Just the cents after the dollar decimal point */
+ cents = amt - (((int) dollars) * 100);
+ ast_debug(1, "Cents is %d, amount is %d\n", cents, amt);
+
+ if (amt >= 100) {
+ fnrecurse = ast_get_number_str((amt / 100), lang);
+ if (!fnrecurse) {
+ ast_log(LOG_WARNING, "Couldn't get string for dollars\n");
+ } else {
+ fnr = ast_str_buffer(fnrecurse);
+ ast_str_append(&filenames, 0, "%s", fnr);
+ }
+
+ /* If this is it, end on a down pitch, otherwise up pitch */
+ if (amt < 200) {
+ ast_str_append(&filenames, 0, "&%s", (cents > 0) ? "letters/dollar_" : "letters/dollar");
+ } else {
+ ast_str_append(&filenames, 0, "&%s", "dollars");
+ }
+
+ /* If dollars and cents, add "and" in the middle */
+ if (cents > 0) {
+ ast_str_append(&filenames, 0, "&%s", "and");
+ }
+ }
+
+ if (cents > 0) {
+ fnrecurse = ast_get_number_str(cents, lang);
+ if (!fnrecurse) {
+ ast_log(LOG_ERROR, "Couldn't get string for cents\n");
+ } else {
+ fnr = ast_str_buffer(fnrecurse);
+ ast_str_append(&filenames, 0, (amt < 100 ? "%s" : "&%s"), fnr);
+ }
+ ast_str_append(&filenames, 0, "&%s", (cents == 1) ? "cent" : "cents");
+ } else if (amt == 0) {
+ fnrecurse = ast_get_digit_str("0", lang);
+ if (!fnrecurse) {
+ ast_log(LOG_ERROR, "Couldn't get string for cents\n");
+ } else {
+ fnr = ast_str_buffer(fnrecurse);
+ ast_str_append(&filenames, 0, "%s", fnr);
+ }
+ ast_str_append(&filenames, 0, "&%s", "cents");
+ }
+
+ if (fnrecurse) {
+ ast_free(fnrecurse);
+ }
+
+ return filenames;
+}
+
+/*! \brief ast_get_money_str: call language-specific functions */
+struct ast_str* ast_get_money_str(const char *str, const char *lang)
+{
+ if (!strncasecmp(lang, "en", 2)) { /* English syntax */
+ return ast_get_money_en_dollars_str(str, lang);
+ }
+
+ ast_log(LOG_WARNING, "Language %s not currently supported, defaulting to US Dollars\n", lang);
+ /* Default to english */
+ return ast_get_money_en_dollars_str(str, lang);
+}
+
+static int say_money_str_full(struct ast_channel *chan, const char *str, const char *ints, const char *lang, int audiofd, int ctrlfd)
+{
+ struct ast_str *filenames = ast_get_money_str(str, lang);
+ return say_filenames(chan, ints, lang, audiofd, ctrlfd, filenames);
+}
+
+static struct ast_str* get_number_str_en(int num, const char *lang)
+{
+ const char *fnr;
+ int loops = 0;
+
+ int res = 0;
+ int playh = 0;
+ char fn[256] = "";
+
+ struct ast_str *filenames;
+
+ if (!num) {
+ return ast_get_digit_str("0", lang);
+ }
+
+ filenames = ast_str_create(20);
+ if (!filenames) {
+ return NULL;
+ }
+ ast_str_reset(filenames);
+
+ while (!res && (num || playh)) {
+ if (num < 0) {
+ ast_copy_string(fn, "digits/minus", sizeof(fn));
+ if ( num > INT_MIN ) {
+ num = -num;
+ } else {
+ num = 0;
+ }
+ } else if (playh) {
+ ast_copy_string(fn, "digits/hundred", sizeof(fn));
+ playh = 0;
+ } else if (num < 20) {
+ snprintf(fn, sizeof(fn), "digits/%d", num);
+ num = 0;
+ } else if (num < 100) {
+ snprintf(fn, sizeof(fn), "digits/%d", (num /10) * 10);
+ num %= 10;
+ } else {
+ if (num < 1000){
+ snprintf(fn, sizeof(fn), "digits/%d", (num/100));
+ playh++;
+ num %= 100;
+ } else {
+ struct ast_str *fnrecurse = NULL;
+ if (num < 1000000) { /* 1,000,000 */
+ fnrecurse = get_number_str_en((num / 1000), lang);
+ if (!fnrecurse) {
+ ast_log(LOG_ERROR, "Couldn't get string for num\n");
+ } else {
+ fnr = ast_str_buffer(fnrecurse);
+ ast_str_append(&filenames, 0, (loops == 0 ? "%s" : "&%s"), fnr);
+ }
+ num %= 1000;
+ snprintf(fn, sizeof(fn), "&digits/thousand");
+ } else {
+ if (num < 1000000000) { /* 1,000,000,000 */
+ fnrecurse = get_number_str_en((num / 1000000), lang);
+ if (!fnrecurse) {
+ ast_log(LOG_ERROR, "Couldn't get string for num\n");
+ } else {
+ fnr = ast_str_buffer(fnrecurse);
+ ast_str_append(&filenames, 0, (loops == 0 ? "%s" : "&%s"), fnr);
+ }
+ num %= 1000000;
+ ast_copy_string(fn, "&digits/million", sizeof(fn));
+ } else {
+ if (num < INT_MAX) {
+ fnrecurse = get_number_str_en((num / 1000000000), lang);
+ if (!fnrecurse) {
+ ast_log(LOG_ERROR, "Couldn't get string for num\n");
+ } else {
+ fnr = ast_str_buffer(fnrecurse);
+ ast_str_append(&filenames, 0, (loops == 0 ? "%s" : "&%s"), fnr);
+ }
+ num %= 1000000000;
+ ast_copy_string(fn, "&digits/billion", sizeof(fn));
+ } else {
+ ast_log(LOG_WARNING, "Number '%d' is too big for me\n", num);
+ res = -1;
+ }
+ }
+ }
+ if (fnrecurse) {
+ ast_free(fnrecurse);
+ }
+ /* we already decided whether or not to add an &, don't add another one immediately */
+ loops = 0;
+ }
+ }
+ if (!res) {
+ ast_str_append(&filenames, 0, (loops == 0 ? "%s" : "&%s"), fn);
+ loops++;
+ }
+ }
+
+ return filenames;
+}
+
+/*! \brief ast_get_number_str: call language-specific functions */
+struct ast_str* ast_get_number_str(int num, const char *lang)
+{
+ if (!strncasecmp(lang, "en", 2)) { /* English syntax */
+ return get_number_str_en(num, lang);
+ }
+
+ ast_log(LOG_WARNING, "Language %s not currently supported, defaulting to English\n", lang);
+ /* Default to english */
+ return get_number_str_en(num, lang);
+}
+
+static struct ast_str* get_ordinal_str_en(int num, const char *lang)
+{
+ const char *fnr;
+ int loops = 0;
+
+ int res = 0;
+ int playh = 0;
+ char fn[256] = "";
+
+ struct ast_str *filenames;
+
+ if (!num) {
+ num = 0;
+ }
+
+ filenames = ast_str_create(20);
+ if (!filenames) {
+ return NULL;
+ }
+ ast_str_reset(filenames);
+
+ while (!res && (num || playh)) {
+ if (num < 0) {
+ ast_copy_string(fn, "digits/minus", sizeof(fn));
+ if ( num > INT_MIN ) {
+ num = -num;
+ } else {
+ num = 0;
+ }
+ } else if (playh) {
+ ast_copy_string(fn, (num % 100 == 0) ? "digits/h-hundred" : "digits/hundred", sizeof(fn));
+ playh = 0;
+ } else if (num < 20) {
+ if (num > 0) {
+ snprintf(fn, sizeof(fn), "digits/h-%d", num);
+ } else {
+ ast_log(LOG_ERROR, "Unsupported ordinal number: %d\n", num);
+ }
+ num = 0;
+ } else if (num < 100) {
+ int base = (num / 10) * 10;
+ if (base != num) {
+ snprintf(fn, sizeof(fn), "digits/%d", base);
+ } else {
+ snprintf(fn, sizeof(fn), "digits/h-%d", base);
+ }
+ num %= 10;
+ } else {
+ if (num < 1000){
+ snprintf(fn, sizeof(fn), "digits/%d", (num/100));
+ playh++;
+ num %= 100;
+ } else {
+ struct ast_str *fnrecurse = NULL;
+ if (num < 1000000) { /* 1,000,000 */
+ fnrecurse = get_number_str_en((num / 1000), lang);
+ if (!fnrecurse) {
+ ast_log(LOG_ERROR, "Couldn't get string for num\n");
+ } else {
+ fnr = ast_str_buffer(fnrecurse);
+ ast_str_append(&filenames, 0, (loops == 0 ? "%s" : "&%s"), fnr);
+ }
+ num %= 1000;
+ snprintf(fn, sizeof(fn), (num % 1000 == 0) ? "&digits/h-thousand" : "&digits/thousand");
+ } else {
+ if (num < 1000000000) { /* 1,000,000,000 */
+ fnrecurse = get_number_str_en((num / 1000000), lang);
+ if (!fnrecurse) {
+ ast_log(LOG_ERROR, "Couldn't get string for num\n");
+ } else {
+ fnr = ast_str_buffer(fnrecurse);
+ ast_str_append(&filenames, 0, (loops == 0 ? "%s" : "&%s"), fnr);
+ }
+ num %= 1000000;
+ ast_copy_string(fn, (num % 1000000 == 0) ? "&digits/h-million" : "&digits/million", sizeof(fn));
+ } else {
+ if (num < INT_MAX) {
+ fnrecurse = get_number_str_en((num / 1000000000), lang);
+ if (!fnrecurse) {
+ ast_log(LOG_ERROR, "Couldn't get string for num\n");
+ } else {
+ fnr = ast_str_buffer(fnrecurse);
+ ast_str_append(&filenames, 0, (loops == 0 ? "%s" : "&%s"), fnr);
+ }
+ num %= 1000000000;
+ ast_copy_string(fn, (num % 1000000000 == 0) ? "&digits/h-billion" : "&digits/billion", sizeof(fn));
+ } else {
+ ast_log(LOG_WARNING, "Number '%d' is too big for me\n", num);
+ res = -1;
+ }
+ }
+ }
+ if (fnrecurse) {
+ ast_free(fnrecurse);
+ }
+ /* we already decided whether or not to add an &, don't add another one immediately */
+ loops = 0;
+ }
+ }
+ if (!res) {
+ ast_str_append(&filenames, 0, (loops == 0 ? "%s" : "&%s"), fn);
+ loops++;
+ }
+ }
+
+ return filenames;
+}
+
+/*! \brief ast_get_ordinal_str: call language-specific functions */
+struct ast_str* ast_get_ordinal_str(int num, const char *lang)
+{
+ if (!strncasecmp(lang, "en", 2)) { /* English syntax */
+ return get_ordinal_str_en(num, lang);
+ }
+
+ ast_log(LOG_WARNING, "Language %s not currently supported, defaulting to English\n", lang);
+ /* Default to english */
+ return get_ordinal_str_en(num, lang);
}
/* Forward declarations */
\note This is the default syntax, if no other syntax defined in this file is used */
static int ast_say_number_full_en(struct ast_channel *chan, int num, const char *ints, const char *language, int audiofd, int ctrlfd)
{
- int res = 0;
- int playh = 0;
- char fn[256] = "";
- if (!num)
- return ast_say_digits_full(chan, 0, ints, language, audiofd, ctrlfd);
+ struct ast_str *filenames = ast_get_number_str(num, language);
+ return say_filenames(chan, ints, language, audiofd, ctrlfd, filenames);
+}
- while (!res && (num || playh)) {
- if (num < 0) {
- ast_copy_string(fn, "digits/minus", sizeof(fn));
- if ( num > INT_MIN ) {
- num = -num;
- } else {
- num = 0;
- }
- } else if (playh) {
- ast_copy_string(fn, "digits/hundred", sizeof(fn));
- playh = 0;
- } else if (num < 20) {
- snprintf(fn, sizeof(fn), "digits/%d", num);
- num = 0;
- } else if (num < 100) {
- snprintf(fn, sizeof(fn), "digits/%d", (num /10) * 10);
- num %= 10;
- } else {
- if (num < 1000){
- snprintf(fn, sizeof(fn), "digits/%d", (num/100));
- playh++;
- num %= 100;
- } else {
- if (num < 1000000) { /* 1,000,000 */
- res = ast_say_number_full_en(chan, num / 1000, ints, language, audiofd, ctrlfd);
- if (res)
- return res;
- num %= 1000;
- snprintf(fn, sizeof(fn), "digits/thousand");
- } else {
- if (num < 1000000000) { /* 1,000,000,000 */
- res = ast_say_number_full_en(chan, num / 1000000, ints, language, audiofd, ctrlfd);
- if (res)
- return res;
- num %= 1000000;
- ast_copy_string(fn, "digits/million", sizeof(fn));
- } else {
- ast_debug(1, "Number '%d' is too big for me\n", num);
- res = -1;
- }
- }
- }
- }
- if (!res) {
- if (!ast_streamfile(chan, fn, language)) {
- if ((audiofd > -1) && (ctrlfd > -1))
- res = ast_waitstream_full(chan, ints, audiofd, ctrlfd);
- else
- res = ast_waitstream(chan, ints);
- }
- ast_stopstream(chan);
- }
- }
- return res;
+/*! \brief say_ordinal_full */
+static int say_ordinal_full(struct ast_channel *chan, int num, const char *ints, const char *language, const char *options, int audiofd, int ctrlfd)
+{
+ struct ast_str *filenames = ast_get_ordinal_str(num, language);
+ return say_filenames(chan, ints, language, audiofd, ctrlfd, filenames);
}
static int exp10_int(int power)
static void __attribute__((constructor)) __say_init(void)
{
ast_say_number_full = say_number_full;
+ ast_say_ordinal_full = say_ordinal_full;
ast_say_enumeration_full = say_enumeration_full;
ast_say_digit_str_full = say_digit_str_full;
+ ast_say_money_str_full = say_money_str_full;
ast_say_character_str_full = say_character_str_full;
ast_say_phonetic_str_full = say_phonetic_str_full;
ast_say_datetime = say_datetime;