aiVideoGenerationService = $aiVideoGenerationService; } /** * 创建视频生成任务 * * @param Request $request * @return JsonResponse */ public function createTask(Request $request): JsonResponse { $data = $request->all(); // 首先验证基本参数 $baseRules = [ 'prompt' => 'required|string|max:1000', 'seed' => 'required|integer', 'video_duration' => 'required|in:5,10', 'video_resolution' => 'required|in:720P,1080P' ]; $baseMessages = [ 'prompt.required' => '提示词不能为空', 'prompt.max' => '提示词不能超过1000个字符', 'seed.required' => '随机种子不能为空', 'seed.integer' => '随机种子必须是整数', 'video_duration.required' => '请选择视频时长', 'video_duration.in' => '视频时长只能是5s或10s', 'video_resolution.required' => '请选择视频分辨率', 'video_resolution.in' => '视频分辨率只能是720P或1080P', ]; // 动态构建验证规则 $validationRules = $baseRules; $validationMessages = $baseMessages; // 检查是否有首帧URL if (isset($data['first_frame_url'])) { $validationRules['first_frame_url'] = 'url'; $validationMessages['first_frame_url.url'] = '首帧图片URL格式不正确'; } // 检查是否有尾帧URL if (isset($data['tail_frame_url'])) { $validationRules['tail_frame_url'] = 'url'; $validationMessages['tail_frame_url.url'] = '尾帧图片URL格式不正确'; } // 检查是否有首帧文件 if ($request->hasFile('first_frame_file')) { $validationRules['first_frame_file'] = 'image|mimes:jpeg,png|max:4800'; $validationMessages = array_merge($validationMessages, [ 'first_frame_file.image' => '首帧必须是图片文件', 'first_frame_file.mimes' => '首帧图片必须是jpeg,png格式', 'first_frame_file.max' => '首帧图片大小不能超过4.7MB', ]); } // 检查是否有尾帧文件 if ($request->hasFile('tail_frame_file')) { $validationRules['tail_frame_file'] = 'image|mimes:jpeg,png|max:4800'; $validationMessages = array_merge($validationMessages, [ 'tail_frame_file.image' => '尾帧必须是图片文件', 'tail_frame_file.mimes' => '尾帧图片必须是jpeg,png格式', 'tail_frame_file.max' => '尾帧图片大小不能超过4.7MB', ]); } if (isset($data['tail_frame_url']) && !isset($data['first_frame_url'])) { Utils::throwError('1002:提供尾帧URL时必须提供首帧URL'); } if ($request->hasFile('tail_frame_file') && !$request->has('first_frame_file')) { Utils::throwError('1002:提供尾帧文件时必须提供首帧文件'); } $validator = Validator::make($data, $validationRules, $validationMessages); if ($validator->fails()) { Utils::throwError('1002:'.$validator->errors()->first()); } $firstFrame = null; $tailFrame = null; // 处理首帧图片(文件上传方式) if ($request->hasFile('first_frame_file')) { $firstFrameFile = $request->file('first_frame_file'); $firstFrame = $this->processFrameFile($firstFrameFile, 'first'); $data['first_frame_url'] = $firstFrame['url']; } // 处理首帧图片(URL方式) elseif (isset($data['first_frame_url']) && !empty($data['first_frame_url'])) { $firstFrame = Utils::getRemoteImageInfo($data['first_frame_url'], true); if (!$firstFrame) { Utils::throwError('1003:首帧图片获取失败'); } } // 处理尾帧图片(文件上传方式) if ($request->hasFile('tail_frame_file')) { $tailFrameFile = $request->file('tail_frame_file'); $tailFrame = $this->processFrameFile($tailFrameFile, 'tail'); $data['tail_frame_url'] = $tailFrame['url']; } // 处理尾帧图片(URL方式) elseif (isset($data['tail_frame_url']) && !empty($data['tail_frame_url'])) { $tailFrame = Utils::getRemoteImageInfo($data['tail_frame_url'], true); if (!$tailFrame) { Utils::throwError('1003:尾帧图片获取失败'); } } // 验证首帧图片规格(如果存在) if ($firstFrame) { $this->validateFrameSpecs($firstFrame, '首帧'); } // 验证尾帧图片规格(如果存在) if ($tailFrame) { $this->validateFrameSpecs($tailFrame, '尾帧'); } // 如果首尾帧都存在,验证比例是否相同 if ($firstFrame && $tailFrame) { $firstRatio = round($firstFrame['width'] / $firstFrame['height'], 2); $tailRatio = round($tailFrame['width'] / $tailFrame['height'], 2); if ($firstRatio != $tailRatio) { Utils::throwError('1003:尾帧图片与首帧图片比例必须相同'); } } try { $task = $this->aiVideoGenerationService->createVideoGenerationTask($data); return $this->success(['task_id' => $task->task_id, 'status' => $task->status]); } catch (\Exception $e) { Utils::throwError('1001:'.$e->getMessage()); } } /** * 查询视频任务状态 * * @param string $taskId * @return JsonResponse */ public function taskStatus(string $taskId): JsonResponse { $task = \App\Models\MpGenerateVideoTask::where('task_id', $taskId)->first(); if (!$task) { return response()->json([ 'success' => false, 'message' => '任务不存在' ], 404); } return response()->json([ 'success' => true, 'data' => [ 'task_id' => $task->task_id, 'status' => $task->status, 'result_url' => $task->result_url, 'error_message' => $task->error_message, 'created_at' => $task->created_at, 'started_at' => $task->started_at, 'completed_at' => $task->completed_at ] ]); } /** * 处理帧图片文件 * * @param \Illuminate\Http\UploadedFile $file * @param string $frameType 帧类型(first/tail) * @return array */ private function processFrameFile($file, string $frameType): array { // 验证图片信息 $frameInfo = @getimagesize($file->getPathname()); if (!$frameInfo) { Utils::throwError('1003:' . ($frameType === 'first' ? '首帧' : '尾帧') . '图片格式无效'); } $frameData = [ 'width' => $frameInfo[0], 'height' => $frameInfo[1], 'size' => $file->getSize(), ]; // 上传图片到服务器 try { $frameData['url'] = uploadFileByTos('image', $file); } catch (\Exception $e) { Utils::throwError('1003:' . ($frameType === 'first' ? '首帧' : '尾帧') . '图片上传失败:' . $e->getMessage()); } return $frameData; } /** * 验证帧图片规格 * * @param array $frame * @param string $frameName * @return void */ private function validateFrameSpecs(array $frame, string $frameName): void { // 1.图片文件最大4.7MB if ($frame['size'] > 4.7 * 1024 * 1024) { Utils::throwError('1003:' . $frameName . '图片大小不能超过4.7MB'); } // 2.图片分辨率最大: 4096*4096, 最短边不低于320 $minDimension = min($frame['width'], $frame['height']); $maxDimension = max($frame['width'], $frame['height']); if ($maxDimension > 4096 || $minDimension < 320) { Utils::throwError('1003:' . $frameName . '图片分辨率不能超过4096*4096,且最短边不能低于320'); } // 3.图片长边与短边比例在3以内 if ($maxDimension / $minDimension > 3) { Utils::throwError('1003:' . $frameName . '图片长边与短边比例不能超过3'); } } /** * 获取视频任务列表 * * @param Request $request * @return mixed */ public function taskList(Request $request) { $data = $request->all(); $result = $this->aiVideoGenerationService->getTaskList($data); return $this->success($result, [new AIGenerationTransformer(), 'newBuildTaskList']); } }