Pārlūkot izejas kodu

调整剧本管理的数据结构,新增分集组别层

lh 2 mēneši atpakaļ
vecāks
revīzija
779f997fd5

+ 70 - 3
app/Http/Controllers/Anime/AnimeController.php

@@ -369,6 +369,7 @@ class AnimeController extends BaseController
     }
 
     public function createSegmentVideoTask(Request $request) {
+        $uid = Site::getUid();
         // 忽略所有超时限制
         set_time_limit(0);
         ini_set('max_execution_time', '0');
@@ -393,7 +394,7 @@ class AnimeController extends BaseController
         $taskId = $result['task_id'];
         
         // 设置 SSE 响应头
-        return response()->stream(function () use ($taskId, $result) {
+        return response()->stream(function () use ($taskId, $result, $uid) {
             // 设置 SSE 响应头
             echo "data: " . json_encode([
                 'type' => 'init',
@@ -460,12 +461,16 @@ class AnimeController extends BaseController
                                     }
                                     
                                     $segmentId = $segment->segment_id;
+                                    $anime_id = $segment->anime_id;
+                                    $episode_number = $segment->episode_number;
+                                    $tail_frame = $segment->tail_frame;
                                     
                                     // 更新分镜表
                                     $segmentUpdateData = [
                                         'video_url' => $statusResult['result_url'],
                                         'video_task_status' => '已完成',
                                         'last_frame_url' => $statusResult['last_frame_url'] ?? '',
+                                        'current_type' => 2,
                                         'updated_at' => $now
                                     ];
                                     
@@ -483,6 +488,35 @@ class AnimeController extends BaseController
                                     if (!$updateResult) {
                                         Utils::throwError('20003:更新分镜表失败');
                                     }
+
+                                    // 更新分镜视频成功后新增对话记录
+                                    // 保存对话记录
+                                    $records = [
+                                        [
+                                            'uid'           => $uid,
+                                            'anime_id'      => $anime_id,
+                                            'sequence'      => $episode_number,
+                                            'role'          => 'user',
+                                            'content'       => '图片转视频',
+                                            'segment_id'    => $segmentId,
+                                            'video_url'     => '',
+                                            'created_at'    => $now,
+                                            'updated_at'    => $now
+                                        ],
+                                        [
+                                            'uid'           => $uid,
+                                            'anime_id'      => $anime_id,
+                                            'sequence'      => $episode_number,
+                                            'role'          => 'assistant',
+                                            'content'       => $tail_frame,
+                                            'segment_id'    => $segmentId,
+                                            'video_url'     => $statusResult['result_url'],
+                                            'created_at'    => $now,
+                                            'updated_at'    => $now
+                                        ]
+                                    ];
+
+                                    DB::table('mp_anime_records')->insert($records);
                                     
                                     DB::commit();
                                     
@@ -668,7 +702,7 @@ class AnimeController extends BaseController
                     'video_duration' => $videoDuration,
                     'video_resolution' => '720P',
                     'seed' => -1,
-                    'ratio' => '16:9',
+                    'ratio' => '9:16',
                     'generate_audio' => false,
                     'draft' => false,
                     'watermark' => false,
@@ -773,6 +807,7 @@ class AnimeController extends BaseController
                     'segment_id' => $segment->segment_id,
                     'task_id' => $task->id,
                     'segment_number' => $segment->segment_number,
+                    'tail_frame' => $segment->tail_frame,
                 ];
                 
             } catch (\Exception $e) {
@@ -803,8 +838,9 @@ class AnimeController extends BaseController
         $startTime = time();
         $maxDuration = 1800; // 30分钟超时
         
+        $uid = Site::getUid();
         // 设置 SSE 响应头并开始长连接
-        return response()->stream(function () use ($taskIds, $segmentTasks, $animeId, $episodeNumber, $episodeId, $startTime, $maxDuration) {
+        return response()->stream(function () use ($taskIds, $segmentTasks, $animeId, $episodeNumber, $episodeId, $startTime, $maxDuration, $uid) {
             // 发送初始任务创建信息
             echo "data: " . json_encode([
                 'type' => 'init',
@@ -835,6 +871,7 @@ class AnimeController extends BaseController
                     foreach ($segmentTasks as $segmentTask) {
                         $taskId = $segmentTask['task_id'];
                         $segmentId = $segmentTask['segment_id'];
+                        $tail_frame = $segmentTasks['tail_frame'];
                         
                         // 跳过已完成或失败的任务
                         if (in_array($taskId, $completedTasks) || in_array($taskId, $failedTasks)) {
@@ -883,6 +920,7 @@ class AnimeController extends BaseController
                                             'video_url' => $statusResult['result_url'],
                                             'video_task_status' => '已完成',
                                             'last_frame_url' => $statusResult['last_frame_url'] ?? '',
+                                            'current_type' => 2,
                                             'updated_at' => $now
                                         ];
                                         
@@ -900,6 +938,35 @@ class AnimeController extends BaseController
                                         if (!$updateResult) {
                                             Utils::throwError('20003:更新分镜表失败');
                                         }
+
+                                        // 更新分镜视频成功后新增对话记录
+                                        // 保存对话记录
+                                        $records = [
+                                            [
+                                                'uid'           => $uid,
+                                                'anime_id'      => $animeId,
+                                                'sequence'      => $episodeNumber,
+                                                'role'          => 'user',
+                                                'content'       => '图片转视频',
+                                                'segment_id'    => $segmentId,
+                                                'video_url'     => '',
+                                                'created_at'    => $now,
+                                                'updated_at'    => $now
+                                            ],
+                                            [
+                                                'uid'           => $uid,
+                                                'anime_id'      => $animeId,
+                                                'sequence'      => $episodeNumber,
+                                                'role'          => 'assistant',
+                                                'content'       => $tail_frame,
+                                                'segment_id'    => $segmentId,
+                                                'video_url'     => $statusResult['result_url'],
+                                                'created_at'    => $now,
+                                                'updated_at'    => $now
+                                            ]
+                                        ];
+
+                                        DB::table('mp_anime_records')->insert($records);
                                         
                                         DB::commit();
                                         

+ 40 - 0
app/Http/Controllers/DeepSeek/DeepSeekController.php

@@ -278,6 +278,46 @@ class DeepSeekController extends BaseController
         return $this->success(['script_id' => $result]);
     }
 
+    // 编辑剧本
+    public function editScript(Request $request) {
+        $data = $request->all();
+        $result = $this->deepseekService->editScript($data);
+        return $this->success(['success' => $result ? 1 : 0]);
+    }
+
+    // 删除剧本
+    public function delScript(Request $request) {
+        $data = $request->all();
+        $result = $this->deepseekService->delScript($data);
+        return $this->success(['success' => $result ? 1 : 0]);
+    }
+
+    /**
+     * 创建分集组
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function createEpisode(Request $request) {
+        $data = $request->all();
+        $result = $this->deepseekService->createEpisode($data);
+        return $this->success(['script_id' => $result]);
+    }
+
+    // 编辑剧本
+    public function editEpisode(Request $request) {
+        $data = $request->all();
+        $result = $this->deepseekService->editEpisode($data);
+        return $this->success(['success' => $result ? 1 : 0]);
+    }
+
+    // 删除剧本
+    public function delEpisode(Request $request) {
+        $data = $request->all();
+        $result = $this->deepseekService->delEpisode($data);
+        return $this->success(['success' => $result ? 1 : 0]);
+    }
+
     /**
      * 与DeepSeek对话(带文件上传)
      * @param Request $request

+ 123 - 1
app/Libs/Helpers.php

@@ -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;

+ 1 - 1
app/Services/AIGeneration/AIVideoGenerationService.php

@@ -439,7 +439,7 @@ class AIVideoGenerationService
                 'execution_expires_after' => $params['execution_expires_after'] ?? 172800,
                 'generate_audio' => $params['generate_audio'] ?? false,
                 'draft' => $params['draft'] ?? false,
-                'ratio' => $params['ratio'] ?? '16:9',
+                'ratio' => $params['ratio'] ?? '9:16',
                 'duration' => $params['video_duration'] ?? 5,
                 'camera_fixed' => $params['camera_fixed'] ?? false,
                 'watermark' => $params['watermark'] ?? false,

+ 6 - 2
app/Services/Anime/AnimeService.php

@@ -138,7 +138,7 @@ class AnimeService
             $validRatios = implode('、', array_keys(BaseConst::IMAGE_RATIOS));
             Utils::throwError("1002:画面比例选择不正确,只支持{$validRatios}");
         }
-        return isset(BaseConst::IMAGE_RATIOS[$ratio]) ? BaseConst::IMAGE_RATIOS[$ratio] : ['width' => 2048, 'height' => 2048];
+        return isset(BaseConst::IMAGE_RATIOS[$ratio]) ? BaseConst::IMAGE_RATIOS[$ratio] : ['width' => 1600, 'height' => 2848];
     }
 
     public function createAnime($data) {
@@ -2697,7 +2697,11 @@ class AnimeService
             Utils::throwError('20003:该分镜没有视频,无法裁剪');
         }
 
+        $video_duration = $video_time_point_end - $video_time_point_start;
+        if (!$video_duration < 0) Utils::throwError('20003:参数异常');
+
         return DB::table('mp_episode_segments')->where('segment_id', $segment_id)->update([
+            'video_duration'            => $video_duration,
             'video_time_point_start'    => $video_time_point_start,
             'video_time_point_end'      => $video_time_point_end,
             'updated_at'                => date('Y-m-d H:i:s')
@@ -3063,7 +3067,7 @@ class AnimeService
             'video_duration' => $videoDuration,
             'video_resolution' => '720P',
             'seed' => -1,
-            'ratio' => '16:9',
+            'ratio' => '9:16',
             'generate_audio' => false,
             'draft' => false,
             'watermark' => false,

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 176 - 173
app/Services/DeepSeek/DeepSeekService.php


+ 6 - 1
routes/api.php

@@ -96,6 +96,12 @@ Route::group(['middleware' => ['bindToken', 'bindExportToken', 'checkLogin']], f
         Route::get('scripts', [DeepSeekController::class, 'scripts']);
         Route::get('scriptInfo', [DeepSeekController::class, 'scriptInfo']);
         Route::get('createScript', [DeepSeekController::class, 'createScript']);
+        Route::post('editScript', [DeepSeekController::class, 'editScript']);
+        Route::get('delScript', [DeepSeekController::class, 'delScript']);
+        Route::get('createEpisode', [DeepSeekController::class, 'createEpisode']);
+        Route::post('editEpisode', [DeepSeekController::class, 'editEpisode']);
+        Route::get('delEpisode', [DeepSeekController::class, 'delEpisode']);
+        Route::get('exportScript', [DeepSeekController::class, 'exportScript']);
         Route::post('chatWithFile', [DeepSeekController::class, 'chatWithFile']);
         Route::post('chatWithFileStream', [DeepSeekController::class, 'chatWithFileStream']);
         Route::post('saveScript', [DeepSeekController::class, 'saveScript']);
@@ -190,7 +196,6 @@ Route::group(['middleware' => ['bindToken', 'bindExportToken', 'checkLogin']], f
 
 // Route::post('addChat', [DeepSeekController::class, 'addChat']);
 // Route::post('testChat', [DeepSeekController::class, 'testChat']);
-Route::get('exportScript', [DeepSeekController::class, 'exportScript']);
 Route::get('login', [AccountController::class, 'login']); // 登录
 Route::get('logout', [AccountController::class, 'logout']); // 退出
 Route::get('sseLink', [DeepSeekController::class, 'sseLink']); // sseLink