• Czas czytania ~5 min
  • 19.04.2023

W tym tygodniu zrobiłem naprawdę fajny mały pakiet, który powinien zainteresować @laravelphp i @php deweloperów, którzy pracują z @ffmpeg. Ale aby zrozumieć, dlaczego jestem tak podekscytowany, muszę wyjaśnić, ile pracy wymaga wygenerowanie prostego filmu, takiego jak ten 👇.

Oto ostatnie polecenie powłoki, które wygeneruje ten film:

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

Mogę to dalej podzielić, jeśli ludzie są zainteresowani, ale tak naprawdę martwimy się tylko o y parametr filtra drawtext wideo.

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))))))))

To, co robimy tutaj z pozycją, to inicjowanie jej tuż poza górną częścią ramki, czekanie jednej sekundy, przeniesienie jej do środka ramki przez 2 sekundy z łatwością EaseOutBounce, utrzymywanie tej pozycji przez 1 sekundę, a następnie przejście na zewnątrz dolnej części ramki z y łatwością EaseInElastic przez 2 sekundy, i trzymanie go tam aż do zakończenia filmu.

Ten opis może nie brzmieć tak ciężko, ale rzućmy okiem na matematykę dla jednej z tych funkcji łagodzenia i zobaczmy, co musimy zrobić, aby podłączyć to do ffmpeg.

Oto funkcja w maszynopisie i wynikowy wykres dla EaseOutBounce (od 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;
    }
}

Uwaga Ponownie przypisujemy x w tych warunkach.

Cała ta funkcja przyjmuje liczbę z zakresu od 0 do 1, która reprezentuje nasz absolutny postęp w animacji i daje nam inną liczbę od 0 do 1. Następnie używamy tej liczby pomnożonej przez nasządelta, aby obliczyć naszą wartość w tym punkcie naszej animacji, lub tween.

Najpierw umieśćmy kilka stałych, do których mamy dostęp w filtrzedrawtext:

  • t = aktualny czas w sekundach dla generowanej klatki
  • th = wysokość tekstu
  • main_h = wysokość klatki wideo

Aby uzyskać x (nasz bezwzględny postęp w tej animacji), będzie to bieżący czas () minus opóźnienie (), czas trwania (t12). W FFMpeg-speak to (t-1)/2jest . Dzieje się tak dlatego, że podczas gdy normalnie byśmy to zrobilit/duration, musimy uwzględnić opóźnienie, odejmując je od numerator naszego ułamka.

Teraz jest super brutto część. Musimy przekształcić tę dość prostą funkcję ts w funkcję FFMpeg i zastąpić każde odniesienie naszymx. Aby to zrobić, będziemy potrzebować trzech funkcji FFMpeg: if, lt (mniej niż) i pow. Ponieważ ponownie przypisujemy wartość czasu (x) w tych warunkach, po prostu wcześniej zrobimy je osobno. Oto funkcja PHP z terminami wstępnie utworzonymi, a następnie połączonymi w celu utworzenia ciągu łagodzącego: Uruchomienie naszej x wartości (t-1)/2 przez tę funkcję daje nam to wyjście:

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)))

To jest nasz ease.

Aby uzyskać nasze delta, używamy naszej początkowej pozycji () (tuż poza górną częścią ramki) i naszej końcowej pozycji () (tofrom-th (main_h/2)-(th/2) pionowego środka ramki). To daje nam Dla ((main_h/2)-(th/2))-(-th)

naszego ostatniego kroku w tej animacjidelta, powiemy ffmpeg, że jeśli jest mniejsze niż , aby użyć naszej wartości, jeśli t t jest większa niż delaynasza delay plus naszduration, aby użyć wartości, to lub dodaj naszą wartość do naszej from delta pomnożonej przez naszą ease.from

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})))";
}

To daje nam ten ostatni ciąg dla tej konkretnej części animacji:

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)))))))

Musi być lepszy sposób

Cóż, cieszę się, że pytasz.

Wpisz projektgopher/laravel-ffmpeg-tools - pakiet, o którym chciałem ci opowiedzieć.

Dzięki temu pakietowi generowanie tego ciągu jest tak proste, jak

(new Tween())
    ->from("-th")
    ->to("(main_h/2)-(th/2)")
    ->delay(Timing::seconds(1))
    ->duration(Timing::seconds(2))
    ->ease(Ease::OutBounce);

Ale nie mieliśmy tylko tej jednej animacji, aby uzyskać ostateczną y wartość naszego drawtext filtra na początku postu. To jest gdzie Timelines i Keyframes wejdź.

Oto część skryptu testowego z tego pakietu, którego użyłem do stworzenia tego pierwszego filmu: Możesz zainstalować pakiet za pomocą kompozytora: Obecna wersja w momencie pisania tego tekstu to

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}";

v0.5.0 Oto nieco zmodyfikowana część skryptu generateEasings testowego, aby wygenerować wykres od EaseOutBounce środka tego postugenerateTimeline:

composer require projektgopher/laravel-ffmpeg-tools

shell cmd dla fabuły

Oto nieco zmodyfikowana część skryptu generateEasings testowego, aby wygenerować wykres od EaseOutBounce środka tego postugenerateTimeline:

// $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}";

"Ciekawe" jest to, że nasza $time wartość nie jest związana, t ale raczej osią x naszej działki.

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

O

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...

O autorze CrazyBoy49z
WORK EXPERIENCE
Kontakt
Ukraine, Lutsk
+380979856297