李帅

1.重构一言表

......@@ -115,7 +115,6 @@ class VideoTempController extends AdminController
$form->hasMany('components','组件', function (Form\NestedForm $form) {
$form->select('name','组件名称')->options([
'one_poem_with_annotate' => '一言带注解组件',
'every_poem' => '每日一言组件',
'one_poem' => '一言组件',
'weather' => '天气组件',
'date' => '日期组件',
......@@ -123,8 +122,17 @@ class VideoTempController extends AdminController
]);
$form->select('position','组件位置')->options(VideoTemp::POSITION_OPTIONS);
$form->switch('fade', '淡入淡出')->help("开启淡入淡出会使背景色失效");
$form->radio('draw', '文字效果')
->options(['fade'=>'淡入淡出', 'fix'=>'固定显示'])->default('fade')
->when('fade',function (Form\NestedForm $form){
$form->selectTable('font_file','字体')
->title('字体选择')
->from(FontTable::make())
->model(Font::class,'file','name');
$form->number('font_size', '字号')->default(12)->min(12);
$form->color('text_color', '字体颜色')->default('#f5f5f5')->addElementClass('text_color');
})
->when('fix',function (Form\NestedForm $form){
$form->number('text_bg_box', '背景厚度')->default(0)
->addElementClass('text_bg_box')->help('设置背景块边缘厚度(用于在背景块边缘用背景色填充一圈),默认为0');
$form->color('text_bg_color', '背景色')->default('#5c6bc6')->addElementClass('text_bg_color');
......@@ -137,8 +145,7 @@ class VideoTempController extends AdminController
$form->number('opacity', '透明度')->min(0)->max(100)
->addElementClass('opacity')->default(100)
->help('范围为0-100,100表示不透明,0表示完全透明');
$form->switch('fix_bounds', '避免剪切');
});
});
$form->hidden('state')->default(1)
......
......@@ -26,9 +26,11 @@ class MakeVideo implements ShouldQueue
protected $ffprobe;
protected $ffplay;
protected $media_info;
protected $width;
protected $output_width;
protected $output_height;
/**
* Create a new job instance.
......@@ -38,159 +40,113 @@ class MakeVideo implements ShouldQueue
public function __construct(AdminMakeVideo $adminMakeVideo)
{
$this->adminMakeVideo = $adminMakeVideo;
$this->ffmpeg = env('FFMPEG_CMD');
$this->ffprobe = env('FFPROBE_CMD');
$this->ffplay = env('FFPLAY_CMD');
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$adminMakeVideo = $this->adminMakeVideo;
$file = Storage::disk('public')->path($adminMakeVideo->video_url);
$is_bgm = $adminMakeVideo->bg_music;
$bgm = Storage::disk('public')->path($adminMakeVideo->bgm_url);
// 1.getmediainfo 记录时长,音频视频取最长。
$cmd = $this->ffprobe . ' -v quiet -print_format json -show_format -show_streams ' . escapeshellarg($file);
$output = $this->execmd($cmd);
$media_info = json_decode($output, true);
if (json_last_error() === JSON_ERROR_UTF8) {
$output = mb_convert_encoding($output, "UTF-8");
$media_info = json_decode($output, true);
}
/** 记录媒体信息时长*/
$media_file_time_length = isset($media_info['format']['duration']) ? $media_info['format']['duration'] : 0;
if ($media_info['streams'][0]['codec_type'] !== 'video') {
Log::channel('daily')->error('视频没有video track');
return;
}
// 2. 判断是否有视频原音,没有原音用背景音,没有背景音则混入anullsrc
if ( $media_info['format']['nb_streams'] >= 2 ){ /** 音频视频轨都有 */
if ($is_bgm){
$this->output_width = 720;
$this->output_height = 1280;
$file = $this->getAbsolutePath($adminMakeVideo->video_url);
// 分析视频
$media_info = $this->mediainfo($file);
// 素材准备
$drawtext = $this->getTextContentString();
if ($media_info['format']['nb_streams'] >= 2) {
/** 音频视频轨都有 */
if ($is_bgm) {
// 有背景音 融合
$audio = $this->getTempPath('.mp3');
$cmd = $this->ffmpeg.
' -y -i ' . escapeshellarg($file).
' -y -i ' . escapeshellarg($bgm).
$audio = $this->getAbsolutePath($this->getTempPath('.mp3','audio'));
$cmd = $this->ffmpeg .
' -y -i ' . escapeshellarg($file) .
' -y -i ' . escapeshellarg($bgm) .
' -filter_complex amix=inputs=2:duration=first:dropout_transition=2 ' .
'-ar 48000 -ab 64k ' . escapeshellarg($audio);
if (!$this->execmd($cmd)) return;
$audio_input = ' -i ' . escapeshellarg($audio);
$audio_filter = '[3:a]';
}else{
$audio_filter = '2:a';
} else {
// 没有背景音
$audio_input = '';
$audio_filter = '[0:1]';
$audio_filter = '0:a';
}
}elseif ( $media_info['format']['nb_streams'] == 1 ){
$audio = $this->getTempPath('.mp3');
} elseif ($media_info['format']['nb_streams'] == 1) {
/** 只有视频轨 */
// 生成一段无声音频
$audio = $this->getAbsolutePath($this->getTempPath('.mp3','audio'));
$cmd = $this->ffmpeg .
' -y -f lavfi -i aevalsrc=0:duration='. escapeshellarg($media_file_time_length) .
' -y -f lavfi -i aevalsrc=0:duration=' . escapeshellarg($media_info['format']['duration']) .
' -ar 48000 -ab 64k ' . escapeshellarg($audio);
if (!$this->execmd($cmd)) return;
if ($is_bgm){
if ($is_bgm) {
// 有背景音 融合
$audio_empty = $audio;
$audio = $this->getTempPath('.mp3');
$cmd = $this->ffmpeg.
' -y -i ' . escapeshellarg($audio_empty).
' -y -i ' . escapeshellarg($bgm).
$audio = $this->getAbsolutePath($this->getTempPath('.mp3'));
$cmd = $this->ffmpeg .
' -y -i ' . escapeshellarg($audio_empty) .
' -y -i ' . escapeshellarg($bgm) .
' -filter_complex amix=inputs=2:duration=first:dropout_transition=2 ' .
'-ar 48000 -ab 64k ' . escapeshellarg($audio);
if (!$this->execmd($cmd)) return;
}
$audio_input = ' -i ' . escapeshellarg($audio);
$audio_filter = '[3:a]';
}else{ /** 音频视频轨都没有 */
Log::channel('daily')->error('视频没有video track');
$audio_filter = '2:a';
} else {
/** 音频视频轨都没有 */
Log::channel('daily')->error('视频没有video track, url:' . $file);
return;
}
if ($this->adminMakeVideo->thumbnail == 2){
$thumbnail = $this->getTempPath('.jpg','thumbnail');
if ($adminMakeVideo->thumbnail == 2){
// 截取中间帧作为视频封面
$frame = ceil($media_info['streams'][0]['nb_frames'] / 2);
$thumbnail = $this->getTempPath('.jpg',false);
$cmd = $this->ffmpeg . ' -y ' .
' -i ' . escapeshellarg($file) .
' -filter_complex "[0:v]select=\'eq(n,' . $frame . ')\'[img]" ' .
' -map [img]'.
' -frames:v 1 -s 720x1280 -preset superfast '.
escapeshellarg($thumbnail);
' -frames:v 1 -s ' . $this->output_width . 'x' . $this->output_height . ' -preset superfast ' .
escapeshellarg($this->getAbsolutePath($thumbnail));
if (!$this->execmd($cmd)) return ;
}else{
$thumbnail = $adminMakeVideo->thumbnail_url;
// 手动上传封面
$origin_thumbnail = Storage::disk('public')->path($adminMakeVideo->thumbnail_url);
// 将封面分辨率改为指定分辨率
$cmd = $this->ffmpeg . ' -y ' .
' -i ' . escapeshellarg($origin_thumbnail) .
'-s ' . $this->output_width . 'x' . $this->output_height . ' -preset superfast ' .
escapeshellarg($this->getAbsolutePath($thumbnail));
if (!$this->execmd($cmd)) return ;
}
$end_wallpaper = Storage::disk('public')->path('ffmpeg') . "/end_wallpaper.png";
$avatar = Storage::disk('public')->path('ffmpeg') . "/thumbnail.png";
$font = Storage::disk('public')->path('ffmpeg') . "/arialuni.ttf";
$signature = "一言 · 官方出品";
// 生成贴纸和签名
$end_wallpaper = $this->wallpaperWithSignature($end_wallpaper, $avatar, $signature, $font);
// 截取最后一帧
$last_frame_video = $this->getTempPath();
$this->width = $width = $media_info['streams'][0]['width'];
$height = $media_info['streams'][0]['height'];
$size = $width . 'x' . $height;
$time_length = 0.7;
$r = 24;
$frame_n = $media_info['streams'][0]['nb_frames'] - 2;
$cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($file) .
" -f lavfi -i nullsrc=s={$size}:d={$time_length}:r={$r} -f lavfi -i aevalsrc=0:duration={$time_length}" .
" -filter_complex \"[0:v]select='eq(n,{$frame_n})',setpts=PTS-STARTPTS[lastframe];[1:v][lastframe]overlay[v]\"" .
' -map [v] -map 2:a ' . escapeshellarg($last_frame_video);
if (!$this->execmd($cmd)) return;
$signature_x = 0;
$signature_y = -20;
$animate = $this->makeAnimate($last_frame_video, $end_wallpaper, '', $signature_x, $signature_y, $font);
$watermark = Storage::disk('public')->path('ffmpeg/LOGO_eng.png');
$video = $this->getTempPath('.mp4',false);
$output = $this->getTempPath('.mp4','video');
$cmd = $this->ffmpeg . ' -y '.
' -i ' . escapeshellarg($file).
' -i ' . escapeshellarg($animate).
' -i ' . escapeshellarg($watermark).
$audio_input .
' -filter_complex "[0:0] ' .
$this->getTextContentString().
' -filter_complex "[0:v]scale=' . $this->output_width . ':' . $this->output_height . ',' . $drawtext .
' [text];[text]'.
' [2:v]overlay=20:20[water];[water]' . $audio_filter . '[1:0][1:1] concat=n=2:v=1:a=1[v][a]" ' .
' -map [v] -map [a]'.
' [1:v]overlay=20:20[v]" ' .
' -map [v] -map '. $audio_filter .
' -c:v libx264 -bt 256k -r 25' .
' -ar 44100 -ac 2 -qmin 30 -qmax 60 -profile:v baseline -preset fast ' .
escapeshellarg($video);
escapeshellarg($this->getAbsolutePath($output));
$exec = $this->execmd($cmd);
if (is_array($exec)){
print_r($exec,1);
return;
}
try{
// 全部合成以后创建 临境
$video_info = $this->mediainfo($video);
if (!$this->execmd($cmd)) return ;
$video_info = $this->mediainfo($this->getAbsolutePath($output));
Immerse::query()->create([
'user_id' => 1,
'title' => '',
'content' => $this->adminMakeVideo->feel,
'url' => str_replace(Storage::disk('public')->path(''),'',$video),
'type' => $this->adminMakeVideo->type == 1 ? 2 : 1,
'weather' => $adminMakeVideo->weather,
'huangli' => $adminMakeVideo->huangli,
'content' => $adminMakeVideo->feel,
'location' => $adminMakeVideo->location,
'longitude' => $adminMakeVideo->longitude,
'latitude' => $adminMakeVideo->latitude,
'url' => $output,
'type' => $adminMakeVideo->type == 1 ? 2 : 1,
'upload_file' => '',
'duration' => $video_info['format']['duration'],
'size' => $video_info['format']['size'],
......@@ -198,243 +154,58 @@ class MakeVideo implements ShouldQueue
'origin_image_url' => '',
'poem_id' => $this->adminMakeVideo->poem_id,
'temp_id' => $this->adminMakeVideo->temp_id,
'thumbnail' => str_replace(Storage::disk('public')->path(''), '', $thumbnail),
'thumbnail' => $thumbnail,
'state' => 1,
'bgm' => $this->adminMakeVideo->bgm_url ?? '',
'bgm' => $is_bgm ? $bgm : '',
]);
}catch (\Exception $exception){
// echo $exception->getMessage();
}
}
/**
* 获取圆形头像
* @param $img
* @param int $dst_w
* @param int $dst_h
* @return resource
* Execute the job.
*
* @return void
*/
public function getCircleAvatar($img, $dst_w = 96, $dst_h = 96)
public function handle()
{
$w = 130;
$h = 130;
$src = imagecreatetruecolor($dst_w, $dst_h);
imagecopyresized($src, $img, 0, 0, 0, 0, $dst_w, $dst_h, $w, $h);
$file = $this->getAbsolutePath($this->adminMakeVideo->video_url);
// 分析视频
$this->media_info = $this->mediaInfo($file);
$newpic = imagecreatetruecolor($dst_w, $dst_h);
imagealphablending($newpic, false);
imagecopyresampled($newpic, $img, 0, 0, 0, 0, $dst_w, $dst_h, $w, $h);
$mask = imagecreatetruecolor($dst_w, $dst_h);
$transparent = imagecolorallocate($mask, 255, 0, 0);
imagecolortransparent($mask,$transparent);
imagefilledellipse($mask, $dst_w / 2, $dst_h / 2, $dst_w, $dst_h, $transparent);
$red = imagecolorallocate($mask, 0, 0, 0);
imagecopymerge($newpic, $mask, 0, 0, 0, 0, $dst_w, $dst_h, 100);
imagecolortransparent($newpic,$red);
imagesavealpha($newpic,true);
imagefill($newpic, 0, 0, $red);
imagedestroy($mask);
return $newpic;
}
// 准备素材
/**
* 制作最后一帧
* @param $file
* @return bool|string
*/
public function makeLastFrameVideo($file) {
$video = $this->getTempPath();
$width = $this->getVideoWith($file);
$height = $this->getVideoHeight($file);
$size = $width . 'x' . $height;
$time_length = 0.7;
$r = 24;
$frame_n = $this->getVideoFrameNum($file) - 2;
$cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($file) .
" -f lavfi -i nullsrc=s={$size}:d={$time_length}:r={$r} -f lavfi -i aevalsrc=0:duration={$time_length}" .
" -filter_complex \"[0:v]select='eq(n,{$frame_n})',setpts=PTS-STARTPTS[lastframe];[1:v][lastframe]overlay[v]\"" .
' -map [v] -map 2:a ' . escapeshellarg($video);
if ($this->execmd($cmd)) {
return $video;
} else {
return false;
}
}
// 组装文字参数
/**
* 用最后一帧和贴纸制作动画
* @param $last_frame_video
* @param $end_wallpaper
* @param $signature
* @param $signature_x
* @param $signature_y
* @param $font
* @return bool|string
*/
public function makeAnimate($last_frame_video, $end_wallpaper, $signature, $signature_x, $signature_y, $font) {
$signature_x = $signature_x >= 0 ? '+' . $signature_x : '-' . abs($signature_x);
$signature_y = $signature_y >= 0 ? '+' . $signature_y : '-' . abs($signature_y);
$video = $this->getTempPath();
if ($signature !== '') {
$cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($last_frame_video) .
' -t 0.7 -loop 1 -i ' . escapeshellarg($end_wallpaper) .
' -filter_complex "'.
'geq=lum=\'if(lte(T,0.6), 255*T*(1/0.6),255)\',format=gray[grad];'.
'[0:v]boxblur=8[blur];'.
'[blur][1:v]overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2 [lay];[lay]'.
'drawtext='.
'fontfile=' . escapeshellarg($font) . ':'.
'text=' . escapeshellarg($signature) . ':'.
'fontsize=23:'.
'fontcolor=white@1.0:'.
'x=main_w/2' . $signature_x . ':'.
'y=main_h/2' . $signature_y . '[text];[text]'.
'[grad]alphamerge[alpha];'.
'[0:v][alpha]overlay'.
'" ' . escapeshellarg($video);
} else {
$cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($last_frame_video) .
' -t 0.7 -loop 1 -i ' . escapeshellarg($end_wallpaper) .
' -filter_complex "'.
'geq=lum=\'if(lte(T,0.6), 255*T*(1/0.6),255)\',format=gray[grad];'.
'[0:v]boxblur=8[blur];'.
'[blur][1:v]overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2 [lay];'.
'[lay][grad]alphamerge[alpha];'.
'[0:v][alpha]overlay'.
'" ' . escapeshellarg($video);
}
if ($this->execmd($cmd)) {
return $video;
} else {
return false;
}
}
// 合成视频
/**
* 获取视频宽度
* @param $file
* @param bool $cache
* @return int|null
*/
public function getVideoWith($file, $cache = true) {
$result = $this->getFirstVideoTrackOption($file, $option = 'width', $cache);
if ($result) {
return (int)$result;
} else {
return $result;
}
}
/**
* 获取视频高度
* @param $file
* @param bool $cache
* @return int|null
*/
public function getVideoHeight($file, $cache = true) {
$result = $this->getFirstVideoTrackOption($file, $option = 'height', $cache);
if ($result) {
return (int)$result;
} else {
return $result;
}
}
// 制作封面图
/**
* 获取视频帧数
* @param $file
* @param bool $cache
* @return null
*/
public function getVideoFrameNum($file, $cache = true) {
return $this->getFirstVideoTrackOption($file, $option = 'nb_frames', $cache);
// 分析视频 入库
}
public function getAbsolutePath($path)
{
if ($path == '') return '';
public function getFirstVideoTrackOption($file, $option, $cache = true) {
return $this->getFirstTrackOption($file, $option, $codec_type = 'video', $cache = true);
return Storage::disk('public')->path($path);
}
public function getFirstTrackOption($file, $option, $codec_type = '', $cache = true) {
$result = $this->mediainfo($file, $cache);
if (!isset($result['streams'])) {
return null;
}
$_track = null;
foreach($result['streams'] as $track) {
if (empty($codec_type)) {
$_track = $track;
break;
} elseif ($track['codec_type'] == $codec_type) {
$_track = $track;
break;
}
}
if (isset($_track[$option])) {
return $_track[$option];
}
return null;
}
public function mediaInfo($file)
{
if ($this->media_info) return $this->media_info;
/***
* 获取视频信息(配合ffprobe)
* @param $file
* @param bool $cache
* @return mixed
*/
public function mediainfo($file, $cache = true) {
global $_mediainfo;
$cmd = $this->ffprobe . ' -v quiet -print_format json -show_format -show_streams ' . escapeshellarg($file);
if ($cache && isset($_mediainfo[$file])) {
return $_mediainfo[$file];
}
$output = $this->execmd($cmd);
$data = json_decode($output, true);
if (json_last_error() === JSON_ERROR_UTF8) {
$output = mb_convert_encoding($output, "UTF-8");
$data = json_decode($output, true);
}
if ($cache) {
$mediainfo[$file] = $data;
}
$this->media_info = $data;
return $data;
}
/**
* 获取输出临时文件名
* @param string $ext
* @param bool $is_temp
* @return string
*/
public function getTempPath($ext = '.mp4',$is_temp = true)
{
$filename = "/output_" . time() . rand(0, 10000);
$prefix = $is_temp ? 'temp/' : 'video/';
$hash_hex = md5($filename);
// 16进制表示的字符串一共32字节,表示16个二进制字节。
// 前16个字符用来第一级求摸,后16个用做第二级
$hash_hex_l1 = substr($hash_hex, 0, 8);
$hash_hex_l2 = substr($hash_hex, 8, 8);
$dir_l1 = hexdec($hash_hex_l1) % 256;
$dir_l2 = hexdec($hash_hex_l2) % 512;
$dir = $prefix . $dir_l1 . '/' . $dir_l2;
if( !Storage::disk('public')->exists($dir)) Storage::disk('public')->makeDirectory($dir);
return Storage::disk('public')->path($dir . $filename . $ext);
}
/**
* 执行命令
* @param $cmd
* @param bool $update_progress
* @return mixed
*/
public function execmd($cmd, $update_progress = false) {
// echo $cmd . "\n". "\n". "\n";
echo $cmd . "\n". "\n". "\n";
$descriptorspec = array(
1 => array("pipe", "w"), // 标准输出,子进程向此管道中写入数据
);
......@@ -475,237 +246,15 @@ class MakeVideo implements ShouldQueue
return $stdout;
} else {
$error = trim($error0,"\n") . ' '. trim($error1,"\n");
Log::channel('daily')->error(print_r(array("cmd:{$cmd}", "errno:{$exitedcode}", "stdout:{$stdout}"),1));
return [$error,$exitedcode];
}
} else {
Log::channel('daily')->error('proc_open error');
}
}
/**
* 贴纸和签名
* @param $end_wallpaper
* @param $thumbnail
* @param $signature
* @param $font
* @return string
*/
public function wallpaperWithSignature($end_wallpaper, $thumbnail, $signature, $font) {
$_imagetype = $this->getImageType($thumbnail);
$_img = null;
switch ($_imagetype) {
case 'gif':
if (function_exists('imagecreatefromgif')) {
$_img = imagecreatefromgif($thumbnail);
}
break;
case 'jpg':
case 'jpeg':
$_img = imagecreatefromjpeg($thumbnail);
break;
case 'png':
$_img = imagecreatefrompng($thumbnail);
break;
default:
$_img = imagecreatefromstring($thumbnail);
break;
}
$width = 130;
$height = 130;
$_width = 130;
$_height = 130;
if(is_resource($_img)){
$_width = imagesx($_img);
$_height = imagesy($_img);
}
$bite = $_width / $_height;
if($_width > $_height){
if($_width > $width){
$height = round($width / $bite);
}
}else{
if($_height > $height){
$width = round($height * $bite);
}
// LogUtil::write(array("cmd:{$cmd}", "errno:{$exitedcode}", "stdout:{$stdout}"), __CLASS__);
// ErrorUtil::triggerErrorMsg($error, $exitedcode);
Log::error("cmd:{$cmd}");
Log::error($error);
Log::error("stdout:{$stdout}");
}
$tmpimg = imagecreatetruecolor($width,$height);
if(function_exists('imagecopyresampled')) {
imagecopyresampled($tmpimg, $_img, 0, 0, 0, 0, $width, $height, $_width, $_height);
} else {
imagecopyresized($tmpimg, $_img, 0, 0, 0, 0, $width, $height, $_width, $_height);
}
if(is_resource($_img)) imagedestroy($_img);
$_img = $this->getCircleAvatar($tmpimg);
if(is_resource($tmpimg)) imagedestroy($tmpimg);
$wp = $this->imagesMerge($end_wallpaper, $_img);
// $white = imagecolorallocate($wp, 0xd0, 0xcd, 0xcc);
$white = imagecolorallocate($wp, 0xDC, 0x14, 0x3C); //fixme 字体颜色
imagettftext($wp, 20, 0, 75, 240, $white, $font, $signature);
// $dst = "./output_new_end_wallpaper.png";
$dst = Storage::disk('public')->path('ffmpeg') . "/output_new_end_wallpaper.png";
imagepng($wp, $dst);
if(is_resource($end_wallpaper)) imagedestroy($end_wallpaper);
if(is_resource($_img)) imagedestroy($_img);
return $dst;
// return ErrorUtil::triggerErrorMsg('proc_open error');
Log::error('proc_open error');
}
/**
* 获取图像文件类型
* @param $img_name
* @return string
*/
public function getImageType($img_name)
{
if (preg_match("/\.(jpg|jpeg|gif|png)$/i", $img_name, $matches)){
$type = strtolower($matches[1]);
}else{
$type = "string";
}
return $type;
}
/**
* 多图融合
* @param $end_wallpaper
* @param $thumbnail
* @return resource
*/
public function imagesMerge($end_wallpaper, $thumbnail) {
$end_wallpaper = imagecreatefrompng($end_wallpaper);
$background = imagecreatefrompng(Storage::disk('public')->path('ffmpeg/background.png'));
imagesavealpha($background,true);
$temp_wallpaper = imagecreatetruecolor(350, 204);
$color = imagecolorallocate($temp_wallpaper, 0xd0, 0xcd, 0xcc);
// $color = imagecolorallocate($temp_wallpaper, 0xDC, 0x14, 0x3C);
imagefill($temp_wallpaper, 0, 0, $color);
imageColorTransparent($temp_wallpaper, $color);
imagecopyresampled($temp_wallpaper, $end_wallpaper, 0, 0, 0, 0, imagesx($temp_wallpaper), imagesy($temp_wallpaper), imagesx($end_wallpaper), imagesy($end_wallpaper));
imagecopymerge($background, $temp_wallpaper, 0, 0, 0, 0, imagesx($temp_wallpaper), imagesy($temp_wallpaper), 60);
imagecopymerge($background, $thumbnail, 127, 26, 0, 0, imagesx($thumbnail), imagesy($thumbnail), 100);
return $background;
}
/**
* logo 大小转换
* @param $logo
* @return bool
*/
public function translateLogo($logo)
{
$image = Storage::disk('public')->path('ffmpeg/output_150x150.jpg');
$cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($logo) .
' -vf scale=150:150 ' . escapeshellarg($image);
if ($this->execmd($cmd)) {
return $image;
} else {
return false;
}
}
public function getTextContentString()
{
$components = $this->adminMakeVideo->temp()->first()->components()->get();
$font = Storage::disk('public')->path('ffmpeg/arialuni.ttf');
$drawtext = '';
foreach ($components as $component) {
switch ($component->name){
case 'one_poem':
$content = $this->adminMakeVideo->poem->content;
$text_file = $this->getTempPath('.txt');
file_put_contents($text_file, $content);
$text_color = $component->text_color ?? 'white';
$text_bg_color = $component->text_bg_color ?? '0xd0cdcc';
$opacity = $component->opacity ? $component->opacity / 100 : '0.5';
$drawtext .= 'drawtext="'.
'fontfile=' . escapeshellarg($font) . ':' .
'textfile=' . escapeshellarg($text_file) . ':' .
'fontsize=' . $this->calcFontSize($component->font_size,$content) . ':' .
'fontcolor=' . $text_color . '@1.0:' .
'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' .
'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' .
'box=1:boxcolor=' . $text_bg_color . '@' . $opacity . '", ';
break;
case 'every_poem':
break;
case 'weather':
$content = '多云';
$text_color = $component->text_color ?? 'white';
$text_bg_color = $component->text_bg_color ?? '0xd0cdcc';
$opacity = $component->opacity ? $component->opacity / 100 : '0.5';
$drawtext .= 'drawtext="'.
'fontfile=' . escapeshellarg($font) . ':' .
'text=' . escapeshellarg($content) . ':' .
'fontsize=' . $this->calcFontSize($component->font_size,$content) . ':' .
'fontcolor=' . $text_color . '@1.0:' .
'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' .
'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' .
'box=1:boxcolor=' . $text_bg_color . '@' . $opacity . '", ';
break;
case 'date':
$content = Carbon::now()->format('Y年m月d日H时');
$text_color = $component->text_color ?? 'white';
$text_bg_color = $component->text_bg_color ?? '0xd0cdcc';
$opacity = $component->opacity ? $component->opacity / 100 : '0.5';
$drawtext .= 'drawtext="'.
'fontfile=' . escapeshellarg($font) . ':' .
'text=' . escapeshellarg($content) . ':' .
'fontsize=' . $this->calcFontSize($component->font_size,$content) . ':' .
'fontcolor=' . $text_color . '@1.0:' .
'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' .
'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' .
'box=1:boxcolor=' . $text_bg_color . '@' . $opacity . '", ';
break;
case 'feel':
$content = $this->adminMakeVideo->feel;
$text_color = $component->text_color ?? 'white';
$text_bg_color = $component->text_bg_color ?? '0xd0cdcc';
$opacity = $component->opacity ? $component->opacity / 100 : '0.5';
$drawtext .= 'drawtext="'.
'fontfile=' . escapeshellarg($font) . ':' .
'text=' . escapeshellarg($content) . ':' .
'fontsize=' . $this->calcFontSize($component->font_size,$content) . ':' .
'fontcolor=' . $text_color . '@1.0:' .
'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' .
'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' .
'box=1:boxcolor=' . $text_bg_color . '@' . $opacity . '", ';
break;
}
}
return rtrim($drawtext,', ');
}
/**
* @param $width
* @param $content
* @return float
*/
public function calcFontSize($width, $content)
{
$max_len = 1;
foreach (explode("\n",$content) as $item){
if (mb_strlen($item) > $max_len){
$max_len = mb_strlen($item);
}
}
return ceil($this->width * $width / 10 / $max_len);
}
}
......
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class UpdateComponentsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::dropColumns('components', ['fix_bounds']);
Schema::table('components', function (Blueprint $table) {
$table->string('draw')->after('position')->comment('文字效果');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('components', function (Blueprint $table) {
$table->string('fix_bounds')->after('opacity')->comment('超出避免剪切');
});
}
}