foreversheikh commited on
Commit
b4e047c
·
verified ·
1 Parent(s): 8ad412d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +292 -1000
app.py CHANGED
@@ -1,1001 +1,293 @@
1
- # import os
2
- # import cv2
3
- # import torch
4
- # import numpy as np
5
- # import time
6
- # from datetime import datetime
7
- # import threading
8
- # import base64
9
- # from werkzeug.utils import secure_filename
10
-
11
- # # --- Load .env file ---
12
- # from dotenv import load_dotenv
13
- # load_dotenv() # Reads the .env file and sets environment variables
14
- # # --- END ---
15
-
16
- # from flask import Flask, render_template, Response, request, jsonify
17
- # from flask_socketio import SocketIO
18
-
19
- # # Important: Make sure your custom utility scripts are accessible
20
- # from utils.load_model import load_models
21
- # from utils.utils import build_transforms
22
- # from network.TorchUtils import get_torch_device
23
- # from yolo_detection import analyze_video_with_yolo
24
- # from video_sumrrizer import summarize_video # Your summarizer
25
-
26
- # # ---- App Setup ----
27
- # app = Flask(__name__)
28
- # app.config['SECRET_KEY'] = 'your_secret_key!'
29
- # UPLOAD_FOLDER = 'uploads'
30
- # os.makedirs(UPLOAD_FOLDER, exist_ok=True)
31
- # app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
32
- # socketio = SocketIO(app, async_mode='eventlet')
33
-
34
- # # ---- Global Config & Model Loading ----
35
- # print("[INFO] Loading models...")
36
- # DEVICE = get_torch_device()
37
- # FEATURE_EXTRACTOR_PATH = "pretrained/c3d.pickle"
38
- # AD_MODEL_PATH = "exps/c3d/models/epoch_80000.pt"
39
- # YOLO_MODEL_PATH = "yolo_my_model.pt"
40
- # SAVE_DIR = "outputs/anomaly_frames"
41
- # ANOMALY_THRESHOLD = 0.4 # Using 0.4 from pipeline_demo
42
- # COOLDOWN_SECS = 60.0 # 60 seconds cooldown
43
- # os.makedirs(SAVE_DIR, exist_ok=True)
44
-
45
- # # --- Get the API key safely ---
46
- # OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
47
- # if not OPENAI_API_KEY:
48
- # print("⚠️ WARNING: OPENAI_API_KEY environment variable not set OR not found in .env file. Summarizer will fail.")
49
-
50
- # anomaly_detector, feature_extractor = load_models(
51
- # FEATURE_EXTRACTOR_PATH, AD_MODEL_PATH, features_method="c3d", device=DEVICE
52
- # )
53
- # feature_extractor.eval()
54
- # anomaly_detector.eval()
55
- # TRANSFORMS = build_transforms(mode="c3d")
56
- # print("[INFO] Models loaded successfully.")
57
-
58
- # VIDEO_PATHS = {
59
- # "Abuse": "static/videos/Abuse.mp4",
60
- # "Arrest": "static/videos/Arrest.mp4",
61
- # "Arson": "static/videos/Arson.mp4",
62
- # "Assault": "static/videos/Assault.mp4",
63
- # "Burglary": "static/videos/Burglary.mp4",
64
- # "Explosion": "static/videos/Explosion.mp4",
65
- # "Fighting": "static/videos/Fighting.mp4",
66
- # "RoadAccidents": "static/videos/RoadAccidents.mp4",
67
- # "Robbery": "static/videos/Robbery.mp4",
68
- # "Shooting": "static/videos/Shooting.mp4",
69
- # "Shoplifting": "static/videos/Shoplifting.mp4",
70
- # "Stealing": "static/videos/Stealing.mp4",
71
- # "Vandalism": "static/videos/Vandalism.mp4",
72
- # "Normal": "static/videos/Normal.mp4"
73
- # }
74
-
75
- # # --- Threading control ---
76
- # thread = None
77
- # thread_lock = threading.Lock()
78
- # stop_event = threading.Event()
79
-
80
- # def smooth_score(scores, new_score, window=5):
81
- # scores.append(new_score)
82
- # if len(scores) > window:
83
- # scores.pop(0)
84
- # return float(np.mean(scores))
85
-
86
- # # --- Background task for saving and summarizing ---
87
- # def _save_clip_and_summarize(video_path, clip_dir, initial_frames, fps, width, height):
88
- # """
89
- # Saves a 30s clip and runs the summarizer ON THAT SAVED CLIP.
90
- # This runs in a separate thread.
91
- # Emits recording=False when finished.
92
- # """
93
- # summary_text = None
94
- # out_path = os.path.join(clip_dir, "anomaly_clip.mp4")
95
-
96
- # try:
97
- # # 1. Save the 30-second clip
98
- # socketio.emit('update_status', {'status': 'Saving 30s clip...'})
99
- # cap2 = cv2.VideoCapture(video_path)
100
- # if not cap2.isOpened():
101
- # raise Exception(f"Save thread: Cannot open video {video_path}")
102
-
103
- # fourcc = cv2.VideoWriter_fourcc(*"mp4v")
104
- # out = cv2.VideoWriter(out_path, fourcc, fps, (width, height))
105
-
106
- # for f in initial_frames:
107
- # out.write(f)
108
- # # socketio.sleep(0) # Optional: uncomment if main thread still unresponsive
109
-
110
- # frames_to_save = int(fps * 30)
111
- # remaining_frames_to_save = frames_to_save - len(initial_frames)
112
-
113
- # # Assuming seek works, otherwise read from start (less accurate for live)
114
- # # Attempt seek if possible, otherwise just continue reading
115
- # # start_frame_approx = int(cap2.get(cv2.CAP_PROP_POS_FRAMES)) - len(initial_frames) # This might not be right
116
- # # cap2.set(cv2.CAP_PROP_POS_FRAMES, start_frame_approx) # Attempt seek
117
-
118
- # for _ in range(remaining_frames_to_save):
119
- # ret, frame = cap2.read()
120
- # if not ret: break
121
- # out.write(frame)
122
- # # socketio.sleep(0) # Optional: uncomment if main thread still unresponsive
123
-
124
- # out.release()
125
- # cap2.release()
126
- # print(f" [INFO] Saved 30-sec anomaly clip -> {out_path}")
127
-
128
- # # 2. Summarize THE SAVED CLIP
129
- # socketio.emit('update_status', {'status': 'Running video summarizer on the clip...'})
130
-
131
- # api_key_from_env = os.getenv("OPENAI_API_KEY") # Get key again in thread
132
- # if not api_key_from_env:
133
- # raise ValueError("OPENAI_API_KEY missing in background thread.")
134
-
135
- # summary_text = summarize_video(video_path=out_path, api=api_key_from_env)
136
- # print("\n✅ VIDEO SUMMARY:\n", summary_text, "\n")
137
-
138
- # except Exception as e:
139
- # summary_text = f"Summarizer Error: {e}"
140
- # print(f"❌ [ERROR] _save_clip_and_summarize failed: {e}")
141
- # finally:
142
- # # Ensure a message is always sent back
143
- # if summary_text is None:
144
- # summary_text = "Summarizer Error: Unknown failure in background thread."
145
-
146
- # # Send the final summary (or error) to the UI
147
- # socketio.emit('update_summary', {'summary': summary_text})
148
- # # Tell the UI the recording has finished
149
- # socketio.emit('recording_signal', {'recording': False})
150
- # print("ℹ️ Background processing finished.")
151
-
152
-
153
- # # --- Main video processing loop (Corrected) ---
154
- # def video_processing_task(video_path):
155
- # global thread
156
- # try:
157
- # cap = cv2.VideoCapture(video_path)
158
- # if not cap.isOpened():
159
- # socketio.emit('processing_error', {'error': 'Could not open video file.'})
160
- # return
161
-
162
- # frame_buffer = []
163
- # last_save_time = 0.0 # Initialize with float
164
- # recent_scores = []
165
-
166
- # # --- ADJUSTED: Frame skipping for graph speed ---
167
- # FRAME_SKIP = 4 # Process 1 frame, skip next 4. Increase if needed.
168
- # # --- END ---
169
-
170
- # frame_count = 0
171
-
172
- # fps = cap.get(cv2.CAP_PROP_FPS) or 25.0
173
- # width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
174
- # height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
175
-
176
- # while cap.isOpened() and not stop_event.is_set():
177
- # socketio.sleep(0.001) # Small sleep to prevent 100% CPU, allow task switching
178
- # ret, frame = cap.read()
179
- # if not ret: break
180
-
181
- # frame_count += 1
182
- # # --- APPLY FRAME SKIPPING ---
183
- # if frame_count % (FRAME_SKIP + 1) != 0:
184
- # continue
185
- # # --- END ---
186
-
187
- # frame_buffer.append(frame.copy())
188
-
189
- # if len(frame_buffer) == 16:
190
- # # Anomaly Detection
191
- # frames_resized = [cv2.resize(f, (112, 112)) for f in frame_buffer]
192
- # clip_np = np.array(frames_resized, dtype=np.uint8)
193
- # clip_torch = torch.from_numpy(clip_np)
194
- # clip_torch = TRANSFORMS(clip_torch)
195
- # clip_torch = clip_torch.unsqueeze(0).to(DEVICE)
196
-
197
- # with torch.no_grad():
198
- # features = feature_extractor(clip_torch).detach()
199
- # score_tensor = anomaly_detector(features).detach()
200
- # score = float(score_tensor.view(-1)[0].item())
201
-
202
- # score = smooth_score(recent_scores, score)
203
- # score = float(np.clip(score, 0, 1))
204
- # socketio.emit('update_graph', {'score': score}) # Send score regardless of anomaly
205
-
206
- # # Anomaly Actions (Check threshold and cooldown)
207
- # now = time.time()
208
- # if score > ANOMALY_THRESHOLD and (now - last_save_time) >= COOLDOWN_SECS:
209
- # print(f"🔥 Anomaly detected! Score: {score:.2f}, Cooldown passed.")
210
- # last_save_time = now # Update time *immediately*
211
-
212
- # # Emit recording signal ONLY when anomaly confirmed + cooldown passed
213
- # socketio.emit('recording_signal', {'recording': True})
214
-
215
- # timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
216
- # clip_dir = os.path.join(SAVE_DIR, f"anomaly_{timestamp}")
217
- # os.makedirs(clip_dir, exist_ok=True)
218
-
219
- # # Run YOLO immediately on the latest frame
220
- # yolo_frame_path = os.path.join(clip_dir, "yolo_frame.jpg")
221
- # preview_frame_img = frame_buffer[-1].copy()
222
- # cv2.imwrite(yolo_frame_path, preview_frame_img)
223
-
224
- # try:
225
- # yolo_result = analyze_video_with_yolo(yolo_frame_path, model_path=YOLO_MODEL_PATH, return_class=True)
226
- # yolo_text = f"🚨 Anomaly Detected YOLO: {yolo_result}"
227
- # except Exception as e:
228
- # yolo_text = f"YOLO Error: {e}"
229
-
230
- # # Send YOLO results immediately
231
- # socketio.emit('update_yolo_text', {'text': yolo_text})
232
- # _, buffer = cv2.imencode('.jpg', preview_frame_img)
233
- # b64_str = base64.b64encode(buffer).decode('utf-8')
234
- # socketio.emit('update_yolo_image', {'image_data': b64_str})
235
-
236
- # # Signal loading for summary
237
- # socketio.emit('update_summary', {'summary': 'loading'})
238
-
239
- # # Start background task for saving & summarizing
240
- # socketio.start_background_task(
241
- # _save_clip_and_summarize,
242
- # video_path,
243
- # clip_dir,
244
- # frame_buffer.copy(), # Pass copy of current buffer
245
- # fps,
246
- # width,
247
- # height
248
- # )
249
- # # End of anomaly action block
250
-
251
- # frame_buffer.clear() # Clear buffer after processing or anomaly trigger
252
-
253
- # cap.release()
254
- # if not stop_event.is_set():
255
- # socketio.emit('processing_finished', {'message': 'Video finished.'})
256
- # print("🏁 Video processing finished normally.")
257
-
258
- # except Exception as e:
259
- # print(f"❌ [ERROR] Unhandled exception in video_processing_task: {e}")
260
- # socketio.emit('processing_error', {'error': f'Runtime error: {e}'})
261
- # finally:
262
- # # Cleanup: Ensure the global thread variable is reset
263
- # with thread_lock:
264
- # # Check if this *specific* thread instance is the one finishing
265
- # # This check helps prevent race conditions if reset is called rapidly
266
- # # if threading.current_thread() is thread: # This comparison might not work with eventlet's green threads
267
- # if thread is not None: # Simpler check: if a thread exists, clear it
268
- # print("🧹 Cleaning up video processing task.")
269
- # thread = None
270
- # stop_event.clear()
271
-
272
-
273
- # # --- (Routes: /, /upload, /video_stream remain the same) ---
274
- # @app.route('/')
275
- # def index():
276
- # # Pass the list of demo video names to the template
277
- # return render_template('index.html', anomaly_names=VIDEO_PATHS.keys())
278
-
279
- # @app.route('/upload', methods=['POST'])
280
- # def upload_file():
281
- # if 'video' not in request.files:
282
- # return jsonify({'error': 'No video file found'}), 400
283
- # file = request.files['video']
284
- # if file.filename == '':
285
- # return jsonify({'error': 'No video file selected'}), 400
286
- # if file:
287
- # filename = secure_filename(file.filename)
288
- # # Add timestamp for uniqueness
289
- # unique_filename = f"{datetime.now().strftime('%Y%m%d%H%M%S')}_{filename}"
290
- # save_path = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)
291
- # try:
292
- # file.save(save_path)
293
- # print(f"✅ [INFO] File uploaded successfully: {save_path}")
294
- # return jsonify({'success': True, 'filename': unique_filename})
295
- # except Exception as e:
296
- # print(f"❌ [ERROR] File save failed: {e}")
297
- # return jsonify({'error': f'File save failed: {e}'}), 500
298
- # # Fallback error
299
- # return jsonify({'error': 'Unknown file upload error'}), 500
300
-
301
-
302
- # @app.route('/video_stream/<source>/<path:filename>') # Use path converter for flexibility
303
- # def video_stream(source, filename):
304
- # path = None
305
- # safe_filename = secure_filename(filename) # Sanitize filename
306
-
307
- # if source == 'demo':
308
- # # Demo keys should ideally be safe, direct lookup might be fine
309
- # path = VIDEO_PATHS.get(filename)
310
- # elif source == 'upload':
311
- # path = os.path.join(app.config['UPLOAD_FOLDER'], safe_filename)
312
- # else:
313
- # print(f"⚠️ [WARN] Invalid source requested for video stream: {source}")
314
- # return "Invalid source", 404
315
-
316
- # if not path or not os.path.exists(path):
317
- # print(f"❌ [ERROR] Video stream file not found: {path}")
318
- # return "Video not found", 404
319
-
320
- # def generate():
321
- # try:
322
- # with open(path, "rb") as f:
323
- # while chunk := f.read(1024 * 1024): # Stream in 1MB chunks
324
- # yield chunk
325
- # socketio.sleep(0.0001) # Small sleep during streaming helps prevent blocking
326
- # except Exception as e:
327
- # print(f"❌ [ERROR] Error during video streaming generation for {path}: {e}")
328
-
329
- # # Use Werkzeug's FileWrapper for potentially better streaming performance/handling
330
- # # from werkzeug.wsgi import FileWrapper
331
- # # response = Response(FileWrapper(open(path, "rb")), mimetype="video/mp4", direct_passthrough=True)
332
- # # return response
333
- # print(f"ℹ️ Streaming video: {path}")
334
- # return Response(generate(), mimetype="video/mp4")
335
-
336
-
337
- # # --- (SocketIO handlers: start_processing, reset_system remain the same) ---
338
- # @socketio.on('start_processing')
339
- # def handle_start_processing(data):
340
- # global thread
341
- # with thread_lock:
342
- # if thread is None: # Only start if no thread is running
343
- # stop_event.clear() # Ensure stop event is clear before starting
344
- # source = data.get('source')
345
- # filename = data.get('filename')
346
- # video_path = None
347
- # safe_filename = secure_filename(filename) if filename else None
348
-
349
- # if not safe_filename:
350
- # print("❌ [ERROR] Start processing requested with invalid filename.")
351
- # socketio.emit('processing_error', {'error': 'Invalid filename provided.'})
352
- # return
353
-
354
- # if source == 'demo':
355
- # video_path = VIDEO_PATHS.get(filename) # Use original filename for dict lookup
356
- # elif source == 'upload':
357
- # video_path = os.path.join(app.config['UPLOAD_FOLDER'], safe_filename)
358
-
359
- # if video_path and os.path.exists(video_path):
360
- # print(f"🚀 [INFO] Starting processing for '{safe_filename}' from source '{source}'")
361
- # # Start the background task using socketio's method for compatibility with eventlet/gevent
362
- # thread = socketio.start_background_task(target=video_processing_task, video_path=video_path)
363
- # else:
364
- # error_msg = f'Video file not found at path: {video_path}'
365
- # print(f"❌ [ERROR] {error_msg}")
366
- # socketio.emit('processing_error', {'error': 'Video file could not be found.'}) # User-friendly message
367
- # else:
368
- # # Prevent starting multiple threads
369
- # print("⚠️ [WARN] Processing task already running. Ignoring new 'start_processing' request.")
370
- # socketio.emit('update_status', {'status': 'Processing is already in progress.'})
371
-
372
-
373
- # @socketio.on('reset_system')
374
- # def handle_reset():
375
- # global thread
376
- # with thread_lock:
377
- # if thread is not None:
378
- # print("🔄 [INFO] Reset requested. Signaling background task to stop...")
379
- # stop_event.set()
380
- # # The background task's 'finally' block handles cleanup (thread = None, stop_event.clear())
381
- # else:
382
- # print("ℹ️ [INFO] Reset requested, but no processing task was running.")
383
- # # Always confirm reset to UI immediately
384
- # socketio.emit('system_reset_confirm')
385
- # print("✅ [INFO] Reset confirmation sent to client.")
386
-
387
-
388
- # if __name__ == '__main__':
389
- # print("🐍 [INFO] Starting Flask server...")
390
- # # Use host='0.0.0.0' to allow access from other devices on your network
391
- # # Use debug=True for development (auto-reloads), False for production
392
- # # Port 5000 is the default for Flask
393
- # socketio.run(app, debug=True)
394
-
395
- #============================================================================================================================================================================
396
-
397
-
398
- # import os
399
- # import cv2
400
- # import torch
401
- # import numpy as np
402
- # import time
403
- # from datetime import datetime
404
- # import threading
405
- # import base64
406
- # from werkzeug.utils import secure_filename
407
-
408
- # # --- Load .env file ---
409
- # from dotenv import load_dotenv
410
- # load_dotenv()
411
- # # --- END ---
412
-
413
- # from flask import Flask, render_template, Response, request, jsonify
414
- # from flask_socketio import SocketIO
415
-
416
- # # Important: Adjust imports if your structure changed
417
- # from utils.load_model import load_models
418
- # from utils.utils import build_transforms
419
- # from network.TorchUtils import get_torch_device
420
- # from yolo_detection import analyze_video_with_yolo
421
- # from video_sumrrizer import summarize_video # Your summarizer
422
-
423
- # # ---- App Setup ----
424
- # app = Flask(__name__)
425
- # app.config['SECRET_KEY'] = 'your_secret_key!'
426
- # UPLOAD_FOLDER = 'uploads'
427
- # os.makedirs(UPLOAD_FOLDER, exist_ok=True)
428
- # app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
429
- # socketio = SocketIO(app, async_mode='eventlet')
430
-
431
- # # ---- Global Config & Model Loading ----
432
- # print("[INFO] Loading models...")
433
- # DEVICE = get_torch_device()
434
- # FEATURE_EXTRACTOR_PATH = "pretrained/c3d.pickle"
435
- # AD_MODEL_PATH = "exps/c3d/models/epoch_80000.pt"
436
- # YOLO_MODEL_PATH = "yolo_my_model.pt"
437
- # SAVE_DIR = "outputs/anomaly_frames"
438
- # ANOMALY_THRESHOLD = 0.4
439
- # COOLDOWN_SECS = 60.0
440
- # os.makedirs(SAVE_DIR, exist_ok=True)
441
-
442
- # OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
443
- # if not OPENAI_API_KEY:
444
- # print("⚠️ WARNING: OPENAI_API_KEY not set!")
445
-
446
- # anomaly_detector, feature_extractor = load_models(
447
- # FEATURE_EXTRACTOR_PATH, AD_MODEL_PATH, features_method="c3d", device=DEVICE
448
- # )
449
- # feature_extractor.eval()
450
- # anomaly_detector.eval()
451
- # TRANSFORMS = build_transforms(mode="c3d")
452
- # print("[INFO] Models loaded successfully.")
453
-
454
- # VIDEO_PATHS = { # Ensure these paths are correct relative to app.py
455
- # "Abuse": "static/videos/Abuse.mp4", "Arrest": "static/videos/Arrest.mp4",
456
- # "Arson": "static/videos/Arson.mp4", "Assault": "static/videos/Assault.mp4",
457
- # "Burglary": "static/videos/Burglary.mp4", "Explosion": "static/videos/Explosion.mp4",
458
- # "Fighting": "static/videos/Fighting.mp4", "RoadAccidents": "static/videos/RoadAccidents.mp4",
459
- # "Robbery": "static/videos/Robbery.mp4", "Shooting": "static/videos/Shooting.mp4",
460
- # "Shoplifting": "static/videos/Shoplifting.mp4", "Stealing": "static/videos/Stealing.mp4",
461
- # "Vandalism": "static/videos/Vandalism.mp4", "Normal": "static/videos/Normal.mp4"
462
- # }
463
-
464
- # # --- Thread control ---
465
- # thread = None
466
- # thread_lock = threading.Lock()
467
- # stop_event = threading.Event()
468
-
469
- # def smooth_score(scores, new_score, window=5):
470
- # scores.append(new_score); scores = scores[-window:]; return float(np.mean(scores))
471
-
472
- # # --- Background Task 1 - Save Clip, THEN Schedule Summarizer ---
473
- # def _save_clip_then_summarize(video_path, clip_dir, initial_frames, fps, width, height):
474
- # """
475
- # Saves a 30s clip, waits 30 seconds, then starts the summarizer task.
476
- # """
477
- # out_path = os.path.join(clip_dir, "anomaly_clip.mp4")
478
- # save_success = False
479
- # try:
480
- # socketio.emit('update_status', {'status': 'Saving 30s clip...'})
481
- # cap2 = cv2.VideoCapture(video_path)
482
- # if not cap2.isOpened(): raise Exception(f"Cannot open video {video_path}")
483
-
484
- # fourcc = cv2.VideoWriter_fourcc(*"mp4v")
485
- # out = cv2.VideoWriter(out_path, fourcc, fps, (width, height))
486
-
487
- # for f_idx, f in enumerate(initial_frames):
488
- # out.write(f)
489
- # if f_idx % 5 == 0: socketio.sleep(0.001) # Yield
490
-
491
- # frames_to_save = int(fps * 30)
492
- # remaining = frames_to_save - len(initial_frames)
493
-
494
- # for i in range(remaining):
495
- # ret, frame = cap2.read()
496
- # if not ret: break
497
- # out.write(frame)
498
- # if i % 5 == 0: socketio.sleep(0.001) # Yield more
499
-
500
- # out.release()
501
- # cap2.release()
502
- # print(f"✅ [INFO] Saved 30-sec clip -> {out_path}")
503
- # save_success = True
504
-
505
- # except Exception as e:
506
- # print(f"❌ [ERROR] _save_clip failed: {e}")
507
- # socketio.emit('update_summary', {'summary': f"Error saving clip: {e}"})
508
- # socketio.emit('recording_signal', {'recording': False}) # Stop recording on error
509
-
510
- # finally:
511
- # # --- NEW PLAN: If saving succeeded, wait then summarize ---
512
- # if save_success:
513
- # try:
514
- # wait_duration = 30 # Seconds to wait
515
- # print(f"⏳ [INFO] Clip saved. Waiting {wait_duration} seconds before starting summarization...")
516
- # socketio.emit('update_status', {'status': f'Clip saved. Waiting {wait_duration}s...'})
517
- # socketio.sleep(wait_duration) # Wait within this background task context
518
-
519
- # print("🚀 Starting background task for summarization...")
520
- # socketio.emit('update_status', {'status': 'Starting summarizer...'})
521
- # # Start summarizer in a *new* background task
522
- # socketio.start_background_task(_run_summarizer, out_path)
523
- # except Exception as e:
524
- # print(f"❌ [ERROR] Failed to start summarizer task after delay: {e}")
525
- # socketio.emit('update_summary', {'summary': f"Error starting summarizer: {e}"})
526
- # socketio.emit('recording_signal', {'recording': False}) # Ensure recording stops
527
- # # --- End New Plan ---
528
-
529
-
530
- # # --- Background Task 2 - Just Run Summarizer ---
531
- # def _run_summarizer(saved_clip_path):
532
- # """
533
- # Runs the summarizer on the already saved clip. Emits results and recording=False.
534
- # """
535
- # summary_text = None
536
- # try:
537
- # print(f"⏳ [INFO] Summarizing clip: {saved_clip_path}")
538
- # socketio.emit('update_status', {'status': 'Summarizing clip...'})
539
- # socketio.sleep(0.01) # Yield before blocking call
540
-
541
- # api_key_from_env = os.getenv("OPENAI_API_KEY")
542
- # if not api_key_from_env: raise ValueError("OPENAI_API_KEY missing.")
543
-
544
- # summary_text = summarize_video(video_path=saved_clip_path, api=api_key_from_env)
545
- # print("\n✅ VIDEO SUMMARY (snippet):\n", summary_text[:100] + "...", "\n")
546
-
547
- # except Exception as e:
548
- # summary_text = f"Summarizer Error: {e}"
549
- # print(f"❌ [ERROR] _run_summarizer failed: {e}")
550
- # finally:
551
- # if summary_text is None: summary_text = "Summarizer Error: Unknown failure."
552
- # socketio.emit('update_summary', {'summary': summary_text})
553
- # socketio.emit('recording_signal', {'recording': False}) # Signal recording finished
554
- # print("ℹ️ Summarization processing finished.")
555
-
556
-
557
- # # --- Main video processing loop ---
558
- # def video_processing_task(video_path):
559
- # global thread
560
- # try:
561
- # cap = cv2.VideoCapture(video_path)
562
- # if not cap.isOpened(): raise Exception("Cannot open video file")
563
-
564
- # frame_buffer, last_save_time, recent_scores = [], 0.0, []
565
- # FRAME_SKIP = 6 # Further increased frame skipping
566
- # frame_count = 0
567
- # fps, width, height = (cap.get(cv2.CAP_PROP_FPS) or 25.0,
568
- # int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),
569
- # int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
570
-
571
- # while cap.isOpened() and not stop_event.is_set():
572
- # socketio.sleep(0.002) # Slightly longer sleep in main loop for yielding
573
-
574
- # ret, frame = cap.read()
575
- # if not ret: break
576
-
577
- # frame_count += 1
578
- # if frame_count % (FRAME_SKIP + 1) != 0: continue # Apply skipping
579
-
580
- # frame_buffer.append(frame.copy())
581
-
582
- # if len(frame_buffer) == 16:
583
- # socketio.sleep(0.001) # Yield before inference
584
-
585
- # # --- Anomaly Detection ---
586
- # frames_resized = [cv2.resize(f, (112, 112)) for f in frame_buffer]
587
- # clip_np = np.array(frames_resized, dtype=np.uint8)
588
- # clip_torch = torch.from_numpy(clip_np)
589
- # clip_torch = TRANSFORMS(clip_torch).unsqueeze(0).to(DEVICE)
590
- # with torch.no_grad():
591
- # features = feature_extractor(clip_torch).detach()
592
- # score = float(anomaly_detector(features).detach().item())
593
- # score = smooth_score(recent_scores, score)
594
- # score = float(np.clip(score, 0, 1))
595
- # socketio.emit('update_graph', {'score': score})
596
- # # --- End Anomaly Detection ---
597
-
598
- # # --- Anomaly Actions ---
599
- # now = time.time()
600
- # if score > ANOMALY_THRESHOLD and (now - last_save_time) >= COOLDOWN_SECS:
601
- # print(f"🔥 Anomaly! Score: {score:.2f}")
602
- # last_save_time = now
603
-
604
- # socketio.emit('recording_signal', {'recording': True})
605
- # timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
606
- # clip_dir = os.path.join(SAVE_DIR, f"anomaly_{timestamp}")
607
- # os.makedirs(clip_dir, exist_ok=True)
608
-
609
- # # Quick YOLO
610
- # yolo_frame_path = os.path.join(clip_dir, "yolo_frame.jpg")
611
- # preview_frame_img = frame_buffer[-1].copy()
612
- # cv2.imwrite(yolo_frame_path, preview_frame_img)
613
- # try:
614
- # yolo_result = analyze_video_with_yolo(yolo_frame_path, model_path=YOLO_MODEL_PATH, return_class=True)
615
- # yolo_text = f"🚨 Anomaly → YOLO: {yolo_result}"
616
- # except Exception as e: yolo_text = f"YOLO Error: {e}"
617
- # socketio.emit('update_yolo_text', {'text': yolo_text})
618
- # _, buffer = cv2.imencode('.jpg', preview_frame_img)
619
- # b64_str = base64.b64encode(buffer).decode('utf-8')
620
- # socketio.emit('update_yolo_image', {'image_data': b64_str})
621
-
622
- # # Signal loading for summary
623
- # socketio.emit('update_summary', {'summary': 'loading'})
624
- # socketio.sleep(0.005) # Yield before starting save task
625
-
626
- # # Start ONLY the saving task (which will later schedule summarizer)
627
- # print("🚀 Starting background task for clip saving...")
628
- # socketio.start_background_task(
629
- # _save_clip_then_summarize, # Use the new function
630
- # video_path, clip_dir, frame_buffer.copy(),
631
- # fps, width, height
632
- # )
633
- # # --- End anomaly actions ---
634
- # frame_buffer.clear()
635
-
636
- # cap.release()
637
- # if not stop_event.is_set(): socketio.emit('processing_finished', {'message': 'Video finished.'})
638
- # print("🏁 Video processing task ended.")
639
-
640
- # except Exception as e:
641
- # print(f"❌ [ERROR] Unhandled exception in video_processing_task: {e}")
642
- # socketio.emit('processing_error', {'error': f'Runtime error: {e}'})
643
- # finally:
644
- # with thread_lock:
645
- # if thread is not None:
646
- # print("🧹 Cleaning up video processing task.")
647
- # thread = None; stop_event.clear()
648
-
649
- # # --- (Routes: /, /upload, /video_stream remain the same) ---
650
- # @app.route('/')
651
- # def index(): return render_template('index.html', anomaly_names=VIDEO_PATHS.keys())
652
-
653
- # @app.route('/upload', methods=['POST'])
654
- # def upload_file():
655
- # if 'video' not in request.files: return jsonify({'error': 'No video file'}), 400
656
- # file = request.files['video']; name = file.filename
657
- # if name == '': return jsonify({'error': 'No video selected'}), 400
658
- # filename = secure_filename(name); ts = datetime.now().strftime('%Y%m%d%H%M%S')
659
- # unique_name = f"{ts}_{filename}"; path = os.path.join(app.config['UPLOAD_FOLDER'], unique_name)
660
- # try: file.save(path); print(f"✅ Uploaded: {path}"); return jsonify({'success': True, 'filename': unique_name})
661
- # except Exception as e: print(f"❌ Upload failed: {e}"); return jsonify({'error': f'{e}'}), 500
662
-
663
- # @app.route('/video_stream/<source>/<path:filename>')
664
- # def video_stream(source, filename):
665
- # path, safe_name = None, secure_filename(filename)
666
- # if source == 'demo': path = VIDEO_PATHS.get(filename)
667
- # elif source == 'upload': path = os.path.join(app.config['UPLOAD_FOLDER'], safe_name)
668
- # else: return "Invalid source", 404
669
- # if not path or not os.path.exists(path): return "Video not found", 404
670
- # def generate():
671
- # try:
672
- # with open(path, "rb") as f:
673
- # while chunk := f.read(1024*1024): yield chunk; socketio.sleep(0.0001)
674
- # except Exception as e: print(f"❌ Streaming error: {e}")
675
- # print(f"ℹ️ Streaming: {path}"); return Response(generate(), mimetype="video/mp4")
676
-
677
- # # --- (SocketIO handlers: start_processing, reset_system remain the same) ---
678
- # @socketio.on('start_processing')
679
- # def handle_start_processing(data):
680
- # global thread
681
- # with thread_lock:
682
- # if thread is None:
683
- # stop_event.clear(); source, name = data.get('source'), data.get('filename')
684
- # path, safe_name = None, secure_filename(name) if name else None
685
- # if not safe_name: return socketio.emit('processing_error', {'error': 'Invalid filename.'})
686
- # if source == 'demo': path = VIDEO_PATHS.get(name)
687
- # elif source == 'upload': path = os.path.join(app.config['UPLOAD_FOLDER'], safe_name)
688
- # if path and os.path.exists(path):
689
- # print(f"🚀 Starting processing: '{safe_name}' ({source})")
690
- # thread = socketio.start_background_task(video_processing_task, path)
691
- # else: socketio.emit('processing_error', {'error': 'Video file not found.'})
692
- # else: socketio.emit('update_status', {'status': 'Processing already running.'})
693
-
694
- # @socketio.on('reset_system')
695
- # def handle_reset():
696
- # global thread
697
- # with thread_lock:
698
- # if thread: print("🔄 Reset requested. Stopping..."); stop_event.set()
699
- # else: print("ℹ️ Reset requested, none running.")
700
- # socketio.emit('system_reset_confirm'); print("✅ Reset confirmed.")
701
-
702
- # if __name__ == '__main__':
703
- # print("🐍 Starting Flask server...")
704
- # socketio.run(app, debug=True)
705
-
706
-
707
- #================================================================================================================================================
708
-
709
- import os
710
- import cv2
711
- import torch
712
- import numpy as np
713
- import time
714
- from datetime import datetime
715
- import threading
716
- import base64
717
- from werkzeug.utils import secure_filename
718
-
719
- # --- Load .env file ---
720
- from dotenv import load_dotenv
721
- load_dotenv()
722
- # --- END ---
723
-
724
- from flask import Flask, render_template, Response, request, jsonify
725
- from flask_socketio import SocketIO
726
-
727
- # Important: Adjust imports if your structure changed
728
- from utils.load_model import load_models
729
- from utils.utils import build_transforms
730
- from network.TorchUtils import get_torch_device
731
- from yolo_detection import analyze_video_with_yolo
732
- from video_sumrrizer import summarize_video # Your summarizer
733
-
734
- # ---- App Setup ----
735
- app = Flask(__name__)
736
- app.config['SECRET_KEY'] = 'your_secret_key!'
737
- UPLOAD_FOLDER = 'uploads'
738
- os.makedirs(UPLOAD_FOLDER, exist_ok=True)
739
- app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
740
- socketio = SocketIO(app, async_mode='eventlet')
741
-
742
- # ---- Global Config & Model Loading ----
743
- print("[INFO] Loading models...")
744
- DEVICE = get_torch_device()
745
- FEATURE_EXTRACTOR_PATH = "pretrained/c3d.pickle"
746
- AD_MODEL_PATH = "exps/c3d/models/epoch_80000.pt"
747
- YOLO_MODEL_PATH = "yolo_my_model.pt"
748
- SAVE_DIR = "outputs/anomaly_frames"
749
- ANOMALY_THRESHOLD = 0.4
750
- COOLDOWN_SECS = 60.0 # <--- This enforces the 60 second anomaly cooldown
751
- os.makedirs(SAVE_DIR, exist_ok=True)
752
-
753
- OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
754
- if not OPENAI_API_KEY: print("⚠️ WARNING: OPENAI_API_KEY not set!")
755
-
756
- anomaly_detector, feature_extractor = load_models( FEATURE_EXTRACTOR_PATH, AD_MODEL_PATH, features_method="c3d", device=DEVICE)
757
- feature_extractor.eval(); anomaly_detector.eval()
758
- TRANSFORMS = build_transforms(mode="c3d")
759
- print("[INFO] Models loaded successfully.")
760
-
761
- VIDEO_PATHS = { # Ensure these paths are correct relative to app.py
762
- "Abuse": "static/videos/Abuse.mp4", "Arrest": "static/videos/Arrest.mp4",
763
- "Arson": "static/videos/Arson.mp4", "Assault": "static/videos/Assault.mp4",
764
- "Burglary": "static/videos/Burglary.mp4", "Explosion": "static/videos/Explosion.mp4",
765
- "Fighting": "static/videos/Fighting.mp4", "RoadAccidents": "static/videos/RoadAccidents.mp4",
766
- "Robbery": "static/videos/Robbery.mp4", "Shooting": "static/videos/Shooting.mp4",
767
- "Shoplifting": "static/videos/Shoplifting.mp4", "Stealing": "static/videos/Stealing.mp4",
768
- "Vandalism": "static/videos/Vandalism.mp4", "Normal": "static/videos/Normal.mp4"
769
- }
770
-
771
- # --- Thread control ---
772
- thread = None; thread_lock = threading.Lock(); stop_event = threading.Event()
773
-
774
- def smooth_score(scores, new_score, window=5):
775
- scores.append(new_score); scores = scores[-window:]; return float(np.mean(scores))
776
-
777
- # --- MODIFIED: Renamed and removed internal wait ---
778
- def _save_clip_and_start_summarizer(video_path, clip_dir, initial_frames, fps, width, height):
779
- """
780
- Saves a 30s clip, then immediately starts the summarizer task.
781
- """
782
- out_path = os.path.join(clip_dir, "anomaly_clip.mp4")
783
- save_success = False
784
- try:
785
- socketio.emit('update_status', {'status': 'Saving 30s clip...'})
786
- cap2 = cv2.VideoCapture(video_path)
787
- if not cap2.isOpened(): raise Exception(f"Cannot open video {video_path}")
788
- fourcc = cv2.VideoWriter_fourcc(*"mp4v")
789
- out = cv2.VideoWriter(out_path, fourcc, fps, (width, height))
790
-
791
- for f_idx, f in enumerate(initial_frames):
792
- out.write(f)
793
- if f_idx % 5 == 0: socketio.sleep(0.001)
794
-
795
- frames_to_save = int(fps * 30)
796
- remaining = frames_to_save - len(initial_frames)
797
-
798
- for i in range(remaining):
799
- ret, frame = cap2.read()
800
- if not ret: break
801
- out.write(frame)
802
- if i % 5 == 0: socketio.sleep(0.001)
803
-
804
- out.release(); cap2.release()
805
- print(f"✅ [INFO] Saved 30-sec clip -> {out_path}")
806
- save_success = True
807
-
808
- except Exception as e:
809
- print(f"❌ [ERROR] _save_clip failed: {e}")
810
- socketio.emit('update_summary', {'summary': f"Error saving clip: {e}"})
811
- socketio.emit('recording_signal', {'recording': False})
812
-
813
- finally:
814
- # --- If saving succeeded, start summarizer immediately ---
815
- if save_success:
816
- try:
817
- print("🚀 Starting background task for summarization...")
818
- socketio.emit('update_status', {'status': 'Clip saved. Starting summarizer...'})
819
- # Start summarizer in a *new* background task
820
- socketio.start_background_task(_run_summarizer, out_path)
821
- except Exception as e:
822
- print(f"❌ [ERROR] Failed to start summarizer task: {e}")
823
- socketio.emit('update_summary', {'summary': f"Error starting summarizer: {e}"})
824
- socketio.emit('recording_signal', {'recording': False})
825
- # --- End ---
826
-
827
- # --- Background Task 2 - Just Run Summarizer (Unchanged) ---
828
- def _run_summarizer(saved_clip_path):
829
- """
830
- Runs the summarizer on the already saved clip. Emits results and recording=False.
831
- """
832
- summary_text = None
833
- try:
834
- print(f"⏳ [INFO] Summarizing clip: {saved_clip_path}")
835
- socketio.emit('update_status', {'status': 'Summarizing clip...'})
836
- socketio.sleep(0.01) # Yield before blocking call
837
-
838
- api_key_from_env = os.getenv("OPENAI_API_KEY")
839
- if not api_key_from_env: raise ValueError("OPENAI_API_KEY missing.")
840
-
841
- summary_text = summarize_video(video_path=saved_clip_path, api=api_key_from_env)
842
- print("\n✅ VIDEO SUMMARY (snippet):\n", summary_text[:100] + "...", "\n")
843
-
844
- except Exception as e:
845
- summary_text = f"Summarizer Error: {e}"
846
- print(f"❌ [ERROR] _run_summarizer failed: {e}")
847
- finally:
848
- if summary_text is None: summary_text = "Summarizer Error: Unknown failure."
849
- socketio.emit('update_summary', {'summary': summary_text})
850
- socketio.emit('recording_signal', {'recording': False}) # Signal recording finished
851
- print("ℹ️ Summarization processing finished.")
852
-
853
-
854
- # --- Main video processing loop (Unchanged from previous fix) ---
855
- def video_processing_task(video_path):
856
- global thread
857
- try:
858
- cap = cv2.VideoCapture(video_path)
859
- if not cap.isOpened(): raise Exception("Cannot open video file")
860
-
861
- frame_buffer, last_save_time, recent_scores = [], 0.0, []
862
- FRAME_SKIP = 6 # Keep increased frame skipping
863
- frame_count = 0
864
- fps, width, height = (cap.get(cv2.CAP_PROP_FPS) or 25.0,
865
- int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),
866
- int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
867
-
868
- while cap.isOpened() and not stop_event.is_set():
869
- socketio.sleep(0.002) # Keep yielding
870
-
871
- ret, frame = cap.read()
872
- if not ret: break
873
-
874
- frame_count += 1
875
- if frame_count % (FRAME_SKIP + 1) != 0: continue # Apply skipping
876
-
877
- frame_buffer.append(frame.copy())
878
-
879
- if len(frame_buffer) == 16:
880
- socketio.sleep(0.001) # Yield before inference
881
-
882
- # --- Anomaly Detection ---
883
- frames_resized = [cv2.resize(f, (112, 112)) for f in frame_buffer]
884
- clip_np = np.array(frames_resized, dtype=np.uint8)
885
- clip_torch = torch.from_numpy(clip_np)
886
- clip_torch = TRANSFORMS(clip_torch).unsqueeze(0).to(DEVICE)
887
- with torch.no_grad():
888
- features = feature_extractor(clip_torch).detach()
889
- score = float(anomaly_detector(features).detach().item())
890
- score = smooth_score(recent_scores, score)
891
- score = float(np.clip(score, 0, 1))
892
- socketio.emit('update_graph', {'score': score})
893
- # --- End Anomaly Detection ---
894
-
895
- # --- Anomaly Actions ---
896
- now = time.time()
897
- # --- This check enforces the 60 second cooldown ---
898
- if score > ANOMALY_THRESHOLD and (now - last_save_time) >= COOLDOWN_SECS:
899
- print(f"🔥 Anomaly! Score: {score:.2f}")
900
- last_save_time = now # Reset cooldown timer
901
-
902
- socketio.emit('recording_signal', {'recording': True})
903
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
904
- clip_dir = os.path.join(SAVE_DIR, f"anomaly_{timestamp}")
905
- os.makedirs(clip_dir, exist_ok=True)
906
-
907
- # Quick YOLO
908
- yolo_frame_path = os.path.join(clip_dir, "yolo_frame.jpg")
909
- preview_frame_img = frame_buffer[-1].copy()
910
- cv2.imwrite(yolo_frame_path, preview_frame_img)
911
- try:
912
- yolo_result = analyze_video_with_yolo(yolo_frame_path, model_path=YOLO_MODEL_PATH, return_class=True)
913
- yolo_text = f"🚨 Anomaly → YOLO: {yolo_result}"
914
- except Exception as e: yolo_text = f"YOLO Error: {e}"
915
- socketio.emit('update_yolo_text', {'text': yolo_text})
916
- _, buffer = cv2.imencode('.jpg', preview_frame_img)
917
- b64_str = base64.b64encode(buffer).decode('utf-8')
918
- socketio.emit('update_yolo_image', {'image_data': b64_str})
919
-
920
- socketio.emit('update_summary', {'summary': 'loading'})
921
- socketio.sleep(0.005) # Yield before starting save task
922
-
923
- # Start the saving task (which will later schedule summarizer)
924
- print("🚀 Starting background task for clip saving...")
925
- socketio.start_background_task(
926
- _save_clip_and_start_summarizer, # Use the modified function
927
- video_path, clip_dir, frame_buffer.copy(),
928
- fps, width, height
929
- )
930
- # --- End anomaly actions ---
931
- frame_buffer.clear()
932
-
933
- cap.release()
934
- if not stop_event.is_set(): socketio.emit('processing_finished', {'message': 'Video finished.'})
935
- print("🏁 Video processing task ended.")
936
-
937
- except Exception as e:
938
- print(f"❌ [ERROR] Unhandled exception in video_processing_task: {e}")
939
- socketio.emit('processing_error', {'error': f'Runtime error: {e}'})
940
- finally:
941
- with thread_lock:
942
- if thread is not None:
943
- print("🧹 Cleaning up video processing task.")
944
- thread = None; stop_event.clear()
945
-
946
- # --- (Routes: /, /upload, /video_stream remain the same) ---
947
- @app.route('/')
948
- def index(): return render_template('index.html', anomaly_names=VIDEO_PATHS.keys())
949
-
950
- @app.route('/upload', methods=['POST'])
951
- def upload_file():
952
- if 'video' not in request.files: return jsonify({'error': 'No video file'}), 400
953
- file = request.files['video']; name = file.filename
954
- if name == '': return jsonify({'error': 'No video selected'}), 400
955
- filename = secure_filename(name); ts = datetime.now().strftime('%Y%m%d%H%M%S')
956
- unique_name = f"{ts}_{filename}"; path = os.path.join(app.config['UPLOAD_FOLDER'], unique_name)
957
- try: file.save(path); print(f"✅ Uploaded: {path}"); return jsonify({'success': True, 'filename': unique_name})
958
- except Exception as e: print(f"❌ Upload failed: {e}"); return jsonify({'error': f'{e}'}), 500
959
-
960
- @app.route('/video_stream/<source>/<path:filename>')
961
- def video_stream(source, filename):
962
- path, safe_name = None, secure_filename(filename)
963
- if source == 'demo': path = VIDEO_PATHS.get(filename)
964
- elif source == 'upload': path = os.path.join(app.config['UPLOAD_FOLDER'], safe_name)
965
- else: return "Invalid source", 404
966
- if not path or not os.path.exists(path): return "Video not found", 404
967
- def generate():
968
- try:
969
- with open(path, "rb") as f:
970
- while chunk := f.read(1024*1024): yield chunk; socketio.sleep(0.0001)
971
- except Exception as e: print(f"❌ Streaming error: {e}")
972
- print(f"ℹ️ Streaming: {path}"); return Response(generate(), mimetype="video/mp4")
973
-
974
- # --- (SocketIO handlers: start_processing, reset_system remain the same) ---
975
- @socketio.on('start_processing')
976
- def handle_start_processing(data):
977
- global thread
978
- with thread_lock:
979
- if thread is None:
980
- stop_event.clear(); source, name = data.get('source'), data.get('filename')
981
- path, safe_name = None, secure_filename(name) if name else None
982
- if not safe_name: return socketio.emit('processing_error', {'error': 'Invalid filename.'})
983
- if source == 'demo': path = VIDEO_PATHS.get(name)
984
- elif source == 'upload': path = os.path.join(app.config['UPLOAD_FOLDER'], safe_name)
985
- if path and os.path.exists(path):
986
- print(f"🚀 Starting processing: '{safe_name}' ({source})")
987
- thread = socketio.start_background_task(video_processing_task, path)
988
- else: socketio.emit('processing_error', {'error': 'Video file not found.'})
989
- else: socketio.emit('update_status', {'status': 'Processing already running.'})
990
-
991
- @socketio.on('reset_system')
992
- def handle_reset():
993
- global thread
994
- with thread_lock:
995
- if thread: print("🔄 Reset requested. Stopping..."); stop_event.set()
996
- else: print("ℹ️ Reset requested, none running.")
997
- socketio.emit('system_reset_confirm'); print("✅ Reset confirmed.")
998
-
999
- if __name__ == '__main__':
1000
- print("🐍 Starting Flask server...")
1001
  socketio.run(app, debug=True)
 
1
+ import os
2
+ import cv2
3
+ import torch
4
+ import numpy as np
5
+ import time
6
+ from datetime import datetime
7
+ import threading
8
+ import base64
9
+ from werkzeug.utils import secure_filename
10
+
11
+ # --- Load .env file ---
12
+ from dotenv import load_dotenv
13
+ load_dotenv()
14
+ # --- END ---
15
+
16
+ from flask import Flask, render_template, Response, request, jsonify
17
+ from flask_socketio import SocketIO
18
+
19
+ # Important: Adjust imports if your structure changed
20
+ from utils.load_model import load_models
21
+ from utils.utils import build_transforms
22
+ from network.TorchUtils import get_torch_device
23
+ from yolo_detection import analyze_video_with_yolo
24
+ from video_sumrrizer import summarize_video # Your summarizer
25
+
26
+ # ---- App Setup ----
27
+ app = Flask(__name__)
28
+ app.config['SECRET_KEY'] = 'your_secret_key!'
29
+ UPLOAD_FOLDER = 'uploads'
30
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
31
+ app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
32
+ socketio = SocketIO(app, async_mode='eventlet')
33
+
34
+ # ---- Global Config & Model Loading ----
35
+ print("[INFO] Loading models...")
36
+ DEVICE = get_torch_device()
37
+ FEATURE_EXTRACTOR_PATH = "pretrained/c3d.pickle"
38
+ AD_MODEL_PATH = "exps/c3d/models/epoch_80000.pt"
39
+ YOLO_MODEL_PATH = "yolo_my_model.pt"
40
+ SAVE_DIR = "outputs/anomaly_frames"
41
+ ANOMALY_THRESHOLD = 0.4
42
+ COOLDOWN_SECS = 60.0 # <--- This enforces the 60 second anomaly cooldown
43
+ os.makedirs(SAVE_DIR, exist_ok=True)
44
+
45
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
46
+ if not OPENAI_API_KEY: print("⚠️ WARNING: OPENAI_API_KEY not set!")
47
+
48
+ anomaly_detector, feature_extractor = load_models( FEATURE_EXTRACTOR_PATH, AD_MODEL_PATH, features_method="c3d", device=DEVICE)
49
+ feature_extractor.eval(); anomaly_detector.eval()
50
+ TRANSFORMS = build_transforms(mode="c3d")
51
+ print("[INFO] Models loaded successfully.")
52
+
53
+ VIDEO_PATHS = { # Ensure these paths are correct relative to app.py
54
+ "Abuse": "static/videos/Abuse.mp4", "Arrest": "static/videos/Arrest.mp4",
55
+ "Arson": "static/videos/Arson.mp4", "Assault": "static/videos/Assault.mp4",
56
+ "Burglary": "static/videos/Burglary.mp4", "Explosion": "static/videos/Explosion.mp4",
57
+ "Fighting": "static/videos/Fighting.mp4", "RoadAccidents": "static/videos/RoadAccidents.mp4",
58
+ "Robbery": "static/videos/Robbery.mp4", "Shooting": "static/videos/Shooting.mp4",
59
+ "Shoplifting": "static/videos/Shoplifting.mp4", "Stealing": "static/videos/Stealing.mp4",
60
+ "Vandalism": "static/videos/Vandalism.mp4", "Normal": "static/videos/Normal.mp4"
61
+ }
62
+
63
+ # --- Thread control ---
64
+ thread = None; thread_lock = threading.Lock(); stop_event = threading.Event()
65
+
66
+ def smooth_score(scores, new_score, window=5):
67
+ scores.append(new_score); scores = scores[-window:]; return float(np.mean(scores))
68
+
69
+ # --- MODIFIED: Renamed and removed internal wait ---
70
+ def _save_clip_and_start_summarizer(video_path, clip_dir, initial_frames, fps, width, height):
71
+ """
72
+ Saves a 30s clip, then immediately starts the summarizer task.
73
+ """
74
+ out_path = os.path.join(clip_dir, "anomaly_clip.mp4")
75
+ save_success = False
76
+ try:
77
+ socketio.emit('update_status', {'status': 'Saving 30s clip...'})
78
+ cap2 = cv2.VideoCapture(video_path)
79
+ if not cap2.isOpened(): raise Exception(f"Cannot open video {video_path}")
80
+ fourcc = cv2.VideoWriter_fourcc(*"mp4v")
81
+ out = cv2.VideoWriter(out_path, fourcc, fps, (width, height))
82
+
83
+ for f_idx, f in enumerate(initial_frames):
84
+ out.write(f)
85
+ if f_idx % 5 == 0: socketio.sleep(0.001)
86
+
87
+ frames_to_save = int(fps * 30)
88
+ remaining = frames_to_save - len(initial_frames)
89
+
90
+ for i in range(remaining):
91
+ ret, frame = cap2.read()
92
+ if not ret: break
93
+ out.write(frame)
94
+ if i % 5 == 0: socketio.sleep(0.001)
95
+
96
+ out.release(); cap2.release()
97
+ print(f"✅ [INFO] Saved 30-sec clip -> {out_path}")
98
+ save_success = True
99
+
100
+ except Exception as e:
101
+ print(f" [ERROR] _save_clip failed: {e}")
102
+ socketio.emit('update_summary', {'summary': f"Error saving clip: {e}"})
103
+ socketio.emit('recording_signal', {'recording': False})
104
+
105
+ finally:
106
+ # --- If saving succeeded, start summarizer immediately ---
107
+ if save_success:
108
+ try:
109
+ print("🚀 Starting background task for summarization...")
110
+ socketio.emit('update_status', {'status': 'Clip saved. Starting summarizer...'})
111
+ # Start summarizer in a *new* background task
112
+ socketio.start_background_task(_run_summarizer, out_path)
113
+ except Exception as e:
114
+ print(f"❌ [ERROR] Failed to start summarizer task: {e}")
115
+ socketio.emit('update_summary', {'summary': f"Error starting summarizer: {e}"})
116
+ socketio.emit('recording_signal', {'recording': False})
117
+ # --- End ---
118
+
119
+ # --- Background Task 2 - Just Run Summarizer (Unchanged) ---
120
+ def _run_summarizer(saved_clip_path):
121
+ """
122
+ Runs the summarizer on the already saved clip. Emits results and recording=False.
123
+ """
124
+ summary_text = None
125
+ try:
126
+ print(f" [INFO] Summarizing clip: {saved_clip_path}")
127
+ socketio.emit('update_status', {'status': 'Summarizing clip...'})
128
+ socketio.sleep(0.01) # Yield before blocking call
129
+
130
+ api_key_from_env = os.getenv("OPENAI_API_KEY")
131
+ if not api_key_from_env: raise ValueError("OPENAI_API_KEY missing.")
132
+
133
+ summary_text = summarize_video(video_path=saved_clip_path, api=api_key_from_env)
134
+ print("\n✅ VIDEO SUMMARY (snippet):\n", summary_text[:100] + "...", "\n")
135
+
136
+ except Exception as e:
137
+ summary_text = f"Summarizer Error: {e}"
138
+ print(f"❌ [ERROR] _run_summarizer failed: {e}")
139
+ finally:
140
+ if summary_text is None: summary_text = "Summarizer Error: Unknown failure."
141
+ socketio.emit('update_summary', {'summary': summary_text})
142
+ socketio.emit('recording_signal', {'recording': False}) # Signal recording finished
143
+ print("ℹ️ Summarization processing finished.")
144
+
145
+
146
+ # --- Main video processing loop (Unchanged from previous fix) ---
147
+ def video_processing_task(video_path):
148
+ global thread
149
+ try:
150
+ cap = cv2.VideoCapture(video_path)
151
+ if not cap.isOpened(): raise Exception("Cannot open video file")
152
+
153
+ frame_buffer, last_save_time, recent_scores = [], 0.0, []
154
+ FRAME_SKIP = 6 # Keep increased frame skipping
155
+ frame_count = 0
156
+ fps, width, height = (cap.get(cv2.CAP_PROP_FPS) or 25.0,
157
+ int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),
158
+ int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
159
+
160
+ while cap.isOpened() and not stop_event.is_set():
161
+ socketio.sleep(0.002) # Keep yielding
162
+
163
+ ret, frame = cap.read()
164
+ if not ret: break
165
+
166
+ frame_count += 1
167
+ if frame_count % (FRAME_SKIP + 1) != 0: continue # Apply skipping
168
+
169
+ frame_buffer.append(frame.copy())
170
+
171
+ if len(frame_buffer) == 16:
172
+ socketio.sleep(0.001) # Yield before inference
173
+
174
+ # --- Anomaly Detection ---
175
+ frames_resized = [cv2.resize(f, (112, 112)) for f in frame_buffer]
176
+ clip_np = np.array(frames_resized, dtype=np.uint8)
177
+ clip_torch = torch.from_numpy(clip_np)
178
+ clip_torch = TRANSFORMS(clip_torch).unsqueeze(0).to(DEVICE)
179
+ with torch.no_grad():
180
+ features = feature_extractor(clip_torch).detach()
181
+ score = float(anomaly_detector(features).detach().item())
182
+ score = smooth_score(recent_scores, score)
183
+ score = float(np.clip(score, 0, 1))
184
+ socketio.emit('update_graph', {'score': score})
185
+ # --- End Anomaly Detection ---
186
+
187
+ # --- Anomaly Actions ---
188
+ now = time.time()
189
+ # --- This check enforces the 60 second cooldown ---
190
+ if score > ANOMALY_THRESHOLD and (now - last_save_time) >= COOLDOWN_SECS:
191
+ print(f"🔥 Anomaly! Score: {score:.2f}")
192
+ last_save_time = now # Reset cooldown timer
193
+
194
+ socketio.emit('recording_signal', {'recording': True})
195
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
196
+ clip_dir = os.path.join(SAVE_DIR, f"anomaly_{timestamp}")
197
+ os.makedirs(clip_dir, exist_ok=True)
198
+
199
+ # Quick YOLO
200
+ yolo_frame_path = os.path.join(clip_dir, "yolo_frame.jpg")
201
+ preview_frame_img = frame_buffer[-1].copy()
202
+ cv2.imwrite(yolo_frame_path, preview_frame_img)
203
+ try:
204
+ yolo_result = analyze_video_with_yolo(yolo_frame_path, model_path=YOLO_MODEL_PATH, return_class=True)
205
+ yolo_text = f"🚨 Anomaly → YOLO: {yolo_result}"
206
+ except Exception as e: yolo_text = f"YOLO Error: {e}"
207
+ socketio.emit('update_yolo_text', {'text': yolo_text})
208
+ _, buffer = cv2.imencode('.jpg', preview_frame_img)
209
+ b64_str = base64.b64encode(buffer).decode('utf-8')
210
+ socketio.emit('update_yolo_image', {'image_data': b64_str})
211
+
212
+ socketio.emit('update_summary', {'summary': 'loading'})
213
+ socketio.sleep(0.005) # Yield before starting save task
214
+
215
+ # Start the saving task (which will later schedule summarizer)
216
+ print("🚀 Starting background task for clip saving...")
217
+ socketio.start_background_task(
218
+ _save_clip_and_start_summarizer, # Use the modified function
219
+ video_path, clip_dir, frame_buffer.copy(),
220
+ fps, width, height
221
+ )
222
+ # --- End anomaly actions ---
223
+ frame_buffer.clear()
224
+
225
+ cap.release()
226
+ if not stop_event.is_set(): socketio.emit('processing_finished', {'message': 'Video finished.'})
227
+ print("🏁 Video processing task ended.")
228
+
229
+ except Exception as e:
230
+ print(f"❌ [ERROR] Unhandled exception in video_processing_task: {e}")
231
+ socketio.emit('processing_error', {'error': f'Runtime error: {e}'})
232
+ finally:
233
+ with thread_lock:
234
+ if thread is not None:
235
+ print("🧹 Cleaning up video processing task.")
236
+ thread = None; stop_event.clear()
237
+
238
+ # --- (Routes: /, /upload, /video_stream remain the same) ---
239
+ @app.route('/')
240
+ def index(): return render_template('index.html', anomaly_names=VIDEO_PATHS.keys())
241
+
242
+ @app.route('/upload', methods=['POST'])
243
+ def upload_file():
244
+ if 'video' not in request.files: return jsonify({'error': 'No video file'}), 400
245
+ file = request.files['video']; name = file.filename
246
+ if name == '': return jsonify({'error': 'No video selected'}), 400
247
+ filename = secure_filename(name); ts = datetime.now().strftime('%Y%m%d%H%M%S')
248
+ unique_name = f"{ts}_{filename}"; path = os.path.join(app.config['UPLOAD_FOLDER'], unique_name)
249
+ try: file.save(path); print(f"✅ Uploaded: {path}"); return jsonify({'success': True, 'filename': unique_name})
250
+ except Exception as e: print(f"❌ Upload failed: {e}"); return jsonify({'error': f'{e}'}), 500
251
+
252
+ @app.route('/video_stream/<source>/<path:filename>')
253
+ def video_stream(source, filename):
254
+ path, safe_name = None, secure_filename(filename)
255
+ if source == 'demo': path = VIDEO_PATHS.get(filename)
256
+ elif source == 'upload': path = os.path.join(app.config['UPLOAD_FOLDER'], safe_name)
257
+ else: return "Invalid source", 404
258
+ if not path or not os.path.exists(path): return "Video not found", 404
259
+ def generate():
260
+ try:
261
+ with open(path, "rb") as f:
262
+ while chunk := f.read(1024*1024): yield chunk; socketio.sleep(0.0001)
263
+ except Exception as e: print(f"❌ Streaming error: {e}")
264
+ print(f"ℹ️ Streaming: {path}"); return Response(generate(), mimetype="video/mp4")
265
+
266
+ # --- (SocketIO handlers: start_processing, reset_system remain the same) ---
267
+ @socketio.on('start_processing')
268
+ def handle_start_processing(data):
269
+ global thread
270
+ with thread_lock:
271
+ if thread is None:
272
+ stop_event.clear(); source, name = data.get('source'), data.get('filename')
273
+ path, safe_name = None, secure_filename(name) if name else None
274
+ if not safe_name: return socketio.emit('processing_error', {'error': 'Invalid filename.'})
275
+ if source == 'demo': path = VIDEO_PATHS.get(name)
276
+ elif source == 'upload': path = os.path.join(app.config['UPLOAD_FOLDER'], safe_name)
277
+ if path and os.path.exists(path):
278
+ print(f"🚀 Starting processing: '{safe_name}' ({source})")
279
+ thread = socketio.start_background_task(video_processing_task, path)
280
+ else: socketio.emit('processing_error', {'error': 'Video file not found.'})
281
+ else: socketio.emit('update_status', {'status': 'Processing already running.'})
282
+
283
+ @socketio.on('reset_system')
284
+ def handle_reset():
285
+ global thread
286
+ with thread_lock:
287
+ if thread: print("🔄 Reset requested. Stopping..."); stop_event.set()
288
+ else: print("ℹ️ Reset requested, none running.")
289
+ socketio.emit('system_reset_confirm'); print("✅ Reset confirmed.")
290
+
291
+ if __name__ == '__main__':
292
+ print("🐍 Starting Flask server...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
  socketio.run(app, debug=True)