|
|
@@ -67,12 +67,11 @@ class AnimeService
|
|
|
}
|
|
|
|
|
|
public function batchSetRoleImg($data) {
|
|
|
- $anime_id = getProp($data, 'anime_id');
|
|
|
- $episode_number = getProp($data, 'episode_number');
|
|
|
- if (!$anime_id) Utils::throwError('1002:请选择对话');
|
|
|
- $anime = DB::table('mp_animes')->where('id', $anime_id)->where('is_deleted', 0)->first();
|
|
|
- $roles = json_decode(getProp($anime, 'roles'), true);
|
|
|
- $art_style = getProp($anime, 'art_style');
|
|
|
+ $episode_id = getProp($data, 'episode_id');
|
|
|
+ if (!$episode_id) Utils::throwError('1002:请选择分集');
|
|
|
+ $episode = DB::table('mp_anime_episodes')->where('id', $episode_id)->first();
|
|
|
+ $roles = json_decode(getProp($data, 'roles'), true);
|
|
|
+ $art_style = getProp($episode, 'art_style');
|
|
|
|
|
|
foreach ($roles as &$role) {
|
|
|
$role_name = getProp($role, 'role');
|
|
|
@@ -104,8 +103,7 @@ class AnimeService
|
|
|
}
|
|
|
|
|
|
// 保存角色信息
|
|
|
- // $boolen = DB::table('mp_animes')->where('anime_id', $anime_id)->where('episode_number', $episode_number)->where('is_default', 1)->update([
|
|
|
- $boolen = DB::table('mp_animes')->where('id', $anime_id)->update([
|
|
|
+ $boolen = DB::table('mp_anime_episodes')->where('id', $episode_id)->update([
|
|
|
'roles' => json_encode($roles, 256),
|
|
|
'generate_status' => '执行中',
|
|
|
'generate_at' => date('Y-m-d H:i:s'),
|
|
|
@@ -120,15 +118,16 @@ class AnimeService
|
|
|
|
|
|
|
|
|
public function batchSetSceneImg($data) {
|
|
|
- $anime_id = getProp($data, 'anime_id');
|
|
|
- if (!$anime_id) Utils::throwError('1002:请选择对话');
|
|
|
- $episode_number = getProp($data, 'episode_number');
|
|
|
- $anime = DB::table('mp_animes')->where('id', $anime_id)->where('is_deleted', 0)->first();
|
|
|
- $scenes = json_decode(getProp($anime, 'scenes'), true);
|
|
|
- $art_style = getProp($anime, 'art_style');
|
|
|
+ $episode_id = getProp($data, 'episode_id');
|
|
|
+ if (!$episode_id) Utils::throwError('1002:请选择分集');
|
|
|
+ $episode = DB::table('mp_anime_episodes')->where('id', $episode_id)->first();
|
|
|
+ $scenes = json_decode(getProp($episode, 'scenes'), true);
|
|
|
+ $art_style = getProp($episode, 'art_style');
|
|
|
+ $target_scene = getProp($data, 'target_scene');
|
|
|
|
|
|
foreach ($scenes as &$scene) {
|
|
|
$scene_name = getProp($scene, 'scene');
|
|
|
+ if ($scene_name != $target_scene) continue;
|
|
|
$description = getProp($scene, 'description');
|
|
|
if ($art_style) $description = "美术风格:\n$art_style\n\n场景描述:$description";
|
|
|
// 参考图地址
|
|
|
@@ -156,8 +155,7 @@ class AnimeService
|
|
|
}
|
|
|
|
|
|
// 保存角色信息
|
|
|
- // $boolen = DB::table('mp_anime_episodes')->where('anime_id', $anime_id)->where('episode_number', $episode_number)->where('is_default', 1)->update([
|
|
|
- $boolen = DB::table('mp_animes')->where('id', $anime_id)->update([
|
|
|
+ $boolen = DB::table('mp_anime_episodes')->where('id', $episode_id)->update([
|
|
|
'scenes' => json_encode($scenes, 256),
|
|
|
'generate_status' => '执行中',
|
|
|
'generate_at' => date('Y-m-d H:i:s'),
|
|
|
@@ -512,7 +510,7 @@ class AnimeService
|
|
|
$segment_id = getProp($data, 'segment_id');
|
|
|
$prompt = getProp($data, 'prompt');
|
|
|
if (!$prompt || !$segment_id) {
|
|
|
- Utils::throwError('1002:请输入提示词,并且选择需修改的图片');
|
|
|
+ Utils::throwError('1002:请输入提示词,并且选择分镜');
|
|
|
}
|
|
|
$segment = DB::table("mp_episode_segments")->where('segment_id', $segment_id)->first();
|
|
|
if (!$segment) Utils::throwError('20003:该分镜不存在!');
|
|
|
@@ -2492,4 +2490,256 @@ class AnimeService
|
|
|
// 限制调整范围
|
|
|
return max(-1, min(4, $score));
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建完整视频合成任务
|
|
|
+ *
|
|
|
+ * @param array $data
|
|
|
+ * @return array
|
|
|
+ */
|
|
|
+ public function createCompleteVideoTask($data)
|
|
|
+ {
|
|
|
+ $animeId = getProp($data, 'anime_id');
|
|
|
+ $episodeId = getProp($data, 'episode_id');
|
|
|
+
|
|
|
+ // 验证参数
|
|
|
+ if (!$animeId || !$episodeId) {
|
|
|
+ Utils::throwError('20003:anime_id 和 episode_id 不能为空');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查是否已存在处理中的任务
|
|
|
+ $existingTask = DB::table('mp_complete_video_tasks')
|
|
|
+ ->where('anime_id', $animeId)
|
|
|
+ ->where('episode_id', $episodeId)
|
|
|
+ ->whereIn('generate_status', ['执行中'])
|
|
|
+ ->first();
|
|
|
+
|
|
|
+ if ($existingTask) {
|
|
|
+ Utils::throwError('20003:该剧集已有正在处理的视频合成任务');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取所有分镜的配音视频,按 segment_number 排序
|
|
|
+ $segments = DB::table('mp_episode_segments')
|
|
|
+ ->where('anime_id', $animeId)
|
|
|
+ ->where('episode_id', $episodeId)
|
|
|
+ ->orderBy('segment_number')
|
|
|
+ ->select('segment_id', 'segment_number', 'dub_video_url')
|
|
|
+ ->get();
|
|
|
+
|
|
|
+ if ($segments->isEmpty()) {
|
|
|
+ Utils::throwError('20003:未找到该剧集的分镜数据');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查是否所有分镜都有配音视频
|
|
|
+ $missingVideos = $segments->filter(function($segment) {
|
|
|
+ return empty($segment->dub_video_url);
|
|
|
+ });
|
|
|
+
|
|
|
+ if ($missingVideos->isNotEmpty()) {
|
|
|
+ Utils::throwError('20003:存在未完成配音的分镜,请确保所有分镜都已完成配音');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建 generate_json
|
|
|
+ $generateJson = [];
|
|
|
+ $sequence = 1;
|
|
|
+ foreach ($segments as $segment) {
|
|
|
+ $generateJson[] = [
|
|
|
+ 'sequence' => $sequence++,
|
|
|
+ 'dub_video_url' => $segment->dub_video_url,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建任务
|
|
|
+ $now = date('Y-m-d H:i:s');
|
|
|
+ $taskId = DB::table('mp_complete_video_tasks')->insertGetId([
|
|
|
+ 'anime_id' => $animeId,
|
|
|
+ 'episode_id' => $episodeId,
|
|
|
+ 'generate_json' => json_encode($generateJson, 256),
|
|
|
+ 'generate_status' => '执行中',
|
|
|
+ 'complete_video_url' => '',
|
|
|
+ 'created_at' => $now,
|
|
|
+ 'updated_at' => $now
|
|
|
+ ]);
|
|
|
+
|
|
|
+ dLog('anime')->info('创建完整视频合成任务', [
|
|
|
+ 'task_id' => $taskId,
|
|
|
+ 'anime_id' => $animeId,
|
|
|
+ 'episode_id' => $episodeId,
|
|
|
+ 'segment_count' => count($generateJson)
|
|
|
+ ]);
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'task_id' => $taskId,
|
|
|
+ 'anime_id' => $animeId,
|
|
|
+ 'episode_id' => $episodeId,
|
|
|
+ 'segment_count' => count($generateJson),
|
|
|
+ 'generate_status' => '执行中'
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ public function setRoleOrScene($data) {
|
|
|
+ $anime_id = getProp($data, 'anime_id');
|
|
|
+ $episode_id = getProp($data, 'episode_id');
|
|
|
+ $json_data = getProp($data, 'json_data');
|
|
|
+
|
|
|
+ // 参数验证
|
|
|
+ if (!$anime_id) Utils::throwError('20003:请提供动漫ID');
|
|
|
+ if (!$episode_id) Utils::throwError('20003:请提供剧集ID');
|
|
|
+ if (!$json_data) Utils::throwError('20003:请提供json_data数据');
|
|
|
+
|
|
|
+ // 验证剧集是否存在
|
|
|
+ $episode = DB::table('mp_anime_episodes')
|
|
|
+ ->where('anime_id', $anime_id)
|
|
|
+ ->where('id', $episode_id)
|
|
|
+ ->first();
|
|
|
+
|
|
|
+ if (!$episode) Utils::throwError('20003:该剧集不存在');
|
|
|
+
|
|
|
+ // 判断是 role 还是 scene
|
|
|
+ $isRole = isset($json_data['role']);
|
|
|
+ $isScene = isset($json_data['scene']);
|
|
|
+
|
|
|
+ if (!$isRole && !$isScene) {
|
|
|
+ Utils::throwError('20003:json_data必须包含role或scene字段');
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($isRole && $isScene) {
|
|
|
+ Utils::throwError('20003:json_data只能包含role或scene其中一个');
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 获取当前的 roles 或 scenes
|
|
|
+ $fieldName = $isRole ? 'roles' : 'scenes';
|
|
|
+ $currentData = json_decode($episode->$fieldName, true) ?: [];
|
|
|
+
|
|
|
+ // 查找匹配的项
|
|
|
+ $targetName = $isRole ? $json_data['role'] : $json_data['scene'];
|
|
|
+ $foundIndex = -1;
|
|
|
+
|
|
|
+ foreach ($currentData as $index => $item) {
|
|
|
+ $itemName = $isRole ? ($item['role'] ?? '') : ($item['scene'] ?? '');
|
|
|
+ if ($itemName === $targetName) {
|
|
|
+ $foundIndex = $index;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($foundIndex === -1) {
|
|
|
+ Utils::throwError('20003:未找到匹配的' . ($isRole ? '角色' : '场景'));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查是否有 url
|
|
|
+ $url = getProp($json_data, 'url', '');
|
|
|
+
|
|
|
+ if (empty($url)) {
|
|
|
+ // 没有 url,需要生成图片
|
|
|
+ $description = getProp($json_data, 'description', '');
|
|
|
+ if (empty($description)) {
|
|
|
+ Utils::throwError('20003:缺少description字段,无法生成图片');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取艺术风格
|
|
|
+ $art_style = $episode->art_style ?? '';
|
|
|
+
|
|
|
+ // 构建提示词
|
|
|
+ $prompt = $description;
|
|
|
+ if ($art_style) {
|
|
|
+ $prompt = $art_style . ',' . $prompt;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建图片生成任务
|
|
|
+ $params = [
|
|
|
+ 'prompt' => $prompt,
|
|
|
+ 'ref_img_urls' => [],
|
|
|
+ 'width' => 2048,
|
|
|
+ 'height' => 2048
|
|
|
+ ];
|
|
|
+
|
|
|
+ $task = $this->aiImageGenerationService->createImageGenerationTask($params);
|
|
|
+ $task_id = $task->id;
|
|
|
+
|
|
|
+ if (!$task_id) {
|
|
|
+ Utils::throwError('20003:创建图片生成任务失败');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 轮询获取结果(3分钟超时,每3秒查询一次)
|
|
|
+ $start_time = time();
|
|
|
+ $timeout = 180; // 3分钟
|
|
|
+ $img_url = '';
|
|
|
+
|
|
|
+ while (time() - $start_time < $timeout) {
|
|
|
+ sleep(3);
|
|
|
+
|
|
|
+ $task = MpGeneratePicTask::where('id', $task_id)->first();
|
|
|
+ $statusInfo = $this->aiImageGenerationService->queryTaskStatus($task);
|
|
|
+
|
|
|
+ if ($statusInfo['status'] === 'success') {
|
|
|
+ $task->updateStatus('success', [
|
|
|
+ 'result_url' => $statusInfo['result_url'],
|
|
|
+ 'result_json' => $statusInfo['result_json'] ?? []
|
|
|
+ ]);
|
|
|
+
|
|
|
+ $img_url = $statusInfo['result_url'][0];
|
|
|
+ break;
|
|
|
+ } elseif ($statusInfo['status'] === 'failed') {
|
|
|
+ $task->updateStatus('failed', [
|
|
|
+ 'error_message' => $statusInfo['error_message'],
|
|
|
+ 'result_json' => $statusInfo['result_json'] ?? []
|
|
|
+ ]);
|
|
|
+
|
|
|
+ Utils::throwError('20003:图片生成失败: ' . ($statusInfo['error_message'] ?? '未知错误'));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (empty($img_url)) {
|
|
|
+ Utils::throwError('20003:图片生成超时,请稍后重试');
|
|
|
+ }
|
|
|
+
|
|
|
+ $url = $img_url;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新数据
|
|
|
+ $json_data['url'] = $url;
|
|
|
+ $currentData[$foundIndex] = $json_data;
|
|
|
+
|
|
|
+ // 保存到数据库
|
|
|
+ $result = DB::table('mp_anime_episodes')
|
|
|
+ ->where('anime_id', $anime_id)
|
|
|
+ ->where('id', $episode_id)
|
|
|
+ ->update([
|
|
|
+ $fieldName => json_encode($currentData, 256),
|
|
|
+ 'updated_at' => date('Y-m-d H:i:s')
|
|
|
+ ]);
|
|
|
+
|
|
|
+ if ($result === false) {
|
|
|
+ Utils::throwError('20003:更新' . ($isRole ? '角色' : '场景') . '失败');
|
|
|
+ }
|
|
|
+
|
|
|
+ dLog('anime')->info('更新' . ($isRole ? '角色' : '场景') . '成功', [
|
|
|
+ 'anime_id' => $anime_id,
|
|
|
+ 'episode_id' => $episode_id,
|
|
|
+ 'type' => $isRole ? 'role' : 'scene',
|
|
|
+ 'name' => $targetName,
|
|
|
+ 'url' => $url
|
|
|
+ ]);
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'json_data' => $currentData[$foundIndex]
|
|
|
+ ];
|
|
|
+
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ dLog('anime')->error('更新角色或场景失败', [
|
|
|
+ 'anime_id' => $anime_id,
|
|
|
+ 'episode_id' => $episode_id,
|
|
|
+ 'error' => $e->getMessage()
|
|
|
+ ]);
|
|
|
+
|
|
|
+ // 如果是已知错误,直接抛出
|
|
|
+ if (strpos($e->getMessage(), '20003:') === 0) {
|
|
|
+ throw $e;
|
|
|
+ }
|
|
|
+
|
|
|
+ Utils::throwError('20003:更新失败: ' . $e->getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|