Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- Html:
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8" />
- <title>Don Toliver Concert Videos Gallery</title>
- <style>
- body {
- background: #111;
- color: #f5f5f5;
- font-family: Arial, sans-serif;
- margin: 0;
- }
- .gallery {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
- gap: 40px;
- padding: 48px;
- justify-items: center;
- align-items: start;
- max-width: 1600px;
- margin: 0 auto;
- }
- .video-icon {
- width: 150px;
- cursor: pointer;
- display: flex;
- flex-direction: column;
- align-items: center;
- margin-bottom: 16px;
- }
- .video-thumb-wrapper {
- position: relative;
- width: 100%;
- aspect-ratio: 9 / 16;
- background: #221a22;
- overflow: hidden;
- border-radius: 7px;
- margin-bottom: 7px;
- box-shadow: 0 4px 18px rgba(0, 0, 0, 0.1);
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .video-thumb {
- width: 100%;
- height: 100%;
- object-fit: cover;
- border-radius: 7px;
- }
- .play-overlay {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- width: 44px;
- height: 44px;
- background: rgba(0, 0, 0, 0.38);
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- opacity: 0;
- transition: opacity 0.16s;
- }
- .video-thumb-wrapper:hover .play-overlay {
- opacity: 1;
- }
- .play-overlay svg {
- width: 27px;
- height: 27px;
- fill: #fff;
- filter: drop-shadow(0 2px 10px #e5337a88);
- }
- .filename,
- .customname {
- word-break: break-all;
- width: 100%;
- text-align: center;
- font-size: 0.95rem;
- color: #e6d3ef;
- margin-bottom: 3px;
- }
- .customname {
- color: #fff;
- }
- .editname {
- width: 93%;
- padding: 3px;
- border-radius: 5px;
- border: 1px solid #adadad;
- background: #18181b;
- color: #efd1fa;
- margin-bottom: 5px;
- margin-top: 0;
- text-align: center;
- font-size: 0.93rem;
- }
- .edit-btn {
- background: #e5337a;
- color: #fff;
- border: none;
- font-size: 0.93rem;
- padding: 2px 14px;
- border-radius: 5px;
- cursor: pointer;
- }
- #video-modal {
- display: none;
- position: fixed;
- z-index: 99;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(0, 0, 0, 0.77);
- justify-content: center;
- align-items: center;
- }
- #video-modal.active {
- display: flex;
- }
- #modal-player-wrap {
- background: #232323;
- padding: 2rem;
- border-radius: 12px;
- box-shadow: 0 8px 32px #81114955;
- display: flex;
- flex-direction: column;
- align-items: center;
- min-width: 350px;
- }
- #modal-player {
- width: 480px;
- max-width: 98vw;
- aspect-ratio: 9 / 16;
- object-fit: cover;
- background: #000;
- border-radius: 9px;
- margin-bottom: 18px;
- }
- .modal-close {
- position: absolute;
- top: 22px;
- right: 36px;
- color: #fff;
- font-size: 2.4rem;
- cursor: pointer;
- z-index: 101;
- }
- @media (max-width: 650px) {
- .gallery {
- grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
- padding: 10px;
- }
- .video-icon {
- width: 98px;
- }
- }
- </style>
- </head>
- <body>
- <div id="loadingOverlay" style="
- position: fixed; top: 0; left: 0; right:0; bottom:0;
- background: rgba(0,0,0,0.85);
- color: white;
- font-size: 1.5rem;
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- z-index: 1000;
- ">
- <div>Loading videos and previews...</div>
- <progress id="loadingProgress" max="100" value="0" style="width: 300px; margin-top: 20px; height: 25px; border-radius: 10px;"></progress>
- </div>
- <div class="gallery" id="gallery"></div>
- <!-- Modal Player -->
- <div id="video-modal">
- <span class="modal-close" onclick="closeModal()">×</span>
- <div id="modal-player-wrap">
- <video id="modal-player" controls></video>
- <div class="customname" id="modal-customname"></div>
- </div>
- </div>
- <script>
- // Wait for DOM to be ready so #loadingOverlay and #loadingProgress exist.
- document.addEventListener('DOMContentLoaded', () => {
- const SERVER_URL = 'http://localhost:8000';
- const VIDEO_API = SERVER_URL + '/videos';
- // Elements
- const overlay = document.getElementById('loadingOverlay');
- const prog = document.getElementById('loadingProgress');
- const galleryEl = document.getElementById('gallery');
- const modal = document.getElementById('video-modal');
- const modalPlayer = document.getElementById('modal-player');
- const modalCustom = document.getElementById('modal-customname');
- // State
- let names = {};
- // Helpers: names persistence
- function getVideoNames() {
- try { return JSON.parse(localStorage.getItem('videoEditableNames') || '{}'); }
- catch { return {}; }
- }
- function setVideoName(video, name) {
- names[video] = name;
- localStorage.setItem('videoEditableNames', JSON.stringify(names));
- }
- // Progress helpers: clamp and show overlay
- function setProgress(pct) {
- const v = Math.max(0, Math.min(100, Math.round(pct)));
- prog.value = v;
- }
- function showOverlay(msg) {
- overlay.style.display = 'flex';
- overlay.querySelector('div').firstChild.nodeValue = msg || 'Loading videos and previews...';
- }
- function hideOverlay() {
- overlay.style.display = 'none';
- }
- // Generate a portrait thumbnail from a frame
- function getVideoThumb(src, cb) {
- const video = document.createElement('video');
- video.src = src;
- video.muted = true;
- video.playsInline = true;
- video.crossOrigin = 'anonymous';
- // Seek a bit into the video once metadata is ready to avoid frame 0
- const draw = () => {
- const canvas = document.createElement('canvas');
- canvas.width = 180;
- canvas.height = 320;
- const ctx = canvas.getContext('2d');
- ctx.fillStyle = '#18181c';
- ctx.fillRect(0, 0, canvas.width, canvas.height);
- try { ctx.drawImage(video, 0, 0, canvas.width, canvas.height); } catch(e){}
- cb(canvas.toDataURL());
- video.remove();
- };
- video.addEventListener('loadedmetadata', () => {
- // Use min(1s, duration/3) as a safer seek target
- let t = Math.min(1, (video.duration || 3) / 3);
- // Some browsers need a small delay before setting currentTime
- setTimeout(() => {
- try { video.currentTime = t; } catch(e) { draw(); }
- }, 0);
- });
- video.addEventListener('seeked', draw);
- video.addEventListener('error', () => { cb(''); video.remove(); });
- // Attach to DOM only after listeners to avoid GC in some browsers
- document.body.appendChild(video);
- }
- // Modal controls
- function showModal(src, name) {
- modalPlayer.src = src;
- modalPlayer.setAttribute('controls', 'controls'); // ensure native controls
- modalCustom.textContent = name;
- modal.classList.add('active');
- modalPlayer.play().catch(()=>{});
- }
- function closeModal() {
- modal.classList.remove('active');
- modalPlayer.pause();
- modalPlayer.src = '';
- }
- window.closeModal = closeModal;
- // Build the grid with progress updates
- async function buildGallery(files) {
- names = getVideoNames();
- // Filter playable types; adjust as needed
- const vids = files.filter(f => /\.(mp4|webm|mov|mkv|avi|flv)$/i.test(f));
- if (vids.length === 0) {
- overlay.querySelector('div').innerHTML = 'No videos found.';
- setProgress(100);
- return;
- }
- galleryEl.innerHTML = '';
- // Multi-phase progress: 20% fetch, 75% thumbnails, 5% finalize
- // We arrive here with 20% already set by fetchList().
- const thumbStart = 20;
- const thumbEnd = 95;
- const perThumb = (thumbEnd - thumbStart) / vids.length;
- let loadedThumbs = 0;
- await Promise.all(vids.map(file => new Promise(resolve => {
- const src = SERVER_URL + '/videos/' + encodeURIComponent(file);
- const thumbId = 'thumb_' + btoa(file).replace(/[=+]/g, '');
- const custom = names[file] || '';
- galleryEl.insertAdjacentHTML('beforeend', `
- <div class="video-icon" id="icon_${thumbId}">
- <div class="video-thumb-wrapper" id="${thumbId}">
- <img class="video-thumb" src="" alt="Preview" />
- <div class="play-overlay">
- <svg viewBox="0 0 60 60">
- <circle fill="#0007" cx="30" cy="30" r="30"/>
- <polygon points="22,17 49,30 22,43" fill="#fff"/>
- </svg>
- </div>
- </div>
- <div class="customname">${custom}</div>
- <input class="editname" type="text" placeholder="Add name..." value="${custom}">
- <button class="edit-btn" style="display:${custom ? 'none' : 'inline-block'};">Save</button>
- <div class="filename" style="display:${custom ? 'none' : 'block'};">${file}</div>
- </div>
- `);
- // Load thumbnail
- getVideoThumb(src, (dataUrl) => {
- const img = document.querySelector(`#${thumbId} .video-thumb`);
- if (img) img.src = dataUrl || '';
- loadedThumbs++;
- setProgress(thumbStart + loadedThumbs * perThumb);
- // Wire up interactions after thumb ready
- const wrap = document.getElementById(thumbId);
- if (wrap) wrap.onclick = () => showModal(src, names[file] || file);
- const root = document.getElementById(`icon_${thumbId}`);
- const saveBtn = root.querySelector('.edit-btn');
- const editInp = root.querySelector('.editname');
- const nameEl = root.querySelector('.customname');
- const fileEl = root.querySelector('.filename');
- if (saveBtn && editInp) {
- saveBtn.onclick = () => {
- const val = editInp.value.trim();
- setVideoName(file, val);
- nameEl.textContent = val;
- // Hide filename and save button if a custom name exists
- if (val) {
- if (fileEl) fileEl.style.display = 'none';
- saveBtn.style.display = 'none';
- } else {
- if (fileEl) fileEl.style.display = 'block';
- saveBtn.style.display = 'inline-block';
- }
- };
- }
- resolve();
- });
- })));
- // Finalize
- setProgress(100);
- setTimeout(hideOverlay, 250);
- }
- // Fetch list with deterministic progress
- async function fetchList() {
- showOverlay('Loading videos and previews...');
- setProgress(5);
- const res = await fetch(VIDEO_API);
- setProgress(15);
- const arr = await res.json();
- setProgress(20);
- return arr;
- }
- // Boot
- fetchList()
- .then(buildGallery)
- .catch(err => {
- console.error(err);
- overlay.querySelector('div').innerHTML = 'Failed to load videos. Ensure server is running.';
- setProgress(100);
- });
- });
- </script>
- </body>
- </html>
- python:
- import os
- import json
- from http.server import SimpleHTTPRequestHandler, HTTPServer
- VIDEO_DIR = r"F:\Don toliver concert"
- PORT = 8000
- class CustomHandler(SimpleHTTPRequestHandler):
- def do_GET(self):
- if self.path == '/videos':
- # List video files in VIDEO_DIR
- videos = [f for f in os.listdir(VIDEO_DIR) if f.lower().endswith(('.mp4', '.webm', '.mkv', '.avi', '.mov', '.flv'))]
- self.send_response(200)
- self.send_header('Content-type', 'application/json')
- self.end_headers()
- self.wfile.write(json.dumps(videos).encode())
- elif self.path.startswith('/videos/'):
- # Serve requested video file from VIDEO_DIR
- filename = self.path[len('/videos/'):]
- full_path = os.path.join(VIDEO_DIR, filename)
- if os.path.exists(full_path) and os.path.isfile(full_path):
- self.send_response(200)
- # Basic mime type detection by extension
- if filename.lower().endswith('.mp4'):
- self.send_header('Content-type', 'video/mp4')
- elif filename.lower().endswith('.webm'):
- self.send_header('Content-type', 'video/webm')
- elif filename.lower().endswith('.ogg'):
- self.send_header('Content-type', 'video/ogg')
- else:
- self.send_header('Content-type', 'application/octet-stream')
- self.end_headers()
- with open(full_path, 'rb') as f:
- self.wfile.write(f.read())
- else:
- self.send_error(404, 'File not found')
- else:
- # Serve normal files for html/js if any in server folder
- super().do_GET()
- httpd = HTTPServer(('localhost', PORT), CustomHandler)
- print(f"Serving at http://localhost:{PORT}")
- httpd.serve_forever()
Advertisement
Add Comment
Please, Sign In to add comment