From e1f6bf795ecd73711847e7b3109665a2b0df119a Mon Sep 17 00:00:00 2001 From: Alex <40072887+alexdcrane@users.noreply.github.com> Date: Tue, 26 May 2026 15:18:16 -0700 Subject: [PATCH] IVR Menu Edit: Add text to speech option for new greetings (#7994) * IVR Menu Edit: Add text to speech option for new greetings * Update app_languages.php --- app/ivr_menus/app_languages.php | 161 +++++++++++++++++ app/ivr_menus/ivr_menu_edit.php | 302 +++++++++++++++++++++++++++++++- 2 files changed, 460 insertions(+), 3 deletions(-) diff --git a/app/ivr_menus/app_languages.php b/app/ivr_menus/app_languages.php index dba38d87f..7b4e59d38 100644 --- a/app/ivr_menus/app_languages.php +++ b/app/ivr_menus/app_languages.php @@ -945,6 +945,167 @@ $text['label-copy']['zh-cn'] = "复制"; $text['label-copy']['ja-jp'] = "コピー"; $text['label-copy']['ko-kr'] = "복사"; +$text['label-new']['en-us'] = "New"; +$text['label-new']['en-gb'] = "New"; +$text['label-new']['ar-eg'] = "جديدة"; +$text['label-new']['de-at'] = "Neu"; +$text['label-new']['de-ch'] = "Neu"; +$text['label-new']['de-de'] = "Neu"; +$text['label-new']['el-gr'] = "Νέος"; +$text['label-new']['es-cl'] = "Nueva"; +$text['label-new']['es-mx'] = "Nueva"; +$text['label-new']['fr-ca'] = "Nouveau"; +$text['label-new']['fr-fr'] = "Nouveau"; +$text['label-new']['he-il'] = "חדש"; +$text['label-new']['it-it'] = "Nuovo"; +$text['label-new']['ka-ge'] = "ახალი"; +$text['label-new']['nl-nl'] = "Nieuws"; +$text['label-new']['pl-pl'] = "Nowy"; +$text['label-new']['pt-br'] = "Nova"; +$text['label-new']['pt-pt'] = "Nova"; +$text['label-new']['ro-ro'] = "Nou"; +$text['label-new']['ru-ru'] = "Новый"; +$text['label-new']['sv-se'] = "Nytt"; +$text['label-new']['uk-ua'] = "Нові"; +$text['label-new']['tr-tr'] = "Yeni"; +$text['label-new']['zh-cn'] = "新的"; +$text['label-new']['ja-jp'] = "新しい"; +$text['label-new']['ko-kr'] = "새로운"; + +$text['label-text_to_speech']['en-us'] = "Text to Speech"; +$text['label-text_to_speech']['en-gb'] = "Text to Speech"; +$text['label-text_to_speech']['ar-eg'] = "تحويل النص إلى كلام"; +$text['label-text_to_speech']['de-at'] = "Text-to-Speech"; +$text['label-text_to_speech']['de-ch'] = "Text-to-Speech"; +$text['label-text_to_speech']['de-de'] = "Text-to-Speech"; +$text['label-text_to_speech']['el-gr'] = "Κείμενο σε Ομιλία"; +$text['label-text_to_speech']['es-cl'] = "Texto a voz"; +$text['label-text_to_speech']['es-mx'] = "Texto a voz"; +$text['label-text_to_speech']['fr-ca'] = "Synthèse vocale"; +$text['label-text_to_speech']['fr-fr'] = "Synthèse vocale"; +$text['label-text_to_speech']['he-il'] = "טקסט לדיבור"; +$text['label-text_to_speech']['it-it'] = "Testo a voce"; +$text['label-text_to_speech']['ka-ge'] = "ტექსტის ხმოვანი"; +$text['label-text_to_speech']['nl-nl'] = "Tekst naar spraak"; +$text['label-text_to_speech']['pl-pl'] = "Tekst na mowę"; +$text['label-text_to_speech']['pt-br'] = "Texto para fala"; +$text['label-text_to_speech']['pt-pt'] = "Texto para fala"; +$text['label-text_to_speech']['ro-ro'] = "Text la voce"; +$text['label-text_to_speech']['ru-ru'] = "Синтез речи"; +$text['label-text_to_speech']['sv-se'] = "Text till tal"; +$text['label-text_to_speech']['uk-ua'] = "Синтез мови"; +$text['label-text_to_speech']['tr-tr'] = "Metinden Konuşmaya"; +$text['label-text_to_speech']['zh-cn'] = "文字转语音"; +$text['label-text_to_speech']['ja-jp'] = "テキスト読み上げ"; +$text['label-text_to_speech']['ko-kr'] = "텍스트 음성 변환"; + +$text['label-message']['en-us'] = "Message"; +$text['label-message']['en-gb'] = "Message"; +$text['label-message']['ar-eg'] = "رسالة"; +$text['label-message']['de-at'] = "Nachricht"; +$text['label-message']['de-ch'] = "Nachricht"; +$text['label-message']['de-de'] = "Nachricht"; +$text['label-message']['el-gr'] = "Μήνυμα"; +$text['label-message']['es-cl'] = "Mensaje"; +$text['label-message']['es-mx'] = "Mensaje"; +$text['label-message']['fr-ca'] = "Message"; +$text['label-message']['fr-fr'] = "Message"; +$text['label-message']['he-il'] = "הודעה"; +$text['label-message']['it-it'] = "Messaggio"; +$text['label-message']['ka-ge'] = "შეტყობინება"; +$text['label-message']['nl-nl'] = "Boodschap"; +$text['label-message']['pl-pl'] = "Wiadomość"; +$text['label-message']['pt-br'] = "Mensagem"; +$text['label-message']['pt-pt'] = "Mensagem"; +$text['label-message']['ro-ro'] = "Mesaj"; +$text['label-message']['ru-ru'] = "Сообщение"; +$text['label-message']['sv-se'] = "Meddelande"; +$text['label-message']['uk-ua'] = "Повідомлення"; +$text['label-message']['zh-cn'] = "信息"; +$text['label-message']['ja-jp'] = "メッセージ"; +$text['label-message']['ko-kr'] = "메시지"; + +$text['label-speed']['en-us'] = "Speed"; +$text['label-speed']['en-gb'] = "Speed"; +$text['label-speed']['ar-eg'] = "سرعة"; +$text['label-speed']['de-at'] = "Geschwindigkeit"; +$text['label-speed']['de-ch'] = "Geschwindigkeit"; +$text['label-speed']['de-de'] = "Geschwindigkeit"; +$text['label-speed']['el-gr'] = "Ταχύτητα"; +$text['label-speed']['es-cl'] = "Velocidad"; +$text['label-speed']['es-mx'] = "Velocidad"; +$text['label-speed']['fr-ca'] = "Vitesse"; +$text['label-speed']['fr-fr'] = "Vitesse"; +$text['label-speed']['he-il'] = "מהירות"; +$text['label-speed']['it-it'] = "Velocità"; +$text['label-speed']['ka-ge'] = "სიჩქარე"; +$text['label-speed']['nl-nl'] = "Snelheid"; +$text['label-speed']['pl-pl'] = "Prędkość"; +$text['label-speed']['pt-br'] = "Velocidade"; +$text['label-speed']['pt-pt'] = "Velocidade"; +$text['label-speed']['ro-ro'] = "Viteză"; +$text['label-speed']['ru-ru'] = "Скорость"; +$text['label-speed']['sv-se'] = "Hastighet"; +$text['label-speed']['uk-ua'] = "Швидкість"; +$text['label-speed']['tr-tr'] = "Hız"; +$text['label-speed']['zh-cn'] = "速度"; +$text['label-speed']['ja-jp'] = "速度"; +$text['label-speed']['ko-kr'] = "속도"; + +$text['label-voice']['en-us'] = "Voice"; +$text['label-voice']['en-gb'] = "Voice"; +$text['label-voice']['ar-eg'] = "صوت"; +$text['label-voice']['de-at'] = "Stimme"; +$text['label-voice']['de-ch'] = "Stimme"; +$text['label-voice']['de-de'] = "Stimme"; +$text['label-voice']['el-gr'] = "Φωνή"; +$text['label-voice']['es-cl'] = "Voz"; +$text['label-voice']['es-mx'] = "Voz"; +$text['label-voice']['fr-ca'] = "Voix"; +$text['label-voice']['fr-fr'] = "Voix"; +$text['label-voice']['he-il'] = "קוֹל"; +$text['label-voice']['it-it'] = "Voce"; +$text['label-voice']['ka-ge'] = "ხმა"; +$text['label-voice']['nl-nl'] = "Stem"; +$text['label-voice']['pl-pl'] = "Głos"; +$text['label-voice']['pt-br'] = "Voz"; +$text['label-voice']['pt-pt'] = "Voz"; +$text['label-voice']['ro-ro'] = "Voce"; +$text['label-voice']['ru-ru'] = "Голос"; +$text['label-voice']['sv-se'] = "Röst"; +$text['label-voice']['uk-ua'] = "Голос"; +$text['label-voice']['tr-tr'] = "Ses"; +$text['label-voice']['zh-cn'] = "嗓音"; +$text['label-voice']['ja-jp'] = "声"; +$text['label-voice']['ko-kr'] = "목소리"; + +$text['label-recording_name']['en-us'] = "Recording Name"; +$text['label-recording_name']['en-gb'] = "Recording Name"; +$text['label-recording_name']['ar-eg'] = "الاسم المسجل"; +$text['label-recording_name']['de-at'] = "Name der Aufnahme"; +$text['label-recording_name']['de-ch'] = "Name der Aufnahme"; +$text['label-recording_name']['de-de'] = "Name der Aufnahme"; +$text['label-recording_name']['el-gr'] = "Όνομα εγγραφής"; +$text['label-recording_name']['es-cl'] = "Nombre de la grabación"; +$text['label-recording_name']['es-mx'] = "Nombre de la grabación"; +$text['label-recording_name']['fr-ca'] = "Nom de l'enregistrement"; +$text['label-recording_name']['fr-fr'] = "Nom de l'enregistrement"; +$text['label-recording_name']['he-il'] = "שם הקובץ מדיה"; +$text['label-recording_name']['it-it'] = "Nome Registrazione"; +$text['label-recording_name']['ka-ge'] = "ჩანაწერის სახელი"; +$text['label-recording_name']['nl-nl'] = "Opnamenaam"; +$text['label-recording_name']['pl-pl'] = "Nazwa nagrania"; +$text['label-recording_name']['pt-br'] = "Nome da gravação"; +$text['label-recording_name']['pt-pt'] = "Nome da gravação"; +$text['label-recording_name']['ro-ro'] = "Nume înregistrare"; +$text['label-recording_name']['ru-ru'] = "Название записи"; +$text['label-recording_name']['sv-se'] = "Namn på inspelning"; +$text['label-recording_name']['uk-ua'] = "Назва запису"; +$text['label-recording_name']['tr-tr'] = "Kayıt Adı"; +$text['label-recording_name']['zh-cn'] = "录音名称"; +$text['label-recording_name']['ja-jp'] = "録音名"; +$text['label-recording_name']['ko-kr'] = "녹음 이름"; + $text['header-option_list']['en-us'] = "Option List"; $text['header-option_list']['en-gb'] = "Option List"; $text['header-option_list']['ar-eg'] = "قائمة الخيارات"; diff --git a/app/ivr_menus/ivr_menu_edit.php b/app/ivr_menus/ivr_menu_edit.php index 7f989dd4c..7182fe6a2 100644 --- a/app/ivr_menus/ivr_menu_edit.php +++ b/app/ivr_menus/ivr_menu_edit.php @@ -50,6 +50,42 @@ $ivr_menu_greet_short = ''; $ivr_menu_description = ''; $ivr_menu_ringback = $settings->get('ivr_menu','default_ringback', 'local_stream://default'); + $recording_name = ''; + $recording_message = ''; + $recording_description = ''; + $recording_speed = '1.0'; + $recording_uuid = ''; + $speed_enabled = false; + $speed_options = []; + $translate_enabled = false; + $language_enabled = false; + +//set the variables + $domain_uuid = $_SESSION['domain_uuid']; + $domain_name = $_SESSION['domain_name']; + $user_uuid = $_SESSION['user_uuid']; + +//add the settings object + $settings = new settings(["domain_uuid" => $domain_uuid, "user_uuid" => $user_uuid]); + $speech_enabled = class_exists('speech') && $settings->get('speech', 'enabled', false); + $speech_engine = $settings->get('speech', 'engine', ''); + +//add the speech object and get the voices and languages arrays + if ($speech_enabled && !empty($speech_engine)) { + $speech = new speech($settings); + $voices = $speech->get_voices(); + $recording_extension = $speech->get_format(); + $speed_enabled = $speech->is_speed_enabled(); + $speed_options = $speed_enabled ? $speech->get_speed_options() : []; + + // Determine the aray type single, or multi + $voices_array_type = array_type($voices); + + // Sort the array by language code keys alphabetically + if ($voices_array_type == 'multi') { + ksort($voices); + } + } //initialize the destinations object $destination = new destinations; @@ -120,7 +156,6 @@ //get http post values and set them to php variables if (!empty($_POST)) { - //process the http post data by submitted action if (!empty($_POST['action']) && is_uuid($_POST['ivr_menu_uuid'])) { $array[0]['checked'] = 'true'; @@ -436,6 +471,76 @@ $p->add("dialplan_edit", "temp"); } + //build the recording array + $greeting_types = ['long', 'short']; + + foreach ($greeting_types as $x => $type) { + $greeting_type = 'greeting_'.$type; + $recording_name = $_POST[$greeting_type]['recording_name'] ?? ''; + $recording_voice = $_POST[$greeting_type]['recording_voice'] ?? ''; + $recording_speed = $_POST[$greeting_type]['recording_speed'] ?? '1.0'; + $recording_message = $_POST[$greeting_type]['recording_message'] ?? ''; + $recording_desc = $_POST[$greeting_type]['recording_description'] ?? ''; + + if (permission_exists('recording_edit') && !empty($recording_message)) { + $recording_uuid = uuid(); + + if (empty($recording_name)) { + $recording_name = 'recording'.$ivr_menu_extension.($type == 'short' ? '-'.$type : null).'-'.str_pad(random_int(0, 9999), 4, '0', STR_PAD_LEFT); + } + + //set the recording format for approved types + if (!in_array($recording_extension, ['mp3', 'wav'], true)) { + //default to wav + $recording_extension = 'wav'; + } + + //build the setting object and get the recording path + $recording_path = $settings->get('switch', 'recordings').'/'.$domain_name; + + //create the file name + $recording_filename = empty($_POST[$greeting_type]['recording_filename'] ?? '') ? preg_replace('#[^a-zA-Z0-9_\-]#', '_', $recording_name) : $_POST[$greeting_type]['recording_filename']; + if (!str_ends_with($recording_filename, ".$recording_extension")) { + $recording_filename .= ".$recording_extension"; + } + + //text to audio - make a new audio file from the message + $speech->audio_path = $recording_path; + $speech->audio_filename = $recording_filename; + $speech->audio_voice = $recording_voice; + if ($speed_enabled) { + $speech->audio_speed = (float)$recording_speed; + } + $speech->audio_message = $recording_message; + $speech->speech(); + + //fix invalid riff & data header lengths in generated wave file + if ($speech_engine == 'openai') { + $recording_filename_temp = str_replace('.'.$recording_extension, '.tmp.'.$recording_extension, $recording_filename); + if (file_exists($recording_path.'/'.$recording_filename)) { + exec('sox --ignore-length '.escapeshellarg($recording_path.'/'.$recording_filename).' '.escapeshellarg($recording_path.'/'.$recording_filename_temp)); + } + if (file_exists($recording_path.'/'.$recording_filename_temp)) { + recursive_delete($recording_path.'/'.$recording_filename); + exec('mv '.escapeshellarg($recording_path.'/'.$recording_filename_temp).' '.escapeshellarg($recording_path.'/'.$recording_filename)); + } + unset($recording_filename_temp); + } + + $array['recordings'][$x]['domain_uuid'] = $domain_uuid; + $array['recordings'][$x]['recording_uuid'] = $recording_uuid; + $array['recordings'][$x]['recording_filename'] = $recording_filename; + $array['recordings'][$x]['recording_name'] = $recording_name; + $array['recordings'][$x]['recording_voice'] = $speech_enabled ? $recording_voice : null; + $array['recordings'][$x]['recording_speed'] = ($speech_enabled && $speed_enabled) ? $recording_speed : null; + $array['recordings'][$x]['recording_message'] = $speech_enabled ? $recording_message : null; + $array['recordings'][$x]['recording_description'] = $recording_desc; + + // Update IVR menu fields directly + $array['ivr_menus'][0]["ivr_menu_greet_{$type}"] = $recording_filename; + } + } + //save to the data $database->save($array); $message = $database->message; @@ -858,6 +963,23 @@ echo "\n"; echo "\n"; + // Greet recording tts styles + echo "\n"; + $instance_id = 'ivr_menu_greet_long'; $instance_label = 'greet_long'; $instance_value = $ivr_menu_greet_long; @@ -869,9 +991,14 @@ echo "\n"; echo "