Files
KIMJUNGHOandClaude Sonnet 4.6 94929c6bd0 feat: v2.0.0 — Major refactor and node reorganization
## Nodes
- Unified node key prefix to Util* (UtilSwitch, UtilStringToList, etc.)
- Added UtilSaveFile: combined Save Text & Image node with batch/list support,
  shared counter, subfolder, INPUT_IS_LIST
- Added UtilJsonToList: JSON file/string to STRING list via dot-notation key
- Added UtilEmptyLatent channels param (16=Flux/Lumina/SD3, 4=SD/SDXL)
- Updated UtilLoadImageList: count-based dropdown selectors from Input folder
- Added CNStepControl, CNPreprocessor (canny/depth/lineart/openpose/mlsd)
- Added ImageCropResize, LoRAStepLoader
- Added SBPromptFilter (Claude API style/controlnet filtering)
- Removed layer_nodes.py, sampler_nodes.py (layer separation experiments)
- Removed experiments/ directory

## Dependencies
- Removed controlnet_aux — direct preprocessor implementations in preprocessors/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 10:56:17 +09:00

159 lines
4.6 KiB
Python

"""
Video processing utilities
Frame extraction and video preparation for Qwen3-VL
"""
import cv2
import numpy as np
from pathlib import Path
from typing import List, Union, Optional
import logging
logger = logging.getLogger(__name__)
class VideoProcessor:
"""
Video frame extraction and processing for vision-language models
"""
@staticmethod
def extract_frames(
video_path: Union[str, Path],
fps: float = 1.0,
max_frames: Optional[int] = None
) -> List[np.ndarray]:
"""
Extract frames from video at specified FPS
Args:
video_path: Path to video file
fps: Frames per second to extract (e.g., 1.0 = 1 frame per second)
max_frames: Maximum number of frames to extract (None = no limit)
Returns:
List of frames as numpy arrays (RGB format)
"""
video_path = Path(video_path)
if not video_path.exists():
raise FileNotFoundError(f"Video not found: {video_path}")
logger.info(f"Extracting frames from: {video_path.name}")
logger.info(f"Target FPS: {fps}")
cap = cv2.VideoCapture(str(video_path))
if not cap.isOpened():
raise ValueError(f"Could not open video: {video_path}")
try:
# Get video properties
video_fps = cap.get(cv2.CAP_PROP_FPS)
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
duration = total_frames / video_fps if video_fps > 0 else 0
logger.info(f"Video FPS: {video_fps:.2f}, Total frames: {total_frames}, Duration: {duration:.2f}s")
# Calculate frame interval
frame_interval = int(video_fps / fps) if fps > 0 else 1
frames = []
frame_count = 0
extracted_count = 0
while True:
ret, frame = cap.read()
if not ret:
break
# Extract frame at specified interval
if frame_count % frame_interval == 0:
# Convert BGR to RGB
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
frames.append(frame_rgb)
extracted_count += 1
# Check max frames limit
if max_frames and extracted_count >= max_frames:
logger.info(f"Reached max frames limit: {max_frames}")
break
frame_count += 1
logger.info(f"✓ Extracted {len(frames)} frames")
return frames
finally:
cap.release()
@staticmethod
def get_video_info(video_path: Union[str, Path]) -> dict:
"""
Get video metadata
Args:
video_path: Path to video file
Returns:
Dictionary with video information
"""
video_path = Path(video_path)
if not video_path.exists():
raise FileNotFoundError(f"Video not found: {video_path}")
cap = cv2.VideoCapture(str(video_path))
if not cap.isOpened():
raise ValueError(f"Could not open video: {video_path}")
try:
fps = cap.get(cv2.CAP_PROP_FPS)
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
duration = total_frames / fps if fps > 0 else 0
return {
"fps": fps,
"total_frames": total_frames,
"width": width,
"height": height,
"duration": duration,
"path": str(video_path)
}
finally:
cap.release()
@staticmethod
def validate_video(video_path: Union[str, Path]) -> bool:
"""
Check if video file is valid and readable
Args:
video_path: Path to video file
Returns:
True if valid, False otherwise
"""
try:
video_path = Path(video_path)
if not video_path.exists():
logger.error(f"Video file not found: {video_path}")
return False
cap = cv2.VideoCapture(str(video_path))
is_valid = cap.isOpened()
cap.release()
return is_valid
except Exception as e:
logger.error(f"Error validating video: {e}")
return False