A comprehensive, production-ready RTSP video extraction system built with Python and FFmpeg. Features continuous video buffering, incident-based clip extraction, automatic upload with offline retry, and a RESTful API for management.
- Continuous Video Buffering: Maintains a rolling 2-hour buffer from RTSP cameras
- Automatic Reconnection: Handles camera disconnections with automatic retry
- Incident-Based Extraction: Extracts video clips (pre/post incident) from buffer
- Upload Management: Automatic upload to server with offline retry queue
- Web-Compatible Output: MP4 format optimized for web playback
- RESTful API: Complete API for camera and incident management
- SQLite Database: Persistent storage for cameras, incidents, and audit logs
- Thread Management: Independent threads for each component with health monitoring
- Audit Logging: Comprehensive logging to both files and database
The system follows OOP principles with clear separation of concerns:
rtsp_extractor/
├── api/ # REST API
│ └── flask_api.py
├── config/ # Configuration
│ └── settings.py
├── database/ # Database layer
│ ├── interface.py # Abstract interface
│ └── sqlite_repository.py # SQLite implementation
├── models/ # Data models
│ └── data_models.py
├── services/ # Business logic services
│ ├── video_service_interface.py
│ └── ffmpeg_video_service.py
├── threads/ # Worker threads
│ ├── base_thread.py
│ ├── buffer_thread.py
│ ├── incident_processor_thread.py
│ ├── offline_uploader_thread.py
│ ├── cleanup_thread.py
│ └── thread_manager.py
├── utils/ # Utilities
│ └── logger.py
└── main.py # Application entry point
- Open/Closed Principle: Easy to extend without modifying existing code
- Interface Segregation: Clear interfaces for database and video services
- Single Responsibility: Each class has one clear purpose
- Dependency Injection: Components receive dependencies rather than creating them
- Separation of Concerns: Clear boundaries between API, business logic, and data
The system uses multiple specialized threads:
- Buffer Thread (per camera): Continuously records video to buffer
- Incident Processor Thread (per camera): Processes incidents and extracts clips
- Offline Uploader Thread: Retries failed uploads
- Cleanup Thread: Removes old segments and logs
- Install system dependencies:
sudo apt-get update
sudo apt-get install -y ffmpeg- Install Python dependencies:
pip install -r requirements.txt- Run the application:
python main.pyEdit config/settings.py to customize:
- Buffer duration (default: 2 hours)
- Pre/post incident seconds (default: 5 seconds each)
- API host and port
- Video codec and format
- Cleanup intervals
- Reconnection settings
Add Camera
POST /api/cameras
{
"name": "Front Door",
"rtsp_url": "rtsp://192.168.1.100:554/stream",
"post_url": "https://server.com/upload",
"enabled": true
}Get All Cameras
GET /api/camerasGet Camera
GET /api/cameras/{camera_id}Delete Camera
DELETE /api/cameras/{camera_id}Restart Camera
POST /api/cameras/{camera_id}/restartCreate Incident
POST /api/incidents
{
"camera_id": 1,
"incident_time": "2025-01-27T14:30:00"
}Get Incidents
GET /api/incidents?camera_id=1&status=pendingGet Thread Status
GET /api/threadsGet Audit Logs
GET /api/logs?limit=100&source=Buffer-Camera-1Start System
POST /api/system/startStop System
POST /api/system/stopHealth Check
GET /healthimport requests
response = requests.post('http://localhost:5000/api/cameras', json={
'name': 'Parking Lot',
'rtsp_url': 'rtsp://admin:password@192.168.1.100:554/stream1',
'post_url': 'https://myserver.com/api/videos',
'enabled': True
})
camera_id = response.json()['id']
print(f"Camera added with ID: {camera_id}")from datetime import datetime
import requests
response = requests.post('http://localhost:5000/api/incidents', json={
'camera_id': 1,
'incident_time': datetime.now().isoformat()
})
print(f"Incident created: {response.json()}")import requests
# Check thread status
threads = requests.get('http://localhost:5000/api/threads').json()
for thread in threads:
print(f"{thread['name']}: {thread['status']}")
# Check logs
logs = requests.get('http://localhost:5000/api/logs?limit=10').json()
for log in logs:
print(f"[{log['level']}] {log['source']}: {log['message']}")- Each camera has a dedicated buffer thread
- FFmpeg continuously records to timestamped TS segments
- Segments are automatically cleaned up after buffer duration
- Buffer remains in memory-mapped filesystem for fast access
- User creates incident via API with timestamp
- Incident processor extracts relevant segments from buffer
- Segments are concatenated and re-encoded to MP4
- Video is uploaded to configured post URL
- If upload fails, incident moves to offline queue
- Offline uploader periodically checks for failed uploads
- Retries uploads in order (oldest first)
- Successfully uploaded files are deleted locally
- Failed uploads remain in queue for next retry
- Cleanup thread runs periodically
- Removes buffer segments older than configured duration
- Removes audit logs older than 30 days
- Removes successfully uploaded videos
Output videos are in MP4 format with:
- Video codec: H.264 (libx264)
- Audio codec: AAC
- Container: MP4 with faststart flag
- Optimized for web playback
cameras
- id, name, rtsp_url, status, post_url, enabled, created_at, updated_at
incidents
- id, camera_id, incident_time, video_path, upload_status, upload_attempts, error_message, created_at, uploaded_at
audit_logs
- id, level, source, message, details, created_at
- Automatic camera reconnection on failure
- Graceful handling of network issues
- Comprehensive error logging
- Upload retry mechanism
- Thread health monitoring
- Use systemd service for automatic startup
- Configure firewall for API port
- Set up HTTPS reverse proxy (nginx/Apache)
- Monitor disk space for buffer storage
- Regular database backups
- Log rotation for application logs
Camera won't connect:
- Check RTSP URL and credentials
- Verify network connectivity
- Check FFmpeg logs in
logs/Buffer-Camera-X.log
Videos not extracting:
- Ensure buffer has sufficient data
- Check incident timestamp is within buffer range
- Verify disk space availability
Upload failures:
- Check post_url is accessible
- Verify network connectivity
- Check offline queue in database
MIT License - Feel free to use and modify for your needs.
For issues and questions, check the audit logs:
GET /api/logsOr check the thread status:
GET /api/threads