Skip to content

Commit 96553db

Browse files
authored
Node.js package (ggml-org#260)
* npm : preparing infra for node package * npm : package infra ready * npm : initial version ready * npm : change name to whisper.cpp whisper.js is taken
1 parent 9af91c1 commit 96553db

15 files changed

+373
-67
lines changed

CMakeLists.txt

+13-11
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
1414
if (EXISTS "${CMAKE_SOURCE_DIR}/bindings/ios/Makefile-tmpl")
1515
configure_file(${CMAKE_SOURCE_DIR}/bindings/ios/Makefile-tmpl ${CMAKE_SOURCE_DIR}/bindings/ios/Makefile @ONLY)
1616
endif()
17+
configure_file(${CMAKE_SOURCE_DIR}/bindings/javascript/package-tmpl.json ${CMAKE_SOURCE_DIR}/bindings/javascript/package.json @ONLY)
1718
else()
1819
set(WHISPER_STANDALONE OFF)
1920
endif()
@@ -151,8 +152,7 @@ else()
151152
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /arch:AVX2")
152153
else()
153154
if (EMSCRIPTEN)
154-
# we require support for WASM SIMD 128-bit
155-
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread -msimd128")
155+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread")
156156
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
157157
else()
158158
if(NOT WHISPER_NO_AVX)
@@ -203,6 +203,10 @@ if (BUILD_SHARED_LIBS)
203203
)
204204
endif()
205205

206+
if (EMSCRIPTEN)
207+
set_target_properties(${TARGET} PROPERTIES COMPILE_FLAGS "-msimd128")
208+
endif()
209+
206210
target_compile_definitions(${TARGET} PUBLIC
207211
${WHISPER_EXTRA_FLAGS}
208212
)
@@ -222,13 +226,11 @@ add_subdirectory(bindings)
222226
# programs, examples and tests
223227
#
224228

225-
if (WHISPER_STANDALONE)
226-
if (WHISPER_BUILD_TESTS)
227-
enable_testing()
228-
add_subdirectory(tests)
229-
endif ()
230-
231-
if (WHISPER_BUILD_EXAMPLES)
232-
add_subdirectory(examples)
233-
endif()
229+
if (WHISPER_BUILD_TESTS)
230+
enable_testing()
231+
add_subdirectory(tests)
234232
endif ()
233+
234+
if (WHISPER_BUILD_EXAMPLES)
235+
add_subdirectory(examples)
236+
endif()

bindings/CMakeLists.txt

+16
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
11
if (EMSCRIPTEN)
22
add_subdirectory(javascript)
3+
4+
add_custom_command(
5+
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/javascript/publish.log
6+
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/javascript/whisper.js
7+
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/javascript/libwhisper.worker.js
8+
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/javascript/package.json
9+
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/javascript
10+
COMMAND npm publish
11+
COMMAND touch publish.log
12+
COMMENT "Publishing npm module v${PROJECT_VERSION}"
13+
VERBATIM
14+
)
15+
16+
add_custom_target(publish-npm
17+
DEPENDS javascript/publish.log
18+
)
319
endif()

bindings/javascript/CMakeLists.txt

+11-4
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,22 @@ if (WHISPER_WASM_SINGLE_FILE)
2020
${CMAKE_BINARY_DIR}/bin/libwhisper.js
2121
${CMAKE_CURRENT_SOURCE_DIR}/whisper.js
2222
)
23+
24+
add_custom_command(
25+
TARGET ${TARGET} POST_BUILD
26+
COMMAND ${CMAKE_COMMAND} -E copy
27+
${CMAKE_BINARY_DIR}/bin/libwhisper.worker.js
28+
${CMAKE_CURRENT_SOURCE_DIR}/libwhisper.worker.js
29+
)
2330
endif()
2431

2532
set_target_properties(${TARGET} PROPERTIES LINK_FLAGS " \
2633
--bind \
34+
-s MODULARIZE=1 \
35+
-s EXPORT_NAME=\"'whisper_factory'\" \
36+
-s FORCE_FILESYSTEM=1 \
2737
-s USE_PTHREADS=1 \
2838
-s PTHREAD_POOL_SIZE=8 \
29-
-s INITIAL_MEMORY=1610612736 \
30-
-s TOTAL_MEMORY=1610612736 \
31-
-s FORCE_FILESYSTEM=1 \
32-
-s EXPORTED_RUNTIME_METHODS=\"['print', 'printErr', 'ccall', 'cwrap']\" \
39+
-s ALLOW_MEMORY_GROWTH=1 \
3340
${EXTRA_FLAGS} \
3441
")

bindings/javascript/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# whisper.cpp
2+
3+
Node.js package for Whisper speech recognition
4+
5+
For sample usage check [tests/test-whisper.js](/tests/test-whisper.js)

bindings/javascript/emscripten.cpp

+31-46
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,56 @@
1+
//
2+
// This is the Javascript API of whisper.cpp
3+
//
4+
// Very crude at the moment.
5+
// Feel free to contribute and make this better!
6+
//
7+
// See the tests/test-whisper.js for sample usage
8+
//
9+
110
#include "whisper.h"
211

312
#include <emscripten.h>
413
#include <emscripten/bind.h>
514

6-
#include <vector>
715
#include <thread>
16+
#include <vector>
817

9-
std::thread g_worker;
10-
11-
std::vector<struct whisper_context *> g_contexts(4, nullptr);
18+
struct whisper_context * g_context;
1219

1320
EMSCRIPTEN_BINDINGS(whisper) {
1421
emscripten::function("init", emscripten::optional_override([](const std::string & path_model) {
15-
if (g_worker.joinable()) {
16-
g_worker.join();
17-
}
18-
19-
for (size_t i = 0; i < g_contexts.size(); ++i) {
20-
if (g_contexts[i] == nullptr) {
21-
g_contexts[i] = whisper_init(path_model.c_str());
22-
if (g_contexts[i] != nullptr) {
23-
return i + 1;
24-
} else {
25-
return (size_t) 0;
26-
}
22+
if (g_context == nullptr) {
23+
g_context = whisper_init(path_model.c_str());
24+
if (g_context != nullptr) {
25+
return true;
26+
} else {
27+
return false;
2728
}
2829
}
2930

30-
return (size_t) 0;
31+
return false;
3132
}));
3233

33-
emscripten::function("free", emscripten::optional_override([](size_t index) {
34-
if (g_worker.joinable()) {
35-
g_worker.join();
36-
}
37-
38-
--index;
39-
40-
if (index < g_contexts.size()) {
41-
whisper_free(g_contexts[index]);
42-
g_contexts[index] = nullptr;
34+
emscripten::function("free", emscripten::optional_override([]() {
35+
if (g_context) {
36+
whisper_free(g_context);
37+
g_context = nullptr;
4338
}
4439
}));
4540

46-
emscripten::function("full_default", emscripten::optional_override([](size_t index, const emscripten::val & audio, const std::string & lang, bool translate) {
47-
if (g_worker.joinable()) {
48-
g_worker.join();
49-
}
50-
51-
--index;
52-
53-
if (index >= g_contexts.size()) {
41+
emscripten::function("full_default", emscripten::optional_override([](const emscripten::val & audio, const std::string & lang, bool translate) {
42+
if (g_context == nullptr) {
5443
return -1;
5544
}
5645

57-
if (g_contexts[index] == nullptr) {
58-
return -2;
59-
}
60-
6146
struct whisper_full_params params = whisper_full_default_params(whisper_sampling_strategy::WHISPER_SAMPLING_GREEDY);
6247

6348
params.print_realtime = true;
6449
params.print_progress = false;
6550
params.print_timestamps = true;
6651
params.print_special = false;
6752
params.translate = translate;
68-
params.language = whisper_is_multilingual(g_contexts[index]) ? lang.c_str() : "en";
53+
params.language = whisper_is_multilingual(g_context) ? lang.c_str() : "en";
6954
params.n_threads = std::min(8, (int) std::thread::hardware_concurrency());
7055
params.offset_ms = 0;
7156

@@ -82,9 +67,11 @@ EMSCRIPTEN_BINDINGS(whisper) {
8267

8368
// print system information
8469
{
70+
printf("\n");
8571
printf("system_info: n_threads = %d / %d | %s\n",
8672
params.n_threads, std::thread::hardware_concurrency(), whisper_print_system_info());
8773

74+
printf("\n");
8875
printf("%s: processing %d samples, %.1f sec, %d threads, %d processors, lang = %s, task = %s ...\n",
8976
__func__, int(pcmf32.size()), float(pcmf32.size())/WHISPER_SAMPLE_RATE,
9077
params.n_threads, 1,
@@ -94,13 +81,11 @@ EMSCRIPTEN_BINDINGS(whisper) {
9481
printf("\n");
9582
}
9683

97-
// run the worker
84+
// run whisper
9885
{
99-
g_worker = std::thread([index, params, pcmf32 = std::move(pcmf32)]() {
100-
whisper_reset_timings(g_contexts[index]);
101-
whisper_full(g_contexts[index], params, pcmf32.data(), pcmf32.size());
102-
whisper_print_timings(g_contexts[index]);
103-
});
86+
whisper_reset_timings(g_context);
87+
whisper_full(g_context, params, pcmf32.data(), pcmf32.size());
88+
whisper_print_timings(g_context);
10489
}
10590

10691
return 0;

bindings/javascript/libwhisper.worker.js

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bindings/javascript/package-tmpl.json

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "whisper.cpp",
3+
"version": "@PROJECT_VERSION@",
4+
"description": "Whisper speech recognition",
5+
"main": "whisper.js",
6+
"scripts": {
7+
"test": "echo \"todo: add tests\" && exit 0"
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "git+https://github.com/ggerganov/whisper.cpp"
12+
},
13+
"keywords": [
14+
"openai",
15+
"whisper",
16+
"speech-to-text",
17+
"speech-recognition",
18+
"transformer"
19+
],
20+
"author": "Georgi Gerganov",
21+
"license": "MIT",
22+
"bugs": {
23+
"url": "https://github.com/ggerganov/whisper.cpp/issues"
24+
},
25+
"homepage": "https://github.com/ggerganov/whisper.cpp#readme"
26+
}

bindings/javascript/package.json

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "whisper.cpp",
3+
"version": "1.0.0",
4+
"description": "Whisper speech recognition",
5+
"main": "whisper.js",
6+
"scripts": {
7+
"test": "echo \"todo: add tests\" && exit 0"
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "git+https://github.com/ggerganov/whisper.cpp"
12+
},
13+
"keywords": [
14+
"openai",
15+
"whisper",
16+
"speech-to-text",
17+
"speech-recognition",
18+
"transformer"
19+
],
20+
"author": "Georgi Gerganov",
21+
"license": "MIT",
22+
"bugs": {
23+
"url": "https://github.com/ggerganov/whisper.cpp/issues"
24+
},
25+
"homepage": "https://github.com/ggerganov/whisper.cpp#readme"
26+
}

bindings/javascript/whisper.js

+21-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/whisper.wasm/CMakeLists.txt

+45-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,47 @@
1+
#
2+
# libmain
3+
#
4+
5+
set(TARGET libmain)
6+
7+
add_executable(${TARGET}
8+
emscripten.cpp
9+
)
10+
11+
target_link_libraries(${TARGET} PRIVATE
12+
whisper
13+
)
14+
15+
unset(EXTRA_FLAGS)
16+
17+
if (WHISPER_WASM_SINGLE_FILE)
18+
set(EXTRA_FLAGS "-s SINGLE_FILE=1")
19+
message(STATUS "Embedding WASM inside main.js")
20+
21+
add_custom_command(
22+
TARGET ${TARGET} POST_BUILD
23+
COMMAND ${CMAKE_COMMAND} -E copy
24+
${CMAKE_BINARY_DIR}/bin/libmain.js
25+
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/whisper.wasm/main.js
26+
)
27+
endif()
28+
29+
set_target_properties(${TARGET} PROPERTIES LINK_FLAGS " \
30+
--bind \
31+
-s USE_PTHREADS=1 \
32+
-s PTHREAD_POOL_SIZE=8 \
33+
-s INITIAL_MEMORY=1024MB \
34+
-s TOTAL_MEMORY=1024MB \
35+
-s FORCE_FILESYSTEM=1 \
36+
-s EXPORTED_RUNTIME_METHODS=\"['print', 'printErr', 'ccall', 'cwrap']\" \
37+
${EXTRA_FLAGS} \
38+
")
39+
40+
#
41+
# whisper.wasm
42+
#
43+
144
set(TARGET whisper.wasm)
245

3-
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/index-tmpl.html ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/index.html @ONLY)
4-
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/../helpers.js ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/helpers.js @ONLY)
5-
configure_file(${CMAKE_SOURCE_DIR}/bindings/javascript/whisper.js ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/whisper.js COPYONLY)
46+
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/index-tmpl.html ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/index.html @ONLY)
47+
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/../helpers.js ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/helpers.js @ONLY)

0 commit comments

Comments
 (0)