http.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. import base64
  2. import datetime
  3. import logging
  4. import os
  5. import time
  6. from functools import reduce
  7. import cv2
  8. import numpy as np
  9. from flask import (Blueprint, Flask, Response, current_app, jsonify,
  10. make_response, request)
  11. from peewee import SqliteDatabase, operator, fn, DoesNotExist
  12. from playhouse.shortcuts import model_to_dict
  13. from frigate.models import Event
  14. logger = logging.getLogger(__name__)
  15. bp = Blueprint('frigate', __name__)
  16. def create_app(frigate_config, database: SqliteDatabase, camera_metrics, detectors, detected_frames_processor):
  17. app = Flask(__name__)
  18. log = logging.getLogger('werkzeug')
  19. log.setLevel(logging.INFO)
  20. @app.before_request
  21. def _db_connect():
  22. database.connect()
  23. @app.teardown_request
  24. def _db_close(exc):
  25. if not database.is_closed():
  26. database.close()
  27. app.frigate_config = frigate_config
  28. app.camera_metrics = camera_metrics
  29. app.detectors = detectors
  30. app.detected_frames_processor = detected_frames_processor
  31. app.register_blueprint(bp)
  32. return app
  33. @bp.route('/')
  34. def is_healthy():
  35. return "Frigate is running. Alive and healthy!"
  36. @bp.route('/events/summary')
  37. def events_summary():
  38. groups = (
  39. Event
  40. .select(
  41. Event.camera,
  42. Event.label,
  43. fn.strftime('%Y-%m-%d', fn.datetime(Event.start_time, 'unixepoch', 'localtime')).alias('day'),
  44. Event.zones,
  45. fn.COUNT(Event.id).alias('count')
  46. )
  47. .group_by(
  48. Event.camera,
  49. Event.label,
  50. fn.strftime('%Y-%m-%d', fn.datetime(Event.start_time, 'unixepoch', 'localtime')),
  51. Event.zones
  52. )
  53. )
  54. return jsonify([e for e in groups.dicts()])
  55. @bp.route('/events/<id>')
  56. def event(id):
  57. try:
  58. return model_to_dict(Event.get(Event.id == id))
  59. except DoesNotExist:
  60. return "Event not found", 404
  61. @bp.route('/events/<id>/snapshot.jpg')
  62. def event_snapshot(id):
  63. try:
  64. event = Event.get(Event.id == id)
  65. response = make_response(base64.b64decode(event.thumbnail))
  66. response.headers['Content-Type'] = 'image/jpg'
  67. return response
  68. except DoesNotExist:
  69. # see if the object is currently being tracked
  70. try:
  71. for camera_state in current_app.detected_frames_processor.camera_states.values():
  72. if id in camera_state.tracked_objects:
  73. tracked_obj = camera_state.tracked_objects.get(id)
  74. if not tracked_obj is None:
  75. response = make_response(tracked_obj.get_jpg_bytes())
  76. response.headers['Content-Type'] = 'image/jpg'
  77. return response
  78. except:
  79. return "Event not found", 404
  80. return "Event not found", 404
  81. @bp.route('/events')
  82. def events():
  83. limit = request.args.get('limit', 100)
  84. camera = request.args.get('camera')
  85. label = request.args.get('label')
  86. zone = request.args.get('zone')
  87. after = request.args.get('after', type=int)
  88. before = request.args.get('before', type=int)
  89. clauses = []
  90. if camera:
  91. clauses.append((Event.camera == camera))
  92. if label:
  93. clauses.append((Event.label == label))
  94. if zone:
  95. clauses.append((Event.zones.cast('text') % f"*\"{zone}\"*"))
  96. if after:
  97. clauses.append((Event.start_time >= after))
  98. if before:
  99. clauses.append((Event.start_time <= before))
  100. if len(clauses) == 0:
  101. clauses.append((1 == 1))
  102. events = (Event.select()
  103. .where(reduce(operator.and_, clauses))
  104. .order_by(Event.start_time.desc())
  105. .limit(limit))
  106. return jsonify([model_to_dict(e) for e in events])
  107. @bp.route('/config')
  108. def config():
  109. return jsonify(current_app.frigate_config.to_dict())
  110. @bp.route('/stats')
  111. def stats():
  112. camera_metrics = current_app.camera_metrics
  113. stats = {}
  114. total_detection_fps = 0
  115. for name, camera_stats in camera_metrics.items():
  116. total_detection_fps += camera_stats['detection_fps'].value
  117. stats[name] = {
  118. 'camera_fps': round(camera_stats['camera_fps'].value, 2),
  119. 'process_fps': round(camera_stats['process_fps'].value, 2),
  120. 'skipped_fps': round(camera_stats['skipped_fps'].value, 2),
  121. 'detection_fps': round(camera_stats['detection_fps'].value, 2),
  122. 'pid': camera_stats['process'].pid,
  123. 'capture_pid': camera_stats['capture_process'].pid
  124. }
  125. stats['detectors'] = {}
  126. for name, detector in current_app.detectors.items():
  127. stats['detectors'][name] = {
  128. 'inference_speed': round(detector.avg_inference_speed.value*1000, 2),
  129. 'detection_start': detector.detection_start.value,
  130. 'pid': detector.detect_process.pid
  131. }
  132. stats['detection_fps'] = round(total_detection_fps, 2)
  133. return jsonify(stats)
  134. @bp.route('/<camera_name>/<label>/best.jpg')
  135. def best(camera_name, label):
  136. if camera_name in current_app.frigate_config.cameras:
  137. best_object = current_app.detected_frames_processor.get_best(camera_name, label)
  138. best_frame = best_object.get('frame')
  139. if best_frame is None:
  140. best_frame = np.zeros((720,1280,3), np.uint8)
  141. else:
  142. best_frame = cv2.cvtColor(best_frame, cv2.COLOR_YUV2BGR_I420)
  143. crop = bool(request.args.get('crop', 0, type=int))
  144. if crop:
  145. region = best_object.get('region', [0,0,300,300])
  146. best_frame = best_frame[region[1]:region[3], region[0]:region[2]]
  147. height = int(request.args.get('h', str(best_frame.shape[0])))
  148. width = int(height*best_frame.shape[1]/best_frame.shape[0])
  149. best_frame = cv2.resize(best_frame, dsize=(width, height), interpolation=cv2.INTER_AREA)
  150. ret, jpg = cv2.imencode('.jpg', best_frame)
  151. response = make_response(jpg.tobytes())
  152. response.headers['Content-Type'] = 'image/jpg'
  153. return response
  154. else:
  155. return "Camera named {} not found".format(camera_name), 404
  156. @bp.route('/<camera_name>')
  157. def mjpeg_feed(camera_name):
  158. fps = int(request.args.get('fps', '3'))
  159. height = int(request.args.get('h', '360'))
  160. if camera_name in current_app.frigate_config.cameras:
  161. # return a multipart response
  162. return Response(imagestream(current_app.detected_frames_processor, camera_name, fps, height),
  163. mimetype='multipart/x-mixed-replace; boundary=frame')
  164. else:
  165. return "Camera named {} not found".format(camera_name), 404
  166. @bp.route('/<camera_name>/latest.jpg')
  167. def latest_frame(camera_name):
  168. if camera_name in current_app.frigate_config.cameras:
  169. # max out at specified FPS
  170. frame = current_app.detected_frames_processor.get_current_frame(camera_name)
  171. if frame is None:
  172. frame = np.zeros((720,1280,3), np.uint8)
  173. height = int(request.args.get('h', str(frame.shape[0])))
  174. width = int(height*frame.shape[1]/frame.shape[0])
  175. frame = cv2.resize(frame, dsize=(width, height), interpolation=cv2.INTER_AREA)
  176. ret, jpg = cv2.imencode('.jpg', frame)
  177. response = make_response(jpg.tobytes())
  178. response.headers['Content-Type'] = 'image/jpg'
  179. return response
  180. else:
  181. return "Camera named {} not found".format(camera_name), 404
  182. def imagestream(detected_frames_processor, camera_name, fps, height):
  183. while True:
  184. # max out at specified FPS
  185. time.sleep(1/fps)
  186. frame = detected_frames_processor.get_current_frame(camera_name, draw=True)
  187. if frame is None:
  188. frame = np.zeros((height,int(height*16/9),3), np.uint8)
  189. width = int(height*frame.shape[1]/frame.shape[0])
  190. frame = cv2.resize(frame, dsize=(width, height), interpolation=cv2.INTER_LINEAR)
  191. ret, jpg = cv2.imencode('.jpg', frame)
  192. yield (b'--frame\r\n'
  193. b'Content-Type: image/jpeg\r\n\r\n' + jpg.tobytes() + b'\r\n\r\n')