From 666d8896ee15b6dca5e7a8cabcd1ad5f6fb3b787 Mon Sep 17 00:00:00 2001 From: Nanin <7017924+nanin@users.noreply.github.com> Date: Sat, 8 Aug 2026 19:28:33 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dbug=EF=BC=8C=E9=92=88?= =?UTF-8?q?=E5=AF=B9=E9=83=A8=E5=88=86=E6=B2=A1=E6=9C=89MD5=E5=90=8D?= =?UTF-8?q?=E5=AD=97=E7=9A=84=E8=A7=86=E9=A2=91=E4=B9=9F=E8=83=BD=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=AF=BC=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/export-service.ts | 3 +-- src/main/index.ts | 8 ++++++- src/main/message-parser.ts | 6 ++--- src/main/video-asset-service.ts | 24 ++++++++++++++++++- src/preload/index.d.ts | 9 ++++++- src/preload/index.ts | 8 ++++++- src/renderer/src/components/VideoBubble.tsx | 6 +++-- .../src/components/chat/MessageBubble.tsx | 1 + src/shared/types.ts | 1 + tests/integration/export-media-flow.test.ts | 12 ++++++++-- tests/unit/message-parser.test.ts | 18 ++++++++++++++ tests/unit/video-asset-service.test.ts | 24 +++++++++++++++++++ 12 files changed, 107 insertions(+), 13 deletions(-) diff --git a/src/main/export-service.ts b/src/main/export-service.ts index 15eaa8a..f9f04ed 100644 --- a/src/main/export-service.ts +++ b/src/main/export-service.ts @@ -1152,11 +1152,10 @@ export async function runExport( ].filter((value): value is string => Boolean(value)) if (!videoService) { keepMediaError(request, message, '数据库未连接,无法定位本地视频') - } else if (hashes.length === 0) { - keepMediaError(request, message, '视频标识不完整,无法定位本地视频') } else { const resolved = await videoService.resolve(hashes, { createTime: message.createTime, + byteLength: message.contentData.byteLength, duration: message.contentData.duration, width: message.contentData.width, height: message.contentData.height diff --git a/src/main/index.ts b/src/main/index.ts index 35d6bf9..7de6595 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1339,7 +1339,13 @@ app.whenReady().then(async () => { async ( _, hashes: string[], - options?: { createTime?: number; duration?: number; width?: number; height?: number } + options?: { + createTime?: number + byteLength?: number + duration?: number + width?: number + height?: number + } ) => { if (!videoAssetService) { const client = chat.getChatDb()?.getWcdb4Client() diff --git a/src/main/message-parser.ts b/src/main/message-parser.ts index fcb937a..aa6e4c1 100644 --- a/src/main/message-parser.ts +++ b/src/main/message-parser.ts @@ -65,6 +65,7 @@ type VideoContent = { md5?: string newMd5?: string rawMd5?: string + byteLength?: number duration?: number width?: number height?: number @@ -160,12 +161,11 @@ function parseVideoMessage(content: string): ParsedContent { const md5 = normalizeMd5(extractXmlAttribute(decoded, 'videomsg', 'md5')) const newMd5 = normalizeMd5(extractXmlAttribute(decoded, 'videomsg', 'newmd5')) const rawMd5 = normalizeMd5(extractXmlAttribute(decoded, 'videomsg', 'rawmd5')) - if (!md5 && !newMd5 && !rawMd5) return { type: 'unknown', raw: content } - + const byteLength = Number(extractXmlAttribute(decoded, 'videomsg', 'length')) || undefined const duration = Number(extractXmlAttribute(decoded, 'videomsg', 'playlength')) || undefined const width = Number(extractXmlAttribute(decoded, 'videomsg', 'cdnthumbwidth')) || undefined const height = Number(extractXmlAttribute(decoded, 'videomsg', 'cdnthumbheight')) || undefined - return { type: 'video', md5, newMd5, rawMd5, duration, width, height } + return { type: 'video', md5, newMd5, rawMd5, byteLength, duration, width, height } } function parseSystemMessage(content: string): ParsedContent { diff --git a/src/main/video-asset-service.ts b/src/main/video-asset-service.ts index 2ea672d..3577105 100644 --- a/src/main/video-asset-service.ts +++ b/src/main/video-asset-service.ts @@ -10,6 +10,7 @@ type VideoAsset = { export type VideoResolveOptions = { createTime?: number + byteLength?: number duration?: number width?: number height?: number @@ -52,7 +53,13 @@ export class VideoAssetService { .filter((value) => /^[a-f0-9]{32}$/.test(value)) ) ) - if (candidates.length === 0) return { success: false, error: '视频标识为空' } + const hasMetadata = + Number(options.byteLength) > 0 || + Number(options.duration) > 0 || + (Number(options.width) > 0 && Number(options.height) > 0) + if (candidates.length === 0 && !hasMetadata) { + return { success: false, error: '视频标识为空' } + } const hardlinkDb = path.join( this.client.getAccountRoot(), @@ -178,6 +185,21 @@ export class VideoAssetService { let narrowed = assets let appliedCriteria = 0 + const byteLength = Number(options.byteLength) + if (byteLength > 0) { + const matches = narrowed.filter((asset) => { + try { + return fs.statSync(asset.filePath).size === byteLength + } catch { + return false + } + }) + if (matches.length > 0) { + narrowed = matches + appliedCriteria += 1 + } + } + const width = Number(options.width) const height = Number(options.height) if (width > 0 && height > 0) { diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index de0c44b..b0a0b02 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -102,6 +102,7 @@ export type ParsedContent = md5?: string newMd5?: string rawMd5?: string + byteLength?: number duration?: number width?: number height?: number @@ -278,7 +279,13 @@ declare global { }> getVideo: ( hashes: string[], - options?: { createTime?: number; duration?: number; width?: number; height?: number } + options?: { + createTime?: number + byteLength?: number + duration?: number + width?: number + height?: number + } ) => Promise<{ success: boolean; url?: string; poster?: string; error?: string }> getSticker: ( cdnUrl?: string, diff --git a/src/preload/index.ts b/src/preload/index.ts index a997f81..a0e87eb 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -179,7 +179,13 @@ const api = { ) => ipcRenderer.invoke('db:getImage', imageMd5, imageDatNameOrThumb, sessionId, options), getVideo: ( hashes: string[], - options?: { createTime?: number; duration?: number; width?: number; height?: number } + options?: { + createTime?: number + byteLength?: number + duration?: number + width?: number + height?: number + } ) => ipcRenderer.invoke('db:getVideo', hashes, options), getSticker: (cdnUrl?: string, md5?: string) => ipcRenderer.invoke('db:getSticker', cdnUrl, md5), startExport: (request: ExportRequest) => ipcRenderer.invoke('export:start', request), diff --git a/src/renderer/src/components/VideoBubble.tsx b/src/renderer/src/components/VideoBubble.tsx index ff23765..7f17ab6 100644 --- a/src/renderer/src/components/VideoBubble.tsx +++ b/src/renderer/src/components/VideoBubble.tsx @@ -5,6 +5,7 @@ interface VideoBubbleProps { newMd5?: string rawMd5?: string createTime?: number + byteLength?: number duration?: number width?: number height?: number @@ -15,6 +16,7 @@ export function VideoBubble({ newMd5, rawMd5, createTime, + byteLength, duration, width, height @@ -28,7 +30,7 @@ export function VideoBubble({ useEffect(() => { let cancelled = false window.api - .getVideo(hashes, { createTime, duration, width, height }) + .getVideo(hashes, { createTime, byteLength, duration, width, height }) .then((result) => { if (!cancelled) setMedia(result.success ? result : { error: result.error }) }) @@ -38,7 +40,7 @@ export function VideoBubble({ return () => { cancelled = true } - }, [createTime, duration, hashes, height, width]) + }, [byteLength, createTime, duration, hashes, height, width]) if (!media.url) { return