app.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. import json
  2. import logging
  3. import multiprocessing as mp
  4. import os
  5. from logging.handlers import QueueHandler
  6. from typing import Dict, List
  7. import sys
  8. import signal
  9. import yaml
  10. from peewee_migrate import Router
  11. from playhouse.sqlite_ext import SqliteExtDatabase
  12. from frigate.config import FrigateConfig
  13. from frigate.const import RECORD_DIR, CLIPS_DIR, CACHE_DIR
  14. from frigate.edgetpu import EdgeTPUProcess
  15. from frigate.events import EventProcessor, EventCleanup
  16. from frigate.http import create_app
  17. from frigate.log import log_process, root_configurer
  18. from frigate.models import Event
  19. from frigate.mqtt import create_mqtt_client
  20. from frigate.object_processing import TrackedObjectProcessor
  21. from frigate.record import RecordingMaintainer
  22. from frigate.stats import StatsEmitter, stats_init
  23. from frigate.video import capture_camera, track_camera
  24. from frigate.watchdog import FrigateWatchdog
  25. from frigate.zeroconf import broadcast_zeroconf
  26. logger = logging.getLogger(__name__)
  27. class FrigateApp():
  28. def __init__(self):
  29. self.stop_event = mp.Event()
  30. self.config: FrigateConfig = None
  31. self.detection_queue = mp.Queue()
  32. self.detectors: Dict[str, EdgeTPUProcess] = {}
  33. self.detection_out_events: Dict[str, mp.Event] = {}
  34. self.detection_shms: List[mp.shared_memory.SharedMemory] = []
  35. self.log_queue = mp.Queue()
  36. self.camera_metrics = {}
  37. def set_environment_vars(self):
  38. for key, value in self.config.environment_vars.items():
  39. os.environ[key] = value
  40. def ensure_dirs(self):
  41. for d in [RECORD_DIR, CLIPS_DIR, CACHE_DIR]:
  42. if not os.path.exists(d) and not os.path.islink(d):
  43. logger.info(f"Creating directory: {d}")
  44. os.makedirs(d)
  45. else:
  46. logger.debug(f"Skipping directory: {d}")
  47. tmpfs_size = self.config.clips.tmpfs_cache_size
  48. if tmpfs_size:
  49. logger.info(f"Creating tmpfs of size {tmpfs_size}")
  50. rc = os.system(f"mount -t tmpfs -o size={tmpfs_size} tmpfs {CACHE_DIR}")
  51. if rc != 0:
  52. logger.error(f"Failed to create tmpfs, error code: {rc}")
  53. def init_logger(self):
  54. self.log_process = mp.Process(target=log_process, args=(self.log_queue,), name='log_process')
  55. self.log_process.daemon = True
  56. self.log_process.start()
  57. root_configurer(self.log_queue)
  58. def init_config(self):
  59. config_file = os.environ.get('CONFIG_FILE', '/config/config.yml')
  60. self.config = FrigateConfig(config_file=config_file)
  61. for camera_name in self.config.cameras.keys():
  62. # create camera_metrics
  63. self.camera_metrics[camera_name] = {
  64. 'camera_fps': mp.Value('d', 0.0),
  65. 'skipped_fps': mp.Value('d', 0.0),
  66. 'process_fps': mp.Value('d', 0.0),
  67. 'detection_enabled': mp.Value('i', self.config.cameras[camera_name].detect.enabled),
  68. 'detection_fps': mp.Value('d', 0.0),
  69. 'detection_frame': mp.Value('d', 0.0),
  70. 'read_start': mp.Value('d', 0.0),
  71. 'ffmpeg_pid': mp.Value('i', 0),
  72. 'frame_queue': mp.Queue(maxsize=2),
  73. }
  74. def check_config(self):
  75. for name, camera in self.config.cameras.items():
  76. assigned_roles = list(set([r for i in camera.ffmpeg.inputs for r in i.roles]))
  77. if not camera.clips.enabled and 'clips' in assigned_roles:
  78. logger.warning(f"Camera {name} has clips assigned to an input, but clips is not enabled.")
  79. elif camera.clips.enabled and not 'clips' in assigned_roles:
  80. logger.warning(f"Camera {name} has clips enabled, but clips is not assigned to an input.")
  81. if not camera.record.enabled and 'record' in assigned_roles:
  82. logger.warning(f"Camera {name} has record assigned to an input, but record is not enabled.")
  83. elif camera.record.enabled and not 'record' in assigned_roles:
  84. logger.warning(f"Camera {name} has record enabled, but record is not assigned to an input.")
  85. if not camera.rtmp.enabled and 'rtmp' in assigned_roles:
  86. logger.warning(f"Camera {name} has rtmp assigned to an input, but rtmp is not enabled.")
  87. elif camera.rtmp.enabled and not 'rtmp' in assigned_roles:
  88. logger.warning(f"Camera {name} has rtmp enabled, but rtmp is not assigned to an input.")
  89. def set_log_levels(self):
  90. logging.getLogger().setLevel(self.config.logger.default)
  91. for log, level in self.config.logger.logs.items():
  92. logging.getLogger(log).setLevel(level)
  93. if not 'werkzeug' in self.config.logger.logs:
  94. logging.getLogger('werkzeug').setLevel('ERROR')
  95. def init_queues(self):
  96. # Queues for clip processing
  97. self.event_queue = mp.Queue()
  98. self.event_processed_queue = mp.Queue()
  99. # Queue for cameras to push tracked objects to
  100. self.detected_frames_queue = mp.Queue(maxsize=len(self.config.cameras.keys())*2)
  101. def init_database(self):
  102. self.db = SqliteExtDatabase(self.config.database.path)
  103. # Run migrations
  104. del(logging.getLogger('peewee_migrate').handlers[:])
  105. router = Router(self.db)
  106. router.run()
  107. models = [Event]
  108. self.db.bind(models)
  109. def init_stats(self):
  110. self.stats_tracking = stats_init(self.camera_metrics, self.detectors)
  111. def init_web_server(self):
  112. self.flask_app = create_app(self.config, self.db, self.stats_tracking, self.detected_frames_processor)
  113. def init_mqtt(self):
  114. self.mqtt_client = create_mqtt_client(self.config, self.camera_metrics)
  115. def start_detectors(self):
  116. model_shape = (self.config.model.height, self.config.model.width)
  117. for name in self.config.cameras.keys():
  118. self.detection_out_events[name] = mp.Event()
  119. shm_in = mp.shared_memory.SharedMemory(name=name, create=True, size=self.config.model.height*self.config.model.width*3)
  120. shm_out = mp.shared_memory.SharedMemory(name=f"out-{name}", create=True, size=20*6*4)
  121. self.detection_shms.append(shm_in)
  122. self.detection_shms.append(shm_out)
  123. for name, detector in self.config.detectors.items():
  124. if detector.type == 'cpu':
  125. self.detectors[name] = EdgeTPUProcess(name, self.detection_queue, self.detection_out_events, model_shape, 'cpu', detector.num_threads)
  126. if detector.type == 'edgetpu':
  127. self.detectors[name] = EdgeTPUProcess(name, self.detection_queue, self.detection_out_events, model_shape, detector.device, detector.num_threads)
  128. def start_detected_frames_processor(self):
  129. self.detected_frames_processor = TrackedObjectProcessor(self.config, self.mqtt_client, self.config.mqtt.topic_prefix,
  130. self.detected_frames_queue, self.event_queue, self.event_processed_queue, self.stop_event)
  131. self.detected_frames_processor.start()
  132. def start_camera_processors(self):
  133. model_shape = (self.config.model.height, self.config.model.width)
  134. for name, config in self.config.cameras.items():
  135. camera_process = mp.Process(target=track_camera, name=f"camera_processor:{name}", args=(name, config, model_shape,
  136. self.detection_queue, self.detection_out_events[name], self.detected_frames_queue,
  137. self.camera_metrics[name]))
  138. camera_process.daemon = True
  139. self.camera_metrics[name]['process'] = camera_process
  140. camera_process.start()
  141. logger.info(f"Camera processor started for {name}: {camera_process.pid}")
  142. def start_camera_capture_processes(self):
  143. for name, config in self.config.cameras.items():
  144. capture_process = mp.Process(target=capture_camera, name=f"camera_capture:{name}", args=(name, config,
  145. self.camera_metrics[name]))
  146. capture_process.daemon = True
  147. self.camera_metrics[name]['capture_process'] = capture_process
  148. capture_process.start()
  149. logger.info(f"Capture process started for {name}: {capture_process.pid}")
  150. def start_event_processor(self):
  151. self.event_processor = EventProcessor(self.config, self.camera_metrics, self.event_queue, self.event_processed_queue, self.stop_event)
  152. self.event_processor.start()
  153. def start_event_cleanup(self):
  154. self.event_cleanup = EventCleanup(self.config, self.stop_event)
  155. self.event_cleanup.start()
  156. def start_recording_maintainer(self):
  157. self.recording_maintainer = RecordingMaintainer(self.config, self.stop_event)
  158. self.recording_maintainer.start()
  159. def start_stats_emitter(self):
  160. self.stats_emitter = StatsEmitter(self.config, self.stats_tracking, self.mqtt_client, self.config.mqtt.topic_prefix, self.stop_event)
  161. self.stats_emitter.start()
  162. def start_watchdog(self):
  163. self.frigate_watchdog = FrigateWatchdog(self.detectors, self.stop_event)
  164. self.frigate_watchdog.start()
  165. def start(self):
  166. self.init_logger()
  167. try:
  168. try:
  169. self.init_config()
  170. except Exception as e:
  171. print(f"Error parsing config: {e}")
  172. self.log_process.terminate()
  173. sys.exit(1)
  174. self.set_environment_vars()
  175. self.ensure_dirs()
  176. self.check_config()
  177. self.set_log_levels()
  178. self.init_queues()
  179. self.init_database()
  180. self.init_mqtt()
  181. except Exception as e:
  182. print(e)
  183. self.log_process.terminate()
  184. sys.exit(1)
  185. self.start_detectors()
  186. self.start_detected_frames_processor()
  187. self.start_camera_processors()
  188. self.start_camera_capture_processes()
  189. self.init_stats()
  190. self.init_web_server()
  191. self.start_event_processor()
  192. self.start_event_cleanup()
  193. self.start_recording_maintainer()
  194. self.start_stats_emitter()
  195. self.start_watchdog()
  196. # self.zeroconf = broadcast_zeroconf(self.config.mqtt.client_id)
  197. def receiveSignal(signalNumber, frame):
  198. self.stop()
  199. sys.exit()
  200. signal.signal(signal.SIGTERM, receiveSignal)
  201. self.flask_app.run(host='127.0.0.1', port=5001, debug=False)
  202. self.stop()
  203. def stop(self):
  204. logger.info(f"Stopping...")
  205. self.stop_event.set()
  206. self.detected_frames_processor.join()
  207. self.event_processor.join()
  208. self.event_cleanup.join()
  209. self.recording_maintainer.join()
  210. self.stats_emitter.join()
  211. self.frigate_watchdog.join()
  212. for detector in self.detectors.values():
  213. detector.stop()
  214. while len(self.detection_shms) > 0:
  215. shm = self.detection_shms.pop()
  216. shm.close()
  217. shm.unlink()