I have been creating walk-through videos on the cheap by just wandering through an area with my little Sony camera. Lacking a steady-cam I just try to hold the camera as steady as possible. Fortunately, with the proper (free) tools I can still end up with a reasonable result. It's nice to be able to compare before and after videos so I'll describe the application I put together, and the tools I used to build it.
First the tools (again, all free)
- ffplay (included with ffmpeg version 4.2.3) download
- ffprobe (included with ffmpeg)
- AutoIt (version 3) download
- Python (version 3.8 or newer) download
- VirtualDubMod (optional - version 1.10.5) download
At the heart of it all is
ffplay.exe which comes bundled with ffmpeg. Ffmpeg is a free and open source command line video suite that with a little effort (warning - there is a learning curve) is unbelievably versatile. While
ffmpeg.exe is the main engine that you'll use to apply complex filters or convert formats,
ffplay.exe offers a small, simple playback utility as well as a way to preview effects in real time.
ffprobe.exe is used to get the frame size of the video.
AutoIt (or more specifically AutoItX) is used to control windows/applications from within Python. In this case, I use it to resize and place the playback windows which will be approximately half the screen wide, and placed side by side (thus the name of the application).
Python, of course, will be used to tie everything together.
Unless you are de-shaking video you won't need VirtualDubMod.
Once you have downloaded ffmpeg, unzip it into a folder and add the full path of ffmpeg\bin to your system PATH environment variable.
To use AutoItX you'll also need the Python interface module which you can get by running
python -m pip install --upgrade pip pip install PyAutoIt pip install pywin32
The first line upgrades to the latest version of pip. In my experience, this should always be done whenever you run pip. It updates frequently.
PyAutoIt is the interface to AutoIt and
pywin32 is the interface to various Windows components.
In it's simplest form, you can run two videos side by side by typing
SideBySide video1.ext video2.ext
or by creating a shortcut to SideBySide.py on your desktop and dragging the two videos onto it.
The videos do not have to be the same size or even the same format, however, they should be at least the same aspect ratio. You may want to compare the quality of a video after converting it to another format, resizing it, or decreasing the bitrate. You may also want to compare the result of tweaking a video before actually applying the tweak to the entire video. For that you specify the video file as the first parameter and
-same as the second parameter. This tells the app to use the same video for both windows. That's not very useful unless you also specify an ffmpeg video filter. For example to apply a blur filter you can type:
SideBySide video.ext -same -vf smartblur=5:0.8:0
To increase the contrast slightly you can type:
SideBySide video.ext -same -vf colorlevels=rimin=0.1:gimin=0.1:bimin=0.1
The parameters are obtuse but there is lots of documentation explaining the options. You can find it in the ffmpeg docs that were installed when you unzipped it, or you can google
ffmpeg video filters. Because of a slight delay in loading the videos, the two windows may not be exactly in sync, but they should be close enough to make an easy comparison possible.
I've added command line options for some of the more common effects.
-gamma #.# gamma correction (see ffmpeg doc for details) -contrast #.# contrast correction (see ffmpeg doc for details) -grey convert to grey scale -sepia convert to sepia
As an aside, I've seen lots of questions over the years asking "how do I convert from format X to format Y?" In ffmpeg, to convert (for example) from AVI to MP4 you type:
ffmpeg -i input.avi output.mp4
Of course you can also apply a ton of options and filters between the two file names. For example, to rescale a video to 1280x720 you type
ffmpeg -i input.ext -s 1280x720 output.mp4
mp4 with your extensions of choice. You can also use ffmpeg to convert image, and even subtitle formats.
If anyone is interested in learning how to de-shake a video using VirtualDubMod please post the request in this thread and I'll be happy to write something up.
""" Name: SideBySide.py Description: Given two video files, runs them concurrently in two side by side instances of ffplay. Ths is very useful when you have processed a video and want to compare the original with the processed version. If you want to test a process (e.g. a filter) before processing the entire video, run the script by specifying -same as the second video as in SideBySide video1.mp4 -same -vf smartblur=5:0.8:0 Try the following filter to increase the contrast -vf colorlevels=rimin=0.2:gimin=0.2:bimin=0.2 Convert to greyscale -vf colorchannelmixer=.3:.4:.3:0:.3:.4:.3:0:.3:.4:.3 Convert to sepia -vf colorchannelmixer=.393:.769:.189:0:.349:.686:.168:0:.272:.534:.131 adjust gamma/saturation -vf eq=gamma=1.5:saturation=1.3 Requires: Python version 3.8 or later ffmpeg (which includes ffplay) autoit (version 3) Usage: SideBySide video1 video2 Notes: Regardless of the dimensions of the input videos, they will always be scaled so that they can be placed side by side, each filling just under half the width of the display. I haven't verified this, but I'm assuming that manipulating windows by handle rather than by name is more efficient which may be a consideration because I do it repeatedly in the wait loop at the bottom. Audit: 2021-08-31 rj original code """ import os import re #needed to extract video frame size import tkinter #needed to get display size import win32com.client #needed to create the AutoId com object import subprocess #needed to run ffprobe.exe import sys import time def DisplaySize(): """Returns the monitor display resolution as (width, height)""" root = tkinter.Tk(None) return root.winfo_screenwidth(), root.winfo_screenheight() def VideoSize(file): """Returns the frame size of a video as (width, height)""" #Run ffprobe to get video info res = subprocess.run(['ffprobe', '-i', file], shell=True, stderr=subprocess.PIPE, text=True) #Extract frame size for line in res.stderr.split('\n'): if 'Video:' in line: if (search := re.search(' \d+x\d+ ', line)): w,h = line[1+search.start():search.end()-1].split('x') return int(w), int(h) return 0, 0 def WaitFor(title, timeout): """Waits for up to timeout seconds for the window with the given title to be active""" timeout *= 10 while not aut.WinActive(title): time.sleep(0.1) timeout -= 1 if timeout == 0: print('expired') sys.exit() return #check for sufficient number of parameters if len(sys.argv) < 3: print(""" SideBySide video1 video2 Displays two videos side by side for comparison. This is useful to see before and after video effects such as colour/contrast manipulation or scaling. If you want to try some ffmpeg filters before applying them to a complete video you can supply ffmpeg parameters ad hoc. To use the same video for both panels specify '-same' as the second video. For example, to see the effect of a gamma correction you can type: sidebyside video.mp4 -same -vf eq=gamma=0.9 To save you the trouble of remembering ffmpeg filters several shortcuts are provided as follows: sidebyside video.mp4 -same -gamma 0.9 apply gamma correction sidebyside video.mp4 -same -contrast .12 apply contrast correction sidebyside video.mp4 -same -grey convert to greyscale sidebyside video.mp4 -same -sepia convert to sepia """) sys.exit() #get file names and command line options video1 = sys.argv video2 = sys.argv if video2 == '-same': video2 = video1 if len(sys.argv) > 3: if sys.argv.lower() == '-grey': args = '-vf colorchannelmixer=.3:.4:.3:0:.3:.4:.3:0:.3:.4:.3' elif sys.argv.lower() == '-sepia': args = '-vf colorchannelmixer=.393:.769:.189:0:.349:.686:.168:0:.272:.534:.131' elif sys.argv.lower() == '-contrast' and len(sys.argv) > 4: cval = sys.argv.strip('0') args = '-vf colorlevels=rimin=%s:gimin=%s:bimin=%s' % (cval, cval, cval) elif sys.argv.lower() == '-gamma' and len(sys.argv) > 4: gval = sys.argv.strip('0') args = '-vf eq=gamma=%s' % gval else: args = ' '.join(sys.argv[3:]) else: args = '' if not os.path.isfile(video1): print('Could not find:', video1) sys.exit() if not os.path.isfile(video2): print('Could not find:', video2) sys.exit() #personal hack - when I deshake a video I add '-ds' to the end of #the filename. The following forces the -ds video to the right hand #frame. Feel free to remove these two lines. if '-ds.' in video1: video1,video2 = video2,video1 #create unique window titles title1 = '1: ' + video1 title2 = '2: ' + video2 #create the AutoIt com object aut = win32com.client.Dispatch("AutoItX3.Control") aut.Opt("WinTitleMatchMode", 3) #3 = Match Exact Title String) #get the display width and height, and same for video dw,dh = DisplaySize() vw,vh = VideoSize(video1) aspect = vw / vh #Calculate size and position of playback windows vw = int((dw-20) / 2) vh = int(vw / aspect) x1 = '10' y1 = '35' x2 = str(int((dw/2)) + 5) y2 = '35' #set up the commands to run ffplay # -v 0 suppresses the standard ffplay output # -window_title guarantees unique windo titles even if using the same video cmd1 = 'ffplay -v 0 -window_title "' + title1 + '" -i "' + video1 + '"' \ + ' -x ' + str(vw) + ' -y ' + str(vh) + ' -left ' + x1 + ' -top ' + y1 cmd2 = 'ffplay -v 0 -window_title "' + title2 + '" -i "' + video2 + '" ' + args \ + ' -x ' + str(vw) + ' -y ' + str(vh) + ' -left ' + x2 + ' -top ' + y2 #Run ffplay on the first video. Wait for it to be active then get the handle. print('\n' + cmd1) if (p1 := aut.Run(cmd1)) == 0: print('Could not start ffplay.exe') sys.exit() WaitFor(title1, 5) handle1 = aut.WinGetHandle(title1) handle1 = '[HANDLE:%s]' % handle1 #print('video 1 active - handle is', handle1) #Run ffplay on the second video. Wait for it to be active then get the handle. print('\n' + cmd2) if (p2 := aut.Run(cmd2)) == 0: print('Could not start ffplay.exe') sys.exit() WaitFor(title2, 5) handle2 = aut.WinGetHandle(title2) handle2 = '[HANDLE:%s]' % handle2 #print('video 2 active - handle is', handle2) #This loop will terminate on CTRL-C or when both video players are closed try: while aut.WinExists(handle1) or aut.WinExists(handle2): time.sleep(1) except: pass