Guest User

Untitled

a guest
Sep 25th, 2025
21
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 13.49 KB | None | 0 0
  1. Html:
  2. <!DOCTYPE html>
  3. <html lang="en">
  4. <head>
  5. <meta charset="UTF-8" />
  6. <title>Don Toliver Concert Videos Gallery</title>
  7. <style>
  8. body {
  9. background: #111;
  10. color: #f5f5f5;
  11. font-family: Arial, sans-serif;
  12. margin: 0;
  13. }
  14. .gallery {
  15. display: grid;
  16. grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  17. gap: 40px;
  18. padding: 48px;
  19. justify-items: center;
  20. align-items: start;
  21. max-width: 1600px;
  22. margin: 0 auto;
  23. }
  24. .video-icon {
  25. width: 150px;
  26. cursor: pointer;
  27. display: flex;
  28. flex-direction: column;
  29. align-items: center;
  30. margin-bottom: 16px;
  31. }
  32. .video-thumb-wrapper {
  33. position: relative;
  34. width: 100%;
  35. aspect-ratio: 9 / 16;
  36. background: #221a22;
  37. overflow: hidden;
  38. border-radius: 7px;
  39. margin-bottom: 7px;
  40. box-shadow: 0 4px 18px rgba(0, 0, 0, 0.1);
  41. display: flex;
  42. align-items: center;
  43. justify-content: center;
  44. }
  45. .video-thumb {
  46. width: 100%;
  47. height: 100%;
  48. object-fit: cover;
  49. border-radius: 7px;
  50. }
  51. .play-overlay {
  52. position: absolute;
  53. top: 50%;
  54. left: 50%;
  55. transform: translate(-50%, -50%);
  56. width: 44px;
  57. height: 44px;
  58. background: rgba(0, 0, 0, 0.38);
  59. border-radius: 50%;
  60. display: flex;
  61. align-items: center;
  62. justify-content: center;
  63. opacity: 0;
  64. transition: opacity 0.16s;
  65. }
  66. .video-thumb-wrapper:hover .play-overlay {
  67. opacity: 1;
  68. }
  69. .play-overlay svg {
  70. width: 27px;
  71. height: 27px;
  72. fill: #fff;
  73. filter: drop-shadow(0 2px 10px #e5337a88);
  74. }
  75. .filename,
  76. .customname {
  77. word-break: break-all;
  78. width: 100%;
  79. text-align: center;
  80. font-size: 0.95rem;
  81. color: #e6d3ef;
  82. margin-bottom: 3px;
  83. }
  84. .customname {
  85. color: #fff;
  86. }
  87. .editname {
  88. width: 93%;
  89. padding: 3px;
  90. border-radius: 5px;
  91. border: 1px solid #adadad;
  92. background: #18181b;
  93. color: #efd1fa;
  94. margin-bottom: 5px;
  95. margin-top: 0;
  96. text-align: center;
  97. font-size: 0.93rem;
  98. }
  99. .edit-btn {
  100. background: #e5337a;
  101. color: #fff;
  102. border: none;
  103. font-size: 0.93rem;
  104. padding: 2px 14px;
  105. border-radius: 5px;
  106. cursor: pointer;
  107. }
  108. #video-modal {
  109. display: none;
  110. position: fixed;
  111. z-index: 99;
  112. top: 0;
  113. left: 0;
  114. right: 0;
  115. bottom: 0;
  116. background: rgba(0, 0, 0, 0.77);
  117. justify-content: center;
  118. align-items: center;
  119. }
  120. #video-modal.active {
  121. display: flex;
  122. }
  123. #modal-player-wrap {
  124. background: #232323;
  125. padding: 2rem;
  126. border-radius: 12px;
  127. box-shadow: 0 8px 32px #81114955;
  128. display: flex;
  129. flex-direction: column;
  130. align-items: center;
  131. min-width: 350px;
  132. }
  133. #modal-player {
  134. width: 480px;
  135. max-width: 98vw;
  136. aspect-ratio: 9 / 16;
  137. object-fit: cover;
  138. background: #000;
  139. border-radius: 9px;
  140. margin-bottom: 18px;
  141. }
  142.  
  143.  
  144. .modal-close {
  145. position: absolute;
  146. top: 22px;
  147. right: 36px;
  148. color: #fff;
  149. font-size: 2.4rem;
  150. cursor: pointer;
  151. z-index: 101;
  152. }
  153. @media (max-width: 650px) {
  154. .gallery {
  155. grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
  156. padding: 10px;
  157. }
  158. .video-icon {
  159. width: 98px;
  160. }
  161. }
  162. </style>
  163. </head>
  164. <body>
  165. <div id="loadingOverlay" style="
  166. position: fixed; top: 0; left: 0; right:0; bottom:0;
  167. background: rgba(0,0,0,0.85);
  168. color: white;
  169. font-size: 1.5rem;
  170. display: flex;
  171. flex-direction: column;
  172. justify-content: center;
  173. align-items: center;
  174. z-index: 1000;
  175. ">
  176. <div>Loading videos and previews...</div>
  177. <progress id="loadingProgress" max="100" value="0" style="width: 300px; margin-top: 20px; height: 25px; border-radius: 10px;"></progress>
  178. </div>
  179.  
  180. <div class="gallery" id="gallery"></div>
  181.  
  182. <!-- Modal Player -->
  183. <div id="video-modal">
  184. <span class="modal-close" onclick="closeModal()">&times;</span>
  185. <div id="modal-player-wrap">
  186. <video id="modal-player" controls></video>
  187. <div class="customname" id="modal-customname"></div>
  188. </div>
  189. </div>
  190.  
  191.  
  192. <script>
  193. // Wait for DOM to be ready so #loadingOverlay and #loadingProgress exist.
  194. document.addEventListener('DOMContentLoaded', () => {
  195. const SERVER_URL = 'http://localhost:8000';
  196. const VIDEO_API = SERVER_URL + '/videos';
  197.  
  198. // Elements
  199. const overlay = document.getElementById('loadingOverlay');
  200. const prog = document.getElementById('loadingProgress');
  201. const galleryEl = document.getElementById('gallery');
  202. const modal = document.getElementById('video-modal');
  203. const modalPlayer = document.getElementById('modal-player');
  204. const modalCustom = document.getElementById('modal-customname');
  205.  
  206. // State
  207. let names = {};
  208.  
  209. // Helpers: names persistence
  210. function getVideoNames() {
  211. try { return JSON.parse(localStorage.getItem('videoEditableNames') || '{}'); }
  212. catch { return {}; }
  213. }
  214. function setVideoName(video, name) {
  215. names[video] = name;
  216. localStorage.setItem('videoEditableNames', JSON.stringify(names));
  217. }
  218.  
  219. // Progress helpers: clamp and show overlay
  220. function setProgress(pct) {
  221. const v = Math.max(0, Math.min(100, Math.round(pct)));
  222. prog.value = v;
  223. }
  224. function showOverlay(msg) {
  225. overlay.style.display = 'flex';
  226. overlay.querySelector('div').firstChild.nodeValue = msg || 'Loading videos and previews...';
  227. }
  228. function hideOverlay() {
  229. overlay.style.display = 'none';
  230. }
  231.  
  232. // Generate a portrait thumbnail from a frame
  233. function getVideoThumb(src, cb) {
  234. const video = document.createElement('video');
  235. video.src = src;
  236. video.muted = true;
  237. video.playsInline = true;
  238. video.crossOrigin = 'anonymous';
  239. // Seek a bit into the video once metadata is ready to avoid frame 0
  240. const draw = () => {
  241. const canvas = document.createElement('canvas');
  242. canvas.width = 180;
  243. canvas.height = 320;
  244. const ctx = canvas.getContext('2d');
  245. ctx.fillStyle = '#18181c';
  246. ctx.fillRect(0, 0, canvas.width, canvas.height);
  247. try { ctx.drawImage(video, 0, 0, canvas.width, canvas.height); } catch(e){}
  248. cb(canvas.toDataURL());
  249. video.remove();
  250. };
  251. video.addEventListener('loadedmetadata', () => {
  252. // Use min(1s, duration/3) as a safer seek target
  253. let t = Math.min(1, (video.duration || 3) / 3);
  254. // Some browsers need a small delay before setting currentTime
  255. setTimeout(() => {
  256. try { video.currentTime = t; } catch(e) { draw(); }
  257. }, 0);
  258. });
  259. video.addEventListener('seeked', draw);
  260. video.addEventListener('error', () => { cb(''); video.remove(); });
  261. // Attach to DOM only after listeners to avoid GC in some browsers
  262. document.body.appendChild(video);
  263. }
  264.  
  265. // Modal controls
  266. function showModal(src, name) {
  267. modalPlayer.src = src;
  268. modalPlayer.setAttribute('controls', 'controls'); // ensure native controls
  269. modalCustom.textContent = name;
  270. modal.classList.add('active');
  271. modalPlayer.play().catch(()=>{});
  272. }
  273. function closeModal() {
  274. modal.classList.remove('active');
  275. modalPlayer.pause();
  276. modalPlayer.src = '';
  277. }
  278. window.closeModal = closeModal;
  279.  
  280. // Build the grid with progress updates
  281. async function buildGallery(files) {
  282. names = getVideoNames();
  283.  
  284. // Filter playable types; adjust as needed
  285. const vids = files.filter(f => /\.(mp4|webm|mov|mkv|avi|flv)$/i.test(f));
  286. if (vids.length === 0) {
  287. overlay.querySelector('div').innerHTML = 'No videos found.';
  288. setProgress(100);
  289. return;
  290. }
  291.  
  292. galleryEl.innerHTML = '';
  293. // Multi-phase progress: 20% fetch, 75% thumbnails, 5% finalize
  294. // We arrive here with 20% already set by fetchList().
  295. const thumbStart = 20;
  296. const thumbEnd = 95;
  297. const perThumb = (thumbEnd - thumbStart) / vids.length;
  298.  
  299. let loadedThumbs = 0;
  300.  
  301. await Promise.all(vids.map(file => new Promise(resolve => {
  302. const src = SERVER_URL + '/videos/' + encodeURIComponent(file);
  303. const thumbId = 'thumb_' + btoa(file).replace(/[=+]/g, '');
  304. const custom = names[file] || '';
  305.  
  306. galleryEl.insertAdjacentHTML('beforeend', `
  307. <div class="video-icon" id="icon_${thumbId}">
  308. <div class="video-thumb-wrapper" id="${thumbId}">
  309. <img class="video-thumb" src="" alt="Preview" />
  310. <div class="play-overlay">
  311. <svg viewBox="0 0 60 60">
  312. <circle fill="#0007" cx="30" cy="30" r="30"/>
  313. <polygon points="22,17 49,30 22,43" fill="#fff"/>
  314. </svg>
  315. </div>
  316. </div>
  317. <div class="customname">${custom}</div>
  318. <input class="editname" type="text" placeholder="Add name..." value="${custom}">
  319. <button class="edit-btn" style="display:${custom ? 'none' : 'inline-block'};">Save</button>
  320. <div class="filename" style="display:${custom ? 'none' : 'block'};">${file}</div>
  321. </div>
  322. `);
  323.  
  324. // Load thumbnail
  325. getVideoThumb(src, (dataUrl) => {
  326. const img = document.querySelector(`#${thumbId} .video-thumb`);
  327. if (img) img.src = dataUrl || '';
  328. loadedThumbs++;
  329. setProgress(thumbStart + loadedThumbs * perThumb);
  330.  
  331. // Wire up interactions after thumb ready
  332. const wrap = document.getElementById(thumbId);
  333. if (wrap) wrap.onclick = () => showModal(src, names[file] || file);
  334.  
  335. const root = document.getElementById(`icon_${thumbId}`);
  336. const saveBtn = root.querySelector('.edit-btn');
  337. const editInp = root.querySelector('.editname');
  338. const nameEl = root.querySelector('.customname');
  339. const fileEl = root.querySelector('.filename');
  340.  
  341. if (saveBtn && editInp) {
  342. saveBtn.onclick = () => {
  343. const val = editInp.value.trim();
  344. setVideoName(file, val);
  345. nameEl.textContent = val;
  346. // Hide filename and save button if a custom name exists
  347. if (val) {
  348. if (fileEl) fileEl.style.display = 'none';
  349. saveBtn.style.display = 'none';
  350. } else {
  351. if (fileEl) fileEl.style.display = 'block';
  352. saveBtn.style.display = 'inline-block';
  353. }
  354. };
  355. }
  356. resolve();
  357. });
  358. })));
  359.  
  360. // Finalize
  361. setProgress(100);
  362. setTimeout(hideOverlay, 250);
  363. }
  364.  
  365. // Fetch list with deterministic progress
  366. async function fetchList() {
  367. showOverlay('Loading videos and previews...');
  368. setProgress(5);
  369. const res = await fetch(VIDEO_API);
  370. setProgress(15);
  371. const arr = await res.json();
  372. setProgress(20);
  373. return arr;
  374. }
  375.  
  376. // Boot
  377. fetchList()
  378. .then(buildGallery)
  379. .catch(err => {
  380. console.error(err);
  381. overlay.querySelector('div').innerHTML = 'Failed to load videos. Ensure server is running.';
  382. setProgress(100);
  383. });
  384. });
  385. </script>
  386.  
  387.  
  388.  
  389. </body>
  390. </html>
  391.  
  392.  
  393.  
  394.  
  395.  
  396.  
  397. python:
  398.  
  399. import os
  400. import json
  401. from http.server import SimpleHTTPRequestHandler, HTTPServer
  402.  
  403. VIDEO_DIR = r"F:\Don toliver concert"
  404. PORT = 8000
  405.  
  406. class CustomHandler(SimpleHTTPRequestHandler):
  407. def do_GET(self):
  408. if self.path == '/videos':
  409. # List video files in VIDEO_DIR
  410. videos = [f for f in os.listdir(VIDEO_DIR) if f.lower().endswith(('.mp4', '.webm', '.mkv', '.avi', '.mov', '.flv'))]
  411. self.send_response(200)
  412. self.send_header('Content-type', 'application/json')
  413. self.end_headers()
  414. self.wfile.write(json.dumps(videos).encode())
  415. elif self.path.startswith('/videos/'):
  416. # Serve requested video file from VIDEO_DIR
  417. filename = self.path[len('/videos/'):]
  418. full_path = os.path.join(VIDEO_DIR, filename)
  419. if os.path.exists(full_path) and os.path.isfile(full_path):
  420. self.send_response(200)
  421. # Basic mime type detection by extension
  422. if filename.lower().endswith('.mp4'):
  423. self.send_header('Content-type', 'video/mp4')
  424. elif filename.lower().endswith('.webm'):
  425. self.send_header('Content-type', 'video/webm')
  426. elif filename.lower().endswith('.ogg'):
  427. self.send_header('Content-type', 'video/ogg')
  428. else:
  429. self.send_header('Content-type', 'application/octet-stream')
  430. self.end_headers()
  431. with open(full_path, 'rb') as f:
  432. self.wfile.write(f.read())
  433. else:
  434. self.send_error(404, 'File not found')
  435. else:
  436. # Serve normal files for html/js if any in server folder
  437. super().do_GET()
  438.  
  439. httpd = HTTPServer(('localhost', PORT), CustomHandler)
  440. print(f"Serving at http://localhost:{PORT}")
  441. httpd.serve_forever()
  442.  
  443.  
Advertisement
Add Comment
Please, Sign In to add comment