From 8d738d8b359e84092c5907d8f4bc07742f5427ed Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 16:21:20 +0000 Subject: [PATCH] feat: Enable camera access for video and photo publishing Adds a new feature allowing users to access their device's camera to capture and upload photos and videos. - Creates a new `/camera` page with a live camera stream and controls for capturing photos and videos. - Implements JavaScript to handle camera access, media capture, and file uploads using the Fetch API. - Adds backend endpoints in `app.py` to handle file uploads, store media in an `uploads/` directory, and provide a gallery API. - Generates unique, timestamped filenames on the client-side to prevent uploads from overwriting each other. - Links the new camera page from the main `index.html` for user access. --- eco_project/backend/app.py | 26 ++++++ eco_project/backend/static/camera.css | 53 ++++++++++++ eco_project/backend/static/camera.html | 38 +++++++++ eco_project/backend/static/camera.js | 109 +++++++++++++++++++++++++ eco_project/backend/static/index.html | 1 + 5 files changed, 227 insertions(+) create mode 100644 eco_project/backend/static/camera.css create mode 100644 eco_project/backend/static/camera.html create mode 100644 eco_project/backend/static/camera.js diff --git a/eco_project/backend/app.py b/eco_project/backend/app.py index 96a112f..6a93c44 100644 --- a/eco_project/backend/app.py +++ b/eco_project/backend/app.py @@ -627,6 +627,32 @@ def handle_connect(): def handle_disconnect(): print('Client disconnected') +from werkzeug.utils import secure_filename + +UPLOAD_FOLDER = os.path.join(app.root_path, 'uploads') +os.makedirs(UPLOAD_FOLDER, exist_ok=True) + +@app.route('/api/upload', methods=['POST']) +def upload_file(): + if 'media' not in request.files: + return jsonify({"error": "No file part"}), 400 + file = request.files['media'] + if file.filename == '': + return jsonify({"error": "No selected file"}), 400 + if file: + filename = secure_filename(file.filename) + file.save(os.path.join(UPLOAD_FOLDER, filename)) + return jsonify({"message": "File uploaded successfully"}), 201 + +@app.route('/api/gallery') +def gallery(): + files = os.listdir(UPLOAD_FOLDER) + return jsonify(files) + +@app.route('/uploads/') +def uploaded_file(filename): + return send_from_directory(UPLOAD_FOLDER, filename) + @socketio.on('chat_message') def handle_chat_message(message): emit('chat_message', message, broadcast=True) diff --git a/eco_project/backend/static/camera.css b/eco_project/backend/static/camera.css new file mode 100644 index 0000000..b77ff8a --- /dev/null +++ b/eco_project/backend/static/camera.css @@ -0,0 +1,53 @@ +/* General styles for the camera page */ +#camera-container { + display: flex; + flex-direction: column; + align-items: center; + margin-bottom: 2rem; +} + +#camera-stream { + width: 100%; + max-width: 600px; + border: 2px solid #ccc; + border-radius: 5px; +} + +#camera-controls { + margin-top: 1rem; +} + +#camera-controls button { + margin: 0 0.5rem; + padding: 0.8rem 1.2rem; + font-size: 1rem; + cursor: pointer; + border-radius: 5px; + border: none; + background-color: #007bff; + color: white; +} + +#camera-controls button:disabled { + background-color: #ccc; + cursor: not-allowed; +} + +/* Gallery styles */ +#gallery { + padding: 1rem; +} + +#gallery-container { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 1rem; +} + +.gallery-item img, +.gallery-item video { + width: 100%; + height: auto; + border-radius: 5px; + border: 1px solid #ddd; +} diff --git a/eco_project/backend/static/camera.html b/eco_project/backend/static/camera.html new file mode 100644 index 0000000..035975b --- /dev/null +++ b/eco_project/backend/static/camera.html @@ -0,0 +1,38 @@ + + + + + + Camera Access - Environment Protection + + + + +
+

Camera Access

+ +
+
+
+ +
+ + + +
+
+ +
+ + + + \ No newline at end of file diff --git a/eco_project/backend/static/camera.js b/eco_project/backend/static/camera.js new file mode 100644 index 0000000..a241830 --- /dev/null +++ b/eco_project/backend/static/camera.js @@ -0,0 +1,109 @@ +document.addEventListener('DOMContentLoaded', () => { + const cameraStream = document.getElementById('camera-stream'); + const capturePhotoButton = document.getElementById('capture-photo'); + const startVideoButton = document.getElementById('start-video'); + const stopVideoButton = document.getElementById('stop-video'); + const galleryContainer = document.getElementById('gallery-container'); + + let mediaRecorder; + let recordedChunks = []; + + // Access camera + navigator.mediaDevices.getUserMedia({ video: true, audio: true }) + .then(stream => { + cameraStream.srcObject = stream; + }) + .catch(err => { + console.error("Error accessing camera: ", err); + galleryContainer.innerHTML = '

Could not access the camera. Please check permissions.

'; + }); + + // Capture photo + capturePhotoButton.addEventListener('click', () => { + const canvas = document.createElement('canvas'); + canvas.width = cameraStream.videoWidth; + canvas.height = cameraStream.videoHeight; + canvas.getContext('2d').drawImage(cameraStream, 0, 0); + canvas.toBlob(blob => { + const timestamp = new Date().toISOString(); + uploadFile(blob, `photo_${timestamp}.png`); + }, 'image/png'); + }); + + // Start recording + startVideoButton.addEventListener('click', () => { + recordedChunks = []; + const stream = cameraStream.srcObject; + mediaRecorder = new MediaRecorder(stream, { mimeType: 'video/webm;codecs=vp9' }); + + mediaRecorder.ondataavailable = event => { + if (event.data.size > 0) { + recordedChunks.push(event.data); + } + }; + + mediaRecorder.onstop = () => { + const blob = new Blob(recordedChunks, { type: 'video/webm' }); + const timestamp = new Date().toISOString(); + uploadFile(blob, `video_${timestamp}.webm`); + }; + + mediaRecorder.start(); + startVideoButton.disabled = true; + stopVideoButton.disabled = false; + }); + + // Stop recording + stopVideoButton.addEventListener('click', () => { + mediaRecorder.stop(); + startVideoButton.disabled = false; + stopVideoButton.disabled = true; + }); + + // Upload file + function uploadFile(blob, filename) { + const formData = new FormData(); + formData.append('media', blob, filename); + + fetch('/api/upload', { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + console.log(data.message); + fetchGallery(); + }) + .catch(error => { + console.error('Error uploading file:', error); + }); + } + + // Fetch and display gallery + function fetchGallery() { + fetch('/api/gallery') + .then(response => response.json()) + .then(files => { + displayMedia(files); + }) + .catch(error => console.error('Error fetching gallery:', error)); + } + + function displayMedia(files) { + galleryContainer.innerHTML = ''; + if (files.length === 0) { + galleryContainer.innerHTML = '

No media in the gallery yet.

'; + return; + } + files.forEach(file => { + const mediaElement = file.endsWith('.png') || file.endsWith('.jpg') ? + `${file}` : + ``; + + galleryContainer.innerHTML += ``; + }); + } + + // Initial load + fetchGallery(); +}); diff --git a/eco_project/backend/static/index.html b/eco_project/backend/static/index.html index 395b720..5dcc5d7 100644 --- a/eco_project/backend/static/index.html +++ b/eco_project/backend/static/index.html @@ -52,6 +52,7 @@

Community

Share your ideas and connect with others.