李帅

1.打印paypal token

...@@ -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 ]);
......
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 +}
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
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
......