Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,15 @@ jobs:
cmake
-DVOXLOCAL_PLATFORM=linux
-DVOXLOCAL_PAYLOAD_DIR=${{ github.workspace }}/package/plugin
-DVOXLOCAL_OUTPUT=${{ github.workspace }}/release/VoxLocal-Setup-1.1.1-Linux-x86_64.run
-DVOXLOCAL_OUTPUT=${{ github.workspace }}/release/VoxLocal-Setup-1.1.2-Linux-x86_64.run
-DVOXLOCAL_WORK_DIR=${{ github.workspace }}/build/installer-linux
-P cmake/package-installer.cmake
- name: Mark setup executable
run: chmod +x release/VoxLocal-Setup-1.1.1-Linux-x86_64.run
run: chmod +x release/VoxLocal-Setup-1.1.2-Linux-x86_64.run
- uses: actions/upload-artifact@v4
with:
name: VoxLocal-Setup-1.1.1-Linux-x86_64
path: release/VoxLocal-Setup-1.1.1-Linux-x86_64.run
name: VoxLocal-Setup-1.1.2-Linux-x86_64
path: release/VoxLocal-Setup-1.1.2-Linux-x86_64.run
if-no-files-found: error

windows-plugin:
Expand Down Expand Up @@ -170,13 +170,13 @@ jobs:
cmake
-DVOXLOCAL_PLATFORM=windows
-DVOXLOCAL_PAYLOAD_DIR=${{ github.workspace }}/package/plugin
-DVOXLOCAL_OUTPUT=${{ github.workspace }}/release/VoxLocal-Setup-1.1.1-Windows-x64.exe
-DVOXLOCAL_OUTPUT=${{ github.workspace }}/release/VoxLocal-Setup-1.1.2-Windows-x64.exe
-DVOXLOCAL_WORK_DIR=${{ github.workspace }}/build/installer-windows
-P cmake/package-installer.cmake
- uses: actions/upload-artifact@v4
with:
name: VoxLocal-Setup-1.1.1-Windows-x64
path: release/VoxLocal-Setup-1.1.1-Windows-x64.exe
name: VoxLocal-Setup-1.1.2-Windows-x64
path: release/VoxLocal-Setup-1.1.2-Windows-x64.exe
if-no-files-found: error

macos-plugin:
Expand Down Expand Up @@ -281,13 +281,13 @@ jobs:
cmake
-DVOXLOCAL_PLATFORM=macos
-DVOXLOCAL_PAYLOAD_DIR=${{ github.workspace }}/package/plugin
-DVOXLOCAL_OUTPUT=${{ github.workspace }}/release/VoxLocal-Setup-1.1.1-macOS-universal.dmg
-DVOXLOCAL_OUTPUT=${{ github.workspace }}/release/VoxLocal-Setup-1.1.2-macOS-universal.dmg
-DVOXLOCAL_WORK_DIR=${{ github.workspace }}/build/installer-macos
-P cmake/package-installer.cmake
- uses: actions/upload-artifact@v4
with:
name: VoxLocal-Setup-1.1.1-macOS-universal
path: release/VoxLocal-Setup-1.1.1-macOS-universal.dmg
name: VoxLocal-Setup-1.1.2-macOS-universal
path: release/VoxLocal-Setup-1.1.2-macOS-universal.dmg
if-no-files-found: error

release:
Expand All @@ -301,11 +301,11 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
pattern: VoxLocal-Setup-1.1.1-*
pattern: VoxLocal-Setup-1.1.2-*
path: dist
merge-multiple: true
- uses: softprops/action-gh-release@v2
with:
body_path: docs/RELEASE_NOTES_1.1.1.md
body_path: docs/RELEASE_NOTES_1.1.2.md
files: dist/*
fail_on_unmatched_files: true
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## 1.1.2 — 2026-08-14

- Fixed Windows model startup by applying DirectML's required sequential/no-memory-pattern session settings.
- Updated Windows to Microsoft ONNX Runtime DirectML 1.24.4 and added automatic strongest-GPU selection with full CPU fallback.
- Moved model initialization to a background worker after OBS finishes loading, preventing the OBS interface from freezing during model startup.
- Added `Ask at startup`, `Load automatically`, and `Do not load` model-memory policies to both Welcome and the VoxLocal dock; the default is `Ask at startup`.
- Kept the VoxLocal dock hidden until Welcome finishes and made Welcome non-dismissible, while adding a `Skip` button to every page so optional setup sections never block progress.
- Added a manual `Load model now` action and a visible loaded backend indicator to the dock.
- Logged model initialization and inference failures to the OBS log instead of showing them only in the dock.
- Prevented harmless reads from an already-closed TLS reply after a completed model download.
- Made the Windows setup remove VoxLocal's obsolete `%APPDATA%` plugin copy when installing to `%ProgramData%`, avoiding duplicate-version conflicts.

## 1.1.1 — 2026-08-13

- Updated the Windows and macOS SDK builds to OBS Studio 32.2.1 and its matching 2026-07-15 dependencies.
Expand Down
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.28)

project(VoxLocal VERSION 1.1.1 LANGUAGES CXX)
project(VoxLocal VERSION 1.1.2 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
Expand Down Expand Up @@ -87,6 +87,7 @@ if(VOXLOCAL_ENABLE_ONNX)
target_compile_definitions(voxlocal-core PRIVATE VOXLOCAL_HAVE_ONNXRUNTIME=1)
if(VOXLOCAL_ORT_DIRECTML)
target_compile_definitions(voxlocal-core PRIVATE VOXLOCAL_ORT_DIRECTML=1)
target_link_libraries(voxlocal-core PRIVATE dxgi)
endif()
else()
message(STATUS "ONNX Runtime not found: VoxLocal will build with model-management support but inference disabled")
Expand Down
23 changes: 14 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ VoxLocal is a native OBS Studio plugin that turns live Kick chat commands into l
## Features

- Local Chatterbox Multilingual ONNX inference with Turkish and 22 other languages, including safe handling for tokenizer characters that are absent from the exported embedding table.
- Non-blocking model initialization after OBS is ready, with `Ask at startup`, `Load automatically`, and `Do not load` memory policies.
- Automatic hardware backend selection with DirectML GPU/CPU fallback on Windows, CoreML detection on macOS, and CUDA detection when a CUDA-enabled Linux runtime is supplied.
- Checksum-verified model download with pause/resume support and detailed byte progress.
- Zero-shot voice cloning from audio or video. FFmpeg extracts the first audio track and stores a validated 24 kHz mono WAV locally.
- Multiple personas, each with its own chat command, language mode, voice sample, and viewer role policy.
Expand Down Expand Up @@ -45,15 +47,15 @@ VoxLocal attempts DirectML on Windows, CoreML on macOS, and CUDA on Linux when t

Close OBS, download the one setup matching your platform from [Releases](../../releases), and run it:

- `VoxLocal-Setup-1.1.1-Windows-x64.exe`
- `VoxLocal-Setup-1.1.1-macOS-universal.dmg` for Apple Silicon and Intel Macs
- `VoxLocal-Setup-1.1.1-Linux-x86_64.run`
- `VoxLocal-Setup-1.1.2-Windows-x64.exe`
- `VoxLocal-Setup-1.1.2-macOS-universal.dmg` for Apple Silicon and Intel Macs
- `VoxLocal-Setup-1.1.2-Linux-x86_64.run`

On Linux, make the downloaded setup executable first if your browser removed its executable bit:

```bash
chmod +x VoxLocal-Setup-1.1.1-Linux-x86_64.run
./VoxLocal-Setup-1.1.1-Linux-x86_64.run
chmod +x VoxLocal-Setup-1.1.2-Linux-x86_64.run
./VoxLocal-Setup-1.1.2-Linux-x86_64.run
```

The setup detects and selects the plugin folder scanned by OBS Studio 32.2.1 automatically:
Expand All @@ -69,10 +71,13 @@ Windows and macOS setups are currently unsigned. Windows SmartScreen or macOS Ga
## First run

1. Select English or Turkish.
2. Download and verify the local model. Setup cannot continue before it finishes.
3. Enter the public Kick channel slug.
4. Create a persona with a name, command, language, access policy, and audio or video sample.
5. VoxLocal adds **VoxLocal Overlay** to the current scene.
2. Download and verify the local model, or use **Skip** to do it later.
3. Choose whether the model should ask, load automatically, or remain unloaded when OBS starts.
4. Enter the public Kick channel slug or skip it.
5. Create a persona with a name, command, language, access policy, and audio or video sample, or skip it.
6. VoxLocal adds **VoxLocal Overlay** to the current scene.

The VoxLocal dock remains hidden until Welcome finishes. Welcome cannot be accidentally dismissed, but each page has a **Skip** button and all sections can be completed later from the dock. Model weights are loaded into memory on a background worker only after OBS has finished opening. The dock also provides **Load model now** and shows the selected CPU/GPU backend.

The settings dock opens automatically. Reopen it from **Tools → VoxLocal Settings** or press `Ctrl+Shift+V`.

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": "voxlocal",
"displayName": "VoxLocal",
"version": "1.1.1",
"version": "1.1.2",
"author": "malidev",
"website": "https://github.com/malithedeveloper/voxlocal",
"email": ""
Expand Down
4 changes: 2 additions & 2 deletions cmake/onnxruntime.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ include(FetchContent)

function(voxlocal_fetch_onnxruntime)
if(WIN32)
set(_ort_url "https://github.com/microsoft/onnxruntime/releases/download/v1.22.1/Microsoft.ML.OnnxRuntime.DirectML.1.22.1.nupkg")
set(_ort_hash "9fe1d91a697fb8a68abd29fdc2a88dea8f864ee74e758a055ce63c9e023c6908")
set(_ort_url "https://api.nuget.org/v3-flatcontainer/microsoft.ml.onnxruntime.directml/1.24.4/microsoft.ml.onnxruntime.directml.1.24.4.nupkg")
set(_ort_hash "57e9f11b73437bef7a309496135d4c1f96b1a8e9ddba60013fa27bfc1d788681")
set(_ort_include_suffix "build/native/include")
set(_ort_library_suffix "runtimes/win-x64/native")
set(_ort_directml ON)
Expand Down
6 changes: 5 additions & 1 deletion cmake/package-installer.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ endif()
get_filename_component(VOXLOCAL_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
file(READ "${VOXLOCAL_SOURCE_DIR}/buildspec.json" buildspec)
string(JSON VOXLOCAL_VERSION GET "${buildspec}" version)
set(VOXLOCAL_RELEASE_DATE "2026-08-13")
set(VOXLOCAL_RELEASE_DATE "2026-08-14")

if(VOXLOCAL_PLATFORM STREQUAL "windows")
set(VOXLOCAL_DEFAULT_TARGET "@RootDir@/ProgramData/obs-studio/plugins")
Expand Down Expand Up @@ -76,6 +76,10 @@ configure_file(
"${VOXLOCAL_SOURCE_DIR}/installer/packages/dev.malithedeveloper.voxlocal/meta/package.xml.in"
"${meta_dir}/package.xml"
@ONLY)
configure_file(
"${VOXLOCAL_SOURCE_DIR}/installer/packages/dev.malithedeveloper.voxlocal/meta/installscript.qs"
"${meta_dir}/installscript.qs"
COPYONLY)
configure_file(
"${VOXLOCAL_SOURCE_DIR}/installer/config/controller.qs"
"${config_dir}/controller.qs"
Expand Down
34 changes: 34 additions & 0 deletions docs/RELEASE_NOTES_1.1.2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# VoxLocal 1.1.2

VoxLocal 1.1.2 fixes Windows TTS model startup, makes model loading non-blocking, and gives the streamer explicit control over whether the model enters memory when OBS starts.

## Download

Choose one setup for your operating system:

- `VoxLocal-Setup-1.1.2-Windows-x64.exe`
- `VoxLocal-Setup-1.1.2-macOS-universal.dmg` for Apple Silicon and Intel Macs
- `VoxLocal-Setup-1.1.2-Linux-x86_64.run`

Close OBS before running the setup. The Windows setup uses `%ProgramData%\obs-studio\plugins` and removes VoxLocal's obsolete `%APPDATA%` plugin copy. Existing settings, voices, and downloaded models remain untouched.

## Changes

- Applied the session options required by ONNX Runtime DirectML, fixing model initialization failures on Windows.
- Updated the Windows inference runtime from DirectML 1.22.1 to 1.24.4.
- Automatically chooses the hardware GPU with the most dedicated video memory on Windows. If the GPU or any model session cannot initialize, VoxLocal recreates every session on CPU automatically.
- Detects the available ONNX execution provider on each platform and displays the backend selected for the current session.
- Opens the OBS interface first and initializes the model on a background worker afterward, so loading model weights does not block OBS startup.
- Added three model startup behaviors to Welcome and the dock: **Ask at startup** (default), **Load automatically**, and **Do not load**.
- Added **Load model now** to the dock for manual per-session loading.
- Keeps the VoxLocal dock closed while Welcome is active. Welcome cannot be dismissed with Cancel, Escape, or the window close button, but every page has **Skip** and all setup sections are optional.
- Writes model load and inference failures into the OBS log for useful diagnostics.
- Avoids reading a closed TLS socket after a completed model download.
- Retains the 1.1.1 Turkish tokenizer bounds fix and larger Minimal overlay text.

## Notes

- The startup behavior controls loading the already-downloaded model into memory; it does not delete or redownload model files.
- Windows and macOS setups are unsigned and may require SmartScreen or Gatekeeper confirmation.
- The Linux setup targets native x86-64 OBS installations. Flatpak OBS uses Flatpak's own plugin mechanism.
- FFmpeg must be available locally to import voice samples.
6 changes: 6 additions & 0 deletions include/voxlocal/runtime.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,23 @@ class VoxLocalRuntime final : public QObject
[[nodiscard]] KickConnector *kickConnector() { return &kick_; }
[[nodiscard]] QString status() const { return status_; }
[[nodiscard]] bool engineReady() const { return engine_->isReady(); }
[[nodiscard]] bool engineLoading() const { return engineLoading_; }
[[nodiscard]] QString engineBackend() const { return engine_->backendName(); }
[[nodiscard]] bool isVoiceImporting(const QString &personaId = {}) const;

bool applySettings(Settings settings, QString *error = nullptr);
QString importVoice(const QString &sourcePath, const QString &personaId, QString *error = nullptr) const;
void importVoiceAsync(const QString &sourcePath, const QString &personaId);
void start();
void stop();
void loadModel();
bool preview(const QString &personaId, const QString &text, QString *error = nullptr);

signals:
void settingsChanged();
void statusChanged(const QString &status);
void modelProgress(qint64 received, qint64 total, const QString &fileName);
void engineLoadingChanged(bool loading);
void voiceImportProgress(const QString &personaId, int percent, const QString &operation);
void voiceImportFinished(const QString &personaId, const QString &voicePath);
void voiceImportFailed(const QString &personaId, const QString &error);
Expand All @@ -68,10 +72,12 @@ class VoxLocalRuntime final : public QObject
std::shared_ptr<ChatterboxEngine> engine_;
SpeechQueue queue_;
QThreadPool voiceImportPool_;
QThreadPool modelLoadPool_;
QSet<QString> voiceImports_;
QString activeChatRequestId_;
QDateTime globalCooldownUntil_;
bool chatGenerationActive_ = false;
bool engineLoading_ = false;
QString status_ = QStringLiteral("idle");
};

Expand Down
1 change: 1 addition & 0 deletions include/voxlocal/speech-queue.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class SpeechQueue final : public QObject
~SpeechQueue() override;

void setCapacity(int capacity);
bool setEngine(std::shared_ptr<ITtsEngine> engine);
[[nodiscard]] int size() const;
bool enqueue(const QueueItem &item);

Expand Down
4 changes: 3 additions & 1 deletion include/voxlocal/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@

namespace voxlocal {

inline constexpr int kConfigSchemaVersion = 3;
inline constexpr int kConfigSchemaVersion = 4;
inline constexpr int kDefaultMaxTextLength = 250;
inline constexpr int kDefaultGlobalCooldownSeconds = 10;
inline constexpr int kDefaultQueueCapacity = 10;

enum class InterfaceLanguage { English, Turkish };
enum class LanguageMode { Fixed, Automatic };
enum class ModelStartupBehavior { Ask, AlwaysLoad, NeverLoad };
enum class UserRole : std::uint32_t {
None = 0,
Everyone = 1u << 0,
Expand Down Expand Up @@ -112,6 +113,7 @@ struct Settings {
InterfaceLanguage interfaceLanguage = InterfaceLanguage::English;
QString defaultSpeechLanguage = QStringLiteral("en");
bool welcomeCompleted = false;
ModelStartupBehavior modelStartupBehavior = ModelStartupBehavior::Ask;
bool readUrls = false;
bool ttsEnabled = true;
int globalCooldownSeconds = kDefaultGlobalCooldownSeconds;
Expand Down
4 changes: 4 additions & 0 deletions include/voxlocal/voxlocal-dock.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class VoxLocalDock final : public QWidget
void chooseColor(QPushButton *button, QString *target);
void updateModelProgress(qint64 received, qint64 total, const QString &fileName);
void updateModelDownloadState(bool installing);
void updateModelRuntimeState();
void updateVoiceImportProgress(const QString &personaId, int percent, const QString &operation);
void finishVoiceImport(const QString &personaId, const QString &voicePath);
void failVoiceImport(const QString &personaId, const QString &error);
Expand All @@ -59,6 +60,9 @@ class VoxLocalDock final : public QWidget
QLabel *model_ = nullptr;
QProgressBar *modelProgress_ = nullptr;
QPushButton *modelInstall_ = nullptr;
QPushButton *modelLoad_ = nullptr;
QLabel *modelRuntime_ = nullptr;
QComboBox *modelStartup_ = nullptr;
QComboBox *interfaceLanguage_ = nullptr;
QLineEdit *channel_ = nullptr;
QListWidget *personas_ = nullptr;
Expand Down
6 changes: 6 additions & 0 deletions include/voxlocal/welcome-wizard.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <QWizard>

class QComboBox;
class QCloseEvent;
class QLabel;
class QLineEdit;
class QListWidget;
Expand All @@ -20,9 +21,12 @@ class WelcomeWizard final : public QWizard

public:
explicit WelcomeWizard(VoxLocalRuntime *runtime, QWidget *parent = nullptr);
void closeForShutdown();

protected:
void accept() override;
void reject() override;
void closeEvent(QCloseEvent *event) override;

private:
struct LocalizedWidget {
Expand All @@ -42,6 +46,7 @@ class WelcomeWizard final : public QWizard
VoxLocalRuntime *runtime_;
QVector<LocalizedWidget> localizedWidgets_;
QComboBox *interfaceLanguage_ = nullptr;
QComboBox *modelStartup_ = nullptr;
QLabel *modelInfo_ = nullptr;
QLabel *modelStatus_ = nullptr;
QProgressBar *modelProgress_ = nullptr;
Expand All @@ -59,6 +64,7 @@ class WelcomeWizard final : public QWizard
QLineEdit *voice_ = nullptr;
QComboBox *language_ = nullptr;
QComboBox *access_ = nullptr;
bool allowClose_ = false;
};

} // namespace voxlocal
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
function Component()
{
}

Component.prototype.createOperations = function()
{
component.createOperations();

if (systemInfo.productType !== "windows")
return;

var appData = installer.environmentVariable("APPDATA");
if (appData === "")
return;

var stalePlugin = appData + "/obs-studio/plugins/voxlocal";
var selectedTarget = installer.value("TargetDir").replace(/\\/g, "/").toLowerCase();
var staleParent = (appData + "/obs-studio/plugins").replace(/\\/g, "/").toLowerCase();
if (selectedTarget === staleParent)
return;

var systemRoot = installer.environmentVariable("SystemRoot");
var command = (systemRoot !== "" ? systemRoot : "C:/Windows") + "/System32/cmd.exe";
component.addOperation("Execute", command, "/D", "/S", "/C",
"if exist \"" + stalePlugin + "\" rmdir /S /Q \"" + stalePlugin + "\"");
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<ReleaseDate>@VOXLOCAL_RELEASE_DATE@</ReleaseDate>
<Name>dev.malithedeveloper.voxlocal</Name>
<Default>true</Default>
<Script>installscript.qs</Script>
@VOXLOCAL_ADMIN_REQUIREMENT@
<Licenses>
<License name="GNU GPL 2.0 or later" file="LICENSE" />
Expand Down
Loading
Loading