Skip to content

Commit

Permalink
Update version to 0.3.9 in README.md and buildspec.json; add availabl… (
Browse files Browse the repository at this point in the history
#184)

* Update version to 0.3.9 in README.md and buildspec.json; add available versions section for clarity

* Add support for custom API translation provider and update configuration options

* Enable ccache support in CMake build scripts for improved build performance

* Add status message for detected ccache in CMake build scripts
  • Loading branch information
royshil authored Nov 25, 2024
1 parent a32e327 commit bb1db72
Show file tree
Hide file tree
Showing 16 changed files with 252 additions and 35 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build-project.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,9 @@ jobs:
id: ccache-cache
with:
path: ${{ github.workspace }}/.ccache
key: ${{ runner.os }}-ccache-${{ needs.check-event.outputs.config }}
key: ${{ runner.os }}-ccache-${{ needs.check-event.outputs.config }}-${{ matrix.architecture }}
restore-keys: |
${{ runner.os }}-ccache-
${{ runner.os }}-ccache-${{ matrix.architecture }}-
- name: Set Up Codesigning 🔑
uses: ./.github/actions/setup-macos-codesigning
Expand Down
22 changes: 18 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
[![Discord](https://img.shields.io/discord/1200229425141252116)](https://discord.gg/KbjGU2vvUz)
<br/>
Download:</br>
<a href="https://github.com/locaal-ai/obs-localvocal/releases/download/0.3.8/obs-localvocal-0.3.8-windows-x64-cuda-Installer.exe"><img src="https://img.shields.io/badge/Windows-0078D6?style=for-the-badge&logo=windows&logoColor=white" /></a>
<a href="https://github.com/locaal-ai/obs-localvocal/releases/download/0.3.8/obs-localvocal-0.3.8-macos-x86_64.pkg"><img src="https://img.shields.io/badge/mac%20Intel-000000?style=for-the-badge" /></a>
<a href="https://github.com/locaal-ai/obs-localvocal/releases/download/0.3.8/obs-localvocal-0.3.8-macos-arm64.pkg"><img src="https://img.shields.io/badge/mac%20M1/2/3-0a0a0a?style=for-the-badge&"/></a>
<a href="https://github.com/locaal-ai/obs-localvocal/releases/download/0.3.8/obs-localvocal-0.3.8-x86_64-linux-gnu.deb"><img src="https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black"/></a>
<a href="https://github.com/locaal-ai/obs-localvocal/releases/download/0.3.9/obs-localvocal-0.3.9-windows-x64-cuda-Installer.exe"><img src="https://img.shields.io/badge/Windows-0078D6?style=for-the-badge&logo=windows&logoColor=white" /></a>
<a href="https://github.com/locaal-ai/obs-localvocal/releases/download/0.3.9/obs-localvocal-0.3.9-macos-x86_64.pkg"><img src="https://img.shields.io/badge/mac%20Intel-000000?style=for-the-badge" /></a>
<a href="https://github.com/locaal-ai/obs-localvocal/releases/download/0.3.9/obs-localvocal-0.3.9-macos-arm64.pkg"><img src="https://img.shields.io/badge/mac%20M1/2/3-0a0a0a?style=for-the-badge&"/></a>
<a href="https://github.com/locaal-ai/obs-localvocal/releases/download/0.3.9/obs-localvocal-0.3.9-x86_64-linux-gnu.deb"><img src="https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black"/></a>


</div>
Expand Down Expand Up @@ -85,6 +85,20 @@ Check out our other plugins:
## Download
Check out the [latest releases](https://github.com/locaal-ai/obs-localvocal/releases) for downloads and install instructions.

### Available Versions

LocalVocal is available in multiple versions to cater to different hardware configurations and operating systems. Below is a brief explanation of the different versions you can download:

- **Windows CUDA**: This version is optimized for systems with NVIDIA GPUs and utilizes CUDA for accelerated performance. Make sure you have the latest NVidia GPU drivers installed.
- **Windows CPU**: This version is designed for systems without dedicated GPUs, running solely on the CPU.
- **Windows HIPBLAS**: This version utilizes the HIP framework from AMD that accelerates computation on AMD GPUs. (⚠️ Experimental ⚠️ Please provide feedback)
- **Windows Vulkan**: This version uses Vulkan for GPU-based acceleration across many vendors like NVidia, AMD, and Intel. (⚠️ Experimental ⚠️ Please provide feedback)
- **macOS Intel (x86_64)**: This version is for Mac computers with Intel processors.
- **macOS Apple Silicon (arm64)**: This version is optimized for Mac computers with Apple Silicon (M1, M2, etc.) processors.
- **Linux x86_64**: This version is for Linux systems with x86_64 architecture.

Make sure to download the version that matches your system's hardware and operating system for the best performance.

### Models
The plugin ships with the Tiny.en model, and will autonomously download other Whisper models through a dropdown.
There's also an option to select an external GGML Whisper model file if you have it on disk.
Expand Down
2 changes: 1 addition & 1 deletion buildspec.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
},
"name": "obs-localvocal",
"displayName": "OBS Localvocal",
"version": "0.3.8",
"version": "0.3.9",
"author": "Roy Shilkrot",
"website": "https://github.com/locaal-ai/obs-localvocal",
"email": "roy.shil@gmail.com",
Expand Down
8 changes: 8 additions & 0 deletions cmake/BuildCTranslate2.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ elseif(WIN32)
file(GLOB CT2_DLLS ${ctranslate2_fetch_SOURCE_DIR}/bin/*.dll)
install(FILES ${CT2_DLLS} DESTINATION "obs-plugins/64bit")
else()
# Enable ccache if available
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
message(STATUS "Found ccache: ${CCACHE_PROGRAM}")
set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
endif()

# build cpu_features from source
set(CPU_FEATURES_VERSION "0.9.0")
set(CPU_FEATURES_URL "https://github.com/google/cpu_features.git")
Expand Down
8 changes: 8 additions & 0 deletions cmake/BuildSentencepiece.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ elseif(WIN32)

else()

# Enable ccache if available
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
message(STATUS "Found ccache: ${CCACHE_PROGRAM}")
set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
endif()

set(SP_URL
"https://github.com/google/sentencepiece.git"
CACHE STRING "URL of sentencepiece repository")
Expand Down
8 changes: 8 additions & 0 deletions cmake/BuildWhispercpp.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,14 @@ elseif(WIN32)
file(GLOB WHISPER_DLLS ${whispercpp_fetch_SOURCE_DIR}/bin/*.dll)
install(FILES ${WHISPER_DLLS} DESTINATION "obs-plugins/64bit")
else()
# Enable ccache if available
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
message(STATUS "Found ccache: ${CCACHE_PROGRAM}")
set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
endif()

if(${CMAKE_BUILD_TYPE} STREQUAL Release OR ${CMAKE_BUILD_TYPE} STREQUAL RelWithDebInfo)
set(Whispercpp_BUILD_TYPE Release)
else()
Expand Down
4 changes: 4 additions & 0 deletions data/locale/en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,8 @@ Deepl-Translate="Deepl"
Bing-Translate="Bing Translate"
OpenAI-Translate="OpenAI"
Claude-Translate="Claude"
API-Translate="Custom API"
translate_cloud_deepl_free="Use Deepl Free API Endpoint"
translate_cloud_endpoint="API Endpoint"
translate_cloud_body="API Body"
translate_cloud_response_json_path="Response JSON Path"
13 changes: 4 additions & 9 deletions src/transcription-filter-callbacks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,22 +91,17 @@ void send_sentence_to_cloud_translation_async(const std::string &sentence,
gf->last_text_for_cloud_translation = sentence;
if (gf->translate_cloud && !sentence.empty()) {
obs_log(gf->log_level, "Translating text with cloud provider %s. %s -> %s",
gf->translate_cloud_provider.c_str(), source_language.c_str(),
gf->translate_cloud_config.provider.c_str(),
source_language.c_str(),
gf->translate_cloud_target_language.c_str());
std::string translated_text;
if (sentence == last_text) {
// do not translate the same sentence twice
callback(gf->last_text_cloud_translation);
return;
}
CloudTranslatorConfig config;
config.provider = gf->translate_cloud_provider;
config.access_key = gf->translate_cloud_api_key;
config.secret_key = gf->translate_cloud_secret_key;
config.free = gf->translate_cloud_deepl_free;
config.region = gf->translate_cloud_region;

translated_text = translate_cloud(config, sentence,

translated_text = translate_cloud(gf->translate_cloud_config, sentence,
gf->translate_cloud_target_language,
source_language);
if (!translated_text.empty()) {
Expand Down
7 changes: 2 additions & 5 deletions src/transcription-filter-data.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "whisper-utils/silero-vad-onnx.h"
#include "whisper-utils/whisper-processing.h"
#include "whisper-utils/token-buffer-thread.h"
#include "translation/cloud-translation/translation-cloud.h"

#define MAX_PREPROC_CHANNELS 10

Expand Down Expand Up @@ -92,16 +93,12 @@ struct transcription_filter_data {

// Cloud translation options
bool translate_cloud = false;
std::string translate_cloud_provider;
CloudTranslatorConfig translate_cloud_config;
std::string translate_cloud_target_language;
std::string translate_cloud_output;
std::string translate_cloud_api_key;
std::string translate_cloud_secret_key;
bool translate_cloud_only_full_sentences = true;
std::string last_text_for_cloud_translation;
std::string last_text_cloud_translation;
bool translate_cloud_deepl_free;
std::string translate_cloud_region;

// Transcription context sentences
int n_context_sentences;
Expand Down
38 changes: 34 additions & 4 deletions src/transcription-filter-properties.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ bool translation_cloud_provider_selection_callback(obs_properties_t *props, obs_
{
UNUSED_PARAMETER(p);
const char *provider = obs_data_get_string(s, "translate_cloud_provider");
// show the access key for all except the custom provider
obs_property_set_visible(obs_properties_get(props, "translate_cloud_api_key"),
strcmp(provider, "api") != 0);
obs_property_set_visible(obs_properties_get(props, "translate_cloud_deepl_free"),
strcmp(provider, "deepl") == 0);
// show the secret key input for the papago provider only
Expand All @@ -58,6 +61,14 @@ bool translation_cloud_provider_selection_callback(obs_properties_t *props, obs_
// show the region input for the azure provider only
obs_property_set_visible(obs_properties_get(props, "translate_cloud_region"),
strcmp(provider, "azure") == 0);
// show the endpoint and body input for the custom provider only
obs_property_set_visible(obs_properties_get(props, "translate_cloud_endpoint"),
strcmp(provider, "api") == 0);
obs_property_set_visible(obs_properties_get(props, "translate_cloud_body"),
strcmp(provider, "api") == 0);
// show the response json path input for the custom provider only
obs_property_set_visible(obs_properties_get(props, "translate_cloud_response_json_path"),
strcmp(provider, "api") == 0);
return true;
}

Expand All @@ -67,10 +78,12 @@ bool translation_cloud_options_callback(obs_properties_t *props, obs_property_t
UNUSED_PARAMETER(property);
// Show/Hide the cloud translation group options
const bool translate_enabled = obs_data_get_bool(settings, "translate_cloud");
for (const auto &prop : {"translate_cloud_provider", "translate_cloud_target_language",
"translate_cloud_output", "translate_cloud_api_key",
"translate_cloud_only_full_sentences",
"translate_cloud_secret_key", "translate_cloud_deepl_free"}) {
for (const auto &prop :
{"translate_cloud_provider", "translate_cloud_target_language",
"translate_cloud_output", "translate_cloud_api_key",
"translate_cloud_only_full_sentences", "translate_cloud_secret_key",
"translate_cloud_deepl_free", "translate_cloud_region", "translate_cloud_endpoint",
"translate_cloud_body", "translate_cloud_response_json_path"}) {
obs_property_set_visible(obs_properties_get(props, prop), translate_enabled);
}
if (translate_enabled) {
Expand Down Expand Up @@ -259,6 +272,7 @@ void add_translation_cloud_group_properties(obs_properties_t *ppts)
"openai");
obs_property_list_add_string(prop_translate_cloud_provider, MT_("Claude-Translate"),
"claude");
obs_property_list_add_string(prop_translate_cloud_provider, MT_("API-Translate"), "api");

// add callback to show/hide the free API option for deepl
obs_property_set_modified_callback(prop_translate_cloud_provider,
Expand Down Expand Up @@ -298,6 +312,16 @@ void add_translation_cloud_group_properties(obs_properties_t *ppts)
// add translate_cloud_region for azure
obs_properties_add_text(translation_cloud_group, "translate_cloud_region",
MT_("translate_cloud_region"), OBS_TEXT_DEFAULT);

// add input for API endpoint
obs_properties_add_text(translation_cloud_group, "translate_cloud_endpoint",
MT_("translate_cloud_endpoint"), OBS_TEXT_DEFAULT);
// add input for API body
obs_properties_add_text(translation_cloud_group, "translate_cloud_body",
MT_("translate_cloud_body"), OBS_TEXT_MULTILINE);
// add input for json response path
obs_properties_add_text(translation_cloud_group, "translate_cloud_response_json_path",
MT_("translate_cloud_response_json_path"), OBS_TEXT_DEFAULT);
}

void add_translation_group_properties(obs_properties_t *ppts)
Expand Down Expand Up @@ -667,6 +691,12 @@ void transcription_filter_defaults(obs_data_t *s)
obs_data_set_default_string(s, "translate_cloud_secret_key", "");
obs_data_set_default_bool(s, "translate_cloud_deepl_free", true);
obs_data_set_default_string(s, "translate_cloud_region", "eastus");
obs_data_set_default_string(s, "translate_cloud_endpoint",
"http://localhost:5000/translate");
obs_data_set_default_string(
s, "translate_cloud_body",
"{\n\t\"text\":\"{{sentence}}\",\n\t\"target\":\"{{target_language}}\"\n}");
obs_data_set_default_string(s, "translate_cloud_response_json_path", "translations.0.text");

// Whisper parameters
apply_whisper_params_defaults_on_settings(s);
Expand Down
15 changes: 10 additions & 5 deletions src/transcription-filter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -333,16 +333,21 @@ void transcription_filter_update(void *data, obs_data_t *s)
}

gf->translate_cloud = obs_data_get_bool(s, "translate_cloud");
gf->translate_cloud_provider = obs_data_get_string(s, "translate_cloud_provider");
gf->translate_cloud_config.provider = obs_data_get_string(s, "translate_cloud_provider");
gf->translate_cloud_target_language =
obs_data_get_string(s, "translate_cloud_target_language");
gf->translate_cloud_output = obs_data_get_string(s, "translate_cloud_output");
gf->translate_cloud_only_full_sentences =
obs_data_get_bool(s, "translate_cloud_only_full_sentences");
gf->translate_cloud_api_key = obs_data_get_string(s, "translate_cloud_api_key");
gf->translate_cloud_secret_key = obs_data_get_string(s, "translate_cloud_secret_key");
gf->translate_cloud_deepl_free = obs_data_get_bool(s, "translate_cloud_deepl_free");
gf->translate_cloud_region = obs_data_get_string(s, "translate_cloud_region");
gf->translate_cloud_config.access_key = obs_data_get_string(s, "translate_cloud_api_key");
gf->translate_cloud_config.secret_key =
obs_data_get_string(s, "translate_cloud_secret_key");
gf->translate_cloud_config.free = obs_data_get_bool(s, "translate_cloud_deepl_free");
gf->translate_cloud_config.region = obs_data_get_string(s, "translate_cloud_region");
gf->translate_cloud_config.endpoint = obs_data_get_string(s, "translate_cloud_endpoint");
gf->translate_cloud_config.body = obs_data_get_string(s, "translate_cloud_body");
gf->translate_cloud_config.response_json_path =
obs_data_get_string(s, "translate_cloud_response_json_path");

obs_log(gf->log_level, "update text source");
// update the text source
Expand Down
1 change: 1 addition & 0 deletions src/translation/cloud-translation/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ target_sources(
${CMAKE_SOURCE_DIR}/src/translation/cloud-translation/azure.cpp
${CMAKE_SOURCE_DIR}/src/translation/cloud-translation/claude.cpp
${CMAKE_SOURCE_DIR}/src/translation/cloud-translation/curl-helper.cpp
${CMAKE_SOURCE_DIR}/src/translation/cloud-translation/custom-api.cpp
${CMAKE_SOURCE_DIR}/src/translation/cloud-translation/deepl.cpp
${CMAKE_SOURCE_DIR}/src/translation/cloud-translation/google-cloud.cpp
${CMAKE_SOURCE_DIR}/src/translation/cloud-translation/openai.cpp
Expand Down
112 changes: 112 additions & 0 deletions src/translation/cloud-translation/custom-api.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#include "custom-api.h"
#include "curl-helper.h"
#include <nlohmann/json.hpp>
#include <sstream>
#include <regex>
#include <stdexcept>

using json = nlohmann::json;

CustomApiTranslator::CustomApiTranslator(const std::string &endpoint,
const std::string &body_template,
const std::string &response_json_path)
: endpoint_(endpoint),
body_template_(body_template),
response_json_path_(response_json_path),
curl_helper_(std::make_unique<CurlHelper>())
{
}

CustomApiTranslator::~CustomApiTranslator() = default;

std::string CustomApiTranslator::translate(const std::string &text, const std::string &target_lang,
const std::string &source_lang)
{
// first encode text to JSON compatible string
nlohmann::json tmp = text;
std::string textStr = tmp.dump();
// remove '"' from the beginning and end of the string
textStr = textStr.substr(1, textStr.size() - 2);
// then replace the placeholders in the body template
std::unordered_map<std::string, std::string> values = {
{"\\{\\{sentence\\}\\}", textStr},
{"\\{\\{target_lang\\}\\}", target_lang},
{"\\{\\{source_lang\\}\\}", source_lang}};

std::string body = replacePlaceholders(body_template_, values);
std::string response;

std::unique_ptr<CURL, decltype(&curl_easy_cleanup)> curl(curl_easy_init(),
curl_easy_cleanup);

if (!curl) {
throw std::runtime_error("Failed to initialize CURL session");
}

try {
// Set up curl options
curl_easy_setopt(curl.get(), CURLOPT_URL, endpoint_.c_str());
curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, CurlHelper::WriteCallback);
curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl.get(), CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(curl.get(), CURLOPT_SSL_VERIFYHOST, 2L);
curl_easy_setopt(curl.get(), CURLOPT_TIMEOUT, 30L);

// Set up POST request
curl_easy_setopt(curl.get(), CURLOPT_POST, 1L);
curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDS, body.c_str());

// Set up headers
struct curl_slist *headers = nullptr;
headers = curl_slist_append(headers, "Content-Type: application/json");
curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, headers);

// Perform request
CURLcode res = curl_easy_perform(curl.get());

// Clean up headers
curl_slist_free_all(headers);

if (res != CURLE_OK) {
throw TranslationError(std::string("CURL request failed: ") +
curl_easy_strerror(res));
}

return parseResponse(response);

} catch (const std::exception &e) {
throw TranslationError(std::string("JSON parsing error: ") + e.what());
}
}

std::string CustomApiTranslator::replacePlaceholders(
const std::string &template_str,
const std::unordered_map<std::string, std::string> &values) const
{
std::string result = template_str;
for (const auto &pair : values) {
try {
std::regex placeholder(pair.first);
result = std::regex_replace(result, placeholder, pair.second);
} catch (const std::regex_error &e) {
// Handle regex error
throw TranslationError(std::string("Regex error: ") + e.what());
}
}
return result;
}

std::string CustomApiTranslator::parseResponse(const std::string &response_str)
{
try {
// parse the JSON response
json response = json::parse(response_str);

// extract the translation from the JSON response
std::string response_out = response[response_json_path_];

return response_out;
} catch (const json::exception &e) {
throw TranslationError(std::string("JSON parsing error: ") + e.what());
}
}
Loading

0 comments on commit bb1db72

Please sign in to comment.