""" Apply Blur Operator for masked face blur in VSE. Provides operators to apply blur effects using mask strips generated by the face detection operators. """ import bpy from bpy.props import FloatProperty, IntProperty, StringProperty from bpy.types import Operator class SEQUENCER_OT_apply_mask_blur(Operator): """Apply blur effect using mask strip.""" bl_idname = "sequencer.apply_mask_blur" bl_label = "Apply Mask Blur" bl_description = "Apply blur effect to video using mask strip" bl_options = {'REGISTER', 'UNDO'} blur_size: IntProperty( name="Blur Size", description="Size of the blur effect in pixels", default=50, min=1, max=500, ) mask_strip_name: StringProperty( name="Mask Strip", description="Name of the mask strip to use", default="", ) @classmethod def poll(cls, context): """Check if operator can run.""" if not context.scene.sequence_editor: return False strip = context.scene.sequence_editor.active_strip if not strip: return False return strip.type in {'MOVIE', 'IMAGE'} def invoke(self, context, event): """Show dialog to select mask strip.""" return context.window_manager.invoke_props_dialog(self) def draw(self, context): """Draw the operator dialog.""" layout = self.layout layout.prop(self, "blur_size") layout.prop_search( self, "mask_strip_name", context.scene.sequence_editor, "strips", text="Mask Strip", ) def execute(self, context): seq_editor = context.scene.sequence_editor video_strip = seq_editor.active_strip # Find mask strip if not self.mask_strip_name: # Try to find auto-generated mask auto_mask_name = f"{video_strip.name}_mask" if auto_mask_name in seq_editor.strips: self.mask_strip_name = auto_mask_name else: self.report({'ERROR'}, "Please select a mask strip") return {'CANCELLED'} mask_strip = seq_editor.strips.get(self.mask_strip_name) if not mask_strip: self.report({'ERROR'}, f"Mask strip not found: {self.mask_strip_name}") return {'CANCELLED'} try: # Use Mask Modifier approach (Blender 5.0 compatible) self._apply_with_mask_modifier(context, video_strip, mask_strip) except Exception as e: self.report({'ERROR'}, f"Failed to apply blur: {e}") return {'CANCELLED'} return {'FINISHED'} def _apply_with_mask_modifier(self, context, video_strip: "bpy.types.Strip", mask_strip: "bpy.types.Strip"): """ Apply blur using Mask Modifier. Workflow: 1. Duplicate the video strip 2. Create Gaussian Blur effect on the duplicate 3. Add Mask modifier to the blur effect (references mask strip) 4. Composite with Alpha Over effect """ seq_editor = context.scene.sequence_editor # Find available channels used_channels = {s.channel for s in seq_editor.strips} duplicate_channel = video_strip.channel + 1 while duplicate_channel in used_channels: duplicate_channel += 1 blur_channel = duplicate_channel + 1 while blur_channel in used_channels: blur_channel += 1 composite_channel = blur_channel + 1 while composite_channel in used_channels: composite_channel += 1 # Step 1: Duplicate the video strip if video_strip.type == 'MOVIE': video_copy = seq_editor.strips.new_movie( name=f"{video_strip.name}_copy", filepath=bpy.path.abspath(video_strip.filepath), channel=duplicate_channel, frame_start=video_strip.frame_final_start, ) elif video_strip.type == 'IMAGE': # For image sequences, duplicate differently video_copy = seq_editor.strips.new_image( name=f"{video_strip.name}_copy", filepath=bpy.path.abspath(video_strip.elements[0].filename) if video_strip.elements else "", channel=duplicate_channel, frame_start=video_strip.frame_final_start, ) # Copy all elements for elem in video_strip.elements[1:]: video_copy.elements.append(elem.filename) else: raise ValueError(f"Unsupported strip type: {video_strip.type}") # Match strip length strip_length = video_strip.frame_final_end - video_strip.frame_final_start video_copy.frame_final_end = video_copy.frame_final_start + strip_length # Step 2: Create Gaussian Blur effect on the duplicate blur_effect = seq_editor.strips.new_effect( name=f"{video_strip.name}_blur", type='GAUSSIAN_BLUR', channel=blur_channel, frame_start=video_strip.frame_final_start, length=strip_length, input1=video_copy, ) # Set blur size (Blender 5.0 API) if hasattr(blur_effect, 'size_x'): blur_effect.size_x = self.blur_size blur_effect.size_y = self.blur_size elif hasattr(blur_effect, 'size'): blur_effect.size = self.blur_size # Step 3: Add Mask modifier to the blur effect mask_mod = blur_effect.modifiers.new( name="FaceMask", type='MASK' ) # Set mask input (Blender 5.0 API) if hasattr(mask_mod, 'input_mask_strip'): mask_mod.input_mask_strip = mask_strip elif hasattr(mask_mod, 'input_mask_id'): mask_mod.input_mask_type = 'STRIP' mask_mod.input_mask_id = mask_strip # Step 4: Composite with Alpha Over effect alpha_over = seq_editor.strips.new_effect( name=f"{video_strip.name}_composite", type='ALPHA_OVER', channel=composite_channel, frame_start=video_strip.frame_final_start, length=strip_length, input1=video_strip, # Base (original) input2=blur_effect, # Blurred with mask ) # Hide intermediate strips (but keep them active) video_copy.mute = True mask_strip.mute = True self.report({'INFO'}, f"Applied blur with Mask Modifier (composite on channel {composite_channel})") def _apply_with_meta_strip(self, context, video_strip: "bpy.types.Strip", mask_strip: "bpy.types.Strip"): """ Fallback method using Meta Strip and effects. This is less elegant but works on all Blender versions. """ seq_editor = context.scene.sequence_editor # Find available channels base_channel = video_strip.channel blur_channel = base_channel + 1 effect_channel = blur_channel + 1 # Ensure mask is in correct position mask_strip.channel = blur_channel mask_strip.frame_start = video_strip.frame_final_start # Create Gaussian Blur effect on the video strip # First, we need to duplicate the video for the blurred version video_copy = seq_editor.strips.new_movie( name=f"{video_strip.name}_blur", filepath=bpy.path.abspath(video_strip.filepath) if hasattr(video_strip, 'filepath') else "", channel=blur_channel, frame_start=video_strip.frame_final_start, ) if video_strip.type == 'MOVIE' else None if video_copy: # Calculate length (Blender 5.0 uses length instead of frame_end) strip_length = video_strip.frame_final_end - video_strip.frame_final_start # Apply Gaussian blur effect (Blender 5.0 API) blur_effect = seq_editor.strips.new_effect( name=f"{video_strip.name}_gaussian", type='GAUSSIAN_BLUR', channel=effect_channel, frame_start=video_strip.frame_final_start, length=strip_length, input1=video_copy, ) # Set blur size (Blender 5.0 uses size property, not size_x/size_y) if hasattr(blur_effect, 'size_x'): blur_effect.size_x = self.blur_size blur_effect.size_y = self.blur_size elif hasattr(blur_effect, 'size'): blur_effect.size = self.blur_size # Create Alpha Over to combine original with blurred (using mask) # Note: Full implementation would require compositing # This is a simplified version self.report({'INFO'}, "Created blur effect (full compositing in development)") else: # For image sequences, different approach needed self.report({'WARNING'}, "Image sequence blur not yet fully implemented") # Registration classes = [ SEQUENCER_OT_apply_mask_blur, ] def register(): for cls in classes: bpy.utils.register_class(cls) def unregister(): for cls in reversed(classes): bpy.utils.unregister_class(cls)