在Laravel框架中的FFMpeg工具包:laravel-ffmpeg-tools
这周我做了一个非常酷的小包,应该会引起@laravelphp和@php开发者的兴趣,他们都是用@ffmpeg工作的。但要理解我为什么对它如此兴奋,我需要解释一下像这样一个简单的视频需要多少工作才能生成。
这里是最后的shell命令,将生成该视频:
ffmpeg -y -f lavfi -i "color=c=black:s=256x256:d=1" -filter_complex "[0:v] loop=-1:1 [bg]; [bg] drawtext=text='Motion Tween':fontcolor=white:x=(main_w/2)-(tw/2):y=if(gt(t\,4)\,if(lt(t\,4)\,((main_h/2)-(th/2))\,if(gt(t\,4+2)\,(main_h)\,((main_h/2)-(th/2))+(((main_h)-((main_h/2)-(th/2)))*(if(eq(((t-4)/2)\,0)\,0\,if(eq(((t-4)/2)\,1)\,1\,-pow(2\,10*((t-4)/2)-10)*sin((((t-4)/2)*10-10.75)*2.0943951023932)))))))\,if(lt(t\,1)\,(-th)\,if(gt(t\,1+2)\,((main_h/2)-(th/2))\,(-th)+((((main_h/2)-(th/2))-(-th))*(if(lt(((t-1)/2)\, 1/2.75)\,7.5625*pow(((t-1)/2)\,2)\,if(lt(((t-1)/2)\,2/2.75)\,7.5625*(((t-1)/2)-1.5/2.75)*(((t-1)/2)-1.5/2.75)+0.75\,if(lt(((t-1)/2)\,2.5/2.75)\,7.5625*(((t-1)/2)-2.25/2.75)*(((t-1)/2)-2.25/2.75)+0.9375\,7.5625*(((t-1)/2)-2.65/2.75)*(((t-1)/2)-2.65/2.75)+0.984375))))))))" -codec:a copy -codec:v libx264 -crf 25 -pix_fmt yuv420p -t 8 drawtext_y_enter-OutBounce_exit-InElastic.mp4
如果大家有兴趣,我可以进一步分解,但实际上我们只关心drawtext视频过滤器的y参数。
if(gt(t\,4)\,if(lt(t\,4)\,((main_h/2)-(th/2))\,if(gt(t\,4+2)\,(main_h)\,((main_h/2)-(th/2))+(((main_h)-((main_h/2)-(th/2)))*(if(eq(((t-4)/2)\,0)\,0\,if(eq(((t-4)/2)\,1)\,1\,-pow(2\,10*((t-4)/2)-10)*sin((((t-4)/2)*10-10.75)*2.0943951023932)))))))\,if(lt(t\,1)\,(-th)\,if(gt(t\,1+2)\,((main_h/2)-(th/2))\,(-th)+((((main_h/2)-(th/2))-(-th))*(if(lt(((t-1)/2)\, 1/2.75)\,7.5625*pow(((t-1)/2)\,2)\,if(lt(((t-1)/2)\,2/2.75)\,7.5625*(((t-1)/2)-1.5/2.75)*(((t-1)/2)-1.5/2.75)+0.75\,if(lt(((t-1)/2)\,2.5/2.75)\,7.5625*(((t-1)/2)-2.25/2.75)*(((t-1)/2)-2.25/2.75)+0.9375\,7.5625*(((t-1)/2)-2.65/2.75)*(((t-1)/2)-2.65/2.75)+0.984375))))))))
我们在这里对y位置所做的是初始化它在帧的顶部,等待一秒钟,
用EaseOutBounce的缓和方式在2秒内将它过渡到帧的中心,保持这个位置1秒钟,
然后用EaseInElastic的缓和方式在2秒内过渡到帧的底部,并保持它直到视频结束。
这个描述可能听起来并不难,
但让我们快速看一下这些缓和函数的数学运算,
看看我们需要做什么来把它插入ffmpeg。
下面是这个函数的typescript和EaseOutBounce的结果图(来自Easings.net)。
https://easings.net/#easeOutBounce
function easeOutBounce(x: number): number {
const n1 = 7.5625;
const d1 = 2.75;
if (x < 1 / d1) {
return n1 * x * x;
} else if (x < 2 / d1) {
return n1 * (x -= 1.5 / d1) * x + 0.75;
} else if (x < 2.5 / d1) {
return n1 * (x -= 2.25 / d1) * x + 0.9375;
} else {
return n1 * (x -= 2.625 / d1) * x + 0.984375;
}
}
> 注意 我们在这些条件语句中重新分配了x。
这个函数所做的就是接收一个介于0和1之间的数字,代表我们在动画中的绝对进度,
然后给我们一个介于0和1之间的不同数字,然后用这个数字与我们的delta相乘来计算我们在动画这一点上的值,或者说tween。
首先让我们列出几个常量,我们可以在drawtext过滤器中使用:
t = 正在生成的这一帧的当前时间(秒)。
th = 文本高度
main_h = 视频帧的高度
为了得到x(我们在这个动画中的绝对进度),它将是当前时间(t)减去延迟(1),再加上持续时间(2)。
用FFMpeg的话说,就是(t-1)/2。
这是因为,虽然通常我们会做t/持续时间,但我们必须考虑到延迟,从我们的分数的分子中减去它。
现在是超级严重的部分。
我们必须把那个非常简单的ts函数变成一个FFMpeg函数,用我们的函数替换所有对x的引用。
要做到这一点,我们需要三个FFMpeg函数:
if、lt(小于)和pow
因为我们要在这些条件中重新分配时间值(x),所以我们要事先把它们分开。
这里有一个PHP函数,所有的条件都是预先制作好的,然后串联起来形成缓和字符串:
public static function EaseOutBounce(string $time): string
{
$n1 = 7.5625;
$d1 = 2.75;
$firstExpr = "{$n1}*pow(({$time})\\,2)";
$secondTime = "(({$time})-1.5/{$d1})";
$secondExpr = "{$n1}*{$secondTime}*{$secondTime}+0.75";
$thirdTime = "(({$time})-2.25/{$d1})";
$thirdExpr = "{$n1}*{$thirdTime}*{$thirdTime}+0.9375";
$fourthTime = "(({$time})-2.65/{$d1})";
$fourthExpr = "{$n1}*{$fourthTime}*{$fourthTime}+0.984375";
return "if(lt(({$time})\\, 1/{$d1})\\,{$firstExpr}\\,if(lt(({$time})\\,2/{$d1})\\,{$secondExpr}\\,if(lt(({$time})\\,2.5/{$d1})\\,{$thirdExpr}\\,{$fourthExpr})))";
}
通过这个函数运行我们的x值(t-1)/2,可以得到这样的输出:
if(lt(((t-1)/2)\, 1/2.75)\,7.5625*pow(((t-1)/2)\,2)\,if(lt(((t-1)/2)\,2/2.75)\,7.5625*(((t-1)/2)-1.5/2.75)*(((t-1)/2)-1.5/2.75)+0.75\,if(lt(((t-1)/2)\,2.5/2.75)\,7.5625*(((t-1)/2)-2.25/2.75)*(((t-1)/2)-2.25/2.75)+0.9375\,7.5625*(((t-1)/2)-2.65/2.75)*(((t-1)/2)-2.65/2.75)+0.984375)))
这就是我们的轻松。
为了得到我们的delta,我们使用我们的初始位置(从)-th(就在框架的顶部之外),和我们的最终位置(到)(main_h/2)-(th/2)(框架的垂直中心)。
这给了我们一个
delta((main_h/2)-(th/2))-(-th)
在这个Tween的最后一步,
我们要告诉ffmpeg,如果t小于延迟,就使用我们的from值,
如果t大于我们的延迟加上我们的持续时间,就使用to值,
否则就把我们的from值加上delta乘以我们的ease。
public function build(): string
{
return "if(lt(t\,{$this->delay})\,{$this->from}\,if(gt(t\,{$this->delay}+{$this->duration})\,{$this->to}\,{$this->from}+({$this->getDelta()}*{$this->ease})))";
}
这给了我们这个动画特定部分的最终字符串:
if(lt(t\,1)\,(-th)\,if(gt(t\,1+2)\,((main_h/2)-(th/2))\,(-th)+((((main_h/2)-(th/2))-(-th))*(if(lt(((t-1)/2)\, 1/2.75)\,7.5625*pow(((t-1)/2)\,2)\,if(lt(((t-1)/2)\,2/2.75)\,7.5625*(((t-1)/2)-1.5/2.75)*(((t-1)/2)-1.5/2.75)+0.75\,if(lt(((t-1)/2)\,2.5/2.75)\,7.5625*(((t-1)/2)-2.25/2.75)*(((t-1)/2)-2.25/2.75)+0.9375\,7.5625*(((t-1)/2)-2.65/2.75)*(((t-1)/2)-2.65/2.75)+0.984375)))))))
一定有更好的方法 好吧,我很高兴你这么问。
我已经迫不及待地想告诉你这个软件包:
https://github.com/ProjektGopher/laravel-ffmpeg-tools
有了这个软件包,生成这个字符串就像下面这样简单了
(new Tween())
->from("-th")
->to("(main_h/2)-(th/2)")
->delay(Timing::seconds(1))
->duration(Timing::seconds(2))
->ease(Ease::OutBounce);
但我们并不是只有这一个Tween来达到我们在文章开头的drawtext过滤器的最终y值。
这就是时间线和关键帧的作用。
下面是这个包中生成时间线测试脚本的一部分,我用它来制作第一个视频:
use ProjektGopher\FFMpegTools\Timeline;
use ProjektGopher\FFMpegTools\Keyframe;
use ProjektGopher\FFMpegTools\Ease;
use ProjektGopher\FFMpegTools\Timing;
echo 'Generating video sample using Timeline...'.PHP_EOL;
$timeline = new Timeline();
$timeline->keyframe((new Keyframe())
->value('-th')
->hold(Timing::seconds(1))
);
$timeline->keyframe((new Keyframe())
->value('(main_h/2)-(th/2)')
->ease(Ease::OutBounce)
->duration(Timing::seconds(2))
->hold(Timing::seconds(1))
);
$timeline->keyframe((new Keyframe())
->value('main_h')
->ease(Ease::InElastic)
->duration(Timing::seconds(2))
);
$input = "-f lavfi -i \"color=c=black:s=256x256:d=1\"";
$filter = "-filter_complex \"[0:v] loop=-1:1 [bg]; [bg] drawtext=text='Motion Tween':fontcolor=white:x=(main_w/2)-(tw/2):y={$timeline}\"";
$codecs = '-codec:a copy -codec:v libx264 -crf 25 -pix_fmt yuv420p';
$duration = '-t 8'; // in seconds
$out = "tests/Snapshots/Timelines/drawtext_y_enter-OutBounce_exit-InElastic.mp4";
$redirect = '2>&1'; // redirect stderr to stdout
$cmd = "ffmpeg -y {$input} {$filter} {$codecs} {$duration} {$out} {$redirect}";
你可以通过composer安装该软件包:
composer require projektgopher/laravel-ffmpeg-tools
在写这篇文章时,目前的版本是v0.5.0:
https://github.com/ProjektGopher/laravel-ffmpeg-tools/tree/v0.5.0
额外的
绘图的shell cmd
这里是generateEasings测试脚本的一个稍加修改的部分,用于生成本帖中间的EaseOutBounce的情节:
// $ease->value = "OutBounce";
echo "Generating snapshot for {$ease->value} easing...".PHP_EOL;
$time = "X/H";
$easeMultiplier = Ease::{$ease->value}($time);
$input = "-f lavfi -i \"color=c=black:s=256x256:d=1\"";
$margin = '28';
$filter = "-vf \"geq=if(eq(round((H-2*{$margin})*({$easeMultiplier}))\,H-Y-{$margin})\,128\,0):128:128\"";
$out = "-frames:v 1 -update 1 tests/Snapshots/Easings/{$ease->value}.png";
$redirect = '2>&1'; // redirect stderr to stdout
$cmd = "ffmpeg -y {$input} {$filter} {$out} {$redirect}";
这里的 "有趣 "之处在于,我们的$时间值并不与t挂钩,而是与我们的绘图的X轴挂钩。
转:
https://laravel-news.com/laravel-ffmpeg-tools
相关文章