Showing
12 changed files
with
727 additions
and
34 deletions
| 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([ | ... | ... |
app/Admin/Repositories/AdminMakeVideo.php
0 → 100755
| 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 | /** 订单*/ | ... | ... |
app/Jobs/MakeVideo.php
0 → 100644
| 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 | +} |
app/Models/AdminMakeVideo.php
0 → 100755
| 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 | +} |
-
Please register or login to post a comment