diff --git a/scripts/batch-export.js b/scripts/batch-export.js index 1bbf8dc..8b481a3 100644 --- a/scripts/batch-export.js +++ b/scripts/batch-export.js @@ -232,9 +232,9 @@ class BatchVideoExporter { } /** 调用导出流接口 */ - async callExportStream(exportRequest) { + async callExportStream(exportRequest, attemptNumber = 1) { const projectId = exportRequest.project_id; - this.log(`项目 ${projectId}: 开始调用导出流接口...`); + this.log(`项目 ${projectId}: 开始调用导出流接口(第${attemptNumber}次尝试)...`); // 使用fetch API(Node.js 18+支持) let fetch; @@ -361,9 +361,11 @@ class BatchVideoExporter { export_id: progress && progress.export_id }; } else if (status === 'failed') { - throw new Error(`导出任务失败: ${(progress && progress.message) || '未知错误'}`); + const errorMessage = `导出任务失败: ${(progress && progress.message) || '未知错误'}`; + throw new Error(errorMessage); } else if (status === 'error') { - throw new Error(`导出任务错误: ${(progress && progress.message) || '未知错误'}`); + const errorMessage = `导出任务错误: ${(progress && progress.message) || '未知错误'}`; + throw new Error(errorMessage); } else { const percentage = (progress && progress.percentage) || 0; const message = (progress && progress.message) || '处理中...'; @@ -374,6 +376,14 @@ class BatchVideoExporter { await new Promise(resolve => setTimeout(resolve, 5000)); // 5秒间隔 } catch (error) { this.log(`项目 ${projectId}: 轮询进度出错: ${error.message}`, 'error'); + // 对于明确的失败/错误状态,立即抛出,让上层进行重新导出重试 + if ( + typeof error?.message === 'string' && + (error.message.includes('导出任务失败') || error.message.includes('导出任务错误')) + ) { + throw error; + } + // 其他网络类错误,继续有限次数的轮询重试 attempts++; if (attempts >= maxAttempts) { throw error; @@ -385,6 +395,67 @@ class BatchVideoExporter { throw new Error(`轮询超时,已尝试${maxAttempts}次`); } + /** 导出任务处理(包含重试逻辑) */ + async processExportWithRetry(exportRequest) { + const projectId = exportRequest.project_id; + const maxExportRetries = 3; // 导出重试3次 + + for (let attempt = 1; attempt <= maxExportRetries; attempt++) { + try { + this.log(`项目 ${projectId}: 开始第${attempt}次导出尝试...`); + + // 1. 调用导出流接口 + let exportResult = await this.callExportStream(exportRequest, attempt); + + // 2. 如果SSE没有返回完整结果,使用轮询 + let taskId = null; + if (!exportResult || !exportResult.video_url) { + taskId = (exportResult && exportResult.export_id) || (exportResult && exportResult.task_id); + if (!taskId) { + throw new Error('无法获取任务ID,无法轮询进度'); + } + + try { + exportResult = await this.pollExportProgress(taskId, projectId); + } catch (pollError) { + // 如果轮询过程中发现任务失败,并且还有重试机会,则重新导出 + if (pollError.message.includes('导出任务失败') || pollError.message.includes('导出任务错误')) { + this.log(`项目 ${projectId}: 第${attempt}次导出失败 - ${pollError.message}`, 'warn'); + + if (attempt < maxExportRetries) { + this.log(`项目 ${projectId}: 准备重新导出(剩余${maxExportRetries - attempt}次重试机会)...`); + await new Promise(resolve => setTimeout(resolve, 3000)); // 等待3秒后重试 + continue; + } else { + throw new Error(`导出失败,已重试${maxExportRetries}次: ${pollError.message}`); + } + } else { + // 其他错误(如网络错误、超时等)直接抛出 + throw pollError; + } + } + } + + // 3. 导出成功,返回结果 + this.log(`项目 ${projectId}: 第${attempt}次导出尝试成功!`); + return exportResult; + + } catch (error) { + // 如果是导出接口调用失败(如网络错误、服务器错误等) + this.log(`项目 ${projectId}: 第${attempt}次导出尝试失败 - ${error.message}`, 'warn'); + + if (attempt < maxExportRetries) { + this.log(`项目 ${projectId}: 准备重新导出(剩余${maxExportRetries - attempt}次重试机会)...`); + await new Promise(resolve => setTimeout(resolve, 3000)); // 等待3秒后重试 + } else { + throw new Error(`导出失败,已重试${maxExportRetries}次: ${error.message}`); + } + } + } + + throw new Error(`导出失败,已达到最大重试次数${maxExportRetries}`); + } + /** 处理单个项目 */ async processProject(projectId) { const status = { @@ -411,23 +482,13 @@ class BatchVideoExporter { // 2. 构建导出请求 const exportRequest = this.buildExportRequest(projectId, editingPlan); - // 3. 调用导出接口 + // 3. 调用导出接口(包含重试逻辑) status.status = 'exporting'; status.exportStarted = true; - let exportResult = await this.callExportStream(exportRequest); + const exportResult = await this.processExportWithRetry(exportRequest); - // 4. 如果SSE没有返回完整结果,使用轮询 - if (!exportResult || !exportResult.video_url) { - const taskId = (exportResult && exportResult.export_id) || (exportResult && exportResult.task_id); - if (taskId) { - exportResult = await this.pollExportProgress(taskId, projectId); - } else { - throw new Error('无法获取任务ID,无法轮询进度'); - } - } - - // 5. 处理完成 + // 4. 处理完成 status.status = 'completed'; status.exportCompleted = true; status.videoUrl = exportResult.video_url; diff --git a/scripts/projects.example.txt b/scripts/projects.example.txt index 5b7ba92..7583ba1 100644 --- a/scripts/projects.example.txt +++ b/scripts/projects.example.txt @@ -1,36 +1,6 @@ # 项目ID列表示例文件 # 每行一个项目ID,以#开头的行为注释 - -107c5fcc-8348-4c3b-b9f3-f7474e24295d -6243a491-d8cd-46cf-8d11-933300f27569 -2b296c0f-dcc3-4141-8977-b9f3579bdb04 -2238ca87-1c6a-484a-9405-9e25d0999072 -481e672d-3a00-454f-bee0-71797d40ea17 -de805e6c-2374-4a5d-9f8a-f0b6baa3fad5 -64f137e0-06e5-4d30-86e7-28c6b8644de1 -8a132503-456a-462f-83e3-367636cd0286 -8d81241b-ae7c-431a-b953-85cfd6af77f3 -98b53d49-a02d-4fed-aa4e-62b6cc45fae8 -f23d558c-6f18-43f3-a373-8e3d1e8d278a -12daaba2-79c0-4fe8-bdc8-5b746b6bee3b -6b7ca8b9-5760-4908-aac6-b7388402652d -19db2b3e-9fb7-408c-90d5-35f9969b3d01 -89c20909-4ebf-4ccc-a5e7-401ef91ef242 -4de9ef61-cefd-4027-b07e-a2520a62f218 -d2e23eaf-e806-44c9-a241-7fc1af53dab4 -64941470-7689-4ea1-872c-672f2b5e4628 -469c5792-aa1a-4489-bfea-779d3782218c -7c55bd8e-8f16-4caa-a001-1a34497a9711 -86dcc7cf-f116-429f-b298-fe0878aa25ef -ded3ed74-b6fa-48fd-ad7e-fbfa717d54f5 -96da7e21-7284-4056-900e-59835e99b651 -892083d2-a7e7-4436-aac9-91cfdf24feae -52543696-fbe0-4fd9-bbe2-ef31e642fb8e -675ae612-d6ac-4017-98a6-ac9c4fd4eac9 -e430dfe1-3cb8-40bd-a6a9-f6b223052ff7 -3b12c5d3-7899-4ef6-97c4-8ff07ee5bc8e 35abfaba-d5b8-4caf-bbb7-89feaa007415 -71d52c66-b83b-4d51-91a2-be26f88f9eba 8013eeb8-6938-43e1-b5e1-91f68d860eb8 30c8582f-5231-4a31-af08-5bba4b6171f3 0cece1ba-ea3b-43cb-a7f2-10ce0de96936