AnimeController.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. <?php
  2. namespace App\Http\Controllers\Anime;
  3. use App\Transformer\Anime\AnimeTransformer;
  4. use App\Facade\Site;
  5. use App\Consts\ErrorConst;
  6. use App\Exceptions\ApiException;
  7. use App\Libs\ApiResponse;
  8. use App\Libs\Utils;
  9. use App\Models\MpGenerateVideoTask;
  10. use App\Services\AIGeneration\AIImageGenerationService;
  11. use App\Services\AIGeneration\AIVideoGenerationService;
  12. use App\Services\Anime\AnimeService;
  13. use Illuminate\Http\Request;
  14. use Illuminate\Routing\Controller as BaseController;
  15. use Illuminate\Support\Facades\DB;
  16. use Illuminate\Support\Facades\Redis;
  17. use Illuminate\Support\Facades\Validator;
  18. class AnimeController extends BaseController
  19. {
  20. use ApiResponse;
  21. protected $AnimeService;
  22. protected $AIImageGenerationService;
  23. protected $AIVideoGenerationService;
  24. public function __construct(
  25. AnimeService $AnimeService,
  26. AIImageGenerationService $AIImageGenerationService,
  27. AIVideoGenerationService $AIVideoGenerationService
  28. ) {
  29. $this->AnimeService = $AnimeService;
  30. $this->AIImageGenerationService = $AIImageGenerationService;
  31. $this->AIVideoGenerationService = $AIVideoGenerationService;
  32. }
  33. public function textModel() {
  34. $models = [
  35. [
  36. 'model' => 'v3',
  37. 'name' => 'DeepSeek 推理',
  38. ],
  39. [
  40. 'model' => 'r1',
  41. 'name' => 'DeepSeek 思考'
  42. ],
  43. ];
  44. return $this->success($models);
  45. }
  46. // 批量生成主体图片
  47. public function batchSetRoleImg(Request $request) {
  48. // 忽略所有超时限制
  49. set_time_limit(0);
  50. ini_set('max_execution_time', '0');
  51. $data = $request->all();
  52. $result = $this->AnimeService->batchSetRoleImg($data);
  53. return $this->success($result);
  54. }
  55. // 批量生成场景图片
  56. public function batchSetSceneImg(Request $request) {
  57. // 忽略所有超时限制
  58. set_time_limit(0);
  59. ini_set('max_execution_time', '0');
  60. $data = $request->all();
  61. $result = $this->AnimeService->batchSetSceneImg($data);
  62. return $this->success($result);
  63. }
  64. // 编辑动漫
  65. public function editAnime(Request $request) {
  66. $data = $request->all();
  67. $result = $this->AnimeService->editAnime($data);
  68. return $this->success(['success'=>$result ? 1 : 0]);
  69. }
  70. // 动漫对话列表
  71. public function chatList(Request $request) {
  72. $data = $request->all();
  73. $result = $this->AnimeService->chatList($data);
  74. return $this->success($result, [new AnimeTransformer(), 'newBuildChatList']);
  75. }
  76. // 动漫对话历史记录
  77. public function chatHistory(Request $request) {
  78. $data = $request->all();
  79. $result = $this->AnimeService->chatHistory($data);
  80. return $this->success($result);
  81. }
  82. // 动漫大纲
  83. public function animeDetail(Request $request) {
  84. $data = $request->all();
  85. $result = $this->AnimeService->animeDetail($data);
  86. return $this->success($result);
  87. }
  88. // 动漫剧集
  89. public function episodeInfo(Request $request) {
  90. $data = $request->all();
  91. $result = $this->AnimeService->episodeInfo($data);
  92. return $this->success($result);
  93. }
  94. // 复制剧集副本
  95. public function copyEpisodeVersion(Request $request) {
  96. $data = $request->all();
  97. $result = $this->AnimeService->copyEpisodeVersion($data);
  98. return $this->success(['success' => $result ? 1 : 0]);
  99. }
  100. // 剧集副本列表
  101. public function episodeVersions(Request $request) {
  102. $data = $request->all();
  103. $result = $this->AnimeService->episodeVersions($data);
  104. return $this->success($result);
  105. }
  106. // 绑定剧集副本
  107. public function bindEpisodeVersion(Request $request) {
  108. $data = $request->all();
  109. $result = $this->AnimeService->bindEpisodeVersion($data);
  110. return $this->success(['success' => $result ? 1 : 0]);
  111. }
  112. // 对话改图
  113. public function chatChangeImg(Request $request) {
  114. $data = $request->all();
  115. $result = $this->AnimeService->chatChangeImg($data);
  116. return $this->success(['img_url' => $result]);
  117. }
  118. // 一键生成分镜
  119. public function batchSetSegmentPics(Request $request) {
  120. // 忽略所有超时限制
  121. set_time_limit(0);
  122. ini_set('max_execution_time', '0');
  123. $data = $request->all();
  124. $result = $this->AnimeService->batchSetSegmentPics($data);
  125. return $this->success($result);
  126. }
  127. // 重新生成分镜
  128. public function reGenerateSegment(Request $request) {
  129. // 忽略所有超时限制
  130. set_time_limit(0);
  131. ini_set('max_execution_time', '0');
  132. $data = $request->all();
  133. $result = $this->AnimeService->reGenerateSegment($data);
  134. return $this->success(['img_url'=>$result]);
  135. }
  136. // 添加分镜
  137. public function addSegment(Request $request) {
  138. // 忽略所有超时限制
  139. set_time_limit(0);
  140. ini_set('max_execution_time', '0');
  141. $data = $request->all();
  142. $result = $this->AnimeService->addSegment($data);
  143. return $this->success($result);
  144. }
  145. // 复制分镜
  146. public function copySegment(Request $request) {
  147. $data = $request->all();
  148. $result = $this->AnimeService->copySegment($data);
  149. return $this->success(['success'=>$result ? 1: 0]);
  150. }
  151. // 移动分镜
  152. public function moveSegment(Request $request) {
  153. $data = $request->all();
  154. $result = $this->AnimeService->moveSegment($data);
  155. return $this->success(['success'=>$result ? 1: 0]);
  156. }
  157. // 删除分镜
  158. public function delSegment(Request $request) {
  159. $data = $request->all();
  160. $result = $this->AnimeService->delSegment($data);
  161. return $this->success(['success'=>$result ? 1: 0]);
  162. }
  163. // 分镜历史图片|视频
  164. public function segmentHistory(Request $request) {
  165. $data = $request->all();
  166. $result = $this->AnimeService->segmentHistory($data);
  167. return $this->success($result);
  168. }
  169. public function createSegmentVideoTask(Request $request) {
  170. $data = $request->all();
  171. // 验证参数
  172. $validator = Validator::make($data, [
  173. 'segment_id' => 'required|string',
  174. 'tail_frame' => 'nullable|string|max:500',
  175. ], [
  176. 'segment_id.required' => '分镜ID不能为空',
  177. 'tail_frame.max' => '尾帧描述不能超过500个字符',
  178. ]);
  179. if ($validator->fails()) {
  180. Utils::throwError('1002:' . $validator->errors()->first());
  181. }
  182. // 创建视频生成任务
  183. $result = $this->AnimeService->createSegmentVideoTask($data);
  184. $taskId = $result['task_id'];
  185. // 设置 SSE 响应头
  186. return response()->stream(function () use ($taskId, $result) {
  187. // 设置 SSE 响应头
  188. echo "data: " . json_encode([
  189. 'type' => 'task_created',
  190. 'data' => $result
  191. ]) . "\n\n";
  192. ob_flush();
  193. flush();
  194. $startTime = time();
  195. $maxDuration = 300; // 5分钟超时
  196. $checkInterval = 5; // 每5秒检查一次
  197. while (time() - $startTime < $maxDuration) {
  198. try {
  199. // 查询任务状态
  200. $task = \App\Models\MpGenerateVideoTask::find($taskId);
  201. if (!$task) {
  202. echo "data: " . json_encode([
  203. 'type' => 'error',
  204. 'message' => '任务不存在'
  205. ]) . "\n\n";
  206. ob_flush();
  207. flush();
  208. break;
  209. }
  210. // 如果任务还在处理中,查询最新状态
  211. if ($task->status === 'processing') {
  212. $statusResult = $this->AIVideoGenerationService->querySeedanceTaskStatus($task);
  213. if (isset($statusResult['status'])) {
  214. // 更新任务状态
  215. $task->update([
  216. 'status' => $statusResult['status'],
  217. 'result_url' => $statusResult['result_url'] ?? null,
  218. 'last_frame_url' => $statusResult['last_frame_url'] ?? '',
  219. 'error_message' => $statusResult['error_message'] ?? null,
  220. 'completed_at' => in_array($statusResult['status'], [
  221. 'success',
  222. 'failed'
  223. ]) ? now() : null
  224. ]);
  225. // 如果任务成功,更新分镜表
  226. if ($statusResult['status'] === 'success' && isset($statusResult['result_url'])) {
  227. DB::table('mp_episode_segments')
  228. ->where('video_task_id', $taskId)
  229. ->update([
  230. 'video_url' => $statusResult['result_url'],
  231. 'video_task_status' => '已完成',
  232. 'last_frame_url' => $statusResult['last_frame_url'] ?? '',
  233. 'updated_at' => date('Y-m-d H:i:s')
  234. ]);
  235. } elseif ($statusResult['status'] === 'failed') {
  236. DB::table('mp_episode_segments')
  237. ->where('video_task_id', $taskId)
  238. ->update([
  239. 'video_task_status' => '失败',
  240. 'updated_at' => date('Y-m-d H:i:s')
  241. ]);
  242. }
  243. }
  244. }
  245. // 发送当前状态
  246. echo "data: " . json_encode([
  247. 'type' => 'status_update',
  248. 'data' => [
  249. 'task_id' => $task->id,
  250. 'status' => $task->status,
  251. 'result_url' => $task->result_url,
  252. 'error_message' => $task->error_message,
  253. 'progress' => $this->getTaskProgress($task->status),
  254. 'elapsed_time' => time() - $startTime
  255. ]
  256. ]) . "\n\n";
  257. ob_flush();
  258. flush();
  259. // 如果任务完成(成功或失败),结束连接
  260. if (in_array($task->status, [
  261. 'success',
  262. 'failed'
  263. ])) {
  264. echo "data: " . json_encode([
  265. 'type' => 'completed',
  266. 'data' => [
  267. 'task_id' => $task->id,
  268. 'status' => $task->status,
  269. 'result_url' => $task->result_url,
  270. 'video_url' => $task->result_url, // 兼容字段
  271. 'error_message' => $task->error_message
  272. ]
  273. ]) . "\n\n";
  274. ob_flush();
  275. flush();
  276. break;
  277. }
  278. sleep($checkInterval);
  279. } catch (\Exception $e) {
  280. echo "data: " . json_encode([
  281. 'type' => 'error',
  282. 'message' => '查询任务状态失败: ' . $e->getMessage()
  283. ]) . "\n\n";
  284. ob_flush();
  285. flush();
  286. sleep($checkInterval);
  287. }
  288. }
  289. // 超时处理
  290. if (time() - $startTime >= $maxDuration) {
  291. echo "data: " . json_encode([
  292. 'type' => 'timeout',
  293. 'message' => '任务执行超时,请稍后查询任务状态',
  294. 'data' => [
  295. 'task_id' => $taskId
  296. ]
  297. ]) . "\n\n";
  298. ob_flush();
  299. flush();
  300. }
  301. }, 200, [
  302. 'Content-Type' => 'text/event-stream',
  303. 'Cache-Control' => 'no-cache',
  304. 'Connection' => 'keep-alive',
  305. 'X-Accel-Buffering' => 'no', // 禁用 Nginx 缓冲
  306. ]);
  307. }
  308. /**
  309. * 获取任务进度百分比
  310. */
  311. private function getTaskProgress($status) {
  312. switch ($status) {
  313. case \App\Models\MpGenerateVideoTask::STATUS_PENDING:
  314. return 10;
  315. case \App\Models\MpGenerateVideoTask::STATUS_PROCESSING:
  316. return 50;
  317. case \App\Models\MpGenerateVideoTask::STATUS_SUCCESS:
  318. return 100;
  319. case \App\Models\MpGenerateVideoTask::STATUS_FAILED:
  320. return 0;
  321. default:
  322. return 0;
  323. }
  324. }
  325. // public function scriptList(Request $request) {
  326. // $data = $request->all();
  327. // $result = $this->AnimeService->scriptList($data);
  328. // return $this->success($result, [new AnimeTransformer(), 'newBuildScriptList']);
  329. // }
  330. }