Toggle navigation
Toggle navigation
This project
Loading...
Sign in
OnePoem
/
OnePoem-Server
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Snippets
Network
Create a new issue
Builds
Commits
Issue Boards
Authored by
李帅
2022-05-11 14:35:31 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
6ac4c130398ef6520899b05ae07c46c68b8abbf0
6ac4c130
1 parent
e3c595db
1.后台可上传图文内容。
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
1192 additions
and
36 deletions
app/Console/Commands/DevFFmpeg.php
app/Http/Controllers/V1/ImmerseController.php
app/Jobs/MakeImages.php
app/Jobs/UserMakeImages.php
app/Models/AdminMakeVideo.php
app/Console/Commands/DevFFmpeg.php
View file @
6ac4c13
...
...
@@ -2,6 +2,7 @@
namespace
App\Console\Commands
;
use
App\Jobs\MakeImages
;
use
App\Models\AdminMakeVideo
;
use
App\Models\Immerse
;
use
App\Models\VideoTemp
;
...
...
@@ -50,6 +51,71 @@ 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/'
;
...
...
app/Http/Controllers/V1/ImmerseController.php
View file @
6ac4c13
...
...
@@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
use
App\Models\Immerse
;
use
App\Models\UserMakeVideo
;
use
App\Jobs\UserMakeVideo
as
MakeVideo
;
use
App\Jobs\UserMakeImages
as
MakeImages
;
use
Illuminate\Http\Request
;
use
Illuminate\Support\Facades\Storage
;
use
Illuminate\Support\Facades\Validator
;
...
...
@@ -34,8 +35,9 @@ class ImmerseController extends Controller
public
function
store
(
Request
$request
)
{
$validator
=
Validator
::
make
(
$request
->
all
(),[
'video_url'
=>
'required|string'
,
'video_id'
=>
'required'
,
'item_url'
=>
'required|string'
,
'item_id'
=>
'required'
,
'type'
=>
'required'
,
'content'
=>
'sometimes'
,
'weather'
=>
'sometimes'
,
'thumbnail_url'
=>
'sometimes'
,
...
...
@@ -47,32 +49,54 @@ class ImmerseController extends Controller
$validated
=
$validator
->
validated
();
if
(
Str
::
contains
(
$validated
[
'
video
_url'
],
'//'
)){
$
video
_url
=
''
;
}
elseif
(
Str
::
contains
(
$validated
[
'
video
_url'
],
'/storage/app/public/'
)){
$
video_url
=
$validated
[
'video
_url'
];
if
(
Str
::
contains
(
$validated
[
'
item
_url'
],
'//'
)){
$
item
_url
=
''
;
}
elseif
(
Str
::
contains
(
$validated
[
'
item
_url'
],
'/storage/app/public/'
)){
$
item_url
=
$validated
[
'item
_url'
];
}
else
{
$video_url
=
Storage
::
disk
(
'public'
)
->
path
(
$validated
[
'video_url'
]);
$item_url
=
Storage
::
disk
(
'public'
)
->
path
(
$validated
[
'item_url'
]);
}
$immerse
=
Immerse
::
query
()
->
find
(
$request
->
item_id
);
if
(
$validated
[
'type'
]
==
1
){
// 图文音频
$create
=
UserMakeVideo
::
query
()
->
create
([
'poem_id'
=>
$immerse
->
poem_id
,
'type'
=>
$immerse
->
type
,
'video_url'
=>
$item_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'
],
]);
// 添加至队列
MakeImages
::
dispatch
(
$create
);
}
else
{
// 视频
$create
=
UserMakeVideo
::
query
()
->
create
([
'poem_id'
=>
$immerse
->
poem_id
,
'type'
=>
$immerse
->
type
,
'video_url'
=>
$item_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
(
$create
);
}
$immerse
=
Immerse
::
query
()
->
find
(
$request
->
video_id
);
$video
=
UserMakeVideo
::
query
()
->
create
([
'poem_id'
=>
$immerse
->
poem_id
,
'type'
=>
$immerse
->
type
,
'video_url'
=>
$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
();
}
...
...
app/Jobs/MakeImages.php
View file @
6ac4c13
...
...
@@ -3,12 +3,16 @@
namespace
App\Jobs
;
use
App\Models\AdminMakeVideo
;
use
App\Models\Immerse
;
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\Storage
;
class
MakeImages
implements
ShouldQueue
{
...
...
@@ -22,6 +26,10 @@ class MakeImages implements ShouldQueue
protected
$ffplay
;
protected
$width
;
protected
$height
;
/**
* Create a new job instance.
* @param AdminMakeVideo $adminMakeVideo
...
...
@@ -43,9 +51,513 @@ class MakeImages implements ShouldQueue
*/
public
function
handle
()
{
//思路:
// if 有背景音 多张图合成视频,时长为音频时长,音频加入背景音
// else 没有背景音,单图一张,输出为单图。
$watermark
=
Storage
::
disk
(
'public'
)
->
path
(
'ffmpeg/LOGO_eng.png'
);
$image
=
Storage
::
disk
(
'public'
)
->
path
(
$this
->
adminMakeVideo
->
images_url
);
$media_info
=
$this
->
mediainfo
(
$image
);
$this
->
width
=
$width
=
$media_info
[
'streams'
][
0
][
'width'
];
$this
->
height
=
$height
=
$media_info
[
'streams'
][
0
][
'height'
];
if
(
$this
->
adminMakeVideo
->
type
==
2
&&
$this
->
adminMakeVideo
->
bg_music
==
0
){
// 没有背景音,单图一张,输出为单图。
$output
=
$this
->
getTempPath
(
'.png'
,
false
);
$cmd
=
$this
->
ffmpeg
.
' -y '
.
' -i '
.
escapeshellarg
(
$image
)
.
' -i '
.
escapeshellarg
(
$watermark
)
.
' -filter_complex "[0:0] '
.
$this
->
getTextContentString
()
.
' [text];[text]'
.
' [1:0]overlay=20:20" '
.
escapeshellarg
(
$output
);
if
(
!
$this
->
execmd
(
$cmd
))
return
;
// 全部合成以后创建 临境
$video_info
=
$this
->
mediainfo
(
$output
);
$create
=
[
'user_id'
=>
1
,
'title'
=>
''
,
'content'
=>
$this
->
adminMakeVideo
->
feel
,
'url'
=>
$output
,
'type'
=>
$this
->
adminMakeVideo
->
type
==
1
?
2
:
1
,
'duration'
=>
0
,
'size'
=>
$video_info
[
'format'
][
'size'
],
'poem_id'
=>
$this
->
adminMakeVideo
->
poem_id
,
'temp_id'
=>
$this
->
adminMakeVideo
->
temp_id
,
'thumbnail'
=>
''
,
'bgm'
=>
$this
->
adminMakeVideo
->
bgm_url
,
];
}
else
{
$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
=
"一言 · 官方出品"
;
// 生成贴纸和签名
$end_wallpaper
=
$this
->
wallpaperWithSignature
(
$end_wallpaper
,
$thumbnail
,
$signature
,
$font
);
// 有背景音 单图合成视频,时长为音频时长,音频加入背景音
$bgm
=
Storage
::
disk
(
'public'
)
->
path
(
$this
->
adminMakeVideo
->
bgm_url
);
// 制作最后一帧
$size
=
$this
->
width
.
'x'
.
$this
->
height
;
$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
);
if
(
!
$this
->
execmd
(
$cmd
))
return
;
// 利用最后一帧制作动画
$signature_x
=
0
;
$signature_y
=
-
20
;
$animate
=
$this
->
makeAnimate
(
$last_frame_video
,
$end_wallpaper
,
''
,
$signature_x
,
$signature_y
,
$font
);
$output
=
$this
->
getTempPath
(
'.mp4'
,
false
);
$cmd
=
$this
->
ffmpeg
.
' -y '
.
' -i '
.
escapeshellarg
(
$image
)
.
' -i '
.
escapeshellarg
(
$watermark
)
.
' -i '
.
escapeshellarg
(
$bgm
)
.
' -i '
.
escapeshellarg
(
$animate
)
.
' -filter_complex "[0:0] '
.
$this
->
getTextContentString
()
.
'[text];[text][1:0]overlay=20:20[water];'
.
'[water][2:a][3:v][3:a]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
(
$output
);
if
(
!
$this
->
execmd
(
$cmd
))
return
;
// 全部合成以后创建 临境
$video_info
=
$this
->
mediainfo
(
$output
);
$create
=
[
'user_id'
=>
1
,
'title'
=>
''
,
'content'
=>
$this
->
adminMakeVideo
->
feel
,
'url'
=>
$output
,
'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'
=>
''
,
'bgm'
=>
$this
->
adminMakeVideo
->
bgm_url
,
];
}
Immerse
::
query
()
->
create
(
$create
);
}
/***
* 获取视频信息(配合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');
}
}
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: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
/
100
/
$max_len
);
}
/**
* 贴纸和签名
* @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
=
$this
->
getTempPath
(
'.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
;
}
/**
* 获取圆形头像
* @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 $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
;
}
}
}
...
...
app/Jobs/UserMakeImages.php
0 → 100644
View file @
6ac4c13
<?php
namespace
App\Jobs
;
use
App\Models\AdminMakeVideo
;
use
App\Models\Immerse
;
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\Storage
;
class
UserMakeImages
implements
ShouldQueue
{
use
Dispatchable
,
InteractsWithQueue
,
Queueable
,
SerializesModels
;
public
$adminMakeVideo
;
protected
$ffmpeg
;
protected
$ffprobe
;
protected
$ffplay
;
protected
$width
;
protected
$height
;
/**
* Create a new job instance.
* @param AdminMakeVideo $adminMakeVideo
* @return void
*/
public
function
__construct
(
AdminMakeVideo
$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
()
{
$watermark
=
Storage
::
disk
(
'public'
)
->
path
(
'ffmpeg/LOGO_eng.png'
);
$image
=
Storage
::
disk
(
'public'
)
->
path
(
$this
->
adminMakeVideo
->
images_url
);
$media_info
=
$this
->
mediainfo
(
$image
);
$this
->
width
=
$width
=
$media_info
[
'streams'
][
0
][
'width'
];
$this
->
height
=
$height
=
$media_info
[
'streams'
][
0
][
'height'
];
if
(
$this
->
adminMakeVideo
->
type
==
2
&&
$this
->
adminMakeVideo
->
bg_music
==
0
){
// 没有背景音,单图一张,输出为单图。
$output
=
$this
->
getTempPath
(
'.png'
,
false
);
$cmd
=
$this
->
ffmpeg
.
' -y '
.
' -i '
.
escapeshellarg
(
$image
)
.
' -i '
.
escapeshellarg
(
$watermark
)
.
' -filter_complex "[0:0] '
.
$this
->
getTextContentString
()
.
' [text];[text]'
.
' [1:0]overlay=20:20" '
.
escapeshellarg
(
$output
);
if
(
!
$this
->
execmd
(
$cmd
))
return
;
// 全部合成以后创建 临境
$video_info
=
$this
->
mediainfo
(
$output
);
$create
=
[
'user_id'
=>
1
,
'title'
=>
''
,
'content'
=>
$this
->
adminMakeVideo
->
feel
,
'url'
=>
$output
,
'type'
=>
$this
->
adminMakeVideo
->
type
==
1
?
2
:
1
,
'duration'
=>
0
,
'size'
=>
$video_info
[
'format'
][
'size'
],
'poem_id'
=>
$this
->
adminMakeVideo
->
poem_id
,
'temp_id'
=>
$this
->
adminMakeVideo
->
temp_id
,
'thumbnail'
=>
''
,
'bgm'
=>
$this
->
adminMakeVideo
->
bgm_url
,
];
}
else
{
$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
=
"一言 · 官方出品"
;
// 生成贴纸和签名
$end_wallpaper
=
$this
->
wallpaperWithSignature
(
$end_wallpaper
,
$thumbnail
,
$signature
,
$font
);
// 有背景音 单图合成视频,时长为音频时长,音频加入背景音
$bgm
=
Storage
::
disk
(
'public'
)
->
path
(
$this
->
adminMakeVideo
->
bgm_url
);
// 制作最后一帧
$size
=
$this
->
width
.
'x'
.
$this
->
height
;
$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
);
if
(
!
$this
->
execmd
(
$cmd
))
return
;
// 利用最后一帧制作动画
$signature_x
=
0
;
$signature_y
=
-
20
;
$animate
=
$this
->
makeAnimate
(
$last_frame_video
,
$end_wallpaper
,
''
,
$signature_x
,
$signature_y
,
$font
);
$output
=
$this
->
getTempPath
(
'.mp4'
,
false
);
$cmd
=
$this
->
ffmpeg
.
' -y '
.
' -i '
.
escapeshellarg
(
$image
)
.
' -i '
.
escapeshellarg
(
$watermark
)
.
' -i '
.
escapeshellarg
(
$bgm
)
.
' -i '
.
escapeshellarg
(
$animate
)
.
' -filter_complex "[0:0] '
.
$this
->
getTextContentString
()
.
'[text];[text][1:0]overlay=20:20[water];'
.
'[water][2:a][3:v][3:a]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
(
$output
);
if
(
!
$this
->
execmd
(
$cmd
))
return
;
// 全部合成以后创建 临境
$video_info
=
$this
->
mediainfo
(
$output
);
$create
=
[
'user_id'
=>
1
,
'title'
=>
''
,
'content'
=>
$this
->
adminMakeVideo
->
feel
,
'url'
=>
$output
,
'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'
=>
''
,
'bgm'
=>
$this
->
adminMakeVideo
->
bgm_url
,
];
}
Immerse
::
query
()
->
create
(
$create
);
}
/***
* 获取视频信息(配合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');
}
}
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: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
/
100
/
$max_len
);
}
/**
* 贴纸和签名
* @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
=
$this
->
getTempPath
(
'.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
;
}
/**
* 获取圆形头像
* @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 $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
;
}
}
}
app/Models/AdminMakeVideo.php
View file @
6ac4c13
...
...
@@ -33,15 +33,6 @@ class AdminMakeVideo extends Model
return
Storage
::
disk
(
'public'
)
->
url
(
$this
->
thumbnail_url
);
}
public
function
getImagesUrl
()
{
if
(
Str
::
contains
(
$this
->
images_url
,
'//'
))
{
return
$this
->
images_url
;
}
return
Storage
::
disk
(
'public'
)
->
url
(
$this
->
images_url
);
}
public
function
poem
()
{
return
$this
->
hasOne
(
OnePoem
::
class
,
'id'
,
'poem_id'
);
...
...
Please
register
or
login
to post a comment