whisper.rn 0.3.0-rc.5 → 0.3.0-rc.7
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 +75 -14
- package/android/src/main/java/com/rnwhisper/Downloader.java +83 -0
- package/android/src/main/java/com/rnwhisper/WhisperContext.java +9 -9
- package/android/src/main/jni/whisper/Whisper.mk +11 -8
- package/android/src/main/jni/whisper/jni.cpp +102 -0
- package/android/src/newarch/java/com/rnwhisper/RNWhisperModule.java +59 -5
- package/android/src/oldarch/java/com/rnwhisper/RNWhisperModule.java +59 -5
- package/ios/RNWhisper.mm +32 -7
- package/ios/RNWhisperDownloader.h +8 -0
- package/ios/RNWhisperDownloader.m +39 -0
- package/lib/commonjs/NativeRNWhisper.js.map +1 -1
- package/lib/commonjs/index.js +61 -2
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/NativeRNWhisper.js.map +1 -1
- package/lib/module/index.js +62 -3
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/NativeRNWhisper.d.ts +11 -1
- package/lib/typescript/NativeRNWhisper.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +15 -4
- package/lib/typescript/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/NativeRNWhisper.ts +13 -1
- package/src/index.ts +156 -64
package/README.md
CHANGED
|
@@ -8,9 +8,12 @@ React Native binding of [whisper.cpp](https://github.com/ggerganov/whisper.cpp).
|
|
|
8
8
|
|
|
9
9
|
[whisper.cpp](https://github.com/ggerganov/whisper.cpp): High-performance inference of [OpenAI's Whisper](https://github.com/openai/whisper) automatic speech recognition (ASR) model
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
## Screenshots
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
| <img src="https://github.com/mybigday/whisper.rn/assets/3001525/2fea7b2d-c911-44fb-9afc-8efc7b594446" width="300" /> | <img src="https://github.com/mybigday/whisper.rn/assets/3001525/a5005a6c-44f7-4db9-95e8-0fd951a2e147" width="300" /> |
|
|
14
|
+
| :------------------------------------------: | :------------------------------------------: |
|
|
15
|
+
| iOS: Tested on iPhone 13 Pro Max | Android: Tested on Pixel 6 |
|
|
16
|
+
| (tiny.en, Core ML enabled, release mode + archive) | (tiny.en, armv8.2-a+fp16, release mode) |
|
|
14
17
|
|
|
15
18
|
## Installation
|
|
16
19
|
|
|
@@ -48,7 +51,6 @@ import { initWhisper } from 'whisper.rn'
|
|
|
48
51
|
|
|
49
52
|
const whisperContext = await initWhisper({
|
|
50
53
|
filePath: 'file://.../ggml-tiny.en.bin',
|
|
51
|
-
isBundleAsset: false, // Set to true if you want to load the model from bundle resources, the filePath will be like `ggml-tiny.en.bin`
|
|
52
54
|
})
|
|
53
55
|
|
|
54
56
|
const sampleFilePath = 'file://.../sample.wav'
|
|
@@ -81,6 +83,44 @@ In Android, you may need to request the microphone permission by [`PermissionAnd
|
|
|
81
83
|
|
|
82
84
|
Please visit the [Documentation](docs/) for more details.
|
|
83
85
|
|
|
86
|
+
## Usage with assets
|
|
87
|
+
|
|
88
|
+
You can also use the model file / audio file from assets:
|
|
89
|
+
|
|
90
|
+
```js
|
|
91
|
+
import { initWhisper } from 'whisper.rn'
|
|
92
|
+
|
|
93
|
+
const whisperContext = await initWhisper({
|
|
94
|
+
filePath: require('../assets/ggml-tiny.en.bin'),
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
const { stop, promise } =
|
|
98
|
+
whisperContext.transcribe(require('../assets/sample.wav'), options)
|
|
99
|
+
|
|
100
|
+
// ...
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
This requires editing the `metro.config.js` to support assets:
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
// ...
|
|
107
|
+
const defaultAssetExts = require('metro-config/src/defaults/defaults').assetExts
|
|
108
|
+
|
|
109
|
+
module.exports = {
|
|
110
|
+
// ...
|
|
111
|
+
resolver: {
|
|
112
|
+
// ...
|
|
113
|
+
assetExts: [
|
|
114
|
+
...defaultAssetExts,
|
|
115
|
+
'bin', // whisper.rn: ggml model binary
|
|
116
|
+
'mil', // whisper.rn: CoreML model asset
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Please note that it will significantly increase the size of the app in release mode.
|
|
123
|
+
|
|
84
124
|
## Core ML support
|
|
85
125
|
|
|
86
126
|
__*Platform: iOS 15.0+, tvOS 15.0+*__
|
|
@@ -89,33 +129,50 @@ To use Core ML on iOS, you will need to have the Core ML model files.
|
|
|
89
129
|
|
|
90
130
|
The `.mlmodelc` model files is load depend on the ggml model file path. For example, if your ggml model path is `ggml-tiny.en.bin`, the Core ML model path will be `ggml-tiny.en-encoder.mlmodelc`. Please note that the ggml model is still needed as decoder or encoder fallback.
|
|
91
131
|
|
|
92
|
-
|
|
132
|
+
The Core ML models are hosted here: https://huggingface.co/ggerganov/whisper.cpp/tree/main
|
|
133
|
+
|
|
134
|
+
If you want to download model at runtime, during the host file is archive, you will need to unzip the file to get the `.mlmodelc` directory, you can use library like [react-native-zip-archive](https://github.com/mockingbot/react-native-zip-archive), or host those individual files to download yourself.
|
|
93
135
|
|
|
94
|
-
|
|
136
|
+
The `.mlmodelc` is a directory, usually it includes 5 files (3 required):
|
|
95
137
|
|
|
96
138
|
```json5
|
|
97
139
|
[
|
|
98
140
|
'model.mil',
|
|
99
|
-
'metadata.json',
|
|
100
141
|
'coremldata.bin',
|
|
101
142
|
'weights/weight.bin',
|
|
102
|
-
|
|
143
|
+
// Not required:
|
|
144
|
+
// 'metadata.json', 'analytics/coremldata.bin',
|
|
103
145
|
]
|
|
104
146
|
```
|
|
105
147
|
|
|
106
|
-
Or just
|
|
148
|
+
Or just use `require` to bundle that in your app, like the example app does, but this would increase the app size significantly.
|
|
149
|
+
|
|
150
|
+
```js
|
|
151
|
+
const whisperContext = await initWhisper({
|
|
152
|
+
filePath: require('../assets/ggml-tiny.en.bin')
|
|
153
|
+
coreMLModelAsset:
|
|
154
|
+
Platform.OS === 'ios'
|
|
155
|
+
? {
|
|
156
|
+
filename: 'ggml-tiny.en-encoder.mlmodelc',
|
|
157
|
+
assets: [
|
|
158
|
+
require('../assets/ggml-tiny.en-encoder.mlmodelc/weights/weight.bin'),
|
|
159
|
+
require('../assets/ggml-tiny.en-encoder.mlmodelc/model.mil'),
|
|
160
|
+
require('../assets/ggml-tiny.en-encoder.mlmodelc/coremldata.bin'),
|
|
161
|
+
],
|
|
162
|
+
}
|
|
163
|
+
: undefined,
|
|
164
|
+
})
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
In real world, we recommended to split the asset imports into another platform specific file (e.g. `context-opts.ios.js`) to avoid these unused files in the bundle for Android.
|
|
107
168
|
|
|
108
169
|
## Run with example
|
|
109
170
|
|
|
110
|
-
The example app
|
|
171
|
+
The example app provide a simple UI for testing the functions.
|
|
111
172
|
|
|
112
|
-
|
|
173
|
+
Used Whisper model: `tiny.en` in https://huggingface.co/ggerganov/whisper.cpp
|
|
113
174
|
Sample file: `jfk.wav` in https://github.com/ggerganov/whisper.cpp/tree/master/samples
|
|
114
175
|
|
|
115
|
-
For test better performance on transcribe, you can run the app in Release mode.
|
|
116
|
-
- iOS: `yarn example ios --configuration Release`
|
|
117
|
-
- Android: `yarn example android --mode release`
|
|
118
|
-
|
|
119
176
|
Please follow the [Development Workflow section of contributing guide](./CONTRIBUTING.md#development-workflow) to run the example app.
|
|
120
177
|
|
|
121
178
|
## Mock `whisper.rn`
|
|
@@ -130,6 +187,10 @@ jest.mock('whisper.rn', () => require('whisper.rn/jest/mock'))
|
|
|
130
187
|
|
|
131
188
|
See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
|
|
132
189
|
|
|
190
|
+
## Troubleshooting
|
|
191
|
+
|
|
192
|
+
See the [troubleshooting](TROUBLESHOOTING.md) if you encounter any problem while using `whisper.rn`.
|
|
193
|
+
|
|
133
194
|
## License
|
|
134
195
|
|
|
135
196
|
MIT
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
package com.rnwhisper;
|
|
2
|
+
|
|
3
|
+
import android.content.Context;
|
|
4
|
+
|
|
5
|
+
import java.io.BufferedInputStream;
|
|
6
|
+
import java.io.FileOutputStream;
|
|
7
|
+
import java.io.File;
|
|
8
|
+
import java.io.InputStream;
|
|
9
|
+
import java.io.OutputStream;
|
|
10
|
+
import java.net.URL;
|
|
11
|
+
import java.net.URLConnection;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* NOTE: This is simple downloader,
|
|
15
|
+
* the main purpose is supported load assets on RN Debug mode,
|
|
16
|
+
* so it's a very crude implementation.
|
|
17
|
+
*
|
|
18
|
+
* If you want to use file download in production to load model / audio files,
|
|
19
|
+
* I would recommend using react-native-fs or expo-file-system to manage the files.
|
|
20
|
+
*/
|
|
21
|
+
public class Downloader {
|
|
22
|
+
private static Context context;
|
|
23
|
+
|
|
24
|
+
public Downloader(Context context) {
|
|
25
|
+
this.context = context;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private String getDir() {
|
|
29
|
+
String dir = context.getCacheDir().getAbsolutePath() + "/rnwhisper_debug_assets/";
|
|
30
|
+
File file = new File(dir);
|
|
31
|
+
if (!file.exists()) {
|
|
32
|
+
file.mkdirs();
|
|
33
|
+
}
|
|
34
|
+
return dir;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private boolean fileExists(String filename) {
|
|
38
|
+
File file = new File(getDir() + filename);
|
|
39
|
+
return file.exists();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public String downloadFile(String urlPath) throws Exception {
|
|
43
|
+
String filename = urlPath.substring(urlPath.lastIndexOf('/') + 1);
|
|
44
|
+
if (filename.contains("?")) {
|
|
45
|
+
filename = filename.substring(0, filename.indexOf("?"));
|
|
46
|
+
}
|
|
47
|
+
String filepath = getDir() + filename;
|
|
48
|
+
if (fileExists(filename)) {
|
|
49
|
+
return filepath;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
URL url = new URL(urlPath);
|
|
53
|
+
URLConnection connection = url.openConnection();
|
|
54
|
+
connection.connect();
|
|
55
|
+
InputStream input = new BufferedInputStream(url.openStream());
|
|
56
|
+
OutputStream output = new FileOutputStream(filepath);
|
|
57
|
+
byte data[] = new byte[1024];
|
|
58
|
+
int count;
|
|
59
|
+
while ((count = input.read(data)) != -1) {
|
|
60
|
+
output.write(data, 0, count);
|
|
61
|
+
}
|
|
62
|
+
output.flush();
|
|
63
|
+
output.close();
|
|
64
|
+
input.close();
|
|
65
|
+
} catch (Exception e) {
|
|
66
|
+
throw e;
|
|
67
|
+
}
|
|
68
|
+
return filepath;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private void deleteFile(File fileOrDir) {
|
|
72
|
+
if (fileOrDir.isDirectory()) {
|
|
73
|
+
for (File child : fileOrDir.listFiles()) {
|
|
74
|
+
deleteFile(child);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
fileOrDir.delete();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public void clearCache() {
|
|
81
|
+
deleteFile(new File(getDir()));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -26,6 +26,7 @@ import java.io.File;
|
|
|
26
26
|
import java.io.FileInputStream;
|
|
27
27
|
import java.io.IOException;
|
|
28
28
|
import java.io.InputStream;
|
|
29
|
+
import java.io.PushbackInputStream;
|
|
29
30
|
import java.nio.ByteBuffer;
|
|
30
31
|
import java.nio.ByteOrder;
|
|
31
32
|
import java.nio.ShortBuffer;
|
|
@@ -281,10 +282,10 @@ public class WhisperContext {
|
|
|
281
282
|
eventEmitter.emit(eventName, event);
|
|
282
283
|
}
|
|
283
284
|
|
|
284
|
-
public WritableMap
|
|
285
|
+
public WritableMap transcribeInputStream(int jobId, InputStream inputStream, ReadableMap options) throws IOException, Exception {
|
|
285
286
|
this.jobId = jobId;
|
|
286
287
|
isTranscribing = true;
|
|
287
|
-
float[] audioData = decodeWaveFile(
|
|
288
|
+
float[] audioData = decodeWaveFile(inputStream);
|
|
288
289
|
int code = full(jobId, options, audioData, audioData.length);
|
|
289
290
|
isTranscribing = false;
|
|
290
291
|
this.jobId = -1;
|
|
@@ -383,14 +384,12 @@ public class WhisperContext {
|
|
|
383
384
|
freeContext(context);
|
|
384
385
|
}
|
|
385
386
|
|
|
386
|
-
public static float[] decodeWaveFile(
|
|
387
|
+
public static float[] decodeWaveFile(InputStream inputStream) throws IOException {
|
|
387
388
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
baos.write(buffer, 0, bytesRead);
|
|
393
|
-
}
|
|
389
|
+
byte[] buffer = new byte[1024];
|
|
390
|
+
int bytesRead;
|
|
391
|
+
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
|
392
|
+
baos.write(buffer, 0, bytesRead);
|
|
394
393
|
}
|
|
395
394
|
ByteBuffer byteBuffer = ByteBuffer.wrap(baos.toByteArray());
|
|
396
395
|
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
|
|
@@ -472,6 +471,7 @@ public class WhisperContext {
|
|
|
472
471
|
|
|
473
472
|
protected static native long initContext(String modelPath);
|
|
474
473
|
protected static native long initContextWithAsset(AssetManager assetManager, String modelPath);
|
|
474
|
+
protected static native long initContextWithInputStream(PushbackInputStream inputStream);
|
|
475
475
|
protected static native int fullTranscribe(
|
|
476
476
|
int job_id,
|
|
477
477
|
long context,
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
WHISPER_LIB_DIR := $(LOCAL_PATH)/../../../../../cpp
|
|
2
2
|
LOCAL_LDLIBS := -landroid -llog
|
|
3
3
|
|
|
4
|
+
# NOTE: If you want to debug the native code, you can uncomment ifneq and endif
|
|
5
|
+
# ifneq ($(APP_OPTIM),debug)
|
|
6
|
+
|
|
4
7
|
# Make the final output library smaller by only keeping the symbols referenced from the app.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
endif
|
|
8
|
+
LOCAL_CFLAGS += -O3 -DNDEBUG
|
|
9
|
+
LOCAL_CFLAGS += -fvisibility=hidden -fvisibility-inlines-hidden
|
|
10
|
+
LOCAL_CFLAGS += -ffunction-sections -fdata-sections
|
|
11
|
+
LOCAL_LDFLAGS += -Wl,--gc-sections
|
|
12
|
+
LOCAL_LDFLAGS += -Wl,--exclude-libs,ALL
|
|
13
|
+
LOCAL_LDFLAGS += -flto
|
|
14
|
+
|
|
15
|
+
# endif
|
|
13
16
|
|
|
14
17
|
LOCAL_CFLAGS += -DSTDC_HEADERS -std=c11 -I $(WHISPER_LIB_DIR)
|
|
15
18
|
LOCAL_CPPFLAGS += -std=c++11 -I $(WHISPER_LIB_DIR)
|
|
@@ -20,6 +20,97 @@ static inline int min(int a, int b) {
|
|
|
20
20
|
return (a < b) ? a : b;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
// Load model from input stream (used for drawable / raw resources)
|
|
24
|
+
struct input_stream_context {
|
|
25
|
+
JNIEnv *env;
|
|
26
|
+
jobject input_stream;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
static size_t input_stream_read(void *ctx, void *output, size_t read_size) {
|
|
30
|
+
input_stream_context *context = (input_stream_context *)ctx;
|
|
31
|
+
JNIEnv *env = context->env;
|
|
32
|
+
jobject input_stream = context->input_stream;
|
|
33
|
+
jclass input_stream_class = env->GetObjectClass(input_stream);
|
|
34
|
+
|
|
35
|
+
jbyteArray buffer = env->NewByteArray(read_size);
|
|
36
|
+
jint bytes_read = env->CallIntMethod(
|
|
37
|
+
input_stream,
|
|
38
|
+
env->GetMethodID(input_stream_class, "read", "([B)I"),
|
|
39
|
+
buffer
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
if (bytes_read > 0) {
|
|
43
|
+
env->GetByteArrayRegion(buffer, 0, bytes_read, (jbyte *) output);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
env->DeleteLocalRef(buffer);
|
|
47
|
+
|
|
48
|
+
return bytes_read;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static bool input_stream_is_eof(void *ctx) {
|
|
52
|
+
input_stream_context *context = (input_stream_context *)ctx;
|
|
53
|
+
JNIEnv *env = context->env;
|
|
54
|
+
jobject input_stream = context->input_stream;
|
|
55
|
+
|
|
56
|
+
jclass input_stream_class = env->GetObjectClass(input_stream);
|
|
57
|
+
|
|
58
|
+
jbyteArray buffer = env->NewByteArray(1);
|
|
59
|
+
jint bytes_read = env->CallIntMethod(
|
|
60
|
+
input_stream,
|
|
61
|
+
env->GetMethodID(input_stream_class, "read", "([B)I"),
|
|
62
|
+
buffer
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
bool is_eof = (bytes_read == -1);
|
|
66
|
+
if (!is_eof) {
|
|
67
|
+
// If we successfully read a byte, "unread" it by pushing it back into the stream.
|
|
68
|
+
env->CallVoidMethod(
|
|
69
|
+
input_stream,
|
|
70
|
+
env->GetMethodID(input_stream_class, "unread", "([BII)V"),
|
|
71
|
+
buffer,
|
|
72
|
+
0,
|
|
73
|
+
1
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
env->DeleteLocalRef(buffer);
|
|
78
|
+
|
|
79
|
+
return is_eof;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
static void input_stream_close(void *ctx) {
|
|
83
|
+
input_stream_context *context = (input_stream_context *)ctx;
|
|
84
|
+
JNIEnv *env = context->env;
|
|
85
|
+
jobject input_stream = context->input_stream;
|
|
86
|
+
jclass input_stream_class = env->GetObjectClass(input_stream);
|
|
87
|
+
|
|
88
|
+
env->CallVoidMethod(
|
|
89
|
+
input_stream,
|
|
90
|
+
env->GetMethodID(input_stream_class, "close", "()V")
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
env->DeleteGlobalRef(input_stream);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
static struct whisper_context *whisper_init_from_input_stream(
|
|
97
|
+
JNIEnv *env,
|
|
98
|
+
jobject input_stream // PushbackInputStream
|
|
99
|
+
) {
|
|
100
|
+
input_stream_context *context = new input_stream_context;
|
|
101
|
+
context->env = env;
|
|
102
|
+
context->input_stream = env->NewGlobalRef(input_stream);
|
|
103
|
+
|
|
104
|
+
whisper_model_loader loader = {
|
|
105
|
+
.context = context,
|
|
106
|
+
.read = &input_stream_read,
|
|
107
|
+
.eof = &input_stream_is_eof,
|
|
108
|
+
.close = &input_stream_close
|
|
109
|
+
};
|
|
110
|
+
return whisper_init(&loader);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Load model from asset
|
|
23
114
|
static size_t asset_read(void *ctx, void *output, size_t read_size) {
|
|
24
115
|
return AAsset_read((AAsset *) ctx, output, read_size);
|
|
25
116
|
}
|
|
@@ -81,6 +172,17 @@ Java_com_rnwhisper_WhisperContext_initContextWithAsset(
|
|
|
81
172
|
return reinterpret_cast<jlong>(context);
|
|
82
173
|
}
|
|
83
174
|
|
|
175
|
+
JNIEXPORT jlong JNICALL
|
|
176
|
+
Java_com_rnwhisper_WhisperContext_initContextWithInputStream(
|
|
177
|
+
JNIEnv *env,
|
|
178
|
+
jobject thiz,
|
|
179
|
+
jobject input_stream
|
|
180
|
+
) {
|
|
181
|
+
UNUSED(thiz);
|
|
182
|
+
struct whisper_context *context = nullptr;
|
|
183
|
+
context = whisper_init_from_input_stream(env, input_stream);
|
|
184
|
+
return reinterpret_cast<jlong>(context);
|
|
185
|
+
}
|
|
84
186
|
|
|
85
187
|
JNIEXPORT jint JNICALL
|
|
86
188
|
Java_com_rnwhisper_WhisperContext_fullTranscribe(
|
|
@@ -17,17 +17,22 @@ import com.facebook.react.module.annotations.ReactModule;
|
|
|
17
17
|
|
|
18
18
|
import java.util.HashMap;
|
|
19
19
|
import java.util.Random;
|
|
20
|
+
import java.io.File;
|
|
21
|
+
import java.io.FileInputStream;
|
|
22
|
+
import java.io.PushbackInputStream;
|
|
20
23
|
|
|
21
24
|
@ReactModule(name = RNWhisperModule.NAME)
|
|
22
25
|
public class RNWhisperModule extends NativeRNWhisperSpec implements LifecycleEventListener {
|
|
23
26
|
public static final String NAME = "RNWhisper";
|
|
24
27
|
|
|
25
28
|
private ReactApplicationContext reactContext;
|
|
29
|
+
private Downloader downloader;
|
|
26
30
|
|
|
27
31
|
public RNWhisperModule(ReactApplicationContext reactContext) {
|
|
28
32
|
super(reactContext);
|
|
29
33
|
reactContext.addLifecycleEventListener(this);
|
|
30
34
|
this.reactContext = reactContext;
|
|
35
|
+
this.downloader = new Downloader(reactContext);
|
|
31
36
|
}
|
|
32
37
|
|
|
33
38
|
@Override
|
|
@@ -49,19 +54,48 @@ public class RNWhisperModule extends NativeRNWhisperSpec implements LifecycleEve
|
|
|
49
54
|
|
|
50
55
|
private HashMap<Integer, WhisperContext> contexts = new HashMap<>();
|
|
51
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;
|
|
71
|
+
}
|
|
72
|
+
|
|
52
73
|
@ReactMethod
|
|
53
|
-
public void initContext(final
|
|
74
|
+
public void initContext(final ReadableMap options, final Promise promise) {
|
|
54
75
|
new AsyncTask<Void, Void, Integer>() {
|
|
55
76
|
private Exception exception;
|
|
56
77
|
|
|
57
78
|
@Override
|
|
58
79
|
protected Integer doInBackground(Void... voids) {
|
|
59
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
|
+
|
|
60
89
|
long context;
|
|
61
|
-
|
|
62
|
-
|
|
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);
|
|
63
97
|
} else {
|
|
64
|
-
context = WhisperContext.initContext(
|
|
98
|
+
context = WhisperContext.initContext(modelFilePath);
|
|
65
99
|
}
|
|
66
100
|
if (context == 0) {
|
|
67
101
|
throw new Exception("Failed to initialize context");
|
|
@@ -108,7 +142,26 @@ public class RNWhisperModule extends NativeRNWhisperSpec implements LifecycleEve
|
|
|
108
142
|
@Override
|
|
109
143
|
protected WritableMap doInBackground(Void... voids) {
|
|
110
144
|
try {
|
|
111
|
-
|
|
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
|
+
);
|
|
112
165
|
} catch (Exception e) {
|
|
113
166
|
exception = e;
|
|
114
167
|
return null;
|
|
@@ -228,5 +281,6 @@ public class RNWhisperModule extends NativeRNWhisperSpec implements LifecycleEve
|
|
|
228
281
|
context.release();
|
|
229
282
|
}
|
|
230
283
|
contexts.clear();
|
|
284
|
+
downloader.clearCache();
|
|
231
285
|
}
|
|
232
286
|
}
|
|
@@ -18,17 +18,22 @@ import com.facebook.react.module.annotations.ReactModule;
|
|
|
18
18
|
|
|
19
19
|
import java.util.HashMap;
|
|
20
20
|
import java.util.Random;
|
|
21
|
+
import java.io.File;
|
|
22
|
+
import java.io.FileInputStream;
|
|
23
|
+
import java.io.PushbackInputStream;
|
|
21
24
|
|
|
22
25
|
@ReactModule(name = RNWhisperModule.NAME)
|
|
23
26
|
public class RNWhisperModule extends ReactContextBaseJavaModule implements LifecycleEventListener {
|
|
24
27
|
public static final String NAME = "RNWhisper";
|
|
25
28
|
|
|
26
29
|
private ReactApplicationContext reactContext;
|
|
30
|
+
private Downloader downloader;
|
|
27
31
|
|
|
28
32
|
public RNWhisperModule(ReactApplicationContext reactContext) {
|
|
29
33
|
super(reactContext);
|
|
30
34
|
reactContext.addLifecycleEventListener(this);
|
|
31
35
|
this.reactContext = reactContext;
|
|
36
|
+
this.downloader = new Downloader(reactContext);
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
@Override
|
|
@@ -39,19 +44,48 @@ public class RNWhisperModule extends ReactContextBaseJavaModule implements Lifec
|
|
|
39
44
|
|
|
40
45
|
private HashMap<Integer, WhisperContext> contexts = new HashMap<>();
|
|
41
46
|
|
|
47
|
+
private int getResourceIdentifier(String filePath) {
|
|
48
|
+
int identifier = reactContext.getResources().getIdentifier(
|
|
49
|
+
filePath,
|
|
50
|
+
"drawable",
|
|
51
|
+
reactContext.getPackageName()
|
|
52
|
+
);
|
|
53
|
+
if (identifier == 0) {
|
|
54
|
+
identifier = reactContext.getResources().getIdentifier(
|
|
55
|
+
filePath,
|
|
56
|
+
"raw",
|
|
57
|
+
reactContext.getPackageName()
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
return identifier;
|
|
61
|
+
}
|
|
62
|
+
|
|
42
63
|
@ReactMethod
|
|
43
|
-
public void initContext(final
|
|
64
|
+
public void initContext(final ReadableMap options, final Promise promise) {
|
|
44
65
|
new AsyncTask<Void, Void, Integer>() {
|
|
45
66
|
private Exception exception;
|
|
46
67
|
|
|
47
68
|
@Override
|
|
48
69
|
protected Integer doInBackground(Void... voids) {
|
|
49
70
|
try {
|
|
71
|
+
String modelPath = options.getString("filePath");
|
|
72
|
+
boolean isBundleAsset = options.getBoolean("isBundleAsset");
|
|
73
|
+
|
|
74
|
+
String modelFilePath = modelPath;
|
|
75
|
+
if (!isBundleAsset && (modelPath.startsWith("http://") || modelPath.startsWith("https://"))) {
|
|
76
|
+
modelFilePath = downloader.downloadFile(modelPath);
|
|
77
|
+
}
|
|
78
|
+
|
|
50
79
|
long context;
|
|
51
|
-
|
|
52
|
-
|
|
80
|
+
int resId = getResourceIdentifier(modelFilePath);
|
|
81
|
+
if (resId > 0) {
|
|
82
|
+
context = WhisperContext.initContextWithInputStream(
|
|
83
|
+
new PushbackInputStream(reactContext.getResources().openRawResource(resId))
|
|
84
|
+
);
|
|
85
|
+
} else if (isBundleAsset) {
|
|
86
|
+
context = WhisperContext.initContextWithAsset(reactContext.getAssets(), modelFilePath);
|
|
53
87
|
} else {
|
|
54
|
-
context = WhisperContext.initContext(
|
|
88
|
+
context = WhisperContext.initContext(modelFilePath);
|
|
55
89
|
}
|
|
56
90
|
if (context == 0) {
|
|
57
91
|
throw new Exception("Failed to initialize context");
|
|
@@ -98,7 +132,26 @@ public class RNWhisperModule extends ReactContextBaseJavaModule implements Lifec
|
|
|
98
132
|
@Override
|
|
99
133
|
protected WritableMap doInBackground(Void... voids) {
|
|
100
134
|
try {
|
|
101
|
-
|
|
135
|
+
String waveFilePath = filePath;
|
|
136
|
+
|
|
137
|
+
if (filePath.startsWith("http://") || filePath.startsWith("https://")) {
|
|
138
|
+
waveFilePath = downloader.downloadFile(filePath);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
int resId = getResourceIdentifier(waveFilePath);
|
|
142
|
+
if (resId > 0) {
|
|
143
|
+
return context.transcribeInputStream(
|
|
144
|
+
(int) jobId,
|
|
145
|
+
reactContext.getResources().openRawResource(resId),
|
|
146
|
+
options
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return context.transcribeInputStream(
|
|
151
|
+
(int) jobId,
|
|
152
|
+
new FileInputStream(new File(waveFilePath)),
|
|
153
|
+
options
|
|
154
|
+
);
|
|
102
155
|
} catch (Exception e) {
|
|
103
156
|
exception = e;
|
|
104
157
|
return null;
|
|
@@ -217,5 +270,6 @@ public class RNWhisperModule extends ReactContextBaseJavaModule implements Lifec
|
|
|
217
270
|
context.release();
|
|
218
271
|
}
|
|
219
272
|
contexts.clear();
|
|
273
|
+
downloader.clearCache();
|
|
220
274
|
}
|
|
221
275
|
}
|