Play two videos side by side for comparison

Updated Reverend Jim 1 Tallied Votes 851 Views Share

Comparing two videos side by side

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)

  1. ffplay (included with ffmpeg version 4.2.3) download
  2. ffprobe (included with ffmpeg)
  3. AutoIt (version 3) download
  4. Python (version 3.8 or newer) download
  5. 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.

Making sure you have the tools

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.

Running the application

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

Just replace ext and 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.

The code:

"""
    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[1]
video2 = sys.argv[2]

if video2 == '-same':
    video2 = video1

if len(sys.argv) > 3:
    if sys.argv[3].lower() == '-grey':
        args = '-vf colorchannelmixer=.3:.4:.3:0:.3:.4:.3:0:.3:.4:.3'
    elif sys.argv[3].lower() == '-sepia':
        args = '-vf colorchannelmixer=.393:.769:.189:0:.349:.686:.168:0:.272:.534:.131'
    elif sys.argv[3].lower() == '-contrast' and len(sys.argv) > 4:
        cval = sys.argv[4].strip('0')
        args = '-vf colorlevels=rimin=%s:gimin=%s:bimin=%s' % (cval, cval, cval)
    elif sys.argv[3].lower() == '-gamma' and len(sys.argv) > 4:
        gval = sys.argv[4].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
rproffitt commented: Double plus good! +16
Reverend Jim 4,780 Hi, I'm Jim, one of DaniWeb's moderators. Moderator Featured Poster

And here it is:

    """
        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[1]
    video2 = sys.argv[2]

    if video2 == '-same':
        video2 = video1

    if len(sys.argv) > 3:
        if sys.argv[3].lower() == '-grey':
            args = '-vf colorchannelmixer=.3:.4:.3:0:.3:.4:.3:0:.3:.4:.3'
        elif sys.argv[3].lower() == '-sepia':
            args = '-vf colorchannelmixer=.393:.769:.189:0:.349:.686:.168:0:.272:.534:.131'
        elif sys.argv[3].lower() == '-contrast' and len(sys.argv) > 4:
            cval = sys.argv[4].strip('0')
            args = '-vf colorlevels=rimin=%s:gimin=%s:bimin=%s' % (cval, cval, cval)
        elif sys.argv[3].lower() == '-gamma' and len(sys.argv) > 4:
            gval = sys.argv[4].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()

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

    #aut.WinWaitActive(title1, '', 5)
    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()

    #aut.WinWaitActive(title2, '', 5)
    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
Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.