@ztimson/utils 0.28.8 → 0.28.10
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/dist/index.cjs +160 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +160 -0
- package/dist/index.mjs.map +1 -1
- package/dist/string.d.ts +6 -0
- package/dist/tts.d.ts +57 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
package/dist/index.mjs
CHANGED
|
@@ -719,6 +719,10 @@ function pascalCase(str) {
|
|
|
719
719
|
if (!str) return "";
|
|
720
720
|
return wordSegments(str).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
|
|
721
721
|
}
|
|
722
|
+
function removeEmojis(str) {
|
|
723
|
+
const emojiRegex = /(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud83c[\udde6-\uddff]|[\ud83d[\ude00-\ude4f]|[\ud83d[\ude80-\udeff]|[\ud83c[\udd00-\uddff]|[\ud83d[\ude50-\ude7f]|[\u2600-\u26ff]|[\u2700-\u27bf]|[\ud83e[\udd00-\uddff]|[\ud83c[\udf00-\uffff]|[\ud83d[\ude00-\udeff]|[\ud83c[\udde6-\uddff])/g;
|
|
724
|
+
return str.replace(emojiRegex, "");
|
|
725
|
+
}
|
|
722
726
|
function randomHex(length) {
|
|
723
727
|
return Array(length).fill(null).map(() => Math.round(Math.random() * 15).toString(16)).join("");
|
|
724
728
|
}
|
|
@@ -2527,6 +2531,160 @@ ${err.message || err.toString()}`);
|
|
|
2527
2531
|
}
|
|
2528
2532
|
return process(template);
|
|
2529
2533
|
}
|
|
2534
|
+
class TTS {
|
|
2535
|
+
static QUALITY_PATTERNS = ["Google", "Microsoft", "Samantha", "Premium", "Natural", "Neural"];
|
|
2536
|
+
_currentUtterance = null;
|
|
2537
|
+
_voicesLoaded;
|
|
2538
|
+
_isStopping = false;
|
|
2539
|
+
_rate = 1;
|
|
2540
|
+
get rate() {
|
|
2541
|
+
return this._rate;
|
|
2542
|
+
}
|
|
2543
|
+
set rate(value) {
|
|
2544
|
+
this._rate = value;
|
|
2545
|
+
if (this._currentUtterance) this._currentUtterance.rate = value;
|
|
2546
|
+
}
|
|
2547
|
+
_pitch = 1;
|
|
2548
|
+
get pitch() {
|
|
2549
|
+
return this._pitch;
|
|
2550
|
+
}
|
|
2551
|
+
set pitch(value) {
|
|
2552
|
+
this._pitch = value;
|
|
2553
|
+
if (this._currentUtterance) this._currentUtterance.pitch = value;
|
|
2554
|
+
}
|
|
2555
|
+
_volume = 1;
|
|
2556
|
+
get volume() {
|
|
2557
|
+
return this._volume;
|
|
2558
|
+
}
|
|
2559
|
+
set volume(value) {
|
|
2560
|
+
this._volume = value;
|
|
2561
|
+
if (this._currentUtterance) this._currentUtterance.volume = value;
|
|
2562
|
+
}
|
|
2563
|
+
_voice;
|
|
2564
|
+
get voice() {
|
|
2565
|
+
return this._voice;
|
|
2566
|
+
}
|
|
2567
|
+
set voice(value) {
|
|
2568
|
+
this._voice = value;
|
|
2569
|
+
if (this._currentUtterance && value) this._currentUtterance.voice = value;
|
|
2570
|
+
}
|
|
2571
|
+
/** Create a TTS instance with optional configuration */
|
|
2572
|
+
constructor(config) {
|
|
2573
|
+
this._voicesLoaded = this.initializeVoices();
|
|
2574
|
+
if (config) {
|
|
2575
|
+
if (config.rate !== void 0) this._rate = config.rate;
|
|
2576
|
+
if (config.pitch !== void 0) this._pitch = config.pitch;
|
|
2577
|
+
if (config.volume !== void 0) this._volume = config.volume;
|
|
2578
|
+
this._voice = config.voice === null ? void 0 : config.voice || void 0;
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
/** Initializes voice loading and sets default voice if needed */
|
|
2582
|
+
initializeVoices() {
|
|
2583
|
+
return new Promise((resolve) => {
|
|
2584
|
+
const voices = window.speechSynthesis.getVoices();
|
|
2585
|
+
if (voices.length > 0) {
|
|
2586
|
+
if (!this._voice) this._voice = TTS.bestVoice();
|
|
2587
|
+
resolve();
|
|
2588
|
+
} else {
|
|
2589
|
+
const handler = () => {
|
|
2590
|
+
window.speechSynthesis.removeEventListener("voiceschanged", handler);
|
|
2591
|
+
if (!this._voice) this._voice = TTS.bestVoice();
|
|
2592
|
+
resolve();
|
|
2593
|
+
};
|
|
2594
|
+
window.speechSynthesis.addEventListener("voiceschanged", handler);
|
|
2595
|
+
}
|
|
2596
|
+
});
|
|
2597
|
+
}
|
|
2598
|
+
/**
|
|
2599
|
+
* Selects the best available TTS voice, prioritizing high-quality options
|
|
2600
|
+
* @param lang Speaking language
|
|
2601
|
+
* @returns Highest quality voice
|
|
2602
|
+
*/
|
|
2603
|
+
static bestVoice(lang = "en") {
|
|
2604
|
+
const voices = window.speechSynthesis.getVoices();
|
|
2605
|
+
for (const pattern of this.QUALITY_PATTERNS) {
|
|
2606
|
+
const voice = voices.find((v) => v.name.includes(pattern) && v.lang.startsWith(lang));
|
|
2607
|
+
if (voice) return voice;
|
|
2608
|
+
}
|
|
2609
|
+
return voices.find((v) => v.lang.startsWith(lang));
|
|
2610
|
+
}
|
|
2611
|
+
/** Cleans text for TTS by removing emojis, markdown and code block */
|
|
2612
|
+
static cleanText(text) {
|
|
2613
|
+
return removeEmojis(text).replace(/```[\s\S]*?```/g, " code block ").replace(/[#*_~`]/g, "");
|
|
2614
|
+
}
|
|
2615
|
+
/** Creates a speech utterance with current options */
|
|
2616
|
+
createUtterance(text) {
|
|
2617
|
+
const cleanedText = TTS.cleanText(text);
|
|
2618
|
+
const utterance = new SpeechSynthesisUtterance(cleanedText);
|
|
2619
|
+
const voice = this._voice || TTS.bestVoice();
|
|
2620
|
+
if (voice) utterance.voice = voice;
|
|
2621
|
+
utterance.rate = this._rate;
|
|
2622
|
+
utterance.pitch = this._pitch;
|
|
2623
|
+
utterance.volume = this._volume;
|
|
2624
|
+
return utterance;
|
|
2625
|
+
}
|
|
2626
|
+
/** Speaks text and returns a Promise which resolves once complete */
|
|
2627
|
+
async speak(text) {
|
|
2628
|
+
if (!text.trim()) return Promise.resolve();
|
|
2629
|
+
await this._voicesLoaded;
|
|
2630
|
+
return new Promise((resolve, reject) => {
|
|
2631
|
+
this._currentUtterance = this.createUtterance(text);
|
|
2632
|
+
this._currentUtterance.onend = () => {
|
|
2633
|
+
this._currentUtterance = null;
|
|
2634
|
+
resolve();
|
|
2635
|
+
};
|
|
2636
|
+
this._currentUtterance.onerror = (error) => {
|
|
2637
|
+
this._currentUtterance = null;
|
|
2638
|
+
if (this._isStopping && error.error === "interrupted") resolve();
|
|
2639
|
+
else reject(error);
|
|
2640
|
+
};
|
|
2641
|
+
window.speechSynthesis.speak(this._currentUtterance);
|
|
2642
|
+
});
|
|
2643
|
+
}
|
|
2644
|
+
/** Stops all TTS */
|
|
2645
|
+
stop() {
|
|
2646
|
+
this._isStopping = true;
|
|
2647
|
+
window.speechSynthesis.cancel();
|
|
2648
|
+
this._currentUtterance = null;
|
|
2649
|
+
setTimeout(() => {
|
|
2650
|
+
this._isStopping = false;
|
|
2651
|
+
}, 0);
|
|
2652
|
+
}
|
|
2653
|
+
/**
|
|
2654
|
+
* Initialize a stream that chunks text into sentences and speak them.
|
|
2655
|
+
*
|
|
2656
|
+
* @example
|
|
2657
|
+
* const stream = tts.speakStream();
|
|
2658
|
+
* stream.next("Hello ");
|
|
2659
|
+
* stream.next("World. How");
|
|
2660
|
+
* stream.next(" are you?");
|
|
2661
|
+
* await stream.done();
|
|
2662
|
+
*
|
|
2663
|
+
* @returns Object with next function for passing chunk of streamed text and done for completing the stream
|
|
2664
|
+
*/
|
|
2665
|
+
speakStream() {
|
|
2666
|
+
let buffer = "";
|
|
2667
|
+
let streamPromise = Promise.resolve();
|
|
2668
|
+
const sentenceRegex = /[^.!?\n]+[.!?\n]+/g;
|
|
2669
|
+
return {
|
|
2670
|
+
next: (text) => {
|
|
2671
|
+
buffer += text;
|
|
2672
|
+
const sentences = buffer.match(sentenceRegex);
|
|
2673
|
+
if (sentences) {
|
|
2674
|
+
sentences.forEach((sentence) => streamPromise = this.speak(sentence.trim()));
|
|
2675
|
+
buffer = buffer.replace(sentenceRegex, "");
|
|
2676
|
+
}
|
|
2677
|
+
},
|
|
2678
|
+
done: async () => {
|
|
2679
|
+
if (buffer.trim()) {
|
|
2680
|
+
streamPromise = this.speak(buffer.trim());
|
|
2681
|
+
buffer = "";
|
|
2682
|
+
}
|
|
2683
|
+
await streamPromise;
|
|
2684
|
+
}
|
|
2685
|
+
};
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2530
2688
|
var dist = {};
|
|
2531
2689
|
var persist = {};
|
|
2532
2690
|
var hasRequiredPersist;
|
|
@@ -2750,6 +2908,7 @@ export {
|
|
|
2750
2908
|
PromiseProgress,
|
|
2751
2909
|
SYMBOL_LIST,
|
|
2752
2910
|
ServiceUnavailableError,
|
|
2911
|
+
TTS,
|
|
2753
2912
|
Table,
|
|
2754
2913
|
TemplateError,
|
|
2755
2914
|
TooManyRequestsError,
|
|
@@ -2816,6 +2975,7 @@ export {
|
|
|
2816
2975
|
randomHex,
|
|
2817
2976
|
randomString,
|
|
2818
2977
|
randomStringBuilder,
|
|
2978
|
+
removeEmojis,
|
|
2819
2979
|
renderTemplate,
|
|
2820
2980
|
reservedIp,
|
|
2821
2981
|
search,
|