|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Media Stream Capture and Analysis</title> |
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"> |
|
<style> |
|
body { |
|
background-color: #f8f9fa; |
|
} |
|
.container { |
|
max-width: 720px; |
|
margin-top: 20px; |
|
} |
|
#video { |
|
width: 100%; |
|
height: auto; |
|
background-color: #ddd; |
|
margin-bottom: 20px; |
|
} |
|
.button-container { |
|
display: flex; |
|
flex-direction: column; |
|
align-items: center; |
|
} |
|
.btn-group-toggle { |
|
display: flex; |
|
justify-content: center; |
|
align-items: center; |
|
width: 100%; |
|
} |
|
.btn-group-toggle .btn { |
|
width: auto; |
|
margin: 0 10px; |
|
} |
|
.btn-group { |
|
margin-bottom: 10px; |
|
} |
|
.btn-secondary.active { |
|
background-color: #6c757d; |
|
border-color: #6c757d; |
|
color: white; |
|
} |
|
.settings-panel { |
|
display: none; |
|
margin-bottom: 20px; |
|
} |
|
.settings-panel .nav-tabs { |
|
margin-bottom: 10px; |
|
} |
|
#formattedMarkdown { |
|
background-color: #fff; |
|
border: 1px solid #ddd; |
|
padding: 10px; |
|
margin-top: 20px; |
|
border-radius: 5px; |
|
max-height: 200px; |
|
overflow-y: auto; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div class="container text-center"> |
|
<h2 class="my-4">GPT-4o Media Stream Capture & Analysis</h2> |
|
<p class="mb-4">A web app to capture and analyze media streams from your webcam, desktop, or specific apps. Easily switch sources and capture frames at intervals. Use AI for insightful frame analysis and summaries.</p> |
|
<video id="video" class="mb-4" autoplay></video> |
|
<div class="button-container"> |
|
<div class="btn-group btn-group-toggle mb-4" data-toggle="buttons"> |
|
<label class="btn btn-secondary"> |
|
<input type="radio" name="options" id="shareWebcam" autocomplete="off"> Share Webcam |
|
</label> |
|
<label class="btn btn-secondary"> |
|
<input type="radio" name="options" id="shareScreen" autocomplete="off"> Share Screen |
|
</label> |
|
<label class="btn btn-secondary"> |
|
<input type="radio" name="options" id="shareApplication" autocomplete="off"> Share Application |
|
</label> |
|
</div> |
|
<div class="btn-group mb-4"> |
|
<button id="startCapture" class="btn btn-primary">📸 Start Capture</button> |
|
<button id="stopCapture" class="btn btn-danger">🛑 Stop Capture</button> |
|
<button id="toggleSettings" class="btn btn-info">⚙️ Settings</button> |
|
</div> |
|
</div> |
|
<div class="settings-panel card card-body"> |
|
<ul class="nav nav-tabs" id="settingsTabs" role="tablist"> |
|
<li class="nav-item"> |
|
<a class="nav-link active" id="prompt-tab" data-toggle="tab" href="#prompt" role="tab" aria-controls="prompt" aria-selected="true">Prompt</a> |
|
</li> |
|
<li class="nav-item"> |
|
<a class="nav-link" id="settings-tab" data-toggle="tab" href="#settings" role="tab" aria-controls="settings" aria-selected="false">Settings</a> |
|
</li> |
|
<li class="nav-item"> |
|
<a class="nav-link" id="api-tab" data-toggle="tab" href="#api" role="tab" aria-controls="api" aria-selected="false">API Config</a> |
|
</li> |
|
</ul> |
|
<div class="tab-content" id="settingsTabsContent"> |
|
<div class="tab-pane fade show active" id="prompt" role="tabpanel" aria-labelledby="prompt-tab"> |
|
<textarea id="customPrompt" class="form-control" rows="3" placeholder="Enter custom prompt here...">Analyze this frame</textarea> |
|
</div> |
|
<div class="tab-pane fade" id="settings" role="tabpanel" aria-labelledby="settings-tab"> |
|
<input type="number" id="refreshRate" class="form-control" placeholder="Refresh rate in seconds" value="15"> |
|
</div> |
|
<div class="tab-pane fade" id="api" role="tabpanel" aria-labelledby="api-tab"> |
|
<input type="password" id="apiKey" class="form-control" placeholder="OpenAI API Key"> |
|
<input type="checkbox" id="showApiKey"> Show API Key |
|
</div> |
|
<button id="saveSettings" class="btn btn-success mt-3">Save Settings</button> |
|
</div> |
|
</div> |
|
<div id="formattedMarkdown" class="text-left"></div> |
|
</div> |
|
<div class="container text-center"> |
|
created by rUv |
|
</div> |
|
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js"></script> |
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> |
|
<script src="/static/script.js"></script> |
|
<script> |
|
const video = document.getElementById('video'); |
|
const formattedMarkdownDiv = document.getElementById('formattedMarkdown'); |
|
const toggleSettingsButton = document.getElementById('toggleSettings'); |
|
const settingsPanel = document.querySelector('.settings-panel'); |
|
const saveSettingsButton = document.getElementById('saveSettings'); |
|
const startCaptureButton = document.getElementById('startCapture'); |
|
const stopCaptureButton = document.getElementById('stopCapture'); |
|
const showApiKeyCheckbox = document.getElementById('showApiKey'); |
|
const apiKeyInput = document.getElementById('apiKey'); |
|
let captureInterval; |
|
let refreshRate = 15; |
|
let customPrompt = "Analyze this frame"; |
|
let apiKey = ""; |
|
let activeStream = null; |
|
let isProcessing = false; |
|
|
|
function logMessage(message) { |
|
const p = document.createElement('p'); |
|
p.textContent = message; |
|
formattedMarkdownDiv.appendChild(p); |
|
formattedMarkdownDiv.scrollTop = formattedMarkdownDiv.scrollHeight; |
|
} |
|
|
|
async function startWebcamStream() { |
|
activeStream = await navigator.mediaDevices.getUserMedia({ video: true }); |
|
logMessage("Webcam stream started."); |
|
video.srcObject = activeStream; |
|
} |
|
|
|
async function startScreenShare() { |
|
activeStream = await navigator.mediaDevices.getDisplayMedia({ video: true }); |
|
logMessage("Screen share started."); |
|
video.srcObject = activeStream; |
|
} |
|
|
|
async function startApplicationShare() { |
|
activeStream = await navigator.mediaDevices.getDisplayMedia({ |
|
video: { |
|
cursor: "always", |
|
displaySurface: "application" |
|
} |
|
}); |
|
logMessage("Application share started."); |
|
video.srcObject = activeStream; |
|
} |
|
|
|
async function handleStreamSelection(event) { |
|
if (isProcessing) { |
|
event.stopImmediatePropagation(); |
|
return; |
|
} |
|
isProcessing = true; |
|
|
|
|
|
document.querySelectorAll('.btn-group-toggle .btn').forEach(btn => btn.classList.remove('active')); |
|
|
|
|
|
event.target.closest('.btn').classList.add('active'); |
|
|
|
const selectedButton = event.target.closest('.btn').querySelector('input'); |
|
if (!selectedButton) { |
|
console.error('No input element found inside the clicked button.'); |
|
isProcessing = false; |
|
return; |
|
} |
|
|
|
const selectedButtonId = selectedButton.id; |
|
if (activeStream) { |
|
|
|
let tracks = activeStream.getTracks(); |
|
tracks.forEach(track => track.stop()); |
|
activeStream = null; |
|
} |
|
|
|
if (selectedButtonId === 'shareWebcam') { |
|
await startWebcamStream(); |
|
} else if (selectedButtonId === 'shareScreen') { |
|
await startScreenShare(); |
|
} else if (selectedButtonId === 'shareApplication') { |
|
await startApplicationShare(); |
|
} |
|
|
|
isProcessing = false; |
|
} |
|
|
|
|
|
document.body.addEventListener('click', (event) => { |
|
if (event.target.closest('.btn-group-toggle .btn')) { |
|
handleStreamSelection(event); |
|
} |
|
}); |
|
|
|
|
|
toggleSettingsButton.addEventListener('click', () => { |
|
settingsPanel.style.display = settingsPanel.style.display === 'none' ? 'block' : 'none'; |
|
logMessage(""); |
|
}); |
|
|
|
|
|
saveSettingsButton.addEventListener('click', () => { |
|
customPrompt = document.getElementById('customPrompt').value || "Analyze this frame"; |
|
refreshRate = document.getElementById('refreshRate').value || 15; |
|
apiKey = document.getElementById('apiKey').value || ""; |
|
settingsPanel.style.display = 'none'; |
|
logMessage("Settings saved."); |
|
}); |
|
|
|
|
|
showApiKeyCheckbox.addEventListener('change', () => { |
|
apiKeyInput.type = showApiKeyCheckbox.checked ? 'text' : 'password'; |
|
}); |
|
|
|
|
|
startCaptureButton.addEventListener('click', () => { |
|
if (isProcessing || captureInterval) { |
|
return; |
|
} |
|
isProcessing = true; |
|
|
|
captureFrame(); |
|
captureInterval = setInterval(captureFrame, refreshRate * 1000); |
|
logMessage("Capture started."); |
|
|
|
isProcessing = false; |
|
}); |
|
|
|
|
|
stopCaptureButton.addEventListener('click', () => { |
|
if (isProcessing || !captureInterval) { |
|
return; |
|
} |
|
isProcessing = true; |
|
|
|
clearInterval(captureInterval); |
|
captureInterval = null; |
|
logMessage("Capture stopped."); |
|
|
|
isProcessing = false; |
|
}); |
|
|
|
function captureFrame() { |
|
const canvas = document.createElement('canvas'); |
|
canvas.width = video.videoWidth; |
|
canvas.height = video.videoHeight; |
|
const context = canvas.getContext('2d'); |
|
context.drawImage(video, 0, 0, canvas.width, canvas.height); |
|
const dataUrl = canvas.toDataURL('image/jpeg'); |
|
|
|
logMessage("Frame captured and sent to API."); |
|
fetch('/process_frame', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json' |
|
}, |
|
body: JSON.stringify({ image: dataUrl, prompt: customPrompt, api_key: apiKey }) |
|
}) |
|
.then(response => response.json()) |
|
.then(data => { |
|
const formattedMarkdown = marked.parse(data.response); |
|
formattedMarkdownDiv.innerHTML = formattedMarkdown; |
|
formattedMarkdownDiv.scrollTop = formattedMarkdownDiv.scrollHeight; |
|
}) |
|
.catch(error => { |
|
console.error('Error:', error); |
|
logMessage(`Error: ${error.message}`); |
|
}); |
|
} |
|
</script> |
|
</body> |
|
</html> |
|
|