mirror of
https://github.com/chidiwilliams/buzz.git
synced 2026-03-14 14:45:46 +01:00
Adding support for Vulkan for whisper.cpp (#1194)
This commit is contained in:
parent
8fa065eb95
commit
93afbead37
50 changed files with 1161 additions and 382 deletions
|
|
@ -1,6 +1,7 @@
|
|||
[run]
|
||||
omit =
|
||||
buzz/whisper_cpp.py
|
||||
buzz/whisper_cpp/*
|
||||
buzz/whisper_cpp_vulkan/*
|
||||
*_test.py
|
||||
demucs/*
|
||||
|
||||
|
|
|
|||
37
.github/workflows/ci.yml
vendored
37
.github/workflows/ci.yml
vendored
|
|
@ -70,8 +70,9 @@ jobs:
|
|||
poetry add torch==2.2.2 torchaudio==2.2.2
|
||||
if: "matrix.os == 'macos-13'"
|
||||
|
||||
- name: Install dependencies
|
||||
run: poetry install
|
||||
- name: Add msbuild to PATH
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
if: runner.os == 'Windows'
|
||||
|
||||
- name: Install apt dependencies
|
||||
run: |
|
||||
|
|
@ -82,6 +83,15 @@ jobs:
|
|||
sudo apt-get install libyaml-dev libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-shape0 libxcb-cursor0 libportaudio2 gettext libpulse0 libgl1-mesa-dev
|
||||
if: "startsWith(matrix.os, 'ubuntu-')"
|
||||
|
||||
- name: Install Vulkan SDK
|
||||
uses: humbletim/install-vulkan-sdk@v1.2
|
||||
with:
|
||||
version: 1.4.309.0
|
||||
cache: true
|
||||
|
||||
- name: Install dependencies
|
||||
run: poetry install
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
poetry run make test
|
||||
|
|
@ -132,6 +142,12 @@ jobs:
|
|||
- uses: AnimMouse/setup-ffmpeg@v1.2.1
|
||||
id: setup-ffmpeg
|
||||
|
||||
- name: Install Vulkan SDK
|
||||
uses: humbletim/install-vulkan-sdk@v1.2
|
||||
with:
|
||||
version: 1.4.309.0
|
||||
cache: true
|
||||
|
||||
# Adding windows Nvidia CUDA torch and its dependencies
|
||||
# Linux only packages were removed from the list.
|
||||
- name: Install CUDA torch for Windows
|
||||
|
|
@ -153,6 +169,10 @@ jobs:
|
|||
poetry add torch==2.2.2 torchaudio==2.2.2 ctranslate2==4.3.1
|
||||
if: "matrix.os == 'macos-13'"
|
||||
|
||||
- name: Add msbuild to PATH
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
if: runner.os == 'Windows'
|
||||
|
||||
- name: Install dependencies
|
||||
run: poetry install
|
||||
|
||||
|
|
@ -255,19 +275,12 @@ jobs:
|
|||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Copy Windows DLLs
|
||||
run: |
|
||||
if [ "$RUNNER_OS" == "Windows" ]; then
|
||||
cp -r ./dll_backup ./buzz/
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- name: Build wheels
|
||||
uses: pypa/cibuildwheel@v2.22.0
|
||||
env:
|
||||
CIBW_ARCHS_WINDOWS: "auto"
|
||||
CIBW_ARCHS_MACOS: "universal2"
|
||||
CIBW_ARCHS_LINUX: "auto"
|
||||
CIBW_ARCHS_WINDOWS: "auto64"
|
||||
CIBW_ARCHS_MACOS: "auto64"
|
||||
CIBW_ARCHS_LINUX: "auto64"
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
|
|
|||
2
.github/workflows/snapcraft.yml
vendored
2
.github/workflows/snapcraft.yml
vendored
|
|
@ -21,7 +21,7 @@ jobs:
|
|||
- name: Maximize build space
|
||||
uses: easimon/maximize-build-space@master
|
||||
with:
|
||||
root-reserve-mb: 20000
|
||||
root-reserve-mb: 25000
|
||||
swap-size-mb: 1024
|
||||
remove-dotnet: 'true'
|
||||
remove-android: 'true'
|
||||
|
|
|
|||
12
.gitignore
vendored
12
.gitignore
vendored
|
|
@ -13,13 +13,13 @@ coverage.xml
|
|||
venv/
|
||||
|
||||
# whisper_cpp
|
||||
libwhisper.*
|
||||
libwhisper-coreml.*
|
||||
whisper_cpp
|
||||
whisper_cpp.exe
|
||||
whisper.dll
|
||||
buzz/whisper_cpp.py
|
||||
buzz/whisper_cpp_coreml.py
|
||||
*.exe
|
||||
*.dll
|
||||
*.dylib
|
||||
*.so
|
||||
buzz/whisper_cpp/*
|
||||
buzz/whisper_cpp_vulkan/*
|
||||
|
||||
# Internationalization - compiled binaries
|
||||
*.mo
|
||||
|
|
|
|||
51
Buzz.spec
51
Buzz.spec
|
|
@ -1,4 +1,5 @@
|
|||
# -*- mode: python ; coding: utf-8 -*-
|
||||
import os
|
||||
import os.path
|
||||
import platform
|
||||
import shutil
|
||||
|
|
@ -43,26 +44,50 @@ if DEBUG:
|
|||
else:
|
||||
options = []
|
||||
|
||||
binaries = [
|
||||
(
|
||||
"buzz/whisper.dll" if platform.system() == "Windows" else "buzz/libwhisper.*",
|
||||
".",
|
||||
),
|
||||
(shutil.which("ffmpeg"), "."),
|
||||
(shutil.which("ffprobe"), "."),
|
||||
]
|
||||
def find_dependency(name: str) -> str:
|
||||
paths = os.environ["PATH"].split(os.pathsep)
|
||||
candidates = []
|
||||
for path in paths:
|
||||
exe_path = os.path.join(path, name)
|
||||
if os.path.isfile(exe_path):
|
||||
candidates.append(exe_path)
|
||||
|
||||
# Include libwhisper-coreml.dylib on Apple Silicon
|
||||
if platform.system() == "Darwin" and platform.machine() == "arm64":
|
||||
binaries.append(("buzz/libwhisper-coreml.dylib", "."))
|
||||
# Check for chocolatery shims
|
||||
shim_path = os.path.normpath(os.path.join(path, "..", "lib", "ffmpeg", "tools", "ffmpeg", "bin", name))
|
||||
if os.path.isfile(shim_path):
|
||||
candidates.append(shim_path)
|
||||
|
||||
if not candidates:
|
||||
return None
|
||||
|
||||
# Pick the largest file
|
||||
return max(candidates, key=lambda f: os.path.getsize(f))
|
||||
|
||||
if platform.system() == "Windows":
|
||||
binaries = [
|
||||
(find_dependency("ffmpeg.exe"), "."),
|
||||
(find_dependency("ffprobe.exe"), "."),
|
||||
]
|
||||
else:
|
||||
binaries = [
|
||||
(shutil.which("ffmpeg"), "."),
|
||||
(shutil.which("ffprobe"), "."),
|
||||
]
|
||||
|
||||
if platform.system() == "Linux":
|
||||
binaries.append(("buzz/whisper_cpp/*.so", "buzz/whisper_cpp"))
|
||||
binaries.append(("buzz/whisper_cpp_vulkan/*.so", "buzz/whisper_cpp_vulkan"))
|
||||
|
||||
if platform.system() == "Darwin":
|
||||
binaries.append(("buzz/whisper_cpp/*.dylib", "buzz/whisper_cpp"))
|
||||
|
||||
# Include dll_backup folder and its contents on Windows
|
||||
if platform.system() == "Windows":
|
||||
datas += [("dll_backup", "dll_backup")]
|
||||
datas += collect_data_files("msvc-runtime")
|
||||
|
||||
binaries.append(("dll_backup/SDL2.dll", "dll_backup"))
|
||||
binaries.append(("dll_backup/whisper.dll", "dll_backup"))
|
||||
binaries.append(("buzz/whisper_cpp/*.dll", "buzz/whisper_cpp"))
|
||||
binaries.append(("buzz/*.exe", "."))
|
||||
|
||||
a = Analysis(
|
||||
["main.py"],
|
||||
|
|
|
|||
149
Makefile
149
Makefile
|
|
@ -12,35 +12,23 @@ bundle_mac: dist/Buzz.app codesign_all_mac zip_mac notarize_zip staple_app_mac d
|
|||
|
||||
bundle_mac_unsigned: dist/Buzz.app zip_mac dmg_mac_unsigned
|
||||
|
||||
UNAME_S := $(shell uname -s)
|
||||
UNAME_M := $(shell uname -m)
|
||||
|
||||
LIBWHISPER :=
|
||||
ifeq ($(OS), Windows_NT)
|
||||
LIBWHISPER=whisper.dll
|
||||
else
|
||||
ifeq ($(UNAME_S), Darwin)
|
||||
LIBWHISPER=libwhisper.dylib
|
||||
else
|
||||
LIBWHISPER=libwhisper.so
|
||||
endif
|
||||
endif
|
||||
|
||||
clean:
|
||||
ifeq ($(OS), Windows_NT)
|
||||
-del /f buzz\$(LIBWHISPER) 2> nul
|
||||
-del /f buzz\whisper_cpp.py 2> nul
|
||||
-rmdir /s /q whisper.cpp\build 2> nul
|
||||
-rmdir /s /q dist 2> nul
|
||||
-rm -f buzz/$(LIBWHISPER)
|
||||
-rm -f buzz/whisper_cpp.py
|
||||
-rm -rf whisper.cpp/build || true
|
||||
-rm -rf dist/* || true
|
||||
-rmdir /s /q buzz\whisper_cpp
|
||||
-rmdir /s /q buzz\whisper-server.exe
|
||||
-rmdir /s /q whisper.cpp\build
|
||||
-rmdir /s /q dist
|
||||
-Remove-Item -Recurse -Force buzz\whisper_cpp
|
||||
-Remove-Item -Recurse -Force buzz\whisper-server.exe
|
||||
-Remove-Item -Recurse -Force whisper.cpp\build
|
||||
-Remove-Item -Recurse -Force dist\*
|
||||
-rm -rf buzz/whisper_cpp
|
||||
-rm -fr buzz/whisper-server.exe
|
||||
-rm -rf whisper.cpp/build
|
||||
-rm -rf dist/*
|
||||
else
|
||||
rm -f buzz/$(LIBWHISPER)
|
||||
rm -f buzz/whisper_cpp.py
|
||||
rm -f buzz/libwhisper-coreml.dylib || true
|
||||
rm -f buzz/whisper_cpp_coreml.py || true
|
||||
rm -rf buzz/whisper_cpp || true
|
||||
rm -fr buzz/whisper_cpp_vulkan || true
|
||||
rm -rf whisper.cpp/build || true
|
||||
rm -rf dist/* || true
|
||||
endif
|
||||
|
|
@ -60,53 +48,74 @@ version:
|
|||
poetry version ${version}
|
||||
echo "VERSION = \"${version}\"" > buzz/__version__.py
|
||||
|
||||
CMAKE_FLAGS=
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
AVX1_M := $(shell sysctl machdep.cpu.features)
|
||||
ifeq (,$(findstring AVX1.0,$(AVX1_M)))
|
||||
CMAKE_FLAGS += -DWHISPER_NO_AVX=ON
|
||||
endif
|
||||
ifeq (,$(findstring FMA,$(AVX1_M)))
|
||||
CMAKE_FLAGS += -DWHISPER_NO_FMA=ON
|
||||
endif
|
||||
AVX2_M := $(shell sysctl machdep.cpu.leaf7_features)
|
||||
ifeq (,$(findstring AVX2,$(AVX2_M)))
|
||||
CMAKE_FLAGS += -DWHISPER_NO_AVX2=ON
|
||||
endif
|
||||
CMAKE_FLAGS += -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64"
|
||||
else
|
||||
ifeq ($(OS), Windows_NT)
|
||||
CMAKE_FLAGS += -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release
|
||||
endif
|
||||
endif
|
||||
|
||||
buzz/$(LIBWHISPER):
|
||||
buzz/whisper_cpp:
|
||||
ifeq ($(OS), Windows_NT)
|
||||
cp dll_backup/whisper.dll buzz || copy dll_backup\whisper.dll buzz\whisper.dll
|
||||
cp dll_backup/SDL2.dll buzz || copy dll_backup\SDL2.dll buzz\SDL2.dll
|
||||
else
|
||||
cmake -S whisper.cpp -B whisper.cpp/build/ $(CMAKE_FLAGS)
|
||||
cmake --build whisper.cpp/build --verbose
|
||||
cp whisper.cpp/build/bin/Debug/$(LIBWHISPER) buzz || true
|
||||
cp whisper.cpp/build/$(LIBWHISPER) buzz || true
|
||||
endif
|
||||
# Build CoreML support on ARM Macs
|
||||
ifeq ($(shell uname -m), arm64)
|
||||
ifeq ($(shell uname -s), Darwin)
|
||||
rm -rf whisper.cpp/build || true
|
||||
cmake -S whisper.cpp -B whisper.cpp/build/ $(CMAKE_FLAGS) -DWHISPER_COREML=1
|
||||
cmake --build whisper.cpp/build --verbose
|
||||
cp whisper.cpp/build/bin/Debug/$(LIBWHISPER) buzz/libwhisper-coreml.dylib || true
|
||||
cp whisper.cpp/build/$(LIBWHISPER) buzz/libwhisper-coreml.dylib || true
|
||||
endif
|
||||
# Build Whisper for CPU
|
||||
# The _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR is needed to prevent mutex lock issues on Windows
|
||||
# https://github.com/actions/runner-images/issues/10004#issuecomment-2156109231
|
||||
# -DCMAKE_[C|CXX]_COMPILER_WORKS=TRUE is used to prevent issue in building test program that fails on CI
|
||||
cmake -S whisper.cpp -B whisper.cpp/build/ -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_RPATH='$$ORIGIN' -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON -DCMAKE_C_FLAGS="-D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR" -DCMAKE_CXX_FLAGS="-D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR" -DCMAKE_C_COMPILER_WORKS=TRUE -DCMAKE_CXX_COMPILER_WORKS=TRUE
|
||||
cmake --build whisper.cpp/build -j --config Release --verbose
|
||||
|
||||
-mkdir buzz/whisper_cpp
|
||||
cp dll_backup/SDL2.dll buzz/whisper_cpp
|
||||
cp whisper.cpp/build/bin/Release/whisper.dll buzz/whisper_cpp
|
||||
cp whisper.cpp/build/bin/Release/ggml.dll buzz/whisper_cpp
|
||||
cp whisper.cpp/build/bin/Release/ggml-base.dll buzz/whisper_cpp
|
||||
cp whisper.cpp/build/bin/Release/ggml-cpu.dll buzz/whisper_cpp
|
||||
|
||||
# Build Whisper with Vulkan support. On Windows whisper-server.exe wil lbe used as dll approach is unreliable,
|
||||
# it often does not see the GPU
|
||||
cmake -S whisper.cpp -B whisper.cpp/build/ -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_RPATH='$$ORIGIN' -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON -DCMAKE_C_FLAGS="-D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR" -DCMAKE_CXX_FLAGS="-D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR" -DCMAKE_C_COMPILER_WORKS=TRUE -DCMAKE_CXX_COMPILER_WORKS=TRUE -DGGML_VULKAN=1
|
||||
cmake --build whisper.cpp/build -j --config Release --verbose
|
||||
|
||||
cp whisper.cpp/build/bin/Release/whisper-server.exe buzz/
|
||||
endif
|
||||
|
||||
buzz/whisper_cpp.py: buzz/$(LIBWHISPER) translation_mo
|
||||
cd buzz && ctypesgen ../whisper.cpp/whisper.h -lwhisper -o whisper_cpp.py
|
||||
ifeq ($(shell uname -m), arm64)
|
||||
ifeq ($(shell uname -s), Darwin)
|
||||
cd buzz && ctypesgen ../whisper.cpp/whisper.h -lwhisper-coreml -o whisper_cpp_coreml.py
|
||||
ifeq ($(shell uname -s), Linux)
|
||||
# Build Whisper for CPU
|
||||
-rm -rf whisper.cpp/build || true
|
||||
-mkdir -p buzz/whisper_cpp
|
||||
cmake -S whisper.cpp -B whisper.cpp/build/ -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_RPATH='$$ORIGIN' -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON
|
||||
cmake --build whisper.cpp/build -j --config Release --verbose
|
||||
cp whisper.cpp/build/src/libwhisper.so buzz/whisper_cpp/libwhisper.so || true
|
||||
cp whisper.cpp/build/ggml/src/libggml.so buzz/whisper_cpp || true
|
||||
cp whisper.cpp/build/ggml/src/libggml-base.so buzz/whisper_cpp || true
|
||||
cp whisper.cpp/build/ggml/src/libggml-cpu.so buzz/whisper_cpp || true
|
||||
|
||||
# Build Whisper for Vulkan
|
||||
rm -rf whisper.cpp/build || true
|
||||
-mkdir -p buzz/whisper_cpp_vulkan
|
||||
cmake -S whisper.cpp -B whisper.cpp/build/ -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_RPATH='$$ORIGIN' -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON -DGGML_VULKAN=1
|
||||
cmake --build whisper.cpp/build -j --config Release --verbose
|
||||
cp whisper.cpp/build/src/libwhisper.so buzz/whisper_cpp_vulkan/whisper-vulkan.so || true
|
||||
cp whisper.cpp/build/ggml/src/libggml.so buzz/whisper_cpp_vulkan || true
|
||||
cp whisper.cpp/build/ggml/src/libggml-base.so buzz/whisper_cpp_vulkan || true
|
||||
cp whisper.cpp/build/ggml/src/libggml-cpu.so buzz/whisper_cpp_vulkan || true
|
||||
cp whisper.cpp/build/ggml/src/ggml-vulkan/libggml-vulkan.so buzz/whisper_cpp_vulkan || true
|
||||
endif
|
||||
|
||||
# Build on Macs
|
||||
ifeq ($(shell uname -s), Darwin)
|
||||
-rm -rf whisper.cpp/build || true
|
||||
-mkdir -p buzz/whisper_cpp
|
||||
|
||||
ifeq ($(shell uname -m), arm64)
|
||||
cmake -S whisper.cpp -B whisper.cpp/build/ -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DWHISPER_COREML=1
|
||||
else
|
||||
cmake -S whisper.cpp -B whisper.cpp/build/ -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON
|
||||
endif
|
||||
|
||||
cmake --build whisper.cpp/build -j --config Release --verbose
|
||||
cp whisper.cpp/build/src/libwhisper.dylib buzz/whisper_cpp/ || true
|
||||
cp whisper.cpp/build/ggml/src/libggml* buzz/whisper_cpp/ || true
|
||||
endif
|
||||
|
||||
buzz/whisper_cpp.py: buzz/whisper_cpp translation_mo
|
||||
cd buzz && ctypesgen ../whisper.cpp/include/whisper.h -I../whisper.cpp/ggml/include -lwhisper -o ./whisper_cpp/whisper_cpp.py
|
||||
|
||||
ifeq ($(shell uname -s), Linux)
|
||||
cd buzz && ctypesgen ../whisper.cpp/include/whisper.h -I../whisper.cpp/ggml/include -lwhisper-vulkan -o ./whisper_cpp_vulkan/whisper_cpp_vulkan.py
|
||||
endif
|
||||
|
||||
# Prints all the Mac developer identities used for code signing
|
||||
|
|
@ -238,7 +247,7 @@ ifeq ($(OS), Windows_NT)
|
|||
done
|
||||
else
|
||||
for dir in buzz/locale/*/ ; do \
|
||||
python msgfmt.py -o $$dir/LC_MESSAGES/buzz.mo $$dir/LC_MESSAGES/buzz.po; \
|
||||
python3 msgfmt.py -o $$dir/LC_MESSAGES/buzz.mo $$dir/LC_MESSAGES/buzz.po; \
|
||||
done
|
||||
endif
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,10 @@ os.environ["PATH"] += os.pathsep + APP_BASE_DIR
|
|||
# Add the app directory to the DLL list: https://stackoverflow.com/a/64303856
|
||||
if platform.system() == "Windows":
|
||||
os.add_dll_directory(APP_BASE_DIR)
|
||||
os.add_dll_directory(os.path.join(APP_BASE_DIR, "dll_backup"))
|
||||
|
||||
dll_backup_dir = os.path.join(APP_BASE_DIR, "dll_backup")
|
||||
if os.path.isdir(dll_backup_dir):
|
||||
os.add_dll_directory(dll_backup_dir)
|
||||
|
||||
|
||||
def main():
|
||||
|
|
|
|||
|
|
@ -37,11 +37,16 @@ class FileTranscriberQueueWorker(QObject):
|
|||
super().__init__(parent)
|
||||
self.tasks_queue = queue.Queue()
|
||||
self.canceled_tasks: Set[UUID] = set()
|
||||
self.current_transcriber = None
|
||||
|
||||
@pyqtSlot()
|
||||
def run(self):
|
||||
logging.debug("Waiting for next transcription task")
|
||||
|
||||
# Clean up of previous run.
|
||||
if self.current_transcriber is not None:
|
||||
self.current_transcriber.stop()
|
||||
|
||||
# Get next non-canceled task from queue
|
||||
while True:
|
||||
self.current_task: Optional[FileTranscriptionTask] = self.tasks_queue.get()
|
||||
|
|
@ -131,9 +136,14 @@ class FileTranscriberQueueWorker(QObject):
|
|||
def cancel_task(self, task_id: UUID):
|
||||
self.canceled_tasks.add(task_id)
|
||||
|
||||
if self.current_task.uid == task_id:
|
||||
if self.current_task is not None and self.current_task.uid == task_id:
|
||||
if self.current_transcriber is not None:
|
||||
self.current_transcriber.stop()
|
||||
|
||||
if self.current_transcriber_thread is not None:
|
||||
if not self.current_transcriber_thread.wait(3000):
|
||||
logging.warning("Transcriber thread did not terminate gracefully")
|
||||
self.current_transcriber_thread.terminate()
|
||||
|
||||
def on_task_error(self, error: str):
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: buzz\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-07-06 20:16+0300\n"
|
||||
"POT-Creation-Date: 2025-07-18 09:13+0300\n"
|
||||
"PO-Revision-Date: 2025-05-17 18:25+0200\n"
|
||||
"Last-Translator: Éric Duarte <contacto@ericdq.com>\n"
|
||||
"Language-Team: Catalan <jmas@softcatala.org>\n"
|
||||
|
|
@ -295,8 +295,8 @@ msgid "Download failed"
|
|||
msgstr "Descàrrega fallida"
|
||||
|
||||
#: buzz/widgets/preferences_dialog/models_preferences_widget.py:273
|
||||
#: buzz/widgets/main_window.py:295 buzz/model_loader.py:515
|
||||
#: buzz/model_loader.py:529
|
||||
#: buzz/widgets/main_window.py:295 buzz/model_loader.py:505
|
||||
#: buzz/model_loader.py:519
|
||||
msgid "Error"
|
||||
msgstr "Error"
|
||||
|
||||
|
|
@ -486,11 +486,11 @@ msgstr "Esperant la traducció de la IA..."
|
|||
msgid "Microphone:"
|
||||
msgstr "Micròfon:"
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:577
|
||||
#: buzz/widgets/recording_transcriber_widget.py:579
|
||||
msgid "An error occurred while starting a new recording:"
|
||||
msgstr "S'ha produït un error en iniciar un enregistrament nou:"
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:581
|
||||
#: buzz/widgets/recording_transcriber_widget.py:583
|
||||
msgid ""
|
||||
"Please check your audio devices or check the application logs for more "
|
||||
"information."
|
||||
|
|
@ -540,7 +540,6 @@ msgid "Export"
|
|||
msgstr "Exporta"
|
||||
|
||||
#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:174
|
||||
#: buzz/transcriber/transcriber.py:24
|
||||
msgid "Translate"
|
||||
msgstr "Traduir"
|
||||
|
||||
|
|
@ -664,6 +663,24 @@ msgstr "Selecciona un fitxer d'àudio"
|
|||
msgid "Unable to save OpenAI API key to keyring"
|
||||
msgstr "No s'ha pogut desar la clau OpenAI API a l'anell de claus"
|
||||
|
||||
#: buzz/transcriber/local_whisper_cpp_server_transcriber.py:51
|
||||
#: buzz/transcriber/recording_transcriber.py:372
|
||||
msgid "Whisper server failed to start. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/local_whisper_cpp_server_transcriber.py:54
|
||||
#: buzz/transcriber/recording_transcriber.py:375
|
||||
msgid ""
|
||||
"Whisper server failed to start due to insufficient memory. Please try again "
|
||||
"with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment "
|
||||
"variable."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/transcriber.py:24
|
||||
#, fuzzy
|
||||
msgid "Translate to English"
|
||||
msgstr "Configuració de la traducció"
|
||||
|
||||
#: buzz/transcriber/transcriber.py:25
|
||||
msgid "Transcribe"
|
||||
msgstr "Transcriure"
|
||||
|
|
@ -1024,7 +1041,11 @@ msgstr "Sundanès"
|
|||
msgid "Cantonese"
|
||||
msgstr "Cantonès"
|
||||
|
||||
#: buzz/model_loader.py:548
|
||||
#: buzz/transcriber/recording_transcriber.py:338
|
||||
msgid "Starting Whisper.cpp..."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/model_loader.py:538
|
||||
msgid "A connection error occurred"
|
||||
msgstr "S'ha produït un error de connexió"
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-07-06 20:16+0300\n"
|
||||
"POT-Creation-Date: 2025-07-18 09:13+0300\n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: Ole Guldberg2 <xalt7x.service@gmail.com>\n"
|
||||
"Language-Team: \n"
|
||||
|
|
@ -295,8 +295,8 @@ msgid "Download failed"
|
|||
msgstr "Download mislykkedes"
|
||||
|
||||
#: buzz/widgets/preferences_dialog/models_preferences_widget.py:273
|
||||
#: buzz/widgets/main_window.py:295 buzz/model_loader.py:515
|
||||
#: buzz/model_loader.py:529
|
||||
#: buzz/widgets/main_window.py:295 buzz/model_loader.py:505
|
||||
#: buzz/model_loader.py:519
|
||||
msgid "Error"
|
||||
msgstr "Fejl"
|
||||
|
||||
|
|
@ -487,11 +487,11 @@ msgstr "Venter på AI oversættelse..."
|
|||
msgid "Microphone:"
|
||||
msgstr "Mikrofon:"
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:577
|
||||
#: buzz/widgets/recording_transcriber_widget.py:579
|
||||
msgid "An error occurred while starting a new recording:"
|
||||
msgstr "Der skete en fejl ved opstart af en ny optagelse:"
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:581
|
||||
#: buzz/widgets/recording_transcriber_widget.py:583
|
||||
msgid ""
|
||||
"Please check your audio devices or check the application logs for more "
|
||||
"information."
|
||||
|
|
@ -541,7 +541,6 @@ msgid "Export"
|
|||
msgstr "Eksporter"
|
||||
|
||||
#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:174
|
||||
#: buzz/transcriber/transcriber.py:24
|
||||
msgid "Translate"
|
||||
msgstr "Oversæt"
|
||||
|
||||
|
|
@ -665,6 +664,24 @@ msgstr "Vælg audio-fil"
|
|||
msgid "Unable to save OpenAI API key to keyring"
|
||||
msgstr "Kan ikke gemme OpenAI API-nøgle i nøgleringen"
|
||||
|
||||
#: buzz/transcriber/local_whisper_cpp_server_transcriber.py:51
|
||||
#: buzz/transcriber/recording_transcriber.py:372
|
||||
msgid "Whisper server failed to start. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/local_whisper_cpp_server_transcriber.py:54
|
||||
#: buzz/transcriber/recording_transcriber.py:375
|
||||
msgid ""
|
||||
"Whisper server failed to start due to insufficient memory. Please try again "
|
||||
"with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment "
|
||||
"variable."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/transcriber.py:24
|
||||
#, fuzzy
|
||||
msgid "Translate to English"
|
||||
msgstr "Oversættelsesindstillinger"
|
||||
|
||||
#: buzz/transcriber/transcriber.py:25
|
||||
msgid "Transcribe"
|
||||
msgstr "Transkriber"
|
||||
|
|
@ -1025,7 +1042,11 @@ msgstr ""
|
|||
msgid "Cantonese"
|
||||
msgstr ""
|
||||
|
||||
#: buzz/model_loader.py:548
|
||||
#: buzz/transcriber/recording_transcriber.py:338
|
||||
msgid "Starting Whisper.cpp..."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/model_loader.py:538
|
||||
msgid "A connection error occurred"
|
||||
msgstr "Der er opstået en forbindelsesfejl"
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-07-06 20:16+0300\n"
|
||||
"POT-Creation-Date: 2025-07-18 09:13+0300\n"
|
||||
"PO-Revision-Date: 2025-03-05 14:41+0100\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
|
|
@ -295,8 +295,8 @@ msgid "Download failed"
|
|||
msgstr "Der Download ist fehlgeschlagen"
|
||||
|
||||
#: buzz/widgets/preferences_dialog/models_preferences_widget.py:273
|
||||
#: buzz/widgets/main_window.py:295 buzz/model_loader.py:515
|
||||
#: buzz/model_loader.py:529
|
||||
#: buzz/widgets/main_window.py:295 buzz/model_loader.py:505
|
||||
#: buzz/model_loader.py:519
|
||||
msgid "Error"
|
||||
msgstr "Fehler"
|
||||
|
||||
|
|
@ -487,11 +487,11 @@ msgstr "Warten auf KI-Übersetzung..."
|
|||
msgid "Microphone:"
|
||||
msgstr "Mikrofon:"
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:577
|
||||
#: buzz/widgets/recording_transcriber_widget.py:579
|
||||
msgid "An error occurred while starting a new recording:"
|
||||
msgstr "Beim Starten einer neuen Aufnahme ist ein Fehler aufgetreten:"
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:581
|
||||
#: buzz/widgets/recording_transcriber_widget.py:583
|
||||
msgid ""
|
||||
"Please check your audio devices or check the application logs for more "
|
||||
"information."
|
||||
|
|
@ -541,7 +541,6 @@ msgid "Export"
|
|||
msgstr "Export"
|
||||
|
||||
#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:174
|
||||
#: buzz/transcriber/transcriber.py:24
|
||||
msgid "Translate"
|
||||
msgstr "Übersetzen"
|
||||
|
||||
|
|
@ -666,6 +665,24 @@ msgid "Unable to save OpenAI API key to keyring"
|
|||
msgstr ""
|
||||
"Der OpenAI-API-Schlüssel kann nicht im Schlüsselbund gespeichert werden"
|
||||
|
||||
#: buzz/transcriber/local_whisper_cpp_server_transcriber.py:51
|
||||
#: buzz/transcriber/recording_transcriber.py:372
|
||||
msgid "Whisper server failed to start. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/local_whisper_cpp_server_transcriber.py:54
|
||||
#: buzz/transcriber/recording_transcriber.py:375
|
||||
msgid ""
|
||||
"Whisper server failed to start due to insufficient memory. Please try again "
|
||||
"with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment "
|
||||
"variable."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/transcriber.py:24
|
||||
#, fuzzy
|
||||
msgid "Translate to English"
|
||||
msgstr "Übersetzungseinstellungen"
|
||||
|
||||
#: buzz/transcriber/transcriber.py:25
|
||||
msgid "Transcribe"
|
||||
msgstr "Transkribieren"
|
||||
|
|
@ -1026,7 +1043,11 @@ msgstr "Sundanesisch"
|
|||
msgid "Cantonese"
|
||||
msgstr "Kantonesisch"
|
||||
|
||||
#: buzz/model_loader.py:548
|
||||
#: buzz/transcriber/recording_transcriber.py:338
|
||||
msgid "Starting Whisper.cpp..."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/model_loader.py:538
|
||||
msgid "A connection error occurred"
|
||||
msgstr "Ein Verbindungsfehler ist aufgetreten"
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-07-06 20:16+0300\n"
|
||||
"POT-Creation-Date: 2025-07-18 09:13+0300\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
|
@ -287,8 +287,8 @@ msgid "Download failed"
|
|||
msgstr ""
|
||||
|
||||
#: buzz/widgets/preferences_dialog/models_preferences_widget.py:273
|
||||
#: buzz/widgets/main_window.py:295 buzz/model_loader.py:515
|
||||
#: buzz/model_loader.py:529
|
||||
#: buzz/widgets/main_window.py:295 buzz/model_loader.py:505
|
||||
#: buzz/model_loader.py:519
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -477,11 +477,11 @@ msgstr ""
|
|||
msgid "Microphone:"
|
||||
msgstr ""
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:577
|
||||
#: buzz/widgets/recording_transcriber_widget.py:579
|
||||
msgid "An error occurred while starting a new recording:"
|
||||
msgstr ""
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:581
|
||||
#: buzz/widgets/recording_transcriber_widget.py:583
|
||||
msgid ""
|
||||
"Please check your audio devices or check the application logs for more "
|
||||
"information."
|
||||
|
|
@ -529,7 +529,6 @@ msgid "Export"
|
|||
msgstr ""
|
||||
|
||||
#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:174
|
||||
#: buzz/transcriber/transcriber.py:24
|
||||
msgid "Translate"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -647,6 +646,23 @@ msgstr ""
|
|||
msgid "Unable to save OpenAI API key to keyring"
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/local_whisper_cpp_server_transcriber.py:51
|
||||
#: buzz/transcriber/recording_transcriber.py:372
|
||||
msgid "Whisper server failed to start. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/local_whisper_cpp_server_transcriber.py:54
|
||||
#: buzz/transcriber/recording_transcriber.py:375
|
||||
msgid ""
|
||||
"Whisper server failed to start due to insufficient memory. Please try again "
|
||||
"with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment "
|
||||
"variable."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/transcriber.py:24
|
||||
msgid "Translate to English"
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/transcriber.py:25
|
||||
msgid "Transcribe"
|
||||
msgstr ""
|
||||
|
|
@ -1007,7 +1023,11 @@ msgstr ""
|
|||
msgid "Cantonese"
|
||||
msgstr ""
|
||||
|
||||
#: buzz/model_loader.py:548
|
||||
#: buzz/transcriber/recording_transcriber.py:338
|
||||
msgid "Starting Whisper.cpp..."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/model_loader.py:538
|
||||
msgid "A connection error occurred"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-07-06 20:16+0300\n"
|
||||
"POT-Creation-Date: 2025-07-18 09:13+0300\n"
|
||||
"PO-Revision-Date: 2025-05-17 18:27+0200\n"
|
||||
"Last-Translator: Éric Duarte <contacto@ericdq.com>\n"
|
||||
"Language-Team: \n"
|
||||
|
|
@ -301,8 +301,8 @@ msgid "Download failed"
|
|||
msgstr "Descarga fallida"
|
||||
|
||||
#: buzz/widgets/preferences_dialog/models_preferences_widget.py:273
|
||||
#: buzz/widgets/main_window.py:295 buzz/model_loader.py:515
|
||||
#: buzz/model_loader.py:529
|
||||
#: buzz/widgets/main_window.py:295 buzz/model_loader.py:505
|
||||
#: buzz/model_loader.py:519
|
||||
msgid "Error"
|
||||
msgstr "Error"
|
||||
|
||||
|
|
@ -517,12 +517,12 @@ msgid "Microphone:"
|
|||
msgstr "Micrófono:"
|
||||
|
||||
# automatic translation
|
||||
#: buzz/widgets/recording_transcriber_widget.py:577
|
||||
#: buzz/widgets/recording_transcriber_widget.py:579
|
||||
msgid "An error occurred while starting a new recording:"
|
||||
msgstr "Se produjo un error al iniciar una grabación nueva:"
|
||||
|
||||
# automatic translation
|
||||
#: buzz/widgets/recording_transcriber_widget.py:581
|
||||
#: buzz/widgets/recording_transcriber_widget.py:583
|
||||
msgid ""
|
||||
"Please check your audio devices or check the application logs for more "
|
||||
"information."
|
||||
|
|
@ -575,7 +575,6 @@ msgid "Export"
|
|||
msgstr "Exportar"
|
||||
|
||||
#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:174
|
||||
#: buzz/transcriber/transcriber.py:24
|
||||
msgid "Translate"
|
||||
msgstr "Traducir"
|
||||
|
||||
|
|
@ -707,6 +706,24 @@ msgstr "Seleccionar archivo de audio"
|
|||
msgid "Unable to save OpenAI API key to keyring"
|
||||
msgstr "No se puede guardar la clave de la API de OpenAI en el llavero"
|
||||
|
||||
#: buzz/transcriber/local_whisper_cpp_server_transcriber.py:51
|
||||
#: buzz/transcriber/recording_transcriber.py:372
|
||||
msgid "Whisper server failed to start. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/local_whisper_cpp_server_transcriber.py:54
|
||||
#: buzz/transcriber/recording_transcriber.py:375
|
||||
msgid ""
|
||||
"Whisper server failed to start due to insufficient memory. Please try again "
|
||||
"with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment "
|
||||
"variable."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/transcriber.py:24
|
||||
#, fuzzy
|
||||
msgid "Translate to English"
|
||||
msgstr "Ajustes de traducción"
|
||||
|
||||
# automatic translation
|
||||
#: buzz/transcriber/transcriber.py:25
|
||||
msgid "Transcribe"
|
||||
|
|
@ -1068,7 +1085,11 @@ msgstr "Sundanés"
|
|||
msgid "Cantonese"
|
||||
msgstr "Cantonés"
|
||||
|
||||
#: buzz/model_loader.py:548
|
||||
#: buzz/transcriber/recording_transcriber.py:338
|
||||
msgid "Starting Whisper.cpp..."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/model_loader.py:538
|
||||
msgid "A connection error occurred"
|
||||
msgstr "Se ha producido un error de conexión"
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: buzz\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-07-06 20:16+0300\n"
|
||||
"POT-Creation-Date: 2025-07-18 09:13+0300\n"
|
||||
"PO-Revision-Date: 2025-05-30 15:22+0100\n"
|
||||
"Language-Team: (Italiano) Albano Battistella <albanobattistella@gmail.com>\n"
|
||||
"Language: it_IT\n"
|
||||
|
|
@ -294,8 +294,8 @@ msgid "Download failed"
|
|||
msgstr "Download non riuscito"
|
||||
|
||||
#: buzz/widgets/preferences_dialog/models_preferences_widget.py:273
|
||||
#: buzz/widgets/main_window.py:295 buzz/model_loader.py:515
|
||||
#: buzz/model_loader.py:529
|
||||
#: buzz/widgets/main_window.py:295 buzz/model_loader.py:505
|
||||
#: buzz/model_loader.py:519
|
||||
msgid "Error"
|
||||
msgstr "Errore"
|
||||
|
||||
|
|
@ -486,11 +486,11 @@ msgstr "In attesa della traduzione AI..."
|
|||
msgid "Microphone:"
|
||||
msgstr "Microfono:"
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:577
|
||||
#: buzz/widgets/recording_transcriber_widget.py:579
|
||||
msgid "An error occurred while starting a new recording:"
|
||||
msgstr "Si è verificato un errore durante l'avvio della nuova registrazione:"
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:581
|
||||
#: buzz/widgets/recording_transcriber_widget.py:583
|
||||
msgid ""
|
||||
"Please check your audio devices or check the application logs for more "
|
||||
"information."
|
||||
|
|
@ -540,7 +540,6 @@ msgid "Export"
|
|||
msgstr "Esporta"
|
||||
|
||||
#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:174
|
||||
#: buzz/transcriber/transcriber.py:24
|
||||
msgid "Translate"
|
||||
msgstr "Tradurre"
|
||||
|
||||
|
|
@ -664,6 +663,24 @@ msgstr "Seleziona file audio"
|
|||
msgid "Unable to save OpenAI API key to keyring"
|
||||
msgstr "Impossibile salvare la chiave API OpenAI nel portachiavi"
|
||||
|
||||
#: buzz/transcriber/local_whisper_cpp_server_transcriber.py:51
|
||||
#: buzz/transcriber/recording_transcriber.py:372
|
||||
msgid "Whisper server failed to start. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/local_whisper_cpp_server_transcriber.py:54
|
||||
#: buzz/transcriber/recording_transcriber.py:375
|
||||
msgid ""
|
||||
"Whisper server failed to start due to insufficient memory. Please try again "
|
||||
"with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment "
|
||||
"variable."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/transcriber.py:24
|
||||
#, fuzzy
|
||||
msgid "Translate to English"
|
||||
msgstr "Impostazioni di traduzione"
|
||||
|
||||
#: buzz/transcriber/transcriber.py:25
|
||||
msgid "Transcribe"
|
||||
msgstr "Trascrivere"
|
||||
|
|
@ -1024,7 +1041,11 @@ msgstr "Sundanese"
|
|||
msgid "Cantonese"
|
||||
msgstr "Cantonese"
|
||||
|
||||
#: buzz/model_loader.py:548
|
||||
#: buzz/transcriber/recording_transcriber.py:338
|
||||
msgid "Starting Whisper.cpp..."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/model_loader.py:538
|
||||
msgid "A connection error occurred"
|
||||
msgstr "Si è verificato un errore di connessione"
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-07-06 20:16+0300\n"
|
||||
"POT-Creation-Date: 2025-07-18 09:13+0300\n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: nunawa <71294849+nunawa@users.noreply.github.com>\n"
|
||||
"Language-Team: \n"
|
||||
|
|
@ -291,8 +291,8 @@ msgid "Download failed"
|
|||
msgstr "ダウンロード失敗"
|
||||
|
||||
#: buzz/widgets/preferences_dialog/models_preferences_widget.py:273
|
||||
#: buzz/widgets/main_window.py:295 buzz/model_loader.py:515
|
||||
#: buzz/model_loader.py:529
|
||||
#: buzz/widgets/main_window.py:295 buzz/model_loader.py:505
|
||||
#: buzz/model_loader.py:519
|
||||
msgid "Error"
|
||||
msgstr "エラー"
|
||||
|
||||
|
|
@ -483,11 +483,11 @@ msgstr "AI翻訳を待っています..."
|
|||
msgid "Microphone:"
|
||||
msgstr "マイク:"
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:577
|
||||
#: buzz/widgets/recording_transcriber_widget.py:579
|
||||
msgid "An error occurred while starting a new recording:"
|
||||
msgstr "新規録音開始時にエラーが発生しました:"
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:581
|
||||
#: buzz/widgets/recording_transcriber_widget.py:583
|
||||
msgid ""
|
||||
"Please check your audio devices or check the application logs for more "
|
||||
"information."
|
||||
|
|
@ -537,7 +537,6 @@ msgid "Export"
|
|||
msgstr "出力"
|
||||
|
||||
#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:174
|
||||
#: buzz/transcriber/transcriber.py:24
|
||||
msgid "Translate"
|
||||
msgstr "翻訳"
|
||||
|
||||
|
|
@ -660,6 +659,24 @@ msgstr "音声ファイルを選択"
|
|||
msgid "Unable to save OpenAI API key to keyring"
|
||||
msgstr "OpenAI API キーをkeyringに保存できません"
|
||||
|
||||
#: buzz/transcriber/local_whisper_cpp_server_transcriber.py:51
|
||||
#: buzz/transcriber/recording_transcriber.py:372
|
||||
msgid "Whisper server failed to start. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/local_whisper_cpp_server_transcriber.py:54
|
||||
#: buzz/transcriber/recording_transcriber.py:375
|
||||
msgid ""
|
||||
"Whisper server failed to start due to insufficient memory. Please try again "
|
||||
"with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment "
|
||||
"variable."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/transcriber.py:24
|
||||
#, fuzzy
|
||||
msgid "Translate to English"
|
||||
msgstr "翻訳設定"
|
||||
|
||||
#: buzz/transcriber/transcriber.py:25
|
||||
msgid "Transcribe"
|
||||
msgstr "文字起こし"
|
||||
|
|
@ -1020,7 +1037,11 @@ msgstr ""
|
|||
msgid "Cantonese"
|
||||
msgstr ""
|
||||
|
||||
#: buzz/model_loader.py:548
|
||||
#: buzz/transcriber/recording_transcriber.py:338
|
||||
msgid "Starting Whisper.cpp..."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/model_loader.py:538
|
||||
msgid "A connection error occurred"
|
||||
msgstr "接続エラーが発生しました"
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-07-06 20:16+0300\n"
|
||||
"PO-Revision-Date: 2025-07-06 20:11+0300\n"
|
||||
"POT-Creation-Date: 2025-07-18 09:13+0300\n"
|
||||
"PO-Revision-Date: 2025-07-18 09:14+0300\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Language: lv_LV\n"
|
||||
|
|
@ -297,8 +297,8 @@ msgid "Download failed"
|
|||
msgstr "Lejupielāde neizdevās"
|
||||
|
||||
#: buzz/widgets/preferences_dialog/models_preferences_widget.py:273
|
||||
#: buzz/widgets/main_window.py:295 buzz/model_loader.py:515
|
||||
#: buzz/model_loader.py:529
|
||||
#: buzz/widgets/main_window.py:295 buzz/model_loader.py:505
|
||||
#: buzz/model_loader.py:519
|
||||
msgid "Error"
|
||||
msgstr "Kļūda"
|
||||
|
||||
|
|
@ -383,6 +383,8 @@ msgid ""
|
|||
"Enter instructions for AI on how to translate, for example 'Please translate "
|
||||
"each text sent to you from English to Spanish.'"
|
||||
msgstr ""
|
||||
"Ievadiet instrukcijas mākslīgajam intelektam, piemēram, 'Lūdzu, iztulko "
|
||||
"katru tev atsūtīto tekstu no angļu valodas latviski'"
|
||||
|
||||
#: buzz/widgets/transcriber/advanced_settings_dialog.py:92
|
||||
msgid "Instructions for AI:"
|
||||
|
|
@ -489,11 +491,11 @@ msgstr "Gaida MI tulkojumu..."
|
|||
msgid "Microphone:"
|
||||
msgstr "Mikrofons:"
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:577
|
||||
#: buzz/widgets/recording_transcriber_widget.py:579
|
||||
msgid "An error occurred while starting a new recording:"
|
||||
msgstr "Sākot jaunu ierakstu notikusi kļūda:"
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:581
|
||||
#: buzz/widgets/recording_transcriber_widget.py:583
|
||||
msgid ""
|
||||
"Please check your audio devices or check the application logs for more "
|
||||
"information."
|
||||
|
|
@ -543,7 +545,6 @@ msgid "Export"
|
|||
msgstr "Eksportēt"
|
||||
|
||||
#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:174
|
||||
#: buzz/transcriber/transcriber.py:24
|
||||
msgid "Translate"
|
||||
msgstr "Tulkot"
|
||||
|
||||
|
|
@ -665,6 +666,28 @@ msgstr "Izvēlieties audio failu"
|
|||
msgid "Unable to save OpenAI API key to keyring"
|
||||
msgstr "Neizdevās saglabāt OpenAI API atslēgu atslēgu saišķī"
|
||||
|
||||
#: buzz/transcriber/local_whisper_cpp_server_transcriber.py:51
|
||||
#: buzz/transcriber/recording_transcriber.py:372
|
||||
msgid "Whisper server failed to start. Check logs for details."
|
||||
msgstr ""
|
||||
"Whisper serverim neizdevās ieslēgties. Lūdzu pārbaudiet lietotnes žurnāla "
|
||||
"ierakstus."
|
||||
|
||||
#: buzz/transcriber/local_whisper_cpp_server_transcriber.py:54
|
||||
#: buzz/transcriber/recording_transcriber.py:375
|
||||
msgid ""
|
||||
"Whisper server failed to start due to insufficient memory. Please try again "
|
||||
"with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment "
|
||||
"variable."
|
||||
msgstr ""
|
||||
"Whisper serverim neizdevās ieslēgties, jo nepietika atmiņas. Lūdzu mēģiniet "
|
||||
"vēlreiz ar mazāku modeli. Lai izmantotu tikai CPU iestatiet "
|
||||
"BUZZ_FORCE_CPU=TRUE vides mainīgo."
|
||||
|
||||
#: buzz/transcriber/transcriber.py:24
|
||||
msgid "Translate to English"
|
||||
msgstr "Tulkot angliski"
|
||||
|
||||
#: buzz/transcriber/transcriber.py:25
|
||||
msgid "Transcribe"
|
||||
msgstr "Atpazīt"
|
||||
|
|
@ -1025,7 +1048,11 @@ msgstr "Sundāņu"
|
|||
msgid "Cantonese"
|
||||
msgstr "Kantonas"
|
||||
|
||||
#: buzz/model_loader.py:548
|
||||
#: buzz/transcriber/recording_transcriber.py:338
|
||||
msgid "Starting Whisper.cpp..."
|
||||
msgstr "Palaiž Whisper.cpp..."
|
||||
|
||||
#: buzz/model_loader.py:538
|
||||
msgid "A connection error occurred"
|
||||
msgstr "Notika savienojuma kļūda"
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-07-06 20:16+0300\n"
|
||||
"POT-Creation-Date: 2025-07-18 09:13+0300\n"
|
||||
"PO-Revision-Date: 2025-03-20 18:30+0100\n"
|
||||
"Last-Translator: Heimen Stoffels <vistausss@fastmail.com>\n"
|
||||
"Language-Team: none\n"
|
||||
|
|
@ -297,8 +297,8 @@ msgid "Download failed"
|
|||
msgstr "Het downloaden is mislukt"
|
||||
|
||||
#: buzz/widgets/preferences_dialog/models_preferences_widget.py:273
|
||||
#: buzz/widgets/main_window.py:295 buzz/model_loader.py:515
|
||||
#: buzz/model_loader.py:529
|
||||
#: buzz/widgets/main_window.py:295 buzz/model_loader.py:505
|
||||
#: buzz/model_loader.py:519
|
||||
msgid "Error"
|
||||
msgstr "Foutmelding"
|
||||
|
||||
|
|
@ -489,11 +489,11 @@ msgstr "Bezig met wachten op AI-vertaling…"
|
|||
msgid "Microphone:"
|
||||
msgstr "Microfoon:"
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:577
|
||||
#: buzz/widgets/recording_transcriber_widget.py:579
|
||||
msgid "An error occurred while starting a new recording:"
|
||||
msgstr "Er is een fout opgetreden tijdens het starten van de opname:"
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:581
|
||||
#: buzz/widgets/recording_transcriber_widget.py:583
|
||||
msgid ""
|
||||
"Please check your audio devices or check the application logs for more "
|
||||
"information."
|
||||
|
|
@ -541,7 +541,6 @@ msgid "Export"
|
|||
msgstr "Exporteren"
|
||||
|
||||
#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:174
|
||||
#: buzz/transcriber/transcriber.py:24
|
||||
msgid "Translate"
|
||||
msgstr "Vertalen"
|
||||
|
||||
|
|
@ -664,6 +663,24 @@ msgstr "Kies een audiobestand"
|
|||
msgid "Unable to save OpenAI API key to keyring"
|
||||
msgstr "De OpenAI-api-sleutel kan niet worden bewaard in de sleutelbos"
|
||||
|
||||
#: buzz/transcriber/local_whisper_cpp_server_transcriber.py:51
|
||||
#: buzz/transcriber/recording_transcriber.py:372
|
||||
msgid "Whisper server failed to start. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/local_whisper_cpp_server_transcriber.py:54
|
||||
#: buzz/transcriber/recording_transcriber.py:375
|
||||
msgid ""
|
||||
"Whisper server failed to start due to insufficient memory. Please try again "
|
||||
"with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment "
|
||||
"variable."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/transcriber.py:24
|
||||
#, fuzzy
|
||||
msgid "Translate to English"
|
||||
msgstr "Vertaalinstellingen"
|
||||
|
||||
#: buzz/transcriber/transcriber.py:25
|
||||
msgid "Transcribe"
|
||||
msgstr "Transcriberen"
|
||||
|
|
@ -1024,7 +1041,11 @@ msgstr "Soedanees"
|
|||
msgid "Cantonese"
|
||||
msgstr "Kantonees"
|
||||
|
||||
#: buzz/model_loader.py:548
|
||||
#: buzz/transcriber/recording_transcriber.py:338
|
||||
msgid "Starting Whisper.cpp..."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/model_loader.py:538
|
||||
msgid "A connection error occurred"
|
||||
msgstr "Er is een verbindingsfout opgetreden"
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-07-06 20:16+0300\n"
|
||||
"POT-Creation-Date: 2025-07-18 09:13+0300\n"
|
||||
"PO-Revision-Date: 2024-03-17 20:50+0200\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
|
|
@ -298,8 +298,8 @@ msgid "Download failed"
|
|||
msgstr "Pobrany"
|
||||
|
||||
#: buzz/widgets/preferences_dialog/models_preferences_widget.py:273
|
||||
#: buzz/widgets/main_window.py:295 buzz/model_loader.py:515
|
||||
#: buzz/model_loader.py:529
|
||||
#: buzz/widgets/main_window.py:295 buzz/model_loader.py:505
|
||||
#: buzz/model_loader.py:519
|
||||
msgid "Error"
|
||||
msgstr "Błąd"
|
||||
|
||||
|
|
@ -495,11 +495,11 @@ msgstr ""
|
|||
msgid "Microphone:"
|
||||
msgstr "Mikrofon:"
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:577
|
||||
#: buzz/widgets/recording_transcriber_widget.py:579
|
||||
msgid "An error occurred while starting a new recording:"
|
||||
msgstr "Wystąpił błąd podczas rozpoczęcia nowego nagrania:"
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:581
|
||||
#: buzz/widgets/recording_transcriber_widget.py:583
|
||||
msgid ""
|
||||
"Please check your audio devices or check the application logs for more "
|
||||
"information."
|
||||
|
|
@ -550,7 +550,6 @@ msgid "Export"
|
|||
msgstr ""
|
||||
|
||||
#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:174
|
||||
#: buzz/transcriber/transcriber.py:24
|
||||
msgid "Translate"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -674,6 +673,24 @@ msgstr "Wybierz plik audio"
|
|||
msgid "Unable to save OpenAI API key to keyring"
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/local_whisper_cpp_server_transcriber.py:51
|
||||
#: buzz/transcriber/recording_transcriber.py:372
|
||||
msgid "Whisper server failed to start. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/local_whisper_cpp_server_transcriber.py:54
|
||||
#: buzz/transcriber/recording_transcriber.py:375
|
||||
msgid ""
|
||||
"Whisper server failed to start due to insufficient memory. Please try again "
|
||||
"with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment "
|
||||
"variable."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/transcriber.py:24
|
||||
#, fuzzy
|
||||
msgid "Translate to English"
|
||||
msgstr "Nowa transkrypcja"
|
||||
|
||||
#: buzz/transcriber/transcriber.py:25
|
||||
#, fuzzy
|
||||
msgid "Transcribe"
|
||||
|
|
@ -1035,7 +1052,11 @@ msgstr ""
|
|||
msgid "Cantonese"
|
||||
msgstr ""
|
||||
|
||||
#: buzz/model_loader.py:548
|
||||
#: buzz/transcriber/recording_transcriber.py:338
|
||||
msgid "Starting Whisper.cpp..."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/model_loader.py:538
|
||||
msgid "A connection error occurred"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: Buzz\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-07-06 20:16+0300\n"
|
||||
"POT-Creation-Date: 2025-07-18 09:13+0300\n"
|
||||
"PO-Revision-Date: 2025-06-29 22:23-0300\n"
|
||||
"Last-Translator: Paulo Schopf <pschopf@gmail.com>\n"
|
||||
"Language-Team: none\n"
|
||||
|
|
@ -295,8 +295,8 @@ msgid "Download failed"
|
|||
msgstr "Falha ao baixar"
|
||||
|
||||
#: buzz/widgets/preferences_dialog/models_preferences_widget.py:273
|
||||
#: buzz/widgets/main_window.py:295 buzz/model_loader.py:515
|
||||
#: buzz/model_loader.py:529
|
||||
#: buzz/widgets/main_window.py:295 buzz/model_loader.py:505
|
||||
#: buzz/model_loader.py:519
|
||||
msgid "Error"
|
||||
msgstr "Erro"
|
||||
|
||||
|
|
@ -485,11 +485,11 @@ msgstr "Aguardando tradução da IA..."
|
|||
msgid "Microphone:"
|
||||
msgstr "Microfone:"
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:577
|
||||
#: buzz/widgets/recording_transcriber_widget.py:579
|
||||
msgid "An error occurred while starting a new recording:"
|
||||
msgstr "Ocorreu um erro ao iniciar uma nova gravação:"
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:581
|
||||
#: buzz/widgets/recording_transcriber_widget.py:583
|
||||
msgid ""
|
||||
"Please check your audio devices or check the application logs for more "
|
||||
"information."
|
||||
|
|
@ -539,7 +539,6 @@ msgid "Export"
|
|||
msgstr "Exportar"
|
||||
|
||||
#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:174
|
||||
#: buzz/transcriber/transcriber.py:24
|
||||
msgid "Translate"
|
||||
msgstr "Traduzir"
|
||||
|
||||
|
|
@ -663,6 +662,24 @@ msgstr "Selecionar arquivo de áudio"
|
|||
msgid "Unable to save OpenAI API key to keyring"
|
||||
msgstr "Não foi possível salvar a chave da API OpenAI no cofre de chaves"
|
||||
|
||||
#: buzz/transcriber/local_whisper_cpp_server_transcriber.py:51
|
||||
#: buzz/transcriber/recording_transcriber.py:372
|
||||
msgid "Whisper server failed to start. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/local_whisper_cpp_server_transcriber.py:54
|
||||
#: buzz/transcriber/recording_transcriber.py:375
|
||||
msgid ""
|
||||
"Whisper server failed to start due to insufficient memory. Please try again "
|
||||
"with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment "
|
||||
"variable."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/transcriber.py:24
|
||||
#, fuzzy
|
||||
msgid "Translate to English"
|
||||
msgstr "Configurações de tradução"
|
||||
|
||||
#: buzz/transcriber/transcriber.py:25
|
||||
msgid "Transcribe"
|
||||
msgstr "Transcrever"
|
||||
|
|
@ -1023,7 +1040,11 @@ msgstr "Sundanês"
|
|||
msgid "Cantonese"
|
||||
msgstr "Cantonês"
|
||||
|
||||
#: buzz/model_loader.py:548
|
||||
#: buzz/transcriber/recording_transcriber.py:338
|
||||
msgid "Starting Whisper.cpp..."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/model_loader.py:538
|
||||
msgid "A connection error occurred"
|
||||
msgstr "Ocorreu um erro de conexão"
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-07-06 20:16+0300\n"
|
||||
"POT-Creation-Date: 2025-07-18 09:13+0300\n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: Yevhen Popok <xalt7x.service@gmail.com>\n"
|
||||
"Language-Team: \n"
|
||||
|
|
@ -293,8 +293,8 @@ msgid "Download failed"
|
|||
msgstr "Невдале завантаження"
|
||||
|
||||
#: buzz/widgets/preferences_dialog/models_preferences_widget.py:273
|
||||
#: buzz/widgets/main_window.py:295 buzz/model_loader.py:515
|
||||
#: buzz/model_loader.py:529
|
||||
#: buzz/widgets/main_window.py:295 buzz/model_loader.py:505
|
||||
#: buzz/model_loader.py:519
|
||||
msgid "Error"
|
||||
msgstr "Помилка"
|
||||
|
||||
|
|
@ -485,11 +485,11 @@ msgstr "Очікування перекладу від ШІ..."
|
|||
msgid "Microphone:"
|
||||
msgstr "Мікрофон:"
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:577
|
||||
#: buzz/widgets/recording_transcriber_widget.py:579
|
||||
msgid "An error occurred while starting a new recording:"
|
||||
msgstr "При старті нового запису виникла помилка:"
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:581
|
||||
#: buzz/widgets/recording_transcriber_widget.py:583
|
||||
msgid ""
|
||||
"Please check your audio devices or check the application logs for more "
|
||||
"information."
|
||||
|
|
@ -539,7 +539,6 @@ msgid "Export"
|
|||
msgstr "Експорт"
|
||||
|
||||
#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:174
|
||||
#: buzz/transcriber/transcriber.py:24
|
||||
msgid "Translate"
|
||||
msgstr "Перекласти"
|
||||
|
||||
|
|
@ -661,6 +660,24 @@ msgstr "Вибрати аудіофайл"
|
|||
msgid "Unable to save OpenAI API key to keyring"
|
||||
msgstr "Не вдається додати до звʼязки ключів API-ключ OpenAI"
|
||||
|
||||
#: buzz/transcriber/local_whisper_cpp_server_transcriber.py:51
|
||||
#: buzz/transcriber/recording_transcriber.py:372
|
||||
msgid "Whisper server failed to start. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/local_whisper_cpp_server_transcriber.py:54
|
||||
#: buzz/transcriber/recording_transcriber.py:375
|
||||
msgid ""
|
||||
"Whisper server failed to start due to insufficient memory. Please try again "
|
||||
"with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment "
|
||||
"variable."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/transcriber.py:24
|
||||
#, fuzzy
|
||||
msgid "Translate to English"
|
||||
msgstr "Налаштування перекладу"
|
||||
|
||||
#: buzz/transcriber/transcriber.py:25
|
||||
msgid "Transcribe"
|
||||
msgstr "Розпізнати"
|
||||
|
|
@ -1021,7 +1038,11 @@ msgstr ""
|
|||
msgid "Cantonese"
|
||||
msgstr ""
|
||||
|
||||
#: buzz/model_loader.py:548
|
||||
#: buzz/transcriber/recording_transcriber.py:338
|
||||
msgid "Starting Whisper.cpp..."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/model_loader.py:538
|
||||
msgid "A connection error occurred"
|
||||
msgstr "Виникла помилка зʼєднання"
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-07-06 20:16+0300\n"
|
||||
"POT-Creation-Date: 2025-07-18 09:13+0300\n"
|
||||
"PO-Revision-Date: 2023-05-01 15:45+0800\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: lamb \n"
|
||||
|
|
@ -301,8 +301,8 @@ msgid "Download failed"
|
|||
msgstr "下载模型失败"
|
||||
|
||||
#: buzz/widgets/preferences_dialog/models_preferences_widget.py:273
|
||||
#: buzz/widgets/main_window.py:295 buzz/model_loader.py:515
|
||||
#: buzz/model_loader.py:529
|
||||
#: buzz/widgets/main_window.py:295 buzz/model_loader.py:505
|
||||
#: buzz/model_loader.py:519
|
||||
msgid "Error"
|
||||
msgstr "错误"
|
||||
|
||||
|
|
@ -498,11 +498,11 @@ msgstr "等待AI翻译..."
|
|||
msgid "Microphone:"
|
||||
msgstr "麦克风:"
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:577
|
||||
#: buzz/widgets/recording_transcriber_widget.py:579
|
||||
msgid "An error occurred while starting a new recording:"
|
||||
msgstr "开始新录制时出错"
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:581
|
||||
#: buzz/widgets/recording_transcriber_widget.py:583
|
||||
msgid ""
|
||||
"Please check your audio devices or check the application logs for more "
|
||||
"information."
|
||||
|
|
@ -551,7 +551,6 @@ msgid "Export"
|
|||
msgstr "导出"
|
||||
|
||||
#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:174
|
||||
#: buzz/transcriber/transcriber.py:24
|
||||
msgid "Translate"
|
||||
msgstr "翻译"
|
||||
|
||||
|
|
@ -674,6 +673,24 @@ msgstr "选择音频文件"
|
|||
msgid "Unable to save OpenAI API key to keyring"
|
||||
msgstr "无法将OpenAI API密钥保存到密钥串"
|
||||
|
||||
#: buzz/transcriber/local_whisper_cpp_server_transcriber.py:51
|
||||
#: buzz/transcriber/recording_transcriber.py:372
|
||||
msgid "Whisper server failed to start. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/local_whisper_cpp_server_transcriber.py:54
|
||||
#: buzz/transcriber/recording_transcriber.py:375
|
||||
msgid ""
|
||||
"Whisper server failed to start due to insufficient memory. Please try again "
|
||||
"with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment "
|
||||
"variable."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/transcriber.py:24
|
||||
#, fuzzy
|
||||
msgid "Translate to English"
|
||||
msgstr "翻译设置"
|
||||
|
||||
#: buzz/transcriber/transcriber.py:25
|
||||
#, fuzzy
|
||||
msgid "Transcribe"
|
||||
|
|
@ -1035,7 +1052,11 @@ msgstr ""
|
|||
msgid "Cantonese"
|
||||
msgstr ""
|
||||
|
||||
#: buzz/model_loader.py:548
|
||||
#: buzz/transcriber/recording_transcriber.py:338
|
||||
msgid "Starting Whisper.cpp..."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/model_loader.py:538
|
||||
msgid "A connection error occurred"
|
||||
msgstr "连接发生错误"
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-07-06 20:16+0300\n"
|
||||
"POT-Creation-Date: 2025-07-18 09:13+0300\n"
|
||||
"PO-Revision-Date: 2023-05-01 15:45+0800\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Lamb\n"
|
||||
|
|
@ -296,8 +296,8 @@ msgid "Download failed"
|
|||
msgstr "下載模型"
|
||||
|
||||
#: buzz/widgets/preferences_dialog/models_preferences_widget.py:273
|
||||
#: buzz/widgets/main_window.py:295 buzz/model_loader.py:515
|
||||
#: buzz/model_loader.py:529
|
||||
#: buzz/widgets/main_window.py:295 buzz/model_loader.py:505
|
||||
#: buzz/model_loader.py:519
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -493,11 +493,11 @@ msgstr ""
|
|||
msgid "Microphone:"
|
||||
msgstr "麥克風:"
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:577
|
||||
#: buzz/widgets/recording_transcriber_widget.py:579
|
||||
msgid "An error occurred while starting a new recording:"
|
||||
msgstr "開始新錄製出錯"
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:581
|
||||
#: buzz/widgets/recording_transcriber_widget.py:583
|
||||
msgid ""
|
||||
"Please check your audio devices or check the application logs for more "
|
||||
"information."
|
||||
|
|
@ -546,7 +546,6 @@ msgid "Export"
|
|||
msgstr ""
|
||||
|
||||
#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:174
|
||||
#: buzz/transcriber/transcriber.py:24
|
||||
msgid "Translate"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -668,6 +667,24 @@ msgstr "選擇聲音檔案"
|
|||
msgid "Unable to save OpenAI API key to keyring"
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/local_whisper_cpp_server_transcriber.py:51
|
||||
#: buzz/transcriber/recording_transcriber.py:372
|
||||
msgid "Whisper server failed to start. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/local_whisper_cpp_server_transcriber.py:54
|
||||
#: buzz/transcriber/recording_transcriber.py:375
|
||||
msgid ""
|
||||
"Whisper server failed to start due to insufficient memory. Please try again "
|
||||
"with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment "
|
||||
"variable."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/transcriber/transcriber.py:24
|
||||
#, fuzzy
|
||||
msgid "Translate to English"
|
||||
msgstr "新錄製"
|
||||
|
||||
#: buzz/transcriber/transcriber.py:25
|
||||
#, fuzzy
|
||||
msgid "Transcribe"
|
||||
|
|
@ -1029,7 +1046,11 @@ msgstr ""
|
|||
msgid "Cantonese"
|
||||
msgstr ""
|
||||
|
||||
#: buzz/model_loader.py:548
|
||||
#: buzz/transcriber/recording_transcriber.py:338
|
||||
msgid "Starting Whisper.cpp..."
|
||||
msgstr ""
|
||||
|
||||
#: buzz/model_loader.py:538
|
||||
msgid "A connection error occurred"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import threading
|
|||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import warnings
|
||||
import platform
|
||||
import requests
|
||||
|
|
@ -23,16 +22,6 @@ from huggingface_hub.errors import LocalEntryNotFoundError
|
|||
|
||||
from buzz.locale import _
|
||||
|
||||
# Catch exception from whisper.dll not getting loaded.
|
||||
# TODO: Remove flag and try-except when issue with loading
|
||||
# the DLL in some envs is fixed.
|
||||
LOADED_WHISPER_CPP_BINARY = False
|
||||
try:
|
||||
import buzz.whisper_cpp as whisper_cpp # noqa: F401
|
||||
|
||||
LOADED_WHISPER_CPP_BINARY = True
|
||||
except ImportError:
|
||||
logging.exception("")
|
||||
|
||||
model_root_dir = user_cache_dir("Buzz")
|
||||
model_root_dir = os.path.join(model_root_dir, "models")
|
||||
|
|
@ -89,6 +78,7 @@ class ModelType(enum.Enum):
|
|||
)
|
||||
|
||||
def is_available(self):
|
||||
from buzz.transcriber.whisper_cpp import LOADED_WHISPER_CPP_BINARY
|
||||
if (
|
||||
# Hide Whisper.cpp option if whisper.dll did not load correctly.
|
||||
# See: https://github.com/chidiwilliams/buzz/issues/274,
|
||||
|
|
|
|||
|
|
@ -6,11 +6,13 @@ import shutil
|
|||
import tempfile
|
||||
from abc import abstractmethod
|
||||
from typing import Optional, List
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot
|
||||
from yt_dlp import YoutubeDL
|
||||
|
||||
from buzz.whisper_audio import SAMPLE_RATE
|
||||
from buzz import whisper_audio
|
||||
from buzz.assets import APP_BASE_DIR
|
||||
from buzz.transcriber.transcriber import (
|
||||
FileTranscriptionTask,
|
||||
get_output_file_path,
|
||||
|
|
@ -18,6 +20,9 @@ from buzz.transcriber.transcriber import (
|
|||
OutputFormat,
|
||||
)
|
||||
|
||||
app_env = os.environ.copy()
|
||||
app_env['PATH'] = os.pathsep.join([os.path.join(APP_BASE_DIR, "_internal")] + [app_env['PATH']])
|
||||
|
||||
|
||||
class FileTranscriber(QObject):
|
||||
transcription_task: FileTranscriptionTask
|
||||
|
|
@ -35,6 +40,7 @@ class FileTranscriber(QObject):
|
|||
if self.transcription_task.source == FileTranscriptionTask.Source.URL_IMPORT:
|
||||
temp_output_path = tempfile.mktemp()
|
||||
wav_file = temp_output_path + ".wav"
|
||||
wav_file = str(Path(wav_file).resolve())
|
||||
|
||||
cookiefile = os.getenv("BUZZ_DOWNLOAD_COOKIEFILE")
|
||||
|
||||
|
|
@ -64,16 +70,17 @@ class FileTranscriber(QObject):
|
|||
"-threads", "0",
|
||||
"-i", temp_output_path,
|
||||
"-ac", "1",
|
||||
"-ar", str(SAMPLE_RATE),
|
||||
"-ar", str(whisper_audio.SAMPLE_RATE),
|
||||
"-acodec", "pcm_s16le",
|
||||
"-loglevel", "panic",
|
||||
wav_file]
|
||||
wav_file
|
||||
]
|
||||
|
||||
if sys.platform == "win32":
|
||||
si = subprocess.STARTUPINFO()
|
||||
si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
si.wShowWindow = subprocess.SW_HIDE
|
||||
result = subprocess.run(cmd, capture_output=True, startupinfo=si)
|
||||
result = subprocess.run(cmd, capture_output=True, startupinfo=si, env=app_env)
|
||||
else:
|
||||
result = subprocess.run(cmd, capture_output=True)
|
||||
|
||||
|
|
|
|||
87
buzz/transcriber/local_whisper_cpp_server_transcriber.py
Normal file
87
buzz/transcriber/local_whisper_cpp_server_transcriber.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import logging
|
||||
import os
|
||||
import time
|
||||
import subprocess
|
||||
from typing import Optional, List
|
||||
|
||||
from PyQt6.QtCore import QObject
|
||||
from openai import OpenAI
|
||||
|
||||
from buzz.locale import _
|
||||
from buzz.assets import APP_BASE_DIR
|
||||
from buzz.transcriber.openai_whisper_api_file_transcriber import OpenAIWhisperAPIFileTranscriber
|
||||
from buzz.transcriber.transcriber import FileTranscriptionTask, Segment
|
||||
|
||||
|
||||
class LocalWhisperCppServerTranscriber(OpenAIWhisperAPIFileTranscriber):
|
||||
# To be used on Windows only
|
||||
def __init__(self, task: FileTranscriptionTask, parent: Optional["QObject"] = None) -> None:
|
||||
super().__init__(task=task, parent=parent)
|
||||
|
||||
self.process = None
|
||||
self.initialization_error = None
|
||||
command = [
|
||||
os.path.join(APP_BASE_DIR, "whisper-server.exe"),
|
||||
"--port", "3000",
|
||||
"--inference-path", "/audio/transcriptions",
|
||||
"--threads", str(os.getenv("BUZZ_WHISPERCPP_N_THREADS", (os.cpu_count() or 8)//2)),
|
||||
"--language", task.transcription_options.language,
|
||||
"--model", task.model_path
|
||||
]
|
||||
|
||||
logging.debug(f"Starting Whisper server with command: {' '.join(command)}")
|
||||
|
||||
self.process = subprocess.Popen(
|
||||
command,
|
||||
stdout=subprocess.DEVNULL, # For debug set to subprocess.PIPE, but it will freeze on Windows after ~30 seconds
|
||||
stderr=subprocess.PIPE,
|
||||
shell=False,
|
||||
creationflags=subprocess.CREATE_NO_WINDOW
|
||||
)
|
||||
|
||||
# Wait for server to start and load model
|
||||
time.sleep(10)
|
||||
|
||||
if self.process is not None and self.process.poll() is None:
|
||||
logging.debug(f"Whisper server started successfully.")
|
||||
logging.debug(f"Model: {task.model_path}")
|
||||
else:
|
||||
stderr_output = self.process.stderr.read().decode()
|
||||
logging.error(f"Whisper server failed to start. Error: {stderr_output}")
|
||||
self.initialization_error = _("Whisper server failed to start. Check logs for details.")
|
||||
|
||||
if "ErrorOutOfDeviceMemory" in stderr_output:
|
||||
self.initialization_error = _("Whisper server failed to start due to insufficient memory. "
|
||||
"Please try again with a smaller model. "
|
||||
"To force CPU mode use BUZZ_FORCE_CPU=TRUE environment variable.")
|
||||
return
|
||||
|
||||
self.openai_client = OpenAI(
|
||||
api_key="not-used",
|
||||
base_url="http://127.0.0.1:3000"
|
||||
)
|
||||
|
||||
def transcribe(self) -> List[Segment]:
|
||||
if self.initialization_error:
|
||||
raise Exception(self.initialization_error)
|
||||
|
||||
return super().transcribe()
|
||||
|
||||
def stop(self):
|
||||
if self.process and self.process.poll() is None:
|
||||
try:
|
||||
self.process.terminate()
|
||||
self.process.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
# Force kill if terminate doesn't work within 5 seconds
|
||||
logging.warning("Whisper server didn't terminate gracefully, force killing")
|
||||
self.process.kill()
|
||||
try:
|
||||
self.process.wait(timeout=2)
|
||||
except subprocess.TimeoutExpired:
|
||||
logging.error("Failed to kill whisper server process")
|
||||
except Exception as e:
|
||||
logging.error(f"Error stopping whisper server: {e}")
|
||||
|
||||
def __del__(self):
|
||||
self.stop()
|
||||
|
|
@ -4,6 +4,8 @@ import os
|
|||
import sys
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Optional, List
|
||||
|
||||
from PyQt6.QtCore import QObject
|
||||
|
|
@ -11,7 +13,7 @@ from openai import OpenAI
|
|||
|
||||
from buzz.settings.settings import Settings
|
||||
from buzz.model_loader import get_custom_api_whisper_model
|
||||
from buzz.transcriber.file_transcriber import FileTranscriber
|
||||
from buzz.transcriber.file_transcriber import FileTranscriber, app_env
|
||||
from buzz.transcriber.transcriber import FileTranscriptionTask, Segment, Task
|
||||
from buzz.transcriber.whisper_cpp import append_segment
|
||||
|
||||
|
|
@ -41,6 +43,7 @@ class OpenAIWhisperAPIFileTranscriber(FileTranscriber):
|
|||
)
|
||||
|
||||
mp3_file = tempfile.mktemp() + ".mp3"
|
||||
mp3_file = str(Path(mp3_file).resolve())
|
||||
|
||||
cmd = [
|
||||
"ffmpeg",
|
||||
|
|
@ -53,7 +56,7 @@ class OpenAIWhisperAPIFileTranscriber(FileTranscriber):
|
|||
si = subprocess.STARTUPINFO()
|
||||
si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
si.wShowWindow = subprocess.SW_HIDE
|
||||
result = subprocess.run(cmd, capture_output=True, startupinfo=si)
|
||||
result = subprocess.run(cmd, capture_output=True, startupinfo=si, env=app_env)
|
||||
else:
|
||||
result = subprocess.run(cmd, capture_output=True)
|
||||
|
||||
|
|
@ -80,7 +83,7 @@ class OpenAIWhisperAPIFileTranscriber(FileTranscriber):
|
|||
si.wShowWindow = subprocess.SW_HIDE
|
||||
|
||||
duration_secs = float(
|
||||
subprocess.run(cmd, capture_output=True, check=True, startupinfo=si).stdout.decode("utf-8")
|
||||
subprocess.run(cmd, capture_output=True, check=True, startupinfo=si, env=app_env).stdout.decode("utf-8")
|
||||
)
|
||||
else:
|
||||
duration_secs = float(
|
||||
|
|
@ -107,6 +110,7 @@ class OpenAIWhisperAPIFileTranscriber(FileTranscriber):
|
|||
chunk_end = min((i + 1) * chunk_duration, duration_secs)
|
||||
|
||||
chunk_file = tempfile.mktemp() + ".mp3"
|
||||
chunk_file = str(Path(chunk_file).resolve())
|
||||
|
||||
# fmt: off
|
||||
cmd = [
|
||||
|
|
@ -122,7 +126,7 @@ class OpenAIWhisperAPIFileTranscriber(FileTranscriber):
|
|||
si = subprocess.STARTUPINFO()
|
||||
si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
si.wShowWindow = subprocess.SW_HIDE
|
||||
subprocess.run(cmd, capture_output=True, check=True, startupinfo=si)
|
||||
subprocess.run(cmd, capture_output=True, check=True, startupinfo=si, env=app_env)
|
||||
else:
|
||||
subprocess.run(cmd, capture_output=True, check=True)
|
||||
|
||||
|
|
@ -168,7 +172,7 @@ class OpenAIWhisperAPIFileTranscriber(FileTranscriber):
|
|||
segments = getattr(transcript, "segments", None)
|
||||
|
||||
words = getattr(transcript, "words", None)
|
||||
if "words" is None and "words" in transcript.model_extra:
|
||||
if words is None and "words" in transcript.model_extra:
|
||||
words = transcript.model_extra["words"]
|
||||
|
||||
if segments is None:
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@ import datetime
|
|||
import logging
|
||||
import platform
|
||||
import os
|
||||
import sys
|
||||
import wave
|
||||
import time
|
||||
import tempfile
|
||||
import threading
|
||||
import subprocess
|
||||
from typing import Optional
|
||||
from platformdirs import user_cache_dir
|
||||
|
||||
|
|
@ -17,7 +19,9 @@ from openai import OpenAI
|
|||
from PyQt6.QtCore import QObject, pyqtSignal
|
||||
|
||||
from buzz import whisper_audio
|
||||
from buzz.model_loader import WhisperModelSize, ModelType, get_custom_api_whisper_model
|
||||
from buzz.locale import _
|
||||
from buzz.assets import APP_BASE_DIR
|
||||
from buzz.model_loader import ModelType, get_custom_api_whisper_model
|
||||
from buzz.settings.settings import Settings
|
||||
from buzz.transcriber.transcriber import TranscriptionOptions, Task
|
||||
from buzz.transcriber.whisper_cpp import WhisperCpp
|
||||
|
|
@ -65,8 +69,11 @@ class RecordingTranscriber(QObject):
|
|||
self.sounddevice = sounddevice
|
||||
self.openai_client = None
|
||||
self.whisper_api_model = get_custom_api_whisper_model("")
|
||||
self.is_windows = sys.platform == "win32"
|
||||
self.process = None
|
||||
|
||||
def start(self):
|
||||
model = None
|
||||
model_path = self.model_path
|
||||
keep_samples = int(self.keep_sample_seconds * self.sample_rate)
|
||||
|
||||
|
|
@ -80,7 +87,11 @@ class RecordingTranscriber(QObject):
|
|||
device = "cuda" if use_cuda else "cpu"
|
||||
model = whisper.load_model(model_path, device=device)
|
||||
elif self.transcription_options.model.model_type == ModelType.WHISPER_CPP:
|
||||
model = WhisperCpp(model_path)
|
||||
# As DLL mode on Windows is somewhat unreliable, will use local whisper-server
|
||||
if self.is_windows:
|
||||
self.start_local_whisper_server()
|
||||
else:
|
||||
model = WhisperCpp(model_path)
|
||||
elif self.transcription_options.model.model_type == ModelType.FASTER_WHISPER:
|
||||
model_root_dir = user_cache_dir("Buzz")
|
||||
model_root_dir = os.path.join(model_root_dir, "models")
|
||||
|
|
@ -91,6 +102,10 @@ class RecordingTranscriber(QObject):
|
|||
logging.debug("Unsupported CUDA version (<12), using CPU")
|
||||
device = "cpu"
|
||||
|
||||
if not torch.cuda.is_available():
|
||||
logging.debug("CUDA is not available, using CPU")
|
||||
device = "cpu"
|
||||
|
||||
if force_cpu != "false":
|
||||
device = "cpu"
|
||||
|
||||
|
|
@ -134,7 +149,6 @@ class RecordingTranscriber(QObject):
|
|||
)
|
||||
|
||||
self.is_running = True
|
||||
amplitude = 0.0
|
||||
try:
|
||||
with self.sounddevice.InputStream(
|
||||
samplerate=self.sample_rate,
|
||||
|
|
@ -159,7 +173,7 @@ class RecordingTranscriber(QObject):
|
|||
amplitude,
|
||||
)
|
||||
|
||||
if amplitude < 0.01:
|
||||
if amplitude < 0.025:
|
||||
time.sleep(0.5)
|
||||
continue
|
||||
|
||||
|
|
@ -181,6 +195,8 @@ class RecordingTranscriber(QObject):
|
|||
elif (
|
||||
self.transcription_options.model.model_type
|
||||
== ModelType.WHISPER_CPP
|
||||
# On Windows we use the local whisper server via OpenAI API
|
||||
and not self.is_windows
|
||||
):
|
||||
assert isinstance(model, WhisperCpp)
|
||||
result = model.transcribe(
|
||||
|
|
@ -220,7 +236,11 @@ class RecordingTranscriber(QObject):
|
|||
task=self.transcription_options.task.value,
|
||||
)
|
||||
else: # OPEN_AI_WHISPER_API
|
||||
assert self.openai_client is not None
|
||||
if self.openai_client is None:
|
||||
self.transcription.emit(_("A connection error occurred"))
|
||||
self.stop_recording()
|
||||
return
|
||||
|
||||
# scale samples to 16-bit PCM
|
||||
pcm_data = (samples * 32767).astype(np.int16).tobytes()
|
||||
|
||||
|
|
@ -237,7 +257,7 @@ class RecordingTranscriber(QObject):
|
|||
options = {
|
||||
"model": self.whisper_api_model,
|
||||
"file": temp_file,
|
||||
"response_format": "verbose_json",
|
||||
"response_format": "json",
|
||||
"prompt": self.transcription_options.initial_prompt,
|
||||
}
|
||||
|
||||
|
|
@ -251,10 +271,17 @@ class RecordingTranscriber(QObject):
|
|||
else self.openai_client.audio.translations.create(**options)
|
||||
)
|
||||
|
||||
result = {"text": " ".join(
|
||||
[segment["text"] for segment in transcript.model_extra["segments"]])}
|
||||
if "segments" in transcript.model_extra:
|
||||
result = {"text": " ".join(
|
||||
[segment["text"] for segment in transcript.model_extra["segments"]])}
|
||||
else:
|
||||
result = {"text": transcript.text}
|
||||
|
||||
except Exception as e:
|
||||
result = {"text": f"Error: {str(e)}"}
|
||||
if self.is_running:
|
||||
result = {"text": f"Error: {str(e)}"}
|
||||
else:
|
||||
result = {"text": ""}
|
||||
|
||||
os.unlink(temp_filename)
|
||||
|
||||
|
|
@ -279,6 +306,12 @@ class RecordingTranscriber(QObject):
|
|||
|
||||
self.finished.emit()
|
||||
|
||||
# Cleanup
|
||||
if model:
|
||||
del model
|
||||
if torch.cuda.is_available():
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
@staticmethod
|
||||
def get_device_sample_rate(device_id: Optional[int]) -> int:
|
||||
"""Returns the sample rate to be used for recording. It uses the default sample rate
|
||||
|
|
@ -308,3 +341,65 @@ class RecordingTranscriber(QObject):
|
|||
|
||||
def stop_recording(self):
|
||||
self.is_running = False
|
||||
if self.process and self.process.poll() is None:
|
||||
self.process.terminate()
|
||||
self.process.wait()
|
||||
|
||||
def start_local_whisper_server(self):
|
||||
self.transcription.emit(_("Starting Whisper.cpp..."))
|
||||
|
||||
self.process = None
|
||||
command = [
|
||||
os.path.join(APP_BASE_DIR, "whisper-server.exe"),
|
||||
"--port", "3004",
|
||||
"--inference-path", "/audio/transcriptions",
|
||||
"--threads", str(os.getenv("BUZZ_WHISPERCPP_N_THREADS", (os.cpu_count() or 8)//2)),
|
||||
"--language", self.transcription_options.language,
|
||||
"--model", self.model_path,
|
||||
"--no-timestamps",
|
||||
"--no-context", # on Windows context causes duplications of last message
|
||||
]
|
||||
|
||||
logging.debug(f"Starting Whisper server with command: {' '.join(command)}")
|
||||
|
||||
self.process = subprocess.Popen(
|
||||
command,
|
||||
stdout=subprocess.DEVNULL, # For debug set to subprocess.PIPE, but it will freeze on Windows after ~30 seconds
|
||||
stderr=subprocess.PIPE,
|
||||
shell=False,
|
||||
creationflags=subprocess.CREATE_NO_WINDOW
|
||||
)
|
||||
|
||||
# Wait for server to start and load model
|
||||
time.sleep(10)
|
||||
|
||||
if self.process is not None and self.process.poll() is None:
|
||||
logging.debug(f"Whisper server started successfully.")
|
||||
logging.debug(f"Model: {self.model_path}")
|
||||
else:
|
||||
stderr_output = self.process.stderr.read().decode()
|
||||
logging.error(f"Whisper server failed to start. Error: {stderr_output}")
|
||||
|
||||
self.transcription.emit(_("Whisper server failed to start. Check logs for details."))
|
||||
|
||||
if "ErrorOutOfDeviceMemory" in stderr_output:
|
||||
message = _("Whisper server failed to start due to insufficient memory. "
|
||||
"Please try again with a smaller model. "
|
||||
"To force CPU mode use BUZZ_FORCE_CPU=TRUE environment variable.")
|
||||
logging.error(message)
|
||||
self.transcription.emit(message)
|
||||
|
||||
self.transcription.emit(_("Whisper server failed to start. Check logs for details."))
|
||||
return
|
||||
|
||||
self.openai_client = OpenAI(
|
||||
api_key="not-used",
|
||||
base_url="http://127.0.0.1:3004",
|
||||
timeout=10.0,
|
||||
max_retries=0
|
||||
)
|
||||
|
||||
def __del__(self):
|
||||
if self.process and self.process.poll() is None:
|
||||
self.process.terminate()
|
||||
self.process.wait()
|
||||
|
|
@ -21,7 +21,7 @@ class Task(enum.Enum):
|
|||
|
||||
|
||||
TASK_LABEL_TRANSLATIONS = {
|
||||
Task.TRANSLATE: _("Translate"),
|
||||
Task.TRANSLATE: _("Translate to English"),
|
||||
Task.TRANSCRIBE: _("Transcribe"),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import platform
|
||||
import os
|
||||
import sys
|
||||
import ctypes
|
||||
import logging
|
||||
from typing import Union, Any, List
|
||||
|
|
@ -7,21 +8,43 @@ from typing import Union, Any, List
|
|||
import numpy as np
|
||||
|
||||
from buzz import whisper_audio
|
||||
from buzz.model_loader import LOADED_WHISPER_CPP_BINARY
|
||||
from buzz.transcriber.transcriber import Segment, Task, TranscriptionOptions
|
||||
|
||||
if LOADED_WHISPER_CPP_BINARY:
|
||||
from buzz import whisper_cpp
|
||||
|
||||
LOADED_WHISPER_CPP_BINARY = False
|
||||
IS_VULKAN_SUPPORTED = False
|
||||
try:
|
||||
import vulkan
|
||||
|
||||
IS_COREML_SUPPORTED = False
|
||||
if platform.system() == "Darwin" and platform.machine() == "arm64":
|
||||
instance = vulkan.vkCreateInstance(vulkan.VkInstanceCreateInfo(), None)
|
||||
vulkan.vkDestroyInstance(instance, None)
|
||||
vulkan_version = vulkan.vkEnumerateInstanceVersion()
|
||||
major = (vulkan_version >> 22) & 0x3FF
|
||||
minor = (vulkan_version >> 12) & 0x3FF
|
||||
|
||||
logging.debug("Vulkan version = %s.%s", major, minor)
|
||||
|
||||
# On macOS, default whisper_cpp is compiled with CoreML (Apple Silicon) or Vulkan (Intel).
|
||||
# On Windows whisper-server.exe subprocess will be used
|
||||
if (platform.system() == "Linux") and ((major > 1) or (major == 1 and minor >= 2)):
|
||||
from buzz.whisper_cpp_vulkan import whisper_cpp_vulkan
|
||||
|
||||
IS_VULKAN_SUPPORTED = True
|
||||
LOADED_WHISPER_CPP_BINARY = True
|
||||
|
||||
except (ImportError, Exception) as e:
|
||||
logging.debug(f"Vulkan import error: {e}")
|
||||
|
||||
IS_VULKAN_SUPPORTED = False
|
||||
|
||||
if not IS_VULKAN_SUPPORTED:
|
||||
try:
|
||||
from buzz import whisper_cpp_coreml # noqa: F401
|
||||
from buzz.whisper_cpp import whisper_cpp # noqa: F401
|
||||
|
||||
IS_COREML_SUPPORTED = True
|
||||
except ImportError:
|
||||
logging.exception("")
|
||||
LOADED_WHISPER_CPP_BINARY = True
|
||||
|
||||
except ImportError as e:
|
||||
logging.exception("whisper_cpp load error: %s", e)
|
||||
|
||||
def append_segment(result, txt: bytes, start: int, end: int):
|
||||
if txt == b'':
|
||||
|
|
@ -45,14 +68,9 @@ def append_segment(result, txt: bytes, start: int, end: int):
|
|||
class WhisperCpp:
|
||||
def __init__(self, model: str) -> None:
|
||||
|
||||
self.is_coreml_supported = IS_COREML_SUPPORTED
|
||||
self.is_vulkan_supported = IS_VULKAN_SUPPORTED
|
||||
|
||||
if self.is_coreml_supported:
|
||||
coreml_model = model.replace(".bin", "-encoder.mlmodelc")
|
||||
if not os.path.exists(coreml_model):
|
||||
self.is_coreml_supported = False
|
||||
|
||||
logging.debug(f"WhisperCpp model {model}, (Core ML: {self.is_coreml_supported})")
|
||||
logging.debug(f"WhisperCpp model {model}, Vulkan: {self.is_vulkan_supported})")
|
||||
|
||||
self.instance = self.get_instance()
|
||||
self.ctx = self.instance.init_from_file(model)
|
||||
|
|
@ -120,8 +138,8 @@ class WhisperCpp:
|
|||
}
|
||||
|
||||
def get_instance(self):
|
||||
if self.is_coreml_supported:
|
||||
return WhisperCppCoreML()
|
||||
if self.is_vulkan_supported:
|
||||
return WhisperCppVulkan()
|
||||
return WhisperCppCpu()
|
||||
|
||||
def get_params(
|
||||
|
|
@ -130,7 +148,7 @@ class WhisperCpp:
|
|||
print_realtime=False,
|
||||
print_progress=False,
|
||||
):
|
||||
params = self.instance.full_default_params(whisper_cpp.WHISPER_SAMPLING_GREEDY)
|
||||
params = self.instance.full_default_params(0) # WHISPER_SAMPLING_GREEDY
|
||||
params.n_threads = int(os.getenv("BUZZ_WHISPERCPP_N_THREADS", (os.cpu_count() or 8)//2))
|
||||
params.print_realtime = print_realtime
|
||||
params.print_progress = print_progress
|
||||
|
|
@ -216,38 +234,41 @@ class WhisperCppCpu(WhisperCppInterface):
|
|||
def free(self, ctx):
|
||||
if ctx and whisper_cpp is not None:
|
||||
return whisper_cpp.whisper_free(ctx)
|
||||
return None
|
||||
|
||||
|
||||
class WhisperCppCoreML(WhisperCppInterface):
|
||||
class WhisperCppVulkan(WhisperCppInterface):
|
||||
def full_default_params(self, sampling: int):
|
||||
return whisper_cpp_coreml.whisper_full_default_params(sampling)
|
||||
return whisper_cpp_vulkan.whisper_full_default_params(sampling)
|
||||
|
||||
def get_string(self, string: str):
|
||||
return whisper_cpp_coreml.String(string.encode())
|
||||
return whisper_cpp_vulkan.String(string.encode())
|
||||
|
||||
def get_encoder_begin_callback(self, callback):
|
||||
return whisper_cpp_coreml.whisper_encoder_begin_callback(callback)
|
||||
return whisper_cpp_vulkan.whisper_encoder_begin_callback(callback)
|
||||
|
||||
def get_new_segment_callback(self, callback):
|
||||
return whisper_cpp_coreml.whisper_new_segment_callback(callback)
|
||||
return whisper_cpp_vulkan.whisper_new_segment_callback(callback)
|
||||
|
||||
def init_from_file(self, model: str):
|
||||
return whisper_cpp_coreml.whisper_init_from_file(model.encode())
|
||||
return whisper_cpp_vulkan.whisper_init_from_file(model.encode())
|
||||
|
||||
def full(self, ctx, params, audio, length):
|
||||
return whisper_cpp_coreml.whisper_full(ctx, params, audio, length)
|
||||
return whisper_cpp_vulkan.whisper_full(ctx, params, audio, length)
|
||||
|
||||
def full_n_segments(self, ctx):
|
||||
return whisper_cpp_coreml.whisper_full_n_segments(ctx)
|
||||
return whisper_cpp_vulkan.whisper_full_n_segments(ctx)
|
||||
|
||||
def full_get_segment_text(self, ctx, i):
|
||||
return whisper_cpp_coreml.whisper_full_get_segment_text(ctx, i)
|
||||
return whisper_cpp_vulkan.whisper_full_get_segment_text(ctx, i)
|
||||
|
||||
def full_get_segment_t0(self, ctx, i):
|
||||
return whisper_cpp_coreml.whisper_full_get_segment_t0(ctx, i)
|
||||
return whisper_cpp_vulkan.whisper_full_get_segment_t0(ctx, i)
|
||||
|
||||
def full_get_segment_t1(self, ctx, i):
|
||||
return whisper_cpp_coreml.whisper_full_get_segment_t1(ctx, i)
|
||||
return whisper_cpp_vulkan.whisper_full_get_segment_t1(ctx, i)
|
||||
|
||||
def free(self, ctx):
|
||||
return whisper_cpp_coreml.whisper_free(ctx)
|
||||
if ctx and whisper_cpp_vulkan is not None:
|
||||
return whisper_cpp_vulkan.whisper_free(ctx)
|
||||
return None
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import ctypes
|
||||
import logging
|
||||
import sys
|
||||
import os
|
||||
from typing import Optional, List
|
||||
|
||||
from PyQt6.QtCore import QObject
|
||||
|
|
@ -9,6 +10,7 @@ from buzz import whisper_audio
|
|||
from buzz.transcriber.file_transcriber import FileTranscriber
|
||||
from buzz.transcriber.transcriber import FileTranscriptionTask, Segment, Stopped
|
||||
from buzz.transcriber.whisper_cpp import WhisperCpp
|
||||
from buzz.transcriber.local_whisper_cpp_server_transcriber import LocalWhisperCppServerTranscriber
|
||||
|
||||
|
||||
class WhisperCppFileTranscriber(FileTranscriber):
|
||||
|
|
@ -25,7 +27,16 @@ class WhisperCppFileTranscriber(FileTranscriber):
|
|||
|
||||
self.transcription_options = task.transcription_options
|
||||
self.model_path = task.model_path
|
||||
self.model = WhisperCpp(model=self.model_path)
|
||||
self.transcriber = None
|
||||
self.model = None
|
||||
is_windows = sys.platform == "win32"
|
||||
force_cpu = os.getenv("BUZZ_FORCE_CPU", "false")
|
||||
|
||||
# As DLL mode on Windows is somewhat unreliable, will use local whisper-server
|
||||
if is_windows and force_cpu == "false":
|
||||
self.transcriber = LocalWhisperCppServerTranscriber(task, parent)
|
||||
else:
|
||||
self.model = WhisperCpp(model=self.model_path)
|
||||
self.state = self.State()
|
||||
|
||||
def transcribe(self) -> List[Segment]:
|
||||
|
|
@ -41,32 +52,42 @@ class WhisperCppFileTranscriber(FileTranscriber):
|
|||
self.transcription_options.word_level_timings,
|
||||
)
|
||||
|
||||
audio = whisper_audio.load_audio(self.transcription_task.file_path)
|
||||
self.duration_audio_ms = len(audio) * 1000 / whisper_audio.SAMPLE_RATE
|
||||
if self.model:
|
||||
audio = whisper_audio.load_audio(self.transcription_task.file_path)
|
||||
self.duration_audio_ms = len(audio) * 1000 / whisper_audio.SAMPLE_RATE
|
||||
|
||||
whisper_params = self.model.get_params(
|
||||
transcription_options=self.transcription_options
|
||||
)
|
||||
whisper_params.encoder_begin_callback_user_data = ctypes.c_void_p(
|
||||
id(self.state)
|
||||
)
|
||||
whisper_params.encoder_begin_callback = (
|
||||
self.model.get_instance().get_encoder_begin_callback(self.encoder_begin_callback)
|
||||
)
|
||||
whisper_params.new_segment_callback_user_data = ctypes.c_void_p(id(self.state))
|
||||
whisper_params.new_segment_callback = self.model.get_instance().get_new_segment_callback(
|
||||
self.new_segment_callback
|
||||
)
|
||||
whisper_params = self.model.get_params(
|
||||
transcription_options=self.transcription_options
|
||||
)
|
||||
whisper_params.encoder_begin_callback_user_data = ctypes.c_void_p(
|
||||
id(self.state)
|
||||
)
|
||||
whisper_params.encoder_begin_callback = (
|
||||
self.model.get_instance().get_encoder_begin_callback(self.encoder_begin_callback)
|
||||
)
|
||||
whisper_params.new_segment_callback_user_data = ctypes.c_void_p(id(self.state))
|
||||
whisper_params.new_segment_callback = self.model.get_instance().get_new_segment_callback(
|
||||
self.new_segment_callback
|
||||
)
|
||||
|
||||
result = self.model.transcribe(
|
||||
audio=self.transcription_task.file_path, params=whisper_params
|
||||
)
|
||||
result = self.model.transcribe(
|
||||
audio=self.transcription_task.file_path, params=whisper_params
|
||||
)
|
||||
|
||||
if not self.state.running:
|
||||
raise Stopped
|
||||
|
||||
self.state.running = False
|
||||
return result["segments"]
|
||||
|
||||
# On Windows we use the local whisper server
|
||||
if self.transcriber is not None:
|
||||
return self.transcriber.transcribe()
|
||||
|
||||
if not self.state.running:
|
||||
raise Stopped
|
||||
|
||||
self.state.running = False
|
||||
return result["segments"]
|
||||
return []
|
||||
|
||||
def new_segment_callback(self, ctx, _state, _n_new, user_data):
|
||||
n_segments = self.model.get_instance().full_n_segments(ctx)
|
||||
|
|
@ -89,3 +110,9 @@ class WhisperCppFileTranscriber(FileTranscriber):
|
|||
|
||||
def stop(self):
|
||||
self.state.running = False
|
||||
|
||||
if self.transcriber is not None:
|
||||
self.transcriber.stop()
|
||||
|
||||
def __del__(self):
|
||||
self.stop()
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ class WhisperFileTranscriber(FileTranscriber):
|
|||
self.segments = []
|
||||
self.started_process = False
|
||||
self.stopped = False
|
||||
self.recv_pipe = None
|
||||
self.send_pipe = None
|
||||
|
||||
def transcribe(self) -> List[Segment]:
|
||||
time_started = datetime.datetime.now()
|
||||
|
|
@ -56,24 +58,30 @@ class WhisperFileTranscriber(FileTranscriber):
|
|||
if torch.cuda.is_available():
|
||||
logging.debug(f"CUDA version detected: {torch.version.cuda}")
|
||||
|
||||
recv_pipe, send_pipe = multiprocessing.Pipe(duplex=False)
|
||||
self.recv_pipe, self.send_pipe = multiprocessing.Pipe(duplex=False)
|
||||
|
||||
self.current_process = multiprocessing.Process(
|
||||
target=self.transcribe_whisper, args=(send_pipe, self.transcription_task)
|
||||
target=self.transcribe_whisper, args=(self.send_pipe, self.transcription_task)
|
||||
)
|
||||
if not self.stopped:
|
||||
self.current_process.start()
|
||||
self.started_process = True
|
||||
|
||||
self.read_line_thread = Thread(target=self.read_line, args=(recv_pipe,))
|
||||
self.read_line_thread = Thread(target=self.read_line, args=(self.recv_pipe,))
|
||||
self.read_line_thread.start()
|
||||
|
||||
self.current_process.join()
|
||||
|
||||
if self.current_process.exitcode != 0:
|
||||
send_pipe.close()
|
||||
self.send_pipe.close()
|
||||
|
||||
self.read_line_thread.join()
|
||||
# Join read_line_thread with timeout to prevent hanging
|
||||
if self.read_line_thread and self.read_line_thread.is_alive():
|
||||
self.read_line_thread.join(timeout=3)
|
||||
if self.read_line_thread.is_alive():
|
||||
logging.warning("Read line thread didn't terminate gracefully in transcribe()")
|
||||
|
||||
self.started_process = False
|
||||
|
||||
logging.debug(
|
||||
"whisper process completed with code = %s, time taken = %s,"
|
||||
|
|
@ -153,6 +161,10 @@ class WhisperFileTranscriber(FileTranscriber):
|
|||
logging.debug("Unsupported CUDA version (<12), using CPU")
|
||||
device = "cpu"
|
||||
|
||||
if not torch.cuda.is_available():
|
||||
logging.debug("CUDA is not available, using CPU")
|
||||
device = "cpu"
|
||||
|
||||
if force_cpu != "false":
|
||||
device = "cpu"
|
||||
|
||||
|
|
@ -168,7 +180,8 @@ class WhisperFileTranscriber(FileTranscriber):
|
|||
audio=task.file_path,
|
||||
language=task.transcription_options.language,
|
||||
task=task.transcription_options.task.value,
|
||||
temperature=task.transcription_options.temperature,
|
||||
# Prevent crash on Windows https://github.com/SYSTRAN/faster-whisper/issues/71#issuecomment-1526263764
|
||||
temperature = 0 if platform.system() == "Windows" else task.transcription_options.temperature,
|
||||
initial_prompt=task.transcription_options.initial_prompt,
|
||||
word_timestamps=task.transcription_options.word_level_timings,
|
||||
no_speech_threshold=0.4,
|
||||
|
|
@ -249,8 +262,30 @@ class WhisperFileTranscriber(FileTranscriber):
|
|||
|
||||
def stop(self):
|
||||
self.stopped = True
|
||||
|
||||
if self.started_process:
|
||||
self.current_process.terminate()
|
||||
# Use timeout to avoid hanging indefinitely
|
||||
self.current_process.join(timeout=5)
|
||||
if self.current_process.is_alive():
|
||||
logging.warning("Process didn't terminate gracefully, force killing")
|
||||
self.current_process.kill()
|
||||
self.current_process.join(timeout=2)
|
||||
|
||||
# Close pipes to unblock the read_line thread
|
||||
try:
|
||||
if hasattr(self, 'send_pipe'):
|
||||
self.send_pipe.close()
|
||||
if hasattr(self, 'recv_pipe'):
|
||||
self.recv_pipe.close()
|
||||
except Exception as e:
|
||||
logging.debug(f"Error closing pipes: {e}")
|
||||
|
||||
# Join read_line_thread with timeout to prevent hanging
|
||||
if self.read_line_thread and self.read_line_thread.is_alive():
|
||||
self.read_line_thread.join(timeout=3)
|
||||
if self.read_line_thread.is_alive():
|
||||
logging.warning("Read line thread didn't terminate gracefully")
|
||||
|
||||
def read_line(self, pipe: Connection):
|
||||
while True:
|
||||
|
|
@ -260,7 +295,10 @@ class WhisperFileTranscriber(FileTranscriber):
|
|||
# Uncomment to debug
|
||||
# print(f"*** DEBUG ***: {line}")
|
||||
|
||||
except EOFError: # Connection closed
|
||||
except (EOFError, BrokenPipeError, ConnectionResetError): # Connection closed or broken
|
||||
break
|
||||
except Exception as e:
|
||||
logging.debug(f"Error reading from pipe: {e}")
|
||||
break
|
||||
|
||||
if line == self.READ_LINE_THREAD_STOP_TOKEN:
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ class PipelineWithProgress(AutomaticSpeechRecognitionPipeline): # pragma: no co
|
|||
inputs_len = inputs.shape[0]
|
||||
step = chunk_len - stride_left - stride_right
|
||||
for chunk_start_idx in range(0, inputs_len, step):
|
||||
# Print progress to stderr
|
||||
|
||||
# Buzz will print progress to stderr
|
||||
progress = int((chunk_start_idx / inputs_len) * 100)
|
||||
sys.stderr.write(f"{progress}%\n")
|
||||
|
||||
|
|
@ -27,8 +28,7 @@ class PipelineWithProgress(AutomaticSpeechRecognitionPipeline): # pragma: no co
|
|||
if dtype is not None:
|
||||
processed = processed.to(dtype=dtype)
|
||||
_stride_left = 0 if chunk_start_idx == 0 else stride_left
|
||||
# all right strides must be full, otherwise it is the last item
|
||||
is_last = chunk_end_idx > inputs_len if stride_right > 0 else chunk_end_idx >= inputs_len
|
||||
is_last = chunk_end_idx >= inputs_len
|
||||
_stride_right = 0 if is_last else stride_right
|
||||
|
||||
chunk_len = chunk.shape[0]
|
||||
|
|
@ -98,7 +98,7 @@ class PipelineWithProgress(AutomaticSpeechRecognitionPipeline): # pragma: no co
|
|||
# of the original length in the stride so we can cut properly.
|
||||
stride = (inputs.shape[0], int(round(stride[0] * ratio)), int(round(stride[1] * ratio)))
|
||||
if not isinstance(inputs, np.ndarray):
|
||||
raise ValueError(f"We expect a numpy ndarray as input, got `{type(inputs)}`")
|
||||
raise TypeError(f"We expect a numpy ndarray as input, got `{type(inputs)}`")
|
||||
if len(inputs.shape) != 1:
|
||||
raise ValueError("We expect a single channel audio input for AutomaticSpeechRecognitionPipeline")
|
||||
|
||||
|
|
@ -109,7 +109,7 @@ class PipelineWithProgress(AutomaticSpeechRecognitionPipeline): # pragma: no co
|
|||
if isinstance(stride_length_s, (int, float)):
|
||||
stride_length_s = [stride_length_s, stride_length_s]
|
||||
|
||||
# XXX: Carefuly, this variable will not exist in `seq2seq` setting.
|
||||
# XXX: Carefully, this variable will not exist in `seq2seq` setting.
|
||||
# Currently chunking is not possible at this level for `seq2seq` so
|
||||
# it's ok.
|
||||
align_to = getattr(self.model.config, "inputs_to_logits_ratio", 1)
|
||||
|
|
@ -120,11 +120,11 @@ class PipelineWithProgress(AutomaticSpeechRecognitionPipeline): # pragma: no co
|
|||
if chunk_len < stride_left + stride_right:
|
||||
raise ValueError("Chunk length must be superior to stride length")
|
||||
|
||||
# Will use our custom chunk_iter with progress
|
||||
# Buzz use our custom chunk_iter with progress
|
||||
for item in self.chunk_iter(
|
||||
inputs, self.feature_extractor, chunk_len, stride_left, stride_right, self.torch_dtype
|
||||
):
|
||||
yield item
|
||||
yield {**item, **extra}
|
||||
else:
|
||||
if self.type == "seq2seq_whisper" and inputs.shape[0] > self.feature_extractor.n_samples:
|
||||
processed = self.feature_extractor(
|
||||
|
|
@ -133,12 +133,25 @@ class PipelineWithProgress(AutomaticSpeechRecognitionPipeline): # pragma: no co
|
|||
truncation=False,
|
||||
padding="longest",
|
||||
return_tensors="pt",
|
||||
return_attention_mask=True,
|
||||
)
|
||||
else:
|
||||
processed = self.feature_extractor(
|
||||
inputs, sampling_rate=self.feature_extractor.sampling_rate, return_tensors="pt"
|
||||
)
|
||||
|
||||
if self.type == "seq2seq_whisper" and stride is None:
|
||||
processed = self.feature_extractor(
|
||||
inputs,
|
||||
sampling_rate=self.feature_extractor.sampling_rate,
|
||||
return_tensors="pt",
|
||||
return_token_timestamps=True,
|
||||
return_attention_mask=True,
|
||||
)
|
||||
extra["num_frames"] = processed.pop("num_frames")
|
||||
else:
|
||||
processed = self.feature_extractor(
|
||||
inputs,
|
||||
sampling_rate=self.feature_extractor.sampling_rate,
|
||||
return_tensors="pt",
|
||||
return_attention_mask=True,
|
||||
)
|
||||
if self.torch_dtype is not None:
|
||||
processed = processed.to(dtype=self.torch_dtype)
|
||||
if stride is not None:
|
||||
|
|
@ -193,7 +206,8 @@ class TransformersWhisper:
|
|||
chunk_length_s=30 if word_timestamps else None,
|
||||
torch_dtype=torch_dtype,
|
||||
device=device,
|
||||
)
|
||||
ignore_warning=True # Ignore warning about chunk_length_s being experimental for seq2seq models
|
||||
)
|
||||
|
||||
transcript = pipe(
|
||||
audio,
|
||||
|
|
|
|||
|
|
@ -430,6 +430,8 @@ class MainWindow(QMainWindow):
|
|||
if self.transcription_viewer_widget is not None:
|
||||
self.transcription_viewer_widget.close()
|
||||
|
||||
logging.debug("Closing MainWindow")
|
||||
|
||||
super().closeEvent(event)
|
||||
|
||||
def save_geometry(self):
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ class GeneralPreferencesWidget(QWidget):
|
|||
def on_click_test_openai_api_key_button(self):
|
||||
self.test_openai_api_key_button.setEnabled(False)
|
||||
|
||||
job = TestOpenAIApiKeyJob(api_key=self.openai_api_key)
|
||||
job = ValidateOpenAIApiKeyJob(api_key=self.openai_api_key)
|
||||
job.signals.success.connect(self.on_test_openai_api_key_success)
|
||||
job.signals.failed.connect(self.on_test_openai_api_key_failure)
|
||||
job.setAutoDelete(True)
|
||||
|
|
@ -265,7 +265,7 @@ class GeneralPreferencesWidget(QWidget):
|
|||
def on_recording_transcriber_mode_changed(self, value):
|
||||
self.settings.set_value(Settings.Key.RECORDING_TRANSCRIBER_MODE, value)
|
||||
|
||||
class TestOpenAIApiKeyJob(QRunnable):
|
||||
class ValidateOpenAIApiKeyJob(QRunnable):
|
||||
class Signals(QObject):
|
||||
success = pyqtSignal()
|
||||
failed = pyqtSignal(str)
|
||||
|
|
|
|||
|
|
@ -306,7 +306,9 @@ class RecordingTranscriberWidget(QWidget):
|
|||
self.reset_recording_controls()
|
||||
self.model_loader = None
|
||||
|
||||
if model_path == "":
|
||||
if model_path == "" and self.transcription_options.model.model_type != ModelType.OPEN_AI_WHISPER_API:
|
||||
self.on_transcriber_error("")
|
||||
logging.error("Model path is empty, cannot start recording.")
|
||||
return
|
||||
|
||||
self.transcription_thread = QThread()
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
DLLs extracted from whisper.cpp release v1.6.2
|
||||
https://github.com/ggerganov/whisper.cpp/actions/runs/9251036100
|
||||
https://github.com/ggerganov/whisper.cpp/commit/c7b6988678779901d02ceba1a8212d2c9908956e
|
||||
|
||||
whisper.cpp submodule commit version should match the commit where the
|
||||
whisper.dll are extracted from to ensure compatibility.
|
||||
Binary file not shown.
Binary file not shown.
33
poetry.lock
generated
33
poetry.lock
generated
|
|
@ -1181,7 +1181,6 @@ description = "Fast transfer of large files with the Hugging Face Hub."
|
|||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\""
|
||||
files = [
|
||||
{file = "hf_xet-1.1.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f52c2fa3635b8c37c7764d8796dfa72706cc4eded19d638331161e82b0792e23"},
|
||||
{file = "hf_xet-1.1.5-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:9fa6e3ee5d61912c4a113e0708eaaef987047616465ac7aa30f7121a48fc1af8"},
|
||||
|
|
@ -3427,6 +3426,21 @@ pytest = "*"
|
|||
dev = ["pre-commit", "tox"]
|
||||
doc = ["sphinx", "sphinx-rtd-theme"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-timeout"
|
||||
version = "2.4.0"
|
||||
description = "pytest plugin to abort hanging tests"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2"},
|
||||
{file = "pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pytest = ">=7.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "pytest-xvfb"
|
||||
version = "2.0.0"
|
||||
|
|
@ -4790,6 +4804,21 @@ platformdirs = ">=3.9.1,<5"
|
|||
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
|
||||
test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""]
|
||||
|
||||
[[package]]
|
||||
name = "vulkan"
|
||||
version = "1.3.275.1"
|
||||
description = "Ultimate Python binding for Vulkan API"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "vulkan-1.3.275.1-py3-none-any.whl", hash = "sha256:e1e0ddf57d3a7d19f79ebf1e192b20dbd378172b027cad4f495d961b51409586"},
|
||||
{file = "vulkan-1.3.275.1.tar.gz", hash = "sha256:5cf7961cb4a4e60d063eb815147c2289244575b75a4512089cc6e8a959b996d2"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cffi = ">=1.10"
|
||||
|
||||
[[package]]
|
||||
name = "wrapt"
|
||||
version = "1.17.2"
|
||||
|
|
@ -4925,4 +4954,4 @@ type = ["pytest-mypy"]
|
|||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.9,<3.13"
|
||||
content-hash = "6fd00715df18f91cd02b7524a34897f408f686eab80fd77b2d8eefd4f2ce99ca"
|
||||
content-hash = "068de55b5f53b353c9ac9880a1d420f4187decbd547f8b2b871f3dc790a977c8"
|
||||
|
|
|
|||
|
|
@ -20,12 +20,9 @@ packages = [
|
|||
{ include = "buzz" },
|
||||
]
|
||||
include = [
|
||||
{ path = "buzz/libwhisper.*", format = ["sdist", "wheel"] },
|
||||
{ path = "buzz/libwhisper-coreml.*", format = ["sdist", "wheel"] },
|
||||
{ path = "buzz/*.dll", format = ["sdist", "wheel"] },
|
||||
{ path = "buzz/whisper_cpp.py", format = ["sdist", "wheel"] },
|
||||
{ path = "buzz/whisper_cpp/*", format = ["sdist", "wheel"] },
|
||||
{ path = "buzz/whisper_cpp_vulkan/*", format = ["sdist", "wheel"] },
|
||||
{ path = "buzz/locale/*/LC_MESSAGES/buzz.mo", format = ["sdist", "wheel"] },
|
||||
{ path = "buzz/dll_backup/*", format = ["sdist", "wheel"] },
|
||||
]
|
||||
|
||||
[[tool.poetry.source]]
|
||||
|
|
@ -80,6 +77,8 @@ urllib3 = "^2.3.0"
|
|||
demucs = {url = "https://github.com/raivisdejus/demucs/releases/download/4.1.0a3/demucs-4.1.0a3-py3-none-any.whl"}
|
||||
posthog = "^3.23.0"
|
||||
onnxruntime = "^1.18.0"
|
||||
vulkan = "^1.3.275.1"
|
||||
hf-xet = "^1.1.5"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
autopep8 = "^1.7.0"
|
||||
|
|
@ -91,6 +90,7 @@ pytest-cov = "^4.0.0"
|
|||
pytest-qt = "^4.1.0"
|
||||
pytest-xvfb = "^2.0.0"
|
||||
pytest-mock = "^3.12.0"
|
||||
pytest-timeout = "^2.4.0"
|
||||
pylint = "^2.15.5"
|
||||
pre-commit = "^2.20.0"
|
||||
pytest-benchmark = "^4.0.0"
|
||||
|
|
|
|||
|
|
@ -2,5 +2,8 @@
|
|||
log_cli = 1
|
||||
log_cli_level = DEBUG
|
||||
qt_api=pyqt6
|
||||
log_format = %(asctime)s %(levelname)s %(message)s
|
||||
log_format = %(asctime)s %(levelname)s %(module)s::%(funcName)s %(message)s
|
||||
log_date_format = %Y-%m-%d %H:%M:%S
|
||||
addopts = -x
|
||||
markers =
|
||||
timeout: set a timeout on a test function.
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
# Development notes:
|
||||
# - To build the snap run `snapcraft clean` and `snapcraft --debug --verbosity verbose`
|
||||
# - To build the snap run `snapcraft clean` and `snapcraft --verbose`
|
||||
# - To install local snap `snap install ./buzz_*.snap --dangerous`
|
||||
name: buzz
|
||||
base: core22
|
||||
|
|
@ -83,7 +83,12 @@ parts:
|
|||
plugin: python
|
||||
source: .
|
||||
build-packages:
|
||||
- wget
|
||||
- portaudio19-dev
|
||||
- qt6-declarative-dev
|
||||
- qt6-multimedia-dev
|
||||
- libvulkan-dev
|
||||
- cmake
|
||||
stage-packages:
|
||||
# Audio
|
||||
- ffmpeg
|
||||
|
|
@ -100,8 +105,6 @@ parts:
|
|||
- libgstreamer1.0-0
|
||||
- libgstreamer-plugins-base1.0-0
|
||||
- libgstreamer-plugins-good1.0-0
|
||||
- qt6-declarative-dev
|
||||
- qt6-multimedia-dev
|
||||
# Display
|
||||
- libxkbcommon-x11-0
|
||||
- libxcb-icccm4
|
||||
|
|
@ -114,7 +117,20 @@ parts:
|
|||
- libxcb-cursor0
|
||||
# GPU
|
||||
- libglu1-mesa
|
||||
- libvulkan1
|
||||
- mesa-vulkan-drivers
|
||||
python-packages:
|
||||
- ctypesgen
|
||||
- setuptools
|
||||
- cmake
|
||||
- polib
|
||||
override-build: |
|
||||
# https://vulkan.lunarg.com/doc/view/latest/linux/getting_started_ubuntu.html
|
||||
wget -qO- https://packages.lunarg.com/lunarg-signing-key-pub.asc | tee /etc/apt/trusted.gpg.d/lunarg.asc
|
||||
wget -qO /etc/apt/sources.list.d/lunarg-vulkan-jammy.list http://packages.lunarg.com/vulkan/lunarg-vulkan-jammy.list
|
||||
apt update
|
||||
apt install -y vulkan-sdk
|
||||
|
||||
# Clear cache to save space on CI
|
||||
apt clean
|
||||
|
||||
|
|
@ -122,39 +138,48 @@ parts:
|
|||
pip install poetry
|
||||
make translation_mo
|
||||
pip install .
|
||||
pip uninstall -y ctranslate2
|
||||
pip uninstall -y torch torchaudio nvidia-cublas-cu12 nvidia-cuda-cupti-cu12 nvidia-cuda-nvrtc-cu12 nvidia-cuda-runtime-cu12 nvidia-cudnn-cu12 nvidia-cufft-cu12 nvidia-cufile-cu12 nvidia-curand-cu12 nvidia-cusolver-cu12 nvidia-cusparse-cu12 nvidia-cusparselt-cu12 nvidia-nccl-cu12 nvidia-nvjitlink-cu12 nvidia-nvtx-cu12
|
||||
pip cache purge
|
||||
|
||||
pip install https://github.com/raivisdejus/CTranslate2-no-execstack/releases/download/v4.2.1/ctranslate2-4.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
|
||||
pip install -U torch==2.7.1+cu128 torchaudio==2.7.1+cu128 --index-url https://download.pytorch.org/whl/cu128
|
||||
pip cache purge
|
||||
pip install nvidia-cublas-cu12==12.8.3.14 nvidia-cuda-cupti-cu12==12.8.57 nvidia-cuda-nvrtc-cu12==12.8.61 --extra-index-url https://pypi.ngc.nvidia.com
|
||||
pip cache purge
|
||||
pip install nvidia-cuda-runtime-cu12==12.8.57 nvidia-cudnn-cu12==9.7.1.26 nvidia-cufft-cu12==11.3.3.41 --extra-index-url https://pypi.ngc.nvidia.com
|
||||
pip cache purge
|
||||
pip install nvidia-curand-cu12==10.3.9.55 nvidia-cusolver-cu12==11.7.2.55 nvidia-cusparse-cu12==12.5.4.2 --extra-index-url https://pypi.ngc.nvidia.com
|
||||
pip install nvidia-curand-cu12==10.3.9.55 nvidia-cusolver-cu12==11.7.2.55 nvidia-cusparse-cu12==12.5.7.53 --extra-index-url https://pypi.ngc.nvidia.com
|
||||
pip cache purge
|
||||
pip install nvidia-cusparselt-cu12==0.6.3 nvidia-nvjitlink-cu12==12.8.61 nvidia-nvtx-cu12==12.8.55 --extra-index-url https://pypi.ngc.nvidia.com
|
||||
pip cache purge
|
||||
|
||||
python3 build.py
|
||||
mkdir $CRAFT_PART_INSTALL/buzz
|
||||
cp $CRAFT_PART_BUILD/buzz/whisper_cpp.py $CRAFT_PART_INSTALL/buzz/
|
||||
cp $CRAFT_PART_BUILD/buzz/libwhisper.so $CRAFT_PART_INSTALL/buzz/
|
||||
cp -r $CRAFT_PART_BUILD/buzz/whisper_cpp $CRAFT_PART_INSTALL/buzz/
|
||||
cp -r $CRAFT_PART_BUILD/buzz/whisper_cpp_vulkan $CRAFT_PART_INSTALL/buzz/
|
||||
|
||||
# Create desktop file
|
||||
mkdir -p $CRAFT_PART_INSTALL/usr/share/applications
|
||||
cp $CRAFT_PART_BUILD/buzz.desktop $CRAFT_PART_INSTALL/usr/share/applications/
|
||||
after: [ desktop-qt5 ]
|
||||
|
||||
desktop-file:
|
||||
graphics-core22:
|
||||
after: [ buzz ]
|
||||
source: https://github.com/canonical/gpu-snap.git
|
||||
plugin: dump
|
||||
source: .
|
||||
organize:
|
||||
buzz.desktop: usr/share/applications/buzz.desktop
|
||||
# To prevent error of the same file having different permissions
|
||||
override-prime: |
|
||||
craftctl default
|
||||
${CRAFT_PART_SRC}/bin/graphics-core22-cleanup mesa-core22 nvidia-core22
|
||||
stage:
|
||||
- -buzz/whisper_cpp.py
|
||||
- ./*
|
||||
- -venv/**
|
||||
prime:
|
||||
- ./*
|
||||
- -venv/**
|
||||
- bin/graphics-core22-wrapper
|
||||
|
||||
apps:
|
||||
buzz:
|
||||
command-chain:
|
||||
- bin/graphics-core22-wrapper
|
||||
command: bin/desktop-launch python3 -m buzz
|
||||
desktop: usr/share/applications/buzz.desktop
|
||||
environment:
|
||||
|
|
@ -202,7 +227,19 @@ plugs:
|
|||
interface: content
|
||||
target: $SNAP/share/sounds
|
||||
default-provider: gtk-common-themes
|
||||
graphics-core22:
|
||||
interface: content
|
||||
target: $SNAP/graphics
|
||||
default-provider: mesa-core22
|
||||
|
||||
layout:
|
||||
/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/alsa-lib:
|
||||
bind: $SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/alsa-lib
|
||||
/usr/share/libdrm:
|
||||
bind: $SNAP/graphics/libdrm
|
||||
/usr/share/drirc.d:
|
||||
symlink: $SNAP/graphics/drirc.d
|
||||
/usr/share/X11/XErrorDB:
|
||||
symlink: $SNAP/graphics/X11/XErrorDB
|
||||
/usr/share/X11/locale:
|
||||
symlink: $SNAP/graphics/X11/locale
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@ import pytest
|
|||
import unittest.mock
|
||||
from PyQt6.QtCore import QCoreApplication, QThread
|
||||
from buzz.file_transcriber_queue_worker import FileTranscriberQueueWorker
|
||||
from buzz.model_loader import ModelType, TranscriptionModel
|
||||
from buzz.model_loader import ModelType, TranscriptionModel, WhisperModelSize
|
||||
from buzz.transcriber.transcriber import FileTranscriptionTask, TranscriptionOptions, FileTranscriptionOptions
|
||||
from buzz.transcriber.whisper_cpp_file_transcriber import WhisperCppFileTranscriber
|
||||
from tests.audio import test_multibyte_utf8_audio_path
|
||||
import time
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def qapp():
|
||||
app = QCoreApplication.instance()
|
||||
|
|
@ -16,6 +17,7 @@ def qapp():
|
|||
yield app
|
||||
app.quit()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def worker(qapp):
|
||||
worker = FileTranscriberQueueWorker()
|
||||
|
|
@ -28,29 +30,27 @@ def worker(qapp):
|
|||
thread.quit()
|
||||
thread.wait()
|
||||
|
||||
@pytest.fixture
|
||||
def audio_file():
|
||||
# Use a small, existing audio file for testing
|
||||
return test_multibyte_utf8_audio_path
|
||||
|
||||
def test_transcription_with_whisper_cpp_tiny_no_speech_extraction(worker, audio_file):
|
||||
def test_transcription_with_whisper_cpp_tiny_no_speech_extraction(worker):
|
||||
options = TranscriptionOptions(
|
||||
model=TranscriptionModel(model_type=ModelType.WHISPER_CPP, whisper_model_size="tiny"),
|
||||
model=TranscriptionModel(model_type=ModelType.WHISPER_CPP, whisper_model_size=WhisperModelSize.TINY),
|
||||
extract_speech=False
|
||||
)
|
||||
task = FileTranscriptionTask(file_path=str(audio_file), transcription_options=options, file_transcription_options=FileTranscriptionOptions(), model_path="mock_path")
|
||||
task = FileTranscriptionTask(file_path=str(test_multibyte_utf8_audio_path), transcription_options=options,
|
||||
file_transcription_options=FileTranscriptionOptions(), model_path="mock_path")
|
||||
|
||||
with unittest.mock.patch.object(WhisperCppFileTranscriber, 'run') as mock_run:
|
||||
with unittest.mock.patch('buzz.transcriber.whisper_cpp_file_transcriber.LocalWhisperCppServerTranscriber'), \
|
||||
unittest.mock.patch.object(WhisperCppFileTranscriber, 'run') as mock_run:
|
||||
mock_run.side_effect = lambda: worker.current_transcriber.completed.emit([
|
||||
{"start": 0, "end": 1000, "text": "Test transcription."}
|
||||
])
|
||||
|
||||
|
||||
completed_spy = unittest.mock.Mock()
|
||||
worker.task_completed.connect(completed_spy)
|
||||
worker.add_task(task)
|
||||
|
||||
# Wait for the signal to be emitted
|
||||
timeout = 5 # seconds
|
||||
timeout = 10 # seconds
|
||||
start_time = time.time()
|
||||
while not completed_spy.called and (time.time() - start_time) < timeout:
|
||||
QCoreApplication.processEvents()
|
||||
|
|
@ -62,17 +62,19 @@ def test_transcription_with_whisper_cpp_tiny_no_speech_extraction(worker, audio_
|
|||
assert len(args[1]) > 0
|
||||
assert args[1][0]["text"] == "Test transcription."
|
||||
|
||||
def test_transcription_with_whisper_cpp_tiny_with_speech_extraction(worker, audio_file):
|
||||
|
||||
def test_transcription_with_whisper_cpp_tiny_with_speech_extraction(worker):
|
||||
options = TranscriptionOptions(
|
||||
model=TranscriptionModel(model_type=ModelType.WHISPER_CPP, whisper_model_size="tiny"),
|
||||
model=TranscriptionModel(model_type=ModelType.WHISPER_CPP, whisper_model_size=WhisperModelSize.TINY),
|
||||
extract_speech=True
|
||||
)
|
||||
task = FileTranscriptionTask(file_path=str(audio_file), transcription_options=options, file_transcription_options=FileTranscriptionOptions(), model_path="mock_path")
|
||||
|
||||
with unittest.mock.patch('demucs.api.Separator') as mock_separator_class, \
|
||||
unittest.mock.patch('demucs.api.save_audio') as mock_save_audio, \
|
||||
unittest.mock.patch.object(WhisperCppFileTranscriber, 'run') as mock_run:
|
||||
task = FileTranscriptionTask(file_path=str(test_multibyte_utf8_audio_path), transcription_options=options,
|
||||
file_transcription_options=FileTranscriptionOptions(), model_path="mock_path")
|
||||
|
||||
with unittest.mock.patch('buzz.transcriber.whisper_cpp_file_transcriber.LocalWhisperCppServerTranscriber'), \
|
||||
unittest.mock.patch('demucs.api.Separator') as mock_separator_class, \
|
||||
unittest.mock.patch('demucs.api.save_audio') as mock_save_audio, \
|
||||
unittest.mock.patch.object(WhisperCppFileTranscriber, 'run') as mock_run:
|
||||
# Mock demucs.api.Separator and save_audio
|
||||
mock_separator_instance = unittest.mock.Mock()
|
||||
mock_separator_instance.separate_audio_file.return_value = (None, {"vocals": "mock_vocals_data"})
|
||||
|
|
@ -88,7 +90,7 @@ def test_transcription_with_whisper_cpp_tiny_with_speech_extraction(worker, audi
|
|||
worker.add_task(task)
|
||||
|
||||
# Wait for the signal to be emitted
|
||||
timeout = 5 # seconds
|
||||
timeout = 10 # seconds
|
||||
start_time = time.time()
|
||||
while not completed_spy.called and (time.time() - start_time) < timeout:
|
||||
QCoreApplication.processEvents()
|
||||
|
|
@ -100,4 +102,4 @@ def test_transcription_with_whisper_cpp_tiny_with_speech_extraction(worker, audi
|
|||
args, kwargs = completed_spy.call_args
|
||||
assert args[0] == task
|
||||
assert len(args[1]) > 0
|
||||
assert args[1][0]["text"] == "Test transcription with speech extraction."
|
||||
assert args[1][0]["text"] == "Test transcription with speech extraction."
|
||||
|
|
@ -1,7 +1,12 @@
|
|||
import os
|
||||
import sys
|
||||
import time
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from PyQt6.QtCore import QThread
|
||||
|
||||
from buzz.locale import _
|
||||
from buzz.assets import APP_BASE_DIR
|
||||
from buzz.model_loader import TranscriptionModel, ModelType, WhisperModelSize
|
||||
from buzz.transcriber.recording_transcriber import RecordingTranscriber
|
||||
from buzz.transcriber.transcriber import TranscriptionOptions, Task
|
||||
|
|
@ -21,6 +26,10 @@ class TestRecordingTranscriber:
|
|||
|
||||
model_path = get_model_path(transcription_model)
|
||||
|
||||
model_exe_path = os.path.join(APP_BASE_DIR, "whisper-server.exe")
|
||||
if sys.platform.startswith("win"):
|
||||
assert os.path.exists(model_exe_path), f"{model_exe_path} does not exist"
|
||||
|
||||
transcriber = RecordingTranscriber(
|
||||
transcription_options=TranscriptionOptions(
|
||||
model=transcription_model, language="fr", task=Task.TRANSCRIBE
|
||||
|
|
@ -34,17 +43,24 @@ class TestRecordingTranscriber:
|
|||
|
||||
thread.started.connect(transcriber.start)
|
||||
|
||||
mock_transcription = Mock()
|
||||
transcriber.transcription.connect(mock_transcription)
|
||||
transcriptions = []
|
||||
|
||||
with qtbot.wait_signal(transcriber.transcription, timeout=60 * 1000):
|
||||
thread.start()
|
||||
def on_transcription(text):
|
||||
transcriptions.append(text)
|
||||
|
||||
transcriber.stop_recording()
|
||||
transcriber.transcription.connect(on_transcription)
|
||||
|
||||
text = mock_transcription.call_args[0][0]
|
||||
assert "Bienvenue dans Passe" in text
|
||||
thread.start()
|
||||
qtbot.waitUntil(lambda: len(transcriptions) == 3, timeout=60_000)
|
||||
|
||||
# any string in any transcription
|
||||
strings_to_check = [_("Starting Whisper.cpp..."), "Bienvenue dans Passe"]
|
||||
assert any(s in t for s in strings_to_check for t in transcriptions)
|
||||
|
||||
# Wait for the thread to finish
|
||||
transcriber.stop_recording()
|
||||
time.sleep(10)
|
||||
|
||||
thread.quit()
|
||||
thread.wait()
|
||||
time.sleep(3)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
import os
|
||||
import time
|
||||
from typing import List
|
||||
from unittest.mock import Mock
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from pytestqt.qtbot import QtBot
|
||||
|
|
@ -31,8 +34,9 @@ class TestWhisperCppFileTranscriber:
|
|||
def test_transcribe(
|
||||
self, qtbot: QtBot, word_level_timings: bool, expected_segments: List[Segment]
|
||||
):
|
||||
os.environ["BUZZ_FORCE_CPU"] = "true"
|
||||
file_transcription_options = FileTranscriptionOptions(
|
||||
file_paths=[test_audio_path]
|
||||
file_paths=[str(Path(test_audio_path).resolve())]
|
||||
)
|
||||
transcription_options = TranscriptionOptions(
|
||||
language="fr",
|
||||
|
|
@ -47,7 +51,7 @@ class TestWhisperCppFileTranscriber:
|
|||
model_path = get_model_path(transcription_options.model)
|
||||
transcriber = WhisperCppFileTranscriber(
|
||||
task=FileTranscriptionTask(
|
||||
file_path=test_audio_path,
|
||||
file_path=str(Path(test_audio_path).resolve()),
|
||||
transcription_options=transcription_options,
|
||||
file_transcription_options=file_transcription_options,
|
||||
model_path=model_path,
|
||||
|
|
@ -76,6 +80,9 @@ class TestWhisperCppFileTranscriber:
|
|||
assert expected_segment.end == segments[i].end
|
||||
assert expected_segment.text in segments[i].text
|
||||
|
||||
transcriber.stop()
|
||||
time.sleep(3)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"word_level_timings,expected_segments",
|
||||
[
|
||||
|
|
@ -91,8 +98,9 @@ class TestWhisperCppFileTranscriber:
|
|||
def test_transcribe_latvian(
|
||||
self, qtbot: QtBot, word_level_timings: bool, expected_segments: List[Segment]
|
||||
):
|
||||
os.environ["BUZZ_FORCE_CPU"] = "true"
|
||||
file_transcription_options = FileTranscriptionOptions(
|
||||
file_paths=[test_multibyte_utf8_audio_path]
|
||||
file_paths=[str(Path(test_multibyte_utf8_audio_path).resolve())]
|
||||
)
|
||||
transcription_options = TranscriptionOptions(
|
||||
language="lv",
|
||||
|
|
@ -107,7 +115,7 @@ class TestWhisperCppFileTranscriber:
|
|||
model_path = get_model_path(transcription_options.model)
|
||||
transcriber = WhisperCppFileTranscriber(
|
||||
task=FileTranscriptionTask(
|
||||
file_path=test_multibyte_utf8_audio_path,
|
||||
file_path=str(Path(test_multibyte_utf8_audio_path).resolve()),
|
||||
transcription_options=transcription_options,
|
||||
file_transcription_options=file_transcription_options,
|
||||
model_path=model_path,
|
||||
|
|
@ -135,3 +143,6 @@ class TestWhisperCppFileTranscriber:
|
|||
assert expected_segment.start == segments[i].start
|
||||
assert expected_segment.end == segments[i].end
|
||||
assert expected_segment.text in segments[i].text
|
||||
|
||||
transcriber.stop()
|
||||
time.sleep(3)
|
||||
|
|
@ -230,6 +230,9 @@ class TestWhisperFileTranscriber:
|
|||
assert len(segments[i].text) > 0
|
||||
logging.debug(f"{segments[i].start} {segments[i].end} {segments[i].text}")
|
||||
|
||||
transcriber.stop()
|
||||
time.sleep(3)
|
||||
|
||||
def test_transcribe_from_url(self, qtbot):
|
||||
url = (
|
||||
"https://github.com/chidiwilliams/buzz/raw/main/testdata/whisper-french.mp3"
|
||||
|
|
@ -270,6 +273,9 @@ class TestWhisperFileTranscriber:
|
|||
assert len(segments[i].text) > 0
|
||||
logging.debug(f"{segments[i].start} {segments[i].end} {segments[i].text}")
|
||||
|
||||
transcriber.stop()
|
||||
time.sleep(3)
|
||||
|
||||
def test_transcribe_from_folder_watch_source(self, qtbot):
|
||||
file_path = tempfile.mktemp(suffix=".mp3")
|
||||
shutil.copy(test_audio_path, file_path)
|
||||
|
|
@ -301,6 +307,9 @@ class TestWhisperFileTranscriber:
|
|||
)
|
||||
assert len(glob.glob("*.txt", root_dir=output_directory)) > 0
|
||||
|
||||
transcriber.stop()
|
||||
time.sleep(3)
|
||||
|
||||
@pytest.mark.skip()
|
||||
def test_transcribe_stop(self):
|
||||
output_file_path = os.path.join(tempfile.gettempdir(), "whisper.txt")
|
||||
|
|
@ -335,3 +344,5 @@ class TestWhisperFileTranscriber:
|
|||
|
||||
# Assert that file was not created
|
||||
assert os.path.isfile(output_file_path) is False
|
||||
|
||||
time.sleep(3)
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import logging
|
||||
import os
|
||||
from typing import List
|
||||
from unittest.mock import patch
|
||||
|
|
@ -95,6 +96,7 @@ class TestMainWindow:
|
|||
|
||||
window.close()
|
||||
|
||||
@pytest.mark.timeout(300)
|
||||
def test_should_run_and_cancel_transcription_task(
|
||||
self, qtbot, db, transcription_service
|
||||
):
|
||||
|
|
@ -105,19 +107,44 @@ class TestMainWindow:
|
|||
|
||||
table_widget = self._get_tasks_table(window)
|
||||
|
||||
qtbot.wait_until(
|
||||
self._get_assert_task_status_callback(table_widget, 0, "in_progress"),
|
||||
timeout=2 * 60 * 1000,
|
||||
)
|
||||
try:
|
||||
qtbot.wait_until(
|
||||
self._get_assert_task_status_callback(table_widget, 0, "in_progress"),
|
||||
timeout=60 * 1000,
|
||||
)
|
||||
except Exception:
|
||||
logging.error("Task never reached 'in_progress' status")
|
||||
assert False, "Task did not start as expected"
|
||||
|
||||
logging.debug("Will cancel transcription task")
|
||||
|
||||
# Stop task in progress
|
||||
table_widget.selectRow(0)
|
||||
|
||||
# Force immediate processing of pending events before triggering cancellation
|
||||
qtbot.wait(100)
|
||||
|
||||
window.toolbar.stop_transcription_action.trigger()
|
||||
|
||||
# Give some time for the cancellation to be processed
|
||||
qtbot.wait(500)
|
||||
|
||||
qtbot.wait_until(
|
||||
self._get_assert_task_status_callback(table_widget, 0, "canceled"),
|
||||
timeout=60 * 1000,
|
||||
)
|
||||
logging.debug("Will wait for task to reach 'canceled' status")
|
||||
|
||||
try:
|
||||
qtbot.wait_until(
|
||||
self._get_assert_task_status_callback(table_widget, 0, "canceled"),
|
||||
timeout=30 * 1000,
|
||||
)
|
||||
except Exception:
|
||||
# On Windows, the cancellation might be slower, check final state
|
||||
final_status = self._get_status(table_widget, 0)
|
||||
logging.error(f"Task status after timeout: {final_status}")
|
||||
if "canceled" not in final_status.lower():
|
||||
assert False, f"Task did not cancel as expected. Final status: {final_status}"
|
||||
|
||||
logging.debug("Task canceled")
|
||||
|
||||
qtbot.wait(200)
|
||||
|
||||
table_widget.selectRow(0)
|
||||
assert window.toolbar.stop_transcription_action.isEnabled() is False
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from PyQt6.QtWidgets import QPushButton, QMessageBox, QLineEdit, QCheckBox
|
|||
from buzz.locale import _
|
||||
from buzz.settings.settings import Settings
|
||||
from buzz.widgets.preferences_dialog.general_preferences_widget import (
|
||||
GeneralPreferencesWidget, TestOpenAIApiKeyJob
|
||||
GeneralPreferencesWidget, ValidateOpenAIApiKeyJob
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -118,7 +118,7 @@ class TestTestOpenAIApiKeyJob:
|
|||
mocker.patch('buzz.widgets.preferences_dialog.general_preferences_widget.OpenAI', return_value=mock_client)
|
||||
mocker.patch('buzz.settings.settings.Settings.value', return_value="") # No custom base URL
|
||||
|
||||
job = TestOpenAIApiKeyJob(api_key="test_key")
|
||||
job = ValidateOpenAIApiKeyJob(api_key="test_key")
|
||||
mock_success = mocker.Mock()
|
||||
mock_failed = mocker.Mock()
|
||||
job.signals.success.connect(mock_success)
|
||||
|
|
@ -140,7 +140,7 @@ class TestTestOpenAIApiKeyJob:
|
|||
mocker.patch('buzz.widgets.preferences_dialog.general_preferences_widget.OpenAI', return_value=mock_client)
|
||||
mocker.patch('buzz.settings.settings.Settings.value', return_value="") # No custom base URL
|
||||
|
||||
job = TestOpenAIApiKeyJob(api_key="wrong_key")
|
||||
job = ValidateOpenAIApiKeyJob(api_key="wrong_key")
|
||||
mock_success = mocker.Mock()
|
||||
mock_failed = mocker.Mock()
|
||||
job.signals.success.connect(mock_success)
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ class TestRecordingTranscriberWidget:
|
|||
|
||||
widget.close()
|
||||
|
||||
@pytest.mark.timeout(60)
|
||||
def test_on_next_transcription_append_above(self, qtbot: QtBot):
|
||||
with (patch("sounddevice.InputStream", side_effect=MockInputStream),
|
||||
patch("buzz.transcriber.recording_transcriber.RecordingTranscriber.get_device_sample_rate",
|
||||
|
|
@ -129,7 +130,7 @@ class TestRecordingTranscriberWidget:
|
|||
widget.on_next_transcription('test2')
|
||||
assert widget.transcription_text_box.toPlainText() == 'test2\n\ntest1\n\n'
|
||||
|
||||
qtbot.wait(200)
|
||||
qtbot.wait(500)
|
||||
|
||||
widget.close()
|
||||
|
||||
|
|
@ -161,6 +162,7 @@ class TestRecordingTranscriberWidget:
|
|||
assert RecordingTranscriberWidget.find_common_part("hello world", "") == ""
|
||||
assert RecordingTranscriberWidget.find_common_part("", "") == ""
|
||||
|
||||
@pytest.mark.timeout(60)
|
||||
def test_on_next_transcription_append_and_correct(self, qtbot: QtBot):
|
||||
with (patch("sounddevice.InputStream", side_effect=MockInputStream),
|
||||
patch("buzz.transcriber.recording_transcriber.RecordingTranscriber.get_device_sample_rate",
|
||||
|
|
@ -182,6 +184,6 @@ class TestRecordingTranscriberWidget:
|
|||
widget.on_next_transcription('Ceci est la deuxième phrase. Et voici la troisième.')
|
||||
assert widget.transcription_text_box.toPlainText() == 'Bienvenue dans la transcription en direct de Buzz. Ceci est la deuxième phrase. Et voici la troisième.'
|
||||
|
||||
qtbot.wait(200)
|
||||
qtbot.wait(500)
|
||||
|
||||
widget.close()
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit c7b6988678779901d02ceba1a8212d2c9908956e
|
||||
Subproject commit a8d002cfd879315632a579e73f0148d06959de36
|
||||
Loading…
Add table
Add a link
Reference in a new issue