whisper.rn 0.3.0-rc.4 → 0.3.0-rc.6
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 +74 -11
- package/android/build.gradle +9 -0
- package/android/src/main/java/com/rnwhisper/Downloader.java +83 -0
- package/android/src/main/java/com/rnwhisper/RNWhisperPackage.java +33 -13
- package/android/src/main/java/com/rnwhisper/WhisperContext.java +9 -9
- package/android/src/main/jni/whisper/Whisper.mk +1 -1
- package/android/src/main/jni/whisper/jni.cpp +102 -0
- package/android/src/newarch/java/com/rnwhisper/RNWhisperModule.java +286 -0
- package/android/src/{main → oldarch}/java/com/rnwhisper/RNWhisperModule.java +59 -5
- package/ios/RNWhisper.mm +54 -8
- package/ios/RNWhisperDownloader.h +8 -0
- package/ios/RNWhisperDownloader.m +39 -0
- package/lib/commonjs/NativeRNWhisper.js +10 -0
- package/lib/commonjs/NativeRNWhisper.js.map +1 -0
- package/lib/commonjs/index.js +81 -23
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/NativeRNWhisper.js +3 -0
- package/lib/module/NativeRNWhisper.js.map +1 -0
- package/lib/module/index.js +73 -16
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/NativeRNWhisper.d.ts +65 -0
- package/lib/typescript/NativeRNWhisper.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +17 -43
- package/lib/typescript/index.d.ts.map +1 -1
- package/package.json +6 -1
- package/src/NativeRNWhisper.ts +79 -0
- package/src/index.ts +164 -122
|
@@ -0,0 +1,286 @@
|
|
|
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
|
+
import com.facebook.react.module.annotations.ReactModule;
|
|
17
|
+
|
|
18
|
+
import java.util.HashMap;
|
|
19
|
+
import java.util.Random;
|
|
20
|
+
import java.io.File;
|
|
21
|
+
import java.io.FileInputStream;
|
|
22
|
+
import java.io.PushbackInputStream;
|
|
23
|
+
|
|
24
|
+
@ReactModule(name = RNWhisperModule.NAME)
|
|
25
|
+
public class RNWhisperModule extends NativeRNWhisperSpec implements LifecycleEventListener {
|
|
26
|
+
public static final String NAME = "RNWhisper";
|
|
27
|
+
|
|
28
|
+
private ReactApplicationContext reactContext;
|
|
29
|
+
private Downloader downloader;
|
|
30
|
+
|
|
31
|
+
public RNWhisperModule(ReactApplicationContext reactContext) {
|
|
32
|
+
super(reactContext);
|
|
33
|
+
reactContext.addLifecycleEventListener(this);
|
|
34
|
+
this.reactContext = reactContext;
|
|
35
|
+
this.downloader = new Downloader(reactContext);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@Override
|
|
39
|
+
@NonNull
|
|
40
|
+
public String getName() {
|
|
41
|
+
return NAME;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@Override
|
|
45
|
+
public HashMap<String, Object> getTypedExportedConstants() {
|
|
46
|
+
HashMap<String, Object> constants = new HashMap<>();
|
|
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;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@ReactMethod
|
|
74
|
+
public void initContext(final ReadableMap options, final Promise promise) {
|
|
75
|
+
new AsyncTask<Void, Void, Integer>() {
|
|
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();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@ReactMethod
|
|
125
|
+
public void transcribeFile(double id, double jobId, String filePath, ReadableMap options, Promise promise) {
|
|
126
|
+
final WhisperContext context = contexts.get((int) id);
|
|
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();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@ReactMethod
|
|
183
|
+
public void startRealtimeTranscribe(double id, double jobId, ReadableMap options, Promise promise) {
|
|
184
|
+
final WhisperContext context = contexts.get((int) id);
|
|
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);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
@ReactMethod
|
|
202
|
+
public void abortTranscribe(double contextId, double jobId, Promise promise) {
|
|
203
|
+
WhisperContext context = contexts.get((int) contextId);
|
|
204
|
+
if (context == null) {
|
|
205
|
+
promise.reject("Context not found");
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
context.stopTranscribe((int) jobId);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
@ReactMethod
|
|
212
|
+
public void releaseContext(double id, Promise promise) {
|
|
213
|
+
final int contextId = (int) id;
|
|
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();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
@ReactMethod
|
|
244
|
+
public void releaseAllContexts(Promise promise) {
|
|
245
|
+
new AsyncTask<Void, Void, Void>() {
|
|
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();
|
|
285
|
+
}
|
|
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
|
}
|
package/ios/RNWhisper.mm
CHANGED
|
@@ -1,29 +1,42 @@
|
|
|
1
1
|
#import "RNWhisper.h"
|
|
2
2
|
#import "RNWhisperContext.h"
|
|
3
|
+
#import "RNWhisperDownloader.h"
|
|
3
4
|
#include <stdlib.h>
|
|
4
5
|
#include <string>
|
|
5
6
|
|
|
7
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
8
|
+
#import <RNWhisperSpec/RNWhisperSpec.h>
|
|
9
|
+
#endif
|
|
10
|
+
|
|
6
11
|
@implementation RNWhisper
|
|
7
12
|
|
|
8
13
|
NSMutableDictionary *contexts;
|
|
9
14
|
|
|
10
15
|
RCT_EXPORT_MODULE()
|
|
11
16
|
|
|
17
|
+
+ (BOOL)requiresMainQueueSetup
|
|
18
|
+
{
|
|
19
|
+
return NO;
|
|
20
|
+
}
|
|
21
|
+
|
|
12
22
|
- (NSDictionary *)constantsToExport
|
|
13
23
|
{
|
|
14
24
|
return @{
|
|
15
25
|
#if WHISPER_USE_COREML
|
|
16
|
-
@"
|
|
26
|
+
@"useCoreML": @YES,
|
|
27
|
+
#else
|
|
28
|
+
@"useCoreML": @NO,
|
|
17
29
|
#endif
|
|
18
30
|
#if WHISPER_COREML_ALLOW_FALLBACK
|
|
19
|
-
@"
|
|
31
|
+
@"coreMLAllowFallback": @YES,
|
|
32
|
+
#else
|
|
33
|
+
@"coreMLAllowFallback": @NO,
|
|
20
34
|
#endif
|
|
21
35
|
};
|
|
22
36
|
}
|
|
23
37
|
|
|
24
38
|
RCT_REMAP_METHOD(initContext,
|
|
25
|
-
|
|
26
|
-
withBundleResource:(BOOL)isBundleAsset
|
|
39
|
+
withOptions:(NSDictionary *)modelOptions
|
|
27
40
|
withResolver:(RCTPromiseResolveBlock)resolve
|
|
28
41
|
withRejecter:(RCTPromiseRejectBlock)reject)
|
|
29
42
|
{
|
|
@@ -31,7 +44,26 @@ RCT_REMAP_METHOD(initContext,
|
|
|
31
44
|
contexts = [[NSMutableDictionary alloc] init];
|
|
32
45
|
}
|
|
33
46
|
|
|
47
|
+
NSString *modelPath = [modelOptions objectForKey:@"filePath"];
|
|
48
|
+
BOOL isBundleAsset = [[modelOptions objectForKey:@"isBundleAsset"] boolValue];
|
|
49
|
+
|
|
50
|
+
// For support debug assets in development mode
|
|
51
|
+
BOOL downloadCoreMLAssets = [[modelOptions objectForKey:@"downloadCoreMLAssets"] boolValue];
|
|
52
|
+
if (downloadCoreMLAssets) {
|
|
53
|
+
NSArray *coreMLAssets = [modelOptions objectForKey:@"coreMLAssets"];
|
|
54
|
+
// Download coreMLAssets ([{ uri, filepath }])
|
|
55
|
+
for (NSDictionary *coreMLAsset in coreMLAssets) {
|
|
56
|
+
NSString *path = coreMLAsset[@"uri"];
|
|
57
|
+
if ([path hasPrefix:@"http://"] || [path hasPrefix:@"https://"]) {
|
|
58
|
+
[RNWhisperDownloader downloadFile:path toFile:coreMLAsset[@"filepath"]];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
34
63
|
NSString *path = modelPath;
|
|
64
|
+
if ([path hasPrefix:@"http://"] || [path hasPrefix:@"https://"]) {
|
|
65
|
+
path = [RNWhisperDownloader downloadFile:path toFile:nil];
|
|
66
|
+
}
|
|
35
67
|
if (isBundleAsset) {
|
|
36
68
|
path = [[NSBundle mainBundle] pathForResource:modelPath ofType:nil];
|
|
37
69
|
}
|
|
@@ -71,10 +103,13 @@ RCT_REMAP_METHOD(transcribeFile,
|
|
|
71
103
|
return;
|
|
72
104
|
}
|
|
73
105
|
|
|
74
|
-
|
|
106
|
+
NSString *path = waveFilePath;
|
|
107
|
+
if ([path hasPrefix:@"http://"] || [path hasPrefix:@"https://"]) {
|
|
108
|
+
path = [RNWhisperDownloader downloadFile:path toFile:nil];
|
|
109
|
+
}
|
|
75
110
|
|
|
76
111
|
int count = 0;
|
|
77
|
-
float *waveFile = [self decodeWaveFile:
|
|
112
|
+
float *waveFile = [self decodeWaveFile:path count:&count];
|
|
78
113
|
if (waveFile == nil) {
|
|
79
114
|
reject(@"whisper_error", @"Invalid file", nil);
|
|
80
115
|
return;
|
|
@@ -172,8 +207,9 @@ RCT_REMAP_METHOD(releaseAllContexts,
|
|
|
172
207
|
resolve(nil);
|
|
173
208
|
}
|
|
174
209
|
|
|
175
|
-
- (float *)decodeWaveFile:(
|
|
176
|
-
|
|
210
|
+
- (float *)decodeWaveFile:(NSString*)filePath count:(int *)count {
|
|
211
|
+
NSURL *url = [NSURL fileURLWithPath:filePath];
|
|
212
|
+
NSData *fileData = [NSData dataWithContentsOfURL:url];
|
|
177
213
|
if (fileData == nil) {
|
|
178
214
|
return nil;
|
|
179
215
|
}
|
|
@@ -206,6 +242,16 @@ RCT_REMAP_METHOD(releaseAllContexts,
|
|
|
206
242
|
|
|
207
243
|
[contexts removeAllObjects];
|
|
208
244
|
contexts = nil;
|
|
245
|
+
|
|
246
|
+
[RNWhisperDownloader clearCache];
|
|
209
247
|
}
|
|
210
248
|
|
|
249
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
250
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
251
|
+
(const facebook::react::ObjCTurboModule::InitParams &)params
|
|
252
|
+
{
|
|
253
|
+
return std::make_shared<facebook::react::NativeRNWhisperSpecJSI>(params);
|
|
254
|
+
}
|
|
255
|
+
#endif
|
|
256
|
+
|
|
211
257
|
@end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#import "RNWhisperDownloader.h"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* NOTE: This is simple downloader,
|
|
5
|
+
* the main purpose is supported load assets on RN Debug mode,
|
|
6
|
+
* so it's a very crude implementation.
|
|
7
|
+
*
|
|
8
|
+
* If you want to use file download in production to load model / audio files,
|
|
9
|
+
* I would recommend using react-native-fs or expo-file-system to manage the files.
|
|
10
|
+
*/
|
|
11
|
+
@implementation RNWhisperDownloader
|
|
12
|
+
|
|
13
|
+
+ (NSString *)downloadFile:(NSString *)urlString toFile:(NSString *)path {
|
|
14
|
+
NSURL *url = [NSURL URLWithString:urlString];
|
|
15
|
+
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"rnwhisper_debug_assets/"];
|
|
16
|
+
if (path) {
|
|
17
|
+
filePath = [filePath stringByAppendingPathComponent:path];
|
|
18
|
+
} else {
|
|
19
|
+
filePath = [filePath stringByAppendingPathComponent:[url lastPathComponent]];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
NSString *folderPath = [filePath stringByDeletingLastPathComponent];
|
|
23
|
+
if (![[NSFileManager defaultManager] fileExistsAtPath:folderPath]) {
|
|
24
|
+
[[NSFileManager defaultManager] createDirectoryAtPath:folderPath withIntermediateDirectories:YES attributes:nil error:nil];
|
|
25
|
+
}
|
|
26
|
+
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
|
|
27
|
+
return filePath;
|
|
28
|
+
}
|
|
29
|
+
NSData *urlData = [NSData dataWithContentsOfURL:url];
|
|
30
|
+
[urlData writeToFile:filePath atomically:YES];
|
|
31
|
+
return filePath;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
+ (void)clearCache {
|
|
35
|
+
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"rnwhisper_debug_assets/"];
|
|
36
|
+
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _reactNative = require("react-native");
|
|
8
|
+
var _default = _reactNative.TurboModuleRegistry.get('RNWhisper');
|
|
9
|
+
exports.default = _default;
|
|
10
|
+
//# sourceMappingURL=NativeRNWhisper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_reactNative","require","_default","TurboModuleRegistry","get","exports","default"],"sourceRoot":"../../src","sources":["NativeRNWhisper.ts"],"mappings":";;;;;;AACA,IAAAA,YAAA,GAAAC,OAAA;AAAkD,IAAAC,QAAA,GA6EnCC,gCAAmB,CAACC,GAAG,CAAO,WAAW,CAAC;AAAAC,OAAA,CAAAC,OAAA,GAAAJ,QAAA"}
|