На цьому тижні я зробив дійсно крутий маленький пакет, який повинен зацікавити @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))))))))
Те, що ми робимо тут з позицією, - це ініціалізація його просто з верхньої частини кадру, очікування однієї секунди, перехід його в центр кадру протягом 2 секунд з ослабленням EaseOutBounce, утримання цієї позиції протягом 1 секунди, а потім перехід безпосередньо за межі нижньої частини кадру з y
ослабленням EaseInElastic протягом 2 секунд, і зберігаючи його там, поки відео не закінчиться.
Цей опис може здатися не таким складним, але давайте коротко розглянемо математику для однієї з цих функцій полегшення та подивимося, що нам потрібно зробити, щоб підключити її до ffmpeg.
Ось функція в typescript і результуючий графік для 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
). У FFMpeg-speak це (t-1)/2
. Це тому, що, хоча зазвичай ми робимо 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
.
Щоб отримати наше , ми використовуємо наше початкове положення () (відразу за межами верхньої частини кадру), і наше delta
кінцеве положення (from
to
) -th
((main_h/2)-(th/2)
вертикальний центр кадру). Це дає нам Для delta
((main_h/2)-(th/2))-(-th)
нашого останнього кроку в цьому підлітку, ми збираємося сказати ffmpeg, що якщо менше delay
, використовувати наше значення, якщо t
t
більше нашого плюс наш використовувати значення, або ж додати наше from
from
значення до нашого delay
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})))";
}
duration
to
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
заходьте.
Ось частина сценарію тестування з цього пакета, яку я використовував для створення цього першого відео:Ви можете встановити пакет за допомогою 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}";
Ось дещо змінена частина generateEasings
generateTimeline
сценарію тестування для створення сюжету з EaseOutBounce
середини цієї публікації:
composer require projektgopher/laravel-ffmpeg-tools
shell cmd для plot
Ось дещо змінена частина generateEasings
generateTimeline
сценарію тестування для створення сюжету з 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
цінність не прив'язана доt
, а скоріше осі нашого сюжетуx
.