Ver código fonte

1.新增自动创建素材及查询素材结果的公共方法2.新增生成片段视频接口3.新增处理Seedance2.0提示词的公共方法

lh 1 mês atrás
pai
commit
1239cc766e

+ 1 - 1
app/Http/Controllers/Anime/AnimeController.php

@@ -708,7 +708,7 @@ class AnimeController extends BaseController
             flush();
             
             $startTime = time();
-            $maxDuration = 300; // 5分钟超时
+            $maxDuration = 900; // 15分钟超时
             $checkInterval = 5; // 每5秒检查一次
             
             while (time() - $startTime < $maxDuration) {

+ 1 - 0
app/Models/MpGenerateVideoTask.php

@@ -33,6 +33,7 @@ class MpGenerateVideoTask extends Model
         'api_type', // 新增:API类型标识(seedance_1_5_pro, jimeng_ai等)
         'last_frame_url', // 新增:尾帧图片URL(用于return_last_frame功能)
         'alias_segment_id', // 新增:关联分镜ID
+        'alias_act_id', // 新增: 关联片段ID
     ];
 
     protected $casts = [

+ 151 - 0
app/Services/AIGeneration/AIImageGenerationService.php

@@ -1270,4 +1270,155 @@ class AIImageGenerationService
             'can_submit_new_task' => $processingCount === 0
         ];
     }
+
+    /**
+     * 处理智真AI的参考图片转素材
+     *
+     * @param array $referenceImages 参考图片URL数组
+     * @param string &$fullPrompt 完整提示词(引用传递,用于删除失败图片的标记)
+     * @return array 返回素材ID数组
+     */
+    public function processReferenceImagesToAssets(array $referenceImages, string &$fullPrompt): array
+    {
+        $assetIds = [];
+        $failedIndexes = []; // 记录失败的图片序号
+        
+        foreach ($referenceImages as $index => $imageUrl) {
+            $imageIndex = $index + 1; // 图片序号从1开始
+            
+            try {
+                dLog('generate')->info("开始处理参考图片 {$imageIndex}: {$imageUrl}");
+                
+                // 1. 创建素材
+                $createResult = $this->createAsset([
+                    'url' => $imageUrl,
+                    'asset_type' => MpAsset::TYPE_IMAGE,
+                    'name' => "参考图片_{$imageIndex}_" . time(),
+                ]);
+                
+                if ($createResult['code'] !== 0) {
+                    dLog('generate')->warning("图片 {$imageIndex} 创建素材失败: " . $createResult['msg']);
+                    logDB('generate', 'warning', '创建素材失败', [
+                        'image_url' => $imageUrl,
+                        'index' => $imageIndex,
+                        'error' => $createResult['msg']
+                    ]);
+                    $failedIndexes[] = $imageIndex;
+                    continue;
+                }
+                
+                $assetId = $createResult['data']['asset_id'];
+                dLog('generate')->info("图片 {$imageIndex} 素材创建成功,asset_id: {$assetId},开始轮询查询状态");
+                
+                // 2. 轮询查询素材状态(每3秒查询一次,最多查询100次即5分钟)
+                $maxAttempts = 100;
+                $attempt = 0;
+                $assetStatus = null;
+                
+                while ($attempt < $maxAttempts) {
+                    sleep(3); // 等待3秒
+                    $attempt++;
+                    
+                    $getResult = $this->getAsset($assetId);
+                    
+                    if ($getResult['code'] !== 0) {
+                        dLog('generate')->warning("图片 {$imageIndex} 查询素材状态失败: " . $getResult['msg']);
+                        continue;
+                    }
+                    
+                    $assetStatus = $getResult['data']['asset']['status'] ?? null;
+                    dLog('generate')->info("图片 {$imageIndex} 素材状态查询第 {$attempt} 次,状态: {$assetStatus}");
+                    
+                    if ($assetStatus === 'Active') {
+                        // 素材可用
+                        $assetIds[] = $assetId;
+                        dLog('generate')->info("图片 {$imageIndex} 素材处理成功,asset_id: {$assetId}");
+                        logDB('generate', 'info', '素材转换成功', [
+                            'image_url' => $imageUrl,
+                            'index' => $imageIndex,
+                            'asset_id' => $assetId
+                        ]);
+                        break;
+                    } elseif ($assetStatus === 'Failed') {
+                        // 素材处理失败
+                        dLog('generate')->warning("图片 {$imageIndex} 素材处理失败");
+                        logDB('generate', 'warning', '素材处理失败', [
+                            'image_url' => $imageUrl,
+                            'index' => $imageIndex,
+                            'asset_id' => $assetId
+                        ]);
+                        $failedIndexes[] = $imageIndex;
+                        break;
+                    }
+                    // 其他状态(Processing等)继续等待
+                }
+                
+                // 如果超时还未成功
+                if ($attempt >= $maxAttempts && $assetStatus !== 'Active') {
+                    dLog('generate')->warning("图片 {$imageIndex} 素材处理超时");
+                    logDB('generate', 'warning', '素材处理超时', [
+                        'image_url' => $imageUrl,
+                        'index' => $imageIndex,
+                        'asset_id' => $assetId,
+                        'last_status' => $assetStatus
+                    ]);
+                    $failedIndexes[] = $imageIndex;
+                }
+                
+            } catch (\Exception $e) {
+                dLog('generate')->error("图片 {$imageIndex} 处理异常: " . $e->getMessage());
+                logDB('generate', 'error', '素材转换异常', [
+                    'image_url' => $imageUrl,
+                    'index' => $imageIndex,
+                    'error' => $e->getMessage()
+                ]);
+                $failedIndexes[] = $imageIndex;
+            }
+        }
+        
+        // 处理失败的图片:从 fullPrompt 中删除对应的标记并调整序号
+        if (!empty($failedIndexes)) {
+            dLog('generate')->info('开始处理失败图片的提示词调整,失败序号: ' . implode(',', $failedIndexes));
+            
+            // 按序号从大到小排序,避免删除时影响后续序号
+            rsort($failedIndexes);
+            
+            foreach ($failedIndexes as $failedIndex) {
+                // 删除失败图片的标记,例如 <图片1>、<图片2> 等
+                $pattern = "/<图片{$failedIndex}>/u";
+                $fullPrompt = preg_replace($pattern, '', $fullPrompt);
+                dLog('generate')->info("已从提示词中删除 <图片{$failedIndex}>");
+            }
+            
+            // 重新调整剩余图片的序号
+            // 例如:如果图片2失败,图片3、图片4需要改为图片2、图片3
+            $needAdjust = false;
+            foreach ($failedIndexes as $failedIndex) {
+                $needAdjust = true;
+                // 将所有大于失败序号的图片标记序号减1
+                for ($i = $failedIndex + 1; $i <= count($referenceImages); $i++) {
+                    $oldPattern = "/<图片{$i}>/u";
+                    $newTag = "<图片" . ($i - 1) . ">";
+                    $fullPrompt = preg_replace($oldPattern, $newTag, $fullPrompt);
+                }
+            }
+            
+            if ($needAdjust) {
+                dLog('generate')->info('提示词图片序号调整完成');
+                logDB('generate', 'info', '提示词调整', [
+                    'failed_indexes' => $failedIndexes,
+                    'adjusted_prompt' => $fullPrompt
+                ]);
+            }
+        }
+        
+        dLog('generate')->info('参考图片转素材完成,成功数量: ' . count($assetIds) . ',失败数量: ' . count($failedIndexes));
+
+        // 格式化为可直接在API接口中使用的数组
+        foreach($assetIds as &$assetId) {
+            $assetId = "asset://{$assetId}";
+        }
+        
+        return $assetIds;
+    }
 }

+ 2 - 0
app/Services/AIGeneration/AIVideoGenerationService.php

@@ -433,6 +433,7 @@ class AIVideoGenerationService
         $task = MpGenerateVideoTask::create([
             'task_id' => $taskId,
             'alias_segment_id' => $params['alias_segment_id'] ?? '',
+            'alias_act_id' => $params['alias_act_id'] ?? '',
             'prompt' => $params['prompt'] ?? '',
             'first_frame_url' => $params['first_frame_url'] ?? '',
             'tail_frame_url' => $params['tail_frame_url'] ?? '',
@@ -2206,6 +2207,7 @@ class AIVideoGenerationService
         $task = MpGenerateVideoTask::create([
             'task_id' => $taskId,
             'alias_segment_id' => $params['alias_segment_id'] ?? '',
+            'alias_act_id' => $params['alias_act_id'] ?? '',
             'prompt' => $params['prompt'] ?? '',
             'first_frame_url' => $params['first_frame_url'] ?? '',
             'tail_frame_url' => $params['last_frame_url'] ?? '',

+ 19 - 15
app/Services/Anime/AnimeService.php

@@ -4029,11 +4029,6 @@ class AnimeService
         $processResult = $this->processActContentWithProducts($actContent, $products);
         $fullPrompt = $processResult['content'];
         $reference_images = array_merge($processResult['reference_images'], $reference_images);
-        dd($fullPrompt, $reference_images);
-        
-        // 去除多余的换行符(将连续多个换行符替换为单个换行符)
-        $fullPrompt = preg_replace('/\n{2,}/', "\n", $fullPrompt);
-        $fullPrompt = trim($fullPrompt);
         
         $videoDuration = getProp($act, 'act_duration');
         
@@ -4187,11 +4182,13 @@ class AnimeService
             }
 
             if ($reference_images) {
+                $reference_images = $this->aiImageGenerationService->processReferenceImagesToAssets($reference_images, $unifiedParams['prompt']);
                 $parameters['reference_images'] = $reference_images;
+                $parameters['video_mode'] = 'multi_image';
+                $parameters['mode'] = 'multi_image';
             }
             
             $unifiedParams['parameters'] = $parameters;
-            dd($unifiedParams);
             
             // 调用统一API创建任务
             $task = $this->aiVideoGenerationService->createUnifiedApiTask($unifiedParams);
@@ -6031,13 +6028,9 @@ class AnimeService
                     $url = $product_dict[$product_name]['url'];
                     $voice_prompt = $product_dict[$product_name]['voice_prompt'];
                     
-                    // 处理voice_prompt(旁白单独处理,放在最后)
-                    if (!empty($voice_prompt)) {
-                        if ($product_name === '旁白') {
-                            $narrator_voice_prompt = $product_name . ':' . $voice_prompt . "\n";
-                        } else {
-                            $voice_prompts[] = $product_name . ':' . $voice_prompt . "\n";
-                        }
+                    // 处理voice_prompt(旁白不在这里处理,旁白是公共的在后面单独处理)
+                    if (!empty($voice_prompt) && $product_name !== '旁白') {
+                        $voice_prompts[] = $product_name . ':' . $voice_prompt . "\n";
                     }
                     
                     // 只有URL非空才分配序号和添加到参考图片
@@ -6070,7 +6063,7 @@ class AnimeService
             
             if ($index > 0) {
                 // 有图片,替换为:产品名称(图X)
-                $replacement = $product_name . '(图' . $index . ')';
+                $replacement = $product_name . '<图片' . $index . '>';
             } else {
                 // 无图片,只保留产品名称
                 $replacement = $product_name;
@@ -6080,7 +6073,18 @@ class AnimeService
             $processedContent = preg_replace('/\{' . $escaped_name . '\}/u', $replacement, $processedContent);
         }
         
-        // 第三步:将voice_prompt信息添加到内容最前面
+        // 第三步:单独处理旁白的voice_prompt(旁白是公共的,不依赖product_dict)
+        foreach ($products as $product) {
+            $product_name = getProp($product, 'product_name');
+            $voice_prompt = getProp($product, 'voice_prompt');
+            
+            if ($product_name === '旁白' && !empty($voice_prompt)) {
+                $narrator_voice_prompt = $product_name . ':' . $voice_prompt . "\n";
+                break; // 找到旁白后退出循环
+            }
+        }
+        
+        // 第四步:将voice_prompt信息添加到内容最前面
         $voice_prompt_prefix = '';
         if (!empty($voice_prompts)) {
             $voice_prompt_prefix .= implode('', $voice_prompts);