94 lines
3.0 KiB
Python
94 lines
3.0 KiB
Python
import os
|
|
import time
|
|
import base64
|
|
import requests
|
|
import ollama
|
|
import chromadb
|
|
from datetime import datetime
|
|
|
|
FRIGATE_URL = os.getenv("FRIGATE_URL", "http://frigate:5000")
|
|
OLLAMA_URL = os.getenv("OLLAMA_URL", "http://ollama:11434")
|
|
CHROMA_URL = os.getenv("CHROMA_URL", "http://chromadb:8000")
|
|
CAMERAS = os.getenv("CAMERAS", "front_door").split(",")
|
|
INTERVAL = int(os.getenv("INTERVAL", "1"))
|
|
RETENTION_HOURS = int(os.getenv("RETENTION_HOURS", "72"))
|
|
|
|
ollama_client = ollama.Client(host=OLLAMA_URL)
|
|
chroma_host = CHROMA_URL.replace("http://", "").split(":")[0]
|
|
chroma_port = int(CHROMA_URL.split(":")[-1])
|
|
chroma = chromadb.HttpClient(host=chroma_host, port=chroma_port)
|
|
collection = chroma.get_or_create_collection(name="camera_frames", metadata={"hnsw:space": "cosine"})
|
|
|
|
def get_frame(camera):
|
|
try:
|
|
r = requests.get(f"{FRIGATE_URL}/api/{camera}/latest.jpg", timeout=5)
|
|
if r.status_code == 200:
|
|
return r.content
|
|
except Exception as e:
|
|
print(f"[ERROR] Frame grab {camera}: {e}")
|
|
return None
|
|
|
|
def describe(image_bytes):
|
|
try:
|
|
b64 = base64.b64encode(image_bytes).decode()
|
|
res = ollama_client.chat(
|
|
model='moondream',
|
|
messages=[{
|
|
'role': 'user',
|
|
'content': 'Describe briefly: people (gender, clothing, action), vehicles (color, type), animals, packages, unusual activity.',
|
|
'images': [b64]
|
|
}]
|
|
)
|
|
return res['message']['content']
|
|
except Exception as e:
|
|
print(f"[ERROR] Vision: {e}")
|
|
return ""
|
|
|
|
def embed(text):
|
|
res = ollama_client.embeddings(model='nomic-embed-text', prompt=text)
|
|
return res['embedding']
|
|
|
|
def store(camera, description, timestamp):
|
|
if not description.strip():
|
|
return
|
|
dt = datetime.fromtimestamp(timestamp)
|
|
doc_id = f"{camera}_{int(timestamp)}"
|
|
collection.add(
|
|
ids=[doc_id],
|
|
embeddings=[embed(description)],
|
|
documents=[description],
|
|
metadatas=[{"camera": camera, "timestamp": timestamp, "datetime": dt.isoformat(), "hour": dt.hour}]
|
|
)
|
|
print(f"[{dt.strftime('%H:%M:%S')}] {camera}: {description[:80]}...")
|
|
|
|
def cleanup_old():
|
|
cutoff = time.time() - (RETENTION_HOURS * 3600)
|
|
try:
|
|
collection.delete(where={"timestamp": {"$lt": cutoff}})
|
|
print(f"[CLEANUP] Removed entries older than {RETENTION_HOURS}h")
|
|
except Exception as e:
|
|
print(f"[CLEANUP ERROR] {e}")
|
|
|
|
def main():
|
|
print(f"Camera Memory Service")
|
|
print(f" Frigate: {FRIGATE_URL}")
|
|
print(f" Ollama: {OLLAMA_URL}")
|
|
print(f" Chroma: {CHROMA_URL}")
|
|
print(f" Cameras: {CAMERAS}")
|
|
print(f" Interval: {INTERVAL}s")
|
|
time.sleep(10)
|
|
iteration = 0
|
|
while True:
|
|
for camera in CAMERAS:
|
|
frame = get_frame(camera)
|
|
if frame:
|
|
desc = describe(frame)
|
|
store(camera, desc, time.time())
|
|
iteration += 1
|
|
if iteration % 3600 == 0:
|
|
cleanup_old()
|
|
time.sleep(INTERVAL)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|