Files
stream-shot/ui.html

158 lines
7.2 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>StreamShot</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body {
background-color: #0f172a;
color: #f1f5f9;
}
.sh-card {
background: rgba(30, 41, 59, 0.7);
backdrop-filter: blur(8px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
</style>
</head>
<body class="min-h-screen flex flex-col items-center justify-center p-4">
<div class="max-w-2xl w-full space-y-8 text-center">
<header>
<h1 class="text-4xl font-extrabold tracking-tight text-white mb-2">StreamShot</h1>
<p class="text-slate-400">Capture a high-quality frame from any YouTube livestream</p>
</header>
<div class="sh-card p-6 rounded-2xl shadow-xl">
<div class="flex flex-col sm:flex-row gap-3">
<input
type="text"
id="streamUrl"
placeholder="Enter YouTube URL..."
class="flex-1 bg-slate-900 border border-slate-700 rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all text-white placeholder-slate-500"
>
<button
onclick="captureSnapshot()"
id="snapBtn"
class="bg-blue-600 hover:bg-blue-500 text-white font-semibold py-3 px-8 rounded-xl transition-all shadow-lg active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed"
>
Capture
</button>
</div>
<div id="status" class="mt-4 text-sm font-medium hidden text-blue-400"></div>
</div>
<div id="imageContainer" class="hidden space-y-4">
<div class="sh-card p-2 rounded-2xl overflow-hidden shadow-2xl">
<img id="snapshotImg" class="w-full rounded-xl" src="" alt="Snapshot">
</div>
<div class="flex justify-center gap-4">
<a
id="downloadBtn"
href="#"
download="snapshot.jpg"
class="inline-flex items-center gap-2 bg-blue-600 hover:bg-blue-500 text-white py-2.5 px-6 rounded-xl transition-all shadow-md"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
Download Image
</a>
</div>
</div>
<footer class="mt-12 text-left bg-slate-900/50 rounded-2xl p-6 border border-slate-800">
<h2 class="text-lg font-semibold text-white mb-4">API Documentation (for AI/CLI)</h2>
<div class="space-y-4">
<div>
<p class="text-sm text-slate-400 mb-2">To capture a snapshot and save the binary image directly using <code class="bg-slate-800 px-1.5 py-0.5 rounded text-blue-300">curl</code> and <code class="bg-slate-800 px-1.5 py-0.5 rounded text-blue-300">jq</code>:</p>
<pre class="bg-black/40 p-4 rounded-xl text-xs overflow-x-auto text-slate-300 border border-slate-700"><code>curl -X POST \
https://shsf-api.reversed.dev/api/exec/16/cca815a2-216d-4362-b6f5-1d57ac9f088d/snapshot \
-H "Content-Type: application/json" \
-d '{"url": "https://www.youtube.com/watch?v=STREAMID"}' \
| jq -r .buffer | base64 -d > snapshot.jpg</code></pre>
</div>
<div class="text-xs text-slate-500 italic">
Note: The response is a JSON object with a base64 encoded string in the <code class="text-slate-400">buffer</code> field.
</div>
</div>
</footer>
</div>
<script>
const API_BASE = 'https://shsf-api.reversed.dev/api/exec/16/cca815a2-216d-4362-b6f5-1d57ac9f088d';
async function captureSnapshot() {
const urlInput = document.getElementById('streamUrl');
const snapBtn = document.getElementById('snapBtn');
const status = document.getElementById('status');
const imgContainer = document.getElementById('imageContainer');
const snapshotImg = document.getElementById('snapshotImg');
const downloadBtn = document.getElementById('downloadBtn');
const youtubeUrl = urlInput.value.trim();
if (!youtubeUrl) {
status.textContent = 'Please enter a valid YouTube URL';
status.classList.remove('hidden', 'text-blue-400');
status.classList.add('text-red-400');
return;
}
// Reset UI
snapBtn.disabled = true;
snapBtn.textContent = 'Capturing...';
status.textContent = 'Extracting stream and capturing frame...';
status.classList.remove('hidden', 'text-red-400');
status.classList.add('text-blue-400');
imgContainer.classList.add('hidden');
try {
// The API expects a POST to /snapshot with a JSON body { "url": "..." }
const response = await fetch(API_BASE + '/snapshot', {
method: 'POST',
body: JSON.stringify({ url: youtubeUrl }),
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || `Server error: ${response.status}`);
}
const data = await response.json();
const binaryString = atob(data.buffer);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
const blob = new Blob([bytes], { type: 'image/jpeg' });
const objectUrl = URL.createObjectURL(blob);
snapshotImg.src = objectUrl;
downloadBtn.href = objectUrl;
imgContainer.classList.remove('hidden');
status.classList.add('hidden');
} catch (err) {
console.error(err);
status.textContent = `Error: ${err.message}`;
status.classList.remove('text-blue-400');
status.classList.add('text-red-400');
} finally {
snapBtn.disabled = false;
snapBtn.textContent = 'Capture';
}
}
// Add Enter key support
document.getElementById('streamUrl').addEventListener('keypress', (e) => {
if (e.key === 'Enter') captureSnapshot();
});
</script>
</body>
</html>