| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- 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 };
|