李帅

1.首页接口实现。

2.支持用户端上传。
......@@ -3,6 +3,7 @@
namespace App\Http\Controllers\V1;
use App\Http\Controllers\Controller;
use App\Models\Immerse;
use App\Models\PackPoem;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
......@@ -17,8 +18,11 @@ class HomeController extends Controller
*/
public function index()
{
//
return Response::success();
// admin video
// user video
return Response::success(Immerse::query()->get());
}
......
......@@ -3,7 +3,12 @@
namespace App\Http\Controllers\V1;
use App\Http\Controllers\Controller;
use App\Models\Immerse;
use App\Models\UserMakeVideo;
use App\Jobs\UserMakeVideo as MakeVideo;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Jiannei\Response\Laravel\Support\Facades\Response;
class ImmerseController extends Controller
{
......@@ -21,16 +26,46 @@ class ImmerseController extends Controller
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
* @throws \Illuminate\Validation\ValidationException
* @return \Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
//todo 发布流程
// if type == 1
// 写入audio-show表,写入immerse表,发送异步转码任务,
// if type == 2
// 写入video-show表,写入immerse表,发送异步转码合成视频水印任务
$validator = Validator::make($request->all(),[
'video_url' => 'required|string',
'video_id' => 'required',
'content' => 'sometimes',
'weather' => 'sometimes',
'thumbnail_url' => 'sometimes',
]);
if ($validator->fails()){
return Response::fail('',500,$validator->errors());
}
$validated = $validator->validated();
$immerse = Immerse::query()->find($request->video_id);
$video = UserMakeVideo::query()->create([
'poem_id' => $immerse->poem_id,
'type' => $immerse->type,
'video_url' => $validated['video_url'],
'image_url' => $immerse->image_url,
'bg_music' => $immerse->bg_music,
'bgm_url' => $immerse->bgm_url,
'feel' => $validated['content'],
'weather' => $validated['weather'],
'temp_id' => $immerse->temp_id,
'thumbnail' => $validated['thumbnail_url'] ? 1 : 0,
'thumbnail_url' => $validated['thumbnail_url'],
]);
// 添加至队列
MakeVideo::dispatch($video);
return Response::created();
}
/**
......
......@@ -37,7 +37,7 @@ class SettingController extends Controller
return Response::success($array);
}
public function upload(Request $request)
public function uploadImage(Request $request)
{
$validator = Validator::make($request->all(),[
'image' => 'required|mimes:jpeg,png,bmp,gif'
......@@ -59,12 +59,41 @@ class SettingController extends Controller
$dir_l2 = hexdec($hash_hex_l2) % 512;
$dir = 'uploads/'. $dir_l1. '/' . $dir_l2;
if( !Storage::disk('public')->exists($dir)) {
if( !Storage::disk('public')->exists($dir)) Storage::disk('public')->makeDirectory($dir);
Storage::disk('public')->makeDirectory($dir);
$file = $request->file('image')->store($dir,'public');
return Response::success([
'relative_path' => $file,
'absolute_path' => Storage::disk('public')->url($file),
]);
}
public function uploadVideo(Request $request)
{
$validator = Validator::make($request->all(),[
'video' => 'required|mimes:mp4,flv,mov,avi'
]);
if ($validator->fails()){
return Response::fail('',500,$validator->errors());
}
$file = $request->file('image')->store($dir,'public');
$hashName = $request->file('video')->hashName();
$hash_hex = md5($hashName);
// 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 = 'uploads/'. $dir_l1. '/' . $dir_l2;
if( !Storage::disk('public')->exists($dir)) Storage::disk('public')->makeDirectory($dir);
$file = $request->file('video')->store($dir,'public');
return Response::success([
'relative_path' => $file,
......
......@@ -604,7 +604,7 @@ class MakeVideo implements ShouldQueue
foreach ($components as $component) {
switch ($component->name){
case 'one_poem':
$content = $this->adminMakeVideo->poem->content . PHP_EOL;
$content = $this->adminMakeVideo->poem->content;
$text_file = $this->getTempPath('txt');
file_put_contents($text_file, $content);
......@@ -619,7 +619,7 @@ class MakeVideo implements ShouldQueue
'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=1:boxcolor=' . $text_bg_color . '@' . $opacity . '", ';
break;
case 'every_poem':
......
<?php
namespace App\Jobs;
use App\Models\AdminMakeVideo;
use App\Models\Immerse;
use App\Models\User;
use App\Models\VideoTemp;
use Carbon\Carbon;
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\Log;
use Illuminate\Support\Facades\Storage;
class UserMakeVideo implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $adminMakeVideo;
protected $ffmpeg;
protected $ffprobe;
protected $ffplay;
protected $width;
/**
* Create a new job instance.
* @param AdminMakeVideo $adminMakeVideo
* @return void
*/
public function __construct(\App\Models\UserMakeVideo $adminMakeVideo)
{
$this->adminMakeVideo = $adminMakeVideo;
$this->ffmpeg = env('FFMPEG_CMD');
$this->ffprobe = env('FFPROBE_CMD');
$this->ffplay = env('FFPLAY_CMD');
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$adminMakeVideo = $this->adminMakeVideo;
$file = Storage::disk('public')->path($adminMakeVideo->video_url);
$is_bgm = $adminMakeVideo->bg_music;
$bgm = Storage::disk('public')->path($adminMakeVideo->bgm_url);
// 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;
if ($media_info['streams'][0]['codec_type'] !== 'video') {
Log::channel('daily')->error('视频没有video track');
return;
}
// 2. 判断是否有视频原音,没有原音用背景音,没有背景音则混入anullsrc
if ( $media_info['format']['nb_streams'] >= 2 ){ /** 音频视频轨都有 */
if ($is_bgm){
// 有背景音 融合
$audio = $this->getTempPath('.mp3');
$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;
$audio_input = ' -i ' . escapeshellarg($audio);
$audio_filter = '[3:a]';
}else{
// 没有背景音
$audio_input = '';
$audio_filter = '[0:1]';
}
}elseif ( $media_info['format']['nb_streams'] == 1 ){
$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;
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;
}
$audio_input = ' -i ' . escapeshellarg($audio);
$audio_filter = '[3:a]';
}else{ /** 音频视频轨都没有 */
Log::channel('daily')->error('视频没有video track');
return;
}
$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";
$user = User::query()->find($this->adminMakeVideo->user_id);
$signature = $user->nickname;
// 生成贴纸和签名
$end_wallpaper = $this->wallpaperWithSignature($end_wallpaper, $thumbnail, $signature, $font);
// 截取最后一帧
$last_frame_video = $this->getTempPath();
$this->width = $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;
$signature_x = 0;
$signature_y = -20;
$animate = $this->makeAnimate($last_frame_video, $end_wallpaper, '', $signature_x, $signature_y, $font);
$watermark = Storage::disk('public')->path('ffmpeg/LOGO_eng.png');
$video = $this->getTempPath('.mp4',false);
$cmd = $this->ffmpeg . ' -y '.
' -i ' . escapeshellarg($file).
' -i ' . escapeshellarg($animate).
' -i ' . escapeshellarg($watermark).
$audio_input .
' -filter_complex "[0:0] ' .
$this->getTextContentString().
' [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($video);
if (!$this->execmd($cmd)) return;
// $video = $this->getTempPath();
// if ( $adminMakeVideo->thumbnail == 1 && $adminMakeVideo->thumbnail_url){
// $thumbnail = Storage::disk('public')->path($adminMakeVideo->thumbnail_url);
// }else{
// $thumbnail = $last_frame_video;
// }
// $cmd = $this->ffmpeg. ' -y'.
// ' -i ' . escapeshellarg($video_temp).
// ' -i ' . escapeshellarg($thumbnail).
// ' -map 1 -map 0 -c copy -disposition:0 attached_pic '.
// escapeshellarg($video);
// $this->execmd($cmd);
// 全部合成以后创建 临境
$video_info = $this->mediainfo($video);
Immerse::query()->create([
'user_id' => $this->adminMakeVideo->user_id,
'title' => '',
'content' => $this->adminMakeVideo->feel,
'url' => $video,
'type' => $this->adminMakeVideo->type == 1 ? 2 : 1,
'duration' => $video_info['format']['duration'],
'size' => $video_info['format']['size'],
'poem_id' => $this->adminMakeVideo->poem_id,
'temp_id' => $this->adminMakeVideo->temp_id,
'thumbnail' => $thumbnail,
'bgm' => $this->adminMakeVideo->bgm_url,
]);
}
/**
* 获取圆形头像
* @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;
}
}
/**
* 用最后一帧和贴纸制作动画
* @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;
}
}
/**
* 获取视频宽度
* @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;
}
}
/**
* 获取视频高度
* @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;
}
}
/**
* 获取视频帧数
* @param $file
* @param bool $cache
* @return null
*/
public function getVideoFrameNum($file, $cache = true) {
return $this->getFirstVideoTrackOption($file, $option = 'nb_frames', $cache);
}
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;
}
/***
* 获取视频信息(配合ffprobe)
* @param $file
* @param bool $cache
* @return mixed
*/
public function mediainfo($file, $cache = true) {
global $_mediainfo;
$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;
}
/**
* 获取输出临时文件名
* @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 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(
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 $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 = "./output_new_end_wallpaper.png";
$dst = Storage::disk('public')->path('ffmpeg') . "/output_new_end_wallpaper.png";
imagepng($wp, $dst);
if(is_resource($end_wallpaper)) imagedestroy($end_wallpaper);
if(is_resource($_img)) imagedestroy($_img);
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;
}
}
public function getTextContentString()
{
$components = $this->adminMakeVideo->temp()->first()->components()->get();
$font = Storage::disk('public')->path('ffmpeg/arialuni.ttf');
$drawtext = '';
foreach ($components as $component) {
switch ($component->name){
case 'one_poem':
$content = $this->adminMakeVideo->poem->content;
$text_file = $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,$content) . ':' .
'fontcolor=' . $text_color . '@1.0:' .
'x=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][0]) . ':' .
'y=' . escapeshellarg(VideoTemp::POSITION_FFMPEG[$component->position][1]) . ':' .
'box=1:boxborderw=1: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,$content) . ':' .
'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 . '", ';
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,$content) . ':' .
'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 . '", ';
break;
case 'feel':
$content = $this->adminMakeVideo->feel;
$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,$content) . ':' .
'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 . '", ';
break;
}
}
return rtrim($drawtext,', ');
}
/**
* @param $width
* @param $content
* @return float
*/
public function calcFontSize($width, $content)
{
$max_len = 1;
foreach (explode("\n",$content) as $item){
if (mb_strlen($item) > $max_len){
$max_len = mb_strlen($item);
}
}
return ceil($this->width * $width / 10 / $max_len);
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class UserMakeVideo extends Model
{
use HasFactory;
protected $guarded = [''];
}
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUserMakeVideosTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('user_make_videos', function (Blueprint $table) {
$table->id();
$table->unsignedInteger('user_id')->comment('用户id');
$table->string('poem_id')->default('')->comment('一言id');
$table->unsignedTinyInteger('type')->comment('类型');
$table->string('video_url')->nullable()->comment('视频地址');
$table->string('images_url')->nullable()->comment('图片地址');
$table->unsignedTinyInteger('bg_music')->comment('是否背景音');
$table->string('bgm_url')->nullable()->comment('背景音地址');
$table->text('feel')->nullable()->comment('有感');
$table->text('weather')->nullable()->comment('天气');
$table->string('temp_id')->default('')->comment('模板id');
$table->unsignedTinyInteger('thumbnail')->comment('封面图');
$table->string('thumbnail_url')->nullable()->comment('封面图地址');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('user_make_videos');
}
}
......@@ -59,6 +59,9 @@ Route::prefix('v1')->namespace('App\Http\Controllers\V1')->group(function (Route
/** 会员页 */
$api->apiResource('/membership', 'MembershipController');
/** 文件上传 */
$api->post('/upload/image', 'SettingController@upload');
/** 图片上传 */
$api->post('/upload/image', 'SettingController@uploadImage');
/** 视频上传 */
$api->post('/upload/video', 'SettingController@uploadVideo');
});
\ No newline at end of file
......