VideoGenerationController.php 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. <?php
  2. namespace App\Http\Controllers\AIGeneration;
  3. use Illuminate\Routing\Controller as BaseController;
  4. use App\Libs\Utils;
  5. use App\Services\AIGeneration\AIVideoGenerationService;
  6. use App\Transformer\AIGeneration\AIGenerationTransformer;
  7. use Illuminate\Http\Request;
  8. use Illuminate\Http\JsonResponse;
  9. use Illuminate\Support\Facades\Validator;
  10. use App\Libs\ApiResponse;
  11. class VideoGenerationController extends BaseController
  12. {
  13. use ApiResponse;
  14. private $aiVideoGenerationService;
  15. public function __construct(
  16. AIVideoGenerationService $aiVideoGenerationService
  17. ) {
  18. $this->aiVideoGenerationService = $aiVideoGenerationService;
  19. }
  20. /**
  21. * 创建视频生成任务
  22. *
  23. * @param Request $request
  24. * @return JsonResponse
  25. */
  26. public function createTask(Request $request): JsonResponse
  27. {
  28. $data = $request->all();
  29. // 首先验证基本参数
  30. $baseRules = [
  31. 'prompt' => 'required|string|max:1000',
  32. 'seed' => 'required|integer',
  33. 'video_duration' => 'required|in:5,10',
  34. ];
  35. $baseMessages = [
  36. 'prompt.required' => '提示词不能为空',
  37. 'prompt.max' => '提示词不能超过1000个字符',
  38. 'video_duration.required' => '请选择视频时长',
  39. 'video_duration.in' => '视频时长只能是5s或10s',
  40. ];
  41. // 根据传入参数决定验证规则
  42. if (isset($data['first_frame_url']) && isset($data['tail_frame_url'])) {
  43. // URL 方式
  44. $validationRules = array_merge($baseRules, [
  45. 'first_frame_url' => 'required|url',
  46. 'tail_frame_url' => 'required|url',
  47. ]);
  48. $validationMessages = array_merge($baseMessages, [
  49. 'first_frame_url.required' => '首帧图片URL不能为空',
  50. 'first_frame_url.url' => '首帧图片URL格式不正确',
  51. 'tail_frame_url.required' => '尾帧图片URL不能为空',
  52. 'tail_frame_url.url' => '尾帧图片URL格式不正确',
  53. ]);
  54. } else {
  55. // 上传文件方式
  56. $validationRules = array_merge($baseRules, [
  57. 'first_frame_file' => 'required|image|mimes:jpeg,png|max:4800',
  58. 'tail_frame_file' => 'required|image|mimes:jpeg,png|max:4800',
  59. ]);
  60. $validationMessages = array_merge($baseMessages, [
  61. 'first_frame_file.required' => '首帧图片不能为空',
  62. 'first_frame_file.image' => '首帧必须是图片文件',
  63. 'first_frame_file.mimes' => '首帧图片必须是jpeg,png格式',
  64. 'first_frame_file.max' => '首帧图片大小不能超过4.7MB',
  65. 'tail_frame_file.required' => '尾帧图片不能为空',
  66. 'tail_frame_file.image' => '尾帧必须是图片文件',
  67. 'tail_frame_file.mimes' => '尾帧图片必须是jpeg,png格式',
  68. 'tail_frame_file.max' => '尾帧图片大小不能超过4.7MB',
  69. ]);
  70. }
  71. $validator = Validator::make($data, $validationRules, $validationMessages);
  72. if ($validator->fails()) {
  73. Utils::throwError('1002:'.$validator->errors()->first());
  74. }
  75. // 处理上传的图片
  76. if ($request->hasFile('first_frame_file') && $request->hasFile('tail_frame_file')) {
  77. $firstFrameFile = $request->file('first_frame_file');
  78. $tailFrameFile = $request->file('tail_frame_file');
  79. $firstFrameExtension = $firstFrameFile->getClientOriginalExtension();
  80. $tailFrameExtension = $tailFrameFile->getClientOriginalExtension();
  81. // 验证图片分辨率和比例
  82. $firstFrameInfo = @getimagesize($firstFrameFile->getPathname());
  83. $tailFrameInfo = @getimagesize($tailFrameFile->getPathname());
  84. if (!$firstFrameInfo || !$tailFrameInfo) {
  85. Utils::throwError('1003:图片格式无效');
  86. }
  87. $firstFrame = [
  88. 'width' => $firstFrameInfo[0],
  89. 'height' => $firstFrameInfo[1],
  90. 'size' => $firstFrameFile->getSize(),
  91. ];
  92. $tailFrame = [
  93. 'width' => $tailFrameInfo[0],
  94. 'height' => $tailFrameInfo[1],
  95. 'size' => $tailFrameFile->getSize(),
  96. ];
  97. // 本地保存
  98. $firstFramePath = $firstFrameFile->store('video_frames', 'public');
  99. $tailFramePath = $tailFrameFile->store('video_frames', 'public');
  100. // 获取图片绝对路径
  101. $firstFramePath = storage_path('app/public/' . $firstFramePath);
  102. $tailFramePath = storage_path('app/public/' . $tailFramePath);
  103. // 图片上传的方式需获取图片的图片文件base64编码
  104. $data['first_frame_base64'] = 'data:image/' . $firstFrameExtension . ';base64,' . base64_encode(file_get_contents($firstFramePath));
  105. $data['tail_frame_base64'] = 'data:image/' . $tailFrameExtension . ';base64,' . base64_encode(file_get_contents($tailFramePath));
  106. unlink($firstFramePath);
  107. unlink($tailFramePath);
  108. } else {
  109. // 使用URL方式,验证远程图片
  110. $firstFrame = Utils::getRemoteImageInfo($data['first_frame_url'], true);
  111. $tailFrame = Utils::getRemoteImageInfo($data['tail_frame_url'], true);
  112. if (!$firstFrame || !$tailFrame) {
  113. Utils::throwError('1003:图片获取失败');
  114. }
  115. }
  116. // 判断首尾帧图片是否满足以下条件:
  117. // 1.图片文件最大4.7MB
  118. // 2.图片分辨率最大: 4096*4096, 最短边不低于320
  119. // 3.图片长边与短边比例在3以内
  120. // 4.尾帧图片与首帧图片比例相同
  121. if ($firstFrame['size'] > 4.7 * 1024 * 1024) {
  122. Utils::throwError('1003:图片大小不能超过4.7MB');
  123. }
  124. if ($firstFrame['width'] > 4096 || $firstFrame['height'] > 4096 || $firstFrame['width'] < 320 || $firstFrame['height'] < 320) {
  125. Utils::throwError('1003:图片分辨率不能超过4096*4096,且最短边不能低于320');
  126. }
  127. if ($firstFrame['width'] / $firstFrame['height'] > 3 || $tailFrame['width'] / $tailFrame['height'] > 3) {
  128. Utils::throwError('1003:图片长边与短边比例不能超过3');
  129. }
  130. if ($firstFrame['width'] / $firstFrame['height'] != $tailFrame['width'] / $tailFrame['height']) {
  131. Utils::throwError('1003:尾帧图片与首帧图片比例相同');
  132. }
  133. dd($data);
  134. try {
  135. $task = $this->aiVideoGenerationService->createVideoGenerationTask($data);
  136. return $this->success(['task_id' => $task->task_id, 'status' => $task->status]);
  137. } catch (\Exception $e) {
  138. Utils::throwError('1001:'.$e->getMessage());
  139. }
  140. }
  141. /**
  142. * 查询视频任务状态
  143. *
  144. * @param string $taskId
  145. * @return JsonResponse
  146. */
  147. public function taskStatus(string $taskId): JsonResponse
  148. {
  149. $task = \App\Models\MpGenerateVideoTask::where('task_id', $taskId)->first();
  150. if (!$task) {
  151. return response()->json([
  152. 'success' => false,
  153. 'message' => '任务不存在'
  154. ], 404);
  155. }
  156. return response()->json([
  157. 'success' => true,
  158. 'data' => [
  159. 'task_id' => $task->task_id,
  160. 'status' => $task->status,
  161. 'result_url' => $task->result_url,
  162. 'error_message' => $task->error_message,
  163. 'created_at' => $task->created_at,
  164. 'started_at' => $task->started_at,
  165. 'completed_at' => $task->completed_at
  166. ]
  167. ]);
  168. }
  169. /**
  170. * 获取视频任务列表
  171. *
  172. * @param Request $request
  173. * @return mixed
  174. */
  175. public function taskList(Request $request)
  176. {
  177. $data = $request->all();
  178. $result = $this->aiVideoGenerationService->getTaskList($data);
  179. return $this->success($result, [new AIGenerationTransformer(), 'newBuildTaskList']);
  180. }
  181. }