This commit is contained in:
2026-02-12 22:03:02 +09:00
parent f2665a49dd
commit eeb8400727
6 changed files with 382 additions and 38 deletions
+3
View File
@@ -2,13 +2,16 @@
from . import generate_mask
from . import apply_blur
from . import clear_cache
def register():
generate_mask.register()
apply_blur.register()
clear_cache.register()
def unregister():
clear_cache.unregister()
apply_blur.unregister()
generate_mask.unregister()
+126
View File
@@ -0,0 +1,126 @@
"""
Clear Cache Operator.
Provides operators to clear mask cache directories.
"""
import os
import shutil
import bpy
from bpy.types import Operator
from bpy.props import BoolProperty
class SEQUENCER_OT_clear_mask_cache(Operator):
"""Clear mask cache directories."""
bl_idname = "sequencer.clear_mask_cache"
bl_label = "Clear Mask Cache"
bl_description = "Delete cached mask images"
bl_options = {'REGISTER', 'UNDO'}
all_strips: BoolProperty(
name="All Strips",
description="Clear cache for all strips (otherwise only current strip)",
default=False,
)
def execute(self, context):
import tempfile
blend_file = bpy.data.filepath
total_size = 0
cleared_count = 0
if self.all_strips:
# Clear all cache directories
if blend_file:
# Project cache
project_dir = os.path.dirname(blend_file)
cache_root = os.path.join(project_dir, ".mask_cache")
else:
# Temp cache
cache_root = os.path.join(tempfile.gettempdir(), "blender_mask_cache")
if os.path.exists(cache_root):
# Calculate size before deletion
for root, dirs, files in os.walk(cache_root):
for file in files:
file_path = os.path.join(root, file)
try:
total_size += os.path.getsize(file_path)
except OSError:
pass
# Delete cache directory
try:
shutil.rmtree(cache_root)
cleared_count = len(os.listdir(cache_root)) if os.path.exists(cache_root) else 0
self.report({'INFO'}, f"Cleared all cache ({self._format_size(total_size)})")
except Exception as e:
self.report({'ERROR'}, f"Failed to clear cache: {e}")
return {'CANCELLED'}
else:
self.report({'INFO'}, "No cache to clear")
return {'FINISHED'}
else:
# Clear cache for active strip only
seq_editor = context.scene.sequence_editor
if not seq_editor or not seq_editor.active_strip:
self.report({'WARNING'}, "No strip selected")
return {'CANCELLED'}
strip = seq_editor.active_strip
if blend_file:
project_dir = os.path.dirname(blend_file)
cache_dir = os.path.join(project_dir, ".mask_cache", strip.name)
else:
cache_dir = os.path.join(tempfile.gettempdir(), "blender_mask_cache", strip.name)
if os.path.exists(cache_dir):
# Calculate size
for root, dirs, files in os.walk(cache_dir):
for file in files:
file_path = os.path.join(root, file)
try:
total_size += os.path.getsize(file_path)
except OSError:
pass
# Delete
try:
shutil.rmtree(cache_dir)
self.report({'INFO'}, f"Cleared cache for {strip.name} ({self._format_size(total_size)})")
except Exception as e:
self.report({'ERROR'}, f"Failed to clear cache: {e}")
return {'CANCELLED'}
else:
self.report({'INFO'}, f"No cache for {strip.name}")
return {'FINISHED'}
return {'FINISHED'}
def _format_size(self, size_bytes):
"""Format bytes to human-readable size."""
for unit in ['B', 'KB', 'MB', 'GB']:
if size_bytes < 1024.0:
return f"{size_bytes:.1f} {unit}"
size_bytes /= 1024.0
return f"{size_bytes:.1f} TB"
# Registration
classes = [
SEQUENCER_OT_clear_mask_cache,
]
def register():
for cls in classes:
bpy.utils.register_class(cls)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
+10 -30
View File
@@ -7,7 +7,7 @@ from video strips in the Video Sequence Editor.
import os
import bpy
from bpy.props import FloatProperty, IntProperty
from bpy.props import IntProperty
from bpy.types import Operator
from ..core.async_generator import get_generator
@@ -15,37 +15,12 @@ from ..core.async_generator import get_generator
class SEQUENCER_OT_generate_face_mask(Operator):
"""Generate face mask image sequence from video strip."""
bl_idname = "sequencer.generate_face_mask"
bl_label = "Generate Face Mask"
bl_description = "Detect faces and generate mask image sequence"
bl_options = {'REGISTER', 'UNDO'}
# YOLO Detection parameters
conf_threshold: FloatProperty(
name="Confidence",
description="YOLO confidence threshold (higher = fewer false positives)",
default=0.25,
min=0.1,
max=1.0,
)
iou_threshold: FloatProperty(
name="IOU Threshold",
description="Non-maximum suppression IOU threshold",
default=0.45,
min=0.1,
max=1.0,
)
mask_scale: FloatProperty(
name="Mask Scale",
description="Scale factor for mask region (1.0 = exact face size)",
default=1.5,
min=1.0,
max=3.0,
)
@classmethod
def poll(cls, context):
"""Check if operator can run."""
@@ -126,6 +101,11 @@ class SEQUENCER_OT_generate_face_mask(Operator):
wm.mask_progress = 0
wm.mask_total = end_frame - start_frame + 1
# Get parameters from scene properties
conf_threshold = scene.facemask_conf_threshold
iou_threshold = scene.facemask_iou_threshold
mask_scale = scene.facemask_mask_scale
# Start generation
generator.start(
video_path=video_path,
@@ -133,9 +113,9 @@ class SEQUENCER_OT_generate_face_mask(Operator):
start_frame=0, # Frame indices in video
end_frame=end_frame - start_frame,
fps=fps,
conf_threshold=self.conf_threshold,
iou_threshold=self.iou_threshold,
mask_scale=self.mask_scale,
conf_threshold=conf_threshold,
iou_threshold=iou_threshold,
mask_scale=mask_scale,
on_complete=on_complete,
on_progress=on_progress,
)