Files
diodiogod-TTS-Audio-Suite/engines/video/utils/preview_creator.py
diodiogod bca3fa73fd Implement OpenSeeFace provider with sophisticated MAR calculation and rename node
Major Features:
- Complete OpenSeeFace integration with bundled components and model downloading
- Sophisticated multi-point MAR calculation using both inner and outer mouth landmarks
- Unified preview video creation system for all providers
- Node renamed from "🎥 Mouth Movement Analyzer" to "🗣️ Silent Speech Analyzer"

OpenSeeFace Implementation:
- Handle 3D landmarks (X,Y,Z) with coordinate transformation for 68-point dlib model
- Fallback logic for missing inner mouth landmarks in bundled model
- Adaptive MAR calculation: outer mouth for stability, inner mouth for precision
- Enhanced threshold mapping (0.2-0.8 range) for better speech detection
- Automatic model downloading integrated with existing F5-TTS infrastructure

Preview System Enhancements:
- Unified PreviewVideoCreator utility for consistent preview generation
- Fast WebM encoding with optimized settings (CRF 45, speed 8)
- Full frame rate capture (removed frame skipping) for smoother previews
- Proper coordinate visualization for OpenSeeFace landmarks

UI/UX Improvements:
- Updated tooltips to mark OpenSeeFace as EXPERIMENTAL
- Enhanced provider descriptions with clear recommendations
- Better error handling and user feedback

Technical Fixes:
- Fix OpenSeeFace tracker RetinaFace conditional initialization
- Handle bundled vs downloaded model scenarios
- Proper SRT formatting for viseme sequences (fix TypeError with nested lists)
- Robust landmark attribute detection across different OpenSeeFace versions
2025-08-15 23:30:00 -03:00

149 lines
5.9 KiB
Python

"""
Unified Preview Video Creation Utility
Handles preview video generation for all video analysis providers
"""
import os
import cv2
import time
import logging
from typing import List, Optional
import numpy as np
logger = logging.getLogger(__name__)
class PreviewVideoCreator:
"""
Unified preview video creation utility for all video analysis providers.
Uses the same proven logic as MediaPipe for consistency and reliability.
"""
@staticmethod
def create_preview_video(frames: List[np.ndarray], fps: float, width: int, height: int, provider_name: str = "Unknown") -> Optional[str]:
"""
Create preview video with annotations using proven MediaPipe logic
Args:
frames: List of annotated video frames
fps: Video frame rate
width: Video width
height: Video height
provider_name: Name of the provider creating the preview
Returns:
Path to created preview video file, or None if creation failed
"""
try:
if not frames:
logger.warning("No frames available for preview video creation")
return None
logger.info(f"Creating {provider_name} preview video with {len(frames)} frames, {width}x{height} at {fps} FPS")
# Create output path in ComfyUI output directory like Save Video does
import folder_paths
output_dir = folder_paths.get_output_directory()
os.makedirs(output_dir, exist_ok=True)
# Generate unique filename
timestamp = int(time.time())
filename_mp4 = f"mouth_preview_{provider_name.lower()}_{timestamp}.mp4"
filename_webp = f"mouth_preview_{provider_name.lower()}_{timestamp}.webp"
output_path_mp4 = os.path.join(output_dir, filename_mp4)
output_path_webp = os.path.join(output_dir, filename_webp)
# Create MP4 video
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path_mp4, fourcc, fps, (width, height))
if not out.isOpened():
logger.error(f"Failed to open video writer for {output_path_mp4}")
return None
frames_written = 0
for i, frame in enumerate(frames):
try:
# Ensure frame dimensions match
if frame.shape[:2] != (height, width):
frame = cv2.resize(frame, (width, height))
out.write(frame)
frames_written += 1
except Exception as e:
logger.warning(f"Failed to write frame {i}: {e}")
out.release()
logger.info(f"Successfully wrote {frames_written}/{len(frames)} frames to MP4")
# Create WEBM video for native ComfyUI display (like SaveWEBM - better performance)
webm_path = PreviewVideoCreator._create_webm_preview(frames, fps, width, height, output_path_webp)
if webm_path and os.path.exists(webm_path):
logger.info(f"Created {provider_name} WebM preview: {webm_path}")
# Clean up MP4 if WebM was successful
try:
os.unlink(output_path_mp4)
return webm_path
except:
pass
logger.info(f"Created {provider_name} MP4 preview: {output_path_mp4}")
return output_path_mp4
except Exception as e:
logger.error(f"Failed to create {provider_name} preview video: {e}")
import traceback
traceback.print_exc()
return None
@staticmethod
def _create_webm_preview(frames: List[np.ndarray], fps: float, width: int, height: int, output_path_webp: str) -> Optional[str]:
"""
Create WebM preview using av library (like SaveWEBM) - optimized for fast preview
"""
try:
import av
from fractions import Fraction
# Convert BGR frames to RGB
rgb_frames = []
for frame in frames:
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
rgb_frames.append(rgb_frame)
# Create WEBM using av library like SaveWEBM does - optimized for fast preview
webm_path = output_path_webp.replace('.webp', '.webm')
container = av.open(webm_path, mode="w")
stream = container.add_stream("libvpx-vp9", rate=Fraction(round(fps * 1000), 1000))
stream.width = width
stream.height = height
stream.pix_fmt = "yuv420p"
# Fast encoding options for preview (lower quality but much faster)
stream.options = {
"crf": "45", # Higher CRF = lower quality but faster encoding
"speed": "8", # Fastest encoding speed
"cpu-used": "8" # Maximum CPU efficiency mode
}
for rgb_frame in rgb_frames:
av_frame = av.VideoFrame.from_ndarray(rgb_frame, format="rgb24")
for packet in stream.encode(av_frame):
container.mux(packet)
# Flush encoder
for packet in stream.encode():
container.mux(packet)
container.close()
if os.path.exists(webm_path) and os.path.getsize(webm_path) > 1000: # Sanity check
return webm_path
else:
logger.warning("WebM creation failed or file too small")
return None
except Exception as e:
logger.warning(f"WebM creation failed: {e}")
return None