From 3364dbd89d749c0f49292fbc0f4652b19856eb48 Mon Sep 17 00:00:00 2001 From: ifeif <36729028+ifeif@users.noreply.github.com> Date: Sun, 14 May 2023 20:47:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E5=8A=A0=E5=85=A5?= =?UTF-8?q?=E9=AB=98=E6=B8=85=E8=AF=AD=E9=9F=B3=EF=BC=8C=E5=9B=9E=E5=A4=8D?= =?UTF-8?q?=E2=80=9C#chatgpt=E5=BC=80=E5=90=AF=E9=AB=98=E6=B8=85=E8=AF=AD?= =?UTF-8?q?=E9=9F=B3=E2=80=9D=E6=9D=A5=E5=BC=80=E5=90=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1.新增:加入高清语音,回复“#chatgpt开启高清语音”来开启 2.优化:语音模式下遇到超长文本将会使用图片模式 --- apps/chat.js | 2 +- apps/management.js | 14 ++++++++- utils/config.js | 1 + utils/uploadRecord.js | 70 +++++++++++++++++++++++++++++++++++-------- 4 files changed, 73 insertions(+), 14 deletions(-) diff --git a/apps/chat.js b/apps/chat.js index b1b4ae0..28af7f5 100644 --- a/apps/chat.js +++ b/apps/chat.js @@ -1113,7 +1113,7 @@ export class chatgpt extends plugin { for (let quote of quotemessage) { if (quote.imageLink) imgUrls.push(quote.imageLink) } - if (useTTS) { + if (useTTS && response.length <= Config.autoUsePictureThreshold { // 缓存数据 this.cacheContent(e, use, response, prompt, quotemessage, mood, chatMessage.suggestedResponses, imgUrls) if (response === 'Thanks for this conversation! I\'ve reached my limit, will you hit “New topic,” please?') { diff --git a/apps/management.js b/apps/management.js index 27585e8..3da2df6 100644 --- a/apps/management.js +++ b/apps/management.js @@ -184,6 +184,11 @@ export class ChatgptManagement extends plugin { fnc: 'setDefaultReplySetting', permission: 'master' }, + { + reg: '^#chatgpt(开启|关闭)高清语音', + fnc: 'enableTtsHD', + permission: 'master' + }, { /** 命令正则匹配 */ reg: '^#(关闭|打开)群聊上下文$', @@ -483,7 +488,14 @@ export class ChatgptManagement extends plugin { await this.reply('设置成功', e.isGroup) return false } - + async enableTtsHD (e) { + Config.ttsHD = e.msg.indexOf('开启') > -1 + if(Config.ttsHD) { + await this.reply('已开启高清语音,电脑端将无法播放语音', true) + } else { + await this.reply('已关闭高清语音', true) + } + } async enableGroupContext (e) { const reg = /(关闭|打开)/ const match = e.msg.match(reg) diff --git a/utils/config.js b/utils/config.js index 4abbda9..77791aa 100644 --- a/utils/config.js +++ b/utils/config.js @@ -114,6 +114,7 @@ const defaultConfig = { cloudMode: 'url', cloudDPR: 1, ttsMode: 'vits-uma-genshin-honkai', // or azure + ttsHD: false, azureTTSKey: '', azureTTSRegion: '', azureTTSSpeaker: 'zh-CN-XiaochenNeural', diff --git a/utils/uploadRecord.js b/utils/uploadRecord.js index 5328f13..5874d5d 100644 --- a/utils/uploadRecord.js +++ b/utils/uploadRecord.js @@ -27,7 +27,9 @@ if (module) { try { pcm2slk = (await import('node-silk')).pcm2slk } catch (e) { - if (Config.cloudTranscode) { + if (Config.ttsHD) { + logger.info('已开启高清语音,电脑端将无法播放语音') + } else if (Config.cloudTranscode) { logger.warn('未安装node-silk,将尝试使用云转码服务进行合成') } else { Config.debug && logger.error(e) @@ -49,8 +51,10 @@ async function uploadRecord (recordUrl, ttsMode = 'vits-uma-genshin-honkai') { tmpFile = `data/chatgpt/tts/tmp/${crypto.randomUUID()}.wav` } let result - if (pcm2slk) { - result = await getPttBuffer(recordUrl, Bot.config.ffmpeg_path) + if (Config.ttsHD) { + result = await getPttBuffer(recordUrl, Bot.config.ffmpeg_path, false) + } else if (pcm2slk) { + result = await getPttBuffer(recordUrl, Bot.config.ffmpeg_path, true) } else if (Config.cloudTranscode) { logger.mark('使用云转码silk进行高清语音生成:"') try { @@ -196,21 +200,30 @@ async function uploadRecord (recordUrl, ttsMode = 'vits-uma-genshin-honkai') { export default uploadRecord -async function getPttBuffer (file, ffmpeg = 'ffmpeg') { +async function getPttBuffer (file, ffmpeg = 'ffmpeg', transcoding = true) { let buffer let time if (file instanceof Buffer || file.startsWith('base64://')) { // Buffer或base64 const buf = file instanceof Buffer ? file : Buffer.from(file.slice(9), 'base64') const head = buf.slice(0, 7).toString() - if (head.includes('SILK') || head.includes('AMR')) { - return buf - } else { - const tmpfile = TMP_DIR + '/' + (0, uuid)() + if (head.includes('SILK') || head.includes('AMR') || !transcoding) { + const tmpfile = path.join(TMP_DIR, (0, uuid)()) await fs.promises.writeFile(tmpfile, buf) - return audioTrans(tmpfile, ffmpeg) + let result = await getAudioTime(tmpfile, ffmpeg) + if (result.code == 1) time = result.data + fs.unlink(tmpfile, NOOP) + buffer = buf + } else { + const tmpfile = path.join(TMP_DIR, (0, uuid)()) + let result = await getAudioTime(tmpfile, ffmpeg) + if (result.code == 1) time = result.data + await fs.promises.writeFile(tmpfile, buf) + buffer = await audioTrans(tmpfile, ffmpeg) } } else if (file.startsWith('http://') || file.startsWith('https://')) { + // 网络文件 + // const readable = (await axios.get(file, { responseType: "stream" })).data; try { const headers = { 'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 12; MI 9 Build/SKQ1.211230.001)' @@ -220,11 +233,14 @@ async function getPttBuffer (file, ffmpeg = 'ffmpeg') { headers }) const buf = Buffer.from(await response.arrayBuffer()) - const tmpfile = TMP_DIR + '/' + (0, uuid)() + const tmpfile = path.join(TMP_DIR, (0, uuid)()) await fs.promises.writeFile(tmpfile, buf) // await (0, pipeline)(readable.pipe(new DownloadTransform), fs.createWriteStream(tmpfile)); const head = await read7Bytes(tmpfile) - if (head.includes('SILK') || head.includes('AMR')) { + let result = await getAudioTime(tmpfile, ffmpeg) + if (result.code == 1) time = result.data + if (head.includes('SILK') || head.includes('AMR') || !transcoding) { + // const buf = await fs.promises.readFile(tmpfile); fs.unlink(tmpfile, NOOP) buffer = buf } else { @@ -236,7 +252,9 @@ async function getPttBuffer (file, ffmpeg = 'ffmpeg') { file = String(file).replace(/^file:\/{2}/, '') IS_WIN && file.startsWith('/') && (file = file.slice(1)) const head = await read7Bytes(file) - if (head.includes('SILK') || head.includes('AMR')) { + let result = await getAudioTime(file, ffmpeg) + if (result.code == 1) time = result.data + if (head.includes('SILK') || head.includes('AMR') || !transcoding) { buffer = await fs.promises.readFile(file) } else { buffer = await audioTrans(file, ffmpeg) @@ -245,6 +263,34 @@ async function getPttBuffer (file, ffmpeg = 'ffmpeg') { return { buffer, time } } +async function getAudioTime (file, ffmpeg = 'ffmpeg') { + return new Promise((resolve, _reject) => { + (0, child_process.exec)(`${ffmpeg} -i "${file}"`, async (_error, _stdout, stderr) => { + try { + let time = stderr.split('Duration:')[1]?.split(',')[0].trim() + let arr = time?.split(':') + arr.reverse() + let n = 1 + let s = 0 + for (let val of arr) { + if (parseInt(val) > 0) s += parseInt(val) * n + n *= 60 + } + resolve({ + code: 1, + data: { + time, + seconds: s, + exec_text: stderr + } + }) + } catch { + resolve({ code: -1 }) + } + }) + }) +} + async function audioTrans (file, ffmpeg = 'ffmpeg') { const tmpfile = path.join(TMP_DIR, uuid()) const cmd = IS_WIN