""" Video Display Type Renders video content for display in the annotation interface. Supports video controls and links to video annotation schemas. """ import html from typing import Dict, Any, List from urllib.parse import urlparse from .base import BaseDisplay class VideoDisplay(BaseDisplay): """ Display type for video content. Displays videos with standard HTML5 video player controls. Can be linked to video_annotation schemas via source_field. """ name = "video" required_fields = ["key"] optional_fields = { "max_width": None, "max_height": None, "controls": True, "autoplay": False, "loop": False, "muted": False, "poster": None, } description = "Video player display" supports_span_target = False def render(self, field_config: Dict[str, Any], data: Any) -> str: """ Render a video as HTML. Args: field_config: The field configuration data: The video URL or path Returns: HTML string for the video display """ if not data: return '
No video provided
' # Get the video URL video_url = str(data) # Validate URL if not self._is_valid_url(video_url): return f'
Invalid video URL: {html.escape(video_url)}
' # Get display options options = self.get_display_options(field_config) max_width = options.get("max_width") max_height = options.get("max_height") controls = options.get("controls", True) autoplay = options.get("autoplay", False) loop = options.get("loop", False) muted = options.get("muted", False) poster = options.get("poster") # Build style style_parts = ["max-width: 100%"] if max_width: style_parts.append(f"width: {max_width}px" if isinstance(max_width, int) else f"width: {max_width}") if max_height: style_parts.append(f"max-height: {max_height}px" if isinstance(max_height, int) else f"max-height: {max_height}") style_attr = f' style="{"; ".join(style_parts)}"' # Build attributes attrs = [] if controls: attrs.append("controls") if autoplay: attrs.append("autoplay") if loop: attrs.append("loop") if muted: attrs.append("muted") if poster: attrs.append(f'poster="{html.escape(poster, quote=True)}"') attrs_str = " ".join(attrs) # Escape values escaped_url = html.escape(video_url, quote=True) field_key = html.escape(field_config.get("key", ""), quote=True) # Determine video type from URL video_type = self._get_video_type(video_url) type_attr = f' type="{video_type}"' if video_type else "" return f'''
''' def _is_valid_url(self, url: str) -> bool: """Check if URL is valid.""" if not url: return False if url.startswith('/') or url.startswith('./') or url.startswith('../'): return True try: parsed = urlparse(url) if parsed.scheme: return parsed.scheme.lower() in ('http', 'https') return True except Exception: return False def _get_video_type(self, url: str) -> str: """Get video MIME type from URL extension.""" url_lower = url.lower() if url_lower.endswith('.mp4'): return 'video/mp4' elif url_lower.endswith('.webm'): return 'video/webm' elif url_lower.endswith('.ogg') or url_lower.endswith('.ogv'): return 'video/ogg' elif url_lower.endswith('.mov'): return 'video/quicktime' return "" def get_css_classes(self, field_config: Dict[str, Any]) -> List[str]: """Get CSS classes for the container.""" classes = super().get_css_classes(field_config) return classes def get_data_attributes(self, field_config: Dict[str, Any], data: Any) -> Dict[str, str]: """Get data attributes for the container.""" attrs = super().get_data_attributes(field_config, data) if data: attrs["source-url"] = str(data) return attrs