This commit is contained in:
2026-05-30 00:22:52 +02:00
parent 49828c9323
commit f83e01380c
4 changed files with 171 additions and 103 deletions
+17 -103
View File
@@ -9,7 +9,7 @@ import os
import threading
import time
from datetime import datetime
from flask import Flask, Response, jsonify, render_template_string
from flask import Flask, Response, jsonify, render_template, send_from_directory
from picamera2 import Picamera2
from picamera2.encoders import JpegEncoder
@@ -70,106 +70,6 @@ def generate_frames():
)
# ─── 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__)
@@ -177,8 +77,8 @@ app = Flask(__name__)
@app.route("/")
def index():
return render_template_string(
HTML_PAGE,
return render_template(
"index.html",
width=RESOLUTION[0],
height=RESOLUTION[1],
quality=QUALITY
@@ -200,6 +100,20 @@ def capture():
return jsonify({"status": "ok", "file": filename})
@app.route("/photos")
def photos():
files = sorted(
[f for f in os.listdir(PHOTOS_DIR) if f.endswith(".jpg")],
reverse=True
)[:5]
return jsonify(files)
@app.route("/photo/<filename>")
def photo(filename):
return send_from_directory(PHOTOS_DIR, filename)
@app.route("/video_feed")
def video_feed():
"""Endpoint du flux MJPEG."""
+20
View File
@@ -0,0 +1,20 @@
async function refreshGallery() {
const res = await fetch("/photos");
const files = await res.json();
const gallery = document.getElementById("gallery");
if (files.length === 0) return;
gallery.innerHTML = files
.map((f) => `<img src="/photo/${f}" alt="${f}" title="${f}">`)
.join("");
}
document.addEventListener("keydown", async (e) => {
if (e.key !== "c" && e.key !== "C") return;
await fetch("/capture");
refreshGallery();
});
refreshGallery();
setInterval(refreshGallery, 10000);
+103
View File
@@ -0,0 +1,103 @@
* {
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: 75vh;
max-height: 90vw;
}
.gallery {
display: flex;
gap: 8px;
}
.gallery-empty {
font-size: 0.7rem;
color: #333;
letter-spacing: 0.1em;
}
.gallery img {
width: 120px;
height: 80px;
object-fit: cover;
border: 1px solid #1a1a1a;
opacity: 0.75;
transition: opacity 0.2s;
}
.gallery img:hover {
opacity: 1;
}
footer {
font-size: 0.7rem;
color: #333;
letter-spacing: 0.1em;
}
footer span {
color: #00ff88;
}
+31
View File
@@ -0,0 +1,31 @@
<!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>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</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>
<section class="gallery" id="gallery">
<p class="gallery-empty">Aucune photo pour l'instant</p>
</section>
<footer>
Résolution <span>{{ width }}×{{ height }}</span>
Qualité <span>{{ quality }}%</span>
</footer>
<script src="{{ url_for('static', filename='script.js') }}"></script>
</body>
</html>