|
|
@@ -3793,12 +3793,12 @@ class AnimeService
|
|
|
$model = getProp($episode, 'video_model');
|
|
|
if (!$model) {
|
|
|
// episode表也没有,使用默认值
|
|
|
- $model = 'doubao-seedance-1-0-pro-250528';
|
|
|
+ $model = 'doubao-seedance-1-5-pro-251215';
|
|
|
}
|
|
|
}
|
|
|
// 验证模型是否在可用模型表中
|
|
|
if (!DB::table('mp_video_models')->where('model', $model)->where('is_enabled', 1)->exists()) {
|
|
|
- $model = 'doubao-seedance-1-0-pro-250528';
|
|
|
+ $model = 'doubao-seedance-1-5-pro-251215';
|
|
|
}
|
|
|
// 保存到episode表
|
|
|
$episode_id = getProp($segment, 'episode_id');
|
|
|
@@ -3943,7 +3943,7 @@ class AnimeService
|
|
|
}
|
|
|
} else {
|
|
|
// 没有图片时,记录错误日志
|
|
|
- logDB('anime', 'error', '智针20模型缺少首帧图片', [
|
|
|
+ logDB('anime', 'error', '智帧20模型缺少首帧图片', [
|
|
|
'segment_id' => $segment_id,
|
|
|
'model' => $model
|
|
|
]);
|
|
|
@@ -3991,6 +3991,222 @@ class AnimeService
|
|
|
'video_duration' => $videoDuration
|
|
|
];
|
|
|
}
|
|
|
+
|
|
|
+ public function createActVideoTask($data) {
|
|
|
+ $act_id = getProp($data, 'act_id');
|
|
|
+ $first_frame_url = getProp($data, 'first_frame_url');
|
|
|
+ $tail_frame_url = getProp($data, 'tail_frame_url');
|
|
|
+ $generate_audio = getProp($data, 'generate_audio', 1);
|
|
|
+ $reference_images = getProp($data, 'reference_images', []);
|
|
|
+
|
|
|
+ if (!$act_id) {
|
|
|
+ Utils::throwError('1002:片段ID不能为空');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取片段信息
|
|
|
+ $act = DB::table('mp_episode_segments')->where('id', $act_id)->first();
|
|
|
+ if (!$act) {
|
|
|
+ Utils::throwError('20003:片段不存在');
|
|
|
+ }
|
|
|
+
|
|
|
+ $act = (array)$act;
|
|
|
+
|
|
|
+ $episode_id = getProp($act, 'episode_id');
|
|
|
+ $ratio = DB::table('mp_anime_episodes')->where('id', $episode_id)->value('ratio');
|
|
|
+ if (!$ratio) $ratio = '9:16';
|
|
|
+
|
|
|
+ // 获取分镜内容
|
|
|
+ $actContent = getProp($act, 'act_content', '');
|
|
|
+
|
|
|
+ // 音效同出(默认使用)
|
|
|
+ $current_generate_audio = $generate_audio ? 1 : 0;
|
|
|
+
|
|
|
+ // 构建完整的提示词
|
|
|
+ $fullPrompt = $actContent;
|
|
|
+
|
|
|
+ // 去除多余的换行符(将连续多个换行符替换为单个换行符)
|
|
|
+ $fullPrompt = preg_replace('/\n{2,}/', "\n", $fullPrompt);
|
|
|
+ $fullPrompt = trim($fullPrompt);
|
|
|
+
|
|
|
+ $videoDuration = getProp($act, 'act_duration');
|
|
|
+
|
|
|
+ // 处理视频模型
|
|
|
+ $model = getProp($data, 'model');
|
|
|
+ if (!$model) {
|
|
|
+ // 用户没有输入,从episode表获取
|
|
|
+ $episode = DB::table('mp_anime_episodes')->where('id', $episode_id)->first();
|
|
|
+ $model = getProp($episode, 'video_model');
|
|
|
+ if (!$model) {
|
|
|
+ // episode表也没有,使用默认值
|
|
|
+ $model = 'zhizhen-20';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 验证模型是否在可用模型表中
|
|
|
+ if (!DB::table('mp_video_models')->where('model', $model)->where('is_enabled', 1)->exists()) {
|
|
|
+ $model = 'zhizhen-20';
|
|
|
+ }
|
|
|
+ // 保存到episode表
|
|
|
+ DB::table('mp_anime_episodes')->where('id', $episode_id)->update([
|
|
|
+ 'video_model' => $model,
|
|
|
+ 'updated_at' => date('Y-m-d H:i:s')
|
|
|
+ ]);
|
|
|
+
|
|
|
+ // 构建视频生成参数
|
|
|
+ $videoParams = [
|
|
|
+ 'model' => $model,
|
|
|
+ 'alias_act_id' => $act_id,
|
|
|
+ 'prompt' => trim($fullPrompt),
|
|
|
+ 'video_duration' => $videoDuration,
|
|
|
+ 'video_resolution' => '720P',
|
|
|
+ 'seed' => -1,
|
|
|
+ 'ratio' => $ratio,
|
|
|
+ 'generate_audio' => (int)$current_generate_audio === 0 ? false : true,
|
|
|
+ 'draft' => false,
|
|
|
+ 'watermark' => false,
|
|
|
+ 'camera_fixed' => false,
|
|
|
+ // 'callback_url' => 'http://mpaudio.yqsd.cn/api/video/seedanceCallback'
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 如果分镜有图片,作为首帧
|
|
|
+ $hasImage = !empty(getProp($act, 'img_url')) || $first_frame_url;
|
|
|
+ $hasVideo = !empty(getProp($act, 'video_url'));
|
|
|
+
|
|
|
+ if ($hasImage) {
|
|
|
+ if ($first_frame_url) {
|
|
|
+ $videoParams['first_frame_url'] = $first_frame_url;
|
|
|
+ }else {
|
|
|
+ $videoParams['first_frame_url'] = getProp($act, 'img_url');
|
|
|
+ }
|
|
|
+ if ($tail_frame_url) $videoParams['tail_frame_url'] = $tail_frame_url;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建content数组
|
|
|
+ $videoParams['content'] = [
|
|
|
+ [
|
|
|
+ 'type' => 'text',
|
|
|
+ 'text' => $videoParams['prompt'],
|
|
|
+ ]
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 如果有首帧图片,添加到content中
|
|
|
+ if ($hasImage) {
|
|
|
+ if (in_array($model, ['doubao-seedance-2-0-260128', 'doubao-seedance-2-0-fast-260128'])) {
|
|
|
+ $videoParams['content'][] = [
|
|
|
+ 'type' => 'image_url',
|
|
|
+ 'image_url' => [
|
|
|
+ 'url' => $videoParams['first_frame_url'],
|
|
|
+ ],
|
|
|
+ 'role' => 'reference_image',
|
|
|
+ ];
|
|
|
+ if (isset($videoParams['tail_frame_url'])) {
|
|
|
+ $videoParams['content'][] = [
|
|
|
+ 'type' => 'image_url',
|
|
|
+ 'image_url' => [
|
|
|
+ 'url' => $videoParams['tail_frame_url'],
|
|
|
+ ],
|
|
|
+ 'role' => 'reference_image',
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ }else {
|
|
|
+ $videoParams['content'][] = [
|
|
|
+ 'type' => 'image_url',
|
|
|
+ 'image_url' => [
|
|
|
+ 'url' => $videoParams['first_frame_url'],
|
|
|
+ ],
|
|
|
+ 'role' => 'first_frame',
|
|
|
+ ];
|
|
|
+
|
|
|
+ if (isset($videoParams['tail_frame_url'])) {
|
|
|
+ $videoParams['content'][] = [
|
|
|
+ 'type' => 'image_url',
|
|
|
+ 'image_url' => [
|
|
|
+ 'url' => $videoParams['tail_frame_url'],
|
|
|
+ ],
|
|
|
+ 'role' => 'last_frame',
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 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'];
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 没有图片时,记录错误日志
|
|
|
+ logDB('anime', 'error', '智帧20模型缺少首帧图片', [
|
|
|
+ 'act_id' => $act_id,
|
|
|
+ 'model' => $model
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($reference_images) {
|
|
|
+ $parameters['reference_images'] = $reference_images;
|
|
|
+ }
|
|
|
+
|
|
|
+ $unifiedParams['parameters'] = $parameters;
|
|
|
+ dd($unifiedParams);
|
|
|
+
|
|
|
+ // 调用统一API创建任务
|
|
|
+ $task = $this->aiVideoGenerationService->createUnifiedApiTask($unifiedParams);
|
|
|
+ } else {
|
|
|
+ $task = $this->aiVideoGenerationService->createSeedanceTask($videoParams);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新分镜表的视频任务信息
|
|
|
+ DB::table('mp_episode_segments')->where('id', $act_id)->update([
|
|
|
+ 'video_task_id' => $task->id,
|
|
|
+ 'video_task_status' => '生成中',
|
|
|
+ 'generate_audio' => $generate_audio,
|
|
|
+ 'updated_at' => date('Y-m-d H:i:s')
|
|
|
+ ]);
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'task_id' => $task->id,
|
|
|
+ 'status' => $task->status,
|
|
|
+ 'act_id' => $act_id,
|
|
|
+ 'video_duration' => $videoDuration
|
|
|
+ ];
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
|
* 根据提示词内容智能计算最优视频时长
|