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
李帅
2023-03-22 17:43:42 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
b113dbd36ff4abfd59b8897a4c3c05dd062f386c
b113dbd3
1 parent
420c7c36
1.重构一言表
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
141 additions
and
551 deletions
app/Admin/Controllers/VideoTempController.php
app/Jobs/MakeVideo.php
database/migrations/2023_03_22_163425_update_components_table.php
app/Admin/Controllers/VideoTempController.php
View file @
b113dbd
...
...
@@ -115,7 +115,6 @@ class VideoTempController extends AdminController
$form
->
hasMany
(
'components'
,
'组件'
,
function
(
Form\NestedForm
$form
)
{
$form
->
select
(
'name'
,
'组件名称'
)
->
options
([
'one_poem_with_annotate'
=>
'一言带注解组件'
,
'every_poem'
=>
'每日一言组件'
,
'one_poem'
=>
'一言组件'
,
'weather'
=>
'天气组件'
,
'date'
=>
'日期组件'
,
...
...
@@ -123,8 +122,17 @@ class VideoTempController extends AdminController
]);
$form
->
select
(
'position'
,
'组件位置'
)
->
options
(
VideoTemp
::
POSITION_OPTIONS
);
$form
->
switch
(
'fade'
,
'淡入淡出'
)
->
help
(
"开启淡入淡出会使背景色失效"
);
$form
->
radio
(
'draw'
,
'文字效果'
)
->
options
([
'fade'
=>
'淡入淡出'
,
'fix'
=>
'固定显示'
])
->
default
(
'fade'
)
->
when
(
'fade'
,
function
(
Form\NestedForm
$form
){
$form
->
selectTable
(
'font_file'
,
'字体'
)
->
title
(
'字体选择'
)
->
from
(
FontTable
::
make
())
->
model
(
Font
::
class
,
'file'
,
'name'
);
$form
->
number
(
'font_size'
,
'字号'
)
->
default
(
12
)
->
min
(
12
);
$form
->
color
(
'text_color'
,
'字体颜色'
)
->
default
(
'#f5f5f5'
)
->
addElementClass
(
'text_color'
);
})
->
when
(
'fix'
,
function
(
Form\NestedForm
$form
){
$form
->
number
(
'text_bg_box'
,
'背景厚度'
)
->
default
(
0
)
->
addElementClass
(
'text_bg_box'
)
->
help
(
'设置背景块边缘厚度(用于在背景块边缘用背景色填充一圈),默认为0'
);
$form
->
color
(
'text_bg_color'
,
'背景色'
)
->
default
(
'#5c6bc6'
)
->
addElementClass
(
'text_bg_color'
);
...
...
@@ -137,8 +145,7 @@ class VideoTempController extends AdminController
$form
->
number
(
'opacity'
,
'透明度'
)
->
min
(
0
)
->
max
(
100
)
->
addElementClass
(
'opacity'
)
->
default
(
100
)
->
help
(
'范围为0-100,100表示不透明,0表示完全透明'
);
$form
->
switch
(
'fix_bounds'
,
'避免剪切'
);
});
});
$form
->
hidden
(
'state'
)
->
default
(
1
)
...
...
app/Jobs/MakeVideo.php
View file @
b113dbd
...
...
@@ -26,9 +26,11 @@ class MakeVideo implements ShouldQueue
protected
$ffprobe
;
protected
$
ffplay
;
protected
$
media_info
;
protected
$width
;
protected
$output_width
;
protected
$output_height
;
/**
* Create a new job instance.
...
...
@@ -38,159 +40,113 @@ class MakeVideo implements ShouldQueue
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
()
{
$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
){
$this
->
output_width
=
720
;
$this
->
output_height
=
1280
;
$file
=
$this
->
getAbsolutePath
(
$adminMakeVideo
->
video_url
);
// 分析视频
$media_info
=
$this
->
mediainfo
(
$file
);
// 素材准备
$drawtext
=
$this
->
getTextContentString
();
if
(
$media_info
[
'format'
][
'nb_streams'
]
>=
2
)
{
/** 音频视频轨都有 */
if
(
$is_bgm
)
{
// 有背景音 融合
$audio
=
$this
->
get
TempPath
(
'.mp3'
);
$cmd
=
$this
->
ffmpeg
.
' -y -i '
.
escapeshellarg
(
$file
)
.
' -y -i '
.
escapeshellarg
(
$bgm
)
.
$audio
=
$this
->
get
AbsolutePath
(
$this
->
getTempPath
(
'.mp3'
,
'audio'
)
);
$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_filter
=
'
2:a
'
;
}
else
{
// 没有背景音
$audio_input
=
''
;
$audio_filter
=
'
[0:1]
'
;
$audio_filter
=
'
0:a
'
;
}
}
elseif
(
$media_info
[
'format'
][
'nb_streams'
]
==
1
){
$audio
=
$this
->
getTempPath
(
'.mp3'
);
}
elseif
(
$media_info
[
'format'
][
'nb_streams'
]
==
1
)
{
/** 只有视频轨 */
// 生成一段无声音频
$audio
=
$this
->
getAbsolutePath
(
$this
->
getTempPath
(
'.mp3'
,
'audio'
));
$cmd
=
$this
->
ffmpeg
.
' -y -f lavfi -i aevalsrc=0:duration='
.
escapeshellarg
(
$media_file_time_length
)
.
' -y -f lavfi -i aevalsrc=0:duration='
.
escapeshellarg
(
$media_info
[
'format'
][
'duration'
]
)
.
' -ar 48000 -ab 64k '
.
escapeshellarg
(
$audio
);
if
(
!
$this
->
execmd
(
$cmd
))
return
;
if
(
$is_bgm
){
if
(
$is_bgm
)
{
// 有背景音 融合
$audio_empty
=
$audio
;
$audio
=
$this
->
get
TempPath
(
'.mp3'
);
$cmd
=
$this
->
ffmpeg
.
' -y -i '
.
escapeshellarg
(
$audio_empty
)
.
' -y -i '
.
escapeshellarg
(
$bgm
)
.
$audio
=
$this
->
get
AbsolutePath
(
$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
'
);
$audio_filter
=
'
2:a
'
;
}
else
{
/** 音频视频轨都没有 */
Log
::
channel
(
'daily'
)
->
error
(
'视频没有video track
, url:'
.
$file
);
return
;
}
if
(
$this
->
adminMakeVideo
->
thumbnail
==
2
){
$thumbnail
=
$this
->
getTempPath
(
'.jpg'
,
'thumbnail'
);
if
(
$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
(
$th
umbnail
);
' -frames:v 1 -s
'
.
$this
->
output_width
.
'x'
.
$this
->
output_height
.
' -preset superfast '
.
escapeshellarg
(
$th
is
->
getAbsolutePath
(
$thumbnail
)
);
if
(
!
$this
->
execmd
(
$cmd
))
return
;
}
else
{
$thumbnail
=
$adminMakeVideo
->
thumbnail_url
;
// 手动上传封面
$origin_thumbnail
=
Storage
::
disk
(
'public'
)
->
path
(
$adminMakeVideo
->
thumbnail_url
);
// 将封面分辨率改为指定分辨率
$cmd
=
$this
->
ffmpeg
.
' -y '
.
' -i '
.
escapeshellarg
(
$origin_thumbnail
)
.
'-s '
.
$this
->
output_width
.
'x'
.
$this
->
output_height
.
' -preset superfast '
.
escapeshellarg
(
$this
->
getAbsolutePath
(
$thumbnail
));
if
(
!
$this
->
execmd
(
$cmd
))
return
;
}
$end_wallpaper
=
Storage
::
disk
(
'public'
)
->
path
(
'ffmpeg'
)
.
"/end_wallpaper.png"
;
$avatar
=
Storage
::
disk
(
'public'
)
->
path
(
'ffmpeg'
)
.
"/thumbnail.png"
;
$font
=
Storage
::
disk
(
'public'
)
->
path
(
'ffmpeg'
)
.
"/arialuni.ttf"
;
$signature
=
"一言 · 官方出品"
;
// 生成贴纸和签名
$end_wallpaper
=
$this
->
wallpaperWithSignature
(
$end_wallpaper
,
$avatar
,
$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
);
$output
=
$this
->
getTempPath
(
'.mp4'
,
'video'
);
$cmd
=
$this
->
ffmpeg
.
' -y '
.
' -i '
.
escapeshellarg
(
$file
)
.
' -i '
.
escapeshellarg
(
$animate
)
.
' -i '
.
escapeshellarg
(
$watermark
)
.
$audio_input
.
' -filter_complex "[0:0] '
.
$this
->
getTextContentString
()
.
' -filter_complex "[0:v]scale='
.
$this
->
output_width
.
':'
.
$this
->
output_height
.
','
.
$drawtext
.
' [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]'
.
' [
1:v]overlay=20:20[v
]" '
.
' -map [v] -map
'
.
$audio_filter
.
' -c:v libx264 -bt 256k -r 25'
.
' -ar 44100 -ac 2 -qmin 30 -qmax 60 -profile:v baseline -preset fast '
.
escapeshellarg
(
$
video
);
escapeshellarg
(
$
this
->
getAbsolutePath
(
$output
)
);
$exec
=
$this
->
execmd
(
$cmd
);
if
(
is_array
(
$exec
)){
print_r
(
$exec
,
1
);
return
;
}
try
{
// 全部合成以后创建 临境
$video_info
=
$this
->
mediainfo
(
$video
);
if
(
!
$this
->
execmd
(
$cmd
))
return
;
$video_info
=
$this
->
mediainfo
(
$this
->
getAbsolutePath
(
$output
));
Immerse
::
query
()
->
create
([
'user_id'
=>
1
,
'title'
=>
''
,
'content'
=>
$this
->
adminMakeVideo
->
feel
,
'url'
=>
str_replace
(
Storage
::
disk
(
'public'
)
->
path
(
''
),
''
,
$video
),
'type'
=>
$this
->
adminMakeVideo
->
type
==
1
?
2
:
1
,
'weather'
=>
$adminMakeVideo
->
weather
,
'huangli'
=>
$adminMakeVideo
->
huangli
,
'content'
=>
$adminMakeVideo
->
feel
,
'location'
=>
$adminMakeVideo
->
location
,
'longitude'
=>
$adminMakeVideo
->
longitude
,
'latitude'
=>
$adminMakeVideo
->
latitude
,
'url'
=>
$output
,
'type'
=>
$adminMakeVideo
->
type
==
1
?
2
:
1
,
'upload_file'
=>
''
,
'duration'
=>
$video_info
[
'format'
][
'duration'
],
'size'
=>
$video_info
[
'format'
][
'size'
],
...
...
@@ -198,243 +154,58 @@ class MakeVideo implements ShouldQueue
'origin_image_url'
=>
''
,
'poem_id'
=>
$this
->
adminMakeVideo
->
poem_id
,
'temp_id'
=>
$this
->
adminMakeVideo
->
temp_id
,
'thumbnail'
=>
str_replace
(
Storage
::
disk
(
'public'
)
->
path
(
''
),
''
,
$thumbnail
)
,
'thumbnail'
=>
$thumbnail
,
'state'
=>
1
,
'bgm'
=>
$this
->
adminMakeVideo
->
bgm_url
??
''
,
'bgm'
=>
$is_bgm
?
$bgm
:
''
,
]);
}
catch
(
\Exception
$exception
){
// echo $exception->getMessage();
}
}
/**
* 获取圆形头像
* @param $img
* @param int $dst_w
* @param int $dst_h
* @return resource
* Execute the job.
*
* @return void
*/
public
function
getCircleAvatar
(
$img
,
$dst_w
=
96
,
$dst_h
=
96
)
public
function
handle
(
)
{
$w
=
130
;
$h
=
130
;
$src
=
imagecreatetruecolor
(
$dst_w
,
$dst_h
);
imagecopyresized
(
$src
,
$img
,
0
,
0
,
0
,
0
,
$dst_w
,
$dst_h
,
$w
,
$h
);
$file
=
$this
->
getAbsolutePath
(
$this
->
adminMakeVideo
->
video_url
);
// 分析视频
$this
->
media_info
=
$this
->
mediaInfo
(
$file
);
$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
)
.
' -t 0.7 -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
)
.
' -t 0.7 -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
getAbsolutePath
(
$path
)
{
if
(
$path
==
''
)
return
''
;
public
function
getFirstVideoTrackOption
(
$file
,
$option
,
$cache
=
true
)
{
return
$this
->
getFirstTrackOption
(
$file
,
$option
,
$codec_type
=
'video'
,
$cache
=
true
);
return
Storage
::
disk
(
'public'
)
->
path
(
$path
);
}
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
;
}
public
function
mediaInfo
(
$file
)
{
if
(
$this
->
media_info
)
return
$this
->
media_info
;
/***
* 获取视频信息(配合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
;
}
$this
->
media_info
=
$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 mixed
*/
public
function
execmd
(
$cmd
,
$update_progress
=
false
)
{
//
echo $cmd . "\n". "\n". "\n";
echo
$cmd
.
"
\n
"
.
"
\n
"
.
"
\n
"
;
$descriptorspec
=
array
(
1
=>
array
(
"pipe"
,
"w"
),
// 标准输出,子进程向此管道中写入数据
);
...
...
@@ -475,237 +246,15 @@ class MakeVideo implements ShouldQueue
return
$stdout
;
}
else
{
$error
=
trim
(
$error0
,
"
\n
"
)
.
' '
.
trim
(
$error1
,
"
\n
"
);
Log
::
channel
(
'daily'
)
->
error
(
print_r
(
array
(
"cmd:
{
$cmd
}
"
,
"errno:
{
$exitedcode
}
"
,
"stdout:
{
$stdout
}
"
),
1
));
return
[
$error
,
$exitedcode
];
}
}
else
{
Log
::
channel
(
'daily'
)
->
error
(
'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
);
}
// LogUtil::write(array("cmd:{$cmd}", "errno:{$exitedcode}", "stdout:{$stdout}"), __CLASS__);
// ErrorUtil::triggerErrorMsg($error, $exitedcode);
Log
::
error
(
"cmd:
{
$cmd
}
"
);
Log
::
error
(
$error
);
Log
::
error
(
"stdout:
{
$stdout
}
"
);
}
$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
;
// return ErrorUtil::triggerErrorMsg('proc_open error');
Log
::
error
(
'proc_open error'
);
}
/**
* 获取图像文件类型
* @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: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
);
}
}
...
...
database/migrations/2023_03_22_163425_update_components_table.php
0 → 100644
View file @
b113dbd
<?php
use
Illuminate\Database\Migrations\Migration
;
use
Illuminate\Database\Schema\Blueprint
;
use
Illuminate\Support\Facades\Schema
;
class
UpdateComponentsTable
extends
Migration
{
/**
* Run the migrations.
*
* @return void
*/
public
function
up
()
{
Schema
::
dropColumns
(
'components'
,
[
'fix_bounds'
]);
Schema
::
table
(
'components'
,
function
(
Blueprint
$table
)
{
$table
->
string
(
'draw'
)
->
after
(
'position'
)
->
comment
(
'文字效果'
);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public
function
down
()
{
Schema
::
table
(
'components'
,
function
(
Blueprint
$table
)
{
$table
->
string
(
'fix_bounds'
)
->
after
(
'opacity'
)
->
comment
(
'超出避免剪切'
);
});
}
}
Please
register
or
login
to post a comment