GeneratePreviewVideo.php 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. <?php
  2. namespace App\Console\Commands;
  3. use App\Libraries\CommonHelper;
  4. use Illuminate\Console\Command;
  5. use Illuminate\Http\File;
  6. use Illuminate\Support\Carbon;
  7. use Illuminate\Support\Facades\DB;
  8. use Illuminate\Support\Facades\Log;
  9. use Dcat\Admin\Traits\HasUploadedFile;
  10. use FFMpeg\FFMpeg;
  11. use FFMpeg\Coordinate\TimeCode;
  12. /*
  13. * 生成预览10秒视频
  14. *
  15. * @author <EMAIL>
  16. * 运行命令:php artisan command:preview-video
  17. */
  18. class GeneratePreviewVideo extends Command
  19. {
  20. use HasUploadedFile;
  21. /**
  22. * The name and signature of the console command.
  23. *
  24. * @var string
  25. */
  26. protected $signature = 'command:preview-video';
  27. /**
  28. * The console command description.
  29. *
  30. * @var string
  31. */
  32. protected $description = '生成预览视频';
  33. public $timeSecond = 5;
  34. /**
  35. * Execute the console command.
  36. *
  37. * @return int
  38. */
  39. public function handle()
  40. {
  41. $disk = $this->disk('oss');
  42. //如果目录不存在则创建
  43. $localPreviewPath = storage_path('tmp/previews');
  44. if (!file_exists($localPreviewPath)) {
  45. mkdir($localPreviewPath, 0777, true);
  46. }
  47. $reslut = DB::table('site_preview_video')
  48. ->where('status', '==', 0) // 过滤非空 video 字段
  49. ->orderBy('id') // 按主键排序确保顺序
  50. ->limit(1) // 限制处理 1 条
  51. ->get();
  52. foreach ($reslut as $item) {
  53. try {
  54. $video_oss_url = $this->ossUrl($item->video_url);
  55. $fileName = basename($video_oss_url);
  56. $fileInfo = pathinfo($fileName);
  57. //下载$video_url到本地
  58. $localFilePath = $localPreviewPath. '/'. $fileName;
  59. $this->saveLocal($video_oss_url, $localFilePath);
  60. $previewName = $fileInfo['filename'] . '_preview.' . $fileInfo['extension'];
  61. $previewPath = storage_path('tmp/previews/'.$previewName);
  62. //截取视频前5秒
  63. $this->generatePreview($localFilePath, $previewPath);
  64. //上传到OSS
  65. $uploadPath = 'videos/uploads/previews/'. date('Ym');
  66. $file = new File($previewPath);
  67. $disk->putFileAs($uploadPath, $file, $previewName);
  68. DB::table('site_preview_video')
  69. ->where('id', $item->id)
  70. ->update([
  71. 'preview_url' => $uploadPath . '/' . $previewName,
  72. 'status' => 1
  73. ]);
  74. //删除本地文件
  75. unlink($localFilePath);
  76. unlink($previewPath);
  77. } catch (\Exception $e) {
  78. DB::table('site_preview_video')
  79. ->where('id', $item->id)
  80. ->update([
  81. 'remark' => $e->getMessage(),
  82. 'status' => -1
  83. ]);
  84. }
  85. }
  86. //$this->importVideo();
  87. return dd('success1');
  88. }
  89. private function saveLocal($url, $localPath) {
  90. $remote = fopen($url, 'rb');
  91. $local = fopen($localPath, 'wb');
  92. while (!feof($remote)) {
  93. fwrite($local, fread($remote, 8192)); // 8KB分块
  94. }
  95. fclose($remote);
  96. fclose($local);
  97. return $localPath;
  98. }
  99. private function ossUrl($url)
  100. {
  101. if (strpos($url, 'http:') === 0 || strpos($url, 'https:') === 0) {
  102. return $url;
  103. }
  104. return "https://mietublcom.oss-cn-hongkong.aliyuncs.com".'/'.$url;
  105. }
  106. public function generatePreview($inputUrl, $outputPath) {
  107. $ffmpeg = FFMpeg::create([
  108. 'ffmpeg.binaries' => '/usr/bin/ffmpeg',
  109. 'ffprobe.binaries' => '/usr/bin/ffprobe',
  110. 'timeout' => 300
  111. ]);
  112. $video = $ffmpeg->open($inputUrl);
  113. $video->filters()->clip(TimeCode::fromSeconds(0), TimeCode::fromSeconds($this->timeSecond));
  114. // 输出为H.264编码的MP4
  115. $format = new \FFMpeg\Format\Video\X264();
  116. $video->save($format, $outputPath);
  117. return $outputPath;
  118. }
  119. /*ssssssss
  120. * 把旧数据导入新的预览表
  121. */
  122. function importVideo()
  123. {
  124. try {
  125. // 分批处理数据,避免内存溢出
  126. DB::table('site_album')
  127. ->where('video', '<>', '[]') // 过滤非空 video 字段
  128. ->orderBy('id') // 按主键排序确保顺序
  129. ->chunkById(10000, function ($albums) { // 每次处理 100 条
  130. $insertData = [];
  131. foreach ($albums as $album) {
  132. // 跳过无效 JSON 数据
  133. $videos = json_decode($album->video, true);
  134. if (json_last_error() !== JSON_ERROR_NONE || !is_array($videos)) {
  135. Log::warning("Invalid JSON in album ID: {$album->id}");
  136. continue;
  137. }
  138. foreach ($videos as $video) {
  139. // 校验必要字段存在性
  140. if (empty($video['video_src']) || empty($video['cover'])) {
  141. Log::warning("Missing video_src or cover in album ID: {$album->id}");
  142. continue;
  143. }
  144. // 处理 URL 长度限制
  145. $videoUrl = substr($video['video_src'], 0, 255);
  146. // 构建插入数据
  147. $insertData[] = [
  148. 'video_url' => $videoUrl,
  149. 'preview_url' => '',
  150. 'status' => 0,
  151. 'created_at' => Carbon::now(),
  152. 'updated_at' => Carbon::now(),
  153. ];
  154. }
  155. }
  156. // 插入剩余数据
  157. if (!empty($insertData)) {
  158. DB::table('site_preview_video')->insert($insertData);
  159. }
  160. });
  161. return true;
  162. } catch (\Exception $e) {
  163. Log::error("Video import failed: " . $e->getMessage());
  164. return false;
  165. }
  166. }
  167. }