add Immich send

This commit is contained in:
Romain
2026-04-17 15:44:42 +02:00
parent 640442b399
commit 49828c9323
61 changed files with 10506 additions and 1 deletions
+225
View File
@@ -0,0 +1,225 @@
#!/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 os
import threading
import time
from datetime import datetime
from flask import Flask, Response, jsonify, 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
PHOTOS_DIR = os.path.join(os.path.dirname(__file__), "photos")
# ─── 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)")
os.makedirs(PHOTOS_DIR, exist_ok=True)
# ─── 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("/capture")
def capture():
"""Sauvegarde la frame courante dans le dossier photos/."""
with output.condition:
output.condition.wait()
frame = output.frame
filename = datetime.now().strftime("%Y%m%d_%H%M%S.jpg")
filepath = os.path.join(PHOTOS_DIR, filename)
with open(filepath, "wb") as f:
f.write(frame)
return jsonify({"status": "ok", "file": filename})
@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.")