""" VSE Panel for Face Mask controls. Provides a sidebar panel in the Video Sequence Editor for controlling mask generation and blur application. """ import bpy from bpy.types import Panel from ..core.async_bake_generator import get_bake_generator from ..core.async_generator import get_generator from ..core.utils import get_server_status, get_cache_info, format_size class SEQUENCER_PT_face_mask(Panel): """Panel for face mask blur controls.""" bl_label = "Face Mask" bl_idname = "SEQUENCER_PT_face_mask" bl_space_type = 'SEQUENCE_EDITOR' bl_region_type = 'UI' bl_category = "Face Mask" def draw(self, context): layout = self.layout scene = context.scene wm = context.window_manager seq_editor = context.scene.sequence_editor # Note: Blender 5.0 uses 'strips' instead of 'sequences' generator = get_generator() bake_generator = get_bake_generator() # Show progress if generating masks if generator.is_running: self._draw_progress(layout, wm, generator) return # Show progress if baking blur if bake_generator.is_running: self._draw_bake_progress(layout, wm, bake_generator) return # Show primary controls first (top priority in UI) if seq_editor and seq_editor.active_strip: strip = seq_editor.active_strip if strip.type in {'MOVIE', 'IMAGE'}: self._draw_generation_controls(layout, context, strip) self._draw_blur_controls(layout, context, strip) else: layout.label(text="Select a video or image strip") else: layout.label(text="No strip selected") layout.separator() # Secondary sections self._draw_parameters(layout, scene) self._draw_server_status(layout) self._draw_cache_info(layout, context, seq_editor) def _draw_parameters(self, layout, scene): """Draw detection parameters.""" box = layout.box() box.label(text="Parameters", icon='PREFERENCES') col = box.column(align=True) col.prop(scene, "facemask_conf_threshold") col.prop(scene, "facemask_iou_threshold") col.prop(scene, "facemask_mask_scale") def _draw_server_status(self, layout): """Draw server status and GPU info.""" box = layout.box() box.label(text="Server Status", icon='SYSTEM') status = get_server_status() # Server status row = box.row() if status['running']: row.label(text="Server:", icon='CHECKMARK') row.label(text="Running") else: row.label(text="Server:", icon='ERROR') row.label(text="Stopped") # GPU status if status['running']: row = box.row() if status['gpu_available']: row.label(text="GPU:", icon='CHECKMARK') gpu_name = status['gpu_device'] or "Available" # Truncate long GPU names if len(gpu_name) > 25: gpu_name = gpu_name[:22] + "..." row.label(text=gpu_name) else: row.label(text="GPU:", icon='ERROR') row.label(text="Not Available") def _draw_cache_info(self, layout, context, seq_editor): """Draw cache information and clear button.""" box = layout.box() box.label(text="Cache", icon='FILE_CACHE') # Get cache info if seq_editor and seq_editor.active_strip: strip_name = seq_editor.active_strip.name cache_path, total_size, file_count = get_cache_info(strip_name) else: cache_path, total_size, file_count = get_cache_info() # Cache info row = box.row() row.label(text="Size:") row.label(text=format_size(total_size)) row = box.row() row.label(text="Files:") row.label(text=str(file_count)) # Cache directory setting box.prop(context.scene, "facemask_cache_dir") # Clear cache buttons row = box.row(align=True) if seq_editor and seq_editor.active_strip: op = row.operator( "sequencer.clear_mask_cache", text="Clear Strip Cache", icon='TRASH', ) op.all_strips = False op = row.operator( "sequencer.clear_mask_cache", text="Clear All", icon='TRASH', ) op.all_strips = True def _draw_progress(self, layout, wm, generator): """Draw progress bar during generation.""" box = layout.box() box.label(text="Generating Masks...", icon='RENDER_ANIMATION') # Progress bar progress = wm.mask_progress / max(wm.mask_total, 1) box.progress( factor=progress, text=f"Frame {wm.mask_progress} / {wm.mask_total}", ) # Cancel button box.operator( "sequencer.cancel_mask_generation", text="Cancel", icon='CANCEL', ) def _draw_bake_progress(self, layout, wm, generator): """Draw progress bar during blur bake.""" box = layout.box() box.label(text="Baking Blur...", icon='RENDER_ANIMATION') progress = wm.bake_progress / max(wm.bake_total, 1) box.progress( factor=progress, text=f"Frame {wm.bake_progress} / {wm.bake_total}", ) box.operator( "sequencer.cancel_bake_blur", text="Cancel", icon='CANCEL', ) def _draw_generation_controls(self, layout, context, strip): """Draw mask generation controls.""" box = layout.box() box.label(text="Mask Generation", icon='MOD_MASK') # Info about selected strip row = box.row() row.label(text=f"Strip: {strip.name}") # Check for existing mask seq_editor = context.scene.sequence_editor mask_name = f"{strip.name}_mask" has_mask = mask_name in seq_editor.strips if has_mask: row = box.row() row.label(text="✓ Mask exists", icon='CHECKMARK') # Generate button op = box.operator( "sequencer.generate_face_mask", text="Generate Face Mask" if not has_mask else "Regenerate Mask", icon='FACE_MAPS', ) def _draw_blur_controls(self, layout, context, strip): """Draw blur application controls.""" box = layout.box() box.label(text="Blur Bake", icon='MATFLUID') # Check for mask strip seq_editor = context.scene.sequence_editor mask_name = f"{strip.name}_mask" has_mask = mask_name in seq_editor.strips if not has_mask: box.label(text="Generate a mask first", icon='INFO') return # Bake parameters col = box.column(align=True) col.prop(context.scene, "facemask_bake_blur_size") col.prop(context.scene, "facemask_bake_format") # Source status source_mode = strip.get("facemask_source_mode", "original") if source_mode == "baked": box.label(text="Source: Baked", icon='CHECKMARK') else: box.label(text="Source: Original", icon='FILE_MOVIE') # Bake and restore buttons box.operator( "sequencer.bake_and_swap_blur_source", text="Bake & Swap Source", icon='RENDER_STILL', ) box.operator( "sequencer.restore_original_source", text="Restore Original Source", icon='LOOP_BACK', ) # Registration classes = [ SEQUENCER_PT_face_mask, ] def register(): for cls in classes: bpy.utils.register_class(cls) def unregister(): for cls in reversed(classes): bpy.utils.unregister_class(cls)