import os import glob import uvicorn import urllib.parse from urllib.parse import unquote, quote import gradio as gr DEFAULT_DIR = "/bucket_data" # Authenticated WebDAV Base configuration WEBDAV_USER = os.getenv("WEBDAV_USER") WEBDAV_PASS = os.getenv("WEBDAV_PASS") WEBDAV_BASE_URL = f"https://{WEBDAV_USER}:{WEBDAV_PASS}@kphfrepo-webdav.hf.space" def load_live_cctv_dashboard(): try: print("\nā” Rocket-Loading local filesystem assets ...") image_exts = ('.png', '.jpg', '.jpeg', '.webp', '.gif') video_exts = ('.mp4', '.mkv', '.avi', '.mov', '.dav', '.ts') grouped_assets = {} video_count = 0 image_count = 0 for root, dirs, files in os.walk(DEFAULT_DIR): has_video = any(f.lower().endswith(video_exts) for f in files) if has_video: print(f"š„ Video found in '{root}'. Skipping all its subfolders.") dirs.clear() for filename in files: if filename.lower().endswith('.7z') or filename == ".gitattributes": continue f = os.path.join(root, filename) relative_path = os.path.relpath(f, DEFAULT_DIR).replace("\\", "/") if "/" in relative_path: folder_name = relative_path.split("/")[0] else: folder_name = "Root Directory" if folder_name not in grouped_assets: grouped_assets[folder_name] = {"images": [], "videos": []} abs_path = os.path.abspath(f) encoded_path = urllib.parse.quote(abs_path) web_url = f"{encoded_path}" if filename.lower().endswith(video_exts): video_count += 1 grouped_assets[folder_name]["videos"].append({ "name": filename, "video_url": web_url }) elif filename.lower().endswith(image_exts): image_count += 1 grouped_assets[folder_name]["images"].append({ "name": filename, "image_url": web_url }) print(f"ā Chunk Load Finished. Mapped {video_count} videos with URLs natively.\n") return grouped_assets except Exception as e: print(f"ā Chunk Scan Error: {str(e)}") return {} # š ļø JavaScript State Engine + Dynamic Multi-Frame Thumbnail Handler (FIXED URL JOIN) custom_js = """ function() { window.videoTimeRegistry = window.videoTimeRegistry || {}; // 2. Thumbnail Engine utilizing URL path structures safely function initThumbStrip(placeholder) { // 1. Guard Clauses & State Locks if (placeholder.dataset.thumbInit === "1") return; let base = placeholder.getAttribute('data-thumb-base'); if (!base) return; // Ensure the base path ends with a clean slash for asset indexing if (!base.endsWith('/')) { base += '/'; } placeholder.dataset.thumbInit = "1"; placeholder.style.position = "relative"; placeholder.style.overflow = "hidden"; // 2. DOM Setup for Poster Image let thumbImg = placeholder.querySelector('.native-thumb-poster'); if (!thumbImg) { thumbImg = document.createElement('img'); thumbImg.className = 'native-thumb-poster'; thumbImg.style.cssText = "position:absolute; inset:0; width:100%; height:100%; object-fit:cover; z-index:1; pointer-events:none;"; // Handle broken image paths gracefully without breaking layout thumbImg.onerror = () => { console.warn(`ā ļø Thumbnail failed to load at path: ${base}thumbnail1.webp`); thumbImg.style.display = 'none'; placeholder.style.backgroundColor = '#1e293b'; }; placeholder.appendChild(thumbImg); } // Keep play buttons and titles on top of the thumbnail image placeholder.querySelectorAll('span, div').forEach((el) => { if (el !== thumbImg) { el.style.position = "relative"; el.style.zIndex = "2"; } }); // 3. Asset Ingestion & Image Preloading thumbImg.src = base + "thumbnail1.webp"; let currentFrame = 1; let loadedFramesCount = 0; const preloadedImages = []; for (let i = 1; i <= 5; i++) { const img = new Image(); img.onload = () => { loadedFramesCount++; }; // Track successfully loaded frames img.src = base + "thumbnail" + i + ".webp"; preloadedImages.push(img); console.log(`Loaded Thumbnail Path: ${img.src}`); } // 4. Safe Rotator Engine if (placeholder.cycleInterval) clearInterval(placeholder.cycleInterval); placeholder.cycleInterval = setInterval(() => { // Only cycle frames if assets are completing network transit if (loadedFramesCount >= 2) { currentFrame = (currentFrame % 5) + 1; thumbImg.src = base + "thumbnail" + currentFrame + ".webp"; } }, 2000); } // VIEWPORT OBSERVER TO CONSERVE NETWORK TRAFFIC const thumbObserver = new IntersectionObserver((entries) => { entries.forEach((entry) => { const placeholder = entry.target; if (entry.isIntersecting) { initThumbStrip(placeholder); } else { if (placeholder.cycleInterval) { clearInterval(placeholder.cycleInterval); placeholder.cycleInterval = null; } placeholder.dataset.thumbInit = "0"; } }); }, { root: null, threshold: 0.1, rootMargin: "200px 0px" }); function observeThumbPlaceholders() { document.querySelectorAll('.video-placeholder').forEach((el) => { thumbObserver.observe(el); }); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", observeThumbPlaceholders); } else { observeThumbPlaceholders(); } const domObserver = new MutationObserver(() => { observeThumbPlaceholders(); }); domObserver.observe(document.body, { childList: true, subtree: true }); // Video Player Stream Mounting & State Management Toggle document.body.addEventListener('click', function(e) { const placeholder = e.target.closest('.video-placeholder'); if (!placeholder) return; const container = placeholder.parentElement; const videoUrl = placeholder.getAttribute('data-video-url'); if (placeholder.cycleInterval) { clearInterval(placeholder.cycleInterval); } const currentActiveVideo = document.querySelector('.active-dashboard-player'); if (currentActiveVideo) { const activeUrl = currentActiveVideo.getAttribute('data-video-url'); window.videoTimeRegistry[activeUrl] = currentActiveVideo.currentTime; currentActiveVideo.pause(); const activeContainer = currentActiveVideo.parentElement; try { const canvas = document.createElement('canvas'); canvas.width = currentActiveVideo.videoWidth || currentActiveVideo.clientWidth || 420; canvas.height = currentActiveVideo.videoHeight || currentActiveVideo.clientHeight || 250; canvas.style.cssText = "width: 100%; height: 100%; object-fit: contain; position: absolute; top: 0; left: 0; background: #000000;"; const ctx = canvas.getContext('2d'); ctx.drawImage(currentActiveVideo, 0, 0, canvas.width, canvas.height); activeContainer.innerHTML = ''; activeContainer.style.position = 'relative'; activeContainer.appendChild(canvas); } catch(err) { activeContainer.innerHTML = `
`; } currentActiveVideo.removeAttribute('src'); currentActiveVideo.load(); const fakePlaceholder = document.createElement('div'); fakePlaceholder.className = 'video-placeholder'; fakePlaceholder.setAttribute('data-video-url', activeUrl); fakePlaceholder.setAttribute('data-thumb-base', placeholder.getAttribute('data-thumb-base')); fakePlaceholder.style.cssText = "position: absolute; top: 0; left: 0; width: 100%; height: 100%; cursor: pointer; z-index: 10; display: flex; align-items: center; justify-content: center; color: white;"; fakePlaceholder.innerHTML = `ā¶ļø`; activeContainer.appendChild(fakePlaceholder); } const savedTime = window.videoTimeRegistry[videoUrl] || 0; container.style.position = 'relative'; container.innerHTML = ` `; const newVideoPlayer = container.querySelector('video'); newVideoPlayer.addEventListener('loadedmetadata', function() { if (savedTime > 0) { newVideoPlayer.currentTime = savedTime; } }); }); } """ custom_img_js = """""" gr.set_static_paths(paths=[DEFAULT_DIR]) with gr.Blocks(title="CCTV Ultimate Explorer", theme=gr.themes.Soft(), js=custom_js) as demo: init_trigger = gr.HTML(custom_img_js, visible=True) gr.Markdown("# š¹ CCTV Structured Media Monitor (Instant Chunk Thumbnail View)") refresh_btn = gr.Button("š Sync Latest Camera Footage", variant="primary") @gr.render(inputs=refresh_btn, triggers=[demo.load, refresh_btn.click]) def render_ui(*args): print("\n--- [RENDER START] ---") assets = load_live_cctv_dashboard() if not assets: gr.Markdown("### ā No media data discovered inside the local folders.") return html_buffer = [] for folder, media in assets.items(): html_buffer.append(f"""{folder}
*No video tracks detected.*
') if media["images"]: html_buffer.append('