Просмотр исходного кода

新增压缩视频公共方法

lh 2 месяцев назад
Родитель
Сommit
1a02f8346d
1 измененных файлов с 154 добавлено и 0 удалено
  1. 154 0
      app/Libs/Helpers.php

+ 154 - 0
app/Libs/Helpers.php

@@ -981,6 +981,160 @@ function filterIntro($content)
     return $content;
 }
 
+/**
+ * 压缩视频文件
+ * 压缩策略:3秒视频约1M~1.5M
+ * 
+ * @param string $videoUrl 视频URL地址
+ * @param string $prefix 上传文件夹前缀(如:'videos')
+ * @return string 返回压缩后的视频URL,失败返回原URL
+ */
+function compressVideo($videoUrl, $prefix = 'videos')
+{
+    try {
+        // 创建临时目录
+        $tempDir = storage_path('app/temp/videos');
+        if (!is_dir($tempDir)) {
+            mkdir($tempDir, 0755, true);
+        }
+
+        // 生成唯一文件名
+        $uniqueId = uniqid('video_', true);
+        $videoExt = getVideoExtFromUrl($videoUrl);
+        $inputFile = $tempDir . '/' . $uniqueId . '_input' . $videoExt;
+        $outputFile = $tempDir . '/' . $uniqueId . '_output.mp4';
+
+        // 下载视频到本地
+        dLog('video_compress')->info('开始下载视频', ['url' => $videoUrl]);
+        $client = new \GuzzleHttp\Client(['timeout' => 300]);
+        $response = $client->get($videoUrl);
+        file_put_contents($inputFile, $response->getBody()->getContents());
+
+        if (!file_exists($inputFile)) {
+            dLog('video_compress')->error('视频下载失败', ['url' => $videoUrl]);
+            return $videoUrl;
+        }
+
+        $inputFileSize = filesize($inputFile);
+        dLog('video_compress')->info('视频下载成功', [
+            'url' => $videoUrl,
+            'size' => $inputFileSize,
+            'size_mb' => round($inputFileSize / 1024 / 1024, 2) . 'MB'
+        ]);
+
+        // 获取视频时长(秒)
+        $ffprobePath = env('FFPROBE_PATH', 'ffprobe');
+        $durationCmd = "$ffprobePath -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 \"$inputFile\"";
+        $duration = (float)trim(shell_exec($durationCmd));
+
+        if ($duration <= 0) {
+            dLog('video_compress')->error('无法获取视频时长', ['file' => $inputFile]);
+            @unlink($inputFile);
+            return $videoUrl;
+        }
+
+        // 计算目标码率
+        // 目标:3秒视频 = 1~1.5MB,即每秒约 0.33~0.5MB = 2730~4096 kbps
+        // 使用中间值:3500 kbps
+        $targetBitrate = 3500; // kbps
+        
+        // 根据视频时长动态调整码率,确保文件大小合理
+        // 目标文件大小 = duration * 0.4MB/s = duration * 400KB/s
+        $targetFileSizeKB = $duration * 400; // KB
+        $targetBitrateCalculated = (int)(($targetFileSizeKB * 8) / $duration); // kbps
+        
+        // 使用计算出的码率,但不低于1500kbps,不高于5000kbps
+        $targetBitrate = max(1500, min(5000, $targetBitrateCalculated));
+
+        dLog('video_compress')->info('视频信息', [
+            'duration' => $duration . 's',
+            'target_bitrate' => $targetBitrate . 'kbps',
+            'estimated_size' => round($targetFileSizeKB / 1024, 2) . 'MB'
+        ]);
+
+        // 使用FFmpeg压缩视频
+        // -c:v libx264: 使用H.264编码器
+        // -b:v: 视频码率
+        // -c:a aac: 音频编码器
+        // -b:a 128k: 音频码率
+        // -movflags +faststart: 优化网络播放
+        // -preset medium: 编码速度与质量平衡
+        $ffmpegPath = env('FFMPEG_PATH', 'ffmpeg');
+        $ffmpegCmd = "$ffmpegPath -i \"$inputFile\" -c:v libx264 -b:v {$targetBitrate}k -c:a aac -b:a 128k -movflags +faststart -preset medium -y \"$outputFile\" 2>&1";
+        
+        dLog('video_compress')->info('开始压缩视频', ['command' => $ffmpegCmd]);
+        $output = shell_exec($ffmpegCmd);
+
+        if (!file_exists($outputFile)) {
+            dLog('video_compress')->error('视频压缩失败', [
+                'input' => $inputFile,
+                'output' => $output
+            ]);
+            @unlink($inputFile);
+            return $videoUrl;
+        }
+
+        $outputFileSize = filesize($outputFile);
+        $compressionRatio = round((1 - $outputFileSize / $inputFileSize) * 100, 2);
+
+        dLog('video_compress')->info('视频压缩成功', [
+            'input_size' => round($inputFileSize / 1024 / 1024, 2) . 'MB',
+            'output_size' => round($outputFileSize / 1024 / 1024, 2) . 'MB',
+            'compression_ratio' => $compressionRatio . '%',
+            'duration' => $duration . 's',
+            'size_per_second' => round($outputFileSize / $duration / 1024 / 1024, 2) . 'MB/s'
+        ]);
+
+        // 上传压缩后的视频到云存储
+        $stream = fopen($outputFile, 'r');
+        $filename = $uniqueId . '.mp4';
+        
+        // 根据环境变量选择上传方式
+        $uploadMethod = env('VIDEO_UPLOAD_METHOD', 'tos'); // 默认使用火山云
+        
+        if ($uploadMethod === 'oss') {
+            $compressedUrl = uploadStreamToOss($prefix, $stream, $filename);
+        } else {
+            $compressedUrl = uploadStreamByTos($prefix, $stream, $filename);
+        }
+        
+        fclose($stream);
+
+        // 清理临时文件
+        @unlink($inputFile);
+        @unlink($outputFile);
+
+        if (!$compressedUrl) {
+            dLog('video_compress')->error('压缩视频上传失败');
+            return $videoUrl;
+        }
+
+        dLog('video_compress')->info('压缩视频上传成功', [
+            'original_url' => $videoUrl,
+            'compressed_url' => $compressedUrl
+        ]);
+
+        return $compressedUrl;
+
+    } catch (\Exception $e) {
+        dLog('video_compress')->error('视频压缩异常', [
+            'url' => $videoUrl,
+            'error' => $e->getMessage(),
+            'trace' => $e->getTraceAsString()
+        ]);
+        
+        // 清理可能存在的临时文件
+        if (isset($inputFile) && file_exists($inputFile)) {
+            @unlink($inputFile);
+        }
+        if (isset($outputFile) && file_exists($outputFile)) {
+            @unlink($outputFile);
+        }
+        
+        return $videoUrl; // 出错时返回原URL
+    }
+}
+
 function sensitiveStr($list, $string)
 {
     $count         = 0;        //违规词的个数