|
|
@@ -235,6 +235,13 @@ class AIVideoGenerationService
|
|
|
// 将视频另存到tos
|
|
|
$url = uploadStreamByTos('video', file_get_contents($result['video_url']), $video_name);
|
|
|
$returnData['result_url'] = $url;
|
|
|
+
|
|
|
+ // 视频分辨率标准化(压缩前处理,避免压缩后重复上传)
|
|
|
+ $targetResolution = $this->getTaskTargetResolution($task);
|
|
|
+ if ($targetResolution) {
|
|
|
+ $url = $this->ensureStandardVideoResolution($url, $targetResolution);
|
|
|
+ $returnData['result_url'] = $url;
|
|
|
+ }
|
|
|
|
|
|
// 压缩视频
|
|
|
$compressed_video_url = compressVideo($url);
|
|
|
@@ -1110,6 +1117,7 @@ class AIVideoGenerationService
|
|
|
|
|
|
// 使用查询任务时已压缩的视频URL,避免重复压缩
|
|
|
$segmentUpdateData['video_url'] = $statusInfo['compressed_url'] ?? $statusInfo['result_url'];
|
|
|
+
|
|
|
|
|
|
// 只有当video_duration存在且大于0时才更新
|
|
|
if (isset($statusInfo['video_duration']) && $statusInfo['video_duration'] > 0) {
|
|
|
@@ -1260,6 +1268,13 @@ class AIVideoGenerationService
|
|
|
$video_name = $video_name . $video_ext;
|
|
|
$url = uploadStreamByTos('video', file_get_contents($video_url), $video_name);
|
|
|
$updateData['result_url'] = $url;
|
|
|
+
|
|
|
+ // 视频分辨率标准化(压缩前处理,避免压缩后重复上传)
|
|
|
+ $targetResolution = $this->getTaskTargetResolution($task);
|
|
|
+ if ($targetResolution) {
|
|
|
+ $url = $this->ensureStandardVideoResolution($url, $targetResolution);
|
|
|
+ $updateData['result_url'] = $url;
|
|
|
+ }
|
|
|
|
|
|
// 压缩视频
|
|
|
$compressed_video_url = compressVideo($url);
|
|
|
@@ -1408,6 +1423,13 @@ class AIVideoGenerationService
|
|
|
$video_name = $video_name . $video_ext;
|
|
|
$url = uploadStreamByTos('video', file_get_contents($video_url), $video_name);
|
|
|
$updateData['result_url'] = $url;
|
|
|
+
|
|
|
+ // 视频分辨率标准化(压缩前处理,避免压缩后重复上传)
|
|
|
+ $targetResolution = $this->getTaskTargetResolution($task);
|
|
|
+ if ($targetResolution) {
|
|
|
+ $url = $this->ensureStandardVideoResolution($url, $targetResolution);
|
|
|
+ $updateData['result_url'] = $url;
|
|
|
+ }
|
|
|
$compressed_video_url = compressVideo($url);
|
|
|
$updateData['compressed_url'] = $compressed_video_url ?: $url;
|
|
|
|
|
|
@@ -1891,6 +1913,13 @@ class AIVideoGenerationService
|
|
|
$video_name = $video_name . $video_ext;
|
|
|
$url = uploadStreamByTos('video', file_get_contents($video_url), $video_name);
|
|
|
$updateData['result_url'] = $url;
|
|
|
+
|
|
|
+ // 视频分辨率标准化(压缩前处理,避免压缩后重复上传)
|
|
|
+ $targetResolution = $this->getTaskTargetResolution($task);
|
|
|
+ if ($targetResolution) {
|
|
|
+ $url = $this->ensureStandardVideoResolution($url, $targetResolution);
|
|
|
+ $updateData['result_url'] = $url;
|
|
|
+ }
|
|
|
|
|
|
// 压缩视频
|
|
|
$compressed_video_url = compressVideo($url);
|
|
|
@@ -2015,6 +2044,7 @@ class AIVideoGenerationService
|
|
|
|
|
|
// 使用查询任务时已压缩的视频URL,避免重复压缩
|
|
|
$segmentUpdateData['video_url'] = $statusInfo['compressed_url'] ?? $statusInfo['result_url'];
|
|
|
+
|
|
|
|
|
|
// 只有当video_duration存在且大于0时才更新
|
|
|
if (isset($statusInfo['video_duration']) && $statusInfo['video_duration'] > 0) {
|
|
|
@@ -2989,6 +3019,13 @@ class AIVideoGenerationService
|
|
|
// 超分成功
|
|
|
dLog('generate')->info('视频超分成功', ['enhanced_url' => $url]);
|
|
|
$returnData['result_url'] = $url;
|
|
|
+
|
|
|
+ // 视频分辨率标准化(压缩前处理,避免压缩后重复上传)
|
|
|
+ $targetResolution = $this->getTaskTargetResolution($task);
|
|
|
+ if ($targetResolution) {
|
|
|
+ $url = $this->ensureStandardVideoResolution($url, $targetResolution);
|
|
|
+ $returnData['result_url'] = $url;
|
|
|
+ }
|
|
|
|
|
|
// 压缩视频
|
|
|
$compressed_video_url = compressVideo($url);
|
|
|
@@ -3001,6 +3038,13 @@ class AIVideoGenerationService
|
|
|
$video_name = $video_name . $video_ext;
|
|
|
$url = uploadStreamByTos('video', $videoContent, $video_name);
|
|
|
$returnData['result_url'] = $url;
|
|
|
+
|
|
|
+ // 视频分辨率标准化(压缩前处理,避免压缩后重复上传)
|
|
|
+ $targetResolution = $this->getTaskTargetResolution($task);
|
|
|
+ if ($targetResolution) {
|
|
|
+ $url = $this->ensureStandardVideoResolution($url, $targetResolution);
|
|
|
+ $returnData['result_url'] = $url;
|
|
|
+ }
|
|
|
|
|
|
// 压缩视频
|
|
|
$compressed_video_url = compressVideo($url);
|
|
|
@@ -3131,6 +3175,7 @@ class AIVideoGenerationService
|
|
|
|
|
|
// 使用查询任务时已压缩的视频URL,避免重复压缩
|
|
|
$segmentUpdateData['video_url'] = $statusInfo['compressed_url'] ?? $statusInfo['result_url'];
|
|
|
+
|
|
|
|
|
|
// 只有当video_duration存在且大于0时才更新
|
|
|
if (isset($statusInfo['video_duration']) && $statusInfo['video_duration'] > 0) {
|
|
|
@@ -3227,6 +3272,175 @@ class AIVideoGenerationService
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
+ * 确保视频为标准分辨率(720p -> 1280*720 / 720*1280,1080p -> 1920*1080 / 1080*1920)
|
|
|
+ *
|
|
|
+ * 下载视频并探测实际分辨率,若属于目标档位但不是标准尺寸,则强制转码为标准分辨率
|
|
|
+ * 并上传TOS返回新地址;已经是标准分辨率、非目标档位或处理失败时返回原地址。
|
|
|
+ *
|
|
|
+ * @param string $videoUrl 视频URL
|
|
|
+ * @param string $targetResolution 目标档位:'720p' 或 '1080p'(其他值不处理)
|
|
|
+ * @return string 规范化后的视频URL(失败返回原URL)
|
|
|
+ */
|
|
|
+ private function ensureStandardVideoResolution($videoUrl, $targetResolution = '720p')
|
|
|
+ {
|
|
|
+ if (empty($videoUrl) || !in_array($targetResolution, ['720p', '1080p'])) {
|
|
|
+ return $videoUrl;
|
|
|
+ }
|
|
|
+
|
|
|
+ $tempDir = storage_path('app/temp/video_standard');
|
|
|
+ $inputFile = '';
|
|
|
+ $outputFile = '';
|
|
|
+
|
|
|
+ try {
|
|
|
+ if (!is_dir($tempDir)) {
|
|
|
+ mkdir($tempDir, 0775, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 1. 下载视频到临时目录
|
|
|
+ $uniqueId = uniqid('std_res_') . bin2hex(random_bytes(4));
|
|
|
+ $inputFile = $tempDir . '/' . $uniqueId . '_input' . getVideoExtFromUrl($videoUrl);
|
|
|
+ $outputFile = $tempDir . '/' . $uniqueId . '_output.mp4';
|
|
|
+
|
|
|
+ $client = new Client(['timeout' => 600, 'verify' => false]);
|
|
|
+ $response = $client->get($videoUrl);
|
|
|
+ if (file_put_contents($inputFile, $response->getBody()->getContents()) === false) {
|
|
|
+ dLog('video_standard')->error('标准分辨率处理:视频下载写入失败', ['url' => $videoUrl]);
|
|
|
+ return $videoUrl;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!file_exists($inputFile) || filesize($inputFile) <= 0) {
|
|
|
+ dLog('video_standard')->error('标准分辨率处理:视频下载文件无效', ['url' => $videoUrl]);
|
|
|
+ return $videoUrl;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 探测视频实际分辨率
|
|
|
+ $ffprobePath = env('FFPROBE_PATH', 'ffprobe');
|
|
|
+ $probeCmd = '"' . $ffprobePath . '" -v quiet -print_format json -show_streams "' . $inputFile . '" 2>&1';
|
|
|
+ $probeOutput = shell_exec($probeCmd);
|
|
|
+ $videoInfo = json_decode($probeOutput, true);
|
|
|
+
|
|
|
+ $width = 0;
|
|
|
+ $height = 0;
|
|
|
+ if (isset($videoInfo['streams']) && is_array($videoInfo['streams'])) {
|
|
|
+ foreach ($videoInfo['streams'] as $stream) {
|
|
|
+ if (isset($stream['codec_type']) && $stream['codec_type'] === 'video') {
|
|
|
+ $width = (int)($stream['width'] ?? 0);
|
|
|
+ $height = (int)($stream['height'] ?? 0);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($width <= 0 || $height <= 0) {
|
|
|
+ dLog('video_standard')->warning('标准分辨率处理:无法获取视频分辨率', [
|
|
|
+ 'url' => $videoUrl,
|
|
|
+ 'probe' => $probeOutput
|
|
|
+ ]);
|
|
|
+ return $videoUrl;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 档位校验:仅处理实际属于720p/1080p档位的视频(短边接近目标值),其他分辨率不处理
|
|
|
+ $shortSide = min($width, $height);
|
|
|
+ $targetShortSide = $targetResolution === '720p' ? 720 : 1080;
|
|
|
+ if (abs($shortSide - $targetShortSide) > 40) {
|
|
|
+ dLog('video_standard')->info('视频不属于目标分辨率档位,跳过处理', [
|
|
|
+ 'url' => $videoUrl,
|
|
|
+ 'source' => $width . 'x' . $height,
|
|
|
+ 'target' => $targetResolution
|
|
|
+ ]);
|
|
|
+ return $videoUrl;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 确定目标标准分辨率
|
|
|
+ $isPortrait = $width < $height;
|
|
|
+ if ($targetResolution === '720p') {
|
|
|
+ $targetWidth = $isPortrait ? 720 : 1280;
|
|
|
+ $targetHeight = $isPortrait ? 1280 : 720;
|
|
|
+ } else {
|
|
|
+ $targetWidth = $isPortrait ? 1080 : 1920;
|
|
|
+ $targetHeight = $isPortrait ? 1920 : 1080;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 已经是标准分辨率则直接返回
|
|
|
+ if ($width === $targetWidth && $height === $targetHeight) {
|
|
|
+ dLog('video_standard')->info('视频已是标准分辨率,无需转换', [
|
|
|
+ 'url' => $videoUrl,
|
|
|
+ 'resolution' => $width . 'x' . $height
|
|
|
+ ]);
|
|
|
+ return $videoUrl;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 强制转码(scale保持纵横比 + pad补边到目标尺寸)
|
|
|
+ $ffmpegPath = env('FFMPEG_PATH', 'ffmpeg');
|
|
|
+ $filter = 'scale=' . $targetWidth . ':' . $targetHeight
|
|
|
+ . ':force_original_aspect_ratio=decrease,pad=' . $targetWidth . ':' . $targetHeight
|
|
|
+ . ':(ow-iw)/2:(oh-ih)/2';
|
|
|
+
|
|
|
+ $ffmpegCmd = '"' . $ffmpegPath . '" -i "' . $inputFile
|
|
|
+ . '" -vf "' . $filter . '" -c:v libx264 -preset medium -crf 23'
|
|
|
+ . ' -c:a aac -b:a 128k -movflags +faststart -y "' . $outputFile . '" 2>&1';
|
|
|
+
|
|
|
+ dLog('video_standard')->info('开始视频分辨率强制转换', [
|
|
|
+ 'url' => $videoUrl,
|
|
|
+ 'source' => $width . 'x' . $height,
|
|
|
+ 'target' => $targetWidth . 'x' . $targetHeight,
|
|
|
+ 'command' => $ffmpegCmd
|
|
|
+ ]);
|
|
|
+
|
|
|
+ $output = shell_exec($ffmpegCmd);
|
|
|
+
|
|
|
+ if (!file_exists($outputFile) || filesize($outputFile) <= 0) {
|
|
|
+ dLog('video_standard')->error('标准分辨率转码失败', [
|
|
|
+ 'url' => $videoUrl,
|
|
|
+ 'output' => $output
|
|
|
+ ]);
|
|
|
+ return $videoUrl;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 上传TOS并返回新地址
|
|
|
+ $videoName = 'ai_generation_' . time() . '_' . uniqid() . '.mp4';
|
|
|
+ $newUrl = uploadStreamByTos('video', file_get_contents($outputFile), $videoName);
|
|
|
+
|
|
|
+ dLog('video_standard')->info('视频分辨率转换完成', [
|
|
|
+ 'source' => $videoUrl,
|
|
|
+ 'target' => $newUrl,
|
|
|
+ 'resolution' => $targetWidth . 'x' . $targetHeight
|
|
|
+ ]);
|
|
|
+
|
|
|
+ return $newUrl ?: $videoUrl;
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ dLog('video_standard')->error('标准分辨率处理异常: ' . $e->getMessage(), ['url' => $videoUrl]);
|
|
|
+ return $videoUrl;
|
|
|
+ } finally {
|
|
|
+ if ($inputFile && file_exists($inputFile)) @unlink($inputFile);
|
|
|
+ if ($outputFile && file_exists($outputFile)) @unlink($outputFile);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据任务的分辨率字段确定标准化目标档位
|
|
|
+ *
|
|
|
+ * 480P(需超分)目标为720p;720P/720p 目标为720p;1080P/1080p 目标为1080p;其他返回空(不处理)
|
|
|
+ *
|
|
|
+ * @param MpGenerateVideoTask|object $task
|
|
|
+ * @return string ''|'720p'|'1080p'
|
|
|
+ */
|
|
|
+ private function getTaskTargetResolution($task)
|
|
|
+ {
|
|
|
+ $resolution = strtoupper((string)getProp($task, 'video_resolution', ''));
|
|
|
+ if (strpos($resolution, '480') !== false) {
|
|
|
+ return '720p';
|
|
|
+ }
|
|
|
+ if (strpos($resolution, '1080') !== false) {
|
|
|
+ return '1080p';
|
|
|
+ }
|
|
|
+ if (strpos($resolution, '720') !== false) {
|
|
|
+ return '720p';
|
|
|
+ }
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
* 更新480p视频到分镜表(超分失败时的降级处理)
|
|
|
*
|
|
|
* @param MpGenerateVideoTask $task
|
|
|
@@ -3327,6 +3541,9 @@ class AIVideoGenerationService
|
|
|
];
|
|
|
}
|
|
|
|
|
|
+ // 视频分辨率标准化兜底(480p不在处理范围,720p/1080p强制标准分辨率;失败回退原URL)
|
|
|
+ $sourceVideoUrl = $this->ensureStandardVideoResolution($originalTask->result_url, '720p');
|
|
|
+
|
|
|
// 获取API Key
|
|
|
$apiKey = env('VOLC_AI_MEDIAKIT_KEY');
|
|
|
if (empty($apiKey)) {
|
|
|
@@ -3338,7 +3555,7 @@ class AIVideoGenerationService
|
|
|
|
|
|
// 构建请求参数
|
|
|
$requestParams = [
|
|
|
- 'video_url' => $originalTask->result_url,
|
|
|
+ 'video_url' => $sourceVideoUrl,
|
|
|
'resolution' => '720p',
|
|
|
'bitrate_level' => 'medium'
|
|
|
];
|
|
|
@@ -3522,6 +3739,13 @@ class AIVideoGenerationService
|
|
|
|
|
|
$returnData['status'] = 'success';
|
|
|
$returnData['result_url'] = $savedUrl;
|
|
|
+
|
|
|
+ // 视频分辨率标准化(压缩前处理,避免压缩后重复上传;超分目标为720p)
|
|
|
+ $targetResolution = $this->getTaskTargetResolution($task);
|
|
|
+ if ($targetResolution) {
|
|
|
+ $savedUrl = $this->ensureStandardVideoResolution($savedUrl, $targetResolution);
|
|
|
+ $returnData['result_url'] = $savedUrl;
|
|
|
+ }
|
|
|
|
|
|
// 视频时长
|
|
|
if (isset($taskData['result']['duration'])) {
|
|
|
@@ -3624,6 +3848,7 @@ class AIVideoGenerationService
|
|
|
|
|
|
// 使用查询任务时已压缩的视频URL,避免重复压缩
|
|
|
$segmentUpdateData['video_url'] = $statusInfo['compressed_url'] ?? $statusInfo['result_url'];
|
|
|
+
|
|
|
|
|
|
// 只有当video_duration存在且大于0时才更新
|
|
|
if (isset($statusInfo['video_duration']) && $statusInfo['video_duration'] > 0) {
|