|
|
@@ -1,5 +1,6 @@
|
|
|
<?php
|
|
|
|
|
|
+use App\Consts\BaseConst;
|
|
|
use GuzzleHttp\Client;
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
use Illuminate\Support\Facades\Log;
|
|
|
@@ -1996,6 +1997,127 @@ function handleScriptWords($text, $enable_emotion=1) {
|
|
|
];
|
|
|
}
|
|
|
|
|
|
+function extractScriptContent($originalContent) {
|
|
|
+ if (!$originalContent) return [];
|
|
|
+
|
|
|
+ // 使用更精确的正则表达式分割内容
|
|
|
+ $parts = [];
|
|
|
+
|
|
|
+ // 提取剧本名(###剧本名:后面的内容,支持多个空格)
|
|
|
+ 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'] = parseRolesFromText($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;
|
|
|
+
|
|
|
+ // 解析分集剧本内容
|
|
|
+ $episodes = [];
|
|
|
+ if ($detailedContent) {
|
|
|
+ // 按 ##分集 分割
|
|
|
+ preg_match_all('/##\s*分集(\d+)\s*\n(.*?)(?=\n\s*##分集|\z)/s', $detailedContent, $episodeMatches, PREG_SET_ORDER);
|
|
|
+
|
|
|
+ foreach ($episodeMatches as $episodeMatch) {
|
|
|
+ $episodeNum = $episodeMatch[1];
|
|
|
+ $episodeContent = trim($episodeMatch[2]);
|
|
|
+
|
|
|
+ $episode = [
|
|
|
+ 'episode_number' => $episodeNum,
|
|
|
+ 'episode_content' => $episodeContent,
|
|
|
+ 'episode_name' => '',
|
|
|
+ 'scene_description' => '',
|
|
|
+ 'camera_movement' => '',
|
|
|
+ 'characters' => '',
|
|
|
+ 'dialogues' => []
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 提取分集名(格式:##分集01第一集: 第一集的标题)
|
|
|
+ if (preg_match('/^第.*?集\s*[::]\s*(.+?)(?=\n|$)/u', $episodeContent, $nameMatch)) {
|
|
|
+ $episode['episode_name'] = trim($nameMatch[1]);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 提取场景描述
|
|
|
+ if (preg_match('/场景描述\s*[::]\s*(.+?)(?=\n|$)/u', $episodeContent, $sceneMatch)) {
|
|
|
+ $episode['scene_description'] = trim($sceneMatch[1]);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 提取运镜
|
|
|
+ if (preg_match('/运镜\s*[::]\s*(.+?)(?=\n|$)/u', $episodeContent, $cameraMatch)) {
|
|
|
+ $episode['camera_movement'] = trim($cameraMatch[1]);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 提取出场角色
|
|
|
+ if (preg_match('/出场角色\s*[::]\s*(.+?)(?=\n|$)/u', $episodeContent, $charactersMatch)) {
|
|
|
+ $episode['characters'] = trim($charactersMatch[1]);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 提取台词内容
|
|
|
+ if (preg_match('/台词内容\s*[::]\s*\n(.*?)$/su', $episodeContent, $dialoguesMatch)) {
|
|
|
+ $dialoguesText = trim($dialoguesMatch[1]);
|
|
|
+ $dialogueLines = explode("\n", $dialoguesText);
|
|
|
+
|
|
|
+ foreach ($dialogueLines as $line) {
|
|
|
+ $line = trim($line);
|
|
|
+ if (empty($line)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 匹配格式:角色名: 对话内容 或 角色名:对话内容
|
|
|
+ if (preg_match('/^(.+?)\s*[::]\s*(.+)$/u', $line, $dialogueMatch)) {
|
|
|
+ $episode['dialogues'][] = [
|
|
|
+ 'character' => trim($dialogueMatch[1]),
|
|
|
+ 'text' => trim($dialogueMatch[2])
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $episodes[] = $episode;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $parts['episodes'] = $episodes;
|
|
|
+
|
|
|
+ return $parts;
|
|
|
+}
|
|
|
+
|
|
|
|
|
|
/**
|
|
|
* 处理剧本内容
|
|
|
@@ -2495,7 +2617,7 @@ function compressRemoteImageUrlToSize(string $url, int $maxBytes = 3 * 1024 * 10
|
|
|
|
|
|
if ($aspectRatio !== null) {
|
|
|
// 解析长宽比,如 "16:9" -> [16, 9]
|
|
|
- $validRatios = ["16:9", "4:3", "1:1", "3:4", "9:16", "21:9"];
|
|
|
+ $validRatios = array_keys(BaseConst::IMAGE_RATIOS);
|
|
|
if (in_array($aspectRatio, $validRatios)) {
|
|
|
list($ratioW, $ratioH) = explode(':', $aspectRatio);
|
|
|
$ratioW = (float)$ratioW;
|