李帅

1.打印paypal token

......@@ -3,8 +3,11 @@
namespace App\Console\Commands;
use App\Jobs\MakeImages;
use App\Jobs\UserMakeImmerse;
use App\Models\AdminMakeVideo;
use App\Models\Immerse;
use App\Models\User;
use App\Models\UserProfile;
use App\Models\VideoTemp;
use Carbon\Carbon;
use Illuminate\Console\Command;
......@@ -36,13 +39,23 @@ class DevFFmpeg extends Command
public function __construct()
{
parent::__construct();
$this->immerse = Immerse::query()->orderByDesc('id')->first();
$this->ffmpeg = env('FFMPEG_CMD');
$this->ffprobe = env('FFPROBE_CMD');
}
protected $ffmpeg = '/Users/lishuai/Documents/ffmpeg/ffmpeg';
public $debug = true;
public $immerse;
protected $ffmpeg;
protected $ffprobe = '/Users/lishuai/Documents/ffmpeg/ffprobe';
protected $ffprobe;
protected $ffplay = '/Users/lishuai/Documents/ffmpeg/ffplay';
protected $output_width;
protected $output_height;
/**
* Execute the console command.
......@@ -51,530 +64,234 @@ class DevFFmpeg extends Command
*/
public function handle()
{
MakeImages::dispatch(AdminMakeVideo::query()->find(24));
dd(1);
$image = Storage::disk('public')->path('images/73f18d443820334c51c36f443c9683b3.png');
$watermark = Storage::disk('public')->path('ffmpeg/LOGO_eng.png');
$end_wallpaper = Storage::disk('public')->path('ffmpeg/output_new_end_wallpaper.png');
// 制作最后一帧
$size = '1242x2208';
$time_length = 0.7;
$r = 24;
$last_frame_video = $this->getTempPath('.mp4');
$font = Storage::disk('public')->path('ffmpeg/arialuni.ttf');
$cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($image) .
' -i ' . escapeshellarg($watermark) .
" -f lavfi -i nullsrc=s={$size}:d={$time_length}:r={$r} -f lavfi -i aevalsrc=0:duration={$time_length}" .
' -filter_complex "'.
' [0:0] ' . $this->getTextContentString() .
'[text];[text][1:0]overlay=20:20[water];' .
' [water]select=\'eq(n,0)\',setpts=PTS-STARTPTS[lastframe];[2:v][lastframe]overlay[v] " ' .
' -map [v] -map 3:a ' . escapeshellarg($last_frame_video);
$output = $this->execmd($cmd);
// 利用最后一帧制作动画
$signature_x = 0;
$signature_y = -20;
$animate = $this->makeAnimate($last_frame_video, $end_wallpaper, '', $signature_x, $signature_y, $font);
dd($animate);
// 这样实现不了
$cmd = $this->ffmpeg . ' -y -i ' .
escapeshellarg($image) .
" -f lavfi -i nullsrc=s=1242x2208:d=0.7:r=24 ".
' -i ' . escapeshellarg($watermark) .
' -i ' . escapeshellarg($end_wallpaper) .
' -filter_complex "' .
' [0:v] ' . $this->getTextContentString() .
' [text];[text][2:0]overlay=20:20[water];' .
' [water]select=\'eq(n,1)\',setpts=PTS-STARTPTS[lastframe];[1:v][lastframe]overlay[last];' .
' [last]boxblur=8[blur];' .
' [blur][3:v]overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2 [lay];' .
' [lay]geq=lum=\'if(lte(T,0.6), 255*T*(1/0.6),255)\',format=gray[grad];' .
' [lay][grad]alphamerge[alpha];' .
// ' [last][alpha]overlay[concat2];'.
' [water][alpha] concat=n=2:v=1[v]" ' .
escapeshellarg($this->getTempPath('.mp4'));
$output = $this->execmd($cmd);
dd($output);
dd(Str::contains("/Users/lishuai/Documents/source/OnePoem-Server/storage/app/public/ffmpeg/output_16479198841364.mp4",'/storage/app/public/'));
$path = '/Users/lishuai/Desktop/test/';
$file = $path . 'qinghuaci.mp4';
// $file = $path . 'lingdang.mov';
$is_bgm = false;
$bgm = $path . 'bgm.aac';
$string = $this->getTextContentString();
// 1.getmediainfo 记录时长,音频视频取最长。
$cmd = $this->ffprobe . ' -v quiet -print_format json -show_format -show_streams ' . escapeshellarg($file);
$output = $this->execmd($cmd);
$media_info = json_decode($output, true);
if (json_last_error() === JSON_ERROR_UTF8) {
$output = mb_convert_encoding($output, "UTF-8");
$media_info = json_decode($output, true);
}
/** 记录媒体信息时长*/
$media_file_time_length = isset($media_info['format']['duration']) ? $media_info['format']['duration'] : 0;
$media_file_size = $media_info['format']['size'];
if ($media_info['streams'][0]['codec_type'] !== 'video') {
return 0;
}
// 2. 判断是否有视频原音,没有原音用背景音,没有背景音则混入anullsrc
// 分情况 1.用户视频,2.用户录音
// 注意事项:1.考虑用户是否会员;非会员分辨率 720x1280,会员分辨率1440x2560
// 2.同时字体大小也很方便确定。
// $profile = UserProfile::query()->find($this->immerse->user_id);
// if ($profile->is_vip == 1){
// $this->output_width = 720;
// $this->output_height = 1280;
// }else{
// $this->output_width = 540;
// $this->output_height = 960;
// }
$this->output_width = 540;
$this->output_height = 960;
if ($this->immerse->type == 1){
// 1. 分析用户上传音频
$upload_file = Storage::disk('public')->path($this->immerse->upload_file);
$mediainfo = $this->mediainfo($upload_file);
// 记录媒体信息时长
$duration = $mediainfo['format']['duration'] ?: 0;
$font = Storage::disk('public')->path('uploads/201/71/arialuni.ttf');
// 音频还取之前的封面。
$thumbnail = Storage::disk('public')->path($this->immerse->thumbnail);
if ($this->immerse->origin_video_url == '' || $this->immerse->origin_video_url == null) {
// 原官方临境是图片
$origin_image_path = Storage::disk('public')->path($this->immerse->origin_image_url);
if ($this->immerse->bgm == '' || $this->immerse->bgm == null) {
$audio = $upload_file;
}else{
$audio = Storage::disk('public')->path($this->getTempPath('.mp3'));
$bgm = Storage::disk('public')->path($this->immerse->bgm);
$cmd = $this->ffmpeg . ' -y ' .
' -i ' . escapeshellarg($upload_file) .
' -i ' . escapeshellarg($bgm) .
' -filter_complex "amix=inputs=2:duration=first" ' .
' -ar 48000 -ab 64k ' . escapeshellarg($audio);
if (!$this->execmd($cmd)) return 0;
}
if ( $media_info['format']['nb_streams'] >= 2 ){ /** 音频视频轨都有 */
if ($is_bgm){
// 有背景音 融合
$audio = $this->getTempPath('.mp3');
$drawtext = $this->getTextContentString($font);
$watermark = Storage::disk('public')->path('uploads/201/71/LOGO_eng.png');
$video = Storage::disk('public')->path($this->getTempPath('.mp4',false));
//直接将音频替换
$cmd = $this->ffmpeg . ' -y ' .
' -loop 1 -i ' . escapeshellarg($origin_image_path) .
' -i ' . escapeshellarg($audio) .
' -i ' . escapeshellarg($watermark) .
' -filter_complex "[0:v]scale=' . $this->output_width . ':' . $this->output_height . ',setdar=dar=9/16,' . $drawtext .
' [text];[text][2:v]overlay=20:20[v]"' .
' -map [v] -map 1:0 ' .
' -c:v libx264 -bt 256k -r 25 -t ' . $duration .
' -ar 48000 -ac 2 -qmin 30 -qmax 60 -profile:v high -pix_fmt yuv420p -preset fast '.
escapeshellarg($video);
$cmd = $this->ffmpeg.
' -y -i ' . escapeshellarg($file).
' -y -i ' . escapeshellarg($bgm).
' -filter_complex amix=inputs=2:duration=first:dropout_transition=2 ' .
'-ar 48000 -ab 64k ' . escapeshellarg($audio);
if (!$this->execmd($cmd)) return 0;
$audio_input = ' -i ' . escapeshellarg($audio);
$audio_filter = '[3:a]';
}else{
// 没有背景音
$audio_input = '';
$audio_filter = '[0:1]';
}
}elseif ( $media_info['format']['nb_streams'] == 1 ){
if ($this->immerse->origin_image_url == '' || $this->immerse->origin_image_url == null) {
// 原官方临境是视频
$origin_video_path = Storage::disk('public')->path($this->immerse->origin_video_url);
$origin_mediainfo = $this->mediainfo($origin_video_path);
//用户录音超长,截取用户录音
if ($mediainfo['format']['duration'] > $origin_mediainfo['format']['duration']) {
if ($this->immerse->bgm == '' || $this->immerse->bgm == null) {
$audio = Storage::disk('public')->path($this->getTempPath('.mp3'));
$cmd = $this->ffmpeg . ' -y ' .
' -i ' . escapeshellarg($upload_file) .
' -ss 0 -t ' . escapeshellarg($origin_mediainfo['format']['duration']) .
' -ar 48000 -ab 64k '.escapeshellarg($audio);
if (!$this->execmd($cmd)) return 0;
}else{
$bgm = Storage::disk('public')->path($this->immerse->bgm);
$audio = Storage::disk('public')->path($this->getTempPath('.mp3'));
$cmd = $this->ffmpeg . ' -y ' .
' -i ' . escapeshellarg($upload_file) .
' -i ' . escapeshellarg($bgm) .
' -filter_complex "amix=inputs=2:duration=first" ' .
' -ss 0 -t ' . escapeshellarg($origin_mediainfo['format']['duration']) .
' -ar 48000 -ab 64k '.escapeshellarg($audio);
if (!$this->execmd($cmd)) return 0;
}
}else{
if ($this->immerse->bgm == '' || $this->immerse->bgm == null) {
$audio = $upload_file;
}else{
$audio = Storage::disk('public')->path($this->getTempPath('.mp3'));
$bgm = Storage::disk('public')->path($this->immerse->bgm);
$cmd = $this->ffmpeg . ' -y ' .
' -i ' . escapeshellarg($upload_file) .
' -i ' . escapeshellarg($bgm) .
' -filter_complex "amix=inputs=2:duration=first[a]" -map [a]' .
' -ar 48000 -ab 64k' . escapeshellarg($audio);
if (!$this->execmd($cmd)) return 0;
}
}
$audio = $this->getTempPath('.mp3');
$cmd = $this->ffmpeg .
' -y -f lavfi -i aevalsrc=0:duration='. escapeshellarg($media_file_time_length) .
' -ar 48000 -ab 64k ' . escapeshellarg($audio);
if (!$this->execmd($cmd)) return 0;
$drawtext = $this->getTextContentString($font);
$watermark = Storage::disk('public')->path('uploads/201/71/LOGO_eng.png');
$video = Storage::disk('public')->path($this->getTempPath('.mp4',false));
//直接将音频替换
$cmd = $this->ffmpeg . ' -y ' .
' -i ' . escapeshellarg($origin_video_path) .
' -i ' . escapeshellarg($audio) .
' -i ' . escapeshellarg($watermark) .
' -filter_complex "[0:v]scale=' . $this->output_width . ':' . $this->output_height . ',setdar=dar=9/16,' . $drawtext .
' [text];[text][2:v]overlay=20:20[v]"' .
' -map [v] -map 1:0 ' .
' -c:v libx264 -bt 256k -r 25' .
' -ar 48000 -ac 2 -qmin 30 -qmax 60 -profile:v high -pix_fmt yuv420p -preset fast '.
escapeshellarg($video);
if ($is_bgm){
$audio_empty = $audio;
$audio = $this->getTempPath('.mp3');
$cmd = $this->ffmpeg.
' -y -i ' . escapeshellarg($audio_empty).
' -y -i ' . escapeshellarg($bgm).
' -filter_complex amix=inputs=2:duration=first:dropout_transition=2 ' .
'-ar 48000 -ab 64k ' . escapeshellarg($audio);
if (!$this->execmd($cmd)) return 0;
}
}else{
// 1. 分析用户上传视频
$upload_file = Storage::disk('public')->path($this->immerse->upload_file);
$mediainfo = $this->mediainfo($upload_file);
$audio_input = ' -i ' . escapeshellarg($audio);
$audio_filter = '[3:a]';
}else{ /** 音频视频轨都没有 */
return 0;
}
// 记录媒体信息时长
$duration = $mediainfo['format']['duration'] ?: 0;
$end_wallpaper = Storage::disk('public')->path('ffmpeg') . "/end_wallpaper.png";
$thumbnail = Storage::disk('public')->path('ffmpeg') . "/thumbnail.png";
$font = Storage::disk('public')->path('ffmpeg') . "/arialuni.ttf";
$signature = "一言 username";
// 生成贴纸和签名
$end_wallpaper = $this->wallpaperWithSignature($end_wallpaper, $thumbnail, $signature, $font);
// 截取最后一帧
$last_frame_video = $this->getTempPath();
$width = $media_info['streams'][0]['width'];
$height = $media_info['streams'][0]['height'];
$size = $width . 'x' . $height;
$time_length = 0.7;
$r = 24;
$frame_n = $media_info['streams'][0]['nb_frames'] - 2;
$cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($file) .
" -f lavfi -i nullsrc=s={$size}:d={$time_length}:r={$r} -f lavfi -i aevalsrc=0:duration={$time_length}" .
" -filter_complex \"[0:v]select='eq(n,{$frame_n})',setpts=PTS-STARTPTS[lastframe];[1:v][lastframe]overlay[v]\"" .
' -map [v] -map 2:a ' . escapeshellarg($last_frame_video);
if (!$this->execmd($cmd)) return 0;
$signature_x = 0;
$signature_y = -20;
$animate = $this->makeAnimate($last_frame_video, $end_wallpaper, '', $signature_x, $signature_y, $font);
$font_size = ceil($width / 14);
$content = '题破山寺后禅院' . "\t" . ' -- 常建' . PHP_EOL .
'清晨入古寺,初日照高林。' . PHP_EOL .
'曲径通幽处,禅房花木深。' . PHP_EOL .
'山光悦鸟性,潭影空人心。' . PHP_EOL .
'万籁此都寂,但余钟磬音。' . PHP_EOL ;
$cmd = $this->ffmpeg . ' -y '.
' -i ' . escapeshellarg($file).
' -i ' . escapeshellarg($animate).
' -i ' . escapeshellarg($path . 'logo.png').
$audio_input.
' -filter_complex "[0:0] ' .
$string.
'[text];[text]'.
'[2:v]overlay=20:20[water];[water]'.$audio_filter.'[1:0][1:1] concat=n=2:v=1:a=1[v][a]" '.
' -map [v] -map [a]'.
' -c:v libx264 -bt 256k -r 25' .
' -ar 44100 -ac 2 -qmin 30 -qmax 60 -profile:v baseline -preset fast ' .
escapeshellarg($path . 'out.mp4');
$this->execmd($cmd);
// $cmd = $this->ffmpeg. ' -y'.
// ' -i ' . escapeshellarg($path . 'out.mp4').
// ' -i ' . escapeshellarg($path . 'thumbnail.png').
// ' -map 1 -map 0 -c copy -disposition:0 attached_pic '.
// escapeshellarg($path . 'out2.mp4');
//
$end_wallpaper = Storage::disk('public')->path('uploads/201/71/end_wallpaper.png');
$avatar = Storage::disk('public')->path('uploads/201/71/thumbnail.png');
$font = Storage::disk('public')->path('uploads/201/71/arialuni.ttf');
//
// $this->execmd($cmd);
return 0;
$adminMakeVideo = AdminMakeVideo::query()->first();
$adminMakeVideo->video_url = Storage::disk('public')->path($adminMakeVideo->video_url);
// $adminMakeVideo->thumbnail_url = Storage::disk('public')->path($adminMakeVideo->thumbnail_url);
$adminMakeVideo->poem;
$adminMakeVideo->temp->components;
$file = $adminMakeVideo->video_url;
// $watermark = Storage::disk('public')->path('image/logo.jpg');
// 转换logo大小
$watermark = $this->translateLogo(Storage::disk('public')->path('image/logo.jpg'));
$watermark_x = 20;
$watermark_y = 20;
$end_wallpaper = Storage::disk('public')->path('ffmpeg') . "/end_wallpaper.png";
$thumbnail = Storage::disk('public')->path('ffmpeg') . "/thumbnail.png";
$font = Storage::disk('public')->path('ffmpeg') . "/arialuni.ttf";
$signature = "一言";
$signature_x = 0;
$signature_y = -20;
// $content = '题破山寺后禅院' . "\t" . ' -- 常建' . PHP_EOL .
// '清晨入古寺,初日照高林。' . PHP_EOL .
// '曲径通幽处,禅房花木深。' . PHP_EOL .
// '山光悦鸟性,潭影空人心。' . PHP_EOL .
// '万籁此都寂,但余钟磬音。' . PHP_EOL ;
$content = $adminMakeVideo->poem->content;
// 生成贴纸和签名
$end_wallpaper = $this->wallpaperWithSignature($end_wallpaper, $thumbnail, $signature, $font);
// 截取最后一帧
$last_frame_video = $this->makeLastFrameVideo($file);
$animate = '';
if ($last_frame_video) {
$animate = $this->makeAnimate($last_frame_video, $end_wallpaper, '', $signature_x, $signature_y, $font);
}
$video = $this->getTempPath();
$watermark_x = $watermark_x ? $watermark_x : 0;
$watermark_y = $watermark_y ? $watermark_y : 0;
$am_inp = ' -i ' . escapeshellarg($watermark);
$am_filter = "[2:v]overlay={$watermark_x}:{$watermark_y}[water];[water]";
$cmd = $this->ffmpeg . ' -y' .
' -i ' . escapeshellarg($file) .
' -i ' . escapeshellarg($animate) .
$am_inp .
// ' -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]"' .
' -filter_complex "[0:0]' . '' . $am_filter . '[0:1] [1:0] [1:1] concat=n=2:v=1:a=1 [v] [a]"' .
' -map [v] -map [a]';
$cmd .=
' -c:v libx264 -s 800x450 -bt 256k -r 25' .
// todo 没有libfdk_aac库
// ' -c:a libfdk_aac -ar 44100 -ac 2 -qmin 30 -qmax 60 -profile:v baseline -preset fast ' .
' -ar 44100 -ac 2 -qmin 30 -qmax 60 -profile:v baseline -preset fast ' .
escapeshellarg($video);
if ($this->execmd($cmd)) {
// todo create insert
} else {
return false;
}
$video2 = $this->getTempPath();
$cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($video) .
' -vf '.
'drawtext="'.
'fontfile=' . escapeshellarg($font) . ':'.
'text=' . escapeshellarg($content) . ':'.
'fontsize=43:'.
'fontcolor=white@1.0:'.
'x=main_w/2' . '-260' . ':'.
'y=main_h/2' . '-20' . ':'.
'box=1:boxcolor=0xd0cdcc@0.5'.
'" ' . escapeshellarg($video2);
if ($this->execmd($cmd)) {
// 全部合成以后创建 临境
$video_info = $this->mediainfo($video2);
Immerse::query()->create([
'user_id' => 1,
'title' => '',
'content' => $adminMakeVideo->feel,
'url' => $video2,
'type' => $adminMakeVideo->type == 1 ? 2 : 1,
'duration' => $video_info['format']['duration'],
'size' => $video_info['format']['size'],
]);
} else {
return false;
}
}
/**
* 获取圆形头像
* @param $img
* @param int $dst_w
* @param int $dst_h
* @return resource
*/
public function getCircleAvatar($img, $dst_w = 96, $dst_h = 96)
{
$w = 130;
$h = 130;
$src = imagecreatetruecolor($dst_w, $dst_h);
imagecopyresized($src, $img, 0, 0, 0, 0, $dst_w, $dst_h, $w, $h);
$newpic = imagecreatetruecolor($dst_w, $dst_h);
imagealphablending($newpic, false);
imagecopyresampled($newpic, $img, 0, 0, 0, 0, $dst_w, $dst_h, $w, $h);
$mask = imagecreatetruecolor($dst_w, $dst_h);
$transparent = imagecolorallocate($mask, 255, 0, 0);
imagecolortransparent($mask,$transparent);
imagefilledellipse($mask, $dst_w / 2, $dst_h / 2, $dst_w, $dst_h, $transparent);
$red = imagecolorallocate($mask, 0, 0, 0);
imagecopymerge($newpic, $mask, 0, 0, 0, 0, $dst_w, $dst_h, 100);
imagecolortransparent($newpic,$red);
imagesavealpha($newpic,true);
imagefill($newpic, 0, 0, $red);
imagedestroy($mask);
return $newpic;
}
/**
* 制作最后一帧
* @param $file
* @return bool|string
*/
public function makeLastFrameVideo($file) {
$video = $this->getTempPath();
$width = $this->getVideoWith($file);
$height = $this->getVideoHeight($file);
$size = $width . 'x' . $height;
$time_length = 0.7;
$r = 24;
$frame_n = $this->getVideoFrameNum($file) - 2;
$cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($file) .
" -f lavfi -i nullsrc=s={$size}:d={$time_length}:r={$r} -f lavfi -i aevalsrc=0:duration={$time_length}" .
" -filter_complex \"[0:v]select='eq(n,{$frame_n})',setpts=PTS-STARTPTS[lastframe];[1:v][lastframe]overlay[v]\"" .
' -map [v] -map 2:a ' . escapeshellarg($video);
if ($this->execmd($cmd)) {
return $video;
} else {
return false;
}
}
// $user = User::query()->find($this->immerse->user_id);
// $signature = $user->nickname ?? $user->email ?? '2@qq.com';
//
// // 生成贴纸和签名
// $end_wallpaper = $this->wallpaperWithSignature($end_wallpaper, $avatar, $signature, $font);
//
// // 截取最后一帧做动画
// $last_frame_video = $this->getTempPath();
// $this->width = $width = $mediainfo['streams'][0]['width'];
// $height = $mediainfo['streams'][0]['height'];
// $size = $width . 'x' . $height;
// $time_length = 0.7;
// $r = 24;
// $frame_n = $mediainfo['streams'][0]['nb_frames'] - 2;
// $cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($upload_file) .
// " -f lavfi -i nullsrc=s={$size}:d={$time_length}:r={$r} -f lavfi -i aevalsrc=0:duration={$time_length}" .
// " -filter_complex \"[0:v]select='eq(n,{$frame_n})',setpts=PTS-STARTPTS[lastframe];[1:v][lastframe]overlay[v]\"" .
// ' -map [v] -map 2:a ' . escapeshellarg($last_frame_video);
// if (!$this->execmd($cmd)) return 0;
//
$drawtext = $this->getTextContentString($font);
$watermark = Storage::disk('public')->path('uploads/201/71/LOGO_eng.png');
// 截取中间帧作为视频封面
$frame = ceil($mediainfo['streams'][0]['nb_frames'] / 2);
$thumbnail = Storage::disk('public')->path($this->getTempPath('.jpg',false));
$cmd = $this->ffmpeg . ' -y ' .
' -i ' . escapeshellarg($upload_file) .
' -i ' . escapeshellarg($watermark) .
' -filter_complex "[0:v]select=\'eq(n,' . $frame . ')\'[img]" ' .
' -map [img]'.
' -frames:v 1 -s ' . $this->output_width . 'x' . $this->output_height .' -preset superfast '.
escapeshellarg($thumbnail);
if (!$this->execmd($cmd)) return 0;
/**
* 用最后一帧和贴纸制作动画
* @param $last_frame_video
* @param $end_wallpaper
* @param $signature
* @param $signature_x
* @param $signature_y
* @param $font
* @return bool|string
*/
public function makeAnimate($last_frame_video, $end_wallpaper, $signature, $signature_x, $signature_y, $font) {
$signature_x = $signature_x >= 0 ? '+' . $signature_x : '-' . abs($signature_x);
$signature_y = $signature_y >= 0 ? '+' . $signature_y : '-' . abs($signature_y);
$video = $this->getTempPath();
if ($signature !== '') {
$cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($last_frame_video) .
' -loop 1 -i ' . escapeshellarg($end_wallpaper) .
' -filter_complex "'.
'geq=lum=\'if(lte(T,0.6), 255*T*(1/0.6),255)\',format=gray[grad];'.
'[0:v]boxblur=8[blur];'.
'[blur][1:v]overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2 [lay];[lay]'.
'drawtext='.
'fontfile=' . escapeshellarg($font) . ':'.
'text=' . escapeshellarg($signature) . ':'.
'fontsize=23:'.
'fontcolor=white@1.0:'.
'x=main_w/2' . $signature_x . ':'.
'y=main_h/2' . $signature_y . '[text];[text]'.
'[grad]alphamerge[alpha];'.
'[0:v][alpha]overlay'.
'" ' . escapeshellarg($video);
} else {
$cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($last_frame_video) .
' -loop 1 -i ' . escapeshellarg($end_wallpaper) .
' -filter_complex "'.
'geq=lum=\'if(lte(T,0.6), 255*T*(1/0.6),255)\',format=gray[grad];'.
'[0:v]boxblur=8[blur];'.
'[blur][1:v]overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2 [lay];'.
'[lay][grad]alphamerge[alpha];'.
'[0:v][alpha]overlay'.
'" ' . escapeshellarg($video);
}
if ($this->execmd($cmd)) {
return $video;
} else {
return false;
}
}
$video = Storage::disk('public')->path($this->getTempPath('.mp4',false));
// 没有背景音
if ($this->immerse->bgm == '' || $this->immerse->bgm == null) {
$cmd = $this->ffmpeg . ' -y ' .
' -i ' . escapeshellarg($upload_file) .
' -i ' . escapeshellarg($watermark) .
' -filter_complex "[0:v]scale=' . $this->output_width . ':' . $this->output_height . ',setdar=dar=9/16,' . $drawtext .
' [text];[text][1:v]overlay=20:20"' .
' -c:v libx264 -bt 256k -r 25' .
' -c:a copy -qmin 30 -qmax 60 -profile:v high -pix_fmt yuv420p -preset fast ' .
escapeshellarg($video);
}else{
$bgm = Storage::disk('public')->path($this->immerse->bgm);
$cmd = $this->ffmpeg . ' -y ' .
' -i ' . escapeshellarg($upload_file) .
' -i ' . escapeshellarg($watermark) .
' -i ' . escapeshellarg($bgm) .
' -filter_complex "[0:v]scale=' . $this->output_width . ':' . $this->output_height . ',setdar=dar=9/16,' . $drawtext .
' [text];[text][1:v]overlay=20:20[v];' .
' amix=inputs=2:duration=first[a] " -map [v] -map [a]' .
' -c:v libx264 -bt 256k -r 25' .
' -ar 44100 -ac 2 -qmin 30 -qmax 60 -profile:v high -pix_fmt yuv420p -preset fast ' .
escapeshellarg($video);
}
/**
* 获取视频宽度
* @param $file
* @param bool $cache
* @return int|null
*/
public function getVideoWith($file, $cache = true) {
$result = $this->getFirstVideoTrackOption($file, $option = 'width', $cache);
if ($result) {
return (int)$result;
} else {
return $result;
if (!$this->execmd($cmd)) return 0;
}
}
/**
* 获取视频高度
* @param $file
* @param bool $cache
* @return int|null
*/
public function getVideoHeight($file, $cache = true) {
$result = $this->getFirstVideoTrackOption($file, $option = 'height', $cache);
if ($result) {
return (int)$result;
} else {
return $result;
}
}
// 全部合成以后创建 临境
$video_info = $this->mediainfo($video);
/**
* 获取视频帧数
* @param $file
* @param bool $cache
* @return null
*/
public function getVideoFrameNum($file, $cache = true) {
return $this->getFirstVideoTrackOption($file, $option = 'nb_frames', $cache);
}
$this->immerse->url = Str::of($video)->replace(Storage::disk('public')->path(''), '');
$this->immerse->state = 1;
$this->immerse->duration = $video_info['format']['duration'] ?? 0.00;
$this->immerse->size = $video_info['format']['size'];
$this->immerse->save();
public function getFirstVideoTrackOption($file, $option, $cache = true) {
return $this->getFirstTrackOption($file, $option, $codec_type = 'video', $cache = true);
}
public function getFirstTrackOption($file, $option, $codec_type = '', $cache = true) {
$result = $this->mediainfo($file, $cache);
if (!isset($result['streams'])) {
return null;
}
$_track = null;
foreach($result['streams'] as $track) {
if (empty($codec_type)) {
$_track = $track;
break;
} elseif ($track['codec_type'] == $codec_type) {
$_track = $track;
break;
}
}
if (isset($_track[$option])) {
return $_track[$option];
}
return null;
return 0;
}
/***
* 获取视频信息(配合ffprobe)
* @param $file
* @param bool $cache
* @return mixed
*/
public function mediainfo($file, $cache = true) {
global $_mediainfo;
public function mediainfo($file)
{
$cmd = $this->ffprobe . ' -v quiet -print_format json -show_format -show_streams ' . escapeshellarg($file);
if ($cache && isset($_mediainfo[$file])) {
return $_mediainfo[$file];
}
$output = $this->execmd($cmd);
$data = json_decode($output, true);
if (json_last_error() === JSON_ERROR_UTF8) {
$output = mb_convert_encoding($output, "UTF-8");
$data = json_decode($output, true);
}
if ($cache) {
$mediainfo[$file] = $data;
}
return $data;
}
/**
* 获取输出临时文件名
* @return string
*/
public function getTempPath($ext = '.mp4')
{
$filename = "/output_" . time() . rand(0, 10000);
$hash_hex = md5($filename);
// 16进制表示的字符串一共32字节,表示16个二进制字节。
// 前16个字符用来第一级求摸,后16个用做第二级
$hash_hex_l1 = substr($hash_hex, 0, 8);
$hash_hex_l2 = substr($hash_hex, 8, 8);
$dir_l1 = hexdec($hash_hex_l1) % 256;
$dir_l2 = hexdec($hash_hex_l2) % 512;
$dir = 'temp/'. $dir_l1. '/' . $dir_l2;
if( !Storage::disk('public')->exists($dir)) Storage::disk('public')->makeDirectory($dir);
return Storage::disk('public')->path($dir . $filename . $ext);
}
/**
* 执行命令
* @param $cmd
* @param bool $update_progress
* @return string
*/
public function execmd($cmd, $update_progress = false) {
echo $cmd . "\n". "\n". "\n";
$descriptorspec = array(
......@@ -626,6 +343,97 @@ class DevFFmpeg extends Command
}
/**
* 获取输出临时文件名
* @param string $ext
* @param bool $is_temp
* @return string
*/
public function getTempPath($ext = '.mp4',$is_temp = true)
{
$filename = "/output_" . time() . rand(0, 10000);
$prefix = $is_temp ? 'temp/' : 'video/';
$hash_hex = md5($filename);
// 16进制表示的字符串一共32字节,表示16个二进制字节。
// 前16个字符用来第一级求摸,后16个用做第二级
$hash_hex_l1 = substr($hash_hex, 0, 8);
$hash_hex_l2 = substr($hash_hex, 8, 8);
$dir_l1 = hexdec($hash_hex_l1) % 256;
$dir_l2 = hexdec($hash_hex_l2) % 512;
$dir = $prefix . $dir_l1 . '/' . $dir_l2;
if( !Storage::disk('public')->exists($dir)) Storage::disk('public')->makeDirectory($dir);
return $dir . $filename . $ext;
}
/**
* 获取图像文件类型
* @param $img_name
* @return string
*/
public function getImageType($img_name)
{
if (preg_match("/\.(jpg|jpeg|gif|png)$/i", $img_name, $matches)){
$type = strtolower($matches[1]);
}else{
$type = "string";
}
return $type;
}
/**
* 获取圆形头像
* @param $img
* @param int $dst_w
* @param int $dst_h
* @return resource
*/
public function getCircleAvatar($img, $dst_w = 96, $dst_h = 96)
{
$w = 130;
$h = 130;
$src = imagecreatetruecolor($dst_w, $dst_h);
imagecopyresized($src, $img, 0, 0, 0, 0, $dst_w, $dst_h, $w, $h);
$newpic = imagecreatetruecolor($dst_w, $dst_h);
imagealphablending($newpic, false);
imagecopyresampled($newpic, $img, 0, 0, 0, 0, $dst_w, $dst_h, $w, $h);
$mask = imagecreatetruecolor($dst_w, $dst_h);
$transparent = imagecolorallocate($mask, 255, 0, 0);
imagecolortransparent($mask,$transparent);
imagefilledellipse($mask, $dst_w / 2, $dst_h / 2, $dst_w, $dst_h, $transparent);
$red = imagecolorallocate($mask, 0, 0, 0);
imagecopymerge($newpic, $mask, 0, 0, 0, 0, $dst_w, $dst_h, 100);
imagecolortransparent($newpic,$red);
imagesavealpha($newpic,true);
imagefill($newpic, 0, 0, $red);
imagedestroy($mask);
return $newpic;
}
/**
* 多图融合
* @param $end_wallpaper
* @param $thumbnail
* @return resource
*/
public function imagesMerge($end_wallpaper, $thumbnail) {
$end_wallpaper = imagecreatefrompng($end_wallpaper);
$background = imagecreatefrompng(Storage::disk('public')->path('ffmpeg/background.png'));
imagesavealpha($background,true);
$temp_wallpaper = imagecreatetruecolor(350, 204);
// $color = imagecolorallocate($temp_wallpaper, 0xd0, 0xcd, 0xcc);
$color = imagecolorallocate($temp_wallpaper, 0xDC, 0x14, 0x3C);
imagefill($temp_wallpaper, 0, 0, $color);
imageColorTransparent($temp_wallpaper, $color);
imagecopyresampled($temp_wallpaper, $end_wallpaper, 0, 0, 0, 0, imagesx($temp_wallpaper), imagesy($temp_wallpaper), imagesx($end_wallpaper), imagesy($end_wallpaper));
imagecopymerge($background, $temp_wallpaper, 0, 0, 0, 0, imagesx($temp_wallpaper), imagesy($temp_wallpaper), 60);
imagecopymerge($background, $thumbnail, 127, 26, 0, 0, imagesx($thumbnail), imagesy($thumbnail), 100);
return $background;
}
/**
* 贴纸和签名
* @param $end_wallpaper
* @param $thumbnail
......@@ -689,8 +497,7 @@ class DevFFmpeg extends Command
$white = imagecolorallocate($wp, 0xDC, 0x14, 0x3C); //fixme 字体颜色
imagettftext($wp, 20, 0, 75, 240, $white, $font, $signature);
// $dst = "./output_new_end_wallpaper.png";
$dst = Storage::disk('public')->path('ffmpeg') . "/output_new_end_wallpaper.png";
$dst = Storage::disk('public')->path($this->getTempPath('.png'));
imagepng($wp, $dst);
if(is_resource($end_wallpaper)) imagedestroy($end_wallpaper);
if(is_resource($_img)) imagedestroy($_img);
......@@ -698,146 +505,33 @@ class DevFFmpeg extends Command
return $dst;
}
/**
* 获取图像文件类型
* @param $img_name
* @return string
*/
public function getImageType($img_name)
{
if (preg_match("/\.(jpg|jpeg|gif|png)$/i", $img_name, $matches)){
$type = strtolower($matches[1]);
}else{
$type = "string";
}
return $type;
}
/**
* 多图融合
* @param $end_wallpaper
* @param $thumbnail
* @return resource
*/
public function imagesMerge($end_wallpaper, $thumbnail) {
$end_wallpaper = imagecreatefrompng($end_wallpaper);
$background = imagecreatefrompng(Storage::disk('public')->path('ffmpeg/background.png'));
imagesavealpha($background,true);
$temp_wallpaper = imagecreatetruecolor(350, 204);
$color = imagecolorallocate($temp_wallpaper, 0xd0, 0xcd, 0xcc);
// $color = imagecolorallocate($temp_wallpaper, 0xDC, 0x14, 0x3C);
imagefill($temp_wallpaper, 0, 0, $color);
imageColorTransparent($temp_wallpaper, $color);
imagecopyresampled($temp_wallpaper, $end_wallpaper, 0, 0, 0, 0, imagesx($temp_wallpaper), imagesy($temp_wallpaper), imagesx($end_wallpaper), imagesy($end_wallpaper));
imagecopymerge($background, $temp_wallpaper, 0, 0, 0, 0, imagesx($temp_wallpaper), imagesy($temp_wallpaper), 60);
imagecopymerge($background, $thumbnail, 127, 26, 0, 0, imagesx($thumbnail), imagesy($thumbnail), 100);
return $background;
}
/**
* logo 大小转换
* @param $logo
* @return bool
*/
public function translateLogo($logo)
{
$image = Storage::disk('public')->path('ffmpeg/output_150x150.jpg');
$cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($logo) .
' -vf scale=150:150 ' . escapeshellarg($image);
if ($this->execmd($cmd)) {
return $image;
} else {
return false;
}
}
/**
* 合成视频秀
* @param $file
* @param $audio
* @param null $animate
* @param null $ass_file
* @param null $watermark
* @param null $watermark_x
* @param null $watermark_y
* @return bool|string
*/
public function makeMofunshow($file, $audio, $animate = null, $ass_file = null, $watermark = null, $watermark_x = null, $watermark_y = null)
{
$video = $this->getTempPath();
if ($animate) {
$ass_filter = $ass_file ? 'ass=' . escapeshellarg($ass_file) . '[ass];[ass]' : '';
if ($watermark && is_file($watermark) && getimagesize($watermark)) {
$watermark_x = $watermark_x ? $watermark_x : 0;
$watermark_y = $watermark_y ? $watermark_y : 0;
$am_inp = ' -i ' . escapeshellarg($watermark);
$am_filter = "[3:v]overlay={$watermark_x}:{$watermark_y}[water];[water]";
} else {
$am_inp = '';
$am_filter = '';
}
$cmd = $this->ffmpeg . ' -y' .
' -i ' . escapeshellarg($file) .
' -i ' . escapeshellarg($animate) .
' -i ' . escapeshellarg($audio) .
$am_inp .
' -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]"' .
' -map [v] -map [a]';
} else {
$ass_filter = $ass_file ? ' -vf ass=' . escapeshellarg($ass_file) : '';
$cmd = $this->ffmpeg . ' -y' .
' -i ' . escapeshellarg($file) .
' -i ' . escapeshellarg($audio) .
' -map 0:0' .
' -map 1:0' .
$ass_filter;
}
$cmd .=
' -c:v libx264 -s 800x450 -bt 256k -r 25' .
// todo 没有libfdk_aac库
// ' -c:a libfdk_aac -ar 44100 -ac 2 -qmin 30 -qmax 60 -profile:v baseline -preset fast ' .
' -ar 44100 -ac 2 -qmin 30 -qmax 60 -profile:v baseline -preset fast ' .
escapeshellarg($video);
if ($this->execmd($cmd)) {
return $video;
} else {
return false;
}
}
public function getTextContentString()
public function getTextContentString($font)
{
$components = VideoTemp::query()->find(6)->components()->get();
$font = Storage::disk('public')->path('ffmpeg/arialuni.ttf');
$components = $this->immerse->temp()->first()->components()->get();
$drawtext = '';
foreach ($components as $component) {
switch ($component->name){
case 'one_poem':
$content = '题破山寺后禅院' . "\t" . ' -- 常建' . PHP_EOL .
'清晨入古寺,初日照高林。' . PHP_EOL .
'曲径通幽处,禅房花木深。' . PHP_EOL .
'山光悦鸟性,潭影空人心。' . PHP_EOL .
'万籁此都寂,但余钟磬音。' . PHP_EOL ;
$content = $this->immerse->poem->content;
$text_file = Storage::disk('public')->path($this->getTempPath('.txt'));
file_put_contents($text_file, $content);
$text_color = $component->text_color ?? 'white';
$text_bg_color = $component->text_bg_color ?? '0xd0cdcc';
$opacity = $component->opacity ? $component->opacity / 100 : '0.5';
$text_file = $this->getTempPath('txt');
file_put_contents($text_file, $content);
$drawtext .= 'drawtext="'.
'fontfile=' . escapeshellarg($font) . ':' .
'textfile=' . escapeshellarg($text_file) . ':' .
'fontsize=' . $component->font_size . ':' .
'fontsize=' . $this->calcFontSize($component->font_size) . ':' .
'fontcolor=' . $text_color . '@1.0:' .
'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' .
'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' .
'box=1:boxcolor=' . $text_bg_color . '@' . $opacity . '", ';
'box=1:boxborderw='. $this->calcBorderSize($component->font_size) . ':' .
'boxcolor=' . $text_bg_color . '@' . $opacity . '", ';
break;
case 'every_poem':
......@@ -851,11 +545,12 @@ class DevFFmpeg extends Command
$drawtext .= 'drawtext="'.
'fontfile=' . escapeshellarg($font) . ':' .
'text=' . escapeshellarg($content) . ':' .
'fontsize=' . $component->font_size . ':' .
'fontsize=' . $this->calcFontSize($component->font_size) . ':' .
'fontcolor=' . $text_color . '@1.0:' .
'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' .
'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' .
'box=1:boxcolor=' . $text_bg_color . '@' . $opacity . '", ';
'box=1:boxborderw='. $this->calcBorderSize($component->font_size) . ':' .
'boxcolor=' . $text_bg_color . '@' . $opacity . '", ';
break;
case 'date':
......@@ -867,14 +562,15 @@ class DevFFmpeg extends Command
$drawtext .= 'drawtext="'.
'fontfile=' . escapeshellarg($font) . ':' .
'text=' . escapeshellarg($content) . ':' .
'fontsize=' . $component->font_size . ':' .
'fontsize=' . $this->calcFontSize($component->font_size) . ':' .
'fontcolor=' . $text_color . '@1.0:' .
'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' .
'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' .
'box=1:boxcolor=' . $text_bg_color . '@' . $opacity . '", ';
'box=1:boxborderw='. $this->calcBorderSize($component->font_size) . ':' .
'boxcolor=' . $text_bg_color . '@' . $opacity . '", ';
break;
case 'feel':
$content = '测试有感文本';
$content = $this->immerse->content ?: '这里是发表临境有感';
$text_color = $component->text_color ?? 'white';
$text_bg_color = $component->text_bg_color ?? '0xd0cdcc';
$opacity = $component->opacity ? $component->opacity / 100 : '0.5';
......@@ -882,11 +578,12 @@ class DevFFmpeg extends Command
$drawtext .= 'drawtext="'.
'fontfile=' . escapeshellarg($font) . ':' .
'text=' . escapeshellarg($content) . ':' .
'fontsize=' . $component->font_size . ':' .
'fontsize=' . $this->calcFontSize($component->font_size) . ':' .
'fontcolor=' . $text_color . '@1.0:' .
'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' .
'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' .
'box=1:boxcolor=' . $text_bg_color . '@' . $opacity . '", ';
'box=1:boxborderw='. $this->calcBorderSize($component->font_size) . ':' .
'boxcolor=' . $text_bg_color . '@' . $opacity . '", ';
break;
}
}
......@@ -894,16 +591,24 @@ class DevFFmpeg extends Command
return rtrim($drawtext,', ');
}
public function calcFontSize($width, $content)
/**
* @param $width
* @param $content
* @return float
*/
public function calcFontSize($width)
{
$max_len = 1;
foreach (explode("\n",$content) as $item){
if (mb_strlen($item) > $max_len){
$max_len = mb_strlen($item);
}
}
return ceil($this->output_width / 360 * $width);
}
return ceil(800 * $width / 100 / $max_len);
/**
* @param $width
* @param $content
* @return float
*/
public function calcBorderSize($width)
{
return ceil($width / 12 * 10);
}
}
......
......@@ -3,6 +3,7 @@
namespace App\Http\Controllers\V1;
use App\Http\Controllers\Controller;
use App\Jobs\UserMakeImmerse;
use App\Models\Collect;
use App\Models\Immerse;
use App\Jobs\UserMakeVideo as MakeVideo;
......@@ -52,34 +53,56 @@ class ImmerseController extends Controller
$user_id = $request->user()->id;
$immerse = Immerse::query()->find($request->item_id);
if ($validated['type'] == 1){
$create = Immerse::query()->create([
'user_id' => $user_id,
'title' => '',
'content' => $validated['content'],
'url' => '',
'type' => $validated['type'],
'duration' => 0,
'size' => 0,
'poem_id' => $immerse->poem_id,
'temp_id' => $immerse->temp_id,
'thumbnail' => $immerse->thumbnail,
'bgm' => $validated['item_url'],
'state' => 0
]);
// if ($validated['type'] == 1){
// $create = Immerse::query()->create([
// 'user_id' => $user_id,
// 'title' => '',
// 'content' => $validated['content'],
// 'url' => '',
// 'type' => $validated['type'],
// 'duration' => 0,
// 'size' => 0,
// 'poem_id' => $immerse->poem_id,
// 'temp_id' => $immerse->temp_id,
// 'thumbnail' => $immerse->thumbnail,
// 'bgm' => $validated['item_url'],
// 'state' => 0
// ]);
//
// // 添加至队列
// MakeImages::dispatch($create);
// }else{
// // 视频
// $create = Immerse::query()->create([
// 'user_id' => $user_id,
// 'title' => '',
// 'content' => $validated['content'],
// 'url' => '',
// 'type' => $validated['type'],
// 'duration' => 0,
// 'size' => 0,
// 'poem_id' => $immerse->poem_id,
// 'temp_id' => $immerse->temp_id,
// 'thumbnail' => '',
// 'bgm' => $immerse->bgm,
// 'state' => 0
// ]);
//
// // 添加至队列
// MakeVideo::dispatch($create, $validated['item_url']);
// }
// 添加至队列
MakeImages::dispatch($create);
}else{
// 视频
$create = Immerse::query()->create([
'user_id' => $user_id,
'title' => '',
'content' => $validated['content'],
'url' => '',
'type' => $validated['type'],
'upload_file' => $validated['item_url'],
'duration' => 0,
'size' => 0,
'origin_video_url' => $immerse->origin_video_url,
'origin_image_url' => $immerse->origin_image_url,
'poem_id' => $immerse->poem_id,
'temp_id' => $immerse->temp_id,
'thumbnail' => '',
......@@ -88,8 +111,7 @@ class ImmerseController extends Controller
]);
// 添加至队列
MakeVideo::dispatch($create, $validated['item_url']);
}
UserMakeImmerse::dispatch($create);
return Response::success($create);
}
......
......@@ -82,8 +82,11 @@ class MakeImages implements ShouldQueue
'content' => $this->adminMakeVideo->feel,
'url' => str_replace(Storage::disk('public')->path(''),'',$output),
'type' => $this->adminMakeVideo->type == 1 ? 2 : 1,
'upload_file' => '',
'duration' => 0,
'size' => filesize($image),
'origin_video_url' => '',
'origin_image_url' => $this->adminMakeVideo->image_url,
'poem_id' => $this->adminMakeVideo->poem_id,
'temp_id' => $this->adminMakeVideo->temp_id,
'thumbnail' => str_replace(Storage::disk('public')->path(''),'',$image),
......@@ -154,8 +157,11 @@ class MakeImages implements ShouldQueue
'content' => $this->adminMakeVideo->feel,
'url' => str_replace(Storage::disk('public')->path(''),'',$output),
'type' => $this->adminMakeVideo->type == 1 ? 2 : 1,
'upload_file' => '',
'duration' => $video_info['format']['duration'],
'size' => $video_info['format']['size'],
'origin_video_url' => '',
'origin_image_url' => $this->adminMakeVideo->image_url,
'poem_id' => $this->adminMakeVideo->poem_id,
'temp_id' => $this->adminMakeVideo->temp_id,
'thumbnail' => str_replace(Storage::disk('public')->path(''),'',$this->adminMakeVideo->images_url),
......
......@@ -116,13 +116,28 @@ class MakeVideo implements ShouldQueue
return;
}
if ($this->adminMakeVideo->thumbnail == 2){
// 截取中间帧作为视频封面
$frame = ceil($media_info['streams'][0]['nb_frames'] / 2);
$thumbnail = $this->getTempPath('.jpg',false);
$cmd = $this->ffmpeg . ' -y ' .
' -i ' . escapeshellarg($file) .
' -filter_complex "[0:v]select=\'eq(n,' . $frame . ')\'[img]" ' .
' -map [img]'.
' -frames:v 1 -s 720x1280 -preset superfast '.
escapeshellarg($thumbnail);
if (!$this->execmd($cmd)) return ;
}else{
$thumbnail = $adminMakeVideo->thumbnail_url;
}
$end_wallpaper = Storage::disk('public')->path('ffmpeg') . "/end_wallpaper.png";
$thumbnail = Storage::disk('public')->path('ffmpeg') . "/thumbnail.png";
$avatar = Storage::disk('public')->path('ffmpeg') . "/thumbnail.png";
$font = Storage::disk('public')->path('ffmpeg') . "/arialuni.ttf";
$signature = "一言 · 官方出品";
// 生成贴纸和签名
$end_wallpaper = $this->wallpaperWithSignature($end_wallpaper, $thumbnail, $signature, $font);
$end_wallpaper = $this->wallpaperWithSignature($end_wallpaper, $avatar, $signature, $font);
// 截取最后一帧
$last_frame_video = $this->getTempPath();
......@@ -176,11 +191,14 @@ class MakeVideo implements ShouldQueue
'content' => $this->adminMakeVideo->feel,
'url' => str_replace(Storage::disk('public')->path(''),'',$video),
'type' => $this->adminMakeVideo->type == 1 ? 2 : 1,
'upload_file' => '',
'duration' => $video_info['format']['duration'],
'size' => $video_info['format']['size'],
'origin_video_url' => $this->adminMakeVideo->video_url,
'origin_image_url' => '',
'poem_id' => $this->adminMakeVideo->poem_id,
'temp_id' => $this->adminMakeVideo->temp_id,
'thumbnail' => '',
'thumbnail' => $thumbnail,
'state' => 1,
'bgm' => $this->adminMakeVideo->bgm_url ?? '',
]);
......
<?php
namespace App\Jobs;
use App\Models\Immerse;
use App\Models\UserProfile;
use App\Models\VideoTemp;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
class UserMakeImmerse implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $debug = true;
public $immerse;
protected $ffmpeg;
protected $ffprobe;
protected $output_width;
protected $output_height;
/**
* Create a new job instance.
* @param Immerse $immerse
* @return void
*/
public function __construct(Immerse $immerse)
{
$this->immerse = $immerse;
$this->ffmpeg = env('FFMPEG_CMD');
$this->ffprobe = env('FFPROBE_CMD');
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
// 分情况 1.用户视频,2.用户录音
// 注意事项:1.考虑用户是否会员;非会员分辨率 720x1280,会员分辨率1440x2560
// 2.同时字体大小也很方便确定。
$profile = UserProfile::query()->find($this->immerse->user_id);
if ($profile->is_vip == 1){
$this->output_width = 720;
$this->output_height = 1280;
}else{
$this->output_width = 540;
$this->output_height = 960;
}
if ($this->immerse->type == 1){
// 1. 分析用户上传音频
$upload_file = Storage::disk('public')->path($this->immerse->upload_file);
$mediainfo = $this->mediainfo($upload_file);
// 记录媒体信息时长
$duration = $mediainfo['format']['duration'] ?: 0;
$font = Storage::disk('public')->path('uploads/201/71/arialuni.ttf');
// 音频还取之前的封面。
$thumbnail = Storage::disk('public')->path($this->immerse->thumbnail);
$video = Storage::disk('public')->path($this->getTempPath('.mp4',false));
if ($this->immerse->origin_video_url == '' || $this->immerse->origin_video_url == null) {
// 原官方临境是图片
$origin_image_path = Storage::disk('public')->path($this->immerse->origin_image_url);
if ($this->immerse->bgm == '' || $this->immerse->bgm == null) {
$audio = $upload_file;
}else{
$audio = Storage::disk('public')->path($this->getTempPath('.mp3'));
$bgm = Storage::disk('public')->path($this->immerse->bgm);
$cmd = $this->ffmpeg . ' -y ' .
' -i ' . escapeshellarg($upload_file) .
' -i ' . escapeshellarg($bgm) .
' -filter_complex "amix=inputs=2:duration=first" ' .
' -ar 48000 -ab 64k ' . escapeshellarg($audio);
if (!$this->execmd($cmd)) return ;
}
$drawtext = $this->getTextContentString($font);
$watermark = Storage::disk('public')->path('uploads/201/71/LOGO_eng.png');
//直接将音频替换
$cmd = $this->ffmpeg . ' -y ' .
' -loop 1 -i ' . escapeshellarg($origin_image_path) .
' -i ' . escapeshellarg($audio) .
' -i ' . escapeshellarg($watermark) .
' -filter_complex "[0:v]scale=' . $this->output_width . ':' . $this->output_height . ',setdar=dar=9/16,' . $drawtext .
' [text];[text][2:v]overlay=20:20[v]"' .
' -map [v] -map 1:0 ' .
' -c:v libx264 -bt 256k -r 25 -t ' . $duration .
' -ar 48000 -ac 2 -qmin 30 -qmax 60 -profile:v high -pix_fmt yuv420p -preset fast '.
escapeshellarg($video);
if (!$this->execmd($cmd)) return ;
}
if ($this->immerse->origin_image_url == '' || $this->immerse->origin_image_url == null) {
// 原官方临境是视频
$origin_video_path = Storage::disk('public')->path($this->immerse->origin_video_url);
$origin_mediainfo = $this->mediainfo($origin_video_path);
//用户录音超长,截取用户录音
if ($mediainfo['format']['duration'] > $origin_mediainfo['format']['duration']) {
if ($this->immerse->bgm == '' || $this->immerse->bgm == null) {
$audio = Storage::disk('public')->path($this->getTempPath('.mp3'));
$cmd = $this->ffmpeg . ' -y ' .
' -i ' . escapeshellarg($upload_file) .
' -ss 0 -t ' . escapeshellarg($origin_mediainfo['format']['duration']) .
' -ar 48000 -ab 64k '.escapeshellarg($audio);
if (!$this->execmd($cmd)) return 0;
}else{
$bgm = Storage::disk('public')->path($this->immerse->bgm);
$audio = Storage::disk('public')->path($this->getTempPath('.mp3'));
$cmd = $this->ffmpeg . ' -y ' .
' -i ' . escapeshellarg($upload_file) .
' -i ' . escapeshellarg($bgm) .
' -filter_complex "amix=inputs=2:duration=first" ' .
' -ss 0 -t ' . escapeshellarg($origin_mediainfo['format']['duration']) .
' -ar 48000 -ab 64k '.escapeshellarg($audio);
if (!$this->execmd($cmd)) return 0;
}
}else{
if ($this->immerse->bgm == '' || $this->immerse->bgm == null) {
$audio = $upload_file;
}else{
$audio = Storage::disk('public')->path($this->getTempPath('.mp3'));
$bgm = Storage::disk('public')->path($this->immerse->bgm);
$cmd = $this->ffmpeg . ' -y ' .
' -i ' . escapeshellarg($upload_file) .
' -i ' . escapeshellarg($bgm) .
' -filter_complex "amix=inputs=2:duration=first[a]" -map [a]' .
' -ar 48000 -ab 64k' . escapeshellarg($audio);
if (!$this->execmd($cmd)) return 0;
}
}
$drawtext = $this->getTextContentString($font);
$watermark = Storage::disk('public')->path('uploads/201/71/LOGO_eng.png');
//直接将音频替换
$cmd = $this->ffmpeg . ' -y ' .
' -i ' . escapeshellarg($origin_video_path) .
' -i ' . escapeshellarg($audio) .
' -i ' . escapeshellarg($watermark) .
' -filter_complex "[0:v]scale=' . $this->output_width . ':' . $this->output_height . ',setdar=dar=9/16,' . $drawtext .
' [text];[text][2:v]overlay=20:20[v]"' .
' -map [v] -map 1:0 ' .
' -c:v libx264 -bt 256k -r 25' .
' -ar 48000 -ac 2 -qmin 30 -qmax 60 -profile:v high -pix_fmt yuv420p -preset fast '.
escapeshellarg($video);
if (!$this->execmd($cmd)) return ;
}
}else{
// 1. 分析用户上传视频
$upload_file = Storage::disk('public')->path($this->immerse->upload_file);
$mediainfo = $this->mediainfo($upload_file);
// 记录媒体信息时长
$duration = $mediainfo['format']['duration'] ?: 0;
//
$end_wallpaper = Storage::disk('public')->path('uploads/201/71/end_wallpaper.png');
$avatar = Storage::disk('public')->path('uploads/201/71/thumbnail.png');
$font = Storage::disk('public')->path('uploads/201/71/arialuni.ttf');
//
// $user = User::query()->find($this->immerse->user_id);
// $signature = $user->nickname ?? $user->email ?? '2@qq.com';
//
// // 生成贴纸和签名
// $end_wallpaper = $this->wallpaperWithSignature($end_wallpaper, $avatar, $signature, $font);
//
// // 截取最后一帧做动画
// $last_frame_video = $this->getTempPath();
// $this->width = $width = $mediainfo['streams'][0]['width'];
// $height = $mediainfo['streams'][0]['height'];
// $size = $width . 'x' . $height;
// $time_length = 0.7;
// $r = 24;
// $frame_n = $mediainfo['streams'][0]['nb_frames'] - 2;
// $cmd = $this->ffmpeg . ' -y -i ' . escapeshellarg($upload_file) .
// " -f lavfi -i nullsrc=s={$size}:d={$time_length}:r={$r} -f lavfi -i aevalsrc=0:duration={$time_length}" .
// " -filter_complex \"[0:v]select='eq(n,{$frame_n})',setpts=PTS-STARTPTS[lastframe];[1:v][lastframe]overlay[v]\"" .
// ' -map [v] -map 2:a ' . escapeshellarg($last_frame_video);
// if (!$this->execmd($cmd)) return 0;
//
$drawtext = $this->getTextContentString($font);
$watermark = Storage::disk('public')->path('uploads/201/71/LOGO_eng.png');
// 截取中间帧作为视频封面
$frame = ceil($mediainfo['streams'][0]['nb_frames'] / 2);
$thumbnail = Storage::disk('public')->path($this->getTempPath('.jpg',false));
$cmd = $this->ffmpeg . ' -y ' .
' -i ' . escapeshellarg($upload_file) .
' -i ' . escapeshellarg($watermark) .
' -filter_complex "[0:v]select=\'eq(n,' . $frame . ')\'[img]" ' .
' -map [img]'.
' -frames:v 1 -s ' . $this->output_width . 'x' . $this->output_height .' -preset superfast '.
escapeshellarg($thumbnail);
if (!$this->execmd($cmd)) return ;
$video = Storage::disk('public')->path($this->getTempPath('.mp4',false));
// 没有背景音
if ($this->immerse->bgm == '' || $this->immerse->bgm == null) {
$cmd = $this->ffmpeg . ' -y ' .
' -i ' . escapeshellarg($upload_file) .
' -i ' . escapeshellarg($watermark) .
' -filter_complex "[0:v]scale=' . $this->output_width . ':' . $this->output_height . ',setdar=dar=9/16,' . $drawtext .
' [text];[text][1:v]overlay=20:20"' .
' -c:v libx264 -bt 256k -r 25' .
' -c:a copy -qmin 30 -qmax 60 -profile:v high -pix_fmt yuv420p -preset fast ' .
escapeshellarg($video);
}else{
$bgm = Storage::disk('public')->path($this->immerse->bgm);
$cmd = $this->ffmpeg . ' -y ' .
' -i ' . escapeshellarg($upload_file) .
' -i ' . escapeshellarg($watermark) .
' -i ' . escapeshellarg($bgm) .
' -filter_complex "[0:v]scale=' . $this->output_width . ':' . $this->output_height . ',setdar=dar=9/16,' . $drawtext .
' [text];[text][1:v]overlay=20:20[v];' .
' amix=inputs=2:duration=first[a] " -map [v] -map [a]' .
' -c:v libx264 -bt 256k -r 25' .
' -ar 44100 -ac 2 -qmin 30 -qmax 60 -profile:v high -pix_fmt yuv420p -preset fast ' .
escapeshellarg($video);
}
if (!$this->execmd($cmd)) return ;
}
// 全部合成以后创建 临境
$video_info = $this->mediainfo($video);
$this->immerse->url = Str::of($video)->replace(Storage::disk('public')->path(''), '');
$this->immerse->thumbnail = $thumbnail;
$this->immerse->state = 1;
$this->immerse->duration = $video_info['format']['duration'] ?? 0;
$this->immerse->size = $video_info['format']['size'];
$this->immerse->save();
return ;
}
public function mediainfo($file)
{
$cmd = $this->ffprobe . ' -v quiet -print_format json -show_format -show_streams ' . escapeshellarg($file);
$output = $this->execmd($cmd);
$data = json_decode($output, true);
if (json_last_error() === JSON_ERROR_UTF8) {
$output = mb_convert_encoding($output, "UTF-8");
$data = json_decode($output, true);
}
return $data;
}
public function execmd($cmd, $update_progress = false) {
echo $cmd . "\n". "\n". "\n";
$descriptorspec = array(
1 => array("pipe", "w"), // 标准输出,子进程向此管道中写入数据
);
$process = proc_open("{$cmd} 2>&1", $descriptorspec, $pipes);
if (is_resource($process)) {
$error0 = '';
$error1 = '';
$stdout = '';
while (!feof($pipes[1])) {
$line = fgets($pipes[1], 150);
$stdout .= $line;
if ($line) {
//记录错误
$error0 = $error1;
$error1 = $line;
if ($update_progress &&
false !== strpos($line, 'size=') &&
false !== strpos($line, 'time=') &&
false !== strpos($line, 'bitrate='))
{
//记录进度 size= 3142kB time=00:00:47.22 bitrate= 545.1kbits/s
$line = explode(' ', $line);
$time = null;
foreach ($line as $item) {
$item = explode('=', $item);
if (isset($item[0]) && isset($item[1]) && $item[0] == 'time') {
$time = $item[1];
break;
}
}
}
}
}
// 切记:在调用 proc_close 之前关闭所有的管道以避免死锁。
fclose($pipes[1]);
$exitedcode = proc_close($process);
if ($exitedcode === 0) {
return $stdout;
} else {
$error = trim($error0,"\n") . ' '. trim($error1,"\n");
// LogUtil::write(array("cmd:{$cmd}", "errno:{$exitedcode}", "stdout:{$stdout}"), __CLASS__);
// ErrorUtil::triggerErrorMsg($error, $exitedcode);
}
} else {
// return ErrorUtil::triggerErrorMsg('proc_open error');
}
}
/**
* 获取输出临时文件名
* @param string $ext
* @param bool $is_temp
* @return string
*/
public function getTempPath($ext = '.mp4',$is_temp = true)
{
$filename = "/output_" . time() . rand(0, 10000);
$prefix = $is_temp ? 'temp/' : 'video/';
$hash_hex = md5($filename);
// 16进制表示的字符串一共32字节,表示16个二进制字节。
// 前16个字符用来第一级求摸,后16个用做第二级
$hash_hex_l1 = substr($hash_hex, 0, 8);
$hash_hex_l2 = substr($hash_hex, 8, 8);
$dir_l1 = hexdec($hash_hex_l1) % 256;
$dir_l2 = hexdec($hash_hex_l2) % 512;
$dir = $prefix . $dir_l1 . '/' . $dir_l2;
if( !Storage::disk('public')->exists($dir)) Storage::disk('public')->makeDirectory($dir);
return $dir . $filename . $ext;
}
/**
* 获取图像文件类型
* @param $img_name
* @return string
*/
public function getImageType($img_name)
{
if (preg_match("/\.(jpg|jpeg|gif|png)$/i", $img_name, $matches)){
$type = strtolower($matches[1]);
}else{
$type = "string";
}
return $type;
}
/**
* 获取圆形头像
* @param $img
* @param int $dst_w
* @param int $dst_h
* @return resource
*/
public function getCircleAvatar($img, $dst_w = 96, $dst_h = 96)
{
$w = 130;
$h = 130;
$src = imagecreatetruecolor($dst_w, $dst_h);
imagecopyresized($src, $img, 0, 0, 0, 0, $dst_w, $dst_h, $w, $h);
$newpic = imagecreatetruecolor($dst_w, $dst_h);
imagealphablending($newpic, false);
imagecopyresampled($newpic, $img, 0, 0, 0, 0, $dst_w, $dst_h, $w, $h);
$mask = imagecreatetruecolor($dst_w, $dst_h);
$transparent = imagecolorallocate($mask, 255, 0, 0);
imagecolortransparent($mask,$transparent);
imagefilledellipse($mask, $dst_w / 2, $dst_h / 2, $dst_w, $dst_h, $transparent);
$red = imagecolorallocate($mask, 0, 0, 0);
imagecopymerge($newpic, $mask, 0, 0, 0, 0, $dst_w, $dst_h, 100);
imagecolortransparent($newpic,$red);
imagesavealpha($newpic,true);
imagefill($newpic, 0, 0, $red);
imagedestroy($mask);
return $newpic;
}
/**
* 多图融合
* @param $end_wallpaper
* @param $thumbnail
* @return resource
*/
public function imagesMerge($end_wallpaper, $thumbnail) {
$end_wallpaper = imagecreatefrompng($end_wallpaper);
$background = imagecreatefrompng(Storage::disk('public')->path('ffmpeg/background.png'));
imagesavealpha($background,true);
$temp_wallpaper = imagecreatetruecolor(350, 204);
// $color = imagecolorallocate($temp_wallpaper, 0xd0, 0xcd, 0xcc);
$color = imagecolorallocate($temp_wallpaper, 0xDC, 0x14, 0x3C);
imagefill($temp_wallpaper, 0, 0, $color);
imageColorTransparent($temp_wallpaper, $color);
imagecopyresampled($temp_wallpaper, $end_wallpaper, 0, 0, 0, 0, imagesx($temp_wallpaper), imagesy($temp_wallpaper), imagesx($end_wallpaper), imagesy($end_wallpaper));
imagecopymerge($background, $temp_wallpaper, 0, 0, 0, 0, imagesx($temp_wallpaper), imagesy($temp_wallpaper), 60);
imagecopymerge($background, $thumbnail, 127, 26, 0, 0, imagesx($thumbnail), imagesy($thumbnail), 100);
return $background;
}
/**
* 贴纸和签名
* @param $end_wallpaper
* @param $thumbnail
* @param $signature
* @param $font
* @return string
*/
public function wallpaperWithSignature($end_wallpaper, $thumbnail, $signature, $font) {
$_imagetype = $this->getImageType($thumbnail);
$_img = null;
switch ($_imagetype) {
case 'gif':
if (function_exists('imagecreatefromgif')) {
$_img = imagecreatefromgif($thumbnail);
}
break;
case 'jpg':
case 'jpeg':
$_img = imagecreatefromjpeg($thumbnail);
break;
case 'png':
$_img = imagecreatefrompng($thumbnail);
break;
default:
$_img = imagecreatefromstring($thumbnail);
break;
}
$width = 130;
$height = 130;
$_width = 130;
$_height = 130;
if(is_resource($_img)){
$_width = imagesx($_img);
$_height = imagesy($_img);
}
$bite = $_width / $_height;
if($_width > $_height){
if($_width > $width){
$height = round($width / $bite);
}
}else{
if($_height > $height){
$width = round($height * $bite);
}
}
$tmpimg = imagecreatetruecolor($width,$height);
if(function_exists('imagecopyresampled')) {
imagecopyresampled($tmpimg, $_img, 0, 0, 0, 0, $width, $height, $_width, $_height);
} else {
imagecopyresized($tmpimg, $_img, 0, 0, 0, 0, $width, $height, $_width, $_height);
}
if(is_resource($_img)) imagedestroy($_img);
$_img = $this->getCircleAvatar($tmpimg);
if(is_resource($tmpimg)) imagedestroy($tmpimg);
$wp = $this->imagesMerge($end_wallpaper, $_img);
// $white = imagecolorallocate($wp, 0xd0, 0xcd, 0xcc);
$white = imagecolorallocate($wp, 0xDC, 0x14, 0x3C); //fixme 字体颜色
imagettftext($wp, 20, 0, 75, 240, $white, $font, $signature);
$dst = Storage::disk('public')->path($this->getTempPath('.png'));
imagepng($wp, $dst);
if(is_resource($end_wallpaper)) imagedestroy($end_wallpaper);
if(is_resource($_img)) imagedestroy($_img);
return $dst;
}
public function getTextContentString($font)
{
$components = $this->immerse->temp()->first()->components()->get();
$drawtext = '';
foreach ($components as $component) {
switch ($component->name){
case 'one_poem':
$content = $this->immerse->poem->content;
$text_file = Storage::disk('public')->path($this->getTempPath('.txt'));
file_put_contents($text_file, $content);
$text_color = $component->text_color ?? 'white';
$text_bg_color = $component->text_bg_color ?? '0xd0cdcc';
$opacity = $component->opacity ? $component->opacity / 100 : '0.5';
$drawtext .= 'drawtext="'.
'fontfile=' . escapeshellarg($font) . ':' .
'textfile=' . escapeshellarg($text_file) . ':' .
'fontsize=' . $this->calcFontSize($component->font_size) . ':' .
'fontcolor=' . $text_color . '@1.0:' .
'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' .
'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' .
'box=1:boxborderw='. $this->calcBorderSize($component->font_size) . ':' .
'boxcolor=' . $text_bg_color . '@' . $opacity . '", ';
break;
case 'every_poem':
break;
case 'weather':
$content = '多云';
$text_color = $component->text_color ?? 'white';
$text_bg_color = $component->text_bg_color ?? '0xd0cdcc';
$opacity = $component->opacity ? $component->opacity / 100 : '0.5';
$drawtext .= 'drawtext="'.
'fontfile=' . escapeshellarg($font) . ':' .
'text=' . escapeshellarg($content) . ':' .
'fontsize=' . $this->calcFontSize($component->font_size) . ':' .
'fontcolor=' . $text_color . '@1.0:' .
'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' .
'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' .
'box=1:boxborderw='. $this->calcBorderSize($component->font_size) . ':' .
'boxcolor=' . $text_bg_color . '@' . $opacity . '", ';
break;
case 'date':
$content = Carbon::now()->format('Y年m月d日H时');
$text_color = $component->text_color ?? 'white';
$text_bg_color = $component->text_bg_color ?? '0xd0cdcc';
$opacity = $component->opacity ? $component->opacity / 100 : '0.5';
$drawtext .= 'drawtext="'.
'fontfile=' . escapeshellarg($font) . ':' .
'text=' . escapeshellarg($content) . ':' .
'fontsize=' . $this->calcFontSize($component->font_size) . ':' .
'fontcolor=' . $text_color . '@1.0:' .
'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' .
'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' .
'box=1:boxborderw='. $this->calcBorderSize($component->font_size) . ':' .
'boxcolor=' . $text_bg_color . '@' . $opacity . '", ';
break;
case 'feel':
$content = $this->immerse->content ?: '这里是发表临境有感';
$text_color = $component->text_color ?? 'white';
$text_bg_color = $component->text_bg_color ?? '0xd0cdcc';
$opacity = $component->opacity ? $component->opacity / 100 : '0.5';
$drawtext .= 'drawtext="'.
'fontfile=' . escapeshellarg($font) . ':' .
'text=' . escapeshellarg($content) . ':' .
'fontsize=' . $this->calcFontSize($component->font_size) . ':' .
'fontcolor=' . $text_color . '@1.0:' .
'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' .
'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' .
'box=1:boxborderw='. $this->calcBorderSize($component->font_size) . ':' .
'boxcolor=' . $text_bg_color . '@' . $opacity . '", ';
break;
}
}
return rtrim($drawtext,', ');
}
/**
* @param $width
* @param $content
* @return float
*/
public function calcFontSize($width)
{
return ceil($this->output_width / 360 * $width);
}
/**
* @param $width
* @param $content
* @return float
*/
public function calcBorderSize($width)
{
return ceil($width / 12 * 10);
}
}
<?php
/**
* Created by PhpStorm.
* User: lishuai
* Date: 2022/2/15
* Time: 4:23 PM
*/
namespace App\Payment;
use App\Models\MembershipGood;
use App\Models\Order;
use App\Models\User;
use App\Models\UserProfile;
use Carbon\Carbon;
use GuzzleHttp\Client;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
class ApplePayment implements PaymentInterface
{
const IS_SANDBOX = true;
public function __construct()
{
}
public function prepare(Order $order)
{
// 查询订单对应的产品id
}
}
\ No newline at end of file
<?php
/**
* Created by PhpStorm.
* User: lishuai
* Date: 2022/2/15
* Time: 4:23 PM
*/
namespace App\Payment;
use App\Models\MembershipGood;
use App\Models\Order;
use App\Models\User;
use App\Models\UserProfile;
use Carbon\Carbon;
use GuzzleHttp\Client;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
class GooglePayment implements PaymentInterface
{
const IS_SANDBOX = true;
public function __construct()
{
}
public function prepare(Order $order)
{
// 查询订单对应的产品id
}
}
\ No newline at end of file
......@@ -19,8 +19,12 @@ class PaymentFactory
return new WechatPayment();
case 'paypal':
return new PaypalPayment();
case 'google':
return new GooglePayment();
case 'apple':
return new ApplePayment();
default:
throw new \Exception('未知的支付方式');
return new \Exception('未知的支付方式');
}
}
}
\ No newline at end of file
......
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AlterImmerse2Table extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('immerse', function (Blueprint $table) {
$table->string('upload_file')->after('type')->comment('上传资源路径');
$table->string('origin_video_url')->after('size')->comment('原始视频路径');
$table->string('origin_image_url')->after('origin_video_url')->comment('原始图片路径');
$table->string('tags')->after('comment')->default('')->comment('标签');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropColumns('immerse', ['upload_file', 'origin_video_url', 'origin_image_url', 'tags']);
}
}
......@@ -23,7 +23,7 @@ Route::prefix('v1')->namespace('App\Http\Controllers\V1')->group(function (Route
/** 社会化用户登录*/
$api->any('auth/{service}/callback', 'AuthController@apiHandleProviderCallback');
/** */
/** 增加观看次数 */
$api->get('/addview/{id}', 'ImmerseController@addview');
});
......