#include "transcribe.h"
#include "arch/whisper/bin_load.h"
#include "ggml-backend.h"
#include "ggml.h"
#include "transcribe-abi.h"
#include "transcribe-arch.h"
#include "transcribe-backend.h"
#include "transcribe-loader.h"
#include "transcribe-log.h"
#include "transcribe-model.h"
#include "transcribe-path.h"
#include "transcribe-session.h"
#include "transcribe-tokenizer.h"
#include "transcribe/whisper.h"
#if defined(TRANSCRIBE_GGML_BACKEND_DL) && defined(_WIN32)
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
# ifndef NOMINMAX
# define NOMINMAX
# endif
# include <windows.h>
#elif defined(TRANSCRIBE_GGML_BACKEND_DL)
# include <dlfcn.h>
#endif
#include <algorithm>
#include <atomic>
#include <climits>
#include <cmath>
#include <cstdarg>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <exception>
#include <filesystem>
#include <fstream>
#include <ios>
#include <mutex>
#include <new>
#include <set>
#include <string>
#include <vector>
namespace {
template <typename Fn> transcribe_status api_guard_status(const char * fn_name, Fn && body) {
try {
return body();
} catch (const std::bad_alloc &) {
transcribe::log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: out of memory", fn_name);
return TRANSCRIBE_ERR_OOM;
} catch (const std::exception & e) {
transcribe::log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: caught backend exception: %s", fn_name, e.what());
return TRANSCRIBE_ERR_BACKEND;
} catch (...) {
transcribe::log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: caught unknown backend exception", fn_name);
return TRANSCRIBE_ERR_BACKEND;
}
}
template <typename T, typename Fn> T api_guard_value(const char * fn_name, T on_throw, Fn && body) {
try {
return body();
} catch (const std::exception & e) {
transcribe::log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: caught backend exception: %s", fn_name, e.what());
return on_throw;
} catch (...) {
transcribe::log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: caught unknown backend exception", fn_name);
return on_throw;
}
}
template <typename Fn> void api_guard_void(const char * fn_name, Fn && body) {
try {
body();
} catch (const std::exception & e) {
transcribe::log_msg(TRANSCRIBE_LOG_LEVEL_WARN, "%s: caught backend exception during teardown: %s", fn_name,
e.what());
} catch (...) {
transcribe::log_msg(TRANSCRIBE_LOG_LEVEL_WARN, "%s: caught unknown backend exception during teardown", fn_name);
}
}
}
extern "C" const char * transcribe_status_string(int status) {
switch (status) {
case TRANSCRIBE_OK:
return "ok";
case TRANSCRIBE_ERR_INVALID_ARG:
return "invalid argument";
case TRANSCRIBE_ERR_NOT_IMPLEMENTED:
return "not implemented";
case TRANSCRIBE_ERR_FILE_NOT_FOUND:
return "file not found";
case TRANSCRIBE_ERR_GGUF:
return "gguf load error";
case TRANSCRIBE_ERR_UNSUPPORTED_ARCH:
return "unsupported architecture";
case TRANSCRIBE_ERR_UNSUPPORTED_VARIANT:
return "unsupported variant";
case TRANSCRIBE_ERR_OOM:
return "out of memory";
case TRANSCRIBE_ERR_BACKEND:
return "backend error";
case TRANSCRIBE_ERR_SAMPLE_RATE:
return "sample rate mismatch";
case TRANSCRIBE_ERR_UNSUPPORTED_LANGUAGE:
return "unsupported language";
case TRANSCRIBE_ERR_UNSUPPORTED_TASK:
return "unsupported task";
case TRANSCRIBE_ERR_UNSUPPORTED_TIMESTAMPS:
return "unsupported timestamp granularity";
case TRANSCRIBE_ERR_ABORTED:
return "aborted by callback";
case TRANSCRIBE_ERR_BAD_STRUCT_SIZE:
return "caller-owned struct missing or has bad struct_size";
case TRANSCRIBE_ERR_UNSUPPORTED_PNC:
return "model does not support runtime PNC control (reserved; not currently returned)";
case TRANSCRIBE_ERR_UNSUPPORTED_ITN:
return "model does not support runtime ITN control (reserved; not currently returned)";
case TRANSCRIBE_ERR_INPUT_TOO_LONG:
return "input audio too long for model context";
case TRANSCRIBE_ERR_OUTPUT_TRUNCATED:
return "output truncated: decode hit the context/generation cap before end-of-stream";
default:
return "unknown status";
}
}
#ifndef TRANSCRIBE_COMMIT
# define TRANSCRIBE_COMMIT "unknown"
#endif
extern "C" const char * transcribe_version(void) {
return TRANSCRIBE_VERSION;
}
extern "C" const char * transcribe_version_commit(void) {
return TRANSCRIBE_COMMIT;
}
static inline int enum_field_raw(const void * field) {
int raw;
std::memcpy(&raw, field, sizeof(raw));
return raw;
}
static_assert(sizeof(transcribe_backend_request) == sizeof(int),
"public enums must be int-sized for raw boundary reads");
extern "C" size_t transcribe_abi_struct_size(transcribe_abi_struct which) {
switch (enum_field_raw(&which)) {
case TRANSCRIBE_ABI_MODEL_LOAD_PARAMS:
return sizeof(struct transcribe_model_load_params);
case TRANSCRIBE_ABI_SESSION_PARAMS:
return sizeof(struct transcribe_session_params);
case TRANSCRIBE_ABI_RUN_PARAMS:
return sizeof(struct transcribe_run_params);
case TRANSCRIBE_ABI_STREAM_PARAMS:
return sizeof(struct transcribe_stream_params);
case TRANSCRIBE_ABI_CAPABILITIES:
return sizeof(struct transcribe_capabilities);
case TRANSCRIBE_ABI_TIMINGS:
return sizeof(struct transcribe_timings);
case TRANSCRIBE_ABI_SEGMENT:
return sizeof(struct transcribe_segment);
case TRANSCRIBE_ABI_WORD:
return sizeof(struct transcribe_word);
case TRANSCRIBE_ABI_TOKEN:
return sizeof(struct transcribe_token);
case TRANSCRIBE_ABI_STREAM_UPDATE:
return sizeof(struct transcribe_stream_update);
case TRANSCRIBE_ABI_STREAM_TEXT:
return sizeof(struct transcribe_stream_text);
case TRANSCRIBE_ABI_SESSION_LIMITS:
return sizeof(struct transcribe_session_limits);
case TRANSCRIBE_ABI_EXT:
return sizeof(struct transcribe_ext);
case TRANSCRIBE_ABI_DEVICE_INFO:
return sizeof(struct transcribe_device_info);
case TRANSCRIBE_ABI_SPEAKER_SEGMENT:
return sizeof(struct transcribe_speaker_segment);
}
return 0; }
extern "C" size_t transcribe_abi_struct_align(transcribe_abi_struct which) {
switch (enum_field_raw(&which)) {
case TRANSCRIBE_ABI_MODEL_LOAD_PARAMS:
return alignof(struct transcribe_model_load_params);
case TRANSCRIBE_ABI_SESSION_PARAMS:
return alignof(struct transcribe_session_params);
case TRANSCRIBE_ABI_RUN_PARAMS:
return alignof(struct transcribe_run_params);
case TRANSCRIBE_ABI_STREAM_PARAMS:
return alignof(struct transcribe_stream_params);
case TRANSCRIBE_ABI_CAPABILITIES:
return alignof(struct transcribe_capabilities);
case TRANSCRIBE_ABI_TIMINGS:
return alignof(struct transcribe_timings);
case TRANSCRIBE_ABI_SEGMENT:
return alignof(struct transcribe_segment);
case TRANSCRIBE_ABI_WORD:
return alignof(struct transcribe_word);
case TRANSCRIBE_ABI_TOKEN:
return alignof(struct transcribe_token);
case TRANSCRIBE_ABI_STREAM_UPDATE:
return alignof(struct transcribe_stream_update);
case TRANSCRIBE_ABI_STREAM_TEXT:
return alignof(struct transcribe_stream_text);
case TRANSCRIBE_ABI_SESSION_LIMITS:
return alignof(struct transcribe_session_limits);
case TRANSCRIBE_ABI_EXT:
return alignof(struct transcribe_ext);
case TRANSCRIBE_ABI_DEVICE_INFO:
return alignof(struct transcribe_device_info);
case TRANSCRIBE_ABI_SPEAKER_SEGMENT:
return alignof(struct transcribe_speaker_segment);
}
return 0;
}
namespace {
int timestamp_rank(transcribe_timestamp_kind k) {
switch (k) {
case TRANSCRIBE_TIMESTAMPS_NONE:
return 0;
case TRANSCRIBE_TIMESTAMPS_SEGMENT:
return 1;
case TRANSCRIBE_TIMESTAMPS_WORD:
return 2;
case TRANSCRIBE_TIMESTAMPS_TOKEN:
return 3;
case TRANSCRIBE_TIMESTAMPS_AUTO:
return -1;
}
return -1;
}
transcribe_status validate_run_params_common(const transcribe_session * session, const transcribe_run_params * params) {
switch (enum_field_raw(¶ms->task)) {
case TRANSCRIBE_TASK_TRANSCRIBE:
case TRANSCRIBE_TASK_TRANSLATE:
break;
default:
return TRANSCRIBE_ERR_INVALID_ARG;
}
switch (enum_field_raw(¶ms->timestamps)) {
case TRANSCRIBE_TIMESTAMPS_NONE:
case TRANSCRIBE_TIMESTAMPS_AUTO:
case TRANSCRIBE_TIMESTAMPS_SEGMENT:
case TRANSCRIBE_TIMESTAMPS_WORD:
case TRANSCRIBE_TIMESTAMPS_TOKEN:
break;
default:
return TRANSCRIBE_ERR_INVALID_ARG;
}
switch (enum_field_raw(¶ms->pnc)) {
case TRANSCRIBE_PNC_MODE_DEFAULT:
case TRANSCRIBE_PNC_MODE_OFF:
case TRANSCRIBE_PNC_MODE_ON:
break;
default:
return TRANSCRIBE_ERR_INVALID_ARG;
}
switch (enum_field_raw(¶ms->itn)) {
case TRANSCRIBE_ITN_MODE_DEFAULT:
case TRANSCRIBE_ITN_MODE_OFF:
case TRANSCRIBE_ITN_MODE_ON:
break;
default:
return TRANSCRIBE_ERR_INVALID_ARG;
}
switch (enum_field_raw(¶ms->diarize)) {
case TRANSCRIBE_DIARIZE_MODE_DEFAULT:
case TRANSCRIBE_DIARIZE_MODE_OFF:
case TRANSCRIBE_DIARIZE_MODE_ON:
break;
default:
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (params->timestamps != TRANSCRIBE_TIMESTAMPS_AUTO) {
const int req_rank = timestamp_rank(params->timestamps);
const int max_rank = timestamp_rank(session->model->caps.max_timestamp_kind);
if (req_rank > max_rank) {
return TRANSCRIBE_ERR_UNSUPPORTED_TIMESTAMPS;
}
}
if (params->language != nullptr && session->model->caps.n_languages > 0 &&
session->model->caps.languages != nullptr) {
bool found = false;
for (int i = 0; i < session->model->caps.n_languages; ++i) {
const char * entry = session->model->caps.languages[i];
if (entry != nullptr && std::strcmp(entry, params->language) == 0) {
found = true;
break;
}
}
if (!found) {
return TRANSCRIBE_ERR_UNSUPPORTED_LANGUAGE;
}
}
if (params->task == TRANSCRIBE_TASK_TRANSLATE && params->target_language != nullptr &&
session->model->caps.n_translate_target_languages > 0 &&
session->model->caps.translate_target_languages != nullptr) {
bool found = false;
for (int i = 0; i < session->model->caps.n_translate_target_languages; ++i) {
const char * entry = session->model->caps.translate_target_languages[i];
if (entry != nullptr && std::strcmp(entry, params->target_language) == 0) {
found = true;
break;
}
}
if (!found) {
return TRANSCRIBE_ERR_UNSUPPORTED_LANGUAGE;
}
}
if (params->task == TRANSCRIBE_TASK_TRANSLATE && params->language != nullptr &&
params->target_language != nullptr &&
!session->model->allows_translation_pair(params->language, params->target_language)) {
return TRANSCRIBE_ERR_UNSUPPORTED_LANGUAGE;
}
return TRANSCRIBE_OK;
}
}
namespace {
void transcribe_log_cb_disabled(transcribe_log_level, const char *, void *) {}
std::atomic<transcribe_log_callback> g_log_cb{ nullptr };
std::atomic<void *> g_log_userdata{ nullptr };
transcribe_log_level transcribe_log_level_from_ggml(int level) {
switch (level) {
case GGML_LOG_LEVEL_NONE:
return TRANSCRIBE_LOG_LEVEL_NONE;
case GGML_LOG_LEVEL_DEBUG:
return TRANSCRIBE_LOG_LEVEL_DEBUG;
case GGML_LOG_LEVEL_INFO:
return TRANSCRIBE_LOG_LEVEL_INFO;
case GGML_LOG_LEVEL_WARN:
return TRANSCRIBE_LOG_LEVEL_WARN;
case GGML_LOG_LEVEL_ERROR:
return TRANSCRIBE_LOG_LEVEL_ERROR;
case GGML_LOG_LEVEL_CONT:
return TRANSCRIBE_LOG_LEVEL_CONT;
default:
return TRANSCRIBE_LOG_LEVEL_INFO;
}
}
void transcribe_ggml_log_bridge(enum ggml_log_level level, const char * text, void *);
}
extern "C" void transcribe_log_set(transcribe_log_callback cb, void * userdata) {
g_log_userdata.store(userdata, std::memory_order_relaxed);
g_log_cb.store(cb != nullptr ? cb : &transcribe_log_cb_disabled, std::memory_order_release);
ggml_log_set(&transcribe_ggml_log_bridge, nullptr);
}
static void transcribe_log_invoke(transcribe_log_callback cb,
transcribe_log_level level,
const char * msg,
void * userdata) noexcept {
try {
cb(level, msg, userdata);
} catch (...) {
std::fprintf(stderr, "transcribe: log callback threw; message dropped: %s\n", msg);
}
}
static void transcribe_log_emit(transcribe_log_level level, const char * msg) {
const auto cb = g_log_cb.load(std::memory_order_acquire);
if (cb == nullptr || cb == &transcribe_log_cb_disabled) {
return;
}
void * userdata = g_log_userdata.load(std::memory_order_relaxed);
transcribe_log_invoke(cb, level, msg, userdata);
}
static void transcribe_log_emit_or_stderr(transcribe_log_level level, const char * msg) {
const auto cb = g_log_cb.load(std::memory_order_acquire);
if (cb == nullptr) { std::fprintf(stderr, "%s\n", msg);
return;
}
if (cb == &transcribe_log_cb_disabled) { return;
}
transcribe_log_invoke(cb, level, msg, g_log_userdata.load(std::memory_order_relaxed));
}
namespace {
void transcribe_ggml_log_bridge(enum ggml_log_level level, const char * text, void *) {
if (text == nullptr) {
return;
}
char buf[1024];
std::snprintf(buf, sizeof(buf), "%s", text);
const size_t n = std::strlen(buf);
if (n > 0 && buf[n - 1] == '\n') {
buf[n - 1] = '\0';
}
transcribe_log_emit_or_stderr(transcribe_log_level_from_ggml(level), buf);
}
}
namespace transcribe {
void log_msg(transcribe_log_level level, const char * fmt, ...) {
char buf[1024];
va_list ap;
va_start(ap, fmt);
std::vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
transcribe_log_emit_or_stderr(level, buf);
}
}
extern "C" void transcribe_model_load_params_init(struct transcribe_model_load_params * p) {
if (p == nullptr) {
return;
}
std::memset(p, 0, sizeof(*p));
p->struct_size = sizeof(*p);
}
extern "C" void transcribe_session_params_init(struct transcribe_session_params * p) {
if (p == nullptr) {
return;
}
std::memset(p, 0, sizeof(*p));
p->struct_size = sizeof(*p);
}
extern "C" void transcribe_run_params_init(struct transcribe_run_params * p) {
if (p == nullptr) {
return;
}
std::memset(p, 0, sizeof(*p));
p->struct_size = sizeof(*p);
p->spec_k_drafts = -1; p->timestamps = TRANSCRIBE_TIMESTAMPS_AUTO;
}
extern "C" void transcribe_stream_params_init(struct transcribe_stream_params * p) {
if (p == nullptr) {
return;
}
std::memset(p, 0, sizeof(*p));
p->struct_size = sizeof(*p);
}
extern "C" void transcribe_capabilities_init(struct transcribe_capabilities * p) {
if (p == nullptr) {
return;
}
std::memset(p, 0, sizeof(*p));
p->struct_size = sizeof(*p);
}
extern "C" void transcribe_session_limits_init(struct transcribe_session_limits * p) {
if (p == nullptr) {
return;
}
std::memset(p, 0, sizeof(*p));
p->struct_size = sizeof(*p);
}
extern "C" void transcribe_timings_init(struct transcribe_timings * p) {
if (p == nullptr) {
return;
}
std::memset(p, 0, sizeof(*p));
p->struct_size = sizeof(*p);
}
extern "C" void transcribe_stream_update_init(struct transcribe_stream_update * p) {
if (p == nullptr) {
return;
}
std::memset(p, 0, sizeof(*p));
p->struct_size = sizeof(*p);
}
extern "C" void transcribe_stream_text_init(struct transcribe_stream_text * p) {
if (p == nullptr) {
return;
}
std::memset(p, 0, sizeof(*p));
p->struct_size = sizeof(*p);
}
extern "C" void transcribe_segment_init(struct transcribe_segment * p) {
if (p == nullptr) {
return;
}
std::memset(p, 0, sizeof(*p));
p->struct_size = sizeof(*p);
}
extern "C" void transcribe_device_info_init(struct transcribe_device_info * p) {
if (p == nullptr) {
return;
}
std::memset(p, 0, sizeof(*p));
p->struct_size = sizeof(*p);
}
extern "C" void transcribe_word_init(struct transcribe_word * p) {
if (p == nullptr) {
return;
}
std::memset(p, 0, sizeof(*p));
p->struct_size = sizeof(*p);
}
extern "C" void transcribe_token_init(struct transcribe_token * p) {
if (p == nullptr) {
return;
}
std::memset(p, 0, sizeof(*p));
p->struct_size = sizeof(*p);
}
extern "C" void transcribe_speaker_segment_init(struct transcribe_speaker_segment * p) {
if (p == nullptr) {
return;
}
std::memset(p, 0, sizeof(*p));
p->struct_size = sizeof(*p);
}
extern "C" transcribe_status transcribe_ext_check(const struct transcribe_ext * ext,
uint32_t expected_kind,
uint64_t min_size) {
if (ext == nullptr) {
return TRANSCRIBE_OK;
}
if (ext->size < sizeof(struct transcribe_ext)) {
return TRANSCRIBE_ERR_BAD_STRUCT_SIZE;
}
if (ext->kind != expected_kind) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (ext->size < min_size) {
return TRANSCRIBE_ERR_BAD_STRUCT_SIZE;
}
return TRANSCRIBE_OK;
}
extern "C" bool transcribe_model_accepts_ext_kind(const struct transcribe_model * model,
transcribe_ext_slot slot,
uint32_t kind) {
if (model == nullptr || model->arch == nullptr) {
return false;
}
if (model->arch->accepts_ext_kind == nullptr) {
return false;
}
return model->arch->accepts_ext_kind(model, slot, kind);
}
extern "C" bool transcribe_model_supports(const struct transcribe_model * model, transcribe_feature feature) {
return transcribe::has_feature(model, enum_field_raw(&feature));
}
namespace {
#define TRANSCRIBE_FIELD_END(type, field) (offsetof(type, field) + sizeof(((type *) 0)->field))
constexpr size_t k_min_model_params_size = TRANSCRIBE_FIELD_END(transcribe_model_load_params, device);
constexpr size_t k_min_context_params_size = TRANSCRIBE_FIELD_END(transcribe_session_params, kv_type);
constexpr size_t k_min_run_params_size = TRANSCRIBE_FIELD_END(transcribe_run_params, spec_k_drafts);
constexpr size_t k_min_stream_params_size = TRANSCRIBE_FIELD_END(transcribe_stream_params, family);
constexpr size_t k_stream_params_commit_policy_size = TRANSCRIBE_FIELD_END(transcribe_stream_params, commit_policy);
constexpr size_t k_stream_params_agreement_n_size =
TRANSCRIBE_FIELD_END(transcribe_stream_params, stable_prefix_agreement_n);
constexpr size_t k_min_stream_update_size = TRANSCRIBE_FIELD_END(transcribe_stream_update, buffered_ms);
constexpr size_t k_stream_update_committed_changed_size =
TRANSCRIBE_FIELD_END(transcribe_stream_update, committed_changed);
constexpr size_t k_stream_update_tentative_changed_size =
TRANSCRIBE_FIELD_END(transcribe_stream_update, tentative_changed);
constexpr size_t k_min_stream_text_size = TRANSCRIBE_FIELD_END(transcribe_stream_text, raw_tentative_start_bytes);
constexpr size_t k_min_capabilities_size = TRANSCRIBE_FIELD_END(transcribe_capabilities, supports_streaming);
constexpr size_t k_min_session_limits_size = TRANSCRIBE_FIELD_END(transcribe_session_limits, max_kv_bytes);
constexpr size_t k_min_segment_size = TRANSCRIBE_FIELD_END(transcribe_segment, text);
constexpr size_t k_min_word_size = TRANSCRIBE_FIELD_END(transcribe_word, text);
constexpr size_t k_min_token_size = TRANSCRIBE_FIELD_END(transcribe_token, text);
constexpr size_t k_min_speaker_segment_size = TRANSCRIBE_FIELD_END(transcribe_speaker_segment, p);
constexpr size_t k_min_timings_size = TRANSCRIBE_FIELD_END(transcribe_timings, decode_ms);
constexpr size_t k_min_device_info_size = TRANSCRIBE_FIELD_END(transcribe_device_info, kind);
#undef TRANSCRIBE_FIELD_END
using transcribe::check_input_struct_size;
using transcribe::check_struct_size;
using transcribe::copy_out_prefix;
static bool has_field(uint64_t struct_size, size_t field_end) {
return struct_size >= static_cast<uint64_t>(field_end);
}
static bool valid_stream_commit_policy(int policy) {
switch (policy) {
case TRANSCRIBE_STREAM_COMMIT_AUTO:
case TRANSCRIBE_STREAM_COMMIT_ON_FINALIZE:
case TRANSCRIBE_STREAM_COMMIT_STABLE_PREFIX:
return true;
}
return false;
}
}
static void ensure_ggml_time_init() {
static std::once_flag once;
std::call_once(once, [] { ggml_time_init(); });
}
#if defined(TRANSCRIBE_GGML_BACKEND_DL)
static std::filesystem::path library_self_dir() {
# if defined(_WIN32)
HMODULE module = nullptr;
const auto flags = GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT;
if (!GetModuleHandleExW(flags, reinterpret_cast<LPCWSTR>(&transcribe_init_backends_default), &module)) {
return {};
}
std::wstring path(1024, L'\0');
for (;;) {
const DWORD n = GetModuleFileNameW(module, path.data(), static_cast<DWORD>(path.size()));
if (n == 0) {
return {};
}
if (n < path.size()) {
path.resize(n);
break;
}
if (path.size() >= 32768) {
return {};
}
path.resize(path.size() * 2);
}
return std::filesystem::path(path).parent_path();
# else
Dl_info info{};
if (dladdr(reinterpret_cast<const void *>(&transcribe_init_backends_default), &info) == 0 ||
info.dli_fname == nullptr || info.dli_fname[0] == '\0') {
return {};
}
std::filesystem::path path(info.dli_fname);
std::error_code ec;
auto canonical = std::filesystem::weakly_canonical(path, ec);
if (ec) {
canonical = std::filesystem::absolute(path, ec);
}
if (ec) {
canonical = path;
}
return canonical.parent_path();
# endif
}
static std::string path_for_c_api(const std::filesystem::path & path) {
# if defined(_WIN32)
const auto u8 = path.u8string();
return std::string(u8.begin(), u8.end());
# else
return path.string();
# endif
}
#endif
static transcribe_status transcribe_init_backends_impl(const char * artifact_dir) {
ensure_ggml_time_init();
if (artifact_dir == nullptr || artifact_dir[0] == '\0') {
return TRANSCRIBE_ERR_INVALID_ARG;
}
{
std::error_code ec;
if (!std::filesystem::is_directory(transcribe::path_from_utf8(artifact_dir), ec)) {
transcribe::log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "transcribe_init_backends: %s is not an existing directory",
artifact_dir);
return TRANSCRIBE_ERR_FILE_NOT_FOUND;
}
}
static std::mutex s_mutex;
static std::set<std::string> s_loaded_dirs;
std::lock_guard<std::mutex> lock(s_mutex);
std::error_code ec;
std::filesystem::path canonical = std::filesystem::weakly_canonical(transcribe::path_from_utf8(artifact_dir), ec);
const std::string key = ec ? std::string(artifact_dir) : canonical.u8string();
if (s_loaded_dirs.find(key) == s_loaded_dirs.end()) {
ggml_backend_load_all_from_path(artifact_dir);
s_loaded_dirs.insert(key);
char devices[256];
size_t off = 0;
const size_t n_dev = ggml_backend_dev_count();
for (size_t i = 0; i < n_dev; ++i) {
const int wrote = std::snprintf(devices + off, sizeof(devices) - off, "%s%s", i == 0 ? "" : ", ",
ggml_backend_dev_name(ggml_backend_dev_get(i)));
if (wrote < 0 || static_cast<size_t>(wrote) >= sizeof(devices) - off) {
break; }
off += static_cast<size_t>(wrote);
}
char msg[512];
std::snprintf(msg, sizeof(msg),
"transcribe_init_backends: %zu compute device(s) "
"registered after scanning %s: %s",
n_dev, artifact_dir, off > 0 ? devices : "(none)");
transcribe_log_emit(TRANSCRIBE_LOG_LEVEL_INFO, msg);
}
if (ggml_backend_dev_count() == 0) {
transcribe::log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"transcribe_init_backends: no compute devices registered after "
"loading %s (no usable ggml backend modules found there, and no "
"backends are compiled in)",
artifact_dir);
return TRANSCRIBE_ERR_BACKEND;
}
return TRANSCRIBE_OK;
}
static transcribe_status transcribe_init_backends_default_impl(void) {
ensure_ggml_time_init();
#if !defined(TRANSCRIBE_GGML_BACKEND_DL)
return TRANSCRIBE_OK;
#else
const auto dir = library_self_dir();
if (dir.empty()) {
transcribe::log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"transcribe_init_backends_default: could not resolve the "
"directory containing libtranscribe");
return TRANSCRIBE_ERR_BACKEND;
}
const auto s = path_for_c_api(dir);
return transcribe_init_backends(s.c_str());
#endif
}
namespace {
transcribe_device_type to_device_type(enum ggml_backend_dev_type t) {
switch (t) {
case GGML_BACKEND_DEVICE_TYPE_CPU:
return TRANSCRIBE_DEVICE_TYPE_CPU;
case GGML_BACKEND_DEVICE_TYPE_IGPU:
return TRANSCRIBE_DEVICE_TYPE_IGPU;
case GGML_BACKEND_DEVICE_TYPE_ACCEL:
return TRANSCRIBE_DEVICE_TYPE_ACCEL;
case GGML_BACKEND_DEVICE_TYPE_GPU:
default:
return TRANSCRIBE_DEVICE_TYPE_GPU;
}
}
void fill_device_info(ggml_backend_dev_t dev, uint64_t caller_size, struct transcribe_device_info * out) {
ggml_backend_dev_props props{};
ggml_backend_dev_get_props(dev, &props);
const transcribe::BackendKind kind = transcribe::classify_device(dev);
const char * kind_name = transcribe::kind_name(kind);
const char * name = ggml_backend_dev_name(dev);
if (name == nullptr || name[0] == '\0') {
name = (props.name != nullptr && props.name[0] != '\0') ? props.name : kind_name;
}
const char * description = ggml_backend_dev_description(dev);
if (description == nullptr) {
description = props.description != nullptr ? props.description : "";
}
struct transcribe_device_info staged{};
staged.struct_size = caller_size;
staged.name = name;
staged.description = description;
staged.kind = kind_name;
staged.device_id = props.device_id;
staged.memory_total = props.memory_total;
staged.memory_free = props.memory_free;
staged.device_type = to_device_type(props.type);
copy_out_prefix(out, &staged, caller_size, sizeof(staged));
}
}
static int transcribe_device_count_impl(void) {
return static_cast<int>(ggml_backend_dev_count());
}
static transcribe_device_t transcribe_device_get_impl(int index) {
if (index < 0 || index >= static_cast<int>(ggml_backend_dev_count())) {
return nullptr;
}
return reinterpret_cast<transcribe_device_t>(ggml_backend_dev_get(static_cast<size_t>(index)));
}
static ggml_backend_dev_t device_from_handle(transcribe_device_t device) {
if (device == nullptr) {
return nullptr;
}
ggml_backend_dev_t candidate = reinterpret_cast<ggml_backend_dev_t>(device);
for (size_t i = 0; i < ggml_backend_dev_count(); ++i) {
if (ggml_backend_dev_get(i) == candidate) {
return candidate;
}
}
return nullptr;
}
static transcribe_status transcribe_device_get_info_impl(transcribe_device_t device,
struct transcribe_device_info * out) {
if (out == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (const auto st = check_struct_size(out->struct_size, k_min_device_info_size); st != TRANSCRIBE_OK) {
return st;
}
ggml_backend_dev_t dev = device_from_handle(device);
if (dev == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
fill_device_info(dev, out->struct_size, out);
return TRANSCRIBE_OK;
}
static bool transcribe_backend_available_impl(int raw) {
const size_t n = ggml_backend_dev_count();
if (raw == TRANSCRIBE_BACKEND_AUTO) {
return n > 0;
}
transcribe::BackendKind want;
switch (raw) {
case TRANSCRIBE_BACKEND_CPU:
case TRANSCRIBE_BACKEND_CPU_ACCEL:
want = transcribe::BackendKind::Cpu;
break;
case TRANSCRIBE_BACKEND_METAL:
want = transcribe::BackendKind::Metal;
break;
case TRANSCRIBE_BACKEND_VULKAN:
want = transcribe::BackendKind::Vulkan;
break;
case TRANSCRIBE_BACKEND_CUDA:
want = transcribe::BackendKind::Cuda;
break;
case TRANSCRIBE_BACKEND_ROCM:
want = transcribe::BackendKind::Rocm;
break;
default:
return false;
}
for (size_t i = 0; i < n; ++i) {
if (transcribe::classify_device(ggml_backend_dev_get(i)) == want) {
return true;
}
}
return false;
}
namespace {
enum class StreamStablePrefixImpl {
GenericTextAgreement,
FamilyTokenAgreement,
FamilyNativeCommit,
};
static StreamStablePrefixImpl stream_stable_prefix_impl_for_arch(const transcribe::Arch * arch) {
struct Entry {
const char * arch_name;
StreamStablePrefixImpl impl;
};
static constexpr Entry k_defaults[] = {
{ "parakeet", StreamStablePrefixImpl::FamilyNativeCommit },
{ "moonshine_streaming", StreamStablePrefixImpl::FamilyTokenAgreement },
};
const char * name = arch != nullptr && arch->name != nullptr ? arch->name : "";
for (const Entry & entry : k_defaults) {
if (std::strcmp(name, entry.arch_name) == 0) {
return entry.impl;
}
}
return StreamStablePrefixImpl::GenericTextAgreement;
}
static size_t utf8_floor_boundary(const std::string & s, size_t pos) {
if (pos >= s.size()) {
return s.size();
}
while (pos > 0 && (static_cast<unsigned char>(s[pos]) & 0xC0u) == 0x80u) {
--pos;
}
return pos;
}
static size_t common_prefix_bytes(const std::string & a, const std::string & b) {
const size_t n = std::min(a.size(), b.size());
size_t i = 0;
while (i < n && a[i] == b[i]) {
++i;
}
return utf8_floor_boundary(a, i);
}
static bool starts_with_bytes(const std::string & s, const std::string & prefix) {
return s.size() >= prefix.size() && s.compare(0, prefix.size(), prefix) == 0;
}
static size_t token_prefix_raw_bytes(const transcribe_session * session, int n_tokens) {
if (session == nullptr || n_tokens <= 0 || session->tokens.empty()) {
return 0;
}
const int capped = std::min<int>(n_tokens, static_cast<int>(session->tokens.size()));
std::string prefix;
prefix.reserve(session->full_text.size());
for (int i = 0; i < capped; ++i) {
prefix += session->tokens[static_cast<size_t>(i)].text;
}
if (starts_with_bytes(session->full_text, prefix)) {
return prefix.size();
}
if (!prefix.empty() && prefix.front() == ' ' && (session->full_text.empty() || session->full_text.front() != ' ')) {
const std::string normalized_prefix = prefix.substr(1);
if (starts_with_bytes(session->full_text, normalized_prefix)) {
return normalized_prefix.size();
}
return common_prefix_bytes(session->full_text, normalized_prefix);
}
return common_prefix_bytes(session->full_text, prefix);
}
static size_t family_candidate_raw_prefix_bytes(const transcribe_session * session) {
if (session == nullptr || !session->has_result) {
return 0;
}
if (session->n_committed_tokens > 0) {
return token_prefix_raw_bytes(session, session->n_committed_tokens);
}
if (session->n_committed_words > 0 && session->n_committed_words >= static_cast<int>(session->words.size())) {
return session->full_text.size();
}
if (session->n_committed_segments > 0 &&
session->n_committed_segments >= static_cast<int>(session->segments.size())) {
return session->full_text.size();
}
return 0;
}
static size_t generic_text_stable_prefix_candidate_raw_bytes(transcribe_session * session) {
if (session == nullptr || !session->has_result) {
return 0;
}
static constexpr uint32_t k_default_stable_prefix_agreement_n = 3;
uint32_t agreement_n = session->stream_stable_prefix_agreement_n;
if (agreement_n == 0) {
agreement_n = k_default_stable_prefix_agreement_n;
}
if (agreement_n <= 1) {
return session->full_text.size();
}
session->stream_raw_history.push_back(session->full_text);
while (session->stream_raw_history.size() > agreement_n) {
session->stream_raw_history.pop_front();
}
if (session->stream_raw_history.size() < agreement_n) {
return 0;
}
size_t prefix_n = session->stream_raw_history.front().size();
for (const auto & text : session->stream_raw_history) {
prefix_n = std::min(prefix_n, common_prefix_bytes(session->stream_raw_history.front(), text));
}
return utf8_floor_boundary(session->full_text, prefix_n);
}
static size_t selected_stable_prefix_candidate_raw_bytes(transcribe_session * session) {
if (session == nullptr) {
return 0;
}
const transcribe::Arch * arch = session->model != nullptr ? session->model->arch : nullptr;
switch (stream_stable_prefix_impl_for_arch(arch)) {
case StreamStablePrefixImpl::FamilyTokenAgreement:
case StreamStablePrefixImpl::FamilyNativeCommit:
return family_candidate_raw_prefix_bytes(session);
case StreamStablePrefixImpl::GenericTextAgreement:
return generic_text_stable_prefix_candidate_raw_bytes(session);
}
return 0;
}
static bool append_committed_raw_prefix(transcribe_session * session, size_t candidate_bytes) {
if (session == nullptr || !session->has_result) {
return false;
}
const std::string & raw = session->full_text;
const size_t old_boundary = utf8_floor_boundary(
raw, std::min<size_t>(static_cast<size_t>(session->stream_raw_tentative_start_bytes), raw.size()));
const size_t candidate = utf8_floor_boundary(raw, std::min(candidate_bytes, raw.size()));
if (candidate <= old_boundary) {
return false;
}
if (old_boundary != session->stream_committed_text.size()) {
return false;
}
if (raw.compare(0, old_boundary, session->stream_committed_text.data(), old_boundary) != 0) {
return false;
}
session->stream_committed_text.append(raw.data() + old_boundary, candidate - old_boundary);
session->stream_raw_tentative_start_bytes = static_cast<uint64_t>(candidate);
return true;
}
static bool finalize_committed_text(transcribe_session * session) {
if (session == nullptr || !session->has_result) {
return false;
}
if (session->stream_commit_policy == TRANSCRIBE_STREAM_COMMIT_ON_FINALIZE ||
session->stream_committed_text.empty()) {
const bool changed = session->stream_committed_text != session->full_text;
session->stream_committed_text = session->full_text;
session->stream_raw_tentative_start_bytes = static_cast<uint64_t>(session->full_text.size());
return changed;
}
if (!starts_with_bytes(session->full_text, session->stream_committed_text)) {
session->stream_raw_tentative_start_bytes = static_cast<uint64_t>(session->full_text.size());
return false;
}
const size_t old_n = session->stream_committed_text.size();
if (session->full_text.size() > old_n) {
session->stream_committed_text.append(session->full_text.data() + old_n, session->full_text.size() - old_n);
session->stream_raw_tentative_start_bytes = static_cast<uint64_t>(session->full_text.size());
return true;
}
session->stream_raw_tentative_start_bytes = static_cast<uint64_t>(session->full_text.size());
return false;
}
struct StreamTextDelta {
bool committed_changed = false;
bool tentative_changed = false;
};
static StreamTextDelta apply_stream_text_policy(transcribe_session * session, bool is_finalize) {
StreamTextDelta delta;
if (session == nullptr) {
return delta;
}
const std::string prev_committed = session->stream_committed_text;
const std::string prev_tentative = session->stream_tentative_text;
if (is_finalize) {
delta.committed_changed = finalize_committed_text(session);
session->stream_tentative_text.clear();
} else if (session->has_result) {
size_t candidate = 0;
switch (session->stream_commit_policy) {
case TRANSCRIBE_STREAM_COMMIT_ON_FINALIZE:
candidate = 0;
break;
case TRANSCRIBE_STREAM_COMMIT_STABLE_PREFIX:
case TRANSCRIBE_STREAM_COMMIT_AUTO:
default:
candidate = selected_stable_prefix_candidate_raw_bytes(session);
break;
}
delta.committed_changed = append_committed_raw_prefix(session, candidate);
const size_t boundary = utf8_floor_boundary(
session->full_text, std::min<size_t>(static_cast<size_t>(session->stream_raw_tentative_start_bytes),
session->full_text.size()));
session->stream_tentative_text.assign(session->full_text.data() + boundary,
session->full_text.size() - boundary);
} else {
session->stream_tentative_text.clear();
}
delta.committed_changed = delta.committed_changed || session->stream_committed_text != prev_committed;
delta.tentative_changed = session->stream_tentative_text != prev_tentative;
return delta;
}
static void publish_stream_update_tail(transcribe_stream_update * update,
bool committed_changed,
bool tentative_changed) {
if (update == nullptr) {
return;
}
if (has_field(update->struct_size, k_stream_update_committed_changed_size)) {
update->committed_changed = committed_changed;
}
if (has_field(update->struct_size, k_stream_update_tentative_changed_size)) {
update->tentative_changed = tentative_changed;
}
}
static void publish_observable_delta(transcribe_session * session,
transcribe_stream_update * update,
int32_t prev_revision,
const std::string & prev_full_text,
bool prev_has_result,
const StreamTextDelta & text_delta) {
const bool raw_changed = session->has_result != prev_has_result || session->full_text != prev_full_text;
const bool observable_changed = raw_changed || text_delta.committed_changed || text_delta.tentative_changed ||
(update != nullptr && update->result_changed);
if (observable_changed && session->stream_revision == prev_revision) {
session->stream_revision += 1;
}
if (update != nullptr) {
update->result_changed =
update->result_changed || observable_changed || session->stream_revision != prev_revision;
update->revision = session->stream_revision;
publish_stream_update_tail(update, text_delta.committed_changed, text_delta.tentative_changed);
}
}
void warn_unsupported_advisory(const struct transcribe_model * model, const struct transcribe_run_params * rp) {
if (model == nullptr || rp == nullptr) {
return;
}
const char * arch_name = (model->arch != nullptr && model->arch->name != nullptr) ? model->arch->name : "(unknown)";
const char * variant = model->variant.c_str();
if (variant == nullptr || variant[0] == '\0') {
variant = "(unknown)";
}
const int pnc_raw = enum_field_raw(&rp->pnc);
const int itn_raw = enum_field_raw(&rp->itn);
const int diarize_raw = enum_field_raw(&rp->diarize);
char buf[512];
if (pnc_raw != TRANSCRIBE_PNC_MODE_DEFAULT && !transcribe::has_feature(model, TRANSCRIBE_FEATURE_PNC)) {
const char * req = (pnc_raw == TRANSCRIBE_PNC_MODE_ON) ? "ON" : "OFF";
std::snprintf(buf, sizeof(buf),
"transcribe_run: caller requested pnc=%s but model '%s' "
"(variant '%s') does not support pnc control; output will use "
"the model's default behavior. Use "
"transcribe_model_supports(model, TRANSCRIBE_FEATURE_PNC) to "
"pre-check.",
req, arch_name, variant);
transcribe_log_emit_or_stderr(TRANSCRIBE_LOG_LEVEL_WARN, buf);
}
if (itn_raw != TRANSCRIBE_ITN_MODE_DEFAULT && !transcribe::has_feature(model, TRANSCRIBE_FEATURE_ITN)) {
const char * req = (itn_raw == TRANSCRIBE_ITN_MODE_ON) ? "ON" : "OFF";
std::snprintf(buf, sizeof(buf),
"transcribe_run: caller requested itn=%s but model '%s' "
"(variant '%s') does not support itn control; output will use "
"the model's default behavior. Use "
"transcribe_model_supports(model, TRANSCRIBE_FEATURE_ITN) to "
"pre-check.",
req, arch_name, variant);
transcribe_log_emit_or_stderr(TRANSCRIBE_LOG_LEVEL_WARN, buf);
}
if (diarize_raw != TRANSCRIBE_DIARIZE_MODE_DEFAULT &&
!transcribe::has_feature(model, TRANSCRIBE_FEATURE_DIARIZATION)) {
const char * req = (diarize_raw == TRANSCRIBE_DIARIZE_MODE_ON) ? "ON" : "OFF";
std::snprintf(buf, sizeof(buf),
"transcribe_run: caller requested diarize=%s but model '%s' "
"(variant '%s') does not support diarization control; output "
"will use the model's default behavior. Use "
"transcribe_model_supports(model, TRANSCRIBE_FEATURE_DIARIZATION) "
"to pre-check.",
req, arch_name, variant);
transcribe_log_emit_or_stderr(TRANSCRIBE_LOG_LEVEL_WARN, buf);
}
}
}
static transcribe_status transcribe_model_load_file_impl(const char * path,
const struct transcribe_model_load_params * params,
struct transcribe_model ** out_model) {
ensure_ggml_time_init();
if (out_model == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
*out_model = nullptr;
if (path == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
struct transcribe_model_load_params params_defaults;
transcribe_model_load_params_init(¶ms_defaults);
if (params == nullptr) {
params = ¶ms_defaults;
}
if (const auto st = check_input_struct_size(params->struct_size, k_min_model_params_size); st != TRANSCRIBE_OK) {
return st;
}
switch (enum_field_raw(¶ms->backend)) {
case TRANSCRIBE_BACKEND_AUTO:
case TRANSCRIBE_BACKEND_CPU:
case TRANSCRIBE_BACKEND_METAL:
case TRANSCRIBE_BACKEND_VULKAN:
case TRANSCRIBE_BACKEND_CPU_ACCEL:
case TRANSCRIBE_BACKEND_CUDA:
case TRANSCRIBE_BACKEND_ROCM:
break;
default:
transcribe::log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "transcribe_model_load_file: invalid backend request %d",
enum_field_raw(¶ms->backend));
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (!transcribe::path_is_present(path)) {
return TRANSCRIBE_ERR_FILE_NOT_FOUND;
}
uint32_t magic = 0;
{
std::ifstream fin(transcribe::path_from_utf8(path), std::ios::binary);
if (!fin) {
return TRANSCRIBE_ERR_GGUF;
}
fin.read(reinterpret_cast<char *>(&magic), sizeof(magic));
if (!fin || fin.gcount() != sizeof(magic)) {
transcribe::log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"transcribe_model_load_file: short read on file "
"magic for %s",
path);
return TRANSCRIBE_ERR_GGUF;
}
}
if (magic == 0x67676d6cu) {
return transcribe::whisper::load_from_bin(path, params, out_model);
}
transcribe::Loader loader;
if (const transcribe_status st = loader.open(path); st != TRANSCRIBE_OK) {
return st;
}
const transcribe::Arch * arch = transcribe::find_arch(loader.arch().c_str());
if (arch == nullptr) {
return TRANSCRIBE_ERR_UNSUPPORTED_ARCH;
}
if (arch->load == nullptr) {
return TRANSCRIBE_ERR_NOT_IMPLEMENTED;
}
const transcribe_status st = arch->load(loader, params, out_model);
if (st == TRANSCRIBE_OK && out_model != nullptr && *out_model != nullptr) {
(*out_model)->meta = loader.meta();
}
return st;
}
static void transcribe_model_free_impl(struct transcribe_model * model) {
delete model;
}
static transcribe_status transcribe_session_init_impl(struct transcribe_model * model,
const struct transcribe_session_params * params,
struct transcribe_session ** out_session) {
if (out_session == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
*out_session = nullptr;
if (model == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
struct transcribe_session_params params_defaults;
transcribe_session_params_init(¶ms_defaults);
if (params == nullptr) {
params = ¶ms_defaults;
}
if (const auto st = check_input_struct_size(params->struct_size, k_min_context_params_size); st != TRANSCRIBE_OK) {
return st;
}
if (params->n_threads < 0) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
switch (enum_field_raw(¶ms->kv_type)) {
case TRANSCRIBE_KV_TYPE_AUTO:
case TRANSCRIBE_KV_TYPE_F32:
case TRANSCRIBE_KV_TYPE_F16:
break;
default:
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (has_field(params->struct_size, offsetof(struct transcribe_session_params, n_ctx) + sizeof(params->n_ctx)) &&
params->n_ctx < 0) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (model->arch == nullptr || model->arch->init_context == nullptr) {
return TRANSCRIBE_ERR_NOT_IMPLEMENTED;
}
return model->arch->init_context(model, params, out_session);
}
static void transcribe_session_free_impl(struct transcribe_session * session) {
if (session == nullptr) {
return;
}
struct transcribe_model * owned = session->owns_model ? session->model : nullptr;
delete session;
transcribe_model_free(owned); }
static transcribe_status transcribe_open_impl(const char * path,
const struct transcribe_model_load_params * load_params,
const struct transcribe_session_params * session_params,
struct transcribe_session ** out_session) {
if (out_session == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
*out_session = nullptr;
struct transcribe_model * model = nullptr;
if (const auto st = transcribe_model_load_file(path, load_params, &model); st != TRANSCRIBE_OK) {
return st;
}
struct transcribe_session * session = nullptr;
if (const auto st = transcribe_session_init(model, session_params, &session); st != TRANSCRIBE_OK) {
transcribe_model_free(model);
return st;
}
session->owns_model = true;
*out_session = session;
return TRANSCRIBE_OK;
}
extern "C" void transcribe_close(struct transcribe_session * session) {
transcribe_session_free(session);
}
extern "C" const struct transcribe_model * transcribe_get_model(const struct transcribe_session * session) {
if (session == nullptr) {
return nullptr;
}
return session->model;
}
extern "C" void transcribe_set_abort_callback(struct transcribe_session * session,
transcribe_abort_callback cb,
void * user_data) {
if (session == nullptr) {
return;
}
session->abort_cb = cb;
session->abort_userdata = user_data;
}
extern "C" bool transcribe_was_aborted(const struct transcribe_session * session) {
if (session == nullptr) {
return false;
}
return session->was_aborted;
}
extern "C" bool transcribe_was_truncated(const struct transcribe_session * session) {
if (session == nullptr) {
return false;
}
return session->was_truncated;
}
static transcribe_status transcribe_stream_begin_impl(struct transcribe_session * session,
const struct transcribe_run_params * run_params,
const struct transcribe_stream_params * stream_params) {
if (session == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
struct transcribe_run_params run_params_defaults;
transcribe_run_params_init(&run_params_defaults);
struct transcribe_stream_params stream_params_defaults;
transcribe_stream_params_init(&stream_params_defaults);
if (run_params == nullptr) {
run_params = &run_params_defaults;
}
if (stream_params == nullptr) {
stream_params = &stream_params_defaults;
}
if (const auto st = check_input_struct_size(run_params->struct_size, k_min_run_params_size); st != TRANSCRIBE_OK) {
return st;
}
if (const auto st = check_input_struct_size(stream_params->struct_size, k_min_stream_params_size);
st != TRANSCRIBE_OK) {
return st;
}
int commit_policy_raw = TRANSCRIBE_STREAM_COMMIT_AUTO;
uint32_t stable_prefix_agreement_n = 0;
if (has_field(stream_params->struct_size, k_stream_params_commit_policy_size)) {
static_assert(sizeof(stream_params->commit_policy) == sizeof(int),
"commit_policy must be int-sized for the raw read");
std::memcpy(&commit_policy_raw, &stream_params->commit_policy, sizeof(commit_policy_raw));
}
if (!valid_stream_commit_policy(commit_policy_raw)) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
const transcribe_stream_commit_policy commit_policy =
static_cast<transcribe_stream_commit_policy>(commit_policy_raw);
if (has_field(stream_params->struct_size, k_stream_params_agreement_n_size)) {
stable_prefix_agreement_n = stream_params->stable_prefix_agreement_n;
if (stable_prefix_agreement_n > 32) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
}
if (session->stream_state == TRANSCRIBE_STREAM_ACTIVE) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (session->model == nullptr || session->model->arch == nullptr) {
return TRANSCRIBE_ERR_NOT_IMPLEMENTED;
}
if (!session->model->caps.supports_streaming || session->model->arch->stream_begin == nullptr ||
session->model->arch->stream_feed == nullptr || session->model->arch->stream_finalize == nullptr) {
return TRANSCRIBE_ERR_NOT_IMPLEMENTED;
}
if (const transcribe_status st = validate_run_params_common(session, run_params); st != TRANSCRIBE_OK) {
return st;
}
if (run_params->task == TRANSCRIBE_TASK_TRANSLATE) {
return TRANSCRIBE_ERR_UNSUPPORTED_TASK;
}
if (stream_params->family != nullptr) {
if (stream_params->family->size < sizeof(struct transcribe_ext)) {
return TRANSCRIBE_ERR_BAD_STRUCT_SIZE;
}
if (!transcribe_model_accepts_ext_kind(session->model, TRANSCRIBE_EXT_SLOT_STREAM,
stream_params->family->kind)) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
}
warn_unsupported_advisory(session->model, run_params);
if (session->model->arch->stream_validate != nullptr) {
if (const transcribe_status st = session->model->arch->stream_validate(session, run_params, stream_params);
st != TRANSCRIBE_OK) {
return st;
}
}
session->clear_result();
session->t_mel_us = 0;
session->t_encode_us = 0;
session->t_decode_us = 0;
session->was_aborted = false;
session->was_truncated = false;
session->stream_state = TRANSCRIBE_STREAM_ACTIVE;
session->stream_commit_policy = commit_policy;
session->stream_stable_prefix_agreement_n = stable_prefix_agreement_n;
session->stream_language_owned = run_params->language != nullptr ? run_params->language : "";
session->stream_target_language_owned = run_params->target_language != nullptr ? run_params->target_language : "";
struct transcribe_run_params run_params_owned;
transcribe_run_params_init(&run_params_owned);
std::memcpy(&run_params_owned, run_params,
static_cast<size_t>(std::min<uint64_t>(run_params->struct_size, sizeof(run_params_owned))));
run_params_owned.language = run_params->language != nullptr ? session->stream_language_owned.c_str() : nullptr;
run_params_owned.target_language =
run_params->target_language != nullptr ? session->stream_target_language_owned.c_str() : nullptr;
run_params_owned.family = nullptr;
const transcribe_status st = session->model->arch->stream_begin(session, &run_params_owned, stream_params);
if (st != TRANSCRIBE_OK) {
session->stream_state = TRANSCRIBE_STREAM_FAILED;
session->stream_last_status = st;
}
return st;
}
static transcribe_status prepare_stream_update(struct transcribe_stream_update * update) {
if (update == nullptr) {
return TRANSCRIBE_OK;
}
if (const auto st = check_struct_size(update->struct_size, k_min_stream_update_size); st != TRANSCRIBE_OK) {
return st;
}
const uint64_t caller_size = update->struct_size;
transcribe_stream_update staged{};
staged.struct_size = caller_size;
copy_out_prefix(update, &staged, caller_size, sizeof(staged));
return TRANSCRIBE_OK;
}
static transcribe_status transcribe_stream_feed_impl(struct transcribe_session * session,
const float * pcm,
int n_samples,
struct transcribe_stream_update * update) {
if (const auto st = prepare_stream_update(update); st != TRANSCRIBE_OK) {
return st;
}
if (session == nullptr || pcm == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (n_samples <= 0) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (session->stream_state != TRANSCRIBE_STREAM_ACTIVE) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (session->model == nullptr || session->model->arch == nullptr || session->model->arch->stream_feed == nullptr) {
return TRANSCRIBE_ERR_NOT_IMPLEMENTED;
}
const int32_t prev_revision = session->stream_revision;
const std::string prev_full_text = session->full_text;
const bool prev_has_result = session->has_result;
const transcribe_status st = session->model->arch->stream_feed(session, pcm, n_samples, update);
if (st != TRANSCRIBE_OK) {
session->stream_state = TRANSCRIBE_STREAM_FAILED;
session->stream_last_status = st;
return st;
}
const StreamTextDelta text_delta = apply_stream_text_policy(session, false);
if (session->stream_commit_policy == TRANSCRIBE_STREAM_COMMIT_ON_FINALIZE) {
session->n_committed_tokens = 0;
session->n_committed_words = 0;
session->n_committed_segments = 0;
}
publish_observable_delta(session, update, prev_revision, prev_full_text, prev_has_result, text_delta);
if (update != nullptr && session->stream_commit_policy == TRANSCRIBE_STREAM_COMMIT_ON_FINALIZE) {
update->audio_committed_ms = 0;
}
return st;
}
static transcribe_status transcribe_stream_finalize_impl(struct transcribe_session * session,
struct transcribe_stream_update * update) {
if (const auto st = prepare_stream_update(update); st != TRANSCRIBE_OK) {
return st;
}
if (session == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (session->stream_state != TRANSCRIBE_STREAM_ACTIVE) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (session->model == nullptr || session->model->arch == nullptr ||
session->model->arch->stream_finalize == nullptr) {
return TRANSCRIBE_ERR_NOT_IMPLEMENTED;
}
const int32_t prev_revision = session->stream_revision;
const std::string prev_full_text = session->full_text;
const bool prev_has_result = session->has_result;
const transcribe_status st = session->model->arch->stream_finalize(session, update);
if (update != nullptr) {
update->is_final = true;
}
if (st != TRANSCRIBE_OK) {
session->stream_state = TRANSCRIBE_STREAM_FAILED;
session->stream_last_status = st;
return st;
}
const StreamTextDelta text_delta = apply_stream_text_policy(session, true);
publish_observable_delta(session, update, prev_revision, prev_full_text, prev_has_result, text_delta);
session->stream_state = TRANSCRIBE_STREAM_FINISHED;
return TRANSCRIBE_OK;
}
static void transcribe_stream_reset_impl(struct transcribe_session * session) {
if (session == nullptr) {
return;
}
if (session->model != nullptr && session->model->arch != nullptr && session->model->arch->stream_reset != nullptr) {
session->model->arch->stream_reset(session);
}
session->clear_result();
session->stream_state = TRANSCRIBE_STREAM_IDLE;
session->was_aborted = false;
}
extern "C" enum transcribe_stream_state transcribe_stream_get_state(const struct transcribe_session * session) {
if (session == nullptr) {
return TRANSCRIBE_STREAM_IDLE;
}
return session->stream_state;
}
extern "C" int transcribe_stream_revision(const struct transcribe_session * session) {
if (session == nullptr) {
return 0;
}
return session->stream_revision;
}
extern "C" int transcribe_stream_n_committed_segments(const struct transcribe_session * session) {
if (session == nullptr) {
return 0;
}
return session->n_committed_segments;
}
extern "C" int transcribe_stream_n_committed_words(const struct transcribe_session * session) {
if (session == nullptr) {
return 0;
}
return session->n_committed_words;
}
extern "C" int transcribe_stream_n_committed_tokens(const struct transcribe_session * session) {
if (session == nullptr) {
return 0;
}
return session->n_committed_tokens;
}
extern "C" transcribe_status transcribe_stream_last_status(const struct transcribe_session * session) {
if (session == nullptr) {
return TRANSCRIBE_OK;
}
return session->stream_last_status;
}
extern "C" transcribe_status transcribe_stream_get_text(const struct transcribe_session * session,
struct transcribe_stream_text * out) {
if (out == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (const auto st = check_struct_size(out->struct_size, k_min_stream_text_size); st != TRANSCRIBE_OK) {
return st;
}
const uint64_t caller_size = out->struct_size;
transcribe_stream_text staged{};
staged.struct_size = caller_size;
if (session != nullptr && session->has_result) {
staged.full_text = session->full_text.c_str();
staged.full_text_bytes = static_cast<uint64_t>(session->full_text.size());
staged.committed_text = session->stream_committed_text.c_str();
staged.committed_text_bytes = static_cast<uint64_t>(session->stream_committed_text.size());
staged.tentative_text = session->stream_tentative_text.c_str();
staged.tentative_text_bytes = static_cast<uint64_t>(session->stream_tentative_text.size());
staged.raw_tentative_start_bytes =
std::min<uint64_t>(session->stream_raw_tentative_start_bytes, staged.full_text_bytes);
} else {
staged.full_text = "";
staged.committed_text = "";
staged.tentative_text = "";
}
copy_out_prefix(out, &staged, caller_size, sizeof(staged));
return TRANSCRIBE_OK;
}
namespace {
struct scratch_release_guard {
transcribe_session * session = nullptr;
bool armed = false;
~scratch_release_guard() {
if (armed && session != nullptr) {
session->release_scratch();
}
}
};
}
static transcribe_status run_one_inner(struct transcribe_session * session,
const float * pcm,
int n_samples,
const struct transcribe_run_params * params,
bool * committed = nullptr) {
if (session == nullptr || pcm == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (n_samples <= 0) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
struct transcribe_run_params params_defaults;
transcribe_run_params_init(¶ms_defaults);
if (params == nullptr) {
params = ¶ms_defaults;
}
if (const auto st = check_input_struct_size(params->struct_size, k_min_run_params_size); st != TRANSCRIBE_OK) {
return st;
}
if (session->stream_state == TRANSCRIBE_STREAM_ACTIVE) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (params->family != nullptr && session->model != nullptr && session->model->arch != nullptr) {
if (params->family->size < sizeof(struct transcribe_ext)) {
return TRANSCRIBE_ERR_BAD_STRUCT_SIZE;
}
if (!transcribe_model_accepts_ext_kind(session->model, TRANSCRIBE_EXT_SLOT_RUN, params->family->kind)) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
}
if (session->model != nullptr) {
if (const transcribe_status st = validate_run_params_common(session, params); st != TRANSCRIBE_OK) {
return st;
}
warn_unsupported_advisory(session->model, params);
if (params->task == TRANSCRIBE_TASK_TRANSLATE && !session->model->caps.supports_translate) {
return TRANSCRIBE_ERR_UNSUPPORTED_TASK;
}
if (session->model->arch != nullptr && session->model->arch->run_validate != nullptr) {
if (const transcribe_status st = session->model->arch->run_validate(session, params); st != TRANSCRIBE_OK) {
return st;
}
}
}
if (committed != nullptr) {
*committed = true;
}
session->clear_result();
session->t_mel_us = 0;
session->t_encode_us = 0;
session->t_decode_us = 0;
session->was_aborted = false;
session->was_truncated = false;
session->stream_state = TRANSCRIBE_STREAM_IDLE;
if (session->model == nullptr || session->model->arch == nullptr || session->model->arch->run == nullptr) {
return TRANSCRIBE_ERR_NOT_IMPLEMENTED;
}
return session->model->arch->run(session, pcm, n_samples, params);
}
static transcribe_status transcribe_run_impl(struct transcribe_session * session,
const float * pcm,
int n_samples,
const struct transcribe_run_params * params) {
if (session != nullptr && pcm != nullptr && n_samples > 0) {
session->batch_results.clear();
}
scratch_release_guard scratch_release;
scratch_release.session = session;
return run_one_inner(session, pcm, n_samples, params, &scratch_release.armed);
}
namespace {
void pad_batch_results_aborted(transcribe_session * s, int n) {
while (s->batch_results.size() < static_cast<size_t>(n)) {
transcribe_session::ResultSet rs;
rs.status = TRANSCRIBE_ERR_ABORTED;
s->batch_results.push_back(std::move(rs));
}
}
void restore_scratch_from_result(transcribe_session * s, const transcribe_session::ResultSet & rs) {
s->tokens = rs.tokens;
s->words = rs.words;
s->segments = rs.segments;
s->speaker_segments = rs.speaker_segments;
s->full_text = rs.full_text;
s->raw_text = rs.raw_text;
s->detected_language = rs.detected_language;
s->result_kind = rs.result_kind;
s->has_result = rs.has_result;
}
}
static transcribe_status transcribe_run_batch_impl(struct transcribe_session * session,
const float * const * pcm,
const int * n_samples,
int n,
const struct transcribe_run_params * params) {
if (session == nullptr || pcm == nullptr || n_samples == nullptr || n <= 0) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
struct transcribe_run_params params_defaults;
transcribe_run_params_init(¶ms_defaults);
if (params == nullptr) {
params = ¶ms_defaults;
}
if (const auto st = check_input_struct_size(params->struct_size, k_min_run_params_size); st != TRANSCRIBE_OK) {
return st;
}
if (session->stream_state == TRANSCRIBE_STREAM_ACTIVE) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (params->family != nullptr && session->model != nullptr && session->model->arch != nullptr) {
if (params->family->size < sizeof(struct transcribe_ext)) {
return TRANSCRIBE_ERR_BAD_STRUCT_SIZE;
}
if (!transcribe_model_accepts_ext_kind(session->model, TRANSCRIBE_EXT_SLOT_RUN, params->family->kind)) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
}
if (session->model != nullptr) {
if (const transcribe_status st = validate_run_params_common(session, params); st != TRANSCRIBE_OK) {
return st;
}
warn_unsupported_advisory(session->model, params);
if (params->task == TRANSCRIBE_TASK_TRANSLATE && !session->model->caps.supports_translate) {
return TRANSCRIBE_ERR_UNSUPPORTED_TASK;
}
if (session->model->arch != nullptr && session->model->arch->run_validate != nullptr) {
if (const transcribe_status st = session->model->arch->run_validate(session, params); st != TRANSCRIBE_OK) {
return st;
}
}
}
if (session->model == nullptr || session->model->arch == nullptr || session->model->arch->run == nullptr) {
return TRANSCRIBE_ERR_NOT_IMPLEMENTED;
}
session->clear_result();
session->t_mel_us = 0;
session->t_encode_us = 0;
session->t_decode_us = 0;
session->was_aborted = false;
session->was_truncated = false;
session->stream_state = TRANSCRIBE_STREAM_IDLE;
session->batch_results.clear();
scratch_release_guard scratch_release;
scratch_release.session = session;
scratch_release.armed = true;
if (session->model->arch->run_batch != nullptr) {
const transcribe_status st = session->model->arch->run_batch(session, pcm, n_samples, n, params);
if (st == TRANSCRIBE_ERR_ABORTED) {
pad_batch_results_aborted(session, n);
}
if (!session->batch_results.empty()) {
restore_scratch_from_result(session, session->batch_results.front());
}
return st;
}
session->batch_results.reserve(static_cast<size_t>(n));
transcribe_status batch_status = TRANSCRIBE_OK;
for (int i = 0; i < n; ++i) {
if (session->poll_abort()) {
batch_status = TRANSCRIBE_ERR_ABORTED;
break;
}
const transcribe_status st = run_one_inner(session, pcm[i], n_samples[i], params);
if (st == TRANSCRIBE_OK) {
session->batch_results.push_back(session->capture_result(st));
} else {
transcribe_session::ResultSet rs;
rs.status = st;
session->batch_results.push_back(std::move(rs));
if (st == TRANSCRIBE_ERR_ABORTED) {
batch_status = TRANSCRIBE_ERR_ABORTED;
break;
}
}
}
if (batch_status == TRANSCRIBE_ERR_ABORTED) {
pad_batch_results_aborted(session, n);
}
if (!session->batch_results.empty()) {
restore_scratch_from_result(session, session->batch_results.front());
}
return batch_status;
}
extern "C" transcribe_status transcribe_model_get_capabilities(const struct transcribe_model * model,
struct transcribe_capabilities * out_caps) {
if (model == nullptr || out_caps == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (const auto st = check_struct_size(out_caps->struct_size, k_min_capabilities_size); st != TRANSCRIBE_OK) {
return st;
}
const uint64_t caller_size = out_caps->struct_size;
transcribe_capabilities staged = model->caps;
staged.struct_size = caller_size;
copy_out_prefix(out_caps, &staged, caller_size, sizeof(staged));
return TRANSCRIBE_OK;
}
extern "C" transcribe_status transcribe_session_get_limits(const struct transcribe_session * session,
struct transcribe_session_limits * out) {
if (session == nullptr || out == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (const auto st = check_struct_size(out->struct_size, k_min_session_limits_size); st != TRANSCRIBE_OK) {
return st;
}
transcribe_session_limits staged;
transcribe_session_limits_init(&staged);
const transcribe_model * model = session->model;
const auto & lb = model->limits;
if (lb.has_context_cap && lb.model_max_ctx > 0) {
int32_t eff = lb.model_max_ctx;
if (session->n_ctx > 0 && session->n_ctx < eff) {
eff = session->n_ctx;
}
staged.effective_n_ctx = eff;
if (lb.audio_from_caps) {
staged.effective_max_audio_ms = model->caps.max_audio_ms;
} else {
const int64_t audio_tokens = (int64_t) eff - lb.prompt_overhead - lb.gen_reserve;
staged.effective_max_audio_ms = (audio_tokens > 0 && lb.ms_per_audio_token > 0.0) ?
(int64_t) ((double) audio_tokens * lb.ms_per_audio_token) :
0;
}
const int64_t kv_bytes_per_elem = (session->kv_type == TRANSCRIBE_KV_TYPE_F32) ? 4 : 2;
staged.max_kv_bytes = lb.kv_elems_per_ctx_token * (int64_t) eff * kv_bytes_per_elem;
} else {
staged.effective_n_ctx = 0;
staged.effective_max_audio_ms = model->caps.max_audio_ms;
staged.max_kv_bytes = 0;
}
const uint64_t caller_size = out->struct_size;
staged.struct_size = caller_size;
copy_out_prefix(out, &staged, caller_size, sizeof(staged));
return TRANSCRIBE_OK;
}
extern "C" const char * transcribe_model_arch_string(const struct transcribe_model * model) {
if (model == nullptr || model->arch == nullptr) {
return "";
}
return model->arch->name != nullptr ? model->arch->name : "";
}
extern "C" const char * transcribe_model_variant_string(const struct transcribe_model * model) {
if (model == nullptr) {
return "";
}
return model->variant.c_str();
}
extern "C" const char * transcribe_model_meta_val_str(const struct transcribe_model * model, const char * key) {
if (model == nullptr || key == nullptr) {
return "";
}
const auto it = model->meta.find(key);
return it != model->meta.end() ? it->second.c_str() : "";
}
static int transcribe_tokenize_impl(const struct transcribe_model * model,
const char * text,
int32_t * tokens,
size_t n_max) {
if (model == nullptr || text == nullptr) {
return INT_MIN;
}
const transcribe::Tokenizer * tok = model->tokenizer();
if (tok == nullptr) {
return INT_MIN;
}
std::vector<int32_t> ids;
if (tok->encode(std::string(text), ids) != TRANSCRIBE_OK) {
return INT_MIN;
}
const size_t n = ids.size();
if (n > static_cast<size_t>(INT_MAX)) {
return INT_MIN;
}
if (n > n_max) {
if (n > static_cast<size_t>(INT_MAX)) {
return INT_MIN;
}
return -static_cast<int>(n);
}
if (tokens == nullptr && n > 0) {
return INT_MIN;
}
for (size_t i = 0; i < n; ++i) {
tokens[i] = ids[i];
}
return static_cast<int>(n);
}
extern "C" const char * transcribe_model_backend(const struct transcribe_model * model) {
if (model == nullptr) {
return "";
}
return model->backend.c_str();
}
static transcribe_device_t transcribe_model_device_impl(const struct transcribe_model * model) {
if (model == nullptr || model->primary_backend == nullptr) {
return nullptr;
}
ggml_backend_dev_t dev = ggml_backend_get_device(model->primary_backend);
if (dev == nullptr || device_from_handle(reinterpret_cast<transcribe_device_t>(dev)) == nullptr) {
return nullptr;
}
return reinterpret_cast<transcribe_device_t>(dev);
}
extern "C" transcribe_status transcribe_get_timings(const struct transcribe_session * session,
struct transcribe_timings * out_timings) {
if (session == nullptr || out_timings == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (const auto st = check_struct_size(out_timings->struct_size, k_min_timings_size); st != TRANSCRIBE_OK) {
return st;
}
const uint64_t caller_size = out_timings->struct_size;
transcribe_timings staged{};
staged.struct_size = caller_size;
if (session->model != nullptr) {
staged.load_ms = static_cast<float>(session->model->t_load_us) / 1000.0f;
}
staged.mel_ms = static_cast<float>(session->t_mel_us) / 1000.0f;
staged.encode_ms = static_cast<float>(session->t_encode_us) / 1000.0f;
staged.decode_ms = static_cast<float>(session->t_decode_us) / 1000.0f;
copy_out_prefix(out_timings, &staged, caller_size, sizeof(staged));
return TRANSCRIBE_OK;
}
extern "C" void transcribe_print_timings(const struct transcribe_session * session) {
if (session == nullptr) {
return;
}
struct transcribe_timings t;
transcribe_timings_init(&t);
(void) transcribe_get_timings(session, &t);
char buf[256];
std::snprintf(buf, sizeof(buf),
"timings: load=%.2f ms mel=%.2f ms "
"encode=%.2f ms decode=%.2f ms",
t.load_ms, t.mel_ms, t.encode_ms, t.decode_ms);
transcribe_log_emit_or_stderr(TRANSCRIBE_LOG_LEVEL_INFO, buf);
}
extern "C" void transcribe_reset_timings(struct transcribe_session * session) {
if (session == nullptr) {
return;
}
session->t_mel_us = 0;
session->t_encode_us = 0;
session->t_decode_us = 0;
}
extern "C" const char * transcribe_full_text(const struct transcribe_session * session) {
if (session == nullptr || !session->has_result) {
return "";
}
return session->full_text.c_str();
}
extern "C" const char * transcribe_raw_text(const struct transcribe_session * session) {
if (session == nullptr || !session->has_result) {
return "";
}
return session->raw_text.c_str();
}
extern "C" const char * transcribe_detected_language(const struct transcribe_session * session) {
if (session == nullptr || !session->has_result) {
return "";
}
return session->detected_language.c_str();
}
extern "C" transcribe_timestamp_kind transcribe_returned_timestamp_kind(const struct transcribe_session * session) {
if (session == nullptr || !session->has_result) {
return TRANSCRIBE_TIMESTAMPS_NONE;
}
return session->result_kind;
}
extern "C" int transcribe_n_segments(const struct transcribe_session * session) {
if (session == nullptr || !session->has_result) {
return 0;
}
return static_cast<int>(session->segments.size());
}
extern "C" int transcribe_n_words(const struct transcribe_session * session) {
if (session == nullptr || !session->has_result) {
return 0;
}
return static_cast<int>(session->words.size());
}
extern "C" int transcribe_n_tokens(const struct transcribe_session * session) {
if (session == nullptr || !session->has_result) {
return 0;
}
return static_cast<int>(session->tokens.size());
}
extern "C" int transcribe_n_speaker_segments(const struct transcribe_session * session) {
if (session == nullptr || !session->has_result) {
return 0;
}
return static_cast<int>(session->speaker_segments.size());
}
extern "C" transcribe_status transcribe_get_segment(const struct transcribe_session * session,
int i,
struct transcribe_segment * out) {
if (out == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (const auto st = check_struct_size(out->struct_size, k_min_segment_size); st != TRANSCRIBE_OK) {
return st;
}
const uint64_t caller_size = out->struct_size;
transcribe_segment zero{};
zero.struct_size = caller_size;
copy_out_prefix(out, &zero, caller_size, sizeof(zero));
if (session == nullptr || !session->has_result || i < 0 || static_cast<size_t>(i) >= session->segments.size()) {
return TRANSCRIBE_OK;
}
const auto & s = session->segments[static_cast<size_t>(i)];
transcribe_segment staged{};
staged.struct_size = caller_size;
staged.t0_ms = s.t0_ms;
staged.t1_ms = s.t1_ms;
staged.first_word = s.first_word;
staged.n_words = s.n_words;
staged.first_token = s.first_token;
staged.n_tokens = s.n_tokens;
staged.text = s.text.c_str();
staged.speaker_id = s.speaker_id;
copy_out_prefix(out, &staged, caller_size, sizeof(staged));
return TRANSCRIBE_OK;
}
extern "C" transcribe_status transcribe_get_word(const struct transcribe_session * session,
int i,
struct transcribe_word * out) {
if (out == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (const auto st = check_struct_size(out->struct_size, k_min_word_size); st != TRANSCRIBE_OK) {
return st;
}
const uint64_t caller_size = out->struct_size;
transcribe_word zero{};
zero.struct_size = caller_size;
copy_out_prefix(out, &zero, caller_size, sizeof(zero));
if (session == nullptr || !session->has_result || i < 0 || static_cast<size_t>(i) >= session->words.size()) {
return TRANSCRIBE_OK;
}
const auto & w = session->words[static_cast<size_t>(i)];
transcribe_word staged{};
staged.struct_size = caller_size;
staged.t0_ms = w.t0_ms;
staged.t1_ms = w.t1_ms;
staged.seg_index = w.seg_index;
staged.first_token = w.first_token;
staged.n_tokens = w.n_tokens;
staged.text = w.text.c_str();
copy_out_prefix(out, &staged, caller_size, sizeof(staged));
return TRANSCRIBE_OK;
}
extern "C" transcribe_status transcribe_get_token(const struct transcribe_session * session,
int i,
struct transcribe_token * out) {
if (out == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (const auto st = check_struct_size(out->struct_size, k_min_token_size); st != TRANSCRIBE_OK) {
return st;
}
const uint64_t caller_size = out->struct_size;
transcribe_token zero{};
zero.struct_size = caller_size;
copy_out_prefix(out, &zero, caller_size, sizeof(zero));
if (session == nullptr || !session->has_result || i < 0 || static_cast<size_t>(i) >= session->tokens.size()) {
return TRANSCRIBE_OK;
}
const auto & t = session->tokens[static_cast<size_t>(i)];
transcribe_token staged{};
staged.struct_size = caller_size;
staged.id = t.id;
staged.p = t.p;
staged.t0_ms = t.t0_ms;
staged.t1_ms = t.t1_ms;
staged.seg_index = t.seg_index;
staged.word_index = t.word_index;
staged.text = t.text.c_str();
copy_out_prefix(out, &staged, caller_size, sizeof(staged));
return TRANSCRIBE_OK;
}
extern "C" transcribe_status transcribe_get_speaker_segment(const struct transcribe_session * session,
int i,
struct transcribe_speaker_segment * out) {
if (out == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (const auto st = check_struct_size(out->struct_size, k_min_speaker_segment_size); st != TRANSCRIBE_OK) {
return st;
}
const uint64_t caller_size = out->struct_size;
transcribe_speaker_segment zero{};
zero.struct_size = caller_size;
copy_out_prefix(out, &zero, caller_size, sizeof(zero));
if (session == nullptr || !session->has_result || i < 0 ||
static_cast<size_t>(i) >= session->speaker_segments.size()) {
return TRANSCRIBE_OK;
}
const auto & s = session->speaker_segments[static_cast<size_t>(i)];
transcribe_speaker_segment staged{};
staged.struct_size = caller_size;
staged.t0_ms = s.t0_ms;
staged.t1_ms = s.t1_ms;
staged.speaker_id = s.speaker_id;
staged.p = s.p;
copy_out_prefix(out, &staged, caller_size, sizeof(staged));
return TRANSCRIBE_OK;
}
namespace {
struct BatchResultView {
const std::vector<transcribe_session::TokenEntry> * tokens = nullptr;
const std::vector<transcribe_session::WordEntry> * words = nullptr;
const std::vector<transcribe_session::SegmentEntry> * segments = nullptr;
const std::vector<transcribe_session::SpeakerSegmentEntry> * speaker_segments = nullptr;
const std::string * full_text = nullptr;
const std::string * raw_text = nullptr;
const std::string * detected_language = nullptr;
transcribe_timestamp_kind result_kind = TRANSCRIBE_TIMESTAMPS_NONE;
bool valid = false;
};
int batch_result_count(const transcribe_session * s) {
if (s == nullptr) {
return 0;
}
if (!s->batch_results.empty()) {
return static_cast<int>(s->batch_results.size());
}
return s->has_result ? 1 : 0;
}
BatchResultView batch_result_view(const transcribe_session * s, int i) {
BatchResultView v;
if (s == nullptr || i < 0) {
return v;
}
if (!s->batch_results.empty()) {
if (static_cast<size_t>(i) >= s->batch_results.size()) {
return v;
}
const auto & rs = s->batch_results[static_cast<size_t>(i)];
if (!rs.has_result) {
return v; }
v.tokens = &rs.tokens;
v.words = &rs.words;
v.segments = &rs.segments;
v.speaker_segments = &rs.speaker_segments;
v.full_text = &rs.full_text;
v.raw_text = &rs.raw_text;
v.detected_language = &rs.detected_language;
v.result_kind = rs.result_kind;
v.valid = true;
return v;
}
if (i == 0 && s->has_result) {
v.tokens = &s->tokens;
v.words = &s->words;
v.segments = &s->segments;
v.speaker_segments = &s->speaker_segments;
v.full_text = &s->full_text;
v.raw_text = &s->raw_text;
v.detected_language = &s->detected_language;
v.result_kind = s->result_kind;
v.valid = true;
}
return v;
}
}
extern "C" int transcribe_batch_n_results(const struct transcribe_session * session) {
return batch_result_count(session);
}
extern "C" transcribe_status transcribe_batch_status(const struct transcribe_session * session, int i) {
if (session == nullptr || i < 0) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (!session->batch_results.empty()) {
if (static_cast<size_t>(i) >= session->batch_results.size()) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
return session->batch_results[static_cast<size_t>(i)].status;
}
if (i == 0 && session->has_result) {
return TRANSCRIBE_OK;
}
return TRANSCRIBE_ERR_INVALID_ARG;
}
extern "C" const char * transcribe_batch_full_text(const struct transcribe_session * session, int i) {
const BatchResultView v = batch_result_view(session, i);
return v.valid ? v.full_text->c_str() : "";
}
extern "C" const char * transcribe_batch_raw_text(const struct transcribe_session * session, int i) {
const BatchResultView v = batch_result_view(session, i);
return v.valid ? v.raw_text->c_str() : "";
}
extern "C" const char * transcribe_batch_detected_language(const struct transcribe_session * session, int i) {
const BatchResultView v = batch_result_view(session, i);
return v.valid ? v.detected_language->c_str() : "";
}
extern "C" transcribe_timestamp_kind transcribe_batch_returned_timestamp_kind(const struct transcribe_session * session,
int i) {
const BatchResultView v = batch_result_view(session, i);
return v.valid ? v.result_kind : TRANSCRIBE_TIMESTAMPS_NONE;
}
extern "C" int transcribe_batch_n_segments(const struct transcribe_session * session, int i) {
const BatchResultView v = batch_result_view(session, i);
return v.valid ? static_cast<int>(v.segments->size()) : 0;
}
extern "C" int transcribe_batch_n_words(const struct transcribe_session * session, int i) {
const BatchResultView v = batch_result_view(session, i);
return v.valid ? static_cast<int>(v.words->size()) : 0;
}
extern "C" int transcribe_batch_n_tokens(const struct transcribe_session * session, int i) {
const BatchResultView v = batch_result_view(session, i);
return v.valid ? static_cast<int>(v.tokens->size()) : 0;
}
extern "C" transcribe_status transcribe_batch_get_segment(const struct transcribe_session * session,
int i,
int j,
struct transcribe_segment * out) {
if (out == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (const auto st = check_struct_size(out->struct_size, k_min_segment_size); st != TRANSCRIBE_OK) {
return st;
}
const uint64_t caller_size = out->struct_size;
transcribe_segment zero{};
zero.struct_size = caller_size;
copy_out_prefix(out, &zero, caller_size, sizeof(zero));
const BatchResultView v = batch_result_view(session, i);
if (!v.valid || j < 0 || static_cast<size_t>(j) >= v.segments->size()) {
return TRANSCRIBE_OK;
}
const auto & s = (*v.segments)[static_cast<size_t>(j)];
transcribe_segment staged{};
staged.struct_size = caller_size;
staged.t0_ms = s.t0_ms;
staged.t1_ms = s.t1_ms;
staged.first_word = s.first_word;
staged.n_words = s.n_words;
staged.first_token = s.first_token;
staged.n_tokens = s.n_tokens;
staged.text = s.text.c_str();
staged.speaker_id = s.speaker_id;
copy_out_prefix(out, &staged, caller_size, sizeof(staged));
return TRANSCRIBE_OK;
}
extern "C" transcribe_status transcribe_batch_get_word(const struct transcribe_session * session,
int i,
int j,
struct transcribe_word * out) {
if (out == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (const auto st = check_struct_size(out->struct_size, k_min_word_size); st != TRANSCRIBE_OK) {
return st;
}
const uint64_t caller_size = out->struct_size;
transcribe_word zero{};
zero.struct_size = caller_size;
copy_out_prefix(out, &zero, caller_size, sizeof(zero));
const BatchResultView v = batch_result_view(session, i);
if (!v.valid || j < 0 || static_cast<size_t>(j) >= v.words->size()) {
return TRANSCRIBE_OK;
}
const auto & w = (*v.words)[static_cast<size_t>(j)];
transcribe_word staged{};
staged.struct_size = caller_size;
staged.t0_ms = w.t0_ms;
staged.t1_ms = w.t1_ms;
staged.seg_index = w.seg_index;
staged.first_token = w.first_token;
staged.n_tokens = w.n_tokens;
staged.text = w.text.c_str();
copy_out_prefix(out, &staged, caller_size, sizeof(staged));
return TRANSCRIBE_OK;
}
extern "C" transcribe_status transcribe_batch_get_token(const struct transcribe_session * session,
int i,
int j,
struct transcribe_token * out) {
if (out == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (const auto st = check_struct_size(out->struct_size, k_min_token_size); st != TRANSCRIBE_OK) {
return st;
}
const uint64_t caller_size = out->struct_size;
transcribe_token zero{};
zero.struct_size = caller_size;
copy_out_prefix(out, &zero, caller_size, sizeof(zero));
const BatchResultView v = batch_result_view(session, i);
if (!v.valid || j < 0 || static_cast<size_t>(j) >= v.tokens->size()) {
return TRANSCRIBE_OK;
}
const auto & t = (*v.tokens)[static_cast<size_t>(j)];
transcribe_token staged{};
staged.struct_size = caller_size;
staged.id = t.id;
staged.p = t.p;
staged.t0_ms = t.t0_ms;
staged.t1_ms = t.t1_ms;
staged.seg_index = t.seg_index;
staged.word_index = t.word_index;
staged.text = t.text.c_str();
copy_out_prefix(out, &staged, caller_size, sizeof(staged));
return TRANSCRIBE_OK;
}
extern "C" int transcribe_batch_n_speaker_segments(const struct transcribe_session * session, int i) {
const BatchResultView v = batch_result_view(session, i);
return v.valid ? static_cast<int>(v.speaker_segments->size()) : 0;
}
extern "C" transcribe_status transcribe_batch_get_speaker_segment(const struct transcribe_session * session,
int i,
int j,
struct transcribe_speaker_segment * out) {
if (out == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (const auto st = check_struct_size(out->struct_size, k_min_speaker_segment_size); st != TRANSCRIBE_OK) {
return st;
}
const uint64_t caller_size = out->struct_size;
transcribe_speaker_segment zero{};
zero.struct_size = caller_size;
copy_out_prefix(out, &zero, caller_size, sizeof(zero));
const BatchResultView v = batch_result_view(session, i);
if (!v.valid || j < 0 || static_cast<size_t>(j) >= v.speaker_segments->size()) {
return TRANSCRIBE_OK;
}
const auto & s = (*v.speaker_segments)[static_cast<size_t>(j)];
transcribe_speaker_segment staged{};
staged.struct_size = caller_size;
staged.t0_ms = s.t0_ms;
staged.t1_ms = s.t1_ms;
staged.speaker_id = s.speaker_id;
staged.p = s.p;
copy_out_prefix(out, &staged, caller_size, sizeof(staged));
return TRANSCRIBE_OK;
}
extern "C" transcribe_status transcribe_batch_get_timings(const struct transcribe_session * session,
int i,
struct transcribe_timings * out) {
if (session == nullptr || out == nullptr || i < 0) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (const auto st = check_struct_size(out->struct_size, k_min_timings_size); st != TRANSCRIBE_OK) {
return st;
}
int64_t mel_us = 0, enc_us = 0, dec_us = 0;
if (!session->batch_results.empty()) {
if (static_cast<size_t>(i) >= session->batch_results.size()) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
const auto & rs = session->batch_results[static_cast<size_t>(i)];
mel_us = rs.t_mel_us;
enc_us = rs.t_encode_us;
dec_us = rs.t_decode_us;
} else if (i == 0) {
mel_us = session->t_mel_us;
enc_us = session->t_encode_us;
dec_us = session->t_decode_us;
} else {
return TRANSCRIBE_ERR_INVALID_ARG;
}
const uint64_t caller_size = out->struct_size;
transcribe_timings staged{};
staged.struct_size = caller_size;
if (session->model != nullptr) {
staged.load_ms = static_cast<float>(session->model->t_load_us) / 1000.0f;
}
staged.mel_ms = static_cast<float>(mel_us) / 1000.0f;
staged.encode_ms = static_cast<float>(enc_us) / 1000.0f;
staged.decode_ms = static_cast<float>(dec_us) / 1000.0f;
copy_out_prefix(out, &staged, caller_size, sizeof(staged));
return TRANSCRIBE_OK;
}
extern "C" transcribe_status transcribe_init_backends(const char * artifact_dir) {
return api_guard_status("transcribe_init_backends", [&] { return transcribe_init_backends_impl(artifact_dir); });
}
extern "C" transcribe_status transcribe_init_backends_default(void) {
return api_guard_status("transcribe_init_backends_default",
[&] { return transcribe_init_backends_default_impl(); });
}
extern "C" int transcribe_device_count(void) {
return api_guard_value("transcribe_device_count", 0, [&] { return transcribe_device_count_impl(); });
}
extern "C" transcribe_device_t transcribe_device_get(int index) {
return api_guard_value("transcribe_device_get", static_cast<transcribe_device_t>(nullptr),
[&] { return transcribe_device_get_impl(index); });
}
extern "C" transcribe_status transcribe_device_get_info(transcribe_device_t device,
struct transcribe_device_info * out) {
return api_guard_status("transcribe_device_get_info", [&] { return transcribe_device_get_info_impl(device, out); });
}
extern "C" bool transcribe_backend_available(transcribe_backend_request kind) {
const int raw = enum_field_raw(&kind);
return api_guard_value("transcribe_backend_available", false,
[&] { return transcribe_backend_available_impl(raw); });
}
extern "C" transcribe_status transcribe_model_load_file(const char * path,
const struct transcribe_model_load_params * params,
struct transcribe_model ** out_model) {
const transcribe_status st = api_guard_status(
"transcribe_model_load_file", [&] { return transcribe_model_load_file_impl(path, params, out_model); });
if (st != TRANSCRIBE_OK && out_model != nullptr && *out_model != nullptr) {
transcribe_model_free(*out_model);
*out_model = nullptr;
}
return st;
}
extern "C" void transcribe_model_free(struct transcribe_model * model) {
api_guard_void("transcribe_model_free", [&] { transcribe_model_free_impl(model); });
}
extern "C" transcribe_status transcribe_session_init(struct transcribe_model * model,
const struct transcribe_session_params * params,
struct transcribe_session ** out_session) {
const transcribe_status st = api_guard_status(
"transcribe_session_init", [&] { return transcribe_session_init_impl(model, params, out_session); });
if (st != TRANSCRIBE_OK && out_session != nullptr && *out_session != nullptr) {
transcribe_session_free(*out_session);
*out_session = nullptr;
}
return st;
}
extern "C" void transcribe_session_free(struct transcribe_session * session) {
api_guard_void("transcribe_session_free", [&] { transcribe_session_free_impl(session); });
}
extern "C" transcribe_status transcribe_open(const char * path,
const struct transcribe_model_load_params * load_params,
const struct transcribe_session_params * session_params,
struct transcribe_session ** out_session) {
const transcribe_status st = api_guard_status(
"transcribe_open", [&] { return transcribe_open_impl(path, load_params, session_params, out_session); });
if (st != TRANSCRIBE_OK && out_session != nullptr && *out_session != nullptr) {
transcribe_session_free(*out_session);
*out_session = nullptr;
}
return st;
}
extern "C" transcribe_status transcribe_run(struct transcribe_session * session,
const float * pcm,
int n_samples,
const struct transcribe_run_params * params) {
return api_guard_status("transcribe_run", [&] { return transcribe_run_impl(session, pcm, n_samples, params); });
}
extern "C" transcribe_status transcribe_run_batch(struct transcribe_session * session,
const float * const * pcm,
const int * n_samples,
int n,
const struct transcribe_run_params * params) {
return api_guard_status("transcribe_run_batch",
[&] { return transcribe_run_batch_impl(session, pcm, n_samples, n, params); });
}
extern "C" transcribe_status transcribe_stream_begin(struct transcribe_session * session,
const struct transcribe_run_params * run_params,
const struct transcribe_stream_params * stream_params) {
return api_guard_status("transcribe_stream_begin",
[&] { return transcribe_stream_begin_impl(session, run_params, stream_params); });
}
extern "C" transcribe_status transcribe_stream_feed(struct transcribe_session * session,
const float * pcm,
int n_samples,
struct transcribe_stream_update * update) {
return api_guard_status("transcribe_stream_feed",
[&] { return transcribe_stream_feed_impl(session, pcm, n_samples, update); });
}
extern "C" transcribe_status transcribe_stream_finalize(struct transcribe_session * session,
struct transcribe_stream_update * update) {
return api_guard_status("transcribe_stream_finalize",
[&] { return transcribe_stream_finalize_impl(session, update); });
}
extern "C" void transcribe_stream_reset(struct transcribe_session * session) {
api_guard_void("transcribe_stream_reset", [&] { transcribe_stream_reset_impl(session); });
}
extern "C" transcribe_device_t transcribe_model_device(const struct transcribe_model * model) {
return api_guard_value("transcribe_model_device", static_cast<transcribe_device_t>(nullptr),
[&] { return transcribe_model_device_impl(model); });
}
extern "C" int transcribe_tokenize(const struct transcribe_model * model,
const char * text,
int32_t * tokens,
size_t n_max) {
return api_guard_value("transcribe_tokenize", INT_MIN,
[&] { return transcribe_tokenize_impl(model, text, tokens, n_max); });
}