whisper.rn 0.3.3 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -2
- package/android/build.gradle +12 -2
- package/android/src/main/CMakeLists.txt +55 -0
- package/android/src/main/java/com/rnwhisper/RNWhisper.java +268 -0
- package/android/src/newarch/java/com/rnwhisper/RNWhisperModule.java +10 -228
- package/android/src/oldarch/java/com/rnwhisper/RNWhisperModule.java +16 -222
- package/cpp/README.md +4 -0
- package/cpp/coreml/whisper-encoder.mm +4 -2
- package/cpp/ggml.c +9 -1
- package/cpp/ggml.h +1 -0
- package/cpp/whisper.cpp +151 -99
- package/cpp/whisper.h +2 -1
- package/ios/RNWhisperContext.mm +2 -2
- package/lib/commonjs/NativeRNWhisper.js.map +1 -1
- package/lib/commonjs/index.js +5 -2
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/NativeRNWhisper.js +3 -0
- package/lib/module/NativeRNWhisper.js.map +1 -1
- package/lib/module/index.js +5 -2
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/NativeRNWhisper.d.ts +1 -3
- package/lib/typescript/NativeRNWhisper.d.ts.map +1 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/NativeRNWhisper.ts +2 -3
- package/src/index.ts +4 -3
- package/android/src/main/jni/whisper/Android.mk +0 -26
- package/android/src/main/jni/whisper/Application.mk +0 -1
- package/android/src/main/jni/whisper/Whisper.mk +0 -22
- /package/android/src/main/{jni/whisper/jni.cpp → jni.cpp} +0 -0
package/README.md
CHANGED
|
@@ -21,13 +21,26 @@ React Native binding of [whisper.cpp](https://github.com/ggerganov/whisper.cpp).
|
|
|
21
21
|
npm install whisper.rn
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
#### iOS
|
|
25
|
+
|
|
26
|
+
Please re-run `npx pod-install` again.
|
|
27
|
+
|
|
28
|
+
#### Android
|
|
25
29
|
|
|
26
30
|
If you want to use `medium` or `large` model, the [Extended Virtual Addressing](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_kernel_extended-virtual-addressing) capability is recommended to enable on iOS project.
|
|
27
31
|
|
|
28
32
|
For Android, it's recommended to use `ndkVersion = "24.0.8215888"` (or above) in your root project build configuration for Apple Silicon Macs. Otherwise please follow this trobleshooting [issue](./TROUBLESHOOTING.md#android-got-build-error-unknown-host-cpu-architecture-arm64-on-apple-silicon-macs).
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
Don't forget to add proguard rule if it's enabled in project (android/app/proguard-rules.pro):
|
|
35
|
+
|
|
36
|
+
```proguard
|
|
37
|
+
# whisper.rn
|
|
38
|
+
-keep class com.rnwhisper.** { *; }
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
#### Expo
|
|
42
|
+
|
|
43
|
+
You will need to prebuild the project before using it. See [Expo guide](https://docs.expo.io/guides/using-libraries/#using-a-library-in-a-expo-project) for more details.
|
|
31
44
|
|
|
32
45
|
## Add Microphone Permissions (Optional)
|
|
33
46
|
|
package/android/build.gradle
CHANGED
|
@@ -30,6 +30,11 @@ def getExtOrIntegerDefault(name) {
|
|
|
30
30
|
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["RNWhisper_" + name]).toInteger()
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
def reactNativeArchitectures() {
|
|
34
|
+
def value = project.getProperties().get("reactNativeArchitectures")
|
|
35
|
+
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
|
|
36
|
+
}
|
|
37
|
+
|
|
33
38
|
android {
|
|
34
39
|
ndkVersion getExtOrDefault("ndkVersion")
|
|
35
40
|
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
|
|
@@ -38,10 +43,15 @@ android {
|
|
|
38
43
|
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
|
|
39
44
|
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
40
45
|
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
|
46
|
+
externalNativeBuild {
|
|
47
|
+
cmake {
|
|
48
|
+
abiFilters (*reactNativeArchitectures())
|
|
49
|
+
}
|
|
50
|
+
}
|
|
41
51
|
}
|
|
42
52
|
externalNativeBuild {
|
|
43
|
-
|
|
44
|
-
path 'src/main/
|
|
53
|
+
cmake {
|
|
54
|
+
path = file('src/main/CMakeLists.txt')
|
|
45
55
|
}
|
|
46
56
|
}
|
|
47
57
|
buildTypes {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.10)
|
|
2
|
+
|
|
3
|
+
project(whisper.rn)
|
|
4
|
+
|
|
5
|
+
set(CMAKE_CXX_STANDARD 11)
|
|
6
|
+
set(RNWHISPER_LIB_DIR ${CMAKE_SOURCE_DIR}/../../../cpp)
|
|
7
|
+
|
|
8
|
+
set(
|
|
9
|
+
SOURCE_FILES
|
|
10
|
+
${RNWHISPER_LIB_DIR}/ggml.c
|
|
11
|
+
${RNWHISPER_LIB_DIR}/whisper.cpp
|
|
12
|
+
${RNWHISPER_LIB_DIR}/rn-whisper.cpp
|
|
13
|
+
${CMAKE_SOURCE_DIR}/jni.cpp
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
find_library(LOG_LIB log)
|
|
17
|
+
|
|
18
|
+
function(build_library target_name)
|
|
19
|
+
add_library(
|
|
20
|
+
${target_name}
|
|
21
|
+
SHARED
|
|
22
|
+
${SOURCE_FILES}
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
target_link_libraries(${target_name} ${LOG_LIB} android)
|
|
26
|
+
|
|
27
|
+
if (${target_name} STREQUAL "whisper_v8fp16_va")
|
|
28
|
+
target_compile_options(${target_name} PRIVATE -march=armv8.2-a+fp16)
|
|
29
|
+
elseif (${target_name} STREQUAL "whisper_vfpv4")
|
|
30
|
+
target_compile_options(${target_name} PRIVATE -mfpu=neon-vfpv4)
|
|
31
|
+
endif ()
|
|
32
|
+
|
|
33
|
+
# NOTE: If you want to debug the native code, you can uncomment if and endif
|
|
34
|
+
# if (NOT ${CMAKE_BUILD_TYPE} STREQUAL "Debug")
|
|
35
|
+
|
|
36
|
+
target_compile_options(${target_name} PRIVATE -O3 -DNDEBUG -pthread)
|
|
37
|
+
target_compile_options(${target_name} PRIVATE -fvisibility=hidden -fvisibility-inlines-hidden)
|
|
38
|
+
target_compile_options(${target_name} PRIVATE -ffunction-sections -fdata-sections)
|
|
39
|
+
|
|
40
|
+
target_link_options(${target_name} PRIVATE -Wl,--gc-sections)
|
|
41
|
+
target_link_options(${target_name} PRIVATE -Wl,--exclude-libs,ALL)
|
|
42
|
+
target_link_options(${target_name} PRIVATE -flto)
|
|
43
|
+
|
|
44
|
+
# endif ()
|
|
45
|
+
endfunction()
|
|
46
|
+
|
|
47
|
+
build_library("whisper") # Default target
|
|
48
|
+
|
|
49
|
+
if (${ANDROID_ABI} STREQUAL "arm64-v8a")
|
|
50
|
+
build_library("whisper_v8fp16_va")
|
|
51
|
+
elseif (${ANDROID_ABI} STREQUAL "armeabi-v7a")
|
|
52
|
+
build_library("whisper_vfpv4")
|
|
53
|
+
endif ()
|
|
54
|
+
|
|
55
|
+
include_directories(${RNWHISPER_LIB_DIR})
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
package com.rnwhisper;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
import android.util.Log;
|
|
5
|
+
import android.os.Build;
|
|
6
|
+
import android.os.Handler;
|
|
7
|
+
import android.os.AsyncTask;
|
|
8
|
+
import android.media.AudioRecord;
|
|
9
|
+
|
|
10
|
+
import com.facebook.react.bridge.Promise;
|
|
11
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
12
|
+
import com.facebook.react.bridge.ReactMethod;
|
|
13
|
+
import com.facebook.react.bridge.LifecycleEventListener;
|
|
14
|
+
import com.facebook.react.bridge.ReadableMap;
|
|
15
|
+
import com.facebook.react.bridge.WritableMap;
|
|
16
|
+
|
|
17
|
+
import java.util.HashMap;
|
|
18
|
+
import java.util.Random;
|
|
19
|
+
import java.io.File;
|
|
20
|
+
import java.io.FileInputStream;
|
|
21
|
+
import java.io.PushbackInputStream;
|
|
22
|
+
|
|
23
|
+
public class RNWhisper implements LifecycleEventListener {
|
|
24
|
+
private ReactApplicationContext reactContext;
|
|
25
|
+
private Downloader downloader;
|
|
26
|
+
|
|
27
|
+
public RNWhisper(ReactApplicationContext reactContext) {
|
|
28
|
+
reactContext.addLifecycleEventListener(this);
|
|
29
|
+
this.reactContext = reactContext;
|
|
30
|
+
this.downloader = new Downloader(reactContext);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public HashMap<String, Object> getTypedExportedConstants() {
|
|
34
|
+
HashMap<String, Object> constants = new HashMap<>();
|
|
35
|
+
|
|
36
|
+
// iOS only constants, put for passing type checks
|
|
37
|
+
constants.put("useCoreML", false);
|
|
38
|
+
constants.put("coreMLAllowFallback", false);
|
|
39
|
+
|
|
40
|
+
return constants;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private HashMap<Integer, WhisperContext> contexts = new HashMap<>();
|
|
44
|
+
|
|
45
|
+
private int getResourceIdentifier(String filePath) {
|
|
46
|
+
int identifier = reactContext.getResources().getIdentifier(
|
|
47
|
+
filePath,
|
|
48
|
+
"drawable",
|
|
49
|
+
reactContext.getPackageName()
|
|
50
|
+
);
|
|
51
|
+
if (identifier == 0) {
|
|
52
|
+
identifier = reactContext.getResources().getIdentifier(
|
|
53
|
+
filePath,
|
|
54
|
+
"raw",
|
|
55
|
+
reactContext.getPackageName()
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return identifier;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public void initContext(final ReadableMap options, final Promise promise) {
|
|
62
|
+
new AsyncTask<Void, Void, Integer>() {
|
|
63
|
+
private Exception exception;
|
|
64
|
+
|
|
65
|
+
@Override
|
|
66
|
+
protected Integer doInBackground(Void... voids) {
|
|
67
|
+
try {
|
|
68
|
+
String modelPath = options.getString("filePath");
|
|
69
|
+
boolean isBundleAsset = options.getBoolean("isBundleAsset");
|
|
70
|
+
|
|
71
|
+
String modelFilePath = modelPath;
|
|
72
|
+
if (!isBundleAsset && (modelPath.startsWith("http://") || modelPath.startsWith("https://"))) {
|
|
73
|
+
modelFilePath = downloader.downloadFile(modelPath);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
long context;
|
|
77
|
+
int resId = getResourceIdentifier(modelFilePath);
|
|
78
|
+
if (resId > 0) {
|
|
79
|
+
context = WhisperContext.initContextWithInputStream(
|
|
80
|
+
new PushbackInputStream(reactContext.getResources().openRawResource(resId))
|
|
81
|
+
);
|
|
82
|
+
} else if (isBundleAsset) {
|
|
83
|
+
context = WhisperContext.initContextWithAsset(reactContext.getAssets(), modelFilePath);
|
|
84
|
+
} else {
|
|
85
|
+
context = WhisperContext.initContext(modelFilePath);
|
|
86
|
+
}
|
|
87
|
+
if (context == 0) {
|
|
88
|
+
throw new Exception("Failed to initialize context");
|
|
89
|
+
}
|
|
90
|
+
int id = Math.abs(new Random().nextInt());
|
|
91
|
+
WhisperContext whisperContext = new WhisperContext(id, reactContext, context);
|
|
92
|
+
contexts.put(id, whisperContext);
|
|
93
|
+
return id;
|
|
94
|
+
} catch (Exception e) {
|
|
95
|
+
exception = e;
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@Override
|
|
101
|
+
protected void onPostExecute(Integer id) {
|
|
102
|
+
if (exception != null) {
|
|
103
|
+
promise.reject(exception);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
promise.resolve(id);
|
|
107
|
+
}
|
|
108
|
+
}.execute();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public void transcribeFile(double id, double jobId, String filePath, ReadableMap options, Promise promise) {
|
|
112
|
+
final WhisperContext context = contexts.get((int) id);
|
|
113
|
+
if (context == null) {
|
|
114
|
+
promise.reject("Context not found");
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (context.isCapturing()) {
|
|
118
|
+
promise.reject("The context is in realtime transcribe mode");
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (context.isTranscribing()) {
|
|
122
|
+
promise.reject("Context is already transcribing");
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
new AsyncTask<Void, Void, WritableMap>() {
|
|
126
|
+
private Exception exception;
|
|
127
|
+
|
|
128
|
+
@Override
|
|
129
|
+
protected WritableMap doInBackground(Void... voids) {
|
|
130
|
+
try {
|
|
131
|
+
String waveFilePath = filePath;
|
|
132
|
+
|
|
133
|
+
if (filePath.startsWith("http://") || filePath.startsWith("https://")) {
|
|
134
|
+
waveFilePath = downloader.downloadFile(filePath);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
int resId = getResourceIdentifier(waveFilePath);
|
|
138
|
+
if (resId > 0) {
|
|
139
|
+
return context.transcribeInputStream(
|
|
140
|
+
(int) jobId,
|
|
141
|
+
reactContext.getResources().openRawResource(resId),
|
|
142
|
+
options
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return context.transcribeInputStream(
|
|
147
|
+
(int) jobId,
|
|
148
|
+
new FileInputStream(new File(waveFilePath)),
|
|
149
|
+
options
|
|
150
|
+
);
|
|
151
|
+
} catch (Exception e) {
|
|
152
|
+
exception = e;
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
@Override
|
|
158
|
+
protected void onPostExecute(WritableMap data) {
|
|
159
|
+
if (exception != null) {
|
|
160
|
+
promise.reject(exception);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
promise.resolve(data);
|
|
164
|
+
}
|
|
165
|
+
}.execute();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
public void startRealtimeTranscribe(double id, double jobId, ReadableMap options, Promise promise) {
|
|
169
|
+
final WhisperContext context = contexts.get((int) id);
|
|
170
|
+
if (context == null) {
|
|
171
|
+
promise.reject("Context not found");
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (context.isCapturing()) {
|
|
175
|
+
promise.reject("Context is already in capturing");
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
int state = context.startRealtimeTranscribe((int) jobId, options);
|
|
179
|
+
if (state == AudioRecord.STATE_INITIALIZED) {
|
|
180
|
+
promise.resolve(null);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
promise.reject("Failed to start realtime transcribe. State: " + state);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
public void abortTranscribe(double contextId, double jobId, Promise promise) {
|
|
187
|
+
WhisperContext context = contexts.get((int) contextId);
|
|
188
|
+
if (context == null) {
|
|
189
|
+
promise.reject("Context not found");
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
context.stopTranscribe((int) jobId);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
public void releaseContext(double id, Promise promise) {
|
|
196
|
+
final int contextId = (int) id;
|
|
197
|
+
new AsyncTask<Void, Void, Void>() {
|
|
198
|
+
private Exception exception;
|
|
199
|
+
|
|
200
|
+
@Override
|
|
201
|
+
protected Void doInBackground(Void... voids) {
|
|
202
|
+
try {
|
|
203
|
+
WhisperContext context = contexts.get(contextId);
|
|
204
|
+
if (context == null) {
|
|
205
|
+
throw new Exception("Context " + id + " not found");
|
|
206
|
+
}
|
|
207
|
+
context.release();
|
|
208
|
+
contexts.remove(contextId);
|
|
209
|
+
} catch (Exception e) {
|
|
210
|
+
exception = e;
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
@Override
|
|
216
|
+
protected void onPostExecute(Void result) {
|
|
217
|
+
if (exception != null) {
|
|
218
|
+
promise.reject(exception);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
promise.resolve(null);
|
|
222
|
+
}
|
|
223
|
+
}.execute();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
public void releaseAllContexts(Promise promise) {
|
|
227
|
+
new AsyncTask<Void, Void, Void>() {
|
|
228
|
+
private Exception exception;
|
|
229
|
+
|
|
230
|
+
@Override
|
|
231
|
+
protected Void doInBackground(Void... voids) {
|
|
232
|
+
try {
|
|
233
|
+
onHostDestroy();
|
|
234
|
+
} catch (Exception e) {
|
|
235
|
+
exception = e;
|
|
236
|
+
}
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
@Override
|
|
241
|
+
protected void onPostExecute(Void result) {
|
|
242
|
+
if (exception != null) {
|
|
243
|
+
promise.reject(exception);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
promise.resolve(null);
|
|
247
|
+
}
|
|
248
|
+
}.execute();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
@Override
|
|
252
|
+
public void onHostResume() {
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
@Override
|
|
256
|
+
public void onHostPause() {
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
@Override
|
|
260
|
+
public void onHostDestroy() {
|
|
261
|
+
WhisperContext.abortAllTranscribe();
|
|
262
|
+
for (WhisperContext context : contexts.values()) {
|
|
263
|
+
context.release();
|
|
264
|
+
}
|
|
265
|
+
contexts.clear();
|
|
266
|
+
downloader.clearCache();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
package com.rnwhisper;
|
|
2
2
|
|
|
3
3
|
import androidx.annotation.NonNull;
|
|
4
|
-
import android.util.Log;
|
|
5
|
-
import android.os.Build;
|
|
6
|
-
import android.os.Handler;
|
|
7
|
-
import android.os.AsyncTask;
|
|
8
|
-
import android.media.AudioRecord;
|
|
9
4
|
|
|
10
5
|
import com.facebook.react.bridge.Promise;
|
|
11
6
|
import com.facebook.react.bridge.ReactApplicationContext;
|
|
12
7
|
import com.facebook.react.bridge.ReactMethod;
|
|
13
|
-
import com.facebook.react.bridge.LifecycleEventListener;
|
|
14
8
|
import com.facebook.react.bridge.ReadableMap;
|
|
15
|
-
import com.facebook.react.bridge.WritableMap;
|
|
16
9
|
import com.facebook.react.module.annotations.ReactModule;
|
|
17
10
|
|
|
18
11
|
import java.util.HashMap;
|
|
@@ -22,17 +15,14 @@ import java.io.FileInputStream;
|
|
|
22
15
|
import java.io.PushbackInputStream;
|
|
23
16
|
|
|
24
17
|
@ReactModule(name = RNWhisperModule.NAME)
|
|
25
|
-
public class RNWhisperModule extends NativeRNWhisperSpec
|
|
18
|
+
public class RNWhisperModule extends NativeRNWhisperSpec {
|
|
26
19
|
public static final String NAME = "RNWhisper";
|
|
27
20
|
|
|
28
|
-
private
|
|
29
|
-
private Downloader downloader;
|
|
21
|
+
private RNWhisper rnwhisper;
|
|
30
22
|
|
|
31
23
|
public RNWhisperModule(ReactApplicationContext reactContext) {
|
|
32
24
|
super(reactContext);
|
|
33
|
-
reactContext
|
|
34
|
-
this.reactContext = reactContext;
|
|
35
|
-
this.downloader = new Downloader(reactContext);
|
|
25
|
+
rnwhisper = new RNWhisper(reactContext);
|
|
36
26
|
}
|
|
37
27
|
|
|
38
28
|
@Override
|
|
@@ -43,244 +33,36 @@ public class RNWhisperModule extends NativeRNWhisperSpec implements LifecycleEve
|
|
|
43
33
|
|
|
44
34
|
@Override
|
|
45
35
|
public HashMap<String, Object> getTypedExportedConstants() {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// iOS only constants, put for passing type checks
|
|
49
|
-
constants.put("useCoreML", false);
|
|
50
|
-
constants.put("coreMLAllowFallback", false);
|
|
51
|
-
|
|
52
|
-
return constants;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
private HashMap<Integer, WhisperContext> contexts = new HashMap<>();
|
|
56
|
-
|
|
57
|
-
private int getResourceIdentifier(String filePath) {
|
|
58
|
-
int identifier = reactContext.getResources().getIdentifier(
|
|
59
|
-
filePath,
|
|
60
|
-
"drawable",
|
|
61
|
-
reactContext.getPackageName()
|
|
62
|
-
);
|
|
63
|
-
if (identifier == 0) {
|
|
64
|
-
identifier = reactContext.getResources().getIdentifier(
|
|
65
|
-
filePath,
|
|
66
|
-
"raw",
|
|
67
|
-
reactContext.getPackageName()
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
return identifier;
|
|
36
|
+
return rnwhisper.getTypedExportedConstants();
|
|
71
37
|
}
|
|
72
38
|
|
|
73
39
|
@ReactMethod
|
|
74
40
|
public void initContext(final ReadableMap options, final Promise promise) {
|
|
75
|
-
|
|
76
|
-
private Exception exception;
|
|
77
|
-
|
|
78
|
-
@Override
|
|
79
|
-
protected Integer doInBackground(Void... voids) {
|
|
80
|
-
try {
|
|
81
|
-
String modelPath = options.getString("filePath");
|
|
82
|
-
boolean isBundleAsset = options.getBoolean("isBundleAsset");
|
|
83
|
-
|
|
84
|
-
String modelFilePath = modelPath;
|
|
85
|
-
if (!isBundleAsset && (modelPath.startsWith("http://") || modelPath.startsWith("https://"))) {
|
|
86
|
-
modelFilePath = downloader.downloadFile(modelPath);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
long context;
|
|
90
|
-
int resId = getResourceIdentifier(modelFilePath);
|
|
91
|
-
if (resId > 0) {
|
|
92
|
-
context = WhisperContext.initContextWithInputStream(
|
|
93
|
-
new PushbackInputStream(reactContext.getResources().openRawResource(resId))
|
|
94
|
-
);
|
|
95
|
-
} else if (isBundleAsset) {
|
|
96
|
-
context = WhisperContext.initContextWithAsset(reactContext.getAssets(), modelFilePath);
|
|
97
|
-
} else {
|
|
98
|
-
context = WhisperContext.initContext(modelFilePath);
|
|
99
|
-
}
|
|
100
|
-
if (context == 0) {
|
|
101
|
-
throw new Exception("Failed to initialize context");
|
|
102
|
-
}
|
|
103
|
-
int id = Math.abs(new Random().nextInt());
|
|
104
|
-
WhisperContext whisperContext = new WhisperContext(id, reactContext, context);
|
|
105
|
-
contexts.put(id, whisperContext);
|
|
106
|
-
return id;
|
|
107
|
-
} catch (Exception e) {
|
|
108
|
-
exception = e;
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
@Override
|
|
114
|
-
protected void onPostExecute(Integer id) {
|
|
115
|
-
if (exception != null) {
|
|
116
|
-
promise.reject(exception);
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
promise.resolve(id);
|
|
120
|
-
}
|
|
121
|
-
}.execute();
|
|
41
|
+
rnwhisper.initContext(options, promise);
|
|
122
42
|
}
|
|
123
43
|
|
|
124
44
|
@ReactMethod
|
|
125
45
|
public void transcribeFile(double id, double jobId, String filePath, ReadableMap options, Promise promise) {
|
|
126
|
-
|
|
127
|
-
if (context == null) {
|
|
128
|
-
promise.reject("Context not found");
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
if (context.isCapturing()) {
|
|
132
|
-
promise.reject("The context is in realtime transcribe mode");
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
if (context.isTranscribing()) {
|
|
136
|
-
promise.reject("Context is already transcribing");
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
new AsyncTask<Void, Void, WritableMap>() {
|
|
140
|
-
private Exception exception;
|
|
141
|
-
|
|
142
|
-
@Override
|
|
143
|
-
protected WritableMap doInBackground(Void... voids) {
|
|
144
|
-
try {
|
|
145
|
-
String waveFilePath = filePath;
|
|
146
|
-
|
|
147
|
-
if (filePath.startsWith("http://") || filePath.startsWith("https://")) {
|
|
148
|
-
waveFilePath = downloader.downloadFile(filePath);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
int resId = getResourceIdentifier(waveFilePath);
|
|
152
|
-
if (resId > 0) {
|
|
153
|
-
return context.transcribeInputStream(
|
|
154
|
-
(int) jobId,
|
|
155
|
-
reactContext.getResources().openRawResource(resId),
|
|
156
|
-
options
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return context.transcribeInputStream(
|
|
161
|
-
(int) jobId,
|
|
162
|
-
new FileInputStream(new File(waveFilePath)),
|
|
163
|
-
options
|
|
164
|
-
);
|
|
165
|
-
} catch (Exception e) {
|
|
166
|
-
exception = e;
|
|
167
|
-
return null;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
@Override
|
|
172
|
-
protected void onPostExecute(WritableMap data) {
|
|
173
|
-
if (exception != null) {
|
|
174
|
-
promise.reject(exception);
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
promise.resolve(data);
|
|
178
|
-
}
|
|
179
|
-
}.execute();
|
|
46
|
+
rnwhisper.transcribeFile(id, jobId, filePath, options, promise);
|
|
180
47
|
}
|
|
181
48
|
|
|
182
49
|
@ReactMethod
|
|
183
50
|
public void startRealtimeTranscribe(double id, double jobId, ReadableMap options, Promise promise) {
|
|
184
|
-
|
|
185
|
-
if (context == null) {
|
|
186
|
-
promise.reject("Context not found");
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
if (context.isCapturing()) {
|
|
190
|
-
promise.reject("Context is already in capturing");
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
int state = context.startRealtimeTranscribe((int) jobId, options);
|
|
194
|
-
if (state == AudioRecord.STATE_INITIALIZED) {
|
|
195
|
-
promise.resolve(null);
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
promise.reject("Failed to start realtime transcribe. State: " + state);
|
|
51
|
+
rnwhisper.startRealtimeTranscribe(id, jobId, options, promise);
|
|
199
52
|
}
|
|
200
53
|
|
|
201
54
|
@ReactMethod
|
|
202
55
|
public void abortTranscribe(double contextId, double jobId, Promise promise) {
|
|
203
|
-
|
|
204
|
-
if (context == null) {
|
|
205
|
-
promise.reject("Context not found");
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
context.stopTranscribe((int) jobId);
|
|
56
|
+
rnwhisper.abortTranscribe(contextId, jobId, promise);
|
|
209
57
|
}
|
|
210
58
|
|
|
211
59
|
@ReactMethod
|
|
212
60
|
public void releaseContext(double id, Promise promise) {
|
|
213
|
-
|
|
214
|
-
new AsyncTask<Void, Void, Void>() {
|
|
215
|
-
private Exception exception;
|
|
216
|
-
|
|
217
|
-
@Override
|
|
218
|
-
protected Void doInBackground(Void... voids) {
|
|
219
|
-
try {
|
|
220
|
-
WhisperContext context = contexts.get(contextId);
|
|
221
|
-
if (context == null) {
|
|
222
|
-
throw new Exception("Context " + id + " not found");
|
|
223
|
-
}
|
|
224
|
-
context.release();
|
|
225
|
-
contexts.remove(contextId);
|
|
226
|
-
} catch (Exception e) {
|
|
227
|
-
exception = e;
|
|
228
|
-
}
|
|
229
|
-
return null;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
@Override
|
|
233
|
-
protected void onPostExecute(Void result) {
|
|
234
|
-
if (exception != null) {
|
|
235
|
-
promise.reject(exception);
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
promise.resolve(null);
|
|
239
|
-
}
|
|
240
|
-
}.execute();
|
|
61
|
+
rnwhisper.releaseContext(id, promise);
|
|
241
62
|
}
|
|
242
63
|
|
|
243
64
|
@ReactMethod
|
|
244
65
|
public void releaseAllContexts(Promise promise) {
|
|
245
|
-
|
|
246
|
-
private Exception exception;
|
|
247
|
-
|
|
248
|
-
@Override
|
|
249
|
-
protected Void doInBackground(Void... voids) {
|
|
250
|
-
try {
|
|
251
|
-
onHostDestroy();
|
|
252
|
-
} catch (Exception e) {
|
|
253
|
-
exception = e;
|
|
254
|
-
}
|
|
255
|
-
return null;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
@Override
|
|
259
|
-
protected void onPostExecute(Void result) {
|
|
260
|
-
if (exception != null) {
|
|
261
|
-
promise.reject(exception);
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
promise.resolve(null);
|
|
265
|
-
}
|
|
266
|
-
}.execute();
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
@Override
|
|
270
|
-
public void onHostResume() {
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
@Override
|
|
274
|
-
public void onHostPause() {
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
@Override
|
|
278
|
-
public void onHostDestroy() {
|
|
279
|
-
WhisperContext.abortAllTranscribe();
|
|
280
|
-
for (WhisperContext context : contexts.values()) {
|
|
281
|
-
context.release();
|
|
282
|
-
}
|
|
283
|
-
contexts.clear();
|
|
284
|
-
downloader.clearCache();
|
|
66
|
+
rnwhisper.releaseAllContexts(promise);
|
|
285
67
|
}
|
|
286
68
|
}
|