252 lines
8.9 KiB
Python
252 lines
8.9 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
|
|
|
|
|
|
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,
|
|
)
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
"""Check if operator can run."""
|
|
if not context.scene.sequence_editor:
|
|
return False
|
|
|
|
seq_editor = context.scene.sequence_editor
|
|
strip = seq_editor.active_strip
|
|
if not strip:
|
|
return False
|
|
|
|
if strip.type not in {'MOVIE', 'IMAGE'}:
|
|
return False
|
|
|
|
# Check if corresponding mask strip exists
|
|
mask_name = f"{strip.name}_mask"
|
|
return mask_name in seq_editor.strips
|
|
|
|
def execute(self, context):
|
|
seq_editor = context.scene.sequence_editor
|
|
video_strip = seq_editor.active_strip
|
|
|
|
# Auto-detect mask strip
|
|
mask_name = f"{video_strip.name}_mask"
|
|
mask_strip = seq_editor.strips.get(mask_name)
|
|
|
|
if not mask_strip:
|
|
self.report({'ERROR'}, f"Mask strip not found: {mask_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, grouped in a Meta Strip.
|
|
|
|
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. Group all into a Meta Strip
|
|
|
|
The blur effect with mask will automatically composite over the original
|
|
video due to VSE's channel layering system.
|
|
"""
|
|
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
|
|
|
|
# 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
|
|
|
|
# Hide the mask strip (but keep it active for the modifier)
|
|
mask_strip.mute = True
|
|
|
|
# Step 4: Create Meta Strip to group everything
|
|
# Deselect all first
|
|
for strip in seq_editor.strips:
|
|
strip.select = False
|
|
|
|
# Select the strips to group
|
|
video_copy.select = True
|
|
blur_effect.select = True
|
|
mask_strip.select = True
|
|
|
|
# Set active strip for context
|
|
seq_editor.active_strip = blur_effect
|
|
|
|
# Create meta strip using operator
|
|
bpy.ops.sequencer.meta_make()
|
|
|
|
# Find the newly created meta strip (it will be selected)
|
|
meta_strip = None
|
|
for strip in seq_editor.strips:
|
|
if strip.select and strip.type == 'META':
|
|
meta_strip = strip
|
|
break
|
|
|
|
if meta_strip:
|
|
meta_strip.name = f"{video_strip.name}_blurred_meta"
|
|
self.report({'INFO'}, f"Applied blur with Mask Modifier (grouped in Meta Strip)")
|
|
else:
|
|
self.report({'INFO'}, f"Applied blur with Mask Modifier (blur on channel {blur_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)
|