瀏覽代碼

1.新增快快AI下的seedance2.0模型2.适配创建视频任务、查询视频人物结果和素材管理等接口

lh 1 月之前
父節點
當前提交
25996d9bc7

+ 110 - 1
app/Http/Controllers/AIGeneration/VideoGenerationController.php

@@ -9,6 +9,7 @@ use App\Transformer\AIGeneration\AIGenerationTransformer;
 use Illuminate\Http\Request;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Support\Facades\Validator;
+use Illuminate\Support\Facades\DB;
 use App\Libs\ApiResponse;
 use App\Models\AIElement;
 
@@ -454,6 +455,114 @@ class VideoGenerationController extends BaseController
         return $this->success(['task_id' => $task->id, 'status' => $task->status]);
     }
 
+    /**
+     * 上传素材到快快AI素材库(单个或批量)
+     * 参数:url 或 urls 二选一;type:image/video/audio(默认image);purpose:素材用途;wait:是否等待素材就绪(默认1)
+     *
+     * @param Request $request
+     * @return JsonResponse
+     */
+    public function kuaikuaiAssetUpload(Request $request): JsonResponse
+    {
+        $data = $request->all();
+        $url = getProp($data, 'url');
+        $urls = getProp($data, 'urls', []);
+        if (is_string($urls)) {
+            $urls = json_decode($urls, true) ?: [];
+        }
+        if (!is_array($urls)) {
+            Utils::throwError('20003:urls格式不正确');
+        }
+
+        $type = strtolower(getProp($data, 'type', 'image'));
+        if (!in_array($type, ['image', 'video', 'audio'], true)) {
+            Utils::throwError('1002:素材类型只能是image/video/audio');
+        }
+        $assetType = $type === 'video'
+            ? \App\Models\MpAsset::TYPE_VIDEO
+            : ($type === 'audio' ? \App\Models\MpAsset::TYPE_AUDIO : \App\Models\MpAsset::TYPE_IMAGE);
+        $purpose = getProp($data, 'purpose', '');
+        $wait = (int)getProp($data, 'wait', 1) === 1;
+
+        if (!empty($urls)) {
+            $result = $this->aiVideoGenerationService->createKuaikuaiAssetsBatch($urls, $assetType, $purpose, '素材批量上传');
+        } elseif (!empty($url)) {
+            $result = $this->aiVideoGenerationService->createKuaikuaiAsset($url, getProp($data, 'name', '素材上传'), $assetType, $purpose, $wait);
+        } else {
+            Utils::throwError('1002:url与urls必须二选一');
+        }
+
+        if ($result['code'] !== 0) {
+            Utils::throwError('20003:' . $result['msg']);
+        }
+        return $this->success($result['data']);
+    }
+
+    /**
+     * 查询快快AI素材是否可用于生成视频
+     * 参数:asset_id
+     *
+     * @param Request $request
+     * @return JsonResponse
+     */
+    public function kuaikuaiAssetStatus(Request $request): JsonResponse
+    {
+        $assetId = getProp($request->all(), 'asset_id');
+        if (empty($assetId)) {
+            Utils::throwError('1002:asset_id不能为空');
+        }
+        $result = $this->aiVideoGenerationService->queryKuaikuaiAssetUsable($assetId);
+        if ($result['code'] !== 0) {
+            Utils::throwError('20003:' . $result['msg']);
+        }
+        return $this->success($result['data']);
+    }
+
+    /**
+     * 删除快快AI素材(同时清理本地记录)
+     * 参数:asset_id
+     *
+     * @param Request $request
+     * @return JsonResponse
+     */
+    public function kuaikuaiAssetDelete(Request $request): JsonResponse
+    {
+        $assetId = getProp($request->all(), 'asset_id');
+        if (empty($assetId)) {
+            Utils::throwError('1002:asset_id不能为空');
+        }
+        $result = $this->aiVideoGenerationService->deleteKuaikuaiAsset($assetId);
+        if ($result['code'] !== 0) {
+            Utils::throwError('20003:' . $result['msg']);
+        }
+        return $this->success($result['data']);
+    }
+
+    /**
+     * 本地快快AI素材列表
+     * 参数:status、keyword、page、page_size
+     *
+     * @param Request $request
+     * @return JsonResponse
+     */
+    public function kuaikuaiAssetList(Request $request): JsonResponse
+    {
+        $data = $request->all();
+        $query = DB::table('mp_assets')->where('source_type', 'kuaikuai');
+        if (!empty($data['status'])) {
+            $query->where('status', $data['status']);
+        }
+        if (!empty($data['keyword'])) {
+            $query->where(function ($q) use ($data) {
+                $q->where('asset_id', 'like', '%' . $data['keyword'] . '%')
+                    ->orWhere('name', 'like', '%' . $data['keyword'] . '%')
+                    ->orWhere('url', 'like', '%' . $data['keyword'] . '%');
+            });
+        }
+        $list = $query->orderBy('id', 'desc')->paginate((int)getProp($data, 'page_size', 10));
+        return $this->paginate($list);
+    }
+
     public function seedanceCallback(Request $request) {
         $data = $request->all();
         $result = $this->aiVideoGenerationService->seedanceCallback($data);
@@ -1106,4 +1215,4 @@ class VideoGenerationController extends BaseController
         }
     }
 }
-    
+    

文件差異過大導致無法顯示
+ 1117 - 9
app/Services/AIGeneration/AIVideoGenerationService.php


+ 70 - 18
app/Services/Anime/AnimeService.php

@@ -4795,8 +4795,24 @@ class AnimeService
             $task = $this->aiVideoGenerationService->createUnifiedApiTask($unifiedParams);
         } else {
             // Seedance模型(默认)
+            // 快快AI Seedance 2.0/2.5:配音音频需先上传为素材,走聚合网关 /v1/video/generations
+            if ($this->isKuaikuaiSeedanceModel($model)) {
+                $videoParams['gateway_base_url'] = env('KUAIKUAI_AI_BASE_URL', 'https://ai-api.kkidc.com');
+
+                if ($canUseAudio && $audioUrl) {
+                    // 上传配音音频为素材(上传失败时同步清理提示词中的<音频N>标记,不阻断任务)
+                    $reference_audio_assets = $this->aiVideoGenerationService->processReferenceAudiosToKuaikuaiAssets([$audioUrl], $fullPrompt);
+                    $videoParams['reference_audio_assets'] = $reference_audio_assets;
+
+                    // 优化提示词,增加音频相关描述
+                    $audioPrompt = "全程使用音频1作为视频配音。" . $fullPrompt;
+                    $videoParams['prompt'] = $audioPrompt;
+                } elseif ($audioDuration) {    // 有音频时长则需将视频时长设置为比音频时长更长的整数
+                    $videoParams['video_duration'] = max(4, min(12, ceil($audioDuration)));
+                }
+                $task = $this->aiVideoGenerationService->createKuaikuaiVideoTask($videoParams);
+            } elseif ($canUseAudio && in_array($model, ['doubao-seedance-2-0-260128', 'doubao-seedance-2-0-fast-260128'])) {
             // 检查是否为 Seedance 2.0 模型,如果是则需要处理配音音频
-            if ($canUseAudio && in_array($model, ['doubao-seedance-2-0-260128', 'doubao-seedance-2-0-fast-260128'])) {
                 // 添加配音音频到content中
                 $videoParams['content'][] = [
                     'type' => 'audio_url',
@@ -4810,10 +4826,12 @@ class AnimeService
                 $audioPrompt = "全程使用音频1作为视频配音。" . $fullPrompt;
                 $videoParams['prompt'] = $audioPrompt;
                 $videoParams['content'][0]['text'] = $audioPrompt;
-            } else if ($audioDuration) {    // 有音频时长则需将视频时长设置为比音频时长更长的整数
-                $videoParams['video_duration'] = max(4, min(12, ceil($audioDuration)));   // seedance支持2-12s
+            } else {
+                if ($audioDuration) {    // 有音频时长则需将视频时长设置为比音频时长更长的整数
+                    $videoParams['video_duration'] = max(4, min(12, ceil($audioDuration)));   // seedance支持2-12s
+                }
+                $task = $this->aiVideoGenerationService->createSeedanceTask($videoParams);
             }
-            $task = $this->aiVideoGenerationService->createSeedanceTask($videoParams);
         }
         
         // 更新分镜表的视频任务信息
@@ -4832,6 +4850,18 @@ class AnimeService
         ];
     }
     
+    /**
+     * 是否为快快AI Seedance 2.0/2.5 系列模型
+     * 快快AI平台模型ID:seed-2-fast / seed-2-mini / seed-2.5 / seed-2
+     *
+     * @param string $model
+     * @return bool
+     */
+    private function isKuaikuaiSeedanceModel(string $model): bool
+    {
+        return in_array($model, ['seed-2-fast', 'seed-2-mini', 'seed-2.5', 'seed-2'], true);
+    }
+
     public function createActVideoTask($data) {
         $act_id = getProp($data, 'act_id');
         $first_frame_url = getProp($data, 'first_frame_url');
@@ -5112,8 +5142,9 @@ class AnimeService
             
             // 调用统一API创建任务
             $task = $this->aiVideoGenerationService->createUnifiedApiTask($unifiedParams);
-        } elseif (strpos($model, 'doubao-seedance-2.0') !== false) {
-            // 豆包Seedance 2.0(百度AI网关):图片需要先上传为素材
+        } elseif (strpos($model, 'doubao-seedance-2.0') !== false || $this->isKuaikuaiSeedanceModel($model)) {
+            // 豆包Seedance 2.0(百度AI网关)/ 快快AI Seedance 2.0/2.5:图片需要先上传为素材
+            $isKuaikuai = $this->isKuaikuaiSeedanceModel($model);
             // 保存原始参考图用于任务记录
             $videoParams['reference_images'] = $reference_images;
 
@@ -5121,8 +5152,10 @@ class AnimeService
                 // 限制只传入9张参考图
                 $reference_images = array_slice($reference_images, 0, 9);
 
-                // 参考图上传为百度网关素材,上传失败时同步清理提示词中的<图片N>标记
-                $reference_image_assets = $this->aiVideoGenerationService->processReferenceImagesToSeedance20Assets($reference_images, $fullPrompt);
+                // 参考图上传为素材(百度网关/快快AI),上传失败时同步清理提示词中的<图片N>标记
+                $reference_image_assets = $isKuaikuai
+                    ? $this->aiVideoGenerationService->processReferenceImagesToKuaikuaiAssets($reference_images, $fullPrompt)
+                    : $this->aiVideoGenerationService->processReferenceImagesToSeedance20Assets($reference_images, $fullPrompt);
 
                 // 素材上传失败后提示词可能被清理,同步更新视频参数中的提示词
                 $videoParams['prompt'] = trim($fullPrompt);
@@ -5130,7 +5163,13 @@ class AnimeService
                 $videoParams['reference_image_assets'] = $reference_image_assets;
             }
 
-            $task = $this->aiVideoGenerationService->createSeedance20Task($videoParams);
+            // 快快AI走聚合网关 /v1/video/generations(火山豆包格式)
+            if ($isKuaikuai) {
+                $videoParams['gateway_base_url'] = env('KUAIKUAI_AI_BASE_URL', 'https://ai-api.kkidc.com');
+                $task = $this->aiVideoGenerationService->createKuaikuaiVideoTask($videoParams);
+            } else {
+                $task = $this->aiVideoGenerationService->createSeedance20Task($videoParams);
+            }
         } else {
             $task = $this->aiVideoGenerationService->createSeedanceTask($videoParams);
         }
@@ -5315,8 +5354,9 @@ class AnimeService
 
             // 调用统一API创建任务
             $task = $this->aiVideoGenerationService->createUnifiedApiTask($unifiedParams);
-        } elseif (strpos($model, 'doubao-seedance-2.0') !== false) {
-            // 豆包Seedance 2.0(百度AI网关):图片/视频/音频需要先上传为素材
+        } elseif (strpos($model, 'doubao-seedance-2.0') !== false || $this->isKuaikuaiSeedanceModel($model)) {
+            // 豆包Seedance 2.0(百度AI网关)/ 快快AI Seedance 2.0/2.5:图片/视频/音频需要先上传为素材
+            $isKuaikuai = $this->isKuaikuaiSeedanceModel($model);
             $videoParams['reference_images'] = $reference_images;
 
             // 维护ref_image_url字段(参考图+参考视频+参考音频URL,JSON数组)
@@ -5326,20 +5366,26 @@ class AnimeService
             }
 
             if ($reference_images) {
-                // 参考图上传为百度网关素材
-                $reference_image_assets = $this->aiVideoGenerationService->processReferenceImagesToSeedance20Assets($reference_images, $videoParams['prompt']);
+                // 参考图上传为素材(百度网关/快快AI)
+                $reference_image_assets = $isKuaikuai
+                    ? $this->aiVideoGenerationService->processReferenceImagesToKuaikuaiAssets($reference_images, $videoParams['prompt'])
+                    : $this->aiVideoGenerationService->processReferenceImagesToSeedance20Assets($reference_images, $videoParams['prompt']);
                 $videoParams['reference_image_assets'] = $reference_image_assets;
             }
 
-            // 参考视频上传为百度网关素材
+            // 参考视频上传为素材(百度网关/快快AI)
             if ($reference_videos) {
-                $reference_video_assets = $this->aiVideoGenerationService->processReferenceVideosToSeedance20Assets($reference_videos, $videoParams['prompt']);
+                $reference_video_assets = $isKuaikuai
+                    ? $this->aiVideoGenerationService->processReferenceVideosToKuaikuaiAssets($reference_videos, $videoParams['prompt'])
+                    : $this->aiVideoGenerationService->processReferenceVideosToSeedance20Assets($reference_videos, $videoParams['prompt']);
                 $videoParams['reference_video_assets'] = $reference_video_assets;
             }
 
-            // 参考音频上传为百度网关素材
+            // 参考音频上传为素材(百度网关/快快AI)
             if ($reference_audios) {
-                $reference_audio_assets = $this->aiVideoGenerationService->processReferenceAudiosToSeedance20Assets($reference_audios, $videoParams['prompt']);
+                $reference_audio_assets = $isKuaikuai
+                    ? $this->aiVideoGenerationService->processReferenceAudiosToKuaikuaiAssets($reference_audios, $videoParams['prompt'])
+                    : $this->aiVideoGenerationService->processReferenceAudiosToSeedance20Assets($reference_audios, $videoParams['prompt']);
                 $videoParams['reference_audio_assets'] = $reference_audio_assets;
             }
 
@@ -5348,7 +5394,13 @@ class AnimeService
                 $videoParams['content'][0]['text'] = $videoParams['prompt'];
             }
 
-            $task = $this->aiVideoGenerationService->createSeedance20Task($videoParams);
+            // 快快AI走聚合网关 /v1/video/generations(火山豆包格式)
+            if ($isKuaikuai) {
+                $videoParams['gateway_base_url'] = env('KUAIKUAI_AI_BASE_URL', 'https://ai-api.kkidc.com');
+                $task = $this->aiVideoGenerationService->createKuaikuaiVideoTask($videoParams);
+            } else {
+                $task = $this->aiVideoGenerationService->createSeedance20Task($videoParams);
+            }
         } else {
             // 官方Seedance模型:兼容参考视频和参考音频(content直接透传公网URL)
 

+ 6 - 0
routes/api.php

@@ -155,6 +155,12 @@ Route::group(['middleware' => ['bindToken', 'bindExportToken', 'checkLogin']], f
             Route::post('video/createSeedanceTask', [VideoGenerationController::class, 'createSeedanceTask']);
             // Route::get('video/seedance/taskStatus/{taskId}', [VideoGenerationController::class, 'getSeedanceTaskStatus']);
 
+            // 快快AI真人素材库相关路由
+            Route::post('video/kuaikuaiAsset/upload', [VideoGenerationController::class, 'kuaikuaiAssetUpload']);    // 上传素材(单个或批量)
+            Route::get('video/kuaikuaiAsset/status', [VideoGenerationController::class, 'kuaikuaiAssetStatus']);     // 查询素材是否可用于生成视频
+            Route::get('video/kuaikuaiAsset/list', [VideoGenerationController::class, 'kuaikuaiAssetList']);         // 本地素材列表
+            Route::post('video/kuaikuaiAsset/delete', [VideoGenerationController::class, 'kuaikuaiAssetDelete']);    // 删除素材
+
             // 可灵Omni视频生成路由
             Route::post('video/createKelingTask', [VideoGenerationController::class, 'createKelingTask']);
             Route::get('video/keling/taskStatus/{taskId}', [VideoGenerationController::class, 'getKelingTaskStatus']);