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