Showing
10 changed files
with
1114 additions
and
658 deletions
| ... | @@ -3,8 +3,11 @@ | ... | @@ -3,8 +3,11 @@ |
| 3 | namespace App\Console\Commands; | 3 | namespace App\Console\Commands; |
| 4 | 4 | ||
| 5 | use App\Jobs\MakeImages; | 5 | use App\Jobs\MakeImages; |
| 6 | +use App\Jobs\UserMakeImmerse; | ||
| 6 | use App\Models\AdminMakeVideo; | 7 | use App\Models\AdminMakeVideo; |
| 7 | use App\Models\Immerse; | 8 | use App\Models\Immerse; |
| 9 | +use App\Models\User; | ||
| 10 | +use App\Models\UserProfile; | ||
| 8 | use App\Models\VideoTemp; | 11 | use App\Models\VideoTemp; |
| 9 | use Carbon\Carbon; | 12 | use Carbon\Carbon; |
| 10 | use Illuminate\Console\Command; | 13 | use Illuminate\Console\Command; |
| ... | @@ -36,13 +39,23 @@ class DevFFmpeg extends Command | ... | @@ -36,13 +39,23 @@ class DevFFmpeg extends Command |
| 36 | public function __construct() | 39 | public function __construct() |
| 37 | { | 40 | { |
| 38 | parent::__construct(); | 41 | parent::__construct(); |
| 42 | + | ||
| 43 | + $this->immerse = Immerse::query()->orderByDesc('id')->first(); | ||
| 44 | + $this->ffmpeg = env('FFMPEG_CMD'); | ||
| 45 | + $this->ffprobe = env('FFPROBE_CMD'); | ||
| 39 | } | 46 | } |
| 40 | 47 | ||
| 41 | - protected $ffmpeg = '/Users/lishuai/Documents/ffmpeg/ffmpeg'; | 48 | + public $debug = true; |
| 49 | + | ||
| 50 | + public $immerse; | ||
| 51 | + | ||
| 52 | + protected $ffmpeg; | ||
| 42 | 53 | ||
| 43 | - protected $ffprobe = '/Users/lishuai/Documents/ffmpeg/ffprobe'; | 54 | + protected $ffprobe; |
| 44 | 55 | ||
| 45 | - protected $ffplay = '/Users/lishuai/Documents/ffmpeg/ffplay'; | 56 | + protected $output_width; |
| 57 | + | ||
| 58 | + protected $output_height; | ||
| 46 | 59 | ||
| 47 | /** | 60 | /** |
| 48 | * Execute the console command. | 61 | * Execute the console command. |
| ... | @@ -51,530 +64,234 @@ class DevFFmpeg extends Command | ... | @@ -51,530 +64,234 @@ class DevFFmpeg extends Command |
| 51 | */ | 64 | */ |
| 52 | public function handle() | 65 | public function handle() |
| 53 | { | 66 | { |
| 54 | - MakeImages::dispatch(AdminMakeVideo::query()->find(24)); | ||
| 55 | - dd(1); | ||
| 56 | - $image = Storage::disk('public')->path('images/73f18d443820334c51c36f443c9683b3.png'); | ||
| 57 | - $watermark = Storage::disk('public')->path('ffmpeg/LOGO_eng.png'); | ||
| 58 | - $end_wallpaper = Storage::disk('public')->path('ffmpeg/output_new_end_wallpaper.png'); | ||
| 59 | - | ||
| 60 | - | ||
| 61 | - // 制作最后一帧 | ||
| 62 | - $size = '1242x2208'; | ||
| 63 | - $time_length = 0.7; | ||
| 64 | - $r = 24; | ||
| 65 | - $last_frame_video = $this->getTempPath('.mp4'); | ||
| 66 | - $font = Storage::disk('public')->path('ffmpeg/arialuni.ttf'); | ||
| 67 | - | ||
| 68 | - $cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($image) . | ||
| 69 | - ' -i ' . escapeshellarg($watermark) . | ||
| 70 | - " -f lavfi -i nullsrc=s={$size}:d={$time_length}:r={$r} -f lavfi -i aevalsrc=0:duration={$time_length}" . | ||
| 71 | - ' -filter_complex "'. | ||
| 72 | - ' [0:0] ' . $this->getTextContentString() . | ||
| 73 | - '[text];[text][1:0]overlay=20:20[water];' . | ||
| 74 | - ' [water]select=\'eq(n,0)\',setpts=PTS-STARTPTS[lastframe];[2:v][lastframe]overlay[v] " ' . | ||
| 75 | - ' -map [v] -map 3:a ' . escapeshellarg($last_frame_video); | ||
| 76 | - $output = $this->execmd($cmd); | ||
| 77 | - | ||
| 78 | - | ||
| 79 | - // 利用最后一帧制作动画 | ||
| 80 | - $signature_x = 0; | ||
| 81 | - $signature_y = -20; | ||
| 82 | - $animate = $this->makeAnimate($last_frame_video, $end_wallpaper, '', $signature_x, $signature_y, $font); | ||
| 83 | - | ||
| 84 | - | ||
| 85 | - | ||
| 86 | - dd($animate); | ||
| 87 | - | ||
| 88 | - | ||
| 89 | - | ||
| 90 | - | ||
| 91 | - | ||
| 92 | - // 这样实现不了 | ||
| 93 | - $cmd = $this->ffmpeg . ' -y -i ' . | ||
| 94 | - escapeshellarg($image) . | ||
| 95 | - " -f lavfi -i nullsrc=s=1242x2208:d=0.7:r=24 ". | ||
| 96 | - ' -i ' . escapeshellarg($watermark) . | ||
| 97 | - ' -i ' . escapeshellarg($end_wallpaper) . | ||
| 98 | - | ||
| 99 | - ' -filter_complex "' . | ||
| 100 | - ' [0:v] ' . $this->getTextContentString() . | ||
| 101 | - ' [text];[text][2:0]overlay=20:20[water];' . | ||
| 102 | - | ||
| 103 | - | ||
| 104 | - ' [water]select=\'eq(n,1)\',setpts=PTS-STARTPTS[lastframe];[1:v][lastframe]overlay[last];' . | ||
| 105 | - ' [last]boxblur=8[blur];' . | ||
| 106 | - ' [blur][3:v]overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2 [lay];' . | ||
| 107 | - ' [lay]geq=lum=\'if(lte(T,0.6), 255*T*(1/0.6),255)\',format=gray[grad];' . | ||
| 108 | - ' [lay][grad]alphamerge[alpha];' . | ||
| 109 | -// ' [last][alpha]overlay[concat2];'. | ||
| 110 | - | ||
| 111 | - ' [water][alpha] concat=n=2:v=1[v]" ' . | ||
| 112 | - | ||
| 113 | - escapeshellarg($this->getTempPath('.mp4')); | ||
| 114 | 67 | ||
| 115 | - $output = $this->execmd($cmd); | 68 | + // 分情况 1.用户视频,2.用户录音 |
| 116 | - | 69 | + // 注意事项:1.考虑用户是否会员;非会员分辨率 720x1280,会员分辨率1440x2560 |
| 117 | - dd($output); | 70 | + // 2.同时字体大小也很方便确定。 |
| 118 | - | 71 | + |
| 119 | - dd(Str::contains("/Users/lishuai/Documents/source/OnePoem-Server/storage/app/public/ffmpeg/output_16479198841364.mp4",'/storage/app/public/')); | 72 | +// $profile = UserProfile::query()->find($this->immerse->user_id); |
| 120 | - | 73 | +// if ($profile->is_vip == 1){ |
| 121 | - $path = '/Users/lishuai/Desktop/test/'; | 74 | +// $this->output_width = 720; |
| 122 | - $file = $path . 'qinghuaci.mp4'; | 75 | +// $this->output_height = 1280; |
| 123 | -// $file = $path . 'lingdang.mov'; | 76 | +// }else{ |
| 124 | - $is_bgm = false; | 77 | +// $this->output_width = 540; |
| 125 | - $bgm = $path . 'bgm.aac'; | 78 | +// $this->output_height = 960; |
| 126 | - | 79 | +// } |
| 127 | - | 80 | + |
| 128 | - $string = $this->getTextContentString(); | 81 | + $this->output_width = 540; |
| 129 | - | 82 | + $this->output_height = 960; |
| 130 | - // 1.getmediainfo 记录时长,音频视频取最长。 | 83 | + |
| 131 | - $cmd = $this->ffprobe . ' -v quiet -print_format json -show_format -show_streams ' . escapeshellarg($file); | 84 | + if ($this->immerse->type == 1){ |
| 132 | - $output = $this->execmd($cmd); | 85 | + // 1. 分析用户上传音频 |
| 133 | - $media_info = json_decode($output, true); | 86 | + $upload_file = Storage::disk('public')->path($this->immerse->upload_file); |
| 134 | - if (json_last_error() === JSON_ERROR_UTF8) { | 87 | + $mediainfo = $this->mediainfo($upload_file); |
| 135 | - $output = mb_convert_encoding($output, "UTF-8"); | 88 | + // 记录媒体信息时长 |
| 136 | - $media_info = json_decode($output, true); | 89 | + $duration = $mediainfo['format']['duration'] ?: 0; |
| 137 | - } | 90 | + |
| 138 | - | 91 | + $font = Storage::disk('public')->path('uploads/201/71/arialuni.ttf'); |
| 139 | - /** 记录媒体信息时长*/ | 92 | + |
| 140 | - $media_file_time_length = isset($media_info['format']['duration']) ? $media_info['format']['duration'] : 0; | 93 | + // 音频还取之前的封面。 |
| 141 | - $media_file_size = $media_info['format']['size']; | 94 | + $thumbnail = Storage::disk('public')->path($this->immerse->thumbnail); |
| 142 | - if ($media_info['streams'][0]['codec_type'] !== 'video') { | 95 | + |
| 143 | - return 0; | 96 | + |
| 144 | - } | 97 | + if ($this->immerse->origin_video_url == '' || $this->immerse->origin_video_url == null) { |
| 145 | - | 98 | + // 原官方临境是图片 |
| 146 | - | 99 | + $origin_image_path = Storage::disk('public')->path($this->immerse->origin_image_url); |
| 147 | - // 2. 判断是否有视频原音,没有原音用背景音,没有背景音则混入anullsrc | 100 | + |
| 101 | + if ($this->immerse->bgm == '' || $this->immerse->bgm == null) { | ||
| 102 | + $audio = $upload_file; | ||
| 103 | + }else{ | ||
| 104 | + $audio = Storage::disk('public')->path($this->getTempPath('.mp3')); | ||
| 105 | + $bgm = Storage::disk('public')->path($this->immerse->bgm); | ||
| 106 | + $cmd = $this->ffmpeg . ' -y ' . | ||
| 107 | + ' -i ' . escapeshellarg($upload_file) . | ||
| 108 | + ' -i ' . escapeshellarg($bgm) . | ||
| 109 | + ' -filter_complex "amix=inputs=2:duration=first" ' . | ||
| 110 | + ' -ar 48000 -ab 64k ' . escapeshellarg($audio); | ||
| 111 | + | ||
| 112 | + if (!$this->execmd($cmd)) return 0; | ||
| 113 | + } | ||
| 148 | 114 | ||
| 149 | - if ( $media_info['format']['nb_streams'] >= 2 ){ /** 音频视频轨都有 */ | 115 | + $drawtext = $this->getTextContentString($font); |
| 150 | - if ($is_bgm){ | 116 | + $watermark = Storage::disk('public')->path('uploads/201/71/LOGO_eng.png'); |
| 151 | - // 有背景音 融合 | 117 | + $video = Storage::disk('public')->path($this->getTempPath('.mp4',false)); |
| 152 | - $audio = $this->getTempPath('.mp3'); | 118 | + //直接将音频替换 |
| 119 | + $cmd = $this->ffmpeg . ' -y ' . | ||
| 120 | + ' -loop 1 -i ' . escapeshellarg($origin_image_path) . | ||
| 121 | + ' -i ' . escapeshellarg($audio) . | ||
| 122 | + ' -i ' . escapeshellarg($watermark) . | ||
| 123 | + ' -filter_complex "[0:v]scale=' . $this->output_width . ':' . $this->output_height . ',setdar=dar=9/16,' . $drawtext . | ||
| 124 | + ' [text];[text][2:v]overlay=20:20[v]"' . | ||
| 125 | + ' -map [v] -map 1:0 ' . | ||
| 126 | + ' -c:v libx264 -bt 256k -r 25 -t ' . $duration . | ||
| 127 | + ' -ar 48000 -ac 2 -qmin 30 -qmax 60 -profile:v high -pix_fmt yuv420p -preset fast '. | ||
| 128 | + escapeshellarg($video); | ||
| 153 | 129 | ||
| 154 | - $cmd = $this->ffmpeg. | ||
| 155 | - ' -y -i ' . escapeshellarg($file). | ||
| 156 | - ' -y -i ' . escapeshellarg($bgm). | ||
| 157 | - ' -filter_complex amix=inputs=2:duration=first:dropout_transition=2 ' . | ||
| 158 | - '-ar 48000 -ab 64k ' . escapeshellarg($audio); | ||
| 159 | if (!$this->execmd($cmd)) return 0; | 130 | if (!$this->execmd($cmd)) return 0; |
| 160 | - | ||
| 161 | - $audio_input = ' -i ' . escapeshellarg($audio); | ||
| 162 | - $audio_filter = '[3:a]'; | ||
| 163 | - }else{ | ||
| 164 | - // 没有背景音 | ||
| 165 | - $audio_input = ''; | ||
| 166 | - $audio_filter = '[0:1]'; | ||
| 167 | - | ||
| 168 | } | 131 | } |
| 169 | 132 | ||
| 170 | - }elseif ( $media_info['format']['nb_streams'] == 1 ){ | 133 | + if ($this->immerse->origin_image_url == '' || $this->immerse->origin_image_url == null) { |
| 134 | + // 原官方临境是视频 | ||
| 135 | + $origin_video_path = Storage::disk('public')->path($this->immerse->origin_video_url); | ||
| 136 | + $origin_mediainfo = $this->mediainfo($origin_video_path); | ||
| 137 | + | ||
| 138 | + //用户录音超长,截取用户录音 | ||
| 139 | + if ($mediainfo['format']['duration'] > $origin_mediainfo['format']['duration']) { | ||
| 140 | + if ($this->immerse->bgm == '' || $this->immerse->bgm == null) { | ||
| 141 | + $audio = Storage::disk('public')->path($this->getTempPath('.mp3')); | ||
| 142 | + $cmd = $this->ffmpeg . ' -y ' . | ||
| 143 | + ' -i ' . escapeshellarg($upload_file) . | ||
| 144 | + ' -ss 0 -t ' . escapeshellarg($origin_mediainfo['format']['duration']) . | ||
| 145 | + ' -ar 48000 -ab 64k '.escapeshellarg($audio); | ||
| 146 | + if (!$this->execmd($cmd)) return 0; | ||
| 147 | + }else{ | ||
| 148 | + $bgm = Storage::disk('public')->path($this->immerse->bgm); | ||
| 149 | + $audio = Storage::disk('public')->path($this->getTempPath('.mp3')); | ||
| 150 | + $cmd = $this->ffmpeg . ' -y ' . | ||
| 151 | + ' -i ' . escapeshellarg($upload_file) . | ||
| 152 | + ' -i ' . escapeshellarg($bgm) . | ||
| 153 | + ' -filter_complex "amix=inputs=2:duration=first" ' . | ||
| 154 | + ' -ss 0 -t ' . escapeshellarg($origin_mediainfo['format']['duration']) . | ||
| 155 | + ' -ar 48000 -ab 64k '.escapeshellarg($audio); | ||
| 156 | + if (!$this->execmd($cmd)) return 0; | ||
| 157 | + } | ||
| 158 | + }else{ | ||
| 159 | + if ($this->immerse->bgm == '' || $this->immerse->bgm == null) { | ||
| 160 | + $audio = $upload_file; | ||
| 161 | + }else{ | ||
| 162 | + $audio = Storage::disk('public')->path($this->getTempPath('.mp3')); | ||
| 163 | + $bgm = Storage::disk('public')->path($this->immerse->bgm); | ||
| 164 | + $cmd = $this->ffmpeg . ' -y ' . | ||
| 165 | + ' -i ' . escapeshellarg($upload_file) . | ||
| 166 | + ' -i ' . escapeshellarg($bgm) . | ||
| 167 | + ' -filter_complex "amix=inputs=2:duration=first[a]" -map [a]' . | ||
| 168 | + ' -ar 48000 -ab 64k' . escapeshellarg($audio); | ||
| 169 | + | ||
| 170 | + if (!$this->execmd($cmd)) return 0; | ||
| 171 | + } | ||
| 172 | + } | ||
| 171 | 173 | ||
| 172 | - $audio = $this->getTempPath('.mp3'); | 174 | + $drawtext = $this->getTextContentString($font); |
| 173 | - $cmd = $this->ffmpeg . | 175 | + $watermark = Storage::disk('public')->path('uploads/201/71/LOGO_eng.png'); |
| 174 | - ' -y -f lavfi -i aevalsrc=0:duration='. escapeshellarg($media_file_time_length) . | 176 | + $video = Storage::disk('public')->path($this->getTempPath('.mp4',false)); |
| 175 | - ' -ar 48000 -ab 64k ' . escapeshellarg($audio); | 177 | + |
| 176 | - if (!$this->execmd($cmd)) return 0; | 178 | + //直接将音频替换 |
| 179 | + $cmd = $this->ffmpeg . ' -y ' . | ||
| 180 | + ' -i ' . escapeshellarg($origin_video_path) . | ||
| 181 | + ' -i ' . escapeshellarg($audio) . | ||
| 182 | + ' -i ' . escapeshellarg($watermark) . | ||
| 183 | + ' -filter_complex "[0:v]scale=' . $this->output_width . ':' . $this->output_height . ',setdar=dar=9/16,' . $drawtext . | ||
| 184 | + ' [text];[text][2:v]overlay=20:20[v]"' . | ||
| 185 | + ' -map [v] -map 1:0 ' . | ||
| 186 | + ' -c:v libx264 -bt 256k -r 25' . | ||
| 187 | + ' -ar 48000 -ac 2 -qmin 30 -qmax 60 -profile:v high -pix_fmt yuv420p -preset fast '. | ||
| 188 | + escapeshellarg($video); | ||
| 177 | 189 | ||
| 178 | - if ($is_bgm){ | ||
| 179 | - $audio_empty = $audio; | ||
| 180 | - $audio = $this->getTempPath('.mp3'); | ||
| 181 | - $cmd = $this->ffmpeg. | ||
| 182 | - ' -y -i ' . escapeshellarg($audio_empty). | ||
| 183 | - ' -y -i ' . escapeshellarg($bgm). | ||
| 184 | - ' -filter_complex amix=inputs=2:duration=first:dropout_transition=2 ' . | ||
| 185 | - '-ar 48000 -ab 64k ' . escapeshellarg($audio); | ||
| 186 | if (!$this->execmd($cmd)) return 0; | 190 | if (!$this->execmd($cmd)) return 0; |
| 187 | } | 191 | } |
| 192 | + }else{ | ||
| 193 | + // 1. 分析用户上传视频 | ||
| 194 | + $upload_file = Storage::disk('public')->path($this->immerse->upload_file); | ||
| 195 | + $mediainfo = $this->mediainfo($upload_file); | ||
| 188 | 196 | ||
| 189 | - $audio_input = ' -i ' . escapeshellarg($audio); | 197 | + // 记录媒体信息时长 |
| 190 | - $audio_filter = '[3:a]'; | 198 | + $duration = $mediainfo['format']['duration'] ?: 0; |
| 191 | - | ||
| 192 | - }else{ /** 音频视频轨都没有 */ | ||
| 193 | - return 0; | ||
| 194 | - } | ||
| 195 | 199 | ||
| 196 | - $end_wallpaper = Storage::disk('public')->path('ffmpeg') . "/end_wallpaper.png"; | 200 | + // |
| 197 | - $thumbnail = Storage::disk('public')->path('ffmpeg') . "/thumbnail.png"; | 201 | + $end_wallpaper = Storage::disk('public')->path('uploads/201/71/end_wallpaper.png'); |
| 198 | - $font = Storage::disk('public')->path('ffmpeg') . "/arialuni.ttf"; | 202 | + $avatar = Storage::disk('public')->path('uploads/201/71/thumbnail.png'); |
| 199 | - $signature = "一言 username"; | 203 | + $font = Storage::disk('public')->path('uploads/201/71/arialuni.ttf'); |
| 200 | - | ||
| 201 | - // 生成贴纸和签名 | ||
| 202 | - $end_wallpaper = $this->wallpaperWithSignature($end_wallpaper, $thumbnail, $signature, $font); | ||
| 203 | - | ||
| 204 | - // 截取最后一帧 | ||
| 205 | - $last_frame_video = $this->getTempPath(); | ||
| 206 | - $width = $media_info['streams'][0]['width']; | ||
| 207 | - $height = $media_info['streams'][0]['height']; | ||
| 208 | - $size = $width . 'x' . $height; | ||
| 209 | - $time_length = 0.7; | ||
| 210 | - $r = 24; | ||
| 211 | - $frame_n = $media_info['streams'][0]['nb_frames'] - 2; | ||
| 212 | - $cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($file) . | ||
| 213 | - " -f lavfi -i nullsrc=s={$size}:d={$time_length}:r={$r} -f lavfi -i aevalsrc=0:duration={$time_length}" . | ||
| 214 | - " -filter_complex \"[0:v]select='eq(n,{$frame_n})',setpts=PTS-STARTPTS[lastframe];[1:v][lastframe]overlay[v]\"" . | ||
| 215 | - ' -map [v] -map 2:a ' . escapeshellarg($last_frame_video); | ||
| 216 | - if (!$this->execmd($cmd)) return 0; | ||
| 217 | - | ||
| 218 | - | ||
| 219 | - $signature_x = 0; | ||
| 220 | - $signature_y = -20; | ||
| 221 | - $animate = $this->makeAnimate($last_frame_video, $end_wallpaper, '', $signature_x, $signature_y, $font); | ||
| 222 | - | ||
| 223 | - $font_size = ceil($width / 14); | ||
| 224 | - $content = '题破山寺后禅院' . "\t" . ' -- 常建' . PHP_EOL . | ||
| 225 | - '清晨入古寺,初日照高林。' . PHP_EOL . | ||
| 226 | - '曲径通幽处,禅房花木深。' . PHP_EOL . | ||
| 227 | - '山光悦鸟性,潭影空人心。' . PHP_EOL . | ||
| 228 | - '万籁此都寂,但余钟磬音。' . PHP_EOL ; | ||
| 229 | - | ||
| 230 | - $cmd = $this->ffmpeg . ' -y '. | ||
| 231 | - ' -i ' . escapeshellarg($file). | ||
| 232 | - ' -i ' . escapeshellarg($animate). | ||
| 233 | - ' -i ' . escapeshellarg($path . 'logo.png'). | ||
| 234 | - $audio_input. | ||
| 235 | - ' -filter_complex "[0:0] ' . | ||
| 236 | - $string. | ||
| 237 | - '[text];[text]'. | ||
| 238 | - '[2:v]overlay=20:20[water];[water]'.$audio_filter.'[1:0][1:1] concat=n=2:v=1:a=1[v][a]" '. | ||
| 239 | - ' -map [v] -map [a]'. | ||
| 240 | - ' -c:v libx264 -bt 256k -r 25' . | ||
| 241 | - ' -ar 44100 -ac 2 -qmin 30 -qmax 60 -profile:v baseline -preset fast ' . | ||
| 242 | - escapeshellarg($path . 'out.mp4'); | ||
| 243 | - | ||
| 244 | - $this->execmd($cmd); | ||
| 245 | - | ||
| 246 | - | ||
| 247 | -// $cmd = $this->ffmpeg. ' -y'. | ||
| 248 | -// ' -i ' . escapeshellarg($path . 'out.mp4'). | ||
| 249 | -// ' -i ' . escapeshellarg($path . 'thumbnail.png'). | ||
| 250 | -// ' -map 1 -map 0 -c copy -disposition:0 attached_pic '. | ||
| 251 | -// escapeshellarg($path . 'out2.mp4'); | ||
| 252 | // | 204 | // |
| 253 | -// $this->execmd($cmd); | 205 | +// $user = User::query()->find($this->immerse->user_id); |
| 254 | - | 206 | +// $signature = $user->nickname ?? $user->email ?? '2@qq.com'; |
| 255 | - return 0; | 207 | +// |
| 256 | - | 208 | +// // 生成贴纸和签名 |
| 257 | - $adminMakeVideo = AdminMakeVideo::query()->first(); | 209 | +// $end_wallpaper = $this->wallpaperWithSignature($end_wallpaper, $avatar, $signature, $font); |
| 258 | - $adminMakeVideo->video_url = Storage::disk('public')->path($adminMakeVideo->video_url); | 210 | +// |
| 259 | -// $adminMakeVideo->thumbnail_url = Storage::disk('public')->path($adminMakeVideo->thumbnail_url); | 211 | +// // 截取最后一帧做动画 |
| 260 | - $adminMakeVideo->poem; | 212 | +// $last_frame_video = $this->getTempPath(); |
| 261 | - $adminMakeVideo->temp->components; | 213 | +// $this->width = $width = $mediainfo['streams'][0]['width']; |
| 262 | - | 214 | +// $height = $mediainfo['streams'][0]['height']; |
| 263 | - | 215 | +// $size = $width . 'x' . $height; |
| 264 | - $file = $adminMakeVideo->video_url; | 216 | +// $time_length = 0.7; |
| 265 | -// $watermark = Storage::disk('public')->path('image/logo.jpg'); | 217 | +// $r = 24; |
| 266 | - // 转换logo大小 | 218 | +// $frame_n = $mediainfo['streams'][0]['nb_frames'] - 2; |
| 267 | - $watermark = $this->translateLogo(Storage::disk('public')->path('image/logo.jpg')); | 219 | +// $cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($upload_file) . |
| 268 | - $watermark_x = 20; | 220 | +// " -f lavfi -i nullsrc=s={$size}:d={$time_length}:r={$r} -f lavfi -i aevalsrc=0:duration={$time_length}" . |
| 269 | - $watermark_y = 20; | 221 | +// " -filter_complex \"[0:v]select='eq(n,{$frame_n})',setpts=PTS-STARTPTS[lastframe];[1:v][lastframe]overlay[v]\"" . |
| 270 | - $end_wallpaper = Storage::disk('public')->path('ffmpeg') . "/end_wallpaper.png"; | 222 | +// ' -map [v] -map 2:a ' . escapeshellarg($last_frame_video); |
| 271 | - $thumbnail = Storage::disk('public')->path('ffmpeg') . "/thumbnail.png"; | 223 | +// if (!$this->execmd($cmd)) return 0; |
| 272 | - $font = Storage::disk('public')->path('ffmpeg') . "/arialuni.ttf"; | 224 | + // |
| 273 | - $signature = "一言"; | 225 | + |
| 274 | - $signature_x = 0; | 226 | + $drawtext = $this->getTextContentString($font); |
| 275 | - $signature_y = -20; | 227 | + $watermark = Storage::disk('public')->path('uploads/201/71/LOGO_eng.png'); |
| 276 | -// $content = '题破山寺后禅院' . "\t" . ' -- 常建' . PHP_EOL . | 228 | + |
| 277 | -// '清晨入古寺,初日照高林。' . PHP_EOL . | 229 | + // 截取中间帧作为视频封面 |
| 278 | -// '曲径通幽处,禅房花木深。' . PHP_EOL . | 230 | + $frame = ceil($mediainfo['streams'][0]['nb_frames'] / 2); |
| 279 | -// '山光悦鸟性,潭影空人心。' . PHP_EOL . | 231 | + $thumbnail = Storage::disk('public')->path($this->getTempPath('.jpg',false)); |
| 280 | -// '万籁此都寂,但余钟磬音。' . PHP_EOL ; | 232 | + $cmd = $this->ffmpeg . ' -y ' . |
| 281 | - $content = $adminMakeVideo->poem->content; | 233 | + ' -i ' . escapeshellarg($upload_file) . |
| 282 | - | 234 | + ' -i ' . escapeshellarg($watermark) . |
| 283 | - | 235 | + ' -filter_complex "[0:v]select=\'eq(n,' . $frame . ')\'[img]" ' . |
| 284 | - // 生成贴纸和签名 | 236 | + ' -map [img]'. |
| 285 | - $end_wallpaper = $this->wallpaperWithSignature($end_wallpaper, $thumbnail, $signature, $font); | 237 | + ' -frames:v 1 -s ' . $this->output_width . 'x' . $this->output_height .' -preset superfast '. |
| 286 | - | 238 | + escapeshellarg($thumbnail); |
| 287 | - // 截取最后一帧 | 239 | + if (!$this->execmd($cmd)) return 0; |
| 288 | - $last_frame_video = $this->makeLastFrameVideo($file); | ||
| 289 | - | ||
| 290 | - $animate = ''; | ||
| 291 | - if ($last_frame_video) { | ||
| 292 | - $animate = $this->makeAnimate($last_frame_video, $end_wallpaper, '', $signature_x, $signature_y, $font); | ||
| 293 | - } | ||
| 294 | - | ||
| 295 | - | ||
| 296 | - $video = $this->getTempPath(); | ||
| 297 | - $watermark_x = $watermark_x ? $watermark_x : 0; | ||
| 298 | - $watermark_y = $watermark_y ? $watermark_y : 0; | ||
| 299 | - $am_inp = ' -i ' . escapeshellarg($watermark); | ||
| 300 | - $am_filter = "[2:v]overlay={$watermark_x}:{$watermark_y}[water];[water]"; | ||
| 301 | - | ||
| 302 | - $cmd = $this->ffmpeg . ' -y' . | ||
| 303 | - ' -i ' . escapeshellarg($file) . | ||
| 304 | - ' -i ' . escapeshellarg($animate) . | ||
| 305 | - $am_inp . | ||
| 306 | -// ' -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]"' . | ||
| 307 | - ' -filter_complex "[0:0]' . '' . $am_filter . '[0:1] [1:0] [1:1] concat=n=2:v=1:a=1 [v] [a]"' . | ||
| 308 | - ' -map [v] -map [a]'; | ||
| 309 | - | ||
| 310 | - $cmd .= | ||
| 311 | - ' -c:v libx264 -s 800x450 -bt 256k -r 25' . | ||
| 312 | -// todo 没有libfdk_aac库 | ||
| 313 | -// ' -c:a libfdk_aac -ar 44100 -ac 2 -qmin 30 -qmax 60 -profile:v baseline -preset fast ' . | ||
| 314 | - ' -ar 44100 -ac 2 -qmin 30 -qmax 60 -profile:v baseline -preset fast ' . | ||
| 315 | - escapeshellarg($video); | ||
| 316 | - | ||
| 317 | - if ($this->execmd($cmd)) { | ||
| 318 | - // todo create insert | ||
| 319 | - } else { | ||
| 320 | - return false; | ||
| 321 | - } | ||
| 322 | - | ||
| 323 | - $video2 = $this->getTempPath(); | ||
| 324 | - $cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($video) . | ||
| 325 | - ' -vf '. | ||
| 326 | - 'drawtext="'. | ||
| 327 | - 'fontfile=' . escapeshellarg($font) . ':'. | ||
| 328 | - 'text=' . escapeshellarg($content) . ':'. | ||
| 329 | - 'fontsize=43:'. | ||
| 330 | - 'fontcolor=white@1.0:'. | ||
| 331 | - 'x=main_w/2' . '-260' . ':'. | ||
| 332 | - 'y=main_h/2' . '-20' . ':'. | ||
| 333 | - 'box=1:boxcolor=0xd0cdcc@0.5'. | ||
| 334 | - '" ' . escapeshellarg($video2); | ||
| 335 | - | ||
| 336 | - | ||
| 337 | - | ||
| 338 | - if ($this->execmd($cmd)) { | ||
| 339 | - // 全部合成以后创建 临境 | ||
| 340 | - $video_info = $this->mediainfo($video2); | ||
| 341 | - Immerse::query()->create([ | ||
| 342 | - 'user_id' => 1, | ||
| 343 | - 'title' => '', | ||
| 344 | - 'content' => $adminMakeVideo->feel, | ||
| 345 | - 'url' => $video2, | ||
| 346 | - 'type' => $adminMakeVideo->type == 1 ? 2 : 1, | ||
| 347 | - 'duration' => $video_info['format']['duration'], | ||
| 348 | - 'size' => $video_info['format']['size'], | ||
| 349 | - ]); | ||
| 350 | - } else { | ||
| 351 | - return false; | ||
| 352 | - } | ||
| 353 | - | ||
| 354 | - } | ||
| 355 | - | ||
| 356 | - | ||
| 357 | - /** | ||
| 358 | - * 获取圆形头像 | ||
| 359 | - * @param $img | ||
| 360 | - * @param int $dst_w | ||
| 361 | - * @param int $dst_h | ||
| 362 | - * @return resource | ||
| 363 | - */ | ||
| 364 | - public function getCircleAvatar($img, $dst_w = 96, $dst_h = 96) | ||
| 365 | - { | ||
| 366 | - $w = 130; | ||
| 367 | - $h = 130; | ||
| 368 | - $src = imagecreatetruecolor($dst_w, $dst_h); | ||
| 369 | - imagecopyresized($src, $img, 0, 0, 0, 0, $dst_w, $dst_h, $w, $h); | ||
| 370 | - | ||
| 371 | - $newpic = imagecreatetruecolor($dst_w, $dst_h); | ||
| 372 | - imagealphablending($newpic, false); | ||
| 373 | - imagecopyresampled($newpic, $img, 0, 0, 0, 0, $dst_w, $dst_h, $w, $h); | ||
| 374 | - $mask = imagecreatetruecolor($dst_w, $dst_h); | ||
| 375 | - $transparent = imagecolorallocate($mask, 255, 0, 0); | ||
| 376 | - imagecolortransparent($mask,$transparent); | ||
| 377 | - imagefilledellipse($mask, $dst_w / 2, $dst_h / 2, $dst_w, $dst_h, $transparent); | ||
| 378 | - $red = imagecolorallocate($mask, 0, 0, 0); | ||
| 379 | - imagecopymerge($newpic, $mask, 0, 0, 0, 0, $dst_w, $dst_h, 100); | ||
| 380 | - imagecolortransparent($newpic,$red); | ||
| 381 | - imagesavealpha($newpic,true); | ||
| 382 | - imagefill($newpic, 0, 0, $red); | ||
| 383 | - imagedestroy($mask); | ||
| 384 | - return $newpic; | ||
| 385 | - } | ||
| 386 | - | ||
| 387 | - /** | ||
| 388 | - * 制作最后一帧 | ||
| 389 | - * @param $file | ||
| 390 | - * @return bool|string | ||
| 391 | - */ | ||
| 392 | - public function makeLastFrameVideo($file) { | ||
| 393 | - $video = $this->getTempPath(); | ||
| 394 | - $width = $this->getVideoWith($file); | ||
| 395 | - $height = $this->getVideoHeight($file); | ||
| 396 | - $size = $width . 'x' . $height; | ||
| 397 | - $time_length = 0.7; | ||
| 398 | - $r = 24; | ||
| 399 | - $frame_n = $this->getVideoFrameNum($file) - 2; | ||
| 400 | - $cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($file) . | ||
| 401 | - " -f lavfi -i nullsrc=s={$size}:d={$time_length}:r={$r} -f lavfi -i aevalsrc=0:duration={$time_length}" . | ||
| 402 | - " -filter_complex \"[0:v]select='eq(n,{$frame_n})',setpts=PTS-STARTPTS[lastframe];[1:v][lastframe]overlay[v]\"" . | ||
| 403 | - ' -map [v] -map 2:a ' . escapeshellarg($video); | ||
| 404 | - if ($this->execmd($cmd)) { | ||
| 405 | - return $video; | ||
| 406 | - } else { | ||
| 407 | - return false; | ||
| 408 | - } | ||
| 409 | - } | ||
| 410 | 240 | ||
| 411 | - /** | 241 | + $video = Storage::disk('public')->path($this->getTempPath('.mp4',false)); |
| 412 | - * 用最后一帧和贴纸制作动画 | 242 | + // 没有背景音 |
| 413 | - * @param $last_frame_video | 243 | + if ($this->immerse->bgm == '' || $this->immerse->bgm == null) { |
| 414 | - * @param $end_wallpaper | 244 | + $cmd = $this->ffmpeg . ' -y ' . |
| 415 | - * @param $signature | 245 | + ' -i ' . escapeshellarg($upload_file) . |
| 416 | - * @param $signature_x | 246 | + ' -i ' . escapeshellarg($watermark) . |
| 417 | - * @param $signature_y | 247 | + ' -filter_complex "[0:v]scale=' . $this->output_width . ':' . $this->output_height . ',setdar=dar=9/16,' . $drawtext . |
| 418 | - * @param $font | 248 | + ' [text];[text][1:v]overlay=20:20"' . |
| 419 | - * @return bool|string | 249 | + ' -c:v libx264 -bt 256k -r 25' . |
| 420 | - */ | 250 | + ' -c:a copy -qmin 30 -qmax 60 -profile:v high -pix_fmt yuv420p -preset fast ' . |
| 421 | - public function makeAnimate($last_frame_video, $end_wallpaper, $signature, $signature_x, $signature_y, $font) { | 251 | + escapeshellarg($video); |
| 422 | - $signature_x = $signature_x >= 0 ? '+' . $signature_x : '-' . abs($signature_x); | 252 | + }else{ |
| 423 | - $signature_y = $signature_y >= 0 ? '+' . $signature_y : '-' . abs($signature_y); | 253 | + $bgm = Storage::disk('public')->path($this->immerse->bgm); |
| 424 | - $video = $this->getTempPath(); | 254 | + $cmd = $this->ffmpeg . ' -y ' . |
| 425 | - if ($signature !== '') { | 255 | + ' -i ' . escapeshellarg($upload_file) . |
| 426 | - $cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($last_frame_video) . | 256 | + ' -i ' . escapeshellarg($watermark) . |
| 427 | - ' -loop 1 -i ' . escapeshellarg($end_wallpaper) . | 257 | + ' -i ' . escapeshellarg($bgm) . |
| 428 | - ' -filter_complex "'. | 258 | + ' -filter_complex "[0:v]scale=' . $this->output_width . ':' . $this->output_height . ',setdar=dar=9/16,' . $drawtext . |
| 429 | - 'geq=lum=\'if(lte(T,0.6), 255*T*(1/0.6),255)\',format=gray[grad];'. | 259 | + ' [text];[text][1:v]overlay=20:20[v];' . |
| 430 | - '[0:v]boxblur=8[blur];'. | 260 | + ' amix=inputs=2:duration=first[a] " -map [v] -map [a]' . |
| 431 | - '[blur][1:v]overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2 [lay];[lay]'. | 261 | + ' -c:v libx264 -bt 256k -r 25' . |
| 432 | - 'drawtext='. | 262 | + ' -ar 44100 -ac 2 -qmin 30 -qmax 60 -profile:v high -pix_fmt yuv420p -preset fast ' . |
| 433 | - 'fontfile=' . escapeshellarg($font) . ':'. | 263 | + escapeshellarg($video); |
| 434 | - 'text=' . escapeshellarg($signature) . ':'. | 264 | + } |
| 435 | - 'fontsize=23:'. | ||
| 436 | - 'fontcolor=white@1.0:'. | ||
| 437 | - 'x=main_w/2' . $signature_x . ':'. | ||
| 438 | - 'y=main_h/2' . $signature_y . '[text];[text]'. | ||
| 439 | - '[grad]alphamerge[alpha];'. | ||
| 440 | - '[0:v][alpha]overlay'. | ||
| 441 | - '" ' . escapeshellarg($video); | ||
| 442 | - } else { | ||
| 443 | - $cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($last_frame_video) . | ||
| 444 | - ' -loop 1 -i ' . escapeshellarg($end_wallpaper) . | ||
| 445 | - ' -filter_complex "'. | ||
| 446 | - 'geq=lum=\'if(lte(T,0.6), 255*T*(1/0.6),255)\',format=gray[grad];'. | ||
| 447 | - '[0:v]boxblur=8[blur];'. | ||
| 448 | - '[blur][1:v]overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2 [lay];'. | ||
| 449 | - '[lay][grad]alphamerge[alpha];'. | ||
| 450 | - '[0:v][alpha]overlay'. | ||
| 451 | - '" ' . escapeshellarg($video); | ||
| 452 | - } | ||
| 453 | - if ($this->execmd($cmd)) { | ||
| 454 | - return $video; | ||
| 455 | - } else { | ||
| 456 | - return false; | ||
| 457 | - } | ||
| 458 | - } | ||
| 459 | 265 | ||
| 460 | - /** | 266 | + if (!$this->execmd($cmd)) return 0; |
| 461 | - * 获取视频宽度 | ||
| 462 | - * @param $file | ||
| 463 | - * @param bool $cache | ||
| 464 | - * @return int|null | ||
| 465 | - */ | ||
| 466 | - public function getVideoWith($file, $cache = true) { | ||
| 467 | - $result = $this->getFirstVideoTrackOption($file, $option = 'width', $cache); | ||
| 468 | - if ($result) { | ||
| 469 | - return (int)$result; | ||
| 470 | - } else { | ||
| 471 | - return $result; | ||
| 472 | } | 267 | } |
| 473 | - } | ||
| 474 | 268 | ||
| 475 | - /** | 269 | + // 全部合成以后创建 临境 |
| 476 | - * 获取视频高度 | 270 | + $video_info = $this->mediainfo($video); |
| 477 | - * @param $file | ||
| 478 | - * @param bool $cache | ||
| 479 | - * @return int|null | ||
| 480 | - */ | ||
| 481 | - public function getVideoHeight($file, $cache = true) { | ||
| 482 | - $result = $this->getFirstVideoTrackOption($file, $option = 'height', $cache); | ||
| 483 | - if ($result) { | ||
| 484 | - return (int)$result; | ||
| 485 | - } else { | ||
| 486 | - return $result; | ||
| 487 | - } | ||
| 488 | - } | ||
| 489 | 271 | ||
| 490 | - /** | 272 | + $this->immerse->url = Str::of($video)->replace(Storage::disk('public')->path(''), ''); |
| 491 | - * 获取视频帧数 | 273 | + $this->immerse->state = 1; |
| 492 | - * @param $file | 274 | + $this->immerse->duration = $video_info['format']['duration'] ?? 0.00; |
| 493 | - * @param bool $cache | 275 | + $this->immerse->size = $video_info['format']['size']; |
| 494 | - * @return null | 276 | + $this->immerse->save(); |
| 495 | - */ | ||
| 496 | - public function getVideoFrameNum($file, $cache = true) { | ||
| 497 | - return $this->getFirstVideoTrackOption($file, $option = 'nb_frames', $cache); | ||
| 498 | - } | ||
| 499 | 277 | ||
| 500 | 278 | ||
| 501 | - public function getFirstVideoTrackOption($file, $option, $cache = true) { | ||
| 502 | - return $this->getFirstTrackOption($file, $option, $codec_type = 'video', $cache = true); | ||
| 503 | - } | ||
| 504 | 279 | ||
| 505 | - public function getFirstTrackOption($file, $option, $codec_type = '', $cache = true) { | 280 | + return 0; |
| 506 | - $result = $this->mediainfo($file, $cache); | ||
| 507 | - if (!isset($result['streams'])) { | ||
| 508 | - return null; | ||
| 509 | - } | ||
| 510 | - $_track = null; | ||
| 511 | - foreach($result['streams'] as $track) { | ||
| 512 | - if (empty($codec_type)) { | ||
| 513 | - $_track = $track; | ||
| 514 | - break; | ||
| 515 | - } elseif ($track['codec_type'] == $codec_type) { | ||
| 516 | - $_track = $track; | ||
| 517 | - break; | ||
| 518 | - } | ||
| 519 | - } | ||
| 520 | - if (isset($_track[$option])) { | ||
| 521 | - return $_track[$option]; | ||
| 522 | - } | ||
| 523 | - return null; | ||
| 524 | } | 281 | } |
| 525 | 282 | ||
| 526 | - /*** | 283 | + public function mediainfo($file) |
| 527 | - * 获取视频信息(配合ffprobe) | 284 | + { |
| 528 | - * @param $file | ||
| 529 | - * @param bool $cache | ||
| 530 | - * @return mixed | ||
| 531 | - */ | ||
| 532 | - public function mediainfo($file, $cache = true) { | ||
| 533 | - global $_mediainfo; | ||
| 534 | $cmd = $this->ffprobe . ' -v quiet -print_format json -show_format -show_streams ' . escapeshellarg($file); | 285 | $cmd = $this->ffprobe . ' -v quiet -print_format json -show_format -show_streams ' . escapeshellarg($file); |
| 535 | - if ($cache && isset($_mediainfo[$file])) { | ||
| 536 | - return $_mediainfo[$file]; | ||
| 537 | - } | ||
| 538 | $output = $this->execmd($cmd); | 286 | $output = $this->execmd($cmd); |
| 539 | $data = json_decode($output, true); | 287 | $data = json_decode($output, true); |
| 540 | if (json_last_error() === JSON_ERROR_UTF8) { | 288 | if (json_last_error() === JSON_ERROR_UTF8) { |
| 541 | $output = mb_convert_encoding($output, "UTF-8"); | 289 | $output = mb_convert_encoding($output, "UTF-8"); |
| 542 | $data = json_decode($output, true); | 290 | $data = json_decode($output, true); |
| 543 | } | 291 | } |
| 544 | - if ($cache) { | ||
| 545 | - $mediainfo[$file] = $data; | ||
| 546 | - } | ||
| 547 | return $data; | 292 | return $data; |
| 548 | } | 293 | } |
| 549 | 294 | ||
| 550 | - /** | ||
| 551 | - * 获取输出临时文件名 | ||
| 552 | - * @return string | ||
| 553 | - */ | ||
| 554 | - public function getTempPath($ext = '.mp4') | ||
| 555 | - { | ||
| 556 | - $filename = "/output_" . time() . rand(0, 10000); | ||
| 557 | - | ||
| 558 | - $hash_hex = md5($filename); | ||
| 559 | - // 16进制表示的字符串一共32字节,表示16个二进制字节。 | ||
| 560 | - // 前16个字符用来第一级求摸,后16个用做第二级 | ||
| 561 | - $hash_hex_l1 = substr($hash_hex, 0, 8); | ||
| 562 | - $hash_hex_l2 = substr($hash_hex, 8, 8); | ||
| 563 | - $dir_l1 = hexdec($hash_hex_l1) % 256; | ||
| 564 | - $dir_l2 = hexdec($hash_hex_l2) % 512; | ||
| 565 | - $dir = 'temp/'. $dir_l1. '/' . $dir_l2; | ||
| 566 | - | ||
| 567 | - if( !Storage::disk('public')->exists($dir)) Storage::disk('public')->makeDirectory($dir); | ||
| 568 | - | ||
| 569 | - return Storage::disk('public')->path($dir . $filename . $ext); | ||
| 570 | - } | ||
| 571 | - | ||
| 572 | - /** | ||
| 573 | - * 执行命令 | ||
| 574 | - * @param $cmd | ||
| 575 | - * @param bool $update_progress | ||
| 576 | - * @return string | ||
| 577 | - */ | ||
| 578 | public function execmd($cmd, $update_progress = false) { | 295 | public function execmd($cmd, $update_progress = false) { |
| 579 | echo $cmd . "\n". "\n". "\n"; | 296 | echo $cmd . "\n". "\n". "\n"; |
| 580 | $descriptorspec = array( | 297 | $descriptorspec = array( |
| ... | @@ -626,6 +343,97 @@ class DevFFmpeg extends Command | ... | @@ -626,6 +343,97 @@ class DevFFmpeg extends Command |
| 626 | } | 343 | } |
| 627 | 344 | ||
| 628 | /** | 345 | /** |
| 346 | + * 获取输出临时文件名 | ||
| 347 | + * @param string $ext | ||
| 348 | + * @param bool $is_temp | ||
| 349 | + * @return string | ||
| 350 | + */ | ||
| 351 | + public function getTempPath($ext = '.mp4',$is_temp = true) | ||
| 352 | + { | ||
| 353 | + $filename = "/output_" . time() . rand(0, 10000); | ||
| 354 | + | ||
| 355 | + $prefix = $is_temp ? 'temp/' : 'video/'; | ||
| 356 | + $hash_hex = md5($filename); | ||
| 357 | + // 16进制表示的字符串一共32字节,表示16个二进制字节。 | ||
| 358 | + // 前16个字符用来第一级求摸,后16个用做第二级 | ||
| 359 | + $hash_hex_l1 = substr($hash_hex, 0, 8); | ||
| 360 | + $hash_hex_l2 = substr($hash_hex, 8, 8); | ||
| 361 | + $dir_l1 = hexdec($hash_hex_l1) % 256; | ||
| 362 | + $dir_l2 = hexdec($hash_hex_l2) % 512; | ||
| 363 | + $dir = $prefix . $dir_l1 . '/' . $dir_l2; | ||
| 364 | + | ||
| 365 | + if( !Storage::disk('public')->exists($dir)) Storage::disk('public')->makeDirectory($dir); | ||
| 366 | + | ||
| 367 | + return $dir . $filename . $ext; | ||
| 368 | + } | ||
| 369 | + | ||
| 370 | + /** | ||
| 371 | + * 获取图像文件类型 | ||
| 372 | + * @param $img_name | ||
| 373 | + * @return string | ||
| 374 | + */ | ||
| 375 | + public function getImageType($img_name) | ||
| 376 | + { | ||
| 377 | + if (preg_match("/\.(jpg|jpeg|gif|png)$/i", $img_name, $matches)){ | ||
| 378 | + $type = strtolower($matches[1]); | ||
| 379 | + }else{ | ||
| 380 | + $type = "string"; | ||
| 381 | + } | ||
| 382 | + return $type; | ||
| 383 | + } | ||
| 384 | + | ||
| 385 | + /** | ||
| 386 | + * 获取圆形头像 | ||
| 387 | + * @param $img | ||
| 388 | + * @param int $dst_w | ||
| 389 | + * @param int $dst_h | ||
| 390 | + * @return resource | ||
| 391 | + */ | ||
| 392 | + public function getCircleAvatar($img, $dst_w = 96, $dst_h = 96) | ||
| 393 | + { | ||
| 394 | + $w = 130; | ||
| 395 | + $h = 130; | ||
| 396 | + $src = imagecreatetruecolor($dst_w, $dst_h); | ||
| 397 | + imagecopyresized($src, $img, 0, 0, 0, 0, $dst_w, $dst_h, $w, $h); | ||
| 398 | + | ||
| 399 | + $newpic = imagecreatetruecolor($dst_w, $dst_h); | ||
| 400 | + imagealphablending($newpic, false); | ||
| 401 | + imagecopyresampled($newpic, $img, 0, 0, 0, 0, $dst_w, $dst_h, $w, $h); | ||
| 402 | + $mask = imagecreatetruecolor($dst_w, $dst_h); | ||
| 403 | + $transparent = imagecolorallocate($mask, 255, 0, 0); | ||
| 404 | + imagecolortransparent($mask,$transparent); | ||
| 405 | + imagefilledellipse($mask, $dst_w / 2, $dst_h / 2, $dst_w, $dst_h, $transparent); | ||
| 406 | + $red = imagecolorallocate($mask, 0, 0, 0); | ||
| 407 | + imagecopymerge($newpic, $mask, 0, 0, 0, 0, $dst_w, $dst_h, 100); | ||
| 408 | + imagecolortransparent($newpic,$red); | ||
| 409 | + imagesavealpha($newpic,true); | ||
| 410 | + imagefill($newpic, 0, 0, $red); | ||
| 411 | + imagedestroy($mask); | ||
| 412 | + return $newpic; | ||
| 413 | + } | ||
| 414 | + | ||
| 415 | + /** | ||
| 416 | + * 多图融合 | ||
| 417 | + * @param $end_wallpaper | ||
| 418 | + * @param $thumbnail | ||
| 419 | + * @return resource | ||
| 420 | + */ | ||
| 421 | + public function imagesMerge($end_wallpaper, $thumbnail) { | ||
| 422 | + $end_wallpaper = imagecreatefrompng($end_wallpaper); | ||
| 423 | + $background = imagecreatefrompng(Storage::disk('public')->path('ffmpeg/background.png')); | ||
| 424 | + imagesavealpha($background,true); | ||
| 425 | + $temp_wallpaper = imagecreatetruecolor(350, 204); | ||
| 426 | +// $color = imagecolorallocate($temp_wallpaper, 0xd0, 0xcd, 0xcc); | ||
| 427 | + $color = imagecolorallocate($temp_wallpaper, 0xDC, 0x14, 0x3C); | ||
| 428 | + imagefill($temp_wallpaper, 0, 0, $color); | ||
| 429 | + imageColorTransparent($temp_wallpaper, $color); | ||
| 430 | + imagecopyresampled($temp_wallpaper, $end_wallpaper, 0, 0, 0, 0, imagesx($temp_wallpaper), imagesy($temp_wallpaper), imagesx($end_wallpaper), imagesy($end_wallpaper)); | ||
| 431 | + imagecopymerge($background, $temp_wallpaper, 0, 0, 0, 0, imagesx($temp_wallpaper), imagesy($temp_wallpaper), 60); | ||
| 432 | + imagecopymerge($background, $thumbnail, 127, 26, 0, 0, imagesx($thumbnail), imagesy($thumbnail), 100); | ||
| 433 | + return $background; | ||
| 434 | + } | ||
| 435 | + | ||
| 436 | + /** | ||
| 629 | * 贴纸和签名 | 437 | * 贴纸和签名 |
| 630 | * @param $end_wallpaper | 438 | * @param $end_wallpaper |
| 631 | * @param $thumbnail | 439 | * @param $thumbnail |
| ... | @@ -689,8 +497,7 @@ class DevFFmpeg extends Command | ... | @@ -689,8 +497,7 @@ class DevFFmpeg extends Command |
| 689 | $white = imagecolorallocate($wp, 0xDC, 0x14, 0x3C); //fixme 字体颜色 | 497 | $white = imagecolorallocate($wp, 0xDC, 0x14, 0x3C); //fixme 字体颜色 |
| 690 | imagettftext($wp, 20, 0, 75, 240, $white, $font, $signature); | 498 | imagettftext($wp, 20, 0, 75, 240, $white, $font, $signature); |
| 691 | 499 | ||
| 692 | -// $dst = "./output_new_end_wallpaper.png"; | 500 | + $dst = Storage::disk('public')->path($this->getTempPath('.png')); |
| 693 | - $dst = Storage::disk('public')->path('ffmpeg') . "/output_new_end_wallpaper.png"; | ||
| 694 | imagepng($wp, $dst); | 501 | imagepng($wp, $dst); |
| 695 | if(is_resource($end_wallpaper)) imagedestroy($end_wallpaper); | 502 | if(is_resource($end_wallpaper)) imagedestroy($end_wallpaper); |
| 696 | if(is_resource($_img)) imagedestroy($_img); | 503 | if(is_resource($_img)) imagedestroy($_img); |
| ... | @@ -698,146 +505,33 @@ class DevFFmpeg extends Command | ... | @@ -698,146 +505,33 @@ class DevFFmpeg extends Command |
| 698 | return $dst; | 505 | return $dst; |
| 699 | } | 506 | } |
| 700 | 507 | ||
| 701 | - /** | ||
| 702 | - * 获取图像文件类型 | ||
| 703 | - * @param $img_name | ||
| 704 | - * @return string | ||
| 705 | - */ | ||
| 706 | - public function getImageType($img_name) | ||
| 707 | - { | ||
| 708 | - if (preg_match("/\.(jpg|jpeg|gif|png)$/i", $img_name, $matches)){ | ||
| 709 | - $type = strtolower($matches[1]); | ||
| 710 | - }else{ | ||
| 711 | - $type = "string"; | ||
| 712 | - } | ||
| 713 | - return $type; | ||
| 714 | - } | ||
| 715 | - | ||
| 716 | - /** | ||
| 717 | - * 多图融合 | ||
| 718 | - * @param $end_wallpaper | ||
| 719 | - * @param $thumbnail | ||
| 720 | - * @return resource | ||
| 721 | - */ | ||
| 722 | - public function imagesMerge($end_wallpaper, $thumbnail) { | ||
| 723 | - $end_wallpaper = imagecreatefrompng($end_wallpaper); | ||
| 724 | - $background = imagecreatefrompng(Storage::disk('public')->path('ffmpeg/background.png')); | ||
| 725 | - imagesavealpha($background,true); | ||
| 726 | - $temp_wallpaper = imagecreatetruecolor(350, 204); | ||
| 727 | - $color = imagecolorallocate($temp_wallpaper, 0xd0, 0xcd, 0xcc); | ||
| 728 | -// $color = imagecolorallocate($temp_wallpaper, 0xDC, 0x14, 0x3C); | ||
| 729 | - imagefill($temp_wallpaper, 0, 0, $color); | ||
| 730 | - imageColorTransparent($temp_wallpaper, $color); | ||
| 731 | - imagecopyresampled($temp_wallpaper, $end_wallpaper, 0, 0, 0, 0, imagesx($temp_wallpaper), imagesy($temp_wallpaper), imagesx($end_wallpaper), imagesy($end_wallpaper)); | ||
| 732 | - imagecopymerge($background, $temp_wallpaper, 0, 0, 0, 0, imagesx($temp_wallpaper), imagesy($temp_wallpaper), 60); | ||
| 733 | - imagecopymerge($background, $thumbnail, 127, 26, 0, 0, imagesx($thumbnail), imagesy($thumbnail), 100); | ||
| 734 | - return $background; | ||
| 735 | - } | ||
| 736 | - | ||
| 737 | - | ||
| 738 | - /** | ||
| 739 | - * logo 大小转换 | ||
| 740 | - * @param $logo | ||
| 741 | - * @return bool | ||
| 742 | - */ | ||
| 743 | - public function translateLogo($logo) | ||
| 744 | - { | ||
| 745 | - $image = Storage::disk('public')->path('ffmpeg/output_150x150.jpg'); | ||
| 746 | - $cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($logo) . | ||
| 747 | - ' -vf scale=150:150 ' . escapeshellarg($image); | ||
| 748 | - if ($this->execmd($cmd)) { | ||
| 749 | - return $image; | ||
| 750 | - } else { | ||
| 751 | - return false; | ||
| 752 | - } | ||
| 753 | - } | ||
| 754 | - | ||
| 755 | - /** | ||
| 756 | - * 合成视频秀 | ||
| 757 | - * @param $file | ||
| 758 | - * @param $audio | ||
| 759 | - * @param null $animate | ||
| 760 | - * @param null $ass_file | ||
| 761 | - * @param null $watermark | ||
| 762 | - * @param null $watermark_x | ||
| 763 | - * @param null $watermark_y | ||
| 764 | - * @return bool|string | ||
| 765 | - */ | ||
| 766 | - public function makeMofunshow($file, $audio, $animate = null, $ass_file = null, $watermark = null, $watermark_x = null, $watermark_y = null) | ||
| 767 | - { | ||
| 768 | - $video = $this->getTempPath(); | ||
| 769 | - if ($animate) { | ||
| 770 | - $ass_filter = $ass_file ? 'ass=' . escapeshellarg($ass_file) . '[ass];[ass]' : ''; | ||
| 771 | - if ($watermark && is_file($watermark) && getimagesize($watermark)) { | ||
| 772 | - $watermark_x = $watermark_x ? $watermark_x : 0; | ||
| 773 | - $watermark_y = $watermark_y ? $watermark_y : 0; | ||
| 774 | - $am_inp = ' -i ' . escapeshellarg($watermark); | ||
| 775 | - $am_filter = "[3:v]overlay={$watermark_x}:{$watermark_y}[water];[water]"; | ||
| 776 | - } else { | ||
| 777 | - $am_inp = ''; | ||
| 778 | - $am_filter = ''; | ||
| 779 | - } | ||
| 780 | - $cmd = $this->ffmpeg . ' -y' . | ||
| 781 | - ' -i ' . escapeshellarg($file) . | ||
| 782 | - ' -i ' . escapeshellarg($animate) . | ||
| 783 | - ' -i ' . escapeshellarg($audio) . | ||
| 784 | - $am_inp . | ||
| 785 | - ' -filter_complex "[0:0]' . $ass_filter . $am_filter . 'setsar=sar=1/1[t];[t] [2:a] [1:0] [1:1] concat=n=2:v=1:a=1 [v] [a]"' . | ||
| 786 | - ' -map [v] -map [a]'; | ||
| 787 | - } else { | ||
| 788 | - $ass_filter = $ass_file ? ' -vf ass=' . escapeshellarg($ass_file) : ''; | ||
| 789 | - $cmd = $this->ffmpeg . ' -y' . | ||
| 790 | - ' -i ' . escapeshellarg($file) . | ||
| 791 | - ' -i ' . escapeshellarg($audio) . | ||
| 792 | - ' -map 0:0' . | ||
| 793 | - ' -map 1:0' . | ||
| 794 | - $ass_filter; | ||
| 795 | - } | ||
| 796 | - $cmd .= | ||
| 797 | - ' -c:v libx264 -s 800x450 -bt 256k -r 25' . | ||
| 798 | -// todo 没有libfdk_aac库 | ||
| 799 | -// ' -c:a libfdk_aac -ar 44100 -ac 2 -qmin 30 -qmax 60 -profile:v baseline -preset fast ' . | ||
| 800 | - ' -ar 44100 -ac 2 -qmin 30 -qmax 60 -profile:v baseline -preset fast ' . | ||
| 801 | - escapeshellarg($video); | ||
| 802 | - if ($this->execmd($cmd)) { | ||
| 803 | - return $video; | ||
| 804 | - } else { | ||
| 805 | - return false; | ||
| 806 | - } | ||
| 807 | - } | ||
| 808 | 508 | ||
| 809 | - public function getTextContentString() | 509 | + public function getTextContentString($font) |
| 810 | { | 510 | { |
| 811 | - $components = VideoTemp::query()->find(6)->components()->get(); | 511 | + $components = $this->immerse->temp()->first()->components()->get(); |
| 812 | - | ||
| 813 | - $font = Storage::disk('public')->path('ffmpeg/arialuni.ttf'); | ||
| 814 | 512 | ||
| 815 | $drawtext = ''; | 513 | $drawtext = ''; |
| 816 | 514 | ||
| 817 | foreach ($components as $component) { | 515 | foreach ($components as $component) { |
| 818 | switch ($component->name){ | 516 | switch ($component->name){ |
| 819 | case 'one_poem': | 517 | case 'one_poem': |
| 820 | - $content = '题破山寺后禅院' . "\t" . ' -- 常建' . PHP_EOL . | 518 | + $content = $this->immerse->poem->content; |
| 821 | - '清晨入古寺,初日照高林。' . PHP_EOL . | 519 | + $text_file = Storage::disk('public')->path($this->getTempPath('.txt')); |
| 822 | - '曲径通幽处,禅房花木深。' . PHP_EOL . | 520 | + file_put_contents($text_file, $content); |
| 823 | - '山光悦鸟性,潭影空人心。' . PHP_EOL . | ||
| 824 | - '万籁此都寂,但余钟磬音。' . PHP_EOL ; | ||
| 825 | 521 | ||
| 826 | $text_color = $component->text_color ?? 'white'; | 522 | $text_color = $component->text_color ?? 'white'; |
| 827 | $text_bg_color = $component->text_bg_color ?? '0xd0cdcc'; | 523 | $text_bg_color = $component->text_bg_color ?? '0xd0cdcc'; |
| 828 | $opacity = $component->opacity ? $component->opacity / 100 : '0.5'; | 524 | $opacity = $component->opacity ? $component->opacity / 100 : '0.5'; |
| 829 | 525 | ||
| 830 | - $text_file = $this->getTempPath('txt'); | ||
| 831 | - file_put_contents($text_file, $content); | ||
| 832 | - | ||
| 833 | $drawtext .= 'drawtext="'. | 526 | $drawtext .= 'drawtext="'. |
| 834 | 'fontfile=' . escapeshellarg($font) . ':' . | 527 | 'fontfile=' . escapeshellarg($font) . ':' . |
| 835 | 'textfile=' . escapeshellarg($text_file) . ':' . | 528 | 'textfile=' . escapeshellarg($text_file) . ':' . |
| 836 | - 'fontsize=' . $component->font_size . ':' . | 529 | + 'fontsize=' . $this->calcFontSize($component->font_size) . ':' . |
| 837 | 'fontcolor=' . $text_color . '@1.0:' . | 530 | 'fontcolor=' . $text_color . '@1.0:' . |
| 838 | 'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' . | 531 | 'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' . |
| 839 | 'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' . | 532 | 'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' . |
| 840 | - 'box=1:boxcolor=' . $text_bg_color . '@' . $opacity . '", '; | 533 | + 'box=1:boxborderw='. $this->calcBorderSize($component->font_size) . ':' . |
| 534 | + 'boxcolor=' . $text_bg_color . '@' . $opacity . '", '; | ||
| 841 | 535 | ||
| 842 | break; | 536 | break; |
| 843 | case 'every_poem': | 537 | case 'every_poem': |
| ... | @@ -851,11 +545,12 @@ class DevFFmpeg extends Command | ... | @@ -851,11 +545,12 @@ class DevFFmpeg extends Command |
| 851 | $drawtext .= 'drawtext="'. | 545 | $drawtext .= 'drawtext="'. |
| 852 | 'fontfile=' . escapeshellarg($font) . ':' . | 546 | 'fontfile=' . escapeshellarg($font) . ':' . |
| 853 | 'text=' . escapeshellarg($content) . ':' . | 547 | 'text=' . escapeshellarg($content) . ':' . |
| 854 | - 'fontsize=' . $component->font_size . ':' . | 548 | + 'fontsize=' . $this->calcFontSize($component->font_size) . ':' . |
| 855 | 'fontcolor=' . $text_color . '@1.0:' . | 549 | 'fontcolor=' . $text_color . '@1.0:' . |
| 856 | 'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' . | 550 | 'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' . |
| 857 | 'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' . | 551 | 'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' . |
| 858 | - 'box=1:boxcolor=' . $text_bg_color . '@' . $opacity . '", '; | 552 | + 'box=1:boxborderw='. $this->calcBorderSize($component->font_size) . ':' . |
| 553 | + 'boxcolor=' . $text_bg_color . '@' . $opacity . '", '; | ||
| 859 | 554 | ||
| 860 | break; | 555 | break; |
| 861 | case 'date': | 556 | case 'date': |
| ... | @@ -867,14 +562,15 @@ class DevFFmpeg extends Command | ... | @@ -867,14 +562,15 @@ class DevFFmpeg extends Command |
| 867 | $drawtext .= 'drawtext="'. | 562 | $drawtext .= 'drawtext="'. |
| 868 | 'fontfile=' . escapeshellarg($font) . ':' . | 563 | 'fontfile=' . escapeshellarg($font) . ':' . |
| 869 | 'text=' . escapeshellarg($content) . ':' . | 564 | 'text=' . escapeshellarg($content) . ':' . |
| 870 | - 'fontsize=' . $component->font_size . ':' . | 565 | + 'fontsize=' . $this->calcFontSize($component->font_size) . ':' . |
| 871 | 'fontcolor=' . $text_color . '@1.0:' . | 566 | 'fontcolor=' . $text_color . '@1.0:' . |
| 872 | 'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' . | 567 | 'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' . |
| 873 | 'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' . | 568 | 'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' . |
| 874 | - 'box=1:boxcolor=' . $text_bg_color . '@' . $opacity . '", '; | 569 | + 'box=1:boxborderw='. $this->calcBorderSize($component->font_size) . ':' . |
| 570 | + 'boxcolor=' . $text_bg_color . '@' . $opacity . '", '; | ||
| 875 | break; | 571 | break; |
| 876 | case 'feel': | 572 | case 'feel': |
| 877 | - $content = '测试有感文本'; | 573 | + $content = $this->immerse->content ?: '这里是发表临境有感'; |
| 878 | $text_color = $component->text_color ?? 'white'; | 574 | $text_color = $component->text_color ?? 'white'; |
| 879 | $text_bg_color = $component->text_bg_color ?? '0xd0cdcc'; | 575 | $text_bg_color = $component->text_bg_color ?? '0xd0cdcc'; |
| 880 | $opacity = $component->opacity ? $component->opacity / 100 : '0.5'; | 576 | $opacity = $component->opacity ? $component->opacity / 100 : '0.5'; |
| ... | @@ -882,11 +578,12 @@ class DevFFmpeg extends Command | ... | @@ -882,11 +578,12 @@ class DevFFmpeg extends Command |
| 882 | $drawtext .= 'drawtext="'. | 578 | $drawtext .= 'drawtext="'. |
| 883 | 'fontfile=' . escapeshellarg($font) . ':' . | 579 | 'fontfile=' . escapeshellarg($font) . ':' . |
| 884 | 'text=' . escapeshellarg($content) . ':' . | 580 | 'text=' . escapeshellarg($content) . ':' . |
| 885 | - 'fontsize=' . $component->font_size . ':' . | 581 | + 'fontsize=' . $this->calcFontSize($component->font_size) . ':' . |
| 886 | 'fontcolor=' . $text_color . '@1.0:' . | 582 | 'fontcolor=' . $text_color . '@1.0:' . |
| 887 | 'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' . | 583 | 'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' . |
| 888 | 'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' . | 584 | 'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' . |
| 889 | - 'box=1:boxcolor=' . $text_bg_color . '@' . $opacity . '", '; | 585 | + 'box=1:boxborderw='. $this->calcBorderSize($component->font_size) . ':' . |
| 586 | + 'boxcolor=' . $text_bg_color . '@' . $opacity . '", '; | ||
| 890 | break; | 587 | break; |
| 891 | } | 588 | } |
| 892 | } | 589 | } |
| ... | @@ -894,16 +591,24 @@ class DevFFmpeg extends Command | ... | @@ -894,16 +591,24 @@ class DevFFmpeg extends Command |
| 894 | return rtrim($drawtext,', '); | 591 | return rtrim($drawtext,', '); |
| 895 | } | 592 | } |
| 896 | 593 | ||
| 897 | - public function calcFontSize($width, $content) | 594 | + /** |
| 595 | + * @param $width | ||
| 596 | + * @param $content | ||
| 597 | + * @return float | ||
| 598 | + */ | ||
| 599 | + public function calcFontSize($width) | ||
| 898 | { | 600 | { |
| 899 | - $max_len = 1; | 601 | + return ceil($this->output_width / 360 * $width); |
| 900 | - foreach (explode("\n",$content) as $item){ | 602 | + } |
| 901 | - if (mb_strlen($item) > $max_len){ | ||
| 902 | - $max_len = mb_strlen($item); | ||
| 903 | - } | ||
| 904 | - } | ||
| 905 | 603 | ||
| 906 | - return ceil(800 * $width / 100 / $max_len); | 604 | + /** |
| 605 | + * @param $width | ||
| 606 | + * @param $content | ||
| 607 | + * @return float | ||
| 608 | + */ | ||
| 609 | + public function calcBorderSize($width) | ||
| 610 | + { | ||
| 611 | + return ceil($width / 12 * 10); | ||
| 907 | } | 612 | } |
| 908 | } | 613 | } |
| 909 | 614 | ... | ... |
| ... | @@ -3,6 +3,7 @@ | ... | @@ -3,6 +3,7 @@ |
| 3 | namespace App\Http\Controllers\V1; | 3 | namespace App\Http\Controllers\V1; |
| 4 | 4 | ||
| 5 | use App\Http\Controllers\Controller; | 5 | use App\Http\Controllers\Controller; |
| 6 | +use App\Jobs\UserMakeImmerse; | ||
| 6 | use App\Models\Collect; | 7 | use App\Models\Collect; |
| 7 | use App\Models\Immerse; | 8 | use App\Models\Immerse; |
| 8 | use App\Jobs\UserMakeVideo as MakeVideo; | 9 | use App\Jobs\UserMakeVideo as MakeVideo; |
| ... | @@ -52,34 +53,56 @@ class ImmerseController extends Controller | ... | @@ -52,34 +53,56 @@ class ImmerseController extends Controller |
| 52 | $user_id = $request->user()->id; | 53 | $user_id = $request->user()->id; |
| 53 | 54 | ||
| 54 | $immerse = Immerse::query()->find($request->item_id); | 55 | $immerse = Immerse::query()->find($request->item_id); |
| 55 | - if ($validated['type'] == 1){ | 56 | +// if ($validated['type'] == 1){ |
| 56 | - $create = Immerse::query()->create([ | 57 | +// $create = Immerse::query()->create([ |
| 57 | - 'user_id' => $user_id, | 58 | +// 'user_id' => $user_id, |
| 58 | - 'title' => '', | 59 | +// 'title' => '', |
| 59 | - 'content' => $validated['content'], | 60 | +// 'content' => $validated['content'], |
| 60 | - 'url' => '', | 61 | +// 'url' => '', |
| 61 | - 'type' => $validated['type'], | 62 | +// 'type' => $validated['type'], |
| 62 | - 'duration' => 0, | 63 | +// 'duration' => 0, |
| 63 | - 'size' => 0, | 64 | +// 'size' => 0, |
| 64 | - 'poem_id' => $immerse->poem_id, | 65 | +// 'poem_id' => $immerse->poem_id, |
| 65 | - 'temp_id' => $immerse->temp_id, | 66 | +// 'temp_id' => $immerse->temp_id, |
| 66 | - 'thumbnail' => $immerse->thumbnail, | 67 | +// 'thumbnail' => $immerse->thumbnail, |
| 67 | - 'bgm' => $validated['item_url'], | 68 | +// 'bgm' => $validated['item_url'], |
| 68 | - 'state' => 0 | 69 | +// 'state' => 0 |
| 69 | - ]); | 70 | +// ]); |
| 71 | +// | ||
| 72 | +// // 添加至队列 | ||
| 73 | +// MakeImages::dispatch($create); | ||
| 74 | +// }else{ | ||
| 75 | +// // 视频 | ||
| 76 | +// $create = Immerse::query()->create([ | ||
| 77 | +// 'user_id' => $user_id, | ||
| 78 | +// 'title' => '', | ||
| 79 | +// 'content' => $validated['content'], | ||
| 80 | +// 'url' => '', | ||
| 81 | +// 'type' => $validated['type'], | ||
| 82 | +// 'duration' => 0, | ||
| 83 | +// 'size' => 0, | ||
| 84 | +// 'poem_id' => $immerse->poem_id, | ||
| 85 | +// 'temp_id' => $immerse->temp_id, | ||
| 86 | +// 'thumbnail' => '', | ||
| 87 | +// 'bgm' => $immerse->bgm, | ||
| 88 | +// 'state' => 0 | ||
| 89 | +// ]); | ||
| 90 | +// | ||
| 91 | +// // 添加至队列 | ||
| 92 | +// MakeVideo::dispatch($create, $validated['item_url']); | ||
| 93 | +// } | ||
| 70 | 94 | ||
| 71 | - // 添加至队列 | ||
| 72 | - MakeImages::dispatch($create); | ||
| 73 | - }else{ | ||
| 74 | - // 视频 | ||
| 75 | $create = Immerse::query()->create([ | 95 | $create = Immerse::query()->create([ |
| 76 | 'user_id' => $user_id, | 96 | 'user_id' => $user_id, |
| 77 | 'title' => '', | 97 | 'title' => '', |
| 78 | 'content' => $validated['content'], | 98 | 'content' => $validated['content'], |
| 79 | 'url' => '', | 99 | 'url' => '', |
| 80 | 'type' => $validated['type'], | 100 | 'type' => $validated['type'], |
| 101 | + 'upload_file' => $validated['item_url'], | ||
| 81 | 'duration' => 0, | 102 | 'duration' => 0, |
| 82 | 'size' => 0, | 103 | 'size' => 0, |
| 104 | + 'origin_video_url' => $immerse->origin_video_url, | ||
| 105 | + 'origin_image_url' => $immerse->origin_image_url, | ||
| 83 | 'poem_id' => $immerse->poem_id, | 106 | 'poem_id' => $immerse->poem_id, |
| 84 | 'temp_id' => $immerse->temp_id, | 107 | 'temp_id' => $immerse->temp_id, |
| 85 | 'thumbnail' => '', | 108 | 'thumbnail' => '', |
| ... | @@ -88,8 +111,7 @@ class ImmerseController extends Controller | ... | @@ -88,8 +111,7 @@ class ImmerseController extends Controller |
| 88 | ]); | 111 | ]); |
| 89 | 112 | ||
| 90 | // 添加至队列 | 113 | // 添加至队列 |
| 91 | - MakeVideo::dispatch($create, $validated['item_url']); | 114 | + UserMakeImmerse::dispatch($create); |
| 92 | - } | ||
| 93 | 115 | ||
| 94 | return Response::success($create); | 116 | return Response::success($create); |
| 95 | } | 117 | } | ... | ... |
| ... | @@ -82,8 +82,11 @@ class MakeImages implements ShouldQueue | ... | @@ -82,8 +82,11 @@ class MakeImages implements ShouldQueue |
| 82 | 'content' => $this->adminMakeVideo->feel, | 82 | 'content' => $this->adminMakeVideo->feel, |
| 83 | 'url' => str_replace(Storage::disk('public')->path(''),'',$output), | 83 | 'url' => str_replace(Storage::disk('public')->path(''),'',$output), |
| 84 | 'type' => $this->adminMakeVideo->type == 1 ? 2 : 1, | 84 | 'type' => $this->adminMakeVideo->type == 1 ? 2 : 1, |
| 85 | + 'upload_file' => '', | ||
| 85 | 'duration' => 0, | 86 | 'duration' => 0, |
| 86 | 'size' => filesize($image), | 87 | 'size' => filesize($image), |
| 88 | + 'origin_video_url' => '', | ||
| 89 | + 'origin_image_url' => $this->adminMakeVideo->image_url, | ||
| 87 | 'poem_id' => $this->adminMakeVideo->poem_id, | 90 | 'poem_id' => $this->adminMakeVideo->poem_id, |
| 88 | 'temp_id' => $this->adminMakeVideo->temp_id, | 91 | 'temp_id' => $this->adminMakeVideo->temp_id, |
| 89 | 'thumbnail' => str_replace(Storage::disk('public')->path(''),'',$image), | 92 | 'thumbnail' => str_replace(Storage::disk('public')->path(''),'',$image), |
| ... | @@ -154,8 +157,11 @@ class MakeImages implements ShouldQueue | ... | @@ -154,8 +157,11 @@ class MakeImages implements ShouldQueue |
| 154 | 'content' => $this->adminMakeVideo->feel, | 157 | 'content' => $this->adminMakeVideo->feel, |
| 155 | 'url' => str_replace(Storage::disk('public')->path(''),'',$output), | 158 | 'url' => str_replace(Storage::disk('public')->path(''),'',$output), |
| 156 | 'type' => $this->adminMakeVideo->type == 1 ? 2 : 1, | 159 | 'type' => $this->adminMakeVideo->type == 1 ? 2 : 1, |
| 160 | + 'upload_file' => '', | ||
| 157 | 'duration' => $video_info['format']['duration'], | 161 | 'duration' => $video_info['format']['duration'], |
| 158 | 'size' => $video_info['format']['size'], | 162 | 'size' => $video_info['format']['size'], |
| 163 | + 'origin_video_url' => '', | ||
| 164 | + 'origin_image_url' => $this->adminMakeVideo->image_url, | ||
| 159 | 'poem_id' => $this->adminMakeVideo->poem_id, | 165 | 'poem_id' => $this->adminMakeVideo->poem_id, |
| 160 | 'temp_id' => $this->adminMakeVideo->temp_id, | 166 | 'temp_id' => $this->adminMakeVideo->temp_id, |
| 161 | 'thumbnail' => str_replace(Storage::disk('public')->path(''),'',$this->adminMakeVideo->images_url), | 167 | 'thumbnail' => str_replace(Storage::disk('public')->path(''),'',$this->adminMakeVideo->images_url), | ... | ... |
| ... | @@ -116,13 +116,28 @@ class MakeVideo implements ShouldQueue | ... | @@ -116,13 +116,28 @@ class MakeVideo implements ShouldQueue |
| 116 | return; | 116 | return; |
| 117 | } | 117 | } |
| 118 | 118 | ||
| 119 | + if ($this->adminMakeVideo->thumbnail == 2){ | ||
| 120 | + // 截取中间帧作为视频封面 | ||
| 121 | + $frame = ceil($media_info['streams'][0]['nb_frames'] / 2); | ||
| 122 | + $thumbnail = $this->getTempPath('.jpg',false); | ||
| 123 | + $cmd = $this->ffmpeg . ' -y ' . | ||
| 124 | + ' -i ' . escapeshellarg($file) . | ||
| 125 | + ' -filter_complex "[0:v]select=\'eq(n,' . $frame . ')\'[img]" ' . | ||
| 126 | + ' -map [img]'. | ||
| 127 | + ' -frames:v 1 -s 720x1280 -preset superfast '. | ||
| 128 | + escapeshellarg($thumbnail); | ||
| 129 | + if (!$this->execmd($cmd)) return ; | ||
| 130 | + }else{ | ||
| 131 | + $thumbnail = $adminMakeVideo->thumbnail_url; | ||
| 132 | + } | ||
| 133 | + | ||
| 119 | $end_wallpaper = Storage::disk('public')->path('ffmpeg') . "/end_wallpaper.png"; | 134 | $end_wallpaper = Storage::disk('public')->path('ffmpeg') . "/end_wallpaper.png"; |
| 120 | - $thumbnail = Storage::disk('public')->path('ffmpeg') . "/thumbnail.png"; | 135 | + $avatar = Storage::disk('public')->path('ffmpeg') . "/thumbnail.png"; |
| 121 | $font = Storage::disk('public')->path('ffmpeg') . "/arialuni.ttf"; | 136 | $font = Storage::disk('public')->path('ffmpeg') . "/arialuni.ttf"; |
| 122 | $signature = "一言 · 官方出品"; | 137 | $signature = "一言 · 官方出品"; |
| 123 | 138 | ||
| 124 | // 生成贴纸和签名 | 139 | // 生成贴纸和签名 |
| 125 | - $end_wallpaper = $this->wallpaperWithSignature($end_wallpaper, $thumbnail, $signature, $font); | 140 | + $end_wallpaper = $this->wallpaperWithSignature($end_wallpaper, $avatar, $signature, $font); |
| 126 | 141 | ||
| 127 | // 截取最后一帧 | 142 | // 截取最后一帧 |
| 128 | $last_frame_video = $this->getTempPath(); | 143 | $last_frame_video = $this->getTempPath(); |
| ... | @@ -176,11 +191,14 @@ class MakeVideo implements ShouldQueue | ... | @@ -176,11 +191,14 @@ class MakeVideo implements ShouldQueue |
| 176 | 'content' => $this->adminMakeVideo->feel, | 191 | 'content' => $this->adminMakeVideo->feel, |
| 177 | 'url' => str_replace(Storage::disk('public')->path(''),'',$video), | 192 | 'url' => str_replace(Storage::disk('public')->path(''),'',$video), |
| 178 | 'type' => $this->adminMakeVideo->type == 1 ? 2 : 1, | 193 | 'type' => $this->adminMakeVideo->type == 1 ? 2 : 1, |
| 194 | + 'upload_file' => '', | ||
| 179 | 'duration' => $video_info['format']['duration'], | 195 | 'duration' => $video_info['format']['duration'], |
| 180 | 'size' => $video_info['format']['size'], | 196 | 'size' => $video_info['format']['size'], |
| 197 | + 'origin_video_url' => $this->adminMakeVideo->video_url, | ||
| 198 | + 'origin_image_url' => '', | ||
| 181 | 'poem_id' => $this->adminMakeVideo->poem_id, | 199 | 'poem_id' => $this->adminMakeVideo->poem_id, |
| 182 | 'temp_id' => $this->adminMakeVideo->temp_id, | 200 | 'temp_id' => $this->adminMakeVideo->temp_id, |
| 183 | - 'thumbnail' => '', | 201 | + 'thumbnail' => $thumbnail, |
| 184 | 'state' => 1, | 202 | 'state' => 1, |
| 185 | 'bgm' => $this->adminMakeVideo->bgm_url ?? '', | 203 | 'bgm' => $this->adminMakeVideo->bgm_url ?? '', |
| 186 | ]); | 204 | ]); | ... | ... |
app/Jobs/UserMakeImmerse.php
0 → 100644
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace App\Jobs; | ||
| 4 | + | ||
| 5 | +use App\Models\Immerse; | ||
| 6 | +use App\Models\UserProfile; | ||
| 7 | +use App\Models\VideoTemp; | ||
| 8 | +use Illuminate\Bus\Queueable; | ||
| 9 | +use Illuminate\Contracts\Queue\ShouldBeUnique; | ||
| 10 | +use Illuminate\Contracts\Queue\ShouldQueue; | ||
| 11 | +use Illuminate\Foundation\Bus\Dispatchable; | ||
| 12 | +use Illuminate\Queue\InteractsWithQueue; | ||
| 13 | +use Illuminate\Queue\SerializesModels; | ||
| 14 | +use Illuminate\Support\Facades\Storage; | ||
| 15 | +use Illuminate\Support\Str; | ||
| 16 | + | ||
| 17 | +class UserMakeImmerse implements ShouldQueue | ||
| 18 | +{ | ||
| 19 | + use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; | ||
| 20 | + | ||
| 21 | + public $debug = true; | ||
| 22 | + | ||
| 23 | + public $immerse; | ||
| 24 | + | ||
| 25 | + protected $ffmpeg; | ||
| 26 | + | ||
| 27 | + protected $ffprobe; | ||
| 28 | + | ||
| 29 | + protected $output_width; | ||
| 30 | + | ||
| 31 | + protected $output_height; | ||
| 32 | + | ||
| 33 | + /** | ||
| 34 | + * Create a new job instance. | ||
| 35 | + * @param Immerse $immerse | ||
| 36 | + * @return void | ||
| 37 | + */ | ||
| 38 | + public function __construct(Immerse $immerse) | ||
| 39 | + { | ||
| 40 | + $this->immerse = $immerse; | ||
| 41 | + $this->ffmpeg = env('FFMPEG_CMD'); | ||
| 42 | + $this->ffprobe = env('FFPROBE_CMD'); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + /** | ||
| 46 | + * Execute the job. | ||
| 47 | + * | ||
| 48 | + * @return void | ||
| 49 | + */ | ||
| 50 | + public function handle() | ||
| 51 | + { | ||
| 52 | + // 分情况 1.用户视频,2.用户录音 | ||
| 53 | + // 注意事项:1.考虑用户是否会员;非会员分辨率 720x1280,会员分辨率1440x2560 | ||
| 54 | + // 2.同时字体大小也很方便确定。 | ||
| 55 | + | ||
| 56 | + $profile = UserProfile::query()->find($this->immerse->user_id); | ||
| 57 | + if ($profile->is_vip == 1){ | ||
| 58 | + $this->output_width = 720; | ||
| 59 | + $this->output_height = 1280; | ||
| 60 | + }else{ | ||
| 61 | + $this->output_width = 540; | ||
| 62 | + $this->output_height = 960; | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + if ($this->immerse->type == 1){ | ||
| 66 | + // 1. 分析用户上传音频 | ||
| 67 | + $upload_file = Storage::disk('public')->path($this->immerse->upload_file); | ||
| 68 | + $mediainfo = $this->mediainfo($upload_file); | ||
| 69 | + // 记录媒体信息时长 | ||
| 70 | + $duration = $mediainfo['format']['duration'] ?: 0; | ||
| 71 | + | ||
| 72 | + $font = Storage::disk('public')->path('uploads/201/71/arialuni.ttf'); | ||
| 73 | + | ||
| 74 | + // 音频还取之前的封面。 | ||
| 75 | + $thumbnail = Storage::disk('public')->path($this->immerse->thumbnail); | ||
| 76 | + $video = Storage::disk('public')->path($this->getTempPath('.mp4',false)); | ||
| 77 | + | ||
| 78 | + if ($this->immerse->origin_video_url == '' || $this->immerse->origin_video_url == null) { | ||
| 79 | + // 原官方临境是图片 | ||
| 80 | + $origin_image_path = Storage::disk('public')->path($this->immerse->origin_image_url); | ||
| 81 | + | ||
| 82 | + if ($this->immerse->bgm == '' || $this->immerse->bgm == null) { | ||
| 83 | + $audio = $upload_file; | ||
| 84 | + }else{ | ||
| 85 | + $audio = Storage::disk('public')->path($this->getTempPath('.mp3')); | ||
| 86 | + $bgm = Storage::disk('public')->path($this->immerse->bgm); | ||
| 87 | + $cmd = $this->ffmpeg . ' -y ' . | ||
| 88 | + ' -i ' . escapeshellarg($upload_file) . | ||
| 89 | + ' -i ' . escapeshellarg($bgm) . | ||
| 90 | + ' -filter_complex "amix=inputs=2:duration=first" ' . | ||
| 91 | + ' -ar 48000 -ab 64k ' . escapeshellarg($audio); | ||
| 92 | + | ||
| 93 | + if (!$this->execmd($cmd)) return ; | ||
| 94 | + } | ||
| 95 | + | ||
| 96 | + $drawtext = $this->getTextContentString($font); | ||
| 97 | + $watermark = Storage::disk('public')->path('uploads/201/71/LOGO_eng.png'); | ||
| 98 | + | ||
| 99 | + //直接将音频替换 | ||
| 100 | + $cmd = $this->ffmpeg . ' -y ' . | ||
| 101 | + ' -loop 1 -i ' . escapeshellarg($origin_image_path) . | ||
| 102 | + ' -i ' . escapeshellarg($audio) . | ||
| 103 | + ' -i ' . escapeshellarg($watermark) . | ||
| 104 | + ' -filter_complex "[0:v]scale=' . $this->output_width . ':' . $this->output_height . ',setdar=dar=9/16,' . $drawtext . | ||
| 105 | + ' [text];[text][2:v]overlay=20:20[v]"' . | ||
| 106 | + ' -map [v] -map 1:0 ' . | ||
| 107 | + ' -c:v libx264 -bt 256k -r 25 -t ' . $duration . | ||
| 108 | + ' -ar 48000 -ac 2 -qmin 30 -qmax 60 -profile:v high -pix_fmt yuv420p -preset fast '. | ||
| 109 | + escapeshellarg($video); | ||
| 110 | + | ||
| 111 | + if (!$this->execmd($cmd)) return ; | ||
| 112 | + } | ||
| 113 | + | ||
| 114 | + if ($this->immerse->origin_image_url == '' || $this->immerse->origin_image_url == null) { | ||
| 115 | + // 原官方临境是视频 | ||
| 116 | + $origin_video_path = Storage::disk('public')->path($this->immerse->origin_video_url); | ||
| 117 | + $origin_mediainfo = $this->mediainfo($origin_video_path); | ||
| 118 | + | ||
| 119 | + //用户录音超长,截取用户录音 | ||
| 120 | + if ($mediainfo['format']['duration'] > $origin_mediainfo['format']['duration']) { | ||
| 121 | + if ($this->immerse->bgm == '' || $this->immerse->bgm == null) { | ||
| 122 | + $audio = Storage::disk('public')->path($this->getTempPath('.mp3')); | ||
| 123 | + $cmd = $this->ffmpeg . ' -y ' . | ||
| 124 | + ' -i ' . escapeshellarg($upload_file) . | ||
| 125 | + ' -ss 0 -t ' . escapeshellarg($origin_mediainfo['format']['duration']) . | ||
| 126 | + ' -ar 48000 -ab 64k '.escapeshellarg($audio); | ||
| 127 | + if (!$this->execmd($cmd)) return 0; | ||
| 128 | + }else{ | ||
| 129 | + $bgm = Storage::disk('public')->path($this->immerse->bgm); | ||
| 130 | + $audio = Storage::disk('public')->path($this->getTempPath('.mp3')); | ||
| 131 | + $cmd = $this->ffmpeg . ' -y ' . | ||
| 132 | + ' -i ' . escapeshellarg($upload_file) . | ||
| 133 | + ' -i ' . escapeshellarg($bgm) . | ||
| 134 | + ' -filter_complex "amix=inputs=2:duration=first" ' . | ||
| 135 | + ' -ss 0 -t ' . escapeshellarg($origin_mediainfo['format']['duration']) . | ||
| 136 | + ' -ar 48000 -ab 64k '.escapeshellarg($audio); | ||
| 137 | + if (!$this->execmd($cmd)) return 0; | ||
| 138 | + } | ||
| 139 | + }else{ | ||
| 140 | + if ($this->immerse->bgm == '' || $this->immerse->bgm == null) { | ||
| 141 | + $audio = $upload_file; | ||
| 142 | + }else{ | ||
| 143 | + $audio = Storage::disk('public')->path($this->getTempPath('.mp3')); | ||
| 144 | + $bgm = Storage::disk('public')->path($this->immerse->bgm); | ||
| 145 | + $cmd = $this->ffmpeg . ' -y ' . | ||
| 146 | + ' -i ' . escapeshellarg($upload_file) . | ||
| 147 | + ' -i ' . escapeshellarg($bgm) . | ||
| 148 | + ' -filter_complex "amix=inputs=2:duration=first[a]" -map [a]' . | ||
| 149 | + ' -ar 48000 -ab 64k' . escapeshellarg($audio); | ||
| 150 | + | ||
| 151 | + if (!$this->execmd($cmd)) return 0; | ||
| 152 | + } | ||
| 153 | + } | ||
| 154 | + | ||
| 155 | + $drawtext = $this->getTextContentString($font); | ||
| 156 | + $watermark = Storage::disk('public')->path('uploads/201/71/LOGO_eng.png'); | ||
| 157 | + | ||
| 158 | + //直接将音频替换 | ||
| 159 | + $cmd = $this->ffmpeg . ' -y ' . | ||
| 160 | + ' -i ' . escapeshellarg($origin_video_path) . | ||
| 161 | + ' -i ' . escapeshellarg($audio) . | ||
| 162 | + ' -i ' . escapeshellarg($watermark) . | ||
| 163 | + ' -filter_complex "[0:v]scale=' . $this->output_width . ':' . $this->output_height . ',setdar=dar=9/16,' . $drawtext . | ||
| 164 | + ' [text];[text][2:v]overlay=20:20[v]"' . | ||
| 165 | + ' -map [v] -map 1:0 ' . | ||
| 166 | + ' -c:v libx264 -bt 256k -r 25' . | ||
| 167 | + ' -ar 48000 -ac 2 -qmin 30 -qmax 60 -profile:v high -pix_fmt yuv420p -preset fast '. | ||
| 168 | + escapeshellarg($video); | ||
| 169 | + | ||
| 170 | + if (!$this->execmd($cmd)) return ; | ||
| 171 | + } | ||
| 172 | + }else{ | ||
| 173 | + // 1. 分析用户上传视频 | ||
| 174 | + $upload_file = Storage::disk('public')->path($this->immerse->upload_file); | ||
| 175 | + $mediainfo = $this->mediainfo($upload_file); | ||
| 176 | + | ||
| 177 | + // 记录媒体信息时长 | ||
| 178 | + $duration = $mediainfo['format']['duration'] ?: 0; | ||
| 179 | + | ||
| 180 | + // | ||
| 181 | + $end_wallpaper = Storage::disk('public')->path('uploads/201/71/end_wallpaper.png'); | ||
| 182 | + $avatar = Storage::disk('public')->path('uploads/201/71/thumbnail.png'); | ||
| 183 | + $font = Storage::disk('public')->path('uploads/201/71/arialuni.ttf'); | ||
| 184 | +// | ||
| 185 | +// $user = User::query()->find($this->immerse->user_id); | ||
| 186 | +// $signature = $user->nickname ?? $user->email ?? '2@qq.com'; | ||
| 187 | +// | ||
| 188 | +// // 生成贴纸和签名 | ||
| 189 | +// $end_wallpaper = $this->wallpaperWithSignature($end_wallpaper, $avatar, $signature, $font); | ||
| 190 | +// | ||
| 191 | +// // 截取最后一帧做动画 | ||
| 192 | +// $last_frame_video = $this->getTempPath(); | ||
| 193 | +// $this->width = $width = $mediainfo['streams'][0]['width']; | ||
| 194 | +// $height = $mediainfo['streams'][0]['height']; | ||
| 195 | +// $size = $width . 'x' . $height; | ||
| 196 | +// $time_length = 0.7; | ||
| 197 | +// $r = 24; | ||
| 198 | +// $frame_n = $mediainfo['streams'][0]['nb_frames'] - 2; | ||
| 199 | +// $cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($upload_file) . | ||
| 200 | +// " -f lavfi -i nullsrc=s={$size}:d={$time_length}:r={$r} -f lavfi -i aevalsrc=0:duration={$time_length}" . | ||
| 201 | +// " -filter_complex \"[0:v]select='eq(n,{$frame_n})',setpts=PTS-STARTPTS[lastframe];[1:v][lastframe]overlay[v]\"" . | ||
| 202 | +// ' -map [v] -map 2:a ' . escapeshellarg($last_frame_video); | ||
| 203 | +// if (!$this->execmd($cmd)) return 0; | ||
| 204 | + // | ||
| 205 | + | ||
| 206 | + $drawtext = $this->getTextContentString($font); | ||
| 207 | + $watermark = Storage::disk('public')->path('uploads/201/71/LOGO_eng.png'); | ||
| 208 | + | ||
| 209 | + // 截取中间帧作为视频封面 | ||
| 210 | + $frame = ceil($mediainfo['streams'][0]['nb_frames'] / 2); | ||
| 211 | + $thumbnail = Storage::disk('public')->path($this->getTempPath('.jpg',false)); | ||
| 212 | + $cmd = $this->ffmpeg . ' -y ' . | ||
| 213 | + ' -i ' . escapeshellarg($upload_file) . | ||
| 214 | + ' -i ' . escapeshellarg($watermark) . | ||
| 215 | + ' -filter_complex "[0:v]select=\'eq(n,' . $frame . ')\'[img]" ' . | ||
| 216 | + ' -map [img]'. | ||
| 217 | + ' -frames:v 1 -s ' . $this->output_width . 'x' . $this->output_height .' -preset superfast '. | ||
| 218 | + escapeshellarg($thumbnail); | ||
| 219 | + if (!$this->execmd($cmd)) return ; | ||
| 220 | + | ||
| 221 | + $video = Storage::disk('public')->path($this->getTempPath('.mp4',false)); | ||
| 222 | + // 没有背景音 | ||
| 223 | + if ($this->immerse->bgm == '' || $this->immerse->bgm == null) { | ||
| 224 | + $cmd = $this->ffmpeg . ' -y ' . | ||
| 225 | + ' -i ' . escapeshellarg($upload_file) . | ||
| 226 | + ' -i ' . escapeshellarg($watermark) . | ||
| 227 | + ' -filter_complex "[0:v]scale=' . $this->output_width . ':' . $this->output_height . ',setdar=dar=9/16,' . $drawtext . | ||
| 228 | + ' [text];[text][1:v]overlay=20:20"' . | ||
| 229 | + ' -c:v libx264 -bt 256k -r 25' . | ||
| 230 | + ' -c:a copy -qmin 30 -qmax 60 -profile:v high -pix_fmt yuv420p -preset fast ' . | ||
| 231 | + escapeshellarg($video); | ||
| 232 | + }else{ | ||
| 233 | + $bgm = Storage::disk('public')->path($this->immerse->bgm); | ||
| 234 | + $cmd = $this->ffmpeg . ' -y ' . | ||
| 235 | + ' -i ' . escapeshellarg($upload_file) . | ||
| 236 | + ' -i ' . escapeshellarg($watermark) . | ||
| 237 | + ' -i ' . escapeshellarg($bgm) . | ||
| 238 | + ' -filter_complex "[0:v]scale=' . $this->output_width . ':' . $this->output_height . ',setdar=dar=9/16,' . $drawtext . | ||
| 239 | + ' [text];[text][1:v]overlay=20:20[v];' . | ||
| 240 | + ' amix=inputs=2:duration=first[a] " -map [v] -map [a]' . | ||
| 241 | + ' -c:v libx264 -bt 256k -r 25' . | ||
| 242 | + ' -ar 44100 -ac 2 -qmin 30 -qmax 60 -profile:v high -pix_fmt yuv420p -preset fast ' . | ||
| 243 | + escapeshellarg($video); | ||
| 244 | + } | ||
| 245 | + | ||
| 246 | + if (!$this->execmd($cmd)) return ; | ||
| 247 | + } | ||
| 248 | + | ||
| 249 | + // 全部合成以后创建 临境 | ||
| 250 | + $video_info = $this->mediainfo($video); | ||
| 251 | + | ||
| 252 | + $this->immerse->url = Str::of($video)->replace(Storage::disk('public')->path(''), ''); | ||
| 253 | + $this->immerse->thumbnail = $thumbnail; | ||
| 254 | + $this->immerse->state = 1; | ||
| 255 | + $this->immerse->duration = $video_info['format']['duration'] ?? 0; | ||
| 256 | + $this->immerse->size = $video_info['format']['size']; | ||
| 257 | + $this->immerse->save(); | ||
| 258 | + | ||
| 259 | + return ; | ||
| 260 | + } | ||
| 261 | + | ||
| 262 | + public function mediainfo($file) | ||
| 263 | + { | ||
| 264 | + $cmd = $this->ffprobe . ' -v quiet -print_format json -show_format -show_streams ' . escapeshellarg($file); | ||
| 265 | + $output = $this->execmd($cmd); | ||
| 266 | + $data = json_decode($output, true); | ||
| 267 | + if (json_last_error() === JSON_ERROR_UTF8) { | ||
| 268 | + $output = mb_convert_encoding($output, "UTF-8"); | ||
| 269 | + $data = json_decode($output, true); | ||
| 270 | + } | ||
| 271 | + return $data; | ||
| 272 | + } | ||
| 273 | + | ||
| 274 | + public function execmd($cmd, $update_progress = false) { | ||
| 275 | + echo $cmd . "\n". "\n". "\n"; | ||
| 276 | + $descriptorspec = array( | ||
| 277 | + 1 => array("pipe", "w"), // 标准输出,子进程向此管道中写入数据 | ||
| 278 | + ); | ||
| 279 | + $process = proc_open("{$cmd} 2>&1", $descriptorspec, $pipes); | ||
| 280 | + if (is_resource($process)) { | ||
| 281 | + $error0 = ''; | ||
| 282 | + $error1 = ''; | ||
| 283 | + $stdout = ''; | ||
| 284 | + while (!feof($pipes[1])) { | ||
| 285 | + $line = fgets($pipes[1], 150); | ||
| 286 | + $stdout .= $line; | ||
| 287 | + if ($line) { | ||
| 288 | + //记录错误 | ||
| 289 | + $error0 = $error1; | ||
| 290 | + $error1 = $line; | ||
| 291 | + if ($update_progress && | ||
| 292 | + false !== strpos($line, 'size=') && | ||
| 293 | + false !== strpos($line, 'time=') && | ||
| 294 | + false !== strpos($line, 'bitrate=')) | ||
| 295 | + { | ||
| 296 | + //记录进度 size= 3142kB time=00:00:47.22 bitrate= 545.1kbits/s | ||
| 297 | + $line = explode(' ', $line); | ||
| 298 | + $time = null; | ||
| 299 | + foreach ($line as $item) { | ||
| 300 | + $item = explode('=', $item); | ||
| 301 | + if (isset($item[0]) && isset($item[1]) && $item[0] == 'time') { | ||
| 302 | + $time = $item[1]; | ||
| 303 | + break; | ||
| 304 | + } | ||
| 305 | + } | ||
| 306 | + } | ||
| 307 | + } | ||
| 308 | + } | ||
| 309 | + // 切记:在调用 proc_close 之前关闭所有的管道以避免死锁。 | ||
| 310 | + fclose($pipes[1]); | ||
| 311 | + $exitedcode = proc_close($process); | ||
| 312 | + if ($exitedcode === 0) { | ||
| 313 | + return $stdout; | ||
| 314 | + } else { | ||
| 315 | + $error = trim($error0,"\n") . ' '. trim($error1,"\n"); | ||
| 316 | + // LogUtil::write(array("cmd:{$cmd}", "errno:{$exitedcode}", "stdout:{$stdout}"), __CLASS__); | ||
| 317 | + // ErrorUtil::triggerErrorMsg($error, $exitedcode); | ||
| 318 | + } | ||
| 319 | + } else { | ||
| 320 | + // return ErrorUtil::triggerErrorMsg('proc_open error'); | ||
| 321 | + } | ||
| 322 | + } | ||
| 323 | + | ||
| 324 | + /** | ||
| 325 | + * 获取输出临时文件名 | ||
| 326 | + * @param string $ext | ||
| 327 | + * @param bool $is_temp | ||
| 328 | + * @return string | ||
| 329 | + */ | ||
| 330 | + public function getTempPath($ext = '.mp4',$is_temp = true) | ||
| 331 | + { | ||
| 332 | + $filename = "/output_" . time() . rand(0, 10000); | ||
| 333 | + | ||
| 334 | + $prefix = $is_temp ? 'temp/' : 'video/'; | ||
| 335 | + $hash_hex = md5($filename); | ||
| 336 | + // 16进制表示的字符串一共32字节,表示16个二进制字节。 | ||
| 337 | + // 前16个字符用来第一级求摸,后16个用做第二级 | ||
| 338 | + $hash_hex_l1 = substr($hash_hex, 0, 8); | ||
| 339 | + $hash_hex_l2 = substr($hash_hex, 8, 8); | ||
| 340 | + $dir_l1 = hexdec($hash_hex_l1) % 256; | ||
| 341 | + $dir_l2 = hexdec($hash_hex_l2) % 512; | ||
| 342 | + $dir = $prefix . $dir_l1 . '/' . $dir_l2; | ||
| 343 | + | ||
| 344 | + if( !Storage::disk('public')->exists($dir)) Storage::disk('public')->makeDirectory($dir); | ||
| 345 | + | ||
| 346 | + return $dir . $filename . $ext; | ||
| 347 | + } | ||
| 348 | + | ||
| 349 | + /** | ||
| 350 | + * 获取图像文件类型 | ||
| 351 | + * @param $img_name | ||
| 352 | + * @return string | ||
| 353 | + */ | ||
| 354 | + public function getImageType($img_name) | ||
| 355 | + { | ||
| 356 | + if (preg_match("/\.(jpg|jpeg|gif|png)$/i", $img_name, $matches)){ | ||
| 357 | + $type = strtolower($matches[1]); | ||
| 358 | + }else{ | ||
| 359 | + $type = "string"; | ||
| 360 | + } | ||
| 361 | + return $type; | ||
| 362 | + } | ||
| 363 | + | ||
| 364 | + /** | ||
| 365 | + * 获取圆形头像 | ||
| 366 | + * @param $img | ||
| 367 | + * @param int $dst_w | ||
| 368 | + * @param int $dst_h | ||
| 369 | + * @return resource | ||
| 370 | + */ | ||
| 371 | + public function getCircleAvatar($img, $dst_w = 96, $dst_h = 96) | ||
| 372 | + { | ||
| 373 | + $w = 130; | ||
| 374 | + $h = 130; | ||
| 375 | + $src = imagecreatetruecolor($dst_w, $dst_h); | ||
| 376 | + imagecopyresized($src, $img, 0, 0, 0, 0, $dst_w, $dst_h, $w, $h); | ||
| 377 | + | ||
| 378 | + $newpic = imagecreatetruecolor($dst_w, $dst_h); | ||
| 379 | + imagealphablending($newpic, false); | ||
| 380 | + imagecopyresampled($newpic, $img, 0, 0, 0, 0, $dst_w, $dst_h, $w, $h); | ||
| 381 | + $mask = imagecreatetruecolor($dst_w, $dst_h); | ||
| 382 | + $transparent = imagecolorallocate($mask, 255, 0, 0); | ||
| 383 | + imagecolortransparent($mask,$transparent); | ||
| 384 | + imagefilledellipse($mask, $dst_w / 2, $dst_h / 2, $dst_w, $dst_h, $transparent); | ||
| 385 | + $red = imagecolorallocate($mask, 0, 0, 0); | ||
| 386 | + imagecopymerge($newpic, $mask, 0, 0, 0, 0, $dst_w, $dst_h, 100); | ||
| 387 | + imagecolortransparent($newpic,$red); | ||
| 388 | + imagesavealpha($newpic,true); | ||
| 389 | + imagefill($newpic, 0, 0, $red); | ||
| 390 | + imagedestroy($mask); | ||
| 391 | + return $newpic; | ||
| 392 | + } | ||
| 393 | + | ||
| 394 | + /** | ||
| 395 | + * 多图融合 | ||
| 396 | + * @param $end_wallpaper | ||
| 397 | + * @param $thumbnail | ||
| 398 | + * @return resource | ||
| 399 | + */ | ||
| 400 | + public function imagesMerge($end_wallpaper, $thumbnail) { | ||
| 401 | + $end_wallpaper = imagecreatefrompng($end_wallpaper); | ||
| 402 | + $background = imagecreatefrompng(Storage::disk('public')->path('ffmpeg/background.png')); | ||
| 403 | + imagesavealpha($background,true); | ||
| 404 | + $temp_wallpaper = imagecreatetruecolor(350, 204); | ||
| 405 | +// $color = imagecolorallocate($temp_wallpaper, 0xd0, 0xcd, 0xcc); | ||
| 406 | + $color = imagecolorallocate($temp_wallpaper, 0xDC, 0x14, 0x3C); | ||
| 407 | + imagefill($temp_wallpaper, 0, 0, $color); | ||
| 408 | + imageColorTransparent($temp_wallpaper, $color); | ||
| 409 | + imagecopyresampled($temp_wallpaper, $end_wallpaper, 0, 0, 0, 0, imagesx($temp_wallpaper), imagesy($temp_wallpaper), imagesx($end_wallpaper), imagesy($end_wallpaper)); | ||
| 410 | + imagecopymerge($background, $temp_wallpaper, 0, 0, 0, 0, imagesx($temp_wallpaper), imagesy($temp_wallpaper), 60); | ||
| 411 | + imagecopymerge($background, $thumbnail, 127, 26, 0, 0, imagesx($thumbnail), imagesy($thumbnail), 100); | ||
| 412 | + return $background; | ||
| 413 | + } | ||
| 414 | + | ||
| 415 | + /** | ||
| 416 | + * 贴纸和签名 | ||
| 417 | + * @param $end_wallpaper | ||
| 418 | + * @param $thumbnail | ||
| 419 | + * @param $signature | ||
| 420 | + * @param $font | ||
| 421 | + * @return string | ||
| 422 | + */ | ||
| 423 | + public function wallpaperWithSignature($end_wallpaper, $thumbnail, $signature, $font) { | ||
| 424 | + $_imagetype = $this->getImageType($thumbnail); | ||
| 425 | + $_img = null; | ||
| 426 | + switch ($_imagetype) { | ||
| 427 | + case 'gif': | ||
| 428 | + if (function_exists('imagecreatefromgif')) { | ||
| 429 | + $_img = imagecreatefromgif($thumbnail); | ||
| 430 | + } | ||
| 431 | + break; | ||
| 432 | + case 'jpg': | ||
| 433 | + case 'jpeg': | ||
| 434 | + $_img = imagecreatefromjpeg($thumbnail); | ||
| 435 | + break; | ||
| 436 | + case 'png': | ||
| 437 | + $_img = imagecreatefrompng($thumbnail); | ||
| 438 | + break; | ||
| 439 | + default: | ||
| 440 | + $_img = imagecreatefromstring($thumbnail); | ||
| 441 | + break; | ||
| 442 | + } | ||
| 443 | + $width = 130; | ||
| 444 | + $height = 130; | ||
| 445 | + $_width = 130; | ||
| 446 | + $_height = 130; | ||
| 447 | + if(is_resource($_img)){ | ||
| 448 | + $_width = imagesx($_img); | ||
| 449 | + $_height = imagesy($_img); | ||
| 450 | + } | ||
| 451 | + | ||
| 452 | + $bite = $_width / $_height; | ||
| 453 | + | ||
| 454 | + if($_width > $_height){ | ||
| 455 | + if($_width > $width){ | ||
| 456 | + $height = round($width / $bite); | ||
| 457 | + } | ||
| 458 | + }else{ | ||
| 459 | + if($_height > $height){ | ||
| 460 | + $width = round($height * $bite); | ||
| 461 | + } | ||
| 462 | + } | ||
| 463 | + | ||
| 464 | + $tmpimg = imagecreatetruecolor($width,$height); | ||
| 465 | + if(function_exists('imagecopyresampled')) { | ||
| 466 | + imagecopyresampled($tmpimg, $_img, 0, 0, 0, 0, $width, $height, $_width, $_height); | ||
| 467 | + } else { | ||
| 468 | + imagecopyresized($tmpimg, $_img, 0, 0, 0, 0, $width, $height, $_width, $_height); | ||
| 469 | + } | ||
| 470 | + if(is_resource($_img)) imagedestroy($_img); | ||
| 471 | + $_img = $this->getCircleAvatar($tmpimg); | ||
| 472 | + if(is_resource($tmpimg)) imagedestroy($tmpimg); | ||
| 473 | + | ||
| 474 | + $wp = $this->imagesMerge($end_wallpaper, $_img); | ||
| 475 | +// $white = imagecolorallocate($wp, 0xd0, 0xcd, 0xcc); | ||
| 476 | + $white = imagecolorallocate($wp, 0xDC, 0x14, 0x3C); //fixme 字体颜色 | ||
| 477 | + imagettftext($wp, 20, 0, 75, 240, $white, $font, $signature); | ||
| 478 | + | ||
| 479 | + $dst = Storage::disk('public')->path($this->getTempPath('.png')); | ||
| 480 | + imagepng($wp, $dst); | ||
| 481 | + if(is_resource($end_wallpaper)) imagedestroy($end_wallpaper); | ||
| 482 | + if(is_resource($_img)) imagedestroy($_img); | ||
| 483 | + | ||
| 484 | + return $dst; | ||
| 485 | + } | ||
| 486 | + | ||
| 487 | + | ||
| 488 | + public function getTextContentString($font) | ||
| 489 | + { | ||
| 490 | + $components = $this->immerse->temp()->first()->components()->get(); | ||
| 491 | + | ||
| 492 | + $drawtext = ''; | ||
| 493 | + | ||
| 494 | + foreach ($components as $component) { | ||
| 495 | + switch ($component->name){ | ||
| 496 | + case 'one_poem': | ||
| 497 | + $content = $this->immerse->poem->content; | ||
| 498 | + $text_file = Storage::disk('public')->path($this->getTempPath('.txt')); | ||
| 499 | + file_put_contents($text_file, $content); | ||
| 500 | + | ||
| 501 | + $text_color = $component->text_color ?? 'white'; | ||
| 502 | + $text_bg_color = $component->text_bg_color ?? '0xd0cdcc'; | ||
| 503 | + $opacity = $component->opacity ? $component->opacity / 100 : '0.5'; | ||
| 504 | + | ||
| 505 | + $drawtext .= 'drawtext="'. | ||
| 506 | + 'fontfile=' . escapeshellarg($font) . ':' . | ||
| 507 | + 'textfile=' . escapeshellarg($text_file) . ':' . | ||
| 508 | + 'fontsize=' . $this->calcFontSize($component->font_size) . ':' . | ||
| 509 | + 'fontcolor=' . $text_color . '@1.0:' . | ||
| 510 | + 'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' . | ||
| 511 | + 'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' . | ||
| 512 | + 'box=1:boxborderw='. $this->calcBorderSize($component->font_size) . ':' . | ||
| 513 | + 'boxcolor=' . $text_bg_color . '@' . $opacity . '", '; | ||
| 514 | + | ||
| 515 | + break; | ||
| 516 | + case 'every_poem': | ||
| 517 | + break; | ||
| 518 | + case 'weather': | ||
| 519 | + $content = '多云'; | ||
| 520 | + $text_color = $component->text_color ?? 'white'; | ||
| 521 | + $text_bg_color = $component->text_bg_color ?? '0xd0cdcc'; | ||
| 522 | + $opacity = $component->opacity ? $component->opacity / 100 : '0.5'; | ||
| 523 | + | ||
| 524 | + $drawtext .= 'drawtext="'. | ||
| 525 | + 'fontfile=' . escapeshellarg($font) . ':' . | ||
| 526 | + 'text=' . escapeshellarg($content) . ':' . | ||
| 527 | + 'fontsize=' . $this->calcFontSize($component->font_size) . ':' . | ||
| 528 | + 'fontcolor=' . $text_color . '@1.0:' . | ||
| 529 | + 'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' . | ||
| 530 | + 'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' . | ||
| 531 | + 'box=1:boxborderw='. $this->calcBorderSize($component->font_size) . ':' . | ||
| 532 | + 'boxcolor=' . $text_bg_color . '@' . $opacity . '", '; | ||
| 533 | + | ||
| 534 | + break; | ||
| 535 | + case 'date': | ||
| 536 | + $content = Carbon::now()->format('Y年m月d日H时'); | ||
| 537 | + $text_color = $component->text_color ?? 'white'; | ||
| 538 | + $text_bg_color = $component->text_bg_color ?? '0xd0cdcc'; | ||
| 539 | + $opacity = $component->opacity ? $component->opacity / 100 : '0.5'; | ||
| 540 | + | ||
| 541 | + $drawtext .= 'drawtext="'. | ||
| 542 | + 'fontfile=' . escapeshellarg($font) . ':' . | ||
| 543 | + 'text=' . escapeshellarg($content) . ':' . | ||
| 544 | + 'fontsize=' . $this->calcFontSize($component->font_size) . ':' . | ||
| 545 | + 'fontcolor=' . $text_color . '@1.0:' . | ||
| 546 | + 'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' . | ||
| 547 | + 'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' . | ||
| 548 | + 'box=1:boxborderw='. $this->calcBorderSize($component->font_size) . ':' . | ||
| 549 | + 'boxcolor=' . $text_bg_color . '@' . $opacity . '", '; | ||
| 550 | + break; | ||
| 551 | + case 'feel': | ||
| 552 | + $content = $this->immerse->content ?: '这里是发表临境有感'; | ||
| 553 | + $text_color = $component->text_color ?? 'white'; | ||
| 554 | + $text_bg_color = $component->text_bg_color ?? '0xd0cdcc'; | ||
| 555 | + $opacity = $component->opacity ? $component->opacity / 100 : '0.5'; | ||
| 556 | + | ||
| 557 | + $drawtext .= 'drawtext="'. | ||
| 558 | + 'fontfile=' . escapeshellarg($font) . ':' . | ||
| 559 | + 'text=' . escapeshellarg($content) . ':' . | ||
| 560 | + 'fontsize=' . $this->calcFontSize($component->font_size) . ':' . | ||
| 561 | + 'fontcolor=' . $text_color . '@1.0:' . | ||
| 562 | + 'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' . | ||
| 563 | + 'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' . | ||
| 564 | + 'box=1:boxborderw='. $this->calcBorderSize($component->font_size) . ':' . | ||
| 565 | + 'boxcolor=' . $text_bg_color . '@' . $opacity . '", '; | ||
| 566 | + break; | ||
| 567 | + } | ||
| 568 | + } | ||
| 569 | + | ||
| 570 | + return rtrim($drawtext,', '); | ||
| 571 | + } | ||
| 572 | + | ||
| 573 | + /** | ||
| 574 | + * @param $width | ||
| 575 | + * @param $content | ||
| 576 | + * @return float | ||
| 577 | + */ | ||
| 578 | + public function calcFontSize($width) | ||
| 579 | + { | ||
| 580 | + return ceil($this->output_width / 360 * $width); | ||
| 581 | + } | ||
| 582 | + | ||
| 583 | + /** | ||
| 584 | + * @param $width | ||
| 585 | + * @param $content | ||
| 586 | + * @return float | ||
| 587 | + */ | ||
| 588 | + public function calcBorderSize($width) | ||
| 589 | + { | ||
| 590 | + return ceil($width / 12 * 10); | ||
| 591 | + } | ||
| 592 | +} |
app/Payment/ApplePayment.php
0 → 100644
| 1 | +<?php | ||
| 2 | +/** | ||
| 3 | + * Created by PhpStorm. | ||
| 4 | + * User: lishuai | ||
| 5 | + * Date: 2022/2/15 | ||
| 6 | + * Time: 4:23 PM | ||
| 7 | + */ | ||
| 8 | + | ||
| 9 | +namespace App\Payment; | ||
| 10 | + | ||
| 11 | +use App\Models\MembershipGood; | ||
| 12 | +use App\Models\Order; | ||
| 13 | +use App\Models\User; | ||
| 14 | +use App\Models\UserProfile; | ||
| 15 | +use Carbon\Carbon; | ||
| 16 | +use GuzzleHttp\Client; | ||
| 17 | +use Illuminate\Support\Facades\Log; | ||
| 18 | +use Illuminate\Support\Facades\Redis; | ||
| 19 | + | ||
| 20 | +class ApplePayment implements PaymentInterface | ||
| 21 | +{ | ||
| 22 | + | ||
| 23 | + const IS_SANDBOX = true; | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + | ||
| 27 | + public function __construct() | ||
| 28 | + { | ||
| 29 | + | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + | ||
| 33 | + | ||
| 34 | + public function prepare(Order $order) | ||
| 35 | + { | ||
| 36 | + // 查询订单对应的产品id | ||
| 37 | + } | ||
| 38 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
app/Payment/GooglePayment.php
0 → 100644
| 1 | +<?php | ||
| 2 | +/** | ||
| 3 | + * Created by PhpStorm. | ||
| 4 | + * User: lishuai | ||
| 5 | + * Date: 2022/2/15 | ||
| 6 | + * Time: 4:23 PM | ||
| 7 | + */ | ||
| 8 | + | ||
| 9 | +namespace App\Payment; | ||
| 10 | + | ||
| 11 | +use App\Models\MembershipGood; | ||
| 12 | +use App\Models\Order; | ||
| 13 | +use App\Models\User; | ||
| 14 | +use App\Models\UserProfile; | ||
| 15 | +use Carbon\Carbon; | ||
| 16 | +use GuzzleHttp\Client; | ||
| 17 | +use Illuminate\Support\Facades\Log; | ||
| 18 | +use Illuminate\Support\Facades\Redis; | ||
| 19 | + | ||
| 20 | +class GooglePayment implements PaymentInterface | ||
| 21 | +{ | ||
| 22 | + | ||
| 23 | + const IS_SANDBOX = true; | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + | ||
| 27 | + public function __construct() | ||
| 28 | + { | ||
| 29 | + | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + | ||
| 33 | + | ||
| 34 | + public function prepare(Order $order) | ||
| 35 | + { | ||
| 36 | + // 查询订单对应的产品id | ||
| 37 | + } | ||
| 38 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| ... | @@ -19,8 +19,12 @@ class PaymentFactory | ... | @@ -19,8 +19,12 @@ class PaymentFactory |
| 19 | return new WechatPayment(); | 19 | return new WechatPayment(); |
| 20 | case 'paypal': | 20 | case 'paypal': |
| 21 | return new PaypalPayment(); | 21 | return new PaypalPayment(); |
| 22 | + case 'google': | ||
| 23 | + return new GooglePayment(); | ||
| 24 | + case 'apple': | ||
| 25 | + return new ApplePayment(); | ||
| 22 | default: | 26 | default: |
| 23 | - throw new \Exception('未知的支付方式'); | 27 | + return new \Exception('未知的支付方式'); |
| 24 | } | 28 | } |
| 25 | } | 29 | } |
| 26 | } | 30 | } |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +use Illuminate\Database\Migrations\Migration; | ||
| 4 | +use Illuminate\Database\Schema\Blueprint; | ||
| 5 | +use Illuminate\Support\Facades\Schema; | ||
| 6 | + | ||
| 7 | +class AlterImmerse2Table extends Migration | ||
| 8 | +{ | ||
| 9 | + /** | ||
| 10 | + * Run the migrations. | ||
| 11 | + * | ||
| 12 | + * @return void | ||
| 13 | + */ | ||
| 14 | + public function up() | ||
| 15 | + { | ||
| 16 | + Schema::table('immerse', function (Blueprint $table) { | ||
| 17 | + $table->string('upload_file')->after('type')->comment('上传资源路径'); | ||
| 18 | + $table->string('origin_video_url')->after('size')->comment('原始视频路径'); | ||
| 19 | + $table->string('origin_image_url')->after('origin_video_url')->comment('原始图片路径'); | ||
| 20 | + $table->string('tags')->after('comment')->default('')->comment('标签'); | ||
| 21 | + }); | ||
| 22 | + } | ||
| 23 | + | ||
| 24 | + /** | ||
| 25 | + * Reverse the migrations. | ||
| 26 | + * | ||
| 27 | + * @return void | ||
| 28 | + */ | ||
| 29 | + public function down() | ||
| 30 | + { | ||
| 31 | + Schema::dropColumns('immerse', ['upload_file', 'origin_video_url', 'origin_image_url', 'tags']); | ||
| 32 | + } | ||
| 33 | +} |
| ... | @@ -23,7 +23,7 @@ Route::prefix('v1')->namespace('App\Http\Controllers\V1')->group(function (Route | ... | @@ -23,7 +23,7 @@ Route::prefix('v1')->namespace('App\Http\Controllers\V1')->group(function (Route |
| 23 | /** 社会化用户登录*/ | 23 | /** 社会化用户登录*/ |
| 24 | $api->any('auth/{service}/callback', 'AuthController@apiHandleProviderCallback'); | 24 | $api->any('auth/{service}/callback', 'AuthController@apiHandleProviderCallback'); |
| 25 | 25 | ||
| 26 | - /** */ | 26 | + /** 增加观看次数 */ |
| 27 | $api->get('/addview/{id}', 'ImmerseController@addview'); | 27 | $api->get('/addview/{id}', 'ImmerseController@addview'); |
| 28 | }); | 28 | }); |
| 29 | 29 | ... | ... |
-
Please register or login to post a comment