mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-10-05 21:05:39 +08:00
fix: 修复语音消息取错、时长精度与转写不刷新
- 时长解析:改取 <voicemsg voicelength>(毫秒),不再误取 length(SILK 字节数) - 时长显示:四舍五入对齐微信口径,有原生时长时不被解码时长覆盖 - 转写:重新识别改为强制重算,跳过身份级缓存短路(音频级缓存仍生效) - 日报:语音累计秒数显示取整,不再出现小数
This commit is contained in:
@@ -73,12 +73,14 @@ export function VoicePlayer({
|
||||
const audio = new Audio()
|
||||
audio.preload = 'auto'
|
||||
audio.src = blobUrl
|
||||
audio.onloadedmetadata = () => {
|
||||
if (Number.isFinite(audio.duration)) setAudioDuration(audio.duration)
|
||||
}
|
||||
audio.ontimeupdate = () => {
|
||||
// Silk 解码出来的时长比微信的 length 系统性偏短(实测少 20–279ms),
|
||||
// 用它覆盖会把刚补回来的精度又弄丢:有原生时长时一律不覆盖,没有才回退。
|
||||
const useDecodedDuration = () => {
|
||||
if (duration !== undefined) return
|
||||
if (Number.isFinite(audio.duration)) setAudioDuration(audio.duration)
|
||||
}
|
||||
audio.onloadedmetadata = useDecodedDuration
|
||||
audio.ontimeupdate = useDecodedDuration
|
||||
audio.onended = () => {
|
||||
setIsPlaying(false)
|
||||
if (globalCurrentAudio === audio) {
|
||||
@@ -89,7 +91,7 @@ export function VoicePlayer({
|
||||
audioRef.current = audio
|
||||
objectUrlRef.current = blobUrl
|
||||
return audio
|
||||
}, [])
|
||||
}, [duration])
|
||||
|
||||
const handlePlayPause = useCallback(async () => {
|
||||
if (loading) return
|
||||
@@ -163,7 +165,8 @@ export function VoicePlayer({
|
||||
|
||||
setTranscribing(true)
|
||||
try {
|
||||
const result = await window.api.recognizeVoice(voiceReference)
|
||||
// 用户主动触发:跳过身份级缓存,重新取音频(音频级缓存仍生效)。
|
||||
const result = await window.api.recognizeVoice(voiceReference, { force: true })
|
||||
if (result.success) {
|
||||
setTranscript(result.transcript?.trim() || '未识别出文字')
|
||||
setModelStatus(null)
|
||||
@@ -204,8 +207,11 @@ export function VoicePlayer({
|
||||
|
||||
const formatDuration = (seconds: number | undefined): string => {
|
||||
if (!seconds || !isFinite(seconds)) return '0:00'
|
||||
const mins = Math.floor(seconds / 60)
|
||||
const secs = Math.floor(seconds % 60)
|
||||
// 微信的口径是四舍五入到整秒(length=4211ms 显示 4"),所以这里也必须 round 而非 floor。
|
||||
// 先整体取整再拆分钟/秒,59.6 → "1:00" 而不会冒出 "0:60"。
|
||||
const rounded = Math.round(seconds)
|
||||
const mins = Math.floor(rounded / 60)
|
||||
const secs = rounded % 60
|
||||
return `${mins}:${secs.toString().padStart(2, '0')}`
|
||||
}
|
||||
|
||||
|
||||
@@ -138,9 +138,13 @@ export const summarySender = (
|
||||
export const summaryContent = (message: Message): string => {
|
||||
const data = message.contentData
|
||||
if (message.type === '语音' || data?.type === 'voice') {
|
||||
// 语音时长来自 <voicemsg voicelength>(毫秒),是带小数的秒(1600ms → 1.6):
|
||||
// 累加时保留精度,只在显示给人的文案里取整。
|
||||
const durationSec = data?.type === 'voice' ? data.duration : undefined
|
||||
const durationLabel = durationSec ? ` ${Math.round(durationSec)}秒` : ''
|
||||
return message.voiceTranscript?.trim()
|
||||
? `[语音${data?.type === 'voice' && data.duration ? ` ${data.duration}秒` : ''}] ${message.voiceTranscript.trim()}`
|
||||
: `[语音${data?.type === 'voice' && data.duration ? ` ${data.duration}秒` : ''}]`
|
||||
? `[语音${durationLabel}] ${message.voiceTranscript.trim()}`
|
||||
: `[语音${durationLabel}]`
|
||||
}
|
||||
if (!data) return message.content?.trim() || `[${message.type || '消息'}]`
|
||||
|
||||
@@ -567,14 +571,14 @@ const buildMediaSection = async (
|
||||
voiceHighlights.push({
|
||||
title: '语音输出王',
|
||||
sender: voiceLeaderboard[0].sender,
|
||||
note: `共发送 ${voiceLeaderboard[0].count} 条语音,累计 ${voiceLeaderboard[0].durationSec} 秒。`
|
||||
note: `共发送 ${voiceLeaderboard[0].count} 条语音,累计 ${Math.round(voiceLeaderboard[0].durationSec)} 秒。`
|
||||
})
|
||||
}
|
||||
if (bestStreak && bestStreak.count >= 2) {
|
||||
voiceHighlights.push({
|
||||
title: '连续发言时刻',
|
||||
sender: bestStreak.sender,
|
||||
note: `${bestStreak.time} 连发 ${bestStreak.count} 条语音,共 ${bestStreak.duration} 秒。`
|
||||
note: `${bestStreak.time} 连发 ${bestStreak.count} 条语音,共 ${Math.round(bestStreak.duration)} 秒。`
|
||||
})
|
||||
}
|
||||
|
||||
@@ -810,7 +814,7 @@ export const buildGroupReportFacts = async (
|
||||
|
||||
const factsPrompt = [
|
||||
`报告模式:${reportMode === 'compact' ? '精简版(30秒可读完)' : '完整版(保留更多上下文)'}`,
|
||||
`消息统计:共 ${transcriptRows.length} 条,活跃成员 ${speakerCounts.size} 人,图片 ${imageCount} 张,表情 ${stickerCount} 条,语音 ${voiceCount} 条(累计 ${voiceDurationSec} 秒)。`,
|
||||
`消息统计:共 ${transcriptRows.length} 条,活跃成员 ${speakerCounts.size} 人,图片 ${imageCount} 张,表情 ${stickerCount} 条,语音 ${voiceCount} 条(累计 ${Math.round(voiceDurationSec)} 秒)。`,
|
||||
activeTimeline ? `活跃时段:${activeTimeline}` : '',
|
||||
// AI 图片理解结果(由 ImageInsightService 提供,缓存命中或已调用 Vision)
|
||||
(media.visionGallery?.length ?? 0) > 0
|
||||
@@ -824,7 +828,7 @@ export const buildGroupReportFacts = async (
|
||||
voiceLeaderboard.length
|
||||
? `语音榜:${voiceLeaderboard
|
||||
.slice(0, 3)
|
||||
.map((item) => `${item.sender} ${item.count} 条 / ${item.durationSec} 秒`)
|
||||
.map((item) => `${item.sender} ${item.count} 条 / ${Math.round(item.durationSec)} 秒`)
|
||||
.join(';')}`
|
||||
: '',
|
||||
collectQuestionCandidates(messages, contact, isGroup).length
|
||||
|
||||
Reference in New Issue
Block a user