GeneratePreviewVideo.php 5.9 KB

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