Compare commits

...

3 Commits

Author SHA1 Message Date
mcotton a3c530bac8 updated python in docker, fixed port mapping for workstation 2023-10-25 21:36:55 -05:00
Mark Cotton 693b6c1069 ignoring local db file 2023-10-05 21:21:45 -05:00
Mark Cotton e501a5b1b2 merging database branch 2023-10-05 21:18:39 -05:00
9 changed files with 879 additions and 30 deletions

4
.gitignore vendored
View File

@ -10,5 +10,7 @@ my_settings.py
flask_session/ flask_session/
git-version.txt git-version.txt
settings.py settings.py
*.db
videos/*
instance/project.db

View File

@ -1,5 +1,5 @@
FROM python:3.9-slim FROM python:3.10-slim
COPY ./requirements.txt /app/requirements.txt COPY ./requirements.txt /app/requirements.txt

View File

@ -23,8 +23,22 @@ config = {
"server_port": "3333", "server_port": "3333",
"server_path": "login_callback", "server_path": "login_callback",
# preferences # how many days of history should be requested be default, more == slower API call
"log_level": "INFO" "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 ## ## 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` 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
View File

@ -9,7 +9,7 @@ from matplotlib.figure import Figure
import matplotlib.dates as mdates import matplotlib.dates as mdates
import base64 import base64
from tqdm import tqdm from tqdm import tqdm
import datetime
from io import BytesIO from io import BytesIO
import logging import logging
@ -35,12 +35,14 @@ if config:
if 'log_level' in config: if 'log_level' in config:
logger.setLevel(config['log_level']) logger.setLevel(config['log_level'])
else:
logging.setLevel('INFO')
if 'client_secret' in config: if 'client_secret' in config:
SECRET_KEY = config['client_secret'] SECRET_KEY = config['client_secret']
else: else:
logging.setLevel(config['INFO']) logging.setLevel('INFO')
@ -58,22 +60,20 @@ app.config.from_object(__name__)
Session(app) Session(app)
# $ flask routes -s rule
# INFO:root:Using EagleEyev3 version 0.0.17
# Endpoint Methods Rule from flask_sqlalchemy import SQLAlchemy
# --------------------- ------- -------------------------------------------- from models import *
# index GET /
# accounts GET /accounts app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///project.db"
# camera_live_preivew GET /camera/<esn>/preview db.init_app(app)
# camera__preivew_image GET /camera/<esn>/preview_image
# camera_video_player GET /camera/<esn>/video_player/<start_timestamp> with app.app_context():
# camera_list_of_videos GET /camera/<esn>/videos db.create_all()
# cameras GET /cameras
# landing GET /landing
# login_callback GET /login_callback
# logout GET /logout
# static GET /static/<path:filename>
# switch_account GET /switch_account
def login_required(f): def login_required(f):
@ -140,6 +140,8 @@ def index():
een.get_list_of_cameras() een.get_list_of_cameras()
een.get_list_of_accounts() een.get_list_of_accounts()
save_list_of_cameras(een=een)
if len(een.accounts) > 0 and een.active_account == None: if len(een.accounts) > 0 and een.active_account == None:
# they need to pick and account # they need to pick and account
@ -186,24 +188,78 @@ def login_callback():
logging.debug(oauth_object) logging.debug(oauth_object)
# let's try resaving this to see if it fixs the missing access_token on first request after login # let's try resaving this to see if it fixs the missing access_token on first request after login
session['een'] = een 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')) return redirect(url_for('index'))
@app.route('/logout') @app.route('/logout')
def 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:
# if 'een' in session: een = session['een']
# een = session['een'] logging.debug(f"calling logout { een.access_token = }")
# een.logout() een.logout()
session.pop('een') session.pop('een')
return redirect(url_for('index')) 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') @app.route('/cameras')
@login_required @login_required
def cameras(): def cameras():
@ -213,6 +269,9 @@ def cameras():
logging.debug(een.get_list_of_cameras()) logging.debug(een.get_list_of_cameras())
save_list_of_cameras(een=een)
values = { values = {
"current_user": een.current_user, "current_user": een.current_user,
"cameras": een.cameras, "cameras": een.cameras,
@ -327,7 +386,30 @@ def camera_list_of_videos(esn=None):
een = session['een'] een = session['een']
camera = een.get_camera_by_id(esn) 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 = { values = {
"current_user": een.current_user, "current_user": een.current_user,
@ -348,4 +430,4 @@ def camera_list_of_videos(esn=None):
if __name__ == '__main__': if __name__ == '__main__':
app.run(host=config.server_host, port=config.server_port) app.run(host=config['server_host'], port=config['server_port'])

View File

@ -6,7 +6,7 @@ services:
volumes: volumes:
- .:/app - .:/app
ports: ports:
- "9400:3000" - "3333:3000"
restart: always restart: always
tty: true tty: true
user: 1000:1000 user: 1000:1000

172
download_worker.py Normal file
View File

@ -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...")

202
models.py Normal file
View File

@ -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}"

339
playground.ipynb Normal file
View File

@ -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
}

View File

@ -14,8 +14,10 @@ Werkzeug==2.3.6
gunicorn==20.1.0 gunicorn==20.1.0
cachelib==0.10.2 cachelib==0.10.2
Flask-Session==0.5.0 Flask-Session==0.5.0
EagleEyev3>=0.0.18
tqdm tqdm
pandas pandas
numpy numpy
matplotlib matplotlib
Flask-SQLAlchemy==3.0.5
Flask-Migrate==4.0.4
EagleEyev3>=0.0.20