Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
| import gradio as gr | |
| import logging | |
| import json | |
| import os | |
| from typing import Dict, Any, List | |
| from itertools import groupby | |
| # Configure logging | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| video_folder = 'video/' | |
| metadata_folder = 'metadata/' | |
| def load_video_list() -> List[Dict[str, str]]: | |
| video_list = [] | |
| for filename in os.listdir(video_folder): | |
| if filename.endswith('.mp4'): | |
| video_id = os.path.splitext(filename)[0] | |
| metadata_path = os.path.join(metadata_folder, f"{video_id}.json") | |
| if os.path.exists(metadata_path): | |
| with open(metadata_path, 'r') as f: | |
| metadata = json.load(f) | |
| metadata = metadata['content_metadata'] | |
| title = metadata.get('title', 'Untitled') | |
| video_list.append({"video_id": video_id, "title": title}) | |
| # Define the custom order for the first five videos | |
| custom_order = ['7BhJmDPB7RU', 'PrAwsi3Ldzo', '3rhsSPxQ39c', 'P7WnJZ55sgc', 'g9GtUQs7XUM'] | |
| # Custom sorting function | |
| def custom_sort(item): | |
| try: | |
| return custom_order.index(item['video_id']) | |
| except ValueError: | |
| return len(custom_order) + 1 # Place non-specified videos after the custom ordered ones | |
| # Sort the video list | |
| video_list.sort(key=lambda x: (custom_sort(x), x['title'])) | |
| return video_list | |
| def score_to_emoji(score): | |
| if score < 0.2: | |
| return "😴" | |
| elif score < 0.4: | |
| return "🙂" | |
| elif score < 0.6: | |
| return "😊" | |
| elif score < 0.8: | |
| return "😃" | |
| else: | |
| return "🤩" | |
| def load_metadata(video_id: str) -> Dict[str, Any]: | |
| metadata_path = os.path.join(metadata_folder, f"{video_id}.json") | |
| try: | |
| with open(metadata_path, 'r') as f: | |
| asd =json.load(f) | |
| return asd['content_metadata'] | |
| except FileNotFoundError: | |
| logger.error(f"Metadata file not found for video ID: {video_id}") | |
| raise | |
| except json.JSONDecodeError: | |
| logger.error(f"Invalid JSON in metadata file for video ID: {video_id}") | |
| raise | |
| def timestamp_to_seconds(timestamp: str) -> float: | |
| try: | |
| h, m, s = timestamp.split(':') | |
| return int(h) * 3600 + int(m) * 60 + float(s) | |
| except ValueError: | |
| logger.error(f"Invalid timestamp format: {timestamp}") | |
| return 0.0 | |
| def format_timestamp(timestamp: str) -> str: | |
| try: | |
| h, m, s = timestamp.split(':') | |
| return f"{int(m):02d}:{int(float(s)):02d}" | |
| except Exception as e: | |
| logger.error(f"Invalid timestamp format: {timestamp}") | |
| return "" | |
| def create_scene_table(scene: Dict[str, Any]) -> str: | |
| dynamism_score = scene.get('dynamismScore', 0) | |
| av_correlation = scene.get('audioVisualCorrelation', 0) | |
| cast = ", ".join([cast_member for cast_member in scene.get('cast', [])]) | |
| output = f""" | |
| <div class="scene-container"> | |
| <h3>Scene {scene.get('sceneId', 'Unknown')}: {scene.get('title', '')}</h3> | |
| <p>Dynamism: {score_to_emoji(dynamism_score)} Audio-visual correlation: {score_to_emoji(av_correlation)} Cast: {cast}</p> | |
| <table class="metadata-table"> | |
| <tr> | |
| <th>Timestamp</th> | |
| <th>Type</th> | |
| <th>Description</th> | |
| </tr> | |
| """ | |
| scene_events = [] | |
| # Collect all scene data | |
| data_types = [ | |
| ('Activities', scene.get('activities', [])), | |
| ('Props', scene.get('props', [])), | |
| ('Mood', [scene.get('mood', {})]), | |
| ('Narrative Progression', scene.get('narrativeProgression', [])), | |
| ('Video Editing Details', scene.get('videoEditingDetails', [])), | |
| ('Thematic Elements', [{'description': scene.get('thematicElements', '')}]), | |
| ('Contextual Relevance', [{'description': scene.get('contextualRelevance', '')}]), | |
| ('Character Interaction', scene.get('characterInteraction', [])) | |
| ] | |
| for data_type, data_list in data_types: | |
| for item in data_list: | |
| if isinstance(item, dict): | |
| start_time = '' | |
| end_time = '' | |
| description = '' | |
| if data_type == 'Activities': | |
| start_time = item.get('timestamp', {}).get('start_timestamp', '') | |
| end_time = item.get('timestamp', {}).get('end_timestamp', '') | |
| description = item.get('description', '') | |
| elif data_type == 'Props': | |
| start_time = item.get('timestamp', {}).get('start_timestamp', '') | |
| end_time = item.get('timestamp', {}).get('end_timestamp', '') | |
| description = item.get('name', '') | |
| elif data_type == 'Video Editing Details': | |
| start_time = item.get('timestamps', {}).get('start_timestamp', '') | |
| end_time = item.get('timestamps', {}).get('end_timestamp', '') | |
| description = item.get('description', '') | |
| elif data_type == 'Mood': | |
| description = item.get('description', '') | |
| # Handle mood changes | |
| for mood_change in item.get('keyMoments', []): | |
| if isinstance(mood_change, dict): | |
| scene_events.append({ | |
| 'timestamp_start': mood_change.get('timestamp', ''), | |
| 'timestamp_end': '', | |
| 'type': 'Mood Change', | |
| 'description': mood_change.get('changeDescription', '') | |
| }) | |
| elif data_type == 'Character Interaction': | |
| characters = ', '.join(item.get('characters', [])) | |
| description = f"{characters}: {item.get('description', '')}" | |
| else: | |
| start_time = item.get('timestamp', '') | |
| description = item.get('description', '') | |
| scene_events.append({ | |
| 'timestamp_start': start_time, | |
| 'timestamp_end': end_time, | |
| 'type': data_type, | |
| 'description': description | |
| }) | |
| elif isinstance(item, str): | |
| scene_events.append({ | |
| 'timestamp_start': '', | |
| 'timestamp_end': '', | |
| 'type': data_type, | |
| 'description': item | |
| }) | |
| # Sort events by timestamp | |
| scene_events.sort(key=lambda x: x['timestamp_start'] if x['timestamp_start'] else '') | |
| for event in scene_events: | |
| start_time = format_timestamp(event['timestamp_start']) | |
| end_time = format_timestamp(event['timestamp_end']) | |
| start_link = f'<a href="#" class="timestamp-link" data-timestamp="{event["timestamp_start"]}">{start_time}</a>' if start_time else '' | |
| end_link = f' - <a href="#" class="timestamp-link" data-timestamp="{event["timestamp_end"]}">{end_time}</a>' if end_time else '' | |
| output += f""" | |
| <tr> | |
| <td>{start_link}{end_link}</td> | |
| <td>{event['type']}</td> | |
| <td>{event['description']}</td> | |
| </tr> | |
| """ | |
| output += """ | |
| </table> | |
| </div> | |
| """ | |
| return output | |
| def create_storylines_table(storylines: Dict[str, Any]) -> str: | |
| output = """ | |
| <div class="storylines-container"> | |
| <h3>Storylines</h3> | |
| <table class="metadata-table"> | |
| <tr> | |
| <th>Storyline</th> | |
| <th>Scenes Involved</th> | |
| </tr> | |
| """ | |
| output += f""" | |
| <tr> | |
| <td>{storylines.get('description', 'No description available')}</td> | |
| <td>{', '.join(map(str, storylines.get('scenes', [])))}</td> | |
| </tr> | |
| """ | |
| output += """ | |
| </table> | |
| </div> | |
| """ | |
| return output | |
| def create_qa_section(qa_list: List[Dict[str, str]]) -> str: | |
| output = """ | |
| <br> | |
| <div class="qa-container"> | |
| <h3>Q&A</h3> | |
| <div class="chat-discussion"> | |
| """ | |
| for qa in qa_list: | |
| output += f""" | |
| <div class="question">{qa.get('question', '')}</div> | |
| <div class="answer">{qa.get('answer', '')}</div> | |
| """ | |
| output += """ | |
| </div> | |
| </div> | |
| """ | |
| return output | |
| def create_trimming_suggestions(suggestions: List[Dict[str, Any]]) -> str: | |
| output = """ | |
| <br> | |
| <div class="trimming-suggestions-container"> | |
| <h3>Trimming Suggestions</h3> | |
| <table class="metadata-table"> | |
| <tr> | |
| <th>Timestamp</th> | |
| <th>Description</th> | |
| </tr> | |
| """ | |
| for suggestion in suggestions: | |
| start_time = suggestion.get('timestamps', {}).get('start_timestamp', '') | |
| end_time = suggestion.get('timestamps', {}).get('end_timestamp', '') | |
| start_formatted = format_timestamp(start_time) | |
| end_formatted = format_timestamp(end_time) | |
| output += f""" | |
| <tr> | |
| <td> | |
| <a href="#" class="timestamp-link" data-timestamp="{start_time}">{start_formatted}</a> | |
| {f' - <a href="#" class="timestamp-link" data-timestamp="{end_time}">{end_formatted}</a>' if end_time else ''} | |
| </td> | |
| <td>{suggestion.get('description', '')}</td> | |
| </tr> | |
| """ | |
| output += """ | |
| </table> | |
| </div> | |
| """ | |
| return output | |
| def create_filmstrip(scenes: List[Dict[str, Any]], video_duration: float) -> str: | |
| filmstrip_html = f""" | |
| <div id="filmstrip-inner" style="position: relative; width: 100%; height: 100%;" data-duration="{video_duration}"> | |
| """ | |
| for scene in scenes: | |
| start_time = timestamp_to_seconds(scene['timestamps'].get('start_timestamp', '0:00:00')) | |
| end_time = timestamp_to_seconds(scene['timestamps'].get('end_timestamp', str(video_duration))) | |
| left_pos = (start_time / video_duration) * 100 | |
| width = ((end_time - start_time) / video_duration) * 100 | |
| title = scene.get('title', '') | |
| filmstrip_html += f''' | |
| <div class="scene-marker" style="position: absolute; left: {left_pos}%; width: {width}%; height: 100%; background-color: rgba(0, 0, 255, 0.2); border-right: 1px solid blue; overflow: hidden;"> | |
| <div class="scene-title" style="font-size: 10px; word-wrap: break-word; padding: 2px;">{title}</div> | |
| </div> | |
| ''' | |
| filmstrip_html += """ | |
| <div id="scrubbing-needle" style="position: absolute; width: 2px; height: 100%; background-color: red; top: 0; left: 0; pointer-events: none;"></div> | |
| </div> | |
| """ | |
| return filmstrip_html | |
| def process_video(video_id: str): | |
| try: | |
| logger.info(f"Processing video with ID: {video_id}") | |
| metadata = load_metadata(video_id) | |
| # Always use the test URL instead of the actual video file | |
| video_url = f"https://huggingface.co/spaces/HuggingFaceFV/FineVideo-Explorer/resolve/main/video/{video_id}.mp4" | |
| # Create HTML for video player | |
| video_html = f""" | |
| <div id="video-wrapper"> | |
| <video id="video-player" controls> | |
| <source src="{video_url}" type="video/mp4"> | |
| Your browser does not support the video tag. | |
| </video> | |
| </div> | |
| """ | |
| # Character List Table | |
| character_table = """ | |
| <h3>Characters</h3> | |
| <table class="metadata-table"> | |
| <tr> | |
| <th>Character</th> | |
| <th>Description</th> | |
| </tr> | |
| """ | |
| for character in metadata.get('characterList', []): | |
| character_table += f""" | |
| <tr> | |
| <td>{character.get('name', '')}</td> | |
| <td>{character.get('description', '')}</td> | |
| </tr> | |
| """ | |
| character_table += "</table>" | |
| additional_data = f""" | |
| <div class="video-info"> | |
| <h2>{metadata.get('title', 'Untitled')}</h2> | |
| <p><strong>Description:</strong> {metadata.get('description', 'No description available')}</p> | |
| </div> | |
| {character_table} | |
| """ | |
| scenes_output = "" | |
| for scene in metadata.get('scenes', []): | |
| scenes_output += create_scene_table(scene) | |
| storylines_output = create_storylines_table(metadata.get('storylines', {})) | |
| qa_output = create_qa_section(metadata.get('qAndA', [])) | |
| trimming_suggestions_output = create_trimming_suggestions(metadata.get('trimmingSuggestions', [])) | |
| # Generate filmstrip HTML | |
| last_scene = metadata['scenes'][-1] | |
| video_duration = timestamp_to_seconds(last_scene['timestamps'].get('end_timestamp', '0:00:00')) | |
| filmstrip_html = create_filmstrip(metadata['scenes'], video_duration) | |
| logger.info("Video processing completed successfully") | |
| return video_html, filmstrip_html, additional_data + scenes_output + storylines_output + qa_output + trimming_suggestions_output | |
| except Exception as e: | |
| logger.exception(f"Error processing video: {str(e)}") | |
| return None, "", f"Error processing video: {str(e)}" | |
| css = """ | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| # flex: 0!important; | |
| } | |
| html, body, gradio-app { | |
| height: 100%; | |
| min-height: unset!important; | |
| max-height: unset!important; | |
| display: block!important; | |
| } | |
| .main { | |
| flex-grow: 1; | |
| flex-shrink: 1; | |
| overflow: hidden; | |
| } | |
| .main .wrap .contain { | |
| flex-grow: 1; | |
| flex-shrink: 1; | |
| overflow: hidden; | |
| } | |
| #component-0 { | |
| overflow: hidden; | |
| } | |
| # .app { | |
| # overflow: hidden; | |
| # } | |
| #top-panel { | |
| flex-shrink: 0; | |
| } | |
| body { | |
| margin: 0; | |
| padding: 0; | |
| font-family: Arial, sans-serif; | |
| overflow: hidden; | |
| } | |
| .container { | |
| display: flex; | |
| flex-direction: column; | |
| height: 100vh; | |
| } | |
| #header { | |
| display: flex; | |
| align-items: center; | |
| padding: 10px; | |
| background-color: var(--background-fill-secondary); | |
| } | |
| #logo { | |
| width: auto; | |
| height: 150px; | |
| box-shadow: none !important; | |
| border: none !important; | |
| background: none !important; | |
| object-fit: contain; | |
| } | |
| #header-content { | |
| flex-grow: 1; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| #header-content h1 { | |
| margin: 0; | |
| font-size: 36px; | |
| font-weight: bold; | |
| } | |
| #header-content a { | |
| font-size: 18px; | |
| color: #0066cc; | |
| text-decoration: none; | |
| } | |
| #header-content a:hover { | |
| text-decoration: underline; | |
| } | |
| #top-panel { | |
| height: 33vh; | |
| display: flex; | |
| padding: 10px; | |
| box-shadow: 0 2px 5px rgba(0,0,0,0.1); | |
| overflow: hidden; | |
| } | |
| #video-list-column { | |
| max-height: 80vh; /* Adjust as needed */ | |
| overflow-y: auto; | |
| height: 100%; | |
| } | |
| #video-column { | |
| width: 70%; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| #video-wrapper { | |
| flex-grow: 1; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| overflow: hidden; | |
| } | |
| #video-player { | |
| width: 100%; | |
| max-height: calc(33vh - 120px) !important; | |
| } | |
| #filmstrip-container { | |
| width: 100%; | |
| height: 80px !important; | |
| background-color: var(--background-fill-secondary); | |
| position: relative; | |
| overflow: hidden; | |
| cursor: pointer; | |
| } | |
| #filmstrip-container > div, | |
| #filmstrip-container > div > div, | |
| #filmstrip-container > div > div > div { | |
| height: 100% !important; | |
| } | |
| #scrollable-content { | |
| overflow-y: auto; | |
| padding: 20px; | |
| } | |
| #metadata-container { | |
| margin-top: 20px; | |
| } | |
| .content-samples { | |
| display: flex; | |
| flex-direction: column; | |
| overflow-y: auto; | |
| max-height: 100%; | |
| } | |
| .content-samples > .wrap { | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .content-samples .hidden { | |
| display: none !important; | |
| } | |
| .content-samples > .wrap > .wrap { | |
| display: flex !important; | |
| flex-direction: column !important; | |
| } | |
| .content-samples label { | |
| display: block; | |
| padding: 10px; | |
| cursor: pointer; | |
| border-bottom: 1px solid #ddd; | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| } | |
| .content-samples label:hover { | |
| background-color: #f0f0f0; | |
| } | |
| .video-info { | |
| margin-bottom: 20px; | |
| } | |
| .scene-container { | |
| margin-bottom: 30px; | |
| } | |
| .metadata-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| margin-bottom: 20px; | |
| } | |
| .metadata-table th, .metadata-table td { | |
| border: 1px solid #ddd; | |
| padding: 8px; | |
| text-align: left; | |
| } | |
| .metadata-table th { | |
| background-color: var(--background-fill-secondary); | |
| } | |
| .metadata-table tr:nth-child(even) { | |
| background-color: var(--table-even-background-fill); | |
| } | |
| .metadata-table tr:nth-child(odd) { | |
| background-color: var(--table-odd-background-fill); | |
| } | |
| .timestamp-link { | |
| color: #0066cc; | |
| text-decoration: none; | |
| cursor: pointer; | |
| } | |
| .timestamp-link:hover { | |
| text-decoration: underline; | |
| } | |
| .chat-discussion { | |
| background-color: var(--background-fill-secondary); | |
| border-radius: 10px; | |
| padding: 15px; | |
| margin-bottom: 20px; | |
| } | |
| .question { | |
| font-weight: bold; | |
| margin-bottom: 5px; | |
| } | |
| .answer { | |
| margin-bottom: 15px; | |
| padding-left: 15px; | |
| } | |
| .correlation-scores { | |
| font-size: 18px; | |
| margin-bottom: 20px; | |
| } | |
| #reinitialization-overlay { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-color: rgba(0, 0, 0, 0.5); | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 9999; | |
| color: white; | |
| font-size: 24px; | |
| font-weight: bold; | |
| } | |
| @media (max-width: 768px) { | |
| #header { | |
| flex-direction: column; | |
| align-items: flex-start; | |
| flex-wrap: nowrap; | |
| overflow: hidden; | |
| max-height: 25vh; | |
| flex-shrink: 0; | |
| } | |
| #header-content h1 { | |
| font-size: 24px; | |
| } | |
| #header-content p { | |
| font-size: 14px; | |
| } | |
| #logo { | |
| align-self: flex-end; | |
| margin-top: 10px; | |
| position: absolute; | |
| top: 12px; | |
| right: -24px; | |
| height: auto!important; | |
| width: 150px; | |
| min-width: 0!important; | |
| margin:0!important; | |
| } | |
| #logo .image-container { | |
| height: auto!important; | |
| } | |
| .image-frame { | |
| height: auto!important; | |
| } | |
| #top-panel { | |
| flex-direction: column; | |
| height: auto; | |
| flex-shrink: 1; | |
| flex-grow: 1; | |
| min-height: 50vh; | |
| } | |
| #video-list-column, #video-column { | |
| width: 100%; | |
| } | |
| #video-list-column { | |
| flex-grow: 7!important; | |
| } | |
| } | |
| .icon-buttons button { | |
| display: none !important; | |
| } | |
| /* Ensure one element per row in Gradio list */ | |
| #video-list-column .wrap { | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| #video-list-column .wrap > .wrap { | |
| display: flex !important; | |
| flex-direction: column !important; | |
| } | |
| #video-list-column label { | |
| display: block; | |
| width: 100%; | |
| } | |
| """ | |
| js = """ | |
| <script> | |
| // Wrap everything in an IIFE to avoid polluting the global scope | |
| (function() { | |
| function safeLog(message) { | |
| if (typeof console !== 'undefined' && console.log) { | |
| console.log(message); | |
| } | |
| } | |
| function findFilmstripInner(container) { | |
| if (container.id === 'filmstrip-inner') { | |
| return container; | |
| } | |
| for (let child of container.children) { | |
| let result = findFilmstripInner(child); | |
| if (result) { | |
| return result; | |
| } | |
| } | |
| return null; | |
| } | |
| function initializeFilmstrip() { | |
| //safeLog("Initializing filmstrip..."); | |
| var videoElement = document.querySelector('video'); | |
| var filmstripContainer = document.getElementById('filmstrip-container'); | |
| var filmstripInner = findFilmstripInner(filmstripContainer); | |
| var scrubbingNeedle = document.getElementById('scrubbing-needle'); | |
| if (!videoElement || !filmstripContainer || !filmstripInner || !scrubbingNeedle) { | |
| //safeLog("Required elements not found for filmstrip"); | |
| return; | |
| } | |
| var videoDuration = parseFloat(filmstripInner.getAttribute('data-duration') || videoElement.duration); | |
| videoElement.addEventListener('timeupdate', function() { | |
| var progress = videoElement.currentTime / videoDuration; | |
| scrubbingNeedle.style.left = (progress * 100) + '%'; | |
| }); | |
| filmstripContainer.addEventListener('click', function(event) { | |
| var rect = filmstripContainer.getBoundingClientRect(); | |
| var clickPosition = (event.clientX - rect.left) / rect.width; | |
| videoElement.currentTime = clickPosition * videoDuration; | |
| }); | |
| //safeLog("Filmstrip initialization complete"); | |
| } | |
| function initializeTimestampLinks() { | |
| //safeLog("Initializing timestamp links..."); | |
| var videoElement = document.querySelector('video'); | |
| var links = document.querySelectorAll('.timestamp-link'); | |
| if (!videoElement) { | |
| //safeLog("Video element not found for timestamp links"); | |
| return; | |
| } | |
| if (links.length === 0) { | |
| //safeLog("No timestamp links found"); | |
| return; | |
| } | |
| links.forEach(function(link) { | |
| link.addEventListener('click', function(e) { | |
| e.preventDefault(); | |
| var timestamp = this.getAttribute('data-timestamp'); | |
| //safeLog("Timestamp link clicked: " + timestamp); | |
| var parts = timestamp.split(':'); | |
| var seconds = parseInt(parts[0], 10) * 3600 + parseInt(parts[1], 10) * 60 + parseFloat(parts[2]); | |
| videoElement.currentTime = seconds; | |
| }); | |
| }); | |
| //safeLog("Timestamp links initialization complete"); | |
| } | |
| let isReinitializing = false; | |
| function showOverlay() { | |
| let overlay = document.getElementById('reinitialization-overlay'); | |
| if (!overlay) { | |
| overlay = document.createElement('div'); | |
| overlay.id = 'reinitialization-overlay'; | |
| overlay.style.position = 'fixed'; | |
| overlay.style.top = '0'; | |
| overlay.style.left = '0'; | |
| overlay.style.width = '100%'; | |
| overlay.style.height = '100%'; | |
| overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'; | |
| overlay.style.display = 'flex'; | |
| overlay.style.justifyContent = 'center'; | |
| overlay.style.alignItems = 'center'; | |
| overlay.style.zIndex = '9999'; | |
| const message = document.createElement('div'); | |
| message.textContent = 'Loading assets...'; | |
| message.style.color = 'white'; | |
| message.style.fontSize = '24px'; | |
| message.style.fontWeight = 'bold'; | |
| overlay.appendChild(message); | |
| document.body.appendChild(overlay); | |
| } | |
| overlay.style.display = 'flex'; | |
| } | |
| function hideOverlay() { | |
| const overlay = document.getElementById('reinitialization-overlay'); | |
| if (overlay) { | |
| overlay.style.display = 'none'; | |
| } | |
| } | |
| function initializeEverything() { | |
| if (isReinitializing) { | |
| //safeLog("Already reinitializing, skipping..."); | |
| return; | |
| } | |
| isReinitializing = true; | |
| showOverlay(); | |
| //safeLog("Initializing everything..."); | |
| try { | |
| initializeFilmstrip(); | |
| initializeTimestampLinks(); | |
| //safeLog("Initialization complete"); | |
| } catch (error) { | |
| //safeLog("Error during initialization: " + error.message); | |
| } finally { | |
| isReinitializing = false; | |
| hideOverlay(); | |
| } | |
| } | |
| let lastVideoSelection = null; | |
| function checkVideoSelection() { | |
| const videoList = document.getElementById('video-list'); | |
| if (videoList) { | |
| const currentSelection = videoList.querySelector('input:checked'); | |
| if (currentSelection && currentSelection.value !== lastVideoSelection) { | |
| //safeLog("Video selection changed, reinitializing..."); | |
| lastVideoSelection = currentSelection.value; | |
| showOverlay(); | |
| setTimeout(() => { | |
| initializeEverything(); | |
| hideOverlay(); | |
| }, 1000); // Delay to ensure new video is loaded | |
| } | |
| } | |
| } | |
| // Set up a MutationObserver to watch for changes in the entire document | |
| const contentObserver = new MutationObserver((mutations) => { | |
| mutations.forEach((mutation) => { | |
| if (mutation.type === 'childList' || mutation.type === 'attributes') { | |
| checkVideoSelection(); | |
| if (mutation.target.id === 'video-container' || | |
| mutation.target.id === 'filmstrip-container' || | |
| mutation.target.id === 'metadata-container') { | |
| //safeLog("Relevant content updated, reinitializing..."); | |
| setTimeout(initializeEverything, 100); // Small delay to ensure elements are ready | |
| } | |
| } | |
| }); | |
| }); | |
| contentObserver.observe(document.body, { | |
| childList: true, | |
| subtree: true, | |
| attributes: true, | |
| attributeFilter: ['value', 'checked'] | |
| }); | |
| // Function to set up Gradio event listeners | |
| function setupGradioEventListeners() { | |
| if (typeof gradio !== 'undefined') { | |
| //safeLog("Setting up Gradio event listeners..."); | |
| gradio('#video-list').change(function(evt) { | |
| //safeLog("Gradio detected video selection change, reinitializing..."); | |
| setTimeout(initializeEverything, 1000); | |
| }); | |
| } /*else { | |
| safeLog("Gradio not found, using fallback method"); | |
| }*/ | |
| } | |
| // Periodically check for video selection changes | |
| setInterval(checkVideoSelection, 1000); | |
| // Initialize everything when the DOM is ready | |
| document.addEventListener('DOMContentLoaded', function() { | |
| initializeEverything(); | |
| setupGradioEventListeners(); | |
| checkVideoSelection(); | |
| }); | |
| // Also try to initialize after a short delay, in case DOMContentLoaded has already fired | |
| setTimeout(function() { | |
| initializeEverything(); | |
| setupGradioEventListeners(); | |
| checkVideoSelection(); | |
| }, 1000); | |
| })(); | |
| </script> | |
| """ | |
| with gr.Blocks(css=css, head=js) as iface: | |
| with gr.Row(elem_id="header"): | |
| with gr.Column(scale=1): | |
| gr.Image("logo.png", elem_id="logo", show_label=False, interactive=False) | |
| gr.Markdown("### Click a title to dive into the data:") | |
| with gr.Column(elem_id="header-content", scale=10): | |
| gr.Markdown(""" | |
| # Exploration page | |
| ## [🔗 Dataset](https://huggingface.co/datasets/HuggingFaceFV/finevideo) | |
| """) | |
| with gr.Row(elem_id="top-panel"): | |
| with gr.Column(scale=3, elem_id="video-list-column"): | |
| video_list_data = load_video_list() | |
| video_list = gr.Radio( | |
| label="Content Samples", | |
| choices=[video["title"] for video in video_list_data], | |
| elem_id="video-list", | |
| value=None, | |
| container=False | |
| ) | |
| with gr.Column(scale=7, elem_id="video-column"): | |
| video_output = gr.HTML(elem_id="video-container") | |
| filmstrip_output = gr.HTML(elem_id="filmstrip-container") | |
| with gr.Row(elem_id="scrollable-content"): | |
| metadata_output = gr.HTML(elem_id="metadata-container") | |
| def wrapped_process_video(title: str) -> tuple: | |
| if not title: | |
| return "", "", "" | |
| video_id = next(video["video_id"] for video in video_list_data if video["title"] == title) | |
| logging.info(f"Processing video with ID: {video_id}") | |
| video_html, filmstrip_html, metadata_html = process_video(video_id) | |
| return video_html, filmstrip_html, metadata_html | |
| video_list.change( | |
| fn=wrapped_process_video, | |
| inputs=[video_list], | |
| outputs=[video_output, filmstrip_output, metadata_output] | |
| ) | |
| if __name__ == "__main__": | |
| iface.launch() | |