From 62404f406379ad4d53954d55ccf6829dc303d03b Mon Sep 17 00:00:00 2001 From: qikongjian Date: Wed, 17 Sep 2025 14:51:34 +0800 Subject: [PATCH] =?UTF-8?q?=E5=89=AA=E8=BE=91=E8=AE=A1=E5=88=92=E5=8A=A0?= =?UTF-8?q?=E5=85=A5=E9=87=8D=E8=AF=95=E6=9C=BA=E5=88=B6=E6=9C=80=E9=95=BF?= =?UTF-8?q?=E9=87=8D=E8=AF=95=E6=97=B6=E9=97=B4=E4=B8=BA10=E5=88=86?= =?UTF-8?q?=E9=92=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/export-service.ts | 108 ++++++++++++++++++++++++++++++++-------- 1 file changed, 86 insertions(+), 22 deletions(-) diff --git a/utils/export-service.ts b/utils/export-service.ts index 50ab0b5..69738a8 100644 --- a/utils/export-service.ts +++ b/utils/export-service.ts @@ -151,25 +151,83 @@ export class VideoExportService { } } + /** + * 获取剪辑计划(带重试机制) + * 8秒重试一次,最长重试10分钟,直到成功 + */ + private async getEditingPlanWithRetry(episodeId: string, progressCallback?: ExportProgressCallback['onProgress']): Promise { + const maxRetryTime = 10 * 60 * 1000; // 10分钟 + const retryInterval = 8 * 1000; // 8秒 + const maxAttempts = Math.floor(maxRetryTime / retryInterval); // 75次 + + console.log('🎬 开始获取剪辑计划(带重试机制)...'); + console.log(`⏰ 重试配置: ${retryInterval/1000}秒间隔,最多${maxAttempts}次,总时长${maxRetryTime/1000/60}分钟`); + + let attempts = 0; + + while (attempts < maxAttempts) { + attempts++; + + try { + console.log(`🔄 第${attempts}次尝试获取剪辑计划...`); + + // 触发进度回调 + if (progressCallback) { + // 剪辑计划获取占总进度的80%,因为可能需要很长时间 + const progressPercent = Math.min(Math.round((attempts / maxAttempts) * 80), 75); + progressCallback({ + status: 'processing', + percentage: progressPercent, + message: `正在获取剪辑计划... (第${attempts}次尝试,预计${Math.ceil((maxAttempts - attempts) * retryInterval / 1000 / 60)}分钟)`, + stage: 'fetching_editing_plan' + }); + } + + const editPlanResponse = await getGenerateEditPlan({ project_id: episodeId }); + + if (editPlanResponse.successful && editPlanResponse.data.editing_plan) { + console.log(`✅ 第${attempts}次尝试成功获取剪辑计划`); + return editPlanResponse.data.editing_plan; + } else { + console.log(`⚠️ 第${attempts}次尝试失败: ${editPlanResponse.message || '剪辑计划未生成'}`); + + if (attempts >= maxAttempts) { + throw new Error(`获取剪辑计划失败,已重试${maxAttempts}次: ${editPlanResponse.message}`); + } + + console.log(`⏳ ${retryInterval/1000}秒后进行第${attempts + 1}次重试...`); + await new Promise(resolve => setTimeout(resolve, retryInterval)); + } + } catch (error) { + console.log(`❌ 第${attempts}次尝试出现错误:`, error); + + if (attempts >= maxAttempts) { + throw new Error(`获取剪辑计划失败,已重试${maxAttempts}次: ${error instanceof Error ? error.message : '未知错误'}`); + } + + console.log(`⏳ ${retryInterval/1000}秒后进行第${attempts + 1}次重试...`); + await new Promise(resolve => setTimeout(resolve, retryInterval)); + } + } + + throw new Error(`获取剪辑计划超时,已重试${maxAttempts}次`); + } + /** * 基于剪辑计划生成导出数据 */ - private async generateExportDataFromEditingPlan(episodeId: string, taskObject: any): Promise<{ + private async generateExportDataFromEditingPlan( + episodeId: string, + taskObject: any, + progressCallback?: ExportProgressCallback['onProgress'] + ): Promise<{ exportRequest: ExportRequest; editingPlan: any; }> { - console.log('🎬 开始获取剪辑计划...'); - try { - // 1. 首先获取剪辑计划 - const editPlanResponse = await getGenerateEditPlan({ project_id: episodeId }); - - if (!editPlanResponse.successful || !editPlanResponse.data.editing_plan) { - throw new Error('获取剪辑计划失败: ' + editPlanResponse.message); - } - - const editingPlan = editPlanResponse.data.editing_plan; - console.log('📋 获取到剪辑计划:', editingPlan); + // 1. 首先获取剪辑计划(带重试机制) + const editingPlan = await this.getEditingPlanWithRetry(episodeId, progressCallback); + console.log('📋 最终获取到剪辑计划:', editingPlan); // 2. 检查是否有可用的视频数据 if (!taskObject.videos?.data || taskObject.videos.data.length === 0) { @@ -570,22 +628,28 @@ export class VideoExportService { // 重试循环 while (currentAttempt <= this.config.maxRetries) { try { - // 第一步:获取剪辑计划(只在第一次尝试时获取) + // 第一步:获取剪辑计划(只在第一次尝试时获取,或缓存不存在时重新获取) let exportRequest: ExportRequest; - if (currentAttempt === 1) { + if (currentAttempt === 1 || !this.cachedExportRequest) { console.log('🎬 步骤1: 获取剪辑计划...'); - const { exportRequest: generatedExportRequest, editingPlan: generatedEditingPlan } = await this.generateExportDataFromEditingPlan(episodeId, taskObject); - exportRequest = generatedExportRequest; + try { + const { exportRequest: generatedExportRequest, editingPlan: generatedEditingPlan } = await this.generateExportDataFromEditingPlan(episodeId, taskObject, progressCallback); + exportRequest = generatedExportRequest; - console.log('📤 生成的导出请求数据:', exportRequest); - console.log(`📊 包含 ${exportRequest.ir.video.length} 个视频片段,总时长: ${exportRequest.ir.duration}ms`); - console.log('🎬 使用的剪辑计划:', generatedEditingPlan); + console.log('📤 生成的导出请求数据:', exportRequest); + console.log(`📊 包含 ${exportRequest.ir.video.length} 个视频片段,总时长: ${exportRequest.ir.duration}ms`); + console.log('🎬 使用的剪辑计划:', generatedEditingPlan); - // 缓存exportRequest以便重试时使用 - this.cachedExportRequest = exportRequest; + // 缓存exportRequest以便重试时使用 + this.cachedExportRequest = exportRequest; + } catch (editPlanError) { + console.error('❌ 获取剪辑计划失败,无法继续导出:', editPlanError); + // 剪辑计划获取失败是致命错误,直接抛出,不进行导出重试 + throw editPlanError; + } } else { // 重试时使用缓存的请求数据 - exportRequest = this.cachedExportRequest!; + exportRequest = this.cachedExportRequest; console.log(`🔄 第${currentAttempt}次重试,使用缓存的导出请求数据`); }