]> git.ipfire.org Git - thirdparty/freeswitch.git/commitdiff
Initial version of mod_say_pl
authorMariusz Czułada <manieq.net@gmail.com>
Thu, 25 Apr 2013 22:16:49 +0000 (00:16 +0200)
committerTravis Cross <tc@traviscross.com>
Thu, 6 Jun 2013 18:53:24 +0000 (18:53 +0000)
src/mod/say/mod_say_pl/Makefile [new file with mode: 0644]
src/mod/say/mod_say_pl/README [new file with mode: 0644]
src/mod/say/mod_say_pl/conf/dialplan/default/test_say_pl.xml [new file with mode: 0644]
src/mod/say/mod_say_pl/conf/lang/pl/pl.xml [new file with mode: 0644]
src/mod/say/mod_say_pl/create_sound_files.sh [new file with mode: 0755]
src/mod/say/mod_say_pl/mod_say_pl.c [new file with mode: 0644]

diff --git a/src/mod/say/mod_say_pl/Makefile b/src/mod/say/mod_say_pl/Makefile
new file mode 100644 (file)
index 0000000..67f6ea3
--- /dev/null
@@ -0,0 +1,2 @@
+BASE=../../../freeswitch
+include $(BASE)/build/modmake.rules
diff --git a/src/mod/say/mod_say_pl/README b/src/mod/say/mod_say_pl/README
new file mode 100644 (file)
index 0000000..2a35b81
--- /dev/null
@@ -0,0 +1,7 @@
+mod_say_pl - support for Polish language in FreeSWITCH
+=======================================================
+
+Moduł bazuje na mod_say_en. Aktualnie nie jest jeszcze kompletny,
+tym nie mniej pozwala na czytanie liczb czy dat/czasu. Liczę na
+Wasze wsparcie w zakresie testowania modułu, sugestii, łat, etc.
+na http://jira.freeswitch.org/.
diff --git a/src/mod/say/mod_say_pl/conf/dialplan/default/test_say_pl.xml b/src/mod/say/mod_say_pl/conf/dialplan/default/test_say_pl.xml
new file mode 100644 (file)
index 0000000..a19bbe0
--- /dev/null
@@ -0,0 +1,94 @@
+<include>
+
+    <extension name="test_say_pl">
+      <condition field="destination_number" expression="^^44(\d{2})$">
+        <action application="answer"/>
+        <action application="set" data="sound_prefix=$${sounds_dir}/pl/espeak"/>
+        <action application="set" data="default_language=pl"/>
+        <action application="sleep" data="1000"/>
+        <action application="transfer" data="test_say_pl_$1"/>
+      </condition>
+    </extension>
+
+    <extension name="test_say_pl_01">
+      <condition field="destination_number" expression="^test_say_pl_01$">
+        <action application="set" data="num=${expr(randomize(&x);ceil(random(0,1000000,&x)))}"/>
+        <action application="log" data="INFO Test: say pl: ${num}"/>
+        <action application="say" data="pl number pronounced ${num}"/>
+        <action application="sleep" data="2000"/>
+        <action application="transfer" data="test_say_pl_01"/>
+      </condition>
+    </extension>
+
+    <extension name="test_say_pl_02">
+      <condition field="destination_number" expression="^test_say_pl_02$">
+        <action application="set" data="num=${expr(randomize(&x);ceil(random(0,1000000,&x)))}"/>
+        <action application="log" data="INFO Test: say pl: ${num}"/>
+        <action application="say" data="pl number iterated ${num}"/>
+        <action application="sleep" data="2000"/>
+        <action application="transfer" data="test_say_pl_02"/>
+      </condition>
+    </extension>
+
+    <extension name="test_say_pl_03">
+      <condition field="destination_number" expression="^test_say_pl_03$">
+        <action application="set" data="hh=${expr(randomize(&x);ceil(random(0,24,&x)))}"/>
+        <action application="set" data="mm=${expr(randomize(&x);ceil(random(0,60,&x)))}"/>
+        <action application="set" data="ss=${expr(randomize(&x);ceil(random(0,60,&x)))}"/>
+        <action application="log" data="INFO Test: say pl: ${hh}:${mm}:${ss}"/>
+        <action application="say" data="pl TIME_MEASUREMENT pronounced ${hh}:${mm}:${ss}"/>
+        <action application="sleep" data="2000"/>
+        <action application="transfer" data="test_say_pl_03"/>
+      </condition>
+    </extension>
+
+    <extension name="test_say_pl_04">
+      <condition field="destination_number" expression="^test_say_pl_04$">
+        <action application="log" data="INFO Test: say pl: ${strftime()}"/>
+        <action application="say" data="pl TIME_MEASUREMENT pronounced ${strftime()}"/>
+        <action application="sleep" data="2000"/>
+        <action application="transfer" data="test_say_pl_04"/>
+      </condition>
+    </extension>
+
+    <extension name="test_say_pl_05">
+      <condition field="destination_number" expression="^test_say_pl_05$">
+        <action application="log" data="INFO Test: say pl: ${strftime()}"/>
+        <action application="say" data="pl current_date_time pronounced ${strepoch()}"/>
+        <action application="sleep" data="2000"/>
+        <action application="transfer" data="test_say_pl_05"/>
+      </condition>
+    </extension>
+
+    <extension name="test_say_pl_06">
+      <condition field="destination_number" expression="^test_say_pl_06$">
+        <action application="log" data="INFO Test: say pl: ${strftime()}"/>
+        <action application="say" data="pl current_date pronounced ${strepoch()}"/>
+        <action application="sleep" data="2000"/>
+        <action application="transfer" data="test_say_pl_06"/>
+      </condition>
+    </extension>
+
+    <extension name="test_say_pl_07">
+      <condition field="destination_number" expression="^test_say_pl_07$">
+        <action application="log" data="INFO Test: say pl: ${strftime()}"/>
+        <action application="say" data="pl short_date_time pronounced ${strepoch()}"/>
+        <action application="sleep" data="2000"/>
+        <action application="transfer" data="test_say_pl_07"/>
+      </condition>
+    </extension>
+
+    <extension name="test_say_pl_11">
+      <condition field="destination_number" expression="^test_say_pl_11$">
+        <action application="set" data="i1=${expr(randomize(&x);ceil(random(0,255,&x)))}"/>
+        <action application="set" data="i2=${expr(randomize(&x);ceil(random(0,255,&x)))}"/>
+        <action application="set" data="i3=${expr(randomize(&x);ceil(random(0,255,&x)))}"/>
+        <action application="set" data="i4=${expr(randomize(&x);ceil(random(0,255,&x)))}"/>
+        <action application="log" data="INFO Test: say pl: ${i1}.${i2}.${i3}.${i4}"/>
+        <action application="say" data="pl IP_ADDRESS pronounced ${i1}.${i2}.${i3}.${i4}"/>
+        <action application="sleep" data="2000"/>
+        <action application="transfer" data="test_pl_11"/>
+      </condition>
+    </extension>
+
+</include>
diff --git a/src/mod/say/mod_say_pl/conf/lang/pl/pl.xml b/src/mod/say/mod_say_pl/conf/lang/pl/pl.xml
new file mode 100644 (file)
index 0000000..92c4e16
--- /dev/null
@@ -0,0 +1,19 @@
+<include>
+  <language name="pl" say-module="PL" sound-prefix="$${sounds_dir}/pl/espeak">
+    <phrases>
+      <macros>
+      </macros>
+    </phrases>
+  </language>
+</include>
+<!--
+For Emacs:
+Local Variables:
+mode:xml
+indent-tabs-mode:nil
+tab-width:2
+c-basic-offset:2
+End:
+For VIM:
+vim:set softtabstop=2 shiftwidth=2 tabstop=2 expandtab:
+-->
diff --git a/src/mod/say/mod_say_pl/create_sound_files.sh b/src/mod/say/mod_say_pl/create_sound_files.sh
new file mode 100755 (executable)
index 0000000..0ee3855
--- /dev/null
@@ -0,0 +1,358 @@
+#!/bin/bash
+
+TTS=espeak
+TTS_OPTS="-a90 -b1 -s130 -p77"
+
+######################
+# directories for voice files
+######################
+
+rm -rf pl/epeak
+mkdir -p pl/espeak/digits/HD
+mkdir -p pl/espeak/time/HD
+mkdir -p pl/espeak/digits/16000
+mkdir -p pl/espeak/time/16000
+mkdir -p pl/espeak/digits/8000
+mkdir -p pl/espeak/time/8000
+
+######################
+# create sound files
+######################
+
+function mksound {
+    local TYPE=$1; shift
+    local FILE=$1; shift
+    local TEXT=$*
+
+    ${TTS} ${TTS_OPTS} -v pl -w pl/espeak/${TYPE}/HD/${FILE}.wav "${TEXT}"
+    sox pl/espeak/${TYPE}/HD/${FILE}.wav -r 16000 pl/espeak/${TYPE}/16000/${FILE}.wav
+    sox pl/espeak/${TYPE}/HD/${FILE}.wav -r 8000 pl/espeak/${TYPE}/8000/${FILE}.wav
+
+    echo "${TYPE} ${FILE}... done."
+}
+
+######################
+# IVR messages
+######################
+
+TYPE=digits
+
+while read FILE TEXT
+do
+    if [ "${FILE}" != "#" ]
+    then
+        mksound ${TYPE} ${FILE} ${TEXT}
+    else
+       echo "Comment: ${TEXT}"
+    fi
+done <<'EOT'
+# l. główne, r.męski, mianownik
+0      zero
+1      jeden
+2      dwa
+3      trzy
+4      cztery
+5      pięć
+6      sześć
+7      siedem
+8      osiem
+9      dziewięć
+10     dziesięć
+11     jedenaście
+12     dwanaście
+13     trzynaście
+14     czternaście
+15     pietnaście
+16     szesnaście
+17     siedemnaście
+18     osiemnaście
+19     dziewiętnaście
+20     dwadzieścia
+30     trzydzieści
+40     czterdzieści
+50     pięćdziesiąt
+60     sześćdziesiąt
+70     siedemdziesiąt
+80     osiemdziesiąc
+90     dziewięćdziesiąt
+100    sto
+200    dwieście
+300    trzysta
+400    czterysta
+500    pięćset
+600    sześćset
+700    siedemset
+800    osiemset
+900    dziewięćset
+1000   tysiąc
+1000a  tysiące
+1000s  tysięcy
+1000000        milion
+1000000a       miliony
+1000000s       milionów
+# l. główne, r.żeński, mianownik
+1_f    jedna
+2_f    dwie
+# l. główne, r.nijaki, mianownik
+1_n    jedno
+2_n    dwie
+# l. porządowe, r.męski, mianownik
+0_pm   zerowy
+1_pm   pierwszy
+2_pm   drugi
+3_pm   trzeci
+4_pm   czwarty
+5_pm   piąty
+6_pm   szósty
+7_pm   siódmy
+8_pm   ósmy
+9_pm   dziewiąty
+10_pm  dziesiąty
+11_pm  jedenasty
+12_pm  dwunasty
+13_pm  trzynasty
+14_pm  czternasty
+15_pm  piętnasty
+16_pm  szesnasty
+17_pm  siedemnasty
+18_pm  osiemnasty
+19_pm  dziewiętnasty
+20_pm  dwudziesty
+30_pm  trzydziesty
+40_pm  czterdziesty
+50_pm  pięćdziesiąty
+60_pm  sześćdziesiąty
+70_pm  siedemdziesiąty
+80_pm  osiemdziesiąty
+90_pm  dziewięćdziesiąty
+100_pm setny
+200_pm dwusetny
+300_pm trzysetny
+400_pm czterysetny
+500_pm pięćsetny
+600_pm sześćsetny
+700_pm siedemsetny
+800_pm osiemsetny
+900_pm dziewięćsetny
+1000_pm        tysięczny
+# l. porządowe specjane - numery dni, r.męski, mianownik
+01_pm  pierwszy
+02_pm  drugi
+03_pm  trzeci
+04_pm  czwarty
+05_pm  piąty
+06_pm  szósty
+07_pm  siódmy
+08_pm  ósmy
+09_pm  dziewiąty
+21_pm  dwudziesty pierwszy
+22_pm  dwudziesty drugi
+23_pm  dwudziesty trzeci
+24_pm  dwudziesty czwarty
+25_pm  dwudziesty piąty
+26_pm  dwudziesty szósty
+27_pm  dwudziesty siódmy
+28_pm  dwudziesty ósmy
+29_pm  dwudziesty dziewiąty
+31_pm  trzydziesty pierwszy
+
+# l. porządowe, r.żeski, mianownik
+0_pf   zerowa
+1_pf   pierwsza
+2_pf   druga
+3_pf   trzecia
+4_pf   czwarta
+5_pf   piąta
+6_pf   szósta
+7_pf   siódma
+8_pf   ósma
+9_pf   dziewiąta
+10_pf  dziesiąta
+11_pf  jedenasta
+12_pf  dwunasta
+13_pf  trzynasta
+14_pf  czternasta
+15_pf  piętnasta
+16_pf  szesnasta
+17_pf  siedemnasta
+18_pf  osiemnasta
+19_pf  dziewiętnasta
+20_pf  dwudziesta
+30_pf  trzydziesta
+40_pf  czterdziesta
+50_pf  pięćdziesiąta
+60_pf  sześćdziesiąta
+70_pf  siedemdziesiąta
+80_pf  osiemdziesiąta
+90_pf  dziewięćdziesiąta
+100_pf setna
+200_pf dwusetna
+300_pf trzysetna
+400_pf czterysetna
+500_pf pięćsetna
+600_pf sześćsetna
+700_pf siedemsetna
+800_pf osiemsetna
+900_pf dziewięćsetna
+1000_pf        tysięczna
+# l. porządowe, r.męski, dopełniacz
+0_pmD  zerowego
+1_pmD  pierwszego
+2_pmD  drugiego
+3_pmD  trzeciego
+4_pmD  czwartego
+5_pmD  piątego
+6_pmD  szóstego
+7_pmD  siódmego
+8_pmD  ósmego
+9_pmD  dziewiątego
+10_pmD dziesiątego
+11_pmD jedenastego
+12_pmD dwunastego
+13_pmD trzynastego
+14_pmD czternastego
+15_pmD piętnastego
+16_pmD szesnastego
+17_pmD siedemnastego
+18_pmD osiemnastego
+19_pmD dziewiętnastego
+20_pmD dwudziestego
+30_pmD trzydziestego
+40_pmD czterdziestego
+50_pmD pięćdziesiątego
+60_pmD sześćdziesiątego
+70_pmD siedemdziesiątego
+80_pmD osiemdziesiątego
+90_pmD dziewięćdziesiątego
+100_pmD        setnego
+200_pmD        dwusetnego
+300_pmD        trzysetnego
+400_pmD        czterysetnego
+500_pmD        pięćsetnego
+600_pmD        sześćsetnego
+700_pmD        siedemsetnego
+800_pmD        osiemsetnego
+900_pmD        dziewięćsetnego
+1000_pmD       tysięcznego
+# l. porządowe, r.żeski, dopełniacz
+0_pfD  zerowej
+1_pfD  pierwszej
+2_pfD  drugiej
+3_pfD  trzeciej
+4_pfD  czwartej
+5_pfD  piątej
+6_pfD  szóstej
+7_pfD  siódmej
+8_pfD  ósmej
+9_pfD  dziewiątej
+10_pfD dziesiątej
+11_pfD jedenastej
+12_pfD dwunastej
+13_pfD trzynastej
+14_pfD czternastej
+15_pfD piętnastej
+16_pfD szesnastej
+17_pfD siedemnastej
+18_pfD osiemnastej
+19_pfD dziewiętnastej
+20_pfD dwudziestej
+30_pfD trzydziestej
+40_pfD czterdziestej
+50_pfD pięćdziesiątej
+60_pfD sześćdziesiątej
+70_pfD siedemdziesiątej
+80_pfD osiemdziesiątej
+90_pfD dziewięćdziesiątej
+100_pfD        setnej
+200_pfD        dwusetnej
+300_pfD        trzysetnej
+400_pfD        czterysetnej
+500_pfD        pięćsetnej
+600_pfD        sześćsetnej
+700_pfD        siedemsetnej
+800_pfD        osiemsetnej
+900_pfD        dziewięćsetnej
+1000_pfD       tysięcznej
+# dodatkowe
+star   gwiazdka
+star_C gwiazdkę
+pound  krzyżyk
+dot    kropka
+dot_C  kropkę
+coma   przecinek
+EOT
+
+TYPE=time
+
+while read FILE TEXT
+do
+    if [ "${FILE}" != "#" ]
+    then
+        mksound ${TYPE} ${FILE} ${TEXT}
+    else
+       echo "Comment: ${TEXT}"
+    fi
+done <<'EOT'
+# l. jednostki czasu
+t_sekunda      sekunda
+t_sekundy      sekundy
+t_sekund       sekund
+t_minuta       minuta
+t_minuty       minuty
+t_minut        minut
+t_godzina      godzina
+t_godziny      godziny
+t_godzin       godzin
+t_dzien        dzień
+t_dni  dni
+t_tydzien      tydzień
+t_tygodnie     tygodnie
+t_tygodni      tygodni
+t_miesiac      miesiąc
+t_miesiace     miesiące
+t_miesiecy     miesięcy
+t_rok  rok
+t_roku roku
+t_lata lata
+t_lat  lat
+# dni tygodnia, mianownik
+day-0  niedziela
+day-1  poniedziałek
+day-2  wtorek
+day-3  środa
+day-4  czwartek
+day-5  piątek
+day-6  sobota
+# miesiące, mianownik
+mon-0  styczeń
+mon-1  luty
+mon-2  marzec
+mon-3  kwiecień
+mon-4  maj
+mon-5  czerwiec
+mon-6  lipiec
+mon-7  sierpień
+mon-8  wrzesień
+mon-9  październik
+mon-10 listopad
+mon-11 grudzień
+# miesiące, dopełniacz
+mon-0_D        stycznia
+mon-1_D        lutego
+mon-2_D        marca
+mon-3_D        kwietnia
+mon-4_D        maja
+mon-5_D        czerwieca
+mon-6_D        lipieca
+mon-7_D        sierpnia
+mon-8_D        września
+mon-9_D        października
+mon-10_D       listopada
+mon-11_D       grudnia
+# dodatkowe
+t_wczoraj      wczoraj
+t_dzisiaj      dzisiaj
+t_jutro                jutro
+EOT
+
+echo "ALL DONE."
\ No newline at end of file
diff --git a/src/mod/say/mod_say_pl/mod_say_pl.c b/src/mod/say/mod_say_pl/mod_say_pl.c
new file mode 100644 (file)
index 0000000..a5f7a7a
--- /dev/null
@@ -0,0 +1,673 @@
+/*
+ * Copyright (c) 2007, Anthony Minessale II
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 
+ * * Neither the name of the original author; nor the names of any contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * 
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The Initial Developer of the Original Code is
+ * Anthony Minessale II <anthm@freeswitch.org>
+ * Portions created by the Initial Developer are Copyright (C)
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * 
+ * Anthony Minessale II <anthm@freeswitch.org>
+ * Michael B. Murdock <mike@mmurdock.org>
+ * Mariusz Czułada <manieq.net@gmail.com>
+ *
+ * mod_say_pl.c -- Say for Polish
+ *
+ */
+
+#include <switch.h>
+#include <math.h>
+#include <ctype.h>
+
+SWITCH_MODULE_LOAD_FUNCTION(mod_say_pl_load);
+SWITCH_MODULE_DEFINITION(mod_say_pl, mod_say_pl_load, NULL, NULL);
+
+
+#define say_num(_sh, num, meth, gen) {                                                                 \
+               char tmp[80];                                                                                                   \
+               switch_status_t tstatus;                                                                                \
+               switch_say_method_t smeth = say_args->method;                                   \
+               switch_say_type_t stype = say_args->type;                                               \
+               switch_say_gender_t sgen = say_args->gender;                                    \
+               say_args->type = SST_ITEMS; say_args->method = meth;                    \
+               say_args->gender = gen;                                                                                 \
+               switch_snprintf(tmp, sizeof(tmp), "%u", (unsigned)num);                 \
+               if ((tstatus =                                                                                                  \
+                        pl_say_general_count(_sh, tmp, say_args))                                      \
+                       != SWITCH_STATUS_SUCCESS) {                                                                     \
+                       return tstatus;                                                                                         \
+               }                                                                                                                               \
+               say_args->method = smeth; say_args->type = stype;                               \
+               say_args->gender = sgen;                                                                                \
+       }                                                                                                                                       \
+
+static switch_status_t play_group(switch_say_method_t method, switch_say_gender_t gender, int a, int b, int c, char *what, switch_say_file_handle_t *sh)
+{
+       char tmp[80];
+
+       if (a) {
+               switch_say_file(sh, "digits/%d00", a);
+       }
+
+       if (b) {
+               if (b > 1) {
+                       if ((method == SSM_COUNTED)) {
+                               if (gender == SSG_MASCULINE)
+                                       switch_say_file(sh, "digits/%d0_pm", b);
+                               else if (gender == SSG_FEMININE)
+                                       switch_say_file(sh, "digits/%d0_pf", b);
+                               else if (gender == SSG_NEUTER)
+                                       switch_say_file(sh, "digits/%d0_pn", b);
+                       } else {
+                               switch_say_file(sh, "digits/%d0", b);
+                       }
+               } else {
+                       if ((method == SSM_COUNTED)) {
+                               if (gender == SSG_MASCULINE)
+                                       switch_say_file(sh, "digits/%d%d_pm", b, c);
+                               else if (gender == SSG_FEMININE)
+                                       switch_say_file(sh, "digits/%d%d_pf", b, c);
+                               else if (gender == SSG_NEUTER)
+                                       switch_say_file(sh, "digits/%d%d_pn", b, c);
+                       } else {
+                               switch_say_file(sh, "digits/%d%d", b, c);
+                       }
+                       c = 0;
+               }
+       }
+
+       if (c) {
+               if (method == SSM_COUNTED) {
+                       if (gender == SSG_MASCULINE && b != 1  && (c == 1 || c == 2)) {
+                               switch_say_file(sh, "digits/%d_pm", c);
+                       } else if (gender == SSG_FEMININE && b != 1  && (c == 1 || c == 2)) {
+                               switch_say_file(sh, "digits/%d_pf", c);
+                       } else {
+                               switch_say_file(sh, "digits/%d_pn", c);
+                       }
+               } else {
+                       if (gender == SSG_FEMININE && b != 1  && (c == 1 || c == 2)) {
+                               switch_say_file(sh, "digits/%d_f", c);
+                       } else {
+                               switch_say_file(sh, "digits/%d", c);
+                       }
+               }
+       }
+
+       if (what && (a || b || c)) {
+               if (!a && !b && c == 1) {
+                       switch_snprintf(tmp, sizeof(tmp), "%s", what);
+               } else if (b != 1 && (c == 2 || c == 3 || c == 4)) {
+                       switch_snprintf(tmp, sizeof(tmp), "%sa", what);
+               } else if (a*100+b*10+c > 4) {
+                       switch_snprintf(tmp, sizeof(tmp), "%ss", what);
+               }
+               switch_say_file(sh, tmp);
+       }
+
+       return SWITCH_STATUS_SUCCESS;
+}
+
+static switch_status_t pl_say_general_count(switch_say_file_handle_t *sh, char *tosay, switch_say_args_t *say_args)
+{
+       int in;
+       int x = 0;
+       int places[9] = { 0 };
+       char sbuf[128] = "";
+       switch_status_t status;
+
+       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "SAY: %s\n", tosay);
+
+       if (say_args->method == SSM_ITERATED) {
+               if ((tosay = switch_strip_commas(tosay, sbuf, sizeof(sbuf)-1))) {
+                       char *p;
+                       for (p = tosay; p && *p; p++) {
+                               switch_say_file(sh, "digits/%c", *p);
+                       }
+               } else {
+                       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Parse Error!\n");
+                       return SWITCH_STATUS_GENERR;
+               }
+               return SWITCH_STATUS_SUCCESS;
+       }
+
+       if (!(tosay = switch_strip_commas(tosay, sbuf, sizeof(sbuf)-1)) || strlen(tosay) > 9) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Parse Error!\n");
+               return SWITCH_STATUS_GENERR;
+       }
+
+       in = atoi(tosay);
+
+       if (in != 0) {
+               for (x = 8; x >= 0; x--) {
+                       int num = (int) pow(10, x);
+                       if ((places[(uint32_t) x] = in / num)) {
+                               in -= places[(uint32_t) x] * num;
+                       }
+               }
+
+               switch (say_args->method) {
+               case SSM_COUNTED:
+               case SSM_PRONOUNCED:
+                       if ((status = play_group(SSM_PRONOUNCED, say_args->gender, places[8], places[7], places[6], "digits/1000000", sh)) != SWITCH_STATUS_SUCCESS) {
+                               return status;
+                       }
+                       if ((status = play_group(SSM_PRONOUNCED, say_args->gender, places[5], places[4], places[3], "digits/1000", sh)) != SWITCH_STATUS_SUCCESS) {
+                               return status;
+                       }
+                       if ((status = play_group(say_args->method, say_args->gender, places[2], places[1], places[0], NULL, sh)) != SWITCH_STATUS_SUCCESS) {
+                               return status;
+                       }
+                       break;
+               default:
+                       break;
+               }
+       } else {
+               switch_say_file(sh, "digits/0");
+       }
+
+       return SWITCH_STATUS_SUCCESS;
+}
+
+static switch_status_t pl_say_time(switch_say_file_handle_t *sh, char *tosay, switch_say_args_t *say_args)
+{
+       int32_t t;
+       switch_time_t target = 0, target_now = 0;
+       switch_time_exp_t tm, tm_now;
+       uint8_t say_date = 0, say_time = 0, say_year = 0, say_month = 0, say_dow = 0, say_day = 0, say_yesterday = 0, say_today = 0;
+       const char *tz = NULL;
+
+       tz = switch_say_file_handle_get_variable(sh, "timezone");
+
+       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "SAY: %s\n", tosay);
+
+       if (say_args->type == SST_TIME_MEASUREMENT) {
+               int64_t hours = 0;
+               int64_t minutes = 0;
+               int64_t seconds = 0;
+               int64_t r = 0;
+
+               if (strchr(tosay, ':')) {
+                       char *tme = strdup(tosay);
+                       char *p;
+
+                       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Parse time string!\n");
+                       if ((p = strrchr(tme, ':'))) {
+                               *p++ = '\0';
+                               seconds = atoi(p);
+                               if ((p = strchr(tme, ':'))) {
+                                       *p++ = '\0';
+                                       minutes = atoi(p);
+                                       if (tme) {
+                                               hours = atoi(tme);
+                                       }
+                               } else {
+                                       minutes = atoi(tme);
+                               }
+                       }
+                       free(tme);
+               } else {
+                       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Parse time in seconds!\n");
+                       if ((seconds = atol(tosay)) <= 0) {
+                               seconds = (int64_t) switch_epoch_time_now(NULL);
+                       }
+
+                       if (seconds >= 60) {
+                               minutes = seconds / 60;
+                               r = seconds % 60;
+                               seconds = r;
+                       }
+
+                       if (minutes >= 60) {
+                               hours = minutes / 60;
+                               r = minutes % 60;
+                               minutes = r;
+                       }
+               }
+
+               if (hours) {
+                       int hd = 0, hj = 0;
+                       say_num(sh, hours, SSM_PRONOUNCED, SSG_FEMININE);
+                       hj = hours % 10;
+                       hd = (hours/10) % 10;
+                       if (hours == 1) {
+                               switch_say_file(sh, "time/t_godzina");
+                       }else if (hd != 1 && ( hj == 2 || hj == 3 || hj == 4)) {
+                               switch_say_file(sh, "time/t_godziny");
+                       } else {
+                               switch_say_file(sh, "time/t_godzin");
+                       }
+               } else {
+                       switch_say_file(sh, "digits/0");
+                       switch_say_file(sh, "time/t_godzin");
+               }
+
+               if (minutes) {
+                       int md = 0, mj = 0;
+                       say_num(sh, minutes, SSM_PRONOUNCED, SSG_FEMININE);
+                       mj = minutes % 10;
+                       md = (minutes/10) % 10;
+                       if (minutes == 1) {
+                               switch_say_file(sh, "time/minuta");
+                       }else if (md != 1 && ( mj == 2 || mj == 3 || mj == 4)) {
+                               switch_say_file(sh, "time/t_minuty");
+                       } else {
+                               switch_say_file(sh, "time/t_minut");
+                       }
+               } else {
+                       switch_say_file(sh, "digits/0");
+                       switch_say_file(sh, "time/t_minut");
+               }
+
+               if (seconds) {
+                       int sd = 0, sj = 0;
+                       say_num(sh, seconds, SSM_PRONOUNCED, SSG_FEMININE);
+                       sj = seconds % 10;
+                       sd = (seconds/10) % 10;
+                       if (seconds == 1) {
+                               switch_say_file(sh, "time/t_sekunda");
+                       }else if (sd != 1 && ( sj == 2 || sj == 3 || sj == 4)) {
+                               switch_say_file(sh, "time/t_sekundy");
+                       } else {
+                               switch_say_file(sh, "time/t_sekund");
+                       }
+               } else {
+                       switch_say_file(sh, "digits/0");
+                       switch_say_file(sh, "time/t_sekund");
+               }
+
+               return SWITCH_STATUS_SUCCESS;
+       }
+
+       if ((t = atol(tosay)) > 0) {
+                       target = switch_time_make(t, 0);
+                       target_now = switch_micro_time_now();
+       } else {
+                       target = switch_micro_time_now();
+                       target_now = switch_micro_time_now();
+       }
+
+       if (tz) {
+               int check = atoi(tz);
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Timezone is [%s]\n", tz);
+               if (check) {
+                       switch_time_exp_tz(&tm, target, check);
+                       switch_time_exp_tz(&tm_now, target_now, check);
+               } else {
+                       switch_time_exp_tz_name(tz, &tm, target);
+                       switch_time_exp_tz_name(tz, &tm_now, target_now);
+               }
+       } else {
+               switch_time_exp_lt(&tm, target);
+               switch_time_exp_lt(&tm_now, target_now);
+       }
+
+       switch (say_args->type) {
+       case SST_CURRENT_DATE_TIME:
+               say_date = say_time = 1;
+               break;
+       case SST_CURRENT_DATE:
+               say_date = 1;
+               break;
+       case SST_CURRENT_TIME:
+               say_time = 1;
+               break;
+       case SST_SHORT_DATE_TIME:
+               say_time = 1;
+               if (tm.tm_year != tm_now.tm_year) {
+                       say_date = 1;
+                       break;
+               }
+               if (tm.tm_yday == tm_now.tm_yday) {
+                       say_today = 1;
+                       break;
+               }
+               if (tm.tm_yday == tm_now.tm_yday - 1) {
+                       say_yesterday = 1;
+                       break;
+               }
+               if (tm.tm_yday >= tm_now.tm_yday - 5) {
+                       say_dow = 1;
+                       break;
+               }
+               if (tm.tm_mon != tm_now.tm_mon) {
+                       say_month = say_day = say_dow = 1;
+                       break;
+               }
+
+               say_month = say_day = say_dow = 1;
+
+               break;
+       default:
+               break;
+       }
+
+       if (say_today) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "SAY: today\n");
+               switch_say_file(sh, "time/t_dzisiaj");
+       }
+       if (say_yesterday) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "SAY: yesterday\n");
+               switch_say_file(sh, "time/t_wczoraj");
+       }
+       if (say_dow) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "SAY: dow\n");
+               switch_say_file(sh, "time/day-%d", tm.tm_wday);
+       }
+
+       if (say_date) {
+               say_year = say_month = say_day = say_dow = 1;
+               say_today = say_yesterday = 0;
+       }
+
+       if (say_day) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "SAY: day\n");
+               //say_num(sh, tm.tm_mday, SSM_COUNTED, SSG_MASCULINE);
+               switch_say_file(sh, "digits/%02d_pm", tm.tm_mday);
+       }
+       if (say_month) {
+               switch_say_file(sh, "time/mon-%d_D", tm.tm_mon);
+       }
+       if (say_year) {
+               say_num(sh, tm.tm_year + 1900, SSM_COUNTED, SSG_MASCULINE);
+               //switch_say_file(sh, "time/t_roku");
+       }
+
+       if (say_time) {
+               switch_say_file(sh, "time/t_godzina");
+               say_num(sh, tm.tm_hour, SSM_COUNTED, SSG_FEMININE);
+               //switch_say_file(sh, "digits/%da", tm.tm_hour);
+
+               say_num(sh, tm.tm_min, SSM_PRONOUNCED, SSG_FEMININE);
+               /* switch_say_file(sh, "digits/t_minut");*/
+       }
+
+       return SWITCH_STATUS_SUCCESS;
+}
+
+
+static switch_status_t pl_say_money(switch_say_file_handle_t *sh, char *tosay, switch_say_args_t *say_args)
+{
+       char sbuf[16] = "";                     /* enough for 999,999,999,999.99 (w/o the commas or leading $) */
+       char *dollars = NULL;
+       char *cents = NULL;
+
+       if (strlen(tosay) > 15 || !(tosay = switch_strip_nonnumerics(tosay, sbuf, sizeof(sbuf)-1))) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Parse Error!\n");
+               return SWITCH_STATUS_GENERR;
+       }
+
+       dollars = sbuf;
+
+       if ((cents = strchr(sbuf, '.'))) {
+               *cents++ = '\0';
+               if (strlen(cents) > 2) {
+                       cents[2] = '\0';
+               }
+       }
+
+       /* If positive sign - skip over" */
+       if (sbuf[0] == '+') {
+               dollars++;
+       }
+
+       /* If negative say "negative" */
+       if (sbuf[0] == '-') {
+               switch_say_file(sh, "currency/negative");
+               dollars++;
+       }
+
+       /* Say dollar amount */
+       pl_say_general_count(sh, dollars, say_args);
+       if (atoi(dollars) == 1) {
+               switch_say_file(sh, "currency/dollar");
+       } else {
+               switch_say_file(sh, "currency/dollars");
+       }
+
+       /* Say cents */
+       if (cents) {
+               /* Say "and" */
+               switch_say_file(sh, "currency/and");
+
+               pl_say_general_count(sh, cents, say_args);
+               if (atoi(cents) == 1) {
+                       switch_say_file(sh, "currency/cent");
+               } else {
+                       switch_say_file(sh, "currency/cents");
+               }
+       }
+
+       return SWITCH_STATUS_SUCCESS;
+}
+
+
+static switch_status_t pl_say_ip(switch_say_file_handle_t *sh,
+                                                         char *tosay,
+                                                         switch_say_args_t *say_args)
+       
+{
+       char *a, *b, *c, *d;
+       switch_status_t status = SWITCH_STATUS_FALSE;
+       
+       if (!(a = strdup(tosay))) {
+               abort();
+       }
+
+       if (!(b = strchr(a, '.'))) {
+               goto end;
+       }
+
+       *b++ = '\0';
+
+       if (!(c = strchr(b, '.'))) {
+               goto end;
+       }
+
+       *c++ = '\0';
+
+       if (!(d = strchr(c, '.'))) {
+               goto end;
+       }
+
+       *d++ = '\0';
+
+       say_num(sh, atoi(a), say_args->method, SSG_MASCULINE);
+       switch_say_file(sh, "digits/dot");
+       say_num(sh, atoi(b), say_args->method, SSG_MASCULINE);
+       switch_say_file(sh, "digits/dot");
+       say_num(sh, atoi(c), say_args->method, SSG_MASCULINE);
+       switch_say_file(sh, "digits/dot");
+       say_num(sh, atoi(d), say_args->method, SSG_MASCULINE);
+
+ end:
+       
+       free(a);
+
+       return status;
+}
+
+
+static switch_status_t pl_say_spell(switch_say_file_handle_t *sh, char *tosay, switch_say_args_t *say_args)
+{
+       char *p;
+
+       for (p = tosay; p && *p; p++) {
+               int a = tolower((int) *p);
+               if (a >= '0' && a <= '9') {
+                       switch_say_file(sh, "digits/%c", a);
+               } else {
+                       if (say_args->type == SST_NAME_SPELLED) {
+                               switch_say_file(sh, "ascii/%d", a);
+                       } else if (say_args->type == SST_NAME_PHONETIC) {
+                               switch_say_file(sh, "phonetic-ascii/%d", a);
+                       }
+               }
+       }
+
+       return SWITCH_STATUS_SUCCESS;
+}
+
+
+/* pl */
+static switch_new_say_callback_t choose_callback(switch_say_args_t *say_args)
+{
+       switch_new_say_callback_t say_cb = NULL;
+
+       switch (say_args->type) {
+       case SST_NUMBER:
+       case SST_ITEMS:
+       case SST_PERSONS:
+       case SST_MESSAGES:
+               say_cb = pl_say_general_count;
+               break;
+       case SST_TIME_MEASUREMENT:
+       case SST_CURRENT_DATE:
+       case SST_CURRENT_TIME:
+       case SST_CURRENT_DATE_TIME:
+       case SST_SHORT_DATE_TIME:
+               say_cb = pl_say_time;
+               break;
+       case SST_IP_ADDRESS:
+               say_cb = pl_say_ip;
+               break;
+       case SST_NAME_SPELLED:
+       case SST_NAME_PHONETIC:
+               say_cb = pl_say_spell;
+               break;
+       case SST_CURRENCY:
+               say_cb = pl_say_money;
+               break;
+       default:
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unknown Say type=[%d]\n", say_args->type);
+               break;
+       }
+
+       return say_cb;
+}
+
+
+static switch_status_t run_callback(switch_new_say_callback_t say_cb, char *tosay, switch_say_args_t *say_args, switch_core_session_t *session, char **rstr)
+{
+       switch_say_file_handle_t *sh;
+       switch_status_t status = SWITCH_STATUS_FALSE;
+       switch_event_t *var_event = NULL;
+       
+       if (session) {
+               switch_channel_t *channel = switch_core_session_get_channel(session);
+               switch_channel_get_variables(channel, &var_event);
+       }
+
+       switch_say_file_handle_create(&sh, say_args->ext, &var_event);
+       
+       status = say_cb(sh, tosay, say_args);
+
+       if ((*rstr = switch_say_file_handle_detach_path(sh))) {
+               status = SWITCH_STATUS_SUCCESS;
+       }
+
+       switch_say_file_handle_destroy(&sh);
+
+       return status;
+}
+
+/* PL */
+static switch_status_t pl_say(switch_core_session_t *session, char *tosay, switch_say_args_t *say_args, switch_input_args_t *args)
+{
+
+       switch_new_say_callback_t say_cb = NULL;
+       char *string = NULL;
+
+       switch_status_t status = SWITCH_STATUS_FALSE;
+
+       say_cb = choose_callback(say_args);
+
+       if (say_cb) {
+               status = run_callback(say_cb, tosay, say_args, session, &string);
+               if (session && string) {
+                       status = switch_ivr_play_file(session, NULL, string, args);
+               }
+               
+               switch_safe_free(string);
+       }
+
+       return status;
+}
+
+
+/* PL */
+static switch_status_t pl_say_string(switch_core_session_t *session, char *tosay, switch_say_args_t *say_args, char **rstr)
+{
+
+       switch_new_say_callback_t say_cb = NULL;
+       char *string = NULL;
+
+       switch_status_t status = SWITCH_STATUS_FALSE;
+
+       say_cb = choose_callback(say_args);
+
+       if (say_cb) {
+               status = run_callback(say_cb, tosay, say_args, session, &string);
+               if (string) {
+                       status = SWITCH_STATUS_SUCCESS;
+                       *rstr = string;
+               }
+       }
+
+       return status;
+}
+
+SWITCH_MODULE_LOAD_FUNCTION(mod_say_pl_load)
+{
+       switch_say_interface_t *say_interface;
+       /* connect my internal structure to the blank pointer passed to me */
+       *module_interface = switch_loadable_module_create_module_interface(pool, modname);
+       say_interface = switch_loadable_module_create_interface(*module_interface, SWITCH_SAY_INTERFACE);
+       say_interface->interface_name = "pl";
+       say_interface->say_function = pl_say;
+       say_interface->say_string_function = pl_say_string;
+       
+       /* indicate that the module should continue to be loaded */
+       return SWITCH_STATUS_SUCCESS;
+}
+
+/* For Emacs:
+ * Local Variables:
+ * mode:c
+ * indent-tabs-mode:t
+ * tab-width:4
+ * c-basic-offset:4
+ * End:
+ * For VIM:
+ * vim:set softtabstop=4 shiftwidth=4 tabstop=4:
+ */