Video Mosaic in python ffmpeg : success!
I have been working on a way to take a bunch of videos and tile them to play side by side, and the end result is below:
How do we do it? Well, ffmpeg seems like the most powerful and common tool to do editing link this. But the syntax, at least for a python plebian like myself, is completely impenetrable. For example, here is a stack overflow answer :
ffmpeg -i lead_1.mp4 -i lead_2.mp4 -i lead_3.mp4 -i lead_4.mp4
-filter_complex
"color=s=1280x720:c=black [base];
[0:v] setpts=PTS-STARTPTS, scale= 640x360 [upperleft1];
[1:v] setpts=PTS+35/TB, scale=640x360 [lowerright1];
[2:v] setpts=PTS+87/TB, scale=640x360 [upperleft2];
[3:v] setpts=PTS+183/TB, scale=640x360 [lowerright2];
[base][upperleft1] overlay=1 [tmp1];
[tmp1][lowerright1] overlay=1:x=640:y=360 [tmp2];
[tmp2] [upperleft2] overlay=1 [tmp3];
[tmp3][lowerright2] overlay=1:x=640:y=360"
-c :v libx264 lead_1_2_3_4.mp4
There is an explanation of what is going on above on this page , but even copying and pasting as hard as I could… I was unable to get answers like this to work. So Instead I turned to python-ffmpeg which you can download with conda-forge. This allowed me to write a short function that would do the job I needed, while also being relatively readable. See below for my function:
import os , ffmpeg
import numpy as np
def video_tile ( vid_list , row_length , save_name = 'out.mp4' ):
'''
takes in a list of same sized videos and tiles them into a single video at 'save_name'.
important: will overwrite anything that has the file name of the output file
only nonstandard dependency is python ffmpeg
Parameters
----------
vid_list: list of video files
the tiled videos are made from this list,
will probably act weird if their sizes are not the same
row_length: int
number of videos in each row
save_name: str
where the video will be saved and what format
Returns
-------
None: there are no returns, it just saves the video to 'save_name'
'''
N = len ( vid_list )
column_length = int ( np . ceil ( N / row_length ))
videoprobe = ffmpeg . probe ( vid_list [ 0 ]) #get info on video
vwidth , vheight = videoprobe [ "streams" ][ 0 ][ "width" ], videoprobe [ "streams" ][ 0 ][ "height" ] # get dims of fiirst video
videowidth = vwidth * row_length
videoheight = vheight * column_length
video_list = [ ffmpeg . input ( item ) for item in vid_list ] #import all videos into ffmpeg
videostr = ffmpeg . filter ( video_list [ 0 ], "pad" , videowidth , videoheight ) #sets the first video in large frame
#now we just loop over the list, placing the videos onto the main video stream as we go
for i , item in enumerate ( video_list ):
if i > 0 :
j = i // row_length
k = i - row_length * j
videostr = ffmpeg . overlay ( videostr , item , x = k * vwidth , y = j * vheight ) #overlay image
#this is to avoid an error when the target file for the save already exists
try : os . remove ( save_name )
except : pass
videostr = ffmpeg . output ( videostr , save_name )
ffmpeg . run ( videostr )
The video in the post was generated by video_tile(video_list, 3, ‘video_test.mp4’)