Skip to content

Commit 53bfe75

Browse files
author
aks
committed
imported
1 parent 4902ac7 commit 53bfe75

File tree

8 files changed

+975
-1
lines changed

8 files changed

+975
-1
lines changed

README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,16 @@
1-
# mod_openai_asr
1+
<p>
2+
Provides an OpenAI Speech-To-Text service for the Freeswitch. <br>
3+
Also capable to work with <a href="https://github.com/akscf/whisperd target="_blank">whisperd</a>
4+
</p>
5+
6+
### Usage example
7+
```XML
8+
<extension name="openai-asr">
9+
<condition field="destination_number" expression="^(3222)$">
10+
<action application="answer"/>
11+
<action application="play_and_detect_speech" data="/tmp/test2.wav detect:openai"/>
12+
<action application="sleep" data="1000"/>
13+
<action application="hangup"/>
14+
</condition>
15+
</extension>
16+
```

sources/Makefile.am

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
include $(top_srcdir)/build/modmake.rulesam
3+
4+
MODNAME=mod_openai_asr
5+
mod_LTLIBRARIES = mod_openai_asr.la
6+
mod_openai_asr_la_SOURCES = mod_openai_asr.c utils.c curl.c
7+
mod_openai_asr_la_CFLAGS = $(AM_CFLAGS) -I. -Wno-unused-variable -Wno-unused-function -Wno-unused-but-set-variable -Wno-unused-label -Wno-declaration-after-statement -Wno-pointer-sign -Wno-pointer-arith
8+
mod_openai_asr_la_LIBADD = $(switch_builddir)/libfreeswitch.la
9+
mod_openai_asr_la_LDFLAGS = -avoid-version -module -no-undefined -shared
10+
11+
$(am_mod_openai_asr_la_OBJECTS): mod_openai_asr.h
12+
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<configuration name="openai_asr.conf" description="">
2+
<settings>
3+
<!-- for whisperd
4+
<param name="api-url" value="http://127.0.0.1:8080/v1/audio/transcriptions" />
5+
<param name="api-key" value="secret123" />
6+
-->
7+
<!-- for openai service -->
8+
<param name="api-url" value="https://api.openai.com/v1/audio/transcriptions" />
9+
<param name="api-key" value="---YOUR-API-KEY---" />
10+
11+
<param name="connect-timeout" value="10" />
12+
<param name="request-timeout" value="25" />
13+
<param name="log-http-errors" value="true" />
14+
15+
<!-- <param name="proxy" value="http://proxy:port" /> -->
16+
<!-- <param name="proxy-credentials" value="" /> -->
17+
<!-- <param name="user-agent" value="Mozilla/1.0" /> -->
18+
19+
<param name="chunk-size-sec" value="15" />
20+
21+
<param name="vad-enable" value="true" />
22+
<param name="vad-debug" value="false" />
23+
<param name="vad-silence-ms" value="500" />
24+
<param name="vad-voice-ms" value="200" />
25+
<param name="vad-threshold" value="100" />
26+
27+
<param name="encoding" value="wav" />
28+
<param name="model" value="whisper-1" />
29+
</settings>
30+
</configuration>

sources/conf/dialplan/dialplan.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<extension name="openai-asr">
2+
<condition field="destination_number" expression="^(3222)$">
3+
<action application="answer"/>
4+
<action application="play_and_detect_speech" data="/tmp/test2.wav detect:openai"/>
5+
<action application="sleep" data="1000"/>
6+
<action application="hangup"/>
7+
</condition>
8+
</extension>

sources/curl.c

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/**
2+
* (C)2023 aks
3+
* https://github.com/akscf/
4+
**/
5+
#include "mod_openai_asr.h"
6+
7+
extern globals_t globals;
8+
9+
static size_t curl_io_write_callback(char *buffer, size_t size, size_t nitems, void *user_data) {
10+
switch_buffer_t *recv_buffer = (switch_buffer_t *)user_data;
11+
size_t len = (size * nitems);
12+
13+
if(len > 0 && recv_buffer) {
14+
switch_buffer_write(recv_buffer, buffer, len);
15+
}
16+
17+
return len;
18+
}
19+
20+
switch_status_t curl_perform(switch_buffer_t *recv_buffer, char *model_name, char *chunk_file, char *ext_opts) {
21+
switch_status_t status = SWITCH_STATUS_SUCCESS;
22+
CURL *curl_handle = NULL;
23+
curl_mime *form = NULL;
24+
curl_mimepart *field1=NULL, *field2=NULL, *field3=NULL;
25+
switch_curl_slist_t *headers = NULL;
26+
switch_CURLcode curl_ret = 0;
27+
long http_resp = 0;
28+
29+
curl_handle = switch_curl_easy_init();
30+
headers = switch_curl_slist_append(headers, "Content-Type: multipart/form-data");
31+
32+
switch_curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headers);
33+
switch_curl_easy_setopt(curl_handle, CURLOPT_POST, 1);
34+
switch_curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
35+
switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, curl_io_write_callback);
36+
switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) recv_buffer);
37+
38+
if(globals.connect_timeout > 0) {
39+
switch_curl_easy_setopt(curl_handle, CURLOPT_CONNECTTIMEOUT, globals.connect_timeout);
40+
}
41+
if(globals.request_timeout > 0) {
42+
switch_curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, globals.request_timeout);
43+
}
44+
if(globals.user_agent) {
45+
switch_curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, globals.user_agent);
46+
}
47+
if(strncasecmp(globals.api_url, "https", 5) == 0) {
48+
switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0);
49+
switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0);
50+
}
51+
if(globals.proxy) {
52+
if(globals.proxy_credentials != NULL) {
53+
switch_curl_easy_setopt(curl_handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
54+
switch_curl_easy_setopt(curl_handle, CURLOPT_PROXYUSERPWD, globals.proxy_credentials);
55+
}
56+
if(strncasecmp(globals.proxy, "https", 5) == 0) {
57+
switch_curl_easy_setopt(curl_handle, CURLOPT_PROXY_SSL_VERIFYPEER, 0);
58+
}
59+
switch_curl_easy_setopt(curl_handle, CURLOPT_PROXY, globals.proxy);
60+
}
61+
62+
curl_easy_setopt(curl_handle, CURLOPT_XOAUTH2_BEARER, globals.api_key);
63+
curl_easy_setopt(curl_handle, CURLOPT_HTTPAUTH, CURLAUTH_BEARER);
64+
65+
if((form = curl_mime_init(curl_handle))) {
66+
if(ext_opts) {
67+
if((field1 = curl_mime_addpart(form))) {
68+
curl_mime_name(field1, "opts");
69+
curl_mime_data(field1, ext_opts, CURL_ZERO_TERMINATED);
70+
}
71+
}
72+
if((field2 = curl_mime_addpart(form))) {
73+
curl_mime_name(field2, "model");
74+
curl_mime_data(field2, model_name, CURL_ZERO_TERMINATED);
75+
}
76+
if((field3 = curl_mime_addpart(form))) {
77+
curl_mime_name(field3, "file");
78+
curl_mime_filedata(field3, chunk_file);
79+
}
80+
switch_curl_easy_setopt(curl_handle, CURLOPT_MIMEPOST, form);
81+
}
82+
83+
headers = switch_curl_slist_append(headers, "Expect:");
84+
switch_curl_easy_setopt(curl_handle, CURLOPT_URL, globals.api_url);
85+
86+
curl_ret = switch_curl_easy_perform(curl_handle);
87+
if(!curl_ret) {
88+
switch_curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &http_resp);
89+
if(!http_resp) { switch_curl_easy_getinfo(curl_handle, CURLINFO_HTTP_CONNECTCODE, &http_resp); }
90+
} else {
91+
http_resp = curl_ret;
92+
}
93+
94+
if(http_resp != 200) {
95+
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "http-error=[%ld] (%s)\n", http_resp, globals.api_url);
96+
status = SWITCH_STATUS_FALSE;
97+
}
98+
99+
if(recv_buffer) {
100+
if(switch_buffer_inuse(recv_buffer) > 0) {
101+
switch_buffer_write(recv_buffer, "\0", 1);
102+
}
103+
}
104+
105+
if(curl_handle) {
106+
switch_curl_easy_cleanup(curl_handle);
107+
}
108+
if(form) {
109+
curl_mime_free(form);
110+
}
111+
if(headers) {
112+
switch_curl_slist_free_all(headers);
113+
}
114+
115+
return status;
116+
}

0 commit comments

Comments
 (0)