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

新增获取视频第一帧的公共方法

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

+ 141 - 0
app/Libs/Helpers.php

@@ -1170,6 +1170,147 @@ function compressVideo($videoUrl, $prefix = 'videos')
     }
 }
 
+/**
+ * 获取视频第一帧并上传
+ * 根据远程视频URL获取第一帧图片并保存到TOS或OSS(默认TOS)
+ * 
+ * @param string $videoUrl 视频URL地址
+ * @param string $prefix 上传文件夹前缀(如:'videos')
+ * @param string $uploadMethod 上传方式:'tos'(默认) 或 'oss'
+ * @return string 返回图片URL地址,失败返回空字符串
+ */
+function getVideoFirstFrame($videoUrl, $prefix = 'videos', $uploadMethod = 'tos')
+{
+    if (env('APP_ENV') == 'local') return '';
+    try {
+        // 创建临时目录
+        $tempDir = storage_path('app/temp/video_frames');
+        if (!is_dir($tempDir)) {
+            mkdir($tempDir, 0775, true);
+            chmod($tempDir, 0775);
+        }
+
+        // 生成唯一文件名
+        $uniqueId = uniqid('frame_') . bin2hex(random_bytes(4));
+        $videoExt = getVideoExtFromUrl($videoUrl);
+        $inputFile = $tempDir . '/' . $uniqueId . '_video' . $videoExt;
+        $outputFile = $tempDir . '/' . $uniqueId . '_frame.jpg';
+
+        // 下载视频到本地
+        dLog('video_frame')->info('开始下载视频', ['url' => $videoUrl]);
+        $client = new \GuzzleHttp\Client(['timeout' => 300]);
+        $response = $client->get($videoUrl);
+        
+        $fileContent = $response->getBody()->getContents();
+        if (file_put_contents($inputFile, $fileContent) === false) {
+            dLog('video_frame')->error('视频文件写入失败', [
+                'url' => $videoUrl,
+                'file' => $inputFile
+            ]);
+            return '';
+        }
+        
+        chmod($inputFile, 0664);
+
+        if (!file_exists($inputFile)) {
+            dLog('video_frame')->error('视频下载失败', ['url' => $videoUrl]);
+            return '';
+        }
+
+        dLog('video_frame')->info('视频下载成功', [
+            'url' => $videoUrl,
+            'size_mb' => round(filesize($inputFile) / 1024 / 1024, 2) . 'MB'
+        ]);
+
+        // 使用FFmpeg提取第一帧
+        // -i: 输入文件
+        // -ss 00:00:00: 从视频开始位置(第0秒)
+        // -vframes 1: 只提取1帧
+        // -q:v 2: 图片质量(1-31,数字越小质量越高)
+        $ffmpegPath = env('FFMPEG_PATH', 'ffmpeg');
+        $ffmpegCmd = "$ffmpegPath -i \"$inputFile\" -ss 00:00:00 -vframes 1 -q:v 2 -y \"$outputFile\" 2>&1";
+        
+        dLog('video_frame')->info('开始提取第一帧', ['command' => $ffmpegCmd]);
+        $output = shell_exec($ffmpegCmd);
+
+        if (!file_exists($outputFile)) {
+            dLog('video_frame')->error('提取第一帧失败', [
+                'input' => $inputFile,
+                'output' => $output
+            ]);
+            @unlink($inputFile);
+            return '';
+        }
+        
+        chmod($outputFile, 0664);
+
+        dLog('video_frame')->info('第一帧提取成功', [
+            'size' => round(filesize($outputFile) / 1024, 2) . 'KB'
+        ]);
+
+        // 上传图片到云存储
+        $stream = fopen($outputFile, 'r');
+        if (!$stream) {
+            dLog('video_frame')->error('无法打开图片文件', ['file' => $outputFile]);
+            @unlink($inputFile);
+            @unlink($outputFile);
+            return '';
+        }
+        
+        $filename = $uniqueId . '.jpg';
+        
+        // 根据参数选择上传方式,默认使用TOS
+        if ($uploadMethod === 'oss') {
+            $frameUrl = uploadStreamToOss($prefix, $stream, $filename);
+        } else {
+            $frameUrl = uploadStreamByTos($prefix, $stream, $filename);
+        }
+        
+        // 安全关闭文件流
+        if (is_resource($stream)) {
+            fclose($stream);
+        }
+
+        // 清理临时文件
+        @unlink($inputFile);
+        @unlink($outputFile);
+
+        if (!$frameUrl) {
+            dLog('video_frame')->error('图片上传失败');
+            return '';
+        }
+
+        dLog('video_frame')->info('图片上传成功', [
+            'video_url' => $videoUrl,
+            'frame_url' => $frameUrl
+        ]);
+
+        return $frameUrl;
+
+    } catch (\Exception $e) {
+        dLog('video_frame')->error('提取视频第一帧异常', [
+            'url' => $videoUrl,
+            'error' => $e->getMessage(),
+            'trace' => $e->getTraceAsString()
+        ]);
+        
+        // 安全关闭文件流(如果存在)
+        if (isset($stream) && is_resource($stream)) {
+            @fclose($stream);
+        }
+        
+        // 清理可能存在的临时文件
+        if (isset($inputFile) && file_exists($inputFile)) {
+            @unlink($inputFile);
+        }
+        if (isset($outputFile) && file_exists($outputFile)) {
+            @unlink($outputFile);
+        }
+        
+        return '';
+    }
+}
+
 function sensitiveStr($list, $string)
 {
     $count         = 0;        //违规词的个数