mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-10-03 18:33:14 +08:00
Merge pull request #33 from Lazy-CZ/fix/export-audio-fix
fix: 修复批量导出 wav 音频语速变快、音调升高(采样率不匹配)问题
This commit is contained in:
@@ -941,10 +941,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 +1233,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,
|
||||
@@ -1478,10 +1484,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
|
||||
|
||||
@@ -2,6 +2,29 @@ import { describe, expect, it, vi } from 'vitest'
|
||||
import { VoiceService } from '../../src/main/voice-service'
|
||||
|
||||
describe('VoiceService batch lookup', () => {
|
||||
it('writes the decoded sample rate and duration into batch WAV output', async () => {
|
||||
const getVoiceDataBatch = vi.fn().mockResolvedValue([{ success: true, hex: '0102' }])
|
||||
const service = new VoiceService({ getVoiceDataBatch } as never)
|
||||
const decoder = (service as unknown as {
|
||||
decoderRegistry: { decode: ReturnType<typeof vi.fn> }
|
||||
}).decoderRegistry
|
||||
vi.spyOn(decoder, 'decode').mockResolvedValue({
|
||||
pcm: Buffer.alloc(16_000 * 2),
|
||||
sampleRate: 16_000,
|
||||
channels: 1
|
||||
})
|
||||
|
||||
const [result] = await service.resolveVoices([
|
||||
{ sessionId: 'session', localId: 10, createTime: 100, svrId: '1000' }
|
||||
])
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
const wav = Buffer.from(result.data!, 'base64')
|
||||
expect(wav.readUInt32LE(24)).toBe(16_000)
|
||||
expect(wav.readUInt16LE(22)).toBe(1)
|
||||
expect((wav.length - 44) / (wav.readUInt32LE(24) * wav.readUInt16LE(22) * 2)).toBe(1)
|
||||
})
|
||||
|
||||
it('retries failed batch entries with the compatible single-item lookup', async () => {
|
||||
const getVoiceDataBatch = vi.fn().mockResolvedValue([
|
||||
{ success: false, error: '获取语音数据失败' },
|
||||
|
||||
Reference in New Issue
Block a user