Splitting a video in a grid of videos

November 24, 2018 - by Paul Bosselaar

Recently we had a nice idea for the soft-launch of our new website for the company I work for. In a symbolic way we let almost everyone take part by using their phones to display a part of the video. In the end we had 49 phones in a 7x7 grid displaying a video with a countdown timer, showing the new website afterwards.

Pictures to video

The video was made with just still pictures, ffmpeg-concat and the commandline tool FFMPEG. The first thing was to create a video of X seconds per picture we wanted to show. In our case we had a “under construction” page showing for several minutes with some effects in between. After that a countdown with numbers from 5 to 1, and finally a still of the new website.

I created a video per picture first with the help of ffmpeg. With the concat plugin you can easily create a video of X seconds from one picture. The command to do that looks something like

ffmpeg -f concat -i input-pic1.txt pic1.mp4

The input file input-pic.txt contains the following:


The first line contains the picture, the second line is the number of seconds the picture needs to be shown in the video. You can chain multiple different pictures this way, but I needed to put some effects in between the pictures so I needed to make a different video per picture. Because of a quirk in the ffmpeg concat plugin you need to finish with the final picture.

There is one input text file per picture. After that I created a batch file to create the video of al the needed pictures:

ffmpeg -f concat -i underconstruction.txt -vf format=yuv240p -y -pix_fmt rgba uc.mp4
ffmpeg -f concat -i pic1.txt -vf format=yuv240p -y -pix_fmt rgba pic1.mp4
ffmpeg -f concat -i pic2.txt -vf format=yuv240p -y -pix_fmt rgba pic2.mp4

Next we used the ffmpeg-concat project stitch the video’s together with some ffmpeg-opengl effects in between.

ffmpeg-concat is a npm project. So we need to install it first:

npm install --save ffmpeg-concat

After installing we can create javascript code to create the video. Using ffmpeg-concat in javascript can be done with code like:

const concat = require('ffmpeg-concat');

  output: 'video.mp4',
  videos: [
  transitions: [
        name: 'GlitchMemories',
        'duration': 1000
        name: 'GlitchMemories',
        'duration': 1000

And we can run it with nodejs (which should already be installed when you have npm installed ;) )

node createvideo.js

Creating the grid

FFMPEG has an advanced plugin to create a lof of different effects. However, to create a commandline to get this to work can be a lot of work. With the plugin you can have one input, the video we just created, and our 49 outputs. Per output video we can determine which part we want to display by giving the starting coordinates and the with and the height in pixels. And we need to do that 49 times! Yes, that is a lot. So I created a small PHP script to create the commandline options for FFMPEG. The script is the following:


if ($argc < 2) {
	echo 'Usage: ' . basename(__FILE__) . ' <width> <height>' . "\r\n";

$width = intval($argv[1]);
$height = intval($argv[2]);

$total = $width * $height;

#Generate the split
echo'-filter_complex \'[0] ';
echo 'split=' . $total . ' ';
for ($i = 0; $i < $total; $i++) {
  echo '[out' . $i . ']';

# Generate the crop filters
$i = 0;
for ($y = 0; $y < $height; $y++) {
  for ($x = 0; $x < $width; $x++) {
    echo ';[out' . $i . '] crop=';
    echo 'iw/' . $width . ':ih/' . $height . ':';
    echo 'iw/' . $width . '*' . $x . ':ih/' . $height . '*' . $y;
    echo ' [out' . $i . ']';

echo '\'';

# Generate -map parameters for the output
for ($i = 0; $i < $total; $i++) {
  echo ' -map \'[out' . $i . ']\' output-' . ($i+1) . '.mkv';

The script takes two parameters to define the grid’s width and height. So to create a 7x7 grid we use the following:

php creategrid.php 7 7

The result should looks like this:

ffmpeg -i test.mp4 -filter_complex '[0] split=49 [out0][out1][out2][out3][out4][out5][out6][out7][out8][out9][out10][out11][out12][out13][out14][out15][out16][out17][out18][out19][out20][out21][out22][out23][out24][out25][out26][out27][out28][out29][out30][out31][out32][out33][out34][out35][out36][out37][out38][out39][out40][out41][out42][out43][out44][out45][out46][out47][out48];[out0] crop=iw/7:ih/7:iw/7*0:ih/7*0 [out0];[out1] crop=iw/7:ih/7:iw/7*1:ih/7*0 [out1];[out2] crop=iw/7:ih/7:iw/7*2:ih/7*0 [out2];[out3] crop=iw/7:ih/7:iw/7*3:ih/7*0 [out3];[out4] crop=iw/7:ih/7:iw/7*4:ih/7*0 [out4];[out5] crop=iw/7:ih/7:iw/7*5:ih/7*0 [out5];[out6] crop=iw/7:ih/7:iw/7*6:ih/7*0 [out6];[out7] crop=iw/7:ih/7:iw/7*0:ih/7*1 [out7];[out8] crop=iw/7:ih/7:iw/7*1:ih/7*1 [out8];[out9] crop=iw/7:ih/7:iw/7*2:ih/7*1 [out9];[out10] crop=iw/7:ih/7:iw/7*3:ih/7*1 [out10];[out11] crop=iw/7:ih/7:iw/7*4:ih/7*1 [out11];[out12] crop=iw/7:ih/7:iw/7*5:ih/7*1 [out12];[out13] crop=iw/7:ih/7:iw/7*6:ih/7*1 [out13];[out14] crop=iw/7:ih/7:iw/7*0:ih/7*2 [out14];[out15] crop=iw/7:ih/7:iw/7*1:ih/7*2 [out15];[out16] crop=iw/7:ih/7:iw/7*2:ih/7*2 [out16];[out17] crop=iw/7:ih/7:iw/7*3:ih/7*2 [out17];[out18] crop=iw/7:ih/7:iw/7*4:ih/7*2 [out18];[out19] crop=iw/7:ih/7:iw/7*5:ih/7*2 [out19];[out20] crop=iw/7:ih/7:iw/7*6:ih/7*2 [out20];[out21] crop=iw/7:ih/7:iw/7*0:ih/7*3 [out21];[out22] crop=iw/7:ih/7:iw/7*1:ih/7*3 [out22];[out23] crop=iw/7:ih/7:iw/7*2:ih/7*3 [out23];[out24] crop=iw/7:ih/7:iw/7*3:ih/7*3 [out24];[out25] crop=iw/7:ih/7:iw/7*4:ih/7*3 [out25];[out26] crop=iw/7:ih/7:iw/7*5:ih/7*3 [out26];[out27] crop=iw/7:ih/7:iw/7*6:ih/7*3 [out27];[out28] crop=iw/7:ih/7:iw/7*0:ih/7*4 [out28];[out29] crop=iw/7:ih/7:iw/7*1:ih/7*4 [out29];[out30] crop=iw/7:ih/7:iw/7*2:ih/7*4 [out30];[out31] crop=iw/7:ih/7:iw/7*3:ih/7*4 [out31];[out32] crop=iw/7:ih/7:iw/7*4:ih/7*4 [out32];[out33] crop=iw/7:ih/7:iw/7*5:ih/7*4 [out33];[out34] crop=iw/7:ih/7:iw/7*6:ih/7*4 [out34];[out35] crop=iw/7:ih/7:iw/7*0:ih/7*5 [out35];[out36] crop=iw/7:ih/7:iw/7*1:ih/7*5 [out36];[out37] crop=iw/7:ih/7:iw/7*2:ih/7*5 [out37];[out38] crop=iw/7:ih/7:iw/7*3:ih/7*5 [out38];[out39] crop=iw/7:ih/7:iw/7*4:ih/7*5 [out39];[out40] crop=iw/7:ih/7:iw/7*5:ih/7*5 [out40];[out41] crop=iw/7:ih/7:iw/7*6:ih/7*5 [out41];[out42] crop=iw/7:ih/7:iw/7*0:ih/7*6 [out42];[out43] crop=iw/7:ih/7:iw/7*1:ih/7*6 [out43];[out44] crop=iw/7:ih/7:iw/7*2:ih/7*6 [out44];[out45] crop=iw/7:ih/7:iw/7*3:ih/7*6 [out45];[out46] crop=iw/7:ih/7:iw/7*4:ih/7*6 [out46];[out47] crop=iw/7:ih/7:iw/7*5:ih/7*6 [out47];[out48] crop=iw/7:ih/7:iw/7*6:ih/7*6 [out48]' -map '[out0]' output-1.mkv -map '[out1]' output-2.mkv -map '[out2]' output-3.mkv -map '[out3]' output-4.mkv -map '[out4]' output-5.mkv -map '[out5]' output-6.mkv -map '[out6]' output-7.mkv -map '[out7]' output-8.mkv -map '[out8]' output-9.mkv -map '[out9]' output-10.mkv -map '[out10]' output-11.mkv -map '[out11]' output-12.mkv -map '[out12]' output-13.mkv -map '[out13]' output-14.mkv -map '[out14]' output-15.mkv -map '[out15]' output-16.mkv -map '[out16]' output-17.mkv -map '[out17]' output-18.mkv -map '[out18]' output-19.mkv -map '[out19]' output-20.mkv -map '[out20]' output-21.mkv -map '[out21]' output-22.mkv -map '[out22]' output-23.mkv -map '[out23]' output-24.mkv -map '[out24]' output-25.mkv -map '[out25]' output-26.mkv -map '[out26]' output-27.mkv -map '[out27]' output-28.mkv -map '[out28]' output-29.mkv -map '[out29]' output-30.mkv -map '[out30]' output-31.mkv -map '[out31]' output-32.mkv -map '[out32]' output-33.mkv -map '[out33]' output-34.mkv -map '[out34]' output-35.mkv -map '[out35]' output-36.mkv -map '[out36]' output-37.mkv -map '[out37]' output-38.mkv -map '[out38]' output-39.mkv -map '[out39]' output-40.mkv -map '[out40]' output-41.mkv -map '[out41]' output-42.mkv -map '[out42]' output-43.mkv -map '[out43]' output-44.mkv -map '[out44]' output-45.mkv -map '[out45]' output-46.mkv -map '[out46]' output-47.mkv -map '[out47]' output-48.mkv -map '[out48]' output-49.mkv

And finally we have the commandline to split the video in 49 parts. Run that, upload the 49 video’s and we are done!