|
@@ -12268,6 +12268,8 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
$act_id = getProp($data, 'act_id', 0);
|
|
$act_id = getProp($data, 'act_id', 0);
|
|
|
$episode_id = getProp($data, 'episode_id', 0);
|
|
$episode_id = getProp($data, 'episode_id', 0);
|
|
|
$check_mode = (int)getProp($data, 'check_mode', 0); // 1=预览确认模式(仅 act_id 模式生效,不落库,返回 editAct 入参)
|
|
$check_mode = (int)getProp($data, 'check_mode', 0); // 1=预览确认模式(仅 act_id 模式生效,不落库,返回 editAct 入参)
|
|
|
|
|
+ // 单片段模式:act_id 可以是数据库真实ID(数字)或预览生成的虚拟ID(字符串),非空即单片段模式
|
|
|
|
|
+ $isActIdMode = !empty($act_id);
|
|
|
|
|
|
|
|
// 验证:prompt和template_id至少提供一个
|
|
// 验证:prompt和template_id至少提供一个
|
|
|
if (empty($prompt) && empty($template_id)) {
|
|
if (empty($prompt) && empty($template_id)) {
|
|
@@ -12322,11 +12324,27 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
$target_anime_id = 0;
|
|
$target_anime_id = 0;
|
|
|
$target_episode_number = 0;
|
|
$target_episode_number = 0;
|
|
|
|
|
|
|
|
- if ($act_id > 0) {
|
|
|
|
|
|
|
+ if ($isActIdMode) {
|
|
|
// 单个片段模式:获取该片段的内容
|
|
// 单个片段模式:获取该片段的内容
|
|
|
$segment = DB::table('mp_episode_segments')
|
|
$segment = DB::table('mp_episode_segments')
|
|
|
->where('id', $act_id)
|
|
->where('id', $act_id)
|
|
|
->first();
|
|
->first();
|
|
|
|
|
+
|
|
|
|
|
+ // 数据库不存在时,尝试从预览虚拟 act_id 保存的 redis 数据中恢复
|
|
|
|
|
+ if (!$segment) {
|
|
|
|
|
+ $previewData = Redis::get($this->buildActPreviewKey($act_id));
|
|
|
|
|
+ if ($previewData) {
|
|
|
|
|
+ $previewData = json_decode($previewData, true);
|
|
|
|
|
+ $segment = (object)[
|
|
|
|
|
+ 'anime_id' => getProp($previewData, 'anime_id'),
|
|
|
|
|
+ 'episode_id' => getProp($previewData, 'episode_id'),
|
|
|
|
|
+ 'episode_number' => getProp($previewData, 'episode_number'),
|
|
|
|
|
+ 'act_number' => getProp($previewData, 'act_number'),
|
|
|
|
|
+ 'act_duration' => getProp($previewData, 'act_duration'),
|
|
|
|
|
+ 'act_content' => getProp($previewData, 'act_content'),
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
if (!$segment) {
|
|
if (!$segment) {
|
|
|
Utils::throwError('20003:片段不存在');
|
|
Utils::throwError('20003:片段不存在');
|
|
@@ -12481,7 +12499,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
|
|
|
|
|
// 输出格式铁律(用户消息侧再次强制,避免模型自由发挥)
|
|
// 输出格式铁律(用户消息侧再次强制,避免模型自由发挥)
|
|
|
$user_prompt .= "\n\n【输出格式铁律-必须严格遵守】\n";
|
|
$user_prompt .= "\n\n【输出格式铁律-必须严格遵守】\n";
|
|
|
- if ($act_id > 0) {
|
|
|
|
|
|
|
+ if ($isActIdMode) {
|
|
|
$user_prompt .= "- 请直接以“##片段1”开头,紧跟“时长:X s”行,然后输出本片段的完整分镜内容(分镜序号、出场角色、场景、出场道具、画面、运镜、配音台词、背景音效),分镜从1开始连续编号。\n";
|
|
$user_prompt .= "- 请直接以“##片段1”开头,紧跟“时长:X s”行,然后输出本片段的完整分镜内容(分镜序号、出场角色、场景、出场道具、画面、运镜、配音台词、背景音效),分镜从1开始连续编号。\n";
|
|
|
$user_prompt .= "- 只允许根据【原分段剧本内容】生成一个片段(即“##片段1”),禁止生成多个片段。\n";
|
|
$user_prompt .= "- 只允许根据【原分段剧本内容】生成一个片段(即“##片段1”),禁止生成多个片段。\n";
|
|
|
} else {
|
|
} else {
|
|
@@ -12489,9 +12507,15 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
}
|
|
}
|
|
|
$user_prompt .= "- 缺少“##片段N”标题或“时长:”行即为格式错误;输出前必须检查格式,不得输出其他任何内容。\n";
|
|
$user_prompt .= "- 缺少“##片段N”标题或“时长:”行即为格式错误;输出前必须检查格式,不得输出其他任何内容。\n";
|
|
|
$user_prompt .= "- 片段时长必须在4秒到15秒之间(4s≤时长≤15s),超过15秒必须拆分为多个片段,不得输出低于4秒或超过15秒的片段。\n";
|
|
$user_prompt .= "- 片段时长必须在4秒到15秒之间(4s≤时长≤15s),超过15秒必须拆分为多个片段,不得输出低于4秒或超过15秒的片段。\n";
|
|
|
|
|
+
|
|
|
|
|
+ // 整集模式:强制生成与原来一致的片段数量
|
|
|
|
|
+ if ($episode_id > 0 && isset($segments)) {
|
|
|
|
|
+ $originSegmentCount = count($segments);
|
|
|
|
|
+ $user_prompt .= "- 本集原有{$originSegmentCount}个片段,必须严格生成{$originSegmentCount}个片段(##片段1 到 ##片段{$originSegmentCount}),不得多不得少。\n";
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
$system_prompt = "\n\n【输出格式铁律-最高优先级】\n";
|
|
$system_prompt = "\n\n【输出格式铁律-最高优先级】\n";
|
|
|
- if ($act_id > 0) {
|
|
|
|
|
|
|
+ if ($isActIdMode) {
|
|
|
$system_prompt .= "- 直接以“##片段1”开头,紧跟“时长:X s”行,然后输出本片段分镜内容\n";
|
|
$system_prompt .= "- 直接以“##片段1”开头,紧跟“时长:X s”行,然后输出本片段分镜内容\n";
|
|
|
$system_prompt .= "- 只允许根据【原分段剧本内容】生成一个片段(即“##片段1”),禁止生成多个片段。\n";
|
|
$system_prompt .= "- 只允许根据【原分段剧本内容】生成一个片段(即“##片段1”),禁止生成多个片段。\n";
|
|
|
} else {
|
|
} else {
|
|
@@ -12673,7 +12697,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
// 内容未带时长时,单片段模式沿用原片段的时长
|
|
// 内容未带时长时,单片段模式沿用原片段的时长
|
|
|
- if ($fallbackDuration <= 0 && $act_id > 0 && !empty($segment->act_duration)) {
|
|
|
|
|
|
|
+ if ($fallbackDuration <= 0 && $isActIdMode && !empty($segment->act_duration)) {
|
|
|
$fallbackDuration = (float)$segment->act_duration;
|
|
$fallbackDuration = (float)$segment->act_duration;
|
|
|
}
|
|
}
|
|
|
// 时长范围约束:4~15秒,超限兜底修正(0=未知按最低4秒)
|
|
// 时长范围约束:4~15秒,超限兜底修正(0=未知按最低4秒)
|
|
@@ -12693,7 +12717,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// act_id 单片段模式:只允许一个片段,多余片段丢弃,仅保留第一个
|
|
// act_id 单片段模式:只允许一个片段,多余片段丢弃,仅保留第一个
|
|
|
- if ($act_id > 0 && count($parsed_segments) > 1) {
|
|
|
|
|
|
|
+ if ($isActIdMode && count($parsed_segments) > 1) {
|
|
|
dLog('deepseek')->info('regenerateSegmentScript act_id 模式仅保留第一个片段', [
|
|
dLog('deepseek')->info('regenerateSegmentScript act_id 模式仅保留第一个片段', [
|
|
|
'act_id' => $act_id,
|
|
'act_id' => $act_id,
|
|
|
'original_count' => count($parsed_segments),
|
|
'original_count' => count($parsed_segments),
|
|
@@ -12821,7 +12845,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
$now = date('Y-m-d H:i:s');
|
|
$now = date('Y-m-d H:i:s');
|
|
|
$saved_acts = []; // 用于记录保存后的片段信息
|
|
$saved_acts = []; // 用于记录保存后的片段信息
|
|
|
|
|
|
|
|
- if ($act_id > 0) {
|
|
|
|
|
|
|
+ if ($isActIdMode) {
|
|
|
// 单个片段模式:更新单条记录
|
|
// 单个片段模式:更新单条记录
|
|
|
if (count($parsed_segments) > 0) {
|
|
if (count($parsed_segments) > 0) {
|
|
|
$new_segment = $parsed_segments[0];
|
|
$new_segment = $parsed_segments[0];
|
|
@@ -12830,12 +12854,17 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
if ($check_mode === 1) {
|
|
if ($check_mode === 1) {
|
|
|
return [
|
|
return [
|
|
|
'check_mode' => 1,
|
|
'check_mode' => 1,
|
|
|
- 'act_id' => (int)$act_id,
|
|
|
|
|
|
|
+ 'act_id' => $act_id,
|
|
|
'act_title' => getProp($new_segment, 'act_title', ''),
|
|
'act_title' => getProp($new_segment, 'act_title', ''),
|
|
|
'act_duration' => getProp($new_segment, 'act_duration', 0),
|
|
'act_duration' => getProp($new_segment, 'act_duration', 0),
|
|
|
'act_content' => getProp($new_segment, 'act_show_content', ''),
|
|
'act_content' => getProp($new_segment, 'act_show_content', ''),
|
|
|
];
|
|
];
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // 虚拟 act_id 仅用于预览/参考,不允许直接保存到数据库
|
|
|
|
|
+ if (!is_numeric($act_id)) {
|
|
|
|
|
+ Utils::throwError('20003:该片段不可直接保存');
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
$update_data = [
|
|
$update_data = [
|
|
|
'act_content' => getProp($new_segment, 'act_content', ''),
|
|
'act_content' => getProp($new_segment, 'act_content', ''),
|
|
@@ -12882,6 +12911,37 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
'source' => 'regenerateSegmentScript',
|
|
'source' => 'regenerateSegmentScript',
|
|
|
]);
|
|
]);
|
|
|
|
|
|
|
|
|
|
+ // 为每个片段生成虚拟 act_id 并写入 redis(1天有效),供后续按虚拟 act_id 恢复片段数据
|
|
|
|
|
+ $previewActs = [];
|
|
|
|
|
+ foreach ($parsed_segments as $seg) {
|
|
|
|
|
+ $virtualActId = 'preview_' . time() . '_' . uniqid();
|
|
|
|
|
+ Redis::setex($this->buildActPreviewKey($virtualActId), 86400, json_encode([
|
|
|
|
|
+ 'anime_id' => $target_anime_id,
|
|
|
|
|
+ 'episode_id' => $target_episode_id,
|
|
|
|
|
+ 'episode_number' => $target_episode_number,
|
|
|
|
|
+ 'act_number' => $seg['act_number'],
|
|
|
|
|
+ 'act_title' => $seg['act_title'],
|
|
|
|
|
+ 'act_duration' => $seg['act_duration'],
|
|
|
|
|
+ 'act_content' => $seg['act_content'],
|
|
|
|
|
+ 'act_show_content'=> $seg['act_show_content'],
|
|
|
|
|
+ ], JSON_UNESCAPED_UNICODE));
|
|
|
|
|
+
|
|
|
|
|
+ $previewActs[] = [
|
|
|
|
|
+ 'act_id' => $virtualActId,
|
|
|
|
|
+ 'act_number' => $seg['act_number'],
|
|
|
|
|
+ 'act_title' => $seg['act_title'],
|
|
|
|
|
+ 'act_duration' => $seg['act_duration'],
|
|
|
|
|
+ 'act_content' => $seg['act_content'],
|
|
|
|
|
+ 'act_show_content' => $seg['act_show_content'],
|
|
|
|
|
+ 'video_url' => '',
|
|
|
|
|
+ 'video_duration' => 0,
|
|
|
|
|
+ 'video_time_point_start' => 0,
|
|
|
|
|
+ 'video_time_point_end' => 0,
|
|
|
|
|
+ 'last_frame_url' => '',
|
|
|
|
|
+ 'current_type' => 0,
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
return [
|
|
return [
|
|
|
'check_mode' => 1,
|
|
'check_mode' => 1,
|
|
|
'anime_id' => $target_anime_id,
|
|
'anime_id' => $target_anime_id,
|
|
@@ -12889,14 +12949,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
'title' => getProp($episode, 'title', ''),
|
|
'title' => getProp($episode, 'title', ''),
|
|
|
'episode_number' => $target_episode_number,
|
|
'episode_number' => $target_episode_number,
|
|
|
'is_generated' => getProp($episode, 'is_generated', 0),
|
|
'is_generated' => getProp($episode, 'is_generated', 0),
|
|
|
- 'acts' => array_map(function ($seg) {
|
|
|
|
|
- return [
|
|
|
|
|
- 'act_number' => $seg['act_number'],
|
|
|
|
|
- 'act_duration' => $seg['act_duration'],
|
|
|
|
|
- 'act_content' => $seg['act_content'],
|
|
|
|
|
- 'act_show_content' => $seg['act_show_content'],
|
|
|
|
|
- ];
|
|
|
|
|
- }, $parsed_segments),
|
|
|
|
|
|
|
+ 'acts' => $previewActs,
|
|
|
];
|
|
];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -13138,6 +13191,17 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
|
|
+ * 预览虚拟 act_id 在 redis 中的键
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param mixed $actId
|
|
|
|
|
+ * @return string
|
|
|
|
|
+ */
|
|
|
|
|
+ private function buildActPreviewKey($actId): string
|
|
|
|
|
+ {
|
|
|
|
|
+ return 'anime_act_preview:' . $actId;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
* 清理分镜内容:去除时长、旁白音色等非分镜行,只保留完整分镜内容(分镜标记统一为【分镜N】/【镜头N】)
|
|
* 清理分镜内容:去除时长、旁白音色等非分镜行,只保留完整分镜内容(分镜标记统一为【分镜N】/【镜头N】)
|
|
|
*
|
|
*
|
|
|
* @param string $actContent
|
|
* @param string $actContent
|