INFO: How to create a Time Lapse with MV, Meraki API, Python and FFMPEG

PaulF
Meraki Employee
Meraki Employee

INFO: How to create a Time Lapse with MV, Meraki API, Python and FFMPEG

Hello all.

 

Prompted by a request from a customer, I've put together a PoC on using the Meraki API, a simple script, and FFMPEG to request snapshots from a camera every x seconds, and them convert the images into a video

 

I'm going to use Pycharm as my dev environment

 

0. Create a new project in Pycharm

So, to keep things simple, we are going to create a new project, using File > New project in Pycharm

 

Once you've done this, create:

  1. a folder called ffmpeg
  2. a folder called snapshots
  3. a file called MV-Timelapse.py

 

So, you should have :

Screenshot 2024-03-26 at 14.37.23.png

 

1. Libraries

We are going to use two libraries that need to be installed, a handful of included libraries, and the FFMPEG command line utility

 

Open Terminal within Pycharm and type:

 

 

pip install meraki
pip install ffmpeg-python

 

 

Next, download FFMPEG from  https://ffmpeg.org/download.html

 

Move the resulting binary from within the .zip into the folder called ffmpeg within the Pycharm project

 

 

Spoiler
NOTE: I have to CRTL and right click the file and run it first before I could use it, ignoring the prompt that it was from an unknown developer. Do this at your own risk. Whilst I've been using ffmpeg for as long as I can remember, there's always a risk

2. The script

 

# this script is quite simple: It pulls a snapshot from a given camera,
# which is sent to the script via command line arguments
# it needs:
#   Meraki API key
#   Camera serial number

# 3rd party libraries needed
# pip install meraki
# pip install ffmpeg-python
# you also need to install the ffmpeg binary from https://ffmpeg.org/download.html
import meraki
import getopt, sys, requests, time, os, ffmpeg, datetime

output_directory = 'snapshots'  # this is where you'll save your snapshots, relative to the script
periodBetweenShots = 5          # this is an integer in seconds. It should be NO less than 5 seconds
numbersOfSnapshots = 10         # this is the number of snapshots to be taken. No more than 998
pathtoFFMPEG = "/Users/pfidler/ffmpeg/ffmpeg"

def main(argv):
    print("Meraki Library version: ")
    print(meraki.__version__)

    # validate that the script was called with the required paramaters and call printhelp() if not
    try:
        opts, args = getopt.getopt(argv, 'k:s:')
    except getopt.GetoptError:
        printhelp(argv)
        sys.exit(2)

    # assign command line params to variables
    for opt, arg in opts:
        if opt == '-k':
            arg_apikey = arg
        elif opt == '-s':
            arg_camera_serial = arg

    # Create Meraki Client Object and initialise
    client = meraki.DashboardAPI(api_key=arg_apikey)

    #loop
    for a in range(numbersOfSnapshots):
        #make API call to request snapshot
        snapshot_response = client.camera.generateDeviceCameraSnapshot(serial=arg_camera_serial)
        # get resulting URL from response
        snappyURL = snapshot_response["url"]
        #imagename = "image_" + datetime.datetime.now().strftime("%d%m%Y-%H:%M:%S") + ".jpeg"
        # we are going to save the file in the format of xxx.jpeg
        numwithzeros = str(a).zfill(3)
        # append .jpeg to end of filename
        imagename = numwithzeros + ".jpeg"
        time.sleep(periodBetweenShots)      # this is needed as the camera needs time to be told to take a snapshot
        # download image to required location
        filedownloader1(imagename, snappyURL)

    # there's a tonne of customisation for ffmpeg, including video codecs and resolution.
    # To keep things and understandable, I'm using the bareminimum
    # %03d is a way of specifying images in the range of 000 to 999.jpeg
    # testing has shown this to be:
    #  Stream #0:0: Video: h264 (avc1 / 0x31637661), yuvj420p(pc, bt470bg/unknown/unknown, progressive),
    #  1920x1080, q=2-31, 25 fps, 12800 tbn

    # file name format for video
    timelapseVideoname = "timelapse_" + datetime.datetime.now().strftime("%d%m%Y-%H:%M:%S") + ".mp4"
    (
        ffmpeg
        .input("snapshots/%03d.jpeg") # %03d
        .output(timelapseVideoname)
        .run(cmd=pathtoFFMPEG, capture_stdout=True)
        #.run()
    )


def filedownloader1(sent_name, sent_URL):
    # this function receives the name of the file
    # and users the 'output_directory' variable
    # to save a file from a given URL
    # Create a file path by joining the directory name with the desired file name
    file_path = os.path.join(output_directory, sent_name)
    # get the image from the URL
    response = requests.get(sent_URL, allow_redirects=True)
    # save the resulting file
    open(file_path, "wb").write(response.content)

def printhelp(receivedArg):
    # prints help information

    print('# this script is quite simple: It pulls a snapshot from a given camera')
    print('# which is sent to the script via command line arguments')
    print('# it needs:')
    print('   Meraki API key')
    print('   Camera serial number)')


if __name__ == '__main__':
    main(sys.argv[1:])

 

 

 

There's a lot to get through here, but it's actually quite a simple script.

 

 

Spoiler
NOTE: There's no error checking in here, apologies

Script Parameters

 

 

output_directory = 'snapshots'  # this is where you'll save your snapshots, relative to the script
periodBetweenShots = 5          # this is an integer in seconds. It should be NO less than 5 seconds
numbersOfSnapshots = 10         # this is the number of snapshots to be taken. No more than 998
pathtoFFMPEG = "/Users/pfidler/ffmpeg/ffmpeg"

 

 

output_directory is where your snapshots will be saved and is relative to where the script is being run

periodBetweenShots is the delay, in seconds, between each snapshot requested. As it takes a few seconds for the snapshot to be taken and uploaded to Meraki Dashboard, it should not be any less than 5

numbersOfSnapshots this is, well, the number of snapshots you wish to take

pathToFFMPEG this is the FULL path to the FFMPEG binary

 

Command Line Parameters

In order to keep the script generic, we pass 2 parameters when calling the script:

 

-k which is the Meraki API Key

-s which is the serial number of the camera you want to get snapshots from

 

3. The structure

The structure is really simple:

  • Create a client using the Meraki API key
  • Loop x times
    • Request a snapshot:
    •   

 

snapshot_response = client.camera.generateDeviceCameraSnapshot(serial=arg_camera_serial)​

 

  • Get the URL from the response
  • Save the resulting image from the URL
  • call ffmpeg to convert the snapshots into a video

 

4. Notes

 

I created a function to save the image from a given URL:

 

def filedownloader1(sent_name, sent_URL):
    # this function receives the name of the file
    # and users the 'output_directory' variable
    # to save a file from a given URL
    # Create a file path by joining the directory name with the desired file name
    file_path = os.path.join(output_directory, sent_name)
    # get the image from the URL
    response = requests.get(sent_URL, allow_redirects=True)
    # save the resulting file
    open(file_path, "wb").write(response.content)

 

 

The FFMPEG call is incredibly sparse:

 

    (
        ffmpeg
        .input("snapshots/%03d.jpeg") # %03d
        .output(timelapseVideoname)
        .run(cmd=pathtoFFMPEG, capture_stdout=True)
        #.run()
    )

 

You can give it various parameters such as frame rate, resolution, codec, etc. I decided not to to keep the code simple, and, well, because the resulting video with the default parameters of:

 

# Stream #0:0: Video: h264 (avc1 / 0x31637661), yuvj420p(pc, bt470bg/unknown/unknown, progressive), 1920x1080, q=2-31, 25 fps, 12800 tbn

 

resulted in a decent enough video for testing

 

 

 

5. Running the script

The script is called with:

 

 

python3 MV-Timelapse.py -k YOURMERAKIAPIKEY -s 'XXXX-XXXX-XXXX'

 

 

Where XXXX-XXXX-XXXX is the serial number of the camera in question

 

You'll get some output like this:

 

 

(venv) pfidler@PFIDLER-M-TWXW Meraki % python3 MV-Timelapse.py -k XXXX -s 'XXXX'
/Users/pfidler/Developer/Meraki/venv/lib/python3.9/site-packages/urllib3/__init__.py:35: NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'. See: https://github.com/urllib3/urllib3/issues/3020
  warnings.warn(
Meraki Library version: 
1.43.0
2024-03-26 10:50:25       meraki:     INFO > Meraki dashboard API session initialized with these parameters: {'version': '1.43.0', 'api_key': '************************************5f92', 'base_url': 'https://api.meraki.com/api/v1', 'single_request_timeout': 60, 'certificate_path': '', 'requests_proxy': '', 'wait_on_rate_limit': True, 'nginx_429_retry_wait_time': 60, 'action_batch_retry_wait_time': 60, 'network_delete_retry_wait_time': 240, 'retry_4xx_error': False, 'retry_4xx_error_wait_time': 60, 'maximum_retries': 2, 'simulate': False, 'be_geo_id': None, 'caller': None, 'use_iterator_for_get_pages': False}
2024-03-26 10:50:25       meraki:     INFO > POST https://api.meraki.com/api/v1/devices/XXXX/camera/generateSnapshot
2024-03-26 10:50:26       meraki:     INFO > camera, generateDeviceCameraSnapshot - 202 Accepted
2024-03-26 10:50:32       meraki:     INFO > POST https://api.meraki.com/api/v1/devices/XXXX/camera/generateSnapshot
2024-03-26 10:50:32       meraki:     INFO > camera, generateDeviceCameraSnapshot - 202 Accepted

 

 

And then when FFMPEG does its thing:

 

Screenshot 2024-03-26 at 15.04.42.png

And this results in a file called  timelapse_<THE DATE AND TIME>.mp4

 

6. Caveats and references

Don't wait to long to download the image from the URL: There's only a finite time before the link expires

 

This code is given without warranty, support etc, but is free to use

 

Using the FFMPEG Python Library: https://www.bannerbear.com/blog/how-to-use-ffmpeg-in-python-with-examples/#basic-usage-convert-video...

 

Combining images into a video file using FFMPEG: https://www.abyssale.com/generate-video/ffmpeg-combine-images-into-video

 

Meraki API Generate Device Camera Snapshot: https://developer.cisco.com/meraki/api/generate-device-camera-snapshot/

 

 

If there' an errors or omissions, please DM me and I'll update the guide above.

 

 

1 Reply 1
PhilipDAth
Kind of a big deal
Kind of a big deal

Wow, that is pretty cool!

Get notified when there are additional replies to this discussion.
Welcome to the Meraki Community!
To start contributing, simply sign in with your Cisco account. If you don't yet have a Cisco account, you can sign up.