• Час читання ~4 хв
  • 19.04.2023

На цьому тижні я зробив дійсно крутий маленький пакет, який повинен зацікавити @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 (наш абсолютний прогрес через цю анімацію), це буде поточний час () мінус затримка (), протягом тривалості (t12). У 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кінцеве положення (fromto) -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.

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Про автора CrazyBoy49z
WORK EXPERIENCE
Контакти
Ukraine, Lutsk
+380979856297