ai conversation #49

Merged
talksik merged 14 commits from ai-conversation into main 2025-07-27 22:07:30 +00:00
6 changed files with 550 additions and 290 deletions
Showing only changes of commit c93267c9ee - Show all commits
+1 -1
View File
@@ -1,7 +1,7 @@
CC = gcc
CFLAGS = -Wall -g
TARGET = audio_capture
SRC = audio_capture.c
SRC = audio_capture.c deepgram_client.c utils.c
LIBS = -lavdevice -lavformat -lavcodec -lavutil -lasound -lcurl
$(TARGET): $(SRC)
+306 -289
View File
@@ -1,330 +1,347 @@
#include <stdio.h>
#include <stdlib.h>
#include "deepgram_client.h"
#include "utils.h"
#include <alsa/asoundlib.h>
#include <alsa/control.h>
#include <alsa/pcm.h>
#include <curl/curl.h>
#include <stdio.h>
#include <stdlib.h>
// NOTE: find this via `arecord -l`
#define ALSA_MIC_HARDWARE_DEVICE "hw:3"
#define ALSA_SPEAKER_HARDWARE_DEVICE "default"
void print_pcm_state(snd_pcm_t *pcm_handle) {
snd_pcm_state_t state = snd_pcm_state(pcm_handle);
const char *state_name = snd_pcm_state_name(state);
printf("State of pcm handle: %s\n", state_name);
snd_pcm_state_t state = snd_pcm_state(pcm_handle);
const char *state_name = snd_pcm_state_name(state);
printf("State of pcm handle: %s\n", state_name);
}
struct file_data {
FILE *file;
size_t size;
};
struct response_data {
char *data;
size_t size;
};
size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userdata) {
struct file_data *data = (struct file_data *)userdata;
size_t bytes_to_read = size * nmemb;
return fread(ptr, 1, bytes_to_read, data->file);
}
// Returns string that caller must free
char* find_transcript(char* json_response)
{
char *find_ai_response(char *json_response) {
printf("Parsing response: %s \n", json_response);
char* start = strstr(json_response, "\"transcript\":\"");
if (!start) return NULL;
char *start = strstr(json_response, "\"text\":\"");
if (!start)
return NULL;
start += 8; // Skip past "text":"
char *end = strchr(start, '"');
if (!end)
return NULL;
start += 14; // Skip past "transcript":"
char* end = strchr(start, '"'); // Find closing quote
// Copy substring between start and end
size_t len = end - start;
char* result = malloc(len + 1);
char *result = malloc(len + 1);
strncpy(result, start, len);
result[len] = '\0';
return result;
}
size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) {
size_t total_size = size * nmemb;
struct response_data *response = (struct response_data *)userp;
// Reallocate buffer to fit new data
response->data = realloc(response->data, response->size + total_size + 1);
if (response->data == NULL) {
return 0; // Error
}
// Copy new data to buffer
memcpy(&response->data[response->size], contents, total_size);
response->size += total_size;
response->data[response->size] = '\0'; // Null terminate
printf("read an extra %d bytes of data\n", (int)total_size);
return total_size;
}
// Returns transcript of audio
char* transcribe(FILE* audio_file) {
CURL *curl;
CURLcode res;
struct curl_slist *headers = NULL;
// Initialize response buffer
struct response_data response = {0};
response.data = malloc(1);
response.size = 0;
// Get file size
fseek(audio_file, 0, SEEK_END);
long file_size = ftell(audio_file);
fseek(audio_file, 0, SEEK_SET);
struct file_data data;
data.file = audio_file;
data.size = file_size;
curl = curl_easy_init();
if (curl) {
// Set headers for Deepgram API
headers = curl_slist_append(headers, "Authorization: Token YOUR_DEEPGRAM_API_KEY");
curl_easy_setopt(curl, CURLOPT_URL, "https://api.deepgram.com/v1/listen?encoding=linear16&sample_rate=48000&channels=1");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback);
curl_easy_setopt(curl, CURLOPT_READDATA, &data);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, file_size);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr, "curl failed: %s\n", curl_easy_strerror(res));
free(response.data);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
return NULL;
}
printf("done performing curl\n");
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
}
// Parse transcript from complete response
char* transcript = find_transcript(response.data);
free(response.data);
return transcript;
}
char* find_ai_response(char* json_response) {
printf("Parsing response: %s \n", json_response);
char* start = strstr(json_response, "\"text\":\"");
if (!start) return NULL;
start += 8; // Skip past "text":"
char* end = strchr(start, '"');
if (!end) return NULL;
size_t len = end - start;
char* result = malloc(len + 1);
strncpy(result, start, len);
result[len] = '\0';
return result;
}
char *ask_ai(char *question) {
CURL *curl;
CURLcode res;
struct curl_slist *headers = NULL;
CURL *curl;
CURLcode res;
struct curl_slist *headers = NULL;
// Initialize response buffer
struct response_data response = {0};
response.data = malloc(1);
response.size = 0;
// Initialize response buffer
struct curl_response_data response = {0};
response.data = malloc(1);
response.size = 0;
// Create JSON payload
char json_payload[2048];
snprintf(json_payload, sizeof(json_payload),
"{"
"\"model\":\"claude-3-5-sonnet-20241022\","
"\"max_tokens\":1024,"
"\"messages\":[{\"role\":\"user\",\"content\":\"%s\"}]"
"}", question);
// Create JSON payload
char json_payload[2048];
snprintf(json_payload, sizeof(json_payload),
"{"
"\"model\":\"claude-3-haiku-20240307\","
"\"max_tokens\":1024,"
"\"messages\":[{\"role\":\"user\",\"content\":\"%s\"}]"
"}",
question);
curl = curl_easy_init();
if (curl) {
// Set headers for Anthropic API
headers = curl_slist_append(headers, "content-Type: application/json");
headers = curl_slist_append(headers, "anthropic-version: 2023-06-01");
curl = curl_easy_init();
if (curl) {
char *anthropic_api_key = getenv("ANTHROPIC_API_KEY");
if (!anthropic_api_key) {
printf("ANTHROPIC_API_KEY not set\n");
free(response.data);
curl_easy_cleanup(curl);
return NULL;
}
char auth_header[256];
snprintf(auth_header, sizeof(auth_header), "x-api-key: %s",
anthropic_api_key);
headers = curl_slist_append(headers, "content-type: application/json");
headers = curl_slist_append(headers, auth_header);
headers = curl_slist_append(headers, "anthropic-version: 2023-06-01");
curl_easy_setopt(curl, CURLOPT_URL, "https://api.anthropic.com/v1/messages");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_payload);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_URL,
"https://api.anthropic.com/v1/messages");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_payload);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_callback_response);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr, "curl failed: %s\n", curl_easy_strerror(res));
free(response.data);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
return NULL;
}
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr, "curl failed: %s\n", curl_easy_strerror(res));
free(response.data);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
return NULL;
}
// Parse AI response from complete response
char* answer = find_ai_response(response.data);
free(response.data);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
}
return answer;
// Parse AI response from complete response
char *answer = find_ai_response(response.data);
free(response.data);
return answer;
}
/* count: how many bytes in the buffer
* returns int: 0 if success, -1 if failure
* NOTE: This function assumes that the audio data is using:
* - linear16 formatting (each sample is 16 bits)
* - one channel (1 sample)
* - with 48000 HZ sampling rate
* Hence, it's 2 bytes per frame.
*/
int play_pcm_data(char *buf, size_t byte_count) {
printf("going to play audio of %d bytes of data\n", (int)byte_count);
snd_pcm_t *pcm_handle;
// The total frames in the buffer is how many bytes divided by 2 because
// of linear16 encoding, one channel
size_t total_frames = byte_count / 2;
int err;
err = snd_pcm_open(&pcm_handle, ALSA_SPEAKER_HARDWARE_DEVICE,
SND_PCM_STREAM_PLAYBACK, 0);
if (err < 0) {
fprintf(stderr, "unable to open playback device");
return err;
}
print_pcm_state(pcm_handle);
const char *name = snd_pcm_name(pcm_handle);
printf("found this device: %s\n", name);
snd_pcm_info_t *pcm_info;
snd_pcm_info_malloc(&pcm_info);
err = snd_pcm_info(pcm_handle, pcm_info);
if (err < 0) {
snd_pcm_close(pcm_handle);
printf("unable to get info of pcm device");
return -1;
}
name = snd_pcm_info_get_name(pcm_info);
if (err < 0) {
printf("unable to get card info");
snd_pcm_close(pcm_handle);
snd_pcm_info_free(pcm_info);
return -1;
}
printf("got pcm name: %s \n", name);
print_pcm_state(pcm_handle);
snd_pcm_info_free(pcm_info);
if ((err = snd_pcm_set_params(pcm_handle, SND_PCM_FORMAT_S16_LE,
SND_PCM_ACCESS_RW_INTERLEAVED, 1, 48000, 1,
500000)) < 0) { /* 0.5sec */
printf("Playback open error: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
// we want to read in a chunk of data and writei to the pcm
size_t frames_written = 0;
size_t frames_in_each_chunk = 1024; // how many frames to read per chunk (2048
// bytes since each frame is 2 bytes)
while (frames_written < total_frames) {
size_t frames_to_write =
frames_in_each_chunk < (total_frames - frames_written)
? frames_in_each_chunk
: (total_frames - frames_written);
// We move the pointer x2 because each frame is 2 bytes so the next audio
// sample is 16 bits forward, not 8
char *start_ptr = buf + (frames_written * 2);
snd_pcm_sframes_t result =
snd_pcm_writei(pcm_handle, start_ptr, frames_to_write);
if (result < 0) {
result = snd_pcm_recover(pcm_handle, result, 0);
if (result < 0) {
printf("snd_pcm_writei failed: %s\n", snd_strerror(result));
break;
}
}
if (result > 0 && result < frames_to_write) {
printf("Short write \n");
}
frames_written += result;
}
/* pass the remaining samples, otherwise they're dropped in close */
err = snd_pcm_drain(pcm_handle);
if (err < 0) {
printf("snd_pcm_drain failed: %s\n", snd_strerror(err));
}
snd_pcm_close(pcm_handle);
return 0;
}
int main() {
snd_pcm_t *pcm_handle = NULL;
snd_pcm_t *pcm_handle = NULL;
int err;
err = snd_pcm_open(&pcm_handle, ALSA_MIC_HARDWARE_DEVICE, SND_PCM_STREAM_CAPTURE, 0);
if (err < 0) {
printf("unable to open pcm device");
return -1;
}
print_pcm_state(pcm_handle);
int err;
err = snd_pcm_open(&pcm_handle, ALSA_MIC_HARDWARE_DEVICE,
SND_PCM_STREAM_CAPTURE, 0);
if (err < 0) {
printf("unable to open pcm device");
return -1;
}
print_pcm_state(pcm_handle);
const char *name = snd_pcm_name(pcm_handle);
printf("found this device: %s\n", name);
const char *name = snd_pcm_name(pcm_handle);
printf("found this device: %s\n", name);
snd_pcm_info_t *pcm_info;
snd_pcm_info_malloc(&pcm_info);
err = snd_pcm_info(pcm_handle, pcm_info);
if (err < 0) {
snd_pcm_close(pcm_handle);
printf("unable to get info of pcm device");
return -1;
}
name = snd_pcm_info_get_name(pcm_info);
if (err < 0) {
printf("unable to get card info");
snd_pcm_close(pcm_handle);
snd_pcm_info_free(pcm_info);
return -1;
}
printf("got pcm name: %s \n", name);
print_pcm_state(pcm_handle);
snd_pcm_info_t *pcm_info;
snd_pcm_info_malloc(&pcm_info);
err = snd_pcm_info(pcm_handle, pcm_info);
if (err < 0) {
snd_pcm_close(pcm_handle);
printf("unable to get info of pcm device");
return -1;
}
name = snd_pcm_info_get_name(pcm_info);
if (err < 0) {
printf("unable to get card info");
snd_pcm_close(pcm_handle);
snd_pcm_info_free(pcm_info);
return -1;
}
printf("got pcm name: %s \n", name);
snd_pcm_hw_params_t *params;
err = snd_pcm_hw_params_malloc(&params);
if (err < 0) {
fprintf(stderr, "unable to mallow hw params");
return -1;
}
err = snd_pcm_hw_params_any(pcm_handle, params);
if (err < 0) {
fprintf(stderr, "unable to find ranges for hw device");
snd_pcm_close(pcm_handle);
snd_pcm_hw_params_free(params);
return -1;
}
print_pcm_state(pcm_handle);
snd_pcm_info_free(pcm_info);
uint period_size = 1024;
uint buffer_size = 4096;
uint channel_count = 1;
uint bytes_per_sample = 2; // FORMAT_S16_LE
uint samples_per_second = 48000;
snd_pcm_hw_params_set_access(pcm_handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
snd_pcm_hw_params_set_channels(pcm_handle, params, channel_count);
snd_pcm_hw_params_set_buffer_size(pcm_handle, params, buffer_size);
snd_pcm_hw_params_set_format(pcm_handle, params, SND_PCM_FORMAT_S16_LE);
snd_pcm_hw_params_set_rate(pcm_handle, params, samples_per_second, 0);
snd_pcm_hw_params_set_periods(pcm_handle, params, period_size, 0);
err = snd_pcm_hw_params(pcm_handle, params);
if (err < 0) {
fprintf(stderr, "unable to prepare pcm\n");
snd_pcm_close(pcm_handle);
snd_pcm_hw_params_free(params);
return -1;
}
print_pcm_state(pcm_handle);
err = snd_pcm_start(pcm_handle);
if (err < 0) {
fprintf(stderr, "unable to start pcm");
snd_pcm_close(pcm_handle);
snd_pcm_hw_params_free(params);
return -1;
}
print_pcm_state(pcm_handle);
const uint seconds_to_capture = 5;
const uint periods_to_read = (samples_per_second * seconds_to_capture) / period_size;
char *buf = malloc(period_size * channel_count * bytes_per_sample);
uint periods_read = 0;
/* Read one period at a time from the hardware buffer
* We set the hardware buffer to read 4096 frames.
* The period size is 1024 frames.
* Each period is 2048 BYTES because each frame is 2 bytes.
* Each frame is 2 bytes because of 1 channel, and 16 bit format
*/
FILE *file = fopen("output.pcm", "wb");
if (file == NULL) {
perror("Error opening file");
return 1;
}
printf("======READING FRAMES for 5 seconds========\n");
while (snd_pcm_readi(pcm_handle, (void *)buf, period_size) > 0 && periods_read < periods_to_read) {
// Because each frame is 2 bytes (format/bit rate), cast to int16
// int16_t *samples = (int16_t *)buf;
// for (int i = 0; i < period_size; i++) {
// printf("%d ", samples[i]);
// }
fwrite(buf, sizeof(int16_t), period_size, file);
periods_read++;
}
printf("done capturing audio\n");
fclose(file);
// Reopen file for reading and transcribe
file = fopen("output.pcm", "rb");
if (file != NULL) {
char* transcript = transcribe(file);
if (transcript) {
printf("Transcript: %s\n", transcript);
char *ai_answer = ask_ai(transcript);
if (ai_answer) {
printf("AI answer: %s\n", ai_answer);
free(ai_answer);
}
free(transcript);
} else {
fprintf(stderr, "unable to get transcript");
}
fclose(file);
} else {
fprintf(stderr, "unable to open file for reading\n");
}
free((void *)buf);
snd_pcm_hw_params_t *params;
err = snd_pcm_hw_params_malloc(&params);
if (err < 0) {
fprintf(stderr, "unable to mallow hw params");
return -1;
}
err = snd_pcm_hw_params_any(pcm_handle, params);
if (err < 0) {
fprintf(stderr, "unable to find ranges for hw device");
snd_pcm_close(pcm_handle);
snd_pcm_hw_params_free(params);
return -1;
}
return 0;
uint period_size = 1024;
uint buffer_size = 4096;
uint channel_count = 1;
uint bytes_per_sample = 2; // FORMAT_S16_LE
uint samples_per_second = 48000;
snd_pcm_hw_params_set_access(pcm_handle, params,
SND_PCM_ACCESS_RW_INTERLEAVED);
snd_pcm_hw_params_set_channels(pcm_handle, params, channel_count);
snd_pcm_hw_params_set_buffer_size(pcm_handle, params, buffer_size);
snd_pcm_hw_params_set_format(pcm_handle, params, SND_PCM_FORMAT_S16_LE);
snd_pcm_hw_params_set_rate(pcm_handle, params, samples_per_second, 0);
snd_pcm_hw_params_set_periods(pcm_handle, params, period_size, 0);
err = snd_pcm_hw_params(pcm_handle, params);
if (err < 0) {
fprintf(stderr, "unable to prepare pcm\n");
snd_pcm_close(pcm_handle);
snd_pcm_hw_params_free(params);
return -1;
}
print_pcm_state(pcm_handle);
err = snd_pcm_start(pcm_handle);
if (err < 0) {
fprintf(stderr, "unable to start pcm");
snd_pcm_close(pcm_handle);
snd_pcm_hw_params_free(params);
return -1;
}
print_pcm_state(pcm_handle);
const uint seconds_to_capture = 5;
const uint periods_to_read =
(samples_per_second * seconds_to_capture) / period_size;
char *buf = malloc(period_size * channel_count * bytes_per_sample);
uint periods_read = 0;
/* Read one period at a time from the hardware buffer
* We set the hardware buffer to read 4096 frames.
* The period size is 1024 frames.
* Each period is 2048 BYTES because each frame is 2 bytes.
* Each frame is 2 bytes because of 1 channel, and 16 bit format
*/
FILE *file = fopen("output.pcm", "wb");
if (file == NULL) {
perror("Error opening file");
return 1;
}
printf("======READING FRAMES for 5 seconds========\n");
while (snd_pcm_readi(pcm_handle, (void *)buf, period_size) > 0 &&
periods_read < periods_to_read) {
// Because each frame is 2 bytes (format/bit rate), cast to int16
// int16_t *samples = (int16_t *)buf;
// for (int i = 0; i < period_size; i++) {
// printf("%d ", samples[i]);
// }
fwrite(buf, sizeof(int16_t), period_size, file);
periods_read++;
}
printf("done capturing audio\n");
fclose(file);
// Reopen file for reading and transcribe
file = fopen("output.pcm", "rb");
if (file != NULL) {
char *transcript = dg_transcribe(file);
if (transcript) {
printf("Transcript: %s\n", transcript);
char *ai_answer = ask_ai(transcript);
if (ai_answer) {
printf("AI answer: %s\n", ai_answer);
struct curl_response_data *tts_response = dg_text_to_speech(ai_answer);
int result = play_pcm_data(tts_response->data, tts_response->size);
if (result != 0) {
fprintf(stderr, "failure in playback");
}
free(tts_response->data);
free(tts_response);
// we have this response data, and buffer within it. we can loop through
// it and write the pcm data within it to alsa
free(ai_answer);
}
free(transcript);
} else {
fprintf(stderr, "unable to get transcript");
}
fclose(file);
} else {
fprintf(stderr, "unable to open file for reading\n");
}
free(buf);
snd_pcm_close(pcm_handle);
snd_pcm_hw_params_free(params);
return 0;
}
+168
View File
@@ -0,0 +1,168 @@
#include "deepgram_client.h"
#include <string.h>
// Returns string that caller must free
static char *dg_find_transcript(char *json_response) {
printf("Parsing response: %s \n", json_response);
char *start = strstr(json_response, "\"transcript\":\"");
if (!start)
return NULL;
start += 14; // Skip past "transcript":"
char *end = strchr(start, '"'); // Find closing quote
// Copy substring between start and end
size_t len = end - start;
char *result = malloc(len + 1);
strncpy(result, start, len);
result[len] = '\0';
return result;
}
char *dg_transcribe(FILE *audio_file) {
CURL *curl;
CURLcode res;
struct curl_slist *headers = NULL;
// Initialize response buffer
struct curl_response_data response = {0};
response.data = malloc(1);
response.size = 0;
// Get file size
fseek(audio_file, 0, SEEK_END);
long file_size = ftell(audio_file);
fseek(audio_file, 0, SEEK_SET);
struct curl_file_data data;
data.file = audio_file;
data.size = file_size;
curl = curl_easy_init();
if (curl) {
// Set headers for Deepgram API
char *deepgram_api_key = getenv("DEEPGRAM_API_KEY");
if (!deepgram_api_key) {
fprintf(stderr, "DEEPGRAM_API_KEY environment variable not set\n");
free(response.data);
curl_easy_cleanup(curl);
return NULL;
}
char auth_header[256];
snprintf(auth_header, sizeof(auth_header), "Authorization: Token %s", deepgram_api_key);
headers = curl_slist_append(headers, auth_header);
curl_easy_setopt(curl, CURLOPT_URL,
"https://api.deepgram.com/v1/"
"listen?encoding=linear16&sample_rate=48000&channels=1");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_READFUNCTION, curl_read_callback_file);
curl_easy_setopt(curl, CURLOPT_READDATA, &data);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, file_size);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_callback_response);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr, "curl failed: %s\n", curl_easy_strerror(res));
free(response.data);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
return NULL;
}
printf("done performing curl\n");
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
}
// Parse transcript from complete response
char *transcript = dg_find_transcript(response.data);
free(response.data);
return transcript;
}
struct curl_response_data *dg_text_to_speech(char *text) {
CURL *curl;
CURLcode res;
struct curl_slist *headers = NULL;
// Create JSON payload - need to escape the text properly
// Worst case: every char needs escaping, so allocate 2x + overhead
size_t text_len = strlen(text);
size_t payload_size = text_len * 2 + 100;
char *json_payload = malloc(payload_size);
// Simple approach: replace problematic chars with spaces
char *escaped_text = malloc(text_len + 1);
strcpy(escaped_text, text);
for (int i = 0; escaped_text[i]; i++) {
if (escaped_text[i] == '"' || escaped_text[i] == '\n' ||
escaped_text[i] == '\r' || escaped_text[i] == '\t') {
escaped_text[i] = ' ';
}
}
snprintf(json_payload, payload_size,
"{"
"\"text\":\"%s\""
"}",
escaped_text);
free(escaped_text);
// Initialize response buffer
struct curl_response_data *response =
malloc(sizeof(struct curl_response_data));
response->data = malloc(1);
response->size = 0;
curl = curl_easy_init();
if (curl) {
char *deepgram_api_key = getenv("DEEPGRAM_API_KEY");
if (!deepgram_api_key) {
fprintf(stderr, "DEEPGRAM_API_KEY environment variable not set\n");
free(response->data);
curl_easy_cleanup(curl);
return NULL;
}
char auth_header[256];
snprintf(auth_header, sizeof(auth_header), "Authorization: Token %s", deepgram_api_key);
headers = curl_slist_append(headers, auth_header);
headers = curl_slist_append(headers, "Content-Type: application/json");
curl_easy_setopt(curl, CURLOPT_URL,
"https://api.deepgram.com/v1/"
"speak?encoding=linear16&sample_rate=48000");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_payload);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_callback_response);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, response);
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr, "curl failed: %s\n", curl_easy_strerror(res));
free(response->data);
free(response);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
return NULL;
}
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
}
printf("received audio of %d length for tts\n", (int)response->size);
// Debug: print first 100 chars to see if it's audio or error message
printf("First 100 chars: %.100s\n", response->data);
free(json_payload);
return response;
}
+21
View File
@@ -0,0 +1,21 @@
#ifndef DEEPGRAMCLIENT_H
#define DEEPGRAMCLIENT_H
#include <curl/curl.h>
#include <stdio.h>
#include <stdlib.h>
#include "utils.h"
/*
* @param audio_file: must already be opened
* @returns char *: transcript string result. Call must free.
*/
char *dg_transcribe(FILE *audio_file);
/*
* @returns response data. Caller must free the struct AND the data within it.
* TODO: should return custom struct with details about encoding, sample rate, etc.
*/
struct curl_response_data *dg_text_to_speech(char *text);
#endif // DEEPGRAMCLIENT_H
+30
View File
@@ -0,0 +1,30 @@
#include "utils.h"
#include <string.h>
size_t curl_read_callback_file(void *ptr, size_t size, size_t nmemb,
void *userdata) {
struct curl_file_data *data = (struct curl_file_data *)userdata;
size_t bytes_to_read = size * nmemb;
return fread(ptr, 1, bytes_to_read, data->file);
}
size_t curl_write_callback_response(void *contents, size_t size, size_t nmemb,
void *userp) {
size_t total_size = size * nmemb;
struct curl_response_data *response = (struct curl_response_data *)userp;
// Reallocate buffer to fit new data
response->data = realloc(response->data, response->size + total_size + 1);
if (response->data == NULL) {
return 0; // Error
}
// Copy new data to buffer
memcpy(&response->data[response->size], contents, total_size);
response->size += total_size;
response->data[response->size] = '\0'; // Null terminate
printf("read an extra %d bytes of data\n", (int)total_size);
return total_size;
}
+24
View File
@@ -0,0 +1,24 @@
#ifndef UTILS_H
#define UTILS_H
#include <stdlib.h>
#include <stdio.h>
struct curl_file_data {
FILE *file;
size_t size;
};
struct curl_response_data {
char *data;
size_t size;
};
// This callback assumes the userdata is `struct file_data`
size_t curl_read_callback_file(void *ptr, size_t size, size_t nmemb, void *userdata);
// This callback assumes that we are aggregating callback data into an allocated response_data struct
size_t curl_write_callback_response(void *contents, size_t size, size_t nmemb,
void *userp);
#endif // UTILS_H