From 36f7f1b026170d24ebb8a71e3f12f8415c0c9468 Mon Sep 17 00:00:00 2001 From: linyuchen Date: Sat, 23 Mar 2024 21:14:24 +0800 Subject: [PATCH] refactor: audio.ts --- src/common/utils/audio.ts | 134 +++++++++++++++++++++++++++++++++++++ src/common/utils/file.ts | 132 +----------------------------------- src/common/utils/index.ts | 3 +- src/ntqqapi/constructor.ts | 3 +- 4 files changed, 139 insertions(+), 133 deletions(-) create mode 100644 src/common/utils/audio.ts diff --git a/src/common/utils/audio.ts b/src/common/utils/audio.ts new file mode 100644 index 0000000..6ce971d --- /dev/null +++ b/src/common/utils/audio.ts @@ -0,0 +1,134 @@ +import fs from "fs"; +import {encode, getDuration, getWavFileInfo, isWav} from "silk-wasm"; +import fsPromise from "fs/promises"; +import {log} from "./log"; +import path from "node:path"; +import {DATA_DIR} from "./index"; +import {v4 as uuidv4} from "uuid"; +import {getConfigUtil} from "../config"; +import ffmpeg from "fluent-ffmpeg"; + +export async function encodeSilk(filePath: string) { + function getFileHeader(filePath: string) { + // 定义要读取的字节数 + const bytesToRead = 7; + try { + const buffer = fs.readFileSync(filePath, { + encoding: null, + flag: "r", + }); + + const fileHeader = buffer.toString("hex", 0, bytesToRead); + return fileHeader; + } catch (err) { + console.error("读取文件错误:", err); + return; + } + } + + async function isWavFile(filePath: string) { + return isWav(fs.readFileSync(filePath)); + } + + async function guessDuration(pttPath: string) { + const pttFileInfo = await fsPromise.stat(pttPath) + let duration = pttFileInfo.size / 1024 / 3 // 3kb/s + duration = Math.floor(duration) + duration = Math.max(1, duration) + log(`通过文件大小估算语音的时长:`, duration) + return duration + } + + // function verifyDuration(oriDuration: number, guessDuration: number) { + // // 单位都是秒 + // if (oriDuration - guessDuration > 10) { + // return guessDuration + // } + // oriDuration = Math.max(1, oriDuration) + // return oriDuration + // } + // async function getAudioSampleRate(filePath: string) { + // try { + // const mm = await import('music-metadata'); + // const metadata = await mm.parseFile(filePath); + // log(`${filePath}采样率`, metadata.format.sampleRate); + // return metadata.format.sampleRate; + // } catch (error) { + // log(`${filePath}采样率获取失败`, error.stack); + // // console.error(error); + // } + // } + + try { + const pttPath = path.join(DATA_DIR, uuidv4()); + if (getFileHeader(filePath) !== "02232153494c4b") { + log(`语音文件${filePath}需要转换成silk`) + const _isWav = await isWavFile(filePath); + const wavPath = pttPath + ".wav" + const convert = async () => { + return await new Promise((resolve, reject) => { + const ffmpegPath = getConfigUtil().getConfig().ffmpeg; + if (ffmpegPath) { + ffmpeg.setFfmpegPath(ffmpegPath); + } + ffmpeg(filePath).toFormat("wav").audioChannels(1).audioFrequency(24000).on('end', function () { + log('wav转换完成'); + }) + .on('error', function (err) { + log(`wav转换出错: `, err.message,); + reject(err); + }) + .save(wavPath) + .on("end", () => { + filePath = wavPath + resolve(wavPath); + }); + }) + } + let wav: Buffer + if (!_isWav) { + log(`语音文件${filePath}正在转换成wav`) + await convert() + } else { + wav = fs.readFileSync(filePath) + const allowSampleRate = [8000, 12000, 16000, 24000, 32000, 44100, 48000] + const {fmt} = getWavFileInfo(wav) + if (!allowSampleRate.includes(fmt.sampleRate)) { + wav = undefined + await convert() + } + } + wav ||= fs.readFileSync(filePath); + const silk = await encode(wav, 0); + fs.writeFileSync(pttPath, silk.data); + fs.unlink(wavPath, (err) => { + }); + const gDuration = await guessDuration(pttPath) + log(`语音文件${filePath}转换成功!`, pttPath, `时长:`, silk.duration) + return { + converted: true, + path: pttPath, + duration: silk.duration / 1000 + }; + } else { + const silk = fs.readFileSync(filePath); + let duration = 0; + const gDuration = await guessDuration(filePath) + try { + duration = getDuration(silk) / 1000 + } catch (e) { + log("获取语音文件时长失败, 使用文件大小推测时长", filePath, e.stack) + duration = gDuration; + } + + return { + converted: false, + path: filePath, + duration: duration, + }; + } + } catch (error) { + log("convert silk failed", error.stack); + return {}; + } +} \ No newline at end of file diff --git a/src/common/utils/file.ts b/src/common/utils/file.ts index ada26d5..174d81f 100644 --- a/src/common/utils/file.ts +++ b/src/common/utils/file.ts @@ -1,13 +1,10 @@ import fs from "fs"; import fsPromise from "fs/promises"; import crypto from "crypto"; -import ffmpeg from "fluent-ffmpeg"; import util from "util"; -import {encode, getDuration, isWav, getWavFileInfo} from "silk-wasm"; import path from "node:path"; import {v4 as uuidv4} from "uuid"; -import {checkFfmpeg, DATA_DIR, log, TEMP_DIR} from "./index"; -import {getConfigUtil} from "../config"; +import {log, TEMP_DIR} from "./index"; import {dbUtil} from "../db"; import * as fileType from "file-type"; import {net} from "electron"; @@ -66,133 +63,6 @@ export async function file2base64(path: string) { return result; } -export async function encodeSilk(filePath: string) { - const fsp = require("fs").promises - - function getFileHeader(filePath: string) { - // 定义要读取的字节数 - const bytesToRead = 7; - try { - const buffer = fs.readFileSync(filePath, { - encoding: null, - flag: "r", - }); - - const fileHeader = buffer.toString("hex", 0, bytesToRead); - return fileHeader; - } catch (err) { - console.error("读取文件错误:", err); - return; - } - } - - async function isWavFile(filePath: string) { - return isWav(fs.readFileSync(filePath)); - } - - async function guessDuration(pttPath: string) { - const pttFileInfo = await fsPromise.stat(pttPath) - let duration = pttFileInfo.size / 1024 / 3 // 3kb/s - duration = Math.floor(duration) - duration = Math.max(1, duration) - log(`通过文件大小估算语音的时长:`, duration) - return duration - } - - // function verifyDuration(oriDuration: number, guessDuration: number) { - // // 单位都是秒 - // if (oriDuration - guessDuration > 10) { - // return guessDuration - // } - // oriDuration = Math.max(1, oriDuration) - // return oriDuration - // } - // async function getAudioSampleRate(filePath: string) { - // try { - // const mm = await import('music-metadata'); - // const metadata = await mm.parseFile(filePath); - // log(`${filePath}采样率`, metadata.format.sampleRate); - // return metadata.format.sampleRate; - // } catch (error) { - // log(`${filePath}采样率获取失败`, error.stack); - // // console.error(error); - // } - // } - - try { - const pttPath = path.join(DATA_DIR, uuidv4()); - if (getFileHeader(filePath) !== "02232153494c4b") { - log(`语音文件${filePath}需要转换成silk`) - const _isWav = await isWavFile(filePath); - const wavPath = pttPath + ".wav" - const convert = async () => { - return await new Promise((resolve, reject) => { - const ffmpegPath = getConfigUtil().getConfig().ffmpeg; - if (ffmpegPath) { - ffmpeg.setFfmpegPath(ffmpegPath); - } - ffmpeg(filePath).toFormat("wav").audioChannels(1).audioFrequency(24000).on('end', function () { - log('wav转换完成'); - }) - .on('error', function (err) { - log(`wav转换出错: `, err.message,); - reject(err); - }) - .save(wavPath) - .on("end", () => { - filePath = wavPath - resolve(wavPath); - }); - }) - } - let wav: Buffer - if (!_isWav) { - log(`语音文件${filePath}正在转换成wav`) - await convert() - } else { - wav = fs.readFileSync(filePath) - const allowSampleRate = [8000, 12000, 16000, 24000, 32000, 44100, 48000] - const { fmt } = getWavFileInfo(wav) - if (!allowSampleRate.includes(fmt.sampleRate)) { - wav = undefined - await convert() - } - } - wav ||= fs.readFileSync(filePath); - const silk = await encode(wav, 0); - fs.writeFileSync(pttPath, silk.data); - fs.unlink(wavPath, (err) => { - }); - const gDuration = await guessDuration(pttPath) - log(`语音文件${filePath}转换成功!`, pttPath, `时长:`, silk.duration) - return { - converted: true, - path: pttPath, - duration: silk.duration / 1000 - }; - } else { - const silk = fs.readFileSync(filePath); - let duration = 0; - const gDuration = await guessDuration(filePath) - try { - duration = getDuration(silk) / 1000 - } catch (e) { - log("获取语音文件时长失败, 使用文件大小推测时长", filePath, e.stack) - duration = gDuration; - } - - return { - converted: false, - path: filePath, - duration: duration, - }; - } - } catch (error) { - log("convert silk failed", error.stack); - return {}; - } -} - export function calculateFileMD5(filePath: string): Promise { diff --git a/src/common/utils/index.ts b/src/common/utils/index.ts index 5f3812f..5cc4594 100644 --- a/src/common/utils/index.ts +++ b/src/common/utils/index.ts @@ -14,4 +14,5 @@ if (!fs.existsSync(TEMP_DIR)) { fs.mkdirSync(TEMP_DIR, {recursive: true}); } export {getVideoInfo} from "./video"; -export {checkFfmpeg} from "./video"; \ No newline at end of file +export {checkFfmpeg} from "./video"; +export {encodeSilk} from "./audio"; \ No newline at end of file diff --git a/src/ntqqapi/constructor.ts b/src/ntqqapi/constructor.ts index 2ae7f9b..90468a1 100644 --- a/src/ntqqapi/constructor.ts +++ b/src/ntqqapi/constructor.ts @@ -14,9 +14,10 @@ import { import {promises as fs} from "node:fs"; import ffmpeg from "fluent-ffmpeg" import {NTQQFileApi} from "./api/file"; -import {calculateFileMD5, encodeSilk, isGIF} from "../common/utils/file"; +import {calculateFileMD5, isGIF} from "../common/utils/file"; import {log} from "../common/utils/log"; import {defaultVideoThumb, getVideoInfo} from "../common/utils/video"; +import {encodeSilk} from "../common/utils/audio"; export class SendMsgElementConstructor {