8870741c3e
* capture audio with alsa * add brainstorm thoughts * use basecamp project for managing this project * add libcurl basic example * write pcm data to file * transcribe audio file with deepgram * transcribe raw audio * hit anthropic API for ai question * create full flow with stt, anthropic, tts, and playback This organizes some components like deepgram into own module, and also allows gets the full flow to work every time we run the program. * organize audio and intelligence modules * organize header files into include directory * docs: add readme * docs: explain future work * docs: add disclaimer about hardcoded audio params
169 lines
5.1 KiB
C
169 lines
5.1 KiB
C
#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;
|
|
}
|