|
|
@@ -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; //违规词的个数
|