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
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.
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
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)
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: