react-native-davoice-tts 1.0.410 → 1.0.412
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 +19 -0
- package/TTSRNBridge.podspec +1 -1
- package/android/libs/com/davoice/tts/1.0.0/tts-1.0.0.aar +0 -0
- package/android/libs/com/davoice/tts/1.0.0/tts-1.0.0.aar.md5 +1 -1
- package/android/libs/com/davoice/tts/1.0.0/tts-1.0.0.aar.sha1 +1 -1
- package/android/src/main/java/com/davoice/tts/rn/DaVoiceTTSBridge.java +64 -0
- package/ios/SpeechBridge/SpeechBridge.m +40 -0
- package/ios/TTSRNBridge/DaVoiceTTSBridge.m +40 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/DavoiceTTS +0 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/Headers/DavoiceTTS-Swift.h +28 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios.abi.json +505 -116
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios.private.swiftinterface +4 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios.swiftinterface +4 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/DavoiceTTS +0 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Headers/DavoiceTTS-Swift.h +56 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.abi.json +505 -116
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +4 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.swiftinterface +4 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.abi.json +505 -116
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +4 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +4 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/_CodeSignature/CodeDirectory +0 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/_CodeSignature/CodeRequirements-1 +0 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/_CodeSignature/CodeResources +33 -33
- package/package.json +1 -1
- package/speech/index.d.ts +33 -0
- package/speech/index.ts +65 -0
package/README.md
CHANGED
|
@@ -383,5 +383,24 @@ await speech.getVoiceAbilities("Rich");
|
|
|
383
383
|
await speech.changeVoice("Rich");
|
|
384
384
|
await speech.speak("Ok, [drill sergeant] Private, give me 50 push-ups! [regular] And you, Daniel, come with me.");
|
|
385
385
|
```
|
|
386
|
+
#### Voice cloning (your own voices)
|
|
387
|
+
|
|
388
|
+
Turn a recording into a voice: `createVoiceClone()` takes a wav, mp3, m4a,
|
|
389
|
+
aac, ogg or flac recording of one speaker and saves a scrambled `.emd` voice file.
|
|
390
|
+
It needs the voice encoder for the model's language, `embedder_<code>.dm`
|
|
391
|
+
(`embedder_en.dm` for English). Point `setExternalVoicesPath()` at the folder
|
|
392
|
+
and the voice is listed and selected like a bundled one (the file name is the
|
|
393
|
+
voice name; a bundled voice wins over a file with the same name).
|
|
394
|
+
|
|
395
|
+
```ts
|
|
396
|
+
const folder = `${RNFS.DocumentDirectoryPath}/voices`;
|
|
397
|
+
await speech.setExternalVoicesPath(folder);
|
|
398
|
+
const r = await speech.createVoiceClone(recordingPath, `${folder}/Me.emd`,
|
|
399
|
+
require('./assets/models/embedder_en.dm'));
|
|
400
|
+
// r.warnings is non-empty when the user should record again.
|
|
401
|
+
await speech.changeVoice('Me');
|
|
402
|
+
await speech.speak('Hello in my own voice.');
|
|
403
|
+
```
|
|
404
|
+
|
|
386
405
|
The existing speak speed argument still applies, with pitch preserved.
|
|
387
406
|
Rebuild the native app after updating the bundled AAR/XCFramework.
|
package/TTSRNBridge.podspec
CHANGED
|
@@ -2,7 +2,7 @@ require 'json'
|
|
|
2
2
|
|
|
3
3
|
Pod::Spec.new do |s|
|
|
4
4
|
s.name = "TTSRNBridge"
|
|
5
|
-
s.version = "1.0.
|
|
5
|
+
s.version = "1.0.412" # Update to your package version
|
|
6
6
|
s.summary = "TTS for React Native."
|
|
7
7
|
s.description = <<-DESC
|
|
8
8
|
A React Native module for tts .
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
18683c0c4349958c2b6966b5d642f099 tts-1.0.0.aar
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
21805062d457563e3b0a018334985553c1b55de9 tts-1.0.0.aar
|
|
@@ -387,6 +387,70 @@ public class DaVoiceTTSBridge extends ReactContextBaseJavaModule {
|
|
|
387
387
|
promise.resolve(abilities);
|
|
388
388
|
}
|
|
389
389
|
|
|
390
|
+
private static String stripFileScheme(String path) {
|
|
391
|
+
return path != null && path.startsWith("file://") ? Uri.parse(path).getPath() : path;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Any path form the app may pass (file path, file://, asset:/, http(s), RN release
|
|
395
|
+
// res/raw names, asset pack) -> readable local file, or null.
|
|
396
|
+
@Nullable
|
|
397
|
+
private File resolveAnyFile(String pathOrURL, @Nullable String defaultExt) {
|
|
398
|
+
if (pathOrURL == null || pathOrURL.trim().isEmpty()) return null;
|
|
399
|
+
File direct = new File(stripFileScheme(pathOrURL.trim()));
|
|
400
|
+
if (direct.isFile()) return direct;
|
|
401
|
+
try {
|
|
402
|
+
File f = resolveToLocalFileForRead(pathOrURL, defaultExt);
|
|
403
|
+
if (f != null && f.isFile()) return f;
|
|
404
|
+
} catch (Exception ignored) { }
|
|
405
|
+
File plain = resolvePlainModelToFile(pathOrURL.trim(), defaultExt);
|
|
406
|
+
return plain != null && plain.isFile() ? plain : null;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Also look for voices (<name>.emd) in the folder `path`; null/"" removes them.
|
|
410
|
+
@ReactMethod
|
|
411
|
+
public void setExternalVoicesPath(@Nullable String path, Promise promise) {
|
|
412
|
+
WritableArray names = Arguments.createArray();
|
|
413
|
+
try {
|
|
414
|
+
if (tts != null) {
|
|
415
|
+
for (String name : tts.setExternalVoicesPath(stripFileScheme(path))) names.pushString(name);
|
|
416
|
+
}
|
|
417
|
+
} catch (Exception e) {
|
|
418
|
+
Log.w(TAG, "setExternalVoicesPath failed: " + e.getMessage());
|
|
419
|
+
}
|
|
420
|
+
promise.resolve(names);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Reference audio (wav/mp3/m4a/...) -> scrambled .emd at outputPath, using the
|
|
424
|
+
// voice encoder embedder_<code>.dm. Runs off the bridge thread (takes seconds).
|
|
425
|
+
@ReactMethod
|
|
426
|
+
public void createVoiceClone(String inputAudio, String outputPath, String embedder, Promise promise) {
|
|
427
|
+
initExecutor.execute(() -> {
|
|
428
|
+
try {
|
|
429
|
+
if (inputAudio == null || outputPath == null || embedder == null) {
|
|
430
|
+
promise.reject("invalid_args", "createVoiceClone(inputAudio, outputPath, embedder)");
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
String ext = inputAudio.contains(".") ? inputAudio.substring(inputAudio.lastIndexOf('.') + 1) : "wav";
|
|
434
|
+
File input = resolveAnyFile(inputAudio, ext.length() <= 5 ? ext : "wav");
|
|
435
|
+
if (input == null) { promise.reject("input_missing", "Input audio missing/unresolvable: " + inputAudio); return; }
|
|
436
|
+
File embedderFile = resolveAnyFile(embedder, "dm");
|
|
437
|
+
String embedderPath = embedderFile != null ? embedderFile.getAbsolutePath() : embedder; // else an APK asset name
|
|
438
|
+
java.util.Map<String, Object> result =
|
|
439
|
+
tts.createVoiceClone(input.getAbsolutePath(), stripFileScheme(outputPath), embedderPath);
|
|
440
|
+
WritableMap map = Arguments.createMap();
|
|
441
|
+
map.putString("path", String.valueOf(result.get("path")));
|
|
442
|
+
map.putString("voice", String.valueOf(result.get("voice")));
|
|
443
|
+
WritableArray warnings = Arguments.createArray();
|
|
444
|
+
Object value = result.get("warnings");
|
|
445
|
+
if (value instanceof java.util.List) for (Object line : (java.util.List<?>) value) warnings.pushString(String.valueOf(line));
|
|
446
|
+
map.putArray("warnings", warnings);
|
|
447
|
+
promise.resolve(map);
|
|
448
|
+
} catch (Exception e) {
|
|
449
|
+
promise.reject("voice_clone_failed", e.getMessage(), e);
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
390
454
|
@ReactMethod
|
|
391
455
|
public void changeVoice(String name, Promise promise) {
|
|
392
456
|
try {
|
|
@@ -1216,6 +1216,46 @@ RCT_EXPORT_METHOD(getVoiceAbilities:(NSString *)name
|
|
|
1216
1216
|
resolve(self.tts && name ? ([self.tts getVoiceAbilities:name] ?: @[]) : @[]);
|
|
1217
1217
|
}
|
|
1218
1218
|
|
|
1219
|
+
RCT_EXPORT_METHOD(setExternalVoicesPath:(NSString *)path
|
|
1220
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
1221
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
1222
|
+
{
|
|
1223
|
+
DaVoiceTTS *tts = self.tts;
|
|
1224
|
+
if (!tts) { resolve(@[]); return; }
|
|
1225
|
+
NSString *folder = (path && (id)path != [NSNull null] && path.length) ? path : nil;
|
|
1226
|
+
if ([folder hasPrefix:@"file://"]) folder = [NSURL URLWithString:folder].path;
|
|
1227
|
+
[tts setExternalVoicesPath:folder completion:^(NSArray<NSString *> *names) { resolve(names ?: @[]); }];
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
// Reference audio (wav/mp3/m4a/...) -> scrambled .emd at outputPath, using the
|
|
1231
|
+
// voice encoder embedder_<code>.dm. Runs off the bridge queue (takes seconds).
|
|
1232
|
+
RCT_EXPORT_METHOD(createVoiceClone:(NSString *)inputAudio
|
|
1233
|
+
outputPath:(NSString *)outputPath
|
|
1234
|
+
embedder:(NSString *)embedder
|
|
1235
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
1236
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
1237
|
+
{
|
|
1238
|
+
if (!inputAudio || !outputPath || !embedder || (id)outputPath == [NSNull null]) {
|
|
1239
|
+
reject(@"invalid_args", @"createVoiceClone(inputAudio, outputPath, embedder)", nil);
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
|
1243
|
+
BOOL remoteInput = [inputAudio hasPrefix:@"http://"] || [inputAudio hasPrefix:@"https://"];
|
|
1244
|
+
BOOL remoteEmbedder = [embedder hasPrefix:@"http://"] || [embedder hasPrefix:@"https://"];
|
|
1245
|
+
NSURL *inputURL = [self resolveLocalURLFromPathOrURL:inputAudio];
|
|
1246
|
+
NSURL *embedderURL = [self resolveLocalURLFromPathOrURL:embedder];
|
|
1247
|
+
NSString *output = [outputPath hasPrefix:@"file://"] ? [NSURL URLWithString:outputPath].path : outputPath;
|
|
1248
|
+
NSError *error = nil;
|
|
1249
|
+
NSDictionary *result = (inputURL && embedderURL)
|
|
1250
|
+
? [DaVoiceTTS createVoiceClone:inputURL.path outputPath:output embedderPath:embedderURL.path error:&error]
|
|
1251
|
+
: nil;
|
|
1252
|
+
if (remoteInput && inputURL) [[NSFileManager defaultManager] removeItemAtURL:inputURL error:nil];
|
|
1253
|
+
if (remoteEmbedder && embedderURL) [[NSFileManager defaultManager] removeItemAtURL:embedderURL error:nil];
|
|
1254
|
+
if (result) resolve(result);
|
|
1255
|
+
else reject(@"voice_clone_failed", error.localizedDescription ?: @"Could not resolve the input audio or embedder path", error);
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1219
1259
|
RCT_EXPORT_METHOD(changeVoice:(NSString *)name
|
|
1220
1260
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
1221
1261
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
@@ -289,6 +289,46 @@ RCT_EXPORT_METHOD(getVoiceAbilities:(NSString *)name
|
|
|
289
289
|
resolve(self.ttsInstance && name ? ([self.ttsInstance getVoiceAbilities:name] ?: @[]) : @[]);
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
+
RCT_EXPORT_METHOD(setExternalVoicesPath:(NSString *)path
|
|
293
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
294
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
295
|
+
{
|
|
296
|
+
DaVoiceTTS *tts = self.ttsInstance;
|
|
297
|
+
if (!tts) { resolve(@[]); return; }
|
|
298
|
+
NSString *folder = (path && (id)path != [NSNull null] && path.length) ? path : nil;
|
|
299
|
+
if ([folder hasPrefix:@"file://"]) folder = [NSURL URLWithString:folder].path;
|
|
300
|
+
[tts setExternalVoicesPath:folder completion:^(NSArray<NSString *> *names) { resolve(names ?: @[]); }];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Reference audio (wav/mp3/m4a/...) -> scrambled .emd at outputPath, using the
|
|
304
|
+
// voice encoder embedder_<code>.dm. Runs off the bridge queue (takes seconds).
|
|
305
|
+
RCT_EXPORT_METHOD(createVoiceClone:(NSString *)inputAudio
|
|
306
|
+
outputPath:(NSString *)outputPath
|
|
307
|
+
embedder:(NSString *)embedder
|
|
308
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
309
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
310
|
+
{
|
|
311
|
+
if (!inputAudio || !outputPath || !embedder || (id)outputPath == [NSNull null]) {
|
|
312
|
+
reject(@"invalid_args", @"createVoiceClone(inputAudio, outputPath, embedder)", nil);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
|
316
|
+
BOOL remoteInput = [inputAudio hasPrefix:@"http://"] || [inputAudio hasPrefix:@"https://"];
|
|
317
|
+
BOOL remoteEmbedder = [embedder hasPrefix:@"http://"] || [embedder hasPrefix:@"https://"];
|
|
318
|
+
NSURL *inputURL = [self resolveLocalURLFromPathOrURL:inputAudio];
|
|
319
|
+
NSURL *embedderURL = [self resolveLocalURLFromPathOrURL:embedder];
|
|
320
|
+
NSString *output = [outputPath hasPrefix:@"file://"] ? [NSURL URLWithString:outputPath].path : outputPath;
|
|
321
|
+
NSError *error = nil;
|
|
322
|
+
NSDictionary *result = (inputURL && embedderURL)
|
|
323
|
+
? [DaVoiceTTS createVoiceClone:inputURL.path outputPath:output embedderPath:embedderURL.path error:&error]
|
|
324
|
+
: nil;
|
|
325
|
+
if (remoteInput && inputURL) [[NSFileManager defaultManager] removeItemAtURL:inputURL error:nil];
|
|
326
|
+
if (remoteEmbedder && embedderURL) [[NSFileManager defaultManager] removeItemAtURL:embedderURL error:nil];
|
|
327
|
+
if (result) resolve(result);
|
|
328
|
+
else reject(@"voice_clone_failed", error.localizedDescription ?: @"Could not resolve the input audio or embedder path", error);
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
292
332
|
RCT_EXPORT_METHOD(changeVoice:(NSString *)name
|
|
293
333
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
294
334
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
Binary file
|
|
@@ -361,6 +361,34 @@ SWIFT_CLASS("_TtC10DavoiceTTS10DaVoiceTTS")
|
|
|
361
361
|
/// 50! [regular] Thanks.” Unmatched tags use the default voice; tags are never
|
|
362
362
|
/// spoken. Empty for voices without variants, v1 models, or after destroy().
|
|
363
363
|
- (NSArray<NSString *> * _Nonnull)getVoiceAbilities:(NSString * _Nonnull)name SWIFT_WARN_UNUSED_RESULT;
|
|
364
|
+
/// Also look for voices in the folder <code>path</code>: <name>.emd / <name>.npy files,
|
|
365
|
+
/// e.g. made by createVoiceClone(). They are listed by getAllVoices()
|
|
366
|
+
/// after the bundled voices and selected with changeVoice() like them; a
|
|
367
|
+
/// bundled voice wins over an external file with the same name. Call it
|
|
368
|
+
/// again to pick up new files; nil or “” removes the external voices.
|
|
369
|
+
/// <code>completion</code> gets the external voice names found ([] for a v1 model or a
|
|
370
|
+
/// missing folder).
|
|
371
|
+
- (void)setExternalVoicesPath:(NSString * _Nullable)path completion:(void (^ _Nullable)(NSArray<NSString *> * _Nonnull))completion;
|
|
372
|
+
/// Voice cloning: creates a voice from a recording and saves it as a
|
|
373
|
+
/// scrambled .emd at <code>outputPath</code>.
|
|
374
|
+
/// <ul>
|
|
375
|
+
/// <li>
|
|
376
|
+
/// inputAudioPath: a recording of one speaker (wav, mp3, m4a/aac, caf,
|
|
377
|
+
/// aiff, flac, …). It is prepared for cloning automatically.
|
|
378
|
+
/// </li>
|
|
379
|
+
/// <li>
|
|
380
|
+
/// outputPath: the .emd file to write (”.emd” is added when missing), or a
|
|
381
|
+
/// folder (-> <folder>/<input name>.emd). The file name is the voice name.
|
|
382
|
+
/// </li>
|
|
383
|
+
/// <li>
|
|
384
|
+
/// embedderPath: the language’s voice encoder, embedder_<code>.dm
|
|
385
|
+
/// (e.g. embedder_en.dm); it is unscrambled in memory only.
|
|
386
|
+
/// </li>
|
|
387
|
+
/// </ul>
|
|
388
|
+
/// Returns [“path”, “voice”, “warnings”]; when “warnings” is non-empty, ask
|
|
389
|
+
/// the user to record again. Put the file in the folder given to
|
|
390
|
+
/// setExternalVoicesPath() (and call that again) to use it.
|
|
391
|
+
+ (NSDictionary<NSString *, id> * _Nullable)createVoiceClone:(NSString * _Nonnull)inputAudioPath outputPath:(NSString * _Nonnull)outputPath embedderPath:(NSString * _Nonnull)embedderPath error:(NSError * _Nullable * _Nullable)error SWIFT_WARN_UNUSED_RESULT;
|
|
364
392
|
- (void)changeVoice:(NSString * _Nonnull)name completion:(void (^ _Nonnull)(NSInteger))completion;
|
|
365
393
|
@property (nonatomic, copy) void (^ _Nullable onLastUtteranceFinished)(void);
|
|
366
394
|
+ (BOOL)activateLicenseWithLicenseKey:(NSString * _Nonnull)licenseKey SWIFT_WARN_UNUSED_RESULT;
|