|
|
@@ -4,6 +4,7 @@ namespace App\Services\Anime;
|
|
|
|
|
|
use App\Consts\BaseConst;
|
|
|
use App\Consts\ErrorConst;
|
|
|
+use App\Exceptions\ApiException;
|
|
|
use App\Facade\Site;
|
|
|
use App\Libs\Utils;
|
|
|
use App\Models\MpGeneratePicTask;
|
|
|
@@ -5017,6 +5018,8 @@ class AnimeService
|
|
|
// 音效同出(默认使用)
|
|
|
$current_generate_audio = $generate_audio ? 1 : 0;
|
|
|
|
|
|
+ $actContent = '{程悠悠-校服黄发叛逆造型}赤裸着身体在浴室中洗澡,手轻轻抚摸着自己丰腴的乳房,口中发出呻吟声。';
|
|
|
+
|
|
|
// 构建完整的提示词
|
|
|
$processResult = $this->processActContentWithProducts($actContent, $products);
|
|
|
$fullPrompt = $processResult['content'];
|
|
|
@@ -5141,106 +5144,117 @@ class AnimeService
|
|
|
// dd($videoParams);
|
|
|
|
|
|
// 根据模型选择不同的视频生成方法
|
|
|
- if (strpos($model, 'jimeng') !== false) {
|
|
|
- // 即梦模型参数调整
|
|
|
- unset($videoParams['content']); // 即梦不使用content格式
|
|
|
- $task = $this->aiVideoGenerationService->createJimengTask($videoParams);
|
|
|
- } elseif (strpos($model, 'kling') !== false) {
|
|
|
- // Keling模型参数调整
|
|
|
- $videoParams['aspect_ratio'] = $videoParams['ratio']; // Keling使用aspect_ratio
|
|
|
- unset($videoParams['ratio']);
|
|
|
- unset($videoParams['content']); // Keling不使用content格式
|
|
|
- $task = $this->aiVideoGenerationService->createKelingOmniTask($videoParams);
|
|
|
- } elseif (strpos($model, 'zhizhen') !== false) {
|
|
|
- // 智帧20等新模型使用统一API
|
|
|
- // 构建统一API参数
|
|
|
- $unifiedParams = [
|
|
|
- 'model_code' => $model,
|
|
|
- 'alias_act_id' => $act_id,
|
|
|
- 'prompt' => trim($fullPrompt),
|
|
|
- 'video_duration' => $videoDuration,
|
|
|
- 'video_resolution' => strtolower($videoParams['video_resolution']),
|
|
|
- 'video_ratio' => $ratio,
|
|
|
- 'seed' => -1,
|
|
|
- ];
|
|
|
-
|
|
|
- // 构建parameters参数
|
|
|
- $parameters = [
|
|
|
- 'seconds' => $unifiedParams['video_duration'],
|
|
|
- 'resolution' => $unifiedParams['video_resolution'],
|
|
|
- 'ratio' => $ratio,
|
|
|
- // 'video_mode' => 'multi_image',
|
|
|
- // 'mode' => 'multi_image',
|
|
|
- 'generate_audio' => (int)$current_generate_audio === 1 ? true : false,
|
|
|
- ];
|
|
|
-
|
|
|
- $hasImage = false;
|
|
|
- // 处理首帧和尾帧(智帧20模型必须要有首帧图片)
|
|
|
- if ($hasImage) {
|
|
|
- $parameters['first_frame_url'] = $videoParams['first_frame_url'];
|
|
|
- if (isset($videoParams['tail_frame_url'])) {
|
|
|
- $parameters['last_frame_url'] = $videoParams['tail_frame_url'];
|
|
|
+ try {
|
|
|
+ if (strpos($model, 'jimeng') !== false) {
|
|
|
+ // 即梦模型参数调整
|
|
|
+ unset($videoParams['content']); // 即梦不使用content格式
|
|
|
+ $task = $this->aiVideoGenerationService->createJimengTask($videoParams);
|
|
|
+ } elseif (strpos($model, 'kling') !== false) {
|
|
|
+ // Keling模型参数调整
|
|
|
+ $videoParams['aspect_ratio'] = $videoParams['ratio']; // Keling使用aspect_ratio
|
|
|
+ unset($videoParams['ratio']);
|
|
|
+ unset($videoParams['content']); // Keling不使用content格式
|
|
|
+ $task = $this->aiVideoGenerationService->createKelingOmniTask($videoParams);
|
|
|
+ } elseif (strpos($model, 'zhizhen') !== false) {
|
|
|
+ // 智帧20等新模型使用统一API
|
|
|
+ // 构建统一API参数
|
|
|
+ $unifiedParams = [
|
|
|
+ 'model_code' => $model,
|
|
|
+ 'alias_act_id' => $act_id,
|
|
|
+ 'prompt' => trim($fullPrompt),
|
|
|
+ 'video_duration' => $videoDuration,
|
|
|
+ 'video_resolution' => strtolower($videoParams['video_resolution']),
|
|
|
+ 'video_ratio' => $ratio,
|
|
|
+ 'seed' => -1,
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 构建parameters参数
|
|
|
+ $parameters = [
|
|
|
+ 'seconds' => $unifiedParams['video_duration'],
|
|
|
+ 'resolution' => $unifiedParams['video_resolution'],
|
|
|
+ 'ratio' => $ratio,
|
|
|
+ // 'video_mode' => 'multi_image',
|
|
|
+ // 'mode' => 'multi_image',
|
|
|
+ 'generate_audio' => (int)$current_generate_audio === 1 ? true : false,
|
|
|
+ ];
|
|
|
+
|
|
|
+ $hasImage = false;
|
|
|
+ // 处理首帧和尾帧(智帧20模型必须要有首帧图片)
|
|
|
+ if ($hasImage) {
|
|
|
+ $parameters['first_frame_url'] = $videoParams['first_frame_url'];
|
|
|
+ if (isset($videoParams['tail_frame_url'])) {
|
|
|
+ $parameters['last_frame_url'] = $videoParams['tail_frame_url'];
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 没有图片时,记录错误日志
|
|
|
+ logDB('anime', 'error', '智帧20模型缺少首帧图片', [
|
|
|
+ 'act_id' => $act_id,
|
|
|
+ 'model' => $model
|
|
|
+ ]);
|
|
|
}
|
|
|
- } else {
|
|
|
- // 没有图片时,记录错误日志
|
|
|
- logDB('anime', 'error', '智帧20模型缺少首帧图片', [
|
|
|
- 'act_id' => $act_id,
|
|
|
- 'model' => $model
|
|
|
- ]);
|
|
|
- }
|
|
|
|
|
|
- if ($reference_images) {
|
|
|
- // 限制只传入9张参考图
|
|
|
- $reference_images = array_slice($reference_images, 0, 9);
|
|
|
+ if ($reference_images) {
|
|
|
+ // 限制只传入9张参考图
|
|
|
+ $reference_images = array_slice($reference_images, 0, 9);
|
|
|
|
|
|
- // 在处理之前先保存原始reference_images到任务参数中
|
|
|
- $unifiedParams['ref_image_url'] = json_encode($reference_images, 256);
|
|
|
- $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;
|
|
|
-
|
|
|
- // 调用统一API创建任务
|
|
|
- $task = $this->aiVideoGenerationService->createUnifiedApiTask($unifiedParams);
|
|
|
- } elseif ($this->isSeedance20SeriesModel($model)) {
|
|
|
- // 豆包Seedance 2.0(百度AI网关)/ 快快AI Seedance 2.0/2.5:图片需要先上传为素材
|
|
|
- $isKuaikuai = $this->isKuaikuaiSeedanceModel($model);
|
|
|
- $isDreamina = $this->isOverseasBaiduSeedanceModel($model);
|
|
|
- // 保存原始参考图用于任务记录
|
|
|
- $videoParams['reference_images'] = $reference_images;
|
|
|
-
|
|
|
- if ($reference_images) {
|
|
|
- // 限制只传入9张参考图
|
|
|
- $reference_images = array_slice($reference_images, 0, 9);
|
|
|
-
|
|
|
- // 维护ref_image_url字段(参考图URL,JSON数组,与智帧分支保持一致)
|
|
|
- $videoParams['ref_image_url'] = json_encode($reference_images, 256);
|
|
|
-
|
|
|
- // 参考图上传为素材(国内百度/海外百度dreamina/快快AI),上传失败时同步清理提示词中的<图片N>标记
|
|
|
- $reference_image_assets = $isKuaikuai
|
|
|
- ? $this->aiVideoGenerationService->processReferenceImagesToKuaikuaiAssets($reference_images, $fullPrompt)
|
|
|
- : ($isDreamina
|
|
|
- ? $this->aiVideoGenerationService->processReferenceImagesToBaiduDreaminaAssets($reference_images, $fullPrompt)
|
|
|
- : $this->aiVideoGenerationService->processReferenceImagesToSeedance20Assets($reference_images, $fullPrompt));
|
|
|
-
|
|
|
- // 素材上传失败后提示词可能被清理,同步更新视频参数中的提示词
|
|
|
- $videoParams['prompt'] = trim($fullPrompt);
|
|
|
- $videoParams['content'][0]['text'] = trim($fullPrompt);
|
|
|
- $videoParams['reference_image_assets'] = $reference_image_assets;
|
|
|
- }
|
|
|
-
|
|
|
- // 快快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);
|
|
|
+ // 在处理之前先保存原始reference_images到任务参数中
|
|
|
+ $unifiedParams['ref_image_url'] = json_encode($reference_images, 256);
|
|
|
+ $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;
|
|
|
+
|
|
|
+ // 调用统一API创建任务
|
|
|
+ $task = $this->aiVideoGenerationService->createUnifiedApiTask($unifiedParams);
|
|
|
+ } elseif ($this->isSeedance20SeriesModel($model)) {
|
|
|
+ // 豆包Seedance 2.0(百度AI网关)/ 快快AI Seedance 2.0/2.5:图片需要先上传为素材
|
|
|
+ $isKuaikuai = $this->isKuaikuaiSeedanceModel($model);
|
|
|
+ $isDreamina = $this->isOverseasBaiduSeedanceModel($model);
|
|
|
+ // 保存原始参考图用于任务记录
|
|
|
+ $videoParams['reference_images'] = $reference_images;
|
|
|
+
|
|
|
+ if ($reference_images) {
|
|
|
+ // 限制只传入9张参考图
|
|
|
+ $reference_images = array_slice($reference_images, 0, 9);
|
|
|
+
|
|
|
+ // 维护ref_image_url字段(参考图URL,JSON数组,与智帧分支保持一致)
|
|
|
+ $videoParams['ref_image_url'] = json_encode($reference_images, 256);
|
|
|
+
|
|
|
+ // 参考图上传为素材(国内百度/海外百度dreamina/快快AI),上传失败时同步清理提示词中的<图片N>标记
|
|
|
+ $reference_image_assets = $isKuaikuai
|
|
|
+ ? $this->aiVideoGenerationService->processReferenceImagesToKuaikuaiAssets($reference_images, $fullPrompt)
|
|
|
+ : ($isDreamina
|
|
|
+ ? $this->aiVideoGenerationService->processReferenceImagesToBaiduDreaminaAssets($reference_images, $fullPrompt)
|
|
|
+ : $this->aiVideoGenerationService->processReferenceImagesToSeedance20Assets($reference_images, $fullPrompt));
|
|
|
+
|
|
|
+ // 素材上传失败后提示词可能被清理,同步更新视频参数中的提示词
|
|
|
+ $videoParams['prompt'] = trim($fullPrompt);
|
|
|
+ $videoParams['content'][0]['text'] = trim($fullPrompt);
|
|
|
+ $videoParams['reference_image_assets'] = $reference_image_assets;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 快快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->createSeedance20Task($videoParams);
|
|
|
+ $task = $this->aiVideoGenerationService->createSeedanceTask($videoParams);
|
|
|
}
|
|
|
- } else {
|
|
|
- $task = $this->aiVideoGenerationService->createSeedanceTask($videoParams);
|
|
|
+ } catch (ApiException $e) {
|
|
|
+ // 上游调用抛出的网关异常统一做友好化处理;本地业务错误(非1001)保持原提示不变
|
|
|
+ if ($e->getCode() === 1001) {
|
|
|
+ Utils::throwError('1001:' . mapErrorMessage($e->getMessage()));
|
|
|
+ }
|
|
|
+ throw $e;
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ // 其他未包装的异常,同样只对报错信息做格式化,便于前端展示友好提示
|
|
|
+ Utils::throwError('20003:' . mapErrorMessage($e->getMessage()));
|
|
|
}
|
|
|
|
|
|
// 更新分镜表的视频任务信息
|
|
|
@@ -5258,12 +5272,19 @@ class AnimeService
|
|
|
Redis::sadd('anime_act_first_frame_urls', $act_id);
|
|
|
}
|
|
|
|
|
|
- return [
|
|
|
+ $result = [
|
|
|
'task_id' => $task->id,
|
|
|
'status' => $task->status,
|
|
|
'act_id' => $act_id,
|
|
|
'video_duration' => $videoDuration
|
|
|
];
|
|
|
+
|
|
|
+ // 仅当任务提交阶段就已失败时,附带格式化后的错误信息;正常返回结构不受影响
|
|
|
+ if ($task->status === 'failed' && !empty($task->error_message)) {
|
|
|
+ $result['error_message'] = mapErrorMessage($task->error_message);
|
|
|
+ }
|
|
|
+
|
|
|
+ return $result;
|
|
|
}
|
|
|
|
|
|
/**
|