fix: 修复命令行工具
This commit is contained in:
15
python_core/scene_detection/workflows/__init__.py
Normal file
15
python_core/scene_detection/workflows/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Scene Detection Workflows
|
||||
场景检测工作流
|
||||
|
||||
导出所有工作流类
|
||||
"""
|
||||
|
||||
from .workflow_manager import SceneDetectionWorkflowManager
|
||||
from .workflow_nodes import WorkflowNodes
|
||||
|
||||
__all__ = [
|
||||
"SceneDetectionWorkflowManager",
|
||||
"WorkflowNodes"
|
||||
]
|
||||
176
python_core/scene_detection/workflows/workflow_manager.py
Normal file
176
python_core/scene_detection/workflows/workflow_manager.py
Normal file
@@ -0,0 +1,176 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Workflow Manager
|
||||
工作流管理器
|
||||
"""
|
||||
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, Optional, Literal
|
||||
|
||||
from python_core.utils.logger import logger
|
||||
from python_core.utils.jsonrpc_enhanced import EnhancedJSONRPCResponse
|
||||
from ..types import SceneDetectionWorkflowState, DetectorType, OutputFormat
|
||||
from .workflow_nodes import WorkflowNodes
|
||||
|
||||
|
||||
class SceneDetectionWorkflowManager:
|
||||
"""场景检测工作流管理器"""
|
||||
|
||||
def __init__(self):
|
||||
self.nodes = WorkflowNodes()
|
||||
self.workflow = None
|
||||
|
||||
def create_detection_workflow(self):
|
||||
"""创建检测工作流"""
|
||||
try:
|
||||
from langgraph.graph import StateGraph, END
|
||||
|
||||
# 创建状态图
|
||||
workflow = StateGraph(SceneDetectionWorkflowState)
|
||||
|
||||
# 添加节点
|
||||
workflow.add_node("validate", self.nodes.validate_input)
|
||||
workflow.add_node("extract_info", self.nodes.extract_video_info)
|
||||
workflow.add_node("detect", self.nodes.detect_scenes)
|
||||
workflow.add_node("analyze", self.nodes.analyze_with_ai)
|
||||
workflow.add_node("finalize", self.nodes.finalize_results)
|
||||
workflow.add_node("error", self.nodes.handle_error)
|
||||
|
||||
# 设置入口点
|
||||
workflow.set_entry_point("validate")
|
||||
|
||||
# 添加条件边
|
||||
workflow.add_conditional_edges(
|
||||
"validate",
|
||||
self._route_next_step,
|
||||
{
|
||||
"extract_info": "extract_info",
|
||||
"error": "error"
|
||||
}
|
||||
)
|
||||
|
||||
workflow.add_conditional_edges(
|
||||
"extract_info",
|
||||
self._route_next_step,
|
||||
{
|
||||
"detect": "detect",
|
||||
"error": "error"
|
||||
}
|
||||
)
|
||||
|
||||
workflow.add_conditional_edges(
|
||||
"detect",
|
||||
self._route_next_step,
|
||||
{
|
||||
"analyze": "analyze",
|
||||
"error": "error"
|
||||
}
|
||||
)
|
||||
|
||||
workflow.add_conditional_edges(
|
||||
"analyze",
|
||||
self._route_next_step,
|
||||
{
|
||||
"finalize": "finalize",
|
||||
"error": "error"
|
||||
}
|
||||
)
|
||||
|
||||
# 结束节点
|
||||
workflow.add_edge("finalize", END)
|
||||
workflow.add_edge("error", END)
|
||||
|
||||
# 编译工作流
|
||||
self.workflow = workflow.compile()
|
||||
return self.workflow
|
||||
|
||||
except ImportError:
|
||||
logger.error("❌ LangGraph未安装,无法创建工作流")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 创建工作流失败: {e}")
|
||||
return None
|
||||
|
||||
def _route_next_step(self, state: SceneDetectionWorkflowState) -> Literal["extract_info", "detect", "analyze", "finalize", "error"]:
|
||||
"""路由下一步"""
|
||||
if state.errors:
|
||||
return "error"
|
||||
elif state.current_stage == "validated":
|
||||
return "extract_info"
|
||||
elif state.current_stage == "info_extracted":
|
||||
return "detect"
|
||||
elif state.current_stage == "scenes_detected":
|
||||
return "analyze"
|
||||
elif state.current_stage in ["ai_analyzed", "analysis_skipped", "analysis_failed"]:
|
||||
return "finalize"
|
||||
else:
|
||||
return "error"
|
||||
|
||||
def detect_with_workflow(self, video_path: Path, detector_type: DetectorType = DetectorType.CONTENT,
|
||||
threshold: float = 30.0, min_scene_length: float = 1.0,
|
||||
output_path: Optional[Path] = None, output_format: OutputFormat = OutputFormat.JSON,
|
||||
enable_ai_analysis: bool = True, request_id: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""使用LangGraph工作流进行场景检测"""
|
||||
|
||||
# 创建工作流
|
||||
workflow = self.create_detection_workflow()
|
||||
if not workflow:
|
||||
raise Exception("无法创建工作流")
|
||||
|
||||
# 初始化状态
|
||||
initial_state = SceneDetectionWorkflowState(
|
||||
video_path=str(video_path),
|
||||
detector_type=detector_type.value,
|
||||
threshold=threshold,
|
||||
min_scene_length=min_scene_length,
|
||||
output_path=str(output_path) if output_path else None,
|
||||
output_format=output_format.value,
|
||||
enable_ai_analysis=enable_ai_analysis,
|
||||
request_id=request_id,
|
||||
enable_jsonrpc=request_id is not None # 如果有request_id就启用JSON-RPC
|
||||
)
|
||||
|
||||
# 执行工作流
|
||||
config = {"configurable": {"thread_id": f"detection_{int(time.time())}"}}
|
||||
|
||||
try:
|
||||
final_state = workflow.invoke(initial_state, config)
|
||||
|
||||
# 如果是JSON-RPC模式,结果已经通过JSON-RPC发送了
|
||||
if request_id is not None:
|
||||
# JSON-RPC模式:结果已经在工作流节点中发送
|
||||
# 这里返回简化的状态信息供内部使用
|
||||
return {
|
||||
"jsonrpc_mode": True,
|
||||
"request_id": request_id,
|
||||
"workflow_state": final_state.get("current_stage"),
|
||||
"final_result_sent": True
|
||||
}
|
||||
else:
|
||||
# 非JSON-RPC模式:返回完整结果
|
||||
return {
|
||||
"detection_result": final_state.get("detection_result"),
|
||||
"ai_analysis": final_state.get("ai_analysis"),
|
||||
"video_info": final_state.get("video_info"),
|
||||
"workflow_state": final_state.get("current_stage"),
|
||||
"errors": final_state.get("errors", [])
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"工作流执行失败: {e}"
|
||||
|
||||
# 如果是JSON-RPC模式,发送错误响应
|
||||
if request_id is not None:
|
||||
handler = EnhancedJSONRPCResponse(request_id)
|
||||
handler.error(-32603, error_msg, {"exception": str(e)})
|
||||
return {
|
||||
"jsonrpc_mode": True,
|
||||
"request_id": request_id,
|
||||
"error_sent": True,
|
||||
"error": error_msg
|
||||
}
|
||||
else:
|
||||
# 非JSON-RPC模式:抛出异常
|
||||
logger.error(f"❌ {error_msg}")
|
||||
raise
|
||||
242
python_core/scene_detection/workflows/workflow_nodes.py
Normal file
242
python_core/scene_detection/workflows/workflow_nodes.py
Normal file
@@ -0,0 +1,242 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Workflow Nodes
|
||||
工作流节点定义
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any
|
||||
from dataclasses import asdict
|
||||
|
||||
from python_core.utils.logger import logger
|
||||
from python_core.utils.jsonrpc_enhanced import ProgressLevel
|
||||
from ..types import SceneDetectionWorkflowState, DetectorType
|
||||
from ..services import SceneDetectorService, VideoInfoService, AIAnalysisService
|
||||
|
||||
|
||||
class WorkflowNodes:
|
||||
"""工作流节点集合"""
|
||||
|
||||
def __init__(self):
|
||||
self.detector_service = SceneDetectorService()
|
||||
self.video_info_service = VideoInfoService()
|
||||
self.ai_analysis_service = AIAnalysisService()
|
||||
|
||||
def validate_input(self, state: SceneDetectionWorkflowState) -> Dict[str, Any]:
|
||||
"""验证输入参数"""
|
||||
state.send_progress("validate", "🔍 验证输入参数...", ProgressLevel.INFO)
|
||||
|
||||
video_path = Path(state.video_path)
|
||||
errors = []
|
||||
|
||||
# 验证文件存在
|
||||
if not video_path.exists():
|
||||
errors.append(f"视频文件不存在: {video_path}")
|
||||
state.send_progress("validate", f"❌ 文件不存在: {video_path}", ProgressLevel.ERROR)
|
||||
|
||||
# 验证文件格式
|
||||
if video_path.suffix.lower() not in self.detector_service.supported_formats:
|
||||
errors.append(f"不支持的文件格式: {video_path.suffix}")
|
||||
state.send_progress("validate", f"❌ 不支持的格式: {video_path.suffix}", ProgressLevel.ERROR)
|
||||
|
||||
# 验证参数范围
|
||||
if not (0 <= state.threshold <= 100):
|
||||
errors.append(f"阈值超出范围 (0-100): {state.threshold}")
|
||||
state.send_progress("validate", f"❌ 阈值超出范围: {state.threshold}", ProgressLevel.ERROR)
|
||||
|
||||
if state.min_scene_length < 0:
|
||||
errors.append(f"最小场景长度不能为负数: {state.min_scene_length}")
|
||||
state.send_progress("validate", f"❌ 最小场景长度无效: {state.min_scene_length}", ProgressLevel.ERROR)
|
||||
|
||||
if not errors:
|
||||
state.send_progress("validate", "✅ 输入参数验证通过", ProgressLevel.SUCCESS)
|
||||
|
||||
return {
|
||||
"current_stage": "validated" if not errors else "error",
|
||||
"progress": 1,
|
||||
"errors": errors
|
||||
}
|
||||
|
||||
def extract_video_info(self, state: SceneDetectionWorkflowState) -> Dict[str, Any]:
|
||||
"""提取视频信息"""
|
||||
state.send_progress("extract_info", "📊 提取视频信息...", ProgressLevel.INFO)
|
||||
|
||||
try:
|
||||
video_info = self.video_info_service.extract_video_info(Path(state.video_path))
|
||||
|
||||
state.send_progress("extract_info",
|
||||
f"📹 视频信息: {video_info['resolution']}, {video_info['fps']:.2f}fps, {video_info['duration']:.2f}s",
|
||||
ProgressLevel.SUCCESS,
|
||||
{"video_info": video_info}
|
||||
)
|
||||
|
||||
return {
|
||||
"current_stage": "info_extracted",
|
||||
"progress": 2,
|
||||
"video_info": video_info
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"提取视频信息失败: {e}"
|
||||
state.send_progress("extract_info", f"❌ {error_msg}", ProgressLevel.ERROR)
|
||||
return {
|
||||
"current_stage": "error",
|
||||
"errors": state.errors + [error_msg]
|
||||
}
|
||||
|
||||
def detect_scenes(self, state: SceneDetectionWorkflowState) -> Dict[str, Any]:
|
||||
"""执行场景检测"""
|
||||
state.send_progress("detect", "🎯 执行场景检测...", ProgressLevel.INFO)
|
||||
|
||||
try:
|
||||
result = self.detector_service.detect_scenes(
|
||||
Path(state.video_path),
|
||||
DetectorType(state.detector_type),
|
||||
state.threshold,
|
||||
state.min_scene_length
|
||||
)
|
||||
|
||||
state.send_progress("detect",
|
||||
f"✅ 检测完成: {result.total_scenes} 个场景,耗时 {result.detection_time:.2f}秒",
|
||||
ProgressLevel.SUCCESS,
|
||||
{
|
||||
"total_scenes": result.total_scenes,
|
||||
"detection_time": result.detection_time,
|
||||
"total_duration": result.total_duration
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"current_stage": "scenes_detected",
|
||||
"progress": 3,
|
||||
"detection_result": result,
|
||||
"processed_scenes": result.scenes
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"场景检测失败: {e}"
|
||||
state.send_progress("detect", f"❌ {error_msg}", ProgressLevel.ERROR)
|
||||
return {
|
||||
"current_stage": "error",
|
||||
"errors": state.errors + [error_msg]
|
||||
}
|
||||
|
||||
def analyze_with_ai(self, state: SceneDetectionWorkflowState) -> Dict[str, Any]:
|
||||
"""AI分析场景结果"""
|
||||
if not self.ai_analysis_service.ai_enabled or not state.enable_ai_analysis:
|
||||
state.send_progress("analyze", "⚠️ AI分析已禁用,跳过此步骤", ProgressLevel.WARNING)
|
||||
return {
|
||||
"current_stage": "analysis_skipped",
|
||||
"progress": 4,
|
||||
"ai_analysis": "AI分析已禁用"
|
||||
}
|
||||
|
||||
state.send_progress("analyze", "🧠 AI分析场景结果...", ProgressLevel.INFO)
|
||||
|
||||
try:
|
||||
state.send_progress("analyze", "🤖 正在调用AI分析服务...", ProgressLevel.INFO)
|
||||
analysis = self.ai_analysis_service.analyze_detection_result(
|
||||
state.detection_result,
|
||||
state.video_info
|
||||
)
|
||||
|
||||
state.send_progress("analyze", "✅ AI分析完成", ProgressLevel.SUCCESS,
|
||||
{"analysis_length": len(analysis)})
|
||||
|
||||
return {
|
||||
"current_stage": "ai_analyzed",
|
||||
"progress": 4,
|
||||
"ai_analysis": analysis
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"AI分析失败: {e}"
|
||||
state.send_progress("analyze", f"⚠️ {error_msg}", ProgressLevel.WARNING)
|
||||
return {
|
||||
"current_stage": "analysis_failed",
|
||||
"progress": 4,
|
||||
"ai_analysis": error_msg
|
||||
}
|
||||
|
||||
def finalize_results(self, state: SceneDetectionWorkflowState) -> Dict[str, Any]:
|
||||
"""整理最终结果"""
|
||||
state.send_progress("finalize", "📋 整理最终结果...", ProgressLevel.INFO)
|
||||
|
||||
# 保存结果(如果指定了输出路径)
|
||||
if state.output_path and state.detection_result:
|
||||
try:
|
||||
from ..utils import ResultSaver
|
||||
from ..types import OutputFormat
|
||||
|
||||
output_path = Path(state.output_path)
|
||||
output_format = OutputFormat(state.output_format)
|
||||
|
||||
saver = ResultSaver()
|
||||
saver.save_results(state.detection_result, output_path, output_format)
|
||||
state.send_progress("finalize", f"💾 结果已保存到: {output_path}", ProgressLevel.SUCCESS)
|
||||
except Exception as e:
|
||||
state.send_progress("finalize", f"⚠️ 保存结果失败: {e}", ProgressLevel.WARNING)
|
||||
|
||||
# 准备最终结果
|
||||
final_result = {
|
||||
"success": True,
|
||||
"workflow_state": "completed",
|
||||
"detection_result": None,
|
||||
"ai_analysis": state.ai_analysis,
|
||||
"video_info": state.video_info,
|
||||
"errors": state.errors or []
|
||||
}
|
||||
|
||||
# 序列化检测结果
|
||||
if state.detection_result:
|
||||
final_result["detection_result"] = {
|
||||
"success": state.detection_result.success,
|
||||
"filename": state.detection_result.filename,
|
||||
"detector_type": state.detection_result.detector_type,
|
||||
"threshold": state.detection_result.threshold,
|
||||
"total_scenes": state.detection_result.total_scenes,
|
||||
"total_duration": state.detection_result.total_duration,
|
||||
"detection_time": state.detection_result.detection_time,
|
||||
"scenes": [asdict(scene) for scene in state.detection_result.scenes],
|
||||
"error": state.detection_result.error
|
||||
}
|
||||
|
||||
state.send_progress("finalize", "🎉 工作流执行完成", ProgressLevel.SUCCESS, {
|
||||
"total_scenes": state.detection_result.total_scenes if state.detection_result else 0,
|
||||
"workflow_stage": "completed"
|
||||
})
|
||||
|
||||
# 发送最终结果到JSON-RPC客户端
|
||||
state.send_final_result(final_result)
|
||||
|
||||
return {
|
||||
"current_stage": "completed",
|
||||
"progress": 5,
|
||||
"final_result": final_result
|
||||
}
|
||||
|
||||
def handle_error(self, state: SceneDetectionWorkflowState) -> Dict[str, Any]:
|
||||
"""处理错误"""
|
||||
error_msg = "; ".join(state.errors) if state.errors else "未知错误"
|
||||
|
||||
# 发送错误进度
|
||||
state.send_progress("error", f"❌ 工作流错误: {error_msg}", ProgressLevel.ERROR)
|
||||
|
||||
# 准备错误结果
|
||||
error_result = {
|
||||
"success": False,
|
||||
"workflow_state": "failed",
|
||||
"error": error_msg,
|
||||
"errors": state.errors or [],
|
||||
"detection_result": None,
|
||||
"ai_analysis": None,
|
||||
"video_info": state.video_info
|
||||
}
|
||||
|
||||
# 发送错误结果到JSON-RPC客户端
|
||||
state.send_error_result(-32603, f"工作流执行失败: {error_msg}", error_result)
|
||||
|
||||
return {
|
||||
"current_stage": "failed",
|
||||
"error_result": error_result
|
||||
}
|
||||
Reference in New Issue
Block a user