function registerVideoDownloadRoutes(app, deps) { const { path, fs, crypto, Readable, Transform, pipeline, dataVideoDir, downloadTasks, normalizeRemoteUrls, ensureVideoFilename, ensureUniqueVideoFilename, fetchRemoteVideoResponse, createManagedVideoEntry, readManifest, writeManifest, removeFileIfExists, } = deps; app.post('/api/download/video', (req, res) => { const { url, urls, filename, title, description, tags, thumbnail, duration, resolution, awemeId, authorName, } = req.body || {}; if (!url || typeof url !== 'string') { return res.status(400).json({ error: '缺少 url 参数' }); } const candidateUrls = normalizeRemoteUrls(url, urls); if (candidateUrls.length === 0) { return res.status(400).json({ error: '无效的视频地址' }); } const safeFilename = ensureUniqueVideoFilename(ensureVideoFilename(filename || title || awemeId || 'douyin-video.mp4', url)); const filePath = path.join(dataVideoDir, safeFilename); const taskId = `DL-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`; const task = { id: taskId, status: 'pending', progress: 0, downloadedBytes: 0, totalBytes: 0, filename: safeFilename, created_at: new Date().toISOString(), }; downloadTasks.set(taskId, task); res.json({ success: true, taskId, filename: safeFilename }); (async () => { try { task.status = 'downloading'; console.log(`📥 开始下载远程视频: ${safeFilename} ← ${candidateUrls[0]}`); const { url: resolvedUrl, response } = await fetchRemoteVideoResponse(candidateUrls, { Accept: '*/*', 'User-Agent': 'Mozilla/5.0', Referer: 'https://www.douyin.com/', Origin: 'https://www.douyin.com', }); task.sourceUrl = resolvedUrl; const totalBytes = Number.parseInt(response.headers.get('content-length') || '0', 10) || 0; task.totalBytes = totalBytes; let downloadedBytes = 0; let chunkCount = 0; const progressStream = new Transform({ transform(chunk, encoding, callback) { downloadedBytes += chunk.length; chunkCount += 1; task.downloadedBytes = downloadedBytes; task.progress = totalBytes > 0 ? Math.min(99, Math.round((downloadedBytes / totalBytes) * 100)) : Math.min(95, Math.max(task.progress || 0, Math.min(95, chunkCount))); callback(null, chunk); }, }); await pipeline( Readable.fromWeb(response.body), progressStream, fs.createWriteStream(filePath), ); const stat = fs.statSync(filePath); const manifest = readManifest(); const videoEntry = createManagedVideoEntry({ filename: safeFilename, title, description, tags, thumbnail, duration, resolution, awemeId, authorName, size: stat.size, }); manifest.push(videoEntry); writeManifest(manifest); task.status = 'completed'; task.progress = 100; task.completed_at = new Date().toISOString(); task.video = videoEntry; console.log(`✅ 远程视频下载完成: ${safeFilename} (${(stat.size / 1024 / 1024).toFixed(1)}MB)`); } catch (error) { removeFileIfExists(filePath); task.status = 'failed'; task.error = error.message; task.failed_at = new Date().toISOString(); console.error('❌ 远程视频下载失败:', error); } })(); }); app.get('/api/download/video/:taskId', (req, res) => { const task = downloadTasks.get(req.params.taskId); if (!task) { return res.status(404).json({ error: '未找到下载任务' }); } res.json(task); }); } module.exports = { registerVideoDownloadRoutes };