Forráskód Böngészése

1.优化台词内容输出格式2.生成视频增加音效同出字段控制3.尾帧描述格式化输入

lh 1 hónapja
szülő
commit
1fddd3b80a

+ 74 - 27
app/Http/Controllers/Anime/AnimeController.php

@@ -653,6 +653,23 @@ class AnimeController extends BaseController
         if (!$episode) Utils::throwError('20003:分集不存在');
         if (!$episode) Utils::throwError('20003:分集不存在');
         $episodeNumber = $episode->episode_number;        
         $episodeNumber = $episode->episode_number;        
         $ratio = !empty($episode->ratio) ? $episode->ratio : '9:16';
         $ratio = !empty($episode->ratio) ? $episode->ratio : '9:16';
+        $generate_audio = getProp($data, 'generate_audio', 0);
+        
+        // 处理视频模型(在循环外统一处理)
+        $model = getProp($data, 'model');
+        if (!$model) {
+            // 用户没有输入,从episode表获取
+            $model = getProp($episode, 'video_model');
+            if (!$model) {
+                // episode表也没有,使用默认值
+                $model = 'doubao-seedance-1-5-pro-251215';
+            }
+        }
+        // 验证模型是否在可用模型表中
+        if (!DB::table('mp_video_models')->where('model', $model)->where('is_enabled', 1)->exists()) {
+            $model = 'doubao-seedance-1-5-pro-251215';
+        }
+        
         // 获取所有分镜信息
         // 获取所有分镜信息
         $segments = DB::table('mp_episode_segments')
         $segments = DB::table('mp_episode_segments')
             ->where('anime_id', $animeId)
             ->where('anime_id', $animeId)
@@ -680,34 +697,66 @@ class AnimeController extends BaseController
                 $segmentContent = $segment->segment_content ?: '';
                 $segmentContent = $segment->segment_content ?: '';
                 $tailFrame = $segment->tail_frame ?: '';
                 $tailFrame = $segment->tail_frame ?: '';
                 
                 
+                // 判断 $segmentContent 是否有台词内容,如果有则确保使用中文左右双引号
+                $hasDialogue = false;
+                if (preg_match('/(?:台词内容|台词|对白|对话)[::]\s*([^\n]+)/u', $segmentContent, $dialogueMatch)) {
+                    $dialogue = trim($dialogueMatch[1]);
+                    
+                    // 如果台词不为空且不是"无"
+                    if (!empty($dialogue) && $dialogue !== '无') {
+                        // 过滤掉所有符号(引号、标点、空格等),只保留文字内容来判断是否有实际台词
+                        $dialogueFiltered = preg_replace('/[""\'\',。!?、;:…—\-\s\p{P}\p{S}]/u', '', $dialogue);
+                        if (!empty($dialogueFiltered) && $dialogueFiltered !== '无') {
+                            $hasDialogue = true;
+                        }
+                        
+                        $dialogue = preg_replace('/^["”]/u', '“', $dialogue);   // 替换句首英文双引号或中文右双引号
+                        $dialogue = preg_replace('/["“]$/u', '”', $dialogue);   // 替换句尾英文双引号或中文左双引号
+                        // 句首或句尾没有中文双引号,则分别添加中文左右双引号
+                        if (!preg_match('/^[“]/', $dialogue)) {
+                            $dialogue = '“' . $dialogue;
+                        }
+                        if (!preg_match('/[”]$/', $dialogue)) {
+                            $dialogue .= '”';
+                        }
+                        
+                        // 将修改后的台词替换回 $segmentContent
+                        $originalDialogue = $dialogueMatch[1];
+                        $segmentContent = str_replace($originalDialogue, $dialogue, $segmentContent);
+                    }
+                }
+                
+                // 检查 $segmentContent 中是否已有尾帧描述
+                $contentHasTailFrame = preg_match('/(?:尾帧描述|尾帧)[::]\s*([^\n]+)/u', $segmentContent, $contentTailFrameMatch);
+                
+                // 确定最终使用的尾帧描述(优先级:segmentContent自带 > 分镜表字段)
+                $finalTailFrame = '';
+                if ($contentHasTailFrame) {
+                    // segmentContent 中有尾帧描述,使用它
+                    $finalTailFrame = trim($contentTailFrameMatch[1]);
+                } else {
+                    // segmentContent 中没有,使用分镜表中的尾帧描述
+                    $finalTailFrame = $tailFrame;
+                }
+                
                 // 构建完整的提示词
                 // 构建完整的提示词
                 $fullPrompt = $segmentContent;
                 $fullPrompt = $segmentContent;
-                if ($tailFrame) {
-                    $fullPrompt .= "\n尾帧描述:$tailFrame";
+                if ($finalTailFrame && !$contentHasTailFrame) {
+                    // 只有当 segmentContent 中没有尾帧描述时才追加
+                    $fullPrompt .= "\n尾帧描述:$finalTailFrame";
                 }
                 }
                 
                 
+                // 去除多余的换行符(将连续多个换行符替换为单个换行符)
+                $fullPrompt = preg_replace('/\n{2,}/', "\n", $fullPrompt);
+                $fullPrompt = trim($fullPrompt);
+
+                // 当前是否启用音效同出(没台词强制关闭音效同出,节省资源收费)
+                $current_generate_audio = $hasDialogue ? $generate_audio : 0;
+                
                 // 智能选择视频时长
                 // 智能选择视频时长
                 // $videoDuration = $this->AnimeService->calculateOptimalVideoDuration($segmentContent, $tailFrame);
                 // $videoDuration = $this->AnimeService->calculateOptimalVideoDuration($segmentContent, $tailFrame);
                 $videoDuration = -1;
                 $videoDuration = -1;
                 
                 
-                // 处理视频模型(在循环外统一处理,避免重复)
-                if (!isset($model)) {
-                    $model = getProp($data, 'model');
-                    if (!$model) {
-                        // 用户没有输入,从episode表获取
-                        $episode = DB::table('mp_anime_episodes')->where('id', $episodeId)->first();
-                        $model = getProp($episode, 'video_model');
-                        if (!$model) {
-                            // episode表也没有,使用默认值
-                            $model = 'doubao-seedance-1-5-pro-251215';
-                        }
-                    }
-                    // 验证模型是否在可用模型表中
-                    if (!DB::table('mp_video_models')->where('model', $model)->where('is_enabled', 1)->exists()) {
-                        $model = 'doubao-seedance-1-5-pro-251215';
-                    }
-                }
-                
                 // 构建视频生成参数
                 // 构建视频生成参数
                 $videoParams = [
                 $videoParams = [
                     'model' => $model,
                     'model' => $model,
@@ -717,7 +766,7 @@ class AnimeController extends BaseController
                     'video_resolution' => '720P',
                     'video_resolution' => '720P',
                     'seed' => -1,
                     'seed' => -1,
                     'ratio' => $ratio,
                     'ratio' => $ratio,
-                    'generate_audio' => false,
+                    'generate_audio' => (int)$current_generate_audio === 0 ? false : true,
                     'draft' => false,
                     'draft' => false,
                     'watermark' => false,
                     'watermark' => false,
                     'camera_fixed' => false,
                     'camera_fixed' => false,
@@ -842,12 +891,10 @@ class AnimeController extends BaseController
         }
         }
         
         
         // 保存模型到episode表
         // 保存模型到episode表
-        if (isset($model)) {
-            DB::table('mp_anime_episodes')->where('id', $episodeId)->update([
-                'video_model' => $model,
-                'updated_at' => date('Y-m-d H:i:s')
-            ]);
-        }
+        DB::table('mp_anime_episodes')->where('id', $episodeId)->update([
+            'video_model' => $model,
+            'updated_at' => date('Y-m-d H:i:s')
+        ]);
         
         
         // 记录开始时间(包含创建任务的时间)
         // 记录开始时间(包含创建任务的时间)
         $startTime = time();
         $startTime = time();

+ 3 - 0
app/Libs/Helpers.php

@@ -2840,6 +2840,9 @@ function handleEpisodeContent($originalContent) {
                 }
                 }
 
 
                 $segmentData['segment_content'] = str_replace($replaceEmptyArr, '', $segmentContent);
                 $segmentData['segment_content'] = str_replace($replaceEmptyArr, '', $segmentContent);
+                // 去除多余的换行符(将连续多个换行符替换为单个换行符)
+                $segmentData['segment_content'] = preg_replace('/\n{2,}/', "\n", $segmentData['segment_content']);
+                $segmentData['segment_content'] = trim($segmentData['segment_content']);
                 
                 
                 $segments[] = $segmentData;
                 $segments[] = $segmentData;
             }
             }

+ 96 - 2
app/Services/AIGeneration/AIVideoGenerationService.php

@@ -943,8 +943,102 @@ class AIVideoGenerationService
             // 更新任务记录
             // 更新任务记录
             $task->update($updateData);
             $task->update($updateData);
             
             
-            // 更新分镜表的视频任务状态
-            $this->updateSegmentVideoStatus($task, $taskStatus, $updateData);
+            // 如果任务成功,更新分镜表
+            if ($taskStatus === MpGenerateVideoTask::STATUS_SUCCESS && isset($updateData['result_url'])) {
+                try {
+                    DB::beginTransaction();
+                    
+                    $now = date('Y-m-d H:i:s');
+                    
+                    // 获取分镜ID
+                    $segment = DB::table('mp_episode_segments')
+                        ->where('video_task_id', $task->id)
+                        ->first();
+                    
+                    if ($segment) {
+                        $segmentId = $segment->segment_id;
+                        $anime_id = $segment->anime_id;
+                        $episode_number = $segment->episode_number;
+                        $tail_frame = $segment->tail_frame;
+                        $uid = $segment->uid ?? 0;
+                        
+                        // 更新分镜表
+                        $segmentUpdateData = [
+                            'origin_video_url' => $updateData['result_url'],
+                            'video_task_status' => '已完成',
+                            'last_frame_url' => $updateData['last_frame_url'] ?? '',
+                            'current_type' => 2,
+                            'updated_at' => $now
+                        ];
+                        $segmentUpdateData['video_url'] = $updateData['compressed_url'] ?? $updateData['result_url'];
+                        
+                        // 只有当video_duration存在且大于0时才更新
+                        if (isset($data['content']['video_duration']) && $data['content']['video_duration'] > 0) {
+                            $segmentUpdateData['video_duration'] = $data['content']['video_duration'];
+                            $segmentUpdateData['video_time_point_start'] = 0;
+                            $segmentUpdateData['video_time_point_end'] = $segment->audio_duration ?? 0;
+                        }
+                        
+                        $updateResult = DB::table('mp_episode_segments')
+                            ->where('segment_id', $segmentId)
+                            ->update($segmentUpdateData);
+                        
+                        if (!$updateResult) {
+                            throw new \Exception('更新分镜表失败');
+                        }
+
+                        // 更新分镜视频成功后新增对话记录
+                        $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'     => $updateData['result_url'],
+                                'created_at'    => $now,
+                                'updated_at'    => $now
+                            ]
+                        ];
+
+                        DB::table('mp_anime_records')->insert($records);
+                        
+                        dLog('generate')->info('Seedance 分镜表更新成功: ' . $segmentId);
+                    }
+                    
+                    DB::commit();
+                    
+                } catch (\Exception $e) {
+                    DB::rollBack();
+                    
+                    $logData = [
+                        'task_id' => $task->id,
+                        'error' => $e->getMessage()
+                    ];
+                    dLog('generate')->error('Seedance 分镜表更新失败', $logData);
+                    logDB('generate', 'error', 'Seedance分镜表更新失败', $logData);
+                }
+            } elseif ($taskStatus === MpGenerateVideoTask::STATUS_FAILED) {
+                // 任务失败,更新分镜表状态
+                DB::table('mp_episode_segments')
+                    ->where('video_task_id', $task->id)
+                    ->update([
+                        'video_task_status' => '失败',
+                        'updated_at' => date('Y-m-d H:i:s')
+                    ]);
+            }
             
             
             dLog('generate')->info('Seedance 回调处理完成: ' . $taskId . ', 状态: ' . $taskStatus);
             dLog('generate')->info('Seedance 回调处理完成: ' . $taskId . ', 状态: ' . $taskStatus);
             logDB('generate', 'info', 'Seedance回调处理完成', ['task_id' => $taskId, 'status' => $taskStatus]);
             logDB('generate', 'info', 'Seedance回调处理完成', ['task_id' => $taskId, 'status' => $taskStatus]);

+ 63 - 8
app/Services/Anime/AnimeService.php

@@ -3189,6 +3189,7 @@ class AnimeService
         $tail_frame = getProp($data, 'tail_frame');
         $tail_frame = getProp($data, 'tail_frame');
         $first_frame_url = getProp($data, 'first_frame_url');
         $first_frame_url = getProp($data, 'first_frame_url');
         $tail_frame_url = getProp($data, 'tail_frame_url');
         $tail_frame_url = getProp($data, 'tail_frame_url');
+        $generate_audio = getProp($data, 'generate_audio', 0);
         
         
         if (!$segment_id) {
         if (!$segment_id) {
             Utils::throwError('1002:分镜ID不能为空');
             Utils::throwError('1002:分镜ID不能为空');
@@ -3209,17 +3210,70 @@ class AnimeService
         // 获取分镜内容
         // 获取分镜内容
         $segmentContent = getProp($segment, 'segment_content', '');
         $segmentContent = getProp($segment, 'segment_content', '');
         
         
-        // 如果没有传入尾帧描述,使用分镜表中的尾帧描述
-        if (!$tail_frame) {
-            $tail_frame = getProp($segment, 'tail_frame', '');
+        // 检查 $segmentContent 中是否已有尾帧描述
+        $contentHasTailFrame = preg_match('/(?:尾帧描述|尾帧)[::]\s*([^\n]+)/u', $segmentContent, $contentTailFrameMatch);
+        
+        // 确定最终使用的尾帧描述(优先级:用户输入 > segmentContent自带 > 分镜表字段)
+        $finalTailFrame = '';
+        if ($tail_frame) {
+            // 用户输入了尾帧描述,优先使用
+            $finalTailFrame = $tail_frame;
+            
+            // 如果 segmentContent 中已有尾帧描述,需要替换
+            if ($contentHasTailFrame) {
+                $segmentContent = preg_replace('/(?:尾帧描述|尾帧)[::]\s*[^\n]+/u', "尾帧描述:$tail_frame", $segmentContent);
+            }
+        } elseif ($contentHasTailFrame) {
+            // segmentContent 中有尾帧描述,使用它
+            $finalTailFrame = trim($contentTailFrameMatch[1]);
+        } else {
+            // 两者都没有,使用分镜表中的尾帧描述
+            $finalTailFrame = getProp($segment, 'tail_frame', '');
         }
         }
         
         
+        // 判断 $segmentContent 是否有台词内容,如果有则确保使用中文左右双引号
+        $hasDialogue = false;
+        if (preg_match('/(?:台词内容|台词|对白|对话)[::]\s*([^\n]+)/u', $segmentContent, $dialogueMatch)) {
+            $dialogue = trim($dialogueMatch[1]);
+            
+            // 如果台词不为空且不是"无"
+            if (!empty($dialogue) && $dialogue !== '无') {
+                // 过滤掉所有符号(引号、标点、空格等),只保留文字内容来判断是否有实际台词
+                $dialogueFiltered = preg_replace('/[""\'\',。!?、;:…—\-\s\p{P}\p{S}]/u', '', $dialogue);
+                if (!empty($dialogueFiltered) && $dialogueFiltered !== '无') {
+                    $hasDialogue = true;
+                }
+
+                $dialogue = preg_replace('/^["”]/u', '“', $dialogue);   // 替换句首英文双引号或中文右双引号
+                $dialogue = preg_replace('/["“]$/u', '”', $dialogue);   // 替换句尾英文双引号或中文左双引号
+                // 句首或句尾没有中文双引号,则分别添加中文左右双引号
+                if (!preg_match('/^[“]/', $dialogue)) {
+                    $dialogue = '“' . $dialogue;
+                }
+                if (!preg_match('/[”]$/', $dialogue)) {
+                    $dialogue .= '”';
+                }
+                
+                // 将修改后的台词替换回 $segmentContent
+                $originalDialogue = $dialogueMatch[1];
+                $segmentContent = str_replace($originalDialogue, $dialogue, $segmentContent);
+            }
+        }
+
         // 构建完整的提示词
         // 构建完整的提示词
         $fullPrompt = $segmentContent;
         $fullPrompt = $segmentContent;
-        if ($tail_frame) {
-            $fullPrompt .= "\n尾帧描述:$tail_frame";
+        if ($finalTailFrame && !$contentHasTailFrame) {
+            // 只有当 segmentContent 中没有尾帧描述时才追加
+            $fullPrompt .= "\n尾帧描述:$finalTailFrame";
         }
         }
         
         
+        // 去除多余的换行符(将连续多个换行符替换为单个换行符)
+        $fullPrompt = preg_replace('/\n{2,}/', "\n", $fullPrompt);
+        $fullPrompt = trim($fullPrompt);
+
+        // 当前是否启用音效同出(没台词强制关闭音效同出,节省资源收费)
+        $current_generate_audio = $hasDialogue ? $generate_audio : 0;
+        
         // 智能选择视频时长
         // 智能选择视频时长
         // $videoDuration = $this->calculateOptimalVideoDuration($segmentContent, $tail_frame);
         // $videoDuration = $this->calculateOptimalVideoDuration($segmentContent, $tail_frame);
         $videoDuration = -1;
         $videoDuration = -1;
@@ -3250,16 +3304,16 @@ class AnimeService
         $videoParams = [
         $videoParams = [
             'model' => $model,
             'model' => $model,
             'alias_segment_id' => $segment_id,
             'alias_segment_id' => $segment_id,
-            'prompt' => $fullPrompt,
+            'prompt' => trim($fullPrompt),
             'video_duration' => $videoDuration,
             'video_duration' => $videoDuration,
             'video_resolution' => '720P',
             'video_resolution' => '720P',
             'seed' => -1,
             'seed' => -1,
             'ratio' => $ratio,
             'ratio' => $ratio,
-            'generate_audio' => false,
+            'generate_audio' => (int)$current_generate_audio === 0 ? false : true,
             'draft' => false,
             'draft' => false,
             'watermark' => false,
             'watermark' => false,
             'camera_fixed' => false,
             'camera_fixed' => false,
-            'callback_url' => 'http://mpaudio.yqsd.cn/api/video/seedanceCallback'
+            // 'callback_url' => 'http://mpaudio.yqsd.cn/api/video/seedanceCallback'
         ];
         ];
         
         
         // 如果分镜有图片,作为首帧
         // 如果分镜有图片,作为首帧
@@ -3372,6 +3426,7 @@ class AnimeService
         DB::table('mp_episode_segments')->where('segment_id', $segment_id)->update([
         DB::table('mp_episode_segments')->where('segment_id', $segment_id)->update([
             'video_task_id' => $task->id,
             'video_task_id' => $task->id,
             'video_task_status' => '生成中',
             'video_task_status' => '生成中',
+            'generate_audio' => $generate_audio,
             'updated_at' => date('Y-m-d H:i:s')
             'updated_at' => date('Y-m-d H:i:s')
         ]);
         ]);