|
|
@@ -1190,7 +1190,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
// 设置最大输出tokens
|
|
|
$post_data['max_tokens'] = 300000;
|
|
|
|
|
|
- $client = new Client(['timeout' => 1200, 'verify' => false]);
|
|
|
+ $client = new Client(['timeout' => 1800, 'verify' => false]);
|
|
|
$response = $client->post($this->url, [
|
|
|
'json' => $post_data,
|
|
|
'headers' => $this->headers,
|
|
|
@@ -1306,7 +1306,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
Utils::throwError('20003:GPT API Key未配置');
|
|
|
}
|
|
|
|
|
|
- $client = new Client(['timeout' => 1200, 'verify' => false]);
|
|
|
+ $client = new Client(['timeout' => 1800, 'verify' => false]);
|
|
|
$headers = [
|
|
|
'Authorization' => 'Bearer ' . $apiKey,
|
|
|
'Content-Type' => 'application/json'
|
|
|
@@ -1587,7 +1587,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
|
|
|
if ($boolen && !$continue) {
|
|
|
$boolen = false;
|
|
|
- $client = new Client(['timeout' => 300, 'verify' => false]);
|
|
|
+ $client = new Client(['timeout' => 1800, 'verify' => false]);
|
|
|
// 根据ID通过API通知合成音频
|
|
|
// $result = $client->get("http://47.240.171.155:5000/api/previewTask?taskId={$id}");
|
|
|
$result = $client->get("http://122.9.129.83:5000/api/previewTask?taskId={$id}");
|
|
|
@@ -1659,7 +1659,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
|
|
|
DB::commit();
|
|
|
|
|
|
- $client = new Client(['timeout' => 300, 'verify' => false]);
|
|
|
+ $client = new Client(['timeout' => 1800, 'verify' => false]);
|
|
|
// 根据ID通过API通知合成音频
|
|
|
// $result = $client->get("http://47.240.171.155:5000/api/postTask?taskId={$id}");
|
|
|
$result = $client->get("http://122.9.129.83:5000/api/postTask?taskId={$id}");
|
|
|
@@ -1733,7 +1733,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
|
|
|
DB::commit();
|
|
|
|
|
|
- $client = new Client(['timeout' => 300, 'verify' => false]);
|
|
|
+ $client = new Client(['timeout' => 1800, 'verify' => false]);
|
|
|
// 根据ID通过API通知合成音频
|
|
|
// $result = $client->get("http://47.240.171.155:5000/api/postTask?taskId={$id}");
|
|
|
$result = $client->get("http://122.9.129.83:5000/api/postTask?taskId={$id}");
|
|
|
@@ -1964,7 +1964,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
DB::commit();
|
|
|
|
|
|
// 通知火山生成音频
|
|
|
- $client = new Client(['timeout' => 300, 'verify' => false]);
|
|
|
+ $client = new Client(['timeout' => 1800, 'verify' => false]);
|
|
|
// 根据ID通过API通知合成音频
|
|
|
// $result = $client->get("http://47.240.171.155:5000/api/chapterTask?taskId={$id}");
|
|
|
$result = $client->get("http://122.9.129.83:5000/api/chapterTask?taskId={$id}");
|
|
|
@@ -8559,7 +8559,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
}
|
|
|
|
|
|
// 构建完整的AI提示词
|
|
|
- $system_prompt = "你是一个专业的剧本编剧和影视导演,擅长根据已有的分段剧本进行优化和调整。你需要严格按照用户的要求重新生成分段剧本内容,保持原有的格式和结构。";
|
|
|
+ // $system_prompt = "你是一个专业的剧本编剧和影视导演,擅长根据已有的分段剧本进行优化和调整。你需要严格按照用户的要求重新生成分段剧本内容,保持原有的格式和结构。";
|
|
|
|
|
|
$user_prompt = "请根据以下要求重新生成分段剧本:\n\n【用户要求】\n{$final_prompt}";
|
|
|
$user_prompt .= "\n\n【原分段剧本内容】\n{$reference_content}";
|
|
|
@@ -8568,39 +8568,300 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
$user_prompt .= $roles_ref;
|
|
|
$user_prompt .= $scenes_ref;
|
|
|
|
|
|
- $user_prompt .= "\n\n【格式要求】\n";
|
|
|
- $user_prompt .= "1. 必须保持原有的分段格式(##片段X格式)\n";
|
|
|
- $user_prompt .= "2. 每个片段包含:时长、多个分镜\n";
|
|
|
- $user_prompt .= "3. 每个分镜包含:分镜序号、场景、画面、运镜、配音台词、背景音效\n";
|
|
|
- $user_prompt .= "4. 场景必须从【场景列表参考】中选择(如果提供了场景列表)\n";
|
|
|
- $user_prompt .= "5. 出场角色必须从【主体列表参考】中选择(如果提供了主体列表)\n";
|
|
|
- $user_prompt .= "6. 保持美术风格的一致性(如果提供了美术风格参考)\n";
|
|
|
- $user_prompt .= "7. 只需输出分段剧本部分,不要输出其他内容(不要包含###分段剧本标题)\n";
|
|
|
+ $system_prompt = "\n\n【强制要求】\n";
|
|
|
+ $system_prompt .= "- 请仅生成<分段剧本>部分,格式强制设置如下,不要生成其他任何内容:";
|
|
|
+ $system_prompt .= "###分段剧本
|
|
|
+ 片段数量:8
|
|
|
+ 旁白音色:成年男性(约35-50岁),音色厚重且富有磁性,带有老电影叙事般的沧桑感,语调低沉平稳,富有江湖气息。
|
|
|
+
|
|
|
+ ##片段1
|
|
|
+ 时长:12s
|
|
|
+ 分镜1
|
|
|
+ 场景:边境小镇街道
|
|
|
+ 画面:[大远景,冷色调,高对比度] 漫天黄沙在狂风中如怒龙翻滚,遮蔽了微弱的星光。悦来酒馆残破的木门在风中剧烈摇晃,铁链撞击木板发出沉重的闷响,四周建筑尽显荒凉。
|
|
|
+ 运镜:大远景固定镜头,随后风沙席卷向镜头形成遮挡转场。
|
|
|
+ 配音台词:
|
|
|
+ 背景音效:
|
|
|
+
|
|
|
+ 分镜2
|
|
|
+ 场景:悦来酒馆
|
|
|
+ 画面:[全景,暖色调阴影构图] 酒馆内部昏暗,几盏油灯在风中疯狂摇曳,投射出扭曲的长影。角落里的食客低头缩颈,双手紧紧攥着粗瓷碗,空气中漂浮着细小的尘埃。
|
|
|
+ 运镜:缓慢横移,掠过阴影中瑟缩的食客。
|
|
|
+ 配音台词:
|
|
|
+ 背景音效:
|
|
|
+
|
|
|
+ 分镜3
|
|
|
+ 场景:悦来酒馆
|
|
|
+ 画面:[中景,三分法构图] 三名大汉围坐在正中央的圆桌旁,腰间挂着的“武林盟”金色令牌在灯火下闪烁。桌上残羹冷炙横陈,一名大汉正张狂地大笑,脚踩在长凳上。
|
|
|
+ 运镜:从中景推向令牌特写。
|
|
|
+ 配音台词:
|
|
|
+ 背景音效:
|
|
|
+
|
|
|
+ 分镜4
|
|
|
+ 场景:悦来酒馆
|
|
|
+ 画面:[特写,低角度仰拍] 刀疤脸横肉颤动,嘴角挂着轻蔑的笑。他随手倾覆手中的酒碗,琥珀色的液体在空中划过弧线,重重溅在老李打着补丁的布鞋和裤脚上。
|
|
|
+ 运镜:极速推向酒水溅起的瞬间。
|
|
|
+ 配音台词:
|
|
|
+ 背景音效:
|
|
|
+
|
|
|
+ 分镜5
|
|
|
+ 镜头描述
|
|
|
+ 场景:悦来酒馆
|
|
|
+ 画面:[近景,荷兰角倾斜构图] 刀疤脸斜睨着眼,昏暗的灯光勾勒出他脸上狰狞的伤疤。他身体前倾,阴影完全笼罩了对面的老李,眼神如毒蛇般阴冷。
|
|
|
+ 运镜:固定机位,利用倾斜构图制造压迫感。
|
|
|
+ 配音台词:中文配音:[刀疤脸]老李头,这酒里掺水了吧?
|
|
|
+ 背景音效:";
|
|
|
+ $system_prompt .= "- 每个分镜包含:分镜序号、场景、画面、运镜、配音台词、背景音效\n";
|
|
|
+ $system_prompt .= "- 场景必须从【场景列表参考】中选择(如果提供了场景列表)\n";
|
|
|
+ $system_prompt .= "- 出场角色必须从【主体列表参考】中选择(如果提供了主体列表)\n";
|
|
|
+ $system_prompt .= "- 保持美术风格的一致性(如果提供了美术风格参考)\n";
|
|
|
+
|
|
|
+ // 获取模型配置
|
|
|
+ $anime = DB::table('mp_animes')->where('id', $target_anime_id)->first();
|
|
|
+ $model = $anime ? ($anime->model ?? 'doubao-seed-2-0-mini-260215') : 'doubao-seed-2-0-mini-260215';
|
|
|
+
|
|
|
+ // 验证模型是否在可用模型表中
|
|
|
+ if (!DB::table('mp_text_models')->where('model', $model)->where('is_enabled', 1)->exists()) {
|
|
|
+ $model = 'doubao-seed-2-0-mini-260215';
|
|
|
+ }
|
|
|
+
|
|
|
+ // 模型兼容性处理
|
|
|
+ $thinkingMode = 'disabled';
|
|
|
+ $reasoningEffort = 'high';
|
|
|
+
|
|
|
+ if ($model === 'deepseek-chat') {
|
|
|
+ $model = 'deepseek-v4-flash';
|
|
|
+ $thinkingMode = 'disabled';
|
|
|
+ } elseif ($model === 'deepseek-reasoner') {
|
|
|
+ $model = 'deepseek-v4-flash';
|
|
|
+ $thinkingMode = 'enabled';
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建消息
|
|
|
+ $messages = [
|
|
|
+ [
|
|
|
+ 'role' => 'system',
|
|
|
+ 'content' => $system_prompt
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ 'role' => 'user',
|
|
|
+ 'content' => $user_prompt
|
|
|
+ ]
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 构建请求参数
|
|
|
+ $post_data = [
|
|
|
+ 'model' => $model,
|
|
|
+ 'messages' => $messages,
|
|
|
+ 'temperature' => 0.7,
|
|
|
+ 'frequency_penalty' => 0,
|
|
|
+ 'presence_penalty' => 0,
|
|
|
+ 'response_format' => ['type' => 'text'],
|
|
|
+ 'thinking' => ['type' => $thinkingMode],
|
|
|
+ 'stream' => false // 非流式输出
|
|
|
+ ];
|
|
|
+ if ($thinkingMode == 'enabled') $post_data['reasoning_effort'] = $reasoningEffort;
|
|
|
|
|
|
// 调用AI生成新的分段剧本
|
|
|
try {
|
|
|
- $ai_result = $this->newGenerateText([
|
|
|
- 'model' => 'deepseek-v4-pro',
|
|
|
- 'system_prompt' => $system_prompt,
|
|
|
- 'prompt' => $user_prompt,
|
|
|
- 'temperature' => 0.7,
|
|
|
- ]);
|
|
|
+ $fullContent = '';
|
|
|
+ $usage = [];
|
|
|
+
|
|
|
+ // 根据模型类型选择调用方法
|
|
|
+ if (in_array($model, ['deepseek-reasoner', 'deepseek-chat', 'deepseek-v4-flash', 'deepseek-v4-pro'])) {
|
|
|
+ // DeepSeek 官方模型使用 DeepSeek API
|
|
|
+ $chatResult = $this->chatOnly($post_data);
|
|
|
+ } else if (in_array($model, $this->gpt_text_models)) {
|
|
|
+ // GPT 模型使用 TokenRouter API
|
|
|
+ $chatResult = $this->gpt54ChatOnly($post_data);
|
|
|
+ } else {
|
|
|
+ // 其他模型使用火山引擎API
|
|
|
+ $chatResult = $this->volcEngineChatCompletion($post_data);
|
|
|
+ }
|
|
|
|
|
|
- $generated_content = $ai_result['content'] ?? '';
|
|
|
+ if (is_array($chatResult)) {
|
|
|
+ $fullContent = $chatResult['fullContent'] ?? '';
|
|
|
+ $usage = $chatResult['usage'] ?? [];
|
|
|
+ }
|
|
|
+
|
|
|
+ $generated_content = $fullContent;
|
|
|
|
|
|
if (empty($generated_content)) {
|
|
|
Utils::throwError('20003:AI生成内容为空,请重试');
|
|
|
}
|
|
|
|
|
|
- // 解析生成的内容
|
|
|
- $parsed_segments = $this->parseSegmentContent($generated_content);
|
|
|
+ // 解析生成的内容 - 按片段分割
|
|
|
+ $parsed_segments = [];
|
|
|
+
|
|
|
+ // 先在开头添加换行符,确保第1片段也能被正确分割
|
|
|
+ $normalizedText = "\n" . $generated_content;
|
|
|
+ $parts = preg_split('/\n\s*##/', $normalizedText);
|
|
|
+
|
|
|
+ foreach ($parts as $part) {
|
|
|
+ $part = trim($part);
|
|
|
+ if (empty($part)) continue;
|
|
|
+
|
|
|
+ // 匹配"片段X"格式
|
|
|
+ if (!preg_match('/^片段\d+/', $part)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 分离标题和内容
|
|
|
+ $lines = explode("\n", $part, 2);
|
|
|
+ $actTitle = trim($lines[0]);
|
|
|
+ $actContent = isset($lines[1]) ? trim($lines[1]) : '';
|
|
|
+
|
|
|
+ // 解析标题,提取序号
|
|
|
+ $actNumber = 0;
|
|
|
+ if (preg_match('/^片段(\d+)\s*$/u', $actTitle, $segmentTitleMatch)) {
|
|
|
+ $actNumber = intval($segmentTitleMatch[1]);
|
|
|
+ } else {
|
|
|
+ $actNumber = count($parsed_segments) + 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 提取时长(仅保留数字)
|
|
|
+ $actDuration = 0;
|
|
|
+ if (preg_match('/时长[::]\s*([^\n]+)/u', $actContent, $actDurationMatch)) {
|
|
|
+ $actDurationStr = trim($actDurationMatch[1]);
|
|
|
+ // 提取数字部分(支持整数和小数)
|
|
|
+ if (preg_match('/([-+]?[0-9]*\.?[0-9]+)/', $actDurationStr, $numMatch)) {
|
|
|
+ $actDuration = (float)$numMatch[1];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 清理act_content:去除时长、旁白音色等非分镜内容行
|
|
|
+ // 只保留分镜内容,并确保分镜标记使用【】格式
|
|
|
+ $cleaned_content = '';
|
|
|
+ $content_lines = explode("\n", $actContent);
|
|
|
+ $is_capturing = false; // 标记是否开始捕获分镜内容
|
|
|
+
|
|
|
+ foreach ($content_lines as $line) {
|
|
|
+ $trimmed_line = trim($line);
|
|
|
+
|
|
|
+ // 跳过时长和旁白音色行
|
|
|
+ if (preg_match('/^(时长|旁白音色)[::]/u', $trimmed_line)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检测是否是分镜开始
|
|
|
+ // 支持格式:【分镜X】、【镜头X】、分镜X、镜头X
|
|
|
+ if (preg_match('/^(?:【)?(分镜|镜头)(\d+)(?:】)?/u', $trimmed_line, $shot_match)) {
|
|
|
+ $is_capturing = true;
|
|
|
+
|
|
|
+ // 如果没有【】,则添加
|
|
|
+ if (!preg_match('/^【/u', $trimmed_line)) {
|
|
|
+ $trimmed_line = preg_replace('/^(分镜|镜头)(\d+)/u', '【$1$2】', $trimmed_line);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果已经开始捕获,则添加该行
|
|
|
+ if ($is_capturing && !empty($trimmed_line)) {
|
|
|
+ $cleaned_content .= $trimmed_line;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $parsed_segments[] = [
|
|
|
+ 'act_number' => $actNumber,
|
|
|
+ 'act_title' => $actTitle,
|
|
|
+ 'act_duration' => $actDuration,
|
|
|
+ 'act_content' => $cleaned_content,
|
|
|
+ ];
|
|
|
+ }
|
|
|
|
|
|
if (empty($parsed_segments)) {
|
|
|
Utils::throwError('20003:无法解析生成的分段剧本,请检查格式');
|
|
|
}
|
|
|
|
|
|
+ // 获取角色和场景名称用于标记
|
|
|
+ $role_names = [];
|
|
|
+ $scene_names = [];
|
|
|
+
|
|
|
+ if (!empty($roles) && is_array($roles)) {
|
|
|
+ foreach ($roles as $role) {
|
|
|
+ $role_name = getProp($role, 'role', '');
|
|
|
+ if (!empty($role_name)) {
|
|
|
+ $role_names[] = $role_name;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!empty($scenes) && is_array($scenes)) {
|
|
|
+ foreach ($scenes as $scene) {
|
|
|
+ $scene_name = getProp($scene, 'scene', '');
|
|
|
+ if (!empty($scene_name)) {
|
|
|
+ $scene_names[] = $scene_name;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理每个片段的 act_show_content
|
|
|
+ foreach ($parsed_segments as &$segment) {
|
|
|
+ $act_content = getProp($segment, 'act_content', '');
|
|
|
+ if (empty($act_content)) {
|
|
|
+ $segment['act_show_content'] = '';
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ $processed_content = $act_content;
|
|
|
+
|
|
|
+ // 先匹配场景(在"场景:"后面)
|
|
|
+ foreach ($scene_names as $scene_name) {
|
|
|
+ // 转义特殊字符
|
|
|
+ $escaped_scene = preg_quote($scene_name, '/');
|
|
|
+ // 匹配场景:后面的场景名称(可能换行,也可能不换行)
|
|
|
+ $pattern = '/(场景:[\s\r\n]*)(' . $escaped_scene . ')/u';
|
|
|
+
|
|
|
+ $processed_content = preg_replace_callback($pattern, function($matches) {
|
|
|
+ $prefix = $matches[1];
|
|
|
+ $scene_name = $matches[2];
|
|
|
+
|
|
|
+ // 检查是否已经被标记
|
|
|
+ if (strpos($scene_name, '{') === false && strpos($scene_name, '}') === false) {
|
|
|
+ return $prefix . '{' . $scene_name . '}';
|
|
|
+ }
|
|
|
+ return $matches[0];
|
|
|
+ }, $processed_content);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 再匹配角色名称(只在"画面:"部分)
|
|
|
+ foreach ($role_names as $role_name) {
|
|
|
+ // 转义特殊字符
|
|
|
+ $escaped_role = preg_quote($role_name, '/');
|
|
|
+
|
|
|
+ // 只匹配"画面:"开始到换行之前的内容
|
|
|
+ $pattern = '/(画面:[^\n]*)/u';
|
|
|
+ $processed_content = preg_replace_callback($pattern, function($matches) use ($escaped_role) {
|
|
|
+ $line = $matches[1]; // 整行"画面:xxxxx"
|
|
|
+
|
|
|
+ // 在这一行中匹配所有角色名(避免重复标记)
|
|
|
+ $new_line = preg_replace(
|
|
|
+ '/(?<![{])(' . $escaped_role . ')(?![}])/u',
|
|
|
+ '{$1}',
|
|
|
+ $line
|
|
|
+ );
|
|
|
+
|
|
|
+ return $new_line;
|
|
|
+ }, $processed_content);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理【镜头X】或【分镜X】重新编号为从1开始的连续整数【镜头X】
|
|
|
+ $shot_counter = 0;
|
|
|
+ $processed_content = preg_replace_callback(
|
|
|
+ '/【(?:镜头|分镜)(\d+)】/u',
|
|
|
+ function($matches) use (&$shot_counter) {
|
|
|
+ $shot_counter++;
|
|
|
+ return '【镜头' . $shot_counter . '】';
|
|
|
+ },
|
|
|
+ $processed_content
|
|
|
+ );
|
|
|
+
|
|
|
+ $segment['act_show_content'] = $processed_content;
|
|
|
+ }
|
|
|
+ unset($segment); // 解除引用
|
|
|
+
|
|
|
// 保存到数据库
|
|
|
$now = date('Y-m-d H:i:s');
|
|
|
+ $saved_acts = []; // 用于记录保存后的片段信息
|
|
|
|
|
|
if ($act_id > 0) {
|
|
|
// 单个片段模式:更新单条记录
|
|
|
@@ -8608,7 +8869,8 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
$new_segment = $parsed_segments[0];
|
|
|
|
|
|
$update_data = [
|
|
|
- 'act_content' => $generated_content,
|
|
|
+ 'act_content' => getProp($new_segment, 'act_content', ''),
|
|
|
+ 'act_show_content' => getProp($new_segment, 'act_show_content', ''),
|
|
|
'act_title' => getProp($new_segment, 'act_title', ''),
|
|
|
'act_duration' => getProp($new_segment, 'act_duration', 0),
|
|
|
'updated_at' => $now,
|
|
|
@@ -8618,6 +8880,25 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
->where('id', $act_id)
|
|
|
->update($update_data);
|
|
|
|
|
|
+ // 获取更新后的记录
|
|
|
+ $updated_act = DB::table('mp_episode_segments')
|
|
|
+ ->where('id', $act_id)
|
|
|
+ ->first();
|
|
|
+
|
|
|
+ if ($updated_act) {
|
|
|
+ $saved_acts[] = [
|
|
|
+ 'act_id' => $updated_act->id,
|
|
|
+ 'act_number' => $updated_act->act_number,
|
|
|
+ 'act_duration' => $updated_act->video_duration ? $updated_act->video_duration : $updated_act->act_duration,
|
|
|
+ 'act_content' => $updated_act->act_content,
|
|
|
+ 'act_show_content' => $updated_act->act_show_content,
|
|
|
+ 'video_url' => $updated_act->video_url ?? '',
|
|
|
+ 'video_duration' => $updated_act->video_duration ?? 0,
|
|
|
+ 'video_time_point_start' => $updated_act->video_time_point_start ?? 0,
|
|
|
+ 'video_time_point_end' => $updated_act->video_time_point_end ?? 0,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
dLog('deepseek')->info('单个片段重新生成成功', ['act_id' => $act_id]);
|
|
|
}
|
|
|
|
|
|
@@ -8644,6 +8925,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
'act_title' => getProp($segment, 'act_title', ''),
|
|
|
'act_duration' => getProp($segment, 'act_duration', 0),
|
|
|
'act_content' => getProp($segment, 'act_content', ''),
|
|
|
+ 'act_show_content' => getProp($segment, 'act_show_content', ''),
|
|
|
'created_at' => $now,
|
|
|
'updated_at' => $now,
|
|
|
];
|
|
|
@@ -8655,6 +8937,26 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
|
|
|
DB::commit();
|
|
|
|
|
|
+ // 查询保存后的所有片段记录
|
|
|
+ $saved_segments = DB::table('mp_episode_segments')
|
|
|
+ ->where('episode_id', $target_episode_id)
|
|
|
+ ->orderBy('act_number')
|
|
|
+ ->get();
|
|
|
+
|
|
|
+ foreach ($saved_segments as $saved_segment) {
|
|
|
+ $saved_acts[] = [
|
|
|
+ 'act_id' => $saved_segment->id,
|
|
|
+ 'act_number' => $saved_segment->act_number,
|
|
|
+ 'act_duration' => $saved_segment->video_duration ? $saved_segment->video_duration : $saved_segment->act_duration,
|
|
|
+ 'act_content' => $saved_segment->act_content,
|
|
|
+ 'act_show_content' => $saved_segment->act_show_content,
|
|
|
+ 'video_url' => $saved_segment->video_url ?? '',
|
|
|
+ 'video_duration' => $saved_segment->video_duration ?? 0,
|
|
|
+ 'video_time_point_start' => $saved_segment->video_time_point_start ?? 0,
|
|
|
+ 'video_time_point_end' => $saved_segment->video_time_point_end ?? 0,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
dLog('deepseek')->info('整个剧集片段重新生成成功', [
|
|
|
'episode_id' => $target_episode_id,
|
|
|
'segments_count' => count($segments_to_insert)
|
|
|
@@ -8667,12 +8969,14 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // 构造返回数据结构(参考AnimeService返回格式,但不包含products)
|
|
|
return [
|
|
|
- 'success' => true,
|
|
|
- 'message' => $act_id > 0 ? '单个片段重新生成成功' : '整个剧集片段重新生成成功',
|
|
|
- 'generated_content' => $generated_content,
|
|
|
- 'parsed_segments' => $parsed_segments,
|
|
|
- 'usage' => $ai_result['usage'] ?? [],
|
|
|
+ 'anime_id' => $target_anime_id,
|
|
|
+ 'episode_id' => $target_episode_id,
|
|
|
+ 'title' => getProp($episode, 'title', ''),
|
|
|
+ 'episode_number' => $target_episode_number,
|
|
|
+ 'is_generated' => getProp($episode, 'is_generated', 0),
|
|
|
+ 'acts' => $saved_acts,
|
|
|
];
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
@@ -10202,7 +10506,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
// 仅调用deepseek对话接口
|
|
|
private function chatOnly($post_data) {
|
|
|
$post_data['max_tokens'] = 300000;
|
|
|
- $client = new Client(['timeout' => 1200, 'verify' => false]);
|
|
|
+ $client = new Client(['timeout' => 1800, 'verify' => false]);
|
|
|
$result = $client->post($this->url, ['json' => $post_data, 'headers' => $this->headers]);
|
|
|
$response = $result->getBody()->getContents();
|
|
|
$response_arr = json_decode($response, true);
|
|
|
@@ -10230,7 +10534,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
Utils::throwError('20003:GPT-5.4 API Key未配置');
|
|
|
}
|
|
|
|
|
|
- $client = new Client(['timeout' => 1200, 'verify' => false]);
|
|
|
+ $client = new Client(['timeout' => 1800, 'verify' => false]);
|
|
|
$headers = [
|
|
|
'Authorization' => 'Bearer ' . $apiKey,
|
|
|
'Content-Type' => 'application/json'
|
|
|
@@ -10527,7 +10831,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
$access_key, $secret_key, $body
|
|
|
);
|
|
|
|
|
|
- $client = new Client(['timeout' => 60, 'verify' => false]);
|
|
|
+ $client = new Client(['timeout' => 1800, 'verify' => false]);
|
|
|
$response = $client->post("https://{$host}/?Action={$action}&Version={$version}", [
|
|
|
'headers' => $headers,
|
|
|
'body' => $body
|
|
|
@@ -10862,7 +11166,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
|
|
|
dLog('deepseek')->info('火山引擎对话API请求参数: ', $requestData);
|
|
|
|
|
|
- $client = new Client(['verify' => false, 'timeout' => 1200]);
|
|
|
+ $client = new Client(['verify' => false, 'timeout' => 1800]);
|
|
|
|
|
|
try {
|
|
|
// 判断是否为流式输出
|
|
|
@@ -11376,79 +11680,4 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- /**
|
|
|
- * 解析分镜内容的公开方法
|
|
|
- */
|
|
|
- public function parseSegmentContent($segment_content, $model = null) {
|
|
|
- if (!$model) {
|
|
|
- $model = DB::table('mp_text_models')->where('is_enabled', 1)->orderBy('id')->value('model');
|
|
|
- if (!$model) {
|
|
|
- return $segment_content;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 构建系统提示词
|
|
|
- $systemPrompt = "你是一个专业的分镜内容解析助手。请将用户提供的分镜内容解析成标准格式,包含以下字段:
|
|
|
-画面描述:详细描述画面内容
|
|
|
-构图设计:镜头构图方式
|
|
|
-运镜调度:摄影机运动方式
|
|
|
-配音角色:配音的角色名称
|
|
|
-台词内容:角色说的话
|
|
|
-画面类型:普通画面或对口型
|
|
|
-场景:拍摄场景
|
|
|
-出镜角色:画面中出现的角色
|
|
|
-尾帧描述:镜头结束时的画面描述
|
|
|
-情感:配音情感(开心、悲伤、生气、惊讶、恐惧、厌恶、激动、冷漠、中性、沮丧、撒娇、害羞、安慰鼓励、咆哮、温柔、自然讲述、情感电台、磁性、广告营销、气泡音、新闻播报、娱乐八卦)
|
|
|
-性别:1(男)、2(女)、0(中性)
|
|
|
-语速:-50到100之间的整数
|
|
|
-音量:-50到100之间的整数
|
|
|
-情感强度:1到5之间的整数
|
|
|
-音调:-12到12之间的整数
|
|
|
-
|
|
|
-请严格按照上述格式输出,如果某个字段无法确定,请设置合理的默认值。";
|
|
|
-
|
|
|
- // 构建消息
|
|
|
- $messages = [
|
|
|
- [
|
|
|
- 'role' => 'system',
|
|
|
- 'content' => $systemPrompt
|
|
|
- ],
|
|
|
- [
|
|
|
- 'role' => 'user',
|
|
|
- 'content' => $segment_content
|
|
|
- ]
|
|
|
- ];
|
|
|
-
|
|
|
- $post_data = [
|
|
|
- 'model' => $model,
|
|
|
- 'messages' => $messages,
|
|
|
- // 'max_tokens' => 2048,
|
|
|
- 'temperature' => 0.7,
|
|
|
- 'frequency_penalty' => 0,
|
|
|
- 'presence_penalty' => 0,
|
|
|
- 'response_format' => ['type' => 'text'],
|
|
|
- 'stream' => false
|
|
|
- ];
|
|
|
-
|
|
|
- try {
|
|
|
- if (in_array($model, ['deepseek-reasoner', 'deepseek-chat', 'deepseek-v4-flash', 'deepseek-v4-pro'])) {
|
|
|
- // 使用DeepSeek API
|
|
|
- $chatResult = $this->chatOnly($post_data);
|
|
|
- } else {
|
|
|
- // 使用火山引擎API
|
|
|
- $chatResult = $this->volcEngineChatCompletion($post_data);
|
|
|
- }
|
|
|
-
|
|
|
- if (is_array($chatResult) && !empty($chatResult['fullContent'])) {
|
|
|
- return $chatResult['fullContent'];
|
|
|
- }
|
|
|
- } catch (\Exception $e) {
|
|
|
- dLog('deepseek')->error('解析分镜内容失败: ' . $e->getMessage());
|
|
|
- logDB('deepseek', 'error', '解析分镜内容失败', ['error' => $e->getMessage()]);
|
|
|
- }
|
|
|
-
|
|
|
- return $segment_content;
|
|
|
- }
|
|
|
-
|
|
|
}
|