李帅

1.优化一言添加

......@@ -126,8 +126,8 @@ class AdminMakeVideoController extends AdminController
->title('一言诗词库')
->from(PoemTable2::make())
->model(OnePoem2::class,'id','title');
$form->textarea('feel','有感');
$form->text('weather','天气');
$form->textarea('feel','有感')->default('读此一言,仿佛身临其境!');
$form->text('weather','天气')->default('多云');
$form->text('huangli','黄历')->default('宜');
$form->radio('thumbnail','封面')
......
......@@ -120,32 +120,27 @@ class VideoTempController extends AdminController
'date' => '日期组件',
'feel' => '临境有感组件',
]);
$form->select('position','组件位置')->options(VideoTemp::POSITION_OPTIONS);
$form->select('position','组件位置')->options(VideoTemp::POSITION_OPTIONS)->addElementClass('position');
$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())
->model(Font::class,'file','name');
$form->number('font_size', '字号')->default(12)->min(12);
$form->number('font_size', '字号')->default(24)->min(12);
$form->color('text_color', '字体颜色')->default('#f5f5f5')->addElementClass('text_color');
$form->radio('draw', '文字效果')
->options(['fade'=>'淡入淡出', 'fix'=>'固定显示'])->default('fade')
->when('fade',function (Form\NestedForm $form){
$form->number('fade_time', 'fade时间')->default(1500);
})
->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');
$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');
$form->number('opacity', '透明度')->min(0)->max(100)
->addElementClass('opacity')->default(100)
->help('范围为0-100,100表示不透明,0表示完全透明');
$form->number('text_bg_box', '背景厚度')->default(0)
->addElementClass('text_bg_box')->help('设置背景块边缘厚度(用于在背景块边缘用背景色填充一圈),默认为0');
});
});
......
......@@ -24,7 +24,7 @@ class TemplateTable extends LazyRenderable
$grid->column('','组件信息')
->display('展开')
->expand(function (){
$th = ['id','模板id','名称','位置','字号'];
$th = ['id','模板id','名称','位置','字号','文字颜色','文字效果','fade时间','背景色','透明度','背景厚度'];
return Table::make($th, $this->componentsTable->toArray())->withBorder();
});
......
......@@ -63,11 +63,11 @@ class AdminMakeImmerse implements ShouldQueue
// 判断双轨 没有则制作空轨
$is_bgm = $this->adminMakeVideo->temp->bg_music == 1; //是否手动上传背景音
$bgm = $this->getAbsolutePath($this->adminMakeVideo->temp->bgm_url);
if ($this->media_info['format']['nb_streams'] >= 2) { /** 音频视频轨都有 */
if ($is_bgm) {
// 有背景音 融合
$audio = $this->getAbsolutePath($this->getTempPath('.mp3','audio'));
$bgm = $this->getAbsolutePath($this->adminMakeVideo->temp->bgm_url);
$cmd = $this->ffmpeg .
' -y -i ' . escapeshellarg($file) .
' -y -i ' . escapeshellarg($bgm) .
......@@ -93,7 +93,6 @@ class AdminMakeImmerse implements ShouldQueue
if ($is_bgm) {
// 有背景音 融合
$audio_empty = $audio;
$bgm = $this->getAbsolutePath($this->adminMakeVideo->temp->bgm_url);
$audio = $this->getAbsolutePath($this->getTempPath('.mp3','audio'));
$cmd = $this->ffmpeg .
' -y -i ' . escapeshellarg($audio_empty) .
......@@ -150,7 +149,8 @@ class AdminMakeImmerse implements ShouldQueue
if (!$this->execCmd($cmd)) return ;
// 分析视频 入库
$video_info = $this->mediainfo($this->getAbsolutePath($output));
$video_info = $this->mediaInfo($this->getAbsolutePath($output));
Immerse::query()->create([
'user_id' => 1,
'title' => '',
......@@ -184,8 +184,6 @@ class AdminMakeImmerse implements ShouldQueue
public function mediaInfo($file)
{
if ($this->media_info) return $this->media_info;
$cmd = $this->ffprobe . ' -v quiet -print_format json -show_format -show_streams ' . escapeshellarg($file);
$output = $this->execCmd($cmd);
$data = json_decode($output, true);
......@@ -199,6 +197,7 @@ class AdminMakeImmerse implements ShouldQueue
public function execCmd($cmd)
{
echo $cmd . "\n". "\n";
return shell_exec("{$cmd} 2>&1");
}
......
......@@ -27,6 +27,8 @@ class UserMakeImmerse implements ShouldQueue
protected $ffprobe;
protected $media_info;
protected $output_width;
protected $output_height;
......@@ -66,9 +68,9 @@ class UserMakeImmerse implements ShouldQueue
if ($this->immerse->type == 1){
// 1. 分析用户上传音频
$upload_file = Storage::disk('public')->path($this->immerse->upload_file);
$mediainfo = $this->mediainfo($upload_file);
$this->media_info = $this->mediainfo($upload_file);
// 记录媒体信息时长
$duration = $mediainfo['format']['duration'] ?: 0;
$duration = $this->media_info['format']['duration'] ?: 0;
// $font = Storage::disk('public')->path('ffmpeg/arialuni.ttf');
......@@ -118,7 +120,7 @@ class UserMakeImmerse implements ShouldQueue
$origin_mediainfo = $this->mediainfo($origin_video_path);
//用户录音超长,截取用户录音
if ($mediainfo['format']['duration'] > $origin_mediainfo['format']['duration']) {
if ($this->media_info['format']['duration'] > $origin_mediainfo['format']['duration']) {
if ($this->immerse->bgm == '' || $this->immerse->bgm == null) {
$audio = Storage::disk('public')->path($this->getTempPath('.mp3','audio'));
$cmd = $this->ffmpeg . ' -y ' .
......@@ -173,7 +175,7 @@ class UserMakeImmerse implements ShouldQueue
}else{
// 1. 分析用户上传视频
$upload_file = Storage::disk('public')->path($this->immerse->upload_file);
$mediainfo = $this->mediainfo($upload_file);
$this->media_info = $this->mediainfo($upload_file);
// 记录媒体信息时长
// $duration = $mediainfo['format']['duration'] ?: 0;
......@@ -208,7 +210,7 @@ class UserMakeImmerse implements ShouldQueue
$watermark = Storage::disk('public')->path('images/LOGO_eng.png');
// 截取中间帧作为视频封面
$frame = ceil($mediainfo['streams'][0]['nb_frames'] / 2);
$frame = ceil($this->media_info['streams'][0]['nb_frames'] / 2);
$thumbnail = Storage::disk('public')->path($this->getTempPath('.jpg','thumbnail'));
$cmd = $this->ffmpeg . ' -y ' .
' -i ' . escapeshellarg($upload_file) .
......@@ -490,83 +492,99 @@ class UserMakeImmerse implements ShouldQueue
$components = $this->immerse->temp()->first()->components()->get();
$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 = Storage::disk('public')->path($component->font_file ?? 'ffmpeg/arialuni.ttf');
$font_file = Storage::disk('public')->path($component->font_file);
$text_bg_box = $component->text_bg_box ?? 0;
$fix_bounds = $component->fix_bounds == 1;
// 文字淡入淡出模式
if ($component->draw == 'fade'){
$contents = []; //
switch ($component->name){
case 'every_poem':
case 'one_poem':
$content = $this->immerse->poem->content;
$text_file = Storage::disk('public')->path($this->getTempPath('.txt','text'));
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_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]) . ':' .
'fix_bounds='. $fix_bounds . ':' .
'box=1:boxborderw='. $text_bg_box . ':' .
'boxcolor=' . $text_bg_color . '@' . $opacity . '", ';
foreach ($this->immerse->poem2->verses as $item) {
if ($item->stanza != '') $contents[] = $item->stanza;
}
break;
case 'one_poem_with_annotate':
foreach ($this->immerse->poem2->verses as $item) {
if ($item->stanza != '') $contents[] = $item->stanza;
if ($item->annotate != '') $contents[] = $item->annotate;
}
break;
case 'weather':
$content = $this->immerse->weather;
$contents[] = $this->immerse->weather;
break;
case 'date':
$contents[] = Carbon::now()->format('Y年m月d日H时');
break;
case 'feel':
$contents[] = $this->immerse->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 = Storage::disk('public')->path($this->getTempPath('.txt','text'));
file_put_contents($text_file, $content);
$drawtext .= 'drawtext="'.
$sub_text .= 'drawtext="'.
'fontfile=' . escapeshellarg($font_file) . ':' .
'textfile=' . escapeshellarg($text_file) . ':' .
'fontsize=' . $this->calcFontSize($component->font_size) . ':' .
'fontcolor=' . $text_color . '@' . $opacity . ':' .
'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]) . ':' .
'fix_bounds='. $fix_bounds . ':' .
'box=1:boxborderw='. $text_bg_box . ':' .
'boxcolor=' . $text_bg_color . '@' . $opacity . '", ';
'", ';
}
$drawtext .= $sub_text;
}
// 文字固定模式
if ($component->draw == 'fix'){
$contents = []; //
switch ($component->name){
case 'one_poem_with_annotate':
case 'one_poem':
$stanzas = '';
foreach ($this->immerse->poem2->verses as $item) {
if ($item->stanza != '') $stanzas = $item->stanza . "\n";
}
$contents[] = $stanzas;
break;
case 'weather':
$contents[] = $this->immerse->weather;
break;
case 'date':
$content = Carbon::now()->format('Y年m月d日H时');
$text_file = Storage::disk('public')->path($this->getTempPath('.txt','text'));
file_put_contents($text_file, $content);
$drawtext .= '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]) . ':' .
'fix_bounds='. $fix_bounds . ':' .
'box=1:boxborderw='. $text_bg_box . ':' .
'boxcolor=' . $text_bg_color . '@' . $opacity . '", ';
$contents[] = Carbon::now()->format('Y年m月d日H时');
break;
case 'feel':
$content = $this->immerse->content;
$contents[] = $this->immerse->feel ?: '读此一言,仿佛身临其境。';
break;
}
$sub_text = '';
foreach ($contents as $key => $content){
$text_file = Storage::disk('public')->path($this->getTempPath('.txt','text'));
file_put_contents($text_file, $content);
$drawtext .= 'drawtext="'.
$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]) . ':' .
'fix_bounds='. $fix_bounds . ':' .
'box=1:boxborderw='. $text_bg_box . ':' .
'boxcolor=' . $text_bg_color . '@' . $opacity . '", ';
break;
}
$drawtext .= $sub_text;
}
}
......
......@@ -58,6 +58,11 @@ class Immerse 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');
......
......@@ -34,7 +34,8 @@ 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']);
->select(['id', 'temp_id', 'name', 'position', 'font_size', 'text_color', 'draw',
'fade_time', 'text_bg_color', 'text_bg_box', 'opacity']);
}
public function admin_make_video()
......
......@@ -40,7 +40,7 @@
}
.poem-title {
font-size: 26px;
font-size: 14px;
font-weight: bold;
margin: 10px;
}
......@@ -51,7 +51,7 @@
}
.poem-content {
font-size: 24px;
font-size: 12px;
margin: 0;
}
......@@ -64,13 +64,13 @@
<div class="text">模板</div>
<img src="{{asset('storage/images/mobile-head.png')}}" alt="" width="360" height="80">
<div class="bg-box">
<img width="360" height="625" class="bg_img" style="display: none">
<video width="360" height="625" id="bg_video" style="display: none"></video>
<audio id="bg_audio" ></audio>
{{--<img width="360" height="625" class="bg_img" style="display: none">--}}
{{--<video width="360" height="625" id="bg_video" style="display: none"></video>--}}
{{--<audio id="bg_audio" ></audio>--}}
</div>
<div class="poem-block">
<p class="poem-title">题破山寺后禅院</p>
<p class="poem-author">-- 常建</p>
{{--<p class="poem-title">题破山寺后禅院</p>--}}
{{--<p class="poem-author">-- 常建</p>--}}
<p class="poem-content">清晨入古寺,初日照高林。</p>
<p class="poem-content">曲径通幽处,禅房花木深。</p>
<p class="poem-content">山光悦鸟性,潭影空人心。</p>
......@@ -86,20 +86,62 @@
$(document).off('click', '.sync').on('click', '.sync', function () {
let ori_top = 80;
let top = parseInt($('.field_top').val()) + ori_top;
let left = $('.field_left').val();
let top = 0 + ori_top;
let left = 0;
let font = $('.field_font_size').val();
let content_size = 12 + parseInt(font);
let title_size = 14 + parseInt(font);
let content_size = parseInt(font);
let title_size = parseInt(font);
let text_color = $('.text_color').val() || 'whitesmoke';
let text_bg_color = $('.text_bg_color').val() || '#5c6bc6';
let opacity = parseInt($('.opacity').val()) / 100;
let pos = $('.field_position').val();
$('.poem-block').css('top', top + 'px').css('left', left + 'px')
.css('background-color', text_bg_color).css('opacity', opacity);
$('.poem-title').css('font-size', title_size + 'px').css('color', text_color);
$('.poem-content').css('font-size', content_size + 'px').css('color', text_color);
let block_w = $('.poem-block').width();
let block_h = $('.poem-block').height();
switch (pos) {
case 'topLeft':
top = 0 + ori_top;
left = 0;
break;
case 'topMiddle':
top = 0 + ori_top;
left = (360 - block_w) / 2;
break;
case 'topRight':
top = 0 + ori_top;
left = 360 - block_w;
break;
case 'midLeft':
top = (640 - block_h) / 2 + ori_top;
left = 0;
break;
case 'midMiddle':
top = (640 - block_h) / 2 + ori_top;
left = (360 - block_w) / 2;
break;
case 'midRight':
top = (640 - block_h) / 2 + ori_top;
left = 360 - block_w;
break;
case 'botLeft':
top = 640 - block_h + ori_top;
left = 0;
break;
case 'botMiddle':
top = 640 - block_h + ori_top;
left = (360 - block_w) / 2;
break;
case 'botRight':
top = 640 - block_h + ori_top;
left = 360 - block_w;
break;
}
$('.poem-block').css('top', top + 'px').css('left', left + 'px')
let bg_img_url = $('.bg_img_url').find("input[type='hidden'][name='bg_url']").val();
if (bg_img_url !== '') {
......