mirror of
https://github.com/LLOneBot/LLOneBot.git
synced 2024-11-22 01:56:33 +00:00
Merge branch 'dev' into main
This commit is contained in:
commit
2c8a594c38
@ -1,9 +1,10 @@
|
|||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { encode, getDuration, getWavFileInfo, isWav, isSilk } from 'silk-wasm'
|
import fsAsync from 'fs/promises'
|
||||||
import fsPromise from 'fs/promises'
|
import fsPromise from 'fs/promises'
|
||||||
|
import { decode, encode, getDuration, getWavFileInfo, isWav, isSilk } from 'silk-wasm'
|
||||||
import { log } from './log'
|
import { log } from './log'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import { DATA_DIR, TEMP_DIR } from './index'
|
import { TEMP_DIR } from './index'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { getConfigUtil } from '../config'
|
import { getConfigUtil } from '../config'
|
||||||
import { spawn } from 'node:child_process'
|
import { spawn } from 'node:child_process'
|
||||||
@ -80,7 +81,8 @@ export async function encodeSilk(filePath: string) {
|
|||||||
if (code == null || EXIT_CODES.includes(code)) {
|
if (code == null || EXIT_CODES.includes(code)) {
|
||||||
sampleRate = 24000
|
sampleRate = 24000
|
||||||
const data = fs.readFileSync(pcmPath)
|
const data = fs.readFileSync(pcmPath)
|
||||||
fs.unlink(pcmPath, (err) => {})
|
fs.unlink(pcmPath, (err) => {
|
||||||
|
})
|
||||||
return resolve(data)
|
return resolve(data)
|
||||||
}
|
}
|
||||||
log(`FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}`)
|
log(`FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}`)
|
||||||
@ -129,3 +131,41 @@ export async function encodeSilk(filePath: string) {
|
|||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function decodeSilk(inputFilePath: string, outFormat: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac' = 'mp3') {
|
||||||
|
const silkArrayBuffer = await fsAsync.readFile(inputFilePath)
|
||||||
|
const data = (await decode(silkArrayBuffer, 24000)).data
|
||||||
|
const fileName = path.join(TEMP_DIR, path.basename(inputFilePath))
|
||||||
|
const outPCMPath = fileName + '.pcm'
|
||||||
|
const outFilePath = fileName + '.' + outFormat
|
||||||
|
await fsAsync.writeFile(outPCMPath, data)
|
||||||
|
const convert = () => {
|
||||||
|
return new Promise<string>((resolve, reject) => {
|
||||||
|
const ffmpegPath = getConfigUtil().getConfig().ffmpeg || process.env.FFMPEG_PATH || 'ffmpeg'
|
||||||
|
const cp = spawn(ffmpegPath, [
|
||||||
|
'-y',
|
||||||
|
'-f', 's16le', // PCM format
|
||||||
|
'-ar', '24000', // Sample rate
|
||||||
|
'-ac', '1', // Number of audio channels
|
||||||
|
'-i', outPCMPath,
|
||||||
|
outFilePath,
|
||||||
|
])
|
||||||
|
cp.on('error', (err) => {
|
||||||
|
log(`FFmpeg处理转换出错: `, err.message)
|
||||||
|
return reject(err)
|
||||||
|
})
|
||||||
|
cp.on('exit', (code, signal) => {
|
||||||
|
const EXIT_CODES = [0, 255]
|
||||||
|
if (code == null || EXIT_CODES.includes(code)) {
|
||||||
|
fs.unlink(outPCMPath, (err) => {
|
||||||
|
})
|
||||||
|
return resolve(outFilePath)
|
||||||
|
}
|
||||||
|
const exitErr = `FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}`
|
||||||
|
log(exitErr)
|
||||||
|
reject(Error(`FFmpeg处理转换失败,${exitErr}`))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return convert()
|
||||||
|
}
|
@ -2,7 +2,7 @@ import BaseAction from '../BaseAction'
|
|||||||
import fs from 'fs/promises'
|
import fs from 'fs/promises'
|
||||||
import { dbUtil } from '@/common/db'
|
import { dbUtil } from '@/common/db'
|
||||||
import { getConfigUtil } from '@/common/config'
|
import { getConfigUtil } from '@/common/config'
|
||||||
import { log, sleep, uri2local } from '@/common/utils'
|
import { checkFileReceived, log, sleep, uri2local } from '@/common/utils'
|
||||||
import { NTQQFileApi } from '@/ntqqapi/api'
|
import { NTQQFileApi } from '@/ntqqapi/api'
|
||||||
import { ActionName } from '../types'
|
import { ActionName } from '../types'
|
||||||
import { FileElement, RawMessage, VideoElement } from '@/ntqqapi/types'
|
import { FileElement, RawMessage, VideoElement } from '@/ntqqapi/types'
|
||||||
@ -38,20 +38,21 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
|
|||||||
log('找到了文件 element', element)
|
log('找到了文件 element', element)
|
||||||
// 构建下载函数
|
// 构建下载函数
|
||||||
await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid, cache.elementId, '', '', true)
|
await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid, cache.elementId, '', '', true)
|
||||||
await sleep(1000) // 这里延时是为何?
|
// 等待文件下载完成
|
||||||
msg = await dbUtil.getMsgByLongId(cache.msgId)
|
msg = await dbUtil.getMsgByLongId(cache.msgId)
|
||||||
log('下载完成后的msg', msg)
|
log('下载完成后的msg', msg)
|
||||||
cache.filePath = this.getElement(msg, cache.elementId).filePath
|
cache.filePath = this.getElement(msg, cache.elementId).filePath
|
||||||
|
await checkFileReceived(cache.filePath, 10 * 1000)
|
||||||
dbUtil.addFileCache(file, cache).then()
|
dbUtil.addFileCache(file, cache).then()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
protected async _handle(payload: GetFilePayload): Promise<GetFileResponse> {
|
protected async _handle(payload: GetFilePayload): Promise<GetFileResponse> {
|
||||||
const cache = await dbUtil.getFileCache(payload.file)
|
let cache = await dbUtil.getFileCache(payload.file)
|
||||||
const { autoDeleteFile, enableLocalFile2Url, autoDeleteFileSecond } = getConfigUtil().getConfig()
|
|
||||||
if (!cache) {
|
if (!cache) {
|
||||||
throw new Error('file not found')
|
throw new Error('file not found')
|
||||||
}
|
}
|
||||||
|
const { autoDeleteFile, enableLocalFile2Url, autoDeleteFileSecond } = getConfigUtil().getConfig()
|
||||||
if (cache.downloadFunc) {
|
if (cache.downloadFunc) {
|
||||||
await cache.downloadFunc()
|
await cache.downloadFunc()
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import { GetFileBase, GetFilePayload, GetFileResponse } from './GetFile'
|
import { GetFileBase, GetFilePayload, GetFileResponse } from './GetFile'
|
||||||
import { ActionName } from '../types'
|
import { ActionName } from '../types'
|
||||||
|
import {decodeSilk} from "@/common/utils/audio";
|
||||||
|
import { getConfigUtil } from '@/common/config'
|
||||||
|
import path from 'node:path'
|
||||||
|
import fs from 'node:fs'
|
||||||
|
|
||||||
interface Payload extends GetFilePayload {
|
interface Payload extends GetFilePayload {
|
||||||
out_format: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac'
|
out_format: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac'
|
||||||
@ -9,7 +13,13 @@ export default class GetRecord extends GetFileBase {
|
|||||||
actionName = ActionName.GetRecord
|
actionName = ActionName.GetRecord
|
||||||
|
|
||||||
protected async _handle(payload: Payload): Promise<GetFileResponse> {
|
protected async _handle(payload: Payload): Promise<GetFileResponse> {
|
||||||
let res = super._handle(payload)
|
let res = await super._handle(payload)
|
||||||
|
res.file = await decodeSilk(res.file, payload.out_format)
|
||||||
|
res.file_name = path.basename(res.file)
|
||||||
|
res.file_size = fs.statSync(res.file).size.toString()
|
||||||
|
if (getConfigUtil().getConfig().enableLocalFile2Url){
|
||||||
|
res.base64 = fs.readFileSync(res.file, 'base64')
|
||||||
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -188,9 +188,7 @@ export class OB11Constructor {
|
|||||||
element.picElement.sourcePath,
|
element.picElement.sourcePath,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
}).then()
|
||||||
.then()
|
|
||||||
// 不在自动下载图片
|
|
||||||
}
|
}
|
||||||
else if (element.videoElement || element.fileElement) {
|
else if (element.videoElement || element.fileElement) {
|
||||||
const videoOrFileElement = element.videoElement || element.fileElement
|
const videoOrFileElement = element.videoElement || element.fileElement
|
||||||
|
Loading…
x
Reference in New Issue
Block a user