- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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:
- a folder called ffmpeg
- a folder called snapshots
- a file called MV-Timelapse.py
So, you should have :
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
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.
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:
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.
- Labels:
-
Other
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Wow, that is pretty cool!
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
I also came across this on LinkedIn....
I can feel a Feature Request coming on to Meraki to make this functionality available in the dashboard.
I had a request for exactly this functionality a few months back.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
This is quite creative and super useful - will give this a spin! Thanks for sharing.
