react-native-davoice-tts 1.0.409 → 1.0.411

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.
Files changed (30) hide show
  1. package/README.md +20 -0
  2. package/TTSRNBridge.podspec +1 -1
  3. package/android/libs/com/davoice/tts/1.0.0/tts-1.0.0.aar +0 -0
  4. package/android/libs/com/davoice/tts/1.0.0/tts-1.0.0.aar.md5 +1 -1
  5. package/android/libs/com/davoice/tts/1.0.0/tts-1.0.0.aar.sha1 +1 -1
  6. package/android/src/main/java/com/davoice/tts/rn/DaVoiceTTSBridge.java +67 -0
  7. package/ios/SpeechBridge/SpeechBridge.m +40 -0
  8. package/ios/TTSRNBridge/DaVoiceTTSBridge.m +40 -0
  9. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/DavoiceTTS +0 -0
  10. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/Headers/DavoiceTTS-Swift.h +30 -0
  11. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios.abi.json +435 -60
  12. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios.private.swiftinterface +4 -0
  13. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  14. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios.swiftinterface +4 -0
  15. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/DavoiceTTS +0 -0
  16. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Headers/DavoiceTTS-Swift.h +60 -0
  17. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.abi.json +435 -60
  18. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +4 -0
  19. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  20. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.swiftinterface +4 -0
  21. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.abi.json +435 -60
  22. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +4 -0
  23. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  24. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +4 -0
  25. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/_CodeSignature/CodeDirectory +0 -0
  26. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/_CodeSignature/CodeRequirements-1 +0 -0
  27. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/_CodeSignature/CodeResources +33 -33
  28. package/package.json +1 -1
  29. package/speech/index.d.ts +39 -0
  30. package/speech/index.ts +73 -0
package/README.md CHANGED
@@ -383,5 +383,25 @@ 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 file, keeps the best ~6 s of speech (silence trimmed, long
390
+ pauses shortened, cut between words) and saves a scrambled `.emd` voice file.
391
+ It needs the voice encoder for the model's language, `embedder_<code>.dm`
392
+ (`embedder_en.dm` for English). Point `setExternalVoicesPath()` at the folder
393
+ and the voice is listed and selected like a bundled one (the file name is the
394
+ voice name; a bundled voice wins over a file with the same name).
395
+
396
+ ```ts
397
+ const folder = `${RNFS.DocumentDirectoryPath}/voices`;
398
+ await speech.setExternalVoicesPath(folder);
399
+ const r = await speech.createVoiceClone(recordingPath, `${folder}/Me.emd`,
400
+ require('./assets/models/embedder_en.dm'));
401
+ // r.warnings is non-empty when less than 4 s of speech remained.
402
+ await speech.changeVoice('Me');
403
+ await speech.speak('Hello in my own voice.');
404
+ ```
405
+
386
406
  The existing speak speed argument still applies, with pitch preserved.
387
407
  Rebuild the native app after updating the bundled AAR/XCFramework.
@@ -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.405" # Update to your package version
5
+ s.version = "1.0.409" # 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 .
@@ -1 +1 @@
1
- 135c22d998ff29b3e1aeabc5a1fd1392 tts-1.0.0.aar
1
+ a619b1a82c872eb95700500b006c8456 tts-1.0.0.aar
@@ -1 +1 @@
1
- 15e71c2af9e1a87f8a181497d0caafc0ef18e717 tts-1.0.0.aar
1
+ b8d12c2f14c766d8f75e4cd81759a31e14848f54 tts-1.0.0.aar
@@ -387,6 +387,73 @@ 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
+ map.putDouble("seconds", ((Number) result.get("seconds")).doubleValue());
444
+ for (String key : new String[]{"warnings", "report"}) {
445
+ WritableArray list = Arguments.createArray();
446
+ Object value = result.get(key);
447
+ if (value instanceof java.util.List) for (Object line : (java.util.List<?>) value) list.pushString(String.valueOf(line));
448
+ map.putArray(key, list);
449
+ }
450
+ promise.resolve(map);
451
+ } catch (Exception e) {
452
+ promise.reject("voice_clone_failed", e.getMessage(), e);
453
+ }
454
+ });
455
+ }
456
+
390
457
  @ReactMethod
391
458
  public void changeVoice(String name, Promise promise) {
392
459
  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)
@@ -361,6 +361,36 @@ 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
+ /// Creates a voice clone from reference audio and saves it as a
373
+ /// scrambled .emd at <code>outputPath</code>.
374
+ /// <ul>
375
+ /// <li>
376
+ /// inputAudioPath: wav, mp3, m4a/aac, caf, aiff, flac, … The best ~6 s
377
+ /// of speech is used: silence trimmed, long pauses shortened, cut at a gap
378
+ /// between words (never inside a word).
379
+ /// </li>
380
+ /// <li>
381
+ /// outputPath: the .emd file to write (”.emd” is added when missing), or a
382
+ /// folder (-> <folder>/<input name>.emd). The file name is the voice name.
383
+ /// </li>
384
+ /// <li>
385
+ /// embedderPath: the language’s voice encoder, embedder_<code>.dm
386
+ /// (e.g. embedder_en.dm); it is unscrambled in memory only.
387
+ /// </li>
388
+ /// </ul>
389
+ /// Returns [“path”, “voice”, “seconds”, “warnings”, “report”]; “warnings” is
390
+ /// non-empty when less than 4 s of speech remained or the cut had to fall
391
+ /// inside speech. Put the file in the folder given to setExternalVoicesPath()
392
+ /// (and call that again) to use it.
393
+ + (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
394
  - (void)changeVoice:(NSString * _Nonnull)name completion:(void (^ _Nonnull)(NSInteger))completion;
365
395
  @property (nonatomic, copy) void (^ _Nullable onLastUtteranceFinished)(void);
366
396
  + (BOOL)activateLicenseWithLicenseKey:(NSString * _Nonnull)licenseKey SWIFT_WARN_UNUSED_RESULT;