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 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
- <img src="https://user-images.githubusercontent.com/3001525/225511664-8b2ba3ec-864d-4f55-bcb0-447aef168a32.jpeg" width="500" />
11
+ ## Screenshots
12
12
 
13
- > Run example with release mode on iPhone 13 Pro Max
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
- Currently there is no official way to get the Core ML models by URL, you will need to convert Core ML models by yourself. Please see [Core ML Support](https://github.com/ggerganov/whisper.cpp#core-ml-support) of whisper.cpp for more details.
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
- During the `.mlmodelc` is a directory, you will need to download 5 files:
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
- 'analytics/coremldata.bin',
143
+ // Not required:
144
+ // 'metadata.json', 'analytics/coremldata.bin',
103
145
  ]
104
146
  ```
105
147
 
106
- Or just add them to your app's bundle resources, like the example app does, but this would increase the app size significantly.
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 is using [react-native-fs](https://github.com/itinance/react-native-fs) to download the model file and audio file.
171
+ The example app provide a simple UI for testing the functions.
111
172
 
112
- Model: `base.en` in https://huggingface.co/datasets/ggerganov/whisper.cpp
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 transcribeFile(int jobId, String filePath, ReadableMap options) throws IOException, Exception {
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(new File(filePath));
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(File file) throws IOException {
387
+ public static float[] decodeWaveFile(InputStream inputStream) throws IOException {
387
388
  ByteArrayOutputStream baos = new ByteArrayOutputStream();
388
- try (InputStream inputStream = new FileInputStream(file)) {
389
- byte[] buffer = new byte[1024];
390
- int bytesRead;
391
- while ((bytesRead = inputStream.read(buffer)) != -1) {
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
- ifneq ($(APP_OPTIM),debug)
6
- LOCAL_CFLAGS += -O3 -DNDEBUG
7
- LOCAL_CFLAGS += -fvisibility=hidden -fvisibility-inlines-hidden
8
- LOCAL_CFLAGS += -ffunction-sections -fdata-sections
9
- LOCAL_LDFLAGS += -Wl,--gc-sections
10
- LOCAL_LDFLAGS += -Wl,--exclude-libs,ALL
11
- LOCAL_LDFLAGS += -flto
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 String modelPath, final boolean isBundleAsset, final Promise promise) {
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
- if (isBundleAsset) {
62
- context = WhisperContext.initContextWithAsset(reactContext.getAssets(), modelPath);
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(modelPath);
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
- return context.transcribeFile((int) jobId, filePath, options);
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 String modelPath, final boolean isBundleAsset, final Promise promise) {
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
- if (isBundleAsset) {
52
- context = WhisperContext.initContextWithAsset(reactContext.getAssets(), modelPath);
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(modelPath);
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
- return context.transcribeFile(jobId, filePath, options);
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
  }