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