Преглед изворни кода

视频任务更新时强制标准化视频为720p或1080p

lh пре 1 месец
родитељ
комит
b71d7c2d91
1 измењених фајлова са 226 додато и 1 уклоњено
  1. 226 1
      app/Services/AIGeneration/AIVideoGenerationService.php

+ 226 - 1
app/Services/AIGeneration/AIVideoGenerationService.php

@@ -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) {