На этой неделе я сделал действительно классный маленький пакет, который должен заинтересовать @laravelphp и @php разработчиков, которые работают с @ffmpeg. Но чтобы понять, почему я так взволнован этим, мне нужно объяснить, сколько работы требуется для создания такого 👇 простого видео.
Вот последняя команда оболочки, которая сгенерирует это видео:
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
Я могу разбить это дальше, если людям интересно, но на самом деле нас беспокоит y
только параметр видеофильтраdrawtext
.
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
, - это инициализация его прямо из верхней части кадра, ожидание одной секунды, перемещение его в центр кадра в течение 2 секунд с ослаблением EaseOutBounce, удержание этого положения в течение 1 секунды, а затем переход к нижней части кадра с ослаблением EaseInElastic в течение 2 секунд, и держать его там до тех пор, пока видео не закончится.
Это описание может показаться не таким уж сложным, но давайте кратко рассмотрим математику для одной из этих функций смягчения и посмотрим, что нам нужно сделать, чтобы подключить ее к ffmpeg.
Вот функция в машинописном тексте и результирующий график для EaseOutBounce (из Easings.net)Примечание
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
). Говоря языком (t-1)/2
FFMpeg, это . Это связано с тем, что, хотя обычно мы делаем t/duration
, мы должны учитывать задержку, вычитая ее из numerator
нашей дроби.
Теперь супер валовая часть. Мы должны превратить эту довольно простую функцию ts в функцию FFMpeg и заменить каждую ссылку на x
нашу. Для этого нам понадобятся три функции FFMpeg: if
, lt
(меньше) и pow
. Поскольку мы переназначаем значение времени (x
) в этих условных выражениях, мы просто заранее сделаем их отдельно. Вот функция PHP со всеми предготовыми терминами, а затем объединенными для формирования строки смягчения:Запуск нашего x
значения (t-1)/2
через эту функцию дает нам следующий вывод:
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})))";
}
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)))
Это наш ease
.
Чтобы получить наше , мы используем наше начальное -th
положение () (сразу за пределами верхней части кадра) и наше delta
конечное положение () (main_h/2)-(th/2)
(from
to
вертикальный центр кадра). Это дает нам Для delta
((main_h/2)-(th/2))-(-th)
нашего последнего шага в этой анимации мы собираемся сказать ffmpeg, что если меньше , delay
чтобы использовать наше значение, если t
t
больше, чем наш плюс наш duration
delay
использовать to
значение, или же добавить наше значение к нашему delta
умноженному на наше .
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})))";
}
from
from
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)))))))
Должен быть лучший способ
Что ж, я рад, что вы спросили.
Введите 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);
Но у нас была не только одна анимация движения, чтобы добраться до окончательного y
значения нашего drawtext
фильтра в начале поста. Вот тут-то Timelines
и Keyframes
вступают в игру.
Вот часть generateTimeline
сценария тестирования из этого пакета, который я использовал для создания этого первого видео:Вы можете установить пакет через composer:Текущая версия на момент написания этой статьи - v0.5.0
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}";
из EaseOutBounce
середины этого поста:
composer require projektgopher/laravel-ffmpeg-tools
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}";
«Интересно» здесь то, что наша $time
ценность привязана не к оси, а скорее x
к t
оси нашего графика.