webapp1 commited on
Commit
cc3fce6
·
verified ·
1 Parent(s): fa210c2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +117 -76
app.py CHANGED
@@ -17,12 +17,15 @@ import torch.nn as nn
17
  import numpy as np
18
  import pandas as pd
19
  import joblib
 
 
20
  import threading
21
  import uuid
22
  import time
23
  import requests
24
  import datetime
25
  from urllib.parse import urlparse
 
26
  from flask import Flask, request, render_template, jsonify, Response, send_from_directory
27
  from werkzeug.utils import secure_filename
28
 
@@ -53,10 +56,11 @@ print("--- BOOT SEQUENCE INITIATED ---", flush=True)
53
 
54
  app = Flask(__name__)
55
 
56
- # --- HUGGING FACE SAFE SOS DISPATCH ---
57
- # Hugging Face blocks SMTP email ports (465, 587).
58
- # We now use ntfy.sh to instantly send push notifications over standard HTTP (port 443).
59
- NTFY_TOPIC = "CrashVision_SOS_Alerts"
 
60
 
61
  UPLOAD_FOLDER = 'uploads'
62
  MODEL_FOLDER = 'models'
@@ -93,6 +97,7 @@ except:
93
  model_traffic = None
94
 
95
  # --- Helper Functions ---
 
96
  def get_geo_info(ip_or_url=None):
97
  """Fetches location and weather based on IP or URL."""
98
  try:
@@ -125,27 +130,40 @@ def process_accident_async(stream_id, frame, location, confidence, severity):
125
  if stream_id in active_streams:
126
  active_streams[stream_id]["plates"] = detected_plates
127
 
128
- # --- HTTP PUSH NOTIFICATION DISPATCH (GUARANTEED TO WORK ON HF) ---
129
  try:
130
- _, buffer = cv2.imencode('.jpg', frame)
131
-
132
  headers = {
133
  "Title": f"🚨 {severity.upper()} ACCIDENT DETECTED",
134
- "Priority": "5", # Highest priority, rings phone
135
- "Tags": "rotating_light,car",
136
- "Filename": "incident.jpg"
137
  }
138
-
139
- # This sends the snapshot directly to ntfy.sh bypassing all email blocks
140
- res = requests.post(
141
- f"https://ntfy.sh/{NTFY_TOPIC}",
142
- data=buffer.tobytes(),
143
- headers=headers,
144
- timeout=10
145
- )
146
- print(f"📡 Push Notification Sent via ntfy.sh/{NTFY_TOPIC} - HTTP {res.status_code}", flush=True)
147
  except Exception as e:
148
- print(f"📡 SOS Dispatch Failed: {str(e)}", flush=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
 
150
  def run_temporal_analysis(stream_id, frames_3d_copy, prob_max, prob_mean, prob_min, yolo_max_conf, raw_frame, location):
151
  state = active_streams.get(stream_id)
@@ -165,6 +183,7 @@ def run_temporal_analysis(stream_id, frames_3d_copy, prob_max, prob_mean, prob_m
165
  confidence = float(ensemble_probs[final_idx] * 100)
166
  severity = classes[final_idx]
167
  except:
 
168
  final_idx = min(int(np.argmax(prob_max)), 2)
169
  confidence = float(np.max(prob_max)) * 100
170
  severity = classes[final_idx]
@@ -174,7 +193,7 @@ def run_temporal_analysis(stream_id, frames_3d_copy, prob_max, prob_mean, prob_m
174
  state["cnn"] = round(yolo_max_conf, 1)
175
  state["rcnn"] = round(float(np.max(prob_3d)) * 100, 1)
176
 
177
- # Triggers the SOS webhook if confidence is high
178
  if confidence > 75:
179
  state["final_decision"] = True
180
  state["label"] = f"Incident Logged: {severity.capitalize()}"
@@ -184,10 +203,12 @@ def run_temporal_analysis(stream_id, frames_3d_copy, prob_max, prob_mean, prob_m
184
 
185
  except Exception as e:
186
  print(f"AI Error in Analysis Thread: {e}", flush=True)
 
187
  finally:
188
  if state: state["is_analyzing"] = False
189
 
190
  # --- Route Handlers ---
 
191
  @app.route('/init_upload', methods=['POST'])
192
  def init_upload():
193
  file = request.files['file']
@@ -200,7 +221,7 @@ def init_upload():
200
  active_streams[stream_id] = {
201
  "label": "Initializing...", "final_decision": False, "confidence": 0, "severity": "--",
202
  "plates": [], "show_tracking": True, "paused": False, "seek_to": None, "skip_val": 0, "progress": 0,
203
- "cnn": 0, "rcnn": 0
204
  }
205
  return jsonify({"stream_id": stream_id, "location": loc, "weather": wx, "time": file_time})
206
 
@@ -208,13 +229,16 @@ def init_upload():
208
  def init_stream():
209
  url = request.json.get('url')
210
 
211
- # --- YouTube Live Stream Extraction ---
212
  if 'youtube.com' in url or 'youtu.be' in url:
213
  try:
214
- ydl_opts = {'format': 'best', 'quiet': True, 'noplaylist': True}
215
  with yt_dlp.YoutubeDL(ydl_opts) as ydl:
216
  info = ydl.extract_info(url, download=False)
217
- url = info.get('url', url)
 
 
 
218
  except Exception as e:
219
  print(f"yt-dlp extraction failed: {e}", flush=True)
220
 
@@ -225,7 +249,7 @@ def init_stream():
225
  active_streams[stream_id] = {
226
  "label": "Connecting...", "final_decision": False, "confidence": 0, "severity": "--",
227
  "plates": [], "show_tracking": True, "paused": False, "seek_to": None, "skip_val": 0, "progress": 0,
228
- "cnn": 0, "rcnn": 0
229
  }
230
  return jsonify({"stream_id": stream_id, "location": loc, "weather": wx, "time": v_time})
231
 
@@ -236,9 +260,9 @@ def video_control(stream_id):
236
  if not state: return jsonify({"error": "not found"}), 404
237
 
238
  action = request.json.get('action')
239
- # Strict boolean casting to fix the toggle bug
240
  if action == 'toggle_tracking':
241
- state["show_tracking"] = True if request.json.get("track") else False
 
242
  elif action == 'pause': state["paused"] = True
243
  elif action == 'play': state["paused"] = False
244
  elif action == 'seek': state["seek_to"] = request.json.get("value", 0.0)
@@ -257,6 +281,7 @@ def video_stream_gen(stream_id, source, location):
257
  yolo_probs = []
258
  frame_count = 0
259
  last_buffer = None
 
260
 
261
  if stream_id in active_streams:
262
  active_streams[stream_id]["label"] = "Scanning Stream..."
@@ -272,15 +297,21 @@ def video_stream_gen(stream_id, source, location):
272
  target = current_frame + (state['skip_val'] * fps)
273
  cap.set(cv2.CAP_PROP_POS_FRAMES, max(0, min(target, total_frames - 1)))
274
  state['skip_val'] = 0
275
- frames_3d.clear(); yolo_probs.clear()
 
276
  force_read = True
277
 
278
  if state.get('seek_to') is not None:
279
  target = int(state['seek_to'] * total_frames)
280
  cap.set(cv2.CAP_PROP_POS_FRAMES, target)
281
  state['seek_to'] = None
282
- frames_3d.clear(); yolo_probs.clear()
 
 
 
 
283
  force_read = True
 
284
 
285
  if state.get('paused') and not force_read:
286
  time.sleep(0.1)
@@ -290,65 +321,75 @@ def video_stream_gen(stream_id, source, location):
290
  loop_start = time.time()
291
  ret, frame = cap.read()
292
 
293
- # --- Handle Stream End, Reconnections & Loops ---
294
  if not ret:
295
- if total_frames > 0:
296
- # Video File reached the end -> Auto-Loop
297
- cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
298
- frames_3d.clear(); yolo_probs.clear()
 
299
  continue
300
- elif is_live:
301
- # Live stream (or single snapshot URL) dropped -> Attempt Reconnect
302
- cap.release()
303
- time.sleep(0.5)
304
- cap = cv2.VideoCapture(source)
305
  continue
306
  else:
307
  break
 
 
 
 
 
 
 
308
 
309
  if total_frames > 0:
310
  state['progress'] = (cap.get(cv2.CAP_PROP_POS_FRAMES) / total_frames) * 100
311
- else:
312
- state['progress'] = 100 # Hide progress bar for live feeds
313
 
314
- # Visualization Toggle
315
- if state.get("show_tracking", True):
316
- track_res = model_tracker(frame, classes=[2, 3, 5, 7], verbose=False, conf=0.3)[0]
317
- display_frame = track_res.plot()
318
- else:
319
- # When tracking is false, we securely show the raw frame
 
 
320
  display_frame = frame
321
 
322
- # Brain Scanning
323
  if not state.get("final_decision"):
324
- res = model_yolo(frame, verbose=False)[0]
325
-
326
- probs = np.zeros(4)
327
- if res.probs is not None:
328
- data = res.probs.data.cpu().numpy()
329
- length = min(len(data), 4)
330
- probs[:length] = data[:length]
331
- elif res.boxes is not None and len(res.boxes) > 0:
332
- for box in res.boxes:
333
- cls_id = int(box.cls[0].item())
334
- if cls_id < 4: probs[cls_id] = max(probs[cls_id], float(box.conf[0].item()))
335
-
336
- yolo_probs.append(probs)
337
- if len(yolo_probs) > 10: yolo_probs.pop(0)
338
-
339
- f_3d = cv2.resize(frame, (112, 112))
340
- frames_3d.append(cv2.cvtColor(f_3d, cv2.COLOR_BGR2RGB))
341
- if len(frames_3d) > 16: frames_3d.pop(0)
342
-
343
- if len(frames_3d) == 16 and frame_count % 10 == 0 and not state.get("is_analyzing"):
344
- state["is_analyzing"] = True
345
- p_max = np.max(yolo_probs, axis=0) if yolo_probs else np.zeros(4)
346
- p_mean = np.mean(yolo_probs, axis=0) if yolo_probs else np.zeros(4)
347
- p_min = np.min(yolo_probs, axis=0) if yolo_probs else np.zeros(4)
348
 
349
- threading.Thread(target=run_temporal_analysis, args=(
350
- stream_id, list(frames_3d), p_max, p_mean, p_min, float(np.max(p_max))*100, frame.copy(), location
351
- )).start()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
352
 
353
  _, buffer = cv2.imencode('.jpg', display_frame)
354
  last_buffer = buffer.tobytes()
 
17
  import numpy as np
18
  import pandas as pd
19
  import joblib
20
+ import smtplib
21
+ import ssl
22
  import threading
23
  import uuid
24
  import time
25
  import requests
26
  import datetime
27
  from urllib.parse import urlparse
28
+ from email.message import EmailMessage
29
  from flask import Flask, request, render_template, jsonify, Response, send_from_directory
30
  from werkzeug.utils import secure_filename
31
 
 
56
 
57
  app = Flask(__name__)
58
 
59
+ ALERT_EMAIL_SENDER = "gowreeshgowri50@gmail.com"
60
+ # FIX: Automatically strip spaces from Google App Password
61
+ ALERT_EMAIL_PASSWORD = "oynu ulet pynk xsza".replace(" ", "")
62
+ ALERT_EMAIL_RECEIVER = "mcblackdevil12342@gmail.com"
63
+ ENABLE_EMAIL_ALERTS = True
64
 
65
  UPLOAD_FOLDER = 'uploads'
66
  MODEL_FOLDER = 'models'
 
97
  model_traffic = None
98
 
99
  # --- Helper Functions ---
100
+
101
  def get_geo_info(ip_or_url=None):
102
  """Fetches location and weather based on IP or URL."""
103
  try:
 
130
  if stream_id in active_streams:
131
  active_streams[stream_id]["plates"] = detected_plates
132
 
133
+ # --- FOOLPROOF PUSH NOTIFICATION (Bypasses HuggingFace SMTP Blocks) ---
134
  try:
135
+ ntfy_url = "https://ntfy.sh/crashvision_sos_alerts"
 
136
  headers = {
137
  "Title": f"🚨 {severity.upper()} ACCIDENT DETECTED",
138
+ "Priority": "high",
139
+ "Tags": "warning,rotating_light,car"
 
140
  }
141
+ msg_body = f"Location: {location}\nSeverity: {severity.capitalize()}\nConfidence: {confidence}%\nTime: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
142
+ requests.post(ntfy_url, data=msg_body.encode('utf-8'), headers=headers, timeout=5)
143
+ print("📱 Push Notification SOS sent via ntfy.sh!", flush=True)
 
 
 
 
 
 
144
  except Exception as e:
145
+ print(f"📱 Push Notification Failed: {e}", flush=True)
146
+
147
+ # --- EMAIL FALLBACK ---
148
+ if ENABLE_EMAIL_ALERTS:
149
+ try:
150
+ _, buffer = cv2.imencode('.jpg', frame)
151
+ msg = EmailMessage()
152
+ msg['Subject'] = f"🚨 ALERT: {severity.upper()} ACCIDENT DETECTED"
153
+ msg['From'] = ALERT_EMAIL_SENDER
154
+ msg['To'] = ALERT_EMAIL_RECEIVER
155
+ msg.set_content(f"Incident Report\nLocation: {location}\nSeverity: {severity}\nConfidence: {confidence}%\nTime: {datetime.datetime.now()}\n\nSystem has locked this stream for investigation.")
156
+
157
+ msg.add_attachment(buffer.tobytes(), maintype='image', subtype='jpeg', filename='incident.jpg')
158
+
159
+ context = ssl.create_default_context()
160
+ with smtplib.SMTP_SSL('smtp.gmail.com', 465, context=context, timeout=15) as server:
161
+ server.login(ALERT_EMAIL_SENDER, ALERT_EMAIL_PASSWORD)
162
+ server.send_message(msg)
163
+
164
+ print(f"📧 Dispatch Email Sent Successfully for Stream {stream_id}", flush=True)
165
+ except Exception as e:
166
+ print(f"📧 Email Failed to Send (Likely Cloud Firewall): {str(e)}", flush=True)
167
 
168
  def run_temporal_analysis(stream_id, frames_3d_copy, prob_max, prob_mean, prob_min, yolo_max_conf, raw_frame, location):
169
  state = active_streams.get(stream_id)
 
183
  confidence = float(ensemble_probs[final_idx] * 100)
184
  severity = classes[final_idx]
185
  except:
186
+ # Fallback if SVM isn't perfectly aligned
187
  final_idx = min(int(np.argmax(prob_max)), 2)
188
  confidence = float(np.max(prob_max)) * 100
189
  severity = classes[final_idx]
 
193
  state["cnn"] = round(yolo_max_conf, 1)
194
  state["rcnn"] = round(float(np.max(prob_3d)) * 100, 1)
195
 
196
+ # Lock the decision only if confidence crosses threshold (Lowered to 75 to ensure triggers)
197
  if confidence > 75:
198
  state["final_decision"] = True
199
  state["label"] = f"Incident Logged: {severity.capitalize()}"
 
203
 
204
  except Exception as e:
205
  print(f"AI Error in Analysis Thread: {e}", flush=True)
206
+ traceback.print_exc()
207
  finally:
208
  if state: state["is_analyzing"] = False
209
 
210
  # --- Route Handlers ---
211
+
212
  @app.route('/init_upload', methods=['POST'])
213
  def init_upload():
214
  file = request.files['file']
 
221
  active_streams[stream_id] = {
222
  "label": "Initializing...", "final_decision": False, "confidence": 0, "severity": "--",
223
  "plates": [], "show_tracking": True, "paused": False, "seek_to": None, "skip_val": 0, "progress": 0,
224
+ "cnn": 0, "rcnn": 0, "force_update": False
225
  }
226
  return jsonify({"stream_id": stream_id, "location": loc, "weather": wx, "time": file_time})
227
 
 
229
  def init_stream():
230
  url = request.json.get('url')
231
 
232
+ # --- YouTube Live Stream Resolution ---
233
  if 'youtube.com' in url or 'youtu.be' in url:
234
  try:
235
+ ydl_opts = {'format': 'best[ext=mp4]/best/bestvideo', 'quiet': True, 'noplaylist': True}
236
  with yt_dlp.YoutubeDL(ydl_opts) as ydl:
237
  info = ydl.extract_info(url, download=False)
238
+ if 'url' in info:
239
+ url = info['url']
240
+ elif 'formats' in info and len(info['formats']) > 0:
241
+ url = info['formats'][-1]['url']
242
  except Exception as e:
243
  print(f"yt-dlp extraction failed: {e}", flush=True)
244
 
 
249
  active_streams[stream_id] = {
250
  "label": "Connecting...", "final_decision": False, "confidence": 0, "severity": "--",
251
  "plates": [], "show_tracking": True, "paused": False, "seek_to": None, "skip_val": 0, "progress": 0,
252
+ "cnn": 0, "rcnn": 0, "force_update": False
253
  }
254
  return jsonify({"stream_id": stream_id, "location": loc, "weather": wx, "time": v_time})
255
 
 
260
  if not state: return jsonify({"error": "not found"}), 404
261
 
262
  action = request.json.get('action')
 
263
  if action == 'toggle_tracking':
264
+ state["show_tracking"] = request.json.get("track", True)
265
+ state["force_update"] = True # Force frame redraw even if paused
266
  elif action == 'pause': state["paused"] = True
267
  elif action == 'play': state["paused"] = False
268
  elif action == 'seek': state["seek_to"] = request.json.get("value", 0.0)
 
281
  yolo_probs = []
282
  frame_count = 0
283
  last_buffer = None
284
+ retry_count = 0
285
 
286
  if stream_id in active_streams:
287
  active_streams[stream_id]["label"] = "Scanning Stream..."
 
297
  target = current_frame + (state['skip_val'] * fps)
298
  cap.set(cv2.CAP_PROP_POS_FRAMES, max(0, min(target, total_frames - 1)))
299
  state['skip_val'] = 0
300
+ frames_3d.clear()
301
+ yolo_probs.clear()
302
  force_read = True
303
 
304
  if state.get('seek_to') is not None:
305
  target = int(state['seek_to'] * total_frames)
306
  cap.set(cv2.CAP_PROP_POS_FRAMES, target)
307
  state['seek_to'] = None
308
+ frames_3d.clear()
309
+ yolo_probs.clear()
310
+ force_read = True
311
+
312
+ if state.get('force_update'):
313
  force_read = True
314
+ state['force_update'] = False
315
 
316
  if state.get('paused') and not force_read:
317
  time.sleep(0.1)
 
321
  loop_start = time.time()
322
  ret, frame = cap.read()
323
 
 
324
  if not ret:
325
+ if is_live:
326
+ retry_count += 1
327
+ if retry_count > 30: # Give up if IP cam is completely dead
328
+ break
329
+ time.sleep(0.2)
330
  continue
331
+ elif total_frames > 0:
332
+ # Auto-loop the video when it reaches the end for continuous replay
333
+ cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
334
+ frames_3d.clear()
335
+ yolo_probs.clear()
336
  continue
337
  else:
338
  break
339
+
340
+ retry_count = 0 # reset on success
341
+
342
+ # 🚨 FIX: Prevent High-Res IP Cams from OOM Crashing HuggingFace
343
+ h, w = frame.shape[:2]
344
+ if w > 1280:
345
+ frame = cv2.resize(frame, (1280, int(h * 1280 / w)))
346
 
347
  if total_frames > 0:
348
  state['progress'] = (cap.get(cv2.CAP_PROP_POS_FRAMES) / total_frames) * 100
 
 
349
 
350
+ # AI Tracking Visualization Layer
351
+ try:
352
+ if state.get("show_tracking", True):
353
+ track_res = model_tracker(frame, classes=[2, 3, 5, 7], verbose=False, conf=0.3)[0]
354
+ display_frame = track_res.plot()
355
+ else:
356
+ display_frame = frame
357
+ except Exception as e:
358
  display_frame = frame
359
 
360
+ # Brain / Severity Scanning
361
  if not state.get("final_decision"):
362
+ try:
363
+ res = model_yolo(frame, verbose=False)[0]
364
+
365
+ probs = np.zeros(4)
366
+ if res.probs is not None:
367
+ data = res.probs.data.cpu().numpy()
368
+ length = min(len(data), 4)
369
+ probs[:length] = data[:length]
370
+ elif res.boxes is not None and len(res.boxes) > 0:
371
+ for box in res.boxes:
372
+ cls_id = int(box.cls[0].item())
373
+ if cls_id < 4: probs[cls_id] = max(probs[cls_id], float(box.conf[0].item()))
 
 
 
 
 
 
 
 
 
 
 
 
374
 
375
+ yolo_probs.append(probs)
376
+ if len(yolo_probs) > 10: yolo_probs.pop(0)
377
+
378
+ f_3d = cv2.resize(frame, (112, 112))
379
+ frames_3d.append(cv2.cvtColor(f_3d, cv2.COLOR_BGR2RGB))
380
+ if len(frames_3d) > 16: frames_3d.pop(0)
381
+
382
+ if len(frames_3d) == 16 and frame_count % 10 == 0 and not state.get("is_analyzing"):
383
+ state["is_analyzing"] = True
384
+ p_max = np.max(yolo_probs, axis=0) if yolo_probs else np.zeros(4)
385
+ p_mean = np.mean(yolo_probs, axis=0) if yolo_probs else np.zeros(4)
386
+ p_min = np.min(yolo_probs, axis=0) if yolo_probs else np.zeros(4)
387
+
388
+ threading.Thread(target=run_temporal_analysis, args=(
389
+ stream_id, list(frames_3d), p_max, p_mean, p_min, float(np.max(p_max))*100, frame.copy(), location
390
+ )).start()
391
+ except Exception as e:
392
+ pass # Skip frame if AI crashes
393
 
394
  _, buffer = cv2.imencode('.jpg', display_frame)
395
  last_buffer = buffer.tobytes()