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 (t
1
2
). W FFMpeg-speak to (t-1)/2
jest . 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 () (to
from
-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ż delay
nasza 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.