ruiyun-human 1.0.10 → 1.0.12

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 ADDED
@@ -0,0 +1,7 @@
1
+ # shuzirenzujian
2
+
3
+ 数字人前端自助机组件
4
+ npm 发包
5
+ npm publish
6
+
7
+ npm所有人 睿政云-陆洋
@@ -385,6 +385,8 @@ var recorder = { exports: {} };
385
385
  var recorderExports = recorder.exports;
386
386
  var jsAudioRecorder = recorderExports;
387
387
  const Recorder = /* @__PURE__ */ getDefaultExportFromCjs(jsAudioRecorder);
388
+ const baseTTSUrl = "http://192.168.3.182:8002";
389
+ const ASR_URL = "http://192.168.3.182:9988";
388
390
  const _sfc_main = {
389
391
  __name: "index",
390
392
  props: {
@@ -647,22 +649,150 @@ const _sfc_main = {
647
649
  currentAudio.value = null;
648
650
  }
649
651
  }
650
- function waitWithSignal(ms, signal) {
651
- return new Promise((resolve, reject) => {
652
- if (signal == null ? void 0 : signal.aborted) {
653
- reject(new DOMException("Aborted", "AbortError"));
654
- return;
655
- }
656
- const timer = setTimeout(resolve, ms);
657
- const onAbort = () => {
658
- clearTimeout(timer);
659
- reject(new DOMException("Aborted", "AbortError"));
652
+ const voiceFileCache = /* @__PURE__ */ new Map();
653
+ const FILEDATA_META = { _type: "gradio.FileData" };
654
+ const uploadReferenceAudio = async (blob, filename, signal) => {
655
+ const form = new FormData();
656
+ form.append("files", blob, filename);
657
+ const res = await fetch(`${baseTTSUrl}/gradio_api/upload`, {
658
+ method: "POST",
659
+ body: form,
660
+ signal
661
+ });
662
+ if (!res.ok) throw new Error(`参考音频上传失败 HTTP ${res.status}`);
663
+ const paths = await res.json();
664
+ const path = Array.isArray(paths) ? paths[0] : paths;
665
+ if (!path) throw new Error("参考音频上传失败:服务未返回文件路径");
666
+ return { path, orig_name: filename, meta: FILEDATA_META };
667
+ };
668
+ const resolveVoiceFileData = async (voiceSource, signal) => {
669
+ if (voiceSource && typeof voiceSource === "object" && !Array.isArray(voiceSource) && voiceSource.path) {
670
+ return {
671
+ path: voiceSource.path,
672
+ orig_name: voiceSource.orig_name || "reference.mp3",
673
+ meta: FILEDATA_META
660
674
  };
661
- signal == null ? void 0 : signal.addEventListener("abort", onAbort, { once: true });
675
+ }
676
+ if (voiceSource instanceof Blob) {
677
+ const name = voiceSource.name || `reference_${Date.now()}.mp3`;
678
+ return await uploadReferenceAudio(voiceSource, name, signal);
679
+ }
680
+ if (typeof voiceSource === "string" && voiceSource) {
681
+ if (voiceSource.startsWith(baseTTSUrl)) {
682
+ const match = voiceSource.match(/\/gradio_api\/file=(.+)$/);
683
+ if (match) {
684
+ const path = decodeURIComponent(match[1]);
685
+ const origName = path.split(/[\\/]/).pop() || "reference.mp3";
686
+ return { path, orig_name: origName, meta: FILEDATA_META };
687
+ }
688
+ }
689
+ const res = await fetch(voiceSource, { signal });
690
+ if (!res.ok) throw new Error(`参考音频下载失败 HTTP ${res.status}`);
691
+ const blob = await res.blob();
692
+ const fileName = decodeURIComponent(voiceSource.split("?")[0].split("/").pop()) || "reference.mp3";
693
+ return await uploadReferenceAudio(blob, fileName, signal);
694
+ }
695
+ throw new Error("不支持的参考音频来源:voiceUrl 应为音频 URL 字符串、File/Blob 或 { path } 对象");
696
+ };
697
+ const submitCloneJob = async (data, signal) => {
698
+ const res = await fetch(`${baseTTSUrl}/gradio_api/call/_clone_fn`, {
699
+ method: "POST",
700
+ headers: { "Content-Type": "application/json" },
701
+ body: JSON.stringify({ data }),
702
+ signal
662
703
  });
663
- }
704
+ if (!res.ok) {
705
+ const text = await res.text().catch(() => "");
706
+ throw new Error(`克隆任务提交失败 HTTP ${res.status} ${text}`);
707
+ }
708
+ const json = await res.json();
709
+ if (!(json == null ? void 0 : json.event_id)) throw new Error("克隆任务提交失败:未返回 event_id");
710
+ return json.event_id;
711
+ };
712
+ const waitCloneResult = async (eventId, signal) => {
713
+ const res = await fetch(`${baseTTSUrl}/gradio_api/call/_clone_fn/${eventId}`, {
714
+ signal,
715
+ headers: { Accept: "text/event-stream" }
716
+ });
717
+ if (!res.ok) throw new Error(`合成结果监听失败 HTTP ${res.status}`);
718
+ const reader = res.body.getReader();
719
+ const decoder = new TextDecoder("utf-8");
720
+ let buffer = "";
721
+ let eventName = "message";
722
+ while (true) {
723
+ const { done, value } = await reader.read();
724
+ if (done) break;
725
+ buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
726
+ let sep;
727
+ while ((sep = buffer.indexOf("\n\n")) !== -1) {
728
+ const frame = buffer.slice(0, sep);
729
+ buffer = buffer.slice(sep + 2);
730
+ const dataLines = [];
731
+ for (const line of frame.split("\n")) {
732
+ if (line.startsWith("event:")) eventName = line.slice(6).trim();
733
+ else if (line.startsWith("data:")) dataLines.push(line.slice(5).replace(/^ /, ""));
734
+ }
735
+ if (dataLines.length === 0) continue;
736
+ const payload = dataLines.join("\n");
737
+ if (eventName === "complete") {
738
+ return JSON.parse(payload);
739
+ }
740
+ if (eventName === "error") {
741
+ let msg = payload;
742
+ try {
743
+ const parsed = JSON.parse(payload);
744
+ msg = typeof parsed === "string" ? parsed : (parsed == null ? void 0 : parsed.message) || payload;
745
+ } catch {
746
+ }
747
+ throw new Error(msg || "OmniVoice 合成失败");
748
+ }
749
+ eventName = "message";
750
+ }
751
+ }
752
+ throw new Error("OmniVoice 合成结果流提前中断");
753
+ };
754
+ const playAudioFromUrl = async (audioUrl, requestId, signal) => {
755
+ const audioRes = await fetch(audioUrl, { signal });
756
+ if (!audioRes.ok) throw new Error(`合成音频下载失败 HTTP ${audioRes.status}`);
757
+ const audioBlob = await audioRes.blob();
758
+ const objectUrl = URL.createObjectURL(audioBlob);
759
+ if (signal.aborted || currentTtsRequestId.value !== requestId) {
760
+ URL.revokeObjectURL(objectUrl);
761
+ return;
762
+ }
763
+ const audio = new Audio(objectUrl);
764
+ currentAudio.value = audio;
765
+ const cleanup = () => {
766
+ URL.revokeObjectURL(objectUrl);
767
+ if (currentAudio.value === audio) currentAudio.value = null;
768
+ };
769
+ audio.onplaying = () => {
770
+ console.log("[OmniVoice] 音频开始播放");
771
+ speak();
772
+ };
773
+ audio.onended = () => {
774
+ console.log("[OmniVoice] 音频播放结束");
775
+ cleanup();
776
+ stopSpeaking();
777
+ };
778
+ audio.onerror = () => {
779
+ console.error("[OmniVoice] 音频播放失败");
780
+ cleanup();
781
+ stopSpeaking();
782
+ };
783
+ await audio.play();
784
+ };
664
785
  const playTTS = async (text) => {
665
- var _a, _b, _c, _d, _e, _f, _g, _h;
786
+ var _a, _b, _c, _d;
787
+ console.log(props.humans);
788
+ let voiceUrl = "";
789
+ let voiceUrl2 = "http://192.168.3.182:8002/gradio_api/file=C:\\Users\\Administrator\\AppData\\Local\\Temp\\gradio\\4eef6d9157de756de02bccca2f66389382e2ac4acb0c674981aa9fe13dcc294c\\李梓萌.mp3";
790
+ let voiceUrl1 = "http://192.168.3.182:8002/gradio_api/file=C:\\Users\\Administrator\\AppData\\Local\\Temp\\gradio\\ff2665f538a4b0e5efbcd79f9f699236f5f0e52cad7e17f0664ddf431169be30\\guozj.mp3";
791
+ if (props.humans[0].speakSet.sex == "Female / 女") {
792
+ voiceUrl = voiceUrl2;
793
+ } else {
794
+ voiceUrl = voiceUrl1;
795
+ }
666
796
  if (!text || !((_a = currentHuman.value) == null ? void 0 : _a.isMuted)) return;
667
797
  abortTTS();
668
798
  ttsAbortController = new AbortController();
@@ -672,133 +802,33 @@ const _sfc_main = {
672
802
  try {
673
803
  const cleanText = text.replace(/```[\s\S]*?```/g, "").replace(/`[^`]+`/g, "").replace(/[#*_~]/g, "").replace(/\n+/g, ",").replace(/\s+/g, " ").replace(/[,。!?]{2,}/g, ",").trim();
674
804
  if (!cleanText) return;
675
- const baseUrl = "/baseUrl11";
676
- const baseurl22 = "/baseUrl22";
677
- const sessionHash = "session_" + Date.now();
678
- let speed = ((_c = (_b = currentHuman.value) == null ? void 0 : _b.speakSet) == null ? void 0 : _c.speed) || 1;
679
- let age = ((_e = (_d = currentHuman.value) == null ? void 0 : _d.speakSet) == null ? void 0 : _e.age) || "Young Adult / 青年";
680
- let sex = ((_g = (_f = currentHuman.value) == null ? void 0 : _f.speakSet) == null ? void 0 : _g.sex) || "Female / 女";
681
- const data = [cleanText, "Auto", 32, 2, true, speed, null, true, true, sex, age, "Auto", "Auto", "Auto", "Auto"];
682
- console.log("[OmniVoice] 提交请求:", { text: cleanText, length: cleanText.length, fn_index: 1 });
683
- const joinRes = await fetch(`${baseUrl}/gradio_api/queue/join`, {
684
- method: "POST",
685
- headers: { "Content-Type": "application/json" },
686
- body: JSON.stringify({ data, session_hash: sessionHash, fn_index: 1, trigger_id: 50 }),
687
- signal
688
- });
689
- const joinData = await joinRes.json();
690
- console.log("[OmniVoice] Step1 响应:", joinData);
691
- console.log("[OmniVoice] Step2: 轮询等待结果");
692
- let audioUrl = "";
693
- let maxPolls = 60;
694
- let pollInterval = 1e3;
695
- while (!audioUrl && maxPolls > 0) {
696
- if (signal.aborted || currentTtsRequestId.value !== requestId) return;
697
- await waitWithSignal(pollInterval, signal);
698
- maxPolls--;
699
- try {
700
- const pollRes = await fetch(`${baseUrl}/gradio_api/queue/data?session_hash=${sessionHash}`, {
701
- signal
702
- });
703
- const pollText = await pollRes.text();
704
- const lines = pollText.split("\n");
705
- for (const line of lines) {
706
- const trimLine = line.trim();
707
- if (!trimLine.startsWith("data: ")) continue;
708
- try {
709
- const json = JSON.parse(trimLine.replace("data: ", ""));
710
- console.log("[OmniVoice] 轮询消息:", json);
711
- if (json.msg === "process_completed" && ((_h = json.output) == null ? void 0 : _h.data)) {
712
- const fileData = json.output.data[0];
713
- console.log("[OmniVoice] 获取到音频:", fileData);
714
- if (fileData == null ? void 0 : fileData.url) audioUrl = fileData.url;
715
- else if (fileData == null ? void 0 : fileData.path) audioUrl = `${baseUrl}/gradio_api/file=${fileData.path}`;
716
- else if (typeof fileData === "string") audioUrl = fileData.startsWith("http") ? fileData : `${baseUrl}/gradio_api/file=${fileData}`;
717
- else if (Array.isArray(fileData) && fileData.length >= 2) {
718
- const samplingRate = fileData[0];
719
- const waveform = fileData[1];
720
- console.log("[OmniVoice] 直接获取到音频数组:", { samplingRate, waveformLength: waveform == null ? void 0 : waveform.length });
721
- if (samplingRate && waveform && Array.isArray(waveform)) {
722
- const audioBuffer = new ArrayBuffer(waveform.length * 2);
723
- const view = new DataView(audioBuffer);
724
- for (let i = 0; i < waveform.length; i++) {
725
- view.setInt16(i * 2, waveform[i], true);
726
- }
727
- const wavBlob = createWavBlob(audioBuffer, samplingRate, 1, 16);
728
- const audioObjectUrl = URL.createObjectURL(wavBlob);
729
- if (signal.aborted || currentTtsRequestId.value !== requestId) {
730
- URL.revokeObjectURL(audioObjectUrl);
731
- return;
732
- }
733
- currentAudio.value = new Audio(audioObjectUrl);
734
- currentAudio.value.onplaying = () => {
735
- console.log("[OmniVoice] 音频开始播放");
736
- speak();
737
- };
738
- currentAudio.value.onended = () => {
739
- console.log("[OmniVoice] 音频播放结束");
740
- stopSpeaking();
741
- currentAudio.value = null;
742
- URL.revokeObjectURL(audioObjectUrl);
743
- };
744
- currentAudio.value.onerror = () => {
745
- console.error("[OmniVoice] 音频播放失败");
746
- stopSpeaking();
747
- currentAudio.value = null;
748
- URL.revokeObjectURL(audioObjectUrl);
749
- };
750
- await currentAudio.value.play();
751
- return;
752
- }
753
- }
754
- break;
755
- } else if (json.msg === "process_error") {
756
- console.error("[OmniVoice] 处理错误:", json);
757
- standby();
758
- return;
759
- }
760
- } catch (e) {
761
- console.error("[OmniVoice] 解析轮询消息失败:", e);
762
- }
763
- }
764
- } catch (e) {
765
- if ((e == null ? void 0 : e.name) === "AbortError") throw e;
766
- console.warn("[OmniVoice] 轮询失败:", e.message);
767
- }
768
- }
769
- if (audioUrl) {
770
- if (audioUrl.startsWith("/")) audioUrl = baseUrl + audioUrl;
771
- console.log("[OmniVoice] 下载音频:", audioUrl);
772
- if (signal.aborted || currentTtsRequestId.value !== requestId) return;
773
- const audioRes = await fetch(audioUrl, { signal });
774
- const audioBlob = await audioRes.blob();
775
- const audioObjectUrl = URL.createObjectURL(audioBlob);
776
- if (signal.aborted || currentTtsRequestId.value !== requestId) {
777
- URL.revokeObjectURL(audioObjectUrl);
778
- return;
779
- }
780
- currentAudio.value = new Audio(audioObjectUrl);
781
- currentAudio.value.onplaying = () => {
782
- console.log("[OmniVoice] 音频开始播放");
783
- speak();
784
- };
785
- currentAudio.value.onended = () => {
786
- console.log("[OmniVoice] 音频播放结束");
787
- stopSpeaking();
788
- currentAudio.value = null;
789
- URL.revokeObjectURL(audioObjectUrl);
790
- };
791
- currentAudio.value.onerror = () => {
792
- console.error("[OmniVoice] 音频播放失败");
793
- stopSpeaking();
794
- currentAudio.value = null;
795
- URL.revokeObjectURL(audioObjectUrl);
796
- };
797
- await currentAudio.value.play();
798
- } else {
799
- console.error("[OmniVoice] 未获取到音频");
805
+ const speed = Number((_c = (_b = currentHuman.value) == null ? void 0 : _b.speakSet) == null ? void 0 : _c.speed) || 1;
806
+ const voiceSource = voiceUrl || ((_d = currentHuman.value) == null ? void 0 : _d.voiceUrl);
807
+ if (!voiceSource) {
808
+ console.error("[OmniVoice] 未配置参考音频 voiceUrl,无法进行音色克隆");
800
809
  standby();
810
+ return;
811
+ }
812
+ let refFile = voiceFileCache.get(voiceSource);
813
+ if (!refFile) {
814
+ console.log("[OmniVoice] 准备参考音频:", typeof voiceSource === "string" ? voiceSource : "Blob/File");
815
+ refFile = await resolveVoiceFileData(voiceSource, signal);
816
+ voiceFileCache.set(voiceSource, refFile);
801
817
  }
818
+ console.log("[OmniVoice] 参考音频就绪:", refFile);
819
+ const data = [cleanText, "Auto", refFile, "", null, 32, 2, true, speed, null, true, true];
820
+ console.log("[OmniVoice] 提交克隆请求:", { text: cleanText, length: cleanText.length });
821
+ const eventId = await submitCloneJob(data, signal);
822
+ const outputs = await waitCloneResult(eventId, signal);
823
+ const audioFile = outputs == null ? void 0 : outputs[0];
824
+ const statusText = outputs == null ? void 0 : outputs[1];
825
+ console.log("[OmniVoice] 合成结果:", { audioFile, statusText });
826
+ if (signal.aborted || currentTtsRequestId.value !== requestId) return;
827
+ if (!audioFile || !audioFile.path && !audioFile.url) {
828
+ throw new Error(statusText || "OmniVoice 未返回合成音频");
829
+ }
830
+ const audioUrl = audioFile.url && audioFile.url.startsWith("http") ? audioFile.url : `${baseTTSUrl}/gradio_api/file=${audioFile.path}`;
831
+ await playAudioFromUrl(audioUrl, requestId, signal);
802
832
  } catch (e) {
803
833
  if ((e == null ? void 0 : e.name) === "AbortError") {
804
834
  console.log("[OmniVoice] TTS已取消");
@@ -835,7 +865,7 @@ const _sfc_main = {
835
865
  const formData = new FormData();
836
866
  formData.append("file", blob, "recorded_audio.wav");
837
867
  try {
838
- const res = await fetch(baseurl2 + "/transcribe", {
868
+ const res = await fetch(ASR_URL + "/transcribe", {
839
869
  method: "POST",
840
870
  body: formData
841
871
  });
@@ -848,42 +878,6 @@ const _sfc_main = {
848
878
  return { status: e, text: "" };
849
879
  }
850
880
  };
851
- const createWavBlob = (audioBuffer, sampleRate, channels, bitsPerSample) => {
852
- const bytesPerSample = bitsPerSample / 8;
853
- const blockAlign = channels * bytesPerSample;
854
- const dataSize = audioBuffer.byteLength;
855
- const buffer = new ArrayBuffer(44 + dataSize);
856
- const view = new DataView(buffer);
857
- view.setUint8(0, 82);
858
- view.setUint8(1, 73);
859
- view.setUint8(2, 70);
860
- view.setUint8(3, 70);
861
- view.setUint32(4, 36 + dataSize, true);
862
- view.setUint8(8, 87);
863
- view.setUint8(9, 65);
864
- view.setUint8(10, 86);
865
- view.setUint8(11, 69);
866
- view.setUint8(12, 102);
867
- view.setUint8(13, 109);
868
- view.setUint8(14, 116);
869
- view.setUint8(15, 32);
870
- view.setUint32(16, 16, true);
871
- view.setUint16(20, 1, true);
872
- view.setUint16(22, channels, true);
873
- view.setUint32(24, sampleRate, true);
874
- view.setUint32(28, sampleRate * blockAlign, true);
875
- view.setUint16(32, blockAlign, true);
876
- view.setUint16(34, bitsPerSample, true);
877
- view.setUint8(36, 100);
878
- view.setUint8(37, 97);
879
- view.setUint8(38, 116);
880
- view.setUint8(39, 97);
881
- view.setUint32(40, dataSize, true);
882
- const audioData = new Uint8Array(audioBuffer);
883
- const destData = new Uint8Array(buffer, 44);
884
- destData.set(audioData);
885
- return new Blob([buffer], { type: "audio/wav" });
886
- };
887
881
  let recorder2 = vue.ref(null);
888
882
  const initRecorder = () => {
889
883
  console.log("initRecorder");
@@ -935,7 +929,7 @@ const _sfc_main = {
935
929
  };
936
930
  }
937
931
  };
938
- const DigitalHuman = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-f9bef1a6"]]);
932
+ const DigitalHuman = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-9c25016c"]]);
939
933
  DigitalHuman.install = (app) => app.component("DigitalHuman", DigitalHuman);
940
934
  const components = [DemoButton, DemoInput, DigitalHuman];
941
935
  const install = (app) => {
@@ -383,6 +383,8 @@ var recorder = { exports: {} };
383
383
  var recorderExports = recorder.exports;
384
384
  var jsAudioRecorder = recorderExports;
385
385
  const Recorder = /* @__PURE__ */ getDefaultExportFromCjs(jsAudioRecorder);
386
+ const baseTTSUrl = "http://192.168.3.182:8002";
387
+ const ASR_URL = "http://192.168.3.182:9988";
386
388
  const _sfc_main = {
387
389
  __name: "index",
388
390
  props: {
@@ -645,22 +647,150 @@ const _sfc_main = {
645
647
  currentAudio.value = null;
646
648
  }
647
649
  }
648
- function waitWithSignal(ms, signal) {
649
- return new Promise((resolve, reject) => {
650
- if (signal == null ? void 0 : signal.aborted) {
651
- reject(new DOMException("Aborted", "AbortError"));
652
- return;
653
- }
654
- const timer = setTimeout(resolve, ms);
655
- const onAbort = () => {
656
- clearTimeout(timer);
657
- reject(new DOMException("Aborted", "AbortError"));
650
+ const voiceFileCache = /* @__PURE__ */ new Map();
651
+ const FILEDATA_META = { _type: "gradio.FileData" };
652
+ const uploadReferenceAudio = async (blob, filename, signal) => {
653
+ const form = new FormData();
654
+ form.append("files", blob, filename);
655
+ const res = await fetch(`${baseTTSUrl}/gradio_api/upload`, {
656
+ method: "POST",
657
+ body: form,
658
+ signal
659
+ });
660
+ if (!res.ok) throw new Error(`参考音频上传失败 HTTP ${res.status}`);
661
+ const paths = await res.json();
662
+ const path = Array.isArray(paths) ? paths[0] : paths;
663
+ if (!path) throw new Error("参考音频上传失败:服务未返回文件路径");
664
+ return { path, orig_name: filename, meta: FILEDATA_META };
665
+ };
666
+ const resolveVoiceFileData = async (voiceSource, signal) => {
667
+ if (voiceSource && typeof voiceSource === "object" && !Array.isArray(voiceSource) && voiceSource.path) {
668
+ return {
669
+ path: voiceSource.path,
670
+ orig_name: voiceSource.orig_name || "reference.mp3",
671
+ meta: FILEDATA_META
658
672
  };
659
- signal == null ? void 0 : signal.addEventListener("abort", onAbort, { once: true });
673
+ }
674
+ if (voiceSource instanceof Blob) {
675
+ const name = voiceSource.name || `reference_${Date.now()}.mp3`;
676
+ return await uploadReferenceAudio(voiceSource, name, signal);
677
+ }
678
+ if (typeof voiceSource === "string" && voiceSource) {
679
+ if (voiceSource.startsWith(baseTTSUrl)) {
680
+ const match = voiceSource.match(/\/gradio_api\/file=(.+)$/);
681
+ if (match) {
682
+ const path = decodeURIComponent(match[1]);
683
+ const origName = path.split(/[\\/]/).pop() || "reference.mp3";
684
+ return { path, orig_name: origName, meta: FILEDATA_META };
685
+ }
686
+ }
687
+ const res = await fetch(voiceSource, { signal });
688
+ if (!res.ok) throw new Error(`参考音频下载失败 HTTP ${res.status}`);
689
+ const blob = await res.blob();
690
+ const fileName = decodeURIComponent(voiceSource.split("?")[0].split("/").pop()) || "reference.mp3";
691
+ return await uploadReferenceAudio(blob, fileName, signal);
692
+ }
693
+ throw new Error("不支持的参考音频来源:voiceUrl 应为音频 URL 字符串、File/Blob 或 { path } 对象");
694
+ };
695
+ const submitCloneJob = async (data, signal) => {
696
+ const res = await fetch(`${baseTTSUrl}/gradio_api/call/_clone_fn`, {
697
+ method: "POST",
698
+ headers: { "Content-Type": "application/json" },
699
+ body: JSON.stringify({ data }),
700
+ signal
660
701
  });
661
- }
702
+ if (!res.ok) {
703
+ const text = await res.text().catch(() => "");
704
+ throw new Error(`克隆任务提交失败 HTTP ${res.status} ${text}`);
705
+ }
706
+ const json = await res.json();
707
+ if (!(json == null ? void 0 : json.event_id)) throw new Error("克隆任务提交失败:未返回 event_id");
708
+ return json.event_id;
709
+ };
710
+ const waitCloneResult = async (eventId, signal) => {
711
+ const res = await fetch(`${baseTTSUrl}/gradio_api/call/_clone_fn/${eventId}`, {
712
+ signal,
713
+ headers: { Accept: "text/event-stream" }
714
+ });
715
+ if (!res.ok) throw new Error(`合成结果监听失败 HTTP ${res.status}`);
716
+ const reader = res.body.getReader();
717
+ const decoder = new TextDecoder("utf-8");
718
+ let buffer = "";
719
+ let eventName = "message";
720
+ while (true) {
721
+ const { done, value } = await reader.read();
722
+ if (done) break;
723
+ buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
724
+ let sep;
725
+ while ((sep = buffer.indexOf("\n\n")) !== -1) {
726
+ const frame = buffer.slice(0, sep);
727
+ buffer = buffer.slice(sep + 2);
728
+ const dataLines = [];
729
+ for (const line of frame.split("\n")) {
730
+ if (line.startsWith("event:")) eventName = line.slice(6).trim();
731
+ else if (line.startsWith("data:")) dataLines.push(line.slice(5).replace(/^ /, ""));
732
+ }
733
+ if (dataLines.length === 0) continue;
734
+ const payload = dataLines.join("\n");
735
+ if (eventName === "complete") {
736
+ return JSON.parse(payload);
737
+ }
738
+ if (eventName === "error") {
739
+ let msg = payload;
740
+ try {
741
+ const parsed = JSON.parse(payload);
742
+ msg = typeof parsed === "string" ? parsed : (parsed == null ? void 0 : parsed.message) || payload;
743
+ } catch {
744
+ }
745
+ throw new Error(msg || "OmniVoice 合成失败");
746
+ }
747
+ eventName = "message";
748
+ }
749
+ }
750
+ throw new Error("OmniVoice 合成结果流提前中断");
751
+ };
752
+ const playAudioFromUrl = async (audioUrl, requestId, signal) => {
753
+ const audioRes = await fetch(audioUrl, { signal });
754
+ if (!audioRes.ok) throw new Error(`合成音频下载失败 HTTP ${audioRes.status}`);
755
+ const audioBlob = await audioRes.blob();
756
+ const objectUrl = URL.createObjectURL(audioBlob);
757
+ if (signal.aborted || currentTtsRequestId.value !== requestId) {
758
+ URL.revokeObjectURL(objectUrl);
759
+ return;
760
+ }
761
+ const audio = new Audio(objectUrl);
762
+ currentAudio.value = audio;
763
+ const cleanup = () => {
764
+ URL.revokeObjectURL(objectUrl);
765
+ if (currentAudio.value === audio) currentAudio.value = null;
766
+ };
767
+ audio.onplaying = () => {
768
+ console.log("[OmniVoice] 音频开始播放");
769
+ speak();
770
+ };
771
+ audio.onended = () => {
772
+ console.log("[OmniVoice] 音频播放结束");
773
+ cleanup();
774
+ stopSpeaking();
775
+ };
776
+ audio.onerror = () => {
777
+ console.error("[OmniVoice] 音频播放失败");
778
+ cleanup();
779
+ stopSpeaking();
780
+ };
781
+ await audio.play();
782
+ };
662
783
  const playTTS = async (text) => {
663
- var _a, _b, _c, _d, _e, _f, _g, _h;
784
+ var _a, _b, _c, _d;
785
+ console.log(props.humans);
786
+ let voiceUrl = "";
787
+ let voiceUrl2 = "http://192.168.3.182:8002/gradio_api/file=C:\\Users\\Administrator\\AppData\\Local\\Temp\\gradio\\4eef6d9157de756de02bccca2f66389382e2ac4acb0c674981aa9fe13dcc294c\\李梓萌.mp3";
788
+ let voiceUrl1 = "http://192.168.3.182:8002/gradio_api/file=C:\\Users\\Administrator\\AppData\\Local\\Temp\\gradio\\ff2665f538a4b0e5efbcd79f9f699236f5f0e52cad7e17f0664ddf431169be30\\guozj.mp3";
789
+ if (props.humans[0].speakSet.sex == "Female / 女") {
790
+ voiceUrl = voiceUrl2;
791
+ } else {
792
+ voiceUrl = voiceUrl1;
793
+ }
664
794
  if (!text || !((_a = currentHuman.value) == null ? void 0 : _a.isMuted)) return;
665
795
  abortTTS();
666
796
  ttsAbortController = new AbortController();
@@ -670,133 +800,33 @@ const _sfc_main = {
670
800
  try {
671
801
  const cleanText = text.replace(/```[\s\S]*?```/g, "").replace(/`[^`]+`/g, "").replace(/[#*_~]/g, "").replace(/\n+/g, ",").replace(/\s+/g, " ").replace(/[,。!?]{2,}/g, ",").trim();
672
802
  if (!cleanText) return;
673
- const baseUrl = "/baseUrl11";
674
- const baseurl22 = "/baseUrl22";
675
- const sessionHash = "session_" + Date.now();
676
- let speed = ((_c = (_b = currentHuman.value) == null ? void 0 : _b.speakSet) == null ? void 0 : _c.speed) || 1;
677
- let age = ((_e = (_d = currentHuman.value) == null ? void 0 : _d.speakSet) == null ? void 0 : _e.age) || "Young Adult / 青年";
678
- let sex = ((_g = (_f = currentHuman.value) == null ? void 0 : _f.speakSet) == null ? void 0 : _g.sex) || "Female / 女";
679
- const data = [cleanText, "Auto", 32, 2, true, speed, null, true, true, sex, age, "Auto", "Auto", "Auto", "Auto"];
680
- console.log("[OmniVoice] 提交请求:", { text: cleanText, length: cleanText.length, fn_index: 1 });
681
- const joinRes = await fetch(`${baseUrl}/gradio_api/queue/join`, {
682
- method: "POST",
683
- headers: { "Content-Type": "application/json" },
684
- body: JSON.stringify({ data, session_hash: sessionHash, fn_index: 1, trigger_id: 50 }),
685
- signal
686
- });
687
- const joinData = await joinRes.json();
688
- console.log("[OmniVoice] Step1 响应:", joinData);
689
- console.log("[OmniVoice] Step2: 轮询等待结果");
690
- let audioUrl = "";
691
- let maxPolls = 60;
692
- let pollInterval = 1e3;
693
- while (!audioUrl && maxPolls > 0) {
694
- if (signal.aborted || currentTtsRequestId.value !== requestId) return;
695
- await waitWithSignal(pollInterval, signal);
696
- maxPolls--;
697
- try {
698
- const pollRes = await fetch(`${baseUrl}/gradio_api/queue/data?session_hash=${sessionHash}`, {
699
- signal
700
- });
701
- const pollText = await pollRes.text();
702
- const lines = pollText.split("\n");
703
- for (const line of lines) {
704
- const trimLine = line.trim();
705
- if (!trimLine.startsWith("data: ")) continue;
706
- try {
707
- const json = JSON.parse(trimLine.replace("data: ", ""));
708
- console.log("[OmniVoice] 轮询消息:", json);
709
- if (json.msg === "process_completed" && ((_h = json.output) == null ? void 0 : _h.data)) {
710
- const fileData = json.output.data[0];
711
- console.log("[OmniVoice] 获取到音频:", fileData);
712
- if (fileData == null ? void 0 : fileData.url) audioUrl = fileData.url;
713
- else if (fileData == null ? void 0 : fileData.path) audioUrl = `${baseUrl}/gradio_api/file=${fileData.path}`;
714
- else if (typeof fileData === "string") audioUrl = fileData.startsWith("http") ? fileData : `${baseUrl}/gradio_api/file=${fileData}`;
715
- else if (Array.isArray(fileData) && fileData.length >= 2) {
716
- const samplingRate = fileData[0];
717
- const waveform = fileData[1];
718
- console.log("[OmniVoice] 直接获取到音频数组:", { samplingRate, waveformLength: waveform == null ? void 0 : waveform.length });
719
- if (samplingRate && waveform && Array.isArray(waveform)) {
720
- const audioBuffer = new ArrayBuffer(waveform.length * 2);
721
- const view = new DataView(audioBuffer);
722
- for (let i = 0; i < waveform.length; i++) {
723
- view.setInt16(i * 2, waveform[i], true);
724
- }
725
- const wavBlob = createWavBlob(audioBuffer, samplingRate, 1, 16);
726
- const audioObjectUrl = URL.createObjectURL(wavBlob);
727
- if (signal.aborted || currentTtsRequestId.value !== requestId) {
728
- URL.revokeObjectURL(audioObjectUrl);
729
- return;
730
- }
731
- currentAudio.value = new Audio(audioObjectUrl);
732
- currentAudio.value.onplaying = () => {
733
- console.log("[OmniVoice] 音频开始播放");
734
- speak();
735
- };
736
- currentAudio.value.onended = () => {
737
- console.log("[OmniVoice] 音频播放结束");
738
- stopSpeaking();
739
- currentAudio.value = null;
740
- URL.revokeObjectURL(audioObjectUrl);
741
- };
742
- currentAudio.value.onerror = () => {
743
- console.error("[OmniVoice] 音频播放失败");
744
- stopSpeaking();
745
- currentAudio.value = null;
746
- URL.revokeObjectURL(audioObjectUrl);
747
- };
748
- await currentAudio.value.play();
749
- return;
750
- }
751
- }
752
- break;
753
- } else if (json.msg === "process_error") {
754
- console.error("[OmniVoice] 处理错误:", json);
755
- standby();
756
- return;
757
- }
758
- } catch (e) {
759
- console.error("[OmniVoice] 解析轮询消息失败:", e);
760
- }
761
- }
762
- } catch (e) {
763
- if ((e == null ? void 0 : e.name) === "AbortError") throw e;
764
- console.warn("[OmniVoice] 轮询失败:", e.message);
765
- }
766
- }
767
- if (audioUrl) {
768
- if (audioUrl.startsWith("/")) audioUrl = baseUrl + audioUrl;
769
- console.log("[OmniVoice] 下载音频:", audioUrl);
770
- if (signal.aborted || currentTtsRequestId.value !== requestId) return;
771
- const audioRes = await fetch(audioUrl, { signal });
772
- const audioBlob = await audioRes.blob();
773
- const audioObjectUrl = URL.createObjectURL(audioBlob);
774
- if (signal.aborted || currentTtsRequestId.value !== requestId) {
775
- URL.revokeObjectURL(audioObjectUrl);
776
- return;
777
- }
778
- currentAudio.value = new Audio(audioObjectUrl);
779
- currentAudio.value.onplaying = () => {
780
- console.log("[OmniVoice] 音频开始播放");
781
- speak();
782
- };
783
- currentAudio.value.onended = () => {
784
- console.log("[OmniVoice] 音频播放结束");
785
- stopSpeaking();
786
- currentAudio.value = null;
787
- URL.revokeObjectURL(audioObjectUrl);
788
- };
789
- currentAudio.value.onerror = () => {
790
- console.error("[OmniVoice] 音频播放失败");
791
- stopSpeaking();
792
- currentAudio.value = null;
793
- URL.revokeObjectURL(audioObjectUrl);
794
- };
795
- await currentAudio.value.play();
796
- } else {
797
- console.error("[OmniVoice] 未获取到音频");
803
+ const speed = Number((_c = (_b = currentHuman.value) == null ? void 0 : _b.speakSet) == null ? void 0 : _c.speed) || 1;
804
+ const voiceSource = voiceUrl || ((_d = currentHuman.value) == null ? void 0 : _d.voiceUrl);
805
+ if (!voiceSource) {
806
+ console.error("[OmniVoice] 未配置参考音频 voiceUrl,无法进行音色克隆");
798
807
  standby();
808
+ return;
809
+ }
810
+ let refFile = voiceFileCache.get(voiceSource);
811
+ if (!refFile) {
812
+ console.log("[OmniVoice] 准备参考音频:", typeof voiceSource === "string" ? voiceSource : "Blob/File");
813
+ refFile = await resolveVoiceFileData(voiceSource, signal);
814
+ voiceFileCache.set(voiceSource, refFile);
799
815
  }
816
+ console.log("[OmniVoice] 参考音频就绪:", refFile);
817
+ const data = [cleanText, "Auto", refFile, "", null, 32, 2, true, speed, null, true, true];
818
+ console.log("[OmniVoice] 提交克隆请求:", { text: cleanText, length: cleanText.length });
819
+ const eventId = await submitCloneJob(data, signal);
820
+ const outputs = await waitCloneResult(eventId, signal);
821
+ const audioFile = outputs == null ? void 0 : outputs[0];
822
+ const statusText = outputs == null ? void 0 : outputs[1];
823
+ console.log("[OmniVoice] 合成结果:", { audioFile, statusText });
824
+ if (signal.aborted || currentTtsRequestId.value !== requestId) return;
825
+ if (!audioFile || !audioFile.path && !audioFile.url) {
826
+ throw new Error(statusText || "OmniVoice 未返回合成音频");
827
+ }
828
+ const audioUrl = audioFile.url && audioFile.url.startsWith("http") ? audioFile.url : `${baseTTSUrl}/gradio_api/file=${audioFile.path}`;
829
+ await playAudioFromUrl(audioUrl, requestId, signal);
800
830
  } catch (e) {
801
831
  if ((e == null ? void 0 : e.name) === "AbortError") {
802
832
  console.log("[OmniVoice] TTS已取消");
@@ -833,7 +863,7 @@ const _sfc_main = {
833
863
  const formData = new FormData();
834
864
  formData.append("file", blob, "recorded_audio.wav");
835
865
  try {
836
- const res = await fetch(baseurl2 + "/transcribe", {
866
+ const res = await fetch(ASR_URL + "/transcribe", {
837
867
  method: "POST",
838
868
  body: formData
839
869
  });
@@ -846,42 +876,6 @@ const _sfc_main = {
846
876
  return { status: e, text: "" };
847
877
  }
848
878
  };
849
- const createWavBlob = (audioBuffer, sampleRate, channels, bitsPerSample) => {
850
- const bytesPerSample = bitsPerSample / 8;
851
- const blockAlign = channels * bytesPerSample;
852
- const dataSize = audioBuffer.byteLength;
853
- const buffer = new ArrayBuffer(44 + dataSize);
854
- const view = new DataView(buffer);
855
- view.setUint8(0, 82);
856
- view.setUint8(1, 73);
857
- view.setUint8(2, 70);
858
- view.setUint8(3, 70);
859
- view.setUint32(4, 36 + dataSize, true);
860
- view.setUint8(8, 87);
861
- view.setUint8(9, 65);
862
- view.setUint8(10, 86);
863
- view.setUint8(11, 69);
864
- view.setUint8(12, 102);
865
- view.setUint8(13, 109);
866
- view.setUint8(14, 116);
867
- view.setUint8(15, 32);
868
- view.setUint32(16, 16, true);
869
- view.setUint16(20, 1, true);
870
- view.setUint16(22, channels, true);
871
- view.setUint32(24, sampleRate, true);
872
- view.setUint32(28, sampleRate * blockAlign, true);
873
- view.setUint16(32, blockAlign, true);
874
- view.setUint16(34, bitsPerSample, true);
875
- view.setUint8(36, 100);
876
- view.setUint8(37, 97);
877
- view.setUint8(38, 116);
878
- view.setUint8(39, 97);
879
- view.setUint32(40, dataSize, true);
880
- const audioData = new Uint8Array(audioBuffer);
881
- const destData = new Uint8Array(buffer, 44);
882
- destData.set(audioData);
883
- return new Blob([buffer], { type: "audio/wav" });
884
- };
885
879
  let recorder2 = ref(null);
886
880
  const initRecorder = () => {
887
881
  console.log("initRecorder");
@@ -933,7 +927,7 @@ const _sfc_main = {
933
927
  };
934
928
  }
935
929
  };
936
- const DigitalHuman = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-f9bef1a6"]]);
930
+ const DigitalHuman = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-9c25016c"]]);
937
931
  DigitalHuman.install = (app) => app.component("DigitalHuman", DigitalHuman);
938
932
  const components = [DemoButton, DemoInput, DigitalHuman];
939
933
  const install = (app) => {
package/dist/style.css CHANGED
@@ -1 +1 @@
1
- .demo-btn[data-v-9a48516a]{padding:6px 14px;border-radius:4px;border:none;cursor:pointer;font-size:14px}.primary[data-v-9a48516a]{background:#409eff;color:#fff}.success[data-v-9a48516a]{background:#67c23a;color:#fff}.warning[data-v-9a48516a]{background:#e6a23c;color:#fff}.demo-input-wrap[data-v-28a3493c]{display:inline-flex;align-items:center;border:1px solid #dcdcdc;border-radius:4px;padding:0 10px}.demo-input-wrap[data-v-28a3493c]:focus-within{border-color:#409eff}.demo-input[data-v-28a3493c]{border:none;outline:none;padding:8px 6px;flex:1}.prefix[data-v-28a3493c],.suffix[data-v-28a3493c]{color:#909399}.digital-human[data-v-f9bef1a6]{width:100%;height:100%;display:flex;align-items:center;justify-content:center}.digital-human canvas[data-v-f9bef1a6]{max-width:100%;max-height:100%}
1
+ .demo-btn[data-v-9a48516a]{padding:6px 14px;border-radius:4px;border:none;cursor:pointer;font-size:14px}.primary[data-v-9a48516a]{background:#409eff;color:#fff}.success[data-v-9a48516a]{background:#67c23a;color:#fff}.warning[data-v-9a48516a]{background:#e6a23c;color:#fff}.demo-input-wrap[data-v-28a3493c]{display:inline-flex;align-items:center;border:1px solid #dcdcdc;border-radius:4px;padding:0 10px}.demo-input-wrap[data-v-28a3493c]:focus-within{border-color:#409eff}.demo-input[data-v-28a3493c]{border:none;outline:none;padding:8px 6px;flex:1}.prefix[data-v-28a3493c],.suffix[data-v-28a3493c]{color:#909399}.digital-human[data-v-9c25016c]{width:100%;height:100%;display:flex;align-items:center;justify-content:center}.digital-human canvas[data-v-9c25016c]{max-width:100%;max-height:100%}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ruiyun-human",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "type": "module",
5
5
  "description": "数字人初步实现",
6
6
  "main": "./dist/ruiyun-human.cjs.js",
@@ -29,11 +29,11 @@
29
29
  "postcss": "^8.5.20",
30
30
  "tailwindcss": "^4.3.3",
31
31
  "terser": "^5.49.0",
32
- "vite": "^5.1.4",
33
- "vue": "^3.4.21"
32
+ "vite": "^5.1.4"
34
33
  },
35
34
  "dependencies": {
36
35
  "js-audio-recorder": "^1.0.7",
37
- "vue-router": "^4.6.4"
36
+ "vue-router": "^4.6.4",
37
+ "vue": "^3.4.21"
38
38
  }
39
39
  }
package/dist/favicon.svg DELETED
@@ -1,4 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
2
- <rect width="100" height="100" rx="20" fill="#42b883"/>
3
- <text y=".9em" font-size="90" fill="white" text-anchor="middle" x="50">V</text>
4
- </svg>