fire and smoke alerts
Browse files- .gitignore +3 -0
- app.py +110 -67
- mcp_client.py +206 -0
- rtsp_server.py +42 -0
.gitignore
CHANGED
@@ -1,2 +1,5 @@
|
|
1 |
.env
|
2 |
yolov8x-world.pt.eac99ff4aff54a2a95f4462dc49b3d49.partial
|
|
|
|
|
|
|
|
1 |
.env
|
2 |
yolov8x-world.pt.eac99ff4aff54a2a95f4462dc49b3d49.partial
|
3 |
+
fire.mp4
|
4 |
+
test.mp4
|
5 |
+
yolov8s-world.pt
|
app.py
CHANGED
@@ -6,7 +6,7 @@ import time
|
|
6 |
import os
|
7 |
from datetime import datetime
|
8 |
from ultralytics import YOLO
|
9 |
-
from transformers import
|
10 |
import torch
|
11 |
import dotenv
|
12 |
dotenv.load_dotenv()
|
@@ -17,44 +17,72 @@ For more information on `huggingface_hub` Inference API support, please check th
|
|
17 |
client = InferenceClient("HuggingFaceH4/zephyr-7b-beta",token=os.getenv("HUGGINGFACE_HUB_TOKEN"))
|
18 |
|
19 |
# Load YOLO-World model
|
20 |
-
model = YOLO('
|
21 |
|
22 |
-
# Load
|
23 |
-
|
24 |
-
|
|
|
25 |
device = "cuda" if torch.cuda.is_available() else "cpu"
|
26 |
vqa_model = vqa_model.to(device)
|
27 |
|
28 |
def analyze_fire_scene(frame):
|
29 |
-
|
30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
|
32 |
-
# Initialize detection flags and details
|
33 |
fire_detected = False
|
34 |
smoke_detected = False
|
35 |
fire_details = []
|
36 |
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
58 |
|
59 |
return fire_detected, smoke_detected, fire_details
|
60 |
|
@@ -77,52 +105,67 @@ def get_fire_analysis(frame, fire_details):
|
|
77 |
# out = vqa_model.generate(**inputs)
|
78 |
# print(blip_processor.decode(out[0], skip_special_tokens=True))
|
79 |
|
80 |
-
# Generate answer
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
81 |
with torch.no_grad():
|
82 |
-
outputs = vqa_model.generate(
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
top_p=0.9,
|
88 |
-
repetition_penalty=1.5,
|
89 |
-
length_penalty=1.0,
|
90 |
-
temperature=1.0,
|
91 |
-
)
|
92 |
-
answer = blip_processor.decode(outputs[0], skip_special_tokens=True)
|
93 |
-
analysis.append(f"Q: {question}\nA: {answer}")
|
94 |
|
95 |
return analysis
|
96 |
|
97 |
-
def check_for_fire():
|
98 |
-
|
99 |
-
cap = cv2.VideoCapture(
|
100 |
if not cap.isOpened():
|
101 |
-
return "Error: Could not access
|
102 |
|
103 |
-
|
104 |
-
ret, frame = cap.read()
|
105 |
-
if not ret:
|
106 |
-
cap.release()
|
107 |
-
return "Error: Could not read from webcam"
|
108 |
|
109 |
-
|
110 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
111 |
|
112 |
-
# Release webcam
|
113 |
cap.release()
|
114 |
-
|
115 |
-
# Get location (you might want to implement a more sophisticated location detection)
|
116 |
-
location = "Webcam Location" # Replace with actual location detection
|
117 |
-
|
118 |
-
if fire_detected:
|
119 |
-
# Get detailed analysis of the fire
|
120 |
-
analysis = get_fire_analysis(frame, fire_details)
|
121 |
-
return f"Fire detected at {location}!\n\nAnalysis:\n" + "\n".join(analysis)
|
122 |
-
elif smoke_detected:
|
123 |
-
return f"Smoke detected at {location}!"
|
124 |
-
else:
|
125 |
-
return "No fire or smoke detected"
|
126 |
|
127 |
def respond(
|
128 |
message,
|
@@ -134,7 +177,7 @@ def respond(
|
|
134 |
):
|
135 |
# Check if user wants to detect fire
|
136 |
if "detect fire" in message.lower():
|
137 |
-
return check_for_fire()
|
138 |
|
139 |
messages = [{"role": "system", "content": system_message}]
|
140 |
|
|
|
6 |
import os
|
7 |
from datetime import datetime
|
8 |
from ultralytics import YOLO
|
9 |
+
from transformers import AutoProcessor, AutoModelForVision2Seq
|
10 |
import torch
|
11 |
import dotenv
|
12 |
dotenv.load_dotenv()
|
|
|
17 |
client = InferenceClient("HuggingFaceH4/zephyr-7b-beta",token=os.getenv("HUGGINGFACE_HUB_TOKEN"))
|
18 |
|
19 |
# Load YOLO-World model
|
20 |
+
model = YOLO('yolov8s-world.pt')
|
21 |
|
22 |
+
# Load SmolVLM for VQA (lighter and better)
|
23 |
+
from transformers import AutoProcessor, AutoModelForVision2Seq
|
24 |
+
vqa_processor = AutoProcessor.from_pretrained("HuggingFaceTB/SmolVLM-Instruct", token=os.getenv("HUGGINGFACE_HUB_TOKEN"))
|
25 |
+
vqa_model = AutoModelForVision2Seq.from_pretrained("HuggingFaceTB/SmolVLM-Instruct", torch_dtype=torch.float16, token=os.getenv("HUGGINGFACE_HUB_TOKEN"))
|
26 |
device = "cuda" if torch.cuda.is_available() else "cpu"
|
27 |
vqa_model = vqa_model.to(device)
|
28 |
|
29 |
def analyze_fire_scene(frame):
|
30 |
+
"""Fast fire/smoke detection with early exit"""
|
31 |
+
from PIL import Image
|
32 |
+
|
33 |
+
# Convert frame to PIL Image
|
34 |
+
image = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
|
35 |
+
|
36 |
+
# Priority questions - most likely to detect fire/smoke quickly
|
37 |
+
questions = [
|
38 |
+
"Is there fire or flames in this image?",
|
39 |
+
"Is there smoke in this image?"
|
40 |
+
]
|
41 |
|
|
|
42 |
fire_detected = False
|
43 |
smoke_detected = False
|
44 |
fire_details = []
|
45 |
|
46 |
+
for question in questions:
|
47 |
+
messages = [{
|
48 |
+
"role": "user",
|
49 |
+
"content": [
|
50 |
+
{"type": "image", "image": image},
|
51 |
+
{"type": "text", "text": question}
|
52 |
+
]
|
53 |
+
}]
|
54 |
+
|
55 |
+
prompt = vqa_processor.apply_chat_template(messages, tokenize=False)
|
56 |
+
inputs = vqa_processor(text=prompt, images=[image], return_tensors="pt")
|
57 |
+
inputs = inputs.to(device)
|
58 |
+
|
59 |
+
with torch.no_grad():
|
60 |
+
outputs = vqa_model.generate(**inputs, max_new_tokens=20, do_sample=False) # Shorter responses
|
61 |
+
|
62 |
+
answer = vqa_processor.decode(outputs[0], skip_special_tokens=True)
|
63 |
+
answer = answer.split("Assistant:")[-1].strip() if "Assistant:" in answer else answer
|
64 |
+
answer_lower = answer.lower()
|
65 |
+
|
66 |
+
# Check fire
|
67 |
+
if 'fire' in question.lower():
|
68 |
+
fire_keywords = ['fire', 'flame', 'burning', 'blaze', 'yes']
|
69 |
+
if any(word in answer_lower for word in fire_keywords):
|
70 |
+
fire_detected = True
|
71 |
+
fire_details.append({
|
72 |
+
'type': 'fire_detected_by_vision',
|
73 |
+
'confidence': 0.8,
|
74 |
+
'description': answer
|
75 |
+
})
|
76 |
+
# Early exit if fire detected
|
77 |
+
return True, smoke_detected, fire_details
|
78 |
+
|
79 |
+
# Check smoke
|
80 |
+
if 'smoke' in question.lower():
|
81 |
+
smoke_keywords = ['smoke', 'smoky', 'yes']
|
82 |
+
if any(word in answer_lower for word in smoke_keywords):
|
83 |
+
smoke_detected = True
|
84 |
+
# Early exit if smoke detected
|
85 |
+
return fire_detected, True, fire_details
|
86 |
|
87 |
return fire_detected, smoke_detected, fire_details
|
88 |
|
|
|
105 |
# out = vqa_model.generate(**inputs)
|
106 |
# print(blip_processor.decode(out[0], skip_special_tokens=True))
|
107 |
|
108 |
+
# Generate answer using SmolVLM
|
109 |
+
messages = [
|
110 |
+
{
|
111 |
+
"role": "user",
|
112 |
+
"content": [
|
113 |
+
{"type": "image", "image": frame},
|
114 |
+
{"type": "text", "text": question}
|
115 |
+
]
|
116 |
+
}
|
117 |
+
]
|
118 |
+
|
119 |
+
prompt = vqa_processor.apply_chat_template(messages, tokenize=False)
|
120 |
+
inputs = vqa_processor(text=prompt, images=[frame], return_tensors="pt")
|
121 |
+
inputs = inputs.to(device)
|
122 |
+
|
123 |
with torch.no_grad():
|
124 |
+
outputs = vqa_model.generate(**inputs, max_new_tokens=50, do_sample=False)
|
125 |
+
|
126 |
+
answer = vqa_processor.decode(outputs[0], skip_special_tokens=True)
|
127 |
+
answer = answer.split("Assistant:")[-1].strip() if "Assistant:" in answer else answer
|
128 |
+
analysis.append(f"Q: {question}\nA: {answer}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
129 |
|
130 |
return analysis
|
131 |
|
132 |
+
def check_for_fire(video_source=0):
|
133 |
+
"""Real-time fire detection processing every 100th frame"""
|
134 |
+
cap = cv2.VideoCapture(video_source)
|
135 |
if not cap.isOpened():
|
136 |
+
return "Error: Could not access video source"
|
137 |
|
138 |
+
frame_count = 0
|
|
|
|
|
|
|
|
|
139 |
|
140 |
+
while True:
|
141 |
+
ret, frame = cap.read()
|
142 |
+
if not ret:
|
143 |
+
break
|
144 |
+
|
145 |
+
frame_count += 1
|
146 |
+
|
147 |
+
# Process every 100th frame for real-time performance
|
148 |
+
if frame_count % 100 == 0:
|
149 |
+
print(f"Analyzing frame {frame_count}...")
|
150 |
+
|
151 |
+
# Detect fire and smoke
|
152 |
+
fire_detected, smoke_detected, fire_details = analyze_fire_scene(frame)
|
153 |
+
|
154 |
+
if fire_detected or smoke_detected:
|
155 |
+
cap.release()
|
156 |
+
location = "Video Stream"
|
157 |
+
|
158 |
+
if fire_detected:
|
159 |
+
return f"π₯ FIRE DETECTED at {location}! Frame: {frame_count}"
|
160 |
+
elif smoke_detected:
|
161 |
+
return f"π¨ SMOKE DETECTED at {location}! Frame: {frame_count}"
|
162 |
+
|
163 |
+
# Break on 'q' key (for testing)
|
164 |
+
if cv2.waitKey(1) & 0xFF == ord('q'):
|
165 |
+
break
|
166 |
|
|
|
167 |
cap.release()
|
168 |
+
return "No fire or smoke detected in video stream"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
169 |
|
170 |
def respond(
|
171 |
message,
|
|
|
177 |
):
|
178 |
# Check if user wants to detect fire
|
179 |
if "detect fire" in message.lower():
|
180 |
+
return check_for_fire(0) # Use webcam
|
181 |
|
182 |
messages = [{"role": "system", "content": system_message}]
|
183 |
|
mcp_client.py
ADDED
@@ -0,0 +1,206 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import cv2
|
3 |
+
import threading
|
4 |
+
import time
|
5 |
+
import requests
|
6 |
+
import json
|
7 |
+
from datetime import datetime
|
8 |
+
|
9 |
+
class FireDetectionClient:
|
10 |
+
def __init__(self):
|
11 |
+
self.video_sources = {}
|
12 |
+
self.detection_threads = {}
|
13 |
+
self.running = {}
|
14 |
+
self.mcp_server_url = "http://localhost:7860"
|
15 |
+
|
16 |
+
def detect_fire_mcp(self, frame):
|
17 |
+
"""Send frame to MCP server for fire detection"""
|
18 |
+
try:
|
19 |
+
# Convert frame to base64 or save temporarily
|
20 |
+
import base64
|
21 |
+
import io
|
22 |
+
from PIL import Image
|
23 |
+
|
24 |
+
# Convert frame to PIL Image
|
25 |
+
image = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
|
26 |
+
|
27 |
+
# Convert to base64
|
28 |
+
buffer = io.BytesIO()
|
29 |
+
image.save(buffer, format='JPEG')
|
30 |
+
img_str = base64.b64encode(buffer.getvalue()).decode()
|
31 |
+
|
32 |
+
# Send to MCP server (assuming it has an API endpoint)
|
33 |
+
response = requests.post(
|
34 |
+
f"{self.mcp_server_url}/detect_fire",
|
35 |
+
json={"image": img_str},
|
36 |
+
timeout=5
|
37 |
+
)
|
38 |
+
|
39 |
+
if response.status_code == 200:
|
40 |
+
return response.json()
|
41 |
+
else:
|
42 |
+
return {"error": "MCP server error"}
|
43 |
+
|
44 |
+
except Exception as e:
|
45 |
+
return {"error": str(e)}
|
46 |
+
|
47 |
+
def monitor_video_source(self, source_id, video_source):
|
48 |
+
"""Monitor a video source for fire/smoke detection"""
|
49 |
+
cap = cv2.VideoCapture(video_source)
|
50 |
+
if not cap.isOpened():
|
51 |
+
return f"Error: Could not open video source {source_id}"
|
52 |
+
|
53 |
+
frame_count = 0
|
54 |
+
self.running[source_id] = True
|
55 |
+
|
56 |
+
while self.running.get(source_id, False):
|
57 |
+
ret, frame = cap.read()
|
58 |
+
if not ret:
|
59 |
+
break
|
60 |
+
|
61 |
+
frame_count += 1
|
62 |
+
|
63 |
+
# Process every 100th frame
|
64 |
+
if frame_count % 100 == 0:
|
65 |
+
timestamp = datetime.now().strftime("%H:%M:%S")
|
66 |
+
print(f"[{timestamp}] Source {source_id}: Analyzing frame {frame_count}")
|
67 |
+
|
68 |
+
# Simple fire detection (replace with MCP call)
|
69 |
+
fire_detected, smoke_detected = self.simple_fire_detection(frame)
|
70 |
+
|
71 |
+
if fire_detected or smoke_detected:
|
72 |
+
alert = f"π¨ ALERT - Source {source_id} at {timestamp}:\n"
|
73 |
+
if fire_detected:
|
74 |
+
alert += "π₯ FIRE DETECTED!\n"
|
75 |
+
if smoke_detected:
|
76 |
+
alert += "π¨ SMOKE DETECTED!\n"
|
77 |
+
alert += f"Frame: {frame_count}"
|
78 |
+
|
79 |
+
print(alert)
|
80 |
+
# Here you could send notifications, save alerts, etc.
|
81 |
+
|
82 |
+
time.sleep(0.01) # Small delay to prevent CPU overload
|
83 |
+
|
84 |
+
cap.release()
|
85 |
+
print(f"Stopped monitoring source {source_id}")
|
86 |
+
|
87 |
+
def simple_fire_detection(self, frame):
|
88 |
+
"""Simple color-based fire detection as fallback"""
|
89 |
+
# Convert to HSV for better color detection
|
90 |
+
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
|
91 |
+
|
92 |
+
# Fire color ranges (orange/red/yellow)
|
93 |
+
fire_lower1 = (0, 50, 50)
|
94 |
+
fire_upper1 = (10, 255, 255)
|
95 |
+
fire_lower2 = (170, 50, 50)
|
96 |
+
fire_upper2 = (180, 255, 255)
|
97 |
+
|
98 |
+
# Create masks
|
99 |
+
mask1 = cv2.inRange(hsv, fire_lower1, fire_upper1)
|
100 |
+
mask2 = cv2.inRange(hsv, fire_lower2, fire_upper2)
|
101 |
+
fire_mask = cv2.bitwise_or(mask1, mask2)
|
102 |
+
|
103 |
+
# Smoke detection (gray areas)
|
104 |
+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
105 |
+
smoke_mask = cv2.inRange(gray, 100, 200)
|
106 |
+
|
107 |
+
# Check if significant area detected
|
108 |
+
fire_area = cv2.countNonZero(fire_mask)
|
109 |
+
smoke_area = cv2.countNonZero(smoke_mask)
|
110 |
+
|
111 |
+
fire_detected = fire_area > 1000 # Threshold for fire
|
112 |
+
smoke_detected = smoke_area > 5000 # Threshold for smoke
|
113 |
+
|
114 |
+
return fire_detected, smoke_detected
|
115 |
+
|
116 |
+
def start_monitoring(self, sources):
|
117 |
+
"""Start monitoring selected video sources"""
|
118 |
+
results = []
|
119 |
+
|
120 |
+
for i, source in enumerate(sources):
|
121 |
+
if source.strip():
|
122 |
+
source_id = f"Source_{i+1}"
|
123 |
+
|
124 |
+
# Convert source to appropriate format
|
125 |
+
if source.isdigit():
|
126 |
+
video_source = int(source)
|
127 |
+
else:
|
128 |
+
video_source = source
|
129 |
+
|
130 |
+
# Start monitoring thread
|
131 |
+
thread = threading.Thread(
|
132 |
+
target=self.monitor_video_source,
|
133 |
+
args=(source_id, video_source),
|
134 |
+
daemon=True
|
135 |
+
)
|
136 |
+
|
137 |
+
self.detection_threads[source_id] = thread
|
138 |
+
thread.start()
|
139 |
+
|
140 |
+
results.append(f"β
Started monitoring {source_id}: {source}")
|
141 |
+
|
142 |
+
return "\n".join(results) if results else "No valid sources provided"
|
143 |
+
|
144 |
+
def stop_monitoring(self):
|
145 |
+
"""Stop all monitoring threads"""
|
146 |
+
for source_id in self.running:
|
147 |
+
self.running[source_id] = False
|
148 |
+
|
149 |
+
return "π Stopped all monitoring"
|
150 |
+
|
151 |
+
# Initialize client
|
152 |
+
client = FireDetectionClient()
|
153 |
+
|
154 |
+
def create_interface():
|
155 |
+
"""Create Gradio interface for fire detection client"""
|
156 |
+
|
157 |
+
with gr.Blocks(title="Fire Detection Client") as interface:
|
158 |
+
gr.Markdown("# π₯ Fire Detection Client")
|
159 |
+
gr.Markdown("Monitor up to 4 video sources for fire and smoke detection")
|
160 |
+
|
161 |
+
with gr.Row():
|
162 |
+
with gr.Column():
|
163 |
+
gr.Markdown("### Video Sources")
|
164 |
+
source1 = gr.Textbox(label="Source 1 (webcam: 0, file path, or RTSP URL)", placeholder="0")
|
165 |
+
source2 = gr.Textbox(label="Source 2", placeholder="rtsp://localhost:8554/stream")
|
166 |
+
source3 = gr.Textbox(label="Source 3", placeholder="C:/path/to/video.mp4")
|
167 |
+
source4 = gr.Textbox(label="Source 4", placeholder="")
|
168 |
+
|
169 |
+
with gr.Row():
|
170 |
+
start_btn = gr.Button("π Start Monitoring", variant="primary")
|
171 |
+
stop_btn = gr.Button("π Stop Monitoring", variant="secondary")
|
172 |
+
|
173 |
+
with gr.Column():
|
174 |
+
gr.Markdown("### Status")
|
175 |
+
status_output = gr.Textbox(
|
176 |
+
label="Monitoring Status",
|
177 |
+
lines=10,
|
178 |
+
interactive=False
|
179 |
+
)
|
180 |
+
|
181 |
+
gr.Markdown("### Instructions")
|
182 |
+
gr.Markdown("""
|
183 |
+
- **Webcam**: Enter `0` for default webcam, `1` for second camera
|
184 |
+
- **Video File**: Enter full path like `C:/videos/fire.mp4`
|
185 |
+
- **RTSP Stream**: Enter URL like `rtsp://localhost:8554/stream`
|
186 |
+
- **Detection**: Analyzes every 100th frame for real-time performance
|
187 |
+
- **Alerts**: Check console output for fire/smoke detection alerts
|
188 |
+
""")
|
189 |
+
|
190 |
+
# Event handlers
|
191 |
+
start_btn.click(
|
192 |
+
fn=lambda s1, s2, s3, s4: client.start_monitoring([s1, s2, s3, s4]),
|
193 |
+
inputs=[source1, source2, source3, source4],
|
194 |
+
outputs=status_output
|
195 |
+
)
|
196 |
+
|
197 |
+
stop_btn.click(
|
198 |
+
fn=client.stop_monitoring,
|
199 |
+
outputs=status_output
|
200 |
+
)
|
201 |
+
|
202 |
+
return interface
|
203 |
+
|
204 |
+
if __name__ == "__main__":
|
205 |
+
interface = create_interface()
|
206 |
+
interface.launch(server_port=7861, share=False)
|
rtsp_server.py
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import cv2
|
2 |
+
import subprocess
|
3 |
+
import threading
|
4 |
+
import time
|
5 |
+
|
6 |
+
def stream_mp4_as_rtsp(mp4_file, rtsp_port=8554):
|
7 |
+
"""Stream MP4 file as RTSP in a loop"""
|
8 |
+
rtsp_url = f"rtsp://localhost:{rtsp_port}/stream"
|
9 |
+
|
10 |
+
# FFmpeg command to stream MP4 as RTSP
|
11 |
+
cmd = [
|
12 |
+
'ffmpeg',
|
13 |
+
'-re', # Read input at native frame rate
|
14 |
+
'-stream_loop', '-1', # Loop infinitely
|
15 |
+
'-i', mp4_file,
|
16 |
+
'-c', 'copy', # Copy without re-encoding
|
17 |
+
'-f', 'rtsp',
|
18 |
+
rtsp_url
|
19 |
+
]
|
20 |
+
|
21 |
+
print(f"Starting RTSP server...")
|
22 |
+
print(f"RTSP URL: {rtsp_url}")
|
23 |
+
|
24 |
+
try:
|
25 |
+
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
26 |
+
return process, rtsp_url
|
27 |
+
except Exception as e:
|
28 |
+
print(f"Error starting RTSP server: {e}")
|
29 |
+
return None, None
|
30 |
+
|
31 |
+
if __name__ == "__main__":
|
32 |
+
mp4_file = input("Enter MP4 file path: ")
|
33 |
+
process, url = stream_mp4_as_rtsp(mp4_file)
|
34 |
+
|
35 |
+
if process:
|
36 |
+
print(f"RTSP stream running at: {url}")
|
37 |
+
print("Press Ctrl+C to stop")
|
38 |
+
try:
|
39 |
+
process.wait()
|
40 |
+
except KeyboardInterrupt:
|
41 |
+
process.terminate()
|
42 |
+
print("RTSP server stopped")
|