mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-10-06 13:54:10 +08:00
Merge branch 'develop' of github.com:Wxw-Gu/TraceMemo into develop
This commit is contained in:
@@ -941,10 +941,7 @@ async function runSingleExport(
|
|||||||
request.format === 'html'
|
request.format === 'html'
|
||||||
? options.outputFolderName || safeFilePart(request.outputName)
|
? options.outputFolderName || safeFilePart(request.outputName)
|
||||||
: `${safeFilePart(request.outputName)}_${exportStamp()}`
|
: `${safeFilePart(request.outputName)}_${exportStamp()}`
|
||||||
const root =
|
const root = options.outputRoot || request.outputDirectory || (await resolveDefaultExportRoot(outputFolder))
|
||||||
options.outputRoot ||
|
|
||||||
request.outputDirectory ||
|
|
||||||
(await resolveDefaultExportRoot(outputFolder))
|
|
||||||
await fs.mkdir(root, { recursive: true })
|
await fs.mkdir(root, { recursive: true })
|
||||||
const outputDir = join(root, outputFolder)
|
const outputDir = join(root, outputFolder)
|
||||||
const outputPath =
|
const outputPath =
|
||||||
@@ -1236,7 +1233,16 @@ async function runSingleExport(
|
|||||||
markResourceExists(voiceUrl)
|
markResourceExists(voiceUrl)
|
||||||
}
|
}
|
||||||
message.voiceDataUrl = 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) {
|
} catch (error) {
|
||||||
keepMediaError(
|
keepMediaError(
|
||||||
request,
|
request,
|
||||||
@@ -1478,10 +1484,13 @@ async function runSingleExport(
|
|||||||
} else if (message.contentData.type === 'sticker' && stickerService) {
|
} else if (message.contentData.type === 'sticker' && stickerService) {
|
||||||
const stickerSource = message.contentData.url || message.contentData.thumbUrl
|
const stickerSource = message.contentData.url || message.contentData.thumbUrl
|
||||||
const result = await stickerService.resolveSticker(stickerSource, message.contentData.md5)
|
const result = await stickerService.resolveSticker(stickerSource, message.contentData.md5)
|
||||||
const decoded = result.data ? decodeDataUrl(result.data) : null
|
const decoded = result.data
|
||||||
const stickerExtension = decoded ? detectAssetExtension(decoded.buffer) : null
|
? decodeDataUrl(result.data)
|
||||||
if (decoded && stickerExtension) {
|
: stickerSource
|
||||||
const name = `sticker_${bufferHashPart(decoded.buffer)}.${stickerExtension}`
|
? await readAvatarAsset(stickerSource)
|
||||||
|
: null
|
||||||
|
if (decoded) {
|
||||||
|
const name = `sticker_${bufferHashPart(decoded.buffer)}.${decoded.extension}`
|
||||||
const mediaUrl = `media/${name}`
|
const mediaUrl = `media/${name}`
|
||||||
if (!(await resourceExists(mediaUrl))) {
|
if (!(await resourceExists(mediaUrl))) {
|
||||||
await fs.writeFile(join(outputDir, 'media', name), decoded.buffer)
|
await fs.writeFile(join(outputDir, 'media', name), decoded.buffer)
|
||||||
|
|||||||
@@ -125,7 +125,11 @@ export class VoiceService {
|
|||||||
codec: 'silk',
|
codec: 'silk',
|
||||||
sourceHash: createHash('sha256').update(silkData).digest('hex')
|
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 data = wavData.toString('base64')
|
||||||
const cacheKey = this.buildCacheKey(
|
const cacheKey = this.buildCacheKey(
|
||||||
reference.sessionId,
|
reference.sessionId,
|
||||||
@@ -239,7 +243,7 @@ export class VoiceService {
|
|||||||
|
|
||||||
private createWavBuffer(
|
private createWavBuffer(
|
||||||
pcmData: Buffer,
|
pcmData: Buffer,
|
||||||
sampleRate: number = 24000,
|
sampleRate: number = 16000,
|
||||||
channels: number = 1
|
channels: number = 1
|
||||||
): Buffer {
|
): Buffer {
|
||||||
const pcmLength = pcmData.length
|
const pcmLength = pcmData.length
|
||||||
|
|||||||
@@ -2,6 +2,29 @@ import { describe, expect, it, vi } from 'vitest'
|
|||||||
import { VoiceService } from '../../src/main/voice-service'
|
import { VoiceService } from '../../src/main/voice-service'
|
||||||
|
|
||||||
describe('VoiceService batch lookup', () => {
|
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 () => {
|
it('retries failed batch entries with the compatible single-item lookup', async () => {
|
||||||
const getVoiceDataBatch = vi.fn().mockResolvedValue([
|
const getVoiceDataBatch = vi.fn().mockResolvedValue([
|
||||||
{ success: false, error: '获取语音数据失败' },
|
{ success: false, error: '获取语音数据失败' },
|
||||||
|
|||||||
Reference in New Issue
Block a user