Skip to content

Commit

Permalink
chore: Update video URL in Home component and add recording functiona…
Browse files Browse the repository at this point in the history
…lity
  • Loading branch information
infinitel8p committed Aug 21, 2024
1 parent 18f695d commit 5e6daab
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 73 deletions.
69 changes: 7 additions & 62 deletions client/main.py
Original file line number Diff line number Diff line change
@@ -1,65 +1,10 @@
import cv2
from flask import Flask, Response, jsonify
from flask_cors import CORS
import os
import threading
from datetime import datetime
import system_helpers
import stream_helpers

app = Flask(__name__)
CORS(app) # Enable CORS for all routes

# Create a lock for thread-safe access to the recording flag and writer
lock = threading.Lock()
out = None
is_recording = False

os.makedirs('./recordings', exist_ok=True)


def generate_frames():
global out, is_recording
cap = cv2.VideoCapture(0)

while True:
success, frame = cap.read()
if not success:
break

with lock:
if is_recording and out is not None:
out.write(frame)

# Encode the frame in JPEG format
ret, buffer = cv2.imencode('.jpg', frame)
frame = buffer.tobytes()

# Use yield to return the frame as a byte array
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')

cap.release()


def start_recording():
global out, is_recording
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f'./recordings/output_{timestamp}.avi'

# Set up the video writer
fourcc = cv2.VideoWriter_fourcc(*'XVID')
with lock:
out = cv2.VideoWriter(filename, fourcc, 20.0, (640, 480))
is_recording = True


def stop_recording():
global out, is_recording
with lock:
if out is not None:
out.release()
out = None
is_recording = False
CORS(app, resources={r"/*": {"origins": "*"}})


@app.route('/system_info', methods=['GET'])
Expand All @@ -79,19 +24,19 @@ def system_info():

@app.route('/video_feed')
def video_feed():
return Response(generate_frames(), mimetype='multipart/x-mixed-replace; boundary=frame')
return Response(stream_helpers.generate_frames(), mimetype='multipart/x-mixed-replace; boundary=frame')


@app.route('/toggle_recording', methods=['POST'])
def toggle_recording():
global is_recording
if is_recording:
stop_recording()
if stream_helpers.is_recording:
stream_helpers.stop_recording()
return jsonify({"message": "Recording stopped"})
else:
start_recording()
stream_helpers.start_recording()
return jsonify({"message": "Recording started"})


if __name__ == "__main__":
app.run(host='0.0.0.0', port=5000, debug=True)
app.run(host='0.0.0.0', port=5005, debug=True)
78 changes: 78 additions & 0 deletions client/stream_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import os
import cv2
import threading
from datetime import datetime

# Lock for thread-safe access to the recording flag and writer
lock = threading.Lock()
out = None
is_recording = False

# Ensure recordings directory exists
os.makedirs('./recordings', exist_ok=True)


def generate_frames():
"""
Captures frames from the default camera feed, encodes them as JPEG images, and yields them as byte streams.
Yields:
bytes: A sequence of JPEG-encoded image frames as byte streams, suitable for streaming in an HTTP response.
Returns:
None
"""

global out, is_recording
cap = cv2.VideoCapture(0)

while True:
success, frame = cap.read()
if not success:
break

with lock:
if is_recording and out is not None:
out.write(frame)

ret, buffer = cv2.imencode('.jpg', frame)
frame = buffer.tobytes()

yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')

cap.release()


def start_recording() -> None:
"""
Starts recording video frames from the default camera feed and saves them to a file.
Returns:
None
"""

global out, is_recording
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f'./recordings/output_{timestamp}.avi'

fourcc = cv2.VideoWriter_fourcc(*'XVID')
with lock:
out = cv2.VideoWriter(filename, fourcc, 20.0, (640, 480))
is_recording = True


def stop_recording() -> None:
"""
Stops recording video frames and releases the video writer.
Returns:
None
"""

global out, is_recording
with lock:
if out is not None:
out.release()
out = None
is_recording = False
3 changes: 0 additions & 3 deletions client/system_helpers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import os
import psutil


Expand All @@ -11,10 +10,8 @@ def get_cpu_temp() -> float | None:
None: If the temperature file is not found.
"""
try:
# Read temperature from thermal zone
with open("/sys/class/thermal/thermal_zone0/temp", "r") as file:
temp_str = file.read().strip()
# Convert to Celsius
cpu_temp = int(temp_str) / 1000.0
return cpu_temp
except FileNotFoundError:
Expand Down
7 changes: 6 additions & 1 deletion server/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"dev": "next dev -H 0.0.0.0",
"build": "next build",
"start": "next start",
"lint": "next lint"
Expand All @@ -12,7 +12,8 @@
"next": "14.2.5",
"react": "^18",
"react-dom": "^18",
"react-player": "^2.16.0"
"react-player": "^2.16.0",
"server": "file:"
},
"devDependencies": {
"@types/node": "^20",
Expand All @@ -24,4 +25,4 @@
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}
}
3 changes: 2 additions & 1 deletion server/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from "react";
import ClientVideoPlayer from "@/components/ClientVideoPlayer";

const Home = () => {

return (
<div className="space-y-10 mx-10">
<div className="text-center border border-red-500">
Expand All @@ -10,7 +11,7 @@ const Home = () => {

<div className="grid grid-cols-2 border border-red-200 gap-10">
<div className="border border-red-500 h-96">
<ClientVideoPlayer url="http://localhost:5000/video_feed" />
<ClientVideoPlayer />
</div>
<div className="border border-red-500">
(log) monitor - (un-)befugter zutritt als status ggfs, date time, falls bekannte person/handy auch namen,bild
Expand Down
7 changes: 4 additions & 3 deletions server/src/components/ClientVideoPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

import React, { useState } from 'react';

const ClientVideoPlayer = ({ url }: { url: string }) => {
const ClientVideoPlayer = () => {
const [isRecording, setIsRecording] = useState(false);
const videoFeedUrl = `${window.location.protocol}//${window.location.hostname}:5005`;

const toggleRecording = async () => {
try {
const response = await fetch("http://localhost:5000/toggle_recording", {
const response = await fetch(`${videoFeedUrl}/toggle_recording`, {
method: "POST",
});
const data = await response.json();
Expand All @@ -25,7 +26,7 @@ const ClientVideoPlayer = ({ url }: { url: string }) => {
return (
<>
<img
src={url}
src={`${videoFeedUrl}/video_feed`}
alt="Live Stream"
style={{ width: '100%', height: '100%' }}
/>
Expand Down

0 comments on commit 5e6daab

Please sign in to comment.