blender-mask-peoples/panels/vse_panel.py

264 lines
8.0 KiB
Python

"""
VSE Panel for Face Mask controls.
Provides a sidebar panel in the Video Sequence Editor
for controlling mask generation and blur application.
"""
import os
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,
get_detections_path_for_strip,
)
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")
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}")
detections_path = get_detections_path_for_strip(strip.name)
has_mask = bpy.path.abspath(detections_path) and os.path.exists(
bpy.path.abspath(detections_path)
)
if has_mask:
row = box.row()
row.label(text="✓ Detection cache exists", icon='CHECKMARK')
# Generate button
box.operator(
"sequencer.generate_face_mask",
text="Generate Detection Cache" if not has_mask else "Regenerate Cache",
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')
detections_path = get_detections_path_for_strip(strip.name)
has_mask = bpy.path.abspath(detections_path) and os.path.exists(
bpy.path.abspath(detections_path)
)
if not has_mask:
box.label(text="Generate detection cache 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_display_scale")
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)