jan-contract / components /video_recorder.py
Amodit's picture
Cleanup and Fixes
66c7ada
# D:\jan-contract\components\video_recorder.py
import os
import streamlit as st
import datetime
import streamlit.components.v1 as components
VIDEO_CONSENT_DIR = "video_consents"
os.makedirs(VIDEO_CONSENT_DIR, exist_ok=True)
def record_consent_video():
"""
Production-grade Video Recorder using RecordRTC.
Features:
- Camera Selection (Fixes 'wrong camera' issues)
- RecordRTC Library (Handles cross-browser compatibility)
- Client-side Encoding (Works on Vercel/Heroku)
"""
st.markdown("### 📹 Record Video Consent")
st.info("Ensure you grant camera permissions when prompted by your browser.")
# We use RecordRTC via CDN for maximum robustness
html_code = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://www.WebRTC-Experiment.com/RecordRTC.js"></script>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; background: transparent; }
.container { text-align: center; max-width: 640px; margin: auto; }
video {
width: 100%;
border-radius: 8px;
background: #000;
margin-bottom: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
}
select {
padding: 8px;
border-radius: 4px;
border: 1px solid #ccc;
margin-bottom: 15px;
width: 100%;
font-size: 14px;
}
.btn-group { display: flex; gap: 10px; justify-content: center; margin-top: 10px; }
button {
padding: 10px 20px;
border: none;
border-radius: 4px;
color: white;
font-weight: 600;
cursor: pointer;
}
#btn-start { background: #28a745; }
#btn-stop { background: #dc3545; }
#btn-download { background: #007bff; display: none; }
button:disabled { opacity: 0.5; cursor: not-allowed; }
#status { margin-top: 10px; font-size: 13px; color: #555; }
</style>
</head>
<body>
<div class="container">
<select id="video-source"><option value="">Loading cameras...</option></select>
<video id="preview" autoplay muted playsinline></video>
<div class="btn-group">
<button id="btn-start">Start Recording</button>
<button id="btn-stop" disabled>Stop</button>
<button id="btn-download">Save Video</button>
</div>
<div id="status">Ready. Select camera and click Start.</div>
</div>
<script>
const videoElement = document.getElementById('preview');
const videoSelect = document.getElementById('video-source');
const btnStart = document.getElementById('btn-start');
const btnStop = document.getElementById('btn-stop');
const btnDownload = document.getElementById('btn-download');
const status = document.getElementById('status');
let recorder;
let stream;
// 1. Enumerate Cameras
async function getCameras() {
try {
await navigator.mediaDevices.getUserMedia({ video: true }); // Request permission first
const devices = await navigator.mediaDevices.enumerateDevices();
const videoDevices = devices.filter(device => device.kind === 'videoinput');
videoSelect.innerHTML = '';
videoDevices.forEach(device => {
const option = document.createElement('option');
option.value = device.deviceId;
option.text = device.label || `Camera ${videoSelect.length + 1}`;
videoSelect.appendChild(option);
});
if(videoDevices.length === 0) {
videoSelect.innerHTML = '<option>No cameras found</option>';
status.innerText = "Error: No camera devices detected.";
}
} catch (err) {
status.innerText = "Error: Permission denied or no camera. " + err.message;
videoSelect.innerHTML = '<option>Camera Access Denied</option>';
}
}
getCameras();
// 2. Start Recording
btnStart.onclick = async () => {
const deviceId = videoSelect.value;
const constraints = {
video: { deviceId: deviceId ? { exact: deviceId } : undefined },
audio: true
};
try {
stream = await navigator.mediaDevices.getUserMedia(constraints);
videoElement.srcObject = stream;
videoElement.muted = true; // Avoid feedback
recorder = new RecordRTC(stream, {
type: 'video',
mimeType: 'video/webm;codecs=vp8',
disableLogs: false
});
recorder.startRecording();
btnStart.disabled = true;
btnStop.disabled = false;
btnDownload.style.display = 'none';
status.innerText = "Recording... Speak clearly.";
} catch (err) {
status.innerText = "Failed to start: " + err.message;
console.error(err);
}
};
// 3. Stop Recording
btnStop.onclick = () => {
recorder.stopRecording(() => {
const blob = recorder.getBlob();
const url = URL.createObjectURL(blob);
btnStart.disabled = false;
btnStop.disabled = true;
btnDownload.style.display = 'inline-block';
status.innerText = "Recording finished. Download to save.";
// Stop stream
stream.getTracks().forEach(track => track.stop());
videoElement.srcObject = null;
// Setup Download
btnDownload.onclick = () => {
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = 'recorded_consent.webm';
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 100);
status.innerText = "File kept. Now upload below.";
};
});
};
</script>
</body>
</html>
"""
# Height 600 to accommodate camera dropdown
components.html(html_code, height=600)
st.write("---")
st.markdown("### 📤 Upload Your Recording")
st.caption("Once you've saved the video above, upload it here to confirm.")
uploaded_file = st.file_uploader("Drop your recorded video here", type=["webm", "mp4", "mov"])
if uploaded_file is not None:
try:
# Process the uploaded file
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
ext = os.path.splitext(uploaded_file.name)[1] or ".webm"
video_filename = os.path.join(VIDEO_CONSENT_DIR, f"consent_upload_{timestamp}{ext}")
with open(video_filename, "wb") as f:
f.write(uploaded_file.getbuffer())
st.success("✅ Consent Video Received!")
st.video(video_filename)
return video_filename
except Exception as e:
st.error(f"Error saving file: {e}")
return None
return None