fix(installer): repair pywhispercpp library loading (#433)
This commit is contained in:
+192
-6
@@ -1898,9 +1898,87 @@ EOF
|
||||
chmod +x "$ACTIVATION_SCRIPT"
|
||||
print_info "Created activation script: $ACTIVATION_SCRIPT"
|
||||
|
||||
get_pywhispercpp_library_path() {
|
||||
[ -x "$VENV_DIR/bin/python" ] || return 1
|
||||
"$VENV_DIR/bin/python" - <<'PY' 2>/dev/null
|
||||
from pathlib import Path
|
||||
import site
|
||||
import sys
|
||||
import sysconfig
|
||||
|
||||
roots = []
|
||||
for attr in ("getsitepackages",):
|
||||
get_paths = getattr(site, attr, None)
|
||||
if get_paths is None:
|
||||
continue
|
||||
try:
|
||||
roots.extend(get_paths())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
user_site = getattr(site, "getusersitepackages", lambda: None)()
|
||||
if user_site:
|
||||
roots.append(user_site)
|
||||
|
||||
for key in ("platlib", "purelib"):
|
||||
path = sysconfig.get_paths().get(key)
|
||||
if path:
|
||||
roots.append(path)
|
||||
|
||||
roots.extend(path for path in sys.path if path)
|
||||
|
||||
dirs = []
|
||||
seen = set()
|
||||
for root in roots:
|
||||
root_path = Path(root)
|
||||
candidates = [
|
||||
root_path / "pywhispercpp.libs",
|
||||
root_path / "pywhispercpp" / ".libs",
|
||||
root_path / "pywhispercpp" / "lib",
|
||||
root_path,
|
||||
]
|
||||
for candidate in candidates:
|
||||
try:
|
||||
resolved = str(candidate.resolve())
|
||||
except OSError:
|
||||
continue
|
||||
if resolved in seen or not candidate.is_dir():
|
||||
continue
|
||||
if any(candidate.glob("libwhisper*.so*")) or any(candidate.glob("libggml*.so*")):
|
||||
seen.add(resolved)
|
||||
dirs.append(resolved)
|
||||
|
||||
print(":".join(dirs))
|
||||
PY
|
||||
}
|
||||
|
||||
with_pywhispercpp_library_path() {
|
||||
local PYWHISPERCPP_LIBRARY_PATH
|
||||
PYWHISPERCPP_LIBRARY_PATH=$(get_pywhispercpp_library_path || true)
|
||||
|
||||
if [ -n "$PYWHISPERCPP_LIBRARY_PATH" ]; then
|
||||
LD_LIBRARY_PATH="$PYWHISPERCPP_LIBRARY_PATH${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" "$@"
|
||||
else
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
|
||||
get_pywhispercpp_cmake_args() {
|
||||
printf '%s\n' '-DCMAKE_INSTALL_RPATH=$ORIGIN -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON'
|
||||
}
|
||||
|
||||
install_cpu_pywhispercpp() {
|
||||
local PIP_LOG_FILE="$1"
|
||||
local PYWHISPERCPP_CMAKE_ARGS
|
||||
PYWHISPERCPP_CMAKE_ARGS=$(get_pywhispercpp_cmake_args)
|
||||
|
||||
CMAKE_ARGS="${CMAKE_ARGS:+$CMAKE_ARGS }$PYWHISPERCPP_CMAKE_ARGS" \
|
||||
pip install --force-reinstall --no-cache-dir pywhispercpp --log "$PIP_LOG_FILE"
|
||||
}
|
||||
|
||||
is_pywhispercpp_installed() {
|
||||
[ -x "$VENV_DIR/bin/python" ] || return 1
|
||||
"$VENV_DIR/bin/python" -c "from pywhispercpp.model import Model" >/dev/null 2>&1
|
||||
with_pywhispercpp_library_path "$VENV_DIR/bin/python" -c "from pywhispercpp.model import Model" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
get_pywhispercpp_version() {
|
||||
@@ -2030,6 +2108,8 @@ install_python_package() {
|
||||
local GPU_BACKEND="CPU"
|
||||
local GPU_INSTALL_SUCCESS=false
|
||||
local SKIP_WHISPERCPP_INSTALL=false
|
||||
local PYWHISPERCPP_CMAKE_ARGS
|
||||
PYWHISPERCPP_CMAKE_ARGS=$(get_pywhispercpp_cmake_args)
|
||||
|
||||
if [[ "$WHISPERCPP_ALREADY_INSTALLED" == "true" ]]; then
|
||||
GPU_BACKEND="existing"
|
||||
@@ -2053,7 +2133,7 @@ install_python_package() {
|
||||
print_info " Installing pywhispercpp with Vulkan support..."
|
||||
GPU_BACKEND="Vulkan"
|
||||
print_info "Installing pywhispercpp ($GPU_BACKEND backend)..."
|
||||
if GGML_VULKAN=1 pip install --force-reinstall --no-cache-dir git+https://github.com/absadiki/pywhispercpp --log "$PIP_LOG_FILE" 2>&1; then
|
||||
if CMAKE_ARGS="${CMAKE_ARGS:+$CMAKE_ARGS }$PYWHISPERCPP_CMAKE_ARGS" GGML_VULKAN=1 pip install --force-reinstall --no-cache-dir git+https://github.com/absadiki/pywhispercpp --log "$PIP_LOG_FILE" 2>&1; then
|
||||
GPU_INSTALL_SUCCESS=true
|
||||
else
|
||||
print_warning "Vulkan build failed - checking for NVIDIA GPU to try CUDA..."
|
||||
@@ -2066,7 +2146,7 @@ install_python_package() {
|
||||
print_info " Installing pywhispercpp with CUDA support..."
|
||||
GPU_BACKEND="CUDA"
|
||||
print_info "Installing pywhispercpp ($GPU_BACKEND backend)..."
|
||||
if GGML_CUDA=1 pip install --force-reinstall --no-cache-dir git+https://github.com/absadiki/pywhispercpp --log "$PIP_LOG_FILE" 2>&1; then
|
||||
if CMAKE_ARGS="${CMAKE_ARGS:+$CMAKE_ARGS }$PYWHISPERCPP_CMAKE_ARGS" GGML_CUDA=1 pip install --force-reinstall --no-cache-dir git+https://github.com/absadiki/pywhispercpp --log "$PIP_LOG_FILE" 2>&1; then
|
||||
GPU_INSTALL_SUCCESS=true
|
||||
fi
|
||||
fi
|
||||
@@ -2093,7 +2173,7 @@ install_python_package() {
|
||||
fi
|
||||
GPU_BACKEND="CPU"
|
||||
print_info "Installing pywhispercpp ($GPU_BACKEND backend)..."
|
||||
pip install pywhispercpp --log "$PIP_LOG_FILE" || {
|
||||
install_cpu_pywhispercpp "$PIP_LOG_FILE" || {
|
||||
print_error "Failed to install pywhispercpp"
|
||||
return 1
|
||||
}
|
||||
@@ -2104,6 +2184,60 @@ install_python_package() {
|
||||
else
|
||||
print_success "pywhispercpp installed with $GPU_BACKEND backend"
|
||||
fi
|
||||
|
||||
if ! is_pywhispercpp_installed; then
|
||||
print_warning "pywhispercpp installed but import verification failed."
|
||||
print_warning "This can happen when libwhisper.so is installed beside the Python extension without a runtime library path."
|
||||
|
||||
if [[ "$SKIP_WHISPERCPP_INSTALL" != "true" ]]; then
|
||||
print_warning "Trying RPATH-aware CPU pywhispercpp fallback..."
|
||||
GPU_BACKEND="CPU"
|
||||
if install_cpu_pywhispercpp "$PIP_LOG_FILE"; then
|
||||
print_success "CPU pywhispercpp fallback installed"
|
||||
else
|
||||
print_warning "CPU pywhispercpp fallback installation failed"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! is_pywhispercpp_installed; then
|
||||
print_warning "pywhispercpp is still unavailable; setting VOSK as the default engine so Vocalinux can start."
|
||||
print_warning "You can switch back to whisper.cpp from Settings after reinstalling pywhispercpp."
|
||||
SELECTED_ENGINE="vosk"
|
||||
|
||||
local FALLBACK_VOSK_CONFIG="$CONFIG_DIR/config.json"
|
||||
if [ ! -f "$FALLBACK_VOSK_CONFIG" ]; then
|
||||
mkdir -p "$CONFIG_DIR"
|
||||
cat > "$FALLBACK_VOSK_CONFIG" << 'FALLBACK_VOSK_CONFIG'
|
||||
{
|
||||
"speech_recognition": {
|
||||
"engine": "vosk",
|
||||
"model_size": "small",
|
||||
"vosk_model_size": "small",
|
||||
"whisper_model_size": "tiny",
|
||||
"whisper_cpp_model_size": "tiny",
|
||||
"vad_sensitivity": 3,
|
||||
"silence_timeout": 2.0
|
||||
},
|
||||
"audio": {
|
||||
"device_index": null,
|
||||
"device_name": null
|
||||
},
|
||||
"shortcuts": {
|
||||
"toggle_recognition": "ctrl+ctrl"
|
||||
},
|
||||
"ui": {
|
||||
"start_minimized": false,
|
||||
"show_notifications": true
|
||||
},
|
||||
"advanced": {
|
||||
"debug_logging": false,
|
||||
"wayland_mode": false
|
||||
}
|
||||
}
|
||||
FALLBACK_VOSK_CONFIG
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
;;
|
||||
|
||||
@@ -2181,7 +2315,7 @@ WHISPER_CONFIG
|
||||
print_info ""
|
||||
|
||||
# Fall back to whisper.cpp installation
|
||||
pip install pywhispercpp --log "$PIP_LOG_FILE" || {
|
||||
install_cpu_pywhispercpp "$PIP_LOG_FILE" || {
|
||||
print_error "Failed to install pywhispercpp fallback"
|
||||
print_error "Please try installing manually: pip install pywhispercpp"
|
||||
return 1
|
||||
@@ -2284,6 +2418,32 @@ VOSK_CONFIG
|
||||
# Wrapper script for Vocalinux that sets required environment variables
|
||||
# and applies the 'input' group for keyboard shortcuts on Wayland
|
||||
export GI_TYPELIB_PATH=$GI_TYPELIB_DETECTED
|
||||
PYWHISPERCPP_LIBRARY_PATH=""
|
||||
PY_SITE_PATHS=\$("$VENV_DIR/bin/python" - <<'PY' 2>/dev/null
|
||||
import sysconfig
|
||||
|
||||
paths = []
|
||||
for key in ("platlib", "purelib"):
|
||||
path = sysconfig.get_paths().get(key)
|
||||
if path and path not in paths:
|
||||
paths.append(path)
|
||||
print(" ".join(paths))
|
||||
PY
|
||||
)
|
||||
for PY_SITE in \$PY_SITE_PATHS; do
|
||||
for PY_LIB_DIR in "\$PY_SITE/pywhispercpp.libs" "\$PY_SITE/pywhispercpp/.libs" "\$PY_SITE/pywhispercpp/lib"; do
|
||||
if [ -d "\$PY_LIB_DIR" ] && { ls "\$PY_LIB_DIR"/libwhisper*.so* >/dev/null 2>&1 || ls "\$PY_LIB_DIR"/libggml*.so* >/dev/null 2>&1; }; then
|
||||
if [ -z "\$PYWHISPERCPP_LIBRARY_PATH" ]; then
|
||||
PYWHISPERCPP_LIBRARY_PATH="\$PY_LIB_DIR"
|
||||
else
|
||||
PYWHISPERCPP_LIBRARY_PATH="\$PYWHISPERCPP_LIBRARY_PATH:\$PY_LIB_DIR"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
done
|
||||
if [ -n "\$PYWHISPERCPP_LIBRARY_PATH" ]; then
|
||||
export LD_LIBRARY_PATH="\$PYWHISPERCPP_LIBRARY_PATH\${LD_LIBRARY_PATH:+:\$LD_LIBRARY_PATH}"
|
||||
fi
|
||||
|
||||
# Check if user is in input group but current session doesn't have it
|
||||
if grep -q "^input:.*\b\$(whoami)\b" /etc/group 2>/dev/null && ! groups | grep -q '\binput\b'; then
|
||||
@@ -2302,6 +2462,32 @@ WRAPPER_EOF
|
||||
# Wrapper script for Vocalinux GUI that sets required environment variables
|
||||
# and applies the 'input' group for keyboard shortcuts on Wayland
|
||||
export GI_TYPELIB_PATH=$GI_TYPELIB_DETECTED
|
||||
PYWHISPERCPP_LIBRARY_PATH=""
|
||||
PY_SITE_PATHS=\$("$VENV_DIR/bin/python" - <<'PY' 2>/dev/null
|
||||
import sysconfig
|
||||
|
||||
paths = []
|
||||
for key in ("platlib", "purelib"):
|
||||
path = sysconfig.get_paths().get(key)
|
||||
if path and path not in paths:
|
||||
paths.append(path)
|
||||
print(" ".join(paths))
|
||||
PY
|
||||
)
|
||||
for PY_SITE in \$PY_SITE_PATHS; do
|
||||
for PY_LIB_DIR in "\$PY_SITE/pywhispercpp.libs" "\$PY_SITE/pywhispercpp/.libs" "\$PY_SITE/pywhispercpp/lib"; do
|
||||
if [ -d "\$PY_LIB_DIR" ] && { ls "\$PY_LIB_DIR"/libwhisper*.so* >/dev/null 2>&1 || ls "\$PY_LIB_DIR"/libggml*.so* >/dev/null 2>&1; }; then
|
||||
if [ -z "\$PYWHISPERCPP_LIBRARY_PATH" ]; then
|
||||
PYWHISPERCPP_LIBRARY_PATH="\$PY_LIB_DIR"
|
||||
else
|
||||
PYWHISPERCPP_LIBRARY_PATH="\$PYWHISPERCPP_LIBRARY_PATH:\$PY_LIB_DIR"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
done
|
||||
if [ -n "\$PYWHISPERCPP_LIBRARY_PATH" ]; then
|
||||
export LD_LIBRARY_PATH="\$PYWHISPERCPP_LIBRARY_PATH\${LD_LIBRARY_PATH:+:\$LD_LIBRARY_PATH}"
|
||||
fi
|
||||
|
||||
# Check if user is in input group but current session doesn't have it
|
||||
if grep -q "^input:.*\b\$(whoami)\b" /etc/group 2>/dev/null && ! groups | grep -q '\binput\b'; then
|
||||
@@ -2760,7 +2946,7 @@ if [ "$SKIP_MODELS" = "no" ]; then
|
||||
# Check which engines are installed and download appropriate models
|
||||
|
||||
# Install whisper.cpp model (default engine)
|
||||
if "$VENV_DIR/bin/python" -c "from pywhispercpp.model import Model" 2>/dev/null; then
|
||||
if is_pywhispercpp_installed; then
|
||||
print_info "whisper.cpp is installed - downloading tiny model (default engine)..."
|
||||
install_whispercpp_model || print_warning "whisper.cpp model download failed - model will be downloaded on first run"
|
||||
fi
|
||||
|
||||
@@ -6,6 +6,7 @@ currently supporting VOSK, Whisper, and whisper.cpp.
|
||||
"""
|
||||
|
||||
import ctypes
|
||||
import importlib.util
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
@@ -13,6 +14,7 @@ import queue
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Callable, Optional
|
||||
|
||||
from ..common_types import RecognitionState
|
||||
@@ -61,6 +63,119 @@ def _setup_alsa_error_handler():
|
||||
# Set up ALSA error handler at module load time
|
||||
_alsa_handler = _setup_alsa_error_handler()
|
||||
|
||||
_PYWHISPERCPP_PRELOADED_LIBS: list[ctypes.CDLL] = []
|
||||
|
||||
|
||||
def _find_pywhispercpp_shared_library_dirs() -> list[str]:
|
||||
"""Find bundled pywhispercpp native library directories without importing it."""
|
||||
candidate_dirs: list[Path] = []
|
||||
|
||||
for module_name in ("_pywhispercpp", "pywhispercpp"):
|
||||
try:
|
||||
spec = importlib.util.find_spec(module_name)
|
||||
except (ImportError, AttributeError, ValueError):
|
||||
spec = None
|
||||
if spec is None:
|
||||
continue
|
||||
|
||||
if spec.origin:
|
||||
module_dir = Path(spec.origin).resolve().parent
|
||||
candidate_dirs.extend(
|
||||
[
|
||||
module_dir,
|
||||
module_dir / ".libs",
|
||||
module_dir / "lib",
|
||||
module_dir / "pywhispercpp.libs",
|
||||
module_dir.parent / "pywhispercpp.libs",
|
||||
]
|
||||
)
|
||||
|
||||
if spec.submodule_search_locations:
|
||||
for location in spec.submodule_search_locations:
|
||||
package_dir = Path(location).resolve()
|
||||
candidate_dirs.extend(
|
||||
[
|
||||
package_dir,
|
||||
package_dir / ".libs",
|
||||
package_dir / "lib",
|
||||
package_dir.parent / "pywhispercpp.libs",
|
||||
]
|
||||
)
|
||||
|
||||
for path_entry in sys.path:
|
||||
if not path_entry:
|
||||
continue
|
||||
path_root = Path(path_entry).resolve()
|
||||
candidate_dirs.append(path_root / "pywhispercpp.libs")
|
||||
|
||||
library_dirs: list[str] = []
|
||||
seen: set[str] = set()
|
||||
for candidate_dir in candidate_dirs:
|
||||
try:
|
||||
resolved_dir = str(candidate_dir.resolve())
|
||||
except OSError:
|
||||
continue
|
||||
|
||||
if resolved_dir in seen or not candidate_dir.is_dir():
|
||||
continue
|
||||
|
||||
has_native_lib = any(candidate_dir.glob("libwhisper*.so*")) or any(
|
||||
candidate_dir.glob("libggml*.so*")
|
||||
)
|
||||
if has_native_lib:
|
||||
seen.add(resolved_dir)
|
||||
library_dirs.append(resolved_dir)
|
||||
|
||||
return library_dirs
|
||||
|
||||
|
||||
def _preload_pywhispercpp_shared_libraries() -> None:
|
||||
"""Preload bundled pywhispercpp shared libraries for source-built installs.
|
||||
|
||||
Some source builds place libwhisper/libggml next to the Python extension
|
||||
without an RPATH. Preloading by absolute path lets the dynamic loader satisfy
|
||||
the extension's libwhisper.so.1 dependency before importing pywhispercpp.
|
||||
"""
|
||||
if _PYWHISPERCPP_PRELOADED_LIBS:
|
||||
return
|
||||
|
||||
libraries: list[Path] = []
|
||||
for library_dir in _find_pywhispercpp_shared_library_dirs():
|
||||
root = Path(library_dir)
|
||||
libraries.extend(sorted(root.glob("libggml*.so*")))
|
||||
libraries.extend(sorted(root.glob("libwhisper*.so*")))
|
||||
|
||||
if not libraries:
|
||||
return
|
||||
|
||||
pending = list(dict.fromkeys(libraries))
|
||||
loaded: list[ctypes.CDLL] = []
|
||||
last_errors: dict[str, OSError] = {}
|
||||
mode = getattr(ctypes, "RTLD_GLOBAL", 0)
|
||||
|
||||
# Native libs can depend on each other. Retry while progress is made so a
|
||||
# dependency loaded earlier in the same directory can unlock later libraries.
|
||||
while pending:
|
||||
loaded_this_pass = False
|
||||
for library_path in pending[:]:
|
||||
try:
|
||||
loaded.append(ctypes.CDLL(str(library_path), mode=mode))
|
||||
pending.remove(library_path)
|
||||
loaded_this_pass = True
|
||||
except OSError as e:
|
||||
last_errors[str(library_path)] = e
|
||||
|
||||
if not loaded_this_pass:
|
||||
break
|
||||
|
||||
_PYWHISPERCPP_PRELOADED_LIBS.extend(loaded)
|
||||
|
||||
if pending:
|
||||
logger.debug(
|
||||
"Could not preload all pywhispercpp native libraries: %s",
|
||||
{str(path): str(last_errors.get(str(path))) for path in pending},
|
||||
)
|
||||
|
||||
|
||||
def get_audio_input_devices() -> list:
|
||||
"""
|
||||
@@ -804,6 +919,7 @@ class SpeechRecognitionManager:
|
||||
def _init_whispercpp(self):
|
||||
"""Initialize the whisper.cpp speech recognition engine."""
|
||||
try:
|
||||
_preload_pywhispercpp_shared_libraries()
|
||||
from pywhispercpp.model import Model # noqa: F401 — used in _load_whispercpp_model
|
||||
|
||||
# Validate model size for whisper.cpp
|
||||
@@ -864,6 +980,7 @@ class SpeechRecognitionManager:
|
||||
def _get_supported_whispercpp_params(self) -> Optional[set[str]]:
|
||||
"""Return params supported by the active pywhispercpp native binding."""
|
||||
try:
|
||||
_preload_pywhispercpp_shared_libraries()
|
||||
import _pywhispercpp as pw
|
||||
|
||||
params = pw.whisper_full_default_params(
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
from importlib.machinery import EXTENSION_SUFFIXES
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
@@ -33,10 +34,13 @@ if "gi" not in sys.modules:
|
||||
if "gi.repository" not in sys.modules:
|
||||
sys.modules["gi.repository"] = MagicMock()
|
||||
|
||||
from vocalinux.speech_recognition import recognition_manager as rm
|
||||
from vocalinux.speech_recognition.recognition_manager import (
|
||||
SpeechRecognitionManager,
|
||||
_filter_non_speech,
|
||||
_find_pywhispercpp_shared_library_dirs,
|
||||
_get_system_model_paths,
|
||||
_preload_pywhispercpp_shared_libraries,
|
||||
_show_notification,
|
||||
)
|
||||
from vocalinux.speech_recognition.recognition_manager import ( # noqa: E402
|
||||
@@ -140,6 +144,55 @@ class TestGetSystemModelPaths(unittest.TestCase):
|
||||
self.assertIsInstance(paths, list)
|
||||
|
||||
|
||||
class TestPywhispercppLibraryHelpers:
|
||||
"""Test pywhispercpp native library discovery and preloading."""
|
||||
|
||||
def test_find_pywhispercpp_shared_library_dirs(self, tmp_path, monkeypatch):
|
||||
site_packages = tmp_path / "site-packages"
|
||||
package_dir = site_packages / "pywhispercpp"
|
||||
libs_dir = site_packages / "pywhispercpp.libs"
|
||||
package_dir.mkdir(parents=True)
|
||||
libs_dir.mkdir()
|
||||
(package_dir / "__init__.py").write_text("")
|
||||
(site_packages / f"_pywhispercpp{EXTENSION_SUFFIXES[0]}").write_text("")
|
||||
(libs_dir / "libwhisper.so.1").write_text("")
|
||||
|
||||
monkeypatch.syspath_prepend(str(site_packages))
|
||||
sys.modules.pop("pywhispercpp", None)
|
||||
sys.modules.pop("_pywhispercpp", None)
|
||||
|
||||
library_dirs = _find_pywhispercpp_shared_library_dirs()
|
||||
|
||||
assert str(libs_dir.resolve()) in library_dirs
|
||||
|
||||
def test_preload_pywhispercpp_shared_libraries(self, tmp_path, monkeypatch):
|
||||
libs_dir = tmp_path / "pywhispercpp.libs"
|
||||
libs_dir.mkdir()
|
||||
ggml_lib = libs_dir / "libggml.so"
|
||||
whisper_lib = libs_dir / "libwhisper.so.1"
|
||||
ggml_lib.write_text("")
|
||||
whisper_lib.write_text("")
|
||||
|
||||
loaded_handles = []
|
||||
|
||||
def fake_cdll(path, mode=0):
|
||||
handle = MagicMock(path=path, mode=mode)
|
||||
loaded_handles.append(handle)
|
||||
return handle
|
||||
|
||||
monkeypatch.setattr(rm, "_PYWHISPERCPP_PRELOADED_LIBS", [])
|
||||
monkeypatch.setattr(rm, "_find_pywhispercpp_shared_library_dirs", lambda: [str(libs_dir)])
|
||||
monkeypatch.setattr(rm.ctypes, "CDLL", fake_cdll)
|
||||
|
||||
_preload_pywhispercpp_shared_libraries()
|
||||
|
||||
assert [handle.path for handle in loaded_handles] == [
|
||||
str(ggml_lib),
|
||||
str(whisper_lib),
|
||||
]
|
||||
assert rm._PYWHISPERCPP_PRELOADED_LIBS == loaded_handles
|
||||
|
||||
|
||||
class TestTestAudioInput(unittest.TestCase):
|
||||
"""Test test_audio_input function."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user