Browse Source

1.优化了画风提示词(提取关键点),并增加了主体和场景分别使用的提示词2.生成主体或场景使用新画风提示词3.优化了生成分镜或分段剧本的提示词,强调只用原文内容

lh 3 months ago
parent
commit
3ab7c89d88

+ 23 - 9
app/Console/Anime/AnimeCheckImgUrlCommand.php

@@ -4,6 +4,7 @@ namespace App\Console\Anime;
 use Illuminate\Console\Command;
 use Illuminate\Support\Facades\DB;
 use App\Services\AIGeneration\AIImageGenerationService;
+use App\Services\DeepSeek\DeepSeekService;
 
 class AnimeCheckImgUrlCommand extends Command
 {
@@ -22,11 +23,13 @@ class AnimeCheckImgUrlCommand extends Command
     protected $description = '检查动漫对话角色和场景图片生成状态';
 
     protected $aiImageGenerationService;
+    protected $DeepSeekService;
 
-    public function __construct(AIImageGenerationService $aiImageGenerationService)
+    public function __construct(AIImageGenerationService $aiImageGenerationService, DeepSeekService $DeepSeekService)
     {
         parent::__construct();
         $this->aiImageGenerationService = $aiImageGenerationService;
+        $this->DeepSeekService = $DeepSeekService;
     }
 
     /**
@@ -201,6 +204,15 @@ class AnimeCheckImgUrlCommand extends Command
     private function syncRolesAndScenes($id, $roles, $scenes) {
         // 获取该动漫对话对应的所有剧集
         $episodes = DB::table('mp_anime_episodes')->where('anime_id', $id)->get();
+        $anime = DB::table('mp_animes')->where('id', $id)->first();
+        $art_style_type = getProp($anime, 'art_style_type');
+        $character_prefix = '';
+        $scene_prefix = '';
+        if ($art_style_type) {
+            $art_style_arr = $this->DeepSeekService->getArtStyleShortPromptByInput($art_style_type);
+            $character_prefix = isset($art_style_arr['character_prefix']) ? $art_style_arr['character_prefix'] : '';
+            $scene_prefix = isset($art_style_arr['scene_prefix']) ? $art_style_arr['scene_prefix'] : '';
+        }
         
         if ($episodes->isEmpty()) {
             dLog('command')->info("动漫对话ID:{$id} 没有关联的剧集");
@@ -265,10 +277,11 @@ class AnimeCheckImgUrlCommand extends Command
                         // 优先使用 pic_prompt,如果没有则使用 description
                         $description = !empty($episode_pic_prompt) ? trim((string)$episode_pic_prompt) : $episode_description;
                         
-                        if ($art_style) {
-                            // $description = "美术风格:\n{$art_style}\n主体描述:{$description}\n图片要求:图片侧重点是人物,不要出现场景或背景,并且只能生成一个人物";
-                            $description = "主体描述:{$description}\n图片要求:图片侧重点是人物,不要出现场景或背景,并且只能生成一个人物";
-                        }
+                        // if ($art_style) {
+                        //     // $description = "美术风格:\n{$art_style}\n主体描述:{$description}\n图片要求:图片侧重点是人物,不要出现场景或背景,并且只能生成一个人物";
+                        //     $description = "主体描述:{$description}\n图片要求:图片侧重点是人物,不要出现场景或背景,并且只能生成一个人物";
+                        // }
+                        $description = "{$character_prefix}。\n主体描述:{$description}\n超高清,高细节,主体突出,构图合理,画面干净,无场景或背景,单人物,无文字,无水印,无字幕,电影级光影,视觉焦点明确";
 
                         $params = [
                             'prompt' => $description,
@@ -340,10 +353,11 @@ class AnimeCheckImgUrlCommand extends Command
                         // 优先使用 pic_prompt,如果没有则使用 description
                         $description = !empty($episode_pic_prompt) ? trim((string)$episode_pic_prompt) : $episode_description;
                         
-                        if ($art_style) {
-                            // $description = "美术风格:\n{$art_style}\n场景描述:{$description}\n图片要求: 图片侧重点是场景,生成的是场景环境图片,不要出现人物(此为第一优先要求)";
-                            $description = "场景描述:{$description}\n图片要求: 图片侧重点是场景,生成的是场景环境图片,不要出现人物(此为第一优先要求)";
-                        }
+                        // if ($art_style) {
+                        //     // $description = "美术风格:\n{$art_style}\n场景描述:{$description}\n图片要求: 图片侧重点是场景,生成的是场景环境图片,不要出现人物(此为第一优先要求)";
+                        //     $description = "场景描述:{$description}\n图片要求: 图片侧重点是场景,生成的是场景环境图片,不要出现人物(此为第一优先要求)";
+                        // }
+                        $description = "{$scene_prefix}。\n场景描述:{$description}\n无人物,超高清,高细节,主体突出,构图合理,画面干净,无文字,无水印,无字幕,电影级光影,视觉焦点明确";
 
                         $params = [
                             'prompt' => $description,

+ 383 - 44
app/Libs/Helpers.php

@@ -2568,30 +2568,33 @@ function handleEpisodeContent($originalContent) {
                 
                 // 如果有主体图片提示词,添加到数组中
                 if ($picPrompt) {
-                    // 检查开头是否有"写实,全景,正面拍摄。"
-                    $prefix = '写实,全景,正面拍摄。';
-                    $prefixParts = ['写实', '全景', '正面拍摄'];
+                    // // 检查开头是否有"写实,全景,正面拍摄。"
+                    // $prefix = '写实,全景,正面拍摄。';
+                    // $prefixParts = ['写实', '全景', '正面拍摄'];
                     
-                    // 检查是否包含完整前缀
-                    if (strpos($picPrompt, $prefix) !== 0) {
-                        // 检查是否包含部分前缀词汇
-                        $hasPartialPrefix = false;
-                        foreach ($prefixParts as $part) {
-                            if (mb_strpos($picPrompt, $part) === 0 || mb_strpos($picPrompt, $part . ',') === 0) {
-                                $hasPartialPrefix = true;
-                                break;
-                            }
-                        }
+                    // // 检查是否包含完整前缀
+                    // if (strpos($picPrompt, $prefix) !== 0) {
+                    //     // 检查是否包含部分前缀词汇(不论位置)
+                    //     $hasPartialPrefix = false;
+                    //     foreach ($prefixParts as $part) {
+                    //         if (mb_strpos($picPrompt, $part) !== false) {
+                    //             $hasPartialPrefix = true;
+                    //             break;
+                    //         }
+                    //     }
                         
-                        if ($hasPartialPrefix) {
-                            // 如果有部分前缀,移除不完整的部分并补全
-                            $picPrompt = preg_replace('/^(写实[,,]?|全景[,,]?|正面拍摄[。.]?)+/u', '', $picPrompt);
-                            $picPrompt = $prefix . preg_replace('/^[。,, ]+|[。,, ]+$/u', '', $picPrompt);
-                        } else {
-                            // 如果没有前缀,直接在开头添加
-                            $picPrompt = $prefix . $picPrompt;
-                        }
-                    }
+                    //     if ($hasPartialPrefix) {
+                    //         // 移除开头的所有前缀词汇(包括它们后面的标点)
+                    //         $picPrompt = preg_replace('/^(写实[,,、。. ]*|全景[,,、。. ]*|正面拍摄[,,、。. ]*)+/u', '', $picPrompt);
+                    //         // 清理开头和结尾的标点符号和空格
+                    //         $picPrompt = preg_replace('/^[。,,、 ]+|[。,,、 ]+$/u', '', $picPrompt);
+                    //         // 添加完整前缀
+                    //         $picPrompt = $prefix . $picPrompt;
+                    //     } else {
+                    //         // 如果没有前缀,直接在开头添加
+                    //         $picPrompt = $prefix . $picPrompt;
+                    //     }
+                    // }
                     
                     // 检查是否包含"姿态:"或"姿态:"
                     if (preg_match('/姿态[::]/u', $picPrompt)) {
@@ -2895,6 +2898,339 @@ function handleEpisodeContent($originalContent) {
     return $result;
 }
 
+/**
+ * 处理剧本内容(全能模式)
+ */
+function handleScriptContentForAce($originalContent) {
+    if (!$originalContent) return [];
+    
+    // 使用更精确的正则表达式分割内容
+    $parts = [];
+        
+    // // 确保内容使用UTF-8编码
+    // if (!mb_check_encoding($originalContent, 'UTF-8')) {
+    //     $originalContent = mb_convert_encoding($originalContent, 'UTF-8', 'auto');
+    // }
+    
+    // 提取剧本名(###剧本名:后面的内容,支持多个空格)
+    preg_match('/###\s*剧本名\s*[::]\s*(.*?)(?=\n|$)/u', $originalContent, $scriptNameMatch);
+    $parts['script_name'] = isset($scriptNameMatch[1]) ? trim($scriptNameMatch[1]) : '';
+
+    // 提取故事梗概(直到遇到下一个###标记,支持多个空格)
+    preg_match('/###\s*故事梗概\s*\n(.*?)(?=\n\s*###|$)/s', $originalContent, $summaryMatch);
+    $parts['intro'] = isset($summaryMatch[1]) ? trim($summaryMatch[1]) : '';
+        
+    // 提取剧本亮点(支持多个空格)
+    preg_match('/###\s*剧本亮点\s*\n(.*?)(?=\n\s*###|$)/s', $originalContent, $highlightsMatch);
+    $parts['highlights'] = isset($highlightsMatch[1]) ? trim($highlightsMatch[1]) : '';
+        
+    // 提取人物关系(支持多个空格)
+    preg_match('/###\s*人物关系\s*\n(.*?)(?=\n\s*###|$)/s', $originalContent, $relationsMatch);
+    $parts['role_relationship'] = isset($relationsMatch[1]) ? trim($relationsMatch[1]) : '';
+        
+    // 提取核心矛盾(支持多个空格)
+    preg_match('/###\s*核心矛盾\s*\n(.*?)(?=\n\s*###|$)/s', $originalContent, $contradictionsMatch);
+    $parts['core_contradiction'] = isset($contradictionsMatch[1]) ? trim($contradictionsMatch[1]) : '';
+        
+    // 提取主体列表(支持多个空格)
+    preg_match('/###\s*主体列表\s*\n(.*?)(?=\n\s*###|$)/s', $originalContent, $subjectsMatch);
+    $rolesText = isset($subjectsMatch[1]) ? trim($subjectsMatch[1]) : '';
+    $parts['roles'] = parseRolesFromTextForAce($rolesText);
+        
+    // 提取美术风格(支持多个空格)
+    preg_match('/###\s*美术风格\s*\n(.*?)(?=\n\s*###|$)/s', $originalContent, $artStyleMatch);
+    $parts['art_style'] = isset($artStyleMatch[1]) ? trim($artStyleMatch[1]) : '';
+        
+    // 提取场景列表(支持多个空格)
+    preg_match('/###\s*场景列表\s*\n(.*?)(?=\n\s*###|$)/s', $originalContent, $scenesMatch);
+    $scenesText = isset($scenesMatch[1]) ? trim($scenesMatch[1]) : '';
+    $parts['scenes'] = parseScenesFromText($scenesText);
+
+    // 提取分集详细内容(多剧集模式,支持多个空格)
+    preg_match('/###\s*分集详细内容\s*\n(.*?)(?=\n\s*###|$)/s', $originalContent, $contentMatch);
+    $detailedContent = isset($contentMatch[1]) ? trim($contentMatch[1]) : '';
+    
+    // 提取原文内容(单剧集模式,AI生成的原文,支持多个空格)
+    preg_match('/###\s*原文内容\s*\n(.*?)(?=\n\s*###|$)/s', $originalContent, $originalContentMatch);
+    $generatedContent = isset($originalContentMatch[1]) ? trim($originalContentMatch[1]) : '';
+    
+    // 优先使用原文内容,其次使用分集详细内容
+    $parts['content'] = $generatedContent ?: $detailedContent;
+
+    $parts['episode_title'] = '';
+    $parts['acts'] = [];
+
+    // 单剧集格式:顶层是大纲信息,分镜部分采用 handleEpisodeContent 的结构标准
+    $singleStoryboard = '';
+    if (preg_match('/###\s*分镜剧本\s*\n(.*?)(?=\n\s*###[^#]|\z)/su', $originalContent, $singleStoryboardMatch)) {
+        $singleStoryboard = trim($singleStoryboardMatch[1]);
+    }
+
+    if ($singleStoryboard !== '') {
+        $episodeContent = $singleStoryboard;
+        if (!preg_match('/第\d+集[::\s]+/u', $episodeContent)) {
+            $defaultEpisodeTitle = '第1集:' . ($parts['script_name'] ?? '未命名');
+            $episodeContent = $defaultEpisodeTitle . "\n\n" . $episodeContent;
+        }
+
+        $episodeSections = [];
+        $episodeSections[] = $episodeContent;
+        if ($parts['intro'] !== '') {
+            $episodeSections[] = "###故事梗概\n" . $parts['intro'];
+        }
+        if ($parts['art_style'] !== '') {
+            $episodeSections[] = "###美术风格\n" . $parts['art_style'];
+        }
+        if ($rolesText !== '') {
+            $episodeSections[] = "###主体列表\n" . $rolesText;
+        }
+        if ($scenesText !== '') {
+            $episodeSections[] = "###场景列表\n" . $scenesText;
+        }
+        if (strpos($episodeContent, '###分镜剧本') === false) {
+            $episodeSections[] = "###分镜剧本\n" . $singleStoryboard;
+        }
+
+        $episode_arr = handleEpisodeContentForAce(implode("\n\n", $episodeSections));
+        if (!empty($episode_arr['acts'])) {
+            $parts['episode_title'] = getProp($episode_arr, 'episode_title');
+            $parts['acts'] = getProp($episode_arr, 'acts', []);
+            if (empty($parts['roles'])) {
+                $parts['roles'] = getProp($episode_arr, 'roles', []);
+            }
+            if (empty($parts['scenes'])) {
+                $parts['scenes'] = getProp($episode_arr, 'scenes', []);
+            }
+        }
+    }
+
+    if (empty($parts['acts'])) {
+        $fallbackEpisodeArr = handleEpisodeContentForAce($originalContent);
+        if (!empty($fallbackEpisodeArr['acts'])) {
+            $parts['episode_title'] = getProp($fallbackEpisodeArr, 'episode_title');
+            $parts['acts'] = getProp($fallbackEpisodeArr, 'acts', []);
+            if (empty($parts['intro'])) {
+                $parts['intro'] = getProp($fallbackEpisodeArr, 'intro');
+            }
+            if (empty($parts['art_style'])) {
+                $parts['art_style'] = getProp($fallbackEpisodeArr, 'art_style');
+            }
+            if (empty($parts['roles'])) {
+                $parts['roles'] = getProp($fallbackEpisodeArr, 'roles', []);
+            }
+            if (empty($parts['scenes'])) {
+                $parts['scenes'] = getProp($fallbackEpisodeArr, 'scenes', []);
+            }
+        }
+    }
+
+    // 多剧集格式:继续兼容旧的分集剧本结构
+    $fullScript = '';
+    if (preg_match('/###分集剧本\s*\n(.*)/su', $originalContent, $storyboardMatch)) {
+        $fullScript = trim($storyboardMatch[1]);
+    } elseif (preg_match('/##分集剧本\s*\n(.*)/su', $originalContent, $storyboardMatch)) {
+        $fullScript = trim($storyboardMatch[1]);
+    } elseif (preg_match('/分集剧本[::]\s*\n(.*)/su', $originalContent, $storyboardMatch)) {
+        $fullScript = trim($storyboardMatch[1]);
+    }
+
+    if (empty($fullScript) && preg_match('/(##分集.*)/su', $originalContent, $fallbackMatch)) {
+        $fullScript = trim($fallbackMatch[1]);
+    }
+
+    $episodes = [];
+    preg_match_all('/##分集(\d+)\s*\n分集名[::]\s*(.*?)\s*\n(?:场景描述[::].*?\n出场角色[::].*?\n台词内容[::]\s*\n)?(.*?)(?=\n##分集|$)/su', $fullScript, $matches, PREG_SET_ORDER);
+
+    foreach ($matches as $match) {
+        $episodeNumber = (int)trim($match[1]);
+        $episodeName = trim($match[2]);
+        $episodeContent = trim($match[3]);
+
+        $segments = [];
+        preg_match_all('/分镜(\d+)\s*\n(.*?)(?=\n分镜\d+|\z)/s', $episodeContent, $segmentMatches, PREG_SET_ORDER);
+        foreach ($segmentMatches as $segMatch) {
+            $segmentNumber = (int)trim($segMatch[1]);
+            $segmentContent = trim($segMatch[2]);
+
+            $segments[] = [
+                'segment_number' => $segmentNumber,
+                'segment_content' => $segmentContent,
+            ];
+        }
+
+        $episodes[] = [
+            'episode_number' => $episodeNumber,
+            'title' => $episodeName,
+            'content' => $episodeContent,
+            'segments' => $segments,
+        ];
+    }
+
+    if (empty($episodes) && !empty($parts['acts'])) {
+        $singleSegments = [];
+        foreach ($parts['acts'] as $act) {
+            $actSegments = getProp($act, 'segments', []);
+            if (!is_array($actSegments)) {
+                continue;
+            }
+
+            foreach ($actSegments as $segment) {
+                $singleSegments[] = [
+                    'segment_number' => getProp($segment, 'segment_number'),
+                    'segment_content' => getProp($segment, 'segment_content'),
+                ];
+            }
+        }
+
+        $episodeTitle = $parts['episode_title'] ?: ('第1集:' . ($parts['script_name'] ?? '未命名'));
+        $episodeName = preg_replace('/^第\d+集[::]\s*/u', '', $episodeTitle);
+        $episodes[] = [
+            'episode_number' => 1,
+            'title' => $episodeName,
+            'content' => $singleStoryboard,
+            'segments' => $singleSegments,
+        ];
+    }
+
+    $parts['episodes'] = $episodes;
+        
+    return $parts;
+}
+
+/**
+ * 将主体列表文本拆分为主体数组
+ * 
+ * @param string $rolesText 主体列表文本内容
+ * @return array 主体名称数组
+ */
+function parseRolesFromTextForAce(string $rolesText): array
+{
+    if (empty($rolesText)) {
+        return [];
+    }
+    
+    $roles = [];
+    
+    // 按行分割文本
+    $lines = explode("\n", $rolesText);
+    
+    foreach ($lines as $line) {
+        $line = trim($line);
+        if (empty($line)) {
+            continue;
+        }
+
+        if (preg_match('/^([^::]+)[::](.+)$/u', $line, $charMatch)) {
+            $role = trim($charMatch[1]);
+            $description = trim($charMatch[2]);
+            $timbreName = null;
+
+            // 检查描述末尾是否有{主体图片提示词}{{音色名}}格式
+            $picPrompt = null;
+            $timbrePrompt = null;
+            if (preg_match('/^(.*?)\{([^}]+)\}\{\{([^}]+)\}\}\s*$/u', $description, $fullMatch)) {
+                // 匹配格式:主体描述{主体图片提示词}{{音色名}}
+                $description = trim($fullMatch[1]);
+                $picPrompt = trim($fullMatch[2]);
+                $timbrePrompt = trim($fullMatch[3]);
+            } elseif (preg_match('/^(.*?)\{\{([^}]+)\}\}\s*$/u', $description, $timbreMatch)) {
+                // 兼容旧格式:主体描述{{音色名}}
+                $description = trim($timbreMatch[1]);
+                $timbrePrompt = trim($timbreMatch[2]);
+            }
+
+            $roleData = [
+                'role' => $role,
+                'description' => $description,
+            ];
+
+            // 如果有主体图片提示词,添加到数组中
+            if ($picPrompt) $roleData['pic_prompt'] = $picPrompt;
+
+            // 如果有音色提示词,添加到数组中
+            if ($timbrePrompt) $roleData['voice_prompt'] = $timbrePrompt;
+
+            if ($timbreName) {
+                $timbre = DB::table('mp_timbres')
+                    ->where('is_enabled', 1)
+                    ->where('timbre_name', 'like', "%{$timbreName}%")
+                    ->orderBy('id')
+                    ->select('timbre_type', 'audio_url')
+                    ->first();
+
+                if ($timbre) {
+                    $roleData['voice_name'] = $timbreName;
+                    $roleData['voice_type'] = getProp($timbre, 'timbre_type');
+                    $roleData['voice_audio_url'] = getProp($timbre, 'audio_url');
+                }
+            }
+            // else {
+            //     // 从描述或人物提示词中获取"男"或"女",赋予默认音色,获取不到则使用旁白音色
+            //     if ($picPrompt) {
+            //         if (strstr($picPrompt, '男')) {
+            //             $roleData['voice_name'] = '阳光青年';
+            //         }elseif (strstr($picPrompt, '女')) {
+            //             $roleData['voice_name'] = '爽快思思';
+            //         }else {
+            //             if ($description) {
+            //                 if (strstr($description, '男')) {
+            //                     $roleData['voice_name'] = '阳光青年';
+            //                 }elseif (strstr($description, '女')) {
+            //                     $roleData['voice_name'] = '爽快思思';
+            //                 }else {
+            //                     $roleData['voice_name'] = '旁白';
+            //                 }
+            //             }
+            //         }
+            //     }
+
+            //     if (!empty($roleData['voice_name'])) {
+            //         if ($roleData['voice_name'] == '旁白') {
+            //             $roleData['voice_type'] = 'zh_male_linjiananhai_moon_bigtts';
+            //             $roleData['voice_audio_url'] = 'https://zw-audiobook.tos-cn-beijing.volces.com/demonstrate/zh_male_linjiananhai_moon_bigtts.wav';
+            //         }else {
+            //             $timbre = DB::table('mp_timbres')
+            //                 ->where('is_enabled', 1)
+            //                 ->where('timbre_name', 'like', "%".$roleData['voice_name']."%")
+            //                 ->orderBy('id')
+            //                 ->select('timbre_type', 'audio_url')
+            //                 ->first();
+
+            //             if ($timbre) {
+            //                 $roleData['voice_type'] = getProp($timbre, 'timbre_type');
+            //                 $roleData['voice_audio_url'] = getProp($timbre, 'audio_url');
+            //             }
+            //         }
+            //     }
+            // }
+
+            $roles[] = $roleData;
+        }
+    }
+
+    $hasNarrator = false;
+    foreach ($roles as $role) {
+        if (getProp($role, 'role') === '旁白') {
+            $hasNarrator = true;
+            break;
+        }
+    }
+
+    if (!$hasNarrator) {
+        $roles[] = [
+            'role' => '旁白',
+            'description' => '负责叙述剧情、补充说明和情感渲染的非视觉角色。',
+            'pic_prompt' => '',
+            'voice_prompt' => '',
+            // 'voice_name' => '旁白',
+            // 'voice_type' => 'zh_male_linjiananhai_moon_bigtts',
+            // 'voice_audio_url' => 'https://zw-audiobook.tos-cn-beijing.volces.com/demonstrate/zh_male_linjiananhai_moon_bigtts.wav'
+        ];
+    }
+    
+    return $roles;
+}
+
 function handleEpisodeContentForAce($originalContent) {
     if (!$originalContent) return [];
     
@@ -2969,30 +3305,33 @@ function handleEpisodeContentForAce($originalContent) {
                 
                 // 如果有主体图片提示词,添加到数组中
                 if ($picPrompt) {
-                    // 检查开头是否有"写实,全景,正面拍摄。"
-                    $prefix = '写实,全景,正面拍摄。';
-                    $prefixParts = ['写实', '全景', '正面拍摄'];
+                    // // 检查开头是否有"写实,全景,正面拍摄。"
+                    // $prefix = '写实,全景,正面拍摄。';
+                    // $prefixParts = ['写实', '全景', '正面拍摄'];
                     
-                    // 检查是否包含完整前缀
-                    if (strpos($picPrompt, $prefix) !== 0) {
-                        // 检查是否包含部分前缀词汇
-                        $hasPartialPrefix = false;
-                        foreach ($prefixParts as $part) {
-                            if (mb_strpos($picPrompt, $part) === 0 || mb_strpos($picPrompt, $part . ',') === 0) {
-                                $hasPartialPrefix = true;
-                                break;
-                            }
-                        }
+                    // // 检查是否包含完整前缀
+                    // if (strpos($picPrompt, $prefix) !== 0) {
+                    //     // 检查是否包含部分前缀词汇(不论位置)
+                    //     $hasPartialPrefix = false;
+                    //     foreach ($prefixParts as $part) {
+                    //         if (mb_strpos($picPrompt, $part) !== false) {
+                    //             $hasPartialPrefix = true;
+                    //             break;
+                    //         }
+                    //     }
                         
-                        if ($hasPartialPrefix) {
-                            // 如果有部分前缀,移除不完整的部分并补全
-                            $picPrompt = preg_replace('/^(写实[,,]?|全景[,,]?|正面拍摄[。.]?)+/u', '', $picPrompt);
-                            $picPrompt = $prefix . preg_replace('/^[。,, ]+|[。,, ]+$/u', '', $picPrompt);
-                        } else {
-                            // 如果没有前缀,直接在开头添加
-                            $picPrompt = $prefix . $picPrompt;
-                        }
-                    }
+                    //     if ($hasPartialPrefix) {
+                    //         // 移除开头的所有前缀词汇(包括它们后面的标点)
+                    //         $picPrompt = preg_replace('/^(写实[,,、。. ]*|全景[,,、。. ]*|正面拍摄[,,、。. ]*)+/u', '', $picPrompt);
+                    //         // 清理开头和结尾的标点符号和空格
+                    //         $picPrompt = preg_replace('/^[。,,、 ]+|[。,,、 ]+$/u', '', $picPrompt);
+                    //         // 添加完整前缀
+                    //         $picPrompt = $prefix . $picPrompt;
+                    //     } else {
+                    //         // 如果没有前缀,直接在开头添加
+                    //         $picPrompt = $prefix . $picPrompt;
+                    //     }
+                    // }
 
                     // 检查是否包含"姿态:"或"姿态:"
                     if (preg_match('/姿态[::]/u', $picPrompt)) {

+ 41 - 10
app/Services/Anime/AnimeService.php

@@ -238,6 +238,7 @@ class AnimeService
             'anime_name'    => '新剧本策划',
             'is_multi'      => getProp($data, 'is_multi', 1),
             'content'       => $content,
+            'art_style_type'    => $input_art_style,
             'ace_mode'      => $ace_mode,
             'extra_products'    => json_encode($extra_products, 256),
             'created_at'    => date('Y-m-d H:i:s'),
@@ -296,6 +297,16 @@ class AnimeService
         $episode = DB::table('mp_anime_episodes')->where('id', $episode_id)->first();
         $roles = json_decode(getProp($data, 'roles'), true) ?: [];
         $art_style = getProp($episode, 'art_style');
+        $anime_id = getProp($episode, 'anime_id');
+        $anime = DB::table('mp_animes')->where('id', $anime_id)->first();
+        $art_style_type = getProp($anime, 'art_style_type');
+        $character_prefix = '';
+        $scene_prefix = '';
+        if ($art_style_type) {
+            $art_style_arr = $this->DeepSeekService->getArtStyleShortPromptByInput($art_style_type);
+            $character_prefix = isset($art_style_arr['character_prefix']) ? $art_style_arr['character_prefix'] : '';
+            $scene_prefix = isset($art_style_arr['scene_prefix']) ? $art_style_arr['scene_prefix'] : '';
+        }
         
         foreach ($roles as &$role) {
             $role_name = getProp($role, 'role');
@@ -306,7 +317,7 @@ class AnimeService
             $description = !empty($pic_prompt) ? trim((string)$pic_prompt) : getProp($role, 'description');
             
             // if ($art_style) $description = "美术风格:\n{$art_style}\n主体描述:{$description}\n图片要求:图片侧重点是人物,不要出现场景或背景,并且只能生成一个人物";
-            if ($art_style) $description = "主体描述:{$description}\n图片要求:图片侧重点是人物,不要出现场景或背景,并且只能生成一个人物";
+            $description = "{$character_prefix}。\n主体描述:{$description}\n超高清,高细节,主体突出,构图合理,画面干净,无场景或背景,单人物,无文字,无水印,无字幕,电影级光影,视觉焦点明确";
             // 参考图地址
             $ref_img_url = getProp($role, 'url');
             if ($ref_img_url) continue;     // 已有图片则跳过
@@ -355,6 +366,16 @@ class AnimeService
         $scenes = json_decode(getProp($episode, 'scenes'), true);
         $art_style = getProp($episode, 'art_style');
         $target_scene = getProp($data, 'target_scene');
+        $anime_id = getProp($episode, 'anime_id');
+        $anime = DB::table('mp_animes')->where('id', $anime_id)->first();
+        $art_style_type = getProp($anime, 'art_style_type');
+        $character_prefix = '';
+        $scene_prefix = '';
+        if ($art_style_type) {
+            $art_style_arr = $this->DeepSeekService->getArtStyleShortPromptByInput($art_style_type);
+            $character_prefix = isset($art_style_arr['character_prefix']) ? $art_style_arr['character_prefix'] : '';
+            $scene_prefix = isset($art_style_arr['scene_prefix']) ? $art_style_arr['scene_prefix'] : '';
+        }
         
         foreach ($scenes as &$scene) {
             $scene_name = getProp($scene, 'scene');
@@ -365,7 +386,7 @@ class AnimeService
             $description = !empty($pic_prompt) ? trim((string)$pic_prompt) : getProp($scene, 'description');
             
             // if ($art_style) $description = "美术风格:\n{$art_style}\n\n场景描述:{$description}\n图片要求: 图片侧重点是场景,生成的是场景环境图片,不要出现人物(此为第一优先要求)";
-            if ($art_style) $description = "场景描述:{$description}\n图片要求: 图片侧重点是场景,生成的是场景环境图片,不要出现人物(此为第一优先要求)";
+            $description = "{$scene_prefix}。\n场景描述:{$description}\n无人物,超高清,高细节,主体突出,构图合理,画面干净,无文字,无水印,无字幕,电影级光影,视觉焦点明确";
             // 参考图地址
             $ref_img_url = getProp($scene, 'url');
             if ($ref_img_url) continue;     // 已有图片则跳过
@@ -4834,6 +4855,14 @@ class AnimeService
         // 获取全局动漫的角色和场景数据
         $anime = DB::table('mp_animes')->where('id', $anime_id)->first();
         if (!$anime) return;
+        $art_style_type = getProp($anime, 'art_style_type');
+        $character_prefix = '';
+        $scene_prefix = '';
+        if ($art_style_type) {
+            $art_style_arr = $this->DeepSeekService->getArtStyleShortPromptByInput($art_style_type);
+            $character_prefix = isset($art_style_arr['character_prefix']) ? $art_style_arr['character_prefix'] : '';
+            $scene_prefix = isset($art_style_arr['scene_prefix']) ? $art_style_arr['scene_prefix'] : '';
+        }
         
         $global_roles = json_decode(getProp($anime, 'roles'), true) ?: [];
         $global_scenes = json_decode(getProp($anime, 'scenes'), true) ?: [];
@@ -4889,10 +4918,11 @@ class AnimeService
                     // 优先使用 pic_prompt,如果没有则使用 description
                     $description = !empty($role_pic_prompt) ? trim((string)$role_pic_prompt) : $role_description;
                     
-                    if ($art_style) {
-                        // $description = "美术风格:\n{$art_style}\n主体描述:{$description}\n图片要求:图片侧重点是人物,不要出现场景或背景,并且只能生成一个人物";
-                        $description = "主体描述:{$description}\n图片要求:图片侧重点是人物,不要出现场景或背景,并且只能生成一个人物";
-                    }
+                    // if ($art_style) {
+                    //     // $description = "美术风格:\n{$art_style}\n主体描述:{$description}\n图片要求:图片侧重点是人物,不要出现场景或背景,并且只能生成一个人物";
+                    //     $description = "主体描述:{$description}\n图片要求:图片侧重点是人物,不要出现场景或背景,并且只能生成一个人物";
+                    // }
+                    $description = "{$character_prefix}。\n主体描述:{$description}\n超高清,高细节,主体突出,构图合理,画面干净,无场景或背景,单人物,无文字,无水印,无字幕,电影级光影,视觉焦点明确";
                     
                     $params = [
                         'prompt' => $description,
@@ -4962,10 +4992,11 @@ class AnimeService
                     // 优先使用 pic_prompt,如果没有则使用 description
                     $description = !empty($scene_pic_prompt) ? trim((string)$scene_pic_prompt) : $scene_description;
                     
-                    if ($art_style) {
-                        // $description = "美术风格:\n{$art_style}\n场景描述:{$description}\n图片要求: 图片侧重点是场景,生成的是场景环境图片,不要出现人物(此为第一优先要求)";
-                        $description = "场景描述:{$description}\n图片要求: 图片侧重点是场景,生成的是场景环境图片,不要出现人物(此为第一优先要求)";
-                    }
+                    // if ($art_style) {
+                    //     // $description = "美术风格:\n{$art_style}\n场景描述:{$description}\n图片要求: 图片侧重点是场景,生成的是场景环境图片,不要出现人物(此为第一优先要求)";
+                    //     $description = "场景描述:{$description}\n图片要求: 图片侧重点是场景,生成的是场景环境图片,不要出现人物(此为第一优先要求)";
+                    // }
+                    $description = "{$scene_prefix}。\n场景描述:{$description}\n无人物,超高清,高细节,主体突出,构图合理,画面干净,无文字,无水印,无字幕,电影级光影,视觉焦点明确";
                     
                     $params = [
                         'prompt' => $description,

File diff suppressed because it is too large
+ 288 - 102
app/Services/DeepSeek/DeepSeekService.php