@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 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;