|
|
@@ -8233,6 +8233,12 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
$episode_arr = handleEpisodeContentForAce($fullContent);
|
|
|
logDB('deepseek', 'info', '解析内容', $episode_arr);
|
|
|
|
|
|
+ // // 如果有anime_script_id,进行主体和场景名称的智能匹配和替换(激进策略)
|
|
|
+ // if ($anime_script_id && !empty($extra_roles) && !empty($extra_scenes)) {
|
|
|
+ // $episode_arr = $this->matchAndReplaceRolesAndScenes($episode_arr, $extra_roles, $extra_scenes);
|
|
|
+ // logDB('deepseek', 'info', '匹配替换后的解析内容', $episode_arr);
|
|
|
+ // }
|
|
|
+
|
|
|
// 验证逻辑:如果anime_script_id有值,需要验证生成的主体和场景是否匹配
|
|
|
$max_retries = 3;
|
|
|
$retry_count = 0;
|
|
|
@@ -8353,6 +8359,12 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
// 重新解析内容
|
|
|
dLog('deepseek')->info("第{$retry_count}次重试完整内容: " . $fullContent);
|
|
|
$episode_arr = handleEpisodeContentForAce($fullContent);
|
|
|
+
|
|
|
+ // // 进行主体和场景名称的智能匹配和替换(激进策略)
|
|
|
+ // if ($anime_script_id && !empty($extra_roles) && !empty($extra_scenes)) {
|
|
|
+ // $episode_arr = $this->matchAndReplaceRolesAndScenes($episode_arr, $extra_roles, $extra_scenes);
|
|
|
+ // }
|
|
|
+
|
|
|
logDB('deepseek', 'info', "第{$retry_count}次重试解析内容", $episode_arr);
|
|
|
|
|
|
// 重新验证
|
|
|
@@ -8568,6 +8580,181 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
+ * 智能匹配并替换主体和场景名称
|
|
|
+ * 如果生成的主体或场景名称是强制设定名称的子串,则自动替换为完整名称
|
|
|
+ *
|
|
|
+ * @param array $episode_arr 解析后的剧集数据
|
|
|
+ * @param array $extra_roles 强制主体设定(key为主体名称)
|
|
|
+ * @param array $extra_scenes 强制场景设定(key为场景名称)
|
|
|
+ * @return array 替换后的剧集数据
|
|
|
+ */
|
|
|
+ private function matchAndReplaceRolesAndScenes($episode_arr, $extra_roles, $extra_scenes) {
|
|
|
+ $replaced_count = 0;
|
|
|
+
|
|
|
+ // 1. 处理主体列表
|
|
|
+ if (!empty($episode_arr['roles'])) {
|
|
|
+ foreach ($episode_arr['roles'] as &$role) {
|
|
|
+ $generated_role_name = $role['role'] ?? '';
|
|
|
+
|
|
|
+ // 跳过旁白
|
|
|
+ if ($generated_role_name === '旁白') {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果生成的主体名不在强制设定中,尝试模糊匹配
|
|
|
+ if (!isset($extra_roles[$generated_role_name])) {
|
|
|
+ $matched_role = $this->findMatchingName($generated_role_name, array_keys($extra_roles));
|
|
|
+
|
|
|
+ if ($matched_role) {
|
|
|
+ dLog('deepseek')->info("主体名称替换: {$generated_role_name} => {$matched_role}");
|
|
|
+ $role['role'] = $matched_role;
|
|
|
+ $replaced_count++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 处理场景列表
|
|
|
+ if (!empty($episode_arr['scenes'])) {
|
|
|
+ foreach ($episode_arr['scenes'] as &$scene) {
|
|
|
+ $generated_scene_name = $scene['scene'] ?? '';
|
|
|
+
|
|
|
+ // 如果生成的场景名不在强制设定中,尝试模糊匹配
|
|
|
+ if (!isset($extra_scenes[$generated_scene_name])) {
|
|
|
+ $matched_scene = $this->findMatchingName($generated_scene_name, array_keys($extra_scenes));
|
|
|
+
|
|
|
+ if ($matched_scene) {
|
|
|
+ dLog('deepseek')->info("场景名称替换: {$generated_scene_name} => {$matched_scene}");
|
|
|
+ $scene['scene'] = $matched_scene;
|
|
|
+ $replaced_count++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 处理分段剧本中的主体和场景引用(全能模式下只使用segment_content字段)
|
|
|
+ if (!empty($episode_arr['acts'])) {
|
|
|
+ foreach ($episode_arr['acts'] as &$act) {
|
|
|
+ if (!empty($act['segments'])) {
|
|
|
+ foreach ($act['segments'] as &$segment) {
|
|
|
+ if (!empty($segment['segment_content'])) {
|
|
|
+ $original_content = $segment['segment_content'];
|
|
|
+ $replaced_content = $original_content;
|
|
|
+
|
|
|
+ // 替换场景名 - 在文本中查找并替换
|
|
|
+ foreach (array_keys($extra_scenes) as $full_scene_name) {
|
|
|
+ // 尝试找到可能的短名称
|
|
|
+ $potential_short_names = [];
|
|
|
+
|
|
|
+ // 提取场景名的主要部分(冒号之前)
|
|
|
+ if (mb_strpos($full_scene_name, ':') !== false) {
|
|
|
+ $short_name = mb_substr($full_scene_name, 0, mb_strpos($full_scene_name, ':'));
|
|
|
+ $potential_short_names[] = $short_name;
|
|
|
+ } elseif (mb_strpos($full_scene_name, ':') !== false) {
|
|
|
+ $short_name = mb_substr($full_scene_name, 0, mb_strpos($full_scene_name, ':'));
|
|
|
+ $potential_short_names[] = $short_name;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 替换场景字段中的短名称
|
|
|
+ foreach ($potential_short_names as $short_name) {
|
|
|
+ if ($short_name !== $full_scene_name) {
|
|
|
+ $pattern = '/(?<=场景:|场景:|拍摄场景:|拍摄场景:|背景场景:|背景场景:|环境:|环境:)' . preg_quote($short_name, '/') . '(?=\s|$|\n)/u';
|
|
|
+ $new_content = preg_replace($pattern, $full_scene_name, $replaced_content);
|
|
|
+ if ($new_content !== $replaced_content) {
|
|
|
+ $replaced_content = $new_content;
|
|
|
+ $replaced_count++;
|
|
|
+ dLog('deepseek')->info("场景名替换: {$short_name} => {$full_scene_name}");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 替换配音角色名(跳过旁白)
|
|
|
+ foreach (array_keys($extra_roles) as $full_role_name) {
|
|
|
+ if ($full_role_name === '旁白') {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 尝试找到可能的短名称
|
|
|
+ $potential_short_names = [];
|
|
|
+
|
|
|
+ // 提取角色名的主要部分(短横线之前)
|
|
|
+ if (mb_strpos($full_role_name, '-') !== false) {
|
|
|
+ $short_name = mb_substr($full_role_name, 0, mb_strpos($full_role_name, '-'));
|
|
|
+ $potential_short_names[] = $short_name;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 替换配音台词中的短名称
|
|
|
+ foreach ($potential_short_names as $short_name) {
|
|
|
+ if ($short_name !== $full_role_name) {
|
|
|
+ $pattern = '/(?<=配音台词:\[|配音台词:\[|中文配音:\[|中文配音:\[)\s*' . preg_quote($short_name, '/') . '\s*(?=\])/u';
|
|
|
+ $new_content = preg_replace($pattern, $full_role_name, $replaced_content);
|
|
|
+ if ($new_content !== $replaced_content) {
|
|
|
+ $replaced_content = $new_content;
|
|
|
+ $replaced_count++;
|
|
|
+ dLog('deepseek')->info("角色名替换: {$short_name} => {$full_role_name}");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果有替换,更新segment_content
|
|
|
+ if ($replaced_content !== $original_content) {
|
|
|
+ $segment['segment_content'] = $replaced_content;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($replaced_count > 0) {
|
|
|
+ logDB('deepseek', 'info', '主体和场景名称智能替换', [
|
|
|
+ 'replaced_count' => $replaced_count
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ return $episode_arr;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查找匹配的名称
|
|
|
+ * 规则:如果生成的名称是强制设定名称的子串(开头部分),则返回匹配的强制设定名称
|
|
|
+ *
|
|
|
+ * @param string $generated_name 生成的名称
|
|
|
+ * @param array $expected_names 预期的名称列表
|
|
|
+ * @return string|null 匹配到的名称,如果没有匹配则返回null
|
|
|
+ */
|
|
|
+ private function findMatchingName($generated_name, $expected_names) {
|
|
|
+ if (empty($generated_name)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 尝试精确匹配
|
|
|
+ if (in_array($generated_name, $expected_names)) {
|
|
|
+ return $generated_name;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 尝试模糊匹配:生成的名称是否为某个预期名称的开头部分
|
|
|
+ foreach ($expected_names as $expected_name) {
|
|
|
+ // 检查生成的名称是否是预期名称的开头(如"沈秋岚"是"沈秋岚-程家居家围裙造型"的开头)
|
|
|
+ if (mb_strpos($expected_name, $generated_name) === 0) {
|
|
|
+ return $expected_name;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更宽松的匹配:检查生成的名称是否包含在预期名称中的任意位置
|
|
|
+ // 这样可以处理一些变体,但优先级较低
|
|
|
+ foreach ($expected_names as $expected_name) {
|
|
|
+ if (mb_strpos($expected_name, $generated_name) !== false) {
|
|
|
+ return $expected_name;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
* 重新生成分段剧本
|
|
|
* @param array $data 包含以下字段:
|
|
|
* - prompt: string|null 用户修改要求(可选)
|