李帅

1.重构模板和临境上传功能

...@@ -9,3 +9,11 @@ ...@@ -9,3 +9,11 @@
9 - composer install 9 - composer install
10 - php artisan admin:publish 10 - php artisan admin:publish
11 - php artisan admin:install 11 - php artisan admin:install
12 +
13 +
14 +
15 +## todo
16 +
17 +- 合成优化
18 +- 登录、注册(仅邮箱)
19 +- 支付(Paypal)
......
1 +<?php
2 +
3 +namespace App\Admin\Controllers;
4 +
5 +use App\Admin\Renderable\PoemTable;
6 +use App\Admin\Renderable\TemplateTable;
7 +use App\Jobs\MakeVideo;
8 +use App\Models\AdminMakeVideo;
9 +use App\Models\Order;
10 +use Dcat\Admin\Form;
11 +use Dcat\Admin\Grid;
12 +use Dcat\Admin\Layout\Content;
13 +use Dcat\Admin\Show;
14 +use Dcat\Admin\Http\Controllers\AdminController;
15 +
16 +class AdminMakeVideoController extends AdminController
17 +{
18 + /**
19 + * Make a grid builder.
20 + *
21 + * @return Grid
22 + */
23 + protected function grid()
24 + {
25 + return Grid::make(new AdminMakeVideo(), function (Grid $grid) {
26 + $grid->column('id')->sortable();
27 + $grid->column('poem_id');
28 + $grid->column('type');
29 + $grid->column('video_url');
30 + $grid->column('images_url');
31 + $grid->column('bg_music');
32 + $grid->column('bgm_url');
33 + $grid->column('feel');
34 + $grid->column('temp_id');
35 + $grid->column('thumbnail');
36 + $grid->column('thumbnail_url');
37 + $grid->column('created_at');
38 + $grid->column('updated_at')->sortable();
39 +
40 + $grid->filter(function (Grid\Filter $filter) {
41 + $filter->equal('id');
42 +
43 + });
44 + });
45 + }
46 +
47 + /**
48 + * Make a show builder.
49 + *
50 + * @param mixed $id
51 + *
52 + * @return Show
53 + */
54 + protected function detail($id)
55 + {
56 + return Show::make($id, new AdminMakeVideo(), function (Show $show) {
57 + $show->field('id');
58 + $show->field('poem_id');
59 + $show->field('type');
60 + $show->field('video_url');
61 + $show->field('images_url');
62 + $show->field('bg_music');
63 + $show->field('bgm_url');
64 + $show->field('feel');
65 + $show->field('temp_id');
66 + $show->field('thumbnail');
67 + $show->field('thumbnail_url');
68 + $show->field('created_at');
69 + $show->field('updated_at');
70 + });
71 + }
72 +
73 + /**
74 + * Make a form builder.
75 + *
76 + * @return Form
77 + */
78 + protected function form()
79 + {
80 + return Form::make(new AdminMakeVideo(), function (Form $form) {
81 + $form->display('id');
82 +
83 + $form->selectTable('poem_id','选择一言')
84 + ->title('一言诗词库')
85 + ->from(PoemTable::make());
86 +
87 + $form->radio('type')
88 + ->options([1=>'视频', 2=>'图文音频'])
89 + ->when(1,function (Form $form){
90 + $form->file('video_url','上传视频')
91 + ->accept('mp4')
92 + ->autoUpload()
93 + ->uniqueName()
94 + ->maxSize('102400')
95 + ->addElementClass('video_url');
96 + })
97 + ->when(2,function (Form $form){
98 + $form->multipleImage('images_url','上传图片')
99 + ->limit(5)
100 + ->uniqueName()
101 + ->addElementClass('images_url');
102 + })
103 + ->default(1);
104 + $form->radio('bg_music','背景音')
105 + ->options(['无', '有'])
106 + ->when(1,function (Form $form){
107 + $form->file('bgm_url','上传背景音')
108 + ->accept('mp3,aac,wav')
109 + ->autoUpload()
110 + ->uniqueName()
111 + ->addElementClass('bg_music');
112 + })
113 + ->default(0);
114 +
115 + $form->textarea('feel','有感');
116 +
117 + $form->selectTable('temp_id','选择模板')
118 + ->title('模板选择')
119 + ->from(TemplateTable::make());
120 +
121 + $form->radio('thumbnail','封面')
122 + ->options([1=>'手动上传', 2=>'自动截屏'])
123 + ->when(1,function (Form $form){
124 + $form->multipleImage('thumbnail_url','上传图片')
125 + ->limit(5)
126 + ->uniqueName();
127 +// ->addElementClass('bg_img_url');
128 + })
129 + ->when(2,function (Form $form){
130 + $form->html('');
131 + })
132 + ->default(1);
133 +
134 + $form->display('created_at');
135 + $form->display('updated_at');
136 + });
137 + }
138 +
139 + public function store()
140 + {
141 + $all = request()->all();
142 +
143 + if (isset($all['upload_column'])) return $this->form()->store();
144 +
145 + try{
146 + $video = AdminMakeVideo::query()->create($all);
147 + // 添加至队列
148 + MakeVideo::dispatch($video);
149 +
150 + }catch (\Exception $exception){
151 + return $this->form()->response()->error($exception->getMessage());
152 + }
153 +
154 + return $this->form()->response()->refresh()->success(trans('admin.save_succeeded'));
155 + }
156 +}
...@@ -5,7 +5,7 @@ namespace App\Admin\Controllers; ...@@ -5,7 +5,7 @@ namespace App\Admin\Controllers;
5 use App\Admin\Renderable\PoemTable; 5 use App\Admin\Renderable\PoemTable;
6 use App\Admin\Renderable\TemplateTable; 6 use App\Admin\Renderable\TemplateTable;
7 use App\Admin\Repositories\VideoShow; 7 use App\Admin\Repositories\VideoShow;
8 -use App\Models\OnePoem; 8 +use App\Models\AdminMakeVideo;
9 use Dcat\Admin\Form; 9 use Dcat\Admin\Form;
10 use Dcat\Admin\Grid; 10 use Dcat\Admin\Grid;
11 use Dcat\Admin\Show; 11 use Dcat\Admin\Show;
...@@ -63,30 +63,28 @@ class VideoShowController extends AdminController ...@@ -63,30 +63,28 @@ class VideoShowController extends AdminController
63 */ 63 */
64 protected function form() 64 protected function form()
65 { 65 {
66 - return Form::make(new VideoShow(), function (Form $form) { 66 + return Form::make(new AdminMakeVideo(), function (Form $form) {
67 $form->display('id'); 67 $form->display('id');
68 68
69 $form->selectTable('poem_id','选择一言') 69 $form->selectTable('poem_id','选择一言')
70 ->title('一言诗词库') 70 ->title('一言诗词库')
71 ->from(PoemTable::make()); 71 ->from(PoemTable::make());
72 72
73 -// $form->radio('type')->addElementClass('type')
74 -// ->options([1=>'图文音频',2=>'视频'])->default(1);
75 -
76 $form->radio('type') 73 $form->radio('type')
77 ->options([1=>'视频', 2=>'图文音频']) 74 ->options([1=>'视频', 2=>'图文音频'])
78 ->when(1,function (Form $form){ 75 ->when(1,function (Form $form){
79 - $form->file('bg_url','上传视频') 76 + $form->file('video_url','上传视频')
80 ->accept('mp4') 77 ->accept('mp4')
81 ->autoUpload() 78 ->autoUpload()
82 ->uniqueName() 79 ->uniqueName()
83 - ->addElementClass('bg_video_url'); 80 + ->maxSize('102400')
81 + ->addElementClass('video_url');
84 }) 82 })
85 ->when(2,function (Form $form){ 83 ->when(2,function (Form $form){
86 - $form->multipleImage('bg_url','上传图片') 84 + $form->multipleImage('images_url','上传图片')
87 ->limit(5) 85 ->limit(5)
88 ->uniqueName() 86 ->uniqueName()
89 - ->addElementClass('bg_img_url'); 87 + ->addElementClass('images_url');
90 }) 88 })
91 ->default(1); 89 ->default(1);
92 $form->radio('bg_music','背景音') 90 $form->radio('bg_music','背景音')
...@@ -96,23 +94,23 @@ class VideoShowController extends AdminController ...@@ -96,23 +94,23 @@ class VideoShowController extends AdminController
96 ->accept('mp3,aac,wav') 94 ->accept('mp3,aac,wav')
97 ->autoUpload() 95 ->autoUpload()
98 ->uniqueName() 96 ->uniqueName()
99 - ->addElementClass('bgm_url'); 97 + ->addElementClass('bg_music');
100 }) 98 })
101 ->default(0); 99 ->default(0);
102 100
103 $form->textarea('feel','有感'); 101 $form->textarea('feel','有感');
104 102
105 - $form->selectTable('poem_id','选择模板') 103 + $form->selectTable('temp_id','选择模板')
106 ->title('模板选择') 104 ->title('模板选择')
107 ->from(TemplateTable::make()); 105 ->from(TemplateTable::make());
108 106
109 $form->radio('thumbnail','封面') 107 $form->radio('thumbnail','封面')
110 - ->options([1=>'手动上传', 2=>'选择截屏']) 108 + ->options([1=>'手动上传', 2=>'自动截屏'])
111 ->when(1,function (Form $form){ 109 ->when(1,function (Form $form){
112 - $form->multipleImage('bg_url','上传图片') 110 + $form->multipleImage('thumbnail_url','上传图片')
113 ->limit(5) 111 ->limit(5)
114 - ->uniqueName() 112 + ->uniqueName();
115 - ->addElementClass('bg_img_url'); 113 +// ->addElementClass('bg_img_url');
116 }) 114 })
117 ->when(2,function (Form $form){ 115 ->when(2,function (Form $form){
118 $form->html(''); 116 $form->html('');
...@@ -123,4 +121,22 @@ class VideoShowController extends AdminController ...@@ -123,4 +121,22 @@ class VideoShowController extends AdminController
123 $form->display('updated_at'); 121 $form->display('updated_at');
124 }); 122 });
125 } 123 }
124 +
125 + public function store()
126 + {
127 + $all = request()->all();
128 +
129 + if (isset($all['upload_column'])) return $this->form()->store();
130 +
131 + try{
132 + $video = AdminMakeVideo::query()->create($all);
133 +
134 + // todo 添加至队列
135 +
136 + }catch (\Exception $exception){
137 + return $this->form()->response()->error($exception->getMessage());
138 + }
139 +
140 + return $this->form()->response()->refresh()->success(trans('admin.save_succeeded'));
141 + }
126 } 142 }
......
...@@ -171,33 +171,23 @@ class VideoTempController extends AdminController ...@@ -171,33 +171,23 @@ class VideoTempController extends AdminController
171 171
172 $form->checkbox('components','组件') 172 $form->checkbox('components','组件')
173 ->when('every_poem', function (Form\BlockForm $form) { 173 ->when('every_poem', function (Form\BlockForm $form) {
174 - $form->select('pos_every_poem', '每日位置')->options([ 174 + $form->select('pos_every_poem', '每日位置')->options(VideoTemp::POSITION_OPTIONS);
175 - 'topLeft'=>'上左','topMiddle'=>'上中','topRight'=>'上右',
176 - ]);
177 $form->divider(); 175 $form->divider();
178 }) 176 })
179 ->when('one_poem', function (Form\BlockForm $form) { 177 ->when('one_poem', function (Form\BlockForm $form) {
180 - $form->select('pos_one_poem', '一言位置')->options([ 178 + $form->select('pos_one_poem', '一言位置')->options(VideoTemp::POSITION_OPTIONS);
181 - 'topLeft'=>'上左','topMiddle'=>'上中','topRight'=>'上右',
182 - ]);
183 $form->divider(); 179 $form->divider();
184 }) 180 })
185 ->when('weather', function (Form\BlockForm $form) { 181 ->when('weather', function (Form\BlockForm $form) {
186 - $form->select('pos_weather', '天气位置')->options([ 182 + $form->select('pos_weather', '天气位置')->options(VideoTemp::POSITION_OPTIONS);
187 - 'topLeft'=>'上左','topMiddle'=>'上中','topRight'=>'上右',
188 - ]);
189 $form->divider(); 183 $form->divider();
190 }) 184 })
191 ->when('date', function (Form\BlockForm $form) { 185 ->when('date', function (Form\BlockForm $form) {
192 - $form->select('pos_date', '日期位置')->options([ 186 + $form->select('pos_date', '日期位置')->options(VideoTemp::POSITION_OPTIONS);
193 - 'topLeft'=>'上左','topMiddle'=>'上中','topRight'=>'上右',
194 - ]);
195 $form->divider(); 187 $form->divider();
196 }) 188 })
197 ->when('feel', function (Form\BlockForm $form) { 189 ->when('feel', function (Form\BlockForm $form) {
198 - $form->select('pos_feel', '有感位置')->options([ 190 + $form->select('pos_feel', '有感位置')->options(VideoTemp::POSITION_OPTIONS);
199 - 'topLeft'=>'上左','topMiddle'=>'上中','topRight'=>'上右',
200 - ]);
201 $form->divider(); 191 $form->divider();
202 }) 192 })
203 ->default(['one_poem','weather','date']) 193 ->default(['one_poem','weather','date'])
...@@ -228,7 +218,7 @@ class VideoTempController extends AdminController ...@@ -228,7 +218,7 @@ class VideoTempController extends AdminController
228 public function store() 218 public function store()
229 { 219 {
230 $all = \request()->all(); 220 $all = \request()->all();
231 - dd($all); 221 +
232 try{ 222 try{
233 DB::transaction(function ()use ($all){ 223 DB::transaction(function ()use ($all){
234 $vide_temp = VideoTemp::query()->create([ 224 $vide_temp = VideoTemp::query()->create([
......
1 +<?php
2 +
3 +namespace App\Admin\Repositories;
4 +
5 +use App\Models\AdminMakeVideo as Model;
6 +use Dcat\Admin\Repositories\EloquentRepository;
7 +
8 +class AdminMakeVideo extends EloquentRepository
9 +{
10 + /**
11 + * Model.
12 + *
13 + * @var string
14 + */
15 + protected $eloquentClass = Model::class;
16 +}
...@@ -27,7 +27,7 @@ Route::group([ ...@@ -27,7 +27,7 @@ Route::group([
27 /** 临境*/ 27 /** 临境*/
28 $router->group(['prefix'=>'/linjing'],function (Router $router){ 28 $router->group(['prefix'=>'/linjing'],function (Router $router){
29 $router->resource('/template', 'VideoTempController'); 29 $router->resource('/template', 'VideoTempController');
30 - $router->resource('/official', 'VideoShowController'); 30 + $router->resource('/official', 'AdminMakeVideoController');
31 }); 31 });
32 32
33 /** 订单*/ 33 /** 订单*/
......
1 +<?php
2 +
3 +namespace App\Jobs;
4 +
5 +use App\Models\AdminMakeVideo;
6 +use Illuminate\Bus\Queueable;
7 +use Illuminate\Contracts\Queue\ShouldBeUnique;
8 +use Illuminate\Contracts\Queue\ShouldQueue;
9 +use Illuminate\Foundation\Bus\Dispatchable;
10 +use Illuminate\Queue\InteractsWithQueue;
11 +use Illuminate\Queue\SerializesModels;
12 +use Illuminate\Support\Facades\Storage;
13 +
14 +class MakeVideo implements ShouldQueue
15 +{
16 + use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
17 +
18 + public $adminMakeVideo;
19 +
20 + protected $ffmpeg = '/Users/lishuai/Documents/ffmpeg/ffmpeg';
21 +
22 + protected $ffprobe = '/Users/lishuai/Documents/ffmpeg/ffprobe';
23 +
24 + protected $ffplay = '/Users/lishuai/Documents/ffmpeg/ffplay';
25 +
26 + /**
27 + * Create a new job instance.
28 + * @param AdminMakeVideo $adminMakeVideo
29 + * @return void
30 + */
31 + public function __construct(AdminMakeVideo $adminMakeVideo)
32 + {
33 + $this->adminMakeVideo = $adminMakeVideo;
34 + }
35 +
36 + /**
37 + * Execute the job.
38 + *
39 + * @return void
40 + */
41 + public function handle()
42 + {
43 + // 执行合成逻辑
44 + $file = Storage::disk('public')->path('ffmpeg') . "/output_1646128658383.mp4";
45 + $watermark = Storage::disk('public')->path('ffmpeg') . "/LOGO_eng.png";
46 + $watermark_x = 20;
47 + $watermark_y = 20;
48 + $animate = Storage::disk('public')->path('ffmpeg') . "/output_16461288409938.mp4";
49 +
50 + $video = $this->getTempPath();
51 + $watermark_x = $watermark_x ? $watermark_x : 0;
52 + $watermark_y = $watermark_y ? $watermark_y : 0;
53 + $am_inp = ' -i ' . escapeshellarg($watermark);
54 + $am_filter = "[2:v]overlay={$watermark_x}:{$watermark_y}[water];[water]";
55 +
56 + $cmd = $this->ffmpeg . ' -y' .
57 + ' -i ' . escapeshellarg($file) .
58 + ' -i ' . escapeshellarg($animate) .
59 + $am_inp .
60 +// ' -filter_complex "[0:0]' . '' . $am_filter . 'setsar=sar=1/1[t];[t] [2:a] [1:0] [1:1] concat=n=2:v=1:a=1 [v] [a]"' .
61 + ' -filter_complex "[0:0]' . '' . $am_filter . '[0:1] [1:0] [1:1] concat=n=2:v=1:a=1 [v] [a]"' .
62 + ' -map [v] -map [a]';
63 +
64 + $cmd .=
65 + ' -c:v libx264 -s 800x450 -bt 256k -r 25' .
66 +// todo 没有libfdk_aac库
67 +// ' -c:a libfdk_aac -ar 44100 -ac 2 -qmin 30 -qmax 60 -profile:v baseline -preset fast ' .
68 + ' -ar 44100 -ac 2 -qmin 30 -qmax 60 -profile:v baseline -preset fast ' .
69 + escapeshellarg($video);
70 + if ($this->execmd($cmd)) {
71 + return $video;
72 + } else {
73 + return false;
74 + }
75 +
76 + }
77 +
78 + /**
79 + * 获取圆形头像
80 + * @param $img
81 + * @param int $dst_w
82 + * @param int $dst_h
83 + * @return resource
84 + */
85 + public function getCircleAvatar($img, $dst_w = 96, $dst_h = 96)
86 + {
87 + $w = 130;
88 + $h = 130;
89 + $src = imagecreatetruecolor($dst_w, $dst_h);
90 + imagecopyresized($src, $img, 0, 0, 0, 0, $dst_w, $dst_h, $w, $h);
91 +
92 + $newpic = imagecreatetruecolor($dst_w, $dst_h);
93 + imagealphablending($newpic, false);
94 + imagecopyresampled($newpic, $img, 0, 0, 0, 0, $dst_w, $dst_h, $w, $h);
95 + $mask = imagecreatetruecolor($dst_w, $dst_h);
96 + $transparent = imagecolorallocate($mask, 255, 0, 0);
97 + imagecolortransparent($mask,$transparent);
98 + imagefilledellipse($mask, $dst_w / 2, $dst_h / 2, $dst_w, $dst_h, $transparent);
99 + $red = imagecolorallocate($mask, 0, 0, 0);
100 + imagecopymerge($newpic, $mask, 0, 0, 0, 0, $dst_w, $dst_h, 100);
101 + imagecolortransparent($newpic,$red);
102 + imagesavealpha($newpic,true);
103 + imagefill($newpic, 0, 0, $red);
104 + imagedestroy($mask);
105 + return $newpic;
106 + }
107 +
108 + /**
109 + * 制作最后一帧
110 + * @param $file
111 + * @return bool|string
112 + */
113 + public function makeLastFrameVideo($file) {
114 + $video = $this->getTempPath();
115 + $width = $this->getVideoWith($file);
116 + $height = $this->getVideoHeight($file);
117 + $size = $width . 'x' . $height;
118 + $time_length = 0.7;
119 + $r = 24;
120 + $frame_n = $this->getVideoFrameNum($file) - 2;
121 + $cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($file) .
122 + " -f lavfi -i nullsrc=s={$size}:d={$time_length}:r={$r} -f lavfi -i aevalsrc=0:duration={$time_length}" .
123 + " -filter_complex \"[0:v]select='eq(n,{$frame_n})',setpts=PTS-STARTPTS[lastframe];[1:v][lastframe]overlay[v]\"" .
124 + ' -map [v] -map 2:a ' . escapeshellarg($video);
125 + if ($this->execmd($cmd)) {
126 + return $video;
127 + } else {
128 + return false;
129 + }
130 + }
131 +
132 + /**
133 + * 用最后一帧和贴纸制作动画
134 + * @param $last_frame_video
135 + * @param $end_wallpaper
136 + * @param $signature
137 + * @param $signature_x
138 + * @param $signature_y
139 + * @param $font
140 + * @return bool|string
141 + */
142 + public function makeAnimate($last_frame_video, $end_wallpaper, $signature, $signature_x, $signature_y, $font) {
143 + $signature_x = $signature_x >= 0 ? '+' . $signature_x : '-' . abs($signature_x);
144 + $signature_y = $signature_y >= 0 ? '+' . $signature_y : '-' . abs($signature_y);
145 + $video = $this->getTempPath();
146 + if ($signature !== '') {
147 + $cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($last_frame_video) .
148 + ' -loop 1 -i ' . escapeshellarg($end_wallpaper) .
149 + ' -filter_complex "'.
150 + 'geq=lum=\'if(lte(T,0.6), 255*T*(1/0.6),255)\',format=gray[grad];'.
151 + '[0:v]boxblur=8[blur];'.
152 + '[blur][1:v]overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2 [lay];[lay]'.
153 + 'drawtext='.
154 + 'fontfile=' . escapeshellarg($font) . ':'.
155 + 'text=' . escapeshellarg($signature) . ':'.
156 + 'fontsize=23:'.
157 + 'fontcolor=white@1.0:'.
158 + 'x=main_w/2' . $signature_x . ':'.
159 + 'y=main_h/2' . $signature_y . '[text];[text]'.
160 + '[grad]alphamerge[alpha];'.
161 + '[0:v][alpha]overlay'.
162 + '" ' . escapeshellarg($video);
163 + } else {
164 + $cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($last_frame_video) .
165 + ' -loop 1 -i ' . escapeshellarg($end_wallpaper) .
166 + ' -filter_complex "'.
167 + 'geq=lum=\'if(lte(T,0.6), 255*T*(1/0.6),255)\',format=gray[grad];'.
168 + '[0:v]boxblur=8[blur];'.
169 + '[blur][1:v]overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2 [lay];'.
170 + '[lay][grad]alphamerge[alpha];'.
171 + '[0:v][alpha]overlay'.
172 + '" ' . escapeshellarg($video);
173 + }
174 + if ($this->execmd($cmd)) {
175 + return $video;
176 + } else {
177 + return false;
178 + }
179 + }
180 +
181 + /**
182 + * 获取视频宽度
183 + * @param $file
184 + * @param bool $cache
185 + * @return int|null
186 + */
187 + public function getVideoWith($file, $cache = true) {
188 + $result = $this->getFirstVideoTrackOption($file, $option = 'width', $cache);
189 + if ($result) {
190 + return (int)$result;
191 + } else {
192 + return $result;
193 + }
194 + }
195 +
196 + /**
197 + * 获取视频高度
198 + * @param $file
199 + * @param bool $cache
200 + * @return int|null
201 + */
202 + public function getVideoHeight($file, $cache = true) {
203 + $result = $this->getFirstVideoTrackOption($file, $option = 'height', $cache);
204 + if ($result) {
205 + return (int)$result;
206 + } else {
207 + return $result;
208 + }
209 + }
210 +
211 + /**
212 + * 获取视频帧数
213 + * @param $file
214 + * @param bool $cache
215 + * @return null
216 + */
217 + public function getVideoFrameNum($file, $cache = true) {
218 + return $this->getFirstVideoTrackOption($file, $option = 'nb_frames', $cache);
219 + }
220 +
221 +
222 + public function getFirstVideoTrackOption($file, $option, $cache = true) {
223 + return $this->getFirstTrackOption($file, $option, $codec_type = 'video', $cache = true);
224 + }
225 +
226 + public function getFirstTrackOption($file, $option, $codec_type = '', $cache = true) {
227 + $result = $this->mediainfo($file, $cache);
228 + if (!isset($result['streams'])) {
229 + return null;
230 + }
231 + $_track = null;
232 + foreach($result['streams'] as $track) {
233 + if (empty($codec_type)) {
234 + $_track = $track;
235 + break;
236 + } elseif ($track['codec_type'] == $codec_type) {
237 + $_track = $track;
238 + break;
239 + }
240 + }
241 + if (isset($_track[$option])) {
242 + return $_track[$option];
243 + }
244 + return null;
245 + }
246 +
247 + /***
248 + * 获取视频信息(配合ffprobe)
249 + * @param $file
250 + * @param bool $cache
251 + * @return mixed
252 + */
253 + public function mediainfo($file, $cache = true) {
254 + global $_mediainfo;
255 + $cmd = $this->ffprobe . ' -v quiet -print_format json -show_format -show_streams ' . escapeshellarg($file);
256 + if ($cache && isset($_mediainfo[$file])) {
257 + return $_mediainfo[$file];
258 + }
259 + $output = $this->execmd($cmd);
260 + $data = json_decode($output, true);
261 + if (json_last_error() === JSON_ERROR_UTF8) {
262 + $output = mb_convert_encoding($output, "UTF-8");
263 + $data = json_decode($output, true);
264 + }
265 + if ($cache) {
266 + $mediainfo[$file] = $data;
267 + }
268 + return $data;
269 + }
270 +
271 + /**
272 + * 获取输出临时文件名
273 + * @return string
274 + */
275 + public function getTempPath() {
276 + return Storage::disk('public')->path('ffmpeg') . "/output_" . time() . rand(0, 10000) . ".mp4";
277 + }
278 +
279 + /**
280 + * 执行命令
281 + * @param $cmd
282 + * @param bool $update_progress
283 + * @return string
284 + */
285 + public function execmd($cmd, $update_progress = false) {
286 + echo $cmd . "\n". "\n". "\n";
287 + $descriptorspec = array(
288 + 1 => array("pipe", "w"), // 标准输出,子进程向此管道中写入数据
289 + );
290 + $process = proc_open("{$cmd} 2>&1", $descriptorspec, $pipes);
291 + if (is_resource($process)) {
292 + $error0 = '';
293 + $error1 = '';
294 + $stdout = '';
295 + while (!feof($pipes[1])) {
296 + $line = fgets($pipes[1], 150);
297 + $stdout .= $line;
298 + if ($line) {
299 + //记录错误
300 + $error0 = $error1;
301 + $error1 = $line;
302 + if ($update_progress &&
303 + false !== strpos($line, 'size=') &&
304 + false !== strpos($line, 'time=') &&
305 + false !== strpos($line, 'bitrate='))
306 + {
307 + //记录进度 size= 3142kB time=00:00:47.22 bitrate= 545.1kbits/s
308 + $line = explode(' ', $line);
309 + $time = null;
310 + foreach ($line as $item) {
311 + $item = explode('=', $item);
312 + if (isset($item[0]) && isset($item[1]) && $item[0] == 'time') {
313 + $time = $item[1];
314 + break;
315 + }
316 + }
317 + }
318 + }
319 + }
320 + // 切记:在调用 proc_close 之前关闭所有的管道以避免死锁。
321 + fclose($pipes[1]);
322 + $exitedcode = proc_close($process);
323 + if ($exitedcode === 0) {
324 + return $stdout;
325 + } else {
326 + $error = trim($error0,"\n") . ' '. trim($error1,"\n");
327 + // LogUtil::write(array("cmd:{$cmd}", "errno:{$exitedcode}", "stdout:{$stdout}"), __CLASS__);
328 + // ErrorUtil::triggerErrorMsg($error, $exitedcode);
329 + }
330 + } else {
331 + // return ErrorUtil::triggerErrorMsg('proc_open error');
332 + }
333 + }
334 +
335 + /**
336 + * 贴纸和签名
337 + * @param $end_wallpaper
338 + * @param $thumbnail
339 + * @param $signature
340 + * @param $font
341 + * @return string
342 + */
343 + public function wallpaperWithSignature($end_wallpaper, $thumbnail, $signature, $font) {
344 + $_imagetype = $this->getImageType($thumbnail);
345 + $_img = null;
346 + switch ($_imagetype) {
347 + case 'gif':
348 + if (function_exists('imagecreatefromgif')) {
349 + $_img = imagecreatefromgif($thumbnail);
350 + }
351 + break;
352 + case 'jpg':
353 + case 'jpeg':
354 + $_img = imagecreatefromjpeg($thumbnail);
355 + break;
356 + case 'png':
357 + $_img = imagecreatefrompng($thumbnail);
358 + break;
359 + default:
360 + $_img = imagecreatefromstring($thumbnail);
361 + break;
362 + }
363 + $width = 130;
364 + $height = 130;
365 + $_width = 130;
366 + $_height = 130;
367 + if(is_resource($_img)){
368 + $_width = imagesx($_img);
369 + $_height = imagesy($_img);
370 + }
371 +
372 + $bite = $_width / $_height;
373 +
374 + if($_width > $_height){
375 + if($_width > $width){
376 + $height = round($width / $bite);
377 + }
378 + }else{
379 + if($_height > $height){
380 + $width = round($height * $bite);
381 + }
382 + }
383 +
384 + $tmpimg = imagecreatetruecolor($width,$height);
385 + if(function_exists('imagecopyresampled')) {
386 + imagecopyresampled($tmpimg, $_img, 0, 0, 0, 0, $width, $height, $_width, $_height);
387 + } else {
388 + imagecopyresized($tmpimg, $_img, 0, 0, 0, 0, $width, $height, $_width, $_height);
389 + }
390 + if(is_resource($_img)) imagedestroy($_img);
391 + $_img = $this->getCircleAvatar($tmpimg);
392 + if(is_resource($tmpimg)) imagedestroy($tmpimg);
393 +
394 + $wp = $this->imagesMerge($end_wallpaper, $_img);
395 +// $white = imagecolorallocate($wp, 0xd0, 0xcd, 0xcc);
396 + $white = imagecolorallocate($wp, 0xDC, 0x14, 0x3C); //fixme 字体颜色
397 + imagettftext($wp, 20, 0, 75, 240, $white, $font, $signature);
398 +
399 +// $dst = "./output_new_end_wallpaper.png";
400 + $dst = Storage::disk('public')->path('ffmpeg') . "/output_new_end_wallpaper.png";
401 + imagepng($wp, $dst);
402 + if(is_resource($end_wallpaper)) imagedestroy($end_wallpaper);
403 + if(is_resource($_img)) imagedestroy($_img);
404 +
405 + return $dst;
406 + }
407 +
408 + /**
409 + * 获取图像文件类型
410 + * @param $img_name
411 + * @return string
412 + */
413 + public function getImageType($img_name)
414 + {
415 + if (preg_match("/\.(jpg|jpeg|gif|png)$/i", $img_name, $matches)){
416 + $type = strtolower($matches[1]);
417 + }else{
418 + $type = "string";
419 + }
420 + return $type;
421 + }
422 +
423 + /**
424 + * 多图融合
425 + * @param $end_wallpaper
426 + * @param $thumbnail
427 + * @return resource
428 + */
429 + public function imagesMerge($end_wallpaper, $thumbnail) {
430 + $end_wallpaper = imagecreatefrompng($end_wallpaper);
431 + $background = imagecreatefrompng(Storage::disk('public')->path('ffmpeg/background.png'));
432 + imagesavealpha($background,true);
433 + $temp_wallpaper = imagecreatetruecolor(350, 204);
434 + $color = imagecolorallocate($temp_wallpaper, 0xd0, 0xcd, 0xcc);
435 +// $color = imagecolorallocate($temp_wallpaper, 0xDC, 0x14, 0x3C);
436 + imagefill($temp_wallpaper, 0, 0, $color);
437 + imageColorTransparent($temp_wallpaper, $color);
438 + imagecopyresampled($temp_wallpaper, $end_wallpaper, 0, 0, 0, 0, imagesx($temp_wallpaper), imagesy($temp_wallpaper), imagesx($end_wallpaper), imagesy($end_wallpaper));
439 + imagecopymerge($background, $temp_wallpaper, 0, 0, 0, 0, imagesx($temp_wallpaper), imagesy($temp_wallpaper), 60);
440 + imagecopymerge($background, $thumbnail, 127, 26, 0, 0, imagesx($thumbnail), imagesy($thumbnail), 100);
441 + return $background;
442 + }
443 +}
1 +<?php
2 +
3 +namespace App\Models;
4 +
5 +use Dcat\Admin\Traits\HasDateTimeFormatter;
6 +
7 +use Illuminate\Database\Eloquent\Model;
8 +
9 +class AdminMakeVideo extends Model
10 +{
11 + use HasDateTimeFormatter;
12 + protected $table = 'admin_make_video';
13 +
14 + protected $guarded = [''];
15 +}
...@@ -51,6 +51,7 @@ class Order extends Model ...@@ -51,6 +51,7 @@ class Order extends Model
51 51
52 public function timeoutCanceled() 52 public function timeoutCanceled()
53 { 53 {
54 - 54 + $this->status = self::TIMEOUT_CANCEL;
55 + $this->save();
55 } 56 }
56 } 57 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -9,6 +9,13 @@ use Illuminate\Database\Eloquent\Model; ...@@ -9,6 +9,13 @@ use Illuminate\Database\Eloquent\Model;
9 class VideoTemp extends Model 9 class VideoTemp extends Model
10 { 10 {
11 use HasDateTimeFormatter; 11 use HasDateTimeFormatter;
12 +
13 + const POSITION_OPTIONS = [
14 + 'topLeft'=>'上左','topMiddle'=>'上中','topRight'=>'上右',
15 + 'midLeft'=>'中左','midMiddle'=>'中中','midRight'=>'中右',
16 + 'botLeft'=>'下左','botMiddle'=>'下中','botRight'=>'下右',
17 + ];
18 +
12 protected $table = 'video_temp'; 19 protected $table = 'video_temp';
13 20
14 protected $guarded = ['']; 21 protected $guarded = [''];
......
...@@ -221,7 +221,7 @@ return [ ...@@ -221,7 +221,7 @@ return [
221 'Queue' => Illuminate\Support\Facades\Queue::class, 221 'Queue' => Illuminate\Support\Facades\Queue::class,
222 'RateLimiter' => Illuminate\Support\Facades\RateLimiter::class, 222 'RateLimiter' => Illuminate\Support\Facades\RateLimiter::class,
223 'Redirect' => Illuminate\Support\Facades\Redirect::class, 223 'Redirect' => Illuminate\Support\Facades\Redirect::class,
224 - // 'Redis' => Illuminate\Support\Facades\Redis::class, 224 + 'Redis' => Illuminate\Support\Facades\Redis::class,
225 'Request' => Illuminate\Support\Facades\Request::class, 225 'Request' => Illuminate\Support\Facades\Request::class,
226 'Response' => Illuminate\Support\Facades\Response::class, 226 'Response' => Illuminate\Support\Facades\Response::class,
227 'Route' => Illuminate\Support\Facades\Route::class, 227 'Route' => Illuminate\Support\Facades\Route::class,
......
1 +<?php
2 +
3 +use Illuminate\Support\Facades\Schema;
4 +use Illuminate\Database\Schema\Blueprint;
5 +use Illuminate\Database\Migrations\Migration;
6 +
7 +class CreateAdminMakeVideoTable extends Migration
8 +{
9 + /**
10 + * Run the migrations.
11 + *
12 + * @return void
13 + */
14 + public function up()
15 + {
16 + Schema::create('admin_make_video', function (Blueprint $table) {
17 + $table->increments('id');
18 + $table->string('poem_id')->default('')->comment('一言id');
19 + $table->unsignedTinyInteger('type')->comment('类型');
20 + $table->string('video_url')->nullable()->comment('视频地址');
21 + $table->string('images_url')->nullable()->comment('图片地址');
22 + $table->unsignedTinyInteger('bg_music')->comment('是否背景音');
23 + $table->string('bgm_url')->nullable()->comment('背景音地址');
24 + $table->string('feel')->default('')->comment('有感');
25 + $table->string('temp_id')->default('')->comment('模板id');
26 + $table->unsignedTinyInteger('thumbnail')->comment('封面图');
27 + $table->string('thumbnail_url')->nullable()->comment('封面图地址');
28 + $table->timestamps();
29 + });
30 + }
31 +
32 + /**
33 + * Reverse the migrations.
34 + *
35 + * @return void
36 + */
37 + public function down()
38 + {
39 + Schema::dropIfExists('admin_make_video');
40 + }
41 +}