import fs from 'fs-extra' import path from 'path' import crypto from 'crypto' import type { Wcdb4Client } from './wcdb4-client' type VideoAsset = { filePath: string posterPath?: string } export class VideoAssetService { private readonly urlTokens = new Map() private index: Map | null = null constructor(private readonly client: Wcdb4Client) {} resolve(hashes: string[]): { success: boolean; url?: string; poster?: string; error?: string } { const candidates = Array.from( new Set( hashes .map((value) => String(value || '') .trim() .toLowerCase() ) .filter((value) => /^[a-f0-9]{32}$/.test(value)) ) ) if (candidates.length === 0) return { success: false, error: '视频标识为空' } const hardlinkDb = path.join( this.client.getAccountRoot(), 'db_storage', 'hardlink', 'hardlink.db' ) const lookupKeys = [...candidates] if (fs.existsSync(hardlinkDb)) { for (const hash of candidates) { const resolved = this.client.resolveVideoHardlink(hash, hardlinkDb)?.resolved_md5 if (resolved) lookupKeys.unshift(String(resolved).trim().toLowerCase()) } } const index = this.getIndex() for (const key of lookupKeys) { const asset = index.get(key) || index.get(`${key}_raw`) if (!asset) continue return { success: true, url: this.createUrl(asset.filePath), poster: asset.posterPath ? this.createUrl(asset.posterPath) : undefined } } return { success: false, error: '本地未找到该视频文件' } } pathForToken(token: string): string | undefined { const filePath = this.urlTokens.get(token) if (!filePath || !fs.existsSync(filePath)) return undefined return filePath } private createUrl(filePath: string): string { const token = crypto.randomBytes(18).toString('hex') this.urlTokens.set(token, filePath) if (this.urlTokens.size > 500) { const first = this.urlTokens.keys().next().value if (first) this.urlTokens.delete(first) } return `wxe-media://local/${token}` } private getIndex(): Map { if (this.index) return this.index const result = new Map() const root = path.join(this.client.getAccountRoot(), 'msg', 'video') if (!fs.existsSync(root)) { this.index = result return result } for (const month of fs.readdirSync(root)) { const monthPath = path.join(root, month) if (!fs.statSync(monthPath).isDirectory()) continue for (const name of fs.readdirSync(monthPath)) { const match = /^([a-f0-9]{32})(?:(_raw))?\.(mp4|jpg)$/i.exec(name) if (!match) continue const key = `${match[1].toLowerCase()}${match[2] || ''}` const fullPath = path.join(monthPath, name) const existing = result.get(key) || { filePath: '' } if (match[3].toLowerCase() === 'mp4') existing.filePath = fullPath else if (!existing.posterPath) existing.posterPath = fullPath result.set(key, existing) } } for (const [key, asset] of result) { if (!asset.filePath) result.delete(key) } this.index = result return result } }