Showing
3 changed files
with
5 additions
and
1239 deletions
app/Jobs/UserMakeImages.php
deleted
100644 → 0
| 1 | -<?php | ||
| 2 | - | ||
| 3 | -namespace App\Jobs; | ||
| 4 | - | ||
| 5 | -use App\Models\Immerse; | ||
| 6 | -use App\Models\VideoTemp; | ||
| 7 | -use Carbon\Carbon; | ||
| 8 | -use Illuminate\Bus\Queueable; | ||
| 9 | -use Illuminate\Contracts\Queue\ShouldBeUnique; | ||
| 10 | -use Illuminate\Contracts\Queue\ShouldQueue; | ||
| 11 | -use Illuminate\Foundation\Bus\Dispatchable; | ||
| 12 | -use Illuminate\Queue\InteractsWithQueue; | ||
| 13 | -use Illuminate\Queue\SerializesModels; | ||
| 14 | -use Illuminate\Support\Facades\File; | ||
| 15 | -use Illuminate\Support\Facades\Log; | ||
| 16 | -use Illuminate\Support\Facades\Storage; | ||
| 17 | -use Illuminate\Support\Str; | ||
| 18 | - | ||
| 19 | -class UserMakeImages implements ShouldQueue | ||
| 20 | -{ | ||
| 21 | - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; | ||
| 22 | - | ||
| 23 | - public $debug = true; | ||
| 24 | - | ||
| 25 | - public $immerse; | ||
| 26 | - | ||
| 27 | - protected $ffmpeg; | ||
| 28 | - | ||
| 29 | - protected $ffprobe; | ||
| 30 | - | ||
| 31 | - protected $ffplay; | ||
| 32 | - | ||
| 33 | - protected $width; | ||
| 34 | - | ||
| 35 | - protected $height; | ||
| 36 | - | ||
| 37 | - /** | ||
| 38 | - * Create a new job instance. | ||
| 39 | - * @param Immerse $immerse | ||
| 40 | - * @return void | ||
| 41 | - */ | ||
| 42 | - public function __construct(Immerse $immerse) | ||
| 43 | - { | ||
| 44 | - $this->immerse = $immerse; | ||
| 45 | - | ||
| 46 | - $this->ffmpeg = env('FFMPEG_CMD'); | ||
| 47 | - $this->ffprobe = env('FFPROBE_CMD'); | ||
| 48 | - $this->ffplay = env('FFPLAY_CMD'); | ||
| 49 | - } | ||
| 50 | - | ||
| 51 | - /** | ||
| 52 | - * Execute the job. | ||
| 53 | - * | ||
| 54 | - * @return void | ||
| 55 | - */ | ||
| 56 | - public function handle() | ||
| 57 | - { | ||
| 58 | - $watermark = Storage::disk('public')->path('ffmpeg/LOGO_eng.png'); | ||
| 59 | - $image = Storage::disk('public')->path($this->immerse->thumbnail); | ||
| 60 | - if (!File::exists($image)) return; | ||
| 61 | - if ($this->debug) Log::debug('image url :' . $image); | ||
| 62 | - $media_info = $this->mediainfo($image); | ||
| 63 | - $this->width = $width = $media_info['streams'][0]['width']; | ||
| 64 | - $this->height = $height = $media_info['streams'][0]['height']; | ||
| 65 | - | ||
| 66 | - try{ | ||
| 67 | - if ($this->immerse->bgm == '' || $this->immerse->bgm == null) { | ||
| 68 | - // 没有背景音,单图一张,输出为单图。 | ||
| 69 | - $output = $this->getTempPath('.png',false); | ||
| 70 | - $cmd = $this->ffmpeg . ' -y '. | ||
| 71 | - ' -i ' . escapeshellarg($image). | ||
| 72 | - ' -i ' . escapeshellarg($watermark). | ||
| 73 | - ' -filter_complex "[0:0] ' . | ||
| 74 | - $this->getTextContentString(). | ||
| 75 | - ' [text];[text]'. | ||
| 76 | - ' [1:0]overlay=20:20" ' . | ||
| 77 | - escapeshellarg($output); | ||
| 78 | - | ||
| 79 | - if (!$this->execmd($cmd)) return; | ||
| 80 | - | ||
| 81 | - // 全部合成以后创建 临境 | ||
| 82 | - $video_info = $this->mediainfo($output); | ||
| 83 | - }else{ | ||
| 84 | - // 有背景音 单图合成视频,时长为音频时长,音频加入背景音 | ||
| 85 | - $bgm = Storage::disk('public')->path($this->immerse->bgm); | ||
| 86 | - | ||
| 87 | - $end_wallpaper = Storage::disk('public')->path('ffmpeg/end_wallpaper.png'); | ||
| 88 | - $thumbnail = Storage::disk('public')->path('ffmpeg/thumbnail.png'); | ||
| 89 | - $font = Storage::disk('public')->path('ffmpeg') . "/arialuni.ttf"; | ||
| 90 | - $signature = "一言 · 官方出品"; | ||
| 91 | - | ||
| 92 | - // 生成贴纸和签名 | ||
| 93 | - $end_wallpaper = $this->wallpaperWithSignature($end_wallpaper, $thumbnail, $signature, $font); | ||
| 94 | - | ||
| 95 | - // 制作最后一帧 | ||
| 96 | - $size = $this->width . 'x' . $this->height; | ||
| 97 | - $time_length = 0.7; | ||
| 98 | - $r = 24; | ||
| 99 | - $last_frame_video = $this->getTempPath('.mp4'); | ||
| 100 | - $font = Storage::disk('public')->path('ffmpeg/arialuni.ttf'); | ||
| 101 | - | ||
| 102 | - $cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($image) . | ||
| 103 | - ' -i ' . escapeshellarg($watermark) . | ||
| 104 | - " -f lavfi -i nullsrc=s={$size}:d={$time_length}:r={$r} -f lavfi -i aevalsrc=0:duration={$time_length}" . | ||
| 105 | - ' -filter_complex "'. | ||
| 106 | - ' [0:0] ' . $this->getTextContentString() . | ||
| 107 | - '[text];[text][1:0]overlay=20:20[water];' . | ||
| 108 | - ' [water]select=\'eq(n,0)\',setpts=PTS-STARTPTS[lastframe];[2:v][lastframe]overlay[v] " ' . | ||
| 109 | - ' -map [v] -map 3:a ' . escapeshellarg($last_frame_video); | ||
| 110 | - | ||
| 111 | - if (!$this->execmd($cmd)) return; | ||
| 112 | - | ||
| 113 | - // 利用最后一帧制作动画 | ||
| 114 | - $signature_x = 0; | ||
| 115 | - $signature_y = -20; | ||
| 116 | - $animate = $this->makeAnimate($last_frame_video, $end_wallpaper, '', $signature_x, $signature_y, $font); | ||
| 117 | - | ||
| 118 | - $output = $this->getTempPath('.mp4',false); | ||
| 119 | - | ||
| 120 | - $cmd = $this->ffmpeg . ' -y ' . | ||
| 121 | - ' -i ' . escapeshellarg($image). | ||
| 122 | - ' -i ' . escapeshellarg($watermark). | ||
| 123 | - ' -i ' . escapeshellarg($bgm) . | ||
| 124 | - ' -i ' . escapeshellarg($animate) . | ||
| 125 | - ' -filter_complex "[0:0] ' . $this->getTextContentString(). | ||
| 126 | - '[text];[text][1:0]overlay=20:20[water];' . | ||
| 127 | - '[water][2:a][3:v][3:a]concat=n=2:v=1:a=1[v][a]" '. | ||
| 128 | - ' -map [v] -map [a] '. | ||
| 129 | - ' -c:v libx264 -bt 256k -r 25' . | ||
| 130 | - ' -ar 44100 -ac 2 -qmin 30 -qmax 60 -profile:v baseline -preset fast ' | ||
| 131 | - . escapeshellarg($output); | ||
| 132 | - | ||
| 133 | - if (!$this->execmd($cmd)) return; | ||
| 134 | - | ||
| 135 | - $video_info = $this->mediainfo($output); | ||
| 136 | - } | ||
| 137 | - | ||
| 138 | - $this->immerse->url = Str::of($output)->replace(Storage::disk('public')->path(''), ''); | ||
| 139 | - $this->immerse->state = 1; | ||
| 140 | - $this->immerse->duration = $video_info['format']['duration'] ?? 0.00; | ||
| 141 | - $this->immerse->size = $video_info['format']['size']; | ||
| 142 | - $this->immerse->save(); | ||
| 143 | - }catch (\Exception $exception){ | ||
| 144 | - $this->immerse->state = 2; | ||
| 145 | - $this->immerse->save(); | ||
| 146 | - Log::channel('daily')->error($exception->getMessage()); | ||
| 147 | - } | ||
| 148 | - } | ||
| 149 | - | ||
| 150 | - /*** | ||
| 151 | - * 获取视频信息(配合ffprobe) | ||
| 152 | - * @param $file | ||
| 153 | - * @param bool $cache | ||
| 154 | - * @return mixed | ||
| 155 | - */ | ||
| 156 | - public function mediainfo($file, $cache = true) { | ||
| 157 | - global $_mediainfo; | ||
| 158 | - $cmd = $this->ffprobe . ' -v quiet -print_format json -show_format -show_streams ' . escapeshellarg($file); | ||
| 159 | - if ($cache && isset($_mediainfo[$file])) { | ||
| 160 | - return $_mediainfo[$file]; | ||
| 161 | - } | ||
| 162 | - $output = $this->execmd($cmd); | ||
| 163 | - $data = json_decode($output, true); | ||
| 164 | - if (json_last_error() === JSON_ERROR_UTF8) { | ||
| 165 | - $output = mb_convert_encoding($output, "UTF-8"); | ||
| 166 | - $data = json_decode($output, true); | ||
| 167 | - } | ||
| 168 | - if ($cache) { | ||
| 169 | - $mediainfo[$file] = $data; | ||
| 170 | - } | ||
| 171 | - return $data; | ||
| 172 | - } | ||
| 173 | - | ||
| 174 | - /** | ||
| 175 | - * 获取输出临时文件名 | ||
| 176 | - * @param string $ext | ||
| 177 | - * @param bool $is_temp | ||
| 178 | - * @return string | ||
| 179 | - */ | ||
| 180 | - public function getTempPath($ext = '.mp4',$is_temp = true) | ||
| 181 | - { | ||
| 182 | - $filename = "/output_" . time() . rand(0, 10000); | ||
| 183 | - | ||
| 184 | - $prefix = $is_temp ? 'temp/' : 'video/'; | ||
| 185 | - $hash_hex = md5($filename); | ||
| 186 | - // 16进制表示的字符串一共32字节,表示16个二进制字节。 | ||
| 187 | - // 前16个字符用来第一级求摸,后16个用做第二级 | ||
| 188 | - $hash_hex_l1 = substr($hash_hex, 0, 8); | ||
| 189 | - $hash_hex_l2 = substr($hash_hex, 8, 8); | ||
| 190 | - $dir_l1 = hexdec($hash_hex_l1) % 256; | ||
| 191 | - $dir_l2 = hexdec($hash_hex_l2) % 512; | ||
| 192 | - $dir = $prefix . $dir_l1 . '/' . $dir_l2; | ||
| 193 | - | ||
| 194 | - if( !Storage::disk('public')->exists($dir)) Storage::disk('public')->makeDirectory($dir); | ||
| 195 | - | ||
| 196 | - return Storage::disk('public')->path($dir . $filename . $ext); | ||
| 197 | - } | ||
| 198 | - | ||
| 199 | - /** | ||
| 200 | - * 执行命令 | ||
| 201 | - * @param $cmd | ||
| 202 | - * @param bool $update_progress | ||
| 203 | - * @return string | ||
| 204 | - */ | ||
| 205 | - public function execmd($cmd, $update_progress = false) { | ||
| 206 | - echo $cmd . "\n". "\n". "\n"; | ||
| 207 | - $descriptorspec = array( | ||
| 208 | - 1 => array("pipe", "w"), // 标准输出,子进程向此管道中写入数据 | ||
| 209 | - ); | ||
| 210 | - $process = proc_open("{$cmd} 2>&1", $descriptorspec, $pipes); | ||
| 211 | - if (is_resource($process)) { | ||
| 212 | - $error0 = ''; | ||
| 213 | - $error1 = ''; | ||
| 214 | - $stdout = ''; | ||
| 215 | - while (!feof($pipes[1])) { | ||
| 216 | - $line = fgets($pipes[1], 150); | ||
| 217 | - $stdout .= $line; | ||
| 218 | - if ($line) { | ||
| 219 | - //记录错误 | ||
| 220 | - $error0 = $error1; | ||
| 221 | - $error1 = $line; | ||
| 222 | - if ($update_progress && | ||
| 223 | - false !== strpos($line, 'size=') && | ||
| 224 | - false !== strpos($line, 'time=') && | ||
| 225 | - false !== strpos($line, 'bitrate=')) | ||
| 226 | - { | ||
| 227 | - //记录进度 size= 3142kB time=00:00:47.22 bitrate= 545.1kbits/s | ||
| 228 | - $line = explode(' ', $line); | ||
| 229 | - $time = null; | ||
| 230 | - foreach ($line as $item) { | ||
| 231 | - $item = explode('=', $item); | ||
| 232 | - if (isset($item[0]) && isset($item[1]) && $item[0] == 'time') { | ||
| 233 | - $time = $item[1]; | ||
| 234 | - break; | ||
| 235 | - } | ||
| 236 | - } | ||
| 237 | - } | ||
| 238 | - } | ||
| 239 | - } | ||
| 240 | - // 切记:在调用 proc_close 之前关闭所有的管道以避免死锁。 | ||
| 241 | - fclose($pipes[1]); | ||
| 242 | - $exitedcode = proc_close($process); | ||
| 243 | - if ($exitedcode === 0) { | ||
| 244 | - return $stdout; | ||
| 245 | - } else { | ||
| 246 | - $error = trim($error0,"\n") . ' '. trim($error1,"\n"); | ||
| 247 | - // LogUtil::write(array("cmd:{$cmd}", "errno:{$exitedcode}", "stdout:{$stdout}"), __CLASS__); | ||
| 248 | - // ErrorUtil::triggerErrorMsg($error, $exitedcode); | ||
| 249 | - } | ||
| 250 | - } else { | ||
| 251 | - // return ErrorUtil::triggerErrorMsg('proc_open error'); | ||
| 252 | - } | ||
| 253 | - } | ||
| 254 | - | ||
| 255 | - public function getTextContentString() | ||
| 256 | - { | ||
| 257 | - $components = $this->immerse->temp()->first()->components()->get(); | ||
| 258 | - | ||
| 259 | - $font = Storage::disk('public')->path('ffmpeg/arialuni.ttf'); | ||
| 260 | - | ||
| 261 | - $drawtext = ''; | ||
| 262 | - | ||
| 263 | - foreach ($components as $component) { | ||
| 264 | - switch ($component->name){ | ||
| 265 | - case 'one_poem': | ||
| 266 | - $content = $this->immerse->poem->content; | ||
| 267 | - $text_file = $this->getTempPath('.txt'); | ||
| 268 | - file_put_contents($text_file, $content); | ||
| 269 | - | ||
| 270 | - $text_color = $component->text_color ?? 'white'; | ||
| 271 | - $text_bg_color = $component->text_bg_color ?? '0xd0cdcc'; | ||
| 272 | - $opacity = $component->opacity ? $component->opacity / 100 : '0.5'; | ||
| 273 | - | ||
| 274 | - $drawtext .= 'drawtext="'. | ||
| 275 | - 'fontfile=' . escapeshellarg($font) . ':' . | ||
| 276 | - 'textfile=' . escapeshellarg($text_file) . ':' . | ||
| 277 | - 'fontsize=' . $this->calcFontSize($component->font_size,$content) . ':' . | ||
| 278 | - 'fontcolor=' . $text_color . '@1.0:' . | ||
| 279 | - 'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' . | ||
| 280 | - 'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' . | ||
| 281 | - 'box=1:boxcolor=' . $text_bg_color . '@' . $opacity . '", '; | ||
| 282 | - | ||
| 283 | - break; | ||
| 284 | - case 'every_poem': | ||
| 285 | - break; | ||
| 286 | - case 'weather': | ||
| 287 | - $content = '多云'; | ||
| 288 | - $text_color = $component->text_color ?? 'white'; | ||
| 289 | - $text_bg_color = $component->text_bg_color ?? '0xd0cdcc'; | ||
| 290 | - $opacity = $component->opacity ? $component->opacity / 100 : '0.5'; | ||
| 291 | - | ||
| 292 | - $drawtext .= 'drawtext="'. | ||
| 293 | - 'fontfile=' . escapeshellarg($font) . ':' . | ||
| 294 | - 'text=' . escapeshellarg($content) . ':' . | ||
| 295 | - 'fontsize=' . $this->calcFontSize($component->font_size,$content) . ':' . | ||
| 296 | - 'fontcolor=' . $text_color . '@1.0:' . | ||
| 297 | - 'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' . | ||
| 298 | - 'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' . | ||
| 299 | - 'box=1:boxcolor=' . $text_bg_color . '@' . $opacity . '", '; | ||
| 300 | - | ||
| 301 | - break; | ||
| 302 | - case 'date': | ||
| 303 | - $content = Carbon::now()->format('Y年m月d日H时'); | ||
| 304 | - $text_color = $component->text_color ?? 'white'; | ||
| 305 | - $text_bg_color = $component->text_bg_color ?? '0xd0cdcc'; | ||
| 306 | - $opacity = $component->opacity ? $component->opacity / 100 : '0.5'; | ||
| 307 | - | ||
| 308 | - $drawtext .= 'drawtext="'. | ||
| 309 | - 'fontfile=' . escapeshellarg($font) . ':' . | ||
| 310 | - 'text=' . escapeshellarg($content) . ':' . | ||
| 311 | - 'fontsize=' . $this->calcFontSize($component->font_size,$content) . ':' . | ||
| 312 | - 'fontcolor=' . $text_color . '@1.0:' . | ||
| 313 | - 'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' . | ||
| 314 | - 'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' . | ||
| 315 | - 'box=1:boxcolor=' . $text_bg_color . '@' . $opacity . '", '; | ||
| 316 | - break; | ||
| 317 | - case 'feel': | ||
| 318 | - $content = $this->immerse->feel; | ||
| 319 | - $text_color = $component->text_color ?? 'white'; | ||
| 320 | - $text_bg_color = $component->text_bg_color ?? '0xd0cdcc'; | ||
| 321 | - $opacity = $component->opacity ? $component->opacity / 100 : '0.5'; | ||
| 322 | - | ||
| 323 | - $drawtext .= 'drawtext="'. | ||
| 324 | - 'fontfile=' . escapeshellarg($font) . ':' . | ||
| 325 | - 'text=' . escapeshellarg($content) . ':' . | ||
| 326 | - 'fontsize=' . $this->calcFontSize($component->font_size,$content) . ':' . | ||
| 327 | - 'fontcolor=' . $text_color . '@1.0:' . | ||
| 328 | - 'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' . | ||
| 329 | - 'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' . | ||
| 330 | - 'box=1:boxcolor=' . $text_bg_color . '@' . $opacity . '", '; | ||
| 331 | - break; | ||
| 332 | - } | ||
| 333 | - } | ||
| 334 | - | ||
| 335 | - return rtrim($drawtext,', '); | ||
| 336 | - } | ||
| 337 | - | ||
| 338 | - /** | ||
| 339 | - * @param $width | ||
| 340 | - * @param $content | ||
| 341 | - * @return float | ||
| 342 | - */ | ||
| 343 | - public function calcFontSize($width, $content) | ||
| 344 | - { | ||
| 345 | - $max_len = 1; | ||
| 346 | - foreach (explode("\n",$content) as $item){ | ||
| 347 | - if (mb_strlen($item) > $max_len){ | ||
| 348 | - $max_len = mb_strlen($item); | ||
| 349 | - } | ||
| 350 | - } | ||
| 351 | - | ||
| 352 | - return ceil($this->width * $width / 100 / $max_len); | ||
| 353 | - } | ||
| 354 | - | ||
| 355 | - /** | ||
| 356 | - * 贴纸和签名 | ||
| 357 | - * @param $end_wallpaper | ||
| 358 | - * @param $thumbnail | ||
| 359 | - * @param $signature | ||
| 360 | - * @param $font | ||
| 361 | - * @return string | ||
| 362 | - */ | ||
| 363 | - public function wallpaperWithSignature($end_wallpaper, $thumbnail, $signature, $font) { | ||
| 364 | - $_imagetype = $this->getImageType($thumbnail); | ||
| 365 | - $_img = null; | ||
| 366 | - switch ($_imagetype) { | ||
| 367 | - case 'gif': | ||
| 368 | - if (function_exists('imagecreatefromgif')) { | ||
| 369 | - $_img = imagecreatefromgif($thumbnail); | ||
| 370 | - } | ||
| 371 | - break; | ||
| 372 | - case 'jpg': | ||
| 373 | - case 'jpeg': | ||
| 374 | - $_img = imagecreatefromjpeg($thumbnail); | ||
| 375 | - break; | ||
| 376 | - case 'png': | ||
| 377 | - $_img = imagecreatefrompng($thumbnail); | ||
| 378 | - break; | ||
| 379 | - default: | ||
| 380 | - $_img = imagecreatefromstring($thumbnail); | ||
| 381 | - break; | ||
| 382 | - } | ||
| 383 | - $width = 130; | ||
| 384 | - $height = 130; | ||
| 385 | - $_width = 130; | ||
| 386 | - $_height = 130; | ||
| 387 | - if(is_resource($_img)){ | ||
| 388 | - $_width = imagesx($_img); | ||
| 389 | - $_height = imagesy($_img); | ||
| 390 | - } | ||
| 391 | - | ||
| 392 | - $bite = $_width / $_height; | ||
| 393 | - | ||
| 394 | - if($_width > $_height){ | ||
| 395 | - if($_width > $width){ | ||
| 396 | - $height = round($width / $bite); | ||
| 397 | - } | ||
| 398 | - }else{ | ||
| 399 | - if($_height > $height){ | ||
| 400 | - $width = round($height * $bite); | ||
| 401 | - } | ||
| 402 | - } | ||
| 403 | - | ||
| 404 | - $tmpimg = imagecreatetruecolor($width,$height); | ||
| 405 | - if(function_exists('imagecopyresampled')) { | ||
| 406 | - imagecopyresampled($tmpimg, $_img, 0, 0, 0, 0, $width, $height, $_width, $_height); | ||
| 407 | - } else { | ||
| 408 | - imagecopyresized($tmpimg, $_img, 0, 0, 0, 0, $width, $height, $_width, $_height); | ||
| 409 | - } | ||
| 410 | - if(is_resource($_img)) imagedestroy($_img); | ||
| 411 | - $_img = $this->getCircleAvatar($tmpimg); | ||
| 412 | - if(is_resource($tmpimg)) imagedestroy($tmpimg); | ||
| 413 | - | ||
| 414 | - $wp = $this->imagesMerge($end_wallpaper, $_img); | ||
| 415 | -// $white = imagecolorallocate($wp, 0xd0, 0xcd, 0xcc); | ||
| 416 | - $white = imagecolorallocate($wp, 0xDC, 0x14, 0x3C); //fixme 字体颜色 | ||
| 417 | - imagettftext($wp, 20, 0, 75, 240, $white, $font, $signature); | ||
| 418 | - | ||
| 419 | -// $dst = "./output_new_end_wallpaper.png"; | ||
| 420 | - $dst = $this->getTempPath('.png'); | ||
| 421 | - imagepng($wp, $dst); | ||
| 422 | - if(is_resource($end_wallpaper)) imagedestroy($end_wallpaper); | ||
| 423 | - if(is_resource($_img)) imagedestroy($_img); | ||
| 424 | - | ||
| 425 | - return $dst; | ||
| 426 | - } | ||
| 427 | - | ||
| 428 | - /** | ||
| 429 | - * 获取图像文件类型 | ||
| 430 | - * @param $img_name | ||
| 431 | - * @return string | ||
| 432 | - */ | ||
| 433 | - public function getImageType($img_name) | ||
| 434 | - { | ||
| 435 | - if (preg_match("/\.(jpg|jpeg|gif|png)$/i", $img_name, $matches)){ | ||
| 436 | - $type = strtolower($matches[1]); | ||
| 437 | - }else{ | ||
| 438 | - $type = "string"; | ||
| 439 | - } | ||
| 440 | - return $type; | ||
| 441 | - } | ||
| 442 | - | ||
| 443 | - /** | ||
| 444 | - * 多图融合 | ||
| 445 | - * @param $end_wallpaper | ||
| 446 | - * @param $thumbnail | ||
| 447 | - * @return resource | ||
| 448 | - */ | ||
| 449 | - public function imagesMerge($end_wallpaper, $thumbnail) { | ||
| 450 | - $end_wallpaper = imagecreatefrompng($end_wallpaper); | ||
| 451 | - $background = imagecreatefrompng(Storage::disk('public')->path('ffmpeg/background.png')); | ||
| 452 | - imagesavealpha($background,true); | ||
| 453 | - $temp_wallpaper = imagecreatetruecolor(350, 204); | ||
| 454 | - $color = imagecolorallocate($temp_wallpaper, 0xd0, 0xcd, 0xcc); | ||
| 455 | -// $color = imagecolorallocate($temp_wallpaper, 0xDC, 0x14, 0x3C); | ||
| 456 | - imagefill($temp_wallpaper, 0, 0, $color); | ||
| 457 | - imageColorTransparent($temp_wallpaper, $color); | ||
| 458 | - imagecopyresampled($temp_wallpaper, $end_wallpaper, 0, 0, 0, 0, imagesx($temp_wallpaper), imagesy($temp_wallpaper), imagesx($end_wallpaper), imagesy($end_wallpaper)); | ||
| 459 | - imagecopymerge($background, $temp_wallpaper, 0, 0, 0, 0, imagesx($temp_wallpaper), imagesy($temp_wallpaper), 60); | ||
| 460 | - imagecopymerge($background, $thumbnail, 127, 26, 0, 0, imagesx($thumbnail), imagesy($thumbnail), 100); | ||
| 461 | - return $background; | ||
| 462 | - } | ||
| 463 | - | ||
| 464 | - /** | ||
| 465 | - * 获取圆形头像 | ||
| 466 | - * @param $img | ||
| 467 | - * @param int $dst_w | ||
| 468 | - * @param int $dst_h | ||
| 469 | - * @return resource | ||
| 470 | - */ | ||
| 471 | - public function getCircleAvatar($img, $dst_w = 96, $dst_h = 96) | ||
| 472 | - { | ||
| 473 | - $w = 130; | ||
| 474 | - $h = 130; | ||
| 475 | - $src = imagecreatetruecolor($dst_w, $dst_h); | ||
| 476 | - imagecopyresized($src, $img, 0, 0, 0, 0, $dst_w, $dst_h, $w, $h); | ||
| 477 | - | ||
| 478 | - $newpic = imagecreatetruecolor($dst_w, $dst_h); | ||
| 479 | - imagealphablending($newpic, false); | ||
| 480 | - imagecopyresampled($newpic, $img, 0, 0, 0, 0, $dst_w, $dst_h, $w, $h); | ||
| 481 | - $mask = imagecreatetruecolor($dst_w, $dst_h); | ||
| 482 | - $transparent = imagecolorallocate($mask, 255, 0, 0); | ||
| 483 | - imagecolortransparent($mask,$transparent); | ||
| 484 | - imagefilledellipse($mask, $dst_w / 2, $dst_h / 2, $dst_w, $dst_h, $transparent); | ||
| 485 | - $red = imagecolorallocate($mask, 0, 0, 0); | ||
| 486 | - imagecopymerge($newpic, $mask, 0, 0, 0, 0, $dst_w, $dst_h, 100); | ||
| 487 | - imagecolortransparent($newpic,$red); | ||
| 488 | - imagesavealpha($newpic,true); | ||
| 489 | - imagefill($newpic, 0, 0, $red); | ||
| 490 | - imagedestroy($mask); | ||
| 491 | - return $newpic; | ||
| 492 | - } | ||
| 493 | - | ||
| 494 | - /** | ||
| 495 | - * 用最后一帧和贴纸制作动画 | ||
| 496 | - * @param $last_frame_video | ||
| 497 | - * @param $end_wallpaper | ||
| 498 | - * @param $signature | ||
| 499 | - * @param $signature_x | ||
| 500 | - * @param $signature_y | ||
| 501 | - * @param $font | ||
| 502 | - * @return bool|string | ||
| 503 | - */ | ||
| 504 | - public function makeAnimate($last_frame_video, $end_wallpaper, $signature, $signature_x, $signature_y, $font) { | ||
| 505 | - $signature_x = $signature_x >= 0 ? '+' . $signature_x : '-' . abs($signature_x); | ||
| 506 | - $signature_y = $signature_y >= 0 ? '+' . $signature_y : '-' . abs($signature_y); | ||
| 507 | - $video = $this->getTempPath(); | ||
| 508 | - if ($signature !== '') { | ||
| 509 | - $cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($last_frame_video) . | ||
| 510 | - ' -loop 1 -i ' . escapeshellarg($end_wallpaper) . | ||
| 511 | - ' -filter_complex "'. | ||
| 512 | - 'geq=lum=\'if(lte(T,0.6), 255*T*(1/0.6),255)\',format=gray[grad];'. | ||
| 513 | - '[0:v]boxblur=8[blur];'. | ||
| 514 | - '[blur][1:v]overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2 [lay];[lay]'. | ||
| 515 | - 'drawtext='. | ||
| 516 | - 'fontfile=' . escapeshellarg($font) . ':'. | ||
| 517 | - 'text=' . escapeshellarg($signature) . ':'. | ||
| 518 | - 'fontsize=23:'. | ||
| 519 | - 'fontcolor=white@1.0:'. | ||
| 520 | - 'x=main_w/2' . $signature_x . ':'. | ||
| 521 | - 'y=main_h/2' . $signature_y . '[text];[text]'. | ||
| 522 | - '[grad]alphamerge[alpha];'. | ||
| 523 | - '[0:v][alpha]overlay'. | ||
| 524 | - '" ' . escapeshellarg($video); | ||
| 525 | - } else { | ||
| 526 | - $cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($last_frame_video) . | ||
| 527 | - ' -loop 1 -i ' . escapeshellarg($end_wallpaper) . | ||
| 528 | - ' -filter_complex "'. | ||
| 529 | - 'geq=lum=\'if(lte(T,0.6), 255*T*(1/0.6),255)\',format=gray[grad];'. | ||
| 530 | - '[0:v]boxblur=8[blur];'. | ||
| 531 | - '[blur][1:v]overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2 [lay];'. | ||
| 532 | - '[lay][grad]alphamerge[alpha];'. | ||
| 533 | - '[0:v][alpha]overlay'. | ||
| 534 | - '" ' . escapeshellarg($video); | ||
| 535 | - } | ||
| 536 | - if ($this->execmd($cmd)) { | ||
| 537 | - return $video; | ||
| 538 | - } else { | ||
| 539 | - return false; | ||
| 540 | - } | ||
| 541 | - } | ||
| 542 | -} |
| ... | @@ -251,7 +251,7 @@ class UserMakeImmerse implements ShouldQueue | ... | @@ -251,7 +251,7 @@ class UserMakeImmerse implements ShouldQueue |
| 251 | $video_info = $this->mediainfo($video); | 251 | $video_info = $this->mediainfo($video); |
| 252 | 252 | ||
| 253 | $this->immerse->url = Str::of($video)->replace(Storage::disk('public')->path(''), ''); | 253 | $this->immerse->url = Str::of($video)->replace(Storage::disk('public')->path(''), ''); |
| 254 | - $this->immerse->thumbnail = $thumbnail; | 254 | + $this->immerse->thumbnail = Str::of($thumbnail)->replace(Storage::disk('public')->path(''), ''); |
| 255 | $this->immerse->state = 1; | 255 | $this->immerse->state = 1; |
| 256 | $this->immerse->duration = $video_info['format']['duration'] ?? 0; | 256 | $this->immerse->duration = $video_info['format']['duration'] ?? 0; |
| 257 | $this->immerse->size = $video_info['format']['size']; | 257 | $this->immerse->size = $video_info['format']['size']; |
| ... | @@ -510,8 +510,7 @@ class UserMakeImmerse implements ShouldQueue | ... | @@ -510,8 +510,7 @@ class UserMakeImmerse implements ShouldQueue |
| 510 | 'fontcolor=' . $text_color . '@1.0:' . | 510 | 'fontcolor=' . $text_color . '@1.0:' . |
| 511 | 'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' . | 511 | 'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' . |
| 512 | 'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' . | 512 | 'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' . |
| 513 | -// 'box=1:boxborderw='. $this->calcBorderSize($component->font_size) . ':' . //todo 10号机不支持此选项 | 513 | + 'box=1:boxborderw='. $this->calcBorderSize($component->font_size) . ':' . |
| 514 | - 'box=1:' . | ||
| 515 | 'boxcolor=' . $text_bg_color . '@' . $opacity . '", '; | 514 | 'boxcolor=' . $text_bg_color . '@' . $opacity . '", '; |
| 516 | 515 | ||
| 517 | break; | 516 | break; |
| ... | @@ -530,8 +529,7 @@ class UserMakeImmerse implements ShouldQueue | ... | @@ -530,8 +529,7 @@ class UserMakeImmerse implements ShouldQueue |
| 530 | 'fontcolor=' . $text_color . '@1.0:' . | 529 | 'fontcolor=' . $text_color . '@1.0:' . |
| 531 | 'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' . | 530 | 'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' . |
| 532 | 'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' . | 531 | 'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' . |
| 533 | -// 'box=1:boxborderw='. $this->calcBorderSize($component->font_size) . ':' . //todo 10号机不支持此选项 | 532 | + 'box=1:boxborderw='. $this->calcBorderSize($component->font_size) . ':' . |
| 534 | - 'box=1:' . | ||
| 535 | 'boxcolor=' . $text_bg_color . '@' . $opacity . '", '; | 533 | 'boxcolor=' . $text_bg_color . '@' . $opacity . '", '; |
| 536 | 534 | ||
| 537 | break; | 535 | break; |
| ... | @@ -548,8 +546,7 @@ class UserMakeImmerse implements ShouldQueue | ... | @@ -548,8 +546,7 @@ class UserMakeImmerse implements ShouldQueue |
| 548 | 'fontcolor=' . $text_color . '@1.0:' . | 546 | 'fontcolor=' . $text_color . '@1.0:' . |
| 549 | 'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' . | 547 | 'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' . |
| 550 | 'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' . | 548 | 'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' . |
| 551 | -// 'box=1:boxborderw='. $this->calcBorderSize($component->font_size) . ':' . //todo 10号机不支持此选项 | 549 | + 'box=1:boxborderw='. $this->calcBorderSize($component->font_size) . ':' . |
| 552 | - 'box=1:' . | ||
| 553 | 'boxcolor=' . $text_bg_color . '@' . $opacity . '", '; | 550 | 'boxcolor=' . $text_bg_color . '@' . $opacity . '", '; |
| 554 | break; | 551 | break; |
| 555 | case 'feel': | 552 | case 'feel': |
| ... | @@ -565,8 +562,7 @@ class UserMakeImmerse implements ShouldQueue | ... | @@ -565,8 +562,7 @@ class UserMakeImmerse implements ShouldQueue |
| 565 | 'fontcolor=' . $text_color . '@1.0:' . | 562 | 'fontcolor=' . $text_color . '@1.0:' . |
| 566 | 'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' . | 563 | 'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' . |
| 567 | 'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' . | 564 | 'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' . |
| 568 | -// 'box=1:boxborderw='. $this->calcBorderSize($component->font_size) . ':' . //todo 10号机不支持此选项 | 565 | + 'box=1:boxborderw='. $this->calcBorderSize($component->font_size) . ':' . |
| 569 | - 'box=1:' . | ||
| 570 | 'boxcolor=' . $text_bg_color . '@' . $opacity . '", '; | 566 | 'boxcolor=' . $text_bg_color . '@' . $opacity . '", '; |
| 571 | break; | 567 | break; |
| 572 | } | 568 | } | ... | ... |
app/Jobs/UserMakeVideo.php
deleted
100644 → 0
| 1 | -<?php | ||
| 2 | - | ||
| 3 | -namespace App\Jobs; | ||
| 4 | - | ||
| 5 | -use App\Models\AdminMakeVideo; | ||
| 6 | -use App\Models\Immerse; | ||
| 7 | -use App\Models\User; | ||
| 8 | -use App\Models\VideoTemp; | ||
| 9 | -use Carbon\Carbon; | ||
| 10 | -use Illuminate\Bus\Queueable; | ||
| 11 | -use Illuminate\Contracts\Queue\ShouldBeUnique; | ||
| 12 | -use Illuminate\Contracts\Queue\ShouldQueue; | ||
| 13 | -use Illuminate\Foundation\Bus\Dispatchable; | ||
| 14 | -use Illuminate\Queue\InteractsWithQueue; | ||
| 15 | -use Illuminate\Queue\SerializesModels; | ||
| 16 | -use Illuminate\Support\Facades\Log; | ||
| 17 | -use Illuminate\Support\Facades\Storage; | ||
| 18 | -use Illuminate\Support\Str; | ||
| 19 | - | ||
| 20 | -class UserMakeVideo implements ShouldQueue | ||
| 21 | -{ | ||
| 22 | - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; | ||
| 23 | - | ||
| 24 | - public $immerse; | ||
| 25 | - | ||
| 26 | - public $resource_url; | ||
| 27 | - | ||
| 28 | - protected $ffmpeg; | ||
| 29 | - | ||
| 30 | - protected $ffprobe; | ||
| 31 | - | ||
| 32 | - protected $ffplay; | ||
| 33 | - | ||
| 34 | - protected $width; | ||
| 35 | - | ||
| 36 | - /** | ||
| 37 | - * Create a new job instance. | ||
| 38 | - * @param Immerse $immerse | ||
| 39 | - * @return void | ||
| 40 | - */ | ||
| 41 | - public function __construct(Immerse $immerse, $resource_url) | ||
| 42 | - { | ||
| 43 | - $this->immerse = $immerse; | ||
| 44 | - | ||
| 45 | - $this->resource_url = $resource_url; | ||
| 46 | - | ||
| 47 | - $this->ffmpeg = env('FFMPEG_CMD'); | ||
| 48 | - $this->ffprobe = env('FFPROBE_CMD'); | ||
| 49 | - $this->ffplay = env('FFPLAY_CMD'); | ||
| 50 | - } | ||
| 51 | - | ||
| 52 | - /** | ||
| 53 | - * Execute the job. | ||
| 54 | - * | ||
| 55 | - * @return void | ||
| 56 | - */ | ||
| 57 | - public function handle() | ||
| 58 | - { | ||
| 59 | - $file = Storage::disk('public')->path($this->resource_url); | ||
| 60 | - $is_bgm = isset($this->immerse->bgm) && $this->immerse->bgm != ''; | ||
| 61 | - $bgm = Storage::disk('public')->path($this->immerse->bgm); | ||
| 62 | - | ||
| 63 | - // 1.getmediainfo 记录时长,音频视频取最长。 | ||
| 64 | - $cmd = $this->ffprobe . ' -v quiet -print_format json -show_format -show_streams ' . escapeshellarg($file); | ||
| 65 | - $output = $this->execmd($cmd); | ||
| 66 | - $media_info = json_decode($output, true); | ||
| 67 | - if (json_last_error() === JSON_ERROR_UTF8) { | ||
| 68 | - $output = mb_convert_encoding($output, "UTF-8"); | ||
| 69 | - $media_info = json_decode($output, true); | ||
| 70 | - } | ||
| 71 | - | ||
| 72 | - /** 记录媒体信息时长*/ | ||
| 73 | - $media_file_time_length = isset($media_info['format']['duration']) ?: 0; | ||
| 74 | - if ($media_info['streams'][0]['codec_type'] !== 'video') { | ||
| 75 | - Log::channel('daily')->error('视频没有video track'); | ||
| 76 | - return; | ||
| 77 | - } | ||
| 78 | - | ||
| 79 | - // 2. 判断是否有视频原音,没有原音用背景音,没有背景音则混入anullsrc | ||
| 80 | - if ( $media_info['format']['nb_streams'] >= 2 ){ /** 音频视频轨都有 */ | ||
| 81 | - if ($is_bgm){ | ||
| 82 | - // 有背景音 融合 | ||
| 83 | - $audio = $this->getTempPath('.mp3'); | ||
| 84 | - $cmd = $this->ffmpeg. | ||
| 85 | - ' -y -i ' . escapeshellarg($file). | ||
| 86 | - ' -y -i ' . escapeshellarg($bgm). | ||
| 87 | - ' -filter_complex amix=inputs=2:duration=first:dropout_transition=2 ' . | ||
| 88 | - '-ar 48000 -ab 64k ' . escapeshellarg($audio); | ||
| 89 | - if (!$this->execmd($cmd)) return; | ||
| 90 | - | ||
| 91 | - $audio_input = ' -i ' . escapeshellarg($audio); | ||
| 92 | - $audio_filter = '[3:a]'; | ||
| 93 | - }else{ | ||
| 94 | - // 没有背景音 | ||
| 95 | - $audio_input = ''; | ||
| 96 | - $audio_filter = '[0:1]'; | ||
| 97 | - } | ||
| 98 | - }elseif ( $media_info['format']['nb_streams'] == 1 ){ | ||
| 99 | - $audio = $this->getTempPath('.mp3'); | ||
| 100 | - $cmd = $this->ffmpeg . | ||
| 101 | - ' -y -f lavfi -i aevalsrc=0:duration='. escapeshellarg($media_file_time_length) . | ||
| 102 | - ' -ar 48000 -ab 64k ' . escapeshellarg($audio); | ||
| 103 | - if (!$this->execmd($cmd)) return; | ||
| 104 | - | ||
| 105 | - if ($is_bgm){ | ||
| 106 | - $audio_empty = $audio; | ||
| 107 | - $audio = $this->getTempPath('.mp3'); | ||
| 108 | - $cmd = $this->ffmpeg. | ||
| 109 | - ' -y -i ' . escapeshellarg($audio_empty). | ||
| 110 | - ' -y -i ' . escapeshellarg($bgm). | ||
| 111 | - ' -filter_complex amix=inputs=2:duration=first:dropout_transition=2 ' . | ||
| 112 | - '-ar 48000 -ab 64k ' . escapeshellarg($audio); | ||
| 113 | - if (!$this->execmd($cmd)) return; | ||
| 114 | - } | ||
| 115 | - $audio_input = ' -i ' . escapeshellarg($audio); | ||
| 116 | - $audio_filter = '[3:a]'; | ||
| 117 | - | ||
| 118 | - }else{ /** 音频视频轨都没有 */ | ||
| 119 | - Log::channel('daily')->error('视频没有video track'); | ||
| 120 | - return; | ||
| 121 | - } | ||
| 122 | - | ||
| 123 | - try{ | ||
| 124 | - $end_wallpaper = Storage::disk('public')->path('ffmpeg') . "/end_wallpaper.png"; | ||
| 125 | - $thumbnail = Storage::disk('public')->path('ffmpeg') . "/thumbnail.png"; | ||
| 126 | - $font = Storage::disk('public')->path('ffmpeg') . "/arialuni.ttf"; | ||
| 127 | - | ||
| 128 | - $user = User::query()->find($this->immerse->user_id); | ||
| 129 | - $signature = $user->nickname ?? $user->email; | ||
| 130 | - | ||
| 131 | - // 生成贴纸和签名 | ||
| 132 | - $end_wallpaper = $this->wallpaperWithSignature($end_wallpaper, $thumbnail, $signature, $font); | ||
| 133 | - | ||
| 134 | - // 截取最后一帧 | ||
| 135 | - $last_frame_video = $this->getTempPath(); | ||
| 136 | - $this->width = $width = $media_info['streams'][0]['width']; | ||
| 137 | - $height = $media_info['streams'][0]['height']; | ||
| 138 | - $size = $width . 'x' . $height; | ||
| 139 | - $time_length = 0.7; | ||
| 140 | - $r = 24; | ||
| 141 | - $frame_n = $media_info['streams'][0]['nb_frames'] - 2; | ||
| 142 | - $cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($file) . | ||
| 143 | - " -f lavfi -i nullsrc=s={$size}:d={$time_length}:r={$r} -f lavfi -i aevalsrc=0:duration={$time_length}" . | ||
| 144 | - " -filter_complex \"[0:v]select='eq(n,{$frame_n})',setpts=PTS-STARTPTS[lastframe];[1:v][lastframe]overlay[v]\"" . | ||
| 145 | - ' -map [v] -map 2:a ' . escapeshellarg($last_frame_video); | ||
| 146 | - if (!$this->execmd($cmd)) return; | ||
| 147 | - | ||
| 148 | - | ||
| 149 | - $signature_x = 0; | ||
| 150 | - $signature_y = -20; | ||
| 151 | - $animate = $this->makeAnimate($last_frame_video, $end_wallpaper, '', $signature_x, $signature_y, $font); | ||
| 152 | - | ||
| 153 | - $watermark = Storage::disk('public')->path('ffmpeg/LOGO_eng.png'); | ||
| 154 | - | ||
| 155 | - $video = $this->getTempPath('.mp4',false); | ||
| 156 | - $cmd = $this->ffmpeg . ' -y '. | ||
| 157 | - ' -i ' . escapeshellarg($file). | ||
| 158 | - ' -i ' . escapeshellarg($animate). | ||
| 159 | - ' -i ' . escapeshellarg($watermark). | ||
| 160 | - $audio_input . | ||
| 161 | - ' -filter_complex "[0:0] ' . | ||
| 162 | - $this->getTextContentString(). | ||
| 163 | - ' [text];[text]'. | ||
| 164 | - ' [2:v]overlay=20:20[water];[water]' . $audio_filter . '[1:0][1:1] concat=n=2:v=1:a=1[v][a]" ' . | ||
| 165 | - ' -map [v] -map [a]'. | ||
| 166 | - ' -c:v libx264 -bt 256k -r 25' . | ||
| 167 | - ' -ar 44100 -ac 2 -qmin 30 -qmax 60 -profile:v baseline -preset fast ' . | ||
| 168 | - escapeshellarg($video); | ||
| 169 | - | ||
| 170 | - if (!$this->execmd($cmd)) return; | ||
| 171 | - | ||
| 172 | - // 全部合成以后创建 临境 | ||
| 173 | - $video_info = $this->mediainfo($video); | ||
| 174 | - | ||
| 175 | - $this->immerse->url = Str::of($video)->replace(Storage::disk('public')->path(''), ''); | ||
| 176 | - $this->immerse->state = 1; | ||
| 177 | - $this->immerse->duration = $video_info['format']['duration'] ?? 0.00; | ||
| 178 | - $this->immerse->size = $video_info['format']['size']; | ||
| 179 | - $this->immerse->save(); | ||
| 180 | - }catch (\Exception $exception){ | ||
| 181 | - $this->immerse->state = 2; | ||
| 182 | - $this->immerse->save(); | ||
| 183 | - Log::channel('daily')->error($exception->getMessage()); | ||
| 184 | - } | ||
| 185 | - | ||
| 186 | - return; | ||
| 187 | - } | ||
| 188 | - | ||
| 189 | - /** | ||
| 190 | - * 获取圆形头像 | ||
| 191 | - * @param $img | ||
| 192 | - * @param int $dst_w | ||
| 193 | - * @param int $dst_h | ||
| 194 | - * @return resource | ||
| 195 | - */ | ||
| 196 | - public function getCircleAvatar($img, $dst_w = 96, $dst_h = 96) | ||
| 197 | - { | ||
| 198 | - $w = 130; | ||
| 199 | - $h = 130; | ||
| 200 | - $src = imagecreatetruecolor($dst_w, $dst_h); | ||
| 201 | - imagecopyresized($src, $img, 0, 0, 0, 0, $dst_w, $dst_h, $w, $h); | ||
| 202 | - | ||
| 203 | - $newpic = imagecreatetruecolor($dst_w, $dst_h); | ||
| 204 | - imagealphablending($newpic, false); | ||
| 205 | - imagecopyresampled($newpic, $img, 0, 0, 0, 0, $dst_w, $dst_h, $w, $h); | ||
| 206 | - $mask = imagecreatetruecolor($dst_w, $dst_h); | ||
| 207 | - $transparent = imagecolorallocate($mask, 255, 0, 0); | ||
| 208 | - imagecolortransparent($mask,$transparent); | ||
| 209 | - imagefilledellipse($mask, $dst_w / 2, $dst_h / 2, $dst_w, $dst_h, $transparent); | ||
| 210 | - $red = imagecolorallocate($mask, 0, 0, 0); | ||
| 211 | - imagecopymerge($newpic, $mask, 0, 0, 0, 0, $dst_w, $dst_h, 100); | ||
| 212 | - imagecolortransparent($newpic,$red); | ||
| 213 | - imagesavealpha($newpic,true); | ||
| 214 | - imagefill($newpic, 0, 0, $red); | ||
| 215 | - imagedestroy($mask); | ||
| 216 | - return $newpic; | ||
| 217 | - } | ||
| 218 | - | ||
| 219 | - /** | ||
| 220 | - * 制作最后一帧 | ||
| 221 | - * @param $file | ||
| 222 | - * @return bool|string | ||
| 223 | - */ | ||
| 224 | - public function makeLastFrameVideo($file) { | ||
| 225 | - $video = $this->getTempPath(); | ||
| 226 | - $width = $this->getVideoWith($file); | ||
| 227 | - $height = $this->getVideoHeight($file); | ||
| 228 | - $size = $width . 'x' . $height; | ||
| 229 | - $time_length = 0.7; | ||
| 230 | - $r = 24; | ||
| 231 | - $frame_n = $this->getVideoFrameNum($file) - 2; | ||
| 232 | - $cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($file) . | ||
| 233 | - " -f lavfi -i nullsrc=s={$size}:d={$time_length}:r={$r} -f lavfi -i aevalsrc=0:duration={$time_length}" . | ||
| 234 | - " -filter_complex \"[0:v]select='eq(n,{$frame_n})',setpts=PTS-STARTPTS[lastframe];[1:v][lastframe]overlay[v]\"" . | ||
| 235 | - ' -map [v] -map 2:a ' . escapeshellarg($video); | ||
| 236 | - if ($this->execmd($cmd)) { | ||
| 237 | - return $video; | ||
| 238 | - } else { | ||
| 239 | - return false; | ||
| 240 | - } | ||
| 241 | - } | ||
| 242 | - | ||
| 243 | - /** | ||
| 244 | - * 用最后一帧和贴纸制作动画 | ||
| 245 | - * @param $last_frame_video | ||
| 246 | - * @param $end_wallpaper | ||
| 247 | - * @param $signature | ||
| 248 | - * @param $signature_x | ||
| 249 | - * @param $signature_y | ||
| 250 | - * @param $font | ||
| 251 | - * @return bool|string | ||
| 252 | - */ | ||
| 253 | - public function makeAnimate($last_frame_video, $end_wallpaper, $signature, $signature_x, $signature_y, $font) { | ||
| 254 | - $signature_x = $signature_x >= 0 ? '+' . $signature_x : '-' . abs($signature_x); | ||
| 255 | - $signature_y = $signature_y >= 0 ? '+' . $signature_y : '-' . abs($signature_y); | ||
| 256 | - $video = $this->getTempPath(); | ||
| 257 | - if ($signature !== '') { | ||
| 258 | - $cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($last_frame_video) . | ||
| 259 | - ' -loop 1 -i ' . escapeshellarg($end_wallpaper) . | ||
| 260 | - ' -filter_complex "'. | ||
| 261 | - 'geq=lum=\'if(lte(T,0.6), 255*T*(1/0.6),255)\',format=gray[grad];'. | ||
| 262 | - '[0:v]boxblur=8[blur];'. | ||
| 263 | - '[blur][1:v]overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2 [lay];[lay]'. | ||
| 264 | - 'drawtext='. | ||
| 265 | - 'fontfile=' . escapeshellarg($font) . ':'. | ||
| 266 | - 'text=' . escapeshellarg($signature) . ':'. | ||
| 267 | - 'fontsize=23:'. | ||
| 268 | - 'fontcolor=white@1.0:'. | ||
| 269 | - 'x=main_w/2' . $signature_x . ':'. | ||
| 270 | - 'y=main_h/2' . $signature_y . '[text];[text]'. | ||
| 271 | - '[grad]alphamerge[alpha];'. | ||
| 272 | - '[0:v][alpha]overlay'. | ||
| 273 | - '" ' . escapeshellarg($video); | ||
| 274 | - } else { | ||
| 275 | - $cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($last_frame_video) . | ||
| 276 | - ' -loop 1 -i ' . escapeshellarg($end_wallpaper) . | ||
| 277 | - ' -filter_complex "'. | ||
| 278 | - 'geq=lum=\'if(lte(T,0.6), 255*T*(1/0.6),255)\',format=gray[grad];'. | ||
| 279 | - '[0:v]boxblur=8[blur];'. | ||
| 280 | - '[blur][1:v]overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2 [lay];'. | ||
| 281 | - '[lay][grad]alphamerge[alpha];'. | ||
| 282 | - '[0:v][alpha]overlay'. | ||
| 283 | - '" ' . escapeshellarg($video); | ||
| 284 | - } | ||
| 285 | - if ($this->execmd($cmd)) { | ||
| 286 | - return $video; | ||
| 287 | - } else { | ||
| 288 | - return false; | ||
| 289 | - } | ||
| 290 | - } | ||
| 291 | - | ||
| 292 | - /** | ||
| 293 | - * 获取视频宽度 | ||
| 294 | - * @param $file | ||
| 295 | - * @param bool $cache | ||
| 296 | - * @return int|null | ||
| 297 | - */ | ||
| 298 | - public function getVideoWith($file, $cache = true) { | ||
| 299 | - $result = $this->getFirstVideoTrackOption($file, $option = 'width', $cache); | ||
| 300 | - if ($result) { | ||
| 301 | - return (int)$result; | ||
| 302 | - } else { | ||
| 303 | - return $result; | ||
| 304 | - } | ||
| 305 | - } | ||
| 306 | - | ||
| 307 | - /** | ||
| 308 | - * 获取视频高度 | ||
| 309 | - * @param $file | ||
| 310 | - * @param bool $cache | ||
| 311 | - * @return int|null | ||
| 312 | - */ | ||
| 313 | - public function getVideoHeight($file, $cache = true) { | ||
| 314 | - $result = $this->getFirstVideoTrackOption($file, $option = 'height', $cache); | ||
| 315 | - if ($result) { | ||
| 316 | - return (int)$result; | ||
| 317 | - } else { | ||
| 318 | - return $result; | ||
| 319 | - } | ||
| 320 | - } | ||
| 321 | - | ||
| 322 | - /** | ||
| 323 | - * 获取视频帧数 | ||
| 324 | - * @param $file | ||
| 325 | - * @param bool $cache | ||
| 326 | - * @return null | ||
| 327 | - */ | ||
| 328 | - public function getVideoFrameNum($file, $cache = true) { | ||
| 329 | - return $this->getFirstVideoTrackOption($file, $option = 'nb_frames', $cache); | ||
| 330 | - } | ||
| 331 | - | ||
| 332 | - | ||
| 333 | - public function getFirstVideoTrackOption($file, $option, $cache = true) { | ||
| 334 | - return $this->getFirstTrackOption($file, $option, $codec_type = 'video', $cache = true); | ||
| 335 | - } | ||
| 336 | - | ||
| 337 | - public function getFirstTrackOption($file, $option, $codec_type = '', $cache = true) { | ||
| 338 | - $result = $this->mediainfo($file, $cache); | ||
| 339 | - if (!isset($result['streams'])) { | ||
| 340 | - return null; | ||
| 341 | - } | ||
| 342 | - $_track = null; | ||
| 343 | - foreach($result['streams'] as $track) { | ||
| 344 | - if (empty($codec_type)) { | ||
| 345 | - $_track = $track; | ||
| 346 | - break; | ||
| 347 | - } elseif ($track['codec_type'] == $codec_type) { | ||
| 348 | - $_track = $track; | ||
| 349 | - break; | ||
| 350 | - } | ||
| 351 | - } | ||
| 352 | - if (isset($_track[$option])) { | ||
| 353 | - return $_track[$option]; | ||
| 354 | - } | ||
| 355 | - return null; | ||
| 356 | - } | ||
| 357 | - | ||
| 358 | - /*** | ||
| 359 | - * 获取视频信息(配合ffprobe) | ||
| 360 | - * @param $file | ||
| 361 | - * @param bool $cache | ||
| 362 | - * @return mixed | ||
| 363 | - */ | ||
| 364 | - public function mediainfo($file, $cache = true) { | ||
| 365 | - global $_mediainfo; | ||
| 366 | - $cmd = $this->ffprobe . ' -v quiet -print_format json -show_format -show_streams ' . escapeshellarg($file); | ||
| 367 | - if ($cache && isset($_mediainfo[$file])) { | ||
| 368 | - return $_mediainfo[$file]; | ||
| 369 | - } | ||
| 370 | - $output = $this->execmd($cmd); | ||
| 371 | - $data = json_decode($output, true); | ||
| 372 | - if (json_last_error() === JSON_ERROR_UTF8) { | ||
| 373 | - $output = mb_convert_encoding($output, "UTF-8"); | ||
| 374 | - $data = json_decode($output, true); | ||
| 375 | - } | ||
| 376 | - if ($cache) { | ||
| 377 | - $mediainfo[$file] = $data; | ||
| 378 | - } | ||
| 379 | - return $data; | ||
| 380 | - } | ||
| 381 | - | ||
| 382 | - /** | ||
| 383 | - * 获取输出临时文件名 | ||
| 384 | - * @param string $ext | ||
| 385 | - * @param bool $is_temp | ||
| 386 | - * @return string | ||
| 387 | - */ | ||
| 388 | - public function getTempPath($ext = '.mp4',$is_temp = true) | ||
| 389 | - { | ||
| 390 | - $filename = "/output_" . time() . rand(0, 10000); | ||
| 391 | - | ||
| 392 | - $prefix = $is_temp ? 'temp/' : 'video/'; | ||
| 393 | - $hash_hex = md5($filename); | ||
| 394 | - // 16进制表示的字符串一共32字节,表示16个二进制字节。 | ||
| 395 | - // 前16个字符用来第一级求摸,后16个用做第二级 | ||
| 396 | - $hash_hex_l1 = substr($hash_hex, 0, 8); | ||
| 397 | - $hash_hex_l2 = substr($hash_hex, 8, 8); | ||
| 398 | - $dir_l1 = hexdec($hash_hex_l1) % 256; | ||
| 399 | - $dir_l2 = hexdec($hash_hex_l2) % 512; | ||
| 400 | - $dir = $prefix . $dir_l1 . '/' . $dir_l2; | ||
| 401 | - | ||
| 402 | - if( !Storage::disk('public')->exists($dir)) Storage::disk('public')->makeDirectory($dir); | ||
| 403 | - | ||
| 404 | - return Storage::disk('public')->path($dir . $filename . $ext); | ||
| 405 | - } | ||
| 406 | - | ||
| 407 | - /** | ||
| 408 | - * 执行命令 | ||
| 409 | - * @param $cmd | ||
| 410 | - * @param bool $update_progress | ||
| 411 | - * @return string | ||
| 412 | - */ | ||
| 413 | - public function execmd($cmd, $update_progress = false) { | ||
| 414 | - echo $cmd . "\n". "\n". "\n"; | ||
| 415 | - $descriptorspec = array( | ||
| 416 | - 1 => array("pipe", "w"), // 标准输出,子进程向此管道中写入数据 | ||
| 417 | - ); | ||
| 418 | - $process = proc_open("{$cmd} 2>&1", $descriptorspec, $pipes); | ||
| 419 | - if (is_resource($process)) { | ||
| 420 | - $error0 = ''; | ||
| 421 | - $error1 = ''; | ||
| 422 | - $stdout = ''; | ||
| 423 | - while (!feof($pipes[1])) { | ||
| 424 | - $line = fgets($pipes[1], 150); | ||
| 425 | - $stdout .= $line; | ||
| 426 | - if ($line) { | ||
| 427 | - //记录错误 | ||
| 428 | - $error0 = $error1; | ||
| 429 | - $error1 = $line; | ||
| 430 | - if ($update_progress && | ||
| 431 | - false !== strpos($line, 'size=') && | ||
| 432 | - false !== strpos($line, 'time=') && | ||
| 433 | - false !== strpos($line, 'bitrate=')) | ||
| 434 | - { | ||
| 435 | - //记录进度 size= 3142kB time=00:00:47.22 bitrate= 545.1kbits/s | ||
| 436 | - $line = explode(' ', $line); | ||
| 437 | - $time = null; | ||
| 438 | - foreach ($line as $item) { | ||
| 439 | - $item = explode('=', $item); | ||
| 440 | - if (isset($item[0]) && isset($item[1]) && $item[0] == 'time') { | ||
| 441 | - $time = $item[1]; | ||
| 442 | - break; | ||
| 443 | - } | ||
| 444 | - } | ||
| 445 | - } | ||
| 446 | - } | ||
| 447 | - } | ||
| 448 | - // 切记:在调用 proc_close 之前关闭所有的管道以避免死锁。 | ||
| 449 | - fclose($pipes[1]); | ||
| 450 | - $exitedcode = proc_close($process); | ||
| 451 | - if ($exitedcode === 0) { | ||
| 452 | - return $stdout; | ||
| 453 | - } else { | ||
| 454 | - $error = trim($error0,"\n") . ' '. trim($error1,"\n"); | ||
| 455 | - // LogUtil::write(array("cmd:{$cmd}", "errno:{$exitedcode}", "stdout:{$stdout}"), __CLASS__); | ||
| 456 | - // ErrorUtil::triggerErrorMsg($error, $exitedcode); | ||
| 457 | - } | ||
| 458 | - } else { | ||
| 459 | - // return ErrorUtil::triggerErrorMsg('proc_open error'); | ||
| 460 | - } | ||
| 461 | - } | ||
| 462 | - | ||
| 463 | - /** | ||
| 464 | - * 贴纸和签名 | ||
| 465 | - * @param $end_wallpaper | ||
| 466 | - * @param $thumbnail | ||
| 467 | - * @param $signature | ||
| 468 | - * @param $font | ||
| 469 | - * @return string | ||
| 470 | - */ | ||
| 471 | - public function wallpaperWithSignature($end_wallpaper, $thumbnail, $signature, $font) { | ||
| 472 | - $_imagetype = $this->getImageType($thumbnail); | ||
| 473 | - $_img = null; | ||
| 474 | - switch ($_imagetype) { | ||
| 475 | - case 'gif': | ||
| 476 | - if (function_exists('imagecreatefromgif')) { | ||
| 477 | - $_img = imagecreatefromgif($thumbnail); | ||
| 478 | - } | ||
| 479 | - break; | ||
| 480 | - case 'jpg': | ||
| 481 | - case 'jpeg': | ||
| 482 | - $_img = imagecreatefromjpeg($thumbnail); | ||
| 483 | - break; | ||
| 484 | - case 'png': | ||
| 485 | - $_img = imagecreatefrompng($thumbnail); | ||
| 486 | - break; | ||
| 487 | - default: | ||
| 488 | - $_img = imagecreatefromstring($thumbnail); | ||
| 489 | - break; | ||
| 490 | - } | ||
| 491 | - $width = 130; | ||
| 492 | - $height = 130; | ||
| 493 | - $_width = 130; | ||
| 494 | - $_height = 130; | ||
| 495 | - if(is_resource($_img)){ | ||
| 496 | - $_width = imagesx($_img); | ||
| 497 | - $_height = imagesy($_img); | ||
| 498 | - } | ||
| 499 | - | ||
| 500 | - $bite = $_width / $_height; | ||
| 501 | - | ||
| 502 | - if($_width > $_height){ | ||
| 503 | - if($_width > $width){ | ||
| 504 | - $height = round($width / $bite); | ||
| 505 | - } | ||
| 506 | - }else{ | ||
| 507 | - if($_height > $height){ | ||
| 508 | - $width = round($height * $bite); | ||
| 509 | - } | ||
| 510 | - } | ||
| 511 | - | ||
| 512 | - $tmpimg = imagecreatetruecolor($width,$height); | ||
| 513 | - if(function_exists('imagecopyresampled')) { | ||
| 514 | - imagecopyresampled($tmpimg, $_img, 0, 0, 0, 0, $width, $height, $_width, $_height); | ||
| 515 | - } else { | ||
| 516 | - imagecopyresized($tmpimg, $_img, 0, 0, 0, 0, $width, $height, $_width, $_height); | ||
| 517 | - } | ||
| 518 | - if(is_resource($_img)) imagedestroy($_img); | ||
| 519 | - $_img = $this->getCircleAvatar($tmpimg); | ||
| 520 | - if(is_resource($tmpimg)) imagedestroy($tmpimg); | ||
| 521 | - | ||
| 522 | - $wp = $this->imagesMerge($end_wallpaper, $_img); | ||
| 523 | -// $white = imagecolorallocate($wp, 0xd0, 0xcd, 0xcc); | ||
| 524 | - $white = imagecolorallocate($wp, 0xDC, 0x14, 0x3C); //fixme 字体颜色 | ||
| 525 | - imagettftext($wp, 20, 0, 75, 240, $white, $font, $signature); | ||
| 526 | - | ||
| 527 | -// $dst = "./output_new_end_wallpaper.png"; | ||
| 528 | - $dst = Storage::disk('public')->path('ffmpeg') . "/output_new_end_wallpaper.png"; | ||
| 529 | - imagepng($wp, $dst); | ||
| 530 | - if(is_resource($end_wallpaper)) imagedestroy($end_wallpaper); | ||
| 531 | - if(is_resource($_img)) imagedestroy($_img); | ||
| 532 | - | ||
| 533 | - return $dst; | ||
| 534 | - } | ||
| 535 | - | ||
| 536 | - /** | ||
| 537 | - * 获取图像文件类型 | ||
| 538 | - * @param $img_name | ||
| 539 | - * @return string | ||
| 540 | - */ | ||
| 541 | - public function getImageType($img_name) | ||
| 542 | - { | ||
| 543 | - if (preg_match("/\.(jpg|jpeg|gif|png)$/i", $img_name, $matches)){ | ||
| 544 | - $type = strtolower($matches[1]); | ||
| 545 | - }else{ | ||
| 546 | - $type = "string"; | ||
| 547 | - } | ||
| 548 | - return $type; | ||
| 549 | - } | ||
| 550 | - | ||
| 551 | - /** | ||
| 552 | - * 多图融合 | ||
| 553 | - * @param $end_wallpaper | ||
| 554 | - * @param $thumbnail | ||
| 555 | - * @return resource | ||
| 556 | - */ | ||
| 557 | - public function imagesMerge($end_wallpaper, $thumbnail) { | ||
| 558 | - $end_wallpaper = imagecreatefrompng($end_wallpaper); | ||
| 559 | - $background = imagecreatefrompng(Storage::disk('public')->path('ffmpeg/background.png')); | ||
| 560 | - imagesavealpha($background,true); | ||
| 561 | - $temp_wallpaper = imagecreatetruecolor(350, 204); | ||
| 562 | - $color = imagecolorallocate($temp_wallpaper, 0xd0, 0xcd, 0xcc); | ||
| 563 | -// $color = imagecolorallocate($temp_wallpaper, 0xDC, 0x14, 0x3C); | ||
| 564 | - imagefill($temp_wallpaper, 0, 0, $color); | ||
| 565 | - imageColorTransparent($temp_wallpaper, $color); | ||
| 566 | - imagecopyresampled($temp_wallpaper, $end_wallpaper, 0, 0, 0, 0, imagesx($temp_wallpaper), imagesy($temp_wallpaper), imagesx($end_wallpaper), imagesy($end_wallpaper)); | ||
| 567 | - imagecopymerge($background, $temp_wallpaper, 0, 0, 0, 0, imagesx($temp_wallpaper), imagesy($temp_wallpaper), 60); | ||
| 568 | - imagecopymerge($background, $thumbnail, 127, 26, 0, 0, imagesx($thumbnail), imagesy($thumbnail), 100); | ||
| 569 | - return $background; | ||
| 570 | - } | ||
| 571 | - | ||
| 572 | - /** | ||
| 573 | - * logo 大小转换 | ||
| 574 | - * @param $logo | ||
| 575 | - * @return bool | ||
| 576 | - */ | ||
| 577 | - public function translateLogo($logo) | ||
| 578 | - { | ||
| 579 | - $image = Storage::disk('public')->path('ffmpeg/output_150x150.jpg'); | ||
| 580 | - $cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($logo) . | ||
| 581 | - ' -vf scale=150:150 ' . escapeshellarg($image); | ||
| 582 | - if ($this->execmd($cmd)) { | ||
| 583 | - return $image; | ||
| 584 | - } else { | ||
| 585 | - return false; | ||
| 586 | - } | ||
| 587 | - } | ||
| 588 | - | ||
| 589 | - public function getTextContentString() | ||
| 590 | - { | ||
| 591 | - $components = $this->immerse->temp()->first()->components()->get(); | ||
| 592 | - | ||
| 593 | - $font = Storage::disk('public')->path('ffmpeg/arialuni.ttf'); | ||
| 594 | - | ||
| 595 | - $drawtext = ''; | ||
| 596 | - | ||
| 597 | - foreach ($components as $component) { | ||
| 598 | - switch ($component->name){ | ||
| 599 | - case 'one_poem': | ||
| 600 | - $content = $this->immerse->poem->content; | ||
| 601 | - $text_file = $this->getTempPath('txt'); | ||
| 602 | - file_put_contents($text_file, $content); | ||
| 603 | - | ||
| 604 | - $text_color = $component->text_color ?? 'white'; | ||
| 605 | - $text_bg_color = $component->text_bg_color ?? '0xd0cdcc'; | ||
| 606 | - $opacity = $component->opacity ? $component->opacity / 100 : '0.5'; | ||
| 607 | - | ||
| 608 | - $drawtext .= 'drawtext="'. | ||
| 609 | - 'fontfile=' . escapeshellarg($font) . ':' . | ||
| 610 | - 'textfile=' . escapeshellarg($text_file) . ':' . | ||
| 611 | - 'fontsize=' . $this->calcFontSize($component->font_size,$content) . ':' . | ||
| 612 | - 'fontcolor=' . $text_color . '@1.0:' . | ||
| 613 | - 'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' . | ||
| 614 | - 'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' . | ||
| 615 | - 'box=1:boxcolor=' . $text_bg_color . '@' . $opacity . '", '; | ||
| 616 | - | ||
| 617 | - break; | ||
| 618 | - case 'every_poem': | ||
| 619 | - break; | ||
| 620 | - case 'weather': | ||
| 621 | - $content = '多云'; | ||
| 622 | - $text_color = $component->text_color ?? 'white'; | ||
| 623 | - $text_bg_color = $component->text_bg_color ?? '0xd0cdcc'; | ||
| 624 | - $opacity = $component->opacity ? $component->opacity / 100 : '0.5'; | ||
| 625 | - | ||
| 626 | - $drawtext .= 'drawtext="'. | ||
| 627 | - 'fontfile=' . escapeshellarg($font) . ':' . | ||
| 628 | - 'text=' . escapeshellarg($content) . ':' . | ||
| 629 | - 'fontsize=' . $this->calcFontSize($component->font_size,$content) . ':' . | ||
| 630 | - 'fontcolor=' . $text_color . '@1.0:' . | ||
| 631 | - 'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' . | ||
| 632 | - 'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' . | ||
| 633 | - 'box=1:boxcolor=' . $text_bg_color . '@' . $opacity . '", '; | ||
| 634 | - | ||
| 635 | - break; | ||
| 636 | - case 'date': | ||
| 637 | - $content = Carbon::now()->format('Y年m月d日H时'); | ||
| 638 | - $text_color = $component->text_color ?? 'white'; | ||
| 639 | - $text_bg_color = $component->text_bg_color ?? '0xd0cdcc'; | ||
| 640 | - $opacity = $component->opacity ? $component->opacity / 100 : '0.5'; | ||
| 641 | - | ||
| 642 | - $drawtext .= 'drawtext="'. | ||
| 643 | - 'fontfile=' . escapeshellarg($font) . ':' . | ||
| 644 | - 'text=' . escapeshellarg($content) . ':' . | ||
| 645 | - 'fontsize=' . $this->calcFontSize($component->font_size,$content) . ':' . | ||
| 646 | - 'fontcolor=' . $text_color . '@1.0:' . | ||
| 647 | - 'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' . | ||
| 648 | - 'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' . | ||
| 649 | - 'box=1:boxcolor=' . $text_bg_color . '@' . $opacity . '", '; | ||
| 650 | - break; | ||
| 651 | - case 'feel': | ||
| 652 | - $content = $this->immerse->content; | ||
| 653 | - $text_color = $component->text_color ?? 'white'; | ||
| 654 | - $text_bg_color = $component->text_bg_color ?? '0xd0cdcc'; | ||
| 655 | - $opacity = $component->opacity ? $component->opacity / 100 : '0.5'; | ||
| 656 | - | ||
| 657 | - $drawtext .= 'drawtext="'. | ||
| 658 | - 'fontfile=' . escapeshellarg($font) . ':' . | ||
| 659 | - 'text=' . escapeshellarg($content) . ':' . | ||
| 660 | - 'fontsize=' . $this->calcFontSize($component->font_size,$content) . ':' . | ||
| 661 | - 'fontcolor=' . $text_color . '@1.0:' . | ||
| 662 | - 'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' . | ||
| 663 | - 'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' . | ||
| 664 | - 'box=1:boxcolor=' . $text_bg_color . '@' . $opacity . '", '; | ||
| 665 | - break; | ||
| 666 | - } | ||
| 667 | - } | ||
| 668 | - | ||
| 669 | - return rtrim($drawtext,', '); | ||
| 670 | - } | ||
| 671 | - | ||
| 672 | - /** | ||
| 673 | - * @param $width | ||
| 674 | - * @param $content | ||
| 675 | - * @return float | ||
| 676 | - */ | ||
| 677 | - public function calcFontSize($width, $content) | ||
| 678 | - { | ||
| 679 | - $max_len = 1; | ||
| 680 | - foreach (explode("\n",$content) as $item){ | ||
| 681 | - if (mb_strlen($item) > $max_len){ | ||
| 682 | - $max_len = mb_strlen($item); | ||
| 683 | - } | ||
| 684 | - } | ||
| 685 | - | ||
| 686 | - return ceil($this->width * $width / 100 / $max_len); | ||
| 687 | - } | ||
| 688 | -} |
-
Please register or login to post a comment