• Время чтения ~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))))))))

То, что мы делаем здесь с положением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 (наш абсолютный прогресс в этой анимации), это будет текущее время () минус задержка (), по длительности (t12). Говоря языком (t-1)/2FFMpeg, это . Это связано с тем, что, хотя обычно мы делаем 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) (fromtoвертикальный центр кадра). Это дает нам Для 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 оси нашего графика.

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