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-23 09:32:30 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
3cf3b6b8c8c41736588b094439f442876a15aa77
3cf3b6b8
1 parent
b113dbd3
1.优化一言添加
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
191 additions
and
90 deletions
app/Admin/Controllers/VideoTempController.php
app/Console/Commands/DevFFmpeg.php
app/Jobs/MakeVideo.php
app/Models/AdminMakeVideo.php
app/Models/VideoTemp.php
database/migrations/2023_03_22_163425_update_components_table.php
app/Admin/Controllers/VideoTempController.php
View file @
3cf3b6b
...
...
@@ -125,6 +125,7 @@ class VideoTempController extends AdminController
$form
->
radio
(
'draw'
,
'文字效果'
)
->
options
([
'fade'
=>
'淡入淡出'
,
'fix'
=>
'固定显示'
])
->
default
(
'fade'
)
->
when
(
'fade'
,
function
(
Form\NestedForm
$form
){
$form
->
number
(
'fade_time'
,
'fade时间'
)
->
default
(
1500
);
$form
->
selectTable
(
'font_file'
,
'字体'
)
->
title
(
'字体选择'
)
->
from
(
FontTable
::
make
())
...
...
app/Console/Commands/DevFFmpeg.php
View file @
3cf3b6b
...
...
@@ -66,6 +66,12 @@ class DevFFmpeg extends Command
*/
public
function
handle
()
{
// $json = shell_exec(env('FFPROBE_CMD') . ' -v quiet -print_format json -show_format -show_streams ' . escapeshellarg(storage_path('app/public/uploads/131/309/K4LTgHuDhcmDr3MZBDkd0vyMUmRbfBxrFbU0CoNs.png')). ' 2>&1');
// $arr = json_decode($json,true);
// dd($arr);
// dd(AdminMakeVideo::query()->find(1)->poem2());
dd
(
AdminMakeVideo
::
query
()
->
find
(
1
)
->
poem2
->
verses
->
toArray
());
return
0
;
dd
(
AdminMakeVideo
::
query
()
->
find
(
33
)
->
temp
->
components
->
toArray
());
AdminMakeImmerse
::
dispatch
(
AdminMakeVideo
::
query
()
->
find
(
33
)
->
temp
->
components
);
...
...
app/Jobs/MakeVideo.php
View file @
3cf3b6b
...
...
@@ -51,52 +51,7 @@ class MakeVideo implements ShouldQueue
// 素材准备
$drawtext
=
$this
->
getTextContentString
();
if
(
$media_info
[
'format'
][
'nb_streams'
]
>=
2
)
{
/** 音频视频轨都有 */
if
(
$is_bgm
)
{
// 有背景音 融合
$audio
=
$this
->
getAbsolutePath
(
$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
=
'2:a'
;
}
else
{
// 没有背景音
$audio_input
=
''
;
$audio_filter
=
'0:a'
;
}
}
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_info
[
'format'
][
'duration'
])
.
' -ar 48000 -ab 64k '
.
escapeshellarg
(
$audio
);
if
(
!
$this
->
execmd
(
$cmd
))
return
;
if
(
$is_bgm
)
{
// 有背景音 融合
$audio_empty
=
$audio
;
$audio
=
$this
->
getAbsolutePath
(
$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
=
'2:a'
;
}
else
{
/** 音频视频轨都没有 */
Log
::
channel
(
'daily'
)
->
error
(
'视频没有video track, url:'
.
$file
);
return
;
}
$thumbnail
=
$this
->
getTempPath
(
'.jpg'
,
'thumbnail'
);
if
(
$adminMakeVideo
->
thumbnail
==
2
){
...
...
@@ -174,9 +129,57 @@ class MakeVideo implements ShouldQueue
// 准备素材
// 组装文字参数
$drawtext
=
$this
->
getTextContentString
();
// 合成视频
if
(
$this
->
media_info
[
'format'
][
'nb_streams'
]
>=
2
)
{
/** 音频视频轨都有 */
if
(
$is_bgm
)
{
// 有背景音 融合
$audio
=
$this
->
getAbsolutePath
(
$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
=
'2:a'
;
}
else
{
// 没有背景音
$audio_input
=
''
;
$audio_filter
=
'0:a'
;
}
}
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_info
[
'format'
][
'duration'
])
.
' -ar 48000 -ab 64k '
.
escapeshellarg
(
$audio
);
if
(
!
$this
->
execmd
(
$cmd
))
return
;
if
(
$is_bgm
)
{
// 有背景音 融合
$audio_empty
=
$audio
;
$audio
=
$this
->
getAbsolutePath
(
$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
=
'2:a'
;
}
else
{
/** 音频视频轨都没有 */
Log
::
channel
(
'daily'
)
->
error
(
'视频没有video track, url:'
.
$file
);
return
;
}
// 制作封面图
// 分析视频 入库
...
...
@@ -194,7 +197,7 @@ class MakeVideo implements ShouldQueue
if
(
$this
->
media_info
)
return
$this
->
media_info
;
$cmd
=
$this
->
ffprobe
.
' -v quiet -print_format json -show_format -show_streams '
.
escapeshellarg
(
$file
);
$output
=
$this
->
execmd
(
$cmd
);
$output
=
$this
->
exec
C
md
(
$cmd
);
$data
=
json_decode
(
$output
,
true
);
if
(
json_last_error
()
===
JSON_ERROR_UTF8
)
{
$output
=
mb_convert_encoding
(
$output
,
"UTF-8"
);
...
...
@@ -204,57 +207,140 @@ class MakeVideo implements ShouldQueue
return
$data
;
}
public
function
execmd
(
$cmd
,
$update_progress
=
false
)
{
echo
$cmd
.
"
\n
"
.
"
\n
"
.
"
\n
"
;
$descriptorspec
=
array
(
1
=>
array
(
"pipe"
,
"w"
),
// 标准输出,子进程向此管道中写入数据
);
$process
=
proc_open
(
"
{
$cmd
}
2>&1"
,
$descriptorspec
,
$pipes
);
if
(
is_resource
(
$process
))
{
$error0
=
''
;
$error1
=
''
;
$stdout
=
''
;
while
(
!
feof
(
$pipes
[
1
]))
{
$line
=
fgets
(
$pipes
[
1
],
150
);
$stdout
.=
$line
;
if
(
$line
)
{
//记录错误
$error0
=
$error1
;
$error1
=
$line
;
if
(
$update_progress
&&
false
!==
strpos
(
$line
,
'size='
)
&&
false
!==
strpos
(
$line
,
'time='
)
&&
false
!==
strpos
(
$line
,
'bitrate='
))
public
function
execCmd
(
$cmd
)
{
return
shell_exec
(
"
{
$cmd
}
2>&1"
);
}
public
function
getTextContentString
()
{
//记录进度 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
];
$components
=
$this
->
adminMakeVideo
->
temp
->
components
;
$drawtext
=
''
;
foreach
(
$components
as
$component
)
{
$text_color
=
$component
->
text_color
??
'white'
;
$text_bg_color
=
$component
->
text_bg_color
??
'0xd0cdcc'
;
$opacity
=
$component
->
opacity
?
$component
->
opacity
/
100
:
0.5
;
$font_file
=
$this
->
getAbsolutePath
(
$component
->
font_file
);
$text_bg_box
=
$component
->
text_bg_box
??
0
;
// 文字淡入淡出模式
if
(
$component
->
draw
==
'fade'
){
$contents
=
[];
//
switch
(
$component
->
name
){
case
'one_poem'
:
foreach
(
$this
->
adminMakeVideo
->
poem2
->
verses
as
$item
)
{
if
(
$item
->
stanza
!=
''
)
$contents
[]
=
$item
->
stanza
;
}
break
;
case
'one_poem_with_annotate'
:
foreach
(
$this
->
adminMakeVideo
->
poem2
->
verses
as
$item
)
{
if
(
$item
->
stanza
!=
''
)
$contents
[]
=
$item
->
stanza
;
if
(
$item
->
annotate
!=
''
)
$contents
[]
=
$item
->
annotate
;
}
break
;
case
'weather'
:
$contents
[]
=
$this
->
adminMakeVideo
->
weather
;
break
;
case
'date'
:
$contents
[]
=
Carbon
::
now
()
->
format
(
'Y年m月d日H时'
);
break
;
case
'feel'
:
$contents
[]
=
$this
->
adminMakeVideo
->
feel
?:
'读此一言,仿佛身临其境。'
;
break
;
}
$FID
=
$FOD
=
floatval
(
$component
->
fade_time
/
1000
);
$round
=
round
(
$this
->
media_info
[
'format'
][
'duration'
]
/
count
(
$contents
),
1
);
if
(
$round
<
1
)
$round
=
1
;
$sub_text
=
''
;
foreach
(
$contents
as
$key
=>
$content
){
$DS
=
$key
*
$round
;
$DE
=
$DS
+
$round
;
$text_file
=
$this
->
getAbsolutePath
(
$this
->
getTempPath
(
'.txt'
,
'text'
));
file_put_contents
(
$text_file
,
$content
);
$sub_text
.=
'drawtext="'
.
'fontfile='
.
escapeshellarg
(
$font_file
)
.
':'
.
'textfile='
.
escapeshellarg
(
$text_file
)
.
':'
.
'fontsize='
.
$this
->
calcFontSize
(
$component
->
font_size
)
.
':'
.
'fontcolor_expr='
.
escapeshellarg
(
$text_color
.
'%{eif\\\\: clip(255*(1*between(t\\, '
.
$DS
.
' + '
.
$FID
.
'\\, '
.
$DE
.
' - '
.
$FOD
.
') + ((t - '
.
$DS
.
')/'
.
$FID
.
')*between(t\\, '
.
$DS
.
'\\, '
.
$DS
.
' + '
.
$FID
.
') + (-(t - '
.
$DE
.
')/'
.
$FOD
.
')*between(t\\, '
.
$DE
.
' - '
.
$FOD
.
'\\, '
.
$DE
.
'))\\, 0\\, 255) \\\\: x\\\\: 2 }'
)
.
':'
.
'x='
.
escapeshellarg
(
VideoTemp
::
POSITION_FFMPEG
[
$component
->
position
][
0
])
.
':'
.
'y='
.
escapeshellarg
(
VideoTemp
::
POSITION_FFMPEG
[
$component
->
position
][
1
])
.
':'
.
'", '
;
}
$drawtext
.=
$sub_text
;
}
// 文字固定模式
if
(
$component
->
draw
==
'fix'
){
$contents
=
[];
//
switch
(
$component
->
name
){
case
'one_poem_with_annotate'
:
case
'one_poem'
:
$stanzas
=
''
;
foreach
(
$this
->
adminMakeVideo
->
poem2
->
verses
as
$item
)
{
if
(
$item
->
stanza
!=
''
)
$stanzas
=
$item
->
stanza
.
"
\n
"
;
}
// 切记:在调用 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);
Log
::
error
(
"cmd:
{
$cmd
}
"
);
Log
::
error
(
$error
);
Log
::
error
(
"stdout:
{
$stdout
}
"
);
$contents
[]
=
$stanzas
;
break
;
case
'weather'
:
$contents
[]
=
$this
->
adminMakeVideo
->
weather
;
break
;
case
'date'
:
$contents
[]
=
Carbon
::
now
()
->
format
(
'Y年m月d日H时'
);
break
;
case
'feel'
:
$contents
[]
=
$this
->
adminMakeVideo
->
feel
?:
'读此一言,仿佛身临其境。'
;
break
;
}
}
else
{
// return ErrorUtil::triggerErrorMsg('proc_open error');
Log
::
error
(
'proc_open error'
);
$sub_text
=
''
;
foreach
(
$contents
as
$key
=>
$content
){
$text_file
=
$this
->
getAbsolutePath
(
$this
->
getTempPath
(
'.txt'
,
'text'
));
file_put_contents
(
$text_file
,
$content
);
$sub_text
.=
'drawtext="'
.
'fontfile='
.
escapeshellarg
(
$font_file
)
.
':'
.
'textfile='
.
escapeshellarg
(
$text_file
)
.
':'
.
'fontsize='
.
$this
->
calcFontSize
(
$component
->
font_size
)
.
':'
.
'fontcolor='
.
$text_color
.
'@'
.
$opacity
.
':'
.
'x='
.
escapeshellarg
(
VideoTemp
::
POSITION_FFMPEG
[
$component
->
position
][
0
])
.
':'
.
'y='
.
escapeshellarg
(
VideoTemp
::
POSITION_FFMPEG
[
$component
->
position
][
1
])
.
':'
.
'box=1:boxborderw='
.
$text_bg_box
.
':'
.
'boxcolor='
.
$text_bg_color
.
'@'
.
$opacity
.
'", '
;
}
$drawtext
.=
$sub_text
;
}
}
return
rtrim
(
$drawtext
,
', '
);
}
public
function
calcFontSize
(
$width
)
{
return
ceil
(
$this
->
output_width
/
360
*
$width
);
}
/**
* 获取输出临时文件名
* @param string $ext
* @param string $dir
* @return string
*/
public
function
getTempPath
(
$ext
=
'.mp4'
,
$dir
=
'video'
)
{
$filename
=
"/output_"
.
time
()
.
rand
(
0
,
10000
);
$hash_hex
=
md5
(
$filename
);
// 16进制表示的字符串一共32字节,表示16个二进制字节。
// 前16个字符用来第一级求摸,后16个用做第二级
$hash_hex_l1
=
substr
(
$hash_hex
,
0
,
8
);
$hash_hex_l2
=
substr
(
$hash_hex
,
8
,
8
);
$dir_l1
=
hexdec
(
$hash_hex_l1
)
%
256
;
$dir_l2
=
hexdec
(
$hash_hex_l2
)
%
512
;
$dir
=
$dir
.
'/'
.
$dir_l1
.
'/'
.
$dir_l2
;
if
(
!
Storage
::
disk
(
'public'
)
->
exists
(
$dir
))
Storage
::
disk
(
'public'
)
->
makeDirectory
(
$dir
);
return
$dir
.
$filename
.
$ext
;
}
}
...
...
app/Models/AdminMakeVideo.php
View file @
3cf3b6b
...
...
@@ -38,6 +38,11 @@ class AdminMakeVideo extends Model
return
$this
->
hasOne
(
OnePoem
::
class
,
'id'
,
'poem_id'
);
}
public
function
poem2
()
{
return
$this
->
hasOne
(
OnePoem2
::
class
,
'id'
,
'poem_id'
);
}
public
function
temp
()
{
return
$this
->
hasOne
(
VideoTemp
::
class
,
'id'
,
'temp_id'
);
...
...
app/Models/VideoTemp.php
View file @
3cf3b6b
...
...
@@ -34,7 +34,7 @@ class VideoTemp extends Model
public
function
componentsTable
()
{
return
$this
->
hasMany
(
'App\Models\Component'
,
'temp_id'
)
->
select
([
'id'
,
'temp_id'
,
'name'
,
'position'
,
'font_size'
,
'text_color'
,
'text_bg_color'
,
'text_bg_box'
,
'opacity'
,
'fix_bounds'
]);
->
select
([
'id'
,
'temp_id'
,
'name'
,
'position'
,
'font_size'
,
'text_color'
,
'text_bg_color'
,
'text_bg_box'
,
'opacity'
]);
}
public
function
admin_make_video
()
...
...
database/migrations/2023_03_22_163425_update_components_table.php
View file @
3cf3b6b
...
...
@@ -17,6 +17,7 @@ class UpdateComponentsTable extends Migration
Schema
::
table
(
'components'
,
function
(
Blueprint
$table
)
{
$table
->
string
(
'draw'
)
->
after
(
'position'
)
->
comment
(
'文字效果'
);
$table
->
integer
(
'fade_time'
)
->
after
(
'draw'
)
->
default
(
1500
)
->
comment
(
'fade切换时间(毫秒)'
);
});
}
...
...
@@ -27,6 +28,8 @@ class UpdateComponentsTable extends Migration
*/
public
function
down
()
{
Schema
::
dropColumns
(
'components'
,
[
'draw'
,
'fade_time'
]);
Schema
::
table
(
'components'
,
function
(
Blueprint
$table
)
{
$table
->
string
(
'fix_bounds'
)
->
after
(
'opacity'
)
->
comment
(
'超出避免剪切'
);
});
...
...
Please
register
or
login
to post a comment