Compare commits
3 Commits
Author | SHA1 | Date |
---|---|---|
mcotton | a3c530bac8 | |
Mark Cotton | 693b6c1069 | |
Mark Cotton | e501a5b1b2 |
|
@ -10,5 +10,7 @@ my_settings.py
|
|||
flask_session/
|
||||
git-version.txt
|
||||
settings.py
|
||||
|
||||
*.db
|
||||
videos/*
|
||||
instance/project.db
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
FROM python:3.9-slim
|
||||
FROM python:3.10-slim
|
||||
|
||||
COPY ./requirements.txt /app/requirements.txt
|
||||
|
||||
|
|
54
README.md
54
README.md
|
@ -23,8 +23,22 @@ config = {
|
|||
"server_port": "3333",
|
||||
"server_path": "login_callback",
|
||||
|
||||
# preferences
|
||||
"log_level": "INFO"
|
||||
# how many days of history should be requested be default, more == slower API call
|
||||
"days_of_history": 1,
|
||||
|
||||
"log_level": "INFO",
|
||||
|
||||
# determines directory where videos should be stored, see formating option below, don't include trailing slash
|
||||
"video_dir": "videos",
|
||||
|
||||
# Folder structure that will be created, default is to save as:
|
||||
# {video_dir}/{camera_device_id}/{start.year}/{start.month}/{start.day}/
|
||||
# Setting this to False to save it as:
|
||||
# {video_dir}/{start.year}/{start.month}/{start.day}/{camera_device_id}/
|
||||
"path_esn_first": True,
|
||||
|
||||
# default is set at 4, but we feel the need for speed
|
||||
"num_of_threads_in_pool": 4
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -35,3 +49,39 @@ You can create your application and setup credentials at: [https://developerv3.e
|
|||
## Ideas on how to use ##
|
||||
|
||||
I encluded an example Flask server in `app.py`. You can run it locally with `python flask run -p 3333 --debug`
|
||||
|
||||
|
||||
|
||||
## EagleEyev3 Version ##
|
||||
|
||||
Using EagleEyev3 version *0.0.24*
|
||||
|
||||
|
||||
|
||||
## Routes ##
|
||||
|
||||
|
||||
### Command ###
|
||||
|
||||
`$ flask routes -s rule`
|
||||
|
||||
|
||||
### Output ###
|
||||
|
||||
| Endpoint | Methods | Rule |
|
||||
| --------------------- | ------- | -------------------------------------------- |
|
||||
| index | GET | / |
|
||||
| accounts | GET | /accounts |
|
||||
| camera_live_preivew | GET | /camera/<esn>/preview |
|
||||
| camera__preivew_image | GET | /camera/<esn>/preview_image |
|
||||
| camera_video_player | GET | /camera/<esn>/video_player/<start_timestamp> |
|
||||
| camera_list_of_videos | GET | /camera/<esn>/videos |
|
||||
| cameras | GET | /cameras |
|
||||
| landing | GET | /landing |
|
||||
| login_callback | GET | /login_callback |
|
||||
| logout | GET | /logout |
|
||||
| static | GET | /static/<path:filename> |
|
||||
| switch_account | GET | /switch_account |
|
||||
|
||||
|
||||
|
||||
|
|
130
app.py
130
app.py
|
@ -9,7 +9,7 @@ from matplotlib.figure import Figure
|
|||
import matplotlib.dates as mdates
|
||||
import base64
|
||||
from tqdm import tqdm
|
||||
|
||||
import datetime
|
||||
from io import BytesIO
|
||||
|
||||
import logging
|
||||
|
@ -35,12 +35,14 @@ if config:
|
|||
|
||||
if 'log_level' in config:
|
||||
logger.setLevel(config['log_level'])
|
||||
else:
|
||||
logging.setLevel('INFO')
|
||||
|
||||
if 'client_secret' in config:
|
||||
SECRET_KEY = config['client_secret']
|
||||
|
||||
else:
|
||||
logging.setLevel(config['INFO'])
|
||||
logging.setLevel('INFO')
|
||||
|
||||
|
||||
|
||||
|
@ -58,22 +60,20 @@ app.config.from_object(__name__)
|
|||
Session(app)
|
||||
|
||||
|
||||
# $ flask routes -s rule
|
||||
# INFO:root:Using EagleEyev3 version 0.0.17
|
||||
# Endpoint Methods Rule
|
||||
# --------------------- ------- --------------------------------------------
|
||||
# index GET /
|
||||
# accounts GET /accounts
|
||||
# camera_live_preivew GET /camera/<esn>/preview
|
||||
# camera__preivew_image GET /camera/<esn>/preview_image
|
||||
# camera_video_player GET /camera/<esn>/video_player/<start_timestamp>
|
||||
# camera_list_of_videos GET /camera/<esn>/videos
|
||||
# cameras GET /cameras
|
||||
# landing GET /landing
|
||||
# login_callback GET /login_callback
|
||||
# logout GET /logout
|
||||
# static GET /static/<path:filename>
|
||||
# switch_account GET /switch_account
|
||||
|
||||
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from models import *
|
||||
|
||||
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///project.db"
|
||||
db.init_app(app)
|
||||
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def login_required(f):
|
||||
|
@ -140,6 +140,8 @@ def index():
|
|||
een.get_list_of_cameras()
|
||||
een.get_list_of_accounts()
|
||||
|
||||
save_list_of_cameras(een=een)
|
||||
|
||||
if len(een.accounts) > 0 and een.active_account == None:
|
||||
# they need to pick and account
|
||||
|
||||
|
@ -185,25 +187,79 @@ def login_callback():
|
|||
oauth_object = een.login_tokens(code)
|
||||
logging.debug(oauth_object)
|
||||
|
||||
|
||||
|
||||
|
||||
# let's try resaving this to see if it fixs the missing access_token on first request after login
|
||||
session['een'] = een
|
||||
|
||||
een.get_current_user()
|
||||
|
||||
try:
|
||||
user = User.query.filter(User.email == een.current_user['email']).first()
|
||||
if user is None:
|
||||
logging.debug(f"no user found with email {een.current_user['email']}, creating new user")
|
||||
user = User(account_id=een.current_user['accountId'],\
|
||||
user_id=een.current_user['id'],\
|
||||
email=een.current_user['email'],\
|
||||
timezone=een.current_user['timeZone']['timeZone'],\
|
||||
access_token=een.access_token,\
|
||||
refresh_token=een.refresh_token,\
|
||||
last_login=datetime.now())
|
||||
else:
|
||||
logging.debug(f"found user with {een.current_user['email']}, updating")
|
||||
user.last_login = datetime.now()
|
||||
user.access_token = een.access_token
|
||||
user.refresh_token = een.refresh_token
|
||||
|
||||
logging.debug(f"saving user object")
|
||||
user.save()
|
||||
except Exception as e:
|
||||
logging.warn(e)
|
||||
|
||||
|
||||
|
||||
return redirect(url_for('index'))
|
||||
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
|
||||
# logout isn't working APIv3 right now so don't wait for the call to fail before logging out
|
||||
# if 'een' in session:
|
||||
# een = session['een']
|
||||
# een.logout()
|
||||
if 'een' in session:
|
||||
een = session['een']
|
||||
logging.debug(f"calling logout { een.access_token = }")
|
||||
een.logout()
|
||||
|
||||
session.pop('een')
|
||||
|
||||
return redirect(url_for('index'))
|
||||
|
||||
|
||||
def save_list_of_cameras(een=None):
|
||||
user = User.query.filter(User.email == een.current_user['email']).first()
|
||||
if user:
|
||||
user_id = user.id
|
||||
else:
|
||||
user_id = None
|
||||
|
||||
for c in een.cameras:
|
||||
try:
|
||||
check_camera = Camera.query.filter(Camera.device_id == c.id).first()
|
||||
if check_camera is None:
|
||||
logging.debug(f"no camera found for {c.id}, creating new camera")
|
||||
check_camera = Camera(device_id=c.id,\
|
||||
timezone=None, \
|
||||
name=c.name,
|
||||
user_id=user_id)
|
||||
else:
|
||||
logging.debug(f"found camera for {c.id}")
|
||||
|
||||
logging.debug(f"saving camera object")
|
||||
check_camera.save()
|
||||
except Exception as e:
|
||||
logging.warn(e)
|
||||
|
||||
|
||||
@app.route('/cameras')
|
||||
@login_required
|
||||
def cameras():
|
||||
|
@ -213,6 +269,9 @@ def cameras():
|
|||
|
||||
logging.debug(een.get_list_of_cameras())
|
||||
|
||||
|
||||
save_list_of_cameras(een=een)
|
||||
|
||||
values = {
|
||||
"current_user": een.current_user,
|
||||
"cameras": een.cameras,
|
||||
|
@ -327,7 +386,30 @@ def camera_list_of_videos(esn=None):
|
|||
een = session['een']
|
||||
|
||||
camera = een.get_camera_by_id(esn)
|
||||
logging.debug(camera.get_list_of_videos(start_timestamp=een.time_before(hours=24), end_timestamp=een.time_now()))
|
||||
logging.debug(camera.get_list_of_videos(start_timestamp=een.time_before(hours=24 * DAYS_OF_HISTORY), end_timestamp=een.time_now()))
|
||||
|
||||
db_camera = Camera.query.filter(Camera.device_id == esn).first()
|
||||
if db_camera:
|
||||
|
||||
for v in camera.videos:
|
||||
try:
|
||||
|
||||
check_video = Download.query.filter(Download.camera_id == db_camera.id)\
|
||||
.filter(Download.start_timestamp == datetime.fromisoformat(v['startTimestamp']))\
|
||||
.first()
|
||||
|
||||
|
||||
if check_video is None:
|
||||
logging.debug(f"creating a new record for {db_camera.id} {v['startTimestamp']}")
|
||||
new_video = Download(url=v['mp4Url'],\
|
||||
camera_id=db_camera.id,\
|
||||
start_timestamp=datetime.fromisoformat(v['startTimestamp']),\
|
||||
end_timestamp=datetime.fromisoformat(v['endTimestamp']),\
|
||||
status='new',
|
||||
error=None)
|
||||
new_video.save()
|
||||
except Exception as e:
|
||||
logging.warn(f"Exception saving videos to db: {e}")
|
||||
|
||||
values = {
|
||||
"current_user": een.current_user,
|
||||
|
@ -348,4 +430,4 @@ def camera_list_of_videos(esn=None):
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host=config.server_host, port=config.server_port)
|
||||
app.run(host=config['server_host'], port=config['server_port'])
|
||||
|
|
|
@ -6,7 +6,7 @@ services:
|
|||
volumes:
|
||||
- .:/app
|
||||
ports:
|
||||
- "9400:3000"
|
||||
- "3333:3000"
|
||||
restart: always
|
||||
tty: true
|
||||
user: 1000:1000
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
import requests
|
||||
import json
|
||||
import glob
|
||||
import sys
|
||||
import os
|
||||
|
||||
from multiprocessing import Process, Pool
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel('DEBUG')
|
||||
|
||||
from EagleEyev3 import *
|
||||
from settings import config
|
||||
|
||||
import sqlite3
|
||||
|
||||
con = sqlite3.connect('instance/project.db')
|
||||
cur = con.cursor()
|
||||
|
||||
|
||||
def run(args):
|
||||
# iterate through the first argument, appending the second and third to each iteration
|
||||
args = [([i], args[1], args[2]) for i in args[0]]
|
||||
pool.map(download, args, 1)
|
||||
|
||||
|
||||
def download(args):
|
||||
|
||||
# explode arguments into variable names
|
||||
row, een_obj, cam = args
|
||||
|
||||
row = row[0]
|
||||
camera_device_id = row[2]
|
||||
start = row[3]
|
||||
end = row[4]
|
||||
|
||||
start = datetime.fromisoformat(start)
|
||||
|
||||
video_dir = 'videos'
|
||||
if 'video_dir' in config:
|
||||
video_dir = config['video_dir']
|
||||
|
||||
path_esn_first = True
|
||||
if 'path_esn_first' in config:
|
||||
path_esn_first = config['path_esn_first']
|
||||
|
||||
if path_esn_first:
|
||||
path = f"{video_dir}/{camera_device_id}/{start.year}/{start.month}/{start.day}/"
|
||||
else:
|
||||
path = f"{video_dir}/{camera_device_id}/{start.year}/{start.month}/{start.day}/{camera_device_id}"
|
||||
|
||||
os.makedirs(path, exist_ok=True)
|
||||
|
||||
fname = f"{path}/{camera_device_id}_{start}-{end}.mp4"
|
||||
|
||||
if os.path.isfile(fname) == False:
|
||||
|
||||
save_result = cam.save_video_to_file(url=row[1], filename=fname)
|
||||
save_code = save_result['response_http_status']
|
||||
|
||||
match save_code:
|
||||
case 200:
|
||||
save_status = 'done'
|
||||
case 400 | 404 | 409:
|
||||
save_status = 'client failure'
|
||||
case 401 | 403:
|
||||
save_status = 'auth failure'
|
||||
case 500 | 502 | 503 | 504:
|
||||
save_status = 'cloud failure'
|
||||
case _:
|
||||
save_status = 'unknown failure'
|
||||
|
||||
else:
|
||||
# file exists, don't do anything and mark as done
|
||||
save_status = 'already exists'
|
||||
save_code = None
|
||||
|
||||
new_cur = con.cursor()
|
||||
new_cur.execute("update download set status = ?, error = ? where download.id == ?;", (save_status, save_code, row[0]))
|
||||
con.commit()
|
||||
|
||||
return (save_code, row[0])
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# the settings object to see how many threads we should run in the pool
|
||||
num_of_threads_in_pool = 4
|
||||
if 'num_of_threads_in_pool' in config:
|
||||
num_of_threads_in_pool = config['num_of_threads_in_pool']
|
||||
|
||||
pool = Pool(num_of_threads_in_pool)
|
||||
print("starting up...")
|
||||
|
||||
|
||||
een = EagleEyev3(config)
|
||||
logging.info(f"EagleEyev3 version: {een.__version__}")
|
||||
|
||||
|
||||
results = cur.execute('select email from user')
|
||||
|
||||
list_of_users = [r[0] for r in results]
|
||||
|
||||
for user_email in list_of_users:
|
||||
|
||||
if een:
|
||||
|
||||
result = cur.execute("select user.refresh_token from user where user.email = ?;", (user_email,))
|
||||
|
||||
for row in result:
|
||||
een.refresh_token = row[0]
|
||||
|
||||
else:
|
||||
logging.error('een object is None')
|
||||
|
||||
|
||||
een.login_tokens(code=None, cascade=True, refresh_token=een.refresh_token)
|
||||
|
||||
if een and een.current_user and 'email' in een.current_user:
|
||||
|
||||
result = cur.execute("select user.refresh_token, user.id from user where user.email = ?;", (een.current_user['email'],))
|
||||
|
||||
for row in result:
|
||||
print(f"found user {een.current_user['email']}, updating refresh_token")
|
||||
print(row)
|
||||
print(een.refresh_token)
|
||||
print(f"update user set refresh_token = {een.refresh_token} where id == {row[1]};")
|
||||
cur.execute("update user set refresh_token = ? where id == ?;", (een.refresh_token, row[1]))
|
||||
con.commit()
|
||||
|
||||
|
||||
een.get_list_of_cameras()
|
||||
|
||||
|
||||
# iterate through all the cameras this user has access to
|
||||
for current_camera in een.cameras:
|
||||
|
||||
sql = '''SELECT
|
||||
download.id,
|
||||
download.url,
|
||||
camera.device_id,
|
||||
download.start_timestamp,
|
||||
download.end_timestamp,
|
||||
download.status
|
||||
FROM
|
||||
camera
|
||||
JOIN download ON download.camera_id = camera.id
|
||||
JOIN USER ON camera.user_id = user.id
|
||||
WHERE
|
||||
download.status == ?
|
||||
AND camera.device_id == ?
|
||||
ORDER BY download.start_timestamp
|
||||
LIMIT 10000;'''
|
||||
|
||||
results = cur.execute(sql, ('new', current_camera.id))
|
||||
|
||||
# Here is where we send the list of files to be run in the multiprocessing pool
|
||||
run([results, een, current_camera])
|
||||
else:
|
||||
logging.info(f"failed to login for {user_email}")
|
||||
|
||||
|
||||
|
||||
|
||||
pool.close()
|
||||
print("Shutting down...")
|
||||
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from sqlalchemy import exc
|
||||
from settings import *
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
|
||||
__all__ = ['db', 'save_row', 'delete_row', 'User', 'Camera', 'Download' ]
|
||||
|
||||
|
||||
#
|
||||
# helper function to reduce repeated code
|
||||
#
|
||||
def commit_or_rollback(db, obj):
|
||||
try:
|
||||
db.session.add(obj)
|
||||
db.session.commit()
|
||||
except exc.IntegrityError as e:
|
||||
print(f"caught an exception {e}")
|
||||
db.session.rollback()
|
||||
except exc.OperationalError as e:
|
||||
print(f"caught an exception {e}")
|
||||
db.session.rollback()
|
||||
|
||||
def delete_or_rollback(db, obj):
|
||||
try:
|
||||
db.session.delete(obj)
|
||||
db.session.commit()
|
||||
except exc.IntegrityError as e:
|
||||
print(f"caught an exception {e}")
|
||||
db.session.rollback()
|
||||
except exc.OperationalError as e:
|
||||
print(f"caught an exception {e}")
|
||||
db.session.rollback()
|
||||
|
||||
def save_row(obj):
|
||||
commit_or_rollback(db, obj)
|
||||
|
||||
def delete_row(obj):
|
||||
delete_or_rollback(db, obj)
|
||||
|
||||
|
||||
|
||||
|
||||
# trying out making my own BaseModel, follow this blogpost
|
||||
# https://dev.to/chidioguejiofor/making-sqlalchemy-models-simpler-by-creating-a-basemodel-3m9c
|
||||
|
||||
class BaseModel(db.Model):
|
||||
__abstract__ = True
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
created = db.Column(db.DateTime, server_default=db.func.now())
|
||||
updated = db.Column(db.DateTime, server_default=db.func.now(), server_onupdate=db.func.now())
|
||||
|
||||
def before_save(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def after_save(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def save(self, commit=True):
|
||||
self.before_save()
|
||||
db.session.add(self)
|
||||
if commit:
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
raise e
|
||||
|
||||
self.after_save()
|
||||
|
||||
|
||||
def before_update(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def after_update(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def update(self, *args, **kwargs):
|
||||
self.before_update(*args, **kwargs)
|
||||
db.session.commit()
|
||||
self.after_update(*args, **kwargs)
|
||||
|
||||
def delete(self, commit=True):
|
||||
db.session.delete(self)
|
||||
if commit:
|
||||
db.session.commit()
|
||||
|
||||
@classmethod
|
||||
def eager(cls, *args):
|
||||
cols = [orm.joinedload(arg) for arg in args]
|
||||
return cls.query.options(*cols)
|
||||
|
||||
|
||||
@classmethod
|
||||
def before_bulk_create(cls, iterable, *args, **kwargs):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def after_bulk_create(cls, model_objs, *args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
@classmethod
|
||||
def bulk_create(cls, iterable, *args, **kwargs):
|
||||
cls.before_bulk_create(iterable, *args, **kwargs)
|
||||
model_objs = []
|
||||
for data in iterable:
|
||||
if not isinstance(data, cls):
|
||||
data = cls(**data)
|
||||
model_objs.append(data)
|
||||
|
||||
db.session.bulk_save_objects(model_objs)
|
||||
if kwargs.get('commit', True) is True:
|
||||
db.session.commit()
|
||||
cls.after_bulk_create(model_objs, *args, **kwargs)
|
||||
return model_objs
|
||||
|
||||
|
||||
@classmethod
|
||||
def bulk_create_or_none(cls, iterable, *args, **kwargs):
|
||||
try:
|
||||
return cls.bulk_create(iterable, *args, **kwargs)
|
||||
except exc.IntegrityError as e:
|
||||
db.session.rollback()
|
||||
return None
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
'''
|
||||
The User object stores basic data from EagleEye API along with cameras they have access to, and stores the access/refresh tokens.
|
||||
'''
|
||||
account_id = db.Column(db.String(8), nullable=True)
|
||||
user_id = db.Column(db.String(8), nullable=True)
|
||||
email = db.Column(db.String(), nullable=True)
|
||||
timezone = db.Column(db.String(), nullable=True)
|
||||
access_token = db.Column(db.String(), nullable=True)
|
||||
refresh_token = db.Column(db.String(), nullable=True)
|
||||
last_login = db.Column(db.DateTime, nullable=True)
|
||||
cameras = db.relationship('Camera', backref=db.backref('user', lazy=True))
|
||||
|
||||
def __repr__(self):
|
||||
return f"[User] {email}"
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"account_id": self.account_id,
|
||||
"user_id": self.user_id,
|
||||
"email": self.email,
|
||||
"timezone": self.timezone,
|
||||
"access_token": self.access_token,
|
||||
"refresh_token": self.refresh_token,
|
||||
"last_login": self.last_login,
|
||||
"cameras": self.cameras
|
||||
}
|
||||
|
||||
|
||||
|
||||
class Camera(BaseModel):
|
||||
'''
|
||||
The Camera object holds the list of recordings (Downloads) and some basics about the device
|
||||
'''
|
||||
device_id = db.Column(db.String(8), nullable=True)
|
||||
timezone = db.Column(db.String(), nullable=True)
|
||||
name = db.Column(db.String(), nullable=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True)
|
||||
downloads = db.relationship('Download', backref=db.backref('camera', lazy=True))
|
||||
|
||||
def __repr__(self):
|
||||
return f"[Camera] {device_id} {name}"
|
||||
|
||||
def get_newest_download(self, status=None):
|
||||
return None
|
||||
|
||||
def get_oldest_download(self, status=None):
|
||||
return None
|
||||
|
||||
|
||||
|
||||
class Download(BaseModel):
|
||||
'''
|
||||
Downloads are the actual recording that should be downloaded.
|
||||
It keeps its own state/progress/errors so it can be reported on later.
|
||||
Downloads belong to a camera which by extension belongs to a user
|
||||
'''
|
||||
url = db.Column(db.String(), nullable=True)
|
||||
camera_id = db.Column(db.Integer, db.ForeignKey('camera.id'), nullable=True)
|
||||
start_timestamp = db.Column(db.DateTime, nullable=True)
|
||||
end_timestamp = db.Column(db.DateTime, nullable=True)
|
||||
status = db.Column(db.String(), nullable=True)
|
||||
error = db.Column(db.String(), nullable=True)
|
||||
|
||||
def __repr__(self):
|
||||
return f"[Download] {id} {camera_id} {status} {error}"
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,339 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b276f988-0f92-4ac0-b440-523252f841e9",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Playing around with Machine to Machine API access"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0aac543d-e9f0-4a10-b4c2-35466085f2c2",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import requests\n",
|
||||
"from tqdm import tqdm"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5efb08d1-b0f6-4f64-b87a-f2f5afd4c050",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Let's do some EagleEye setup"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8f557a22-7ffe-4c0d-910d-9dee63b40832",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import logging\n",
|
||||
"logger = logging.getLogger()\n",
|
||||
"logger.setLevel('INFO')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d8ced614-480a-4341-bc40-6c66e16b54cd",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from EagleEyev3 import *\n",
|
||||
"from settings import config"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "091f1c2f-6bff-40e2-a0e8-d22d5f0ccd31",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"een = EagleEyev3(config)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "23864933-181c-402e-b2f5-e47a9f1871c8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"een.__version__"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2aaf14a9-f206-4909-a85f-fc736e964592",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Let's do some DB setup"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "12364fa5-fa06-419a-9d1c-b44f02c9ac63",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import sqlite3"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c960ddb6-316f-4737-bd88-d796cdd7897b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"sqlite3.sqlite_version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d8ca6f35-ee80-4463-aab1-3c83e72068f0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"con = sqlite3.connect('instance/project.db')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a8c9679c-37e1-4091-998d-08fc83f9c53b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"cur = con.cursor()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "80f58426-168f-47e1-b4f6-164cd5c6840a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Login as a user with their refresh_token and query their cameras"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "954c5d7b-db09-483e-a5f0-a59fb5733c34",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if een:\n",
|
||||
" if een.refresh_token == None or een.refresh_token == '':\n",
|
||||
" # if you get out of sync, pull the refresh_token from the db to get the loop started\n",
|
||||
" result = cur.execute(\"select user.refresh_token from user where user.email = ?;\", (\"mcotton@mcottondesign.com\",))\n",
|
||||
" \n",
|
||||
" for row in result:\n",
|
||||
" een.refresh_token = row[0]\n",
|
||||
" else:\n",
|
||||
" # een object and refresh_token appear to be good\n",
|
||||
" pass\n",
|
||||
"else:\n",
|
||||
" logging.error('een object is None')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a3cd64b9-3e3c-4f02-971c-1b4436a776ea",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"_ = een.login_tokens(code=None, cascade=True, refresh_token=een.refresh_token)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a6a4aaac-64b0-424b-b5a1-50a3630e3b6f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"een.refresh_token"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "205b1c7b-0845-4412-a77e-98e6506d36de",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"een.current_user"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e640fd2d-05eb-42c1-bcf6-3183afa065ef",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"result = cur.execute(\"select user.refresh_token, user.id from user where user.email = ?;\", (een.current_user['email'],))\n",
|
||||
"\n",
|
||||
"for row in result:\n",
|
||||
" print(f\"found user {een.current_user['email']}, updating refresh_token\")\n",
|
||||
" print(row)\n",
|
||||
" print(een.refresh_token)\n",
|
||||
" print(f\"update user set refresh_token = {een.refresh_token} where id == {row[1]};\")\n",
|
||||
" cur.execute(\"update user set refresh_token = ? where id == ?;\", (een.refresh_token, row[1]))\n",
|
||||
" con.commit()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "bdfde2f6-28b1-4623-8528-1492de00b83d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"_ = een.get_list_of_cameras()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e85f28ee-165d-442b-896e-05d74e29e644",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"een.cameras"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "17806b22-5bad-497d-977d-b1c31ed48304",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"r2d2 = een.cameras[3]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a6dc31ee-6041-44f7-8967-67856033a6a7",
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"_ = r2d2.get_list_of_videos(start_timestamp=een.time_before(hours=24*1), end_timestamp=een.time_now())"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3395164b-307d-473e-a090-fd782d0c1dc1",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"len(r2d2.videos)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a924b74d-7a77-45b1-90a5-b9b0c3ab8d7d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Download all the videos ##"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "74378ae5-0ade-4d8a-9636-2bd2800e59a3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"sql = '''SELECT\n",
|
||||
"\tdownload.id,\n",
|
||||
"\tdownload.url,\n",
|
||||
"\tcamera.device_id,\n",
|
||||
"\tdownload.start_timestamp,\n",
|
||||
"\tdownload.end_timestamp,\n",
|
||||
"\tdownload.status\n",
|
||||
"FROM\n",
|
||||
"\tcamera\n",
|
||||
"\tJOIN download ON download.camera_id = camera.id\n",
|
||||
"\tJOIN USER ON camera.user_id = user.id\n",
|
||||
"WHERE\n",
|
||||
"\tdownload.status == ?\n",
|
||||
"\tAND camera.id == ?;'''"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8b61db76-e925-4ccb-bead-39239615daae",
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"results = cur.execute(sql, ('new', 4))\n",
|
||||
"\n",
|
||||
"for row in tqdm(results):\n",
|
||||
" fname = f\"videos/{row[2]}/{row[2]}_{row[3]}-{row[4]}.mp4\"\n",
|
||||
" print(f\"{row[0]} - {row[5]}\")\n",
|
||||
" r2d2.save_video_to_file(url=row[1], filename=fname)\n",
|
||||
" new_cur = con.cursor()\n",
|
||||
" new_cur.execute(\"update download set status = ? where download.id == ?;\", ('done', row[0]))\n",
|
||||
" \n",
|
||||
" # less efficient to commit every iteration but it means I can watch the updates at the DB level\n",
|
||||
" con.commit()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "raw",
|
||||
"id": "4f728219-12e2-4b60-b0af-0320efec95fb",
|
||||
"metadata": {},
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "7a72cb7f-a6c5-49c1-8dca-38fe4a4981bb",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "flask2",
|
||||
"language": "python",
|
||||
"name": "flask2"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
|
@ -14,8 +14,10 @@ Werkzeug==2.3.6
|
|||
gunicorn==20.1.0
|
||||
cachelib==0.10.2
|
||||
Flask-Session==0.5.0
|
||||
EagleEyev3>=0.0.18
|
||||
tqdm
|
||||
pandas
|
||||
numpy
|
||||
matplotlib
|
||||
Flask-SQLAlchemy==3.0.5
|
||||
Flask-Migrate==4.0.4
|
||||
EagleEyev3>=0.0.20
|
Loading…
Reference in New Issue