-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Start building a library for ElevenLabs' API
- Loading branch information
Showing
22 changed files
with
881 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
cmake_minimum_required(VERSION 3.25) | ||
|
||
|
||
project(CreatureVoicesLib | ||
VERSION "0.1.0" | ||
DESCRIPTION "Voice library for April's Creatures" | ||
HOMEPAGE_URL https://github.com/opsnlops/creature-server | ||
LANGUAGES C CXX) | ||
|
||
set(CMAKE_C_STANDARD 11) | ||
set(CMAKE_CXX_STANDARD 20) | ||
|
||
# Overshadowed declarations keep biting me in the butt 😅 | ||
add_compile_options(-Wshadow) | ||
add_compile_options(-Wall) | ||
add_compile_options(-Wextra) | ||
add_compile_options(-Wpedantic) | ||
|
||
set(PACKAGE_AUTHOR "April White") | ||
|
||
|
||
# Log loudly | ||
include(FetchContent) | ||
set(FETCHCONTENT_QUIET OFF) | ||
|
||
# This project uses threads | ||
set(THREADS_PREFER_PTHREAD_FLAG ON) | ||
find_package(Threads REQUIRED) | ||
|
||
|
||
find_package(PkgConfig REQUIRED) | ||
find_package(CURL REQUIRED) | ||
|
||
# fmt | ||
message(STATUS "fmt") | ||
FetchContent_Declare( | ||
fmt | ||
GIT_REPOSITORY https://github.com/fmtlib/fmt | ||
GIT_TAG 9.1.0 | ||
) | ||
FetchContent_MakeAvailable(fmt) | ||
set(FMT_HEADER_ONLY ON) | ||
set(FMT_LOCALE ON) | ||
|
||
|
||
# spdlog | ||
FetchContent_Declare( | ||
spdlog | ||
GIT_REPOSITORY https://github.com/gabime/spdlog | ||
GIT_TAG v1.11.0 | ||
) | ||
FetchContent_MakeAvailable(spdlog) | ||
|
||
|
||
# json | ||
FetchContent_Declare( | ||
json | ||
URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz) | ||
FetchContent_MakeAvailable(json) | ||
|
||
|
||
# Use the oatpp that we've already made | ||
find_package(oatpp REQUIRED PATHS ${CMAKE_SOURCE_DIR}/../../externals/install/lib/cmake/oatpp-1.3.0) | ||
|
||
# Define our library | ||
add_library(CreatureVoicesLib STATIC | ||
|
||
src/model/HttpMethod.h | ||
src/model/HttpMethod.cpp | ||
src/model/Voice.h | ||
src/model/Voice.cpp | ||
|
||
|
||
src/CreatureVoices.h | ||
src/CreatureVoices.cpp | ||
src/VoiceResult.h | ||
src/CurlBase.h | ||
src/CurlBase.cpp | ||
src/CurlHandle.h | ||
src/CurlHandle.cpp | ||
|
||
|
||
) | ||
|
||
# Include directories for this library | ||
target_include_directories(CreatureVoicesLib PUBLIC | ||
${CMAKE_CURRENT_SOURCE_DIR}/src | ||
${CURL_INCLUDE_DIR} | ||
${oatpp_INCLUDE_DIRS} | ||
) | ||
|
||
target_link_libraries(CreatureVoicesLib | ||
${CURL_LIBRARIES} | ||
spdlog::spdlog $<$<BOOL:${MINGW}>:ws2_32> | ||
fmt::fmt | ||
nlohmann_json::nlohmann_json | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
|
||
#include <algorithm> | ||
#include <string> | ||
#include <utility> | ||
#include <vector> | ||
|
||
#include <curl/curl.h> | ||
#include <fmt/format.h> | ||
#include <nlohmann/json.hpp> | ||
#include <spdlog/spdlog.h> | ||
|
||
|
||
#include "model/HttpMethod.h" | ||
#include "VoiceResult.h" | ||
#include "CreatureVoices.h" | ||
|
||
using json = nlohmann::json; | ||
|
||
namespace creatures::voice { | ||
|
||
|
||
CreatureVoices::CreatureVoices(std::string apiKey) : apiKey(std::move(apiKey)) {} | ||
|
||
|
||
VoiceResult<std::vector<Voice>> CreatureVoices::listAllAvailableVoices() { | ||
const std::string url ="https://api.elevenlabs.io/v1/voices"; | ||
|
||
debug("Fetching available voices"); | ||
|
||
auto curlHandle = createCurlHandle(url); | ||
auto res = performRequest(curlHandle, HttpMethod::GET, ""); | ||
if(!res.isSuccess()) { | ||
auto error = res.getError(); | ||
std::string errorMessage = fmt::format("Failed to fetch available voices: {}", error->getMessage()); | ||
curl_easy_cleanup(curlHandle.get()); | ||
return VoiceResult<std::vector<Voice>>{VoiceError(error->getCode(), error->getMessage())}; | ||
} | ||
curl_easy_cleanup(curlHandle.get()); | ||
|
||
auto httpResponse = res.getValue().value(); | ||
trace("httpResponse was: {}", httpResponse); | ||
|
||
|
||
json jsonResponse; | ||
try { | ||
jsonResponse = json::parse(httpResponse); | ||
} catch (const json::parse_error& e) { | ||
std::string errorMessage = fmt::format("Failed to parse JSON response: {}", e.what()); | ||
warn(errorMessage); | ||
return VoiceResult<std::vector<Voice>>{VoiceError(VoiceError::InvalidData, errorMessage)}; | ||
} | ||
|
||
std::vector<Voice> voices; | ||
for (const auto& item : jsonResponse["voices"]) { | ||
Voice voice; | ||
voice.voiceId = item["voice_id"].get<std::string>(); | ||
voice.name = item["name"].get<std::string>(); | ||
voices.push_back(voice); | ||
} | ||
debug("ElevenLabs gave us {} voices", voices.size()); | ||
|
||
|
||
// Sort the voices by name | ||
std::sort(voices.begin(), voices.end(), [](const Voice& a, const Voice& b) { | ||
return a.name < b.name; | ||
}); | ||
|
||
return voices; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
|
||
#pragma once | ||
|
||
|
||
#include <curl/curl.h> | ||
|
||
#include <string> | ||
#include <vector> | ||
|
||
#include "CurlBase.h" | ||
#include "VoiceResult.h" | ||
#include "model/HttpMethod.h" | ||
#include "model/Voice.h" | ||
|
||
|
||
namespace creatures :: voice { | ||
|
||
class CreatureVoices : public CurlBase { | ||
public: | ||
CreatureVoices(std::string apiKey); | ||
VoiceResult<std::vector<Voice>> listAllAvailableVoices(); | ||
|
||
private: | ||
std::string apiKey; | ||
|
||
}; | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
|
||
#include <algorithm> | ||
#include <string> | ||
#include <vector> | ||
|
||
#include <curl/curl.h> | ||
#include <fmt/format.h> | ||
#include <spdlog/spdlog.h> | ||
|
||
|
||
#include "model/HttpMethod.h" | ||
#include "VoiceResult.h" | ||
#include "CurlBase.h" | ||
|
||
|
||
namespace creatures::voice { | ||
|
||
|
||
size_t CurlBase::WriteCallback(char* ptr, size_t size, size_t nmemb, std::string* data) { | ||
data->append(ptr, size * nmemb); | ||
return size * nmemb; | ||
} | ||
|
||
|
||
CurlHandle CurlBase::createCurlHandle(const std::string& url) { | ||
debug("Creating a curl handle for URL: {}", url); | ||
return CurlHandle(url); | ||
} | ||
|
||
VoiceResult<std::string> CurlBase::performRequest(CurlHandle& curlHandle, | ||
HttpMethod method, | ||
const std::string& data) { | ||
|
||
debug("performing a request! method: {}, data: {}", httpMethodToString(method), data); | ||
|
||
std::string response; | ||
std::string errorMessage; | ||
|
||
// Make sure CURL is initialized | ||
if(!curlHandle.get()) { | ||
errorMessage = "Unable to perform request because CURL handle is not initialized"; | ||
error(errorMessage); | ||
return VoiceResult<std::string>{VoiceError(VoiceError::InternalError, errorMessage)}; | ||
} | ||
|
||
curl_easy_setopt(curlHandle.get(), CURLOPT_WRITEDATA, &response); | ||
trace("CURL handle set up for writing"); | ||
|
||
switch (method) { | ||
case HttpMethod::GET: | ||
// GET is the default method, no need to set anything special | ||
break; | ||
case HttpMethod::POST: | ||
curl_easy_setopt(curlHandle.get(), CURLOPT_POST, 1L); | ||
curl_easy_setopt(curlHandle.get(), CURLOPT_POSTFIELDS, data.c_str()); | ||
break; | ||
case HttpMethod::PUT: | ||
curl_easy_setopt(curlHandle.get(), CURLOPT_CUSTOMREQUEST, httpMethodToString(method).c_str()); | ||
curl_easy_setopt(curlHandle.get(), CURLOPT_POSTFIELDS, data.c_str()); | ||
break; | ||
case HttpMethod::DELETE: | ||
curl_easy_setopt(curlHandle.get(), CURLOPT_CUSTOMREQUEST, httpMethodToString(method).c_str()); | ||
break; | ||
default: | ||
errorMessage = fmt::format("Unknown HTTP method: {}", httpMethodToString(method)); | ||
error(errorMessage); | ||
curl_easy_cleanup(curlHandle.get()); | ||
return VoiceResult<std::string>{VoiceError(VoiceError::InternalError, errorMessage)}; | ||
} | ||
|
||
|
||
CURLcode res = curl_easy_perform(curlHandle.get()); | ||
if (res != CURLE_OK) { | ||
errorMessage = fmt::format("CURL request failed: {}", curl_easy_strerror(res)); | ||
error(errorMessage); | ||
curl_easy_cleanup(curlHandle.get()); | ||
return VoiceResult<std::string>{VoiceError(VoiceError::InternalError, errorMessage)}; | ||
} | ||
|
||
long http_code = 0; | ||
curl_easy_getinfo(curlHandle.get(), CURLINFO_RESPONSE_CODE, &http_code); | ||
|
||
// Let's look at the response code | ||
switch(http_code) { | ||
case 200: | ||
case 201: | ||
case 204: | ||
// These are all good responses | ||
break; | ||
case 301: | ||
case 302: | ||
errorMessage = fmt::format("HTTP error: {} - redirect", http_code); | ||
warn(errorMessage); | ||
curl_easy_cleanup(curlHandle.get()); | ||
return VoiceResult<std::string>{VoiceError(VoiceError::InvalidData, errorMessage)}; | ||
case 400: | ||
errorMessage = fmt::format("HTTP error: {} - bad request", http_code); | ||
warn(errorMessage); | ||
curl_easy_cleanup(curlHandle.get()); | ||
return VoiceResult<std::string>{VoiceError(VoiceError::InvalidData, errorMessage)}; | ||
case 401: | ||
case 403: | ||
errorMessage = fmt::format("HTTP error: {} - unauthorized", http_code); | ||
warn(errorMessage); | ||
curl_easy_cleanup(curlHandle.get()); | ||
return VoiceResult<std::string>{VoiceError(VoiceError::InvalidApiKey, errorMessage)}; | ||
case 404: | ||
errorMessage = fmt::format("HTTP error: {} - not found", http_code); | ||
warn(errorMessage); | ||
curl_easy_cleanup(curlHandle.get()); | ||
return VoiceResult<std::string>{VoiceError(VoiceError::NotFound, errorMessage)}; | ||
|
||
// Map everything else to an error | ||
default: | ||
errorMessage = fmt::format("HTTP error: {}", http_code); | ||
error(errorMessage); | ||
curl_easy_cleanup(curlHandle.get()); | ||
return VoiceResult<std::string>{VoiceError(VoiceError::InternalError, errorMessage)}; | ||
} | ||
|
||
|
||
// Looks good! We have good data | ||
curl_easy_cleanup(curlHandle.get()); | ||
|
||
debug("request successful!"); | ||
return VoiceResult<std::string>{response}; | ||
} | ||
} |
Oops, something went wrong.