176 lines
6.0 KiB
Python
176 lines
6.0 KiB
Python
"""
|
|
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
|
|
|
|
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."""
|
|
|
|
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:
|
|
# Try using Compositor Modifier (preferred method)
|
|
self._apply_with_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,
|
|
)
|
|
|
|
self.report({'INFO'}, f"Applied blur with Compositor Modifier")
|
|
|
|
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)
|