From 0c9b6466906c8a291917e4217df40147d529f2ee Mon Sep 17 00:00:00 2001 From: Hare Date: Fri, 6 Feb 2026 21:22:21 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20blur=E9=81=A9=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/compositor_setup.py | 19 ++++-- operators/apply_blur.py | 123 ++++++++++++++++++++++++++++++++------- 2 files changed, 116 insertions(+), 26 deletions(-) diff --git a/core/compositor_setup.py b/core/compositor_setup.py index 687f7ed..6bfdfc5 100644 --- a/core/compositor_setup.py +++ b/core/compositor_setup.py @@ -132,17 +132,24 @@ def setup_strip_compositor_modifier( name="FaceMaskBlur", type='COMPOSITOR', ) - - # Set the node tree - modifier.node_tree = node_tree - + + # Set the node tree/group (Blender 5.0 uses node_group instead of node_tree) + if hasattr(modifier, 'node_group'): + # Blender 5.0+ + modifier.node_group = node_tree + elif hasattr(modifier, 'node_tree'): + # Blender 4.x + modifier.node_tree = node_tree + else: + raise AttributeError("Compositor modifier has neither 'node_group' nor 'node_tree' attribute") + # Configure input mapping # The modifier automatically maps strip image to first input # We need to configure the mask input - + # TODO: Blender 5.0 may have different API for this # This is a placeholder for the actual implementation - + return modifier diff --git a/operators/apply_blur.py b/operators/apply_blur.py index bb68e76..a3f891b 100644 --- a/operators/apply_blur.py +++ b/operators/apply_blur.py @@ -9,8 +9,6 @@ import bpy from bpy.props import FloatProperty, IntProperty, StringProperty from bpy.types import Operator -from ..core.compositor_setup import get_or_create_blur_node_tree, setup_strip_compositor_modifier - class SEQUENCER_OT_apply_mask_blur(Operator): """Apply blur effect using mask strip.""" @@ -63,7 +61,7 @@ class SEQUENCER_OT_apply_mask_blur(Operator): 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 @@ -73,34 +71,119 @@ class SEQUENCER_OT_apply_mask_blur(Operator): 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: - # Try using Compositor Modifier (preferred method) - self._apply_with_modifier(context, video_strip, mask_strip) + # 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_modifier(self, context, video_strip: "bpy.types.Strip", mask_strip: "bpy.types.Strip"): - """Apply blur using Compositor Modifier.""" - # Get or create the blur node tree - node_tree = get_or_create_blur_node_tree(blur_size=self.blur_size) - - # Add compositor modifier to the video strip - modifier = setup_strip_compositor_modifier( - strip=video_strip, - mask_strip=mask_strip, - node_tree=node_tree, + 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, ) - - self.report({'INFO'}, f"Applied blur with Compositor Modifier") + + # 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"): """