mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-10-03 18:33:14 +08:00
fix: 修复导出的音频文件变调、变速的问题
This commit is contained in:
+19
-39
@@ -584,19 +584,6 @@ const detectAssetExtension = (buffer: Buffer): string | null => {
|
||||
return 'webp'
|
||||
return null
|
||||
}
|
||||
const htmlArchiveStickerResourceIsValid = async (
|
||||
outputDir: string,
|
||||
value?: string
|
||||
): Promise<boolean> => {
|
||||
const filePath = htmlArchiveResourcePath(outputDir, value)
|
||||
if (!filePath) return false
|
||||
try {
|
||||
return detectAssetExtension(await fs.readFile(filePath)) !== null
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return false
|
||||
throw error
|
||||
}
|
||||
}
|
||||
async function readAvatarAsset(
|
||||
source: string
|
||||
): Promise<{ extension: string; buffer: Buffer } | null> {
|
||||
@@ -941,10 +928,7 @@ async function runSingleExport(
|
||||
request.format === 'html'
|
||||
? options.outputFolderName || safeFilePart(request.outputName)
|
||||
: `${safeFilePart(request.outputName)}_${exportStamp()}`
|
||||
const root =
|
||||
options.outputRoot ||
|
||||
request.outputDirectory ||
|
||||
(await resolveDefaultExportRoot(outputFolder))
|
||||
const root = options.outputRoot || request.outputDirectory || (await resolveDefaultExportRoot(outputFolder))
|
||||
await fs.mkdir(root, { recursive: true })
|
||||
const outputDir = join(root, outputFolder)
|
||||
const outputPath =
|
||||
@@ -1236,7 +1220,16 @@ async function runSingleExport(
|
||||
markResourceExists(voiceUrl)
|
||||
}
|
||||
message.voiceDataUrl = voiceUrl
|
||||
message.voiceDuration = Math.max(1, Math.round(audioBuffer.length / (24000 * 2)))
|
||||
// 读取 WAV 头中的采样率,避免按错误采样率计算时长(Silk 解码为 16kHz)
|
||||
const wavSampleRate =
|
||||
audioBuffer.length >= 44 ? audioBuffer.readUInt32LE(24) : 16000
|
||||
const wavChannels =
|
||||
audioBuffer.length >= 44 ? audioBuffer.readUInt16LE(22) : 1
|
||||
const pcmBytes = Math.max(0, audioBuffer.length - 44)
|
||||
message.voiceDuration = Math.max(
|
||||
1,
|
||||
Math.round(pcmBytes / (wavSampleRate * wavChannels * 2))
|
||||
)
|
||||
} catch (error) {
|
||||
keepMediaError(
|
||||
request,
|
||||
@@ -1345,28 +1338,12 @@ async function runSingleExport(
|
||||
const reusableImageQuality =
|
||||
previous?.exportMediaQuality === 'original' ||
|
||||
(request.preferOriginal === false && previous?.exportMediaQuality === 'thumbnail')
|
||||
let reusableMediaExists = false
|
||||
if (reusableMediaType && previous?.exportMediaUrl) {
|
||||
reusableMediaExists = await resourceExists(previous.exportMediaUrl)
|
||||
if (
|
||||
reusableMediaExists &&
|
||||
reusableMediaType === 'sticker' &&
|
||||
!(await htmlArchiveStickerResourceIsValid(outputDir, previous.exportMediaUrl))
|
||||
) {
|
||||
reusableMediaExists = false
|
||||
resourceExistence.set(previous.exportMediaUrl, Promise.resolve(false))
|
||||
delete previous.exportMediaUrl
|
||||
delete previous.exportMediaType
|
||||
delete previous.exportMediaName
|
||||
delete previous.exportMediaQuality
|
||||
}
|
||||
}
|
||||
if (
|
||||
reusableMediaType &&
|
||||
previous?.exportMediaUrl &&
|
||||
(!previous.exportMediaType || previous.exportMediaType === reusableMediaType) &&
|
||||
(reusableMediaType !== 'image' || reusableImageQuality) &&
|
||||
reusableMediaExists
|
||||
(await resourceExists(previous.exportMediaUrl))
|
||||
) {
|
||||
message.exportMediaUrl = previous.exportMediaUrl
|
||||
message.exportMediaType = reusableMediaType
|
||||
@@ -1478,10 +1455,13 @@ async function runSingleExport(
|
||||
} else if (message.contentData.type === 'sticker' && stickerService) {
|
||||
const stickerSource = message.contentData.url || message.contentData.thumbUrl
|
||||
const result = await stickerService.resolveSticker(stickerSource, message.contentData.md5)
|
||||
const decoded = result.data ? decodeDataUrl(result.data) : null
|
||||
const stickerExtension = decoded ? detectAssetExtension(decoded.buffer) : null
|
||||
if (decoded && stickerExtension) {
|
||||
const name = `sticker_${bufferHashPart(decoded.buffer)}.${stickerExtension}`
|
||||
const decoded = result.data
|
||||
? decodeDataUrl(result.data)
|
||||
: stickerSource
|
||||
? await readAvatarAsset(stickerSource)
|
||||
: null
|
||||
if (decoded) {
|
||||
const name = `sticker_${bufferHashPart(decoded.buffer)}.${decoded.extension}`
|
||||
const mediaUrl = `media/${name}`
|
||||
if (!(await resourceExists(mediaUrl))) {
|
||||
await fs.writeFile(join(outputDir, 'media', name), decoded.buffer)
|
||||
|
||||
@@ -125,7 +125,11 @@ export class VoiceService {
|
||||
codec: 'silk',
|
||||
sourceHash: createHash('sha256').update(silkData).digest('hex')
|
||||
})
|
||||
const wavData = this.createWavBuffer(decoded.pcm, 24000)
|
||||
const wavData = this.createWavBuffer(
|
||||
decoded.pcm,
|
||||
decoded.sampleRate,
|
||||
decoded.channels
|
||||
)
|
||||
const data = wavData.toString('base64')
|
||||
const cacheKey = this.buildCacheKey(
|
||||
reference.sessionId,
|
||||
@@ -239,7 +243,7 @@ export class VoiceService {
|
||||
|
||||
private createWavBuffer(
|
||||
pcmData: Buffer,
|
||||
sampleRate: number = 24000,
|
||||
sampleRate: number = 16000,
|
||||
channels: number = 1
|
||||
): Buffer {
|
||||
const pcmLength = pcmData.length
|
||||
|
||||
Reference in New Issue
Block a user