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