Skip to content

Instantly share code, notes, and snippets.

@LukeLambert
Last active May 23, 2022 14:01
Show Gist options
  • Save LukeLambert/10e8228ce78e9931616153dc560ec1ec to your computer and use it in GitHub Desktop.
Save LukeLambert/10e8228ce78e9931616153dc560ec1ec to your computer and use it in GitHub Desktop.
A quick-and-dirty script to grab a JPEG snapshot from a Ubiquiti camera at a specified interval.
#!/usr/bin/env python
"""Grab a JPEG snapshot from a Ubiquiti camera at a specified interval.
Usage: python uvcsnapshot.py -i INTERVAL -c CAMERA -p PASSWORD -o OUTPUT
Required arguments:
-i interval in seconds
-c camera IP address
-p camera password
-o path to output directory
Optional arguments:
-u camera username, defaults to ubnt
Example:
python uvcsnapshot.py -i 10 -c 192.168.0.100 -p pass1234 -o mysnaps
"""
import json
try:
from http.cookiejar import CookieJar
from urllib.error import HTTPError
from urllib.request import Request, urlopen
except ImportError:
from cookielib import CookieJar
from urllib2 import HTTPError, Request, urlopen
class Snapshooter:
"""Class to grab snapshots from Ubiquiti cameras.
Args:
camera (str): IP address of the camera
password (str): Camera password can be found in NVR settings
username (str, optional): Username defaults to 'ubnt'
"""
def __init__(self, camera, password, username='ubnt'):
self.camera = camera
self.password = password
self.username = username
self.jar = CookieJar()
self.login()
def open(self, url, data=None, headers={}, auth=False):
"""Open a URL and persist cookies in a CookieJar.
Args:
url (str): URL
data (bytes): additional data for POST
headers (dict): additional headers
auth (bool): does request require login?
"""
if auth:
# If auth is required, check if we have a session cookie.
has_session = False
for cookie in self.jar:
if cookie.name == 'authId':
has_session = True
break
if not has_session:
self.login()
request = Request(url, data=data, headers=headers)
self.jar.add_cookie_header(request)
try:
response = urlopen(request)
except HTTPError as e:
if auth and e.code == 401:
# Session cookie is invalid. Login and try again.
self.login()
response = urlopen(request)
else:
raise
self.jar.extract_cookies(response, request)
return response
def login(self):
"""Login to the camera and create a session."""
url = 'http://{}/api/1.1/login'.format(self.camera)
data = json.dumps({
'username': self.username,
'password': self.password
}).encode('utf-8')
headers = {
'Content-Type': 'application/json'
}
try:
response = self.open(url, data, headers)
except HTTPError as e:
if e.code == 401:
raise Exception('401 Invalid credentials.')
else:
raise
def to_bytes(self):
"""Get a JPEG snapshot as bytes."""
url = 'http://{}/snap.jpeg'.format(self.camera)
response = self.open(url, auth=True)
return response.read()
def to_file(self, path):
"""Save a JPEG snapshot to a file specified by path.
Args:
path (str): full path to file ending in .jpg"""
data = self.to_bytes()
with open(path, 'wb') as f:
f.write(data)
if __name__ == '__main__':
import argparse
import os
import time
import datetime
parser = argparse.ArgumentParser(description='Grab a snapshot from a Ubiquiti camera at a specified interval.')
parser.add_argument('-u', '--username', nargs='?', default='ubnt', help='camera username, defaults to ubnt')
required_named = parser.add_argument_group('required arguments')
required_named.add_argument('-i', '--interval', type=int, required=True, help='interval in seconds')
required_named.add_argument('-c', '--camera', required=True, help='camera IP address')
required_named.add_argument('-p', '--password', required=True, help='camera password')
required_named.add_argument('-o', '--output', required=True, help='path to output directory')
args = parser.parse_args()
snapshooter = Snapshooter(
camera=args.camera,
password=args.password,
username=args.username,
)
while True:
file_name = '%s.jpg' % datetime.datetime.now().strftime('%Y%m%d%H%M%S')
file_path = os.path.join(args.output, file_name)
try:
snapshooter.to_file(file_path)
print(file_name)
except Exception as e:
print(('ERROR %s. %s' % (file_name, str(e))))
time.sleep(args.interval)
@LukeLambert
Copy link
Author

LukeLambert commented Jul 30, 2018

Updated:

  • Python 2 and 3 compatibility
  • Command line friendly
  • Portable Snapshooter class
  • Authentication via camera password and username

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment