app.py 9.6 KB

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