李帅

1.ffmpeg命令微调

......@@ -172,22 +172,42 @@ class VideoTempController extends AdminController
$form->checkbox('components','组件')
->when('every_poem', function (Form\BlockForm $form) {
$form->select('pos_every_poem', '每日位置')->options(VideoTemp::POSITION_OPTIONS);
$form->number('font_size_every_poem','容器宽度')->default(6)->help('范围为1-9,默认容器外边距为1/20');
$form->text('text_color_every_poem','字体颜色')->default('white');
$form->text('text_bg_color_every_poem','背景颜色');
$form->rate('opacity_every_poem','透明度')->default(50)->help('范围为0-100,100表示不透明,0表示完全透明');
$form->divider();
})
->when('one_poem', function (Form\BlockForm $form) {
$form->select('pos_one_poem', '一言位置')->options(VideoTemp::POSITION_OPTIONS);
$form->number('font_size_one_poem','容器宽度')->default(6)->help('范围为1-9,默认容器外边距为1/20');
$form->text('text_color_one_poem','字体颜色')->default('white');
$form->text('text_bg_color_one_poem','背景颜色');
$form->rate('opacity_one_poem','透明度')->default(50)->help('范围为0-100,100表示不透明,0表示完全透明');
$form->divider();
})
->when('weather', function (Form\BlockForm $form) {
$form->select('pos_weather', '天气位置')->options(VideoTemp::POSITION_OPTIONS);
$form->number('font_size_weather','容器宽度')->default(6)->help('范围为1-9,默认容器外边距为1/20');
$form->text('text_color_weather','字体颜色')->default('white');
$form->text('text_bg_color_weather','背景颜色');
$form->rate('opacity_weather','透明度')->default(50)->help('范围为0-100,100表示不透明,0表示完全透明');
$form->divider();
})
->when('date', function (Form\BlockForm $form) {
$form->select('pos_date', '日期位置')->options(VideoTemp::POSITION_OPTIONS);
$form->number('font_size_date','容器宽度')->default(6)->help('范围为1-9,默认容器外边距为1/20');
$form->text('text_color_date','字体颜色')->default('white');
$form->text('text_bg_color_date','背景颜色');
$form->rate('opacity_date','透明度')->default(50)->help('范围为0-100,100表示不透明,0表示完全透明');
$form->divider();
})
->when('feel', function (Form\BlockForm $form) {
$form->select('pos_feel', '有感位置')->options(VideoTemp::POSITION_OPTIONS);
$form->number('font_size_feel','容器宽度')->default(6)->help('范围为1-9,默认容器外边距为1/20');
$form->text('text_color_feel','字体颜色')->default('white');
$form->text('text_bg_color_feel','背景颜色');
$form->rate('opacity_feel','透明度')->default(50)->help('范围为0-100,100表示不透明,0表示完全透明');
$form->divider();
})
->default(['one_poem','weather','date'])
......@@ -230,7 +250,11 @@ class VideoTempController extends AdminController
Component::query()->create([
'temp_id' => $vide_temp->id,
'name' => $component,
'position' => $all['pos_' . $component]
'position' => $all['pos_' . $component],
'font_size' => $all['font_size_' . $component],
'text_color' => $all['text_color_' . $component],
'text_bg_color' => $all['text_bg_color_' . $component],
'opacity' => $all['opacity_' . $component],
]);
}
}
......
......@@ -4,8 +4,11 @@ namespace App\Console\Commands;
use App\Models\AdminMakeVideo;
use App\Models\Immerse;
use App\Models\VideoTemp;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
use voku\helper\ASCII;
class DevFFmpeg extends Command
{
......@@ -52,6 +55,9 @@ class DevFFmpeg extends Command
$is_bgm = false;
$bgm = $path . 'bgm.aac';
$string = $this->getTextContentString();
// 1.getmediainfo 记录时长,音频视频取最长。
$cmd = $this->ffprobe . ' -v quiet -print_format json -show_format -show_streams ' . escapeshellarg($file);
$output = $this->execmd($cmd);
......@@ -158,16 +164,9 @@ class DevFFmpeg extends Command
' -i ' . escapeshellarg($path . 'logo.png').
$audio_input.
' -filter_complex "[0:0] ' .
'drawtext="'.
'fontfile=' . escapeshellarg($path . 'arialuni.ttf') . ':' .
'text=' . escapeshellarg($content) . ':'.
'fontsize='.$font_size.':'.
'fontcolor=white@1.0:'.
'x=' . escapeshellarg('(w-text_w)/2') . ':' .
'y=' . escapeshellarg('(h-text_h)/2') . ':' .
'box=1:boxcolor=0xd0cdcc@0.5'.
'" [text];'.
'[text] [2:v]overlay=20:20[water];[water]'.$audio_filter.'[1:0][1:1] concat=n=2:v=1:a=1[v][a]" '.
$string.
'[text];[text]'.
'[2:v]overlay=20:20[water];[water]'.$audio_filter.'[1:0][1:1] concat=n=2:v=1:a=1[v][a]" '.
' -map [v] -map [a]'.
' -c:v libx264 -bt 256k -r 25' .
' -ar 44100 -ac 2 -qmin 30 -qmax 60 -profile:v baseline -preset fast ' .
......@@ -737,5 +736,98 @@ class DevFFmpeg extends Command
return false;
}
}
public function getTextContentString()
{
$components = VideoTemp::query()->find(6)->components()->get();
$font = Storage::disk('public')->path('ffmpeg/arialuni.ttf');
$drawtext = '';
foreach ($components as $component) {
switch ($component->name){
case 'one_poem':
$content = '题破山寺后禅院' . "\t" . ' -- 常建' . PHP_EOL .
'清晨入古寺,初日照高林。' . PHP_EOL .
'曲径通幽处,禅房花木深。' . PHP_EOL .
'山光悦鸟性,潭影空人心。' . PHP_EOL .
'万籁此都寂,但余钟磬音。' . PHP_EOL ;
$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=' . $component->font_size . ':' .
'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=' . $component->font_size . ':' .
'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=' . $component->font_size . ':' .
'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 = '测试有感文本';
$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=' . $component->font_size . ':' .
'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,', ');
}
}
......
......@@ -4,6 +4,8 @@ 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;
......@@ -25,6 +27,8 @@ class MakeVideo implements ShouldQueue
protected $ffplay;
protected $width;
/**
* Create a new job instance.
* @param AdminMakeVideo $adminMakeVideo
......@@ -114,14 +118,14 @@ class MakeVideo implements ShouldQueue
$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 = "一言官方出品";
$signature = "一言 · 官方出品";
// 生成贴纸和签名
$end_wallpaper = $this->wallpaperWithSignature($end_wallpaper, $thumbnail, $signature, $font);
// 截取最后一帧
$last_frame_video = $this->getTempPath();
$width = $media_info['streams'][0]['width'];
$this->width = $width = $media_info['streams'][0]['width'];
$height = $media_info['streams'][0]['height'];
$size = $width . 'x' . $height;
$time_length = 0.7;
......@@ -138,12 +142,9 @@ class MakeVideo implements ShouldQueue
$signature_y = -20;
$animate = $this->makeAnimate($last_frame_video, $end_wallpaper, '', $signature_x, $signature_y, $font);
$font_size = ceil($width / 16);
$content = $adminMakeVideo->poem->content . PHP_EOL;
$content_position = $adminMakeVideo->getContentPosition();
$watermark = Storage::disk('public')->path('ffmpeg/LOGO_eng.png');
$font_family = Storage::disk('public')->path('ffmpeg/arialuni.ttf');
$this->getTextContentString($adminMakeVideo);
$watermark = Storage::disk('public')->path('ffmpeg/LOGO_eng.png');
$video = $this->getTempPath('.mp4',false);
$cmd = $this->ffmpeg . ' -y '.
......@@ -152,15 +153,8 @@ class MakeVideo implements ShouldQueue
' -i ' . escapeshellarg($watermark).
$audio_input .
' -filter_complex "[0:0] ' .
'drawtext="'.
'fontfile=' . escapeshellarg($font_family) . ':' .
'text=' . escapeshellcmd($content) . ':'.
'fontsize='.$font_size.':'.
'fontcolor=white@1.0:'.
'x=' . escapeshellarg($content_position[0]) . ':' .
'y=' . escapeshellarg($content_position[1]) . ':' .
'box=1:boxcolor=0xd0cdcc@0.5'.
'" [text];'.
$this->getTextContentString().
'[text];[text]'.
'[text] [2:v]overlay=20:20[water];[water]' . $audio_filter . '[1:0][1:1] concat=n=2:v=1:a=1[v][a]" ' .
' -map [v] -map [a]'.
' -c:v libx264 -bt 256k -r 25' .
......@@ -600,4 +594,102 @@ class MakeVideo implements ShouldQueue
return false;
}
}
public function getTextContentString()
{
$components = $this->adminMakeVideo->temp->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 . PHP_EOL;
$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=' . escapeshellcmd($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 '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);
}
}
......
......@@ -17,9 +17,9 @@ class VideoTemp extends Model
];
const POSITION_FFMPEG = [
'topLeft' => [0, 0], 'topMiddle' => ['(w-text_w)/2', 0], 'topRight' => ['w-text_w', 0],
'midLeft' => [0, '(h-text_h)/2'], 'midMiddle' => ['(w-text_w)/2', '(h-text_h)/2'], 'midRight' => ['w-text_w', '(h-text_h)/2'],
'botLeft' => [0, 'h-text_h'], 'botMiddle' => ['(w-text_w)/2', 'h-text_h'], 'botRight' => ['w-text_w', 'h-text_h'],
'topLeft' => ['0', 'text_h'], 'topMiddle' => ['(w-text_w)/2', 'text_h'], 'topRight' => ['w-text_w', 'text_h'],
'midLeft' => ['0', '(h-text_h)/2'], 'midMiddle' => ['(w-text_w)/2', '(h-text_h)/2'], 'midRight' => ['w-text_w', '(h-text_h)/2'],
'botLeft' => ['0', 'h-text_h*2'], 'botMiddle' => ['(w-text_w)/2', 'h-text_h*2'], 'botRight' => ['w-text_w', 'h-text_h*2'],
];
protected $table = 'video_temp';
......