deepmindfx/surveillance.chat
0
1from flask import Flask
2from flask_sqlalchemy import SQLAlchemy
3from flask_cors import CORS
4import os
5from flask import request, jsonify
6from flask_bcrypt import Bcrypt
7from flask_login import LoginManager, login_user, logout_user, login_required, current_user, UserMixin
8from werkzeug.utils import secure_filename
9from flask import send_from_directory
10from datetime import timedelta
11
12app = Flask(__name__)
13CORS(app)
14
15# Configurations
16app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///cctv_chat.db'
17app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
18app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev_secret_key')
19app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=24) # Session timeout
20
21db = SQLAlchemy(app)
22
23bcrypt = Bcrypt(app)
24login_manager = LoginManager(app)
25
26# Update User model to inherit from UserMixin
27class User(UserMixin, db.Model):
28 id = db.Column(db.Integer, primary_key=True)
29 email = db.Column(db.String(120), unique=True, nullable=False)
30 password_hash = db.Column(db.String(128), nullable=False)
31 username = db.Column(db.String(64), unique=True, nullable=False)
32 created_at = db.Column(db.DateTime, server_default=db.func.now())
33 last_login = db.Column(db.DateTime)
34 is_active = db.Column(db.Boolean, default=True)
35 videos = db.relationship('Video', backref='user', lazy=True)
36 chats = db.relationship('ChatHistory', backref='user', lazy=True)
37
38 def get_id(self):
39 return str(self.id)
40
41@login_manager.user_loader
42def load_user(user_id):
43 return User.query.get(int(user_id))
44
45# User registration endpoint
46@app.route('/api/register', methods=['POST'])
47def register():
48 data = request.json
49 email = data.get('email')
50 username = data.get('username')
51 password = data.get('password')
52 if not email or not username or not password:
53 return jsonify({'error': 'Missing required fields'}), 400
54 if User.query.filter((User.email == email) | (User.username == username)).first():
55 return jsonify({'error': 'User already exists'}), 409
56 password_hash = bcrypt.generate_password_hash(password).decode('utf-8')
57 user = User(email=email, username=username, password_hash=password_hash)
58 db.session.add(user)
59 db.session.commit()
60 return jsonify({'message': 'User registered successfully'}), 201
61
62# User login endpoint
63@app.route('/api/login', methods=['POST'])
64def login():
65 data = request.json
66 email = data.get('email')
67 password = data.get('password')
68 user = User.query.filter_by(email=email).first()
69 if user and bcrypt.check_password_hash(user.password_hash, password):
70 login_user(user)
71 response = jsonify({'message': 'Login successful', 'user_id': user.id, 'username': user.username})
72 return response, 200
73 return jsonify({'error': 'Invalid credentials'}), 401
74
75# Logout endpoint
76@app.route('/api/logout', methods=['POST'])
77@login_required
78def logout():
79 logout_user()
80 return jsonify({'message': 'Logged out successfully'}), 200
81
82# Video model
83class Video(db.Model):
84 id = db.Column(db.Integer, primary_key=True)
85 user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
86 video_name = db.Column(db.String(256))
87 video_type = db.Column(db.String(32)) # upload/camera/url
88 file_path_or_url = db.Column(db.String(512))
89 upload_date = db.Column(db.DateTime, server_default=db.func.now())
90 file_size = db.Column(db.Integer)
91 duration = db.Column(db.Integer)
92 thumbnail_path = db.Column(db.String(512))
93 is_processed = db.Column(db.Boolean, default=False)
94 is_favorite = db.Column(db.Boolean, default=False)
95 chats = db.relationship('ChatHistory', backref='video', lazy=True)
96
97# Chat history model
98class ChatHistory(db.Model):
99 id = db.Column(db.Integer, primary_key=True)
100 video_id = db.Column(db.Integer, db.ForeignKey('video.id'), nullable=False)
101 user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
102 question = db.Column(db.Text, nullable=False)
103 answer = db.Column(db.Text)
104 timestamp = db.Column(db.DateTime, server_default=db.func.now())
105 model_used = db.Column(db.String(64))
106
107UPLOAD_FOLDER = os.path.join(os.getcwd(), 'upload')
108ALLOWED_EXTENSIONS = {'mp4', 'avi', 'mov', 'mkv', 'flv', 'wmv'}
109app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
110os.makedirs(UPLOAD_FOLDER, exist_ok=True)
111
112def allowed_file(filename):
113 return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
114
115# Video upload endpoint (file)
116@app.route('/api/upload_video', methods=['POST'])
117@login_required
118def upload_video():
119 if 'file' not in request.files:
120 return jsonify({'error': 'No file part'}), 400
121 file = request.files['file']
122 if file.filename == '':
123 return jsonify({'error': 'No selected file'}), 400
124 if file and allowed_file(file.filename):
125 filename = secure_filename(file.filename)
126 filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
127 file.save(filepath)
128 video = Video(
129 user_id=current_user.id,
130 video_name=filename,
131 video_type='upload',
132 file_path_or_url=filepath,
133 file_size=os.path.getsize(filepath),
134 is_processed=False
135 )
136 db.session.add(video)
137 db.session.commit()
138 return jsonify({'message': 'Video uploaded successfully', 'video_id': video.id}), 201
139 return jsonify({'error': 'Invalid file type'}), 400
140
141# Video upload endpoint (URL or camera)
142@app.route('/api/add_video', methods=['POST'])
143@login_required
144def add_video():
145 data = request.json
146 video_name = data.get('video_name')
147 video_type = data.get('video_type') # 'url' or 'camera'
148 file_path_or_url = data.get('file_path_or_url')
149 if not video_name or not video_type or not file_path_or_url:
150 return jsonify({'error': 'Missing required fields'}), 400
151 video = Video(
152 user_id=current_user.id,
153 video_name=video_name,
154 video_type=video_type,
155 file_path_or_url=file_path_or_url,
156 is_processed=False
157 )
158 db.session.add(video)
159 db.session.commit()
160 return jsonify({'message': 'Video added successfully', 'video_id': video.id}), 201
161
162# Get all videos for current user with search and filter
163@app.route('/api/videos', methods=['GET'])
164@login_required
165def get_videos():
166 videos = Video.query.filter_by(user_id=current_user.id)
167
168 # Search by video name
169 search = request.args.get('search', '').strip()
170 if search:
171 videos = videos.filter(Video.video_name.ilike(f'%{search}%'))
172
173 # Filter by video type
174 video_type = request.args.get('type', '').strip()
175 if video_type:
176 videos = videos.filter_by(video_type=video_type)
177
178 # Filter by favorite status
179 favorite = request.args.get('favorite', '').strip()
180 if favorite.lower() == 'true':
181 videos = videos.filter_by(is_favorite=True)
182 elif favorite.lower() == 'false':
183 videos = videos.filter_by(is_favorite=False)
184
185 # Sort by favorite status and upload date
186 videos = videos.order_by(Video.is_favorite.desc(), Video.upload_date.desc())
187
188 # Pagination
189 page = request.args.get('page', 1, type=int)
190 per_page = request.args.get('per_page', 10, type=int)
191 per_page = min(per_page, 50) # Limit max items per page
192
193 pagination = videos.paginate(page=page, per_page=per_page, error_out=False)
194
195 video_list = []
196 for video in pagination.items:
197 video_data = {
198 'id': video.id,
199 'video_name': video.video_name,
200 'video_type': video.video_type,
201 'file_path_or_url': video.file_path_or_url,
202 'upload_date': video.upload_date.strftime('%Y-%m-%d %H:%M:%S'),
203 'file_size': video.file_size,
204 'duration': video.duration,
205 'thumbnail_path': video.thumbnail_path,
206 'is_processed': video.is_processed,
207 'is_favorite': video.is_favorite
208 }
209 video_list.append(video_data)
210
211 return jsonify({
212 'videos': video_list,
213 'pagination': {
214 'page': page,
215 'per_page': per_page,
216 'total': pagination.total,
217 'pages': pagination.pages,
218 'has_next': pagination.has_next,
219 'has_prev': pagination.has_prev
220 }
221 })
222
223# Serve uploaded video files
224@app.route('/api/video_file/<filename>')
225def get_video_file(filename):
226 return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
227
228# Save a chat/question for a video
229@app.route('/api/chat', methods=['POST'])
230@login_required
231def save_chat():
232 data = request.json
233 video_id = data.get('video_id')
234 question = data.get('question')
235 answer = data.get('answer')
236 model_used = data.get('model_used')
237 if not video_id or not question or not answer:
238 return jsonify({'error': 'Missing required fields'}), 400
239 video = Video.query.filter_by(id=video_id, user_id=current_user.id).first()
240 if not video:
241 return jsonify({'error': 'Video not found or not owned by user'}), 404
242 chat = ChatHistory(
243 video_id=video_id,
244 user_id=current_user.id,
245 question=question,
246 answer=answer,
247 model_used=model_used
248 )
249 db.session.add(chat)
250 db.session.commit()
251 return jsonify({'message': 'Chat saved successfully', 'chat_id': chat.id}), 201
252
253# Get chat history for a video
254@app.route('/api/chat_history/<int:video_id>', methods=['GET'])
255@login_required
256def get_chat_history(video_id):
257 video = Video.query.filter_by(id=video_id, user_id=current_user.id).first()
258 if not video:
259 return jsonify({'error': 'Video not found or not owned by user'}), 404
260 chats = ChatHistory.query.filter_by(video_id=video_id, user_id=current_user.id).order_by(ChatHistory.timestamp.asc()).all()
261 chat_list = [
262 {
263 'id': c.id,
264 'question': c.question,
265 'answer': c.answer,
266 'timestamp': c.timestamp,
267 'model_used': c.model_used
268 } for c in chats
269 ]
270 return jsonify({'chats': chat_list}), 200
271
272# Delete a specific chat Q&A pair
273@app.route('/api/chat/<int:chat_id>', methods=['DELETE'])
274@login_required
275def delete_chat(chat_id):
276 chat = ChatHistory.query.filter_by(id=chat_id, user_id=current_user.id).first()
277 if not chat:
278 return jsonify({'error': 'Chat not found or not owned by user'}), 404
279 db.session.delete(chat)
280 db.session.commit()
281 return jsonify({'message': 'Chat deleted successfully'}), 200
282
283# Delete a video
284@app.route('/api/video/<int:video_id>', methods=['DELETE'])
285@login_required
286def delete_video(video_id):
287 video = Video.query.filter_by(id=video_id, user_id=current_user.id).first()
288 if not video:
289 return jsonify({'error': 'Video not found or not owned by user'}), 404
290 db.session.delete(video)
291 db.session.commit()
292 return jsonify({'message': 'Video deleted successfully'}), 200
293
294# Rename a video
295@app.route('/api/video/<int:video_id>/rename', methods=['POST'])
296@login_required
297def rename_video(video_id):
298 data = request.json
299 new_name = data.get('new_name') # Changed from 'video_name' to 'new_name'
300 if not new_name:
301 return jsonify({'error': 'New video name is required'}), 400
302 video = Video.query.filter_by(id=video_id, user_id=current_user.id).first()
303 if not video:
304 return jsonify({'error': 'Video not found or not owned by user'}), 404
305 video.video_name = new_name
306 db.session.commit()
307 return jsonify({'message': 'Video name updated successfully'}), 200
308
309# Toggle favorite status for a video
310@app.route('/api/video/<int:video_id>/favorite', methods=['POST'])
311@login_required
312def toggle_favorite(video_id):
313 video = Video.query.filter_by(id=video_id, user_id=current_user.id).first()
314 if not video:
315 return jsonify({'error': 'Video not found or not owned by user'}), 404
316 video.is_favorite = not video.is_favorite
317 db.session.commit()
318 return jsonify({'message': 'Favorite status updated', 'is_favorite': video.is_favorite}), 200
319
320# Get current user profile
321@app.route('/api/profile', methods=['GET'])
322@login_required
323def get_profile():
324 user = current_user
325 return jsonify({
326 'id': user.id,
327 'email': user.email,
328 'username': user.username,
329 'created_at': user.created_at,
330 'last_login': user.last_login,
331 'is_active': user.is_active
332 }), 200
333
334# Update current user profile (username only for now)
335@app.route('/api/profile', methods=['POST'])
336@login_required
337def update_profile():
338 data = request.get_json()
339 username = data.get('username')
340
341 if not username:
342 return jsonify({'error': 'Username is required'}), 400
343
344 # Check if username is already taken by another user
345 existing_user = User.query.filter_by(username=username).first()
346 if existing_user and existing_user.id != current_user.id:
347 return jsonify({'error': 'Username already taken'}), 400
348
349 current_user.username = username
350 db.session.commit()
351 return jsonify({'message': 'Profile updated successfully'}), 200
352
353# Change password endpoint
354@app.route('/api/profile/password', methods=['POST'])
355@login_required
356def change_password():
357 data = request.get_json()
358 current_password = data.get('current_password')
359 new_password = data.get('new_password')
360
361 if not current_password or not new_password:
362 return jsonify({'error': 'Current and new password are required'}), 400
363
364 # Verify current password
365 if not bcrypt.check_password_hash(current_user.password_hash, current_password):
366 return jsonify({'error': 'Current password is incorrect'}), 400
367
368 # Hash new password
369 new_password_hash = bcrypt.generate_password_hash(new_password).decode('utf-8')
370 current_user.password_hash = new_password_hash
371 db.session.commit()
372
373 return jsonify({'message': 'Password changed successfully'}), 200
374
375# Analyze video endpoint
376@app.route('/api/analyze_video', methods=['POST'])
377@login_required
378def analyze_video():
379 data = request.json
380 video_id = data.get('video_id')
381 question = data.get('question')
382
383 if not video_id or not question:
384 return jsonify({'error': 'Video ID and question are required'}), 400
385
386 video = Video.query.filter_by(id=video_id, user_id=current_user.id).first()
387 if not video:
388 return jsonify({'error': 'Video not found or not owned by user'}), 404
389
390 # For now, return a placeholder response
391 # In a real implementation, this would call the AI model
392 answer = f"Analysis of video {video.video_name}: {question} - This is a placeholder response. The actual AI analysis would be implemented here."
393
394 return jsonify({'answer': answer}), 200
395
396# Add chat endpoint
397@app.route('/api/add_chat', methods=['POST'])
398@login_required
399def add_chat():
400 data = request.json
401 video_id = data.get('video_id')
402 question = data.get('question')
403 answer = data.get('answer')
404
405 if not video_id or not question or not answer:
406 return jsonify({'error': 'Video ID, question, and answer are required'}), 400
407
408 video = Video.query.filter_by(id=video_id, user_id=current_user.id).first()
409 if not video:
410 return jsonify({'error': 'Video not found or not owned by user'}), 404
411
412 chat = ChatHistory(
413 video_id=video_id,
414 user_id=current_user.id,
415 question=question,
416 answer=answer
417 )
418 db.session.add(chat)
419 db.session.commit()
420
421 return jsonify({'message': 'Chat saved successfully', 'chat_id': chat.id}), 200
422
423if __name__ == '__main__':
424 with app.app_context():
425 db.create_all()
426 app.run(debug=True) 