Files
photo_booth/main.py
T
2026-04-17 14:06:18 +02:00

206 lines
6.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Serveur Flask - Stream vidéo Raspberry Pi HQ Camera M12
Compatible Raspberry Pi 4 et 5 avec Picamera2
"""
import io
import threading
import time
from flask import Flask, Response, render_template_string
from picamera2 import Picamera2
from picamera2.encoders import JpegEncoder
from picamera2.outputs import FileOutput
# ─── Configuration ────────────────────────────────────────────────────────────
RESOLUTION = (1280, 720) # Résolution du stream (largeur, hauteur)
FRAMERATE = 30 # Images par seconde
QUALITY = 85 # Qualité JPEG (1-100)
HOST = "0.0.0.0" # Ecoute sur toutes les interfaces réseau
PORT = 5000
# ─── Buffer de frames thread-safe ─────────────────────────────────────────────
class StreamOutput(io.BufferedIOBase):
def __init__(self):
self.frame = None
self.condition = threading.Condition()
def write(self, buf):
with self.condition:
self.frame = buf
self.condition.notify_all()
# ─── Initialisation caméra ────────────────────────────────────────────────────
picam2 = Picamera2()
video_config = picam2.create_video_configuration(
main={"size": RESOLUTION, "format": "RGB888"},
controls={"FrameRate": FRAMERATE}
)
picam2.configure(video_config)
output = StreamOutput()
encoder = JpegEncoder(q=QUALITY)
picam2.start_recording(encoder, FileOutput(output))
print(f"[✓] Caméra démarrée ({RESOLUTION[0]}x{RESOLUTION[1]} @ {FRAMERATE}fps)")
# ─── Générateur MJPEG ─────────────────────────────────────────────────────────
def generate_frames():
"""Génère un flux MJPEG frame par frame."""
while True:
with output.condition:
output.condition.wait()
frame = output.frame
yield (
b"--frame\r\n"
b"Content-Type: image/jpeg\r\n\r\n" + frame + b"\r\n"
)
# ─── Interface HTML ───────────────────────────────────────────────────────────
HTML_PAGE = """
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pi Camera Stream</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #0a0a0a;
color: #e0e0e0;
font-family: 'Courier New', monospace;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 20px;
}
header {
text-align: center;
}
h1 {
font-size: 1.2rem;
letter-spacing: 0.3em;
text-transform: uppercase;
color: #00ff88;
}
.subtitle {
font-size: 0.75rem;
color: #555;
letter-spacing: 0.15em;
margin-top: 4px;
}
.stream-container {
position: relative;
border: 1px solid #1a1a1a;
box-shadow: 0 0 40px rgba(0, 255, 136, 0.05);
}
.stream-container::before {
content: '● REC';
position: absolute;
top: 12px;
left: 12px;
font-size: 0.65rem;
color: #ff4444;
letter-spacing: 0.1em;
z-index: 10;
animation: blink 1.5s infinite;
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.2; }
}
img#stream {
display: block;
max-width: 90vw;
max-height: 75vh;
}
footer {
font-size: 0.7rem;
color: #333;
letter-spacing: 0.1em;
}
footer span {
color: #00ff88;
}
</style>
</head>
<body>
<header>
<h1>Raspberry Pi Camera</h1>
<p class="subtitle">HQ Camera M12 — Live Stream</p>
</header>
<div class="stream-container">
<img id="stream" src="/video_feed" alt="Camera stream">
</div>
<footer>
Résolution <span>{{ width }}×{{ height }}</span> —
Qualité <span>{{ quality }}%</span>
</footer>
</body>
</html>
"""
# ─── Application Flask ────────────────────────────────────────────────────────
app = Flask(__name__)
@app.route("/")
def index():
return render_template_string(
HTML_PAGE,
width=RESOLUTION[0],
height=RESOLUTION[1],
quality=QUALITY
)
@app.route("/video_feed")
def video_feed():
"""Endpoint du flux MJPEG."""
return Response(
generate_frames(),
mimetype="multipart/x-mixed-replace; boundary=frame"
)
# ─── Lancement ────────────────────────────────────────────────────────────────
if __name__ == "__main__":
try:
print(f"[✓] Serveur démarré → http://{HOST}:{PORT}")
print(f" Accès local : http://localhost:{PORT}")
print(f" Accès réseau : http://<IP_DU_PI>:{PORT}")
app.run(host=HOST, port=PORT, threaded=True)
except KeyboardInterrupt:
print("\n[!] Arrêt du serveur...")
finally:
picam2.stop_recording()
picam2.close()
print("[✓] Caméra arrêtée proprement.")